From 443333d8c059c87db408ec2d11685db00031b30a Mon Sep 17 00:00:00 2001 From: Tpt Date: Fri, 28 Apr 2023 21:34:42 +0200 Subject: [PATCH] Makes directory smaller by removing files not used by Oxigraph --- .circleci/config.yml | 892 -- .circleci/ubsan_suppression_list.txt | 6 - .github/workflows/sanity_check.yml | 47 - build_tools/amalgamate.py | 168 - build_tools/benchmark_log_tool.py | 238 - build_tools/build_detect_platform | 900 -- build_tools/check-sources.sh | 48 - build_tools/dependencies_platform010.sh | 22 - build_tools/dockerbuild.sh | 3 - build_tools/error_filter.py | 181 - build_tools/fb_compile_mongo.sh | 55 - build_tools/fbcode_config.sh | 175 - build_tools/fbcode_config_platform010.sh | 175 - build_tools/format-diff.sh | 203 - build_tools/gnu_parallel | 7971 -------------- build_tools/make_package.sh | 129 - build_tools/ps_with_stack | 38 - build_tools/regression_build_test.sh | 396 - build_tools/run_ci_db_test.ps1 | 493 - build_tools/setup_centos7.sh | 45 - build_tools/ubuntu20_image/Dockerfile | 57 - build_tools/update_dependencies.sh | 106 - build_tools/version.sh | 23 - cache/cache_reservation_manager_test.cc | 469 - cache/cache_test.cc | 969 -- cache/compressed_secondary_cache_test.cc | 980 -- cache/lru_cache_test.cc | 2558 ----- coverage/coverage_test.sh | 82 - coverage/parse_gcov_output.py | 128 - db/column_family_test.cc | 3382 ------ db/compact_files_test.cc | 491 - db/comparator_db_test.cc | 678 -- db/corruption_test.cc | 1669 --- db/cuckoo_table_db_test.cc | 351 - db/db_basic_test.cc | 4777 --------- db/db_block_cache_test.cc | 1969 ---- db/db_bloom_filter_test.cc | 3473 ------- db/db_compaction_filter_test.cc | 1030 -- db/db_compaction_test.cc | 9118 ----------------- db/db_dynamic_level_test.cc | 499 - db/db_encryption_test.cc | 126 - db/db_flush_test.cc | 3202 ------ db/db_inplace_update_test.cc | 262 - db/db_io_failure_test.cc | 589 -- db/db_iter_stress_test.cc | 658 -- db/db_iter_test.cc | 3195 ------ db/db_iterator_test.cc | 3253 ------ db/db_kv_checksum_test.cc | 885 -- db/db_log_iter_test.cc | 297 - db/db_logical_block_size_cache_test.cc | 505 - db/db_memtable_test.cc | 344 - db/db_merge_operand_test.cc | 488 - db/db_merge_operator_test.cc | 824 -- db/db_options_test.cc | 1215 --- db/db_properties_test.cc | 2376 ----- db/db_range_del_test.cc | 3414 ------ db/db_rate_limiter_test.cc | 436 - db/db_readonly_with_timestamp_test.cc | 956 -- db/db_secondary_test.cc | 1691 --- db/db_sst_test.cc | 1864 ---- db/db_statistics_test.cc | 213 - db/db_table_properties_test.cc | 623 -- db/db_tailing_iter_test.cc | 595 -- db/db_test.cc | 7338 ------------- db/db_universal_compaction_test.cc | 2227 ---- db/db_wal_test.cc | 2408 ----- db/db_with_timestamp_basic_test.cc | 3928 ------- db/db_with_timestamp_compaction_test.cc | 353 - db/db_write_buffer_manager_test.cc | 860 -- db/db_write_test.cc | 790 -- db/dbformat_test.cc | 214 - db/deletefile_test.cc | 603 -- db/error_handler_fs_test.cc | 2862 ------ db/external_sst_file_basic_test.cc | 1999 ---- db/external_sst_file_test.cc | 2860 ------ db/fault_injection_test.cc | 637 -- db/file_indexer_test.cc | 352 - db/filename_test.cc | 241 - db/flush_job_test.cc | 743 -- db/import_column_family_test.cc | 746 -- db/listener_test.cc | 1598 --- db/log_test.cc | 1095 -- db/manual_compaction_test.cc | 308 - db/memtable_list_test.cc | 1037 -- db/merge_helper_test.cc | 298 - db/merge_test.cc | 621 -- db/obsolete_files_test.cc | 317 - db/options_file_test.cc | 110 - db/perf_context_test.cc | 1157 --- db/periodic_task_scheduler_test.cc | 229 - db/plain_table_db_test.cc | 1347 --- db/prefix_test.cc | 894 -- db/range_del_aggregator_test.cc | 713 -- db/range_tombstone_fragmenter_test.cc | 555 - db/repair_test.cc | 484 - db/seqno_time_test.cc | 994 -- db/table_properties_collector_test.cc | 509 - db/version_builder_test.cc | 1820 ---- db/version_edit_test.cc | 732 -- db/version_set_test.cc | 3619 ------- db/wal_edit_test.cc | 213 - db/wal_manager_test.cc | 336 - db/write_batch_test.cc | 1112 -- db/write_callback_test.cc | 454 - db/write_controller_test.cc | 248 - db_stress_tool/CMakeLists.txt | 17 - db_stress_tool/batched_ops_stress.cc | 501 - db_stress_tool/cf_consistency_stress.cc | 769 -- db_stress_tool/db_stress.cc | 25 - db_stress_tool/db_stress_common.cc | 491 - db_stress_tool/db_stress_common.h | 670 -- db_stress_tool/db_stress_compaction_filter.h | 96 - db_stress_tool/db_stress_driver.cc | 211 - db_stress_tool/db_stress_driver.h | 18 - db_stress_tool/db_stress_env_wrapper.h | 44 - db_stress_tool/db_stress_gflags.cc | 1081 -- db_stress_tool/db_stress_listener.cc | 189 - db_stress_tool/db_stress_listener.h | 269 - db_stress_tool/db_stress_shared_state.cc | 17 - db_stress_tool/db_stress_shared_state.h | 427 - db_stress_tool/db_stress_stat.cc | 17 - db_stress_tool/db_stress_stat.h | 219 - .../db_stress_table_properties_collector.h | 65 - db_stress_tool/db_stress_test_base.cc | 3282 ------ db_stress_tool/db_stress_test_base.h | 322 - db_stress_tool/db_stress_tool.cc | 366 - db_stress_tool/expected_state.cc | 745 -- db_stress_tool/expected_state.h | 287 - db_stress_tool/multi_ops_txns_stress.cc | 1753 ---- db_stress_tool/multi_ops_txns_stress.h | 440 - db_stress_tool/no_batched_ops_stress.cc | 1676 --- docs/.gitignore | 8 - docs/CNAME | 1 - docs/CONTRIBUTING.md | 115 - docs/Gemfile | 4 - docs/LICENSE-DOCUMENTATION | 385 - docs/README.md | 80 - docs/TEMPLATE-INFORMATION.md | 17 - docs/_config.yml | 85 - docs/_data/authors.yml | 81 - docs/_data/features.yml | 19 - docs/_data/nav.yml | 30 - docs/_data/nav_docs.yml | 3 - docs/_data/powered_by.yml | 1 - docs/_data/powered_by_highlight.yml | 1 - docs/_data/promo.yml | 6 - docs/_docs/faq.md | 48 - docs/_docs/getting-started.md | 78 - docs/_includes/blog_pagination.html | 28 - docs/_includes/content/gridblocks.html | 5 - docs/_includes/content/items/gridblock.html | 37 - docs/_includes/doc.html | 25 - docs/_includes/doc_paging.html | 0 docs/_includes/footer.html | 34 - docs/_includes/head.html | 23 - docs/_includes/header.html | 19 - docs/_includes/hero.html | 0 docs/_includes/home_header.html | 22 - docs/_includes/katex_import.html | 3 - docs/_includes/katex_render.html | 210 - docs/_includes/nav.html | 37 - docs/_includes/nav/collection_nav.html | 64 - docs/_includes/nav/collection_nav_group.html | 19 - .../nav/collection_nav_group_item.html | 1 - docs/_includes/nav/header_nav.html | 30 - docs/_includes/nav_search.html | 15 - docs/_includes/plugins/all_share.html | 3 - docs/_includes/plugins/ascii_cinema.html | 2 - docs/_includes/plugins/button.html | 6 - docs/_includes/plugins/github_star.html | 4 - docs/_includes/plugins/github_watch.html | 4 - docs/_includes/plugins/google_share.html | 5 - docs/_includes/plugins/iframe.html | 6 - docs/_includes/plugins/like_button.html | 18 - docs/_includes/plugins/plugin_row.html | 5 - .../plugins/post_social_plugins.html | 41 - docs/_includes/plugins/slideshow.html | 88 - docs/_includes/plugins/twitter_follow.html | 12 - docs/_includes/plugins/twitter_share.html | 11 - docs/_includes/post.html | 40 - docs/_includes/powered_by.html | 28 - docs/_includes/social_plugins.html | 31 - docs/_includes/ui/button.html | 1 - docs/_layouts/basic.html | 12 - docs/_layouts/blog.html | 11 - docs/_layouts/blog_default.html | 14 - docs/_layouts/default.html | 12 - docs/_layouts/doc_default.html | 14 - docs/_layouts/doc_page.html | 10 - docs/_layouts/docs.html | 5 - docs/_layouts/home.html | 26 - docs/_layouts/page.html | 3 - docs/_layouts/plain.html | 10 - docs/_layouts/post.html | 8 - docs/_layouts/redirect.html | 6 - docs/_layouts/top-level.html | 10 - .../2014-03-27-how-to-backup-rocksdb.markdown | 135 - ...ersist-in-memory-rocksdb-database.markdown | 54 - ...ocal-meetup-held-on-march-27-2014.markdown | 53 - .../2014-04-07-rocksdb-2-8-release.markdown | 40 - ...les-for-better-lookup-performance.markdown | 28 - docs/_posts/2014-05-14-lock.markdown | 88 - .../2014-05-19-rocksdb-3-0-release.markdown | 24 - .../2014-05-22-rocksdb-3-1-release.markdown | 20 - ...6-23-plaintable-a-new-file-format.markdown | 47 - ...6-27-avoid-expensive-locks-in-get.markdown | 89 - .../2014-06-27-rocksdb-3-2-release.markdown | 30 - .../2014-07-29-rocksdb-3-3-release.markdown | 34 - docs/_posts/2014-09-12-cuckoo.markdown | 74 - ...014-09-12-new-bloom-filter-format.markdown | 52 - .../2014-09-15-rocksdb-3-5-release.markdown | 38 - ...grating-from-leveldb-to-rocksdb-2.markdown | 112 - ...ading-rocksdb-options-from-a-file.markdown | 41 - ...2015-02-27-write-batch-with-index.markdown | 20 - ...ntegrating-rocksdb-with-mongodb-2.markdown | 16 - .../2015-06-12-rocksdb-in-osquery.markdown | 10 - ...015-07-15-rocksdb-2015-h2-roadmap.markdown | 92 - ...07-17-spatial-indexing-in-rocksdb.markdown | 78 - ...now-available-in-windows-platform.markdown | 30 - docs/_posts/2015-07-23-dynamic-level.markdown | 29 - docs/_posts/2015-10-27-getthreadlist.markdown | 193 - ...eckpoints-for-efficient-snapshots.markdown | 45 - ...alysis-file-read-latency-by-level.markdown | 244 - .../_posts/2016-01-29-compaction_pri.markdown | 51 - .../2016-02-24-rocksdb-4-2-release.markdown | 41 - docs/_posts/2016-02-25-rocksdb-ama.markdown | 20 - .../2016-03-07-rocksdb-options-file.markdown | 24 - ...2016-04-26-rocksdb-4-5-1-released.markdown | 60 - .../2016-07-26-rocksdb-4-8-released.markdown | 48 - ...016-09-28-rocksdb-4-11-2-released.markdown | 49 - ...2017-01-06-rocksdb-5-0-1-released.markdown | 26 - ...2017-02-07-rocksdb-5-1-2-released.markdown | 15 - ...017-02-17-bulkoad-ingest-sst-file.markdown | 50 - ...2017-03-02-rocksdb-5-2-1-released.markdown | 22 - ...17-05-12-partitioned-index-filter.markdown | 34 - .../2017-05-14-core-local-stats.markdown | 106 - ...2017-05-26-rocksdb-5-4-5-released.markdown | 39 - ...2017-06-26-17-level-based-changes.markdown | 60 - ...2017-06-29-rocksdb-5-5-1-released.markdown | 22 - ...2017-07-25-rocksdb-5-6-1-released.markdown | 22 - docs/_posts/2017-08-24-pinnableslice.markdown | 37 - docs/_posts/2017-08-25-flushwal.markdown | 26 - .../2017-09-28-rocksdb-5-8-released.markdown | 25 - ...-12-18-17-auto-tuned-rate-limiter.markdown | 28 - .../2017-12-19-write-prepared-txn.markdown | 41 - ...018-02-05-rocksdb-5-10-2-released.markdown | 22 - ...2018-08-01-rocksdb-tuning-advisor.markdown | 58 - .../2018-08-23-data-block-hash-index.markdown | 118 - docs/_posts/2018-11-21-delete-range.markdown | 292 - .../2019-03-08-format-version-4.markdown | 36 - .../2019-08-15-unordered-write.markdown | 56 - ...2021-04-12-universal-improvements.markdown | 46 - .../2021-05-26-integrated-blob-db.markdown | 101 - .../2021-05-26-online-validation.markdown | 17 - ...021-05-27-rocksdb-secondary-cache.markdown | 195 - ...2021-05-31-dictionary-compression.markdown | 157 - docs/_posts/2021-12-29-ribbon-filter.markdown | 281 - ...2022-07-18-per-key-value-checksum.markdown | 142 - ...0-05-lost-buffered-write-recovery.markdown | 123 - ...-10-07-asynchronous-io-in-rocksdb.markdown | 133 - ...0-31-align-compaction-output-file.markdown | 107 - ...2-11-09-time-aware-tiered-storage.markdown | 121 - docs/_sass/_base.scss | 492 - docs/_sass/_blog.scss | 47 - docs/_sass/_buttons.scss | 47 - docs/_sass/_footer.scss | 82 - docs/_sass/_gridBlock.scss | 115 - docs/_sass/_header.scss | 139 - docs/_sass/_poweredby.scss | 69 - docs/_sass/_promo.scss | 55 - docs/_sass/_react_docs_nav.scss | 332 - docs/_sass/_react_header_nav.scss | 141 - docs/_sass/_reset.scss | 43 - docs/_sass/_search.scss | 142 - docs/_sass/_slideshow.scss | 48 - docs/_sass/_syntax-highlighting.scss | 129 - docs/_sass/_tables.scss | 47 - docs/_top-level/support.md | 22 - docs/blog/all.html | 20 - docs/blog/index.html | 12 - docs/css/main.scss | 159 - .../2016-04-07-blog-post-example.md | 21 - docs/doc-type-examples/docs-hello-world.md | 12 - docs/doc-type-examples/top-level-example.md | 8 - docs/docs/index.html | 6 - docs/feed.xml | 30 - docs/index.md | 9 - docs/static/favicon.png | Bin 3927 -> 0 bytes docs/static/fonts/LatoLatin-Black.woff | Bin 70460 -> 0 bytes docs/static/fonts/LatoLatin-Black.woff2 | Bin 43456 -> 0 bytes docs/static/fonts/LatoLatin-BlackItalic.woff | Bin 72372 -> 0 bytes docs/static/fonts/LatoLatin-BlackItalic.woff2 | Bin 44316 -> 0 bytes docs/static/fonts/LatoLatin-Italic.woff | Bin 74708 -> 0 bytes docs/static/fonts/LatoLatin-Italic.woff2 | Bin 45388 -> 0 bytes docs/static/fonts/LatoLatin-Light.woff | Bin 72604 -> 0 bytes docs/static/fonts/LatoLatin-Light.woff2 | Bin 43468 -> 0 bytes docs/static/fonts/LatoLatin-Regular.woff | Bin 72456 -> 0 bytes docs/static/fonts/LatoLatin-Regular.woff2 | Bin 43760 -> 0 bytes .../Resize-of-20140327_200754-300x225.jpg | Bin 26670 -> 0 bytes .../compaction_output_file_size_compare.png | Bin 334304 -> 0 bytes .../file_cut_align.png | Bin 15323 -> 0 bytes .../file_cut_normal.png | Bin 19657 -> 0 bytes .../file_cut_trival_move.png | Bin 14648 -> 0 bytes .../file_size_compare.png | Bin 430123 -> 0 bytes .../write_amp_compare.png | Bin 597565 -> 0 bytes .../images/asynchronous-io/mget_async.png | Bin 169781 -> 0 bytes .../images/asynchronous-io/scan_async.png | Bin 78433 -> 0 bytes docs/static/images/binaryseek.png | Bin 68892 -> 0 bytes docs/static/images/bloom_fp_vs_bpk.png | Bin 51924 -> 0 bytes docs/static/images/compaction/full-range.png | Bin 193353 -> 0 bytes .../images/compaction/l0-l1-contend.png | Bin 203828 -> 0 bytes .../images/compaction/l1-l2-contend.png | Bin 230195 -> 0 bytes .../images/compaction/part-range-old.png | Bin 165547 -> 0 bytes .../block-format-binary-seek.png | Bin 68892 -> 0 bytes .../block-format-hash-index.png | Bin 31288 -> 0 bytes .../hash-index-data-structure.png | Bin 84389 -> 0 bytes .../data-block-hash-index/perf-cache-miss.png | Bin 44540 -> 0 bytes .../data-block-hash-index/perf-throughput.png | Bin 35170 -> 0 bytes .../images/delrange/delrange_collapsed.png | Bin 29265 -> 0 bytes .../images/delrange/delrange_key_schema.png | Bin 55178 -> 0 bytes .../images/delrange/delrange_sst_blocks.png | Bin 25596 -> 0 bytes .../images/delrange/delrange_uncollapsed.png | Bin 25358 -> 0 bytes .../images/delrange/delrange_write_path.png | Bin 109609 -> 0 bytes .../images/dictcmp/dictcmp_raw_sampled.png | Bin 247385 -> 0 bytes .../images/dictcmp/dictcmp_sst_blocks.png | Bin 55789 -> 0 bytes .../images/dictcmp/dictcmp_zstd_trained.png | Bin 357368 -> 0 bytes ...BlobDB_Benchmarks_Legacy_Vs_Integrated.png | Bin 45798 -> 0 bytes .../BlobDB_Benchmarks_RW_RO_Perf.png | Bin 179656 -> 0 bytes .../BlobDB_Benchmarks_Write_Amp.png | Bin 148777 -> 0 bytes .../BlobDB_Benchmarks_Write_Perf.png | Bin 182185 -> 0 bytes .../images/kv-checksum/Memtable-entry.png | Bin 23812 -> 0 bytes .../images/kv-checksum/Memtable-write.png | Bin 48417 -> 0 bytes .../images/kv-checksum/ProtInfo-Memtable.png | Bin 74050 -> 0 bytes .../ProtInfo-Writebatch-to-Memtable.png | Bin 62330 -> 0 bytes .../kv-checksum/ProtInfo-Writebatch.png | Bin 74028 -> 0 bytes .../images/kv-checksum/WAL-fragment.png | Bin 18832 -> 0 bytes docs/static/images/kv-checksum/WAL-read.png | Bin 50350 -> 0 bytes docs/static/images/kv-checksum/WAL-write.png | Bin 53414 -> 0 bytes .../static/images/kv-checksum/Write-batch.png | Bin 21493 -> 0 bytes .../images/kv-checksum/Writebatch-write.png | Bin 49403 -> 0 bytes .../angry-cat.png | Bin 44801 -> 0 bytes .../basic-setup.png | Bin 59645 -> 0 bytes .../happy-cat.png | Bin 45197 -> 0 bytes .../replay-extension.png | Bin 105737 -> 0 bytes .../test-fs-writable-file.png | Bin 30462 -> 0 bytes .../trace-extension.png | Bin 36632 -> 0 bytes docs/static/images/pcache-blockindex.jpg | Bin 55324 -> 0 bytes docs/static/images/pcache-fileindex.jpg | Bin 54922 -> 0 bytes docs/static/images/pcache-filelayout.jpg | Bin 47197 -> 0 bytes docs/static/images/pcache-readiopath.jpg | Bin 16381 -> 0 bytes docs/static/images/pcache-tieredstorage.jpg | Bin 78208 -> 0 bytes docs/static/images/pcache-writeiopath.jpg | Bin 22616 -> 0 bytes docs/static/images/promo-adapt.svg | 8 - docs/static/images/promo-flash.svg | 28 - docs/static/images/promo-operations.svg | 6 - docs/static/images/promo-performance.svg | 134 - .../auto-tuned-write-KBps-series.png | Bin 176624 -> 0 bytes .../images/rate-limiter/write-KBps-cdf.png | Bin 80439 -> 0 bytes .../images/rate-limiter/write-KBps-series.png | Bin 310422 -> 0 bytes .../Mixgraph_hit_rate.png | Bin 802366 -> 0 bytes .../Mixgraph_throughput.png | Bin 929381 -> 0 bytes .../rocksdb-secondary-cache/arch_diagram.png | Bin 100745 -> 0 bytes .../rocksdb-secondary-cache/insert_flow.png | Bin 53482 -> 0 bytes .../rocksdb-secondary-cache/lookup_flow.png | Bin 58107 -> 0 bytes .../compaction_moving_up_conflict.png | Bin 166368 -> 0 bytes .../per_key_placement_compaction.png | Bin 149731 -> 0 bytes .../tiered_storage_design.png | Bin 38700 -> 0 bytes .../tiered_storage_overview.png | Bin 73052 -> 0 bytes .../tiered_storage_problem.png | Bin 188590 -> 0 bytes docs/static/images/tree_example1.png | Bin 17804 -> 0 bytes docs/static/logo.svg | 76 - docs/static/og_image.png | Bin 17639 -> 0 bytes env/env_basic_test.cc | 397 - env/env_test.cc | 3546 ------- env/io_posix_test.cc | 141 - env/mock_env_test.cc | 84 - examples/.gitignore | 10 - examples/CMakeLists.txt | 45 - examples/Makefile | 58 - examples/README.md | 2 - examples/c_simple_example.c | 96 - examples/column_families_example.cc | 88 - examples/compact_files_example.cc | 177 - examples/compaction_filter_example.cc | 96 - examples/multi_processes_example.cc | 393 - examples/optimistic_transaction_example.cc | 190 - examples/options_file_example.cc | 132 - examples/rocksdb_backup_restore_example.cc | 99 - examples/rocksdb_option_file_example.ini | 142 - examples/simple_example.cc | 93 - examples/transaction_example.cc | 196 - file/delete_scheduler_test.cc | 717 -- file/prefetch_test.cc | 2285 ----- file/random_access_file_reader_test.cc | 479 - fuzz/.gitignore | 5 - fuzz/Makefile | 67 - fuzz/README.md | 165 - fuzz/db_fuzzer.cc | 172 - fuzz/db_map_fuzzer.cc | 107 - fuzz/proto/db_operation.proto | 28 - fuzz/sst_file_writer_fuzzer.cc | 209 - fuzz/util.h | 29 - java/CMakeLists.txt | 549 - java/GetBenchmarks.md | 161 - java/HISTORY-JAVA.md | 86 - java/Makefile | 453 - java/RELEASE.md | 59 - .../org/rocksdb/benchmark/DbBenchmark.java | 1640 --- java/crossbuild/Vagrantfile | 51 - java/crossbuild/build-linux-alpine.sh | 70 - java/crossbuild/build-linux-centos.sh | 38 - java/crossbuild/build-linux.sh | 15 - java/crossbuild/docker-build-linux-alpine.sh | 17 - java/crossbuild/docker-build-linux-centos.sh | 38 - java/jdb_bench.sh | 13 - java/jmh/LICENSE-HEADER.txt | 5 - java/jmh/README.md | 24 - java/jmh/pom.xml | 138 - .../org/rocksdb/jmh/ComparatorBenchmarks.java | 139 - .../java/org/rocksdb/jmh/GetBenchmarks.java | 215 - .../org/rocksdb/jmh/MultiGetBenchmarks.java | 214 - .../java/org/rocksdb/jmh/PutBenchmarks.java | 112 - .../main/java/org/rocksdb/util/FileUtils.java | 59 - .../main/java/org/rocksdb/util/KVUtils.java | 72 - java/pom.xml.template | 178 - java/rocksjni/backup_engine_options.cc | 365 - java/rocksjni/backupenginejni.cc | 279 - java/rocksjni/cache.cc | 34 - .../rocksjni/cassandra_compactionfilterjni.cc | 25 - java/rocksjni/cassandra_value_operator.cc | 50 - java/rocksjni/checkpoint.cc | 71 - java/rocksjni/clock_cache.cc | 42 - java/rocksjni/columnfamilyhandle.cc | 72 - java/rocksjni/compact_range_options.cc | 222 - java/rocksjni/compaction_filter.cc | 29 - java/rocksjni/compaction_filter_factory.cc | 42 - .../compaction_filter_factory_jnicallback.cc | 79 - .../compaction_filter_factory_jnicallback.h | 37 - java/rocksjni/compaction_job_info.cc | 230 - java/rocksjni/compaction_job_stats.cc | 345 - java/rocksjni/compaction_options.cc | 112 - java/rocksjni/compaction_options_fifo.cc | 83 - java/rocksjni/compaction_options_universal.cc | 209 - java/rocksjni/comparator.cc | 60 - java/rocksjni/comparatorjnicallback.cc | 647 -- java/rocksjni/comparatorjnicallback.h | 137 - java/rocksjni/compression_options.cc | 214 - java/rocksjni/concurrent_task_limiter.cc | 97 - java/rocksjni/config_options.cc | 103 - java/rocksjni/cplusplus_to_java_convert.h | 37 - java/rocksjni/env.cc | 205 - java/rocksjni/env_options.cc | 305 - java/rocksjni/event_listener.cc | 44 - java/rocksjni/event_listener_jnicallback.cc | 502 - java/rocksjni/event_listener_jnicallback.h | 122 - java/rocksjni/filter.cc | 46 - java/rocksjni/ingest_external_file_options.cc | 199 - java/rocksjni/iterator.cc | 340 - java/rocksjni/jnicallback.cc | 54 - java/rocksjni/jnicallback.h | 32 - java/rocksjni/loggerjnicallback.cc | 299 - java/rocksjni/loggerjnicallback.h | 51 - java/rocksjni/lru_cache.cc | 49 - java/rocksjni/memory_util.cc | 100 - java/rocksjni/memtablejni.cc | 94 - java/rocksjni/merge_operator.cc | 98 - .../native_comparator_wrapper_test.cc | 45 - java/rocksjni/optimistic_transaction_db.cc | 270 - .../optimistic_transaction_options.cc | 78 - java/rocksjni/options.cc | 8695 ---------------- java/rocksjni/options_util.cc | 139 - java/rocksjni/persistent_cache.cc | 60 - java/rocksjni/portal.h | 8686 ---------------- java/rocksjni/ratelimiterjni.cc | 128 - .../remove_emptyvalue_compactionfilterjni.cc | 24 - java/rocksjni/restorejni.cc | 42 - java/rocksjni/rocks_callback_object.cc | 30 - java/rocksjni/rocksdb_exception_test.cc | 81 - java/rocksjni/rocksjni.cc | 3957 ------- java/rocksjni/slice.cc | 374 - java/rocksjni/snapshot.cc | 27 - java/rocksjni/sst_file_manager.cc | 250 - java/rocksjni/sst_file_reader_iterator.cc | 373 - java/rocksjni/sst_file_readerjni.cc | 118 - java/rocksjni/sst_file_writerjni.cc | 310 - java/rocksjni/sst_partitioner.cc | 43 - java/rocksjni/statistics.cc | 268 - java/rocksjni/statisticsjni.cc | 31 - java/rocksjni/statisticsjni.h | 34 - java/rocksjni/table.cc | 145 - java/rocksjni/table_filter.cc | 27 - java/rocksjni/table_filter_jnicallback.cc | 66 - java/rocksjni/table_filter_jnicallback.h | 36 - java/rocksjni/testable_event_listener.cc | 219 - java/rocksjni/thread_status.cc | 125 - java/rocksjni/trace_writer.cc | 24 - java/rocksjni/trace_writer_jnicallback.cc | 118 - java/rocksjni/trace_writer_jnicallback.h | 36 - java/rocksjni/transaction.cc | 1655 --- java/rocksjni/transaction_db.cc | 451 - java/rocksjni/transaction_db_options.cc | 169 - java/rocksjni/transaction_log.cc | 80 - java/rocksjni/transaction_notifier.cc | 44 - .../transaction_notifier_jnicallback.cc | 42 - .../transaction_notifier_jnicallback.h | 42 - java/rocksjni/transaction_options.cc | 191 - java/rocksjni/ttl.cc | 212 - java/rocksjni/wal_filter.cc | 24 - java/rocksjni/wal_filter_jnicallback.cc | 139 - java/rocksjni/wal_filter_jnicallback.h | 42 - java/rocksjni/write_batch.cc | 676 -- java/rocksjni/write_batch_test.cc | 199 - java/rocksjni/write_batch_with_index.cc | 953 -- java/rocksjni/write_buffer_manager.cc | 47 - java/rocksjni/writebatchhandlerjnicallback.cc | 519 - java/rocksjni/writebatchhandlerjnicallback.h | 92 - .../java/OptimisticTransactionSample.java | 184 - .../main/java/RocksDBColumnFamilySample.java | 78 - java/samples/src/main/java/RocksDBSample.java | 295 - .../src/main/java/TransactionSample.java | 183 - .../org/rocksdb/AbstractCompactionFilter.java | 59 - .../AbstractCompactionFilterFactory.java | 77 - .../java/org/rocksdb/AbstractComparator.java | 124 - .../rocksdb/AbstractComparatorJniBridge.java | 125 - .../org/rocksdb/AbstractEventListener.java | 334 - .../AbstractImmutableNativeReference.java | 65 - .../org/rocksdb/AbstractMutableOptions.java | 370 - .../org/rocksdb/AbstractNativeReference.java | 48 - .../org/rocksdb/AbstractRocksIterator.java | 146 - .../main/java/org/rocksdb/AbstractSlice.java | 191 - .../java/org/rocksdb/AbstractTableFilter.java | 20 - .../java/org/rocksdb/AbstractTraceWriter.java | 70 - .../rocksdb/AbstractTransactionNotifier.java | 54 - .../java/org/rocksdb/AbstractWalFilter.java | 49 - .../java/org/rocksdb/AbstractWriteBatch.java | 204 - .../src/main/java/org/rocksdb/AccessHint.java | 53 - .../AdvancedColumnFamilyOptionsInterface.java | 464 - ...edMutableColumnFamilyOptionsInterface.java | 830 -- .../org/rocksdb/BackgroundErrorReason.java | 46 - .../main/java/org/rocksdb/BackupEngine.java | 259 - .../java/org/rocksdb/BackupEngineOptions.java | 458 - .../src/main/java/org/rocksdb/BackupInfo.java | 76 - .../org/rocksdb/BlockBasedTableConfig.java | 951 -- .../main/java/org/rocksdb/BloomFilter.java | 73 - .../java/org/rocksdb/BuiltinComparator.java | 20 - .../java/org/rocksdb/ByteBufferGetStatus.java | 50 - java/src/main/java/org/rocksdb/Cache.java | 40 - .../rocksdb/CassandraCompactionFilter.java | 19 - .../rocksdb/CassandraValueMergeOperator.java | 25 - .../src/main/java/org/rocksdb/Checkpoint.java | 66 - .../main/java/org/rocksdb/ChecksumType.java | 45 - .../src/main/java/org/rocksdb/ClockCache.java | 59 - .../org/rocksdb/ColumnFamilyDescriptor.java | 84 - .../java/org/rocksdb/ColumnFamilyHandle.java | 151 - .../org/rocksdb/ColumnFamilyMetaData.java | 70 - .../java/org/rocksdb/ColumnFamilyOptions.java | 1543 --- .../rocksdb/ColumnFamilyOptionsInterface.java | 536 - .../java/org/rocksdb/CompactRangeOptions.java | 246 - .../java/org/rocksdb/CompactionJobInfo.java | 161 - .../java/org/rocksdb/CompactionJobStats.java | 295 - .../java/org/rocksdb/CompactionOptions.java | 121 - .../org/rocksdb/CompactionOptionsFIFO.java | 89 - .../rocksdb/CompactionOptionsUniversal.java | 273 - .../java/org/rocksdb/CompactionPriority.java | 81 - .../java/org/rocksdb/CompactionReason.java | 141 - .../java/org/rocksdb/CompactionStopStyle.java | 55 - .../java/org/rocksdb/CompactionStyle.java | 80 - .../java/org/rocksdb/ComparatorOptions.java | 133 - .../main/java/org/rocksdb/ComparatorType.java | 48 - .../java/org/rocksdb/CompressionOptions.java | 151 - .../java/org/rocksdb/CompressionType.java | 121 - .../org/rocksdb/ConcurrentTaskLimiter.java | 44 - .../rocksdb/ConcurrentTaskLimiterImpl.java | 48 - .../main/java/org/rocksdb/ConfigOptions.java | 53 - java/src/main/java/org/rocksdb/DBOptions.java | 1496 --- .../java/org/rocksdb/DBOptionsInterface.java | 1756 ---- .../java/org/rocksdb/DataBlockIndexType.java | 32 - java/src/main/java/org/rocksdb/DbPath.java | 47 - .../main/java/org/rocksdb/DirectSlice.java | 137 - .../main/java/org/rocksdb/EncodingType.java | 55 - java/src/main/java/org/rocksdb/Env.java | 167 - .../src/main/java/org/rocksdb/EnvOptions.java | 366 - .../main/java/org/rocksdb/EventListener.java | 335 - .../main/java/org/rocksdb/Experimental.java | 23 - .../rocksdb/ExternalFileIngestionInfo.java | 103 - .../java/org/rocksdb/FileOperationInfo.java | 112 - java/src/main/java/org/rocksdb/Filter.java | 36 - .../main/java/org/rocksdb/FlushJobInfo.java | 186 - .../main/java/org/rocksdb/FlushOptions.java | 90 - .../main/java/org/rocksdb/FlushReason.java | 53 - .../rocksdb/HashLinkedListMemTableConfig.java | 174 - .../rocksdb/HashSkipListMemTableConfig.java | 106 - .../main/java/org/rocksdb/HistogramData.java | 75 - .../main/java/org/rocksdb/HistogramType.java | 208 - java/src/main/java/org/rocksdb/Holder.java | 46 - .../java/org/rocksdb/IndexShorteningMode.java | 60 - java/src/main/java/org/rocksdb/IndexType.java | 55 - .../main/java/org/rocksdb/InfoLogLevel.java | 49 - .../rocksdb/IngestExternalFileOptions.java | 227 - .../main/java/org/rocksdb/KeyMayExist.java | 36 - java/src/main/java/org/rocksdb/LRUCache.java | 106 - .../main/java/org/rocksdb/LevelMetaData.java | 56 - .../java/org/rocksdb/LiveFileMetaData.java | 55 - java/src/main/java/org/rocksdb/LogFile.java | 75 - java/src/main/java/org/rocksdb/Logger.java | 122 - .../main/java/org/rocksdb/MemTableConfig.java | 29 - .../main/java/org/rocksdb/MemTableInfo.java | 103 - .../java/org/rocksdb/MemoryUsageType.java | 72 - .../src/main/java/org/rocksdb/MemoryUtil.java | 60 - .../main/java/org/rocksdb/MergeOperator.java | 18 - .../rocksdb/MutableColumnFamilyOptions.java | 623 -- .../MutableColumnFamilyOptionsInterface.java | 156 - .../java/org/rocksdb/MutableDBOptions.java | 294 - .../rocksdb/MutableDBOptionsInterface.java | 440 - .../java/org/rocksdb/MutableOptionKey.java | 16 - .../java/org/rocksdb/MutableOptionValue.java | 369 - .../org/rocksdb/NativeComparatorWrapper.java | 59 - .../java/org/rocksdb/NativeLibraryLoader.java | 172 - .../main/java/org/rocksdb/OperationStage.java | 59 - .../main/java/org/rocksdb/OperationType.java | 54 - .../org/rocksdb/OptimisticTransactionDB.java | 226 - .../rocksdb/OptimisticTransactionOptions.java | 53 - .../main/java/org/rocksdb/OptionString.java | 256 - java/src/main/java/org/rocksdb/Options.java | 2578 ----- .../main/java/org/rocksdb/OptionsUtil.java | 98 - .../java/org/rocksdb/PersistentCache.java | 26 - .../java/org/rocksdb/PlainTableConfig.java | 251 - .../org/rocksdb/PrepopulateBlobCache.java | 117 - java/src/main/java/org/rocksdb/Priority.java | 49 - java/src/main/java/org/rocksdb/Range.java | 19 - .../main/java/org/rocksdb/RateLimiter.java | 227 - .../java/org/rocksdb/RateLimiterMode.java | 52 - .../main/java/org/rocksdb/ReadOptions.java | 823 -- java/src/main/java/org/rocksdb/ReadTier.java | 49 - .../RemoveEmptyValueCompactionFilter.java | 18 - .../main/java/org/rocksdb/RestoreOptions.java | 32 - .../rocksdb/ReusedSynchronisationType.java | 65 - .../java/org/rocksdb/RocksCallbackObject.java | 73 - java/src/main/java/org/rocksdb/RocksDB.java | 4694 --------- .../java/org/rocksdb/RocksDBException.java | 44 - java/src/main/java/org/rocksdb/RocksEnv.java | 32 - .../main/java/org/rocksdb/RocksIterator.java | 140 - .../org/rocksdb/RocksIteratorInterface.java | 127 - .../main/java/org/rocksdb/RocksMemEnv.java | 31 - .../java/org/rocksdb/RocksMutableObject.java | 87 - .../main/java/org/rocksdb/RocksObject.java | 45 - .../main/java/org/rocksdb/SanityLevel.java | 47 - .../org/rocksdb/SizeApproximationFlag.java | 31 - .../org/rocksdb/SkipListMemTableConfig.java | 51 - java/src/main/java/org/rocksdb/Slice.java | 136 - java/src/main/java/org/rocksdb/Snapshot.java | 41 - .../main/java/org/rocksdb/SstFileManager.java | 251 - .../java/org/rocksdb/SstFileMetaData.java | 162 - .../main/java/org/rocksdb/SstFileReader.java | 82 - .../org/rocksdb/SstFileReaderIterator.java | 140 - .../main/java/org/rocksdb/SstFileWriter.java | 238 - .../org/rocksdb/SstPartitionerFactory.java | 15 - .../SstPartitionerFixedPrefixFactory.java | 19 - java/src/main/java/org/rocksdb/StateType.java | 53 - .../src/main/java/org/rocksdb/Statistics.java | 152 - .../java/org/rocksdb/StatisticsCollector.java | 111 - .../rocksdb/StatisticsCollectorCallback.java | 32 - .../java/org/rocksdb/StatsCollectorInput.java | 35 - .../src/main/java/org/rocksdb/StatsLevel.java | 65 - java/src/main/java/org/rocksdb/Status.java | 155 - .../org/rocksdb/StringAppendOperator.java | 29 - .../rocksdb/TableFileCreationBriefInfo.java | 107 - .../org/rocksdb/TableFileCreationInfo.java | 86 - .../org/rocksdb/TableFileCreationReason.java | 46 - .../org/rocksdb/TableFileDeletionInfo.java | 86 - .../main/java/org/rocksdb/TableFilter.java | 21 - .../java/org/rocksdb/TableFormatConfig.java | 22 - .../java/org/rocksdb/TableProperties.java | 426 - .../main/java/org/rocksdb/ThreadStatus.java | 224 - .../src/main/java/org/rocksdb/ThreadType.java | 65 - .../src/main/java/org/rocksdb/TickerType.java | 792 -- java/src/main/java/org/rocksdb/TimedEnv.java | 30 - .../main/java/org/rocksdb/TraceOptions.java | 32 - .../main/java/org/rocksdb/TraceWriter.java | 36 - .../main/java/org/rocksdb/Transaction.java | 2170 ---- .../main/java/org/rocksdb/TransactionDB.java | 403 - .../org/rocksdb/TransactionDBOptions.java | 217 - .../org/rocksdb/TransactionLogIterator.java | 112 - .../java/org/rocksdb/TransactionOptions.java | 189 - .../java/org/rocksdb/TransactionalDB.java | 65 - .../org/rocksdb/TransactionalOptions.java | 31 - java/src/main/java/org/rocksdb/TtlDB.java | 245 - .../java/org/rocksdb/TxnDBWritePolicy.java | 62 - .../java/org/rocksdb/UInt64AddOperator.java | 19 - .../org/rocksdb/VectorMemTableConfig.java | 46 - .../java/org/rocksdb/WALRecoveryMode.java | 83 - .../java/org/rocksdb/WBWIRocksIterator.java | 203 - .../main/java/org/rocksdb/WalFileType.java | 55 - java/src/main/java/org/rocksdb/WalFilter.java | 87 - .../java/org/rocksdb/WalProcessingOption.java | 54 - .../src/main/java/org/rocksdb/WriteBatch.java | 396 - .../java/org/rocksdb/WriteBatchInterface.java | 283 - .../java/org/rocksdb/WriteBatchWithIndex.java | 361 - .../java/org/rocksdb/WriteBufferManager.java | 50 - .../main/java/org/rocksdb/WriteOptions.java | 256 - .../java/org/rocksdb/WriteStallCondition.java | 44 - .../main/java/org/rocksdb/WriteStallInfo.java | 75 - .../main/java/org/rocksdb/util/ByteUtil.java | 52 - .../org/rocksdb/util/BytewiseComparator.java | 121 - .../java/org/rocksdb/util/Environment.java | 245 - .../java/org/rocksdb/util/IntComparator.java | 67 - .../util/ReverseBytewiseComparator.java | 88 - .../main/java/org/rocksdb/util/SizeUnit.java | 16 - .../org/rocksdb/AbstractTransactionTest.java | 965 -- .../org/rocksdb/BackupEngineOptionsTest.java | 300 - .../java/org/rocksdb/BackupEngineTest.java | 261 - .../java/org/rocksdb/BlobOptionsTest.java | 351 - .../rocksdb/BlockBasedTableConfigTest.java | 416 - .../org/rocksdb/BuiltinComparatorTest.java | 145 - .../ByteBufferUnsupportedOperationTest.java | 132 - .../BytewiseComparatorRegressionTest.java | 126 - .../test/java/org/rocksdb/CheckPointTest.java | 83 - .../test/java/org/rocksdb/ClockCacheTest.java | 26 - .../org/rocksdb/ColumnFamilyOptionsTest.java | 714 -- .../java/org/rocksdb/ColumnFamilyTest.java | 582 -- .../org/rocksdb/CompactRangeOptionsTest.java | 101 - .../rocksdb/CompactionFilterFactoryTest.java | 61 - .../org/rocksdb/CompactionJobInfoTest.java | 114 - .../org/rocksdb/CompactionJobStatsTest.java | 196 - .../rocksdb/CompactionOptionsFIFOTest.java | 35 - .../org/rocksdb/CompactionOptionsTest.java | 52 - .../CompactionOptionsUniversalTest.java | 80 - .../org/rocksdb/CompactionPriorityTest.java | 31 - .../org/rocksdb/CompactionStopStyleTest.java | 31 - .../org/rocksdb/ComparatorOptionsTest.java | 58 - .../org/rocksdb/CompressionOptionsTest.java | 71 - .../org/rocksdb/CompressionTypesTest.java | 20 - .../rocksdb/ConcurrentTaskLimiterTest.java | 56 - .../test/java/org/rocksdb/DBOptionsTest.java | 904 -- .../test/java/org/rocksdb/DefaultEnvTest.java | 113 - .../java/org/rocksdb/DirectSliceTest.java | 93 - .../test/java/org/rocksdb/EnvOptionsTest.java | 145 - .../java/org/rocksdb/EventListenerTest.java | 725 -- .../src/test/java/org/rocksdb/FilterTest.java | 39 - .../java/org/rocksdb/FlushOptionsTest.java | 31 - java/src/test/java/org/rocksdb/FlushTest.java | 49 - .../java/org/rocksdb/InfoLogLevelTest.java | 109 - .../IngestExternalFileOptionsTest.java | 107 - .../java/org/rocksdb/KeyMayExistTest.java | 528 - .../test/java/org/rocksdb/LRUCacheTest.java | 32 - .../src/test/java/org/rocksdb/LoggerTest.java | 239 - .../test/java/org/rocksdb/MemTableTest.java | 111 - .../test/java/org/rocksdb/MemoryUtilTest.java | 144 - java/src/test/java/org/rocksdb/MergeTest.java | 465 - .../java/org/rocksdb/MixedOptionsTest.java | 85 - .../rocksdb/MultiColumnRegressionTest.java | 146 - .../org/rocksdb/MultiGetManyKeysTest.java | 241 - .../test/java/org/rocksdb/MultiGetTest.java | 530 - .../MutableColumnFamilyOptionsTest.java | 167 - .../org/rocksdb/MutableDBOptionsTest.java | 85 - .../org/rocksdb/MutableOptionsGetSetTest.java | 429 - .../rocksdb/NativeComparatorWrapperTest.java | 95 - .../org/rocksdb/NativeLibraryLoaderTest.java | 41 - .../rocksdb/OptimisticTransactionDBTest.java | 131 - .../OptimisticTransactionOptionsTest.java | 38 - .../rocksdb/OptimisticTransactionTest.java | 446 - .../test/java/org/rocksdb/OptionsTest.java | 1492 --- .../java/org/rocksdb/OptionsUtilTest.java | 129 - .../org/rocksdb/PlainTableConfigTest.java | 89 - .../org/rocksdb/PlatformRandomHelper.java | 58 - .../org/rocksdb/PutMultiplePartsTest.java | 164 - .../java/org/rocksdb/RateLimiterTest.java | 65 - .../test/java/org/rocksdb/ReadOnlyTest.java | 234 - .../java/org/rocksdb/ReadOptionsTest.java | 375 - .../org/rocksdb/RocksDBExceptionTest.java | 115 - .../test/java/org/rocksdb/RocksDBTest.java | 1695 --- .../java/org/rocksdb/RocksIteratorTest.java | 289 - .../java/org/rocksdb/RocksMemEnvTest.java | 141 - .../rocksdb/RocksNativeLibraryResource.java | 18 - .../java/org/rocksdb/SecondaryDBTest.java | 135 - java/src/test/java/org/rocksdb/SliceTest.java | 80 - .../test/java/org/rocksdb/SnapshotTest.java | 169 - .../java/org/rocksdb/SstFileManagerTest.java | 66 - .../java/org/rocksdb/SstFileReaderTest.java | 222 - .../java/org/rocksdb/SstFileWriterTest.java | 241 - .../java/org/rocksdb/SstPartitionerTest.java | 72 - .../org/rocksdb/StatisticsCollectorTest.java | 55 - .../test/java/org/rocksdb/StatisticsTest.java | 168 - .../java/org/rocksdb/StatsCallbackMock.java | 20 - .../java/org/rocksdb/TableFilterTest.java | 106 - .../test/java/org/rocksdb/TimedEnvTest.java | 43 - .../org/rocksdb/TransactionDBOptionsTest.java | 64 - .../java/org/rocksdb/TransactionDBTest.java | 178 - .../rocksdb/TransactionLogIteratorTest.java | 139 - .../org/rocksdb/TransactionOptionsTest.java | 72 - .../java/org/rocksdb/TransactionTest.java | 488 - java/src/test/java/org/rocksdb/TtlDBTest.java | 112 - java/src/test/java/org/rocksdb/Types.java | 43 - .../java/org/rocksdb/VerifyChecksumsTest.java | 213 - .../java/org/rocksdb/WALRecoveryModeTest.java | 22 - .../test/java/org/rocksdb/WalFilterTest.java | 165 - .../org/rocksdb/WriteBatchHandlerTest.java | 76 - .../test/java/org/rocksdb/WriteBatchTest.java | 528 - .../org/rocksdb/WriteBatchThreadedTest.java | 104 - .../org/rocksdb/WriteBatchWithIndexTest.java | 1068 -- .../java/org/rocksdb/WriteOptionsTest.java | 75 - ...moveEmptyValueCompactionFilterFactory.java | 21 - .../org/rocksdb/test/RocksJunitRunner.java | 174 - .../rocksdb/test/TestableEventListener.java | 23 - .../org/rocksdb/util/ByteBufferAllocator.java | 16 - .../util/BytewiseComparatorIntTest.java | 267 - .../rocksdb/util/BytewiseComparatorTest.java | 531 - .../util/CapturingWriteBatchHandler.java | 190 - .../util/DirectByteBufferAllocator.java | 18 - .../org/rocksdb/util/EnvironmentTest.java | 304 - .../rocksdb/util/HeapByteBufferAllocator.java | 18 - .../org/rocksdb/util/IntComparatorTest.java | 266 - .../org/rocksdb/util/JNIComparatorTest.java | 180 - .../ReverseBytewiseComparatorIntTest.java | 270 - .../java/org/rocksdb/util/SizeUnitTest.java | 27 - .../test/java/org/rocksdb/util/TestUtil.java | 72 - .../org/rocksdb/util/WriteBatchGetter.java | 139 - java/understanding_options.md | 79 - logging/auto_roll_logger_test.cc | 731 -- logging/env_logger_test.cc | 163 - logging/event_logger_test.cc | 45 - memory/arena_test.cc | 295 - memory/memory_allocator_test.cc | 236 - memtable/inlineskiplist_test.cc | 664 -- memtable/skiplist_test.cc | 387 - memtable/write_buffer_manager_test.cc | 304 - microbench/CMakeLists.txt | 17 - microbench/README.md | 60 - microbench/db_basic_bench.cc | 1575 --- microbench/ribbon_bench.cc | 155 - monitoring/histogram_test.cc | 254 - monitoring/iostats_context_test.cc | 31 - monitoring/statistics_test.cc | 88 - monitoring/stats_history_test.cc | 662 -- options/configurable_test.cc | 861 -- options/customizable_test.cc | 2116 ---- options/options_settable_test.cc | 627 -- options/options_test.cc | 4976 --------- table/block_fetcher_test.cc | 519 - table/cleanable_test.cc | 390 - table/merger_test.cc | 182 - table/sst_file_reader_test.cc | 423 - table/table_test.cc | 5625 ---------- test_util/testutil_test.cc | 43 - tools/db_bench_tool_test.cc | 342 - tools/db_sanity_test.cc | 296 - tools/io_tracer_parser_test.cc | 182 - tools/ldb_cmd_test.cc | 1216 --- tools/reduce_levels_test.cc | 212 - tools/sst_dump_test.cc | 471 - tools/trace_analyzer_test.cc | 880 -- trace_replay/block_cache_tracer_test.cc | 421 - trace_replay/io_tracer_test.cc | 353 - util/autovector_test.cc | 324 - util/bloom_test.cc | 1175 --- util/coding_test.cc | 217 - util/crc32c_test.cc | 213 - util/defer_test.cc | 51 - util/dynamic_bloom_test.cc | 325 - util/file_reader_writer_test.cc | 1058 -- util/filelock_test.cc | 148 - util/hash_test.cc | 853 -- util/heap_test.cc | 131 - util/random_test.cc | 107 - util/rate_limiter_test.cc | 476 - util/repeatable_thread_test.cc | 111 - util/ribbon_test.cc | 1308 --- util/slice_test.cc | 252 - util/slice_transform_test.cc | 154 - util/thread_list_test.cc | 360 - util/thread_local_test.cc | 578 -- util/timer_queue_test.cc | 73 - util/timer_test.cc | 402 - util/work_queue_test.cc | 272 - utilities/env_mirror_test.cc | 216 - utilities/env_timed_test.cc | 34 - utilities/object_registry_test.cc | 862 -- utilities/util_merge_operators_test.cc | 100 - 878 files changed, 296067 deletions(-) delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/ubsan_suppression_list.txt delete mode 100644 .github/workflows/sanity_check.yml delete mode 100755 build_tools/amalgamate.py delete mode 100755 build_tools/benchmark_log_tool.py delete mode 100755 build_tools/build_detect_platform delete mode 100755 build_tools/check-sources.sh delete mode 100644 build_tools/dependencies_platform010.sh delete mode 100755 build_tools/dockerbuild.sh delete mode 100644 build_tools/error_filter.py delete mode 100755 build_tools/fb_compile_mongo.sh delete mode 100644 build_tools/fbcode_config.sh delete mode 100644 build_tools/fbcode_config_platform010.sh delete mode 100755 build_tools/format-diff.sh delete mode 100755 build_tools/gnu_parallel delete mode 100755 build_tools/make_package.sh delete mode 100755 build_tools/ps_with_stack delete mode 100755 build_tools/regression_build_test.sh delete mode 100644 build_tools/run_ci_db_test.ps1 delete mode 100755 build_tools/setup_centos7.sh delete mode 100644 build_tools/ubuntu20_image/Dockerfile delete mode 100755 build_tools/update_dependencies.sh delete mode 100755 build_tools/version.sh delete mode 100644 cache/cache_reservation_manager_test.cc delete mode 100644 cache/cache_test.cc delete mode 100644 cache/compressed_secondary_cache_test.cc delete mode 100644 cache/lru_cache_test.cc delete mode 100755 coverage/coverage_test.sh delete mode 100644 coverage/parse_gcov_output.py delete mode 100644 db/column_family_test.cc delete mode 100644 db/compact_files_test.cc delete mode 100644 db/comparator_db_test.cc delete mode 100644 db/corruption_test.cc delete mode 100644 db/cuckoo_table_db_test.cc delete mode 100644 db/db_basic_test.cc delete mode 100644 db/db_block_cache_test.cc delete mode 100644 db/db_bloom_filter_test.cc delete mode 100644 db/db_compaction_filter_test.cc delete mode 100644 db/db_compaction_test.cc delete mode 100644 db/db_dynamic_level_test.cc delete mode 100644 db/db_encryption_test.cc delete mode 100644 db/db_flush_test.cc delete mode 100644 db/db_inplace_update_test.cc delete mode 100644 db/db_io_failure_test.cc delete mode 100644 db/db_iter_stress_test.cc delete mode 100644 db/db_iter_test.cc delete mode 100644 db/db_iterator_test.cc delete mode 100644 db/db_kv_checksum_test.cc delete mode 100644 db/db_log_iter_test.cc delete mode 100644 db/db_logical_block_size_cache_test.cc delete mode 100644 db/db_memtable_test.cc delete mode 100644 db/db_merge_operand_test.cc delete mode 100644 db/db_merge_operator_test.cc delete mode 100644 db/db_options_test.cc delete mode 100644 db/db_properties_test.cc delete mode 100644 db/db_range_del_test.cc delete mode 100644 db/db_rate_limiter_test.cc delete mode 100644 db/db_readonly_with_timestamp_test.cc delete mode 100644 db/db_secondary_test.cc delete mode 100644 db/db_sst_test.cc delete mode 100644 db/db_statistics_test.cc delete mode 100644 db/db_table_properties_test.cc delete mode 100644 db/db_tailing_iter_test.cc delete mode 100644 db/db_test.cc delete mode 100644 db/db_universal_compaction_test.cc delete mode 100644 db/db_wal_test.cc delete mode 100644 db/db_with_timestamp_basic_test.cc delete mode 100644 db/db_with_timestamp_compaction_test.cc delete mode 100644 db/db_write_buffer_manager_test.cc delete mode 100644 db/db_write_test.cc delete mode 100644 db/dbformat_test.cc delete mode 100644 db/deletefile_test.cc delete mode 100644 db/error_handler_fs_test.cc delete mode 100644 db/external_sst_file_basic_test.cc delete mode 100644 db/external_sst_file_test.cc delete mode 100644 db/fault_injection_test.cc delete mode 100644 db/file_indexer_test.cc delete mode 100644 db/filename_test.cc delete mode 100644 db/flush_job_test.cc delete mode 100644 db/import_column_family_test.cc delete mode 100644 db/listener_test.cc delete mode 100644 db/log_test.cc delete mode 100644 db/manual_compaction_test.cc delete mode 100644 db/memtable_list_test.cc delete mode 100644 db/merge_helper_test.cc delete mode 100644 db/merge_test.cc delete mode 100644 db/obsolete_files_test.cc delete mode 100644 db/options_file_test.cc delete mode 100644 db/perf_context_test.cc delete mode 100644 db/periodic_task_scheduler_test.cc delete mode 100644 db/plain_table_db_test.cc delete mode 100644 db/prefix_test.cc delete mode 100644 db/range_del_aggregator_test.cc delete mode 100644 db/range_tombstone_fragmenter_test.cc delete mode 100644 db/repair_test.cc delete mode 100644 db/seqno_time_test.cc delete mode 100644 db/table_properties_collector_test.cc delete mode 100644 db/version_builder_test.cc delete mode 100644 db/version_edit_test.cc delete mode 100644 db/version_set_test.cc delete mode 100644 db/wal_edit_test.cc delete mode 100644 db/wal_manager_test.cc delete mode 100644 db/write_batch_test.cc delete mode 100644 db/write_callback_test.cc delete mode 100644 db/write_controller_test.cc delete mode 100644 db_stress_tool/CMakeLists.txt delete mode 100644 db_stress_tool/batched_ops_stress.cc delete mode 100644 db_stress_tool/cf_consistency_stress.cc delete mode 100644 db_stress_tool/db_stress.cc delete mode 100644 db_stress_tool/db_stress_common.cc delete mode 100644 db_stress_tool/db_stress_common.h delete mode 100644 db_stress_tool/db_stress_compaction_filter.h delete mode 100644 db_stress_tool/db_stress_driver.cc delete mode 100644 db_stress_tool/db_stress_driver.h delete mode 100644 db_stress_tool/db_stress_env_wrapper.h delete mode 100644 db_stress_tool/db_stress_gflags.cc delete mode 100644 db_stress_tool/db_stress_listener.cc delete mode 100644 db_stress_tool/db_stress_listener.h delete mode 100644 db_stress_tool/db_stress_shared_state.cc delete mode 100644 db_stress_tool/db_stress_shared_state.h delete mode 100644 db_stress_tool/db_stress_stat.cc delete mode 100644 db_stress_tool/db_stress_stat.h delete mode 100644 db_stress_tool/db_stress_table_properties_collector.h delete mode 100644 db_stress_tool/db_stress_test_base.cc delete mode 100644 db_stress_tool/db_stress_test_base.h delete mode 100644 db_stress_tool/db_stress_tool.cc delete mode 100644 db_stress_tool/expected_state.cc delete mode 100644 db_stress_tool/expected_state.h delete mode 100644 db_stress_tool/multi_ops_txns_stress.cc delete mode 100644 db_stress_tool/multi_ops_txns_stress.h delete mode 100644 db_stress_tool/no_batched_ops_stress.cc delete mode 100644 docs/.gitignore delete mode 100644 docs/CNAME delete mode 100644 docs/CONTRIBUTING.md delete mode 100644 docs/Gemfile delete mode 100644 docs/LICENSE-DOCUMENTATION delete mode 100644 docs/README.md delete mode 100644 docs/TEMPLATE-INFORMATION.md delete mode 100644 docs/_config.yml delete mode 100644 docs/_data/authors.yml delete mode 100644 docs/_data/features.yml delete mode 100644 docs/_data/nav.yml delete mode 100644 docs/_data/nav_docs.yml delete mode 100644 docs/_data/powered_by.yml delete mode 100644 docs/_data/powered_by_highlight.yml delete mode 100644 docs/_data/promo.yml delete mode 100644 docs/_docs/faq.md delete mode 100644 docs/_docs/getting-started.md delete mode 100644 docs/_includes/blog_pagination.html delete mode 100644 docs/_includes/content/gridblocks.html delete mode 100644 docs/_includes/content/items/gridblock.html delete mode 100644 docs/_includes/doc.html delete mode 100644 docs/_includes/doc_paging.html delete mode 100644 docs/_includes/footer.html delete mode 100644 docs/_includes/head.html delete mode 100644 docs/_includes/header.html delete mode 100644 docs/_includes/hero.html delete mode 100644 docs/_includes/home_header.html delete mode 100644 docs/_includes/katex_import.html delete mode 100644 docs/_includes/katex_render.html delete mode 100644 docs/_includes/nav.html delete mode 100644 docs/_includes/nav/collection_nav.html delete mode 100644 docs/_includes/nav/collection_nav_group.html delete mode 100644 docs/_includes/nav/collection_nav_group_item.html delete mode 100644 docs/_includes/nav/header_nav.html delete mode 100644 docs/_includes/nav_search.html delete mode 100644 docs/_includes/plugins/all_share.html delete mode 100644 docs/_includes/plugins/ascii_cinema.html delete mode 100644 docs/_includes/plugins/button.html delete mode 100644 docs/_includes/plugins/github_star.html delete mode 100644 docs/_includes/plugins/github_watch.html delete mode 100644 docs/_includes/plugins/google_share.html delete mode 100644 docs/_includes/plugins/iframe.html delete mode 100644 docs/_includes/plugins/like_button.html delete mode 100644 docs/_includes/plugins/plugin_row.html delete mode 100644 docs/_includes/plugins/post_social_plugins.html delete mode 100644 docs/_includes/plugins/slideshow.html delete mode 100644 docs/_includes/plugins/twitter_follow.html delete mode 100644 docs/_includes/plugins/twitter_share.html delete mode 100644 docs/_includes/post.html delete mode 100644 docs/_includes/powered_by.html delete mode 100644 docs/_includes/social_plugins.html delete mode 100644 docs/_includes/ui/button.html delete mode 100644 docs/_layouts/basic.html delete mode 100644 docs/_layouts/blog.html delete mode 100644 docs/_layouts/blog_default.html delete mode 100644 docs/_layouts/default.html delete mode 100644 docs/_layouts/doc_default.html delete mode 100644 docs/_layouts/doc_page.html delete mode 100644 docs/_layouts/docs.html delete mode 100644 docs/_layouts/home.html delete mode 100644 docs/_layouts/page.html delete mode 100644 docs/_layouts/plain.html delete mode 100644 docs/_layouts/post.html delete mode 100644 docs/_layouts/redirect.html delete mode 100644 docs/_layouts/top-level.html delete mode 100644 docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown delete mode 100644 docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown delete mode 100644 docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown delete mode 100644 docs/_posts/2014-04-07-rocksdb-2-8-release.markdown delete mode 100644 docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown delete mode 100644 docs/_posts/2014-05-14-lock.markdown delete mode 100644 docs/_posts/2014-05-19-rocksdb-3-0-release.markdown delete mode 100644 docs/_posts/2014-05-22-rocksdb-3-1-release.markdown delete mode 100644 docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown delete mode 100644 docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown delete mode 100644 docs/_posts/2014-06-27-rocksdb-3-2-release.markdown delete mode 100644 docs/_posts/2014-07-29-rocksdb-3-3-release.markdown delete mode 100644 docs/_posts/2014-09-12-cuckoo.markdown delete mode 100644 docs/_posts/2014-09-12-new-bloom-filter-format.markdown delete mode 100644 docs/_posts/2014-09-15-rocksdb-3-5-release.markdown delete mode 100644 docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown delete mode 100644 docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown delete mode 100644 docs/_posts/2015-02-27-write-batch-with-index.markdown delete mode 100644 docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown delete mode 100644 docs/_posts/2015-06-12-rocksdb-in-osquery.markdown delete mode 100644 docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown delete mode 100644 docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown delete mode 100644 docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown delete mode 100644 docs/_posts/2015-07-23-dynamic-level.markdown delete mode 100644 docs/_posts/2015-10-27-getthreadlist.markdown delete mode 100644 docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown delete mode 100644 docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown delete mode 100644 docs/_posts/2016-01-29-compaction_pri.markdown delete mode 100644 docs/_posts/2016-02-24-rocksdb-4-2-release.markdown delete mode 100644 docs/_posts/2016-02-25-rocksdb-ama.markdown delete mode 100644 docs/_posts/2016-03-07-rocksdb-options-file.markdown delete mode 100644 docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown delete mode 100644 docs/_posts/2016-07-26-rocksdb-4-8-released.markdown delete mode 100644 docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown delete mode 100644 docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown delete mode 100644 docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown delete mode 100644 docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown delete mode 100644 docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown delete mode 100644 docs/_posts/2017-05-12-partitioned-index-filter.markdown delete mode 100644 docs/_posts/2017-05-14-core-local-stats.markdown delete mode 100644 docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown delete mode 100644 docs/_posts/2017-06-26-17-level-based-changes.markdown delete mode 100644 docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown delete mode 100644 docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown delete mode 100644 docs/_posts/2017-08-24-pinnableslice.markdown delete mode 100644 docs/_posts/2017-08-25-flushwal.markdown delete mode 100644 docs/_posts/2017-09-28-rocksdb-5-8-released.markdown delete mode 100644 docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown delete mode 100644 docs/_posts/2017-12-19-write-prepared-txn.markdown delete mode 100644 docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown delete mode 100644 docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown delete mode 100644 docs/_posts/2018-08-23-data-block-hash-index.markdown delete mode 100644 docs/_posts/2018-11-21-delete-range.markdown delete mode 100644 docs/_posts/2019-03-08-format-version-4.markdown delete mode 100644 docs/_posts/2019-08-15-unordered-write.markdown delete mode 100644 docs/_posts/2021-04-12-universal-improvements.markdown delete mode 100644 docs/_posts/2021-05-26-integrated-blob-db.markdown delete mode 100644 docs/_posts/2021-05-26-online-validation.markdown delete mode 100644 docs/_posts/2021-05-27-rocksdb-secondary-cache.markdown delete mode 100644 docs/_posts/2021-05-31-dictionary-compression.markdown delete mode 100644 docs/_posts/2021-12-29-ribbon-filter.markdown delete mode 100644 docs/_posts/2022-07-18-per-key-value-checksum.markdown delete mode 100644 docs/_posts/2022-10-05-lost-buffered-write-recovery.markdown delete mode 100644 docs/_posts/2022-10-07-asynchronous-io-in-rocksdb.markdown delete mode 100644 docs/_posts/2022-10-31-align-compaction-output-file.markdown delete mode 100644 docs/_posts/2022-11-09-time-aware-tiered-storage.markdown delete mode 100644 docs/_sass/_base.scss delete mode 100644 docs/_sass/_blog.scss delete mode 100644 docs/_sass/_buttons.scss delete mode 100644 docs/_sass/_footer.scss delete mode 100644 docs/_sass/_gridBlock.scss delete mode 100644 docs/_sass/_header.scss delete mode 100644 docs/_sass/_poweredby.scss delete mode 100644 docs/_sass/_promo.scss delete mode 100644 docs/_sass/_react_docs_nav.scss delete mode 100644 docs/_sass/_react_header_nav.scss delete mode 100644 docs/_sass/_reset.scss delete mode 100644 docs/_sass/_search.scss delete mode 100644 docs/_sass/_slideshow.scss delete mode 100644 docs/_sass/_syntax-highlighting.scss delete mode 100644 docs/_sass/_tables.scss delete mode 100644 docs/_top-level/support.md delete mode 100644 docs/blog/all.html delete mode 100644 docs/blog/index.html delete mode 100644 docs/css/main.scss delete mode 100644 docs/doc-type-examples/2016-04-07-blog-post-example.md delete mode 100644 docs/doc-type-examples/docs-hello-world.md delete mode 100644 docs/doc-type-examples/top-level-example.md delete mode 100644 docs/docs/index.html delete mode 100644 docs/feed.xml delete mode 100644 docs/index.md delete mode 100644 docs/static/favicon.png delete mode 100644 docs/static/fonts/LatoLatin-Black.woff delete mode 100644 docs/static/fonts/LatoLatin-Black.woff2 delete mode 100644 docs/static/fonts/LatoLatin-BlackItalic.woff delete mode 100644 docs/static/fonts/LatoLatin-BlackItalic.woff2 delete mode 100644 docs/static/fonts/LatoLatin-Italic.woff delete mode 100644 docs/static/fonts/LatoLatin-Italic.woff2 delete mode 100644 docs/static/fonts/LatoLatin-Light.woff delete mode 100644 docs/static/fonts/LatoLatin-Light.woff2 delete mode 100644 docs/static/fonts/LatoLatin-Regular.woff delete mode 100644 docs/static/fonts/LatoLatin-Regular.woff2 delete mode 100644 docs/static/images/Resize-of-20140327_200754-300x225.jpg delete mode 100644 docs/static/images/align-compaction-output/compaction_output_file_size_compare.png delete mode 100644 docs/static/images/align-compaction-output/file_cut_align.png delete mode 100644 docs/static/images/align-compaction-output/file_cut_normal.png delete mode 100644 docs/static/images/align-compaction-output/file_cut_trival_move.png delete mode 100644 docs/static/images/align-compaction-output/file_size_compare.png delete mode 100644 docs/static/images/align-compaction-output/write_amp_compare.png delete mode 100644 docs/static/images/asynchronous-io/mget_async.png delete mode 100644 docs/static/images/asynchronous-io/scan_async.png delete mode 100644 docs/static/images/binaryseek.png delete mode 100644 docs/static/images/bloom_fp_vs_bpk.png delete mode 100644 docs/static/images/compaction/full-range.png delete mode 100644 docs/static/images/compaction/l0-l1-contend.png delete mode 100644 docs/static/images/compaction/l1-l2-contend.png delete mode 100644 docs/static/images/compaction/part-range-old.png delete mode 100644 docs/static/images/data-block-hash-index/block-format-binary-seek.png delete mode 100644 docs/static/images/data-block-hash-index/block-format-hash-index.png delete mode 100644 docs/static/images/data-block-hash-index/hash-index-data-structure.png delete mode 100644 docs/static/images/data-block-hash-index/perf-cache-miss.png delete mode 100644 docs/static/images/data-block-hash-index/perf-throughput.png delete mode 100644 docs/static/images/delrange/delrange_collapsed.png delete mode 100644 docs/static/images/delrange/delrange_key_schema.png delete mode 100644 docs/static/images/delrange/delrange_sst_blocks.png delete mode 100644 docs/static/images/delrange/delrange_uncollapsed.png delete mode 100644 docs/static/images/delrange/delrange_write_path.png delete mode 100644 docs/static/images/dictcmp/dictcmp_raw_sampled.png delete mode 100644 docs/static/images/dictcmp/dictcmp_sst_blocks.png delete mode 100644 docs/static/images/dictcmp/dictcmp_zstd_trained.png delete mode 100644 docs/static/images/integrated-blob-db/BlobDB_Benchmarks_Legacy_Vs_Integrated.png delete mode 100644 docs/static/images/integrated-blob-db/BlobDB_Benchmarks_RW_RO_Perf.png delete mode 100644 docs/static/images/integrated-blob-db/BlobDB_Benchmarks_Write_Amp.png delete mode 100644 docs/static/images/integrated-blob-db/BlobDB_Benchmarks_Write_Perf.png delete mode 100644 docs/static/images/kv-checksum/Memtable-entry.png delete mode 100644 docs/static/images/kv-checksum/Memtable-write.png delete mode 100644 docs/static/images/kv-checksum/ProtInfo-Memtable.png delete mode 100644 docs/static/images/kv-checksum/ProtInfo-Writebatch-to-Memtable.png delete mode 100644 docs/static/images/kv-checksum/ProtInfo-Writebatch.png delete mode 100644 docs/static/images/kv-checksum/WAL-fragment.png delete mode 100644 docs/static/images/kv-checksum/WAL-read.png delete mode 100644 docs/static/images/kv-checksum/WAL-write.png delete mode 100644 docs/static/images/kv-checksum/Write-batch.png delete mode 100644 docs/static/images/kv-checksum/Writebatch-write.png delete mode 100644 docs/static/images/lost-buffered-write-recovery/angry-cat.png delete mode 100644 docs/static/images/lost-buffered-write-recovery/basic-setup.png delete mode 100644 docs/static/images/lost-buffered-write-recovery/happy-cat.png delete mode 100644 docs/static/images/lost-buffered-write-recovery/replay-extension.png delete mode 100644 docs/static/images/lost-buffered-write-recovery/test-fs-writable-file.png delete mode 100644 docs/static/images/lost-buffered-write-recovery/trace-extension.png delete mode 100644 docs/static/images/pcache-blockindex.jpg delete mode 100644 docs/static/images/pcache-fileindex.jpg delete mode 100644 docs/static/images/pcache-filelayout.jpg delete mode 100644 docs/static/images/pcache-readiopath.jpg delete mode 100644 docs/static/images/pcache-tieredstorage.jpg delete mode 100644 docs/static/images/pcache-writeiopath.jpg delete mode 100644 docs/static/images/promo-adapt.svg delete mode 100644 docs/static/images/promo-flash.svg delete mode 100644 docs/static/images/promo-operations.svg delete mode 100644 docs/static/images/promo-performance.svg delete mode 100644 docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png delete mode 100644 docs/static/images/rate-limiter/write-KBps-cdf.png delete mode 100644 docs/static/images/rate-limiter/write-KBps-series.png delete mode 100644 docs/static/images/rocksdb-secondary-cache/Mixgraph_hit_rate.png delete mode 100644 docs/static/images/rocksdb-secondary-cache/Mixgraph_throughput.png delete mode 100644 docs/static/images/rocksdb-secondary-cache/arch_diagram.png delete mode 100644 docs/static/images/rocksdb-secondary-cache/insert_flow.png delete mode 100644 docs/static/images/rocksdb-secondary-cache/lookup_flow.png delete mode 100644 docs/static/images/time-aware-tiered-storage/compaction_moving_up_conflict.png delete mode 100644 docs/static/images/time-aware-tiered-storage/per_key_placement_compaction.png delete mode 100644 docs/static/images/time-aware-tiered-storage/tiered_storage_design.png delete mode 100644 docs/static/images/time-aware-tiered-storage/tiered_storage_overview.png delete mode 100644 docs/static/images/time-aware-tiered-storage/tiered_storage_problem.png delete mode 100644 docs/static/images/tree_example1.png delete mode 100644 docs/static/logo.svg delete mode 100644 docs/static/og_image.png delete mode 100644 env/env_basic_test.cc delete mode 100644 env/env_test.cc delete mode 100644 env/io_posix_test.cc delete mode 100644 env/mock_env_test.cc delete mode 100644 examples/.gitignore delete mode 100644 examples/CMakeLists.txt delete mode 100644 examples/Makefile delete mode 100644 examples/README.md delete mode 100644 examples/c_simple_example.c delete mode 100644 examples/column_families_example.cc delete mode 100644 examples/compact_files_example.cc delete mode 100644 examples/compaction_filter_example.cc delete mode 100644 examples/multi_processes_example.cc delete mode 100644 examples/optimistic_transaction_example.cc delete mode 100644 examples/options_file_example.cc delete mode 100644 examples/rocksdb_backup_restore_example.cc delete mode 100644 examples/rocksdb_option_file_example.ini delete mode 100644 examples/simple_example.cc delete mode 100644 examples/transaction_example.cc delete mode 100644 file/delete_scheduler_test.cc delete mode 100644 file/prefetch_test.cc delete mode 100644 file/random_access_file_reader_test.cc delete mode 100644 fuzz/.gitignore delete mode 100644 fuzz/Makefile delete mode 100644 fuzz/README.md delete mode 100644 fuzz/db_fuzzer.cc delete mode 100644 fuzz/db_map_fuzzer.cc delete mode 100644 fuzz/proto/db_operation.proto delete mode 100644 fuzz/sst_file_writer_fuzzer.cc delete mode 100644 fuzz/util.h delete mode 100644 java/CMakeLists.txt delete mode 100644 java/GetBenchmarks.md delete mode 100644 java/HISTORY-JAVA.md delete mode 100644 java/Makefile delete mode 100644 java/RELEASE.md delete mode 100644 java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java delete mode 100644 java/crossbuild/Vagrantfile delete mode 100755 java/crossbuild/build-linux-alpine.sh delete mode 100755 java/crossbuild/build-linux-centos.sh delete mode 100755 java/crossbuild/build-linux.sh delete mode 100755 java/crossbuild/docker-build-linux-alpine.sh delete mode 100755 java/crossbuild/docker-build-linux-centos.sh delete mode 100755 java/jdb_bench.sh delete mode 100644 java/jmh/LICENSE-HEADER.txt delete mode 100644 java/jmh/README.md delete mode 100644 java/jmh/pom.xml delete mode 100644 java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java delete mode 100644 java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java delete mode 100644 java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java delete mode 100644 java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java delete mode 100644 java/jmh/src/main/java/org/rocksdb/util/FileUtils.java delete mode 100644 java/jmh/src/main/java/org/rocksdb/util/KVUtils.java delete mode 100644 java/pom.xml.template delete mode 100644 java/rocksjni/backup_engine_options.cc delete mode 100644 java/rocksjni/backupenginejni.cc delete mode 100644 java/rocksjni/cache.cc delete mode 100644 java/rocksjni/cassandra_compactionfilterjni.cc delete mode 100644 java/rocksjni/cassandra_value_operator.cc delete mode 100644 java/rocksjni/checkpoint.cc delete mode 100644 java/rocksjni/clock_cache.cc delete mode 100644 java/rocksjni/columnfamilyhandle.cc delete mode 100644 java/rocksjni/compact_range_options.cc delete mode 100644 java/rocksjni/compaction_filter.cc delete mode 100644 java/rocksjni/compaction_filter_factory.cc delete mode 100644 java/rocksjni/compaction_filter_factory_jnicallback.cc delete mode 100644 java/rocksjni/compaction_filter_factory_jnicallback.h delete mode 100644 java/rocksjni/compaction_job_info.cc delete mode 100644 java/rocksjni/compaction_job_stats.cc delete mode 100644 java/rocksjni/compaction_options.cc delete mode 100644 java/rocksjni/compaction_options_fifo.cc delete mode 100644 java/rocksjni/compaction_options_universal.cc delete mode 100644 java/rocksjni/comparator.cc delete mode 100644 java/rocksjni/comparatorjnicallback.cc delete mode 100644 java/rocksjni/comparatorjnicallback.h delete mode 100644 java/rocksjni/compression_options.cc delete mode 100644 java/rocksjni/concurrent_task_limiter.cc delete mode 100644 java/rocksjni/config_options.cc delete mode 100644 java/rocksjni/cplusplus_to_java_convert.h delete mode 100644 java/rocksjni/env.cc delete mode 100644 java/rocksjni/env_options.cc delete mode 100644 java/rocksjni/event_listener.cc delete mode 100644 java/rocksjni/event_listener_jnicallback.cc delete mode 100644 java/rocksjni/event_listener_jnicallback.h delete mode 100644 java/rocksjni/filter.cc delete mode 100644 java/rocksjni/ingest_external_file_options.cc delete mode 100644 java/rocksjni/iterator.cc delete mode 100644 java/rocksjni/jnicallback.cc delete mode 100644 java/rocksjni/jnicallback.h delete mode 100644 java/rocksjni/loggerjnicallback.cc delete mode 100644 java/rocksjni/loggerjnicallback.h delete mode 100644 java/rocksjni/lru_cache.cc delete mode 100644 java/rocksjni/memory_util.cc delete mode 100644 java/rocksjni/memtablejni.cc delete mode 100644 java/rocksjni/merge_operator.cc delete mode 100644 java/rocksjni/native_comparator_wrapper_test.cc delete mode 100644 java/rocksjni/optimistic_transaction_db.cc delete mode 100644 java/rocksjni/optimistic_transaction_options.cc delete mode 100644 java/rocksjni/options.cc delete mode 100644 java/rocksjni/options_util.cc delete mode 100644 java/rocksjni/persistent_cache.cc delete mode 100644 java/rocksjni/portal.h delete mode 100644 java/rocksjni/ratelimiterjni.cc delete mode 100644 java/rocksjni/remove_emptyvalue_compactionfilterjni.cc delete mode 100644 java/rocksjni/restorejni.cc delete mode 100644 java/rocksjni/rocks_callback_object.cc delete mode 100644 java/rocksjni/rocksdb_exception_test.cc delete mode 100644 java/rocksjni/rocksjni.cc delete mode 100644 java/rocksjni/slice.cc delete mode 100644 java/rocksjni/snapshot.cc delete mode 100644 java/rocksjni/sst_file_manager.cc delete mode 100644 java/rocksjni/sst_file_reader_iterator.cc delete mode 100644 java/rocksjni/sst_file_readerjni.cc delete mode 100644 java/rocksjni/sst_file_writerjni.cc delete mode 100644 java/rocksjni/sst_partitioner.cc delete mode 100644 java/rocksjni/statistics.cc delete mode 100644 java/rocksjni/statisticsjni.cc delete mode 100644 java/rocksjni/statisticsjni.h delete mode 100644 java/rocksjni/table.cc delete mode 100644 java/rocksjni/table_filter.cc delete mode 100644 java/rocksjni/table_filter_jnicallback.cc delete mode 100644 java/rocksjni/table_filter_jnicallback.h delete mode 100644 java/rocksjni/testable_event_listener.cc delete mode 100644 java/rocksjni/thread_status.cc delete mode 100644 java/rocksjni/trace_writer.cc delete mode 100644 java/rocksjni/trace_writer_jnicallback.cc delete mode 100644 java/rocksjni/trace_writer_jnicallback.h delete mode 100644 java/rocksjni/transaction.cc delete mode 100644 java/rocksjni/transaction_db.cc delete mode 100644 java/rocksjni/transaction_db_options.cc delete mode 100644 java/rocksjni/transaction_log.cc delete mode 100644 java/rocksjni/transaction_notifier.cc delete mode 100644 java/rocksjni/transaction_notifier_jnicallback.cc delete mode 100644 java/rocksjni/transaction_notifier_jnicallback.h delete mode 100644 java/rocksjni/transaction_options.cc delete mode 100644 java/rocksjni/ttl.cc delete mode 100644 java/rocksjni/wal_filter.cc delete mode 100644 java/rocksjni/wal_filter_jnicallback.cc delete mode 100644 java/rocksjni/wal_filter_jnicallback.h delete mode 100644 java/rocksjni/write_batch.cc delete mode 100644 java/rocksjni/write_batch_test.cc delete mode 100644 java/rocksjni/write_batch_with_index.cc delete mode 100644 java/rocksjni/write_buffer_manager.cc delete mode 100644 java/rocksjni/writebatchhandlerjnicallback.cc delete mode 100644 java/rocksjni/writebatchhandlerjnicallback.h delete mode 100644 java/samples/src/main/java/OptimisticTransactionSample.java delete mode 100644 java/samples/src/main/java/RocksDBColumnFamilySample.java delete mode 100644 java/samples/src/main/java/RocksDBSample.java delete mode 100644 java/samples/src/main/java/TransactionSample.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractCompactionFilter.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractComparator.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractEventListener.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractMutableOptions.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractNativeReference.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractRocksIterator.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractSlice.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractTableFilter.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractTraceWriter.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractWalFilter.java delete mode 100644 java/src/main/java/org/rocksdb/AbstractWriteBatch.java delete mode 100644 java/src/main/java/org/rocksdb/AccessHint.java delete mode 100644 java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java delete mode 100644 java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java delete mode 100644 java/src/main/java/org/rocksdb/BackgroundErrorReason.java delete mode 100644 java/src/main/java/org/rocksdb/BackupEngine.java delete mode 100644 java/src/main/java/org/rocksdb/BackupEngineOptions.java delete mode 100644 java/src/main/java/org/rocksdb/BackupInfo.java delete mode 100644 java/src/main/java/org/rocksdb/BlockBasedTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/BloomFilter.java delete mode 100644 java/src/main/java/org/rocksdb/BuiltinComparator.java delete mode 100644 java/src/main/java/org/rocksdb/ByteBufferGetStatus.java delete mode 100644 java/src/main/java/org/rocksdb/Cache.java delete mode 100644 java/src/main/java/org/rocksdb/CassandraCompactionFilter.java delete mode 100644 java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java delete mode 100644 java/src/main/java/org/rocksdb/Checkpoint.java delete mode 100644 java/src/main/java/org/rocksdb/ChecksumType.java delete mode 100644 java/src/main/java/org/rocksdb/ClockCache.java delete mode 100644 java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java delete mode 100644 java/src/main/java/org/rocksdb/ColumnFamilyHandle.java delete mode 100644 java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java delete mode 100644 java/src/main/java/org/rocksdb/ColumnFamilyOptions.java delete mode 100644 java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java delete mode 100644 java/src/main/java/org/rocksdb/CompactRangeOptions.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionJobInfo.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionJobStats.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionOptions.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionPriority.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionReason.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionStopStyle.java delete mode 100644 java/src/main/java/org/rocksdb/CompactionStyle.java delete mode 100644 java/src/main/java/org/rocksdb/ComparatorOptions.java delete mode 100644 java/src/main/java/org/rocksdb/ComparatorType.java delete mode 100644 java/src/main/java/org/rocksdb/CompressionOptions.java delete mode 100644 java/src/main/java/org/rocksdb/CompressionType.java delete mode 100644 java/src/main/java/org/rocksdb/ConcurrentTaskLimiter.java delete mode 100644 java/src/main/java/org/rocksdb/ConcurrentTaskLimiterImpl.java delete mode 100644 java/src/main/java/org/rocksdb/ConfigOptions.java delete mode 100644 java/src/main/java/org/rocksdb/DBOptions.java delete mode 100644 java/src/main/java/org/rocksdb/DBOptionsInterface.java delete mode 100644 java/src/main/java/org/rocksdb/DataBlockIndexType.java delete mode 100644 java/src/main/java/org/rocksdb/DbPath.java delete mode 100644 java/src/main/java/org/rocksdb/DirectSlice.java delete mode 100644 java/src/main/java/org/rocksdb/EncodingType.java delete mode 100644 java/src/main/java/org/rocksdb/Env.java delete mode 100644 java/src/main/java/org/rocksdb/EnvOptions.java delete mode 100644 java/src/main/java/org/rocksdb/EventListener.java delete mode 100644 java/src/main/java/org/rocksdb/Experimental.java delete mode 100644 java/src/main/java/org/rocksdb/ExternalFileIngestionInfo.java delete mode 100644 java/src/main/java/org/rocksdb/FileOperationInfo.java delete mode 100644 java/src/main/java/org/rocksdb/Filter.java delete mode 100644 java/src/main/java/org/rocksdb/FlushJobInfo.java delete mode 100644 java/src/main/java/org/rocksdb/FlushOptions.java delete mode 100644 java/src/main/java/org/rocksdb/FlushReason.java delete mode 100644 java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/HistogramData.java delete mode 100644 java/src/main/java/org/rocksdb/HistogramType.java delete mode 100644 java/src/main/java/org/rocksdb/Holder.java delete mode 100644 java/src/main/java/org/rocksdb/IndexShorteningMode.java delete mode 100644 java/src/main/java/org/rocksdb/IndexType.java delete mode 100644 java/src/main/java/org/rocksdb/InfoLogLevel.java delete mode 100644 java/src/main/java/org/rocksdb/IngestExternalFileOptions.java delete mode 100644 java/src/main/java/org/rocksdb/KeyMayExist.java delete mode 100644 java/src/main/java/org/rocksdb/LRUCache.java delete mode 100644 java/src/main/java/org/rocksdb/LevelMetaData.java delete mode 100644 java/src/main/java/org/rocksdb/LiveFileMetaData.java delete mode 100644 java/src/main/java/org/rocksdb/LogFile.java delete mode 100644 java/src/main/java/org/rocksdb/Logger.java delete mode 100644 java/src/main/java/org/rocksdb/MemTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/MemTableInfo.java delete mode 100644 java/src/main/java/org/rocksdb/MemoryUsageType.java delete mode 100644 java/src/main/java/org/rocksdb/MemoryUtil.java delete mode 100644 java/src/main/java/org/rocksdb/MergeOperator.java delete mode 100644 java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java delete mode 100644 java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java delete mode 100644 java/src/main/java/org/rocksdb/MutableDBOptions.java delete mode 100644 java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java delete mode 100644 java/src/main/java/org/rocksdb/MutableOptionKey.java delete mode 100644 java/src/main/java/org/rocksdb/MutableOptionValue.java delete mode 100644 java/src/main/java/org/rocksdb/NativeComparatorWrapper.java delete mode 100644 java/src/main/java/org/rocksdb/NativeLibraryLoader.java delete mode 100644 java/src/main/java/org/rocksdb/OperationStage.java delete mode 100644 java/src/main/java/org/rocksdb/OperationType.java delete mode 100644 java/src/main/java/org/rocksdb/OptimisticTransactionDB.java delete mode 100644 java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java delete mode 100644 java/src/main/java/org/rocksdb/OptionString.java delete mode 100644 java/src/main/java/org/rocksdb/Options.java delete mode 100644 java/src/main/java/org/rocksdb/OptionsUtil.java delete mode 100644 java/src/main/java/org/rocksdb/PersistentCache.java delete mode 100644 java/src/main/java/org/rocksdb/PlainTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/PrepopulateBlobCache.java delete mode 100644 java/src/main/java/org/rocksdb/Priority.java delete mode 100644 java/src/main/java/org/rocksdb/Range.java delete mode 100644 java/src/main/java/org/rocksdb/RateLimiter.java delete mode 100644 java/src/main/java/org/rocksdb/RateLimiterMode.java delete mode 100755 java/src/main/java/org/rocksdb/ReadOptions.java delete mode 100644 java/src/main/java/org/rocksdb/ReadTier.java delete mode 100644 java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java delete mode 100644 java/src/main/java/org/rocksdb/RestoreOptions.java delete mode 100644 java/src/main/java/org/rocksdb/ReusedSynchronisationType.java delete mode 100644 java/src/main/java/org/rocksdb/RocksCallbackObject.java delete mode 100644 java/src/main/java/org/rocksdb/RocksDB.java delete mode 100644 java/src/main/java/org/rocksdb/RocksDBException.java delete mode 100644 java/src/main/java/org/rocksdb/RocksEnv.java delete mode 100644 java/src/main/java/org/rocksdb/RocksIterator.java delete mode 100644 java/src/main/java/org/rocksdb/RocksIteratorInterface.java delete mode 100644 java/src/main/java/org/rocksdb/RocksMemEnv.java delete mode 100644 java/src/main/java/org/rocksdb/RocksMutableObject.java delete mode 100644 java/src/main/java/org/rocksdb/RocksObject.java delete mode 100644 java/src/main/java/org/rocksdb/SanityLevel.java delete mode 100644 java/src/main/java/org/rocksdb/SizeApproximationFlag.java delete mode 100644 java/src/main/java/org/rocksdb/SkipListMemTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/Slice.java delete mode 100644 java/src/main/java/org/rocksdb/Snapshot.java delete mode 100644 java/src/main/java/org/rocksdb/SstFileManager.java delete mode 100644 java/src/main/java/org/rocksdb/SstFileMetaData.java delete mode 100644 java/src/main/java/org/rocksdb/SstFileReader.java delete mode 100644 java/src/main/java/org/rocksdb/SstFileReaderIterator.java delete mode 100644 java/src/main/java/org/rocksdb/SstFileWriter.java delete mode 100644 java/src/main/java/org/rocksdb/SstPartitionerFactory.java delete mode 100644 java/src/main/java/org/rocksdb/SstPartitionerFixedPrefixFactory.java delete mode 100644 java/src/main/java/org/rocksdb/StateType.java delete mode 100644 java/src/main/java/org/rocksdb/Statistics.java delete mode 100644 java/src/main/java/org/rocksdb/StatisticsCollector.java delete mode 100644 java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java delete mode 100644 java/src/main/java/org/rocksdb/StatsCollectorInput.java delete mode 100644 java/src/main/java/org/rocksdb/StatsLevel.java delete mode 100644 java/src/main/java/org/rocksdb/Status.java delete mode 100644 java/src/main/java/org/rocksdb/StringAppendOperator.java delete mode 100644 java/src/main/java/org/rocksdb/TableFileCreationBriefInfo.java delete mode 100644 java/src/main/java/org/rocksdb/TableFileCreationInfo.java delete mode 100644 java/src/main/java/org/rocksdb/TableFileCreationReason.java delete mode 100644 java/src/main/java/org/rocksdb/TableFileDeletionInfo.java delete mode 100644 java/src/main/java/org/rocksdb/TableFilter.java delete mode 100644 java/src/main/java/org/rocksdb/TableFormatConfig.java delete mode 100644 java/src/main/java/org/rocksdb/TableProperties.java delete mode 100644 java/src/main/java/org/rocksdb/ThreadStatus.java delete mode 100644 java/src/main/java/org/rocksdb/ThreadType.java delete mode 100644 java/src/main/java/org/rocksdb/TickerType.java delete mode 100644 java/src/main/java/org/rocksdb/TimedEnv.java delete mode 100644 java/src/main/java/org/rocksdb/TraceOptions.java delete mode 100644 java/src/main/java/org/rocksdb/TraceWriter.java delete mode 100644 java/src/main/java/org/rocksdb/Transaction.java delete mode 100644 java/src/main/java/org/rocksdb/TransactionDB.java delete mode 100644 java/src/main/java/org/rocksdb/TransactionDBOptions.java delete mode 100644 java/src/main/java/org/rocksdb/TransactionLogIterator.java delete mode 100644 java/src/main/java/org/rocksdb/TransactionOptions.java delete mode 100644 java/src/main/java/org/rocksdb/TransactionalDB.java delete mode 100644 java/src/main/java/org/rocksdb/TransactionalOptions.java delete mode 100644 java/src/main/java/org/rocksdb/TtlDB.java delete mode 100644 java/src/main/java/org/rocksdb/TxnDBWritePolicy.java delete mode 100644 java/src/main/java/org/rocksdb/UInt64AddOperator.java delete mode 100644 java/src/main/java/org/rocksdb/VectorMemTableConfig.java delete mode 100644 java/src/main/java/org/rocksdb/WALRecoveryMode.java delete mode 100644 java/src/main/java/org/rocksdb/WBWIRocksIterator.java delete mode 100644 java/src/main/java/org/rocksdb/WalFileType.java delete mode 100644 java/src/main/java/org/rocksdb/WalFilter.java delete mode 100644 java/src/main/java/org/rocksdb/WalProcessingOption.java delete mode 100644 java/src/main/java/org/rocksdb/WriteBatch.java delete mode 100644 java/src/main/java/org/rocksdb/WriteBatchInterface.java delete mode 100644 java/src/main/java/org/rocksdb/WriteBatchWithIndex.java delete mode 100644 java/src/main/java/org/rocksdb/WriteBufferManager.java delete mode 100644 java/src/main/java/org/rocksdb/WriteOptions.java delete mode 100644 java/src/main/java/org/rocksdb/WriteStallCondition.java delete mode 100644 java/src/main/java/org/rocksdb/WriteStallInfo.java delete mode 100644 java/src/main/java/org/rocksdb/util/ByteUtil.java delete mode 100644 java/src/main/java/org/rocksdb/util/BytewiseComparator.java delete mode 100644 java/src/main/java/org/rocksdb/util/Environment.java delete mode 100644 java/src/main/java/org/rocksdb/util/IntComparator.java delete mode 100644 java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java delete mode 100644 java/src/main/java/org/rocksdb/util/SizeUnit.java delete mode 100644 java/src/test/java/org/rocksdb/AbstractTransactionTest.java delete mode 100644 java/src/test/java/org/rocksdb/BackupEngineOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/BackupEngineTest.java delete mode 100644 java/src/test/java/org/rocksdb/BlobOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java delete mode 100644 java/src/test/java/org/rocksdb/BuiltinComparatorTest.java delete mode 100644 java/src/test/java/org/rocksdb/ByteBufferUnsupportedOperationTest.java delete mode 100644 java/src/test/java/org/rocksdb/BytewiseComparatorRegressionTest.java delete mode 100644 java/src/test/java/org/rocksdb/CheckPointTest.java delete mode 100644 java/src/test/java/org/rocksdb/ClockCacheTest.java delete mode 100644 java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/ColumnFamilyTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionJobInfoTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionJobStatsTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionPriorityTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompactionStopStyleTest.java delete mode 100644 java/src/test/java/org/rocksdb/ComparatorOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompressionOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/CompressionTypesTest.java delete mode 100644 java/src/test/java/org/rocksdb/ConcurrentTaskLimiterTest.java delete mode 100644 java/src/test/java/org/rocksdb/DBOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/DefaultEnvTest.java delete mode 100644 java/src/test/java/org/rocksdb/DirectSliceTest.java delete mode 100644 java/src/test/java/org/rocksdb/EnvOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/EventListenerTest.java delete mode 100644 java/src/test/java/org/rocksdb/FilterTest.java delete mode 100644 java/src/test/java/org/rocksdb/FlushOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/FlushTest.java delete mode 100644 java/src/test/java/org/rocksdb/InfoLogLevelTest.java delete mode 100644 java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/KeyMayExistTest.java delete mode 100644 java/src/test/java/org/rocksdb/LRUCacheTest.java delete mode 100644 java/src/test/java/org/rocksdb/LoggerTest.java delete mode 100644 java/src/test/java/org/rocksdb/MemTableTest.java delete mode 100644 java/src/test/java/org/rocksdb/MemoryUtilTest.java delete mode 100644 java/src/test/java/org/rocksdb/MergeTest.java delete mode 100644 java/src/test/java/org/rocksdb/MixedOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/MultiColumnRegressionTest.java delete mode 100644 java/src/test/java/org/rocksdb/MultiGetManyKeysTest.java delete mode 100644 java/src/test/java/org/rocksdb/MultiGetTest.java delete mode 100644 java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/MutableDBOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/MutableOptionsGetSetTest.java delete mode 100644 java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java delete mode 100644 java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java delete mode 100644 java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java delete mode 100644 java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/OptimisticTransactionTest.java delete mode 100644 java/src/test/java/org/rocksdb/OptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/OptionsUtilTest.java delete mode 100644 java/src/test/java/org/rocksdb/PlainTableConfigTest.java delete mode 100644 java/src/test/java/org/rocksdb/PlatformRandomHelper.java delete mode 100644 java/src/test/java/org/rocksdb/PutMultiplePartsTest.java delete mode 100644 java/src/test/java/org/rocksdb/RateLimiterTest.java delete mode 100644 java/src/test/java/org/rocksdb/ReadOnlyTest.java delete mode 100644 java/src/test/java/org/rocksdb/ReadOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/RocksDBExceptionTest.java delete mode 100644 java/src/test/java/org/rocksdb/RocksDBTest.java delete mode 100644 java/src/test/java/org/rocksdb/RocksIteratorTest.java delete mode 100644 java/src/test/java/org/rocksdb/RocksMemEnvTest.java delete mode 100644 java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java delete mode 100644 java/src/test/java/org/rocksdb/SecondaryDBTest.java delete mode 100644 java/src/test/java/org/rocksdb/SliceTest.java delete mode 100644 java/src/test/java/org/rocksdb/SnapshotTest.java delete mode 100644 java/src/test/java/org/rocksdb/SstFileManagerTest.java delete mode 100644 java/src/test/java/org/rocksdb/SstFileReaderTest.java delete mode 100644 java/src/test/java/org/rocksdb/SstFileWriterTest.java delete mode 100644 java/src/test/java/org/rocksdb/SstPartitionerTest.java delete mode 100644 java/src/test/java/org/rocksdb/StatisticsCollectorTest.java delete mode 100644 java/src/test/java/org/rocksdb/StatisticsTest.java delete mode 100644 java/src/test/java/org/rocksdb/StatsCallbackMock.java delete mode 100644 java/src/test/java/org/rocksdb/TableFilterTest.java delete mode 100644 java/src/test/java/org/rocksdb/TimedEnvTest.java delete mode 100644 java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/TransactionDBTest.java delete mode 100644 java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java delete mode 100644 java/src/test/java/org/rocksdb/TransactionOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/TransactionTest.java delete mode 100644 java/src/test/java/org/rocksdb/TtlDBTest.java delete mode 100644 java/src/test/java/org/rocksdb/Types.java delete mode 100644 java/src/test/java/org/rocksdb/VerifyChecksumsTest.java delete mode 100644 java/src/test/java/org/rocksdb/WALRecoveryModeTest.java delete mode 100644 java/src/test/java/org/rocksdb/WalFilterTest.java delete mode 100644 java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java delete mode 100644 java/src/test/java/org/rocksdb/WriteBatchTest.java delete mode 100644 java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java delete mode 100644 java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java delete mode 100644 java/src/test/java/org/rocksdb/WriteOptionsTest.java delete mode 100644 java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java delete mode 100644 java/src/test/java/org/rocksdb/test/RocksJunitRunner.java delete mode 100644 java/src/test/java/org/rocksdb/test/TestableEventListener.java delete mode 100644 java/src/test/java/org/rocksdb/util/ByteBufferAllocator.java delete mode 100644 java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java delete mode 100644 java/src/test/java/org/rocksdb/util/DirectByteBufferAllocator.java delete mode 100644 java/src/test/java/org/rocksdb/util/EnvironmentTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/HeapByteBufferAllocator.java delete mode 100644 java/src/test/java/org/rocksdb/util/IntComparatorTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/JNIComparatorTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/SizeUnitTest.java delete mode 100644 java/src/test/java/org/rocksdb/util/TestUtil.java delete mode 100644 java/src/test/java/org/rocksdb/util/WriteBatchGetter.java delete mode 100644 java/understanding_options.md delete mode 100644 logging/auto_roll_logger_test.cc delete mode 100644 logging/env_logger_test.cc delete mode 100644 logging/event_logger_test.cc delete mode 100644 memory/arena_test.cc delete mode 100644 memory/memory_allocator_test.cc delete mode 100644 memtable/inlineskiplist_test.cc delete mode 100644 memtable/skiplist_test.cc delete mode 100644 memtable/write_buffer_manager_test.cc delete mode 100644 microbench/CMakeLists.txt delete mode 100644 microbench/README.md delete mode 100644 microbench/db_basic_bench.cc delete mode 100644 microbench/ribbon_bench.cc delete mode 100644 monitoring/histogram_test.cc delete mode 100644 monitoring/iostats_context_test.cc delete mode 100644 monitoring/statistics_test.cc delete mode 100644 monitoring/stats_history_test.cc delete mode 100644 options/configurable_test.cc delete mode 100644 options/customizable_test.cc delete mode 100644 options/options_settable_test.cc delete mode 100644 options/options_test.cc delete mode 100644 table/block_fetcher_test.cc delete mode 100644 table/cleanable_test.cc delete mode 100644 table/merger_test.cc delete mode 100644 table/sst_file_reader_test.cc delete mode 100644 table/table_test.cc delete mode 100644 test_util/testutil_test.cc delete mode 100644 tools/db_bench_tool_test.cc delete mode 100644 tools/db_sanity_test.cc delete mode 100644 tools/io_tracer_parser_test.cc delete mode 100644 tools/ldb_cmd_test.cc delete mode 100644 tools/reduce_levels_test.cc delete mode 100644 tools/sst_dump_test.cc delete mode 100644 tools/trace_analyzer_test.cc delete mode 100644 trace_replay/block_cache_tracer_test.cc delete mode 100644 trace_replay/io_tracer_test.cc delete mode 100644 util/autovector_test.cc delete mode 100644 util/bloom_test.cc delete mode 100644 util/coding_test.cc delete mode 100644 util/crc32c_test.cc delete mode 100644 util/defer_test.cc delete mode 100644 util/dynamic_bloom_test.cc delete mode 100644 util/file_reader_writer_test.cc delete mode 100644 util/filelock_test.cc delete mode 100644 util/hash_test.cc delete mode 100644 util/heap_test.cc delete mode 100644 util/random_test.cc delete mode 100644 util/rate_limiter_test.cc delete mode 100644 util/repeatable_thread_test.cc delete mode 100644 util/ribbon_test.cc delete mode 100644 util/slice_test.cc delete mode 100644 util/slice_transform_test.cc delete mode 100644 util/thread_list_test.cc delete mode 100644 util/thread_local_test.cc delete mode 100644 util/timer_queue_test.cc delete mode 100644 util/timer_test.cc delete mode 100644 util/work_queue_test.cc delete mode 100644 utilities/env_mirror_test.cc delete mode 100644 utilities/env_timed_test.cc delete mode 100644 utilities/object_registry_test.cc delete mode 100644 utilities/util_merge_operators_test.cc diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c614d3f0e..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,892 +0,0 @@ -version: 2.1 - -orbs: - win: circleci/windows@5.0.0 - -commands: - install-cmake-on-macos: - steps: - - run: - name: Install cmake on macos - command: | - HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake - - install-jdk8-on-macos: - steps: - - run: - name: Install JDK 8 on macos - command: | - brew install --cask adoptopenjdk/openjdk/adoptopenjdk8 - - increase-max-open-files-on-macos: - steps: - - run: - name: Increase max open files - command: | - sudo sysctl -w kern.maxfiles=1048576 - sudo sysctl -w kern.maxfilesperproc=1048576 - sudo launchctl limit maxfiles 1048576 - - pre-steps: - steps: - - checkout - - run: - name: Setup Environment Variables - command: | - echo "export GTEST_THROW_ON_FAILURE=0" >> $BASH_ENV - echo "export GTEST_OUTPUT=\"xml:/tmp/test-results/\"" >> $BASH_ENV - echo "export SKIP_FORMAT_BUCK_CHECKS=1" >> $BASH_ENV - echo "export GTEST_COLOR=1" >> $BASH_ENV - echo "export CTEST_OUTPUT_ON_FAILURE=1" >> $BASH_ENV - echo "export CTEST_TEST_TIMEOUT=300" >> $BASH_ENV - echo "export ZLIB_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/zlib" >> $BASH_ENV - echo "export BZIP2_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/bzip2" >> $BASH_ENV - echo "export SNAPPY_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/snappy" >> $BASH_ENV - echo "export LZ4_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/lz4" >> $BASH_ENV - echo "export ZSTD_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/zstd" >> $BASH_ENV - - windows-build-steps: - steps: - - checkout - - run: - name: "Install thirdparty dependencies" - command: | - echo "Installing CMake..." - choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -y - mkdir $Env:THIRDPARTY_HOME - cd $Env:THIRDPARTY_HOME - echo "Building Snappy dependency..." - curl https://github.com/google/snappy/archive/refs/tags/1.1.8.zip -O snappy-1.1.8.zip - unzip -q snappy-1.1.8.zip - cd snappy-1.1.8 - mkdir build - cd build - & $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" .. - msbuild.exe Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64 - - run: - name: "Build RocksDB" - command: | - mkdir build - cd build - & $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 .. - cd .. - echo "Building with VS version: $Env:CMAKE_GENERATOR" - msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64 - - run: - name: "Test RocksDB" - shell: powershell.exe - command: | - build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16 - pre-steps-macos: - steps: - - pre-steps - - post-steps: - steps: - - store_test_results: # store test result if there's any - path: /tmp/test-results - - store_artifacts: # store LOG for debugging if there's any - path: LOG - - run: # on fail, compress Test Logs for diagnosing the issue - name: Compress Test Logs - command: tar -cvzf t.tar.gz t - when: on_fail - - store_artifacts: # on fail, store Test Logs for diagnosing the issue - path: t.tar.gz - destination: test_logs - when: on_fail - - run: # store core dumps if there's any - command: | - mkdir -p /tmp/core_dumps - cp core.* /tmp/core_dumps - when: on_fail - - store_artifacts: - path: /tmp/core_dumps - when: on_fail - - upgrade-cmake: - steps: - - run: - name: Upgrade cmake - command: | - sudo apt remove --purge cmake - sudo snap install cmake --classic - - install-gflags: - steps: - - run: - name: Install gflags - command: | - sudo apt-get update -y && sudo apt-get install -y libgflags-dev - - install-gflags-on-macos: - steps: - - run: - name: Install gflags on macos - command: | - HOMEBREW_NO_AUTO_UPDATE=1 brew install gflags - - setup-folly: - steps: - - run: - name: Checkout folly sources - command: | - make checkout_folly - - build-folly: - steps: - - run: - name: Build folly and dependencies - command: | - make build_folly - - build-for-benchmarks: - steps: - - pre-steps - - run: - name: "Linux build for benchmarks" - command: #sized for the resource-class rocksdb-benchmark-sys1 - make V=1 J=8 -j8 release - - perform-benchmarks: - steps: - - run: - name: "Test low-variance benchmarks" - command: ./tools/benchmark_ci.py --db_dir /tmp/rocksdb-benchmark-datadir --output_dir /tmp/benchmark-results --num_keys 20000000 - environment: - LD_LIBRARY_PATH: /usr/local/lib - # How long to run parts of the test(s) - DURATION_RO: 300 - DURATION_RW: 500 - # Keep threads within physical capacity of server (much lower than default) - NUM_THREADS: 1 - MAX_BACKGROUND_JOBS: 4 - # Don't run a couple of "optional" initial tests - CI_TESTS_ONLY: "true" - # Reduce configured size of levels to ensure more levels in the leveled compaction LSM tree - WRITE_BUFFER_SIZE_MB: 16 - TARGET_FILE_SIZE_BASE_MB: 16 - MAX_BYTES_FOR_LEVEL_BASE_MB: 64 - # The benchmark host has 32GB memory - # The following values are tailored to work with that - # Note, tests may not exercise the targeted issues if the memory is increased on new test hosts. - COMPRESSION_TYPE: "none" - CACHE_INDEX_AND_FILTER_BLOCKS: 1 - MIN_LEVEL_TO_COMPRESS: 3 - CACHE_SIZE_MB: 10240 - MB_WRITE_PER_SEC: 2 - - post-benchmarks: - steps: - - store_artifacts: # store the benchmark output - path: /tmp/benchmark-results - destination: test_logs - - run: - name: Send benchmark report to visualisation - command: | - set +e - set +o pipefail - ./build_tools/benchmark_log_tool.py --tsvfile /tmp/benchmark-results/report.tsv --esdocument https://search-rocksdb-bench-k2izhptfeap2hjfxteolsgsynm.us-west-2.es.amazonaws.com/bench_test3_rix/_doc - true - -executors: - linux-docker: - docker: - # The image configuration is build_tools/ubuntu20_image/Dockerfile - # To update and build the image: - # $ cd build_tools/ubuntu20_image - # $ docker build -t zjay437/rocksdb:0.5 . - # $ docker push zjay437/rocksdb:0.5 - # `zjay437` is the account name for zjay@meta.com which readwrite token is shared internally. To login: - # $ docker login --username zjay437 - # Or please feel free to change it to your docker hub account for hosting the image, meta employee should already have the account and able to login with SSO. - # To avoid impacting the existing CI runs, please bump the version every time creating a new image - # to run the CI image environment locally: - # $ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it zjay437/rocksdb:0.5 bash - # option `--cap-add=SYS_PTRACE --security-opt seccomp=unconfined` is used to enable gdb to attach an existing process - - image: zjay437/rocksdb:0.6 - -jobs: - build-macos: - macos: - xcode: 12.5.1 - resource_class: large - environment: - ROCKSDB_DISABLE_JEMALLOC: 1 # jemalloc cause env_test hang, disable it for now - steps: - - increase-max-open-files-on-macos - - install-gflags-on-macos - - pre-steps-macos - - run: ulimit -S -n `ulimit -H -n` && OPT=-DCIRCLECI make V=1 J=32 -j32 all - - post-steps - - build-macos-cmake: - macos: - xcode: 12.5.1 - resource_class: large - parameters: - run_even_tests: - description: run even or odd tests, used to split tests to 2 groups - type: boolean - default: true - steps: - - increase-max-open-files-on-macos - - install-cmake-on-macos - - install-gflags-on-macos - - pre-steps-macos - - run: - name: "cmake generate project file" - command: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. - - run: - name: "Build tests" - command: cd build && make V=1 -j32 - - when: - condition: << parameters.run_even_tests >> - steps: - - run: - name: "Run even tests" - command: ulimit -S -n `ulimit -H -n` && cd build && ctest -j32 -I 0,,2 - - when: - condition: - not: << parameters.run_even_tests >> - steps: - - run: - name: "Run odd tests" - command: ulimit -S -n `ulimit -H -n` && cd build && ctest -j32 -I 1,,2 - - post-steps - - build-linux: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: make V=1 J=32 -j32 check - - post-steps - - build-linux-encrypted_env-no_compression: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: ENCRYPTED_ENV=1 ROCKSDB_DISABLE_SNAPPY=1 ROCKSDB_DISABLE_ZLIB=1 ROCKSDB_DISABLE_BZIP=1 ROCKSDB_DISABLE_LZ4=1 ROCKSDB_DISABLE_ZSTD=1 make V=1 J=32 -j32 check - - run: | - ./sst_dump --help | grep -E -q 'Supported compression types: kNoCompression$' # Verify no compiled in compression - - post-steps - - build-linux-static_lib-alt_namespace-status_checked: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check - - post-steps - - build-linux-release: - executor: linux-docker - resource_class: 2xlarge - steps: - - checkout # check out the code in the project directory - - run: make V=1 -j32 LIB_MODE=shared release - - run: ls librocksdb.so # ensure shared lib built - - run: ./db_stress --version # ensure with gflags - - run: make clean - - run: make V=1 -j32 release - - run: ls librocksdb.a # ensure static lib built - - run: ./db_stress --version # ensure with gflags - - run: make clean - - run: apt-get remove -y libgflags-dev - - run: make V=1 -j32 LIB_MODE=shared release - - run: ls librocksdb.so # ensure shared lib built - - run: if ./db_stress --version; then false; else true; fi # ensure without gflags - - run: make clean - - run: make V=1 -j32 release - - run: ls librocksdb.a # ensure static lib built - - run: if ./db_stress --version; then false; else true; fi # ensure without gflags - - post-steps - - build-linux-release-rtti: - executor: linux-docker - resource_class: xlarge - steps: - - checkout # check out the code in the project directory - - run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench - - run: ./db_stress --version # ensure with gflags - - run: make clean - - run: apt-get remove -y libgflags-dev - - run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench - - run: if ./db_stress --version; then false; else true; fi # ensure without gflags - - build-linux-clang-no_test_run: - executor: linux-docker - resource_class: xlarge - steps: - - checkout # check out the code in the project directory - - run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make V=1 -j16 all - - post-steps - - build-linux-clang10-asan: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: COMPILE_WITH_ASAN=1 CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check # aligned new doesn't work for reason we haven't figured out - - post-steps - - build-linux-clang10-mini-tsan: - executor: linux-docker - resource_class: 2xlarge+ - steps: - - pre-steps - - run: COMPILE_WITH_TSAN=1 CC=clang-13 CXX=clang++-13 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check - - post-steps - - build-linux-clang10-ubsan: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: COMPILE_WITH_UBSAN=1 OPT="-fsanitize-blacklist=.circleci/ubsan_suppression_list.txt" CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 ubsan_check # aligned new doesn't work for reason we haven't figured out - - post-steps - - build-linux-valgrind: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: PORTABLE=1 make V=1 -j32 valgrind_test - - post-steps - - build-linux-clang10-clang-analyze: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze # aligned new doesn't work for reason we haven't figured out. For unknown, reason passing "clang++-10" as CLANG_ANALYZER doesn't work, and we need a full path. - - post-steps - - run: - name: "compress test report" - command: tar -cvzf scan_build_report.tar.gz scan_build_report - when: on_fail - - store_artifacts: - path: scan_build_report.tar.gz - destination: scan_build_report - when: on_fail - - build-linux-runner: - machine: true - resource_class: facebook/rocksdb-benchmark-sys1 - steps: - - pre-steps - - run: - name: "Checked Linux build (Runner)" - command: make V=1 J=8 -j8 check - environment: - LD_LIBRARY_PATH: /usr/local/lib - - post-steps - - build-linux-cmake-with-folly: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - setup-folly - - build-folly - - run: (mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20) - - post-steps - - build-linux-cmake-with-folly-lite-no-test: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - setup-folly - - run: (mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 .. && make V=1 -j20) - - post-steps - - build-linux-cmake-with-benchmark: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20 - - post-steps - - build-linux-unity-and-headers: - docker: # executor type - - image: gcc:latest - environment: - EXTRA_CXXFLAGS: -mno-avx512f # Warnings-as-error in avx512fintrin.h, would be used on newer hardware - resource_class: large - steps: - - checkout # check out the code in the project directory - - run: apt-get update -y && apt-get install -y libgflags-dev - - run: - name: "Unity build" - command: make V=1 -j8 unity_test - no_output_timeout: 20m - - run: make V=1 -j8 -k check-headers # could be moved to a different build - - post-steps - - build-linux-gcc-7-with-folly: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - setup-folly - - build-folly - - run: USE_FOLLY=1 LIB_MODE=static CC=gcc-7 CXX=g++-7 V=1 make -j32 check # TODO: LIB_MODE only to work around unresolved linker failures - - post-steps - - build-linux-gcc-7-with-folly-lite-no-test: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - setup-folly - - run: USE_FOLLY_LITE=1 CC=gcc-7 CXX=g++-7 V=1 make -j32 all - - post-steps - - build-linux-gcc-8-no_test_run: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: CC=gcc-8 CXX=g++-8 V=1 make -j32 all - - post-steps - - build-linux-cmake-with-folly-coroutines: - executor: linux-docker - resource_class: 2xlarge - environment: - CC: gcc-10 - CXX: g++-10 - steps: - - pre-steps - - setup-folly - - build-folly - - run: (mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20) - - post-steps - - build-linux-gcc-10-cxx20-no_test_run: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: CC=gcc-10 CXX=g++-10 V=1 ROCKSDB_CXX_STANDARD=c++20 make -j32 all - - post-steps - - build-linux-gcc-11-no_test_run: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: LIB_MODE=static CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench # TODO: LIB_MODE only to work around unresolved linker failures - - post-steps - - build-linux-clang-13-no_test_run: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j32 all microbench - - post-steps - - # Ensure ASAN+UBSAN with folly, and full testsuite with clang 13 - build-linux-clang-13-asan-ubsan-with-folly: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - setup-folly - - build-folly - - run: CC=clang-13 CXX=clang++-13 LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check # TODO: LIB_MODE only to work around unresolved linker failures - - post-steps - - # This job is only to make sure the microbench tests are able to run, the benchmark result is not meaningful as the CI host is changing. - build-linux-run-microbench: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: DEBUG_LEVEL=0 make -j32 run_microbench - - post-steps - - build-linux-mini-crashtest: - executor: linux-docker - resource_class: large - steps: - - pre-steps - - run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=960 --max_key=2500000 --use_io_uring=0' blackbox_crash_test_with_atomic_flush - - post-steps - - build-linux-crashtest-tiered-storage-bb: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: - name: "run crashtest" - command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS='--duration=10800 --use_io_uring=0' blackbox_crash_test_with_tiered_storage - no_output_timeout: 100m - - post-steps - - build-linux-crashtest-tiered-storage-wb: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: - name: "run crashtest" - command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS='--duration=10800 --use_io_uring=0' whitebox_crash_test_with_tiered_storage - no_output_timeout: 100m - - post-steps - - build-windows-vs2022: - executor: - name: win/server-2022 - size: 2xlarge - environment: - THIRDPARTY_HOME: C:/Users/circleci/thirdparty - CMAKE_HOME: C:/Program Files/CMake - CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe - SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8 - SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build - SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib - CMAKE_GENERATOR: Visual Studio 17 2022 - steps: - - windows-build-steps - - build-windows-vs2019: - executor: - name: win/server-2019 - size: 2xlarge - environment: - THIRDPARTY_HOME: C:/Users/circleci/thirdparty - CMAKE_HOME: C:/Program Files/CMake - CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe - SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8 - SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build - SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib - CMAKE_GENERATOR: Visual Studio 16 2019 - steps: - - windows-build-steps - - build-linux-java: - executor: linux-docker - resource_class: large - steps: - - pre-steps - - run: - name: "Set Java Environment" - command: | - echo "JAVA_HOME=${JAVA_HOME}" - echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV - which java && java -version - which javac && javac -version - - run: - name: "Test RocksDBJava" - command: make V=1 J=8 -j8 jtest - - post-steps - - build-linux-java-static: - executor: linux-docker - resource_class: large - steps: - - pre-steps - - run: - name: "Set Java Environment" - command: | - echo "JAVA_HOME=${JAVA_HOME}" - echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV - which java && java -version - which javac && javac -version - - run: - name: "Build RocksDBJava Static Library" - command: make V=1 J=8 -j8 rocksdbjavastatic - - post-steps - - build-macos-java: - macos: - xcode: 12.5.1 - resource_class: large - environment: - JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home - ROCKSDB_DISABLE_JEMALLOC: 1 # jemalloc causes java 8 crash - steps: - - increase-max-open-files-on-macos - - install-gflags-on-macos - - install-jdk8-on-macos - - pre-steps-macos - - run: - name: "Set Java Environment" - command: | - echo "JAVA_HOME=${JAVA_HOME}" - echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV - which java && java -version - which javac && javac -version - - run: - name: "Test RocksDBJava" - command: make V=1 J=16 -j16 jtest - no_output_timeout: 20m - - post-steps - - build-macos-java-static: - macos: - xcode: 12.5.1 - resource_class: large - environment: - JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home - steps: - - increase-max-open-files-on-macos - - install-gflags-on-macos - - install-cmake-on-macos - - install-jdk8-on-macos - - pre-steps-macos - - run: - name: "Set Java Environment" - command: | - echo "JAVA_HOME=${JAVA_HOME}" - echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV - which java && java -version - which javac && javac -version - - run: - name: "Build RocksDBJava x86 and ARM Static Libraries" - command: make V=1 J=16 -j16 rocksdbjavastaticosx - no_output_timeout: 20m - - post-steps - - build-macos-java-static-universal: - macos: - xcode: 12.5.1 - resource_class: large - environment: - JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home - steps: - - increase-max-open-files-on-macos - - install-gflags-on-macos - - install-cmake-on-macos - - install-jdk8-on-macos - - pre-steps-macos - - run: - name: "Set Java Environment" - command: | - echo "JAVA_HOME=${JAVA_HOME}" - echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV - which java && java -version - which javac && javac -version - - run: - name: "Build RocksDBJava Universal Binary Static Library" - command: make V=1 J=16 -j16 rocksdbjavastaticosx_ub - no_output_timeout: 20m - - post-steps - - build-examples: - executor: linux-docker - resource_class: large - steps: - - pre-steps - - run: - name: "Build examples" - command: | - make V=1 -j4 static_lib && cd examples && make V=1 -j4 - - post-steps - - build-cmake-mingw: - executor: linux-docker - resource_class: large - steps: - - pre-steps - - run: update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - - run: - name: "Build cmake-mingw" - command: | - export PATH=$JAVA_HOME/bin:$PATH - echo "JAVA_HOME=${JAVA_HOME}" - which java && java -version - which javac && javac -version - mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni - - post-steps - - build-linux-non-shm: - executor: linux-docker - resource_class: 2xlarge - environment: - TEST_TMPDIR: /tmp/rocksdb_test_tmp - steps: - - pre-steps - - run: make V=1 -j32 check - - post-steps - - build-linux-arm-test-full: - machine: - image: ubuntu-2004:202111-02 - resource_class: arm.large - steps: - - pre-steps - - install-gflags - - run: make V=1 J=4 -j4 check - - post-steps - - build-linux-arm: - machine: - image: ubuntu-2004:202111-02 - resource_class: arm.large - steps: - - pre-steps - - install-gflags - - run: ROCKSDBTESTS_PLATFORM_DEPENDENT=only make V=1 J=4 -j4 all_but_some_tests check_some - - post-steps - - build-linux-arm-cmake-no_test_run: - machine: - image: ubuntu-2004:202111-02 - resource_class: arm.large - environment: - JAVA_HOME: /usr/lib/jvm/java-8-openjdk-arm64 - steps: - - pre-steps - - install-gflags - - run: - name: "Set Java Environment" - command: | - echo "JAVA_HOME=${JAVA_HOME}" - echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV - which java && java -version - which javac && javac -version - - run: - name: "Build with cmake" - command: | - mkdir build - cd build - cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=0 -DWITH_GFLAGS=1 -DWITH_BENCHMARK_TOOLS=0 -DWITH_TOOLS=0 -DWITH_CORE_TOOLS=1 .. - make -j4 - - run: - name: "Build Java with cmake" - command: | - rm -rf build - mkdir build - cd build - cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 .. - make -j4 rocksdb rocksdbjni - - post-steps - - build-format-compatible: - executor: linux-docker - resource_class: 2xlarge - steps: - - pre-steps - - run: - name: "test" - command: | - export TEST_TMPDIR=/dev/shm/rocksdb - rm -rf /dev/shm/rocksdb - mkdir /dev/shm/rocksdb - tools/check_format_compatible.sh - - post-steps - - build-fuzzers: - executor: linux-docker - resource_class: large - steps: - - pre-steps - - run: - name: "Build rocksdb lib" - command: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j4 static_lib - - run: - name: "Build fuzzers" - command: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer - - post-steps - - benchmark-linux: #use a private Circle CI runner (resource_class) to run the job - machine: true - resource_class: facebook/rocksdb-benchmark-sys1 - steps: - - build-for-benchmarks - - perform-benchmarks - - post-benchmarks - -workflows: - version: 2 - jobs-linux-run-tests: - jobs: - - build-linux - - build-linux-cmake-with-folly - - build-linux-cmake-with-folly-lite-no-test - - build-linux-gcc-7-with-folly - - build-linux-gcc-7-with-folly-lite-no-test - - build-linux-cmake-with-folly-coroutines - - build-linux-cmake-with-benchmark - - build-linux-encrypted_env-no_compression - jobs-linux-run-tests-san: - jobs: - - build-linux-clang10-asan - - build-linux-clang10-ubsan - - build-linux-clang10-mini-tsan - - build-linux-static_lib-alt_namespace-status_checked - jobs-linux-no-test-run: - jobs: - - build-linux-release - - build-linux-release-rtti - - build-examples - - build-fuzzers - - build-linux-clang-no_test_run - - build-linux-clang-13-no_test_run - - build-linux-gcc-8-no_test_run - - build-linux-gcc-10-cxx20-no_test_run - - build-linux-gcc-11-no_test_run - - build-linux-arm-cmake-no_test_run - jobs-linux-other-checks: - jobs: - - build-linux-clang10-clang-analyze - - build-linux-unity-and-headers - - build-linux-mini-crashtest - jobs-windows: - jobs: - - build-windows-vs2022 - - build-windows-vs2019 - - build-cmake-mingw - jobs-java: - jobs: - - build-linux-java - - build-linux-java-static - - build-macos-java - - build-macos-java-static - - build-macos-java-static-universal - jobs-macos: - jobs: - - build-macos - - build-macos-cmake: - run_even_tests: true - - build-macos-cmake: - run_even_tests: false - jobs-linux-arm: - jobs: - - build-linux-arm - build-fuzzers: - jobs: - - build-fuzzers - benchmark-linux: - triggers: - - schedule: - cron: "0 * * * *" - filters: - branches: - only: - - main - jobs: - - benchmark-linux - nightly: - triggers: - - schedule: - cron: "0 9 * * *" - filters: - branches: - only: - - main - jobs: - - build-format-compatible - - build-linux-arm-test-full - - build-linux-run-microbench - - build-linux-non-shm - - build-linux-clang-13-asan-ubsan-with-folly - - build-linux-valgrind diff --git a/.circleci/ubsan_suppression_list.txt b/.circleci/ubsan_suppression_list.txt deleted file mode 100644 index d7db81806..000000000 --- a/.circleci/ubsan_suppression_list.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Supress UBSAN warnings related to stl_tree.h, e.g. -# UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_tree.h:1505:43 in -# /usr/bin/../lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_tree.h:1505:43: -# runtime error: upcast of address 0x000001fa8820 with insufficient space for an object of type -# 'std::_Rb_tree_node, rocksdb::(anonymous namespace)::LockHoldingInfo> >' -src:*bits/stl_tree.h diff --git a/.github/workflows/sanity_check.yml b/.github/workflows/sanity_check.yml deleted file mode 100644 index 6ee53ce1b..000000000 --- a/.github/workflows/sanity_check.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Check buck targets and code format -on: [push, pull_request] -permissions: - contents: read - -jobs: - check: - name: Check TARGETS file and code format - runs-on: ubuntu-latest - steps: - - name: Checkout feature branch - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Fetch from upstream - run: | - git remote add upstream https://github.com/facebook/rocksdb.git && git fetch upstream - - - name: Where am I - run: | - echo git status && git status - echo "git remote -v" && git remote -v - echo git branch && git branch - - - name: Setup Python - uses: actions/setup-python@v1 - - - name: Install Dependencies - run: python -m pip install --upgrade pip - - - name: Install argparse - run: pip install argparse - - - name: Download clang-format-diff.py - uses: wei/wget@v1 - with: - args: https://raw.githubusercontent.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py - - - name: Check format - run: VERBOSE_CHECK=1 make check-format - - - name: Compare buckify output - run: make check-buck-targets - - - name: Simple source code checks - run: make check-sources diff --git a/build_tools/amalgamate.py b/build_tools/amalgamate.py deleted file mode 100755 index f79e9075e..000000000 --- a/build_tools/amalgamate.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/python -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -# amalgamate.py creates an amalgamation from a unity build. -# It can be run with either Python 2 or 3. -# An amalgamation consists of a header that includes the contents of all public -# headers and a source file that includes the contents of all source files and -# private headers. -# -# This script works by starting with the unity build file and recursively expanding -# #include directives. If the #include is found in a public include directory, -# that header is expanded into the amalgamation header. -# -# A particular header is only expanded once, so this script will -# break if there are multiple inclusions of the same header that are expected to -# expand differently. Similarly, this type of code causes issues: -# -# #ifdef FOO -# #include "bar.h" -# // code here -# #else -# #include "bar.h" // oops, doesn't get expanded -# // different code here -# #endif -# -# The solution is to move the include out of the #ifdef. - -from __future__ import print_function - -import argparse -import re -import sys -from os import path - -include_re = re.compile('^[ \t]*#include[ \t]+"(.*)"[ \t]*$') -included = set() -excluded = set() - - -def find_header(name, abs_path, include_paths): - samedir = path.join(path.dirname(abs_path), name) - if path.exists(samedir): - return samedir - for include_path in include_paths: - include_path = path.join(include_path, name) - if path.exists(include_path): - return include_path - return None - - -def expand_include( - include_path, - f, - abs_path, - source_out, - header_out, - include_paths, - public_include_paths, -): - if include_path in included: - return False - - included.add(include_path) - with open(include_path) as f: - print('#line 1 "{}"'.format(include_path), file=source_out) - process_file( - f, include_path, source_out, header_out, include_paths, public_include_paths - ) - return True - - -def process_file( - f, abs_path, source_out, header_out, include_paths, public_include_paths -): - for (line, text) in enumerate(f): - m = include_re.match(text) - if m: - filename = m.groups()[0] - # first check private headers - include_path = find_header(filename, abs_path, include_paths) - if include_path: - if include_path in excluded: - source_out.write(text) - expanded = False - else: - expanded = expand_include( - include_path, - f, - abs_path, - source_out, - header_out, - include_paths, - public_include_paths, - ) - else: - # now try public headers - include_path = find_header(filename, abs_path, public_include_paths) - if include_path: - # found public header - expanded = False - if include_path in excluded: - source_out.write(text) - else: - expand_include( - include_path, - f, - abs_path, - header_out, - None, - public_include_paths, - [], - ) - else: - sys.exit( - "unable to find {}, included in {} on line {}".format( - filename, abs_path, line - ) - ) - - if expanded: - print('#line {} "{}"'.format(line + 1, abs_path), file=source_out) - elif text != "#pragma once\n": - source_out.write(text) - - -def main(): - parser = argparse.ArgumentParser( - description="Transform a unity build into an amalgamation" - ) - parser.add_argument("source", help="source file") - parser.add_argument( - "-I", - action="append", - dest="include_paths", - help="include paths for private headers", - ) - parser.add_argument( - "-i", - action="append", - dest="public_include_paths", - help="include paths for public headers", - ) - parser.add_argument( - "-x", action="append", dest="excluded", help="excluded header files" - ) - parser.add_argument("-o", dest="source_out", help="output C++ file", required=True) - parser.add_argument( - "-H", dest="header_out", help="output C++ header file", required=True - ) - args = parser.parse_args() - - include_paths = list(map(path.abspath, args.include_paths or [])) - public_include_paths = list(map(path.abspath, args.public_include_paths or [])) - excluded.update(map(path.abspath, args.excluded or [])) - filename = args.source - abs_path = path.abspath(filename) - with open(filename) as f, open(args.source_out, "w") as source_out, open( - args.header_out, "w" - ) as header_out: - print('#line 1 "{}"'.format(filename), file=source_out) - print('#include "{}"'.format(header_out.name), file=source_out) - process_file( - f, abs_path, source_out, header_out, include_paths, public_include_paths - ) - - -if __name__ == "__main__": - main() diff --git a/build_tools/benchmark_log_tool.py b/build_tools/benchmark_log_tool.py deleted file mode 100755 index d1ad45911..000000000 --- a/build_tools/benchmark_log_tool.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -# This source code is licensed under both the GPLv2 (found in the -# COPYING file in the root directory) and Apache 2.0 License -# (found in the LICENSE.Apache file in the root directory). - -"""Access the results of benchmark runs -Send these results on to OpenSearch graphing service -""" - -import argparse -import itertools -import logging -import os -import re -import sys - -import requests -from dateutil import parser - -logging.basicConfig(level=logging.DEBUG) - - -class Configuration: - opensearch_user = os.environ["ES_USER"] - opensearch_pass = os.environ["ES_PASS"] - - -class BenchmarkResultException(Exception): - def __init__(self, message, content): - super().__init__(self, message) - self.content = content - - -class BenchmarkUtils: - - expected_keys = [ - "ops_sec", - "mb_sec", - "lsm_sz", - "blob_sz", - "c_wgb", - "w_amp", - "c_mbps", - "c_wsecs", - "c_csecs", - "b_rgb", - "b_wgb", - "usec_op", - "p50", - "p99", - "p99.9", - "p99.99", - "pmax", - "uptime", - "stall%", - "Nstall", - "u_cpu", - "s_cpu", - "rss", - "test", - "date", - "version", - "job_id", - ] - - def sanity_check(row): - if "test" not in row: - logging.debug(f"not 'test' in row: {row}") - return False - if row["test"] == "": - logging.debug(f"row['test'] == '': {row}") - return False - if "date" not in row: - logging.debug(f"not 'date' in row: {row}") - return False - if "ops_sec" not in row: - logging.debug(f"not 'ops_sec' in row: {row}") - return False - try: - _ = int(row["ops_sec"]) - except (ValueError, TypeError): - logging.debug(f"int(row['ops_sec']): {row}") - return False - try: - (_, _) = parser.parse(row["date"], fuzzy_with_tokens=True) - except (parser.ParserError): - logging.error( - f"parser.parse((row['date']): not a valid format for date in row: {row}" - ) - return False - return True - - def conform_opensearch(row): - (dt, _) = parser.parse(row["date"], fuzzy_with_tokens=True) - # create a test_date field, which was previously what was expected - # repair the date field, which has what can be a WRONG ISO FORMAT, (no leading 0 on single-digit day-of-month) - # e.g. 2022-07-1T00:14:55 should be 2022-07-01T00:14:55 - row["test_date"] = dt.isoformat() - row["date"] = dt.isoformat() - return {key.replace(".", "_"): value for key, value in row.items()} - - -class ResultParser: - def __init__(self, field="(\w|[+-:.%])+", intrafield="(\s)+", separator="\t"): - self.field = re.compile(field) - self.intra = re.compile(intrafield) - self.sep = re.compile(separator) - - def ignore(self, l_in: str): - if len(l_in) == 0: - return True - if l_in[0:1] == "#": - return True - return False - - def line(self, line_in: str): - """Parse a line into items - Being clever about separators - """ - line = line_in - row = [] - while line != "": - match_item = self.field.match(line) - if match_item: - item = match_item.group(0) - row.append(item) - line = line[len(item) :] - else: - match_intra = self.intra.match(line) - if match_intra: - intra = match_intra.group(0) - # Count the separators - # If there are >1 then generate extra blank fields - # White space with no true separators fakes up a single separator - tabbed = self.sep.split(intra) - sep_count = len(tabbed) - 1 - if sep_count == 0: - sep_count = 1 - for _ in range(sep_count - 1): - row.append("") - line = line[len(intra) :] - else: - raise BenchmarkResultException( - "Invalid TSV line", f"{line_in} at {line}" - ) - return row - - def parse(self, lines): - """Parse something that iterates lines""" - rows = [self.line(line) for line in lines if not self.ignore(line)] - header = rows[0] - width = len(header) - records = [ - {k: v for (k, v) in itertools.zip_longest(header, row[:width])} - for row in rows[1:] - ] - return records - - -def load_report_from_tsv(filename: str): - file = open(filename, "r") - contents = file.readlines() - file.close() - parser = ResultParser() - report = parser.parse(contents) - logging.debug(f"Loaded TSV Report: {report}") - return report - - -def push_report_to_opensearch(report, esdocument): - sanitized = [ - BenchmarkUtils.conform_opensearch(row) - for row in report - if BenchmarkUtils.sanity_check(row) - ] - logging.debug( - f"upload {len(sanitized)} sane of {len(report)} benchmarks to opensearch" - ) - for single_benchmark in sanitized: - logging.debug(f"upload benchmark: {single_benchmark}") - response = requests.post( - esdocument, - json=single_benchmark, - auth=(os.environ["ES_USER"], os.environ["ES_PASS"]), - ) - logging.debug( - f"Sent to OpenSearch, status: {response.status_code}, result: {response.text}" - ) - response.raise_for_status() - - -def push_report_to_null(report): - - for row in report: - if BenchmarkUtils.sanity_check(row): - logging.debug(f"row {row}") - conformed = BenchmarkUtils.conform_opensearch(row) - logging.debug(f"conformed row {conformed}") - - -def main(): - """Tool for fetching, parsing and uploading benchmark results to OpenSearch / ElasticSearch - This tool will - - (1) Open a local tsv benchmark report file - (2) Upload to OpenSearch document, via https/JSON - """ - - parser = argparse.ArgumentParser(description="CircleCI benchmark scraper.") - - # --tsvfile is the name of the file to read results from - # --esdocument is the ElasticSearch document to push these results into - # - parser.add_argument( - "--tsvfile", - default="build_tools/circle_api_scraper_input.txt", - help="File from which to read tsv report", - ) - parser.add_argument( - "--esdocument", - help="ElasticSearch/OpenSearch document URL to upload report into", - ) - parser.add_argument( - "--upload", choices=["opensearch", "none"], default="opensearch" - ) - - args = parser.parse_args() - logging.debug(f"Arguments: {args}") - reports = load_report_from_tsv(args.tsvfile) - if args.upload == "opensearch": - push_report_to_opensearch(reports, args.esdocument) - else: - push_report_to_null(reports) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/build_tools/build_detect_platform b/build_tools/build_detect_platform deleted file mode 100755 index c03d9ae41..000000000 --- a/build_tools/build_detect_platform +++ /dev/null @@ -1,900 +0,0 @@ -#!/usr/bin/env bash -# -# Detects OS we're compiling on and outputs a file specified by the first -# argument, which in turn gets read while processing Makefile. -# -# The output will set the following variables: -# CC C Compiler path -# CXX C++ Compiler path -# PLATFORM_LDFLAGS Linker flags -# JAVA_LDFLAGS Linker flags for RocksDBJava -# JAVA_STATIC_LDFLAGS Linker flags for RocksDBJava static build -# JAVAC_ARGS Arguments for javac -# PLATFORM_SHARED_EXT Extension for shared libraries -# PLATFORM_SHARED_LDFLAGS Flags for building shared library -# PLATFORM_SHARED_CFLAGS Flags for compiling objects for shared library -# PLATFORM_CCFLAGS C compiler flags -# PLATFORM_CXXFLAGS C++ compiler flags. Will contain: -# PLATFORM_SHARED_VERSIONED Set to 'true' if platform supports versioned -# shared libraries, empty otherwise. -# FIND Command for the find utility -# WATCH Command for the watch utility -# -# The PLATFORM_CCFLAGS and PLATFORM_CXXFLAGS might include the following: -# -# -DROCKSDB_PLATFORM_POSIX if posix-platform based -# -DSNAPPY if the Snappy library is present -# -DLZ4 if the LZ4 library is present -# -DZSTD if the ZSTD library is present -# -DNUMA if the NUMA library is present -# -DTBB if the TBB library is present -# -DMEMKIND if the memkind library is present -# -# Using gflags in rocksdb: -# Our project depends on gflags, which requires users to take some extra steps -# before they can compile the whole repository: -# 1. Install gflags. You may download it from here: -# https://gflags.github.io/gflags/ (Mac users can `brew install gflags`) -# 2. Once installed, add the include path for gflags to your CPATH env var and -# the lib path to LIBRARY_PATH. If installed with default settings, the lib -# will be /usr/local/lib and the include path will be /usr/local/include - -OUTPUT=$1 -if test -z "$OUTPUT"; then - echo "usage: $0 " >&2 - exit 1 -fi - -# we depend on C++17, but should be compatible with newer standards -if [ "$ROCKSDB_CXX_STANDARD" ]; then - PLATFORM_CXXFLAGS="-std=$ROCKSDB_CXX_STANDARD" -else - PLATFORM_CXXFLAGS="-std=c++17" -fi - -# we currently depend on POSIX platform -COMMON_FLAGS="-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX" - -# Default to fbcode gcc on internal fb machines -if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then - FBCODE_BUILD="true" - # If we're compiling with TSAN or shared lib, we need pic build - PIC_BUILD=$COMPILE_WITH_TSAN - if [ "$LIB_MODE" == "shared" ]; then - PIC_BUILD=1 - fi - source "$PWD/build_tools/fbcode_config_platform010.sh" -fi - -# Delete existing output, if it exists -rm -f "$OUTPUT" -touch "$OUTPUT" - -if test -z "$CC"; then - if [ -x "$(command -v cc)" ]; then - CC=cc - elif [ -x "$(command -v clang)" ]; then - CC=clang - else - CC=cc - fi -fi - -if test -z "$CXX"; then - if [ -x "$(command -v g++)" ]; then - CXX=g++ - elif [ -x "$(command -v clang++)" ]; then - CXX=clang++ - else - CXX=g++ - fi -fi - -if test -z "$AR"; then - if [ -x "$(command -v gcc-ar)" ]; then - AR=gcc-ar - elif [ -x "$(command -v llvm-ar)" ]; then - AR=llvm-ar - else - AR=ar - fi -fi - -# Detect OS -if test -z "$TARGET_OS"; then - TARGET_OS=`uname -s` -fi - -if test -z "$TARGET_ARCHITECTURE"; then - TARGET_ARCHITECTURE=`uname -m` -fi - -if test -z "$CLANG_SCAN_BUILD"; then - CLANG_SCAN_BUILD=scan-build -fi - -if test -z "$CLANG_ANALYZER"; then - CLANG_ANALYZER=$(command -v clang++ 2> /dev/null) -fi - -if test -z "$FIND"; then - FIND=find -fi - -if test -z "$WATCH"; then - WATCH=watch -fi - -COMMON_FLAGS="$COMMON_FLAGS ${CFLAGS}" -CROSS_COMPILE= -PLATFORM_CCFLAGS= -PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS" -PLATFORM_SHARED_EXT="so" -PLATFORM_SHARED_LDFLAGS="-Wl,--no-as-needed -shared -Wl,-soname -Wl," -PLATFORM_SHARED_CFLAGS="-fPIC" -PLATFORM_SHARED_VERSIONED=true - -# generic port files (working on all platform by #ifdef) go directly in /port -GENERIC_PORT_FILES=`cd "$ROCKSDB_ROOT"; find port -name '*.cc' | tr "\n" " "` - -# On GCC, we pick libc's memcmp over GCC's memcmp via -fno-builtin-memcmp -case "$TARGET_OS" in - Darwin) - PLATFORM=OS_MACOSX - COMMON_FLAGS="$COMMON_FLAGS -DOS_MACOSX" - PLATFORM_SHARED_EXT=dylib - PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name " - # PORT_FILES=port/darwin/darwin_specific.cc - ;; - IOS) - PLATFORM=IOS - COMMON_FLAGS="$COMMON_FLAGS -DOS_MACOSX -DIOS_CROSS_COMPILE " - PLATFORM_SHARED_EXT=dylib - PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name " - CROSS_COMPILE=true - PLATFORM_SHARED_VERSIONED= - ;; - Linux) - PLATFORM=OS_LINUX - COMMON_FLAGS="$COMMON_FLAGS -DOS_LINUX" - if [ -z "$USE_CLANG" ]; then - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp" - else - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic" - fi - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt -ldl" - if test -z "$ROCKSDB_USE_IO_URING"; then - ROCKSDB_USE_IO_URING=1 - fi - if test "$ROCKSDB_USE_IO_URING" -ne 0; then - # check for liburing - $CXX $PLATFORM_CXXFLAGS -x c++ - -luring -o test.o 2>/dev/null < - int main() { - struct io_uring ring; - io_uring_queue_init(1, &ring, 0); - return 0; - } -EOF - if [ "$?" = 0 ]; then - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -luring" - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_IOURING_PRESENT" - fi - fi - # PORT_FILES=port/linux/linux_specific.cc - ;; - SunOS) - PLATFORM=OS_SOLARIS - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_SOLARIS -m64" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt -static-libstdc++ -static-libgcc -m64" - # PORT_FILES=port/sunos/sunos_specific.cc - ;; - AIX) - PLATFORM=OS_AIX - CC=gcc - COMMON_FLAGS="$COMMON_FLAGS -maix64 -pthread -fno-builtin-memcmp -D_REENTRANT -DOS_AIX -D__STDC_FORMAT_MACROS" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -pthread -lpthread -lrt -maix64 -static-libstdc++ -static-libgcc" - # PORT_FILES=port/aix/aix_specific.cc - ;; - FreeBSD) - PLATFORM=OS_FREEBSD - CXX=clang++ - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_FREEBSD" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread" - # PORT_FILES=port/freebsd/freebsd_specific.cc - ;; - GNU/kFreeBSD) - PLATFORM=OS_GNU_KFREEBSD - COMMON_FLAGS="$COMMON_FLAGS -DOS_GNU_KFREEBSD" - if [ -z "$USE_CLANG" ]; then - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp" - else - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic" - fi - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt" - # PORT_FILES=port/gnu_kfreebsd/gnu_kfreebsd_specific.cc - ;; - NetBSD) - PLATFORM=OS_NETBSD - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_NETBSD" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lgcc_s" - # PORT_FILES=port/netbsd/netbsd_specific.cc - ;; - OpenBSD) - PLATFORM=OS_OPENBSD - CXX=clang++ - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_OPENBSD" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -pthread" - # PORT_FILES=port/openbsd/openbsd_specific.cc - FIND=gfind - WATCH=gnuwatch - ;; - DragonFly) - PLATFORM=OS_DRAGONFLYBSD - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_DRAGONFLYBSD" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread" - # PORT_FILES=port/dragonfly/dragonfly_specific.cc - ;; - Cygwin) - PLATFORM=CYGWIN - PLATFORM_SHARED_CFLAGS="" - PLATFORM_CXXFLAGS="-std=gnu++11" - COMMON_FLAGS="$COMMON_FLAGS -DCYGWIN" - if [ -z "$USE_CLANG" ]; then - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp" - else - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic" - fi - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt" - # PORT_FILES=port/linux/linux_specific.cc - ;; - OS_ANDROID_CROSSCOMPILE) - PLATFORM=OS_ANDROID - COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_ANDROID -DROCKSDB_PLATFORM_POSIX" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS " # All pthread features are in the Android C library - # PORT_FILES=port/android/android.cc - CROSS_COMPILE=true - ;; - *) - echo "Unknown platform!" >&2 - exit 1 -esac - -PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS ${CXXFLAGS}" -JAVA_LDFLAGS="$PLATFORM_LDFLAGS" -JAVA_STATIC_LDFLAGS="$PLATFORM_LDFLAGS" -JAVAC_ARGS="-source 8" - -if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then - # Cross-compiling; do not try any compilation tests. - # Also don't need any compilation tests if compiling on fbcode - if [ "$FBCODE_BUILD" = "true" ]; then - # Enable backtrace on fbcode since the necessary libraries are present - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE" - FOLLY_DIR="third-party/folly" - fi - true -else - if ! test $ROCKSDB_DISABLE_FALLOCATE; then - # Test whether fallocate is available - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - #include - int main() { - int fd = open("/dev/null", 0); - fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024); - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_FALLOCATE_PRESENT" - fi - fi - - if ! test $ROCKSDB_DISABLE_SNAPPY; then - # Test whether Snappy library is installed - # http://code.google.com/p/snappy/ - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lsnappy" - fi - fi - - if ! test $ROCKSDB_DISABLE_GFLAGS; then - # Test whether gflags library is installed - # http://gflags.github.io/gflags/ - # check if the namespace is gflags - if $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF - #include - using namespace GFLAGS_NAMESPACE; - int main() {} -EOF - then - COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" - # check if namespace is gflags - elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF - #include - using namespace gflags; - int main() {} -EOF - then - COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" - # check if namespace is google - elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF - #include - using namespace google; - int main() {} -EOF - then - COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=google" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" - fi - fi - - if ! test $ROCKSDB_DISABLE_ZLIB; then - # Test whether zlib library is installed - $CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null < - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DZLIB" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lz" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lz" - fi - fi - - if ! test $ROCKSDB_DISABLE_BZIP; then - # Test whether bzip library is installed - $CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null < - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DBZIP2" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbz2" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lbz2" - fi - fi - - if ! test $ROCKSDB_DISABLE_LZ4; then - # Test whether lz4 library is installed - $CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null < - #include - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DLZ4" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -llz4" - JAVA_LDFLAGS="$JAVA_LDFLAGS -llz4" - fi - fi - - if ! test $ROCKSDB_DISABLE_ZSTD; then - # Test whether zstd library is installed - $CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DZSTD" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lzstd" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lzstd" - fi - fi - - if ! test $ROCKSDB_DISABLE_NUMA; then - # Test whether numa is available - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -lnuma 2>/dev/null < - #include - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DNUMA" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lnuma" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma" - fi - fi - - if ! test $ROCKSDB_DISABLE_TBB; then - # Test whether tbb is available - $CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -ltbb 2>/dev/null < - int main() {} -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DTBB" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb" - JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb" - fi - fi - - if ! test $ROCKSDB_DISABLE_JEMALLOC; then - # Test whether jemalloc is available - if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -ljemalloc \ - 2>/dev/null; then - # This will enable some preprocessor identifiers in the Makefile - JEMALLOC=1 - # JEMALLOC can be enabled either using the flag (like here) or by - # providing direct link to the jemalloc library - WITH_JEMALLOC_FLAG=1 - # check for JEMALLOC installed with HomeBrew - if [ "$PLATFORM" == "OS_MACOSX" ]; then - if hash brew 2>/dev/null && brew ls --versions jemalloc > /dev/null; then - JEMALLOC_VER=$(brew ls --versions jemalloc | tail -n 1 | cut -f 2 -d ' ') - JEMALLOC_INCLUDE="-I/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/include" - JEMALLOC_LIB="/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $JEMALLOC_LIB" - JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS $JEMALLOC_LIB" - fi - fi - fi - fi - if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then - # jemalloc is not available. Let's try tcmalloc - if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o \ - -ltcmalloc 2>/dev/null; then - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc" - JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc" - fi - fi - - if ! test $ROCKSDB_DISABLE_MALLOC_USABLE_SIZE; then - # Test whether malloc_usable_size is available - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - int main() { - size_t res = malloc_usable_size(0); - (void)res; - return 0; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE" - fi - fi - - if ! test $ROCKSDB_DISABLE_MEMKIND; then - # Test whether memkind library is installed - $CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -lmemkind 2>/dev/null < - int main() { - memkind_malloc(MEMKIND_DAX_KMEM, 1024); - return 0; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DMEMKIND" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lmemkind" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lmemkind" - fi - fi - - if ! test $ROCKSDB_DISABLE_PTHREAD_MUTEX_ADAPTIVE_NP; then - # Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - int main() { - int x = PTHREAD_MUTEX_ADAPTIVE_NP; - (void)x; - return 0; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX" - fi - fi - - if ! test $ROCKSDB_DISABLE_BACKTRACE; then - # Test whether backtrace is available - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - int main() { - void* frames[1]; - backtrace_symbols(frames, backtrace(frames, 1)); - return 0; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE" - else - # Test whether execinfo library is installed - $CXX $PLATFORM_CXXFLAGS -lexecinfo -x c++ - -o test.o 2>/dev/null < - int main() { - void* frames[1]; - backtrace_symbols(frames, backtrace(frames, 1)); - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lexecinfo" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lexecinfo" - fi - fi - fi - - if ! test $ROCKSDB_DISABLE_PG; then - # Test if -pg is supported - $CXX $PLATFORM_CXXFLAGS -pg -x c++ - -o test.o 2>/dev/null </dev/null < - int main() { - int fd = open("/dev/null", 0); - sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE); - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_RANGESYNC_PRESENT" - fi - fi - - if ! test $ROCKSDB_DISABLE_SCHED_GETCPU; then - # Test whether sched_getcpu is supported - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - int main() { - int cpuid = sched_getcpu(); - (void)cpuid; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SCHED_GETCPU_PRESENT" - fi - fi - - if ! test $ROCKSDB_DISABLE_AUXV_GETAUXVAL; then - # Test whether getauxval is supported - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null < - int main() { - uint64_t auxv = getauxval(AT_HWCAP); - (void)auxv; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_AUXV_GETAUXVAL_PRESENT" - fi - fi - - if ! test $ROCKSDB_DISABLE_ALIGNED_NEW; then - # Test whether c++17 aligned-new is supported - $CXX $PLATFORM_CXXFLAGS -faligned-new -x c++ - -o test.o 2>/dev/null </dev/null < - int main() {} -EOF - if [ "$?" = 0 ]; then - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbenchmark" - fi - fi - if test $USE_FOLLY; then - # Test whether libfolly library is installed - $CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() {} -EOF - if [ "$?" != 0 ]; then - FOLLY_DIR="./third-party/folly" - fi - fi - -fi - -# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning. -# -Wshorten-64-to-32 breaks compilation on FreeBSD aarch64 and i386 -if ! { [ "$TARGET_OS" = FreeBSD ] && [ "$TARGET_ARCHITECTURE" = arm64 -o "$TARGET_ARCHITECTURE" = i386 ]; }; then - # Test whether -Wshorten-64-to-32 is available - $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -Wshorten-64-to-32 2>/dev/null </dev/null; then - COMMON_FLAGS="$COMMON_FLAGS -march=native " - else - COMMON_FLAGS="$COMMON_FLAGS -march=z196 " - fi - COMMON_FLAGS="$COMMON_FLAGS" - elif test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then - RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-) - COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}" - elif [ "$TARGET_OS" == "IOS" ]; then - COMMON_FLAGS="$COMMON_FLAGS" - elif [ "$TARGET_OS" == "AIX" ] || [ "$TARGET_OS" == "SunOS" ]; then - # TODO: Not sure why we don't use -march=native on these OSes - if test "$USE_SSE"; then - TRY_SSE_ETC="1" - fi - else - COMMON_FLAGS="$COMMON_FLAGS -march=native " - fi -else - # PORTABLE=1 - if test "$USE_SSE"; then - TRY_SSE_ETC="1" - fi - - if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then - COMMON_FLAGS="$COMMON_FLAGS -march=z196 " - fi - - if test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then - RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-) - COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}" - fi - - if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then - # For portability compile for macOS 10.13 (2017) or newer - COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.13" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.13" - # -mmacosx-version-min must come first here. - PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.13 $PLATFORM_SHARED_LDFLAGS" - PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13" - JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.13" - JAVA_STATIC_DEPS_LDFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS" - JAVA_STATIC_DEPS_CCFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS" - JAVA_STATIC_DEPS_CXXFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS" - fi -fi - -if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then - # check for GNU libc on ppc64 - $CXX -x c++ - -o /dev/null 2>/dev/null < - #include - #include - - int main(int argc, char *argv[]) { - printf("GNU libc version: %s\n", gnu_get_libc_version()); - return 0; - } -EOF - if [ "$?" != 0 ]; then - PPC_LIBC_IS_GNU=0 - fi -fi - -if test "$TRY_SSE_ETC"; then - # The USE_SSE flag now means "attempt to compile with widely-available - # Intel architecture extensions utilized by specific optimizations in the - # source code." It's a qualifier on PORTABLE=1 that means "mostly portable." - # It doesn't even really check that your current CPU is compatible. - # - # SSE4.2 available since nehalem, ca. 2008-2010 - # Includes POPCNT for BitsSetToOne, BitParity - TRY_SSE42="-msse4.2" - # PCLMUL available since westmere, ca. 2010-2011 - TRY_PCLMUL="-mpclmul" - # AVX2 available since haswell, ca. 2013-2015 - TRY_AVX2="-mavx2" - # BMI available since haswell, ca. 2013-2015 - # Primarily for TZCNT for CountTrailingZeroBits - TRY_BMI="-mbmi" - # LZCNT available since haswell, ca. 2013-2015 - # For FloorLog2 - TRY_LZCNT="-mlzcnt" -fi - -$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_SSE42 -x c++ - -o test.o 2>/dev/null < - #include - int main() { - volatile uint32_t x = _mm_crc32_u32(0, 0); - (void)x; - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS $TRY_SSE42 -DHAVE_SSE42" -elif test "$USE_SSE"; then - echo "warning: USE_SSE specified but compiler could not use SSE intrinsics, disabling" >&2 -fi - -$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_PCLMUL -x c++ - -o test.o 2>/dev/null < - #include - int main() { - const auto a = _mm_set_epi64x(0, 0); - const auto b = _mm_set_epi64x(0, 0); - const auto c = _mm_clmulepi64_si128(a, b, 0x00); - auto d = _mm_cvtsi128_si64(c); - (void)d; - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS $TRY_PCLMUL -DHAVE_PCLMUL" -elif test "$USE_SSE"; then - echo "warning: USE_SSE specified but compiler could not use PCLMUL intrinsics, disabling" >&2 -fi - -$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_AVX2 -x c++ - -o test.o 2>/dev/null < - #include - int main() { - const auto a = _mm256_setr_epi32(0, 1, 2, 3, 4, 7, 6, 5); - const auto b = _mm256_permutevar8x32_epi32(a, a); - (void)b; - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS $TRY_AVX2 -DHAVE_AVX2" -elif test "$USE_SSE"; then - echo "warning: USE_SSE specified but compiler could not use AVX2 intrinsics, disabling" >&2 -fi - -$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_BMI -x c++ - -o test.o 2>/dev/null < - #include - int main(int argc, char *argv[]) { - (void)argv; - return (int)_tzcnt_u64((uint64_t)argc); - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS $TRY_BMI -DHAVE_BMI" -elif test "$USE_SSE"; then - echo "warning: USE_SSE specified but compiler could not use BMI intrinsics, disabling" >&2 -fi - -$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_LZCNT -x c++ - -o test.o 2>/dev/null < - #include - int main(int argc, char *argv[]) { - (void)argv; - return (int)_lzcnt_u64((uint64_t)argc); - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS $TRY_LZCNT -DHAVE_LZCNT" -elif test "$USE_SSE"; then - echo "warning: USE_SSE specified but compiler could not use LZCNT intrinsics, disabling" >&2 -fi - -$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null < - int main() { - uint64_t a = 0xffffFFFFffffFFFF; - __uint128_t b = __uint128_t(a) * a; - a = static_cast(b >> 64); - (void)a; - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DHAVE_UINT128_EXTENSION" -fi - -if [ "$FBCODE_BUILD" != "true" -a "$PLATFORM" = OS_LINUX ]; then - $CXX $COMMON_FLAGS $PLATFORM_SHARED_CFLAGS -x c++ -c - -o test_dl.o 2>/dev/null </dev/null - if [ "$?" = 0 ]; then - EXEC_LDFLAGS+="-ldl" - rm -f test_dl.o - fi - fi -fi - -# check for F_FULLFSYNC -$CXX $PLATFORM_CXXFALGS -x c++ - -o test.o 2>/dev/null < - int main() { - fcntl(0, F_FULLFSYNC); - return 0; - } -EOF -if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DHAVE_FULLFSYNC" -fi - -rm -f test.o test_dl.o - -# Get the path for the folly installation dir -if [ "$USE_FOLLY" ]; then - if [ "$FOLLY_DIR" ]; then - FOLLY_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-inst-dir folly` - fi -fi - -PLATFORM_CCFLAGS="$PLATFORM_CCFLAGS $COMMON_FLAGS" -PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS $COMMON_FLAGS" - -VALGRIND_VER="$VALGRIND_VER" - -ROCKSDB_MAJOR=`build_tools/version.sh major` -ROCKSDB_MINOR=`build_tools/version.sh minor` -ROCKSDB_PATCH=`build_tools/version.sh patch` - -echo "CC=$CC" >> "$OUTPUT" -echo "CXX=$CXX" >> "$OUTPUT" -echo "AR=$AR" >> "$OUTPUT" -echo "PLATFORM=$PLATFORM" >> "$OUTPUT" -echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> "$OUTPUT" -echo "PLATFORM_CMAKE_FLAGS=$PLATFORM_CMAKE_FLAGS" >> "$OUTPUT" -echo "JAVA_LDFLAGS=$JAVA_LDFLAGS" >> "$OUTPUT" -echo "JAVA_STATIC_LDFLAGS=$JAVA_STATIC_LDFLAGS" >> "$OUTPUT" -echo "JAVA_STATIC_DEPS_CCFLAGS=$JAVA_STATIC_DEPS_CCFLAGS" >> "$OUTPUT" -echo "JAVA_STATIC_DEPS_CXXFLAGS=$JAVA_STATIC_DEPS_CXXFLAGS" >> "$OUTPUT" -echo "JAVA_STATIC_DEPS_LDFLAGS=$JAVA_STATIC_DEPS_LDFLAGS" >> "$OUTPUT" -echo "JAVAC_ARGS=$JAVAC_ARGS" >> "$OUTPUT" -echo "VALGRIND_VER=$VALGRIND_VER" >> "$OUTPUT" -echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> "$OUTPUT" -echo "PLATFORM_CXXFLAGS=$PLATFORM_CXXFLAGS" >> "$OUTPUT" -echo "PLATFORM_SHARED_CFLAGS=$PLATFORM_SHARED_CFLAGS" >> "$OUTPUT" -echo "PLATFORM_SHARED_EXT=$PLATFORM_SHARED_EXT" >> "$OUTPUT" -echo "PLATFORM_SHARED_LDFLAGS=$PLATFORM_SHARED_LDFLAGS" >> "$OUTPUT" -echo "PLATFORM_SHARED_VERSIONED=$PLATFORM_SHARED_VERSIONED" >> "$OUTPUT" -echo "EXEC_LDFLAGS=$EXEC_LDFLAGS" >> "$OUTPUT" -echo "JEMALLOC_INCLUDE=$JEMALLOC_INCLUDE" >> "$OUTPUT" -echo "JEMALLOC_LIB=$JEMALLOC_LIB" >> "$OUTPUT" -echo "ROCKSDB_MAJOR=$ROCKSDB_MAJOR" >> "$OUTPUT" -echo "ROCKSDB_MINOR=$ROCKSDB_MINOR" >> "$OUTPUT" -echo "ROCKSDB_PATCH=$ROCKSDB_PATCH" >> "$OUTPUT" -echo "CLANG_SCAN_BUILD=$CLANG_SCAN_BUILD" >> "$OUTPUT" -echo "CLANG_ANALYZER=$CLANG_ANALYZER" >> "$OUTPUT" -echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT" -echo "FIND=$FIND" >> "$OUTPUT" -echo "WATCH=$WATCH" >> "$OUTPUT" -echo "FOLLY_PATH=$FOLLY_PATH" >> "$OUTPUT" - -# This will enable some related identifiers for the preprocessor -if test -n "$JEMALLOC"; then - echo "JEMALLOC=1" >> "$OUTPUT" -fi -# Indicates that jemalloc should be enabled using -ljemalloc flag -# The alternative is to porvide a direct link to the library via JEMALLOC_LIB -# and JEMALLOC_INCLUDE -if test -n "$WITH_JEMALLOC_FLAG"; then - echo "WITH_JEMALLOC_FLAG=$WITH_JEMALLOC_FLAG" >> "$OUTPUT" -fi -echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT" -if test -n "$USE_FOLLY"; then - echo "USE_FOLLY=$USE_FOLLY" >> "$OUTPUT" -fi -if test -n "$PPC_LIBC_IS_GNU"; then - echo "PPC_LIBC_IS_GNU=$PPC_LIBC_IS_GNU" >> "$OUTPUT" -fi diff --git a/build_tools/check-sources.sh b/build_tools/check-sources.sh deleted file mode 100755 index 5672f7b2b..000000000 --- a/build_tools/check-sources.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# -# Check for some simple mistakes that should prevent commit or push - -BAD="" - -git grep -n 'namespace rocksdb' -- '*.[ch]*' -if [ "$?" != "1" ]; then - echo "^^^^^ Do not hardcode namespace rocksdb. Use ROCKSDB_NAMESPACE" - BAD=1 -fi - -git grep -n -i 'nocommit' -- ':!build_tools/check-sources.sh' -if [ "$?" != "1" ]; then - echo "^^^^^ Code was not intended to be committed" - BAD=1 -fi - -git grep -n 'include :: Failure' - _GTEST_FAIL_PATTERN = re.compile(r"(unknown file|\S+:\d+): Failure$") - - def __init__(self): - self._last_gtest_name = "Unknown test" - - def parse_error(self, line): - gtest_name_match = self._GTEST_NAME_PATTERN.match(line) - if gtest_name_match: - self._last_gtest_name = gtest_name_match.group(1) - return None - gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line) - if gtest_fail_match: - return "%s failed: %s" % (self._last_gtest_name, gtest_fail_match.group(1)) - return None - - -class MatchErrorParser(ErrorParserBase): - """A simple parser that returns the whole line if it matches the pattern.""" - - def __init__(self, pattern): - self._pattern = re.compile(pattern) - - def parse_error(self, line): - if self._pattern.match(line): - return line - return None - - -class CompilerErrorParser(MatchErrorParser): - def __init__(self): - # format (compile error): - # '::: error: ' - # format (link error): - # ':: error: ' - # The below regex catches both - super(CompilerErrorParser, self).__init__(r"\S+:\d+: error:") - - -class ScanBuildErrorParser(MatchErrorParser): - def __init__(self): - super(ScanBuildErrorParser, self).__init__(r"scan-build: \d+ bugs found.$") - - -class DbCrashErrorParser(MatchErrorParser): - def __init__(self): - super(DbCrashErrorParser, self).__init__(r"\*\*\*.*\^$|TEST FAILED.") - - -class WriteStressErrorParser(MatchErrorParser): - def __init__(self): - super(WriteStressErrorParser, self).__init__( - r"ERROR: write_stress died with exitcode=\d+" - ) - - -class AsanErrorParser(MatchErrorParser): - def __init__(self): - super(AsanErrorParser, self).__init__(r"==\d+==ERROR: AddressSanitizer:") - - -class UbsanErrorParser(MatchErrorParser): - def __init__(self): - # format: '::: runtime error: ' - super(UbsanErrorParser, self).__init__(r"\S+:\d+:\d+: runtime error:") - - -class ValgrindErrorParser(MatchErrorParser): - def __init__(self): - # just grab the summary, valgrind doesn't clearly distinguish errors - # from other log messages. - super(ValgrindErrorParser, self).__init__(r"==\d+== ERROR SUMMARY:") - - -class CompatErrorParser(MatchErrorParser): - def __init__(self): - super(CompatErrorParser, self).__init__(r"==== .*[Ee]rror.* ====$") - - -class TsanErrorParser(MatchErrorParser): - def __init__(self): - super(TsanErrorParser, self).__init__(r"WARNING: ThreadSanitizer:") - - -_TEST_NAME_TO_PARSERS = { - "punit": [CompilerErrorParser, GTestErrorParser], - "unit": [CompilerErrorParser, GTestErrorParser], - "release": [CompilerErrorParser, GTestErrorParser], - "unit_481": [CompilerErrorParser, GTestErrorParser], - "release_481": [CompilerErrorParser, GTestErrorParser], - "clang_unit": [CompilerErrorParser, GTestErrorParser], - "clang_release": [CompilerErrorParser, GTestErrorParser], - "clang_analyze": [CompilerErrorParser, ScanBuildErrorParser], - "code_cov": [CompilerErrorParser, GTestErrorParser], - "unity": [CompilerErrorParser, GTestErrorParser], - "lite": [CompilerErrorParser], - "lite_test": [CompilerErrorParser, GTestErrorParser], - "stress_crash": [CompilerErrorParser, DbCrashErrorParser], - "stress_crash_with_atomic_flush": [CompilerErrorParser, DbCrashErrorParser], - "stress_crash_with_txn": [CompilerErrorParser, DbCrashErrorParser], - "write_stress": [CompilerErrorParser, WriteStressErrorParser], - "asan": [CompilerErrorParser, GTestErrorParser, AsanErrorParser], - "asan_crash": [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], - "asan_crash_with_atomic_flush": [ - CompilerErrorParser, - AsanErrorParser, - DbCrashErrorParser, - ], - "asan_crash_with_txn": [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], - "ubsan": [CompilerErrorParser, GTestErrorParser, UbsanErrorParser], - "ubsan_crash": [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], - "ubsan_crash_with_atomic_flush": [ - CompilerErrorParser, - UbsanErrorParser, - DbCrashErrorParser, - ], - "ubsan_crash_with_txn": [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], - "valgrind": [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser], - "tsan": [CompilerErrorParser, GTestErrorParser, TsanErrorParser], - "format_compatible": [CompilerErrorParser, CompatErrorParser], - "run_format_compatible": [CompilerErrorParser, CompatErrorParser], - "no_compression": [CompilerErrorParser, GTestErrorParser], - "run_no_compression": [CompilerErrorParser, GTestErrorParser], - "regression": [CompilerErrorParser], - "run_regression": [CompilerErrorParser], -} - - -def main(): - if len(sys.argv) != 2: - return "Usage: %s " % sys.argv[0] - test_name = sys.argv[1] - if test_name not in _TEST_NAME_TO_PARSERS: - return "Unknown test name: %s" % test_name - - error_parsers = [] - for parser_cls in _TEST_NAME_TO_PARSERS[test_name]: - error_parsers.append(parser_cls()) - - for line in sys.stdin: - line = line.strip() - for error_parser in error_parsers: - error_msg = error_parser.parse_error(line) - if error_msg is not None: - print(error_msg) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/build_tools/fb_compile_mongo.sh b/build_tools/fb_compile_mongo.sh deleted file mode 100755 index ec733cdf1..000000000 --- a/build_tools/fb_compile_mongo.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# fail early -set -e - -if test -z $ROCKSDB_PATH; then - ROCKSDB_PATH=~/rocksdb -fi -source $ROCKSDB_PATH/build_tools/fbcode_config4.8.1.sh - -EXTRA_LDFLAGS="" - -if test -z $ALLOC; then - # default - ALLOC=tcmalloc -elif [[ $ALLOC == "jemalloc" ]]; then - ALLOC=system - EXTRA_LDFLAGS+=" -Wl,--whole-archive $JEMALLOC_LIB -Wl,--no-whole-archive" -fi - -# we need to force mongo to use static library, not shared -STATIC_LIB_DEP_DIR='build/static_library_dependencies' -test -d $STATIC_LIB_DEP_DIR || mkdir $STATIC_LIB_DEP_DIR -test -h $STATIC_LIB_DEP_DIR/`basename $SNAPPY_LIBS` || ln -s $SNAPPY_LIBS $STATIC_LIB_DEP_DIR -test -h $STATIC_LIB_DEP_DIR/`basename $LZ4_LIBS` || ln -s $LZ4_LIBS $STATIC_LIB_DEP_DIR - -EXTRA_LDFLAGS+=" -L $STATIC_LIB_DEP_DIR" - -set -x - -EXTRA_CMD="" -if ! test -e version.json; then - # this is Mongo 3.0 - EXTRA_CMD="--rocksdb \ - --variant-dir=linux2/norm - --cxx=${CXX} \ - --cc=${CC} \ - --use-system-zlib" # add this line back to normal code path - # when https://jira.mongodb.org/browse/SERVER-19123 is resolved -fi - -scons \ - LINKFLAGS="$EXTRA_LDFLAGS $EXEC_LDFLAGS $PLATFORM_LDFLAGS" \ - CCFLAGS="$CXXFLAGS -L $STATIC_LIB_DEP_DIR" \ - LIBS="lz4 gcc stdc++" \ - LIBPATH="$ROCKSDB_PATH" \ - CPPPATH="$ROCKSDB_PATH/include" \ - -j32 \ - --allocator=$ALLOC \ - --nostrip \ - --opt=on \ - --disable-minimum-compiler-version-enforcement \ - --use-system-snappy \ - --disable-warnings-as-errors \ - $EXTRA_CMD $* diff --git a/build_tools/fbcode_config.sh b/build_tools/fbcode_config.sh deleted file mode 100644 index cf3c355b1..000000000 --- a/build_tools/fbcode_config.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/sh -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# -# Set environment variables so that we can compile rocksdb using -# fbcode settings. It uses the latest g++ and clang compilers and also -# uses jemalloc -# Environment variables that change the behavior of this script: -# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included - - -BASEDIR=`dirname $BASH_SOURCE` -source "$BASEDIR/dependencies.sh" - -CFLAGS="" - -# libgcc -LIBGCC_INCLUDE="$LIBGCC_BASE/include" -LIBGCC_LIBS=" -L $LIBGCC_BASE/lib" - -# glibc -GLIBC_INCLUDE="$GLIBC_BASE/include" -GLIBC_LIBS=" -L $GLIBC_BASE/lib" - -if ! test $ROCKSDB_DISABLE_SNAPPY; then - # snappy - SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/" - if test -z $PIC_BUILD; then - SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a" - else - SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a" - fi - CFLAGS+=" -DSNAPPY" -fi - -if test -z $PIC_BUILD; then - if ! test $ROCKSDB_DISABLE_ZLIB; then - # location of zlib headers and libraries - ZLIB_INCLUDE=" -I $ZLIB_BASE/include/" - ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a" - CFLAGS+=" -DZLIB" - fi - - if ! test $ROCKSDB_DISABLE_BZIP; then - # location of bzip headers and libraries - BZIP_INCLUDE=" -I $BZIP2_BASE/include/" - BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a" - CFLAGS+=" -DBZIP2" - fi - - if ! test $ROCKSDB_DISABLE_LZ4; then - LZ4_INCLUDE=" -I $LZ4_BASE/include/" - LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a" - CFLAGS+=" -DLZ4" - fi -fi - -if ! test $ROCKSDB_DISABLE_ZSTD; then - ZSTD_INCLUDE=" -I $ZSTD_BASE/include/" - if test -z $PIC_BUILD; then - ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a" - else - ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a" - fi - CFLAGS+=" -DZSTD -DZSTD_STATIC_LINKING_ONLY" -fi - -# location of gflags headers and libraries -GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/" -if test -z $PIC_BUILD; then - GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a" -else - GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a" -fi -CFLAGS+=" -DGFLAGS=gflags" - -# location of jemalloc -JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/" -JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a" - -if test -z $PIC_BUILD; then - # location of numa - NUMA_INCLUDE=" -I $NUMA_BASE/include/" - NUMA_LIB=" $NUMA_BASE/lib/libnuma.a" - CFLAGS+=" -DNUMA" - - # location of libunwind - LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a" -fi - -# location of TBB -TBB_INCLUDE=" -isystem $TBB_BASE/include/" -if test -z $PIC_BUILD; then - TBB_LIBS="$TBB_BASE/lib/libtbb.a" -else - TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a" -fi -CFLAGS+=" -DTBB" - -test "$USE_SSE" || USE_SSE=1 -export USE_SSE -test "$PORTABLE" || PORTABLE=1 -export PORTABLE - -BINUTILS="$BINUTILS_BASE/bin" -AR="$BINUTILS/ar" - -DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE" - -STDLIBS="-L $GCC_BASE/lib64" - -CLANG_BIN="$CLANG_BASE/bin" -CLANG_LIB="$CLANG_BASE/lib" -CLANG_SRC="$CLANG_BASE/../../src" - -CLANG_ANALYZER="$CLANG_BIN/clang++" -CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build" - -if [ -z "$USE_CLANG" ]; then - # gcc - CC="$GCC_BASE/bin/gcc" - CXX="$GCC_BASE/bin/g++" - AR="$GCC_BASE/bin/gcc-ar" - - CFLAGS+=" -B$BINUTILS/gold" - CFLAGS+=" -isystem $GLIBC_INCLUDE" - CFLAGS+=" -isystem $LIBGCC_INCLUDE" - JEMALLOC=1 -else - # clang - CLANG_INCLUDE="$CLANG_LIB/clang/stable/include" - CC="$CLANG_BIN/clang" - CXX="$CLANG_BIN/clang++" - AR="$CLANG_BIN/llvm-ar" - - KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include" - - CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib" - CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/5.x " - CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/5.x/x86_64-facebook-linux " - CFLAGS+=" -isystem $GLIBC_INCLUDE" - CFLAGS+=" -isystem $LIBGCC_INCLUDE" - CFLAGS+=" -isystem $CLANG_INCLUDE" - CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux " - CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE " - CFLAGS+=" -Wno-expansion-to-defined " - CXXFLAGS="-nostdinc++" -fi - -CFLAGS+=" $DEPS_INCLUDE" -CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42" -CXXFLAGS+=" $CFLAGS" - -EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS" -EXEC_LDFLAGS+=" -B$BINUTILS/gold" -EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-5-glibc-2.23/lib/ld.so" -EXEC_LDFLAGS+=" $LIBUNWIND" -EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-5-glibc-2.23/lib" -# required by libtbb -EXEC_LDFLAGS+=" -ldl" - -PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++" - -EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS" - -VALGRIND_VER="$VALGRIND_BASE/bin/" - -LUA_PATH="$LUA_BASE" - -if test -z $PIC_BUILD; then - LUA_LIB=" $LUA_PATH/lib/liblua.a" -else - LUA_LIB=" $LUA_PATH/lib/liblua_pic.a" -fi - -export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB diff --git a/build_tools/fbcode_config_platform010.sh b/build_tools/fbcode_config_platform010.sh deleted file mode 100644 index babe92c41..000000000 --- a/build_tools/fbcode_config_platform010.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/sh -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# -# Set environment variables so that we can compile rocksdb using -# fbcode settings. It uses the latest g++ and clang compilers and also -# uses jemalloc -# Environment variables that change the behavior of this script: -# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included - - -BASEDIR=`dirname $BASH_SOURCE` -source "$BASEDIR/dependencies_platform010.sh" - -# Disallow using libraries from default locations as they might not be compatible with platform010 libraries. -CFLAGS=" --sysroot=/DOES/NOT/EXIST" - -# libgcc -LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/trunk" -LIBGCC_LIBS=" -L $LIBGCC_BASE/lib -B$LIBGCC_BASE/lib/gcc/x86_64-facebook-linux/trunk/" - -# glibc -GLIBC_INCLUDE="$GLIBC_BASE/include" -GLIBC_LIBS=" -L $GLIBC_BASE/lib" -GLIBC_LIBS+=" -B$GLIBC_BASE/lib" - -if test -z $PIC_BUILD; then - MAYBE_PIC= -else - MAYBE_PIC=_pic -fi - -if ! test $ROCKSDB_DISABLE_SNAPPY; then - # snappy - SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/" - SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy${MAYBE_PIC}.a" - CFLAGS+=" -DSNAPPY" -fi - -if ! test $ROCKSDB_DISABLE_ZLIB; then - # location of zlib headers and libraries - ZLIB_INCLUDE=" -I $ZLIB_BASE/include/" - ZLIB_LIBS=" $ZLIB_BASE/lib/libz${MAYBE_PIC}.a" - CFLAGS+=" -DZLIB" -fi - -if ! test $ROCKSDB_DISABLE_BZIP; then - # location of bzip headers and libraries - BZIP_INCLUDE=" -I $BZIP2_BASE/include/" - BZIP_LIBS=" $BZIP2_BASE/lib/libbz2${MAYBE_PIC}.a" - CFLAGS+=" -DBZIP2" -fi - -if ! test $ROCKSDB_DISABLE_LZ4; then - LZ4_INCLUDE=" -I $LZ4_BASE/include/" - LZ4_LIBS=" $LZ4_BASE/lib/liblz4${MAYBE_PIC}.a" - CFLAGS+=" -DLZ4" -fi - -if ! test $ROCKSDB_DISABLE_ZSTD; then - ZSTD_INCLUDE=" -I $ZSTD_BASE/include/" - ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd${MAYBE_PIC}.a" - CFLAGS+=" -DZSTD" -fi - -# location of gflags headers and libraries -GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/" -GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags${MAYBE_PIC}.a" -CFLAGS+=" -DGFLAGS=gflags" - -BENCHMARK_INCLUDE=" -I $BENCHMARK_BASE/include/" -BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark${MAYBE_PIC}.a" - -# location of jemalloc -JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/" -JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc${MAYBE_PIC}.a" - -# location of numa -NUMA_INCLUDE=" -I $NUMA_BASE/include/" -NUMA_LIB=" $NUMA_BASE/lib/libnuma${MAYBE_PIC}.a" -CFLAGS+=" -DNUMA" - -# location of libunwind -LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind${MAYBE_PIC}.a" - -# location of TBB -TBB_INCLUDE=" -isystem $TBB_BASE/include/" -TBB_LIBS="$TBB_BASE/lib/libtbb${MAYBE_PIC}.a" -CFLAGS+=" -DTBB" - -# location of LIBURING -LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/" -LIBURING_LIBS="$LIBURING_BASE/lib/liburing${MAYBE_PIC}.a" -CFLAGS+=" -DLIBURING" - -test "$USE_SSE" || USE_SSE=1 -export USE_SSE -test "$PORTABLE" || PORTABLE=1 -export PORTABLE - -BINUTILS="$BINUTILS_BASE/bin" -AR="$BINUTILS/ar" -AS="$BINUTILS/as" - -DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE" - -STDLIBS="-L $GCC_BASE/lib64" - -CLANG_BIN="$CLANG_BASE/bin" -CLANG_LIB="$CLANG_BASE/lib" -CLANG_SRC="$CLANG_BASE/../../src" - -CLANG_ANALYZER="$CLANG_BIN/clang++" -CLANG_SCAN_BUILD="$CLANG_SRC/llvm/clang/tools/scan-build/bin/scan-build" - -if [ -z "$USE_CLANG" ]; then - # gcc - CC="$GCC_BASE/bin/gcc" - CXX="$GCC_BASE/bin/g++" - AR="$GCC_BASE/bin/gcc-ar" - - CFLAGS+=" -B$BINUTILS -nostdinc -nostdlib" - CFLAGS+=" -I$GCC_BASE/include" - CFLAGS+=" -isystem $GCC_BASE/lib/gcc/x86_64-redhat-linux-gnu/11.2.1/include" - CFLAGS+=" -isystem $GCC_BASE/lib/gcc/x86_64-redhat-linux-gnu/11.2.1/install-tools/include" - CFLAGS+=" -isystem $GCC_BASE/lib/gcc/x86_64-redhat-linux-gnu/11.2.1/include-fixed/" - CFLAGS+=" -isystem $LIBGCC_INCLUDE" - CFLAGS+=" -isystem $GLIBC_INCLUDE" - CFLAGS+=" -I$GLIBC_INCLUDE" - CFLAGS+=" -I$LIBGCC_BASE/include" - CFLAGS+=" -I$LIBGCC_BASE/include/c++/11.x/" - CFLAGS+=" -I$LIBGCC_BASE/include/c++/11.x/x86_64-facebook-linux/" - CFLAGS+=" -I$LIBGCC_BASE/include/c++/11.x/backward" - CFLAGS+=" -isystem $GLIBC_INCLUDE -I$GLIBC_INCLUDE" - JEMALLOC=1 -else - # clang - CLANG_INCLUDE="$CLANG_LIB/clang/stable/include" - CC="$CLANG_BIN/clang" - CXX="$CLANG_BIN/clang++" - AR="$CLANG_BIN/llvm-ar" - - CFLAGS+=" -B$BINUTILS -nostdinc -nostdlib" - CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/trunk " - CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/trunk/x86_64-facebook-linux " - CFLAGS+=" -isystem $GLIBC_INCLUDE" - CFLAGS+=" -isystem $LIBGCC_INCLUDE" - CFLAGS+=" -isystem $CLANG_INCLUDE" - CFLAGS+=" -Wno-expansion-to-defined " - CXXFLAGS="-nostdinc++" -fi - -KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include" -CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux " -CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE " - -CFLAGS+=" $DEPS_INCLUDE" -CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT" -CXXFLAGS+=" $CFLAGS" - -EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS" -EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform010/lib/ld.so" -EXEC_LDFLAGS+=" $LIBUNWIND" -EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform010/lib" -EXEC_LDFLAGS+=" -Wl,-rpath=$GCC_BASE/lib64" -# required by libtbb -EXEC_LDFLAGS+=" -ldl" - -PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++" -PLATFORM_LDFLAGS+=" -B$BINUTILS" - -EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS" - -VALGRIND_VER="$VALGRIND_BASE/bin/" - -export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB diff --git a/build_tools/format-diff.sh b/build_tools/format-diff.sh deleted file mode 100755 index 62e8834f7..000000000 --- a/build_tools/format-diff.sh +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# If clang_format_diff.py command is not specfied, we assume we are able to -# access directly without any path. - -print_usage () { - echo "Usage:" - echo "format-diff.sh [OPTIONS]" - echo "-c: check only." - echo "-h: print this message." -} - -while getopts ':ch' OPTION; do - case "$OPTION" in - c) - CHECK_ONLY=1 - ;; - h) - print_usage - exit 1 - ;; - ?) - print_usage - exit 1 - ;; - esac -done - -REPO_ROOT="$(git rev-parse --show-toplevel)" - -if [ "$CLANG_FORMAT_DIFF" ]; then - echo "Note: CLANG_FORMAT_DIFF='$CLANG_FORMAT_DIFF'" - # Dry run to confirm dependencies like argparse - if $CLANG_FORMAT_DIFF --help >/dev/null < /dev/null; then - true #Good - else - exit 128 - fi -else - # First try directly executing the possibilities - if clang-format-diff --help &> /dev/null < /dev/null; then - CLANG_FORMAT_DIFF=clang-format-diff - elif clang-format-diff.py --help &> /dev/null < /dev/null; then - CLANG_FORMAT_DIFF=clang-format-diff.py - elif $REPO_ROOT/clang-format-diff.py --help &> /dev/null < /dev/null; then - CLANG_FORMAT_DIFF=$REPO_ROOT/clang-format-diff.py - else - # This probably means we need to directly invoke the interpreter. - # But first find clang-format-diff.py - if [ -f "$REPO_ROOT/clang-format-diff.py" ]; then - CFD_PATH="$REPO_ROOT/clang-format-diff.py" - elif which clang-format-diff.py &> /dev/null; then - CFD_PATH="$(which clang-format-diff.py)" - else - echo "You didn't have clang-format-diff.py and/or clang-format available in your computer!" - echo "You can download clang-format-diff.py by running: " - echo " curl --location https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o ${REPO_ROOT}/clang-format-diff.py" - echo "You should make sure the downloaded script is not compromised." - echo "You can download clang-format by running:" - echo " brew install clang-format" - echo " Or" - echo " apt install clang-format" - echo " This might work too:" - echo " yum install git-clang-format" - echo "Then make sure clang-format is available and executable from \$PATH:" - echo " clang-format --version" - exit 128 - fi - # Check argparse pre-req on interpreter, or it will fail - if echo import argparse | ${PYTHON:-python3}; then - true # Good - else - echo "To run clang-format-diff.py, we'll need the library "argparse" to be" - echo "installed. You can try either of the follow ways to install it:" - echo " 1. Manually download argparse: https://pypi.python.org/pypi/argparse" - echo " 2. easy_install argparse (if you have easy_install)" - echo " 3. pip install argparse (if you have pip)" - exit 129 - fi - # Unfortunately, some machines have a Python2 clang-format-diff.py - # installed but only a Python3 interpreter installed. Unfortunately, - # automatic 2to3 migration is insufficient, so suggest downloading latest. - if grep -q "print '" "$CFD_PATH" && \ - ${PYTHON:-python3} --version | grep -q 'ython 3'; then - echo "You have clang-format-diff.py for Python 2 but are using a Python 3" - echo "interpreter (${PYTHON:-python3})." - echo "You can download clang-format-diff.py for Python 3 by running: " - echo " curl --location https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o ${REPO_ROOT}/clang-format-diff.py" - echo "You should make sure the downloaded script is not compromised." - exit 130 - fi - CLANG_FORMAT_DIFF="${PYTHON:-python3} $CFD_PATH" - # This had better work after all those checks - if $CLANG_FORMAT_DIFF --help >/dev/null < /dev/null; then - true #Good - else - exit 128 - fi - fi -fi - -# TODO(kailiu) following work is not complete since we still need to figure -# out how to add the modified files done pre-commit hook to git's commit index. -# -# Check if this script has already been added to pre-commit hook. -# Will suggest user to add this script to pre-commit hook if their pre-commit -# is empty. -# PRE_COMMIT_SCRIPT_PATH="`git rev-parse --show-toplevel`/.git/hooks/pre-commit" -# if ! ls $PRE_COMMIT_SCRIPT_PATH &> /dev/null -# then -# echo "Would you like to add this script to pre-commit hook, which will do " -# echo -n "the format check for all the affected lines before you check in (y/n):" -# read add_to_hook -# if [ "$add_to_hook" == "y" ] -# then -# ln -s `git rev-parse --show-toplevel`/build_tools/format-diff.sh $PRE_COMMIT_SCRIPT_PATH -# fi -# fi -set -e - -uncommitted_code=`git diff HEAD` - -# If there's no uncommitted changes, we assume user are doing post-commit -# format check, in which case we'll try to check the modified lines vs. the -# facebook/rocksdb.git main branch. Otherwise, we'll check format of the -# uncommitted code only. -if [ -z "$uncommitted_code" ] -then - # Attempt to get name of facebook/rocksdb.git remote. - [ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(LC_ALL=POSIX LANG=POSIX git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)" - # Fall back on 'origin' if that fails - [ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin - # Use main branch from that remote - [ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/$(LC_ALL=POSIX LANG=POSIX git remote show $FORMAT_REMOTE | sed -n '/HEAD branch/s/.*: //p')" - # Get the common ancestor with that remote branch. Everything after that - # common ancestor would be considered the contents of a pull request, so - # should be relevant for formatting fixes. - FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)" - # Get the differences - diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1) - echo "Checking format of changes not yet in $FORMAT_UPSTREAM..." -else - # Check the format of uncommitted lines, - diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1) - echo "Checking format of uncommitted changes..." -fi - -if [ -z "$diffs" ] -then - echo "Nothing needs to be reformatted!" - exit 0 -elif [ $CHECK_ONLY ] -then - echo "Your change has unformatted code. Please run make format!" - if [ $VERBOSE_CHECK ]; then - clang-format --version - echo "$diffs" - fi - exit 1 -fi - -# Highlight the insertion/deletion from the clang-format-diff.py's output -COLOR_END="\033[0m" -COLOR_RED="\033[0;31m" -COLOR_GREEN="\033[0;32m" - -echo -e "Detect lines that doesn't follow the format rules:\r" -# Add the color to the diff. lines added will be green; lines removed will be red. -echo "$diffs" | - sed -e "s/\(^-.*$\)/`echo -e \"$COLOR_RED\1$COLOR_END\"`/" | - sed -e "s/\(^+.*$\)/`echo -e \"$COLOR_GREEN\1$COLOR_END\"`/" - -echo -e "Would you like to fix the format automatically (y/n): \c" - -# Make sure under any mode, we can read user input. -exec < /dev/tty -read to_fix - -if [ "$to_fix" != "y" ] -then - exit 1 -fi - -# Do in-place format adjustment. -if [ -z "$uncommitted_code" ] -then - git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -i -p 1 -else - git diff -U0 HEAD | $CLANG_FORMAT_DIFF -i -p 1 -fi -echo "Files reformatted!" - -# Amend to last commit if user do the post-commit format check -if [ -z "$uncommitted_code" ]; then - echo -e "Would you like to amend the changes to last commit (`git log HEAD --oneline | head -1`)? (y/n): \c" - read to_amend - - if [ "$to_amend" == "y" ] - then - git commit -a --amend --reuse-message HEAD - echo "Amended to last commit" - fi -fi diff --git a/build_tools/gnu_parallel b/build_tools/gnu_parallel deleted file mode 100755 index 3365f46ba..000000000 --- a/build_tools/gnu_parallel +++ /dev/null @@ -1,7971 +0,0 @@ -#!/usr/bin/env perl - -# Copyright (C) 2007,2008,2009,2010,2011,2012,2013,2014 Ole Tange and -# Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, see -# or write to the Free Software Foundation, Inc., 51 Franklin St, -# Fifth Floor, Boston, MA 02110-1301 USA - -# open3 used in Job::start -use IPC::Open3; -# &WNOHANG used in reaper -use POSIX qw(:sys_wait_h setsid ceil :errno_h); -# gensym used in Job::start -use Symbol qw(gensym); -# tempfile used in Job::start -use File::Temp qw(tempfile tempdir); -# mkpath used in openresultsfile -use File::Path; -# GetOptions used in get_options_from_array -use Getopt::Long; -# Used to ensure code quality -use strict; -use File::Basename; - -if(not $ENV{HOME}) { - # $ENV{HOME} is sometimes not set if called from PHP - ::warning("\$HOME not set. Using /tmp\n"); - $ENV{HOME} = "/tmp"; -} - -save_stdin_stdout_stderr(); -save_original_signal_handler(); -parse_options(); -::debug("init", "Open file descriptors: ", join(" ",keys %Global::fd), "\n"); -my $number_of_args; -if($Global::max_number_of_args) { - $number_of_args=$Global::max_number_of_args; -} elsif ($opt::X or $opt::m or $opt::xargs) { - $number_of_args = undef; -} else { - $number_of_args = 1; -} - -my @command; -@command = @ARGV; - -my @fhlist; -if($opt::pipepart) { - @fhlist = map { open_or_exit($_) } "/dev/null"; -} else { - @fhlist = map { open_or_exit($_) } @opt::a; - if(not @fhlist and not $opt::pipe) { - @fhlist = (*STDIN); - } -} - -if($opt::skip_first_line) { - # Skip the first line for the first file handle - my $fh = $fhlist[0]; - <$fh>; -} -if($opt::header and not $opt::pipe) { - my $fh = $fhlist[0]; - # split with colsep or \t - # $header force $colsep = \t if undef? - my $delimiter = $opt::colsep; - $delimiter ||= "\$"; - my $id = 1; - for my $fh (@fhlist) { - my $line = <$fh>; - chomp($line); - ::debug("init", "Delimiter: '$delimiter'"); - for my $s (split /$delimiter/o, $line) { - ::debug("init", "Colname: '$s'"); - # Replace {colname} with {2} - # TODO accept configurable short hands - # TODO how to deal with headers in {=...=} - for(@command) { - s:\{$s(|/|//|\.|/\.)\}:\{$id$1\}:g; - } - $Global::input_source_header{$id} = $s; - $id++; - } - } -} else { - my $id = 1; - for my $fh (@fhlist) { - $Global::input_source_header{$id} = $id; - $id++; - } -} - -if($opt::filter_hosts and (@opt::sshlogin or @opt::sshloginfile)) { - # Parallel check all hosts are up. Remove hosts that are down - filter_hosts(); -} - -if($opt::nonall or $opt::onall) { - onall(@command); - wait_and_exit(min(undef_as_zero($Global::exitstatus),254)); -} - -# TODO --transfer foo/./bar --cleanup -# multiple --transfer and --basefile with different /./ - -$Global::JobQueue = JobQueue->new( - \@command,\@fhlist,$Global::ContextReplace,$number_of_args,\@Global::ret_files); - -if($opt::eta or $opt::bar) { - # Count the number of jobs before starting any - $Global::JobQueue->total_jobs(); -} -if($opt::pipepart) { - @Global::cat_partials = map { pipe_part_files($_) } @opt::a; - # Unget the command as many times as there are parts - $Global::JobQueue->{'commandlinequeue'}->unget( - map { $Global::JobQueue->{'commandlinequeue'}->get() } @Global::cat_partials - ); -} -for my $sshlogin (values %Global::host) { - $sshlogin->max_jobs_running(); -} - -init_run_jobs(); -my $sem; -if($Global::semaphore) { - $sem = acquire_semaphore(); -} -$SIG{TERM} = \&start_no_new_jobs; - -start_more_jobs(); -if(not $opt::pipepart) { - if($opt::pipe) { - spreadstdin(); - } -} -::debug("init", "Start draining\n"); -drain_job_queue(); -::debug("init", "Done draining\n"); -reaper(); -::debug("init", "Done reaping\n"); -if($opt::pipe and @opt::a) { - for my $job (@Global::tee_jobs) { - unlink $job->fh(2,"name"); - $job->set_fh(2,"name",""); - $job->print(); - unlink $job->fh(1,"name"); - } -} -::debug("init", "Cleaning\n"); -cleanup(); -if($Global::semaphore) { - $sem->release(); -} -for(keys %Global::sshmaster) { - kill "TERM", $_; -} -::debug("init", "Halt\n"); -if($opt::halt_on_error) { - wait_and_exit($Global::halt_on_error_exitstatus); -} else { - wait_and_exit(min(undef_as_zero($Global::exitstatus),254)); -} - -sub __PIPE_MODE__ {} - -sub pipe_part_files { - # Input: - # $file = the file to read - # Returns: - # @commands that will cat_partial each part - my ($file) = @_; - my $buf = ""; - my $header = find_header(\$buf,open_or_exit($file)); - # find positions - my @pos = find_split_positions($file,$opt::blocksize,length $header); - # Make @cat_partials - my @cat_partials = (); - for(my $i=0; $i<$#pos; $i++) { - push @cat_partials, cat_partial($file, 0, length($header), $pos[$i], $pos[$i+1]); - } - # Remote exec should look like: - # ssh -oLogLevel=quiet lo 'eval `echo $SHELL | grep "/t\{0,1\}csh" > /dev/null && echo setenv PARALLEL_SEQ '$PARALLEL_SEQ'\; setenv PARALLEL_PID '$PARALLEL_PID' || echo PARALLEL_SEQ='$PARALLEL_SEQ'\;export PARALLEL_SEQ\; PARALLEL_PID='$PARALLEL_PID'\;export PARALLEL_PID` ;' tty\ \>/dev/null\ \&\&\ stty\ isig\ -onlcr\ -echo\;echo\ \$SHELL\ \|\ grep\ \"/t\\\{0,1\\\}csh\"\ \>\ /dev/null\ \&\&\ setenv\ FOO\ /tmp/foo\ \|\|\ export\ FOO=/tmp/foo\; \(wc\ -\ \$FOO\) - # ssh -tt not allowed. Remote will die due to broken pipe anyway. - # TODO test remote with --fifo / --cat - return @cat_partials; -} - -sub find_header { - # Input: - # $buf_ref = reference to read-in buffer - # $fh = filehandle to read from - # Uses: - # $opt::header - # $opt::blocksize - # Returns: - # $header string - my ($buf_ref, $fh) = @_; - my $header = ""; - if($opt::header) { - if($opt::header eq ":") { $opt::header = "(.*\n)"; } - # Number = number of lines - $opt::header =~ s/^(\d+)$/"(.*\n)"x$1/e; - while(read($fh,substr($$buf_ref,length $$buf_ref,0),$opt::blocksize)) { - if($$buf_ref=~s/^($opt::header)//) { - $header = $1; - last; - } - } - } - return $header; -} - -sub find_split_positions { - # Input: - # $file = the file to read - # $block = (minimal) --block-size of each chunk - # $headerlen = length of header to be skipped - # Uses: - # $opt::recstart - # $opt::recend - # Returns: - # @positions of block start/end - my($file, $block, $headerlen) = @_; - my $size = -s $file; - $block = int $block; - # The optimal dd blocksize for mint, redhat, solaris, openbsd = 2^17..2^20 - # The optimal dd blocksize for freebsd = 2^15..2^17 - my $dd_block_size = 131072; # 2^17 - my @pos; - my ($recstart,$recend) = recstartrecend(); - my $recendrecstart = $recend.$recstart; - my $fh = ::open_or_exit($file); - push(@pos,$headerlen); - for(my $pos = $block+$headerlen; $pos < $size; $pos += $block) { - my $buf; - seek($fh, $pos, 0) || die; - while(read($fh,substr($buf,length $buf,0),$dd_block_size)) { - if($opt::regexp) { - # If match /$recend$recstart/ => Record position - if($buf =~ /(.*$recend)$recstart/os) { - my $i = length($1); - push(@pos,$pos+$i); - # Start looking for next record _after_ this match - $pos += $i; - last; - } - } else { - # If match $recend$recstart => Record position - my $i = index($buf,$recendrecstart); - if($i != -1) { - push(@pos,$pos+$i); - # Start looking for next record _after_ this match - $pos += $i; - last; - } - } - } - } - push(@pos,$size); - close $fh; - return @pos; -} - -sub cat_partial { - # Input: - # $file = the file to read - # ($start, $end, [$start2, $end2, ...]) = start byte, end byte - # Returns: - # Efficient perl command to copy $start..$end, $start2..$end2, ... to stdout - my($file, @start_end) = @_; - my($start, $i); - # Convert start_end to start_len - my @start_len = map { if(++$i % 2) { $start = $_; } else { $_-$start } } @start_end; - return "<". shell_quote_scalar($file) . - q{ perl -e 'while(@ARGV) { sysseek(STDIN,shift,0) || die; $left = shift; while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ $left -= $read; syswrite(STDOUT,$buf); } }' } . - " @start_len"; -} - -sub spreadstdin { - # read a record - # Spawn a job and print the record to it. - # Uses: - # $opt::blocksize - # STDIN - # $opr::r - # $Global::max_lines - # $Global::max_number_of_args - # $opt::regexp - # $Global::start_no_new_jobs - # $opt::roundrobin - # %Global::running - - my $buf = ""; - my ($recstart,$recend) = recstartrecend(); - my $recendrecstart = $recend.$recstart; - my $chunk_number = 1; - my $one_time_through; - my $blocksize = $opt::blocksize; - my $in = *STDIN; - my $header = find_header(\$buf,$in); - while(1) { - my $anything_written = 0; - if(not read($in,substr($buf,length $buf,0),$blocksize)) { - # End-of-file - $chunk_number != 1 and last; - # Force the while-loop once if everything was read by header reading - $one_time_through++ and last; - } - if($opt::r) { - # Remove empty lines - $buf =~ s/^\s*\n//gm; - if(length $buf == 0) { - next; - } - } - if($Global::max_lines and not $Global::max_number_of_args) { - # Read n-line records - my $n_lines = $buf =~ tr/\n/\n/; - my $last_newline_pos = rindex($buf,"\n"); - while($n_lines % $Global::max_lines) { - $n_lines--; - $last_newline_pos = rindex($buf,"\n",$last_newline_pos-1); - } - # Chop at $last_newline_pos as that is where n-line record ends - $anything_written += - write_record_to_pipe($chunk_number++,\$header,\$buf, - $recstart,$recend,$last_newline_pos+1); - substr($buf,0,$last_newline_pos+1) = ""; - } elsif($opt::regexp) { - if($Global::max_number_of_args) { - # -N => (start..*?end){n} - # -L -N => (start..*?end){n*l} - my $read_n_lines = $Global::max_number_of_args * ($Global::max_lines || 1); - while($buf =~ s/((?:$recstart.*?$recend){$read_n_lines})($recstart.*)$/$2/os) { - # Copy to modifiable variable - my $b = $1; - $anything_written += - write_record_to_pipe($chunk_number++,\$header,\$b, - $recstart,$recend,length $1); - } - } else { - # Find the last recend-recstart in $buf - if($buf =~ s/(.*$recend)($recstart.*?)$/$2/os) { - # Copy to modifiable variable - my $b = $1; - $anything_written += - write_record_to_pipe($chunk_number++,\$header,\$b, - $recstart,$recend,length $1); - } - } - } else { - if($Global::max_number_of_args) { - # -N => (start..*?end){n} - my $i = 0; - my $read_n_lines = $Global::max_number_of_args * ($Global::max_lines || 1); - while(($i = nindex(\$buf,$recendrecstart,$read_n_lines)) != -1) { - $i += length $recend; # find the actual splitting location - $anything_written += - write_record_to_pipe($chunk_number++,\$header,\$buf, - $recstart,$recend,$i); - substr($buf,0,$i) = ""; - } - } else { - # Find the last recend-recstart in $buf - my $i = rindex($buf,$recendrecstart); - if($i != -1) { - $i += length $recend; # find the actual splitting location - $anything_written += - write_record_to_pipe($chunk_number++,\$header,\$buf, - $recstart,$recend,$i); - substr($buf,0,$i) = ""; - } - } - } - if(not $anything_written and not eof($in)) { - # Nothing was written - maybe the block size < record size? - # Increase blocksize exponentially - my $old_blocksize = $blocksize; - $blocksize = ceil($blocksize * 1.3 + 1); - ::warning("A record was longer than $old_blocksize. " . - "Increasing to --blocksize $blocksize\n"); - } - } - ::debug("init", "Done reading input\n"); - - # If there is anything left in the buffer write it - substr($buf,0,0) = ""; - write_record_to_pipe($chunk_number++,\$header,\$buf,$recstart,$recend,length $buf); - - $Global::start_no_new_jobs ||= 1; - if($opt::roundrobin) { - for my $job (values %Global::running) { - close $job->fh(0,"w"); - } - my %incomplete_jobs = %Global::running; - my $sleep = 1; - while(keys %incomplete_jobs) { - my $something_written = 0; - for my $pid (keys %incomplete_jobs) { - my $job = $incomplete_jobs{$pid}; - if($job->stdin_buffer_length()) { - $something_written += $job->non_block_write(); - } else { - delete $incomplete_jobs{$pid} - } - } - if($something_written) { - $sleep = $sleep/2+0.001; - } - $sleep = ::reap_usleep($sleep); - } - } -} - -sub recstartrecend { - # Uses: - # $opt::recstart - # $opt::recend - # Returns: - # $recstart,$recend with default values and regexp conversion - my($recstart,$recend); - if(defined($opt::recstart) and defined($opt::recend)) { - # If both --recstart and --recend is given then both must match - $recstart = $opt::recstart; - $recend = $opt::recend; - } elsif(defined($opt::recstart)) { - # If --recstart is given it must match start of record - $recstart = $opt::recstart; - $recend = ""; - } elsif(defined($opt::recend)) { - # If --recend is given then it must match end of record - $recstart = ""; - $recend = $opt::recend; - } - - if($opt::regexp) { - # If $recstart/$recend contains '|' this should only apply to the regexp - $recstart = "(?:".$recstart.")"; - $recend = "(?:".$recend.")"; - } else { - # $recstart/$recend = printf strings (\n) - $recstart =~ s/\\([0rnt\'\"\\])/"qq|\\$1|"/gee; - $recend =~ s/\\([0rnt\'\"\\])/"qq|\\$1|"/gee; - } - return ($recstart,$recend); -} - -sub nindex { - # See if string is in buffer N times - # Returns: - # the position where the Nth copy is found - my ($buf_ref, $str, $n) = @_; - my $i = 0; - for(1..$n) { - $i = index($$buf_ref,$str,$i+1); - if($i == -1) { last } - } - return $i; -} - -{ - my @robin_queue; - - sub round_robin_write { - # Input: - # $header_ref = ref to $header string - # $block_ref = ref to $block to be written - # $recstart = record start string - # $recend = record end string - # $endpos = end position of $block - # Uses: - # %Global::running - my ($header_ref,$block_ref,$recstart,$recend,$endpos) = @_; - my $something_written = 0; - my $block_passed = 0; - my $sleep = 1; - while(not $block_passed) { - # Continue flushing existing buffers - # until one is empty and a new block is passed - # Make a queue to spread the blocks evenly - if(not @robin_queue) { - push @robin_queue, values %Global::running; - } - while(my $job = shift @robin_queue) { - if($job->stdin_buffer_length() > 0) { - $something_written += $job->non_block_write(); - } else { - $job->set_stdin_buffer($header_ref,$block_ref,$endpos,$recstart,$recend); - $block_passed = 1; - $job->set_virgin(0); - $something_written += $job->non_block_write(); - last; - } - } - $sleep = ::reap_usleep($sleep); - } - return $something_written; - } -} - -sub write_record_to_pipe { - # Fork then - # Write record from pos 0 .. $endpos to pipe - # Input: - # $chunk_number = sequence number - to see if already run - # $header_ref = reference to header string to prepend - # $record_ref = reference to record to write - # $recstart = start string of record - # $recend = end string of record - # $endpos = position in $record_ref where record ends - # Uses: - # $Global::job_already_run - # $opt::roundrobin - # @Global::virgin_jobs - # Returns: - # Number of chunks written (0 or 1) - my ($chunk_number,$header_ref,$record_ref,$recstart,$recend,$endpos) = @_; - if($endpos == 0) { return 0; } - if(vec($Global::job_already_run,$chunk_number,1)) { return 1; } - if($opt::roundrobin) { - return round_robin_write($header_ref,$record_ref,$recstart,$recend,$endpos); - } - # If no virgin found, backoff - my $sleep = 0.0001; # 0.01 ms - better performance on highend - while(not @Global::virgin_jobs) { - ::debug("pipe", "No virgin jobs"); - $sleep = ::reap_usleep($sleep); - # Jobs may not be started because of loadavg - # or too little time between each ssh login. - start_more_jobs(); - } - my $job = shift @Global::virgin_jobs; - # Job is no longer virgin - $job->set_virgin(0); - if(fork()) { - # Skip - } else { - # Chop of at $endpos as we do not know how many rec_sep will - # be removed. - substr($$record_ref,$endpos,length $$record_ref) = ""; - # Remove rec_sep - if($opt::remove_rec_sep) { - Job::remove_rec_sep($record_ref,$recstart,$recend); - } - $job->write($header_ref); - $job->write($record_ref); - close $job->fh(0,"w"); - exit(0); - } - close $job->fh(0,"w"); - return 1; -} - -sub __SEM_MODE__ {} - -sub acquire_semaphore { - # Acquires semaphore. If needed: spawns to the background - # Uses: - # @Global::host - # Returns: - # The semaphore to be released when jobs is complete - $Global::host{':'} = SSHLogin->new(":"); - my $sem = Semaphore->new($Semaphore::name,$Global::host{':'}->max_jobs_running()); - $sem->acquire(); - if($Semaphore::fg) { - # skip - } else { - # If run in the background, the PID will change - # therefore release and re-acquire the semaphore - $sem->release(); - if(fork()) { - exit(0); - } else { - # child - # Get a semaphore for this pid - ::die_bug("Can't start a new session: $!") if setsid() == -1; - $sem = Semaphore->new($Semaphore::name,$Global::host{':'}->max_jobs_running()); - $sem->acquire(); - } - } - return $sem; -} - -sub __PARSE_OPTIONS__ {} - -sub options_hash { - # Returns: - # %hash = the GetOptions config - return - ("debug|D=s" => \$opt::D, - "xargs" => \$opt::xargs, - "m" => \$opt::m, - "X" => \$opt::X, - "v" => \@opt::v, - "joblog=s" => \$opt::joblog, - "results|result|res=s" => \$opt::results, - "resume" => \$opt::resume, - "resume-failed|resumefailed" => \$opt::resume_failed, - "silent" => \$opt::silent, - #"silent-error|silenterror" => \$opt::silent_error, - "keep-order|keeporder|k" => \$opt::keeporder, - "group" => \$opt::group, - "g" => \$opt::retired, - "ungroup|u" => \$opt::ungroup, - "linebuffer|linebuffered|line-buffer|line-buffered" => \$opt::linebuffer, - "tmux" => \$opt::tmux, - "null|0" => \$opt::0, - "quote|q" => \$opt::q, - # Replacement strings - "parens=s" => \$opt::parens, - "rpl=s" => \@opt::rpl, - "plus" => \$opt::plus, - "I=s" => \$opt::I, - "extensionreplace|er=s" => \$opt::U, - "U=s" => \$opt::retired, - "basenamereplace|bnr=s" => \$opt::basenamereplace, - "dirnamereplace|dnr=s" => \$opt::dirnamereplace, - "basenameextensionreplace|bner=s" => \$opt::basenameextensionreplace, - "seqreplace=s" => \$opt::seqreplace, - "slotreplace=s" => \$opt::slotreplace, - "jobs|j=s" => \$opt::jobs, - "delay=f" => \$opt::delay, - "sshdelay=f" => \$opt::sshdelay, - "load=s" => \$opt::load, - "noswap" => \$opt::noswap, - "max-line-length-allowed" => \$opt::max_line_length_allowed, - "number-of-cpus" => \$opt::number_of_cpus, - "number-of-cores" => \$opt::number_of_cores, - "use-cpus-instead-of-cores" => \$opt::use_cpus_instead_of_cores, - "shellquote|shell_quote|shell-quote" => \$opt::shellquote, - "nice=i" => \$opt::nice, - "timeout=s" => \$opt::timeout, - "tag" => \$opt::tag, - "tagstring|tag-string=s" => \$opt::tagstring, - "onall" => \$opt::onall, - "nonall" => \$opt::nonall, - "filter-hosts|filterhosts|filter-host" => \$opt::filter_hosts, - "sshlogin|S=s" => \@opt::sshlogin, - "sshloginfile|slf=s" => \@opt::sshloginfile, - "controlmaster|M" => \$opt::controlmaster, - "return=s" => \@opt::return, - "trc=s" => \@opt::trc, - "transfer" => \$opt::transfer, - "cleanup" => \$opt::cleanup, - "basefile|bf=s" => \@opt::basefile, - "B=s" => \$opt::retired, - "ctrlc|ctrl-c" => \$opt::ctrlc, - "noctrlc|no-ctrlc|no-ctrl-c" => \$opt::noctrlc, - "workdir|work-dir|wd=s" => \$opt::workdir, - "W=s" => \$opt::retired, - "tmpdir=s" => \$opt::tmpdir, - "tempdir=s" => \$opt::tmpdir, - "use-compress-program|compress-program=s" => \$opt::compress_program, - "use-decompress-program|decompress-program=s" => \$opt::decompress_program, - "compress" => \$opt::compress, - "tty" => \$opt::tty, - "T" => \$opt::retired, - "halt-on-error|halt=s" => \$opt::halt_on_error, - "H=i" => \$opt::retired, - "retries=i" => \$opt::retries, - "dry-run|dryrun" => \$opt::dryrun, - "progress" => \$opt::progress, - "eta" => \$opt::eta, - "bar" => \$opt::bar, - "arg-sep|argsep=s" => \$opt::arg_sep, - "arg-file-sep|argfilesep=s" => \$opt::arg_file_sep, - "trim=s" => \$opt::trim, - "env=s" => \@opt::env, - "recordenv|record-env" => \$opt::record_env, - "plain" => \$opt::plain, - "profile|J=s" => \@opt::profile, - "pipe|spreadstdin" => \$opt::pipe, - "robin|round-robin|roundrobin" => \$opt::roundrobin, - "recstart=s" => \$opt::recstart, - "recend=s" => \$opt::recend, - "regexp|regex" => \$opt::regexp, - "remove-rec-sep|removerecsep|rrs" => \$opt::remove_rec_sep, - "files|output-as-files|outputasfiles" => \$opt::files, - "block|block-size|blocksize=s" => \$opt::blocksize, - "tollef" => \$opt::retired, - "gnu" => \$opt::gnu, - "xapply" => \$opt::xapply, - "bibtex" => \$opt::bibtex, - "nn|nonotice|no-notice" => \$opt::no_notice, - # xargs-compatibility - implemented, man, testsuite - "max-procs|P=s" => \$opt::jobs, - "delimiter|d=s" => \$opt::d, - "max-chars|s=i" => \$opt::max_chars, - "arg-file|a=s" => \@opt::a, - "no-run-if-empty|r" => \$opt::r, - "replace|i:s" => \$opt::i, - "E=s" => \$opt::eof, - "eof|e:s" => \$opt::eof, - "max-args|n=i" => \$opt::max_args, - "max-replace-args|N=i" => \$opt::max_replace_args, - "colsep|col-sep|C=s" => \$opt::colsep, - "help|h" => \$opt::help, - "L=f" => \$opt::L, - "max-lines|l:f" => \$opt::max_lines, - "interactive|p" => \$opt::p, - "verbose|t" => \$opt::verbose, - "version|V" => \$opt::version, - "minversion|min-version=i" => \$opt::minversion, - "show-limits|showlimits" => \$opt::show_limits, - "exit|x" => \$opt::x, - # Semaphore - "semaphore" => \$opt::semaphore, - "semaphoretimeout=i" => \$opt::semaphoretimeout, - "semaphorename|id=s" => \$opt::semaphorename, - "fg" => \$opt::fg, - "bg" => \$opt::bg, - "wait" => \$opt::wait, - # Shebang #!/usr/bin/parallel --shebang - "shebang|hashbang" => \$opt::shebang, - "internal-pipe-means-argfiles" => \$opt::internal_pipe_means_argfiles, - "Y" => \$opt::retired, - "skip-first-line" => \$opt::skip_first_line, - "header=s" => \$opt::header, - "cat" => \$opt::cat, - "fifo" => \$opt::fifo, - "pipepart|pipe-part" => \$opt::pipepart, - "hgrp|hostgroup|hostgroups" => \$opt::hostgroups, - ); -} - -sub get_options_from_array { - # Run GetOptions on @array - # Input: - # $array_ref = ref to @ARGV to parse - # @keep_only = Keep only these options - # Uses: - # @ARGV - # Returns: - # true if parsing worked - # false if parsing failed - # @$array_ref is changed - my ($array_ref, @keep_only) = @_; - if(not @$array_ref) { - # Empty array: No need to look more at that - return 1; - } - # A bit of shuffling of @ARGV needed as GetOptionsFromArray is not - # supported everywhere - my @save_argv; - my $this_is_ARGV = (\@::ARGV == $array_ref); - if(not $this_is_ARGV) { - @save_argv = @::ARGV; - @::ARGV = @{$array_ref}; - } - # If @keep_only set: Ignore all values except @keep_only - my %options = options_hash(); - if(@keep_only) { - my (%keep,@dummy); - @keep{@keep_only} = @keep_only; - for my $k (grep { not $keep{$_} } keys %options) { - # Store the value of the option in @dummy - $options{$k} = \@dummy; - } - } - my $retval = GetOptions(%options); - if(not $this_is_ARGV) { - @{$array_ref} = @::ARGV; - @::ARGV = @save_argv; - } - return $retval; -} - -sub parse_options { - # Returns: N/A - # Defaults: - $Global::version = 20141122; - $Global::progname = 'parallel'; - $Global::infinity = 2**31; - $Global::debug = 0; - $Global::verbose = 0; - $Global::quoting = 0; - # Read only table with default --rpl values - %Global::replace = - ( - '{}' => '', - '{#}' => '1 $_=$job->seq()', - '{%}' => '1 $_=$job->slot()', - '{/}' => 's:.*/::', - '{//}' => '$Global::use{"File::Basename"} ||= eval "use File::Basename; 1;"; $_ = dirname($_);', - '{/.}' => 's:.*/::; s:\.[^/.]+$::;', - '{.}' => 's:\.[^/.]+$::', - ); - %Global::plus = - ( - # {} = {+/}/{/} - # = {.}.{+.} = {+/}/{/.}.{+.} - # = {..}.{+..} = {+/}/{/..}.{+..} - # = {...}.{+...} = {+/}/{/...}.{+...} - '{+/}' => 's:/[^/]*$::', - '{+.}' => 's:.*\.::', - '{+..}' => 's:.*\.([^.]*\.):$1:', - '{+...}' => 's:.*\.([^.]*\.[^.]*\.):$1:', - '{..}' => 's:\.[^/.]+$::; s:\.[^/.]+$::', - '{...}' => 's:\.[^/.]+$::; s:\.[^/.]+$::; s:\.[^/.]+$::', - '{/..}' => 's:.*/::; s:\.[^/.]+$::; s:\.[^/.]+$::', - '{/...}' => 's:.*/::; s:\.[^/.]+$::; s:\.[^/.]+$::; s:\.[^/.]+$::', - ); - # Modifiable copy of %Global::replace - %Global::rpl = %Global::replace; - $Global::parens = "{==}"; - $/="\n"; - $Global::ignore_empty = 0; - $Global::interactive = 0; - $Global::stderr_verbose = 0; - $Global::default_simultaneous_sshlogins = 9; - $Global::exitstatus = 0; - $Global::halt_on_error_exitstatus = 0; - $Global::arg_sep = ":::"; - $Global::arg_file_sep = "::::"; - $Global::trim = 'n'; - $Global::max_jobs_running = 0; - $Global::job_already_run = ''; - $ENV{'TMPDIR'} ||= "/tmp"; - - @ARGV=read_options(); - - if(@opt::v) { $Global::verbose = $#opt::v+1; } # Convert -v -v to v=2 - $Global::debug = $opt::D; - $Global::shell = $ENV{'PARALLEL_SHELL'} || parent_shell($$) || $ENV{'SHELL'} || "/bin/sh"; - if(defined $opt::X) { $Global::ContextReplace = 1; } - if(defined $opt::silent) { $Global::verbose = 0; } - if(defined $opt::0) { $/ = "\0"; } - if(defined $opt::d) { my $e="sprintf \"$opt::d\""; $/ = eval $e; } - if(defined $opt::p) { $Global::interactive = $opt::p; } - if(defined $opt::q) { $Global::quoting = 1; } - if(defined $opt::r) { $Global::ignore_empty = 1; } - if(defined $opt::verbose) { $Global::stderr_verbose = 1; } - # Deal with --rpl - sub rpl { - # Modify %Global::rpl - # Replace $old with $new - my ($old,$new) = @_; - if($old ne $new) { - $Global::rpl{$new} = $Global::rpl{$old}; - delete $Global::rpl{$old}; - } - } - if(defined $opt::parens) { $Global::parens = $opt::parens; } - my $parenslen = 0.5*length $Global::parens; - $Global::parensleft = substr($Global::parens,0,$parenslen); - $Global::parensright = substr($Global::parens,$parenslen); - if(defined $opt::plus) { %Global::rpl = (%Global::plus,%Global::rpl); } - if(defined $opt::I) { rpl('{}',$opt::I); } - if(defined $opt::U) { rpl('{.}',$opt::U); } - if(defined $opt::i and $opt::i) { rpl('{}',$opt::i); } - if(defined $opt::basenamereplace) { rpl('{/}',$opt::basenamereplace); } - if(defined $opt::dirnamereplace) { rpl('{//}',$opt::dirnamereplace); } - if(defined $opt::seqreplace) { rpl('{#}',$opt::seqreplace); } - if(defined $opt::slotreplace) { rpl('{%}',$opt::slotreplace); } - if(defined $opt::basenameextensionreplace) { - rpl('{/.}',$opt::basenameextensionreplace); - } - for(@opt::rpl) { - # Create $Global::rpl entries for --rpl options - # E.g: "{..} s:\.[^.]+$:;s:\.[^.]+$:;" - my ($shorthand,$long) = split/ /,$_,2; - $Global::rpl{$shorthand} = $long; - } - if(defined $opt::eof) { $Global::end_of_file_string = $opt::eof; } - if(defined $opt::max_args) { $Global::max_number_of_args = $opt::max_args; } - if(defined $opt::timeout) { $Global::timeoutq = TimeoutQueue->new($opt::timeout); } - if(defined $opt::tmpdir) { $ENV{'TMPDIR'} = $opt::tmpdir; } - if(defined $opt::help) { die_usage(); } - if(defined $opt::colsep) { $Global::trim = 'lr'; } - if(defined $opt::header) { $opt::colsep = defined $opt::colsep ? $opt::colsep : "\t"; } - if(defined $opt::trim) { $Global::trim = $opt::trim; } - if(defined $opt::arg_sep) { $Global::arg_sep = $opt::arg_sep; } - if(defined $opt::arg_file_sep) { $Global::arg_file_sep = $opt::arg_file_sep; } - if(defined $opt::number_of_cpus) { print SSHLogin::no_of_cpus(),"\n"; wait_and_exit(0); } - if(defined $opt::number_of_cores) { - print SSHLogin::no_of_cores(),"\n"; wait_and_exit(0); - } - if(defined $opt::max_line_length_allowed) { - print Limits::Command::real_max_length(),"\n"; wait_and_exit(0); - } - if(defined $opt::version) { version(); wait_and_exit(0); } - if(defined $opt::bibtex) { bibtex(); wait_and_exit(0); } - if(defined $opt::record_env) { record_env(); wait_and_exit(0); } - if(defined $opt::show_limits) { show_limits(); } - if(@opt::sshlogin) { @Global::sshlogin = @opt::sshlogin; } - if(@opt::sshloginfile) { read_sshloginfiles(@opt::sshloginfile); } - if(@opt::return) { push @Global::ret_files, @opt::return; } - if(not defined $opt::recstart and - not defined $opt::recend) { $opt::recend = "\n"; } - if(not defined $opt::blocksize) { $opt::blocksize = "1M"; } - $opt::blocksize = multiply_binary_prefix($opt::blocksize); - if(defined $opt::controlmaster) { $opt::noctrlc = 1; } - if(defined $opt::semaphore) { $Global::semaphore = 1; } - if(defined $opt::semaphoretimeout) { $Global::semaphore = 1; } - if(defined $opt::semaphorename) { $Global::semaphore = 1; } - if(defined $opt::fg) { $Global::semaphore = 1; } - if(defined $opt::bg) { $Global::semaphore = 1; } - if(defined $opt::wait) { $Global::semaphore = 1; } - if(defined $opt::halt_on_error and - $opt::halt_on_error=~/%/) { $opt::halt_on_error /= 100; } - if(defined $opt::timeout and $opt::timeout !~ /^\d+(\.\d+)?%?$/) { - ::error("--timeout must be seconds or percentage\n"); - wait_and_exit(255); - } - if(defined $opt::minversion) { - print $Global::version,"\n"; - if($Global::version < $opt::minversion) { - wait_and_exit(255); - } else { - wait_and_exit(0); - } - } - if(not defined $opt::delay) { - # Set --delay to --sshdelay if not set - $opt::delay = $opt::sshdelay; - } - if($opt::compress_program) { - $opt::compress = 1; - $opt::decompress_program ||= $opt::compress_program." -dc"; - } - if($opt::compress) { - my ($compress, $decompress) = find_compression_program(); - $opt::compress_program ||= $compress; - $opt::decompress_program ||= $decompress; - } - if(defined $opt::nonall) { - # Append a dummy empty argument - push @ARGV, $Global::arg_sep, ""; - } - if(defined $opt::tty) { - # Defaults for --tty: -j1 -u - # Can be overridden with -jXXX -g - if(not defined $opt::jobs) { - $opt::jobs = 1; - } - if(not defined $opt::group) { - $opt::ungroup = 0; - } - } - if(@opt::trc) { - push @Global::ret_files, @opt::trc; - $opt::transfer = 1; - $opt::cleanup = 1; - } - if(defined $opt::max_lines) { - if($opt::max_lines eq "-0") { - # -l -0 (swallowed -0) - $opt::max_lines = 1; - $opt::0 = 1; - $/ = "\0"; - } elsif ($opt::max_lines == 0) { - # If not given (or if 0 is given) => 1 - $opt::max_lines = 1; - } - $Global::max_lines = $opt::max_lines; - if(not $opt::pipe) { - # --pipe -L means length of record - not max_number_of_args - $Global::max_number_of_args ||= $Global::max_lines; - } - } - - # Read more than one arg at a time (-L, -N) - if(defined $opt::L) { - $Global::max_lines = $opt::L; - if(not $opt::pipe) { - # --pipe -L means length of record - not max_number_of_args - $Global::max_number_of_args ||= $Global::max_lines; - } - } - if(defined $opt::max_replace_args) { - $Global::max_number_of_args = $opt::max_replace_args; - $Global::ContextReplace = 1; - } - if((defined $opt::L or defined $opt::max_replace_args) - and - not ($opt::xargs or $opt::m)) { - $Global::ContextReplace = 1; - } - if(defined $opt::tag and not defined $opt::tagstring) { - $opt::tagstring = "\257<\257>"; # Default = {} - } - if(defined $opt::pipepart and - (defined $opt::L or defined $opt::max_lines - or defined $opt::max_replace_args)) { - ::error("--pipepart is incompatible with --max-replace-args, ", - "--max-lines, and -L.\n"); - wait_and_exit(255); - } - if(grep /^$Global::arg_sep$|^$Global::arg_file_sep$/o, @ARGV) { - # Deal with ::: and :::: - @ARGV=read_args_from_command_line(); - } - - # Semaphore defaults - # Must be done before computing number of processes and max_line_length - # because when running as a semaphore GNU Parallel does not read args - $Global::semaphore ||= ($0 =~ m:(^|/)sem$:); # called as 'sem' - if($Global::semaphore) { - # A semaphore does not take input from neither stdin nor file - @opt::a = ("/dev/null"); - push(@Global::unget_argv, [Arg->new("")]); - $Semaphore::timeout = $opt::semaphoretimeout || 0; - if(defined $opt::semaphorename) { - $Semaphore::name = $opt::semaphorename; - } else { - $Semaphore::name = `tty`; - chomp $Semaphore::name; - } - $Semaphore::fg = $opt::fg; - $Semaphore::wait = $opt::wait; - $Global::default_simultaneous_sshlogins = 1; - if(not defined $opt::jobs) { - $opt::jobs = 1; - } - if($Global::interactive and $opt::bg) { - ::error("Jobs running in the ". - "background cannot be interactive.\n"); - ::wait_and_exit(255); - } - } - if(defined $opt::eta) { - $opt::progress = $opt::eta; - } - if(defined $opt::bar) { - $opt::progress = $opt::bar; - } - if(defined $opt::retired) { - ::error("-g has been retired. Use --group.\n"); - ::error("-B has been retired. Use --bf.\n"); - ::error("-T has been retired. Use --tty.\n"); - ::error("-U has been retired. Use --er.\n"); - ::error("-W has been retired. Use --wd.\n"); - ::error("-Y has been retired. Use --shebang.\n"); - ::error("-H has been retired. Use --halt.\n"); - ::error("--tollef has been retired. Use -u -q --arg-sep -- and --load for -l.\n"); - ::wait_and_exit(255); - } - citation_notice(); - - parse_sshlogin(); - parse_env_var(); - - if(remote_hosts() and ($opt::X or $opt::m or $opt::xargs)) { - # As we do not know the max line length on the remote machine - # long commands generated by xargs may fail - # If opt_N is set, it is probably safe - ::warning("Using -X or -m with --sshlogin may fail.\n"); - } - - if(not defined $opt::jobs) { - $opt::jobs = "100%"; - } - open_joblog(); -} - -sub env_quote { - # Input: - # $v = value to quote - # Returns: - # $v = value quoted as environment variable - my $v = $_[0]; - $v =~ s/([\\])/\\$1/g; - $v =~ s/([\[\] \#\'\&\<\>\(\)\;\{\}\t\"\$\`\*\174\!\?\~])/\\$1/g; - $v =~ s/\n/"\n"/g; - return $v; -} - -sub record_env { - # Record current %ENV-keys in ~/.parallel/ignored_vars - # Returns: N/A - my $ignore_filename = $ENV{'HOME'} . "/.parallel/ignored_vars"; - if(open(my $vars_fh, ">", $ignore_filename)) { - print $vars_fh map { $_,"\n" } keys %ENV; - } else { - ::error("Cannot write to $ignore_filename\n"); - ::wait_and_exit(255); - } -} - -sub parse_env_var { - # Parse --env and set $Global::envvar, $Global::envwarn and $Global::envvarlen - # - # Bash functions must be parsed to export them remotely - # Pre-shellshock style bash function: - # myfunc=() {... - # Post-shellshock style bash function: - # BASH_FUNC_myfunc()=() {... - # - # Uses: - # $Global::envvar = eval string that will set variables in both bash and csh - # $Global::envwarn = If functions are used: Give warning in csh - # $Global::envvarlen = length of $Global::envvar - # @opt::env - # $Global::shell - # %ENV - # Returns: N/A - $Global::envvar = ""; - $Global::envwarn = ""; - my @vars = ('parallel_bash_environment'); - for my $varstring (@opt::env) { - # Split up --env VAR1,VAR2 - push @vars, split /,/, $varstring; - } - if(grep { /^_$/ } @vars) { - # --env _ - # Include all vars that are not in a clean environment - if(open(my $vars_fh, "<", $ENV{'HOME'} . "/.parallel/ignored_vars")) { - my @ignore = <$vars_fh>; - chomp @ignore; - my %ignore; - @ignore{@ignore} = @ignore; - close $vars_fh; - push @vars, grep { not defined $ignore{$_} } keys %ENV; - @vars = grep { not /^_$/ } @vars; - } else { - ::error("Run '$Global::progname --record-env' in a clean environment first.\n"); - ::wait_and_exit(255); - } - } - # Duplicate vars as BASH functions to include post-shellshock functions. - # So --env myfunc should also look for BASH_FUNC_myfunc() - @vars = map { $_, "BASH_FUNC_$_()" } @vars; - # Keep only defined variables - @vars = grep { defined($ENV{$_}) } @vars; - # Pre-shellshock style bash function: - # myfunc=() { echo myfunc - # } - # Post-shellshock style bash function: - # BASH_FUNC_myfunc()=() { echo myfunc - # } - my @bash_functions = grep { substr($ENV{$_},0,4) eq "() {" } @vars; - my @non_functions = grep { substr($ENV{$_},0,4) ne "() {" } @vars; - if(@bash_functions) { - # Functions are not supported for all shells - if($Global::shell !~ m:/(bash|rbash|zsh|rzsh|dash|ksh):) { - ::warning("Shell functions may not be supported in $Global::shell\n"); - } - } - - # Pre-shellschock names are without () - my @bash_pre_shellshock = grep { not /\(\)/ } @bash_functions; - # Post-shellschock names are with () - my @bash_post_shellshock = grep { /\(\)/ } @bash_functions; - - my @qcsh = (map { my $a=$_; "setenv $a " . env_quote($ENV{$a}) } - grep { not /^parallel_bash_environment$/ } @non_functions); - my @qbash = (map { my $a=$_; "export $a=" . env_quote($ENV{$a}) } - @non_functions, @bash_pre_shellshock); - - push @qbash, map { my $a=$_; "eval $a\"\$$a\"" } @bash_pre_shellshock; - push @qbash, map { /BASH_FUNC_(.*)\(\)/; "$1 $ENV{$_}" } @bash_post_shellshock; - - #ssh -tt -oLogLevel=quiet lo 'eval `echo PARALLEL_SEQ='$PARALLEL_SEQ'\;export PARALLEL_SEQ\; PARALLEL_PID='$PARALLEL_PID'\;export PARALLEL_PID` ;' tty\ \>/dev/null\ \&\&\ stty\ isig\ -onlcr\ -echo\;echo\ \$SHELL\ \|\ grep\ \"/t\\\{0,1\\\}csh\"\ \>\ /dev/null\ \&\&\ setenv\ BASH_FUNC_myfunc\ \\\(\\\)\\\ \\\{\\\ \\\ echo\\\ a\"' - #'\"\\\}\ \|\|\ myfunc\(\)\ \{\ \ echo\ a' - #'\}\ \;myfunc\ 1; - - # Check if any variables contain \n - if(my @v = map { s/BASH_FUNC_(.*)\(\)/$1/; $_ } grep { $ENV{$_}=~/\n/ } @vars) { - # \n is bad for csh and will cause it to fail. - $Global::envwarn = ::shell_quote_scalar(q{echo $SHELL | grep -E "/t?csh" > /dev/null && echo CSH/TCSH DO NOT SUPPORT newlines IN VARIABLES/FUNCTIONS. Unset }."@v".q{ && exec false;}."\n\n") . $Global::envwarn; - } - - if(not @qcsh) { push @qcsh, "true"; } - if(not @qbash) { push @qbash, "true"; } - # Create lines like: - # echo $SHELL | grep "/t\\{0,1\\}csh" >/dev/null && setenv V1 val1 && setenv V2 val2 || export V1=val1 && export V2=val2 ; echo "$V1$V2" - if(@vars) { - $Global::envvar .= - join"", - (q{echo $SHELL | grep "/t\\{0,1\\}csh" > /dev/null && } - . join(" && ", @qcsh) - . q{ || } - . join(" && ", @qbash) - .q{;}); - if($ENV{'parallel_bash_environment'}) { - $Global::envvar .= 'eval "$parallel_bash_environment";'."\n"; - } - } - $Global::envvarlen = length $Global::envvar; -} - -sub open_joblog { - # Open joblog as specified by --joblog - # Uses: - # $opt::resume - # $opt::resume_failed - # $opt::joblog - # $opt::results - # $Global::job_already_run - # %Global::fd - my $append = 0; - if(($opt::resume or $opt::resume_failed) - and - not ($opt::joblog or $opt::results)) { - ::error("--resume and --resume-failed require --joblog or --results.\n"); - ::wait_and_exit(255); - } - if($opt::joblog) { - if($opt::resume || $opt::resume_failed) { - if(open(my $joblog_fh, "<", $opt::joblog)) { - # Read the joblog - $append = <$joblog_fh>; # If there is a header: Open as append later - my $joblog_regexp; - if($opt::resume_failed) { - # Make a regexp that only matches commands with exit+signal=0 - # 4 host 1360490623.067 3.445 1023 1222 0 0 command - $joblog_regexp='^(\d+)(?:\t[^\t]+){5}\t0\t0\t'; - } else { - # Just match the job number - $joblog_regexp='^(\d+)'; - } - while(<$joblog_fh>) { - if(/$joblog_regexp/o) { - # This is 30% faster than set_job_already_run($1); - vec($Global::job_already_run,($1||0),1) = 1; - } elsif(not /\d+\s+[^\s]+\s+([0-9.]+\s+){6}/) { - ::error("Format of '$opt::joblog' is wrong: $_"); - ::wait_and_exit(255); - } - } - close $joblog_fh; - } - } - if($append) { - # Append to joblog - if(not open($Global::joblog, ">>", $opt::joblog)) { - ::error("Cannot append to --joblog $opt::joblog.\n"); - ::wait_and_exit(255); - } - } else { - if($opt::joblog eq "-") { - # Use STDOUT as joblog - $Global::joblog = $Global::fd{1}; - } elsif(not open($Global::joblog, ">", $opt::joblog)) { - # Overwrite the joblog - ::error("Cannot write to --joblog $opt::joblog.\n"); - ::wait_and_exit(255); - } - print $Global::joblog - join("\t", "Seq", "Host", "Starttime", "JobRuntime", - "Send", "Receive", "Exitval", "Signal", "Command" - ). "\n"; - } - } -} - -sub find_compression_program { - # Find a fast compression program - # Returns: - # $compress_program = compress program with options - # $decompress_program = decompress program with options - - # Search for these. Sorted by speed - my @prg = qw(lzop pigz pxz gzip plzip pbzip2 lzma xz lzip bzip2); - for my $p (@prg) { - if(which($p)) { - return ("$p -c -1","$p -dc"); - } - } - # Fall back to cat - return ("cat","cat"); -} - - -sub read_options { - # Read options from command line, profile and $PARALLEL - # Uses: - # $opt::shebang_wrap - # $opt::shebang - # @ARGV - # $opt::plain - # @opt::profile - # $ENV{'HOME'} - # $ENV{'PARALLEL'} - # Returns: - # @ARGV_no_opt = @ARGV without --options - - # This must be done first as this may exec myself - if(defined $ARGV[0] and ($ARGV[0] =~ /^--shebang/ or - $ARGV[0] =~ /^--shebang-?wrap/ or - $ARGV[0] =~ /^--hashbang/)) { - # Program is called from #! line in script - # remove --shebang-wrap if it is set - $opt::shebang_wrap = ($ARGV[0] =~ s/^--shebang-?wrap *//); - # remove --shebang if it is set - $opt::shebang = ($ARGV[0] =~ s/^--shebang *//); - # remove --hashbang if it is set - $opt::shebang .= ($ARGV[0] =~ s/^--hashbang *//); - if($opt::shebang) { - my $argfile = shell_quote_scalar(pop @ARGV); - # exec myself to split $ARGV[0] into separate fields - exec "$0 --skip-first-line -a $argfile @ARGV"; - } - if($opt::shebang_wrap) { - my @options; - my @parser; - if ($^O eq 'freebsd') { - # FreeBSD's #! puts different values in @ARGV than Linux' does. - my @nooptions = @ARGV; - get_options_from_array(\@nooptions); - while($#ARGV > $#nooptions) { - push @options, shift @ARGV; - } - while(@ARGV and $ARGV[0] ne ":::") { - push @parser, shift @ARGV; - } - if(@ARGV and $ARGV[0] eq ":::") { - shift @ARGV; - } - } else { - @options = shift @ARGV; - } - my $script = shell_quote_scalar(shift @ARGV); - # exec myself to split $ARGV[0] into separate fields - exec "$0 --internal-pipe-means-argfiles @options @parser $script ::: @ARGV"; - } - } - - Getopt::Long::Configure("bundling","require_order"); - my @ARGV_copy = @ARGV; - # Check if there is a --profile to set @opt::profile - get_options_from_array(\@ARGV_copy,"profile|J=s","plain") || die_usage(); - my @ARGV_profile = (); - my @ARGV_env = (); - if(not $opt::plain) { - # Add options from .parallel/config and other profiles - my @config_profiles = ( - "/etc/parallel/config", - $ENV{'HOME'}."/.parallel/config", - $ENV{'HOME'}."/.parallelrc"); - my @profiles = @config_profiles; - if(@opt::profile) { - # --profile overrides default profiles - @profiles = (); - for my $profile (@opt::profile) { - if(-r $profile) { - push @profiles, $profile; - } else { - push @profiles, $ENV{'HOME'}."/.parallel/".$profile; - } - } - } - for my $profile (@profiles) { - if(-r $profile) { - open (my $in_fh, "<", $profile) || ::die_bug("read-profile: $profile"); - while(<$in_fh>) { - /^\s*\#/ and next; - chomp; - push @ARGV_profile, shellwords($_); - } - close $in_fh; - } else { - if(grep /^$profile$/, @config_profiles) { - # config file is not required to exist - } else { - ::error("$profile not readable.\n"); - wait_and_exit(255); - } - } - } - # Add options from shell variable $PARALLEL - if($ENV{'PARALLEL'}) { - @ARGV_env = shellwords($ENV{'PARALLEL'}); - } - } - Getopt::Long::Configure("bundling","require_order"); - get_options_from_array(\@ARGV_profile) || die_usage(); - get_options_from_array(\@ARGV_env) || die_usage(); - get_options_from_array(\@ARGV) || die_usage(); - - # Prepend non-options to @ARGV (such as commands like 'nice') - unshift @ARGV, @ARGV_profile, @ARGV_env; - return @ARGV; -} - -sub read_args_from_command_line { - # Arguments given on the command line after: - # ::: ($Global::arg_sep) - # :::: ($Global::arg_file_sep) - # Removes the arguments from @ARGV and: - # - puts filenames into -a - # - puts arguments into files and add the files to -a - # Input: - # @::ARGV = command option ::: arg arg arg :::: argfiles - # Uses: - # $Global::arg_sep - # $Global::arg_file_sep - # $opt::internal_pipe_means_argfiles - # $opt::pipe - # @opt::a - # Returns: - # @argv_no_argsep = @::ARGV without ::: and :::: and following args - my @new_argv = (); - for(my $arg = shift @ARGV; @ARGV; $arg = shift @ARGV) { - if($arg eq $Global::arg_sep - or - $arg eq $Global::arg_file_sep) { - my $group = $arg; # This group of arguments is args or argfiles - my @group; - while(defined ($arg = shift @ARGV)) { - if($arg eq $Global::arg_sep - or - $arg eq $Global::arg_file_sep) { - # exit while loop if finding new separator - last; - } else { - # If not hitting ::: or :::: - # Append it to the group - push @group, $arg; - } - } - - if($group eq $Global::arg_file_sep - or ($opt::internal_pipe_means_argfiles and $opt::pipe) - ) { - # Group of file names on the command line. - # Append args into -a - push @opt::a, @group; - } elsif($group eq $Global::arg_sep) { - # Group of arguments on the command line. - # Put them into a file. - # Create argfile - my ($outfh,$name) = ::tmpfile(SUFFIX => ".arg"); - unlink($name); - # Put args into argfile - print $outfh map { $_,$/ } @group; - seek $outfh, 0, 0; - # Append filehandle to -a - push @opt::a, $outfh; - } else { - ::die_bug("Unknown command line group: $group"); - } - if(defined($arg)) { - # $arg is ::: or :::: - redo; - } else { - # $arg is undef -> @ARGV empty - last; - } - } - push @new_argv, $arg; - } - # Output: @ARGV = command to run with options - return @new_argv; -} - -sub cleanup { - # Returns: N/A - if(@opt::basefile) { cleanup_basefile(); } -} - -sub __QUOTING_ARGUMENTS_FOR_SHELL__ {} - -sub shell_quote { - # Input: - # @strings = strings to be quoted - # Output: - # @shell_quoted_strings = string quoted with \ as needed by the shell - my @strings = (@_); - for my $a (@strings) { - $a =~ s/([\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\*\>\<\~\|\; \"\!\$\&\'\202-\377])/\\$1/g; - $a =~ s/[\n]/'\n'/g; # filenames with '\n' is quoted using \' - } - return wantarray ? @strings : "@strings"; -} - -sub shell_quote_empty { - # Inputs: - # @strings = strings to be quoted - # Returns: - # @quoted_strings = empty strings quoted as ''. - my @strings = shell_quote(@_); - for my $a (@strings) { - if($a eq "") { - $a = "''"; - } - } - return wantarray ? @strings : "@strings"; -} - -sub shell_quote_scalar { - # Quote the string so shell will not expand any special chars - # Inputs: - # $string = string to be quoted - # Returns: - # $shell_quoted = string quoted with \ as needed by the shell - my $a = $_[0]; - if(defined $a) { - # $a =~ s/([\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\*\>\<\~\|\; \"\!\$\&\'\202-\377])/\\$1/g; - # This is 1% faster than the above - $a =~ s/[\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\*\>\<\~\|\; \"\!\$\&\'\202-\377]/\\$&/go; - $a =~ s/[\n]/'\n'/go; # filenames with '\n' is quoted using \' - } - return $a; -} - -sub shell_quote_file { - # Quote the string so shell will not expand any special chars and prepend ./ if needed - # Input: - # $filename = filename to be shell quoted - # Returns: - # $quoted_filename = filename quoted with \ as needed by the shell and ./ if needed - my $a = shell_quote_scalar(shift); - if(defined $a) { - if($a =~ m:^/: or $a =~ m:^\./:) { - # /abs/path or ./rel/path => skip - } else { - # rel/path => ./rel/path - $a = "./".$a; - } - } - return $a; -} - -sub shellwords { - # Input: - # $string = shell line - # Returns: - # @shell_words = $string split into words as shell would do - $Global::use{"Text::ParseWords"} ||= eval "use Text::ParseWords; 1;"; - return Text::ParseWords::shellwords(@_); -} - - -sub __FILEHANDLES__ {} - - -sub save_stdin_stdout_stderr { - # Remember the original STDIN, STDOUT and STDERR - # and file descriptors opened by the shell (e.g. 3>/tmp/foo) - # Uses: - # %Global::fd - # $Global::original_stderr - # $Global::original_stdin - # Returns: N/A - - # Find file descriptors that are already opened (by the shell) - for my $fdno (1..61) { - # /dev/fd/62 and above are used by bash for <(cmd) - my $fh; - # 2-argument-open is used to be compatible with old perl 5.8.0 - # bug #43570: Perl 5.8.0 creates 61 files - if(open($fh,">&=$fdno")) { - $Global::fd{$fdno}=$fh; - } - } - open $Global::original_stderr, ">&", "STDERR" or - ::die_bug("Can't dup STDERR: $!"); - open $Global::original_stdin, "<&", "STDIN" or - ::die_bug("Can't dup STDIN: $!"); - $Global::is_terminal = (-t $Global::original_stderr) && !$ENV{'CIRCLECI'} && !$ENV{'TRAVIS'}; -} - -sub enough_file_handles { - # Check that we have enough filehandles available for starting - # another job - # Uses: - # $opt::ungroup - # %Global::fd - # Returns: - # 1 if ungrouped (thus not needing extra filehandles) - # 0 if too few filehandles - # 1 if enough filehandles - if(not $opt::ungroup) { - my %fh; - my $enough_filehandles = 1; - # perl uses 7 filehandles for something? - # open3 uses 2 extra filehandles temporarily - # We need a filehandle for each redirected file descriptor - # (normally just STDOUT and STDERR) - for my $i (1..(7+2+keys %Global::fd)) { - $enough_filehandles &&= open($fh{$i}, "<", "/dev/null"); - } - for (values %fh) { close $_; } - return $enough_filehandles; - } else { - # Ungrouped does not need extra file handles - return 1; - } -} - -sub open_or_exit { - # Open a file name or exit if the file cannot be opened - # Inputs: - # $file = filehandle or filename to open - # Uses: - # $Global::stdin_in_opt_a - # $Global::original_stdin - # Returns: - # $fh = file handle to read-opened file - my $file = shift; - if($file eq "-") { - $Global::stdin_in_opt_a = 1; - return ($Global::original_stdin || *STDIN); - } - if(ref $file eq "GLOB") { - # This is an open filehandle - return $file; - } - my $fh = gensym; - if(not open($fh, "<", $file)) { - ::error("Cannot open input file `$file': No such file or directory.\n"); - wait_and_exit(255); - } - return $fh; -} - -sub __RUNNING_THE_JOBS_AND_PRINTING_PROGRESS__ {} - -# Variable structure: -# -# $Global::running{$pid} = Pointer to Job-object -# @Global::virgin_jobs = Pointer to Job-object that have received no input -# $Global::host{$sshlogin} = Pointer to SSHLogin-object -# $Global::total_running = total number of running jobs -# $Global::total_started = total jobs started - -sub init_run_jobs { - $Global::total_running = 0; - $Global::total_started = 0; - $Global::tty_taken = 0; - $SIG{USR1} = \&list_running_jobs; - $SIG{USR2} = \&toggle_progress; - if(@opt::basefile) { setup_basefile(); } -} - -{ - my $last_time; - my %last_mtime; - -sub start_more_jobs { - # Run start_another_job() but only if: - # * not $Global::start_no_new_jobs set - # * not JobQueue is empty - # * not load on server is too high - # * not server swapping - # * not too short time since last remote login - # Uses: - # $Global::max_procs_file - # $Global::max_procs_file_last_mod - # %Global::host - # @opt::sshloginfile - # $Global::start_no_new_jobs - # $opt::filter_hosts - # $Global::JobQueue - # $opt::pipe - # $opt::load - # $opt::noswap - # $opt::delay - # $Global::newest_starttime - # Returns: - # $jobs_started = number of jobs started - my $jobs_started = 0; - my $jobs_started_this_round = 0; - if($Global::start_no_new_jobs) { - return $jobs_started; - } - if(time - ($last_time||0) > 1) { - # At most do this every second - $last_time = time; - if($Global::max_procs_file) { - # --jobs filename - my $mtime = (stat($Global::max_procs_file))[9]; - if($mtime > $Global::max_procs_file_last_mod) { - # file changed: Force re-computing max_jobs_running - $Global::max_procs_file_last_mod = $mtime; - for my $sshlogin (values %Global::host) { - $sshlogin->set_max_jobs_running(undef); - } - } - } - if(@opt::sshloginfile) { - # Is --sshloginfile changed? - for my $slf (@opt::sshloginfile) { - my $actual_file = expand_slf_shorthand($slf); - my $mtime = (stat($actual_file))[9]; - $last_mtime{$actual_file} ||= $mtime; - if($mtime - $last_mtime{$actual_file} > 1) { - ::debug("run","--sshloginfile $actual_file changed. reload\n"); - $last_mtime{$actual_file} = $mtime; - # Reload $slf - # Empty sshlogins - @Global::sshlogin = (); - for (values %Global::host) { - # Don't start new jobs on any host - # except the ones added back later - $_->set_max_jobs_running(0); - } - # This will set max_jobs_running on the SSHlogins - read_sshloginfile($actual_file); - parse_sshlogin(); - $opt::filter_hosts and filter_hosts(); - setup_basefile(); - } - } - } - } - do { - $jobs_started_this_round = 0; - # This will start 1 job on each --sshlogin (if possible) - # thus distribute the jobs on the --sshlogins round robin - - for my $sshlogin (values %Global::host) { - if($Global::JobQueue->empty() and not $opt::pipe) { - # No more jobs in the queue - last; - } - debug("run", "Running jobs before on ", $sshlogin->string(), ": ", - $sshlogin->jobs_running(), "\n"); - if ($sshlogin->jobs_running() < $sshlogin->max_jobs_running()) { - if($opt::load and $sshlogin->loadavg_too_high()) { - # The load is too high or unknown - next; - } - if($opt::noswap and $sshlogin->swapping()) { - # The server is swapping - next; - } - if($sshlogin->too_fast_remote_login()) { - # It has been too short since - next; - } - if($opt::delay and $opt::delay > ::now() - $Global::newest_starttime) { - # It has been too short since last start - next; - } - debug("run", $sshlogin->string(), " has ", $sshlogin->jobs_running(), - " out of ", $sshlogin->max_jobs_running(), - " jobs running. Start another.\n"); - if(start_another_job($sshlogin) == 0) { - # No more jobs to start on this $sshlogin - debug("run","No jobs started on ", $sshlogin->string(), "\n"); - next; - } - $sshlogin->inc_jobs_running(); - $sshlogin->set_last_login_at(::now()); - $jobs_started++; - $jobs_started_this_round++; - } - debug("run","Running jobs after on ", $sshlogin->string(), ": ", - $sshlogin->jobs_running(), " of ", - $sshlogin->max_jobs_running(), "\n"); - } - } while($jobs_started_this_round); - - return $jobs_started; -} -} - -{ - my $no_more_file_handles_warned; - -sub start_another_job { - # If there are enough filehandles - # and JobQueue not empty - # and not $job is in joblog - # Then grab a job from Global::JobQueue, - # start it at sshlogin - # mark it as virgin_job - # Inputs: - # $sshlogin = the SSHLogin to start the job on - # Uses: - # $Global::JobQueue - # $opt::pipe - # $opt::results - # $opt::resume - # @Global::virgin_jobs - # Returns: - # 1 if another jobs was started - # 0 otherwise - my $sshlogin = shift; - # Do we have enough file handles to start another job? - if(enough_file_handles()) { - if($Global::JobQueue->empty() and not $opt::pipe) { - # No more commands to run - debug("start", "Not starting: JobQueue empty\n"); - return 0; - } else { - my $job; - # Skip jobs already in job log - # Skip jobs already in results - do { - $job = get_job_with_sshlogin($sshlogin); - if(not defined $job) { - # No command available for that sshlogin - debug("start", "Not starting: no jobs available for ", - $sshlogin->string(), "\n"); - return 0; - } - } while ($job->is_already_in_joblog() - or - ($opt::results and $opt::resume and $job->is_already_in_results())); - debug("start", "Command to run on '", $job->sshlogin()->string(), "': '", - $job->replaced(),"'\n"); - if($job->start()) { - if($opt::pipe) { - push(@Global::virgin_jobs,$job); - } - debug("start", "Started as seq ", $job->seq(), - " pid:", $job->pid(), "\n"); - return 1; - } else { - # Not enough processes to run the job. - # Put it back on the queue. - $Global::JobQueue->unget($job); - # Count down the number of jobs to run for this SSHLogin. - my $max = $sshlogin->max_jobs_running(); - if($max > 1) { $max--; } else { - ::error("No more processes: cannot run a single job. Something is wrong.\n"); - ::wait_and_exit(255); - } - $sshlogin->set_max_jobs_running($max); - # Sleep up to 300 ms to give other processes time to die - ::usleep(rand()*300); - ::warning("No more processes: ", - "Decreasing number of running jobs to $max. ", - "Raising ulimit -u or /etc/security/limits.conf may help.\n"); - return 0; - } - } - } else { - # No more file handles - $no_more_file_handles_warned++ or - ::warning("No more file handles. ", - "Raising ulimit -n or /etc/security/limits.conf may help.\n"); - return 0; - } -} -} - -$opt::min_progress_interval = 0; - -sub init_progress { - # Uses: - # $opt::bar - # Returns: - # list of computers for progress output - $|=1; - if (not $Global::is_terminal) { - $opt::min_progress_interval = 30; - } - if($opt::bar) { - return("",""); - } - my %progress = progress(); - return ("\nComputers / CPU cores / Max jobs to run\n", - $progress{'workerlist'}); -} - -sub drain_job_queue { - # Uses: - # $opt::progress - # $Global::original_stderr - # $Global::total_running - # $Global::max_jobs_running - # %Global::running - # $Global::JobQueue - # %Global::host - # $Global::start_no_new_jobs - # Returns: N/A - if($opt::progress) { - print $Global::original_stderr init_progress(); - } - my $last_header=""; - my $sleep = 0.2; - my $last_left = 1000000000; - my $last_progress_time = 0; - my $ps_reported = 0; - do { - while($Global::total_running > 0) { - debug($Global::total_running, "==", scalar - keys %Global::running," slots: ", $Global::max_jobs_running); - if($opt::pipe) { - # When using --pipe sometimes file handles are not closed properly - for my $job (values %Global::running) { - close $job->fh(0,"w"); - } - } - # When not connected to terminal, assume CI (e.g. CircleCI). In - # that case we want occasional progress output to prevent abort - # due to timeout with no output, but we also need to stop sending - # progress output if there has been no actual progress, so that - # the job can time out appropriately (CirecleCI: 10m) in case of - # a hung test. But without special output, it is extremely - # annoying to diagnose which test is hung, so we add that using - # `ps` below. - if($opt::progress and - ($Global::is_terminal or (time() - $last_progress_time) >= 30)) { - my %progress = progress(); - if($last_header ne $progress{'header'}) { - print $Global::original_stderr "\n", $progress{'header'}, "\n"; - $last_header = $progress{'header'}; - } - if ($Global::is_terminal) { - print $Global::original_stderr "\r",$progress{'status'}; - } - if ($last_left > $Global::left) { - if (not $Global::is_terminal) { - print $Global::original_stderr $progress{'status'},"\n"; - } - $last_progress_time = time(); - $ps_reported = 0; - } elsif (not $ps_reported and (time() - $last_progress_time) >= 60) { - # No progress in at least 60 seconds: run ps - print $Global::original_stderr "\n"; - my $script_dir = ::dirname($0); - system("$script_dir/ps_with_stack || ps -wwf"); - $ps_reported = 1; - } - $last_left = $Global::left; - flush $Global::original_stderr; - } - if($Global::total_running < $Global::max_jobs_running - and not $Global::JobQueue->empty()) { - # These jobs may not be started because of loadavg - # or too little time between each ssh login. - if(start_more_jobs() > 0) { - # Exponential back-on if jobs were started - $sleep = $sleep/2+0.001; - } - } - # Sometimes SIGCHLD is not registered, so force reaper - $sleep = ::reap_usleep($sleep); - } - if(not $Global::JobQueue->empty()) { - # These jobs may not be started: - # * because there the --filter-hosts has removed all - if(not %Global::host) { - ::error("There are no hosts left to run on.\n"); - ::wait_and_exit(255); - } - # * because of loadavg - # * because of too little time between each ssh login. - start_more_jobs(); - $sleep = ::reap_usleep($sleep); - if($Global::max_jobs_running == 0) { - ::warning("There are no job slots available. Increase --jobs.\n"); - } - } - } while ($Global::total_running > 0 - or - not $Global::start_no_new_jobs and not $Global::JobQueue->empty()); - if($opt::progress) { - my %progress = progress(); - print $Global::original_stderr $opt::progress_sep, $progress{'status'}, "\n"; - flush $Global::original_stderr; - } -} - -sub toggle_progress { - # Turn on/off progress view - # Uses: - # $opt::progress - # $Global::original_stderr - # Returns: N/A - $opt::progress = not $opt::progress; - if($opt::progress) { - print $Global::original_stderr init_progress(); - } -} - -sub progress { - # Uses: - # $opt::bar - # $opt::eta - # %Global::host - # $Global::total_started - # Returns: - # $workerlist = list of workers - # $header = that will fit on the screen - # $status = message that will fit on the screen - if($opt::bar) { - return ("workerlist" => "", "header" => "", "status" => bar()); - } - my $eta = ""; - my ($status,$header)=("",""); - if($opt::eta) { - my($total, $completed, $left, $pctcomplete, $avgtime, $this_eta) = - compute_eta(); - $eta = sprintf("ETA: %ds Left: %d AVG: %.2fs ", - $this_eta, $left, $avgtime); - $Global::left = $left; - } - my $termcols = terminal_columns(); - my @workers = sort keys %Global::host; - my %sshlogin = map { $_ eq ":" ? ($_=>"local") : ($_=>$_) } @workers; - my $workerno = 1; - my %workerno = map { ($_=>$workerno++) } @workers; - my $workerlist = ""; - for my $w (@workers) { - $workerlist .= - $workerno{$w}.":".$sshlogin{$w} ." / ". - ($Global::host{$w}->ncpus() || "-")." / ". - $Global::host{$w}->max_jobs_running()."\n"; - } - $status = "x"x($termcols+1); - if(length $status > $termcols) { - # sshlogin1:XX/XX/XX%/XX.Xs sshlogin2:XX/XX/XX%/XX.Xs sshlogin3:XX/XX/XX%/XX.Xs - $header = "Computer:jobs running/jobs completed/%of started jobs/Average seconds to complete"; - $status = $eta . - join(" ",map - { - if($Global::total_started) { - my $completed = ($Global::host{$_}->jobs_completed()||0); - my $running = $Global::host{$_}->jobs_running(); - my $time = $completed ? (time-$^T)/($completed) : "0"; - sprintf("%s:%d/%d/%d%%/%.1fs ", - $sshlogin{$_}, $running, $completed, - ($running+$completed)*100 - / $Global::total_started, $time); - } - } @workers); - } - if(length $status > $termcols) { - # 1:XX/XX/XX%/XX.Xs 2:XX/XX/XX%/XX.Xs 3:XX/XX/XX%/XX.Xs 4:XX/XX/XX%/XX.Xs - $header = "Computer:jobs running/jobs completed/%of started jobs"; - $status = $eta . - join(" ",map - { - my $completed = ($Global::host{$_}->jobs_completed()||0); - my $running = $Global::host{$_}->jobs_running(); - my $time = $completed ? (time-$^T)/($completed) : "0"; - sprintf("%s:%d/%d/%d%%/%.1fs ", - $workerno{$_}, $running, $completed, - ($running+$completed)*100 - / $Global::total_started, $time); - } @workers); - } - if(length $status > $termcols) { - # sshlogin1:XX/XX/XX% sshlogin2:XX/XX/XX% sshlogin3:XX/XX/XX% - $header = "Computer:jobs running/jobs completed/%of started jobs"; - $status = $eta . - join(" ",map - { sprintf("%s:%d/%d/%d%%", - $sshlogin{$_}, - $Global::host{$_}->jobs_running(), - ($Global::host{$_}->jobs_completed()||0), - ($Global::host{$_}->jobs_running()+ - ($Global::host{$_}->jobs_completed()||0))*100 - / $Global::total_started) } - @workers); - } - if(length $status > $termcols) { - # 1:XX/XX/XX% 2:XX/XX/XX% 3:XX/XX/XX% 4:XX/XX/XX% 5:XX/XX/XX% 6:XX/XX/XX% - $header = "Computer:jobs running/jobs completed/%of started jobs"; - $status = $eta . - join(" ",map - { sprintf("%s:%d/%d/%d%%", - $workerno{$_}, - $Global::host{$_}->jobs_running(), - ($Global::host{$_}->jobs_completed()||0), - ($Global::host{$_}->jobs_running()+ - ($Global::host{$_}->jobs_completed()||0))*100 - / $Global::total_started) } - @workers); - } - if(length $status > $termcols) { - # sshlogin1:XX/XX/XX% sshlogin2:XX/XX/XX% sshlogin3:XX/XX sshlogin4:XX/XX - $header = "Computer:jobs running/jobs completed"; - $status = $eta . - join(" ",map - { sprintf("%s:%d/%d", - $sshlogin{$_}, $Global::host{$_}->jobs_running(), - ($Global::host{$_}->jobs_completed()||0)) } - @workers); - } - if(length $status > $termcols) { - # sshlogin1:XX/XX sshlogin2:XX/XX sshlogin3:XX/XX sshlogin4:XX/XX - $header = "Computer:jobs running/jobs completed"; - $status = $eta . - join(" ",map - { sprintf("%s:%d/%d", - $sshlogin{$_}, $Global::host{$_}->jobs_running(), - ($Global::host{$_}->jobs_completed()||0)) } - @workers); - } - if(length $status > $termcols) { - # 1:XX/XX 2:XX/XX 3:XX/XX 4:XX/XX 5:XX/XX 6:XX/XX - $header = "Computer:jobs running/jobs completed"; - $status = $eta . - join(" ",map - { sprintf("%s:%d/%d", - $workerno{$_}, $Global::host{$_}->jobs_running(), - ($Global::host{$_}->jobs_completed()||0)) } - @workers); - } - if(length $status > $termcols) { - # sshlogin1:XX sshlogin2:XX sshlogin3:XX sshlogin4:XX sshlogin5:XX - $header = "Computer:jobs completed"; - $status = $eta . - join(" ",map - { sprintf("%s:%d", - $sshlogin{$_}, - ($Global::host{$_}->jobs_completed()||0)) } - @workers); - } - if(length $status > $termcols) { - # 1:XX 2:XX 3:XX 4:XX 5:XX 6:XX - $header = "Computer:jobs completed"; - $status = $eta . - join(" ",map - { sprintf("%s:%d", - $workerno{$_}, - ($Global::host{$_}->jobs_completed()||0)) } - @workers); - } - return ("workerlist" => $workerlist, "header" => $header, "status" => $status); -} - -{ - my ($total, $first_completed, $smoothed_avg_time); - - sub compute_eta { - # Calculate important numbers for ETA - # Returns: - # $total = number of jobs in total - # $completed = number of jobs completed - # $left = number of jobs left - # $pctcomplete = percent of jobs completed - # $avgtime = averaged time - # $eta = smoothed eta - $total ||= $Global::JobQueue->total_jobs(); - my $completed = 0; - for(values %Global::host) { $completed += $_->jobs_completed() } - my $left = $total - $completed; - if(not $completed) { - return($total, $completed, $left, 0, 0, 0); - } - my $pctcomplete = $completed / $total; - $first_completed ||= time; - my $timepassed = (time - $first_completed); - my $avgtime = $timepassed / $completed; - $smoothed_avg_time ||= $avgtime; - # Smooth the eta so it does not jump wildly - $smoothed_avg_time = (1 - $pctcomplete) * $smoothed_avg_time + - $pctcomplete * $avgtime; - my $eta = int($left * $smoothed_avg_time); - return($total, $completed, $left, $pctcomplete, $avgtime, $eta); - } -} - -{ - my ($rev,$reset); - - sub bar { - # Return: - # $status = bar with eta, completed jobs, arg and pct - $rev ||= "\033[7m"; - $reset ||= "\033[0m"; - my($total, $completed, $left, $pctcomplete, $avgtime, $eta) = - compute_eta(); - my $arg = $Global::newest_job ? - $Global::newest_job->{'commandline'}->replace_placeholders(["\257<\257>"],0,0) : ""; - # These chars mess up display in the terminal - $arg =~ tr/[\011-\016\033\302-\365]//d; - my $bar_text = - sprintf("%d%% %d:%d=%ds %s", - $pctcomplete*100, $completed, $left, $eta, $arg); - my $terminal_width = terminal_columns(); - my $s = sprintf("%-${terminal_width}s", - substr($bar_text." "x$terminal_width, - 0,$terminal_width)); - my $width = int($terminal_width * $pctcomplete); - substr($s,$width,0) = $reset; - my $zenity = sprintf("%-${terminal_width}s", - substr("# $eta sec $arg", - 0,$terminal_width)); - $s = "\r" . $zenity . "\r" . $pctcomplete*100 . # Prefix with zenity header - "\r" . $rev . $s . $reset; - return $s; - } -} - -{ - my ($columns,$last_column_time); - - sub terminal_columns { - # Get the number of columns of the display - # Returns: - # number of columns of the screen - if(not $columns or $last_column_time < time) { - $last_column_time = time; - $columns = $ENV{'COLUMNS'}; - if(not $columns) { - my $resize = qx{ resize 2>/dev/null }; - $resize =~ /COLUMNS=(\d+);/ and do { $columns = $1; }; - } - $columns ||= 80; - } - return $columns; - } -} - -sub get_job_with_sshlogin { - # Returns: - # next job object for $sshlogin if any available - my $sshlogin = shift; - my $job = undef; - - if ($opt::hostgroups) { - my @other_hostgroup_jobs = (); - - while($job = $Global::JobQueue->get()) { - if($sshlogin->in_hostgroups($job->hostgroups())) { - # Found a job for this hostgroup - last; - } else { - # This job was not in the hostgroups of $sshlogin - push @other_hostgroup_jobs, $job; - } - } - $Global::JobQueue->unget(@other_hostgroup_jobs); - if(not defined $job) { - # No more jobs - return undef; - } - } else { - $job = $Global::JobQueue->get(); - if(not defined $job) { - # No more jobs - ::debug("start", "No more jobs: JobQueue empty\n"); - return undef; - } - } - - my $clean_command = $job->replaced(); - if($clean_command =~ /^\s*$/) { - # Do not run empty lines - if(not $Global::JobQueue->empty()) { - return get_job_with_sshlogin($sshlogin); - } else { - return undef; - } - } - $job->set_sshlogin($sshlogin); - if($opt::retries and $clean_command and - $job->failed_here()) { - # This command with these args failed for this sshlogin - my ($no_of_failed_sshlogins,$min_failures) = $job->min_failed(); - # Only look at the Global::host that have > 0 jobslots - if($no_of_failed_sshlogins == grep { $_->max_jobs_running() > 0 } values %Global::host - and $job->failed_here() == $min_failures) { - # It failed the same or more times on another host: - # run it on this host - } else { - # If it failed fewer times on another host: - # Find another job to run - my $nextjob; - if(not $Global::JobQueue->empty()) { - # This can potentially recurse for all args - no warnings 'recursion'; - $nextjob = get_job_with_sshlogin($sshlogin); - } - # Push the command back on the queue - $Global::JobQueue->unget($job); - return $nextjob; - } - } - return $job; -} - -sub __REMOTE_SSH__ {} - -sub read_sshloginfiles { - # Returns: N/A - for my $s (@_) { - read_sshloginfile(expand_slf_shorthand($s)); - } -} - -sub expand_slf_shorthand { - my $file = shift; - if($file eq "-") { - # skip: It is stdin - } elsif($file eq "..") { - $file = $ENV{'HOME'}."/.parallel/sshloginfile"; - } elsif($file eq ".") { - $file = "/etc/parallel/sshloginfile"; - } elsif(not -r $file) { - if(not -r $ENV{'HOME'}."/.parallel/".$file) { - # Try prepending ~/.parallel - ::error("Cannot open $file.\n"); - ::wait_and_exit(255); - } else { - $file = $ENV{'HOME'}."/.parallel/".$file; - } - } - return $file; -} - -sub read_sshloginfile { - # Returns: N/A - my $file = shift; - my $close = 1; - my $in_fh; - ::debug("init","--slf ",$file); - if($file eq "-") { - $in_fh = *STDIN; - $close = 0; - } else { - if(not open($in_fh, "<", $file)) { - # Try the filename - ::error("Cannot open $file.\n"); - ::wait_and_exit(255); - } - } - while(<$in_fh>) { - chomp; - /^\s*#/ and next; - /^\s*$/ and next; - push @Global::sshlogin, $_; - } - if($close) { - close $in_fh; - } -} - -sub parse_sshlogin { - # Returns: N/A - my @login; - if(not @Global::sshlogin) { @Global::sshlogin = (":"); } - for my $sshlogin (@Global::sshlogin) { - # Split up -S sshlogin,sshlogin - for my $s (split /,/, $sshlogin) { - if ($s eq ".." or $s eq "-") { - # This may add to @Global::sshlogin - possibly bug - read_sshloginfile(expand_slf_shorthand($s)); - } else { - push (@login, $s); - } - } - } - $Global::minimal_command_line_length = 8_000_000; - my @allowed_hostgroups; - for my $ncpu_sshlogin_string (::uniq(@login)) { - my $sshlogin = SSHLogin->new($ncpu_sshlogin_string); - my $sshlogin_string = $sshlogin->string(); - if($sshlogin_string eq "") { - # This is an ssh group: -S @webservers - push @allowed_hostgroups, $sshlogin->hostgroups(); - next; - } - if($Global::host{$sshlogin_string}) { - # This sshlogin has already been added: - # It is probably a host that has come back - # Set the max_jobs_running back to the original - debug("run","Already seen $sshlogin_string\n"); - if($sshlogin->{'ncpus'}) { - # If ncpus set by '#/' of the sshlogin, overwrite it: - $Global::host{$sshlogin_string}->set_ncpus($sshlogin->ncpus()); - } - $Global::host{$sshlogin_string}->set_max_jobs_running(undef); - next; - } - if($sshlogin_string eq ":") { - $sshlogin->set_maxlength(Limits::Command::max_length()); - } else { - # If all chars needs to be quoted, every other character will be \ - $sshlogin->set_maxlength(int(Limits::Command::max_length()/2)); - } - $Global::minimal_command_line_length = - ::min($Global::minimal_command_line_length, $sshlogin->maxlength()); - $Global::host{$sshlogin_string} = $sshlogin; - } - if(@allowed_hostgroups) { - # Remove hosts that are not in these groups - while (my ($string, $sshlogin) = each %Global::host) { - if(not $sshlogin->in_hostgroups(@allowed_hostgroups)) { - delete $Global::host{$string}; - } - } - } - - # debug("start", "sshlogin: ", my_dump(%Global::host),"\n"); - if($opt::transfer or @opt::return or $opt::cleanup or @opt::basefile) { - if(not remote_hosts()) { - # There are no remote hosts - if(@opt::trc) { - ::warning("--trc ignored as there are no remote --sshlogin.\n"); - } elsif (defined $opt::transfer) { - ::warning("--transfer ignored as there are no remote --sshlogin.\n"); - } elsif (@opt::return) { - ::warning("--return ignored as there are no remote --sshlogin.\n"); - } elsif (defined $opt::cleanup) { - ::warning("--cleanup ignored as there are no remote --sshlogin.\n"); - } elsif (@opt::basefile) { - ::warning("--basefile ignored as there are no remote --sshlogin.\n"); - } - } - } -} - -sub remote_hosts { - # Return sshlogins that are not ':' - # Returns: - # list of sshlogins with ':' removed - return grep !/^:$/, keys %Global::host; -} - -sub setup_basefile { - # Transfer basefiles to each $sshlogin - # This needs to be done before first jobs on $sshlogin is run - # Returns: N/A - my $cmd = ""; - my $rsync_destdir; - my $workdir; - for my $sshlogin (values %Global::host) { - if($sshlogin->string() eq ":") { next } - for my $file (@opt::basefile) { - if($file !~ m:^/: and $opt::workdir eq "...") { - ::error("Work dir '...' will not work with relative basefiles\n"); - ::wait_and_exit(255); - } - $workdir ||= Job->new("")->workdir(); - $cmd .= $sshlogin->rsync_transfer_cmd($file,$workdir) . "&"; - } - } - $cmd .= "wait;"; - debug("init", "basesetup: $cmd\n"); - print `$cmd`; -} - -sub cleanup_basefile { - # Remove the basefiles transferred - # Returns: N/A - my $cmd=""; - my $workdir = Job->new("")->workdir(); - for my $sshlogin (values %Global::host) { - if($sshlogin->string() eq ":") { next } - for my $file (@opt::basefile) { - $cmd .= $sshlogin->cleanup_cmd($file,$workdir)."&"; - } - } - $cmd .= "wait;"; - debug("init", "basecleanup: $cmd\n"); - print `$cmd`; -} - -sub filter_hosts { - my(@cores, @cpus, @maxline, @echo); - my $envvar = ::shell_quote_scalar($Global::envvar); - while (my ($host, $sshlogin) = each %Global::host) { - if($host eq ":") { next } - # The 'true' is used to get the $host out later - my $sshcmd = "true $host;" . $sshlogin->sshcommand()." ".$sshlogin->serverlogin(); - push(@cores, $host."\t".$sshcmd." ".$envvar." parallel --number-of-cores\n\0"); - push(@cpus, $host."\t".$sshcmd." ".$envvar." parallel --number-of-cpus\n\0"); - push(@maxline, $host."\t".$sshcmd." ".$envvar." parallel --max-line-length-allowed\n\0"); - # 'echo' is used to get the best possible value for an ssh login time - push(@echo, $host."\t".$sshcmd." echo\n\0"); - } - my ($fh, $tmpfile) = ::tmpfile(SUFFIX => ".ssh"); - print $fh @cores, @cpus, @maxline, @echo; - close $fh; - # --timeout 5: Setting up an SSH connection and running a simple - # command should never take > 5 sec. - # --delay 0.1: If multiple sshlogins use the same proxy the delay - # will make it less likely to overload the ssh daemon. - # --retries 3: If the ssh daemon it overloaded, try 3 times - # -s 16000: Half of the max line on UnixWare - my $cmd = "cat $tmpfile | $0 -j0 --timeout 5 -s 16000 --joblog - --plain --delay 0.1 --retries 3 --tag --tagstring {1} -0 --colsep '\t' -k eval {2} 2>/dev/null"; - ::debug("init", $cmd, "\n"); - open(my $host_fh, "-|", $cmd) || ::die_bug("parallel host check: $cmd"); - my (%ncores, %ncpus, %time_to_login, %maxlen, %echo, @down_hosts); - my $prepend = ""; - while(<$host_fh>) { - if(/\'$/) { - # if last char = ' then append next line - # This may be due to quoting of $Global::envvar - $prepend .= $_; - next; - } - $_ = $prepend . $_; - $prepend = ""; - chomp; - my @col = split /\t/, $_; - if(defined $col[6]) { - # This is a line from --joblog - # seq host time spent sent received exit signal command - # 2 : 1372607672.654 0.675 0 0 0 0 eval true\ m\;ssh\ m\ parallel\ --number-of-cores - if($col[0] eq "Seq" and $col[1] eq "Host" and - $col[2] eq "Starttime") { - # Header => skip - next; - } - # Get server from: eval true server\; - $col[8] =~ /eval true..([^;]+).;/ or ::die_bug("col8 does not contain host: $col[8]"); - my $host = $1; - $host =~ tr/\\//d; - $Global::host{$host} or next; - if($col[6] eq "255" or $col[7] eq "15") { - # exit == 255 or signal == 15: ssh failed - # Remove sshlogin - ::debug("init", "--filtered $host\n"); - push(@down_hosts, $host); - @down_hosts = uniq(@down_hosts); - } elsif($col[6] eq "127") { - # signal == 127: parallel not installed remote - # Set ncpus and ncores = 1 - ::warning("Could not figure out ", - "number of cpus on $host. Using 1.\n"); - $ncores{$host} = 1; - $ncpus{$host} = 1; - $maxlen{$host} = Limits::Command::max_length(); - } elsif($col[0] =~ /^\d+$/ and $Global::host{$host}) { - # Remember how log it took to log in - # 2 : 1372607672.654 0.675 0 0 0 0 eval true\ m\;ssh\ m\ echo - $time_to_login{$host} = ::min($time_to_login{$host},$col[3]); - } else { - ::die_bug("host check unmatched long jobline: $_"); - } - } elsif($Global::host{$col[0]}) { - # This output from --number-of-cores, --number-of-cpus, - # --max-line-length-allowed - # ncores: server 8 - # ncpus: server 2 - # maxlen: server 131071 - if(not $ncores{$col[0]}) { - $ncores{$col[0]} = $col[1]; - } elsif(not $ncpus{$col[0]}) { - $ncpus{$col[0]} = $col[1]; - } elsif(not $maxlen{$col[0]}) { - $maxlen{$col[0]} = $col[1]; - } elsif(not $echo{$col[0]}) { - $echo{$col[0]} = $col[1]; - } elsif(m/perl: warning:|LANGUAGE =|LC_ALL =|LANG =|are supported and installed/) { - # Skip these: - # perl: warning: Setting locale failed. - # perl: warning: Please check that your locale settings: - # LANGUAGE = (unset), - # LC_ALL = (unset), - # LANG = "en_US.UTF-8" - # are supported and installed on your system. - # perl: warning: Falling back to the standard locale ("C"). - } else { - ::die_bug("host check too many col0: $_"); - } - } else { - ::die_bug("host check unmatched short jobline ($col[0]): $_"); - } - } - close $host_fh; - $Global::debug or unlink $tmpfile; - delete @Global::host{@down_hosts}; - @down_hosts and ::warning("Removed @down_hosts\n"); - $Global::minimal_command_line_length = 8_000_000; - while (my ($sshlogin, $obj) = each %Global::host) { - if($sshlogin eq ":") { next } - $ncpus{$sshlogin} or ::die_bug("ncpus missing: ".$obj->serverlogin()); - $ncores{$sshlogin} or ::die_bug("ncores missing: ".$obj->serverlogin()); - $time_to_login{$sshlogin} or ::die_bug("time_to_login missing: ".$obj->serverlogin()); - $maxlen{$sshlogin} or ::die_bug("maxlen missing: ".$obj->serverlogin()); - if($opt::use_cpus_instead_of_cores) { - $obj->set_ncpus($ncpus{$sshlogin}); - } else { - $obj->set_ncpus($ncores{$sshlogin}); - } - $obj->set_time_to_login($time_to_login{$sshlogin}); - $obj->set_maxlength($maxlen{$sshlogin}); - $Global::minimal_command_line_length = - ::min($Global::minimal_command_line_length, - int($maxlen{$sshlogin}/2)); - ::debug("init", "Timing from -S:$sshlogin ncpus:",$ncpus{$sshlogin}, - " ncores:", $ncores{$sshlogin}, - " time_to_login:", $time_to_login{$sshlogin}, - " maxlen:", $maxlen{$sshlogin}, - " min_max_len:", $Global::minimal_command_line_length,"\n"); - } -} - -sub onall { - sub tmp_joblog { - my $joblog = shift; - if(not defined $joblog) { - return undef; - } - my ($fh, $tmpfile) = ::tmpfile(SUFFIX => ".log"); - close $fh; - return $tmpfile; - } - my @command = @_; - if($Global::quoting) { - @command = shell_quote_empty(@command); - } - - # Copy all @fhlist into tempfiles - my @argfiles = (); - for my $fh (@fhlist) { - my ($outfh, $name) = ::tmpfile(SUFFIX => ".all", UNLINK => 1); - print $outfh (<$fh>); - close $outfh; - push @argfiles, $name; - } - if(@opt::basefile) { setup_basefile(); } - # for each sshlogin do: - # parallel -S $sshlogin $command :::: @argfiles - # - # Pass some of the options to the sub-parallels, not all of them as - # -P should only go to the first, and -S should not be copied at all. - my $options = - join(" ", - ((defined $opt::jobs) ? "-P $opt::jobs" : ""), - ((defined $opt::linebuffer) ? "--linebuffer" : ""), - ((defined $opt::ungroup) ? "-u" : ""), - ((defined $opt::group) ? "-g" : ""), - ((defined $opt::keeporder) ? "--keeporder" : ""), - ((defined $opt::D) ? "-D $opt::D" : ""), - ((defined $opt::plain) ? "--plain" : ""), - ((defined $opt::max_chars) ? "--max-chars ".$opt::max_chars : ""), - ); - my $suboptions = - join(" ", - ((defined $opt::ungroup) ? "-u" : ""), - ((defined $opt::linebuffer) ? "--linebuffer" : ""), - ((defined $opt::group) ? "-g" : ""), - ((defined $opt::files) ? "--files" : ""), - ((defined $opt::keeporder) ? "--keeporder" : ""), - ((defined $opt::colsep) ? "--colsep ".shell_quote($opt::colsep) : ""), - ((@opt::v) ? "-vv" : ""), - ((defined $opt::D) ? "-D $opt::D" : ""), - ((defined $opt::timeout) ? "--timeout ".$opt::timeout : ""), - ((defined $opt::plain) ? "--plain" : ""), - ((defined $opt::retries) ? "--retries ".$opt::retries : ""), - ((defined $opt::max_chars) ? "--max-chars ".$opt::max_chars : ""), - ((defined $opt::arg_sep) ? "--arg-sep ".$opt::arg_sep : ""), - ((defined $opt::arg_file_sep) ? "--arg-file-sep ".$opt::arg_file_sep : ""), - (@opt::env ? map { "--env ".::shell_quote_scalar($_) } @opt::env : ""), - ); - ::debug("init", "| $0 $options\n"); - open(my $parallel_fh, "|-", "$0 --no-notice -j0 $options") || - ::die_bug("This does not run GNU Parallel: $0 $options"); - my @joblogs; - for my $host (sort keys %Global::host) { - my $sshlogin = $Global::host{$host}; - my $joblog = tmp_joblog($opt::joblog); - if($joblog) { - push @joblogs, $joblog; - $joblog = "--joblog $joblog"; - } - my $quad = $opt::arg_file_sep || "::::"; - ::debug("init", "$0 $suboptions -j1 $joblog ", - ((defined $opt::tag) ? - "--tagstring ".shell_quote_scalar($sshlogin->string()) : ""), - " -S ", shell_quote_scalar($sshlogin->string())," ", - join(" ",shell_quote(@command))," $quad @argfiles\n"); - print $parallel_fh "$0 $suboptions -j1 $joblog ", - ((defined $opt::tag) ? - "--tagstring ".shell_quote_scalar($sshlogin->string()) : ""), - " -S ", shell_quote_scalar($sshlogin->string())," ", - join(" ",shell_quote(@command))," $quad @argfiles\n"; - } - close $parallel_fh; - $Global::exitstatus = $? >> 8; - debug("init", "--onall exitvalue ", $?); - if(@opt::basefile) { cleanup_basefile(); } - $Global::debug or unlink(@argfiles); - my %seen; - for my $joblog (@joblogs) { - # Append to $joblog - open(my $fh, "<", $joblog) || ::die_bug("Cannot open tmp joblog $joblog"); - # Skip first line (header); - <$fh>; - print $Global::joblog (<$fh>); - close $fh; - unlink($joblog); - } -} - -sub __SIGNAL_HANDLING__ {} - -sub save_original_signal_handler { - # Remember the original signal handler - # Returns: N/A - $SIG{TERM} ||= sub { exit 0; }; # $SIG{TERM} is not set on Mac OS X - $SIG{INT} = sub { if($opt::tmux) { qx { tmux kill-session -t p$$ }; } - unlink keys %Global::unlink; exit -1 }; - $SIG{TERM} = sub { if($opt::tmux) { qx { tmux kill-session -t p$$ }; } - unlink keys %Global::unlink; exit -1 }; - %Global::original_sig = %SIG; - $SIG{TERM} = sub {}; # Dummy until jobs really start -} - -sub list_running_jobs { - # Returns: N/A - for my $v (values %Global::running) { - print $Global::original_stderr "$Global::progname: ",$v->replaced(),"\n"; - } -} - -sub start_no_new_jobs { - # Returns: N/A - $SIG{TERM} = $Global::original_sig{TERM}; - print $Global::original_stderr - ("$Global::progname: SIGTERM received. No new jobs will be started.\n", - "$Global::progname: Waiting for these ", scalar(keys %Global::running), - " jobs to finish. Send SIGTERM again to stop now.\n"); - list_running_jobs(); - $Global::start_no_new_jobs ||= 1; -} - -sub reaper { - # A job finished. - # Print the output. - # Start another job - # Returns: N/A - my $stiff; - my $children_reaped = 0; - debug("run", "Reaper "); - while (($stiff = waitpid(-1, &WNOHANG)) > 0) { - $children_reaped++; - if($Global::sshmaster{$stiff}) { - # This is one of the ssh -M: ignore - next; - } - my $job = $Global::running{$stiff}; - # '-a <(seq 10)' will give us a pid not in %Global::running - $job or next; - $job->set_exitstatus($? >> 8); - $job->set_exitsignal($? & 127); - debug("run", "died (", $job->exitstatus(), "): ", $job->seq()); - $job->set_endtime(::now()); - if($stiff == $Global::tty_taken) { - # The process that died had the tty => release it - $Global::tty_taken = 0; - } - - if(not $job->should_be_retried()) { - # The job is done - # Free the jobslot - push @Global::slots, $job->slot(); - if($opt::timeout) { - # Update average runtime for timeout - $Global::timeoutq->update_delta_time($job->runtime()); - } - # Force printing now if the job failed and we are going to exit - my $print_now = ($opt::halt_on_error and $opt::halt_on_error == 2 - and $job->exitstatus()); - if($opt::keeporder and not $print_now) { - print_earlier_jobs($job); - } else { - $job->print(); - } - if($job->exitstatus()) { - process_failed_job($job); - } - - } - my $sshlogin = $job->sshlogin(); - $sshlogin->dec_jobs_running(); - $sshlogin->inc_jobs_completed(); - $Global::total_running--; - delete $Global::running{$stiff}; - start_more_jobs(); - } - debug("run", "done "); - return $children_reaped; -} - -sub process_failed_job { - # The jobs had a exit status <> 0, so error - # Returns: N/A - my $job = shift; - $Global::exitstatus++; - $Global::total_failed++; - if($opt::halt_on_error) { - if($opt::halt_on_error == 1 - or - ($opt::halt_on_error < 1 and $Global::total_failed > 3 - and - $Global::total_failed / $Global::total_started > $opt::halt_on_error)) { - # If halt on error == 1 or --halt 10% - # we should gracefully exit - print $Global::original_stderr - ("$Global::progname: Starting no more jobs. ", - "Waiting for ", scalar(keys %Global::running), - " jobs to finish. This job failed:\n", - $job->replaced(),"\n"); - $Global::start_no_new_jobs ||= 1; - $Global::halt_on_error_exitstatus = $job->exitstatus(); - } elsif($opt::halt_on_error == 2) { - # If halt on error == 2 we should exit immediately - print $Global::original_stderr - ("$Global::progname: This job failed:\n", - $job->replaced(),"\n"); - exit ($job->exitstatus()); - } - } -} - -{ - my (%print_later,$job_end_sequence); - - sub print_earlier_jobs { - # Print jobs completed earlier - # Returns: N/A - my $job = shift; - $print_later{$job->seq()} = $job; - $job_end_sequence ||= 1; - debug("run", "Looking for: $job_end_sequence ", - "Current: ", $job->seq(), "\n"); - for(my $j = $print_later{$job_end_sequence}; - $j or vec($Global::job_already_run,$job_end_sequence,1); - $job_end_sequence++, - $j = $print_later{$job_end_sequence}) { - debug("run", "Found job end $job_end_sequence"); - if($j) { - $j->print(); - delete $print_later{$job_end_sequence}; - } - } - } -} - -sub __USAGE__ {} - -sub wait_and_exit { - # If we do not wait, we sometimes get segfault - # Returns: N/A - my $error = shift; - if($error) { - # Kill all without printing - for my $job (values %Global::running) { - $job->kill("TERM"); - $job->kill("TERM"); - } - } - for (keys %Global::unkilled_children) { - kill 9, $_; - waitpid($_,0); - delete $Global::unkilled_children{$_}; - } - wait(); - exit($error); -} - -sub die_usage { - # Returns: N/A - usage(); - wait_and_exit(255); -} - -sub usage { - # Returns: N/A - print join - ("\n", - "Usage:", - "", - "$Global::progname [options] [command [arguments]] < list_of_arguments", - "$Global::progname [options] [command [arguments]] (::: arguments|:::: argfile(s))...", - "cat ... | $Global::progname --pipe [options] [command [arguments]]", - "", - "-j n Run n jobs in parallel", - "-k Keep same order", - "-X Multiple arguments with context replace", - "--colsep regexp Split input on regexp for positional replacements", - "{} {.} {/} {/.} {#} {%} {= perl code =} Replacement strings", - "{3} {3.} {3/} {3/.} {=3 perl code =} Positional replacement strings", - "With --plus: {} = {+/}/{/} = {.}.{+.} = {+/}/{/.}.{+.} = {..}.{+..} =", - " {+/}/{/..}.{+..} = {...}.{+...} = {+/}/{/...}.{+...}", - "", - "-S sshlogin Example: foo\@server.example.com", - "--slf .. Use ~/.parallel/sshloginfile as the list of sshlogins", - "--trc {}.bar Shorthand for --transfer --return {}.bar --cleanup", - "--onall Run the given command with argument on all sshlogins", - "--nonall Run the given command with no arguments on all sshlogins", - "", - "--pipe Split stdin (standard input) to multiple jobs.", - "--recend str Record end separator for --pipe.", - "--recstart str Record start separator for --pipe.", - "", - "See 'man $Global::progname' for details", - "", - "When using programs that use GNU Parallel to process data for publication please cite:", - "", - "O. Tange (2011): GNU Parallel - The Command-Line Power Tool,", - ";login: The USENIX Magazine, February 2011:42-47.", - "", - "Or you can get GNU Parallel without this requirement by paying 10000 EUR.", - ""); -} - - -sub citation_notice { - # if --no-notice or --plain: do nothing - # if stderr redirected: do nothing - # if ~/.parallel/will-cite: do nothing - # else: print citation notice to stderr - if($opt::no_notice - or - $opt::plain - or - not -t $Global::original_stderr - or - -e $ENV{'HOME'}."/.parallel/will-cite") { - # skip - } else { - print $Global::original_stderr - ("When using programs that use GNU Parallel to process data for publication please cite:\n", - "\n", - " O. Tange (2011): GNU Parallel - The Command-Line Power Tool,\n", - " ;login: The USENIX Magazine, February 2011:42-47.\n", - "\n", - "This helps funding further development; and it won't cost you a cent.\n", - "Or you can get GNU Parallel without this requirement by paying 10000 EUR.\n", - "\n", - "To silence this citation notice run 'parallel --bibtex' once or use '--no-notice'.\n\n", - ); - flush $Global::original_stderr; - } -} - - -sub warning { - my @w = @_; - my $fh = $Global::original_stderr || *STDERR; - my $prog = $Global::progname || "parallel"; - print $fh $prog, ": Warning: ", @w; -} - - -sub error { - my @w = @_; - my $fh = $Global::original_stderr || *STDERR; - my $prog = $Global::progname || "parallel"; - print $fh $prog, ": Error: ", @w; -} - - -sub die_bug { - my $bugid = shift; - print STDERR - ("$Global::progname: This should not happen. You have found a bug.\n", - "Please contact and include:\n", - "* The version number: $Global::version\n", - "* The bugid: $bugid\n", - "* The command line being run\n", - "* The files being read (put the files on a webserver if they are big)\n", - "\n", - "If you get the error on smaller/fewer files, please include those instead.\n"); - ::wait_and_exit(255); -} - -sub version { - # Returns: N/A - if($opt::tollef and not $opt::gnu) { - print "WARNING: YOU ARE USING --tollef. IF THINGS ARE ACTING WEIRD USE --gnu.\n"; - } - print join("\n", - "GNU $Global::progname $Global::version", - "Copyright (C) 2007,2008,2009,2010,2011,2012,2013,2014 Ole Tange and Free Software Foundation, Inc.", - "License GPLv3+: GNU GPL version 3 or later ", - "This is free software: you are free to change and redistribute it.", - "GNU $Global::progname comes with no warranty.", - "", - "Web site: http://www.gnu.org/software/${Global::progname}\n", - "When using programs that use GNU Parallel to process data for publication please cite:\n", - "O. Tange (2011): GNU Parallel - The Command-Line Power Tool, ", - ";login: The USENIX Magazine, February 2011:42-47.\n", - "Or you can get GNU Parallel without this requirement by paying 10000 EUR.\n", - ); -} - -sub bibtex { - # Returns: N/A - if($opt::tollef and not $opt::gnu) { - print "WARNING: YOU ARE USING --tollef. IF THINGS ARE ACTING WEIRD USE --gnu.\n"; - } - print join("\n", - "When using programs that use GNU Parallel to process data for publication please cite:", - "", - "\@article{Tange2011a,", - " title = {GNU Parallel - The Command-Line Power Tool},", - " author = {O. Tange},", - " address = {Frederiksberg, Denmark},", - " journal = {;login: The USENIX Magazine},", - " month = {Feb},", - " number = {1},", - " volume = {36},", - " url = {http://www.gnu.org/s/parallel},", - " year = {2011},", - " pages = {42-47}", - "}", - "", - "(Feel free to use \\nocite{Tange2011a})", - "", - "This helps funding further development.", - "", - "Or you can get GNU Parallel without this requirement by paying 10000 EUR.", - "" - ); - while(not -e $ENV{'HOME'}."/.parallel/will-cite") { - print "\nType: 'will cite' and press enter.\n> "; - my $input = ; - if($input =~ /will cite/i) { - mkdir $ENV{'HOME'}."/.parallel"; - open (my $fh, ">", $ENV{'HOME'}."/.parallel/will-cite") - || ::die_bug("Cannot write: ".$ENV{'HOME'}."/.parallel/will-cite"); - close $fh; - print "\nThank you for your support. It is much appreciated. The citation\n", - "notice is now silenced.\n"; - } - } -} - -sub show_limits { - # Returns: N/A - print("Maximal size of command: ",Limits::Command::real_max_length(),"\n", - "Maximal used size of command: ",Limits::Command::max_length(),"\n", - "\n", - "Execution of will continue now, and it will try to read its input\n", - "and run commands; if this is not what you wanted to happen, please\n", - "press CTRL-D or CTRL-C\n"); -} - -sub __GENERIC_COMMON_FUNCTION__ {} - -sub uniq { - # Remove duplicates and return unique values - return keys %{{ map { $_ => 1 } @_ }}; -} - -sub min { - # Returns: - # Minimum value of array - my $min; - for (@_) { - # Skip undefs - defined $_ or next; - defined $min or do { $min = $_; next; }; # Set $_ to the first non-undef - $min = ($min < $_) ? $min : $_; - } - return $min; -} - -sub max { - # Returns: - # Maximum value of array - my $max; - for (@_) { - # Skip undefs - defined $_ or next; - defined $max or do { $max = $_; next; }; # Set $_ to the first non-undef - $max = ($max > $_) ? $max : $_; - } - return $max; -} - -sub sum { - # Returns: - # Sum of values of array - my @args = @_; - my $sum = 0; - for (@args) { - # Skip undefs - $_ and do { $sum += $_; } - } - return $sum; -} - -sub undef_as_zero { - my $a = shift; - return $a ? $a : 0; -} - -sub undef_as_empty { - my $a = shift; - return $a ? $a : ""; -} - -{ - my $hostname; - sub hostname { - if(not $hostname) { - $hostname = `hostname`; - chomp($hostname); - $hostname ||= "nohostname"; - } - return $hostname; - } -} - -sub which { - # Input: - # @programs = programs to find the path to - # Returns: - # @full_path = full paths to @programs. Nothing if not found - my @which; - for my $prg (@_) { - push @which, map { $_."/".$prg } grep { -x $_."/".$prg } split(":",$ENV{'PATH'}); - } - return @which; -} - -{ - my ($regexp,%fakename); - - sub parent_shell { - # Input: - # $pid = pid to see if (grand)*parent is a shell - # Returns: - # $shellpath = path to shell - undef if no shell found - my $pid = shift; - if(not $regexp) { - # All shells known to mankind - # - # ash bash csh dash fdsh fish fizsh ksh ksh93 mksh pdksh - # posh rbash rush rzsh sash sh static-sh tcsh yash zsh - my @shells = qw(ash bash csh dash fdsh fish fizsh ksh - ksh93 mksh pdksh posh rbash rush rzsh - sash sh static-sh tcsh yash zsh -sh -csh); - # Can be formatted as: - # [sh] -sh sh busybox sh - # /bin/sh /sbin/sh /opt/csw/sh - # NOT: foo.sh sshd crash flush pdflush scosh fsflush ssh - my $shell = "(?:".join("|",@shells).")"; - $regexp = '^((\[)('. $shell. ')(\])|(|\S+/|busybox )('. $shell. '))($| )'; - %fakename = ( - # csh and tcsh disguise themselves as -sh/-csh - "-sh" => ["csh", "tcsh"], - "-csh" => ["tcsh", "csh"], - ); - } - my ($children_of_ref, $parent_of_ref, $name_of_ref) = pid_table(); - my $shellpath; - my $testpid = $pid; - while($testpid) { - ::debug("init", "shell? ". $name_of_ref->{$testpid}."\n"); - if($name_of_ref->{$testpid} =~ /$regexp/o) { - ::debug("init", "which ".($3||$6)." => "); - $shellpath = (which($3 || $6,@{$fakename{$3 || $6}}))[0]; - ::debug("init", "shell path $shellpath\n"); - $shellpath and last; - } - $testpid = $parent_of_ref->{$testpid}; - } - return $shellpath; - } -} - -{ - my %pid_parentpid_cmd; - - sub pid_table { - # Returns: - # %children_of = { pid -> children of pid } - # %parent_of = { pid -> pid of parent } - # %name_of = { pid -> commandname } - - if(not %pid_parentpid_cmd) { - # Filter for SysV-style `ps` - my $sysv = q( ps -ef | perl -ane '1..1 and /^(.*)CO?MM?A?N?D/ and $s=length $1;). - q(s/^.{$s}//; print "@F[1,2] $_"' ); - # BSD-style `ps` - my $bsd = q(ps -o pid,ppid,command -ax); - %pid_parentpid_cmd = - ( - 'aix' => $sysv, - 'cygwin' => $sysv, - 'msys' => $sysv, - 'dec_osf' => $sysv, - 'darwin' => $bsd, - 'dragonfly' => $bsd, - 'freebsd' => $bsd, - 'gnu' => $sysv, - 'hpux' => $sysv, - 'linux' => $sysv, - 'mirbsd' => $bsd, - 'netbsd' => $bsd, - 'nto' => $sysv, - 'openbsd' => $bsd, - 'solaris' => $sysv, - 'svr5' => $sysv, - ); - } - $pid_parentpid_cmd{$^O} or ::die_bug("pid_parentpid_cmd for $^O missing"); - - my (@pidtable,%parent_of,%children_of,%name_of); - # Table with pid -> children of pid - @pidtable = `$pid_parentpid_cmd{$^O}`; - my $p=$$; - for (@pidtable) { - # must match: 24436 21224 busybox ash - /(\S+)\s+(\S+)\s+(\S+.*)/ or ::die_bug("pidtable format: $_"); - $parent_of{$1} = $2; - push @{$children_of{$2}}, $1; - $name_of{$1} = $3; - } - return(\%children_of, \%parent_of, \%name_of); - } -} - -sub reap_usleep { - # Reap dead children. - # If no dead children: Sleep specified amount with exponential backoff - # Input: - # $ms = milliseconds to sleep - # Returns: - # $ms/2+0.001 if children reaped - # $ms*1.1 if no children reaped - my $ms = shift; - if(reaper()) { - # Sleep exponentially shorter (1/2^n) if a job finished - return $ms/2+0.001; - } else { - if($opt::timeout) { - $Global::timeoutq->process_timeouts(); - } - usleep($ms); - Job::exit_if_disk_full(); - if($opt::linebuffer) { - for my $job (values %Global::running) { - $job->print(); - } - } - # Sleep exponentially longer (1.1^n) if a job did not finish - # though at most 1000 ms. - return (($ms < 1000) ? ($ms * 1.1) : ($ms)); - } -} - -sub usleep { - # Sleep this many milliseconds. - # Input: - # $ms = milliseconds to sleep - my $ms = shift; - ::debug(int($ms),"ms "); - select(undef, undef, undef, $ms/1000); -} - -sub now { - # Returns time since epoch as in seconds with 3 decimals - # Uses: - # @Global::use - # Returns: - # $time = time now with millisecond accuracy - if(not $Global::use{"Time::HiRes"}) { - if(eval "use Time::HiRes qw ( time );") { - eval "sub TimeHiRestime { return Time::HiRes::time };"; - } else { - eval "sub TimeHiRestime { return time() };"; - } - $Global::use{"Time::HiRes"} = 1; - } - - return (int(TimeHiRestime()*1000))/1000; -} - -sub multiply_binary_prefix { - # Evalualte numbers with binary prefix - # Ki=2^10, Mi=2^20, Gi=2^30, Ti=2^40, Pi=2^50, Ei=2^70, Zi=2^80, Yi=2^80 - # ki=2^10, mi=2^20, gi=2^30, ti=2^40, pi=2^50, ei=2^70, zi=2^80, yi=2^80 - # K =2^10, M =2^20, G =2^30, T =2^40, P =2^50, E =2^70, Z =2^80, Y =2^80 - # k =10^3, m =10^6, g =10^9, t=10^12, p=10^15, e=10^18, z=10^21, y=10^24 - # 13G = 13*1024*1024*1024 = 13958643712 - # Input: - # $s = string with prefixes - # Returns: - # $value = int with prefixes multiplied - my $s = shift; - $s =~ s/ki/*1024/gi; - $s =~ s/mi/*1024*1024/gi; - $s =~ s/gi/*1024*1024*1024/gi; - $s =~ s/ti/*1024*1024*1024*1024/gi; - $s =~ s/pi/*1024*1024*1024*1024*1024/gi; - $s =~ s/ei/*1024*1024*1024*1024*1024*1024/gi; - $s =~ s/zi/*1024*1024*1024*1024*1024*1024*1024/gi; - $s =~ s/yi/*1024*1024*1024*1024*1024*1024*1024*1024/gi; - $s =~ s/xi/*1024*1024*1024*1024*1024*1024*1024*1024*1024/gi; - - $s =~ s/K/*1024/g; - $s =~ s/M/*1024*1024/g; - $s =~ s/G/*1024*1024*1024/g; - $s =~ s/T/*1024*1024*1024*1024/g; - $s =~ s/P/*1024*1024*1024*1024*1024/g; - $s =~ s/E/*1024*1024*1024*1024*1024*1024/g; - $s =~ s/Z/*1024*1024*1024*1024*1024*1024*1024/g; - $s =~ s/Y/*1024*1024*1024*1024*1024*1024*1024*1024/g; - $s =~ s/X/*1024*1024*1024*1024*1024*1024*1024*1024*1024/g; - - $s =~ s/k/*1000/g; - $s =~ s/m/*1000*1000/g; - $s =~ s/g/*1000*1000*1000/g; - $s =~ s/t/*1000*1000*1000*1000/g; - $s =~ s/p/*1000*1000*1000*1000*1000/g; - $s =~ s/e/*1000*1000*1000*1000*1000*1000/g; - $s =~ s/z/*1000*1000*1000*1000*1000*1000*1000/g; - $s =~ s/y/*1000*1000*1000*1000*1000*1000*1000*1000/g; - $s =~ s/x/*1000*1000*1000*1000*1000*1000*1000*1000*1000/g; - - $s = eval $s; - ::debug($s); - return $s; -} - -sub tmpfile { - # Create tempfile as $TMPDIR/parXXXXX - # Returns: - # $filename = file name created - return ::tempfile(DIR=>$ENV{'TMPDIR'}, TEMPLATE => 'parXXXXX', @_); -} - -sub __DEBUGGING__ {} - -sub debug { - # Uses: - # $Global::debug - # %Global::fd - # Returns: N/A - $Global::debug or return; - @_ = grep { defined $_ ? $_ : "" } @_; - if($Global::debug eq "all" or $Global::debug eq $_[0]) { - if($Global::fd{1}) { - # Original stdout was saved - my $stdout = $Global::fd{1}; - print $stdout @_[1..$#_]; - } else { - print @_[1..$#_]; - } - } -} - -sub my_memory_usage { - # Returns: - # memory usage if found - # 0 otherwise - use strict; - use FileHandle; - - my $pid = $$; - if(-e "/proc/$pid/stat") { - my $fh = FileHandle->new("; - chomp $data; - $fh->close; - - my @procinfo = split(/\s+/,$data); - - return undef_as_zero($procinfo[22]); - } else { - return 0; - } -} - -sub my_size { - # Returns: - # $size = size of object if Devel::Size is installed - # -1 otherwise - my @size_this = (@_); - eval "use Devel::Size qw(size total_size)"; - if ($@) { - return -1; - } else { - return total_size(@_); - } -} - -sub my_dump { - # Returns: - # ascii expression of object if Data::Dump(er) is installed - # error code otherwise - my @dump_this = (@_); - eval "use Data::Dump qw(dump);"; - if ($@) { - # Data::Dump not installed - eval "use Data::Dumper;"; - if ($@) { - my $err = "Neither Data::Dump nor Data::Dumper is installed\n". - "Not dumping output\n"; - print $Global::original_stderr $err; - return $err; - } else { - return Dumper(@dump_this); - } - } else { - # Create a dummy Data::Dump:dump as Hans Schou sometimes has - # it undefined - eval "sub Data::Dump:dump {}"; - eval "use Data::Dump qw(dump);"; - return (Data::Dump::dump(@dump_this)); - } -} - -sub my_croak { - eval "use Carp; 1"; - $Carp::Verbose = 1; - croak(@_); -} - -sub my_carp { - eval "use Carp; 1"; - $Carp::Verbose = 1; - carp(@_); -} - -sub __OBJECT_ORIENTED_PARTS__ {} - -package SSHLogin; - -sub new { - my $class = shift; - my $sshlogin_string = shift; - my $ncpus; - my %hostgroups; - # SSHLogins can have these formats: - # @grp+grp/ncpu//usr/bin/ssh user@server - # ncpu//usr/bin/ssh user@server - # /usr/bin/ssh user@server - # user@server - # ncpu/user@server - # @grp+grp/user@server - if($sshlogin_string =~ s:^\@([^/]+)/?::) { - # Look for SSHLogin hostgroups - %hostgroups = map { $_ => 1 } split(/\+/, $1); - } - if ($sshlogin_string =~ s:^(\d+)/::) { - # Override default autodetected ncpus unless missing - $ncpus = $1; - } - my $string = $sshlogin_string; - # An SSHLogin is always in the hostgroup of its $string-name - $hostgroups{$string} = 1; - @Global::hostgroups{keys %hostgroups} = values %hostgroups; - my @unget = (); - my $no_slash_string = $string; - $no_slash_string =~ s/[^-a-z0-9:]/_/gi; - return bless { - 'string' => $string, - 'jobs_running' => 0, - 'jobs_completed' => 0, - 'maxlength' => undef, - 'max_jobs_running' => undef, - 'orig_max_jobs_running' => undef, - 'ncpus' => $ncpus, - 'hostgroups' => \%hostgroups, - 'sshcommand' => undef, - 'serverlogin' => undef, - 'control_path_dir' => undef, - 'control_path' => undef, - 'time_to_login' => undef, - 'last_login_at' => undef, - 'loadavg_file' => $ENV{'HOME'} . "/.parallel/tmp/loadavg-" . - $no_slash_string, - 'loadavg' => undef, - 'last_loadavg_update' => 0, - 'swap_activity_file' => $ENV{'HOME'} . "/.parallel/tmp/swap_activity-" . - $no_slash_string, - 'swap_activity' => undef, - }, ref($class) || $class; -} - -sub DESTROY { - my $self = shift; - # Remove temporary files if they are created. - unlink $self->{'loadavg_file'}; - unlink $self->{'swap_activity_file'}; -} - -sub string { - my $self = shift; - return $self->{'string'}; -} - -sub jobs_running { - my $self = shift; - - return ($self->{'jobs_running'} || "0"); -} - -sub inc_jobs_running { - my $self = shift; - $self->{'jobs_running'}++; -} - -sub dec_jobs_running { - my $self = shift; - $self->{'jobs_running'}--; -} - -sub set_maxlength { - my $self = shift; - $self->{'maxlength'} = shift; -} - -sub maxlength { - my $self = shift; - return $self->{'maxlength'}; -} - -sub jobs_completed { - my $self = shift; - return $self->{'jobs_completed'}; -} - -sub in_hostgroups { - # Input: - # @hostgroups = the hostgroups to look for - # Returns: - # true if intersection of @hostgroups and the hostgroups of this - # SSHLogin is non-empty - my $self = shift; - return grep { defined $self->{'hostgroups'}{$_} } @_; -} - -sub hostgroups { - my $self = shift; - return keys %{$self->{'hostgroups'}}; -} - -sub inc_jobs_completed { - my $self = shift; - $self->{'jobs_completed'}++; -} - -sub set_max_jobs_running { - my $self = shift; - if(defined $self->{'max_jobs_running'}) { - $Global::max_jobs_running -= $self->{'max_jobs_running'}; - } - $self->{'max_jobs_running'} = shift; - if(defined $self->{'max_jobs_running'}) { - # max_jobs_running could be resat if -j is a changed file - $Global::max_jobs_running += $self->{'max_jobs_running'}; - } - # Initialize orig to the first non-zero value that comes around - $self->{'orig_max_jobs_running'} ||= $self->{'max_jobs_running'}; -} - -sub swapping { - my $self = shift; - my $swapping = $self->swap_activity(); - return (not defined $swapping or $swapping) -} - -sub swap_activity { - # If the currently known swap activity is too old: - # Recompute a new one in the background - # Returns: - # last swap activity computed - my $self = shift; - # Should we update the swap_activity file? - my $update_swap_activity_file = 0; - if(-r $self->{'swap_activity_file'}) { - open(my $swap_fh, "<", $self->{'swap_activity_file'}) || ::die_bug("swap_activity_file-r"); - my $swap_out = <$swap_fh>; - close $swap_fh; - if($swap_out =~ /^(\d+)$/) { - $self->{'swap_activity'} = $1; - ::debug("swap", "New swap_activity: ", $self->{'swap_activity'}); - } - ::debug("swap", "Last update: ", $self->{'last_swap_activity_update'}); - if(time - $self->{'last_swap_activity_update'} > 10) { - # last swap activity update was started 10 seconds ago - ::debug("swap", "Older than 10 sec: ", $self->{'swap_activity_file'}); - $update_swap_activity_file = 1; - } - } else { - ::debug("swap", "No swap_activity file: ", $self->{'swap_activity_file'}); - $self->{'swap_activity'} = undef; - $update_swap_activity_file = 1; - } - if($update_swap_activity_file) { - ::debug("swap", "Updating swap_activity file ", $self->{'swap_activity_file'}); - $self->{'last_swap_activity_update'} = time; - -e $ENV{'HOME'}."/.parallel" or mkdir $ENV{'HOME'}."/.parallel"; - -e $ENV{'HOME'}."/.parallel/tmp" or mkdir $ENV{'HOME'}."/.parallel/tmp"; - my $swap_activity; - $swap_activity = swapactivityscript(); - if($self->{'string'} ne ":") { - $swap_activity = $self->sshcommand() . " " . $self->serverlogin() . " " . - ::shell_quote_scalar($swap_activity); - } - # Run swap_activity measuring. - # As the command can take long to run if run remote - # save it to a tmp file before moving it to the correct file - my $file = $self->{'swap_activity_file'}; - my ($dummy_fh, $tmpfile) = ::tmpfile(SUFFIX => ".swp"); - ::debug("swap", "\n", $swap_activity, "\n"); - qx{ ($swap_activity > $tmpfile && mv $tmpfile $file || rm $tmpfile) & }; - } - return $self->{'swap_activity'}; -} - -{ - my $script; - - sub swapactivityscript { - # Returns: - # shellscript for detecting swap activity - # - # arguments for vmstat are OS dependant - # swap_in and swap_out are in different columns depending on OS - # - if(not $script) { - my %vmstat = ( - # linux: $7*$8 - # $ vmstat 1 2 - # procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu---- - # r b swpd free buff cache si so bi bo in cs us sy id wa - # 5 0 51208 1701096 198012 18857888 0 0 37 153 28 19 56 11 33 1 - # 3 0 51208 1701288 198012 18857972 0 0 0 0 3638 10412 15 3 82 0 - 'linux' => ['vmstat 1 2 | tail -n1', '$7*$8'], - - # solaris: $6*$7 - # $ vmstat -S 1 2 - # kthr memory page disk faults cpu - # r b w swap free si so pi po fr de sr s3 s4 -- -- in sy cs us sy id - # 0 0 0 4628952 3208408 0 0 3 1 1 0 0 -0 2 0 0 263 613 246 1 2 97 - # 0 0 0 4552504 3166360 0 0 0 0 0 0 0 0 0 0 0 246 213 240 1 1 98 - 'solaris' => ['vmstat -S 1 2 | tail -1', '$6*$7'], - - # darwin (macosx): $21*$22 - # $ vm_stat -c 2 1 - # Mach Virtual Memory Statistics: (page size of 4096 bytes) - # free active specul inactive throttle wired prgable faults copy 0fill reactive purged file-backed anonymous cmprssed cmprssor dcomprs comprs pageins pageout swapins swapouts - # 346306 829050 74871 606027 0 240231 90367 544858K 62343596 270837K 14178 415070 570102 939846 356 370 116 922 4019813 4 0 0 - # 345740 830383 74875 606031 0 239234 90369 2696 359 553 0 0 570110 941179 356 370 0 0 0 0 0 0 - 'darwin' => ['vm_stat -c 2 1 | tail -n1', '$21*$22'], - - # ultrix: $12*$13 - # $ vmstat -S 1 2 - # procs faults cpu memory page disk - # r b w in sy cs us sy id avm fre si so pi po fr de sr s0 - # 1 0 0 4 23 2 3 0 97 7743 217k 0 0 0 0 0 0 0 0 - # 1 0 0 6 40 8 0 1 99 7743 217k 0 0 3 0 0 0 0 0 - 'ultrix' => ['vmstat -S 1 2 | tail -1', '$12*$13'], - - # aix: $6*$7 - # $ vmstat 1 2 - # System configuration: lcpu=1 mem=2048MB - # - # kthr memory page faults cpu - # ----- ----------- ------------------------ ------------ ----------- - # r b avm fre re pi po fr sr cy in sy cs us sy id wa - # 0 0 333933 241803 0 0 0 0 0 0 10 143 90 0 0 99 0 - # 0 0 334125 241569 0 0 0 0 0 0 37 5368 184 0 9 86 5 - 'aix' => ['vmstat 1 2 | tail -n1', '$6*$7'], - - # freebsd: $8*$9 - # $ vmstat -H 1 2 - # procs memory page disks faults cpu - # r b w avm fre flt re pi po fr sr ad0 ad1 in sy cs us sy id - # 1 0 0 596716 19560 32 0 0 0 33 8 0 0 11 220 277 0 0 99 - # 0 0 0 596716 19560 2 0 0 0 0 0 0 0 11 144 263 0 1 99 - 'freebsd' => ['vmstat -H 1 2 | tail -n1', '$8*$9'], - - # mirbsd: $8*$9 - # $ vmstat 1 2 - # procs memory page disks traps cpu - # r b w avm fre flt re pi po fr sr wd0 cd0 int sys cs us sy id - # 0 0 0 25776 164968 34 0 0 0 0 0 0 0 230 259 38 4 0 96 - # 0 0 0 25776 164968 24 0 0 0 0 0 0 0 237 275 37 0 0 100 - 'mirbsd' => ['vmstat 1 2 | tail -n1', '$8*$9'], - - # netbsd: $7*$8 - # $ vmstat 1 2 - # procs memory page disks faults cpu - # r b avm fre flt re pi po fr sr w0 w1 in sy cs us sy id - # 0 0 138452 6012 54 0 0 0 1 2 3 0 4 100 23 0 0 100 - # 0 0 138456 6008 1 0 0 0 0 0 0 0 7 26 19 0 0 100 - 'netbsd' => ['vmstat 1 2 | tail -n1', '$7*$8'], - - # openbsd: $8*$9 - # $ vmstat 1 2 - # procs memory page disks traps cpu - # r b w avm fre flt re pi po fr sr wd0 wd1 int sys cs us sy id - # 0 0 0 76596 109944 73 0 0 0 0 0 0 1 5 259 22 0 1 99 - # 0 0 0 76604 109936 24 0 0 0 0 0 0 0 7 114 20 0 1 99 - 'openbsd' => ['vmstat 1 2 | tail -n1', '$8*$9'], - - # hpux: $8*$9 - # $ vmstat 1 2 - # procs memory page faults cpu - # r b w avm free re at pi po fr de sr in sy cs us sy id - # 1 0 0 247211 216476 4 1 0 0 0 0 0 102 73005 54 6 11 83 - # 1 0 0 247211 216421 43 9 0 0 0 0 0 144 1675 96 25269512791222387000 25269512791222387000 105 - 'hpux' => ['vmstat 1 2 | tail -n1', '$8*$9'], - - # dec_osf (tru64): $11*$12 - # $ vmstat 1 2 - # Virtual Memory Statistics: (pagesize = 8192) - # procs memory pages intr cpu - # r w u act free wire fault cow zero react pin pout in sy cs us sy id - # 3 181 36 51K 1895 8696 348M 59M 122M 259 79M 0 5 218 302 4 1 94 - # 3 181 36 51K 1893 8696 3 15 21 0 28 0 4 81 321 1 1 98 - 'dec_osf' => ['vmstat 1 2 | tail -n1', '$11*$12'], - - # gnu (hurd): $7*$8 - # $ vmstat -k 1 2 - # (pagesize: 4, size: 512288, swap size: 894972) - # free actv inact wired zeroed react pgins pgouts pfaults cowpfs hrat caobj cache swfree - # 371940 30844 89228 20276 298348 0 48192 19016 756105 99808 98% 876 20628 894972 - # 371940 30844 89228 20276 +0 +0 +0 +0 +42 +2 98% 876 20628 894972 - 'gnu' => ['vmstat -k 1 2 | tail -n1', '$7*$8'], - - # -nto (qnx has no swap) - #-irix - #-svr5 (scosysv) - ); - my $perlscript = ""; - for my $os (keys %vmstat) { - #q[ { vmstat 1 2 2> /dev/null || vmstat -c 1 2; } | ]. - # q[ awk 'NR!=4{next} NF==17||NF==16{print $7*$8} NF==22{print $21*$22} {exit}' ]; - $vmstat{$os}[1] =~ s/\$/\\\\\\\$/g; # $ => \\\$ - $perlscript .= 'if($^O eq "'.$os.'") { print `'.$vmstat{$os}[0].' | awk "{print ' . - $vmstat{$os}[1] . '}"` }'; - } - $perlscript = "perl -e " . ::shell_quote_scalar($perlscript); - $script = $Global::envvar. " " .$perlscript; - } - return $script; - } -} - -sub too_fast_remote_login { - my $self = shift; - if($self->{'last_login_at'} and $self->{'time_to_login'}) { - # sshd normally allows 10 simultaneous logins - # A login takes time_to_login - # So time_to_login/5 should be safe - # If now <= last_login + time_to_login/5: Then it is too soon. - my $too_fast = (::now() <= $self->{'last_login_at'} - + $self->{'time_to_login'}/5); - ::debug("run", "Too fast? $too_fast "); - return $too_fast; - } else { - # No logins so far (or time_to_login not computed): it is not too fast - return 0; - } -} - -sub last_login_at { - my $self = shift; - return $self->{'last_login_at'}; -} - -sub set_last_login_at { - my $self = shift; - $self->{'last_login_at'} = shift; -} - -sub loadavg_too_high { - my $self = shift; - my $loadavg = $self->loadavg(); - return (not defined $loadavg or - $loadavg > $self->max_loadavg()); -} - -sub loadavg { - # If the currently know loadavg is too old: - # Recompute a new one in the background - # The load average is computed as the number of processes waiting for disk - # or CPU right now. So it is the server load this instant and not averaged over - # several minutes. This is needed so GNU Parallel will at most start one job - # that will push the load over the limit. - # - # Returns: - # $last_loadavg = last load average computed (undef if none) - my $self = shift; - # Should we update the loadavg file? - my $update_loadavg_file = 0; - if(open(my $load_fh, "<", $self->{'loadavg_file'})) { - local $/ = undef; - my $load_out = <$load_fh>; - close $load_fh; - my $load =()= ($load_out=~/(^[DR]....[^\[])/gm); - if($load > 0) { - # load is overestimated by 1 - $self->{'loadavg'} = $load - 1; - ::debug("load", "New loadavg: ", $self->{'loadavg'}); - } else { - ::die_bug("loadavg_invalid_content: $load_out"); - } - ::debug("load", "Last update: ", $self->{'last_loadavg_update'}); - if(time - $self->{'last_loadavg_update'} > 10) { - # last loadavg was started 10 seconds ago - ::debug("load", time - $self->{'last_loadavg_update'}, " secs old: ", - $self->{'loadavg_file'}); - $update_loadavg_file = 1; - } - } else { - ::debug("load", "No loadavg file: ", $self->{'loadavg_file'}); - $self->{'loadavg'} = undef; - $update_loadavg_file = 1; - } - if($update_loadavg_file) { - ::debug("load", "Updating loadavg file", $self->{'loadavg_file'}, "\n"); - $self->{'last_loadavg_update'} = time; - -e $ENV{'HOME'}."/.parallel" or mkdir $ENV{'HOME'}."/.parallel"; - -e $ENV{'HOME'}."/.parallel/tmp" or mkdir $ENV{'HOME'}."/.parallel/tmp"; - my $cmd = ""; - if($self->{'string'} ne ":") { - $cmd = $self->sshcommand() . " " . $self->serverlogin() . " "; - } - # TODO Is is called 'ps ax -o state,command' on other platforms? - $cmd .= "ps ax -o state,command"; - # As the command can take long to run if run remote - # save it to a tmp file before moving it to the correct file - my $file = $self->{'loadavg_file'}; - my ($dummy_fh, $tmpfile) = ::tmpfile(SUFFIX => ".loa"); - qx{ ($cmd > $tmpfile && mv $tmpfile $file || rm $tmpfile) & }; - } - return $self->{'loadavg'}; -} - -sub max_loadavg { - my $self = shift; - # If --load is a file it might be changed - if($Global::max_load_file) { - my $mtime = (stat($Global::max_load_file))[9]; - if($mtime > $Global::max_load_file_last_mod) { - $Global::max_load_file_last_mod = $mtime; - for my $sshlogin (values %Global::host) { - $sshlogin->set_max_loadavg(undef); - } - } - } - if(not defined $self->{'max_loadavg'}) { - $self->{'max_loadavg'} = - $self->compute_max_loadavg($opt::load); - } - ::debug("load", "max_loadavg: ", $self->string(), " ", $self->{'max_loadavg'}); - return $self->{'max_loadavg'}; -} - -sub set_max_loadavg { - my $self = shift; - $self->{'max_loadavg'} = shift; -} - -sub compute_max_loadavg { - # Parse the max loadaverage that the user asked for using --load - # Returns: - # max loadaverage - my $self = shift; - my $loadspec = shift; - my $load; - if(defined $loadspec) { - if($loadspec =~ /^\+(\d+)$/) { - # E.g. --load +2 - my $j = $1; - $load = - $self->ncpus() + $j; - } elsif ($loadspec =~ /^-(\d+)$/) { - # E.g. --load -2 - my $j = $1; - $load = - $self->ncpus() - $j; - } elsif ($loadspec =~ /^(\d+)\%$/) { - my $j = $1; - $load = - $self->ncpus() * $j / 100; - } elsif ($loadspec =~ /^(\d+(\.\d+)?)$/) { - $load = $1; - } elsif (-f $loadspec) { - $Global::max_load_file = $loadspec; - $Global::max_load_file_last_mod = (stat($Global::max_load_file))[9]; - if(open(my $in_fh, "<", $Global::max_load_file)) { - my $opt_load_file = join("",<$in_fh>); - close $in_fh; - $load = $self->compute_max_loadavg($opt_load_file); - } else { - print $Global::original_stderr "Cannot open $loadspec\n"; - ::wait_and_exit(255); - } - } else { - print $Global::original_stderr "Parsing of --load failed\n"; - ::die_usage(); - } - if($load < 0.01) { - $load = 0.01; - } - } - return $load; -} - -sub time_to_login { - my $self = shift; - return $self->{'time_to_login'}; -} - -sub set_time_to_login { - my $self = shift; - $self->{'time_to_login'} = shift; -} - -sub max_jobs_running { - my $self = shift; - if(not defined $self->{'max_jobs_running'}) { - my $nproc = $self->compute_number_of_processes($opt::jobs); - $self->set_max_jobs_running($nproc); - } - return $self->{'max_jobs_running'}; -} - -sub orig_max_jobs_running { - my $self = shift; - return $self->{'orig_max_jobs_running'}; -} - -sub compute_number_of_processes { - # Number of processes wanted and limited by system resources - # Returns: - # Number of processes - my $self = shift; - my $opt_P = shift; - my $wanted_processes = $self->user_requested_processes($opt_P); - if(not defined $wanted_processes) { - $wanted_processes = $Global::default_simultaneous_sshlogins; - } - ::debug("load", "Wanted procs: $wanted_processes\n"); - my $system_limit = - $self->processes_available_by_system_limit($wanted_processes); - ::debug("load", "Limited to procs: $system_limit\n"); - return $system_limit; -} - -sub processes_available_by_system_limit { - # If the wanted number of processes is bigger than the system limits: - # Limit them to the system limits - # Limits are: File handles, number of input lines, processes, - # and taking > 1 second to spawn 10 extra processes - # Returns: - # Number of processes - my $self = shift; - my $wanted_processes = shift; - - my $system_limit = 0; - my @jobs = (); - my $job; - my @args = (); - my $arg; - my $more_filehandles = 1; - my $max_system_proc_reached = 0; - my $slow_spawining_warning_printed = 0; - my $time = time; - my %fh; - my @children; - - # Reserve filehandles - # perl uses 7 filehandles for something? - # parallel uses 1 for memory_usage - # parallel uses 4 for ? - for my $i (1..12) { - open($fh{"init-$i"}, "<", "/dev/null"); - } - - for(1..2) { - # System process limit - my $child; - if($child = fork()) { - push (@children,$child); - $Global::unkilled_children{$child} = 1; - } elsif(defined $child) { - # The child takes one process slot - # It will be killed later - $SIG{TERM} = $Global::original_sig{TERM}; - sleep 10000000; - exit(0); - } else { - $max_system_proc_reached = 1; - } - } - my $count_jobs_already_read = $Global::JobQueue->next_seq(); - my $wait_time_for_getting_args = 0; - my $start_time = time; - while(1) { - $system_limit >= $wanted_processes and last; - not $more_filehandles and last; - $max_system_proc_reached and last; - my $before_getting_arg = time; - if($Global::semaphore or $opt::pipe) { - # Skip: No need to get args - } elsif(defined $opt::retries and $count_jobs_already_read) { - # For retries we may need to run all jobs on this sshlogin - # so include the already read jobs for this sshlogin - $count_jobs_already_read--; - } else { - if($opt::X or $opt::m) { - # The arguments may have to be re-spread over several jobslots - # So pessimistically only read one arg per jobslot - # instead of a full commandline - if($Global::JobQueue->{'commandlinequeue'}->{'arg_queue'}->empty()) { - if($Global::JobQueue->empty()) { - last; - } else { - ($job) = $Global::JobQueue->get(); - push(@jobs, $job); - } - } else { - ($arg) = $Global::JobQueue->{'commandlinequeue'}->{'arg_queue'}->get(); - push(@args, $arg); - } - } else { - # If there are no more command lines, then we have a process - # per command line, so no need to go further - $Global::JobQueue->empty() and last; - ($job) = $Global::JobQueue->get(); - push(@jobs, $job); - } - } - $wait_time_for_getting_args += time - $before_getting_arg; - $system_limit++; - - # Every simultaneous process uses 2 filehandles when grouping - # Every simultaneous process uses 2 filehandles when compressing - $more_filehandles = open($fh{$system_limit*10}, "<", "/dev/null") - && open($fh{$system_limit*10+2}, "<", "/dev/null") - && open($fh{$system_limit*10+3}, "<", "/dev/null") - && open($fh{$system_limit*10+4}, "<", "/dev/null"); - - # System process limit - my $child; - if($child = fork()) { - push (@children,$child); - $Global::unkilled_children{$child} = 1; - } elsif(defined $child) { - # The child takes one process slot - # It will be killed later - $SIG{TERM} = $Global::original_sig{TERM}; - sleep 10000000; - exit(0); - } else { - $max_system_proc_reached = 1; - } - my $forktime = time - $time - $wait_time_for_getting_args; - ::debug("run", "Time to fork $system_limit procs: $wait_time_for_getting_args ", - $forktime, - " (processes so far: ", $system_limit,")\n"); - if($system_limit > 10 and - $forktime > 1 and - $forktime > $system_limit * 0.01 - and not $slow_spawining_warning_printed) { - # It took more than 0.01 second to fork a processes on avg. - # Give the user a warning. He can press Ctrl-C if this - # sucks. - print $Global::original_stderr - ("parallel: Warning: Starting $system_limit processes took > $forktime sec.\n", - "Consider adjusting -j. Press CTRL-C to stop.\n"); - $slow_spawining_warning_printed = 1; - } - } - # Cleanup: Close the files - for (values %fh) { close $_ } - # Cleanup: Kill the children - for my $pid (@children) { - kill 9, $pid; - waitpid($pid,0); - delete $Global::unkilled_children{$pid}; - } - # Cleanup: Unget the command_lines or the @args - $Global::JobQueue->{'commandlinequeue'}->{'arg_queue'}->unget(@args); - $Global::JobQueue->unget(@jobs); - if($system_limit < $wanted_processes) { - # The system_limit is less than the wanted_processes - if($system_limit < 1 and not $Global::JobQueue->empty()) { - ::warning("Cannot spawn any jobs. Raising ulimit -u or /etc/security/limits.conf\n", - "or /proc/sys/kernel/pid_max may help.\n"); - ::wait_and_exit(255); - } - if(not $more_filehandles) { - ::warning("Only enough file handles to run ", $system_limit, " jobs in parallel.\n", - "Running 'parallel -j0 -N", $system_limit, " --pipe parallel -j0' or ", - "raising ulimit -n or /etc/security/limits.conf may help.\n"); - } - if($max_system_proc_reached) { - ::warning("Only enough available processes to run ", $system_limit, - " jobs in parallel. Raising ulimit -u or /etc/security/limits.conf\n", - "or /proc/sys/kernel/pid_max may help.\n"); - } - } - if($] == 5.008008 and $system_limit > 1000) { - # https://savannah.gnu.org/bugs/?36942 - $system_limit = 1000; - } - if($Global::JobQueue->empty()) { - $system_limit ||= 1; - } - if($self->string() ne ":" and - $system_limit > $Global::default_simultaneous_sshlogins) { - $system_limit = - $self->simultaneous_sshlogin_limit($system_limit); - } - return $system_limit; -} - -sub simultaneous_sshlogin_limit { - # Test by logging in wanted number of times simultaneously - # Returns: - # min($wanted_processes,$working_simultaneous_ssh_logins-1) - my $self = shift; - my $wanted_processes = shift; - if($self->{'time_to_login'}) { - return $wanted_processes; - } - - # Try twice because it guesses wrong sometimes - # Choose the minimal - my $ssh_limit = - ::min($self->simultaneous_sshlogin($wanted_processes), - $self->simultaneous_sshlogin($wanted_processes)); - if($ssh_limit < $wanted_processes) { - my $serverlogin = $self->serverlogin(); - ::warning("ssh to $serverlogin only allows ", - "for $ssh_limit simultaneous logins.\n", - "You may raise this by changing ", - "/etc/ssh/sshd_config:MaxStartups and MaxSessions on $serverlogin.\n", - "Using only ",$ssh_limit-1," connections ", - "to avoid race conditions.\n"); - } - # Race condition can cause problem if using all sshs. - if($ssh_limit > 1) { $ssh_limit -= 1; } - return $ssh_limit; -} - -sub simultaneous_sshlogin { - # Using $sshlogin try to see if we can do $wanted_processes - # simultaneous logins - # (ssh host echo simultaneouslogin & ssh host echo simultaneouslogin & ...)|grep simul|wc -l - # Returns: - # Number of succesful logins - my $self = shift; - my $wanted_processes = shift; - my $sshcmd = $self->sshcommand(); - my $serverlogin = $self->serverlogin(); - my $sshdelay = $opt::sshdelay ? "sleep $opt::sshdelay;" : ""; - my $cmd = "$sshdelay$sshcmd $serverlogin echo simultaneouslogin &1 &"x$wanted_processes; - ::debug("init", "Trying $wanted_processes logins at $serverlogin\n"); - open (my $simul_fh, "-|", "($cmd)|grep simultaneouslogin | wc -l") or - ::die_bug("simultaneouslogin"); - my $ssh_limit = <$simul_fh>; - close $simul_fh; - chomp $ssh_limit; - return $ssh_limit; -} - -sub set_ncpus { - my $self = shift; - $self->{'ncpus'} = shift; -} - -sub user_requested_processes { - # Parse the number of processes that the user asked for using -j - # Returns: - # the number of processes to run on this sshlogin - my $self = shift; - my $opt_P = shift; - my $processes; - if(defined $opt_P) { - if($opt_P =~ /^\+(\d+)$/) { - # E.g. -P +2 - my $j = $1; - $processes = - $self->ncpus() + $j; - } elsif ($opt_P =~ /^-(\d+)$/) { - # E.g. -P -2 - my $j = $1; - $processes = - $self->ncpus() - $j; - } elsif ($opt_P =~ /^(\d+(\.\d+)?)\%$/) { - # E.g. -P 10.5% - my $j = $1; - $processes = - $self->ncpus() * $j / 100; - } elsif ($opt_P =~ /^(\d+)$/) { - $processes = $1; - if($processes == 0) { - # -P 0 = infinity (or at least close) - $processes = $Global::infinity; - } - } elsif (-f $opt_P) { - $Global::max_procs_file = $opt_P; - $Global::max_procs_file_last_mod = (stat($Global::max_procs_file))[9]; - if(open(my $in_fh, "<", $Global::max_procs_file)) { - my $opt_P_file = join("",<$in_fh>); - close $in_fh; - $processes = $self->user_requested_processes($opt_P_file); - } else { - ::error("Cannot open $opt_P.\n"); - ::wait_and_exit(255); - } - } else { - ::error("Parsing of --jobs/-j/--max-procs/-P failed.\n"); - ::die_usage(); - } - $processes = ::ceil($processes); - } - return $processes; -} - -sub ncpus { - my $self = shift; - if(not defined $self->{'ncpus'}) { - my $sshcmd = $self->sshcommand(); - my $serverlogin = $self->serverlogin(); - if($serverlogin eq ":") { - if($opt::use_cpus_instead_of_cores) { - $self->{'ncpus'} = no_of_cpus(); - } else { - $self->{'ncpus'} = no_of_cores(); - } - } else { - my $ncpu; - my $sqe = ::shell_quote_scalar($Global::envvar); - if($opt::use_cpus_instead_of_cores) { - $ncpu = qx(echo|$sshcmd $serverlogin $sqe parallel --number-of-cpus); - } else { - ::debug("init",qq(echo|$sshcmd $serverlogin $sqe parallel --number-of-cores\n)); - $ncpu = qx(echo|$sshcmd $serverlogin $sqe parallel --number-of-cores); - } - chomp $ncpu; - if($ncpu =~ /^\s*[0-9]+\s*$/s) { - $self->{'ncpus'} = $ncpu; - } else { - ::warning("Could not figure out ", - "number of cpus on $serverlogin ($ncpu). Using 1.\n"); - $self->{'ncpus'} = 1; - } - } - } - return $self->{'ncpus'}; -} - -sub no_of_cpus { - # Returns: - # Number of physical CPUs - local $/="\n"; # If delimiter is set, then $/ will be wrong - my $no_of_cpus; - if ($^O eq 'linux') { - $no_of_cpus = no_of_cpus_gnu_linux() || no_of_cores_gnu_linux(); - } elsif ($^O eq 'freebsd') { - $no_of_cpus = no_of_cpus_freebsd(); - } elsif ($^O eq 'netbsd') { - $no_of_cpus = no_of_cpus_netbsd(); - } elsif ($^O eq 'openbsd') { - $no_of_cpus = no_of_cpus_openbsd(); - } elsif ($^O eq 'gnu') { - $no_of_cpus = no_of_cpus_hurd(); - } elsif ($^O eq 'darwin') { - $no_of_cpus = no_of_cpus_darwin(); - } elsif ($^O eq 'solaris') { - $no_of_cpus = no_of_cpus_solaris(); - } elsif ($^O eq 'aix') { - $no_of_cpus = no_of_cpus_aix(); - } elsif ($^O eq 'hpux') { - $no_of_cpus = no_of_cpus_hpux(); - } elsif ($^O eq 'nto') { - $no_of_cpus = no_of_cpus_qnx(); - } elsif ($^O eq 'svr5') { - $no_of_cpus = no_of_cpus_openserver(); - } elsif ($^O eq 'irix') { - $no_of_cpus = no_of_cpus_irix(); - } elsif ($^O eq 'dec_osf') { - $no_of_cpus = no_of_cpus_tru64(); - } else { - $no_of_cpus = (no_of_cpus_gnu_linux() - || no_of_cpus_freebsd() - || no_of_cpus_netbsd() - || no_of_cpus_openbsd() - || no_of_cpus_hurd() - || no_of_cpus_darwin() - || no_of_cpus_solaris() - || no_of_cpus_aix() - || no_of_cpus_hpux() - || no_of_cpus_qnx() - || no_of_cpus_openserver() - || no_of_cpus_irix() - || no_of_cpus_tru64() - # Number of cores is better than no guess for #CPUs - || nproc() - ); - } - if($no_of_cpus) { - chomp $no_of_cpus; - return $no_of_cpus; - } else { - ::warning("Cannot figure out number of cpus. Using 1.\n"); - return 1; - } -} - -sub no_of_cores { - # Returns: - # Number of CPU cores - local $/="\n"; # If delimiter is set, then $/ will be wrong - my $no_of_cores; - if ($^O eq 'linux') { - $no_of_cores = no_of_cores_gnu_linux(); - } elsif ($^O eq 'freebsd') { - $no_of_cores = no_of_cores_freebsd(); - } elsif ($^O eq 'netbsd') { - $no_of_cores = no_of_cores_netbsd(); - } elsif ($^O eq 'openbsd') { - $no_of_cores = no_of_cores_openbsd(); - } elsif ($^O eq 'gnu') { - $no_of_cores = no_of_cores_hurd(); - } elsif ($^O eq 'darwin') { - $no_of_cores = no_of_cores_darwin(); - } elsif ($^O eq 'solaris') { - $no_of_cores = no_of_cores_solaris(); - } elsif ($^O eq 'aix') { - $no_of_cores = no_of_cores_aix(); - } elsif ($^O eq 'hpux') { - $no_of_cores = no_of_cores_hpux(); - } elsif ($^O eq 'nto') { - $no_of_cores = no_of_cores_qnx(); - } elsif ($^O eq 'svr5') { - $no_of_cores = no_of_cores_openserver(); - } elsif ($^O eq 'irix') { - $no_of_cores = no_of_cores_irix(); - } elsif ($^O eq 'dec_osf') { - $no_of_cores = no_of_cores_tru64(); - } else { - $no_of_cores = (no_of_cores_gnu_linux() - || no_of_cores_freebsd() - || no_of_cores_netbsd() - || no_of_cores_openbsd() - || no_of_cores_hurd() - || no_of_cores_darwin() - || no_of_cores_solaris() - || no_of_cores_aix() - || no_of_cores_hpux() - || no_of_cores_qnx() - || no_of_cores_openserver() - || no_of_cores_irix() - || no_of_cores_tru64() - || nproc() - ); - } - if($no_of_cores) { - chomp $no_of_cores; - return $no_of_cores; - } else { - ::warning("Cannot figure out number of CPU cores. Using 1.\n"); - return 1; - } -} - -sub nproc { - # Returns: - # Number of cores using `nproc` - my $no_of_cores = `nproc 2>/dev/null`; - return $no_of_cores; -} - -sub no_of_cpus_gnu_linux { - # Returns: - # Number of physical CPUs on GNU/Linux - # undef if not GNU/Linux - my $no_of_cpus; - my $no_of_cores; - if(-e "/proc/cpuinfo") { - $no_of_cpus = 0; - $no_of_cores = 0; - my %seen; - open(my $in_fh, "<", "/proc/cpuinfo") || return undef; - while(<$in_fh>) { - if(/^physical id.*[:](.*)/ and not $seen{$1}++) { - $no_of_cpus++; - } - /^processor.*[:]/i and $no_of_cores++; - } - close $in_fh; - } - return ($no_of_cpus||$no_of_cores); -} - -sub no_of_cores_gnu_linux { - # Returns: - # Number of CPU cores on GNU/Linux - # undef if not GNU/Linux - my $no_of_cores; - if(-e "/proc/cpuinfo") { - $no_of_cores = 0; - open(my $in_fh, "<", "/proc/cpuinfo") || return undef; - while(<$in_fh>) { - /^processor.*[:]/i and $no_of_cores++; - } - close $in_fh; - } - return $no_of_cores; -} - -sub no_of_cpus_freebsd { - # Returns: - # Number of physical CPUs on FreeBSD - # undef if not FreeBSD - my $no_of_cpus = - (`sysctl -a dev.cpu 2>/dev/null | grep \%parent | awk '{ print \$2 }' | uniq | wc -l | awk '{ print \$1 }'` - or - `sysctl hw.ncpu 2>/dev/null | awk '{ print \$2 }'`); - chomp $no_of_cpus; - return $no_of_cpus; -} - -sub no_of_cores_freebsd { - # Returns: - # Number of CPU cores on FreeBSD - # undef if not FreeBSD - my $no_of_cores = - (`sysctl hw.ncpu 2>/dev/null | awk '{ print \$2 }'` - or - `sysctl -a hw 2>/dev/null | grep [^a-z]logicalcpu[^a-z] | awk '{ print \$2 }'`); - chomp $no_of_cores; - return $no_of_cores; -} - -sub no_of_cpus_netbsd { - # Returns: - # Number of physical CPUs on NetBSD - # undef if not NetBSD - my $no_of_cpus = `sysctl -n hw.ncpu 2>/dev/null`; - chomp $no_of_cpus; - return $no_of_cpus; -} - -sub no_of_cores_netbsd { - # Returns: - # Number of CPU cores on NetBSD - # undef if not NetBSD - my $no_of_cores = `sysctl -n hw.ncpu 2>/dev/null`; - chomp $no_of_cores; - return $no_of_cores; -} - -sub no_of_cpus_openbsd { - # Returns: - # Number of physical CPUs on OpenBSD - # undef if not OpenBSD - my $no_of_cpus = `sysctl -n hw.ncpu 2>/dev/null`; - chomp $no_of_cpus; - return $no_of_cpus; -} - -sub no_of_cores_openbsd { - # Returns: - # Number of CPU cores on OpenBSD - # undef if not OpenBSD - my $no_of_cores = `sysctl -n hw.ncpu 2>/dev/null`; - chomp $no_of_cores; - return $no_of_cores; -} - -sub no_of_cpus_hurd { - # Returns: - # Number of physical CPUs on HURD - # undef if not HURD - my $no_of_cpus = `nproc`; - chomp $no_of_cpus; - return $no_of_cpus; -} - -sub no_of_cores_hurd { - # Returns: - # Number of physical CPUs on HURD - # undef if not HURD - my $no_of_cores = `nproc`; - chomp $no_of_cores; - return $no_of_cores; -} - -sub no_of_cpus_darwin { - # Returns: - # Number of physical CPUs on Mac Darwin - # undef if not Mac Darwin - my $no_of_cpus = - (`sysctl -n hw.physicalcpu 2>/dev/null` - or - `sysctl -a hw 2>/dev/null | grep [^a-z]physicalcpu[^a-z] | awk '{ print \$2 }'`); - return $no_of_cpus; -} - -sub no_of_cores_darwin { - # Returns: - # Number of CPU cores on Mac Darwin - # undef if not Mac Darwin - my $no_of_cores = - (`sysctl -n hw.logicalcpu 2>/dev/null` - or - `sysctl -a hw 2>/dev/null | grep [^a-z]logicalcpu[^a-z] | awk '{ print \$2 }'`); - return $no_of_cores; -} - -sub no_of_cpus_solaris { - # Returns: - # Number of physical CPUs on Solaris - # undef if not Solaris - if(-x "/usr/sbin/psrinfo") { - my @psrinfo = `/usr/sbin/psrinfo`; - if($#psrinfo >= 0) { - return $#psrinfo +1; - } - } - if(-x "/usr/sbin/prtconf") { - my @prtconf = `/usr/sbin/prtconf | grep cpu..instance`; - if($#prtconf >= 0) { - return $#prtconf +1; - } - } - return undef; -} - -sub no_of_cores_solaris { - # Returns: - # Number of CPU cores on Solaris - # undef if not Solaris - if(-x "/usr/sbin/psrinfo") { - my @psrinfo = `/usr/sbin/psrinfo`; - if($#psrinfo >= 0) { - return $#psrinfo +1; - } - } - if(-x "/usr/sbin/prtconf") { - my @prtconf = `/usr/sbin/prtconf | grep cpu..instance`; - if($#prtconf >= 0) { - return $#prtconf +1; - } - } - return undef; -} - -sub no_of_cpus_aix { - # Returns: - # Number of physical CPUs on AIX - # undef if not AIX - my $no_of_cpus = 0; - if(-x "/usr/sbin/lscfg") { - open(my $in_fh, "-|", "/usr/sbin/lscfg -vs |grep proc | wc -l|tr -d ' '") - || return undef; - $no_of_cpus = <$in_fh>; - chomp ($no_of_cpus); - close $in_fh; - } - return $no_of_cpus; -} - -sub no_of_cores_aix { - # Returns: - # Number of CPU cores on AIX - # undef if not AIX - my $no_of_cores; - if(-x "/usr/bin/vmstat") { - open(my $in_fh, "-|", "/usr/bin/vmstat 1 1") || return undef; - while(<$in_fh>) { - /lcpu=([0-9]*) / and $no_of_cores = $1; - } - close $in_fh; - } - return $no_of_cores; -} - -sub no_of_cpus_hpux { - # Returns: - # Number of physical CPUs on HP-UX - # undef if not HP-UX - my $no_of_cpus = - (`/usr/bin/mpsched -s 2>&1 | grep 'Locality Domain Count' | awk '{ print \$4 }'`); - return $no_of_cpus; -} - -sub no_of_cores_hpux { - # Returns: - # Number of CPU cores on HP-UX - # undef if not HP-UX - my $no_of_cores = - (`/usr/bin/mpsched -s 2>&1 | grep 'Processor Count' | awk '{ print \$3 }'`); - return $no_of_cores; -} - -sub no_of_cpus_qnx { - # Returns: - # Number of physical CPUs on QNX - # undef if not QNX - # BUG: It is now known how to calculate this. - my $no_of_cpus = 0; - return $no_of_cpus; -} - -sub no_of_cores_qnx { - # Returns: - # Number of CPU cores on QNX - # undef if not QNX - # BUG: It is now known how to calculate this. - my $no_of_cores = 0; - return $no_of_cores; -} - -sub no_of_cpus_openserver { - # Returns: - # Number of physical CPUs on SCO OpenServer - # undef if not SCO OpenServer - my $no_of_cpus = 0; - if(-x "/usr/sbin/psrinfo") { - my @psrinfo = `/usr/sbin/psrinfo`; - if($#psrinfo >= 0) { - return $#psrinfo +1; - } - } - return $no_of_cpus; -} - -sub no_of_cores_openserver { - # Returns: - # Number of CPU cores on SCO OpenServer - # undef if not SCO OpenServer - my $no_of_cores = 0; - if(-x "/usr/sbin/psrinfo") { - my @psrinfo = `/usr/sbin/psrinfo`; - if($#psrinfo >= 0) { - return $#psrinfo +1; - } - } - return $no_of_cores; -} - -sub no_of_cpus_irix { - # Returns: - # Number of physical CPUs on IRIX - # undef if not IRIX - my $no_of_cpus = `hinv | grep HZ | grep Processor | awk '{print \$1}'`; - return $no_of_cpus; -} - -sub no_of_cores_irix { - # Returns: - # Number of CPU cores on IRIX - # undef if not IRIX - my $no_of_cores = `hinv | grep HZ | grep Processor | awk '{print \$1}'`; - return $no_of_cores; -} - -sub no_of_cpus_tru64 { - # Returns: - # Number of physical CPUs on Tru64 - # undef if not Tru64 - my $no_of_cpus = `sizer -pr`; - return $no_of_cpus; -} - -sub no_of_cores_tru64 { - # Returns: - # Number of CPU cores on Tru64 - # undef if not Tru64 - my $no_of_cores = `sizer -pr`; - return $no_of_cores; -} - -sub sshcommand { - my $self = shift; - if (not defined $self->{'sshcommand'}) { - $self->sshcommand_of_sshlogin(); - } - return $self->{'sshcommand'}; -} - -sub serverlogin { - my $self = shift; - if (not defined $self->{'serverlogin'}) { - $self->sshcommand_of_sshlogin(); - } - return $self->{'serverlogin'}; -} - -sub sshcommand_of_sshlogin { - # 'server' -> ('ssh -S /tmp/parallel-ssh-RANDOM/host-','server') - # 'user@server' -> ('ssh','user@server') - # 'myssh user@server' -> ('myssh','user@server') - # 'myssh -l user server' -> ('myssh -l user','server') - # '/usr/bin/myssh -l user server' -> ('/usr/bin/myssh -l user','server') - # Returns: - # sshcommand - defaults to 'ssh' - # login@host - my $self = shift; - my ($sshcmd, $serverlogin); - if($self->{'string'} =~ /(.+) (\S+)$/) { - # Own ssh command - $sshcmd = $1; $serverlogin = $2; - } else { - # Normal ssh - if($opt::controlmaster) { - # Use control_path to make ssh faster - my $control_path = $self->control_path_dir()."/ssh-%r@%h:%p"; - $sshcmd = "ssh -S ".$control_path; - $serverlogin = $self->{'string'}; - if(not $self->{'control_path'}{$control_path}++) { - # Master is not running for this control_path - # Start it - my $pid = fork(); - if($pid) { - $Global::sshmaster{$pid} ||= 1; - } else { - $SIG{'TERM'} = undef; - # Ignore the 'foo' being printed - open(STDOUT,">","/dev/null"); - # OpenSSH_3.6.1p2 gives 'tcgetattr: Invalid argument' with -tt - # STDERR >/dev/null to ignore "process_mux_new_session: tcgetattr: Invalid argument" - open(STDERR,">","/dev/null"); - open(STDIN,"<","/dev/null"); - # Run a sleep that outputs data, so it will discover if the ssh connection closes. - my $sleep = ::shell_quote_scalar('$|=1;while(1){sleep 1;print "foo\n"}'); - my @master = ("ssh", "-tt", "-MTS", $control_path, $serverlogin, "perl", "-e", $sleep); - exec(@master); - } - } - } else { - $sshcmd = "ssh"; $serverlogin = $self->{'string'}; - } - } - $self->{'sshcommand'} = $sshcmd; - $self->{'serverlogin'} = $serverlogin; -} - -sub control_path_dir { - # Returns: - # path to directory - my $self = shift; - if(not defined $self->{'control_path_dir'}) { - -e $ENV{'HOME'}."/.parallel" or mkdir $ENV{'HOME'}."/.parallel"; - -e $ENV{'HOME'}."/.parallel/tmp" or mkdir $ENV{'HOME'}."/.parallel/tmp"; - $self->{'control_path_dir'} = - File::Temp::tempdir($ENV{'HOME'} - . "/.parallel/tmp/control_path_dir-XXXX", - CLEANUP => 1); - } - return $self->{'control_path_dir'}; -} - -sub rsync_transfer_cmd { - # Command to run to transfer a file - # Input: - # $file = filename of file to transfer - # $workdir = destination dir - # Returns: - # $cmd = rsync command to run to transfer $file ("" if unreadable) - my $self = shift; - my $file = shift; - my $workdir = shift; - if(not -r $file) { - ::warning($file, " is not readable and will not be transferred.\n"); - return "true"; - } - my $rsync_destdir; - if($file =~ m:^/:) { - # rsync /foo/bar / - $rsync_destdir = "/"; - } else { - $rsync_destdir = ::shell_quote_file($workdir); - } - $file = ::shell_quote_file($file); - my $sshcmd = $self->sshcommand(); - my $rsync_opt = "-rlDzR -e" . ::shell_quote_scalar($sshcmd); - my $serverlogin = $self->serverlogin(); - # Make dir if it does not exist - return "( $sshcmd $serverlogin mkdir -p $rsync_destdir;" . - rsync()." $rsync_opt $file $serverlogin:$rsync_destdir )"; -} - -sub cleanup_cmd { - # Command to run to remove the remote file - # Input: - # $file = filename to remove - # $workdir = destination dir - # Returns: - # $cmd = ssh command to run to remove $file and empty parent dirs - my $self = shift; - my $file = shift; - my $workdir = shift; - my $f = $file; - if($f =~ m:/\./:) { - # foo/bar/./baz/quux => workdir/baz/quux - # /foo/bar/./baz/quux => workdir/baz/quux - $f =~ s:.*/\./:$workdir/:; - } elsif($f =~ m:^[^/]:) { - # foo/bar => workdir/foo/bar - $f = $workdir."/".$f; - } - my @subdirs = split m:/:, ::dirname($f); - my @rmdir; - my $dir = ""; - for(@subdirs) { - $dir .= $_."/"; - unshift @rmdir, ::shell_quote_file($dir); - } - my $rmdir = @rmdir ? "rmdir @rmdir 2>/dev/null;" : ""; - if(defined $opt::workdir and $opt::workdir eq "...") { - $rmdir .= "rm -rf " . ::shell_quote_file($workdir).';'; - } - - $f = ::shell_quote_file($f); - my $sshcmd = $self->sshcommand(); - my $serverlogin = $self->serverlogin(); - return "$sshcmd $serverlogin ".::shell_quote_scalar("(rm -f $f; $rmdir)"); -} - -{ - my $rsync; - - sub rsync { - # rsync 3.1.x uses protocol 31 which is unsupported by 2.5.7. - # If the version >= 3.1.0: downgrade to protocol 30 - if(not $rsync) { - my @out = `rsync --version`; - for (@out) { - if(/version (\d+.\d+)(.\d+)?/) { - if($1 >= 3.1) { - # Version 3.1.0 or later: Downgrade to protocol 30 - $rsync = "rsync --protocol 30"; - } else { - $rsync = "rsync"; - } - } - } - $rsync or ::die_bug("Cannot figure out version of rsync: @out"); - } - return $rsync; - } -} - - -package JobQueue; - -sub new { - my $class = shift; - my $commandref = shift; - my $read_from = shift; - my $context_replace = shift; - my $max_number_of_args = shift; - my $return_files = shift; - my $commandlinequeue = CommandLineQueue->new - ($commandref, $read_from, $context_replace, $max_number_of_args, - $return_files); - my @unget = (); - return bless { - 'unget' => \@unget, - 'commandlinequeue' => $commandlinequeue, - 'total_jobs' => undef, - }, ref($class) || $class; -} - -sub get { - my $self = shift; - - if(@{$self->{'unget'}}) { - my $job = shift @{$self->{'unget'}}; - return ($job); - } else { - my $commandline = $self->{'commandlinequeue'}->get(); - if(defined $commandline) { - my $job = Job->new($commandline); - return $job; - } else { - return undef; - } - } -} - -sub unget { - my $self = shift; - unshift @{$self->{'unget'}}, @_; -} - -sub empty { - my $self = shift; - my $empty = (not @{$self->{'unget'}}) - && $self->{'commandlinequeue'}->empty(); - ::debug("run", "JobQueue->empty $empty "); - return $empty; -} - -sub total_jobs { - my $self = shift; - if(not defined $self->{'total_jobs'}) { - my $job; - my @queue; - my $start = time; - while($job = $self->get()) { - if(time - $start > 10) { - ::warning("Reading all arguments takes longer than 10 seconds.\n"); - $opt::eta && ::warning("Consider removing --eta.\n"); - $opt::bar && ::warning("Consider removing --bar.\n"); - last; - } - push @queue, $job; - } - while($job = $self->get()) { - push @queue, $job; - } - - $self->unget(@queue); - $self->{'total_jobs'} = $#queue+1; - } - return $self->{'total_jobs'}; -} - -sub next_seq { - my $self = shift; - - return $self->{'commandlinequeue'}->seq(); -} - -sub quote_args { - my $self = shift; - return $self->{'commandlinequeue'}->quote_args(); -} - - -package Job; - -sub new { - my $class = shift; - my $commandlineref = shift; - return bless { - 'commandline' => $commandlineref, # CommandLine object - 'workdir' => undef, # --workdir - 'stdin' => undef, # filehandle for stdin (used for --pipe) - # filename for writing stdout to (used for --files) - 'remaining' => "", # remaining data not sent to stdin (used for --pipe) - 'datawritten' => 0, # amount of data sent via stdin (used for --pipe) - 'transfersize' => 0, # size of files using --transfer - 'returnsize' => 0, # size of files using --return - 'pid' => undef, - # hash of { SSHLogins => number of times the command failed there } - 'failed' => undef, - 'sshlogin' => undef, - # The commandline wrapped with rsync and ssh - 'sshlogin_wrap' => undef, - 'exitstatus' => undef, - 'exitsignal' => undef, - # Timestamp for timeout if any - 'timeout' => undef, - 'virgin' => 1, - }, ref($class) || $class; -} - -sub replaced { - my $self = shift; - $self->{'commandline'} or ::die_bug("commandline empty"); - return $self->{'commandline'}->replaced(); -} - -sub seq { - my $self = shift; - return $self->{'commandline'}->seq(); -} - -sub slot { - my $self = shift; - return $self->{'commandline'}->slot(); -} - -{ - my($cattail); - - sub cattail { - # Returns: - # $cattail = perl program for: cattail "decompress program" writerpid [file_to_decompress or stdin] [file_to_unlink] - if(not $cattail) { - $cattail = q{ - # cat followed by tail. - # If $writerpid dead: finish after this round - use Fcntl; - - $|=1; - - my ($cmd, $writerpid, $read_file, $unlink_file) = @ARGV; - if($read_file) { - open(IN,"<",$read_file) || die("cattail: Cannot open $read_file"); - } else { - *IN = *STDIN; - } - - my $flags; - fcntl(IN, F_GETFL, $flags) || die $!; # Get the current flags on the filehandle - $flags |= O_NONBLOCK; # Add non-blocking to the flags - fcntl(IN, F_SETFL, $flags) || die $!; # Set the flags on the filehandle - open(OUT,"|-",$cmd) || die("cattail: Cannot run $cmd"); - - while(1) { - # clear EOF - seek(IN,0,1); - my $writer_running = kill 0, $writerpid; - $read = sysread(IN,$buf,32768); - if($read) { - # We can unlink the file now: The writer has written something - -e $unlink_file and unlink $unlink_file; - # Blocking print - while($buf) { - my $bytes_written = syswrite(OUT,$buf); - # syswrite may be interrupted by SIGHUP - substr($buf,0,$bytes_written) = ""; - } - # Something printed: Wait less next time - $sleep /= 2; - } else { - if(eof(IN) and not $writer_running) { - # Writer dead: There will never be more to read => exit - exit; - } - # TODO This could probably be done more efficiently using select(2) - # Nothing read: Wait longer before next read - # Up to 30 milliseconds - $sleep = ($sleep < 30) ? ($sleep * 1.001 + 0.01) : ($sleep); - usleep($sleep); - } - } - - sub usleep { - # Sleep this many milliseconds. - my $secs = shift; - select(undef, undef, undef, $secs/1000); - } - }; - $cattail =~ s/#.*//mg; - $cattail =~ s/\s+/ /g; - } - return $cattail; - } -} - -sub openoutputfiles { - # Open files for STDOUT and STDERR - # Set file handles in $self->fh - my $self = shift; - my ($outfhw, $errfhw, $outname, $errname); - if($opt::results) { - my $args_as_dirname = $self->{'commandline'}->args_as_dirname(); - # Output in: prefix/name1/val1/name2/val2/stdout - my $dir = $opt::results."/".$args_as_dirname; - if(eval{ File::Path::mkpath($dir); }) { - # OK - } else { - # mkpath failed: Argument probably too long. - # Set $Global::max_file_length, which will keep the individual - # dir names shorter than the max length - max_file_name_length($opt::results); - $args_as_dirname = $self->{'commandline'}->args_as_dirname(); - # prefix/name1/val1/name2/val2/ - $dir = $opt::results."/".$args_as_dirname; - File::Path::mkpath($dir); - } - # prefix/name1/val1/name2/val2/stdout - $outname = "$dir/stdout"; - if(not open($outfhw, "+>", $outname)) { - ::error("Cannot write to `$outname'.\n"); - ::wait_and_exit(255); - } - # prefix/name1/val1/name2/val2/stderr - $errname = "$dir/stderr"; - if(not open($errfhw, "+>", $errname)) { - ::error("Cannot write to `$errname'.\n"); - ::wait_and_exit(255); - } - $self->set_fh(1,"unlink",""); - $self->set_fh(2,"unlink",""); - } elsif(not $opt::ungroup) { - # To group we create temporary files for STDOUT and STDERR - # To avoid the cleanup unlink the files immediately (but keep them open) - if(@Global::tee_jobs) { - # files must be removed when the tee is done - } elsif($opt::files) { - ($outfhw, $outname) = ::tmpfile(SUFFIX => ".par"); - ($errfhw, $errname) = ::tmpfile(SUFFIX => ".par"); - # --files => only remove stderr - $self->set_fh(1,"unlink",""); - $self->set_fh(2,"unlink",$errname); - } else { - ($outfhw, $outname) = ::tmpfile(SUFFIX => ".par"); - ($errfhw, $errname) = ::tmpfile(SUFFIX => ".par"); - $self->set_fh(1,"unlink",$outname); - $self->set_fh(2,"unlink",$errname); - } - } else { - # --ungroup - open($outfhw,">&",$Global::fd{1}) || die; - open($errfhw,">&",$Global::fd{2}) || die; - # File name must be empty as it will otherwise be printed - $outname = ""; - $errname = ""; - $self->set_fh(1,"unlink",$outname); - $self->set_fh(2,"unlink",$errname); - } - # Set writing FD - $self->set_fh(1,'w',$outfhw); - $self->set_fh(2,'w',$errfhw); - $self->set_fh(1,'name',$outname); - $self->set_fh(2,'name',$errname); - if($opt::compress) { - # Send stdout to stdin for $opt::compress_program(1) - # Send stderr to stdin for $opt::compress_program(2) - # cattail get pid: $pid = $self->fh($fdno,'rpid'); - my $cattail = cattail(); - for my $fdno (1,2) { - my $wpid = open(my $fdw,"|-","$opt::compress_program >>". - $self->fh($fdno,'name')) || die $?; - $self->set_fh($fdno,'w',$fdw); - $self->set_fh($fdno,'wpid',$wpid); - my $rpid = open(my $fdr, "-|", "perl", "-e", $cattail, - $opt::decompress_program, $wpid, - $self->fh($fdno,'name'),$self->fh($fdno,'unlink')) || die $?; - $self->set_fh($fdno,'r',$fdr); - $self->set_fh($fdno,'rpid',$rpid); - } - } elsif(not $opt::ungroup) { - # Set reading FD if using --group (--ungroup does not need) - for my $fdno (1,2) { - # Re-open the file for reading - # so fdw can be closed separately - # and fdr can be seeked separately (for --line-buffer) - open(my $fdr,"<", $self->fh($fdno,'name')) || - ::die_bug("fdr: Cannot open ".$self->fh($fdno,'name')); - $self->set_fh($fdno,'r',$fdr); - # Unlink if required - $Global::debug or unlink $self->fh($fdno,"unlink"); - } - } - if($opt::linebuffer) { - # Set non-blocking when using --linebuffer - $Global::use{"Fcntl"} ||= eval "use Fcntl qw(:DEFAULT :flock); 1;"; - for my $fdno (1,2) { - my $fdr = $self->fh($fdno,'r'); - my $flags; - fcntl($fdr, &F_GETFL, $flags) || die $!; # Get the current flags on the filehandle - $flags |= &O_NONBLOCK; # Add non-blocking to the flags - fcntl($fdr, &F_SETFL, $flags) || die $!; # Set the flags on the filehandle - } - } -} - -sub max_file_name_length { - # Figure out the max length of a subdir - # TODO and the max total length - # Ext4 = 255,130816 - my $testdir = shift; - - my $upper = 8_000_000; - my $len = 8; - my $dir="x"x$len; - do { - rmdir($testdir."/".$dir); - $len *= 16; - $dir="x"x$len; - } while (mkdir $testdir."/".$dir); - # Then search for the actual max length between $len/16 and $len - my $min = $len/16; - my $max = $len; - while($max-$min > 5) { - # If we are within 5 chars of the exact value: - # it is not worth the extra time to find the exact value - my $test = int(($min+$max)/2); - $dir="x"x$test; - if(mkdir $testdir."/".$dir) { - rmdir($testdir."/".$dir); - $min = $test; - } else { - $max = $test; - } - } - $Global::max_file_length = $min; - return $min; -} - -sub set_fh { - # Set file handle - my ($self, $fd_no, $key, $fh) = @_; - $self->{'fd'}{$fd_no,$key} = $fh; -} - -sub fh { - # Get file handle - my ($self, $fd_no, $key) = @_; - return $self->{'fd'}{$fd_no,$key}; -} - -sub write { - my $self = shift; - my $remaining_ref = shift; - my $stdin_fh = $self->fh(0,"w"); - syswrite($stdin_fh,$$remaining_ref); -} - -sub set_stdin_buffer { - # Copy stdin buffer from $block_ref up to $endpos - # Prepend with $header_ref - # Remove $recstart and $recend if needed - # Input: - # $header_ref = ref to $header to prepend - # $block_ref = ref to $block to pass on - # $endpos = length of $block to pass on - # $recstart = --recstart regexp - # $recend = --recend regexp - # Returns: - # N/A - my $self = shift; - my ($header_ref,$block_ref,$endpos,$recstart,$recend) = @_; - $self->{'stdin_buffer'} = ($self->virgin() ? $$header_ref : "").substr($$block_ref,0,$endpos); - if($opt::remove_rec_sep) { - remove_rec_sep(\$self->{'stdin_buffer'},$recstart,$recend); - } - $self->{'stdin_buffer_length'} = length $self->{'stdin_buffer'}; - $self->{'stdin_buffer_pos'} = 0; -} - -sub stdin_buffer_length { - my $self = shift; - return $self->{'stdin_buffer_length'}; -} - -sub remove_rec_sep { - my ($block_ref,$recstart,$recend) = @_; - # Remove record separator - $$block_ref =~ s/$recend$recstart//gos; - $$block_ref =~ s/^$recstart//os; - $$block_ref =~ s/$recend$//os; -} - -sub non_block_write { - my $self = shift; - my $something_written = 0; - use POSIX qw(:errno_h); -# use Fcntl; -# my $flags = ''; - for my $buf (substr($self->{'stdin_buffer'},$self->{'stdin_buffer_pos'})) { - my $in = $self->fh(0,"w"); -# fcntl($in, F_GETFL, $flags) -# or die "Couldn't get flags for HANDLE : $!\n"; -# $flags |= O_NONBLOCK; -# fcntl($in, F_SETFL, $flags) -# or die "Couldn't set flags for HANDLE: $!\n"; - my $rv = syswrite($in, $buf); - if (!defined($rv) && $! == EAGAIN) { - # would block - $something_written = 0; - } elsif ($self->{'stdin_buffer_pos'}+$rv != $self->{'stdin_buffer_length'}) { - # incomplete write - # Remove the written part - $self->{'stdin_buffer_pos'} += $rv; - $something_written = $rv; - } else { - # successfully wrote everything - my $a=""; - $self->set_stdin_buffer(\$a,\$a,"",""); - $something_written = $rv; - } - } - - ::debug("pipe", "Non-block: ", $something_written); - return $something_written; -} - - -sub virgin { - my $self = shift; - return $self->{'virgin'}; -} - -sub set_virgin { - my $self = shift; - $self->{'virgin'} = shift; -} - -sub pid { - my $self = shift; - return $self->{'pid'}; -} - -sub set_pid { - my $self = shift; - $self->{'pid'} = shift; -} - -sub starttime { - # Returns: - # UNIX-timestamp this job started - my $self = shift; - return sprintf("%.3f",$self->{'starttime'}); -} - -sub set_starttime { - my $self = shift; - my $starttime = shift || ::now(); - $self->{'starttime'} = $starttime; -} - -sub runtime { - # Returns: - # Run time in seconds - my $self = shift; - return sprintf("%.3f",int(($self->endtime() - $self->starttime())*1000)/1000); -} - -sub endtime { - # Returns: - # UNIX-timestamp this job ended - # 0 if not ended yet - my $self = shift; - return ($self->{'endtime'} || 0); -} - -sub set_endtime { - my $self = shift; - my $endtime = shift; - $self->{'endtime'} = $endtime; -} - -sub timedout { - # Is the job timedout? - # Input: - # $delta_time = time that the job may run - # Returns: - # True or false - my $self = shift; - my $delta_time = shift; - return time > $self->{'starttime'} + $delta_time; -} - -sub kill { - # Kill the job. - # Send the signals to (grand)*children and pid. - # If no signals: TERM TERM KILL - # Wait 200 ms after each TERM. - # Input: - # @signals = signals to send - my $self = shift; - my @signals = @_; - my @family_pids = $self->family_pids(); - # Record this jobs as failed - $self->set_exitstatus(-1); - # Send two TERMs to give time to clean up - ::debug("run", "Kill seq ", $self->seq(), "\n"); - my @send_signals = @signals || ("TERM", "TERM", "KILL"); - for my $signal (@send_signals) { - my $alive = 0; - for my $pid (@family_pids) { - if(kill 0, $pid) { - # The job still running - kill $signal, $pid; - $alive = 1; - } - } - # If a signal was given as input, do not do the sleep below - @signals and next; - - if($signal eq "TERM" and $alive) { - # Wait up to 200 ms between TERMs - but only if any pids are alive - my $sleep = 1; - for (my $sleepsum = 0; kill 0, $family_pids[0] and $sleepsum < 200; - $sleepsum += $sleep) { - $sleep = ::reap_usleep($sleep); - } - } - } -} - -sub family_pids { - # Find the pids with this->pid as (grand)*parent - # Returns: - # @pids = pids of (grand)*children - my $self = shift; - my $pid = $self->pid(); - my @pids; - - my ($children_of_ref, $parent_of_ref, $name_of_ref) = ::pid_table(); - - my @more = ($pid); - # While more (grand)*children - while(@more) { - my @m; - push @pids, @more; - for my $parent (@more) { - if($children_of_ref->{$parent}) { - # add the children of this parent - push @m, @{$children_of_ref->{$parent}}; - } - } - @more = @m; - } - return (@pids); -} - -sub failed { - # return number of times failed for this $sshlogin - # Input: - # $sshlogin - # Returns: - # Number of times failed for $sshlogin - my $self = shift; - my $sshlogin = shift; - return $self->{'failed'}{$sshlogin}; -} - -sub failed_here { - # return number of times failed for the current $sshlogin - # Returns: - # Number of times failed for this sshlogin - my $self = shift; - return $self->{'failed'}{$self->sshlogin()}; -} - -sub add_failed { - # increase the number of times failed for this $sshlogin - my $self = shift; - my $sshlogin = shift; - $self->{'failed'}{$sshlogin}++; -} - -sub add_failed_here { - # increase the number of times failed for the current $sshlogin - my $self = shift; - $self->{'failed'}{$self->sshlogin()}++; -} - -sub reset_failed { - # increase the number of times failed for this $sshlogin - my $self = shift; - my $sshlogin = shift; - delete $self->{'failed'}{$sshlogin}; -} - -sub reset_failed_here { - # increase the number of times failed for this $sshlogin - my $self = shift; - delete $self->{'failed'}{$self->sshlogin()}; -} - -sub min_failed { - # Returns: - # the number of sshlogins this command has failed on - # the minimal number of times this command has failed - my $self = shift; - my $min_failures = - ::min(map { $self->{'failed'}{$_} } keys %{$self->{'failed'}}); - my $number_of_sshlogins_failed_on = scalar keys %{$self->{'failed'}}; - return ($number_of_sshlogins_failed_on,$min_failures); -} - -sub total_failed { - # Returns: - # $total_failures = the number of times this command has failed - my $self = shift; - my $total_failures = 0; - for (values %{$self->{'failed'}}) { - $total_failures += $_; - } - return $total_failures; -} - -sub wrapped { - # Wrap command with: - # * --shellquote - # * --nice - # * --cat - # * --fifo - # * --sshlogin - # * --pipepart (@Global::cat_partials) - # * --pipe - # * --tmux - # The ordering of the wrapping is important: - # * --nice/--cat/--fifo should be done on the remote machine - # * --pipepart/--pipe should be done on the local machine inside --tmux - # Uses: - # $Global::envvar - # $opt::shellquote - # $opt::nice - # $Global::shell - # $opt::cat - # $opt::fifo - # @Global::cat_partials - # $opt::pipe - # $opt::tmux - # Returns: - # $self->{'wrapped'} = the command wrapped with the above - my $self = shift; - if(not defined $self->{'wrapped'}) { - my $command = $Global::envvar.$self->replaced(); - if($opt::shellquote) { - # Prepend echo - # and quote twice - $command = "echo " . - ::shell_quote_scalar(::shell_quote_scalar($command)); - } - if($opt::nice) { - # Prepend \nice -n19 $SHELL -c - # and quote. - # The '\' before nice is needed to avoid tcsh's built-in - $command = '\nice'. " -n". $opt::nice. " ". - $Global::shell. " -c ". - ::shell_quote_scalar($command); - } - if($opt::cat) { - # Prepend 'cat > {};' - # Append '_EXIT=$?;(rm {};exit $_EXIT)' - $command = - $self->{'commandline'}->replace_placeholders(["cat > \257<\257>; "], 0, 0). - $command. - $self->{'commandline'}->replace_placeholders( - ["; _EXIT=\$?; rm \257<\257>; exit \$_EXIT"], 0, 0); - } elsif($opt::fifo) { - # Prepend 'mkfifo {}; (' - # Append ') & _PID=$!; cat > {}; wait $_PID; _EXIT=$?;(rm {};exit $_EXIT)' - $command = - $self->{'commandline'}->replace_placeholders(["mkfifo \257<\257>; ("], 0, 0). - $command. - $self->{'commandline'}->replace_placeholders([") & _PID=\$!; cat > \257<\257>; ", - "wait \$_PID; _EXIT=\$?; ", - "rm \257<\257>; exit \$_EXIT"], - 0,0); - } - # Wrap with ssh + tranferring of files - $command = $self->sshlogin_wrap($command); - if(@Global::cat_partials) { - # Prepend: - # < /tmp/foo perl -e 'while(@ARGV) { sysseek(STDIN,shift,0) || die; $left = shift; while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ $left -= $read; syswrite(STDOUT,$buf); } }' 0 0 0 11 | - $command = (shift @Global::cat_partials). "|". "(". $command. ")"; - } elsif($opt::pipe) { - # Prepend EOF-detector to avoid starting $command if EOF. - # The $tmpfile might exist if run on a remote system - we accept that risk - my ($dummy_fh, $tmpfile) = ::tmpfile(SUFFIX => ".chr"); - # Unlink to avoid leaving files if --dry-run or --sshlogin - unlink $tmpfile; - $command = - # Exit value: - # empty input = true - # some input = exit val from command - qq{ sh -c 'dd bs=1 count=1 of=$tmpfile 2>/dev/null'; }. - qq{ test \! -s "$tmpfile" && rm -f "$tmpfile" && exec true; }. - qq{ (cat $tmpfile; rm $tmpfile; cat - ) | }. - "($command);"; - } - if($opt::tmux) { - # Wrap command with 'tmux' - $command = $self->tmux_wrap($command); - } - $self->{'wrapped'} = $command; - } - return $self->{'wrapped'}; -} - -sub set_sshlogin { - my $self = shift; - my $sshlogin = shift; - $self->{'sshlogin'} = $sshlogin; - delete $self->{'sshlogin_wrap'}; # If sshlogin is changed the wrap is wrong - delete $self->{'wrapped'}; -} - -sub sshlogin { - my $self = shift; - return $self->{'sshlogin'}; -} - -sub sshlogin_wrap { - # Wrap the command with the commands needed to run remotely - # Returns: - # $self->{'sshlogin_wrap'} = command wrapped with ssh+transfer commands - my $self = shift; - my $command = shift; - if(not defined $self->{'sshlogin_wrap'}) { - my $sshlogin = $self->sshlogin(); - my $sshcmd = $sshlogin->sshcommand(); - my $serverlogin = $sshlogin->serverlogin(); - my ($pre,$post,$cleanup)=("","",""); - - if($serverlogin eq ":") { - # No transfer neeeded - $self->{'sshlogin_wrap'} = $command; - } else { - # --transfer - $pre .= $self->sshtransfer(); - # --return - $post .= $self->sshreturn(); - # --cleanup - $post .= $self->sshcleanup(); - if($post) { - # We need to save the exit status of the job - $post = '_EXIT_status=$?; ' . $post . ' exit $_EXIT_status;'; - } - # If the remote login shell is (t)csh then use 'setenv' - # otherwise use 'export' - # We cannot use parse_env_var(), as PARALLEL_SEQ changes - # for each command - my $parallel_env = - ($Global::envwarn - . q{ 'eval `echo $SHELL | grep "/t\\{0,1\\}csh" > /dev/null } - . q{ && echo setenv PARALLEL_SEQ '$PARALLEL_SEQ'\; } - . q{ setenv PARALLEL_PID '$PARALLEL_PID' } - . q{ || echo PARALLEL_SEQ='$PARALLEL_SEQ'\;export PARALLEL_SEQ\; } - . q{ PARALLEL_PID='$PARALLEL_PID'\;export PARALLEL_PID` ;' }); - my $remote_pre = ""; - my $ssh_options = ""; - if(($opt::pipe or $opt::pipepart) and $opt::ctrlc - or - not ($opt::pipe or $opt::pipepart) and not $opt::noctrlc) { - # TODO Determine if this is needed - # Propagating CTRL-C to kill remote jobs requires - # remote jobs to be run with a terminal. - $ssh_options = "-tt -oLogLevel=quiet"; -# $ssh_options = ""; - # tty - check if we have a tty. - # stty: - # -onlcr - make output 8-bit clean - # isig - pass CTRL-C as signal - # -echo - do not echo input - $remote_pre .= ::shell_quote_scalar('tty >/dev/null && stty isig -onlcr -echo;'); - } - if($opt::workdir) { - my $wd = ::shell_quote_file($self->workdir()); - $remote_pre .= ::shell_quote_scalar("mkdir -p ") . $wd . - ::shell_quote_scalar("; cd ") . $wd . - # exit 255 (instead of exec false) would be the correct thing, - # but that fails on tcsh - ::shell_quote_scalar(qq{ || exec false;}); - } - # This script is to solve the problem of - # * not mixing STDERR and STDOUT - # * terminating with ctrl-c - # It works on Linux but not Solaris - # Finishes on Solaris, but wrong exit code: - # $SIG{CHLD} = sub {exit ($?&127 ? 128+($?&127) : 1+$?>>8)}; - # Hangs on Solaris, but correct exit code on Linux: - # $SIG{CHLD} = sub { $done = 1 }; - # $p->poll; - my $signal_script = "perl -e '". - q{ - use IO::Poll; - $SIG{CHLD} = sub { $done = 1 }; - $p = IO::Poll->new; - $p->mask(STDOUT, POLLHUP); - $pid=fork; unless($pid) {setpgrp; exec $ENV{SHELL}, "-c", @ARGV; die "exec: $!\n"} - $p->poll; - kill SIGHUP, -${pid} unless $done; - wait; exit ($?&127 ? 128+($?&127) : 1+$?>>8) - } . "' "; - $signal_script =~ s/\s+/ /g; - - $self->{'sshlogin_wrap'} = - ($pre - . "$sshcmd $ssh_options $serverlogin $parallel_env " - . $remote_pre -# . ::shell_quote_scalar($signal_script . ::shell_quote_scalar($command)) - . ::shell_quote_scalar($command) - . ";" - . $post); - } - } - return $self->{'sshlogin_wrap'}; -} - -sub transfer { - # Files to transfer - # Returns: - # @transfer - File names of files to transfer - my $self = shift; - my @transfer = (); - $self->{'transfersize'} = 0; - if($opt::transfer) { - for my $record (@{$self->{'commandline'}{'arg_list'}}) { - # Merge arguments from records into args - for my $arg (@$record) { - CORE::push @transfer, $arg->orig(); - # filesize - if(-e $arg->orig()) { - $self->{'transfersize'} += (stat($arg->orig()))[7]; - } - } - } - } - return @transfer; -} - -sub transfersize { - my $self = shift; - return $self->{'transfersize'}; -} - -sub sshtransfer { - # Returns for each transfer file: - # rsync $file remote:$workdir - my $self = shift; - my @pre; - my $sshlogin = $self->sshlogin(); - my $workdir = $self->workdir(); - for my $file ($self->transfer()) { - push @pre, $sshlogin->rsync_transfer_cmd($file,$workdir).";"; - } - return join("",@pre); -} - -sub return { - # Files to return - # Non-quoted and with {...} substituted - # Returns: - # @non_quoted_filenames - my $self = shift; - return $self->{'commandline'}-> - replace_placeholders($self->{'commandline'}{'return_files'},0,0); -} - -sub returnsize { - # This is called after the job has finished - # Returns: - # $number_of_bytes transferred in return - my $self = shift; - for my $file ($self->return()) { - if(-e $file) { - $self->{'returnsize'} += (stat($file))[7]; - } - } - return $self->{'returnsize'}; -} - -sub sshreturn { - # Returns for each return-file: - # rsync remote:$workdir/$file . - my $self = shift; - my $sshlogin = $self->sshlogin(); - my $sshcmd = $sshlogin->sshcommand(); - my $serverlogin = $sshlogin->serverlogin(); - my $rsync_opt = "-rlDzR -e".::shell_quote_scalar($sshcmd); - my $pre = ""; - for my $file ($self->return()) { - $file =~ s:^\./::g; # Remove ./ if any - my $relpath = ($file !~ m:^/:); # Is the path relative? - my $cd = ""; - my $wd = ""; - if($relpath) { - # rsync -avR /foo/./bar/baz.c remote:/tmp/ - # == (on old systems) - # rsync -avR --rsync-path="cd /foo; rsync" remote:bar/baz.c /tmp/ - $wd = ::shell_quote_file($self->workdir()."/"); - } - # Only load File::Basename if actually needed - $Global::use{"File::Basename"} ||= eval "use File::Basename; 1;"; - # dir/./file means relative to dir, so remove dir on remote - $file =~ m:(.*)/\./:; - my $basedir = $1 ? ::shell_quote_file($1."/") : ""; - my $nobasedir = $file; - $nobasedir =~ s:.*/\./::; - $cd = ::shell_quote_file(::dirname($nobasedir)); - my $rsync_cd = '--rsync-path='.::shell_quote_scalar("cd $wd$cd; rsync"); - my $basename = ::shell_quote_scalar(::shell_quote_file(basename($file))); - # --return - # mkdir -p /home/tange/dir/subdir/; - # rsync (--protocol 30) -rlDzR --rsync-path="cd /home/tange/dir/subdir/; rsync" - # server:file.gz /home/tange/dir/subdir/ - $pre .= "mkdir -p $basedir$cd; ".$sshlogin->rsync()." $rsync_cd $rsync_opt $serverlogin:". - $basename . " ".$basedir.$cd.";"; - } - return $pre; -} - -sub sshcleanup { - # Return the sshcommand needed to remove the file - # Returns: - # ssh command needed to remove files from sshlogin - my $self = shift; - my $sshlogin = $self->sshlogin(); - my $sshcmd = $sshlogin->sshcommand(); - my $serverlogin = $sshlogin->serverlogin(); - my $workdir = $self->workdir(); - my $cleancmd = ""; - - for my $file ($self->cleanup()) { - my @subworkdirs = parentdirs_of($file); - $cleancmd .= $sshlogin->cleanup_cmd($file,$workdir).";"; - } - if(defined $opt::workdir and $opt::workdir eq "...") { - $cleancmd .= "$sshcmd $serverlogin rm -rf " . ::shell_quote_scalar($workdir).';'; - } - return $cleancmd; -} - -sub cleanup { - # Returns: - # Files to remove at cleanup - my $self = shift; - if($opt::cleanup) { - my @transfer = $self->transfer(); - my @return = $self->return(); - return (@transfer,@return); - } else { - return (); - } -} - -sub workdir { - # Returns: - # the workdir on a remote machine - my $self = shift; - if(not defined $self->{'workdir'}) { - my $workdir; - if(defined $opt::workdir) { - if($opt::workdir eq ".") { - # . means current dir - my $home = $ENV{'HOME'}; - eval 'use Cwd'; - my $cwd = cwd(); - $workdir = $cwd; - if($home) { - # If homedir exists: remove the homedir from - # workdir if cwd starts with homedir - # E.g. /home/foo/my/dir => my/dir - # E.g. /tmp/my/dir => /tmp/my/dir - my ($home_dev, $home_ino) = (stat($home))[0,1]; - my $parent = ""; - my @dir_parts = split(m:/:,$cwd); - my $part; - while(defined ($part = shift @dir_parts)) { - $part eq "" and next; - $parent .= "/".$part; - my ($parent_dev, $parent_ino) = (stat($parent))[0,1]; - if($parent_dev == $home_dev and $parent_ino == $home_ino) { - # dev and ino is the same: We found the homedir. - $workdir = join("/",@dir_parts); - last; - } - } - } - if($workdir eq "") { - $workdir = "."; - } - } elsif($opt::workdir eq "...") { - $workdir = ".parallel/tmp/" . ::hostname() . "-" . $$ - . "-" . $self->seq(); - } else { - $workdir = $opt::workdir; - # Rsync treats /./ special. We don't want that - $workdir =~ s:/\./:/:g; # Remove /./ - $workdir =~ s:/+$::; # Remove ending / if any - $workdir =~ s:^\./::g; # Remove starting ./ if any - } - } else { - $workdir = "."; - } - $self->{'workdir'} = ::shell_quote_scalar($workdir); - } - return $self->{'workdir'}; -} - -sub parentdirs_of { - # Return: - # all parentdirs except . of this dir or file - sorted desc by length - my $d = shift; - my @parents = (); - while($d =~ s:/[^/]+$::) { - if($d ne ".") { - push @parents, $d; - } - } - return @parents; -} - -sub start { - # Setup STDOUT and STDERR for a job and start it. - # Returns: - # job-object or undef if job not to run - my $job = shift; - # Get the shell command to be executed (possibly with ssh infront). - my $command = $job->wrapped(); - - if($Global::interactive or $Global::stderr_verbose) { - if($Global::interactive) { - print $Global::original_stderr "$command ?..."; - open(my $tty_fh, "<", "/dev/tty") || ::die_bug("interactive-tty"); - my $answer = <$tty_fh>; - close $tty_fh; - my $run_yes = ($answer =~ /^\s*y/i); - if (not $run_yes) { - $command = "true"; # Run the command 'true' - } - } else { - print $Global::original_stderr "$command\n"; - } - } - - my $pid; - $job->openoutputfiles(); - my($stdout_fh,$stderr_fh) = ($job->fh(1,"w"),$job->fh(2,"w")); - local (*IN,*OUT,*ERR); - open OUT, '>&', $stdout_fh or ::die_bug("Can't redirect STDOUT: $!"); - open ERR, '>&', $stderr_fh or ::die_bug("Can't dup STDOUT: $!"); - - if(($opt::dryrun or $Global::verbose) and $opt::ungroup) { - if($Global::verbose <= 1) { - print $stdout_fh $job->replaced(),"\n"; - } else { - # Verbose level > 1: Print the rsync and stuff - print $stdout_fh $command,"\n"; - } - } - if($opt::dryrun) { - $command = "true"; - } - $ENV{'PARALLEL_SEQ'} = $job->seq(); - $ENV{'PARALLEL_PID'} = $$; - ::debug("run", $Global::total_running, " processes . Starting (", - $job->seq(), "): $command\n"); - if($opt::pipe) { - my ($stdin_fh); - # The eval is needed to catch exception from open3 - eval { - $pid = ::open3($stdin_fh, ">&OUT", ">&ERR", $Global::shell, "-c", $command) || - ::die_bug("open3-pipe"); - 1; - }; - $job->set_fh(0,"w",$stdin_fh); - } elsif(@opt::a and not $Global::stdin_in_opt_a and $job->seq() == 1 - and $job->sshlogin()->string() eq ":") { - # Give STDIN to the first job if using -a (but only if running - # locally - otherwise CTRL-C does not work for other jobs Bug#36585) - *IN = *STDIN; - # The eval is needed to catch exception from open3 - eval { - $pid = ::open3("<&IN", ">&OUT", ">&ERR", $Global::shell, "-c", $command) || - ::die_bug("open3-a"); - 1; - }; - # Re-open to avoid complaining - open(STDIN, "<&", $Global::original_stdin) - or ::die_bug("dup-\$Global::original_stdin: $!"); - } elsif ($opt::tty and not $Global::tty_taken and -c "/dev/tty" and - open(my $devtty_fh, "<", "/dev/tty")) { - # Give /dev/tty to the command if no one else is using it - *IN = $devtty_fh; - # The eval is needed to catch exception from open3 - eval { - $pid = ::open3("<&IN", ">&OUT", ">&ERR", $Global::shell, "-c", $command) || - ::die_bug("open3-/dev/tty"); - $Global::tty_taken = $pid; - close $devtty_fh; - 1; - }; - } else { - # The eval is needed to catch exception from open3 - eval { - $pid = ::open3(::gensym, ">&OUT", ">&ERR", $Global::shell, "-c", $command) || - ::die_bug("open3-gensym"); - 1; - }; - } - if($pid) { - # A job was started - $Global::total_running++; - $Global::total_started++; - $job->set_pid($pid); - $job->set_starttime(); - $Global::running{$job->pid()} = $job; - if($opt::timeout) { - $Global::timeoutq->insert($job); - } - $Global::newest_job = $job; - $Global::newest_starttime = ::now(); - return $job; - } else { - # No more processes - ::debug("run", "Cannot spawn more jobs.\n"); - return undef; - } -} - -sub tmux_wrap { - # Wrap command with tmux for session pPID - # Input: - # $actual_command = the actual command being run (incl ssh wrap) - my $self = shift; - my $actual_command = shift; - # Temporary file name. Used for fifo to communicate exit val - my ($fh, $tmpfile) = ::tmpfile(SUFFIX => ".tmx"); - $Global::unlink{$tmpfile}=1; - close $fh; - unlink $tmpfile; - my $visual_command = $self->replaced(); - my $title = $visual_command; - # ; causes problems - # ascii 194-245 annoys tmux - $title =~ tr/[\011-\016;\302-\365]//d; - - my $tmux; - if($Global::total_running == 0) { - $tmux = "tmux new-session -s p$$ -d -n ". - ::shell_quote_scalar($title); - print $Global::original_stderr "See output with: tmux attach -t p$$\n"; - } else { - $tmux = "tmux new-window -t p$$ -n ".::shell_quote_scalar($title); - } - return "mkfifo $tmpfile; $tmux ". - # Run in tmux - ::shell_quote_scalar( - "(".$actual_command.');(echo $?$status;echo 255) >'.$tmpfile."&". - "echo ".::shell_quote_scalar($visual_command).";". - "echo \007Job finished at: `date`;sleep 10"). - # Run outside tmux - # Read the first line from the fifo and use that as status code - "; exit `perl -ne 'unlink \$ARGV; 1..1 and print' $tmpfile` "; -} - -sub is_already_in_results { - # Do we already have results for this job? - # Returns: - # $job_already_run = bool whether there is output for this or not - my $job = $_[0]; - my $args_as_dirname = $job->{'commandline'}->args_as_dirname(); - # prefix/name1/val1/name2/val2/ - my $dir = $opt::results."/".$args_as_dirname; - ::debug("run", "Test $dir/stdout", -e "$dir/stdout", "\n"); - return -e "$dir/stdout"; -} - -sub is_already_in_joblog { - my $job = shift; - return vec($Global::job_already_run,$job->seq(),1); -} - -sub set_job_in_joblog { - my $job = shift; - vec($Global::job_already_run,$job->seq(),1) = 1; -} - -sub should_be_retried { - # Should this job be retried? - # Returns - # 0 - do not retry - # 1 - job queued for retry - my $self = shift; - if (not $opt::retries) { - return 0; - } - if(not $self->exitstatus()) { - # Completed with success. If there is a recorded failure: forget it - $self->reset_failed_here(); - return 0 - } else { - # The job failed. Should it be retried? - $self->add_failed_here(); - if($self->total_failed() == $opt::retries) { - # This has been retried enough - return 0; - } else { - # This command should be retried - $self->set_endtime(undef); - $Global::JobQueue->unget($self); - ::debug("run", "Retry ", $self->seq(), "\n"); - return 1; - } - } -} - -sub print { - # Print the output of the jobs - # Returns: N/A - - my $self = shift; - ::debug("print", ">>joboutput ", $self->replaced(), "\n"); - if($opt::dryrun) { - # Nothing was printed to this job: - # cleanup tmp files if --files was set - unlink $self->fh(1,"name"); - } - if($opt::pipe and $self->virgin()) { - # Skip --joblog, --dryrun, --verbose - } else { - if($Global::joblog and defined $self->{'exitstatus'}) { - # Add to joblog when finished - $self->print_joblog(); - } - - # Printing is only relevant for grouped/--line-buffer output. - $opt::ungroup and return; - # Check for disk full - exit_if_disk_full(); - - if(($opt::dryrun or $Global::verbose) - and - not $self->{'verbose_printed'}) { - $self->{'verbose_printed'}++; - if($Global::verbose <= 1) { - print STDOUT $self->replaced(),"\n"; - } else { - # Verbose level > 1: Print the rsync and stuff - print STDOUT $self->wrapped(),"\n"; - } - # If STDOUT and STDERR are merged, - # we want the command to be printed first - # so flush to avoid STDOUT being buffered - flush STDOUT; - } - } - for my $fdno (sort { $a <=> $b } keys %Global::fd) { - # Sort by file descriptor numerically: 1,2,3,..,9,10,11 - $fdno == 0 and next; - my $out_fd = $Global::fd{$fdno}; - my $in_fh = $self->fh($fdno,"r"); - if(not $in_fh) { - if(not $Job::file_descriptor_warning_printed{$fdno}++) { - # ::warning("File descriptor $fdno not defined\n"); - } - next; - } - ::debug("print", "File descriptor $fdno (", $self->fh($fdno,"name"), "):"); - if($opt::files) { - # If --compress: $in_fh must be closed first. - close $self->fh($fdno,"w"); - close $in_fh; - if($opt::pipe and $self->virgin()) { - # Nothing was printed to this job: - # cleanup unused tmp files if --files was set - for my $fdno (1,2) { - unlink $self->fh($fdno,"name"); - unlink $self->fh($fdno,"unlink"); - } - } elsif($fdno == 1 and $self->fh($fdno,"name")) { - print $out_fd $self->fh($fdno,"name"),"\n"; - } - } elsif($opt::linebuffer) { - # Line buffered print out - $self->linebuffer_print($fdno,$in_fh,$out_fd); - } else { - my $buf; - close $self->fh($fdno,"w"); - seek $in_fh, 0, 0; - # $in_fh is now ready for reading at position 0 - if($opt::tag or defined $opt::tagstring) { - my $tag = $self->tag(); - if($fdno == 2) { - # OpenSSH_3.6.1p2 gives 'tcgetattr: Invalid argument' with -tt - # This is a crappy way of ignoring it. - while(<$in_fh>) { - if(/^(client_process_control: )?tcgetattr: Invalid argument\n/) { - # Skip - } else { - print $out_fd $tag,$_; - } - # At most run the loop once - last; - } - } - while(<$in_fh>) { - print $out_fd $tag,$_; - } - } else { - my $buf; - if($fdno == 2) { - # OpenSSH_3.6.1p2 gives 'tcgetattr: Invalid argument' with -tt - # This is a crappy way of ignoring it. - sysread($in_fh,$buf,1_000); - $buf =~ s/^(client_process_control: )?tcgetattr: Invalid argument\n//; - print $out_fd $buf; - } - while(sysread($in_fh,$buf,32768)) { - print $out_fd $buf; - } - } - close $in_fh; - } - flush $out_fd; - } - ::debug("print", "<{'partial_line',$fdno}; - - if(defined $self->{'exitstatus'}) { - # If the job is dead: close printing fh. Needed for --compress - close $self->fh($fdno,"w"); - if($opt::compress) { - # Blocked reading in final round - $Global::use{"Fcntl"} ||= eval "use Fcntl qw(:DEFAULT :flock); 1;"; - for my $fdno (1,2) { - my $fdr = $self->fh($fdno,'r'); - my $flags; - fcntl($fdr, &F_GETFL, $flags) || die $!; # Get the current flags on the filehandle - $flags &= ~&O_NONBLOCK; # Remove non-blocking to the flags - fcntl($fdr, &F_SETFL, $flags) || die $!; # Set the flags on the filehandle - } - } - } - # This seek will clear EOF - seek $in_fh, tell($in_fh), 0; - # The read is non-blocking: The $in_fh is set to non-blocking. - # 32768 --tag = 5.1s - # 327680 --tag = 4.4s - # 1024000 --tag = 4.4s - # 3276800 --tag = 4.3s - # 32768000 --tag = 4.7s - # 10240000 --tag = 4.3s - while(read($in_fh,substr($$partial,length $$partial),3276800)) { - # Append to $$partial - # Find the last \n - my $i = rindex($$partial,"\n"); - if($i != -1) { - # One or more complete lines were found - if($fdno == 2 and not $self->{'printed_first_line',$fdno}++) { - # OpenSSH_3.6.1p2 gives 'tcgetattr: Invalid argument' with -tt - # This is a crappy way of ignoring it. - $$partial =~ s/^(client_process_control: )?tcgetattr: Invalid argument\n//; - # Length of partial line has changed: Find the last \n again - $i = rindex($$partial,"\n"); - } - if($opt::tag or defined $opt::tagstring) { - # Replace ^ with $tag within the full line - my $tag = $self->tag(); - substr($$partial,0,$i+1) =~ s/^/$tag/gm; - # Length of partial line has changed: Find the last \n again - $i = rindex($$partial,"\n"); - } - # Print up to and including the last \n - print $out_fd substr($$partial,0,$i+1); - # Remove the printed part - substr($$partial,0,$i+1)=""; - } - } - if(defined $self->{'exitstatus'}) { - # If the job is dead: print the remaining partial line - # read remaining - if($$partial and ($opt::tag or defined $opt::tagstring)) { - my $tag = $self->tag(); - $$partial =~ s/^/$tag/gm; - } - print $out_fd $$partial; - # Release the memory - $$partial = undef; - if($self->fh($fdno,"rpid") and CORE::kill 0, $self->fh($fdno,"rpid")) { - # decompress still running - } else { - # decompress done: close fh - close $in_fh; - } - } -} - -sub print_joblog { - my $self = shift; - my $cmd; - if($Global::verbose <= 1) { - $cmd = $self->replaced(); - } else { - # Verbose level > 1: Print the rsync and stuff - $cmd = "@command"; - } - print $Global::joblog - join("\t", $self->seq(), $self->sshlogin()->string(), - $self->starttime(), sprintf("%10.3f",$self->runtime()), - $self->transfersize(), $self->returnsize(), - $self->exitstatus(), $self->exitsignal(), $cmd - ). "\n"; - flush $Global::joblog; - $self->set_job_in_joblog(); -} - -sub tag { - my $self = shift; - if(not defined $self->{'tag'}) { - $self->{'tag'} = $self->{'commandline'}-> - replace_placeholders([$opt::tagstring],0,0)."\t"; - } - return $self->{'tag'}; -} - -sub hostgroups { - my $self = shift; - if(not defined $self->{'hostgroups'}) { - $self->{'hostgroups'} = $self->{'commandline'}->{'arg_list'}[0][0]->{'hostgroups'}; - } - return @{$self->{'hostgroups'}}; -} - -sub exitstatus { - my $self = shift; - return $self->{'exitstatus'}; -} - -sub set_exitstatus { - my $self = shift; - my $exitstatus = shift; - if($exitstatus) { - # Overwrite status if non-zero - $self->{'exitstatus'} = $exitstatus; - } else { - # Set status but do not overwrite - # Status may have been set by --timeout - $self->{'exitstatus'} ||= $exitstatus; - } -} - -sub exitsignal { - my $self = shift; - return $self->{'exitsignal'}; -} - -sub set_exitsignal { - my $self = shift; - my $exitsignal = shift; - $self->{'exitsignal'} = $exitsignal; -} - -{ - my ($disk_full_fh, $b8193, $name); - sub exit_if_disk_full { - # Checks if $TMPDIR is full by writing 8kb to a tmpfile - # If the disk is full: Exit immediately. - # Returns: - # N/A - if(not $disk_full_fh) { - ($disk_full_fh, $name) = ::tmpfile(SUFFIX => ".df"); - unlink $name; - $b8193 = "x"x8193; - } - # Linux does not discover if a disk is full if writing <= 8192 - # Tested on: - # bfs btrfs cramfs ext2 ext3 ext4 ext4dev jffs2 jfs minix msdos - # ntfs reiserfs tmpfs ubifs vfat xfs - # TODO this should be tested on different OS similar to this: - # - # doit() { - # sudo mount /dev/ram0 /mnt/loop; sudo chmod 1777 /mnt/loop - # seq 100000 | parallel --tmpdir /mnt/loop/ true & - # seq 6900000 > /mnt/loop/i && echo seq OK - # seq 6980868 > /mnt/loop/i - # seq 10000 > /mnt/loop/ii - # sleep 3 - # sudo umount /mnt/loop/ || sudo umount -l /mnt/loop/ - # echo >&2 - # } - print $disk_full_fh $b8193; - if(not $disk_full_fh - or - tell $disk_full_fh == 0) { - ::error("Output is incomplete. Cannot append to buffer file in $ENV{'TMPDIR'}. Is the disk full?\n"); - ::error("Change \$TMPDIR with --tmpdir or use --compress.\n"); - ::wait_and_exit(255); - } - truncate $disk_full_fh, 0; - seek($disk_full_fh, 0, 0) || die; - } -} - - -package CommandLine; - -sub new { - my $class = shift; - my $seq = shift; - my $commandref = shift; - $commandref || die; - my $arg_queue = shift; - my $context_replace = shift; - my $max_number_of_args = shift; # for -N and normal (-n1) - my $return_files = shift; - my $replacecount_ref = shift; - my $len_ref = shift; - my %replacecount = %$replacecount_ref; - my %len = %$len_ref; - for (keys %$replacecount_ref) { - # Total length of this replacement string {} replaced with all args - $len{$_} = 0; - } - return bless { - 'command' => $commandref, - 'seq' => $seq, - 'len' => \%len, - 'arg_list' => [], - 'arg_queue' => $arg_queue, - 'max_number_of_args' => $max_number_of_args, - 'replacecount' => \%replacecount, - 'context_replace' => $context_replace, - 'return_files' => $return_files, - 'replaced' => undef, - }, ref($class) || $class; -} - -sub seq { - my $self = shift; - return $self->{'seq'}; -} - -{ - my $max_slot_number; - - sub slot { - # Find the number of a free job slot and return it - # Uses: - # @Global::slots - # Returns: - # $jobslot = number of jobslot - my $self = shift; - if(not $self->{'slot'}) { - if(not @Global::slots) { - # $Global::max_slot_number will typically be $Global::max_jobs_running - push @Global::slots, ++$max_slot_number; - } - $self->{'slot'} = shift @Global::slots; - } - return $self->{'slot'}; - } -} - -sub populate { - # Add arguments from arg_queue until the number of arguments or - # max line length is reached - # Uses: - # $Global::minimal_command_line_length - # $opt::cat - # $opt::fifo - # $Global::JobQueue - # $opt::m - # $opt::X - # $CommandLine::already_spread - # $Global::max_jobs_running - # Returns: N/A - my $self = shift; - my $next_arg; - my $max_len = $Global::minimal_command_line_length || Limits::Command::max_length(); - - if($opt::cat or $opt::fifo) { - # Generate a tempfile name that will be used as {} - my($outfh,$name) = ::tmpfile(SUFFIX => ".pip"); - close $outfh; - # Unlink is needed if: ssh otheruser@localhost - unlink $name; - $Global::JobQueue->{'commandlinequeue'}->{'arg_queue'}->unget([Arg->new($name)]); - } - - while (not $self->{'arg_queue'}->empty()) { - $next_arg = $self->{'arg_queue'}->get(); - if(not defined $next_arg) { - next; - } - $self->push($next_arg); - if($self->len() >= $max_len) { - # Command length is now > max_length - # If there are arguments: remove the last - # If there are no arguments: Error - # TODO stuff about -x opt_x - if($self->number_of_args() > 1) { - # There is something to work on - $self->{'arg_queue'}->unget($self->pop()); - last; - } else { - my $args = join(" ", map { $_->orig() } @$next_arg); - ::error("Command line too long (", - $self->len(), " >= ", - $max_len, - ") at number ", - $self->{'arg_queue'}->arg_number(), - ": ". - (substr($args,0,50))."...\n"); - $self->{'arg_queue'}->unget($self->pop()); - ::wait_and_exit(255); - } - } - - if(defined $self->{'max_number_of_args'}) { - if($self->number_of_args() >= $self->{'max_number_of_args'}) { - last; - } - } - } - if(($opt::m or $opt::X) and not $CommandLine::already_spread - and $self->{'arg_queue'}->empty() and $Global::max_jobs_running) { - # -m or -X and EOF => Spread the arguments over all jobslots - # (unless they are already spread) - $CommandLine::already_spread ||= 1; - if($self->number_of_args() > 1) { - $self->{'max_number_of_args'} = - ::ceil($self->number_of_args()/$Global::max_jobs_running); - $Global::JobQueue->{'commandlinequeue'}->{'max_number_of_args'} = - $self->{'max_number_of_args'}; - $self->{'arg_queue'}->unget($self->pop_all()); - while($self->number_of_args() < $self->{'max_number_of_args'}) { - $self->push($self->{'arg_queue'}->get()); - } - } - } -} - -sub push { - # Add one or more records as arguments - # Returns: N/A - my $self = shift; - my $record = shift; - push @{$self->{'arg_list'}}, $record; - - my $quote_arg = $Global::noquote ? 0 : not $Global::quoting; - my $rep; - for my $arg (@$record) { - if(defined $arg) { - for my $perlexpr (keys %{$self->{'replacecount'}}) { - # 50% faster than below - $self->{'len'}{$perlexpr} += length $arg->replace($perlexpr,$quote_arg,$self); - # $rep = $arg->replace($perlexpr,$quote_arg,$self); - # $self->{'len'}{$perlexpr} += length $rep; - # ::debug("length", "Length: ", length $rep, - # "(", $perlexpr, "=>", $rep, ")\n"); - } - } - } -} - -sub pop { - # Remove last argument - # Returns: - # the last record - my $self = shift; - my $record = pop @{$self->{'arg_list'}}; - my $quote_arg = $Global::noquote ? 0 : not $Global::quoting; - for my $arg (@$record) { - if(defined $arg) { - for my $perlexpr (keys %{$self->{'replacecount'}}) { - $self->{'len'}{$perlexpr} -= - length $arg->replace($perlexpr,$quote_arg,$self); - } - } - } - return $record; -} - -sub pop_all { - # Remove all arguments and zeros the length of replacement strings - # Returns: - # all records - my $self = shift; - my @popped = @{$self->{'arg_list'}}; - for my $replacement_string (keys %{$self->{'replacecount'}}) { - $self->{'len'}{$replacement_string} = 0; - } - $self->{'arg_list'} = []; - return @popped; -} - -sub number_of_args { - # The number of records - # Returns: - # number of records - my $self = shift; - # Ftq rudef oaawuq ime dqxqmeqp az 2011-01-24 mzp ime iaz nk MQhmd - # Mdzrvadp Nvmdymeaz az 2011-04-10. Ftue oaawuq dqxqmeqp az - # 2013-08-18 ue m nuf tmdpqd me kag tmhq fa geq daf14. Bxqmeq - # qymux oaawuq@fmzsq.pw itqz kag dqmp ftue. - # - # U my ftq ymuzfmuzqd ar m buqoq ar rdqq earfimdq omxxqp SZG - # Bmdmxxqx. Rdqq earfimdq sgmdmzfqqe kag mooqee fa ftq eagdoq - # oapq, ngf U tmhq nqqz iazpqduzs tai ymzk mofgmxxk _dqmp_ ftq - # eagdoq oapq. - # - # Fa fqef ftue U bgf uz m oayyqzf fqxxuzs bqabxq fa qymux yq itqz - # ftqk dqmp ftue. Ftq oayyqzf ime bgf uz m eqofuaz ar ftq oapq - # ftmf za azq iagxp xaaw fa ruj ad uybdahq ftq earfimdq - ea ftq - # eagdoq oapq qcguhmxqzf fa m pgefk oadzqd. Fa ymwq egdq ftq - # oayyqzf iagxp zaf etai gb ur eayq azq vgef sdqbbqp ftdagst ftq - # eagdoq oapq U daf13'qp ftq eagdoq oapq - # tffb://qz.iuwubqpum.ads/iuwu/DAF13 - # - # 2.5 yazfte xmfqd U dqoquhqp mz qymux rday eayqazq ita zaf azxk - # ymzmsqp fa ruzp ftq oayyqzf, ngf mxea ymzmsqp fa sgqee ftq oapq - # tmp fa nq daf13'qp. - # - # Ftue nduzse yq fa ftq oazoxgeuaz ftmf ftqdq _mdq_ bqabxq, ita - # mdq zaf mrruxumfqp iuft ftq bdavqof, ftmf iuxx dqmp ftq eagdoq - # oapq - ftagst uf ymk zaf tmbbqz hqdk arfqz. - # - # This is really the number of records - return $#{$self->{'arg_list'}}+1; -} - -sub number_of_recargs { - # The number of args in records - # Returns: - # number of args records - my $self = shift; - my $sum = 0; - my $nrec = scalar @{$self->{'arg_list'}}; - if($nrec) { - $sum = $nrec * (scalar @{$self->{'arg_list'}[0]}); - } - return $sum; -} - -sub args_as_string { - # Returns: - # all unmodified arguments joined with ' ' (similar to {}) - my $self = shift; - return (join " ", map { $_->orig() } - map { @$_ } @{$self->{'arg_list'}}); -} - -sub args_as_dirname { - # Returns: - # all unmodified arguments joined with '/' (similar to {}) - # \t \0 \\ and / are quoted as: \t \0 \\ \_ - # If $Global::max_file_length: Keep subdirs < $Global::max_file_length - my $self = shift; - my @res = (); - - for my $rec_ref (@{$self->{'arg_list'}}) { - # If headers are used, sort by them. - # Otherwise keep the order from the command line. - my @header_indexes_sorted = header_indexes_sorted($#$rec_ref+1); - for my $n (@header_indexes_sorted) { - CORE::push(@res, - $Global::input_source_header{$n}, - map { my $s = $_; - # \t \0 \\ and / are quoted as: \t \0 \\ \_ - $s =~ s/\\/\\\\/g; - $s =~ s/\t/\\t/g; - $s =~ s/\0/\\0/g; - $s =~ s:/:\\_:g; - if($Global::max_file_length) { - # Keep each subdir shorter than the longest - # allowed file name - $s = substr($s,0,$Global::max_file_length); - } - $s; } - $rec_ref->[$n-1]->orig()); - } - } - return join "/", @res; -} - -sub header_indexes_sorted { - # Sort headers first by number then by name. - # E.g.: 1a 1b 11a 11b - # Returns: - # Indexes of %Global::input_source_header sorted - my $max_col = shift; - - no warnings 'numeric'; - for my $col (1 .. $max_col) { - # Make sure the header is defined. If it is not: use column number - if(not defined $Global::input_source_header{$col}) { - $Global::input_source_header{$col} = $col; - } - } - my @header_indexes_sorted = sort { - # Sort headers numerically then asciibetically - $Global::input_source_header{$a} <=> $Global::input_source_header{$b} - or - $Global::input_source_header{$a} cmp $Global::input_source_header{$b} - } 1 .. $max_col; - return @header_indexes_sorted; -} - -sub len { - # Uses: - # $opt::shellquote - # The length of the command line with args substituted - my $self = shift; - my $len = 0; - # Add length of the original command with no args - # Length of command w/ all replacement args removed - $len += $self->{'len'}{'noncontext'} + @{$self->{'command'}} -1; - ::debug("length", "noncontext + command: $len\n"); - my $recargs = $self->number_of_recargs(); - if($self->{'context_replace'}) { - # Context is duplicated for each arg - $len += $recargs * $self->{'len'}{'context'}; - for my $replstring (keys %{$self->{'replacecount'}}) { - # If the replacements string is more than once: mulitply its length - $len += $self->{'len'}{$replstring} * - $self->{'replacecount'}{$replstring}; - ::debug("length", $replstring, " ", $self->{'len'}{$replstring}, "*", - $self->{'replacecount'}{$replstring}, "\n"); - } - # echo 11 22 33 44 55 66 77 88 99 1010 - # echo 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 - # 5 + ctxgrp*arg - ::debug("length", "Ctxgrp: ", $self->{'len'}{'contextgroups'}, - " Groups: ", $self->{'len'}{'noncontextgroups'}, "\n"); - # Add space between context groups - $len += ($recargs-1) * ($self->{'len'}{'contextgroups'}); - } else { - # Each replacement string may occur several times - # Add the length for each time - $len += 1*$self->{'len'}{'context'}; - ::debug("length", "context+noncontext + command: $len\n"); - for my $replstring (keys %{$self->{'replacecount'}}) { - # (space between regargs + length of replacement) - # * number this replacement is used - $len += ($recargs -1 + $self->{'len'}{$replstring}) * - $self->{'replacecount'}{$replstring}; - } - } - if($opt::nice) { - # Pessimistic length if --nice is set - # Worse than worst case: every char needs to be quoted with \ - $len *= 2; - } - if($Global::quoting) { - # Pessimistic length if -q is set - # Worse than worst case: every char needs to be quoted with \ - $len *= 2; - } - if($opt::shellquote) { - # Pessimistic length if --shellquote is set - # Worse than worst case: every char needs to be quoted with \ twice - $len *= 4; - } - # If we are using --env, add the prefix for that, too. - $len += $Global::envvarlen; - - return $len; -} - -sub replaced { - # Uses: - # $Global::noquote - # $Global::quoting - # Returns: - # $replaced = command with place holders replaced and prepended - my $self = shift; - if(not defined $self->{'replaced'}) { - # Don't quote arguments if the input is the full command line - my $quote_arg = $Global::noquote ? 0 : not $Global::quoting; - $self->{'replaced'} = $self->replace_placeholders($self->{'command'},$Global::quoting,$quote_arg); - my $len = length $self->{'replaced'}; - if ($len != $self->len()) { - ::debug("length", $len, " != ", $self->len(), " ", $self->{'replaced'}, "\n"); - } else { - ::debug("length", $len, " == ", $self->len(), " ", $self->{'replaced'}, "\n"); - } - } - return $self->{'replaced'}; -} - -sub replace_placeholders { - # Replace foo{}bar with fooargbar - # Input: - # $targetref = command as shell words - # $quote = should everything be quoted? - # $quote_arg = should replaced arguments be quoted? - # Returns: - # @target with placeholders replaced - my $self = shift; - my $targetref = shift; - my $quote = shift; - my $quote_arg = shift; - my $context_replace = $self->{'context_replace'}; - my @target = @$targetref; - ::debug("replace", "Replace @target\n"); - # -X = context replace - # maybe multiple input sources - # maybe --xapply - if(not @target) { - # @target is empty: Return empty array - return @target; - } - # Fish out the words that have replacement strings in them - my %word; - for (@target) { - my $tt = $_; - ::debug("replace", "Target: $tt"); - # a{1}b{}c{}d - # a{=1 $_=$_ =}b{= $_=$_ =}c{= $_=$_ =}d - # a\257<1 $_=$_ \257>b\257< $_=$_ \257>c\257< $_=$_ \257>d - # A B C => aAbA B CcA B Cd - # -X A B C => aAbAcAd aAbBcBd aAbCcCd - - if($context_replace) { - while($tt =~ s/([^\s\257]* # before {= - (?: - \257< # {= - [^\257]*? # The perl expression - \257> # =} - [^\s\257]* # after =} - )+)/ /x) { - # $1 = pre \257 perlexpr \257 post - $word{"$1"} ||= 1; - } - } else { - while($tt =~ s/( (?: \257<([^\257]*?)\257>) )//x) { - # $f = \257 perlexpr \257 - $word{$1} ||= 1; - } - } - } - my @word = keys %word; - - my %replace; - my @arg; - for my $record (@{$self->{'arg_list'}}) { - # $self->{'arg_list'} = [ [Arg11, Arg12], [Arg21, Arg22], [Arg31, Arg32] ] - # Merge arg-objects from records into @arg for easy access - CORE::push @arg, @$record; - } - # Add one arg if empty to allow {#} and {%} to be computed only once - if(not @arg) { @arg = (Arg->new("")); } - # Number of arguments - used for positional arguments - my $n = $#_+1; - - # This is actually a CommandLine-object, - # but it looks nice to be able to say {= $job->slot() =} - my $job = $self; - for my $word (@word) { - # word = AB \257< perlexpr \257> CD \257< perlexpr \257> EF - my $w = $word; - ::debug("replace", "Replacing in $w\n"); - - # Replace positional arguments - $w =~ s< ([^\s\257]*) # before {= - \257< # {= - (-?\d+) # Position (eg. -2 or 3) - ([^\257]*?) # The perl expression - \257> # =} - ([^\s\257]*) # after =} - > - { $1. # Context (pre) - ( - $arg[$2 > 0 ? $2-1 : $n+$2] ? # If defined: replace - $arg[$2 > 0 ? $2-1 : $n+$2]->replace($3,$quote_arg,$self) - : "") - .$4 }egx;# Context (post) - ::debug("replace", "Positional replaced $word with: $w\n"); - - if($w !~ /\257/) { - # No more replacement strings in $w: No need to do more - if($quote) { - CORE::push(@{$replace{::shell_quote($word)}}, $w); - } else { - CORE::push(@{$replace{$word}}, $w); - } - next; - } - # for each arg: - # compute replacement for each string - # replace replacement strings with replacement in the word value - # push to replace word value - ::debug("replace", "Positional done: $w\n"); - for my $arg (@arg) { - my $val = $w; - my $number_of_replacements = 0; - for my $perlexpr (keys %{$self->{'replacecount'}}) { - # Replace {= perl expr =} with value for each arg - $number_of_replacements += - $val =~ s{\257<\Q$perlexpr\E\257>} - {$arg ? $arg->replace($perlexpr,$quote_arg,$self) : ""}eg; - } - my $ww = $word; - if($quote) { - $ww = ::shell_quote_scalar($word); - $val = ::shell_quote_scalar($val); - } - if($number_of_replacements) { - CORE::push(@{$replace{$ww}}, $val); - } - } - } - - if($quote) { - @target = ::shell_quote(@target); - } - # ::debug("replace", "%replace=",::my_dump(%replace),"\n"); - if(%replace) { - # Substitute the replace strings with the replacement values - # Must be sorted by length if a short word is a substring of a long word - my $regexp = join('|', map { my $s = $_; $s =~ s/(\W)/\\$1/g; $s } - sort { length $b <=> length $a } keys %replace); - for(@target) { - s/($regexp)/join(" ",@{$replace{$1}})/ge; - } - } - ::debug("replace", "Return @target\n"); - return wantarray ? @target : "@target"; -} - - -package CommandLineQueue; - -sub new { - my $class = shift; - my $commandref = shift; - my $read_from = shift; - my $context_replace = shift; - my $max_number_of_args = shift; - my $return_files = shift; - my @unget = (); - my ($count,%replacecount,$posrpl,$perlexpr,%len); - my @command = @$commandref; - # If the first command start with '-' it is probably an option - if($command[0] =~ /^\s*(-\S+)/) { - # Is this really a command in $PATH starting with '-'? - my $cmd = $1; - if(not ::which($cmd)) { - ::error("Command ($cmd) starts with '-'. Is this a wrong option?\n"); - ::wait_and_exit(255); - } - } - # Replace replacement strings with {= perl expr =} - # Protect matching inside {= perl expr =} - # by replacing {= and =} with \257< and \257> - for(@command) { - if(/\257/) { - ::error("Command cannot contain the character \257. Use a function for that.\n"); - ::wait_and_exit(255); - } - s/\Q$Global::parensleft\E(.*?)\Q$Global::parensright\E/\257<$1\257>/gx; - } - for my $rpl (keys %Global::rpl) { - # Replace the short hand string with the {= perl expr =} in $command and $opt::tagstring - # Avoid replacing inside existing {= perl expr =} - for(@command,@Global::ret_files) { - while(s/((^|\257>)[^\257]*?) # Don't replace after \257 unless \257> - \Q$rpl\E/$1\257<$Global::rpl{$rpl}\257>/xg) { - } - } - if(defined $opt::tagstring) { - for($opt::tagstring) { - while(s/((^|\257>)[^\257]*?) # Don't replace after \257 unless \257> - \Q$rpl\E/$1\257<$Global::rpl{$rpl}\257>/x) {} - } - } - # Do the same for the positional replacement strings - # A bit harder as we have to put in the position number - $posrpl = $rpl; - if($posrpl =~ s/^\{//) { - # Only do this if the shorthand start with { - for(@command,@Global::ret_files) { - s/\{(-?\d+)\Q$posrpl\E/\257<$1 $Global::rpl{$rpl}\257>/g; - } - if(defined $opt::tagstring) { - $opt::tagstring =~ s/\{(-?\d+)\Q$posrpl\E/\257<$1 $perlexpr\257>/g; - } - } - } - my $sum = 0; - while($sum == 0) { - # Count how many times each replacement string is used - my @cmd = @command; - my $contextlen = 0; - my $noncontextlen = 0; - my $contextgroups = 0; - for my $c (@cmd) { - while($c =~ s/ \257<([^\257]*?)\257> /\000/x) { - # %replacecount = { "perlexpr" => number of times seen } - # e.g { "$_++" => 2 } - $replacecount{$1} ++; - $sum++; - } - # Measure the length of the context around the {= perl expr =} - # Use that {=...=} has been replaced with \000 above - # So there is no need to deal with \257< - while($c =~ s/ (\S*\000\S*) //x) { - my $w = $1; - $w =~ tr/\000//d; # Remove all \000's - $contextlen += length($w); - $contextgroups++; - } - # All {= perl expr =} have been removed: The rest is non-context - $noncontextlen += length $c; - } - if($opt::tagstring) { - my $t = $opt::tagstring; - while($t =~ s/ \257<([^\257]*)\257> //x) { - # %replacecount = { "perlexpr" => number of times seen } - # e.g { "$_++" => 2 } - # But for tagstring we just need to mark it as seen - $replacecount{$1}||=1; - } - } - - $len{'context'} = 0+$contextlen; - $len{'noncontext'} = $noncontextlen; - $len{'contextgroups'} = $contextgroups; - $len{'noncontextgroups'} = @cmd-$contextgroups; - ::debug("length", "@command Context: ", $len{'context'}, - " Non: ", $len{'noncontext'}, " Ctxgrp: ", $len{'contextgroups'}, - " NonCtxGrp: ", $len{'noncontextgroups'}, "\n"); - if($sum == 0) { - # Default command = {} - # If not replacement string: append {} - if(not @command) { - @command = ("\257<\257>"); - $Global::noquote = 1; - } elsif(($opt::pipe or $opt::pipepart) - and not $opt::fifo and not $opt::cat) { - # With --pipe / --pipe-part you can have no replacement - last; - } else { - # Append {} to the command if there are no {...}'s and no {=...=} - push @command, ("\257<\257>"); - } - } - } - - return bless { - 'unget' => \@unget, - 'command' => \@command, - 'replacecount' => \%replacecount, - 'arg_queue' => RecordQueue->new($read_from,$opt::colsep), - 'context_replace' => $context_replace, - 'len' => \%len, - 'max_number_of_args' => $max_number_of_args, - 'size' => undef, - 'return_files' => $return_files, - 'seq' => 1, - }, ref($class) || $class; -} - -sub get { - my $self = shift; - if(@{$self->{'unget'}}) { - my $cmd_line = shift @{$self->{'unget'}}; - return ($cmd_line); - } else { - my $cmd_line; - $cmd_line = CommandLine->new($self->seq(), - $self->{'command'}, - $self->{'arg_queue'}, - $self->{'context_replace'}, - $self->{'max_number_of_args'}, - $self->{'return_files'}, - $self->{'replacecount'}, - $self->{'len'}, - ); - $cmd_line->populate(); - ::debug("init","cmd_line->number_of_args ", - $cmd_line->number_of_args(), "\n"); - if($opt::pipe or $opt::pipepart) { - if($cmd_line->replaced() eq "") { - # Empty command - pipe requires a command - ::error("--pipe must have a command to pipe into (e.g. 'cat').\n"); - ::wait_and_exit(255); - } - } else { - if($cmd_line->number_of_args() == 0) { - # We did not get more args - maybe at EOF string? - return undef; - } elsif($cmd_line->replaced() eq "") { - # Empty command - get the next instead - return $self->get(); - } - } - $self->set_seq($self->seq()+1); - return $cmd_line; - } -} - -sub unget { - my $self = shift; - unshift @{$self->{'unget'}}, @_; -} - -sub empty { - my $self = shift; - my $empty = (not @{$self->{'unget'}}) && $self->{'arg_queue'}->empty(); - ::debug("run", "CommandLineQueue->empty $empty"); - return $empty; -} - -sub seq { - my $self = shift; - return $self->{'seq'}; -} - -sub set_seq { - my $self = shift; - $self->{'seq'} = shift; -} - -sub quote_args { - my $self = shift; - # If there is not command emulate |bash - return $self->{'command'}; -} - -sub size { - my $self = shift; - if(not $self->{'size'}) { - my @all_lines = (); - while(not $self->{'arg_queue'}->empty()) { - push @all_lines, CommandLine->new($self->{'command'}, - $self->{'arg_queue'}, - $self->{'context_replace'}, - $self->{'max_number_of_args'}); - } - $self->{'size'} = @all_lines; - $self->unget(@all_lines); - } - return $self->{'size'}; -} - - -package Limits::Command; - -# Maximal command line length (for -m and -X) -sub max_length { - # Find the max_length of a command line and cache it - # Returns: - # number of chars on the longest command line allowed - if(not $Limits::Command::line_max_len) { - # Disk cache of max command line length - my $len_cache = $ENV{'HOME'} . "/.parallel/tmp/linelen-" . ::hostname(); - my $cached_limit; - if(-e $len_cache) { - open(my $fh, "<", $len_cache) || ::die_bug("Cannot read $len_cache"); - $cached_limit = <$fh>; - close $fh; - } else { - $cached_limit = real_max_length(); - # If $HOME is write protected: Do not fail - mkdir($ENV{'HOME'} . "/.parallel"); - mkdir($ENV{'HOME'} . "/.parallel/tmp"); - open(my $fh, ">", $len_cache); - print $fh $cached_limit; - close $fh; - } - $Limits::Command::line_max_len = $cached_limit; - if($opt::max_chars) { - if($opt::max_chars <= $cached_limit) { - $Limits::Command::line_max_len = $opt::max_chars; - } else { - ::warning("Value for -s option ", - "should be < $cached_limit.\n"); - } - } - } - return $Limits::Command::line_max_len; -} - -sub real_max_length { - # Find the max_length of a command line - # Returns: - # The maximal command line length - # Use an upper bound of 8 MB if the shell allows for for infinite long lengths - my $upper = 8_000_000; - my $len = 8; - do { - if($len > $upper) { return $len }; - $len *= 16; - } while (is_acceptable_command_line_length($len)); - # Then search for the actual max length between 0 and upper bound - return binary_find_max_length(int($len/16),$len); -} - -sub binary_find_max_length { - # Given a lower and upper bound find the max_length of a command line - # Returns: - # number of chars on the longest command line allowed - my ($lower, $upper) = (@_); - if($lower == $upper or $lower == $upper-1) { return $lower; } - my $middle = int (($upper-$lower)/2 + $lower); - ::debug("init", "Maxlen: $lower,$upper,$middle : "); - if (is_acceptable_command_line_length($middle)) { - return binary_find_max_length($middle,$upper); - } else { - return binary_find_max_length($lower,$middle); - } -} - -sub is_acceptable_command_line_length { - # Test if a command line of this length can run - # Returns: - # 0 if the command line length is too long - # 1 otherwise - my $len = shift; - - local *STDERR; - open (STDERR, ">", "/dev/null"); - system "true "."x"x$len; - close STDERR; - ::debug("init", "$len=$? "); - return not $?; -} - - -package RecordQueue; - -sub new { - my $class = shift; - my $fhs = shift; - my $colsep = shift; - my @unget = (); - my $arg_sub_queue; - if($colsep) { - # Open one file with colsep - $arg_sub_queue = RecordColQueue->new($fhs); - } else { - # Open one or more files if multiple -a - $arg_sub_queue = MultifileQueue->new($fhs); - } - return bless { - 'unget' => \@unget, - 'arg_number' => 0, - 'arg_sub_queue' => $arg_sub_queue, - }, ref($class) || $class; -} - -sub get { - # Returns: - # reference to array of Arg-objects - my $self = shift; - if(@{$self->{'unget'}}) { - $self->{'arg_number'}++; - return shift @{$self->{'unget'}}; - } - my $ret = $self->{'arg_sub_queue'}->get(); - if(defined $Global::max_number_of_args - and $Global::max_number_of_args == 0) { - ::debug("run", "Read 1 but return 0 args\n"); - return [Arg->new("")]; - } else { - return $ret; - } -} - -sub unget { - my $self = shift; - ::debug("run", "RecordQueue-unget '@_'\n"); - $self->{'arg_number'} -= @_; - unshift @{$self->{'unget'}}, @_; -} - -sub empty { - my $self = shift; - my $empty = not @{$self->{'unget'}}; - $empty &&= $self->{'arg_sub_queue'}->empty(); - ::debug("run", "RecordQueue->empty $empty"); - return $empty; -} - -sub arg_number { - my $self = shift; - return $self->{'arg_number'}; -} - - -package RecordColQueue; - -sub new { - my $class = shift; - my $fhs = shift; - my @unget = (); - my $arg_sub_queue = MultifileQueue->new($fhs); - return bless { - 'unget' => \@unget, - 'arg_sub_queue' => $arg_sub_queue, - }, ref($class) || $class; -} - -sub get { - # Returns: - # reference to array of Arg-objects - my $self = shift; - if(@{$self->{'unget'}}) { - return shift @{$self->{'unget'}}; - } - my $unget_ref=$self->{'unget'}; - if($self->{'arg_sub_queue'}->empty()) { - return undef; - } - my $in_record = $self->{'arg_sub_queue'}->get(); - if(defined $in_record) { - my @out_record = (); - for my $arg (@$in_record) { - ::debug("run", "RecordColQueue::arg $arg\n"); - my $line = $arg->orig(); - ::debug("run", "line='$line'\n"); - if($line ne "") { - for my $s (split /$opt::colsep/o, $line, -1) { - push @out_record, Arg->new($s); - } - } else { - push @out_record, Arg->new(""); - } - } - return \@out_record; - } else { - return undef; - } -} - -sub unget { - my $self = shift; - ::debug("run", "RecordColQueue-unget '@_'\n"); - unshift @{$self->{'unget'}}, @_; -} - -sub empty { - my $self = shift; - my $empty = (not @{$self->{'unget'}} and $self->{'arg_sub_queue'}->empty()); - ::debug("run", "RecordColQueue->empty $empty"); - return $empty; -} - - -package MultifileQueue; - -@Global::unget_argv=(); - -sub new { - my $class = shift; - my $fhs = shift; - for my $fh (@$fhs) { - if(-t $fh) { - ::warning("Input is read from the terminal. ". - "Only experts do this on purpose. ". - "Press CTRL-D to exit.\n"); - } - } - return bless { - 'unget' => \@Global::unget_argv, - 'fhs' => $fhs, - 'arg_matrix' => undef, - }, ref($class) || $class; -} - -sub get { - my $self = shift; - if($opt::xapply) { - return $self->xapply_get(); - } else { - return $self->nest_get(); - } -} - -sub unget { - my $self = shift; - ::debug("run", "MultifileQueue-unget '@_'\n"); - unshift @{$self->{'unget'}}, @_; -} - -sub empty { - my $self = shift; - my $empty = (not @Global::unget_argv - and not @{$self->{'unget'}}); - for my $fh (@{$self->{'fhs'}}) { - $empty &&= eof($fh); - } - ::debug("run", "MultifileQueue->empty $empty "); - return $empty; -} - -sub xapply_get { - my $self = shift; - if(@{$self->{'unget'}}) { - return shift @{$self->{'unget'}}; - } - my @record = (); - my $prepend = undef; - my $empty = 1; - for my $fh (@{$self->{'fhs'}}) { - my $arg = read_arg_from_fh($fh); - if(defined $arg) { - # Record $arg for recycling at end of file - push @{$self->{'arg_matrix'}{$fh}}, $arg; - push @record, $arg; - $empty = 0; - } else { - ::debug("run", "EOA "); - # End of file: Recycle arguments - push @{$self->{'arg_matrix'}{$fh}}, shift @{$self->{'arg_matrix'}{$fh}}; - # return last @{$args->{'args'}{$fh}}; - push @record, @{$self->{'arg_matrix'}{$fh}}[-1]; - } - } - if($empty) { - return undef; - } else { - return \@record; - } -} - -sub nest_get { - my $self = shift; - if(@{$self->{'unget'}}) { - return shift @{$self->{'unget'}}; - } - my @record = (); - my $prepend = undef; - my $empty = 1; - my $no_of_inputsources = $#{$self->{'fhs'}} + 1; - if(not $self->{'arg_matrix'}) { - # Initialize @arg_matrix with one arg from each file - # read one line from each file - my @first_arg_set; - my $all_empty = 1; - for (my $fhno = 0; $fhno < $no_of_inputsources ; $fhno++) { - my $arg = read_arg_from_fh($self->{'fhs'}[$fhno]); - if(defined $arg) { - $all_empty = 0; - } - $self->{'arg_matrix'}[$fhno][0] = $arg || Arg->new(""); - push @first_arg_set, $self->{'arg_matrix'}[$fhno][0]; - } - if($all_empty) { - # All filehandles were at eof or eof-string - return undef; - } - return [@first_arg_set]; - } - - # Treat the case with one input source special. For multiple - # input sources we need to remember all previously read values to - # generate all combinations. But for one input source we can - # forget the value after first use. - if($no_of_inputsources == 1) { - my $arg = read_arg_from_fh($self->{'fhs'}[0]); - if(defined($arg)) { - return [$arg]; - } - return undef; - } - for (my $fhno = $no_of_inputsources - 1; $fhno >= 0; $fhno--) { - if(eof($self->{'fhs'}[$fhno])) { - next; - } else { - # read one - my $arg = read_arg_from_fh($self->{'fhs'}[$fhno]); - defined($arg) || next; # If we just read an EOF string: Treat this as EOF - my $len = $#{$self->{'arg_matrix'}[$fhno]} + 1; - $self->{'arg_matrix'}[$fhno][$len] = $arg; - # make all new combinations - my @combarg = (); - for (my $fhn = 0; $fhn < $no_of_inputsources; $fhn++) { - push @combarg, [0, $#{$self->{'arg_matrix'}[$fhn]}]; - } - $combarg[$fhno] = [$len,$len]; # Find only combinations with this new entry - # map combinations - # [ 1, 3, 7 ], [ 2, 4, 1 ] - # => - # [ m[0][1], m[1][3], m[3][7] ], [ m[0][2], m[1][4], m[2][1] ] - my @mapped; - for my $c (expand_combinations(@combarg)) { - my @a; - for my $n (0 .. $no_of_inputsources - 1 ) { - push @a, $self->{'arg_matrix'}[$n][$$c[$n]]; - } - push @mapped, \@a; - } - # append the mapped to the ungotten arguments - push @{$self->{'unget'}}, @mapped; - # get the first - return shift @{$self->{'unget'}}; - } - } - # all are eof or at EOF string; return from the unget queue - return shift @{$self->{'unget'}}; -} - -sub read_arg_from_fh { - # Read one Arg from filehandle - # Returns: - # Arg-object with one read line - # undef if end of file - my $fh = shift; - my $prepend = undef; - my $arg; - do {{ - # This makes 10% faster - if(not ($arg = <$fh>)) { - if(defined $prepend) { - return Arg->new($prepend); - } else { - return undef; - } - } -# ::debug("run", "read $arg\n"); - # Remove delimiter - $arg =~ s:$/$::; - if($Global::end_of_file_string and - $arg eq $Global::end_of_file_string) { - # Ignore the rest of input file - close $fh; - ::debug("run", "EOF-string ($arg) met\n"); - if(defined $prepend) { - return Arg->new($prepend); - } else { - return undef; - } - } - if(defined $prepend) { - $arg = $prepend.$arg; # For line continuation - $prepend = undef; #undef; - } - if($Global::ignore_empty) { - if($arg =~ /^\s*$/) { - redo; # Try the next line - } - } - if($Global::max_lines) { - if($arg =~ /\s$/) { - # Trailing space => continued on next line - $prepend = $arg; - redo; - } - } - }} while (1 == 0); # Dummy loop {{}} for redo - if(defined $arg) { - return Arg->new($arg); - } else { - ::die_bug("multiread arg undefined"); - } -} - -sub expand_combinations { - # Input: - # ([xmin,xmax], [ymin,ymax], ...) - # Returns: ([x,y,...],[x,y,...]) - # where xmin <= x <= xmax and ymin <= y <= ymax - my $minmax_ref = shift; - my $xmin = $$minmax_ref[0]; - my $xmax = $$minmax_ref[1]; - my @p; - if(@_) { - # If there are more columns: Compute those recursively - my @rest = expand_combinations(@_); - for(my $x = $xmin; $x <= $xmax; $x++) { - push @p, map { [$x, @$_] } @rest; - } - } else { - for(my $x = $xmin; $x <= $xmax; $x++) { - push @p, [$x]; - } - } - return @p; -} - - -package Arg; - -sub new { - my $class = shift; - my $orig = shift; - my @hostgroups; - if($opt::hostgroups) { - if($orig =~ s:@(.+)::) { - # We found hostgroups on the arg - @hostgroups = split(/\+/, $1); - if(not grep { defined $Global::hostgroups{$_} } @hostgroups) { - ::warning("No such hostgroup (@hostgroups)\n"); - @hostgroups = (keys %Global::hostgroups); - } - } else { - @hostgroups = (keys %Global::hostgroups); - } - } - return bless { - 'orig' => $orig, - 'hostgroups' => \@hostgroups, - }, ref($class) || $class; -} - -sub replace { - # Calculates the corresponding value for a given perl expression - # Returns: - # The calculated string (quoted if asked for) - my $self = shift; - my $perlexpr = shift; # E.g. $_=$_ or s/.gz// - my $quote = (shift) ? 1 : 0; # should the string be quoted? - # This is actually a CommandLine-object, - # but it looks nice to be able to say {= $job->slot() =} - my $job = shift; - $perlexpr =~ s/^-?\d+ //; # Positional replace treated as normal replace - if(not defined $self->{"rpl",0,$perlexpr}) { - local $_; - if($Global::trim eq "n") { - $_ = $self->{'orig'}; - } else { - $_ = trim_of($self->{'orig'}); - } - ::debug("replace", "eval ", $perlexpr, " ", $_, "\n"); - if(not $Global::perleval{$perlexpr}) { - # Make an anonymous function of the $perlexpr - # And more importantly: Compile it only once - if($Global::perleval{$perlexpr} = - eval('sub { no strict; no warnings; my $job = shift; '. - $perlexpr.' }')) { - # All is good - } else { - # The eval failed. Maybe $perlexpr is invalid perl? - ::error("Cannot use $perlexpr: $@\n"); - ::wait_and_exit(255); - } - } - # Execute the function - $Global::perleval{$perlexpr}->($job); - $self->{"rpl",0,$perlexpr} = $_; - } - if(not defined $self->{"rpl",$quote,$perlexpr}) { - $self->{"rpl",1,$perlexpr} = - ::shell_quote_scalar($self->{"rpl",0,$perlexpr}); - } - return $self->{"rpl",$quote,$perlexpr}; -} - -sub orig { - my $self = shift; - return $self->{'orig'}; -} - -sub trim_of { - # Removes white space as specifed by --trim: - # n = nothing - # l = start - # r = end - # lr|rl = both - # Returns: - # string with white space removed as needed - my @strings = map { defined $_ ? $_ : "" } (@_); - my $arg; - if($Global::trim eq "n") { - # skip - } elsif($Global::trim eq "l") { - for my $arg (@strings) { $arg =~ s/^\s+//; } - } elsif($Global::trim eq "r") { - for my $arg (@strings) { $arg =~ s/\s+$//; } - } elsif($Global::trim eq "rl" or $Global::trim eq "lr") { - for my $arg (@strings) { $arg =~ s/^\s+//; $arg =~ s/\s+$//; } - } else { - ::error("--trim must be one of: r l rl lr.\n"); - ::wait_and_exit(255); - } - return wantarray ? @strings : "@strings"; -} - - -package TimeoutQueue; - -sub new { - my $class = shift; - my $delta_time = shift; - my ($pct); - if($delta_time =~ /(\d+(\.\d+)?)%/) { - # Timeout in percent - $pct = $1/100; - $delta_time = 1_000_000; - } - return bless { - 'queue' => [], - 'delta_time' => $delta_time, - 'pct' => $pct, - 'remedian_idx' => 0, - 'remedian_arr' => [], - 'remedian' => undef, - }, ref($class) || $class; -} - -sub delta_time { - my $self = shift; - return $self->{'delta_time'}; -} - -sub set_delta_time { - my $self = shift; - $self->{'delta_time'} = shift; -} - -sub remedian { - my $self = shift; - return $self->{'remedian'}; -} - -sub set_remedian { - # Set median of the last 999^3 (=997002999) values using Remedian - # - # Rousseeuw, Peter J., and Gilbert W. Bassett Jr. "The remedian: A - # robust averaging method for large data sets." Journal of the - # American Statistical Association 85.409 (1990): 97-104. - my $self = shift; - my $val = shift; - my $i = $self->{'remedian_idx'}++; - my $rref = $self->{'remedian_arr'}; - $rref->[0][$i%999] = $val; - $rref->[1][$i/999%999] = (sort @{$rref->[0]})[$#{$rref->[0]}/2]; - $rref->[2][$i/999/999%999] = (sort @{$rref->[1]})[$#{$rref->[1]}/2]; - $self->{'remedian'} = (sort @{$rref->[2]})[$#{$rref->[2]}/2]; -} - -sub update_delta_time { - # Update delta_time based on runtime of finished job if timeout is - # a percentage - my $self = shift; - my $runtime = shift; - if($self->{'pct'}) { - $self->set_remedian($runtime); - $self->{'delta_time'} = $self->{'pct'} * $self->remedian(); - ::debug("run", "Timeout: $self->{'delta_time'}s "); - } -} - -sub process_timeouts { - # Check if there was a timeout - my $self = shift; - # $self->{'queue'} is sorted by start time - while (@{$self->{'queue'}}) { - my $job = $self->{'queue'}[0]; - if($job->endtime()) { - # Job already finished. No need to timeout the job - # This could be because of --keep-order - shift @{$self->{'queue'}}; - } elsif($job->timedout($self->{'delta_time'})) { - # Need to shift off queue before kill - # because kill calls usleep that calls process_timeouts - shift @{$self->{'queue'}}; - $job->kill(); - } else { - # Because they are sorted by start time the rest are later - last; - } - } -} - -sub insert { - my $self = shift; - my $in = shift; - push @{$self->{'queue'}}, $in; -} - - -package Semaphore; - -# This package provides a counting semaphore -# -# If a process dies without releasing the semaphore the next process -# that needs that entry will clean up dead semaphores -# -# The semaphores are stored in ~/.parallel/semaphores/id- Each -# file in ~/.parallel/semaphores/id-/ is the process ID of the -# process holding the entry. If the process dies, the entry can be -# taken by another process. - -sub new { - my $class = shift; - my $id = shift; - my $count = shift; - $id=~s/([^-_a-z0-9])/unpack("H*",$1)/ige; # Convert non-word chars to hex - $id="id-".$id; # To distinguish it from a process id - my $parallel_dir = $ENV{'HOME'}."/.parallel"; - -d $parallel_dir or mkdir_or_die($parallel_dir); - my $parallel_locks = $parallel_dir."/semaphores"; - -d $parallel_locks or mkdir_or_die($parallel_locks); - my $lockdir = "$parallel_locks/$id"; - my $lockfile = $lockdir.".lock"; - if($count < 1) { ::die_bug("semaphore-count: $count"); } - return bless { - 'lockfile' => $lockfile, - 'lockfh' => Symbol::gensym(), - 'lockdir' => $lockdir, - 'id' => $id, - 'idfile' => $lockdir."/".$id, - 'pid' => $$, - 'pidfile' => $lockdir."/".$$.'@'.::hostname(), - 'count' => $count + 1 # nlinks returns a link for the 'id-' as well - }, ref($class) || $class; -} - -sub acquire { - my $self = shift; - my $sleep = 1; # 1 ms - my $start_time = time; - while(1) { - $self->atomic_link_if_count_less_than() and last; - ::debug("sem", "Remove dead locks"); - my $lockdir = $self->{'lockdir'}; - for my $d (glob "$lockdir/*") { - ::debug("sem", "Lock $d $lockdir\n"); - $d =~ m:$lockdir/([0-9]+)\@([-\._a-z0-9]+)$:o or next; - my ($pid, $host) = ($1, $2); - if($host eq ::hostname()) { - if(not kill 0, $1) { - ::debug("sem", "Dead: $d"); - unlink $d; - } else { - ::debug("sem", "Alive: $d"); - } - } - } - # try again - $self->atomic_link_if_count_less_than() and last; - # Retry slower and slower up to 1 second - $sleep = ($sleep < 1000) ? ($sleep * 1.1) : ($sleep); - # Random to avoid every sleeping job waking up at the same time - ::usleep(rand()*$sleep); - if(defined($opt::timeout) and - $start_time + $opt::timeout > time) { - # Acquire the lock anyway - if(not -e $self->{'idfile'}) { - open (my $fh, ">", $self->{'idfile'}) or - ::die_bug("timeout_write_idfile: $self->{'idfile'}"); - close $fh; - } - link $self->{'idfile'}, $self->{'pidfile'}; - last; - } - } - ::debug("sem", "acquired $self->{'pid'}\n"); -} - -sub release { - my $self = shift; - unlink $self->{'pidfile'}; - if($self->nlinks() == 1) { - # This is the last link, so atomic cleanup - $self->lock(); - if($self->nlinks() == 1) { - unlink $self->{'idfile'}; - rmdir $self->{'lockdir'}; - } - $self->unlock(); - } - ::debug("run", "released $self->{'pid'}\n"); -} - -sub _release { - my $self = shift; - - unlink $self->{'pidfile'}; - $self->lock(); - my $nlinks = $self->nlinks(); - ::debug("sem", $nlinks, "<", $self->{'count'}); - if($nlinks-- > 1) { - unlink $self->{'idfile'}; - open (my $fh, ">", $self->{'idfile'}) or - ::die_bug("write_idfile: $self->{'idfile'}"); - print $fh "#"x$nlinks; - close $fh; - } else { - unlink $self->{'idfile'}; - rmdir $self->{'lockdir'}; - } - $self->unlock(); - ::debug("sem", "released $self->{'pid'}\n"); -} - -sub atomic_link_if_count_less_than { - # Link $file1 to $file2 if nlinks to $file1 < $count - my $self = shift; - my $retval = 0; - $self->lock(); - ::debug($self->nlinks(), "<", $self->{'count'}); - if($self->nlinks() < $self->{'count'}) { - -d $self->{'lockdir'} or mkdir_or_die($self->{'lockdir'}); - if(not -e $self->{'idfile'}) { - open (my $fh, ">", $self->{'idfile'}) or - ::die_bug("write_idfile: $self->{'idfile'}"); - close $fh; - } - $retval = link $self->{'idfile'}, $self->{'pidfile'}; - } - $self->unlock(); - ::debug("run", "atomic $retval"); - return $retval; -} - -sub _atomic_link_if_count_less_than { - # Link $file1 to $file2 if nlinks to $file1 < $count - my $self = shift; - my $retval = 0; - $self->lock(); - my $nlinks = $self->nlinks(); - ::debug("sem", $nlinks, "<", $self->{'count'}); - if($nlinks++ < $self->{'count'}) { - -d $self->{'lockdir'} or mkdir_or_die($self->{'lockdir'}); - if(not -e $self->{'idfile'}) { - open (my $fh, ">", $self->{'idfile'}) or - ::die_bug("write_idfile: $self->{'idfile'}"); - close $fh; - } - open (my $fh, ">", $self->{'idfile'}) or - ::die_bug("write_idfile: $self->{'idfile'}"); - print $fh "#"x$nlinks; - close $fh; - $retval = link $self->{'idfile'}, $self->{'pidfile'}; - } - $self->unlock(); - ::debug("sem", "atomic $retval"); - return $retval; -} - -sub nlinks { - my $self = shift; - if(-e $self->{'idfile'}) { - ::debug("sem", "nlinks", (stat(_))[3], "size", (stat(_))[7], "\n"); - return (stat(_))[3]; - } else { - return 0; - } -} - -sub lock { - my $self = shift; - my $sleep = 100; # 100 ms - my $total_sleep = 0; - $Global::use{"Fcntl"} ||= eval "use Fcntl qw(:DEFAULT :flock); 1;"; - my $locked = 0; - while(not $locked) { - if(tell($self->{'lockfh'}) == -1) { - # File not open - open($self->{'lockfh'}, ">", $self->{'lockfile'}) - or ::debug("run", "Cannot open $self->{'lockfile'}"); - } - if($self->{'lockfh'}) { - # File is open - chmod 0666, $self->{'lockfile'}; # assuming you want it a+rw - if(flock($self->{'lockfh'}, LOCK_EX()|LOCK_NB())) { - # The file is locked: No need to retry - $locked = 1; - last; - } else { - if ($! =~ m/Function not implemented/) { - ::warning("flock: $!"); - ::warning("Will wait for a random while\n"); - ::usleep(rand(5000)); - # File cannot be locked: No need to retry - $locked = 2; - last; - } - } - } - # Locking failed in first round - # Sleep and try again - $sleep = ($sleep < 1000) ? ($sleep * 1.1) : ($sleep); - # Random to avoid every sleeping job waking up at the same time - ::usleep(rand()*$sleep); - $total_sleep += $sleep; - if($opt::semaphoretimeout) { - if($total_sleep/1000 > $opt::semaphoretimeout) { - # Timeout: bail out - ::warning("Semaphore timed out. Ignoring timeout."); - $locked = 3; - last; - } - } else { - if($total_sleep/1000 > 30) { - ::warning("Semaphore stuck for 30 seconds. Consider using --semaphoretimeout."); - } - } - } - ::debug("run", "locked $self->{'lockfile'}"); -} - -sub unlock { - my $self = shift; - unlink $self->{'lockfile'}; - close $self->{'lockfh'}; - ::debug("run", "unlocked\n"); -} - -sub mkdir_or_die { - # If dir is not writable: die - my $dir = shift; - my @dir_parts = split(m:/:,$dir); - my ($ddir,$part); - while(defined ($part = shift @dir_parts)) { - $part eq "" and next; - $ddir .= "/".$part; - -d $ddir and next; - mkdir $ddir; - } - if(not -w $dir) { - ::error("Cannot write to $dir: $!\n"); - ::wait_and_exit(255); - } -} - -# Keep perl -w happy -$opt::x = $Semaphore::timeout = $Semaphore::wait = -$Job::file_descriptor_warning_printed = 0; diff --git a/build_tools/make_package.sh b/build_tools/make_package.sh deleted file mode 100755 index 68a5d8a72..000000000 --- a/build_tools/make_package.sh +++ /dev/null @@ -1,129 +0,0 @@ -# shellcheck disable=SC1113 -#/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -set -e - -function log() { - echo "[+] $1" -} - -function fatal() { - echo "[!] $1" - exit 1 -} - -function platform() { - local __resultvar=$1 - if [[ -f "/etc/yum.conf" ]]; then - eval $__resultvar="centos" - elif [[ -f "/etc/dpkg/dpkg.cfg" ]]; then - eval $__resultvar="ubuntu" - else - fatal "Unknwon operating system" - fi -} -platform OS - -function package() { - if [[ $OS = "ubuntu" ]]; then - if dpkg --get-selections | grep --quiet $1; then - log "$1 is already installed. skipping." - else - # shellcheck disable=SC2068 - apt-get install $@ -y - fi - elif [[ $OS = "centos" ]]; then - if rpm -qa | grep --quiet $1; then - log "$1 is already installed. skipping." - else - # shellcheck disable=SC2068 - yum install $@ -y - fi - fi -} - -function detect_fpm_output() { - if [[ $OS = "ubuntu" ]]; then - export FPM_OUTPUT=deb - elif [[ $OS = "centos" ]]; then - export FPM_OUTPUT=rpm - fi -} -detect_fpm_output - -function gem_install() { - if gem list | grep --quiet $1; then - log "$1 is already installed. skipping." - else - # shellcheck disable=SC2068 - gem install $@ - fi -} - -function main() { - if [[ $# -ne 1 ]]; then - fatal "Usage: $0 " - else - log "using rocksdb version: $1" - fi - - if [[ -d /vagrant ]]; then - if [[ $OS = "ubuntu" ]]; then - package g++-4.8 - export CXX=g++-4.8 - - # the deb would depend on libgflags2, but the static lib is the only thing - # installed by make install - package libgflags-dev - - package ruby-all-dev - elif [[ $OS = "centos" ]]; then - pushd /etc/yum.repos.d - if [[ ! -f /etc/yum.repos.d/devtools-1.1.repo ]]; then - wget http://people.centos.org/tru/devtools-1.1/devtools-1.1.repo - fi - package devtoolset-1.1-gcc --enablerepo=testing-1.1-devtools-6 - package devtoolset-1.1-gcc-c++ --enablerepo=testing-1.1-devtools-6 - export CC=/opt/centos/devtoolset-1.1/root/usr/bin/gcc - export CPP=/opt/centos/devtoolset-1.1/root/usr/bin/cpp - export CXX=/opt/centos/devtoolset-1.1/root/usr/bin/c++ - export PATH=$PATH:/opt/centos/devtoolset-1.1/root/usr/bin - popd - if ! rpm -qa | grep --quiet gflags; then - rpm -i https://github.com/schuhschuh/gflags/releases/download/v2.1.0/gflags-devel-2.1.0-1.amd64.rpm - fi - - package ruby - package ruby-devel - package rubygems - package rpm-build - fi - fi - gem_install fpm - - make static_lib - LIBDIR=/usr/lib - if [[ $FPM_OUTPUT = "rpm" ]]; then - LIBDIR=$(rpm --eval '%_libdir') - fi - - rm -rf package - make install DESTDIR=package PREFIX=/usr LIBDIR=$LIBDIR - - fpm \ - -s dir \ - -t $FPM_OUTPUT \ - -C package \ - -n rocksdb \ - -v $1 \ - --url http://rocksdb.org/ \ - -m rocksdb@fb.com \ - --license BSD \ - --vendor Facebook \ - --description "RocksDB is an embeddable persistent key-value store for fast storage." \ - usr -} - -# shellcheck disable=SC2068 -main $@ diff --git a/build_tools/ps_with_stack b/build_tools/ps_with_stack deleted file mode 100755 index ee4256965..000000000 --- a/build_tools/ps_with_stack +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env perl - -use strict; - -open(my $ps, "-|", "ps -wwf"); -my $cols_known = 0; -my $cmd_col = 0; -my $pid_col = 0; -while (<$ps>) { - print; - my @cols = split(/\s+/); - - if (!$cols_known && /CMD/) { - # Parse relevant ps column headers - for (my $i = 0; $i <= $#cols; $i++) { - if ($cols[$i] eq "CMD") { - $cmd_col = $i; - } - if ($cols[$i] eq "PID") { - $pid_col = $i; - } - } - $cols_known = 1; - } else { - my $pid = $cols[$pid_col]; - my $cmd = $cols[$cmd_col]; - # Match numeric PID and relative path command - # -> The intention is only to dump stack traces for hangs in code under - # test, which means we probably just built it and are executing by - # relative path (e.g. ./my_test or foo/bar_test) rather then by absolute - # path (e.g. /usr/bin/time) or PATH search (e.g. grep). - if ($pid =~ /^[0-9]+$/ && $cmd =~ /^[^\/ ]+[\/]/) { - print "Dumping stacks for $pid...\n"; - system("pstack $pid || gdb -batch -p $pid -ex 'thread apply all bt'"); - } - } -} -close $ps; diff --git a/build_tools/regression_build_test.sh b/build_tools/regression_build_test.sh deleted file mode 100755 index 5ecdb1d21..000000000 --- a/build_tools/regression_build_test.sh +++ /dev/null @@ -1,396 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -set -e - -NUM=10000000 - -if [ $# -eq 1 ];then - DATA_DIR=$1 -elif [ $# -eq 2 ];then - DATA_DIR=$1 - STAT_FILE=$2 -fi - -# On the production build servers, set data and stat -# files/directories not in /tmp or else the tempdir cleaning -# scripts will make you very unhappy. -DATA_DIR=${DATA_DIR:-$(mktemp -t -d rocksdb_XXXX)} -STAT_FILE=${STAT_FILE:-$(mktemp -t -u rocksdb_test_stats_XXXX)} - -function cleanup { - rm -rf $DATA_DIR - rm -f $STAT_FILE.* -} - -trap cleanup EXIT - -make release - -# measure fillseq + fill up the DB for overwrite benchmark -./db_bench \ - --benchmarks=fillseq \ - --db=$DATA_DIR \ - --use_existing_db=0 \ - --bloom_bits=10 \ - --num=$NUM \ - --writes=$NUM \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 > ${STAT_FILE}.fillseq - -# measure overwrite performance -./db_bench \ - --benchmarks=overwrite \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$NUM \ - --writes=$((NUM / 10)) \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=8 > ${STAT_FILE}.overwrite - -# fill up the db for readrandom benchmark (1GB total size) -./db_bench \ - --benchmarks=fillseq \ - --db=$DATA_DIR \ - --use_existing_db=0 \ - --bloom_bits=10 \ - --num=$NUM \ - --writes=$NUM \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=1 > /dev/null - -# measure readrandom with 6GB block cache -./db_bench \ - --benchmarks=readrandom \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$NUM \ - --reads=$((NUM / 5)) \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > ${STAT_FILE}.readrandom - -# measure readrandom with 6GB block cache and tailing iterator -./db_bench \ - --benchmarks=readrandom \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$NUM \ - --reads=$((NUM / 5)) \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --use_tailing_iterator=1 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > ${STAT_FILE}.readrandomtailing - -# measure readrandom with 100MB block cache -./db_bench \ - --benchmarks=readrandom \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$NUM \ - --reads=$((NUM / 5)) \ - --cache_size=104857600 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > ${STAT_FILE}.readrandomsmallblockcache - -# measure readrandom with 8k data in memtable -./db_bench \ - --benchmarks=overwrite,readrandom \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$NUM \ - --reads=$((NUM / 5)) \ - --writes=512 \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --write_buffer_size=1000000000 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > ${STAT_FILE}.readrandom_mem_sst - - -# fill up the db for readrandom benchmark with filluniquerandom (1GB total size) -./db_bench \ - --benchmarks=filluniquerandom \ - --db=$DATA_DIR \ - --use_existing_db=0 \ - --bloom_bits=10 \ - --num=$((NUM / 4)) \ - --writes=$((NUM / 4)) \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=1 > /dev/null - -# dummy test just to compact the data -./db_bench \ - --benchmarks=readrandom \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$((NUM / 1000)) \ - --reads=$((NUM / 1000)) \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > /dev/null - -# measure readrandom after load with filluniquerandom with 6GB block cache -./db_bench \ - --benchmarks=readrandom \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$((NUM / 4)) \ - --reads=$((NUM / 4)) \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --disable_auto_compactions=1 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > ${STAT_FILE}.readrandom_filluniquerandom - -# measure readwhilewriting after load with filluniquerandom with 6GB block cache -./db_bench \ - --benchmarks=readwhilewriting \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --bloom_bits=10 \ - --num=$((NUM / 4)) \ - --reads=$((NUM / 4)) \ - --benchmark_write_rate_limit=$(( 110 * 1024 )) \ - --write_buffer_size=100000000 \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=16 > ${STAT_FILE}.readwhilewriting - -# measure memtable performance -- none of the data gets flushed to disk -./db_bench \ - --benchmarks=fillrandom,readrandom, \ - --db=$DATA_DIR \ - --use_existing_db=0 \ - --num=$((NUM / 10)) \ - --reads=$NUM \ - --cache_size=6442450944 \ - --cache_numshardbits=6 \ - --table_cache_numshardbits=4 \ - --write_buffer_size=1000000000 \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --value_size=10 \ - --threads=16 > ${STAT_FILE}.memtablefillreadrandom - -common_in_mem_args="--db=/dev/shm/rocksdb \ - --num_levels=6 \ - --key_size=20 \ - --prefix_size=12 \ - --keys_per_prefix=10 \ - --value_size=100 \ - --compression_type=none \ - --compression_ratio=1 \ - --write_buffer_size=134217728 \ - --max_write_buffer_number=4 \ - --level0_file_num_compaction_trigger=8 \ - --level0_slowdown_writes_trigger=16 \ - --level0_stop_writes_trigger=24 \ - --target_file_size_base=134217728 \ - --max_bytes_for_level_base=1073741824 \ - --disable_wal=0 \ - --wal_dir=/dev/shm/rocksdb \ - --sync=0 \ - --verify_checksum=1 \ - --delete_obsolete_files_period_micros=314572800 \ - --use_plain_table=1 \ - --open_files=-1 \ - --mmap_read=1 \ - --mmap_write=0 \ - --bloom_bits=10 \ - --bloom_locality=1 \ - --perf_level=0" - -# prepare a in-memory DB with 50M keys, total DB size is ~6G -./db_bench \ - $common_in_mem_args \ - --statistics=0 \ - --max_background_compactions=16 \ - --max_background_flushes=16 \ - --benchmarks=filluniquerandom \ - --use_existing_db=0 \ - --num=52428800 \ - --threads=1 > /dev/null - -# Readwhilewriting -./db_bench \ - $common_in_mem_args \ - --statistics=1 \ - --max_background_compactions=4 \ - --max_background_flushes=0 \ - --benchmarks=readwhilewriting\ - --use_existing_db=1 \ - --duration=600 \ - --threads=32 \ - --benchmark_write_rate_limit=9502720 > ${STAT_FILE}.readwhilewriting_in_ram - -# Seekrandomwhilewriting -./db_bench \ - $common_in_mem_args \ - --statistics=1 \ - --max_background_compactions=4 \ - --max_background_flushes=0 \ - --benchmarks=seekrandomwhilewriting \ - --use_existing_db=1 \ - --use_tailing_iterator=1 \ - --duration=600 \ - --threads=32 \ - --benchmark_write_rate_limit=9502720 > ${STAT_FILE}.seekwhilewriting_in_ram - -# measure fillseq with bunch of column families -./db_bench \ - --benchmarks=fillseq \ - --num_column_families=500 \ - --write_buffer_size=1048576 \ - --db=$DATA_DIR \ - --use_existing_db=0 \ - --num=$NUM \ - --writes=$NUM \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 > ${STAT_FILE}.fillseq_lots_column_families - -# measure overwrite performance with bunch of column families -./db_bench \ - --benchmarks=overwrite \ - --num_column_families=500 \ - --write_buffer_size=1048576 \ - --db=$DATA_DIR \ - --use_existing_db=1 \ - --num=$NUM \ - --writes=$((NUM / 10)) \ - --open_files=55000 \ - --statistics=1 \ - --histogram=1 \ - --disable_wal=1 \ - --sync=0 \ - --threads=8 > ${STAT_FILE}.overwrite_lots_column_families - -# send data to ods -function send_to_ods { - key="$1" - value="$2" - - if [ -z $JENKINS_HOME ]; then - # running on devbox, just print out the values - echo $1 $2 - return - fi - - if [ -z "$value" ];then - echo >&2 "ERROR: Key $key doesn't have a value." - return - fi - curl --silent "https://www.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \ - --connect-timeout 60 -} - -function send_benchmark_to_ods { - bench="$1" - bench_key="$2" - file="$3" - - QPS=$(grep $bench $file | awk '{print $5}') - P50_MICROS=$(grep $bench $file -A 6 | grep "Percentiles" | awk '{print $3}' ) - P75_MICROS=$(grep $bench $file -A 6 | grep "Percentiles" | awk '{print $5}' ) - P99_MICROS=$(grep $bench $file -A 6 | grep "Percentiles" | awk '{print $7}' ) - - send_to_ods rocksdb.build.$bench_key.qps $QPS - send_to_ods rocksdb.build.$bench_key.p50_micros $P50_MICROS - send_to_ods rocksdb.build.$bench_key.p75_micros $P75_MICROS - send_to_ods rocksdb.build.$bench_key.p99_micros $P99_MICROS -} - -send_benchmark_to_ods overwrite overwrite $STAT_FILE.overwrite -send_benchmark_to_ods fillseq fillseq $STAT_FILE.fillseq -send_benchmark_to_ods readrandom readrandom $STAT_FILE.readrandom -send_benchmark_to_ods readrandom readrandom_tailing $STAT_FILE.readrandomtailing -send_benchmark_to_ods readrandom readrandom_smallblockcache $STAT_FILE.readrandomsmallblockcache -send_benchmark_to_ods readrandom readrandom_memtable_sst $STAT_FILE.readrandom_mem_sst -send_benchmark_to_ods readrandom readrandom_fillunique_random $STAT_FILE.readrandom_filluniquerandom -send_benchmark_to_ods fillrandom memtablefillrandom $STAT_FILE.memtablefillreadrandom -send_benchmark_to_ods readrandom memtablereadrandom $STAT_FILE.memtablefillreadrandom -send_benchmark_to_ods readwhilewriting readwhilewriting $STAT_FILE.readwhilewriting -send_benchmark_to_ods readwhilewriting readwhilewriting_in_ram ${STAT_FILE}.readwhilewriting_in_ram -send_benchmark_to_ods seekrandomwhilewriting seekwhilewriting_in_ram ${STAT_FILE}.seekwhilewriting_in_ram -send_benchmark_to_ods fillseq fillseq_lots_column_families ${STAT_FILE}.fillseq_lots_column_families -send_benchmark_to_ods overwrite overwrite_lots_column_families ${STAT_FILE}.overwrite_lots_column_families diff --git a/build_tools/run_ci_db_test.ps1 b/build_tools/run_ci_db_test.ps1 deleted file mode 100644 index f20d3213f..000000000 --- a/build_tools/run_ci_db_test.ps1 +++ /dev/null @@ -1,493 +0,0 @@ -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# This script enables you running RocksDB tests by running -# All the tests concurrently and utilizing all the cores -Param( - [switch]$EnableJE = $false, # Look for and use test executable, append _je to listed exclusions - [switch]$RunAll = $false, # Will attempt discover all *_test[_je].exe binaries and run all - # of them as Google suites. I.e. It will run test cases concurrently - # except those mentioned as $Run, those will run as individual test cases - # And any execlued with $ExcludeExes or $ExcludeCases - # It will also not run any individual test cases - # excluded but $ExcludeCasese - [switch]$RunAllExe = $false, # Look for and use test exdcutables, append _je to exclusions automatically - # It will attempt to run them in parallel w/o breaking them up on individual - # test cases. Those listed with $ExcludeExes will be excluded - [string]$SuiteRun = "", # Split test suites in test cases and run in parallel, not compatible with $RunAll - [string]$Run = "", # Run specified executables in parallel but do not split to test cases - [string]$ExcludeCases = "", # Exclude test cases, expects a comma separated list, no spaces - # Takes effect when $RunAll or $SuiteRun is specified. Must have full - # Test cases name including a group and a parameter if any - [string]$ExcludeExes = "", # Exclude exes from consideration, expects a comma separated list, - # no spaces. Takes effect only when $RunAll is specified - [string]$WorkFolder = "", # Direct tests to use that folder. SSD or Ram drive are better options. - # Number of async tasks that would run concurrently. Recommend a number below 64. - # However, CPU utlization really depends on the storage media. Recommend ram based disk. - # a value of 1 will run everything serially - [int]$Concurrency = 8, - [int]$Limit = -1 # -1 means do not limit for test purposes -) - -# Folders and commands must be fullpath to run assuming -# the current folder is at the root of the git enlistment -$StartDate = (Get-Date) -$StartDate - - -$DebugPreference = "Continue" - -# These tests are not google test suites and we should guard -# Against running them as suites -$RunOnly = New-Object System.Collections.Generic.HashSet[string] -$RunOnly.Add("c_test") | Out-Null -$RunOnly.Add("compact_on_deletion_collector_test") | Out-Null -$RunOnly.Add("merge_test") | Out-Null -$RunOnly.Add("stringappend_test") | Out-Null # Apparently incorrectly written -$RunOnly.Add("backup_engine_test") | Out-Null # Disabled -$RunOnly.Add("timer_queue_test") | Out-Null # Not a gtest - -if($RunAll -and $SuiteRun -ne "") { - Write-Error "$RunAll and $SuiteRun are not compatible" - exit 1 -} - -if($RunAllExe -and $Run -ne "") { - Write-Error "$RunAllExe and $Run are not compatible" - exit 1 -} - -# If running under Appveyor assume that root -[string]$Appveyor = $Env:APPVEYOR_BUILD_FOLDER -if($Appveyor -ne "") { - $RootFolder = $Appveyor -} else { - $RootFolder = $PSScriptRoot -replace '\\build_tools', '' -} - -$LogFolder = -Join($RootFolder, "\db_logs\") -$BinariesFolder = -Join($RootFolder, "\build\Debug\") - -if($WorkFolder -eq "") { - - # If TEST_TMPDIR is set use it - [string]$var = $Env:TEST_TMPDIR - if($var -eq "") { - $WorkFolder = -Join($RootFolder, "\db_tests\") - $Env:TEST_TMPDIR = $WorkFolder - } else { - $WorkFolder = $var - } -} else { -# Override from a command line - $Env:TEST_TMPDIR = $WorkFolder -} - -Write-Output "Root: $RootFolder, WorkFolder: $WorkFolder" -Write-Output "BinariesFolder: $BinariesFolder, LogFolder: $LogFolder" - -# Create test directories in the current folder -md -Path $WorkFolder -ErrorAction Ignore | Out-Null -md -Path $LogFolder -ErrorAction Ignore | Out-Null - - -$ExcludeCasesSet = New-Object System.Collections.Generic.HashSet[string] -if($ExcludeCases -ne "") { - Write-Host "ExcludeCases: $ExcludeCases" - $l = $ExcludeCases -split ' ' - ForEach($t in $l) { - $ExcludeCasesSet.Add($t) | Out-Null - } -} - -$ExcludeExesSet = New-Object System.Collections.Generic.HashSet[string] -if($ExcludeExes -ne "") { - Write-Host "ExcludeExe: $ExcludeExes" - $l = $ExcludeExes -split ' ' - ForEach($t in $l) { - $ExcludeExesSet.Add($t) | Out-Null - } -} - - -# Extract the names of its tests by running db_test with --gtest_list_tests. -# This filter removes the "#"-introduced comments, and expands to -# fully-qualified names by changing input like this: -# -# DBTest. -# Empty -# WriteEmptyBatch -# MultiThreaded/MultiThreadedDBTest. -# MultiThreaded/0 # GetParam() = 0 -# MultiThreaded/1 # GetParam() = 1 -# RibbonTypeParamTest/0. # TypeParam = struct DefaultTypesAndSettings -# CompactnessAndBacktrackAndFpRate -# Extremes -# FindOccupancyForSuccessRate -# -# into this: -# -# DBTest.Empty -# DBTest.WriteEmptyBatch -# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0 -# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1 -# RibbonTypeParamTest/0.CompactnessAndBacktrackAndFpRate -# RibbonTypeParamTest/0.Extremes -# RibbonTypeParamTest/0.FindOccupancyForSuccessRate -# -# Output into the parameter in a form TestName -> Log File Name -function ExtractTestCases([string]$GTestExe, $HashTable) { - - $Tests = @() -# Run db_test to get a list of tests and store it into $a array - &$GTestExe --gtest_list_tests | tee -Variable Tests | Out-Null - - # Current group - $Group="" - - ForEach( $l in $Tests) { - - # remove trailing comment if any - $l = $l -replace '\s+\#.*','' - # Leading whitespace is fine - $l = $l -replace '^\s+','' - # Trailing dot is a test group but no whitespace - if ($l -match "\.$" -and $l -notmatch "\s+") { - $Group = $l - } else { - # Otherwise it is a test name, remove leading space - $test = $l - # create a log name - $test = "$Group$test" - - if($ExcludeCasesSet.Contains($test)) { - Write-Warning "$test case is excluded" - continue - } - - $test_log = $test -replace '[\./]','_' - $test_log += ".log" - $log_path = -join ($LogFolder, $test_log) - - # Add to a hashtable - $HashTable.Add($test, $log_path); - } - } -} - -# The function removes trailing .exe siffix if any, -# creates a name for the log file -# Then adds the test name if it was not excluded into -# a HashTable in a form of test_name -> log_path -function MakeAndAdd([string]$token, $HashTable) { - - $test_name = $token -replace '.exe$', '' - $log_name = -join ($test_name, ".log") - $log_path = -join ($LogFolder, $log_name) - $HashTable.Add($test_name, $log_path) -} - -# This function takes a list of Suites to run -# Lists all the test cases in each of the suite -# and populates HashOfHashes -# Ordered by suite(exe) @{ Exe = @{ TestCase = LogName }} -function ProcessSuites($ListOfSuites, $HashOfHashes) { - - $suite_list = $ListOfSuites - # Problem: if you run --gtest_list_tests on - # a non Google Test executable then it will start executing - # and we will get nowhere - ForEach($suite in $suite_list) { - - if($RunOnly.Contains($suite)) { - Write-Warning "$suite is excluded from running as Google test suite" - continue - } - - if($EnableJE) { - $suite += "_je" - } - - $Cases = [ordered]@{} - $Cases.Clear() - $suite_exe = -Join ($BinariesFolder, $suite) - ExtractTestCases -GTestExe $suite_exe -HashTable $Cases - if($Cases.Count -gt 0) { - $HashOfHashes.Add($suite, $Cases); - } - } - - # Make logs and run - if($CasesToRun.Count -lt 1) { - Write-Error "Failed to extract tests from $SuiteRun" - exit 1 - } - -} - -# This will contain all test executables to run - -# Hash table that contains all non suite -# Test executable to run -$TestExes = [ordered]@{} - -# Check for test exe that are not -# Google Test Suites -# Since this is explicitely mentioned it is not subject -# for exclusions -if($Run -ne "") { - - $test_list = $Run -split ' ' - ForEach($t in $test_list) { - - if($EnableJE) { - $t += "_je" - } - MakeAndAdd -token $t -HashTable $TestExes - } - - if($TestExes.Count -lt 1) { - Write-Error "Failed to extract tests from $Run" - exit 1 - } -} elseif($RunAllExe) { - # Discover all the test binaries - if($EnableJE) { - $pattern = "*_test_je.exe" - } else { - $pattern = "*_test.exe" - } - - $search_path = -join ($BinariesFolder, $pattern) - Write-Host "Binaries Search Path: $search_path" - - $DiscoveredExe = @() - dir -Path $search_path | ForEach-Object { - $DiscoveredExe += ($_.Name) - } - - # Remove exclusions - ForEach($e in $DiscoveredExe) { - $e = $e -replace '.exe$', '' - $bare_name = $e -replace '_je$', '' - - if($ExcludeExesSet.Contains($bare_name)) { - Write-Warning "Test $e is excluded" - continue - } - MakeAndAdd -token $e -HashTable $TestExes - } - - if($TestExes.Count -lt 1) { - Write-Error "Failed to discover test executables" - exit 1 - } -} - -# Ordered by exe @{ Exe = @{ TestCase = LogName }} -$CasesToRun = [ordered]@{} - -if($SuiteRun -ne "") { - $suite_list = $SuiteRun -split ' ' - ProcessSuites -ListOfSuites $suite_list -HashOfHashes $CasesToRun -} elseif ($RunAll) { -# Discover all the test binaries - if($EnableJE) { - $pattern = "*_test_je.exe" - } else { - $pattern = "*_test.exe" - } - - $search_path = -join ($BinariesFolder, $pattern) - Write-Host "Binaries Search Path: $search_path" - - $ListOfExe = @() - dir -Path $search_path | ForEach-Object { - $ListOfExe += ($_.Name) - } - - # Exclude those in RunOnly from running as suites - $ListOfSuites = @() - ForEach($e in $ListOfExe) { - - $e = $e -replace '.exe$', '' - $bare_name = $e -replace '_je$', '' - - if($ExcludeExesSet.Contains($bare_name)) { - Write-Warning "Test $e is excluded" - continue - } - - if($RunOnly.Contains($bare_name)) { - MakeAndAdd -token $e -HashTable $TestExes - } else { - $ListOfSuites += $bare_name - } - } - - ProcessSuites -ListOfSuites $ListOfSuites -HashOfHashes $CasesToRun -} - - -# Invoke a test with a filter and redirect all output -$InvokeTestCase = { - param($exe, $test, $log); - &$exe --gtest_filter=$test > $log 2>&1 -} - -# Invoke all tests and redirect output -$InvokeTestAsync = { - param($exe, $log) - &$exe > $log 2>&1 -} - -# Hash that contains tests to rerun if any failed -# Those tests will be rerun sequentially -# $Rerun = [ordered]@{} -# Test limiting factor here -[int]$count = 0 -# Overall status -[bool]$script:success = $true; - -function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal) -{ - # Array to wait for any of the running jobs - $jobs = @() - # Hash JobToLog - $JobToLog = @{} - - # Wait for all to finish and get the results - while(($JobToLog.Count -gt 0) -or - ($TestCmds.Count -gt 0) -or - ($Suites.Count -gt 0)) { - - # Make sure we have maximum concurrent jobs running if anything - # and the $Limit either not set or allows to proceed - while(($JobToLog.Count -lt $ConcurrencyVal) -and - ((($TestCmds.Count -gt 0) -or ($Suites.Count -gt 0)) -and - (($Limit -lt 0) -or ($count -lt $Limit)))) { - - # We always favore suites to run if available - [string]$exe_name = "" - [string]$log_path = "" - $Cases = @{} - - if($Suites.Count -gt 0) { - # Will the first one - ForEach($e in $Suites.Keys) { - $exe_name = $e - $Cases = $Suites[$e] - break - } - [string]$test_case = "" - [string]$log_path = "" - ForEach($c in $Cases.Keys) { - $test_case = $c - $log_path = $Cases[$c] - break - } - - Write-Host "Starting $exe_name::$test_case" - [string]$Exe = -Join ($BinariesFolder, $exe_name) - $job = Start-Job -Name "$exe_name::$test_case" -ArgumentList @($Exe,$test_case,$log_path) -ScriptBlock $InvokeTestCase - $JobToLog.Add($job, $log_path) - - $Cases.Remove($test_case) - if($Cases.Count -lt 1) { - $Suites.Remove($exe_name) - } - - } elseif ($TestCmds.Count -gt 0) { - - ForEach($e in $TestCmds.Keys) { - $exe_name = $e - $log_path = $TestCmds[$e] - break - } - - Write-Host "Starting $exe_name" - [string]$Exe = -Join ($BinariesFolder, $exe_name) - $job = Start-Job -Name $exe_name -ScriptBlock $InvokeTestAsync -ArgumentList @($Exe,$log_path) - $JobToLog.Add($job, $log_path) - - $TestCmds.Remove($exe_name) - - } else { - Write-Error "In the job loop but nothing to run" - exit 1 - } - - ++$count - } # End of Job starting loop - - if($JobToLog.Count -lt 1) { - break - } - - $jobs = @() - foreach($k in $JobToLog.Keys) { $jobs += $k } - - $completed = Wait-Job -Job $jobs -Any - $log = $JobToLog[$completed] - $JobToLog.Remove($completed) - - $message = -join @($completed.Name, " State: ", ($completed.State)) - - $log_content = @(Get-Content $log) - - if($completed.State -ne "Completed") { - $script:success = $false - Write-Warning $message - $log_content | Write-Warning - } else { - # Scan the log. If we find PASSED and no occurrence of FAILED - # then it is a success - [bool]$pass_found = $false - ForEach($l in $log_content) { - - if(($l -match "^\[\s+FAILED") -or - ($l -match "Assertion failed:")) { - $pass_found = $false - break - } - - if(($l -match "^\[\s+PASSED") -or - ($l -match " : PASSED$") -or - ($l -match "^PASS$") -or # Special c_test case - ($l -match "Passed all tests!") ) { - $pass_found = $true - } - } - - if(!$pass_found) { - $script:success = $false; - Write-Warning $message - $log_content | Write-Warning - } else { - Write-Host $message - } - } - - # Remove cached job info from the system - # Should be no output - Receive-Job -Job $completed | Out-Null - } -} - -RunJobs -Suites $CasesToRun -TestCmds $TestExes -ConcurrencyVal $Concurrency - -$EndDate = (Get-Date) - -New-TimeSpan -Start $StartDate -End $EndDate | - ForEach-Object { - "Elapsed time: {0:g}" -f $_ - } - - -if(!$script:success) { -# This does not succeed killing off jobs quick -# So we simply exit -# Remove-Job -Job $jobs -Force -# indicate failure using this exit code - exit 1 - } - - exit 0 diff --git a/build_tools/setup_centos7.sh b/build_tools/setup_centos7.sh deleted file mode 100755 index 474d91a3d..000000000 --- a/build_tools/setup_centos7.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -set -ex - -ROCKSDB_VERSION="6.7.3" -ZSTD_VERSION="1.4.4" - -echo "This script configures CentOS with everything needed to build and run RocksDB" - -yum update -y && yum install epel-release -y - -yum install -y \ - wget \ - gcc-c++ \ - snappy snappy-devel \ - zlib zlib-devel \ - bzip2 bzip2-devel \ - lz4-devel \ - libasan \ - gflags - -mkdir -pv /usr/local/rocksdb-${ROCKSDB_VERSION} -ln -sfT /usr/local/rocksdb-${ROCKSDB_VERSION} /usr/local/rocksdb - -wget -qO /tmp/zstd-${ZSTD_VERSION}.tar.gz https://github.com/facebook/zstd/archive/v${ZSTD_VERSION}.tar.gz -wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/facebook/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz - -cd /tmp - -tar xzvf zstd-${ZSTD_VERSION}.tar.gz -tar xzvf rocksdb-${ROCKSDB_VERSION}.tar.gz -C /usr/local/ - -echo "Installing ZSTD..." -pushd zstd-${ZSTD_VERSION} -make && make install -popd - -echo "Compiling RocksDB..." -cd /usr/local/rocksdb -chown -R vagrant:vagrant /usr/local/rocksdb/ -sudo -u vagrant make static_lib -cd examples/ -sudo -u vagrant LD_LIBRARY_PATH=/usr/local/lib/ make all -sudo -u vagrant LD_LIBRARY_PATH=/usr/local/lib/ ./c_simple_example - diff --git a/build_tools/ubuntu20_image/Dockerfile b/build_tools/ubuntu20_image/Dockerfile deleted file mode 100644 index d81a5e4b2..000000000 --- a/build_tools/ubuntu20_image/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -# from official ubuntu 20.04 -FROM ubuntu:20.04 -# update system -RUN apt-get update && apt-get upgrade -y -# install basic tools -RUN apt-get install -y vim wget curl -# install tzdata noninteractive -RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata -# install git and default compilers -RUN apt-get install -y git gcc g++ clang clang-tools -# install basic package -RUN apt-get install -y lsb-release software-properties-common gnupg -# install gflags, tbb -RUN apt-get install -y libgflags-dev libtbb-dev -# install compression libs -RUN apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev -# install cmake -RUN apt-get install -y cmake -RUN apt-get install -y libssl-dev -# install clang-13 -WORKDIR /root -RUN wget https://apt.llvm.org/llvm.sh -RUN chmod +x llvm.sh -RUN ./llvm.sh 13 all -# install gcc-7, 8, 10, 11, default is 9 -RUN apt-get install -y gcc-7 g++-7 -RUN apt-get install -y gcc-8 g++-8 -RUN apt-get install -y gcc-10 g++-10 -RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test -RUN apt-get install -y gcc-11 g++-11 -# install apt-get install -y valgrind -RUN apt-get install -y valgrind -# install folly depencencies -RUN apt-get install -y libgoogle-glog-dev -# install openjdk 8 -RUN apt-get install -y openjdk-8-jdk -ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-amd64 -# install mingw -RUN apt-get install -y mingw-w64 - -# install gtest-parallel package -RUN git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel -ENV PATH $PATH:/root/gtest-parallel - -# install libprotobuf for fuzzers test -RUN apt-get install -y ninja-build binutils liblzma-dev libz-dev pkg-config autoconf libtool -RUN git clone --branch v1.0 https://github.com/google/libprotobuf-mutator.git ~/libprotobuf-mutator && cd ~/libprotobuf-mutator && git checkout ffd86a32874e5c08a143019aad1aaf0907294c9f && mkdir build && cd build && cmake .. -GNinja -DCMAKE_C_COMPILER=clang-13 -DCMAKE_CXX_COMPILER=clang++-13 -DCMAKE_BUILD_TYPE=Release -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON && ninja && ninja install -ENV PKG_CONFIG_PATH /usr/local/OFF/:/root/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/ -ENV PROTOC_BIN /root/libprotobuf-mutator/build/external.protobuf/bin/protoc - -# install the latest google benchmark -RUN git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark -RUN cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install - -# clean up -RUN rm -rf /var/lib/apt/lists/* -RUN rm -rf /root/benchmark diff --git a/build_tools/update_dependencies.sh b/build_tools/update_dependencies.sh deleted file mode 100755 index c549e5b6e..000000000 --- a/build_tools/update_dependencies.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/sh -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# -# Update dependencies.sh file with the latest avaliable versions - -BASEDIR=$(dirname $0) -OUTPUT="" - -function log_header() -{ - echo "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved." >> "$OUTPUT" - echo "# The file is generated using update_dependencies.sh." >> "$OUTPUT" -} - - -function log_variable() -{ - echo "$1=${!1}" >> "$OUTPUT" -} - - -TP2_LATEST="/data/users/$USER/fbsource/fbcode/third-party2/" -## $1 => lib name -## $2 => lib version (if not provided, will try to pick latest) -## $3 => platform (if not provided, will try to pick latest gcc) -## -## get_lib_base will set a variable named ${LIB_NAME}_BASE to the lib location -function get_lib_base() -{ - local lib_name=$1 - local lib_version=$2 - local lib_platform=$3 - - local result="$TP2_LATEST/$lib_name/" - - # Lib Version - if [ -z "$lib_version" ] || [ "$lib_version" = "LATEST" ]; then - # version is not provided, use latest - result=`ls -dr1v $result/*/ | head -n1` - else - result="$result/$lib_version/" - fi - - # Lib Platform - if [ -z "$lib_platform" ]; then - # platform is not provided, use latest gcc - result=`ls -dr1v $result/gcc-*[^fb]/ | head -n1` - else - echo $lib_platform - result="$result/$lib_platform/" - fi - - result=`ls -1d $result/*/ | head -n1` - - echo Finding link $result - - # lib_name => LIB_NAME_BASE - local __res_var=${lib_name^^}"_BASE" - __res_var=`echo $__res_var | tr - _` - # LIB_NAME_BASE=$result - eval $__res_var=`readlink -f $result` - - log_variable $__res_var -} - -########################################################### -# platform010 dependencies # -########################################################### - -OUTPUT="$BASEDIR/dependencies_platform010.sh" - -rm -f "$OUTPUT" -touch "$OUTPUT" - -echo "Writing dependencies to $OUTPUT" - -# Compilers locations -GCC_BASE=`readlink -f $TP2_LATEST/gcc/11.x/centos7-native/*/` -CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/12/platform010/*/` - -log_header -log_variable GCC_BASE -log_variable CLANG_BASE - -# Libraries locations -get_lib_base libgcc 11.x platform010 -get_lib_base glibc 2.34 platform010 -get_lib_base snappy LATEST platform010 -get_lib_base zlib LATEST platform010 -get_lib_base bzip2 LATEST platform010 -get_lib_base lz4 LATEST platform010 -get_lib_base zstd LATEST platform010 -get_lib_base gflags LATEST platform010 -get_lib_base jemalloc LATEST platform010 -get_lib_base numa LATEST platform010 -get_lib_base libunwind LATEST platform010 -get_lib_base tbb 2018_U5 platform010 -get_lib_base liburing LATEST platform010 -get_lib_base benchmark LATEST platform010 - -get_lib_base kernel-headers fb platform010 -get_lib_base binutils LATEST centos7-native -get_lib_base valgrind LATEST platform010 -get_lib_base lua 5.3.4 platform010 - -git diff $OUTPUT diff --git a/build_tools/version.sh b/build_tools/version.sh deleted file mode 100755 index dbc1a9296..000000000 --- a/build_tools/version.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -if [ "$#" = "0" ]; then - echo "Usage: $0 major|minor|patch|full" - exit 1 -fi - -if [ "$1" = "major" ]; then - cat include/rocksdb/version.h | grep MAJOR | head -n1 | awk '{print $3}' -fi -if [ "$1" = "minor" ]; then - cat include/rocksdb/version.h | grep MINOR | head -n1 | awk '{print $3}' -fi -if [ "$1" = "patch" ]; then - cat include/rocksdb/version.h | grep PATCH | head -n1 | awk '{print $3}' -fi -if [ "$1" = "full" ]; then - awk '/#define ROCKSDB/ { env[$2] = $3 } - END { printf "%s.%s.%s\n", env["ROCKSDB_MAJOR"], - env["ROCKSDB_MINOR"], - env["ROCKSDB_PATCH"] }' \ - include/rocksdb/version.h -fi diff --git a/cache/cache_reservation_manager_test.cc b/cache/cache_reservation_manager_test.cc deleted file mode 100644 index 2a0c318e0..000000000 --- a/cache/cache_reservation_manager_test.cc +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -#include "cache/cache_reservation_manager.h" - -#include -#include -#include - -#include "cache/cache_entry_roles.h" -#include "rocksdb/cache.h" -#include "rocksdb/slice.h" -#include "test_util/testharness.h" -#include "util/coding.h" - -namespace ROCKSDB_NAMESPACE { -class CacheReservationManagerTest : public ::testing::Test { - protected: - static constexpr std::size_t kSizeDummyEntry = - CacheReservationManagerImpl::GetDummyEntrySize(); - static constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry; - static constexpr int kNumShardBits = 0; // 2^0 shard - static constexpr std::size_t kMetaDataChargeOverhead = 10000; - - std::shared_ptr cache = NewLRUCache(kCacheCapacity, kNumShardBits); - std::shared_ptr test_cache_rev_mng; - - CacheReservationManagerTest() { - test_cache_rev_mng = - std::make_shared>( - cache); - } -}; - -TEST_F(CacheReservationManagerTest, GenerateCacheKey) { - std::size_t new_mem_used = 1 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - ASSERT_EQ(s, Status::OK()); - ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 1 * kSizeDummyEntry + kMetaDataChargeOverhead); - - // Next unique Cache key - CacheKey ckey = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - // Get to the underlying values - uint64_t* ckey_data = reinterpret_cast(&ckey); - // Back it up to the one used by CRM (using CacheKey implementation details) - ckey_data[1]--; - - // Specific key (subject to implementation details) - EXPECT_EQ(ckey_data[0], 0); - EXPECT_EQ(ckey_data[1], 2); - - Cache::Handle* handle = cache->Lookup(ckey.AsSlice()); - EXPECT_NE(handle, nullptr) - << "Failed to generate the cache key for the dummy entry correctly"; - // Clean up the returned handle from Lookup() to prevent memory leak - cache->Release(handle); -} - -TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) { - std::size_t new_mem_used = 1 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - ASSERT_EQ(s, Status::OK()); - ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 1 * kSizeDummyEntry); - ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); - std::size_t initial_pinned_usage = cache->GetPinnedUsage(); - ASSERT_GE(initial_pinned_usage, 1 * kSizeDummyEntry); - ASSERT_LT(initial_pinned_usage, - 1 * kSizeDummyEntry + kMetaDataChargeOverhead); - - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to keep cache reservation the same when new_mem_used equals " - "to current cache reservation"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 1 * kSizeDummyEntry) - << "Failed to bookkeep correctly when new_mem_used equals to current " - "cache reservation"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly when new_mem_used " - "equals to current cache reservation"; - EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage) - << "Failed to keep underlying dummy entries the same when new_mem_used " - "equals to current cache reservation"; -} - -TEST_F(CacheReservationManagerTest, - IncreaseCacheReservationByMultiplesOfDummyEntrySize) { - std::size_t new_mem_used = 2 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to increase cache reservation correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 2 * kSizeDummyEntry) - << "Failed to bookkeep cache reservation increase correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry) - << "Failed to increase underlying dummy entries in cache correctly"; - EXPECT_LT(cache->GetPinnedUsage(), - 2 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to increase underlying dummy entries in cache correctly"; -} - -TEST_F(CacheReservationManagerTest, - IncreaseCacheReservationNotByMultiplesOfDummyEntrySize) { - std::size_t new_mem_used = 2 * kSizeDummyEntry + kSizeDummyEntry / 2; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to increase cache reservation correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 3 * kSizeDummyEntry) - << "Failed to bookkeep cache reservation increase correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 3 * kSizeDummyEntry) - << "Failed to increase underlying dummy entries in cache correctly"; - EXPECT_LT(cache->GetPinnedUsage(), - 3 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to increase underlying dummy entries in cache correctly"; -} - -TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest, - IncreaseCacheReservationOnFullCache) { - ; - constexpr std::size_t kSizeDummyEntry = - CacheReservationManagerImpl::GetDummyEntrySize(); - constexpr std::size_t kSmallCacheCapacity = 4 * kSizeDummyEntry; - constexpr std::size_t kBigCacheCapacity = 4096 * kSizeDummyEntry; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - - LRUCacheOptions lo; - lo.capacity = kSmallCacheCapacity; - lo.num_shard_bits = 0; // 2^0 shard - lo.strict_capacity_limit = true; - std::shared_ptr cache = NewLRUCache(lo); - std::shared_ptr test_cache_rev_mng = - std::make_shared>( - cache); - - std::size_t new_mem_used = kSmallCacheCapacity + 1; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::MemoryLimit()) - << "Failed to return status to indicate failure of dummy entry insertion " - "during cache reservation on full cache"; - EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(), - 1 * kSizeDummyEntry) - << "Failed to bookkeep correctly before cache resevation failure happens " - "due to full cache"; - EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), - kSmallCacheCapacity) - << "Failed to bookkeep correctly (i.e, bookkeep only successful dummy " - "entry insertions) when encountering cache resevation failure due to " - "full cache"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) - << "Failed to insert underlying dummy entries correctly when " - "encountering cache resevation failure due to full cache"; - EXPECT_LE(cache->GetPinnedUsage(), kSmallCacheCapacity) - << "Failed to insert underlying dummy entries correctly when " - "encountering cache resevation failure due to full cache"; - - new_mem_used = kSmallCacheCapacity / 2; // 2 dummy entries - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to decrease cache reservation after encountering cache " - "reservation failure due to full cache"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 2 * kSizeDummyEntry) - << "Failed to bookkeep cache reservation decrease correctly after " - "encountering cache reservation due to full cache"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry) - << "Failed to release underlying dummy entries correctly on cache " - "reservation decrease after encountering cache resevation failure due " - "to full cache"; - EXPECT_LT(cache->GetPinnedUsage(), - 2 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to release underlying dummy entries correctly on cache " - "reservation decrease after encountering cache resevation failure due " - "to full cache"; - - // Create cache full again for subsequent tests - new_mem_used = kSmallCacheCapacity + 1; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::MemoryLimit()) - << "Failed to return status to indicate failure of dummy entry insertion " - "during cache reservation on full cache"; - EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(), - 1 * kSizeDummyEntry) - << "Failed to bookkeep correctly before cache resevation failure happens " - "due to full cache"; - EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), - kSmallCacheCapacity) - << "Failed to bookkeep correctly (i.e, bookkeep only successful dummy " - "entry insertions) when encountering cache resevation failure due to " - "full cache"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) - << "Failed to insert underlying dummy entries correctly when " - "encountering cache resevation failure due to full cache"; - EXPECT_LE(cache->GetPinnedUsage(), kSmallCacheCapacity) - << "Failed to insert underlying dummy entries correctly when " - "encountering cache resevation failure due to full cache"; - - // Increase cache capacity so the previously failed insertion can fully - // succeed - cache->SetCapacity(kBigCacheCapacity); - new_mem_used = kSmallCacheCapacity + 1; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to increase cache reservation after increasing cache capacity " - "and mitigating cache full error"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 5 * kSizeDummyEntry) - << "Failed to bookkeep cache reservation increase correctly after " - "increasing cache capacity and mitigating cache full error"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 5 * kSizeDummyEntry) - << "Failed to insert underlying dummy entries correctly after increasing " - "cache capacity and mitigating cache full error"; - EXPECT_LT(cache->GetPinnedUsage(), - 5 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to insert underlying dummy entries correctly after increasing " - "cache capacity and mitigating cache full error"; -} - -TEST_F(CacheReservationManagerTest, - DecreaseCacheReservationByMultiplesOfDummyEntrySize) { - std::size_t new_mem_used = 2 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - ASSERT_EQ(s, Status::OK()); - ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 2 * kSizeDummyEntry); - ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); - ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 2 * kSizeDummyEntry + kMetaDataChargeOverhead); - - new_mem_used = 1 * kSizeDummyEntry; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to decrease cache reservation correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 1 * kSizeDummyEntry) - << "Failed to bookkeep cache reservation decrease correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) - << "Failed to decrease underlying dummy entries in cache correctly"; - EXPECT_LT(cache->GetPinnedUsage(), - 1 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to decrease underlying dummy entries in cache correctly"; -} - -TEST_F(CacheReservationManagerTest, - DecreaseCacheReservationNotByMultiplesOfDummyEntrySize) { - std::size_t new_mem_used = 2 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - ASSERT_EQ(s, Status::OK()); - ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 2 * kSizeDummyEntry); - ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); - ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 2 * kSizeDummyEntry + kMetaDataChargeOverhead); - - new_mem_used = kSizeDummyEntry / 2; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to decrease cache reservation correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 1 * kSizeDummyEntry) - << "Failed to bookkeep cache reservation decrease correctly"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry) - << "Failed to decrease underlying dummy entries in cache correctly"; - EXPECT_LT(cache->GetPinnedUsage(), - 1 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to decrease underlying dummy entries in cache correctly"; -} - -TEST(CacheReservationManagerWithDelayedDecreaseTest, - DecreaseCacheReservationWithDelayedDecrease) { - constexpr std::size_t kSizeDummyEntry = - CacheReservationManagerImpl::GetDummyEntrySize(); - constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; - std::shared_ptr cache = NewLRUCache(lo); - std::shared_ptr test_cache_rev_mng = - std::make_shared>( - cache, true /* delayed_decrease */); - - std::size_t new_mem_used = 8 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - ASSERT_EQ(s, Status::OK()); - ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 8 * kSizeDummyEntry); - ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used); - std::size_t initial_pinned_usage = cache->GetPinnedUsage(); - ASSERT_GE(initial_pinned_usage, 8 * kSizeDummyEntry); - ASSERT_LT(initial_pinned_usage, - 8 * kSizeDummyEntry + kMetaDataChargeOverhead); - - new_mem_used = 6 * kSizeDummyEntry; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 8 * kSizeDummyEntry) - << "Failed to bookkeep correctly when delaying cache reservation " - "decrease"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage) - << "Failed to delay decreasing underlying dummy entries in cache"; - - new_mem_used = 7 * kSizeDummyEntry; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 8 * kSizeDummyEntry) - << "Failed to bookkeep correctly when delaying cache reservation " - "decrease"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage) - << "Failed to delay decreasing underlying dummy entries in cache"; - - new_mem_used = 6 * kSizeDummyEntry - 1; - s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - EXPECT_EQ(s, Status::OK()) - << "Failed to decrease cache reservation correctly when new_mem_used < " - "GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode"; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), - 6 * kSizeDummyEntry) - << "Failed to bookkeep correctly when new_mem_used < " - "GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode"; - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used) - << "Failed to bookkeep the used memory correctly"; - EXPECT_GE(cache->GetPinnedUsage(), 6 * kSizeDummyEntry) - << "Failed to decrease underlying dummy entries in cache when " - "new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed " - "decrease mode"; - EXPECT_LT(cache->GetPinnedUsage(), - 6 * kSizeDummyEntry + kMetaDataChargeOverhead) - << "Failed to decrease underlying dummy entries in cache when " - "new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed " - "decrease mode"; -} - -TEST(CacheReservationManagerDestructorTest, - ReleaseRemainingDummyEntriesOnDestruction) { - constexpr std::size_t kSizeDummyEntry = - CacheReservationManagerImpl::GetDummyEntrySize(); - constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; - std::shared_ptr cache = NewLRUCache(lo); - { - std::shared_ptr test_cache_rev_mng = - std::make_shared>( - cache); - std::size_t new_mem_used = 1 * kSizeDummyEntry; - Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used); - ASSERT_EQ(s, Status::OK()); - ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 1 * kSizeDummyEntry + kMetaDataChargeOverhead); - } - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry) - << "Failed to release remaining underlying dummy entries in cache in " - "CacheReservationManager's destructor"; -} - -TEST(CacheReservationHandleTest, HandleTest) { - constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024; - constexpr std::size_t kSizeDummyEntry = 256 * 1024; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - - LRUCacheOptions lo; - lo.capacity = kOneGigabyte; - lo.num_shard_bits = 0; - std::shared_ptr cache = NewLRUCache(lo); - - std::shared_ptr test_cache_rev_mng( - std::make_shared>( - cache)); - - std::size_t mem_used = 0; - const std::size_t incremental_mem_used_handle_1 = 1 * kSizeDummyEntry; - const std::size_t incremental_mem_used_handle_2 = 2 * kSizeDummyEntry; - std::unique_ptr handle_1, - handle_2; - - // To test consecutive CacheReservationManager::MakeCacheReservation works - // correctly in terms of returning the handle as well as updating cache - // reservation and the latest total memory used - Status s = test_cache_rev_mng->MakeCacheReservation( - incremental_mem_used_handle_1, &handle_1); - mem_used = mem_used + incremental_mem_used_handle_1; - ASSERT_EQ(s, Status::OK()); - EXPECT_TRUE(handle_1 != nullptr); - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used); - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used); - EXPECT_GE(cache->GetPinnedUsage(), mem_used); - EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); - - s = test_cache_rev_mng->MakeCacheReservation(incremental_mem_used_handle_2, - &handle_2); - mem_used = mem_used + incremental_mem_used_handle_2; - ASSERT_EQ(s, Status::OK()); - EXPECT_TRUE(handle_2 != nullptr); - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used); - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used); - EXPECT_GE(cache->GetPinnedUsage(), mem_used); - EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); - - // To test - // CacheReservationManager::CacheReservationHandle::~CacheReservationHandle() - // works correctly in releasing the cache reserved for the handle - handle_1.reset(); - EXPECT_TRUE(handle_1 == nullptr); - mem_used = mem_used - incremental_mem_used_handle_1; - EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used); - EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used); - EXPECT_GE(cache->GetPinnedUsage(), mem_used); - EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); - - // To test the actual CacheReservationManager object won't be deallocated - // as long as there remain handles pointing to it. - // We strongly recommend deallocating CacheReservationManager object only - // after all its handles are deallocated to keep things easy to reasonate - test_cache_rev_mng.reset(); - EXPECT_GE(cache->GetPinnedUsage(), mem_used); - EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead); - - handle_2.reset(); - // The CacheReservationManager object is now deallocated since all the handles - // and its original pointer is gone - mem_used = mem_used - incremental_mem_used_handle_2; - EXPECT_EQ(mem_used, 0); - EXPECT_EQ(cache->GetPinnedUsage(), mem_used); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/cache/cache_test.cc b/cache/cache_test.cc deleted file mode 100644 index febed5b42..000000000 --- a/cache/cache_test.cc +++ /dev/null @@ -1,969 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/cache.h" - -#include -#include -#include -#include -#include - -#include "cache/lru_cache.h" -#include "cache/typed_cache.h" -#include "port/stack_trace.h" -#include "test_util/secondary_cache_test_util.h" -#include "test_util/testharness.h" -#include "util/coding.h" -#include "util/string_util.h" - -// HyperClockCache only supports 16-byte keys, so some of the tests -// originally written for LRUCache do not work on the other caches. -// Those tests were adapted to use 16-byte keys. We kept the original ones. -// TODO: Remove the original tests if they ever become unused. - -namespace ROCKSDB_NAMESPACE { - -namespace { - -// Conversions between numeric keys/values and the types expected by Cache. -std::string EncodeKey16Bytes(int k) { - std::string result; - PutFixed32(&result, k); - result.append(std::string(12, 'a')); // Because we need a 16B output, we - // add a 12-byte padding. - return result; -} - -int DecodeKey16Bytes(const Slice& k) { - assert(k.size() == 16); - return DecodeFixed32(k.data()); // Decodes only the first 4 bytes of k. -} - -std::string EncodeKey32Bits(int k) { - std::string result; - PutFixed32(&result, k); - return result; -} - -int DecodeKey32Bits(const Slice& k) { - assert(k.size() == 4); - return DecodeFixed32(k.data()); -} - -Cache::ObjectPtr EncodeValue(uintptr_t v) { - return reinterpret_cast(v); -} - -int DecodeValue(void* v) { - return static_cast(reinterpret_cast(v)); -} - -const Cache::CacheItemHelper kDumbHelper{ - CacheEntryRole::kMisc, - [](Cache::ObjectPtr /*value*/, MemoryAllocator* /*alloc*/) {}}; - -const Cache::CacheItemHelper kEraseOnDeleteHelper1{ - CacheEntryRole::kMisc, - [](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) { - Cache* cache = static_cast(value); - cache->Erase("foo"); - }}; - -const Cache::CacheItemHelper kEraseOnDeleteHelper2{ - CacheEntryRole::kMisc, - [](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) { - Cache* cache = static_cast(value); - cache->Erase(EncodeKey16Bytes(1234)); - }}; -} // anonymous namespace - -class CacheTest : public testing::Test, - public secondary_cache_test_util::WithCacheTypeParam { - public: - static CacheTest* current_; - static std::string type_; - - static void Deleter(Cache::ObjectPtr v, MemoryAllocator*) { - current_->deleted_values_.push_back(DecodeValue(v)); - } - static const Cache::CacheItemHelper kHelper; - - static const int kCacheSize = 1000; - static const int kNumShardBits = 4; - - static const int kCacheSize2 = 100; - static const int kNumShardBits2 = 2; - - std::vector deleted_values_; - std::shared_ptr cache_; - std::shared_ptr cache2_; - - CacheTest() - : cache_(NewCache(kCacheSize, kNumShardBits, false)), - cache2_(NewCache(kCacheSize2, kNumShardBits2, false)) { - current_ = this; - type_ = GetParam(); - } - - ~CacheTest() override {} - - // These functions encode/decode keys in tests cases that use - // int keys. - // Currently, HyperClockCache requires keys to be 16B long, whereas - // LRUCache doesn't, so the encoding depends on the cache type. - std::string EncodeKey(int k) { - auto type = GetParam(); - if (type == kHyperClock) { - return EncodeKey16Bytes(k); - } else { - return EncodeKey32Bits(k); - } - } - - int DecodeKey(const Slice& k) { - auto type = GetParam(); - if (type == kHyperClock) { - return DecodeKey16Bytes(k); - } else { - return DecodeKey32Bits(k); - } - } - - int Lookup(std::shared_ptr cache, int key) { - Cache::Handle* handle = cache->Lookup(EncodeKey(key)); - const int r = (handle == nullptr) ? -1 : DecodeValue(cache->Value(handle)); - if (handle != nullptr) { - cache->Release(handle); - } - return r; - } - - void Insert(std::shared_ptr cache, int key, int value, - int charge = 1) { - EXPECT_OK(cache->Insert(EncodeKey(key), EncodeValue(value), &kHelper, - charge, /*handle*/ nullptr, Cache::Priority::HIGH)); - } - - void Erase(std::shared_ptr cache, int key) { - cache->Erase(EncodeKey(key)); - } - - int Lookup(int key) { return Lookup(cache_, key); } - - void Insert(int key, int value, int charge = 1) { - Insert(cache_, key, value, charge); - } - - void Erase(int key) { Erase(cache_, key); } - - int Lookup2(int key) { return Lookup(cache2_, key); } - - void Insert2(int key, int value, int charge = 1) { - Insert(cache2_, key, value, charge); - } - - void Erase2(int key) { Erase(cache2_, key); } -}; - -const Cache::CacheItemHelper CacheTest::kHelper{CacheEntryRole::kMisc, - &CacheTest::Deleter}; - -CacheTest* CacheTest::current_; -std::string CacheTest::type_; - -class LRUCacheTest : public CacheTest {}; - -TEST_P(CacheTest, UsageTest) { - auto type = GetParam(); - - // cache is std::shared_ptr and will be automatically cleaned up. - const size_t kCapacity = 100000; - auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata); - auto precise_cache = NewCache(kCapacity, 0, false, kFullChargeCacheMetadata); - ASSERT_EQ(0, cache->GetUsage()); - size_t baseline_meta_usage = precise_cache->GetUsage(); - if (type != kHyperClock) { - ASSERT_EQ(0, baseline_meta_usage); - } - - size_t usage = 0; - char value[10] = "abcdef"; - // make sure everything will be cached - for (int i = 1; i < 100; ++i) { - std::string key; - if (type == kLRU) { - key = std::string(i, 'a'); - } else { - key = EncodeKey(i); - } - auto kv_size = key.size() + 5; - ASSERT_OK(cache->Insert(key, value, &kDumbHelper, kv_size)); - ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, kv_size)); - usage += kv_size; - ASSERT_EQ(usage, cache->GetUsage()); - if (type == kHyperClock) { - ASSERT_EQ(baseline_meta_usage + usage, precise_cache->GetUsage()); - } else { - ASSERT_LT(usage, precise_cache->GetUsage()); - } - } - - cache->EraseUnRefEntries(); - precise_cache->EraseUnRefEntries(); - ASSERT_EQ(0, cache->GetUsage()); - ASSERT_EQ(baseline_meta_usage, precise_cache->GetUsage()); - - // make sure the cache will be overloaded - for (size_t i = 1; i < kCapacity; ++i) { - std::string key; - if (type == kLRU) { - key = std::to_string(i); - } else { - key = EncodeKey(static_cast(1000 + i)); - } - ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5)); - ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5)); - } - - // the usage should be close to the capacity - ASSERT_GT(kCapacity, cache->GetUsage()); - ASSERT_GT(kCapacity, precise_cache->GetUsage()); - ASSERT_LT(kCapacity * 0.95, cache->GetUsage()); - if (type != kHyperClock) { - ASSERT_LT(kCapacity * 0.95, precise_cache->GetUsage()); - } else { - // estimated value size of 1 is weird for clock cache, because - // almost all of the capacity will be used for metadata, and due to only - // using power of 2 table sizes, we might hit strict occupancy limit - // before hitting capacity limit. - ASSERT_LT(kCapacity * 0.80, precise_cache->GetUsage()); - } -} - -// TODO: This test takes longer than expected on ClockCache. This is -// because the values size estimate at construction is too sloppy. -// Fix this. -// Why is it so slow? The cache is constructed with an estimate of 1, but -// then the charge is claimed to be 21. This will cause the hash table -// to be extremely sparse, which in turn means clock needs to scan too -// many slots to find victims. -TEST_P(CacheTest, PinnedUsageTest) { - auto type = GetParam(); - - // cache is std::shared_ptr and will be automatically cleaned up. - const size_t kCapacity = 200000; - auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata); - auto precise_cache = NewCache(kCapacity, 8, false, kFullChargeCacheMetadata); - size_t baseline_meta_usage = precise_cache->GetUsage(); - if (type != kHyperClock) { - ASSERT_EQ(0, baseline_meta_usage); - } - - size_t pinned_usage = 0; - char value[10] = "abcdef"; - - std::forward_list unreleased_handles; - std::forward_list unreleased_handles_in_precise_cache; - - // Add entries. Unpin some of them after insertion. Then, pin some of them - // again. Check GetPinnedUsage(). - for (int i = 1; i < 100; ++i) { - std::string key; - if (type == kLRU) { - key = std::string(i, 'a'); - } else { - key = EncodeKey(i); - } - auto kv_size = key.size() + 5; - Cache::Handle* handle; - Cache::Handle* handle_in_precise_cache; - ASSERT_OK(cache->Insert(key, value, &kDumbHelper, kv_size, &handle)); - assert(handle); - ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, kv_size, - &handle_in_precise_cache)); - assert(handle_in_precise_cache); - pinned_usage += kv_size; - ASSERT_EQ(pinned_usage, cache->GetPinnedUsage()); - ASSERT_LT(pinned_usage, precise_cache->GetPinnedUsage()); - if (i % 2 == 0) { - cache->Release(handle); - precise_cache->Release(handle_in_precise_cache); - pinned_usage -= kv_size; - ASSERT_EQ(pinned_usage, cache->GetPinnedUsage()); - ASSERT_LT(pinned_usage, precise_cache->GetPinnedUsage()); - } else { - unreleased_handles.push_front(handle); - unreleased_handles_in_precise_cache.push_front(handle_in_precise_cache); - } - if (i % 3 == 0) { - unreleased_handles.push_front(cache->Lookup(key)); - auto x = precise_cache->Lookup(key); - assert(x); - unreleased_handles_in_precise_cache.push_front(x); - // If i % 2 == 0, then the entry was unpinned before Lookup, so pinned - // usage increased - if (i % 2 == 0) { - pinned_usage += kv_size; - } - ASSERT_EQ(pinned_usage, cache->GetPinnedUsage()); - ASSERT_LT(pinned_usage, precise_cache->GetPinnedUsage()); - } - } - auto precise_cache_pinned_usage = precise_cache->GetPinnedUsage(); - ASSERT_LT(pinned_usage, precise_cache_pinned_usage); - - // check that overloading the cache does not change the pinned usage - for (size_t i = 1; i < 2 * kCapacity; ++i) { - std::string key; - if (type == kLRU) { - key = std::to_string(i); - } else { - key = EncodeKey(static_cast(1000 + i)); - } - ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5)); - ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5)); - } - ASSERT_EQ(pinned_usage, cache->GetPinnedUsage()); - ASSERT_EQ(precise_cache_pinned_usage, precise_cache->GetPinnedUsage()); - - cache->EraseUnRefEntries(); - precise_cache->EraseUnRefEntries(); - ASSERT_EQ(pinned_usage, cache->GetPinnedUsage()); - ASSERT_EQ(precise_cache_pinned_usage, precise_cache->GetPinnedUsage()); - - // release handles for pinned entries to prevent memory leaks - for (auto handle : unreleased_handles) { - cache->Release(handle); - } - for (auto handle : unreleased_handles_in_precise_cache) { - precise_cache->Release(handle); - } - ASSERT_EQ(0, cache->GetPinnedUsage()); - ASSERT_EQ(0, precise_cache->GetPinnedUsage()); - cache->EraseUnRefEntries(); - precise_cache->EraseUnRefEntries(); - ASSERT_EQ(0, cache->GetUsage()); - ASSERT_EQ(baseline_meta_usage, precise_cache->GetUsage()); -} - -TEST_P(CacheTest, HitAndMiss) { - ASSERT_EQ(-1, Lookup(100)); - - Insert(100, 101); - ASSERT_EQ(101, Lookup(100)); - ASSERT_EQ(-1, Lookup(200)); - ASSERT_EQ(-1, Lookup(300)); - - Insert(200, 201); - ASSERT_EQ(101, Lookup(100)); - ASSERT_EQ(201, Lookup(200)); - ASSERT_EQ(-1, Lookup(300)); - - Insert(100, 102); - if (GetParam() == kHyperClock) { - // ClockCache usually doesn't overwrite on Insert - ASSERT_EQ(101, Lookup(100)); - } else { - ASSERT_EQ(102, Lookup(100)); - } - ASSERT_EQ(201, Lookup(200)); - ASSERT_EQ(-1, Lookup(300)); - - ASSERT_EQ(1U, deleted_values_.size()); - if (GetParam() == kHyperClock) { - ASSERT_EQ(102, deleted_values_[0]); - } else { - ASSERT_EQ(101, deleted_values_[0]); - } -} - -TEST_P(CacheTest, InsertSameKey) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS( - "ClockCache doesn't guarantee Insert overwrite same key."); - return; - } - Insert(1, 1); - Insert(1, 2); - ASSERT_EQ(2, Lookup(1)); -} - -TEST_P(CacheTest, Erase) { - Erase(200); - ASSERT_EQ(0U, deleted_values_.size()); - - Insert(100, 101); - Insert(200, 201); - Erase(100); - ASSERT_EQ(-1, Lookup(100)); - ASSERT_EQ(201, Lookup(200)); - ASSERT_EQ(1U, deleted_values_.size()); - ASSERT_EQ(101, deleted_values_[0]); - - Erase(100); - ASSERT_EQ(-1, Lookup(100)); - ASSERT_EQ(201, Lookup(200)); - ASSERT_EQ(1U, deleted_values_.size()); -} - -TEST_P(CacheTest, EntriesArePinned) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS( - "ClockCache doesn't guarantee Insert overwrite same key."); - return; - } - Insert(100, 101); - Cache::Handle* h1 = cache_->Lookup(EncodeKey(100)); - ASSERT_EQ(101, DecodeValue(cache_->Value(h1))); - ASSERT_EQ(1U, cache_->GetUsage()); - - Insert(100, 102); - Cache::Handle* h2 = cache_->Lookup(EncodeKey(100)); - ASSERT_EQ(102, DecodeValue(cache_->Value(h2))); - ASSERT_EQ(0U, deleted_values_.size()); - ASSERT_EQ(2U, cache_->GetUsage()); - - cache_->Release(h1); - ASSERT_EQ(1U, deleted_values_.size()); - ASSERT_EQ(101, deleted_values_[0]); - ASSERT_EQ(1U, cache_->GetUsage()); - - Erase(100); - ASSERT_EQ(-1, Lookup(100)); - ASSERT_EQ(1U, deleted_values_.size()); - ASSERT_EQ(1U, cache_->GetUsage()); - - cache_->Release(h2); - ASSERT_EQ(2U, deleted_values_.size()); - ASSERT_EQ(102, deleted_values_[1]); - ASSERT_EQ(0U, cache_->GetUsage()); -} - -TEST_P(CacheTest, EvictionPolicy) { - Insert(100, 101); - Insert(200, 201); - // Frequently used entry must be kept around - for (int i = 0; i < 2 * kCacheSize; i++) { - Insert(1000 + i, 2000 + i); - ASSERT_EQ(101, Lookup(100)); - } - ASSERT_EQ(101, Lookup(100)); - ASSERT_EQ(-1, Lookup(200)); -} - -TEST_P(CacheTest, ExternalRefPinsEntries) { - Insert(100, 101); - Cache::Handle* h = cache_->Lookup(EncodeKey(100)); - ASSERT_TRUE(cache_->Ref(h)); - ASSERT_EQ(101, DecodeValue(cache_->Value(h))); - ASSERT_EQ(1U, cache_->GetUsage()); - - for (int i = 0; i < 3; ++i) { - if (i > 0) { - // First release (i == 1) corresponds to Ref(), second release (i == 2) - // corresponds to Lookup(). Then, since all external refs are released, - // the below insertions should push out the cache entry. - cache_->Release(h); - } - // double cache size because the usage bit in block cache prevents 100 from - // being evicted in the first kCacheSize iterations - for (int j = 0; j < 2 * kCacheSize + 100; j++) { - Insert(1000 + j, 2000 + j); - } - // Clock cache is even more stateful and needs more churn to evict - if (GetParam() == kHyperClock) { - for (int j = 0; j < kCacheSize; j++) { - Insert(11000 + j, 11000 + j); - } - } - if (i < 2) { - ASSERT_EQ(101, Lookup(100)); - } - } - ASSERT_EQ(-1, Lookup(100)); -} - -TEST_P(CacheTest, EvictionPolicyRef) { - Insert(100, 101); - Insert(101, 102); - Insert(102, 103); - Insert(103, 104); - Insert(200, 101); - Insert(201, 102); - Insert(202, 103); - Insert(203, 104); - Cache::Handle* h201 = cache_->Lookup(EncodeKey(200)); - Cache::Handle* h202 = cache_->Lookup(EncodeKey(201)); - Cache::Handle* h203 = cache_->Lookup(EncodeKey(202)); - Cache::Handle* h204 = cache_->Lookup(EncodeKey(203)); - Insert(300, 101); - Insert(301, 102); - Insert(302, 103); - Insert(303, 104); - - // Insert entries much more than cache capacity. - for (int i = 0; i < 100 * kCacheSize; i++) { - Insert(1000 + i, 2000 + i); - } - - // Check whether the entries inserted in the beginning - // are evicted. Ones without extra ref are evicted and - // those with are not. - ASSERT_EQ(-1, Lookup(100)); - ASSERT_EQ(-1, Lookup(101)); - ASSERT_EQ(-1, Lookup(102)); - ASSERT_EQ(-1, Lookup(103)); - - ASSERT_EQ(-1, Lookup(300)); - ASSERT_EQ(-1, Lookup(301)); - ASSERT_EQ(-1, Lookup(302)); - ASSERT_EQ(-1, Lookup(303)); - - ASSERT_EQ(101, Lookup(200)); - ASSERT_EQ(102, Lookup(201)); - ASSERT_EQ(103, Lookup(202)); - ASSERT_EQ(104, Lookup(203)); - - // Cleaning up all the handles - cache_->Release(h201); - cache_->Release(h202); - cache_->Release(h203); - cache_->Release(h204); -} - -TEST_P(CacheTest, EvictEmptyCache) { - auto type = GetParam(); - - // Insert item large than capacity to trigger eviction on empty cache. - auto cache = NewCache(1, 0, false); - if (type == kLRU) { - ASSERT_OK(cache->Insert("foo", nullptr, &kDumbHelper, 10)); - } else { - ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10)); - } -} - -TEST_P(CacheTest, EraseFromDeleter) { - auto type = GetParam(); - - // Have deleter which will erase item from cache, which will re-enter - // the cache at that point. - std::shared_ptr cache = NewCache(10, 0, false); - std::string foo, bar; - const Cache::CacheItemHelper* erase_helper; - if (type == kLRU) { - foo = "foo"; - bar = "bar"; - erase_helper = &kEraseOnDeleteHelper1; - } else { - foo = EncodeKey(1234); - bar = EncodeKey(5678); - erase_helper = &kEraseOnDeleteHelper2; - } - - ASSERT_OK(cache->Insert(foo, nullptr, &kDumbHelper, 1)); - ASSERT_OK(cache->Insert(bar, cache.get(), erase_helper, 1)); - - cache->Erase(bar); - ASSERT_EQ(nullptr, cache->Lookup(foo)); - ASSERT_EQ(nullptr, cache->Lookup(bar)); -} - -TEST_P(CacheTest, ErasedHandleState) { - // insert a key and get two handles - Insert(100, 1000); - Cache::Handle* h1 = cache_->Lookup(EncodeKey(100)); - Cache::Handle* h2 = cache_->Lookup(EncodeKey(100)); - ASSERT_EQ(h1, h2); - ASSERT_EQ(DecodeValue(cache_->Value(h1)), 1000); - ASSERT_EQ(DecodeValue(cache_->Value(h2)), 1000); - - // delete the key from the cache - Erase(100); - // can no longer find in the cache - ASSERT_EQ(-1, Lookup(100)); - - // release one handle - cache_->Release(h1); - // still can't find in cache - ASSERT_EQ(-1, Lookup(100)); - - cache_->Release(h2); -} - -TEST_P(CacheTest, HeavyEntries) { - // Add a bunch of light and heavy entries and then count the combined - // size of items still in the cache, which must be approximately the - // same as the total capacity. - const int kLight = 1; - const int kHeavy = 10; - int added = 0; - int index = 0; - while (added < 2 * kCacheSize) { - const int weight = (index & 1) ? kLight : kHeavy; - Insert(index, 1000 + index, weight); - added += weight; - index++; - } - - int cached_weight = 0; - for (int i = 0; i < index; i++) { - const int weight = (i & 1 ? kLight : kHeavy); - int r = Lookup(i); - if (r >= 0) { - cached_weight += weight; - ASSERT_EQ(1000 + i, r); - } - } - ASSERT_LE(cached_weight, kCacheSize + kCacheSize / 10); -} - -TEST_P(CacheTest, NewId) { - uint64_t a = cache_->NewId(); - uint64_t b = cache_->NewId(); - ASSERT_NE(a, b); -} - -TEST_P(CacheTest, ReleaseAndErase) { - std::shared_ptr cache = NewCache(5, 0, false); - Cache::Handle* handle; - Status s = - cache->Insert(EncodeKey(100), EncodeValue(100), &kHelper, 1, &handle); - ASSERT_TRUE(s.ok()); - ASSERT_EQ(5U, cache->GetCapacity()); - ASSERT_EQ(1U, cache->GetUsage()); - ASSERT_EQ(0U, deleted_values_.size()); - auto erased = cache->Release(handle, true); - ASSERT_TRUE(erased); - // This tests that deleter has been called - ASSERT_EQ(1U, deleted_values_.size()); -} - -TEST_P(CacheTest, ReleaseWithoutErase) { - std::shared_ptr cache = NewCache(5, 0, false); - Cache::Handle* handle; - Status s = - cache->Insert(EncodeKey(100), EncodeValue(100), &kHelper, 1, &handle); - ASSERT_TRUE(s.ok()); - ASSERT_EQ(5U, cache->GetCapacity()); - ASSERT_EQ(1U, cache->GetUsage()); - ASSERT_EQ(0U, deleted_values_.size()); - auto erased = cache->Release(handle); - ASSERT_FALSE(erased); - // This tests that deleter is not called. When cache has free capacity it is - // not expected to immediately erase the released items. - ASSERT_EQ(0U, deleted_values_.size()); -} - -namespace { -class Value { - public: - explicit Value(int v) : v_(v) {} - - int v_; - - static constexpr auto kCacheEntryRole = CacheEntryRole::kMisc; -}; - -using SharedCache = BasicTypedSharedCacheInterface; -using TypedHandle = SharedCache::TypedHandle; -} // namespace - -TEST_P(CacheTest, SetCapacity) { - auto type = GetParam(); - if (type == kHyperClock) { - ROCKSDB_GTEST_BYPASS( - "FastLRUCache and HyperClockCache don't support arbitrary capacity " - "adjustments."); - return; - } - // test1: increase capacity - // lets create a cache with capacity 5, - // then, insert 5 elements, then increase capacity - // to 10, returned capacity should be 10, usage=5 - SharedCache cache{NewCache(5, 0, false)}; - std::vector handles(10); - // Insert 5 entries, but not releasing. - for (int i = 0; i < 5; i++) { - std::string key = EncodeKey(i + 1); - Status s = cache.Insert(key, new Value(i + 1), 1, &handles[i]); - ASSERT_TRUE(s.ok()); - } - ASSERT_EQ(5U, cache.get()->GetCapacity()); - ASSERT_EQ(5U, cache.get()->GetUsage()); - cache.get()->SetCapacity(10); - ASSERT_EQ(10U, cache.get()->GetCapacity()); - ASSERT_EQ(5U, cache.get()->GetUsage()); - - // test2: decrease capacity - // insert 5 more elements to cache, then release 5, - // then decrease capacity to 7, final capacity should be 7 - // and usage should be 7 - for (int i = 5; i < 10; i++) { - std::string key = EncodeKey(i + 1); - Status s = cache.Insert(key, new Value(i + 1), 1, &handles[i]); - ASSERT_TRUE(s.ok()); - } - ASSERT_EQ(10U, cache.get()->GetCapacity()); - ASSERT_EQ(10U, cache.get()->GetUsage()); - for (int i = 0; i < 5; i++) { - cache.Release(handles[i]); - } - ASSERT_EQ(10U, cache.get()->GetCapacity()); - ASSERT_EQ(10U, cache.get()->GetUsage()); - cache.get()->SetCapacity(7); - ASSERT_EQ(7, cache.get()->GetCapacity()); - ASSERT_EQ(7, cache.get()->GetUsage()); - - // release remaining 5 to keep valgrind happy - for (int i = 5; i < 10; i++) { - cache.Release(handles[i]); - } - - // Make sure this doesn't crash or upset ASAN/valgrind - cache.get()->DisownData(); -} - -TEST_P(LRUCacheTest, SetStrictCapacityLimit) { - // test1: set the flag to false. Insert more keys than capacity. See if they - // all go through. - SharedCache cache{NewCache(5, 0, false)}; - std::vector handles(10); - Status s; - for (int i = 0; i < 10; i++) { - std::string key = EncodeKey(i + 1); - s = cache.Insert(key, new Value(i + 1), 1, &handles[i]); - ASSERT_OK(s); - ASSERT_NE(nullptr, handles[i]); - } - ASSERT_EQ(10, cache.get()->GetUsage()); - - // test2: set the flag to true. Insert and check if it fails. - std::string extra_key = EncodeKey(100); - Value* extra_value = new Value(0); - cache.get()->SetStrictCapacityLimit(true); - TypedHandle* handle; - s = cache.Insert(extra_key, extra_value, 1, &handle); - ASSERT_TRUE(s.IsMemoryLimit()); - ASSERT_EQ(nullptr, handle); - ASSERT_EQ(10, cache.get()->GetUsage()); - - for (int i = 0; i < 10; i++) { - cache.Release(handles[i]); - } - - // test3: init with flag being true. - SharedCache cache2{NewCache(5, 0, true)}; - for (int i = 0; i < 5; i++) { - std::string key = EncodeKey(i + 1); - s = cache2.Insert(key, new Value(i + 1), 1, &handles[i]); - ASSERT_OK(s); - ASSERT_NE(nullptr, handles[i]); - } - s = cache2.Insert(extra_key, extra_value, 1, &handle); - ASSERT_TRUE(s.IsMemoryLimit()); - ASSERT_EQ(nullptr, handle); - // test insert without handle - s = cache2.Insert(extra_key, extra_value, 1); - // AS if the key have been inserted into cache but get evicted immediately. - ASSERT_OK(s); - ASSERT_EQ(5, cache2.get()->GetUsage()); - ASSERT_EQ(nullptr, cache2.Lookup(extra_key)); - - for (int i = 0; i < 5; i++) { - cache2.Release(handles[i]); - } -} - -TEST_P(CacheTest, OverCapacity) { - size_t n = 10; - - // a LRUCache with n entries and one shard only - SharedCache cache{NewCache(n, 0, false)}; - std::vector handles(n + 1); - - // Insert n+1 entries, but not releasing. - for (int i = 0; i < static_cast(n + 1); i++) { - std::string key = EncodeKey(i + 1); - Status s = cache.Insert(key, new Value(i + 1), 1, &handles[i]); - ASSERT_TRUE(s.ok()); - } - - // Guess what's in the cache now? - for (int i = 0; i < static_cast(n + 1); i++) { - std::string key = EncodeKey(i + 1); - auto h = cache.Lookup(key); - ASSERT_TRUE(h != nullptr); - if (h) cache.Release(h); - } - - // the cache is over capacity since nothing could be evicted - ASSERT_EQ(n + 1U, cache.get()->GetUsage()); - for (int i = 0; i < static_cast(n + 1); i++) { - cache.Release(handles[i]); - } - - if (GetParam() == kHyperClock) { - // Make sure eviction is triggered. - ASSERT_OK(cache.Insert(EncodeKey(-1), nullptr, 1, &handles[0])); - - // cache is under capacity now since elements were released - ASSERT_GE(n, cache.get()->GetUsage()); - - // clean up - cache.Release(handles[0]); - } else { - // LRUCache checks for over-capacity in Release. - - // cache is exactly at capacity now with minimal eviction - ASSERT_EQ(n, cache.get()->GetUsage()); - - // element 0 is evicted and the rest is there - // This is consistent with the LRU policy since the element 0 - // was released first - for (int i = 0; i < static_cast(n + 1); i++) { - std::string key = EncodeKey(i + 1); - auto h = cache.Lookup(key); - if (h) { - ASSERT_NE(static_cast(i), 0U); - cache.Release(h); - } else { - ASSERT_EQ(static_cast(i), 0U); - } - } - } -} - -TEST_P(CacheTest, ApplyToAllEntriesTest) { - std::vector callback_state; - const auto callback = [&](const Slice& key, Cache::ObjectPtr value, - size_t charge, - const Cache::CacheItemHelper* helper) { - callback_state.push_back(std::to_string(DecodeKey(key)) + "," + - std::to_string(DecodeValue(value)) + "," + - std::to_string(charge)); - assert(helper == &CacheTest::kHelper); - }; - - std::vector inserted; - callback_state.clear(); - - for (int i = 0; i < 10; ++i) { - Insert(i, i * 2, i + 1); - inserted.push_back(std::to_string(i) + "," + std::to_string(i * 2) + "," + - std::to_string(i + 1)); - } - cache_->ApplyToAllEntries(callback, /*opts*/ {}); - - std::sort(inserted.begin(), inserted.end()); - std::sort(callback_state.begin(), callback_state.end()); - ASSERT_EQ(inserted.size(), callback_state.size()); - for (int i = 0; i < static_cast(inserted.size()); ++i) { - EXPECT_EQ(inserted[i], callback_state[i]); - } -} - -TEST_P(CacheTest, ApplyToAllEntriesDuringResize) { - // This is a mini-stress test of ApplyToAllEntries, to ensure - // items in the cache that are neither added nor removed - // during ApplyToAllEntries are counted exactly once. - - // Insert some entries that we expect to be seen exactly once - // during iteration. - constexpr int kSpecialCharge = 2; - constexpr int kNotSpecialCharge = 1; - constexpr int kSpecialCount = 100; - size_t expected_usage = 0; - for (int i = 0; i < kSpecialCount; ++i) { - Insert(i, i * 2, kSpecialCharge); - expected_usage += kSpecialCharge; - } - - // For callback - int special_count = 0; - const auto callback = [&](const Slice&, Cache::ObjectPtr, size_t charge, - const Cache::CacheItemHelper*) { - if (charge == static_cast(kSpecialCharge)) { - ++special_count; - } - }; - - // Start counting - std::thread apply_thread([&]() { - // Use small average_entries_per_lock to make the problem difficult - Cache::ApplyToAllEntriesOptions opts; - opts.average_entries_per_lock = 2; - cache_->ApplyToAllEntries(callback, opts); - }); - - // In parallel, add more entries, enough to cause resize but not enough - // to cause ejections. (Note: if any cache shard is over capacity, there - // will be ejections) - for (int i = kSpecialCount * 1; i < kSpecialCount * 5; ++i) { - Insert(i, i * 2, kNotSpecialCharge); - expected_usage += kNotSpecialCharge; - } - - apply_thread.join(); - // verify no evictions - ASSERT_EQ(cache_->GetUsage(), expected_usage); - // verify everything seen in ApplyToAllEntries - ASSERT_EQ(special_count, kSpecialCount); -} - -TEST_P(CacheTest, DefaultShardBits) { - // Prevent excessive allocation (to save time & space) - estimated_value_size_ = 100000; - // Implementations use different minimum shard sizes - size_t min_shard_size = - (GetParam() == kHyperClock ? 32U * 1024U : 512U) * 1024U; - - std::shared_ptr cache = NewCache(32U * min_shard_size); - ShardedCacheBase* sc = dynamic_cast(cache.get()); - ASSERT_EQ(5, sc->GetNumShardBits()); - - cache = NewCache(min_shard_size / 1000U * 999U); - sc = dynamic_cast(cache.get()); - ASSERT_EQ(0, sc->GetNumShardBits()); - - cache = NewCache(3U * 1024U * 1024U * 1024U); - sc = dynamic_cast(cache.get()); - // current maximum of 6 - ASSERT_EQ(6, sc->GetNumShardBits()); - - if constexpr (sizeof(size_t) > 4) { - cache = NewCache(128U * min_shard_size); - sc = dynamic_cast(cache.get()); - // current maximum of 6 - ASSERT_EQ(6, sc->GetNumShardBits()); - } -} - -TEST_P(CacheTest, GetChargeAndDeleter) { - Insert(1, 2); - Cache::Handle* h1 = cache_->Lookup(EncodeKey(1)); - ASSERT_EQ(2, DecodeValue(cache_->Value(h1))); - ASSERT_EQ(1, cache_->GetCharge(h1)); - ASSERT_EQ(&CacheTest::kHelper, cache_->GetCacheItemHelper(h1)); - cache_->Release(h1); -} - -INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest, - secondary_cache_test_util::GetTestingCacheTypes()); -INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest, - testing::Values(secondary_cache_test_util::kLRU)); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/cache/compressed_secondary_cache_test.cc b/cache/compressed_secondary_cache_test.cc deleted file mode 100644 index 1e41fc142..000000000 --- a/cache/compressed_secondary_cache_test.cc +++ /dev/null @@ -1,980 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "cache/compressed_secondary_cache.h" - -#include -#include -#include -#include - -#include "memory/jemalloc_nodump_allocator.h" -#include "rocksdb/convenience.h" -#include "test_util/secondary_cache_test_util.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -using secondary_cache_test_util::GetTestingCacheTypes; -using secondary_cache_test_util::WithCacheType; - -// 16 bytes for HCC compatibility -const std::string key0 = "____ ____key0"; -const std::string key1 = "____ ____key1"; -const std::string key2 = "____ ____key2"; -const std::string key3 = "____ ____key3"; - -class CompressedSecondaryCacheTestBase : public testing::Test, - public WithCacheType { - public: - CompressedSecondaryCacheTestBase() {} - ~CompressedSecondaryCacheTestBase() override = default; - - protected: - void BasicTestHelper(std::shared_ptr sec_cache, - bool sec_cache_is_compressed) { - get_perf_context()->Reset(); - bool kept_in_sec_cache{true}; - // Lookup an non-existent key. - std::unique_ptr handle0 = - sec_cache->Lookup(key0, GetHelper(), this, true, /*advise_erase=*/true, - kept_in_sec_cache); - ASSERT_EQ(handle0, nullptr); - - Random rnd(301); - // Insert and Lookup the item k1 for the first time. - std::string str1(rnd.RandomString(1000)); - TestItem item1(str1.data(), str1.length()); - // A dummy handle is inserted if the item is inserted for the first time. - ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - - std::unique_ptr handle1_1 = - sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false, - kept_in_sec_cache); - ASSERT_EQ(handle1_1, nullptr); - - // Insert and Lookup the item k1 for the second time and advise erasing it. - ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1); - - std::unique_ptr handle1_2 = - sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true, - kept_in_sec_cache); - ASSERT_NE(handle1_2, nullptr); - ASSERT_FALSE(kept_in_sec_cache); - if (sec_cache_is_compressed) { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, - 1000); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, - 1007); - } else { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - } - - std::unique_ptr val1 = - std::unique_ptr(static_cast(handle1_2->Value())); - ASSERT_NE(val1, nullptr); - ASSERT_EQ(memcmp(val1->Buf(), item1.Buf(), item1.Size()), 0); - - // Lookup the item k1 again. - std::unique_ptr handle1_3 = - sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true, - kept_in_sec_cache); - ASSERT_EQ(handle1_3, nullptr); - - // Insert and Lookup the item k2. - std::string str2(rnd.RandomString(1000)); - TestItem item2(str2.data(), str2.length()); - ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2); - std::unique_ptr handle2_1 = - sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false, - kept_in_sec_cache); - ASSERT_EQ(handle2_1, nullptr); - - ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2); - if (sec_cache_is_compressed) { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, - 2000); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, - 2014); - } else { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - } - std::unique_ptr handle2_2 = - sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false, - kept_in_sec_cache); - ASSERT_NE(handle2_2, nullptr); - std::unique_ptr val2 = - std::unique_ptr(static_cast(handle2_2->Value())); - ASSERT_NE(val2, nullptr); - ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0); - - std::vector handles = {handle1_2.get(), - handle2_2.get()}; - sec_cache->WaitAll(handles); - - sec_cache.reset(); - } - - void BasicTest(bool sec_cache_is_compressed, bool use_jemalloc) { - CompressedSecondaryCacheOptions opts; - opts.capacity = 2048; - opts.num_shard_bits = 0; - - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - opts.compression_type = CompressionType::kNoCompression; - sec_cache_is_compressed = false; - } - } else { - opts.compression_type = CompressionType::kNoCompression; - } - - if (use_jemalloc) { - JemallocAllocatorOptions jopts; - std::shared_ptr allocator; - std::string msg; - if (JemallocNodumpAllocator::IsSupported(&msg)) { - Status s = NewJemallocNodumpAllocator(jopts, &allocator); - if (s.ok()) { - opts.memory_allocator = allocator; - } - } else { - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - } - } - std::shared_ptr sec_cache = - NewCompressedSecondaryCache(opts); - - BasicTestHelper(sec_cache, sec_cache_is_compressed); - } - - void FailsTest(bool sec_cache_is_compressed) { - CompressedSecondaryCacheOptions secondary_cache_opts; - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - } else { - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - - secondary_cache_opts.capacity = 1100; - secondary_cache_opts.num_shard_bits = 0; - std::shared_ptr sec_cache = - NewCompressedSecondaryCache(secondary_cache_opts); - - // Insert and Lookup the first item. - Random rnd(301); - std::string str1(rnd.RandomString(1000)); - TestItem item1(str1.data(), str1.length()); - // Insert a dummy handle. - ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper())); - // Insert k1. - ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper())); - - // Insert and Lookup the second item. - std::string str2(rnd.RandomString(200)); - TestItem item2(str2.data(), str2.length()); - // Insert a dummy handle, k1 is not evicted. - ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper())); - bool kept_in_sec_cache{false}; - std::unique_ptr handle1 = - sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false, - kept_in_sec_cache); - ASSERT_EQ(handle1, nullptr); - - // Insert k2 and k1 is evicted. - ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper())); - std::unique_ptr handle2 = - sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false, - kept_in_sec_cache); - ASSERT_NE(handle2, nullptr); - std::unique_ptr val2 = - std::unique_ptr(static_cast(handle2->Value())); - ASSERT_NE(val2, nullptr); - ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0); - - // Insert k1 again and a dummy handle is inserted. - ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper())); - - std::unique_ptr handle1_1 = - sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false, - kept_in_sec_cache); - ASSERT_EQ(handle1_1, nullptr); - - // Create Fails. - SetFailCreate(true); - std::unique_ptr handle2_1 = - sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/true, - kept_in_sec_cache); - ASSERT_EQ(handle2_1, nullptr); - - // Save Fails. - std::string str3 = rnd.RandomString(10); - TestItem item3(str3.data(), str3.length()); - // The Status is OK because a dummy handle is inserted. - ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail())); - ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail())); - - sec_cache.reset(); - } - - void BasicIntegrationTest(bool sec_cache_is_compressed, - bool enable_custom_split_merge) { - CompressedSecondaryCacheOptions secondary_cache_opts; - - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - sec_cache_is_compressed = false; - } - } else { - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - - secondary_cache_opts.capacity = 6000; - secondary_cache_opts.num_shard_bits = 0; - secondary_cache_opts.enable_custom_split_merge = enable_custom_split_merge; - std::shared_ptr secondary_cache = - NewCompressedSecondaryCache(secondary_cache_opts); - std::shared_ptr cache = NewCache( - /*_capacity =*/1300, /*_num_shard_bits =*/0, - /*_strict_capacity_limit =*/true, secondary_cache); - std::shared_ptr stats = CreateDBStatistics(); - - get_perf_context()->Reset(); - Random rnd(301); - std::string str1 = rnd.RandomString(1001); - auto item1_1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length())); - - std::string str2 = rnd.RandomString(1012); - auto item2_1 = new TestItem(str2.data(), str2.length()); - // After this Insert, primary cache contains k2 and secondary cache contains - // k1's dummy item. - ASSERT_OK(cache->Insert(key2, item2_1, GetHelper(), str2.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - - std::string str3 = rnd.RandomString(1024); - auto item3_1 = new TestItem(str3.data(), str3.length()); - // After this Insert, primary cache contains k3 and secondary cache contains - // k1's dummy item and k2's dummy item. - ASSERT_OK(cache->Insert(key3, item3_1, GetHelper(), str3.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2); - - // After this Insert, primary cache contains k1 and secondary cache contains - // k1's dummy item, k2's dummy item, and k3's dummy item. - auto item1_2 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1_2, GetHelper(), str1.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3); - - // After this Insert, primary cache contains k2 and secondary cache contains - // k1's item, k2's dummy item, and k3's dummy item. - auto item2_2 = new TestItem(str2.data(), str2.length()); - ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1); - if (sec_cache_is_compressed) { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, - str1.length()); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, - 1008); - } else { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - } - - // After this Insert, primary cache contains k3 and secondary cache contains - // k1's item and k2's item. - auto item3_2 = new TestItem(str3.data(), str3.length()); - ASSERT_OK(cache->Insert(key3, item3_2, GetHelper(), str3.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2); - if (sec_cache_is_compressed) { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, - str1.length() + str2.length()); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, - 2027); - } else { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - } - - Cache::Handle* handle; - handle = cache->Lookup(key3, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - ASSERT_NE(handle, nullptr); - auto val3 = static_cast(cache->Value(handle)); - ASSERT_NE(val3, nullptr); - ASSERT_EQ(memcmp(val3->Buf(), item3_2->Buf(), item3_2->Size()), 0); - cache->Release(handle); - - // Lookup an non-existent key. - handle = cache->Lookup(key0, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - ASSERT_EQ(handle, nullptr); - - // This Lookup should just insert a dummy handle in the primary cache - // and the k1 is still in the secondary cache. - handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1); - auto val1_1 = static_cast(cache->Value(handle)); - ASSERT_NE(val1_1, nullptr); - ASSERT_EQ(memcmp(val1_1->Buf(), str1.data(), str1.size()), 0); - cache->Release(handle); - - // This Lookup should erase k1 from the secondary cache and insert - // it into primary cache; then k3 is demoted. - // k2 and k3 are in secondary cache. - handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 3); - cache->Release(handle); - - // k2 is still in secondary cache. - handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 2); - cache->Release(handle); - - // Testing SetCapacity(). - ASSERT_OK(secondary_cache->SetCapacity(0)); - handle = cache->Lookup(key3, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - ASSERT_EQ(handle, nullptr); - - ASSERT_OK(secondary_cache->SetCapacity(7000)); - size_t capacity; - ASSERT_OK(secondary_cache->GetCapacity(capacity)); - ASSERT_EQ(capacity, 7000); - auto item1_3 = new TestItem(str1.data(), str1.length()); - // After this Insert, primary cache contains k1. - ASSERT_OK(cache->Insert(key1, item1_3, GetHelper(), str2.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 4); - - auto item2_3 = new TestItem(str2.data(), str2.length()); - // After this Insert, primary cache contains k2 and secondary cache contains - // k1's dummy item. - ASSERT_OK(cache->Insert(key2, item2_3, GetHelper(), str1.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 4); - - auto item1_4 = new TestItem(str1.data(), str1.length()); - // After this Insert, primary cache contains k1 and secondary cache contains - // k1's dummy item and k2's dummy item. - ASSERT_OK(cache->Insert(key1, item1_4, GetHelper(), str2.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 5); - - auto item2_4 = new TestItem(str2.data(), str2.length()); - // After this Insert, primary cache contains k2 and secondary cache contains - // k1's real item and k2's dummy item. - ASSERT_OK(cache->Insert(key2, item2_4, GetHelper(), str2.length())); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 5); - // This Lookup should just insert a dummy handle in the primary cache - // and the k1 is still in the secondary cache. - handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW, - stats.get()); - - ASSERT_NE(handle, nullptr); - cache->Release(handle); - ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 3); - - cache.reset(); - secondary_cache.reset(); - } - - void BasicIntegrationFailTest(bool sec_cache_is_compressed) { - CompressedSecondaryCacheOptions secondary_cache_opts; - - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - } else { - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - - secondary_cache_opts.capacity = 6000; - secondary_cache_opts.num_shard_bits = 0; - std::shared_ptr secondary_cache = - NewCompressedSecondaryCache(secondary_cache_opts); - - std::shared_ptr cache = NewCache( - /*_capacity=*/1300, /*_num_shard_bits=*/0, - /*_strict_capacity_limit=*/false, secondary_cache); - - Random rnd(301); - std::string str1 = rnd.RandomString(1001); - auto item1 = std::make_unique(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1.get(), GetHelper(), str1.length())); - item1.release(); // Appease clang-analyze "potential memory leak" - - Cache::Handle* handle; - handle = cache->Lookup(key2, nullptr, this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - - Cache::AsyncLookupHandle ah; - ah.key = key2; - ah.helper = GetHelper(); - ah.create_context = this; - ah.priority = Cache::Priority::LOW; - cache->StartAsyncLookup(ah); - cache->Wait(ah); - ASSERT_EQ(ah.Result(), nullptr); - - cache.reset(); - secondary_cache.reset(); - } - - void IntegrationSaveFailTest(bool sec_cache_is_compressed) { - CompressedSecondaryCacheOptions secondary_cache_opts; - - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - } else { - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - - secondary_cache_opts.capacity = 6000; - secondary_cache_opts.num_shard_bits = 0; - - std::shared_ptr secondary_cache = - NewCompressedSecondaryCache(secondary_cache_opts); - - std::shared_ptr cache = NewCache( - /*_capacity=*/1300, /*_num_shard_bits=*/0, - /*_strict_capacity_limit=*/true, secondary_cache); - - Random rnd(301); - std::string str1 = rnd.RandomString(1001); - auto item1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1, GetHelperFail(), str1.length())); - - std::string str2 = rnd.RandomString(1002); - auto item2 = new TestItem(str2.data(), str2.length()); - // k1 should be demoted to the secondary cache. - ASSERT_OK(cache->Insert(key2, item2, GetHelperFail(), str2.length())); - - Cache::Handle* handle; - handle = cache->Lookup(key2, GetHelperFail(), this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - // This lookup should fail, since k1 demotion would have failed. - handle = cache->Lookup(key1, GetHelperFail(), this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - // Since k1 was not promoted, k2 should still be in cache. - handle = cache->Lookup(key2, GetHelperFail(), this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - - cache.reset(); - secondary_cache.reset(); - } - - void IntegrationCreateFailTest(bool sec_cache_is_compressed) { - CompressedSecondaryCacheOptions secondary_cache_opts; - - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - } else { - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - - secondary_cache_opts.capacity = 6000; - secondary_cache_opts.num_shard_bits = 0; - - std::shared_ptr secondary_cache = - NewCompressedSecondaryCache(secondary_cache_opts); - - std::shared_ptr cache = NewCache( - /*_capacity=*/1300, /*_num_shard_bits=*/0, - /*_strict_capacity_limit=*/true, secondary_cache); - - Random rnd(301); - std::string str1 = rnd.RandomString(1001); - auto item1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1, GetHelper(), str1.length())); - - std::string str2 = rnd.RandomString(1002); - auto item2 = new TestItem(str2.data(), str2.length()); - // k1 should be demoted to the secondary cache. - ASSERT_OK(cache->Insert(key2, item2, GetHelper(), str2.length())); - - Cache::Handle* handle; - SetFailCreate(true); - handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - // This lookup should fail, since k1 creation would have failed - handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - // Since k1 didn't get promoted, k2 should still be in cache - handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - - cache.reset(); - secondary_cache.reset(); - } - - void IntegrationFullCapacityTest(bool sec_cache_is_compressed) { - CompressedSecondaryCacheOptions secondary_cache_opts; - - if (sec_cache_is_compressed) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - } else { - secondary_cache_opts.compression_type = CompressionType::kNoCompression; - } - - secondary_cache_opts.capacity = 6000; - secondary_cache_opts.num_shard_bits = 0; - - std::shared_ptr secondary_cache = - NewCompressedSecondaryCache(secondary_cache_opts); - - std::shared_ptr cache = NewCache( - /*_capacity=*/1300, /*_num_shard_bits=*/0, - /*_strict_capacity_limit=*/false, secondary_cache); - - Random rnd(301); - std::string str1 = rnd.RandomString(1001); - auto item1_1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length())); - - std::string str2 = rnd.RandomString(1002); - std::string str2_clone{str2}; - auto item2 = new TestItem(str2.data(), str2.length()); - // After this Insert, primary cache contains k2 and secondary cache contains - // k1's dummy item. - ASSERT_OK(cache->Insert(key2, item2, GetHelper(), str2.length())); - - // After this Insert, primary cache contains k1 and secondary cache contains - // k1's dummy item and k2's dummy item. - auto item1_2 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(key1, item1_2, GetHelper(), str1.length())); - - auto item2_2 = new TestItem(str2.data(), str2.length()); - // After this Insert, primary cache contains k2 and secondary cache contains - // k1's item and k2's dummy item. - ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length())); - - Cache::Handle* handle2; - handle2 = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW); - ASSERT_NE(handle2, nullptr); - cache->Release(handle2); - - // k1 promotion should fail because cache is at capacity and - // strict_capacity_limit is true, but the lookup should still succeed. - // A k1's dummy item is inserted into primary cache. - Cache::Handle* handle1; - handle1 = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW); - ASSERT_NE(handle1, nullptr); - cache->Release(handle1); - - // Since k1 didn't get inserted, k2 should still be in cache - handle2 = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW); - ASSERT_NE(handle2, nullptr); - cache->Release(handle2); - - cache.reset(); - secondary_cache.reset(); - } - - void SplitValueIntoChunksTest() { - JemallocAllocatorOptions jopts; - std::shared_ptr allocator; - std::string msg; - if (JemallocNodumpAllocator::IsSupported(&msg)) { - Status s = NewJemallocNodumpAllocator(jopts, &allocator); - if (!s.ok()) { - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - } - } else { - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - } - - using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk; - std::unique_ptr sec_cache = - std::make_unique(1000, 0, true, 0.5, 0.0, - allocator); - Random rnd(301); - // 8500 = 8169 + 233 + 98, so there should be 3 chunks after split. - size_t str_size{8500}; - std::string str = rnd.RandomString(static_cast(str_size)); - size_t charge{0}; - CacheValueChunk* chunks_head = - sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge); - ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1)); - - CacheValueChunk* current_chunk = chunks_head; - ASSERT_EQ(current_chunk->size, 8192 - sizeof(CacheValueChunk) + 1); - current_chunk = current_chunk->next; - ASSERT_EQ(current_chunk->size, 256 - sizeof(CacheValueChunk) + 1); - current_chunk = current_chunk->next; - ASSERT_EQ(current_chunk->size, 98); - - sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr); - } - - void MergeChunksIntoValueTest() { - using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk; - Random rnd(301); - size_t size1{2048}; - std::string str1 = rnd.RandomString(static_cast(size1)); - CacheValueChunk* current_chunk = reinterpret_cast( - new char[sizeof(CacheValueChunk) - 1 + size1]); - CacheValueChunk* chunks_head = current_chunk; - memcpy(current_chunk->data, str1.data(), size1); - current_chunk->size = size1; - - size_t size2{256}; - std::string str2 = rnd.RandomString(static_cast(size2)); - current_chunk->next = reinterpret_cast( - new char[sizeof(CacheValueChunk) - 1 + size2]); - current_chunk = current_chunk->next; - memcpy(current_chunk->data, str2.data(), size2); - current_chunk->size = size2; - - size_t size3{31}; - std::string str3 = rnd.RandomString(static_cast(size3)); - current_chunk->next = reinterpret_cast( - new char[sizeof(CacheValueChunk) - 1 + size3]); - current_chunk = current_chunk->next; - memcpy(current_chunk->data, str3.data(), size3); - current_chunk->size = size3; - current_chunk->next = nullptr; - - std::string str = str1 + str2 + str3; - - std::unique_ptr sec_cache = - std::make_unique(1000, 0, true, 0.5, 0.0); - size_t charge{0}; - CacheAllocationPtr value = - sec_cache->MergeChunksIntoValue(chunks_head, charge); - ASSERT_EQ(charge, size1 + size2 + size3); - std::string value_str{value.get(), charge}; - ASSERT_EQ(strcmp(value_str.data(), str.data()), 0); - - while (chunks_head != nullptr) { - CacheValueChunk* tmp_chunk = chunks_head; - chunks_head = chunks_head->next; - tmp_chunk->Free(); - } - } - - void SplictValueAndMergeChunksTest() { - JemallocAllocatorOptions jopts; - std::shared_ptr allocator; - std::string msg; - if (JemallocNodumpAllocator::IsSupported(&msg)) { - Status s = NewJemallocNodumpAllocator(jopts, &allocator); - if (!s.ok()) { - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - } - } else { - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - } - - using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk; - std::unique_ptr sec_cache = - std::make_unique(1000, 0, true, 0.5, 0.0, - allocator); - Random rnd(301); - // 8500 = 8169 + 233 + 98, so there should be 3 chunks after split. - size_t str_size{8500}; - std::string str = rnd.RandomString(static_cast(str_size)); - size_t charge{0}; - CacheValueChunk* chunks_head = - sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge); - ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1)); - - CacheAllocationPtr value = - sec_cache->MergeChunksIntoValue(chunks_head, charge); - ASSERT_EQ(charge, str_size); - std::string value_str{value.get(), charge}; - ASSERT_EQ(strcmp(value_str.data(), str.data()), 0); - - sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr); - } -}; - -class CompressedSecondaryCacheTest - : public CompressedSecondaryCacheTestBase, - public testing::WithParamInterface { - const std::string& Type() override { return GetParam(); } -}; - -INSTANTIATE_TEST_CASE_P(CompressedSecondaryCacheTest, - CompressedSecondaryCacheTest, GetTestingCacheTypes()); - -class CompressedSecCacheTestWithCompressAndAllocatorParam - : public CompressedSecondaryCacheTestBase, - public ::testing::WithParamInterface< - std::tuple> { - public: - CompressedSecCacheTestWithCompressAndAllocatorParam() { - sec_cache_is_compressed_ = std::get<0>(GetParam()); - use_jemalloc_ = std::get<1>(GetParam()); - } - const std::string& Type() override { return std::get<2>(GetParam()); } - bool sec_cache_is_compressed_; - bool use_jemalloc_; -}; - -TEST_P(CompressedSecCacheTestWithCompressAndAllocatorParam, BasicTes) { - BasicTest(sec_cache_is_compressed_, use_jemalloc_); -} - -INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests, - CompressedSecCacheTestWithCompressAndAllocatorParam, - ::testing::Combine(testing::Bool(), testing::Bool(), - GetTestingCacheTypes())); - -class CompressedSecondaryCacheTestWithCompressionParam - : public CompressedSecondaryCacheTestBase, - public ::testing::WithParamInterface> { - public: - CompressedSecondaryCacheTestWithCompressionParam() { - sec_cache_is_compressed_ = std::get<0>(GetParam()); - } - const std::string& Type() override { return std::get<1>(GetParam()); } - bool sec_cache_is_compressed_; -}; - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, BasicTestFromString) { - std::shared_ptr sec_cache{nullptr}; - std::string sec_cache_uri; - if (sec_cache_is_compressed_) { - if (LZ4_Supported()) { - sec_cache_uri = - "compressed_secondary_cache://" - "capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;" - "compress_format_version=2"; - } else { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - sec_cache_uri = - "compressed_secondary_cache://" - "capacity=2048;num_shard_bits=0;compression_type=kNoCompression"; - sec_cache_is_compressed_ = false; - } - Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri, - &sec_cache); - EXPECT_OK(s); - } else { - sec_cache_uri = - "compressed_secondary_cache://" - "capacity=2048;num_shard_bits=0;compression_type=kNoCompression"; - Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri, - &sec_cache); - EXPECT_OK(s); - } - BasicTestHelper(sec_cache, sec_cache_is_compressed_); -} - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, - BasicTestFromStringWithSplit) { - std::shared_ptr sec_cache{nullptr}; - std::string sec_cache_uri; - if (sec_cache_is_compressed_) { - if (LZ4_Supported()) { - sec_cache_uri = - "compressed_secondary_cache://" - "capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;" - "compress_format_version=2;enable_custom_split_merge=true"; - } else { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - sec_cache_uri = - "compressed_secondary_cache://" - "capacity=2048;num_shard_bits=0;compression_type=kNoCompression;" - "enable_custom_split_merge=true"; - sec_cache_is_compressed_ = false; - } - Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri, - &sec_cache); - EXPECT_OK(s); - } else { - sec_cache_uri = - "compressed_secondary_cache://" - "capacity=2048;num_shard_bits=0;compression_type=kNoCompression;" - "enable_custom_split_merge=true"; - Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri, - &sec_cache); - EXPECT_OK(s); - } - BasicTestHelper(sec_cache, sec_cache_is_compressed_); -} - - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, FailsTest) { - FailsTest(sec_cache_is_compressed_); -} - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, - BasicIntegrationFailTest) { - BasicIntegrationFailTest(sec_cache_is_compressed_); -} - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, - IntegrationSaveFailTest) { - IntegrationSaveFailTest(sec_cache_is_compressed_); -} - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, - IntegrationCreateFailTest) { - IntegrationCreateFailTest(sec_cache_is_compressed_); -} - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, - IntegrationFullCapacityTest) { - IntegrationFullCapacityTest(sec_cache_is_compressed_); -} - -TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) { - CompressedSecondaryCacheOptions opts; - opts.capacity = 2048; - opts.num_shard_bits = 0; - - if (sec_cache_is_compressed_) { - if (!LZ4_Supported()) { - ROCKSDB_GTEST_SKIP("This test requires LZ4 support."); - return; - } - } else { - opts.compression_type = CompressionType::kNoCompression; - } - - // Select a random subset to include, for fast test - Random& r = *Random::GetTLSInstance(); - CacheEntryRoleSet do_not_compress; - for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) { - // A few included on average, but decent chance of zero - if (r.OneIn(5)) { - do_not_compress.Add(static_cast(i)); - } - } - opts.do_not_compress_roles = do_not_compress; - - std::shared_ptr sec_cache = NewCompressedSecondaryCache(opts); - - // Fixed seed to ensure consistent compressibility (doesn't compress) - std::string junk(Random(301).RandomString(1000)); - - for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) { - CacheEntryRole role = static_cast(i); - - // Uniquify `junk` - junk[0] = static_cast(i); - TestItem item{junk.data(), junk.length()}; - Slice ith_key = Slice(junk.data(), 16); - - get_perf_context()->Reset(); - ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role))); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1U); - - ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role))); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1U); - - bool kept_in_sec_cache{true}; - std::unique_ptr handle = - sec_cache->Lookup(ith_key, GetHelper(role), this, true, - /*advise_erase=*/true, kept_in_sec_cache); - ASSERT_NE(handle, nullptr); - - // Lookup returns the right data - std::unique_ptr val = - std::unique_ptr(static_cast(handle->Value())); - ASSERT_NE(val, nullptr); - ASSERT_EQ(memcmp(val->Buf(), item.Buf(), item.Size()), 0); - - bool compressed = - sec_cache_is_compressed_ && !do_not_compress.Contains(role); - if (compressed) { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, - 1000); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, - 1007); - } else { - ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0); - ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0); - } - } -} - -INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests, - CompressedSecondaryCacheTestWithCompressionParam, - testing::Combine(testing::Bool(), - GetTestingCacheTypes())); - -class CompressedSecCacheTestWithCompressAndSplitParam - : public CompressedSecondaryCacheTestBase, - public ::testing::WithParamInterface< - std::tuple> { - public: - CompressedSecCacheTestWithCompressAndSplitParam() { - sec_cache_is_compressed_ = std::get<0>(GetParam()); - enable_custom_split_merge_ = std::get<1>(GetParam()); - } - const std::string& Type() override { return std::get<2>(GetParam()); } - bool sec_cache_is_compressed_; - bool enable_custom_split_merge_; -}; - -TEST_P(CompressedSecCacheTestWithCompressAndSplitParam, BasicIntegrationTest) { - BasicIntegrationTest(sec_cache_is_compressed_, enable_custom_split_merge_); -} - -INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests, - CompressedSecCacheTestWithCompressAndSplitParam, - ::testing::Combine(testing::Bool(), testing::Bool(), - GetTestingCacheTypes())); - -TEST_P(CompressedSecondaryCacheTest, SplitValueIntoChunksTest) { - SplitValueIntoChunksTest(); -} - -TEST_P(CompressedSecondaryCacheTest, MergeChunksIntoValueTest) { - MergeChunksIntoValueTest(); -} - -TEST_P(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) { - SplictValueAndMergeChunksTest(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/cache/lru_cache_test.cc b/cache/lru_cache_test.cc deleted file mode 100644 index c4f392976..000000000 --- a/cache/lru_cache_test.cc +++ /dev/null @@ -1,2558 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "cache/lru_cache.h" - -#include -#include -#include - -#include "cache/cache_key.h" -#include "cache/clock_cache.h" -#include "cache_helpers.h" -#include "db/db_test_util.h" -#include "file/sst_file_manager_impl.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/io_status.h" -#include "rocksdb/sst_file_manager.h" -#include "rocksdb/utilities/cache_dump_load.h" -#include "test_util/secondary_cache_test_util.h" -#include "test_util/testharness.h" -#include "typed_cache.h" -#include "util/coding.h" -#include "util/random.h" -#include "utilities/cache_dump_load_impl.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -class LRUCacheTest : public testing::Test { - public: - LRUCacheTest() {} - ~LRUCacheTest() override { DeleteCache(); } - - void DeleteCache() { - if (cache_ != nullptr) { - cache_->~LRUCacheShard(); - port::cacheline_aligned_free(cache_); - cache_ = nullptr; - } - } - - void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0, - double low_pri_pool_ratio = 1.0, - bool use_adaptive_mutex = kDefaultToAdaptiveMutex) { - DeleteCache(); - cache_ = reinterpret_cast( - port::cacheline_aligned_alloc(sizeof(LRUCacheShard))); - new (cache_) LRUCacheShard(capacity, /*strict_capacity_limit=*/false, - high_pri_pool_ratio, low_pri_pool_ratio, - use_adaptive_mutex, kDontChargeCacheMetadata, - /*max_upper_hash_bits=*/24, - /*allocator*/ nullptr, &eviction_callback_); - } - - void Insert(const std::string& key, - Cache::Priority priority = Cache::Priority::LOW) { - EXPECT_OK(cache_->Insert(key, 0 /*hash*/, nullptr /*value*/, - &kNoopCacheItemHelper, 1 /*charge*/, - nullptr /*handle*/, priority)); - } - - void Insert(char key, Cache::Priority priority = Cache::Priority::LOW) { - Insert(std::string(1, key), priority); - } - - bool Lookup(const std::string& key) { - auto handle = cache_->Lookup(key, 0 /*hash*/, nullptr, nullptr, - Cache::Priority::LOW, nullptr); - if (handle) { - cache_->Release(handle, true /*useful*/, false /*erase*/); - return true; - } - return false; - } - - bool Lookup(char key) { return Lookup(std::string(1, key)); } - - void Erase(const std::string& key) { cache_->Erase(key, 0 /*hash*/); } - - void ValidateLRUList(std::vector keys, - size_t num_high_pri_pool_keys = 0, - size_t num_low_pri_pool_keys = 0, - size_t num_bottom_pri_pool_keys = 0) { - LRUHandle* lru; - LRUHandle* lru_low_pri; - LRUHandle* lru_bottom_pri; - cache_->TEST_GetLRUList(&lru, &lru_low_pri, &lru_bottom_pri); - - LRUHandle* iter = lru; - - bool in_low_pri_pool = false; - bool in_high_pri_pool = false; - - size_t high_pri_pool_keys = 0; - size_t low_pri_pool_keys = 0; - size_t bottom_pri_pool_keys = 0; - - if (iter == lru_bottom_pri) { - in_low_pri_pool = true; - in_high_pri_pool = false; - } - if (iter == lru_low_pri) { - in_low_pri_pool = false; - in_high_pri_pool = true; - } - - for (const auto& key : keys) { - iter = iter->next; - ASSERT_NE(lru, iter); - ASSERT_EQ(key, iter->key().ToString()); - ASSERT_EQ(in_high_pri_pool, iter->InHighPriPool()); - ASSERT_EQ(in_low_pri_pool, iter->InLowPriPool()); - if (in_high_pri_pool) { - ASSERT_FALSE(iter->InLowPriPool()); - high_pri_pool_keys++; - } else if (in_low_pri_pool) { - ASSERT_FALSE(iter->InHighPriPool()); - low_pri_pool_keys++; - } else { - bottom_pri_pool_keys++; - } - if (iter == lru_bottom_pri) { - ASSERT_FALSE(in_low_pri_pool); - ASSERT_FALSE(in_high_pri_pool); - in_low_pri_pool = true; - in_high_pri_pool = false; - } - if (iter == lru_low_pri) { - ASSERT_TRUE(in_low_pri_pool); - ASSERT_FALSE(in_high_pri_pool); - in_low_pri_pool = false; - in_high_pri_pool = true; - } - } - ASSERT_EQ(lru, iter->next); - ASSERT_FALSE(in_low_pri_pool); - ASSERT_TRUE(in_high_pri_pool); - ASSERT_EQ(num_high_pri_pool_keys, high_pri_pool_keys); - ASSERT_EQ(num_low_pri_pool_keys, low_pri_pool_keys); - ASSERT_EQ(num_bottom_pri_pool_keys, bottom_pri_pool_keys); - } - - private: - LRUCacheShard* cache_ = nullptr; - Cache::EvictionCallback eviction_callback_; -}; - -TEST_F(LRUCacheTest, BasicLRU) { - NewCache(5); - for (char ch = 'a'; ch <= 'e'; ch++) { - Insert(ch); - } - ValidateLRUList({"a", "b", "c", "d", "e"}, 0, 5); - for (char ch = 'x'; ch <= 'z'; ch++) { - Insert(ch); - } - ValidateLRUList({"d", "e", "x", "y", "z"}, 0, 5); - ASSERT_FALSE(Lookup("b")); - ValidateLRUList({"d", "e", "x", "y", "z"}, 0, 5); - ASSERT_TRUE(Lookup("e")); - ValidateLRUList({"d", "x", "y", "z", "e"}, 0, 5); - ASSERT_TRUE(Lookup("z")); - ValidateLRUList({"d", "x", "y", "e", "z"}, 0, 5); - Erase("x"); - ValidateLRUList({"d", "y", "e", "z"}, 0, 4); - ASSERT_TRUE(Lookup("d")); - ValidateLRUList({"y", "e", "z", "d"}, 0, 4); - Insert("u"); - ValidateLRUList({"y", "e", "z", "d", "u"}, 0, 5); - Insert("v"); - ValidateLRUList({"e", "z", "d", "u", "v"}, 0, 5); -} - -TEST_F(LRUCacheTest, LowPriorityMidpointInsertion) { - // Allocate 2 cache entries to high-pri pool and 3 to low-pri pool. - NewCache(5, /* high_pri_pool_ratio */ 0.40, /* low_pri_pool_ratio */ 0.60); - - Insert("a", Cache::Priority::LOW); - Insert("b", Cache::Priority::LOW); - Insert("c", Cache::Priority::LOW); - Insert("x", Cache::Priority::HIGH); - Insert("y", Cache::Priority::HIGH); - ValidateLRUList({"a", "b", "c", "x", "y"}, 2, 3); - - // Low-pri entries inserted to the tail of low-pri list (the midpoint). - // After lookup, it will move to the tail of the full list. - Insert("d", Cache::Priority::LOW); - ValidateLRUList({"b", "c", "d", "x", "y"}, 2, 3); - ASSERT_TRUE(Lookup("d")); - ValidateLRUList({"b", "c", "x", "y", "d"}, 2, 3); - - // High-pri entries will be inserted to the tail of full list. - Insert("z", Cache::Priority::HIGH); - ValidateLRUList({"c", "x", "y", "d", "z"}, 2, 3); -} - -TEST_F(LRUCacheTest, BottomPriorityMidpointInsertion) { - // Allocate 2 cache entries to high-pri pool and 2 to low-pri pool. - NewCache(6, /* high_pri_pool_ratio */ 0.35, /* low_pri_pool_ratio */ 0.35); - - Insert("a", Cache::Priority::BOTTOM); - Insert("b", Cache::Priority::BOTTOM); - Insert("i", Cache::Priority::LOW); - Insert("j", Cache::Priority::LOW); - Insert("x", Cache::Priority::HIGH); - Insert("y", Cache::Priority::HIGH); - ValidateLRUList({"a", "b", "i", "j", "x", "y"}, 2, 2, 2); - - // Low-pri entries will be inserted to the tail of low-pri list (the - // midpoint). After lookup, 'k' will move to the tail of the full list, and - // 'x' will spill over to the low-pri pool. - Insert("k", Cache::Priority::LOW); - ValidateLRUList({"b", "i", "j", "k", "x", "y"}, 2, 2, 2); - ASSERT_TRUE(Lookup("k")); - ValidateLRUList({"b", "i", "j", "x", "y", "k"}, 2, 2, 2); - - // High-pri entries will be inserted to the tail of full list. Although y was - // inserted with high priority, it got spilled over to the low-pri pool. As - // a result, j also got spilled over to the bottom-pri pool. - Insert("z", Cache::Priority::HIGH); - ValidateLRUList({"i", "j", "x", "y", "k", "z"}, 2, 2, 2); - Erase("x"); - ValidateLRUList({"i", "j", "y", "k", "z"}, 2, 1, 2); - Erase("y"); - ValidateLRUList({"i", "j", "k", "z"}, 2, 0, 2); - - // Bottom-pri entries will be inserted to the tail of bottom-pri list. - Insert("c", Cache::Priority::BOTTOM); - ValidateLRUList({"i", "j", "c", "k", "z"}, 2, 0, 3); - Insert("d", Cache::Priority::BOTTOM); - ValidateLRUList({"i", "j", "c", "d", "k", "z"}, 2, 0, 4); - Insert("e", Cache::Priority::BOTTOM); - ValidateLRUList({"j", "c", "d", "e", "k", "z"}, 2, 0, 4); - - // Low-pri entries will be inserted to the tail of low-pri list (the - // midpoint). - Insert("l", Cache::Priority::LOW); - ValidateLRUList({"c", "d", "e", "l", "k", "z"}, 2, 1, 3); - Insert("m", Cache::Priority::LOW); - ValidateLRUList({"d", "e", "l", "m", "k", "z"}, 2, 2, 2); - - Erase("k"); - ValidateLRUList({"d", "e", "l", "m", "z"}, 1, 2, 2); - Erase("z"); - ValidateLRUList({"d", "e", "l", "m"}, 0, 2, 2); - - // Bottom-pri entries will be inserted to the tail of bottom-pri list. - Insert("f", Cache::Priority::BOTTOM); - ValidateLRUList({"d", "e", "f", "l", "m"}, 0, 2, 3); - Insert("g", Cache::Priority::BOTTOM); - ValidateLRUList({"d", "e", "f", "g", "l", "m"}, 0, 2, 4); - - // High-pri entries will be inserted to the tail of full list. - Insert("o", Cache::Priority::HIGH); - ValidateLRUList({"e", "f", "g", "l", "m", "o"}, 1, 2, 3); - Insert("p", Cache::Priority::HIGH); - ValidateLRUList({"f", "g", "l", "m", "o", "p"}, 2, 2, 2); -} - -TEST_F(LRUCacheTest, EntriesWithPriority) { - // Allocate 2 cache entries to high-pri pool and 2 to low-pri pool. - NewCache(6, /* high_pri_pool_ratio */ 0.35, /* low_pri_pool_ratio */ 0.35); - - Insert("a", Cache::Priority::LOW); - Insert("b", Cache::Priority::LOW); - ValidateLRUList({"a", "b"}, 0, 2, 0); - // Low-pri entries can overflow to bottom-pri pool. - Insert("c", Cache::Priority::LOW); - ValidateLRUList({"a", "b", "c"}, 0, 2, 1); - - // Bottom-pri entries can take high-pri pool capacity if available - Insert("t", Cache::Priority::LOW); - Insert("u", Cache::Priority::LOW); - ValidateLRUList({"a", "b", "c", "t", "u"}, 0, 2, 3); - Insert("v", Cache::Priority::LOW); - ValidateLRUList({"a", "b", "c", "t", "u", "v"}, 0, 2, 4); - Insert("w", Cache::Priority::LOW); - ValidateLRUList({"b", "c", "t", "u", "v", "w"}, 0, 2, 4); - - Insert("X", Cache::Priority::HIGH); - Insert("Y", Cache::Priority::HIGH); - ValidateLRUList({"t", "u", "v", "w", "X", "Y"}, 2, 2, 2); - - // After lookup, the high-pri entry 'X' got spilled over to the low-pri pool. - // The low-pri entry 'v' got spilled over to the bottom-pri pool. - Insert("Z", Cache::Priority::HIGH); - ValidateLRUList({"u", "v", "w", "X", "Y", "Z"}, 2, 2, 2); - - // Low-pri entries will be inserted to head of low-pri pool. - Insert("a", Cache::Priority::LOW); - ValidateLRUList({"v", "w", "X", "a", "Y", "Z"}, 2, 2, 2); - - // After lookup, the high-pri entry 'Y' got spilled over to the low-pri pool. - // The low-pri entry 'X' got spilled over to the bottom-pri pool. - ASSERT_TRUE(Lookup("v")); - ValidateLRUList({"w", "X", "a", "Y", "Z", "v"}, 2, 2, 2); - - // After lookup, the high-pri entry 'Z' got spilled over to the low-pri pool. - // The low-pri entry 'a' got spilled over to the bottom-pri pool. - ASSERT_TRUE(Lookup("X")); - ValidateLRUList({"w", "a", "Y", "Z", "v", "X"}, 2, 2, 2); - - // After lookup, the low pri entry 'Z' got promoted back to high-pri pool. The - // high-pri entry 'v' got spilled over to the low-pri pool. - ASSERT_TRUE(Lookup("Z")); - ValidateLRUList({"w", "a", "Y", "v", "X", "Z"}, 2, 2, 2); - - Erase("Y"); - ValidateLRUList({"w", "a", "v", "X", "Z"}, 2, 1, 2); - Erase("X"); - ValidateLRUList({"w", "a", "v", "Z"}, 1, 1, 2); - - Insert("d", Cache::Priority::LOW); - Insert("e", Cache::Priority::LOW); - ValidateLRUList({"w", "a", "v", "d", "e", "Z"}, 1, 2, 3); - - Insert("f", Cache::Priority::LOW); - Insert("g", Cache::Priority::LOW); - ValidateLRUList({"v", "d", "e", "f", "g", "Z"}, 1, 2, 3); - ASSERT_TRUE(Lookup("d")); - ValidateLRUList({"v", "e", "f", "g", "Z", "d"}, 2, 2, 2); - - // Erase some entries. - Erase("e"); - Erase("f"); - Erase("Z"); - ValidateLRUList({"v", "g", "d"}, 1, 1, 1); - - // Bottom-pri entries can take low- and high-pri pool capacity if available - Insert("o", Cache::Priority::BOTTOM); - ValidateLRUList({"v", "o", "g", "d"}, 1, 1, 2); - Insert("p", Cache::Priority::BOTTOM); - ValidateLRUList({"v", "o", "p", "g", "d"}, 1, 1, 3); - Insert("q", Cache::Priority::BOTTOM); - ValidateLRUList({"v", "o", "p", "q", "g", "d"}, 1, 1, 4); - - // High-pri entries can overflow to low-pri pool, and bottom-pri entries will - // be evicted. - Insert("x", Cache::Priority::HIGH); - ValidateLRUList({"o", "p", "q", "g", "d", "x"}, 2, 1, 3); - Insert("y", Cache::Priority::HIGH); - ValidateLRUList({"p", "q", "g", "d", "x", "y"}, 2, 2, 2); - Insert("z", Cache::Priority::HIGH); - ValidateLRUList({"q", "g", "d", "x", "y", "z"}, 2, 2, 2); - - // 'g' is bottom-pri before this lookup, it will be inserted to head of - // high-pri pool after lookup. - ASSERT_TRUE(Lookup("g")); - ValidateLRUList({"q", "d", "x", "y", "z", "g"}, 2, 2, 2); - - // High-pri entries will be inserted to head of high-pri pool after lookup. - ASSERT_TRUE(Lookup("z")); - ValidateLRUList({"q", "d", "x", "y", "g", "z"}, 2, 2, 2); - - // Bottom-pri entries will be inserted to head of high-pri pool after lookup. - ASSERT_TRUE(Lookup("d")); - ValidateLRUList({"q", "x", "y", "g", "z", "d"}, 2, 2, 2); - - // Bottom-pri entries will be inserted to the tail of bottom-pri list. - Insert("m", Cache::Priority::BOTTOM); - ValidateLRUList({"x", "m", "y", "g", "z", "d"}, 2, 2, 2); - - // Bottom-pri entries will be inserted to head of high-pri pool after lookup. - ASSERT_TRUE(Lookup("m")); - ValidateLRUList({"x", "y", "g", "z", "d", "m"}, 2, 2, 2); -} - -namespace clock_cache { - -class ClockCacheTest : public testing::Test { - public: - using Shard = HyperClockCache::Shard; - using Table = HyperClockTable; - using HandleImpl = Shard::HandleImpl; - - ClockCacheTest() {} - ~ClockCacheTest() override { DeleteShard(); } - - void DeleteShard() { - if (shard_ != nullptr) { - shard_->~ClockCacheShard(); - port::cacheline_aligned_free(shard_); - shard_ = nullptr; - } - } - - void NewShard(size_t capacity, bool strict_capacity_limit = true) { - DeleteShard(); - shard_ = - reinterpret_cast(port::cacheline_aligned_alloc(sizeof(Shard))); - - Table::Opts opts; - opts.estimated_value_size = 1; - new (shard_) - Shard(capacity, strict_capacity_limit, kDontChargeCacheMetadata, - /*allocator*/ nullptr, &eviction_callback_, opts); - } - - Status Insert(const UniqueId64x2& hashed_key, - Cache::Priority priority = Cache::Priority::LOW) { - return shard_->Insert(TestKey(hashed_key), hashed_key, nullptr /*value*/, - &kNoopCacheItemHelper, 1 /*charge*/, - nullptr /*handle*/, priority); - } - - Status Insert(char key, Cache::Priority priority = Cache::Priority::LOW) { - return Insert(TestHashedKey(key), priority); - } - - Status InsertWithLen(char key, size_t len) { - std::string skey(len, key); - return shard_->Insert(skey, TestHashedKey(key), nullptr /*value*/, - &kNoopCacheItemHelper, 1 /*charge*/, - nullptr /*handle*/, Cache::Priority::LOW); - } - - bool Lookup(const Slice& key, const UniqueId64x2& hashed_key, - bool useful = true) { - auto handle = shard_->Lookup(key, hashed_key); - if (handle) { - shard_->Release(handle, useful, /*erase_if_last_ref=*/false); - return true; - } - return false; - } - - bool Lookup(const UniqueId64x2& hashed_key, bool useful = true) { - return Lookup(TestKey(hashed_key), hashed_key, useful); - } - - bool Lookup(char key, bool useful = true) { - return Lookup(TestHashedKey(key), useful); - } - - void Erase(char key) { - UniqueId64x2 hashed_key = TestHashedKey(key); - shard_->Erase(TestKey(hashed_key), hashed_key); - } - - static inline Slice TestKey(const UniqueId64x2& hashed_key) { - return Slice(reinterpret_cast(&hashed_key), 16U); - } - - static inline UniqueId64x2 TestHashedKey(char key) { - // For testing hash near-collision behavior, put the variance in - // hashed_key in bits that are unlikely to be used as hash bits. - return {(static_cast(key) << 56) + 1234U, 5678U}; - } - - Shard* shard_ = nullptr; - - private: - Cache::EvictionCallback eviction_callback_; -}; - -TEST_F(ClockCacheTest, Misc) { - NewShard(3); - - // Key size stuff - EXPECT_OK(InsertWithLen('a', 16)); - EXPECT_NOK(InsertWithLen('b', 15)); - EXPECT_OK(InsertWithLen('b', 16)); - EXPECT_NOK(InsertWithLen('c', 17)); - EXPECT_NOK(InsertWithLen('d', 1000)); - EXPECT_NOK(InsertWithLen('e', 11)); - EXPECT_NOK(InsertWithLen('f', 0)); - - // Some of this is motivated by code coverage - std::string wrong_size_key(15, 'x'); - EXPECT_FALSE(Lookup(wrong_size_key, TestHashedKey('x'))); - EXPECT_FALSE(shard_->Ref(nullptr)); - EXPECT_FALSE(shard_->Release(nullptr)); - shard_->Erase(wrong_size_key, TestHashedKey('x')); // no-op -} - -TEST_F(ClockCacheTest, Limits) { - constexpr size_t kCapacity = 3; - NewShard(kCapacity, false /*strict_capacity_limit*/); - for (bool strict_capacity_limit : {false, true, false}) { - SCOPED_TRACE("strict_capacity_limit = " + - std::to_string(strict_capacity_limit)); - - // Also tests switching between strict limit and not - shard_->SetStrictCapacityLimit(strict_capacity_limit); - - UniqueId64x2 hkey = TestHashedKey('x'); - - // Single entry charge beyond capacity - { - Status s = shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/, - &kNoopCacheItemHelper, 5 /*charge*/, - nullptr /*handle*/, Cache::Priority::LOW); - if (strict_capacity_limit) { - EXPECT_TRUE(s.IsMemoryLimit()); - } else { - EXPECT_OK(s); - } - } - - // Single entry fills capacity - { - HandleImpl* h; - ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/, - &kNoopCacheItemHelper, 3 /*charge*/, &h, - Cache::Priority::LOW)); - // Try to insert more - Status s = Insert('a'); - if (strict_capacity_limit) { - EXPECT_TRUE(s.IsMemoryLimit()); - } else { - EXPECT_OK(s); - } - // Release entry filling capacity. - // Cover useful = false case. - shard_->Release(h, false /*useful*/, false /*erase_if_last_ref*/); - } - - // Insert more than table size can handle to exceed occupancy limit. - // (Cleverly using mostly zero-charge entries, but some non-zero to - // verify usage tracking on detached entries.) - { - size_t n = shard_->GetTableAddressCount() + 1; - std::unique_ptr ha { new HandleImpl* [n] {} }; - Status s; - for (size_t i = 0; i < n && s.ok(); ++i) { - hkey[1] = i; - s = shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/, - &kNoopCacheItemHelper, - (i + kCapacity < n) ? 0 : 1 /*charge*/, &ha[i], - Cache::Priority::LOW); - if (i == 0) { - EXPECT_OK(s); - } - } - if (strict_capacity_limit) { - EXPECT_TRUE(s.IsMemoryLimit()); - } else { - EXPECT_OK(s); - } - // Same result if not keeping a reference - s = Insert('a'); - if (strict_capacity_limit) { - EXPECT_TRUE(s.IsMemoryLimit()); - } else { - EXPECT_OK(s); - } - - // Regardless, we didn't allow table to actually get full - EXPECT_LT(shard_->GetOccupancyCount(), shard_->GetTableAddressCount()); - - // Release handles - for (size_t i = 0; i < n; ++i) { - if (ha[i]) { - shard_->Release(ha[i]); - } - } - } - } -} - -TEST_F(ClockCacheTest, ClockEvictionTest) { - for (bool strict_capacity_limit : {false, true}) { - SCOPED_TRACE("strict_capacity_limit = " + - std::to_string(strict_capacity_limit)); - - NewShard(6, strict_capacity_limit); - EXPECT_OK(Insert('a', Cache::Priority::BOTTOM)); - EXPECT_OK(Insert('b', Cache::Priority::LOW)); - EXPECT_OK(Insert('c', Cache::Priority::HIGH)); - EXPECT_OK(Insert('d', Cache::Priority::BOTTOM)); - EXPECT_OK(Insert('e', Cache::Priority::LOW)); - EXPECT_OK(Insert('f', Cache::Priority::HIGH)); - - EXPECT_TRUE(Lookup('a', /*use*/ false)); - EXPECT_TRUE(Lookup('b', /*use*/ false)); - EXPECT_TRUE(Lookup('c', /*use*/ false)); - EXPECT_TRUE(Lookup('d', /*use*/ false)); - EXPECT_TRUE(Lookup('e', /*use*/ false)); - EXPECT_TRUE(Lookup('f', /*use*/ false)); - - // Ensure bottom are evicted first, even if new entries are low - EXPECT_OK(Insert('g', Cache::Priority::LOW)); - EXPECT_OK(Insert('h', Cache::Priority::LOW)); - - EXPECT_FALSE(Lookup('a', /*use*/ false)); - EXPECT_TRUE(Lookup('b', /*use*/ false)); - EXPECT_TRUE(Lookup('c', /*use*/ false)); - EXPECT_FALSE(Lookup('d', /*use*/ false)); - EXPECT_TRUE(Lookup('e', /*use*/ false)); - EXPECT_TRUE(Lookup('f', /*use*/ false)); - // Mark g & h useful - EXPECT_TRUE(Lookup('g', /*use*/ true)); - EXPECT_TRUE(Lookup('h', /*use*/ true)); - - // Then old LOW entries - EXPECT_OK(Insert('i', Cache::Priority::LOW)); - EXPECT_OK(Insert('j', Cache::Priority::LOW)); - - EXPECT_FALSE(Lookup('b', /*use*/ false)); - EXPECT_TRUE(Lookup('c', /*use*/ false)); - EXPECT_FALSE(Lookup('e', /*use*/ false)); - EXPECT_TRUE(Lookup('f', /*use*/ false)); - // Mark g & h useful once again - EXPECT_TRUE(Lookup('g', /*use*/ true)); - EXPECT_TRUE(Lookup('h', /*use*/ true)); - EXPECT_TRUE(Lookup('i', /*use*/ false)); - EXPECT_TRUE(Lookup('j', /*use*/ false)); - - // Then old HIGH entries - EXPECT_OK(Insert('k', Cache::Priority::LOW)); - EXPECT_OK(Insert('l', Cache::Priority::LOW)); - - EXPECT_FALSE(Lookup('c', /*use*/ false)); - EXPECT_FALSE(Lookup('f', /*use*/ false)); - EXPECT_TRUE(Lookup('g', /*use*/ false)); - EXPECT_TRUE(Lookup('h', /*use*/ false)); - EXPECT_TRUE(Lookup('i', /*use*/ false)); - EXPECT_TRUE(Lookup('j', /*use*/ false)); - EXPECT_TRUE(Lookup('k', /*use*/ false)); - EXPECT_TRUE(Lookup('l', /*use*/ false)); - - // Then the (roughly) least recently useful - EXPECT_OK(Insert('m', Cache::Priority::HIGH)); - EXPECT_OK(Insert('n', Cache::Priority::HIGH)); - - EXPECT_TRUE(Lookup('g', /*use*/ false)); - EXPECT_TRUE(Lookup('h', /*use*/ false)); - EXPECT_FALSE(Lookup('i', /*use*/ false)); - EXPECT_FALSE(Lookup('j', /*use*/ false)); - EXPECT_TRUE(Lookup('k', /*use*/ false)); - EXPECT_TRUE(Lookup('l', /*use*/ false)); - - // Now try changing capacity down - shard_->SetCapacity(4); - // Insert to ensure evictions happen - EXPECT_OK(Insert('o', Cache::Priority::LOW)); - EXPECT_OK(Insert('p', Cache::Priority::LOW)); - - EXPECT_FALSE(Lookup('g', /*use*/ false)); - EXPECT_FALSE(Lookup('h', /*use*/ false)); - EXPECT_FALSE(Lookup('k', /*use*/ false)); - EXPECT_FALSE(Lookup('l', /*use*/ false)); - EXPECT_TRUE(Lookup('m', /*use*/ false)); - EXPECT_TRUE(Lookup('n', /*use*/ false)); - EXPECT_TRUE(Lookup('o', /*use*/ false)); - EXPECT_TRUE(Lookup('p', /*use*/ false)); - - // Now try changing capacity up - EXPECT_TRUE(Lookup('m', /*use*/ true)); - EXPECT_TRUE(Lookup('n', /*use*/ true)); - shard_->SetCapacity(6); - EXPECT_OK(Insert('q', Cache::Priority::HIGH)); - EXPECT_OK(Insert('r', Cache::Priority::HIGH)); - EXPECT_OK(Insert('s', Cache::Priority::HIGH)); - EXPECT_OK(Insert('t', Cache::Priority::HIGH)); - - EXPECT_FALSE(Lookup('o', /*use*/ false)); - EXPECT_FALSE(Lookup('p', /*use*/ false)); - EXPECT_TRUE(Lookup('m', /*use*/ false)); - EXPECT_TRUE(Lookup('n', /*use*/ false)); - EXPECT_TRUE(Lookup('q', /*use*/ false)); - EXPECT_TRUE(Lookup('r', /*use*/ false)); - EXPECT_TRUE(Lookup('s', /*use*/ false)); - EXPECT_TRUE(Lookup('t', /*use*/ false)); - } -} - -namespace { -struct DeleteCounter { - int deleted = 0; -}; -const Cache::CacheItemHelper kDeleteCounterHelper{ - CacheEntryRole::kMisc, - [](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) { - static_cast(value)->deleted += 1; - }}; -} // namespace - -// Testing calls to CorrectNearOverflow in Release -TEST_F(ClockCacheTest, ClockCounterOverflowTest) { - NewShard(6, /*strict_capacity_limit*/ false); - HandleImpl* h; - DeleteCounter val; - UniqueId64x2 hkey = TestHashedKey('x'); - ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, &val, &kDeleteCounterHelper, 1, - &h, Cache::Priority::HIGH)); - - // Some large number outstanding - shard_->TEST_RefN(h, 123456789); - // Simulate many lookup/ref + release, plenty to overflow counters - for (int i = 0; i < 10000; ++i) { - shard_->TEST_RefN(h, 1234567); - shard_->TEST_ReleaseN(h, 1234567); - } - // Mark it invisible (to reach a different CorrectNearOverflow() in Release) - shard_->Erase(TestKey(hkey), hkey); - // Simulate many more lookup/ref + release (one-by-one would be too - // expensive for unit test) - for (int i = 0; i < 10000; ++i) { - shard_->TEST_RefN(h, 1234567); - shard_->TEST_ReleaseN(h, 1234567); - } - // Free all but last 1 - shard_->TEST_ReleaseN(h, 123456789); - // Still alive - ASSERT_EQ(val.deleted, 0); - // Free last ref, which will finalize erasure - shard_->Release(h); - // Deleted - ASSERT_EQ(val.deleted, 1); -} - -// This test is mostly to exercise some corner case logic, by forcing two -// keys to have the same hash, and more -TEST_F(ClockCacheTest, CollidingInsertEraseTest) { - NewShard(6, /*strict_capacity_limit*/ false); - DeleteCounter val; - UniqueId64x2 hkey1 = TestHashedKey('x'); - Slice key1 = TestKey(hkey1); - UniqueId64x2 hkey2 = TestHashedKey('y'); - Slice key2 = TestKey(hkey2); - UniqueId64x2 hkey3 = TestHashedKey('z'); - Slice key3 = TestKey(hkey3); - HandleImpl* h1; - ASSERT_OK(shard_->Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, &h1, - Cache::Priority::HIGH)); - HandleImpl* h2; - ASSERT_OK(shard_->Insert(key2, hkey2, &val, &kDeleteCounterHelper, 1, &h2, - Cache::Priority::HIGH)); - HandleImpl* h3; - ASSERT_OK(shard_->Insert(key3, hkey3, &val, &kDeleteCounterHelper, 1, &h3, - Cache::Priority::HIGH)); - - // Can repeatedly lookup+release despite the hash collision - HandleImpl* tmp_h; - for (bool erase_if_last_ref : {true, false}) { // but not last ref - tmp_h = shard_->Lookup(key1, hkey1); - ASSERT_EQ(h1, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - - tmp_h = shard_->Lookup(key2, hkey2); - ASSERT_EQ(h2, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - - tmp_h = shard_->Lookup(key3, hkey3); - ASSERT_EQ(h3, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - } - - // Make h1 invisible - shard_->Erase(key1, hkey1); - // Redundant erase - shard_->Erase(key1, hkey1); - - // All still alive - ASSERT_EQ(val.deleted, 0); - - // Invisible to Lookup - tmp_h = shard_->Lookup(key1, hkey1); - ASSERT_EQ(nullptr, tmp_h); - - // Can still find h2, h3 - for (bool erase_if_last_ref : {true, false}) { // but not last ref - tmp_h = shard_->Lookup(key2, hkey2); - ASSERT_EQ(h2, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - - tmp_h = shard_->Lookup(key3, hkey3); - ASSERT_EQ(h3, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - } - - // Also Insert with invisible entry there - ASSERT_OK(shard_->Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, nullptr, - Cache::Priority::HIGH)); - tmp_h = shard_->Lookup(key1, hkey1); - // Found but distinct handle - ASSERT_NE(nullptr, tmp_h); - ASSERT_NE(h1, tmp_h); - ASSERT_TRUE(shard_->Release(tmp_h, /*erase_if_last_ref*/ true)); - - // tmp_h deleted - ASSERT_EQ(val.deleted--, 1); - - // Release last ref on h1 (already invisible) - ASSERT_TRUE(shard_->Release(h1, /*erase_if_last_ref*/ false)); - - // h1 deleted - ASSERT_EQ(val.deleted--, 1); - h1 = nullptr; - - // Can still find h2, h3 - for (bool erase_if_last_ref : {true, false}) { // but not last ref - tmp_h = shard_->Lookup(key2, hkey2); - ASSERT_EQ(h2, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - - tmp_h = shard_->Lookup(key3, hkey3); - ASSERT_EQ(h3, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - } - - // Release last ref on h2 - ASSERT_FALSE(shard_->Release(h2, /*erase_if_last_ref*/ false)); - - // h2 still not deleted (unreferenced in cache) - ASSERT_EQ(val.deleted, 0); - - // Can still find it - tmp_h = shard_->Lookup(key2, hkey2); - ASSERT_EQ(h2, tmp_h); - - // Release last ref on h2, with erase - ASSERT_TRUE(shard_->Release(h2, /*erase_if_last_ref*/ true)); - - // h2 deleted - ASSERT_EQ(val.deleted--, 1); - tmp_h = shard_->Lookup(key2, hkey2); - ASSERT_EQ(nullptr, tmp_h); - - // Can still find h3 - for (bool erase_if_last_ref : {true, false}) { // but not last ref - tmp_h = shard_->Lookup(key3, hkey3); - ASSERT_EQ(h3, tmp_h); - ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref)); - } - - // Release last ref on h3, without erase - ASSERT_FALSE(shard_->Release(h3, /*erase_if_last_ref*/ false)); - - // h3 still not deleted (unreferenced in cache) - ASSERT_EQ(val.deleted, 0); - - // Explicit erase - shard_->Erase(key3, hkey3); - - // h3 deleted - ASSERT_EQ(val.deleted--, 1); - tmp_h = shard_->Lookup(key3, hkey3); - ASSERT_EQ(nullptr, tmp_h); -} - -// This uses the public API to effectively test CalcHashBits etc. -TEST_F(ClockCacheTest, TableSizesTest) { - for (size_t est_val_size : {1U, 5U, 123U, 2345U, 345678U}) { - SCOPED_TRACE("est_val_size = " + std::to_string(est_val_size)); - for (double est_count : {1.1, 2.2, 511.9, 512.1, 2345.0}) { - SCOPED_TRACE("est_count = " + std::to_string(est_count)); - size_t capacity = static_cast(est_val_size * est_count); - // kDontChargeCacheMetadata - auto cache = HyperClockCacheOptions( - capacity, est_val_size, /*num shard_bits*/ -1, - /*strict_capacity_limit*/ false, - /*memory_allocator*/ nullptr, kDontChargeCacheMetadata) - .MakeSharedCache(); - // Table sizes are currently only powers of two - EXPECT_GE(cache->GetTableAddressCount(), est_count / kLoadFactor); - EXPECT_LE(cache->GetTableAddressCount(), est_count / kLoadFactor * 2.0); - EXPECT_EQ(cache->GetUsage(), 0); - - // kFullChargeMetaData - // Because table sizes are currently only powers of two, sizes get - // really weird when metadata is a huge portion of capacity. For example, - // doubling the table size could cut by 90% the space available to - // values. Therefore, we omit those weird cases for now. - if (est_val_size >= 512) { - cache = HyperClockCacheOptions( - capacity, est_val_size, /*num shard_bits*/ -1, - /*strict_capacity_limit*/ false, - /*memory_allocator*/ nullptr, kFullChargeCacheMetadata) - .MakeSharedCache(); - double est_count_after_meta = - (capacity - cache->GetUsage()) * 1.0 / est_val_size; - EXPECT_GE(cache->GetTableAddressCount(), - est_count_after_meta / kLoadFactor); - EXPECT_LE(cache->GetTableAddressCount(), - est_count_after_meta / kLoadFactor * 2.0); - } - } - } -} - -} // namespace clock_cache - -class TestSecondaryCache : public SecondaryCache { - public: - // Specifies what action to take on a lookup for a particular key - enum ResultType { - SUCCESS, - // Fail lookup immediately - FAIL, - // Defer the result. It will returned after Wait/WaitAll is called - DEFER, - // Defer the result and eventually return failure - DEFER_AND_FAIL - }; - - using ResultMap = std::unordered_map; - - explicit TestSecondaryCache(size_t capacity) - : cache_(NewLRUCache(capacity, 0, false, 0.5 /* high_pri_pool_ratio */, - nullptr, kDefaultToAdaptiveMutex, - kDontChargeCacheMetadata)), - num_inserts_(0), - num_lookups_(0), - inject_failure_(false) {} - - const char* Name() const override { return "TestSecondaryCache"; } - - void InjectFailure() { inject_failure_ = true; } - - void ResetInjectFailure() { inject_failure_ = false; } - - Status Insert(const Slice& key, Cache::ObjectPtr value, - const Cache::CacheItemHelper* helper) override { - if (inject_failure_) { - return Status::Corruption("Insertion Data Corrupted"); - } - CheckCacheKeyCommonPrefix(key); - size_t size; - char* buf; - Status s; - - num_inserts_++; - size = (*helper->size_cb)(value); - buf = new char[size + sizeof(uint64_t)]; - EncodeFixed64(buf, size); - s = (*helper->saveto_cb)(value, 0, size, buf + sizeof(uint64_t)); - if (!s.ok()) { - delete[] buf; - return s; - } - return cache_.Insert(key, buf, size); - } - - std::unique_ptr Lookup( - const Slice& key, const Cache::CacheItemHelper* helper, - Cache::CreateContext* create_context, bool /*wait*/, - bool /*advise_erase*/, bool& kept_in_sec_cache) override { - std::string key_str = key.ToString(); - TEST_SYNC_POINT_CALLBACK("TestSecondaryCache::Lookup", &key_str); - - std::unique_ptr secondary_handle; - kept_in_sec_cache = false; - ResultType type = ResultType::SUCCESS; - auto iter = result_map_.find(key.ToString()); - if (iter != result_map_.end()) { - type = iter->second; - } - if (type == ResultType::FAIL) { - return secondary_handle; - } - - TypedHandle* handle = cache_.Lookup(key); - num_lookups_++; - if (handle) { - Cache::ObjectPtr value = nullptr; - size_t charge = 0; - Status s; - if (type != ResultType::DEFER_AND_FAIL) { - char* ptr = cache_.Value(handle); - size_t size = DecodeFixed64(ptr); - ptr += sizeof(uint64_t); - s = helper->create_cb(Slice(ptr, size), create_context, - /*alloc*/ nullptr, &value, &charge); - } - if (s.ok()) { - secondary_handle.reset(new TestSecondaryCacheResultHandle( - cache_.get(), handle, value, charge, type)); - kept_in_sec_cache = true; - } else { - cache_.Release(handle); - } - } - return secondary_handle; - } - - bool SupportForceErase() const override { return false; } - - void Erase(const Slice& /*key*/) override {} - - void WaitAll(std::vector handles) override { - for (SecondaryCacheResultHandle* handle : handles) { - TestSecondaryCacheResultHandle* sec_handle = - static_cast(handle); - sec_handle->SetReady(); - } - } - - std::string GetPrintableOptions() const override { return ""; } - - void SetResultMap(ResultMap&& map) { result_map_ = std::move(map); } - - uint32_t num_inserts() { return num_inserts_; } - - uint32_t num_lookups() { return num_lookups_; } - - void CheckCacheKeyCommonPrefix(const Slice& key) { - Slice current_prefix(key.data(), OffsetableCacheKey::kCommonPrefixSize); - if (ckey_prefix_.empty()) { - ckey_prefix_ = current_prefix.ToString(); - } else { - EXPECT_EQ(ckey_prefix_, current_prefix.ToString()); - } - } - - private: - class TestSecondaryCacheResultHandle : public SecondaryCacheResultHandle { - public: - TestSecondaryCacheResultHandle(Cache* cache, Cache::Handle* handle, - Cache::ObjectPtr value, size_t size, - ResultType type) - : cache_(cache), - handle_(handle), - value_(value), - size_(size), - is_ready_(true) { - if (type != ResultType::SUCCESS) { - is_ready_ = false; - } - } - - ~TestSecondaryCacheResultHandle() override { cache_->Release(handle_); } - - bool IsReady() override { return is_ready_; } - - void Wait() override {} - - Cache::ObjectPtr Value() override { - assert(is_ready_); - return value_; - } - - size_t Size() override { return Value() ? size_ : 0; } - - void SetReady() { is_ready_ = true; } - - private: - Cache* cache_; - Cache::Handle* handle_; - Cache::ObjectPtr value_; - size_t size_; - bool is_ready_; - }; - - using SharedCache = - BasicTypedSharedCacheInterface; - using TypedHandle = SharedCache::TypedHandle; - SharedCache cache_; - uint32_t num_inserts_; - uint32_t num_lookups_; - bool inject_failure_; - std::string ckey_prefix_; - ResultMap result_map_; -}; - -using secondary_cache_test_util::GetTestingCacheTypes; -using secondary_cache_test_util::WithCacheTypeParam; - -class BasicSecondaryCacheTest : public testing::Test, - public WithCacheTypeParam {}; - -INSTANTIATE_TEST_CASE_P(BasicSecondaryCacheTest, BasicSecondaryCacheTest, - GetTestingCacheTypes()); - -class DBSecondaryCacheTest : public DBTestBase, public WithCacheTypeParam { - public: - DBSecondaryCacheTest() - : DBTestBase("db_secondary_cache_test", /*env_do_fsync=*/true) { - fault_fs_.reset(new FaultInjectionTestFS(env_->GetFileSystem())); - fault_env_.reset(new CompositeEnvWrapper(env_, fault_fs_)); - } - - std::shared_ptr fault_fs_; - std::unique_ptr fault_env_; -}; - -INSTANTIATE_TEST_CASE_P(DBSecondaryCacheTest, DBSecondaryCacheTest, - GetTestingCacheTypes()); - -TEST_P(BasicSecondaryCacheTest, BasicTest) { - std::shared_ptr secondary_cache = - std::make_shared(4096); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - std::shared_ptr stats = CreateDBStatistics(); - CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k3 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - - Random rnd(301); - // Start with warming k3 - std::string str3 = rnd.RandomString(1021); - ASSERT_OK(secondary_cache->InsertSaved(k3.AsSlice(), str3)); - - std::string str1 = rnd.RandomString(1021); - TestItem* item1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length())); - std::string str2 = rnd.RandomString(1021); - TestItem* item2 = new TestItem(str2.data(), str2.length()); - // k1 should be demoted to NVM - ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length())); - - get_perf_context()->Reset(); - Cache::Handle* handle; - handle = cache->Lookup(k2.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW, stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(static_cast(cache->Value(handle))->Size(), str2.size()); - cache->Release(handle); - - // This lookup should promote k1 and demote k2 - handle = cache->Lookup(k1.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW, stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(static_cast(cache->Value(handle))->Size(), str1.size()); - cache->Release(handle); - - // This lookup should promote k3 and demote k1 - handle = cache->Lookup(k3.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW, stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(static_cast(cache->Value(handle))->Size(), str3.size()); - cache->Release(handle); - - ASSERT_EQ(secondary_cache->num_inserts(), 3u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_HITS), - secondary_cache->num_lookups()); - PerfContext perf_ctx = *get_perf_context(); - ASSERT_EQ(perf_ctx.secondary_cache_hit_count, secondary_cache->num_lookups()); - - cache.reset(); - secondary_cache.reset(); -} - -TEST_P(BasicSecondaryCacheTest, StatsTest) { - std::shared_ptr secondary_cache = - std::make_shared(4096); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - std::shared_ptr stats = CreateDBStatistics(); - CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k3 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - - Random rnd(301); - // Start with warming secondary cache - std::string str1 = rnd.RandomString(1020); - std::string str2 = rnd.RandomString(1020); - std::string str3 = rnd.RandomString(1020); - ASSERT_OK(secondary_cache->InsertSaved(k1.AsSlice(), str1)); - ASSERT_OK(secondary_cache->InsertSaved(k2.AsSlice(), str2)); - ASSERT_OK(secondary_cache->InsertSaved(k3.AsSlice(), str3)); - - get_perf_context()->Reset(); - Cache::Handle* handle; - handle = cache->Lookup(k1.AsSlice(), GetHelper(CacheEntryRole::kFilterBlock), - /*context*/ this, Cache::Priority::LOW, stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(static_cast(cache->Value(handle))->Size(), str1.size()); - cache->Release(handle); - - handle = cache->Lookup(k2.AsSlice(), GetHelper(CacheEntryRole::kIndexBlock), - /*context*/ this, Cache::Priority::LOW, stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(static_cast(cache->Value(handle))->Size(), str2.size()); - cache->Release(handle); - - handle = cache->Lookup(k3.AsSlice(), GetHelper(CacheEntryRole::kDataBlock), - /*context*/ this, Cache::Priority::LOW, stats.get()); - ASSERT_NE(handle, nullptr); - ASSERT_EQ(static_cast(cache->Value(handle))->Size(), str3.size()); - cache->Release(handle); - - ASSERT_EQ(secondary_cache->num_inserts(), 3u); - ASSERT_EQ(secondary_cache->num_lookups(), 3u); - ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_HITS), - secondary_cache->num_lookups()); - ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_FILTER_HITS), 1); - ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_INDEX_HITS), 1); - ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_DATA_HITS), 1); - PerfContext perf_ctx = *get_perf_context(); - ASSERT_EQ(perf_ctx.secondary_cache_hit_count, secondary_cache->num_lookups()); - - cache.reset(); - secondary_cache.reset(); -} - -TEST_P(BasicSecondaryCacheTest, BasicFailTest) { - std::shared_ptr secondary_cache = - std::make_shared(2048); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - - Random rnd(301); - std::string str1 = rnd.RandomString(1020); - auto item1 = std::make_unique(str1.data(), str1.length()); - // NOTE: changed to assert helper != nullptr for efficiency / code size - // ASSERT_TRUE(cache->Insert(k1.AsSlice(), item1.get(), nullptr, - // str1.length()).IsInvalidArgument()); - ASSERT_OK( - cache->Insert(k1.AsSlice(), item1.get(), GetHelper(), str1.length())); - item1.release(); // Appease clang-analyze "potential memory leak" - - Cache::Handle* handle; - handle = cache->Lookup(k2.AsSlice(), nullptr, /*context*/ this, - Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - - handle = cache->Lookup(k2.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - - Cache::AsyncLookupHandle async_handle; - async_handle.key = k2.AsSlice(); - async_handle.helper = GetHelper(); - async_handle.create_context = this; - async_handle.priority = Cache::Priority::LOW; - cache->StartAsyncLookup(async_handle); - cache->Wait(async_handle); - handle = async_handle.Result(); - ASSERT_EQ(handle, nullptr); - - cache.reset(); - secondary_cache.reset(); -} - -TEST_P(BasicSecondaryCacheTest, SaveFailTest) { - std::shared_ptr secondary_cache = - std::make_shared(2048); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - - Random rnd(301); - std::string str1 = rnd.RandomString(1020); - TestItem* item1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelperFail(), str1.length())); - std::string str2 = rnd.RandomString(1020); - TestItem* item2 = new TestItem(str2.data(), str2.length()); - // k1 should be demoted to NVM - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelperFail(), str2.length())); - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - - Cache::Handle* handle; - handle = cache->Lookup(k2.AsSlice(), GetHelperFail(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - // This lookup should fail, since k1 demotion would have failed - handle = cache->Lookup(k1.AsSlice(), GetHelperFail(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - // Since k1 didn't get promoted, k2 should still be in cache - handle = cache->Lookup(k2.AsSlice(), GetHelperFail(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 1u); - - cache.reset(); - secondary_cache.reset(); -} - -TEST_P(BasicSecondaryCacheTest, CreateFailTest) { - std::shared_ptr secondary_cache = - std::make_shared(2048); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - - Random rnd(301); - std::string str1 = rnd.RandomString(1020); - TestItem* item1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length())); - std::string str2 = rnd.RandomString(1020); - TestItem* item2 = new TestItem(str2.data(), str2.length()); - // k1 should be demoted to NVM - ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length())); - - Cache::Handle* handle; - SetFailCreate(true); - handle = cache->Lookup(k2.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - // This lookup should fail, since k1 creation would have failed - handle = cache->Lookup(k1.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_EQ(handle, nullptr); - // Since k1 didn't get promoted, k2 should still be in cache - handle = cache->Lookup(k2.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_NE(handle, nullptr); - cache->Release(handle); - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 1u); - - cache.reset(); - secondary_cache.reset(); -} - -TEST_P(BasicSecondaryCacheTest, FullCapacityTest) { - for (bool strict_capacity_limit : {false, true}) { - std::shared_ptr secondary_cache = - std::make_shared(2048); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 0 /* num_shard_bits */, - strict_capacity_limit, secondary_cache); - CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get()); - - Random rnd(301); - std::string str1 = rnd.RandomString(1020); - TestItem* item1 = new TestItem(str1.data(), str1.length()); - ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length())); - std::string str2 = rnd.RandomString(1020); - TestItem* item2 = new TestItem(str2.data(), str2.length()); - // k1 should be demoted to NVM - ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length())); - - Cache::Handle* handle2; - handle2 = cache->Lookup(k2.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_NE(handle2, nullptr); - // k1 lookup fails without secondary cache support - Cache::Handle* handle1; - handle1 = cache->Lookup( - k1.AsSlice(), - GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false), - /*context*/ this, Cache::Priority::LOW); - ASSERT_EQ(handle1, nullptr); - - // k1 promotion can fail with strict_capacit_limit=true, but Lookup still - // succeeds using a standalone handle - handle1 = cache->Lookup(k1.AsSlice(), GetHelper(), - /*context*/ this, Cache::Priority::LOW); - ASSERT_NE(handle1, nullptr); - - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 1u); - - // Releasing k2's handle first, k2 is evicted from primary iff k1 promotion - // was charged to the cache (except HCC doesn't erase in Release() over - // capacity) - // FIXME: Insert to secondary from Release disabled - cache->Release(handle2); - cache->Release(handle1); - handle2 = cache->Lookup( - k2.AsSlice(), - GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false), - /*context*/ this, Cache::Priority::LOW); - if (strict_capacity_limit || GetParam() == kHyperClock) { - ASSERT_NE(handle2, nullptr); - cache->Release(handle2); - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - } else { - ASSERT_EQ(handle2, nullptr); - // FIXME: Insert to secondary from Release disabled - // ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - } - - cache.reset(); - secondary_cache.reset(); - } -} - -// In this test, the block cache size is set to 4096, after insert 6 KV-pairs -// and flush, there are 5 blocks in this SST file, 2 data blocks and 3 meta -// blocks. block_1 size is 4096 and block_2 size is 2056. The total size -// of the meta blocks are about 900 to 1000. Therefore, in any situation, -// if we try to insert block_1 to the block cache, it will always fails. Only -// block_2 will be successfully inserted into the block cache. -// CORRECTION: this is not quite right. block_1 can be inserted into the block -// cache because strict_capacity_limit=false, but it is removed from the cache -// in Release() because of being over-capacity, without demoting to secondary -// cache. HyperClockCache doesn't check capacity on release (for efficiency) -// so can demote the over-capacity item to secondary cache. Also, we intend to -// add support for demotion in Release, but that currently causes too much -// unit test churn. -TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) { - if (GetParam() == kHyperClock) { - // See CORRECTION above - ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors"); - return; - } - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(4 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - - // Set the file paranoid check, so after flush, the file will be read - // all the blocks will be accessed. - options.paranoid_file_checks = true; - DestroyAndReopen(options); - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - // After Flush is successful, RocksDB will do the paranoid check for the new - // SST file. Meta blocks are always cached in the block cache and they - // will not be evicted. When block_2 is cache miss and read out, it is - // inserted to the block cache. Note that, block_1 is never successfully - // inserted to the block cache. Here are 2 lookups in the secondary cache - // for block_1 and block_2 - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - Compact("a", "z"); - // Compaction will create the iterator to scan the whole file. So all the - // blocks are needed. Meta blocks are always cached. When block_1 is read - // out, block_2 is evicted from block cache and inserted to secondary - // cache. - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 3u); - - std::string v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // The first data block is not in the cache, similarly, trigger the block - // cache Lookup and secondary cache lookup for block_1. But block_1 will not - // be inserted successfully due to the size. Currently, cache only has - // the meta blocks. - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - // The second data block is not in the cache, similarly, trigger the block - // cache Lookup and secondary cache lookup for block_2 and block_2 is found - // in the secondary cache. Now block cache has block_2 - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 5u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - // block_2 is in the block cache. There is a block cache hit. No need to - // lookup or insert the secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 5u); - - v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // Lookup the first data block, not in the block cache, so lookup the - // secondary cache. Also not in the secondary cache. After Get, still - // block_1 is will not be cached. - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 6u); - - v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // Lookup the first data block, not in the block cache, so lookup the - // secondary cache. Also not in the secondary cache. After Get, still - // block_1 is will not be cached. - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 7u); - - Destroy(options); -} - -// In this test, the block cache size is set to 6100, after insert 6 KV-pairs -// and flush, there are 5 blocks in this SST file, 2 data blocks and 3 meta -// blocks. block_1 size is 4096 and block_2 size is 2056. The total size -// of the meta blocks are about 900 to 1000. Therefore, we can successfully -// insert and cache block_1 in the block cache (this is the different place -// from TestSecondaryCacheCorrectness1) -TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors"); - return; - } - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(6100 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.paranoid_file_checks = true; - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - DestroyAndReopen(options); - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - // After Flush is successful, RocksDB will do the paranoid check for the new - // SST file. Meta blocks are always cached in the block cache and they - // will not be evicted. When block_2 is cache miss and read out, it is - // inserted to the block cache. Thefore, block_1 is evicted from block - // cache and successfully inserted to the secondary cache. Here are 2 - // lookups in the secondary cache for block_1 and block_2. - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - Compact("a", "z"); - // Compaction will create the iterator to scan the whole file. So all the - // blocks are needed. After Flush, only block_2 is cached in block cache - // and block_1 is in the secondary cache. So when read block_1, it is - // read out from secondary cache and inserted to block cache. At the same - // time, block_2 is inserted to secondary cache. Now, secondary cache has - // both block_1 and block_2. After compaction, block_1 is in the cache. - ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_lookups(), 3u); - - std::string v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // This Get needs to access block_1, since block_1 is cached in block cache - // there is no secondary cache lookup. - ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_lookups(), 3u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - // This Get needs to access block_2 which is not in the block cache. So - // it will lookup the secondary cache for block_2 and cache it in the - // block_cache. - ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - // This Get needs to access block_2 which is already in the block cache. - // No need to lookup secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // This Get needs to access block_1, since block_1 is not in block cache - // there is one econdary cache lookup. Then, block_1 is cached in the - // block cache. - ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_lookups(), 5u); - - v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // This Get needs to access block_1, since block_1 is cached in block cache - // there is no secondary cache lookup. - ASSERT_EQ(secondary_cache->num_inserts(), 2u); - ASSERT_EQ(secondary_cache->num_lookups(), 5u); - - Destroy(options); -} - -// The block cache size is set to 1024*1024, after insert 6 KV-pairs -// and flush, there are 5 blocks in this SST file, 2 data blocks and 3 meta -// blocks. block_1 size is 4096 and block_2 size is 2056. The total size -// of the meta blocks are about 900 to 1000. Therefore, we can successfully -// cache all the blocks in the block cache and there is not secondary cache -// insertion. 2 lookup is needed for the blocks. -TEST_P(DBSecondaryCacheTest, NoSecondaryCacheInsertion) { - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(1024 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.paranoid_file_checks = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - - DestroyAndReopen(options); - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1000); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - // After Flush is successful, RocksDB will do the paranoid check for the new - // SST file. Meta blocks are always cached in the block cache and they - // will not be evicted. Now, block cache is large enough, it cache - // both block_1 and block_2. When first time read block_1 and block_2 - // there are cache misses. So 2 secondary cache lookups are needed for - // the 2 blocks - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - Compact("a", "z"); - // Compaction will iterate the whole SST file. Since all the data blocks - // are in the block cache. No need to lookup the secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - std::string v = Get(Key(0)); - ASSERT_EQ(1000, v.size()); - // Since the block cache is large enough, all the blocks are cached. we - // do not need to lookup the seondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - Destroy(options); -} - -TEST_P(DBSecondaryCacheTest, SecondaryCacheIntensiveTesting) { - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(8 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - DestroyAndReopen(options); - Random rnd(301); - const int N = 256; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1000); - ASSERT_OK(Put(Key(i), p_v)); - } - ASSERT_OK(Flush()); - Compact("a", "z"); - - Random r_index(47); - std::string v; - for (int i = 0; i < 1000; i++) { - uint32_t key_i = r_index.Next() % N; - v = Get(Key(key_i)); - } - - // We have over 200 data blocks there will be multiple insertion - // and lookups. - ASSERT_GE(secondary_cache->num_inserts(), 1u); - ASSERT_GE(secondary_cache->num_lookups(), 1u); - - Destroy(options); -} - -// In this test, the block cache size is set to 4096, after insert 6 KV-pairs -// and flush, there are 5 blocks in this SST file, 2 data blocks and 3 meta -// blocks. block_1 size is 4096 and block_2 size is 2056. The total size -// of the meta blocks are about 900 to 1000. Therefore, in any situation, -// if we try to insert block_1 to the block cache, it will always fails. Only -// block_2 will be successfully inserted into the block cache. -TEST_P(DBSecondaryCacheTest, SecondaryCacheFailureTest) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors"); - return; - } - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(4 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.paranoid_file_checks = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - DestroyAndReopen(options); - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - // After Flush is successful, RocksDB will do the paranoid check for the new - // SST file. Meta blocks are always cached in the block cache and they - // will not be evicted. When block_2 is cache miss and read out, it is - // inserted to the block cache. Note that, block_1 is never successfully - // inserted to the block cache. Here are 2 lookups in the secondary cache - // for block_1 and block_2 - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - // Fail the insertion, in LRU cache, the secondary insertion returned status - // is not checked, therefore, the DB will not be influenced. - secondary_cache->InjectFailure(); - Compact("a", "z"); - // Compaction will create the iterator to scan the whole file. So all the - // blocks are needed. Meta blocks are always cached. When block_1 is read - // out, block_2 is evicted from block cache and inserted to secondary - // cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 3u); - - std::string v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // The first data block is not in the cache, similarly, trigger the block - // cache Lookup and secondary cache lookup for block_1. But block_1 will not - // be inserted successfully due to the size. Currently, cache only has - // the meta blocks. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - // The second data block is not in the cache, similarly, trigger the block - // cache Lookup and secondary cache lookup for block_2 and block_2 is found - // in the secondary cache. Now block cache has block_2 - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 5u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - // block_2 is in the block cache. There is a block cache hit. No need to - // lookup or insert the secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 5u); - - v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // Lookup the first data block, not in the block cache, so lookup the - // secondary cache. Also not in the secondary cache. After Get, still - // block_1 is will not be cached. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 6u); - - v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - // Lookup the first data block, not in the block cache, so lookup the - // secondary cache. Also not in the secondary cache. After Get, still - // block_1 is will not be cached. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 7u); - secondary_cache->ResetInjectFailure(); - - Destroy(options); -} - -TEST_P(BasicSecondaryCacheTest, BasicWaitAllTest) { - std::shared_ptr secondary_cache = - std::make_shared(32 * 1024); - std::shared_ptr cache = - NewCache(1024 /* capacity */, 2 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - const int num_keys = 32; - OffsetableCacheKey ock{"foo", "bar", 1}; - - Random rnd(301); - std::vector values; - for (int i = 0; i < num_keys; ++i) { - std::string str = rnd.RandomString(1020); - values.emplace_back(str); - TestItem* item = new TestItem(str.data(), str.length()); - ASSERT_OK(cache->Insert(ock.WithOffset(i).AsSlice(), item, GetHelper(), - str.length())); - } - // Force all entries to be evicted to the secondary cache - if (GetParam() == kHyperClock) { - // HCC doesn't respond immediately to SetCapacity - for (int i = 9000; i < 9030; ++i) { - ASSERT_OK(cache->Insert(ock.WithOffset(i).AsSlice(), nullptr, - &kNoopCacheItemHelper, 256)); - } - } else { - cache->SetCapacity(0); - } - ASSERT_EQ(secondary_cache->num_inserts(), 32u); - cache->SetCapacity(32 * 1024); - - secondary_cache->SetResultMap( - {{ock.WithOffset(3).AsSlice().ToString(), - TestSecondaryCache::ResultType::DEFER}, - {ock.WithOffset(4).AsSlice().ToString(), - TestSecondaryCache::ResultType::DEFER_AND_FAIL}, - {ock.WithOffset(5).AsSlice().ToString(), - TestSecondaryCache::ResultType::FAIL}}); - - std::array async_handles; - std::array cache_keys; - for (size_t i = 0; i < async_handles.size(); ++i) { - auto& ah = async_handles[i]; - cache_keys[i] = ock.WithOffset(i); - ah.key = cache_keys[i].AsSlice(); - ah.helper = GetHelper(); - ah.create_context = this; - ah.priority = Cache::Priority::LOW; - cache->StartAsyncLookup(ah); - } - cache->WaitAll(&async_handles[0], async_handles.size()); - for (size_t i = 0; i < async_handles.size(); ++i) { - SCOPED_TRACE("i = " + std::to_string(i)); - Cache::Handle* result = async_handles[i].Result(); - if (i == 4 || i == 5) { - ASSERT_EQ(result, nullptr); - continue; - } else { - ASSERT_NE(result, nullptr); - TestItem* item = static_cast(cache->Value(result)); - ASSERT_EQ(item->ToString(), values[i]); - } - cache->Release(result); - } - - cache.reset(); - secondary_cache.reset(); -} - -// In this test, we have one KV pair per data block. We indirectly determine -// the cache key associated with each data block (and thus each KV) by using -// a sync point callback in TestSecondaryCache::Lookup. We then control the -// lookup result by setting the ResultMap. -TEST_P(DBSecondaryCacheTest, TestSecondaryCacheMultiGet) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors"); - return; - } - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(1 << 20 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - table_options.cache_index_and_filter_blocks = false; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.paranoid_file_checks = true; - DestroyAndReopen(options); - Random rnd(301); - const int N = 8; - std::vector keys; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(4000); - keys.emplace_back(p_v); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - // After Flush is successful, RocksDB does the paranoid check for the new - // SST file. This will try to lookup all data blocks in the secondary - // cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 8u); - - cache->SetCapacity(0); - ASSERT_EQ(secondary_cache->num_inserts(), 8u); - cache->SetCapacity(1 << 20); - - std::vector cache_keys; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "TestSecondaryCache::Lookup", [&cache_keys](void* key) -> void { - cache_keys.emplace_back(*(static_cast(key))); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - for (int i = 0; i < N; ++i) { - std::string v = Get(Key(i)); - ASSERT_EQ(4000, v.size()); - ASSERT_EQ(v, keys[i]); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(secondary_cache->num_lookups(), 16u); - cache->SetCapacity(0); - cache->SetCapacity(1 << 20); - - ASSERT_EQ(Get(Key(2)), keys[2]); - ASSERT_EQ(Get(Key(7)), keys[7]); - secondary_cache->SetResultMap( - {{cache_keys[3], TestSecondaryCache::ResultType::DEFER}, - {cache_keys[4], TestSecondaryCache::ResultType::DEFER_AND_FAIL}, - {cache_keys[5], TestSecondaryCache::ResultType::FAIL}}); - - std::vector mget_keys( - {Key(0), Key(1), Key(2), Key(3), Key(4), Key(5), Key(6), Key(7)}); - std::vector values(mget_keys.size()); - std::vector s(keys.size()); - std::vector key_slices; - for (const std::string& key : mget_keys) { - key_slices.emplace_back(key); - } - uint32_t num_lookups = secondary_cache->num_lookups(); - dbfull()->MultiGet(ReadOptions(), dbfull()->DefaultColumnFamily(), - key_slices.size(), key_slices.data(), values.data(), - s.data(), false); - ASSERT_EQ(secondary_cache->num_lookups(), num_lookups + 5); - for (int i = 0; i < N; ++i) { - ASSERT_OK(s[i]); - ASSERT_EQ(values[i].ToString(), keys[i]); - values[i].Reset(); - } - Destroy(options); -} - -class CacheWithStats : public CacheWrapper { - public: - using CacheWrapper::CacheWrapper; - - static const char* kClassName() { return "CacheWithStats"; } - const char* Name() const override { return kClassName(); } - - Status Insert(const Slice& key, Cache::ObjectPtr value, - const CacheItemHelper* helper, size_t charge, - Handle** handle = nullptr, - Priority priority = Priority::LOW) override { - insert_count_++; - return target_->Insert(key, value, helper, charge, handle, priority); - } - Handle* Lookup(const Slice& key, const CacheItemHelper* helper, - CreateContext* create_context, Priority priority, - Statistics* stats = nullptr) override { - lookup_count_++; - return target_->Lookup(key, helper, create_context, priority, stats); - } - - uint32_t GetInsertCount() { return insert_count_; } - uint32_t GetLookupcount() { return lookup_count_; } - void ResetCount() { - insert_count_ = 0; - lookup_count_ = 0; - } - - private: - uint32_t insert_count_ = 0; - uint32_t lookup_count_ = 0; -}; - -TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadBasic) { - std::shared_ptr base_cache = - NewCache(1024 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */); - std::shared_ptr cache = - std::make_shared(base_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - DestroyAndReopen(options); - fault_fs_->SetFailGetUniqueId(true); - - Random rnd(301); - const int N = 256; - std::vector value; - char buf[1000]; - memset(buf, 'a', 1000); - value.resize(N); - for (int i = 0; i < N; i++) { - // std::string p_v = rnd.RandomString(1000); - std::string p_v(buf, 1000); - value[i] = p_v; - ASSERT_OK(Put(Key(i), p_v)); - } - ASSERT_OK(Flush()); - Compact("a", "z"); - - // do th eread for all the key value pairs, so all the blocks should be in - // cache - uint32_t start_insert = cache->GetInsertCount(); - uint32_t start_lookup = cache->GetLookupcount(); - std::string v; - for (int i = 0; i < N; i++) { - v = Get(Key(i)); - ASSERT_EQ(v, value[i]); - } - uint32_t dump_insert = cache->GetInsertCount() - start_insert; - uint32_t dump_lookup = cache->GetLookupcount() - start_lookup; - ASSERT_EQ(63, - static_cast(dump_insert)); // the insert in the block cache - ASSERT_EQ(256, - static_cast(dump_lookup)); // the lookup in the block cache - // We have enough blocks in the block cache - - CacheDumpOptions cd_options; - cd_options.clock = fault_env_->GetSystemClock().get(); - std::string dump_path = db_->GetName() + "/cache_dump"; - std::unique_ptr dump_writer; - Status s = NewToFileCacheDumpWriter(fault_fs_, FileOptions(), dump_path, - &dump_writer); - ASSERT_OK(s); - std::unique_ptr cache_dumper; - s = NewDefaultCacheDumper(cd_options, cache, std::move(dump_writer), - &cache_dumper); - ASSERT_OK(s); - std::vector db_list; - db_list.push_back(db_); - s = cache_dumper->SetDumpFilter(db_list); - ASSERT_OK(s); - s = cache_dumper->DumpCacheEntriesToWriter(); - ASSERT_OK(s); - cache_dumper.reset(); - - // we have a new cache it is empty, then, before we do the Get, we do the - // dumpload - std::shared_ptr secondary_cache = - std::make_shared(2048 * 1024); - // This time with secondary cache - base_cache = NewCache(1024 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - cache = std::make_shared(base_cache); - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - - // start to load the data to new block cache - start_insert = secondary_cache->num_inserts(); - start_lookup = secondary_cache->num_lookups(); - std::unique_ptr dump_reader; - s = NewFromFileCacheDumpReader(fault_fs_, FileOptions(), dump_path, - &dump_reader); - ASSERT_OK(s); - std::unique_ptr cache_loader; - s = NewDefaultCacheDumpedLoader(cd_options, table_options, secondary_cache, - std::move(dump_reader), &cache_loader); - ASSERT_OK(s); - s = cache_loader->RestoreCacheEntriesToSecondaryCache(); - ASSERT_OK(s); - uint32_t load_insert = secondary_cache->num_inserts() - start_insert; - uint32_t load_lookup = secondary_cache->num_lookups() - start_lookup; - // check the number we inserted - ASSERT_EQ(64, static_cast(load_insert)); - ASSERT_EQ(0, static_cast(load_lookup)); - ASSERT_OK(s); - - Reopen(options); - - // After load, we do the Get again - start_insert = secondary_cache->num_inserts(); - start_lookup = secondary_cache->num_lookups(); - uint32_t cache_insert = cache->GetInsertCount(); - uint32_t cache_lookup = cache->GetLookupcount(); - for (int i = 0; i < N; i++) { - v = Get(Key(i)); - ASSERT_EQ(v, value[i]); - } - uint32_t final_insert = secondary_cache->num_inserts() - start_insert; - uint32_t final_lookup = secondary_cache->num_lookups() - start_lookup; - // no insert to secondary cache - ASSERT_EQ(0, static_cast(final_insert)); - // lookup the secondary to get all blocks - ASSERT_EQ(64, static_cast(final_lookup)); - uint32_t block_insert = cache->GetInsertCount() - cache_insert; - uint32_t block_lookup = cache->GetLookupcount() - cache_lookup; - // Check the new block cache insert and lookup, should be no insert since all - // blocks are from the secondary cache. - ASSERT_EQ(0, static_cast(block_insert)); - ASSERT_EQ(256, static_cast(block_lookup)); - - fault_fs_->SetFailGetUniqueId(false); - Destroy(options); -} - -TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) { - std::shared_ptr base_cache = - NewCache(1024 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */); - std::shared_ptr cache = - std::make_shared(base_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - std::string dbname1 = test::PerThreadDBPath("db_1"); - ASSERT_OK(DestroyDB(dbname1, options)); - DB* db1 = nullptr; - ASSERT_OK(DB::Open(options, dbname1, &db1)); - std::string dbname2 = test::PerThreadDBPath("db_2"); - ASSERT_OK(DestroyDB(dbname2, options)); - DB* db2 = nullptr; - ASSERT_OK(DB::Open(options, dbname2, &db2)); - fault_fs_->SetFailGetUniqueId(true); - - // write the KVs to db1 - Random rnd(301); - const int N = 256; - std::vector value1; - WriteOptions wo; - char buf[1000]; - memset(buf, 'a', 1000); - value1.resize(N); - for (int i = 0; i < N; i++) { - std::string p_v(buf, 1000); - value1[i] = p_v; - ASSERT_OK(db1->Put(wo, Key(i), p_v)); - } - ASSERT_OK(db1->Flush(FlushOptions())); - Slice bg("a"); - Slice ed("b"); - ASSERT_OK(db1->CompactRange(CompactRangeOptions(), &bg, &ed)); - - // Write the KVs to DB2 - std::vector value2; - memset(buf, 'b', 1000); - value2.resize(N); - for (int i = 0; i < N; i++) { - std::string p_v(buf, 1000); - value2[i] = p_v; - ASSERT_OK(db2->Put(wo, Key(i), p_v)); - } - ASSERT_OK(db2->Flush(FlushOptions())); - ASSERT_OK(db2->CompactRange(CompactRangeOptions(), &bg, &ed)); - - // do th eread for all the key value pairs, so all the blocks should be in - // cache - uint32_t start_insert = cache->GetInsertCount(); - uint32_t start_lookup = cache->GetLookupcount(); - ReadOptions ro; - std::string v; - for (int i = 0; i < N; i++) { - ASSERT_OK(db1->Get(ro, Key(i), &v)); - ASSERT_EQ(v, value1[i]); - } - for (int i = 0; i < N; i++) { - ASSERT_OK(db2->Get(ro, Key(i), &v)); - ASSERT_EQ(v, value2[i]); - } - uint32_t dump_insert = cache->GetInsertCount() - start_insert; - uint32_t dump_lookup = cache->GetLookupcount() - start_lookup; - ASSERT_EQ(128, - static_cast(dump_insert)); // the insert in the block cache - ASSERT_EQ(512, - static_cast(dump_lookup)); // the lookup in the block cache - // We have enough blocks in the block cache - - CacheDumpOptions cd_options; - cd_options.clock = fault_env_->GetSystemClock().get(); - std::string dump_path = db1->GetName() + "/cache_dump"; - std::unique_ptr dump_writer; - Status s = NewToFileCacheDumpWriter(fault_fs_, FileOptions(), dump_path, - &dump_writer); - ASSERT_OK(s); - std::unique_ptr cache_dumper; - s = NewDefaultCacheDumper(cd_options, cache, std::move(dump_writer), - &cache_dumper); - ASSERT_OK(s); - std::vector db_list; - db_list.push_back(db1); - s = cache_dumper->SetDumpFilter(db_list); - ASSERT_OK(s); - s = cache_dumper->DumpCacheEntriesToWriter(); - ASSERT_OK(s); - cache_dumper.reset(); - - // we have a new cache it is empty, then, before we do the Get, we do the - // dumpload - std::shared_ptr secondary_cache = - std::make_shared(2048 * 1024); - // This time with secondary_cache - base_cache = NewCache(1024 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - cache = std::make_shared(base_cache); - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - - // Start the cache loading process - start_insert = secondary_cache->num_inserts(); - start_lookup = secondary_cache->num_lookups(); - std::unique_ptr dump_reader; - s = NewFromFileCacheDumpReader(fault_fs_, FileOptions(), dump_path, - &dump_reader); - ASSERT_OK(s); - std::unique_ptr cache_loader; - s = NewDefaultCacheDumpedLoader(cd_options, table_options, secondary_cache, - std::move(dump_reader), &cache_loader); - ASSERT_OK(s); - s = cache_loader->RestoreCacheEntriesToSecondaryCache(); - ASSERT_OK(s); - uint32_t load_insert = secondary_cache->num_inserts() - start_insert; - uint32_t load_lookup = secondary_cache->num_lookups() - start_lookup; - // check the number we inserted - ASSERT_EQ(64, static_cast(load_insert)); - ASSERT_EQ(0, static_cast(load_lookup)); - ASSERT_OK(s); - - ASSERT_OK(db1->Close()); - delete db1; - ASSERT_OK(DB::Open(options, dbname1, &db1)); - - // After load, we do the Get again. To validate the cache, we do not allow any - // I/O, so we set the file system to false. - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - fault_fs_->SetFilesystemActive(false, error_msg); - start_insert = secondary_cache->num_inserts(); - start_lookup = secondary_cache->num_lookups(); - uint32_t cache_insert = cache->GetInsertCount(); - uint32_t cache_lookup = cache->GetLookupcount(); - for (int i = 0; i < N; i++) { - ASSERT_OK(db1->Get(ro, Key(i), &v)); - ASSERT_EQ(v, value1[i]); - } - uint32_t final_insert = secondary_cache->num_inserts() - start_insert; - uint32_t final_lookup = secondary_cache->num_lookups() - start_lookup; - // no insert to secondary cache - ASSERT_EQ(0, static_cast(final_insert)); - // lookup the secondary to get all blocks - ASSERT_EQ(64, static_cast(final_lookup)); - uint32_t block_insert = cache->GetInsertCount() - cache_insert; - uint32_t block_lookup = cache->GetLookupcount() - cache_lookup; - // Check the new block cache insert and lookup, should be no insert since all - // blocks are from the secondary cache. - ASSERT_EQ(0, static_cast(block_insert)); - ASSERT_EQ(256, static_cast(block_lookup)); - fault_fs_->SetFailGetUniqueId(false); - fault_fs_->SetFilesystemActive(true); - delete db1; - delete db2; - ASSERT_OK(DestroyDB(dbname1, options)); - ASSERT_OK(DestroyDB(dbname2, options)); -} - -// Test the option not to use the secondary cache in a certain DB. -TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) { - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(4 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - options.lowest_used_cache_tier = CacheTier::kVolatileTier; - - // Set the file paranoid check, so after flush, the file will be read - // all the blocks will be accessed. - options.paranoid_file_checks = true; - DestroyAndReopen(options); - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i + 70), p_v)); - } - - ASSERT_OK(Flush()); - - // Flush will trigger the paranoid check and read blocks. But only block cache - // will be read. No operations for secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - Compact("a", "z"); - - // Compaction will also insert and evict blocks, no operations to the block - // cache. No operations for secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - std::string v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - - // Check the data in first block. Cache miss, direclty read from SST file. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - - // Check the second block. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - - // block cache hit - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - v = Get(Key(70)); - ASSERT_EQ(1007, v.size()); - - // Check the first block in the second SST file. Cache miss and trigger SST - // file read. No operations for secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - v = Get(Key(75)); - ASSERT_EQ(1007, v.size()); - - // Check the second block in the second SST file. Cache miss and trigger SST - // file read. No operations for secondary cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - Destroy(options); -} - -// We disable the secondary cache in DBOptions at first. Close and reopen the DB -// with new options, which set the lowest_used_cache_tier to -// kNonVolatileBlockTier. So secondary cache will be used. -TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors"); - return; - } - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(4 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - fault_fs_->SetFailGetUniqueId(true); - options.lowest_used_cache_tier = CacheTier::kVolatileTier; - - // Set the file paranoid check, so after flush, the file will be read - // all the blocks will be accessed. - options.paranoid_file_checks = true; - DestroyAndReopen(options); - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i), p_v)); - } - - ASSERT_OK(Flush()); - - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(Put(Key(i + 70), p_v)); - } - - ASSERT_OK(Flush()); - - // Flush will trigger the paranoid check and read blocks. But only block cache - // will be read. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - Compact("a", "z"); - - // Compaction will also insert and evict blocks, no operations to the block - // cache. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - std::string v = Get(Key(0)); - ASSERT_EQ(1007, v.size()); - - // Check the data in first block. Cache miss, direclty read from SST file. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - - // Check the second block. - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - v = Get(Key(5)); - ASSERT_EQ(1007, v.size()); - - // block cache hit - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - - // Change the option to enable secondary cache after we Reopen the DB - options.lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier; - Reopen(options); - - v = Get(Key(70)); - ASSERT_EQ(1007, v.size()); - - // Enable the secondary cache, trigger lookup of the first block in second SST - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 1u); - - v = Get(Key(75)); - ASSERT_EQ(1007, v.size()); - - // trigger lookup of the second block in second SST - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - Destroy(options); -} - -// Two DB test. We create 2 DBs sharing the same block cache and secondary -// cache. We diable the secondary cache option for DB2. -TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) { - if (GetParam() == kHyperClock) { - ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors"); - return; - } - std::shared_ptr secondary_cache( - new TestSecondaryCache(2048 * 1024)); - std::shared_ptr cache = - NewCache(4 * 1024 /* capacity */, 0 /* num_shard_bits */, - false /* strict_capacity_limit */, secondary_cache); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.block_size = 4 * 1024; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = fault_env_.get(); - options.paranoid_file_checks = true; - std::string dbname1 = test::PerThreadDBPath("db_t_1"); - ASSERT_OK(DestroyDB(dbname1, options)); - DB* db1 = nullptr; - ASSERT_OK(DB::Open(options, dbname1, &db1)); - std::string dbname2 = test::PerThreadDBPath("db_t_2"); - ASSERT_OK(DestroyDB(dbname2, options)); - DB* db2 = nullptr; - Options options2 = options; - options2.lowest_used_cache_tier = CacheTier::kVolatileTier; - ASSERT_OK(DB::Open(options2, dbname2, &db2)); - fault_fs_->SetFailGetUniqueId(true); - - WriteOptions wo; - Random rnd(301); - const int N = 6; - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(db1->Put(wo, Key(i), p_v)); - } - - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 0u); - ASSERT_OK(db1->Flush(FlushOptions())); - - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - for (int i = 0; i < N; i++) { - std::string p_v = rnd.RandomString(1007); - ASSERT_OK(db2->Put(wo, Key(i), p_v)); - } - - // No change in the secondary cache, since it is disabled in DB2 - ASSERT_EQ(secondary_cache->num_inserts(), 0u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - ASSERT_OK(db2->Flush(FlushOptions())); - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - Slice bg("a"); - Slice ed("b"); - ASSERT_OK(db1->CompactRange(CompactRangeOptions(), &bg, &ed)); - ASSERT_OK(db2->CompactRange(CompactRangeOptions(), &bg, &ed)); - - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 2u); - - ReadOptions ro; - std::string v; - ASSERT_OK(db1->Get(ro, Key(0), &v)); - ASSERT_EQ(1007, v.size()); - - // DB 1 has lookup block 1 and it is miss in block cache, trigger secondary - // cache lookup - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 3u); - - ASSERT_OK(db1->Get(ro, Key(5), &v)); - ASSERT_EQ(1007, v.size()); - - // DB 1 lookup the second block and it is miss in block cache, trigger - // secondary cache lookup - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - ASSERT_OK(db2->Get(ro, Key(0), &v)); - ASSERT_EQ(1007, v.size()); - - // For db2, it is not enabled with secondary cache, so no search in the - // secondary cache - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - ASSERT_OK(db2->Get(ro, Key(5), &v)); - ASSERT_EQ(1007, v.size()); - - // For db2, it is not enabled with secondary cache, so no search in the - // secondary cache - ASSERT_EQ(secondary_cache->num_inserts(), 1u); - ASSERT_EQ(secondary_cache->num_lookups(), 4u); - - fault_fs_->SetFailGetUniqueId(false); - fault_fs_->SetFilesystemActive(true); - delete db1; - delete db2; - ASSERT_OK(DestroyDB(dbname1, options)); - ASSERT_OK(DestroyDB(dbname2, options)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/coverage/coverage_test.sh b/coverage/coverage_test.sh deleted file mode 100755 index d8d750c93..000000000 --- a/coverage/coverage_test.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -# Exit on error. -set -e - -if [ -n "$USE_CLANG" ]; then - echo "Error: Coverage test is supported only for gcc." - exit 1 -fi - -ROOT=".." -# Fetch right version of gcov -if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then - source $ROOT/build_tools/fbcode_config_platform010.sh - GCOV=$GCC_BASE/bin/gcov -else - GCOV=$(which gcov) -fi -echo -e "Using $GCOV" - -COVERAGE_DIR="$PWD/COVERAGE_REPORT" -mkdir -p $COVERAGE_DIR - -# Find all gcno files to generate the coverage report - -PYTHON=${1:-`which python3`} -echo -e "Using $PYTHON" -GCNO_FILES=`find $ROOT -name "*.gcno"` -$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null | - # Parse the raw gcov report to more human readable form. - $PYTHON $ROOT/coverage/parse_gcov_output.py | - # Write the output to both stdout and report file. - tee $COVERAGE_DIR/coverage_report_all.txt && -echo -e "Generated coverage report for all files: $COVERAGE_DIR/coverage_report_all.txt\n" - -# TODO: we also need to get the files of the latest commits. -# Get the most recently committed files. -LATEST_FILES=` - git show --pretty="format:" --name-only HEAD | - grep -v "^$" | - paste -s -d,` -RECENT_REPORT=$COVERAGE_DIR/coverage_report_recent.txt - -echo -e "Recently updated files: $LATEST_FILES\n" > $RECENT_REPORT -$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null | - $PYTHON $ROOT/coverage/parse_gcov_output.py -interested-files $LATEST_FILES | - tee -a $RECENT_REPORT && -echo -e "Generated coverage report for recently updated files: $RECENT_REPORT\n" - -# Unless otherwise specified, we'll not generate html report by default -if [ -z "$HTML" ]; then - exit 0 -fi - -# Generate the html report. If we cannot find lcov in this machine, we'll simply -# skip this step. -echo "Generating the html coverage report..." - -LCOV=$(which lcov || true 2>/dev/null) -if [ -z $LCOV ] -then - echo "Skip: Cannot find lcov to generate the html report." - exit 0 -fi - -LCOV_VERSION=$(lcov -v | grep 1.1 || true) -if [ $LCOV_VERSION ] -then - echo "Not supported lcov version. Expect lcov 1.1." - exit 0 -fi - -(cd $ROOT; lcov --no-external \ - --capture \ - --directory $PWD \ - --gcov-tool $GCOV \ - --output-file $COVERAGE_DIR/coverage.info) - -genhtml $COVERAGE_DIR/coverage.info -o $COVERAGE_DIR - -echo "HTML Coverage report is generated in $COVERAGE_DIR" diff --git a/coverage/parse_gcov_output.py b/coverage/parse_gcov_output.py deleted file mode 100644 index b9788ec81..000000000 --- a/coverage/parse_gcov_output.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -from __future__ import print_function - -import optparse -import re -import sys - -# the gcov report follows certain pattern. Each file will have two lines -# of report, from which we can extract the file name, total lines and coverage -# percentage. -def parse_gcov_report(gcov_input): - per_file_coverage = {} - total_coverage = None - - for line in sys.stdin: - line = line.strip() - - # --First line of the coverage report (with file name in it)? - match_obj = re.match("^File '(.*)'$", line) - if match_obj: - # fetch the file name from the first line of the report. - current_file = match_obj.group(1) - continue - - # -- Second line of the file report (with coverage percentage) - match_obj = re.match("^Lines executed:(.*)% of (.*)", line) - - if match_obj: - coverage = float(match_obj.group(1)) - lines = int(match_obj.group(2)) - - if current_file is not None: - per_file_coverage[current_file] = (coverage, lines) - current_file = None - else: - # If current_file is not set, we reach the last line of report, - # which contains the summarized coverage percentage. - total_coverage = (coverage, lines) - continue - - # If the line's pattern doesn't fall into the above categories. We - # can simply ignore them since they're either empty line or doesn't - # find executable lines of the given file. - current_file = None - - return per_file_coverage, total_coverage - - -def get_option_parser(): - usage = ( - "Parse the gcov output and generate more human-readable code " - + "coverage report." - ) - parser = optparse.OptionParser(usage) - - parser.add_option( - "--interested-files", - "-i", - dest="filenames", - help="Comma separated files names. if specified, we will display " - + "the coverage report only for interested source files. " - + "Otherwise we will display the coverage report for all " - + "source files.", - ) - return parser - - -def display_file_coverage(per_file_coverage, total_coverage): - # To print out auto-adjustable column, we need to know the longest - # length of file names. - max_file_name_length = max(len(fname) for fname in per_file_coverage.keys()) - - # -- Print header - # size of separator is determined by 3 column sizes: - # file name, coverage percentage and lines. - header_template = "%" + str(max_file_name_length) + "s\t%s\t%s" - separator = "-" * (max_file_name_length + 10 + 20) - print( - header_template % ("Filename", "Coverage", "Lines") - ) # noqa: E999 T25377293 Grandfathered in - print(separator) - - # -- Print body - # template for printing coverage report for each file. - record_template = "%" + str(max_file_name_length) + "s\t%5.2f%%\t%10d" - - for fname, coverage_info in per_file_coverage.items(): - coverage, lines = coverage_info - print(record_template % (fname, coverage, lines)) - - # -- Print footer - if total_coverage: - print(separator) - print(record_template % ("Total", total_coverage[0], total_coverage[1])) - - -def report_coverage(): - parser = get_option_parser() - (options, args) = parser.parse_args() - - interested_files = set() - if options.filenames is not None: - interested_files = {f.strip() for f in options.filenames.split(",")} - - # To make things simple, right now we only read gcov report from the input - per_file_coverage, total_coverage = parse_gcov_report(sys.stdin) - - # Check if we need to display coverage info for interested files. - if len(interested_files): - per_file_coverage = dict( - (fname, per_file_coverage[fname]) - for fname in interested_files - if fname in per_file_coverage - ) - # If we only interested in several files, it makes no sense to report - # the total_coverage - total_coverage = None - - if not len(per_file_coverage): - print("Cannot find coverage info for the given files.", file=sys.stderr) - return - display_file_coverage(per_file_coverage, total_coverage) - - -if __name__ == "__main__": - report_coverage() diff --git a/db/column_family_test.cc b/db/column_family_test.cc deleted file mode 100644 index 9c92707d3..000000000 --- a/db/column_family_test.cc +++ /dev/null @@ -1,3382 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "options/options_parser.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/iterator.h" -#include "rocksdb/utilities/object_registry.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding.h" -#include "util/string_util.h" -#include "utilities/fault_injection_env.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -static const int kValueSize = 1000; - -// counts how many operations were performed -class EnvCounter : public SpecialEnv { - public: - explicit EnvCounter(Env* base) - : SpecialEnv(base), num_new_writable_file_(0) {} - int GetNumberOfNewWritableFileCalls() { return num_new_writable_file_; } - Status NewWritableFile(const std::string& f, std::unique_ptr* r, - const EnvOptions& soptions) override { - ++num_new_writable_file_; - return EnvWrapper::NewWritableFile(f, r, soptions); - } - - private: - std::atomic num_new_writable_file_; -}; - -class ColumnFamilyTestBase : public testing::Test { - public: - explicit ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) { - Env* base_env = Env::Default(); - EXPECT_OK( - test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_)); - EXPECT_NE(nullptr, base_env); - env_ = new EnvCounter(base_env); - env_->skip_fsync_ = true; - dbname_ = test::PerThreadDBPath("column_family_test"); - db_options_.create_if_missing = true; - db_options_.fail_if_options_file_error = true; - db_options_.env = env_; - EXPECT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_))); - } - - ~ColumnFamilyTestBase() override { - std::vector column_families; - for (auto h : handles_) { - ColumnFamilyDescriptor cfdescriptor; - Status s = h->GetDescriptor(&cfdescriptor); - EXPECT_OK(s); - column_families.push_back(cfdescriptor); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - Destroy(column_families); - delete env_; - } - - BlockBasedTableOptions GetBlockBasedTableOptions() { - BlockBasedTableOptions options; - options.format_version = format_; - return options; - } - - // Return the value to associate with the specified key - Slice Value(int k, std::string* storage) { - if (k == 0) { - // Ugh. Random seed of 0 used to produce no entropy. This code - // preserves the implementation that was in place when all of the - // magic values in this file were picked. - *storage = std::string(kValueSize, ' '); - } else { - Random r(k); - *storage = r.RandomString(kValueSize); - } - return Slice(*storage); - } - - void Build(int base, int n, int flush_every = 0) { - std::string key_space, value_space; - WriteBatch batch; - - for (int i = 0; i < n; i++) { - if (flush_every != 0 && i != 0 && i % flush_every == 0) { - DBImpl* dbi = static_cast_with_check(db_); - dbi->TEST_FlushMemTable(); - } - - int keyi = base + i; - Slice key(DBTestBase::Key(keyi)); - - batch.Clear(); - batch.Put(handles_[0], key, Value(keyi, &value_space)); - batch.Put(handles_[1], key, Value(keyi, &value_space)); - batch.Put(handles_[2], key, Value(keyi, &value_space)); - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - } - } - - void CheckMissed() { - uint64_t next_expected = 0; - uint64_t missed = 0; - int bad_keys = 0; - int bad_values = 0; - int correct = 0; - std::string value_space; - for (int cf = 0; cf < 3; cf++) { - next_expected = 0; - Iterator* iter = db_->NewIterator(ReadOptions(false, true), handles_[cf]); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - uint64_t key; - Slice in(iter->key()); - in.remove_prefix(3); - if (!ConsumeDecimalNumber(&in, &key) || !in.empty() || - key < next_expected) { - bad_keys++; - continue; - } - missed += (key - next_expected); - next_expected = key + 1; - if (iter->value() != Value(static_cast(key), &value_space)) { - bad_values++; - } else { - correct++; - } - } - delete iter; - } - - ASSERT_EQ(0, bad_keys); - ASSERT_EQ(0, bad_values); - ASSERT_EQ(0, missed); - (void)correct; - } - - void Close() { - for (auto h : handles_) { - if (h) { - ASSERT_OK(db_->DestroyColumnFamilyHandle(h)); - } - } - handles_.clear(); - names_.clear(); - delete db_; - db_ = nullptr; - } - - Status TryOpen(std::vector cf, - std::vector options = {}) { - std::vector column_families; - names_.clear(); - for (size_t i = 0; i < cf.size(); ++i) { - column_families.emplace_back( - cf[i], options.size() == 0 ? column_family_options_ : options[i]); - names_.push_back(cf[i]); - } - return DB::Open(db_options_, dbname_, column_families, &handles_, &db_); - } - - Status OpenReadOnly(std::vector cf, - std::vector options = {}) { - std::vector column_families; - names_.clear(); - for (size_t i = 0; i < cf.size(); ++i) { - column_families.emplace_back( - cf[i], options.size() == 0 ? column_family_options_ : options[i]); - names_.push_back(cf[i]); - } - return DB::OpenForReadOnly(db_options_, dbname_, column_families, &handles_, - &db_); - } - - void AssertOpenReadOnly(std::vector cf, - std::vector options = {}) { - ASSERT_OK(OpenReadOnly(cf, options)); - } - - void Open(std::vector cf, - std::vector options = {}) { - ASSERT_OK(TryOpen(cf, options)); - } - - void Open() { Open({"default"}); } - - DBImpl* dbfull() { return static_cast_with_check(db_); } - - int GetProperty(int cf, std::string property) { - std::string value; - EXPECT_TRUE(dbfull()->GetProperty(handles_[cf], property, &value)); -#ifndef CYGWIN - return std::stoi(value); -#else - return std::strtol(value.c_str(), 0 /* off */, 10 /* base */); -#endif - } - - bool IsDbWriteStopped() { - uint64_t v; - EXPECT_TRUE(dbfull()->GetIntProperty("rocksdb.is-write-stopped", &v)); - return (v == 1); - } - - uint64_t GetDbDelayedWriteRate() { - uint64_t v; - EXPECT_TRUE( - dbfull()->GetIntProperty("rocksdb.actual-delayed-write-rate", &v)); - return v; - } - - void Destroy(const std::vector& column_families = - std::vector()) { - Close(); - ASSERT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_), - column_families)); - } - - void CreateColumnFamilies( - const std::vector& cfs, - const std::vector options = {}) { - int cfi = static_cast(handles_.size()); - handles_.resize(cfi + cfs.size()); - names_.resize(cfi + cfs.size()); - for (size_t i = 0; i < cfs.size(); ++i) { - const auto& current_cf_opt = - options.size() == 0 ? column_family_options_ : options[i]; - ASSERT_OK( - db_->CreateColumnFamily(current_cf_opt, cfs[i], &handles_[cfi])); - names_[cfi] = cfs[i]; - - // Verify the CF options of the returned CF handle. - ColumnFamilyDescriptor desc; - ASSERT_OK(handles_[cfi]->GetDescriptor(&desc)); - // Need to sanitize the default column family options before comparing - // them. - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - ConfigOptions(), desc.options, - SanitizeOptions(dbfull()->immutable_db_options(), current_cf_opt))); - cfi++; - } - } - - void Reopen(const std::vector options = {}) { - std::vector names; - for (auto name : names_) { - if (name != "") { - names.push_back(name); - } - } - Close(); - assert(options.size() == 0 || names.size() == options.size()); - Open(names, options); - } - - void CreateColumnFamiliesAndReopen(const std::vector& cfs) { - CreateColumnFamilies(cfs); - Reopen(); - } - - void DropColumnFamilies(const std::vector& cfs) { - for (auto cf : cfs) { - ASSERT_OK(db_->DropColumnFamily(handles_[cf])); - ASSERT_OK(db_->DestroyColumnFamilyHandle(handles_[cf])); - handles_[cf] = nullptr; - names_[cf] = ""; - } - } - - void PutRandomData(int cf, int num, int key_value_size, bool save = false) { - if (cf >= static_cast(keys_.size())) { - keys_.resize(cf + 1); - } - for (int i = 0; i < num; ++i) { - // 10 bytes for key, rest is value - if (!save) { - ASSERT_OK(Put(cf, test::RandomKey(&rnd_, 11), - rnd_.RandomString(key_value_size - 10))); - } else { - std::string key = test::RandomKey(&rnd_, 11); - keys_[cf].insert(key); - ASSERT_OK(Put(cf, key, rnd_.RandomString(key_value_size - 10))); - } - } - ASSERT_OK(db_->FlushWAL(/*sync=*/false)); - } - - void WaitForFlush(int cf) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf])); - } - - void WaitForCompaction() { ASSERT_OK(dbfull()->TEST_WaitForCompact()); } - - uint64_t MaxTotalInMemoryState() { - return dbfull()->TEST_MaxTotalInMemoryState(); - } - - void AssertMaxTotalInMemoryState(uint64_t value) { - ASSERT_EQ(value, MaxTotalInMemoryState()); - } - - Status Put(int cf, const std::string& key, const std::string& value) { - return db_->Put(WriteOptions(), handles_[cf], Slice(key), Slice(value)); - } - Status Merge(int cf, const std::string& key, const std::string& value) { - return db_->Merge(WriteOptions(), handles_[cf], Slice(key), Slice(value)); - } - Status Flush(int cf) { return db_->Flush(FlushOptions(), handles_[cf]); } - - std::string Get(int cf, const std::string& key) { - ReadOptions options; - options.verify_checksums = true; - std::string result; - Status s = db_->Get(options, handles_[cf], Slice(key), &result); - if (s.IsNotFound()) { - result = "NOT_FOUND"; - } else if (!s.ok()) { - result = s.ToString(); - } - return result; - } - - void CompactAll(int cf) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[cf], nullptr, - nullptr)); - } - - void Compact(int cf, const Slice& start, const Slice& limit) { - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), handles_[cf], &start, &limit)); - } - - int NumTableFilesAtLevel(int level, int cf) { - return GetProperty(cf, - "rocksdb.num-files-at-level" + std::to_string(level)); - } - - // Return spread of files per level - std::string FilesPerLevel(int cf) { - std::string result; - int last_non_zero_offset = 0; - for (int level = 0; level < dbfull()->NumberLevels(handles_[cf]); level++) { - int f = NumTableFilesAtLevel(level, cf); - char buf[100]; - snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f); - result += buf; - if (f > 0) { - last_non_zero_offset = static_cast(result.size()); - } - } - result.resize(last_non_zero_offset); - return result; - } - - void AssertFilesPerLevel(const std::string& value, int cf) { - ASSERT_EQ(value, FilesPerLevel(cf)); - } - - int CountLiveFiles() { - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - return static_cast(metadata.size()); - } - - void AssertCountLiveFiles(int expected_value) { - ASSERT_EQ(expected_value, CountLiveFiles()); - } - - // Do n memtable flushes, each of which produces an sstable - // covering the range [small,large]. - void MakeTables(int cf, int n, const std::string& small, - const std::string& large) { - for (int i = 0; i < n; i++) { - ASSERT_OK(Put(cf, small, "begin")); - ASSERT_OK(Put(cf, large, "end")); - ASSERT_OK(db_->Flush(FlushOptions(), handles_[cf])); - } - } - - int CountLiveLogFiles() { - int micros_wait_for_log_deletion = 20000; - env_->SleepForMicroseconds(micros_wait_for_log_deletion); - int ret = 0; - VectorLogPtr wal_files; - Status s; - // GetSortedWalFiles is a flakey function -- it gets all the wal_dir - // children files and then later checks for their existence. if some of the - // log files doesn't exist anymore, it reports an error. it does all of this - // without DB mutex held, so if a background process deletes the log file - // while the function is being executed, it returns an error. We retry the - // function 10 times to avoid the error failing the test - for (int retries = 0; retries < 10; ++retries) { - wal_files.clear(); - s = db_->GetSortedWalFiles(wal_files); - if (s.ok()) { - break; - } - } - EXPECT_OK(s); - for (const auto& wal : wal_files) { - if (wal->Type() == kAliveLogFile) { - ++ret; - } - } - return ret; - return 0; - } - - void AssertCountLiveLogFiles(int value) { - ASSERT_EQ(value, CountLiveLogFiles()); - } - - void AssertNumberOfImmutableMemtables(std::vector num_per_cf) { - assert(num_per_cf.size() == handles_.size()); - - for (size_t i = 0; i < num_per_cf.size(); ++i) { - ASSERT_EQ(num_per_cf[i], GetProperty(static_cast(i), - "rocksdb.num-immutable-mem-table")); - } - } - - void CopyFile(const std::string& source, const std::string& destination, - uint64_t size = 0) { - const EnvOptions soptions; - std::unique_ptr srcfile; - ASSERT_OK(env_->NewSequentialFile(source, &srcfile, soptions)); - std::unique_ptr destfile; - ASSERT_OK(env_->NewWritableFile(destination, &destfile, soptions)); - - if (size == 0) { - // default argument means copy everything - ASSERT_OK(env_->GetFileSize(source, &size)); - } - - char buffer[4096]; - Slice slice; - while (size > 0) { - uint64_t one = std::min(uint64_t(sizeof(buffer)), size); - ASSERT_OK(srcfile->Read(one, &slice, buffer)); - ASSERT_OK(destfile->Append(slice)); - size -= slice.size(); - } - ASSERT_OK(destfile->Close()); - } - - int GetSstFileCount(std::string path) { - std::vector files; - DBTestBase::GetSstFiles(env_, path, &files); - return static_cast(files.size()); - } - - void RecalculateWriteStallConditions( - ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options) { - // add lock to avoid race condition between - // `RecalculateWriteStallConditions` which writes to CFStats and - // background `DBImpl::DumpStats()` threads which read CFStats - dbfull()->TEST_LockMutex(); - cfd->RecalculateWriteStallConditions(mutable_cf_options); - dbfull()->TEST_UnlockMutex(); - } - - std::vector handles_; - std::vector names_; - std::vector> keys_; - ColumnFamilyOptions column_family_options_; - DBOptions db_options_; - std::string dbname_; - DB* db_ = nullptr; - EnvCounter* env_; - std::shared_ptr env_guard_; - Random rnd_; - uint32_t format_; -}; - -class ColumnFamilyTest - : public ColumnFamilyTestBase, - virtual public ::testing::WithParamInterface { - public: - ColumnFamilyTest() : ColumnFamilyTestBase(GetParam()) {} -}; - -INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest, - testing::Values(test::kDefaultFormatVersion)); -INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest, - testing::Values(kLatestFormatVersion)); - -TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) { - for (int iter = 0; iter < 3; ++iter) { - Open(); - CreateColumnFamilies({"one", "two", "three"}); - for (size_t i = 0; i < handles_.size(); ++i) { - auto cfh = static_cast_with_check(handles_[i]); - ASSERT_EQ(i, cfh->GetID()); - } - if (iter == 1) { - Reopen(); - } - DropColumnFamilies({3}); - Reopen(); - if (iter == 2) { - // this tests if max_column_family is correctly persisted with - // WriteSnapshot() - Reopen(); - } - CreateColumnFamilies({"three2"}); - // ID 3 that was used for dropped column family "three" should not be - // reused - auto cfh3 = static_cast_with_check(handles_[3]); - ASSERT_EQ(4U, cfh3->GetID()); - Close(); - Destroy(); - } -} - -TEST_P(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) { - Open(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WriteOptionsFile:1", - "ColumnFamilyTest.CreateCFRaceWithGetAggProperty:1"}, - {"ColumnFamilyTest.CreateCFRaceWithGetAggProperty:2", - "DBImpl::WriteOptionsFile:2"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread thread( - [&] { CreateColumnFamilies({"one"}); }); - - TEST_SYNC_POINT("ColumnFamilyTest.CreateCFRaceWithGetAggProperty:1"); - uint64_t pv; - db_->GetAggregatedIntProperty(DB::Properties::kEstimateTableReadersMem, &pv); - TEST_SYNC_POINT("ColumnFamilyTest.CreateCFRaceWithGetAggProperty:2"); - - thread.join(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -class FlushEmptyCFTestWithParam - : public ColumnFamilyTestBase, - virtual public testing::WithParamInterface> { - public: - FlushEmptyCFTestWithParam() - : ColumnFamilyTestBase(std::get<0>(GetParam())), - allow_2pc_(std::get<1>(GetParam())) {} - - // Required if inheriting from testing::WithParamInterface<> - static void SetUpTestCase() {} - static void TearDownTestCase() {} - - bool allow_2pc_; -}; - -TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest) { - std::unique_ptr fault_env( - new FaultInjectionTestEnv(env_)); - db_options_.env = fault_env.get(); - db_options_.allow_2pc = allow_2pc_; - Open(); - CreateColumnFamilies({"one", "two"}); - // Generate log file A. - ASSERT_OK(Put(1, "foo", "v1")); // seqID 1 - - Reopen(); - // Log file A is not dropped after reopening because default column family's - // min log number is 0. - // It flushes to SST file X - ASSERT_OK(Put(1, "foo", "v1")); // seqID 2 - ASSERT_OK(Put(1, "bar", "v2")); // seqID 3 - // Current log file is file B now. While flushing, a new log file C is created - // and is set to current. Boths' min log number is set to file C in memory, so - // after flushing file B is deleted. At the same time, the min log number of - // default CF is not written to manifest. Log file A still remains. - // Flushed to SST file Y. - ASSERT_OK(Flush(1)); - ASSERT_OK(Flush(0)); - ASSERT_OK(Put(1, "bar", "v3")); // seqID 4 - ASSERT_OK(Put(1, "foo", "v4")); // seqID 5 - ASSERT_OK(db_->FlushWAL(/*sync=*/false)); - - // Preserve file system state up to here to simulate a crash condition. - fault_env->SetFilesystemActive(false); - std::vector names; - for (auto name : names_) { - if (name != "") { - names.push_back(name); - } - } - - Close(); - fault_env->ResetState(); - - // Before opening, there are four files: - // Log file A contains seqID 1 - // Log file C contains seqID 4, 5 - // SST file X contains seqID 1 - // SST file Y contains seqID 2, 3 - // Min log number: - // default CF: 0 - // CF one, two: C - // When opening the DB, all the seqID should be preserved. - Open(names, {}); - ASSERT_EQ("v4", Get(1, "foo")); - ASSERT_EQ("v3", Get(1, "bar")); - Close(); - - db_options_.env = env_; -} - -TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest2) { - std::unique_ptr fault_env( - new FaultInjectionTestEnv(env_)); - db_options_.env = fault_env.get(); - db_options_.allow_2pc = allow_2pc_; - Open(); - CreateColumnFamilies({"one", "two"}); - // Generate log file A. - ASSERT_OK(Put(1, "foo", "v1")); // seqID 1 - - Reopen(); - // Log file A is not dropped after reopening because default column family's - // min log number is 0. - // It flushes to SST file X - ASSERT_OK(Put(1, "foo", "v1")); // seqID 2 - ASSERT_OK(Put(1, "bar", "v2")); // seqID 3 - // Current log file is file B now. While flushing, a new log file C is created - // and is set to current. Both CFs' min log number is set to file C so after - // flushing file B is deleted. Log file A still remains. - // Flushed to SST file Y. - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(0, "bar", "v2")); // seqID 4 - ASSERT_OK(Put(2, "bar", "v2")); // seqID 5 - ASSERT_OK(Put(1, "bar", "v3")); // seqID 6 - // Flushing all column families. This forces all CFs' min log to current. This - // is written to the manifest file. Log file C is cleared. - ASSERT_OK(Flush(0)); - ASSERT_OK(Flush(1)); - ASSERT_OK(Flush(2)); - // Write to log file D - ASSERT_OK(Put(1, "bar", "v4")); // seqID 7 - ASSERT_OK(Put(1, "bar", "v5")); // seqID 8 - ASSERT_OK(db_->FlushWAL(/*sync=*/false)); - // Preserve file system state up to here to simulate a crash condition. - fault_env->SetFilesystemActive(false); - std::vector names; - for (auto name : names_) { - if (name != "") { - names.push_back(name); - } - } - - Close(); - fault_env->ResetState(); - // Before opening, there are two logfiles: - // Log file A contains seqID 1 - // Log file D contains seqID 7, 8 - // Min log number: - // default CF: D - // CF one, two: D - // When opening the DB, log file D should be replayed using the seqID - // specified in the file. - Open(names, {}); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v5", Get(1, "bar")); - Close(); - - db_options_.env = env_; -} - -INSTANTIATE_TEST_CASE_P( - FormatDef, FlushEmptyCFTestWithParam, - testing::Values(std::make_tuple(test::kDefaultFormatVersion, true), - std::make_tuple(test::kDefaultFormatVersion, false))); -INSTANTIATE_TEST_CASE_P( - FormatLatest, FlushEmptyCFTestWithParam, - testing::Values(std::make_tuple(kLatestFormatVersion, true), - std::make_tuple(kLatestFormatVersion, false))); - -TEST_P(ColumnFamilyTest, AddDrop) { - Open(); - CreateColumnFamilies({"one", "two", "three"}); - ASSERT_EQ("NOT_FOUND", Get(1, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(2, "fodor")); - DropColumnFamilies({2}); - ASSERT_EQ("NOT_FOUND", Get(1, "fodor")); - CreateColumnFamilies({"four"}); - ASSERT_EQ("NOT_FOUND", Get(3, "fodor")); - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_EQ("mirko", Get(1, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(3, "fodor")); - Close(); - ASSERT_TRUE(TryOpen({"default"}).IsInvalidArgument()); - Open({"default", "one", "three", "four"}); - DropColumnFamilies({1}); - Reopen(); - Close(); - - std::vector families; - ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &families)); - std::sort(families.begin(), families.end()); - ASSERT_TRUE(families == - std::vector({"default", "four", "three"})); -} - -TEST_P(ColumnFamilyTest, BulkAddDrop) { - constexpr int kNumCF = 1000; - ColumnFamilyOptions cf_options; - WriteOptions write_options; - Open(); - std::vector cf_names; - std::vector cf_handles; - for (int i = 1; i <= kNumCF; i++) { - cf_names.push_back("cf1-" + std::to_string(i)); - } - ASSERT_OK(db_->CreateColumnFamilies(cf_options, cf_names, &cf_handles)); - for (int i = 1; i <= kNumCF; i++) { - ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar")); - } - ASSERT_OK(db_->DropColumnFamilies(cf_handles)); - std::vector cf_descriptors; - for (auto* handle : cf_handles) { - delete handle; - } - cf_handles.clear(); - for (int i = 1; i <= kNumCF; i++) { - cf_descriptors.emplace_back("cf2-" + std::to_string(i), - ColumnFamilyOptions()); - } - ASSERT_OK(db_->CreateColumnFamilies(cf_descriptors, &cf_handles)); - for (int i = 1; i <= kNumCF; i++) { - ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar")); - } - ASSERT_OK(db_->DropColumnFamilies(cf_handles)); - for (auto* handle : cf_handles) { - delete handle; - } - Close(); - std::vector families; - ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &families)); - std::sort(families.begin(), families.end()); - ASSERT_TRUE(families == std::vector({"default"})); -} - -TEST_P(ColumnFamilyTest, DropTest) { - // first iteration - don't reopen DB before dropping - // second iteration - reopen DB before dropping - for (int iter = 0; iter < 2; ++iter) { - Open({"default"}); - CreateColumnFamiliesAndReopen({"pikachu"}); - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put(1, std::to_string(i), "bar" + std::to_string(i))); - } - ASSERT_OK(Flush(1)); - - if (iter == 1) { - Reopen(); - } - ASSERT_EQ("bar1", Get(1, "1")); - - AssertCountLiveFiles(1); - DropColumnFamilies({1}); - // make sure that all files are deleted when we drop the column family - AssertCountLiveFiles(0); - Destroy(); - } -} - -TEST_P(ColumnFamilyTest, WriteBatchFailure) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two"}); - WriteBatch batch; - ASSERT_OK(batch.Put(handles_[0], Slice("existing"), Slice("column-family"))); - ASSERT_OK( - batch.Put(handles_[1], Slice("non-existing"), Slice("column-family"))); - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - DropColumnFamilies({1}); - WriteOptions woptions_ignore_missing_cf; - woptions_ignore_missing_cf.ignore_missing_column_families = true; - ASSERT_OK( - batch.Put(handles_[0], Slice("still here"), Slice("column-family"))); - ASSERT_OK(db_->Write(woptions_ignore_missing_cf, &batch)); - ASSERT_EQ("column-family", Get(0, "still here")); - Status s = db_->Write(WriteOptions(), &batch); - ASSERT_TRUE(s.IsInvalidArgument()); - Close(); -} - -TEST_P(ColumnFamilyTest, ReadWrite) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two"}); - ASSERT_OK(Put(0, "foo", "v1")); - ASSERT_OK(Put(0, "bar", "v2")); - ASSERT_OK(Put(1, "mirko", "v3")); - ASSERT_OK(Put(0, "foo", "v2")); - ASSERT_OK(Put(2, "fodor", "v5")); - - for (int iter = 0; iter <= 3; ++iter) { - ASSERT_EQ("v2", Get(0, "foo")); - ASSERT_EQ("v2", Get(0, "bar")); - ASSERT_EQ("v3", Get(1, "mirko")); - ASSERT_EQ("v5", Get(2, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(0, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(1, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(2, "foo")); - if (iter <= 1) { - Reopen(); - } - } - Close(); -} - -TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) { - std::string backup_logs = dbname_ + "/backup_logs"; - - // delete old files in backup_logs directory - ASSERT_OK(env_->CreateDirIfMissing(dbname_)); - ASSERT_OK(env_->CreateDirIfMissing(backup_logs)); - std::vector old_files; - ASSERT_OK(env_->GetChildren(backup_logs, &old_files)); - for (auto& file : old_files) { - ASSERT_OK(env_->DeleteFile(backup_logs + "/" + file)); - } - - column_family_options_.merge_operator = - MergeOperators::CreateUInt64AddOperator(); - db_options_.wal_dir = dbname_ + "/logs"; - Destroy(); - Open(); - CreateColumnFamilies({"cf1", "cf2"}); - - // fill up the DB - std::string one, two, three; - PutFixed64(&one, 1); - PutFixed64(&two, 2); - PutFixed64(&three, 3); - ASSERT_OK(Merge(0, "foo", one)); - ASSERT_OK(Merge(1, "mirko", one)); - ASSERT_OK(Merge(0, "foo", one)); - ASSERT_OK(Merge(2, "bla", one)); - ASSERT_OK(Merge(2, "fodor", one)); - ASSERT_OK(Merge(0, "bar", one)); - ASSERT_OK(Merge(2, "bla", one)); - ASSERT_OK(Merge(1, "mirko", two)); - ASSERT_OK(Merge(1, "franjo", one)); - - // copy the logs to backup - std::vector logs; - ASSERT_OK(env_->GetChildren(db_options_.wal_dir, &logs)); - for (auto& log : logs) { - CopyFile(db_options_.wal_dir + "/" + log, backup_logs + "/" + log); - } - - // recover the DB - Close(); - - // 1. check consistency - // 2. copy the logs from backup back to WAL dir. if the recovery happens - // again on the same log files, this should lead to incorrect results - // due to applying merge operator twice - // 3. check consistency - for (int iter = 0; iter < 2; ++iter) { - // assert consistency - Open({"default", "cf1", "cf2"}); - ASSERT_EQ(two, Get(0, "foo")); - ASSERT_EQ(one, Get(0, "bar")); - ASSERT_EQ(three, Get(1, "mirko")); - ASSERT_EQ(one, Get(1, "franjo")); - ASSERT_EQ(one, Get(2, "fodor")); - ASSERT_EQ(two, Get(2, "bla")); - Close(); - - if (iter == 0) { - // copy the logs from backup back to wal dir - for (auto& log : logs) { - CopyFile(backup_logs + "/" + log, db_options_.wal_dir + "/" + log); - } - } - } -} - -TEST_P(ColumnFamilyTest, FlushTest) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two"}); - ASSERT_OK(Put(0, "foo", "v1")); - ASSERT_OK(Put(0, "bar", "v2")); - ASSERT_OK(Put(1, "mirko", "v3")); - ASSERT_OK(Put(0, "foo", "v2")); - ASSERT_OK(Put(2, "fodor", "v5")); - - for (int j = 0; j < 2; j++) { - ReadOptions ro; - std::vector iterators; - // Hold super version. - if (j == 0) { - ASSERT_OK(db_->NewIterators(ro, handles_, &iterators)); - } - - for (int i = 0; i < 3; ++i) { - uint64_t max_total_in_memory_state = MaxTotalInMemoryState(); - ASSERT_OK(Flush(i)); - AssertMaxTotalInMemoryState(max_total_in_memory_state); - } - ASSERT_OK(Put(1, "foofoo", "bar")); - ASSERT_OK(Put(0, "foofoo", "bar")); - - for (auto* it : iterators) { - ASSERT_OK(it->status()); - delete it; - } - } - Reopen(); - - for (int iter = 0; iter <= 2; ++iter) { - ASSERT_EQ("v2", Get(0, "foo")); - ASSERT_EQ("v2", Get(0, "bar")); - ASSERT_EQ("v3", Get(1, "mirko")); - ASSERT_EQ("v5", Get(2, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(0, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(1, "fodor")); - ASSERT_EQ("NOT_FOUND", Get(2, "foo")); - if (iter <= 1) { - Reopen(); - } - } - Close(); -} - -// Makes sure that obsolete log files get deleted -TEST_P(ColumnFamilyTest, LogDeletionTest) { - db_options_.max_total_wal_size = std::numeric_limits::max(); - column_family_options_.arena_block_size = 4 * 1024; - column_family_options_.write_buffer_size = 128000; // 128KB - Open(); - CreateColumnFamilies({"one", "two", "three", "four"}); - // Each bracket is one log file. if number is in (), it means - // we don't need it anymore (it's been flushed) - // [] - AssertCountLiveLogFiles(0); - PutRandomData(0, 1, 128); - // [0] - PutRandomData(1, 1, 128); - // [0, 1] - PutRandomData(1, 1000, 128); - WaitForFlush(1); - // [0, (1)] [1] - AssertCountLiveLogFiles(2); - PutRandomData(0, 1, 128); - // [0, (1)] [0, 1] - AssertCountLiveLogFiles(2); - PutRandomData(2, 1, 128); - // [0, (1)] [0, 1, 2] - PutRandomData(2, 1000, 128); - WaitForFlush(2); - // [0, (1)] [0, 1, (2)] [2] - AssertCountLiveLogFiles(3); - PutRandomData(2, 1000, 128); - WaitForFlush(2); - // [0, (1)] [0, 1, (2)] [(2)] [2] - AssertCountLiveLogFiles(4); - PutRandomData(3, 1, 128); - // [0, (1)] [0, 1, (2)] [(2)] [2, 3] - PutRandomData(1, 1, 128); - // [0, (1)] [0, 1, (2)] [(2)] [1, 2, 3] - AssertCountLiveLogFiles(4); - PutRandomData(1, 1000, 128); - WaitForFlush(1); - // [0, (1)] [0, (1), (2)] [(2)] [(1), 2, 3] [1] - AssertCountLiveLogFiles(5); - PutRandomData(0, 1000, 128); - WaitForFlush(0); - // [(0), (1)] [(0), (1), (2)] [(2)] [(1), 2, 3] [1, (0)] [0] - // delete obsolete logs --> - // [(1), 2, 3] [1, (0)] [0] - AssertCountLiveLogFiles(3); - PutRandomData(0, 1000, 128); - WaitForFlush(0); - // [(1), 2, 3] [1, (0)], [(0)] [0] - AssertCountLiveLogFiles(4); - PutRandomData(1, 1000, 128); - WaitForFlush(1); - // [(1), 2, 3] [(1), (0)] [(0)] [0, (1)] [1] - AssertCountLiveLogFiles(5); - PutRandomData(2, 1000, 128); - WaitForFlush(2); - // [(1), (2), 3] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2] - AssertCountLiveLogFiles(6); - PutRandomData(3, 1000, 128); - WaitForFlush(3); - // [(1), (2), (3)] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2, (3)] [3] - // delete obsolete logs --> - // [0, (1)] [1, (2)], [2, (3)] [3] - AssertCountLiveLogFiles(4); - Close(); -} - -TEST_P(ColumnFamilyTest, CrashAfterFlush) { - std::unique_ptr fault_env( - new FaultInjectionTestEnv(env_)); - db_options_.env = fault_env.get(); - Open(); - CreateColumnFamilies({"one"}); - - WriteBatch batch; - ASSERT_OK(batch.Put(handles_[0], Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Put(handles_[1], Slice("foo"), Slice("bar"))); - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush(0)); - fault_env->SetFilesystemActive(false); - - std::vector names; - for (auto name : names_) { - if (name != "") { - names.push_back(name); - } - } - Close(); - ASSERT_OK(fault_env->DropUnsyncedFileData()); - fault_env->ResetState(); - Open(names, {}); - - // Write batch should be atomic. - ASSERT_EQ(Get(0, "foo"), Get(1, "foo")); - - Close(); - db_options_.env = env_; -} - -TEST_P(ColumnFamilyTest, OpenNonexistentColumnFamily) { - ASSERT_OK(TryOpen({"default"})); - Close(); - ASSERT_TRUE(TryOpen({"default", "dne"}).IsInvalidArgument()); -} - -// Makes sure that obsolete log files get deleted -TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) { - // disable flushing stale column families - db_options_.max_total_wal_size = std::numeric_limits::max(); - Open(); - CreateColumnFamilies({"one", "two", "three"}); - ColumnFamilyOptions default_cf, one, two, three; - // setup options. all column families have max_write_buffer_number setup to 10 - // "default" -> 100KB memtable, start flushing immediately - // "one" -> 200KB memtable, start flushing with two immutable memtables - // "two" -> 1MB memtable, start flushing with three immutable memtables - // "three" -> 90KB memtable, start flushing with four immutable memtables - default_cf.write_buffer_size = 100000; - default_cf.arena_block_size = 4 * 4096; - default_cf.max_write_buffer_number = 10; - default_cf.min_write_buffer_number_to_merge = 1; - default_cf.max_write_buffer_size_to_maintain = 0; - one.write_buffer_size = 200000; - one.arena_block_size = 4 * 4096; - one.max_write_buffer_number = 10; - one.min_write_buffer_number_to_merge = 2; - one.max_write_buffer_size_to_maintain = - static_cast(one.write_buffer_size); - two.write_buffer_size = 1000000; - two.arena_block_size = 4 * 4096; - two.max_write_buffer_number = 10; - two.min_write_buffer_number_to_merge = 3; - two.max_write_buffer_size_to_maintain = - static_cast(two.write_buffer_size); - three.write_buffer_size = 4096 * 22; - three.arena_block_size = 4096; - three.max_write_buffer_number = 10; - three.min_write_buffer_number_to_merge = 4; - three.max_write_buffer_size_to_maintain = - static_cast(three.write_buffer_size); - - Reopen({default_cf, one, two, three}); - - int micros_wait_for_flush = 10000; - PutRandomData(0, 100, 1000); - WaitForFlush(0); - AssertNumberOfImmutableMemtables({0, 0, 0, 0}); - AssertCountLiveLogFiles(1); - PutRandomData(1, 200, 1000); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 0, 0}); - AssertCountLiveLogFiles(2); - PutRandomData(2, 1000, 1000); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 1, 0}); - AssertCountLiveLogFiles(3); - PutRandomData(2, 1000, 1000); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 2, 0}); - AssertCountLiveLogFiles(4); - PutRandomData(3, 93, 990); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 2, 1}); - AssertCountLiveLogFiles(5); - PutRandomData(3, 88, 990); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 2, 2}); - AssertCountLiveLogFiles(6); - PutRandomData(3, 88, 990); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 2, 3}); - AssertCountLiveLogFiles(7); - PutRandomData(0, 100, 1000); - WaitForFlush(0); - AssertNumberOfImmutableMemtables({0, 1, 2, 3}); - AssertCountLiveLogFiles(8); - PutRandomData(2, 100, 10000); - WaitForFlush(2); - AssertNumberOfImmutableMemtables({0, 1, 0, 3}); - AssertCountLiveLogFiles(9); - PutRandomData(3, 88, 990); - WaitForFlush(3); - AssertNumberOfImmutableMemtables({0, 1, 0, 0}); - AssertCountLiveLogFiles(10); - PutRandomData(3, 88, 990); - env_->SleepForMicroseconds(micros_wait_for_flush); - AssertNumberOfImmutableMemtables({0, 1, 0, 1}); - AssertCountLiveLogFiles(11); - PutRandomData(1, 200, 1000); - WaitForFlush(1); - AssertNumberOfImmutableMemtables({0, 0, 0, 1}); - AssertCountLiveLogFiles(5); - PutRandomData(3, 88 * 3, 990); - WaitForFlush(3); - PutRandomData(3, 88 * 4, 990); - WaitForFlush(3); - AssertNumberOfImmutableMemtables({0, 0, 0, 0}); - AssertCountLiveLogFiles(12); - PutRandomData(0, 100, 1000); - WaitForFlush(0); - AssertNumberOfImmutableMemtables({0, 0, 0, 0}); - AssertCountLiveLogFiles(12); - PutRandomData(2, 3 * 1000, 1000); - WaitForFlush(2); - AssertNumberOfImmutableMemtables({0, 0, 0, 0}); - AssertCountLiveLogFiles(12); - PutRandomData(1, 2 * 200, 1000); - WaitForFlush(1); - AssertNumberOfImmutableMemtables({0, 0, 0, 0}); - AssertCountLiveLogFiles(7); - Close(); -} - -// The test is commented out because we want to test that snapshot is -// not created for memtables not supported it, but There isn't a memtable -// that doesn't support snapshot right now. If we have one later, we can -// re-enable the test. -// -// TEST_P(ColumnFamilyTest, MemtableNotSupportSnapshot) { -// db_options_.allow_concurrent_memtable_write = false; -// Open(); -// auto* s1 = dbfull()->GetSnapshot(); -// ASSERT_TRUE(s1 != nullptr); -// dbfull()->ReleaseSnapshot(s1); - -// // Add a column family that doesn't support snapshot -// ColumnFamilyOptions first; -// first.memtable_factory.reset(new DummyMemtableNotSupportingSnapshot()); -// CreateColumnFamilies({"first"}, {first}); -// auto* s2 = dbfull()->GetSnapshot(); -// ASSERT_TRUE(s2 == nullptr); - -// // Add a column family that supports snapshot. Snapshot stays not -// supported. ColumnFamilyOptions second; CreateColumnFamilies({"second"}, -// {second}); auto* s3 = dbfull()->GetSnapshot(); ASSERT_TRUE(s3 == nullptr); -// Close(); -// } - -class TestComparator : public Comparator { - int Compare(const ROCKSDB_NAMESPACE::Slice& /*a*/, - const ROCKSDB_NAMESPACE::Slice& /*b*/) const override { - return 0; - } - const char* Name() const override { return "Test"; } - void FindShortestSeparator( - std::string* /*start*/, - const ROCKSDB_NAMESPACE::Slice& /*limit*/) const override {} - void FindShortSuccessor(std::string* /*key*/) const override {} -}; - -static TestComparator third_comparator; -static TestComparator fourth_comparator; - -// Test that we can retrieve the comparator from a created CF -TEST_P(ColumnFamilyTest, GetComparator) { - Open(); - // Add a column family with no comparator specified - CreateColumnFamilies({"first"}); - const Comparator* comp = handles_[0]->GetComparator(); - ASSERT_EQ(comp, BytewiseComparator()); - - // Add three column families - one with no comparator and two - // with comparators specified - ColumnFamilyOptions second, third, fourth; - second.comparator = &third_comparator; - third.comparator = &fourth_comparator; - CreateColumnFamilies({"second", "third", "fourth"}, {second, third, fourth}); - ASSERT_EQ(handles_[1]->GetComparator(), BytewiseComparator()); - ASSERT_EQ(handles_[2]->GetComparator(), &third_comparator); - ASSERT_EQ(handles_[3]->GetComparator(), &fourth_comparator); - Close(); -} - -TEST_P(ColumnFamilyTest, DifferentMergeOperators) { - Open(); - CreateColumnFamilies({"first", "second"}); - ColumnFamilyOptions default_cf, first, second; - first.merge_operator = MergeOperators::CreateUInt64AddOperator(); - second.merge_operator = MergeOperators::CreateStringAppendOperator(); - Reopen({default_cf, first, second}); - - std::string one, two, three; - PutFixed64(&one, 1); - PutFixed64(&two, 2); - PutFixed64(&three, 3); - - ASSERT_OK(Put(0, "foo", two)); - ASSERT_OK(Put(0, "foo", one)); - ASSERT_TRUE(Merge(0, "foo", two).IsNotSupported()); - ASSERT_EQ(Get(0, "foo"), one); - - ASSERT_OK(Put(1, "foo", two)); - ASSERT_OK(Put(1, "foo", one)); - ASSERT_OK(Merge(1, "foo", two)); - ASSERT_EQ(Get(1, "foo"), three); - - ASSERT_OK(Put(2, "foo", two)); - ASSERT_OK(Put(2, "foo", one)); - ASSERT_OK(Merge(2, "foo", two)); - ASSERT_EQ(Get(2, "foo"), one + "," + two); - Close(); -} - -TEST_P(ColumnFamilyTest, DifferentCompactionStyles) { - Open(); - CreateColumnFamilies({"one", "two"}); - ColumnFamilyOptions default_cf, one, two; - db_options_.max_open_files = 20; // only 10 files in file cache - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = static_cast(1) << 60; - - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - two.compaction_style = kCompactionStyleLevel; - two.num_levels = 4; - two.level0_file_num_compaction_trigger = 3; - two.write_buffer_size = 100000; - - Reopen({default_cf, one, two}); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 1; ++i) { - PutRandomData(1, 10, 12000); - PutRandomData(1, 1, 10); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - - // SETUP column family "two" -- level style with 4 levels - for (int i = 0; i < two.level0_file_num_compaction_trigger - 1; ++i) { - PutRandomData(2, 10, 12000); - PutRandomData(2, 1, 10); - WaitForFlush(2); - AssertFilesPerLevel(std::to_string(i + 1), 2); - } - - // TRIGGER compaction "one" - PutRandomData(1, 10, 12000); - PutRandomData(1, 1, 10); - - // TRIGGER compaction "two" - PutRandomData(2, 10, 12000); - PutRandomData(2, 1, 10); - - // WAIT for compactions - WaitForCompaction(); - - // VERIFY compaction "one" - AssertFilesPerLevel("1", 1); - - // VERIFY compaction "two" - AssertFilesPerLevel("0,1", 2); - CompactAll(2); - AssertFilesPerLevel("0,1", 2); - - Close(); -} - -// Sync points not supported in RocksDB Lite - -TEST_P(ColumnFamilyTest, MultipleManualCompactions) { - Open(); - CreateColumnFamilies({"one", "two"}); - ColumnFamilyOptions default_cf, one, two; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - two.compaction_style = kCompactionStyleLevel; - two.num_levels = 4; - two.level0_file_num_compaction_trigger = 3; - two.write_buffer_size = 100000; - - Reopen({default_cf, one, two}); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - std::atomic_bool cf_1_1{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::MultiManual:4", "ColumnFamilyTest::MultiManual:1"}, - {"ColumnFamilyTest::MultiManual:2", "ColumnFamilyTest::MultiManual:5"}, - {"ColumnFamilyTest::MultiManual:2", "ColumnFamilyTest::MultiManual:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:4"); - TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:3"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - std::vector threads; - threads.emplace_back([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - }); - - // SETUP column family "two" -- level style with 4 levels - for (int i = 0; i < two.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(2, 10, 12000); - PutRandomData(2, 1, 10); - WaitForFlush(2); - AssertFilesPerLevel(std::to_string(i + 1), 2); - } - threads.emplace_back([&] { - TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:1"); - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[2], nullptr, nullptr)); - TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:2"); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:5"); - for (auto& t : threads) { - t.join(); - } - - // VERIFY compaction "one" - AssertFilesPerLevel("1", 1); - - // VERIFY compaction "two" - AssertFilesPerLevel("0,1", 2); - CompactAll(2); - AssertFilesPerLevel("0,1", 2); - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) { - Open(); - CreateColumnFamilies({"one", "two"}); - ColumnFamilyOptions default_cf, one, two; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - ; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - two.compaction_style = kCompactionStyleLevel; - two.num_levels = 4; - two.level0_file_num_compaction_trigger = 3; - two.write_buffer_size = 100000; - - Reopen({default_cf, one, two}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - std::atomic_bool cf_1_1{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::AutoManual:4", "ColumnFamilyTest::AutoManual:1"}, - {"ColumnFamilyTest::AutoManual:2", "ColumnFamilyTest::AutoManual:5"}, - {"ColumnFamilyTest::AutoManual:2", "ColumnFamilyTest::AutoManual:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4"); - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:3"); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:1"); - - // SETUP column family "two" -- level style with 4 levels - for (int i = 0; i < two.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(2, 10, 12000); - PutRandomData(2, 1, 10); - WaitForFlush(2); - AssertFilesPerLevel(std::to_string(i + 1), 2); - } - ROCKSDB_NAMESPACE::port::Thread threads([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[2], nullptr, nullptr)); - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:2"); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:5"); - threads.join(); - - // WAIT for compactions - WaitForCompaction(); - - // VERIFY compaction "one" - AssertFilesPerLevel("1", 1); - - // VERIFY compaction "two" - AssertFilesPerLevel("0,1", 2); - CompactAll(2); - AssertFilesPerLevel("0,1", 2); - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) { - Open(); - CreateColumnFamilies({"one", "two"}); - ColumnFamilyOptions default_cf, one, two; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - ; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - two.compaction_style = kCompactionStyleLevel; - two.num_levels = 4; - two.level0_file_num_compaction_trigger = 3; - two.write_buffer_size = 100000; - - Reopen({default_cf, one, two}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - std::atomic_bool cf_1_1{true}; - std::atomic_bool cf_1_2{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:1"}, - {"ColumnFamilyTest::ManualAuto:5", "ColumnFamilyTest::ManualAuto:2"}, - {"ColumnFamilyTest::ManualAuto:2", "ColumnFamilyTest::ManualAuto:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4"); - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3"); - } else if (cf_1_2.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ROCKSDB_NAMESPACE::port::Thread threads([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:1"); - - // SETUP column family "two" -- level style with 4 levels - for (int i = 0; i < two.level0_file_num_compaction_trigger; ++i) { - PutRandomData(2, 10, 12000); - PutRandomData(2, 1, 10); - WaitForFlush(2); - AssertFilesPerLevel(std::to_string(i + 1), 2); - } - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:5"); - threads.join(); - - // WAIT for compactions - WaitForCompaction(); - - // VERIFY compaction "one" - AssertFilesPerLevel("1", 1); - - // VERIFY compaction "two" - AssertFilesPerLevel("0,1", 2); - CompactAll(2); - AssertFilesPerLevel("0,1", 2); - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) { - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyOptions default_cf, one; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - ; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - Reopen({default_cf, one}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - std::atomic_bool cf_1_1{true}; - std::atomic_bool cf_1_2{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::ManualManual:4", "ColumnFamilyTest::ManualManual:2"}, - {"ColumnFamilyTest::ManualManual:4", "ColumnFamilyTest::ManualManual:5"}, - {"ColumnFamilyTest::ManualManual:1", "ColumnFamilyTest::ManualManual:2"}, - {"ColumnFamilyTest::ManualManual:1", - "ColumnFamilyTest::ManualManual:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:4"); - TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:3"); - } else if (cf_1_2.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:2"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ROCKSDB_NAMESPACE::port::Thread threads([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = true; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:5"); - - WaitForFlush(1); - - // Add more L0 files and force another manual compaction - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel( - std::to_string(one.level0_file_num_compaction_trigger + i), 1); - } - - ROCKSDB_NAMESPACE::port::Thread threads1([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:1"); - - threads.join(); - threads1.join(); - WaitForCompaction(); - // VERIFY compaction "one" - ASSERT_LE(NumTableFilesAtLevel(0, 1), 2); - - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) { - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyOptions default_cf, one; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - ; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - Reopen({default_cf, one}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - std::atomic_bool cf_1_1{true}; - std::atomic_bool cf_1_2{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:2"}, - {"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:5"}, - {"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:2"}, - {"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4"); - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3"); - } else if (cf_1_2.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ROCKSDB_NAMESPACE::port::Thread threads([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:5"); - - WaitForFlush(1); - - // Add more L0 files and force automatic compaction - for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel( - std::to_string(one.level0_file_num_compaction_trigger + i), 1); - } - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:1"); - - threads.join(); - WaitForCompaction(); - // VERIFY compaction "one" - ASSERT_LE(NumTableFilesAtLevel(0, 1), 2); - - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) { - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyOptions default_cf, one; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - ; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleLevel; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 3; - one.write_buffer_size = 120000; - - Reopen({default_cf, one}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - // SETUP column family "one" -- level style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - std::atomic_bool cf_1_1{true}; - std::atomic_bool cf_1_2{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:2"}, - {"ColumnFamilyTest::ManualAuto:4", "ColumnFamilyTest::ManualAuto:5"}, - {"ColumnFamilyTest::ManualAuto:3", "ColumnFamilyTest::ManualAuto:2"}, - {"LevelCompactionPicker::PickCompactionBySize:0", - "ColumnFamilyTest::ManualAuto:3"}, - {"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4"); - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:3"); - } else if (cf_1_2.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:2"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ROCKSDB_NAMESPACE::port::Thread threads([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:5"); - - // Add more L0 files and force automatic compaction - for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel( - std::to_string(one.level0_file_num_compaction_trigger + i), 1); - } - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:1"); - - threads.join(); - WaitForCompaction(); - // VERIFY compaction "one" - AssertFilesPerLevel("0,1", 1); - - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -// In this test, we generate enough files to trigger automatic compactions. -// The automatic compaction waits in NonTrivial:AfterRun -// We generate more files and then trigger an automatic compaction -// This will wait because the automatic compaction has files it needs. -// Once the conflict is hit, the automatic compaction starts and ends -// Then the manual will run and end. -TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) { - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyOptions default_cf, one; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - ; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - Reopen({default_cf, one}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - std::atomic_bool cf_1_1{true}; - std::atomic_bool cf_1_2{true}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyTest::AutoManual:4", "ColumnFamilyTest::AutoManual:2"}, - {"ColumnFamilyTest::AutoManual:4", "ColumnFamilyTest::AutoManual:5"}, - {"CompactionPicker::CompactRange:Conflict", - "ColumnFamilyTest::AutoManual:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (cf_1_1.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4"); - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:3"); - } else if (cf_1_2.exchange(false)) { - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:2"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(std::to_string(i + 1), 1); - } - - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:5"); - - // Add another L0 file and force automatic compaction - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - } - - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - - TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:1"); - - WaitForCompaction(); - // VERIFY compaction "one" - AssertFilesPerLevel("1", 1); - // Compare against saved keys - std::set::iterator key_iter = keys_[1].begin(); - while (key_iter != keys_[1].end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -namespace { -std::string IterStatus(Iterator* iter) { - std::string result; - if (iter->Valid()) { - result = iter->key().ToString() + "->" + iter->value().ToString(); - } else { - EXPECT_OK(iter->status()); - result = "(invalid)"; - } - return result; -} -} // anonymous namespace - -TEST_P(ColumnFamilyTest, NewIteratorsTest) { - // iter == 0 -- no tailing - // iter == 2 -- tailing - for (int iter = 0; iter < 2; ++iter) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two"}); - ASSERT_OK(Put(0, "a", "b")); - ASSERT_OK(Put(1, "b", "a")); - ASSERT_OK(Put(2, "c", "m")); - ASSERT_OK(Put(2, "v", "t")); - std::vector iterators; - ReadOptions options; - options.tailing = (iter == 1); - ASSERT_OK(db_->NewIterators(options, handles_, &iterators)); - - for (auto it : iterators) { - it->SeekToFirst(); - } - ASSERT_EQ(IterStatus(iterators[0]), "a->b"); - ASSERT_EQ(IterStatus(iterators[1]), "b->a"); - ASSERT_EQ(IterStatus(iterators[2]), "c->m"); - - ASSERT_OK(Put(1, "x", "x")); - - for (auto it : iterators) { - it->Next(); - } - - ASSERT_EQ(IterStatus(iterators[0]), "(invalid)"); - if (iter == 0) { - // no tailing - ASSERT_EQ(IterStatus(iterators[1]), "(invalid)"); - } else { - // tailing - ASSERT_EQ(IterStatus(iterators[1]), "x->x"); - } - ASSERT_EQ(IterStatus(iterators[2]), "v->t"); - - for (auto it : iterators) { - delete it; - } - Destroy(); - } -} - -TEST_P(ColumnFamilyTest, ReadOnlyDBTest) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two", "three", "four"}); - ASSERT_OK(Put(0, "a", "b")); - ASSERT_OK(Put(1, "foo", "bla")); - ASSERT_OK(Put(2, "foo", "blabla")); - ASSERT_OK(Put(3, "foo", "blablabla")); - ASSERT_OK(Put(4, "foo", "blablablabla")); - - DropColumnFamilies({2}); - Close(); - // open only a subset of column families - AssertOpenReadOnly({"default", "one", "four"}); - ASSERT_EQ("NOT_FOUND", Get(0, "foo")); - ASSERT_EQ("bla", Get(1, "foo")); - ASSERT_EQ("blablablabla", Get(2, "foo")); - - // test newiterators - { - std::vector iterators; - ASSERT_OK(db_->NewIterators(ReadOptions(), handles_, &iterators)); - for (auto it : iterators) { - it->SeekToFirst(); - } - ASSERT_EQ(IterStatus(iterators[0]), "a->b"); - ASSERT_EQ(IterStatus(iterators[1]), "foo->bla"); - ASSERT_EQ(IterStatus(iterators[2]), "foo->blablablabla"); - for (auto it : iterators) { - it->Next(); - } - ASSERT_EQ(IterStatus(iterators[0]), "(invalid)"); - ASSERT_EQ(IterStatus(iterators[1]), "(invalid)"); - ASSERT_EQ(IterStatus(iterators[2]), "(invalid)"); - - for (auto it : iterators) { - delete it; - } - } - - Close(); - // can't open dropped column family - Status s = OpenReadOnly({"default", "one", "two"}); - ASSERT_TRUE(!s.ok()); - - // Can't open without specifying default column family - s = OpenReadOnly({"one", "four"}); - ASSERT_TRUE(!s.ok()); -} - -TEST_P(ColumnFamilyTest, DontRollEmptyLogs) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two", "three", "four"}); - - for (size_t i = 0; i < handles_.size(); ++i) { - PutRandomData(static_cast(i), 10, 100); - } - int num_writable_file_start = env_->GetNumberOfNewWritableFileCalls(); - // this will trigger the flushes - for (int i = 0; i <= 4; ++i) { - ASSERT_OK(Flush(i)); - } - - for (int i = 0; i < 4; ++i) { - WaitForFlush(i); - } - int total_new_writable_files = - env_->GetNumberOfNewWritableFileCalls() - num_writable_file_start; - ASSERT_EQ(static_cast(total_new_writable_files), handles_.size() + 1); - Close(); -} - -TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) { - Open(); - CreateColumnFamilies({"one", "two"}); - ColumnFamilyOptions default_cf, one, two; - default_cf.write_buffer_size = 100000; // small write buffer size - default_cf.arena_block_size = 4096; - default_cf.disable_auto_compactions = true; - one.disable_auto_compactions = true; - two.disable_auto_compactions = true; - db_options_.max_total_wal_size = 210000; - - Reopen({default_cf, one, two}); - - PutRandomData(2, 1, 10); // 10 bytes - for (int i = 0; i < 2; ++i) { - PutRandomData(0, 100, 1000); // flush - WaitForFlush(0); - - AssertCountLiveFiles(i + 1); - } - // third flush. now, CF [two] should be detected as stale and flushed - // column family 1 should not be flushed since it's empty - PutRandomData(0, 100, 1000); // flush - WaitForFlush(0); - WaitForFlush(2); - // at least 3 files for default column families, 1 file for column family - // [two], zero files for column family [one], because it's empty - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_GE(metadata.size(), 4); - bool has_cf1_sst = false; - bool has_cf2_sst = false; - for (const auto& file : metadata) { - if (file.column_family_name == "one") { - has_cf1_sst = true; - } else if (file.column_family_name == "two") { - has_cf2_sst = true; - } - } - ASSERT_FALSE(has_cf1_sst); - ASSERT_TRUE(has_cf2_sst); - - ASSERT_OK(Flush(0)); - ASSERT_EQ(0, dbfull()->TEST_total_log_size()); - Close(); -} - -TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) { - Status s = TryOpen({"one", "two"}); - ASSERT_TRUE(!s.ok()); - db_options_.create_missing_column_families = true; - s = TryOpen({"default", "one", "two"}); - ASSERT_TRUE(s.ok()); - Close(); -} - -TEST_P(ColumnFamilyTest, SanitizeOptions) { - DBOptions db_options; - for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) { - for (int l = 0; l <= 2; l++) { - for (int i = 1; i <= 3; i++) { - for (int j = 1; j <= 3; j++) { - for (int k = 1; k <= 3; k++) { - ColumnFamilyOptions original; - original.compaction_style = static_cast(s); - original.num_levels = l; - original.level0_stop_writes_trigger = i; - original.level0_slowdown_writes_trigger = j; - original.level0_file_num_compaction_trigger = k; - original.write_buffer_size = - l * 4 * 1024 * 1024 + i * 1024 * 1024 + j * 1024 + k; - - ColumnFamilyOptions result = - SanitizeOptions(ImmutableDBOptions(db_options), original); - ASSERT_TRUE(result.level0_stop_writes_trigger >= - result.level0_slowdown_writes_trigger); - ASSERT_TRUE(result.level0_slowdown_writes_trigger >= - result.level0_file_num_compaction_trigger); - ASSERT_TRUE(result.level0_file_num_compaction_trigger == - original.level0_file_num_compaction_trigger); - if (s == kCompactionStyleLevel) { - ASSERT_GE(result.num_levels, 2); - } else { - ASSERT_GE(result.num_levels, 1); - if (original.num_levels >= 1) { - ASSERT_EQ(result.num_levels, original.num_levels); - } - } - - // Make sure Sanitize options sets arena_block_size to 1/8 of - // the write_buffer_size, rounded up to a multiple of 4k. - size_t expected_arena_block_size = - l * 4 * 1024 * 1024 / 8 + i * 1024 * 1024 / 8; - if (j + k != 0) { - // not a multiple of 4k, round up 4k - expected_arena_block_size += 4 * 1024; - } - expected_arena_block_size = - std::min(size_t{1024 * 1024}, expected_arena_block_size); - ASSERT_EQ(expected_arena_block_size, result.arena_block_size); - } - } - } - } - } -} - -TEST_P(ColumnFamilyTest, ReadDroppedColumnFamily) { - // iter 0 -- drop CF, don't reopen - // iter 1 -- delete CF, reopen - for (int iter = 0; iter < 2; ++iter) { - db_options_.create_missing_column_families = true; - db_options_.max_open_files = 20; - // delete obsolete files always - db_options_.delete_obsolete_files_period_micros = 0; - Open({"default", "one", "two"}); - ColumnFamilyOptions options; - options.level0_file_num_compaction_trigger = 100; - options.level0_slowdown_writes_trigger = 200; - options.level0_stop_writes_trigger = 200; - options.write_buffer_size = 100000; // small write buffer size - Reopen({options, options, options}); - - // 1MB should create ~10 files for each CF - int kKeysNum = 10000; - PutRandomData(0, kKeysNum, 100); - PutRandomData(1, kKeysNum, 100); - PutRandomData(2, kKeysNum, 100); - - { - std::unique_ptr iterator( - db_->NewIterator(ReadOptions(), handles_[2])); - iterator->SeekToFirst(); - - if (iter == 0) { - // Drop CF two - ASSERT_OK(db_->DropColumnFamily(handles_[2])); - } else { - // delete CF two - ASSERT_OK(db_->DestroyColumnFamilyHandle(handles_[2])); - handles_[2] = nullptr; - } - // Make sure iterator created can still be used. - int count = 0; - for (; iterator->Valid(); iterator->Next()) { - ASSERT_OK(iterator->status()); - ++count; - } - ASSERT_OK(iterator->status()); - ASSERT_EQ(count, kKeysNum); - } - - // Add bunch more data to other CFs - PutRandomData(0, kKeysNum, 100); - PutRandomData(1, kKeysNum, 100); - - if (iter == 1) { - Reopen(); - } - - // Since we didn't delete CF handle, RocksDB's contract guarantees that - // we're still able to read dropped CF - for (int i = 0; i < 3; ++i) { - std::unique_ptr iterator( - db_->NewIterator(ReadOptions(), handles_[i])); - int count = 0; - for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { - ASSERT_OK(iterator->status()); - ++count; - } - ASSERT_OK(iterator->status()); - ASSERT_EQ(count, kKeysNum * ((i == 2) ? 1 : 2)); - } - - Close(); - Destroy(); - } -} - -TEST_P(ColumnFamilyTest, LiveIteratorWithDroppedColumnFamily) { - db_options_.create_missing_column_families = true; - db_options_.max_open_files = 20; - // delete obsolete files always - db_options_.delete_obsolete_files_period_micros = 0; - Open({"default", "one", "two"}); - ColumnFamilyOptions options; - options.level0_file_num_compaction_trigger = 100; - options.level0_slowdown_writes_trigger = 200; - options.level0_stop_writes_trigger = 200; - options.write_buffer_size = 100000; // small write buffer size - Reopen({options, options, options}); - - // 1MB should create ~10 files for each CF - int kKeysNum = 10000; - PutRandomData(1, kKeysNum, 100); - { - std::unique_ptr iterator( - db_->NewIterator(ReadOptions(), handles_[1])); - iterator->SeekToFirst(); - - DropColumnFamilies({1}); - - // Make sure iterator created can still be used. - int count = 0; - for (; iterator->Valid(); iterator->Next()) { - ASSERT_OK(iterator->status()); - ++count; - } - ASSERT_OK(iterator->status()); - ASSERT_EQ(count, kKeysNum); - } - - Reopen(); - Close(); - Destroy(); -} - -TEST_P(ColumnFamilyTest, FlushAndDropRaceCondition) { - db_options_.create_missing_column_families = true; - Open({"default", "one"}); - ColumnFamilyOptions options; - options.level0_file_num_compaction_trigger = 100; - options.level0_slowdown_writes_trigger = 200; - options.level0_stop_writes_trigger = 200; - options.max_write_buffer_number = 20; - options.write_buffer_size = 100000; // small write buffer size - Reopen({options, options}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"VersionSet::LogAndApply::ColumnFamilyDrop:0", - "FlushJob::WriteLevel0Table"}, - {"VersionSet::LogAndApply::ColumnFamilyDrop:1", - "FlushJob::InstallResults"}, - {"FlushJob::InstallResults", - "VersionSet::LogAndApply::ColumnFamilyDrop:2"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - test::SleepingBackgroundTask sleeping_task; - - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::HIGH); - // Make sure the task is sleeping. Otherwise, it might start to execute - // after sleeping_task.WaitUntilDone() and cause TSAN warning. - sleeping_task.WaitUntilSleeping(); - - // 1MB should create ~10 files for each CF - int kKeysNum = 10000; - PutRandomData(1, kKeysNum, 100); - - std::vector threads; - threads.emplace_back([&] { ASSERT_OK(db_->DropColumnFamily(handles_[1])); }); - - sleeping_task.WakeUp(); - sleeping_task.WaitUntilDone(); - sleeping_task.Reset(); - // now we sleep again. this is just so we're certain that flush job finished - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::HIGH); - // Make sure the task is sleeping. Otherwise, it might start to execute - // after sleeping_task.WaitUntilDone() and cause TSAN warning. - sleeping_task.WaitUntilSleeping(); - sleeping_task.WakeUp(); - sleeping_task.WaitUntilDone(); - - { - // Since we didn't delete CF handle, RocksDB's contract guarantees that - // we're still able to read dropped CF - std::unique_ptr iterator( - db_->NewIterator(ReadOptions(), handles_[1])); - int count = 0; - for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { - ASSERT_OK(iterator->status()); - ++count; - } - ASSERT_OK(iterator->status()); - ASSERT_EQ(count, kKeysNum); - } - for (auto& t : threads) { - t.join(); - } - - Close(); - Destroy(); -} - -namespace { -std::atomic test_stage(0); -std::atomic ordered_by_writethread(false); -const int kMainThreadStartPersistingOptionsFile = 1; -const int kChildThreadFinishDroppingColumnFamily = 2; -void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id, - std::vector* comparators) { - while (test_stage < kMainThreadStartPersistingOptionsFile && - !ordered_by_writethread) { - Env::Default()->SleepForMicroseconds(100); - } - cf_test->DropColumnFamilies({cf_id}); - if ((*comparators)[cf_id]) { - delete (*comparators)[cf_id]; - (*comparators)[cf_id] = nullptr; - } - test_stage = kChildThreadFinishDroppingColumnFamily; -} -} // anonymous namespace - -TEST_P(ColumnFamilyTest, CreateAndDropRace) { - const int kCfCount = 5; - std::vector cf_opts; - std::vector comparators; - for (int i = 0; i < kCfCount; ++i) { - cf_opts.emplace_back(); - comparators.push_back(new test::SimpleSuffixReverseComparator()); - cf_opts.back().comparator = comparators.back(); - } - db_options_.create_if_missing = true; - db_options_.create_missing_column_families = true; - - auto main_thread_id = std::this_thread::get_id(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PersistRocksDBOptions:start", [&](void* /*arg*/) { - auto current_thread_id = std::this_thread::get_id(); - // If it's the main thread hitting this sync-point, then it - // will be blocked until some other thread update the test_stage. - if (main_thread_id == current_thread_id) { - test_stage = kMainThreadStartPersistingOptionsFile; - while (test_stage < kChildThreadFinishDroppingColumnFamily && - !ordered_by_writethread) { - Env::Default()->SleepForMicroseconds(100); - } - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::EnterUnbatched:Wait", [&](void* /*arg*/) { - // This means a thread doing DropColumnFamily() is waiting for - // other thread to finish persisting options. - // In such case, we update the test_stage to unblock the main thread. - ordered_by_writethread = true; - }); - - // Create a database with four column families - Open({"default", "one", "two", "three"}, - {cf_opts[0], cf_opts[1], cf_opts[2], cf_opts[3]}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Start a thread that will drop the first column family - // and its comparator - ROCKSDB_NAMESPACE::port::Thread drop_cf_thread(DropSingleColumnFamily, this, - 1, &comparators); - - DropColumnFamilies({2}); - - drop_cf_thread.join(); - Close(); - Destroy(); - for (auto* comparator : comparators) { - if (comparator) { - delete comparator; - } - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(ColumnFamilyTest, WriteStallSingleColumnFamily) { - const uint64_t kBaseRate = 800000u; - db_options_.delayed_write_rate = kBaseRate; - db_options_.max_background_compactions = 6; - - Open({"default"}); - ColumnFamilyData* cfd = - static_cast(db_->DefaultColumnFamily())->cfd(); - - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - - MutableCFOptions mutable_cf_options(column_family_options_); - - mutable_cf_options.level0_slowdown_writes_trigger = 20; - mutable_cf_options.level0_stop_writes_trigger = 10000; - mutable_cf_options.soft_pending_compaction_bytes_limit = 200; - mutable_cf_options.hard_pending_compaction_bytes_limit = 2000; - mutable_cf_options.disable_auto_compactions = false; - - vstorage->TEST_set_estimated_compaction_needed_bytes(50); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(201); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(400); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(500); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(450); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(205); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(202); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(201); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(198); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(399); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(599); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(2001); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(3001); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(390); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(100); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage->set_l0_delay_trigger_count(100); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(101); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - - vstorage->set_l0_delay_trigger_count(0); - vstorage->TEST_set_estimated_compaction_needed_bytes(300); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); - - vstorage->set_l0_delay_trigger_count(101); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25 / 1.25 / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(200); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); - - vstorage->set_l0_delay_trigger_count(0); - vstorage->TEST_set_estimated_compaction_needed_bytes(0); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - mutable_cf_options.disable_auto_compactions = true; - dbfull()->TEST_write_controler().set_delayed_write_rate(kBaseRate); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage->set_l0_delay_trigger_count(50); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(0, GetDbDelayedWriteRate()); - ASSERT_EQ(kBaseRate, dbfull()->TEST_write_controler().delayed_write_rate()); - - vstorage->set_l0_delay_trigger_count(60); - vstorage->TEST_set_estimated_compaction_needed_bytes(300); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(0, GetDbDelayedWriteRate()); - ASSERT_EQ(kBaseRate, dbfull()->TEST_write_controler().delayed_write_rate()); - - mutable_cf_options.disable_auto_compactions = false; - vstorage->set_l0_delay_trigger_count(70); - vstorage->TEST_set_estimated_compaction_needed_bytes(500); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage->set_l0_delay_trigger_count(71); - vstorage->TEST_set_estimated_compaction_needed_bytes(501); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); -} - -TEST_P(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) { - db_options_.max_background_compactions = 6; - Open({"default"}); - ColumnFamilyData* cfd = - static_cast(db_->DefaultColumnFamily())->cfd(); - - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - - MutableCFOptions mutable_cf_options(column_family_options_); - - // Speed up threshold = min(4 * 2, 4 + (36 - 4)/4) = 8 - mutable_cf_options.level0_file_num_compaction_trigger = 4; - mutable_cf_options.level0_slowdown_writes_trigger = 36; - mutable_cf_options.level0_stop_writes_trigger = 50; - // Speedup threshold = 200 / 4 = 50 - mutable_cf_options.soft_pending_compaction_bytes_limit = 200; - mutable_cf_options.hard_pending_compaction_bytes_limit = 2000; - - vstorage->TEST_set_estimated_compaction_needed_bytes(40); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(50); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(300); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(45); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(7); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(9); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(6); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - // Speed up threshold = min(4 * 2, 4 + (12 - 4)/4) = 6 - mutable_cf_options.level0_file_num_compaction_trigger = 4; - mutable_cf_options.level0_slowdown_writes_trigger = 16; - mutable_cf_options.level0_stop_writes_trigger = 30; - - vstorage->set_l0_delay_trigger_count(5); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(7); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(3); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); -} - -TEST_P(ColumnFamilyTest, WriteStallTwoColumnFamilies) { - const uint64_t kBaseRate = 810000u; - db_options_.delayed_write_rate = kBaseRate; - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyData* cfd = - static_cast(db_->DefaultColumnFamily())->cfd(); - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - - ColumnFamilyData* cfd1 = - static_cast(handles_[1])->cfd(); - VersionStorageInfo* vstorage1 = cfd1->current()->storage_info(); - - MutableCFOptions mutable_cf_options(column_family_options_); - mutable_cf_options.level0_slowdown_writes_trigger = 20; - mutable_cf_options.level0_stop_writes_trigger = 10000; - mutable_cf_options.soft_pending_compaction_bytes_limit = 200; - mutable_cf_options.hard_pending_compaction_bytes_limit = 2000; - - MutableCFOptions mutable_cf_options1 = mutable_cf_options; - mutable_cf_options1.soft_pending_compaction_bytes_limit = 500; - - vstorage->TEST_set_estimated_compaction_needed_bytes(50); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(201); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(600); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(70); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(800); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(300); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(700); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(500); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(600); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_TRUE(!IsDbWriteStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); -} - -TEST_P(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) { - db_options_.max_background_compactions = 6; - column_family_options_.soft_pending_compaction_bytes_limit = 200; - column_family_options_.hard_pending_compaction_bytes_limit = 2000; - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyData* cfd = - static_cast(db_->DefaultColumnFamily())->cfd(); - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - - ColumnFamilyData* cfd1 = - static_cast(handles_[1])->cfd(); - VersionStorageInfo* vstorage1 = cfd1->current()->storage_info(); - - MutableCFOptions mutable_cf_options(column_family_options_); - // Speed up threshold = min(4 * 2, 4 + (36 - 4)/4) = 8 - mutable_cf_options.level0_file_num_compaction_trigger = 4; - mutable_cf_options.level0_slowdown_writes_trigger = 36; - mutable_cf_options.level0_stop_writes_trigger = 30; - // Speedup threshold = 200 / 4 = 50 - mutable_cf_options.soft_pending_compaction_bytes_limit = 200; - mutable_cf_options.hard_pending_compaction_bytes_limit = 2000; - - MutableCFOptions mutable_cf_options1 = mutable_cf_options; - mutable_cf_options1.level0_slowdown_writes_trigger = 16; - - vstorage->TEST_set_estimated_compaction_needed_bytes(40); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(60); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(30); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(70); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->TEST_set_estimated_compaction_needed_bytes(20); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage1->TEST_set_estimated_compaction_needed_bytes(3); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(9); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage1->set_l0_delay_trigger_count(2); - RecalculateWriteStallConditions(cfd1, mutable_cf_options); - ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); - - vstorage->set_l0_delay_trigger_count(0); - RecalculateWriteStallConditions(cfd, mutable_cf_options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); -} - -TEST_P(ColumnFamilyTest, CreateAndDestroyOptions) { - std::unique_ptr cfo(new ColumnFamilyOptions()); - ColumnFamilyHandle* cfh; - Open(); - ASSERT_OK(db_->CreateColumnFamily(*(cfo.get()), "yoyo", &cfh)); - cfo.reset(); - ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); - ASSERT_OK(db_->Flush(FlushOptions(), cfh)); - ASSERT_OK(db_->DropColumnFamily(cfh)); - ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh)); -} - -TEST_P(ColumnFamilyTest, CreateDropAndDestroy) { - ColumnFamilyHandle* cfh; - Open(); - ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh)); - ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); - ASSERT_OK(db_->Flush(FlushOptions(), cfh)); - ASSERT_OK(db_->DropColumnFamily(cfh)); - ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh)); -} - -TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) { - ColumnFamilyHandle* cfh; - Open(); - ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh)); - ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); - ASSERT_OK(db_->Flush(FlushOptions(), cfh)); - ASSERT_OK(db_->DisableFileDeletions()); - ASSERT_OK(db_->DropColumnFamily(cfh)); - ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh)); -} - -TEST_P(ColumnFamilyTest, FlushCloseWALFiles) { - SpecialEnv env(Env::Default()); - db_options_.env = &env; - db_options_.max_background_flushes = 1; - column_family_options_.memtable_factory.reset( - test::NewSpecialSkipListFactory(2)); - Open(); - CreateColumnFamilies({"one"}); - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(0, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodor", "mirko")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::BGWorkFlush:done", "FlushCloseWALFiles:0"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Block flush jobs from running - test::SleepingBackgroundTask sleeping_task; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::HIGH); - // Make sure the task is sleeping. Otherwise, it might start to execute - // after sleeping_task.WaitUntilDone() and cause TSAN warning. - sleeping_task.WaitUntilSleeping(); - - WriteOptions wo; - wo.sync = true; - ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko")); - - ASSERT_EQ(2, env.num_open_wal_file_.load()); - - sleeping_task.WakeUp(); - sleeping_task.WaitUntilDone(); - TEST_SYNC_POINT("FlushCloseWALFiles:0"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(1, env.num_open_wal_file_.load()); - - Reopen(); - ASSERT_EQ("mirko", Get(0, "fodor")); - ASSERT_EQ("mirko", Get(1, "fodor")); - db_options_.env = env_; - Close(); -} - -TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) { - SpecialEnv env(Env::Default()); - db_options_.env = &env; - db_options_.max_background_flushes = 1; - column_family_options_.memtable_factory.reset( - test::NewSpecialSkipListFactory(2)); - Open(); - CreateColumnFamilies({"one"}); - ASSERT_OK(Put(1, "fodor", "mirko")); - // Create an iterator holding the current super version. - Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]); - ASSERT_OK(it->status()); - // A flush will make `it` hold the last reference of its super version. - ASSERT_OK(Flush(1)); - - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(0, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodor", "mirko")); - - // Flush jobs will close previous WAL files after finishing. By - // block flush jobs from running, we trigger a condition where - // the iterator destructor should close the WAL files. - test::SleepingBackgroundTask sleeping_task; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::HIGH); - // Make sure the task is sleeping. Otherwise, it might start to execute - // after sleeping_task.WaitUntilDone() and cause TSAN warning. - sleeping_task.WaitUntilSleeping(); - - WriteOptions wo; - wo.sync = true; - ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko")); - - ASSERT_EQ(2, env.num_open_wal_file_.load()); - // Deleting the iterator will clear its super version, triggering - // closing all files - delete it; - ASSERT_EQ(1, env.num_open_wal_file_.load()); - - sleeping_task.WakeUp(); - sleeping_task.WaitUntilDone(); - WaitForFlush(1); - - Reopen(); - ASSERT_EQ("mirko", Get(0, "fodor")); - ASSERT_EQ("mirko", Get(1, "fodor")); - db_options_.env = env_; - Close(); -} - -TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) { - SpecialEnv env(Env::Default()); - // Allow both of flush and purge job to schedule. - env.SetBackgroundThreads(2, Env::HIGH); - db_options_.env = &env; - db_options_.max_background_flushes = 1; - column_family_options_.memtable_factory.reset( - test::NewSpecialSkipListFactory(2)); - Open(); - CreateColumnFamilies({"one"}); - ASSERT_OK(Put(1, "fodor", "mirko")); - // Create an iterator holding the current super version. - ReadOptions ro; - ro.background_purge_on_iterator_cleanup = true; - Iterator* it = db_->NewIterator(ro, handles_[1]); - ASSERT_OK(it->status()); - // A flush will make `it` hold the last reference of its super version. - ASSERT_OK(Flush(1)); - - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(0, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodor", "mirko")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"ColumnFamilyTest::IteratorCloseWALFile2:0", - "DBImpl::BGWorkPurge:start"}, - {"ColumnFamilyTest::IteratorCloseWALFile2:2", - "DBImpl::BackgroundCallFlush:start"}, - {"DBImpl::BGWorkPurge:end", "ColumnFamilyTest::IteratorCloseWALFile2:1"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.sync = true; - ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko")); - - ASSERT_EQ(2, env.num_open_wal_file_.load()); - // Deleting the iterator will clear its super version, triggering - // closing all files - delete it; - ASSERT_EQ(2, env.num_open_wal_file_.load()); - - TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:0"); - TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:1"); - ASSERT_EQ(1, env.num_open_wal_file_.load()); - TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:2"); - WaitForFlush(1); - ASSERT_EQ(1, env.num_open_wal_file_.load()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - Reopen(); - ASSERT_EQ("mirko", Get(0, "fodor")); - ASSERT_EQ("mirko", Get(1, "fodor")); - db_options_.env = env_; - Close(); -} - -TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) { - SpecialEnv env(Env::Default()); - // Allow both of flush and purge job to schedule. - env.SetBackgroundThreads(2, Env::HIGH); - db_options_.env = &env; - db_options_.max_background_flushes = 1; - column_family_options_.memtable_factory.reset( - test::NewSpecialSkipListFactory(3)); - column_family_options_.level0_file_num_compaction_trigger = 2; - Open(); - CreateColumnFamilies({"one"}); - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodar2", "mirko")); - ASSERT_OK(Flush(1)); - - // Create an iterator holding the current super version, as well as - // the SST file just flushed. - ReadOptions ro; - ro.tailing = true; - ro.background_purge_on_iterator_cleanup = true; - Iterator* it = db_->NewIterator(ro, handles_[1]); - // A flush will make `it` hold the last reference of its super version. - - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodar2", "mirko")); - ASSERT_OK(Flush(1)); - - WaitForCompaction(); - - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodor", "mirko")); - ASSERT_OK(Put(0, "fodor", "mirko")); - ASSERT_OK(Put(1, "fodor", "mirko")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"ColumnFamilyTest::IteratorCloseWALFile2:0", - "DBImpl::BGWorkPurge:start"}, - {"ColumnFamilyTest::IteratorCloseWALFile2:2", - "DBImpl::BackgroundCallFlush:start"}, - {"DBImpl::BGWorkPurge:end", "ColumnFamilyTest::IteratorCloseWALFile2:1"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.sync = true; - ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko")); - - env.delete_count_.store(0); - ASSERT_EQ(2, env.num_open_wal_file_.load()); - // Deleting the iterator will clear its super version, triggering - // closing all files - it->Seek(""); - ASSERT_OK(it->status()); - - ASSERT_EQ(2, env.num_open_wal_file_.load()); - ASSERT_EQ(0, env.delete_count_.load()); - - TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:0"); - TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:1"); - ASSERT_EQ(1, env.num_open_wal_file_.load()); - ASSERT_EQ(1, env.delete_count_.load()); - TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:2"); - WaitForFlush(1); - ASSERT_EQ(1, env.num_open_wal_file_.load()); - ASSERT_EQ(1, env.delete_count_.load()); - - delete it; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - Reopen(); - ASSERT_EQ("mirko", Get(0, "fodor")); - ASSERT_EQ("mirko", Get(1, "fodor")); - db_options_.env = env_; - Close(); -} - -// Disable on windows because SyncWAL requires env->IsSyncThreadSafe() -// to return true which is not so in unbuffered mode. -#ifndef OS_WIN -TEST_P(ColumnFamilyTest, LogSyncConflictFlush) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two"}); - - ASSERT_OK(Put(0, "", "")); - ASSERT_OK(Put(1, "foo", "bar")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::SyncWAL:BeforeMarkLogsSynced:1", - "ColumnFamilyTest::LogSyncConflictFlush:1"}, - {"ColumnFamilyTest::LogSyncConflictFlush:2", - "DBImpl::SyncWAL:BeforeMarkLogsSynced:2"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread thread([&] { ASSERT_OK(db_->SyncWAL()); }); - - TEST_SYNC_POINT("ColumnFamilyTest::LogSyncConflictFlush:1"); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Flush(1)); - - TEST_SYNC_POINT("ColumnFamilyTest::LogSyncConflictFlush:2"); - - thread.join(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - Close(); -} -#endif - -// this test is placed here, because the infrastructure for Column Family -// test is being used to ensure a roll of wal files. -// Basic idea is to test that WAL truncation is being detected and not -// ignored -TEST_P(ColumnFamilyTest, DISABLED_LogTruncationTest) { - Open(); - CreateColumnFamiliesAndReopen({"one", "two"}); - - Build(0, 100); - - // Flush the 0th column family to force a roll of the wal log - ASSERT_OK(Flush(0)); - - // Add some more entries - Build(100, 100); - - std::vector filenames; - ASSERT_OK(env_->GetChildren(dbname_, &filenames)); - - // collect wal files - std::vector logfs; - for (size_t i = 0; i < filenames.size(); i++) { - uint64_t number; - FileType type; - if (!(ParseFileName(filenames[i], &number, &type))) continue; - - if (type != kWalFile) continue; - - logfs.push_back(filenames[i]); - } - - std::sort(logfs.begin(), logfs.end()); - ASSERT_GE(logfs.size(), 2); - - // Take the last but one file, and truncate it - std::string fpath = dbname_ + "/" + logfs[logfs.size() - 2]; - std::vector names_save = names_; - - uint64_t fsize; - ASSERT_OK(env_->GetFileSize(fpath, &fsize)); - ASSERT_GT(fsize, 0); - - Close(); - - std::string backup_logs = dbname_ + "/backup_logs"; - std::string t_fpath = backup_logs + "/" + logfs[logfs.size() - 2]; - - ASSERT_OK(env_->CreateDirIfMissing(backup_logs)); - // Not sure how easy it is to make this data driven. - // need to read back the WAL file and truncate last 10 - // entries - CopyFile(fpath, t_fpath, fsize - 9180); - - ASSERT_OK(env_->DeleteFile(fpath)); - ASSERT_OK(env_->RenameFile(t_fpath, fpath)); - - db_options_.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - - OpenReadOnly(names_save); - - CheckMissed(); - - Close(); - - Open(names_save); - - CheckMissed(); - - Close(); - - // cleanup - ASSERT_OK(env_->DeleteDir(backup_logs)); -} - -TEST_P(ColumnFamilyTest, DefaultCfPathsTest) { - Open(); - // Leave cf_paths for one column families to be empty. - // Files should be generated according to db_paths for that - // column family. - ColumnFamilyOptions cf_opt1, cf_opt2; - cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1", - std::numeric_limits::max()); - CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2}); - Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2}); - - // Fill Column family 1. - PutRandomData(1, 100, 100); - ASSERT_OK(Flush(1)); - - ASSERT_EQ(1, GetSstFileCount(cf_opt1.cf_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // Fill column family 2 - PutRandomData(2, 100, 100); - ASSERT_OK(Flush(2)); - - // SST from Column family 2 should be generated in - // db_paths which is dbname_ in this case. - ASSERT_EQ(1, GetSstFileCount(dbname_)); -} - -TEST_P(ColumnFamilyTest, MultipleCFPathsTest) { - Open(); - // Configure Column family specific paths. - ColumnFamilyOptions cf_opt1, cf_opt2; - cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1", - std::numeric_limits::max()); - cf_opt2.cf_paths.emplace_back(dbname_ + "_two_1", - std::numeric_limits::max()); - CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2}); - Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2}); - - PutRandomData(1, 100, 100, true /* save */); - ASSERT_OK(Flush(1)); - - // Check that files are generated in appropriate paths. - ASSERT_EQ(1, GetSstFileCount(cf_opt1.cf_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - PutRandomData(2, 100, 100, true /* save */); - ASSERT_OK(Flush(2)); - - ASSERT_EQ(1, GetSstFileCount(cf_opt2.cf_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // Re-open and verify the keys. - Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2}); - DBImpl* dbi = static_cast_with_check(db_); - for (int cf = 1; cf != 3; ++cf) { - ReadOptions read_options; - read_options.readahead_size = 0; - auto it = dbi->NewIterator(read_options, handles_[cf]); - for (it->SeekToFirst(); it->Valid(); it->Next()) { - ASSERT_OK(it->status()); - Slice key(it->key()); - ASSERT_NE(keys_[cf].end(), keys_[cf].find(key.ToString())); - } - ASSERT_OK(it->status()); - delete it; - - for (const auto& key : keys_[cf]) { - ASSERT_NE("NOT_FOUND", Get(cf, key)); - } - } -} - -TEST(ColumnFamilyTest, ValidateBlobGCCutoff) { - DBOptions db_options; - - ColumnFamilyOptions cf_options; - cf_options.enable_blob_garbage_collection = true; - - cf_options.blob_garbage_collection_age_cutoff = -0.5; - ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options) - .IsInvalidArgument()); - - cf_options.blob_garbage_collection_age_cutoff = 0.0; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.blob_garbage_collection_age_cutoff = 0.5; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.blob_garbage_collection_age_cutoff = 1.0; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.blob_garbage_collection_age_cutoff = 1.5; - ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options) - .IsInvalidArgument()); -} - -TEST(ColumnFamilyTest, ValidateBlobGCForceThreshold) { - DBOptions db_options; - - ColumnFamilyOptions cf_options; - cf_options.enable_blob_garbage_collection = true; - - cf_options.blob_garbage_collection_force_threshold = -0.5; - ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options) - .IsInvalidArgument()); - - cf_options.blob_garbage_collection_force_threshold = 0.0; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.blob_garbage_collection_force_threshold = 0.5; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.blob_garbage_collection_force_threshold = 1.0; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.blob_garbage_collection_force_threshold = 1.5; - ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options) - .IsInvalidArgument()); -} - -TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) { - DBOptions db_options; - - ColumnFamilyOptions cf_options; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.memtable_protection_bytes_per_key = 5; - ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options) - .IsNotSupported()); - - cf_options.memtable_protection_bytes_per_key = 1; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); - - cf_options.memtable_protection_bytes_per_key = 16; - ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options) - .IsNotSupported()); - - cf_options.memtable_protection_bytes_per_key = 0; - ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/compact_files_test.cc b/db/compact_files_test.cc deleted file mode 100644 index ad94ad340..000000000 --- a/db/compact_files_test.cc +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "port/port.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "util/cast_util.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class CompactFilesTest : public testing::Test { - public: - CompactFilesTest() { - env_ = Env::Default(); - db_name_ = test::PerThreadDBPath("compact_files_test"); - } - - std::string db_name_; - Env* env_; -}; - -// A class which remembers the name of each flushed file. -class FlushedFileCollector : public EventListener { - public: - FlushedFileCollector() {} - ~FlushedFileCollector() override {} - - void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { - std::lock_guard lock(mutex_); - flushed_files_.push_back(info.file_path); - } - - std::vector GetFlushedFiles() { - std::lock_guard lock(mutex_); - std::vector result; - for (auto fname : flushed_files_) { - result.push_back(fname); - } - return result; - } - void ClearFlushedFiles() { - std::lock_guard lock(mutex_); - flushed_files_.clear(); - } - - private: - std::vector flushed_files_; - std::mutex mutex_; -}; - -TEST_F(CompactFilesTest, L0ConflictsFiles) { - Options options; - // to trigger compaction more easily - const int kWriteBufferSize = 10000; - const int kLevel0Trigger = 2; - options.create_if_missing = true; - options.compaction_style = kCompactionStyleLevel; - // Small slowdown and stop trigger for experimental purpose. - options.level0_slowdown_writes_trigger = 20; - options.level0_stop_writes_trigger = 20; - options.level0_stop_writes_trigger = 20; - options.write_buffer_size = kWriteBufferSize; - options.level0_file_num_compaction_trigger = kLevel0Trigger; - options.compression = kNoCompression; - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - assert(s.ok()); - assert(db); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"CompactFilesImpl:0", "BackgroundCallCompaction:0"}, - {"BackgroundCallCompaction:1", "CompactFilesImpl:1"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // create couple files - // Background compaction starts and waits in BackgroundCallCompaction:0 - for (int i = 0; i < kLevel0Trigger * 4; ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), "")); - ASSERT_OK(db->Put(WriteOptions(), std::to_string(100 - i), "")); - ASSERT_OK(db->Flush(FlushOptions())); - } - - ROCKSDB_NAMESPACE::ColumnFamilyMetaData meta; - db->GetColumnFamilyMetaData(&meta); - std::string file1; - for (auto& file : meta.levels[0].files) { - ASSERT_EQ(0, meta.levels[0].level); - if (file1 == "") { - file1 = file.db_path + "/" + file.name; - } else { - std::string file2 = file.db_path + "/" + file.name; - // Another thread starts a compact files and creates an L0 compaction - // The background compaction then notices that there is an L0 compaction - // already in progress and doesn't do an L0 compaction - // Once the background compaction finishes, the compact files finishes - ASSERT_OK(db->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), - {file1, file2}, 0)); - break; - } - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - delete db; -} - -TEST_F(CompactFilesTest, MultipleLevel) { - Options options; - options.create_if_missing = true; - options.level_compaction_dynamic_level_bytes = true; - options.num_levels = 6; - // Add listener - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - ASSERT_OK(s); - ASSERT_NE(db, nullptr); - - // create couple files in L0, L3, L4 and L5 - for (int i = 5; i > 2; --i) { - collector->ClearFlushedFiles(); - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), "")); - ASSERT_OK(db->Flush(FlushOptions())); - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForBackgroundWork()); - auto l0_files = collector->GetFlushedFiles(); - ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, i)); - - std::string prop; - ASSERT_TRUE(db->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(i), &prop)); - ASSERT_EQ("1", prop); - } - ASSERT_OK(db->Put(WriteOptions(), std::to_string(0), "")); - ASSERT_OK(db->Flush(FlushOptions())); - - ColumnFamilyMetaData meta; - db->GetColumnFamilyMetaData(&meta); - // Compact files except the file in L3 - std::vector files; - for (int i = 0; i < 6; ++i) { - if (i == 3) continue; - for (auto& file : meta.levels[i].files) { - files.push_back(file.db_path + "/" + file.name); - } - } - - SyncPoint::GetInstance()->LoadDependency({ - {"CompactionJob::Run():Start", "CompactFilesTest.MultipleLevel:0"}, - {"CompactFilesTest.MultipleLevel:1", "CompactFilesImpl:3"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - std::thread thread([&] { - TEST_SYNC_POINT("CompactFilesTest.MultipleLevel:0"); - ASSERT_OK(db->Put(WriteOptions(), "bar", "v2")); - ASSERT_OK(db->Put(WriteOptions(), "foo", "v2")); - ASSERT_OK(db->Flush(FlushOptions())); - TEST_SYNC_POINT("CompactFilesTest.MultipleLevel:1"); - }); - - // Compaction cannot move up the data to higher level - // here we have input file from level 5, so the output level has to be >= 5 - for (int invalid_output_level = 0; invalid_output_level < 5; - invalid_output_level++) { - s = db->CompactFiles(CompactionOptions(), files, invalid_output_level); - std::cout << s.ToString() << std::endl; - ASSERT_TRUE(s.IsInvalidArgument()); - } - - ASSERT_OK(db->CompactFiles(CompactionOptions(), files, 5)); - SyncPoint::GetInstance()->DisableProcessing(); - thread.join(); - - delete db; -} - -TEST_F(CompactFilesTest, ObsoleteFiles) { - Options options; - // to trigger compaction more easily - const int kWriteBufferSize = 65536; - options.create_if_missing = true; - // Disable RocksDB background compaction. - options.compaction_style = kCompactionStyleNone; - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.write_buffer_size = kWriteBufferSize; - options.max_write_buffer_number = 2; - options.compression = kNoCompression; - - // Add listener - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - ASSERT_OK(s); - ASSERT_NE(db, nullptr); - - // create couple files - for (int i = 1000; i < 2000; ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), - std::string(kWriteBufferSize / 10, 'a' + (i % 26)))); - } - - auto l0_files = collector->GetFlushedFiles(); - ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, 1)); - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForCompact()); - - // verify all compaction input files are deleted - for (auto fname : l0_files) { - ASSERT_EQ(Status::NotFound(), env_->FileExists(fname)); - } - delete db; -} - -TEST_F(CompactFilesTest, NotCutOutputOnLevel0) { - Options options; - options.create_if_missing = true; - // Disable RocksDB background compaction. - options.compaction_style = kCompactionStyleNone; - options.level0_slowdown_writes_trigger = 1000; - options.level0_stop_writes_trigger = 1000; - options.write_buffer_size = 65536; - options.max_write_buffer_number = 2; - options.compression = kNoCompression; - options.max_compaction_bytes = 5000; - - // Add listener - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - assert(s.ok()); - assert(db); - - // create couple files - for (int i = 0; i < 500; ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), - std::string(1000, 'a' + (i % 26)))); - } - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForFlushMemTable()); - auto l0_files_1 = collector->GetFlushedFiles(); - collector->ClearFlushedFiles(); - for (int i = 0; i < 500; ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), - std::string(1000, 'a' + (i % 26)))); - } - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForFlushMemTable()); - auto l0_files_2 = collector->GetFlushedFiles(); - ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_1, 0)); - ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_2, 0)); - // no assertion failure - delete db; -} - -TEST_F(CompactFilesTest, CapturingPendingFiles) { - Options options; - options.create_if_missing = true; - // Disable RocksDB background compaction. - options.compaction_style = kCompactionStyleNone; - // Always do full scans for obsolete files (needed to reproduce the issue). - options.delete_obsolete_files_period_micros = 0; - - // Add listener. - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - ASSERT_OK(s); - assert(db); - - // Create 5 files. - for (int i = 0; i < 5; ++i) { - ASSERT_OK(db->Put(WriteOptions(), "key" + std::to_string(i), "value")); - ASSERT_OK(db->Flush(FlushOptions())); - } - - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForBackgroundWork()); - auto l0_files = collector->GetFlushedFiles(); - EXPECT_EQ(5, l0_files.size()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"CompactFilesImpl:2", "CompactFilesTest.CapturingPendingFiles:0"}, - {"CompactFilesTest.CapturingPendingFiles:1", "CompactFilesImpl:3"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Start compacting files. - ROCKSDB_NAMESPACE::port::Thread compaction_thread( - [&] { EXPECT_OK(db->CompactFiles(CompactionOptions(), l0_files, 1)); }); - - // In the meantime flush another file. - TEST_SYNC_POINT("CompactFilesTest.CapturingPendingFiles:0"); - ASSERT_OK(db->Put(WriteOptions(), "key5", "value")); - ASSERT_OK(db->Flush(FlushOptions())); - TEST_SYNC_POINT("CompactFilesTest.CapturingPendingFiles:1"); - - compaction_thread.join(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - delete db; - - // Make sure we can reopen the DB. - s = DB::Open(options, db_name_, &db); - ASSERT_OK(s); - assert(db); - delete db; -} - -TEST_F(CompactFilesTest, CompactionFilterWithGetSv) { - class FilterWithGet : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - if (db_ == nullptr) { - return true; - } - std::string res; - db_->Get(ReadOptions(), "", &res); - return true; - } - - void SetDB(DB* db) { db_ = db; } - - const char* Name() const override { return "FilterWithGet"; } - - private: - DB* db_; - }; - - std::shared_ptr cf(new FilterWithGet()); - - Options options; - options.create_if_missing = true; - options.compaction_filter = cf.get(); - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - ASSERT_OK(s); - - cf->SetDB(db); - - // Write one L0 file - ASSERT_OK(db->Put(WriteOptions(), "K1", "V1")); - ASSERT_OK(db->Flush(FlushOptions())); - - // Compact all L0 files using CompactFiles - ROCKSDB_NAMESPACE::ColumnFamilyMetaData meta; - db->GetColumnFamilyMetaData(&meta); - for (auto& file : meta.levels[0].files) { - std::string fname = file.db_path + "/" + file.name; - ASSERT_OK( - db->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), {fname}, 0)); - } - - delete db; -} - -TEST_F(CompactFilesTest, SentinelCompressionType) { - if (!Zlib_Supported()) { - fprintf(stderr, "zlib compression not supported, skip this test\n"); - return; - } - if (!Snappy_Supported()) { - fprintf(stderr, "snappy compression not supported, skip this test\n"); - return; - } - // Check that passing `CompressionType::kDisableCompressionOption` to - // `CompactFiles` causes it to use the column family compression options. - for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel, - CompactionStyle::kCompactionStyleUniversal, - CompactionStyle::kCompactionStyleNone}) { - ASSERT_OK(DestroyDB(db_name_, Options())); - Options options; - options.compaction_style = compaction_style; - // L0: Snappy, L1: ZSTD, L2: Snappy - options.compression_per_level = {CompressionType::kSnappyCompression, - CompressionType::kZlibCompression, - CompressionType::kSnappyCompression}; - options.create_if_missing = true; - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - DB* db = nullptr; - ASSERT_OK(DB::Open(options, db_name_, &db)); - - ASSERT_OK(db->Put(WriteOptions(), "key", "val")); - ASSERT_OK(db->Flush(FlushOptions())); - - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForBackgroundWork()); - auto l0_files = collector->GetFlushedFiles(); - ASSERT_EQ(1, l0_files.size()); - - // L0->L1 compaction, so output should be ZSTD-compressed - CompactionOptions compaction_opts; - compaction_opts.compression = CompressionType::kDisableCompressionOption; - ASSERT_OK(db->CompactFiles(compaction_opts, l0_files, 1)); - - ROCKSDB_NAMESPACE::TablePropertiesCollection all_tables_props; - ASSERT_OK(db->GetPropertiesOfAllTables(&all_tables_props)); - for (const auto& name_and_table_props : all_tables_props) { - ASSERT_EQ(CompressionTypeToString(CompressionType::kZlibCompression), - name_and_table_props.second->compression_name); - } - delete db; - } -} - -TEST_F(CompactFilesTest, GetCompactionJobInfo) { - Options options; - options.create_if_missing = true; - // Disable RocksDB background compaction. - options.compaction_style = kCompactionStyleNone; - options.level0_slowdown_writes_trigger = 1000; - options.level0_stop_writes_trigger = 1000; - options.write_buffer_size = 65536; - options.max_write_buffer_number = 2; - options.compression = kNoCompression; - options.max_compaction_bytes = 5000; - - // Add listener - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - DB* db = nullptr; - ASSERT_OK(DestroyDB(db_name_, options)); - Status s = DB::Open(options, db_name_, &db); - ASSERT_OK(s); - assert(db); - - // create couple files - for (int i = 0; i < 500; ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), - std::string(1000, 'a' + (i % 26)))); - } - ASSERT_OK(static_cast_with_check(db)->TEST_WaitForFlushMemTable()); - auto l0_files_1 = collector->GetFlushedFiles(); - CompactionOptions co; - co.compression = CompressionType::kLZ4Compression; - CompactionJobInfo compaction_job_info{}; - ASSERT_OK( - db->CompactFiles(co, l0_files_1, 0, -1, nullptr, &compaction_job_info)); - ASSERT_EQ(compaction_job_info.base_input_level, 0); - ASSERT_EQ(compaction_job_info.cf_id, db->DefaultColumnFamily()->GetID()); - ASSERT_EQ(compaction_job_info.cf_name, db->DefaultColumnFamily()->GetName()); - ASSERT_EQ(compaction_job_info.compaction_reason, - CompactionReason::kManualCompaction); - ASSERT_EQ(compaction_job_info.compression, CompressionType::kLZ4Compression); - ASSERT_EQ(compaction_job_info.output_level, 0); - ASSERT_OK(compaction_job_info.status); - // no assertion failure - delete db; -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/comparator_db_test.cc b/db/comparator_db_test.cc deleted file mode 100644 index e5e3493b3..000000000 --- a/db/comparator_db_test.cc +++ /dev/null @@ -1,678 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -#include -#include -#include - -#include "memtable/stl_wrappers.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/hash.h" -#include "util/kv_map.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { -namespace { - -static const Comparator* kTestComparator = nullptr; - -class KVIter : public Iterator { - public: - explicit KVIter(const stl_wrappers::KVMap* map) - : map_(map), iter_(map_->end()) {} - bool Valid() const override { return iter_ != map_->end(); } - void SeekToFirst() override { iter_ = map_->begin(); } - void SeekToLast() override { - if (map_->empty()) { - iter_ = map_->end(); - } else { - iter_ = map_->find(map_->rbegin()->first); - } - } - void Seek(const Slice& k) override { - iter_ = map_->lower_bound(k.ToString()); - } - void SeekForPrev(const Slice& k) override { - iter_ = map_->upper_bound(k.ToString()); - Prev(); - } - void Next() override { ++iter_; } - void Prev() override { - if (iter_ == map_->begin()) { - iter_ = map_->end(); - return; - } - --iter_; - } - - Slice key() const override { return iter_->first; } - Slice value() const override { return iter_->second; } - Status status() const override { return Status::OK(); } - - private: - const stl_wrappers::KVMap* const map_; - stl_wrappers::KVMap::const_iterator iter_; -}; - -void AssertItersEqual(Iterator* iter1, Iterator* iter2) { - ASSERT_EQ(iter1->Valid(), iter2->Valid()); - if (iter1->Valid()) { - ASSERT_EQ(iter1->key().ToString(), iter2->key().ToString()); - ASSERT_EQ(iter1->value().ToString(), iter2->value().ToString()); - } -} - -// Measuring operations on DB (expect to be empty). -// source_strings are candidate keys -void DoRandomIteraratorTest(DB* db, std::vector source_strings, - Random* rnd, int num_writes, int num_iter_ops, - int num_trigger_flush) { - stl_wrappers::KVMap map((stl_wrappers::LessOfComparator(kTestComparator))); - - for (int i = 0; i < num_writes; i++) { - if (num_trigger_flush > 0 && i != 0 && i % num_trigger_flush == 0) { - db->Flush(FlushOptions()); - } - - int type = rnd->Uniform(2); - int index = rnd->Uniform(static_cast(source_strings.size())); - auto& key = source_strings[index]; - switch (type) { - case 0: - // put - map[key] = key; - ASSERT_OK(db->Put(WriteOptions(), key, key)); - break; - case 1: - // delete - if (map.find(key) != map.end()) { - map.erase(key); - } - ASSERT_OK(db->Delete(WriteOptions(), key)); - break; - default: - assert(false); - } - } - - std::unique_ptr iter(db->NewIterator(ReadOptions())); - std::unique_ptr result_iter(new KVIter(&map)); - - bool is_valid = false; - for (int i = 0; i < num_iter_ops; i++) { - // Random walk and make sure iter and result_iter returns the - // same key and value - int type = rnd->Uniform(6); - ASSERT_OK(iter->status()); - switch (type) { - case 0: - // Seek to First - iter->SeekToFirst(); - result_iter->SeekToFirst(); - break; - case 1: - // Seek to last - iter->SeekToLast(); - result_iter->SeekToLast(); - break; - case 2: { - // Seek to random key - auto key_idx = rnd->Uniform(static_cast(source_strings.size())); - auto key = source_strings[key_idx]; - iter->Seek(key); - result_iter->Seek(key); - break; - } - case 3: - // Next - if (is_valid) { - iter->Next(); - result_iter->Next(); - } else { - continue; - } - break; - case 4: - // Prev - if (is_valid) { - iter->Prev(); - result_iter->Prev(); - } else { - continue; - } - break; - default: { - assert(type == 5); - auto key_idx = rnd->Uniform(static_cast(source_strings.size())); - auto key = source_strings[key_idx]; - std::string result; - auto status = db->Get(ReadOptions(), key, &result); - if (map.find(key) == map.end()) { - ASSERT_TRUE(status.IsNotFound()); - } else { - ASSERT_EQ(map[key], result); - } - break; - } - } - AssertItersEqual(iter.get(), result_iter.get()); - is_valid = iter->Valid(); - } -} - -class DoubleComparator : public Comparator { - public: - DoubleComparator() {} - - const char* Name() const override { return "DoubleComparator"; } - - int Compare(const Slice& a, const Slice& b) const override { -#ifndef CYGWIN - double da = std::stod(a.ToString()); - double db = std::stod(b.ToString()); -#else - double da = std::strtod(a.ToString().c_str(), 0 /* endptr */); - double db = std::strtod(a.ToString().c_str(), 0 /* endptr */); -#endif - if (da == db) { - return a.compare(b); - } else if (da > db) { - return 1; - } else { - return -1; - } - } - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; - -class HashComparator : public Comparator { - public: - HashComparator() {} - - const char* Name() const override { return "HashComparator"; } - - int Compare(const Slice& a, const Slice& b) const override { - uint32_t ha = Hash(a.data(), a.size(), 66); - uint32_t hb = Hash(b.data(), b.size(), 66); - if (ha == hb) { - return a.compare(b); - } else if (ha > hb) { - return 1; - } else { - return -1; - } - } - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; - -class TwoStrComparator : public Comparator { - public: - TwoStrComparator() {} - - const char* Name() const override { return "TwoStrComparator"; } - - int Compare(const Slice& a, const Slice& b) const override { - assert(a.size() >= 2); - assert(b.size() >= 2); - size_t size_a1 = static_cast(a[0]); - size_t size_b1 = static_cast(b[0]); - size_t size_a2 = static_cast(a[1]); - size_t size_b2 = static_cast(b[1]); - assert(size_a1 + size_a2 + 2 == a.size()); - assert(size_b1 + size_b2 + 2 == b.size()); - - Slice a1 = Slice(a.data() + 2, size_a1); - Slice b1 = Slice(b.data() + 2, size_b1); - Slice a2 = Slice(a.data() + 2 + size_a1, size_a2); - Slice b2 = Slice(b.data() + 2 + size_b1, size_b2); - - if (a1 != b1) { - return a1.compare(b1); - } - return a2.compare(b2); - } - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; -} // anonymous namespace - -class ComparatorDBTest - : public testing::Test, - virtual public ::testing::WithParamInterface { - private: - std::string dbname_; - Env* env_; - DB* db_; - Options last_options_; - std::unique_ptr comparator_guard; - - public: - ComparatorDBTest() : env_(Env::Default()), db_(nullptr) { - kTestComparator = BytewiseComparator(); - dbname_ = test::PerThreadDBPath("comparator_db_test"); - BlockBasedTableOptions toptions; - toptions.format_version = GetParam(); - last_options_.table_factory.reset( - ROCKSDB_NAMESPACE::NewBlockBasedTableFactory(toptions)); - EXPECT_OK(DestroyDB(dbname_, last_options_)); - } - - ~ComparatorDBTest() override { - delete db_; - EXPECT_OK(DestroyDB(dbname_, last_options_)); - kTestComparator = BytewiseComparator(); - } - - DB* GetDB() { return db_; } - - void SetOwnedComparator(const Comparator* cmp, bool owner = true) { - if (owner) { - comparator_guard.reset(cmp); - } else { - comparator_guard.reset(); - } - kTestComparator = cmp; - last_options_.comparator = cmp; - } - - // Return the current option configuration. - Options* GetOptions() { return &last_options_; } - - void DestroyAndReopen() { - // Destroy using last options - Destroy(); - ASSERT_OK(TryReopen()); - } - - void Destroy() { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, last_options_)); - } - - Status TryReopen() { - delete db_; - db_ = nullptr; - last_options_.create_if_missing = true; - - return DB::Open(last_options_, dbname_, &db_); - } -}; - -INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest, - testing::Values(test::kDefaultFormatVersion)); -INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest, - testing::Values(kLatestFormatVersion)); - -TEST_P(ComparatorDBTest, Bytewise) { - for (int rand_seed = 301; rand_seed < 306; rand_seed++) { - DestroyAndReopen(); - Random rnd(rand_seed); - DoRandomIteraratorTest(GetDB(), - {"a", "b", "c", "d", "e", "f", "g", "h", "i"}, &rnd, - 8, 100, 3); - } -} - -TEST_P(ComparatorDBTest, SimpleSuffixReverseComparator) { - SetOwnedComparator(new test::SimpleSuffixReverseComparator()); - - for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { - Options* opt = GetOptions(); - opt->comparator = kTestComparator; - DestroyAndReopen(); - Random rnd(rnd_seed); - - std::vector source_strings; - std::vector source_prefixes; - // Randomly generate 5 prefixes - for (int i = 0; i < 5; i++) { - source_prefixes.push_back(rnd.HumanReadableString(8)); - } - for (int j = 0; j < 20; j++) { - int prefix_index = rnd.Uniform(static_cast(source_prefixes.size())); - std::string key = source_prefixes[prefix_index] + - rnd.HumanReadableString(rnd.Uniform(8)); - source_strings.push_back(key); - } - - DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 30, 600, 66); - } -} - -TEST_P(ComparatorDBTest, Uint64Comparator) { - SetOwnedComparator(test::Uint64Comparator(), false /* owner */); - - for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { - Options* opt = GetOptions(); - opt->comparator = kTestComparator; - DestroyAndReopen(); - Random rnd(rnd_seed); - Random64 rnd64(rnd_seed); - - std::vector source_strings; - // Randomly generate source keys - for (int i = 0; i < 100; i++) { - uint64_t r = rnd64.Next(); - std::string str; - str.resize(8); - memcpy(&str[0], static_cast(&r), 8); - source_strings.push_back(str); - } - - DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); - } -} - -TEST_P(ComparatorDBTest, DoubleComparator) { - SetOwnedComparator(new DoubleComparator()); - - for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { - Options* opt = GetOptions(); - opt->comparator = kTestComparator; - DestroyAndReopen(); - Random rnd(rnd_seed); - - std::vector source_strings; - // Randomly generate source keys - for (int i = 0; i < 100; i++) { - uint32_t r = rnd.Next(); - uint32_t divide_order = rnd.Uniform(8); - double to_divide = 1.0; - for (uint32_t j = 0; j < divide_order; j++) { - to_divide *= 10.0; - } - source_strings.push_back(std::to_string(r / to_divide)); - } - - DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); - } -} - -TEST_P(ComparatorDBTest, HashComparator) { - SetOwnedComparator(new HashComparator()); - - for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { - Options* opt = GetOptions(); - opt->comparator = kTestComparator; - DestroyAndReopen(); - Random rnd(rnd_seed); - - std::vector source_strings; - // Randomly generate source keys - for (int i = 0; i < 100; i++) { - source_strings.push_back(test::RandomKey(&rnd, 8)); - } - - DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); - } -} - -TEST_P(ComparatorDBTest, TwoStrComparator) { - SetOwnedComparator(new TwoStrComparator()); - - for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { - Options* opt = GetOptions(); - opt->comparator = kTestComparator; - DestroyAndReopen(); - Random rnd(rnd_seed); - - std::vector source_strings; - // Randomly generate source keys - for (int i = 0; i < 100; i++) { - std::string str; - uint32_t size1 = rnd.Uniform(8); - uint32_t size2 = rnd.Uniform(8); - str.append(1, static_cast(size1)); - str.append(1, static_cast(size2)); - str.append(test::RandomKey(&rnd, size1)); - str.append(test::RandomKey(&rnd, size2)); - source_strings.push_back(str); - } - - DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); - } -} - -namespace { -void VerifyNotSuccessor(const Slice& s, const Slice& t) { - auto bc = BytewiseComparator(); - auto rbc = ReverseBytewiseComparator(); - ASSERT_FALSE(bc->IsSameLengthImmediateSuccessor(s, t)); - ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(s, t)); - ASSERT_FALSE(bc->IsSameLengthImmediateSuccessor(t, s)); - ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(t, s)); -} - -void VerifySuccessor(const Slice& s, const Slice& t) { - auto bc = BytewiseComparator(); - auto rbc = ReverseBytewiseComparator(); - ASSERT_TRUE(bc->IsSameLengthImmediateSuccessor(s, t)); - ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(s, t)); - ASSERT_FALSE(bc->IsSameLengthImmediateSuccessor(t, s)); - // Should be true but that increases exposure to a design bug in - // auto_prefix_mode, so currently set to FALSE - ASSERT_FALSE(rbc->IsSameLengthImmediateSuccessor(t, s)); -} - -} // anonymous namespace - -TEST_P(ComparatorDBTest, IsSameLengthImmediateSuccessor) { - { - // different length - Slice s("abcxy"); - Slice t("abcxyz"); - VerifyNotSuccessor(s, t); - } - { - Slice s("abcxyz"); - Slice t("abcxy"); - VerifyNotSuccessor(s, t); - } - { - // not last byte different - Slice s("abc1xyz"); - Slice t("abc2xyz"); - VerifyNotSuccessor(s, t); - } - { - // same string - Slice s("abcxyz"); - Slice t("abcxyz"); - VerifyNotSuccessor(s, t); - } - { - Slice s("abcxy"); - Slice t("abcxz"); - VerifySuccessor(s, t); - } - { - const char s_array[] = "\x50\x8a\xac"; - const char t_array[] = "\x50\x8a\xad"; - Slice s(s_array); - Slice t(t_array); - VerifySuccessor(s, t); - } - { - const char s_array[] = "\x50\x8a\xff"; - const char t_array[] = "\x50\x8b\x00"; - Slice s(s_array, 3); - Slice t(t_array, 3); - VerifySuccessor(s, t); - } - { - const char s_array[] = "\x50\x8a\xff\xff"; - const char t_array[] = "\x50\x8b\x00\x00"; - Slice s(s_array, 4); - Slice t(t_array, 4); - VerifySuccessor(s, t); - } - { - const char s_array[] = "\x50\x8a\xff\xff"; - const char t_array[] = "\x50\x8b\x00\x01"; - Slice s(s_array, 4); - Slice t(t_array, 4); - VerifyNotSuccessor(s, t); - } -} - -TEST_P(ComparatorDBTest, FindShortestSeparator) { - std::string s1 = "abc1xyz"; - std::string s2 = "abc3xy"; - - BytewiseComparator()->FindShortestSeparator(&s1, s2); - ASSERT_EQ("abc2", s1); - - s1 = "abc5xyztt"; - - ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); - ASSERT_EQ("abc5", s1); - - s1 = "abc3"; - s2 = "abc2xy"; - ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); - ASSERT_EQ("abc3", s1); - - s1 = "abc3xyz"; - s2 = "abc2xy"; - ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); - ASSERT_EQ("abc3", s1); - - s1 = "abc3xyz"; - s2 = "abc2"; - ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); - ASSERT_EQ("abc3", s1); - - std::string old_s1 = s1 = "abc2xy"; - s2 = "abc2"; - ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); - ASSERT_TRUE(old_s1 >= s1); - ASSERT_TRUE(s1 > s2); -} - -TEST_P(ComparatorDBTest, SeparatorSuccessorRandomizeTest) { - // Char list for boundary cases. - std::array char_list{{0, 1, 2, 253, 254, 255}}; - Random rnd(301); - - for (int attempts = 0; attempts < 1000; attempts++) { - uint32_t size1 = rnd.Skewed(4); - uint32_t size2; - - if (rnd.OneIn(2)) { - // size2 to be random size - size2 = rnd.Skewed(4); - } else { - // size1 is within [-2, +2] of size1 - int diff = static_cast(rnd.Uniform(5)) - 2; - int tmp_size2 = static_cast(size1) + diff; - if (tmp_size2 < 0) { - tmp_size2 = 0; - } - size2 = static_cast(tmp_size2); - } - - std::string s1; - std::string s2; - for (uint32_t i = 0; i < size1; i++) { - if (rnd.OneIn(2)) { - // Use random byte - s1 += static_cast(rnd.Uniform(256)); - } else { - // Use one byte in char_list - char c = static_cast(char_list[rnd.Uniform(sizeof(char_list))]); - s1 += c; - } - } - - // First set s2 to be the same as s1, and then modify s2. - s2 = s1; - s2.resize(size2); - // We start from the back of the string - if (size2 > 0) { - uint32_t pos = size2 - 1; - do { - if (pos >= size1 || rnd.OneIn(4)) { - // For 1/4 chance, use random byte - s2[pos] = static_cast(rnd.Uniform(256)); - } else if (rnd.OneIn(4)) { - // In 1/4 chance, stop here. - break; - } else { - // Create a char within [-2, +2] of the matching char of s1. - int diff = static_cast(rnd.Uniform(5)) - 2; - // char may be signed or unsigned based on platform. - int s1_char = static_cast(static_cast(s1[pos])); - int s2_char = s1_char + diff; - if (s2_char < 0) { - s2_char = 0; - } - if (s2_char > 255) { - s2_char = 255; - } - s2[pos] = static_cast(s2_char); - } - } while (pos-- != 0); - } - - // Test separators - for (int rev = 0; rev < 2; rev++) { - if (rev == 1) { - // switch s1 and s2 - std::string t = s1; - s1 = s2; - s2 = t; - } - std::string separator = s1; - BytewiseComparator()->FindShortestSeparator(&separator, s2); - std::string rev_separator = s1; - ReverseBytewiseComparator()->FindShortestSeparator(&rev_separator, s2); - - if (s1 == s2) { - ASSERT_EQ(s1, separator); - ASSERT_EQ(s2, rev_separator); - } else if (s1 < s2) { - ASSERT_TRUE(s1 <= separator); - ASSERT_TRUE(s2 > separator); - ASSERT_LE(separator.size(), std::max(s1.size(), s2.size())); - ASSERT_EQ(s1, rev_separator); - } else { - ASSERT_TRUE(s1 >= rev_separator); - ASSERT_TRUE(s2 < rev_separator); - ASSERT_LE(rev_separator.size(), std::max(s1.size(), s2.size())); - ASSERT_EQ(s1, separator); - } - } - - // Test successors - std::string succ = s1; - BytewiseComparator()->FindShortSuccessor(&succ); - ASSERT_TRUE(succ >= s1); - - succ = s1; - ReverseBytewiseComparator()->FindShortSuccessor(&succ); - ASSERT_TRUE(succ <= s1); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/corruption_test.cc b/db/corruption_test.cc deleted file mode 100644 index ab506cdb7..000000000 --- a/db/corruption_test.cc +++ /dev/null @@ -1,1669 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/options.h" - -#include -#include -#include - -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/log_format.h" -#include "db/version_set.h" -#include "file/filename.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/table.h" -#include "rocksdb/utilities/transaction_db.h" -#include "rocksdb/write_batch.h" -#include "table/block_based/block_based_table_builder.h" -#include "table/meta_blocks.h" -#include "table/mock_table.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/cast_util.h" -#include "util/random.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -static constexpr int kValueSize = 1000; -namespace { -// A wrapper that allows injection of errors. -class ErrorFS : public FileSystemWrapper { - public: - bool writable_file_error_; - int num_writable_file_errors_; - - explicit ErrorFS(const std::shared_ptr& _target) - : FileSystemWrapper(_target), - writable_file_error_(false), - num_writable_file_errors_(0) {} - const char* Name() const override { return "ErrorEnv"; } - - virtual IOStatus NewWritableFile(const std::string& fname, - const FileOptions& opts, - std::unique_ptr* result, - IODebugContext* dbg) override { - result->reset(); - if (writable_file_error_) { - ++num_writable_file_errors_; - return IOStatus::IOError(fname, "fake error"); - } - return target()->NewWritableFile(fname, opts, result, dbg); - } -}; -} // anonymous namespace -class CorruptionTest : public testing::Test { - public: - std::shared_ptr env_guard_; - std::shared_ptr fs_; - std::unique_ptr env_; - Env* base_env_; - std::string dbname_; - std::shared_ptr tiny_cache_; - Options options_; - DB* db_; - - CorruptionTest() { - // If LRU cache shard bit is smaller than 2 (or -1 which will automatically - // set it to 0), test SequenceNumberRecovery will fail, likely because of a - // bug in recovery code. Keep it 4 for now to make the test passes. - tiny_cache_ = NewLRUCache(100, 4); - base_env_ = Env::Default(); - EXPECT_OK( - test::CreateEnvFromSystem(ConfigOptions(), &base_env_, &env_guard_)); - EXPECT_NE(base_env_, nullptr); - fs_.reset(new ErrorFS(base_env_->GetFileSystem())); - env_ = NewCompositeEnv(fs_); - options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords; - options_.env = env_.get(); - dbname_ = test::PerThreadDBPath(env_.get(), "corruption_test"); - Status s = DestroyDB(dbname_, options_); - EXPECT_OK(s); - - db_ = nullptr; - options_.create_if_missing = true; - BlockBasedTableOptions table_options; - table_options.block_size_deviation = 0; // make unit test pass for now - options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(); - options_.create_if_missing = false; - } - - ~CorruptionTest() override { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({}); - SyncPoint::GetInstance()->ClearAllCallBacks(); - delete db_; - db_ = nullptr; - if (getenv("KEEP_DB")) { - fprintf(stdout, "db is still at %s\n", dbname_.c_str()); - } else { - Options opts; - opts.env = base_env_; - EXPECT_OK(DestroyDB(dbname_, opts)); - } - } - - void CloseDb() { - delete db_; - db_ = nullptr; - } - - Status TryReopen(Options* options = nullptr) { - delete db_; - db_ = nullptr; - Options opt = (options ? *options : options_); - if (opt.env == Options().env) { - // If env is not overridden, replace it with ErrorEnv. - // Otherwise, the test already uses a non-default Env. - opt.env = env_.get(); - } - opt.arena_block_size = 4096; - BlockBasedTableOptions table_options; - table_options.block_cache = tiny_cache_; - table_options.block_size_deviation = 0; - opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); - return DB::Open(opt, dbname_, &db_); - } - - void Reopen(Options* options = nullptr) { ASSERT_OK(TryReopen(options)); } - - void RepairDB() { - delete db_; - db_ = nullptr; - ASSERT_OK(::ROCKSDB_NAMESPACE::RepairDB(dbname_, options_)); - } - - void Build(int n, int start, int flush_every) { - std::string key_space, value_space; - WriteBatch batch; - for (int i = 0; i < n; i++) { - if (flush_every != 0 && i != 0 && i % flush_every == 0) { - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - } - // if ((i % 100) == 0) fprintf(stderr, "@ %d of %d\n", i, n); - Slice key = Key(i + start, &key_space); - batch.Clear(); - ASSERT_OK(batch.Put(key, Value(i + start, &value_space))); - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - } - } - - void Build(int n, int flush_every = 0) { Build(n, 0, flush_every); } - - void Check(int min_expected, int max_expected) { - Check(min_expected, max_expected, ReadOptions(false, true)); - } - - void Check(int min_expected, int max_expected, ReadOptions read_options) { - uint64_t next_expected = 0; - uint64_t missed = 0; - int bad_keys = 0; - int bad_values = 0; - int correct = 0; - std::string value_space; - // Do not verify checksums. If we verify checksums then the - // db itself will raise errors because data is corrupted. - // Instead, we want the reads to be successful and this test - // will detect whether the appropriate corruptions have - // occurred. - Iterator* iter = db_->NewIterator(read_options); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - uint64_t key; - Slice in(iter->key()); - if (!ConsumeDecimalNumber(&in, &key) || !in.empty() || - key < next_expected) { - bad_keys++; - continue; - } - missed += (key - next_expected); - next_expected = key + 1; - if (iter->value() != Value(static_cast(key), &value_space)) { - bad_values++; - } else { - correct++; - } - } - iter->status().PermitUncheckedError(); - delete iter; - - fprintf( - stderr, - "expected=%d..%d; got=%d; bad_keys=%d; bad_values=%d; missed=%llu\n", - min_expected, max_expected, correct, bad_keys, bad_values, - static_cast(missed)); - ASSERT_LE(min_expected, correct); - ASSERT_GE(max_expected, correct); - } - - void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) { - // Pick file to corrupt - std::vector filenames; - ASSERT_OK(env_->GetChildren(dbname_, &filenames)); - uint64_t number; - FileType type; - std::string fname; - int picked_number = -1; - for (size_t i = 0; i < filenames.size(); i++) { - if (ParseFileName(filenames[i], &number, &type) && type == filetype && - static_cast(number) > picked_number) { // Pick latest file - fname = dbname_ + "/" + filenames[i]; - picked_number = static_cast(number); - } - } - ASSERT_TRUE(!fname.empty()) << filetype; - - ASSERT_OK(test::CorruptFile(env_.get(), fname, offset, bytes_to_corrupt, - /*verify_checksum*/ filetype == kTableFile)); - } - - // corrupts exactly one file at level `level`. if no file found at level, - // asserts - void CorruptTableFileAtLevel(int level, int offset, int bytes_to_corrupt) { - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - for (const auto& m : metadata) { - if (m.level == level) { - ASSERT_OK(test::CorruptFile(env_.get(), dbname_ + "/" + m.name, offset, - bytes_to_corrupt)); - return; - } - } - FAIL() << "no file found at level"; - } - - int Property(const std::string& name) { - std::string property; - int result; - if (db_->GetProperty(name, &property) && - sscanf(property.c_str(), "%d", &result) == 1) { - return result; - } else { - return -1; - } - } - - // Return the ith key - Slice Key(int i, std::string* storage) { - char buf[100]; - snprintf(buf, sizeof(buf), "%016d", i); - storage->assign(buf, strlen(buf)); - return Slice(*storage); - } - - // Return the value to associate with the specified key - Slice Value(int k, std::string* storage) { - if (k == 0) { - // Ugh. Random seed of 0 used to produce no entropy. This code - // preserves the implementation that was in place when all of the - // magic values in this file were picked. - *storage = std::string(kValueSize, ' '); - } else { - Random r(k); - *storage = r.RandomString(kValueSize); - } - return Slice(*storage); - } - - void GetSortedWalFiles(std::vector& file_nums) { - std::vector tmp_files; - ASSERT_OK(env_->GetChildren(dbname_, &tmp_files)); - FileType type = kWalFile; - for (const auto& file : tmp_files) { - uint64_t number = 0; - if (ParseFileName(file, &number, &type) && type == kWalFile) { - file_nums.push_back(number); - } - } - std::sort(file_nums.begin(), file_nums.end()); - } - - void CorruptFileWithTruncation(FileType file, uint64_t number, - uint64_t bytes_to_truncate = 0) { - std::string path; - switch (file) { - case FileType::kWalFile: - path = LogFileName(dbname_, number); - break; - // TODO: Add other file types as this method is being used for those file - // types. - default: - return; - } - uint64_t old_size = 0; - ASSERT_OK(env_->GetFileSize(path, &old_size)); - assert(old_size > bytes_to_truncate); - uint64_t new_size = old_size - bytes_to_truncate; - // If bytes_to_truncate == 0, it will do full truncation. - if (bytes_to_truncate == 0) { - new_size = 0; - } - ASSERT_OK(test::TruncateFile(env_.get(), path, new_size)); - } -}; - -TEST_F(CorruptionTest, Recovery) { - Build(100); - Check(100, 100); -#ifdef OS_WIN - // On Wndows OS Disk cache does not behave properly - // We do not call FlushBuffers on every Flush. If we do not close - // the log file prior to the corruption we end up with the first - // block not corrupted but only the second. However, under the debugger - // things work just fine but never pass when running normally - // For that reason people may want to run with unbuffered I/O. That option - // is not available for WAL though. - CloseDb(); -#endif - Corrupt(kWalFile, 19, 1); // WriteBatch tag for first record - Corrupt(kWalFile, log::kBlockSize + 1000, 1); // Somewhere in second block - ASSERT_TRUE(!TryReopen().ok()); - options_.paranoid_checks = false; - Reopen(&options_); - - // The 64 records in the first two log blocks are completely lost. - Check(36, 36); -} - -TEST_F(CorruptionTest, PostPITRCorruptionWALsRetained) { - // Repro for bug where WALs following the point-in-time recovery were not - // retained leading to the next recovery failing. - CloseDb(); - - options_.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - - const std::string test_cf_name = "test_cf"; - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions()); - cf_descs.emplace_back(test_cf_name, ColumnFamilyOptions()); - - uint64_t log_num; - { - options_.create_missing_column_families = true; - std::vector cfhs; - ASSERT_OK(DB::Open(options_, dbname_, cf_descs, &cfhs, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - - ASSERT_OK(db_->Put(WriteOptions(), cfhs[0], "k", "v")); - ASSERT_OK(db_->Put(WriteOptions(), cfhs[1], "k", "v")); - ASSERT_OK(db_->Put(WriteOptions(), cfhs[0], "k2", "v2")); - std::vector file_nums; - GetSortedWalFiles(file_nums); - log_num = file_nums.back(); - for (auto* cfh : cfhs) { - delete cfh; - } - CloseDb(); - } - - CorruptFileWithTruncation(FileType::kWalFile, log_num, - /*bytes_to_truncate=*/1); - - { - // Recover "k" -> "v" for both CFs. "k2" -> "v2" is lost due to truncation. - options_.avoid_flush_during_recovery = true; - std::vector cfhs; - ASSERT_OK(DB::Open(options_, dbname_, cf_descs, &cfhs, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - - // Flush one but not both CFs and write some data so there's a seqno gap - // between the PITR corruption and the next DB session's first WAL. - ASSERT_OK(db_->Put(WriteOptions(), cfhs[1], "k2", "v2")); - ASSERT_OK(db_->Flush(FlushOptions(), cfhs[1])); - - for (auto* cfh : cfhs) { - delete cfh; - } - CloseDb(); - } - - // With the bug, this DB open would remove the WALs following the PITR - // corruption. Then, the next recovery would fail. - for (int i = 0; i < 2; ++i) { - std::vector cfhs; - ASSERT_OK(DB::Open(options_, dbname_, cf_descs, &cfhs, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - - for (auto* cfh : cfhs) { - delete cfh; - } - CloseDb(); - } -} - -TEST_F(CorruptionTest, RecoverWriteError) { - fs_->writable_file_error_ = true; - Status s = TryReopen(); - ASSERT_TRUE(!s.ok()); -} - -TEST_F(CorruptionTest, NewFileErrorDuringWrite) { - // Do enough writing to force minor compaction - fs_->writable_file_error_ = true; - const int num = - static_cast(3 + (Options().write_buffer_size / kValueSize)); - std::string value_storage; - Status s; - bool failed = false; - for (int i = 0; i < num; i++) { - WriteBatch batch; - ASSERT_OK(batch.Put("a", Value(100, &value_storage))); - s = db_->Write(WriteOptions(), &batch); - if (!s.ok()) { - failed = true; - } - ASSERT_TRUE(!failed || !s.ok()); - } - ASSERT_TRUE(!s.ok()); - ASSERT_GE(fs_->num_writable_file_errors_, 1); - fs_->writable_file_error_ = false; - Reopen(); -} - -TEST_F(CorruptionTest, TableFile) { - Build(100); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_OK(dbi->TEST_CompactRange(1, nullptr, nullptr)); - - Corrupt(kTableFile, 100, 1); - Check(99, 99); - ASSERT_NOK(dbi->VerifyChecksum()); -} - -TEST_F(CorruptionTest, VerifyChecksumReadahead) { - Options options; - SpecialEnv senv(base_env_); - options.env = &senv; - // Disable block cache as we are going to check checksum for - // the same file twice and measure number of reads. - BlockBasedTableOptions table_options_no_bc; - table_options_no_bc.no_block_cache = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options_no_bc)); - - Reopen(&options); - - Build(10000); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_OK(dbi->TEST_CompactRange(1, nullptr, nullptr)); - - senv.count_random_reads_ = true; - senv.random_read_counter_.Reset(); - ASSERT_OK(dbi->VerifyChecksum()); - - // Make sure the counter is enabled. - ASSERT_GT(senv.random_read_counter_.Read(), 0); - - // The SST file is about 10MB. Default readahead size is 256KB. - // Give a conservative 20 reads for metadata blocks, The number - // of random reads should be within 10 MB / 256KB + 20 = 60. - ASSERT_LT(senv.random_read_counter_.Read(), 60); - - senv.random_read_bytes_counter_ = 0; - ReadOptions ro; - ro.readahead_size = size_t{32 * 1024}; - ASSERT_OK(dbi->VerifyChecksum(ro)); - // The SST file is about 10MB. We set readahead size to 32KB. - // Give 0 to 20 reads for metadata blocks, and allow real read - // to range from 24KB to 48KB. The lower bound would be: - // 10MB / 48KB + 0 = 213 - // The higher bound is - // 10MB / 24KB + 20 = 447. - ASSERT_GE(senv.random_read_counter_.Read(), 213); - ASSERT_LE(senv.random_read_counter_.Read(), 447); - - // Test readahead shouldn't break mmap mode (where it should be - // disabled). - options.allow_mmap_reads = true; - Reopen(&options); - dbi = static_cast(db_); - ASSERT_OK(dbi->VerifyChecksum(ro)); - - CloseDb(); -} - -TEST_F(CorruptionTest, TableFileIndexData) { - Options options; - // very big, we'll trigger flushes manually - options.write_buffer_size = 100 * 1024 * 1024; - Reopen(&options); - // build 2 tables, flush at 5000 - Build(10000, 5000); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - - // corrupt an index block of an entire file - Corrupt(kTableFile, -2000, 500); - options.paranoid_checks = false; - Reopen(&options); - dbi = static_cast_with_check(db_); - // one full file may be readable, since only one was corrupted - // the other file should be fully non-readable, since index was corrupted - Check(0, 5000, ReadOptions(true, true)); - ASSERT_NOK(dbi->VerifyChecksum()); - - // In paranoid mode, the db cannot be opened due to the corrupted file. - ASSERT_TRUE(TryReopen().IsCorruption()); -} - -TEST_F(CorruptionTest, TableFileFooterMagic) { - Build(100); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - Check(100, 100); - // Corrupt the whole footer - Corrupt(kTableFile, -100, 100); - Status s = TryReopen(); - ASSERT_TRUE(s.IsCorruption()); - // Contains useful message, and magic number should be the first thing - // reported as corrupt. - ASSERT_TRUE(s.ToString().find("magic number") != std::string::npos); - // with file name - ASSERT_TRUE(s.ToString().find(".sst") != std::string::npos); -} - -TEST_F(CorruptionTest, TableFileFooterNotMagic) { - Build(100); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - Check(100, 100); - // Corrupt footer except magic number - Corrupt(kTableFile, -100, 92); - Status s = TryReopen(); - ASSERT_TRUE(s.IsCorruption()); - // The next thing checked after magic number is format_version - ASSERT_TRUE(s.ToString().find("format_version") != std::string::npos); - // with file name - ASSERT_TRUE(s.ToString().find(".sst") != std::string::npos); -} - -TEST_F(CorruptionTest, TableFileWrongSize) { - Build(100); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - Check(100, 100); - - // ******************************************** - // Make the file bigger by appending to it - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(1U, metadata.size()); - std::string filename = dbname_ + metadata[0].name; - const auto& fs = options_.env->GetFileSystem(); - { - std::unique_ptr f; - ASSERT_OK(fs->ReopenWritableFile(filename, FileOptions(), &f, nullptr)); - ASSERT_OK(f->Append("blahblah", IOOptions(), nullptr)); - ASSERT_OK(f->Close(IOOptions(), nullptr)); - } - - // DB actually accepts this without paranoid checks, relying on size - // recorded in manifest to locate the SST footer. - options_.paranoid_checks = false; - options_.skip_checking_sst_file_sizes_on_db_open = false; - Reopen(); - Check(100, 100); - - // But reports the issue with paranoid checks - options_.paranoid_checks = true; - Status s = TryReopen(); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("file size mismatch") != std::string::npos); - - // ******************************************** - // Make the file smaller with truncation. - // First leaving a partial footer, and then completely removing footer. - for (size_t bytes_lost : {8, 100}) { - ASSERT_OK(test::TruncateFile(env_.get(), filename, - metadata[0].size - bytes_lost)); - - // Reported well with paranoid checks - options_.paranoid_checks = true; - s = TryReopen(); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("file size mismatch") != std::string::npos); - - // Without paranoid checks, not reported until read - options_.paranoid_checks = false; - Reopen(); - Check(0, 0); // Missing data - } -} - -TEST_F(CorruptionTest, MissingDescriptor) { - Build(1000); - RepairDB(); - Reopen(); - Check(1000, 1000); -} - -TEST_F(CorruptionTest, SequenceNumberRecovery) { - ASSERT_OK(db_->Put(WriteOptions(), "foo", "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "foo", "v2")); - ASSERT_OK(db_->Put(WriteOptions(), "foo", "v3")); - ASSERT_OK(db_->Put(WriteOptions(), "foo", "v4")); - ASSERT_OK(db_->Put(WriteOptions(), "foo", "v5")); - RepairDB(); - Reopen(); - std::string v; - ASSERT_OK(db_->Get(ReadOptions(), "foo", &v)); - ASSERT_EQ("v5", v); - // Write something. If sequence number was not recovered properly, - // it will be hidden by an earlier write. - ASSERT_OK(db_->Put(WriteOptions(), "foo", "v6")); - ASSERT_OK(db_->Get(ReadOptions(), "foo", &v)); - ASSERT_EQ("v6", v); - Reopen(); - ASSERT_OK(db_->Get(ReadOptions(), "foo", &v)); - ASSERT_EQ("v6", v); -} - -TEST_F(CorruptionTest, CorruptedDescriptor) { - ASSERT_OK(db_->Put(WriteOptions(), "foo", "hello")); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK( - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr)); - - Corrupt(kDescriptorFile, 0, 1000); - Status s = TryReopen(); - ASSERT_TRUE(!s.ok()); - - RepairDB(); - Reopen(); - std::string v; - ASSERT_OK(db_->Get(ReadOptions(), "foo", &v)); - ASSERT_EQ("hello", v); -} - -TEST_F(CorruptionTest, CompactionInputError) { - Options options; - options.env = env_.get(); - Reopen(&options); - Build(10); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_OK(dbi->TEST_CompactRange(1, nullptr, nullptr)); - ASSERT_EQ(1, Property("rocksdb.num-files-at-level2")); - - Corrupt(kTableFile, 100, 1); - Check(9, 9); - ASSERT_NOK(dbi->VerifyChecksum()); - - // Force compactions by writing lots of values - Build(10000); - Check(10000, 10000); - ASSERT_NOK(dbi->VerifyChecksum()); -} - -TEST_F(CorruptionTest, CompactionInputErrorParanoid) { - Options options; - options.env = env_.get(); - options.paranoid_checks = true; - options.write_buffer_size = 131072; - options.max_write_buffer_number = 2; - Reopen(&options); - DBImpl* dbi = static_cast_with_check(db_); - - // Fill levels >= 1 - for (int level = 1; level < dbi->NumberLevels(); level++) { - ASSERT_OK(dbi->Put(WriteOptions(), "", "begin")); - ASSERT_OK(dbi->Put(WriteOptions(), "~", "end")); - ASSERT_OK(dbi->TEST_FlushMemTable()); - for (int comp_level = 0; comp_level < dbi->NumberLevels() - level; - ++comp_level) { - ASSERT_OK(dbi->TEST_CompactRange(comp_level, nullptr, nullptr)); - } - } - - Reopen(&options); - - dbi = static_cast_with_check(db_); - Build(10); - ASSERT_OK(dbi->TEST_FlushMemTable()); - ASSERT_OK(dbi->TEST_WaitForCompact()); - ASSERT_EQ(1, Property("rocksdb.num-files-at-level0")); - - CorruptTableFileAtLevel(0, 100, 1); - Check(9, 9); - ASSERT_NOK(dbi->VerifyChecksum()); - - // Write must eventually fail because of corrupted table - Status s; - std::string tmp1, tmp2; - bool failed = false; - for (int i = 0; i < 10000; i++) { - s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2)); - if (!s.ok()) { - failed = true; - } - // if one write failed, every subsequent write must fail, too - ASSERT_TRUE(!failed || !s.ok()) << "write did not fail in a corrupted db"; - } - ASSERT_TRUE(!s.ok()) << "write did not fail in corrupted paranoid db"; -} - -TEST_F(CorruptionTest, UnrelatedKeys) { - Build(10); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - Corrupt(kTableFile, 100, 1); - ASSERT_NOK(dbi->VerifyChecksum()); - - std::string tmp1, tmp2; - ASSERT_OK(db_->Put(WriteOptions(), Key(1000, &tmp1), Value(1000, &tmp2))); - std::string v; - ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v)); - ASSERT_EQ(Value(1000, &tmp2).ToString(), v); - ASSERT_OK(dbi->TEST_FlushMemTable()); - ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v)); - ASSERT_EQ(Value(1000, &tmp2).ToString(), v); -} - -TEST_F(CorruptionTest, RangeDeletionCorrupted) { - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b")); - ASSERT_OK(db_->Flush(FlushOptions())); - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(static_cast(1), metadata.size()); - std::string filename = dbname_ + metadata[0].name; - - FileOptions file_opts; - const auto& fs = options_.env->GetFileSystem(); - std::unique_ptr file_reader; - ASSERT_OK(RandomAccessFileReader::Create(fs, filename, file_opts, - &file_reader, nullptr)); - - uint64_t file_size; - ASSERT_OK( - fs->GetFileSize(filename, file_opts.io_options, &file_size, nullptr)); - - BlockHandle range_del_handle; - ASSERT_OK(FindMetaBlockInFile( - file_reader.get(), file_size, kBlockBasedTableMagicNumber, - ImmutableOptions(options_), kRangeDelBlockName, &range_del_handle)); - - ASSERT_OK(TryReopen()); - ASSERT_OK(test::CorruptFile(env_.get(), filename, - static_cast(range_del_handle.offset()), 1)); - ASSERT_TRUE(TryReopen().IsCorruption()); -} - -TEST_F(CorruptionTest, FileSystemStateCorrupted) { - for (int iter = 0; iter < 2; ++iter) { - Options options; - options.env = env_.get(); - options.paranoid_checks = true; - options.create_if_missing = true; - Reopen(&options); - Build(10); - ASSERT_OK(db_->Flush(FlushOptions())); - DBImpl* dbi = static_cast_with_check(db_); - std::vector metadata; - dbi->GetLiveFilesMetaData(&metadata); - ASSERT_GT(metadata.size(), 0); - std::string filename = dbname_ + metadata[0].name; - - delete db_; - db_ = nullptr; - - if (iter == 0) { // corrupt file size - std::unique_ptr file; - ASSERT_OK(env_->NewWritableFile(filename, &file, EnvOptions())); - ASSERT_OK(file->Append(Slice("corrupted sst"))); - file.reset(); - Status x = TryReopen(&options); - ASSERT_TRUE(x.IsCorruption()); - } else { // delete the file - ASSERT_OK(env_->DeleteFile(filename)); - Status x = TryReopen(&options); - ASSERT_TRUE(x.IsCorruption()); - } - - ASSERT_OK(DestroyDB(dbname_, options_)); - } -} - -static const auto& corruption_modes = { - mock::MockTableFactory::kCorruptNone, mock::MockTableFactory::kCorruptKey, - mock::MockTableFactory::kCorruptValue, - mock::MockTableFactory::kCorruptReorderKey}; - -TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) { - Options options; - options.env = env_.get(); - options.check_flush_compaction_key_order = false; - options.paranoid_file_checks = true; - options.create_if_missing = true; - Status s; - for (const auto& mode : corruption_modes) { - delete db_; - db_ = nullptr; - s = DestroyDB(dbname_, options); - ASSERT_OK(s); - std::shared_ptr mock = - std::make_shared(); - options.table_factory = mock; - mock->SetCorruptionMode(mode); - ASSERT_OK(DB::Open(options, dbname_, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - Build(10); - s = db_->Flush(FlushOptions()); - if (mode == mock::MockTableFactory::kCorruptNone) { - ASSERT_OK(s); - } else { - ASSERT_NOK(s); - } - } -} - -TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) { - Options options; - options.env = env_.get(); - options.paranoid_file_checks = true; - options.create_if_missing = true; - options.check_flush_compaction_key_order = false; - Status s; - for (const auto& mode : corruption_modes) { - delete db_; - db_ = nullptr; - s = DestroyDB(dbname_, options); - ASSERT_OK(s); - std::shared_ptr mock = - std::make_shared(); - options.table_factory = mock; - ASSERT_OK(DB::Open(options, dbname_, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - Build(100, 2); - // ASSERT_OK(db_->Flush(FlushOptions())); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - mock->SetCorruptionMode(mode); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - s = dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr); - if (mode == mock::MockTableFactory::kCorruptNone) { - ASSERT_OK(s); - } else { - ASSERT_NOK(s); - } - } -} - -TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) { - Options options; - options.env = env_.get(); - options.check_flush_compaction_key_order = false; - options.paranoid_file_checks = true; - options.create_if_missing = true; - for (bool do_flush : {true, false}) { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, options)); - ASSERT_OK(DB::Open(options, dbname_, &db_)); - std::string start, end; - assert(db_ != nullptr); // suppress false clang-analyze report - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(3, &start), Key(7, &end))); - auto snap = db_->GetSnapshot(); - ASSERT_NE(snap, nullptr); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(8, &start), Key(9, &end))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(2, &start), Key(5, &end))); - Build(10); - if (do_flush) { - ASSERT_OK(db_->Flush(FlushOptions())); - } else { - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK( - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr)); - } - db_->ReleaseSnapshot(snap); - } -} - -TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) { - Options options; - options.env = env_.get(); - options.check_flush_compaction_key_order = false; - options.paranoid_file_checks = true; - options.create_if_missing = true; - for (bool do_flush : {true, false}) { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, options)); - ASSERT_OK(DB::Open(options, dbname_, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - Build(10, 0, 0); - std::string start, end; - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(5, &start), Key(15, &end))); - auto snap = db_->GetSnapshot(); - ASSERT_NE(snap, nullptr); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(8, &start), Key(9, &end))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(12, &start), Key(17, &end))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(2, &start), Key(4, &end))); - Build(10, 10, 0); - if (do_flush) { - ASSERT_OK(db_->Flush(FlushOptions())); - } else { - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK( - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr)); - } - db_->ReleaseSnapshot(snap); - } -} - -TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) { - Options options; - options.env = env_.get(); - options.check_flush_compaction_key_order = false; - options.paranoid_file_checks = true; - options.create_if_missing = true; - for (bool do_flush : {true, false}) { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, options)); - ASSERT_OK(DB::Open(options, dbname_, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - std::string start, end; - Build(10); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(3, &start), Key(7, &end))); - auto snap = db_->GetSnapshot(); - ASSERT_NE(snap, nullptr); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(6, &start), Key(8, &end))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(2, &start), Key(5, &end))); - if (do_flush) { - ASSERT_OK(db_->Flush(FlushOptions())); - } else { - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK( - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr)); - } - db_->ReleaseSnapshot(snap); - } -} - -TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) { - Options options; - options.env = env_.get(); - options.create_if_missing = true; - options.allow_data_in_errors = true; - auto mode = mock::MockTableFactory::kCorruptKey; - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, options)); - - std::shared_ptr mock = - std::make_shared(); - mock->SetCorruptionMode(mode); - options.table_factory = mock; - - ASSERT_OK(DB::Open(options, dbname_, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - Build(100, 2); - - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - Status s = - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsCorruption()); -} - -TEST_F(CorruptionTest, CompactionKeyOrderCheck) { - Options options; - options.env = env_.get(); - options.paranoid_file_checks = false; - options.create_if_missing = true; - options.check_flush_compaction_key_order = false; - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, options)); - std::shared_ptr mock = - std::make_shared(); - options.table_factory = mock; - ASSERT_OK(DB::Open(options, dbname_, &db_)); - assert(db_ != nullptr); // suppress false clang-analyze report - mock->SetCorruptionMode(mock::MockTableFactory::kCorruptReorderKey); - Build(100, 2); - DBImpl* dbi = static_cast_with_check(db_); - ASSERT_OK(dbi->TEST_FlushMemTable()); - - mock->SetCorruptionMode(mock::MockTableFactory::kCorruptNone); - ASSERT_OK(db_->SetOptions({{"check_flush_compaction_key_order", "true"}})); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_NOK( - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr)); -} - -TEST_F(CorruptionTest, FlushKeyOrderCheck) { - Options options; - options.env = env_.get(); - options.paranoid_file_checks = false; - options.create_if_missing = true; - ASSERT_OK(db_->SetOptions({{"check_flush_compaction_key_order", "true"}})); - - ASSERT_OK(db_->Put(WriteOptions(), "foo1", "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "foo2", "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "foo3", "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "foo4", "v1")); - - int cnt = 0; - // Generate some out of order keys from the memtable - SyncPoint::GetInstance()->SetCallBack( - "MemTableIterator::Next:0", [&](void* arg) { - MemTableRep::Iterator* mem_iter = - static_cast(arg); - if (++cnt == 3) { - mem_iter->Prev(); - mem_iter->Prev(); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - Status s = static_cast_with_check(db_)->TEST_FlushMemTable(); - ASSERT_NOK(s); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(CorruptionTest, DisableKeyOrderCheck) { - ASSERT_OK(db_->SetOptions({{"check_flush_compaction_key_order", "false"}})); - DBImpl* dbi = static_cast_with_check(db_); - - SyncPoint::GetInstance()->SetCallBack( - "OutputValidator::Add:order_check", - [&](void* /*arg*/) { ASSERT_TRUE(false); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(db_->Put(WriteOptions(), "foo1", "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "foo3", "v1")); - ASSERT_OK(dbi->TEST_FlushMemTable()); - ASSERT_OK(db_->Put(WriteOptions(), "foo2", "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "foo4", "v1")); - ASSERT_OK(dbi->TEST_FlushMemTable()); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK( - dbi->CompactRange(cro, dbi->DefaultColumnFamily(), nullptr, nullptr)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(CorruptionTest, VerifyWholeTableChecksum) { - CloseDb(); - Options options; - options.env = env_.get(); - ASSERT_OK(DestroyDB(dbname_, options)); - options.create_if_missing = true; - options.file_checksum_gen_factory = - ROCKSDB_NAMESPACE::GetFileChecksumGenCrc32cFactory(); - Reopen(&options); - - Build(10, 5); - - ASSERT_OK(db_->VerifyFileChecksums(ReadOptions())); - CloseDb(); - - // Corrupt the first byte of each table file, this must be data block. - Corrupt(kTableFile, 0, 1); - - ASSERT_OK(TryReopen(&options)); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - int count{0}; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::VerifyFullFileChecksum:mismatch", [&](void* arg) { - auto* s = reinterpret_cast(arg); - ASSERT_NE(s, nullptr); - ++count; - ASSERT_NOK(*s); - }); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsCorruption()); - ASSERT_EQ(1, count); -} - -class CrashDuringRecoveryWithCorruptionTest - : public CorruptionTest, - public testing::WithParamInterface> { - public: - explicit CrashDuringRecoveryWithCorruptionTest() - : CorruptionTest(), - avoid_flush_during_recovery_(std::get<0>(GetParam())), - track_and_verify_wals_in_manifest_(std::get<1>(GetParam())) {} - - protected: - const bool avoid_flush_during_recovery_; - const bool track_and_verify_wals_in_manifest_; -}; - -INSTANTIATE_TEST_CASE_P(CorruptionTest, CrashDuringRecoveryWithCorruptionTest, - ::testing::Values(std::make_tuple(true, false), - std::make_tuple(false, false), - std::make_tuple(true, true), - std::make_tuple(false, true))); - -// In case of non-TransactionDB with avoid_flush_during_recovery = true, RocksDB -// won't flush the data from WAL to L0 for all column families if possible. As a -// result, not all column families can increase their log_numbers, and -// min_log_number_to_keep won't change. -// It may prematurely persist a new MANIFEST even before we can declare the DB -// is in consistent state after recovery (this is when the new WAL is synced) -// and advances log_numbers for some column families. -// -// If there is power failure before we sync the new WAL, we will end up in -// a situation in which after persisting the MANIFEST, RocksDB will see some -// column families' log_numbers larger than the corrupted wal, and -// "Column family inconsistency: SST file contains data beyond the point of -// corruption" error will be hit, causing recovery to fail. -// -// After adding the fix, only after new WAL is synced, RocksDB persist a new -// MANIFEST with column families to ensure RocksDB is in consistent state. -// RocksDB writes an empty WriteBatch as a sentinel to the new WAL which is -// synced immediately afterwards. The sequence number of the sentinel -// WriteBatch will be the next sequence number immediately after the largest -// sequence number recovered from previous WALs and MANIFEST because of which DB -// will be in consistent state. -// If a future recovery starts from the new MANIFEST, then it means the new WAL -// is successfully synced. Due to the sentinel empty write batch at the -// beginning, kPointInTimeRecovery of WAL is guaranteed to go after this point. -// If future recovery starts from the old MANIFEST, it means the writing the new -// MANIFEST failed. It won't have the "SST ahead of WAL" error. -// -// The combination of corrupting a WAL and injecting an error during subsequent -// re-open exposes the bug of prematurely persisting a new MANIFEST with -// advanced ColumnFamilyData::log_number. -TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecovery) { - CloseDb(); - Options options; - options.track_and_verify_wals_in_manifest = - track_and_verify_wals_in_manifest_; - options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - options.avoid_flush_during_recovery = false; - options.env = env_.get(); - ASSERT_OK(DestroyDB(dbname_, options)); - options.create_if_missing = true; - options.max_write_buffer_number = 8; - - Reopen(&options); - Status s; - const std::string test_cf_name = "test_cf"; - ColumnFamilyHandle* cfh = nullptr; - s = db_->CreateColumnFamily(options, test_cf_name, &cfh); - ASSERT_OK(s); - delete cfh; - CloseDb(); - - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, options); - cf_descs.emplace_back(test_cf_name, options); - std::vector handles; - - // 1. Open and populate the DB. Write and flush default_cf several times to - // advance wal number so that some column families have advanced log_number - // while other don't. - { - ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_)); - auto* dbimpl = static_cast_with_check(db_); - assert(dbimpl); - - // Write one key to test_cf. - ASSERT_OK(db_->Put(WriteOptions(), handles[1], "old_key", "dontcare")); - ASSERT_OK(db_->Flush(FlushOptions(), handles[1])); - - // Write to default_cf and flush this cf several times to advance wal - // number. TEST_SwitchMemtable makes sure WALs are not synced and test can - // corrupt un-sync WAL. - for (int i = 0; i < 2; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), - "value" + std::to_string(i))); - ASSERT_OK(dbimpl->TEST_SwitchMemtable()); - } - - for (auto* h : handles) { - delete h; - } - handles.clear(); - CloseDb(); - } - - // 2. Corrupt second last un-syned wal file to emulate power reset which - // caused the DB to lose the un-synced WAL. - { - std::vector file_nums; - GetSortedWalFiles(file_nums); - size_t size = file_nums.size(); - assert(size >= 2); - uint64_t log_num = file_nums[size - 2]; - CorruptFileWithTruncation(FileType::kWalFile, log_num, - /*bytes_to_truncate=*/8); - } - - // 3. After first crash reopen the DB which contains corrupted WAL. Default - // family has higher log number than corrupted wal number. - // - // Case1: If avoid_flush_during_recovery = true, RocksDB won't flush the data - // from WAL to L0 for all column families (test_cf_name in this case). As a - // result, not all column families can increase their log_numbers, and - // min_log_number_to_keep won't change. - // - // Case2: If avoid_flush_during_recovery = false, all column families have - // flushed their data from WAL to L0 during recovery, and none of them will - // ever need to read the WALs again. - - // 4. Fault is injected to fail the recovery. - { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::GetLogSizeAndMaybeTruncate:0", [&](void* arg) { - auto* tmp_s = reinterpret_cast(arg); - assert(tmp_s); - *tmp_s = Status::IOError("Injected"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - handles.clear(); - options.avoid_flush_during_recovery = true; - s = DB::Open(options, dbname_, cf_descs, &handles, &db_); - ASSERT_TRUE(s.IsIOError()); - ASSERT_EQ("IO error: Injected", s.ToString()); - for (auto* h : handles) { - delete h; - } - CloseDb(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } - - // 5. After second crash reopen the db with second corruption. Default family - // has higher log number than corrupted wal number. - // - // Case1: If avoid_flush_during_recovery = true, we persist a new - // MANIFEST with advanced log_numbers for some column families only after - // syncing the WAL. So during second crash, RocksDB will skip the corrupted - // WAL files as they have been moved to different folder. Since newly synced - // WAL file's sequence number (sentinel WriteBatch) will be the next - // sequence number immediately after the largest sequence number recovered - // from previous WALs and MANIFEST, db will be in consistent state and opens - // successfully. - // - // Case2: If avoid_flush_during_recovery = false, the corrupted WAL is below - // this number. So during a second crash after persisting the new MANIFEST, - // RocksDB will skip the corrupted WAL(s) because they are all below this - // bound. Therefore, we won't hit the "column family inconsistency" error - // message. - { - options.avoid_flush_during_recovery = avoid_flush_during_recovery_; - ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_)); - - // Verify that data is not lost. - { - std::string v; - ASSERT_OK(db_->Get(ReadOptions(), handles[1], "old_key", &v)); - ASSERT_EQ("dontcare", v); - - v.clear(); - ASSERT_OK(db_->Get(ReadOptions(), "key" + std::to_string(0), &v)); - ASSERT_EQ("value" + std::to_string(0), v); - - // Since it's corrupting second last wal, below key is not found. - v.clear(); - ASSERT_EQ(db_->Get(ReadOptions(), "key" + std::to_string(1), &v), - Status::NotFound()); - } - - for (auto* h : handles) { - delete h; - } - handles.clear(); - CloseDb(); - } -} - -// In case of TransactionDB, it enables two-phase-commit. The prepare section of -// an uncommitted transaction always need to be kept. Even if we perform flush -// during recovery, we may still need to hold an old WAL. The -// min_log_number_to_keep won't change, and "Column family inconsistency: SST -// file contains data beyond the point of corruption" error will be hit, causing -// recovery to fail. -// -// After adding the fix, only after new WAL is synced, RocksDB persist a new -// MANIFEST with column families to ensure RocksDB is in consistent state. -// RocksDB writes an empty WriteBatch as a sentinel to the new WAL which is -// synced immediately afterwards. The sequence number of the sentinel -// WriteBatch will be the next sequence number immediately after the largest -// sequence number recovered from previous WALs and MANIFEST because of which DB -// will be in consistent state. -// If a future recovery starts from the new MANIFEST, then it means the new WAL -// is successfully synced. Due to the sentinel empty write batch at the -// beginning, kPointInTimeRecovery of WAL is guaranteed to go after this point. -// If future recovery starts from the old MANIFEST, it means the writing the new -// MANIFEST failed. It won't have the "SST ahead of WAL" error. -// -// The combination of corrupting a WAL and injecting an error during subsequent -// re-open exposes the bug of prematurely persisting a new MANIFEST with -// advanced ColumnFamilyData::log_number. -TEST_P(CrashDuringRecoveryWithCorruptionTest, TxnDbCrashDuringRecovery) { - CloseDb(); - Options options; - options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - options.track_and_verify_wals_in_manifest = - track_and_verify_wals_in_manifest_; - options.avoid_flush_during_recovery = false; - options.env = env_.get(); - ASSERT_OK(DestroyDB(dbname_, options)); - options.create_if_missing = true; - options.max_write_buffer_number = 3; - Reopen(&options); - - // Create cf test_cf_name. - ColumnFamilyHandle* cfh = nullptr; - const std::string test_cf_name = "test_cf"; - Status s = db_->CreateColumnFamily(options, test_cf_name, &cfh); - ASSERT_OK(s); - delete cfh; - CloseDb(); - - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, options); - cf_descs.emplace_back(test_cf_name, options); - std::vector handles; - - TransactionDB* txn_db = nullptr; - TransactionDBOptions txn_db_opts; - - // 1. Open and populate the DB. Write and flush default_cf several times to - // advance wal number so that some column families have advanced log_number - // while other don't. - { - ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, cf_descs, - &handles, &txn_db)); - - auto* txn = txn_db->BeginTransaction(WriteOptions(), TransactionOptions()); - // Put cf1 - ASSERT_OK(txn->Put(handles[1], "foo", "value")); - ASSERT_OK(txn->SetName("txn0")); - ASSERT_OK(txn->Prepare()); - ASSERT_OK(txn_db->Flush(FlushOptions())); - - delete txn; - txn = nullptr; - - auto* dbimpl = static_cast_with_check(txn_db->GetRootDB()); - assert(dbimpl); - - // Put and flush cf0 - for (int i = 0; i < 2; ++i) { - ASSERT_OK(txn_db->Put(WriteOptions(), "key" + std::to_string(i), - "value" + std::to_string(i))); - ASSERT_OK(dbimpl->TEST_SwitchMemtable()); - } - - // Put cf1 - txn = txn_db->BeginTransaction(WriteOptions(), TransactionOptions()); - ASSERT_OK(txn->Put(handles[1], "foo1", "value1")); - ASSERT_OK(txn->Commit()); - - delete txn; - txn = nullptr; - - for (auto* h : handles) { - delete h; - } - handles.clear(); - delete txn_db; - } - - // 2. Corrupt second last wal to emulate power reset which caused the DB to - // lose the un-synced WAL. - { - std::vector file_nums; - GetSortedWalFiles(file_nums); - size_t size = file_nums.size(); - assert(size >= 2); - uint64_t log_num = file_nums[size - 2]; - CorruptFileWithTruncation(FileType::kWalFile, log_num, - /*bytes_to_truncate=*/8); - } - - // 3. After first crash reopen the DB which contains corrupted WAL. Default - // family has higher log number than corrupted wal number. There may be old - // WAL files that it must not delete because they can contain data of - // uncommitted transactions. As a result, min_log_number_to_keep won't change. - - { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::Open::BeforeSyncWAL", [&](void* arg) { - auto* tmp_s = reinterpret_cast(arg); - assert(tmp_s); - *tmp_s = Status::IOError("Injected"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - handles.clear(); - s = TransactionDB::Open(options, txn_db_opts, dbname_, cf_descs, &handles, - &txn_db); - ASSERT_TRUE(s.IsIOError()); - ASSERT_EQ("IO error: Injected", s.ToString()); - for (auto* h : handles) { - delete h; - } - CloseDb(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } - - // 4. Corrupt max_wal_num. - { - std::vector file_nums; - GetSortedWalFiles(file_nums); - size_t size = file_nums.size(); - uint64_t log_num = file_nums[size - 1]; - CorruptFileWithTruncation(FileType::kWalFile, log_num); - } - - // 5. After second crash reopen the db with second corruption. Default family - // has higher log number than corrupted wal number. - // We persist a new MANIFEST with advanced log_numbers for some column - // families only after syncing the WAL. So during second crash, RocksDB will - // skip the corrupted WAL files as they have been moved to different folder. - // Since newly synced WAL file's sequence number (sentinel WriteBatch) will be - // the next sequence number immediately after the largest sequence number - // recovered from previous WALs and MANIFEST, db will be in consistent state - // and opens successfully. - { - ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, cf_descs, - &handles, &txn_db)); - - // Verify that data is not lost. - { - std::string v; - // Key not visible since it's not committed. - ASSERT_EQ(txn_db->Get(ReadOptions(), handles[1], "foo", &v), - Status::NotFound()); - - v.clear(); - ASSERT_OK(txn_db->Get(ReadOptions(), "key" + std::to_string(0), &v)); - ASSERT_EQ("value" + std::to_string(0), v); - - // Last WAL is corrupted which contains two keys below. - v.clear(); - ASSERT_EQ(txn_db->Get(ReadOptions(), "key" + std::to_string(1), &v), - Status::NotFound()); - v.clear(); - ASSERT_EQ(txn_db->Get(ReadOptions(), handles[1], "foo1", &v), - Status::NotFound()); - } - - for (auto* h : handles) { - delete h; - } - delete txn_db; - } -} - -// This test is similar to -// CrashDuringRecoveryWithCorruptionTest.CrashDuringRecovery except it calls -// flush and corrupts Last WAL. It calls flush to sync some of the WALs and -// remaining are unsyned one of which is then corrupted to simulate crash. -// -// In case of non-TransactionDB with avoid_flush_during_recovery = true, RocksDB -// won't flush the data from WAL to L0 for all column families if possible. As a -// result, not all column families can increase their log_numbers, and -// min_log_number_to_keep won't change. -// It may prematurely persist a new MANIFEST even before we can declare the DB -// is in consistent state after recovery (this is when the new WAL is synced) -// and advances log_numbers for some column families. -// -// If there is power failure before we sync the new WAL, we will end up in -// a situation in which after persisting the MANIFEST, RocksDB will see some -// column families' log_numbers larger than the corrupted wal, and -// "Column family inconsistency: SST file contains data beyond the point of -// corruption" error will be hit, causing recovery to fail. -// -// After adding the fix, only after new WAL is synced, RocksDB persist a new -// MANIFEST with column families to ensure RocksDB is in consistent state. -// RocksDB writes an empty WriteBatch as a sentinel to the new WAL which is -// synced immediately afterwards. The sequence number of the sentinel -// WriteBatch will be the next sequence number immediately after the largest -// sequence number recovered from previous WALs and MANIFEST because of which DB -// will be in consistent state. -// If a future recovery starts from the new MANIFEST, then it means the new WAL -// is successfully synced. Due to the sentinel empty write batch at the -// beginning, kPointInTimeRecovery of WAL is guaranteed to go after this point. -// If future recovery starts from the old MANIFEST, it means the writing the new -// MANIFEST failed. It won't have the "SST ahead of WAL" error. - -// The combination of corrupting a WAL and injecting an error during subsequent -// re-open exposes the bug of prematurely persisting a new MANIFEST with -// advanced ColumnFamilyData::log_number. -TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecoveryWithFlush) { - CloseDb(); - Options options; - options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - options.avoid_flush_during_recovery = false; - options.env = env_.get(); - options.create_if_missing = true; - - ASSERT_OK(DestroyDB(dbname_, options)); - Reopen(&options); - - ColumnFamilyHandle* cfh = nullptr; - const std::string test_cf_name = "test_cf"; - Status s = db_->CreateColumnFamily(options, test_cf_name, &cfh); - ASSERT_OK(s); - delete cfh; - - CloseDb(); - - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, options); - cf_descs.emplace_back(test_cf_name, options); - std::vector handles; - - { - ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_)); - - // Write one key to test_cf. - ASSERT_OK(db_->Put(WriteOptions(), handles[1], "old_key", "dontcare")); - - // Write to default_cf and flush this cf several times to advance wal - // number. - for (int i = 0; i < 2; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), - "value" + std::to_string(i))); - ASSERT_OK(db_->Flush(FlushOptions())); - } - - ASSERT_OK(db_->Put(WriteOptions(), handles[1], "dontcare", "dontcare")); - for (auto* h : handles) { - delete h; - } - handles.clear(); - CloseDb(); - } - - // Corrupt second last un-syned wal file to emulate power reset which - // caused the DB to lose the un-synced WAL. - { - std::vector file_nums; - GetSortedWalFiles(file_nums); - size_t size = file_nums.size(); - uint64_t log_num = file_nums[size - 1]; - CorruptFileWithTruncation(FileType::kWalFile, log_num, - /*bytes_to_truncate=*/8); - } - - // Fault is injected to fail the recovery. - { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::GetLogSizeAndMaybeTruncate:0", [&](void* arg) { - auto* tmp_s = reinterpret_cast(arg); - assert(tmp_s); - *tmp_s = Status::IOError("Injected"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - handles.clear(); - options.avoid_flush_during_recovery = true; - s = DB::Open(options, dbname_, cf_descs, &handles, &db_); - ASSERT_TRUE(s.IsIOError()); - ASSERT_EQ("IO error: Injected", s.ToString()); - for (auto* h : handles) { - delete h; - } - CloseDb(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } - - // Reopen db again - { - options.avoid_flush_during_recovery = avoid_flush_during_recovery_; - ASSERT_OK(DB::Open(options, dbname_, cf_descs, &handles, &db_)); - - // Verify that data is not lost. - { - std::string v; - ASSERT_OK(db_->Get(ReadOptions(), handles[1], "old_key", &v)); - ASSERT_EQ("dontcare", v); - - for (int i = 0; i < 2; ++i) { - v.clear(); - ASSERT_OK(db_->Get(ReadOptions(), "key" + std::to_string(i), &v)); - ASSERT_EQ("value" + std::to_string(i), v); - } - - // Since it's corrupting last wal after Flush, below key is not found. - v.clear(); - ASSERT_EQ(db_->Get(ReadOptions(), handles[1], "dontcare", &v), - Status::NotFound()); - } - - for (auto* h : handles) { - delete h; - } - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/cuckoo_table_db_test.cc b/db/cuckoo_table_db_test.cc deleted file mode 100644 index 7bd4dfda4..000000000 --- a/db/cuckoo_table_db_test.cc +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "table/cuckoo/cuckoo_table_factory.h" -#include "table/cuckoo/cuckoo_table_reader.h" -#include "table/meta_blocks.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/cast_util.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class CuckooTableDBTest : public testing::Test { - private: - std::string dbname_; - Env* env_; - DB* db_; - - public: - CuckooTableDBTest() : env_(Env::Default()) { - dbname_ = test::PerThreadDBPath("cuckoo_table_db_test"); - EXPECT_OK(DestroyDB(dbname_, Options())); - db_ = nullptr; - Reopen(); - } - - ~CuckooTableDBTest() override { - delete db_; - EXPECT_OK(DestroyDB(dbname_, Options())); - } - - Options CurrentOptions() { - Options options; - options.table_factory.reset(NewCuckooTableFactory()); - options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true)); - options.allow_mmap_reads = true; - options.create_if_missing = true; - options.allow_concurrent_memtable_write = false; - return options; - } - - DBImpl* dbfull() { return static_cast_with_check(db_); } - - // The following util methods are copied from plain_table_db_test. - void Reopen(Options* options = nullptr) { - delete db_; - db_ = nullptr; - Options opts; - if (options != nullptr) { - opts = *options; - } else { - opts = CurrentOptions(); - opts.create_if_missing = true; - } - ASSERT_OK(DB::Open(opts, dbname_, &db_)); - } - - void DestroyAndReopen(Options* options) { - assert(options); - ASSERT_OK(db_->Close()); - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, *options)); - Reopen(options); - } - - Status Put(const Slice& k, const Slice& v) { - return db_->Put(WriteOptions(), k, v); - } - - Status Delete(const std::string& k) { return db_->Delete(WriteOptions(), k); } - - std::string Get(const std::string& k) { - ReadOptions options; - std::string result; - Status s = db_->Get(options, k, &result); - if (s.IsNotFound()) { - result = "NOT_FOUND"; - } else if (!s.ok()) { - result = s.ToString(); - } - return result; - } - - int NumTableFilesAtLevel(int level) { - std::string property; - EXPECT_TRUE(db_->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(level), &property)); - return atoi(property.c_str()); - } - - // Return spread of files per level - std::string FilesPerLevel() { - std::string result; - size_t last_non_zero_offset = 0; - for (int level = 0; level < db_->NumberLevels(); level++) { - int f = NumTableFilesAtLevel(level); - char buf[100]; - snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f); - result += buf; - if (f > 0) { - last_non_zero_offset = result.size(); - } - } - result.resize(last_non_zero_offset); - return result; - } -}; - -TEST_F(CuckooTableDBTest, Flush) { - // Try with empty DB first. - ASSERT_TRUE(dbfull() != nullptr); - ASSERT_EQ("NOT_FOUND", Get("key2")); - - // Add some values to db. - Options options = CurrentOptions(); - Reopen(&options); - - ASSERT_OK(Put("key1", "v1")); - ASSERT_OK(Put("key2", "v2")); - ASSERT_OK(Put("key3", "v3")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - TablePropertiesCollection ptc; - ASSERT_OK(reinterpret_cast(dbfull())->GetPropertiesOfAllTables(&ptc)); - VerifySstUniqueIds(ptc); - ASSERT_EQ(1U, ptc.size()); - ASSERT_EQ(3U, ptc.begin()->second->num_entries); - ASSERT_EQ("1", FilesPerLevel()); - - ASSERT_EQ("v1", Get("key1")); - ASSERT_EQ("v2", Get("key2")); - ASSERT_EQ("v3", Get("key3")); - ASSERT_EQ("NOT_FOUND", Get("key4")); - - // Now add more keys and flush. - ASSERT_OK(Put("key4", "v4")); - ASSERT_OK(Put("key5", "v5")); - ASSERT_OK(Put("key6", "v6")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_OK(reinterpret_cast(dbfull())->GetPropertiesOfAllTables(&ptc)); - VerifySstUniqueIds(ptc); - ASSERT_EQ(2U, ptc.size()); - auto row = ptc.begin(); - ASSERT_EQ(3U, row->second->num_entries); - ASSERT_EQ(3U, (++row)->second->num_entries); - ASSERT_EQ("2", FilesPerLevel()); - ASSERT_EQ("v1", Get("key1")); - ASSERT_EQ("v2", Get("key2")); - ASSERT_EQ("v3", Get("key3")); - ASSERT_EQ("v4", Get("key4")); - ASSERT_EQ("v5", Get("key5")); - ASSERT_EQ("v6", Get("key6")); - - ASSERT_OK(Delete("key6")); - ASSERT_OK(Delete("key5")); - ASSERT_OK(Delete("key4")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_OK(reinterpret_cast(dbfull())->GetPropertiesOfAllTables(&ptc)); - VerifySstUniqueIds(ptc); - ASSERT_EQ(3U, ptc.size()); - row = ptc.begin(); - ASSERT_EQ(3U, row->second->num_entries); - ASSERT_EQ(3U, (++row)->second->num_entries); - ASSERT_EQ(3U, (++row)->second->num_entries); - ASSERT_EQ("3", FilesPerLevel()); - ASSERT_EQ("v1", Get("key1")); - ASSERT_EQ("v2", Get("key2")); - ASSERT_EQ("v3", Get("key3")); - ASSERT_EQ("NOT_FOUND", Get("key4")); - ASSERT_EQ("NOT_FOUND", Get("key5")); - ASSERT_EQ("NOT_FOUND", Get("key6")); -} - -TEST_F(CuckooTableDBTest, FlushWithDuplicateKeys) { - Options options = CurrentOptions(); - Reopen(&options); - ASSERT_OK(Put("key1", "v1")); - ASSERT_OK(Put("key2", "v2")); - ASSERT_OK(Put("key1", "v3")); // Duplicate - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - TablePropertiesCollection ptc; - ASSERT_OK(reinterpret_cast(dbfull())->GetPropertiesOfAllTables(&ptc)); - VerifySstUniqueIds(ptc); - ASSERT_EQ(1U, ptc.size()); - ASSERT_EQ(2U, ptc.begin()->second->num_entries); - ASSERT_EQ("1", FilesPerLevel()); - ASSERT_EQ("v3", Get("key1")); - ASSERT_EQ("v2", Get("key2")); -} - -namespace { -static std::string Key(int i) { - char buf[100]; - snprintf(buf, sizeof(buf), "key_______%06d", i); - return std::string(buf); -} -static std::string Uint64Key(uint64_t i) { - std::string str; - str.resize(8); - memcpy(&str[0], static_cast(&i), 8); - return str; -} -} // namespace. - -TEST_F(CuckooTableDBTest, Uint64Comparator) { - Options options = CurrentOptions(); - options.comparator = test::Uint64Comparator(); - DestroyAndReopen(&options); - - ASSERT_OK(Put(Uint64Key(1), "v1")); - ASSERT_OK(Put(Uint64Key(2), "v2")); - ASSERT_OK(Put(Uint64Key(3), "v3")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_EQ("v1", Get(Uint64Key(1))); - ASSERT_EQ("v2", Get(Uint64Key(2))); - ASSERT_EQ("v3", Get(Uint64Key(3))); - ASSERT_EQ("NOT_FOUND", Get(Uint64Key(4))); - - // Add more keys. - ASSERT_OK(Delete(Uint64Key(2))); // Delete. - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_OK(Put(Uint64Key(3), "v0")); // Update. - ASSERT_OK(Put(Uint64Key(4), "v4")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v1", Get(Uint64Key(1))); - ASSERT_EQ("NOT_FOUND", Get(Uint64Key(2))); - ASSERT_EQ("v0", Get(Uint64Key(3))); - ASSERT_EQ("v4", Get(Uint64Key(4))); -} - -TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) { - // Create a big L0 file and check it compacts into multiple files in L1. - Options options = CurrentOptions(); - options.write_buffer_size = 270 << 10; - // Two SST files should be created, each containing 14 keys. - // Number of buckets will be 16. Total size ~156 KB. - options.target_file_size_base = 160 << 10; - Reopen(&options); - - // Write 28 values, each 10016 B ~ 10KB - for (int idx = 0; idx < 28; ++idx) { - ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx)))); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ("1", FilesPerLevel()); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow trivial move */)); - ASSERT_EQ("0,2", FilesPerLevel()); - for (int idx = 0; idx < 28; ++idx) { - ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx))); - } -} - -TEST_F(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) { - // Insert same key twice so that they go to different SST files. Then wait for - // compaction and check if the latest value is stored and old value removed. - Options options = CurrentOptions(); - options.write_buffer_size = 100 << 10; // 100KB - options.level0_file_num_compaction_trigger = 2; - Reopen(&options); - - // Write 11 values, each 10016 B - for (int idx = 0; idx < 11; ++idx) { - ASSERT_OK(Put(Key(idx), std::string(10000, 'a'))); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ("1", FilesPerLevel()); - - // Generate one more file in level-0, and should trigger level-0 compaction - for (int idx = 0; idx < 11; ++idx) { - ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx)))); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - - ASSERT_EQ("0,1", FilesPerLevel()); - for (int idx = 0; idx < 11; ++idx) { - ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx))); - } -} - -TEST_F(CuckooTableDBTest, AdaptiveTable) { - Options options = CurrentOptions(); - - // Ensure options compatible with PlainTable - options.prefix_extractor.reset(NewCappedPrefixTransform(8)); - - // Write some keys using cuckoo table. - options.table_factory.reset(NewCuckooTableFactory()); - Reopen(&options); - - ASSERT_OK(Put("key1", "v1")); - ASSERT_OK(Put("key2", "v2")); - ASSERT_OK(Put("key3", "v3")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - // Write some keys using plain table. - std::shared_ptr block_based_factory( - NewBlockBasedTableFactory()); - std::shared_ptr plain_table_factory(NewPlainTableFactory()); - std::shared_ptr cuckoo_table_factory(NewCuckooTableFactory()); - options.create_if_missing = false; - options.table_factory.reset( - NewAdaptiveTableFactory(plain_table_factory, block_based_factory, - plain_table_factory, cuckoo_table_factory)); - Reopen(&options); - ASSERT_OK(Put("key4", "v4")); - ASSERT_OK(Put("key1", "v5")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - // Write some keys using block based table. - options.table_factory.reset( - NewAdaptiveTableFactory(block_based_factory, block_based_factory, - plain_table_factory, cuckoo_table_factory)); - Reopen(&options); - ASSERT_OK(Put("key5", "v6")); - ASSERT_OK(Put("key2", "v7")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_EQ("v5", Get("key1")); - ASSERT_EQ("v7", Get("key2")); - ASSERT_EQ("v3", Get("key3")); - ASSERT_EQ("v4", Get("key4")); - ASSERT_EQ("v6", Get("key5")); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - if (ROCKSDB_NAMESPACE::port::kLittleEndian) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); - } else { - fprintf(stderr, "SKIPPED as Cuckoo table doesn't support Big Endian\n"); - return 0; - } -} - diff --git a/db/db_basic_test.cc b/db/db_basic_test.cc deleted file mode 100644 index 063b99839..000000000 --- a/db/db_basic_test.cc +++ /dev/null @@ -1,4777 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include "db/db_test_util.h" -#include "options/options_helper.h" -#include "port/stack_trace.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/flush_block_policy.h" -#include "rocksdb/merge_operator.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/table.h" -#include "rocksdb/utilities/debug.h" -#include "table/block_based/block_based_table_reader.h" -#include "table/block_based/block_builder.h" -#include "test_util/sync_point.h" -#include "util/file_checksum_helper.h" -#include "util/random.h" -#include "utilities/counted_fs.h" -#include "utilities/fault_injection_env.h" -#include "utilities/merge_operators.h" -#include "utilities/merge_operators/string_append/stringappend.h" - -namespace ROCKSDB_NAMESPACE { - -static bool enable_io_uring = true; -extern "C" bool RocksDbIOUringEnable() { return enable_io_uring; } - -class DBBasicTest : public DBTestBase { - public: - DBBasicTest() : DBTestBase("db_basic_test", /*env_do_fsync=*/false) {} -}; - -TEST_F(DBBasicTest, OpenWhenOpen) { - Options options = CurrentOptions(); - options.env = env_; - DB* db2 = nullptr; - Status s = DB::Open(options, dbname_, &db2); - ASSERT_NOK(s) << [db2]() { - delete db2; - return "db2 open: ok"; - }(); - ASSERT_EQ(Status::Code::kIOError, s.code()); - ASSERT_EQ(Status::SubCode::kNone, s.subcode()); - ASSERT_TRUE(strstr(s.getState(), "lock ") != nullptr); - - delete db2; -} - -TEST_F(DBBasicTest, EnableDirectIOWithZeroBuf) { - if (!IsDirectIOSupported()) { - ROCKSDB_GTEST_BYPASS("Direct IO not supported"); - return; - } - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.use_direct_io_for_flush_and_compaction = true; - options.writable_file_max_buffer_size = 0; - ASSERT_TRUE(TryReopen(options).IsInvalidArgument()); - - options.writable_file_max_buffer_size = 1024; - Reopen(options); - const std::unordered_map new_db_opts = { - {"writable_file_max_buffer_size", "0"}}; - ASSERT_TRUE(db_->SetDBOptions(new_db_opts).IsInvalidArgument()); -} - -TEST_F(DBBasicTest, UniqueSession) { - Options options = CurrentOptions(); - std::string sid1, sid2, sid3, sid4; - - ASSERT_OK(db_->GetDbSessionId(sid1)); - Reopen(options); - ASSERT_OK(db_->GetDbSessionId(sid2)); - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(db_->GetDbSessionId(sid4)); - Reopen(options); - ASSERT_OK(db_->GetDbSessionId(sid3)); - - ASSERT_NE(sid1, sid2); - ASSERT_NE(sid1, sid3); - ASSERT_NE(sid2, sid3); - - ASSERT_EQ(sid2, sid4); - - // Expected compact format for session ids (see notes in implementation) - TestRegex expected("[0-9A-Z]{20}"); - EXPECT_MATCHES_REGEX(sid1, expected); - EXPECT_MATCHES_REGEX(sid2, expected); - EXPECT_MATCHES_REGEX(sid3, expected); - - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ASSERT_OK(db_->GetDbSessionId(sid1)); - // Test uniqueness between readonly open (sid1) and regular open (sid3) - ASSERT_NE(sid1, sid3); - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ASSERT_OK(db_->GetDbSessionId(sid2)); - ASSERT_EQ("v1", Get("foo")); - ASSERT_OK(db_->GetDbSessionId(sid3)); - - ASSERT_NE(sid1, sid2); - - ASSERT_EQ(sid2, sid3); - - CreateAndReopenWithCF({"goku"}, options); - ASSERT_OK(db_->GetDbSessionId(sid1)); - ASSERT_OK(Put("bar", "e1")); - ASSERT_OK(db_->GetDbSessionId(sid2)); - ASSERT_EQ("e1", Get("bar")); - ASSERT_OK(db_->GetDbSessionId(sid3)); - ReopenWithColumnFamilies({"default", "goku"}, options); - ASSERT_OK(db_->GetDbSessionId(sid4)); - - ASSERT_EQ(sid1, sid2); - ASSERT_EQ(sid2, sid3); - - ASSERT_NE(sid1, sid4); -} - -TEST_F(DBBasicTest, ReadOnlyDB) { - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Put("bar", "v2")); - ASSERT_OK(Put("foo", "v3")); - Close(); - - auto verify_one_iter = [&](Iterator* iter) { - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - ++count; - } - // Always expect two keys: "foo" and "bar" - ASSERT_EQ(count, 2); - }; - - auto verify_all_iters = [&]() { - Iterator* iter = db_->NewIterator(ReadOptions()); - verify_one_iter(iter); - delete iter; - - std::vector iters; - ASSERT_OK(db_->NewIterators(ReadOptions(), - {dbfull()->DefaultColumnFamily()}, &iters)); - ASSERT_EQ(static_cast(1), iters.size()); - verify_one_iter(iters[0]); - delete iters[0]; - }; - - auto options = CurrentOptions(); - assert(options.env == env_); - ASSERT_OK(ReadOnlyReopen(options)); - ASSERT_EQ("v3", Get("foo")); - ASSERT_EQ("v2", Get("bar")); - verify_all_iters(); - Close(); - - // Reopen and flush memtable. - Reopen(options); - ASSERT_OK(Flush()); - Close(); - // Now check keys in read only mode. - ASSERT_OK(ReadOnlyReopen(options)); - ASSERT_EQ("v3", Get("foo")); - ASSERT_EQ("v2", Get("bar")); - verify_all_iters(); - ASSERT_TRUE(db_->SyncWAL().IsNotSupported()); -} - -// TODO akanksha: Update the test to check that combination -// does not actually write to FS (use open read-only with -// CompositeEnvWrapper+ReadOnlyFileSystem). -TEST_F(DBBasicTest, DISABLED_ReadOnlyDBWithWriteDBIdToManifestSet) { - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Put("bar", "v2")); - ASSERT_OK(Put("foo", "v3")); - Close(); - - auto options = CurrentOptions(); - options.write_dbid_to_manifest = true; - assert(options.env == env_); - ASSERT_OK(ReadOnlyReopen(options)); - std::string db_id1; - ASSERT_OK(db_->GetDbIdentity(db_id1)); - ASSERT_EQ("v3", Get("foo")); - ASSERT_EQ("v2", Get("bar")); - Iterator* iter = db_->NewIterator(ReadOptions()); - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - ++count; - } - ASSERT_EQ(count, 2); - delete iter; - Close(); - - // Reopen and flush memtable. - Reopen(options); - ASSERT_OK(Flush()); - Close(); - // Now check keys in read only mode. - ASSERT_OK(ReadOnlyReopen(options)); - ASSERT_EQ("v3", Get("foo")); - ASSERT_EQ("v2", Get("bar")); - ASSERT_TRUE(db_->SyncWAL().IsNotSupported()); - std::string db_id2; - ASSERT_OK(db_->GetDbIdentity(db_id2)); - ASSERT_EQ(db_id1, db_id2); -} - -TEST_F(DBBasicTest, CompactedDB) { - const uint64_t kFileSize = 1 << 20; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.write_buffer_size = kFileSize; - options.target_file_size_base = kFileSize; - options.max_bytes_for_level_base = 1 << 30; - options.compression = kNoCompression; - Reopen(options); - // 1 L0 file, use CompactedDB if max_open_files = -1 - ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, '1'))); - ASSERT_OK(Flush()); - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - Status s = Put("new", "value"); - ASSERT_EQ(s.ToString(), - "Not implemented: Not supported operation in read only mode."); - ASSERT_EQ(DummyString(kFileSize / 2, '1'), Get("aaa")); - Close(); - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - s = Put("new", "value"); - ASSERT_EQ(s.ToString(), - "Not implemented: Not supported in compacted db mode."); - ASSERT_EQ(DummyString(kFileSize / 2, '1'), Get("aaa")); - Close(); - Reopen(options); - // Add more L0 files - ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, '2'))); - ASSERT_OK(Flush()); - ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, 'a'))); - ASSERT_OK(Flush()); - ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, 'b'))); - ASSERT_OK(Put("eee", DummyString(kFileSize / 2, 'e'))); - ASSERT_OK(Flush()); - ASSERT_OK(Put("something_not_flushed", "x")); - Close(); - - ASSERT_OK(ReadOnlyReopen(options)); - // Fallback to read-only DB - s = Put("new", "value"); - ASSERT_EQ(s.ToString(), - "Not implemented: Not supported operation in read only mode."); - - // TODO: validate that other write ops return NotImplemented - // (DBImplReadOnly is missing some overrides) - - // Ensure no deadlock on flush triggered by another API function - // (Old deadlock bug depends on something_not_flushed above.) - std::vector files; - uint64_t manifest_file_size; - ASSERT_OK(db_->GetLiveFiles(files, &manifest_file_size, /*flush*/ true)); - LiveFilesStorageInfoOptions lfsi_opts; - lfsi_opts.wal_size_for_flush = 0; // always - std::vector files2; - ASSERT_OK(db_->GetLiveFilesStorageInfo(lfsi_opts, &files2)); - - Close(); - - // Full compaction - Reopen(options); - // Add more keys - ASSERT_OK(Put("fff", DummyString(kFileSize / 2, 'f'))); - ASSERT_OK(Put("hhh", DummyString(kFileSize / 2, 'h'))); - ASSERT_OK(Put("iii", DummyString(kFileSize / 2, 'i'))); - ASSERT_OK(Put("jjj", DummyString(kFileSize / 2, 'j'))); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(3, NumTableFilesAtLevel(1)); - Close(); - - // CompactedDB - ASSERT_OK(ReadOnlyReopen(options)); - s = Put("new", "value"); - ASSERT_EQ(s.ToString(), - "Not implemented: Not supported in compacted db mode."); - ASSERT_EQ("NOT_FOUND", Get("abc")); - ASSERT_EQ(DummyString(kFileSize / 2, 'a'), Get("aaa")); - ASSERT_EQ(DummyString(kFileSize / 2, 'b'), Get("bbb")); - ASSERT_EQ("NOT_FOUND", Get("ccc")); - ASSERT_EQ(DummyString(kFileSize / 2, 'e'), Get("eee")); - ASSERT_EQ(DummyString(kFileSize / 2, 'f'), Get("fff")); - ASSERT_EQ("NOT_FOUND", Get("ggg")); - ASSERT_EQ(DummyString(kFileSize / 2, 'h'), Get("hhh")); - ASSERT_EQ(DummyString(kFileSize / 2, 'i'), Get("iii")); - ASSERT_EQ(DummyString(kFileSize / 2, 'j'), Get("jjj")); - ASSERT_EQ("NOT_FOUND", Get("kkk")); - - // TODO: validate that other write ops return NotImplemented - // (CompactedDB is missing some overrides) - - // Ensure no deadlock on flush triggered by another API function - ASSERT_OK(db_->GetLiveFiles(files, &manifest_file_size, /*flush*/ true)); - ASSERT_OK(db_->GetLiveFilesStorageInfo(lfsi_opts, &files2)); - - // MultiGet - std::vector values; - std::vector status_list = dbfull()->MultiGet( - ReadOptions(), - std::vector({Slice("aaa"), Slice("ccc"), Slice("eee"), - Slice("ggg"), Slice("iii"), Slice("kkk")}), - &values); - ASSERT_EQ(status_list.size(), static_cast(6)); - ASSERT_EQ(values.size(), static_cast(6)); - ASSERT_OK(status_list[0]); - ASSERT_EQ(DummyString(kFileSize / 2, 'a'), values[0]); - ASSERT_TRUE(status_list[1].IsNotFound()); - ASSERT_OK(status_list[2]); - ASSERT_EQ(DummyString(kFileSize / 2, 'e'), values[2]); - ASSERT_TRUE(status_list[3].IsNotFound()); - ASSERT_OK(status_list[4]); - ASSERT_EQ(DummyString(kFileSize / 2, 'i'), values[4]); - ASSERT_TRUE(status_list[5].IsNotFound()); - - Reopen(options); - // Add a key - ASSERT_OK(Put("fff", DummyString(kFileSize / 2, 'f'))); - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - s = Put("new", "value"); - ASSERT_EQ(s.ToString(), - "Not implemented: Not supported operation in read only mode."); -} - -TEST_F(DBBasicTest, LevelLimitReopen) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - - const std::string value(1024 * 1024, ' '); - int i = 0; - while (NumTableFilesAtLevel(2, 1) == 0) { - ASSERT_OK(Put(1, Key(i++), value)); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - options.num_levels = 1; - options.max_bytes_for_level_multiplier_additional.resize(1, 1); - Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ(s.IsInvalidArgument(), true); - ASSERT_EQ(s.ToString(), - "Invalid argument: db has more levels than options.num_levels"); - - options.num_levels = 10; - options.max_bytes_for_level_multiplier_additional.resize(10, 1); - ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options)); -} - -TEST_F(DBBasicTest, PutDeleteGet) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_OK(Put(1, "foo", "v2")); - ASSERT_EQ("v2", Get(1, "foo")); - ASSERT_OK(Delete(1, "foo")); - ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - } while (ChangeOptions()); -} - -TEST_F(DBBasicTest, PutSingleDeleteGet) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_OK(Put(1, "foo2", "v2")); - ASSERT_EQ("v2", Get(1, "foo2")); - ASSERT_OK(SingleDelete(1, "foo")); - ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - // Ski FIFO and universal compaction because they do not apply to the test - // case. Skip MergePut because single delete does not get removed when it - // encounters a merge. - } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | - kSkipMergePut)); -} - -TEST_F(DBBasicTest, EmptyFlush) { - // It is possible to produce empty flushes when using single deletes. Tests - // whether empty flushes cause issues. - do { - Random rnd(301); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "a", Slice())); - ASSERT_OK(SingleDelete(1, "a")); - ASSERT_OK(Flush(1)); - - ASSERT_EQ("[ ]", AllEntriesFor("a", 1)); - // Skip FIFO and universal compaction as they do not apply to the test - // case. Skip MergePut because merges cannot be combined with single - // deletions. - } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | - kSkipMergePut)); -} - -TEST_F(DBBasicTest, GetFromVersions) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Flush(1)); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("NOT_FOUND", Get(0, "foo")); - } while (ChangeOptions()); -} - -TEST_F(DBBasicTest, GetSnapshot) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override)); - // Try with both a short key and a long key - for (int i = 0; i < 2; i++) { - std::string key = (i == 0) ? std::string("foo") : std::string(200, 'x'); - ASSERT_OK(Put(1, key, "v1")); - const Snapshot* s1 = db_->GetSnapshot(); - ASSERT_OK(Put(1, key, "v2")); - ASSERT_EQ("v2", Get(1, key)); - ASSERT_EQ("v1", Get(1, key, s1)); - ASSERT_OK(Flush(1)); - ASSERT_EQ("v2", Get(1, key)); - ASSERT_EQ("v1", Get(1, key, s1)); - db_->ReleaseSnapshot(s1); - } - } while (ChangeOptions()); -} - -TEST_F(DBBasicTest, CheckLock) { - do { - DB* localdb = nullptr; - Options options = CurrentOptions(); - ASSERT_OK(TryReopen(options)); - - // second open should fail - Status s = DB::Open(options, dbname_, &localdb); - ASSERT_NOK(s) << [localdb]() { - delete localdb; - return "localdb open: ok"; - }(); -#ifdef OS_LINUX - ASSERT_TRUE(s.ToString().find("lock ") != std::string::npos); -#endif // OS_LINUX - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, FlushMultipleMemtable) { - do { - Options options = CurrentOptions(); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.max_write_buffer_size_to_maintain = -1; - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1")); - - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v1", Get(1, "bar")); - ASSERT_OK(Flush(1)); - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, FlushEmptyColumnFamily) { - // Block flush thread and disable compaction thread - env_->SetBackgroundThreads(1, Env::HIGH); - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - test::SleepingBackgroundTask sleeping_task_high; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_high, Env::Priority::HIGH); - - Options options = CurrentOptions(); - // disable compaction - options.disable_auto_compactions = true; - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - options.max_write_buffer_number = 2; - options.min_write_buffer_number_to_merge = 1; - options.max_write_buffer_size_to_maintain = - static_cast(options.write_buffer_size); - CreateAndReopenWithCF({"pikachu"}, options); - - // Compaction can still go through even if no thread can flush the - // mem table. - ASSERT_OK(Flush(0)); - ASSERT_OK(Flush(1)); - - // Insert can go through - ASSERT_OK(dbfull()->Put(writeOpt, handles_[0], "foo", "v1")); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1")); - - ASSERT_EQ("v1", Get(0, "foo")); - ASSERT_EQ("v1", Get(1, "bar")); - - sleeping_task_high.WakeUp(); - sleeping_task_high.WaitUntilDone(); - - // Flush can still go through. - ASSERT_OK(Flush(0)); - ASSERT_OK(Flush(1)); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); -} - -TEST_F(DBBasicTest, Flush) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - SetPerfLevel(kEnableTime); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1")); - // this will now also flush the last 2 writes - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1")); - - get_perf_context()->Reset(); - Get(1, "foo"); - ASSERT_TRUE((int)get_perf_context()->get_from_output_files_time > 0); - ASSERT_EQ(2, (int)get_perf_context()->get_read_bytes); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v1", Get(1, "bar")); - - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v2")); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v2")); - ASSERT_OK(Flush(1)); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("v2", Get(1, "bar")); - get_perf_context()->Reset(); - ASSERT_EQ("v2", Get(1, "foo")); - ASSERT_TRUE((int)get_perf_context()->get_from_output_files_time > 0); - - writeOpt.disableWAL = false; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v3")); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v3")); - ASSERT_OK(Flush(1)); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - // 'foo' should be there because its put - // has WAL enabled. - ASSERT_EQ("v3", Get(1, "foo")); - ASSERT_EQ("v3", Get(1, "bar")); - - SetPerfLevel(kDisable); - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, ManifestRollOver) { - do { - Options options; - options.max_manifest_file_size = 10; // 10 bytes - options = CurrentOptions(options); - CreateAndReopenWithCF({"pikachu"}, options); - { - ASSERT_OK(Put(1, "manifest_key1", std::string(1000, '1'))); - ASSERT_OK(Put(1, "manifest_key2", std::string(1000, '2'))); - ASSERT_OK(Put(1, "manifest_key3", std::string(1000, '3'))); - uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo(); - ASSERT_OK(Flush(1)); // This should trigger LogAndApply. - uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo(); - ASSERT_GT(manifest_after_flush, manifest_before_flush); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush); - // check if a new manifest file got inserted or not. - ASSERT_EQ(std::string(1000, '1'), Get(1, "manifest_key1")); - ASSERT_EQ(std::string(1000, '2'), Get(1, "manifest_key2")); - ASSERT_EQ(std::string(1000, '3'), Get(1, "manifest_key3")); - } - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, IdentityAcrossRestarts) { - constexpr size_t kMinIdSize = 10; - do { - for (bool with_manifest : {false, true}) { - std::string idfilename = IdentityFileName(dbname_); - std::string id1, tmp; - ASSERT_OK(db_->GetDbIdentity(id1)); - ASSERT_GE(id1.size(), kMinIdSize); - - Options options = CurrentOptions(); - options.write_dbid_to_manifest = with_manifest; - Reopen(options); - std::string id2; - ASSERT_OK(db_->GetDbIdentity(id2)); - // id2 should match id1 because identity was not regenerated - ASSERT_EQ(id1, id2); - ASSERT_OK(ReadFileToString(env_, idfilename, &tmp)); - ASSERT_EQ(tmp, id2); - - // Recover from deleted/missing IDENTITY - ASSERT_OK(env_->DeleteFile(idfilename)); - Reopen(options); - std::string id3; - ASSERT_OK(db_->GetDbIdentity(id3)); - if (with_manifest) { - // id3 should match id1 because identity was restored from manifest - ASSERT_EQ(id1, id3); - } else { - // id3 should NOT match id1 because identity was regenerated - ASSERT_NE(id1, id3); - ASSERT_GE(id3.size(), kMinIdSize); - } - ASSERT_OK(ReadFileToString(env_, idfilename, &tmp)); - ASSERT_EQ(tmp, id3); - - // Recover from truncated IDENTITY - { - std::unique_ptr w; - ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions())); - ASSERT_OK(w->Close()); - } - Reopen(options); - std::string id4; - ASSERT_OK(db_->GetDbIdentity(id4)); - if (with_manifest) { - // id4 should match id1 because identity was restored from manifest - ASSERT_EQ(id1, id4); - } else { - // id4 should NOT match id1 because identity was regenerated - ASSERT_NE(id1, id4); - ASSERT_GE(id4.size(), kMinIdSize); - } - ASSERT_OK(ReadFileToString(env_, idfilename, &tmp)); - ASSERT_EQ(tmp, id4); - - // Recover from overwritten IDENTITY - std::string silly_id = "asdf123456789"; - { - std::unique_ptr w; - ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions())); - ASSERT_OK(w->Append(silly_id)); - ASSERT_OK(w->Close()); - } - Reopen(options); - std::string id5; - ASSERT_OK(db_->GetDbIdentity(id5)); - if (with_manifest) { - // id4 should match id1 because identity was restored from manifest - ASSERT_EQ(id1, id5); - } else { - ASSERT_EQ(id5, silly_id); - } - ASSERT_OK(ReadFileToString(env_, idfilename, &tmp)); - ASSERT_EQ(tmp, id5); - } - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, LockFileRecovery) { - Options options = CurrentOptions(); - // Regardless of best_efforts_recovery - for (bool ber : {false, true}) { - options.best_efforts_recovery = ber; - DestroyAndReopen(options); - std::string id1, id2; - ASSERT_OK(db_->GetDbIdentity(id1)); - Close(); - - // Should be OK to re-open DB after lock file deleted - std::string lockfilename = LockFileName(dbname_); - ASSERT_OK(env_->DeleteFile(lockfilename)); - Reopen(options); - - // Should be same DB as before - ASSERT_OK(db_->GetDbIdentity(id2)); - ASSERT_EQ(id1, id2); - } -} - -TEST_F(DBBasicTest, Snapshot) { - env_->SetMockSleep(); - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override)); - ASSERT_OK(Put(0, "foo", "0v1")); - ASSERT_OK(Put(1, "foo", "1v1")); - - const Snapshot* s1 = db_->GetSnapshot(); - ASSERT_EQ(1U, GetNumSnapshots()); - uint64_t time_snap1 = GetTimeOldestSnapshots(); - ASSERT_GT(time_snap1, 0U); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_OK(Put(0, "foo", "0v2")); - ASSERT_OK(Put(1, "foo", "1v2")); - - env_->MockSleepForSeconds(1); - - const Snapshot* s2 = db_->GetSnapshot(); - ASSERT_EQ(2U, GetNumSnapshots()); - ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_OK(Put(0, "foo", "0v3")); - ASSERT_OK(Put(1, "foo", "1v3")); - - { - ManagedSnapshot s3(db_); - ASSERT_EQ(3U, GetNumSnapshots()); - ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - - ASSERT_OK(Put(0, "foo", "0v4")); - ASSERT_OK(Put(1, "foo", "1v4")); - ASSERT_EQ("0v1", Get(0, "foo", s1)); - ASSERT_EQ("1v1", Get(1, "foo", s1)); - ASSERT_EQ("0v2", Get(0, "foo", s2)); - ASSERT_EQ("1v2", Get(1, "foo", s2)); - ASSERT_EQ("0v3", Get(0, "foo", s3.snapshot())); - ASSERT_EQ("1v3", Get(1, "foo", s3.snapshot())); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - } - - ASSERT_EQ(2U, GetNumSnapshots()); - ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_EQ("0v1", Get(0, "foo", s1)); - ASSERT_EQ("1v1", Get(1, "foo", s1)); - ASSERT_EQ("0v2", Get(0, "foo", s2)); - ASSERT_EQ("1v2", Get(1, "foo", s2)); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - - db_->ReleaseSnapshot(s1); - ASSERT_EQ("0v2", Get(0, "foo", s2)); - ASSERT_EQ("1v2", Get(1, "foo", s2)); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - ASSERT_EQ(1U, GetNumSnapshots()); - ASSERT_LT(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s2->GetSequenceNumber()); - - db_->ReleaseSnapshot(s2); - ASSERT_EQ(0U, GetNumSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), 0); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - } while (ChangeOptions()); -} - - -class DBBasicMultiConfigs : public DBBasicTest, - public ::testing::WithParamInterface { - public: - DBBasicMultiConfigs() { option_config_ = GetParam(); } - - static std::vector GenerateOptionConfigs() { - std::vector option_configs; - for (int option_config = kDefault; option_config < kEnd; ++option_config) { - if (!ShouldSkipOptions(option_config, kSkipFIFOCompaction)) { - option_configs.push_back(option_config); - } - } - return option_configs; - } -}; - -TEST_P(DBBasicMultiConfigs, CompactBetweenSnapshots) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - Options options = CurrentOptions(options_override); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - Random rnd(301); - FillLevels("a", "z", 1); - - ASSERT_OK(Put(1, "foo", "first")); - const Snapshot* snapshot1 = db_->GetSnapshot(); - ASSERT_OK(Put(1, "foo", "second")); - ASSERT_OK(Put(1, "foo", "third")); - ASSERT_OK(Put(1, "foo", "fourth")); - const Snapshot* snapshot2 = db_->GetSnapshot(); - ASSERT_OK(Put(1, "foo", "fifth")); - ASSERT_OK(Put(1, "foo", "sixth")); - - // All entries (including duplicates) exist - // before any compaction or flush is triggered. - ASSERT_EQ(AllEntriesFor("foo", 1), - "[ sixth, fifth, fourth, third, second, first ]"); - ASSERT_EQ("sixth", Get(1, "foo")); - ASSERT_EQ("fourth", Get(1, "foo", snapshot2)); - ASSERT_EQ("first", Get(1, "foo", snapshot1)); - - // After a flush, "second", "third" and "fifth" should - // be removed - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth, fourth, first ]"); - - // after we release the snapshot1, only two values left - db_->ReleaseSnapshot(snapshot1); - FillLevels("a", "z", 1); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr, - nullptr)); - - // We have only one valid snapshot snapshot2. Since snapshot1 is - // not valid anymore, "first" should be removed by a compaction. - ASSERT_EQ("sixth", Get(1, "foo")); - ASSERT_EQ("fourth", Get(1, "foo", snapshot2)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth, fourth ]"); - - // after we release the snapshot2, only one value should be left - db_->ReleaseSnapshot(snapshot2); - FillLevels("a", "z", 1); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr, - nullptr)); - ASSERT_EQ("sixth", Get(1, "foo")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth ]"); -} - -INSTANTIATE_TEST_CASE_P( - DBBasicMultiConfigs, DBBasicMultiConfigs, - ::testing::ValuesIn(DBBasicMultiConfigs::GenerateOptionConfigs())); - -TEST_F(DBBasicTest, DBOpen_Options) { - Options options = CurrentOptions(); - Close(); - Destroy(options); - - // Does not exist, and create_if_missing == false: error - DB* db = nullptr; - options.create_if_missing = false; - Status s = DB::Open(options, dbname_, &db); - ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr); - ASSERT_TRUE(db == nullptr); - - // Does not exist, and create_if_missing == true: OK - options.create_if_missing = true; - s = DB::Open(options, dbname_, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - delete db; - db = nullptr; - - // Does exist, and error_if_exists == true: error - options.create_if_missing = false; - options.error_if_exists = true; - s = DB::Open(options, dbname_, &db); - ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr); - ASSERT_TRUE(db == nullptr); - - // Does exist, and error_if_exists == false: OK - options.create_if_missing = true; - options.error_if_exists = false; - s = DB::Open(options, dbname_, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - delete db; - db = nullptr; -} - -TEST_F(DBBasicTest, CompactOnFlush) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - do { - Options options = CurrentOptions(options_override); - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v1 ]"); - - // Write two new keys - ASSERT_OK(Put(1, "a", "begin")); - ASSERT_OK(Put(1, "z", "end")); - ASSERT_OK(Flush(1)); - - // Case1: Delete followed by a put - ASSERT_OK(Delete(1, "foo")); - ASSERT_OK(Put(1, "foo", "v2")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, DEL, v1 ]"); - - // After the current memtable is flushed, the DEL should - // have been removed - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, v1 ]"); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2 ]"); - - // Case 2: Delete followed by another delete - ASSERT_OK(Delete(1, "foo")); - ASSERT_OK(Delete(1, "foo")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, DEL, v2 ]"); - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v2 ]"); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]"); - - // Case 3: Put followed by a delete - ASSERT_OK(Put(1, "foo", "v3")); - ASSERT_OK(Delete(1, "foo")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v3 ]"); - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL ]"); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]"); - - // Case 4: Put followed by another Put - ASSERT_OK(Put(1, "foo", "v4")); - ASSERT_OK(Put(1, "foo", "v5")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5, v4 ]"); - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5 ]"); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v5 ]"); - - // clear database - ASSERT_OK(Delete(1, "foo")); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]"); - - // Case 5: Put followed by snapshot followed by another Put - // Both puts should remain. - ASSERT_OK(Put(1, "foo", "v6")); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(Put(1, "foo", "v7")); - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v7, v6 ]"); - db_->ReleaseSnapshot(snapshot); - - // clear database - ASSERT_OK(Delete(1, "foo")); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]"); - - // Case 5: snapshot followed by a put followed by another Put - // Only the last put should remain. - const Snapshot* snapshot1 = db_->GetSnapshot(); - ASSERT_OK(Put(1, "foo", "v8")); - ASSERT_OK(Put(1, "foo", "v9")); - ASSERT_OK(Flush(1)); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v9 ]"); - db_->ReleaseSnapshot(snapshot1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, FlushOneColumnFamily) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", - "alyosha", "popovich"}, - options); - - ASSERT_OK(Put(0, "Default", "Default")); - ASSERT_OK(Put(1, "pikachu", "pikachu")); - ASSERT_OK(Put(2, "ilya", "ilya")); - ASSERT_OK(Put(3, "muromec", "muromec")); - ASSERT_OK(Put(4, "dobrynia", "dobrynia")); - ASSERT_OK(Put(5, "nikitich", "nikitich")); - ASSERT_OK(Put(6, "alyosha", "alyosha")); - ASSERT_OK(Put(7, "popovich", "popovich")); - - for (int i = 0; i < 8; ++i) { - ASSERT_OK(Flush(i)); - auto tables = ListTableFiles(env_, dbname_); - ASSERT_EQ(tables.size(), i + 1U); - } -} - -TEST_F(DBBasicTest, MultiGetSimple) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - SetPerfLevel(kEnableCount); - ASSERT_OK(Put(1, "k1", "v1")); - ASSERT_OK(Put(1, "k2", "v2")); - ASSERT_OK(Put(1, "k3", "v3")); - ASSERT_OK(Put(1, "k4", "v4")); - ASSERT_OK(Delete(1, "k4")); - ASSERT_OK(Put(1, "k5", "v5")); - ASSERT_OK(Delete(1, "no_key")); - - std::vector keys({"k1", "k2", "k3", "k4", "k5", "no_key"}); - - std::vector values(20, "Temporary data to be overwritten"); - std::vector cfs(keys.size(), handles_[1]); - - get_perf_context()->Reset(); - std::vector s = db_->MultiGet(ReadOptions(), cfs, keys, &values); - ASSERT_EQ(values.size(), keys.size()); - ASSERT_EQ(values[0], "v1"); - ASSERT_EQ(values[1], "v2"); - ASSERT_EQ(values[2], "v3"); - ASSERT_EQ(values[4], "v5"); - // four kv pairs * two bytes per value - ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes); - - ASSERT_OK(s[0]); - ASSERT_OK(s[1]); - ASSERT_OK(s[2]); - ASSERT_TRUE(s[3].IsNotFound()); - ASSERT_OK(s[4]); - ASSERT_TRUE(s[5].IsNotFound()); - SetPerfLevel(kDisable); - } while (ChangeCompactOptions()); -} - -TEST_F(DBBasicTest, MultiGetEmpty) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - // Empty Key Set - std::vector keys; - std::vector values; - std::vector cfs; - std::vector s = db_->MultiGet(ReadOptions(), cfs, keys, &values); - ASSERT_EQ(s.size(), 0U); - - // Empty Database, Empty Key Set - Options options = CurrentOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - s = db_->MultiGet(ReadOptions(), cfs, keys, &values); - ASSERT_EQ(s.size(), 0U); - - // Empty Database, Search for Keys - keys.resize(2); - keys[0] = "a"; - keys[1] = "b"; - cfs.push_back(handles_[0]); - cfs.push_back(handles_[1]); - s = db_->MultiGet(ReadOptions(), cfs, keys, &values); - ASSERT_EQ(static_cast(s.size()), 2); - ASSERT_TRUE(s[0].IsNotFound() && s[1].IsNotFound()); - } while (ChangeCompactOptions()); -} - -class DBBlockChecksumTest : public DBBasicTest, - public testing::WithParamInterface {}; - -INSTANTIATE_TEST_CASE_P(FormatVersions, DBBlockChecksumTest, - testing::ValuesIn(test::kFooterFormatVersionsToTest)); - -TEST_P(DBBlockChecksumTest, BlockChecksumTest) { - BlockBasedTableOptions table_options; - table_options.format_version = GetParam(); - Options options = CurrentOptions(); - const int kNumPerFile = 2; - - const auto algs = GetSupportedChecksums(); - const int algs_size = static_cast(algs.size()); - - // generate one table with each type of checksum - for (int i = 0; i < algs_size; ++i) { - table_options.checksum = algs[i]; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - for (int j = 0; j < kNumPerFile; ++j) { - ASSERT_OK(Put(Key(i * kNumPerFile + j), Key(i * kNumPerFile + j))); - } - ASSERT_OK(Flush()); - } - - // with each valid checksum type setting... - for (int i = 0; i < algs_size; ++i) { - table_options.checksum = algs[i]; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - // verify every type of checksum (should be regardless of that setting) - for (int j = 0; j < algs_size * kNumPerFile; ++j) { - ASSERT_EQ(Key(j), Get(Key(j))); - } - } - - // Now test invalid checksum type - table_options.checksum = static_cast(123); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - ASSERT_TRUE(TryReopen(options).IsInvalidArgument()); -} - -// On Windows you can have either memory mapped file or a file -// with unbuffered access. So this asserts and does not make -// sense to run -#ifndef OS_WIN -TEST_F(DBBasicTest, MmapAndBufferOptions) { - if (!IsMemoryMappedAccessSupported()) { - return; - } - Options options = CurrentOptions(); - - options.use_direct_reads = true; - options.allow_mmap_reads = true; - ASSERT_NOK(TryReopen(options)); - - // All other combinations are acceptable - options.use_direct_reads = false; - ASSERT_OK(TryReopen(options)); - - if (IsDirectIOSupported()) { - options.use_direct_reads = true; - options.allow_mmap_reads = false; - ASSERT_OK(TryReopen(options)); - } - - options.use_direct_reads = false; - ASSERT_OK(TryReopen(options)); -} -#endif - -class TestEnv : public EnvWrapper { - public: - explicit TestEnv(Env* base_env) : EnvWrapper(base_env), close_count(0) {} - static const char* kClassName() { return "TestEnv"; } - const char* Name() const override { return kClassName(); } - - class TestLogger : public Logger { - public: - using Logger::Logv; - explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; } - ~TestLogger() override { - if (!closed_) { - CloseHelper().PermitUncheckedError(); - } - } - void Logv(const char* /*format*/, va_list /*ap*/) override {} - - protected: - Status CloseImpl() override { return CloseHelper(); } - - private: - Status CloseHelper() { - env->CloseCountInc(); - ; - return Status::IOError(); - } - TestEnv* env; - }; - - void CloseCountInc() { close_count++; } - - int GetCloseCount() { return close_count; } - - Status NewLogger(const std::string& /*fname*/, - std::shared_ptr* result) override { - result->reset(new TestLogger(this)); - return Status::OK(); - } - - private: - int close_count; -}; - -TEST_F(DBBasicTest, DBClose) { - Options options = GetDefaultOptions(); - std::string dbname = test::PerThreadDBPath("db_close_test"); - ASSERT_OK(DestroyDB(dbname, options)); - - DB* db = nullptr; - TestEnv* env = new TestEnv(env_); - std::unique_ptr local_env_guard(env); - options.create_if_missing = true; - options.env = env; - Status s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - s = db->Close(); - ASSERT_EQ(env->GetCloseCount(), 1); - ASSERT_EQ(s, Status::IOError()); - - delete db; - ASSERT_EQ(env->GetCloseCount(), 1); - - // Do not call DB::Close() and ensure our logger Close() still gets called - s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - delete db; - ASSERT_EQ(env->GetCloseCount(), 2); - - // Provide our own logger and ensure DB::Close() does not close it - options.info_log.reset(new TestEnv::TestLogger(env)); - options.create_if_missing = false; - s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - s = db->Close(); - ASSERT_EQ(s, Status::OK()); - delete db; - ASSERT_EQ(env->GetCloseCount(), 2); - options.info_log.reset(); - ASSERT_EQ(env->GetCloseCount(), 3); -} - -TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) { - Options options = GetDefaultOptions(); - std::string dbname = test::PerThreadDBPath("db_close_all_dir_fds_test"); - // Configure a specific WAL directory - options.wal_dir = dbname + "_wal_dir"; - // Configure 3 different data directories - options.db_paths.emplace_back(dbname + "_1", 512 * 1024); - options.db_paths.emplace_back(dbname + "_2", 4 * 1024 * 1024); - options.db_paths.emplace_back(dbname + "_3", 1024 * 1024 * 1024); - - ASSERT_OK(DestroyDB(dbname, options)); - - DB* db = nullptr; - std::unique_ptr env = NewCompositeEnv( - std::make_shared(FileSystem::Default())); - options.create_if_missing = true; - options.env = env.get(); - Status s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - // Explicitly close the database to ensure the open and close counter for - // directories are equivalent - s = db->Close(); - auto* counted_fs = - options.env->GetFileSystem()->CheckedCast(); - ASSERT_TRUE(counted_fs != nullptr); - ASSERT_EQ(counted_fs->counters()->dir_opens, - counted_fs->counters()->dir_closes); - ASSERT_OK(s); - delete db; -} - -TEST_F(DBBasicTest, DBCloseFlushError) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.manual_wal_flush = true; - options.write_buffer_size = 100; - options.env = fault_injection_env.get(); - - Reopen(options); - ASSERT_OK(Put("key1", "value1")); - ASSERT_OK(Put("key2", "value2")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - ASSERT_OK(Put("key3", "value3")); - fault_injection_env->SetFilesystemActive(false); - Status s = dbfull()->Close(); - ASSERT_NE(s, Status::OK()); - // retry should return the same error - s = dbfull()->Close(); - ASSERT_NE(s, Status::OK()); - fault_injection_env->SetFilesystemActive(true); - // retry close() is no-op even the system is back. Could be improved if - // Close() is retry-able: #9029 - s = dbfull()->Close(); - ASSERT_NE(s, Status::OK()); - Destroy(options); -} - -class DBMultiGetTestWithParam - : public DBBasicTest, - public testing::WithParamInterface> {}; - -TEST_P(DBMultiGetTestWithParam, MultiGetMultiCF) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", - "alyosha", "popovich"}, - options); - // tuples - std::vector> cf_kv_vec; - static const int num_keys = 24; - cf_kv_vec.reserve(num_keys); - - for (int i = 0; i < num_keys; ++i) { - int cf = i / 3; - int cf_key = 1 % 3; - cf_kv_vec.emplace_back(std::make_tuple( - cf, "cf" + std::to_string(cf) + "_key_" + std::to_string(cf_key), - "cf" + std::to_string(cf) + "_val_" + std::to_string(cf_key))); - ASSERT_OK(Put(std::get<0>(cf_kv_vec[i]), std::get<1>(cf_kv_vec[i]), - std::get<2>(cf_kv_vec[i]))); - } - - int get_sv_count = 0; - ROCKSDB_NAMESPACE::DBImpl* db = static_cast_with_check(db_); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) { - if (++get_sv_count == 2) { - // After MultiGet refs a couple of CFs, flush all CFs so MultiGet - // is forced to repeat the process - for (int i = 0; i < num_keys; ++i) { - int cf = i / 3; - int cf_key = i % 8; - if (cf_key == 0) { - ASSERT_OK(Flush(cf)); - } - ASSERT_OK(Put(std::get<0>(cf_kv_vec[i]), std::get<1>(cf_kv_vec[i]), - std::get<2>(cf_kv_vec[i]) + "_2")); - } - } - if (get_sv_count == 11) { - for (int i = 0; i < 8; ++i) { - auto* cfd = static_cast_with_check( - db->GetColumnFamilyHandle(i)) - ->cfd(); - ASSERT_EQ(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::vector cfs; - std::vector keys; - std::vector values; - - for (int i = 0; i < num_keys; ++i) { - cfs.push_back(std::get<0>(cf_kv_vec[i])); - keys.push_back(std::get<1>(cf_kv_vec[i])); - } - - values = MultiGet(cfs, keys, nullptr, std::get<0>(GetParam()), - std::get<1>(GetParam())); - ASSERT_EQ(values.size(), num_keys); - for (unsigned int j = 0; j < values.size(); ++j) { - ASSERT_EQ(values[j], std::get<2>(cf_kv_vec[j]) + "_2"); - } - - keys.clear(); - cfs.clear(); - cfs.push_back(std::get<0>(cf_kv_vec[0])); - keys.push_back(std::get<1>(cf_kv_vec[0])); - cfs.push_back(std::get<0>(cf_kv_vec[3])); - keys.push_back(std::get<1>(cf_kv_vec[3])); - cfs.push_back(std::get<0>(cf_kv_vec[4])); - keys.push_back(std::get<1>(cf_kv_vec[4])); - values = MultiGet(cfs, keys, nullptr, std::get<0>(GetParam()), - std::get<1>(GetParam())); - ASSERT_EQ(values[0], std::get<2>(cf_kv_vec[0]) + "_2"); - ASSERT_EQ(values[1], std::get<2>(cf_kv_vec[3]) + "_2"); - ASSERT_EQ(values[2], std::get<2>(cf_kv_vec[4]) + "_2"); - - keys.clear(); - cfs.clear(); - cfs.push_back(std::get<0>(cf_kv_vec[7])); - keys.push_back(std::get<1>(cf_kv_vec[7])); - cfs.push_back(std::get<0>(cf_kv_vec[6])); - keys.push_back(std::get<1>(cf_kv_vec[6])); - cfs.push_back(std::get<0>(cf_kv_vec[1])); - keys.push_back(std::get<1>(cf_kv_vec[1])); - values = MultiGet(cfs, keys, nullptr, std::get<0>(GetParam()), - std::get<1>(GetParam())); - ASSERT_EQ(values[0], std::get<2>(cf_kv_vec[7]) + "_2"); - ASSERT_EQ(values[1], std::get<2>(cf_kv_vec[6]) + "_2"); - ASSERT_EQ(values[2], std::get<2>(cf_kv_vec[1]) + "_2"); - - for (int cf = 0; cf < 8; ++cf) { - auto* cfd = - static_cast_with_check( - static_cast_with_check(db_)->GetColumnFamilyHandle(cf)) - ->cfd(); - ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); - ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete); - } -} - -TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFMutex) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", - "alyosha", "popovich"}, - options); - - for (int i = 0; i < 8; ++i) { - ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", - "cf" + std::to_string(i) + "_val")); - } - - int get_sv_count = 0; - int retries = 0; - bool last_try = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::MultiGet::LastTry", [&](void* /*arg*/) { - last_try = true; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) { - if (last_try) { - return; - } - if (++get_sv_count == 2) { - ++retries; - get_sv_count = 0; - for (int i = 0; i < 8; ++i) { - ASSERT_OK(Flush(i)); - ASSERT_OK(Put( - i, "cf" + std::to_string(i) + "_key", - "cf" + std::to_string(i) + "_val" + std::to_string(retries))); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::vector cfs; - std::vector keys; - std::vector values; - - for (int i = 0; i < 8; ++i) { - cfs.push_back(i); - keys.push_back("cf" + std::to_string(i) + "_key"); - } - - values = MultiGet(cfs, keys, nullptr, std::get<0>(GetParam()), - std::get<1>(GetParam())); - ASSERT_TRUE(last_try); - ASSERT_EQ(values.size(), 8); - for (unsigned int j = 0; j < values.size(); ++j) { - ASSERT_EQ(values[j], - "cf" + std::to_string(j) + "_val" + std::to_string(retries)); - } - for (int i = 0; i < 8; ++i) { - auto* cfd = - static_cast_with_check( - static_cast_with_check(db_)->GetColumnFamilyHandle(i)) - ->cfd(); - ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); - } -} - -TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFSnapshot) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", - "alyosha", "popovich"}, - options); - - for (int i = 0; i < 8; ++i) { - ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", - "cf" + std::to_string(i) + "_val")); - } - - int get_sv_count = 0; - ROCKSDB_NAMESPACE::DBImpl* db = static_cast_with_check(db_); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) { - if (++get_sv_count == 2) { - for (int i = 0; i < 8; ++i) { - ASSERT_OK(Flush(i)); - ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", - "cf" + std::to_string(i) + "_val2")); - } - } - if (get_sv_count == 8) { - for (int i = 0; i < 8; ++i) { - auto* cfd = static_cast_with_check( - db->GetColumnFamilyHandle(i)) - ->cfd(); - ASSERT_TRUE( - (cfd->TEST_GetLocalSV()->Get() == SuperVersion::kSVInUse) || - (cfd->TEST_GetLocalSV()->Get() == SuperVersion::kSVObsolete)); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::vector cfs; - std::vector keys; - std::vector values; - - for (int i = 0; i < 8; ++i) { - cfs.push_back(i); - keys.push_back("cf" + std::to_string(i) + "_key"); - } - - const Snapshot* snapshot = db_->GetSnapshot(); - values = MultiGet(cfs, keys, snapshot, std::get<0>(GetParam()), - std::get<1>(GetParam())); - db_->ReleaseSnapshot(snapshot); - ASSERT_EQ(values.size(), 8); - for (unsigned int j = 0; j < values.size(); ++j) { - ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val"); - } - for (int i = 0; i < 8; ++i) { - auto* cfd = - static_cast_with_check( - static_cast_with_check(db_)->GetColumnFamilyHandle(i)) - ->cfd(); - ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); - } -} - -TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFUnsorted) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - Options options = CurrentOptions(); - CreateAndReopenWithCF({"one", "two"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(2, "baz", "xyz")); - ASSERT_OK(Put(1, "abc", "def")); - - // Note: keys for the same CF do not form a consecutive range - std::vector cfs{1, 2, 1}; - std::vector keys{"foo", "baz", "abc"}; - std::vector values; - - values = MultiGet(cfs, keys, /* snapshot */ nullptr, - /* batched */ std::get<0>(GetParam()), - /* async */ std::get<1>(GetParam())); - - ASSERT_EQ(values.size(), 3); - ASSERT_EQ(values[0], "bar"); - ASSERT_EQ(values[1], "xyz"); - ASSERT_EQ(values[2], "def"); -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedSimpleUnsorted) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - SetPerfLevel(kEnableCount); - ASSERT_OK(Put(1, "k1", "v1")); - ASSERT_OK(Put(1, "k2", "v2")); - ASSERT_OK(Put(1, "k3", "v3")); - ASSERT_OK(Put(1, "k4", "v4")); - ASSERT_OK(Delete(1, "k4")); - ASSERT_OK(Put(1, "k5", "v5")); - ASSERT_OK(Delete(1, "no_key")); - - get_perf_context()->Reset(); - - std::vector keys({"no_key", "k5", "k4", "k3", "k2", "k1"}); - std::vector values(keys.size()); - std::vector cfs(keys.size(), handles_[1]); - std::vector s(keys.size()); - - ReadOptions ro; - ro.async_io = std::get<1>(GetParam()); - db_->MultiGet(ro, handles_[1], keys.size(), keys.data(), values.data(), - s.data(), false); - - ASSERT_EQ(values.size(), keys.size()); - ASSERT_EQ(std::string(values[5].data(), values[5].size()), "v1"); - ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v2"); - ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v3"); - ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v5"); - // four kv pairs * two bytes per value - ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes); - - ASSERT_TRUE(s[0].IsNotFound()); - ASSERT_OK(s[1]); - ASSERT_TRUE(s[2].IsNotFound()); - ASSERT_OK(s[3]); - ASSERT_OK(s[4]); - ASSERT_OK(s[5]); - - SetPerfLevel(kDisable); - } while (ChangeCompactOptions()); -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedSortedMultiFile) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - SetPerfLevel(kEnableCount); - // To expand the power of this test, generate > 1 table file and - // mix with memtable - ASSERT_OK(Put(1, "k1", "v1")); - ASSERT_OK(Put(1, "k2", "v2")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "k3", "v3")); - ASSERT_OK(Put(1, "k4", "v4")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Delete(1, "k4")); - ASSERT_OK(Put(1, "k5", "v5")); - ASSERT_OK(Delete(1, "no_key")); - - get_perf_context()->Reset(); - - std::vector keys({"k1", "k2", "k3", "k4", "k5", "no_key"}); - std::vector values(keys.size()); - std::vector cfs(keys.size(), handles_[1]); - std::vector s(keys.size()); - - ReadOptions ro; - ro.async_io = std::get<1>(GetParam()); - db_->MultiGet(ro, handles_[1], keys.size(), keys.data(), values.data(), - s.data(), true); - - ASSERT_EQ(values.size(), keys.size()); - ASSERT_EQ(std::string(values[0].data(), values[0].size()), "v1"); - ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v2"); - ASSERT_EQ(std::string(values[2].data(), values[2].size()), "v3"); - ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v5"); - // four kv pairs * two bytes per value - ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes); - - ASSERT_OK(s[0]); - ASSERT_OK(s[1]); - ASSERT_OK(s[2]); - ASSERT_TRUE(s[3].IsNotFound()); - ASSERT_OK(s[4]); - ASSERT_TRUE(s[5].IsNotFound()); - - SetPerfLevel(kDisable); - } while (ChangeOptions()); -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedDuplicateKeys) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - Options opts = CurrentOptions(); - opts.merge_operator = MergeOperators::CreateStringAppendOperator(); - CreateAndReopenWithCF({"pikachu"}, opts); - SetPerfLevel(kEnableCount); - // To expand the power of this test, generate > 1 table file and - // mix with memtable - ASSERT_OK(Merge(1, "k1", "v1")); - ASSERT_OK(Merge(1, "k2", "v2")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - ASSERT_OK(Merge(1, "k3", "v3")); - ASSERT_OK(Merge(1, "k4", "v4")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - ASSERT_OK(Merge(1, "k4", "v4_2")); - ASSERT_OK(Merge(1, "k6", "v6")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - ASSERT_OK(Merge(1, "k7", "v7")); - ASSERT_OK(Merge(1, "k8", "v8")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - - get_perf_context()->Reset(); - - std::vector keys({"k8", "k8", "k8", "k4", "k4", "k1", "k3"}); - std::vector values(keys.size()); - std::vector cfs(keys.size(), handles_[1]); - std::vector s(keys.size()); - - ReadOptions ro; - ro.async_io = std::get<1>(GetParam()); - db_->MultiGet(ro, handles_[1], keys.size(), keys.data(), values.data(), - s.data(), false); - - ASSERT_EQ(values.size(), keys.size()); - ASSERT_EQ(std::string(values[0].data(), values[0].size()), "v8"); - ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v8"); - ASSERT_EQ(std::string(values[2].data(), values[2].size()), "v8"); - ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v4,v4_2"); - ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v4,v4_2"); - ASSERT_EQ(std::string(values[5].data(), values[5].size()), "v1"); - ASSERT_EQ(std::string(values[6].data(), values[6].size()), "v3"); - ASSERT_EQ(24, (int)get_perf_context()->multiget_read_bytes); - - for (Status& status : s) { - ASSERT_OK(status); - } - - SetPerfLevel(kDisable); -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedMultiLevel) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - Reopen(options); - int num_keys = 0; - - for (int i = 0; i < 128; ++i) { - ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(2); - - for (int i = 0; i < 128; i += 3) { - ASSERT_OK(Put("key_" + std::to_string(i), "val_l1_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(1); - - for (int i = 0; i < 128; i += 5) { - ASSERT_OK(Put("key_" + std::to_string(i), "val_l0_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - ASSERT_EQ(0, num_keys); - - for (int i = 0; i < 128; i += 9) { - ASSERT_OK(Put("key_" + std::to_string(i), "val_mem_" + std::to_string(i))); - } - - std::vector keys; - std::vector values; - - for (int i = 64; i < 80; ++i) { - keys.push_back("key_" + std::to_string(i)); - } - - values = MultiGet(keys, nullptr, std::get<1>(GetParam())); - ASSERT_EQ(values.size(), 16); - for (unsigned int j = 0; j < values.size(); ++j) { - int key = j + 64; - if (key % 9 == 0) { - ASSERT_EQ(values[j], "val_mem_" + std::to_string(key)); - } else if (key % 5 == 0) { - ASSERT_EQ(values[j], "val_l0_" + std::to_string(key)); - } else if (key % 3 == 0) { - ASSERT_EQ(values[j], "val_l1_" + std::to_string(key)); - } else { - ASSERT_EQ(values[j], "val_l2_" + std::to_string(key)); - } - } -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedMultiLevelMerge) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - int num_keys = 0; - - for (int i = 0; i < 128; ++i) { - ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(2); - - for (int i = 0; i < 128; i += 3) { - ASSERT_OK(Merge("key_" + std::to_string(i), "val_l1_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(1); - - for (int i = 0; i < 128; i += 5) { - ASSERT_OK(Merge("key_" + std::to_string(i), "val_l0_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - ASSERT_EQ(0, num_keys); - - for (int i = 0; i < 128; i += 9) { - ASSERT_OK( - Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i))); - } - - std::vector keys; - std::vector values; - - for (int i = 32; i < 80; ++i) { - keys.push_back("key_" + std::to_string(i)); - } - - values = MultiGet(keys, nullptr, std::get<1>(GetParam())); - ASSERT_EQ(values.size(), keys.size()); - for (unsigned int j = 0; j < 48; ++j) { - int key = j + 32; - std::string value; - value.append("val_l2_" + std::to_string(key)); - if (key % 3 == 0) { - value.append(","); - value.append("val_l1_" + std::to_string(key)); - } - if (key % 5 == 0) { - value.append(","); - value.append("val_l0_" + std::to_string(key)); - } - if (key % 9 == 0) { - value.append(","); - value.append("val_mem_" + std::to_string(key)); - } - ASSERT_EQ(values[j], value); - } -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedValueSizeInMemory) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - SetPerfLevel(kEnableCount); - ASSERT_OK(Put(1, "k1", "v_1")); - ASSERT_OK(Put(1, "k2", "v_2")); - ASSERT_OK(Put(1, "k3", "v_3")); - ASSERT_OK(Put(1, "k4", "v_4")); - ASSERT_OK(Put(1, "k5", "v_5")); - ASSERT_OK(Put(1, "k6", "v_6")); - std::vector keys = {"k1", "k2", "k3", "k4", "k5", "k6"}; - std::vector values(keys.size()); - std::vector s(keys.size()); - std::vector cfs(keys.size(), handles_[1]); - - get_perf_context()->Reset(); - ReadOptions ro; - ro.value_size_soft_limit = 11; - ro.async_io = std::get<1>(GetParam()); - db_->MultiGet(ro, handles_[1], keys.size(), keys.data(), values.data(), - s.data(), false); - - ASSERT_EQ(values.size(), keys.size()); - for (unsigned int i = 0; i < 4; i++) { - ASSERT_EQ(std::string(values[i].data(), values[i].size()), - "v_" + std::to_string(i + 1)); - } - - for (unsigned int i = 4; i < 6; i++) { - ASSERT_TRUE(s[i].IsAborted()); - } - - ASSERT_EQ(12, (int)get_perf_context()->multiget_read_bytes); - SetPerfLevel(kDisable); -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedValueSize) { -#ifndef USE_COROUTINES - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - return; - } - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - SetPerfLevel(kEnableCount); - - ASSERT_OK(Put(1, "k6", "v6")); - ASSERT_OK(Put(1, "k7", "v7_")); - ASSERT_OK(Put(1, "k3", "v3_")); - ASSERT_OK(Put(1, "k4", "v4")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Delete(1, "k4")); - ASSERT_OK(Put(1, "k11", "v11")); - ASSERT_OK(Delete(1, "no_key")); - ASSERT_OK(Put(1, "k8", "v8_")); - ASSERT_OK(Put(1, "k13", "v13")); - ASSERT_OK(Put(1, "k14", "v14")); - ASSERT_OK(Put(1, "k15", "v15")); - ASSERT_OK(Put(1, "k16", "v16")); - ASSERT_OK(Put(1, "k17", "v17")); - ASSERT_OK(Flush(1)); - - ASSERT_OK(Put(1, "k1", "v1_")); - ASSERT_OK(Put(1, "k2", "v2_")); - ASSERT_OK(Put(1, "k5", "v5_")); - ASSERT_OK(Put(1, "k9", "v9_")); - ASSERT_OK(Put(1, "k10", "v10")); - ASSERT_OK(Delete(1, "k2")); - ASSERT_OK(Delete(1, "k6")); - - get_perf_context()->Reset(); - - std::vector keys({"k1", "k10", "k11", "k12", "k13", "k14", "k15", - "k16", "k17", "k2", "k3", "k4", "k5", "k6", "k7", - "k8", "k9", "no_key"}); - std::vector values(keys.size()); - std::vector cfs(keys.size(), handles_[1]); - std::vector s(keys.size()); - - ReadOptions ro; - ro.value_size_soft_limit = 20; - ro.async_io = std::get<1>(GetParam()); - db_->MultiGet(ro, handles_[1], keys.size(), keys.data(), values.data(), - s.data(), false); - - ASSERT_EQ(values.size(), keys.size()); - - // In memory keys - ASSERT_EQ(std::string(values[0].data(), values[0].size()), "v1_"); - ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v10"); - ASSERT_TRUE(s[9].IsNotFound()); // k2 - ASSERT_EQ(std::string(values[12].data(), values[12].size()), "v5_"); - ASSERT_TRUE(s[13].IsNotFound()); // k6 - ASSERT_EQ(std::string(values[16].data(), values[16].size()), "v9_"); - - // In sst files - ASSERT_EQ(std::string(values[2].data(), values[1].size()), "v11"); - ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v13"); - ASSERT_EQ(std::string(values[5].data(), values[5].size()), "v14"); - - // Remaining aborted after value_size exceeds. - ASSERT_TRUE(s[3].IsAborted()); - ASSERT_TRUE(s[6].IsAborted()); - ASSERT_TRUE(s[7].IsAborted()); - ASSERT_TRUE(s[8].IsAborted()); - ASSERT_TRUE(s[10].IsAborted()); - ASSERT_TRUE(s[11].IsAborted()); - ASSERT_TRUE(s[14].IsAborted()); - ASSERT_TRUE(s[15].IsAborted()); - ASSERT_TRUE(s[17].IsAborted()); - - // 6 kv pairs * 3 bytes per value (i.e. 18) - ASSERT_EQ(21, (int)get_perf_context()->multiget_read_bytes); - SetPerfLevel(kDisable); - } while (ChangeCompactOptions()); -} - -TEST_P(DBMultiGetTestWithParam, MultiGetBatchedValueSizeMultiLevelMerge) { - if (std::get<1>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test needs to be fixed for async IO"); - return; - } - // Skip for unbatched MultiGet - if (!std::get<0>(GetParam())) { - ROCKSDB_GTEST_BYPASS("This test is only for batched MultiGet"); - return; - } - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - int num_keys = 0; - - for (int i = 0; i < 64; ++i) { - ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(2); - - for (int i = 0; i < 64; i += 3) { - ASSERT_OK(Merge("key_" + std::to_string(i), "val_l1_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(1); - - for (int i = 0; i < 64; i += 5) { - ASSERT_OK(Merge("key_" + std::to_string(i), "val_l0_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - ASSERT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - ASSERT_OK(Flush()); - num_keys = 0; - } - ASSERT_EQ(0, num_keys); - - for (int i = 0; i < 64; i += 9) { - ASSERT_OK( - Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i))); - } - - std::vector keys_str; - for (int i = 10; i < 50; ++i) { - keys_str.push_back("key_" + std::to_string(i)); - } - - std::vector keys(keys_str.size()); - for (int i = 0; i < 40; i++) { - keys[i] = Slice(keys_str[i]); - } - - std::vector values(keys_str.size()); - std::vector statuses(keys_str.size()); - ReadOptions read_options; - read_options.verify_checksums = true; - read_options.value_size_soft_limit = 380; - read_options.async_io = std::get<1>(GetParam()); - db_->MultiGet(read_options, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - - ASSERT_EQ(values.size(), keys.size()); - - for (unsigned int j = 0; j < 26; ++j) { - int key = j + 10; - std::string value; - value.append("val_l2_" + std::to_string(key)); - if (key % 3 == 0) { - value.append(","); - value.append("val_l1_" + std::to_string(key)); - } - if (key % 5 == 0) { - value.append(","); - value.append("val_l0_" + std::to_string(key)); - } - if (key % 9 == 0) { - value.append(","); - value.append("val_mem_" + std::to_string(key)); - } - ASSERT_EQ(values[j], value); - ASSERT_OK(statuses[j]); - } - - // All remaning keys status is set Status::Abort - for (unsigned int j = 26; j < 40; j++) { - ASSERT_TRUE(statuses[j].IsAborted()); - } -} - -INSTANTIATE_TEST_CASE_P(DBMultiGetTestWithParam, DBMultiGetTestWithParam, - testing::Combine(testing::Bool(), testing::Bool())); - -#if USE_COROUTINES -class DBMultiGetAsyncIOTest : public DBBasicTest, - public ::testing::WithParamInterface { - public: - DBMultiGetAsyncIOTest() - : DBBasicTest(), statistics_(ROCKSDB_NAMESPACE::CreateDBStatistics()) { - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10)); - options_ = CurrentOptions(); - options_.disable_auto_compactions = true; - options_.statistics = statistics_; - options_.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options_.env = Env::Default(); - Reopen(options_); - int num_keys = 0; - - // Put all keys in the bottommost level, and overwrite some keys - // in L0 and L1 - for (int i = 0; i < 256; ++i) { - EXPECT_OK(Put(Key(i), "val_l2_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - EXPECT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - EXPECT_OK(Flush()); - num_keys = 0; - } - MoveFilesToLevel(2); - - for (int i = 0; i < 128; i += 3) { - EXPECT_OK(Put(Key(i), "val_l1_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - EXPECT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - EXPECT_OK(Flush()); - num_keys = 0; - } - // Put some range deletes in L1 - for (int i = 128; i < 256; i += 32) { - std::string range_begin = Key(i); - std::string range_end = Key(i + 16); - EXPECT_OK(dbfull()->DeleteRange(WriteOptions(), - dbfull()->DefaultColumnFamily(), - range_begin, range_end)); - // Also do some Puts to force creation of bloom filter - for (int j = i + 16; j < i + 32; ++j) { - if (j % 3 == 0) { - EXPECT_OK(Put(Key(j), "val_l1_" + std::to_string(j))); - } - } - EXPECT_OK(Flush()); - } - MoveFilesToLevel(1); - - for (int i = 0; i < 128; i += 5) { - EXPECT_OK(Put(Key(i), "val_l0_" + std::to_string(i))); - num_keys++; - if (num_keys == 8) { - EXPECT_OK(Flush()); - num_keys = 0; - } - } - if (num_keys > 0) { - EXPECT_OK(Flush()); - num_keys = 0; - } - EXPECT_EQ(0, num_keys); - } - - const std::shared_ptr& statistics() { return statistics_; } - - protected: - void PrepareDBForTest() { -#ifdef ROCKSDB_IOURING_PRESENT - Reopen(options_); -#else // ROCKSDB_IOURING_PRESENT - // Warm up the block cache so we don't need to use the IO uring - Iterator* iter = dbfull()->NewIterator(ReadOptions()); - for (iter->SeekToFirst(); iter->Valid() && iter->status().ok(); - iter->Next()) - ; - EXPECT_OK(iter->status()); - delete iter; -#endif // ROCKSDB_IOURING_PRESENT - } - - void ReopenDB() { Reopen(options_); } - - private: - std::shared_ptr statistics_; - Options options_; -}; - -TEST_P(DBMultiGetAsyncIOTest, GetFromL0) { - // All 3 keys in L0. The L0 files should be read serially. - std::vector key_strs{Key(0), Key(40), Key(80)}; - std::vector keys{key_strs[0], key_strs[1], key_strs[2]}; - std::vector values(key_strs.size()); - std::vector statuses(key_strs.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 3); - ASSERT_OK(statuses[0]); - ASSERT_OK(statuses[1]); - ASSERT_OK(statuses[2]); - ASSERT_EQ(values[0], "val_l0_" + std::to_string(0)); - ASSERT_EQ(values[1], "val_l0_" + std::to_string(40)); - ASSERT_EQ(values[2], "val_l0_" + std::to_string(80)); - - HistogramData multiget_io_batch_size; - - statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size); - - // With async IO, lookups will happen in parallel for each key -#ifdef ROCKSDB_IOURING_PRESENT - if (GetParam()) { - ASSERT_EQ(multiget_io_batch_size.count, 1); - ASSERT_EQ(multiget_io_batch_size.max, 3); - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 3); - } else { - // Without Async IO, MultiGet will call MultiRead 3 times, once for each - // L0 file - ASSERT_EQ(multiget_io_batch_size.count, 3); - } -#else // ROCKSDB_IOURING_PRESENT - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0); -#endif // ROCKSDB_IOURING_PRESENT -} - -TEST_P(DBMultiGetAsyncIOTest, GetFromL1) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - key_strs.push_back(Key(33)); - key_strs.push_back(Key(54)); - key_strs.push_back(Key(102)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - keys.push_back(key_strs[2]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 3); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(statuses[2], Status::OK()); - ASSERT_EQ(values[0], "val_l1_" + std::to_string(33)); - ASSERT_EQ(values[1], "val_l1_" + std::to_string(54)); - ASSERT_EQ(values[2], "val_l1_" + std::to_string(102)); - - HistogramData multiget_io_batch_size; - - statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size); - -#ifdef ROCKSDB_IOURING_PRESENT - // A batch of 3 async IOs is expected, one for each overlapping file in L1 - ASSERT_EQ(multiget_io_batch_size.count, 1); - ASSERT_EQ(multiget_io_batch_size.max, 3); - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 3); -#else // ROCKSDB_IOURING_PRESENT - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0); -#endif // ROCKSDB_IOURING_PRESENT -} - -#ifdef ROCKSDB_IOURING_PRESENT -TEST_P(DBMultiGetAsyncIOTest, GetFromL1Error) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - key_strs.push_back(Key(33)); - key_strs.push_back(Key(54)); - key_strs.push_back(Key(102)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - keys.push_back(key_strs[2]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - int count = 0; - SyncPoint::GetInstance()->SetCallBack( - "TableCache::GetTableReader:BeforeOpenFile", [&](void* status) { - count++; - // Fail the last table reader open, which is the 6th SST file - // since 3 overlapping L0 files + 3 L1 files containing the keys - if (count == 6) { - Status* s = static_cast(status); - *s = Status::IOError(); - } - }); - // DB open will create table readers unless we reduce the table cache - // capacity. - // SanitizeOptions will set max_open_files to minimum of 20. Table cache - // is allocated with max_open_files - 10 as capacity. So override - // max_open_files to 11 so table cache capacity will become 1. This will - // prevent file open during DB open and force the file to be opened - // during MultiGet - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = (int*)arg; - *max_open_files = 11; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(values.size(), 3); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(statuses[2], Status::IOError()); - - HistogramData multiget_io_batch_size; - - statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size); - - // A batch of 3 async IOs is expected, one for each overlapping file in L1 - ASSERT_EQ(multiget_io_batch_size.count, 1); - ASSERT_EQ(multiget_io_batch_size.max, 2); - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 2); -} -#endif // ROCKSDB_IOURING_PRESENT - -TEST_P(DBMultiGetAsyncIOTest, LastKeyInFile) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - // 21 is the last key in the first L1 file - key_strs.push_back(Key(21)); - key_strs.push_back(Key(54)); - key_strs.push_back(Key(102)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - keys.push_back(key_strs[2]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 3); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(statuses[2], Status::OK()); - ASSERT_EQ(values[0], "val_l1_" + std::to_string(21)); - ASSERT_EQ(values[1], "val_l1_" + std::to_string(54)); - ASSERT_EQ(values[2], "val_l1_" + std::to_string(102)); - -#ifdef ROCKSDB_IOURING_PRESENT - HistogramData multiget_io_batch_size; - - statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size); - - // Since the first MultiGet key is the last key in a file, the MultiGet is - // expected to lookup in that file first, before moving on to other files. - // So the first file lookup will issue one async read, and the next lookup - // will lookup 2 files in parallel and issue 2 async reads - ASSERT_EQ(multiget_io_batch_size.count, 2); - ASSERT_EQ(multiget_io_batch_size.max, 2); -#endif // ROCKSDB_IOURING_PRESENT -} - -TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - // 33 and 102 are in L1, and 56 is in L2 - key_strs.push_back(Key(33)); - key_strs.push_back(Key(56)); - key_strs.push_back(Key(102)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - keys.push_back(key_strs[2]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 3); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(statuses[2], Status::OK()); - ASSERT_EQ(values[0], "val_l1_" + std::to_string(33)); - ASSERT_EQ(values[1], "val_l2_" + std::to_string(56)); - ASSERT_EQ(values[2], "val_l1_" + std::to_string(102)); - -#ifdef ROCKSDB_IOURING_PRESENT - HistogramData multiget_io_batch_size; - - statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size); - - // There are 2 keys in L1 in twp separate files, and 1 in L2. With - // optimize_multiget_for_io, all three lookups will happen in parallel. - // Otherwise, the L2 lookup will happen after L1. - ASSERT_EQ(multiget_io_batch_size.count, GetParam() ? 1 : 2); - ASSERT_EQ(multiget_io_batch_size.max, GetParam() ? 3 : 2); -#endif // ROCKSDB_IOURING_PRESENT -} - -TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeOverlapL0L1) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - // 19 and 26 are in L2, but overlap with L0 and L1 file ranges - key_strs.push_back(Key(19)); - key_strs.push_back(Key(26)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 2); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(values[0], "val_l2_" + std::to_string(19)); - ASSERT_EQ(values[1], "val_l2_" + std::to_string(26)); - -#ifdef ROCKSDB_IOURING_PRESENT - // Bloom filters in L0/L1 will avoid the coroutine calls in those levels - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 2); -#else // ROCKSDB_IOURING_PRESENT - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0); -#endif // ROCKSDB_IOURING_PRESENT -} - -#ifdef ROCKSDB_IOURING_PRESENT -TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeDelInL1) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - // 139 and 163 are in L2, but overlap with a range deletes in L1 - key_strs.push_back(Key(139)); - key_strs.push_back(Key(163)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 2); - ASSERT_EQ(statuses[0], Status::NotFound()); - ASSERT_EQ(statuses[1], Status::NotFound()); - - // Bloom filters in L0/L1 will avoid the coroutine calls in those levels - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 2); -} - -TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2WithRangeDelInL1) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - // 139 and 163 are in L2, but overlap with a range deletes in L1 - key_strs.push_back(Key(139)); - key_strs.push_back(Key(144)); - key_strs.push_back(Key(163)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - keys.push_back(key_strs[2]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - PrepareDBForTest(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), keys.size()); - ASSERT_EQ(statuses[0], Status::NotFound()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(values[1], "val_l1_" + std::to_string(144)); - ASSERT_EQ(statuses[2], Status::NotFound()); - - // Bloom filters in L0/L1 will avoid the coroutine calls in those levels - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 3); -} -#endif // ROCKSDB_IOURING_PRESENT - -TEST_P(DBMultiGetAsyncIOTest, GetNoIOUring) { - std::vector key_strs; - std::vector keys; - std::vector values; - std::vector statuses; - - key_strs.push_back(Key(33)); - key_strs.push_back(Key(54)); - key_strs.push_back(Key(102)); - keys.push_back(key_strs[0]); - keys.push_back(key_strs[1]); - keys.push_back(key_strs[2]); - values.resize(keys.size()); - statuses.resize(keys.size()); - - enable_io_uring = false; - ReopenDB(); - - ReadOptions ro; - ro.async_io = true; - ro.optimize_multiget_for_io = GetParam(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data()); - ASSERT_EQ(values.size(), 3); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::OK()); - ASSERT_EQ(statuses[2], Status::OK()); - - HistogramData async_read_bytes; - - statistics()->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - - // A batch of 3 async IOs is expected, one for each overlapping file in L1 - ASSERT_EQ(async_read_bytes.count, 0); - ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0); -} - -INSTANTIATE_TEST_CASE_P(DBMultiGetAsyncIOTest, DBMultiGetAsyncIOTest, - testing::Bool()); -#endif // USE_COROUTINES - -TEST_F(DBBasicTest, MultiGetStats) { - Options options; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.env = env_; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.block_size = 1; - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - table_options.partition_filters = true; - table_options.no_block_cache = true; - table_options.cache_index_and_filter_blocks = false; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - CreateAndReopenWithCF({"pikachu"}, options); - - int total_keys = 2000; - std::vector keys_str(total_keys); - std::vector keys(total_keys); - static size_t kMultiGetBatchSize = 100; - std::vector values(kMultiGetBatchSize); - std::vector s(kMultiGetBatchSize); - ReadOptions read_opts; - - Random rnd(309); - // Create Multiple SST files at multiple levels. - for (int i = 0; i < 500; ++i) { - keys_str[i] = "k" + std::to_string(i); - keys[i] = Slice(keys_str[i]); - ASSERT_OK(Put(1, "k" + std::to_string(i), rnd.RandomString(1000))); - if (i % 100 == 0) { - ASSERT_OK(Flush(1)); - } - } - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - - for (int i = 501; i < 1000; ++i) { - keys_str[i] = "k" + std::to_string(i); - keys[i] = Slice(keys_str[i]); - ASSERT_OK(Put(1, "k" + std::to_string(i), rnd.RandomString(1000))); - if (i % 100 == 0) { - ASSERT_OK(Flush(1)); - } - } - - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - - for (int i = 1001; i < total_keys; ++i) { - keys_str[i] = "k" + std::to_string(i); - keys[i] = Slice(keys_str[i]); - ASSERT_OK(Put(1, "k" + std::to_string(i), rnd.RandomString(1000))); - if (i % 100 == 0) { - ASSERT_OK(Flush(1)); - } - } - ASSERT_OK(Flush(1)); - MoveFilesToLevel(1, 1); - Close(); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK(options.statistics->Reset()); - - db_->MultiGet(read_opts, handles_[1], kMultiGetBatchSize, &keys[1250], - values.data(), s.data(), false); - - ASSERT_EQ(values.size(), kMultiGetBatchSize); - HistogramData hist_level; - HistogramData hist_index_and_filter_blocks; - HistogramData hist_sst; - - options.statistics->histogramData(NUM_LEVEL_READ_PER_MULTIGET, &hist_level); - options.statistics->histogramData(NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL, - &hist_index_and_filter_blocks); - options.statistics->histogramData(NUM_SST_READ_PER_LEVEL, &hist_sst); - - // Maximum number of blocks read from a file system in a level. - ASSERT_EQ(hist_level.max, 1); - ASSERT_GT(hist_index_and_filter_blocks.max, 0); - // Maximum number of sst files read from file system in a level. - ASSERT_EQ(hist_sst.max, 2); - - // Minimun number of blocks read in a level. - ASSERT_EQ(hist_level.min, 1); - ASSERT_GT(hist_index_and_filter_blocks.min, 0); - // Minimun number of sst files read in a level. - ASSERT_EQ(hist_sst.min, 1); - - for (PinnableSlice& value : values) { - value.Reset(); - } - for (Status& status : s) { - status = Status::OK(); - } - db_->MultiGet(read_opts, handles_[1], kMultiGetBatchSize, &keys[950], - values.data(), s.data(), false); - options.statistics->histogramData(NUM_LEVEL_READ_PER_MULTIGET, &hist_level); - ASSERT_EQ(hist_level.max, 2); -} - -// Test class for batched MultiGet with prefix extractor -// Param bool - If true, use partitioned filters -// If false, use full filter block -class MultiGetPrefixExtractorTest : public DBBasicTest, - public ::testing::WithParamInterface { -}; - -TEST_P(MultiGetPrefixExtractorTest, Batched) { - Options options = CurrentOptions(); - options.prefix_extractor.reset(NewFixedPrefixTransform(2)); - options.memtable_prefix_bloom_size_ratio = 10; - BlockBasedTableOptions bbto; - if (GetParam()) { - bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - bbto.partition_filters = true; - } - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.whole_key_filtering = false; - bbto.cache_index_and_filter_blocks = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - - SetPerfLevel(kEnableCount); - get_perf_context()->Reset(); - - ASSERT_OK(Put("k", "v0")); - ASSERT_OK(Put("kk1", "v1")); - ASSERT_OK(Put("kk2", "v2")); - ASSERT_OK(Put("kk3", "v3")); - ASSERT_OK(Put("kk4", "v4")); - std::vector keys( - {"k", "kk1", "kk2", "kk3", "kk4", "rofl", "lmho"}); - std::vector expected( - {"v0", "v1", "v2", "v3", "v4", "NOT_FOUND", "NOT_FOUND"}); - std::vector values; - values = MultiGet(keys, nullptr); - ASSERT_EQ(values, expected); - // One key ("k") is not queried against the filter because it is outside - // the prefix_extractor domain, leaving 6 keys with queried prefixes. - ASSERT_EQ(get_perf_context()->bloom_memtable_miss_count, 2); - ASSERT_EQ(get_perf_context()->bloom_memtable_hit_count, 4); - ASSERT_OK(Flush()); - - get_perf_context()->Reset(); - values = MultiGet(keys, nullptr); - ASSERT_EQ(values, expected); - ASSERT_EQ(get_perf_context()->bloom_sst_miss_count, 2); - ASSERT_EQ(get_perf_context()->bloom_sst_hit_count, 4); - - // Also check Get stat - get_perf_context()->Reset(); - for (size_t i = 0; i < keys.size(); ++i) { - values[i] = Get(keys[i]); - } - ASSERT_EQ(values, expected); - ASSERT_EQ(get_perf_context()->bloom_sst_miss_count, 2); - ASSERT_EQ(get_perf_context()->bloom_sst_hit_count, 4); -} - -INSTANTIATE_TEST_CASE_P(MultiGetPrefix, MultiGetPrefixExtractorTest, - ::testing::Bool()); - -class DBMultiGetRowCacheTest : public DBBasicTest, - public ::testing::WithParamInterface {}; - -TEST_P(DBMultiGetRowCacheTest, MultiGetBatched) { - do { - option_config_ = kRowCache; - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - CreateAndReopenWithCF({"pikachu"}, options); - SetPerfLevel(kEnableCount); - ASSERT_OK(Put(1, "k1", "v1")); - ASSERT_OK(Put(1, "k2", "v2")); - ASSERT_OK(Put(1, "k3", "v3")); - ASSERT_OK(Put(1, "k4", "v4")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "k5", "v5")); - const Snapshot* snap1 = dbfull()->GetSnapshot(); - ASSERT_OK(Delete(1, "k4")); - ASSERT_OK(Flush(1)); - const Snapshot* snap2 = dbfull()->GetSnapshot(); - - get_perf_context()->Reset(); - - std::vector keys({"no_key", "k5", "k4", "k3", "k1"}); - std::vector values(keys.size()); - std::vector cfs(keys.size(), handles_[1]); - std::vector s(keys.size()); - - ReadOptions ro; - bool use_snapshots = GetParam(); - if (use_snapshots) { - ro.snapshot = snap2; - } - db_->MultiGet(ro, handles_[1], keys.size(), keys.data(), values.data(), - s.data(), false); - - ASSERT_EQ(values.size(), keys.size()); - ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v1"); - ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v3"); - ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v5"); - // four kv pairs * two bytes per value - ASSERT_EQ(6, (int)get_perf_context()->multiget_read_bytes); - - ASSERT_TRUE(s[0].IsNotFound()); - ASSERT_OK(s[1]); - ASSERT_TRUE(s[2].IsNotFound()); - ASSERT_OK(s[3]); - ASSERT_OK(s[4]); - - // Call MultiGet() again with some intersection with the previous set of - // keys. Those should already be in the row cache. - keys.assign({"no_key", "k5", "k3", "k2"}); - for (size_t i = 0; i < keys.size(); ++i) { - values[i].Reset(); - s[i] = Status::OK(); - } - get_perf_context()->Reset(); - - if (use_snapshots) { - ro.snapshot = snap1; - } - db_->MultiGet(ReadOptions(), handles_[1], keys.size(), keys.data(), - values.data(), s.data(), false); - - ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v2"); - ASSERT_EQ(std::string(values[2].data(), values[2].size()), "v3"); - ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v5"); - // four kv pairs * two bytes per value - ASSERT_EQ(6, (int)get_perf_context()->multiget_read_bytes); - - ASSERT_TRUE(s[0].IsNotFound()); - ASSERT_OK(s[1]); - ASSERT_OK(s[2]); - ASSERT_OK(s[3]); - if (use_snapshots) { - // Only reads from the first SST file would have been cached, since - // snapshot seq no is > fd.largest_seqno - ASSERT_EQ(1, TestGetTickerCount(options, ROW_CACHE_HIT)); - } else { - ASSERT_EQ(2, TestGetTickerCount(options, ROW_CACHE_HIT)); - } - - SetPerfLevel(kDisable); - dbfull()->ReleaseSnapshot(snap1); - dbfull()->ReleaseSnapshot(snap2); - } while (ChangeCompactOptions()); -} - -INSTANTIATE_TEST_CASE_P(DBMultiGetRowCacheTest, DBMultiGetRowCacheTest, - testing::Values(true, false)); - -TEST_F(DBBasicTest, GetAllKeyVersions) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_EQ(2, handles_.size()); - const size_t kNumInserts = 4; - const size_t kNumDeletes = 4; - const size_t kNumUpdates = 4; - - // Check default column family - for (size_t i = 0; i != kNumInserts; ++i) { - ASSERT_OK(Put(std::to_string(i), "value")); - } - for (size_t i = 0; i != kNumUpdates; ++i) { - ASSERT_OK(Put(std::to_string(i), "value1")); - } - for (size_t i = 0; i != kNumDeletes; ++i) { - ASSERT_OK(Delete(std::to_string(i))); - } - std::vector key_versions; - ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(), - std::numeric_limits::max(), - &key_versions)); - ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size()); - for (size_t i = 0; i < kNumInserts + kNumDeletes + kNumUpdates; i++) { - if (i % 3 == 0) { - ASSERT_EQ(key_versions[i].GetTypeName(), "TypeDeletion"); - } else { - ASSERT_EQ(key_versions[i].GetTypeName(), "TypeValue"); - } - } - ASSERT_OK(GetAllKeyVersions(db_, handles_[0], Slice(), Slice(), - std::numeric_limits::max(), - &key_versions)); - ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates, key_versions.size()); - - // Check non-default column family - for (size_t i = 0; i + 1 != kNumInserts; ++i) { - ASSERT_OK(Put(1, std::to_string(i), "value")); - } - for (size_t i = 0; i + 1 != kNumUpdates; ++i) { - ASSERT_OK(Put(1, std::to_string(i), "value1")); - } - for (size_t i = 0; i + 1 != kNumDeletes; ++i) { - ASSERT_OK(Delete(1, std::to_string(i))); - } - ASSERT_OK(GetAllKeyVersions(db_, handles_[1], Slice(), Slice(), - std::numeric_limits::max(), - &key_versions)); - ASSERT_EQ(kNumInserts + kNumDeletes + kNumUpdates - 3, key_versions.size()); -} - -TEST_F(DBBasicTest, ValueTypeString) { - KeyVersion key_version; - // when adding new type, please also update `value_type_string_map` - for (unsigned char i = ValueType::kTypeDeletion; i < ValueType::kTypeMaxValid; - i++) { - key_version.type = i; - ASSERT_TRUE(key_version.GetTypeName() != "Invalid"); - } -} - -TEST_F(DBBasicTest, MultiGetIOBufferOverrun) { - Options options = CurrentOptions(); - Random rnd(301); - BlockBasedTableOptions table_options; - table_options.pin_l0_filter_and_index_blocks_in_cache = true; - table_options.block_size = 16 * 1024; - ASSERT_TRUE(table_options.block_size > - BlockBasedTable::kMultiGetReadStackBufSize); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - - std::string zero_str(128, '\0'); - for (int i = 0; i < 100; ++i) { - // Make the value compressible. A purely random string doesn't compress - // and the resultant data block will not be compressed - std::string value(rnd.RandomString(128) + zero_str); - assert(Put(Key(i), value) == Status::OK()); - } - ASSERT_OK(Flush()); - - std::vector key_data(10); - std::vector keys; - // We cannot resize a PinnableSlice vector, so just set initial size to - // largest we think we will need - std::vector values(10); - std::vector statuses; - ReadOptions ro; - - // Warm up the cache first - key_data.emplace_back(Key(0)); - keys.emplace_back(Slice(key_data.back())); - key_data.emplace_back(Key(50)); - keys.emplace_back(Slice(key_data.back())); - statuses.resize(keys.size()); - - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); -} - -TEST_F(DBBasicTest, IncrementalRecoveryNoCorrupt) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - WriteOptions write_opts; - write_opts.disableWAL = true; - for (size_t cf = 0; cf != num_cfs; ++cf) { - for (size_t i = 0; i != 10000; ++i) { - std::string key_str = Key(static_cast(i)); - std::string value_str = std::to_string(cf) + "_" + std::to_string(i); - - ASSERT_OK(Put(static_cast(cf), key_str, value_str)); - if (0 == (i % 1000)) { - ASSERT_OK(Flush(static_cast(cf))); - } - } - } - for (size_t cf = 0; cf != num_cfs; ++cf) { - ASSERT_OK(Flush(static_cast(cf))); - } - Close(); - options.best_efforts_recovery = true; - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"}, - options); - num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - for (size_t cf = 0; cf != num_cfs; ++cf) { - for (int i = 0; i != 10000; ++i) { - std::string key_str = Key(static_cast(i)); - std::string expected_value_str = - std::to_string(cf) + "_" + std::to_string(i); - ASSERT_EQ(expected_value_str, Get(static_cast(cf), key_str)); - } - } -} - -TEST_F(DBBasicTest, BestEffortsRecoveryWithVersionBuildingFailure) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) { - ASSERT_NE(nullptr, arg); - *(reinterpret_cast(arg)) = - Status::Corruption("Inject corruption"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - options.best_efforts_recovery = true; - Status s = TryReopen(options); - ASSERT_TRUE(s.IsCorruption()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -namespace { -class TableFileListener : public EventListener { - public: - void OnTableFileCreated(const TableFileCreationInfo& info) override { - InstrumentedMutexLock lock(&mutex_); - cf_to_paths_[info.cf_name].push_back(info.file_path); - } - std::vector& GetFiles(const std::string& cf_name) { - InstrumentedMutexLock lock(&mutex_); - return cf_to_paths_[cf_name]; - } - - private: - InstrumentedMutex mutex_; - std::unordered_map> cf_to_paths_; -}; -} // anonymous namespace - -TEST_F(DBBasicTest, LastSstFileNotInManifest) { - // If the last sst file is not tracked in MANIFEST, - // or the VersionEdit for the last sst file is not synced, - // on recovery, the last sst file should be deleted, - // and new sst files shouldn't reuse its file number. - Options options = CurrentOptions(); - DestroyAndReopen(options); - Close(); - - // Manually add a sst file. - constexpr uint64_t kSstFileNumber = 100; - const std::string kSstFile = MakeTableFileName(dbname_, kSstFileNumber); - ASSERT_OK(WriteStringToFile(env_, /* data = */ "bad sst file content", - /* fname = */ kSstFile, - /* should_sync = */ true)); - ASSERT_OK(env_->FileExists(kSstFile)); - - TableFileListener* listener = new TableFileListener(); - options.listeners.emplace_back(listener); - Reopen(options); - // kSstFile should already be deleted. - ASSERT_TRUE(env_->FileExists(kSstFile).IsNotFound()); - - ASSERT_OK(Put("k", "v")); - ASSERT_OK(Flush()); - // New sst file should have file number > kSstFileNumber. - std::vector& files = - listener->GetFiles(kDefaultColumnFamilyName); - ASSERT_EQ(files.size(), 1); - const std::string fname = files[0].erase(0, (dbname_ + "/").size()); - uint64_t number = 0; - FileType type = kTableFile; - ASSERT_TRUE(ParseFileName(fname, &number, &type)); - ASSERT_EQ(type, kTableFile); - ASSERT_GT(number, kSstFileNumber); -} - -TEST_F(DBBasicTest, RecoverWithMissingFiles) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - TableFileListener* listener = new TableFileListener(); - // Disable auto compaction to simplify SST file name tracking. - options.disable_auto_compactions = true; - options.listeners.emplace_back(listener); - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - std::vector all_cf_names = {kDefaultColumnFamilyName, "pikachu", - "eevee"}; - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - for (size_t cf = 0; cf != num_cfs; ++cf) { - ASSERT_OK(Put(static_cast(cf), "a", "0_value")); - ASSERT_OK(Flush(static_cast(cf))); - ASSERT_OK(Put(static_cast(cf), "b", "0_value")); - ASSERT_OK(Flush(static_cast(cf))); - ASSERT_OK(Put(static_cast(cf), "c", "0_value")); - ASSERT_OK(Flush(static_cast(cf))); - } - - // Delete and corrupt files - for (size_t i = 0; i < all_cf_names.size(); ++i) { - std::vector& files = listener->GetFiles(all_cf_names[i]); - ASSERT_EQ(3, files.size()); - std::string corrupted_data; - ASSERT_OK(ReadFileToString(env_, files[files.size() - 1], &corrupted_data)); - ASSERT_OK(WriteStringToFile( - env_, corrupted_data.substr(0, corrupted_data.size() - 2), - files[files.size() - 1], /*should_sync=*/true)); - for (int j = static_cast(files.size() - 2); j >= static_cast(i); - --j) { - ASSERT_OK(env_->DeleteFile(files[j])); - } - } - options.best_efforts_recovery = true; - ReopenWithColumnFamilies(all_cf_names, options); - // Verify data - ReadOptions read_opts; - read_opts.total_order_seek = true; - { - std::unique_ptr iter(db_->NewIterator(read_opts, handles_[0])); - iter->SeekToFirst(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - iter.reset(db_->NewIterator(read_opts, handles_[1])); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("a", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - iter.reset(db_->NewIterator(read_opts, handles_[2])); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("a", iter->key()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("b", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - } -} - -TEST_F(DBBasicTest, BestEffortsRecoveryTryMultipleManifests) { - Options options = CurrentOptions(); - options.env = env_; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "value0")); - ASSERT_OK(Flush()); - Close(); - { - // Hack by adding a new MANIFEST with high file number - std::string garbage(10, '\0'); - ASSERT_OK(WriteStringToFile(env_, garbage, dbname_ + "/MANIFEST-001000", - /*should_sync=*/true)); - } - { - // Hack by adding a corrupted SST not referenced by any MANIFEST - std::string garbage(10, '\0'); - ASSERT_OK(WriteStringToFile(env_, garbage, dbname_ + "/001001.sst", - /*should_sync=*/true)); - } - - options.best_efforts_recovery = true; - - Reopen(options); - ASSERT_OK(Put("bar", "value")); -} - -TEST_F(DBBasicTest, RecoverWithNoCurrentFile) { - Options options = CurrentOptions(); - options.env = env_; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - options.best_efforts_recovery = true; - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - ASSERT_EQ(2, handles_.size()); - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Put(1, "bar", "value")); - ASSERT_OK(Flush()); - ASSERT_OK(Flush(1)); - Close(); - ASSERT_OK(env_->DeleteFile(CurrentFileName(dbname_))); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - std::vector cf_names; - ASSERT_OK(DB::ListColumnFamilies(DBOptions(options), dbname_, &cf_names)); - ASSERT_EQ(2, cf_names.size()); - for (const auto& name : cf_names) { - ASSERT_TRUE(name == kDefaultColumnFamilyName || name == "pikachu"); - } -} - -TEST_F(DBBasicTest, RecoverWithNoManifest) { - Options options = CurrentOptions(); - options.env = env_; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Flush()); - Close(); - { - // Delete all MANIFEST. - std::vector files; - ASSERT_OK(env_->GetChildren(dbname_, &files)); - for (const auto& file : files) { - uint64_t number = 0; - FileType type = kWalFile; - if (ParseFileName(file, &number, &type) && type == kDescriptorFile) { - ASSERT_OK(env_->DeleteFile(dbname_ + "/" + file)); - } - } - } - options.best_efforts_recovery = true; - options.create_if_missing = false; - Status s = TryReopen(options); - ASSERT_TRUE(s.IsInvalidArgument()); - options.create_if_missing = true; - Reopen(options); - // Since no MANIFEST exists, best-efforts recovery creates a new, empty db. - ASSERT_EQ("NOT_FOUND", Get("foo")); -} - -TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - TableFileListener* listener = new TableFileListener(); - options.listeners.emplace_back(listener); - CreateAndReopenWithCF({"pikachu"}, options); - std::vector kAllCfNames = {kDefaultColumnFamilyName, "pikachu"}; - size_t num_cfs = handles_.size(); - ASSERT_EQ(2, num_cfs); - for (int cf = 0; cf < static_cast(kAllCfNames.size()); ++cf) { - ASSERT_OK(Put(cf, "a", "0_value")); - ASSERT_OK(Flush(cf)); - ASSERT_OK(Put(cf, "b", "0_value")); - } - // Delete files - for (size_t i = 0; i < kAllCfNames.size(); ++i) { - std::vector& files = listener->GetFiles(kAllCfNames[i]); - ASSERT_EQ(1, files.size()); - for (int j = static_cast(files.size() - 1); j >= static_cast(i); - --j) { - ASSERT_OK(env_->DeleteFile(files[j])); - } - } - options.best_efforts_recovery = true; - ReopenWithColumnFamilies(kAllCfNames, options); - // Verify WAL is not applied - ReadOptions read_opts; - read_opts.total_order_seek = true; - std::unique_ptr iter(db_->NewIterator(read_opts, handles_[0])); - iter->SeekToFirst(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - iter.reset(db_->NewIterator(read_opts, handles_[1])); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("a", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); -} - -TEST_F(DBBasicTest, DisableTrackWal) { - // If WAL tracking was enabled, and then disabled during reopen, - // the previously tracked WALs should be removed from MANIFEST. - - Options options = CurrentOptions(); - options.track_and_verify_wals_in_manifest = true; - // extremely small write buffer size, - // so that new WALs are created more frequently. - options.write_buffer_size = 100; - options.env = env_; - DestroyAndReopen(options); - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put("foo" + std::to_string(i), "value" + std::to_string(i))); - } - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - ASSERT_OK(db_->SyncWAL()); - // Some WALs are tracked. - ASSERT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty()); - Close(); - - // Disable WAL tracking. - options.track_and_verify_wals_in_manifest = false; - options.create_if_missing = false; - ASSERT_OK(TryReopen(options)); - // Previously tracked WALs are cleared. - ASSERT_TRUE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty()); - Close(); - - // Re-enable WAL tracking again. - options.track_and_verify_wals_in_manifest = true; - options.create_if_missing = false; - ASSERT_OK(TryReopen(options)); - ASSERT_TRUE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty()); - Close(); -} - -TEST_F(DBBasicTest, ManifestChecksumMismatch) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - ASSERT_OK(Put("bar", "value")); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "LogWriter::EmitPhysicalRecord:BeforeEncodeChecksum", [&](void* arg) { - auto* crc = reinterpret_cast(arg); - *crc = *crc + 1; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions write_opts; - write_opts.disableWAL = true; - Status s = db_->Put(write_opts, "foo", "value"); - ASSERT_OK(s); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - ASSERT_OK(Put("foo", "value1")); - ASSERT_OK(Flush()); - s = TryReopen(options); - ASSERT_TRUE(s.IsCorruption()); -} - -TEST_F(DBBasicTest, ConcurrentlyCloseDB) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - std::vector workers; - for (int i = 0; i < 10; i++) { - workers.push_back(std::thread([&]() { - auto s = db_->Close(); - ASSERT_OK(s); - })); - } - for (auto& w : workers) { - w.join(); - } -} - -class DBBasicTestTrackWal : public DBTestBase, - public testing::WithParamInterface { - public: - DBBasicTestTrackWal() - : DBTestBase("db_basic_test_track_wal", /*env_do_fsync=*/false) {} - - int CountWalFiles() { - VectorLogPtr log_files; - EXPECT_OK(dbfull()->GetSortedWalFiles(log_files)); - return static_cast(log_files.size()); - }; -}; - -TEST_P(DBBasicTestTrackWal, DoNotTrackObsoleteWal) { - // If a WAL becomes obsolete after flushing, but is not deleted from disk yet, - // then if SyncWAL is called afterwards, the obsolete WAL should not be - // tracked in MANIFEST. - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.track_and_verify_wals_in_manifest = true; - options.atomic_flush = GetParam(); - - DestroyAndReopen(options); - CreateAndReopenWithCF({"cf"}, options); - ASSERT_EQ(handles_.size(), 2); // default, cf - // Do not delete WALs. - ASSERT_OK(db_->DisableFileDeletions()); - constexpr int n = 10; - std::vector> wals(n); - for (size_t i = 0; i < n; i++) { - // Generate a new WAL for each key-value. - const int cf = i % 2; - ASSERT_OK(db_->GetCurrentWalFile(&wals[i])); - ASSERT_OK(Put(cf, "k" + std::to_string(i), "v" + std::to_string(i))); - ASSERT_OK(Flush({0, 1})); - } - ASSERT_EQ(CountWalFiles(), n); - // Since all WALs are obsolete, no WAL should be tracked in MANIFEST. - ASSERT_OK(db_->SyncWAL()); - - // Manually delete all WALs. - Close(); - for (const auto& wal : wals) { - ASSERT_OK(env_->DeleteFile(LogFileName(dbname_, wal->LogNumber()))); - } - - // If SyncWAL tracks the obsolete WALs in MANIFEST, - // reopen will fail because the WALs are missing from disk. - ASSERT_OK(TryReopenWithColumnFamilies({"default", "cf"}, options)); - Destroy(options); -} - -INSTANTIATE_TEST_CASE_P(DBBasicTestTrackWal, DBBasicTestTrackWal, - testing::Bool()); - -class DBBasicTestMultiGet : public DBTestBase { - public: - DBBasicTestMultiGet(std::string test_dir, int num_cfs, - bool uncompressed_cache, bool _compression_enabled, - bool _fill_cache, uint32_t compression_parallel_threads) - : DBTestBase(test_dir, /*env_do_fsync=*/false) { - compression_enabled_ = _compression_enabled; - fill_cache_ = _fill_cache; - - if (uncompressed_cache) { - std::shared_ptr cache = NewLRUCache(1048576); - uncompressed_cache_ = std::make_shared(cache); - } - - env_->count_random_reads_ = true; - - Options options = CurrentOptions(); - Random rnd(301); - BlockBasedTableOptions table_options; - - if (compression_enabled_) { - std::vector compression_types; - compression_types = GetSupportedCompressions(); - // Not every platform may have compression libraries available, so - // dynamically pick based on what's available - CompressionType tmp_type = kNoCompression; - for (auto c_type : compression_types) { - if (c_type != kNoCompression) { - tmp_type = c_type; - break; - } - } - if (tmp_type != kNoCompression) { - options.compression = tmp_type; - } else { - compression_enabled_ = false; - } - } - - table_options.block_cache = uncompressed_cache_; - if (table_options.block_cache == nullptr) { - table_options.no_block_cache = true; - } else { - table_options.pin_l0_filter_and_index_blocks_in_cache = true; - } - table_options.flush_block_policy_factory.reset( - new MyFlushBlockPolicyFactory()); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - if (!compression_enabled_) { - options.compression = kNoCompression; - } else { - options.compression_opts.parallel_threads = compression_parallel_threads; - } - options_ = options; - Reopen(options); - - if (num_cfs > 1) { - for (int cf = 0; cf < num_cfs; ++cf) { - cf_names_.emplace_back("cf" + std::to_string(cf)); - } - CreateColumnFamilies(cf_names_, options); - cf_names_.emplace_back("default"); - } - - std::string zero_str(128, '\0'); - for (int cf = 0; cf < num_cfs; ++cf) { - for (int i = 0; i < 100; ++i) { - // Make the value compressible. A purely random string doesn't compress - // and the resultant data block will not be compressed - values_.emplace_back(rnd.RandomString(128) + zero_str); - assert(((num_cfs == 1) ? Put(Key(i), values_[i]) - : Put(cf, Key(i), values_[i])) == Status::OK()); - } - if (num_cfs == 1) { - EXPECT_OK(Flush()); - } else { - EXPECT_OK(dbfull()->Flush(FlushOptions(), handles_[cf])); - } - - for (int i = 0; i < 100; ++i) { - // block cannot gain space by compression - uncompressable_values_.emplace_back(rnd.RandomString(256) + '\0'); - std::string tmp_key = "a" + Key(i); - assert(((num_cfs == 1) ? Put(tmp_key, uncompressable_values_[i]) - : Put(cf, tmp_key, uncompressable_values_[i])) == - Status::OK()); - } - if (num_cfs == 1) { - EXPECT_OK(Flush()); - } else { - EXPECT_OK(dbfull()->Flush(FlushOptions(), handles_[cf])); - } - } - // Clear compressed cache, which is always pre-populated - if (compressed_cache_) { - compressed_cache_->SetCapacity(0); - compressed_cache_->SetCapacity(1048576); - } - } - - bool CheckValue(int i, const std::string& value) { - if (values_[i].compare(value) == 0) { - return true; - } - return false; - } - - bool CheckUncompressableValue(int i, const std::string& value) { - if (uncompressable_values_[i].compare(value) == 0) { - return true; - } - return false; - } - - const std::vector& GetCFNames() const { return cf_names_; } - - int num_lookups() { return uncompressed_cache_->num_lookups(); } - int num_found() { return uncompressed_cache_->num_found(); } - int num_inserts() { return uncompressed_cache_->num_inserts(); } - - int num_lookups_compressed() { return compressed_cache_->num_lookups(); } - int num_found_compressed() { return compressed_cache_->num_found(); } - int num_inserts_compressed() { return compressed_cache_->num_inserts(); } - - bool fill_cache() { return fill_cache_; } - bool compression_enabled() { return compression_enabled_; } - bool has_compressed_cache() { return compressed_cache_ != nullptr; } - bool has_uncompressed_cache() { return uncompressed_cache_ != nullptr; } - Options get_options() { return options_; } - - static void SetUpTestCase() {} - static void TearDownTestCase() {} - - protected: - class MyFlushBlockPolicyFactory : public FlushBlockPolicyFactory { - public: - MyFlushBlockPolicyFactory() {} - - virtual const char* Name() const override { - return "MyFlushBlockPolicyFactory"; - } - - virtual FlushBlockPolicy* NewFlushBlockPolicy( - const BlockBasedTableOptions& /*table_options*/, - const BlockBuilder& data_block_builder) const override { - return new MyFlushBlockPolicy(data_block_builder); - } - }; - - class MyFlushBlockPolicy : public FlushBlockPolicy { - public: - explicit MyFlushBlockPolicy(const BlockBuilder& data_block_builder) - : num_keys_(0), data_block_builder_(data_block_builder) {} - - bool Update(const Slice& /*key*/, const Slice& /*value*/) override { - if (data_block_builder_.empty()) { - // First key in this block - num_keys_ = 1; - return false; - } - // Flush every 10 keys - if (num_keys_ == 10) { - num_keys_ = 1; - return true; - } - num_keys_++; - return false; - } - - private: - int num_keys_; - const BlockBuilder& data_block_builder_; - }; - - class MyBlockCache : public CacheWrapper { - public: - explicit MyBlockCache(std::shared_ptr target) - : CacheWrapper(target), - num_lookups_(0), - num_found_(0), - num_inserts_(0) {} - - const char* Name() const override { return "MyBlockCache"; } - - Status Insert(const Slice& key, Cache::ObjectPtr value, - const CacheItemHelper* helper, size_t charge, - Handle** handle = nullptr, - Priority priority = Priority::LOW) override { - num_inserts_++; - return target_->Insert(key, value, helper, charge, handle, priority); - } - - Handle* Lookup(const Slice& key, const CacheItemHelper* helper, - CreateContext* create_context, - Priority priority = Priority::LOW, - Statistics* stats = nullptr) override { - num_lookups_++; - Handle* handle = - target_->Lookup(key, helper, create_context, priority, stats); - if (handle != nullptr) { - num_found_++; - } - return handle; - } - - int num_lookups() { return num_lookups_; } - - int num_found() { return num_found_; } - - int num_inserts() { return num_inserts_; } - - private: - int num_lookups_; - int num_found_; - int num_inserts_; - }; - - std::shared_ptr compressed_cache_; - std::shared_ptr uncompressed_cache_; - Options options_; - bool compression_enabled_; - std::vector values_; - std::vector uncompressable_values_; - bool fill_cache_; - std::vector cf_names_; -}; - -class DBBasicTestWithParallelIO : public DBBasicTestMultiGet, - public testing::WithParamInterface< - std::tuple> { - public: - DBBasicTestWithParallelIO() - : DBBasicTestMultiGet("/db_basic_test_with_parallel_io", 1, - std::get<0>(GetParam()), std::get<1>(GetParam()), - std::get<2>(GetParam()), std::get<3>(GetParam())) {} -}; - -TEST_P(DBBasicTestWithParallelIO, MultiGet) { - std::vector key_data(10); - std::vector keys; - // We cannot resize a PinnableSlice vector, so just set initial size to - // largest we think we will need - std::vector values(10); - std::vector statuses; - ReadOptions ro; - ro.fill_cache = fill_cache(); - - // Warm up the cache first - key_data.emplace_back(Key(0)); - keys.emplace_back(Slice(key_data.back())); - key_data.emplace_back(Key(50)); - keys.emplace_back(Slice(key_data.back())); - statuses.resize(keys.size()); - - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - ASSERT_TRUE(CheckValue(0, values[0].ToString())); - ASSERT_TRUE(CheckValue(50, values[1].ToString())); - - int random_reads = env_->random_read_counter_.Read(); - key_data[0] = Key(1); - key_data[1] = Key(51); - keys[0] = Slice(key_data[0]); - keys[1] = Slice(key_data[1]); - values[0].Reset(); - values[1].Reset(); - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - ASSERT_TRUE(CheckValue(1, values[0].ToString())); - ASSERT_TRUE(CheckValue(51, values[1].ToString())); - - bool read_from_cache = false; - if (fill_cache()) { - if (has_uncompressed_cache()) { - read_from_cache = true; - } else if (has_compressed_cache() && compression_enabled()) { - read_from_cache = true; - } - } - - int expected_reads = random_reads + (read_from_cache ? 0 : 2); - ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads); - - keys.resize(10); - statuses.resize(10); - std::vector key_ints{1, 2, 15, 16, 55, 81, 82, 83, 84, 85}; - for (size_t i = 0; i < key_ints.size(); ++i) { - key_data[i] = Key(key_ints[i]); - keys[i] = Slice(key_data[i]); - statuses[i] = Status::OK(); - values[i].Reset(); - } - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - for (size_t i = 0; i < key_ints.size(); ++i) { - ASSERT_OK(statuses[i]); - ASSERT_TRUE(CheckValue(key_ints[i], values[i].ToString())); - } - if (compression_enabled() && !has_compressed_cache()) { - expected_reads += (read_from_cache ? 2 : 3); - } else { - expected_reads += (read_from_cache ? 2 : 4); - } - ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads); - - keys.resize(10); - statuses.resize(10); - std::vector key_uncmp{1, 2, 15, 16, 55, 81, 82, 83, 84, 85}; - for (size_t i = 0; i < key_uncmp.size(); ++i) { - key_data[i] = "a" + Key(key_uncmp[i]); - keys[i] = Slice(key_data[i]); - statuses[i] = Status::OK(); - values[i].Reset(); - } - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - for (size_t i = 0; i < key_uncmp.size(); ++i) { - ASSERT_OK(statuses[i]); - ASSERT_TRUE(CheckUncompressableValue(key_uncmp[i], values[i].ToString())); - } - if (compression_enabled() && !has_compressed_cache()) { - expected_reads += (read_from_cache ? 3 : 3); - } else { - expected_reads += (read_from_cache ? 4 : 4); - } - ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads); - - keys.resize(5); - statuses.resize(5); - std::vector key_tr{1, 2, 15, 16, 55}; - for (size_t i = 0; i < key_tr.size(); ++i) { - key_data[i] = "a" + Key(key_tr[i]); - keys[i] = Slice(key_data[i]); - statuses[i] = Status::OK(); - values[i].Reset(); - } - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - for (size_t i = 0; i < key_tr.size(); ++i) { - ASSERT_OK(statuses[i]); - ASSERT_TRUE(CheckUncompressableValue(key_tr[i], values[i].ToString())); - } - if (compression_enabled() && !has_compressed_cache()) { - expected_reads += (read_from_cache ? 0 : 2); - ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads); - } else { - if (has_uncompressed_cache()) { - expected_reads += (read_from_cache ? 0 : 3); - ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads); - } else { - // A rare case, even we enable the block compression but some of data - // blocks are not compressed due to content. If user only enable the - // compressed cache, the uncompressed blocks will not tbe cached, and - // block reads will be triggered. The number of reads is related to - // the compression algorithm. - ASSERT_TRUE(env_->random_read_counter_.Read() >= expected_reads); - } - } -} - -TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) { - class FakeDirectIOEnv : public EnvWrapper { - class FakeDirectIOSequentialFile; - class FakeDirectIORandomAccessFile; - - public: - FakeDirectIOEnv(Env* env) : EnvWrapper(env) {} - static const char* kClassName() { return "FakeDirectIOEnv"; } - const char* Name() const override { return kClassName(); } - - Status NewRandomAccessFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) override { - std::unique_ptr file; - assert(options.use_direct_reads); - EnvOptions opts = options; - opts.use_direct_reads = false; - Status s = target()->NewRandomAccessFile(fname, &file, opts); - if (!s.ok()) { - return s; - } - result->reset(new FakeDirectIORandomAccessFile(std::move(file))); - return s; - } - - private: - class FakeDirectIOSequentialFile : public SequentialFileWrapper { - public: - FakeDirectIOSequentialFile(std::unique_ptr&& file) - : SequentialFileWrapper(file.get()), file_(std::move(file)) {} - ~FakeDirectIOSequentialFile() {} - - bool use_direct_io() const override { return true; } - size_t GetRequiredBufferAlignment() const override { return 1; } - - private: - std::unique_ptr file_; - }; - - class FakeDirectIORandomAccessFile : public RandomAccessFileWrapper { - public: - FakeDirectIORandomAccessFile(std::unique_ptr&& file) - : RandomAccessFileWrapper(file.get()), file_(std::move(file)) {} - ~FakeDirectIORandomAccessFile() {} - - bool use_direct_io() const override { return true; } - size_t GetRequiredBufferAlignment() const override { return 1; } - - private: - std::unique_ptr file_; - }; - }; - - std::unique_ptr env(new FakeDirectIOEnv(env_)); - Options opts = get_options(); - opts.env = env.get(); - opts.use_direct_reads = true; - Reopen(opts); - - std::vector key_data(10); - std::vector keys; - // We cannot resize a PinnableSlice vector, so just set initial size to - // largest we think we will need - std::vector values(10); - std::vector statuses; - ReadOptions ro; - ro.fill_cache = fill_cache(); - - // Warm up the cache first - key_data.emplace_back(Key(0)); - keys.emplace_back(Slice(key_data.back())); - key_data.emplace_back(Key(50)); - keys.emplace_back(Slice(key_data.back())); - statuses.resize(keys.size()); - - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - ASSERT_TRUE(CheckValue(0, values[0].ToString())); - ASSERT_TRUE(CheckValue(50, values[1].ToString())); - - int random_reads = env_->random_read_counter_.Read(); - key_data[0] = Key(1); - key_data[1] = Key(51); - keys[0] = Slice(key_data[0]); - keys[1] = Slice(key_data[1]); - values[0].Reset(); - values[1].Reset(); - if (uncompressed_cache_) { - uncompressed_cache_->SetCapacity(0); - uncompressed_cache_->SetCapacity(1048576); - } - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - ASSERT_TRUE(CheckValue(1, values[0].ToString())); - ASSERT_TRUE(CheckValue(51, values[1].ToString())); - - bool read_from_cache = false; - if (fill_cache()) { - if (has_uncompressed_cache()) { - read_from_cache = true; - } else if (has_compressed_cache() && compression_enabled()) { - read_from_cache = true; - } - } - - int expected_reads = random_reads; - if (!compression_enabled() || !has_compressed_cache()) { - expected_reads += 2; - } else { - expected_reads += (read_from_cache ? 0 : 2); - } - if (env_->random_read_counter_.Read() != expected_reads) { - ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads); - } - Close(); -} - -TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) { - std::vector key_data(10); - std::vector keys; - // We cannot resize a PinnableSlice vector, so just set initial size to - // largest we think we will need - std::vector values(10); - std::vector statuses; - int read_count = 0; - ReadOptions ro; - ro.fill_cache = fill_cache(); - - SyncPoint::GetInstance()->SetCallBack( - "RetrieveMultipleBlocks:VerifyChecksum", [&](void* status) { - Status* s = static_cast(status); - read_count++; - if (read_count == 2) { - *s = Status::Corruption(); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Warm up the cache first - key_data.emplace_back(Key(0)); - keys.emplace_back(Slice(key_data.back())); - key_data.emplace_back(Key(50)); - keys.emplace_back(Slice(key_data.back())); - statuses.resize(keys.size()); - - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - ASSERT_TRUE(CheckValue(0, values[0].ToString())); - // ASSERT_TRUE(CheckValue(50, values[1].ToString())); - ASSERT_EQ(statuses[0], Status::OK()); - ASSERT_EQ(statuses[1], Status::Corruption()); - - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) { - std::vector key_data(10); - std::vector keys; - // We cannot resize a PinnableSlice vector, so just set initial size to - // largest we think we will need - std::vector values(10); - std::vector statuses; - ReadOptions ro; - ro.fill_cache = fill_cache(); - - SyncPoint::GetInstance()->SetCallBack( - "TableCache::MultiGet:FindTable", [&](void* status) { - Status* s = static_cast(status); - *s = Status::IOError(); - }); - // DB open will create table readers unless we reduce the table cache - // capacity. - // SanitizeOptions will set max_open_files to minimum of 20. Table cache - // is allocated with max_open_files - 10 as capacity. So override - // max_open_files to 11 so table cache capacity will become 1. This will - // prevent file open during DB open and force the file to be opened - // during MultiGet - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = (int*)arg; - *max_open_files = 11; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Reopen(CurrentOptions()); - - // Warm up the cache first - key_data.emplace_back(Key(0)); - keys.emplace_back(Slice(key_data.back())); - key_data.emplace_back(Key(50)); - keys.emplace_back(Slice(key_data.back())); - statuses.resize(keys.size()); - - dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(), - keys.data(), values.data(), statuses.data(), true); - ASSERT_EQ(statuses[0], Status::IOError()); - ASSERT_EQ(statuses[1], Status::IOError()); - - SyncPoint::GetInstance()->DisableProcessing(); -} - -INSTANTIATE_TEST_CASE_P(ParallelIO, DBBasicTestWithParallelIO, - // Params are as follows - - // Param 0 - Uncompressed cache enabled - // Param 1 - Data compression enabled - // Param 2 - ReadOptions::fill_cache - // Param 3 - CompressionOptions::parallel_threads - ::testing::Combine(::testing::Bool(), ::testing::Bool(), - ::testing::Bool(), - ::testing::Values(1, 4))); - -// Forward declaration -class DeadlineFS; - -class DeadlineRandomAccessFile : public FSRandomAccessFileOwnerWrapper { - public: - DeadlineRandomAccessFile(DeadlineFS& fs, - std::unique_ptr& file) - : FSRandomAccessFileOwnerWrapper(std::move(file)), fs_(fs) {} - - IOStatus Read(uint64_t offset, size_t len, const IOOptions& opts, - Slice* result, char* scratch, - IODebugContext* dbg) const override; - - IOStatus MultiRead(FSReadRequest* reqs, size_t num_reqs, - const IOOptions& options, IODebugContext* dbg) override; - - IOStatus ReadAsync(FSReadRequest& req, const IOOptions& opts, - std::function cb, - void* cb_arg, void** io_handle, IOHandleDeleter* del_fn, - IODebugContext* dbg) override; - - private: - DeadlineFS& fs_; - std::unique_ptr file_; -}; - -class DeadlineFS : public FileSystemWrapper { - public: - // The error_on_delay parameter specifies whether a IOStatus::TimedOut() - // status should be returned after delaying the IO to exceed the timeout, - // or to simply delay but return success anyway. The latter mimics the - // behavior of PosixFileSystem, which does not enforce any timeout - explicit DeadlineFS(SpecialEnv* env, bool error_on_delay) - : FileSystemWrapper(env->GetFileSystem()), - deadline_(std::chrono::microseconds::zero()), - io_timeout_(std::chrono::microseconds::zero()), - env_(env), - timedout_(false), - ignore_deadline_(false), - error_on_delay_(error_on_delay) {} - - static const char* kClassName() { return "DeadlineFileSystem"; } - const char* Name() const override { return kClassName(); } - - IOStatus NewRandomAccessFile(const std::string& fname, - const FileOptions& opts, - std::unique_ptr* result, - IODebugContext* dbg) override { - std::unique_ptr file; - IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg); - EXPECT_OK(s); - result->reset(new DeadlineRandomAccessFile(*this, file)); - - const std::chrono::microseconds deadline = GetDeadline(); - const std::chrono::microseconds io_timeout = GetIOTimeout(); - if (deadline.count() || io_timeout.count()) { - AssertDeadline(deadline, io_timeout, opts.io_options); - } - return ShouldDelay(opts.io_options); - } - - // Set a vector of {IO counter, delay in microseconds, return status} tuples - // that control when to inject a delay and duration of the delay - void SetDelayTrigger(const std::chrono::microseconds deadline, - const std::chrono::microseconds io_timeout, - const int trigger) { - delay_trigger_ = trigger; - io_count_ = 0; - deadline_ = deadline; - io_timeout_ = io_timeout; - timedout_ = false; - } - - // Increment the IO counter and return a delay in microseconds - IOStatus ShouldDelay(const IOOptions& opts) { - if (timedout_) { - return IOStatus::TimedOut(); - } else if (!deadline_.count() && !io_timeout_.count()) { - return IOStatus::OK(); - } - if (!ignore_deadline_ && delay_trigger_ == io_count_++) { - env_->SleepForMicroseconds(static_cast(opts.timeout.count() + 1)); - timedout_ = true; - if (error_on_delay_) { - return IOStatus::TimedOut(); - } - } - return IOStatus::OK(); - } - - const std::chrono::microseconds GetDeadline() { - return ignore_deadline_ ? std::chrono::microseconds::zero() : deadline_; - } - - const std::chrono::microseconds GetIOTimeout() { - return ignore_deadline_ ? std::chrono::microseconds::zero() : io_timeout_; - } - - bool TimedOut() { return timedout_; } - - void IgnoreDeadline(bool ignore) { ignore_deadline_ = ignore; } - - void AssertDeadline(const std::chrono::microseconds deadline, - const std::chrono::microseconds io_timeout, - const IOOptions& opts) const { - // Give a leeway of +- 10us as it can take some time for the Get/ - // MultiGet call to reach here, in order to avoid false alarms - std::chrono::microseconds now = - std::chrono::microseconds(env_->NowMicros()); - std::chrono::microseconds timeout; - if (deadline.count()) { - timeout = deadline - now; - if (io_timeout.count()) { - timeout = std::min(timeout, io_timeout); - } - } else { - timeout = io_timeout; - } - if (opts.timeout != timeout) { - ASSERT_EQ(timeout, opts.timeout); - } - } - - private: - // The number of IOs to trigger the delay after - int delay_trigger_; - // Current IO count - int io_count_; - // ReadOptions deadline for the Get/MultiGet/Iterator - std::chrono::microseconds deadline_; - // ReadOptions io_timeout for the Get/MultiGet/Iterator - std::chrono::microseconds io_timeout_; - SpecialEnv* env_; - // Flag to indicate whether we injected a delay - bool timedout_; - // Temporarily ignore deadlines/timeouts - bool ignore_deadline_; - // Return IOStatus::TimedOut() or IOStatus::OK() - bool error_on_delay_; -}; - -IOStatus DeadlineRandomAccessFile::Read(uint64_t offset, size_t len, - const IOOptions& opts, Slice* result, - char* scratch, - IODebugContext* dbg) const { - const std::chrono::microseconds deadline = fs_.GetDeadline(); - const std::chrono::microseconds io_timeout = fs_.GetIOTimeout(); - IOStatus s; - if (deadline.count() || io_timeout.count()) { - fs_.AssertDeadline(deadline, io_timeout, opts); - } - if (s.ok()) { - s = FSRandomAccessFileWrapper::Read(offset, len, opts, result, scratch, - dbg); - } - if (s.ok()) { - s = fs_.ShouldDelay(opts); - } - return s; -} - -IOStatus DeadlineRandomAccessFile::ReadAsync( - FSReadRequest& req, const IOOptions& opts, - std::function cb, void* cb_arg, - void** io_handle, IOHandleDeleter* del_fn, IODebugContext* dbg) { - const std::chrono::microseconds deadline = fs_.GetDeadline(); - const std::chrono::microseconds io_timeout = fs_.GetIOTimeout(); - IOStatus s; - if (deadline.count() || io_timeout.count()) { - fs_.AssertDeadline(deadline, io_timeout, opts); - } - if (s.ok()) { - s = FSRandomAccessFileWrapper::ReadAsync(req, opts, cb, cb_arg, io_handle, - del_fn, dbg); - } - if (s.ok()) { - s = fs_.ShouldDelay(opts); - } - return s; -} - -IOStatus DeadlineRandomAccessFile::MultiRead(FSReadRequest* reqs, - size_t num_reqs, - const IOOptions& options, - IODebugContext* dbg) { - const std::chrono::microseconds deadline = fs_.GetDeadline(); - const std::chrono::microseconds io_timeout = fs_.GetIOTimeout(); - IOStatus s; - if (deadline.count() || io_timeout.count()) { - fs_.AssertDeadline(deadline, io_timeout, options); - } - if (s.ok()) { - s = FSRandomAccessFileWrapper::MultiRead(reqs, num_reqs, options, dbg); - } - if (s.ok()) { - s = fs_.ShouldDelay(options); - } - return s; -} - -// A test class for intercepting random reads and injecting artificial -// delays. Used for testing the MultiGet deadline feature -class DBBasicTestMultiGetDeadline : public DBBasicTestMultiGet, - public testing::WithParamInterface { - public: - DBBasicTestMultiGetDeadline() - : DBBasicTestMultiGet( - "db_basic_test_multiget_deadline" /*Test dir*/, - 10 /*# of column families*/, true /*uncompressed cache enabled*/, - true /*compression enabled*/, true /*ReadOptions.fill_cache*/, - 1 /*# of parallel compression threads*/) {} - - inline void CheckStatus(std::vector& statuses, size_t num_ok) { - for (size_t i = 0; i < statuses.size(); ++i) { - if (i < num_ok) { - EXPECT_OK(statuses[i]); - } else { - if (statuses[i] != Status::TimedOut()) { - EXPECT_EQ(statuses[i], Status::TimedOut()); - } - } - } - } -}; - -TEST_P(DBBasicTestMultiGetDeadline, MultiGetDeadlineExceeded) { -#ifndef USE_COROUTINES - if (GetParam()) { - ROCKSDB_GTEST_SKIP("This test requires coroutine support"); - return; - } -#endif // USE_COROUTINES - std::shared_ptr fs = std::make_shared(env_, false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - Options options = CurrentOptions(); - - std::shared_ptr cache = NewLRUCache(1048576); - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.env = env.get(); - SetTimeElapseOnlySleepOnReopen(&options); - ReopenWithColumnFamilies(GetCFNames(), options); - - // Test the non-batched version of MultiGet with multiple column - // families - std::vector key_str; - size_t i; - for (i = 0; i < 5; ++i) { - key_str.emplace_back(Key(static_cast(i))); - } - std::vector cfs(key_str.size()); - ; - std::vector keys(key_str.size()); - std::vector values(key_str.size()); - for (i = 0; i < key_str.size(); ++i) { - cfs[i] = handles_[i]; - keys[i] = Slice(key_str[i].data(), key_str[i].size()); - } - - ReadOptions ro; - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - ro.async_io = GetParam(); - // Delay the first IO - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, 0); - - std::vector statuses = dbfull()->MultiGet(ro, cfs, keys, &values); - // The first key is successful because we check after the lookup, but - // subsequent keys fail due to deadline exceeded - CheckStatus(statuses, 1); - - // Clear the cache - cache->SetCapacity(0); - cache->SetCapacity(1048576); - // Test non-batched Multiget with multiple column families and - // introducing an IO delay in one of the middle CFs - key_str.clear(); - for (i = 0; i < 10; ++i) { - key_str.emplace_back(Key(static_cast(i))); - } - cfs.resize(key_str.size()); - keys.resize(key_str.size()); - values.resize(key_str.size()); - for (i = 0; i < key_str.size(); ++i) { - // 2 keys per CF - cfs[i] = handles_[i / 2]; - keys[i] = Slice(key_str[i].data(), key_str[i].size()); - } - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, 1); - statuses = dbfull()->MultiGet(ro, cfs, keys, &values); - CheckStatus(statuses, 3); - - // Test batched MultiGet with an IO delay in the first data block read. - // Both keys in the first CF should succeed as they're in the same data - // block and would form one batch, and we check for deadline between - // batches. - std::vector pin_values(keys.size()); - cache->SetCapacity(0); - cache->SetCapacity(1048576); - statuses.clear(); - statuses.resize(keys.size()); - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, 0); - dbfull()->MultiGet(ro, keys.size(), cfs.data(), keys.data(), - pin_values.data(), statuses.data()); - CheckStatus(statuses, 2); - - // Similar to the previous one, but an IO delay in the third CF data block - // read - for (PinnableSlice& value : pin_values) { - value.Reset(); - } - cache->SetCapacity(0); - cache->SetCapacity(1048576); - statuses.clear(); - statuses.resize(keys.size()); - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, 2); - dbfull()->MultiGet(ro, keys.size(), cfs.data(), keys.data(), - pin_values.data(), statuses.data()); - CheckStatus(statuses, 6); - - // Similar to the previous one, but an IO delay in the last but one CF - for (PinnableSlice& value : pin_values) { - value.Reset(); - } - cache->SetCapacity(0); - cache->SetCapacity(1048576); - statuses.clear(); - statuses.resize(keys.size()); - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, 3); - dbfull()->MultiGet(ro, keys.size(), cfs.data(), keys.data(), - pin_values.data(), statuses.data()); - CheckStatus(statuses, 8); - - // Test batched MultiGet with single CF and lots of keys. Inject delay - // into the second batch of keys. As each batch is 32, the first 64 keys, - // i.e first two batches, should succeed and the rest should time out - for (PinnableSlice& value : pin_values) { - value.Reset(); - } - cache->SetCapacity(0); - cache->SetCapacity(1048576); - key_str.clear(); - for (i = 0; i < 100; ++i) { - key_str.emplace_back(Key(static_cast(i))); - } - keys.resize(key_str.size()); - pin_values.clear(); - pin_values.resize(key_str.size()); - for (i = 0; i < key_str.size(); ++i) { - keys[i] = Slice(key_str[i].data(), key_str[i].size()); - } - statuses.clear(); - statuses.resize(keys.size()); - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, 1); - dbfull()->MultiGet(ro, handles_[0], keys.size(), keys.data(), - pin_values.data(), statuses.data()); - CheckStatus(statuses, 64); - Close(); -} - -INSTANTIATE_TEST_CASE_P(DeadlineIO, DBBasicTestMultiGetDeadline, - ::testing::Bool()); - -TEST_F(DBBasicTest, ManifestWriteFailure) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.env = env_; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:AfterSyncManifest", [&](void* arg) { - ASSERT_NE(nullptr, arg); - auto* s = reinterpret_cast(arg); - ASSERT_OK(*s); - // Manually overwrite return status - *s = Status::IOError(); - }); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put("key", "value")); - ASSERT_NOK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); -} - -TEST_F(DBBasicTest, DestroyDefaultCfHandle) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - for (const auto* h : handles_) { - ASSERT_NE(db_->DefaultColumnFamily(), h); - } - - // We have two handles to the default column family. The two handles point to - // different ColumnFamilyHandle objects. - assert(db_->DefaultColumnFamily()); - ASSERT_EQ(0U, db_->DefaultColumnFamily()->GetID()); - assert(handles_[0]); - ASSERT_EQ(0U, handles_[0]->GetID()); - - // You can destroy handles_[...]. - for (auto* h : handles_) { - ASSERT_OK(db_->DestroyColumnFamilyHandle(h)); - } - handles_.clear(); - - // But you should not destroy db_->DefaultColumnFamily(), since it's going to - // be deleted in `DBImpl::CloseHelper()`. Before that, it may be used - // elsewhere internally too. - ColumnFamilyHandle* default_cf = db_->DefaultColumnFamily(); - ASSERT_TRUE(db_->DestroyColumnFamilyHandle(default_cf).IsInvalidArgument()); -} - -TEST_F(DBBasicTest, FailOpenIfLoggerCreationFail) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "rocksdb::CreateLoggerFromOptions:AfterGetPath", [&](void* arg) { - auto* s = reinterpret_cast(arg); - assert(s); - *s = Status::IOError("Injected"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - ASSERT_EQ(nullptr, options.info_log); - ASSERT_TRUE(s.IsIOError()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBBasicTest, VerifyFileChecksums) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.env = env_; - DestroyAndReopen(options); - ASSERT_OK(Put("a", "value")); - ASSERT_OK(Flush()); - ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument()); - - options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - Reopen(options); - ASSERT_OK(db_->VerifyFileChecksums(ReadOptions())); - - // Write an L0 with checksum computed. - ASSERT_OK(Put("b", "value")); - ASSERT_OK(Flush()); - - ASSERT_OK(db_->VerifyFileChecksums(ReadOptions())); - - // Does the right thing but with the wrong name -- using it should lead to an - // error. - class MisnamedFileChecksumGenerator : public FileChecksumGenCrc32c { - public: - MisnamedFileChecksumGenerator(const FileChecksumGenContext& context) - : FileChecksumGenCrc32c(context) {} - - const char* Name() const override { return "sha1"; } - }; - - class MisnamedFileChecksumGenFactory : public FileChecksumGenCrc32cFactory { - public: - std::unique_ptr CreateFileChecksumGenerator( - const FileChecksumGenContext& context) override { - return std::unique_ptr( - new MisnamedFileChecksumGenerator(context)); - } - }; - - options.file_checksum_gen_factory.reset(new MisnamedFileChecksumGenFactory()); - Reopen(options); - ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument()); -} - -TEST_F(DBBasicTest, VerifyFileChecksumsReadahead) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.env = env_; - options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - DestroyAndReopen(options); - - Random rnd(301); - int alignment = 256 * 1024; - for (int i = 0; i < 16; ++i) { - ASSERT_OK(Put("key" + std::to_string(i), rnd.RandomString(alignment))); - } - ASSERT_OK(Flush()); - - std::vector filenames; - int sst_cnt = 0; - std::string sst_name; - uint64_t sst_size; - uint64_t number; - FileType type; - ASSERT_OK(env_->GetChildren(dbname_, &filenames)); - for (auto name : filenames) { - if (ParseFileName(name, &number, &type)) { - if (type == kTableFile) { - sst_cnt++; - sst_name = name; - } - } - } - ASSERT_EQ(sst_cnt, 1); - ASSERT_OK(env_->GetFileSize(dbname_ + '/' + sst_name, &sst_size)); - - bool last_read = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "GenerateOneFileChecksum::Chunk:0", [&](void* /*arg*/) { - if (env_->random_read_bytes_counter_.load() == sst_size) { - EXPECT_FALSE(last_read); - last_read = true; - } else { - ASSERT_EQ(env_->random_read_bytes_counter_.load() & (alignment - 1), - 0); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - env_->count_random_reads_ = true; - env_->random_read_bytes_counter_ = 0; - env_->random_read_counter_.Reset(); - - ReadOptions ro; - ro.readahead_size = alignment; - ASSERT_OK(db_->VerifyFileChecksums(ro)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_TRUE(last_read); - ASSERT_EQ(env_->random_read_counter_.Read(), - (sst_size + alignment - 1) / (alignment)); -} - -// TODO: re-enable after we provide finer-grained control for WAL tracking to -// meet the needs of different use cases, durability levels and recovery modes. -TEST_F(DBBasicTest, DISABLED_ManualWalSync) { - Options options = CurrentOptions(); - options.track_and_verify_wals_in_manifest = true; - options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency; - DestroyAndReopen(options); - - ASSERT_OK(Put("x", "y")); - // This does not create a new WAL. - ASSERT_OK(db_->SyncWAL()); - EXPECT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty()); - - std::unique_ptr wal; - Status s = db_->GetCurrentWalFile(&wal); - ASSERT_OK(s); - Close(); - - EXPECT_OK(env_->DeleteFile(LogFileName(dbname_, wal->LogNumber()))); - - ASSERT_TRUE(TryReopen(options).IsCorruption()); -} - -// A test class for intercepting random reads and injecting artificial -// delays. Used for testing the deadline/timeout feature -class DBBasicTestDeadline - : public DBBasicTest, - public testing::WithParamInterface> {}; - -TEST_P(DBBasicTestDeadline, PointLookupDeadline) { - std::shared_ptr fs = std::make_shared(env_, true); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - bool set_deadline = std::get<0>(GetParam()); - bool set_timeout = std::get<1>(GetParam()); - - for (int option_config = kDefault; option_config < kEnd; ++option_config) { - if (ShouldSkipOptions(option_config, kSkipPlainTable | kSkipMmapReads)) { - continue; - } - option_config_ = option_config; - Options options = CurrentOptions(); - if (options.use_direct_reads) { - continue; - } - options.env = env.get(); - options.disable_auto_compactions = true; - Cache* block_cache = nullptr; - // Fileter block reads currently don't cause the request to get - // aborted on a read timeout, so its possible those block reads - // may get issued even if the deadline is past - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Get:BeforeFilterMatch", - [&](void* /*arg*/) { fs->IgnoreDeadline(true); }); - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Get:AfterFilterMatch", - [&](void* /*arg*/) { fs->IgnoreDeadline(false); }); - // DB open will create table readers unless we reduce the table cache - // capacity. - // SanitizeOptions will set max_open_files to minimum of 20. Table cache - // is allocated with max_open_files - 10 as capacity. So override - // max_open_files to 11 so table cache capacity will become 1. This will - // prevent file open during DB open and force the file to be opened - // during MultiGet - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = (int*)arg; - *max_open_files = 11; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - SetTimeElapseOnlySleepOnReopen(&options); - Reopen(options); - - if (options.table_factory) { - block_cache = options.table_factory->GetOptions( - TableFactory::kBlockCacheOpts()); - } - - Random rnd(301); - for (int i = 0; i < 400; ++i) { - std::string key = "k" + std::to_string(i); - ASSERT_OK(Put(key, rnd.RandomString(100))); - } - ASSERT_OK(Flush()); - - bool timedout = true; - // A timeout will be forced when the IO counter reaches this value - int io_deadline_trigger = 0; - // Keep incrementing io_deadline_trigger and call Get() until there is an - // iteration that doesn't cause a timeout. This ensures that we cover - // all file reads in the point lookup path that can potentially timeout - // and cause the Get() to fail. - while (timedout) { - ReadOptions ro; - if (set_deadline) { - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - } - if (set_timeout) { - ro.io_timeout = std::chrono::microseconds{5000}; - } - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, io_deadline_trigger); - - block_cache->SetCapacity(0); - block_cache->SetCapacity(1048576); - - std::string value; - Status s = dbfull()->Get(ro, "k50", &value); - if (fs->TimedOut()) { - ASSERT_EQ(s, Status::TimedOut()); - } else { - timedout = false; - ASSERT_OK(s); - } - io_deadline_trigger++; - } - // Reset the delay sequence in order to avoid false alarms during Reopen - fs->SetDelayTrigger(std::chrono::microseconds::zero(), - std::chrono::microseconds::zero(), 0); - } - Close(); -} - -TEST_P(DBBasicTestDeadline, IteratorDeadline) { - std::shared_ptr fs = std::make_shared(env_, true); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - bool set_deadline = std::get<0>(GetParam()); - bool set_timeout = std::get<1>(GetParam()); - - for (int option_config = kDefault; option_config < kEnd; ++option_config) { - if (ShouldSkipOptions(option_config, kSkipPlainTable | kSkipMmapReads)) { - continue; - } - Options options = CurrentOptions(); - if (options.use_direct_reads) { - continue; - } - options.env = env.get(); - options.disable_auto_compactions = true; - Cache* block_cache = nullptr; - // DB open will create table readers unless we reduce the table cache - // capacity. - // SanitizeOptions will set max_open_files to minimum of 20. Table cache - // is allocated with max_open_files - 10 as capacity. So override - // max_open_files to 11 so table cache capacity will become 1. This will - // prevent file open during DB open and force the file to be opened - // during MultiGet - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = (int*)arg; - *max_open_files = 11; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - SetTimeElapseOnlySleepOnReopen(&options); - Reopen(options); - - if (options.table_factory) { - block_cache = options.table_factory->GetOptions( - TableFactory::kBlockCacheOpts()); - } - - Random rnd(301); - for (int i = 0; i < 400; ++i) { - std::string key = "k" + std::to_string(i); - ASSERT_OK(Put(key, rnd.RandomString(100))); - } - ASSERT_OK(Flush()); - - bool timedout = true; - // A timeout will be forced when the IO counter reaches this value - int io_deadline_trigger = 0; - // Keep incrementing io_deadline_trigger and call Get() until there is an - // iteration that doesn't cause a timeout. This ensures that we cover - // all file reads in the point lookup path that can potentially timeout - while (timedout) { - ReadOptions ro; - if (set_deadline) { - ro.deadline = std::chrono::microseconds{env->NowMicros() + 10000}; - } - if (set_timeout) { - ro.io_timeout = std::chrono::microseconds{5000}; - } - fs->SetDelayTrigger(ro.deadline, ro.io_timeout, io_deadline_trigger); - - block_cache->SetCapacity(0); - block_cache->SetCapacity(1048576); - - Iterator* iter = dbfull()->NewIterator(ro); - int count = 0; - iter->Seek("k50"); - while (iter->Valid() && count++ < 100) { - iter->Next(); - } - if (fs->TimedOut()) { - ASSERT_FALSE(iter->Valid()); - ASSERT_EQ(iter->status(), Status::TimedOut()); - } else { - timedout = false; - ASSERT_OK(iter->status()); - } - delete iter; - io_deadline_trigger++; - } - // Reset the delay sequence in order to avoid false alarms during Reopen - fs->SetDelayTrigger(std::chrono::microseconds::zero(), - std::chrono::microseconds::zero(), 0); - } - Close(); -} - -// Param 0: If true, set read_options.deadline -// Param 1: If true, set read_options.io_timeout -INSTANTIATE_TEST_CASE_P(DBBasicTestDeadline, DBBasicTestDeadline, - ::testing::Values(std::make_tuple(true, false), - std::make_tuple(false, true), - std::make_tuple(true, true))); -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_block_cache_test.cc b/db/db_block_cache_test.cc deleted file mode 100644 index 1a1366353..000000000 --- a/db/db_block_cache_test.cc +++ /dev/null @@ -1,1969 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -#include -#include -#include -#include - -#include "cache/cache_entry_roles.h" -#include "cache/cache_key.h" -#include "cache/lru_cache.h" -#include "cache/typed_cache.h" -#include "db/column_family.h" -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "env/unique_id_gen.h" -#include "port/stack_trace.h" -#include "rocksdb/persistent_cache.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table.h" -#include "rocksdb/table_properties.h" -#include "table/block_based/block_based_table_reader.h" -#include "table/unique_id_impl.h" -#include "util/compression.h" -#include "util/defer.h" -#include "util/hash.h" -#include "util/math.h" -#include "util/random.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -class DBBlockCacheTest : public DBTestBase { - private: - size_t miss_count_ = 0; - size_t hit_count_ = 0; - size_t insert_count_ = 0; - size_t failure_count_ = 0; - size_t compression_dict_miss_count_ = 0; - size_t compression_dict_hit_count_ = 0; - size_t compression_dict_insert_count_ = 0; - - public: - const size_t kNumBlocks = 10; - const size_t kValueSize = 100; - - DBBlockCacheTest() - : DBTestBase("db_block_cache_test", /*env_do_fsync=*/true) {} - - BlockBasedTableOptions GetTableOptions() { - BlockBasedTableOptions table_options; - // Set a small enough block size so that each key-value get its own block. - table_options.block_size = 1; - return table_options; - } - - Options GetOptions(const BlockBasedTableOptions& table_options) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.avoid_flush_during_recovery = false; - // options.compression = kNoCompression; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - return options; - } - - void InitTable(const Options& /*options*/) { - std::string value(kValueSize, 'a'); - for (size_t i = 0; i < kNumBlocks; i++) { - ASSERT_OK(Put(std::to_string(i), value.c_str())); - } - } - - void RecordCacheCounters(const Options& options) { - miss_count_ = TestGetTickerCount(options, BLOCK_CACHE_MISS); - hit_count_ = TestGetTickerCount(options, BLOCK_CACHE_HIT); - insert_count_ = TestGetTickerCount(options, BLOCK_CACHE_ADD); - failure_count_ = TestGetTickerCount(options, BLOCK_CACHE_ADD_FAILURES); - } - - void RecordCacheCountersForCompressionDict(const Options& options) { - compression_dict_miss_count_ = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS); - compression_dict_hit_count_ = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_HIT); - compression_dict_insert_count_ = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD); - } - - void CheckCacheCounters(const Options& options, size_t expected_misses, - size_t expected_hits, size_t expected_inserts, - size_t expected_failures) { - size_t new_miss_count = TestGetTickerCount(options, BLOCK_CACHE_MISS); - size_t new_hit_count = TestGetTickerCount(options, BLOCK_CACHE_HIT); - size_t new_insert_count = TestGetTickerCount(options, BLOCK_CACHE_ADD); - size_t new_failure_count = - TestGetTickerCount(options, BLOCK_CACHE_ADD_FAILURES); - ASSERT_EQ(miss_count_ + expected_misses, new_miss_count); - ASSERT_EQ(hit_count_ + expected_hits, new_hit_count); - ASSERT_EQ(insert_count_ + expected_inserts, new_insert_count); - ASSERT_EQ(failure_count_ + expected_failures, new_failure_count); - miss_count_ = new_miss_count; - hit_count_ = new_hit_count; - insert_count_ = new_insert_count; - failure_count_ = new_failure_count; - } - - void CheckCacheCountersForCompressionDict( - const Options& options, size_t expected_compression_dict_misses, - size_t expected_compression_dict_hits, - size_t expected_compression_dict_inserts) { - size_t new_compression_dict_miss_count = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS); - size_t new_compression_dict_hit_count = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_HIT); - size_t new_compression_dict_insert_count = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD); - ASSERT_EQ(compression_dict_miss_count_ + expected_compression_dict_misses, - new_compression_dict_miss_count); - ASSERT_EQ(compression_dict_hit_count_ + expected_compression_dict_hits, - new_compression_dict_hit_count); - ASSERT_EQ( - compression_dict_insert_count_ + expected_compression_dict_inserts, - new_compression_dict_insert_count); - compression_dict_miss_count_ = new_compression_dict_miss_count; - compression_dict_hit_count_ = new_compression_dict_hit_count; - compression_dict_insert_count_ = new_compression_dict_insert_count; - } - - const std::array GetCacheEntryRoleCountsBg() { - // Verify in cache entry role stats - std::array cache_entry_role_counts; - std::map values; - EXPECT_TRUE(db_->GetMapProperty(DB::Properties::kFastBlockCacheEntryStats, - &values)); - for (size_t i = 0; i < kNumCacheEntryRoles; ++i) { - auto role = static_cast(i); - cache_entry_role_counts[i] = - ParseSizeT(values[BlockCacheEntryStatsMapKeys::EntryCount(role)]); - } - return cache_entry_role_counts; - } -}; - -TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) { - ReadOptions read_options; - read_options.fill_cache = false; - auto table_options = GetTableOptions(); - auto options = GetOptions(table_options); - InitTable(options); - - LRUCacheOptions co; - co.capacity = 0; - co.num_shard_bits = 0; - co.strict_capacity_limit = false; - // Needed not to count entry stats collector - co.metadata_charge_policy = kDontChargeCacheMetadata; - std::shared_ptr cache = NewLRUCache(co); - table_options.block_cache = cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - RecordCacheCounters(options); - - std::vector> iterators(kNumBlocks - 1); - Iterator* iter = nullptr; - - ASSERT_EQ(0, cache->GetUsage()); - iter = db_->NewIterator(read_options); - iter->Seek(std::to_string(0)); - ASSERT_LT(0, cache->GetUsage()); - delete iter; - iter = nullptr; - ASSERT_EQ(0, cache->GetUsage()); -} - -TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) { - ReadOptions read_options; - auto table_options = GetTableOptions(); - auto options = GetOptions(table_options); - InitTable(options); - - LRUCacheOptions co; - co.capacity = 0; - co.num_shard_bits = 0; - co.strict_capacity_limit = false; - // Needed not to count entry stats collector - co.metadata_charge_policy = kDontChargeCacheMetadata; - std::shared_ptr cache = NewLRUCache(co); - table_options.block_cache = cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - RecordCacheCounters(options); - - std::vector> iterators(kNumBlocks - 1); - Iterator* iter = nullptr; - - // Load blocks into cache. - for (size_t i = 0; i + 1 < kNumBlocks; i++) { - iter = db_->NewIterator(read_options); - iter->Seek(std::to_string(i)); - ASSERT_OK(iter->status()); - CheckCacheCounters(options, 1, 0, 1, 0); - iterators[i].reset(iter); - } - size_t usage = cache->GetUsage(); - ASSERT_LT(0, usage); - cache->SetCapacity(usage); - ASSERT_EQ(usage, cache->GetPinnedUsage()); - - // Test with strict capacity limit. - cache->SetStrictCapacityLimit(true); - iter = db_->NewIterator(read_options); - iter->Seek(std::to_string(kNumBlocks - 1)); - ASSERT_TRUE(iter->status().IsMemoryLimit()); - CheckCacheCounters(options, 1, 0, 0, 1); - delete iter; - iter = nullptr; - - // Release iterators and access cache again. - for (size_t i = 0; i + 1 < kNumBlocks; i++) { - iterators[i].reset(); - CheckCacheCounters(options, 0, 0, 0, 0); - } - ASSERT_EQ(0, cache->GetPinnedUsage()); - for (size_t i = 0; i + 1 < kNumBlocks; i++) { - iter = db_->NewIterator(read_options); - iter->Seek(std::to_string(i)); - ASSERT_OK(iter->status()); - CheckCacheCounters(options, 0, 1, 0, 0); - iterators[i].reset(iter); - } -} - -#ifdef SNAPPY - -namespace { -class PersistentCacheFromCache : public PersistentCache { - public: - PersistentCacheFromCache(std::shared_ptr cache, bool read_only) - : cache_(cache), read_only_(read_only) {} - - Status Insert(const Slice& key, const char* data, - const size_t size) override { - if (read_only_) { - return Status::NotSupported(); - } - std::unique_ptr copy{new char[size]}; - std::copy_n(data, size, copy.get()); - Status s = cache_.Insert(key, copy.get(), size); - if (s.ok()) { - copy.release(); - } - return s; - } - - Status Lookup(const Slice& key, std::unique_ptr* data, - size_t* size) override { - auto handle = cache_.Lookup(key); - if (handle) { - char* ptr = cache_.Value(handle); - *size = cache_.get()->GetCharge(handle); - data->reset(new char[*size]); - std::copy_n(ptr, *size, data->get()); - cache_.Release(handle); - return Status::OK(); - } else { - return Status::NotFound(); - } - } - - bool IsCompressed() override { return false; } - - StatsType Stats() override { return StatsType(); } - - std::string GetPrintableOptions() const override { return ""; } - - uint64_t NewId() override { return cache_.get()->NewId(); } - - private: - BasicTypedSharedCacheInterface cache_; - bool read_only_; -}; - -class ReadOnlyCacheWrapper : public CacheWrapper { - public: - using CacheWrapper::CacheWrapper; - - const char* Name() const override { return "ReadOnlyCacheWrapper"; } - - Status Insert(const Slice& /*key*/, Cache::ObjectPtr /*value*/, - const CacheItemHelper* /*helper*/, size_t /*charge*/, - Handle** /*handle*/, Priority /*priority*/) override { - return Status::NotSupported(); - } -}; - -} // anonymous namespace -#endif // SNAPPY - - -// Make sure that when options.block_cache is set, after a new table is -// created its index/filter blocks are added to block cache. -TEST_F(DBBlockCacheTest, IndexAndFilterBlocksOfNewTableAddedToCache) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy.reset(NewBloomFilterPolicy(20)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "key", "val")); - // Create a new table. - ASSERT_OK(Flush(1)); - - // index/filter blocks added to block cache right after table creation. - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(2, /* only index/filter were added */ - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS)); - uint64_t int_num; - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_EQ(int_num, 0U); - - // Make sure filter block is in cache. - std::string value; - ReadOptions ropt; - db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value); - - // Miss count should remain the same. - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - - db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - - // Make sure index block is in cache. - auto index_block_hit = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT); - value = Get(1, "key"); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(index_block_hit + 1, - TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); - - value = Get(1, "key"); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(index_block_hit + 2, - TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); -} - -// With fill_cache = false, fills up the cache, then iterates over the entire -// db, verify dummy entries inserted in `BlockBasedTable::NewDataBlockIterator` -// does not cause heap-use-after-free errors in COMPILE_WITH_ASAN=1 runs -TEST_F(DBBlockCacheTest, FillCacheAndIterateDB) { - ReadOptions read_options; - read_options.fill_cache = false; - auto table_options = GetTableOptions(); - auto options = GetOptions(table_options); - InitTable(options); - - std::shared_ptr cache = NewLRUCache(10, 0, true); - table_options.block_cache = cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_OK(Put("key1", "val1")); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key3", "val3")); - ASSERT_OK(Put("key4", "val4")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key5", "val5")); - ASSERT_OK(Put("key6", "val6")); - ASSERT_OK(Flush()); - - Iterator* iter = nullptr; - - iter = db_->NewIterator(read_options); - iter->Seek(std::to_string(0)); - while (iter->Valid()) { - iter->Next(); - } - delete iter; - iter = nullptr; -} - -TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - LRUCacheOptions co; - // 500 bytes are enough to hold the first two blocks - co.capacity = 500; - co.num_shard_bits = 0; - co.strict_capacity_limit = false; - co.metadata_charge_policy = kDontChargeCacheMetadata; - std::shared_ptr cache = NewLRUCache(co); - table_options.block_cache = cache; - table_options.filter_policy.reset(NewBloomFilterPolicy(20, true)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "longer_key", "val")); - // Create a new table - ASSERT_OK(Flush(1)); - size_t index_bytes_insert = - TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT); - size_t filter_bytes_insert = - TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT); - ASSERT_GT(index_bytes_insert, 0); - ASSERT_GT(filter_bytes_insert, 0); - ASSERT_EQ(cache->GetUsage(), index_bytes_insert + filter_bytes_insert); - // set the cache capacity to the current usage - cache->SetCapacity(index_bytes_insert + filter_bytes_insert); - // Note that the second key needs to be no longer than the first one. - // Otherwise the second index block may not fit in cache. - ASSERT_OK(Put(1, "key", "val")); - // Create a new table - ASSERT_OK(Flush(1)); - // cache evicted old index and block entries - ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT), - index_bytes_insert); - ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT), - filter_bytes_insert); -} - -#if (defined OS_LINUX || defined OS_WIN) -TEST_F(DBBlockCacheTest, WarmCacheWithDataBlocksDuringFlush) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - - BlockBasedTableOptions table_options; - table_options.block_cache = NewLRUCache(1 << 25, 0, false); - table_options.cache_index_and_filter_blocks = false; - table_options.prepopulate_block_cache = - BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - std::string value(kValueSize, 'a'); - for (size_t i = 1; i <= kNumBlocks; i++) { - ASSERT_OK(Put(std::to_string(i), value)); - ASSERT_OK(Flush()); - ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD)); - ASSERT_EQ(value, Get(std::to_string(i))); - ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_HIT)); - } - // Verify compaction not counted - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr, - /*end=*/nullptr)); - EXPECT_EQ(kNumBlocks, - options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD)); -} - -// This test cache data, index and filter blocks during flush. -class DBBlockCacheTest1 : public DBTestBase, - public ::testing::WithParamInterface { - public: - const size_t kNumBlocks = 10; - const size_t kValueSize = 100; - DBBlockCacheTest1() : DBTestBase("db_block_cache_test1", true) {} -}; - -INSTANTIATE_TEST_CASE_P(DBBlockCacheTest1, DBBlockCacheTest1, - ::testing::Values(1, 2)); - -TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - - BlockBasedTableOptions table_options; - table_options.block_cache = NewLRUCache(1 << 25, 0, false); - - uint32_t filter_type = GetParam(); - switch (filter_type) { - case 1: // partition_filter - table_options.partition_filters = true; - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - break; - case 2: // full filter - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - break; - default: - assert(false); - } - - table_options.cache_index_and_filter_blocks = true; - table_options.prepopulate_block_cache = - BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - std::string value(kValueSize, 'a'); - for (size_t i = 1; i <= kNumBlocks; i++) { - ASSERT_OK(Put(std::to_string(i), value)); - ASSERT_OK(Flush()); - ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD)); - if (filter_type == 1) { - ASSERT_EQ(2 * i, - options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD)); - ASSERT_EQ(2 * i, - options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD)); - } else { - ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD)); - ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD)); - } - ASSERT_EQ(value, Get(std::to_string(i))); - - ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(i * 3, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_HIT)); - if (filter_type == 1) { - ASSERT_EQ(i * 3, - options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT)); - } else { - ASSERT_EQ(i * 2, - options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT)); - } - ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS)); - } - - // Verify compaction not counted - CompactRangeOptions cro; - // Ensure files are rewritten, not just trivially moved. - cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, /*begin=*/nullptr, /*end=*/nullptr)); - EXPECT_EQ(kNumBlocks, - options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD)); - // Index and filter blocks are automatically warmed when the new table file - // is automatically opened at the end of compaction. This is not easily - // disabled so results in the new index and filter blocks being warmed. - if (filter_type == 1) { - EXPECT_EQ(2 * (1 + kNumBlocks), - options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD)); - EXPECT_EQ(2 * (1 + kNumBlocks), - options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD)); - } else { - EXPECT_EQ(1 + kNumBlocks, - options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD)); - EXPECT_EQ(1 + kNumBlocks, - options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD)); - } -} - -TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - - BlockBasedTableOptions table_options; - table_options.block_cache = NewLRUCache(1 << 25, 0, false); - table_options.cache_index_and_filter_blocks = false; - table_options.prepopulate_block_cache = - BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly; - - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - std::string value(kValueSize, 'a'); - - for (size_t i = 1; i <= 5; i++) { - ASSERT_OK(Put(std::to_string(i), value)); - ASSERT_OK(Flush()); - ASSERT_EQ(1, - options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD)); - - ASSERT_EQ(value, Get(std::to_string(i))); - ASSERT_EQ(0, - options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD)); - ASSERT_EQ( - 0, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_EQ(1, - options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT)); - } - - ASSERT_OK(dbfull()->SetOptions( - {{"block_based_table_factory", "{prepopulate_block_cache=kDisable;}"}})); - - for (size_t i = 6; i <= kNumBlocks; i++) { - ASSERT_OK(Put(std::to_string(i), value)); - ASSERT_OK(Flush()); - ASSERT_EQ(0, - options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD)); - - ASSERT_EQ(value, Get(std::to_string(i))); - ASSERT_EQ(1, - options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD)); - ASSERT_EQ( - 1, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_EQ(0, - options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT)); - } -} -#endif - -namespace { - -// A mock cache wraps LRUCache, and record how many entries have been -// inserted for each priority. -class MockCache : public LRUCache { - public: - static uint32_t high_pri_insert_count; - static uint32_t low_pri_insert_count; - - MockCache() - : LRUCache((size_t)1 << 25 /*capacity*/, 0 /*num_shard_bits*/, - false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/, - 0.0 /*low_pri_pool_ratio*/) {} - - using ShardedCache::Insert; - - Status Insert(const Slice& key, Cache::ObjectPtr value, - const Cache::CacheItemHelper* helper, size_t charge, - Handle** handle, Priority priority) override { - if (priority == Priority::LOW) { - low_pri_insert_count++; - } else { - high_pri_insert_count++; - } - return LRUCache::Insert(key, value, helper, charge, handle, priority); - } -}; - -uint32_t MockCache::high_pri_insert_count = 0; -uint32_t MockCache::low_pri_insert_count = 0; - -} // anonymous namespace - -TEST_F(DBBlockCacheTest, IndexAndFilterBlocksCachePriority) { - for (auto priority : {Cache::Priority::LOW, Cache::Priority::HIGH}) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.block_cache.reset(new MockCache()); - table_options.filter_policy.reset(NewBloomFilterPolicy(20)); - table_options.cache_index_and_filter_blocks_with_high_priority = - priority == Cache::Priority::HIGH ? true : false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - MockCache::high_pri_insert_count = 0; - MockCache::low_pri_insert_count = 0; - - // Create a new table. - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Put("bar", "value")); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // index/filter blocks added to block cache right after table creation. - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(2, /* only index/filter were added */ - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS)); - if (priority == Cache::Priority::LOW) { - ASSERT_EQ(0u, MockCache::high_pri_insert_count); - ASSERT_EQ(2u, MockCache::low_pri_insert_count); - } else { - ASSERT_EQ(2u, MockCache::high_pri_insert_count); - ASSERT_EQ(0u, MockCache::low_pri_insert_count); - } - - // Access data block. - ASSERT_EQ("value", Get("foo")); - - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(3, /*adding data block*/ - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS)); - - // Data block should be inserted with low priority. - if (priority == Cache::Priority::LOW) { - ASSERT_EQ(0u, MockCache::high_pri_insert_count); - ASSERT_EQ(3u, MockCache::low_pri_insert_count); - } else { - ASSERT_EQ(2u, MockCache::high_pri_insert_count); - ASSERT_EQ(1u, MockCache::low_pri_insert_count); - } - } -} - -namespace { - -// An LRUCache wrapper that can falsely report "not found" on Lookup. -// This allows us to manipulate BlockBasedTableReader into thinking -// another thread inserted the data in between Lookup and Insert, -// while mostly preserving the LRUCache interface/behavior. -class LookupLiarCache : public CacheWrapper { - int nth_lookup_not_found_ = 0; - - public: - explicit LookupLiarCache(std::shared_ptr target) - : CacheWrapper(std::move(target)) {} - - const char* Name() const override { return "LookupLiarCache"; } - - Handle* Lookup(const Slice& key, const CacheItemHelper* helper = nullptr, - CreateContext* create_context = nullptr, - Priority priority = Priority::LOW, - Statistics* stats = nullptr) override { - if (nth_lookup_not_found_ == 1) { - nth_lookup_not_found_ = 0; - return nullptr; - } - if (nth_lookup_not_found_ > 1) { - --nth_lookup_not_found_; - } - return CacheWrapper::Lookup(key, helper, create_context, priority, stats); - } - - // 1 == next lookup, 2 == after next, etc. - void SetNthLookupNotFound(int n) { nth_lookup_not_found_ = n; } -}; - -} // anonymous namespace - -TEST_F(DBBlockCacheTest, AddRedundantStats) { - const size_t capacity = size_t{1} << 25; - const int num_shard_bits = 0; // 1 shard - int iterations_tested = 0; - for (std::shared_ptr base_cache : - {NewLRUCache(capacity, num_shard_bits), - HyperClockCacheOptions( - capacity, - BlockBasedTableOptions().block_size /*estimated_value_size*/, - num_shard_bits) - .MakeSharedCache()}) { - if (!base_cache) { - // Skip clock cache when not supported - continue; - } - ++iterations_tested; - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - - std::shared_ptr cache = - std::make_shared(base_cache); - - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.block_cache = cache; - table_options.filter_policy.reset(NewBloomFilterPolicy(50)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - // Create a new table. - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Put("bar", "value")); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // Normal access filter+index+data. - ASSERT_EQ("value", Get("foo")); - - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD)); - // -------- - ASSERT_EQ(3, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD_REDUNDANT)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD_REDUNDANT)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD_REDUNDANT)); - // -------- - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_ADD_REDUNDANT)); - - // Againt access filter+index+data, but force redundant load+insert on index - cache->SetNthLookupNotFound(2); - ASSERT_EQ("value", Get("bar")); - - ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD)); - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD)); - // -------- - ASSERT_EQ(4, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD_REDUNDANT)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD_REDUNDANT)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD_REDUNDANT)); - // -------- - ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_ADD_REDUNDANT)); - - // Access just filter (with high probability), and force redundant - // load+insert - cache->SetNthLookupNotFound(1); - ASSERT_EQ("NOT_FOUND", Get("this key was not added")); - - EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD)); - EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD)); - EXPECT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD)); - // -------- - EXPECT_EQ(5, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - EXPECT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD_REDUNDANT)); - EXPECT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD_REDUNDANT)); - EXPECT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD_REDUNDANT)); - // -------- - EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_ADD_REDUNDANT)); - - // Access just data, forcing redundant load+insert - ReadOptions read_options; - std::unique_ptr iter{db_->NewIterator(read_options)}; - cache->SetNthLookupNotFound(1); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), "bar"); - - EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD)); - EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD)); - EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD)); - // -------- - EXPECT_EQ(6, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - EXPECT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD_REDUNDANT)); - EXPECT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD_REDUNDANT)); - EXPECT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_ADD_REDUNDANT)); - // -------- - EXPECT_EQ(3, TestGetTickerCount(options, BLOCK_CACHE_ADD_REDUNDANT)); - } - EXPECT_GE(iterations_tested, 1); -} - -TEST_F(DBBlockCacheTest, ParanoidFileChecks) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.level0_file_num_compaction_trigger = 2; - options.paranoid_file_checks = true; - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = false; - table_options.filter_policy.reset(NewBloomFilterPolicy(20)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "1_key", "val")); - ASSERT_OK(Put(1, "9_key", "val")); - // Create a new table. - ASSERT_OK(Flush(1)); - ASSERT_EQ(1, /* read and cache data block */ - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - ASSERT_OK(Put(1, "1_key2", "val2")); - ASSERT_OK(Put(1, "9_key2", "val2")); - // Create a new SST file. This will further trigger a compaction - // and generate another file. - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(3, /* Totally 3 files created up to now */ - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - // After disabling options.paranoid_file_checks. NO further block - // is added after generating a new file. - ASSERT_OK( - dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "false"}})); - - ASSERT_OK(Put(1, "1_key3", "val3")); - ASSERT_OK(Put(1, "9_key3", "val3")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "1_key4", "val4")); - ASSERT_OK(Put(1, "9_key4", "val4")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(3, /* Totally 3 files created up to now */ - TestGetTickerCount(options, BLOCK_CACHE_ADD)); -} - -TEST_F(DBBlockCacheTest, CacheCompressionDict) { - const int kNumFiles = 4; - const int kNumEntriesPerFile = 128; - const int kNumBytesPerEntry = 1024; - - // Try all the available libraries that support dictionary compression - std::vector compression_types; - if (Zlib_Supported()) { - compression_types.push_back(kZlibCompression); - } - if (LZ4_Supported()) { - compression_types.push_back(kLZ4Compression); - compression_types.push_back(kLZ4HCCompression); - } - if (ZSTD_Supported()) { - compression_types.push_back(kZSTD); - } else if (ZSTDNotFinal_Supported()) { - compression_types.push_back(kZSTDNotFinalCompression); - } - Random rnd(301); - for (auto compression_type : compression_types) { - Options options = CurrentOptions(); - options.bottommost_compression = compression_type; - options.bottommost_compression_opts.max_dict_bytes = 4096; - options.bottommost_compression_opts.enabled = true; - options.create_if_missing = true; - options.num_levels = 2; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry; - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.block_cache.reset(new MockCache()); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - RecordCacheCountersForCompressionDict(options); - - for (int i = 0; i < kNumFiles; ++i) { - ASSERT_EQ(i, NumTableFilesAtLevel(0, 0)); - for (int j = 0; j < kNumEntriesPerFile; ++j) { - std::string value = rnd.RandomString(kNumBytesPerEntry); - ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str())); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1)); - - // Compression dictionary blocks are preloaded. - CheckCacheCountersForCompressionDict( - options, kNumFiles /* expected_compression_dict_misses */, - 0 /* expected_compression_dict_hits */, - kNumFiles /* expected_compression_dict_inserts */); - - // Seek to a key in a file. It should cause the SST's dictionary meta-block - // to be read. - RecordCacheCounters(options); - RecordCacheCountersForCompressionDict(options); - ReadOptions read_options; - ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1))); - // Two block hits: index and dictionary since they are prefetched - // One block missed/added: data block - CheckCacheCounters(options, 1 /* expected_misses */, 2 /* expected_hits */, - 1 /* expected_inserts */, 0 /* expected_failures */); - CheckCacheCountersForCompressionDict( - options, 0 /* expected_compression_dict_misses */, - 1 /* expected_compression_dict_hits */, - 0 /* expected_compression_dict_inserts */); - } -} - -static void ClearCache(Cache* cache) { - std::deque keys; - Cache::ApplyToAllEntriesOptions opts; - auto callback = [&](const Slice& key, Cache::ObjectPtr, size_t /*charge*/, - const Cache::CacheItemHelper* helper) { - if (helper && helper->role == CacheEntryRole::kMisc) { - // Keep the stats collector - return; - } - keys.push_back(key.ToString()); - }; - cache->ApplyToAllEntries(callback, opts); - for (auto& k : keys) { - cache->Erase(k); - } -} - -TEST_F(DBBlockCacheTest, CacheEntryRoleStats) { - const size_t capacity = size_t{1} << 25; - int iterations_tested = 0; - for (bool partition : {false, true}) { - for (std::shared_ptr cache : - {NewLRUCache(capacity), - HyperClockCacheOptions( - capacity, - BlockBasedTableOptions().block_size /*estimated_value_size*/) - .MakeSharedCache()}) { - ++iterations_tested; - - Options options = CurrentOptions(); - SetTimeElapseOnlySleepOnReopen(&options); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.max_open_files = 13; - options.table_cache_numshardbits = 0; - // If this wakes up, it could interfere with test - options.stats_dump_period_sec = 0; - - BlockBasedTableOptions table_options; - table_options.block_cache = cache; - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy.reset(NewBloomFilterPolicy(50)); - if (partition) { - table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; - table_options.partition_filters = true; - } - table_options.metadata_cache_options.top_level_index_pinning = - PinningTier::kNone; - table_options.metadata_cache_options.partition_pinning = - PinningTier::kNone; - table_options.metadata_cache_options.unpartitioned_pinning = - PinningTier::kNone; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - // Create a new table. - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Put("bar", "value")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("zfoo", "value")); - ASSERT_OK(Put("zbar", "value")); - ASSERT_OK(Flush()); - - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - - // Fresh cache - ClearCache(cache.get()); - - std::array expected{}; - // For CacheEntryStatsCollector - expected[static_cast(CacheEntryRole::kMisc)] = 1; - EXPECT_EQ(expected, GetCacheEntryRoleCountsBg()); - - std::array prev_expected = expected; - - // First access only filters - ASSERT_EQ("NOT_FOUND", Get("different from any key added")); - expected[static_cast(CacheEntryRole::kFilterBlock)] += 2; - if (partition) { - expected[static_cast(CacheEntryRole::kFilterMetaBlock)] += 2; - } - // Within some time window, we will get cached entry stats - EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg()); - // Not enough to force a miss - env_->MockSleepForSeconds(45); - EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg()); - // Enough to force a miss - env_->MockSleepForSeconds(601); - EXPECT_EQ(expected, GetCacheEntryRoleCountsBg()); - - // Now access index and data block - ASSERT_EQ("value", Get("foo")); - expected[static_cast(CacheEntryRole::kIndexBlock)]++; - if (partition) { - // top-level - expected[static_cast(CacheEntryRole::kIndexBlock)]++; - } - expected[static_cast(CacheEntryRole::kDataBlock)]++; - // Enough to force a miss - env_->MockSleepForSeconds(601); - // But inject a simulated long scan so that we need a longer - // interval to force a miss next time. - SyncPoint::GetInstance()->SetCallBack( - "CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries", - [this](void*) { - // To spend no more than 0.2% of time scanning, we would need - // interval of at least 10000s - env_->MockSleepForSeconds(20); - }); - SyncPoint::GetInstance()->EnableProcessing(); - EXPECT_EQ(expected, GetCacheEntryRoleCountsBg()); - prev_expected = expected; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - // The same for other file - ASSERT_EQ("value", Get("zfoo")); - expected[static_cast(CacheEntryRole::kIndexBlock)]++; - if (partition) { - // top-level - expected[static_cast(CacheEntryRole::kIndexBlock)]++; - } - expected[static_cast(CacheEntryRole::kDataBlock)]++; - // Because of the simulated long scan, this is not enough to force - // a miss - env_->MockSleepForSeconds(601); - EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg()); - // But this is enough - env_->MockSleepForSeconds(10000); - EXPECT_EQ(expected, GetCacheEntryRoleCountsBg()); - prev_expected = expected; - - // Also check the GetProperty interface - std::map values; - ASSERT_TRUE( - db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, &values)); - - for (size_t i = 0; i < kNumCacheEntryRoles; ++i) { - auto role = static_cast(i); - EXPECT_EQ(std::to_string(expected[i]), - values[BlockCacheEntryStatsMapKeys::EntryCount(role)]); - } - - // Add one for kWriteBuffer - { - WriteBufferManager wbm(size_t{1} << 20, cache); - wbm.ReserveMem(1024); - expected[static_cast(CacheEntryRole::kWriteBuffer)]++; - // Now we check that the GetProperty interface is more agressive about - // re-scanning stats, but not totally aggressive. - // Within some time window, we will get cached entry stats - env_->MockSleepForSeconds(1); - EXPECT_EQ(std::to_string(prev_expected[static_cast( - CacheEntryRole::kWriteBuffer)]), - values[BlockCacheEntryStatsMapKeys::EntryCount( - CacheEntryRole::kWriteBuffer)]); - // Not enough for a "background" miss but enough for a "foreground" miss - env_->MockSleepForSeconds(45); - - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, - &values)); - EXPECT_EQ( - std::to_string( - expected[static_cast(CacheEntryRole::kWriteBuffer)]), - values[BlockCacheEntryStatsMapKeys::EntryCount( - CacheEntryRole::kWriteBuffer)]); - } - prev_expected = expected; - - // With collector pinned in cache, we should be able to hit - // even if the cache is full - ClearCache(cache.get()); - Cache::Handle* h = nullptr; - if (strcmp(cache->Name(), "LRUCache") == 0) { - ASSERT_OK(cache->Insert("Fill-it-up", nullptr, &kNoopCacheItemHelper, - capacity + 1, &h, Cache::Priority::HIGH)); - } else { - // For ClockCache we use a 16-byte key. - ASSERT_OK(cache->Insert("Fill-it-up-xxxxx", nullptr, - &kNoopCacheItemHelper, capacity + 1, &h, - Cache::Priority::HIGH)); - } - ASSERT_GT(cache->GetUsage(), cache->GetCapacity()); - expected = {}; - // For CacheEntryStatsCollector - expected[static_cast(CacheEntryRole::kMisc)] = 1; - // For Fill-it-up - expected[static_cast(CacheEntryRole::kMisc)]++; - // Still able to hit on saved stats - EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg()); - // Enough to force a miss - env_->MockSleepForSeconds(1000); - EXPECT_EQ(expected, GetCacheEntryRoleCountsBg()); - - cache->Release(h); - - // Now we test that the DB mutex is not held during scans, for the ways - // we know how to (possibly) trigger them. Without a better good way to - // check this, we simply inject an acquire & release of the DB mutex - // deep in the stat collection code. If we were already holding the - // mutex, that is UB that would at least be found by TSAN. - int scan_count = 0; - SyncPoint::GetInstance()->SetCallBack( - "CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries", - [this, &scan_count](void*) { - dbfull()->TEST_LockMutex(); - dbfull()->TEST_UnlockMutex(); - ++scan_count; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Different things that might trigger a scan, with mock sleeps to - // force a miss. - env_->MockSleepForSeconds(10000); - dbfull()->DumpStats(); - ASSERT_EQ(scan_count, 1); - - env_->MockSleepForSeconds(60); - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kFastBlockCacheEntryStats, - &values)); - ASSERT_EQ(scan_count, 1); - ASSERT_TRUE( - db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, &values)); - ASSERT_EQ(scan_count, 2); - - env_->MockSleepForSeconds(10000); - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kFastBlockCacheEntryStats, - &values)); - ASSERT_EQ(scan_count, 3); - - env_->MockSleepForSeconds(60); - std::string value_str; - ASSERT_TRUE(db_->GetProperty(DB::Properties::kFastBlockCacheEntryStats, - &value_str)); - ASSERT_EQ(scan_count, 3); - ASSERT_TRUE( - db_->GetProperty(DB::Properties::kBlockCacheEntryStats, &value_str)); - ASSERT_EQ(scan_count, 4); - - env_->MockSleepForSeconds(10000); - ASSERT_TRUE(db_->GetProperty(DB::Properties::kFastBlockCacheEntryStats, - &value_str)); - ASSERT_EQ(scan_count, 5); - - ASSERT_TRUE(db_->GetProperty(DB::Properties::kCFStats, &value_str)); - // To match historical speed, querying this property no longer triggers - // a scan, even if results are old. But periodic dump stats should keep - // things reasonably updated. - ASSERT_EQ(scan_count, /*unchanged*/ 5); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } - EXPECT_GE(iterations_tested, 1); - } -} - -namespace { - -void DummyFillCache(Cache& cache, size_t entry_size, - std::vector>& handles) { - // fprintf(stderr, "Entry size: %zu\n", entry_size); - handles.clear(); - cache.EraseUnRefEntries(); - void* fake_value = &cache; - size_t capacity = cache.GetCapacity(); - OffsetableCacheKey ck{"abc", "abc", 42}; - for (size_t my_usage = 0; my_usage < capacity;) { - size_t charge = std::min(entry_size, capacity - my_usage); - Cache::Handle* handle; - Status st = cache.Insert(ck.WithOffset(my_usage).AsSlice(), fake_value, - &kNoopCacheItemHelper, charge, &handle); - ASSERT_OK(st); - handles.emplace_back(&cache, handle); - my_usage += charge; - } -} - -class CountingLogger : public Logger { - public: - ~CountingLogger() override {} - using Logger::Logv; - void Logv(const InfoLogLevel log_level, const char* format, - va_list /*ap*/) override { - if (std::strstr(format, "HyperClockCache") == nullptr) { - // Not a match - return; - } - // static StderrLogger debug; - // debug.Logv(log_level, format, ap); - if (log_level == InfoLogLevel::INFO_LEVEL) { - ++info_count_; - } else if (log_level == InfoLogLevel::WARN_LEVEL) { - ++warn_count_; - } else if (log_level == InfoLogLevel::ERROR_LEVEL) { - ++error_count_; - } - } - - std::array PopCounts() { - std::array rv{{info_count_, warn_count_, error_count_}}; - info_count_ = warn_count_ = error_count_ = 0; - return rv; - } - - private: - int info_count_{}; - int warn_count_{}; - int error_count_{}; -}; - -} // namespace - -TEST_F(DBBlockCacheTest, HyperClockCacheReportProblems) { - size_t capacity = 1024 * 1024; - size_t value_size_est = 8 * 1024; - HyperClockCacheOptions hcc_opts{capacity, value_size_est}; - hcc_opts.num_shard_bits = 2; // 4 shards - hcc_opts.metadata_charge_policy = kDontChargeCacheMetadata; - std::shared_ptr cache = hcc_opts.MakeSharedCache(); - std::shared_ptr logger = std::make_shared(); - - auto table_options = GetTableOptions(); - auto options = GetOptions(table_options); - table_options.block_cache = cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.info_log = logger; - // Going to sample more directly - options.stats_dump_period_sec = 0; - Reopen(options); - - std::vector> handles; - - // Clear anything from DB startup - logger->PopCounts(); - - // Fill cache based on expected size and check that when we - // don't report anything relevant in periodic stats dump - DummyFillCache(*cache, value_size_est, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 0, 0}})); - - // Same, within reasonable bounds - DummyFillCache(*cache, value_size_est - value_size_est / 4, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 0, 0}})); - - DummyFillCache(*cache, value_size_est + value_size_est / 3, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 0, 0}})); - - // Estimate too high (value size too low) eventually reports ERROR - DummyFillCache(*cache, value_size_est / 2, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 1, 0}})); - - DummyFillCache(*cache, value_size_est / 3, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 0, 1}})); - - // Estimate too low (value size too high) starts with INFO - // and is only WARNING in the worst case - DummyFillCache(*cache, value_size_est * 2, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{1, 0, 0}})); - - DummyFillCache(*cache, value_size_est * 3, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 1, 0}})); - - DummyFillCache(*cache, value_size_est * 20, handles); - dbfull()->DumpStats(); - EXPECT_EQ(logger->PopCounts(), (std::array{{0, 1, 0}})); -} - - -class DBBlockCacheKeyTest - : public DBTestBase, - public testing::WithParamInterface> { - public: - DBBlockCacheKeyTest() - : DBTestBase("db_block_cache_test", /*env_do_fsync=*/false) {} - - void SetUp() override { - use_compressed_cache_ = std::get<0>(GetParam()); - exclude_file_numbers_ = std::get<1>(GetParam()); - } - - bool use_compressed_cache_; - bool exclude_file_numbers_; -}; - -// Disable LinkFile so that we can physically copy a DB using Checkpoint. -// Disable file GetUniqueId to enable stable cache keys. -class StableCacheKeyTestFS : public FaultInjectionTestFS { - public: - explicit StableCacheKeyTestFS(const std::shared_ptr& base) - : FaultInjectionTestFS(base) { - SetFailGetUniqueId(true); - } - - virtual ~StableCacheKeyTestFS() override {} - - IOStatus LinkFile(const std::string&, const std::string&, const IOOptions&, - IODebugContext*) override { - return IOStatus::NotSupported("Disabled"); - } -}; - -TEST_P(DBBlockCacheKeyTest, StableCacheKeys) { - std::shared_ptr test_fs{ - new StableCacheKeyTestFS(env_->GetFileSystem())}; - std::unique_ptr test_env{ - new CompositeEnvWrapper(env_, test_fs)}; - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.env = test_env.get(); - - // Corrupting the table properties corrupts the unique id. - // Ignore the unique id recorded in the manifest. - options.verify_sst_unique_id_in_manifest = false; - - BlockBasedTableOptions table_options; - - int key_count = 0; - uint64_t expected_stat = 0; - - std::function verify_stats; - table_options.cache_index_and_filter_blocks = true; - table_options.block_cache = NewLRUCache(1 << 25, 0, false); - verify_stats = [&options, &expected_stat] { - ASSERT_EQ(expected_stat, - options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD)); - ASSERT_EQ(expected_stat, - options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD)); - ASSERT_EQ(expected_stat, - options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD)); - }; - - table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - CreateAndReopenWithCF({"koko"}, options); - - if (exclude_file_numbers_) { - // Simulate something like old behavior without file numbers in properties. - // This is a "control" side of the test that also ensures safely degraded - // behavior on old files. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::BlockBasedTableBuilder:PreSetupBaseCacheKey", - [&](void* arg) { - TableProperties* props = reinterpret_cast(arg); - props->orig_file_number = 0; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - } - - std::function perform_gets = [&key_count, &expected_stat, this]() { - if (exclude_file_numbers_) { - // No cache key reuse should happen, because we can't rely on current - // file number being stable - expected_stat += key_count; - } else { - // Cache keys should be stable - expected_stat = key_count; - } - for (int i = 0; i < key_count; ++i) { - ASSERT_EQ(Get(1, Key(i)), "abc"); - } - }; - - // Ordinary SST files with same session id - const std::string something_compressible(500U, 'x'); - for (int i = 0; i < 2; ++i) { - ASSERT_OK(Put(1, Key(key_count), "abc")); - ASSERT_OK(Put(1, Key(key_count) + "a", something_compressible)); - ASSERT_OK(Flush(1)); - ++key_count; - } - - // Save an export of those ordinary SST files for later - std::string export_files_dir = dbname_ + "/exported"; - ExportImportFilesMetaData* metadata_ptr_ = nullptr; - Checkpoint* checkpoint; - ASSERT_OK(Checkpoint::Create(db_, &checkpoint)); - ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir, - &metadata_ptr_)); - ASSERT_NE(metadata_ptr_, nullptr); - delete checkpoint; - checkpoint = nullptr; - - // External SST files with same session id - SstFileWriter sst_file_writer(EnvOptions(), options); - std::vector external; - for (int i = 0; i < 2; ++i) { - std::string f = dbname_ + "/external" + std::to_string(i) + ".sst"; - external.push_back(f); - ASSERT_OK(sst_file_writer.Open(f)); - ASSERT_OK(sst_file_writer.Put(Key(key_count), "abc")); - ASSERT_OK( - sst_file_writer.Put(Key(key_count) + "a", something_compressible)); - ++key_count; - ExternalSstFileInfo external_info; - ASSERT_OK(sst_file_writer.Finish(&external_info)); - IngestExternalFileOptions ingest_opts; - ASSERT_OK(db_->IngestExternalFile(handles_[1], {f}, ingest_opts)); - } - - perform_gets(); - verify_stats(); - - // Make sure we can cache hit after re-open - ReopenWithColumnFamilies({"default", "koko"}, options); - - perform_gets(); - verify_stats(); - - // Make sure we can cache hit even on a full copy of the DB. Using - // StableCacheKeyTestFS, Checkpoint will resort to full copy not hard link. - // (Checkpoint not available in LITE mode to test this.) - auto db_copy_name = dbname_ + "-copy"; - ASSERT_OK(Checkpoint::Create(db_, &checkpoint)); - ASSERT_OK(checkpoint->CreateCheckpoint(db_copy_name)); - delete checkpoint; - - Close(); - Destroy(options); - - // Switch to the DB copy - SaveAndRestore save_dbname(&dbname_, db_copy_name); - ReopenWithColumnFamilies({"default", "koko"}, options); - - perform_gets(); - verify_stats(); - - // And ensure that re-importing + ingesting the same files into a - // different DB uses same cache keys - DestroyAndReopen(options); - - ColumnFamilyHandle* cfh = nullptr; - ASSERT_OK(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - *metadata_ptr_, &cfh)); - ASSERT_NE(cfh, nullptr); - delete cfh; - cfh = nullptr; - delete metadata_ptr_; - metadata_ptr_ = nullptr; - - ASSERT_OK(DestroyDB(export_files_dir, options)); - - ReopenWithColumnFamilies({"default", "yoyo"}, options); - - IngestExternalFileOptions ingest_opts; - ASSERT_OK(db_->IngestExternalFile(handles_[1], {external}, ingest_opts)); - - perform_gets(); - verify_stats(); - - Close(); - Destroy(options); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -class CacheKeyTest : public testing::Test { - public: - CacheKey GetBaseCacheKey() { - CacheKey rv = GetOffsetableCacheKey(0, /*min file_number*/ 1).WithOffset(0); - // Correct for file_number_ == 1 - *reinterpret_cast(&rv) ^= ReverseBits(uint64_t{1}); - return rv; - } - CacheKey GetCacheKey(uint64_t session_counter, uint64_t file_number, - uint64_t offset) { - OffsetableCacheKey offsetable = - GetOffsetableCacheKey(session_counter, file_number); - // * 4 to counteract optimization that strips lower 2 bits in encoding - // the offset in BlockBasedTable::GetCacheKey (which we prefer to include - // in unit tests to maximize functional coverage). - EXPECT_GE(offset * 4, offset); // no overflow - return BlockBasedTable::GetCacheKey(offsetable, - BlockHandle(offset * 4, /*size*/ 5)); - } - - protected: - OffsetableCacheKey GetOffsetableCacheKey(uint64_t session_counter, - uint64_t file_number) { - // Like SemiStructuredUniqueIdGen::GenerateNext - tp_.db_session_id = EncodeSessionId(base_session_upper_, - base_session_lower_ ^ session_counter); - tp_.db_id = std::to_string(db_id_); - tp_.orig_file_number = file_number; - bool is_stable; - std::string cur_session_id = ""; // ignored - uint64_t cur_file_number = 42; // ignored - OffsetableCacheKey rv; - BlockBasedTable::SetupBaseCacheKey(&tp_, cur_session_id, cur_file_number, - &rv, &is_stable); - EXPECT_TRUE(is_stable); - EXPECT_TRUE(!rv.IsEmpty()); - // BEGIN some assertions in relation to SST unique IDs - std::string external_unique_id_str; - EXPECT_OK(GetUniqueIdFromTableProperties(tp_, &external_unique_id_str)); - UniqueId64x2 sst_unique_id = {}; - EXPECT_OK(DecodeUniqueIdBytes(external_unique_id_str, &sst_unique_id)); - ExternalUniqueIdToInternal(&sst_unique_id); - OffsetableCacheKey ock = - OffsetableCacheKey::FromInternalUniqueId(&sst_unique_id); - EXPECT_EQ(rv.WithOffset(0).AsSlice(), ock.WithOffset(0).AsSlice()); - EXPECT_EQ(ock.ToInternalUniqueId(), sst_unique_id); - // END some assertions in relation to SST unique IDs - return rv; - } - - TableProperties tp_; - uint64_t base_session_upper_ = 0; - uint64_t base_session_lower_ = 0; - uint64_t db_id_ = 0; -}; - -TEST_F(CacheKeyTest, DBImplSessionIdStructure) { - // We have to generate our own session IDs for simulation purposes in other - // tests. Here we verify that the DBImpl implementation seems to match - // our construction here, by using lowest XORed-in bits for "session - // counter." - std::string session_id1 = DBImpl::GenerateDbSessionId(/*env*/ nullptr); - std::string session_id2 = DBImpl::GenerateDbSessionId(/*env*/ nullptr); - uint64_t upper1, upper2, lower1, lower2; - ASSERT_OK(DecodeSessionId(session_id1, &upper1, &lower1)); - ASSERT_OK(DecodeSessionId(session_id2, &upper2, &lower2)); - // Because generated in same process - ASSERT_EQ(upper1, upper2); - // Unless we generate > 4 billion session IDs in this process... - ASSERT_EQ(Upper32of64(lower1), Upper32of64(lower2)); - // But they must be different somewhere - ASSERT_NE(Lower32of64(lower1), Lower32of64(lower2)); -} - -namespace { -// Deconstruct cache key, based on knowledge of implementation details. -void DeconstructNonemptyCacheKey(const CacheKey& key, uint64_t* file_num_etc64, - uint64_t* offset_etc64) { - *file_num_etc64 = *reinterpret_cast(key.AsSlice().data()); - *offset_etc64 = *reinterpret_cast(key.AsSlice().data() + 8); - assert(*file_num_etc64 != 0); - if (*offset_etc64 == 0) { - std::swap(*file_num_etc64, *offset_etc64); - } - assert(*offset_etc64 != 0); -} - -// Make a bit mask of 0 to 64 bits -uint64_t MakeMask64(int bits) { - if (bits >= 64) { - return uint64_t{0} - 1; - } else { - return (uint64_t{1} << bits) - 1; - } -} - -// See CacheKeyTest::Encodings -struct CacheKeyDecoder { - // Inputs - uint64_t base_file_num_etc64, base_offset_etc64; - int session_counter_bits, file_number_bits, offset_bits; - - // Derived - uint64_t session_counter_mask, file_number_mask, offset_mask; - - // Outputs - uint64_t decoded_session_counter, decoded_file_num, decoded_offset; - - void SetBaseCacheKey(const CacheKey& base) { - DeconstructNonemptyCacheKey(base, &base_file_num_etc64, &base_offset_etc64); - } - - void SetRanges(int _session_counter_bits, int _file_number_bits, - int _offset_bits) { - session_counter_bits = _session_counter_bits; - session_counter_mask = MakeMask64(session_counter_bits); - file_number_bits = _file_number_bits; - file_number_mask = MakeMask64(file_number_bits); - offset_bits = _offset_bits; - offset_mask = MakeMask64(offset_bits); - } - - void Decode(const CacheKey& key) { - uint64_t file_num_etc64, offset_etc64; - DeconstructNonemptyCacheKey(key, &file_num_etc64, &offset_etc64); - - // First decode session counter - if (offset_bits + session_counter_bits <= 64) { - // fully recoverable from offset_etc64 - decoded_session_counter = - ReverseBits((offset_etc64 ^ base_offset_etc64)) & - session_counter_mask; - } else if (file_number_bits + session_counter_bits <= 64) { - // fully recoverable from file_num_etc64 - decoded_session_counter = DownwardInvolution( - (file_num_etc64 ^ base_file_num_etc64) & session_counter_mask); - } else { - // Need to combine parts from each word. - // Piece1 will contain some correct prefix of the bottom bits of - // session counter. - uint64_t piece1 = - ReverseBits((offset_etc64 ^ base_offset_etc64) & ~offset_mask); - int piece1_bits = 64 - offset_bits; - // Piece2 will contain involuded bits that we can combine with piece1 - // to infer rest of session counter - int piece2_bits = std::min(64 - file_number_bits, 64 - piece1_bits); - ASSERT_LT(piece2_bits, 64); - uint64_t piece2_mask = MakeMask64(piece2_bits); - uint64_t piece2 = (file_num_etc64 ^ base_file_num_etc64) & piece2_mask; - - // Cancel out the part of piece2 that we can infer from piece1 - // (DownwardInvolution distributes over xor) - piece2 ^= DownwardInvolution(piece1) & piece2_mask; - - // Now we need to solve for the unknown original bits in higher - // positions than piece1 provides. We use Gaussian elimination - // because we know that a piece2_bits X piece2_bits submatrix of - // the matrix underlying DownwardInvolution times the vector of - // unknown original bits equals piece2. - // - // Build an augmented row matrix for that submatrix, built column by - // column. - std::array aug_rows{}; - for (int i = 0; i < piece2_bits; ++i) { // over columns - uint64_t col_i = DownwardInvolution(uint64_t{1} << piece1_bits << i); - ASSERT_NE(col_i & 1U, 0); - for (int j = 0; j < piece2_bits; ++j) { // over rows - aug_rows[j] |= (col_i & 1U) << i; - col_i >>= 1; - } - } - // Augment with right hand side - for (int j = 0; j < piece2_bits; ++j) { // over rows - aug_rows[j] |= (piece2 & 1U) << piece2_bits; - piece2 >>= 1; - } - // Run Gaussian elimination - for (int i = 0; i < piece2_bits; ++i) { // over columns - // Find a row that can be used to cancel others - uint64_t canceller = 0; - // Note: Rows 0 through i-1 contain 1s in columns already eliminated - for (int j = i; j < piece2_bits; ++j) { // over rows - if (aug_rows[j] & (uint64_t{1} << i)) { - // Swap into appropriate row - std::swap(aug_rows[i], aug_rows[j]); - // Keep a handy copy for row reductions - canceller = aug_rows[i]; - break; - } - } - ASSERT_NE(canceller, 0); - for (int j = 0; j < piece2_bits; ++j) { // over rows - if (i != j && ((aug_rows[j] >> i) & 1) != 0) { - // Row reduction - aug_rows[j] ^= canceller; - } - } - } - // Extract result - decoded_session_counter = piece1; - for (int j = 0; j < piece2_bits; ++j) { // over rows - ASSERT_EQ(aug_rows[j] & piece2_mask, uint64_t{1} << j); - decoded_session_counter |= aug_rows[j] >> piece2_bits << piece1_bits - << j; - } - } - - decoded_offset = - offset_etc64 ^ base_offset_etc64 ^ ReverseBits(decoded_session_counter); - - decoded_file_num = ReverseBits(file_num_etc64 ^ base_file_num_etc64 ^ - DownwardInvolution(decoded_session_counter)); - } -}; -} // anonymous namespace - -TEST_F(CacheKeyTest, Encodings) { - // This test primarily verifies this claim from cache_key.cc: - // // In fact, if DB ids were not involved, we would be guaranteed unique - // // cache keys for files generated in a single process until total bits for - // // biggest session_id_counter, orig_file_number, and offset_in_file - // // reach 128 bits. - // - // To demonstrate this, CacheKeyDecoder can reconstruct the structured inputs - // to the cache key when provided an output cache key, the unstructured - // inputs, and bounds on the structured inputs. - // - // See OffsetableCacheKey comments in cache_key.cc. - - // We are going to randomly initialize some values that *should* not affect - // result - Random64 r{std::random_device{}()}; - - CacheKeyDecoder decoder; - db_id_ = r.Next(); - base_session_upper_ = r.Next(); - base_session_lower_ = r.Next(); - if (base_session_lower_ == 0) { - base_session_lower_ = 1; - } - - decoder.SetBaseCacheKey(GetBaseCacheKey()); - - // Loop over configurations and test those - for (int session_counter_bits = 0; session_counter_bits <= 64; - ++session_counter_bits) { - for (int file_number_bits = 1; file_number_bits <= 64; ++file_number_bits) { - // 62 bits max because unoptimized offset will be 64 bits in that case - for (int offset_bits = 0; offset_bits <= 62; ++offset_bits) { - if (session_counter_bits + file_number_bits + offset_bits > 128) { - break; - } - - decoder.SetRanges(session_counter_bits, file_number_bits, offset_bits); - - uint64_t session_counter = r.Next() & decoder.session_counter_mask; - uint64_t file_number = r.Next() & decoder.file_number_mask; - if (file_number == 0) { - // Minimum - file_number = 1; - } - uint64_t offset = r.Next() & decoder.offset_mask; - decoder.Decode(GetCacheKey(session_counter, file_number, offset)); - - EXPECT_EQ(decoder.decoded_session_counter, session_counter); - EXPECT_EQ(decoder.decoded_file_num, file_number); - EXPECT_EQ(decoder.decoded_offset, offset); - } - } - } -} - -INSTANTIATE_TEST_CASE_P(DBBlockCacheKeyTest, DBBlockCacheKeyTest, - ::testing::Combine(::testing::Bool(), - ::testing::Bool())); - -class DBBlockCachePinningTest - : public DBTestBase, - public testing::WithParamInterface< - std::tuple> { - public: - DBBlockCachePinningTest() - : DBTestBase("db_block_cache_test", /*env_do_fsync=*/false) {} - - void SetUp() override { - partition_index_and_filters_ = std::get<0>(GetParam()); - top_level_index_pinning_ = std::get<1>(GetParam()); - partition_pinning_ = std::get<2>(GetParam()); - unpartitioned_pinning_ = std::get<3>(GetParam()); - } - - bool partition_index_and_filters_; - PinningTier top_level_index_pinning_; - PinningTier partition_pinning_; - PinningTier unpartitioned_pinning_; -}; - -TEST_P(DBBlockCachePinningTest, TwoLevelDB) { - // Creates one file in L0 and one file in L1. Both files have enough data that - // their index and filter blocks are partitioned. The L1 file will also have - // a compression dictionary (those are trained only during compaction), which - // must be unpartitioned. - const int kKeySize = 32; - const int kBlockSize = 128; - const int kNumBlocksPerFile = 128; - const int kNumKeysPerFile = kBlockSize * kNumBlocksPerFile / kKeySize; - - Options options = CurrentOptions(); - // `kNoCompression` makes the unit test more portable. But it relies on the - // current behavior of persisting/accessing dictionary even when there's no - // (de)compression happening, which seems fairly likely to change over time. - options.compression = kNoCompression; - options.compression_opts.max_dict_bytes = 4 << 10; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.block_cache = NewLRUCache(1 << 20 /* capacity */); - table_options.block_size = kBlockSize; - table_options.metadata_block_size = kBlockSize; - table_options.cache_index_and_filter_blocks = true; - table_options.metadata_cache_options.top_level_index_pinning = - top_level_index_pinning_; - table_options.metadata_cache_options.partition_pinning = partition_pinning_; - table_options.metadata_cache_options.unpartitioned_pinning = - unpartitioned_pinning_; - table_options.filter_policy.reset( - NewBloomFilterPolicy(10 /* bits_per_key */)); - if (partition_index_and_filters_) { - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - table_options.partition_filters = true; - } - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - - Random rnd(301); - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kKeySize))); - } - ASSERT_OK(Flush()); - if (i == 0) { - // Prevent trivial move so file will be rewritten with dictionary and - // reopened with L1's pinning settings. - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - } - - // Clear all unpinned blocks so unpinned blocks will show up as cache misses - // when reading a key from a file. - table_options.block_cache->EraseUnRefEntries(); - - // Get base cache values - uint64_t filter_misses = TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS); - uint64_t index_misses = TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS); - uint64_t compression_dict_misses = - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS); - - // Read a key from the L0 file - Get(Key(kNumKeysPerFile)); - uint64_t expected_filter_misses = filter_misses; - uint64_t expected_index_misses = index_misses; - uint64_t expected_compression_dict_misses = compression_dict_misses; - if (partition_index_and_filters_) { - if (top_level_index_pinning_ == PinningTier::kNone) { - ++expected_filter_misses; - ++expected_index_misses; - } - if (partition_pinning_ == PinningTier::kNone) { - ++expected_filter_misses; - ++expected_index_misses; - } - } else { - if (unpartitioned_pinning_ == PinningTier::kNone) { - ++expected_filter_misses; - ++expected_index_misses; - } - } - if (unpartitioned_pinning_ == PinningTier::kNone) { - ++expected_compression_dict_misses; - } - ASSERT_EQ(expected_filter_misses, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(expected_index_misses, - TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(expected_compression_dict_misses, - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS)); - - // Clear all unpinned blocks so unpinned blocks will show up as cache misses - // when reading a key from a file. - table_options.block_cache->EraseUnRefEntries(); - - // Read a key from the L1 file - Get(Key(0)); - if (partition_index_and_filters_) { - if (top_level_index_pinning_ == PinningTier::kNone || - top_level_index_pinning_ == PinningTier::kFlushedAndSimilar) { - ++expected_filter_misses; - ++expected_index_misses; - } - if (partition_pinning_ == PinningTier::kNone || - partition_pinning_ == PinningTier::kFlushedAndSimilar) { - ++expected_filter_misses; - ++expected_index_misses; - } - } else { - if (unpartitioned_pinning_ == PinningTier::kNone || - unpartitioned_pinning_ == PinningTier::kFlushedAndSimilar) { - ++expected_filter_misses; - ++expected_index_misses; - } - } - if (unpartitioned_pinning_ == PinningTier::kNone || - unpartitioned_pinning_ == PinningTier::kFlushedAndSimilar) { - ++expected_compression_dict_misses; - } - ASSERT_EQ(expected_filter_misses, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(expected_index_misses, - TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); - ASSERT_EQ(expected_compression_dict_misses, - TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS)); -} - -INSTANTIATE_TEST_CASE_P( - DBBlockCachePinningTest, DBBlockCachePinningTest, - ::testing::Combine( - ::testing::Bool(), - ::testing::Values(PinningTier::kNone, PinningTier::kFlushedAndSimilar, - PinningTier::kAll), - ::testing::Values(PinningTier::kNone, PinningTier::kFlushedAndSimilar, - PinningTier::kAll), - ::testing::Values(PinningTier::kNone, PinningTier::kFlushedAndSimilar, - PinningTier::kAll))); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_bloom_filter_test.cc b/db/db_bloom_filter_test.cc deleted file mode 100644 index 352343c16..000000000 --- a/db/db_bloom_filter_test.cc +++ /dev/null @@ -1,3473 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include -#include -#include -#include - -#include "cache/cache_entry_roles.h" -#include "cache/cache_reservation_manager.h" -#include "db/db_test_util.h" -#include "options/options_helper.h" -#include "port/stack_trace.h" -#include "rocksdb/advanced_options.h" -#include "rocksdb/convenience.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table.h" -#include "table/block_based/block_based_table_reader.h" -#include "table/block_based/filter_policy_internal.h" -#include "table/format.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -std::shared_ptr Create(double bits_per_key, - const std::string& name) { - return BloomLikeFilterPolicy::Create(name, bits_per_key); -} -const std::string kLegacyBloom = test::LegacyBloomFilterPolicy::kClassName(); -const std::string kFastLocalBloom = - test::FastLocalBloomFilterPolicy::kClassName(); -const std::string kStandard128Ribbon = - test::Standard128RibbonFilterPolicy::kClassName(); -const std::string kAutoBloom = BloomFilterPolicy::kClassName(); -const std::string kAutoRibbon = RibbonFilterPolicy::kClassName(); -} // anonymous namespace - -// DB tests related to bloom filter. - -class DBBloomFilterTest : public DBTestBase { - public: - DBBloomFilterTest() - : DBTestBase("db_bloom_filter_test", /*env_do_fsync=*/true) {} -}; - -class DBBloomFilterTestWithParam - : public DBTestBase, - public testing::WithParamInterface< - std::tuple> { - // public testing::WithParamInterface { - protected: - std::string bfp_impl_; - bool partition_filters_; - uint32_t format_version_; - - public: - DBBloomFilterTestWithParam() - : DBTestBase("db_bloom_filter_tests", /*env_do_fsync=*/true) {} - - ~DBBloomFilterTestWithParam() override {} - - void SetUp() override { - bfp_impl_ = std::get<0>(GetParam()); - partition_filters_ = std::get<1>(GetParam()); - format_version_ = std::get<2>(GetParam()); - } -}; - -class DBBloomFilterTestDefFormatVersion : public DBBloomFilterTestWithParam {}; - -class SliceTransformLimitedDomainGeneric : public SliceTransform { - const char* Name() const override { - return "SliceTransformLimitedDomainGeneric"; - } - - Slice Transform(const Slice& src) const override { - return Slice(src.data(), 5); - } - - bool InDomain(const Slice& src) const override { - // prefix will be x???? - return src.size() >= 5; - } - - bool InRange(const Slice& dst) const override { - // prefix will be x???? - return dst.size() == 5; - } -}; - -// KeyMayExist can lead to a few false positives, but not false negatives. -// To make test deterministic, use a much larger number of bits per key-20 than -// bits in the key, so that false positives are eliminated -TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) { - do { - ReadOptions ropts; - std::string value; - anon::OptionsOverride options_override; - options_override.filter_policy = Create(20, bfp_impl_); - options_override.partition_filters = partition_filters_; - options_override.metadata_block_size = 32; - options_override.full_block_cache = true; - Options options = CurrentOptions(options_override); - if (partition_filters_) { - auto* table_options = - options.table_factory->GetOptions(); - if (table_options != nullptr && - table_options->index_type != - BlockBasedTableOptions::kTwoLevelIndexSearch) { - // In the current implementation partitioned filters depend on - // partitioned indexes - continue; - } - } - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value)); - - ASSERT_OK(Put(1, "a", "b")); - bool value_found = false; - ASSERT_TRUE( - db_->KeyMayExist(ropts, handles_[1], "a", &value, &value_found)); - ASSERT_TRUE(value_found); - ASSERT_EQ("b", value); - - ASSERT_OK(Flush(1)); - value.clear(); - - uint64_t numopen = TestGetTickerCount(options, NO_FILE_OPENS); - uint64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - ASSERT_TRUE( - db_->KeyMayExist(ropts, handles_[1], "a", &value, &value_found)); - ASSERT_TRUE(!value_found); - // assert that no new files were opened and no new blocks were - // read into block cache. - ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS)); - ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - ASSERT_OK(Delete(1, "a")); - - numopen = TestGetTickerCount(options, NO_FILE_OPENS); - cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value)); - ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS)); - ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1], - true /* disallow trivial move */)); - - numopen = TestGetTickerCount(options, NO_FILE_OPENS); - cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value)); - ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS)); - ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - ASSERT_OK(Delete(1, "c")); - - numopen = TestGetTickerCount(options, NO_FILE_OPENS); - cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "c", &value)); - ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS)); - ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - // KeyMayExist function only checks data in block caches, which is not used - // by plain table format. - } while ( - ChangeOptions(kSkipPlainTable | kSkipHashIndex | kSkipFIFOCompaction)); -} - -TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) { - for (bool partition_filters : {true, false}) { - Options options = last_options_; - options.prefix_extractor = - std::make_shared(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - get_perf_context()->EnablePerLevelPerfContext(); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10)); - if (partition_filters) { - bbto.partition_filters = true; - bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } - bbto.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - WriteOptions wo; - ReadOptions ro; - FlushOptions fo; - fo.wait = true; - std::string value; - - ASSERT_OK(dbfull()->Put(wo, "barbarbar", "foo")); - ASSERT_OK(dbfull()->Put(wo, "barbarbar2", "foo2")); - ASSERT_OK(dbfull()->Put(wo, "foofoofoo", "bar")); - - ASSERT_OK(dbfull()->Flush(fo)); - - ASSERT_EQ("foo", Get("barbarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - ASSERT_EQ( - 0, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - ASSERT_EQ("foo2", Get("barbarbar2")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - ASSERT_EQ( - 0, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - ASSERT_EQ("NOT_FOUND", Get("barbarbar3")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - ASSERT_EQ( - 0, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - ASSERT_EQ("NOT_FOUND", Get("barfoofoo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ( - 1, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2); - ASSERT_EQ( - 2, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - ro.total_order_seek = true; - // NOTE: total_order_seek no longer affects Get() - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3); - ASSERT_EQ( - 3, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - // No bloom on extractor changed - ASSERT_OK(db_->SetOptions({{"prefix_extractor", "capped:10"}})); - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3); - ASSERT_EQ( - 3, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - // No bloom on extractor changed, after re-open - options.prefix_extractor.reset(NewCappedPrefixTransform(10)); - Reopen(options); - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3); - ASSERT_EQ( - 3, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - get_perf_context()->Reset(); - } -} - -TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) { - for (bool partition_filters : {true, false}) { - Options options = last_options_; - options.prefix_extractor.reset(NewFixedPrefixTransform(8)); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - get_perf_context()->EnablePerLevelPerfContext(); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10)); - if (partition_filters) { - bbto.partition_filters = true; - bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } - bbto.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - WriteOptions wo; - ReadOptions ro; - FlushOptions fo; - fo.wait = true; - std::string value; - - ASSERT_OK(dbfull()->Put(wo, "barbarbar", "foo")); - ASSERT_OK(dbfull()->Put(wo, "barbarbar2", "foo2")); - ASSERT_OK(dbfull()->Put(wo, "foofoofoo", "bar")); - - ASSERT_OK(dbfull()->Flush(fo)); - - ASSERT_EQ("foo", Get("barbarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - ASSERT_EQ("foo2", Get("barbarbar2")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - ASSERT_EQ("NOT_FOUND", Get("barbarbar3")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - - ASSERT_EQ("NOT_FOUND", Get("barfoofoo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2); - - ro.total_order_seek = true; - // NOTE: total_order_seek no longer affects Get() - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3); - ASSERT_EQ( - 3, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - // No bloom on extractor changed - ASSERT_OK(db_->SetOptions({{"prefix_extractor", "capped:10"}})); - ASSERT_EQ("NOT_FOUND", Get("foobarbar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3); - ASSERT_EQ( - 3, - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - - get_perf_context()->Reset(); - } -} - -TEST_F(DBBloomFilterTest, WholeKeyFilterProp) { - for (bool partition_filters : {true, false}) { - Options options = last_options_; - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - get_perf_context()->EnablePerLevelPerfContext(); - - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10)); - bbto.whole_key_filtering = false; - if (partition_filters) { - bbto.partition_filters = true; - bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - WriteOptions wo; - ReadOptions ro; - FlushOptions fo; - fo.wait = true; - std::string value; - - ASSERT_OK(dbfull()->Put(wo, "foobar", "foo")); - // Needs insert some keys to make sure files are not filtered out by key - // ranges. - ASSERT_OK(dbfull()->Put(wo, "aaa", "")); - ASSERT_OK(dbfull()->Put(wo, "zzz", "")); - ASSERT_OK(dbfull()->Flush(fo)); - - Reopen(options); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - - // Reopen with whole key filtering enabled and prefix extractor - // NULL. Bloom filter should be off for both of whole key and - // prefix bloom. - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.prefix_extractor.reset(); - Reopen(options); - - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - // Write DB with only full key filtering. - ASSERT_OK(dbfull()->Put(wo, "foobar", "foo")); - // Needs insert some keys to make sure files are not filtered out by key - // ranges. - ASSERT_OK(dbfull()->Put(wo, "aaa", "")); - ASSERT_OK(dbfull()->Put(wo, "zzz", "")); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // Reopen with both of whole key off and prefix extractor enabled. - // Still no bloom filter should be used. - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - bbto.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - - // Try to create a DB with mixed files: - ASSERT_OK(dbfull()->Put(wo, "foobar", "foo")); - // Needs insert some keys to make sure files are not filtered out by key - // ranges. - ASSERT_OK(dbfull()->Put(wo, "aaa", "")); - ASSERT_OK(dbfull()->Put(wo, "zzz", "")); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - options.prefix_extractor.reset(); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - - // Try to create a DB with mixed files. - ASSERT_OK(dbfull()->Put(wo, "barfoo", "bar")); - // In this case needs insert some keys to make sure files are - // not filtered out by key ranges. - ASSERT_OK(dbfull()->Put(wo, "aaa", "")); - ASSERT_OK(dbfull()->Put(wo, "zzz", "")); - ASSERT_OK(Flush()); - - // Now we have two files: - // File 1: An older file with prefix bloom. - // File 2: A newer file with whole bloom filter. - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4); - ASSERT_EQ("bar", Get("barfoo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4); - - // Reopen with the same setting: only whole key is used - Reopen(options); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 5); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 6); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7); - ASSERT_EQ("bar", Get("barfoo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7); - - // Restart with both filters are allowed - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7); - // File 1 will has it filtered out. - // File 2 will not, as prefix `foo` exists in the file. - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 8); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 10); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11); - ASSERT_EQ("bar", Get("barfoo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11); - - // Restart with only prefix bloom is allowed. - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - bbto.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11); - ASSERT_EQ("NOT_FOUND", Get("bar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12); - ASSERT_EQ("foo", Get("foobar")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12); - ASSERT_EQ("bar", Get("barfoo")); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12); - uint64_t bloom_filter_useful_all_levels = 0; - for (auto& kv : (*(get_perf_context()->level_to_perf_context))) { - if (kv.second.bloom_filter_useful > 0) { - bloom_filter_useful_all_levels += kv.second.bloom_filter_useful; - } - } - ASSERT_EQ(12, bloom_filter_useful_all_levels); - get_perf_context()->Reset(); - } -} - -TEST_P(DBBloomFilterTestWithParam, BloomFilter) { - do { - Options options = CurrentOptions(); - env_->count_random_reads_ = true; - options.env = env_; - // ChangeCompactOptions() only changes compaction style, which does not - // trigger reset of table_factory - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - table_options.filter_policy = Create(10, bfp_impl_); - table_options.partition_filters = partition_filters_; - if (partition_filters_) { - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } - table_options.format_version = format_version_; - if (format_version_ >= 4) { - // value delta encoding challenged more with index interval > 1 - table_options.index_block_restart_interval = 8; - } - table_options.metadata_block_size = 32; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - CreateAndReopenWithCF({"pikachu"}, options); - - // Populate multiple layers - const int N = 10000; - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - Compact(1, "a", "z"); - for (int i = 0; i < N; i += 100) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - ASSERT_OK(Flush(1)); - - // Prevent auto compactions triggered by seeks - env_->delay_sstable_sync_.store(true, std::memory_order_release); - - // Lookup present keys. Should rarely read from small sstable. - env_->random_read_counter_.Reset(); - for (int i = 0; i < N; i++) { - ASSERT_EQ(Key(i), Get(1, Key(i))); - } - int reads = env_->random_read_counter_.Read(); - fprintf(stderr, "%d present => %d reads\n", N, reads); - ASSERT_GE(reads, N); - if (partition_filters_) { - // Without block cache, we read an extra partition filter per each - // level*read and a partition index per each read - ASSERT_LE(reads, 4 * N + 2 * N / 100); - } else { - ASSERT_LE(reads, N + 2 * N / 100); - } - - // Lookup present keys. Should rarely read from either sstable. - env_->random_read_counter_.Reset(); - for (int i = 0; i < N; i++) { - ASSERT_EQ("NOT_FOUND", Get(1, Key(i) + ".missing")); - } - reads = env_->random_read_counter_.Read(); - fprintf(stderr, "%d missing => %d reads\n", N, reads); - if (partition_filters_) { - // With partitioned filter we read one extra filter per level per each - // missed read. - ASSERT_LE(reads, 2 * N + 3 * N / 100); - } else { - ASSERT_LE(reads, 3 * N / 100); - } - - // Sanity check some table properties - std::map props; - ASSERT_TRUE(db_->GetMapProperty( - handles_[1], DB::Properties::kAggregatedTableProperties, &props)); - uint64_t nkeys = N + N / 100; - uint64_t filter_size = ParseUint64(props["filter_size"]); - EXPECT_LE(filter_size, - (partition_filters_ ? 12 : 11) * nkeys / /*bits / byte*/ 8); - if (bfp_impl_ == kAutoRibbon) { - // Sometimes using Ribbon filter which is more space-efficient - EXPECT_GE(filter_size, 7 * nkeys / /*bits / byte*/ 8); - } else { - // Always Bloom - EXPECT_GE(filter_size, 10 * nkeys / /*bits / byte*/ 8); - } - - uint64_t num_filter_entries = ParseUint64(props["num_filter_entries"]); - EXPECT_EQ(num_filter_entries, nkeys); - - env_->delay_sstable_sync_.store(false, std::memory_order_release); - Close(); - } while (ChangeCompactOptions()); -} - -namespace { - -class AlwaysTrueBitsBuilder : public FilterBitsBuilder { - public: - void AddKey(const Slice&) override {} - size_t EstimateEntriesAdded() override { return 0U; } - Slice Finish(std::unique_ptr* /* buf */) override { - // Interpreted as "always true" filter (0 probes over 1 byte of - // payload, 5 bytes metadata) - return Slice("\0\0\0\0\0\0", 6); - } - using FilterBitsBuilder::Finish; - size_t ApproximateNumEntries(size_t) override { return SIZE_MAX; } -}; - -class AlwaysTrueFilterPolicy : public ReadOnlyBuiltinFilterPolicy { - public: - explicit AlwaysTrueFilterPolicy(bool skip) : skip_(skip) {} - - FilterBitsBuilder* GetBuilderWithContext( - const FilterBuildingContext&) const override { - if (skip_) { - return nullptr; - } else { - return new AlwaysTrueBitsBuilder(); - } - } - - private: - bool skip_; -}; - -} // anonymous namespace - -TEST_P(DBBloomFilterTestWithParam, SkipFilterOnEssentiallyZeroBpk) { - constexpr int maxKey = 10; - auto PutFn = [&]() { - int i; - // Put - for (i = 0; i < maxKey; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - Flush(); - }; - auto GetFn = [&]() { - int i; - // Get OK - for (i = 0; i < maxKey; i++) { - ASSERT_EQ(Key(i), Get(Key(i))); - } - // Get NotFound - for (; i < maxKey * 2; i++) { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - }; - auto PutAndGetFn = [&]() { - PutFn(); - GetFn(); - }; - std::map props; - const auto& kAggTableProps = DB::Properties::kAggregatedTableProperties; - - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.partition_filters = partition_filters_; - if (partition_filters_) { - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } - table_options.format_version = format_version_; - - // Test 1: bits per key < 0.5 means skip filters -> no filter - // constructed or read. - table_options.filter_policy = Create(0.4, bfp_impl_); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - PutAndGetFn(); - - // Verify no filter access nor contruction - EXPECT_EQ(TestGetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE), 0); - EXPECT_EQ(TestGetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), 0); - - props.clear(); - ASSERT_TRUE(db_->GetMapProperty(kAggTableProps, &props)); - EXPECT_EQ(props["filter_size"], "0"); - - // Test 2: use custom API to skip filters -> no filter constructed - // or read. - table_options.filter_policy.reset( - new AlwaysTrueFilterPolicy(/* skip */ true)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - PutAndGetFn(); - - // Verify no filter access nor construction - EXPECT_EQ(TestGetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE), 0); - EXPECT_EQ(TestGetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), 0); - - props.clear(); - ASSERT_TRUE(db_->GetMapProperty(kAggTableProps, &props)); - EXPECT_EQ(props["filter_size"], "0"); - - // Control test: using an actual filter with 100% FP rate -> the filter - // is constructed and checked on read. - table_options.filter_policy.reset( - new AlwaysTrueFilterPolicy(/* skip */ false)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - PutAndGetFn(); - - // Verify filter is accessed (and constructed) - EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE), - maxKey * 2); - EXPECT_EQ( - TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), - maxKey); - props.clear(); - ASSERT_TRUE(db_->GetMapProperty(kAggTableProps, &props)); - EXPECT_NE(props["filter_size"], "0"); - - // Test 3 (options test): Able to read existing filters with longstanding - // generated options file entry `filter_policy=rocksdb.BuiltinBloomFilter` - ASSERT_OK(FilterPolicy::CreateFromString(ConfigOptions(), - "rocksdb.BuiltinBloomFilter", - &table_options.filter_policy)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - GetFn(); - - // Verify filter is accessed - EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE), - maxKey * 2); - EXPECT_EQ( - TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), - maxKey); - - // But new filters are not generated (configuration details unknown) - DestroyAndReopen(options); - PutAndGetFn(); - - // Verify no filter access nor construction - EXPECT_EQ(TestGetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE), 0); - EXPECT_EQ(TestGetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), 0); - - props.clear(); - ASSERT_TRUE(db_->GetMapProperty(kAggTableProps, &props)); - EXPECT_EQ(props["filter_size"], "0"); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -INSTANTIATE_TEST_CASE_P( - FormatDef, DBBloomFilterTestDefFormatVersion, - ::testing::Values( - std::make_tuple(kAutoBloom, true, test::kDefaultFormatVersion), - std::make_tuple(kAutoBloom, false, test::kDefaultFormatVersion), - std::make_tuple(kAutoRibbon, false, test::kDefaultFormatVersion))); - -INSTANTIATE_TEST_CASE_P( - FormatDef, DBBloomFilterTestWithParam, - ::testing::Values( - std::make_tuple(kAutoBloom, true, test::kDefaultFormatVersion), - std::make_tuple(kAutoBloom, false, test::kDefaultFormatVersion), - std::make_tuple(kAutoRibbon, false, test::kDefaultFormatVersion))); - -INSTANTIATE_TEST_CASE_P( - FormatLatest, DBBloomFilterTestWithParam, - ::testing::Values(std::make_tuple(kAutoBloom, true, kLatestFormatVersion), - std::make_tuple(kAutoBloom, false, kLatestFormatVersion), - std::make_tuple(kAutoRibbon, false, - kLatestFormatVersion))); -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_F(DBBloomFilterTest, BloomFilterRate) { - while (ChangeFilterOptions()) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - get_perf_context()->EnablePerLevelPerfContext(); - CreateAndReopenWithCF({"pikachu"}, options); - - const int maxKey = 10000; - for (int i = 0; i < maxKey; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - // Add a large key to make the file contain wide range - ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555))); - Flush(1); - - // Check if they can be found - for (int i = 0; i < maxKey; i++) { - ASSERT_EQ(Key(i), Get(1, Key(i))); - } - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - - // Check if filter is useful - for (int i = 0; i < maxKey; i++) { - ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333))); - } - ASSERT_GE(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey * 0.98); - ASSERT_GE( - (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful, - maxKey * 0.98); - get_perf_context()->Reset(); - } -} - -namespace { -struct CompatibilityConfig { - std::shared_ptr policy; - bool partitioned; - uint32_t format_version; - - void SetInTableOptions(BlockBasedTableOptions* table_options) { - table_options->filter_policy = policy; - table_options->partition_filters = partitioned; - if (partitioned) { - table_options->index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } else { - table_options->index_type = - BlockBasedTableOptions::IndexType::kBinarySearch; - } - table_options->format_version = format_version; - } -}; -// High bits per key -> almost no FPs -std::shared_ptr kCompatibilityBloomPolicy{ - NewBloomFilterPolicy(20)}; -// bloom_before_level=-1 -> always use Ribbon -std::shared_ptr kCompatibilityRibbonPolicy{ - NewRibbonFilterPolicy(20, -1)}; - -std::vector kCompatibilityConfigs = { - {kCompatibilityBloomPolicy, false, BlockBasedTableOptions().format_version}, - {kCompatibilityBloomPolicy, true, BlockBasedTableOptions().format_version}, - {kCompatibilityBloomPolicy, false, /* legacy Bloom */ 4U}, - {kCompatibilityRibbonPolicy, false, - BlockBasedTableOptions().format_version}, - {kCompatibilityRibbonPolicy, true, BlockBasedTableOptions().format_version}, -}; -} // anonymous namespace - -TEST_F(DBBloomFilterTest, BloomFilterCompatibility) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.level0_file_num_compaction_trigger = - static_cast(kCompatibilityConfigs.size()) + 1; - options.max_open_files = -1; - - Close(); - - // Create one file for each kind of filter. Each file covers a distinct key - // range. - for (size_t i = 0; i < kCompatibilityConfigs.size(); ++i) { - BlockBasedTableOptions table_options; - kCompatibilityConfigs[i].SetInTableOptions(&table_options); - ASSERT_TRUE(table_options.filter_policy != nullptr); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - - std::string prefix = std::to_string(i) + "_"; - ASSERT_OK(Put(prefix + "A", "val")); - ASSERT_OK(Put(prefix + "Z", "val")); - ASSERT_OK(Flush()); - } - - // Test filter is used between each pair of {reader,writer} configurations, - // because any built-in FilterPolicy should be able to read filters from any - // other built-in FilterPolicy - for (size_t i = 0; i < kCompatibilityConfigs.size(); ++i) { - BlockBasedTableOptions table_options; - kCompatibilityConfigs[i].SetInTableOptions(&table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - for (size_t j = 0; j < kCompatibilityConfigs.size(); ++j) { - std::string prefix = std::to_string(j) + "_"; - ASSERT_EQ("val", Get(prefix + "A")); // Filter positive - ASSERT_EQ("val", Get(prefix + "Z")); // Filter positive - // Filter negative, with high probability - ASSERT_EQ("NOT_FOUND", Get(prefix + "Q")); - EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE), - 2); - EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL), 1); - } - } -} - -// To align with the type of hash entry being reserved in implementation. -using FilterConstructionReserveMemoryHash = uint64_t; - -class ChargeFilterConstructionTestWithParam - : public DBTestBase, - public testing::WithParamInterface> { - public: - ChargeFilterConstructionTestWithParam() - : DBTestBase("db_bloom_filter_tests", - /*env_do_fsync=*/true), - num_key_(0), - charge_filter_construction_(std::get<0>(GetParam())), - policy_(std::get<1>(GetParam())), - partition_filters_(std::get<2>(GetParam())), - detect_filter_construct_corruption_(std::get<3>(GetParam())) { - if (charge_filter_construction_ == - CacheEntryRoleOptions::Decision::kDisabled || - policy_ == kLegacyBloom) { - // For these cases, we only interested in whether filter construction - // cache charging happens instead of its accuracy. Therefore we don't - // need many keys. - num_key_ = 5; - } else if (partition_filters_) { - // For PartitionFilter case, since we set - // table_options.metadata_block_size big enough such that each partition - // trigger at least 1 dummy entry reservation each for hash entries and - // final filter, we need a large number of keys to ensure we have at least - // two partitions. - num_key_ = 18 * - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize() / - sizeof(FilterConstructionReserveMemoryHash); - } else if (policy_ == kFastLocalBloom) { - // For Bloom Filter + FullFilter case, since we design the num_key_ to - // make hash entry cache charging be a multiple of dummy entries, the - // correct behavior of charging final filter on top of it will trigger at - // least another dummy entry insertion. Therefore we can assert that - // behavior and we don't need a large number of keys to verify we - // indeed charge the final filter for in cache, even though final - // filter is a lot smaller than hash entries. - num_key_ = 1 * - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize() / - sizeof(FilterConstructionReserveMemoryHash); - } else { - // For Ribbon Filter + FullFilter case, we need a large enough number of - // keys so that charging final filter after releasing the hash entries - // reservation will trigger at least another dummy entry (or equivalently - // to saying, causing another peak in cache charging) as banding - // reservation might not be a multiple of dummy entry. - num_key_ = 12 * - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize() / - sizeof(FilterConstructionReserveMemoryHash); - } - } - - BlockBasedTableOptions GetBlockBasedTableOptions() { - BlockBasedTableOptions table_options; - - // We set cache capacity big enough to prevent cache full for convenience in - // calculation. - constexpr std::size_t kCacheCapacity = 100 * 1024 * 1024; - - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kFilterConstruction, - {/*.charged = */ charge_filter_construction_}}); - table_options.filter_policy = Create(10, policy_); - table_options.partition_filters = partition_filters_; - if (table_options.partition_filters) { - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - // We set table_options.metadata_block_size big enough so that each - // partition trigger at least 1 dummy entry insertion each for hash - // entries and final filter. - table_options.metadata_block_size = 409000; - } - table_options.detect_filter_construct_corruption = - detect_filter_construct_corruption_; - - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; // 2^0 shard - lo.strict_capacity_limit = true; - cache_ = std::make_shared< - TargetCacheChargeTrackingCache>( - (NewLRUCache(lo))); - table_options.block_cache = cache_; - - return table_options; - } - - std::size_t GetNumKey() { return num_key_; } - - CacheEntryRoleOptions::Decision ChargeFilterConstructMemory() { - return charge_filter_construction_; - } - - std::string GetFilterPolicy() { return policy_; } - - bool PartitionFilters() { return partition_filters_; } - - std::shared_ptr< - TargetCacheChargeTrackingCache> - GetCache() { - return cache_; - } - - private: - std::size_t num_key_; - CacheEntryRoleOptions::Decision charge_filter_construction_; - std::string policy_; - bool partition_filters_; - std::shared_ptr< - TargetCacheChargeTrackingCache> - cache_; - bool detect_filter_construct_corruption_; -}; - -INSTANTIATE_TEST_CASE_P( - ChargeFilterConstructionTestWithParam, - ChargeFilterConstructionTestWithParam, - ::testing::Values( - std::make_tuple(CacheEntryRoleOptions::Decision::kDisabled, - kFastLocalBloom, false, false), - - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kFastLocalBloom, false, false), - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kFastLocalBloom, false, true), - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kFastLocalBloom, true, false), - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kFastLocalBloom, true, true), - - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kStandard128Ribbon, false, false), - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kStandard128Ribbon, false, true), - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kStandard128Ribbon, true, false), - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, - kStandard128Ribbon, true, true), - - std::make_tuple(CacheEntryRoleOptions::Decision::kEnabled, kLegacyBloom, - false, false))); - -// TODO: Speed up this test, and reduce disk space usage (~700MB) -// The current test inserts many keys (on the scale of dummy entry size) -// in order to make small memory user (e.g, final filter, partitioned hash -// entries/filter/banding) , which is proportional to the number of -// keys, big enough so that its cache charging triggers dummy entry insertion -// and becomes observable in the test. -// -// However, inserting that many keys slows down this test and leaves future -// developers an opportunity to speed it up. -// -// Possible approaches & challenges: -// 1. Use sync point during cache charging of filter construction -// -// Benefit: It does not rely on triggering dummy entry insertion -// but the sync point to verify small memory user is charged correctly. -// -// Challenge: this approach is intrusive. -// -// 2. Make dummy entry size configurable and set it small in the test -// -// Benefit: It increases the precision of cache charging and therefore -// small memory usage can still trigger insertion of dummy entry. -// -// Challenge: change CacheReservationManager related APIs and a hack -// might be needed to control the size of dummmy entry of -// CacheReservationManager used in filter construction for testing -// since CacheReservationManager is not exposed at the high level. -// -TEST_P(ChargeFilterConstructionTestWithParam, Basic) { - Options options = CurrentOptions(); - // We set write_buffer_size big enough so that in the case where there is - // filter construction cache charging, flush won't be triggered before we - // manually trigger it for clean testing - options.write_buffer_size = 640 << 20; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - std::shared_ptr< - TargetCacheChargeTrackingCache> - cache = GetCache(); - options.create_if_missing = true; - // Disable auto compaction to prevent its unexpected side effect - // to the number of keys per partition designed by us in the test - options.disable_auto_compactions = true; - DestroyAndReopen(options); - int num_key = static_cast(GetNumKey()); - for (int i = 0; i < num_key; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - - ASSERT_EQ(cache->GetChargedCacheIncrementSum(), 0) - << "Flush was triggered too early in the test case with filter " - "construction cache charging - please make sure no flush triggered " - "during the key insertions above"; - - ASSERT_OK(Flush()); - - bool charge_filter_construction = (ChargeFilterConstructMemory() == - CacheEntryRoleOptions::Decision::kEnabled); - std::string policy = GetFilterPolicy(); - bool partition_filters = PartitionFilters(); - bool detect_filter_construct_corruption = - table_options.detect_filter_construct_corruption; - - std::deque filter_construction_cache_res_peaks = - cache->GetChargedCachePeaks(); - std::size_t filter_construction_cache_res_increments_sum = - cache->GetChargedCacheIncrementSum(); - - if (!charge_filter_construction) { - EXPECT_EQ(filter_construction_cache_res_peaks.size(), 0); - return; - } - - if (policy == kLegacyBloom) { - EXPECT_EQ(filter_construction_cache_res_peaks.size(), 0) - << "There shouldn't be filter construction cache charging as this " - "feature does not support kLegacyBloom"; - return; - } - - const std::size_t kDummyEntrySize = CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize(); - - const std::size_t predicted_hash_entries_cache_res = - num_key * sizeof(FilterConstructionReserveMemoryHash); - ASSERT_EQ(predicted_hash_entries_cache_res % kDummyEntrySize, 0) - << "It's by this test's design that predicted_hash_entries_cache_res is " - "a multipe of dummy entry"; - - const std::size_t predicted_hash_entries_cache_res_dummy_entry_num = - predicted_hash_entries_cache_res / kDummyEntrySize; - const std::size_t predicted_final_filter_cache_res = - static_cast( - std::ceil(1.0 * predicted_hash_entries_cache_res_dummy_entry_num / 6 * - (policy == kStandard128Ribbon ? 0.7 : 1))) * - kDummyEntrySize; - const std::size_t predicted_banding_cache_res = - static_cast( - std::ceil(predicted_hash_entries_cache_res_dummy_entry_num * 2.5)) * - kDummyEntrySize; - - if (policy == kFastLocalBloom) { - /* kFastLocalBloom + FullFilter - * p0 - * / \ - * b / \ - * / \ - * / \ - * 0/ \ - * hash entries = b - 0, final filter = p0 - b - * p0 = hash entries + final filter - * - * The test is designed in a way such that the reservation for b is a - * multiple of dummy entries so that reservation for (p0 - b) - * will trigger at least another dummy entry insertion. - * - * kFastLocalBloom + FullFilter + - * detect_filter_construct_corruption - * The peak p0 stays the same as - * (kFastLocalBloom + FullFilter) but just lasts - * longer since we release hash entries reservation later. - * - * kFastLocalBloom + PartitionedFilter - * p1 - * / \ - * p0 b'/ \ - * / \ / \ - * b / \ / \ - * / \ / \ - * / a \ - * 0/ \ - * partitioned hash entries1 = b - 0, partitioned hash entries1 = b' - a - * parittioned final filter1 = p0 - b, parittioned final filter2 = p1 - b' - * - * (increment p0 - 0) + (increment p1 - a) - * = partitioned hash entries1 + partitioned hash entries2 - * + parittioned final filter1 + parittioned final filter2 - * = hash entries + final filter - * - * kFastLocalBloom + PartitionedFilter + - * detect_filter_construct_corruption - * The peak p0, p1 stay the same as - * (kFastLocalBloom + PartitionedFilter) but just - * last longer since we release hash entries reservation later. - * - */ - if (!partition_filters) { - EXPECT_EQ(filter_construction_cache_res_peaks.size(), 1) - << "Filter construction cache charging should have only 1 peak in " - "case: kFastLocalBloom + FullFilter"; - std::size_t filter_construction_cache_res_peak = - filter_construction_cache_res_peaks[0]; - EXPECT_GT(filter_construction_cache_res_peak, - predicted_hash_entries_cache_res) - << "The testing number of hash entries is designed to make hash " - "entries cache charging be multiples of dummy entries" - " so the correct behavior of charging final filter on top of it" - " should've triggered at least another dummy entry insertion"; - - std::size_t predicted_filter_construction_cache_res_peak = - predicted_hash_entries_cache_res + predicted_final_filter_cache_res; - EXPECT_GE(filter_construction_cache_res_peak, - predicted_filter_construction_cache_res_peak * 0.9); - EXPECT_LE(filter_construction_cache_res_peak, - predicted_filter_construction_cache_res_peak * 1.1); - return; - } else { - EXPECT_GE(filter_construction_cache_res_peaks.size(), 2) - << "Filter construction cache charging should have multiple peaks " - "in case: kFastLocalBloom + " - "PartitionedFilter"; - std::size_t predicted_filter_construction_cache_res_increments_sum = - predicted_hash_entries_cache_res + predicted_final_filter_cache_res; - EXPECT_GE(filter_construction_cache_res_increments_sum, - predicted_filter_construction_cache_res_increments_sum * 0.9); - EXPECT_LE(filter_construction_cache_res_increments_sum, - predicted_filter_construction_cache_res_increments_sum * 1.1); - return; - } - } - - if (policy == kStandard128Ribbon) { - /* kStandard128Ribbon + FullFilter - * p0 - * / \ p1 - * / \/\ - * b / b' \ - * / \ - * 0/ \ - * hash entries = b - 0, banding = p0 - b, final filter = p1 - b' - * p0 = hash entries + banding - * - * The test is designed in a way such that the reservation for (p1 - b') - * will trigger at least another dummy entry insertion - * (or equivalently to saying, creating another peak). - * - * kStandard128Ribbon + FullFilter + - * detect_filter_construct_corruption - * - * new p0 - * / \ - * / \ - * pre p0 \ - * / \ - * / \ - * b / \ - * / \ - * 0/ \ - * hash entries = b - 0, banding = pre p0 - b, - * final filter = new p0 - pre p0 - * new p0 = hash entries + banding + final filter - * - * The previous p0 will no longer be a peak since under - * detect_filter_construct_corruption == true, we do not release hash - * entries reserveration (like p0 - b' previously) until after final filter - * creation and post-verification - * - * kStandard128Ribbon + PartitionedFilter - * p3 - * p0 /\ p4 - * / \ p1 / \ /\ - * / \/\ b''/ a' \ - * b / b' \ / \ - * / \ / \ - * 0/ a \ - * partitioned hash entries1 = b - 0, partitioned hash entries2 = b'' - a - * partitioned banding1 = p0 - b, partitioned banding2 = p3 - b'' - * parittioned final filter1 = p1 - b',parittioned final filter2 = p4 - a' - * - * (increment p0 - 0) + (increment p1 - b') - * + (increment p3 - a) + (increment p4 - a') - * = partitioned hash entries1 + partitioned hash entries2 - * + parittioned banding1 + parittioned banding2 - * + parittioned final filter1 + parittioned final filter2 - * = hash entries + banding + final filter - * - * kStandard128Ribbon + PartitionedFilter + - * detect_filter_construct_corruption - * - * new p3 - * / \ - * pre p3 \ - * new p0 / \ - * / \ / \ - * pre p0 \ / \ - * / \ b'/ \ - * / \ / \ - * b / \ / \ - * / \a \ - * 0/ \ - * partitioned hash entries1 = b - 0, partitioned hash entries2 = b' - a - * partitioned banding1 = pre p0 - b, partitioned banding2 = pre p3 - b' - * parittioned final filter1 = new p0 - pre p0, - * parittioned final filter2 = new p3 - pre p3 - * - * The previous p0 and p3 will no longer be a peak since under - * detect_filter_construct_corruption == true, we do not release hash - * entries reserveration (like p0 - b', p3 - a' previously) until after - * parittioned final filter creation and post-verification - * - * However, increments sum stay the same as shown below: - * (increment new p0 - 0) + (increment new p3 - a) - * = partitioned hash entries1 + partitioned hash entries2 - * + parittioned banding1 + parittioned banding2 - * + parittioned final filter1 + parittioned final filter2 - * = hash entries + banding + final filter - * - */ - if (!partition_filters) { - ASSERT_GE( - std::floor( - 1.0 * predicted_final_filter_cache_res / - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize()), - 1) - << "Final filter cache charging too small for this test - please " - "increase the number of keys"; - if (!detect_filter_construct_corruption) { - EXPECT_EQ(filter_construction_cache_res_peaks.size(), 2) - << "Filter construction cache charging should have 2 peaks in " - "case: kStandard128Ribbon + " - "FullFilter. " - "The second peak is resulted from charging the final filter " - "after " - "decreasing the hash entry reservation since the testing final " - "filter reservation is designed to be at least 1 dummy entry " - "size"; - - std::size_t filter_construction_cache_res_peak = - filter_construction_cache_res_peaks[0]; - std::size_t predicted_filter_construction_cache_res_peak = - predicted_hash_entries_cache_res + predicted_banding_cache_res; - EXPECT_GE(filter_construction_cache_res_peak, - predicted_filter_construction_cache_res_peak * 0.9); - EXPECT_LE(filter_construction_cache_res_peak, - predicted_filter_construction_cache_res_peak * 1.1); - } else { - EXPECT_EQ(filter_construction_cache_res_peaks.size(), 1) - << "Filter construction cache charging should have 1 peaks in " - "case: kStandard128Ribbon + FullFilter " - "+ detect_filter_construct_corruption. " - "The previous second peak now disappears since we don't " - "decrease the hash entry reservation" - "until after final filter reservation and post-verification"; - - std::size_t filter_construction_cache_res_peak = - filter_construction_cache_res_peaks[0]; - std::size_t predicted_filter_construction_cache_res_peak = - predicted_hash_entries_cache_res + predicted_banding_cache_res + - predicted_final_filter_cache_res; - EXPECT_GE(filter_construction_cache_res_peak, - predicted_filter_construction_cache_res_peak * 0.9); - EXPECT_LE(filter_construction_cache_res_peak, - predicted_filter_construction_cache_res_peak * 1.1); - } - return; - } else { - if (!detect_filter_construct_corruption) { - EXPECT_GE(filter_construction_cache_res_peaks.size(), 3) - << "Filter construction cache charging should have more than 3 " - "peaks " - "in case: kStandard128Ribbon + " - "PartitionedFilter"; - } else { - EXPECT_GE(filter_construction_cache_res_peaks.size(), 2) - << "Filter construction cache charging should have more than 2 " - "peaks " - "in case: kStandard128Ribbon + " - "PartitionedFilter + detect_filter_construct_corruption"; - } - std::size_t predicted_filter_construction_cache_res_increments_sum = - predicted_hash_entries_cache_res + predicted_banding_cache_res + - predicted_final_filter_cache_res; - EXPECT_GE(filter_construction_cache_res_increments_sum, - predicted_filter_construction_cache_res_increments_sum * 0.9); - EXPECT_LE(filter_construction_cache_res_increments_sum, - predicted_filter_construction_cache_res_increments_sum * 1.1); - return; - } - } -} - -class DBFilterConstructionCorruptionTestWithParam - : public DBTestBase, - public testing::WithParamInterface< - std::tuple> { - public: - DBFilterConstructionCorruptionTestWithParam() - : DBTestBase("db_bloom_filter_tests", - /*env_do_fsync=*/true) {} - - BlockBasedTableOptions GetBlockBasedTableOptions() { - BlockBasedTableOptions table_options; - table_options.detect_filter_construct_corruption = std::get<0>(GetParam()); - table_options.filter_policy = Create(10, std::get<1>(GetParam())); - table_options.partition_filters = std::get<2>(GetParam()); - if (table_options.partition_filters) { - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - // We set table_options.metadata_block_size small enough so we can - // trigger filter partitioning with GetNumKey() amount of keys - table_options.metadata_block_size = 10; - } - - return table_options; - } - - // Return an appropriate amount of keys for testing - // to generate a long filter (i.e, size >= 8 + kMetadataLen) - std::size_t GetNumKey() { return 5000; } -}; - -INSTANTIATE_TEST_CASE_P( - DBFilterConstructionCorruptionTestWithParam, - DBFilterConstructionCorruptionTestWithParam, - ::testing::Values(std::make_tuple(false, kFastLocalBloom, false), - std::make_tuple(true, kFastLocalBloom, false), - std::make_tuple(true, kFastLocalBloom, true), - std::make_tuple(true, kStandard128Ribbon, false), - std::make_tuple(true, kStandard128Ribbon, true))); - -TEST_P(DBFilterConstructionCorruptionTestWithParam, DetectCorruption) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.create_if_missing = true; - options.disable_auto_compactions = true; - - DestroyAndReopen(options); - int num_key = static_cast(GetNumKey()); - Status s; - - // Case 1: No corruption in filter construction - for (int i = 0; i < num_key; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - s = Flush(); - EXPECT_TRUE(s.ok()); - - // Case 2: Corruption of hash entries in filter construction - for (int i = 0; i < num_key; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - - SyncPoint::GetInstance()->SetCallBack( - "XXPH3FilterBitsBuilder::Finish::TamperHashEntries", [&](void* arg) { - std::deque* hash_entries_to_corrupt = - (std::deque*)arg; - assert(!hash_entries_to_corrupt->empty()); - *(hash_entries_to_corrupt->begin()) = - *(hash_entries_to_corrupt->begin()) ^ uint64_t { 1 }; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - s = Flush(); - - if (table_options.detect_filter_construct_corruption) { - EXPECT_TRUE(s.IsCorruption()); - EXPECT_TRUE( - s.ToString().find("Filter's hash entries checksum mismatched") != - std::string::npos); - } else { - EXPECT_TRUE(s.ok()); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearCallBack( - "XXPH3FilterBitsBuilder::Finish::" - "TamperHashEntries"); - - // Case 3: Corruption of filter content in filter construction - DestroyAndReopen(options); - - for (int i = 0; i < num_key; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - - SyncPoint::GetInstance()->SetCallBack( - "XXPH3FilterBitsBuilder::Finish::TamperFilter", [&](void* arg) { - std::pair*, std::size_t>* TEST_arg_pair = - (std::pair*, std::size_t>*)arg; - std::size_t filter_size = TEST_arg_pair->second; - // 5 is the kMetadataLen and - assert(filter_size >= 8 + 5); - std::unique_ptr* filter_content_to_corrupt = - TEST_arg_pair->first; - std::memset(filter_content_to_corrupt->get(), '\0', 8); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - s = Flush(); - - if (table_options.detect_filter_construct_corruption) { - EXPECT_TRUE(s.IsCorruption()); - EXPECT_TRUE(s.ToString().find("Corrupted filter content") != - std::string::npos); - } else { - EXPECT_TRUE(s.ok()); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearCallBack( - "XXPH3FilterBitsBuilder::Finish::" - "TamperFilter"); -} - -// RocksDB lite does not support dynamic options -TEST_P(DBFilterConstructionCorruptionTestWithParam, - DynamicallyTurnOnAndOffDetectConstructCorruption) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - // We intend to turn on - // table_options.detect_filter_construct_corruption dynamically - // therefore we override this test parmater's value - table_options.detect_filter_construct_corruption = false; - - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.create_if_missing = true; - - int num_key = static_cast(GetNumKey()); - Status s; - - DestroyAndReopen(options); - - // Case 1: !table_options.detect_filter_construct_corruption - for (int i = 0; i < num_key; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - - SyncPoint::GetInstance()->SetCallBack( - "XXPH3FilterBitsBuilder::Finish::TamperHashEntries", [&](void* arg) { - std::deque* hash_entries_to_corrupt = - (std::deque*)arg; - assert(!hash_entries_to_corrupt->empty()); - *(hash_entries_to_corrupt->begin()) = - *(hash_entries_to_corrupt->begin()) ^ uint64_t { 1 }; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - s = Flush(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearCallBack( - "XXPH3FilterBitsBuilder::Finish::" - "TamperHashEntries"); - - ASSERT_FALSE(table_options.detect_filter_construct_corruption); - EXPECT_TRUE(s.ok()); - - // Case 2: dynamically turn on - // table_options.detect_filter_construct_corruption - ASSERT_OK(db_->SetOptions({{"block_based_table_factory", - "{detect_filter_construct_corruption=true;}"}})); - - for (int i = 0; i < num_key; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - } - - SyncPoint::GetInstance()->SetCallBack( - "XXPH3FilterBitsBuilder::Finish::TamperHashEntries", [&](void* arg) { - std::deque* hash_entries_to_corrupt = - (std::deque*)arg; - assert(!hash_entries_to_corrupt->empty()); - *(hash_entries_to_corrupt->begin()) = - *(hash_entries_to_corrupt->begin()) ^ uint64_t { 1 }; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - s = Flush(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearCallBack( - "XXPH3FilterBitsBuilder::Finish::" - "TamperHashEntries"); - - auto updated_table_options = - db_->GetOptions().table_factory->GetOptions(); - EXPECT_TRUE(updated_table_options->detect_filter_construct_corruption); - EXPECT_TRUE(s.IsCorruption()); - EXPECT_TRUE(s.ToString().find("Filter's hash entries checksum mismatched") != - std::string::npos); - - // Case 3: dynamically turn off - // table_options.detect_filter_construct_corruption - ASSERT_OK(db_->SetOptions({{"block_based_table_factory", - "{detect_filter_construct_corruption=false;}"}})); - updated_table_options = - db_->GetOptions().table_factory->GetOptions(); - EXPECT_FALSE(updated_table_options->detect_filter_construct_corruption); -} - -namespace { -// NOTE: This class is referenced by HISTORY.md as a model for a wrapper -// FilterPolicy selecting among configurations based on context. -class LevelAndStyleCustomFilterPolicy : public FilterPolicy { - public: - explicit LevelAndStyleCustomFilterPolicy(int bpk_fifo, int bpk_l0_other, - int bpk_otherwise) - : policy_fifo_(NewBloomFilterPolicy(bpk_fifo)), - policy_l0_other_(NewBloomFilterPolicy(bpk_l0_other)), - policy_otherwise_(NewBloomFilterPolicy(bpk_otherwise)) {} - - const char* Name() const override { - return "LevelAndStyleCustomFilterPolicy"; - } - - // OK to use built-in policy name because we are deferring to a - // built-in builder. We aren't changing the serialized format. - const char* CompatibilityName() const override { - return policy_fifo_->CompatibilityName(); - } - - FilterBitsBuilder* GetBuilderWithContext( - const FilterBuildingContext& context) const override { - if (context.compaction_style == kCompactionStyleFIFO) { - return policy_fifo_->GetBuilderWithContext(context); - } else if (context.level_at_creation == 0) { - return policy_l0_other_->GetBuilderWithContext(context); - } else { - return policy_otherwise_->GetBuilderWithContext(context); - } - } - - FilterBitsReader* GetFilterBitsReader(const Slice& contents) const override { - // OK to defer to any of them; they all can parse built-in filters - // from any settings. - return policy_fifo_->GetFilterBitsReader(contents); - } - - private: - const std::unique_ptr policy_fifo_; - const std::unique_ptr policy_l0_other_; - const std::unique_ptr policy_otherwise_; -}; - -static std::map - table_file_creation_reason_to_string{ - {TableFileCreationReason::kCompaction, "kCompaction"}, - {TableFileCreationReason::kFlush, "kFlush"}, - {TableFileCreationReason::kMisc, "kMisc"}, - {TableFileCreationReason::kRecovery, "kRecovery"}, - }; - -class TestingContextCustomFilterPolicy - : public LevelAndStyleCustomFilterPolicy { - public: - explicit TestingContextCustomFilterPolicy(int bpk_fifo, int bpk_l0_other, - int bpk_otherwise) - : LevelAndStyleCustomFilterPolicy(bpk_fifo, bpk_l0_other, bpk_otherwise) { - } - - FilterBitsBuilder* GetBuilderWithContext( - const FilterBuildingContext& context) const override { - test_report_ += "cf="; - test_report_ += context.column_family_name; - test_report_ += ",s="; - test_report_ += - OptionsHelper::compaction_style_to_string[context.compaction_style]; - test_report_ += ",n="; - test_report_ += std::to_string(context.num_levels); - test_report_ += ",l="; - test_report_ += std::to_string(context.level_at_creation); - test_report_ += ",b="; - test_report_ += std::to_string(int{context.is_bottommost}); - test_report_ += ",r="; - test_report_ += table_file_creation_reason_to_string[context.reason]; - test_report_ += "\n"; - - return LevelAndStyleCustomFilterPolicy::GetBuilderWithContext(context); - } - - std::string DumpTestReport() { - std::string rv; - std::swap(rv, test_report_); - return rv; - } - - private: - mutable std::string test_report_; -}; -} // anonymous namespace - -TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) { - auto policy = std::make_shared(15, 8, 5); - Options options; - for (bool fifo : {true, false}) { - options = CurrentOptions(); - options.max_open_files = fifo ? -1 : options.max_open_files; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.compaction_style = - fifo ? kCompactionStyleFIFO : kCompactionStyleLevel; - - BlockBasedTableOptions table_options; - table_options.filter_policy = policy; - table_options.format_version = 5; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - TryReopen(options); - CreateAndReopenWithCF({fifo ? "abe" : "bob"}, options); - - const int maxKey = 10000; - for (int i = 0; i < maxKey / 2; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - // Add a large key to make the file contain wide range - ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555))); - Flush(1); - EXPECT_EQ(policy->DumpTestReport(), - fifo ? "cf=abe,s=kCompactionStyleFIFO,n=7,l=0,b=0,r=kFlush\n" - : "cf=bob,s=kCompactionStyleLevel,n=7,l=0,b=0,r=kFlush\n"); - - for (int i = maxKey / 2; i < maxKey; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - Flush(1); - EXPECT_EQ(policy->DumpTestReport(), - fifo ? "cf=abe,s=kCompactionStyleFIFO,n=7,l=0,b=0,r=kFlush\n" - : "cf=bob,s=kCompactionStyleLevel,n=7,l=0,b=0,r=kFlush\n"); - - // Check that they can be found - for (int i = 0; i < maxKey; i++) { - ASSERT_EQ(Key(i), Get(1, Key(i))); - } - // Since we have two tables / two filters, we might have Bloom checks on - // our queries, but no more than one "useful" per query on a found key. - EXPECT_LE(TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey); - - // Check that we have two filters, each about - // fifo: 0.12% FP rate (15 bits per key) - // level: 2.3% FP rate (8 bits per key) - for (int i = 0; i < maxKey; i++) { - ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333))); - } - { - auto useful_count = - TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL); - EXPECT_GE(useful_count, maxKey * 2 * (fifo ? 0.9980 : 0.975)); - EXPECT_LE(useful_count, maxKey * 2 * (fifo ? 0.9995 : 0.98)); - } - - if (!fifo) { // FIFO doesn't fully support CompactRange - // Full compaction - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, - nullptr)); - EXPECT_EQ(policy->DumpTestReport(), - "cf=bob,s=kCompactionStyleLevel,n=7,l=1,b=1,r=kCompaction\n"); - - // Check that we now have one filter, about 9.2% FP rate (5 bits per key) - for (int i = 0; i < maxKey; i++) { - ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333))); - } - { - auto useful_count = - TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL); - EXPECT_GE(useful_count, maxKey * 0.90); - EXPECT_LE(useful_count, maxKey * 0.91); - } - } else { - // Also try external SST file - { - std::string file_path = dbname_ + "/external.sst"; - SstFileWriter sst_file_writer(EnvOptions(), options, handles_[1]); - ASSERT_OK(sst_file_writer.Open(file_path)); - ASSERT_OK(sst_file_writer.Put("key", "value")); - ASSERT_OK(sst_file_writer.Finish()); - } - // Note: kCompactionStyleLevel is default, ignored if num_levels == -1 - EXPECT_EQ(policy->DumpTestReport(), - "cf=abe,s=kCompactionStyleLevel,n=-1,l=-1,b=0,r=kMisc\n"); - } - - // Destroy - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1])); - handles_[1] = nullptr; - } -} - -class SliceTransformLimitedDomain : public SliceTransform { - const char* Name() const override { return "SliceTransformLimitedDomain"; } - - Slice Transform(const Slice& src) const override { - return Slice(src.data(), 5); - } - - bool InDomain(const Slice& src) const override { - // prefix will be x???? - return src.size() >= 5 && src[0] == 'x'; - } - - bool InRange(const Slice& dst) const override { - // prefix will be x???? - return dst.size() == 5 && dst[0] == 'x'; - } -}; - -TEST_F(DBBloomFilterTest, PrefixExtractorWithFilter1) { - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); - bbto.whole_key_filtering = false; - - Options options = CurrentOptions(); - options.prefix_extractor = std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - DestroyAndReopen(options); - - ASSERT_OK(Put("x1111_AAAA", "val1")); - ASSERT_OK(Put("x1112_AAAA", "val2")); - ASSERT_OK(Put("x1113_AAAA", "val3")); - ASSERT_OK(Put("x1114_AAAA", "val4")); - // Not in domain, wont be added to filter - ASSERT_OK(Put("zzzzz_AAAA", "val5")); - - ASSERT_OK(Flush()); - - ASSERT_EQ(Get("x1111_AAAA"), "val1"); - ASSERT_EQ(Get("x1112_AAAA"), "val2"); - ASSERT_EQ(Get("x1113_AAAA"), "val3"); - ASSERT_EQ(Get("x1114_AAAA"), "val4"); - // Was not added to filter but rocksdb will try to read it from the filter - ASSERT_EQ(Get("zzzzz_AAAA"), "val5"); -} - -TEST_F(DBBloomFilterTest, PrefixExtractorWithFilter2) { - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); - - Options options = CurrentOptions(); - options.prefix_extractor = std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - DestroyAndReopen(options); - - ASSERT_OK(Put("x1113_AAAA", "val3")); - ASSERT_OK(Put("x1114_AAAA", "val4")); - // Not in domain, wont be added to filter - ASSERT_OK(Put("zzzzz_AAAA", "val1")); - ASSERT_OK(Put("zzzzz_AAAB", "val2")); - ASSERT_OK(Put("zzzzz_AAAC", "val3")); - ASSERT_OK(Put("zzzzz_AAAD", "val4")); - - ASSERT_OK(Flush()); - - std::vector iter_res; - auto iter = db_->NewIterator(ReadOptions()); - // Seek to a key that was not in Domain - for (iter->Seek("zzzzz_AAAA"); iter->Valid(); iter->Next()) { - iter_res.emplace_back(iter->value().ToString()); - } - - std::vector expected_res = {"val1", "val2", "val3", "val4"}; - ASSERT_EQ(iter_res, expected_res); - delete iter; -} - -TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilter) { - // regression test for #2743. the range delete tombstones in memtable should - // be added even when Get() skips searching due to its prefix bloom filter - const int kMemtableSize = 1 << 20; // 1MB - const int kMemtablePrefixFilterSize = 1 << 13; // 8KB - const int kPrefixLen = 4; - Options options = CurrentOptions(); - options.memtable_prefix_bloom_size_ratio = - static_cast(kMemtablePrefixFilterSize) / kMemtableSize; - options.prefix_extractor.reset( - ROCKSDB_NAMESPACE::NewFixedPrefixTransform(kPrefixLen)); - options.write_buffer_size = kMemtableSize; - options.memtable_whole_key_filtering = false; - Reopen(options); - std::string key1("AAAABBBB"); - std::string key2("AAAACCCC"); // not in DB - std::string key3("AAAADDDD"); - std::string key4("AAAAEEEE"); - std::string value1("Value1"); - std::string value3("Value3"); - std::string value4("Value4"); - - ASSERT_OK(Put(key1, value1, WriteOptions())); - - // check memtable bloom stats - ASSERT_EQ("NOT_FOUND", Get(key2)); - ASSERT_EQ(0, get_perf_context()->bloom_memtable_miss_count); - // same prefix, bloom filter false positive - ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); - - // enable whole key bloom filter - options.memtable_whole_key_filtering = true; - Reopen(options); - // check memtable bloom stats - ASSERT_OK(Put(key3, value3, WriteOptions())); - ASSERT_EQ("NOT_FOUND", Get(key2)); - // whole key bloom filter kicks in and determines it's a miss - ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count); - ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); - - // verify whole key filtering does not depend on prefix_extractor - options.prefix_extractor.reset(); - Reopen(options); - // check memtable bloom stats - ASSERT_OK(Put(key4, value4, WriteOptions())); - ASSERT_EQ("NOT_FOUND", Get(key2)); - // whole key bloom filter kicks in and determines it's a miss - ASSERT_EQ(2, get_perf_context()->bloom_memtable_miss_count); - ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); -} - -TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilterMultiGet) { - Options options = CurrentOptions(); - options.memtable_prefix_bloom_size_ratio = 0.015; - options.memtable_whole_key_filtering = true; - Reopen(options); - std::string key1("AA"); - std::string key2("BB"); - std::string key3("CC"); - std::string key4("DD"); - std::string key_not("EE"); - std::string value1("Value1"); - std::string value2("Value2"); - std::string value3("Value3"); - std::string value4("Value4"); - - ASSERT_OK(Put(key1, value1, WriteOptions())); - ASSERT_OK(Put(key2, value2, WriteOptions())); - ASSERT_OK(Flush()); - ASSERT_OK(Put(key3, value3, WriteOptions())); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(Put(key4, value4, WriteOptions())); - - // Delete key2 and key3 - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "BA", "CZ")); - - // Read without snapshot - auto results = MultiGet({key_not, key1, key2, key3, key4}); - ASSERT_EQ(results[0], "NOT_FOUND"); - ASSERT_EQ(results[1], value1); - ASSERT_EQ(results[2], "NOT_FOUND"); - ASSERT_EQ(results[3], "NOT_FOUND"); - ASSERT_EQ(results[4], value4); - - // Also check Get - ASSERT_EQ(Get(key1), value1); - ASSERT_EQ(Get(key2), "NOT_FOUND"); - ASSERT_EQ(Get(key3), "NOT_FOUND"); - ASSERT_EQ(Get(key4), value4); - - // Read with snapshot - results = MultiGet({key_not, key1, key2, key3, key4}, snapshot); - ASSERT_EQ(results[0], "NOT_FOUND"); - ASSERT_EQ(results[1], value1); - ASSERT_EQ(results[2], value2); - ASSERT_EQ(results[3], value3); - ASSERT_EQ(results[4], "NOT_FOUND"); - - // Also check Get - ASSERT_EQ(Get(key1, snapshot), value1); - ASSERT_EQ(Get(key2, snapshot), value2); - ASSERT_EQ(Get(key3, snapshot), value3); - ASSERT_EQ(Get(key4, snapshot), "NOT_FOUND"); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBBloomFilterTest, MemtablePrefixBloomOutOfDomain) { - constexpr size_t kPrefixSize = 8; - const std::string kKey = "key"; - assert(kKey.size() < kPrefixSize); - Options options = CurrentOptions(); - options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixSize)); - options.memtable_prefix_bloom_size_ratio = 0.25; - Reopen(options); - ASSERT_OK(Put(kKey, "v")); - ASSERT_EQ("v", Get(kKey)); - std::unique_ptr iter(dbfull()->NewIterator(ReadOptions())); - iter->Seek(kKey); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(kKey, iter->key()); - iter->SeekForPrev(kKey); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(kKey, iter->key()); -} - -class DBBloomFilterTestVaryPrefixAndFormatVer - : public DBTestBase, - public testing::WithParamInterface> { - protected: - bool use_prefix_; - uint32_t format_version_; - - public: - DBBloomFilterTestVaryPrefixAndFormatVer() - : DBTestBase("db_bloom_filter_tests", /*env_do_fsync=*/true) {} - - ~DBBloomFilterTestVaryPrefixAndFormatVer() override {} - - void SetUp() override { - use_prefix_ = std::get<0>(GetParam()); - format_version_ = std::get<1>(GetParam()); - } - - static std::string UKey(uint32_t i) { return Key(static_cast(i)); } -}; - -TEST_P(DBBloomFilterTestVaryPrefixAndFormatVer, PartitionedMultiGet) { - Options options = CurrentOptions(); - if (use_prefix_) { - // Entire key from UKey() - options.prefix_extractor.reset(NewCappedPrefixTransform(9)); - } - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(20)); - bbto.partition_filters = true; - bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - bbto.whole_key_filtering = !use_prefix_; - if (use_prefix_) { // (not related to prefix, just alternating between) - // Make sure code appropriately deals with metadata block size setting - // that is "too small" (smaller than minimum size for filter builder) - bbto.metadata_block_size = 63; - } else { - // Make sure the test will work even on platforms with large minimum - // filter size, due to large cache line size. - // (Largest cache line size + 10+% overhead.) - bbto.metadata_block_size = 290; - } - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - ReadOptions ropts; - - constexpr uint32_t N = 12000; - // Add N/2 evens - for (uint32_t i = 0; i < N; i += 2) { - ASSERT_OK(Put(UKey(i), UKey(i))); - } - ASSERT_OK(Flush()); - ASSERT_EQ(TotalTableFiles(), 1); - - constexpr uint32_t Q = 29; - // MultiGet In - std::array keys; - std::array key_slices; - std::array column_families; - // MultiGet Out - std::array statuses; - std::array values; - - TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_HIT); - TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_MISS); - TestGetAndResetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL); - TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL); - TestGetAndResetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED); - TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE); - TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE); - - // Check that initial clump of keys only loads one partition filter from - // block cache. - // And that spread out keys load many partition filters. - // In both cases, mix present vs. not present keys. - for (uint32_t stride : {uint32_t{1}, (N / Q) | 1}) { - for (uint32_t i = 0; i < Q; ++i) { - keys[i] = UKey(i * stride); - key_slices[i] = Slice(keys[i]); - column_families[i] = db_->DefaultColumnFamily(); - statuses[i] = Status(); - values[i] = PinnableSlice(); - } - - db_->MultiGet(ropts, Q, &column_families[0], &key_slices[0], &values[0], - /*timestamps=*/nullptr, &statuses[0], true); - - // Confirm correct status results - uint32_t number_not_found = 0; - for (uint32_t i = 0; i < Q; ++i) { - if ((i * stride % 2) == 0) { - ASSERT_OK(statuses[i]); - } else { - ASSERT_TRUE(statuses[i].IsNotFound()); - ++number_not_found; - } - } - - // Confirm correct Bloom stats (no FPs) - uint64_t filter_useful = TestGetAndResetTickerCount( - options, - use_prefix_ ? BLOOM_FILTER_PREFIX_USEFUL : BLOOM_FILTER_USEFUL); - uint64_t filter_checked = - TestGetAndResetTickerCount(options, use_prefix_ - ? BLOOM_FILTER_PREFIX_CHECKED - : BLOOM_FILTER_FULL_POSITIVE) + - (use_prefix_ ? 0 : filter_useful); - EXPECT_EQ(filter_useful, number_not_found); - EXPECT_EQ(filter_checked, Q); - if (!use_prefix_) { - EXPECT_EQ( - TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), - Q - number_not_found); - } - - // Confirm no duplicate loading same filter partition - uint64_t filter_accesses = - TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_HIT) + - TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_MISS); - if (stride == 1) { - EXPECT_EQ(filter_accesses, 1); - } else { - // for large stride - EXPECT_GE(filter_accesses, Q / 2 + 1); - } - } - - // Check that a clump of keys (present and not) works when spanning - // two partitions - int found_spanning = 0; - for (uint32_t start = 0; start < N / 2;) { - for (uint32_t i = 0; i < Q; ++i) { - keys[i] = UKey(start + i); - key_slices[i] = Slice(keys[i]); - column_families[i] = db_->DefaultColumnFamily(); - statuses[i] = Status(); - values[i] = PinnableSlice(); - } - - db_->MultiGet(ropts, Q, &column_families[0], &key_slices[0], &values[0], - /*timestamps=*/nullptr, &statuses[0], true); - - // Confirm correct status results - uint32_t number_not_found = 0; - for (uint32_t i = 0; i < Q; ++i) { - if (((start + i) % 2) == 0) { - ASSERT_OK(statuses[i]); - } else { - ASSERT_TRUE(statuses[i].IsNotFound()); - ++number_not_found; - } - } - - // Confirm correct Bloom stats (might see some FPs) - uint64_t filter_useful = TestGetAndResetTickerCount( - options, - use_prefix_ ? BLOOM_FILTER_PREFIX_USEFUL : BLOOM_FILTER_USEFUL); - uint64_t filter_checked = - TestGetAndResetTickerCount(options, use_prefix_ - ? BLOOM_FILTER_PREFIX_CHECKED - : BLOOM_FILTER_FULL_POSITIVE) + - (use_prefix_ ? 0 : filter_useful); - EXPECT_GE(filter_useful, number_not_found - 2); // possible FP - EXPECT_EQ(filter_checked, Q); - if (!use_prefix_) { - EXPECT_EQ( - TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), - Q - number_not_found); - } - - // Confirm no duplicate loading of same filter partition - uint64_t filter_accesses = - TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_HIT) + - TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_MISS); - if (filter_accesses == 2) { - // Spanned across partitions. - ++found_spanning; - if (found_spanning >= 2) { - break; - } else { - // Ensure that at least once we have at least one present and - // one non-present key on both sides of partition boundary. - start += 2; - } - } else { - EXPECT_EQ(filter_accesses, 1); - // See explanation at "start += 2" - start += Q - 4; - } - } - EXPECT_TRUE(found_spanning >= 2); -} - -INSTANTIATE_TEST_CASE_P(DBBloomFilterTestVaryPrefixAndFormatVer, - DBBloomFilterTestVaryPrefixAndFormatVer, - ::testing::Values( - // (use_prefix, format_version) - std::make_tuple(false, 2), - std::make_tuple(false, 3), - std::make_tuple(false, 4), - std::make_tuple(false, 5), std::make_tuple(true, 2), - std::make_tuple(true, 3), std::make_tuple(true, 4), - std::make_tuple(true, 5))); - -namespace { -static const std::string kPlainTable = "test_PlainTableBloom"; -} // anonymous namespace - -class BloomStatsTestWithParam - : public DBBloomFilterTest, - public testing::WithParamInterface> { - public: - BloomStatsTestWithParam() { - bfp_impl_ = std::get<0>(GetParam()); - partition_filters_ = std::get<1>(GetParam()); - - options_.create_if_missing = true; - options_.prefix_extractor.reset( - ROCKSDB_NAMESPACE::NewFixedPrefixTransform(4)); - options_.memtable_prefix_bloom_size_ratio = - 8.0 * 1024.0 / static_cast(options_.write_buffer_size); - if (bfp_impl_ == kPlainTable) { - assert(!partition_filters_); // not supported in plain table - PlainTableOptions table_options; - options_.table_factory.reset(NewPlainTableFactory(table_options)); - } else { - BlockBasedTableOptions table_options; - if (partition_filters_) { - table_options.partition_filters = partition_filters_; - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } - table_options.filter_policy = Create(10, bfp_impl_); - options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); - } - options_.env = env_; - - get_perf_context()->Reset(); - DestroyAndReopen(options_); - } - - ~BloomStatsTestWithParam() override { - get_perf_context()->Reset(); - Destroy(options_); - } - - // Required if inheriting from testing::WithParamInterface<> - static void SetUpTestCase() {} - static void TearDownTestCase() {} - - std::string bfp_impl_; - bool partition_filters_; - Options options_; -}; - -// 1 Insert 2 K-V pairs into DB -// 2 Call Get() for both keys - expext memtable bloom hit stat to be 2 -// 3 Call Get() for nonexisting key - expect memtable bloom miss stat to be 1 -// 4 Call Flush() to create SST -// 5 Call Get() for both keys - expext SST bloom hit stat to be 2 -// 6 Call Get() for nonexisting key - expect SST bloom miss stat to be 1 -// Test both: block and plain SST -TEST_P(BloomStatsTestWithParam, BloomStatsTest) { - std::string key1("AAAA"); - std::string key2("RXDB"); // not in DB - std::string key3("ZBRA"); - std::string value1("Value1"); - std::string value3("Value3"); - - ASSERT_OK(Put(key1, value1, WriteOptions())); - ASSERT_OK(Put(key3, value3, WriteOptions())); - - // check memtable bloom stats - ASSERT_EQ(value1, Get(key1)); - ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); - ASSERT_EQ(value3, Get(key3)); - ASSERT_EQ(2, get_perf_context()->bloom_memtable_hit_count); - ASSERT_EQ(0, get_perf_context()->bloom_memtable_miss_count); - - ASSERT_EQ("NOT_FOUND", Get(key2)); - ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count); - ASSERT_EQ(2, get_perf_context()->bloom_memtable_hit_count); - - // sanity checks - ASSERT_EQ(0, get_perf_context()->bloom_sst_hit_count); - ASSERT_EQ(0, get_perf_context()->bloom_sst_miss_count); - - Flush(); - - // sanity checks - ASSERT_EQ(0, get_perf_context()->bloom_sst_hit_count); - ASSERT_EQ(0, get_perf_context()->bloom_sst_miss_count); - - // check SST bloom stats - ASSERT_EQ(value1, Get(key1)); - ASSERT_EQ(1, get_perf_context()->bloom_sst_hit_count); - ASSERT_EQ(value3, Get(key3)); - ASSERT_EQ(2, get_perf_context()->bloom_sst_hit_count); - - ASSERT_EQ("NOT_FOUND", Get(key2)); - ASSERT_EQ(1, get_perf_context()->bloom_sst_miss_count); -} - -// Same scenario as in BloomStatsTest but using an iterator -TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) { - std::string key1("AAAA"); - std::string key2("RXDB"); // not in DB - std::string key3("ZBRA"); - std::string value1("Value1"); - std::string value3("Value3"); - - ASSERT_OK(Put(key1, value1, WriteOptions())); - ASSERT_OK(Put(key3, value3, WriteOptions())); - - std::unique_ptr iter(dbfull()->NewIterator(ReadOptions())); - - // check memtable bloom stats - iter->Seek(key1); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(value1, iter->value().ToString()); - ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); - ASSERT_EQ(0, get_perf_context()->bloom_memtable_miss_count); - - iter->Seek(key3); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(value3, iter->value().ToString()); - ASSERT_EQ(2, get_perf_context()->bloom_memtable_hit_count); - ASSERT_EQ(0, get_perf_context()->bloom_memtable_miss_count); - - iter->Seek(key2); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count); - ASSERT_EQ(2, get_perf_context()->bloom_memtable_hit_count); - - Flush(); - - iter.reset(dbfull()->NewIterator(ReadOptions())); - - // Check SST bloom stats - iter->Seek(key1); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(value1, iter->value().ToString()); - ASSERT_EQ(1, get_perf_context()->bloom_sst_hit_count); - - iter->Seek(key3); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(value3, iter->value().ToString()); - uint64_t expected_hits = 2; - ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count); - - iter->Seek(key2); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ(1, get_perf_context()->bloom_sst_miss_count); - ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count); -} - -INSTANTIATE_TEST_CASE_P( - BloomStatsTestWithParam, BloomStatsTestWithParam, - ::testing::Values(std::make_tuple(kLegacyBloom, false), - std::make_tuple(kLegacyBloom, true), - std::make_tuple(kFastLocalBloom, false), - std::make_tuple(kFastLocalBloom, true), - std::make_tuple(kPlainTable, false))); - -namespace { -void PrefixScanInit(DBBloomFilterTest* dbtest) { - char buf[100]; - std::string keystr; - const int small_range_sstfiles = 5; - const int big_range_sstfiles = 5; - - // Generate 11 sst files with the following prefix ranges. - // GROUP 0: [0,10] (level 1) - // GROUP 1: [1,2], [2,3], [3,4], [4,5], [5, 6] (level 0) - // GROUP 2: [0,6], [0,7], [0,8], [0,9], [0,10] (level 0) - // - // A seek with the previous API would do 11 random I/Os (to all the - // files). With the new API and a prefix filter enabled, we should - // only do 2 random I/O, to the 2 files containing the key. - - // GROUP 0 - snprintf(buf, sizeof(buf), "%02d______:start", 0); - keystr = std::string(buf); - ASSERT_OK(dbtest->Put(keystr, keystr)); - snprintf(buf, sizeof(buf), "%02d______:end", 10); - keystr = std::string(buf); - ASSERT_OK(dbtest->Put(keystr, keystr)); - ASSERT_OK(dbtest->Flush()); - ASSERT_OK(dbtest->dbfull()->CompactRange(CompactRangeOptions(), nullptr, - nullptr)); // move to level 1 - - // GROUP 1 - for (int i = 1; i <= small_range_sstfiles; i++) { - snprintf(buf, sizeof(buf), "%02d______:start", i); - keystr = std::string(buf); - ASSERT_OK(dbtest->Put(keystr, keystr)); - snprintf(buf, sizeof(buf), "%02d______:end", i + 1); - keystr = std::string(buf); - ASSERT_OK(dbtest->Put(keystr, keystr)); - dbtest->Flush(); - } - - // GROUP 2 - for (int i = 1; i <= big_range_sstfiles; i++) { - snprintf(buf, sizeof(buf), "%02d______:start", 0); - keystr = std::string(buf); - ASSERT_OK(dbtest->Put(keystr, keystr)); - snprintf(buf, sizeof(buf), "%02d______:end", small_range_sstfiles + i + 1); - keystr = std::string(buf); - ASSERT_OK(dbtest->Put(keystr, keystr)); - dbtest->Flush(); - } -} -} // anonymous namespace - -TEST_F(DBBloomFilterTest, PrefixScan) { - while (ChangeFilterOptions()) { - int count; - Slice prefix; - Slice key; - char buf[100]; - Iterator* iter; - snprintf(buf, sizeof(buf), "03______:"); - prefix = Slice(buf, 8); - key = Slice(buf, 9); - ASSERT_EQ(key.difference_offset(prefix), 8); - ASSERT_EQ(prefix.difference_offset(key), 8); - // db configs - env_->count_random_reads_ = true; - Options options = CurrentOptions(); - options.env = env_; - options.prefix_extractor.reset(NewFixedPrefixTransform(8)); - options.disable_auto_compactions = true; - options.max_background_compactions = 2; - options.create_if_missing = true; - options.memtable_factory.reset(NewHashSkipListRepFactory(16)); - assert(!options.unordered_write); - // It is incompatible with allow_concurrent_memtable_write=false - options.allow_concurrent_memtable_write = false; - - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - table_options.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - // 11 RAND I/Os - DestroyAndReopen(options); - PrefixScanInit(this); - count = 0; - env_->random_read_counter_.Reset(); - iter = db_->NewIterator(ReadOptions()); - for (iter->Seek(prefix); iter->Valid(); iter->Next()) { - if (!iter->key().starts_with(prefix)) { - break; - } - count++; - } - ASSERT_OK(iter->status()); - delete iter; - ASSERT_EQ(count, 2); - ASSERT_EQ(env_->random_read_counter_.Read(), 2); - Close(); - } // end of while -} - -TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) { - Options options = CurrentOptions(); - options.write_buffer_size = 64 * 1024; - options.arena_block_size = 4 * 1024; - options.target_file_size_base = 64 * 1024; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 4; - options.max_bytes_for_level_base = 256 * 1024; - options.max_write_buffer_number = 2; - options.max_background_compactions = 8; - options.max_background_flushes = 8; - options.compression = kNoCompression; - options.compaction_style = kCompactionStyleLevel; - options.level_compaction_dynamic_level_bytes = true; - BlockBasedTableOptions bbto; - bbto.cache_index_and_filter_blocks = true; - bbto.filter_policy.reset(NewBloomFilterPolicy(10)); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.optimize_filters_for_hits = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - get_perf_context()->Reset(); - get_perf_context()->EnablePerLevelPerfContext(); - CreateAndReopenWithCF({"mypikachu"}, options); - - int numkeys = 200000; - - // Generate randomly shuffled keys, so the updates are almost - // random. - std::vector keys; - keys.reserve(numkeys); - for (int i = 0; i < numkeys; i += 2) { - keys.push_back(i); - } - RandomShuffle(std::begin(keys), std::end(keys), /*seed*/ 42); - int num_inserted = 0; - for (int key : keys) { - ASSERT_OK(Put(1, Key(key), "val")); - if (++num_inserted % 1000 == 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - } - ASSERT_OK(Put(1, Key(0), "val")); - ASSERT_OK(Put(1, Key(numkeys), "val")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - if (NumTableFilesAtLevel(0, 1) == 0) { - // No Level 0 file. Create one. - ASSERT_OK(Put(1, Key(0), "val")); - ASSERT_OK(Put(1, Key(numkeys), "val")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - for (int i = 1; i < numkeys; i += 2) { - ASSERT_EQ(Get(1, Key(i)), "NOT_FOUND"); - } - - ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L0)); - ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L1)); - ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L2_AND_UP)); - - // Now we have three sorted run, L0, L5 and L6 with most files in L6 have - // no bloom filter. Most keys be checked bloom filters twice. - ASSERT_GT(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 65000 * 2); - ASSERT_LT(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 120000 * 2); - uint64_t bloom_filter_useful_all_levels = 0; - for (auto& kv : (*(get_perf_context()->level_to_perf_context))) { - if (kv.second.bloom_filter_useful > 0) { - bloom_filter_useful_all_levels += kv.second.bloom_filter_useful; - } - } - ASSERT_GT(bloom_filter_useful_all_levels, 65000 * 2); - ASSERT_LT(bloom_filter_useful_all_levels, 120000 * 2); - - for (int i = 0; i < numkeys; i += 2) { - ASSERT_EQ(Get(1, Key(i)), "val"); - } - - // Part 2 (read path): rewrite last level with blooms, then verify they get - // cached only if !optimize_filters_for_hits - options.disable_auto_compactions = true; - options.num_levels = 9; - options.optimize_filters_for_hits = false; - options.statistics = CreateDBStatistics(); - bbto.block_cache.reset(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - ReopenWithColumnFamilies({"default", "mypikachu"}, options); - MoveFilesToLevel(7 /* level */, 1 /* column family index */); - - std::string value = Get(1, Key(0)); - uint64_t prev_cache_filter_hits = - TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT); - value = Get(1, Key(0)); - ASSERT_EQ(prev_cache_filter_hits + 1, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - - // Now that we know the filter blocks exist in the last level files, see if - // filter caching is skipped for this optimization - options.optimize_filters_for_hits = true; - options.statistics = CreateDBStatistics(); - bbto.block_cache.reset(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - ReopenWithColumnFamilies({"default", "mypikachu"}, options); - - value = Get(1, Key(0)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - ASSERT_EQ(2 /* index and data block */, - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - - // Check filter block ignored for files preloaded during DB::Open() - options.max_open_files = -1; - options.statistics = CreateDBStatistics(); - bbto.block_cache.reset(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - ReopenWithColumnFamilies({"default", "mypikachu"}, options); - - uint64_t prev_cache_filter_misses = - TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS); - prev_cache_filter_hits = TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT); - Get(1, Key(0)); - ASSERT_EQ(prev_cache_filter_misses, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(prev_cache_filter_hits, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - - // Check filter block ignored for file trivially-moved to bottom level - bbto.block_cache.reset(); - options.max_open_files = 100; // setting > -1 makes it not preload all files - options.statistics = CreateDBStatistics(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - ReopenWithColumnFamilies({"default", "mypikachu"}, options); - - ASSERT_OK(Put(1, Key(numkeys + 1), "val")); - ASSERT_OK(Flush(1)); - - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - CompactRangeOptions compact_options; - compact_options.bottommost_level_compaction = - BottommostLevelCompaction::kSkip; - compact_options.change_level = true; - compact_options.target_level = 7; - ASSERT_OK(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - prev_cache_filter_hits = TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT); - prev_cache_filter_misses = - TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS); - value = Get(1, Key(numkeys + 1)); - ASSERT_EQ(prev_cache_filter_hits, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - ASSERT_EQ(prev_cache_filter_misses, - TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - - // Check filter block not cached for iterator - bbto.block_cache.reset(); - options.statistics = CreateDBStatistics(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - ReopenWithColumnFamilies({"default", "mypikachu"}, options); - - std::unique_ptr iter(db_->NewIterator(ReadOptions(), handles_[1])); - iter->SeekToFirst(); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); - ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); - ASSERT_EQ(2 /* index and data block */, - TestGetTickerCount(options, BLOCK_CACHE_ADD)); - get_perf_context()->Reset(); -} - -int CountIter(std::unique_ptr& iter, const Slice& key) { - int count = 0; - for (iter->Seek(key); iter->Valid(); iter->Next()) { - count++; - } - EXPECT_OK(iter->status()); - return count; -} - -// use iterate_upper_bound to hint compatiability of existing bloom filters. -// The BF is considered compatible if 1) upper bound and seek key transform -// into the same string, or 2) the transformed seek key is of the same length -// as the upper bound and two keys are adjacent according to the comparator. -TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) { - for (const auto& bfp_impl : BloomLikeFilterPolicy::GetAllFixedImpls()) { - Options options; - options.create_if_missing = true; - options.env = CurrentOptions().env; - options.prefix_extractor.reset(NewCappedPrefixTransform(4)); - options.disable_auto_compactions = true; - options.statistics = CreateDBStatistics(); - // Enable prefix bloom for SST files - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy = Create(10, bfp_impl); - table_options.index_shortening = BlockBasedTableOptions:: - IndexShorteningMode::kShortenSeparatorsAndSuccessor; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - ASSERT_OK(Put("abcdxxx0", "val1")); - ASSERT_OK(Put("abcdxxx1", "val2")); - ASSERT_OK(Put("abcdxxx2", "val3")); - ASSERT_OK(Put("abcdxxx3", "val4")); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - { - // prefix_extractor has not changed, BF will always be read - Slice upper_bound("abce"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abcd0000"), 4); - } - { - Slice upper_bound("abcdzzzz"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abcd0000"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:5"}})); - ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(), - "rocksdb.FixedPrefix.5"); - { - // BF changed, [abcdxx00, abce) is a valid bound, will trigger BF read - Slice upper_bound("abce"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abcdxx00"), 4); - // should check bloom filter since upper bound meets requirement - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - { - // [abcdxx01, abcey) is not valid bound since upper bound is too long for - // the BF in SST (capped:4) - Slice upper_bound("abcey"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abcdxx01"), 4); - // should skip bloom filter since upper bound is too long - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - { - // [abcdxx02, abcdy) is a valid bound since the prefix is the same - Slice upper_bound("abcdy"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abcdxx02"), 4); - // should check bloom filter since upper bound matches transformed seek - // key - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - { - // [aaaaaaaa, abce) is not a valid bound since 1) they don't share the - // same prefix, 2) the prefixes are not consecutive - Slice upper_bound("abce"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "aaaaaaaa"), 0); - // should skip bloom filter since mismatch is found - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:3"}})); - { - // [abc, abd) is not a valid bound since the upper bound is too short - // for BF (capped:4) - Slice upper_bound("abd"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abc"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - // Same with re-open - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - Reopen(options); - { - Slice upper_bound("abd"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abc"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - // Set back to capped:4 and verify BF is always read - options.prefix_extractor.reset(NewCappedPrefixTransform(4)); - Reopen(options); - { - Slice upper_bound("abd"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abc"), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 5); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1); - } - // Same if there's a problem initally loading prefix transform - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Open::ForceNullTablePrefixExtractor", - [&](void* arg) { *static_cast(arg) = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); - { - Slice upper_bound("abd"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "abc"), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2); - } - SyncPoint::GetInstance()->DisableProcessing(); - } -} - -// Create multiple SST files each with a different prefix_extractor config, -// verify iterators can read all SST files using the latest config. -TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) { - for (const auto& bfp_impl : BloomLikeFilterPolicy::GetAllFixedImpls()) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - options.disable_auto_compactions = true; - options.statistics = CreateDBStatistics(); - // Enable prefix bloom for SST files - BlockBasedTableOptions table_options; - table_options.filter_policy = Create(10, bfp_impl); - table_options.cache_index_and_filter_blocks = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - Slice upper_bound("foz90000"); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - - // first SST with fixed:1 BF - ASSERT_OK(Put("foo2", "bar2")); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Put("foq1", "bar1")); - ASSERT_OK(Put("fpa", "0")); - dbfull()->Flush(FlushOptions()); - std::unique_ptr iter_old(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter_old, "foo"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 1); - - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}})); - ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(), - "rocksdb.CappedPrefix.3"); - read_options.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "foo"), 2); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2); - ASSERT_EQ(CountIter(iter, "gpk"), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - - // second SST with capped:3 BF - ASSERT_OK(Put("foo3", "bar3")); - ASSERT_OK(Put("foo4", "bar4")); - ASSERT_OK(Put("foq5", "bar5")); - ASSERT_OK(Put("fpb", "1")); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - { - // BF is cappped:3 now - std::unique_ptr iter_tmp(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter_tmp, "foo"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0); - // both counters are incremented because BF is "not changed" for 1 of the - // 2 SST files, so filter is checked once and found no match. - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 5); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1); - } - - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}})); - ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(), - "rocksdb.FixedPrefix.2"); - // third SST with fixed:2 BF - ASSERT_OK(Put("foo6", "bar6")); - ASSERT_OK(Put("foo7", "bar7")); - ASSERT_OK(Put("foq8", "bar8")); - ASSERT_OK(Put("fpc", "2")); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - { - // BF is fixed:2 now - std::unique_ptr iter_tmp(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter_tmp, "foo"), 9); - // the first and last BF are checked - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 7); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1); - ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0); - // only last BF is checked and not found - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 8); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2); - } - - // iter_old can only see the first SST, so checked plus 1 - ASSERT_EQ(CountIter(iter_old, "foo"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9); - // iter was created after the first setoptions call so only full filter - // will check the filter - ASSERT_EQ(CountIter(iter, "foo"), 2); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 10); - - { - // keys in all three SSTs are visible to iterator - // The range of [foo, foz90000] is compatible with (fixed:1) and (fixed:2) - // so +2 for checked counter - std::unique_ptr iter_all(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter_all, "foo"), 9); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2); - ASSERT_EQ(CountIter(iter_all, "gpk"), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 13); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3); - } - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}})); - ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(), - "rocksdb.CappedPrefix.3"); - { - std::unique_ptr iter_all(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter_all, "foo"), 6); - // all three SST are checked because the current options has the same as - // the remaining SST (capped:3) - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 16); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3); - ASSERT_EQ(CountIter(iter_all, "gpk"), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 17); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 4); - } - // TODO(Zhongyi): Maybe also need to add Get calls to test point look up? - } -} - -// Create a new column family in a running DB, change prefix_extractor -// dynamically, verify the iterator created on the new column family behaves -// as expected -TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) { - int iteration = 0; - for (const auto& bfp_impl : BloomLikeFilterPolicy::GetAllFixedImpls()) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - options.disable_auto_compactions = true; - options.statistics = CreateDBStatistics(); - // Enable prefix bloom for SST files - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy = Create(10, bfp_impl); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - CreateAndReopenWithCF({"pikachu" + std::to_string(iteration)}, options); - ReadOptions read_options; - read_options.prefix_same_as_start = true; - // create a new CF and set prefix_extractor dynamically - options.prefix_extractor.reset(NewCappedPrefixTransform(3)); - CreateColumnFamilies({"ramen_dojo_" + std::to_string(iteration)}, options); - ASSERT_EQ(dbfull()->GetOptions(handles_[2]).prefix_extractor->AsString(), - "rocksdb.CappedPrefix.3"); - ASSERT_OK(Put(2, "foo3", "bar3")); - ASSERT_OK(Put(2, "foo4", "bar4")); - ASSERT_OK(Put(2, "foo5", "bar5")); - ASSERT_OK(Put(2, "foq6", "bar6")); - ASSERT_OK(Put(2, "fpq7", "bar7")); - dbfull()->Flush(FlushOptions()); - { - std::unique_ptr iter( - db_->NewIterator(read_options, handles_[2])); - ASSERT_EQ(CountIter(iter, "foo"), 3); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - ASSERT_OK( - dbfull()->SetOptions(handles_[2], {{"prefix_extractor", "fixed:2"}})); - ASSERT_EQ(dbfull()->GetOptions(handles_[2]).prefix_extractor->AsString(), - "rocksdb.FixedPrefix.2"); - { - std::unique_ptr iter( - db_->NewIterator(read_options, handles_[2])); - ASSERT_EQ(CountIter(iter, "foo"), 4); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - ASSERT_OK(dbfull()->DropColumnFamily(handles_[2])); - ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[2])); - handles_[2] = nullptr; - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1])); - handles_[1] = nullptr; - iteration++; - } -} - -// Verify it's possible to change prefix_extractor at runtime and iterators -// behaves as expected -TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) { - for (const auto& bfp_impl : BloomLikeFilterPolicy::GetAllFixedImpls()) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - options.disable_auto_compactions = true; - options.statistics = CreateDBStatistics(); - // Enable prefix bloom for SST files - BlockBasedTableOptions table_options; - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy = Create(10, bfp_impl); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - ASSERT_OK(Put("foo2", "bar2")); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("fpa", "0")); - dbfull()->Flush(FlushOptions()); - ASSERT_OK(Put("foo3", "bar3")); - ASSERT_OK(Put("foo4", "bar4")); - ASSERT_OK(Put("foo5", "bar5")); - ASSERT_OK(Put("fpb", "1")); - dbfull()->Flush(FlushOptions()); - ASSERT_OK(Put("foo6", "bar6")); - ASSERT_OK(Put("foo7", "bar7")); - ASSERT_OK(Put("foo8", "bar8")); - ASSERT_OK(Put("fpc", "2")); - dbfull()->Flush(FlushOptions()); - - ReadOptions read_options; - read_options.prefix_same_as_start = true; - { - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter, "foo"), 12); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - std::unique_ptr iter_old(db_->NewIterator(read_options)); - ASSERT_EQ(CountIter(iter_old, "foo"), 12); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}})); - ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(), - "rocksdb.CappedPrefix.3"); - { - std::unique_ptr iter(db_->NewIterator(read_options)); - // "fp*" should be skipped - ASSERT_EQ(CountIter(iter, "foo"), 9); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - } - - // iterator created before should not be affected and see all keys - ASSERT_EQ(CountIter(iter_old, "foo"), 12); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); - ASSERT_EQ(CountIter(iter_old, "abc"), 0); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12); - ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3); - } -} - -TEST_F(DBBloomFilterTest, SeekForPrevWithPartitionedFilters) { - Options options = CurrentOptions(); - constexpr size_t kNumKeys = 10000; - static_assert(kNumKeys <= 10000, "kNumKeys have to be <= 10000"); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeys + 10)); - options.create_if_missing = true; - constexpr size_t kPrefixLength = 4; - options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixLength)); - options.compression = kNoCompression; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(50)); - bbto.index_shortening = - BlockBasedTableOptions::IndexShorteningMode::kNoShortening; - bbto.block_size = 128; - bbto.metadata_block_size = 128; - bbto.partition_filters = true; - bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - const std::string value(64, '\0'); - - WriteOptions write_opts; - write_opts.disableWAL = true; - for (size_t i = 0; i < kNumKeys; ++i) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(4) << std::fixed << i; - ASSERT_OK(db_->Put(write_opts, oss.str(), value)); - } - ASSERT_OK(Flush()); - - ReadOptions read_opts; - // Use legacy, implicit prefix seek - read_opts.total_order_seek = false; - read_opts.auto_prefix_mode = false; - std::unique_ptr it(db_->NewIterator(read_opts)); - for (size_t i = 0; i < kNumKeys; ++i) { - // Seek with a key after each one added but with same prefix. One will - // surely cross a partition boundary. - std::ostringstream oss; - oss << std::setfill('0') << std::setw(4) << std::fixed << i << "a"; - it->SeekForPrev(oss.str()); - ASSERT_OK(it->status()); - ASSERT_TRUE(it->Valid()); - } - it.reset(); -} - -namespace { -class BackwardBytewiseComparator : public Comparator { - public: - const char* Name() const override { return "BackwardBytewiseComparator"; } - - int Compare(const Slice& a, const Slice& b) const override { - int min_size_neg = -static_cast(std::min(a.size(), b.size())); - const char* a_end = a.data() + a.size(); - const char* b_end = b.data() + b.size(); - for (int i = -1; i >= min_size_neg; --i) { - if (a_end[i] != b_end[i]) { - if (static_cast(a_end[i]) < - static_cast(b_end[i])) { - return -1; - } else { - return 1; - } - } - } - return static_cast(a.size()) - static_cast(b.size()); - } - - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; - -const BackwardBytewiseComparator kBackwardBytewiseComparator{}; - -class FixedSuffix4Transform : public SliceTransform { - const char* Name() const override { return "FixedSuffixTransform"; } - - Slice Transform(const Slice& src) const override { - return Slice(src.data() + src.size() - 4, 4); - } - - bool InDomain(const Slice& src) const override { return src.size() >= 4; } -}; - -std::pair GetBloomStat(const Options& options, bool sst) { - if (sst) { - return { - options.statistics->getAndResetTickerCount(BLOOM_FILTER_PREFIX_CHECKED), - options.statistics->getAndResetTickerCount(BLOOM_FILTER_PREFIX_USEFUL)}; - } else { - auto hit = std::exchange(get_perf_context()->bloom_memtable_hit_count, 0); - auto miss = std::exchange(get_perf_context()->bloom_memtable_miss_count, 0); - return {hit + miss, miss}; - } -} - -std::pair CheckedAndUseful(uint64_t checked, - uint64_t useful) { - return {checked, useful}; -} -} // anonymous namespace - -// This uses a prefix_extractor + comparator combination that violates -// one of the old obsolete, unnecessary axioms of prefix extraction: -// * key.starts_with(prefix(key)) -// This axiom is not really needed, and we validate that here. -TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter1) { - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); - bbto.whole_key_filtering = false; - - Options options = CurrentOptions(); - options.comparator = &kBackwardBytewiseComparator; - options.prefix_extractor = std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.memtable_prefix_bloom_size_ratio = 0.1; - options.statistics = CreateDBStatistics(); - - DestroyAndReopen(options); - - ASSERT_OK(Put("321aaaa", "val1")); - ASSERT_OK(Put("112aaaa", "val2")); - ASSERT_OK(Put("009aaaa", "val3")); - ASSERT_OK(Put("baa", "val4")); // out of domain - ASSERT_OK(Put("321abaa", "val5")); - ASSERT_OK(Put("zzz", "val6")); // out of domain - - for (auto flushed : {false, true}) { - SCOPED_TRACE("flushed=" + std::to_string(flushed)); - if (flushed) { - ASSERT_OK(Flush()); - } - ReadOptions read_options; - if (flushed) { // TODO: support auto_prefix_mode in memtable? - read_options.auto_prefix_mode = true; - } - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - { - Slice ub("999aaaa"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaaa"), 3); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - Slice ub("999abaa"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "abaa"), 1); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - Slice ub("999acaa"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "acaa"), 0); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 1)); - } - { - Slice ub("zzzz"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "baa"), 3); - if (flushed) { // TODO: fix memtable case - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - } - } - } -} - -// This uses a prefix_extractor + comparator combination that violates -// one of the old obsolete, unnecessary axioms of prefix extraction: -// * Compare(prefix(key), key) <= 0 -// This axiom is not really needed, and we validate that here. -TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter2) { - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); - bbto.whole_key_filtering = false; - - Options options = CurrentOptions(); - options.comparator = ReverseBytewiseComparator(); - options.prefix_extractor.reset(NewFixedPrefixTransform(4)); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.memtable_prefix_bloom_size_ratio = 0.1; - options.statistics = CreateDBStatistics(); - - DestroyAndReopen(options); - - ASSERT_OK(Put("aaaa123", "val1")); - ASSERT_OK(Put("aaaa211", "val2")); - ASSERT_OK(Put("aaaa900", "val3")); - ASSERT_OK(Put("aab", "val4")); // out of domain - ASSERT_OK(Put("aaba123", "val5")); - ASSERT_OK(Put("qqqq123", "val7")); - ASSERT_OK(Put("qqqq", "val8")); - ASSERT_OK(Put("zzz", "val8")); // out of domain - - for (auto flushed : {false, true}) { - SCOPED_TRACE("flushed=" + std::to_string(flushed)); - if (flushed) { - ASSERT_OK(Flush()); - } - ReadOptions read_options; - if (flushed) { // TODO: support auto_prefix_mode in memtable? - read_options.auto_prefix_mode = true; - } else { - // TODO: why needed? - get_perf_context()->bloom_memtable_hit_count = 0; - get_perf_context()->bloom_memtable_miss_count = 0; - } - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - { - Slice ub("aaaa000"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaaa999"), 3); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - // Note: prefix does work as upper bound - Slice ub("aaaa"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaaa999"), 3); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - // Note: prefix does not work here as seek key - Slice ub("aaaa500"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaaa"), 0); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - Slice ub("aaba000"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaba999"), 1); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - Slice ub("aaca000"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaca999"), 0); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 1)); - } - { - Slice ub("aaaz"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "zzz"), 5); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - } - { - // Note: prefix does work here as seek key, but only finds key equal - // to prefix (others with same prefix are less) - read_options.auto_prefix_mode = false; - read_options.iterate_upper_bound = nullptr; - read_options.prefix_same_as_start = true; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "qqqq"), 1); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - } -} - -namespace { -// A weird comparator that in combination with NonIdempotentFixed4Transform -// breaks an old axiom of prefix filtering. -class WeirdComparator : public Comparator { - public: - const char* Name() const override { return "WeirdComparator"; } - - int Compare(const Slice& a, const Slice& b) const override { - bool a_in = a.size() >= 5; - bool b_in = b.size() >= 5; - if (a_in != b_in) { - // Order keys after prefixes - return a_in - b_in; - } - if (a_in) { - return BytewiseComparator()->Compare(a, b); - } else { - // Different ordering on the prefixes - return ReverseBytewiseComparator()->Compare(a, b); - } - } - - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; -const WeirdComparator kWeirdComparator{}; - -// Non-idempotentent because prefix is always 4 bytes, but this is -// out-of-domain for keys to be assigned prefixes (>= 5 bytes) -class NonIdempotentFixed4Transform : public SliceTransform { - const char* Name() const override { return "NonIdempotentFixed4Transform"; } - - Slice Transform(const Slice& src) const override { - return Slice(src.data(), 4); - } - - bool InDomain(const Slice& src) const override { return src.size() >= 5; } -}; -} // anonymous namespace - -// This uses a prefix_extractor + comparator combination that violates -// two of the old obsolete, unnecessary axioms of prefix extraction: -// * prefix(prefix(key)) == prefix(key) -// * If Compare(k1, k2) <= 0, then Compare(prefix(k1), prefix(k2)) <= 0 -// This axiom is not really needed, and we validate that here. -TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) { - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); - bbto.whole_key_filtering = false; - - Options options = CurrentOptions(); - options.prefix_extractor = std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.memtable_prefix_bloom_size_ratio = 0.1; - options.statistics = CreateDBStatistics(); - - for (auto weird_comparator : {false, true}) { - if (weird_comparator) { - options.comparator = &kWeirdComparator; - } - DestroyAndReopen(options); - - ASSERT_OK(Put("aaaa123", "val1")); - ASSERT_OK(Put("aaaa211", "val2")); - ASSERT_OK(Put("aaaa900", "val3")); - ASSERT_OK(Put("aab", "val4")); // out of domain - ASSERT_OK(Put("aaba123", "val5")); - ASSERT_OK(Put("qqqq123", "val7")); - ASSERT_OK(Put("qqqq", "val8")); // out of domain - ASSERT_OK(Put("zzzz", "val8")); // out of domain - - for (auto flushed : {false, true}) { - SCOPED_TRACE("flushed=" + std::to_string(flushed)); - if (flushed) { - ASSERT_OK(Flush()); - } - ReadOptions read_options; - if (flushed) { // TODO: support auto_prefix_mode in memtable? - read_options.auto_prefix_mode = true; - } else { - // TODO: why needed? - get_perf_context()->bloom_memtable_hit_count = 0; - get_perf_context()->bloom_memtable_miss_count = 0; - } - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - { - Slice ub("aaaa999"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaaa000"), 3); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - // Note: prefix as seek key is not bloom-optimized - // Note: the count works with weird_comparator because "aaaa" is - // ordered as the last of the prefixes - Slice ub("aaaa999"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaaa"), 3); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - } - { - Slice ub("aaba9"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaba0"), 1); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - Slice ub("aaca9"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aaca0"), 0); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 1)); - } - { - Slice ub("qqqq9"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "qqqq0"), 1); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0)); - } - { - // Note: prefix as seek key is not bloom-optimized - Slice ub("qqqq9"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "qqqq"), weird_comparator ? 7 : 2); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - } - { - // Note: prefix as seek key is not bloom-optimized - Slice ub("zzzz9"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "zzzz"), weird_comparator ? 8 : 1); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - } - { - Slice ub("zzzz9"); - read_options.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(read_options)); - EXPECT_EQ(CountIter(iter, "aab"), weird_comparator ? 6 : 5); - EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0)); - } - } - } -} - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_compaction_filter_test.cc b/db/db_compaction_filter_test.cc deleted file mode 100644 index 0b3f3dedc..000000000 --- a/db/db_compaction_filter_test.cc +++ /dev/null @@ -1,1030 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "port/stack_trace.h" - -namespace ROCKSDB_NAMESPACE { - -static int cfilter_count = 0; -static int cfilter_skips = 0; - -// This is a static filter used for filtering -// kvs during the compaction process. -static std::string NEW_VALUE = "NewValue"; - -class DBTestCompactionFilter : public DBTestBase { - public: - DBTestCompactionFilter() - : DBTestBase("db_compaction_filter_test", /*env_do_fsync=*/true) {} -}; - -// Param variant of DBTestBase::ChangeCompactOptions -class DBTestCompactionFilterWithCompactParam - : public DBTestCompactionFilter, - public ::testing::WithParamInterface { - public: - DBTestCompactionFilterWithCompactParam() : DBTestCompactionFilter() { - option_config_ = GetParam(); - Destroy(last_options_); - auto options = CurrentOptions(); - if (option_config_ == kDefault || option_config_ == kUniversalCompaction || - option_config_ == kUniversalCompactionMultiLevel) { - options.create_if_missing = true; - } - if (option_config_ == kLevelSubcompactions || - option_config_ == kUniversalSubcompactions) { - assert(options.max_subcompactions > 1); - } - Reopen(options); - } -}; - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -INSTANTIATE_TEST_CASE_P( - CompactionFilterWithOption, DBTestCompactionFilterWithCompactParam, - ::testing::Values(DBTestBase::OptionConfig::kDefault, - DBTestBase::OptionConfig::kUniversalCompaction, - DBTestBase::OptionConfig::kUniversalCompactionMultiLevel, - DBTestBase::OptionConfig::kLevelSubcompactions, - DBTestBase::OptionConfig::kUniversalSubcompactions)); -#else -// Run fewer cases in non-full valgrind to save time. -INSTANTIATE_TEST_CASE_P(CompactionFilterWithOption, - DBTestCompactionFilterWithCompactParam, - ::testing::Values(DBTestBase::OptionConfig::kDefault)); -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -class KeepFilter : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - cfilter_count++; - return false; - } - - const char* Name() const override { return "KeepFilter"; } -}; - -class DeleteFilter : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - cfilter_count++; - return true; - } - - bool FilterMergeOperand(int /*level*/, const Slice& /*key*/, - const Slice& /*operand*/) const override { - return true; - } - - const char* Name() const override { return "DeleteFilter"; } -}; - -class DeleteISFilter : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - cfilter_count++; - int i = std::stoi(key.ToString()); - if (i > 5 && i <= 105) { - return true; - } - return false; - } - - bool IgnoreSnapshots() const override { return true; } - - const char* Name() const override { return "DeleteFilter"; } -}; - -// Skip x if floor(x/10) is even, use range skips. Requires that keys are -// zero-padded to length 10. -class SkipEvenFilter : public CompactionFilter { - public: - Decision FilterV2(int /*level*/, const Slice& key, ValueType /*value_type*/, - const Slice& /*existing_value*/, std::string* /*new_value*/, - std::string* skip_until) const override { - cfilter_count++; - int i = std::stoi(key.ToString()); - if (i / 10 % 2 == 0) { - char key_str[100]; - snprintf(key_str, sizeof(key_str), "%010d", i / 10 * 10 + 10); - *skip_until = key_str; - ++cfilter_skips; - return Decision::kRemoveAndSkipUntil; - } - return Decision::kKeep; - } - - bool IgnoreSnapshots() const override { return true; } - - const char* Name() const override { return "DeleteFilter"; } -}; - -class ConditionalFilter : public CompactionFilter { - public: - explicit ConditionalFilter(const std::string* filtered_value) - : filtered_value_(filtered_value) {} - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& value, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - return value.ToString() == *filtered_value_; - } - - const char* Name() const override { return "ConditionalFilter"; } - - private: - const std::string* filtered_value_; -}; - -class ChangeFilter : public CompactionFilter { - public: - explicit ChangeFilter() {} - - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* new_value, bool* value_changed) const override { - assert(new_value != nullptr); - *new_value = NEW_VALUE; - *value_changed = true; - return false; - } - - const char* Name() const override { return "ChangeFilter"; } -}; - -class KeepFilterFactory : public CompactionFilterFactory { - public: - explicit KeepFilterFactory(bool check_context = false, - bool check_context_cf_id = false) - : check_context_(check_context), - check_context_cf_id_(check_context_cf_id), - compaction_filter_created_(false) {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - if (check_context_) { - EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction); - EXPECT_EQ(expect_manual_compaction_.load(), context.is_manual_compaction); - } - if (check_context_cf_id_) { - EXPECT_EQ(expect_cf_id_.load(), context.column_family_id); - } - compaction_filter_created_ = true; - return std::unique_ptr(new KeepFilter()); - } - - bool compaction_filter_created() const { return compaction_filter_created_; } - - const char* Name() const override { return "KeepFilterFactory"; } - bool check_context_; - bool check_context_cf_id_; - std::atomic_bool expect_full_compaction_; - std::atomic_bool expect_manual_compaction_; - std::atomic expect_cf_id_; - bool compaction_filter_created_; -}; - -// This filter factory is configured with a `TableFileCreationReason`. Only -// table files created for that reason will undergo filtering. This -// configurability makes it useful to tests for filtering non-compaction table -// files, such as "CompactionFilterFlush" and "CompactionFilterRecovery". -class DeleteFilterFactory : public CompactionFilterFactory { - public: - explicit DeleteFilterFactory(TableFileCreationReason reason) - : reason_(reason) {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - EXPECT_EQ(reason_, context.reason); - if (context.reason == TableFileCreationReason::kCompaction && - !context.is_manual_compaction) { - // Table files created by automatic compaction do not undergo filtering. - // Presumably some tests rely on this. - return std::unique_ptr(nullptr); - } - return std::unique_ptr(new DeleteFilter()); - } - - bool ShouldFilterTableFileCreation( - TableFileCreationReason reason) const override { - return reason_ == reason; - } - - const char* Name() const override { return "DeleteFilterFactory"; } - - private: - const TableFileCreationReason reason_; -}; - -// Delete Filter Factory which ignores snapshots -class DeleteISFilterFactory : public CompactionFilterFactory { - public: - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - if (context.is_manual_compaction) { - return std::unique_ptr(new DeleteISFilter()); - } else { - return std::unique_ptr(nullptr); - } - } - - const char* Name() const override { return "DeleteFilterFactory"; } -}; - -class SkipEvenFilterFactory : public CompactionFilterFactory { - public: - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - if (context.is_manual_compaction) { - return std::unique_ptr(new SkipEvenFilter()); - } else { - return std::unique_ptr(nullptr); - } - } - - const char* Name() const override { return "SkipEvenFilterFactory"; } -}; - -class ConditionalFilterFactory : public CompactionFilterFactory { - public: - explicit ConditionalFilterFactory(const Slice& filtered_value) - : filtered_value_(filtered_value.ToString()) {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& /*context*/) override { - return std::unique_ptr( - new ConditionalFilter(&filtered_value_)); - } - - const char* Name() const override { return "ConditionalFilterFactory"; } - - private: - std::string filtered_value_; -}; - -class ChangeFilterFactory : public CompactionFilterFactory { - public: - explicit ChangeFilterFactory() {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& /*context*/) override { - return std::unique_ptr(new ChangeFilter()); - } - - const char* Name() const override { return "ChangeFilterFactory"; } -}; - -TEST_F(DBTestCompactionFilter, CompactionFilter) { - Options options = CurrentOptions(); - options.max_open_files = -1; - options.num_levels = 3; - options.compaction_filter_factory = std::make_shared(); - options = CurrentOptions(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Write 100K keys, these are written to a few files in L0. - const std::string value(10, 'x'); - for (int i = 0; i < 100000; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - ASSERT_OK(Put(1, key, value)); - } - ASSERT_OK(Flush(1)); - - // Push all files to the highest level L2. Verify that - // the compaction is each level invokes the filter for - // all the keys in that level. - cfilter_count = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_EQ(cfilter_count, 100000); - cfilter_count = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - ASSERT_EQ(cfilter_count, 100000); - - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0); - ASSERT_NE(NumTableFilesAtLevel(2, 1), 0); - cfilter_count = 0; - - // All the files are in the lowest level. - // Verify that all but the 100001st record - // has sequence number zero. The 100001st record - // is at the tip of this snapshot and cannot - // be zeroed out. - int count = 0; - int total = 0; - Arena arena; - { - InternalKeyComparator icmp(options.comparator); - ReadOptions read_options; - ScopedArenaIterator iter(dbfull()->NewInternalIterator( - read_options, &arena, kMaxSequenceNumber, handles_[1])); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - while (iter->Valid()) { - ParsedInternalKey ikey(Slice(), 0, kTypeValue); - ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */)); - total++; - if (ikey.sequence != 0) { - count++; - } - iter->Next(); - } - ASSERT_OK(iter->status()); - } - ASSERT_EQ(total, 100000); - ASSERT_EQ(count, 0); - - // overwrite all the 100K keys once again. - for (int i = 0; i < 100000; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - ASSERT_OK(Put(1, key, value)); - } - ASSERT_OK(Flush(1)); - - // push all files to the highest level L2. This - // means that all keys should pass at least once - // via the compaction filter - cfilter_count = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_EQ(cfilter_count, 100000); - cfilter_count = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - ASSERT_EQ(cfilter_count, 100000); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0); - ASSERT_NE(NumTableFilesAtLevel(2, 1), 0); - - // create a new database with the compaction - // filter in such a way that it deletes all keys - options.compaction_filter_factory = std::make_shared( - TableFileCreationReason::kCompaction); - options.create_if_missing = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // write all the keys once again. - for (int i = 0; i < 100000; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - ASSERT_OK(Put(1, key, value)); - } - ASSERT_OK(Flush(1)); - ASSERT_NE(NumTableFilesAtLevel(0, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2, 1), 0); - - // Push all files to the highest level L2. This - // triggers the compaction filter to delete all keys, - // verify that at the end of the compaction process, - // nothing is left. - cfilter_count = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_EQ(cfilter_count, 100000); - cfilter_count = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - ASSERT_EQ(cfilter_count, 0); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0); - - { - // Scan the entire database to ensure that nothing is left - std::unique_ptr iter( - db_->NewIterator(ReadOptions(), handles_[1])); - iter->SeekToFirst(); - count = 0; - while (iter->Valid()) { - count++; - iter->Next(); - } - ASSERT_OK(iter->status()); - ASSERT_EQ(count, 0); - } - - // The sequence number of the remaining record - // is not zeroed out even though it is at the - // level Lmax because this record is at the tip - count = 0; - { - InternalKeyComparator icmp(options.comparator); - ReadOptions read_options; - ScopedArenaIterator iter(dbfull()->NewInternalIterator( - read_options, &arena, kMaxSequenceNumber, handles_[1])); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - while (iter->Valid()) { - ParsedInternalKey ikey(Slice(), 0, kTypeValue); - ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */)); - ASSERT_NE(ikey.sequence, (unsigned)0); - count++; - iter->Next(); - } - ASSERT_EQ(count, 0); - } -} - -// Tests the edge case where compaction does not produce any output -- all -// entries are deleted. The compaction should create bunch of 'DeleteFile' -// entries in VersionEdit, but none of the 'AddFile's. -TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) { - Options options = CurrentOptions(); - options.compaction_filter_factory = std::make_shared( - TableFileCreationReason::kCompaction); - options.disable_auto_compactions = true; - options.create_if_missing = true; - DestroyAndReopen(options); - - // put some data - for (int table = 0; table < 4; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK(Put(std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(Flush()); - } - - // this will produce empty file (delete compaction filter) - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(0U, CountLiveFiles()); - - Reopen(options); - - Iterator* itr = db_->NewIterator(ReadOptions()); - itr->SeekToFirst(); - ASSERT_OK(itr->status()); - // empty db - ASSERT_TRUE(!itr->Valid()); - - delete itr; -} - -TEST_F(DBTestCompactionFilter, CompactionFilterFlush) { - // Tests a `CompactionFilterFactory` that filters when table file is created - // by flush. - Options options = CurrentOptions(); - options.compaction_filter_factory = - std::make_shared(TableFileCreationReason::kFlush); - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - Reopen(options); - - // Puts and Merges are purged in flush. - ASSERT_OK(Put("a", "v")); - ASSERT_OK(Merge("b", "v")); - ASSERT_OK(Flush()); - ASSERT_EQ("NOT_FOUND", Get("a")); - ASSERT_EQ("NOT_FOUND", Get("b")); - - // However, Puts and Merges are preserved by recovery. - ASSERT_OK(Put("a", "v")); - ASSERT_OK(Merge("b", "v")); - Reopen(options); - ASSERT_EQ("v", Get("a")); - ASSERT_EQ("v", Get("b")); - - // Likewise, compaction does not apply filtering. - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("v", Get("a")); - ASSERT_EQ("v", Get("b")); -} - -TEST_F(DBTestCompactionFilter, CompactionFilterRecovery) { - // Tests a `CompactionFilterFactory` that filters when table file is created - // by recovery. - Options options = CurrentOptions(); - options.compaction_filter_factory = - std::make_shared(TableFileCreationReason::kRecovery); - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - Reopen(options); - - // Puts and Merges are purged in recovery. - ASSERT_OK(Put("a", "v")); - ASSERT_OK(Merge("b", "v")); - Reopen(options); - ASSERT_EQ("NOT_FOUND", Get("a")); - ASSERT_EQ("NOT_FOUND", Get("b")); - - // However, Puts and Merges are preserved by flush. - ASSERT_OK(Put("a", "v")); - ASSERT_OK(Merge("b", "v")); - ASSERT_OK(Flush()); - ASSERT_EQ("v", Get("a")); - ASSERT_EQ("v", Get("b")); - - // Likewise, compaction does not apply filtering. - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("v", Get("a")); - ASSERT_EQ("v", Get("b")); -} - -TEST_P(DBTestCompactionFilterWithCompactParam, - CompactionFilterWithValueChange) { - Options options = CurrentOptions(); - options.num_levels = 3; - options.compaction_filter_factory = std::make_shared(); - CreateAndReopenWithCF({"pikachu"}, options); - - // Write 100K+1 keys, these are written to a few files - // in L0. We do this so that the current snapshot points - // to the 100001 key.The compaction filter is not invoked - // on keys that are visible via a snapshot because we - // anyways cannot delete it. - const std::string value(10, 'x'); - for (int i = 0; i < 100001; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - ASSERT_OK(Put(1, key, value)); - } - - // push all files to lower levels - ASSERT_OK(Flush(1)); - if (option_config_ != kUniversalCompactionMultiLevel && - option_config_ != kUniversalSubcompactions) { - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - } else { - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - } - - // re-write all data again - for (int i = 0; i < 100001; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - ASSERT_OK(Put(1, key, value)); - } - - // push all files to lower levels. This should - // invoke the compaction filter for all 100000 keys. - ASSERT_OK(Flush(1)); - if (option_config_ != kUniversalCompactionMultiLevel && - option_config_ != kUniversalSubcompactions) { - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - } else { - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - } - - // verify that all keys now have the new value that - // was set by the compaction process. - for (int i = 0; i < 100001; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - std::string newvalue = Get(1, key); - ASSERT_EQ(newvalue.compare(NEW_VALUE), 0); - } -} - -TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) { - std::string one, two, three, four; - PutFixed64(&one, 1); - PutFixed64(&two, 2); - PutFixed64(&three, 3); - PutFixed64(&four, 4); - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreateUInt64AddOperator(); - options.num_levels = 3; - // Filter out keys with value is 2. - options.compaction_filter_factory = - std::make_shared(two); - DestroyAndReopen(options); - - // In the same compaction, a value type needs to be deleted based on - // compaction filter, and there is a merge type for the key. compaction - // filter result is ignored. - ASSERT_OK(db_->Put(WriteOptions(), "foo", two)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->Merge(WriteOptions(), "foo", one)); - ASSERT_OK(Flush()); - std::string newvalue = Get("foo"); - ASSERT_EQ(newvalue, three); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - newvalue = Get("foo"); - ASSERT_EQ(newvalue, three); - - // value key can be deleted based on compaction filter, leaving only - // merge keys. - ASSERT_OK(db_->Put(WriteOptions(), "bar", two)); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - newvalue = Get("bar"); - ASSERT_EQ("NOT_FOUND", newvalue); - ASSERT_OK(db_->Merge(WriteOptions(), "bar", two)); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - newvalue = Get("bar"); - ASSERT_EQ(two, two); - - // Compaction filter never applies to merge keys. - ASSERT_OK(db_->Put(WriteOptions(), "foobar", one)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->Merge(WriteOptions(), "foobar", two)); - ASSERT_OK(Flush()); - newvalue = Get("foobar"); - ASSERT_EQ(newvalue, three); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - newvalue = Get("foobar"); - ASSERT_EQ(newvalue, three); - - // In the same compaction, both of value type and merge type keys need to be - // deleted based on compaction filter, and there is a merge type for the key. - // For both keys, compaction filter results are ignored. - ASSERT_OK(db_->Put(WriteOptions(), "barfoo", two)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->Merge(WriteOptions(), "barfoo", two)); - ASSERT_OK(Flush()); - newvalue = Get("barfoo"); - ASSERT_EQ(newvalue, four); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - newvalue = Get("barfoo"); - ASSERT_EQ(newvalue, four); -} - -TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) { - KeepFilterFactory* filter = new KeepFilterFactory(true, true); - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_filter_factory.reset(filter); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = 8; - Reopen(options); - int num_keys_per_file = 400; - for (int j = 0; j < 3; j++) { - // Write several keys. - const std::string value(10, 'x'); - for (int i = 0; i < num_keys_per_file; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%08d%02d", i, j); - ASSERT_OK(Put(key, value)); - } - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - // Make sure next file is much smaller so automatic compaction will not - // be triggered. - num_keys_per_file /= 2; - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Force a manual compaction - cfilter_count = 0; - filter->expect_manual_compaction_.store(true); - filter->expect_full_compaction_.store(true); - filter->expect_cf_id_.store(0); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(cfilter_count, 700); - ASSERT_EQ(NumSortedRuns(0), 1); - ASSERT_TRUE(filter->compaction_filter_created()); - - // Verify total number of keys is correct after manual compaction. - { - int count = 0; - int total = 0; - Arena arena; - InternalKeyComparator icmp(options.comparator); - ReadOptions read_options; - ScopedArenaIterator iter(dbfull()->NewInternalIterator(read_options, &arena, - kMaxSequenceNumber)); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - while (iter->Valid()) { - ParsedInternalKey ikey(Slice(), 0, kTypeValue); - ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */)); - total++; - if (ikey.sequence != 0) { - count++; - } - iter->Next(); - } - ASSERT_EQ(total, 700); - ASSERT_EQ(count, 0); - } -} - -TEST_F(DBTestCompactionFilter, CompactionFilterContextCfId) { - KeepFilterFactory* filter = new KeepFilterFactory(false, true); - filter->expect_cf_id_.store(1); - - Options options = CurrentOptions(); - options.compaction_filter_factory.reset(filter); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = 2; - CreateAndReopenWithCF({"pikachu"}, options); - - int num_keys_per_file = 400; - for (int j = 0; j < 3; j++) { - // Write several keys. - const std::string value(10, 'x'); - for (int i = 0; i < num_keys_per_file; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%08d%02d", i, j); - ASSERT_OK(Put(1, key, value)); - } - ASSERT_OK(Flush(1)); - // Make sure next file is much smaller so automatic compaction will not - // be triggered. - num_keys_per_file /= 2; - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_TRUE(filter->compaction_filter_created()); -} - -// Compaction filters aplies to all records, regardless snapshots. -TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) { - std::string five = std::to_string(5); - Options options = CurrentOptions(); - options.compaction_filter_factory = std::make_shared(); - options.disable_auto_compactions = true; - options.create_if_missing = true; - DestroyAndReopen(options); - - // Put some data. - const Snapshot* snapshot = nullptr; - for (int table = 0; table < 4; ++table) { - for (int i = 0; i < 10; ++i) { - ASSERT_OK(Put(std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(Flush()); - - if (table == 0) { - snapshot = db_->GetSnapshot(); - } - } - assert(snapshot != nullptr); - - cfilter_count = 0; - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // The filter should delete 40 records. - ASSERT_EQ(40, cfilter_count); - - { - // Scan the entire database as of the snapshot to ensure - // that nothing is left - ReadOptions read_options; - read_options.snapshot = snapshot; - std::unique_ptr iter(db_->NewIterator(read_options)); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - int count = 0; - while (iter->Valid()) { - count++; - iter->Next(); - } - ASSERT_EQ(count, 6); - read_options.snapshot = nullptr; - std::unique_ptr iter1(db_->NewIterator(read_options)); - ASSERT_OK(iter1->status()); - iter1->SeekToFirst(); - count = 0; - while (iter1->Valid()) { - count++; - iter1->Next(); - } - // We have deleted 10 keys from 40 using the compaction filter - // Keys 6-9 before the snapshot and 100-105 after the snapshot - ASSERT_EQ(count, 30); - } - - // Release the snapshot and compact again -> now all records should be - // removed. - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBTestCompactionFilter, SkipUntil) { - Options options = CurrentOptions(); - options.compaction_filter_factory = std::make_shared(); - options.disable_auto_compactions = true; - options.create_if_missing = true; - DestroyAndReopen(options); - - // Write 100K keys, these are written to a few files in L0. - for (int table = 0; table < 4; ++table) { - // Key ranges in tables are [0, 38], [106, 149], [212, 260], [318, 371]. - for (int i = table * 6; i < 39 + table * 11; ++i) { - char key[100]; - snprintf(key, sizeof(key), "%010d", table * 100 + i); - ASSERT_OK(Put(key, std::to_string(table * 1000 + i))); - } - ASSERT_OK(Flush()); - } - - cfilter_skips = 0; - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // Number of skips in tables: 2, 3, 3, 3. - ASSERT_EQ(11, cfilter_skips); - - for (int table = 0; table < 4; ++table) { - for (int i = table * 6; i < 39 + table * 11; ++i) { - int k = table * 100 + i; - char key[100]; - snprintf(key, sizeof(key), "%010d", table * 100 + i); - auto expected = std::to_string(table * 1000 + i); - std::string val; - Status s = db_->Get(ReadOptions(), key, &val); - if (k / 10 % 2 == 0) { - ASSERT_TRUE(s.IsNotFound()); - } else { - ASSERT_OK(s); - ASSERT_EQ(expected, val); - } - } - } -} - -TEST_F(DBTestCompactionFilter, SkipUntilWithBloomFilter) { - BlockBasedTableOptions table_options; - table_options.whole_key_filtering = false; - table_options.filter_policy.reset(NewBloomFilterPolicy(100, false)); - - Options options = CurrentOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewCappedPrefixTransform(9)); - options.compaction_filter_factory = std::make_shared(); - options.disable_auto_compactions = true; - options.create_if_missing = true; - DestroyAndReopen(options); - - ASSERT_OK(Put("0000000010", "v10")); - ASSERT_OK(Put("0000000020", "v20")); // skipped - ASSERT_OK(Put("0000000050", "v50")); - ASSERT_OK(Flush()); - - cfilter_skips = 0; - EXPECT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - EXPECT_EQ(1, cfilter_skips); - - Status s; - std::string val; - - s = db_->Get(ReadOptions(), "0000000010", &val); - ASSERT_OK(s); - EXPECT_EQ("v10", val); - - s = db_->Get(ReadOptions(), "0000000020", &val); - EXPECT_TRUE(s.IsNotFound()); - - s = db_->Get(ReadOptions(), "0000000050", &val); - ASSERT_OK(s); - EXPECT_EQ("v50", val); -} - -class TestNotSupportedFilter : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - return true; - } - - const char* Name() const override { return "NotSupported"; } - bool IgnoreSnapshots() const override { return false; } -}; - -TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalse) { - Options options = CurrentOptions(); - options.compaction_filter = new TestNotSupportedFilter(); - DestroyAndReopen(options); - - ASSERT_OK(Put("a", "v10")); - ASSERT_OK(Put("z", "v20")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("a", "v10")); - ASSERT_OK(Put("z", "v20")); - ASSERT_OK(Flush()); - - // Comapction should fail because IgnoreSnapshots() = false - EXPECT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr) - .IsNotSupported()); - - delete options.compaction_filter; -} - -class TestNotSupportedFilterFactory : public CompactionFilterFactory { - public: - explicit TestNotSupportedFilterFactory(TableFileCreationReason reason) - : reason_(reason) {} - - bool ShouldFilterTableFileCreation( - TableFileCreationReason reason) const override { - return reason_ == reason; - } - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& /* context */) override { - return std::unique_ptr(new TestNotSupportedFilter()); - } - - const char* Name() const override { return "TestNotSupportedFilterFactory"; } - - private: - const TableFileCreationReason reason_; -}; - -TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalseDuringFlush) { - Options options = CurrentOptions(); - options.compaction_filter_factory = - std::make_shared( - TableFileCreationReason::kFlush); - Reopen(options); - - ASSERT_OK(Put("a", "v10")); - ASSERT_TRUE(Flush().IsNotSupported()); -} - -TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalseRecovery) { - Options options = CurrentOptions(); - options.compaction_filter_factory = - std::make_shared( - TableFileCreationReason::kRecovery); - Reopen(options); - - ASSERT_OK(Put("a", "v10")); - ASSERT_TRUE(TryReopen(options).IsNotSupported()); -} - -TEST_F(DBTestCompactionFilter, DropKeyWithSingleDelete) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - - Reopen(options); - - ASSERT_OK(Put("a", "v0")); - ASSERT_OK(Put("b", "v0")); - const Snapshot* snapshot = db_->GetSnapshot(); - - ASSERT_OK(SingleDelete("b")); - ASSERT_OK(Flush()); - - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = options.num_levels - 1; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - - db_->ReleaseSnapshot(snapshot); - Close(); - - class DeleteFilterV2 : public CompactionFilter { - public: - Decision FilterV2(int /*level*/, const Slice& key, ValueType /*value_type*/, - const Slice& /*existing_value*/, - std::string* /*new_value*/, - std::string* /*skip_until*/) const override { - if (key.starts_with("b")) { - return Decision::kPurge; - } - return Decision::kRemove; - } - - const char* Name() const override { return "DeleteFilterV2"; } - } delete_filter_v2; - - options.compaction_filter = &delete_filter_v2; - options.level0_file_num_compaction_trigger = 2; - Reopen(options); - - ASSERT_OK(Put("b", "v1")); - ASSERT_OK(Put("x", "v1")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("r", "v1")); - ASSERT_OK(Put("z", "v1")); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - Close(); - - options.compaction_filter = nullptr; - Reopen(options); - ASSERT_OK(SingleDelete("b")); - ASSERT_OK(Flush()); - { - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_compaction_test.cc b/db/db_compaction_test.cc deleted file mode 100644 index 55852aacd..000000000 --- a/db/db_compaction_test.cc +++ /dev/null @@ -1,9118 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include "compaction/compaction_picker_universal.h" -#include "db/blob/blob_index.h" -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "env/mock_env.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/concurrent_task_limiter.h" -#include "rocksdb/experimental.h" -#include "rocksdb/sst_file_writer.h" -#include "rocksdb/utilities/convenience.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "util/concurrent_task_limiter_impl.h" -#include "util/random.h" -#include "utilities/fault_injection_env.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -// SYNC_POINT is not supported in released Windows mode. - -class CompactionStatsCollector : public EventListener { - public: - CompactionStatsCollector() - : compaction_completed_( - static_cast(CompactionReason::kNumOfReasons)) { - for (auto& v : compaction_completed_) { - v.store(0); - } - } - - ~CompactionStatsCollector() override {} - - void OnCompactionCompleted(DB* /* db */, - const CompactionJobInfo& info) override { - int k = static_cast(info.compaction_reason); - int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); - assert(k >= 0 && k < num_of_reasons); - compaction_completed_[k]++; - } - - void OnExternalFileIngested( - DB* /* db */, const ExternalFileIngestionInfo& /* info */) override { - int k = static_cast(CompactionReason::kExternalSstIngestion); - compaction_completed_[k]++; - } - - void OnFlushCompleted(DB* /* db */, const FlushJobInfo& /* info */) override { - int k = static_cast(CompactionReason::kFlush); - compaction_completed_[k]++; - } - - int NumberOfCompactions(CompactionReason reason) const { - int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); - int k = static_cast(reason); - assert(k >= 0 && k < num_of_reasons); - return compaction_completed_.at(k).load(); - } - - private: - std::vector> compaction_completed_; -}; - -class DBCompactionTest : public DBTestBase { - public: - DBCompactionTest() - : DBTestBase("db_compaction_test", /*env_do_fsync=*/true) {} - - protected: - /* - * Verifies compaction stats of cfd are valid. - * - * For each level of cfd, its compaction stats are valid if - * 1) sum(stat.counts) == stat.count, and - * 2) stat.counts[i] == collector.NumberOfCompactions(i) - */ - void VerifyCompactionStats(ColumnFamilyData& cfd, - const CompactionStatsCollector& collector) { -#ifndef NDEBUG - InternalStats* internal_stats_ptr = cfd.internal_stats(); - ASSERT_NE(internal_stats_ptr, nullptr); - const std::vector& comp_stats = - internal_stats_ptr->TEST_GetCompactionStats(); - const int num_of_reasons = - static_cast(CompactionReason::kNumOfReasons); - std::vector counts(num_of_reasons, 0); - // Count the number of compactions caused by each CompactionReason across - // all levels. - for (const auto& stat : comp_stats) { - int sum = 0; - for (int i = 0; i < num_of_reasons; i++) { - counts[i] += stat.counts[i]; - sum += stat.counts[i]; - } - ASSERT_EQ(sum, stat.count); - } - // Verify InternalStats bookkeeping matches that of - // CompactionStatsCollector, assuming that all compactions complete. - for (int i = 0; i < num_of_reasons; i++) { - ASSERT_EQ(collector.NumberOfCompactions(static_cast(i)), - counts[i]); - } -#endif /* NDEBUG */ - } -}; - -class DBCompactionTestWithParam - : public DBTestBase, - public testing::WithParamInterface> { - public: - DBCompactionTestWithParam() - : DBTestBase("db_compaction_test", /*env_do_fsync=*/true) { - max_subcompactions_ = std::get<0>(GetParam()); - exclusive_manual_compaction_ = std::get<1>(GetParam()); - } - - // Required if inheriting from testing::WithParamInterface<> - static void SetUpTestCase() {} - static void TearDownTestCase() {} - - uint32_t max_subcompactions_; - bool exclusive_manual_compaction_; -}; - -class DBCompactionTestWithBottommostParam - : public DBTestBase, - public testing::WithParamInterface { - public: - DBCompactionTestWithBottommostParam() - : DBTestBase("db_compaction_test", /*env_do_fsync=*/true) { - bottommost_level_compaction_ = GetParam(); - } - - BottommostLevelCompaction bottommost_level_compaction_; -}; - -class DBCompactionDirectIOTest : public DBCompactionTest, - public ::testing::WithParamInterface { - public: - DBCompactionDirectIOTest() : DBCompactionTest() {} -}; - -// Param = true : target level is non-empty -// Param = false: level between target level and source level -// is not empty. -class ChangeLevelConflictsWithAuto - : public DBCompactionTest, - public ::testing::WithParamInterface { - public: - ChangeLevelConflictsWithAuto() : DBCompactionTest() {} -}; - -// Param = true: grab the compaction pressure token (enable -// parallel compactions) -// Param = false: Not grab the token (no parallel compactions) -class RoundRobinSubcompactionsAgainstPressureToken - : public DBCompactionTest, - public ::testing::WithParamInterface { - public: - RoundRobinSubcompactionsAgainstPressureToken() { - grab_pressure_token_ = GetParam(); - } - bool grab_pressure_token_; -}; - -class RoundRobinSubcompactionsAgainstResources - : public DBCompactionTest, - public ::testing::WithParamInterface> { - public: - RoundRobinSubcompactionsAgainstResources() { - total_low_pri_threads_ = std::get<0>(GetParam()); - max_compaction_limits_ = std::get<1>(GetParam()); - } - int total_low_pri_threads_; - int max_compaction_limits_; -}; - -namespace { -class FlushedFileCollector : public EventListener { - public: - FlushedFileCollector() {} - ~FlushedFileCollector() override {} - - void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { - std::lock_guard lock(mutex_); - flushed_files_.push_back(info.file_path); - } - - std::vector GetFlushedFiles() { - std::lock_guard lock(mutex_); - std::vector result; - for (auto fname : flushed_files_) { - result.push_back(fname); - } - return result; - } - - void ClearFlushedFiles() { flushed_files_.clear(); } - - private: - std::vector flushed_files_; - std::mutex mutex_; -}; - -class SstStatsCollector : public EventListener { - public: - SstStatsCollector() : num_ssts_creation_started_(0) {} - - void OnTableFileCreationStarted( - const TableFileCreationBriefInfo& /* info */) override { - ++num_ssts_creation_started_; - } - - int num_ssts_creation_started() { return num_ssts_creation_started_; } - - private: - std::atomic num_ssts_creation_started_; -}; - -static const int kCDTValueSize = 1000; -static const int kCDTKeysPerBuffer = 4; -static const int kCDTNumLevels = 8; -Options DeletionTriggerOptions(Options options) { - options.compression = kNoCompression; - options.write_buffer_size = kCDTKeysPerBuffer * (kCDTValueSize + 24); - options.min_write_buffer_number_to_merge = 1; - options.max_write_buffer_size_to_maintain = 0; - options.num_levels = kCDTNumLevels; - options.level0_file_num_compaction_trigger = 1; - options.target_file_size_base = options.write_buffer_size * 2; - options.target_file_size_multiplier = 2; - options.max_bytes_for_level_base = - options.target_file_size_base * options.target_file_size_multiplier; - options.max_bytes_for_level_multiplier = 2; - options.disable_auto_compactions = false; - options.compaction_options_universal.max_size_amplification_percent = 100; - return options; -} - -bool HaveOverlappingKeyRanges(const Comparator* c, const SstFileMetaData& a, - const SstFileMetaData& b) { - if (c->CompareWithoutTimestamp(a.smallestkey, b.smallestkey) >= 0) { - if (c->CompareWithoutTimestamp(a.smallestkey, b.largestkey) <= 0) { - // b.smallestkey <= a.smallestkey <= b.largestkey - return true; - } - } else if (c->CompareWithoutTimestamp(a.largestkey, b.smallestkey) >= 0) { - // a.smallestkey < b.smallestkey <= a.largestkey - return true; - } - if (c->CompareWithoutTimestamp(a.largestkey, b.largestkey) <= 0) { - if (c->CompareWithoutTimestamp(a.largestkey, b.smallestkey) >= 0) { - // b.smallestkey <= a.largestkey <= b.largestkey - return true; - } - } else if (c->CompareWithoutTimestamp(a.smallestkey, b.largestkey) <= 0) { - // a.smallestkey <= b.largestkey < a.largestkey - return true; - } - return false; -} - -// Identifies all files between level "min_level" and "max_level" -// which has overlapping key range with "input_file_meta". -void GetOverlappingFileNumbersForLevelCompaction( - const ColumnFamilyMetaData& cf_meta, const Comparator* comparator, - int min_level, int max_level, const SstFileMetaData* input_file_meta, - std::set* overlapping_file_names) { - std::set overlapping_files; - overlapping_files.insert(input_file_meta); - for (int m = min_level; m <= max_level; ++m) { - for (auto& file : cf_meta.levels[m].files) { - for (auto* included_file : overlapping_files) { - if (HaveOverlappingKeyRanges(comparator, *included_file, file)) { - overlapping_files.insert(&file); - overlapping_file_names->insert(file.name); - break; - } - } - } - } -} - -void VerifyCompactionResult( - const ColumnFamilyMetaData& cf_meta, - const std::set& overlapping_file_numbers) { -#ifndef NDEBUG - for (auto& level : cf_meta.levels) { - for (auto& file : level.files) { - assert(overlapping_file_numbers.find(file.name) == - overlapping_file_numbers.end()); - } - } -#endif -} - -const SstFileMetaData* PickFileRandomly(const ColumnFamilyMetaData& cf_meta, - Random* rand, int* level = nullptr) { - auto file_id = rand->Uniform(static_cast(cf_meta.file_count)) + 1; - for (auto& level_meta : cf_meta.levels) { - if (file_id <= level_meta.files.size()) { - if (level != nullptr) { - *level = level_meta.level; - } - auto result = rand->Uniform(file_id); - return &(level_meta.files[result]); - } - file_id -= static_cast(level_meta.files.size()); - } - assert(false); - return nullptr; -} -} // anonymous namespace - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -// All the TEST_P tests run once with sub_compactions disabled (i.e. -// options.max_subcompactions = 1) and once with it enabled -TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) { - for (int tid = 0; tid < 3; ++tid) { - uint64_t db_size[2]; - Options options = DeletionTriggerOptions(CurrentOptions()); - options.max_subcompactions = max_subcompactions_; - - if (tid == 1) { - // the following only disable stats update in DB::Open() - // and should not affect the result of this test. - options.skip_stats_update_on_db_open = true; - } else if (tid == 2) { - // third pass with universal compaction - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - } - - DestroyAndReopen(options); - Random rnd(301); - - const int kTestSize = kCDTKeysPerBuffer * 1024; - std::vector values; - for (int k = 0; k < kTestSize; ++k) { - values.push_back(rnd.RandomString(kCDTValueSize)); - ASSERT_OK(Put(Key(k), values[k])); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[0])); - - for (int k = 0; k < kTestSize; ++k) { - ASSERT_OK(Delete(Key(k))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[1])); - - if (options.compaction_style == kCompactionStyleUniversal) { - // Claim: in universal compaction none of the original data will remain - // once compactions settle. - // - // Proof: The compensated size of the file containing the most tombstones - // is enough on its own to trigger size amp compaction. Size amp - // compaction is a full compaction, so all tombstones meet the obsolete - // keys they cover. - ASSERT_EQ(0, db_size[1]); - } else { - // Claim: in level compaction at most `db_size[0] / 2` of the original - // data will remain once compactions settle. - // - // Proof: Assume the original data is all in the bottom level. If it were - // not, it would meet its tombstone sooner. The original data size is - // large enough to require fanout to bottom level to be greater than - // `max_bytes_for_level_multiplier == 2`. In the level just above, - // tombstones must cover less than `db_size[0] / 4` bytes since fanout >= - // 2 and file size is compensated by doubling the size of values we expect - // are covered (`kDeletionWeightOnCompaction == 2`). The tombstones in - // levels above must cover less than `db_size[0] / 8` bytes of original - // data, `db_size[0] / 16`, and so on. - ASSERT_GT(db_size[0] / 2, db_size[1]); - } - } -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_F(DBCompactionTest, SkipStatsUpdateTest) { - // This test verify UpdateAccumulatedStats is not on - // if options.skip_stats_update_on_db_open = true - // The test will need to be updated if the internal behavior changes. - - Options options = DeletionTriggerOptions(CurrentOptions()); - options.disable_auto_compactions = true; - options.env = env_; - DestroyAndReopen(options); - Random rnd(301); - - const int kTestSize = kCDTKeysPerBuffer * 512; - std::vector values; - for (int k = 0; k < kTestSize; ++k) { - values.push_back(rnd.RandomString(kCDTValueSize)); - ASSERT_OK(Put(Key(k), values[k])); - } - - ASSERT_OK(Flush()); - - Close(); - - int update_acc_stats_called = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionStorageInfo::UpdateAccumulatedStats", - [&](void* /* arg */) { ++update_acc_stats_called; }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Reopen the DB with stats-update disabled - options.skip_stats_update_on_db_open = true; - options.max_open_files = 20; - Reopen(options); - - ASSERT_EQ(update_acc_stats_called, 0); - - // Repeat the reopen process, but this time we enable - // stats-update. - options.skip_stats_update_on_db_open = false; - Reopen(options); - - ASSERT_GT(update_acc_stats_called, 0); - - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, TestTableReaderForCompaction) { - Options options = CurrentOptions(); - options.env = env_; - options.max_open_files = 20; - options.level0_file_num_compaction_trigger = 3; - // Avoid many shards with small max_open_files, where as little as - // two table insertions could lead to an LRU eviction, depending on - // hash values. - options.table_cache_numshardbits = 2; - DestroyAndReopen(options); - Random rnd(301); - - int num_table_cache_lookup = 0; - int num_new_table_reader = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "TableCache::FindTable:0", [&](void* arg) { - assert(arg != nullptr); - bool no_io = *(reinterpret_cast(arg)); - if (!no_io) { - // filter out cases for table properties queries. - num_table_cache_lookup++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "TableCache::GetTableReader:0", - [&](void* /*arg*/) { num_new_table_reader++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - for (int k = 0; k < options.level0_file_num_compaction_trigger; ++k) { - ASSERT_OK(Put(Key(k), Key(k))); - ASSERT_OK(Put(Key(10 - k), "bar")); - if (k < options.level0_file_num_compaction_trigger - 1) { - num_table_cache_lookup = 0; - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // preloading iterator issues one table cache lookup and create - // a new table reader, if not preloaded. - int old_num_table_cache_lookup = num_table_cache_lookup; - ASSERT_GE(num_table_cache_lookup, 1); - ASSERT_EQ(num_new_table_reader, 1); - - num_table_cache_lookup = 0; - num_new_table_reader = 0; - ASSERT_EQ(Key(k), Get(Key(k))); - // lookup iterator from table cache and no need to create a new one. - ASSERT_EQ(old_num_table_cache_lookup + num_table_cache_lookup, 2); - ASSERT_EQ(num_new_table_reader, 0); - } - } - - num_table_cache_lookup = 0; - num_new_table_reader = 0; - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Preloading iterator issues one table cache lookup and creates - // a new table reader. One file is created for flush and one for compaction. - // Compaction inputs make no table cache look-up for data/range deletion - // iterators - // May preload table cache too. - ASSERT_GE(num_table_cache_lookup, 2); - int old_num_table_cache_lookup2 = num_table_cache_lookup; - - // Create new iterator for: - // (1) 1 for verifying flush results - // (2) 1 for verifying compaction results. - // (3) New TableReaders will not be created for compaction inputs - ASSERT_EQ(num_new_table_reader, 2); - - num_table_cache_lookup = 0; - num_new_table_reader = 0; - ASSERT_EQ(Key(1), Get(Key(1))); - ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 5); - ASSERT_EQ(num_new_table_reader, 0); - - num_table_cache_lookup = 0; - num_new_table_reader = 0; - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 2; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - // Only verifying compaction outputs issues one table cache lookup - // for both data block and range deletion block). - // May preload table cache too. - ASSERT_GE(num_table_cache_lookup, 1); - old_num_table_cache_lookup2 = num_table_cache_lookup; - // One for verifying compaction results. - // No new iterator created for compaction. - ASSERT_EQ(num_new_table_reader, 1); - - num_table_cache_lookup = 0; - num_new_table_reader = 0; - ASSERT_EQ(Key(1), Get(Key(1))); - ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 3); - ASSERT_EQ(num_new_table_reader, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) { - for (int tid = 0; tid < 2; ++tid) { - uint64_t db_size[3]; - Options options = DeletionTriggerOptions(CurrentOptions()); - options.max_subcompactions = max_subcompactions_; - - if (tid == 1) { - // second pass with universal compaction - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - } - - DestroyAndReopen(options); - Random rnd(301); - - // round 1 --- insert key/value pairs. - const int kTestSize = kCDTKeysPerBuffer * 512; - std::vector values; - for (int k = 0; k < kTestSize; ++k) { - values.push_back(rnd.RandomString(kCDTValueSize)); - ASSERT_OK(Put(Key(k), values[k])); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[0])); - Close(); - - // round 2 --- disable auto-compactions and issue deletions. - options.create_if_missing = false; - options.disable_auto_compactions = true; - Reopen(options); - - for (int k = 0; k < kTestSize; ++k) { - ASSERT_OK(Delete(Key(k))); - } - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[1])); - Close(); - // as auto_compaction is off, we shouldn't see any reduction in db size. - ASSERT_LE(db_size[0], db_size[1]); - - // round 3 --- reopen db with auto_compaction on and see if - // deletion compensation still work. - options.disable_auto_compactions = false; - Reopen(options); - // insert relatively small amount of data to trigger auto compaction. - for (int k = 0; k < kTestSize / 10; ++k) { - ASSERT_OK(Put(Key(k), values[k])); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[2])); - // this time we're expecting significant drop in size. - // - // See "CompactionDeletionTrigger" test for proof that at most - // `db_size[0] / 2` of the original data remains. In addition to that, this - // test inserts `db_size[0] / 10` to push the tombstones into SST files and - // then through automatic compactions. So in total `3 * db_size[0] / 5` of - // the original data may remain. - ASSERT_GT(3 * db_size[0] / 5, db_size[2]); - } -} - -TEST_F(DBCompactionTest, CompactRangeBottomPri) { - ASSERT_OK(Put(Key(50), "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(100), "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(200), "")); - ASSERT_OK(Flush()); - - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 2; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - ASSERT_EQ("0,0,3", FilesPerLevel(0)); - - ASSERT_OK(Put(Key(1), "")); - ASSERT_OK(Put(Key(199), "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(2), "")); - ASSERT_OK(Put(Key(199), "")); - ASSERT_OK(Flush()); - ASSERT_EQ("2,0,3", FilesPerLevel(0)); - - // Now we have 2 L0 files, and 3 L2 files, and a manual compaction will - // be triggered. - // Two compaction jobs will run. One compacts 2 L0 files in Low Pri Pool - // and one compact to L2 in bottom pri pool. - int low_pri_count = 0; - int bottom_pri_count = 0; - SyncPoint::GetInstance()->SetCallBack( - "ThreadPoolImpl::Impl::BGThread:BeforeRun", [&](void* arg) { - Env::Priority* pri = reinterpret_cast(arg); - // First time is low pri pool in the test case. - if (low_pri_count == 0 && bottom_pri_count == 0) { - ASSERT_EQ(Env::Priority::LOW, *pri); - } - if (*pri == Env::Priority::LOW) { - low_pri_count++; - } else { - bottom_pri_count++; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - env_->SetBackgroundThreads(1, Env::Priority::BOTTOM); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(1, low_pri_count); - ASSERT_EQ(1, bottom_pri_count); - ASSERT_EQ("0,0,2", FilesPerLevel(0)); - - // Recompact bottom most level uses bottom pool - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - ASSERT_EQ(1, low_pri_count); - ASSERT_EQ(2, bottom_pri_count); - - env_->SetBackgroundThreads(0, Env::Priority::BOTTOM); - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - // Low pri pool is used if bottom pool has size 0. - ASSERT_EQ(2, low_pri_count); - ASSERT_EQ(2, bottom_pri_count); - - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, DisableStatsUpdateReopen) { - uint64_t db_size[3]; - for (int test = 0; test < 2; ++test) { - Options options = DeletionTriggerOptions(CurrentOptions()); - options.skip_stats_update_on_db_open = (test == 0); - - env_->random_read_counter_.Reset(); - DestroyAndReopen(options); - Random rnd(301); - - // round 1 --- insert key/value pairs. - const int kTestSize = kCDTKeysPerBuffer * 512; - std::vector values; - for (int k = 0; k < kTestSize; ++k) { - values.push_back(rnd.RandomString(kCDTValueSize)); - ASSERT_OK(Put(Key(k), values[k])); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // L1 and L2 can fit deletions iff size compensation does not take effect, - // i.e., when `skip_stats_update_on_db_open == true`. Move any remaining - // files at or above L2 down to L3 to ensure obsolete data does not - // accidentally meet its tombstone above L3. This makes the final size more - // deterministic and easy to see whether size compensation for deletions - // took effect. - MoveFilesToLevel(3 /* level */); - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[0])); - Close(); - - // round 2 --- disable auto-compactions and issue deletions. - options.create_if_missing = false; - options.disable_auto_compactions = true; - - env_->random_read_counter_.Reset(); - Reopen(options); - - for (int k = 0; k < kTestSize; ++k) { - ASSERT_OK(Delete(Key(k))); - } - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[1])); - Close(); - // as auto_compaction is off, we shouldn't see any reduction in db size. - ASSERT_LE(db_size[0], db_size[1]); - - // round 3 --- reopen db with auto_compaction on and see if - // deletion compensation still work. - options.disable_auto_compactions = false; - Reopen(options); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(Size(Key(0), Key(kTestSize - 1), &db_size[2])); - - if (options.skip_stats_update_on_db_open) { - // If update stats on DB::Open is disable, we don't expect - // deletion entries taking effect. - // - // The deletions are small enough to fit in L1 and L2, and obsolete keys - // were moved to L3+, so none of the original data should have been - // dropped. - ASSERT_LE(db_size[0], db_size[2]); - } else { - // Otherwise, we should see a significant drop in db size. - // - // See "CompactionDeletionTrigger" test for proof that at most - // `db_size[0] / 2` of the original data remains. - ASSERT_GT(db_size[0] / 2, db_size[2]); - } - } -} - -TEST_P(DBCompactionTestWithParam, CompactionTrigger) { - const int kNumKeysPerFile = 100; - - Options options = CurrentOptions(); - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.num_levels = 3; - options.level0_file_num_compaction_trigger = 3; - options.max_subcompactions = max_subcompactions_; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - std::vector values; - // Write 100KB (100 values, each 1K) - for (int i = 0; i < kNumKeysPerFile; i++) { - values.push_back(rnd.RandomString(990)); - ASSERT_OK(Put(1, Key(i), values[i])); - } - // put extra key to trigger flush - ASSERT_OK(Put(1, "", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), num + 1); - } - - // generate one more file in level-0, and should trigger level-0 compaction - std::vector values; - for (int i = 0; i < kNumKeysPerFile; i++) { - values.push_back(rnd.RandomString(990)); - ASSERT_OK(Put(1, Key(i), values[i])); - } - // put extra key to trigger flush - ASSERT_OK(Put(1, "", "")); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 1), 1); -} - -TEST_F(DBCompactionTest, BGCompactionsAllowed) { - // Create several column families. Make compaction triggers in all of them - // and see number of compactions scheduled to be less than allowed. - const int kNumKeysPerFile = 100; - - Options options = CurrentOptions(); - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.num_levels = 3; - // Should speed up compaction when there are 4 files. - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 20; - options.soft_pending_compaction_bytes_limit = 1 << 30; // Infinitely large - options.max_background_compactions = 3; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - - // Block all threads in thread pool. - const size_t kTotalTasks = 4; - env_->SetBackgroundThreads(4, Env::LOW); - test::SleepingBackgroundTask sleeping_tasks[kTotalTasks]; - for (size_t i = 0; i < kTotalTasks; i++) { - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_tasks[i], Env::Priority::LOW); - sleeping_tasks[i].WaitUntilSleeping(); - } - - CreateAndReopenWithCF({"one", "two", "three"}, options); - - Random rnd(301); - for (int cf = 0; cf < 4; cf++) { - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - for (int i = 0; i < kNumKeysPerFile; i++) { - ASSERT_OK(Put(cf, Key(i), "")); - } - // put extra key to trigger flush - ASSERT_OK(Put(cf, "", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf])); - ASSERT_EQ(NumTableFilesAtLevel(0, cf), num + 1); - } - } - - // Now all column families qualify compaction but only one should be - // scheduled, because no column family hits speed up condition. - ASSERT_EQ(1u, env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - - // Create two more files for one column family, which triggers speed up - // condition, three compactions will be scheduled. - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - for (int i = 0; i < kNumKeysPerFile; i++) { - ASSERT_OK(Put(2, Key(i), "")); - } - // put extra key to trigger flush - ASSERT_OK(Put(2, "", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[2])); - ASSERT_EQ(options.level0_file_num_compaction_trigger + num + 1, - NumTableFilesAtLevel(0, 2)); - } - ASSERT_EQ(3U, env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - - // Unblock all threads to unblock all compactions. - for (size_t i = 0; i < kTotalTasks; i++) { - sleeping_tasks[i].WakeUp(); - sleeping_tasks[i].WaitUntilDone(); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Verify number of compactions allowed will come back to 1. - - for (size_t i = 0; i < kTotalTasks; i++) { - sleeping_tasks[i].Reset(); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_tasks[i], Env::Priority::LOW); - sleeping_tasks[i].WaitUntilSleeping(); - } - for (int cf = 0; cf < 4; cf++) { - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - for (int i = 0; i < kNumKeysPerFile; i++) { - ASSERT_OK(Put(cf, Key(i), "")); - } - // put extra key to trigger flush - ASSERT_OK(Put(cf, "", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf])); - ASSERT_EQ(NumTableFilesAtLevel(0, cf), num + 1); - } - } - - // Now all column families qualify compaction but only one should be - // scheduled, because no column family hits speed up condition. - ASSERT_EQ(1U, env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - - for (size_t i = 0; i < kTotalTasks; i++) { - sleeping_tasks[i].WakeUp(); - sleeping_tasks[i].WaitUntilDone(); - } -} - -TEST_P(DBCompactionTestWithParam, CompactionsGenerateMultipleFiles) { - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; // Large write buffer - options.max_subcompactions = max_subcompactions_; - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - - // Write 8MB (80 values, each 100K) - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - std::vector values; - for (int i = 0; i < 80; i++) { - values.push_back(rnd.RandomString(100000)); - ASSERT_OK(Put(1, Key(i), values[i])); - } - - // Reopening moves updates to level-0 - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1], - true /* disallow trivial move */)); - - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_GT(NumTableFilesAtLevel(1, 1), 1); - for (int i = 0; i < 80; i++) { - ASSERT_EQ(Get(1, Key(i)), values[i]); - } -} - -TEST_F(DBCompactionTest, MinorCompactionsHappen) { - do { - Options options = CurrentOptions(); - options.write_buffer_size = 10000; - CreateAndReopenWithCF({"pikachu"}, options); - - const int N = 500; - - int starting_num_tables = TotalTableFiles(1); - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(1, Key(i), Key(i) + std::string(1000, 'v'))); - } - int ending_num_tables = TotalTableFiles(1); - ASSERT_GT(ending_num_tables, starting_num_tables); - - for (int i = 0; i < N; i++) { - ASSERT_EQ(Key(i) + std::string(1000, 'v'), Get(1, Key(i))); - } - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - for (int i = 0; i < N; i++) { - ASSERT_EQ(Key(i) + std::string(1000, 'v'), Get(1, Key(i))); - } - } while (ChangeCompactOptions()); -} - -TEST_F(DBCompactionTest, UserKeyCrossFile1) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 3; - - DestroyAndReopen(options); - - // create first file and flush to l0 - ASSERT_OK(Put("4", "A")); - ASSERT_OK(Put("3", "A")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - ASSERT_OK(Put("2", "A")); - ASSERT_OK(Delete("3")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ("NOT_FOUND", Get("3")); - - // move both files down to l1 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("NOT_FOUND", Get("3")); - - for (int i = 0; i < 3; i++) { - ASSERT_OK(Put("2", "B")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("NOT_FOUND", Get("3")); -} - -TEST_F(DBCompactionTest, UserKeyCrossFile2) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 3; - - DestroyAndReopen(options); - - // create first file and flush to l0 - ASSERT_OK(Put("4", "A")); - ASSERT_OK(Put("3", "A")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - ASSERT_OK(Put("2", "A")); - ASSERT_OK(SingleDelete("3")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ("NOT_FOUND", Get("3")); - - // move both files down to l1 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("NOT_FOUND", Get("3")); - - for (int i = 0; i < 3; i++) { - ASSERT_OK(Put("2", "B")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("NOT_FOUND", Get("3")); -} - -TEST_F(DBCompactionTest, CompactionSstPartitioner) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 3; - std::shared_ptr factory( - NewSstPartitionerFixedPrefixFactory(4)); - options.sst_partitioner_factory = factory; - - DestroyAndReopen(options); - - // create first file and flush to l0 - ASSERT_OK(Put("aaaa1", "A")); - ASSERT_OK(Put("bbbb1", "B")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - ASSERT_OK(Put("aaaa1", "A2")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - // move both files down to l1 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - std::vector files; - dbfull()->GetLiveFilesMetaData(&files); - ASSERT_EQ(2, files.size()); - ASSERT_EQ("A2", Get("aaaa1")); - ASSERT_EQ("B", Get("bbbb1")); -} - -TEST_F(DBCompactionTest, CompactionSstPartitionWithManualCompaction) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 3; - - DestroyAndReopen(options); - - // create first file and flush to l0 - ASSERT_OK(Put("000015", "A")); - ASSERT_OK(Put("000025", "B")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - // create second file and flush to l0 - ASSERT_OK(Put("000015", "A2")); - ASSERT_OK(Put("000025", "B2")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - // CONTROL 1: compact without partitioner - CompactRangeOptions compact_options; - compact_options.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // Check (compacted but no partitioning yet) - std::vector files; - dbfull()->GetLiveFilesMetaData(&files); - ASSERT_EQ(1, files.size()); - - // Install partitioner - std::shared_ptr factory( - NewSstPartitionerFixedPrefixFactory(5)); - options.sst_partitioner_factory = factory; - Reopen(options); - - // CONTROL 2: request compaction on range with no partition boundary and no - // overlap with actual entries - Slice from("000017"); - Slice to("000019"); - ASSERT_OK(dbfull()->CompactRange(compact_options, &from, &to)); - - // Check (no partitioning yet) - files.clear(); - dbfull()->GetLiveFilesMetaData(&files); - ASSERT_EQ(1, files.size()); - ASSERT_EQ("A2", Get("000015")); - ASSERT_EQ("B2", Get("000025")); - - // TEST: request compaction overlapping with partition boundary but no - // actual entries - // NOTE: `to` is INCLUSIVE - from = Slice("000019"); - to = Slice("000020"); - ASSERT_OK(dbfull()->CompactRange(compact_options, &from, &to)); - - // Check (must be partitioned) - files.clear(); - dbfull()->GetLiveFilesMetaData(&files); - ASSERT_EQ(2, files.size()); - ASSERT_EQ("A2", Get("000015")); - ASSERT_EQ("B2", Get("000025")); -} - -TEST_F(DBCompactionTest, CompactionSstPartitionerNonTrivial) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 1; - std::shared_ptr factory( - NewSstPartitionerFixedPrefixFactory(4)); - options.sst_partitioner_factory = factory; - - DestroyAndReopen(options); - - // create first file and flush to l0 - ASSERT_OK(Put("aaaa1", "A")); - ASSERT_OK(Put("bbbb1", "B")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - - std::vector files; - dbfull()->GetLiveFilesMetaData(&files); - ASSERT_EQ(2, files.size()); - ASSERT_EQ("A", Get("aaaa1")); - ASSERT_EQ("B", Get("bbbb1")); -} - -TEST_F(DBCompactionTest, ZeroSeqIdCompaction) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 3; - - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - // compaction options - CompactionOptions compact_opt; - compact_opt.compression = kNoCompression; - compact_opt.output_file_size_limit = 4096; - const size_t key_len = - static_cast(compact_opt.output_file_size_limit) / 5; - - DestroyAndReopen(options); - - std::vector snaps; - - // create first file and flush to l0 - for (auto& key : {"1", "2", "3", "3", "3", "3"}) { - ASSERT_OK(Put(key, std::string(key_len, 'A'))); - snaps.push_back(dbfull()->GetSnapshot()); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - // create second file and flush to l0 - for (auto& key : {"3", "4", "5", "6", "7", "8"}) { - ASSERT_OK(Put(key, std::string(key_len, 'A'))); - snaps.push_back(dbfull()->GetSnapshot()); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - // move both files down to l1 - ASSERT_OK( - dbfull()->CompactFiles(compact_opt, collector->GetFlushedFiles(), 1)); - - // release snap so that first instance of key(3) can have seqId=0 - for (auto snap : snaps) { - dbfull()->ReleaseSnapshot(snap); - } - - // create 3 files in l0 so to trigger compaction - for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) { - ASSERT_OK(Put("2", std::string(1, 'A'))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(Put("", "")); -} - -TEST_F(DBCompactionTest, ManualCompactionUnknownOutputSize) { - // github issue #2249 - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 3; - DestroyAndReopen(options); - - // create two files in l1 that we can compact - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < options.level0_file_num_compaction_trigger; j++) { - ASSERT_OK(Put(std::to_string(2 * i), std::string(1, 'A'))); - ASSERT_OK(Put(std::to_string(2 * i + 1), std::string(1, 'A'))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_OK( - dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "2"}})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 0), 2); - ASSERT_OK( - dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "3"}})); - - ColumnFamilyMetaData cf_meta; - dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta); - ASSERT_EQ(2, cf_meta.levels[1].files.size()); - std::vector input_filenames; - for (const auto& sst_file : cf_meta.levels[1].files) { - input_filenames.push_back(sst_file.name); - } - - // note CompactionOptions::output_file_size_limit is unset. - CompactionOptions compact_opt; - compact_opt.compression = kNoCompression; - ASSERT_OK(dbfull()->CompactFiles(compact_opt, input_filenames, 1)); -} - -// Check that writes done during a memtable compaction are recovered -// if the database is shutdown during the memtable compaction. -TEST_F(DBCompactionTest, RecoverDuringMemtableCompaction) { - do { - Options options = CurrentOptions(); - options.env = env_; - CreateAndReopenWithCF({"pikachu"}, options); - - // Trigger a long memtable compaction and reopen the database during it - ASSERT_OK(Put(1, "foo", "v1")); // Goes to 1st log file - ASSERT_OK(Put(1, "big1", std::string(10000000, 'x'))); // Fills memtable - ASSERT_OK(Put(1, "big2", std::string(1000, 'y'))); // Triggers compaction - ASSERT_OK(Put(1, "bar", "v2")); // Goes to new log file - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v2", Get(1, "bar")); - ASSERT_EQ(std::string(10000000, 'x'), Get(1, "big1")); - ASSERT_EQ(std::string(1000, 'y'), Get(1, "big2")); - } while (ChangeOptions()); -} - -TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) { - int32_t trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; - options.max_subcompactions = max_subcompactions_; - DestroyAndReopen(options); - - int32_t num_keys = 80; - int32_t value_size = 100 * 1024; // 100 KB - - Random rnd(301); - std::vector values; - for (int i = 0; i < num_keys; i++) { - values.push_back(rnd.RandomString(value_size)); - ASSERT_OK(Put(Key(i), values[i])); - } - - // Reopening moves updates to L0 - Reopen(options); - ASSERT_EQ(NumTableFilesAtLevel(0, 0), 1); // 1 file in L0 - ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // 0 files in L1 - - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 1U); - LiveFileMetaData level0_file = metadata[0]; // L0 file meta - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - - // Compaction will initiate a trivial move from L0 to L1 - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - - // File moved From L0 to L1 - ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); // 0 files in L0 - ASSERT_EQ(NumTableFilesAtLevel(1, 0), 1); // 1 file in L1 - - metadata.clear(); - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 1U); - ASSERT_EQ(metadata[0].name /* level1_file.name */, level0_file.name); - ASSERT_EQ(metadata[0].size /* level1_file.size */, level0_file.size); - - for (int i = 0; i < num_keys; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } - - ASSERT_EQ(trivial_move, 1); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.write_buffer_size = 10 * 1024 * 1024; - options.max_subcompactions = max_subcompactions_; - - DestroyAndReopen(options); - // non overlapping ranges - std::vector> ranges = { - {100, 199}, {300, 399}, {0, 99}, {200, 299}, - {600, 699}, {400, 499}, {500, 550}, {551, 599}, - }; - int32_t value_size = 10 * 1024; // 10 KB - - Random rnd(301); - std::map values; - for (size_t i = 0; i < ranges.size(); i++) { - for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) { - values[j] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(j), values[j])); - } - ASSERT_OK(Flush()); - } - - int32_t level0_files = NumTableFilesAtLevel(0, 0); - ASSERT_EQ(level0_files, ranges.size()); // Multiple files in L0 - ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // No files in L1 - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - - // Since data is non-overlapping we expect compaction to initiate - // a trivial move - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - // We expect that all the files were trivially moved from L0 to L1 - ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); - ASSERT_EQ(NumTableFilesAtLevel(1, 0) /* level1_files */, level0_files); - - for (size_t i = 0; i < ranges.size(); i++) { - for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) { - ASSERT_EQ(Get(Key(j)), values[j]); - } - } - - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - trivial_move = 0; - non_trivial_move = 0; - values.clear(); - DestroyAndReopen(options); - // Same ranges as above but overlapping - ranges = { - {100, 199}, - {300, 399}, - {0, 99}, - {200, 299}, - {600, 699}, - {400, 499}, - {500, 560}, // this range overlap with the next - // one - {551, 599}, - }; - for (size_t i = 0; i < ranges.size(); i++) { - for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) { - values[j] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(j), values[j])); - } - ASSERT_OK(Flush()); - } - - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - for (size_t i = 0; i < ranges.size(); i++) { - for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) { - ASSERT_EQ(Get(Key(j)), values[j]); - } - } - ASSERT_EQ(trivial_move, 0); - ASSERT_EQ(non_trivial_move, 1); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.write_buffer_size = 10 * 1024 * 1024; - options.num_levels = 7; - options.max_subcompactions = max_subcompactions_; - - DestroyAndReopen(options); - int32_t value_size = 10 * 1024; // 10 KB - - // Add 2 non-overlapping files - Random rnd(301); - std::map values; - - // file 1 [0 => 300] - for (int32_t i = 0; i <= 300; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // file 2 [600 => 700] - for (int32_t i = 600; i <= 700; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // 2 files in L0 - ASSERT_EQ("2", FilesPerLevel(0)); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 6; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - // 2 files in L6 - ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel(0)); - - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - for (int32_t i = 0; i <= 300; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } - for (int32_t i = 600; i <= 700; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } -} - -TEST_P(DBCompactionTestWithParam, PartialOverlappingL0) { - class SubCompactionEventListener : public EventListener { - public: - void OnSubcompactionCompleted(const SubcompactionJobInfo&) override { - sub_compaction_finished_++; - } - std::atomic sub_compaction_finished_{0}; - }; - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.write_buffer_size = 10 * 1024 * 1024; - options.max_subcompactions = max_subcompactions_; - SubCompactionEventListener* listener = new SubCompactionEventListener(); - options.listeners.emplace_back(listener); - - DestroyAndReopen(options); - - // For subcompactino to trigger, output level needs to be non-empty. - ASSERT_OK(Put("key", "")); - ASSERT_OK(Put("kez", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key", "")); - ASSERT_OK(Put("kez", "")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // Ranges that are only briefly overlapping so that they won't be trivially - // moved but subcompaction ranges would only contain a subset of files. - std::vector> ranges = { - {100, 199}, {198, 399}, {397, 600}, {598, 800}, {799, 900}, {895, 999}, - }; - int32_t value_size = 10 * 1024; // 10 KB - - Random rnd(301); - std::map values; - for (size_t i = 0; i < ranges.size(); i++) { - for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) { - values[j] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(j), values[j])); - } - ASSERT_OK(Flush()); - } - - int32_t level0_files = NumTableFilesAtLevel(0, 0); - ASSERT_EQ(level0_files, ranges.size()); // Multiple files in L0 - ASSERT_EQ(NumTableFilesAtLevel(1, 0), 1); // One file in L1 - - listener->sub_compaction_finished_ = 0; - ASSERT_OK(db_->EnableAutoCompaction({db_->DefaultColumnFamily()})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - if (max_subcompactions_ > 3) { - // RocksDB might not generate the exact number of sub compactions. - // Here we validate that at least subcompaction happened. - ASSERT_GT(listener->sub_compaction_finished_.load(), 2); - } - - // We expect that all the files were compacted to L1 - ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); - ASSERT_GT(NumTableFilesAtLevel(1, 0), 1); - - for (size_t i = 0; i < ranges.size(); i++) { - for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) { - ASSERT_EQ(Get(Key(j)), values[j]); - } - } -} - -TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - bool first = true; - // Purpose of dependencies: - // 4 -> 1: ensure the order of two non-trivial compactions - // 5 -> 2 and 5 -> 3: ensure we do a check before two non-trivial compactions - // are installed - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBCompaction::ManualPartial:4", "DBCompaction::ManualPartial:1"}, - {"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:2"}, - {"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (first) { - first = false; - TEST_SYNC_POINT("DBCompaction::ManualPartial:4"); - TEST_SYNC_POINT("DBCompaction::ManualPartial:3"); - } else { // second non-trivial compaction - TEST_SYNC_POINT("DBCompaction::ManualPartial:2"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.write_buffer_size = 10 * 1024 * 1024; - options.num_levels = 7; - options.max_subcompactions = max_subcompactions_; - options.level0_file_num_compaction_trigger = 3; - options.max_background_compactions = 3; - options.target_file_size_base = 1 << 23; // 8 MB - - DestroyAndReopen(options); - int32_t value_size = 10 * 1024; // 10 KB - - // Add 2 non-overlapping files - Random rnd(301); - std::map values; - - // file 1 [0 => 100] - for (int32_t i = 0; i < 100; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // file 2 [100 => 300] - for (int32_t i = 100; i < 300; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // 2 files in L0 - ASSERT_EQ("2", FilesPerLevel(0)); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 6; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - // Trivial move the two non-overlapping files to level 6 - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - // 2 files in L6 - ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel(0)); - - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - // file 3 [ 0 => 200] - for (int32_t i = 0; i < 200; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // 1 files in L0 - ASSERT_EQ("1,0,0,0,0,0,2", FilesPerLevel(0)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, false)); - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, nullptr, false)); - ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr, nullptr, false)); - ASSERT_OK(dbfull()->TEST_CompactRange(3, nullptr, nullptr, nullptr, false)); - ASSERT_OK(dbfull()->TEST_CompactRange(4, nullptr, nullptr, nullptr, false)); - // 2 files in L6, 1 file in L5 - ASSERT_EQ("0,0,0,0,0,1,2", FilesPerLevel(0)); - - ASSERT_EQ(trivial_move, 6); - ASSERT_EQ(non_trivial_move, 0); - - ROCKSDB_NAMESPACE::port::Thread threads([&] { - compact_options.change_level = false; - compact_options.exclusive_manual_compaction = false; - std::string begin_string = Key(0); - std::string end_string = Key(199); - Slice begin(begin_string); - Slice end(end_string); - // First non-trivial compaction is triggered - ASSERT_OK(db_->CompactRange(compact_options, &begin, &end)); - }); - - TEST_SYNC_POINT("DBCompaction::ManualPartial:1"); - // file 4 [300 => 400) - for (int32_t i = 300; i <= 400; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // file 5 [400 => 500) - for (int32_t i = 400; i <= 500; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // file 6 [500 => 600) - for (int32_t i = 500; i <= 600; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - // Second non-trivial compaction is triggered - ASSERT_OK(Flush()); - - // Before two non-trivial compactions are installed, there are 3 files in L0 - ASSERT_EQ("3,0,0,0,0,1,2", FilesPerLevel(0)); - TEST_SYNC_POINT("DBCompaction::ManualPartial:5"); - - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // After two non-trivial compactions are installed, there is 1 file in L6, and - // 1 file in L1 - ASSERT_EQ("0,1,0,0,0,0,1", FilesPerLevel(0)); - threads.join(); - - for (int32_t i = 0; i < 600; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } -} - -// Disable as the test is flaky. -TEST_F(DBCompactionTest, DISABLED_ManualPartialFill) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - bool first = true; - bool second = true; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBCompaction::PartialFill:4", "DBCompaction::PartialFill:1"}, - {"DBCompaction::PartialFill:2", "DBCompaction::PartialFill:3"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { - if (first) { - TEST_SYNC_POINT("DBCompaction::PartialFill:4"); - first = false; - TEST_SYNC_POINT("DBCompaction::PartialFill:3"); - } else if (second) { - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.write_buffer_size = 10 * 1024 * 1024; - options.max_bytes_for_level_multiplier = 2; - options.num_levels = 4; - options.level0_file_num_compaction_trigger = 3; - options.max_background_compactions = 3; - - DestroyAndReopen(options); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - int32_t value_size = 10 * 1024; // 10 KB - - // Add 2 non-overlapping files - Random rnd(301); - std::map values; - - // file 1 [0 => 100] - for (int32_t i = 0; i < 100; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // file 2 [100 => 300] - for (int32_t i = 100; i < 300; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // 2 files in L0 - ASSERT_EQ("2", FilesPerLevel(0)); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - // 2 files in L2 - ASSERT_EQ("0,0,2", FilesPerLevel(0)); - - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - // file 3 [ 0 => 200] - for (int32_t i = 0; i < 200; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // 2 files in L2, 1 in L0 - ASSERT_EQ("1,0,2", FilesPerLevel(0)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, false)); - // 2 files in L2, 1 in L1 - ASSERT_EQ("0,1,2", FilesPerLevel(0)); - - ASSERT_EQ(trivial_move, 2); - ASSERT_EQ(non_trivial_move, 0); - - ROCKSDB_NAMESPACE::port::Thread threads([&] { - compact_options.change_level = false; - compact_options.exclusive_manual_compaction = false; - std::string begin_string = Key(0); - std::string end_string = Key(199); - Slice begin(begin_string); - Slice end(end_string); - ASSERT_OK(db_->CompactRange(compact_options, &begin, &end)); - }); - - TEST_SYNC_POINT("DBCompaction::PartialFill:1"); - // Many files 4 [300 => 4300) - for (int32_t i = 0; i <= 5; i++) { - for (int32_t j = 300; j < 4300; j++) { - if (j == 2300) { - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - values[j] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(j), values[j])); - } - } - - // Verify level sizes - uint64_t target_size = 4 * options.max_bytes_for_level_base; - for (int32_t i = 1; i < options.num_levels; i++) { - ASSERT_LE(SizeAtLevel(i), target_size); - target_size = static_cast(target_size * - options.max_bytes_for_level_multiplier); - } - - TEST_SYNC_POINT("DBCompaction::PartialFill:2"); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - threads.join(); - - for (int32_t i = 0; i < 4300; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } -} - -TEST_F(DBCompactionTest, ManualCompactionWithUnorderedWrite) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL", - "DBCompactionTest::ManualCompactionWithUnorderedWrite:WaitWriteWAL"}, - {"DBImpl::WaitForPendingWrites:BeforeBlock", - "DBImpl::WriteImpl:BeforeUnorderedWriteMemtable"}}); - - Options options = CurrentOptions(); - options.unordered_write = true; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("bar", "v1")); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer([&]() { ASSERT_OK(Put("foo", "v2")); }); - - TEST_SYNC_POINT( - "DBCompactionTest::ManualCompactionWithUnorderedWrite:WaitWriteWAL"); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - writer.join(); - ASSERT_EQ(Get("foo"), "v2"); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - Reopen(options); - ASSERT_EQ(Get("foo"), "v2"); -} - -TEST_F(DBCompactionTest, DeleteFileRange) { - Options options = CurrentOptions(); - options.write_buffer_size = 10 * 1024 * 1024; - options.max_bytes_for_level_multiplier = 2; - options.num_levels = 4; - options.level0_file_num_compaction_trigger = 3; - options.max_background_compactions = 3; - - DestroyAndReopen(options); - int32_t value_size = 10 * 1024; // 10 KB - - // Add 2 non-overlapping files - Random rnd(301); - std::map values; - - // file 1 [0 => 100] - for (int32_t i = 0; i < 100; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // file 2 [100 => 300] - for (int32_t i = 100; i < 300; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // 2 files in L0 - ASSERT_EQ("2", FilesPerLevel(0)); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - // 2 files in L2 - ASSERT_EQ("0,0,2", FilesPerLevel(0)); - - // file 3 [ 0 => 200] - for (int32_t i = 0; i < 200; i++) { - values[i] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - // Many files 4 [300 => 4300) - for (int32_t i = 0; i <= 5; i++) { - for (int32_t j = 300; j < 4300; j++) { - if (j == 2300) { - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - values[j] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(j), values[j])); - } - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Verify level sizes - uint64_t target_size = 4 * options.max_bytes_for_level_base; - for (int32_t i = 1; i < options.num_levels; i++) { - ASSERT_LE(SizeAtLevel(i), target_size); - target_size = static_cast(target_size * - options.max_bytes_for_level_multiplier); - } - - const size_t old_num_files = CountFiles(); - std::string begin_string = Key(1000); - std::string end_string = Key(2000); - Slice begin(begin_string); - Slice end(end_string); - ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end)); - - int32_t deleted_count = 0; - for (int32_t i = 0; i < 4300; i++) { - if (i < 1000 || i > 2000) { - ASSERT_EQ(Get(Key(i)), values[i]); - } else { - ReadOptions roptions; - std::string result; - Status s = db_->Get(roptions, Key(i), &result); - ASSERT_TRUE(s.IsNotFound() || s.ok()); - if (s.IsNotFound()) { - deleted_count++; - } - } - } - ASSERT_GT(deleted_count, 0); - begin_string = Key(5000); - end_string = Key(6000); - Slice begin1(begin_string); - Slice end1(end_string); - // Try deleting files in range which contain no keys - ASSERT_OK( - DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin1, &end1)); - - // Push data from level 0 to level 1 to force all data to be deleted - // Note that we don't delete level 0 files - compact_options.change_level = true; - compact_options.target_level = 1; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_OK( - DeleteFilesInRange(db_, db_->DefaultColumnFamily(), nullptr, nullptr)); - - int32_t deleted_count2 = 0; - for (int32_t i = 0; i < 4300; i++) { - ReadOptions roptions; - std::string result; - ASSERT_TRUE(db_->Get(roptions, Key(i), &result).IsNotFound()); - deleted_count2++; - } - ASSERT_GT(deleted_count2, deleted_count); - const size_t new_num_files = CountFiles(); - ASSERT_GT(old_num_files, new_num_files); -} - -TEST_F(DBCompactionTest, DeleteFilesInRanges) { - Options options = CurrentOptions(); - options.write_buffer_size = 10 * 1024 * 1024; - options.max_bytes_for_level_multiplier = 2; - options.num_levels = 4; - options.max_background_compactions = 3; - options.disable_auto_compactions = true; - - DestroyAndReopen(options); - int32_t value_size = 10 * 1024; // 10 KB - - Random rnd(301); - std::map values; - - // file [0 => 100), [100 => 200), ... [900, 1000) - for (auto i = 0; i < 10; i++) { - for (auto j = 0; j < 100; j++) { - auto k = i * 100 + j; - values[k] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(k), values[k])); - } - ASSERT_OK(Flush()); - } - ASSERT_EQ("10", FilesPerLevel(0)); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,0,10", FilesPerLevel(0)); - - // file [0 => 100), [200 => 300), ... [800, 900) - for (auto i = 0; i < 10; i += 2) { - for (auto j = 0; j < 100; j++) { - auto k = i * 100 + j; - ASSERT_OK(Put(Key(k), values[k])); - } - ASSERT_OK(Flush()); - } - ASSERT_EQ("5,0,10", FilesPerLevel(0)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_EQ("0,5,10", FilesPerLevel(0)); - - // Delete files in range [0, 299] (inclusive) - { - auto begin_str1 = Key(0), end_str1 = Key(100); - auto begin_str2 = Key(100), end_str2 = Key(200); - auto begin_str3 = Key(200), end_str3 = Key(299); - Slice begin1(begin_str1), end1(end_str1); - Slice begin2(begin_str2), end2(end_str2); - Slice begin3(begin_str3), end3(end_str3); - std::vector ranges; - ranges.push_back(RangePtr(&begin1, &end1)); - ranges.push_back(RangePtr(&begin2, &end2)); - ranges.push_back(RangePtr(&begin3, &end3)); - ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), - ranges.data(), ranges.size())); - ASSERT_EQ("0,3,7", FilesPerLevel(0)); - - // Keys [0, 300) should not exist. - for (auto i = 0; i < 300; i++) { - ReadOptions ropts; - std::string result; - auto s = db_->Get(ropts, Key(i), &result); - ASSERT_TRUE(s.IsNotFound()); - } - for (auto i = 300; i < 1000; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } - } - - // Delete files in range [600, 999) (exclusive) - { - auto begin_str1 = Key(600), end_str1 = Key(800); - auto begin_str2 = Key(700), end_str2 = Key(900); - auto begin_str3 = Key(800), end_str3 = Key(999); - Slice begin1(begin_str1), end1(end_str1); - Slice begin2(begin_str2), end2(end_str2); - Slice begin3(begin_str3), end3(end_str3); - std::vector ranges; - ranges.push_back(RangePtr(&begin1, &end1)); - ranges.push_back(RangePtr(&begin2, &end2)); - ranges.push_back(RangePtr(&begin3, &end3)); - ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), - ranges.data(), ranges.size(), false)); - ASSERT_EQ("0,1,4", FilesPerLevel(0)); - - // Keys [600, 900) should not exist. - for (auto i = 600; i < 900; i++) { - ReadOptions ropts; - std::string result; - auto s = db_->Get(ropts, Key(i), &result); - ASSERT_TRUE(s.IsNotFound()); - } - for (auto i = 300; i < 600; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } - for (auto i = 900; i < 1000; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } - } - - // Delete all files. - { - RangePtr range; - ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1)); - ASSERT_EQ("", FilesPerLevel(0)); - - for (auto i = 0; i < 1000; i++) { - ReadOptions ropts; - std::string result; - auto s = db_->Get(ropts, Key(i), &result); - ASSERT_TRUE(s.IsNotFound()); - } - } -} - -TEST_F(DBCompactionTest, DeleteFileRangeFileEndpointsOverlapBug) { - // regression test for #2833: groups of files whose user-keys overlap at the - // endpoints could be split by `DeleteFilesInRange`. This caused old data to - // reappear, either because a new version of the key was removed, or a range - // deletion was partially dropped. It could also cause non-overlapping - // invariant to be violated if the files dropped by DeleteFilesInRange were - // a subset of files that a range deletion spans. - const int kNumL0Files = 2; - const int kValSize = 8 << 10; // 8KB - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - options.target_file_size_base = 1 << 10; // 1KB - DestroyAndReopen(options); - - // The snapshot prevents key 1 from having its old version dropped. The low - // `target_file_size_base` ensures two keys will be in each output file. - const Snapshot* snapshot = nullptr; - Random rnd(301); - // The value indicates which flush the key belonged to, which is enough - // for us to determine the keys' relative ages. After L0 flushes finish, - // files look like: - // - // File 0: 0 -> vals[0], 1 -> vals[0] - // File 1: 1 -> vals[1], 2 -> vals[1] - // - // Then L0->L1 compaction happens, which outputs keys as follows: - // - // File 0: 0 -> vals[0], 1 -> vals[1] - // File 1: 1 -> vals[0], 2 -> vals[1] - // - // DeleteFilesInRange shouldn't be allowed to drop just file 0, as that - // would cause `1 -> vals[0]` (an older key) to reappear. - std::string vals[kNumL0Files]; - for (int i = 0; i < kNumL0Files; ++i) { - vals[i] = rnd.RandomString(kValSize); - ASSERT_OK(Put(Key(i), vals[i])); - ASSERT_OK(Put(Key(i + 1), vals[i])); - ASSERT_OK(Flush()); - if (i == 0) { - snapshot = db_->GetSnapshot(); - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Verify `DeleteFilesInRange` can't drop only file 0 which would cause - // "1 -> vals[0]" to reappear. - std::string begin_str = Key(0), end_str = Key(1); - Slice begin = begin_str, end = end_str; - ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end)); - ASSERT_EQ(vals[1], Get(Key(1))); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; - options.max_subcompactions = max_subcompactions_; - DestroyAndReopen(options); - - int32_t value_size = 10 * 1024; // 10 KB - - Random rnd(301); - std::vector values; - // File with keys [ 0 => 99 ] - for (int i = 0; i < 100; i++) { - values.push_back(rnd.RandomString(value_size)); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - ASSERT_EQ("1", FilesPerLevel(0)); - // Compaction will do L0=>L1 (trivial move) then move L1 files to L3 - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 3; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,0,0,1", FilesPerLevel(0)); - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - // File with keys [ 100 => 199 ] - for (int i = 100; i < 200; i++) { - values.push_back(rnd.RandomString(value_size)); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Flush()); - - ASSERT_EQ("1,0,0,1", FilesPerLevel(0)); - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - // Compaction will do L0=>L1 L1=>L2 L2=>L3 (3 trivial moves) - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_EQ("0,0,0,2", FilesPerLevel(0)); - ASSERT_EQ(trivial_move, 4); - ASSERT_EQ(non_trivial_move, 0); - - for (int i = 0; i < 200; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBCompactionTestWithParam, LevelCompactionThirdPath) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 500 * 1024); - options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024); - options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_style = kCompactionStyleLevel; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 4; - options.max_bytes_for_level_base = 400 * 1024; - options.max_subcompactions = max_subcompactions_; - - DestroyAndReopen(options); - - Random rnd(301); - int key_idx = 0; - - // First three 110KB files are not going to second path. - // After that, (100K, 200K) - for (int num = 0; num < 3; num++) { - GenerateNewFile(&rnd, &key_idx); - } - - // Another 110KB triggers a compaction to 400K file to fill up first path - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(3, GetSstFileCount(options.db_paths[1].path)); - - // (1, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4", FilesPerLevel(0)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 1) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,1", FilesPerLevel(0)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 2) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,2", FilesPerLevel(0)); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 3) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,3", FilesPerLevel(0)); - ASSERT_EQ(3, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,4", FilesPerLevel(0)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 5) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,5", FilesPerLevel(0)); - ASSERT_EQ(5, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 6) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,6", FilesPerLevel(0)); - ASSERT_EQ(6, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 7) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,7", FilesPerLevel(0)); - ASSERT_EQ(7, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 4, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,8", FilesPerLevel(0)); - ASSERT_EQ(8, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(4, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Reopen(options); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Destroy(options); -} - -TEST_P(DBCompactionTestWithParam, LevelCompactionPathUse) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 500 * 1024); - options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024); - options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_style = kCompactionStyleLevel; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 4; - options.max_bytes_for_level_base = 400 * 1024; - options.max_subcompactions = max_subcompactions_; - - DestroyAndReopen(options); - - Random rnd(301); - int key_idx = 0; - - // Always gets compacted into 1 Level1 file, - // 0/1 Level 0 file - for (int num = 0; num < 3; num++) { - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - } - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,1", FilesPerLevel(0)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("0,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("0,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("0,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("0,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,1", FilesPerLevel(0)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Reopen(options); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Destroy(options); -} - -TEST_P(DBCompactionTestWithParam, LevelCompactionCFPathUse) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 500 * 1024); - options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024); - options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_style = kCompactionStyleLevel; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 4; - options.max_bytes_for_level_base = 400 * 1024; - options.max_subcompactions = max_subcompactions_; - - std::vector option_vector; - option_vector.emplace_back(options); - ColumnFamilyOptions cf_opt1(options), cf_opt2(options); - // Configure CF1 specific paths. - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 500 * 1024); - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 4 * 1024 * 1024); - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_3", 1024 * 1024 * 1024); - option_vector.emplace_back(DBOptions(options), cf_opt1); - CreateColumnFamilies({"one"}, option_vector[1]); - - // Configure CF2 specific paths. - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2", 500 * 1024); - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_2", 4 * 1024 * 1024); - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_3", 1024 * 1024 * 1024); - option_vector.emplace_back(DBOptions(options), cf_opt2); - CreateColumnFamilies({"two"}, option_vector[2]); - - ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); - - Random rnd(301); - int key_idx = 0; - int key_idx1 = 0; - int key_idx2 = 0; - - auto generate_file = [&]() { - GenerateNewFile(0, &rnd, &key_idx); - GenerateNewFile(1, &rnd, &key_idx1); - GenerateNewFile(2, &rnd, &key_idx2); - }; - - auto check_sstfilecount = [&](int path_id, int expected) { - ASSERT_EQ(expected, GetSstFileCount(options.db_paths[path_id].path)); - ASSERT_EQ(expected, GetSstFileCount(cf_opt1.cf_paths[path_id].path)); - ASSERT_EQ(expected, GetSstFileCount(cf_opt2.cf_paths[path_id].path)); - }; - - auto check_filesperlevel = [&](const std::string& expected) { - ASSERT_EQ(expected, FilesPerLevel(0)); - ASSERT_EQ(expected, FilesPerLevel(1)); - ASSERT_EQ(expected, FilesPerLevel(2)); - }; - - auto check_getvalues = [&]() { - for (int i = 0; i < key_idx; i++) { - auto v = Get(0, Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - for (int i = 0; i < key_idx1; i++) { - auto v = Get(1, Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - for (int i = 0; i < key_idx2; i++) { - auto v = Get(2, Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - }; - - // Check that default column family uses db_paths. - // And Column family "one" uses cf_paths. - - // The compaction in level0 outputs the sst files in level1. - // The first path cannot hold level1's data(400KB+400KB > 500KB), - // so every compaction move a sst file to second path. Please - // refer to LevelCompactionBuilder::GetPathId. - for (int num = 0; num < 3; num++) { - generate_file(); - } - check_sstfilecount(0, 1); - check_sstfilecount(1, 2); - - generate_file(); - check_sstfilecount(1, 3); - - // (1, 4) - generate_file(); - check_filesperlevel("1,4"); - check_sstfilecount(1, 4); - check_sstfilecount(0, 1); - - // (1, 4, 1) - generate_file(); - check_filesperlevel("1,4,1"); - check_sstfilecount(2, 1); - check_sstfilecount(1, 4); - check_sstfilecount(0, 1); - - // (1, 4, 2) - generate_file(); - check_filesperlevel("1,4,2"); - check_sstfilecount(2, 2); - check_sstfilecount(1, 4); - check_sstfilecount(0, 1); - - check_getvalues(); - - { // Also verify GetLiveFilesStorageInfo with db_paths / cf_paths - std::vector new_infos; - LiveFilesStorageInfoOptions lfsio; - lfsio.wal_size_for_flush = UINT64_MAX; // no flush - ASSERT_OK(db_->GetLiveFilesStorageInfo(lfsio, &new_infos)); - std::unordered_map live_sst_by_dir; - for (auto& info : new_infos) { - if (info.file_type == kTableFile) { - live_sst_by_dir[info.directory]++; - // Verify file on disk (no directory confusion) - uint64_t size; - ASSERT_OK(env_->GetFileSize( - info.directory + "/" + info.relative_filename, &size)); - ASSERT_EQ(info.size, size); - } - } - ASSERT_EQ(3U * 3U, live_sst_by_dir.size()); - for (auto& paths : {options.db_paths, cf_opt1.cf_paths, cf_opt2.cf_paths}) { - ASSERT_EQ(1, live_sst_by_dir[paths[0].path]); - ASSERT_EQ(4, live_sst_by_dir[paths[1].path]); - ASSERT_EQ(2, live_sst_by_dir[paths[2].path]); - } - } - - ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); - - check_getvalues(); - - Destroy(options, true); -} - -TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) { - Random rnd(301); - int max_key_level_insert = 200; - int max_key_universal_insert = 600; - - // Stage 1: generate a db with level compaction - Options options = CurrentOptions(); - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.num_levels = 4; - options.level0_file_num_compaction_trigger = 3; - options.max_bytes_for_level_base = 500 << 10; // 500KB - options.max_bytes_for_level_multiplier = 1; - options.target_file_size_base = 200 << 10; // 200KB - options.target_file_size_multiplier = 1; - options.max_subcompactions = max_subcompactions_; - CreateAndReopenWithCF({"pikachu"}, options); - - for (int i = 0; i <= max_key_level_insert; i++) { - // each value is 10K - ASSERT_OK(Put(1, Key(i), rnd.RandomString(10000))); - } - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(TotalTableFiles(1, 4), 1); - int non_level0_num_files = 0; - for (int i = 1; i < options.num_levels; i++) { - non_level0_num_files += NumTableFilesAtLevel(i, 1); - } - ASSERT_GT(non_level0_num_files, 0); - - // Stage 2: reopen with universal compaction - should fail - options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - options = CurrentOptions(options); - Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_TRUE(s.IsInvalidArgument()); - - // Stage 3: compact into a single file and move the file to level 0 - options = CurrentOptions(); - options.disable_auto_compactions = true; - options.target_file_size_base = INT_MAX; - options.target_file_size_multiplier = 1; - options.max_bytes_for_level_base = INT_MAX; - options.max_bytes_for_level_multiplier = 1; - options.num_levels = 4; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 0; - // cannot use kForceOptimized here because the compaction here is expected - // to generate one output file - compact_options.bottommost_level_compaction = - BottommostLevelCompaction::kForce; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK( - dbfull()->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - - // Only 1 file in L0 - ASSERT_EQ("1", FilesPerLevel(1)); - - // Stage 4: re-open in universal compaction style and do some db operations - options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 4; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 3; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - options.num_levels = 1; - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - for (int i = max_key_level_insert / 2; i <= max_key_universal_insert; i++) { - ASSERT_OK(Put(1, Key(i), rnd.RandomString(10000))); - } - ASSERT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - for (int i = 1; i < options.num_levels; i++) { - ASSERT_EQ(NumTableFilesAtLevel(i, 1), 0); - } - - // verify keys inserted in both level compaction style and universal - // compaction style - std::string keys_in_db; - Iterator* iter = dbfull()->NewIterator(ReadOptions(), handles_[1]); - ASSERT_OK(iter->status()); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - keys_in_db.append(iter->key().ToString()); - keys_in_db.push_back(','); - } - delete iter; - - std::string expected_keys; - for (int i = 0; i <= max_key_universal_insert; i++) { - expected_keys.append(Key(i)); - expected_keys.push_back(','); - } - - ASSERT_EQ(keys_in_db, expected_keys); -} - -TEST_F(DBCompactionTest, L0_CompactionBug_Issue44_a) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "b", "v")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Delete(1, "b")); - ASSERT_OK(Delete(1, "a")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Delete(1, "a")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "a", "v")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("(a->v)", Contents(1)); - env_->SleepForMicroseconds(1000000); // Wait for compaction to finish - ASSERT_EQ("(a->v)", Contents(1)); - } while (ChangeCompactOptions()); -} - -TEST_F(DBCompactionTest, L0_CompactionBug_Issue44_b) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "", "")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Delete(1, "e")); - ASSERT_OK(Put(1, "", "")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "c", "cv")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "", "")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "", "")); - env_->SleepForMicroseconds(1000000); // Wait for compaction to finish - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "d", "dv")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "", "")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Delete(1, "d")); - ASSERT_OK(Delete(1, "b")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("(->)(c->cv)", Contents(1)); - env_->SleepForMicroseconds(1000000); // Wait for compaction to finish - ASSERT_EQ("(->)(c->cv)", Contents(1)); - } while (ChangeCompactOptions()); -} - -TEST_F(DBCompactionTest, ManualAutoRace) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BGWorkCompaction", "DBCompactionTest::ManualAutoRace:1"}, - {"DBImpl::RunManualCompaction:WaitScheduled", - "BackgroundCallCompaction:0"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(1, "foo", "")); - ASSERT_OK(Put(1, "bar", "")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "")); - ASSERT_OK(Put(1, "bar", "")); - // Generate four files in CF 0, which should trigger an auto compaction - ASSERT_OK(Put("foo", "")); - ASSERT_OK(Put("bar", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "")); - ASSERT_OK(Put("bar", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "")); - ASSERT_OK(Put("bar", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "")); - ASSERT_OK(Put("bar", "")); - ASSERT_OK(Flush()); - - // The auto compaction is scheduled but waited until here - TEST_SYNC_POINT("DBCompactionTest::ManualAutoRace:1"); - // The auto compaction will wait until the manual compaction is registerd - // before processing so that it will be cancelled. - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - ASSERT_OK(dbfull()->CompactRange(cro, handles_[1], nullptr, nullptr)); - ASSERT_EQ("0,1", FilesPerLevel(1)); - - // Eventually the cancelled compaction will be rescheduled and executed. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,1", FilesPerLevel(0)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBCompactionTestWithParam, ManualCompaction) { - Options options = CurrentOptions(); - options.max_subcompactions = max_subcompactions_; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - CreateAndReopenWithCF({"pikachu"}, options); - - // iter - 0 with 7 levels - // iter - 1 with 3 levels - for (int iter = 0; iter < 2; ++iter) { - MakeTables(3, "p", "q", 1); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - - // Compaction range falls before files - Compact(1, "", "c"); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - - // Compaction range falls after files - Compact(1, "r", "z"); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - - // Compaction range overlaps files - Compact(1, "p", "q"); - ASSERT_EQ("0,0,1", FilesPerLevel(1)); - - // Populate a different range - MakeTables(3, "c", "e", 1); - ASSERT_EQ("1,1,2", FilesPerLevel(1)); - - // Compact just the new range - Compact(1, "b", "f"); - ASSERT_EQ("0,0,2", FilesPerLevel(1)); - - // Compact all - MakeTables(1, "a", "z", 1); - ASSERT_EQ("1,0,2", FilesPerLevel(1)); - - uint64_t prev_block_cache_add = - options.statistics->getTickerCount(BLOCK_CACHE_ADD); - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr)); - // Verify manual compaction doesn't fill block cache - ASSERT_EQ(prev_block_cache_add, - options.statistics->getTickerCount(BLOCK_CACHE_ADD)); - - ASSERT_EQ("0,0,1", FilesPerLevel(1)); - - if (iter == 0) { - options = CurrentOptions(); - options.num_levels = 3; - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - } - } -} - -TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_ + "_2", 2 * 10485760); - options.db_paths.emplace_back(dbname_ + "_3", 100 * 10485760); - options.db_paths.emplace_back(dbname_ + "_4", 120 * 10485760); - options.max_subcompactions = max_subcompactions_; - CreateAndReopenWithCF({"pikachu"}, options); - - // iter - 0 with 7 levels - // iter - 1 with 3 levels - for (int iter = 0; iter < 2; ++iter) { - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put(1, "p", "begin")); - ASSERT_OK(Put(1, "q", "end")); - ASSERT_OK(Flush(1)); - } - ASSERT_EQ("3", FilesPerLevel(1)); - ASSERT_EQ(3, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // Compaction range falls before files - Compact(1, "", "c"); - ASSERT_EQ("3", FilesPerLevel(1)); - - // Compaction range falls after files - Compact(1, "r", "z"); - ASSERT_EQ("3", FilesPerLevel(1)); - - // Compaction range overlaps files - Compact(1, "p", "q", 1); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,1", FilesPerLevel(1)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // Populate a different range - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put(1, "c", "begin")); - ASSERT_OK(Put(1, "e", "end")); - ASSERT_OK(Flush(1)); - } - ASSERT_EQ("3,1", FilesPerLevel(1)); - - // Compact just the new range - Compact(1, "b", "f", 1); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,2", FilesPerLevel(1)); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // Compact all - ASSERT_OK(Put(1, "a", "begin")); - ASSERT_OK(Put(1, "z", "end")); - ASSERT_OK(Flush(1)); - ASSERT_EQ("1,2", FilesPerLevel(1)); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path)); - CompactRangeOptions compact_options; - compact_options.target_path_id = 1; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("0,1", FilesPerLevel(1)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - if (iter == 0) { - DestroyAndReopen(options); - options = CurrentOptions(); - options.db_paths.emplace_back(dbname_ + "_2", 2 * 10485760); - options.db_paths.emplace_back(dbname_ + "_3", 100 * 10485760); - options.db_paths.emplace_back(dbname_ + "_4", 120 * 10485760); - options.max_background_flushes = 1; - options.num_levels = 3; - options.create_if_missing = true; - CreateAndReopenWithCF({"pikachu"}, options); - } - } -} - -TEST_F(DBCompactionTest, FilesDeletedAfterCompaction) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v2")); - Compact(1, "a", "z"); - const size_t num_files = CountLiveFiles(); - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(1, "foo", "v2")); - Compact(1, "a", "z"); - } - ASSERT_EQ(CountLiveFiles(), num_files); - } while (ChangeCompactOptions()); -} - -// Check level comapction with compact files -TEST_P(DBCompactionTestWithParam, DISABLED_CompactFilesOnLevelCompaction) { - const int kTestKeySize = 16; - const int kTestValueSize = 984; - const int kEntrySize = kTestKeySize + kTestValueSize; - const int kEntriesPerBuffer = 100; - Options options; - options.create_if_missing = true; - options.write_buffer_size = kEntrySize * kEntriesPerBuffer; - options.compaction_style = kCompactionStyleLevel; - options.target_file_size_base = options.write_buffer_size; - options.max_bytes_for_level_base = options.target_file_size_base * 2; - options.level0_stop_writes_trigger = 2; - options.max_bytes_for_level_multiplier = 2; - options.compression = kNoCompression; - options.max_subcompactions = max_subcompactions_; - options = CurrentOptions(options); - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - for (int key = 64 * kEntriesPerBuffer; key >= 0; --key) { - ASSERT_OK(Put(1, std::to_string(key), rnd.RandomString(kTestValueSize))); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ColumnFamilyMetaData cf_meta; - dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta); - int output_level = static_cast(cf_meta.levels.size()) - 1; - for (int file_picked = 5; file_picked > 0; --file_picked) { - std::set overlapping_file_names; - std::vector compaction_input_file_names; - for (int f = 0; f < file_picked; ++f) { - int level = 0; - auto file_meta = PickFileRandomly(cf_meta, &rnd, &level); - compaction_input_file_names.push_back(file_meta->name); - GetOverlappingFileNumbersForLevelCompaction( - cf_meta, options.comparator, level, output_level, file_meta, - &overlapping_file_names); - } - - ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), handles_[1], - compaction_input_file_names, - output_level)); - - // Make sure all overlapping files do not exist after compaction - dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta); - VerifyCompactionResult(cf_meta, overlapping_file_names); - } - - // make sure all key-values are still there. - for (int key = 64 * kEntriesPerBuffer; key >= 0; --key) { - ASSERT_NE(Get(1, std::to_string(key)), "NOT_FOUND"); - } -} - -TEST_P(DBCompactionTestWithParam, PartialCompactionFailure) { - Options options; - const int kKeySize = 16; - const int kKvSize = 1000; - const int kKeysPerBuffer = 100; - const int kNumL1Files = 5; - options.create_if_missing = true; - options.write_buffer_size = kKeysPerBuffer * kKvSize; - options.max_write_buffer_number = 2; - options.target_file_size_base = - options.write_buffer_size * (options.max_write_buffer_number - 1); - options.level0_file_num_compaction_trigger = kNumL1Files; - options.max_bytes_for_level_base = - options.level0_file_num_compaction_trigger * - options.target_file_size_base; - options.max_bytes_for_level_multiplier = 2; - options.compression = kNoCompression; - options.max_subcompactions = max_subcompactions_; - - env_->SetBackgroundThreads(1, Env::HIGH); - env_->SetBackgroundThreads(1, Env::LOW); - // stop the compaction thread until we simulate the file creation failure. - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - options.env = env_; - - DestroyAndReopen(options); - - const int kNumInsertedKeys = options.level0_file_num_compaction_trigger * - (options.max_write_buffer_number - 1) * - kKeysPerBuffer; - - Random rnd(301); - std::vector keys; - std::vector values; - for (int k = 0; k < kNumInsertedKeys; ++k) { - keys.emplace_back(rnd.RandomString(kKeySize)); - values.emplace_back(rnd.RandomString(kKvSize - kKeySize)); - ASSERT_OK(Put(Slice(keys[k]), Slice(values[k]))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - - ASSERT_OK(dbfull()->TEST_FlushMemTable(true)); - // Make sure the number of L0 files can trigger compaction. - ASSERT_GE(NumTableFilesAtLevel(0), - options.level0_file_num_compaction_trigger); - - auto previous_num_level0_files = NumTableFilesAtLevel(0); - - // Fail the first file creation. - env_->non_writable_count_ = 1; - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - - // Expect compaction to fail here as one file will fail its - // creation. - ASSERT_TRUE(!dbfull()->TEST_WaitForCompact().ok()); - - // Verify L0 -> L1 compaction does fail. - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - - // Verify all L0 files are still there. - ASSERT_EQ(NumTableFilesAtLevel(0), previous_num_level0_files); - - // All key-values must exist after compaction fails. - for (int k = 0; k < kNumInsertedKeys; ++k) { - ASSERT_EQ(values[k], Get(keys[k])); - } - - env_->non_writable_count_ = 0; - - // Make sure RocksDB will not get into corrupted state. - Reopen(options); - - // Verify again after reopen. - for (int k = 0; k < kNumInsertedKeys; ++k) { - ASSERT_EQ(values[k], Get(keys[k])); - } -} - -TEST_P(DBCompactionTestWithParam, DeleteMovedFileAfterCompaction) { - // iter 1 -- delete_obsolete_files_period_micros == 0 - for (int iter = 0; iter < 2; ++iter) { - // This test triggers move compaction and verifies that the file is not - // deleted when it's part of move compaction - Options options = CurrentOptions(); - options.env = env_; - if (iter == 1) { - options.delete_obsolete_files_period_micros = 0; - } - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = - 2; // trigger compaction when we have 2 files - OnFileDeletionListener* listener = new OnFileDeletionListener(); - options.listeners.emplace_back(listener); - options.max_subcompactions = max_subcompactions_; - DestroyAndReopen(options); - - Random rnd(301); - // Create two 1MB sst files - for (int i = 0; i < 2; ++i) { - // Create 1MB sst file - for (int j = 0; j < 100; ++j) { - ASSERT_OK(Put(Key(i * 50 + j), rnd.RandomString(10 * 1024))); - } - ASSERT_OK(Flush()); - } - // this should execute L0->L1 - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,1", FilesPerLevel(0)); - - // block compactions - test::SleepingBackgroundTask sleeping_task; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::LOW); - - options.max_bytes_for_level_base = 1024 * 1024; // 1 MB - Reopen(options); - std::unique_ptr iterator(db_->NewIterator(ReadOptions())); - ASSERT_EQ("0,1", FilesPerLevel(0)); - // let compactions go - sleeping_task.WakeUp(); - sleeping_task.WaitUntilDone(); - - // this should execute L1->L2 (move) - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("0,0,1", FilesPerLevel(0)); - - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 1U); - auto moved_file_name = metadata[0].name; - - // Create two more 1MB sst files - for (int i = 0; i < 2; ++i) { - // Create 1MB sst file - for (int j = 0; j < 100; ++j) { - ASSERT_OK(Put(Key(i * 50 + j + 100), rnd.RandomString(10 * 1024))); - } - ASSERT_OK(Flush()); - } - // this should execute both L0->L1 and L1->L2 (merge with previous file) - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("0,0,2", FilesPerLevel(0)); - - // iterator is holding the file - ASSERT_OK(env_->FileExists(dbname_ + moved_file_name)); - - listener->SetExpectedFileName(dbname_ + moved_file_name); - ASSERT_OK(iterator->status()); - iterator.reset(); - - // this file should have been compacted away - ASSERT_NOK(env_->FileExists(dbname_ + moved_file_name)); - listener->VerifyMatchedCount(1); - } -} - -TEST_P(DBCompactionTestWithParam, CompressLevelCompaction) { - if (!Zlib_Supported()) { - return; - } - Options options = CurrentOptions(); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_style = kCompactionStyleLevel; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 4; - options.max_bytes_for_level_base = 400 * 1024; - options.max_subcompactions = max_subcompactions_; - // First two levels have no compression, so that a trivial move between - // them will be allowed. Level 2 has Zlib compression so that a trivial - // move to level 3 will not be allowed - options.compression_per_level = {kNoCompression, kNoCompression, - kZlibCompression}; - int matches = 0, didnt_match = 0, trivial_move = 0, non_trivial = 0; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "Compaction::InputCompressionMatchesOutput:Matches", - [&](void* /*arg*/) { matches++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "Compaction::InputCompressionMatchesOutput:DidntMatch", - [&](void* /*arg*/) { didnt_match++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Reopen(options); - - Random rnd(301); - int key_idx = 0; - - // First three 110KB files are going to level 0 - // After that, (100K, 200K) - for (int num = 0; num < 3; num++) { - GenerateNewFile(&rnd, &key_idx); - } - - // Another 110KB triggers a compaction to 400K file to fill up level 0 - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(4, GetSstFileCount(dbname_)); - - // (1, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4", FilesPerLevel(0)); - - // (1, 4, 1) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,1", FilesPerLevel(0)); - - // (1, 4, 2) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,2", FilesPerLevel(0)); - - // (1, 4, 3) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,3", FilesPerLevel(0)); - - // (1, 4, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,4", FilesPerLevel(0)); - - // (1, 4, 5) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,5", FilesPerLevel(0)); - - // (1, 4, 6) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,6", FilesPerLevel(0)); - - // (1, 4, 7) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,7", FilesPerLevel(0)); - - // (1, 4, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,4,8", FilesPerLevel(0)); - - ASSERT_EQ(matches, 12); - // Currently, the test relies on the number of calls to - // InputCompressionMatchesOutput() per compaction. - const int kCallsToInputCompressionMatch = 2; - ASSERT_EQ(didnt_match, 8 * kCallsToInputCompressionMatch); - ASSERT_EQ(trivial_move, 12); - ASSERT_EQ(non_trivial, 8); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Reopen(options); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Destroy(options); -} - -TEST_F(DBCompactionTest, SanitizeCompactionOptionsTest) { - Options options = CurrentOptions(); - options.max_background_compactions = 5; - options.soft_pending_compaction_bytes_limit = 0; - options.hard_pending_compaction_bytes_limit = 100; - options.create_if_missing = true; - DestroyAndReopen(options); - ASSERT_EQ(100, db_->GetOptions().soft_pending_compaction_bytes_limit); - - options.max_background_compactions = 3; - options.soft_pending_compaction_bytes_limit = 200; - options.hard_pending_compaction_bytes_limit = 150; - DestroyAndReopen(options); - ASSERT_EQ(150, db_->GetOptions().soft_pending_compaction_bytes_limit); -} - -// This tests for a bug that could cause two level0 compactions running -// concurrently -// TODO(aekmekji): Make sure that the reason this fails when run with -// max_subcompactions > 1 is not a correctness issue but just inherent to -// running parallel L0-L1 compactions -TEST_F(DBCompactionTest, SuggestCompactRangeNoTwoLevel0Compactions) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.write_buffer_size = 110 << 10; - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 4; - options.num_levels = 4; - options.compression = kNoCompression; - options.max_bytes_for_level_base = 450 << 10; - options.target_file_size_base = 98 << 10; - options.max_write_buffer_number = 2; - options.max_background_compactions = 2; - - DestroyAndReopen(options); - - // fill up the DB - Random rnd(301); - for (int num = 0; num < 10; num++) { - GenerateNewRandomFile(&rnd); - } - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"CompactionJob::Run():Start", - "DBCompactionTest::SuggestCompactRangeNoTwoLevel0Compactions:1"}, - {"DBCompactionTest::SuggestCompactRangeNoTwoLevel0Compactions:2", - "CompactionJob::Run():End"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // trigger L0 compaction - for (int num = 0; num < options.level0_file_num_compaction_trigger + 1; - num++) { - GenerateNewRandomFile(&rnd, /* nowait */ true); - ASSERT_OK(Flush()); - } - - TEST_SYNC_POINT( - "DBCompactionTest::SuggestCompactRangeNoTwoLevel0Compactions:1"); - - GenerateNewRandomFile(&rnd, /* nowait */ true); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr)); - for (int num = 0; num < options.level0_file_num_compaction_trigger + 1; - num++) { - GenerateNewRandomFile(&rnd, /* nowait */ true); - ASSERT_OK(Flush()); - } - - TEST_SYNC_POINT( - "DBCompactionTest::SuggestCompactRangeNoTwoLevel0Compactions:2"); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); -} - -static std::string ShortKey(int i) { - assert(i < 10000); - char buf[100]; - snprintf(buf, sizeof(buf), "key%04d", i); - return std::string(buf); -} - -TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // The key size is guaranteed to be <= 8 - class ShortKeyComparator : public Comparator { - int Compare(const ROCKSDB_NAMESPACE::Slice& a, - const ROCKSDB_NAMESPACE::Slice& b) const override { - assert(a.size() <= 8); - assert(b.size() <= 8); - return BytewiseComparator()->Compare(a, b); - } - const char* Name() const override { return "ShortKeyComparator"; } - void FindShortestSeparator( - std::string* start, - const ROCKSDB_NAMESPACE::Slice& limit) const override { - return BytewiseComparator()->FindShortestSeparator(start, limit); - } - void FindShortSuccessor(std::string* key) const override { - return BytewiseComparator()->FindShortSuccessor(key); - } - } short_key_cmp; - Options options = CurrentOptions(); - options.target_file_size_base = 100000000; - options.write_buffer_size = 100000000; - options.max_subcompactions = max_subcompactions_; - options.comparator = &short_key_cmp; - DestroyAndReopen(options); - - int32_t value_size = 10 * 1024; // 10 KB - - Random rnd(301); - std::vector values; - // File with keys [ 0 => 99 ] - for (int i = 0; i < 100; i++) { - values.push_back(rnd.RandomString(value_size)); - ASSERT_OK(Put(ShortKey(i), values[i])); - } - ASSERT_OK(Flush()); - - ASSERT_EQ("1", FilesPerLevel(0)); - // Compaction will do L0=>L1 (trivial move) then move L1 files to L3 - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 3; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,0,0,1", FilesPerLevel(0)); - ASSERT_EQ(trivial_move, 1); - ASSERT_EQ(non_trivial_move, 0); - - // File with keys [ 100 => 199 ] - for (int i = 100; i < 200; i++) { - values.push_back(rnd.RandomString(value_size)); - ASSERT_OK(Put(ShortKey(i), values[i])); - } - ASSERT_OK(Flush()); - - ASSERT_EQ("1,0,0,1", FilesPerLevel(0)); - // Compaction will do L0=>L1 L1=>L2 L2=>L3 (3 trivial moves) - // then compacte the bottommost level L3=>L3 (non trivial move) - compact_options = CompactRangeOptions(); - compact_options.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,0,0,1", FilesPerLevel(0)); - ASSERT_EQ(trivial_move, 4); - ASSERT_EQ(non_trivial_move, 1); - - // File with keys [ 200 => 299 ] - for (int i = 200; i < 300; i++) { - values.push_back(rnd.RandomString(value_size)); - ASSERT_OK(Put(ShortKey(i), values[i])); - } - ASSERT_OK(Flush()); - - ASSERT_EQ("1,0,0,1", FilesPerLevel(0)); - trivial_move = 0; - non_trivial_move = 0; - compact_options = CompactRangeOptions(); - compact_options.bottommost_level_compaction = - BottommostLevelCompaction::kSkip; - // Compaction will do L0=>L1 L1=>L2 L2=>L3 (3 trivial moves) - // and will skip bottommost level compaction - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,0,0,2", FilesPerLevel(0)); - ASSERT_EQ(trivial_move, 3); - ASSERT_EQ(non_trivial_move, 0); - - for (int i = 0; i < 300; i++) { - ASSERT_EQ(Get(ShortKey(i)), values[i]); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBCompactionTestWithParam, IntraL0Compaction) { - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = 5; - options.max_background_compactions = 2; - options.max_subcompactions = max_subcompactions_; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.write_buffer_size = 2 << 20; // 2MB - - BlockBasedTableOptions table_options; - table_options.block_cache = NewLRUCache(64 << 20); // 64MB - table_options.cache_index_and_filter_blocks = true; - table_options.pin_l0_filter_and_index_blocks_in_cache = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - - const size_t kValueSize = 1 << 20; - Random rnd(301); - std::string value(rnd.RandomString(kValueSize)); - - // The L0->L1 must be picked before we begin flushing files to trigger - // intra-L0 compaction, and must not finish until after an intra-L0 - // compaction has been picked. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"LevelCompactionPicker::PickCompaction:Return", - "DBCompactionTest::IntraL0Compaction:L0ToL1Ready"}, - {"LevelCompactionPicker::PickCompactionBySize:0", - "CompactionJob::Run():Start"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // index: 0 1 2 3 4 5 6 7 8 9 - // size: 1MB 1MB 1MB 1MB 1MB 2MB 1MB 1MB 1MB 1MB - // score: 1.5 1.3 1.5 2.0 inf - // - // Files 0-4 will be included in an L0->L1 compaction. - // - // L0->L0 will be triggered since the sync points guarantee compaction to base - // level is still blocked when files 5-9 trigger another compaction. - // - // Files 6-9 are the longest span of available files for which - // work-per-deleted-file decreases (see "score" row above). - for (int i = 0; i < 10; ++i) { - ASSERT_OK(Put(Key(0), "")); // prevents trivial move - if (i == 5) { - TEST_SYNC_POINT("DBCompactionTest::IntraL0Compaction:L0ToL1Ready"); - ASSERT_OK(Put(Key(i + 1), value + value)); - } else { - ASSERT_OK(Put(Key(i + 1), value)); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_GE(level_to_files.size(), 2); // at least L0 and L1 - // L0 has the 2MB file (not compacted) and 4MB file (output of L0->L0) - ASSERT_EQ(2, level_to_files[0].size()); - ASSERT_GT(level_to_files[1].size(), 0); - for (int i = 0; i < 2; ++i) { - ASSERT_GE(level_to_files[0][i].fd.file_size, 1 << 21); - } - - // The index/filter in the file produced by intra-L0 should not be pinned. - // That means clearing unref'd entries in block cache and re-accessing the - // file produced by intra-L0 should bump the index block miss count. - uint64_t prev_index_misses = - TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS); - table_options.block_cache->EraseUnRefEntries(); - ASSERT_EQ("", Get(Key(0))); - ASSERT_EQ(prev_index_misses + 1, - TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); -} - -TEST_P(DBCompactionTestWithParam, IntraL0CompactionDoesNotObsoleteDeletions) { - // regression test for issue #2722: L0->L0 compaction can resurrect deleted - // keys from older L0 files if L1+ files' key-ranges do not include the key. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = 5; - options.max_background_compactions = 2; - options.max_subcompactions = max_subcompactions_; - DestroyAndReopen(options); - - const size_t kValueSize = 1 << 20; - Random rnd(301); - std::string value(rnd.RandomString(kValueSize)); - - // The L0->L1 must be picked before we begin flushing files to trigger - // intra-L0 compaction, and must not finish until after an intra-L0 - // compaction has been picked. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"LevelCompactionPicker::PickCompaction:Return", - "DBCompactionTest::IntraL0CompactionDoesNotObsoleteDeletions:" - "L0ToL1Ready"}, - {"LevelCompactionPicker::PickCompactionBySize:0", - "CompactionJob::Run():Start"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // index: 0 1 2 3 4 5 6 7 8 9 - // size: 1MB 1MB 1MB 1MB 1MB 1MB 1MB 1MB 1MB 1MB - // score: 1.25 1.33 1.5 2.0 inf - // - // Files 0-4 will be included in an L0->L1 compaction. - // - // L0->L0 will be triggered since the sync points guarantee compaction to base - // level is still blocked when files 5-9 trigger another compaction. All files - // 5-9 are included in the L0->L0 due to work-per-deleted file decreasing. - // - // Put a key-value in files 0-4. Delete that key in files 5-9. Verify the - // L0->L0 preserves the deletion such that the key remains deleted. - for (int i = 0; i < 10; ++i) { - // key 0 serves both to prevent trivial move and as the key we want to - // verify is not resurrected by L0->L0 compaction. - if (i < 5) { - ASSERT_OK(Put(Key(0), "")); - } else { - ASSERT_OK(Delete(Key(0))); - } - if (i == 5) { - TEST_SYNC_POINT( - "DBCompactionTest::IntraL0CompactionDoesNotObsoleteDeletions:" - "L0ToL1Ready"); - } - ASSERT_OK(Put(Key(i + 1), value)); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_GE(level_to_files.size(), 2); // at least L0 and L1 - // L0 has a single output file from L0->L0 - ASSERT_EQ(1, level_to_files[0].size()); - ASSERT_GT(level_to_files[1].size(), 0); - ASSERT_GE(level_to_files[0][0].fd.file_size, 1 << 22); - - ReadOptions roptions; - std::string result; - ASSERT_TRUE(db_->Get(roptions, Key(0), &result).IsNotFound()); -} - -TEST_P(DBCompactionTestWithParam, FullCompactionInBottomPriThreadPool) { - const int kNumFilesTrigger = 3; - Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM); - for (bool use_universal_compaction : {false, true}) { - Options options = CurrentOptions(); - if (use_universal_compaction) { - options.compaction_style = kCompactionStyleUniversal; - } else { - options.compaction_style = kCompactionStyleLevel; - options.level_compaction_dynamic_level_bytes = true; - } - options.num_levels = 4; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = kNumFilesTrigger; - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - DestroyAndReopen(options); - - int num_bottom_pri_compactions = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkBottomCompaction", - [&](void* /*arg*/) { ++num_bottom_pri_compactions; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int num = 0; num < kNumFilesTrigger; num++) { - ASSERT_EQ(NumSortedRuns(), num); - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(1, num_bottom_pri_compactions); - - // Verify that size amplification did occur - ASSERT_EQ(NumSortedRuns(), 1); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM); -} - -TEST_F(DBCompactionTest, CancelCompactionWaitingOnConflict) { - // This test verifies cancellation of a compaction waiting to be scheduled due - // to conflict with a running compaction. - // - // A `CompactRange()` in universal compacts all files, waiting for files to - // become available if they are locked for another compaction. This test - // triggers an automatic compaction that blocks a `CompactRange()`, and - // verifies that `DisableManualCompaction()` can successfully cancel the - // `CompactRange()` without waiting for the automatic compaction to finish. - const int kNumSortedRuns = 4; - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.level0_file_num_compaction_trigger = kNumSortedRuns; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - Reopen(options); - - test::SleepingBackgroundTask auto_compaction_sleeping_task; - // Block automatic compaction when it runs in the callback - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():Start", - [&](void* /*arg*/) { auto_compaction_sleeping_task.DoSleep(); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Fill overlapping files in L0 to trigger an automatic compaction - Random rnd(301); - for (int i = 0; i < kNumSortedRuns; ++i) { - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx, true /* nowait */); - } - auto_compaction_sleeping_task.WaitUntilSleeping(); - - // Make sure the manual compaction has seen the conflict before being canceled - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyData::CompactRange:Return", - "DBCompactionTest::CancelCompactionWaitingOnConflict:" - "PreDisableManualCompaction"}}); - auto manual_compaction_thread = port::Thread([this]() { - ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr) - .IsIncomplete()); - }); - - // Cancel it. Thread should be joinable, i.e., manual compaction was unblocked - // despite finding a conflict with an automatic compaction that is still - // running - TEST_SYNC_POINT( - "DBCompactionTest::CancelCompactionWaitingOnConflict:" - "PreDisableManualCompaction"); - db_->DisableManualCompaction(); - manual_compaction_thread.join(); -} - -TEST_F(DBCompactionTest, OptimizedDeletionObsoleting) { - // Deletions can be dropped when compacted to non-last level if they fall - // outside the lower-level files' key-ranges. - const int kNumL0Files = 4; - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - - // put key 1 and 3 in separate L1, L2 files. - // So key 0, 2, and 4+ fall outside these levels' key-ranges. - for (int level = 2; level >= 1; --level) { - for (int i = 0; i < 2; ++i) { - ASSERT_OK(Put(Key(2 * i + 1), "val")); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(level); - ASSERT_EQ(2, NumTableFilesAtLevel(level)); - } - - // Delete keys in range [1, 4]. These L0 files will be compacted with L1: - // - Tombstones for keys 2 and 4 can be dropped early. - // - Tombstones for keys 1 and 3 must be kept due to L2 files' key-ranges. - for (int i = 0; i < kNumL0Files; ++i) { - ASSERT_OK(Put(Key(0), "val")); // sentinel to prevent trivial move - ASSERT_OK(Delete(Key(i + 1))); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - for (int i = 0; i < kNumL0Files; ++i) { - std::string value; - ASSERT_TRUE(db_->Get(ReadOptions(), Key(i + 1), &value).IsNotFound()); - } - ASSERT_EQ(2, options.statistics->getTickerCount( - COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE)); - ASSERT_EQ(2, - options.statistics->getTickerCount(COMPACTION_KEY_DROP_OBSOLETE)); -} - -TEST_F(DBCompactionTest, CompactFilesPendingL0Bug) { - // https://www.facebook.com/groups/rocksdb.dev/permalink/1389452781153232/ - // CompactFiles() had a bug where it failed to pick a compaction when an L0 - // compaction existed, but marked it as scheduled anyways. It'd never be - // unmarked as scheduled, so future compactions or DB close could hang. - const int kNumL0Files = 5; - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files - 1; - options.max_background_compactions = 2; - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"LevelCompactionPicker::PickCompaction:Return", - "DBCompactionTest::CompactFilesPendingL0Bug:Picked"}, - {"DBCompactionTest::CompactFilesPendingL0Bug:ManualCompacted", - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - auto schedule_multi_compaction_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - // Files 0-3 will be included in an L0->L1 compaction. - // - // File 4 will be included in a call to CompactFiles() while the first - // compaction is running. - for (int i = 0; i < kNumL0Files - 1; ++i) { - ASSERT_OK(Put(Key(0), "val")); // sentinel to prevent trivial move - ASSERT_OK(Put(Key(i + 1), "val")); - ASSERT_OK(Flush()); - } - TEST_SYNC_POINT("DBCompactionTest::CompactFilesPendingL0Bug:Picked"); - // file 4 flushed after 0-3 picked - ASSERT_OK(Put(Key(kNumL0Files), "val")); - ASSERT_OK(Flush()); - - // previously DB close would hang forever as this situation caused scheduled - // compactions count to never decrement to zero. - ColumnFamilyMetaData cf_meta; - dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta); - ASSERT_EQ(kNumL0Files, cf_meta.levels[0].files.size()); - std::vector input_filenames; - input_filenames.push_back(cf_meta.levels[0].files.front().name); - ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), input_filenames, - 0 /* output_level */)); - TEST_SYNC_POINT("DBCompactionTest::CompactFilesPendingL0Bug:ManualCompacted"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, CompactFilesOverlapInL0Bug) { - // Regression test for bug of not pulling in L0 files that overlap the user- - // specified input files in time- and key-ranges. - ASSERT_OK(Put(Key(0), "old_val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(0), "new_val")); - ASSERT_OK(Flush()); - - ColumnFamilyMetaData cf_meta; - dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta); - ASSERT_GE(cf_meta.levels.size(), 2); - ASSERT_EQ(2, cf_meta.levels[0].files.size()); - - // Compacting {new L0 file, L1 file} should pull in the old L0 file since it - // overlaps in key-range and time-range. - std::vector input_filenames; - input_filenames.push_back(cf_meta.levels[0].files.front().name); - ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), input_filenames, - 1 /* output_level */)); - ASSERT_EQ("new_val", Get(Key(0))); -} - -TEST_F(DBCompactionTest, DeleteFilesInRangeConflictWithCompaction) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - const Snapshot* snapshot = nullptr; - const int kMaxKey = 10; - - for (int i = 0; i < kMaxKey; i++) { - ASSERT_OK(Put(Key(i), Key(i))); - ASSERT_OK(Delete(Key(i))); - if (!snapshot) { - snapshot = db_->GetSnapshot(); - } - } - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - ASSERT_OK(Put(Key(kMaxKey), Key(kMaxKey))); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // test DeleteFilesInRange() deletes the files already picked for compaction - SyncPoint::GetInstance()->LoadDependency( - {{"VersionSet::LogAndApply:WriteManifestStart", - "BackgroundCallCompaction:0"}, - {"DBImpl::BackgroundCompaction:Finish", - "VersionSet::LogAndApply:WriteManifestDone"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - // release snapshot which mark bottommost file for compaction - db_->ReleaseSnapshot(snapshot); - std::string begin_string = Key(0); - std::string end_string = Key(kMaxKey + 1); - Slice begin(begin_string); - Slice end(end_string); - ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end)); - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) { - // bottom-level files may contain deletions due to snapshots protecting the - // deleted keys. Once the snapshot is released, we should see files with many - // such deletions undergo single-file compactions. - const int kNumKeysPerFile = 1024; - const int kNumLevelFiles = 4; - const int kValueSize = 128; - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = kNumLevelFiles; - // inflate it a bit to account for key/metadata overhead - options.target_file_size_base = 120 * kNumKeysPerFile * kValueSize / 100; - CreateAndReopenWithCF({"one"}, options); - - Random rnd(301); - const Snapshot* snapshot = nullptr; - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - if (i == kNumLevelFiles - 1) { - snapshot = db_->GetSnapshot(); - // delete every other key after grabbing a snapshot, so these deletions - // and the keys they cover can't be dropped until after the snapshot is - // released. - for (int j = 0; j < kNumLevelFiles * kNumKeysPerFile; j += 2) { - ASSERT_OK(Delete(Key(j))); - } - } - ASSERT_OK(Flush()); - if (i < kNumLevelFiles - 1) { - ASSERT_EQ(i + 1, NumTableFilesAtLevel(0)); - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1)); - - std::vector pre_release_metadata, post_release_metadata; - db_->GetLiveFilesMetaData(&pre_release_metadata); - // just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST - // files does not need to be preserved in case of a future snapshot. - ASSERT_OK(Put(Key(0), "val")); - ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_); - // release snapshot and wait for compactions to finish. Single-file - // compactions should be triggered, which reduce the size of each bottom-level - // file without changing file count. - db_->ReleaseSnapshot(snapshot); - ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->compaction_reason() == - CompactionReason::kBottommostFiles); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - db_->GetLiveFilesMetaData(&post_release_metadata); - ASSERT_EQ(pre_release_metadata.size(), post_release_metadata.size()); - - for (size_t i = 0; i < pre_release_metadata.size(); ++i) { - const auto& pre_file = pre_release_metadata[i]; - const auto& post_file = post_release_metadata[i]; - ASSERT_EQ(1, pre_file.level); - ASSERT_EQ(1, post_file.level); - // each file is smaller than it was before as it was rewritten without - // deletion markers/deleted keys. - ASSERT_LT(post_file.size, pre_file.size); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, NoCompactBottomLevelFilesWithDeletions) { - // bottom-level files may contain deletions due to snapshots protecting the - // deleted keys. Once the snapshot is released, we should see files with many - // such deletions undergo single-file compactions. But when disabling auto - // compactions, it shouldn't be triggered which may causing too many - // background jobs. - const int kNumKeysPerFile = 1024; - const int kNumLevelFiles = 4; - const int kValueSize = 128; - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.level0_file_num_compaction_trigger = kNumLevelFiles; - // inflate it a bit to account for key/metadata overhead - options.target_file_size_base = 120 * kNumKeysPerFile * kValueSize / 100; - Reopen(options); - - Random rnd(301); - const Snapshot* snapshot = nullptr; - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - if (i == kNumLevelFiles - 1) { - snapshot = db_->GetSnapshot(); - // delete every other key after grabbing a snapshot, so these deletions - // and the keys they cover can't be dropped until after the snapshot is - // released. - for (int j = 0; j < kNumLevelFiles * kNumKeysPerFile; j += 2) { - ASSERT_OK(Delete(Key(j))); - } - } - ASSERT_OK(Flush()); - if (i < kNumLevelFiles - 1) { - ASSERT_EQ(i + 1, NumTableFilesAtLevel(0)); - } - } - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr)); - ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1)); - - std::vector pre_release_metadata, post_release_metadata; - db_->GetLiveFilesMetaData(&pre_release_metadata); - // just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST - // files does not need to be preserved in case of a future snapshot. - ASSERT_OK(Put(Key(0), "val")); - - // release snapshot and no compaction should be triggered. - std::atomic num_compactions{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Start", - [&](void* /*arg*/) { num_compactions.fetch_add(1); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - db_->ReleaseSnapshot(snapshot); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, num_compactions); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - db_->GetLiveFilesMetaData(&post_release_metadata); - ASSERT_EQ(pre_release_metadata.size(), post_release_metadata.size()); - for (size_t i = 0; i < pre_release_metadata.size(); ++i) { - const auto& pre_file = pre_release_metadata[i]; - const auto& post_file = post_release_metadata[i]; - ASSERT_EQ(1, pre_file.level); - ASSERT_EQ(1, post_file.level); - // each file is same as before with deletion markers/deleted keys. - ASSERT_EQ(post_file.size, pre_file.size); - } -} - -TEST_F(DBCompactionTest, RoundRobinTtlCompactionNormal) { - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = 20; - options.ttl = 24 * 60 * 60; // 24 hours - options.compaction_pri = kRoundRobin; - env_->now_cpu_count_.store(0); - env_->SetMockSleep(); - options.env = env_; - - // add a small second for each wait time, to make sure the file is expired - int small_seconds = 1; - - std::atomic_int ttl_compactions{0}; - std::atomic_int round_robin_ttl_compactions{0}; - std::atomic_int other_compactions{0}; - - SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kTtl) { - ttl_compactions++; - } else if (compaction_reason == CompactionReason::kRoundRobinTtl) { - round_robin_ttl_compactions++; - } else { - other_compactions++; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - - // Setup the files from lower level to up level, each file is 1 hour's older - // than the next one. - // create 10 files on the last level (L6) - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 100; j++) { - ASSERT_OK(Put(Key(i * 100 + j), "value" + std::to_string(i * 100 + j))); - } - ASSERT_OK(Flush()); - env_->MockSleepForSeconds(60 * 60); // generate 1 file per hour - } - MoveFilesToLevel(6); - - // create 5 files on L5 - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 200; j++) { - ASSERT_OK(Put(Key(i * 200 + j), "value" + std::to_string(i * 200 + j))); - } - ASSERT_OK(Flush()); - env_->MockSleepForSeconds(60 * 60); - } - MoveFilesToLevel(5); - - // create 3 files on L4 - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 300; j++) { - ASSERT_OK(Put(Key(i * 300 + j), "value" + std::to_string(i * 300 + j))); - } - ASSERT_OK(Flush()); - env_->MockSleepForSeconds(60 * 60); - } - MoveFilesToLevel(4); - - // The LSM tree should be like: - // L4: [0, 299], [300, 599], [600, 899] - // L5: [0, 199] [200, 399]...............[800, 999] - // L6: [0,99][100,199][200,299][300,399]...............[800,899][900,999] - ASSERT_EQ("0,0,0,0,3,5,10", FilesPerLevel()); - - // make sure the first L5 file is expired - env_->MockSleepForSeconds(16 * 60 * 60 + small_seconds++); - - // trigger TTL compaction - ASSERT_OK(Put(Key(4), "value" + std::to_string(1))); - ASSERT_OK(Put(Key(5), "value" + std::to_string(1))); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // verify there's a RoundRobin TTL compaction - ASSERT_EQ(1, round_robin_ttl_compactions); - round_robin_ttl_compactions = 0; - - // expire 2 more files - env_->MockSleepForSeconds(2 * 60 * 60 + small_seconds++); - // trigger TTL compaction - ASSERT_OK(Put(Key(4), "value" + std::to_string(2))); - ASSERT_OK(Put(Key(5), "value" + std::to_string(2))); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(2, round_robin_ttl_compactions); - round_robin_ttl_compactions = 0; - - // expire 4 more files, 2 out of 3 files on L4 are expired - env_->MockSleepForSeconds(4 * 60 * 60 + small_seconds++); - // trigger TTL compaction - ASSERT_OK(Put(Key(6), "value" + std::to_string(3))); - ASSERT_OK(Put(Key(7), "value" + std::to_string(3))); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(1, NumTableFilesAtLevel(4)); - ASSERT_EQ(0, NumTableFilesAtLevel(5)); - - ASSERT_GT(round_robin_ttl_compactions, 0); - round_robin_ttl_compactions = 0; - - // make the first L0 file expired, which triggers a normal TTL compaction - // instead of roundrobin TTL compaction, it will also include an extra file - // from L0 because of overlap - ASSERT_EQ(0, ttl_compactions); - env_->MockSleepForSeconds(19 * 60 * 60 + small_seconds++); - - // trigger TTL compaction - ASSERT_OK(Put(Key(6), "value" + std::to_string(4))); - ASSERT_OK(Put(Key(7), "value" + std::to_string(4))); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // L0 -> L1 compaction is normal TTL compaction, L1 -> next levels compactions - // are RoundRobin TTL compaction. - ASSERT_GT(ttl_compactions, 0); - ttl_compactions = 0; - ASSERT_GT(round_robin_ttl_compactions, 0); - round_robin_ttl_compactions = 0; - - // All files are expired, so only the last level has data - env_->MockSleepForSeconds(24 * 60 * 60); - // trigger TTL compaction - ASSERT_OK(Put(Key(6), "value" + std::to_string(4))); - ASSERT_OK(Put(Key(7), "value" + std::to_string(4))); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel()); - - ASSERT_GT(ttl_compactions, 0); - ttl_compactions = 0; - ASSERT_GT(round_robin_ttl_compactions, 0); - round_robin_ttl_compactions = 0; - - ASSERT_EQ(0, other_compactions); -} - -TEST_F(DBCompactionTest, RoundRobinTtlCompactionUnsortedTime) { - // This is to test the case that the RoundRobin compaction cursor not pointing - // to the oldest file, RoundRobin compaction should still compact the file - // after cursor until all expired files are compacted. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = 20; - options.ttl = 24 * 60 * 60; // 24 hours - options.compaction_pri = kRoundRobin; - env_->now_cpu_count_.store(0); - env_->SetMockSleep(); - options.env = env_; - - std::atomic_int ttl_compactions{0}; - std::atomic_int round_robin_ttl_compactions{0}; - std::atomic_int other_compactions{0}; - - SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kTtl) { - ttl_compactions++; - } else if (compaction_reason == CompactionReason::kRoundRobinTtl) { - round_robin_ttl_compactions++; - } else { - other_compactions++; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - - // create 10 files on the last level (L6) - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 100; j++) { - ASSERT_OK(Put(Key(i * 100 + j), "value" + std::to_string(i * 100 + j))); - } - ASSERT_OK(Flush()); - env_->MockSleepForSeconds(60 * 60); // generate 1 file per hour - } - MoveFilesToLevel(6); - - // create 5 files on L5 - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 200; j++) { - ASSERT_OK(Put(Key(i * 200 + j), "value" + std::to_string(i * 200 + j))); - } - ASSERT_OK(Flush()); - env_->MockSleepForSeconds(60 * 60); // 1 hour - } - MoveFilesToLevel(5); - - // The LSM tree should be like: - // L5: [0, 199] [200, 399] [400,599] [600,799] [800, 999] - // L6: [0,99][100,199][200,299][300,399]....................[800,899][900,999] - ASSERT_EQ("0,0,0,0,0,5,10", FilesPerLevel()); - - // point the compaction cursor to the 4th file on L5 - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - VersionStorageInfo* storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - const InternalKey split_cursor = InternalKey(Key(600), 100000, kTypeValue); - storage_info->AddCursorForOneLevel(5, split_cursor); - - // make the first file on L5 expired, there should be 3 TTL compactions: - // 4th one, 5th one, then 1st one. - env_->MockSleepForSeconds(19 * 60 * 60 + 1); - // trigger TTL compaction - ASSERT_OK(Put(Key(6), "value" + std::to_string(4))); - ASSERT_OK(Put(Key(7), "value" + std::to_string(4))); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(2, NumTableFilesAtLevel(5)); - - ASSERT_EQ(3, round_robin_ttl_compactions); - ASSERT_EQ(0, ttl_compactions); - ASSERT_EQ(0, other_compactions); -} - -TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) { - const int kNumKeysPerFile = 32; - const int kNumLevelFiles = 2; - const int kValueSize = 1024; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.ttl = 24 * 60 * 60; // 24 hours - options.max_open_files = -1; - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - MoveFilesToLevel(3); - ASSERT_EQ("0,0,0,2", FilesPerLevel()); - - // Delete previously written keys. - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("2,0,0,2", FilesPerLevel()); - MoveFilesToLevel(1); - ASSERT_EQ("0,2,0,2", FilesPerLevel()); - - env_->MockSleepForSeconds(36 * 60 * 60); // 36 hours - ASSERT_EQ("0,2,0,2", FilesPerLevel()); - - // Just do a simple write + flush so that the Ttl expired files get - // compacted. - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Flush()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // All non-L0 files are deleted, as they contained only deleted data. - ASSERT_EQ("1", FilesPerLevel()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - // Test dynamically changing ttl. - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - MoveFilesToLevel(3); - ASSERT_EQ("0,0,0,2", FilesPerLevel()); - - // Delete previously written keys. - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("2,0,0,2", FilesPerLevel()); - MoveFilesToLevel(1); - ASSERT_EQ("0,2,0,2", FilesPerLevel()); - - // Move time forward by 12 hours, and make sure that compaction still doesn't - // trigger as ttl is set to 24 hours. - env_->MockSleepForSeconds(12 * 60 * 60); - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("1,2,0,2", FilesPerLevel()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Dynamically change ttl to 10 hours. - // This should trigger a ttl compaction, as 12 hours have already passed. - ASSERT_OK(dbfull()->SetOptions({{"ttl", "36000"}})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // All non-L0 files are deleted, as they contained only deleted data. - ASSERT_EQ("1", FilesPerLevel()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, LevelTtlCompactionOutputCuttingIteractingWithOther) { - // This test is for a bug fix in CompactionOutputs::ShouldStopBefore() where - // TTL states were not being updated for keys that ShouldStopBefore() would - // return true for reasons other than TTL. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.ttl = 24 * 60 * 60; // 24 hours - options.max_open_files = -1; - options.compaction_pri = kMinOverlappingRatio; - env_->SetMockSleep(); - options.env = env_; - options.target_file_size_base = 4 << 10; - options.disable_auto_compactions = true; - options.level_compaction_dynamic_file_size = false; - - DestroyAndReopen(options); - Random rnd(301); - - // This makes sure the manual compaction below - // is not a bottommost compaction as TTL is only - // for non-bottommost compactions. - ASSERT_OK(Put(Key(3), rnd.RandomString(1 << 10))); - ASSERT_OK(Put(Key(0), rnd.RandomString(1 << 10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(6); - - // L2: - ASSERT_OK(Put(Key(2), rnd.RandomString(4 << 10))); - ASSERT_OK(Put(Key(3), rnd.RandomString(4 << 10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - - // L1, overlaps in range with the file in L2 so - // that they compact together. - ASSERT_OK(Put(Key(0), rnd.RandomString(4 << 10))); - ASSERT_OK(Put(Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(Put(Key(3), rnd.RandomString(4 << 10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - - ASSERT_EQ("0,1,1,0,0,0,1", FilesPerLevel()); - // 36 hours so that the file in L2 is eligible for TTL - env_->MockSleepForSeconds(36 * 60 * 60); - - CompactRangeOptions compact_range_opts; - - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 1 /* input_level */, 2 /* output_level */, compact_range_opts, - nullptr /* begin */, nullptr /* end */, true /* exclusive */, - true /* disallow_trivial_move */, - std::numeric_limits::max() /*max_file_num_to_ignore*/, - "" /*trim_ts*/)); - - // L2 should have 2 files: - // file 1: Key(0), Key(1) - // ShouldStopBefore(Key(2)) return true due to TTL or output file size - // file 2: Key(2), Key(3) - // - // Before the fix in this PR, L2 would have 3 files: - // file 1: Key(0), Key(1) - // CompactionOutputs::ShouldStopBefore(Key(2)) returns true due to output file - // size. - // file 2: Key(2) - // CompactionOutput::ShouldStopBefore(Key(3)) returns true - // due to TTL cutting and that TTL states were not updated - // for Key(2). - // file 3: Key(3) - ASSERT_EQ("0,0,2,0,0,0,1", FilesPerLevel()); -} - -TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) { - env_->SetMockSleep(); - const int kValueSize = 100; - - for (bool if_restart : {false, true}) { - for (bool if_open_all_files : {false, true}) { - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.ttl = 24 * 60 * 60; // 24 hours - if (if_open_all_files) { - options.max_open_files = -1; - } else { - options.max_open_files = 20; - } - // RocksDB sanitize max open files to at least 20. Modify it back. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = static_cast(arg); - *max_open_files = 2; - }); - // In the case where all files are opened and doing DB restart - // forcing the oldest ancester time in manifest file to be 0 to - // simulate the case of reading from an old version. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionEdit::EncodeTo:VarintOldestAncesterTime", [&](void* arg) { - if (if_restart && if_open_all_files) { - std::string* encoded_fieled = static_cast(arg); - *encoded_fieled = ""; - PutVarint64(encoded_fieled, 0); - } - }); - - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - int ttl_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kTtl) { - ttl_compactions++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Add two L6 files with key ranges: [1 .. 100], [101 .. 200]. - Random rnd(301); - for (int i = 1; i <= 100; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - // Get the first file's creation time. This will be the oldest file in the - // DB. Compactions inolving this file's descendents should keep getting - // this time. - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - uint64_t oldest_time = level_to_files[0][0].oldest_ancester_time; - // Add 1 hour and do another flush. - env_->MockSleepForSeconds(1 * 60 * 60); - for (int i = 101; i <= 200; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(6); - ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel()); - - env_->MockSleepForSeconds(1 * 60 * 60); - // Add two L4 files with key ranges: [1 .. 50], [51 .. 150]. - for (int i = 1; i <= 50; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - env_->MockSleepForSeconds(1 * 60 * 60); - for (int i = 51; i <= 150; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(4); - ASSERT_EQ("0,0,0,0,2,0,2", FilesPerLevel()); - - env_->MockSleepForSeconds(1 * 60 * 60); - // Add one L1 file with key range: [26, 75]. - for (int i = 26; i <= 75; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - MoveFilesToLevel(1); - ASSERT_EQ("0,1,0,0,2,0,2", FilesPerLevel()); - - // LSM tree: - // L1: [26 .. 75] - // L4: [1 .. 50][51 ..... 150] - // L6: [1 ........ 100][101 .... 200] - // - // On TTL expiry, TTL compaction should be initiated on L1 file, and the - // compactions should keep going on until the key range hits bottom level. - // In other words: the compaction on this data range "cascasdes" until - // reaching the bottom level. - // - // Order of events on TTL expiry: - // 1. L1 file falls to L3 via 2 trivial moves which are initiated by the - // ttl - // compaction. - // 2. A TTL compaction happens between L3 and L4 files. Output file in L4. - // 3. The new output file from L4 falls to L5 via 1 trival move initiated - // by the ttl compaction. - // 4. A TTL compaction happens between L5 and L6 files. Ouptut in L6. - - // Add 25 hours and do a write - env_->MockSleepForSeconds(25 * 60 * 60); - - ASSERT_OK(Put(Key(1), "1")); - if (if_restart) { - Reopen(options); - } else { - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("1,0,0,0,0,0,1", FilesPerLevel()); - ASSERT_EQ(5, ttl_compactions); - - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(oldest_time, level_to_files[6][0].oldest_ancester_time); - - env_->MockSleepForSeconds(25 * 60 * 60); - ASSERT_OK(Put(Key(2), "1")); - if (if_restart) { - Reopen(options); - } else { - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("1,0,0,0,0,0,1", FilesPerLevel()); - ASSERT_GE(ttl_compactions, 6); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - } -} - -TEST_F(DBCompactionTest, LevelPeriodicCompaction) { - env_->SetMockSleep(); - const int kNumKeysPerFile = 32; - const int kNumLevelFiles = 2; - const int kValueSize = 100; - - for (bool if_restart : {false, true}) { - for (bool if_open_all_files : {false, true}) { - Options options = CurrentOptions(); - options.periodic_compaction_seconds = 48 * 60 * 60; // 2 days - if (if_open_all_files) { - options.max_open_files = -1; // needed for ttl compaction - } else { - options.max_open_files = 20; - } - // RocksDB sanitize max open files to at least 20. Modify it back. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = static_cast(arg); - *max_open_files = 0; - }); - // In the case where all files are opened and doing DB restart - // forcing the file creation time in manifest file to be 0 to - // simulate the case of reading from an old version. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionEdit::EncodeTo:VarintFileCreationTime", [&](void* arg) { - if (if_restart && if_open_all_files) { - std::string* encoded_fieled = static_cast(arg); - *encoded_fieled = ""; - PutVarint64(encoded_fieled, 0); - } - }); - - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - int periodic_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kPeriodicCompaction) { - periodic_compactions++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("2", FilesPerLevel()); - ASSERT_EQ(0, periodic_compactions); - - // Add 50 hours and do a write - env_->MockSleepForSeconds(50 * 60 * 60); - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Assert that the files stay in the same level - ASSERT_EQ("3", FilesPerLevel()); - // The two old files go through the periodic compaction process - ASSERT_EQ(2, periodic_compactions); - - MoveFilesToLevel(1); - ASSERT_EQ("0,3", FilesPerLevel()); - - // Add another 50 hours and do another write - env_->MockSleepForSeconds(50 * 60 * 60); - ASSERT_OK(Put("b", "2")); - if (if_restart) { - Reopen(options); - } else { - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("1,3", FilesPerLevel()); - // The three old files now go through the periodic compaction process. 2 - // + 3. - ASSERT_EQ(5, periodic_compactions); - - // Add another 50 hours and do another write - env_->MockSleepForSeconds(50 * 60 * 60); - ASSERT_OK(Put("c", "3")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("2,3", FilesPerLevel()); - // The four old files now go through the periodic compaction process. 5 - // + 4. - ASSERT_EQ(9, periodic_compactions); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - } -} - -TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) { - // This test makes sure that periodic compactions are working with a DB - // where file_creation_time of some files is 0. - // After compactions the new files are created with a valid file_creation_time - - const int kNumKeysPerFile = 32; - const int kNumFiles = 4; - const int kValueSize = 100; - - Options options = CurrentOptions(); - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - int periodic_compactions = 0; - bool set_file_creation_time_to_zero = true; - bool set_creation_time_to_zero = true; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kPeriodicCompaction) { - periodic_compactions++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) { - TableProperties* props = reinterpret_cast(arg); - if (set_file_creation_time_to_zero) { - props->file_creation_time = 0; - } - if (set_creation_time_to_zero) { - props->creation_time = 0; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - // Move the first two files to L2. - if (i == 1) { - MoveFilesToLevel(2); - set_creation_time_to_zero = false; - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("2,0,2", FilesPerLevel()); - ASSERT_EQ(0, periodic_compactions); - - Close(); - - set_file_creation_time_to_zero = false; - // Forward the clock by 2 days. - env_->MockSleepForSeconds(2 * 24 * 60 * 60); - options.periodic_compaction_seconds = 1 * 24 * 60 * 60; // 1 day - - Reopen(options); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("2,0,2", FilesPerLevel()); - // Make sure that all files go through periodic compaction. - ASSERT_EQ(kNumFiles, periodic_compactions); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, LevelPeriodicAndTtlCompaction) { - const int kNumKeysPerFile = 32; - const int kNumLevelFiles = 2; - const int kValueSize = 100; - - Options options = CurrentOptions(); - options.ttl = 10 * 60 * 60; // 10 hours - options.periodic_compaction_seconds = 48 * 60 * 60; // 2 days - options.max_open_files = -1; // needed for both periodic and ttl compactions - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - int periodic_compactions = 0; - int ttl_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kPeriodicCompaction) { - periodic_compactions++; - } else if (compaction_reason == CompactionReason::kTtl) { - ttl_compactions++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - MoveFilesToLevel(3); - - ASSERT_EQ("0,0,0,2", FilesPerLevel()); - ASSERT_EQ(0, periodic_compactions); - ASSERT_EQ(0, ttl_compactions); - - // Add some time greater than periodic_compaction_time. - env_->MockSleepForSeconds(50 * 60 * 60); - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Files in the bottom level go through periodic compactions. - ASSERT_EQ("1,0,0,2", FilesPerLevel()); - ASSERT_EQ(2, periodic_compactions); - ASSERT_EQ(0, ttl_compactions); - - // Add a little more time than ttl - env_->MockSleepForSeconds(11 * 60 * 60); - ASSERT_OK(Put("b", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Notice that the previous file in level 1 falls down to the bottom level - // due to ttl compactions, one level at a time. - // And bottom level files don't get picked up for ttl compactions. - ASSERT_EQ("1,0,0,3", FilesPerLevel()); - ASSERT_EQ(2, periodic_compactions); - ASSERT_EQ(3, ttl_compactions); - - // Add some time greater than periodic_compaction_time. - env_->MockSleepForSeconds(50 * 60 * 60); - ASSERT_OK(Put("c", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Previous L0 file falls one level at a time to bottom level due to ttl. - // And all 4 bottom files go through periodic compactions. - ASSERT_EQ("1,0,0,4", FilesPerLevel()); - ASSERT_EQ(6, periodic_compactions); - ASSERT_EQ(6, ttl_compactions); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, LevelTtlBooster) { - const int kNumKeysPerFile = 32; - const int kNumLevelFiles = 3; - const int kValueSize = 1000; - - Options options = CurrentOptions(); - options.ttl = 10 * 60 * 60; // 10 hours - options.periodic_compaction_seconds = 480 * 60 * 60; // very long - options.level0_file_num_compaction_trigger = 2; - options.max_bytes_for_level_base = 5 * uint64_t{kNumKeysPerFile * kValueSize}; - options.max_open_files = -1; // needed for both periodic and ttl compactions - options.compaction_pri = CompactionPri::kMinOverlappingRatio; - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - MoveFilesToLevel(2); - - ASSERT_EQ("0,0,3", FilesPerLevel()); - - // Create some files for L1 - for (int i = 0; i < 2; i++) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(Put(Key(2 * j + i), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - ASSERT_EQ("0,1,3", FilesPerLevel()); - - // Make the new L0 files qualify TTL boosting and generate one more to trigger - // L1 -> L2 compaction. Old files will be picked even if their priority is - // lower without boosting. - env_->MockSleepForSeconds(8 * 60 * 60); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(Put(Key(kNumKeysPerFile * 2 + 2 * j + i), - rnd.RandomString(kValueSize * 2))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - // Force files to be compacted to L1 - ASSERT_OK( - dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "1"}})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,1,2", FilesPerLevel()); - ASSERT_OK( - dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "2"}})); - - ASSERT_GT(SizeAtLevel(1), kNumKeysPerFile * 4 * kValueSize); -} - -TEST_F(DBCompactionTest, LevelPeriodicCompactionWithCompactionFilters) { - class TestCompactionFilter : public CompactionFilter { - const char* Name() const override { return "TestCompactionFilter"; } - }; - class TestCompactionFilterFactory : public CompactionFilterFactory { - const char* Name() const override { return "TestCompactionFilterFactory"; } - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& /*context*/) override { - return std::unique_ptr(new TestCompactionFilter()); - } - }; - - const int kNumKeysPerFile = 32; - const int kNumLevelFiles = 2; - const int kValueSize = 100; - - Random rnd(301); - - Options options = CurrentOptions(); - TestCompactionFilter test_compaction_filter; - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - enum CompactionFilterType { - kUseCompactionFilter, - kUseCompactionFilterFactory - }; - - for (CompactionFilterType comp_filter_type : - {kUseCompactionFilter, kUseCompactionFilterFactory}) { - // Assert that periodic compactions are not enabled. - ASSERT_EQ(std::numeric_limits::max() - 1, - options.periodic_compaction_seconds); - - if (comp_filter_type == kUseCompactionFilter) { - options.compaction_filter = &test_compaction_filter; - options.compaction_filter_factory.reset(); - } else if (comp_filter_type == kUseCompactionFilterFactory) { - options.compaction_filter = nullptr; - options.compaction_filter_factory.reset( - new TestCompactionFilterFactory()); - } - DestroyAndReopen(options); - - // periodic_compaction_seconds should be set to the sanitized value when - // a compaction filter or a compaction filter factory is used. - ASSERT_EQ(30 * 24 * 60 * 60, - dbfull()->GetOptions().periodic_compaction_seconds); - - int periodic_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - auto compaction_reason = compaction->compaction_reason(); - if (compaction_reason == CompactionReason::kPeriodicCompaction) { - periodic_compactions++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ("2", FilesPerLevel()); - ASSERT_EQ(0, periodic_compactions); - - // Add 31 days and do a write - env_->MockSleepForSeconds(31 * 24 * 60 * 60); - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Assert that the files stay in the same level - ASSERT_EQ("3", FilesPerLevel()); - // The two old files go through the periodic compaction process - ASSERT_EQ(2, periodic_compactions); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) { - // Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual - // compaction only triggers flush after it's sure stall won't be triggered for - // L0 file count going too high. - const int kNumL0FilesTrigger = 4; - const int kNumL0FilesLimit = 8; - // i == 0: verifies normal case where stall is avoided by delay - // i == 1: verifies no delay in edge case where stall trigger is same as - // compaction trigger, so stall can't be avoided - for (int i = 0; i < 2; ++i) { - Options options = CurrentOptions(); - options.level0_slowdown_writes_trigger = kNumL0FilesLimit; - if (i == 0) { - options.level0_file_num_compaction_trigger = kNumL0FilesTrigger; - } else { - options.level0_file_num_compaction_trigger = kNumL0FilesLimit; - } - Reopen(options); - - if (i == 0) { - // ensure the auto compaction doesn't finish until manual compaction has - // had a chance to be delayed. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", - "CompactionJob::Run():End"}}); - } else { - // ensure the auto-compaction doesn't finish until manual compaction has - // continued without delay. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:StallWaitDone", - "CompactionJob::Run():End"}}); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int j = 0; j < kNumL0FilesLimit - 1; ++j) { - for (int k = 0; k < 2; ++k) { - ASSERT_OK(Put(Key(k), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - auto manual_compaction_thread = port::Thread([this]() { - CompactRangeOptions cro; - cro.allow_write_stall = false; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - }); - - manual_compaction_thread.join(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(1), 0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) { - // Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual - // compaction only triggers flush after it's sure stall won't be triggered for - // immutable memtable count going too high. - const int kNumImmMemTableLimit = 8; - // i == 0: verifies normal case where stall is avoided by delay - // i == 1: verifies no delay in edge case where stall trigger is same as flush - // trigger, so stall can't be avoided - for (int i = 0; i < 2; ++i) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - // the delay limit is one less than the stop limit. This test focuses on - // avoiding delay limit, but this option sets stop limit, so add one. - options.max_write_buffer_number = kNumImmMemTableLimit + 1; - if (i == 1) { - options.min_write_buffer_number_to_merge = kNumImmMemTableLimit; - } - Reopen(options); - - if (i == 0) { - // ensure the flush doesn't finish until manual compaction has had a - // chance to be delayed. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", - "FlushJob::WriteLevel0Table"}}); - } else { - // ensure the flush doesn't finish until manual compaction has continued - // without delay. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:StallWaitDone", - "FlushJob::WriteLevel0Table"}}); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int j = 0; j < kNumImmMemTableLimit - 1; ++j) { - ASSERT_OK(Put(Key(0), rnd.RandomString(1024))); - FlushOptions flush_opts; - flush_opts.wait = false; - flush_opts.allow_write_stall = true; - ASSERT_OK(dbfull()->Flush(flush_opts)); - } - - auto manual_compaction_thread = port::Thread([this]() { - CompactRangeOptions cro; - cro.allow_write_stall = false; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - }); - - manual_compaction_thread.join(); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(1), 0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) { - // Verify that, when `CompactRangeOptions::allow_write_stall == false`, delay - // does not hang if CF is dropped or DB is closed - const int kNumL0FilesTrigger = 4; - const int kNumL0FilesLimit = 8; - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0FilesTrigger; - options.level0_slowdown_writes_trigger = kNumL0FilesLimit; - // i == 0: DB::DropColumnFamily() on CompactRange's target CF unblocks it - // i == 1: DB::CancelAllBackgroundWork() unblocks CompactRange. This is to - // simulate what happens during Close as we can't call Close (it - // blocks on the auto-compaction, making a cycle). - for (int i = 0; i < 2; ++i) { - CreateAndReopenWithCF({"one"}, options); - // The calls to close CF/DB wait until the manual compaction stalls. - // The auto-compaction waits until the manual compaction finishes to ensure - // the signal comes from closing CF/DB, not from compaction making progress. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", - "DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"}, - {"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual", - "CompactionJob::Run():End"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int j = 0; j < kNumL0FilesLimit - 1; ++j) { - for (int k = 0; k < 2; ++k) { - ASSERT_OK(Put(1, Key(k), rnd.RandomString(1024))); - } - ASSERT_OK(Flush(1)); - } - auto manual_compaction_thread = port::Thread([this, i]() { - CompactRangeOptions cro; - cro.allow_write_stall = false; - if (i == 0) { - ASSERT_TRUE(db_->CompactRange(cro, handles_[1], nullptr, nullptr) - .IsColumnFamilyDropped()); - } else { - ASSERT_TRUE(db_->CompactRange(cro, handles_[1], nullptr, nullptr) - .IsShutdownInProgress()); - } - }); - - TEST_SYNC_POINT( - "DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"); - if (i == 0) { - ASSERT_OK(db_->DropColumnFamily(handles_[1])); - } else { - dbfull()->CancelAllBackgroundWork(false /* wait */); - } - manual_compaction_thread.join(); - TEST_SYNC_POINT( - "DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual"); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) { - // Verify that, when `CompactRangeOptions::allow_write_stall == false`, - // CompactRange skips its flush if the delay is long enough that the memtables - // existing at the beginning of the call have already been flushed. - const int kNumL0FilesTrigger = 4; - const int kNumL0FilesLimit = 8; - Options options = CurrentOptions(); - options.level0_slowdown_writes_trigger = kNumL0FilesLimit; - options.level0_file_num_compaction_trigger = kNumL0FilesTrigger; - Reopen(options); - - Random rnd(301); - // The manual flush includes the memtable that was active when CompactRange - // began. So it unblocks CompactRange and precludes its flush. Throughout the - // test, stall conditions are upheld via high L0 file count. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", - "DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"}, - {"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush", - "DBImpl::FlushMemTable:StallWaitDone"}, - {"DBImpl::FlushMemTable:StallWaitDone", "CompactionJob::Run():End"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // used for the delayable flushes - FlushOptions flush_opts; - flush_opts.allow_write_stall = true; - for (int i = 0; i < kNumL0FilesLimit - 1; ++i) { - for (int j = 0; j < 2; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(1024))); - } - ASSERT_OK(dbfull()->Flush(flush_opts)); - } - auto manual_compaction_thread = port::Thread([this]() { - CompactRangeOptions cro; - cro.allow_write_stall = false; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - }); - - TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"); - ASSERT_OK(Put(std::to_string(0), rnd.RandomString(1024))); - ASSERT_OK(dbfull()->Flush(flush_opts)); - ASSERT_OK(Put(std::to_string(0), rnd.RandomString(1024))); - TEST_SYNC_POINT( - "DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush"); - manual_compaction_thread.join(); - - // If CompactRange's flush was skipped, the final Put above will still be - // in the active memtable. - std::string num_keys_in_memtable; - ASSERT_TRUE(db_->GetProperty(DB::Properties::kNumEntriesActiveMemTable, - &num_keys_in_memtable)); - ASSERT_EQ(std::to_string(1), num_keys_in_memtable); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBCompactionTest, CompactRangeFlushOverlappingMemtable) { - // Verify memtable only gets flushed if it contains data overlapping the range - // provided to `CompactRange`. Tests all kinds of overlap/non-overlap. - const int kNumEndpointKeys = 5; - std::string keys[kNumEndpointKeys] = {"a", "b", "c", "d", "e"}; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - Reopen(options); - - // One extra iteration for nullptr, which means left side of interval is - // unbounded. - for (int i = 0; i <= kNumEndpointKeys; ++i) { - Slice begin; - Slice* begin_ptr; - if (i == 0) { - begin_ptr = nullptr; - } else { - begin = keys[i - 1]; - begin_ptr = &begin; - } - // Start at `i` so right endpoint comes after left endpoint. One extra - // iteration for nullptr, which means right side of interval is unbounded. - for (int j = std::max(0, i - 1); j <= kNumEndpointKeys; ++j) { - Slice end; - Slice* end_ptr; - if (j == kNumEndpointKeys) { - end_ptr = nullptr; - } else { - end = keys[j]; - end_ptr = &end; - } - ASSERT_OK(Put("b", "val")); - ASSERT_OK(Put("d", "val")); - CompactRangeOptions compact_range_opts; - ASSERT_OK(db_->CompactRange(compact_range_opts, begin_ptr, end_ptr)); - - uint64_t get_prop_tmp, num_memtable_entries = 0; - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesImmMemTables, - &get_prop_tmp)); - num_memtable_entries += get_prop_tmp; - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &get_prop_tmp)); - num_memtable_entries += get_prop_tmp; - if (begin_ptr == nullptr || end_ptr == nullptr || - (i <= 4 && j >= 1 && (begin != "c" || end != "c"))) { - // In this case `CompactRange`'s range overlapped in some way with the - // memtable's range, so flush should've happened. Then "b" and "d" won't - // be in the memtable. - ASSERT_EQ(0, num_memtable_entries); - } else { - ASSERT_EQ(2, num_memtable_entries); - // flush anyways to prepare for next iteration - ASSERT_OK(db_->Flush(FlushOptions())); - } - } - } -} - -TEST_F(DBCompactionTest, CompactionStatsTest) { - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - CompactionStatsCollector* collector = new CompactionStatsCollector(); - options.listeners.emplace_back(collector); - DestroyAndReopen(options); - - for (int i = 0; i < 32; i++) { - for (int j = 0; j < 5000; j++) { - ASSERT_OK(Put(std::to_string(j), std::string(1, 'A'))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ColumnFamilyHandleImpl* cfh = - static_cast(dbfull()->DefaultColumnFamily()); - ColumnFamilyData* cfd = cfh->cfd(); - - VerifyCompactionStats(*cfd, *collector); -} - -TEST_F(DBCompactionTest, SubcompactionEvent) { - class SubCompactionEventListener : public EventListener { - public: - void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override { - InstrumentedMutexLock l(&mutex_); - ASSERT_EQ(running_compactions_.find(ci.job_id), - running_compactions_.end()); - running_compactions_.emplace(ci.job_id, std::unordered_set()); - } - - void OnCompactionCompleted(DB* /*db*/, - const CompactionJobInfo& ci) override { - InstrumentedMutexLock l(&mutex_); - auto it = running_compactions_.find(ci.job_id); - ASSERT_NE(it, running_compactions_.end()); - ASSERT_EQ(it->second.size(), 0); - running_compactions_.erase(it); - } - - void OnSubcompactionBegin(const SubcompactionJobInfo& si) override { - InstrumentedMutexLock l(&mutex_); - auto it = running_compactions_.find(si.job_id); - ASSERT_NE(it, running_compactions_.end()); - auto r = it->second.insert(si.subcompaction_job_id); - ASSERT_TRUE(r.second); // each subcompaction_job_id should be different - total_subcompaction_cnt_++; - } - - void OnSubcompactionCompleted(const SubcompactionJobInfo& si) override { - InstrumentedMutexLock l(&mutex_); - auto it = running_compactions_.find(si.job_id); - ASSERT_NE(it, running_compactions_.end()); - auto r = it->second.erase(si.subcompaction_job_id); - ASSERT_EQ(r, 1); - } - - size_t GetRunningCompactionCount() { - InstrumentedMutexLock l(&mutex_); - return running_compactions_.size(); - } - - size_t GetTotalSubcompactionCount() { - InstrumentedMutexLock l(&mutex_); - return total_subcompaction_cnt_; - } - - private: - InstrumentedMutex mutex_; - std::unordered_map> running_compactions_; - size_t total_subcompaction_cnt_ = 0; - }; - - Options options = CurrentOptions(); - options.target_file_size_base = 1024; - options.level0_file_num_compaction_trigger = 10; - auto* listener = new SubCompactionEventListener(); - options.listeners.emplace_back(listener); - - DestroyAndReopen(options); - - // generate 4 files @ L2 - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 10; j++) { - int key_id = i * 10 + j; - ASSERT_OK(Put(Key(key_id), "value" + std::to_string(key_id))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - - // generate 2 files @ L1 which overlaps with L2 files - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 10; j++) { - int key_id = i * 20 + j * 2; - ASSERT_OK(Put(Key(key_id), "value" + std::to_string(key_id))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(1); - ASSERT_EQ(FilesPerLevel(), "0,2,4"); - - CompactRangeOptions comp_opts; - comp_opts.max_subcompactions = 4; - Status s = dbfull()->CompactRange(comp_opts, nullptr, nullptr); - ASSERT_OK(s); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // make sure there's no running compaction - ASSERT_EQ(listener->GetRunningCompactionCount(), 0); - // and sub compaction is triggered - ASSERT_GT(listener->GetTotalSubcompactionCount(), 0); -} - -TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) { - // LSM setup: - // L1: [ba bz] - // L2: [a b] [c d] - // L3: [a b] [c d] - // - // Thread 1: Thread 2: - // Begin compacting all L2->L3 - // Compact [ba bz] L1->L3 - // End compacting all L2->L3 - // - // The compaction operation in thread 2 should be disallowed because the range - // overlaps with the compaction in thread 1, which also covers that range in - // L3. - Options options = CurrentOptions(); - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - Reopen(options); - - for (int level = 3; level >= 2; --level) { - ASSERT_OK(Put("a", "val")); - ASSERT_OK(Put("b", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("c", "val")); - ASSERT_OK(Put("d", "val")); - ASSERT_OK(Flush()); - MoveFilesToLevel(level); - } - ASSERT_OK(Put("ba", "val")); - ASSERT_OK(Put("bz", "val")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - - SyncPoint::GetInstance()->LoadDependency({ - {"CompactFilesImpl:0", - "DBCompactionTest::CompactFilesOutputRangeConflict:Thread2Begin"}, - {"DBCompactionTest::CompactFilesOutputRangeConflict:Thread2End", - "CompactFilesImpl:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - auto bg_thread = port::Thread([&]() { - // Thread 1 - std::vector filenames = collector->GetFlushedFiles(); - filenames.pop_back(); - ASSERT_OK(db_->CompactFiles(CompactionOptions(), filenames, - 3 /* output_level */)); - }); - - // Thread 2 - TEST_SYNC_POINT( - "DBCompactionTest::CompactFilesOutputRangeConflict:Thread2Begin"); - std::string filename = collector->GetFlushedFiles().back(); - ASSERT_FALSE( - db_->CompactFiles(CompactionOptions(), {filename}, 3 /* output_level */) - .ok()); - TEST_SYNC_POINT( - "DBCompactionTest::CompactFilesOutputRangeConflict:Thread2End"); - - bg_thread.join(); -} - -TEST_F(DBCompactionTest, CompactionHasEmptyOutput) { - Options options = CurrentOptions(); - SstStatsCollector* collector = new SstStatsCollector(); - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(collector); - Reopen(options); - - // Make sure the L0 files overlap to prevent trivial move. - ASSERT_OK(Put("a", "val")); - ASSERT_OK(Put("b", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Delete("a")); - ASSERT_OK(Delete("b")); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - - // Expect one file creation to start for each flush, and zero for compaction - // since no keys are written. - ASSERT_EQ(2, collector->num_ssts_creation_started()); -} - -TEST_F(DBCompactionTest, CompactionLimiter) { - const int kNumKeysPerFile = 10; - const int kMaxBackgroundThreads = 64; - - struct CompactionLimiter { - std::string name; - int limit_tasks; - int max_tasks; - int tasks; - std::shared_ptr limiter; - }; - - std::vector limiter_settings; - limiter_settings.push_back({"limiter_1", 1, 0, 0, nullptr}); - limiter_settings.push_back({"limiter_2", 2, 0, 0, nullptr}); - limiter_settings.push_back({"limiter_3", 3, 0, 0, nullptr}); - - for (auto& ls : limiter_settings) { - ls.limiter.reset(NewConcurrentTaskLimiter(ls.name, ls.limit_tasks)); - } - - std::shared_ptr unique_limiter( - NewConcurrentTaskLimiter("unique_limiter", -1)); - - const char* cf_names[] = {"default", "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", "a", "b", "c", "d", "e", "f"}; - const unsigned int cf_count = sizeof cf_names / sizeof cf_names[0]; - - std::unordered_map cf_to_limiter; - - Options options = CurrentOptions(); - options.write_buffer_size = 110 * 1024; // 110KB - options.arena_block_size = 4096; - options.num_levels = 3; - options.level0_file_num_compaction_trigger = 4; - options.level0_slowdown_writes_trigger = 64; - options.level0_stop_writes_trigger = 64; - options.max_background_jobs = kMaxBackgroundThreads; // Enough threads - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - options.max_write_buffer_number = 10; // Enough memtables - DestroyAndReopen(options); - - std::vector option_vector; - option_vector.reserve(cf_count); - - for (unsigned int cf = 0; cf < cf_count; cf++) { - ColumnFamilyOptions cf_opt(options); - if (cf == 0) { - // "Default" CF does't use compaction limiter - cf_opt.compaction_thread_limiter = nullptr; - } else if (cf == 1) { - // "1" CF uses bypass compaction limiter - unique_limiter->SetMaxOutstandingTask(-1); - cf_opt.compaction_thread_limiter = unique_limiter; - } else { - // Assign limiter by mod - auto& ls = limiter_settings[cf % 3]; - cf_opt.compaction_thread_limiter = ls.limiter; - cf_to_limiter[cf_names[cf]] = &ls; - } - option_vector.emplace_back(DBOptions(options), cf_opt); - } - - for (unsigned int cf = 1; cf < cf_count; cf++) { - CreateColumnFamilies({cf_names[cf]}, option_vector[cf]); - } - - ReopenWithColumnFamilies( - std::vector(cf_names, cf_names + cf_count), option_vector); - - port::Mutex mutex; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:BeforeCompaction", [&](void* arg) { - const auto& cf_name = static_cast(arg)->GetName(); - auto iter = cf_to_limiter.find(cf_name); - if (iter != cf_to_limiter.end()) { - MutexLock l(&mutex); - ASSERT_GE(iter->second->limit_tasks, ++iter->second->tasks); - iter->second->max_tasks = - std::max(iter->second->max_tasks, iter->second->limit_tasks); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:AfterCompaction", [&](void* arg) { - const auto& cf_name = static_cast(arg)->GetName(); - auto iter = cf_to_limiter.find(cf_name); - if (iter != cf_to_limiter.end()) { - MutexLock l(&mutex); - ASSERT_GE(--iter->second->tasks, 0); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Block all compact threads in thread pool. - const size_t kTotalFlushTasks = kMaxBackgroundThreads / 4; - const size_t kTotalCompactTasks = kMaxBackgroundThreads - kTotalFlushTasks; - env_->SetBackgroundThreads((int)kTotalFlushTasks, Env::HIGH); - env_->SetBackgroundThreads((int)kTotalCompactTasks, Env::LOW); - - test::SleepingBackgroundTask sleeping_compact_tasks[kTotalCompactTasks]; - - // Block all compaction threads in thread pool. - for (size_t i = 0; i < kTotalCompactTasks; i++) { - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_compact_tasks[i], Env::LOW); - sleeping_compact_tasks[i].WaitUntilSleeping(); - } - - int keyIndex = 0; - - for (int n = 0; n < options.level0_file_num_compaction_trigger; n++) { - for (unsigned int cf = 0; cf < cf_count; cf++) { - for (int i = 0; i < kNumKeysPerFile; i++) { - ASSERT_OK(Put(cf, Key(keyIndex++), "")); - } - // put extra key to trigger flush - ASSERT_OK(Put(cf, "", "")); - } - - for (unsigned int cf = 0; cf < cf_count; cf++) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf])); - } - } - - // Enough L0 files to trigger compaction - for (unsigned int cf = 0; cf < cf_count; cf++) { - ASSERT_EQ(NumTableFilesAtLevel(0, cf), - options.level0_file_num_compaction_trigger); - } - - // Create more files for one column family, which triggers speed up - // condition, all compactions will be scheduled. - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - for (int i = 0; i < kNumKeysPerFile; i++) { - ASSERT_OK(Put(0, Key(i), "")); - } - // put extra key to trigger flush - ASSERT_OK(Put(0, "", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[0])); - ASSERT_EQ(options.level0_file_num_compaction_trigger + num + 1, - NumTableFilesAtLevel(0, 0)); - } - - // All CFs are pending compaction - ASSERT_EQ(cf_count, env_->GetThreadPoolQueueLen(Env::LOW)); - - // Unblock all compaction threads - for (size_t i = 0; i < kTotalCompactTasks; i++) { - sleeping_compact_tasks[i].WakeUp(); - sleeping_compact_tasks[i].WaitUntilDone(); - } - - for (unsigned int cf = 0; cf < cf_count; cf++) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf])); - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Max outstanding compact tasks reached limit - for (auto& ls : limiter_settings) { - ASSERT_EQ(ls.limit_tasks, ls.max_tasks); - ASSERT_EQ(0, ls.limiter->GetOutstandingTask()); - } - - // test manual compaction under a fully throttled limiter - int cf_test = 1; - unique_limiter->SetMaxOutstandingTask(0); - - // flush one more file to cf 1 - for (int i = 0; i < kNumKeysPerFile; i++) { - ASSERT_OK(Put(cf_test, Key(keyIndex++), "")); - } - // put extra key to trigger flush - ASSERT_OK(Put(cf_test, "", "")); - - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf_test])); - ASSERT_EQ(1, NumTableFilesAtLevel(0, cf_test)); - - Compact(cf_test, Key(0), Key(keyIndex)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); -} - -INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam, - ::testing::Values(std::make_tuple(1, true), - std::make_tuple(1, false), - std::make_tuple(4, true), - std::make_tuple(4, false))); - -TEST_P(DBCompactionDirectIOTest, DirectIO) { - Options options = CurrentOptions(); - Destroy(options); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.use_direct_io_for_flush_and_compaction = GetParam(); - options.env = MockEnv::Create(Env::Default()); - Reopen(options); - bool readahead = false; - SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::OpenCompactionOutputFile", [&](void* arg) { - bool* use_direct_writes = static_cast(arg); - ASSERT_EQ(*use_direct_writes, - options.use_direct_io_for_flush_and_compaction); - }); - if (options.use_direct_io_for_flush_and_compaction) { - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions:direct_io", [&](void* /*arg*/) { readahead = true; }); - } - SyncPoint::GetInstance()->EnableProcessing(); - CreateAndReopenWithCF({"pikachu"}, options); - MakeTables(3, "p", "q", 1); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - Compact(1, "p", "q"); - ASSERT_EQ(readahead, options.use_direct_reads); - ASSERT_EQ("0,0,1", FilesPerLevel(1)); - Destroy(options); - delete options.env; -} - -INSTANTIATE_TEST_CASE_P(DBCompactionDirectIOTest, DBCompactionDirectIOTest, - testing::Bool()); - -class CompactionPriTest : public DBTestBase, - public testing::WithParamInterface { - public: - CompactionPriTest() - : DBTestBase("compaction_pri_test", /*env_do_fsync=*/true) { - compaction_pri_ = GetParam(); - } - - // Required if inheriting from testing::WithParamInterface<> - static void SetUpTestCase() {} - static void TearDownTestCase() {} - - uint32_t compaction_pri_; -}; - -TEST_P(CompactionPriTest, Test) { - Options options = CurrentOptions(); - options.write_buffer_size = 16 * 1024; - options.compaction_pri = static_cast(compaction_pri_); - options.hard_pending_compaction_bytes_limit = 256 * 1024; - options.max_bytes_for_level_base = 64 * 1024; - options.max_bytes_for_level_multiplier = 4; - options.compression = kNoCompression; - - DestroyAndReopen(options); - - Random rnd(301); - const int kNKeys = 5000; - int keys[kNKeys]; - for (int i = 0; i < kNKeys; i++) { - keys[i] = i; - } - RandomShuffle(std::begin(keys), std::end(keys), rnd.Next()); - - for (int i = 0; i < kNKeys; i++) { - ASSERT_OK(Put(Key(keys[i]), rnd.RandomString(102))); - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - for (int i = 0; i < kNKeys; i++) { - ASSERT_NE("NOT_FOUND", Get(Key(i))); - } -} - -INSTANTIATE_TEST_CASE_P( - CompactionPriTest, CompactionPriTest, - ::testing::Values(CompactionPri::kByCompensatedSize, - CompactionPri::kOldestLargestSeqFirst, - CompactionPri::kOldestSmallestSeqFirst, - CompactionPri::kMinOverlappingRatio, - CompactionPri::kRoundRobin)); - -TEST_F(DBCompactionTest, PersistRoundRobinCompactCursor) { - Options options = CurrentOptions(); - options.write_buffer_size = 16 * 1024; - options.max_bytes_for_level_base = 128 * 1024; - options.target_file_size_base = 64 * 1024; - options.level0_file_num_compaction_trigger = 4; - options.compaction_pri = CompactionPri::kRoundRobin; - options.max_bytes_for_level_multiplier = 4; - options.num_levels = 3; - options.compression = kNoCompression; - - DestroyAndReopen(options); - - Random rnd(301); - - // 30 Files in L0 to trigger compactions between L1 and L2 - for (int i = 0; i < 30; i++) { - for (int j = 0; j < 16; j++) { - ASSERT_OK(Put(rnd.RandomString(24), rnd.RandomString(1000))); - } - ASSERT_OK(Flush()); - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - const std::vector compact_cursors = - storage_info->GetCompactCursors(); - - Reopen(options); - - VersionSet* const reopened_versions = dbfull()->GetVersionSet(); - assert(reopened_versions); - - ColumnFamilyData* const reopened_cfd = - reopened_versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(reopened_cfd, nullptr); - - Version* const reopened_current = reopened_cfd->current(); - ASSERT_NE(reopened_current, nullptr); - - const VersionStorageInfo* const reopened_storage_info = - reopened_current->storage_info(); - ASSERT_NE(reopened_storage_info, nullptr); - - const std::vector reopened_compact_cursors = - reopened_storage_info->GetCompactCursors(); - const auto icmp = reopened_storage_info->InternalComparator(); - ASSERT_EQ(compact_cursors.size(), reopened_compact_cursors.size()); - for (size_t i = 0; i < compact_cursors.size(); i++) { - if (compact_cursors[i].Valid()) { - ASSERT_EQ(0, - icmp->Compare(compact_cursors[i], reopened_compact_cursors[i])); - } else { - ASSERT_TRUE(!reopened_compact_cursors[i].Valid()); - } - } -} - -TEST_P(RoundRobinSubcompactionsAgainstPressureToken, PressureTokenTest) { - const int kKeysPerBuffer = 100; - Options options = CurrentOptions(); - options.num_levels = 4; - options.max_bytes_for_level_multiplier = 2; - options.level0_file_num_compaction_trigger = 4; - options.target_file_size_base = kKeysPerBuffer * 1024; - options.compaction_pri = CompactionPri::kRoundRobin; - options.max_bytes_for_level_base = 8 * kKeysPerBuffer * 1024; - options.disable_auto_compactions = true; - // Setup 7 threads but limited subcompactions so that - // RoundRobin requires extra compactions from reserved threads - options.max_subcompactions = 1; - options.max_background_compactions = 7; - options.max_compaction_bytes = 100000000; - DestroyAndReopen(options); - env_->SetBackgroundThreads(7, Env::LOW); - - Random rnd(301); - const std::vector files_per_level = {0, 15, 25}; - for (int lvl = 2; lvl > 0; lvl--) { - for (int i = 0; i < files_per_level[lvl]; i++) { - for (int j = 0; j < kKeysPerBuffer; j++) { - // Add (lvl-1) to ensure nearly equivallent number of files - // in L2 are overlapped with fils selected to compact from - // L1 - ASSERT_OK(Put(Key(2 * i * kKeysPerBuffer + 2 * j + (lvl - 1)), - rnd.RandomString(1010))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(lvl); - ASSERT_EQ(files_per_level[lvl], NumTableFilesAtLevel(lvl, 0)); - } - // 15 files in L1; 25 files in L2 - - // This is a variable for making sure the following callback is called - // and the assertions in it are indeed excuted. - bool num_planned_subcompactions_verified = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::GenSubcompactionBoundaries:0", [&](void* arg) { - uint64_t num_planned_subcompactions = *(static_cast(arg)); - if (grab_pressure_token_) { - // 7 files are selected for round-robin under auto - // compaction. The number of planned subcompaction is restricted by - // the limited number of max_background_compactions - ASSERT_EQ(num_planned_subcompactions, 7); - } else { - ASSERT_EQ(num_planned_subcompactions, 1); - } - num_planned_subcompactions_verified = true; - }); - - // The following 3 dependencies have to be added to ensure the auto - // compaction and the pressure token is correctly enabled. Same for - // RoundRobinSubcompactionsUsingResources and - // DBCompactionTest.RoundRobinSubcompactionsShrinkResources - SyncPoint::GetInstance()->LoadDependency( - {{"RoundRobinSubcompactionsAgainstPressureToken:0", - "BackgroundCallCompaction:0"}, - {"CompactionJob::AcquireSubcompactionResources:0", - "RoundRobinSubcompactionsAgainstPressureToken:1"}, - {"RoundRobinSubcompactionsAgainstPressureToken:2", - "CompactionJob::AcquireSubcompactionResources:1"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()})); - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstPressureToken:0"); - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstPressureToken:1"); - std::unique_ptr pressure_token; - if (grab_pressure_token_) { - pressure_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - } - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstPressureToken:2"); - - ASSERT_OK(dbfull()->WaitForCompact()); - ASSERT_TRUE(num_planned_subcompactions_verified); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -INSTANTIATE_TEST_CASE_P(RoundRobinSubcompactionsAgainstPressureToken, - RoundRobinSubcompactionsAgainstPressureToken, - testing::Bool()); - -TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) { - const int kKeysPerBuffer = 200; - Options options = CurrentOptions(); - options.num_levels = 4; - options.level0_file_num_compaction_trigger = 3; - options.target_file_size_base = kKeysPerBuffer * 1024; - options.compaction_pri = CompactionPri::kRoundRobin; - options.max_bytes_for_level_base = 30 * kKeysPerBuffer * 1024; - options.disable_auto_compactions = true; - options.max_subcompactions = 1; - options.max_background_compactions = max_compaction_limits_; - // Set a large number for max_compaction_bytes so that one round-robin - // compaction is enough to make post-compaction L1 size less than - // the maximum size (this test assumes only one round-robin compaction - // is triggered by kLevelMaxLevelSize) - options.max_compaction_bytes = 100000000; - - DestroyAndReopen(options); - env_->SetBackgroundThreads(total_low_pri_threads_, Env::LOW); - - Random rnd(301); - const std::vector files_per_level = {0, 40, 100}; - for (int lvl = 2; lvl > 0; lvl--) { - for (int i = 0; i < files_per_level[lvl]; i++) { - for (int j = 0; j < kKeysPerBuffer; j++) { - // Add (lvl-1) to ensure nearly equivallent number of files - // in L2 are overlapped with fils selected to compact from - // L1 - ASSERT_OK(Put(Key(2 * i * kKeysPerBuffer + 2 * j + (lvl - 1)), - rnd.RandomString(1010))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(lvl); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(files_per_level[lvl], NumTableFilesAtLevel(lvl, 0)); - } - - // 40 files in L1; 100 files in L2 - // This is a variable for making sure the following callback is called - // and the assertions in it are indeed excuted. - bool num_planned_subcompactions_verified = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::GenSubcompactionBoundaries:0", [&](void* arg) { - uint64_t num_planned_subcompactions = *(static_cast(arg)); - // More than 10 files are selected for round-robin under auto - // compaction. The number of planned subcompaction is restricted by - // the minimum number between available threads and compaction limits - ASSERT_EQ(num_planned_subcompactions - options.max_subcompactions, - std::min(total_low_pri_threads_, max_compaction_limits_) - 1); - num_planned_subcompactions_verified = true; - }); - SyncPoint::GetInstance()->LoadDependency( - {{"RoundRobinSubcompactionsAgainstResources:0", - "BackgroundCallCompaction:0"}, - {"CompactionJob::AcquireSubcompactionResources:0", - "RoundRobinSubcompactionsAgainstResources:1"}, - {"RoundRobinSubcompactionsAgainstResources:2", - "CompactionJob::AcquireSubcompactionResources:1"}, - {"CompactionJob::ReleaseSubcompactionResources:0", - "RoundRobinSubcompactionsAgainstResources:3"}, - {"RoundRobinSubcompactionsAgainstResources:4", - "CompactionJob::ReleaseSubcompactionResources:1"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->WaitForCompact()); - ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()})); - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:0"); - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:1"); - auto pressure_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:2"); - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:3"); - // We can reserve more threads now except one is being used - ASSERT_EQ(total_low_pri_threads_ - 1, - env_->ReserveThreads(total_low_pri_threads_, Env::Priority::LOW)); - ASSERT_EQ( - total_low_pri_threads_ - 1, - env_->ReleaseThreads(total_low_pri_threads_ - 1, Env::Priority::LOW)); - TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:4"); - ASSERT_OK(dbfull()->WaitForCompact()); - ASSERT_TRUE(num_planned_subcompactions_verified); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -INSTANTIATE_TEST_CASE_P(RoundRobinSubcompactionsAgainstResources, - RoundRobinSubcompactionsAgainstResources, - ::testing::Values(std::make_tuple(1, 5), - std::make_tuple(5, 1), - std::make_tuple(10, 5), - std::make_tuple(5, 10), - std::make_tuple(10, 10))); - -TEST_P(DBCompactionTestWithParam, RoundRobinWithoutAdditionalResources) { - const int kKeysPerBuffer = 200; - Options options = CurrentOptions(); - options.num_levels = 4; - options.level0_file_num_compaction_trigger = 3; - options.target_file_size_base = kKeysPerBuffer * 1024; - options.compaction_pri = CompactionPri::kRoundRobin; - options.max_bytes_for_level_base = 30 * kKeysPerBuffer * 1024; - options.disable_auto_compactions = true; - options.max_subcompactions = max_subcompactions_; - options.max_background_compactions = 1; - options.max_compaction_bytes = 100000000; - // Similar experiment setting as above except the max_subcompactions - // is given by max_subcompactions_ (1 or 4), and we fix the - // additional resources as (1, 1) and thus no more extra resources - // can be used - DestroyAndReopen(options); - env_->SetBackgroundThreads(1, Env::LOW); - - Random rnd(301); - const std::vector files_per_level = {0, 33, 100}; - for (int lvl = 2; lvl > 0; lvl--) { - for (int i = 0; i < files_per_level[lvl]; i++) { - for (int j = 0; j < kKeysPerBuffer; j++) { - // Add (lvl-1) to ensure nearly equivallent number of files - // in L2 are overlapped with fils selected to compact from - // L1 - ASSERT_OK(Put(Key(2 * i * kKeysPerBuffer + 2 * j + (lvl - 1)), - rnd.RandomString(1010))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(lvl); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(files_per_level[lvl], NumTableFilesAtLevel(lvl, 0)); - } - - // 33 files in L1; 100 files in L2 - // This is a variable for making sure the following callback is called - // and the assertions in it are indeed excuted. - bool num_planned_subcompactions_verified = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::GenSubcompactionBoundaries:0", [&](void* arg) { - uint64_t num_planned_subcompactions = *(static_cast(arg)); - // At most 4 files are selected for round-robin under auto - // compaction. The number of planned subcompaction is restricted by - // the max_subcompactions since no extra resources can be used - ASSERT_EQ(num_planned_subcompactions, options.max_subcompactions); - num_planned_subcompactions_verified = true; - }); - // No need to setup dependency for pressure token since - // AcquireSubcompactionResources may not be called and it anyway cannot - // reserve any additional resources - SyncPoint::GetInstance()->LoadDependency( - {{"DBCompactionTest::RoundRobinWithoutAdditionalResources:0", - "BackgroundCallCompaction:0"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->WaitForCompact()); - ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()})); - TEST_SYNC_POINT("DBCompactionTest::RoundRobinWithoutAdditionalResources:0"); - - ASSERT_OK(dbfull()->WaitForCompact()); - ASSERT_TRUE(num_planned_subcompactions_verified); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBCompactionTest, RoundRobinCutOutputAtCompactCursor) { - Options options = CurrentOptions(); - options.num_levels = 3; - options.compression = kNoCompression; - options.write_buffer_size = 4 * 1024; - options.max_bytes_for_level_base = 64 * 1024; - options.max_bytes_for_level_multiplier = 4; - options.level0_file_num_compaction_trigger = 4; - options.compaction_pri = CompactionPri::kRoundRobin; - - DestroyAndReopen(options); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - VersionStorageInfo* storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - const InternalKey split_cursor = InternalKey(Key(600), 100, kTypeValue); - storage_info->AddCursorForOneLevel(2, split_cursor); - - Random rnd(301); - - for (int i = 0; i < 50; i++) { - for (int j = 0; j < 50; j++) { - ASSERT_OK(Put(Key(j * 2 + i * 100), rnd.RandomString(102))); - } - } - // Add more overlapping files (avoid trivial move) to trigger compaction that - // output files in L2. Note that trivial move does not trigger compaction and - // in that case the cursor is not necessarily the boundary of file. - for (int i = 0; i < 50; i++) { - for (int j = 0; j < 50; j++) { - ASSERT_OK(Put(Key(j * 2 + 1 + i * 100), rnd.RandomString(1014))); - } - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - const auto icmp = cfd->current()->storage_info()->InternalComparator(); - // Files in level 2 should be split by the cursor - for (const auto& file : level_to_files[2]) { - ASSERT_TRUE( - icmp->Compare(file.smallest.Encode(), split_cursor.Encode()) >= 0 || - icmp->Compare(file.largest.Encode(), split_cursor.Encode()) < 0); - } -} - -class NoopMergeOperator : public MergeOperator { - public: - NoopMergeOperator() {} - - bool FullMergeV2(const MergeOperationInput& /*merge_in*/, - MergeOperationOutput* merge_out) const override { - std::string val("bar"); - merge_out->new_value = val; - return true; - } - - const char* Name() const override { return "Noop"; } -}; - -TEST_F(DBCompactionTest, PartialManualCompaction) { - Options opts = CurrentOptions(); - opts.num_levels = 3; - opts.level0_file_num_compaction_trigger = 10; - opts.compression = kNoCompression; - opts.merge_operator.reset(new NoopMergeOperator()); - opts.target_file_size_base = 10240; - DestroyAndReopen(opts); - - Random rnd(301); - for (auto i = 0; i < 8; ++i) { - for (auto j = 0; j < 10; ++j) { - ASSERT_OK(Merge("foo", rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - - MoveFilesToLevel(2); - - std::string prop; - EXPECT_TRUE(dbfull()->GetProperty(DB::Properties::kLiveSstFilesSize, &prop)); - uint64_t max_compaction_bytes = atoi(prop.c_str()) / 2; - ASSERT_OK(dbfull()->SetOptions( - {{"max_compaction_bytes", std::to_string(max_compaction_bytes)}})); - - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); -} - -TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) { - // Regression test for bug where manual compaction hangs forever when the DB - // is in read-only mode. Verify it now at least returns, despite failing. - const int kNumL0Files = 4; - std::unique_ptr mock_env( - new FaultInjectionTestEnv(env_)); - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.env = mock_env.get(); - DestroyAndReopen(opts); - - Random rnd(301); - for (int i = 0; i < kNumL0Files; ++i) { - // Make sure files are overlapping in key-range to prevent trivial move. - ASSERT_OK(Put("key1", rnd.RandomString(1024))); - ASSERT_OK(Put("key2", rnd.RandomString(1024))); - ASSERT_OK(Flush()); - } - ASSERT_EQ(kNumL0Files, NumTableFilesAtLevel(0)); - - // Enter read-only mode by failing a write. - mock_env->SetFilesystemActive(false); - // Make sure this is outside `CompactRange`'s range so that it doesn't fail - // early trying to flush memtable. - ASSERT_NOK(Put("key3", rnd.RandomString(1024))); - - // In the bug scenario, the first manual compaction would fail and forget to - // unregister itself, causing the second one to hang forever due to conflict - // with a non-running compaction. - CompactRangeOptions cro; - cro.exclusive_manual_compaction = false; - Slice begin_key("key1"); - Slice end_key("key2"); - ASSERT_NOK(dbfull()->CompactRange(cro, &begin_key, &end_key)); - ASSERT_NOK(dbfull()->CompactRange(cro, &begin_key, &end_key)); - - // Close before mock_env destruct. - Close(); -} - -// ManualCompactionBottomLevelOptimization tests the bottom level manual -// compaction optimization to skip recompacting files created by Ln-1 to Ln -// compaction -TEST_F(DBCompactionTest, ManualCompactionBottomLevelOptimized) { - Options opts = CurrentOptions(); - opts.num_levels = 3; - opts.level0_file_num_compaction_trigger = 5; - opts.compression = kNoCompression; - opts.merge_operator.reset(new NoopMergeOperator()); - opts.target_file_size_base = 1024; - opts.max_bytes_for_level_multiplier = 2; - opts.disable_auto_compactions = true; - DestroyAndReopen(opts); - ColumnFamilyHandleImpl* cfh = - static_cast(dbfull()->DefaultColumnFamily()); - ColumnFamilyData* cfd = cfh->cfd(); - InternalStats* internal_stats_ptr = cfd->internal_stats(); - ASSERT_NE(internal_stats_ptr, nullptr); - - Random rnd(301); - for (auto i = 0; i < 8; ++i) { - for (auto j = 0; j < 10; ++j) { - ASSERT_OK( - Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - - MoveFilesToLevel(2); - - for (auto i = 0; i < 8; ++i) { - for (auto j = 0; j < 10; ++j) { - ASSERT_OK( - Put("bar" + std::to_string(i * 10 + j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - const std::vector& comp_stats = - internal_stats_ptr->TEST_GetCompactionStats(); - int num = comp_stats[2].num_input_files_in_output_level; - ASSERT_EQ(num, 0); - - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - - const std::vector& comp_stats2 = - internal_stats_ptr->TEST_GetCompactionStats(); - num = comp_stats2[2].num_input_files_in_output_level; - ASSERT_EQ(num, 0); -} - -TEST_F(DBCompactionTest, ManualCompactionMax) { - uint64_t l1_avg_size = 0, l2_avg_size = 0; - auto generate_sst_func = [&]() { - Random rnd(301); - for (auto i = 0; i < 100; i++) { - for (auto j = 0; j < 10; j++) { - ASSERT_OK(Put(Key(i * 10 + j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - - for (auto i = 0; i < 10; i++) { - for (auto j = 0; j < 10; j++) { - ASSERT_OK(Put(Key(i * 100 + j * 10), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(1); - - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - - uint64_t total = 0; - for (const auto& file : level_to_files[1]) { - total += file.compensated_file_size; - } - l1_avg_size = total / level_to_files[1].size(); - - total = 0; - for (const auto& file : level_to_files[2]) { - total += file.compensated_file_size; - } - l2_avg_size = total / level_to_files[2].size(); - }; - - std::atomic_int num_compactions(0); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkCompaction", [&](void* /*arg*/) { ++num_compactions; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - - // with default setting (1.6G by default), it should cover all files in 1 - // compaction - DestroyAndReopen(opts); - generate_sst_func(); - num_compactions.store(0); - CompactRangeOptions cro; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_TRUE(num_compactions.load() == 1); - - // split the compaction to 5 - int num_split = 5; - DestroyAndReopen(opts); - generate_sst_func(); - uint64_t total_size = (l1_avg_size * 10) + (l2_avg_size * 100); - opts.max_compaction_bytes = total_size / num_split; - opts.target_file_size_base = total_size / num_split; - Reopen(opts); - num_compactions.store(0); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_TRUE(num_compactions.load() == num_split); - - // very small max_compaction_bytes, it should still move forward - opts.max_compaction_bytes = l1_avg_size / 2; - opts.target_file_size_base = l1_avg_size / 2; - DestroyAndReopen(opts); - generate_sst_func(); - num_compactions.store(0); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_TRUE(num_compactions.load() > 10); - - // dynamically set the option - num_split = 2; - opts.max_compaction_bytes = 0; - DestroyAndReopen(opts); - generate_sst_func(); - total_size = (l1_avg_size * 10) + (l2_avg_size * 100); - Status s = db_->SetOptions( - {{"max_compaction_bytes", std::to_string(total_size / num_split)}, - {"target_file_size_base", std::to_string(total_size / num_split)}}); - ASSERT_OK(s); - - num_compactions.store(0); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_TRUE(num_compactions.load() == num_split); -} - -TEST_F(DBCompactionTest, CompactionDuringShutdown) { - Options opts = CurrentOptions(); - opts.level0_file_num_compaction_trigger = 2; - opts.disable_auto_compactions = true; - DestroyAndReopen(opts); - ColumnFamilyHandleImpl* cfh = - static_cast(dbfull()->DefaultColumnFamily()); - ColumnFamilyData* cfd = cfh->cfd(); - InternalStats* internal_stats_ptr = cfd->internal_stats(); - ASSERT_NE(internal_stats_ptr, nullptr); - - Random rnd(301); - for (auto i = 0; i < 2; ++i) { - for (auto j = 0; j < 10; ++j) { - ASSERT_OK( - Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", - [&](void* /*arg*/) { dbfull()->shutting_down_.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); - ASSERT_TRUE(s.ok() || s.IsShutdownInProgress()); - ASSERT_OK(dbfull()->error_handler_.GetBGError()); -} - -// FixFileIngestionCompactionDeadlock tests and verifies that compaction and -// file ingestion do not cause deadlock in the event of write stall triggered -// by number of L0 files reaching level0_stop_writes_trigger. -TEST_P(DBCompactionTestWithParam, FixFileIngestionCompactionDeadlock) { - const int kNumKeysPerFile = 100; - // Generate SST files. - Options options = CurrentOptions(); - - // Generate an external SST file containing a single key, i.e. 99 - std::string sst_files_dir = dbname_ + "/sst_files/"; - ASSERT_OK(DestroyDir(env_, sst_files_dir)); - ASSERT_OK(env_->CreateDir(sst_files_dir)); - SstFileWriter sst_writer(EnvOptions(), options); - const std::string sst_file_path = sst_files_dir + "test.sst"; - ASSERT_OK(sst_writer.Open(sst_file_path)); - ASSERT_OK(sst_writer.Put(Key(kNumKeysPerFile - 1), "value")); - ASSERT_OK(sst_writer.Finish()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::IngestExternalFile:AfterIncIngestFileCounter", - "BackgroundCallCompaction:0"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - options.write_buffer_size = 110 << 10; // 110KB - options.level0_file_num_compaction_trigger = - options.level0_stop_writes_trigger; - options.max_subcompactions = max_subcompactions_; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - Random rnd(301); - - // Generate level0_stop_writes_trigger L0 files to trigger write stop - for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) { - for (int j = 0; j != kNumKeysPerFile; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(990))); - } - if (i > 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(NumTableFilesAtLevel(0 /*level*/, 0 /*cf*/), i); - } - } - // When we reach this point, there will be level0_stop_writes_trigger L0 - // files and one extra key (99) in memory, which overlaps with the external - // SST file. Write stall triggers, and can be cleared only after compaction - // reduces the number of L0 files. - - // Compaction will also be triggered since we have reached the threshold for - // auto compaction. Note that compaction may begin after the following file - // ingestion thread and waits for ingestion to finish. - - // Thread to ingest file with overlapping key range with the current - // memtable. Consequently ingestion will trigger a flush. The flush MUST - // proceed without waiting for the write stall condition to clear, otherwise - // deadlock can happen. - port::Thread ingestion_thr([&]() { - IngestExternalFileOptions ifo; - Status s = db_->IngestExternalFile({sst_file_path}, ifo); - ASSERT_OK(s); - }); - - // More write to trigger write stop - ingestion_thr.join(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - Close(); -} - -class DBCompactionTestWithOngoingFileIngestionParam - : public DBCompactionTest, - public testing::WithParamInterface { - public: - DBCompactionTestWithOngoingFileIngestionParam() : DBCompactionTest() { - compaction_path_to_test_ = GetParam(); - } - void SetupOptions() { - options_ = CurrentOptions(); - options_.create_if_missing = true; - - if (compaction_path_to_test_ == "RefitLevelCompactRange") { - options_.num_levels = 7; - } else { - options_.num_levels = 3; - } - options_.compaction_style = CompactionStyle::kCompactionStyleLevel; - if (compaction_path_to_test_ == "AutoCompaction") { - options_.disable_auto_compactions = false; - options_.level0_file_num_compaction_trigger = 1; - } else { - options_.disable_auto_compactions = true; - } - } - - void PauseCompactionThread() { - sleeping_task_.reset(new test::SleepingBackgroundTask()); - env_->SetBackgroundThreads(1, Env::LOW); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - sleeping_task_.get(), Env::Priority::LOW); - sleeping_task_->WaitUntilSleeping(); - } - - void ResumeCompactionThread() { - if (sleeping_task_) { - sleeping_task_->WakeUp(); - sleeping_task_->WaitUntilDone(); - } - } - - void SetupFilesToForceFutureFilesIngestedToCertainLevel() { - SstFileWriter sst_file_writer(EnvOptions(), options_); - std::string dummy = dbname_ + "/dummy.sst"; - ASSERT_OK(sst_file_writer.Open(dummy)); - ASSERT_OK(sst_file_writer.Put("k2", "dummy")); - ASSERT_OK(sst_file_writer.Finish()); - ASSERT_OK(db_->IngestExternalFile({dummy}, IngestExternalFileOptions())); - // L2 is made to contain a file overlapped with files to be ingested in - // later steps on key "k2". This will force future files ingested to L1 or - // above. - ASSERT_EQ("0,0,1", FilesPerLevel(0)); - } - - void SetupSyncPoints() { - if (compaction_path_to_test_ == "AutoCompaction") { - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Run", [&](void*) { - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCompaction():AfterPickCompaction", - "VersionSet::LogAndApply:WriteManifest"}}); - }); - } else if (compaction_path_to_test_ == "NonRefitLevelCompactRange") { - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Run", [&](void*) { - SyncPoint::GetInstance()->LoadDependency( - {{"ColumnFamilyData::CompactRange:Return", - "VersionSet::LogAndApply:WriteManifest"}}); - }); - } else if (compaction_path_to_test_ == "RefitLevelCompactRange") { - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Run", [&](void*) { - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::CompactRange:PostRefitLevel", - "VersionSet::LogAndApply:WriteManifest"}}); - }); - } else if (compaction_path_to_test_ == "CompactFiles") { - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Run", [&](void*) { - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::CompactFilesImpl::PostSanitizeCompactionInputFiles", - "VersionSet::LogAndApply:WriteManifest"}}); - }); - } else { - assert(false); - } - SyncPoint::GetInstance()->LoadDependency( - {{"ExternalSstFileIngestionJob::Run", "PreCompaction"}}); - SyncPoint::GetInstance()->EnableProcessing(); - } - - void RunCompactionOverlappedWithFileIngestion() { - if (compaction_path_to_test_ == "AutoCompaction") { - TEST_SYNC_POINT("PreCompaction"); - ResumeCompactionThread(); - // Without proper range conflict check, - // this would have been `Status::Corruption` about overlapping ranges - Status s = dbfull()->TEST_WaitForCompact(); - EXPECT_OK(s); - } else if (compaction_path_to_test_ == "NonRefitLevelCompactRange") { - CompactRangeOptions cro; - cro.change_level = false; - std::string start_key = "k1"; - Slice start(start_key); - std::string end_key = "k4"; - Slice end(end_key); - TEST_SYNC_POINT("PreCompaction"); - // Without proper range conflict check, - // this would have been `Status::Corruption` about overlapping ranges - Status s = dbfull()->CompactRange(cro, &start, &end); - EXPECT_OK(s); - } else if (compaction_path_to_test_ == "RefitLevelCompactRange") { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 5; - std::string start_key = "k1"; - Slice start(start_key); - std::string end_key = "k4"; - Slice end(end_key); - TEST_SYNC_POINT("PreCompaction"); - Status s = dbfull()->CompactRange(cro, &start, &end); - // Without proper range conflict check, - // this would have been `Status::Corruption` about overlapping ranges - // To see this, remove the fix AND replace - // `DBImpl::CompactRange:PostRefitLevel` in sync point dependency with - // `DBImpl::ReFitLevel:PostRegisterCompaction` - EXPECT_TRUE(s.IsNotSupported()); - EXPECT_TRUE(s.ToString().find("some ongoing compaction's output") != - std::string::npos); - } else if (compaction_path_to_test_ == "CompactFiles") { - ColumnFamilyMetaData cf_meta_data; - db_->GetColumnFamilyMetaData(&cf_meta_data); - ASSERT_EQ(cf_meta_data.levels[0].files.size(), 1); - std::vector input_files; - for (const auto& file : cf_meta_data.levels[0].files) { - input_files.push_back(file.name); - } - TEST_SYNC_POINT("PreCompaction"); - Status s = db_->CompactFiles(CompactionOptions(), input_files, 1); - // Without proper range conflict check, - // this would have been `Status::Corruption` about overlapping ranges - EXPECT_TRUE(s.IsAborted()); - EXPECT_TRUE( - s.ToString().find( - "A running compaction is writing to the same output level") != - std::string::npos); - } else { - assert(false); - } - } - - void DisableSyncPoints() { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - - protected: - std::string compaction_path_to_test_; - Options options_; - std::shared_ptr sleeping_task_; -}; - -INSTANTIATE_TEST_CASE_P(DBCompactionTestWithOngoingFileIngestionParam, - DBCompactionTestWithOngoingFileIngestionParam, - ::testing::Values("AutoCompaction", - "NonRefitLevelCompactRange", - "RefitLevelCompactRange", - "CompactFiles")); - -TEST_P(DBCompactionTestWithOngoingFileIngestionParam, RangeConflictCheck) { - SetupOptions(); - DestroyAndReopen(options_); - - if (compaction_path_to_test_ == "AutoCompaction") { - PauseCompactionThread(); - } - - if (compaction_path_to_test_ != "RefitLevelCompactRange") { - SetupFilesToForceFutureFilesIngestedToCertainLevel(); - } - - // Create s1 - ASSERT_OK(Put("k1", "v")); - ASSERT_OK(Put("k4", "v")); - ASSERT_OK(Flush()); - if (compaction_path_to_test_ == "RefitLevelCompactRange") { - MoveFilesToLevel(6 /* level */); - ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel(0)); - } else { - ASSERT_EQ("1,0,1", FilesPerLevel(0)); - } - - // To coerce following sequence of events - // Timeline Thread 1 (Ingest s2) Thread 2 (Compact s1) - // t0 | Decide to output to Lk - // t1 | Release lock in LogAndApply() - // t2 | Acquire lock - // t3 | Decides to compact to Lk - // | Expected to fail due to range - // | conflict check with file - // | ingestion - // t4 | Release lock in LogAndApply() - // t5 | Acquire lock again and finish - // t6 | Acquire lock again and finish - SetupSyncPoints(); - - // Ingest s2 - port::Thread thread1([&] { - SstFileWriter sst_file_writer(EnvOptions(), options_); - std::string s2 = dbname_ + "/ingested_s2.sst"; - ASSERT_OK(sst_file_writer.Open(s2)); - ASSERT_OK(sst_file_writer.Put("k2", "v2")); - ASSERT_OK(sst_file_writer.Put("k3", "v2")); - ASSERT_OK(sst_file_writer.Finish()); - ASSERT_OK(db_->IngestExternalFile({s2}, IngestExternalFileOptions())); - }); - - // Compact s1. Without proper range conflict check, - // this will encounter overlapping file corruption. - port::Thread thread2([&] { RunCompactionOverlappedWithFileIngestion(); }); - - thread1.join(); - thread2.join(); - DisableSyncPoints(); -} - -TEST_F(DBCompactionTest, ConsistencyFailTest) { - Options options = CurrentOptions(); - options.force_consistency_checks = true; - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionBuilder::CheckConsistency0", [&](void* arg) { - auto p = - reinterpret_cast*>(arg); - // just swap the two FileMetaData so that we hit error - // in CheckConsistency funcion - FileMetaData* temp = *(p->first); - *(p->first) = *(p->second); - *(p->second) = temp; - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - for (int k = 0; k < 2; ++k) { - ASSERT_OK(Put("foo", "bar")); - Status s = Flush(); - if (k < 1) { - ASSERT_OK(s); - } else { - ASSERT_TRUE(s.IsCorruption()); - } - } - - ASSERT_NOK(Put("foo", "bar")); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBCompactionTest, ConsistencyFailTest2) { - Options options = CurrentOptions(); - options.force_consistency_checks = true; - options.target_file_size_base = 1000; - options.level0_file_num_compaction_trigger = 2; - BlockBasedTableOptions bbto; - bbto.block_size = 400; // small block size - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionBuilder::CheckConsistency1", [&](void* arg) { - auto p = - reinterpret_cast*>(arg); - // just swap the two FileMetaData so that we hit error - // in CheckConsistency funcion - FileMetaData* temp = *(p->first); - *(p->first) = *(p->second); - *(p->second) = temp; - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - std::string value = rnd.RandomString(1000); - - ASSERT_OK(Put("foo1", value)); - ASSERT_OK(Put("z", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo2", value)); - ASSERT_OK(Put("z", "")); - Status s = Flush(); - ASSERT_TRUE(s.ok() || s.IsCorruption()); - - // This probably returns non-OK, but we rely on the next Put() - // to determine the DB is frozen. - ASSERT_NOK(dbfull()->TEST_WaitForCompact()); - ASSERT_NOK(Put("foo", "bar")); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -void IngestOneKeyValue(DBImpl* db, const std::string& key, - const std::string& value, const Options& options) { - ExternalSstFileInfo info; - std::string f = test::PerThreadDBPath("sst_file" + key); - EnvOptions env; - ROCKSDB_NAMESPACE::SstFileWriter writer(env, options); - auto s = writer.Open(f); - ASSERT_OK(s); - // ASSERT_OK(writer.Put(Key(), "")); - ASSERT_OK(writer.Put(key, value)); - - ASSERT_OK(writer.Finish(&info)); - IngestExternalFileOptions ingest_opt; - - ASSERT_OK(db->IngestExternalFile({info.file_path}, ingest_opt)); -} - -class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest { - public: - DBCompactionTestL0FilesMisorderCorruption() : DBCompactionTest() {} - void SetupOptions(const CompactionStyle compaciton_style, - const std::string& compaction_path_to_test = "") { - options_ = CurrentOptions(); - options_.create_if_missing = true; - options_.compression = kNoCompression; - - options_.force_consistency_checks = true; - options_.compaction_style = compaciton_style; - - if (compaciton_style == CompactionStyle::kCompactionStyleLevel) { - options_.num_levels = 7; - // Level compaction's PickIntraL0Compaction() impl detail requires - // `options.level0_file_num_compaction_trigger` to be - // at least 2 files less than the actual number of level 0 files - // (i.e, 7 by design in this test) - options_.level0_file_num_compaction_trigger = 5; - options_.max_background_compactions = 2; - options_.write_buffer_size = 2 << 20; - options_.max_write_buffer_number = 6; - } else if (compaciton_style == CompactionStyle::kCompactionStyleUniversal) { - // TODO: expand test coverage to num_lvels > 1 for universal compacion, - // which requires careful unit test design to compact to level 0 despite - // num_levels > 1 - options_.num_levels = 1; - options_.level0_file_num_compaction_trigger = 5; - - CompactionOptionsUniversal universal_options; - if (compaction_path_to_test == "PickCompactionToReduceSizeAmp") { - universal_options.max_size_amplification_percent = 50; - } else if (compaction_path_to_test == - "PickCompactionToReduceSortedRuns") { - universal_options.max_size_amplification_percent = 400; - } else if (compaction_path_to_test == "PickDeleteTriggeredCompaction") { - universal_options.max_size_amplification_percent = 400; - universal_options.min_merge_width = 6; - } - options_.compaction_options_universal = universal_options; - } else if (compaciton_style == CompactionStyle::kCompactionStyleFIFO) { - options_.max_open_files = -1; - options_.num_levels = 1; - options_.level0_file_num_compaction_trigger = 3; - - CompactionOptionsFIFO fifo_options; - if (compaction_path_to_test == "FindIntraL0Compaction" || - compaction_path_to_test == "CompactRange") { - fifo_options.allow_compaction = true; - fifo_options.age_for_warm = 0; - } else if (compaction_path_to_test == "CompactFile") { - fifo_options.allow_compaction = false; - fifo_options.age_for_warm = 0; - } - options_.compaction_options_fifo = fifo_options; - } - - if (compaction_path_to_test == "CompactFile" || - compaction_path_to_test == "CompactRange") { - options_.disable_auto_compactions = true; - } else { - options_.disable_auto_compactions = false; - } - } - - void Destroy(const Options& options) { - if (snapshot_) { - assert(db_); - db_->ReleaseSnapshot(snapshot_); - snapshot_ = nullptr; - } - DBTestBase::Destroy(options); - } - - void Reopen(const Options& options) { - DBTestBase::Reopen(options); - if (options.compaction_style != CompactionStyle::kCompactionStyleLevel) { - // To force assigning the global seqno to ingested file - // for our test purpose. - assert(snapshot_ == nullptr); - snapshot_ = db_->GetSnapshot(); - } - } - - void DestroyAndReopen(Options& options) { - Destroy(options); - Reopen(options); - } - - void PauseCompactionThread() { - sleeping_task_.reset(new test::SleepingBackgroundTask()); - env_->SetBackgroundThreads(1, Env::LOW); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - sleeping_task_.get(), Env::Priority::LOW); - sleeping_task_->WaitUntilSleeping(); - } - - void ResumeCompactionThread() { - if (sleeping_task_) { - sleeping_task_->WakeUp(); - sleeping_task_->WaitUntilDone(); - } - } - - void AddFilesMarkedForPeriodicCompaction(const size_t num_files) { - assert(options_.compaction_style == - CompactionStyle::kCompactionStyleUniversal); - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - Version* const current = cfd->current(); - assert(current); - - VersionStorageInfo* const storage_info = current->storage_info(); - assert(storage_info); - - const std::vector level0_files = storage_info->LevelFiles(0); - assert(level0_files.size() == num_files); - - for (FileMetaData* f : level0_files) { - storage_info->TEST_AddFileMarkedForPeriodicCompaction(0, f); - } - } - - void AddFilesMarkedForCompaction(const size_t num_files) { - assert(options_.compaction_style == - CompactionStyle::kCompactionStyleUniversal); - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - Version* const current = cfd->current(); - assert(current); - - VersionStorageInfo* const storage_info = current->storage_info(); - assert(storage_info); - - const std::vector level0_files = storage_info->LevelFiles(0); - assert(level0_files.size() == num_files); - - for (FileMetaData* f : level0_files) { - storage_info->TEST_AddFileMarkedForCompaction(0, f); - } - } - - void SetupSyncPoints(const std::string& compaction_path_to_test) { - compaction_path_sync_point_called_.store(false); - if (compaction_path_to_test == "FindIntraL0Compaction" && - options_.compaction_style == CompactionStyle::kCompactionStyleLevel) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PostPickFileToCompact", [&](void* arg) { - bool* picked_file_to_compact = (bool*)arg; - // To trigger intra-L0 compaction specifically, - // we mock PickFileToCompact()'s result to be false - *picked_file_to_compact = false; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FindIntraL0Compaction", [&](void* /*arg*/) { - compaction_path_sync_point_called_.store(true); - }); - - } else if (compaction_path_to_test == "PickPeriodicCompaction") { - assert(options_.compaction_style == - CompactionStyle::kCompactionStyleUniversal); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PostPickPeriodicCompaction", [&](void* compaction_arg) { - Compaction* compaction = (Compaction*)compaction_arg; - if (compaction != nullptr) { - compaction_path_sync_point_called_.store(true); - } - }); - } else if (compaction_path_to_test == "PickCompactionToReduceSizeAmp") { - assert(options_.compaction_style == - CompactionStyle::kCompactionStyleUniversal); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PickCompactionToReduceSizeAmpReturnNonnullptr", [&](void* /*arg*/) { - compaction_path_sync_point_called_.store(true); - }); - } else if (compaction_path_to_test == "PickCompactionToReduceSortedRuns") { - assert(options_.compaction_style == - CompactionStyle::kCompactionStyleUniversal); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PickCompactionToReduceSortedRunsReturnNonnullptr", - [&](void* /*arg*/) { - compaction_path_sync_point_called_.store(true); - }); - } else if (compaction_path_to_test == "PickDeleteTriggeredCompaction") { - assert(options_.compaction_style == - CompactionStyle::kCompactionStyleUniversal); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PickDeleteTriggeredCompactionReturnNonnullptr", [&](void* /*arg*/) { - compaction_path_sync_point_called_.store(true); - }); - } else if ((compaction_path_to_test == "FindIntraL0Compaction" || - compaction_path_to_test == "CompactRange") && - options_.compaction_style == - CompactionStyle::kCompactionStyleFIFO) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FindIntraL0Compaction", [&](void* /*arg*/) { - compaction_path_sync_point_called_.store(true); - }); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - } - - bool SyncPointsCalled() { return compaction_path_sync_point_called_.load(); } - - void DisableSyncPoints() { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - - // Return the largest seqno of the latest L0 file based on file number - SequenceNumber GetLatestL0FileLargestSeqnoHelper() { - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - Version* const current = cfd->current(); - assert(current); - VersionStorageInfo* const storage_info = current->storage_info(); - assert(storage_info); - const std::vector level0_files = storage_info->LevelFiles(0); - assert(level0_files.size() >= 1); - - uint64_t latest_file_num = 0; - uint64_t latest_file_largest_seqno = 0; - for (FileMetaData* f : level0_files) { - if (f->fd.GetNumber() > latest_file_num) { - latest_file_num = f->fd.GetNumber(); - latest_file_largest_seqno = f->fd.largest_seqno; - } - } - - return latest_file_largest_seqno; - } - - protected: - Options options_; - - private: - const Snapshot* snapshot_ = nullptr; - std::atomic compaction_path_sync_point_called_; - std::shared_ptr sleeping_task_; -}; - -TEST_F(DBCompactionTestL0FilesMisorderCorruption, - FlushAfterIntraL0LevelCompactionWithIngestedFile) { - SetupOptions(CompactionStyle::kCompactionStyleLevel, ""); - DestroyAndReopen(options_); - // Prevents trivial move - for (int i = 0; i < 10; ++i) { - ASSERT_OK(Put(Key(i), "")); // Prevents trivial move - } - ASSERT_OK(Flush()); - Compact("", Key(99)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - - // To get accurate NumTableFilesAtLevel(0) when the number reaches - // options_.level0_file_num_compaction_trigger - PauseCompactionThread(); - - // To create below LSM tree - // (key:value@n indicates key-value pair has seqno "n", L0 is sorted): - // - // memtable: m1[ 5:new@12 .. 1:new@8, 0:new@7] - // L0: s6[6:new@13], s5[5:old@6] ... s1[1:old@2],s0[0:old@1] - // - // (1) Make 6 L0 sst (i.e, s0 - s5) - for (int i = 0; i < 6; ++i) { - if (i % 2 == 0) { - IngestOneKeyValue(dbfull(), Key(i), "old", options_); - } else { - ASSERT_OK(Put(Key(i), "old")); - ASSERT_OK(Flush()); - } - } - ASSERT_EQ(6, NumTableFilesAtLevel(0)); - - // (2) Create m1 - for (int i = 0; i < 6; ++i) { - ASSERT_OK(Put(Key(i), "new")); - } - ASSERT_EQ(6, NumTableFilesAtLevel(0)); - - // (3) Ingest file (i.e, s6) to trigger IntraL0Compaction() - for (int i = 6; i < 7; ++i) { - ASSERT_EQ(i, NumTableFilesAtLevel(0)); - IngestOneKeyValue(dbfull(), Key(i), "new", options_); - } - - SetupSyncPoints("FindIntraL0Compaction"); - ResumeCompactionThread(); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_TRUE(SyncPointsCalled()); - DisableSyncPoints(); - - // After compaction, we have LSM tree: - // - // memtable: m1[ 5:new@12 .. 1:new@8, 0:new@7] - // L0: s7[6:new@13, 5:old@6 .. 0:old@1] - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - SequenceNumber compact_output_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - ASSERT_OK(Flush()); - // After flush, we have LSM tree: - // - // L0: s8[5:new@12 .. 0:new@7],s7[6:new@13, 5:old@5 .. 0:old@1] - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - SequenceNumber flushed_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - // To verify there isn't any file misorder leading to returning a old value - // of Key(0) - Key(5) , which is caused by flushed table s8 has a - // smaller largest seqno than the compaction output file s7's largest seqno - // while the flushed table has the newer version of the values than the - // compaction output file's. - ASSERT_TRUE(flushed_file_largest_seqno < compact_output_file_largest_seqno); - for (int i = 0; i < 6; ++i) { - ASSERT_EQ("new", Get(Key(i))); - } - for (int i = 6; i < 7; ++i) { - ASSERT_EQ("new", Get(Key(i))); - } -} - -TEST_F(DBCompactionTestL0FilesMisorderCorruption, - FlushAfterIntraL0UniversalCompactionWithIngestedFile) { - for (const std::string compaction_path_to_test : - {"PickPeriodicCompaction", "PickCompactionToReduceSizeAmp", - "PickCompactionToReduceSortedRuns", "PickDeleteTriggeredCompaction"}) { - SetupOptions(CompactionStyle::kCompactionStyleUniversal, - compaction_path_to_test); - DestroyAndReopen(options_); - - // To get accurate NumTableFilesAtLevel(0) when the number reaches - // options_.level0_file_num_compaction_trigger - PauseCompactionThread(); - - // To create below LSM tree - // (key:value@n indicates key-value pair has seqno "n", L0 is sorted): - // - // memtable: m1 [ k2:new@8, k1:new@7] - // L0: s4[k9:dummy@10], s3[k8:dummy@9], - // s2[k7:old@6, k6:old@5].. s0[k3:old@2, k1:old@1] - // - // (1) Create 3 existing SST file (i.e, s0 - s2) - ASSERT_OK(Put("k1", "old")); - ASSERT_OK(Put("k3", "old")); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_OK(Put("k4", "old")); - ASSERT_OK(Put("k5", "old")); - ASSERT_OK(Flush()); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - ASSERT_OK(Put("k6", "old")); - ASSERT_OK(Put("k7", "old")); - ASSERT_OK(Flush()); - ASSERT_EQ(3, NumTableFilesAtLevel(0)); - - // (2) Create m1. Noted that it contains a overlaped key with s0 - ASSERT_OK(Put("k1", "new")); // overlapped key - ASSERT_OK(Put("k2", "new")); - - // (3) Ingest two SST files s3, s4 - IngestOneKeyValue(dbfull(), "k8", "dummy", options_); - IngestOneKeyValue(dbfull(), "k9", "dummy", options_); - // Up to now, L0 contains s0 - s4 - ASSERT_EQ(5, NumTableFilesAtLevel(0)); - - if (compaction_path_to_test == "PickPeriodicCompaction") { - AddFilesMarkedForPeriodicCompaction(5); - } else if (compaction_path_to_test == "PickDeleteTriggeredCompaction") { - AddFilesMarkedForCompaction(5); - } - - SetupSyncPoints(compaction_path_to_test); - ResumeCompactionThread(); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_TRUE(SyncPointsCalled()) - << "failed for compaction path to test: " << compaction_path_to_test; - DisableSyncPoints(); - - // After compaction, we have LSM tree: - // - // memtable: m1[ k2:new@8, k1:new@7] - // L0: s5[k9:dummy@10, k8@dummy@9, k7:old@6 .. k3:old@2, k1:old@1] - ASSERT_EQ(1, NumTableFilesAtLevel(0)) - << "failed for compaction path to test: " << compaction_path_to_test; - SequenceNumber compact_output_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - ASSERT_OK(Flush()) << "failed for compaction path to test: " - << compaction_path_to_test; - // After flush, we have LSM tree: - // - // L0: s6[k2:new@8, k1:new@7], - // s5[k9:dummy@10, k8@dummy@9, k7:old@6 .. k3:old@2, k1:old@1] - ASSERT_EQ(2, NumTableFilesAtLevel(0)) - << "failed for compaction path to test: " << compaction_path_to_test; - SequenceNumber flushed_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - // To verify there isn't any file misorder leading to returning a old - // value of "k1" , which is caused by flushed table s6 has a - // smaller largest seqno than the compaction output file s5's largest seqno - // while the flushed table has the newer version of the value - // than the compaction output file's. - ASSERT_TRUE(flushed_file_largest_seqno < compact_output_file_largest_seqno) - << "failed for compaction path to test: " << compaction_path_to_test; - EXPECT_EQ(Get("k1"), "new") - << "failed for compaction path to test: " << compaction_path_to_test; - } - - Destroy(options_); -} - -TEST_F(DBCompactionTestL0FilesMisorderCorruption, - FlushAfterIntraL0FIFOCompactionWithIngestedFile) { - for (const std::string compaction_path_to_test : {"FindIntraL0Compaction"}) { - SetupOptions(CompactionStyle::kCompactionStyleFIFO, - compaction_path_to_test); - DestroyAndReopen(options_); - - // To create below LSM tree - // (key:value@n indicates key-value pair has seqno "n", L0 is sorted): - // - // memtable: m1 [ k2:new@4, k1:new@3] - // L0: s2[k5:dummy@6], s1[k4:dummy@5], s0[k3:old@2, k1:old@1] - // - // (1) Create an existing SST file s0 - ASSERT_OK(Put("k1", "old")); - ASSERT_OK(Put("k3", "old")); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // (2) Create memtable m1. Noted that it contains a overlaped key with s0 - ASSERT_OK(Put("k1", "new")); // overlapped key - ASSERT_OK(Put("k2", "new")); - - // To get accurate NumTableFilesAtLevel(0) when the number reaches - // options_.level0_file_num_compaction_trigger - PauseCompactionThread(); - - // (3) Ingest two SST files s1, s2 - IngestOneKeyValue(dbfull(), "k4", "dummy", options_); - IngestOneKeyValue(dbfull(), "k5", "dummy", options_); - // Up to now, L0 contains s0, s1, s2 - ASSERT_EQ(3, NumTableFilesAtLevel(0)); - - SetupSyncPoints(compaction_path_to_test); - ResumeCompactionThread(); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_TRUE(SyncPointsCalled()) - << "failed for compaction path to test: " << compaction_path_to_test; - DisableSyncPoints(); - // After compaction, we have LSM tree: - // - // memtable: m1 [ k2:new@4, k1:new@3] - // L0: s3[k5:dummy@6, k4:dummy@5, k3:old@2, k1:old@1] - ASSERT_EQ(1, NumTableFilesAtLevel(0)) - << "failed for compaction path to test: " << compaction_path_to_test; - SequenceNumber compact_output_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - ASSERT_OK(Flush()) << "failed for compaction path to test: " - << compaction_path_to_test; - // After flush, we have LSM tree: - // - // L0: s4[k2:new@4, k1:new@3], s3[k5:dummy@6, k4:dummy@5, k3:old@2, - // k1:old@1] - ASSERT_EQ(2, NumTableFilesAtLevel(0)) - << "failed for compaction path to test: " << compaction_path_to_test; - SequenceNumber flushed_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - // To verify there isn't any file misorder leading to returning a old - // value of "k1" , which is caused by flushed table s4 has a - // smaller largest seqno than the compaction output file s3's largest seqno - // while the flushed table has the newer version of the value - // than the compaction output file's. - ASSERT_TRUE(flushed_file_largest_seqno < compact_output_file_largest_seqno) - << "failed for compaction path to test: " << compaction_path_to_test; - EXPECT_EQ(Get("k1"), "new") - << "failed for compaction path to test: " << compaction_path_to_test; - } - - Destroy(options_); -} - -class DBCompactionTestL0FilesMisorderCorruptionWithParam - : public DBCompactionTestL0FilesMisorderCorruption, - public testing::WithParamInterface { - public: - DBCompactionTestL0FilesMisorderCorruptionWithParam() - : DBCompactionTestL0FilesMisorderCorruption() {} -}; - -// TODO: add `CompactionStyle::kCompactionStyleLevel` to testing parameter, -// which requires careful unit test -// design for ingesting file to L0 and CompactRange()/CompactFile() to L0 -INSTANTIATE_TEST_CASE_P( - DBCompactionTestL0FilesMisorderCorruptionWithParam, - DBCompactionTestL0FilesMisorderCorruptionWithParam, - ::testing::Values(CompactionStyle::kCompactionStyleUniversal, - CompactionStyle::kCompactionStyleFIFO)); - -TEST_P(DBCompactionTestL0FilesMisorderCorruptionWithParam, - FlushAfterIntraL0CompactFileWithIngestedFile) { - SetupOptions(GetParam(), "CompactFile"); - DestroyAndReopen(options_); - - // To create below LSM tree - // (key:value@n indicates key-value pair has seqno "n", L0 is sorted): - // - // memtable: m1 [ k2:new@4, k1:new@3] - // L0: s2[k5:dummy@6], s1[k4:dummy@5], s0[k3:old@2, k1:old@1] - // - // (1) Create an existing SST file s0 - ASSERT_OK(Put("k1", "old")); - ASSERT_OK(Put("k3", "old")); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // (2) Create memtable m1. Noted that it contains a overlaped key with s0 - ASSERT_OK(Put("k1", "new")); // overlapped key - ASSERT_OK(Put("k2", "new")); - - // (3) Ingest two SST files s1, s2 - IngestOneKeyValue(dbfull(), "k4", "dummy", options_); - IngestOneKeyValue(dbfull(), "k5", "dummy", options_); - // Up to now, L0 contains s0, s1, s2 - ASSERT_EQ(3, NumTableFilesAtLevel(0)); - - ColumnFamilyMetaData cf_meta_data; - db_->GetColumnFamilyMetaData(&cf_meta_data); - ASSERT_EQ(cf_meta_data.levels[0].files.size(), 3); - std::vector input_files; - for (const auto& file : cf_meta_data.levels[0].files) { - input_files.push_back(file.name); - } - ASSERT_EQ(input_files.size(), 3); - - Status s = db_->CompactFiles(CompactionOptions(), input_files, 0); - // After compaction, we have LSM tree: - // - // memtable: m1 [ k2:new@4, k1:new@3] - // L0: s3[k5:dummy@6, k4:dummy@5, k3:old@2, k1:old@1] - ASSERT_OK(s); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - SequenceNumber compact_output_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - ASSERT_OK(Flush()); - // After flush, we have LSM tree: - // - // L0: s4[k2:new@4, k1:new@3], s3[k5:dummy@6, k4:dummy@5, k3:old@2, - // k1:old@1] - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - SequenceNumber flushed_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - // To verify there isn't any file misorder leading to returning a old value - // of "1" , which is caused by flushed table s4 has a smaller - // largest seqno than the compaction output file s3's largest seqno while the - // flushed table has the newer version of the value than the - // compaction output file's. - ASSERT_TRUE(flushed_file_largest_seqno < compact_output_file_largest_seqno); - EXPECT_EQ(Get("k1"), "new"); - - Destroy(options_); -} - -TEST_P(DBCompactionTestL0FilesMisorderCorruptionWithParam, - FlushAfterIntraL0CompactRangeWithIngestedFile) { - SetupOptions(GetParam(), "CompactRange"); - DestroyAndReopen(options_); - - // To create below LSM tree - // (key:value@n indicates key-value pair has seqno "n", L0 is sorted): - // - // memtable: m1 [ k2:new@4, k1:new@3] - // L0: s2[k5:dummy@6], s1[k4:dummy@5], s0[k3:old@2, k1:old@1] - // - // (1) Create an existing SST file s0 - ASSERT_OK(Put("k1", "old")); - ASSERT_OK(Put("k3", "old")); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // (2) Create memtable m1. Noted that it contains a overlaped key with s0 - ASSERT_OK(Put("k1", "new")); // overlapped key - ASSERT_OK(Put("k2", "new")); - - // (3) Ingest two SST files s1, s2 - IngestOneKeyValue(dbfull(), "k4", "dummy", options_); - IngestOneKeyValue(dbfull(), "k5", "dummy", options_); - // Up to now, L0 contains s0, s1, s2 - ASSERT_EQ(3, NumTableFilesAtLevel(0)); - - if (options_.compaction_style == CompactionStyle::kCompactionStyleFIFO) { - SetupSyncPoints("CompactRange"); - } - // `start` and `end` is carefully chosen so that compact range: - // (1) doesn't overlap with memtable therefore the memtable won't be flushed - // (2) should target at compacting s0 with s1 and s2 - Slice start("k3"), end("k5"); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end)); - // After compaction, we have LSM tree: - // - // memtable: m1 [ k2:new@4, k1:new@3] - // L0: s3[k5:dummy@6, k4:dummy@5, k3:old@2, k1:old@1] - if (options_.compaction_style == CompactionStyle::kCompactionStyleFIFO) { - ASSERT_TRUE(SyncPointsCalled()); - DisableSyncPoints(); - } - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - SequenceNumber compact_output_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - ASSERT_OK(Flush()); - // After flush, we have LSM tree: - // - // L0: s4[k2:new@4, k1:new@3], s3[k5:dummy@6, k4:dummy@5, k3:old@2, - // k1:old@1] - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - SequenceNumber flushed_file_largest_seqno = - GetLatestL0FileLargestSeqnoHelper(); - - // To verify there isn't any file misorder leading to returning a old value - // of "k1" , which is caused by flushed table s4 has a smaller - // largest seqno than the compaction output file s3's largest seqno while the - // flushed table has the newer version of the value than the - // compaction output file's. - ASSERT_TRUE(flushed_file_largest_seqno < compact_output_file_largest_seqno); - EXPECT_EQ(Get("k1"), "new"); - - Destroy(options_); -} - -TEST_P(DBCompactionTestWithBottommostParam, SequenceKeysManualCompaction) { - constexpr int kSstNum = 10; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - // Generate some sst files on level 0 with sequence keys (no overlap) - for (int i = 0; i < kSstNum; i++) { - for (int j = 1; j < UCHAR_MAX; j++) { - auto key = std::string(kSstNum, '\0'); - key[kSstNum - i] += static_cast(j); - ASSERT_OK(Put(key, std::string(i % 1000, 'A'))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - ASSERT_EQ(std::to_string(kSstNum), FilesPerLevel(0)); - - auto cro = CompactRangeOptions(); - cro.bottommost_level_compaction = bottommost_level_compaction_; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - if (bottommost_level_compaction_ == BottommostLevelCompaction::kForce || - bottommost_level_compaction_ == - BottommostLevelCompaction::kForceOptimized) { - // Real compaction to compact all sst files from level 0 to 1 file on level - // 1 - ASSERT_EQ("0,1", FilesPerLevel(0)); - } else { - // Just trivial move from level 0 -> 1 - ASSERT_EQ("0," + std::to_string(kSstNum), FilesPerLevel(0)); - } -} - -INSTANTIATE_TEST_CASE_P( - DBCompactionTestWithBottommostParam, DBCompactionTestWithBottommostParam, - ::testing::Values(BottommostLevelCompaction::kSkip, - BottommostLevelCompaction::kIfHaveCompactionFilter, - BottommostLevelCompaction::kForce, - BottommostLevelCompaction::kForceOptimized)); - -TEST_F(DBCompactionTest, UpdateLevelSubCompactionTest) { - Options options = CurrentOptions(); - options.max_subcompactions = 10; - options.target_file_size_base = 1 << 10; // 1KB - DestroyAndReopen(options); - - bool has_compaction = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->max_subcompactions() == 10); - has_compaction = true; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_TRUE(dbfull()->GetDBOptions().max_subcompactions == 10); - // Trigger compaction - for (int i = 0; i < 32; i++) { - for (int j = 0; j < 5000; j++) { - ASSERT_OK(Put(std::to_string(j), std::string(1, 'A'))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(has_compaction); - - has_compaction = false; - ASSERT_OK(dbfull()->SetDBOptions({{"max_subcompactions", "2"}})); - ASSERT_TRUE(dbfull()->GetDBOptions().max_subcompactions == 2); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->max_subcompactions() == 2); - has_compaction = true; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Trigger compaction - for (int i = 0; i < 32; i++) { - for (int j = 0; j < 5000; j++) { - ASSERT_OK(Put(std::to_string(j), std::string(1, 'A'))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(has_compaction); -} - -TEST_F(DBCompactionTest, UpdateUniversalSubCompactionTest) { - Options options = CurrentOptions(); - options.max_subcompactions = 10; - options.compaction_style = kCompactionStyleUniversal; - options.target_file_size_base = 1 << 10; // 1KB - DestroyAndReopen(options); - - bool has_compaction = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->max_subcompactions() == 10); - has_compaction = true; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Trigger compaction - for (int i = 0; i < 32; i++) { - for (int j = 0; j < 5000; j++) { - ASSERT_OK(Put(std::to_string(j), std::string(1, 'A'))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(has_compaction); - has_compaction = false; - - ASSERT_OK(dbfull()->SetDBOptions({{"max_subcompactions", "2"}})); - ASSERT_TRUE(dbfull()->GetDBOptions().max_subcompactions == 2); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(compaction->max_subcompactions() == 2); - has_compaction = true; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Trigger compaction - for (int i = 0; i < 32; i++) { - for (int j = 0; j < 5000; j++) { - ASSERT_OK(Put(std::to_string(j), std::string(1, 'A'))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(has_compaction); -} - -TEST_P(ChangeLevelConflictsWithAuto, TestConflict) { - // A `CompactRange()` may race with an automatic compaction, we'll need - // to make sure it doesn't corrupte the data. - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - Reopen(options); - - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Put("bar", "v1")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 2; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - ASSERT_EQ("0,0,1", FilesPerLevel(0)); - - // Run a qury to refitting to level 1 while another thread writing to - // the same level. - SyncPoint::GetInstance()->LoadDependency({ - // The first two dependencies ensure the foreground creates an L0 file - // between the background compaction's L0->L1 and its L1->L2. - { - "DBImpl::CompactRange:BeforeRefit:1", - "AutoCompactionFinished1", - }, - { - "AutoCompactionFinished2", - "DBImpl::CompactRange:BeforeRefit:2", - }, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - std::thread auto_comp([&] { - TEST_SYNC_POINT("AutoCompactionFinished1"); - ASSERT_OK(Put("bar", "v2")); - ASSERT_OK(Put("foo", "v2")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("bar", "v3")); - ASSERT_OK(Put("foo", "v3")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - TEST_SYNC_POINT("AutoCompactionFinished2"); - }); - - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = GetParam() ? 1 : 0; - // This should return non-OK, but it's more important for the test to - // make sure that the DB is not corrupted. - ASSERT_NOK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - auto_comp.join(); - // Refitting didn't happen. - SyncPoint::GetInstance()->DisableProcessing(); - - // Write something to DB just make sure that consistency check didn't - // fail and make the DB readable. -} - -INSTANTIATE_TEST_CASE_P(ChangeLevelConflictsWithAuto, - ChangeLevelConflictsWithAuto, testing::Bool()); - -TEST_F(DBCompactionTest, ChangeLevelCompactRangeConflictsWithManual) { - // A `CompactRange()` with `change_level == true` needs to execute its final - // step, `ReFitLevel()`, in isolation. Previously there was a bug where - // refitting could target the same level as an ongoing manual compaction, - // leading to overlapping files in that level. - // - // This test ensures that case is not possible by verifying any manual - // compaction issued during the `ReFitLevel()` phase fails with - // `Status::Incomplete`. - Options options = CurrentOptions(); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 3; - Reopen(options); - - // Setup an LSM with three levels populated. - Random rnd(301); - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 2; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - ASSERT_EQ("0,0,2", FilesPerLevel(0)); - - GenerateNewFile(&rnd, &key_idx); - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1,1,2", FilesPerLevel(0)); - - // The background thread will refit L2->L1 while the - // foreground thread will try to simultaneously compact L0->L1. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - // The first two dependencies ensure the foreground creates an L0 file - // between the background compaction's L0->L1 and its L1->L2. - { - "DBImpl::RunManualCompaction()::1", - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:" - "PutFG", - }, - { - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:" - "FlushedFG", - "DBImpl::RunManualCompaction()::2", - }, - // The next two dependencies ensure the foreground invokes - // `CompactRange()` while the background is refitting. The - // foreground's `CompactRange()` is guaranteed to attempt an L0->L1 - // as we set it up with an empty memtable and a new L0 file. - { - "DBImpl::CompactRange:PreRefitLevel", - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:" - "CompactFG", - }, - { - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:" - "CompactedFG", - "DBImpl::CompactRange:PostRefitLevel", - }, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread refit_level_thread([&] { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 1; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - }); - - TEST_SYNC_POINT( - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:PutFG"); - // Make sure we have something new to compact in the foreground. - // Note key 1 is carefully chosen as it ensures the file we create here - // overlaps with one of the files being refitted L2->L1 in the background. - // If we chose key 0, the file created here would not overlap. - ASSERT_OK(Put(Key(1), "val")); - ASSERT_OK(Flush()); - TEST_SYNC_POINT( - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:FlushedFG"); - - TEST_SYNC_POINT( - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:CompactFG"); - ASSERT_TRUE(dbfull() - ->CompactRange(CompactRangeOptions(), nullptr, nullptr) - .IsIncomplete()); - TEST_SYNC_POINT( - "DBCompactionTest::ChangeLevelCompactRangeConflictsWithManual:" - "CompactedFG"); - refit_level_thread.join(); -} - -TEST_F(DBCompactionTest, ChangeLevelErrorPathTest) { - // This test is added to ensure that RefitLevel() error paths are clearing - // internal flags and to test that subsequent valid RefitLevel() calls - // succeeds - Options options = CurrentOptions(); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 3; - Reopen(options); - - ASSERT_EQ("", FilesPerLevel(0)); - - // Setup an LSM with three levels populated. - Random rnd(301); - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ("1", FilesPerLevel(0)); - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 2; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - ASSERT_EQ("0,0,2", FilesPerLevel(0)); - - auto start_idx = key_idx; - GenerateNewFile(&rnd, &key_idx); - GenerateNewFile(&rnd, &key_idx); - auto end_idx = key_idx - 1; - ASSERT_EQ("1,1,2", FilesPerLevel(0)); - - // Next two CompactRange() calls are used to test exercise error paths within - // RefitLevel() before triggering a valid RefitLevel() call - - // Trigger a refit to L1 first - { - std::string begin_string = Key(start_idx); - std::string end_string = Key(end_idx); - Slice begin(begin_string); - Slice end(end_string); - - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 1; - ASSERT_OK(dbfull()->CompactRange(cro, &begin, &end)); - } - ASSERT_EQ("0,3,2", FilesPerLevel(0)); - - // Try a refit from L2->L1 - this should fail and exercise error paths in - // RefitLevel() - { - // Select key range that matches the bottom most level (L2) - std::string begin_string = Key(0); - std::string end_string = Key(start_idx - 1); - Slice begin(begin_string); - Slice end(end_string); - - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 1; - ASSERT_NOK(dbfull()->CompactRange(cro, &begin, &end)); - } - ASSERT_EQ("0,3,2", FilesPerLevel(0)); - - // Try a valid Refit request to ensure, the path is still working - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 1; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - ASSERT_EQ("0,5", FilesPerLevel(0)); -} - -TEST_F(DBCompactionTest, CompactionWithBlob) { - Options options; - options.env = env_; - options.disable_auto_compactions = true; - - Reopen(options); - - constexpr char first_key[] = "first_key"; - constexpr char second_key[] = "second_key"; - constexpr char first_value[] = "first_value"; - constexpr char second_value[] = "second_value"; - constexpr char third_value[] = "third_value"; - - ASSERT_OK(Put(first_key, first_value)); - ASSERT_OK(Put(second_key, first_value)); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(first_key, second_value)); - ASSERT_OK(Put(second_key, second_value)); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(first_key, third_value)); - ASSERT_OK(Put(second_key, third_value)); - ASSERT_OK(Flush()); - - options.enable_blob_files = true; - - Reopen(options); - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end)); - - ASSERT_EQ(Get(first_key), third_value); - ASSERT_EQ(Get(second_key), third_value); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - const auto& l1_files = storage_info->LevelFiles(1); - ASSERT_EQ(l1_files.size(), 1); - - const FileMetaData* const table_file = l1_files[0]; - ASSERT_NE(table_file, nullptr); - - const auto& blob_files = storage_info->GetBlobFiles(); - ASSERT_EQ(blob_files.size(), 1); - - const auto& blob_file = blob_files.front(); - ASSERT_NE(blob_file, nullptr); - - ASSERT_EQ(table_file->smallest.user_key(), first_key); - ASSERT_EQ(table_file->largest.user_key(), second_key); - ASSERT_EQ(table_file->oldest_blob_file_number, - blob_file->GetBlobFileNumber()); - - ASSERT_EQ(blob_file->GetTotalBlobCount(), 2); - - const InternalStats* const internal_stats = cfd->internal_stats(); - ASSERT_NE(internal_stats, nullptr); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_GE(compaction_stats.size(), 2); - ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0); - ASSERT_EQ(compaction_stats[1].bytes_written, table_file->fd.GetFileSize()); - ASSERT_EQ(compaction_stats[1].bytes_written_blob, - blob_file->GetTotalBlobBytes()); - ASSERT_EQ(compaction_stats[1].num_output_files, 1); - ASSERT_EQ(compaction_stats[1].num_output_files_blob, 1); -} - -class DBCompactionTestBlobError - : public DBCompactionTest, - public testing::WithParamInterface { - public: - DBCompactionTestBlobError() : sync_point_(GetParam()) {} - - std::string sync_point_; -}; - -INSTANTIATE_TEST_CASE_P(DBCompactionTestBlobError, DBCompactionTestBlobError, - ::testing::ValuesIn(std::vector{ - "BlobFileBuilder::WriteBlobToFile:AddRecord", - "BlobFileBuilder::WriteBlobToFile:AppendFooter"})); - -TEST_P(DBCompactionTestBlobError, CompactionError) { - Options options; - options.disable_auto_compactions = true; - options.env = env_; - - Reopen(options); - - constexpr char first_key[] = "first_key"; - constexpr char second_key[] = "second_key"; - constexpr char first_value[] = "first_value"; - constexpr char second_value[] = "second_value"; - constexpr char third_value[] = "third_value"; - - ASSERT_OK(Put(first_key, first_value)); - ASSERT_OK(Put(second_key, first_value)); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(first_key, second_value)); - ASSERT_OK(Put(second_key, second_value)); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(first_key, third_value)); - ASSERT_OK(Put(second_key, third_value)); - ASSERT_OK(Flush()); - - options.enable_blob_files = true; - - Reopen(options); - - SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) { - Status* const s = static_cast(arg); - assert(s); - - (*s) = Status::IOError(sync_point_); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), begin, end).IsIOError()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - const auto& l1_files = storage_info->LevelFiles(1); - ASSERT_TRUE(l1_files.empty()); - - const auto& blob_files = storage_info->GetBlobFiles(); - ASSERT_TRUE(blob_files.empty()); - - const InternalStats* const internal_stats = cfd->internal_stats(); - ASSERT_NE(internal_stats, nullptr); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_GE(compaction_stats.size(), 2); - - if (sync_point_ == "BlobFileBuilder::WriteBlobToFile:AddRecord") { - ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0); - ASSERT_EQ(compaction_stats[1].bytes_written, 0); - ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0); - ASSERT_EQ(compaction_stats[1].num_output_files, 0); - ASSERT_EQ(compaction_stats[1].num_output_files_blob, 0); - } else { - // SST file writing succeeded; blob file writing failed (during Finish) - ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0); - ASSERT_GT(compaction_stats[1].bytes_written, 0); - ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0); - ASSERT_EQ(compaction_stats[1].num_output_files, 1); - ASSERT_EQ(compaction_stats[1].num_output_files_blob, 0); - } -} - -class DBCompactionTestBlobGC - : public DBCompactionTest, - public testing::WithParamInterface> { - public: - DBCompactionTestBlobGC() - : blob_gc_age_cutoff_(std::get<0>(GetParam())), - updated_enable_blob_files_(std::get<1>(GetParam())) {} - - double blob_gc_age_cutoff_; - bool updated_enable_blob_files_; -}; - -INSTANTIATE_TEST_CASE_P(DBCompactionTestBlobGC, DBCompactionTestBlobGC, - ::testing::Combine(::testing::Values(0.0, 0.5, 1.0), - ::testing::Bool())); - -TEST_P(DBCompactionTestBlobGC, CompactionWithBlobGCOverrides) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.enable_blob_files = true; - options.blob_file_size = 32; // one blob per file - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0; - - DestroyAndReopen(options); - - for (int i = 0; i < 128; i += 2) { - ASSERT_OK(Put("key" + std::to_string(i), "value" + std::to_string(i))); - ASSERT_OK( - Put("key" + std::to_string(i + 1), "value" + std::to_string(i + 1))); - ASSERT_OK(Flush()); - } - - std::vector original_blob_files = GetBlobFileNumbers(); - ASSERT_EQ(original_blob_files.size(), 128); - - // Note: turning off enable_blob_files before the compaction results in - // garbage collected values getting inlined. - ASSERT_OK(db_->SetOptions({{"enable_blob_files", "false"}})); - - CompactRangeOptions cro; - cro.blob_garbage_collection_policy = BlobGarbageCollectionPolicy::kForce; - cro.blob_garbage_collection_age_cutoff = blob_gc_age_cutoff_; - - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - // Check that the GC stats are correct - { - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - assert(versions->GetColumnFamilySet()); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - const InternalStats* const internal_stats = cfd->internal_stats(); - assert(internal_stats); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_GE(compaction_stats.size(), 2); - - ASSERT_GE(compaction_stats[1].bytes_read_blob, 0); - ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0); - } - - const size_t cutoff_index = static_cast( - cro.blob_garbage_collection_age_cutoff * original_blob_files.size()); - const size_t expected_num_files = original_blob_files.size() - cutoff_index; - - const std::vector new_blob_files = GetBlobFileNumbers(); - - ASSERT_EQ(new_blob_files.size(), expected_num_files); - - // Original blob files below the cutoff should be gone, original blob files - // at or above the cutoff should be still there - for (size_t i = cutoff_index; i < original_blob_files.size(); ++i) { - ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]); - } - - for (size_t i = 0; i < 128; ++i) { - ASSERT_EQ(Get("key" + std::to_string(i)), "value" + std::to_string(i)); - } -} - -TEST_P(DBCompactionTestBlobGC, CompactionWithBlobGC) { - Options options; - options.env = env_; - options.disable_auto_compactions = true; - options.enable_blob_files = true; - options.blob_file_size = 32; // one blob per file - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = blob_gc_age_cutoff_; - - Reopen(options); - - constexpr char first_key[] = "first_key"; - constexpr char first_value[] = "first_value"; - constexpr char second_key[] = "second_key"; - constexpr char second_value[] = "second_value"; - - ASSERT_OK(Put(first_key, first_value)); - ASSERT_OK(Put(second_key, second_value)); - ASSERT_OK(Flush()); - - constexpr char third_key[] = "third_key"; - constexpr char third_value[] = "third_value"; - constexpr char fourth_key[] = "fourth_key"; - constexpr char fourth_value[] = "fourth_value"; - - ASSERT_OK(Put(third_key, third_value)); - ASSERT_OK(Put(fourth_key, fourth_value)); - ASSERT_OK(Flush()); - - const std::vector original_blob_files = GetBlobFileNumbers(); - - ASSERT_EQ(original_blob_files.size(), 4); - - const size_t cutoff_index = static_cast( - options.blob_garbage_collection_age_cutoff * original_blob_files.size()); - - // Note: turning off enable_blob_files before the compaction results in - // garbage collected values getting inlined. - size_t expected_number_of_files = original_blob_files.size(); - - if (!updated_enable_blob_files_) { - ASSERT_OK(db_->SetOptions({{"enable_blob_files", "false"}})); - - expected_number_of_files -= cutoff_index; - } - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end)); - - ASSERT_EQ(Get(first_key), first_value); - ASSERT_EQ(Get(second_key), second_value); - ASSERT_EQ(Get(third_key), third_value); - ASSERT_EQ(Get(fourth_key), fourth_value); - - const std::vector new_blob_files = GetBlobFileNumbers(); - - ASSERT_EQ(new_blob_files.size(), expected_number_of_files); - - // Original blob files below the cutoff should be gone, original blob files at - // or above the cutoff should be still there - for (size_t i = cutoff_index; i < original_blob_files.size(); ++i) { - ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]); - } - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - assert(versions->GetColumnFamilySet()); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - const InternalStats* const internal_stats = cfd->internal_stats(); - assert(internal_stats); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_GE(compaction_stats.size(), 2); - - if (blob_gc_age_cutoff_ > 0.0) { - ASSERT_GT(compaction_stats[1].bytes_read_blob, 0); - - if (updated_enable_blob_files_) { - // GC relocated some blobs to new blob files - ASSERT_GT(compaction_stats[1].bytes_written_blob, 0); - ASSERT_EQ(compaction_stats[1].bytes_read_blob, - compaction_stats[1].bytes_written_blob); - } else { - // GC moved some blobs back to the LSM, no new blob files - ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0); - } - } else { - ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0); - ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0); - } -} - -TEST_F(DBCompactionTest, CompactionWithBlobGCError_CorruptIndex) { - Options options; - options.env = env_; - options.disable_auto_compactions = true; - options.enable_blob_files = true; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 1.0; - - Reopen(options); - - constexpr char first_key[] = "first_key"; - constexpr char first_value[] = "first_value"; - ASSERT_OK(Put(first_key, first_value)); - - constexpr char second_key[] = "second_key"; - constexpr char second_value[] = "second_value"; - ASSERT_OK(Put(second_key, second_value)); - - ASSERT_OK(Flush()); - - constexpr char third_key[] = "third_key"; - constexpr char third_value[] = "third_value"; - ASSERT_OK(Put(third_key, third_value)); - - constexpr char fourth_key[] = "fourth_key"; - constexpr char fourth_value[] = "fourth_value"; - ASSERT_OK(Put(fourth_key, fourth_value)); - - ASSERT_OK(Flush()); - - SyncPoint::GetInstance()->SetCallBack( - "CompactionIterator::GarbageCollectBlobIfNeeded::TamperWithBlobIndex", - [](void* arg) { - Slice* const blob_index = static_cast(arg); - assert(blob_index); - assert(!blob_index->empty()); - blob_index->remove_prefix(1); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_TRUE( - db_->CompactRange(CompactRangeOptions(), begin, end).IsCorruption()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBCompactionTest, CompactionWithBlobGCError_InlinedTTLIndex) { - constexpr uint64_t min_blob_size = 10; - - Options options; - options.env = env_; - options.disable_auto_compactions = true; - options.enable_blob_files = true; - options.min_blob_size = min_blob_size; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 1.0; - - Reopen(options); - - constexpr char first_key[] = "first_key"; - constexpr char first_value[] = "first_value"; - ASSERT_OK(Put(first_key, first_value)); - - constexpr char second_key[] = "second_key"; - constexpr char second_value[] = "second_value"; - ASSERT_OK(Put(second_key, second_value)); - - ASSERT_OK(Flush()); - - constexpr char third_key[] = "third_key"; - constexpr char third_value[] = "third_value"; - ASSERT_OK(Put(third_key, third_value)); - - constexpr char fourth_key[] = "fourth_key"; - constexpr char blob[] = "short"; - static_assert(sizeof(short) - 1 < min_blob_size, - "Blob too long to be inlined"); - - // Fake an inlined TTL blob index. - std::string blob_index; - - constexpr uint64_t expiration = 1234567890; - - BlobIndex::EncodeInlinedTTL(&blob_index, expiration, blob); - - WriteBatch batch; - ASSERT_OK( - WriteBatchInternal::PutBlobIndex(&batch, 0, fourth_key, blob_index)); - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - ASSERT_OK(Flush()); - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_TRUE( - db_->CompactRange(CompactRangeOptions(), begin, end).IsCorruption()); -} - -TEST_F(DBCompactionTest, CompactionWithBlobGCError_IndexWithInvalidFileNumber) { - Options options; - options.env = env_; - options.disable_auto_compactions = true; - options.enable_blob_files = true; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 1.0; - - Reopen(options); - - constexpr char first_key[] = "first_key"; - constexpr char first_value[] = "first_value"; - ASSERT_OK(Put(first_key, first_value)); - - constexpr char second_key[] = "second_key"; - constexpr char second_value[] = "second_value"; - ASSERT_OK(Put(second_key, second_value)); - - ASSERT_OK(Flush()); - - constexpr char third_key[] = "third_key"; - constexpr char third_value[] = "third_value"; - ASSERT_OK(Put(third_key, third_value)); - - constexpr char fourth_key[] = "fourth_key"; - - // Fake a blob index referencing a non-existent blob file. - std::string blob_index; - - constexpr uint64_t blob_file_number = 1000; - constexpr uint64_t offset = 1234; - constexpr uint64_t size = 5678; - - BlobIndex::EncodeBlob(&blob_index, blob_file_number, offset, size, - kNoCompression); - - WriteBatch batch; - ASSERT_OK( - WriteBatchInternal::PutBlobIndex(&batch, 0, fourth_key, blob_index)); - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - ASSERT_OK(Flush()); - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_TRUE( - db_->CompactRange(CompactRangeOptions(), begin, end).IsCorruption()); -} - -TEST_F(DBCompactionTest, CompactionWithChecksumHandoff1) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 3; - options.env = fault_fs_env.get(); - options.create_if_missing = true; - options.checksum_handoff_file_types.Add(FileType::kTableFile); - Status s; - Reopen(options); - - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - Destroy(options); - Reopen(options); - - // The hash does not match, compaction write fails - // fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - // Since the file system returns IOStatus::Corruption, it is an - // unrecoverable error. - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), - ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); - Reopen(options); - - // The file system does not support checksum handoff. The check - // will be ignored. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - - // Each write will be similated as corrupted. - // Since the file system returns IOStatus::Corruption, it is an - // unrecoverable error. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", - [&](void*) { fault_fs->IngestDataCorruptionBeforeWrite(); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), - ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError); - SyncPoint::GetInstance()->DisableProcessing(); - - Destroy(options); -} - -TEST_F(DBCompactionTest, CompactionWithChecksumHandoff2) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 3; - options.env = fault_fs_env.get(); - options.create_if_missing = true; - Status s; - Reopen(options); - - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - Destroy(options); - Reopen(options); - - // options is not set, the checksum handoff will not be triggered - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); - Reopen(options); - - // The file system does not support checksum handoff. The check - // will be ignored. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - - // options is not set, the checksum handoff will not be triggered - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", - [&](void*) { fault_fs->IngestDataCorruptionBeforeWrite(); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - - Destroy(options); -} - -TEST_F(DBCompactionTest, CompactionWithChecksumHandoffManifest1) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 3; - options.env = fault_fs_env.get(); - options.create_if_missing = true; - options.checksum_handoff_file_types.Add(FileType::kDescriptorFile); - Status s; - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - Reopen(options); - - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - Destroy(options); - Reopen(options); - - // The hash does not match, compaction write fails - // fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - // Since the file system returns IOStatus::Corruption, it is mapped to - // kFatalError error. - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); -} - -TEST_F(DBCompactionTest, CompactionWithChecksumHandoffManifest2) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 3; - options.env = fault_fs_env.get(); - options.create_if_missing = true; - options.checksum_handoff_file_types.Add(FileType::kDescriptorFile); - Status s; - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - Reopen(options); - - // The file system does not support checksum handoff. The check - // will be ignored. - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s, Status::OK()); - - // Each write will be similated as corrupted. - // Since the file system returns IOStatus::Corruption, it is mapped to - // kFatalError error. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put(Key(0), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", - [&](void*) { fault_fs->IngestDataCorruptionBeforeWrite(); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(Key(1), "value3")); - s = Flush(); - ASSERT_EQ(s, Status::OK()); - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - SyncPoint::GetInstance()->DisableProcessing(); - - Destroy(options); -} - -TEST_F(DBCompactionTest, FIFOWarm) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleFIFO; - options.num_levels = 1; - options.max_open_files = -1; - options.level0_file_num_compaction_trigger = 2; - options.create_if_missing = true; - CompactionOptionsFIFO fifo_options; - fifo_options.age_for_warm = 1000; - fifo_options.max_table_files_size = 100000000; - options.compaction_options_fifo = fifo_options; - env_->SetMockSleep(); - Reopen(options); - - int total_warm = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "NewWritableFile::FileOptions.temperature", [&](void* arg) { - Temperature temperature = *(static_cast(arg)); - if (temperature == Temperature::kWarm) { - total_warm++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // The file system does not support checksum handoff. The check - // will be ignored. - ASSERT_OK(Put(Key(0), "value1")); - env_->MockSleepForSeconds(800); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(0), "value1")); - env_->MockSleepForSeconds(800); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(0), "value1")); - env_->MockSleepForSeconds(800); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_OK(Put(Key(0), "value1")); - env_->MockSleepForSeconds(800); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ColumnFamilyMetaData metadata; - db_->GetColumnFamilyMetaData(&metadata); - ASSERT_EQ(4, metadata.file_count); - ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature); - ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[1].temperature); - ASSERT_EQ(Temperature::kWarm, metadata.levels[0].files[2].temperature); - ASSERT_EQ(Temperature::kWarm, metadata.levels[0].files[3].temperature); - ASSERT_EQ(2, total_warm); - - Destroy(options); -} - -TEST_F(DBCompactionTest, DisableMultiManualCompaction) { - const int kNumL0Files = 10; - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - // Generate 2 levels of file to make sure the manual compaction is not skipped - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), "value")); - if (i % 2) { - ASSERT_OK(Flush()); - } - } - MoveFilesToLevel(2); - - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), "value")); - if (i % 2) { - ASSERT_OK(Flush()); - } - } - MoveFilesToLevel(1); - - // Block compaction queue - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - port::Thread compact_thread1([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = false; - std::string begin_str = Key(0); - std::string end_str = Key(3); - Slice b = begin_str; - Slice e = end_str; - auto s = db_->CompactRange(cro, &b, &e); - ASSERT_TRUE(s.IsIncomplete()); - }); - - port::Thread compact_thread2([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = false; - std::string begin_str = Key(4); - std::string end_str = Key(7); - Slice b = begin_str; - Slice e = end_str; - auto s = db_->CompactRange(cro, &b, &e); - ASSERT_TRUE(s.IsIncomplete()); - }); - - // Disable manual compaction should cancel both manual compactions and both - // compaction should return incomplete. - db_->DisableManualCompaction(); - - compact_thread1.join(); - compact_thread2.join(); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); -} - -TEST_F(DBCompactionTest, DisableJustStartedManualCompaction) { - const int kNumL0Files = 4; - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - // generate files, but avoid trigger auto compaction - for (int i = 0; i < kNumL0Files / 2; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - - // make sure the manual compaction background is started but not yet set the - // status to in_progress, then cancel the manual compaction, which should not - // result in segfault - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BGWorkCompaction", - "DBCompactionTest::DisableJustStartedManualCompaction:" - "PreDisableManualCompaction"}, - {"DBImpl::RunManualCompaction:Unscheduled", - "BackgroundCallCompaction:0"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - port::Thread compact_thread([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - auto s = db_->CompactRange(cro, nullptr, nullptr); - ASSERT_TRUE(s.IsIncomplete()); - }); - TEST_SYNC_POINT( - "DBCompactionTest::DisableJustStartedManualCompaction:" - "PreDisableManualCompaction"); - db_->DisableManualCompaction(); - - compact_thread.join(); -} - -TEST_F(DBCompactionTest, DisableInProgressManualCompaction) { - const int kNumL0Files = 4; - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCompaction:InProgress", - "DBCompactionTest::DisableInProgressManualCompaction:" - "PreDisableManualCompaction"}, - {"DBImpl::RunManualCompaction:Unscheduled", - "CompactionJob::Run():Start"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - // generate files, but avoid trigger auto compaction - for (int i = 0; i < kNumL0Files / 2; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - - port::Thread compact_thread([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - auto s = db_->CompactRange(cro, nullptr, nullptr); - ASSERT_TRUE(s.IsIncomplete()); - }); - - TEST_SYNC_POINT( - "DBCompactionTest::DisableInProgressManualCompaction:" - "PreDisableManualCompaction"); - db_->DisableManualCompaction(); - - compact_thread.join(); -} - -TEST_F(DBCompactionTest, DisableManualCompactionThreadQueueFull) { - const int kNumL0Files = 4; - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::RunManualCompaction:Scheduled", - "DBCompactionTest::DisableManualCompactionThreadQueueFull:" - "PreDisableManualCompaction"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - // Block compaction queue - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - // generate files, but avoid trigger auto compaction - for (int i = 0; i < kNumL0Files / 2; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - - port::Thread compact_thread([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - auto s = db_->CompactRange(cro, nullptr, nullptr); - ASSERT_TRUE(s.IsIncomplete()); - }); - - TEST_SYNC_POINT( - "DBCompactionTest::DisableManualCompactionThreadQueueFull:" - "PreDisableManualCompaction"); - - // Generate more files to trigger auto compaction which is scheduled after - // manual compaction. Has to generate 4 more files because existing files are - // pending compaction - for (int i = 0; i < kNumL0Files; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - ASSERT_EQ(std::to_string(kNumL0Files + (kNumL0Files / 2)), FilesPerLevel(0)); - - db_->DisableManualCompaction(); - - // CompactRange should return before the compaction has the chance to run - compact_thread.join(); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - ASSERT_EQ("0,1", FilesPerLevel(0)); -} - -TEST_F(DBCompactionTest, DisableManualCompactionThreadQueueFullDBClose) { - const int kNumL0Files = 4; - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::RunManualCompaction:Scheduled", - "DBCompactionTest::DisableManualCompactionThreadQueueFullDBClose:" - "PreDisableManualCompaction"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - // Block compaction queue - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - // generate files, but avoid trigger auto compaction - for (int i = 0; i < kNumL0Files / 2; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - - port::Thread compact_thread([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - auto s = db_->CompactRange(cro, nullptr, nullptr); - ASSERT_TRUE(s.IsIncomplete()); - }); - - TEST_SYNC_POINT( - "DBCompactionTest::DisableManualCompactionThreadQueueFullDBClose:" - "PreDisableManualCompaction"); - - // Generate more files to trigger auto compaction which is scheduled after - // manual compaction. Has to generate 4 more files because existing files are - // pending compaction - for (int i = 0; i < kNumL0Files; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - ASSERT_EQ(std::to_string(kNumL0Files + (kNumL0Files / 2)), FilesPerLevel(0)); - - db_->DisableManualCompaction(); - - // CompactRange should return before the compaction has the chance to run - compact_thread.join(); - - // Try close DB while manual compaction is canceled but still in the queue. - // And an auto-triggered compaction is also in the queue. - auto s = db_->Close(); - ASSERT_OK(s); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); -} - -TEST_F(DBCompactionTest, DBCloseWithManualCompaction) { - const int kNumL0Files = 4; - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::RunManualCompaction:Scheduled", - "DBCompactionTest::DisableManualCompactionThreadQueueFullDBClose:" - "PreDisableManualCompaction"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - // Block compaction queue - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - // generate files, but avoid trigger auto compaction - for (int i = 0; i < kNumL0Files / 2; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - - port::Thread compact_thread([&]() { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - auto s = db_->CompactRange(cro, nullptr, nullptr); - ASSERT_TRUE(s.IsIncomplete()); - }); - - TEST_SYNC_POINT( - "DBCompactionTest::DisableManualCompactionThreadQueueFullDBClose:" - "PreDisableManualCompaction"); - - // Generate more files to trigger auto compaction which is scheduled after - // manual compaction. Has to generate 4 more files because existing files are - // pending compaction - for (int i = 0; i < kNumL0Files; i++) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - ASSERT_EQ(std::to_string(kNumL0Files + (kNumL0Files / 2)), FilesPerLevel(0)); - - // Close DB with manual compaction and auto triggered compaction in the queue. - auto s = db_->Close(); - ASSERT_OK(s); - - // manual compaction thread should return with Incomplete(). - compact_thread.join(); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); -} - -TEST_F(DBCompactionTest, - DisableManualCompactionDoesNotWaitForDrainingAutomaticCompaction) { - // When `CompactRangeOptions::exclusive_manual_compaction == true`, we wait - // for automatic compactions to drain before starting the manual compaction. - // This test verifies `DisableManualCompaction()` can cancel such a compaction - // without waiting for the drain to complete. - const int kNumL0Files = 4; - - // Enforces manual compaction enters wait loop due to pending automatic - // compaction. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BGWorkCompaction", "DBImpl::RunManualCompaction:NotScheduled"}, - {"DBImpl::RunManualCompaction:WaitScheduled", - "BackgroundCallCompaction:0"}}); - // The automatic compaction will cancel the waiting manual compaction. - // Completing this implies the cancellation did not wait on automatic - // compactions to finish. - bool callback_completed = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void* /*arg*/) { - db_->DisableManualCompaction(); - callback_completed = true; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - Reopen(options); - - for (int i = 0; i < kNumL0Files; ++i) { - ASSERT_OK(Put(Key(1), "value1")); - ASSERT_OK(Put(Key(2), "value2")); - ASSERT_OK(Flush()); - } - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = true; - ASSERT_TRUE(db_->CompactRange(cro, nullptr, nullptr).IsIncomplete()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(callback_completed); -} - -TEST_F(DBCompactionTest, ChangeLevelConflictsWithManual) { - Options options = CurrentOptions(); - options.num_levels = 3; - Reopen(options); - - // Setup an LSM with L2 populated. - Random rnd(301); - ASSERT_OK(Put(Key(0), rnd.RandomString(990))); - ASSERT_OK(Put(Key(1), rnd.RandomString(990))); - { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 2; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - } - ASSERT_EQ("0,0,1", FilesPerLevel(0)); - - // The background thread will refit L2->L1 while the foreground thread will - // attempt to run a compaction on new data. The following dependencies - // ensure the background manual compaction's refitting phase disables manual - // compaction immediately before the foreground manual compaction can register - // itself. Manual compaction is kept disabled until the foreground manual - // checks for the failure once. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - // Only do Put()s for foreground CompactRange() once the background - // CompactRange() has reached the refitting phase. - { - "DBImpl::CompactRange:BeforeRefit:1", - "DBCompactionTest::ChangeLevelConflictsWithManual:" - "PreForegroundCompactRange", - }, - // Right before we register the manual compaction, proceed with - // the refitting phase so manual compactions are disabled. Stay in - // the refitting phase with manual compactions disabled until it is - // noticed. - { - "DBImpl::RunManualCompaction:0", - "DBImpl::CompactRange:BeforeRefit:2", - }, - { - "DBImpl::CompactRange:PreRefitLevel", - "DBImpl::RunManualCompaction:1", - }, - { - "DBImpl::RunManualCompaction:PausedAtStart", - "DBImpl::CompactRange:PostRefitLevel", - }, - // If compaction somehow were scheduled, let's let it run after reenabling - // manual compactions. This dependency is not expected to be hit but is - // here for speculatively coercing future bugs. - { - "DBImpl::CompactRange:PostRefitLevel:ManualCompactionEnabled", - "BackgroundCallCompaction:0", - }, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread refit_level_thread([&] { - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 1; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - }); - - TEST_SYNC_POINT( - "DBCompactionTest::ChangeLevelConflictsWithManual:" - "PreForegroundCompactRange"); - ASSERT_OK(Put(Key(0), rnd.RandomString(990))); - ASSERT_OK(Put(Key(1), rnd.RandomString(990))); - ASSERT_TRUE(dbfull() - ->CompactRange(CompactRangeOptions(), nullptr, nullptr) - .IsIncomplete()); - - refit_level_thread.join(); -} - -TEST_F(DBCompactionTest, BottomPriCompactionCountsTowardConcurrencyLimit) { - // Flushes several files to trigger compaction while lock is released during - // a bottom-pri compaction. Verifies it does not get scheduled to thread pool - // because per-DB limit for compaction parallelism is one (default). - const int kNumL0Files = 4; - const int kNumLevels = 3; - - env_->SetBackgroundThreads(1, Env::Priority::BOTTOM); - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - options.num_levels = kNumLevels; - DestroyAndReopen(options); - - // Setup last level to be non-empty since it's a bit unclear whether - // compaction to an empty level would be considered "bottommost". - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - MoveFilesToLevel(kNumLevels - 1); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BGWorkBottomCompaction", - "DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:" - "PreTriggerCompaction"}, - {"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:" - "PostTriggerCompaction", - "BackgroundCallCompaction:0"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - port::Thread compact_range_thread([&] { - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - cro.exclusive_manual_compaction = false; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - }); - - // Sleep in the low-pri thread so any newly scheduled compaction will be - // queued. Otherwise it might finish before we check its existence. - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - TEST_SYNC_POINT( - "DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:" - "PreTriggerCompaction"); - for (int i = 0; i < kNumL0Files; ++i) { - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - } - ASSERT_EQ(0u, env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - TEST_SYNC_POINT( - "DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:" - "PostTriggerCompaction"); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - compact_range_thread.join(); -} - -TEST_F(DBCompactionTest, BottommostFileCompactionAllowIngestBehind) { - // allow_ingest_behind prevents seqnum zeroing, and could cause - // compaction loop with reason kBottommostFiles. - Options options = CurrentOptions(); - options.env = env_; - options.compaction_style = kCompactionStyleLevel; - options.allow_ingest_behind = true; - options.comparator = BytewiseComparator(); - DestroyAndReopen(options); - - WriteOptions write_opts; - ASSERT_OK(db_->Put(write_opts, "infinite", "compaction loop")); - ASSERT_OK(db_->Put(write_opts, "infinite", "loop")); - - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - ASSERT_OK(db_->Put(write_opts, "bumpseqnum", "")); - ASSERT_OK(Flush()); - auto snapshot = db_->GetSnapshot(); - // Bump up oldest_snapshot_seqnum_ in VersionStorageInfo. - db_->ReleaseSnapshot(snapshot); - bool compacted = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* /* arg */) { - // There should not be a compaction. - compacted = true; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - // Wait for compaction to be scheduled. - env_->SleepForMicroseconds(2000000); - ASSERT_FALSE(compacted); - // The following assert can be used to check for compaction loop: - // it used to wait forever before the fix. - // ASSERT_OK(dbfull()->TEST_WaitForCompact(true /* wait_unscheduled */)); -} - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_dynamic_level_test.cc b/db/db_dynamic_level_test.cc deleted file mode 100644 index a1c2fa943..000000000 --- a/db/db_dynamic_level_test.cc +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -// Introduction of SyncPoint effectively disabled building and running this test -// in Release build. -// which is a pity, it is a good test - -#include "db/db_test_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/env.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { -class DBTestDynamicLevel : public DBTestBase { - public: - DBTestDynamicLevel() - : DBTestBase("db_dynamic_level_test", /*env_do_fsync=*/true) {} -}; - -TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase) { - if (!Snappy_Supported() || !LZ4_Supported()) { - return; - } - // Use InMemoryEnv, or it would be too slow. - std::unique_ptr env(NewMemEnv(env_)); - - const int kNKeys = 1000; - int keys[kNKeys]; - - auto verify_func = [&]() { - for (int i = 0; i < kNKeys; i++) { - ASSERT_NE("NOT_FOUND", Get(Key(i))); - ASSERT_NE("NOT_FOUND", Get(Key(kNKeys * 2 + i))); - if (i < kNKeys / 10) { - ASSERT_EQ("NOT_FOUND", Get(Key(kNKeys + keys[i]))); - } else { - ASSERT_NE("NOT_FOUND", Get(Key(kNKeys + keys[i]))); - } - } - }; - - Random rnd(301); - for (int ordered_insert = 0; ordered_insert <= 1; ordered_insert++) { - for (int i = 0; i < kNKeys; i++) { - keys[i] = i; - } - if (ordered_insert == 0) { - RandomShuffle(std::begin(keys), std::end(keys), rnd.Next()); - } - for (int max_background_compactions = 1; max_background_compactions < 4; - max_background_compactions += 2) { - Options options; - options.env = env.get(); - options.create_if_missing = true; - options.write_buffer_size = 2048; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 2; - options.target_file_size_base = 2048; - options.level_compaction_dynamic_level_bytes = true; - options.max_bytes_for_level_base = 10240; - options.max_bytes_for_level_multiplier = 4; - options.max_background_compactions = max_background_compactions; - options.num_levels = 5; - - options.compression_per_level.resize(3); - options.compression_per_level[0] = kNoCompression; - options.compression_per_level[1] = kLZ4Compression; - options.compression_per_level[2] = kSnappyCompression; - options.env = env_; - - DestroyAndReopen(options); - - for (int i = 0; i < kNKeys; i++) { - int key = keys[i]; - ASSERT_OK(Put(Key(kNKeys + key), rnd.RandomString(102))); - ASSERT_OK(Put(Key(key), rnd.RandomString(102))); - ASSERT_OK(Put(Key(kNKeys * 2 + key), rnd.RandomString(102))); - ASSERT_OK(Delete(Key(kNKeys + keys[i / 10]))); - env_->SleepForMicroseconds(5000); - } - - uint64_t int_prop; - ASSERT_TRUE(db_->GetIntProperty("rocksdb.background-errors", &int_prop)); - ASSERT_EQ(0U, int_prop); - - // Verify DB - for (int j = 0; j < 2; j++) { - verify_func(); - if (j == 0) { - Reopen(options); - } - } - - // Test compact range works - ASSERT_OK( - dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // All data should be in the last level. - ColumnFamilyMetaData cf_meta; - db_->GetColumnFamilyMetaData(&cf_meta); - ASSERT_EQ(5U, cf_meta.levels.size()); - for (int i = 0; i < 4; i++) { - ASSERT_EQ(0U, cf_meta.levels[i].files.size()); - } - ASSERT_GT(cf_meta.levels[4U].files.size(), 0U); - verify_func(); - - Close(); - } - } - - env_->SetBackgroundThreads(1, Env::LOW); - env_->SetBackgroundThreads(1, Env::HIGH); -} - -// Test specific cases in dynamic max bytes -TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { - Random rnd(301); - int kMaxKey = 1000000; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.create_if_missing = true; - options.write_buffer_size = 20480; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 9999; - options.level0_stop_writes_trigger = 9999; - options.target_file_size_base = 9102; - options.level_compaction_dynamic_level_bytes = true; - options.max_bytes_for_level_base = 40960; - options.max_bytes_for_level_multiplier = 4; - options.max_background_compactions = 2; - options.num_levels = 5; - options.max_compaction_bytes = 0; // Force not expanding in compactions - options.db_host_id = ""; // Setting this messes up the file size calculation - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "true"}, - })); - - uint64_t int_prop; - std::string str_prop; - - // Initial base level is the last level - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(4U, int_prop); - - // Put about 28K to L0 - for (int i = 0; i < 70; i++) { - ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), - rnd.RandomString(380))); - } - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "false"}, - })); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(4U, int_prop); - - // Insert extra about 28K to L0. After they are compacted to L4, the base - // level should be changed to L3. - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "true"}, - })); - for (int i = 0; i < 70; i++) { - ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), - rnd.RandomString(380))); - } - - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "false"}, - })); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(3U, int_prop); - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop)); - ASSERT_EQ("0", str_prop); - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop)); - ASSERT_EQ("0", str_prop); - - // Write even more data while leaving the base level at L3. - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "true"}, - })); - // Write about 40K more - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), - rnd.RandomString(380))); - } - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "false"}, - })); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(3U, int_prop); - - // Fill up L0, and then run an (auto) L0->Lmax compaction to raise the base - // level to 2. - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "true"}, - })); - // Write about 650K more. - // Each file is about 11KB, with 9KB of data. - for (int i = 0; i < 1300; i++) { - ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), - rnd.RandomString(380))); - } - - // Make sure that the compaction starts before the last bit of data is - // flushed, so that the base level isn't raised to L1. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:0"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "false"}, - })); - - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:0"); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(2U, int_prop); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - // Write more data until the base level changes to L1. There will be - // a manual compaction going on at the same time. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:1"}, - {"DynamicLevelMaxBytesBase2:2", "CompactionJob::Run():End"}, - {"DynamicLevelMaxBytesBase2:compact_range_finish", - "FlushJob::WriteLevel0Table"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread thread([this] { - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_start"); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_finish"); - }); - - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:1"); - for (int i = 0; i < 2; i++) { - ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), - rnd.RandomString(380))); - } - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:2"); - - ASSERT_OK(Flush()); - - thread.join(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(1U, int_prop); -} - -// Test specific cases in dynamic max bytes -TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesCompactRange) { - Random rnd(301); - int kMaxKey = 1000000; - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.write_buffer_size = 2048; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 9999; - options.level0_stop_writes_trigger = 9999; - options.target_file_size_base = 2; - options.level_compaction_dynamic_level_bytes = true; - options.max_bytes_for_level_base = 10240; - options.max_bytes_for_level_multiplier = 4; - options.max_background_compactions = 1; - const int kNumLevels = 5; - options.num_levels = kNumLevels; - options.max_compaction_bytes = 1; // Force not expanding in compactions - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - - // Compact against empty DB - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - uint64_t int_prop; - std::string str_prop; - - // Initial base level is the last level - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(4U, int_prop); - - // Put about 7K to L0 - for (int i = 0; i < 140; i++) { - ASSERT_OK( - Put(Key(static_cast(rnd.Uniform(kMaxKey))), rnd.RandomString(80))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - if (NumTableFilesAtLevel(0) == 0) { - // Make sure level 0 is not empty - ASSERT_OK( - Put(Key(static_cast(rnd.Uniform(kMaxKey))), rnd.RandomString(80))); - ASSERT_OK(Flush()); - } - - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(3U, int_prop); - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop)); - ASSERT_EQ("0", str_prop); - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop)); - ASSERT_EQ("0", str_prop); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - std::set output_levels; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionPicker::CompactRange:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - output_levels.insert(compaction->output_level()); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(output_levels.size(), 2); - ASSERT_TRUE(output_levels.find(3) != output_levels.end()); - ASSERT_TRUE(output_levels.find(4) != output_levels.end()); - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level0", &str_prop)); - ASSERT_EQ("0", str_prop); - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level3", &str_prop)); - ASSERT_EQ("0", str_prop); - // Base level is still level 3. - ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); - ASSERT_EQ(3U, int_prop); -} - -TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBaseInc) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.write_buffer_size = 2048; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 2; - options.target_file_size_base = 2048; - options.level_compaction_dynamic_level_bytes = true; - options.max_bytes_for_level_base = 10240; - options.max_bytes_for_level_multiplier = 4; - options.max_background_compactions = 2; - options.num_levels = 5; - options.max_compaction_bytes = 100000000; - - DestroyAndReopen(options); - - int non_trivial = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* /*arg*/) { non_trivial++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - const int total_keys = 3000; - const int random_part_size = 100; - for (int i = 0; i < total_keys; i++) { - std::string value = rnd.RandomString(random_part_size); - PutFixed32(&value, static_cast(i)); - ASSERT_OK(Put(Key(i), value)); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ASSERT_EQ(non_trivial, 0); - - for (int i = 0; i < total_keys; i++) { - std::string value = Get(Key(i)); - ASSERT_EQ(DecodeFixed32(value.c_str() + random_part_size), - static_cast(i)); - } - - env_->SetBackgroundThreads(1, Env::LOW); - env_->SetBackgroundThreads(1, Env::HIGH); -} - -TEST_F(DBTestDynamicLevel, DISABLED_MigrateToDynamicLevelMaxBytesBase) { - Random rnd(301); - const int kMaxKey = 2000; - - Options options; - options.create_if_missing = true; - options.write_buffer_size = 2048; - options.max_write_buffer_number = 8; - options.level0_file_num_compaction_trigger = 4; - options.level0_slowdown_writes_trigger = 4; - options.level0_stop_writes_trigger = 8; - options.target_file_size_base = 2048; - options.level_compaction_dynamic_level_bytes = false; - options.max_bytes_for_level_base = 10240; - options.max_bytes_for_level_multiplier = 4; - options.num_levels = 8; - - DestroyAndReopen(options); - - auto verify_func = [&](int num_keys, bool if_sleep) { - for (int i = 0; i < num_keys; i++) { - ASSERT_NE("NOT_FOUND", Get(Key(kMaxKey + i))); - if (i < num_keys / 10) { - ASSERT_EQ("NOT_FOUND", Get(Key(i))); - } else { - ASSERT_NE("NOT_FOUND", Get(Key(i))); - } - if (if_sleep && i % 1000 == 0) { - // Without it, valgrind may choose not to give another - // thread a chance to run before finishing the function, - // causing the test to be extremely slow. - env_->SleepForMicroseconds(1); - } - } - }; - - int total_keys = 1000; - for (int i = 0; i < total_keys; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(102))); - ASSERT_OK(Put(Key(kMaxKey + i), rnd.RandomString(102))); - ASSERT_OK(Delete(Key(i / 10))); - } - verify_func(total_keys, false); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - options.level_compaction_dynamic_level_bytes = true; - options.disable_auto_compactions = true; - Reopen(options); - verify_func(total_keys, false); - - std::atomic_bool compaction_finished; - compaction_finished = false; - // Issue manual compaction in one thread and still verify DB state - // in main thread. - ROCKSDB_NAMESPACE::port::Thread t([&]() { - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = options.num_levels - 1; - ASSERT_OK(dbfull()->CompactRange(compact_options, nullptr, nullptr)); - compaction_finished.store(true); - }); - do { - verify_func(total_keys, true); - } while (!compaction_finished.load()); - t.join(); - - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "false"}, - })); - - int total_keys2 = 2000; - for (int i = total_keys; i < total_keys2; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(102))); - ASSERT_OK(Put(Key(kMaxKey + i), rnd.RandomString(102))); - ASSERT_OK(Delete(Key(i / 10))); - } - - verify_func(total_keys2, false); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - verify_func(total_keys2, false); - - // Base level is not level 1 - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2), 0); -} -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_encryption_test.cc b/db/db_encryption_test.cc deleted file mode 100644 index fc8be5b69..000000000 --- a/db/db_encryption_test.cc +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include "db/db_test_util.h" -#include "port/stack_trace.h" -#include "rocksdb/perf_context.h" -#include "test_util/sync_point.h" -#include -#include - -namespace ROCKSDB_NAMESPACE { - -class DBEncryptionTest : public DBTestBase { - public: - DBEncryptionTest() - : DBTestBase("db_encryption_test", /*env_do_fsync=*/true) {} - Env* GetTargetEnv() { - if (encrypted_env_ != nullptr) { - return (static_cast(encrypted_env_))->target(); - } else { - return env_; - } - } -}; - - -TEST_F(DBEncryptionTest, CheckEncrypted) { - ASSERT_OK(Put("foo567", "v1.fetdq")); - ASSERT_OK(Put("bar123", "v2.dfgkjdfghsd")); - Close(); - - // Open all files and look for the values we've put in there. - // They should not be found if encrypted, otherwise - // they should be found. - std::vector fileNames; - auto status = env_->GetChildren(dbname_, &fileNames); - ASSERT_OK(status); - - Env* target = GetTargetEnv(); - int hits = 0; - for (auto it = fileNames.begin(); it != fileNames.end(); ++it) { - if (*it == "LOCK") { - continue; - } - auto filePath = dbname_ + "/" + *it; - std::unique_ptr seqFile; - auto envOptions = EnvOptions(CurrentOptions()); - status = target->NewSequentialFile(filePath, &seqFile, envOptions); - ASSERT_OK(status); - - uint64_t fileSize; - status = target->GetFileSize(filePath, &fileSize); - ASSERT_OK(status); - - std::string scratch; - scratch.reserve(fileSize); - Slice data; - status = seqFile->Read(fileSize, &data, (char*)scratch.data()); - ASSERT_OK(status); - - if (data.ToString().find("foo567") != std::string::npos) { - hits++; - // std::cout << "Hit in " << filePath << "\n"; - } - if (data.ToString().find("v1.fetdq") != std::string::npos) { - hits++; - // std::cout << "Hit in " << filePath << "\n"; - } - if (data.ToString().find("bar123") != std::string::npos) { - hits++; - // std::cout << "Hit in " << filePath << "\n"; - } - if (data.ToString().find("v2.dfgkjdfghsd") != std::string::npos) { - hits++; - // std::cout << "Hit in " << filePath << "\n"; - } - if (data.ToString().find("dfgk") != std::string::npos) { - hits++; - // std::cout << "Hit in " << filePath << "\n"; - } - } - if (encrypted_env_) { - ASSERT_EQ(hits, 0); - } else { - ASSERT_GE(hits, 4); - } -} - -TEST_F(DBEncryptionTest, ReadEmptyFile) { - auto defaultEnv = GetTargetEnv(); - - // create empty file for reading it back in later - auto envOptions = EnvOptions(CurrentOptions()); - auto filePath = dbname_ + "/empty.empty"; - - Status status; - { - std::unique_ptr writableFile; - status = defaultEnv->NewWritableFile(filePath, &writableFile, envOptions); - ASSERT_OK(status); - } - - std::unique_ptr seqFile; - status = defaultEnv->NewSequentialFile(filePath, &seqFile, envOptions); - ASSERT_OK(status); - - std::string scratch; - Slice data; - // reading back 16 bytes from the empty file shouldn't trigger an assertion. - // it should just work and return an empty string - status = seqFile->Read(16, &data, (char*)scratch.data()); - ASSERT_OK(status); - - ASSERT_TRUE(data.empty()); -} - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_flush_test.cc b/db/db_flush_test.cc deleted file mode 100644 index 0b2e7abb1..000000000 --- a/db/db_flush_test.cc +++ /dev/null @@ -1,3202 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "env/mock_env.h" -#include "file/filename.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/utilities/transaction_db.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "util/cast_util.h" -#include "util/mutexlock.h" -#include "utilities/fault_injection_env.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -// This is a static filter used for filtering -// kvs during the compaction process. -static std::string NEW_VALUE = "NewValue"; - -class DBFlushTest : public DBTestBase { - public: - DBFlushTest() : DBTestBase("db_flush_test", /*env_do_fsync=*/true) {} -}; - -class DBFlushDirectIOTest : public DBFlushTest, - public ::testing::WithParamInterface { - public: - DBFlushDirectIOTest() : DBFlushTest() {} -}; - -class DBAtomicFlushTest : public DBFlushTest, - public ::testing::WithParamInterface { - public: - DBAtomicFlushTest() : DBFlushTest() {} -}; - -// We had issue when two background threads trying to flush at the same time, -// only one of them get committed. The test verifies the issue is fixed. -TEST_F(DBFlushTest, FlushWhileWritingManifest) { - Options options; - options.disable_auto_compactions = true; - options.max_background_flushes = 2; - options.env = env_; - Reopen(options); - FlushOptions no_wait; - no_wait.wait = false; - no_wait.allow_write_stall = true; - - SyncPoint::GetInstance()->LoadDependency( - {{"VersionSet::LogAndApply:WriteManifest", - "DBFlushTest::FlushWhileWritingManifest:1"}, - {"MemTableList::TryInstallMemtableFlushResults:InProgress", - "VersionSet::LogAndApply:WriteManifestDone"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("foo", "v")); - ASSERT_OK(dbfull()->Flush(no_wait)); - TEST_SYNC_POINT("DBFlushTest::FlushWhileWritingManifest:1"); - ASSERT_OK(Put("bar", "v")); - ASSERT_OK(dbfull()->Flush(no_wait)); - // If the issue is hit we will wait here forever. - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(2, TotalTableFiles()); -} - -// Disable this test temporarily on Travis as it fails intermittently. -// Github issue: #4151 -TEST_F(DBFlushTest, SyncFail) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options; - options.disable_auto_compactions = true; - options.env = fault_injection_env.get(); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"}, - {"DBImpl::SyncClosedLogs:Failed", "DBFlushTest::SyncFail:2"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put("key", "value")); - FlushOptions flush_options; - flush_options.wait = false; - ASSERT_OK(dbfull()->Flush(flush_options)); - // Flush installs a new super-version. Get the ref count after that. - fault_injection_env->SetFilesystemActive(false); - TEST_SYNC_POINT("DBFlushTest::SyncFail:1"); - TEST_SYNC_POINT("DBFlushTest::SyncFail:2"); - fault_injection_env->SetFilesystemActive(true); - // Now the background job will do the flush; wait for it. - // Returns the IO error happend during flush. - ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ("", FilesPerLevel()); // flush failed. - Destroy(options); -} - -TEST_F(DBFlushTest, SyncSkip) { - Options options = CurrentOptions(); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBFlushTest::SyncSkip:1", "DBImpl::SyncClosedLogs:Skip"}, - {"DBImpl::SyncClosedLogs:Skip", "DBFlushTest::SyncSkip:2"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - Reopen(options); - ASSERT_OK(Put("key", "value")); - - FlushOptions flush_options; - flush_options.wait = false; - ASSERT_OK(dbfull()->Flush(flush_options)); - - TEST_SYNC_POINT("DBFlushTest::SyncSkip:1"); - TEST_SYNC_POINT("DBFlushTest::SyncSkip:2"); - - // Now the background job will do the flush; wait for it. - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - Destroy(options); -} - -TEST_F(DBFlushTest, FlushInLowPriThreadPool) { - // Verify setting an empty high-pri (flush) thread pool causes flushes to be - // scheduled in the low-pri (compaction) thread pool. - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 4; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - Reopen(options); - env_->SetBackgroundThreads(0, Env::HIGH); - - std::thread::id tid; - int num_flushes = 0, num_compactions = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkFlush", [&](void* /*arg*/) { - if (tid == std::thread::id()) { - tid = std::this_thread::get_id(); - } else { - ASSERT_EQ(tid, std::this_thread::get_id()); - } - ++num_flushes; - }); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkCompaction", [&](void* /*arg*/) { - ASSERT_EQ(tid, std::this_thread::get_id()); - ++num_compactions; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("key", "val")); - for (int i = 0; i < 4; ++i) { - ASSERT_OK(Put("key", "val")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(4, num_flushes); - ASSERT_EQ(1, num_compactions); -} - -// Test when flush job is submitted to low priority thread pool and when DB is -// closed in the meanwhile, CloseHelper doesn't hang. -TEST_F(DBFlushTest, CloseDBWhenFlushInLowPri) { - Options options = CurrentOptions(); - options.max_background_flushes = 1; - options.max_total_wal_size = 8192; - - DestroyAndReopen(options); - CreateColumnFamilies({"cf1", "cf2"}, options); - - env_->SetBackgroundThreads(0, Env::HIGH); - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - int num_flushes = 0; - - SyncPoint::GetInstance()->SetCallBack("DBImpl::BGWorkFlush", - [&](void* /*arg*/) { ++num_flushes; }); - - int num_low_flush_unscheduled = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::UnscheduleLowFlushCallback", [&](void* /*arg*/) { - num_low_flush_unscheduled++; - // There should be one flush job in low pool that needs to be - // unscheduled - ASSERT_EQ(num_low_flush_unscheduled, 1); - }); - - int num_high_flush_unscheduled = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::UnscheduleHighFlushCallback", [&](void* /*arg*/) { - num_high_flush_unscheduled++; - // There should be no flush job in high pool - ASSERT_EQ(num_high_flush_unscheduled, 0); - }); - - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(0, "key1", DummyString(8192))); - // Block thread so that flush cannot be run and can be removed from the queue - // when called Unschedule. - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - sleeping_task_low.WaitUntilSleeping(); - - // Trigger flush and flush job will be scheduled to LOW priority thread. - ASSERT_OK(Put(0, "key2", DummyString(8192))); - - // Close DB and flush job in low priority queue will be removed without - // running. - Close(); - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - ASSERT_EQ(0, num_flushes); - - TryReopenWithColumnFamilies({"default", "cf1", "cf2"}, options); - ASSERT_OK(Put(0, "key3", DummyString(8192))); - ASSERT_OK(Flush(0)); - ASSERT_EQ(1, num_flushes); -} - -TEST_F(DBFlushTest, ManualFlushWithMinWriteBufferNumberToMerge) { - Options options = CurrentOptions(); - options.write_buffer_size = 100; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - Reopen(options); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BGWorkFlush", - "DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1"}, - {"DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2", - "FlushJob::WriteLevel0Table"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("key1", "value1")); - - port::Thread t([&]() { - // The call wait for flush to finish, i.e. with flush_options.wait = true. - ASSERT_OK(Flush()); - }); - - // Wait for flush start. - TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1"); - // Insert a second memtable before the manual flush finish. - // At the end of the manual flush job, it will check if further flush - // is needed, but it will not trigger flush of the second memtable because - // min_write_buffer_number_to_merge is not reached. - ASSERT_OK(Put("key2", "value2")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2"); - - // Manual flush should return, without waiting for flush indefinitely. - t.join(); -} - -TEST_F(DBFlushTest, ScheduleOnlyOneBgThread) { - Options options = CurrentOptions(); - Reopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - int called = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::MaybeScheduleFlushOrCompaction:AfterSchedule:0", [&](void* arg) { - ASSERT_NE(nullptr, arg); - auto unscheduled_flushes = *reinterpret_cast(arg); - ASSERT_EQ(0, unscheduled_flushes); - ++called; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("a", "foo")); - FlushOptions flush_opts; - ASSERT_OK(dbfull()->Flush(flush_opts)); - ASSERT_EQ(1, called); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -// The following 3 tests are designed for testing garbage statistics at flush -// time. -// -// ======= General Information ======= (from GitHub Wiki). -// There are three scenarios where memtable flush can be triggered: -// -// 1 - Memtable size exceeds ColumnFamilyOptions::write_buffer_size -// after a write. -// 2 - Total memtable size across all column families exceeds -// DBOptions::db_write_buffer_size, -// or DBOptions::write_buffer_manager signals a flush. In this scenario -// the largest memtable will be flushed. -// 3 - Total WAL file size exceeds DBOptions::max_total_wal_size. -// In this scenario the memtable with the oldest data will be flushed, -// in order to allow the WAL file with data from this memtable to be -// purged. -// -// As a result, a memtable can be flushed before it is full. This is one -// reason the generated SST file can be smaller than the corresponding -// memtable. Compression is another factor to make SST file smaller than -// corresponding memtable, since data in memtable is uncompressed. - -TEST_F(DBFlushTest, StatisticsGarbageBasic) { - Options options = CurrentOptions(); - - // The following options are used to enforce several values that - // may already exist as default values to make this test resilient - // to default value updates in the future. - options.statistics = CreateDBStatistics(); - - // Record all statistics. - options.statistics->set_stats_level(StatsLevel::kAll); - - // create the DB if it's not already present - options.create_if_missing = true; - - // Useful for now as we are trying to compare uncompressed data savings on - // flush(). - options.compression = kNoCompression; - - // Prevent memtable in place updates. Should already be disabled - // (from Wiki: - // In place updates can be enabled by toggling on the bool - // inplace_update_support flag. However, this flag is by default set to - // false - // because this thread-safe in-place update support is not compatible - // with concurrent memtable writes. Note that the bool - // allow_concurrent_memtable_write is set to true by default ) - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - - // Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes). - options.write_buffer_size = 64 << 20; - - ASSERT_OK(TryReopen(options)); - - // Put multiple times the same key-values. - // The encoded length of a db entry in the memtable is - // defined in db/memtable.cc (MemTable::Add) as the variable: - // encoded_len= VarintLength(internal_key_size) --> = - // log_256(internal_key). - // Min # of bytes - // necessary to - // store - // internal_key_size. - // + internal_key_size --> = actual key string, - // (size key_size: w/o term null char) - // + 8 bytes for - // fixed uint64 "seq - // number - // + - // insertion type" - // + VarintLength(val_size) --> = min # of bytes to - // store val_size - // + val_size --> = actual value - // string - // For example, in our situation, "key1" : size 4, "value1" : size 6 - // (the terminating null characters are not copied over to the memtable). - // And therefore encoded_len = 1 + (4+8) + 1 + 6 = 20 bytes per entry. - // However in terms of raw data contained in the memtable, and written - // over to the SSTable, we only count internal_key_size and val_size, - // because this is the only raw chunk of bytes that contains everything - // necessary to reconstruct a user entry: sequence number, insertion type, - // key, and value. - - // To test the relevance of our Memtable garbage statistics, - // namely MEMTABLE_PAYLOAD_BYTES_AT_FLUSH and MEMTABLE_GARBAGE_BYTES_AT_FLUSH, - // we insert K-V pairs with 3 distinct keys (of length 4), - // and random values of arbitrary length RAND_VALUES_LENGTH, - // and we repeat this step NUM_REPEAT times total. - // At the end, we insert 3 final K-V pairs with the same 3 keys - // and known values (these will be the final values, of length 6). - // I chose NUM_REPEAT=2,000 such that no automatic flush is - // triggered (the number of bytes in the memtable is therefore - // well below any meaningful heuristic for a memtable of size 64MB). - // As a result, since each K-V pair is inserted as a payload - // of N meaningful bytes (sequence number, insertion type, - // key, and value = 8 + 4 + RAND_VALUE_LENGTH), - // MEMTABLE_GARBAGE_BYTES_AT_FLUSH should be equal to 2,000 * N bytes - // and MEMTABLE_PAYLAOD_BYTES_AT_FLUSH = MEMTABLE_GARBAGE_BYTES_AT_FLUSH + - // (3*(8 + 4 + 6)) bytes. For RAND_VALUE_LENGTH = 172 (arbitrary value), we - // expect: - // N = 8 + 4 + 172 = 184 bytes - // MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 2,000 * 184 = 368,000 bytes. - // MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 368,000 + 3*18 = 368,054 bytes. - - const size_t NUM_REPEAT = 2000; - const size_t RAND_VALUES_LENGTH = 172; - const std::string KEY1 = "key1"; - const std::string KEY2 = "key2"; - const std::string KEY3 = "key3"; - const std::string VALUE1 = "value1"; - const std::string VALUE2 = "value2"; - const std::string VALUE3 = "value3"; - uint64_t EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 0; - uint64_t EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 0; - - Random rnd(301); - // Insertion of of K-V pairs, multiple times. - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - std::string p_v1 = rnd.RandomString(RAND_VALUES_LENGTH); - std::string p_v2 = rnd.RandomString(RAND_VALUES_LENGTH); - std::string p_v3 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY1, p_v1)); - ASSERT_OK(Put(KEY2, p_v2)); - ASSERT_OK(Put(KEY3, p_v3)); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY1.size() + p_v1.size() + sizeof(uint64_t); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY2.size() + p_v2.size() + sizeof(uint64_t); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY3.size() + p_v3.size() + sizeof(uint64_t); - } - - // The memtable data bytes includes the "garbage" - // bytes along with the useful payload. - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH; - - ASSERT_OK(Put(KEY1, VALUE1)); - ASSERT_OK(Put(KEY2, VALUE2)); - ASSERT_OK(Put(KEY3, VALUE3)); - - // Add useful payload to the memtable data bytes: - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH += - KEY1.size() + VALUE1.size() + KEY2.size() + VALUE2.size() + KEY3.size() + - VALUE3.size() + 3 * sizeof(uint64_t); - - // We assert that the last K-V pairs have been successfully inserted, - // and that the valid values are VALUE1, VALUE2, VALUE3. - PinnableSlice value; - ASSERT_OK(Get(KEY1, &value)); - ASSERT_EQ(value.ToString(), VALUE1); - ASSERT_OK(Get(KEY2, &value)); - ASSERT_EQ(value.ToString(), VALUE2); - ASSERT_OK(Get(KEY3, &value)); - ASSERT_EQ(value.ToString(), VALUE3); - - // Force flush to SST. Increments the statistics counter. - ASSERT_OK(Flush()); - - // Collect statistics. - uint64_t mem_data_bytes = - TestGetTickerCount(options, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH); - uint64_t mem_garbage_bytes = - TestGetTickerCount(options, MEMTABLE_GARBAGE_BYTES_AT_FLUSH); - - EXPECT_EQ(mem_data_bytes, EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH); - EXPECT_EQ(mem_garbage_bytes, EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH); - - Close(); -} - -TEST_F(DBFlushTest, StatisticsGarbageInsertAndDeletes) { - Options options = CurrentOptions(); - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - options.create_if_missing = true; - options.compression = kNoCompression; - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - options.write_buffer_size = 67108864; - - ASSERT_OK(TryReopen(options)); - - const size_t NUM_REPEAT = 2000; - const size_t RAND_VALUES_LENGTH = 37; - const std::string KEY1 = "key1"; - const std::string KEY2 = "key2"; - const std::string KEY3 = "key3"; - const std::string KEY4 = "key4"; - const std::string KEY5 = "key5"; - const std::string KEY6 = "key6"; - - uint64_t EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 0; - uint64_t EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 0; - - WriteBatch batch; - - Random rnd(301); - // Insertion of of K-V pairs, multiple times. - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - std::string p_v1 = rnd.RandomString(RAND_VALUES_LENGTH); - std::string p_v2 = rnd.RandomString(RAND_VALUES_LENGTH); - std::string p_v3 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY1, p_v1)); - ASSERT_OK(Put(KEY2, p_v2)); - ASSERT_OK(Put(KEY3, p_v3)); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY1.size() + p_v1.size() + sizeof(uint64_t); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY2.size() + p_v2.size() + sizeof(uint64_t); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY3.size() + p_v3.size() + sizeof(uint64_t); - ASSERT_OK(Delete(KEY1)); - ASSERT_OK(Delete(KEY2)); - ASSERT_OK(Delete(KEY3)); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY1.size() + KEY2.size() + KEY3.size() + 3 * sizeof(uint64_t); - } - - // The memtable data bytes includes the "garbage" - // bytes along with the useful payload. - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH; - - // Note : one set of delete for KEY1, KEY2, KEY3 is written to - // SSTable to propagate the delete operations to K-V pairs - // that could have been inserted into the database during past Flush - // opeartions. - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -= - KEY1.size() + KEY2.size() + KEY3.size() + 3 * sizeof(uint64_t); - - // Additional useful paylaod. - ASSERT_OK(Delete(KEY4)); - ASSERT_OK(Delete(KEY5)); - ASSERT_OK(Delete(KEY6)); - - // // Add useful payload to the memtable data bytes: - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH += - KEY4.size() + KEY5.size() + KEY6.size() + 3 * sizeof(uint64_t); - - // We assert that the K-V pairs have been successfully deleted. - PinnableSlice value; - ASSERT_NOK(Get(KEY1, &value)); - ASSERT_NOK(Get(KEY2, &value)); - ASSERT_NOK(Get(KEY3, &value)); - - // Force flush to SST. Increments the statistics counter. - ASSERT_OK(Flush()); - - // Collect statistics. - uint64_t mem_data_bytes = - TestGetTickerCount(options, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH); - uint64_t mem_garbage_bytes = - TestGetTickerCount(options, MEMTABLE_GARBAGE_BYTES_AT_FLUSH); - - EXPECT_EQ(mem_data_bytes, EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH); - EXPECT_EQ(mem_garbage_bytes, EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH); - - Close(); -} - -TEST_F(DBFlushTest, StatisticsGarbageRangeDeletes) { - Options options = CurrentOptions(); - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - options.create_if_missing = true; - options.compression = kNoCompression; - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - options.write_buffer_size = 67108864; - - ASSERT_OK(TryReopen(options)); - - const size_t NUM_REPEAT = 1000; - const size_t RAND_VALUES_LENGTH = 42; - const std::string KEY1 = "key1"; - const std::string KEY2 = "key2"; - const std::string KEY3 = "key3"; - const std::string KEY4 = "key4"; - const std::string KEY5 = "key5"; - const std::string KEY6 = "key6"; - const std::string VALUE3 = "value3"; - - uint64_t EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = 0; - uint64_t EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH = 0; - - Random rnd(301); - // Insertion of of K-V pairs, multiple times. - // Also insert DeleteRange - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - std::string p_v1 = rnd.RandomString(RAND_VALUES_LENGTH); - std::string p_v2 = rnd.RandomString(RAND_VALUES_LENGTH); - std::string p_v3 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY1, p_v1)); - ASSERT_OK(Put(KEY2, p_v2)); - ASSERT_OK(Put(KEY3, p_v3)); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY1.size() + p_v1.size() + sizeof(uint64_t); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY2.size() + p_v2.size() + sizeof(uint64_t); - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - KEY3.size() + p_v3.size() + sizeof(uint64_t); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY1, - KEY2)); - // Note: DeleteRange have an exclusive upper bound, e.g. here: [KEY2,KEY3) - // is deleted. - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY2, - KEY3)); - // Delete ranges are stored as a regular K-V pair, with key=STARTKEY, - // value=ENDKEY. - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH += - (KEY1.size() + KEY2.size() + sizeof(uint64_t)) + - (KEY2.size() + KEY3.size() + sizeof(uint64_t)); - } - - // The memtable data bytes includes the "garbage" - // bytes along with the useful payload. - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH = - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH; - - // Note : one set of deleteRange for (KEY1, KEY2) and (KEY2, KEY3) is written - // to SSTable to propagate the deleteRange operations to K-V pairs that could - // have been inserted into the database during past Flush opeartions. - EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -= - (KEY1.size() + KEY2.size() + sizeof(uint64_t)) + - (KEY2.size() + KEY3.size() + sizeof(uint64_t)); - - // Overwrite KEY3 with known value (VALUE3) - // Note that during the whole time KEY3 has never been deleted - // by the RangeDeletes. - ASSERT_OK(Put(KEY3, VALUE3)); - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH += - KEY3.size() + VALUE3.size() + sizeof(uint64_t); - - // Additional useful paylaod. - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY4, KEY5)); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY5, KEY6)); - - // Add useful payload to the memtable data bytes: - EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH += - (KEY4.size() + KEY5.size() + sizeof(uint64_t)) + - (KEY5.size() + KEY6.size() + sizeof(uint64_t)); - - // We assert that the K-V pairs have been successfully deleted. - PinnableSlice value; - ASSERT_NOK(Get(KEY1, &value)); - ASSERT_NOK(Get(KEY2, &value)); - // And that KEY3's value is correct. - ASSERT_OK(Get(KEY3, &value)); - ASSERT_EQ(value, VALUE3); - - // Force flush to SST. Increments the statistics counter. - ASSERT_OK(Flush()); - - // Collect statistics. - uint64_t mem_data_bytes = - TestGetTickerCount(options, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH); - uint64_t mem_garbage_bytes = - TestGetTickerCount(options, MEMTABLE_GARBAGE_BYTES_AT_FLUSH); - - EXPECT_EQ(mem_data_bytes, EXPECTED_MEMTABLE_PAYLOAD_BYTES_AT_FLUSH); - EXPECT_EQ(mem_garbage_bytes, EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH); - - Close(); -} - -// This simple Listener can only handle one flush at a time. -class TestFlushListener : public EventListener { - public: - TestFlushListener(Env* env, DBFlushTest* test) - : slowdown_count(0), stop_count(0), db_closed(), env_(env), test_(test) { - db_closed = false; - } - - ~TestFlushListener() override { - prev_fc_info_.status.PermitUncheckedError(); // Ignore the status - } - - void OnTableFileCreated(const TableFileCreationInfo& info) override { - // remember the info for later checking the FlushJobInfo. - prev_fc_info_ = info; - ASSERT_GT(info.db_name.size(), 0U); - ASSERT_GT(info.cf_name.size(), 0U); - ASSERT_GT(info.file_path.size(), 0U); - ASSERT_GT(info.job_id, 0); - ASSERT_GT(info.table_properties.data_size, 0U); - ASSERT_GT(info.table_properties.raw_key_size, 0U); - ASSERT_GT(info.table_properties.raw_value_size, 0U); - ASSERT_GT(info.table_properties.num_data_blocks, 0U); - ASSERT_GT(info.table_properties.num_entries, 0U); - ASSERT_EQ(info.file_checksum, kUnknownFileChecksum); - ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName); - } - - void OnFlushCompleted(DB* db, const FlushJobInfo& info) override { - flushed_dbs_.push_back(db); - flushed_column_family_names_.push_back(info.cf_name); - if (info.triggered_writes_slowdown) { - slowdown_count++; - } - if (info.triggered_writes_stop) { - stop_count++; - } - // verify whether the previously created file matches the flushed file. - ASSERT_EQ(prev_fc_info_.db_name, db->GetName()); - ASSERT_EQ(prev_fc_info_.cf_name, info.cf_name); - ASSERT_EQ(prev_fc_info_.job_id, info.job_id); - ASSERT_EQ(prev_fc_info_.file_path, info.file_path); - ASSERT_EQ(TableFileNameToNumber(info.file_path), info.file_number); - - // Note: the following chunk relies on the notification pertaining to the - // database pointed to by DBTestBase::db_, and is thus bypassed when - // that assumption does not hold (see the test case MultiDBMultiListeners - // below). - ASSERT_TRUE(test_); - if (db == test_->db_) { - std::vector> files_by_level; - test_->dbfull()->TEST_GetFilesMetaData(db->DefaultColumnFamily(), - &files_by_level); - - ASSERT_FALSE(files_by_level.empty()); - auto it = std::find_if(files_by_level[0].begin(), files_by_level[0].end(), - [&](const FileMetaData& meta) { - return meta.fd.GetNumber() == info.file_number; - }); - ASSERT_NE(it, files_by_level[0].end()); - ASSERT_EQ(info.oldest_blob_file_number, it->oldest_blob_file_number); - } - - ASSERT_EQ(db->GetEnv()->GetThreadID(), info.thread_id); - ASSERT_GT(info.thread_id, 0U); - } - - std::vector flushed_column_family_names_; - std::vector flushed_dbs_; - int slowdown_count; - int stop_count; - bool db_closing; - std::atomic_bool db_closed; - TableFileCreationInfo prev_fc_info_; - - protected: - Env* env_; - DBFlushTest* test_; -}; - -TEST_F( - DBFlushTest, - FixUnrecoverableWriteDuringAtomicFlushWaitUntilFlushWouldNotStallWrites) { - Options options = CurrentOptions(); - options.atomic_flush = true; - - // To simulate a real-life crash where we can't flush during db's shutdown - options.avoid_flush_during_shutdown = true; - - // Set 3 low thresholds (while `disable_auto_compactions=false`) here so flush - // adding one more L0 file during `GetLiveFiles()` will have to wait till such - // flush will not stall writes - options.level0_stop_writes_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - // Disable level-0 compaction triggered by number of files to avoid - // stalling check being skipped (resulting in the flush mentioned above didn't - // wait) - options.level0_file_num_compaction_trigger = -1; - - CreateAndReopenWithCF({"cf1"}, options); - - // Manually pause compaction thread to ensure enough L0 files as - // `disable_auto_compactions=false`is needed, in order to meet the 3 low - // thresholds above - std::unique_ptr sleeping_task_; - sleeping_task_.reset(new test::SleepingBackgroundTask()); - env_->SetBackgroundThreads(1, Env::LOW); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - sleeping_task_.get(), Env::Priority::LOW); - sleeping_task_->WaitUntilSleeping(); - - // Create some initial file to help meet the 3 low thresholds above - ASSERT_OK(Put(1, "dontcare", "dontcare")); - ASSERT_OK(Flush(1)); - - // Insert some initial data so we have something to atomic-flush later - // triggered by `GetLiveFiles()` - WriteOptions write_opts; - write_opts.disableWAL = true; - ASSERT_OK(Put(1, "k1", "v1", write_opts)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({{ - "DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", - "DBFlushTest::" - "UnrecoverableWriteInAtomicFlushWaitUntilFlushWouldNotStallWrites::Write", - }}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Write to db when atomic flush releases the lock to wait on write stall - // condition to be gone in `WaitUntilFlushWouldNotStallWrites()` - port::Thread write_thread([&] { - TEST_SYNC_POINT( - "DBFlushTest::" - "UnrecoverableWriteInAtomicFlushWaitUntilFlushWouldNotStallWrites::" - "Write"); - // Before the fix, the empty default CF would've been prematurely excluded - // from this atomic flush. The following two writes together make default CF - // later contain data that should've been included in the atomic flush. - ASSERT_OK(Put(0, "k2", "v2", write_opts)); - // The following write increases the max seqno of this atomic flush to be 3, - // which is greater than the seqno of default CF's data. This then violates - // the invariant that all entries of seqno less than the max seqno - // of this atomic flush should've been flushed by the time of this atomic - // flush finishes. - ASSERT_OK(Put(1, "k3", "v3", write_opts)); - - // Resume compaction threads and reduce L0 files so `GetLiveFiles()` can - // resume from the wait - sleeping_task_->WakeUp(); - sleeping_task_->WaitUntilDone(); - MoveFilesToLevel(1, 1); - }); - - // Trigger an atomic flush by `GetLiveFiles()` - std::vector files; - uint64_t manifest_file_size; - ASSERT_OK(db_->GetLiveFiles(files, &manifest_file_size, /*flush*/ true)); - - write_thread.join(); - - ReopenWithColumnFamilies({"default", "cf1"}, options); - - ASSERT_EQ(Get(1, "k3"), "v3"); - // Prior to the fix, `Get()` will return `NotFound as "k2" entry in default CF - // can't be recovered from a crash right after the atomic flush finishes, - // resulting in a "recovery hole" as "k3" can be recovered. It's due to the - // invariant violation described above. - ASSERT_EQ(Get(0, "k2"), "v2"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBFlushTest, FixFlushReasonRaceFromConcurrentFlushes) { - Options options = CurrentOptions(); - options.atomic_flush = true; - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"cf1"}, options); - - for (int idx = 0; idx < 1; ++idx) { - ASSERT_OK(Put(0, Key(idx), std::string(1, 'v'))); - ASSERT_OK(Put(1, Key(idx), std::string(1, 'v'))); - } - - // To coerce a manual flush happenning in the middle of GetLiveFiles's flush, - // we need to pause background flush thread and enable it later. - std::shared_ptr sleeping_task = - std::make_shared(); - env_->SetBackgroundThreads(1, Env::HIGH); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - sleeping_task.get(), Env::Priority::HIGH); - sleeping_task->WaitUntilSleeping(); - - // Coerce a manual flush happenning in the middle of GetLiveFiles's flush - bool get_live_files_paused_at_sync_point = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::AtomicFlushMemTables:AfterScheduleFlush", [&](void* /* arg */) { - if (get_live_files_paused_at_sync_point) { - // To prevent non-GetLiveFiles() flush from pausing at this sync point - return; - } - get_live_files_paused_at_sync_point = true; - - FlushOptions fo; - fo.wait = false; - fo.allow_write_stall = true; - ASSERT_OK(dbfull()->Flush(fo)); - - // Resume background flush thread so GetLiveFiles() can finish - sleeping_task->WakeUp(); - sleeping_task->WaitUntilDone(); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::vector files; - uint64_t manifest_file_size; - // Before the fix, a race condition on default cf's flush reason due to - // concurrent GetLiveFiles's flush and manual flush will fail - // an internal assertion. - // After the fix, such race condition is fixed and there is no assertion - // failure. - ASSERT_OK(db_->GetLiveFiles(files, &manifest_file_size, /*flush*/ true)); - ASSERT_TRUE(get_live_files_paused_at_sync_point); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBFlushTest, MemPurgeBasic) { - Options options = CurrentOptions(); - - // The following options are used to enforce several values that - // may already exist as default values to make this test resilient - // to default value updates in the future. - options.statistics = CreateDBStatistics(); - - // Record all statistics. - options.statistics->set_stats_level(StatsLevel::kAll); - - // create the DB if it's not already present - options.create_if_missing = true; - - // Useful for now as we are trying to compare uncompressed data savings on - // flush(). - options.compression = kNoCompression; - - // Prevent memtable in place updates. Should already be disabled - // (from Wiki: - // In place updates can be enabled by toggling on the bool - // inplace_update_support flag. However, this flag is by default set to - // false - // because this thread-safe in-place update support is not compatible - // with concurrent memtable writes. Note that the bool - // allow_concurrent_memtable_write is set to true by default ) - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - - // Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes). - options.write_buffer_size = 1 << 20; - // Initially deactivate the MemPurge prototype. - options.experimental_mempurge_threshold = 0.0; - TestFlushListener* listener = new TestFlushListener(options.env, this); - options.listeners.emplace_back(listener); - ASSERT_OK(TryReopen(options)); - - // RocksDB lite does not support dynamic options - // Dynamically activate the MemPurge prototype without restarting the DB. - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - ASSERT_OK(db_->SetOptions(cfh, {{"experimental_mempurge_threshold", "1.0"}})); - - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::string KEY1 = "IamKey1"; - std::string KEY2 = "IamKey2"; - std::string KEY3 = "IamKey3"; - std::string KEY4 = "IamKey4"; - std::string KEY5 = "IamKey5"; - std::string KEY6 = "IamKey6"; - std::string KEY7 = "IamKey7"; - std::string KEY8 = "IamKey8"; - std::string KEY9 = "IamKey9"; - std::string RNDKEY1, RNDKEY2, RNDKEY3; - const std::string NOT_FOUND = "NOT_FOUND"; - - // Heavy overwrite workload, - // more than would fit in maximum allowed memtables. - Random rnd(719); - const size_t NUM_REPEAT = 100; - const size_t RAND_KEYS_LENGTH = 57; - const size_t RAND_VALUES_LENGTH = 10240; - std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9, p_rv1, - p_rv2, p_rv3; - - // Insert a very first set of keys that will be - // mempurged at least once. - p_v1 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v2 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v3 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v4 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY1, p_v1)); - ASSERT_OK(Put(KEY2, p_v2)); - ASSERT_OK(Put(KEY3, p_v3)); - ASSERT_OK(Put(KEY4, p_v4)); - ASSERT_EQ(Get(KEY1), p_v1); - ASSERT_EQ(Get(KEY2), p_v2); - ASSERT_EQ(Get(KEY3), p_v3); - ASSERT_EQ(Get(KEY4), p_v4); - - // Insertion of of K-V pairs, multiple times (overwrites). - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - p_v5 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v6 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v7 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v8 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v9 = rnd.RandomString(RAND_VALUES_LENGTH); - - ASSERT_OK(Put(KEY5, p_v5)); - ASSERT_OK(Put(KEY6, p_v6)); - ASSERT_OK(Put(KEY7, p_v7)); - ASSERT_OK(Put(KEY8, p_v8)); - ASSERT_OK(Put(KEY9, p_v9)); - - ASSERT_EQ(Get(KEY1), p_v1); - ASSERT_EQ(Get(KEY2), p_v2); - ASSERT_EQ(Get(KEY3), p_v3); - ASSERT_EQ(Get(KEY4), p_v4); - ASSERT_EQ(Get(KEY5), p_v5); - ASSERT_EQ(Get(KEY6), p_v6); - ASSERT_EQ(Get(KEY7), p_v7); - ASSERT_EQ(Get(KEY8), p_v8); - ASSERT_EQ(Get(KEY9), p_v9); - } - - // Check that there was at least one mempurge - const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1; - // Check that there was no SST files created during flush. - const uint32_t EXPECTED_SST_COUNT = 0; - - EXPECT_GE(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - EXPECT_EQ(sst_count.exchange(0), EXPECTED_SST_COUNT); - - // Insertion of of K-V pairs, no overwrites. - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - RNDKEY1 = rnd.RandomString(RAND_KEYS_LENGTH); - RNDKEY2 = rnd.RandomString(RAND_KEYS_LENGTH); - RNDKEY3 = rnd.RandomString(RAND_KEYS_LENGTH); - p_rv1 = rnd.RandomString(RAND_VALUES_LENGTH); - p_rv2 = rnd.RandomString(RAND_VALUES_LENGTH); - p_rv3 = rnd.RandomString(RAND_VALUES_LENGTH); - - ASSERT_OK(Put(RNDKEY1, p_rv1)); - ASSERT_OK(Put(RNDKEY2, p_rv2)); - ASSERT_OK(Put(RNDKEY3, p_rv3)); - - ASSERT_EQ(Get(KEY1), p_v1); - ASSERT_EQ(Get(KEY2), p_v2); - ASSERT_EQ(Get(KEY3), p_v3); - ASSERT_EQ(Get(KEY4), p_v4); - ASSERT_EQ(Get(KEY5), p_v5); - ASSERT_EQ(Get(KEY6), p_v6); - ASSERT_EQ(Get(KEY7), p_v7); - ASSERT_EQ(Get(KEY8), p_v8); - ASSERT_EQ(Get(KEY9), p_v9); - ASSERT_EQ(Get(RNDKEY1), p_rv1); - ASSERT_EQ(Get(RNDKEY2), p_rv2); - ASSERT_EQ(Get(RNDKEY3), p_rv3); - } - - // Assert that at least one flush to storage has been performed - EXPECT_GT(sst_count.exchange(0), EXPECTED_SST_COUNT); - // (which will consequently increase the number of mempurges recorded too). - EXPECT_GE(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - - // Assert that there is no data corruption, even with - // a flush to storage. - ASSERT_EQ(Get(KEY1), p_v1); - ASSERT_EQ(Get(KEY2), p_v2); - ASSERT_EQ(Get(KEY3), p_v3); - ASSERT_EQ(Get(KEY4), p_v4); - ASSERT_EQ(Get(KEY5), p_v5); - ASSERT_EQ(Get(KEY6), p_v6); - ASSERT_EQ(Get(KEY7), p_v7); - ASSERT_EQ(Get(KEY8), p_v8); - ASSERT_EQ(Get(KEY9), p_v9); - ASSERT_EQ(Get(RNDKEY1), p_rv1); - ASSERT_EQ(Get(RNDKEY2), p_rv2); - ASSERT_EQ(Get(RNDKEY3), p_rv3); - - Close(); -} - -// RocksDB lite does not support dynamic options -TEST_F(DBFlushTest, MemPurgeBasicToggle) { - Options options = CurrentOptions(); - - // The following options are used to enforce several values that - // may already exist as default values to make this test resilient - // to default value updates in the future. - options.statistics = CreateDBStatistics(); - - // Record all statistics. - options.statistics->set_stats_level(StatsLevel::kAll); - - // create the DB if it's not already present - options.create_if_missing = true; - - // Useful for now as we are trying to compare uncompressed data savings on - // flush(). - options.compression = kNoCompression; - - // Prevent memtable in place updates. Should already be disabled - // (from Wiki: - // In place updates can be enabled by toggling on the bool - // inplace_update_support flag. However, this flag is by default set to - // false - // because this thread-safe in-place update support is not compatible - // with concurrent memtable writes. Note that the bool - // allow_concurrent_memtable_write is set to true by default ) - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - - // Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes). - options.write_buffer_size = 1 << 20; - // Initially deactivate the MemPurge prototype. - // (negative values are equivalent to 0.0). - options.experimental_mempurge_threshold = -25.3; - TestFlushListener* listener = new TestFlushListener(options.env, this); - options.listeners.emplace_back(listener); - - ASSERT_OK(TryReopen(options)); - // Dynamically activate the MemPurge prototype without restarting the DB. - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - // Values greater than 1.0 are equivalent to 1.0 - ASSERT_OK( - db_->SetOptions(cfh, {{"experimental_mempurge_threshold", "3.7898"}})); - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - const size_t KVSIZE = 3; - std::vector KEYS(KVSIZE); - for (size_t k = 0; k < KVSIZE; k++) { - KEYS[k] = "IamKey" + std::to_string(k); - } - - std::vector RNDVALS(KVSIZE); - const std::string NOT_FOUND = "NOT_FOUND"; - - // Heavy overwrite workload, - // more than would fit in maximum allowed memtables. - Random rnd(719); - const size_t NUM_REPEAT = 100; - const size_t RAND_VALUES_LENGTH = 10240; - - // Insertion of of K-V pairs, multiple times (overwrites). - for (size_t i = 0; i < NUM_REPEAT; i++) { - for (size_t j = 0; j < KEYS.size(); j++) { - RNDVALS[j] = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEYS[j], RNDVALS[j])); - ASSERT_EQ(Get(KEYS[j]), RNDVALS[j]); - } - for (size_t j = 0; j < KEYS.size(); j++) { - ASSERT_EQ(Get(KEYS[j]), RNDVALS[j]); - } - } - - // Check that there was at least one mempurge - const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1; - // Check that there was no SST files created during flush. - const uint32_t EXPECTED_SST_COUNT = 0; - - EXPECT_GE(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - EXPECT_EQ(sst_count.exchange(0), EXPECTED_SST_COUNT); - - // Dynamically deactivate MemPurge. - ASSERT_OK( - db_->SetOptions(cfh, {{"experimental_mempurge_threshold", "-1023.0"}})); - - // Insertion of of K-V pairs, multiple times (overwrites). - for (size_t i = 0; i < NUM_REPEAT; i++) { - for (size_t j = 0; j < KEYS.size(); j++) { - RNDVALS[j] = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEYS[j], RNDVALS[j])); - ASSERT_EQ(Get(KEYS[j]), RNDVALS[j]); - } - for (size_t j = 0; j < KEYS.size(); j++) { - ASSERT_EQ(Get(KEYS[j]), RNDVALS[j]); - } - } - - // Check that there was at least one mempurge - const uint32_t ZERO = 0; - // Assert that at least one flush to storage has been performed - EXPECT_GT(sst_count.exchange(0), EXPECTED_SST_COUNT); - // The mempurge count is expected to be set to 0 when the options are updated. - // We expect no mempurge at all. - EXPECT_EQ(mempurge_count.exchange(0), ZERO); - - Close(); -} -// End of MemPurgeBasicToggle, which is not -// supported with RocksDB LITE because it -// relies on dynamically changing the option -// flag experimental_mempurge_threshold. - -// At the moment, MemPurge feature is deactivated -// when atomic_flush is enabled. This is because the level -// of garbage between Column Families is not guaranteed to -// be consistent, therefore a CF could hypothetically -// trigger a MemPurge while another CF would trigger -// a regular Flush. -TEST_F(DBFlushTest, MemPurgeWithAtomicFlush) { - Options options = CurrentOptions(); - - // The following options are used to enforce several values that - // may already exist as default values to make this test resilient - // to default value updates in the future. - options.statistics = CreateDBStatistics(); - - // Record all statistics. - options.statistics->set_stats_level(StatsLevel::kAll); - - // create the DB if it's not already present - options.create_if_missing = true; - - // Useful for now as we are trying to compare uncompressed data savings on - // flush(). - options.compression = kNoCompression; - - // Prevent memtable in place updates. Should already be disabled - // (from Wiki: - // In place updates can be enabled by toggling on the bool - // inplace_update_support flag. However, this flag is by default set to - // false - // because this thread-safe in-place update support is not compatible - // with concurrent memtable writes. Note that the bool - // allow_concurrent_memtable_write is set to true by default ) - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - - // Enforce size of a single MemTable to 64KB (64KB = 65,536 bytes). - options.write_buffer_size = 1 << 20; - // Activate the MemPurge prototype. - options.experimental_mempurge_threshold = 153.245; - // Activate atomic_flush. - options.atomic_flush = true; - - const std::vector new_cf_names = {"pikachu", "eevie"}; - CreateColumnFamilies(new_cf_names, options); - - Close(); - - // 3 CFs: default will be filled with overwrites (would normally trigger - // mempurge) - // new_cf_names[1] will be filled with random values (would trigger - // flush) new_cf_names[2] not filled with anything. - ReopenWithColumnFamilies( - {kDefaultColumnFamilyName, new_cf_names[0], new_cf_names[1]}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(2, "bar", "baz")); - - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - const size_t KVSIZE = 3; - std::vector KEYS(KVSIZE); - for (size_t k = 0; k < KVSIZE; k++) { - KEYS[k] = "IamKey" + std::to_string(k); - } - - std::string RNDKEY; - std::vector RNDVALS(KVSIZE); - const std::string NOT_FOUND = "NOT_FOUND"; - - // Heavy overwrite workload, - // more than would fit in maximum allowed memtables. - Random rnd(106); - const size_t NUM_REPEAT = 100; - const size_t RAND_KEY_LENGTH = 128; - const size_t RAND_VALUES_LENGTH = 10240; - - // Insertion of of K-V pairs, multiple times (overwrites). - for (size_t i = 0; i < NUM_REPEAT; i++) { - for (size_t j = 0; j < KEYS.size(); j++) { - RNDKEY = rnd.RandomString(RAND_KEY_LENGTH); - RNDVALS[j] = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEYS[j], RNDVALS[j])); - ASSERT_OK(Put(1, RNDKEY, RNDVALS[j])); - ASSERT_EQ(Get(KEYS[j]), RNDVALS[j]); - ASSERT_EQ(Get(1, RNDKEY), RNDVALS[j]); - } - } - - // Check that there was no mempurge because atomic_flush option is true. - const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 0; - // Check that there was at least one SST files created during flush. - const uint32_t EXPECTED_SST_COUNT = 1; - - EXPECT_EQ(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - EXPECT_GE(sst_count.exchange(0), EXPECTED_SST_COUNT); - - Close(); -} - -TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) { - Options options = CurrentOptions(); - - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - options.create_if_missing = true; - options.compression = kNoCompression; - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - TestFlushListener* listener = new TestFlushListener(options.env, this); - options.listeners.emplace_back(listener); - // Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes). - options.write_buffer_size = 1 << 20; - // Activate the MemPurge prototype. - options.experimental_mempurge_threshold = 15.0; - - ASSERT_OK(TryReopen(options)); - - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::string KEY1 = "ThisIsKey1"; - std::string KEY2 = "ThisIsKey2"; - std::string KEY3 = "ThisIsKey3"; - std::string KEY4 = "ThisIsKey4"; - std::string KEY5 = "ThisIsKey5"; - const std::string NOT_FOUND = "NOT_FOUND"; - - Random rnd(117); - const size_t NUM_REPEAT = 100; - const size_t RAND_VALUES_LENGTH = 10240; - - std::string key, value, p_v1, p_v2, p_v3, p_v3b, p_v4, p_v5; - int count = 0; - const int EXPECTED_COUNT_FORLOOP = 3; - const int EXPECTED_COUNT_END = 4; - - ReadOptions ropt; - ropt.pin_data = true; - ropt.total_order_seek = true; - Iterator* iter = nullptr; - - // Insertion of of K-V pairs, multiple times. - // Also insert DeleteRange - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - p_v1 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v2 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v3 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v3b = rnd.RandomString(RAND_VALUES_LENGTH); - p_v4 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v5 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY1, p_v1)); - ASSERT_OK(Put(KEY2, p_v2)); - ASSERT_OK(Put(KEY3, p_v3)); - ASSERT_OK(Put(KEY4, p_v4)); - ASSERT_OK(Put(KEY5, p_v5)); - ASSERT_OK(Delete(KEY2)); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY2, - KEY4)); - ASSERT_OK(Put(KEY3, p_v3b)); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), KEY1, - KEY3)); - ASSERT_OK(Delete(KEY1)); - - ASSERT_EQ(Get(KEY1), NOT_FOUND); - ASSERT_EQ(Get(KEY2), NOT_FOUND); - ASSERT_EQ(Get(KEY3), p_v3b); - ASSERT_EQ(Get(KEY4), p_v4); - ASSERT_EQ(Get(KEY5), p_v5); - - iter = db_->NewIterator(ropt); - iter->SeekToFirst(); - count = 0; - for (; iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - key = (iter->key()).ToString(false); - value = (iter->value()).ToString(false); - if (key.compare(KEY3) == 0) - ASSERT_EQ(value, p_v3b); - else if (key.compare(KEY4) == 0) - ASSERT_EQ(value, p_v4); - else if (key.compare(KEY5) == 0) - ASSERT_EQ(value, p_v5); - else - ASSERT_EQ(value, NOT_FOUND); - count++; - } - - // Expected count here is 3: KEY3, KEY4, KEY5. - ASSERT_EQ(count, EXPECTED_COUNT_FORLOOP); - if (iter) { - delete iter; - } - } - - // Check that there was at least one mempurge - const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1; - // Check that there was no SST files created during flush. - const uint32_t EXPECTED_SST_COUNT = 0; - - EXPECT_GE(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - EXPECT_EQ(sst_count.exchange(0), EXPECTED_SST_COUNT); - - // Additional test for the iterator+memPurge. - ASSERT_OK(Put(KEY2, p_v2)); - iter = db_->NewIterator(ropt); - iter->SeekToFirst(); - ASSERT_OK(Put(KEY4, p_v4)); - count = 0; - for (; iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - key = (iter->key()).ToString(false); - value = (iter->value()).ToString(false); - if (key.compare(KEY2) == 0) - ASSERT_EQ(value, p_v2); - else if (key.compare(KEY3) == 0) - ASSERT_EQ(value, p_v3b); - else if (key.compare(KEY4) == 0) - ASSERT_EQ(value, p_v4); - else if (key.compare(KEY5) == 0) - ASSERT_EQ(value, p_v5); - else - ASSERT_EQ(value, NOT_FOUND); - count++; - } - - // Expected count here is 4: KEY2, KEY3, KEY4, KEY5. - ASSERT_EQ(count, EXPECTED_COUNT_END); - if (iter) delete iter; - - Close(); -} - -// Create a Compaction Fitler that will be invoked -// at flush time and will update the value of a KV pair -// if the key string is "lower" than the filter_key_ string. -class ConditionalUpdateFilter : public CompactionFilter { - public: - explicit ConditionalUpdateFilter(const std::string* filtered_key) - : filtered_key_(filtered_key) {} - bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/, - std::string* new_value, bool* value_changed) const override { - // If key CreateCompactionFilter( - const CompactionFilter::Context& /*context*/) override { - return std::unique_ptr( - new ConditionalUpdateFilter(&filtered_key_)); - } - - const char* Name() const override { return "ConditionalUpdateFilterFactory"; } - - bool ShouldFilterTableFileCreation( - TableFileCreationReason reason) const override { - // This compaction filter will be invoked - // at flush time (and therefore at MemPurge time). - return (reason == TableFileCreationReason::kFlush); - } - - private: - std::string filtered_key_; -}; - -TEST_F(DBFlushTest, MemPurgeAndCompactionFilter) { - Options options = CurrentOptions(); - - std::string KEY1 = "ThisIsKey1"; - std::string KEY2 = "ThisIsKey2"; - std::string KEY3 = "ThisIsKey3"; - std::string KEY4 = "ThisIsKey4"; - std::string KEY5 = "ThisIsKey5"; - std::string KEY6 = "ThisIsKey6"; - std::string KEY7 = "ThisIsKey7"; - std::string KEY8 = "ThisIsKey8"; - std::string KEY9 = "ThisIsKey9"; - const std::string NOT_FOUND = "NOT_FOUND"; - - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - options.create_if_missing = true; - options.compression = kNoCompression; - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - TestFlushListener* listener = new TestFlushListener(options.env, this); - options.listeners.emplace_back(listener); - // Create a ConditionalUpdate compaction filter - // that will update all the values of the KV pairs - // where the keys are "lower" than KEY4. - options.compaction_filter_factory = - std::make_shared(KEY4); - - // Enforce size of a single MemTable to 64MB (64MB = 67108864 bytes). - options.write_buffer_size = 1 << 20; - // Activate the MemPurge prototype. - options.experimental_mempurge_threshold = 26.55; - - ASSERT_OK(TryReopen(options)); - - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(53); - const size_t NUM_REPEAT = 1000; - const size_t RAND_VALUES_LENGTH = 10240; - std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9; - - p_v1 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v2 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v3 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v4 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v5 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY1, p_v1)); - ASSERT_OK(Put(KEY2, p_v2)); - ASSERT_OK(Put(KEY3, p_v3)); - ASSERT_OK(Put(KEY4, p_v4)); - ASSERT_OK(Put(KEY5, p_v5)); - ASSERT_OK(Delete(KEY1)); - - // Insertion of of K-V pairs, multiple times. - for (size_t i = 0; i < NUM_REPEAT; i++) { - // Create value strings of arbitrary - // length RAND_VALUES_LENGTH bytes. - p_v6 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v7 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v8 = rnd.RandomString(RAND_VALUES_LENGTH); - p_v9 = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEY6, p_v6)); - ASSERT_OK(Put(KEY7, p_v7)); - ASSERT_OK(Put(KEY8, p_v8)); - ASSERT_OK(Put(KEY9, p_v9)); - - ASSERT_OK(Delete(KEY7)); - } - - // Check that there was at least one mempurge - const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1; - // Check that there was no SST files created during flush. - const uint32_t EXPECTED_SST_COUNT = 0; - - EXPECT_GE(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - EXPECT_EQ(sst_count.exchange(0), EXPECTED_SST_COUNT); - - // Verify that the ConditionalUpdateCompactionFilter - // updated the values of KEY2 and KEY3, and not KEY4 and KEY5. - ASSERT_EQ(Get(KEY1), NOT_FOUND); - ASSERT_EQ(Get(KEY2), NEW_VALUE); - ASSERT_EQ(Get(KEY3), NEW_VALUE); - ASSERT_EQ(Get(KEY4), p_v4); - ASSERT_EQ(Get(KEY5), p_v5); -} - -TEST_F(DBFlushTest, DISABLED_MemPurgeWALSupport) { - Options options = CurrentOptions(); - - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - options.create_if_missing = true; - options.compression = kNoCompression; - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - - // Enforce size of a single MemTable to 128KB. - options.write_buffer_size = 128 << 10; - // Activate the MemPurge prototype - // (values >1.0 are equivalent to 1.0). - options.experimental_mempurge_threshold = 2.5; - - ASSERT_OK(TryReopen(options)); - - const size_t KVSIZE = 10; - - do { - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Put(1, "baz", "v5")); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("v1", Get(1, "foo")); - - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v5", Get(1, "baz")); - ASSERT_OK(Put(0, "bar", "v2")); - ASSERT_OK(Put(1, "bar", "v2")); - ASSERT_OK(Put(1, "foo", "v3")); - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::vector keys; - for (size_t k = 0; k < KVSIZE; k++) { - keys.push_back("IamKey" + std::to_string(k)); - } - - std::string RNDKEY, RNDVALUE; - const std::string NOT_FOUND = "NOT_FOUND"; - - // Heavy overwrite workload, - // more than would fit in maximum allowed memtables. - Random rnd(719); - const size_t NUM_REPEAT = 100; - const size_t RAND_KEY_LENGTH = 4096; - const size_t RAND_VALUES_LENGTH = 1024; - std::vector values_default(KVSIZE), values_pikachu(KVSIZE); - - // Insert a very first set of keys that will be - // mempurged at least once. - for (size_t k = 0; k < KVSIZE / 2; k++) { - values_default[k] = rnd.RandomString(RAND_VALUES_LENGTH); - values_pikachu[k] = rnd.RandomString(RAND_VALUES_LENGTH); - } - - // Insert keys[0:KVSIZE/2] to - // both 'default' and 'pikachu' CFs. - for (size_t k = 0; k < KVSIZE / 2; k++) { - ASSERT_OK(Put(0, keys[k], values_default[k])); - ASSERT_OK(Put(1, keys[k], values_pikachu[k])); - } - - // Check that the insertion was seamless. - for (size_t k = 0; k < KVSIZE / 2; k++) { - ASSERT_EQ(Get(0, keys[k]), values_default[k]); - ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]); - } - - // Insertion of of K-V pairs, multiple times (overwrites) - // into 'default' CF. Will trigger mempurge. - for (size_t j = 0; j < NUM_REPEAT; j++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - for (size_t k = KVSIZE / 2; k < KVSIZE; k++) { - values_default[k] = rnd.RandomString(RAND_VALUES_LENGTH); - } - - // Insert K-V into default CF. - for (size_t k = KVSIZE / 2; k < KVSIZE; k++) { - ASSERT_OK(Put(0, keys[k], values_default[k])); - } - - // Check key validity, for all keys, both in - // default and pikachu CFs. - for (size_t k = 0; k < KVSIZE; k++) { - ASSERT_EQ(Get(0, keys[k]), values_default[k]); - } - // Note that at this point, only keys[0:KVSIZE/2] - // have been inserted into Pikachu. - for (size_t k = 0; k < KVSIZE / 2; k++) { - ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]); - } - } - - // Insertion of of K-V pairs, multiple times (overwrites) - // into 'pikachu' CF. Will trigger mempurge. - // Check that we keep the older logs for 'default' imm(). - for (size_t j = 0; j < NUM_REPEAT; j++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - for (size_t k = KVSIZE / 2; k < KVSIZE; k++) { - values_pikachu[k] = rnd.RandomString(RAND_VALUES_LENGTH); - } - - // Insert K-V into pikachu CF. - for (size_t k = KVSIZE / 2; k < KVSIZE; k++) { - ASSERT_OK(Put(1, keys[k], values_pikachu[k])); - } - - // Check key validity, for all keys, - // both in default and pikachu. - for (size_t k = 0; k < KVSIZE; k++) { - ASSERT_EQ(Get(0, keys[k]), values_default[k]); - ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]); - } - } - - // Check that there was at least one mempurge - const uint32_t EXPECTED_MIN_MEMPURGE_COUNT = 1; - // Check that there was no SST files created during flush. - const uint32_t EXPECTED_SST_COUNT = 0; - - EXPECT_GE(mempurge_count.exchange(0), EXPECTED_MIN_MEMPURGE_COUNT); - if (options.experimental_mempurge_threshold == - std::numeric_limits::max()) { - EXPECT_EQ(sst_count.exchange(0), EXPECTED_SST_COUNT); - } - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - // Check that there was no data corruption anywhere, - // not in 'default' nor in 'Pikachu' CFs. - ASSERT_EQ("v3", Get(1, "foo")); - ASSERT_OK(Put(1, "foo", "v4")); - ASSERT_EQ("v4", Get(1, "foo")); - ASSERT_EQ("v2", Get(1, "bar")); - ASSERT_EQ("v5", Get(1, "baz")); - // Check keys in 'Default' and 'Pikachu'. - // keys[0:KVSIZE/2] were for sure contained - // in the imm() at Reopen/recovery time. - for (size_t k = 0; k < KVSIZE; k++) { - ASSERT_EQ(Get(0, keys[k]), values_default[k]); - ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]); - } - // Insertion of random K-V pairs to trigger - // a flush in the Pikachu CF. - for (size_t j = 0; j < NUM_REPEAT; j++) { - RNDKEY = rnd.RandomString(RAND_KEY_LENGTH); - RNDVALUE = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(1, RNDKEY, RNDVALUE)); - } - // ASsert than there was at least one flush to storage. - EXPECT_GT(sst_count.exchange(0), EXPECTED_SST_COUNT); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("v4", Get(1, "foo")); - ASSERT_EQ("v2", Get(1, "bar")); - ASSERT_EQ("v5", Get(1, "baz")); - // Since values in default are held in mutable mem() - // and imm(), check if the flush in pikachu didn't - // affect these values. - for (size_t k = 0; k < KVSIZE; k++) { - ASSERT_EQ(Get(0, keys[k]), values_default[k]); - ASSERT_EQ(Get(1, keys[k]), values_pikachu[k]); - } - ASSERT_EQ(Get(1, RNDKEY), RNDVALUE); - } while (ChangeWalOptions()); -} - -TEST_F(DBFlushTest, MemPurgeCorrectLogNumberAndSSTFileCreation) { - // Before our bug fix, we noticed that when 2 memtables were - // being flushed (with one memtable being the output of a - // previous MemPurge and one memtable being a newly-sealed memtable), - // the SST file created was not properly added to the DB version - // (via the VersionEdit obj), leading to data loss (the SST file - // was later being purged as an obsolete file). - // Therefore, we reproduce this scenario to test our fix. - Options options = CurrentOptions(); - - options.create_if_missing = true; - options.compression = kNoCompression; - options.inplace_update_support = false; - options.allow_concurrent_memtable_write = true; - - // Enforce size of a single MemTable to 1MB (64MB = 1048576 bytes). - options.write_buffer_size = 1 << 20; - // Activate the MemPurge prototype. - options.experimental_mempurge_threshold = 1.0; - - // Force to have more than one memtable to trigger a flush. - // For some reason this option does not seem to be enforced, - // so the following test is designed to make sure that we - // are testing the correct test case. - options.min_write_buffer_number_to_merge = 3; - options.max_write_buffer_number = 5; - options.max_write_buffer_size_to_maintain = 2 * (options.write_buffer_size); - options.disable_auto_compactions = true; - ASSERT_OK(TryReopen(options)); - - std::atomic mempurge_count{0}; - std::atomic sst_count{0}; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:MemPurgeSuccessful", - [&](void* /*arg*/) { mempurge_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushJob:SSTFileCreated", [&](void* /*arg*/) { sst_count++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Dummy variable used for the following callback function. - uint64_t ZERO = 0; - // We will first execute mempurge operations exclusively. - // Therefore, when the first flush is triggered, we want to make - // sure there is at least 2 memtables being flushed: one output - // from a previous mempurge, and one newly sealed memtable. - // This is when we observed in the past that some SST files created - // were not properly added to the DB version (via the VersionEdit obj). - std::atomic num_memtable_at_first_flush(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FlushJob::WriteLevel0Table:num_memtables", [&](void* arg) { - uint64_t* mems_size = reinterpret_cast(arg); - // atomic_compare_exchange_strong sometimes updates the value - // of ZERO (the "expected" object), so we make sure ZERO is indeed... - // zero. - ZERO = 0; - std::atomic_compare_exchange_strong(&num_memtable_at_first_flush, &ZERO, - *mems_size); - }); - - const std::vector KEYS = { - "ThisIsKey1", "ThisIsKey2", "ThisIsKey3", "ThisIsKey4", "ThisIsKey5", - "ThisIsKey6", "ThisIsKey7", "ThisIsKey8", "ThisIsKey9"}; - const std::string NOT_FOUND = "NOT_FOUND"; - - Random rnd(117); - const uint64_t NUM_REPEAT_OVERWRITES = 100; - const uint64_t NUM_RAND_INSERTS = 500; - const uint64_t RAND_VALUES_LENGTH = 10240; - - std::string key, value; - std::vector values(9, ""); - - // Keys used to check that no SST file disappeared. - for (uint64_t k = 0; k < 5; k++) { - values[k] = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEYS[k], values[k])); - } - - // Insertion of of K-V pairs, multiple times. - // Trigger at least one mempurge and no SST file creation. - for (size_t i = 0; i < NUM_REPEAT_OVERWRITES; i++) { - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - for (uint64_t k = 5; k < values.size(); k++) { - values[k] = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(KEYS[k], values[k])); - } - // Check database consistency. - for (uint64_t k = 0; k < values.size(); k++) { - ASSERT_EQ(Get(KEYS[k]), values[k]); - } - } - - // Check that there was at least one mempurge - uint32_t expected_min_mempurge_count = 1; - // Check that there was no SST files created during flush. - uint32_t expected_sst_count = 0; - EXPECT_GE(mempurge_count.load(), expected_min_mempurge_count); - EXPECT_EQ(sst_count.load(), expected_sst_count); - - // Trigger an SST file creation and no mempurge. - for (size_t i = 0; i < NUM_RAND_INSERTS; i++) { - key = rnd.RandomString(RAND_VALUES_LENGTH); - // Create value strings of arbitrary length RAND_VALUES_LENGTH bytes. - value = rnd.RandomString(RAND_VALUES_LENGTH); - ASSERT_OK(Put(key, value)); - // Check database consistency. - for (uint64_t k = 0; k < values.size(); k++) { - ASSERT_EQ(Get(KEYS[k]), values[k]); - } - ASSERT_EQ(Get(key), value); - } - - // Check that there was at least one SST files created during flush. - expected_sst_count = 1; - EXPECT_GE(sst_count.load(), expected_sst_count); - - // Oddly enough, num_memtable_at_first_flush is not enforced to be - // equal to min_write_buffer_number_to_merge. So by asserting that - // the first SST file creation comes from one output memtable - // from a previous mempurge, and one newly sealed memtable. This - // is the scenario where we observed that some SST files created - // were not properly added to the DB version before our bug fix. - ASSERT_GE(num_memtable_at_first_flush.load(), 2); - - // Check that no data was lost after SST file creation. - for (uint64_t k = 0; k < values.size(); k++) { - ASSERT_EQ(Get(KEYS[k]), values[k]); - } - // Extra check of database consistency. - ASSERT_EQ(Get(key), value); - - Close(); -} - -TEST_P(DBFlushDirectIOTest, DirectIO) { - Options options; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.max_background_flushes = 2; - options.use_direct_io_for_flush_and_compaction = GetParam(); - options.env = MockEnv::Create(Env::Default()); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:create_file", [&](void* arg) { - bool* use_direct_writes = static_cast(arg); - ASSERT_EQ(*use_direct_writes, - options.use_direct_io_for_flush_and_compaction); - }); - - SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); - ASSERT_OK(Put("foo", "v")); - FlushOptions flush_options; - flush_options.wait = true; - ASSERT_OK(dbfull()->Flush(flush_options)); - Destroy(options); - delete options.env; -} - -TEST_F(DBFlushTest, FlushError) { - Options options; - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - options.write_buffer_size = 100; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.disable_auto_compactions = true; - options.env = fault_injection_env.get(); - Reopen(options); - - ASSERT_OK(Put("key1", "value1")); - ASSERT_OK(Put("key2", "value2")); - fault_injection_env->SetFilesystemActive(false); - Status s = dbfull()->TEST_SwitchMemtable(); - fault_injection_env->SetFilesystemActive(true); - Destroy(options); - ASSERT_NE(s, Status::OK()); -} - -TEST_F(DBFlushTest, ManualFlushFailsInReadOnlyMode) { - // Regression test for bug where manual flush hangs forever when the DB - // is in read-only mode. Verify it now at least returns, despite failing. - Options options; - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - options.env = fault_injection_env.get(); - options.max_write_buffer_number = 2; - Reopen(options); - - // Trigger a first flush but don't let it run - ASSERT_OK(db_->PauseBackgroundWork()); - ASSERT_OK(Put("key1", "value1")); - FlushOptions flush_opts; - flush_opts.wait = false; - ASSERT_OK(db_->Flush(flush_opts)); - - // Write a key to the second memtable so we have something to flush later - // after the DB is in read-only mode. - ASSERT_OK(Put("key2", "value2")); - - // Let the first flush continue, hit an error, and put the DB in read-only - // mode. - fault_injection_env->SetFilesystemActive(false); - ASSERT_OK(db_->ContinueBackgroundWork()); - // We ingested the error to env, so the returned status is not OK. - ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable()); - uint64_t num_bg_errors; - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBackgroundErrors, &num_bg_errors)); - ASSERT_GT(num_bg_errors, 0); - - // In the bug scenario, triggering another flush would cause the second flush - // to hang forever. After the fix we expect it to return an error. - ASSERT_NOK(db_->Flush(FlushOptions())); - - Close(); -} - -TEST_F(DBFlushTest, CFDropRaceWithWaitForFlushMemTables) { - Options options = CurrentOptions(); - options.create_if_missing = true; - CreateAndReopenWithCF({"pikachu"}, options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:AfterScheduleFlush", - "DBFlushTest::CFDropRaceWithWaitForFlushMemTables:BeforeDrop"}, - {"DBFlushTest::CFDropRaceWithWaitForFlushMemTables:AfterFree", - "DBImpl::BackgroundCallFlush:start"}, - {"DBImpl::BackgroundCallFlush:start", - "DBImpl::FlushMemTable:BeforeWaitForBgFlush"}}); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_EQ(2, handles_.size()); - ASSERT_OK(Put(1, "key", "value")); - auto* cfd = static_cast(handles_[1])->cfd(); - port::Thread drop_cf_thr([&]() { - TEST_SYNC_POINT( - "DBFlushTest::CFDropRaceWithWaitForFlushMemTables:BeforeDrop"); - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1])); - handles_.resize(1); - TEST_SYNC_POINT( - "DBFlushTest::CFDropRaceWithWaitForFlushMemTables:AfterFree"); - }); - FlushOptions flush_opts; - flush_opts.allow_write_stall = true; - ASSERT_NOK(dbfull()->TEST_FlushMemTable(cfd, flush_opts)); - drop_cf_thr.join(); - Close(); - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBFlushTest, FireOnFlushCompletedAfterCommittedResult) { - class TestListener : public EventListener { - public: - void OnFlushCompleted(DB* db, const FlushJobInfo& info) override { - // There's only one key in each flush. - ASSERT_EQ(info.smallest_seqno, info.largest_seqno); - ASSERT_NE(0, info.smallest_seqno); - if (info.smallest_seqno == seq1) { - // First flush completed - ASSERT_FALSE(completed1); - completed1 = true; - CheckFlushResultCommitted(db, seq1); - } else { - // Second flush completed - ASSERT_FALSE(completed2); - completed2 = true; - ASSERT_EQ(info.smallest_seqno, seq2); - CheckFlushResultCommitted(db, seq2); - } - } - - void CheckFlushResultCommitted(DB* db, SequenceNumber seq) { - DBImpl* db_impl = static_cast_with_check(db); - InstrumentedMutex* mutex = db_impl->mutex(); - mutex->Lock(); - auto* cfd = static_cast_with_check( - db->DefaultColumnFamily()) - ->cfd(); - ASSERT_LT(seq, cfd->imm()->current()->GetEarliestSequenceNumber()); - mutex->Unlock(); - } - - std::atomic seq1{0}; - std::atomic seq2{0}; - std::atomic completed1{false}; - std::atomic completed2{false}; - }; - std::shared_ptr listener = std::make_shared(); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTableToOutputFile:AfterPickMemtables", - "DBFlushTest::FireOnFlushCompletedAfterCommittedResult:WaitFirst"}, - {"DBImpl::FlushMemTableToOutputFile:Finish", - "DBFlushTest::FireOnFlushCompletedAfterCommittedResult:WaitSecond"}}); - SyncPoint::GetInstance()->SetCallBack( - "FlushJob::WriteLevel0Table", [&listener](void* arg) { - // Wait for the second flush finished, out of mutex. - auto* mems = reinterpret_cast*>(arg); - if (mems->front()->GetEarliestSequenceNumber() == listener->seq1 - 1) { - TEST_SYNC_POINT( - "DBFlushTest::FireOnFlushCompletedAfterCommittedResult:" - "WaitSecond"); - } - }); - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.listeners.push_back(listener); - // Setting max_flush_jobs = max_background_jobs / 4 = 2. - options.max_background_jobs = 8; - // Allow 2 immutable memtables. - options.max_write_buffer_number = 3; - Reopen(options); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put("foo", "v")); - listener->seq1 = db_->GetLatestSequenceNumber(); - // t1 will wait for the second flush complete before committing flush result. - auto t1 = port::Thread([&]() { - // flush_opts.wait = true - ASSERT_OK(db_->Flush(FlushOptions())); - }); - // Wait for first flush started. - TEST_SYNC_POINT( - "DBFlushTest::FireOnFlushCompletedAfterCommittedResult:WaitFirst"); - // The second flush will exit early without commit its result. The work - // is delegated to the first flush. - ASSERT_OK(Put("bar", "v")); - listener->seq2 = db_->GetLatestSequenceNumber(); - FlushOptions flush_opts; - flush_opts.wait = false; - ASSERT_OK(db_->Flush(flush_opts)); - t1.join(); - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork()); - ASSERT_TRUE(listener->completed1); - ASSERT_TRUE(listener->completed2); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBFlushTest, FlushWithBlob) { - constexpr uint64_t min_blob_size = 10; - - Options options; - options.enable_blob_files = true; - options.min_blob_size = min_blob_size; - options.disable_auto_compactions = true; - options.env = env_; - - Reopen(options); - - constexpr char short_value[] = "short"; - static_assert(sizeof(short_value) - 1 < min_blob_size, - "short_value too long"); - - constexpr char long_value[] = "long_value"; - static_assert(sizeof(long_value) - 1 >= min_blob_size, - "long_value too short"); - - ASSERT_OK(Put("key1", short_value)); - ASSERT_OK(Put("key2", long_value)); - - ASSERT_OK(Flush()); - - ASSERT_EQ(Get("key1"), short_value); - ASSERT_EQ(Get("key2"), long_value); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - Version* const current = cfd->current(); - assert(current); - - const VersionStorageInfo* const storage_info = current->storage_info(); - assert(storage_info); - - const auto& l0_files = storage_info->LevelFiles(0); - ASSERT_EQ(l0_files.size(), 1); - - const FileMetaData* const table_file = l0_files[0]; - assert(table_file); - - const auto& blob_files = storage_info->GetBlobFiles(); - ASSERT_EQ(blob_files.size(), 1); - - const auto& blob_file = blob_files.front(); - assert(blob_file); - - ASSERT_EQ(table_file->smallest.user_key(), "key1"); - ASSERT_EQ(table_file->largest.user_key(), "key2"); - ASSERT_EQ(table_file->fd.smallest_seqno, 1); - ASSERT_EQ(table_file->fd.largest_seqno, 2); - ASSERT_EQ(table_file->oldest_blob_file_number, - blob_file->GetBlobFileNumber()); - - ASSERT_EQ(blob_file->GetTotalBlobCount(), 1); - - const InternalStats* const internal_stats = cfd->internal_stats(); - assert(internal_stats); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_FALSE(compaction_stats.empty()); - ASSERT_EQ(compaction_stats[0].bytes_written, table_file->fd.GetFileSize()); - ASSERT_EQ(compaction_stats[0].bytes_written_blob, - blob_file->GetTotalBlobBytes()); - ASSERT_EQ(compaction_stats[0].num_output_files, 1); - ASSERT_EQ(compaction_stats[0].num_output_files_blob, 1); - - const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue(); - ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED], - compaction_stats[0].bytes_written + - compaction_stats[0].bytes_written_blob); -} - -TEST_F(DBFlushTest, FlushWithChecksumHandoff1) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.write_buffer_size = 100; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.disable_auto_compactions = true; - options.env = fault_fs_env.get(); - options.checksum_handoff_file_types.Add(FileType::kTableFile); - Reopen(options); - - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put("key1", "value1")); - ASSERT_OK(Put("key2", "value2")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - - // The hash does not match, write fails - // fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - // Since the file system returns IOStatus::Corruption, it is an - // unrecoverable error. - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - }); - ASSERT_OK(Put("key3", "value3")); - ASSERT_OK(Put("key4", "value4")); - SyncPoint::GetInstance()->EnableProcessing(); - Status s = Flush(); - ASSERT_EQ(s.severity(), - ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); - Reopen(options); - - // The file system does not support checksum handoff. The check - // will be ignored. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - ASSERT_OK(Put("key5", "value5")); - ASSERT_OK(Put("key6", "value6")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - - // Each write will be similated as corrupted. - // Since the file system returns IOStatus::Corruption, it is an - // unrecoverable error. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs->IngestDataCorruptionBeforeWrite(); - }); - ASSERT_OK(Put("key7", "value7")); - ASSERT_OK(Put("key8", "value8")); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), - ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError); - SyncPoint::GetInstance()->DisableProcessing(); - - Destroy(options); -} - -TEST_F(DBFlushTest, FlushWithChecksumHandoff2) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.write_buffer_size = 100; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.disable_auto_compactions = true; - options.env = fault_fs_env.get(); - Reopen(options); - - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - ASSERT_OK(Put("key1", "value1")); - ASSERT_OK(Put("key2", "value2")); - ASSERT_OK(Flush()); - - // options is not set, the checksum handoff will not be triggered - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - }); - ASSERT_OK(Put("key3", "value3")); - ASSERT_OK(Put("key4", "value4")); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); - Reopen(options); - - // The file system does not support checksum handoff. The check - // will be ignored. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - ASSERT_OK(Put("key5", "value5")); - ASSERT_OK(Put("key6", "value6")); - ASSERT_OK(Flush()); - - // options is not set, the checksum handoff will not be triggered - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs->IngestDataCorruptionBeforeWrite(); - }); - ASSERT_OK(Put("key7", "value7")); - ASSERT_OK(Put("key8", "value8")); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - - Destroy(options); -} - -TEST_F(DBFlushTest, FlushWithChecksumHandoffManifest1) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.write_buffer_size = 100; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.disable_auto_compactions = true; - options.env = fault_fs_env.get(); - options.checksum_handoff_file_types.Add(FileType::kDescriptorFile); - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - Reopen(options); - - ASSERT_OK(Put("key1", "value1")); - ASSERT_OK(Put("key2", "value2")); - ASSERT_OK(Flush()); - - // The hash does not match, write fails - // fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - // Since the file system returns IOStatus::Corruption, it is mapped to - // kFatalError error. - ASSERT_OK(Put("key3", "value3")); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - }); - ASSERT_OK(Put("key3", "value3")); - ASSERT_OK(Put("key4", "value4")); - SyncPoint::GetInstance()->EnableProcessing(); - Status s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); -} - -TEST_F(DBFlushTest, FlushWithChecksumHandoffManifest2) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - Options options = CurrentOptions(); - options.write_buffer_size = 100; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.disable_auto_compactions = true; - options.env = fault_fs_env.get(); - options.checksum_handoff_file_types.Add(FileType::kDescriptorFile); - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - Reopen(options); - // The file system does not support checksum handoff. The check - // will be ignored. - ASSERT_OK(Put("key5", "value5")); - ASSERT_OK(Put("key6", "value6")); - ASSERT_OK(Flush()); - - // Each write will be similated as corrupted. - // Since the file system returns IOStatus::Corruption, it is mapped to - // kFatalError error. - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", - [&](void*) { fault_fs->IngestDataCorruptionBeforeWrite(); }); - ASSERT_OK(Put("key7", "value7")); - ASSERT_OK(Put("key8", "value8")); - SyncPoint::GetInstance()->EnableProcessing(); - Status s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - SyncPoint::GetInstance()->DisableProcessing(); - - Destroy(options); -} - -TEST_F(DBFlushTest, PickRightMemtables) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - options.create_if_missing = true; - - const std::string test_cf_name = "test_cf"; - options.max_write_buffer_number = 128; - CreateColumnFamilies({test_cf_name}, options); - - Close(); - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, test_cf_name}, options); - - ASSERT_OK(db_->Put(WriteOptions(), "key", "value")); - - ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "key", "value")); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::SyncClosedLogs:BeforeReLock", [&](void* /*arg*/) { - ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "what", "v")); - auto* cfhi = - static_cast_with_check(handles_[1]); - assert(cfhi); - ASSERT_OK(dbfull()->TEST_SwitchMemtable(cfhi->cfd())); - }); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushMemTableToOutputFile:AfterPickMemtables", [&](void* arg) { - auto* job = reinterpret_cast(arg); - assert(job); - const auto& mems = job->GetMemTables(); - assert(mems.size() == 1); - assert(mems[0]); - ASSERT_EQ(1, mems[0]->GetID()); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(db_->Flush(FlushOptions(), handles_[1])); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -class DBFlushTestBlobError : public DBFlushTest, - public testing::WithParamInterface { - public: - DBFlushTestBlobError() : sync_point_(GetParam()) {} - - std::string sync_point_; -}; - -INSTANTIATE_TEST_CASE_P(DBFlushTestBlobError, DBFlushTestBlobError, - ::testing::ValuesIn(std::vector{ - "BlobFileBuilder::WriteBlobToFile:AddRecord", - "BlobFileBuilder::WriteBlobToFile:AppendFooter"})); - -TEST_P(DBFlushTestBlobError, FlushError) { - Options options; - options.enable_blob_files = true; - options.disable_auto_compactions = true; - options.env = env_; - - Reopen(options); - - ASSERT_OK(Put("key", "blob")); - - SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) { - Status* const s = static_cast(arg); - assert(s); - - (*s) = Status::IOError(sync_point_); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_NOK(Flush()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - Version* const current = cfd->current(); - assert(current); - - const VersionStorageInfo* const storage_info = current->storage_info(); - assert(storage_info); - - const auto& l0_files = storage_info->LevelFiles(0); - ASSERT_TRUE(l0_files.empty()); - - const auto& blob_files = storage_info->GetBlobFiles(); - ASSERT_TRUE(blob_files.empty()); - - // Make sure the files generated by the failed job have been deleted - std::vector files; - ASSERT_OK(env_->GetChildren(dbname_, &files)); - for (const auto& file : files) { - uint64_t number = 0; - FileType type = kTableFile; - - if (!ParseFileName(file, &number, &type)) { - continue; - } - - ASSERT_NE(type, kTableFile); - ASSERT_NE(type, kBlobFile); - } - - const InternalStats* const internal_stats = cfd->internal_stats(); - assert(internal_stats); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_FALSE(compaction_stats.empty()); - - if (sync_point_ == "BlobFileBuilder::WriteBlobToFile:AddRecord") { - ASSERT_EQ(compaction_stats[0].bytes_written, 0); - ASSERT_EQ(compaction_stats[0].bytes_written_blob, 0); - ASSERT_EQ(compaction_stats[0].num_output_files, 0); - ASSERT_EQ(compaction_stats[0].num_output_files_blob, 0); - } else { - // SST file writing succeeded; blob file writing failed (during Finish) - ASSERT_GT(compaction_stats[0].bytes_written, 0); - ASSERT_EQ(compaction_stats[0].bytes_written_blob, 0); - ASSERT_EQ(compaction_stats[0].num_output_files, 1); - ASSERT_EQ(compaction_stats[0].num_output_files_blob, 0); - } - - const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue(); - ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED], - compaction_stats[0].bytes_written + - compaction_stats[0].bytes_written_blob); -} - -TEST_F(DBFlushTest, TombstoneVisibleInSnapshot) { - class SimpleTestFlushListener : public EventListener { - public: - explicit SimpleTestFlushListener(DBFlushTest* _test) : test_(_test) {} - ~SimpleTestFlushListener() override {} - - void OnFlushBegin(DB* db, const FlushJobInfo& info) override { - ASSERT_EQ(static_cast(0), info.cf_id); - - ASSERT_OK(db->Delete(WriteOptions(), "foo")); - snapshot_ = db->GetSnapshot(); - ASSERT_OK(db->Put(WriteOptions(), "foo", "value")); - - auto* dbimpl = static_cast_with_check(db); - assert(dbimpl); - - ColumnFamilyHandle* cfh = db->DefaultColumnFamily(); - auto* cfhi = static_cast_with_check(cfh); - assert(cfhi); - ASSERT_OK(dbimpl->TEST_SwitchMemtable(cfhi->cfd())); - } - - DBFlushTest* test_ = nullptr; - const Snapshot* snapshot_ = nullptr; - }; - - Options options = CurrentOptions(); - options.create_if_missing = true; - auto* listener = new SimpleTestFlushListener(this); - options.listeners.emplace_back(listener); - DestroyAndReopen(options); - - ASSERT_OK(db_->Put(WriteOptions(), "foo", "value0")); - - ManagedSnapshot snapshot_guard(db_); - - ColumnFamilyHandle* default_cf = db_->DefaultColumnFamily(); - ASSERT_OK(db_->Flush(FlushOptions(), default_cf)); - - const Snapshot* snapshot = listener->snapshot_; - assert(snapshot); - - ReadOptions read_opts; - read_opts.snapshot = snapshot; - - // Using snapshot should not see "foo". - { - std::string value; - Status s = db_->Get(read_opts, "foo", &value); - ASSERT_TRUE(s.IsNotFound()); - } - - db_->ReleaseSnapshot(snapshot); -} - -TEST_P(DBAtomicFlushTest, ManualFlushUnder2PC) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.allow_2pc = true; - options.atomic_flush = GetParam(); - // 64MB so that memtable flush won't be trigger by the small writes. - options.write_buffer_size = (static_cast(64) << 20); - auto flush_listener = std::make_shared(); - flush_listener->expected_flush_reason = FlushReason::kManualFlush; - options.listeners.push_back(flush_listener); - // Destroy the DB to recreate as a TransactionDB. - Close(); - Destroy(options, true); - - // Create a TransactionDB. - TransactionDB* txn_db = nullptr; - TransactionDBOptions txn_db_opts; - txn_db_opts.write_policy = TxnDBWritePolicy::WRITE_COMMITTED; - ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db)); - ASSERT_NE(txn_db, nullptr); - db_ = txn_db; - - // Create two more columns other than default CF. - std::vector cfs = {"puppy", "kitty"}; - CreateColumnFamilies(cfs, options); - ASSERT_EQ(handles_.size(), 2); - ASSERT_EQ(handles_[0]->GetName(), cfs[0]); - ASSERT_EQ(handles_[1]->GetName(), cfs[1]); - const size_t kNumCfToFlush = options.atomic_flush ? 2 : 1; - - WriteOptions wopts; - TransactionOptions txn_opts; - // txn1 only prepare, but does not commit. - // The WAL containing the prepared but uncommitted data must be kept. - Transaction* txn1 = txn_db->BeginTransaction(wopts, txn_opts, nullptr); - // txn2 not only prepare, but also commit. - Transaction* txn2 = txn_db->BeginTransaction(wopts, txn_opts, nullptr); - ASSERT_NE(txn1, nullptr); - ASSERT_NE(txn2, nullptr); - for (size_t i = 0; i < kNumCfToFlush; i++) { - ASSERT_OK(txn1->Put(handles_[i], "k1", "v1")); - ASSERT_OK(txn2->Put(handles_[i], "k2", "v2")); - } - // A txn must be named before prepare. - ASSERT_OK(txn1->SetName("txn1")); - ASSERT_OK(txn2->SetName("txn2")); - // Prepare writes to WAL, but not to memtable. (WriteCommitted) - ASSERT_OK(txn1->Prepare()); - ASSERT_OK(txn2->Prepare()); - // Commit writes to memtable. - ASSERT_OK(txn2->Commit()); - delete txn1; - delete txn2; - - // There are still data in memtable not flushed. - // But since data is small enough to reside in the active memtable, - // there are no immutable memtable. - for (size_t i = 0; i < kNumCfToFlush; i++) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_FALSE(cfh->cfd()->mem()->IsEmpty()); - } - - // Atomic flush memtables, - // the min log with prepared data should be written to MANIFEST. - std::vector cfs_to_flush(kNumCfToFlush); - for (size_t i = 0; i < kNumCfToFlush; i++) { - cfs_to_flush[i] = handles_[i]; - } - ASSERT_OK(txn_db->Flush(FlushOptions(), cfs_to_flush)); - - // There are no remaining data in memtable after flush. - for (size_t i = 0; i < kNumCfToFlush; i++) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); - } - - // The recovered min log number with prepared data should be non-zero. - // In 2pc mode, MinLogNumberToKeep returns the - // VersionSet::min_log_number_to_keep recovered from MANIFEST, if it's 0, - // it means atomic flush didn't write the min_log_number_to_keep to MANIFEST. - cfs.push_back(kDefaultColumnFamilyName); - ASSERT_OK(TryReopenWithColumnFamilies(cfs, options)); - DBImpl* db_impl = reinterpret_cast(db_); - ASSERT_TRUE(db_impl->allow_2pc()); - ASSERT_NE(db_impl->MinLogNumberToKeep(), 0); -} - -TEST_P(DBAtomicFlushTest, ManualAtomicFlush) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = GetParam(); - options.write_buffer_size = (static_cast(64) << 20); - auto flush_listener = std::make_shared(); - flush_listener->expected_flush_reason = FlushReason::kManualFlush; - options.listeners.push_back(flush_listener); - - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - WriteOptions wopts; - wopts.disableWAL = true; - for (size_t i = 0; i != num_cfs; ++i) { - ASSERT_OK(Put(static_cast(i) /*cf*/, "key", "value", wopts)); - } - - for (size_t i = 0; i != num_cfs; ++i) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_FALSE(cfh->cfd()->mem()->IsEmpty()); - } - - std::vector cf_ids; - for (size_t i = 0; i != num_cfs; ++i) { - cf_ids.emplace_back(static_cast(i)); - } - ASSERT_OK(Flush(cf_ids)); - - for (size_t i = 0; i != num_cfs; ++i) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); - } -} - -TEST_P(DBAtomicFlushTest, PrecomputeMinLogNumberToKeepNon2PC) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = GetParam(); - options.write_buffer_size = (static_cast(64) << 20); - CreateAndReopenWithCF({"pikachu"}, options); - - const size_t num_cfs = handles_.size(); - ASSERT_EQ(num_cfs, 2); - WriteOptions wopts; - for (size_t i = 0; i != num_cfs; ++i) { - ASSERT_OK(Put(static_cast(i) /*cf*/, "key", "value", wopts)); - } - - { - // Flush the default CF only. - std::vector cf_ids{0}; - ASSERT_OK(Flush(cf_ids)); - - autovector flushed_cfds; - autovector> flush_edits; - auto flushed_cfh = static_cast(handles_[0]); - flushed_cfds.push_back(flushed_cfh->cfd()); - flush_edits.push_back({}); - auto unflushed_cfh = static_cast(handles_[1]); - - ASSERT_EQ(PrecomputeMinLogNumberToKeepNon2PC(dbfull()->GetVersionSet(), - flushed_cfds, flush_edits), - unflushed_cfh->cfd()->GetLogNumber()); - } - - { - // Flush all CFs. - std::vector cf_ids; - for (size_t i = 0; i != num_cfs; ++i) { - cf_ids.emplace_back(static_cast(i)); - } - ASSERT_OK(Flush(cf_ids)); - uint64_t log_num_after_flush = dbfull()->TEST_GetCurrentLogNumber(); - - uint64_t min_log_number_to_keep = std::numeric_limits::max(); - autovector flushed_cfds; - autovector> flush_edits; - for (size_t i = 0; i != num_cfs; ++i) { - auto cfh = static_cast(handles_[i]); - flushed_cfds.push_back(cfh->cfd()); - flush_edits.push_back({}); - min_log_number_to_keep = - std::min(min_log_number_to_keep, cfh->cfd()->GetLogNumber()); - } - ASSERT_EQ(min_log_number_to_keep, log_num_after_flush); - ASSERT_EQ(PrecomputeMinLogNumberToKeepNon2PC(dbfull()->GetVersionSet(), - flushed_cfds, flush_edits), - min_log_number_to_keep); - } -} - -TEST_P(DBAtomicFlushTest, AtomicFlushTriggeredByMemTableFull) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = GetParam(); - // 4KB so that we can easily trigger auto flush. - options.write_buffer_size = 4096; - - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCallFlush:FlushFinish:0", - "DBAtomicFlushTest::AtomicFlushTriggeredByMemTableFull:BeforeCheck"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - WriteOptions wopts; - wopts.disableWAL = true; - for (size_t i = 0; i != num_cfs; ++i) { - ASSERT_OK(Put(static_cast(i) /*cf*/, "key", "value", wopts)); - } - // Keep writing to one of them column families to trigger auto flush. - for (int i = 0; i != 4000; ++i) { - ASSERT_OK(Put(static_cast(num_cfs) - 1 /*cf*/, - "key" + std::to_string(i), "value" + std::to_string(i), - wopts)); - } - - TEST_SYNC_POINT( - "DBAtomicFlushTest::AtomicFlushTriggeredByMemTableFull:BeforeCheck"); - if (options.atomic_flush) { - for (size_t i = 0; i + 1 != num_cfs; ++i) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); - } - } else { - for (size_t i = 0; i + 1 != num_cfs; ++i) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_FALSE(cfh->cfd()->mem()->IsEmpty()); - } - } - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBAtomicFlushTest, AtomicFlushRollbackSomeJobs) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - options.env = fault_injection_env.get(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:1", - "DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:1"}, - {"DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:2", - "DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:2"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - WriteOptions wopts; - wopts.disableWAL = true; - for (size_t i = 0; i != num_cfs; ++i) { - int cf_id = static_cast(i); - ASSERT_OK(Put(cf_id, "key", "value", wopts)); - } - FlushOptions flush_opts; - flush_opts.wait = false; - ASSERT_OK(dbfull()->Flush(flush_opts, handles_)); - TEST_SYNC_POINT("DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:1"); - fault_injection_env->SetFilesystemActive(false); - TEST_SYNC_POINT("DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:2"); - for (auto* cfh : handles_) { - // Returns the IO error happend during flush. - ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable(cfh)); - } - for (size_t i = 0; i != num_cfs; ++i) { - auto cfh = static_cast(handles_[i]); - ASSERT_EQ(1, cfh->cfd()->imm()->NumNotFlushed()); - ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); - } - fault_injection_env->SetFilesystemActive(true); - Destroy(options); -} - -TEST_P(DBAtomicFlushTest, FlushMultipleCFs_DropSomeBeforeRequestFlush) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - WriteOptions wopts; - wopts.disableWAL = true; - std::vector cf_ids; - for (size_t i = 0; i != num_cfs; ++i) { - int cf_id = static_cast(i); - ASSERT_OK(Put(cf_id, "key", "value", wopts)); - cf_ids.push_back(cf_id); - } - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - ASSERT_TRUE(Flush(cf_ids).IsColumnFamilyDropped()); - Destroy(options); -} - -TEST_P(DBAtomicFlushTest, - FlushMultipleCFs_DropSomeAfterScheduleFlushBeforeFlushJobRun) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::AtomicFlushMemTables:AfterScheduleFlush", - "DBAtomicFlushTest::BeforeDropCF"}, - {"DBAtomicFlushTest::AfterDropCF", - "DBImpl::BackgroundCallFlush:start"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - size_t num_cfs = handles_.size(); - ASSERT_EQ(3, num_cfs); - WriteOptions wopts; - wopts.disableWAL = true; - for (size_t i = 0; i != num_cfs; ++i) { - int cf_id = static_cast(i); - ASSERT_OK(Put(cf_id, "key", "value", wopts)); - } - port::Thread user_thread([&]() { - TEST_SYNC_POINT("DBAtomicFlushTest::BeforeDropCF"); - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - TEST_SYNC_POINT("DBAtomicFlushTest::AfterDropCF"); - }); - FlushOptions flush_opts; - flush_opts.wait = true; - ASSERT_OK(dbfull()->Flush(flush_opts, handles_)); - user_thread.join(); - for (size_t i = 0; i != num_cfs; ++i) { - int cf_id = static_cast(i); - ASSERT_EQ("value", Get(cf_id, "key")); - } - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "eevee"}, options); - num_cfs = handles_.size(); - ASSERT_EQ(2, num_cfs); - for (size_t i = 0; i != num_cfs; ++i) { - int cf_id = static_cast(i); - ASSERT_EQ("value", Get(cf_id, "key")); - } - Destroy(options); -} - -TEST_P(DBAtomicFlushTest, TriggerFlushAndClose) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - const int kNumKeysTriggerFlush = 4; - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysTriggerFlush)); - CreateAndReopenWithCF({"pikachu"}, options); - - for (int i = 0; i != kNumKeysTriggerFlush; ++i) { - ASSERT_OK(Put(0, "key" + std::to_string(i), "value" + std::to_string(i))); - } - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put(0, "key", "value")); - Close(); - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - ASSERT_EQ("value", Get(0, "key")); -} - -TEST_P(DBAtomicFlushTest, PickMemtablesRaceWithBackgroundFlush) { - bool atomic_flush = GetParam(); - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - options.max_write_buffer_number = 4; - // Set min_write_buffer_number_to_merge to be greater than 1, so that - // a column family with one memtable in the imm will not cause IsFlushPending - // to return true when flush_requested_ is false. - options.min_write_buffer_number_to_merge = 2; - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_EQ(2, handles_.size()); - ASSERT_OK(dbfull()->PauseBackgroundWork()); - ASSERT_OK(Put(0, "key00", "value00")); - ASSERT_OK(Put(1, "key10", "value10")); - FlushOptions flush_opts; - flush_opts.wait = false; - ASSERT_OK(dbfull()->Flush(flush_opts, handles_)); - ASSERT_OK(Put(0, "key01", "value01")); - // Since max_write_buffer_number is 4, the following flush won't cause write - // stall. - ASSERT_OK(dbfull()->Flush(flush_opts)); - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1])); - handles_[1] = nullptr; - ASSERT_OK(dbfull()->ContinueBackgroundWork()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[0])); - delete handles_[0]; - handles_.clear(); -} - -TEST_P(DBAtomicFlushTest, CFDropRaceWithWaitForFlushMemTables) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - Options options = CurrentOptions(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - CreateAndReopenWithCF({"pikachu"}, options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::AtomicFlushMemTables:AfterScheduleFlush", - "DBAtomicFlushTest::CFDropRaceWithWaitForFlushMemTables:BeforeDrop"}, - {"DBAtomicFlushTest::CFDropRaceWithWaitForFlushMemTables:AfterFree", - "DBImpl::BackgroundCallFlush:start"}, - {"DBImpl::BackgroundCallFlush:start", - "DBImpl::AtomicFlushMemTables:BeforeWaitForBgFlush"}}); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_EQ(2, handles_.size()); - ASSERT_OK(Put(0, "key", "value")); - ASSERT_OK(Put(1, "key", "value")); - auto* cfd_default = - static_cast(dbfull()->DefaultColumnFamily()) - ->cfd(); - auto* cfd_pikachu = static_cast(handles_[1])->cfd(); - port::Thread drop_cf_thr([&]() { - TEST_SYNC_POINT( - "DBAtomicFlushTest::CFDropRaceWithWaitForFlushMemTables:BeforeDrop"); - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - delete handles_[1]; - handles_.resize(1); - TEST_SYNC_POINT( - "DBAtomicFlushTest::CFDropRaceWithWaitForFlushMemTables:AfterFree"); - }); - FlushOptions flush_opts; - flush_opts.allow_write_stall = true; - ASSERT_OK(dbfull()->TEST_AtomicFlushMemTables({cfd_default, cfd_pikachu}, - flush_opts)); - drop_cf_thr.join(); - Close(); - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBAtomicFlushTest, RollbackAfterFailToInstallResults) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - auto fault_injection_env = std::make_shared(env_); - Options options = CurrentOptions(); - options.env = fault_injection_env.get(); - options.create_if_missing = true; - options.atomic_flush = atomic_flush; - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_EQ(2, handles_.size()); - for (size_t cf = 0; cf < handles_.size(); ++cf) { - ASSERT_OK(Put(static_cast(cf), "a", "value")); - } - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0", - [&](void* /*arg*/) { fault_injection_env->SetFilesystemActive(false); }); - SyncPoint::GetInstance()->EnableProcessing(); - FlushOptions flush_opts; - Status s = db_->Flush(flush_opts, handles_); - ASSERT_NOK(s); - fault_injection_env->SetFilesystemActive(true); - Close(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -// In atomic flush, concurrent bg flush threads commit to the MANIFEST in -// serial, in the order of their picked memtables for each column family. -// Only when a bg flush thread finds out that its memtables are the earliest -// unflushed ones for all the included column families will this bg flush -// thread continue to commit to MANIFEST. -// This unit test uses sync point to coordinate the execution of two bg threads -// executing the same sequence of functions. The interleaving are as follows. -// time bg1 bg2 -// | pick memtables to flush -// | flush memtables cf1_m1, cf2_m1 -// | join MANIFEST write queue -// | pick memtabls to flush -// | flush memtables cf1_(m1+1) -// | join MANIFEST write queue -// | wait to write MANIFEST -// | write MANIFEST -// | IO error -// | detect IO error and stop waiting -// V -TEST_P(DBAtomicFlushTest, BgThreadNoWaitAfterManifestError) { - bool atomic_flush = GetParam(); - if (!atomic_flush) { - return; - } - auto fault_injection_env = std::make_shared(env_); - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.atomic_flush = true; - options.env = fault_injection_env.get(); - // Set a larger value than default so that RocksDB can schedule concurrent - // background flush threads. - options.max_background_jobs = 8; - options.max_write_buffer_number = 8; - CreateAndReopenWithCF({"pikachu"}, options); - - assert(2 == handles_.size()); - - WriteOptions write_opts; - write_opts.disableWAL = true; - - ASSERT_OK(Put(0, "a", "v_0_a", write_opts)); - ASSERT_OK(Put(1, "a", "v_1_a", write_opts)); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - SyncPoint::GetInstance()->LoadDependency({ - {"BgFlushThr2:WaitToCommit", "BgFlushThr1:BeforeWriteManifest"}, - }); - - std::thread::id bg_flush_thr1, bg_flush_thr2; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCallFlush:start", [&](void*) { - if (bg_flush_thr1 == std::thread::id()) { - bg_flush_thr1 = std::this_thread::get_id(); - } else if (bg_flush_thr2 == std::thread::id()) { - bg_flush_thr2 = std::this_thread::get_id(); - } - }); - - int called = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::AtomicFlushMemTablesToOutputFiles:WaitToCommit", [&](void* arg) { - if (std::this_thread::get_id() == bg_flush_thr2) { - const auto* ptr = reinterpret_cast*>(arg); - assert(ptr); - if (0 == called) { - // When bg flush thread 2 reaches here for the first time. - ASSERT_OK(ptr->first); - ASSERT_TRUE(ptr->second); - } else if (1 == called) { - // When bg flush thread 2 reaches here for the second time. - ASSERT_TRUE(ptr->first.IsIOError()); - ASSERT_FALSE(ptr->second); - } - ++called; - TEST_SYNC_POINT("BgFlushThr2:WaitToCommit"); - } - }); - - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0", - [&](void*) { - if (std::this_thread::get_id() == bg_flush_thr1) { - TEST_SYNC_POINT("BgFlushThr1:BeforeWriteManifest"); - } - }); - - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - if (std::this_thread::get_id() != bg_flush_thr1) { - return; - } - ASSERT_OK(db_->Put(write_opts, "b", "v_1_b")); - - FlushOptions flush_opts; - flush_opts.wait = false; - std::vector cfhs(1, db_->DefaultColumnFamily()); - ASSERT_OK(dbfull()->Flush(flush_opts, cfhs)); - }); - - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:AfterSyncManifest", [&](void* arg) { - auto* ptr = reinterpret_cast(arg); - assert(ptr); - *ptr = IOStatus::IOError("Injected failure"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_TRUE(dbfull()->Flush(FlushOptions(), handles_).IsIOError()); - - Close(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(DBAtomicFlushTest, NoWaitWhenWritesStopped) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.atomic_flush = GetParam(); - options.max_write_buffer_number = 2; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - - Reopen(options); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::DelayWrite:Start", - "DBAtomicFlushTest::NoWaitWhenWritesStopped:0"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->PauseBackgroundWork()); - for (int i = 0; i < options.max_write_buffer_number; ++i) { - ASSERT_OK(Put("k" + std::to_string(i), "v" + std::to_string(i))); - } - std::thread stalled_writer([&]() { ASSERT_OK(Put("k", "v")); }); - - TEST_SYNC_POINT("DBAtomicFlushTest::NoWaitWhenWritesStopped:0"); - - { - FlushOptions flush_opts; - flush_opts.wait = false; - flush_opts.allow_write_stall = true; - ASSERT_TRUE(db_->Flush(flush_opts).IsTryAgain()); - } - - ASSERT_OK(dbfull()->ContinueBackgroundWork()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - stalled_writer.join(); - - SyncPoint::GetInstance()->DisableProcessing(); -} - -INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest, - testing::Bool()); - -INSTANTIATE_TEST_CASE_P(DBAtomicFlushTest, DBAtomicFlushTest, testing::Bool()); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_inplace_update_test.cc b/db/db_inplace_update_test.cc deleted file mode 100644 index 3921a3b00..000000000 --- a/db/db_inplace_update_test.cc +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -#include "db/db_test_util.h" -#include "port/stack_trace.h" - -namespace ROCKSDB_NAMESPACE { - -class DBTestInPlaceUpdate : public DBTestBase { - public: - DBTestInPlaceUpdate() - : DBTestBase("db_inplace_update_test", /*env_do_fsync=*/true) {} -}; - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdate) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - options.env = env_; - options.write_buffer_size = 100000; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of smaller size - int numValues = 10; - for (int i = numValues; i > 0; i--) { - std::string value = DummyString(i, 'a'); - ASSERT_OK(Put(1, "key", value)); - ASSERT_EQ(value, Get(1, "key")); - } - - // Only 1 instance for that key. - validateNumberOfEntries(1, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateLargeNewValue) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - options.env = env_; - options.write_buffer_size = 100000; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of larger size - int numValues = 10; - for (int i = 0; i < numValues; i++) { - std::string value = DummyString(i, 'a'); - ASSERT_OK(Put(1, "key", value)); - ASSERT_EQ(value, Get(1, "key")); - } - - // All 10 updates exist in the internal iterator - validateNumberOfEntries(numValues, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateEntitySmallerNewValue) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - options.env = env_; - options.allow_concurrent_memtable_write = false; - - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of smaller size - constexpr int num_values = 10; - for (int i = num_values; i > 0; --i) { - constexpr char key[] = "key"; - const std::string value = DummyString(i, 'a'); - WideColumns wide_columns{{"attr", value}}; - - ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[1], key, wide_columns)); - // TODO: use Get to check entity once it's supported - } - - // Only 1 instance for that key. - validateNumberOfEntries(1, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateEntityLargerNewValue) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - options.env = env_; - options.allow_concurrent_memtable_write = false; - - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of larger size - constexpr int num_values = 10; - for (int i = 0; i < num_values; ++i) { - constexpr char key[] = "key"; - const std::string value = DummyString(i, 'a'); - WideColumns wide_columns{{"attr", value}}; - - ASSERT_OK(db_->PutEntity(WriteOptions(), handles_[1], key, wide_columns)); - // TODO: use Get to check entity once it's supported - } - - // All 10 updates exist in the internal iterator - validateNumberOfEntries(num_values, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerSize) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - - options.env = env_; - options.write_buffer_size = 100000; - options.inplace_callback = - ROCKSDB_NAMESPACE::DBTestInPlaceUpdate::updateInPlaceSmallerSize; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of smaller size - int numValues = 10; - ASSERT_OK(Put(1, "key", DummyString(numValues, 'a'))); - ASSERT_EQ(DummyString(numValues, 'c'), Get(1, "key")); - - for (int i = numValues; i > 0; i--) { - ASSERT_OK(Put(1, "key", DummyString(i, 'a'))); - ASSERT_EQ(DummyString(i - 1, 'b'), Get(1, "key")); - } - - // Only 1 instance for that key. - validateNumberOfEntries(1, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerVarintSize) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - - options.env = env_; - options.write_buffer_size = 100000; - options.inplace_callback = - ROCKSDB_NAMESPACE::DBTestInPlaceUpdate::updateInPlaceSmallerVarintSize; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of smaller varint size - int numValues = 265; - ASSERT_OK(Put(1, "key", DummyString(numValues, 'a'))); - ASSERT_EQ(DummyString(numValues, 'c'), Get(1, "key")); - - for (int i = numValues; i > 0; i--) { - ASSERT_OK(Put(1, "key", DummyString(i, 'a'))); - ASSERT_EQ(DummyString(1, 'b'), Get(1, "key")); - } - - // Only 1 instance for that key. - validateNumberOfEntries(1, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackLargeNewValue) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - - options.env = env_; - options.write_buffer_size = 100000; - options.inplace_callback = - ROCKSDB_NAMESPACE::DBTestInPlaceUpdate::updateInPlaceLargerSize; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of larger size - int numValues = 10; - for (int i = 0; i < numValues; i++) { - ASSERT_OK(Put(1, "key", DummyString(i, 'a'))); - ASSERT_EQ(DummyString(i, 'c'), Get(1, "key")); - } - - // No inplace updates. All updates are puts with new seq number - // All 10 updates exist in the internal iterator - validateNumberOfEntries(numValues, 1); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackNoAction) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - - options.env = env_; - options.write_buffer_size = 100000; - options.inplace_callback = - ROCKSDB_NAMESPACE::DBTestInPlaceUpdate::updateInPlaceNoAction; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Callback function requests no actions from db - ASSERT_OK(Put(1, "key", DummyString(1, 'a'))); - ASSERT_EQ(Get(1, "key"), "NOT_FOUND"); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestInPlaceUpdate, InPlaceUpdateAndSnapshot) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = true; - options.env = env_; - options.write_buffer_size = 100000; - options.allow_concurrent_memtable_write = false; - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Update key with values of smaller size, and - // run GetSnapshot and ReleaseSnapshot - int numValues = 2; - for (int i = numValues; i > 0; i--) { - const Snapshot* s = db_->GetSnapshot(); - ASSERT_EQ(nullptr, s); - std::string value = DummyString(i, 'a'); - ASSERT_OK(Put(1, "key", value)); - ASSERT_EQ(value, Get(1, "key")); - // release s (nullptr) - db_->ReleaseSnapshot(s); - } - - // Only 1 instance for that key. - validateNumberOfEntries(1, 1); - } while (ChangeCompactOptions()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_io_failure_test.cc b/db/db_io_failure_test.cc deleted file mode 100644 index e79272ea7..000000000 --- a/db/db_io_failure_test.cc +++ /dev/null @@ -1,589 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "port/stack_trace.h" -#include "test_util/testutil.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class DBIOFailureTest : public DBTestBase { - public: - DBIOFailureTest() : DBTestBase("db_io_failure_test", /*env_do_fsync=*/true) {} -}; - -// Check that number of files does not grow when writes are dropped -TEST_F(DBIOFailureTest, DropWrites) { - do { - Options options = CurrentOptions(); - options.env = env_; - options.paranoid_checks = false; - Reopen(options); - - ASSERT_OK(Put("foo", "v1")); - ASSERT_EQ("v1", Get("foo")); - Compact("a", "z"); - const size_t num_files = CountFiles(); - // Force out-of-space errors - env_->drop_writes_.store(true, std::memory_order_release); - env_->sleep_counter_.Reset(); - env_->SetMockSleep(); - for (int i = 0; i < 5; i++) { - if (option_config_ != kUniversalCompactionMultiLevel && - option_config_ != kUniversalSubcompactions) { - for (int level = 0; level < dbfull()->NumberLevels(); level++) { - if (level > 0 && level == dbfull()->NumberLevels() - 1) { - break; - } - Status s = - dbfull()->TEST_CompactRange(level, nullptr, nullptr, nullptr, - true /* disallow trivial move */); - ASSERT_TRUE(s.ok() || s.IsCorruption()); - } - } else { - Status s = - dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); - ASSERT_TRUE(s.ok() || s.IsCorruption()); - } - } - - std::string property_value; - ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value)); - ASSERT_EQ("5", property_value); - - env_->drop_writes_.store(false, std::memory_order_release); - const size_t count = CountFiles(); - ASSERT_LT(count, num_files + 3); - - // Check that compaction attempts slept after errors - // TODO @krad: Figure out why ASSERT_EQ 5 keeps failing in certain compiler - // versions - ASSERT_GE(env_->sleep_counter_.Read(), 4); - } while (ChangeCompactOptions()); -} - -// Check background error counter bumped on flush failures. -TEST_F(DBIOFailureTest, DropWritesFlush) { - do { - Options options = CurrentOptions(); - options.env = env_; - options.max_background_flushes = 1; - Reopen(options); - - ASSERT_OK(Put("foo", "v1")); - // Force out-of-space errors - env_->drop_writes_.store(true, std::memory_order_release); - - std::string property_value; - // Background error count is 0 now. - ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value)); - ASSERT_EQ("0", property_value); - - // ASSERT file is too short - ASSERT_TRUE(dbfull()->TEST_FlushMemTable(true).IsCorruption()); - - ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value)); - ASSERT_EQ("1", property_value); - - env_->drop_writes_.store(false, std::memory_order_release); - } while (ChangeCompactOptions()); -} - -// Check that CompactRange() returns failure if there is not enough space left -// on device -TEST_F(DBIOFailureTest, NoSpaceCompactRange) { - do { - Options options = CurrentOptions(); - options.env = env_; - options.disable_auto_compactions = true; - Reopen(options); - - // generate 5 tables - for (int i = 0; i < 5; ++i) { - ASSERT_OK(Put(Key(i), Key(i) + "v")); - ASSERT_OK(Flush()); - } - - // Force out-of-space errors - env_->no_space_.store(true, std::memory_order_release); - - Status s = dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow trivial move */); - ASSERT_TRUE(s.IsIOError()); - ASSERT_TRUE(s.IsNoSpace()); - - env_->no_space_.store(false, std::memory_order_release); - } while (ChangeCompactOptions()); -} - -TEST_F(DBIOFailureTest, NonWritableFileSystem) { - do { - Options options = CurrentOptions(); - options.write_buffer_size = 4096; - options.arena_block_size = 4096; - options.env = env_; - Reopen(options); - ASSERT_OK(Put("foo", "v1")); - env_->non_writeable_rate_.store(100); - std::string big(100000, 'x'); - int errors = 0; - for (int i = 0; i < 20; i++) { - if (!Put("foo", big).ok()) { - errors++; - env_->SleepForMicroseconds(100000); - } - } - ASSERT_GT(errors, 0); - env_->non_writeable_rate_.store(0); - } while (ChangeCompactOptions()); -} - -TEST_F(DBIOFailureTest, ManifestWriteError) { - // Test for the following problem: - // (a) Compaction produces file F - // (b) Log record containing F is written to MANIFEST file, but Sync() fails - // (c) GC deletes F - // (d) After reopening DB, reads fail since deleted F is named in log record - - // We iterate twice. In the second iteration, everything is the - // same except the log record never makes it to the MANIFEST file. - for (int iter = 0; iter < 2; iter++) { - std::atomic* error_type = (iter == 0) ? &env_->manifest_sync_error_ - : &env_->manifest_write_error_; - - // Insert foo=>bar mapping - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "bar")); - ASSERT_EQ("bar", Get("foo")); - - // Memtable compaction (will succeed) - ASSERT_OK(Flush()); - ASSERT_EQ("bar", Get("foo")); - const int last = 2; - MoveFilesToLevel(2); - ASSERT_EQ(NumTableFilesAtLevel(last), 1); // foo=>bar is now in last level - - // Merging compaction (will fail) - error_type->store(true, std::memory_order_release); - ASSERT_NOK( - dbfull()->TEST_CompactRange(last, nullptr, nullptr)); // Should fail - ASSERT_EQ("bar", Get("foo")); - - error_type->store(false, std::memory_order_release); - - // Since paranoid_checks=true, writes should fail - ASSERT_NOK(Put("foo2", "bar2")); - - // Recovery: should not lose data - ASSERT_EQ("bar", Get("foo")); - - // Try again with paranoid_checks=false - Close(); - options.paranoid_checks = false; - Reopen(options); - - // Merging compaction (will fail) - error_type->store(true, std::memory_order_release); - Status s = - dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail - if (iter == 0) { - ASSERT_OK(s); - } else { - ASSERT_TRUE(s.IsIOError()); - } - ASSERT_EQ("bar", Get("foo")); - - // Recovery: should not lose data - error_type->store(false, std::memory_order_release); - Reopen(options); - ASSERT_EQ("bar", Get("foo")); - - // Since paranoid_checks=false, writes should succeed - ASSERT_OK(Put("foo2", "bar2")); - ASSERT_EQ("bar", Get("foo")); - ASSERT_EQ("bar2", Get("foo2")); - } -} - -TEST_F(DBIOFailureTest, PutFailsParanoid) { - // Test the following: - // (a) A random put fails in paranoid mode (simulate by sync fail) - // (b) All other puts have to fail, even if writes would succeed - // (c) All of that should happen ONLY if paranoid_checks = true - - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo1", "bar1")); - // simulate error - env_->log_write_error_.store(true, std::memory_order_release); - ASSERT_NOK(Put(1, "foo2", "bar2")); - env_->log_write_error_.store(false, std::memory_order_release); - // the next put should fail, too - ASSERT_NOK(Put(1, "foo3", "bar3")); - // but we're still able to read - ASSERT_EQ("bar", Get(1, "foo")); - - // do the same thing with paranoid checks off - options.paranoid_checks = false; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo1", "bar1")); - // simulate error - env_->log_write_error_.store(true, std::memory_order_release); - ASSERT_NOK(Put(1, "foo2", "bar2")); - env_->log_write_error_.store(false, std::memory_order_release); - // the next put should NOT fail - ASSERT_OK(Put(1, "foo3", "bar3")); -} -#if !(defined NDEBUG) || !defined(OS_WIN) -TEST_F(DBIOFailureTest, FlushSstRangeSyncError) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - options.write_buffer_size = 256 * 1024 * 1024; - options.writable_file_max_buffer_size = 128 * 1024; - options.bytes_per_sync = 128 * 1024; - options.level0_file_num_compaction_trigger = 4; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(10)); - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - const char* io_error_msg = "range sync dummy error"; - std::atomic range_sync_called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SpecialEnv::SStableFile::RangeSync", [&](void* arg) { - if (range_sync_called.fetch_add(1) == 0) { - Status* st = static_cast(arg); - *st = Status::IOError(io_error_msg); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - std::string rnd_str = - rnd.RandomString(static_cast(options.bytes_per_sync / 2)); - std::string rnd_str_512kb = rnd.RandomString(512 * 1024); - - ASSERT_OK(Put(1, "foo", "bar")); - // First 1MB doesn't get range synced - ASSERT_OK(Put(1, "foo0_0", rnd_str_512kb)); - ASSERT_OK(Put(1, "foo0_1", rnd_str_512kb)); - ASSERT_OK(Put(1, "foo1_1", rnd_str)); - ASSERT_OK(Put(1, "foo1_2", rnd_str)); - ASSERT_OK(Put(1, "foo1_3", rnd_str)); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Put(1, "foo3_1", rnd_str)); - ASSERT_OK(Put(1, "foo3_2", rnd_str)); - ASSERT_OK(Put(1, "foo3_3", rnd_str)); - ASSERT_OK(Put(1, "foo4", "bar")); - Status s = dbfull()->TEST_WaitForFlushMemTable(handles_[1]); - ASSERT_TRUE(s.IsIOError()); - ASSERT_STREQ(s.getState(), io_error_msg); - - // Following writes should fail as flush failed. - ASSERT_NOK(Put(1, "foo2", "bar3")); - ASSERT_EQ("bar", Get(1, "foo")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_GE(1, range_sync_called.load()); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("bar", Get(1, "foo")); -} - -TEST_F(DBIOFailureTest, CompactSstRangeSyncError) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - options.write_buffer_size = 256 * 1024 * 1024; - options.writable_file_max_buffer_size = 128 * 1024; - options.bytes_per_sync = 128 * 1024; - options.level0_file_num_compaction_trigger = 2; - options.target_file_size_base = 256 * 1024 * 1024; - options.disable_auto_compactions = true; - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - std::string rnd_str = - rnd.RandomString(static_cast(options.bytes_per_sync / 2)); - std::string rnd_str_512kb = rnd.RandomString(512 * 1024); - - ASSERT_OK(Put(1, "foo", "bar")); - // First 1MB doesn't get range synced - ASSERT_OK(Put(1, "foo0_0", rnd_str_512kb)); - ASSERT_OK(Put(1, "foo0_1", rnd_str_512kb)); - ASSERT_OK(Put(1, "foo1_1", rnd_str)); - ASSERT_OK(Put(1, "foo1_2", rnd_str)); - ASSERT_OK(Put(1, "foo1_3", rnd_str)); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo3_1", rnd_str)); - ASSERT_OK(Put(1, "foo3_2", rnd_str)); - ASSERT_OK(Put(1, "foo3_3", rnd_str)); - ASSERT_OK(Put(1, "foo4", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - - const char* io_error_msg = "range sync dummy error"; - std::atomic range_sync_called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SpecialEnv::SStableFile::RangeSync", [&](void* arg) { - if (range_sync_called.fetch_add(1) == 0) { - Status* st = static_cast(arg); - *st = Status::IOError(io_error_msg); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->SetOptions(handles_[1], - { - {"disable_auto_compactions", "false"}, - })); - Status s = dbfull()->TEST_WaitForCompact(); - ASSERT_TRUE(s.IsIOError()); - ASSERT_STREQ(s.getState(), io_error_msg); - - // Following writes should fail as flush failed. - ASSERT_NOK(Put(1, "foo2", "bar3")); - ASSERT_EQ("bar", Get(1, "foo")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_GE(1, range_sync_called.load()); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("bar", Get(1, "foo")); -} - -TEST_F(DBIOFailureTest, FlushSstCloseError) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - options.level0_file_num_compaction_trigger = 4; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(2)); - - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - const char* io_error_msg = "close dummy error"; - std::atomic close_called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SpecialEnv::SStableFile::Close", [&](void* arg) { - if (close_called.fetch_add(1) == 0) { - Status* st = static_cast(arg); - *st = Status::IOError(io_error_msg); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo1", "bar1")); - ASSERT_OK(Put(1, "foo", "bar2")); - Status s = dbfull()->TEST_WaitForFlushMemTable(handles_[1]); - ASSERT_TRUE(s.IsIOError()); - ASSERT_STREQ(s.getState(), io_error_msg); - - // Following writes should fail as flush failed. - ASSERT_NOK(Put(1, "foo2", "bar3")); - ASSERT_EQ("bar2", Get(1, "foo")); - ASSERT_EQ("bar1", Get(1, "foo1")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("bar2", Get(1, "foo")); - ASSERT_EQ("bar1", Get(1, "foo1")); -} - -TEST_F(DBIOFailureTest, CompactionSstCloseError) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - options.level0_file_num_compaction_trigger = 2; - options.disable_auto_compactions = true; - - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "bar2")); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "bar3")); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - const char* io_error_msg = "close dummy error"; - std::atomic close_called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SpecialEnv::SStableFile::Close", [&](void* arg) { - if (close_called.fetch_add(1) == 0) { - Status* st = static_cast(arg); - *st = Status::IOError(io_error_msg); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->SetOptions(handles_[1], - { - {"disable_auto_compactions", "false"}, - })); - Status s = dbfull()->TEST_WaitForCompact(); - ASSERT_TRUE(s.IsIOError()); - ASSERT_STREQ(s.getState(), io_error_msg); - - // Following writes should fail as compaction failed. - ASSERT_NOK(Put(1, "foo2", "bar3")); - ASSERT_EQ("bar3", Get(1, "foo")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("bar3", Get(1, "foo")); -} - -TEST_F(DBIOFailureTest, FlushSstSyncError) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - options.use_fsync = false; - options.level0_file_num_compaction_trigger = 4; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(2)); - - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - const char* io_error_msg = "sync dummy error"; - std::atomic sync_called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SpecialEnv::SStableFile::Sync", [&](void* arg) { - if (sync_called.fetch_add(1) == 0) { - Status* st = static_cast(arg); - *st = Status::IOError(io_error_msg); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo1", "bar1")); - ASSERT_OK(Put(1, "foo", "bar2")); - Status s = dbfull()->TEST_WaitForFlushMemTable(handles_[1]); - ASSERT_TRUE(s.IsIOError()); - ASSERT_STREQ(s.getState(), io_error_msg); - - // Following writes should fail as flush failed. - ASSERT_NOK(Put(1, "foo2", "bar3")); - ASSERT_EQ("bar2", Get(1, "foo")); - ASSERT_EQ("bar1", Get(1, "foo1")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("bar2", Get(1, "foo")); - ASSERT_EQ("bar1", Get(1, "foo1")); -} - -TEST_F(DBIOFailureTest, CompactionSstSyncError) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.error_if_exists = false; - options.paranoid_checks = true; - options.level0_file_num_compaction_trigger = 2; - options.disable_auto_compactions = true; - options.use_fsync = false; - - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "bar2")); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "bar3")); - ASSERT_OK(Put(1, "foo2", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - const char* io_error_msg = "sync dummy error"; - std::atomic sync_called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SpecialEnv::SStableFile::Sync", [&](void* arg) { - if (sync_called.fetch_add(1) == 0) { - Status* st = static_cast(arg); - *st = Status::IOError(io_error_msg); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->SetOptions(handles_[1], - { - {"disable_auto_compactions", "false"}, - })); - Status s = dbfull()->TEST_WaitForCompact(); - ASSERT_TRUE(s.IsIOError()); - ASSERT_STREQ(s.getState(), io_error_msg); - - // Following writes should fail as compaction failed. - ASSERT_NOK(Put(1, "foo2", "bar3")); - ASSERT_EQ("bar3", Get(1, "foo")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("bar3", Get(1, "foo")); -} -#endif // !(defined NDEBUG) || !defined(OS_WIN) -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_iter_stress_test.cc b/db/db_iter_stress_test.cc deleted file mode 100644 index 872f7e6bd..000000000 --- a/db/db_iter_stress_test.cc +++ /dev/null @@ -1,658 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/db_iter.h" -#include "db/dbformat.h" -#include "rocksdb/comparator.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" -#include "test_util/testharness.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -#ifdef GFLAGS - -#include "util/gflags_compat.h" - -using GFLAGS_NAMESPACE::ParseCommandLineFlags; - -DEFINE_bool(verbose, false, - "Print huge, detailed trace. Intended for debugging failures."); - -#else - -void ParseCommandLineFlags(int*, char***, bool) {} -bool FLAGS_verbose = false; - -#endif - -namespace ROCKSDB_NAMESPACE { - -class DBIteratorStressTest : public testing::Test { - public: - Env* env_; - - DBIteratorStressTest() : env_(Env::Default()) {} -}; - -namespace { - -struct Entry { - std::string key; - ValueType type; // kTypeValue, kTypeDeletion, kTypeMerge - uint64_t sequence; - std::string ikey; // internal key, made from `key`, `sequence` and `type` - std::string value; - // If false, we'll pretend that this entry doesn't exist. - bool visible = true; - - bool operator<(const Entry& e) const { - if (key != e.key) return key < e.key; - return std::tie(sequence, type) > std::tie(e.sequence, e.type); - } -}; - -struct Data { - std::vector entries; - - // Indices in `entries` with `visible` = false. - std::vector hidden; - // Keys of entries whose `visible` changed since the last seek of iterators. - std::set recently_touched_keys; -}; - -struct StressTestIterator : public InternalIterator { - Data* data; - Random64* rnd; - InternalKeyComparator cmp; - - // Each operation will return error with this probability... - double error_probability = 0; - // ... and add/remove entries with this probability. - double mutation_probability = 0; - // The probability of adding vs removing entries will be chosen so that the - // amount of removed entries stays somewhat close to this number. - double target_hidden_fraction = 0; - // If true, print all mutations to stdout for debugging. - bool trace = false; - - int iter = -1; - Status status_; - - StressTestIterator(Data* _data, Random64* _rnd, const Comparator* _cmp) - : data(_data), rnd(_rnd), cmp(_cmp) {} - - bool Valid() const override { - if (iter >= 0 && iter < (int)data->entries.size()) { - assert(status_.ok()); - return true; - } - return false; - } - - Status status() const override { return status_; } - - bool MaybeFail() { - if (rnd->Next() >= - static_cast(std::numeric_limits::max()) * - error_probability) { - return false; - } - if (rnd->Next() % 2) { - status_ = Status::Incomplete("test"); - } else { - status_ = Status::IOError("test"); - } - if (trace) { - std::cout << "injecting " << status_.ToString() << std::endl; - } - iter = -1; - return true; - } - - void MaybeMutate() { - if (rnd->Next() >= - static_cast(std::numeric_limits::max()) * - mutation_probability) { - return; - } - do { - // If too many entries are hidden, hide less, otherwise hide more. - double hide_probability = - data->hidden.size() > data->entries.size() * target_hidden_fraction - ? 1. / 3 - : 2. / 3; - if (data->hidden.empty()) { - hide_probability = 1; - } - bool do_hide = rnd->Next() < - static_cast(std::numeric_limits::max()) * - hide_probability; - if (do_hide) { - // Hide a random entry. - size_t idx = rnd->Next() % data->entries.size(); - Entry& e = data->entries[idx]; - if (e.visible) { - if (trace) { - std::cout << "hiding idx " << idx << std::endl; - } - e.visible = false; - data->hidden.push_back(idx); - data->recently_touched_keys.insert(e.key); - } else { - // Already hidden. Let's go unhide something instead, just because - // it's easy and it doesn't really matter what we do. - do_hide = false; - } - } - if (!do_hide) { - // Unhide a random entry. - size_t hi = rnd->Next() % data->hidden.size(); - size_t idx = data->hidden[hi]; - if (trace) { - std::cout << "unhiding idx " << idx << std::endl; - } - Entry& e = data->entries[idx]; - assert(!e.visible); - e.visible = true; - data->hidden[hi] = data->hidden.back(); - data->hidden.pop_back(); - data->recently_touched_keys.insert(e.key); - } - } while (rnd->Next() % 3 != 0); // do 3 mutations on average - } - - void SkipForward() { - while (iter < (int)data->entries.size() && !data->entries[iter].visible) { - ++iter; - } - } - void SkipBackward() { - while (iter >= 0 && !data->entries[iter].visible) { - --iter; - } - } - - void SeekToFirst() override { - if (MaybeFail()) return; - MaybeMutate(); - - status_ = Status::OK(); - iter = 0; - SkipForward(); - } - void SeekToLast() override { - if (MaybeFail()) return; - MaybeMutate(); - - status_ = Status::OK(); - iter = (int)data->entries.size() - 1; - SkipBackward(); - } - - void Seek(const Slice& target) override { - if (MaybeFail()) return; - MaybeMutate(); - - status_ = Status::OK(); - // Binary search. - auto it = std::partition_point( - data->entries.begin(), data->entries.end(), - [&](const Entry& e) { return cmp.Compare(e.ikey, target) < 0; }); - iter = (int)(it - data->entries.begin()); - SkipForward(); - } - void SeekForPrev(const Slice& target) override { - if (MaybeFail()) return; - MaybeMutate(); - - status_ = Status::OK(); - // Binary search. - auto it = std::partition_point( - data->entries.begin(), data->entries.end(), - [&](const Entry& e) { return cmp.Compare(e.ikey, target) <= 0; }); - iter = (int)(it - data->entries.begin()); - --iter; - SkipBackward(); - } - - void Next() override { - assert(Valid()); - if (MaybeFail()) return; - MaybeMutate(); - ++iter; - SkipForward(); - } - void Prev() override { - assert(Valid()); - if (MaybeFail()) return; - MaybeMutate(); - --iter; - SkipBackward(); - } - - Slice key() const override { - assert(Valid()); - return data->entries[iter].ikey; - } - Slice value() const override { - assert(Valid()); - return data->entries[iter].value; - } - - bool IsKeyPinned() const override { return true; } - bool IsValuePinned() const override { return true; } -}; - -// A small reimplementation of DBIter, supporting only some of the features, -// and doing everything in O(log n). -// Skips all keys that are in recently_touched_keys. -struct ReferenceIterator { - Data* data; - uint64_t sequence; // ignore entries with sequence number below this - - bool valid = false; - std::string key; - std::string value; - - ReferenceIterator(Data* _data, uint64_t _sequence) - : data(_data), sequence(_sequence) {} - - bool Valid() const { return valid; } - - // Finds the first entry with key - // greater/less/greater-or-equal/less-or-equal than `key`, depending on - // arguments: if `skip`, inequality is strict; if `forward`, it's - // greater/greater-or-equal, otherwise less/less-or-equal. - // Sets `key` to the result. - // If no such key exists, returns false. Doesn't check `visible`. - bool FindNextKey(bool skip, bool forward) { - valid = false; - auto it = std::partition_point(data->entries.begin(), data->entries.end(), - [&](const Entry& e) { - if (forward != skip) { - return e.key < key; - } else { - return e.key <= key; - } - }); - if (forward) { - if (it != data->entries.end()) { - key = it->key; - return true; - } - } else { - if (it != data->entries.begin()) { - --it; - key = it->key; - return true; - } - } - return false; - } - - bool FindValueForCurrentKey() { - if (data->recently_touched_keys.count(key)) { - return false; - } - - // Find the first entry for the key. The caller promises that it exists. - auto it = std::partition_point(data->entries.begin(), data->entries.end(), - [&](const Entry& e) { - if (e.key != key) { - return e.key < key; - } - return e.sequence > sequence; - }); - - // Find the first visible entry. - for (;; ++it) { - if (it == data->entries.end()) { - return false; - } - Entry& e = *it; - if (e.key != key) { - return false; - } - assert(e.sequence <= sequence); - if (!e.visible) continue; - if (e.type == kTypeDeletion) { - return false; - } - if (e.type == kTypeValue) { - value = e.value; - valid = true; - return true; - } - assert(e.type == kTypeMerge); - break; - } - - // Collect merge operands. - std::vector operands; - for (; it != data->entries.end(); ++it) { - Entry& e = *it; - if (e.key != key) { - break; - } - assert(e.sequence <= sequence); - if (!e.visible) continue; - if (e.type == kTypeDeletion) { - break; - } - operands.push_back(e.value); - if (e.type == kTypeValue) { - break; - } - } - - // Do a merge. - value = operands.back().ToString(); - for (int i = (int)operands.size() - 2; i >= 0; --i) { - value.append(","); - value.append(operands[i].data(), operands[i].size()); - } - - valid = true; - return true; - } - - // Start at `key` and move until we encounter a valid value. - // `forward` defines the direction of movement. - // If `skip` is true, we're looking for key not equal to `key`. - void DoTheThing(bool skip, bool forward) { - while (FindNextKey(skip, forward) && !FindValueForCurrentKey()) { - skip = true; - } - } - - void Seek(const Slice& target) { - key = target.ToString(); - DoTheThing(false, true); - } - void SeekForPrev(const Slice& target) { - key = target.ToString(); - DoTheThing(false, false); - } - void SeekToFirst() { Seek(""); } - void SeekToLast() { - key = data->entries.back().key; - DoTheThing(false, false); - } - void Next() { - assert(Valid()); - DoTheThing(true, true); - } - void Prev() { - assert(Valid()); - DoTheThing(true, false); - } -}; - -} // anonymous namespace - -// Use an internal iterator that sometimes returns errors and sometimes -// adds/removes entries on the fly. Do random operations on a DBIter and -// check results. -// TODO: can be improved for more coverage: -// * Override IsKeyPinned() and IsValuePinned() to actually use -// PinnedIteratorManager and check that there's no use-after free. -// * Try different combinations of prefix_extractor, total_order_seek, -// prefix_same_as_start, iterate_lower_bound, iterate_upper_bound. -TEST_F(DBIteratorStressTest, StressTest) { - // We use a deterministic RNG, and everything happens in a single thread. - Random64 rnd(826909345792864532ll); - - auto gen_key = [&](int max_key) { - assert(max_key > 0); - int len = 0; - int a = max_key; - while (a) { - a /= 10; - ++len; - } - std::string s = std::to_string(rnd.Next() % static_cast(max_key)); - s.insert(0, len - (int)s.size(), '0'); - return s; - }; - - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - ReadOptions ropt; - - size_t num_matching = 0; - size_t num_at_end = 0; - size_t num_not_ok = 0; - size_t num_recently_removed = 0; - - // Number of iterations for each combination of parameters - // (there are ~250 of those). - // Tweak this to change the test run time. - // As of the time of writing, the test takes ~4 seconds for value of 5000. - const int num_iterations = 5000; - // Enable this to print all the operations for debugging. - bool trace = FLAGS_verbose; - - for (int num_entries : {5, 10, 100}) { - for (double key_space : {0.1, 1.0, 3.0}) { - for (ValueType prevalent_entry_type : - {kTypeValue, kTypeDeletion, kTypeMerge}) { - for (double error_probability : {0.01, 0.1}) { - for (double mutation_probability : {0.01, 0.5}) { - for (double target_hidden_fraction : {0.1, 0.5}) { - std::string trace_str = - "entries: " + std::to_string(num_entries) + - ", key_space: " + std::to_string(key_space) + - ", error_probability: " + std::to_string(error_probability) + - ", mutation_probability: " + - std::to_string(mutation_probability) + - ", target_hidden_fraction: " + - std::to_string(target_hidden_fraction); - SCOPED_TRACE(trace_str); - if (trace) { - std::cout << trace_str << std::endl; - } - - // Generate data. - Data data; - int max_key = (int)(num_entries * key_space) + 1; - for (int i = 0; i < num_entries; ++i) { - Entry e; - e.key = gen_key(max_key); - if (rnd.Next() % 10 != 0) { - e.type = prevalent_entry_type; - } else { - const ValueType types[] = {kTypeValue, kTypeDeletion, - kTypeMerge}; - e.type = - types[rnd.Next() % (sizeof(types) / sizeof(types[0]))]; - } - e.sequence = i; - e.value = "v" + std::to_string(i); - ParsedInternalKey internal_key(e.key, e.sequence, e.type); - AppendInternalKey(&e.ikey, internal_key); - - data.entries.push_back(e); - } - std::sort(data.entries.begin(), data.entries.end()); - if (trace) { - std::cout << "entries:"; - for (size_t i = 0; i < data.entries.size(); ++i) { - Entry& e = data.entries[i]; - std::cout << "\n idx " << i << ": \"" << e.key << "\": \"" - << e.value << "\" seq: " << e.sequence << " type: " - << (e.type == kTypeValue ? "val" - : e.type == kTypeDeletion ? "del" - : "merge"); - } - std::cout << std::endl; - } - - std::unique_ptr db_iter; - std::unique_ptr ref_iter; - for (int iteration = 0; iteration < num_iterations; ++iteration) { - SCOPED_TRACE(iteration); - // Create a new iterator every ~30 operations. - if (db_iter == nullptr || rnd.Next() % 30 == 0) { - uint64_t sequence = rnd.Next() % (data.entries.size() + 2); - ref_iter.reset(new ReferenceIterator(&data, sequence)); - if (trace) { - std::cout << "new iterator, seq: " << sequence << std::endl; - } - - auto internal_iter = - new StressTestIterator(&data, &rnd, BytewiseComparator()); - internal_iter->error_probability = error_probability; - internal_iter->mutation_probability = mutation_probability; - internal_iter->target_hidden_fraction = - target_hidden_fraction; - internal_iter->trace = trace; - db_iter.reset(NewDBIterator( - env_, ropt, ImmutableOptions(options), - MutableCFOptions(options), BytewiseComparator(), - internal_iter, nullptr /* version */, sequence, - options.max_sequential_skip_in_iterations, - nullptr /*read_callback*/)); - } - - // Do a random operation. It's important to do it on ref_it - // later than on db_iter to make sure ref_it sees the correct - // recently_touched_keys. - std::string old_key; - bool forward = rnd.Next() % 2 > 0; - // Do Next()/Prev() ~90% of the time. - bool seek = !ref_iter->Valid() || rnd.Next() % 10 == 0; - if (trace) { - std::cout << iteration << ": "; - } - - if (!seek) { - assert(db_iter->Valid()); - old_key = ref_iter->key; - if (trace) { - std::cout << (forward ? "Next" : "Prev") << std::endl; - } - - if (forward) { - db_iter->Next(); - ref_iter->Next(); - } else { - db_iter->Prev(); - ref_iter->Prev(); - } - } else { - data.recently_touched_keys.clear(); - // Do SeekToFirst less often than Seek. - if (rnd.Next() % 4 == 0) { - if (trace) { - std::cout << (forward ? "SeekToFirst" : "SeekToLast") - << std::endl; - } - - if (forward) { - old_key = ""; - db_iter->SeekToFirst(); - ref_iter->SeekToFirst(); - } else { - old_key = data.entries.back().key; - db_iter->SeekToLast(); - ref_iter->SeekToLast(); - } - } else { - old_key = gen_key(max_key); - if (trace) { - std::cout << (forward ? "Seek" : "SeekForPrev") << " \"" - << old_key << '"' << std::endl; - } - if (forward) { - db_iter->Seek(old_key); - ref_iter->Seek(old_key); - } else { - db_iter->SeekForPrev(old_key); - ref_iter->SeekForPrev(old_key); - } - } - } - - // Check the result. - if (db_iter->Valid()) { - ASSERT_TRUE(db_iter->status().ok()); - if (data.recently_touched_keys.count( - db_iter->key().ToString())) { - // Ended on a key that may have been mutated during the - // operation. Reference iterator skips such keys, so we - // can't check the exact result. - - // Check that the key moved in the right direction. - if (forward) { - if (seek) - ASSERT_GE(db_iter->key().ToString(), old_key); - else - ASSERT_GT(db_iter->key().ToString(), old_key); - } else { - if (seek) - ASSERT_LE(db_iter->key().ToString(), old_key); - else - ASSERT_LT(db_iter->key().ToString(), old_key); - } - - if (ref_iter->Valid()) { - // Check that DBIter didn't miss any non-mutated key. - if (forward) { - ASSERT_LT(db_iter->key().ToString(), ref_iter->key); - } else { - ASSERT_GT(db_iter->key().ToString(), ref_iter->key); - } - } - // Tell the next iteration of the loop to reseek the - // iterators. - ref_iter->valid = false; - - ++num_recently_removed; - } else { - ASSERT_TRUE(ref_iter->Valid()); - ASSERT_EQ(ref_iter->key, db_iter->key().ToString()); - ASSERT_EQ(ref_iter->value, db_iter->value()); - ++num_matching; - } - } else if (db_iter->status().ok()) { - ASSERT_FALSE(ref_iter->Valid()); - ++num_at_end; - } else { - // Non-ok status. Nothing to check here. - // Tell the next iteration of the loop to reseek the - // iterators. - ref_iter->valid = false; - ++num_not_ok; - } - } - } - } - } - } - } - } - - // Check that all cases were hit many times. - EXPECT_GT(num_matching, 10000); - EXPECT_GT(num_at_end, 10000); - EXPECT_GT(num_not_ok, 10000); - EXPECT_GT(num_recently_removed, 10000); - - std::cout << "stats:\n exact matches: " << num_matching - << "\n end reached: " << num_at_end - << "\n non-ok status: " << num_not_ok - << "\n mutated on the fly: " << num_recently_removed << std::endl; -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - ParseCommandLineFlags(&argc, &argv, true); - return RUN_ALL_TESTS(); -} diff --git a/db/db_iter_test.cc b/db/db_iter_test.cc deleted file mode 100644 index 65290bfad..000000000 --- a/db/db_iter_test.cc +++ /dev/null @@ -1,3195 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/db_iter.h" - -#include -#include -#include -#include - -#include "db/dbformat.h" -#include "rocksdb/comparator.h" -#include "rocksdb/options.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/slice.h" -#include "rocksdb/statistics.h" -#include "table/iterator_wrapper.h" -#include "table/merging_iterator.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -static uint64_t TestGetTickerCount(const Options& options, - Tickers ticker_type) { - return options.statistics->getTickerCount(ticker_type); -} - -class TestIterator : public InternalIterator { - public: - explicit TestIterator(const Comparator* comparator) - : initialized_(false), - valid_(false), - sequence_number_(0), - iter_(0), - cmp(comparator) { - data_.reserve(16); - } - - void AddPut(std::string argkey, std::string argvalue) { - Add(argkey, kTypeValue, argvalue); - } - - void AddDeletion(std::string argkey) { - Add(argkey, kTypeDeletion, std::string()); - } - - void AddSingleDeletion(std::string argkey) { - Add(argkey, kTypeSingleDeletion, std::string()); - } - - void AddMerge(std::string argkey, std::string argvalue) { - Add(argkey, kTypeMerge, argvalue); - } - - void Add(std::string argkey, ValueType type, std::string argvalue) { - Add(argkey, type, argvalue, sequence_number_++); - } - - void Add(std::string argkey, ValueType type, std::string argvalue, - size_t seq_num, bool update_iter = false) { - valid_ = true; - ParsedInternalKey internal_key(argkey, seq_num, type); - data_.push_back( - std::pair(std::string(), argvalue)); - AppendInternalKey(&data_.back().first, internal_key); - if (update_iter && valid_ && cmp.Compare(data_.back().first, key()) < 0) { - // insert a key smaller than current key - Finish(); - // data_[iter_] is not anymore the current element of the iterator. - // Increment it to reposition it to the right position. - iter_++; - } - } - - // should be called before operations with iterator - void Finish() { - initialized_ = true; - std::sort(data_.begin(), data_.end(), - [this](std::pair a, - std::pair b) { - return (cmp.Compare(a.first, b.first) < 0); - }); - } - - // Removes the key from the set of keys over which this iterator iterates. - // Not to be confused with AddDeletion(). - // If the iterator is currently positioned on this key, the deletion will - // apply next time the iterator moves. - // Used for simulating ForwardIterator updating to a new version that doesn't - // have some of the keys (e.g. after compaction with a filter). - void Vanish(std::string _key) { - if (valid_ && data_[iter_].first == _key) { - delete_current_ = true; - return; - } - for (auto it = data_.begin(); it != data_.end(); ++it) { - ParsedInternalKey ikey; - Status pik_status = - ParseInternalKey(it->first, &ikey, true /* log_err_key */); - pik_status.PermitUncheckedError(); - assert(pik_status.ok()); - if (!pik_status.ok() || ikey.user_key != _key) { - continue; - } - if (valid_ && data_.begin() + iter_ > it) { - --iter_; - } - data_.erase(it); - return; - } - assert(false); - } - - // Number of operations done on this iterator since construction. - size_t steps() const { return steps_; } - - bool Valid() const override { - assert(initialized_); - return valid_; - } - - void SeekToFirst() override { - assert(initialized_); - ++steps_; - DeleteCurrentIfNeeded(); - valid_ = (data_.size() > 0); - iter_ = 0; - } - - void SeekToLast() override { - assert(initialized_); - ++steps_; - DeleteCurrentIfNeeded(); - valid_ = (data_.size() > 0); - iter_ = data_.size() - 1; - } - - void Seek(const Slice& target) override { - assert(initialized_); - SeekToFirst(); - ++steps_; - if (!valid_) { - return; - } - while (iter_ < data_.size() && - (cmp.Compare(data_[iter_].first, target) < 0)) { - ++iter_; - } - - if (iter_ == data_.size()) { - valid_ = false; - } - } - - void SeekForPrev(const Slice& target) override { - assert(initialized_); - DeleteCurrentIfNeeded(); - SeekForPrevImpl(target, &cmp); - } - - void Next() override { - assert(initialized_); - assert(valid_); - assert(iter_ < data_.size()); - - ++steps_; - if (delete_current_) { - DeleteCurrentIfNeeded(); - } else { - ++iter_; - } - valid_ = iter_ < data_.size(); - } - - void Prev() override { - assert(initialized_); - assert(valid_); - assert(iter_ < data_.size()); - - ++steps_; - DeleteCurrentIfNeeded(); - if (iter_ == 0) { - valid_ = false; - } else { - --iter_; - } - } - - Slice key() const override { - assert(initialized_); - return data_[iter_].first; - } - - Slice value() const override { - assert(initialized_); - return data_[iter_].second; - } - - Status status() const override { - assert(initialized_); - return Status::OK(); - } - - bool IsKeyPinned() const override { return true; } - bool IsValuePinned() const override { return true; } - - private: - bool initialized_; - bool valid_; - size_t sequence_number_; - size_t iter_; - size_t steps_ = 0; - - InternalKeyComparator cmp; - std::vector> data_; - bool delete_current_ = false; - - void DeleteCurrentIfNeeded() { - if (!delete_current_) { - return; - } - data_.erase(data_.begin() + iter_); - delete_current_ = false; - } -}; - -class DBIteratorTest : public testing::Test { - public: - Env* env_; - - DBIteratorTest() : env_(Env::Default()) {} -}; - -TEST_F(DBIteratorTest, DBIteratorPrevNext) { - Options options; - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddDeletion("a"); - internal_iter->AddDeletion("a"); - internal_iter->AddDeletion("a"); - internal_iter->AddDeletion("a"); - internal_iter->AddPut("a", "val_a"); - - internal_iter->AddPut("b", "val_b"); - internal_iter->Finish(); - - ReadOptions ro; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - } - // Test to check the SeekToLast() with iterate_upper_bound not set - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - ReadOptions ro; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - } - - // Test to check the SeekToLast() with iterate_upper_bound set - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("d", "val_d"); - internal_iter->AddPut("e", "val_e"); - internal_iter->AddPut("f", "val_f"); - internal_iter->Finish(); - - Slice prefix("d"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - } - // Test to check the SeekToLast() iterate_upper_bound set to a key that - // is not Put yet - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("d", "val_d"); - internal_iter->Finish(); - - Slice prefix("z"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - } - // Test to check the SeekToLast() with iterate_upper_bound set to the - // first key - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("b", "val_b"); - internal_iter->Finish(); - - Slice prefix("a"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - // Test case to check SeekToLast with iterate_upper_bound set - // (same key put may times - SeekToLast should start with the - // maximum sequence id of the upper bound) - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - Slice prefix("c"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 7 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - SetPerfLevel(kEnableCount); - ASSERT_TRUE(GetPerfLevel() == kEnableCount); - - get_perf_context()->Reset(); - db_iter->SeekToLast(); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(static_cast(get_perf_context()->internal_key_skipped_count), - 1); - ASSERT_EQ(db_iter->key().ToString(), "b"); - - SetPerfLevel(kDisable); - } - // Test to check the SeekToLast() with the iterate_upper_bound set - // (Checking the value of the key which has sequence ids greater than - // and less that the iterator's sequence id) - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - - internal_iter->AddPut("a", "val_a1"); - internal_iter->AddPut("a", "val_a2"); - internal_iter->AddPut("b", "val_b1"); - internal_iter->AddPut("c", "val_c1"); - internal_iter->AddPut("c", "val_c2"); - internal_iter->AddPut("c", "val_c3"); - internal_iter->AddPut("b", "val_b2"); - internal_iter->AddPut("d", "val_d1"); - internal_iter->Finish(); - - Slice prefix("c"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 4 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b1"); - } - - // Test to check the SeekToLast() with the iterate_upper_bound set to the - // key that is deleted - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - Slice prefix("a"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - // Test to check the SeekToLast() with the iterate_upper_bound set - // (Deletion cases) - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - Slice prefix("c"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - } - // Test to check the SeekToLast() with iterate_upper_bound set - // (Deletion cases - Lot of internal keys after the upper_bound - // is deleted) - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddDeletion("c"); - internal_iter->AddDeletion("d"); - internal_iter->AddDeletion("e"); - internal_iter->AddDeletion("f"); - internal_iter->AddDeletion("g"); - internal_iter->AddDeletion("h"); - internal_iter->Finish(); - - Slice prefix("c"); - - ReadOptions ro; - ro.iterate_upper_bound = &prefix; - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 7 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - SetPerfLevel(kEnableCount); - ASSERT_TRUE(GetPerfLevel() == kEnableCount); - - get_perf_context()->Reset(); - db_iter->SeekToLast(); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ( - static_cast(get_perf_context()->internal_delete_skipped_count), 0); - ASSERT_EQ(db_iter->key().ToString(), "b"); - - SetPerfLevel(kDisable); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddDeletion("a"); - internal_iter->AddDeletion("a"); - internal_iter->AddDeletion("a"); - internal_iter->AddDeletion("a"); - internal_iter->AddPut("a", "val_a"); - - internal_iter->AddPut("b", "val_b"); - internal_iter->Finish(); - - ReadOptions ro; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->Finish(); - - ReadOptions ro; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b"); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("a", "val_a"); - - internal_iter->AddPut("b", "val_b"); - - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - ReadOptions ro; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val_b"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - } -} - -TEST_F(DBIteratorTest, DBIteratorEmpty) { - Options options; - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - ReadOptions ro; - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 0 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 0 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } -} - -TEST_F(DBIteratorTest, DBIteratorUseSkipCountSkips) { - ReadOptions ro; - Options options; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - for (size_t i = 0; i < 200; ++i) { - internal_iter->AddPut("a", "a"); - internal_iter->AddPut("b", "b"); - internal_iter->AddPut("c", "c"); - } - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 2 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "c"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1u); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "b"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2u); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "a"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 3u); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 3u); -} - -TEST_F(DBIteratorTest, DBIteratorUseSkip) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - - { - for (size_t i = 0; i < 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("b", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - for (size_t k = 0; k < 200; ++k) { - internal_iter->AddPut("c", std::to_string(k)); - } - internal_iter->Finish(); - - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, i + 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), std::to_string(i)); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_2"); - db_iter->Prev(); - - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - } - - { - for (size_t i = 0; i < 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("b", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - for (size_t k = 0; k < 200; ++k) { - internal_iter->AddDeletion("c"); - } - internal_iter->AddPut("c", "200"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, i + 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_2"); - db_iter->Prev(); - - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("b", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - for (size_t i = 0; i < 200; ++i) { - internal_iter->AddDeletion("c"); - } - internal_iter->AddPut("c", "200"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 202 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "200"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_2"); - db_iter->Prev(); - - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - } - - { - for (size_t i = 0; i < 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - for (size_t k = 0; k < 200; ++k) { - internal_iter->AddDeletion("c"); - } - internal_iter->AddPut("c", "200"); - internal_iter->Finish(); - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, i /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - - db_iter->SeekToFirst(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - for (size_t i = 0; i < 200; ++i) { - internal_iter->AddDeletion("c"); - } - internal_iter->AddPut("c", "200"); - internal_iter->Finish(); - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 200 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "200"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "200"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - - { - for (size_t i = 0; i < 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("b", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - for (size_t k = 0; k < 200; ++k) { - internal_iter->AddPut("d", std::to_string(k)); - } - - for (size_t k = 0; k < 200; ++k) { - internal_iter->AddPut("c", std::to_string(k)); - } - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, i + 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), std::to_string(i)); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_2"); - db_iter->Prev(); - - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - } - - { - for (size_t i = 0; i < 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("b", "b"); - internal_iter->AddMerge("a", "a"); - for (size_t k = 0; k < 200; ++k) { - internal_iter->AddMerge("c", std::to_string(k)); - } - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, i + 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - std::string merge_result = "0"; - for (size_t j = 1; j <= i; ++j) { - merge_result += "," + std::to_string(j); - } - ASSERT_EQ(db_iter->value().ToString(), merge_result); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "b"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "a"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - } -} - -TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { - Options options; - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - ReadOptions ro; - - // Basic test case ... Make sure explicityly passing the default value works. - // Skipping internal keys is disabled by default, when the value is 0. - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddDeletion("c"); - internal_iter->AddPut("d", "val_d"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 0; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "val_d"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().ok()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "val_d"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } - - // Test to make sure that the request will *not* fail as incomplete if - // num_internal_keys_skipped is *equal* to max_skippable_internal_keys - // threshold. (It will fail as incomplete only when the threshold is - // exceeded.) - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().ok()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Prev(); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().ok()); - } - - // Fail the request as incomplete when num_internal_keys_skipped > - // max_skippable_internal_keys - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } - - // Test that the num_internal_keys_skipped counter resets after a successful - // read. - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddDeletion("d"); - internal_iter->AddDeletion("d"); - internal_iter->AddDeletion("d"); - internal_iter->AddPut("e", "val_e"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Next(); // num_internal_keys_skipped counter resets here. - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } - - // Test that the num_internal_keys_skipped counter resets after a successful - // read. - // Reverse direction - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddDeletion("d"); - internal_iter->AddDeletion("d"); - internal_iter->AddPut("e", "val_e"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "e"); - ASSERT_EQ(db_iter->value().ToString(), "val_e"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Prev(); // num_internal_keys_skipped counter resets here. - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } - - // Test that skipping separate keys is handled - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("c"); - internal_iter->AddDeletion("d"); - internal_iter->AddPut("e", "val_e"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "e"); - ASSERT_EQ(db_iter->value().ToString(), "val_e"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } - - // Test if alternating puts and deletes of the same key are handled correctly. - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddPut("b", "val_b"); - internal_iter->AddDeletion("b"); - internal_iter->AddPut("c", "val_c"); - internal_iter->AddDeletion("c"); - internal_iter->AddPut("d", "val_d"); - internal_iter->AddDeletion("d"); - internal_iter->AddPut("e", "val_e"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "e"); - ASSERT_EQ(db_iter->value().ToString(), "val_e"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } - - // Test for large number of skippable internal keys with *default* - // max_sequential_skip_in_iterations. - { - for (size_t i = 1; i <= 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - for (size_t j = 1; j <= i; ++j) { - internal_iter->AddPut("b", "val_b"); - internal_iter->AddDeletion("b"); - } - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - ro.max_skippable_internal_keys = i; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - if ((options.max_sequential_skip_in_iterations + 1) >= - ro.max_skippable_internal_keys) { - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } else { - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - } - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Prev(); - if ((options.max_sequential_skip_in_iterations + 1) >= - ro.max_skippable_internal_keys) { - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } else { - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - } - } - } - - // Test for large number of skippable internal keys with a *non-default* - // max_sequential_skip_in_iterations. - { - for (size_t i = 1; i <= 200; ++i) { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - for (size_t j = 1; j <= i; ++j) { - internal_iter->AddPut("b", "val_b"); - internal_iter->AddDeletion("b"); - } - internal_iter->AddPut("c", "val_c"); - internal_iter->Finish(); - - options.max_sequential_skip_in_iterations = 1000; - ro.max_skippable_internal_keys = i; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "val_a"); - - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "val_c"); - - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - ASSERT_TRUE(db_iter->status().IsIncomplete()); - } - } -} - -TEST_F(DBIteratorTest, DBIterator1) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "0"); - internal_iter->AddPut("b", "0"); - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("a", "1"); - internal_iter->AddMerge("b", "2"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 1 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "0"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - db_iter->Next(); - ASSERT_FALSE(db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator2) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "0"); - internal_iter->AddPut("b", "0"); - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("a", "1"); - internal_iter->AddMerge("b", "2"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 0 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "0"); - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator3) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "0"); - internal_iter->AddPut("b", "0"); - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("a", "1"); - internal_iter->AddMerge("b", "2"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 2 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "0"); - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator4) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "0"); - internal_iter->AddPut("b", "0"); - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("a", "1"); - internal_iter->AddMerge("b", "2"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 4 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "0,1"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "2"); - db_iter->Next(); - ASSERT_TRUE(!db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator5) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 0 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 1 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2,merge_3"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 3 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "put_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 4 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "put_1,merge_4"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 5 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "put_1,merge_4,merge_5"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddPut("a", "put_1"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 6 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "put_1,merge_4,merge_5,merge_6"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - // put, singledelete, merge - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "val_a"); - internal_iter->AddSingleDeletion("a"); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddPut("b", "val_b"); - internal_iter->Finish(); - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 10 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->Seek("b"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - } -} - -TEST_F(DBIteratorTest, DBIterator6) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 0 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 1 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2,merge_3"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 3 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 4 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 5 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("a", "merge_3"); - internal_iter->AddDeletion("a"); - internal_iter->AddMerge("a", "merge_4"); - internal_iter->AddMerge("a", "merge_5"); - internal_iter->AddMerge("a", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 6 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5,merge_6"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } -} - -TEST_F(DBIteratorTest, DBIterator7) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - ImmutableOptions ioptions = ImmutableOptions(options); - MutableCFOptions mutable_cf_options = MutableCFOptions(options); - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 0 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 2 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "val,merge_2"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 4 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 5 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4"); - db_iter->Prev(); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 6 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 7 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 9 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_6,merge_7"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 13 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "merge_4,merge_5"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), - "merge_6,merge_7,merge_8,merge_9,merge_10,merge_11"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } - - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddPut("b", "val"); - internal_iter->AddMerge("b", "merge_2"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_3"); - - internal_iter->AddMerge("c", "merge_4"); - internal_iter->AddMerge("c", "merge_5"); - - internal_iter->AddDeletion("b"); - internal_iter->AddMerge("b", "merge_6"); - internal_iter->AddMerge("b", "merge_7"); - internal_iter->AddMerge("b", "merge_8"); - internal_iter->AddMerge("b", "merge_9"); - internal_iter->AddMerge("b", "merge_10"); - internal_iter->AddMerge("b", "merge_11"); - - internal_iter->AddDeletion("c"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ioptions, mutable_cf_options, BytewiseComparator(), - internal_iter, nullptr /* version */, 14 /* sequence */, - options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), - "merge_6,merge_7,merge_8,merge_9,merge_10,merge_11"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1"); - db_iter->Prev(); - ASSERT_TRUE(!db_iter->Valid()); - } -} - -TEST_F(DBIteratorTest, DBIterator8) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddDeletion("a"); - internal_iter->AddPut("a", "0"); - internal_iter->AddPut("b", "0"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "0"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "0"); -} - -// TODO(3.13): fix the issue of Seek() then Prev() which might not necessary -// return the biggest element smaller than the seek key. -TEST_F(DBIteratorTest, DBIterator9) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - { - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddMerge("a", "merge_1"); - internal_iter->AddMerge("a", "merge_2"); - internal_iter->AddMerge("b", "merge_3"); - internal_iter->AddMerge("b", "merge_4"); - internal_iter->AddMerge("d", "merge_5"); - internal_iter->AddMerge("d", "merge_6"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "merge_5,merge_6"); - - db_iter->Seek("b"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2"); - - db_iter->SeekForPrev("b"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "merge_5,merge_6"); - - db_iter->Seek("c"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "merge_5,merge_6"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4"); - - db_iter->SeekForPrev("c"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "merge_5,merge_6"); - } -} - -// TODO(3.13): fix the issue of Seek() then Prev() which might not necessary -// return the biggest element smaller than the seek key. -TEST_F(DBIteratorTest, DBIterator10) { - ReadOptions ro; - Options options; - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "1"); - internal_iter->AddPut("b", "2"); - internal_iter->AddPut("c", "3"); - internal_iter->AddPut("d", "4"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->Seek("c"); - ASSERT_TRUE(db_iter->Valid()); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "2"); - - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "3"); - - db_iter->SeekForPrev("c"); - ASSERT_TRUE(db_iter->Valid()); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "d"); - ASSERT_EQ(db_iter->value().ToString(), "4"); - - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "3"); -} - -TEST_F(DBIteratorTest, SeekToLastOccurrenceSeq0) { - ReadOptions ro; - Options options; - options.merge_operator = nullptr; - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "1"); - internal_iter->AddPut("b", "2"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, 0 /* force seek */, nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "1"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "2"); - db_iter->Next(); - ASSERT_FALSE(db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator11) { - ReadOptions ro; - Options options; - options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "0"); - internal_iter->AddPut("b", "0"); - internal_iter->AddSingleDeletion("b"); - internal_iter->AddMerge("a", "1"); - internal_iter->AddMerge("b", "2"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 1 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - db_iter->SeekToFirst(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "0"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - db_iter->Next(); - ASSERT_FALSE(db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator12) { - ReadOptions ro; - Options options; - options.merge_operator = nullptr; - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "1"); - internal_iter->AddPut("b", "2"); - internal_iter->AddPut("c", "3"); - internal_iter->AddSingleDeletion("b"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, 0 /* force seek */, nullptr /* read_callback */)); - db_iter->SeekToLast(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "c"); - ASSERT_EQ(db_iter->value().ToString(), "3"); - db_iter->Prev(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "1"); - db_iter->Prev(); - ASSERT_FALSE(db_iter->Valid()); -} - -TEST_F(DBIteratorTest, DBIterator13) { - ReadOptions ro; - Options options; - options.merge_operator = nullptr; - - std::string key; - key.resize(9); - key.assign(9, static_cast(0)); - key[0] = 'b'; - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut(key, "0"); - internal_iter->AddPut(key, "1"); - internal_iter->AddPut(key, "2"); - internal_iter->AddPut(key, "3"); - internal_iter->AddPut(key, "4"); - internal_iter->AddPut(key, "5"); - internal_iter->AddPut(key, "6"); - internal_iter->AddPut(key, "7"); - internal_iter->AddPut(key, "8"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 2 /* sequence */, 3 /* max_sequential_skip_in_iterations */, - nullptr /* read_callback */)); - db_iter->Seek("b"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), key); - ASSERT_EQ(db_iter->value().ToString(), "2"); -} - -TEST_F(DBIteratorTest, DBIterator14) { - ReadOptions ro; - Options options; - options.merge_operator = nullptr; - - std::string key("b"); - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("b", "0"); - internal_iter->AddPut("b", "1"); - internal_iter->AddPut("b", "2"); - internal_iter->AddPut("b", "3"); - internal_iter->AddPut("a", "4"); - internal_iter->AddPut("a", "5"); - internal_iter->AddPut("a", "6"); - internal_iter->AddPut("c", "7"); - internal_iter->AddPut("c", "8"); - internal_iter->AddPut("c", "9"); - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 4 /* sequence */, 1 /* max_sequential_skip_in_iterations */, - nullptr /* read_callback */)); - db_iter->Seek("b"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(db_iter->key().ToString(), "b"); - ASSERT_EQ(db_iter->value().ToString(), "3"); - db_iter->SeekToFirst(); - ASSERT_EQ(db_iter->key().ToString(), "a"); - ASSERT_EQ(db_iter->value().ToString(), "4"); -} - -class DBIterWithMergeIterTest : public testing::Test { - public: - DBIterWithMergeIterTest() - : env_(Env::Default()), icomp_(BytewiseComparator()) { - options_.merge_operator = nullptr; - - internal_iter1_ = new TestIterator(BytewiseComparator()); - internal_iter1_->Add("a", kTypeValue, "1", 3u); - internal_iter1_->Add("f", kTypeValue, "2", 5u); - internal_iter1_->Add("g", kTypeValue, "3", 7u); - internal_iter1_->Finish(); - - internal_iter2_ = new TestIterator(BytewiseComparator()); - internal_iter2_->Add("a", kTypeValue, "4", 6u); - internal_iter2_->Add("b", kTypeValue, "5", 1u); - internal_iter2_->Add("c", kTypeValue, "6", 2u); - internal_iter2_->Add("d", kTypeValue, "7", 3u); - internal_iter2_->Finish(); - - std::vector child_iters; - child_iters.push_back(internal_iter1_); - child_iters.push_back(internal_iter2_); - InternalKeyComparator icomp(BytewiseComparator()); - InternalIterator* merge_iter = - NewMergingIterator(&icomp_, &child_iters[0], 2u); - - db_iter_.reset(NewDBIterator( - env_, ro_, ImmutableOptions(options_), MutableCFOptions(options_), - BytewiseComparator(), merge_iter, nullptr /* version */, - 8 /* read data earlier than seqId 8 */, - 3 /* max iterators before reseek */, nullptr /* read_callback */)); - } - - Env* env_; - ReadOptions ro_; - Options options_; - TestIterator* internal_iter1_; - TestIterator* internal_iter2_; - InternalKeyComparator icomp_; - Iterator* merge_iter_; - std::unique_ptr db_iter_; -}; - -TEST_F(DBIterWithMergeIterTest, InnerMergeIterator1) { - db_iter_->SeekToFirst(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - db_iter_->Next(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Next(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Next(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Next(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Next(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - db_iter_->Next(); - ASSERT_FALSE(db_iter_->Valid()); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIterator2) { - // Test Prev() when one child iterator is at its end. - db_iter_->SeekForPrev("g"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace1) { - // Test Prev() when one child iterator is at its end but more rows - // are added. - db_iter_->Seek("f"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - - // Test call back inserts a key in the end of the mem table after - // MergeIterator::Prev() realized the mem table iterator is at its end - // and before an SeekToLast() is called. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", - [&](void* /*arg*/) { internal_iter2_->Add("z", kTypeValue, "7", 12u); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace2) { - // Test Prev() when one child iterator is at its end but more rows - // are added. - db_iter_->Seek("f"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - - // Test call back inserts entries for update a key in the end of the - // mem table after MergeIterator::Prev() realized the mem tableiterator is at - // its end and before an SeekToLast() is called. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) { - internal_iter2_->Add("z", kTypeValue, "7", 12u); - internal_iter2_->Add("z", kTypeValue, "7", 11u); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace3) { - // Test Prev() when one child iterator is at its end but more rows - // are added and max_skipped is triggered. - db_iter_->Seek("f"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - - // Test call back inserts entries for update a key in the end of the - // mem table after MergeIterator::Prev() realized the mem table iterator is at - // its end and before an SeekToLast() is called. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) { - internal_iter2_->Add("z", kTypeValue, "7", 16u, true); - internal_iter2_->Add("z", kTypeValue, "7", 15u, true); - internal_iter2_->Add("z", kTypeValue, "7", 14u, true); - internal_iter2_->Add("z", kTypeValue, "7", 13u, true); - internal_iter2_->Add("z", kTypeValue, "7", 12u, true); - internal_iter2_->Add("z", kTypeValue, "7", 11u, true); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace4) { - // Test Prev() when one child iterator has more rows inserted - // between Seek() and Prev() when changing directions. - internal_iter2_->Add("z", kTypeValue, "9", 4u); - - db_iter_->Seek("g"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - - // Test call back inserts entries for update a key before "z" in - // mem table after MergeIterator::Prev() calls mem table iterator's - // Seek() and before calling Prev() - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* arg) { - IteratorWrapper* it = reinterpret_cast(arg); - if (it->key().starts_with("z")) { - internal_iter2_->Add("x", kTypeValue, "7", 16u, true); - internal_iter2_->Add("x", kTypeValue, "7", 15u, true); - internal_iter2_->Add("x", kTypeValue, "7", 14u, true); - internal_iter2_->Add("x", kTypeValue, "7", 13u, true); - internal_iter2_->Add("x", kTypeValue, "7", 12u, true); - internal_iter2_->Add("x", kTypeValue, "7", 11u, true); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace5) { - internal_iter2_->Add("z", kTypeValue, "9", 4u); - - // Test Prev() when one child iterator has more rows inserted - // between Seek() and Prev() when changing directions. - db_iter_->Seek("g"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - - // Test call back inserts entries for update a key before "z" in - // mem table after MergeIterator::Prev() calls mem table iterator's - // Seek() and before calling Prev() - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* arg) { - IteratorWrapper* it = reinterpret_cast(arg); - if (it->key().starts_with("z")) { - internal_iter2_->Add("x", kTypeValue, "7", 16u, true); - internal_iter2_->Add("x", kTypeValue, "7", 15u, true); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace6) { - internal_iter2_->Add("z", kTypeValue, "9", 4u); - - // Test Prev() when one child iterator has more rows inserted - // between Seek() and Prev() when changing directions. - db_iter_->Seek("g"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - - // Test call back inserts an entry for update a key before "z" in - // mem table after MergeIterator::Prev() calls mem table iterator's - // Seek() and before calling Prev() - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* arg) { - IteratorWrapper* it = reinterpret_cast(arg); - if (it->key().starts_with("z")) { - internal_iter2_->Add("x", kTypeValue, "7", 16u, true); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace7) { - internal_iter1_->Add("u", kTypeValue, "10", 4u); - internal_iter1_->Add("v", kTypeValue, "11", 4u); - internal_iter1_->Add("w", kTypeValue, "12", 4u); - internal_iter2_->Add("z", kTypeValue, "9", 4u); - - // Test Prev() when one child iterator has more rows inserted - // between Seek() and Prev() when changing directions. - db_iter_->Seek("g"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - - // Test call back inserts entries for update a key before "z" in - // mem table after MergeIterator::Prev() calls mem table iterator's - // Seek() and before calling Prev() - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* arg) { - IteratorWrapper* it = reinterpret_cast(arg); - if (it->key().starts_with("z")) { - internal_iter2_->Add("x", kTypeValue, "7", 16u, true); - internal_iter2_->Add("x", kTypeValue, "7", 15u, true); - internal_iter2_->Add("x", kTypeValue, "7", 14u, true); - internal_iter2_->Add("x", kTypeValue, "7", 13u, true); - internal_iter2_->Add("x", kTypeValue, "7", 12u, true); - internal_iter2_->Add("x", kTypeValue, "7", 11u, true); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "c"); - ASSERT_EQ(db_iter_->value().ToString(), "6"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "b"); - ASSERT_EQ(db_iter_->value().ToString(), "5"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "a"); - ASSERT_EQ(db_iter_->value().ToString(), "4"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace8) { - // internal_iter1_: a, f, g - // internal_iter2_: a, b, c, d, adding (z) - internal_iter2_->Add("z", kTypeValue, "9", 4u); - - // Test Prev() when one child iterator has more rows inserted - // between Seek() and Prev() when changing directions. - db_iter_->Seek("g"); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "g"); - ASSERT_EQ(db_iter_->value().ToString(), "3"); - - // Test call back inserts two keys before "z" in mem table after - // MergeIterator::Prev() calls mem table iterator's Seek() and - // before calling Prev() - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforePrev", [&](void* arg) { - IteratorWrapper* it = reinterpret_cast(arg); - if (it->key().starts_with("z")) { - internal_iter2_->Add("x", kTypeValue, "7", 16u, true); - internal_iter2_->Add("y", kTypeValue, "7", 17u, true); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "f"); - ASSERT_EQ(db_iter_->value().ToString(), "2"); - db_iter_->Prev(); - ASSERT_TRUE(db_iter_->Valid()); - ASSERT_EQ(db_iter_->key().ToString(), "d"); - ASSERT_EQ(db_iter_->value().ToString(), "7"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBIteratorTest, SeekPrefixTombstones) { - ReadOptions ro; - Options options; - options.prefix_extractor.reset(NewNoopTransform()); - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddDeletion("b"); - internal_iter->AddDeletion("c"); - internal_iter->AddDeletion("d"); - internal_iter->AddDeletion("e"); - internal_iter->AddDeletion("f"); - internal_iter->AddDeletion("g"); - internal_iter->Finish(); - - ro.prefix_same_as_start = true; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - int skipped_keys = 0; - - get_perf_context()->Reset(); - db_iter->SeekForPrev("z"); - skipped_keys = - static_cast(get_perf_context()->internal_key_skipped_count); - ASSERT_EQ(skipped_keys, 0); - - get_perf_context()->Reset(); - db_iter->Seek("a"); - skipped_keys = - static_cast(get_perf_context()->internal_key_skipped_count); - ASSERT_EQ(skipped_keys, 0); -} - -TEST_F(DBIteratorTest, SeekToFirstLowerBound) { - const int kNumKeys = 3; - for (int i = 0; i < kNumKeys + 2; ++i) { - // + 2 for two special cases: lower bound before and lower bound after the - // internal iterator's keys - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - for (int j = 1; j <= kNumKeys; ++j) { - internal_iter->AddPut(std::to_string(j), "val"); - } - internal_iter->Finish(); - - ReadOptions ro; - auto lower_bound_str = std::to_string(i); - Slice lower_bound(lower_bound_str); - ro.iterate_lower_bound = &lower_bound; - Options options; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToFirst(); - if (i == kNumKeys + 1) { - // lower bound was beyond the last key - ASSERT_FALSE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - } else { - ASSERT_TRUE(db_iter->Valid()); - int expected; - if (i == 0) { - // lower bound was before the first key - expected = 1; - } else { - // lower bound was at the ith key - expected = i; - } - ASSERT_EQ(std::to_string(expected), db_iter->key().ToString()); - } - } -} - -TEST_F(DBIteratorTest, PrevLowerBound) { - const int kNumKeys = 3; - const int kLowerBound = 2; - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - for (int j = 1; j <= kNumKeys; ++j) { - internal_iter->AddPut(std::to_string(j), "val"); - } - internal_iter->Finish(); - - ReadOptions ro; - auto lower_bound_str = std::to_string(kLowerBound); - Slice lower_bound(lower_bound_str); - ro.iterate_lower_bound = &lower_bound; - Options options; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekToLast(); - for (int i = kNumKeys; i >= kLowerBound; --i) { - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(std::to_string(i), db_iter->key().ToString()); - db_iter->Prev(); - } - ASSERT_FALSE(db_iter->Valid()); -} - -TEST_F(DBIteratorTest, SeekLessLowerBound) { - const int kNumKeys = 3; - const int kLowerBound = 2; - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - for (int j = 1; j <= kNumKeys; ++j) { - internal_iter->AddPut(std::to_string(j), "val"); - } - internal_iter->Finish(); - - ReadOptions ro; - auto lower_bound_str = std::to_string(kLowerBound); - Slice lower_bound(lower_bound_str); - ro.iterate_lower_bound = &lower_bound; - Options options; - std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - auto before_lower_bound_str = std::to_string(kLowerBound - 1); - Slice before_lower_bound(lower_bound_str); - - db_iter->Seek(before_lower_bound); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(lower_bound_str, db_iter->key().ToString()); -} - -TEST_F(DBIteratorTest, ReverseToForwardWithDisappearingKeys) { - Options options; - options.prefix_extractor.reset(NewCappedPrefixTransform(0)); - - TestIterator* internal_iter = new TestIterator(BytewiseComparator()); - internal_iter->AddPut("a", "A"); - internal_iter->AddPut("b", "B"); - for (int i = 0; i < 100; ++i) { - internal_iter->AddPut("c" + std::to_string(i), ""); - } - internal_iter->Finish(); - - std::unique_ptr db_iter(NewDBIterator( - env_, ReadOptions(), ImmutableOptions(options), MutableCFOptions(options), - BytewiseComparator(), internal_iter, nullptr /* version */, - 10 /* sequence */, options.max_sequential_skip_in_iterations, - nullptr /* read_callback */)); - - db_iter->SeekForPrev("a"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - ASSERT_EQ("a", db_iter->key().ToString()); - - internal_iter->Vanish("a"); - db_iter->Next(); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - ASSERT_EQ("b", db_iter->key().ToString()); - - // A (sort of) bug used to cause DBIter to pointlessly drag the internal - // iterator all the way to the end. But this doesn't really matter at the time - // of writing because the only iterator that can see disappearing keys is - // ForwardIterator, which doesn't support SeekForPrev(). - EXPECT_LT(internal_iter->steps(), 20); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_iterator_test.cc b/db/db_iterator_test.cc deleted file mode 100644 index f9e026a8c..000000000 --- a/db/db_iterator_test.cc +++ /dev/null @@ -1,3253 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include "db/arena_wrapped_db_iter.h" -#include "db/db_iter.h" -#include "db/db_test_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/iostats_context.h" -#include "rocksdb/perf_context.h" -#include "table/block_based/flush_block_policy.h" -#include "util/random.h" -#include "utilities/merge_operators/string_append/stringappend2.h" - -namespace ROCKSDB_NAMESPACE { - -// A dumb ReadCallback which saying every key is committed. -class DummyReadCallback : public ReadCallback { - public: - DummyReadCallback() : ReadCallback(kMaxSequenceNumber) {} - bool IsVisibleFullCheck(SequenceNumber /*seq*/) override { return true; } - void SetSnapshot(SequenceNumber seq) { max_visible_seq_ = seq; } -}; - -// Test param: -// bool: whether to pass read_callback to NewIterator(). -class DBIteratorTest : public DBTestBase, - public testing::WithParamInterface { - public: - DBIteratorTest() : DBTestBase("db_iterator_test", /*env_do_fsync=*/true) {} - - Iterator* NewIterator(const ReadOptions& read_options, - ColumnFamilyHandle* column_family = nullptr) { - if (column_family == nullptr) { - column_family = db_->DefaultColumnFamily(); - } - auto* cfd = - static_cast_with_check(column_family)->cfd(); - SequenceNumber seq = read_options.snapshot != nullptr - ? read_options.snapshot->GetSequenceNumber() - : db_->GetLatestSequenceNumber(); - bool use_read_callback = GetParam(); - DummyReadCallback* read_callback = nullptr; - if (use_read_callback) { - read_callback = new DummyReadCallback(); - read_callback->SetSnapshot(seq); - InstrumentedMutexLock lock(&mutex_); - read_callbacks_.push_back( - std::unique_ptr(read_callback)); - } - return dbfull()->NewIteratorImpl(read_options, cfd, seq, read_callback); - } - - private: - InstrumentedMutex mutex_; - std::vector> read_callbacks_; -}; - -TEST_P(DBIteratorTest, IteratorProperty) { - // The test needs to be changed if kPersistedTier is supported in iterator. - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(1, "1", "2")); - ASSERT_OK(Delete(1, "2")); - ReadOptions ropt; - ropt.pin_data = false; - { - std::unique_ptr iter(NewIterator(ropt, handles_[1])); - iter->SeekToFirst(); - std::string prop_value; - ASSERT_NOK(iter->GetProperty("non_existing.value", &prop_value)); - ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("0", prop_value); - ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value)); - ASSERT_EQ("1", prop_value); - iter->Next(); - ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("Iterator is not valid.", prop_value); - - // Get internal key at which the iteration stopped (tombstone in this case). - ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value)); - ASSERT_EQ("2", prop_value); - } - Close(); -} - -TEST_P(DBIteratorTest, PersistedTierOnIterator) { - // The test needs to be changed if kPersistedTier is supported in iterator. - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ReadOptions ropt; - ropt.read_tier = kPersistedTier; - - auto* iter = db_->NewIterator(ropt, handles_[1]); - ASSERT_TRUE(iter->status().IsNotSupported()); - delete iter; - - std::vector iters; - ASSERT_TRUE(db_->NewIterators(ropt, {handles_[1]}, &iters).IsNotSupported()); - Close(); -} - -TEST_P(DBIteratorTest, NonBlockingIteration) { - do { - ReadOptions non_blocking_opts, regular_opts; - anon::OptionsOverride options_override; - options_override.full_block_cache = true; - Options options = CurrentOptions(options_override); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - non_blocking_opts.read_tier = kBlockCacheTier; - - CreateAndReopenWithCF({"pikachu"}, options); - // write one kv to the database. - ASSERT_OK(Put(1, "a", "b")); - - // scan using non-blocking iterator. We should find it because - // it is in memtable. - Iterator* iter = NewIterator(non_blocking_opts, handles_[1]); - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - count++; - } - ASSERT_EQ(count, 1); - delete iter; - - // flush memtable to storage. Now, the key should not be in the - // memtable neither in the block cache. - ASSERT_OK(Flush(1)); - - // verify that a non-blocking iterator does not find any - // kvs. Neither does it do any IOs to storage. - uint64_t numopen = TestGetTickerCount(options, NO_FILE_OPENS); - uint64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - iter = NewIterator(non_blocking_opts, handles_[1]); - count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - count++; - } - ASSERT_EQ(count, 0); - ASSERT_TRUE(iter->status().IsIncomplete()); - ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS)); - ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - delete iter; - - // read in the specified block via a regular get - ASSERT_EQ(Get(1, "a"), "b"); - - // verify that we can find it via a non-blocking scan - numopen = TestGetTickerCount(options, NO_FILE_OPENS); - cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - iter = NewIterator(non_blocking_opts, handles_[1]); - count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - count++; - } - ASSERT_EQ(count, 1); - ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS)); - ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD)); - delete iter; - - // This test verifies block cache behaviors, which is not used by plain - // table format. - } while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipMmapReads)); -} - -TEST_P(DBIteratorTest, IterSeekBeforePrev) { - ASSERT_OK(Put("a", "b")); - ASSERT_OK(Put("c", "d")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("0", "f")); - ASSERT_OK(Put("1", "h")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("2", "j")); - auto iter = NewIterator(ReadOptions()); - iter->Seek(Slice("c")); - iter->Prev(); - iter->Seek(Slice("a")); - iter->Prev(); - delete iter; -} - -TEST_P(DBIteratorTest, IterReseekNewUpperBound) { - Random rnd(301); - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - table_options.block_size_deviation = 50; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.compression = kNoCompression; - Reopen(options); - - ASSERT_OK(Put("a", rnd.RandomString(400))); - ASSERT_OK(Put("aabb", rnd.RandomString(400))); - ASSERT_OK(Put("aaef", rnd.RandomString(400))); - ASSERT_OK(Put("b", rnd.RandomString(400))); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ReadOptions opts; - Slice ub = Slice("aa"); - opts.iterate_upper_bound = &ub; - auto iter = NewIterator(opts); - iter->Seek(Slice("a")); - ub = Slice("b"); - iter->Seek(Slice("aabc")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "aaef"); - delete iter; -} - -TEST_P(DBIteratorTest, IterSeekForPrevBeforeNext) { - ASSERT_OK(Put("a", "b")); - ASSERT_OK(Put("c", "d")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("0", "f")); - ASSERT_OK(Put("1", "h")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("2", "j")); - auto iter = NewIterator(ReadOptions()); - iter->SeekForPrev(Slice("0")); - iter->Next(); - iter->SeekForPrev(Slice("1")); - iter->Next(); - delete iter; -} - -namespace { -std::string MakeLongKey(size_t length, char c) { - return std::string(length, c); -} -} // anonymous namespace - -TEST_P(DBIteratorTest, IterLongKeys) { - ASSERT_OK(Put(MakeLongKey(20, 0), "0")); - ASSERT_OK(Put(MakeLongKey(32, 2), "2")); - ASSERT_OK(Put("a", "b")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put(MakeLongKey(50, 1), "1")); - ASSERT_OK(Put(MakeLongKey(127, 3), "3")); - ASSERT_OK(Put(MakeLongKey(64, 4), "4")); - auto iter = NewIterator(ReadOptions()); - - // Create a key that needs to be skipped for Seq too new - iter->Seek(MakeLongKey(20, 0)); - ASSERT_EQ(IterStatus(iter), MakeLongKey(20, 0) + "->0"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(32, 2) + "->2"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(127, 3) + "->3"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(64, 4) + "->4"); - - iter->SeekForPrev(MakeLongKey(127, 3)); - ASSERT_EQ(IterStatus(iter), MakeLongKey(127, 3) + "->3"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(32, 2) + "->2"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1"); - delete iter; - - iter = NewIterator(ReadOptions()); - iter->Seek(MakeLongKey(50, 1)); - ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(32, 2) + "->2"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), MakeLongKey(127, 3) + "->3"); - delete iter; -} - -TEST_P(DBIteratorTest, IterNextWithNewerSeq) { - ASSERT_OK(Put("0", "0")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("a", "b")); - ASSERT_OK(Put("c", "d")); - ASSERT_OK(Put("d", "e")); - auto iter = NewIterator(ReadOptions()); - - // Create a key that needs to be skipped for Seq too new - for (uint64_t i = 0; i < last_options_.max_sequential_skip_in_iterations + 1; - i++) { - ASSERT_OK(Put("b", "f")); - } - - iter->Seek(Slice("a")); - ASSERT_EQ(IterStatus(iter), "a->b"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "c->d"); - iter->SeekForPrev(Slice("b")); - ASSERT_EQ(IterStatus(iter), "a->b"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "c->d"); - - delete iter; -} - -TEST_P(DBIteratorTest, IterPrevWithNewerSeq) { - ASSERT_OK(Put("0", "0")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("a", "b")); - ASSERT_OK(Put("c", "d")); - ASSERT_OK(Put("d", "e")); - auto iter = NewIterator(ReadOptions()); - - // Create a key that needs to be skipped for Seq too new - for (uint64_t i = 0; i < last_options_.max_sequential_skip_in_iterations + 1; - i++) { - ASSERT_OK(Put("b", "f")); - } - - iter->Seek(Slice("d")); - ASSERT_EQ(IterStatus(iter), "d->e"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "c->d"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "a->b"); - iter->Prev(); - iter->SeekForPrev(Slice("d")); - ASSERT_EQ(IterStatus(iter), "d->e"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "c->d"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "a->b"); - iter->Prev(); - delete iter; -} - -TEST_P(DBIteratorTest, IterPrevWithNewerSeq2) { - ASSERT_OK(Put("0", "0")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("a", "b")); - ASSERT_OK(Put("c", "d")); - ASSERT_OK(Put("e", "f")); - auto iter = NewIterator(ReadOptions()); - auto iter2 = NewIterator(ReadOptions()); - iter->Seek(Slice("c")); - iter2->SeekForPrev(Slice("d")); - ASSERT_EQ(IterStatus(iter), "c->d"); - ASSERT_EQ(IterStatus(iter2), "c->d"); - - // Create a key that needs to be skipped for Seq too new - for (uint64_t i = 0; i < last_options_.max_sequential_skip_in_iterations + 1; - i++) { - ASSERT_OK(Put("b", "f")); - } - - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "a->b"); - iter->Prev(); - iter2->Prev(); - ASSERT_EQ(IterStatus(iter2), "a->b"); - iter2->Prev(); - delete iter; - delete iter2; -} - -TEST_P(DBIteratorTest, IterEmpty) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->Seek("foo"); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->SeekForPrev("foo"); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - ASSERT_OK(iter->status()); - - delete iter; - } while (ChangeCompactOptions()); -} - -TEST_P(DBIteratorTest, IterSingle) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "a", "va")); - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->Seek(""); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekForPrev(""); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->Seek("a"); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekForPrev("a"); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->Seek("b"); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekForPrev("b"); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - delete iter; - } while (ChangeCompactOptions()); -} - -TEST_P(DBIteratorTest, IterMulti) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "a", "va")); - ASSERT_OK(Put(1, "b", "vb")); - ASSERT_OK(Put(1, "c", "vc")); - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->Seek(""); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Seek("a"); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Seek("ax"); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->SeekForPrev("d"); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->SeekForPrev("c"); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->SeekForPrev("bx"); - ASSERT_EQ(IterStatus(iter), "b->vb"); - - iter->Seek("b"); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->Seek("z"); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekForPrev("b"); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->SeekForPrev(""); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - // Switch from reverse to forward - iter->SeekToLast(); - iter->Prev(); - iter->Prev(); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "b->vb"); - - // Switch from forward to reverse - iter->SeekToFirst(); - iter->Next(); - iter->Next(); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "b->vb"); - - // Make sure iter stays at snapshot - ASSERT_OK(Put(1, "a", "va2")); - ASSERT_OK(Put(1, "a2", "va3")); - ASSERT_OK(Put(1, "b", "vb2")); - ASSERT_OK(Put(1, "c", "vc2")); - ASSERT_OK(Delete(1, "b")); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "b->vb"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - delete iter; - } while (ChangeCompactOptions()); -} - -// Check that we can skip over a run of user keys -// by using reseek rather than sequential scan -TEST_P(DBIteratorTest, IterReseek) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - Options options = CurrentOptions(options_override); - options.max_sequential_skip_in_iterations = 3; - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // insert three keys with same userkey and verify that - // reseek is not invoked. For each of these test cases, - // verify that we can find the next key "b". - ASSERT_OK(Put(1, "a", "zero")); - ASSERT_OK(Put(1, "a", "one")); - ASSERT_OK(Put(1, "a", "two")); - ASSERT_OK(Put(1, "b", "bone")); - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - iter->SeekToFirst(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); - ASSERT_EQ(IterStatus(iter), "a->two"); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); - ASSERT_EQ(IterStatus(iter), "b->bone"); - delete iter; - - // insert a total of three keys with same userkey and verify - // that reseek is still not invoked. - ASSERT_OK(Put(1, "a", "three")); - iter = NewIterator(ReadOptions(), handles_[1]); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->three"); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); - ASSERT_EQ(IterStatus(iter), "b->bone"); - delete iter; - - // insert a total of four keys with same userkey and verify - // that reseek is invoked. - ASSERT_OK(Put(1, "a", "four")); - iter = NewIterator(ReadOptions(), handles_[1]); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->four"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1); - ASSERT_EQ(IterStatus(iter), "b->bone"); - delete iter; - - // Testing reverse iterator - // At this point, we have three versions of "a" and one version of "b". - // The reseek statistics is already at 1. - int num_reseeks = static_cast( - TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION)); - - // Insert another version of b and assert that reseek is not invoked - ASSERT_OK(Put(1, "b", "btwo")); - iter = NewIterator(ReadOptions(), handles_[1]); - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "b->btwo"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), - num_reseeks); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), - num_reseeks + 1); - ASSERT_EQ(IterStatus(iter), "a->four"); - delete iter; - - // insert two more versions of b. This makes a total of 4 versions - // of b and 4 versions of a. - ASSERT_OK(Put(1, "b", "bthree")); - ASSERT_OK(Put(1, "b", "bfour")); - iter = NewIterator(ReadOptions(), handles_[1]); - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "b->bfour"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), - num_reseeks + 2); - iter->Prev(); - - // the previous Prev call should have invoked reseek - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), - num_reseeks + 3); - ASSERT_EQ(IterStatus(iter), "a->four"); - delete iter; -} - -TEST_F(DBIteratorTest, ReseekUponDirectionChange) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.merge_operator.reset( - new StringAppendTESTOperator(/*delim_char=*/' ')); - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Put("bar", "value")); - { - std::unique_ptr it(db_->NewIterator(ReadOptions())); - it->SeekToLast(); - it->Prev(); - it->Next(); - } - ASSERT_EQ(1, - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - - const std::string merge_key("good"); - ASSERT_OK(Put(merge_key, "orig")); - ASSERT_OK(Merge(merge_key, "suffix")); - { - std::unique_ptr it(db_->NewIterator(ReadOptions())); - it->Seek(merge_key); - ASSERT_TRUE(it->Valid()); - const uint64_t prev_reseek_count = - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION); - it->Prev(); - ASSERT_EQ(prev_reseek_count + 1, options.statistics->getTickerCount( - NUMBER_OF_RESEEKS_IN_ITERATION)); - } -} - -TEST_P(DBIteratorTest, IterSmallAndLargeMix) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "a", "va")); - ASSERT_OK(Put(1, "b", std::string(100000, 'b'))); - ASSERT_OK(Put(1, "c", "vc")); - ASSERT_OK(Put(1, "d", std::string(100000, 'd'))); - ASSERT_OK(Put(1, "e", std::string(100000, 'e'))); - - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "b->" + std::string(100000, 'b')); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "d->" + std::string(100000, 'd')); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "e->" + std::string(100000, 'e')); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->SeekToLast(); - ASSERT_EQ(IterStatus(iter), "e->" + std::string(100000, 'e')); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "d->" + std::string(100000, 'd')); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "b->" + std::string(100000, 'b')); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - delete iter; - } while (ChangeCompactOptions()); -} - -TEST_P(DBIteratorTest, IterMultiWithDelete) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "ka", "va")); - ASSERT_OK(Put(1, "kb", "vb")); - ASSERT_OK(Put(1, "kc", "vc")); - ASSERT_OK(Delete(1, "kb")); - ASSERT_EQ("NOT_FOUND", Get(1, "kb")); - - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - iter->Seek("kc"); - ASSERT_EQ(IterStatus(iter), "kc->vc"); - if (!CurrentOptions().merge_operator) { - // TODO: merge operator does not support backward iteration yet - if (kPlainTableAllBytesPrefix != option_config_ && - kBlockBasedTableWithWholeKeyHashIndex != option_config_ && - kHashLinkList != option_config_ && - kHashSkipList != option_config_) { // doesn't support SeekToLast - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "ka->va"); - } - } - delete iter; - } while (ChangeOptions()); -} - -TEST_P(DBIteratorTest, IterPrevMaxSkip) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - for (int i = 0; i < 2; i++) { - ASSERT_OK(Put(1, "key1", "v1")); - ASSERT_OK(Put(1, "key2", "v2")); - ASSERT_OK(Put(1, "key3", "v3")); - ASSERT_OK(Put(1, "key4", "v4")); - ASSERT_OK(Put(1, "key5", "v5")); - } - - VerifyIterLast("key5->v5", 1); - - ASSERT_OK(Delete(1, "key5")); - VerifyIterLast("key4->v4", 1); - - ASSERT_OK(Delete(1, "key4")); - VerifyIterLast("key3->v3", 1); - - ASSERT_OK(Delete(1, "key3")); - VerifyIterLast("key2->v2", 1); - - ASSERT_OK(Delete(1, "key2")); - VerifyIterLast("key1->v1", 1); - - ASSERT_OK(Delete(1, "key1")); - VerifyIterLast("(invalid)", 1); - } while (ChangeOptions(kSkipMergePut | kSkipNoSeekToLast)); -} - -TEST_P(DBIteratorTest, IterWithSnapshot) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override)); - ASSERT_OK(Put(1, "key1", "val1")); - ASSERT_OK(Put(1, "key2", "val2")); - ASSERT_OK(Put(1, "key3", "val3")); - ASSERT_OK(Put(1, "key4", "val4")); - ASSERT_OK(Put(1, "key5", "val5")); - - const Snapshot* snapshot = db_->GetSnapshot(); - ReadOptions options; - options.snapshot = snapshot; - Iterator* iter = NewIterator(options, handles_[1]); - - ASSERT_OK(Put(1, "key0", "val0")); - // Put more values after the snapshot - ASSERT_OK(Put(1, "key100", "val100")); - ASSERT_OK(Put(1, "key101", "val101")); - - iter->Seek("key5"); - ASSERT_EQ(IterStatus(iter), "key5->val5"); - if (!CurrentOptions().merge_operator) { - // TODO: merge operator does not support backward iteration yet - if (kPlainTableAllBytesPrefix != option_config_ && - kBlockBasedTableWithWholeKeyHashIndex != option_config_ && - kHashLinkList != option_config_ && kHashSkipList != option_config_) { - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "key4->val4"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "key3->val3"); - - iter->Next(); - ASSERT_EQ(IterStatus(iter), "key4->val4"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "key5->val5"); - } - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - } - - if (!CurrentOptions().merge_operator) { - // TODO(gzh): merge operator does not support backward iteration yet - if (kPlainTableAllBytesPrefix != option_config_ && - kBlockBasedTableWithWholeKeyHashIndex != option_config_ && - kHashLinkList != option_config_ && kHashSkipList != option_config_) { - iter->SeekForPrev("key1"); - ASSERT_EQ(IterStatus(iter), "key1->val1"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "key2->val2"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "key3->val3"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "key2->val2"); - iter->Prev(); - ASSERT_EQ(IterStatus(iter), "key1->val1"); - iter->Prev(); - ASSERT_TRUE(!iter->Valid()); - } - } - db_->ReleaseSnapshot(snapshot); - delete iter; - } while (ChangeOptions()); -} - -TEST_P(DBIteratorTest, IteratorPinsRef) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "hello")); - - // Get iterator that will yield the current contents of the DB. - Iterator* iter = NewIterator(ReadOptions(), handles_[1]); - - // Write to force compactions - ASSERT_OK(Put(1, "foo", "newvalue1")); - for (int i = 0; i < 100; i++) { - // 100K values - ASSERT_OK(Put(1, Key(i), Key(i) + std::string(100000, 'v'))); - } - ASSERT_OK(Put(1, "foo", "newvalue2")); - - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - ASSERT_EQ("hello", iter->value().ToString()); - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - delete iter; - } while (ChangeCompactOptions()); -} - -TEST_P(DBIteratorTest, IteratorDeleteAfterCfDelete) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - - ASSERT_OK(Put(1, "foo", "delete-cf-then-delete-iter")); - ASSERT_OK(Put(1, "hello", "value2")); - - ColumnFamilyHandle* cf = handles_[1]; - ReadOptions ro; - - auto* iter = db_->NewIterator(ro, cf); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "foo->delete-cf-then-delete-iter"); - - // delete CF handle - EXPECT_OK(db_->DestroyColumnFamilyHandle(cf)); - handles_.erase(std::begin(handles_) + 1); - - // delete Iterator after CF handle is deleted - iter->Next(); - ASSERT_EQ(IterStatus(iter), "hello->value2"); - delete iter; -} - -TEST_P(DBIteratorTest, IteratorDeleteAfterCfDrop) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - - ASSERT_OK(Put(1, "foo", "drop-cf-then-delete-iter")); - - ReadOptions ro; - ColumnFamilyHandle* cf = handles_[1]; - - auto* iter = db_->NewIterator(ro, cf); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "foo->drop-cf-then-delete-iter"); - - // drop and delete CF - EXPECT_OK(db_->DropColumnFamily(cf)); - EXPECT_OK(db_->DestroyColumnFamilyHandle(cf)); - handles_.erase(std::begin(handles_) + 1); - - // delete Iterator after CF handle is dropped - delete iter; -} - -// SetOptions not defined in ROCKSDB LITE -TEST_P(DBIteratorTest, DBIteratorBoundTest) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - - options.prefix_extractor = nullptr; - DestroyAndReopen(options); - ASSERT_OK(Put("a", "0")); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("g1", "0")); - - // testing basic case with no iterate_upper_bound and no prefix_extractor - { - ReadOptions ro; - ro.iterate_upper_bound = nullptr; - - std::unique_ptr iter(NewIterator(ro)); - - iter->Seek("foo"); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo")), 0); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo1")), 0); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("g1")), 0); - - iter->SeekForPrev("g1"); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("g1")), 0); - - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo1")), 0); - - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo")), 0); - } - - // testing iterate_upper_bound and forward iterator - // to make sure it stops at bound - { - ReadOptions ro; - // iterate_upper_bound points beyond the last expected entry - Slice prefix("foo2"); - ro.iterate_upper_bound = &prefix; - - std::unique_ptr iter(NewIterator(ro)); - - iter->Seek("foo"); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo")), 0); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(("foo1")), 0); - - iter->Next(); - // should stop here... - ASSERT_TRUE(!iter->Valid()); - } - // Testing SeekToLast with iterate_upper_bound set - { - ReadOptions ro; - - Slice prefix("foo"); - ro.iterate_upper_bound = &prefix; - - std::unique_ptr iter(NewIterator(ro)); - - iter->SeekToLast(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("a")), 0); - } - - // prefix is the first letter of the key - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:1"}})); - ASSERT_OK(Put("a", "0")); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("g1", "0")); - - // testing with iterate_upper_bound and prefix_extractor - // Seek target and iterate_upper_bound are not is same prefix - // This should be an error - { - ReadOptions ro; - Slice upper_bound("g"); - ro.iterate_upper_bound = &upper_bound; - - std::unique_ptr iter(NewIterator(ro)); - - iter->Seek("foo"); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo1", iter->key().ToString()); - - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - } - - // testing that iterate_upper_bound prevents iterating over deleted items - // if the bound has already reached - { - options.prefix_extractor = nullptr; - DestroyAndReopen(options); - ASSERT_OK(Put("a", "0")); - ASSERT_OK(Put("b", "0")); - ASSERT_OK(Put("b1", "0")); - ASSERT_OK(Put("c", "0")); - ASSERT_OK(Put("d", "0")); - ASSERT_OK(Put("e", "0")); - ASSERT_OK(Delete("c")); - ASSERT_OK(Delete("d")); - - // base case with no bound - ReadOptions ro; - ro.iterate_upper_bound = nullptr; - - std::unique_ptr iter(NewIterator(ro)); - - iter->Seek("b"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("b")), 0); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(("b1")), 0); - - get_perf_context()->Reset(); - iter->Next(); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ( - static_cast(get_perf_context()->internal_delete_skipped_count), 2); - - // now testing with iterate_bound - Slice prefix("c"); - ro.iterate_upper_bound = &prefix; - - iter.reset(NewIterator(ro)); - - get_perf_context()->Reset(); - - iter->Seek("b"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("b")), 0); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(("b1")), 0); - - iter->Next(); - // the iteration should stop as soon as the bound key is reached - // even though the key is deleted - // hence internal_delete_skipped_count should be 0 - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ( - static_cast(get_perf_context()->internal_delete_skipped_count), 0); - } -} - -TEST_P(DBIteratorTest, DBIteratorBoundMultiSeek) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.prefix_extractor = nullptr; - DestroyAndReopen(options); - ASSERT_OK(Put("a", "0")); - ASSERT_OK(Put("z", "0")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("foo2", "bar2")); - ASSERT_OK(Put("foo3", "bar3")); - ASSERT_OK(Put("foo4", "bar4")); - - { - std::string up_str = "foo5"; - Slice up(up_str); - ReadOptions ro; - ro.iterate_upper_bound = &up; - std::unique_ptr iter(NewIterator(ro)); - - iter->Seek("foo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo1")), 0); - - uint64_t prev_block_cache_hit = - TestGetTickerCount(options, BLOCK_CACHE_HIT); - uint64_t prev_block_cache_miss = - TestGetTickerCount(options, BLOCK_CACHE_MISS); - - ASSERT_GT(prev_block_cache_hit + prev_block_cache_miss, 0); - - iter->Seek("foo4"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo4")), 0); - ASSERT_EQ(prev_block_cache_hit, - TestGetTickerCount(options, BLOCK_CACHE_HIT)); - ASSERT_EQ(prev_block_cache_miss, - TestGetTickerCount(options, BLOCK_CACHE_MISS)); - - iter->Seek("foo2"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo2")), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo3")), 0); - ASSERT_EQ(prev_block_cache_hit, - TestGetTickerCount(options, BLOCK_CACHE_HIT)); - ASSERT_EQ(prev_block_cache_miss, - TestGetTickerCount(options, BLOCK_CACHE_MISS)); - } -} - -TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) { - for (auto format_version : {2, 3, 4}) { - int upper_bound_hits = 0; - Options options = CurrentOptions(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableIterator:out_of_bound", - [&upper_bound_hits](void*) { upper_bound_hits++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor = nullptr; - BlockBasedTableOptions table_options; - table_options.format_version = format_version; - table_options.flush_block_policy_factory = - std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("foo2", "bar2")); - ASSERT_OK(Put("foo4", "bar4")); - ASSERT_OK(Flush()); - - Slice ub("foo3"); - ReadOptions ro; - ro.iterate_upper_bound = &ub; - - std::unique_ptr iter(NewIterator(ro)); - - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo1")), 0); - ASSERT_EQ(upper_bound_hits, 0); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("foo2")), 0); - ASSERT_EQ(upper_bound_hits, 0); - - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_EQ(upper_bound_hits, 1); - } -} - -// Enable kBinarySearchWithFirstKey, do some iterator operations and check that -// they don't do unnecessary block reads. -TEST_P(DBIteratorTest, IndexWithFirstKey) { - for (int tailing = 0; tailing < 2; ++tailing) { - SCOPED_TRACE("tailing = " + std::to_string(tailing)); - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor = nullptr; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - Statistics* stats = options.statistics.get(); - BlockBasedTableOptions table_options; - table_options.index_type = - BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey; - table_options.index_shortening = - BlockBasedTableOptions::IndexShorteningMode::kNoShortening; - table_options.flush_block_policy_factory = - std::make_shared(); - table_options.block_cache = - NewLRUCache(8000); // fits all blocks and their cache metadata overhead - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - ASSERT_OK(Merge("a1", "x1")); - ASSERT_OK(Merge("b1", "y1")); - ASSERT_OK(Merge("c0", "z1")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("a2", "x2")); - ASSERT_OK(Merge("b2", "y2")); - ASSERT_OK(Merge("c0", "z2")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("a3", "x3")); - ASSERT_OK(Merge("b3", "y3")); - ASSERT_OK(Merge("c3", "z3")); - ASSERT_OK(Flush()); - - // Block cache is not important for this test. - // We use BLOCK_CACHE_DATA_* counters just because they're the most readily - // available way of counting block accesses. - - ReadOptions ropt; - ropt.tailing = tailing; - std::unique_ptr iter(NewIterator(ropt)); - - ropt.read_tier = ReadTier::kBlockCacheTier; - std::unique_ptr nonblocking_iter(NewIterator(ropt)); - - iter->Seek("b10"); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ("b2", iter->key().ToString()); - EXPECT_EQ("y2", iter->value().ToString()); - EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - // The cache-only iterator should succeed too, using the blocks pulled into - // the cache by the previous iterator. - nonblocking_iter->Seek("b10"); - ASSERT_TRUE(nonblocking_iter->Valid()); - EXPECT_EQ("b2", nonblocking_iter->key().ToString()); - EXPECT_EQ("y2", nonblocking_iter->value().ToString()); - EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // ... but it shouldn't be able to step forward since the next block is - // not in cache yet. - nonblocking_iter->Next(); - ASSERT_FALSE(nonblocking_iter->Valid()); - ASSERT_TRUE(nonblocking_iter->status().IsIncomplete()); - - // ... nor should a seek to the next key succeed. - nonblocking_iter->Seek("b20"); - ASSERT_FALSE(nonblocking_iter->Valid()); - ASSERT_TRUE(nonblocking_iter->status().IsIncomplete()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ("b3", iter->key().ToString()); - EXPECT_EQ("y3", iter->value().ToString()); - EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // After the blocking iterator loaded the next block, the nonblocking - // iterator's seek should succeed. - nonblocking_iter->Seek("b20"); - ASSERT_TRUE(nonblocking_iter->Valid()); - EXPECT_EQ("b3", nonblocking_iter->key().ToString()); - EXPECT_EQ("y3", nonblocking_iter->value().ToString()); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - iter->Seek("c0"); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ("c0", iter->key().ToString()); - EXPECT_EQ("z1,z2", iter->value().ToString()); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(6, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ("c3", iter->key().ToString()); - EXPECT_EQ("z3", iter->value().ToString()); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - iter.reset(); - - // Enable iterate_upper_bound and check that iterator is not trying to read - // blocks that are fully above upper bound. - std::string ub = "b3"; - Slice ub_slice(ub); - ropt.iterate_upper_bound = &ub_slice; - iter.reset(NewIterator(ropt)); - - iter->Seek("b2"); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ("b2", iter->key().ToString()); - EXPECT_EQ("y2", iter->value().ToString()); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - iter->Next(); - ASSERT_FALSE(iter->Valid()); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(7, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - } -} - -TEST_P(DBIteratorTest, IndexWithFirstKeyGet) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor = nullptr; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - Statistics* stats = options.statistics.get(); - BlockBasedTableOptions table_options; - table_options.index_type = - BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey; - table_options.index_shortening = - BlockBasedTableOptions::IndexShorteningMode::kNoShortening; - table_options.flush_block_policy_factory = - std::make_shared(); - table_options.block_cache = NewLRUCache(1000); // fits all blocks - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - ASSERT_OK(Merge("a", "x1")); - ASSERT_OK(Merge("c", "y1")); - ASSERT_OK(Merge("e", "z1")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("c", "y2")); - ASSERT_OK(Merge("e", "z2")); - ASSERT_OK(Flush()); - - // Get() between blocks shouldn't read any blocks. - ASSERT_EQ("NOT_FOUND", Get("b")); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Get() of an existing key shouldn't read any unnecessary blocks when there's - // only one key per block. - - ASSERT_EQ("y1,y2", Get("c")); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - ASSERT_EQ("x1", Get("a")); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - EXPECT_EQ(std::vector({"NOT_FOUND", "z1,z2"}), - MultiGet({"b", "e"})); -} - -// TODO(3.13): fix the issue of Seek() + Prev() which might not necessary -// return the biggest key which is smaller than the seek key. -TEST_P(DBIteratorTest, PrevAfterAndNextAfterMerge) { - Options options; - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreatePutOperator(); - options.env = env_; - DestroyAndReopen(options); - - // write three entries with different keys using Merge() - WriteOptions wopts; - ASSERT_OK(db_->Merge(wopts, "1", "data1")); - ASSERT_OK(db_->Merge(wopts, "2", "data2")); - ASSERT_OK(db_->Merge(wopts, "3", "data3")); - - std::unique_ptr it(NewIterator(ReadOptions())); - - it->Seek("2"); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("2", it->key().ToString()); - - it->Prev(); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("1", it->key().ToString()); - - it->SeekForPrev("1"); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("1", it->key().ToString()); - - it->Next(); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("2", it->key().ToString()); -} - -class DBIteratorTestForPinnedData : public DBIteratorTest { - public: - enum TestConfig { - NORMAL, - CLOSE_AND_OPEN, - COMPACT_BEFORE_READ, - FLUSH_EVERY_1000, - MAX - }; - DBIteratorTestForPinnedData() : DBIteratorTest() {} - void PinnedDataIteratorRandomized(TestConfig run_config) { - // Generate Random data - Random rnd(301); - - int puts = 100000; - int key_pool = static_cast(puts * 0.7); - int key_size = 100; - int val_size = 1000; - int seeks_percentage = 20; // 20% of keys will be used to test seek() - int delete_percentage = 20; // 20% of keys will be deleted - int merge_percentage = 20; // 20% of keys will be added using Merge() - - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.use_delta_encoding = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.merge_operator = MergeOperators::CreatePutOperator(); - DestroyAndReopen(options); - - std::vector generated_keys(key_pool); - for (int i = 0; i < key_pool; i++) { - generated_keys[i] = rnd.RandomString(key_size); - } - - std::map true_data; - std::vector random_keys; - std::vector deleted_keys; - for (int i = 0; i < puts; i++) { - auto& k = generated_keys[rnd.Next() % key_pool]; - auto v = rnd.RandomString(val_size); - - // Insert data to true_data map and to DB - true_data[k] = v; - if (rnd.PercentTrue(merge_percentage)) { - ASSERT_OK(db_->Merge(WriteOptions(), k, v)); - } else { - ASSERT_OK(Put(k, v)); - } - - // Pick random keys to be used to test Seek() - if (rnd.PercentTrue(seeks_percentage)) { - random_keys.push_back(k); - } - - // Delete some random keys - if (rnd.PercentTrue(delete_percentage)) { - deleted_keys.push_back(k); - true_data.erase(k); - ASSERT_OK(Delete(k)); - } - - if (run_config == TestConfig::FLUSH_EVERY_1000) { - if (i && i % 1000 == 0) { - ASSERT_OK(Flush()); - } - } - } - - if (run_config == TestConfig::CLOSE_AND_OPEN) { - Close(); - Reopen(options); - } else if (run_config == TestConfig::COMPACT_BEFORE_READ) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - - ReadOptions ro; - ro.pin_data = true; - auto iter = NewIterator(ro); - - { - // Test Seek to random keys - std::vector keys_slices; - std::vector true_keys; - for (auto& k : random_keys) { - iter->Seek(k); - if (!iter->Valid()) { - ASSERT_EQ(true_data.lower_bound(k), true_data.end()); - continue; - } - std::string prop_value; - ASSERT_OK( - iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - keys_slices.push_back(iter->key()); - true_keys.push_back(true_data.lower_bound(k)->first); - } - - for (size_t i = 0; i < keys_slices.size(); i++) { - ASSERT_EQ(keys_slices[i].ToString(), true_keys[i]); - } - } - - { - // Test SeekForPrev to random keys - std::vector keys_slices; - std::vector true_keys; - for (auto& k : random_keys) { - iter->SeekForPrev(k); - if (!iter->Valid()) { - ASSERT_EQ(true_data.upper_bound(k), true_data.begin()); - continue; - } - std::string prop_value; - ASSERT_OK( - iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - keys_slices.push_back(iter->key()); - true_keys.push_back((--true_data.upper_bound(k))->first); - } - - for (size_t i = 0; i < keys_slices.size(); i++) { - ASSERT_EQ(keys_slices[i].ToString(), true_keys[i]); - } - } - - { - // Test iterating all data forward - std::vector all_keys; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - std::string prop_value; - ASSERT_OK( - iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - all_keys.push_back(iter->key()); - } - ASSERT_EQ(all_keys.size(), true_data.size()); - - // Verify that all keys slices are valid - auto data_iter = true_data.begin(); - for (size_t i = 0; i < all_keys.size(); i++) { - ASSERT_EQ(all_keys[i].ToString(), data_iter->first); - data_iter++; - } - } - - { - // Test iterating all data backward - std::vector all_keys; - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - std::string prop_value; - ASSERT_OK( - iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - all_keys.push_back(iter->key()); - } - ASSERT_EQ(all_keys.size(), true_data.size()); - - // Verify that all keys slices are valid (backward) - auto data_iter = true_data.rbegin(); - for (size_t i = 0; i < all_keys.size(); i++) { - ASSERT_EQ(all_keys[i].ToString(), data_iter->first); - data_iter++; - } - } - - delete iter; - } -}; - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedNormal) { - PinnedDataIteratorRandomized(TestConfig::NORMAL); -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedCLoseAndOpen) { - PinnedDataIteratorRandomized(TestConfig::CLOSE_AND_OPEN); -} - -TEST_P(DBIteratorTestForPinnedData, - PinnedDataIteratorRandomizedCompactBeforeRead) { - PinnedDataIteratorRandomized(TestConfig::COMPACT_BEFORE_READ); -} - -TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedFlush) { - PinnedDataIteratorRandomized(TestConfig::FLUSH_EVERY_1000); -} - -INSTANTIATE_TEST_CASE_P(DBIteratorTestForPinnedDataInstance, - DBIteratorTestForPinnedData, - testing::Values(true, false)); - -TEST_P(DBIteratorTest, PinnedDataIteratorMultipleFiles) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.use_delta_encoding = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.disable_auto_compactions = true; - options.write_buffer_size = 1024 * 1024 * 10; // 10 Mb - DestroyAndReopen(options); - - std::map true_data; - - // Generate 4 sst files in L2 - Random rnd(301); - for (int i = 1; i <= 1000; i++) { - std::string k = Key(i * 3); - std::string v = rnd.RandomString(100); - ASSERT_OK(Put(k, v)); - true_data[k] = v; - if (i % 250 == 0) { - ASSERT_OK(Flush()); - } - } - ASSERT_EQ(FilesPerLevel(0), "4"); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(FilesPerLevel(0), "0,4"); - - // Generate 4 sst files in L0 - for (int i = 1; i <= 1000; i++) { - std::string k = Key(i * 2); - std::string v = rnd.RandomString(100); - ASSERT_OK(Put(k, v)); - true_data[k] = v; - if (i % 250 == 0) { - ASSERT_OK(Flush()); - } - } - ASSERT_EQ(FilesPerLevel(0), "4,4"); - - // Add some keys/values in memtables - for (int i = 1; i <= 1000; i++) { - std::string k = Key(i); - std::string v = rnd.RandomString(100); - ASSERT_OK(Put(k, v)); - true_data[k] = v; - } - ASSERT_EQ(FilesPerLevel(0), "4,4"); - - ReadOptions ro; - ro.pin_data = true; - auto iter = NewIterator(ro); - - std::vector> results; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - std::string prop_value; - ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - results.emplace_back(iter->key(), iter->value().ToString()); - } - - ASSERT_EQ(results.size(), true_data.size()); - auto data_iter = true_data.begin(); - for (size_t i = 0; i < results.size(); i++, data_iter++) { - auto& kv = results[i]; - ASSERT_EQ(kv.first, data_iter->first); - ASSERT_EQ(kv.second, data_iter->second); - } - - delete iter; -} - -TEST_P(DBIteratorTest, PinnedDataIteratorMergeOperator) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.use_delta_encoding = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.merge_operator = MergeOperators::CreateUInt64AddOperator(); - DestroyAndReopen(options); - - std::string numbers[7]; - for (int val = 0; val <= 6; val++) { - PutFixed64(numbers + val, val); - } - - // +1 all keys in range [ 0 => 999] - for (int i = 0; i < 1000; i++) { - WriteOptions wo; - ASSERT_OK(db_->Merge(wo, Key(i), numbers[1])); - } - - // +2 all keys divisible by 2 in range [ 0 => 999] - for (int i = 0; i < 1000; i += 2) { - WriteOptions wo; - ASSERT_OK(db_->Merge(wo, Key(i), numbers[2])); - } - - // +3 all keys divisible by 5 in range [ 0 => 999] - for (int i = 0; i < 1000; i += 5) { - WriteOptions wo; - ASSERT_OK(db_->Merge(wo, Key(i), numbers[3])); - } - - ReadOptions ro; - ro.pin_data = true; - auto iter = NewIterator(ro); - - std::vector> results; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - std::string prop_value; - ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - results.emplace_back(iter->key(), iter->value().ToString()); - } - - ASSERT_EQ(results.size(), 1000); - for (size_t i = 0; i < results.size(); i++) { - auto& kv = results[i]; - ASSERT_EQ(kv.first, Key(static_cast(i))); - int expected_val = 1; - if (i % 2 == 0) { - expected_val += 2; - } - if (i % 5 == 0) { - expected_val += 3; - } - ASSERT_EQ(kv.second, numbers[expected_val]); - } - - delete iter; -} - -TEST_P(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.use_delta_encoding = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.write_buffer_size = 100000; - DestroyAndReopen(options); - - Random rnd(301); - - std::map true_data; - for (int i = 0; i < 1000; i++) { - std::string k = rnd.RandomString(10); - std::string v = rnd.RandomString(1000); - ASSERT_OK(Put(k, v)); - true_data[k] = v; - } - - ReadOptions ro; - ro.pin_data = true; - auto iter = NewIterator(ro); - - // Delete 50% of the keys and update the other 50% - for (auto& kv : true_data) { - if (rnd.OneIn(2)) { - ASSERT_OK(Delete(kv.first)); - } else { - std::string new_val = rnd.RandomString(1000); - ASSERT_OK(Put(kv.first, new_val)); - } - } - - std::vector> results; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - std::string prop_value; - ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); - ASSERT_EQ("1", prop_value); - results.emplace_back(iter->key(), iter->value().ToString()); - } - - auto data_iter = true_data.begin(); - for (size_t i = 0; i < results.size(); i++, data_iter++) { - auto& kv = results[i]; - ASSERT_EQ(kv.first, data_iter->first); - ASSERT_EQ(kv.second, data_iter->second); - } - - delete iter; -} - -class SliceTransformLimitedDomainGeneric : public SliceTransform { - const char* Name() const override { - return "SliceTransformLimitedDomainGeneric"; - } - - Slice Transform(const Slice& src) const override { - return Slice(src.data(), 1); - } - - bool InDomain(const Slice& src) const override { - // prefix will be x???? - return src.size() >= 1; - } - - bool InRange(const Slice& dst) const override { - // prefix will be x???? - return dst.size() == 1; - } -}; - -TEST_P(DBIteratorTest, IterSeekForPrevCrossingFiles) { - Options options = CurrentOptions(); - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - options.disable_auto_compactions = true; - // Enable prefix bloom for SST files - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - ASSERT_OK(Put("a1", "va1")); - ASSERT_OK(Put("a2", "va2")); - ASSERT_OK(Put("a3", "va3")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("b1", "vb1")); - ASSERT_OK(Put("b2", "vb2")); - ASSERT_OK(Put("b3", "vb3")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("b4", "vb4")); - ASSERT_OK(Put("d1", "vd1")); - ASSERT_OK(Put("d2", "vd2")); - ASSERT_OK(Put("d4", "vd4")); - ASSERT_OK(Flush()); - - MoveFilesToLevel(1); - { - ReadOptions ro; - Iterator* iter = NewIterator(ro); - - iter->SeekForPrev("a4"); - ASSERT_EQ(iter->key().ToString(), "a3"); - ASSERT_EQ(iter->value().ToString(), "va3"); - - iter->SeekForPrev("c2"); - ASSERT_EQ(iter->key().ToString(), "b3"); - iter->SeekForPrev("d3"); - ASSERT_EQ(iter->key().ToString(), "d2"); - iter->SeekForPrev("b5"); - ASSERT_EQ(iter->key().ToString(), "b4"); - delete iter; - } - - { - ReadOptions ro; - ro.prefix_same_as_start = true; - Iterator* iter = NewIterator(ro); - iter->SeekForPrev("c2"); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - delete iter; - } -} - -TEST_P(DBIteratorTest, IterSeekForPrevCrossingFilesCustomPrefixExtractor) { - Options options = CurrentOptions(); - options.prefix_extractor = - std::make_shared(); - options.disable_auto_compactions = true; - // Enable prefix bloom for SST files - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - ASSERT_OK(Put("a1", "va1")); - ASSERT_OK(Put("a2", "va2")); - ASSERT_OK(Put("a3", "va3")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("b1", "vb1")); - ASSERT_OK(Put("b2", "vb2")); - ASSERT_OK(Put("b3", "vb3")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("b4", "vb4")); - ASSERT_OK(Put("d1", "vd1")); - ASSERT_OK(Put("d2", "vd2")); - ASSERT_OK(Put("d4", "vd4")); - ASSERT_OK(Flush()); - - MoveFilesToLevel(1); - { - ReadOptions ro; - Iterator* iter = NewIterator(ro); - - iter->SeekForPrev("a4"); - ASSERT_EQ(iter->key().ToString(), "a3"); - ASSERT_EQ(iter->value().ToString(), "va3"); - - iter->SeekForPrev("c2"); - ASSERT_EQ(iter->key().ToString(), "b3"); - iter->SeekForPrev("d3"); - ASSERT_EQ(iter->key().ToString(), "d2"); - iter->SeekForPrev("b5"); - ASSERT_EQ(iter->key().ToString(), "b4"); - delete iter; - } - - { - ReadOptions ro; - ro.prefix_same_as_start = true; - Iterator* iter = NewIterator(ro); - iter->SeekForPrev("c2"); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - delete iter; - } -} - -TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocks) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.block_size = 1; // every block will contain one entry - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.merge_operator = MergeOperators::CreateStringAppendTESTOperator(); - options.disable_auto_compactions = true; - options.max_sequential_skip_in_iterations = 8; - - DestroyAndReopen(options); - - // Putting such deletes will force DBIter::Prev() to fallback to a Seek - for (int file_num = 0; file_num < 10; file_num++) { - ASSERT_OK(Delete("key4")); - ASSERT_OK(Flush()); - } - - // First File containing 5 blocks of puts - ASSERT_OK(Put("key1", "val1.0")); - ASSERT_OK(Put("key2", "val2.0")); - ASSERT_OK(Put("key3", "val3.0")); - ASSERT_OK(Put("key4", "val4.0")); - ASSERT_OK(Put("key5", "val5.0")); - ASSERT_OK(Flush()); - - // Second file containing 9 blocks of merge operands - ASSERT_OK(db_->Merge(WriteOptions(), "key1", "val1.1")); - ASSERT_OK(db_->Merge(WriteOptions(), "key1", "val1.2")); - - ASSERT_OK(db_->Merge(WriteOptions(), "key2", "val2.1")); - ASSERT_OK(db_->Merge(WriteOptions(), "key2", "val2.2")); - ASSERT_OK(db_->Merge(WriteOptions(), "key2", "val2.3")); - - ASSERT_OK(db_->Merge(WriteOptions(), "key3", "val3.1")); - ASSERT_OK(db_->Merge(WriteOptions(), "key3", "val3.2")); - ASSERT_OK(db_->Merge(WriteOptions(), "key3", "val3.3")); - ASSERT_OK(db_->Merge(WriteOptions(), "key3", "val3.4")); - ASSERT_OK(Flush()); - - { - ReadOptions ro; - ro.fill_cache = false; - Iterator* iter = NewIterator(ro); - - iter->SeekToLast(); - ASSERT_EQ(iter->key().ToString(), "key5"); - ASSERT_EQ(iter->value().ToString(), "val5.0"); - - iter->Prev(); - ASSERT_EQ(iter->key().ToString(), "key4"); - ASSERT_EQ(iter->value().ToString(), "val4.0"); - - iter->Prev(); - ASSERT_EQ(iter->key().ToString(), "key3"); - ASSERT_EQ(iter->value().ToString(), "val3.0,val3.1,val3.2,val3.3,val3.4"); - - iter->Prev(); - ASSERT_EQ(iter->key().ToString(), "key2"); - ASSERT_EQ(iter->value().ToString(), "val2.0,val2.1,val2.2,val2.3"); - - iter->Prev(); - ASSERT_EQ(iter->key().ToString(), "key1"); - ASSERT_EQ(iter->value().ToString(), "val1.0,val1.1,val1.2"); - - delete iter; - } -} - -TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) { - Options options = CurrentOptions(); - options.merge_operator = MergeOperators::CreateStringAppendTESTOperator(); - options.disable_auto_compactions = true; - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.max_sequential_skip_in_iterations = 8; - DestroyAndReopen(options); - - const int kNumKeys = 500; - // Small number of merge operands to make sure that DBIter::Prev() don't - // fall back to Seek() - const int kNumMergeOperands = 3; - // Use value size that will make sure that every block contain 1 key - const int kValSize = - static_cast(BlockBasedTableOptions().block_size) * 4; - // Percentage of keys that wont get merge operations - const int kNoMergeOpPercentage = 20; - // Percentage of keys that will be deleted - const int kDeletePercentage = 10; - - // For half of the key range we will write multiple deletes first to - // force DBIter::Prev() to fall back to Seek() - for (int file_num = 0; file_num < 10; file_num++) { - for (int i = 0; i < kNumKeys; i += 2) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - } - - Random rnd(301); - std::map true_data; - std::string gen_key; - std::string gen_val; - - for (int i = 0; i < kNumKeys; i++) { - gen_key = Key(i); - gen_val = rnd.RandomString(kValSize); - - ASSERT_OK(Put(gen_key, gen_val)); - true_data[gen_key] = gen_val; - } - ASSERT_OK(Flush()); - - // Separate values and merge operands in different file so that we - // make sure that we don't merge them while flushing but actually - // merge them in the read path - for (int i = 0; i < kNumKeys; i++) { - if (rnd.PercentTrue(kNoMergeOpPercentage)) { - // Dont give merge operations for some keys - continue; - } - - for (int j = 0; j < kNumMergeOperands; j++) { - gen_key = Key(i); - gen_val = rnd.RandomString(kValSize); - - ASSERT_OK(db_->Merge(WriteOptions(), gen_key, gen_val)); - true_data[gen_key] += "," + gen_val; - } - } - ASSERT_OK(Flush()); - - for (int i = 0; i < kNumKeys; i++) { - if (rnd.PercentTrue(kDeletePercentage)) { - gen_key = Key(i); - - ASSERT_OK(Delete(gen_key)); - true_data.erase(gen_key); - } - } - ASSERT_OK(Flush()); - - { - ReadOptions ro; - ro.fill_cache = false; - Iterator* iter = NewIterator(ro); - auto data_iter = true_data.rbegin(); - - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - ASSERT_EQ(iter->key().ToString(), data_iter->first); - ASSERT_EQ(iter->value().ToString(), data_iter->second); - data_iter++; - } - ASSERT_EQ(data_iter, true_data.rend()); - - delete iter; - } - - { - ReadOptions ro; - ro.fill_cache = false; - Iterator* iter = NewIterator(ro); - auto data_iter = true_data.rbegin(); - - int entries_right = 0; - std::string seek_key; - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - // Verify key/value of current position - ASSERT_EQ(iter->key().ToString(), data_iter->first); - ASSERT_EQ(iter->value().ToString(), data_iter->second); - - bool restore_position_with_seek = rnd.Uniform(2); - if (restore_position_with_seek) { - seek_key = iter->key().ToString(); - } - - // Do some Next() operations the restore the iterator to orignal position - int next_count = - entries_right > 0 ? rnd.Uniform(std::min(entries_right, 10)) : 0; - for (int i = 0; i < next_count; i++) { - iter->Next(); - data_iter--; - - ASSERT_EQ(iter->key().ToString(), data_iter->first); - ASSERT_EQ(iter->value().ToString(), data_iter->second); - } - - if (restore_position_with_seek) { - // Restore orignal position using Seek() - iter->Seek(seek_key); - for (int i = 0; i < next_count; i++) { - data_iter++; - } - - ASSERT_EQ(iter->key().ToString(), data_iter->first); - ASSERT_EQ(iter->value().ToString(), data_iter->second); - } else { - // Restore original position using Prev() - for (int i = 0; i < next_count; i++) { - iter->Prev(); - data_iter++; - - ASSERT_EQ(iter->key().ToString(), data_iter->first); - ASSERT_EQ(iter->value().ToString(), data_iter->second); - } - } - - entries_right++; - data_iter++; - } - ASSERT_EQ(data_iter, true_data.rend()); - - delete iter; - } -} - -TEST_P(DBIteratorTest, IteratorWithLocalStatistics) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 1000; i++) { - // Key 10 bytes / Value 10 bytes - ASSERT_OK(Put(rnd.RandomString(10), rnd.RandomString(10))); - } - - std::atomic total_next(0); - std::atomic total_next_found(0); - std::atomic total_prev(0); - std::atomic total_prev_found(0); - std::atomic total_bytes(0); - - std::vector threads; - std::function reader_func_next = [&]() { - SetPerfLevel(kEnableCount); - get_perf_context()->Reset(); - Iterator* iter = NewIterator(ReadOptions()); - - iter->SeekToFirst(); - // Seek will bump ITER_BYTES_READ - uint64_t bytes = 0; - bytes += iter->key().size(); - bytes += iter->value().size(); - while (true) { - iter->Next(); - total_next++; - - if (!iter->Valid()) { - break; - } - total_next_found++; - bytes += iter->key().size(); - bytes += iter->value().size(); - } - - delete iter; - ASSERT_EQ(bytes, get_perf_context()->iter_read_bytes); - SetPerfLevel(kDisable); - total_bytes += bytes; - }; - - std::function reader_func_prev = [&]() { - SetPerfLevel(kEnableCount); - Iterator* iter = NewIterator(ReadOptions()); - - iter->SeekToLast(); - // Seek will bump ITER_BYTES_READ - uint64_t bytes = 0; - bytes += iter->key().size(); - bytes += iter->value().size(); - while (true) { - iter->Prev(); - total_prev++; - - if (!iter->Valid()) { - break; - } - total_prev_found++; - bytes += iter->key().size(); - bytes += iter->value().size(); - } - - delete iter; - ASSERT_EQ(bytes, get_perf_context()->iter_read_bytes); - SetPerfLevel(kDisable); - total_bytes += bytes; - }; - - for (int i = 0; i < 10; i++) { - threads.emplace_back(reader_func_next); - } - for (int i = 0; i < 15; i++) { - threads.emplace_back(reader_func_prev); - } - - for (auto& t : threads) { - t.join(); - } - - ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_NEXT), (uint64_t)total_next); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_NEXT_FOUND), - (uint64_t)total_next_found); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_PREV), (uint64_t)total_prev); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_DB_PREV_FOUND), - (uint64_t)total_prev_found); - ASSERT_EQ(TestGetTickerCount(options, ITER_BYTES_READ), - (uint64_t)total_bytes); -} - -TEST_P(DBIteratorTest, ReadAhead) { - Options options; - env_->count_random_reads_ = true; - options.env = env_; - options.disable_auto_compactions = true; - options.write_buffer_size = 4 << 20; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - table_options.no_block_cache = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - - std::string value(1024, 'a'); - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(i), value)); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(i), value)); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(i), value)); - } - ASSERT_OK(Flush()); - ASSERT_EQ("1,1,1", FilesPerLevel()); - - env_->random_read_bytes_counter_ = 0; - options.statistics->setTickerCount(NO_FILE_OPENS, 0); - ReadOptions read_options; - auto* iter = NewIterator(read_options); - iter->SeekToFirst(); - int64_t num_file_opens = TestGetTickerCount(options, NO_FILE_OPENS); - size_t bytes_read = env_->random_read_bytes_counter_; - delete iter; - - env_->random_read_bytes_counter_ = 0; - options.statistics->setTickerCount(NO_FILE_OPENS, 0); - read_options.readahead_size = 1024 * 10; - iter = NewIterator(read_options); - iter->SeekToFirst(); - int64_t num_file_opens_readahead = TestGetTickerCount(options, NO_FILE_OPENS); - size_t bytes_read_readahead = env_->random_read_bytes_counter_; - delete iter; - ASSERT_EQ(num_file_opens, num_file_opens_readahead); - ASSERT_GT(bytes_read_readahead, bytes_read); - ASSERT_GT(bytes_read_readahead, read_options.readahead_size * 3); - - // Verify correctness. - iter = NewIterator(read_options); - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_EQ(value, iter->value()); - count++; - } - ASSERT_EQ(100, count); - for (int i = 0; i < 100; i++) { - iter->Seek(Key(i)); - ASSERT_EQ(value, iter->value()); - } - delete iter; -} - -// Insert a key, create a snapshot iterator, overwrite key lots of times, -// seek to a smaller key. Expect DBIter to fall back to a seek instead of -// going through all the overwrites linearly. -TEST_P(DBIteratorTest, DBIteratorSkipRecentDuplicatesTest) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.max_sequential_skip_in_iterations = 3; - options.prefix_extractor = nullptr; - options.write_buffer_size = 1 << 27; // big enough to avoid flush - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - - // Insert. - ASSERT_OK(Put("b", "0")); - - // Create iterator. - ReadOptions ro; - std::unique_ptr iter(NewIterator(ro)); - - // Insert a lot. - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put("b", std::to_string(i + 1).c_str())); - } - - // Check that memtable wasn't flushed. - std::string val; - ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level0", &val)); - EXPECT_EQ("0", val); - - // Seek iterator to a smaller key. - get_perf_context()->Reset(); - iter->Seek("a"); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ("b", iter->key().ToString()); - EXPECT_EQ("0", iter->value().ToString()); - - // Check that the seek didn't do too much work. - // Checks are not tight, just make sure that everything is well below 100. - EXPECT_LT(get_perf_context()->internal_key_skipped_count, 4); - EXPECT_LT(get_perf_context()->internal_recent_skipped_count, 8); - EXPECT_LT(get_perf_context()->seek_on_memtable_count, 10); - EXPECT_LT(get_perf_context()->next_on_memtable_count, 10); - EXPECT_LT(get_perf_context()->prev_on_memtable_count, 10); - - // Check that iterator did something like what we expect. - EXPECT_EQ(get_perf_context()->internal_delete_skipped_count, 0); - EXPECT_EQ(get_perf_context()->internal_merge_count, 0); - EXPECT_GE(get_perf_context()->internal_recent_skipped_count, 2); - EXPECT_GE(get_perf_context()->seek_on_memtable_count, 2); - EXPECT_EQ(1, - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); -} - -TEST_P(DBIteratorTest, Refresh) { - ASSERT_OK(Put("x", "y")); - - std::unique_ptr iter(NewIterator(ReadOptions())); - ASSERT_OK(iter->status()); - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - ASSERT_OK(Put("c", "d")); - - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - ASSERT_OK(iter->status()); - ASSERT_OK(iter->Refresh()); - - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("c")), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - EXPECT_OK(dbfull()->Flush(FlushOptions())); - - ASSERT_OK(Put("m", "n")); - - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("c")), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - ASSERT_OK(iter->status()); - ASSERT_OK(iter->Refresh()); - - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("c")), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("m")), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - iter.reset(); -} - -TEST_P(DBIteratorTest, RefreshWithSnapshot) { - ASSERT_OK(Put("x", "y")); - const Snapshot* snapshot = db_->GetSnapshot(); - ReadOptions options; - options.snapshot = snapshot; - Iterator* iter = NewIterator(options); - ASSERT_OK(iter->status()); - - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - ASSERT_OK(Put("c", "d")); - - iter->Seek(Slice("a")); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Slice("x")), 0); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - ASSERT_OK(iter->status()); - Status s = iter->Refresh(); - ASSERT_TRUE(s.IsNotSupported()); - db_->ReleaseSnapshot(snapshot); - delete iter; -} - -TEST_P(DBIteratorTest, CreationFailure) { - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::NewInternalIterator:StatusCallback", [](void* arg) { - *(reinterpret_cast(arg)) = Status::Corruption("test status"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Iterator* iter = NewIterator(ReadOptions()); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsCorruption()); - delete iter; -} - -TEST_P(DBIteratorTest, UpperBoundWithChangeDirection) { - Options options = CurrentOptions(); - options.max_sequential_skip_in_iterations = 3; - DestroyAndReopen(options); - - // write a bunch of kvs to the database. - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Put("y", "1")); - ASSERT_OK(Put("y1", "1")); - ASSERT_OK(Put("y2", "1")); - ASSERT_OK(Put("y3", "1")); - ASSERT_OK(Put("z", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Put("z", "1")); - ASSERT_OK(Put("bar", "1")); - ASSERT_OK(Put("foo", "1")); - - std::string upper_bound = "x"; - Slice ub_slice(upper_bound); - ReadOptions ro; - ro.iterate_upper_bound = &ub_slice; - ro.max_skippable_internal_keys = 1000; - - Iterator* iter = NewIterator(ro); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("bar", iter->key().ToString()); - - delete iter; -} - -TEST_P(DBIteratorTest, TableFilter) { - ASSERT_OK(Put("a", "1")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("b", "2")); - ASSERT_OK(Put("c", "3")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("d", "4")); - ASSERT_OK(Put("e", "5")); - ASSERT_OK(Put("f", "6")); - EXPECT_OK(dbfull()->Flush(FlushOptions())); - - // Ensure the table_filter callback is called once for each table. - { - std::set unseen{1, 2, 3}; - ReadOptions opts; - opts.table_filter = [&](const TableProperties& props) { - auto it = unseen.find(props.num_entries); - if (it == unseen.end()) { - ADD_FAILURE() << "saw table properties with an unexpected " - << props.num_entries << " entries"; - } else { - unseen.erase(it); - } - return true; - }; - auto iter = NewIterator(opts); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->1"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "b->2"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "c->3"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "d->4"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "e->5"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "f->6"); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(unseen.empty()); - delete iter; - } - - // Ensure returning false in the table_filter hides the keys from that table - // during iteration. - { - ReadOptions opts; - opts.table_filter = [](const TableProperties& props) { - return props.num_entries != 2; - }; - auto iter = NewIterator(opts); - iter->SeekToFirst(); - ASSERT_EQ(IterStatus(iter), "a->1"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "d->4"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "e->5"); - iter->Next(); - ASSERT_EQ(IterStatus(iter), "f->6"); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - delete iter; - } -} - -TEST_P(DBIteratorTest, UpperBoundWithPrevReseek) { - Options options = CurrentOptions(); - options.max_sequential_skip_in_iterations = 3; - DestroyAndReopen(options); - - // write a bunch of kvs to the database. - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Put("y", "1")); - ASSERT_OK(Put("z", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Put("z", "1")); - ASSERT_OK(Put("bar", "1")); - ASSERT_OK(Put("foo", "1")); - ASSERT_OK(Put("foo", "2")); - - ASSERT_OK(Put("foo", "3")); - ASSERT_OK(Put("foo", "4")); - ASSERT_OK(Put("foo", "5")); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(Put("foo", "6")); - - std::string upper_bound = "x"; - Slice ub_slice(upper_bound); - ReadOptions ro; - ro.snapshot = snapshot; - ro.iterate_upper_bound = &ub_slice; - - Iterator* iter = NewIterator(ro); - iter->SeekForPrev("goo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - iter->Prev(); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bar", iter->key().ToString()); - - delete iter; - db_->ReleaseSnapshot(snapshot); -} - -TEST_P(DBIteratorTest, SkipStatistics) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - - int skip_count = 0; - - // write a bunch of kvs to the database. - ASSERT_OK(Put("a", "1")); - ASSERT_OK(Put("b", "1")); - ASSERT_OK(Put("c", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("d", "1")); - ASSERT_OK(Put("e", "1")); - ASSERT_OK(Put("f", "1")); - ASSERT_OK(Put("a", "2")); - ASSERT_OK(Put("b", "2")); - ASSERT_OK(Flush()); - ASSERT_OK(Delete("d")); - ASSERT_OK(Delete("e")); - ASSERT_OK(Delete("f")); - - Iterator* iter = NewIterator(ReadOptions()); - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - count++; - } - ASSERT_EQ(count, 3); - delete iter; - skip_count += 8; // 3 deletes + 3 original keys + 2 lower in sequence - ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); - - iter = NewIterator(ReadOptions()); - count = 0; - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - ASSERT_OK(iter->status()); - count++; - } - ASSERT_EQ(count, 3); - delete iter; - skip_count += 8; // Same as above, but in reverse order - ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); - - ASSERT_OK(Put("aa", "1")); - ASSERT_OK(Put("ab", "1")); - ASSERT_OK(Put("ac", "1")); - ASSERT_OK(Put("ad", "1")); - ASSERT_OK(Flush()); - ASSERT_OK(Delete("ab")); - ASSERT_OK(Delete("ac")); - ASSERT_OK(Delete("ad")); - - ReadOptions ro; - Slice prefix("b"); - ro.iterate_upper_bound = &prefix; - - iter = NewIterator(ro); - count = 0; - for (iter->Seek("aa"); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - count++; - } - ASSERT_EQ(count, 1); - delete iter; - skip_count += 6; // 3 deletes + 3 original keys - ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); - - iter = NewIterator(ro); - count = 0; - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - ASSERT_OK(iter->status()); - count++; - } - ASSERT_EQ(count, 2); - delete iter; - // 3 deletes + 3 original keys + lower sequence of "a" - skip_count += 7; - ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); -} - -TEST_P(DBIteratorTest, SeekAfterHittingManyInternalKeys) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - ReadOptions ropts; - ropts.max_skippable_internal_keys = 2; - - ASSERT_OK(Put("1", "val_1")); - // Add more tombstones than max_skippable_internal_keys so that Next() fails. - ASSERT_OK(Delete("2")); - ASSERT_OK(Delete("3")); - ASSERT_OK(Delete("4")); - ASSERT_OK(Delete("5")); - ASSERT_OK(Put("6", "val_6")); - - std::unique_ptr iter(NewIterator(ropts)); - iter->SeekToFirst(); - - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "1"); - ASSERT_EQ(iter->value().ToString(), "val_1"); - - // This should fail as incomplete due to too many non-visible internal keys on - // the way to the next valid user key. - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_TRUE(iter->status().IsIncomplete()); - - // Get the internal key at which Next() failed. - std::string prop_value; - ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value)); - ASSERT_EQ("4", prop_value); - - // Create a new iterator to seek to the internal key. - std::unique_ptr iter2(NewIterator(ropts)); - iter2->Seek(prop_value); - ASSERT_TRUE(iter2->Valid()); - ASSERT_OK(iter2->status()); - - ASSERT_EQ(iter2->key().ToString(), "6"); - ASSERT_EQ(iter2->value().ToString(), "val_6"); -} - -// Reproduces a former bug where iterator would skip some records when DBIter -// re-seeks subiterator with Incomplete status. -TEST_P(DBIteratorTest, NonBlockingIterationBugRepro) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - // Make sure the sst file has more than one block. - table_options.flush_block_policy_factory = - std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - // Two records in sst file, each in its own block. - ASSERT_OK(Put("b", "")); - ASSERT_OK(Put("d", "")); - ASSERT_OK(Flush()); - - // Create a nonblocking iterator before writing to memtable. - ReadOptions ropt; - ropt.read_tier = kBlockCacheTier; - std::unique_ptr iter(NewIterator(ropt)); - - // Overwrite a key in memtable many times to hit - // max_sequential_skip_in_iterations (which is 8 by default). - for (int i = 0; i < 20; ++i) { - ASSERT_OK(Put("c", "")); - } - - // Load the second block in sst file into the block cache. - { - std::unique_ptr iter2(NewIterator(ReadOptions())); - iter2->Seek("d"); - } - - // Finally seek the nonblocking iterator. - iter->Seek("a"); - // With the bug, the status used to be OK, and the iterator used to point to - // "d". - EXPECT_TRUE(iter->status().IsIncomplete()); -} - -TEST_P(DBIteratorTest, SeekBackwardAfterOutOfUpperBound) { - ASSERT_OK(Put("a", "")); - ASSERT_OK(Put("b", "")); - ASSERT_OK(Flush()); - - ReadOptions ropt; - Slice ub = "b"; - ropt.iterate_upper_bound = &ub; - - std::unique_ptr it(dbfull()->NewIterator(ropt)); - it->SeekForPrev("a"); - ASSERT_TRUE(it->Valid()); - ASSERT_OK(it->status()); - ASSERT_EQ("a", it->key().ToString()); - it->Next(); - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - it->SeekForPrev("a"); - ASSERT_OK(it->status()); - - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("a", it->key().ToString()); -} - -TEST_P(DBIteratorTest, AvoidReseekLevelIterator) { - Options options = CurrentOptions(); - options.compression = CompressionType::kNoCompression; - BlockBasedTableOptions table_options; - table_options.block_size = 800; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - - Random rnd(301); - std::string random_str = rnd.RandomString(180); - - ASSERT_OK(Put("1", random_str)); - ASSERT_OK(Put("2", random_str)); - ASSERT_OK(Put("3", random_str)); - ASSERT_OK(Put("4", random_str)); - // A new block - ASSERT_OK(Put("5", random_str)); - ASSERT_OK(Put("6", random_str)); - ASSERT_OK(Put("7", random_str)); - ASSERT_OK(Flush()); - ASSERT_OK(Put("8", random_str)); - ASSERT_OK(Put("9", random_str)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - int num_find_file_in_level = 0; - int num_idx_blk_seek = 0; - SyncPoint::GetInstance()->SetCallBack( - "LevelIterator::Seek:BeforeFindFile", - [&](void* /*arg*/) { num_find_file_in_level++; }); - SyncPoint::GetInstance()->SetCallBack( - "IndexBlockIter::Seek:0", [&](void* /*arg*/) { num_idx_blk_seek++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - { - std::unique_ptr iter(NewIterator(ReadOptions())); - iter->Seek("1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(1, num_idx_blk_seek); - - iter->Seek("2"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(1, num_idx_blk_seek); - - iter->Seek("3"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(1, num_idx_blk_seek); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(1, num_idx_blk_seek); - - iter->Seek("5"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(2, num_idx_blk_seek); - - iter->Seek("6"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(2, num_idx_blk_seek); - - iter->Seek("7"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(1, num_find_file_in_level); - ASSERT_EQ(3, num_idx_blk_seek); - - iter->Seek("8"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(2, num_find_file_in_level); - // Still re-seek because "8" is the boundary key, which has - // the same user key as the seek key. - ASSERT_EQ(4, num_idx_blk_seek); - - iter->Seek("5"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(3, num_find_file_in_level); - ASSERT_EQ(5, num_idx_blk_seek); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(3, num_find_file_in_level); - ASSERT_EQ(5, num_idx_blk_seek); - - // Seek backward never triggers the index block seek to be skipped - iter->Seek("5"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(3, num_find_file_in_level); - ASSERT_EQ(6, num_idx_blk_seek); - } - - SyncPoint::GetInstance()->DisableProcessing(); -} - -// MyRocks may change iterate bounds before seek. Simply test to make sure such -// usage doesn't break iterator. -TEST_P(DBIteratorTest, IterateBoundChangedBeforeSeek) { - Options options = CurrentOptions(); - options.compression = CompressionType::kNoCompression; - BlockBasedTableOptions table_options; - table_options.block_size = 100; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - std::string value(50, 'v'); - Reopen(options); - ASSERT_OK(Put("aaa", value)); - ASSERT_OK(Flush()); - ASSERT_OK(Put("bbb", "v")); - ASSERT_OK(Put("ccc", "v")); - ASSERT_OK(Put("ddd", "v")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("eee", "v")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - std::string ub1 = "e"; - std::string ub2 = "c"; - Slice ub(ub1); - ReadOptions read_opts1; - read_opts1.iterate_upper_bound = &ub; - Iterator* iter = NewIterator(read_opts1); - // Seek and iterate accross block boundary. - iter->Seek("b"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("bbb", iter->key()); - ub = Slice(ub2); - iter->Seek("b"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("bbb", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - delete iter; - - std::string lb1 = "a"; - std::string lb2 = "c"; - Slice lb(lb1); - ReadOptions read_opts2; - read_opts2.iterate_lower_bound = &lb; - iter = NewIterator(read_opts2); - iter->SeekForPrev("d"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("ccc", iter->key()); - lb = Slice(lb2); - iter->SeekForPrev("d"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("ccc", iter->key()); - iter->Prev(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - delete iter; -} - -TEST_P(DBIteratorTest, IterateWithLowerBoundAcrossFileBoundary) { - ASSERT_OK(Put("aaa", "v")); - ASSERT_OK(Put("bbb", "v")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("ccc", "v")); - ASSERT_OK(Put("ddd", "v")); - ASSERT_OK(Flush()); - // Move both files to bottom level. - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - Slice lower_bound("b"); - ReadOptions read_opts; - read_opts.iterate_lower_bound = &lower_bound; - std::unique_ptr iter(NewIterator(read_opts)); - iter->SeekForPrev("d"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("ccc", iter->key()); - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("bbb", iter->key()); - iter->Prev(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); -} - -TEST_P(DBIteratorTest, Blob) { - Options options = CurrentOptions(); - options.enable_blob_files = true; - options.max_sequential_skip_in_iterations = 2; - options.statistics = CreateDBStatistics(); - - Reopen(options); - - // Note: we have 4 KVs (3 of which are hidden) for key "b" and - // max_sequential_skip_in_iterations is set to 2. Thus, we need to do a reseek - // anytime we move from "b" to "c" or vice versa. - ASSERT_OK(Put("a", "va")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("b", "vb0")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("b", "vb1")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("b", "vb2")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("b", "vb3")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("c", "vc")); - ASSERT_OK(Flush()); - - std::unique_ptr iter_guard(NewIterator(ReadOptions())); - Iterator* const iter = iter_guard.get(); - - iter->SeekToFirst(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToFirst(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->SeekToLast(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 1); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekToLast(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - iter->Seek(""); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Seek("a"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "a->va"); - iter->Seek("ax"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - - iter->SeekForPrev("d"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->SeekForPrev("c"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 2); - ASSERT_EQ(IterStatus(iter), "c->vc"); - iter->SeekForPrev("bx"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 3); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - - iter->Seek("b"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 3); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - iter->Seek("z"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 3); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - iter->SeekForPrev("b"); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 4); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - iter->SeekForPrev(""); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 4); - ASSERT_EQ(IterStatus(iter), "(invalid)"); - - // Switch from reverse to forward - iter->SeekToLast(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 4); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 5); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 5); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 6); - ASSERT_EQ(IterStatus(iter), "b->vb3"); - - // Switch from forward to reverse - iter->SeekToFirst(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 6); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 6); - iter->Next(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 7); - iter->Prev(); - ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 8); - ASSERT_EQ(IterStatus(iter), "b->vb3"); -} - -INSTANTIATE_TEST_CASE_P(DBIteratorTestInstance, DBIteratorTest, - testing::Values(true, false)); - -// Tests how DBIter work with ReadCallback -class DBIteratorWithReadCallbackTest : public DBIteratorTest {}; - -TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) { - class TestReadCallback : public ReadCallback { - public: - explicit TestReadCallback(SequenceNumber _max_visible_seq) - : ReadCallback(_max_visible_seq) {} - - bool IsVisibleFullCheck(SequenceNumber seq) override { - return seq <= max_visible_seq_; - } - }; - - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Put("foo", "v2")); - ASSERT_OK(Put("foo", "v3")); - ASSERT_OK(Put("a", "va")); - ASSERT_OK(Put("z", "vz")); - SequenceNumber seq1 = db_->GetLatestSequenceNumber(); - TestReadCallback callback1(seq1); - ASSERT_OK(Put("foo", "v4")); - ASSERT_OK(Put("foo", "v5")); - ASSERT_OK(Put("bar", "v7")); - - SequenceNumber seq2 = db_->GetLatestSequenceNumber(); - auto* cfd = - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(); - // The iterator are suppose to see data before seq1. - Iterator* iter = - dbfull()->NewIteratorImpl(ReadOptions(), cfd, seq2, &callback1); - - // Seek - // The latest value of "foo" before seq1 is "v3" - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("foo", iter->key()); - ASSERT_EQ("v3", iter->value()); - // "bar" is not visible to the iterator. It will move on to the next key - // "foo". - iter->Seek("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("foo", iter->key()); - ASSERT_EQ("v3", iter->value()); - - // Next - // Seek to "a" - iter->Seek("a"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("va", iter->value()); - // "bar" is not visible to the iterator. It will move on to the next key - // "foo". - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("foo", iter->key()); - ASSERT_EQ("v3", iter->value()); - - // Prev - // Seek to "z" - iter->Seek("z"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("vz", iter->value()); - // The previous key is "foo", which is visible to the iterator. - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("foo", iter->key()); - ASSERT_EQ("v3", iter->value()); - // "bar" is not visible to the iterator. It will move on to the next key "a". - iter->Prev(); // skipping "bar" - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("a", iter->key()); - ASSERT_EQ("va", iter->value()); - - // SeekForPrev - // The previous key is "foo", which is visible to the iterator. - iter->SeekForPrev("y"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("foo", iter->key()); - ASSERT_EQ("v3", iter->value()); - // "bar" is not visible to the iterator. It will move on to the next key "a". - iter->SeekForPrev("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("a", iter->key()); - ASSERT_EQ("va", iter->value()); - - delete iter; - - // Prev beyond max_sequential_skip_in_iterations - uint64_t num_versions = - CurrentOptions().max_sequential_skip_in_iterations + 10; - for (uint64_t i = 0; i < num_versions; i++) { - ASSERT_OK(Put("bar", std::to_string(i))); - } - SequenceNumber seq3 = db_->GetLatestSequenceNumber(); - TestReadCallback callback2(seq3); - ASSERT_OK(Put("bar", "v8")); - SequenceNumber seq4 = db_->GetLatestSequenceNumber(); - - // The iterator is suppose to see data before seq3. - iter = dbfull()->NewIteratorImpl(ReadOptions(), cfd, seq4, &callback2); - // Seek to "z", which is visible. - iter->Seek("z"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("vz", iter->value()); - // Previous key is "foo" and the last value "v5" is visible. - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("foo", iter->key()); - ASSERT_EQ("v5", iter->value()); - // Since the number of values of "bar" is more than - // max_sequential_skip_in_iterations, Prev() will ultimately fallback to - // seek in forward direction. Here we test the fallback seek is correct. - // The last visible value should be (num_versions - 1), as "v8" is not - // visible. - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_EQ("bar", iter->key()); - ASSERT_EQ(std::to_string(num_versions - 1), iter->value()); - - delete iter; -} - -TEST_F(DBIteratorTest, BackwardIterationOnInplaceUpdateMemtable) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.inplace_update_support = false; - options.env = env_; - DestroyAndReopen(options); - constexpr int kNumKeys = 10; - - // Write kNumKeys to WAL. - for (int i = 0; i < kNumKeys; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ReadOptions read_opts; - read_opts.total_order_seek = true; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - int count = 0; - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - ++count; - } - ASSERT_EQ(kNumKeys, count); - } - - // Reopen and rebuild the memtable from WAL. - options.create_if_missing = false; - options.avoid_flush_during_recovery = true; - options.inplace_update_support = true; - options.allow_concurrent_memtable_write = false; - Reopen(options); - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->SeekToLast(); - // Backward iteration not supported due to inplace_update_support = true. - ASSERT_TRUE(iter->status().IsNotSupported()); - ASSERT_FALSE(iter->Valid()); - } -} - -TEST_F(DBIteratorTest, IteratorRefreshReturnSV) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - std::unique_ptr iter{db_->NewIterator(ReadOptions())}; - SyncPoint::GetInstance()->SetCallBack( - "ArenaWrappedDBIter::Refresh:SV", [&](void*) { - ASSERT_OK(db_->Put(WriteOptions(), "dummy", "new SV")); - // This makes the local SV obselete. - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - }); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(iter->Refresh()); - iter.reset(); - // iter used to not cleanup SV, so the Close() below would hit an assertion - // error. - Close(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_kv_checksum_test.cc b/db/db_kv_checksum_test.cc deleted file mode 100644 index 614399243..000000000 --- a/db/db_kv_checksum_test.cc +++ /dev/null @@ -1,885 +0,0 @@ -// Copyright (c) 2020-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/blob/blob_index.h" -#include "db/db_test_util.h" -#include "rocksdb/rocksdb_namespace.h" - -namespace ROCKSDB_NAMESPACE { - -enum class WriteBatchOpType { - kPut = 0, - kDelete, - kSingleDelete, - kMerge, - kPutEntity, - kDeleteRange, - kNum, -}; - -// Integer addition is needed for `::testing::Range()` to take the enum type. -WriteBatchOpType operator+(WriteBatchOpType lhs, const int rhs) { - using T = std::underlying_type::type; - return static_cast(static_cast(lhs) + rhs); -} - -enum class WriteMode { - // `Write()` a `WriteBatch` constructed with `protection_bytes_per_key = 0` - // and `WriteOptions::protection_bytes_per_key = 0` - kWriteUnprotectedBatch = 0, - // `Write()` a `WriteBatch` constructed with `protection_bytes_per_key > 0`. - kWriteProtectedBatch, - // `Write()` a `WriteBatch` constructed with `protection_bytes_per_key == 0`. - // Protection is enabled via `WriteOptions::protection_bytes_per_key > 0`. - kWriteOptionProtectedBatch, - // TODO(ajkr): add a mode that uses `Write()` wrappers, e.g., `Put()`. - kNum, -}; - -// Integer addition is needed for `::testing::Range()` to take the enum type. -WriteMode operator+(WriteMode lhs, const int rhs) { - using T = std::underlying_type::type; - return static_cast(static_cast(lhs) + rhs); -} - -std::pair GetWriteBatch(ColumnFamilyHandle* cf_handle, - size_t protection_bytes_per_key, - WriteBatchOpType op_type) { - Status s; - WriteBatch wb(0 /* reserved_bytes */, 0 /* max_bytes */, - protection_bytes_per_key, 0 /* default_cf_ts_sz */); - switch (op_type) { - case WriteBatchOpType::kPut: - s = wb.Put(cf_handle, "key", "val"); - break; - case WriteBatchOpType::kDelete: - s = wb.Delete(cf_handle, "key"); - break; - case WriteBatchOpType::kSingleDelete: - s = wb.SingleDelete(cf_handle, "key"); - break; - case WriteBatchOpType::kDeleteRange: - s = wb.DeleteRange(cf_handle, "begin", "end"); - break; - case WriteBatchOpType::kMerge: - s = wb.Merge(cf_handle, "key", "val"); - break; - case WriteBatchOpType::kPutEntity: - s = wb.PutEntity(cf_handle, "key", - {{"attr_name1", "foo"}, {"attr_name2", "bar"}}); - break; - case WriteBatchOpType::kNum: - assert(false); - } - return {std::move(wb), std::move(s)}; -} - -class DbKvChecksumTestBase : public DBTestBase { - public: - DbKvChecksumTestBase(const std::string& path, bool env_do_fsync) - : DBTestBase(path, env_do_fsync) {} - - ColumnFamilyHandle* GetCFHandleToUse(ColumnFamilyHandle* column_family, - WriteBatchOpType op_type) const { - // Note: PutEntity cannot be called without column family - if (op_type == WriteBatchOpType::kPutEntity && !column_family) { - return db_->DefaultColumnFamily(); - } - - return column_family; - } -}; - -class DbKvChecksumTest - : public DbKvChecksumTestBase, - public ::testing::WithParamInterface< - std::tuple> { - public: - DbKvChecksumTest() - : DbKvChecksumTestBase("db_kv_checksum_test", /*env_do_fsync=*/false) { - op_type_ = std::get<0>(GetParam()); - corrupt_byte_addend_ = std::get<1>(GetParam()); - write_mode_ = std::get<2>(GetParam()); - memtable_protection_bytes_per_key_ = std::get<3>(GetParam()); - } - - Status ExecuteWrite(ColumnFamilyHandle* cf_handle) { - switch (write_mode_) { - case WriteMode::kWriteUnprotectedBatch: { - auto batch_and_status = - GetWriteBatch(GetCFHandleToUse(cf_handle, op_type_), - 0 /* protection_bytes_per_key */, op_type_); - assert(batch_and_status.second.ok()); - // Default write option has protection_bytes_per_key = 0 - return db_->Write(WriteOptions(), &batch_and_status.first); - } - case WriteMode::kWriteProtectedBatch: { - auto batch_and_status = - GetWriteBatch(GetCFHandleToUse(cf_handle, op_type_), - 8 /* protection_bytes_per_key */, op_type_); - assert(batch_and_status.second.ok()); - return db_->Write(WriteOptions(), &batch_and_status.first); - } - case WriteMode::kWriteOptionProtectedBatch: { - auto batch_and_status = - GetWriteBatch(GetCFHandleToUse(cf_handle, op_type_), - 0 /* protection_bytes_per_key */, op_type_); - assert(batch_and_status.second.ok()); - WriteOptions write_opts; - write_opts.protection_bytes_per_key = 8; - return db_->Write(write_opts, &batch_and_status.first); - } - case WriteMode::kNum: - assert(false); - } - return Status::NotSupported("WriteMode " + - std::to_string(static_cast(write_mode_))); - } - - void CorruptNextByteCallBack(void* arg) { - Slice encoded = *static_cast(arg); - if (entry_len_ == std::numeric_limits::max()) { - // We learn the entry size on the first attempt - entry_len_ = encoded.size(); - } - char* buf = const_cast(encoded.data()); - buf[corrupt_byte_offset_] += corrupt_byte_addend_; - ++corrupt_byte_offset_; - } - - bool MoreBytesToCorrupt() { return corrupt_byte_offset_ < entry_len_; } - - protected: - WriteBatchOpType op_type_; - char corrupt_byte_addend_; - WriteMode write_mode_; - uint32_t memtable_protection_bytes_per_key_; - size_t corrupt_byte_offset_ = 0; - size_t entry_len_ = std::numeric_limits::max(); -}; - -std::string GetOpTypeString(const WriteBatchOpType& op_type) { - switch (op_type) { - case WriteBatchOpType::kPut: - return "Put"; - case WriteBatchOpType::kDelete: - return "Delete"; - case WriteBatchOpType::kSingleDelete: - return "SingleDelete"; - case WriteBatchOpType::kDeleteRange: - return "DeleteRange"; - case WriteBatchOpType::kMerge: - return "Merge"; - case WriteBatchOpType::kPutEntity: - return "PutEntity"; - case WriteBatchOpType::kNum: - assert(false); - } - assert(false); - return ""; -} - -std::string GetWriteModeString(const WriteMode& mode) { - switch (mode) { - case WriteMode::kWriteUnprotectedBatch: - return "WriteUnprotectedBatch"; - case WriteMode::kWriteProtectedBatch: - return "WriteProtectedBatch"; - case WriteMode::kWriteOptionProtectedBatch: - return "kWriteOptionProtectedBatch"; - case WriteMode::kNum: - assert(false); - } - return ""; -} - -INSTANTIATE_TEST_CASE_P( - DbKvChecksumTest, DbKvChecksumTest, - ::testing::Combine(::testing::Range(static_cast(0), - WriteBatchOpType::kNum), - ::testing::Values(2, 103, 251), - ::testing::Range(WriteMode::kWriteProtectedBatch, - WriteMode::kNum), - ::testing::Values(0)), - [](const testing::TestParamInfo< - std::tuple>& args) { - std::ostringstream oss; - oss << GetOpTypeString(std::get<0>(args.param)) << "Add" - << static_cast( - static_cast(std::get<1>(args.param))) - << GetWriteModeString(std::get<2>(args.param)) - << static_cast(std::get<3>(args.param)); - return oss.str(); - }); - -// TODO(ajkr): add a test that corrupts the `WriteBatch` contents. Such -// corruptions should only be detectable in `WriteMode::kWriteProtectedBatch`. - -TEST_P(DbKvChecksumTest, MemTableAddCorrupted) { - // This test repeatedly attempts to write `WriteBatch`es containing a single - // entry of type `op_type_`. Each attempt has one byte corrupted in its - // memtable entry by adding `corrupt_byte_addend_` to its original value. The - // test repeats until an attempt has been made on each byte in the encoded - // memtable entry. All attempts are expected to fail with `Status::Corruption` - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:Encoded", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - - while (MoreBytesToCorrupt()) { - // Failed memtable insert always leads to read-only mode, so we have to - // reopen for every attempt. - Options options = CurrentOptions(); - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - Reopen(options); - - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption()); - SyncPoint::GetInstance()->DisableProcessing(); - - // In case the above callback is not invoked, this test will run - // numeric_limits::max() times until it reports an error (or will - // exhaust disk space). Added this assert to report error early. - ASSERT_TRUE(entry_len_ < std::numeric_limits::max()); - } -} - -TEST_P(DbKvChecksumTest, MemTableAddWithColumnFamilyCorrupted) { - // This test repeatedly attempts to write `WriteBatch`es containing a single - // entry of type `op_type_` to a non-default column family. Each attempt has - // one byte corrupted in its memtable entry by adding `corrupt_byte_addend_` - // to its original value. The test repeats until an attempt has been made on - // each byte in the encoded memtable entry. All attempts are expected to fail - // with `Status::Corruption`. - Options options = CurrentOptions(); - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - CreateAndReopenWithCF({"pikachu"}, options); - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:Encoded", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - - while (MoreBytesToCorrupt()) { - // Failed memtable insert always leads to read-only mode, so we have to - // reopen for every attempt. - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE(ExecuteWrite(handles_[1]).IsCorruption()); - SyncPoint::GetInstance()->DisableProcessing(); - - // In case the above callback is not invoked, this test will run - // numeric_limits::max() times until it reports an error (or will - // exhaust disk space). Added this assert to report error early. - ASSERT_TRUE(entry_len_ < std::numeric_limits::max()); - } -} - -TEST_P(DbKvChecksumTest, NoCorruptionCase) { - // If this test fails, we may have found a piece of malfunctioned hardware - auto batch_and_status = - GetWriteBatch(GetCFHandleToUse(nullptr, op_type_), - 8 /* protection_bytes_per_key */, op_type_); - ASSERT_OK(batch_and_status.second); - ASSERT_OK(batch_and_status.first.VerifyChecksum()); -} - -TEST_P(DbKvChecksumTest, WriteToWALCorrupted) { - // This test repeatedly attempts to write `WriteBatch`es containing a single - // entry of type `op_type_`. Each attempt has one byte corrupted by adding - // `corrupt_byte_addend_` to its original value. The test repeats until an - // attempt has been made on each byte in the encoded write batch. All attempts - // are expected to fail with `Status::Corruption` - Options options = CurrentOptions(); - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::WriteToWAL:log_entry", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - // First 8 bytes are for sequence number which is not protected in write batch - corrupt_byte_offset_ = 8; - - while (MoreBytesToCorrupt()) { - // Corrupted write batch leads to read-only mode, so we have to - // reopen for every attempt. - Reopen(options); - auto log_size_pre_write = dbfull()->TEST_total_log_size(); - - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption()); - // Confirm that nothing was written to WAL - ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size()); - ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption()); - SyncPoint::GetInstance()->DisableProcessing(); - - // In case the above callback is not invoked, this test will run - // numeric_limits::max() times until it reports an error (or will - // exhaust disk space). Added this assert to report error early. - ASSERT_TRUE(entry_len_ < std::numeric_limits::max()); - } -} - -TEST_P(DbKvChecksumTest, WriteToWALWithColumnFamilyCorrupted) { - // This test repeatedly attempts to write `WriteBatch`es containing a single - // entry of type `op_type_`. Each attempt has one byte corrupted by adding - // `corrupt_byte_addend_` to its original value. The test repeats until an - // attempt has been made on each byte in the encoded write batch. All attempts - // are expected to fail with `Status::Corruption` - Options options = CurrentOptions(); - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - CreateAndReopenWithCF({"pikachu"}, options); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::WriteToWAL:log_entry", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - // First 8 bytes are for sequence number which is not protected in write batch - corrupt_byte_offset_ = 8; - - while (MoreBytesToCorrupt()) { - // Corrupted write batch leads to read-only mode, so we have to - // reopen for every attempt. - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - auto log_size_pre_write = dbfull()->TEST_total_log_size(); - - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption()); - // Confirm that nothing was written to WAL - ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size()); - ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption()); - SyncPoint::GetInstance()->DisableProcessing(); - - // In case the above callback is not invoked, this test will run - // numeric_limits::max() times until it reports an error (or will - // exhaust disk space). Added this assert to report error early. - ASSERT_TRUE(entry_len_ < std::numeric_limits::max()); - } -} - -class DbKvChecksumTestMergedBatch - : public DbKvChecksumTestBase, - public ::testing::WithParamInterface< - std::tuple> { - public: - DbKvChecksumTestMergedBatch() - : DbKvChecksumTestBase("db_kv_checksum_test", /*env_do_fsync=*/false) { - op_type1_ = std::get<0>(GetParam()); - op_type2_ = std::get<1>(GetParam()); - corrupt_byte_addend_ = std::get<2>(GetParam()); - } - - protected: - WriteBatchOpType op_type1_; - WriteBatchOpType op_type2_; - char corrupt_byte_addend_; -}; - -void CorruptWriteBatch(Slice* content, size_t offset, - char corrupt_byte_addend) { - ASSERT_TRUE(offset < content->size()); - char* buf = const_cast(content->data()); - buf[offset] += corrupt_byte_addend; -} - -TEST_P(DbKvChecksumTestMergedBatch, NoCorruptionCase) { - // Veirfy write batch checksum after write batch append - auto batch1 = GetWriteBatch(GetCFHandleToUse(nullptr, op_type1_), - 8 /* protection_bytes_per_key */, op_type1_); - ASSERT_OK(batch1.second); - auto batch2 = GetWriteBatch(GetCFHandleToUse(nullptr, op_type2_), - 8 /* protection_bytes_per_key */, op_type2_); - ASSERT_OK(batch2.second); - ASSERT_OK(WriteBatchInternal::Append(&batch1.first, &batch2.first)); - ASSERT_OK(batch1.first.VerifyChecksum()); -} - -TEST_P(DbKvChecksumTestMergedBatch, WriteToWALCorrupted) { - // This test has two writers repeatedly attempt to write `WriteBatch`es - // containing a single entry of type op_type1_ and op_type2_ respectively. The - // leader of the write group writes the batch containinng the entry of type - // op_type1_. One byte of the pre-merged write batches is corrupted by adding - // `corrupt_byte_addend_` to the batch's original value during each attempt. - // The test repeats until an attempt has been made on each byte in both - // pre-merged write batches. All attempts are expected to fail with - // `Status::Corruption`. - Options options = CurrentOptions(); - if (op_type1_ == WriteBatchOpType::kMerge || - op_type2_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - - auto leader_batch_and_status = - GetWriteBatch(GetCFHandleToUse(nullptr, op_type1_), - 8 /* protection_bytes_per_key */, op_type1_); - ASSERT_OK(leader_batch_and_status.second); - auto follower_batch_and_status = - GetWriteBatch(GetCFHandleToUse(nullptr, op_type2_), - 8 /* protection_bytes_per_key */, op_type2_); - size_t leader_batch_size = leader_batch_and_status.first.GetDataSize(); - size_t total_bytes = - leader_batch_size + follower_batch_and_status.first.GetDataSize(); - // First 8 bytes are for sequence number which is not protected in write batch - size_t corrupt_byte_offset = 8; - - std::atomic follower_joined{false}; - std::atomic leader_count{0}; - port::Thread follower_thread; - // This callback should only be called by the leader thread - SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait2", [&](void* arg_leader) { - auto* leader = reinterpret_cast(arg_leader); - ASSERT_EQ(leader->state, WriteThread::STATE_GROUP_LEADER); - - // This callback should only be called by the follower thread - SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait", [&](void* arg_follower) { - auto* follower = - reinterpret_cast(arg_follower); - // The leader thread will wait on this bool and hence wait until - // this writer joins the write group - ASSERT_NE(follower->state, WriteThread::STATE_GROUP_LEADER); - if (corrupt_byte_offset >= leader_batch_size) { - Slice batch_content = follower->batch->Data(); - CorruptWriteBatch(&batch_content, - corrupt_byte_offset - leader_batch_size, - corrupt_byte_addend_); - } - // Leader busy waits on this flag - follower_joined = true; - // So the follower does not enter the outer callback at - // WriteThread::JoinBatchGroup:Wait2 - SyncPoint::GetInstance()->DisableProcessing(); - }); - - // Start the other writer thread which will join the write group as - // follower - follower_thread = port::Thread([&]() { - follower_batch_and_status = - GetWriteBatch(GetCFHandleToUse(nullptr, op_type2_), - 8 /* protection_bytes_per_key */, op_type2_); - ASSERT_OK(follower_batch_and_status.second); - ASSERT_TRUE( - db_->Write(WriteOptions(), &follower_batch_and_status.first) - .IsCorruption()); - }); - - ASSERT_EQ(leader->batch->GetDataSize(), leader_batch_size); - if (corrupt_byte_offset < leader_batch_size) { - Slice batch_content = leader->batch->Data(); - CorruptWriteBatch(&batch_content, corrupt_byte_offset, - corrupt_byte_addend_); - } - leader_count++; - while (!follower_joined) { - // busy waiting - } - }); - while (corrupt_byte_offset < total_bytes) { - // Reopen DB since it failed WAL write which lead to read-only mode - Reopen(options); - SyncPoint::GetInstance()->EnableProcessing(); - auto log_size_pre_write = dbfull()->TEST_total_log_size(); - leader_batch_and_status = - GetWriteBatch(GetCFHandleToUse(nullptr, op_type1_), - 8 /* protection_bytes_per_key */, op_type1_); - ASSERT_OK(leader_batch_and_status.second); - ASSERT_TRUE(db_->Write(WriteOptions(), &leader_batch_and_status.first) - .IsCorruption()); - follower_thread.join(); - // Prevent leader thread from entering this callback - SyncPoint::GetInstance()->ClearCallBack("WriteThread::JoinBatchGroup:Wait"); - ASSERT_EQ(1, leader_count); - // Nothing should have been written to WAL - ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size()); - ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption()); - - corrupt_byte_offset++; - if (corrupt_byte_offset == leader_batch_size) { - // skip over the sequence number part of follower's write batch - corrupt_byte_offset += 8; - } - follower_joined = false; - leader_count = 0; - } - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DbKvChecksumTestMergedBatch, WriteToWALWithColumnFamilyCorrupted) { - // This test has two writers repeatedly attempt to write `WriteBatch`es - // containing a single entry of type op_type1_ and op_type2_ respectively. The - // leader of the write group writes the batch containinng the entry of type - // op_type1_. One byte of the pre-merged write batches is corrupted by adding - // `corrupt_byte_addend_` to the batch's original value during each attempt. - // The test repeats until an attempt has been made on each byte in both - // pre-merged write batches. All attempts are expected to fail with - // `Status::Corruption`. - Options options = CurrentOptions(); - if (op_type1_ == WriteBatchOpType::kMerge || - op_type2_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - CreateAndReopenWithCF({"ramen"}, options); - - auto leader_batch_and_status = - GetWriteBatch(GetCFHandleToUse(handles_[1], op_type1_), - 8 /* protection_bytes_per_key */, op_type1_); - ASSERT_OK(leader_batch_and_status.second); - auto follower_batch_and_status = - GetWriteBatch(GetCFHandleToUse(handles_[1], op_type2_), - 8 /* protection_bytes_per_key */, op_type2_); - size_t leader_batch_size = leader_batch_and_status.first.GetDataSize(); - size_t total_bytes = - leader_batch_size + follower_batch_and_status.first.GetDataSize(); - // First 8 bytes are for sequence number which is not protected in write batch - size_t corrupt_byte_offset = 8; - - std::atomic follower_joined{false}; - std::atomic leader_count{0}; - port::Thread follower_thread; - // This callback should only be called by the leader thread - SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait2", [&](void* arg_leader) { - auto* leader = reinterpret_cast(arg_leader); - ASSERT_EQ(leader->state, WriteThread::STATE_GROUP_LEADER); - - // This callback should only be called by the follower thread - SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait", [&](void* arg_follower) { - auto* follower = - reinterpret_cast(arg_follower); - // The leader thread will wait on this bool and hence wait until - // this writer joins the write group - ASSERT_NE(follower->state, WriteThread::STATE_GROUP_LEADER); - if (corrupt_byte_offset >= leader_batch_size) { - Slice batch_content = - WriteBatchInternal::Contents(follower->batch); - CorruptWriteBatch(&batch_content, - corrupt_byte_offset - leader_batch_size, - corrupt_byte_addend_); - } - follower_joined = true; - // So the follower does not enter the outer callback at - // WriteThread::JoinBatchGroup:Wait2 - SyncPoint::GetInstance()->DisableProcessing(); - }); - - // Start the other writer thread which will join the write group as - // follower - follower_thread = port::Thread([&]() { - follower_batch_and_status = - GetWriteBatch(GetCFHandleToUse(handles_[1], op_type2_), - 8 /* protection_bytes_per_key */, op_type2_); - ASSERT_OK(follower_batch_and_status.second); - ASSERT_TRUE( - db_->Write(WriteOptions(), &follower_batch_and_status.first) - .IsCorruption()); - }); - - ASSERT_EQ(leader->batch->GetDataSize(), leader_batch_size); - if (corrupt_byte_offset < leader_batch_size) { - Slice batch_content = WriteBatchInternal::Contents(leader->batch); - CorruptWriteBatch(&batch_content, corrupt_byte_offset, - corrupt_byte_addend_); - } - leader_count++; - while (!follower_joined) { - // busy waiting - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - while (corrupt_byte_offset < total_bytes) { - // Reopen DB since it failed WAL write which lead to read-only mode - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "ramen"}, options); - SyncPoint::GetInstance()->EnableProcessing(); - auto log_size_pre_write = dbfull()->TEST_total_log_size(); - leader_batch_and_status = - GetWriteBatch(GetCFHandleToUse(handles_[1], op_type1_), - 8 /* protection_bytes_per_key */, op_type1_); - ASSERT_OK(leader_batch_and_status.second); - ASSERT_TRUE(db_->Write(WriteOptions(), &leader_batch_and_status.first) - .IsCorruption()); - follower_thread.join(); - // Prevent leader thread from entering this callback - SyncPoint::GetInstance()->ClearCallBack("WriteThread::JoinBatchGroup:Wait"); - - ASSERT_EQ(1, leader_count); - // Nothing should have been written to WAL - ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size()); - ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption()); - - corrupt_byte_offset++; - if (corrupt_byte_offset == leader_batch_size) { - // skip over the sequence number part of follower's write batch - corrupt_byte_offset += 8; - } - follower_joined = false; - leader_count = 0; - } - SyncPoint::GetInstance()->DisableProcessing(); -} - -INSTANTIATE_TEST_CASE_P( - DbKvChecksumTestMergedBatch, DbKvChecksumTestMergedBatch, - ::testing::Combine(::testing::Range(static_cast(0), - WriteBatchOpType::kNum), - ::testing::Range(static_cast(0), - WriteBatchOpType::kNum), - ::testing::Values(2, 103, 251)), - [](const testing::TestParamInfo< - std::tuple>& args) { - std::ostringstream oss; - oss << GetOpTypeString(std::get<0>(args.param)) - << GetOpTypeString(std::get<1>(args.param)) << "Add" - << static_cast( - static_cast(std::get<2>(args.param))); - return oss.str(); - }); - -// TODO: add test for transactions -// TODO: add test for corrupted write batch with WAL disabled - -class DbKVChecksumWALToWriteBatchTest : public DBTestBase { - public: - DbKVChecksumWALToWriteBatchTest() - : DBTestBase("db_kv_checksum_test", /*env_do_fsync=*/false) {} -}; - -TEST_F(DbKVChecksumWALToWriteBatchTest, WriteBatchChecksumHandoff) { - Options options = CurrentOptions(); - Reopen(options); - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - std::string content = ""; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:batch", - [&](void* batch_ptr) { - WriteBatch* batch = reinterpret_cast(batch_ptr); - content.assign(batch->Data().data(), batch->GetDataSize()); - Slice batch_content = batch->Data(); - // Corrupt first bit - CorruptWriteBatch(&batch_content, 0, 1); - }); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:checksum", - [&](void* checksum_ptr) { - // Verify that checksum is produced on the batch content - uint64_t checksum = *reinterpret_cast(checksum_ptr); - ASSERT_EQ(checksum, XXH3_64bits(content.data(), content.size())); - }); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE(TryReopen(options).IsCorruption()); - SyncPoint::GetInstance()->DisableProcessing(); -}; - -// TODO (cbi): add DeleteRange coverage once it is implemented -class DbMemtableKVChecksumTest : public DbKvChecksumTest { - public: - DbMemtableKVChecksumTest() : DbKvChecksumTest() {} - - protected: - // Indices in the memtable entry that we will not corrupt. - // For memtable entry format, see comments in MemTable::Add(). - // We do not corrupt key length and value length fields in this test - // case since it causes segfault and ASAN will complain. - // For this test case, key and value are all of length 3, so - // key length field is at index 0 and value length field is at index 12. - const std::set index_not_to_corrupt{0, 12}; - - void SkipNotToCorruptEntry() { - if (index_not_to_corrupt.find(corrupt_byte_offset_) != - index_not_to_corrupt.end()) { - corrupt_byte_offset_++; - } - } -}; - -INSTANTIATE_TEST_CASE_P( - DbMemtableKVChecksumTest, DbMemtableKVChecksumTest, - ::testing::Combine(::testing::Range(static_cast(0), - WriteBatchOpType::kDeleteRange), - ::testing::Values(2, 103, 251), - ::testing::Range(static_cast(0), - WriteMode::kWriteOptionProtectedBatch), - // skip 1 byte checksum as it makes test flaky - ::testing::Values(2, 4, 8)), - [](const testing::TestParamInfo< - std::tuple>& args) { - std::ostringstream oss; - oss << GetOpTypeString(std::get<0>(args.param)) << "Add" - << static_cast( - static_cast(std::get<1>(args.param))) - << GetWriteModeString(std::get<2>(args.param)) - << static_cast(std::get<3>(args.param)); - return oss.str(); - }); - -TEST_P(DbMemtableKVChecksumTest, GetWithCorruptAfterMemtableInsert) { - // Record memtable entry size. - // Not corrupting memtable entry here since it will segfault - // or fail some asserts inside memtablerep implementation - // e.g., when key_len is corrupted. - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:BeforeReturn:Encoded", [&](void* arg) { - Slice encoded = *static_cast(arg); - entry_len_ = encoded.size(); - }); - - SyncPoint::GetInstance()->SetCallBack( - "Memtable::SaveValue:Begin:entry", [&](void* entry) { - char* buf = *static_cast(entry); - buf[corrupt_byte_offset_] += corrupt_byte_addend_; - ++corrupt_byte_offset_; - }); - SyncPoint::GetInstance()->EnableProcessing(); - Options options = CurrentOptions(); - options.memtable_protection_bytes_per_key = - memtable_protection_bytes_per_key_; - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - - SkipNotToCorruptEntry(); - while (MoreBytesToCorrupt()) { - Reopen(options); - ASSERT_OK(ExecuteWrite(nullptr)); - std::string val; - ASSERT_TRUE(db_->Get(ReadOptions(), "key", &val).IsCorruption()); - Destroy(options); - SkipNotToCorruptEntry(); - } -} - -TEST_P(DbMemtableKVChecksumTest, - GetWithColumnFamilyCorruptAfterMemtableInsert) { - // Record memtable entry size. - // Not corrupting memtable entry here since it will segfault - // or fail some asserts inside memtablerep implementation - // e.g., when key_len is corrupted. - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:BeforeReturn:Encoded", [&](void* arg) { - Slice encoded = *static_cast(arg); - entry_len_ = encoded.size(); - }); - - SyncPoint::GetInstance()->SetCallBack( - "Memtable::SaveValue:Begin:entry", [&](void* entry) { - char* buf = *static_cast(entry); - buf[corrupt_byte_offset_] += corrupt_byte_addend_; - ++corrupt_byte_offset_; - }); - SyncPoint::GetInstance()->EnableProcessing(); - Options options = CurrentOptions(); - options.memtable_protection_bytes_per_key = - memtable_protection_bytes_per_key_; - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - - SkipNotToCorruptEntry(); - while (MoreBytesToCorrupt()) { - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(ExecuteWrite(handles_[1])); - std::string val; - ASSERT_TRUE( - db_->Get(ReadOptions(), handles_[1], "key", &val).IsCorruption()); - Destroy(options); - SkipNotToCorruptEntry(); - } -} - -TEST_P(DbMemtableKVChecksumTest, IteratorWithCorruptAfterMemtableInsert) { - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:BeforeReturn:Encoded", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - SyncPoint::GetInstance()->EnableProcessing(); - Options options = CurrentOptions(); - options.memtable_protection_bytes_per_key = - memtable_protection_bytes_per_key_; - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - - SkipNotToCorruptEntry(); - while (MoreBytesToCorrupt()) { - Reopen(options); - ASSERT_OK(ExecuteWrite(nullptr)); - Iterator* it = db_->NewIterator(ReadOptions()); - it->SeekToFirst(); - ASSERT_FALSE(it->Valid()); - ASSERT_TRUE(it->status().IsCorruption()); - delete it; - Destroy(options); - SkipNotToCorruptEntry(); - } -} - -TEST_P(DbMemtableKVChecksumTest, - IteratorWithColumnFamilyCorruptAfterMemtableInsert) { - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:BeforeReturn:Encoded", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - SyncPoint::GetInstance()->EnableProcessing(); - Options options = CurrentOptions(); - options.memtable_protection_bytes_per_key = - memtable_protection_bytes_per_key_; - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - - SkipNotToCorruptEntry(); - while (MoreBytesToCorrupt()) { - Reopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(ExecuteWrite(handles_[1])); - Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]); - it->SeekToFirst(); - ASSERT_FALSE(it->Valid()); - ASSERT_TRUE(it->status().IsCorruption()); - delete it; - Destroy(options); - SkipNotToCorruptEntry(); - } -} - -TEST_P(DbMemtableKVChecksumTest, FlushWithCorruptAfterMemtableInsert) { - SyncPoint::GetInstance()->SetCallBack( - "MemTable::Add:BeforeReturn:Encoded", - std::bind(&DbKvChecksumTest::CorruptNextByteCallBack, this, - std::placeholders::_1)); - SyncPoint::GetInstance()->EnableProcessing(); - Options options = CurrentOptions(); - options.memtable_protection_bytes_per_key = - memtable_protection_bytes_per_key_; - if (op_type_ == WriteBatchOpType::kMerge) { - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - } - - SkipNotToCorruptEntry(); - // Not corruping each byte like other tests since Flush() is relatively slow. - Reopen(options); - ASSERT_OK(ExecuteWrite(nullptr)); - ASSERT_TRUE(Flush().IsCorruption()); - // DB enters read-only state when flush reads corrupted data - ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption()); - Destroy(options); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_log_iter_test.cc b/db/db_log_iter_test.cc deleted file mode 100644 index 4c9434586..000000000 --- a/db/db_log_iter_test.cc +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -// Introduction of SyncPoint effectively disabled building and running this test -// in Release build. -// which is a pity, it is a good test - -#include "db/db_test_util.h" -#include "env/mock_env.h" -#include "port/stack_trace.h" - -namespace ROCKSDB_NAMESPACE { - -class DBTestXactLogIterator : public DBTestBase { - public: - DBTestXactLogIterator() - : DBTestBase("db_log_iter_test", /*env_do_fsync=*/true) {} - - std::unique_ptr OpenTransactionLogIter( - const SequenceNumber seq) { - std::unique_ptr iter; - Status status = dbfull()->GetUpdatesSince(seq, &iter); - EXPECT_OK(status); - EXPECT_TRUE(iter->Valid()); - return iter; - } -}; - -namespace { -SequenceNumber ReadRecords(std::unique_ptr& iter, - int& count, bool expect_ok = true) { - count = 0; - SequenceNumber lastSequence = 0; - BatchResult res; - while (iter->Valid()) { - res = iter->GetBatch(); - EXPECT_TRUE(res.sequence > lastSequence); - ++count; - lastSequence = res.sequence; - EXPECT_OK(iter->status()); - iter->Next(); - } - if (expect_ok) { - EXPECT_OK(iter->status()); - } else { - EXPECT_NOK(iter->status()); - } - return res.sequence; -} - -void ExpectRecords(const int expected_no_records, - std::unique_ptr& iter) { - int num_records; - ReadRecords(iter, num_records); - ASSERT_EQ(num_records, expected_no_records); -} -} // anonymous namespace - -TEST_F(DBTestXactLogIterator, TransactionLogIterator) { - do { - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(0, "key1", DummyString(1024))); - ASSERT_OK(Put(1, "key2", DummyString(1024))); - ASSERT_OK(Put(1, "key2", DummyString(1024))); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3U); - { - auto iter = OpenTransactionLogIter(0); - ExpectRecords(3, iter); - } - ReopenWithColumnFamilies({"default", "pikachu"}, options); - env_->SleepForMicroseconds(2 * 1000 * 1000); - { - ASSERT_OK(Put(0, "key4", DummyString(1024))); - ASSERT_OK(Put(1, "key5", DummyString(1024))); - ASSERT_OK(Put(0, "key6", DummyString(1024))); - } - { - auto iter = OpenTransactionLogIter(0); - ExpectRecords(6, iter); - } - } while (ChangeCompactOptions()); -} - -#ifndef NDEBUG // sync point is not included with DNDEBUG build -TEST_F(DBTestXactLogIterator, TransactionLogIteratorRace) { - static const int LOG_ITERATOR_RACE_TEST_COUNT = 2; - static const char* sync_points[LOG_ITERATOR_RACE_TEST_COUNT][4] = { - {"WalManager::GetSortedWalFiles:1", "WalManager::PurgeObsoleteFiles:1", - "WalManager::PurgeObsoleteFiles:2", "WalManager::GetSortedWalFiles:2"}, - {"WalManager::GetSortedWalsOfType:1", "WalManager::PurgeObsoleteFiles:1", - "WalManager::PurgeObsoleteFiles:2", - "WalManager::GetSortedWalsOfType:2"}}; - for (int test = 0; test < LOG_ITERATOR_RACE_TEST_COUNT; ++test) { - // Setup sync point dependency to reproduce the race condition of - // a log file moved to archived dir, in the middle of GetSortedWalFiles - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {sync_points[test][0], sync_points[test][1]}, - {sync_points[test][2], sync_points[test][3]}, - }); - - do { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - ASSERT_OK(Put("key1", DummyString(1024))); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("key2", DummyString(1024))); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("key3", DummyString(1024))); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(Put("key4", DummyString(1024))); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4U); - ASSERT_OK(dbfull()->FlushWAL(false)); - - { - auto iter = OpenTransactionLogIter(0); - ExpectRecords(4, iter); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - // trigger async flush, and log move. Well, log move will - // wait until the GetSortedWalFiles:1 to reproduce the race - // condition - FlushOptions flush_options; - flush_options.wait = false; - ASSERT_OK(dbfull()->Flush(flush_options)); - - // "key5" would be written in a new memtable and log - ASSERT_OK(Put("key5", DummyString(1024))); - ASSERT_OK(dbfull()->FlushWAL(false)); - { - // this iter would miss "key4" if not fixed - auto iter = OpenTransactionLogIter(0); - ExpectRecords(5, iter); - } - } while (ChangeCompactOptions()); - } -} -#endif - -TEST_F(DBTestXactLogIterator, TransactionLogIteratorStallAtLastRecord) { - do { - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - ASSERT_OK(Put("key1", DummyString(1024))); - auto iter = OpenTransactionLogIter(0); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_OK(Put("key2", DummyString(1024))); - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestXactLogIterator, TransactionLogIteratorCheckAfterRestart) { - do { - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - ASSERT_OK(Put("key1", DummyString(1024))); - ASSERT_OK(Put("key2", DummyString(1023))); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - Reopen(options); - auto iter = OpenTransactionLogIter(0); - ExpectRecords(2, iter); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestXactLogIterator, TransactionLogIteratorCorruptedLog) { - do { - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - - for (int i = 0; i < 1024; i++) { - ASSERT_OK(Put("key" + std::to_string(i), DummyString(10))); - } - - ASSERT_OK(Flush()); - ASSERT_OK(db_->FlushWAL(false)); - - // Corrupt this log to create a gap - ASSERT_OK(db_->DisableFileDeletions()); - - VectorLogPtr wal_files; - ASSERT_OK(db_->GetSortedWalFiles(wal_files)); - ASSERT_FALSE(wal_files.empty()); - - const auto logfile_path = dbname_ + "/" + wal_files.front()->PathName(); - ASSERT_OK(test::TruncateFile(env_, logfile_path, - wal_files.front()->SizeFileBytes() / 2)); - - ASSERT_OK(db_->EnableFileDeletions()); - - // Insert a new entry to a new log file - ASSERT_OK(Put("key1025", DummyString(10))); - ASSERT_OK(db_->FlushWAL(false)); - - // Try to read from the beginning. Should stop before the gap and read less - // than 1025 entries - auto iter = OpenTransactionLogIter(0); - int count = 0; - SequenceNumber last_sequence_read = ReadRecords(iter, count, false); - ASSERT_LT(last_sequence_read, 1025U); - - // Try to read past the gap, should be able to seek to key1025 - auto iter2 = OpenTransactionLogIter(last_sequence_read + 1); - ExpectRecords(1, iter2); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestXactLogIterator, TransactionLogIteratorBatchOperations) { - do { - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - WriteBatch batch; - ASSERT_OK(batch.Put(handles_[1], "key1", DummyString(1024))); - ASSERT_OK(batch.Put(handles_[0], "key2", DummyString(1024))); - ASSERT_OK(batch.Put(handles_[1], "key3", DummyString(1024))); - ASSERT_OK(batch.Delete(handles_[0], "key2")); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush(1)); - ASSERT_OK(Flush(0)); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK(Put(1, "key4", DummyString(1024))); - auto iter = OpenTransactionLogIter(3); - ExpectRecords(2, iter); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTestXactLogIterator, TransactionLogIteratorBlobs) { - Options options = OptionsForLogIterTest(); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - { - WriteBatch batch; - ASSERT_OK(batch.Put(handles_[1], "key1", DummyString(1024))); - ASSERT_OK(batch.Put(handles_[0], "key2", DummyString(1024))); - ASSERT_OK(batch.PutLogData(Slice("blob1"))); - ASSERT_OK(batch.Put(handles_[1], "key3", DummyString(1024))); - ASSERT_OK(batch.PutLogData(Slice("blob2"))); - ASSERT_OK(batch.Delete(handles_[0], "key2")); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - } - - auto res = OpenTransactionLogIter(0)->GetBatch(); - struct Handler : public WriteBatch::Handler { - std::string seen; - Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override { - seen += "Put(" + std::to_string(cf) + ", " + key.ToString() + ", " + - std::to_string(value.size()) + ")"; - return Status::OK(); - } - Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override { - seen += "Merge(" + std::to_string(cf) + ", " + key.ToString() + ", " + - std::to_string(value.size()) + ")"; - return Status::OK(); - } - void LogData(const Slice& blob) override { - seen += "LogData(" + blob.ToString() + ")"; - } - Status DeleteCF(uint32_t cf, const Slice& key) override { - seen += "Delete(" + std::to_string(cf) + ", " + key.ToString() + ")"; - return Status::OK(); - } - } handler; - ASSERT_OK(res.writeBatchPtr->Iterate(&handler)); - ASSERT_EQ( - "Put(1, key1, 1024)" - "Put(0, key2, 1024)" - "LogData(blob1)" - "Put(1, key3, 1024)" - "LogData(blob2)" - "Delete(0, key2)", - handler.seen); -} -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_logical_block_size_cache_test.cc b/db/db_logical_block_size_cache_test.cc deleted file mode 100644 index ff56d56e3..000000000 --- a/db/db_logical_block_size_cache_test.cc +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright (c) 2020-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "test_util/testharness.h" - -#ifdef OS_LINUX -#include "env/io_posix.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" - -namespace ROCKSDB_NAMESPACE { -class EnvWithCustomLogicalBlockSizeCache : public EnvWrapper { - public: - EnvWithCustomLogicalBlockSizeCache(Env* env, LogicalBlockSizeCache* cache) - : EnvWrapper(env), cache_(cache) {} - - Status RegisterDbPaths(const std::vector& paths) override { - return cache_->RefAndCacheLogicalBlockSize(paths); - } - - Status UnregisterDbPaths(const std::vector& paths) override { - cache_->UnrefAndTryRemoveCachedLogicalBlockSize(paths); - return Status::OK(); - } - - private: - LogicalBlockSizeCache* cache_; -}; - -class DBLogicalBlockSizeCacheTest : public testing::Test { - public: - DBLogicalBlockSizeCacheTest() - : dbname_(test::PerThreadDBPath("logical_block_size_cache_test")), - data_path_0_(dbname_ + "/data_path_0"), - data_path_1_(dbname_ + "/data_path_1"), - cf_path_0_(dbname_ + "/cf_path_0"), - cf_path_1_(dbname_ + "/cf_path_1") { - auto get_fd_block_size = [&](int fd) { return fd; }; - auto get_dir_block_size = [&](const std::string& /*dir*/, size_t* size) { - *size = 1024; - return Status::OK(); - }; - cache_.reset( - new LogicalBlockSizeCache(get_fd_block_size, get_dir_block_size)); - env_.reset( - new EnvWithCustomLogicalBlockSizeCache(Env::Default(), cache_.get())); - } - - protected: - std::string dbname_; - std::string data_path_0_; - std::string data_path_1_; - std::string cf_path_0_; - std::string cf_path_1_; - std::unique_ptr cache_; - std::unique_ptr env_; -}; - -TEST_F(DBLogicalBlockSizeCacheTest, OpenClose) { - // Tests that Open will cache the logical block size for data paths, - // and Close will remove the cached sizes. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - options.db_paths = {{data_path_0_, 2048}, {data_path_1_, 2048}}; - - for (int i = 0; i < 2; i++) { - DB* db; - if (!i) { - printf("Open\n"); - ASSERT_OK(DB::Open(options, dbname_, &db)); - } else { - printf("OpenForReadOnly\n"); - ASSERT_OK(DB::OpenForReadOnly(options, dbname_, &db)); - } - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(data_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_1_)); - ASSERT_OK(db->Close()); - ASSERT_EQ(0, cache_->Size()); - delete db; - } - ASSERT_OK(DestroyDB(dbname_, options, {})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, OpenDelete) { - // Tests that Open will cache the logical block size for data paths, - // and delete the db pointer will remove the cached sizes. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - - for (int i = 0; i < 2; i++) { - DB* db; - if (!i) { - printf("Open\n"); - ASSERT_OK(DB::Open(options, dbname_, &db)); - } else { - printf("OpenForReadOnly\n"); - ASSERT_OK(DB::OpenForReadOnly(options, dbname_, &db)); - } - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - delete db; - ASSERT_EQ(0, cache_->Size()); - } - ASSERT_OK(DestroyDB(dbname_, options, {})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamily) { - // Tests that CreateColumnFamily will cache the cf_paths, - // drop the column family handle won't drop the cache, - // drop and then delete the column family handle will drop the cache. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - ColumnFamilyOptions cf_options; - cf_options.cf_paths = {{cf_path_0_, 1024}, {cf_path_1_, 2048}}; - - DB* db; - ASSERT_OK(DB::Open(options, dbname_, &db)); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - - ColumnFamilyHandle* cf = nullptr; - ASSERT_OK(db->CreateColumnFamily(cf_options, "cf", &cf)); - ASSERT_EQ(3, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_)); - - // Drop column family does not drop cache. - ASSERT_OK(db->DropColumnFamily(cf)); - ASSERT_EQ(3, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_)); - - // Delete handle will drop cache. - ASSERT_OK(db->DestroyColumnFamilyHandle(cf)); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - - delete db; - ASSERT_EQ(0, cache_->Size()); - ASSERT_OK(DestroyDB(dbname_, options, {{"cf", cf_options}})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamilies) { - // To test: - // (1) CreateColumnFamilies will cache the cf_paths in - // DBLogicalBlockSizeCache - // (2) Dropping column family handles associated with - // that cf_paths won't drop the cached cf_paths - // (3) Deleting all the column family handles associated - // with that cf_paths will drop the cached cf_paths - - Options options; - options.create_if_missing = true; - options.env = env_.get(); - ColumnFamilyOptions cf_options; - cf_options.cf_paths = {{cf_path_0_, 1024}}; - - DB* db; - ASSERT_OK(DB::Open(options, dbname_, &db)); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - - std::vector cfs; - ASSERT_OK(db->CreateColumnFamilies(cf_options, {"cf1", "cf2"}, &cfs)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_)); - - // Drop column family does not drop cf_path_0_'s entry from cache - for (ColumnFamilyHandle* cf : cfs) { - ASSERT_OK(db->DropColumnFamily(cf)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_)); - } - - // Delete one cf handle will not drop cf_path_0_'s entry from cache because - // another handle is still referencing cf_path_0_. - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[0])); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - - // Delete all cf handles and ensure the ref count of cf_path_0_ in cache_ - // can be properly decreased by releasing any background reference to the - // ColumnFamilyData during db deletion - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1])); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - delete db; - - // Now cf_path_0_ in cache_ has been properly decreased and cf_path_0_'s entry - // is dropped from cache - ASSERT_EQ(0, cache_->Size()); - ASSERT_OK( - DestroyDB(dbname_, options, {{"cf1", cf_options}, {"cf2", cf_options}})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, OpenWithColumnFamilies) { - // Tests that Open two column families with the same cf_path will cache the - // cf_path and have 2 references to the cached size, - // drop the column family handle won't drop the cache, - // drop and then delete the column family handle will drop the cache. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - - ColumnFamilyOptions cf_options; - cf_options.cf_paths = {{cf_path_0_, 1024}}; - - for (int i = 0; i < 2; i++) { - DB* db; - ColumnFamilyHandle* cf1 = nullptr; - ColumnFamilyHandle* cf2 = nullptr; - ASSERT_OK(DB::Open(options, dbname_, &db)); - ASSERT_OK(db->CreateColumnFamily(cf_options, "cf1", &cf1)); - ASSERT_OK(db->CreateColumnFamily(cf_options, "cf2", &cf2)); - ASSERT_OK(db->DestroyColumnFamilyHandle(cf1)); - ASSERT_OK(db->DestroyColumnFamilyHandle(cf2)); - delete db; - ASSERT_EQ(0, cache_->Size()); - - std::vector cfs; - if (!i) { - printf("Open\n"); - ASSERT_OK(DB::Open(options, dbname_, - {{"cf1", cf_options}, - {"cf2", cf_options}, - {"default", ColumnFamilyOptions()}}, - &cfs, &db)); - } else { - printf("OpenForReadOnly\n"); - ASSERT_OK(DB::OpenForReadOnly(options, dbname_, - {{"cf1", cf_options}, - {"cf2", cf_options}, - {"default", ColumnFamilyOptions()}}, - &cfs, &db)); - } - - // Logical block sizes of dbname_ and cf_path_0_ are cached during Open. - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_)); - - // Drop handles won't drop the cache. - ASSERT_OK(db->DropColumnFamily(cfs[0])); - ASSERT_OK(db->DropColumnFamily(cfs[1])); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_)); - - // Delete 1st handle won't drop the cache for cf_path_0_. - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[0])); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - // Delete 2nd handle will drop the cache for cf_path_0_. - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1])); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - - // Delete the default handle won't affect the cache because db still refers - // to the default CF. - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[2])); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - - delete db; - ASSERT_EQ(0, cache_->Size()); - } - ASSERT_OK( - DestroyDB(dbname_, options, {{"cf1", cf_options}, {"cf2", cf_options}})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, DestroyColumnFamilyHandle) { - // Tests that destroy column family without dropping won't drop the cache, - // because compaction and flush might still need to get logical block size - // when opening new files. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - ColumnFamilyOptions cf_options; - cf_options.cf_paths = {{cf_path_0_, 1024}}; - - DB* db; - ASSERT_OK(DB::Open(options, dbname_, &db)); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ColumnFamilyHandle* cf = nullptr; - ASSERT_OK(db->CreateColumnFamily(cf_options, "cf", &cf)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - // Delete handle won't drop cache. - ASSERT_OK(db->DestroyColumnFamilyHandle(cf)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - delete db; - ASSERT_EQ(0, cache_->Size()); - - // Open with column families. - std::vector cfs; - for (int i = 0; i < 2; i++) { - if (!i) { - printf("Open\n"); - ASSERT_OK(DB::Open( - options, dbname_, - {{"cf", cf_options}, {"default", ColumnFamilyOptions()}}, &cfs, &db)); - } else { - printf("OpenForReadOnly\n"); - ASSERT_OK(DB::OpenForReadOnly( - options, dbname_, - {{"cf", cf_options}, {"default", ColumnFamilyOptions()}}, &cfs, &db)); - } - // cf_path_0_ and dbname_ are cached. - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - // Deleting handle won't drop cache. - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[0])); - ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1])); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(dbname_)); - ASSERT_EQ(1, cache_->GetRefCount(dbname_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - delete db; - ASSERT_EQ(0, cache_->Size()); - } - ASSERT_OK(DestroyDB(dbname_, options, {{"cf", cf_options}})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithDifferentPaths) { - // Tests the cache behavior when there are multiple DBs sharing the same env - // with different db_paths and cf_paths. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - - ASSERT_OK(env_->CreateDirIfMissing(dbname_)); - - DB* db0; - ASSERT_OK(DB::Open(options, data_path_0_, &db0)); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - - ColumnFamilyOptions cf_options0; - cf_options0.cf_paths = {{cf_path_0_, 1024}}; - ColumnFamilyHandle* cf0; - ASSERT_OK(db0->CreateColumnFamily(cf_options0, "cf", &cf0)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - DB* db1; - ASSERT_OK(DB::Open(options, data_path_1_, &db1)); - ASSERT_EQ(3, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - ASSERT_TRUE(cache_->Contains(data_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_1_)); - - ColumnFamilyOptions cf_options1; - cf_options1.cf_paths = {{cf_path_1_, 1024}}; - ColumnFamilyHandle* cf1; - ASSERT_OK(db1->CreateColumnFamily(cf_options1, "cf", &cf1)); - ASSERT_EQ(4, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - ASSERT_TRUE(cache_->Contains(data_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_1_)); - ASSERT_TRUE(cache_->Contains(cf_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_)); - - ASSERT_OK(db0->DestroyColumnFamilyHandle(cf0)); - delete db0; - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_1_)); - ASSERT_TRUE(cache_->Contains(cf_path_1_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_)); - ASSERT_OK(DestroyDB(data_path_0_, options, {{"cf", cf_options0}})); - - ASSERT_OK(db1->DestroyColumnFamilyHandle(cf1)); - delete db1; - ASSERT_EQ(0, cache_->Size()); - ASSERT_OK(DestroyDB(data_path_1_, options, {{"cf", cf_options1}})); -} - -TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) { - // Tests the cache behavior when there are multiple DBs sharing the same env - // with the same db_paths and cf_paths. - Options options; - options.create_if_missing = true; - options.env = env_.get(); - options.db_paths = {{data_path_0_, 1024}}; - ColumnFamilyOptions cf_options; - cf_options.cf_paths = {{cf_path_0_, 1024}}; - - ASSERT_OK(env_->CreateDirIfMissing(dbname_)); - - DB* db0; - ASSERT_OK(DB::Open(options, dbname_ + "/db0", &db0)); - ASSERT_EQ(1, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - - ColumnFamilyHandle* cf0; - ASSERT_OK(db0->CreateColumnFamily(cf_options, "cf", &cf0)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - DB* db1; - ASSERT_OK(DB::Open(options, dbname_ + "/db1", &db1)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - - ColumnFamilyHandle* cf1; - ASSERT_OK(db1->CreateColumnFamily(cf_options, "cf", &cf1)); - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_)); - - ASSERT_OK(db0->DestroyColumnFamilyHandle(cf0)); - delete db0; - ASSERT_EQ(2, cache_->Size()); - ASSERT_TRUE(cache_->Contains(data_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(data_path_0_)); - ASSERT_TRUE(cache_->Contains(cf_path_0_)); - ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_)); - ASSERT_OK(DestroyDB(dbname_ + "/db0", options, {{"cf", cf_options}})); - - ASSERT_OK(db1->DestroyColumnFamilyHandle(cf1)); - delete db1; - ASSERT_EQ(0, cache_->Size()); - ASSERT_OK(DestroyDB(dbname_ + "/db1", options, {{"cf", cf_options}})); -} - -} // namespace ROCKSDB_NAMESPACE -#endif // OS_LINUX - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_memtable_test.cc b/db/db_memtable_test.cc deleted file mode 100644 index cae592db3..000000000 --- a/db/db_memtable_test.cc +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include - -#include "db/db_test_util.h" -#include "db/memtable.h" -#include "db/range_del_aggregator.h" -#include "port/stack_trace.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/slice_transform.h" - -namespace ROCKSDB_NAMESPACE { - -class DBMemTableTest : public DBTestBase { - public: - DBMemTableTest() : DBTestBase("db_memtable_test", /*env_do_fsync=*/true) {} -}; - -class MockMemTableRep : public MemTableRep { - public: - explicit MockMemTableRep(Allocator* allocator, MemTableRep* rep) - : MemTableRep(allocator), rep_(rep), num_insert_with_hint_(0) {} - - KeyHandle Allocate(const size_t len, char** buf) override { - return rep_->Allocate(len, buf); - } - - void Insert(KeyHandle handle) override { rep_->Insert(handle); } - - void InsertWithHint(KeyHandle handle, void** hint) override { - num_insert_with_hint_++; - EXPECT_NE(nullptr, hint); - last_hint_in_ = *hint; - rep_->InsertWithHint(handle, hint); - last_hint_out_ = *hint; - } - - bool Contains(const char* key) const override { return rep_->Contains(key); } - - void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, const char* entry)) override { - rep_->Get(k, callback_args, callback_func); - } - - size_t ApproximateMemoryUsage() override { - return rep_->ApproximateMemoryUsage(); - } - - Iterator* GetIterator(Arena* arena) override { - return rep_->GetIterator(arena); - } - - void* last_hint_in() { return last_hint_in_; } - void* last_hint_out() { return last_hint_out_; } - int num_insert_with_hint() { return num_insert_with_hint_; } - - private: - std::unique_ptr rep_; - void* last_hint_in_; - void* last_hint_out_; - int num_insert_with_hint_; -}; - -class MockMemTableRepFactory : public MemTableRepFactory { - public: - MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp, - Allocator* allocator, - const SliceTransform* transform, - Logger* logger) override { - SkipListFactory factory; - MemTableRep* skiplist_rep = - factory.CreateMemTableRep(cmp, allocator, transform, logger); - mock_rep_ = new MockMemTableRep(allocator, skiplist_rep); - return mock_rep_; - } - - MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp, - Allocator* allocator, - const SliceTransform* transform, - Logger* logger, - uint32_t column_family_id) override { - last_column_family_id_ = column_family_id; - return CreateMemTableRep(cmp, allocator, transform, logger); - } - - const char* Name() const override { return "MockMemTableRepFactory"; } - - MockMemTableRep* rep() { return mock_rep_; } - - bool IsInsertConcurrentlySupported() const override { return false; } - - uint32_t GetLastColumnFamilyId() { return last_column_family_id_; } - - private: - MockMemTableRep* mock_rep_; - // workaround since there's no std::numeric_limits::max() yet. - uint32_t last_column_family_id_ = static_cast(-1); -}; - -class TestPrefixExtractor : public SliceTransform { - public: - const char* Name() const override { return "TestPrefixExtractor"; } - - Slice Transform(const Slice& key) const override { - const char* p = separator(key); - if (p == nullptr) { - return Slice(); - } - return Slice(key.data(), p - key.data() + 1); - } - - bool InDomain(const Slice& key) const override { - return separator(key) != nullptr; - } - - bool InRange(const Slice& /*key*/) const override { return false; } - - private: - const char* separator(const Slice& key) const { - return reinterpret_cast(memchr(key.data(), '_', key.size())); - } -}; - -// Test that ::Add properly returns false when inserting duplicate keys -TEST_F(DBMemTableTest, DuplicateSeq) { - SequenceNumber seq = 123; - std::string value; - MergeContext merge_context; - Options options; - InternalKeyComparator ikey_cmp(options.comparator); - ReadRangeDelAggregator range_del_agg(&ikey_cmp, - kMaxSequenceNumber /* upper_bound */); - - // Create a MemTable - InternalKeyComparator cmp(BytewiseComparator()); - auto factory = std::make_shared(); - options.memtable_factory = factory; - ImmutableOptions ioptions(options); - WriteBufferManager wb(options.db_write_buffer_size); - MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - - // Write some keys and make sure it returns false on duplicates - ASSERT_OK( - mem->Add(seq, kTypeValue, "key", "value2", nullptr /* kv_prot_info */)); - ASSERT_TRUE( - mem->Add(seq, kTypeValue, "key", "value2", nullptr /* kv_prot_info */) - .IsTryAgain()); - // Changing the type should still cause the duplicatae key - ASSERT_TRUE( - mem->Add(seq, kTypeMerge, "key", "value2", nullptr /* kv_prot_info */) - .IsTryAgain()); - // Changing the seq number will make the key fresh - ASSERT_OK(mem->Add(seq + 1, kTypeMerge, "key", "value2", - nullptr /* kv_prot_info */)); - // Test with different types for duplicate keys - ASSERT_TRUE( - mem->Add(seq, kTypeDeletion, "key", "", nullptr /* kv_prot_info */) - .IsTryAgain()); - ASSERT_TRUE( - mem->Add(seq, kTypeSingleDeletion, "key", "", nullptr /* kv_prot_info */) - .IsTryAgain()); - - // Test the duplicate keys under stress - for (int i = 0; i < 10000; i++) { - bool insert_dup = i % 10 == 1; - if (!insert_dup) { - seq++; - } - Status s = mem->Add(seq, kTypeValue, "foo", "value" + std::to_string(seq), - nullptr /* kv_prot_info */); - if (insert_dup) { - ASSERT_TRUE(s.IsTryAgain()); - } else { - ASSERT_OK(s); - } - } - delete mem; - - // Test with InsertWithHint - options.memtable_insert_with_hint_prefix_extractor.reset( - new TestPrefixExtractor()); // which uses _ to extract the prefix - ioptions = ImmutableOptions(options); - mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - // Insert a duplicate key with _ in it - ASSERT_OK( - mem->Add(seq, kTypeValue, "key_1", "value", nullptr /* kv_prot_info */)); - ASSERT_TRUE( - mem->Add(seq, kTypeValue, "key_1", "value", nullptr /* kv_prot_info */) - .IsTryAgain()); - delete mem; - - // Test when InsertConcurrently will be invoked - options.allow_concurrent_memtable_write = true; - ioptions = ImmutableOptions(options); - mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - MemTablePostProcessInfo post_process_info; - ASSERT_OK(mem->Add(seq, kTypeValue, "key", "value", - nullptr /* kv_prot_info */, true, &post_process_info)); - ASSERT_TRUE(mem->Add(seq, kTypeValue, "key", "value", - nullptr /* kv_prot_info */, true, &post_process_info) - .IsTryAgain()); - delete mem; -} - -// A simple test to verify that the concurrent merge writes is functional -TEST_F(DBMemTableTest, ConcurrentMergeWrite) { - int num_ops = 1000; - std::string value; - MergeContext merge_context; - Options options; - // A merge operator that is not sensitive to concurrent writes since in this - // test we don't order the writes. - options.merge_operator = MergeOperators::CreateUInt64AddOperator(); - - // Create a MemTable - InternalKeyComparator cmp(BytewiseComparator()); - auto factory = std::make_shared(); - options.memtable_factory = factory; - options.allow_concurrent_memtable_write = true; - ImmutableOptions ioptions(options); - WriteBufferManager wb(options.db_write_buffer_size); - MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - - // Put 0 as the base - PutFixed64(&value, static_cast(0)); - ASSERT_OK(mem->Add(0, kTypeValue, "key", value, nullptr /* kv_prot_info */)); - value.clear(); - - // Write Merge concurrently - ROCKSDB_NAMESPACE::port::Thread write_thread1([&]() { - MemTablePostProcessInfo post_process_info1; - std::string v1; - for (int seq = 1; seq < num_ops / 2; seq++) { - PutFixed64(&v1, seq); - ASSERT_OK(mem->Add(seq, kTypeMerge, "key", v1, nullptr /* kv_prot_info */, - true, &post_process_info1)); - v1.clear(); - } - }); - ROCKSDB_NAMESPACE::port::Thread write_thread2([&]() { - MemTablePostProcessInfo post_process_info2; - std::string v2; - for (int seq = num_ops / 2; seq < num_ops; seq++) { - PutFixed64(&v2, seq); - ASSERT_OK(mem->Add(seq, kTypeMerge, "key", v2, nullptr /* kv_prot_info */, - true, &post_process_info2)); - v2.clear(); - } - }); - write_thread1.join(); - write_thread2.join(); - - Status status; - ReadOptions roptions; - SequenceNumber max_covering_tombstone_seq = 0; - LookupKey lkey("key", kMaxSequenceNumber); - bool res = mem->Get(lkey, &value, /*columns=*/nullptr, /*timestamp=*/nullptr, - &status, &merge_context, &max_covering_tombstone_seq, - roptions, false /* immutable_memtable */); - ASSERT_OK(status); - ASSERT_TRUE(res); - uint64_t ivalue = DecodeFixed64(Slice(value).data()); - uint64_t sum = 0; - for (int seq = 0; seq < num_ops; seq++) { - sum += seq; - } - ASSERT_EQ(ivalue, sum); - - delete mem; -} - -TEST_F(DBMemTableTest, InsertWithHint) { - Options options; - options.allow_concurrent_memtable_write = false; - options.create_if_missing = true; - options.memtable_factory.reset(new MockMemTableRepFactory()); - options.memtable_insert_with_hint_prefix_extractor.reset( - new TestPrefixExtractor()); - options.env = env_; - Reopen(options); - MockMemTableRep* rep = - reinterpret_cast(options.memtable_factory.get()) - ->rep(); - ASSERT_OK(Put("foo_k1", "foo_v1")); - ASSERT_EQ(nullptr, rep->last_hint_in()); - void* hint_foo = rep->last_hint_out(); - ASSERT_OK(Put("foo_k2", "foo_v2")); - ASSERT_EQ(hint_foo, rep->last_hint_in()); - ASSERT_EQ(hint_foo, rep->last_hint_out()); - ASSERT_OK(Put("foo_k3", "foo_v3")); - ASSERT_EQ(hint_foo, rep->last_hint_in()); - ASSERT_EQ(hint_foo, rep->last_hint_out()); - ASSERT_OK(Put("bar_k1", "bar_v1")); - ASSERT_EQ(nullptr, rep->last_hint_in()); - void* hint_bar = rep->last_hint_out(); - ASSERT_NE(hint_foo, hint_bar); - ASSERT_OK(Put("bar_k2", "bar_v2")); - ASSERT_EQ(hint_bar, rep->last_hint_in()); - ASSERT_EQ(hint_bar, rep->last_hint_out()); - ASSERT_EQ(5, rep->num_insert_with_hint()); - ASSERT_OK(Put("NotInPrefixDomain", "vvv")); - ASSERT_EQ(5, rep->num_insert_with_hint()); - ASSERT_EQ("foo_v1", Get("foo_k1")); - ASSERT_EQ("foo_v2", Get("foo_k2")); - ASSERT_EQ("foo_v3", Get("foo_k3")); - ASSERT_EQ("bar_v1", Get("bar_k1")); - ASSERT_EQ("bar_v2", Get("bar_k2")); - ASSERT_EQ("vvv", Get("NotInPrefixDomain")); -} - -TEST_F(DBMemTableTest, ColumnFamilyId) { - // Verifies MemTableRepFactory is told the right column family id. - Options options; - options.env = CurrentOptions().env; - options.allow_concurrent_memtable_write = false; - options.create_if_missing = true; - options.memtable_factory.reset(new MockMemTableRepFactory()); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - for (uint32_t cf = 0; cf < 2; ++cf) { - ASSERT_OK(Put(cf, "key", "val")); - ASSERT_OK(Flush(cf)); - ASSERT_EQ( - cf, static_cast(options.memtable_factory.get()) - ->GetLastColumnFamilyId()); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_merge_operand_test.cc b/db/db_merge_operand_test.cc deleted file mode 100644 index 774ae4a96..000000000 --- a/db/db_merge_operand_test.cc +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright (c) 2018-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/db_test_util.h" -#include "port/stack_trace.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/utilities/debug.h" -#include "table/block_based/block_builder.h" -#include "test_util/sync_point.h" -#include "rocksdb/merge_operator.h" -#include "utilities/fault_injection_env.h" -#include "utilities/merge_operators.h" -#include "utilities/merge_operators/sortlist.h" -#include "utilities/merge_operators/string_append/stringappend2.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -class LimitedStringAppendMergeOp : public StringAppendTESTOperator { - public: - LimitedStringAppendMergeOp(int limit, char delim) - : StringAppendTESTOperator(delim), limit_(limit) {} - - const char* Name() const override { - return "DBMergeOperatorTest::LimitedStringAppendMergeOp"; - } - - bool ShouldMerge(const std::vector& operands) const override { - if (operands.size() > 0 && limit_ > 0 && operands.size() >= limit_) { - return true; - } - return false; - } - - private: - size_t limit_ = 0; -}; -} // anonymous namespace - -class DBMergeOperandTest : public DBTestBase { - public: - DBMergeOperandTest() - : DBTestBase("db_merge_operand_test", /*env_do_fsync=*/true) {} -}; - -TEST_F(DBMergeOperandTest, CacheEvictedMergeOperandReadAfterFreeBug) { - // There was a bug of reading merge operands after they are mistakely freed - // in DB::GetMergeOperands, which is surfaced by cache full. - // See PR#9507 for more. - Options options; - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - options.env = env_; - BlockBasedTableOptions table_options; - - // Small cache to simulate cache full - table_options.block_cache = NewLRUCache(1); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Reopen(options); - int num_records = 4; - int number_of_operands = 0; - std::vector values(num_records); - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = num_records; - - ASSERT_OK(Merge("k1", "v1")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k1", "v2")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k1", "v3")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k1", "v4")); - - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(number_of_operands, 4); - ASSERT_EQ(values[0].ToString(), "v1"); - ASSERT_EQ(values[1].ToString(), "v2"); - ASSERT_EQ(values[2].ToString(), "v3"); - ASSERT_EQ(values[3].ToString(), "v4"); -} - -TEST_F(DBMergeOperandTest, FlushedMergeOperandReadAfterFreeBug) { - // Repro for a bug where a memtable containing a merge operand could be - // deleted before the merge operand was saved to the result. - auto options = CurrentOptions(); - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - Reopen(options); - - ASSERT_OK(Merge("key", "value")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::GetImpl:PostMemTableGet:0", - "DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PreFlush"}, - {"DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PostFlush", - "DBImpl::GetImpl:PostMemTableGet:1"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - auto flush_thread = port::Thread([&]() { - TEST_SYNC_POINT( - "DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PreFlush"); - ASSERT_OK(Flush()); - TEST_SYNC_POINT( - "DBMergeOperandTest::FlushedMergeOperandReadAfterFreeBug:PostFlush"); - }); - - PinnableSlice value; - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = 1; - int number_of_operands; - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "key", &value, &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(1, number_of_operands); - - flush_thread.join(); -} - -TEST_F(DBMergeOperandTest, GetMergeOperandsBasic) { - Options options; - options.create_if_missing = true; - // Use only the latest two merge operands. - options.merge_operator = std::make_shared(2, ','); - options.env = env_; - Reopen(options); - int num_records = 4; - int number_of_operands = 0; - std::vector values(num_records); - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = num_records; - - // k0 value in memtable - ASSERT_OK(Put("k0", "PutARock")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k0", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "PutARock"); - - // k0.1 value in SST - ASSERT_OK(Put("k0.1", "RockInSST")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k0.1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "RockInSST"); - - // All k1 values are in memtable. - ASSERT_OK(Merge("k1", "a")); - ASSERT_OK(Put("k1", "x")); - ASSERT_OK(Merge("k1", "b")); - ASSERT_OK(Merge("k1", "c")); - ASSERT_OK(Merge("k1", "d")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "x"); - ASSERT_EQ(values[1], "b"); - ASSERT_EQ(values[2], "c"); - ASSERT_EQ(values[3], "d"); - - // expected_max_number_of_operands is less than number of merge operands so - // status should be Incomplete. - merge_operands_info.expected_max_number_of_operands = num_records - 1; - Status status = db_->GetMergeOperands( - ReadOptions(), db_->DefaultColumnFamily(), "k1", values.data(), - &merge_operands_info, &number_of_operands); - ASSERT_EQ(status.IsIncomplete(), true); - merge_operands_info.expected_max_number_of_operands = num_records; - - // All k1.1 values are in memtable. - ASSERT_OK(Merge("k1.1", "r")); - ASSERT_OK(Delete("k1.1")); - ASSERT_OK(Merge("k1.1", "c")); - ASSERT_OK(Merge("k1.1", "k")); - ASSERT_OK(Merge("k1.1", "s")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k1.1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "c"); - ASSERT_EQ(values[1], "k"); - ASSERT_EQ(values[2], "s"); - - // All k2 values are flushed to L0 into a single file. - ASSERT_OK(Merge("k2", "q")); - ASSERT_OK(Merge("k2", "w")); - ASSERT_OK(Merge("k2", "e")); - ASSERT_OK(Merge("k2", "r")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k2", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "q"); - ASSERT_EQ(values[1], "w"); - ASSERT_EQ(values[2], "e"); - ASSERT_EQ(values[3], "r"); - - // All k2.1 values are flushed to L0 into a single file. - ASSERT_OK(Merge("k2.1", "m")); - ASSERT_OK(Put("k2.1", "l")); - ASSERT_OK(Merge("k2.1", "n")); - ASSERT_OK(Merge("k2.1", "o")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k2.1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "l,n,o"); - - // All k2.2 values are flushed to L0 into a single file. - ASSERT_OK(Merge("k2.2", "g")); - ASSERT_OK(Delete("k2.2")); - ASSERT_OK(Merge("k2.2", "o")); - ASSERT_OK(Merge("k2.2", "t")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k2.2", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "o,t"); - - // Do some compaction that will make the following tests more predictable - // Slice start("PutARock"); - // Slice end("t"); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // All k3 values are flushed and are in different files. - ASSERT_OK(Merge("k3", "ab")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "bc")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "cd")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "de")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k3", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "ab"); - ASSERT_EQ(values[1], "bc"); - ASSERT_EQ(values[2], "cd"); - ASSERT_EQ(values[3], "de"); - - // All k3.1 values are flushed and are in different files. - ASSERT_OK(Merge("k3.1", "ab")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("k3.1", "bc")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3.1", "cd")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3.1", "de")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k3.1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "bc"); - ASSERT_EQ(values[1], "cd"); - ASSERT_EQ(values[2], "de"); - - // All k3.2 values are flushed and are in different files. - ASSERT_OK(Merge("k3.2", "ab")); - ASSERT_OK(Flush()); - ASSERT_OK(Delete("k3.2")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3.2", "cd")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3.2", "de")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k3.2", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "cd"); - ASSERT_EQ(values[1], "de"); - - // All K4 values are in different levels - ASSERT_OK(Merge("k4", "ba")); - ASSERT_OK(Flush()); - MoveFilesToLevel(4); - ASSERT_OK(Merge("k4", "cb")); - ASSERT_OK(Flush()); - MoveFilesToLevel(3); - ASSERT_OK(Merge("k4", "dc")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - ASSERT_OK(Merge("k4", "ed")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k4", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "ba"); - ASSERT_EQ(values[1], "cb"); - ASSERT_EQ(values[2], "dc"); - ASSERT_EQ(values[3], "ed"); - - // First 3 k5 values are in SST and next 4 k5 values are in Immutable - // Memtable - ASSERT_OK(Merge("k5", "who")); - ASSERT_OK(Merge("k5", "am")); - ASSERT_OK(Merge("k5", "i")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("k5", "remember")); - ASSERT_OK(Merge("k5", "i")); - ASSERT_OK(Merge("k5", "am")); - ASSERT_OK(Merge("k5", "rocks")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k5", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "remember"); - ASSERT_EQ(values[1], "i"); - ASSERT_EQ(values[2], "am"); -} - -TEST_F(DBMergeOperandTest, BlobDBGetMergeOperandsBasic) { - Options options; - options.create_if_missing = true; - options.enable_blob_files = true; - options.min_blob_size = 0; - // Use only the latest two merge operands. - options.merge_operator = std::make_shared(2, ','); - options.env = env_; - Reopen(options); - int num_records = 4; - int number_of_operands = 0; - std::vector values(num_records); - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = num_records; - - // All k1 values are in memtable. - ASSERT_OK(Put("k1", "x")); - ASSERT_OK(Merge("k1", "b")); - ASSERT_OK(Merge("k1", "c")); - ASSERT_OK(Merge("k1", "d")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k1", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "x"); - ASSERT_EQ(values[1], "b"); - ASSERT_EQ(values[2], "c"); - ASSERT_EQ(values[3], "d"); - - // expected_max_number_of_operands is less than number of merge operands so - // status should be Incomplete. - merge_operands_info.expected_max_number_of_operands = num_records - 1; - Status status = db_->GetMergeOperands( - ReadOptions(), db_->DefaultColumnFamily(), "k1", values.data(), - &merge_operands_info, &number_of_operands); - ASSERT_EQ(status.IsIncomplete(), true); - merge_operands_info.expected_max_number_of_operands = num_records; - - // All k2 values are flushed to L0 into a single file. - ASSERT_OK(Put("k2", "q")); - ASSERT_OK(Merge("k2", "w")); - ASSERT_OK(Merge("k2", "e")); - ASSERT_OK(Merge("k2", "r")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k2", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "q,w,e,r"); - - // Do some compaction that will make the following tests more predictable - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // All k3 values are flushed and are in different files. - ASSERT_OK(Put("k3", "ab")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "bc")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "cd")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "de")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k3", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "ab"); - ASSERT_EQ(values[1], "bc"); - ASSERT_EQ(values[2], "cd"); - ASSERT_EQ(values[3], "de"); - - // All K4 values are in different levels - ASSERT_OK(Put("k4", "ba")); - ASSERT_OK(Flush()); - MoveFilesToLevel(4); - ASSERT_OK(Merge("k4", "cb")); - ASSERT_OK(Flush()); - MoveFilesToLevel(3); - ASSERT_OK(Merge("k4", "dc")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - ASSERT_OK(Merge("k4", "ed")); - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k4", values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(values[0], "ba"); - ASSERT_EQ(values[1], "cb"); - ASSERT_EQ(values[2], "dc"); - ASSERT_EQ(values[3], "ed"); -} - -TEST_F(DBMergeOperandTest, GetMergeOperandsLargeResultOptimization) { - // These constants are chosen to trigger the large result optimization - // (pinning a bundle of `DBImpl` resources). - const int kNumOperands = 1024; - const int kOperandLen = 1024; - - Options options; - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - DestroyAndReopen(options); - - Random rnd(301); - std::vector expected_merge_operands; - expected_merge_operands.reserve(kNumOperands); - for (int i = 0; i < kNumOperands; ++i) { - expected_merge_operands.emplace_back(rnd.RandomString(kOperandLen)); - ASSERT_OK(Merge("key", expected_merge_operands.back())); - } - - std::vector merge_operands(kNumOperands); - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = kNumOperands; - int num_merge_operands = 0; - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "key", merge_operands.data(), - &merge_operands_info, &num_merge_operands)); - ASSERT_EQ(num_merge_operands, kNumOperands); - - // Ensures the large result optimization was used. - for (int i = 0; i < kNumOperands; ++i) { - ASSERT_TRUE(merge_operands[i].IsPinned()); - } - - // Add a Flush() to change the `SuperVersion` to challenge the resource - // pinning. - ASSERT_OK(Flush()); - - for (int i = 0; i < kNumOperands; ++i) { - ASSERT_EQ(expected_merge_operands[i], merge_operands[i]); - } -} - -TEST_F(DBMergeOperandTest, GetMergeOperandsBaseDeletionInImmMem) { - // In this test, "k1" has a MERGE in a mutable memtable on top of a base - // DELETE in an immutable memtable. - Options opts = CurrentOptions(); - opts.max_write_buffer_number = 10; - opts.min_write_buffer_number_to_merge = 10; - opts.merge_operator = MergeOperators::CreateDeprecatedPutOperator(); - Reopen(opts); - - ASSERT_OK(Put("k1", "val")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("k0", "val")); - ASSERT_OK(Delete("k1")); - ASSERT_OK(Put("k2", "val")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - ASSERT_OK(Merge("k1", "val")); - - { - std::vector values(2); - - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = - static_cast(values.size()); - - std::string key = "k1", from_db; - int number_of_operands = 0; - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - key, values.data(), &merge_operands_info, - &number_of_operands)); - ASSERT_EQ(1, number_of_operands); - from_db = values[0].ToString(); - ASSERT_EQ("val", from_db); - } - - { - std::string val; - ASSERT_OK(db_->Get(ReadOptions(), "k1", &val)); - ASSERT_EQ("val", val); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_merge_operator_test.cc b/db/db_merge_operator_test.cc deleted file mode 100644 index 19c7bd1e8..000000000 --- a/db/db_merge_operator_test.cc +++ /dev/null @@ -1,824 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -#include -#include - -#include "db/db_test_util.h" -#include "db/forward_iterator.h" -#include "port/stack_trace.h" -#include "rocksdb/merge_operator.h" -#include "util/random.h" -#include "utilities/merge_operators.h" -#include "utilities/merge_operators/string_append/stringappend2.h" - -namespace ROCKSDB_NAMESPACE { - -class TestReadCallback : public ReadCallback { - public: - TestReadCallback(SnapshotChecker* snapshot_checker, - SequenceNumber snapshot_seq) - : ReadCallback(snapshot_seq), - snapshot_checker_(snapshot_checker), - snapshot_seq_(snapshot_seq) {} - - bool IsVisibleFullCheck(SequenceNumber seq) override { - return snapshot_checker_->CheckInSnapshot(seq, snapshot_seq_) == - SnapshotCheckerResult::kInSnapshot; - } - - private: - SnapshotChecker* snapshot_checker_; - SequenceNumber snapshot_seq_; -}; - -// Test merge operator functionality. -class DBMergeOperatorTest : public DBTestBase { - public: - DBMergeOperatorTest() - : DBTestBase("db_merge_operator_test", /*env_do_fsync=*/false) {} - - std::string GetWithReadCallback(SnapshotChecker* snapshot_checker, - const Slice& key, - const Snapshot* snapshot = nullptr) { - SequenceNumber seq = snapshot == nullptr ? db_->GetLatestSequenceNumber() - : snapshot->GetSequenceNumber(); - TestReadCallback read_callback(snapshot_checker, seq); - ReadOptions read_opt; - read_opt.snapshot = snapshot; - PinnableSlice value; - DBImpl::GetImplOptions get_impl_options; - get_impl_options.column_family = db_->DefaultColumnFamily(); - get_impl_options.value = &value; - get_impl_options.callback = &read_callback; - Status s = dbfull()->GetImpl(read_opt, key, get_impl_options); - if (!s.ok()) { - return s.ToString(); - } - return value.ToString(); - } -}; - -TEST_F(DBMergeOperatorTest, LimitMergeOperands) { - class LimitedStringAppendMergeOp : public StringAppendTESTOperator { - public: - LimitedStringAppendMergeOp(int limit, char delim) - : StringAppendTESTOperator(delim), limit_(limit) {} - - const char* Name() const override { - return "DBMergeOperatorTest::LimitedStringAppendMergeOp"; - } - - bool ShouldMerge(const std::vector& operands) const override { - if (operands.size() > 0 && limit_ > 0 && operands.size() >= limit_) { - return true; - } - return false; - } - - private: - size_t limit_ = 0; - }; - - Options options; - options.create_if_missing = true; - // Use only the latest two merge operands. - options.merge_operator = std::make_shared(2, ','); - options.env = env_; - Reopen(options); - // All K1 values are in memtable. - ASSERT_OK(Merge("k1", "a")); - ASSERT_OK(Merge("k1", "b")); - ASSERT_OK(Merge("k1", "c")); - ASSERT_OK(Merge("k1", "d")); - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), "k1", &value)); - // Make sure that only the latest two merge operands are used. If this was - // not the case the value would be "a,b,c,d". - ASSERT_EQ(value, "c,d"); - - // All K2 values are flushed to L0 into a single file. - ASSERT_OK(Merge("k2", "a")); - ASSERT_OK(Merge("k2", "b")); - ASSERT_OK(Merge("k2", "c")); - ASSERT_OK(Merge("k2", "d")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->Get(ReadOptions(), "k2", &value)); - ASSERT_EQ(value, "c,d"); - - // All K3 values are flushed and are in different files. - ASSERT_OK(Merge("k3", "ab")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "bc")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "cd")); - ASSERT_OK(Flush()); - ASSERT_OK(Merge("k3", "de")); - ASSERT_OK(db_->Get(ReadOptions(), "k3", &value)); - ASSERT_EQ(value, "cd,de"); - - // All K4 values are in different levels - ASSERT_OK(Merge("k4", "ab")); - ASSERT_OK(Flush()); - MoveFilesToLevel(4); - ASSERT_OK(Merge("k4", "bc")); - ASSERT_OK(Flush()); - MoveFilesToLevel(3); - ASSERT_OK(Merge("k4", "cd")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - ASSERT_OK(Merge("k4", "de")); - ASSERT_OK(db_->Get(ReadOptions(), "k4", &value)); - ASSERT_EQ(value, "cd,de"); -} - -TEST_F(DBMergeOperatorTest, MergeErrorOnRead) { - Options options; - options.create_if_missing = true; - options.merge_operator.reset(new TestPutOperator()); - options.env = env_; - Reopen(options); - ASSERT_OK(Merge("k1", "v1")); - ASSERT_OK(Merge("k1", "corrupted")); - std::string value; - ASSERT_TRUE(db_->Get(ReadOptions(), "k1", &value).IsCorruption()); - VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v1"}}); -} - -TEST_F(DBMergeOperatorTest, MergeErrorOnWrite) { - Options options; - options.create_if_missing = true; - options.merge_operator.reset(new TestPutOperator()); - options.max_successive_merges = 3; - options.env = env_; - Reopen(options); - ASSERT_OK(Merge("k1", "v1")); - ASSERT_OK(Merge("k1", "v2")); - // Will trigger a merge when hitting max_successive_merges and the merge - // will fail. The delta will be inserted nevertheless. - ASSERT_OK(Merge("k1", "corrupted")); - // Data should stay unmerged after the error. - VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v2"}, {"k1", "v1"}}); -} - -TEST_F(DBMergeOperatorTest, MergeErrorOnIteration) { - Options options; - options.create_if_missing = true; - options.merge_operator.reset(new TestPutOperator()); - options.env = env_; - - DestroyAndReopen(options); - ASSERT_OK(Merge("k1", "v1")); - ASSERT_OK(Merge("k1", "corrupted")); - ASSERT_OK(Put("k2", "v2")); - auto* iter = db_->NewIterator(ReadOptions()); - iter->Seek("k1"); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsCorruption()); - delete iter; - iter = db_->NewIterator(ReadOptions()); - iter->Seek("k2"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - iter->Prev(); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsCorruption()); - delete iter; - VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v1"}, {"k2", "v2"}}); - - DestroyAndReopen(options); - ASSERT_OK(Merge("k1", "v1")); - ASSERT_OK(Put("k2", "v2")); - ASSERT_OK(Merge("k2", "corrupted")); - iter = db_->NewIterator(ReadOptions()); - iter->Seek("k1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsCorruption()); - delete iter; - VerifyDBInternal({{"k1", "v1"}, {"k2", "corrupted"}, {"k2", "v2"}}); -} - - -TEST_F(DBMergeOperatorTest, MergeOperatorFailsWithMustMerge) { - // This is like a mini-stress test dedicated to `OpFailureScope::kMustMerge`. - // Some or most of it might be deleted upon adding that option to the actual - // stress test. - // - // "k0" and "k2" are stable (uncorrupted) keys before and after a corrupted - // key ("k1"). The outer loop (`i`) varies which write (`j`) to "k1" triggers - // the corruption. Inside that loop there are three cases: - // - // - Case 1: pure `Merge()`s - // - Case 2: `Merge()`s on top of a `Put()` - // - Case 3: `Merge()`s on top of a `Delete()` - // - // For each case we test query results before flush, after flush, and after - // compaction, as well as cleanup after deletion+compaction. The queries - // expect "k0" and "k2" to always be readable. "k1" is expected to be readable - // only by APIs that do not require merging, such as `GetMergeOperands()`. - const int kNumOperands = 3; - Options options; - options.merge_operator.reset(new TestPutOperator()); - options.env = env_; - Reopen(options); - - for (int i = 0; i < kNumOperands; ++i) { - auto check_query = [&]() { - { - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), "k0", &value)); - Status s = db_->Get(ReadOptions(), "k1", &value); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_EQ(Status::SubCode::kMergeOperatorFailed, s.subcode()); - ASSERT_OK(db_->Get(ReadOptions(), "k2", &value)); - } - - { - std::unique_ptr iter; - iter.reset(db_->NewIterator(ReadOptions())); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("k0", iter->key()); - iter->Next(); - ASSERT_TRUE(iter->status().IsCorruption()); - ASSERT_EQ(Status::SubCode::kMergeOperatorFailed, - iter->status().subcode()); - - iter->SeekToLast(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("k2", iter->key()); - iter->Prev(); - ASSERT_TRUE(iter->status().IsCorruption()); - - iter->Seek("k2"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("k2", iter->key()); - } - - std::vector values(kNumOperands); - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = kNumOperands; - int num_operands_found = 0; - ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(), - "k1", values.data(), &merge_operands_info, - &num_operands_found)); - ASSERT_EQ(kNumOperands, num_operands_found); - for (int j = 0; j < num_operands_found; ++j) { - if (i == j) { - ASSERT_EQ(values[j], "corrupted_must_merge"); - } else { - ASSERT_EQ(values[j], "ok"); - } - } - }; - - ASSERT_OK(Put("k0", "val")); - ASSERT_OK(Put("k2", "val")); - - // Case 1 - for (int j = 0; j < kNumOperands; ++j) { - if (j == i) { - ASSERT_OK(Merge("k1", "corrupted_must_merge")); - } else { - ASSERT_OK(Merge("k1", "ok")); - } - } - check_query(); - ASSERT_OK(Flush()); - check_query(); - { - CompactRangeOptions cro; - cro.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - check_query(); - - // Case 2 - for (int j = 0; j < kNumOperands; ++j) { - Slice val; - if (j == i) { - val = "corrupted_must_merge"; - } else { - val = "ok"; - } - if (j == 0) { - ASSERT_OK(Put("k1", val)); - } else { - ASSERT_OK(Merge("k1", val)); - } - } - check_query(); - ASSERT_OK(Flush()); - check_query(); - { - CompactRangeOptions cro; - cro.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - check_query(); - - // Case 3 - ASSERT_OK(Delete("k1")); - for (int j = 0; j < kNumOperands; ++j) { - if (i == j) { - ASSERT_OK(Merge("k1", "corrupted_must_merge")); - } else { - ASSERT_OK(Merge("k1", "ok")); - } - } - check_query(); - ASSERT_OK(Flush()); - check_query(); - { - CompactRangeOptions cro; - cro.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - check_query(); - - // Verify obsolete data removal still happens - ASSERT_OK(Delete("k0")); - ASSERT_OK(Delete("k1")); - ASSERT_OK(Delete("k2")); - ASSERT_EQ("NOT_FOUND", Get("k0")); - ASSERT_EQ("NOT_FOUND", Get("k1")); - ASSERT_EQ("NOT_FOUND", Get("k2")); - CompactRangeOptions cro; - cro.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_EQ("", FilesPerLevel()); - } -} - - -class MergeOperatorPinningTest : public DBMergeOperatorTest, - public testing::WithParamInterface { - public: - MergeOperatorPinningTest() { disable_block_cache_ = GetParam(); } - - bool disable_block_cache_; -}; - -INSTANTIATE_TEST_CASE_P(MergeOperatorPinningTest, MergeOperatorPinningTest, - ::testing::Bool()); - -TEST_P(MergeOperatorPinningTest, OperandsMultiBlocks) { - Options options = CurrentOptions(); - BlockBasedTableOptions table_options; - table_options.block_size = 1; // every block will contain one entry - table_options.no_block_cache = disable_block_cache_; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.merge_operator = MergeOperators::CreateStringAppendTESTOperator(); - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - const int kKeysPerFile = 10; - const int kOperandsPerKeyPerFile = 7; - const int kOperandSize = 100; - // Filse to write in L0 before compacting to lower level - const int kFilesPerLevel = 3; - - Random rnd(301); - std::map true_data; - int batch_num = 1; - int lvl_to_fill = 4; - int key_id = 0; - while (true) { - for (int j = 0; j < kKeysPerFile; j++) { - std::string key = Key(key_id % 35); - key_id++; - for (int k = 0; k < kOperandsPerKeyPerFile; k++) { - std::string val = rnd.RandomString(kOperandSize); - ASSERT_OK(db_->Merge(WriteOptions(), key, val)); - if (true_data[key].size() == 0) { - true_data[key] = val; - } else { - true_data[key] += "," + val; - } - } - } - - if (lvl_to_fill == -1) { - // Keep last batch in memtable and stop - break; - } - - ASSERT_OK(Flush()); - if (batch_num % kFilesPerLevel == 0) { - if (lvl_to_fill != 0) { - MoveFilesToLevel(lvl_to_fill); - } - lvl_to_fill--; - } - batch_num++; - } - - // 3 L0 files - // 1 L1 file - // 3 L2 files - // 1 L3 file - // 3 L4 Files - ASSERT_EQ(FilesPerLevel(), "3,1,3,1,3"); - - VerifyDBFromMap(true_data); -} - -class MergeOperatorHook : public MergeOperator { - public: - explicit MergeOperatorHook(std::shared_ptr _merge_op) - : merge_op_(_merge_op) {} - - bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { - before_merge_(); - bool res = merge_op_->FullMergeV2(merge_in, merge_out); - after_merge_(); - return res; - } - - const char* Name() const override { return merge_op_->Name(); } - - std::shared_ptr merge_op_; - std::function before_merge_ = []() {}; - std::function after_merge_ = []() {}; -}; - -TEST_P(MergeOperatorPinningTest, EvictCacheBeforeMerge) { - Options options = CurrentOptions(); - - auto merge_hook = - std::make_shared(MergeOperators::CreateMaxOperator()); - options.merge_operator = merge_hook; - options.disable_auto_compactions = true; - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.max_open_files = 20; - BlockBasedTableOptions bbto; - bbto.no_block_cache = disable_block_cache_; - if (bbto.no_block_cache == false) { - bbto.block_cache = NewLRUCache(64 * 1024 * 1024); - } else { - bbto.block_cache = nullptr; - } - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - const int kNumOperands = 30; - const int kNumKeys = 1000; - const int kOperandSize = 100; - Random rnd(301); - - // 1000 keys every key have 30 operands, every operand is in a different file - std::map true_data; - for (int i = 0; i < kNumOperands; i++) { - for (int j = 0; j < kNumKeys; j++) { - std::string k = Key(j); - std::string v = rnd.RandomString(kOperandSize); - ASSERT_OK(db_->Merge(WriteOptions(), k, v)); - - true_data[k] = std::max(true_data[k], v); - } - ASSERT_OK(Flush()); - } - - std::vector file_numbers = ListTableFiles(env_, dbname_); - ASSERT_EQ(file_numbers.size(), kNumOperands); - int merge_cnt = 0; - - // Code executed before merge operation - merge_hook->before_merge_ = [&]() { - // Evict all tables from cache before every merge operation - auto* table_cache = dbfull()->TEST_table_cache(); - for (uint64_t num : file_numbers) { - TableCache::Evict(table_cache, num); - } - // Decrease cache capacity to force all unrefed blocks to be evicted - if (bbto.block_cache) { - bbto.block_cache->SetCapacity(1); - } - merge_cnt++; - }; - - // Code executed after merge operation - merge_hook->after_merge_ = [&]() { - // Increase capacity again after doing the merge - if (bbto.block_cache) { - bbto.block_cache->SetCapacity(64 * 1024 * 1024); - } - }; - - size_t total_reads; - VerifyDBFromMap(true_data, &total_reads); - ASSERT_EQ(merge_cnt, total_reads); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - VerifyDBFromMap(true_data, &total_reads); -} - -TEST_P(MergeOperatorPinningTest, TailingIterator) { - Options options = CurrentOptions(); - options.merge_operator = MergeOperators::CreateMaxOperator(); - BlockBasedTableOptions bbto; - bbto.no_block_cache = disable_block_cache_; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - const int kNumOperands = 100; - const int kNumWrites = 100000; - - std::function writer_func = [&]() { - int k = 0; - for (int i = 0; i < kNumWrites; i++) { - ASSERT_OK(db_->Merge(WriteOptions(), Key(k), Key(k))); - - if (i && i % kNumOperands == 0) { - k++; - } - if (i && i % 127 == 0) { - ASSERT_OK(Flush()); - } - if (i && i % 317 == 0) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - } - }; - - std::function reader_func = [&]() { - ReadOptions ro; - ro.tailing = true; - Iterator* iter = db_->NewIterator(ro); - ASSERT_OK(iter->status()); - iter->SeekToFirst(); - for (int i = 0; i < (kNumWrites / kNumOperands); i++) { - while (!iter->Valid()) { - // wait for the key to be written - env_->SleepForMicroseconds(100); - iter->Seek(Key(i)); - } - ASSERT_EQ(iter->key(), Key(i)); - ASSERT_EQ(iter->value(), Key(i)); - - iter->Next(); - } - ASSERT_OK(iter->status()); - - delete iter; - }; - - ROCKSDB_NAMESPACE::port::Thread writer_thread(writer_func); - ROCKSDB_NAMESPACE::port::Thread reader_thread(reader_func); - - writer_thread.join(); - reader_thread.join(); -} - -TEST_F(DBMergeOperatorTest, TailingIteratorMemtableUnrefedBySomeoneElse) { - Options options = CurrentOptions(); - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - DestroyAndReopen(options); - - // Overview of the test: - // * There are two merge operands for the same key: one in an sst file, - // another in a memtable. - // * Seek a tailing iterator to this key. - // * As part of the seek, the iterator will: - // (a) first visit the operand in the memtable and tell ForwardIterator - // to pin this operand, then - // (b) move on to the operand in the sst file, then pass both operands - // to merge operator. - // * The memtable may get flushed and unreferenced by another thread between - // (a) and (b). The test simulates it by flushing the memtable inside a - // SyncPoint callback located between (a) and (b). - // * In this case it's ForwardIterator's responsibility to keep the memtable - // pinned until (b) is complete. There used to be a bug causing - // ForwardIterator to not pin it in some circumstances. This test - // reproduces it. - - ASSERT_OK(db_->Merge(WriteOptions(), "key", "sst")); - ASSERT_OK(db_->Flush(FlushOptions())); // Switch to SuperVersion A - ASSERT_OK(db_->Merge(WriteOptions(), "key", "memtable")); - - // Pin SuperVersion A - std::unique_ptr someone_else(db_->NewIterator(ReadOptions())); - ASSERT_OK(someone_else->status()); - - bool pushed_first_operand = false; - bool stepped_to_next_operand = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBIter::MergeValuesNewToOld:PushedFirstOperand", [&](void*) { - EXPECT_FALSE(pushed_first_operand); - pushed_first_operand = true; - EXPECT_OK(db_->Flush(FlushOptions())); // Switch to SuperVersion B - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBIter::MergeValuesNewToOld:SteppedToNextOperand", [&](void*) { - EXPECT_FALSE(stepped_to_next_operand); - stepped_to_next_operand = true; - someone_else.reset(); // Unpin SuperVersion A - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ReadOptions ro; - ro.tailing = true; - std::unique_ptr iter(db_->NewIterator(ro)); - iter->Seek("key"); - - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(std::string("sst,memtable"), iter->value().ToString()); - EXPECT_TRUE(pushed_first_operand); - EXPECT_TRUE(stepped_to_next_operand); -} - -TEST_F(DBMergeOperatorTest, SnapshotCheckerAndReadCallback) { - Options options = CurrentOptions(); - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - DestroyAndReopen(options); - - class TestSnapshotChecker : public SnapshotChecker { - public: - SnapshotCheckerResult CheckInSnapshot( - SequenceNumber seq, SequenceNumber snapshot_seq) const override { - return IsInSnapshot(seq, snapshot_seq) - ? SnapshotCheckerResult::kInSnapshot - : SnapshotCheckerResult::kNotInSnapshot; - } - - bool IsInSnapshot(SequenceNumber seq, SequenceNumber snapshot_seq) const { - switch (snapshot_seq) { - case 0: - return seq == 0; - case 1: - return seq <= 1; - case 2: - // seq = 2 not visible to snapshot with seq = 2 - return seq <= 1; - case 3: - return seq <= 3; - case 4: - // seq = 4 not visible to snpahost with seq = 4 - return seq <= 3; - default: - // seq >=4 is uncommitted - return seq <= 4; - }; - } - }; - TestSnapshotChecker* snapshot_checker = new TestSnapshotChecker(); - dbfull()->SetSnapshotChecker(snapshot_checker); - - std::string value; - ASSERT_OK(Merge("foo", "v1")); - ASSERT_EQ(1, db_->GetLatestSequenceNumber()); - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo")); - ASSERT_OK(Merge("foo", "v2")); - ASSERT_EQ(2, db_->GetLatestSequenceNumber()); - // v2 is not visible to latest snapshot, which has seq = 2. - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo")); - // Take a snapshot with seq = 2. - const Snapshot* snapshot1 = db_->GetSnapshot(); - ASSERT_EQ(2, snapshot1->GetSequenceNumber()); - // v2 is not visible to snapshot1, which has seq = 2 - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); - - // Verify flush doesn't alter the result. - ASSERT_OK(Flush()); - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo")); - - ASSERT_OK(Merge("foo", "v3")); - ASSERT_EQ(3, db_->GetLatestSequenceNumber()); - ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo")); - ASSERT_OK(Merge("foo", "v4")); - ASSERT_EQ(4, db_->GetLatestSequenceNumber()); - // v4 is not visible to latest snapshot, which has seq = 4. - ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo")); - const Snapshot* snapshot2 = db_->GetSnapshot(); - ASSERT_EQ(4, snapshot2->GetSequenceNumber()); - // v4 is not visible to snapshot2, which has seq = 4. - ASSERT_EQ("v1,v2,v3", - GetWithReadCallback(snapshot_checker, "foo", snapshot2)); - - // Verify flush doesn't alter the result. - ASSERT_OK(Flush()); - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); - ASSERT_EQ("v1,v2,v3", - GetWithReadCallback(snapshot_checker, "foo", snapshot2)); - ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo")); - - ASSERT_OK(Merge("foo", "v5")); - ASSERT_EQ(5, db_->GetLatestSequenceNumber()); - // v5 is uncommitted - ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo")); - - // full manual compaction. - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // Verify compaction doesn't alter the result. - ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); - ASSERT_EQ("v1,v2,v3", - GetWithReadCallback(snapshot_checker, "foo", snapshot2)); - ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo")); - - db_->ReleaseSnapshot(snapshot1); - db_->ReleaseSnapshot(snapshot2); -} - -class PerConfigMergeOperatorPinningTest - : public DBMergeOperatorTest, - public testing::WithParamInterface> { - public: - PerConfigMergeOperatorPinningTest() { - std::tie(disable_block_cache_, option_config_) = GetParam(); - } - - bool disable_block_cache_; -}; - -INSTANTIATE_TEST_CASE_P( - MergeOperatorPinningTest, PerConfigMergeOperatorPinningTest, - ::testing::Combine(::testing::Bool(), - ::testing::Range(static_cast(DBTestBase::kDefault), - static_cast(DBTestBase::kEnd)))); - -TEST_P(PerConfigMergeOperatorPinningTest, Randomized) { - if (ShouldSkipOptions(option_config_, kSkipMergePut)) { - return; - } - - Options options = CurrentOptions(); - options.merge_operator = MergeOperators::CreateMaxOperator(); - BlockBasedTableOptions table_options; - table_options.no_block_cache = disable_block_cache_; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - - Random rnd(301); - std::map true_data; - - const int kTotalMerges = 5000; - // Every key gets ~10 operands - const int kKeyRange = kTotalMerges / 10; - const int kOperandSize = 20; - const int kNumPutBefore = kKeyRange / 10; // 10% value - const int kNumPutAfter = kKeyRange / 10; // 10% overwrite - const int kNumDelete = kKeyRange / 10; // 10% delete - - // kNumPutBefore keys will have base values - for (int i = 0; i < kNumPutBefore; i++) { - std::string key = Key(rnd.Next() % kKeyRange); - std::string value = rnd.RandomString(kOperandSize); - ASSERT_OK(db_->Put(WriteOptions(), key, value)); - - true_data[key] = value; - } - - // Do kTotalMerges merges - for (int i = 0; i < kTotalMerges; i++) { - std::string key = Key(rnd.Next() % kKeyRange); - std::string value = rnd.RandomString(kOperandSize); - ASSERT_OK(db_->Merge(WriteOptions(), key, value)); - - if (true_data[key] < value) { - true_data[key] = value; - } - } - - // Overwrite random kNumPutAfter keys - for (int i = 0; i < kNumPutAfter; i++) { - std::string key = Key(rnd.Next() % kKeyRange); - std::string value = rnd.RandomString(kOperandSize); - ASSERT_OK(db_->Put(WriteOptions(), key, value)); - - true_data[key] = value; - } - - // Delete random kNumDelete keys - for (int i = 0; i < kNumDelete; i++) { - std::string key = Key(rnd.Next() % kKeyRange); - ASSERT_OK(db_->Delete(WriteOptions(), key)); - - true_data.erase(key); - } - - VerifyDBFromMap(true_data); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_options_test.cc b/db/db_options_test.cc deleted file mode 100644 index 3304c6339..000000000 --- a/db/db_options_test.cc +++ /dev/null @@ -1,1215 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -#include -#include -#include - -#include "db/column_family.h" -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "options/options_helper.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/rate_limiter.h" -#include "rocksdb/stats_history.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class DBOptionsTest : public DBTestBase { - public: - DBOptionsTest() : DBTestBase("db_options_test", /*env_do_fsync=*/true) {} - - std::unordered_map GetMutableDBOptionsMap( - const DBOptions& options) { - std::string options_str; - std::unordered_map mutable_map; - ConfigOptions config_options(options); - config_options.delimiter = "; "; - - EXPECT_OK(GetStringFromMutableDBOptions( - config_options, MutableDBOptions(options), &options_str)); - EXPECT_OK(StringToMap(options_str, &mutable_map)); - - return mutable_map; - } - - std::unordered_map GetMutableCFOptionsMap( - const ColumnFamilyOptions& options) { - std::string options_str; - ConfigOptions config_options; - config_options.delimiter = "; "; - - std::unordered_map mutable_map; - EXPECT_OK(GetStringFromMutableCFOptions( - config_options, MutableCFOptions(options), &options_str)); - EXPECT_OK(StringToMap(options_str, &mutable_map)); - return mutable_map; - } - - std::unordered_map GetRandomizedMutableCFOptionsMap( - Random* rnd) { - Options options = CurrentOptions(); - options.env = env_; - ImmutableDBOptions db_options(options); - test::RandomInitCFOptions(&options, options, rnd); - auto sanitized_options = SanitizeOptions(db_options, options); - auto opt_map = GetMutableCFOptionsMap(sanitized_options); - delete options.compaction_filter; - return opt_map; - } - - std::unordered_map GetRandomizedMutableDBOptionsMap( - Random* rnd) { - DBOptions db_options; - test::RandomInitDBOptions(&db_options, rnd); - auto sanitized_options = SanitizeOptions(dbname_, db_options); - return GetMutableDBOptionsMap(sanitized_options); - } -}; - -TEST_F(DBOptionsTest, ImmutableTrackAndVerifyWalsInManifest) { - Options options; - options.env = env_; - options.track_and_verify_wals_in_manifest = true; - - ImmutableDBOptions db_options(options); - ASSERT_TRUE(db_options.track_and_verify_wals_in_manifest); - - Reopen(options); - ASSERT_TRUE(dbfull()->GetDBOptions().track_and_verify_wals_in_manifest); - - Status s = - dbfull()->SetDBOptions({{"track_and_verify_wals_in_manifest", "false"}}); - ASSERT_FALSE(s.ok()); -} - -TEST_F(DBOptionsTest, ImmutableVerifySstUniqueIdInManifest) { - Options options; - options.env = env_; - options.verify_sst_unique_id_in_manifest = true; - - ImmutableDBOptions db_options(options); - ASSERT_TRUE(db_options.verify_sst_unique_id_in_manifest); - - Reopen(options); - ASSERT_TRUE(dbfull()->GetDBOptions().verify_sst_unique_id_in_manifest); - - Status s = - dbfull()->SetDBOptions({{"verify_sst_unique_id_in_manifest", "false"}}); - ASSERT_FALSE(s.ok()); -} - -// RocksDB lite don't support dynamic options. - -TEST_F(DBOptionsTest, AvoidUpdatingOptions) { - Options options; - options.env = env_; - options.max_background_jobs = 4; - options.delayed_write_rate = 1024; - - Reopen(options); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - bool is_changed_stats = false; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::WriteOptionsFile:PersistOptions", [&](void* /*arg*/) { - ASSERT_FALSE(is_changed_stats); // should only save options file once - is_changed_stats = true; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - // helper function to check the status and reset after each check - auto is_changed = [&] { - bool ret = is_changed_stats; - is_changed_stats = false; - return ret; - }; - - // without changing the value, but it's sanitized to a different value - ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "0"}})); - ASSERT_TRUE(is_changed()); - - // without changing the value - ASSERT_OK(dbfull()->SetDBOptions({{"max_background_jobs", "4"}})); - ASSERT_FALSE(is_changed()); - - // changing the value - ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "123"}})); - ASSERT_TRUE(is_changed()); - - // update again - ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "123"}})); - ASSERT_FALSE(is_changed()); - - // without changing a default value - ASSERT_OK(dbfull()->SetDBOptions({{"strict_bytes_per_sync", "false"}})); - ASSERT_FALSE(is_changed()); - - // now change - ASSERT_OK(dbfull()->SetDBOptions({{"strict_bytes_per_sync", "true"}})); - ASSERT_TRUE(is_changed()); - - // multiple values without change - ASSERT_OK(dbfull()->SetDBOptions( - {{"max_total_wal_size", "0"}, {"stats_dump_period_sec", "600"}})); - ASSERT_FALSE(is_changed()); - - // multiple values with change - ASSERT_OK(dbfull()->SetDBOptions( - {{"max_open_files", "100"}, {"stats_dump_period_sec", "600"}})); - ASSERT_TRUE(is_changed()); -} - -TEST_F(DBOptionsTest, GetLatestDBOptions) { - // GetOptions should be able to get latest option changed by SetOptions. - Options options; - options.create_if_missing = true; - options.env = env_; - Random rnd(228); - Reopen(options); - auto new_options = GetRandomizedMutableDBOptionsMap(&rnd); - ASSERT_OK(dbfull()->SetDBOptions(new_options)); - ASSERT_EQ(new_options, GetMutableDBOptionsMap(dbfull()->GetDBOptions())); -} - -TEST_F(DBOptionsTest, GetLatestCFOptions) { - // GetOptions should be able to get latest option changed by SetOptions. - Options options; - options.create_if_missing = true; - options.env = env_; - Random rnd(228); - Reopen(options); - CreateColumnFamilies({"foo"}, options); - ReopenWithColumnFamilies({"default", "foo"}, options); - auto options_default = GetRandomizedMutableCFOptionsMap(&rnd); - auto options_foo = GetRandomizedMutableCFOptionsMap(&rnd); - ASSERT_OK(dbfull()->SetOptions(handles_[0], options_default)); - ASSERT_OK(dbfull()->SetOptions(handles_[1], options_foo)); - ASSERT_EQ(options_default, - GetMutableCFOptionsMap(dbfull()->GetOptions(handles_[0]))); - ASSERT_EQ(options_foo, - GetMutableCFOptionsMap(dbfull()->GetOptions(handles_[1]))); -} - -TEST_F(DBOptionsTest, SetMutableTableOptions) { - Options options; - options.create_if_missing = true; - options.env = env_; - options.blob_file_size = 16384; - BlockBasedTableOptions bbto; - bbto.no_block_cache = true; - bbto.block_size = 8192; - bbto.block_restart_interval = 7; - - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - Reopen(options); - - ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily(); - Options c_opts = dbfull()->GetOptions(cfh); - - const auto* c_bbto = - c_opts.table_factory->GetOptions(); - ASSERT_NE(c_bbto, nullptr); - ASSERT_EQ(c_opts.blob_file_size, 16384); - ASSERT_EQ(c_bbto->no_block_cache, true); - ASSERT_EQ(c_bbto->block_size, 8192); - ASSERT_EQ(c_bbto->block_restart_interval, 7); - ASSERT_OK(dbfull()->SetOptions( - cfh, {{"table_factory.block_size", "16384"}, - {"table_factory.block_restart_interval", "11"}})); - ASSERT_EQ(c_bbto->block_size, 16384); - ASSERT_EQ(c_bbto->block_restart_interval, 11); - - // Now set an option that is not mutable - options should not change - ASSERT_NOK( - dbfull()->SetOptions(cfh, {{"table_factory.no_block_cache", "false"}})); - ASSERT_EQ(c_bbto->no_block_cache, true); - ASSERT_EQ(c_bbto->block_size, 16384); - ASSERT_EQ(c_bbto->block_restart_interval, 11); - - // Set some that are mutable and some that are not - options should not change - ASSERT_NOK(dbfull()->SetOptions( - cfh, {{"table_factory.no_block_cache", "false"}, - {"table_factory.block_size", "8192"}, - {"table_factory.block_restart_interval", "7"}})); - ASSERT_EQ(c_bbto->no_block_cache, true); - ASSERT_EQ(c_bbto->block_size, 16384); - ASSERT_EQ(c_bbto->block_restart_interval, 11); - - // Set some that are mutable and some that do not exist - options should not - // change - ASSERT_NOK(dbfull()->SetOptions( - cfh, {{"table_factory.block_size", "8192"}, - {"table_factory.does_not_exist", "true"}, - {"table_factory.block_restart_interval", "7"}})); - ASSERT_EQ(c_bbto->no_block_cache, true); - ASSERT_EQ(c_bbto->block_size, 16384); - ASSERT_EQ(c_bbto->block_restart_interval, 11); - - // Trying to change the table factory fails - ASSERT_NOK(dbfull()->SetOptions( - cfh, {{"table_factory", TableFactory::kPlainTableName()}})); - - // Set some on the table and some on the Column Family - ASSERT_OK(dbfull()->SetOptions( - cfh, {{"table_factory.block_size", "16384"}, - {"blob_file_size", "32768"}, - {"table_factory.block_restart_interval", "13"}})); - c_opts = dbfull()->GetOptions(cfh); - ASSERT_EQ(c_opts.blob_file_size, 32768); - ASSERT_EQ(c_bbto->block_size, 16384); - ASSERT_EQ(c_bbto->block_restart_interval, 13); - // Set some on the table and a bad one on the ColumnFamily - options should - // not change - ASSERT_NOK(dbfull()->SetOptions( - cfh, {{"table_factory.block_size", "1024"}, - {"no_such_option", "32768"}, - {"table_factory.block_restart_interval", "7"}})); - ASSERT_EQ(c_bbto->block_size, 16384); - ASSERT_EQ(c_bbto->block_restart_interval, 13); -} - -TEST_F(DBOptionsTest, SetWithCustomMemTableFactory) { - class DummySkipListFactory : public SkipListFactory { - public: - static const char* kClassName() { return "DummySkipListFactory"; } - const char* Name() const override { return kClassName(); } - explicit DummySkipListFactory() : SkipListFactory(2) {} - }; - { - // Verify the DummySkipList cannot be created - ConfigOptions config_options; - config_options.ignore_unsupported_options = false; - std::unique_ptr factory; - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, DummySkipListFactory::kClassName(), &factory)); - } - Options options; - options.create_if_missing = true; - // Try with fail_if_options_file_error=false/true to update the options - for (bool on_error : {false, true}) { - options.fail_if_options_file_error = on_error; - options.env = env_; - options.disable_auto_compactions = false; - - options.memtable_factory.reset(new DummySkipListFactory()); - Reopen(options); - - ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily(); - ASSERT_OK( - dbfull()->SetOptions(cfh, {{"disable_auto_compactions", "true"}})); - ColumnFamilyDescriptor cfd; - ASSERT_OK(cfh->GetDescriptor(&cfd)); - ASSERT_STREQ(cfd.options.memtable_factory->Name(), - DummySkipListFactory::kClassName()); - ColumnFamilyHandle* test = nullptr; - ASSERT_OK(dbfull()->CreateColumnFamily(options, "test", &test)); - ASSERT_OK(test->GetDescriptor(&cfd)); - ASSERT_STREQ(cfd.options.memtable_factory->Name(), - DummySkipListFactory::kClassName()); - - ASSERT_OK(dbfull()->DropColumnFamily(test)); - delete test; - } -} - -TEST_F(DBOptionsTest, SetBytesPerSync) { - const size_t kValueSize = 1024 * 1024; // 1MB - Options options; - options.create_if_missing = true; - options.bytes_per_sync = 1024 * 1024; - options.use_direct_reads = false; - options.write_buffer_size = 400 * kValueSize; - options.disable_auto_compactions = true; - options.compression = kNoCompression; - options.env = env_; - Reopen(options); - int counter = 0; - int low_bytes_per_sync = 0; - int i = 0; - const std::string kValue(kValueSize, 'v'); - ASSERT_EQ(options.bytes_per_sync, dbfull()->GetDBOptions().bytes_per_sync); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) { counter++; }); - - WriteOptions write_opts; - // should sync approximately 40MB/1MB ~= 40 times. - for (i = 0; i < 40; i++) { - ASSERT_OK(Put(Key(i), kValue, write_opts)); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - low_bytes_per_sync = counter; - ASSERT_GT(low_bytes_per_sync, 35); - ASSERT_LT(low_bytes_per_sync, 45); - - counter = 0; - // 8388608 = 8 * 1024 * 1024 - ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "8388608"}})); - ASSERT_EQ(8388608, dbfull()->GetDBOptions().bytes_per_sync); - // should sync approximately 40MB*2/8MB ~= 10 times. - // data will be 40*2MB because of previous Puts too. - for (i = 0; i < 40; i++) { - ASSERT_OK(Put(Key(i), kValue, write_opts)); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_GT(counter, 5); - ASSERT_LT(counter, 15); - - // Redundant assert. But leaving it here just to get the point across that - // low_bytes_per_sync > counter. - ASSERT_GT(low_bytes_per_sync, counter); -} - -TEST_F(DBOptionsTest, SetWalBytesPerSync) { - const size_t kValueSize = 1024 * 1024 * 3; - Options options; - options.create_if_missing = true; - options.wal_bytes_per_sync = 512; - options.write_buffer_size = 100 * kValueSize; - options.disable_auto_compactions = true; - options.compression = kNoCompression; - options.env = env_; - Reopen(options); - ASSERT_EQ(512, dbfull()->GetDBOptions().wal_bytes_per_sync); - std::atomic_int counter{0}; - int low_bytes_per_sync = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::RangeSync:0", - [&](void* /*arg*/) { counter.fetch_add(1); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - const std::string kValue(kValueSize, 'v'); - int i = 0; - for (; i < 10; i++) { - ASSERT_OK(Put(Key(i), kValue)); - } - // Do not flush. If we flush here, SwitchWAL will reuse old WAL file since its - // empty and will not get the new wal_bytes_per_sync value. - low_bytes_per_sync = counter; - // 5242880 = 1024 * 1024 * 5 - ASSERT_OK(dbfull()->SetDBOptions({{"wal_bytes_per_sync", "5242880"}})); - ASSERT_EQ(5242880, dbfull()->GetDBOptions().wal_bytes_per_sync); - counter = 0; - i = 0; - for (; i < 10; i++) { - ASSERT_OK(Put(Key(i), kValue)); - } - ASSERT_GT(counter, 0); - ASSERT_GT(low_bytes_per_sync, 0); - ASSERT_GT(low_bytes_per_sync, counter); -} - -TEST_F(DBOptionsTest, WritableFileMaxBufferSize) { - Options options; - options.create_if_missing = true; - options.writable_file_max_buffer_size = 1024 * 1024; - options.level0_file_num_compaction_trigger = 3; - options.max_manifest_file_size = 1; - options.env = env_; - int buffer_size = 1024 * 1024; - Reopen(options); - ASSERT_EQ(buffer_size, - dbfull()->GetDBOptions().writable_file_max_buffer_size); - - std::atomic match_cnt(0); - std::atomic unmatch_cnt(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::WritableFileWriter:0", [&](void* arg) { - int value = static_cast(reinterpret_cast(arg)); - if (value == buffer_size) { - match_cnt++; - } else { - unmatch_cnt++; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - int i = 0; - for (; i < 3; i++) { - ASSERT_OK(Put("foo", std::to_string(i))); - ASSERT_OK(Put("bar", std::to_string(i))); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(unmatch_cnt, 0); - ASSERT_GE(match_cnt, 11); - - ASSERT_OK( - dbfull()->SetDBOptions({{"writable_file_max_buffer_size", "524288"}})); - buffer_size = 512 * 1024; - match_cnt = 0; - unmatch_cnt = 0; // SetDBOptions() will create a WritableFileWriter - - ASSERT_EQ(buffer_size, - dbfull()->GetDBOptions().writable_file_max_buffer_size); - i = 0; - for (; i < 3; i++) { - ASSERT_OK(Put("foo", std::to_string(i))); - ASSERT_OK(Put("bar", std::to_string(i))); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(unmatch_cnt, 0); - ASSERT_GE(match_cnt, 11); -} - -TEST_F(DBOptionsTest, SetOptionsAndReopen) { - Random rnd(1044); - auto rand_opts = GetRandomizedMutableCFOptionsMap(&rnd); - ASSERT_OK(dbfull()->SetOptions(rand_opts)); - // Verify if DB can be reopen after setting options. - Options options; - options.env = env_; - ASSERT_OK(TryReopen(options)); -} - -TEST_F(DBOptionsTest, EnableAutoCompactionAndTriggerStall) { - const std::string kValue(1024, 'v'); - for (int method_type = 0; method_type < 2; method_type++) { - for (int option_type = 0; option_type < 4; option_type++) { - Options options; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.write_buffer_size = 1024 * 1024 * 10; - options.compression = CompressionType::kNoCompression; - options.level0_file_num_compaction_trigger = 1; - options.level0_stop_writes_trigger = std::numeric_limits::max(); - options.level0_slowdown_writes_trigger = std::numeric_limits::max(); - options.hard_pending_compaction_bytes_limit = - std::numeric_limits::max(); - options.soft_pending_compaction_bytes_limit = - std::numeric_limits::max(); - options.env = env_; - - DestroyAndReopen(options); - int i = 0; - for (; i < 1024; i++) { - ASSERT_OK(Put(Key(i), kValue)); - } - ASSERT_OK(Flush()); - for (; i < 1024 * 2; i++) { - ASSERT_OK(Put(Key(i), kValue)); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - uint64_t l0_size = SizeAtLevel(0); - - switch (option_type) { - case 0: - // test with level0_stop_writes_trigger - options.level0_stop_writes_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - break; - case 1: - options.level0_slowdown_writes_trigger = 2; - break; - case 2: - options.hard_pending_compaction_bytes_limit = l0_size; - options.soft_pending_compaction_bytes_limit = l0_size; - break; - case 3: - options.soft_pending_compaction_bytes_limit = l0_size; - break; - } - Reopen(options); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped()); - ASSERT_FALSE(dbfull()->TEST_write_controler().NeedsDelay()); - - SyncPoint::GetInstance()->LoadDependency( - {{"DBOptionsTest::EnableAutoCompactionAndTriggerStall:1", - "BackgroundCallCompaction:0"}, - {"DBImpl::BackgroundCompaction():BeforePickCompaction", - "DBOptionsTest::EnableAutoCompactionAndTriggerStall:2"}, - {"DBOptionsTest::EnableAutoCompactionAndTriggerStall:3", - "DBImpl::BackgroundCompaction():AfterPickCompaction"}}); - // Block background compaction. - SyncPoint::GetInstance()->EnableProcessing(); - - switch (method_type) { - case 0: - ASSERT_OK( - dbfull()->SetOptions({{"disable_auto_compactions", "false"}})); - break; - case 1: - ASSERT_OK(dbfull()->EnableAutoCompaction( - {dbfull()->DefaultColumnFamily()})); - break; - } - TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:1"); - // Wait for stall condition recalculate. - TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:2"); - - switch (option_type) { - case 0: - ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped()); - break; - case 1: - ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - break; - case 2: - ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped()); - break; - case 3: - ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - break; - } - TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:3"); - - // Background compaction executed. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped()); - ASSERT_FALSE(dbfull()->TEST_write_controler().NeedsDelay()); - } - } -} - -TEST_F(DBOptionsTest, SetOptionsMayTriggerCompaction) { - Options options; - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 1000; - options.env = env_; - Reopen(options); - for (int i = 0; i < 3; i++) { - // Need to insert two keys to avoid trivial move. - ASSERT_OK(Put("foo", std::to_string(i))); - ASSERT_OK(Put("bar", std::to_string(i))); - ASSERT_OK(Flush()); - } - ASSERT_EQ("3", FilesPerLevel()); - ASSERT_OK( - dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "3"}})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,1", FilesPerLevel()); -} - -TEST_F(DBOptionsTest, SetBackgroundCompactionThreads) { - Options options; - options.create_if_missing = true; - options.max_background_compactions = 1; // default value - options.env = env_; - Reopen(options); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - ASSERT_OK(dbfull()->SetDBOptions({{"max_background_compactions", "3"}})); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - auto stop_token = dbfull()->TEST_write_controler().GetStopToken(); - ASSERT_EQ(3, dbfull()->TEST_BGCompactionsAllowed()); -} - -TEST_F(DBOptionsTest, SetBackgroundFlushThreads) { - Options options; - options.create_if_missing = true; - options.max_background_flushes = 1; - options.env = env_; - Reopen(options); - ASSERT_EQ(1, dbfull()->TEST_BGFlushesAllowed()); - ASSERT_EQ(1, env_->GetBackgroundThreads(Env::Priority::HIGH)); - ASSERT_OK(dbfull()->SetDBOptions({{"max_background_flushes", "3"}})); - ASSERT_EQ(3, env_->GetBackgroundThreads(Env::Priority::HIGH)); - ASSERT_EQ(3, dbfull()->TEST_BGFlushesAllowed()); -} - -TEST_F(DBOptionsTest, SetBackgroundJobs) { - Options options; - options.create_if_missing = true; - options.max_background_jobs = 8; - options.env = env_; - Reopen(options); - - for (int i = 0; i < 2; ++i) { - if (i > 0) { - options.max_background_jobs = 12; - ASSERT_OK(dbfull()->SetDBOptions( - {{"max_background_jobs", - std::to_string(options.max_background_jobs)}})); - } - - const int expected_max_flushes = options.max_background_jobs / 4; - - ASSERT_EQ(expected_max_flushes, dbfull()->TEST_BGFlushesAllowed()); - ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - - auto stop_token = dbfull()->TEST_write_controler().GetStopToken(); - - const int expected_max_compactions = 3 * expected_max_flushes; - - ASSERT_EQ(expected_max_flushes, dbfull()->TEST_BGFlushesAllowed()); - ASSERT_EQ(expected_max_compactions, dbfull()->TEST_BGCompactionsAllowed()); - - ASSERT_EQ(expected_max_flushes, - env_->GetBackgroundThreads(Env::Priority::HIGH)); - ASSERT_EQ(expected_max_compactions, - env_->GetBackgroundThreads(Env::Priority::LOW)); - } -} - -TEST_F(DBOptionsTest, AvoidFlushDuringShutdown) { - Options options; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.env = env_; - WriteOptions write_without_wal; - write_without_wal.disableWAL = true; - - ASSERT_FALSE(options.avoid_flush_during_shutdown); - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "v1", write_without_wal)); - Reopen(options); - ASSERT_EQ("v1", Get("foo")); - ASSERT_EQ("1", FilesPerLevel()); - - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "v2", write_without_wal)); - ASSERT_OK(dbfull()->SetDBOptions({{"avoid_flush_during_shutdown", "true"}})); - Reopen(options); - ASSERT_EQ("NOT_FOUND", Get("foo")); - ASSERT_EQ("", FilesPerLevel()); -} - -TEST_F(DBOptionsTest, SetDelayedWriteRateOption) { - Options options; - options.create_if_missing = true; - options.delayed_write_rate = 2 * 1024U * 1024U; - options.env = env_; - Reopen(options); - ASSERT_EQ(2 * 1024U * 1024U, - dbfull()->TEST_write_controler().max_delayed_write_rate()); - - ASSERT_OK(dbfull()->SetDBOptions({{"delayed_write_rate", "20000"}})); - ASSERT_EQ(20000, dbfull()->TEST_write_controler().max_delayed_write_rate()); -} - -TEST_F(DBOptionsTest, MaxTotalWalSizeChange) { - Random rnd(1044); - const auto value_size = size_t(1024); - std::string value = rnd.RandomString(value_size); - - Options options; - options.create_if_missing = true; - options.env = env_; - CreateColumnFamilies({"1", "2", "3"}, options); - ReopenWithColumnFamilies({"default", "1", "2", "3"}, options); - - WriteOptions write_options; - - const int key_count = 100; - for (int i = 0; i < key_count; ++i) { - for (size_t cf = 0; cf < handles_.size(); ++cf) { - ASSERT_OK(Put(static_cast(cf), Key(i), value)); - } - } - ASSERT_OK(dbfull()->SetDBOptions({{"max_total_wal_size", "10"}})); - - for (size_t cf = 0; cf < handles_.size(); ++cf) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf])); - ASSERT_EQ("1", FilesPerLevel(static_cast(cf))); - } -} - -TEST_F(DBOptionsTest, SetStatsDumpPeriodSec) { - Options options; - options.create_if_missing = true; - options.stats_dump_period_sec = 5; - options.env = env_; - Reopen(options); - ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_dump_period_sec); - - for (int i = 0; i < 20; i++) { - unsigned int num = rand() % 5000 + 1; - ASSERT_OK(dbfull()->SetDBOptions( - {{"stats_dump_period_sec", std::to_string(num)}})); - ASSERT_EQ(num, dbfull()->GetDBOptions().stats_dump_period_sec); - } - Close(); -} - -TEST_F(DBOptionsTest, SetOptionsStatsPersistPeriodSec) { - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = 5; - options.env = env_; - Reopen(options); - ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_persist_period_sec); - - ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "12345"}})); - ASSERT_EQ(12345u, dbfull()->GetDBOptions().stats_persist_period_sec); - ASSERT_NOK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "abcde"}})); - ASSERT_EQ(12345u, dbfull()->GetDBOptions().stats_persist_period_sec); -} - -static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) { - dbfull->TEST_LockMutex(); - JobContext job_context(0); - dbfull->FindObsoleteFiles(&job_context, false); - ASSERT_EQ(empty, job_context.full_scan_candidate_files.empty()); - dbfull->TEST_UnlockMutex(); - if (job_context.HaveSomethingToDelete()) { - // fulfill the contract of FindObsoleteFiles by calling PurgeObsoleteFiles - // afterwards; otherwise the test may hang on shutdown - dbfull->PurgeObsoleteFiles(job_context); - } - job_context.Clean(); -} - -TEST_F(DBOptionsTest, DeleteObsoleteFilesPeriodChange) { - Options options; - options.env = env_; - SetTimeElapseOnlySleepOnReopen(&options); - options.create_if_missing = true; - ASSERT_OK(TryReopen(options)); - - // Verify that candidate files set is empty when no full scan requested. - assert_candidate_files_empty(dbfull(), true); - - ASSERT_OK( - dbfull()->SetDBOptions({{"delete_obsolete_files_period_micros", "0"}})); - - // After delete_obsolete_files_period_micros updated to 0, the next call - // to FindObsoleteFiles should make a full scan - assert_candidate_files_empty(dbfull(), false); - - ASSERT_OK( - dbfull()->SetDBOptions({{"delete_obsolete_files_period_micros", "20"}})); - - assert_candidate_files_empty(dbfull(), true); - - env_->MockSleepForMicroseconds(20); - assert_candidate_files_empty(dbfull(), true); - - env_->MockSleepForMicroseconds(1); - assert_candidate_files_empty(dbfull(), false); - - Close(); -} - -TEST_F(DBOptionsTest, MaxOpenFilesChange) { - SpecialEnv env(env_); - Options options; - options.env = CurrentOptions().env; - options.max_open_files = -1; - - Reopen(options); - - Cache* tc = dbfull()->TEST_table_cache(); - - ASSERT_EQ(-1, dbfull()->GetDBOptions().max_open_files); - ASSERT_LT(2000, tc->GetCapacity()); - ASSERT_OK(dbfull()->SetDBOptions({{"max_open_files", "1024"}})); - ASSERT_EQ(1024, dbfull()->GetDBOptions().max_open_files); - // examine the table cache (actual size should be 1014) - ASSERT_GT(1500, tc->GetCapacity()); - Close(); -} - -TEST_F(DBOptionsTest, SanitizeDelayedWriteRate) { - Options options; - options.env = CurrentOptions().env; - options.delayed_write_rate = 0; - Reopen(options); - ASSERT_EQ(16 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate); - - options.rate_limiter.reset(NewGenericRateLimiter(31 * 1024 * 1024)); - Reopen(options); - ASSERT_EQ(31 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate); -} - -TEST_F(DBOptionsTest, SanitizeUniversalTTLCompaction) { - Options options; - options.env = CurrentOptions().env; - options.compaction_style = kCompactionStyleUniversal; - - options.ttl = 0; - options.periodic_compaction_seconds = 0; - Reopen(options); - ASSERT_EQ(0, dbfull()->GetOptions().ttl); - ASSERT_EQ(0, dbfull()->GetOptions().periodic_compaction_seconds); - - options.ttl = 0; - options.periodic_compaction_seconds = 100; - Reopen(options); - ASSERT_EQ(0, dbfull()->GetOptions().ttl); - ASSERT_EQ(100, dbfull()->GetOptions().periodic_compaction_seconds); - - options.ttl = 100; - options.periodic_compaction_seconds = 0; - Reopen(options); - ASSERT_EQ(100, dbfull()->GetOptions().ttl); - ASSERT_EQ(100, dbfull()->GetOptions().periodic_compaction_seconds); - - options.ttl = 100; - options.periodic_compaction_seconds = 500; - Reopen(options); - ASSERT_EQ(100, dbfull()->GetOptions().ttl); - ASSERT_EQ(100, dbfull()->GetOptions().periodic_compaction_seconds); -} - -TEST_F(DBOptionsTest, SanitizeTtlDefault) { - Options options; - options.env = CurrentOptions().env; - Reopen(options); - ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl); - - options.compaction_style = kCompactionStyleLevel; - options.ttl = 0; - Reopen(options); - ASSERT_EQ(0, dbfull()->GetOptions().ttl); - - options.ttl = 100; - Reopen(options); - ASSERT_EQ(100, dbfull()->GetOptions().ttl); -} - -TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) { - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.env = CurrentOptions().env; - options.ttl = 0; - Reopen(options); - ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl); - - options.ttl = 100; - Reopen(options); - ASSERT_EQ(100, dbfull()->GetOptions().ttl); - - options.ttl = 100 * 24 * 60 * 60; - Reopen(options); - ASSERT_EQ(100 * 24 * 60 * 60, dbfull()->GetOptions().ttl); - - options.ttl = 200; - options.periodic_compaction_seconds = 300; - Reopen(options); - ASSERT_EQ(200, dbfull()->GetOptions().ttl); - - options.ttl = 500; - options.periodic_compaction_seconds = 300; - Reopen(options); - ASSERT_EQ(300, dbfull()->GetOptions().ttl); -} - -TEST_F(DBOptionsTest, SetFIFOCompactionOptions) { - Options options; - options.env = CurrentOptions().env; - options.compaction_style = kCompactionStyleFIFO; - options.write_buffer_size = 10 << 10; // 10KB - options.arena_block_size = 4096; - options.compression = kNoCompression; - options.create_if_missing = true; - options.compaction_options_fifo.allow_compaction = false; - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - // Test dynamically changing ttl. - options.ttl = 1 * 60 * 60; // 1 hour - ASSERT_OK(TryReopen(options)); - - Random rnd(301); - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - env_->MockSleepForSeconds(61); - - // No files should be compacted as ttl is set to 1 hour. - ASSERT_EQ(dbfull()->GetOptions().ttl, 3600); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // Set ttl to 1 minute. So all files should get deleted. - ASSERT_OK(dbfull()->SetOptions({{"ttl", "60"}})); - ASSERT_EQ(dbfull()->GetOptions().ttl, 60); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - // Test dynamically changing compaction_options_fifo.max_table_files_size - options.compaction_options_fifo.max_table_files_size = 500 << 10; // 00KB - options.ttl = 0; - DestroyAndReopen(options); - - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // No files should be compacted as max_table_files_size is set to 500 KB. - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 500 << 10); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // Set max_table_files_size to 12 KB. So only 1 file should remain now. - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", "{max_table_files_size=12288;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 12 << 10); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 1); - - // Test dynamically changing compaction_options_fifo.allow_compaction - options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB - options.ttl = 0; - options.compaction_options_fifo.allow_compaction = false; - options.level0_file_num_compaction_trigger = 6; - DestroyAndReopen(options); - - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // No files should be compacted as max_table_files_size is set to 500 KB and - // allow_compaction is false - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - false); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // Set allow_compaction to true. So number of files should be between 1 and 5. - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", "{allow_compaction=true;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - true); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_GE(NumTableFilesAtLevel(0), 1); - ASSERT_LE(NumTableFilesAtLevel(0), 5); -} - -TEST_F(DBOptionsTest, CompactionReadaheadSizeChange) { - SpecialEnv env(env_); - Options options; - options.env = &env; - - options.compaction_readahead_size = 0; - options.level0_file_num_compaction_trigger = 2; - const std::string kValue(1024, 'v'); - Reopen(options); - - ASSERT_EQ(0, dbfull()->GetDBOptions().compaction_readahead_size); - ASSERT_OK(dbfull()->SetDBOptions({{"compaction_readahead_size", "256"}})); - ASSERT_EQ(256, dbfull()->GetDBOptions().compaction_readahead_size); - for (int i = 0; i < 1024; i++) { - ASSERT_OK(Put(Key(i), kValue)); - } - ASSERT_OK(Flush()); - for (int i = 0; i < 1024 * 2; i++) { - ASSERT_OK(Put(Key(i), kValue)); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(256, env_->compaction_readahead_size_); - Close(); -} - -TEST_F(DBOptionsTest, FIFOTtlBackwardCompatible) { - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.write_buffer_size = 10 << 10; // 10KB - options.create_if_missing = true; - options.env = CurrentOptions().env; - - ASSERT_OK(TryReopen(options)); - - Random rnd(301); - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // In release 6.0, ttl was promoted from a secondary level option under - // compaction_options_fifo to a top level option under ColumnFamilyOptions. - // We still need to handle old SetOptions calls but should ignore - // ttl under compaction_options_fifo. - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", - "{allow_compaction=true;max_table_files_size=1024;ttl=731;}"}, - {"ttl", "60"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - true); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 1024); - ASSERT_EQ(dbfull()->GetOptions().ttl, 60); - - // Put ttl as the first option inside compaction_options_fifo. That works as - // it doesn't overwrite any other option. - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", - "{ttl=985;allow_compaction=true;max_table_files_size=1024;}"}, - {"ttl", "191"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - true); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 1024); - ASSERT_EQ(dbfull()->GetOptions().ttl, 191); -} - -TEST_F(DBOptionsTest, ChangeCompression) { - if (!Snappy_Supported() || !LZ4_Supported()) { - return; - } - Options options; - options.write_buffer_size = 10 << 10; // 10KB - options.level0_file_num_compaction_trigger = 2; - options.create_if_missing = true; - options.compression = CompressionType::kLZ4Compression; - options.bottommost_compression = CompressionType::kNoCompression; - options.bottommost_compression_opts.level = 2; - options.bottommost_compression_opts.parallel_threads = 1; - options.env = CurrentOptions().env; - - ASSERT_OK(TryReopen(options)); - - CompressionType compression_used = CompressionType::kLZ4Compression; - CompressionOptions compression_opt_used; - bool compacted = false; - SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* c = reinterpret_cast(arg); - compression_used = c->output_compression(); - compression_opt_used = c->output_compression_opts(); - compacted = true; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("foo", "foofoofoo")); - ASSERT_OK(Put("bar", "foofoofoo")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "foofoofoo")); - ASSERT_OK(Put("bar", "foofoofoo")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(compacted); - ASSERT_EQ(CompressionType::kNoCompression, compression_used); - ASSERT_EQ(options.compression_opts.level, compression_opt_used.level); - ASSERT_EQ(options.compression_opts.parallel_threads, - compression_opt_used.parallel_threads); - - compression_used = CompressionType::kLZ4Compression; - compacted = false; - ASSERT_OK(dbfull()->SetOptions( - {{"bottommost_compression", "kSnappyCompression"}, - {"bottommost_compression_opts", "0:6:0:0:4:true"}})); - ASSERT_OK(Put("foo", "foofoofoo")); - ASSERT_OK(Put("bar", "foofoofoo")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "foofoofoo")); - ASSERT_OK(Put("bar", "foofoofoo")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(compacted); - ASSERT_EQ(CompressionType::kSnappyCompression, compression_used); - ASSERT_EQ(6, compression_opt_used.level); - // Right now parallel_level is not yet allowed to be changed. - - SyncPoint::GetInstance()->DisableProcessing(); -} - - -TEST_F(DBOptionsTest, BottommostCompressionOptsWithFallbackType) { - // Verify the bottommost compression options still take effect even when the - // bottommost compression type is left at its default value. Verify for both - // automatic and manual compaction. - if (!Snappy_Supported() || !LZ4_Supported()) { - return; - } - - constexpr int kUpperCompressionLevel = 1; - constexpr int kBottommostCompressionLevel = 2; - constexpr int kNumL0Files = 2; - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = kNumL0Files; - options.compression = CompressionType::kLZ4Compression; - options.compression_opts.level = kUpperCompressionLevel; - options.bottommost_compression_opts.level = kBottommostCompressionLevel; - options.bottommost_compression_opts.enabled = true; - Reopen(options); - - CompressionType compression_used = CompressionType::kDisableCompressionOption; - CompressionOptions compression_opt_used; - bool compacted = false; - SyncPoint::GetInstance()->SetCallBack( - "CompactionPicker::RegisterCompaction:Registered", [&](void* arg) { - Compaction* c = static_cast(arg); - compression_used = c->output_compression(); - compression_opt_used = c->output_compression_opts(); - compacted = true; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - // First, verify for automatic compaction. - for (int i = 0; i < kNumL0Files; ++i) { - ASSERT_OK(Put("foo", "foofoofoo")); - ASSERT_OK(Put("bar", "foofoofoo")); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_TRUE(compacted); - ASSERT_EQ(CompressionType::kLZ4Compression, compression_used); - ASSERT_EQ(kBottommostCompressionLevel, compression_opt_used.level); - - // Second, verify for manual compaction. - compacted = false; - compression_used = CompressionType::kDisableCompressionOption; - compression_opt_used = CompressionOptions(); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - ASSERT_TRUE(compacted); - ASSERT_EQ(CompressionType::kLZ4Compression, compression_used); - ASSERT_EQ(kBottommostCompressionLevel, compression_opt_used.level); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_properties_test.cc b/db/db_properties_test.cc deleted file mode 100644 index 074f4e9a8..000000000 --- a/db/db_properties_test.cc +++ /dev/null @@ -1,2376 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include -#include - -#include "db/db_test_util.h" -#include "db/write_stall_stats.h" -#include "options/cf_options.h" -#include "port/stack_trace.h" -#include "rocksdb/listener.h" -#include "rocksdb/options.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/perf_level.h" -#include "rocksdb/table.h" -#include "table/block_based/block.h" -#include "table/format.h" -#include "table/meta_blocks.h" -#include "table/table_builder.h" -#include "test_util/mock_time_env.h" -#include "util/random.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class DBPropertiesTest : public DBTestBase { - public: - DBPropertiesTest() - : DBTestBase("db_properties_test", /*env_do_fsync=*/false) {} - - void AssertDbStats(const std::map& db_stats, - double expected_uptime, int expected_user_bytes_written, - int expected_wal_bytes_written, - int expected_user_writes_by_self, - int expected_user_writes_with_wal) { - ASSERT_EQ(std::to_string(expected_uptime), db_stats.at("db.uptime")); - ASSERT_EQ(std::to_string(expected_wal_bytes_written), - db_stats.at("db.wal_bytes_written")); - ASSERT_EQ("0", db_stats.at("db.wal_syncs")); - ASSERT_EQ(std::to_string(expected_user_bytes_written), - db_stats.at("db.user_bytes_written")); - ASSERT_EQ("0", db_stats.at("db.user_writes_by_other")); - ASSERT_EQ(std::to_string(expected_user_writes_by_self), - db_stats.at("db.user_writes_by_self")); - ASSERT_EQ(std::to_string(expected_user_writes_with_wal), - db_stats.at("db.user_writes_with_wal")); - ASSERT_EQ("0", db_stats.at("db.user_write_stall_micros")); - } -}; - -TEST_F(DBPropertiesTest, Empty) { - do { - Options options; - options.env = env_; - options.write_buffer_size = 100000; // Small write buffer - options.allow_concurrent_memtable_write = false; - options = CurrentOptions(options); - CreateAndReopenWithCF({"pikachu"}, options); - - std::string num; - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ("0", num); - - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ("1", num); - - // Block sync calls - env_->delay_sstable_sync_.store(true, std::memory_order_release); - ASSERT_OK(Put(1, "k1", std::string(100000, 'x'))); // Fill memtable - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ("2", num); - - ASSERT_OK(Put(1, "k2", std::string(100000, 'y'))); // Trigger compaction - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ("1", num); - - ASSERT_EQ("v1", Get(1, "foo")); - // Release sync calls - env_->delay_sstable_sync_.store(false, std::memory_order_release); - - ASSERT_OK(db_->DisableFileDeletions()); - ASSERT_TRUE( - dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("0", num); - - ASSERT_OK(db_->DisableFileDeletions()); - ASSERT_TRUE( - dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("0", num); - - ASSERT_OK(db_->DisableFileDeletions()); - ASSERT_TRUE( - dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("0", num); - - ASSERT_OK(db_->EnableFileDeletions(false)); - ASSERT_TRUE( - dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("0", num); - - ASSERT_OK(db_->EnableFileDeletions()); - ASSERT_TRUE( - dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("1", num); - } while (ChangeOptions()); -} - -TEST_F(DBPropertiesTest, CurrentVersionNumber) { - uint64_t v1, v2, v3; - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.current-super-version-number", &v1)); - ASSERT_OK(Put("12345678", "")); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.current-super-version-number", &v2)); - ASSERT_OK(Flush()); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.current-super-version-number", &v3)); - - ASSERT_EQ(v1, v2); - ASSERT_GT(v3, v2); -} - -TEST_F(DBPropertiesTest, GetAggregatedIntPropertyTest) { - const int kKeySize = 100; - const int kValueSize = 500; - const int kKeyNum = 100; - - Options options; - options.env = env_; - options.create_if_missing = true; - options.write_buffer_size = (kKeySize + kValueSize) * kKeyNum / 10; - // Make them never flush - options.min_write_buffer_number_to_merge = 1000; - options.max_write_buffer_number = 1000; - options = CurrentOptions(options); - CreateAndReopenWithCF({"one", "two", "three", "four"}, options); - - Random rnd(301); - for (auto* handle : handles_) { - for (int i = 0; i < kKeyNum; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), handle, rnd.RandomString(kKeySize), - rnd.RandomString(kValueSize))); - } - } - - uint64_t manual_sum = 0; - uint64_t api_sum = 0; - uint64_t value = 0; - for (auto* handle : handles_) { - ASSERT_TRUE( - db_->GetIntProperty(handle, DB::Properties::kSizeAllMemTables, &value)); - manual_sum += value; - } - ASSERT_TRUE(db_->GetAggregatedIntProperty(DB::Properties::kSizeAllMemTables, - &api_sum)); - ASSERT_GT(manual_sum, 0); - ASSERT_EQ(manual_sum, api_sum); - - ASSERT_FALSE(db_->GetAggregatedIntProperty(DB::Properties::kDBStats, &value)); - - uint64_t before_flush_trm; - uint64_t after_flush_trm; - for (auto* handle : handles_) { - ASSERT_TRUE(db_->GetAggregatedIntProperty( - DB::Properties::kEstimateTableReadersMem, &before_flush_trm)); - - // Issue flush and expect larger memory usage of table readers. - ASSERT_OK(db_->Flush(FlushOptions(), handle)); - - ASSERT_TRUE(db_->GetAggregatedIntProperty( - DB::Properties::kEstimateTableReadersMem, &after_flush_trm)); - ASSERT_GT(after_flush_trm, before_flush_trm); - } -} - -namespace { -void ResetTableProperties(TableProperties* tp) { - tp->data_size = 0; - tp->index_size = 0; - tp->filter_size = 0; - tp->raw_key_size = 0; - tp->raw_value_size = 0; - tp->num_data_blocks = 0; - tp->num_entries = 0; - tp->num_deletions = 0; - tp->num_merge_operands = 0; - tp->num_range_deletions = 0; -} - -void ParseTablePropertiesString(std::string tp_string, TableProperties* tp) { - double dummy_double; - std::replace(tp_string.begin(), tp_string.end(), ';', ' '); - std::replace(tp_string.begin(), tp_string.end(), '=', ' '); - ResetTableProperties(tp); - sscanf(tp_string.c_str(), - "# data blocks %" SCNu64 " # entries %" SCNu64 " # deletions %" SCNu64 - " # merge operands %" SCNu64 " # range deletions %" SCNu64 - " raw key size %" SCNu64 - " raw average key size %lf " - " raw value size %" SCNu64 - " raw average value size %lf " - " data block size %" SCNu64 " index block size (user-key? %" SCNu64 - ", delta-value? %" SCNu64 ") %" SCNu64 " filter block size %" SCNu64, - &tp->num_data_blocks, &tp->num_entries, &tp->num_deletions, - &tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size, - &dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size, - &tp->index_key_is_user_key, &tp->index_value_is_delta_encoded, - &tp->index_size, &tp->filter_size); -} - -void VerifySimilar(uint64_t a, uint64_t b, double bias) { - ASSERT_EQ(a == 0U, b == 0U); - if (a == 0) { - return; - } - double dbl_a = static_cast(a); - double dbl_b = static_cast(b); - if (dbl_a > dbl_b) { - ASSERT_LT(static_cast(dbl_a - dbl_b) / (dbl_a + dbl_b), bias); - } else { - ASSERT_LT(static_cast(dbl_b - dbl_a) / (dbl_a + dbl_b), bias); - } -} - -void VerifyTableProperties( - const TableProperties& base_tp, const TableProperties& new_tp, - double filter_size_bias = CACHE_LINE_SIZE >= 256 ? 0.18 : 0.1, - double index_size_bias = 0.1, double data_size_bias = 0.1, - double num_data_blocks_bias = 0.05) { - VerifySimilar(base_tp.data_size, new_tp.data_size, data_size_bias); - VerifySimilar(base_tp.index_size, new_tp.index_size, index_size_bias); - VerifySimilar(base_tp.filter_size, new_tp.filter_size, filter_size_bias); - VerifySimilar(base_tp.num_data_blocks, new_tp.num_data_blocks, - num_data_blocks_bias); - - ASSERT_EQ(base_tp.raw_key_size, new_tp.raw_key_size); - ASSERT_EQ(base_tp.raw_value_size, new_tp.raw_value_size); - ASSERT_EQ(base_tp.num_entries, new_tp.num_entries); - ASSERT_EQ(base_tp.num_deletions, new_tp.num_deletions); - ASSERT_EQ(base_tp.num_range_deletions, new_tp.num_range_deletions); - - // Merge operands may become Puts, so we only have an upper bound the exact - // number of merge operands. - ASSERT_GE(base_tp.num_merge_operands, new_tp.num_merge_operands); -} - -void GetExpectedTableProperties( - TableProperties* expected_tp, const int kKeySize, const int kValueSize, - const int kPutsPerTable, const int kDeletionsPerTable, - const int kMergeOperandsPerTable, const int kRangeDeletionsPerTable, - const int kTableCount, const int kBloomBitsPerKey, const size_t kBlockSize, - const bool index_key_is_user_key, const bool value_delta_encoding) { - const int kKeysPerTable = - kPutsPerTable + kDeletionsPerTable + kMergeOperandsPerTable; - const int kPutCount = kTableCount * kPutsPerTable; - const int kDeletionCount = kTableCount * kDeletionsPerTable; - const int kMergeCount = kTableCount * kMergeOperandsPerTable; - const int kRangeDeletionCount = kTableCount * kRangeDeletionsPerTable; - const int kKeyCount = - kPutCount + kDeletionCount + kMergeCount + kRangeDeletionCount; - const int kAvgSuccessorSize = kKeySize / 5; - const int kEncodingSavePerKey = kKeySize / 4; - expected_tp->raw_key_size = kKeyCount * (kKeySize + 8); - expected_tp->raw_value_size = - (kPutCount + kMergeCount + kRangeDeletionCount) * kValueSize; - expected_tp->num_entries = kKeyCount; - expected_tp->num_deletions = kDeletionCount + kRangeDeletionCount; - expected_tp->num_merge_operands = kMergeCount; - expected_tp->num_range_deletions = kRangeDeletionCount; - expected_tp->num_data_blocks = - kTableCount * - (kKeysPerTable * (kKeySize - kEncodingSavePerKey + kValueSize)) / - kBlockSize; - expected_tp->data_size = - kTableCount * (kKeysPerTable * (kKeySize + 8 + kValueSize)); - expected_tp->index_size = - expected_tp->num_data_blocks * - (kAvgSuccessorSize + (index_key_is_user_key ? 0 : 8) - - // discount 1 byte as value size is not encoded in value delta encoding - (value_delta_encoding ? 1 : 0)); - expected_tp->filter_size = - kTableCount * ((kKeysPerTable * kBloomBitsPerKey + 7) / 8 + - /*average-ish overhead*/ CACHE_LINE_SIZE / 2); -} -} // anonymous namespace - -TEST_F(DBPropertiesTest, ValidatePropertyInfo) { - for (const auto& ppt_name_and_info : InternalStats::ppt_name_to_info) { - // If C++ gets a std::string_literal, this would be better to check at - // compile-time using static_assert. - ASSERT_TRUE(ppt_name_and_info.first.empty() || - !isdigit(ppt_name_and_info.first.back())); - - int count = 0; - count += (ppt_name_and_info.second.handle_string == nullptr) ? 0 : 1; - count += (ppt_name_and_info.second.handle_int == nullptr) ? 0 : 1; - count += (ppt_name_and_info.second.handle_string_dbimpl == nullptr) ? 0 : 1; - ASSERT_TRUE(count == 1); - } -} - -TEST_F(DBPropertiesTest, ValidateSampleNumber) { - // When "max_open_files" is -1, we read all the files for - // "rocksdb.estimate-num-keys" computation, which is the ground truth. - // Otherwise, we sample 20 newest files to make an estimation. - // Formula: lastest_20_files_active_key_ratio * total_files - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.level0_stop_writes_trigger = 1000; - DestroyAndReopen(options); - int key = 0; - for (int files = 20; files >= 10; files -= 10) { - for (int i = 0; i < files; i++) { - int rows = files / 10; - for (int j = 0; j < rows; j++) { - ASSERT_OK(db_->Put(WriteOptions(), std::to_string(++key), "foo")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - } - std::string num; - Reopen(options); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num)); - ASSERT_EQ("45", num); - options.max_open_files = -1; - Reopen(options); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num)); - ASSERT_EQ("50", num); -} - -TEST_F(DBPropertiesTest, AggregatedTableProperties) { - for (int kTableCount = 40; kTableCount <= 100; kTableCount += 30) { - const int kDeletionsPerTable = 0; - const int kMergeOperandsPerTable = 15; - const int kRangeDeletionsPerTable = 5; - const int kPutsPerTable = 100; - const int kKeySize = 80; - const int kValueSize = 200; - const int kBloomBitsPerKey = 20; - - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 8; - options.compression = kNoCompression; - options.create_if_missing = true; - options.merge_operator.reset(new TestPutOperator()); - - BlockBasedTableOptions table_options; - table_options.filter_policy.reset( - NewBloomFilterPolicy(kBloomBitsPerKey, false)); - table_options.block_size = 1024; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - - // Hold open a snapshot to prevent range tombstones from being compacted - // away. - ManagedSnapshot snapshot(db_); - - Random rnd(5632); - for (int table = 1; table <= kTableCount; ++table) { - for (int i = 0; i < kPutsPerTable; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), rnd.RandomString(kKeySize), - rnd.RandomString(kValueSize))); - } - for (int i = 0; i < kDeletionsPerTable; i++) { - ASSERT_OK(db_->Delete(WriteOptions(), rnd.RandomString(kKeySize))); - } - for (int i = 0; i < kMergeOperandsPerTable; i++) { - ASSERT_OK(db_->Merge(WriteOptions(), rnd.RandomString(kKeySize), - rnd.RandomString(kValueSize))); - } - for (int i = 0; i < kRangeDeletionsPerTable; i++) { - std::string start = rnd.RandomString(kKeySize); - std::string end = start; - end.resize(kValueSize); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - start, end)); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - std::string property; - db_->GetProperty(DB::Properties::kAggregatedTableProperties, &property); - TableProperties output_tp; - ParseTablePropertiesString(property, &output_tp); - bool index_key_is_user_key = output_tp.index_key_is_user_key > 0; - bool value_is_delta_encoded = output_tp.index_value_is_delta_encoded > 0; - - TableProperties expected_tp; - GetExpectedTableProperties( - &expected_tp, kKeySize, kValueSize, kPutsPerTable, kDeletionsPerTable, - kMergeOperandsPerTable, kRangeDeletionsPerTable, kTableCount, - kBloomBitsPerKey, table_options.block_size, index_key_is_user_key, - value_is_delta_encoded); - - VerifyTableProperties(expected_tp, output_tp); - } -} - -TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) { - Options options = CurrentOptions(); - options.write_buffer_size = 110 << 10; - options.level0_file_num_compaction_trigger = 6; - options.num_levels = 4; - options.compression = kNoCompression; - options.max_bytes_for_level_base = 4500 << 10; - options.target_file_size_base = 98 << 10; - options.max_write_buffer_number = 2; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.max_open_files = 11; // Make sure no proloading of table readers - - // RocksDB sanitize max open files to at least 20. Modify it back. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = static_cast(arg); - *max_open_files = 11; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - - CreateAndReopenWithCF({"pikachu"}, options); - int key_index = 0; - Random rnd(301); - for (int num = 0; num < 8; num++) { - ASSERT_OK(Put("foo", "bar")); - GenerateNewFile(&rnd, &key_index); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - std::string prop; - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.dbstats", &prop)); - - // Get() after flushes, See latency histogram tracked. - for (int key = 0; key < key_index; key++) { - Get(Key(key)); - } - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cfstats", &prop)); - ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_NE(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - - // Reopen and issue Get(). See thee latency tracked - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - for (int key = 0; key < key_index; key++) { - Get(Key(key)); - } - - // Test for getting immutable_db_options_.statistics - ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(), - "rocksdb.options-statistics", &prop)); - ASSERT_NE(std::string::npos, prop.find("rocksdb.block.cache.miss")); - ASSERT_EQ(std::string::npos, prop.find("rocksdb.db.f.micros")); - - ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(), - "rocksdb.cf-file-histogram", &prop)); - ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_NE(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - - // Reopen and issue iterating. See thee latency tracked - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cf-file-histogram", &prop)); - ASSERT_EQ(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - { - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - for (iter->Seek(Key(0)); iter->Valid(); iter->Next()) { - } - ASSERT_OK(iter->status()); - } - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cf-file-histogram", &prop)); - ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_NE(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - - // CF 1 should show no histogram. - ASSERT_TRUE( - dbfull()->GetProperty(handles_[1], "rocksdb.cf-file-histogram", &prop)); - ASSERT_EQ(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - // put something and read it back , CF 1 should show histogram. - ASSERT_OK(Put(1, "foo", "bar")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("bar", Get(1, "foo")); - - ASSERT_TRUE( - dbfull()->GetProperty(handles_[1], "rocksdb.cf-file-histogram", &prop)); - ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - - // options.max_open_files preloads table readers. - options.max_open_files = -1; - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(), - "rocksdb.cf-file-histogram", &prop)); - ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_NE(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - for (int key = 0; key < key_index; key++) { - Get(Key(key)); - } - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cfstats", &prop)); - ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_NE(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); - - // Clear internal stats - ASSERT_OK(dbfull()->ResetStats()); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cfstats", &prop)); - ASSERT_EQ(std::string::npos, prop.find("** Level 0 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 1 read latency histogram")); - ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); -} - -TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) { - const int kTableCount = 100; - const int kDeletionsPerTable = 0; - const int kMergeOperandsPerTable = 2; - const int kRangeDeletionsPerTable = 2; - const int kPutsPerTable = 10; - const int kKeySize = 50; - const int kValueSize = 400; - const int kMaxLevel = 7; - const int kBloomBitsPerKey = 20; - Random rnd(301); - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 8; - options.compression = kNoCompression; - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.target_file_size_base = 8192; - options.max_bytes_for_level_base = 10000; - options.max_bytes_for_level_multiplier = 2; - // This ensures there no compaction happening when we call GetProperty(). - options.disable_auto_compactions = true; - options.merge_operator.reset(new TestPutOperator()); - - BlockBasedTableOptions table_options; - table_options.filter_policy.reset( - NewBloomFilterPolicy(kBloomBitsPerKey, false)); - table_options.block_size = 1024; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - DestroyAndReopen(options); - - // Hold open a snapshot to prevent range tombstones from being compacted away. - ManagedSnapshot snapshot(db_); - - std::string level_tp_strings[kMaxLevel]; - std::string tp_string; - TableProperties level_tps[kMaxLevel]; - TableProperties tp, sum_tp, expected_tp; - for (int table = 1; table <= kTableCount; ++table) { - for (int i = 0; i < kPutsPerTable; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), rnd.RandomString(kKeySize), - rnd.RandomString(kValueSize))); - } - for (int i = 0; i < kDeletionsPerTable; i++) { - ASSERT_OK(db_->Delete(WriteOptions(), rnd.RandomString(kKeySize))); - } - for (int i = 0; i < kMergeOperandsPerTable; i++) { - ASSERT_OK(db_->Merge(WriteOptions(), rnd.RandomString(kKeySize), - rnd.RandomString(kValueSize))); - } - for (int i = 0; i < kRangeDeletionsPerTable; i++) { - std::string start = rnd.RandomString(kKeySize); - std::string end = start; - end.resize(kValueSize); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - start, end)); - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ResetTableProperties(&sum_tp); - for (int level = 0; level < kMaxLevel; ++level) { - db_->GetProperty(DB::Properties::kAggregatedTablePropertiesAtLevel + - std::to_string(level), - &level_tp_strings[level]); - ParseTablePropertiesString(level_tp_strings[level], &level_tps[level]); - sum_tp.data_size += level_tps[level].data_size; - sum_tp.index_size += level_tps[level].index_size; - sum_tp.filter_size += level_tps[level].filter_size; - sum_tp.raw_key_size += level_tps[level].raw_key_size; - sum_tp.raw_value_size += level_tps[level].raw_value_size; - sum_tp.num_data_blocks += level_tps[level].num_data_blocks; - sum_tp.num_entries += level_tps[level].num_entries; - sum_tp.num_deletions += level_tps[level].num_deletions; - sum_tp.num_merge_operands += level_tps[level].num_merge_operands; - sum_tp.num_range_deletions += level_tps[level].num_range_deletions; - } - db_->GetProperty(DB::Properties::kAggregatedTableProperties, &tp_string); - ParseTablePropertiesString(tp_string, &tp); - bool index_key_is_user_key = tp.index_key_is_user_key > 0; - bool value_is_delta_encoded = tp.index_value_is_delta_encoded > 0; - ASSERT_EQ(sum_tp.data_size, tp.data_size); - ASSERT_EQ(sum_tp.index_size, tp.index_size); - ASSERT_EQ(sum_tp.filter_size, tp.filter_size); - ASSERT_EQ(sum_tp.raw_key_size, tp.raw_key_size); - ASSERT_EQ(sum_tp.raw_value_size, tp.raw_value_size); - ASSERT_EQ(sum_tp.num_data_blocks, tp.num_data_blocks); - ASSERT_EQ(sum_tp.num_entries, tp.num_entries); - ASSERT_EQ(sum_tp.num_deletions, tp.num_deletions); - ASSERT_EQ(sum_tp.num_merge_operands, tp.num_merge_operands); - ASSERT_EQ(sum_tp.num_range_deletions, tp.num_range_deletions); - if (table > 3) { - GetExpectedTableProperties( - &expected_tp, kKeySize, kValueSize, kPutsPerTable, kDeletionsPerTable, - kMergeOperandsPerTable, kRangeDeletionsPerTable, table, - kBloomBitsPerKey, table_options.block_size, index_key_is_user_key, - value_is_delta_encoded); - // Gives larger bias here as index block size, filter block size, - // and data block size become much harder to estimate in this test. - VerifyTableProperties(expected_tp, tp, CACHE_LINE_SIZE >= 256 ? 0.6 : 0.5, - 0.5, 0.5, 0.25); - } - } -} - -TEST_F(DBPropertiesTest, NumImmutableMemTable) { - do { - Options options = CurrentOptions(); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.write_buffer_size = 1000000; - options.max_write_buffer_size_to_maintain = - 5 * static_cast(options.write_buffer_size); - CreateAndReopenWithCF({"pikachu"}, options); - - std::string big_value(1000000 * 2, 'x'); - std::string num; - uint64_t value; - SetPerfLevel(kEnableTime); - ASSERT_TRUE(GetPerfLevel() == kEnableTime); - - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k1", big_value)); - ASSERT_TRUE(dbfull()->GetProperty(handles_[1], - "rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], DB::Properties::kNumImmutableMemTableFlushed, &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ(num, "1"); - get_perf_context()->Reset(); - Get(1, "k1"); - ASSERT_EQ(1, static_cast(get_perf_context()->get_from_memtable_count)); - - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k2", big_value)); - ASSERT_TRUE(dbfull()->GetProperty(handles_[1], - "rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "1"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ(num, "1"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-imm-mem-tables", &num)); - ASSERT_EQ(num, "1"); - - get_perf_context()->Reset(); - Get(1, "k1"); - ASSERT_EQ(2, static_cast(get_perf_context()->get_from_memtable_count)); - get_perf_context()->Reset(); - Get(1, "k2"); - ASSERT_EQ(1, static_cast(get_perf_context()->get_from_memtable_count)); - - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k3", big_value)); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.cur-size-active-mem-table", &num)); - ASSERT_TRUE(dbfull()->GetProperty(handles_[1], - "rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "2"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &num)); - ASSERT_EQ(num, "1"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], "rocksdb.num-entries-imm-mem-tables", &num)); - ASSERT_EQ(num, "2"); - get_perf_context()->Reset(); - Get(1, "k2"); - ASSERT_EQ(2, static_cast(get_perf_context()->get_from_memtable_count)); - get_perf_context()->Reset(); - Get(1, "k3"); - ASSERT_EQ(1, static_cast(get_perf_context()->get_from_memtable_count)); - get_perf_context()->Reset(); - Get(1, "k1"); - ASSERT_EQ(3, static_cast(get_perf_context()->get_from_memtable_count)); - - ASSERT_OK(Flush(1)); - ASSERT_TRUE(dbfull()->GetProperty(handles_[1], - "rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty( - handles_[1], DB::Properties::kNumImmutableMemTableFlushed, &num)); - ASSERT_EQ(num, "3"); - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.cur-size-active-mem-table", &value)); - // "192" is the size of the metadata of two empty skiplists, this would - // break if we change the default skiplist implementation - ASSERT_GE(value, 192); - - uint64_t int_num; - uint64_t base_total_size; - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.estimate-num-keys", &base_total_size)); - - ASSERT_OK(dbfull()->Delete(writeOpt, handles_[1], "k2")); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k3", "")); - ASSERT_OK(dbfull()->Delete(writeOpt, handles_[1], "k3")); - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.num-deletes-active-mem-table", &int_num)); - ASSERT_EQ(int_num, 2U); - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.num-entries-active-mem-table", &int_num)); - ASSERT_EQ(int_num, 3U); - - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k2", big_value)); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "k2", big_value)); - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.num-entries-imm-mem-tables", &int_num)); - ASSERT_EQ(int_num, 4U); - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.num-deletes-imm-mem-tables", &int_num)); - ASSERT_EQ(int_num, 2U); - - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.estimate-num-keys", &int_num)); - ASSERT_EQ(int_num, base_total_size + 1); - - SetPerfLevel(kDisable); - ASSERT_TRUE(GetPerfLevel() == kDisable); - } while (ChangeCompactOptions()); -} - -// TODO(techdept) : Disabled flaky test #12863555 -TEST_F(DBPropertiesTest, DISABLED_GetProperty) { - // Set sizes to both background thread pool to be 1 and block them. - env_->SetBackgroundThreads(1, Env::HIGH); - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - test::SleepingBackgroundTask sleeping_task_high; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_high, Env::Priority::HIGH); - - Options options = CurrentOptions(); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - options.compaction_style = kCompactionStyleUniversal; - options.level0_file_num_compaction_trigger = 1; - options.compaction_options_universal.size_ratio = 50; - options.max_background_compactions = 1; - options.max_background_flushes = 1; - options.max_write_buffer_number = 10; - options.min_write_buffer_number_to_merge = 1; - options.max_write_buffer_size_to_maintain = 0; - options.write_buffer_size = 1000000; - Reopen(options); - - std::string big_value(1000000 * 2, 'x'); - std::string num; - uint64_t int_num; - SetPerfLevel(kEnableTime); - - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_EQ(int_num, 0U); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-live-data-size", &int_num)); - ASSERT_EQ(int_num, 0U); - - ASSERT_OK(dbfull()->Put(writeOpt, "k1", big_value)); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.mem-table-flush-pending", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num)); - ASSERT_EQ(num, "1"); - get_perf_context()->Reset(); - - ASSERT_OK(dbfull()->Put(writeOpt, "k2", big_value)); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "1"); - ASSERT_OK(dbfull()->Delete(writeOpt, "k-non-existing")); - ASSERT_OK(dbfull()->Put(writeOpt, "k3", big_value)); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num)); - ASSERT_EQ(num, "2"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.mem-table-flush-pending", &num)); - ASSERT_EQ(num, "1"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num)); - ASSERT_EQ(num, "2"); - // Verify the same set of properties through GetIntProperty - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.num-immutable-mem-table", &int_num)); - ASSERT_EQ(int_num, 2U); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.mem-table-flush-pending", &int_num)); - ASSERT_EQ(int_num, 1U); - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.compaction-pending", &int_num)); - ASSERT_EQ(int_num, 0U); - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num)); - ASSERT_EQ(int_num, 2U); - - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_EQ(int_num, 0U); - - sleeping_task_high.WakeUp(); - sleeping_task_high.WaitUntilDone(); - dbfull()->TEST_WaitForFlushMemTable(); - - ASSERT_OK(dbfull()->Put(writeOpt, "k4", big_value)); - ASSERT_OK(dbfull()->Put(writeOpt, "k5", big_value)); - dbfull()->TEST_WaitForFlushMemTable(); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.mem-table-flush-pending", &num)); - ASSERT_EQ(num, "0"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num)); - ASSERT_EQ(num, "1"); - ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num)); - ASSERT_EQ(num, "4"); - - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_GT(int_num, 0U); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - - // Wait for compaction to be done. This is important because otherwise RocksDB - // might schedule a compaction when reopening the database, failing assertion - // (A) as a result. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - options.max_open_files = 10; - Reopen(options); - // After reopening, no table reader is loaded, so no memory for table readers - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_EQ(int_num, 0U); // (A) - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num)); - ASSERT_GT(int_num, 0U); - - // After reading a key, at least one table reader is loaded. - Get("k5"); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_GT(int_num, 0U); - - // Test rocksdb.num-live-versions - { - options.level0_file_num_compaction_trigger = 20; - Reopen(options); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num)); - ASSERT_EQ(int_num, 1U); - - // Use an iterator to hold current version - std::unique_ptr iter1(dbfull()->NewIterator(ReadOptions())); - - ASSERT_OK(dbfull()->Put(writeOpt, "k6", big_value)); - ASSERT_OK(Flush()); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num)); - ASSERT_EQ(int_num, 2U); - - // Use an iterator to hold current version - std::unique_ptr iter2(dbfull()->NewIterator(ReadOptions())); - - ASSERT_OK(dbfull()->Put(writeOpt, "k7", big_value)); - ASSERT_OK(Flush()); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num)); - ASSERT_EQ(int_num, 3U); - - iter2.reset(); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num)); - ASSERT_EQ(int_num, 2U); - - iter1.reset(); - ASSERT_TRUE( - dbfull()->GetIntProperty("rocksdb.num-live-versions", &int_num)); - ASSERT_EQ(int_num, 1U); - } -} - -TEST_F(DBPropertiesTest, ApproximateMemoryUsage) { - const int kNumRounds = 10; - // TODO(noetzli) kFlushesPerRound does not really correlate with how many - // flushes happen. - const int kFlushesPerRound = 10; - const int kWritesPerFlush = 10; - const int kKeySize = 100; - const int kValueSize = 1000; - Options options; - options.write_buffer_size = 1000; // small write buffer - options.min_write_buffer_number_to_merge = 4; - options.compression = kNoCompression; - options.create_if_missing = true; - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - - std::vector iters; - - uint64_t active_mem; - uint64_t unflushed_mem; - uint64_t all_mem; - uint64_t prev_all_mem; - - // Phase 0. The verify the initial value of all these properties are the same - // as we have no mem-tables. - dbfull()->GetIntProperty("rocksdb.cur-size-active-mem-table", &active_mem); - dbfull()->GetIntProperty("rocksdb.cur-size-all-mem-tables", &unflushed_mem); - dbfull()->GetIntProperty("rocksdb.size-all-mem-tables", &all_mem); - ASSERT_EQ(all_mem, active_mem); - ASSERT_EQ(all_mem, unflushed_mem); - - // Phase 1. Simply issue Put() and expect "cur-size-all-mem-tables" equals to - // "size-all-mem-tables" - for (int r = 0; r < kNumRounds; ++r) { - for (int f = 0; f < kFlushesPerRound; ++f) { - for (int w = 0; w < kWritesPerFlush; ++w) { - ASSERT_OK( - Put(rnd.RandomString(kKeySize), rnd.RandomString(kValueSize))); - } - } - // Make sure that there is no flush between getting the two properties. - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - dbfull()->GetIntProperty("rocksdb.cur-size-all-mem-tables", &unflushed_mem); - dbfull()->GetIntProperty("rocksdb.size-all-mem-tables", &all_mem); - // in no iterator case, these two number should be the same. - ASSERT_EQ(unflushed_mem, all_mem); - } - prev_all_mem = all_mem; - - // Phase 2. Keep issuing Put() but also create new iterators. This time we - // expect "size-all-mem-tables" > "cur-size-all-mem-tables". - for (int r = 0; r < kNumRounds; ++r) { - iters.push_back(db_->NewIterator(ReadOptions())); - for (int f = 0; f < kFlushesPerRound; ++f) { - for (int w = 0; w < kWritesPerFlush; ++w) { - ASSERT_OK( - Put(rnd.RandomString(kKeySize), rnd.RandomString(kValueSize))); - } - } - // Force flush to prevent flush from happening between getting the - // properties or after getting the properties and before the new round. - ASSERT_OK(Flush()); - - // In the second round, add iterators. - dbfull()->GetIntProperty("rocksdb.cur-size-active-mem-table", &active_mem); - dbfull()->GetIntProperty("rocksdb.cur-size-all-mem-tables", &unflushed_mem); - dbfull()->GetIntProperty("rocksdb.size-all-mem-tables", &all_mem); - ASSERT_GT(all_mem, active_mem); - ASSERT_GT(all_mem, unflushed_mem); - ASSERT_GT(all_mem, prev_all_mem); - prev_all_mem = all_mem; - } - - // Phase 3. Delete iterators and expect "size-all-mem-tables" shrinks - // whenever we release an iterator. - for (auto* iter : iters) { - ASSERT_OK(iter->status()); - delete iter; - dbfull()->GetIntProperty("rocksdb.size-all-mem-tables", &all_mem); - // Expect the size shrinking - ASSERT_LT(all_mem, prev_all_mem); - prev_all_mem = all_mem; - } - - // Expect all these three counters to be the same. - dbfull()->GetIntProperty("rocksdb.cur-size-active-mem-table", &active_mem); - dbfull()->GetIntProperty("rocksdb.cur-size-all-mem-tables", &unflushed_mem); - dbfull()->GetIntProperty("rocksdb.size-all-mem-tables", &all_mem); - ASSERT_EQ(active_mem, unflushed_mem); - ASSERT_EQ(unflushed_mem, all_mem); - - // Phase 5. Reopen, and expect all these three counters to be the same again. - Reopen(options); - dbfull()->GetIntProperty("rocksdb.cur-size-active-mem-table", &active_mem); - dbfull()->GetIntProperty("rocksdb.cur-size-all-mem-tables", &unflushed_mem); - dbfull()->GetIntProperty("rocksdb.size-all-mem-tables", &all_mem); - ASSERT_EQ(active_mem, unflushed_mem); - ASSERT_EQ(unflushed_mem, all_mem); -} - -TEST_F(DBPropertiesTest, EstimatePendingCompBytes) { - // Set sizes to both background thread pool to be 1 and block them. - env_->SetBackgroundThreads(1, Env::HIGH); - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - Options options = CurrentOptions(); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - options.compaction_style = kCompactionStyleLevel; - options.level0_file_num_compaction_trigger = 2; - options.max_background_compactions = 1; - options.max_background_flushes = 1; - options.max_write_buffer_number = 10; - options.min_write_buffer_number_to_merge = 1; - options.max_write_buffer_size_to_maintain = 0; - options.write_buffer_size = 1000000; - Reopen(options); - - std::string big_value(1000000 * 2, 'x'); - std::string num; - uint64_t int_num; - - ASSERT_OK(dbfull()->Put(writeOpt, "k1", big_value)); - ASSERT_OK(Flush()); - ASSERT_TRUE(dbfull()->GetIntProperty( - "rocksdb.estimate-pending-compaction-bytes", &int_num)); - ASSERT_EQ(int_num, 0U); - - ASSERT_OK(dbfull()->Put(writeOpt, "k2", big_value)); - ASSERT_OK(Flush()); - ASSERT_TRUE(dbfull()->GetIntProperty( - "rocksdb.estimate-pending-compaction-bytes", &int_num)); - ASSERT_GT(int_num, 0U); - - ASSERT_OK(dbfull()->Put(writeOpt, "k3", big_value)); - ASSERT_OK(Flush()); - ASSERT_TRUE(dbfull()->GetIntProperty( - "rocksdb.estimate-pending-compaction-bytes", &int_num)); - ASSERT_GT(int_num, 0U); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(dbfull()->GetIntProperty( - "rocksdb.estimate-pending-compaction-bytes", &int_num)); - ASSERT_EQ(int_num, 0U); -} - -TEST_F(DBPropertiesTest, EstimateCompressionRatio) { - if (!Snappy_Supported()) { - return; - } - const int kNumL0Files = 3; - const int kNumEntriesPerFile = 1000; - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.num_levels = 3; - Reopen(options); - - ASSERT_OK(db_->SetOptions( - {{"compression_per_level", "kNoCompression:kSnappyCompression"}})); - auto opts = db_->GetOptions(); - ASSERT_EQ(opts.compression_per_level.size(), 2); - ASSERT_EQ(opts.compression_per_level[0], kNoCompression); - ASSERT_EQ(opts.compression_per_level[1], kSnappyCompression); - - // compression ratio is -1.0 when no open files at level - ASSERT_EQ(CompressionRatioAtLevel(0), -1.0); - - const std::string kVal(100, 'a'); - for (int i = 0; i < kNumL0Files; ++i) { - for (int j = 0; j < kNumEntriesPerFile; ++j) { - // Put common data ("key") at end to prevent delta encoding from - // compressing the key effectively - std::string key = std::to_string(i) + std::to_string(j) + "key"; - ASSERT_OK(dbfull()->Put(WriteOptions(), key, kVal)); - } - ASSERT_OK(Flush()); - } - - // no compression at L0, so ratio is less than one - ASSERT_LT(CompressionRatioAtLevel(0), 1.0); - ASSERT_GT(CompressionRatioAtLevel(0), 0.0); - ASSERT_EQ(CompressionRatioAtLevel(1), -1.0); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - - ASSERT_EQ(CompressionRatioAtLevel(0), -1.0); - // Data at L1 should be highly compressed thanks to Snappy and redundant data - // in values (ratio is 12.846 as of 4/19/2016). - ASSERT_GT(CompressionRatioAtLevel(1), 10.0); -} - - -class CountingUserTblPropCollector : public TablePropertiesCollector { - public: - const char* Name() const override { return "CountingUserTblPropCollector"; } - - Status Finish(UserCollectedProperties* properties) override { - std::string encoded; - PutVarint32(&encoded, count_); - *properties = UserCollectedProperties{ - {"CountingUserTblPropCollector", message_}, - {"Count", encoded}, - }; - return Status::OK(); - } - - Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/, - EntryType /*type*/, SequenceNumber /*seq*/, - uint64_t /*file_size*/) override { - ++count_; - return Status::OK(); - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - private: - std::string message_ = "Rocksdb"; - uint32_t count_ = 0; -}; - -class CountingUserTblPropCollectorFactory - : public TablePropertiesCollectorFactory { - public: - explicit CountingUserTblPropCollectorFactory( - uint32_t expected_column_family_id) - : expected_column_family_id_(expected_column_family_id), - num_created_(0) {} - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) override { - EXPECT_EQ(expected_column_family_id_, context.column_family_id); - num_created_++; - return new CountingUserTblPropCollector(); - } - const char* Name() const override { - return "CountingUserTblPropCollectorFactory"; - } - void set_expected_column_family_id(uint32_t v) { - expected_column_family_id_ = v; - } - uint32_t expected_column_family_id_; - uint32_t num_created_; -}; - -class CountingDeleteTabPropCollector : public TablePropertiesCollector { - public: - const char* Name() const override { return "CountingDeleteTabPropCollector"; } - - Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/, - EntryType type, SequenceNumber /*seq*/, - uint64_t /*file_size*/) override { - if (type == kEntryDelete) { - num_deletes_++; - } - return Status::OK(); - } - - bool NeedCompact() const override { return num_deletes_ > 10; } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - Status Finish(UserCollectedProperties* properties) override { - *properties = - UserCollectedProperties{{"num_delete", std::to_string(num_deletes_)}}; - return Status::OK(); - } - - private: - uint32_t num_deletes_ = 0; -}; - -class CountingDeleteTabPropCollectorFactory - : public TablePropertiesCollectorFactory { - public: - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - return new CountingDeleteTabPropCollector(); - } - const char* Name() const override { - return "CountingDeleteTabPropCollectorFactory"; - } -}; - -class BlockCountingTablePropertiesCollector : public TablePropertiesCollector { - public: - static const std::string kNumSampledBlocksPropertyName; - - const char* Name() const override { - return "BlockCountingTablePropertiesCollector"; - } - - Status Finish(UserCollectedProperties* properties) override { - (*properties)[kNumSampledBlocksPropertyName] = - std::to_string(num_sampled_blocks_); - return Status::OK(); - } - - Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/, - EntryType /*type*/, SequenceNumber /*seq*/, - uint64_t /*file_size*/) override { - return Status::OK(); - } - - void BlockAdd(uint64_t /* block_uncomp_bytes */, - uint64_t block_compressed_bytes_fast, - uint64_t block_compressed_bytes_slow) override { - if (block_compressed_bytes_fast > 0 || block_compressed_bytes_slow > 0) { - num_sampled_blocks_++; - } - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{ - {kNumSampledBlocksPropertyName, std::to_string(num_sampled_blocks_)}, - }; - } - - private: - uint32_t num_sampled_blocks_ = 0; -}; - -const std::string - BlockCountingTablePropertiesCollector::kNumSampledBlocksPropertyName = - "NumSampledBlocks"; - -class BlockCountingTablePropertiesCollectorFactory - : public TablePropertiesCollectorFactory { - public: - const char* Name() const override { - return "BlockCountingTablePropertiesCollectorFactory"; - } - - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /* context */) override { - return new BlockCountingTablePropertiesCollector(); - } -}; - -TEST_F(DBPropertiesTest, GetUserDefinedTableProperties) { - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = (1 << 30); - options.table_properties_collector_factories.resize(1); - std::shared_ptr collector_factory = - std::make_shared(0); - options.table_properties_collector_factories[0] = collector_factory; - Reopen(options); - // Create 4 tables - for (int table = 0; table < 4; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK( - db_->Put(WriteOptions(), std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - - TablePropertiesCollection props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); - ASSERT_EQ(4U, props.size()); - uint32_t sum = 0; - for (const auto& item : props) { - auto& user_collected = item.second->user_collected_properties; - ASSERT_TRUE(user_collected.find("CountingUserTblPropCollector") != - user_collected.end()); - ASSERT_EQ(user_collected.at("CountingUserTblPropCollector"), "Rocksdb"); - ASSERT_TRUE(user_collected.find("Count") != user_collected.end()); - Slice key(user_collected.at("Count")); - uint32_t count; - ASSERT_TRUE(GetVarint32(&key, &count)); - sum += count; - } - ASSERT_EQ(10u + 11u + 12u + 13u, sum); - - ASSERT_GT(collector_factory->num_created_, 0U); - collector_factory->num_created_ = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_GT(collector_factory->num_created_, 0U); -} - -TEST_F(DBPropertiesTest, UserDefinedTablePropertiesContext) { - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 3; - options.table_properties_collector_factories.resize(1); - std::shared_ptr collector_factory = - std::make_shared(1); - options.table_properties_collector_factories[0] = collector_factory, - CreateAndReopenWithCF({"pikachu"}, options); - // Create 2 files - for (int table = 0; table < 2; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK(Put(1, std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(Flush(1)); - } - ASSERT_GT(collector_factory->num_created_, 0U); - - collector_factory->num_created_ = 0; - // Trigger automatic compactions. - for (int table = 0; table < 3; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK(Put(1, std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_GT(collector_factory->num_created_, 0U); - - collector_factory->num_created_ = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_GT(collector_factory->num_created_, 0U); - - // Come back to write to default column family - collector_factory->num_created_ = 0; - collector_factory->set_expected_column_family_id(0); // default CF - // Create 4 tables in default column family - for (int table = 0; table < 2; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK(Put(std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(Flush()); - } - ASSERT_GT(collector_factory->num_created_, 0U); - - collector_factory->num_created_ = 0; - // Trigger automatic compactions. - for (int table = 0; table < 3; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK(Put(std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_GT(collector_factory->num_created_, 0U); - - collector_factory->num_created_ = 0; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_GT(collector_factory->num_created_, 0U); -} - -TEST_F(DBPropertiesTest, TablePropertiesNeedCompactTest) { - Random rnd(301); - - Options options; - options.create_if_missing = true; - options.write_buffer_size = 4096; - options.max_write_buffer_number = 8; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 4; - options.target_file_size_base = 2048; - options.max_bytes_for_level_base = 10240; - options.max_bytes_for_level_multiplier = 4; - options.soft_pending_compaction_bytes_limit = 1024 * 1024; - options.num_levels = 8; - options.env = env_; - - std::shared_ptr collector_factory = - std::make_shared(); - options.table_properties_collector_factories.resize(1); - options.table_properties_collector_factories[0] = collector_factory; - - DestroyAndReopen(options); - - const int kMaxKey = 1000; - for (int i = 0; i < kMaxKey; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(102))); - ASSERT_OK(Put(Key(kMaxKey + i), rnd.RandomString(102))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - if (NumTableFilesAtLevel(0) == 1) { - // Clear Level 0 so that when later flush a file with deletions, - // we don't trigger an organic compaction. - ASSERT_OK(Put(Key(0), "")); - ASSERT_OK(Put(Key(kMaxKey * 2), "")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - - { - int c = 0; - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - iter->Seek(Key(kMaxKey - 100)); - while (iter->Valid() && iter->key().compare(Key(kMaxKey + 100)) < 0) { - iter->Next(); - ++c; - } - ASSERT_OK(iter->status()); - ASSERT_EQ(c, 200); - } - - ASSERT_OK(Delete(Key(0))); - for (int i = kMaxKey - 100; i < kMaxKey + 100; i++) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Delete(Key(kMaxKey * 2))); - - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - { - SetPerfLevel(kEnableCount); - get_perf_context()->Reset(); - int c = 0; - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - iter->Seek(Key(kMaxKey - 100)); - while (iter->Valid() && iter->key().compare(Key(kMaxKey + 100)) < 0) { - iter->Next(); - } - ASSERT_OK(iter->status()); - ASSERT_EQ(c, 0); - ASSERT_LT(get_perf_context()->internal_delete_skipped_count, 30u); - ASSERT_LT(get_perf_context()->internal_key_skipped_count, 30u); - SetPerfLevel(kDisable); - } -} - -TEST_F(DBPropertiesTest, NeedCompactHintPersistentTest) { - Random rnd(301); - - Options options; - options.create_if_missing = true; - options.max_write_buffer_number = 8; - options.level0_file_num_compaction_trigger = 10; - options.level0_slowdown_writes_trigger = 10; - options.level0_stop_writes_trigger = 10; - options.disable_auto_compactions = true; - options.env = env_; - - std::shared_ptr collector_factory = - std::make_shared(); - options.table_properties_collector_factories.resize(1); - options.table_properties_collector_factories[0] = collector_factory; - - DestroyAndReopen(options); - - const int kMaxKey = 100; - for (int i = 0; i < kMaxKey; i++) { - ASSERT_OK(Put(Key(i), "")); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - for (int i = 1; i < kMaxKey - 1; i++) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(NumTableFilesAtLevel(0), 2); - - // Restart the DB. Although number of files didn't reach - // options.level0_file_num_compaction_trigger, compaction should - // still be triggered because of the need-compaction hint. - options.disable_auto_compactions = false; - Reopen(options); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - { - SetPerfLevel(kEnableCount); - get_perf_context()->Reset(); - int c = 0; - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - for (iter->Seek(Key(0)); iter->Valid(); iter->Next()) { - c++; - } - ASSERT_OK(iter->status()); - ASSERT_EQ(c, 2); - ASSERT_EQ(get_perf_context()->internal_delete_skipped_count, 0); - // We iterate every key twice. Is it a bug? - ASSERT_LE(get_perf_context()->internal_key_skipped_count, 2); - SetPerfLevel(kDisable); - } -} - -// Excluded from RocksDB lite tests due to `GetPropertiesOfAllTables()` usage. -TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) { - // Sampled compression requires at least one of the following four types. - if (!Snappy_Supported() && !Zlib_Supported() && !LZ4_Supported() && - !ZSTD_Supported()) { - return; - } - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.table_properties_collector_factories.emplace_back( - std::make_shared()); - - for (bool sample_for_compression : {false, true}) { - // For simplicity/determinism, sample 100% when enabled, or 0% when disabled - options.sample_for_compression = sample_for_compression ? 1 : 0; - - DestroyAndReopen(options); - - // Setup the following LSM: - // - // L0_0 ["a", "b"] - // L1_0 ["a", "b"] - // - // L0_0 was created by flush. L1_0 was created by compaction. Each file - // contains one data block. - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("a", "val")); - ASSERT_OK(Put("b", "val")); - ASSERT_OK(Flush()); - if (i == 1) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - } - - // A `BlockAdd()` should have been seen for files generated by flush or - // compaction when `sample_for_compression` is enabled. - TablePropertiesCollection file_to_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&file_to_props)); - ASSERT_EQ(2, file_to_props.size()); - for (const auto& file_and_props : file_to_props) { - auto& user_props = file_and_props.second->user_collected_properties; - ASSERT_TRUE(user_props.find(BlockCountingTablePropertiesCollector:: - kNumSampledBlocksPropertyName) != - user_props.end()); - ASSERT_EQ(user_props.at(BlockCountingTablePropertiesCollector:: - kNumSampledBlocksPropertyName), - std::to_string(sample_for_compression ? 1 : 0)); - } - } -} - -class CompressionSamplingDBPropertiesTest - : public DBPropertiesTest, - public ::testing::WithParamInterface { - public: - CompressionSamplingDBPropertiesTest() : fast_(GetParam()) {} - - protected: - const bool fast_; -}; - -INSTANTIATE_TEST_CASE_P(CompressionSamplingDBPropertiesTest, - CompressionSamplingDBPropertiesTest, ::testing::Bool()); - -// Excluded from RocksDB lite tests due to `GetPropertiesOfAllTables()` usage. -TEST_P(CompressionSamplingDBPropertiesTest, - EstimateDataSizeWithCompressionSampling) { - Options options = CurrentOptions(); - if (fast_) { - // One of the following light compression libraries must be present. - if (LZ4_Supported()) { - options.compression = kLZ4Compression; - } else if (Snappy_Supported()) { - options.compression = kSnappyCompression; - } else { - return; - } - } else { - // One of the following heavy compression libraries must be present. - if (ZSTD_Supported()) { - options.compression = kZSTD; - } else if (Zlib_Supported()) { - options.compression = kZlibCompression; - } else { - return; - } - } - options.disable_auto_compactions = true; - // For simplicity/determinism, sample 100%. - options.sample_for_compression = 1; - Reopen(options); - - // Setup the following LSM: - // - // L0_0 ["a", "b"] - // L1_0 ["a", "b"] - // - // L0_0 was created by flush. L1_0 was created by compaction. Each file - // contains one data block. The value consists of compressible data so the - // data block should be stored compressed. - std::string val(1024, 'a'); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("a", val)); - ASSERT_OK(Put("b", val)); - ASSERT_OK(Flush()); - if (i == 1) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - } - - TablePropertiesCollection file_to_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&file_to_props)); - ASSERT_EQ(2, file_to_props.size()); - for (const auto& file_and_props : file_to_props) { - ASSERT_GT(file_and_props.second->data_size, 0); - if (fast_) { - ASSERT_EQ(file_and_props.second->data_size, - file_and_props.second->fast_compression_estimated_data_size); - } else { - ASSERT_EQ(file_and_props.second->data_size, - file_and_props.second->slow_compression_estimated_data_size); - } - } -} - -TEST_F(DBPropertiesTest, EstimateNumKeysUnderflow) { - Options options = CurrentOptions(); - Reopen(options); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Delete("foo")); - ASSERT_OK(Delete("foo")); - uint64_t num_keys = 0; - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &num_keys)); - ASSERT_EQ(0, num_keys); -} - -TEST_F(DBPropertiesTest, EstimateOldestKeyTime) { - uint64_t oldest_key_time = 0; - Options options = CurrentOptions(); - SetTimeElapseOnlySleepOnReopen(&options); - - // "rocksdb.estimate-oldest-key-time" only available to fifo compaction. - for (auto compaction : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleNone}) { - options.compaction_style = compaction; - options.create_if_missing = true; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "bar")); - ASSERT_FALSE(dbfull()->GetIntProperty( - DB::Properties::kEstimateOldestKeyTime, &oldest_key_time)); - } - - int64_t mock_start_time; - ASSERT_OK(env_->GetCurrentTime(&mock_start_time)); - - options.compaction_style = kCompactionStyleFIFO; - options.ttl = 300; - options.max_open_files = -1; - options.compaction_options_fifo.allow_compaction = false; - DestroyAndReopen(options); - - env_->MockSleepForSeconds(100); - ASSERT_OK(Put("k1", "v1")); - ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); - ASSERT_EQ(100, oldest_key_time - mock_start_time); - ASSERT_OK(Flush()); - ASSERT_EQ("1", FilesPerLevel()); - ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); - ASSERT_EQ(100, oldest_key_time - mock_start_time); - - env_->MockSleepForSeconds(100); // -> 200 - ASSERT_OK(Put("k2", "v2")); - ASSERT_OK(Flush()); - ASSERT_EQ("2", FilesPerLevel()); - ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); - ASSERT_EQ(100, oldest_key_time - mock_start_time); - - env_->MockSleepForSeconds(100); // -> 300 - ASSERT_OK(Put("k3", "v3")); - ASSERT_OK(Flush()); - ASSERT_EQ("3", FilesPerLevel()); - ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); - ASSERT_EQ(100, oldest_key_time - mock_start_time); - - env_->MockSleepForSeconds(150); // -> 450 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("2", FilesPerLevel()); - ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); - ASSERT_EQ(200, oldest_key_time - mock_start_time); - - env_->MockSleepForSeconds(100); // -> 550 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("1", FilesPerLevel()); - ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); - ASSERT_EQ(300, oldest_key_time - mock_start_time); - - env_->MockSleepForSeconds(100); // -> 650 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("", FilesPerLevel()); - ASSERT_FALSE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime, - &oldest_key_time)); -} - -TEST_F(DBPropertiesTest, SstFilesSize) { - struct TestListener : public EventListener { - void OnCompactionCompleted(DB* db, - const CompactionJobInfo& /*info*/) override { - assert(callback_triggered == false); - assert(size_before_compaction > 0); - callback_triggered = true; - uint64_t total_sst_size = 0; - uint64_t live_sst_size = 0; - bool ok = db->GetIntProperty(DB::Properties::kTotalSstFilesSize, - &total_sst_size); - ASSERT_TRUE(ok); - // total_sst_size include files before and after compaction. - ASSERT_GT(total_sst_size, size_before_compaction); - ok = - db->GetIntProperty(DB::Properties::kLiveSstFilesSize, &live_sst_size); - ASSERT_TRUE(ok); - // live_sst_size only include files after compaction. - ASSERT_GT(live_sst_size, 0); - ASSERT_LT(live_sst_size, size_before_compaction); - } - - uint64_t size_before_compaction = 0; - bool callback_triggered = false; - }; - std::shared_ptr listener = std::make_shared(); - - Options options; - options.env = CurrentOptions().env; - options.disable_auto_compactions = true; - options.listeners.push_back(listener); - Reopen(options); - - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put("key" + std::to_string(i), std::string(1000, 'v'))); - } - ASSERT_OK(Flush()); - for (int i = 0; i < 5; i++) { - ASSERT_OK(Delete("key" + std::to_string(i))); - } - ASSERT_OK(Flush()); - uint64_t sst_size; - bool ok = db_->GetIntProperty(DB::Properties::kTotalSstFilesSize, &sst_size); - ASSERT_TRUE(ok); - ASSERT_GT(sst_size, 0); - listener->size_before_compaction = sst_size; - // Compact to clean all keys and trigger listener. - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_TRUE(listener->callback_triggered); -} - -TEST_F(DBPropertiesTest, MinObsoleteSstNumberToKeep) { - class TestListener : public EventListener { - public: - void OnTableFileCreated(const TableFileCreationInfo& info) override { - if (info.reason == TableFileCreationReason::kCompaction) { - // Verify the property indicates that SSTs created by a running - // compaction cannot be deleted. - uint64_t created_file_num; - FileType created_file_type; - std::string filename = - info.file_path.substr(info.file_path.rfind('/') + 1); - ASSERT_TRUE( - ParseFileName(filename, &created_file_num, &created_file_type)); - ASSERT_EQ(kTableFile, created_file_type); - - uint64_t keep_sst_lower_bound; - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kMinObsoleteSstNumberToKeep, - &keep_sst_lower_bound)); - - ASSERT_LE(keep_sst_lower_bound, created_file_num); - validated_ = true; - } - } - - void SetDB(DB* db) { db_ = db; } - - int GetNumCompactions() { return num_compactions_; } - - // True if we've verified the property for at least one output file - bool Validated() { return validated_; } - - private: - int num_compactions_ = 0; - bool validated_ = false; - DB* db_ = nullptr; - }; - - const int kNumL0Files = 4; - - std::shared_ptr listener = std::make_shared(); - - Options options = CurrentOptions(); - options.listeners.push_back(listener); - options.level0_file_num_compaction_trigger = kNumL0Files; - DestroyAndReopen(options); - listener->SetDB(db_); - - for (int i = 0; i < kNumL0Files; ++i) { - // Make sure they overlap in keyspace to prevent trivial move - ASSERT_OK(Put("key1", "val")); - ASSERT_OK(Put("key2", "val")); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_TRUE(listener->Validated()); -} - -TEST_F(DBPropertiesTest, BlobCacheProperties) { - Options options; - uint64_t value; - - options.env = CurrentOptions().env; - - // Test with empty blob cache. - constexpr size_t kCapacity = 100; - LRUCacheOptions co; - co.capacity = kCapacity; - co.num_shard_bits = 0; - co.metadata_charge_policy = kDontChargeCacheMetadata; - auto blob_cache = NewLRUCache(co); - options.blob_cache = blob_cache; - - Reopen(options); - - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheUsage, &value)); - ASSERT_EQ(0, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlobCachePinnedUsage, &value)); - ASSERT_EQ(0, value); - - // Insert unpinned blob to the cache and check size. - constexpr size_t kSize1 = 70; - ASSERT_OK(blob_cache->Insert("blob1", nullptr /*value*/, - &kNoopCacheItemHelper, kSize1)); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheUsage, &value)); - ASSERT_EQ(kSize1, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlobCachePinnedUsage, &value)); - ASSERT_EQ(0, value); - - // Insert pinned blob to the cache and check size. - constexpr size_t kSize2 = 60; - Cache::Handle* blob2 = nullptr; - ASSERT_OK(blob_cache->Insert("blob2", nullptr /*value*/, - &kNoopCacheItemHelper, kSize2, &blob2)); - ASSERT_NE(nullptr, blob2); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheUsage, &value)); - // blob1 is evicted. - ASSERT_EQ(kSize2, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlobCachePinnedUsage, &value)); - ASSERT_EQ(kSize2, value); - - // Insert another pinned blob to make the cache over-sized. - constexpr size_t kSize3 = 80; - Cache::Handle* blob3 = nullptr; - ASSERT_OK(blob_cache->Insert("blob3", nullptr /*value*/, - &kNoopCacheItemHelper, kSize3, &blob3)); - ASSERT_NE(nullptr, blob3); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheUsage, &value)); - ASSERT_EQ(kSize2 + kSize3, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlobCachePinnedUsage, &value)); - ASSERT_EQ(kSize2 + kSize3, value); - - // Check size after release. - blob_cache->Release(blob2); - blob_cache->Release(blob3); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlobCacheUsage, &value)); - // blob2 will be evicted, while blob3 remain in cache after release. - ASSERT_EQ(kSize3, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlobCachePinnedUsage, &value)); - ASSERT_EQ(0, value); -} - -TEST_F(DBPropertiesTest, BlockCacheProperties) { - Options options; - uint64_t value; - - options.env = CurrentOptions().env; - - // Block cache properties are not available for tables other than - // block-based table. - options.table_factory.reset(NewPlainTableFactory()); - Reopen(options); - ASSERT_FALSE( - db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_FALSE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - ASSERT_FALSE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - - options.table_factory.reset(NewCuckooTableFactory()); - Reopen(options); - ASSERT_FALSE( - db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_FALSE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - ASSERT_FALSE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - - // Block cache properties are not available if block cache is not used. - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_FALSE( - db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_FALSE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - ASSERT_FALSE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - - // Test with empty block cache. - constexpr size_t kCapacity = 100; - LRUCacheOptions co; - co.capacity = kCapacity; - co.num_shard_bits = 0; - co.metadata_charge_policy = kDontChargeCacheMetadata; - auto block_cache = NewLRUCache(co); - table_options.block_cache = block_cache; - table_options.no_block_cache = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - ASSERT_EQ(0, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - ASSERT_EQ(0, value); - - // Insert unpinned item to the cache and check size. - constexpr size_t kSize1 = 50; - ASSERT_OK(block_cache->Insert("item1", nullptr /*value*/, - &kNoopCacheItemHelper, kSize1)); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - ASSERT_EQ(kSize1, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - ASSERT_EQ(0, value); - - // Insert pinned item to the cache and check size. - constexpr size_t kSize2 = 30; - Cache::Handle* item2 = nullptr; - ASSERT_OK(block_cache->Insert("item2", nullptr /*value*/, - &kNoopCacheItemHelper, kSize2, &item2)); - ASSERT_NE(nullptr, item2); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - ASSERT_EQ(kSize1 + kSize2, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - ASSERT_EQ(kSize2, value); - - // Insert another pinned item to make the cache over-sized. - constexpr size_t kSize3 = 80; - Cache::Handle* item3 = nullptr; - ASSERT_OK(block_cache->Insert("item3", nullptr /*value*/, - &kNoopCacheItemHelper, kSize3, &item3)); - ASSERT_NE(nullptr, item2); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - // Item 1 is evicted. - ASSERT_EQ(kSize2 + kSize3, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - ASSERT_EQ(kSize2 + kSize3, value); - - // Check size after release. - block_cache->Release(item2); - block_cache->Release(item3); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); - ASSERT_EQ(kCapacity, value); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); - // item2 will be evicted, while item3 remain in cache after release. - ASSERT_EQ(kSize3, value); - ASSERT_TRUE( - db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); - ASSERT_EQ(0, value); -} - -TEST_F(DBPropertiesTest, GetMapPropertyDbStats) { - auto mock_clock = std::make_shared(env_->GetSystemClock()); - CompositeEnvWrapper env(env_, mock_clock); - - Options opts = CurrentOptions(); - opts.env = &env; - Reopen(opts); - - { - std::map db_stats; - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats)); - AssertDbStats(db_stats, 0.0 /* expected_uptime */, - 0 /* expected_user_bytes_written */, - 0 /* expected_wal_bytes_written */, - 0 /* expected_user_writes_by_self */, - 0 /* expected_user_writes_with_wal */); - } - - { - mock_clock->SleepForMicroseconds(1500000); - - std::map db_stats; - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats)); - AssertDbStats(db_stats, 1.5 /* expected_uptime */, - 0 /* expected_user_bytes_written */, - 0 /* expected_wal_bytes_written */, - 0 /* expected_user_writes_by_self */, - 0 /* expected_user_writes_with_wal */); - } - - int expected_user_bytes_written = 0; - { - // Write with WAL disabled. - WriteOptions write_opts; - write_opts.disableWAL = true; - - WriteBatch batch; - ASSERT_OK(batch.Put("key", "val")); - expected_user_bytes_written += static_cast(batch.GetDataSize()); - - ASSERT_OK(db_->Write(write_opts, &batch)); - - std::map db_stats; - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats)); - AssertDbStats(db_stats, 1.5 /* expected_uptime */, - expected_user_bytes_written, - 0 /* expected_wal_bytes_written */, - 1 /* expected_user_writes_by_self */, - 0 /* expected_user_writes_with_wal */); - } - - int expected_wal_bytes_written = 0; - { - // Write with WAL enabled. - WriteBatch batch; - ASSERT_OK(batch.Delete("key")); - expected_user_bytes_written += static_cast(batch.GetDataSize()); - expected_wal_bytes_written += static_cast(batch.GetDataSize()); - - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::map db_stats; - ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats)); - AssertDbStats(db_stats, 1.5 /* expected_uptime */, - expected_user_bytes_written, expected_wal_bytes_written, - 2 /* expected_user_writes_by_self */, - 1 /* expected_user_writes_with_wal */); - } - - Close(); -} - -TEST_F(DBPropertiesTest, GetMapPropertyBlockCacheEntryStats) { - // Currently only verifies the expected properties are present - std::map values; - ASSERT_TRUE( - db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, &values)); - - ASSERT_TRUE(values.find(BlockCacheEntryStatsMapKeys::CacheId()) != - values.end()); - ASSERT_TRUE(values.find(BlockCacheEntryStatsMapKeys::CacheCapacityBytes()) != - values.end()); - ASSERT_TRUE( - values.find( - BlockCacheEntryStatsMapKeys::LastCollectionDurationSeconds()) != - values.end()); - ASSERT_TRUE( - values.find(BlockCacheEntryStatsMapKeys::LastCollectionAgeSeconds()) != - values.end()); - for (size_t i = 0; i < kNumCacheEntryRoles; ++i) { - CacheEntryRole role = static_cast(i); - ASSERT_TRUE(values.find(BlockCacheEntryStatsMapKeys::EntryCount(role)) != - values.end()); - ASSERT_TRUE(values.find(BlockCacheEntryStatsMapKeys::UsedBytes(role)) != - values.end()); - ASSERT_TRUE(values.find(BlockCacheEntryStatsMapKeys::UsedPercent(role)) != - values.end()); - } - - // There should be no extra values in the map. - ASSERT_EQ(3 * kNumCacheEntryRoles + 4, values.size()); -} - -TEST_F(DBPropertiesTest, WriteStallStatsSanityCheck) { - for (uint32_t i = 0; i < static_cast(WriteStallCause::kNone); ++i) { - WriteStallCause cause = static_cast(i); - const std::string& str = WriteStallCauseToHyphenString(cause); - ASSERT_TRUE(!str.empty()) - << "Please ensure mapping from `WriteStallCause` to " - "`WriteStallCauseToHyphenString` is complete"; - if (cause == WriteStallCause::kCFScopeWriteStallCauseEnumMax || - cause == WriteStallCause::kDBScopeWriteStallCauseEnumMax) { - ASSERT_EQ(str, InvalidWriteStallHyphenString()) - << "Please ensure order in `WriteStallCauseToHyphenString` is " - "consistent with `WriteStallCause`"; - } - } - - for (uint32_t i = 0; i < static_cast(WriteStallCondition::kNormal); - ++i) { - WriteStallCondition condition = static_cast(i); - const std::string& str = WriteStallConditionToHyphenString(condition); - ASSERT_TRUE(!str.empty()) - << "Please ensure mapping from `WriteStallCondition` to " - "`WriteStallConditionToHyphenString` is complete"; - } - - for (uint32_t i = 0; i < static_cast(WriteStallCause::kNone); ++i) { - for (uint32_t j = 0; - j < static_cast(WriteStallCondition::kNormal); ++j) { - WriteStallCause cause = static_cast(i); - WriteStallCondition condition = static_cast(j); - - if (isCFScopeWriteStallCause(cause)) { - ASSERT_TRUE(InternalCFStat(cause, condition) != - InternalStats::INTERNAL_CF_STATS_ENUM_MAX) - << "Please ensure the combination of WriteStallCause(" + - std::to_string(static_cast(cause)) + - ") + WriteStallCondition(" + - std::to_string(static_cast(condition)) + - ") is correctly mapped to a valid `InternalStats` or bypass " - "its check in this test"; - } else if (isDBScopeWriteStallCause(cause)) { - InternalStats::InternalDBStatsType internal_db_stat = - InternalDBStat(cause, condition); - if (internal_db_stat == InternalStats::kIntStatsNumMax) { - ASSERT_TRUE(cause == WriteStallCause::kWriteBufferManagerLimit && - condition == WriteStallCondition::kDelayed) - << "Please ensure the combination of WriteStallCause(" + - std::to_string(static_cast(cause)) + - ") + WriteStallCondition(" + - std::to_string(static_cast(condition)) + - ") is correctly mapped to a valid `InternalStats` or " - "bypass its check in this test"; - } - } else if (cause != WriteStallCause::kCFScopeWriteStallCauseEnumMax && - cause != WriteStallCause::kDBScopeWriteStallCauseEnumMax) { - ASSERT_TRUE(false) << "Please ensure the WriteStallCause(" + - std::to_string(static_cast(cause)) + - ") is either CF-scope or DB-scope write " - "stall cause in enum `WriteStallCause`"; - } - } - } -} -TEST_F(DBPropertiesTest, GetMapPropertyWriteStallStats) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"heavy_write_cf"}, options); - - for (auto test_cause : {WriteStallCause::kWriteBufferManagerLimit, - WriteStallCause::kMemtableLimit}) { - if (test_cause == WriteStallCause::kWriteBufferManagerLimit) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } else if (test_cause == WriteStallCause::kMemtableLimit) { - options.max_write_buffer_number = 2; - options.disable_auto_compactions = true; - } - ReopenWithColumnFamilies({"default", "heavy_write_cf"}, options); - - // Assert initial write stall stats are all 0 - std::map db_values; - ASSERT_TRUE(dbfull()->GetMapProperty(DB::Properties::kDBWriteStallStats, - &db_values)); - ASSERT_EQ(std::stoi(db_values[WriteStallStatsMapKeys::CauseConditionCount( - WriteStallCause::kWriteBufferManagerLimit, - WriteStallCondition::kStopped)]), - 0); - - for (int cf = 0; cf <= 1; ++cf) { - std::map cf_values; - ASSERT_TRUE(dbfull()->GetMapProperty( - handles_[cf], DB::Properties::kCFWriteStallStats, &cf_values)); - ASSERT_EQ(std::stoi(cf_values[WriteStallStatsMapKeys::TotalStops()]), 0); - ASSERT_EQ(std::stoi(cf_values[WriteStallStatsMapKeys::TotalDelays()]), 0); - } - - // Pause flush thread to help coerce write stall - std::unique_ptr sleeping_task( - new test::SleepingBackgroundTask()); - env_->SetBackgroundThreads(1, Env::HIGH); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - sleeping_task.get(), Env::Priority::HIGH); - sleeping_task->WaitUntilSleeping(); - - // Coerce write stall - if (test_cause == WriteStallCause::kWriteBufferManagerLimit) { - ASSERT_OK(dbfull()->Put( - WriteOptions(), handles_[1], Key(1), - DummyString(options.write_buffer_manager->buffer_size()))); - - WriteOptions wo; - wo.no_slowdown = true; - Status s = dbfull()->Put( - wo, handles_[1], Key(2), - DummyString(options.write_buffer_manager->buffer_size())); - ASSERT_TRUE(s.IsIncomplete()); - ASSERT_TRUE(s.ToString().find("Write stall") != std::string::npos); - } else if (test_cause == WriteStallCause::kMemtableLimit) { - FlushOptions fo; - fo.allow_write_stall = true; - fo.wait = false; - - ASSERT_OK( - dbfull()->Put(WriteOptions(), handles_[1], Key(1), DummyString(1))); - ASSERT_OK(dbfull()->Flush(fo, handles_[1])); - - ASSERT_OK( - dbfull()->Put(WriteOptions(), handles_[1], Key(2), DummyString(1))); - ASSERT_OK(dbfull()->Flush(fo, handles_[1])); - } - - if (test_cause == WriteStallCause::kWriteBufferManagerLimit) { - db_values.clear(); - EXPECT_TRUE(dbfull()->GetMapProperty(DB::Properties::kDBWriteStallStats, - &db_values)); - EXPECT_EQ(std::stoi(db_values[WriteStallStatsMapKeys::CauseConditionCount( - WriteStallCause::kWriteBufferManagerLimit, - WriteStallCondition::kStopped)]), - 1); - // `WriteStallCause::kWriteBufferManagerLimit` should not result in any - // CF-scope write stall stats changes - for (int cf = 0; cf <= 1; ++cf) { - std::map cf_values; - EXPECT_TRUE(dbfull()->GetMapProperty( - handles_[cf], DB::Properties::kCFWriteStallStats, &cf_values)); - EXPECT_EQ(std::stoi(cf_values[WriteStallStatsMapKeys::TotalStops()]), - 0); - EXPECT_EQ(std::stoi(cf_values[WriteStallStatsMapKeys::TotalDelays()]), - 0); - } - } else if (test_cause == WriteStallCause::kMemtableLimit) { - for (int cf = 0; cf <= 1; ++cf) { - std::map cf_values; - EXPECT_TRUE(dbfull()->GetMapProperty( - handles_[cf], DB::Properties::kCFWriteStallStats, &cf_values)); - EXPECT_EQ(std::stoi(cf_values[WriteStallStatsMapKeys::TotalStops()]), - cf == 1 ? 1 : 0); - EXPECT_EQ( - std::stoi(cf_values[WriteStallStatsMapKeys::CauseConditionCount( - WriteStallCause::kMemtableLimit, - WriteStallCondition::kStopped)]), - cf == 1 ? 1 : 0); - EXPECT_EQ(std::stoi(cf_values[WriteStallStatsMapKeys::TotalDelays()]), - 0); - EXPECT_EQ( - std::stoi(cf_values[WriteStallStatsMapKeys::CauseConditionCount( - WriteStallCause::kMemtableLimit, - WriteStallCondition::kDelayed)]), - 0); - } - } - - sleeping_task->WakeUp(); - sleeping_task->WaitUntilDone(); - } -} - -namespace { -std::string PopMetaIndexKey(InternalIterator* meta_iter) { - Status s = meta_iter->status(); - if (!s.ok()) { - return s.ToString(); - } else if (meta_iter->Valid()) { - std::string rv = meta_iter->key().ToString(); - meta_iter->Next(); - return rv; - } else { - return "NOT_FOUND"; - } -} - -} // anonymous namespace - -TEST_F(DBPropertiesTest, TableMetaIndexKeys) { - // This is to detect unexpected churn in metaindex block keys. This is more - // of a "table test" but table_test.cc doesn't depend on db_test_util.h and - // we need ChangeOptions() for broad coverage. - constexpr int kKeyCount = 100; - do { - Options options; - options = CurrentOptions(options); - DestroyAndReopen(options); - - // Create an SST file - for (int key = 0; key < kKeyCount; key++) { - ASSERT_OK(Put(Key(key), "val")); - } - ASSERT_OK(Flush()); - - // Find its file number - std::vector files; - db_->GetLiveFilesMetaData(&files); - // 1 SST file - ASSERT_EQ(1, files.size()); - - // Open it for inspection - std::string sst_file = - files[0].directory + "/" + files[0].relative_filename; - std::unique_ptr f; - ASSERT_OK(env_->GetFileSystem()->NewRandomAccessFile( - sst_file, FileOptions(), &f, nullptr)); - std::unique_ptr r; - r.reset(new RandomAccessFileReader(std::move(f), sst_file)); - uint64_t file_size = 0; - ASSERT_OK(env_->GetFileSize(sst_file, &file_size)); - - // Read metaindex - BlockContents bc; - ASSERT_OK(ReadMetaIndexBlockInFile(r.get(), file_size, 0U, - ImmutableOptions(options), &bc)); - Block metaindex_block(std::move(bc)); - std::unique_ptr meta_iter; - meta_iter.reset(metaindex_block.NewMetaIterator()); - meta_iter->SeekToFirst(); - - if (strcmp(options.table_factory->Name(), - TableFactory::kBlockBasedTableName()) == 0) { - auto bbto = options.table_factory->GetOptions(); - if (bbto->filter_policy) { - if (bbto->partition_filters) { - // The key names are intentionally hard-coded here to detect - // accidental regression on compatibility. - EXPECT_EQ("partitionedfilter.rocksdb.BuiltinBloomFilter", - PopMetaIndexKey(meta_iter.get())); - } else { - EXPECT_EQ("fullfilter.rocksdb.BuiltinBloomFilter", - PopMetaIndexKey(meta_iter.get())); - } - } - if (bbto->index_type == BlockBasedTableOptions::kHashSearch) { - EXPECT_EQ("rocksdb.hashindex.metadata", - PopMetaIndexKey(meta_iter.get())); - EXPECT_EQ("rocksdb.hashindex.prefixes", - PopMetaIndexKey(meta_iter.get())); - } - } - EXPECT_EQ("rocksdb.properties", PopMetaIndexKey(meta_iter.get())); - EXPECT_EQ("NOT_FOUND", PopMetaIndexKey(meta_iter.get())); - } while (ChangeOptions()); -} - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_range_del_test.cc b/db/db_range_del_test.cc deleted file mode 100644 index 08bd3af04..000000000 --- a/db/db_range_del_test.cc +++ /dev/null @@ -1,3414 +0,0 @@ -// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/db_test_util.h" -#include "db/version_set.h" -#include "port/stack_trace.h" -#include "rocksdb/utilities/write_batch_with_index.h" -#include "test_util/testutil.h" -#include "util/random.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -// TODO(cbi): parameterize the test to cover user-defined timestamp cases -class DBRangeDelTest : public DBTestBase { - public: - DBRangeDelTest() : DBTestBase("db_range_del_test", /*env_do_fsync=*/false) {} - - std::string GetNumericStr(int key) { - uint64_t uint64_key = static_cast(key); - std::string str; - str.resize(8); - memcpy(&str[0], static_cast(&uint64_key), 8); - return str; - } -}; - -TEST_F(DBRangeDelTest, NonBlockBasedTableNotSupported) { - // TODO: figure out why MmapReads trips the iterator pinning assertion in - // RangeDelAggregator. Ideally it would be supported; otherwise it should at - // least be explicitly unsupported. - for (auto config : {kPlainTableAllBytesPrefix, /* kWalDirAndMmapReads */}) { - option_config_ = config; - DestroyAndReopen(CurrentOptions()); - ASSERT_TRUE(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "dr1", "dr1") - .IsNotSupported()); - } -} - -TEST_F(DBRangeDelTest, WriteBatchWithIndexNotSupported) { - WriteBatchWithIndex indexedBatch{}; - ASSERT_TRUE(indexedBatch.DeleteRange(db_->DefaultColumnFamily(), "dr1", "dr1") - .IsNotSupported()); - ASSERT_TRUE(indexedBatch.DeleteRange("dr1", "dr1").IsNotSupported()); -} - -TEST_F(DBRangeDelTest, EndSameAsStartCoversNothing) { - ASSERT_OK(db_->Put(WriteOptions(), "b", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "b", "b")); - ASSERT_EQ("val", Get("b")); -} - -TEST_F(DBRangeDelTest, EndComesBeforeStartInvalidArgument) { - ASSERT_OK(db_->Put(WriteOptions(), "b", "val")); - ASSERT_TRUE( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "b", "a") - .IsInvalidArgument()); - ASSERT_EQ("val", Get("b")); -} - -TEST_F(DBRangeDelTest, FlushOutputHasOnlyRangeTombstones) { - do { - DestroyAndReopen(CurrentOptions()); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "dr1", "dr2")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - } while (ChangeOptions(kRangeDelSkipConfigs)); -} - -TEST_F(DBRangeDelTest, DictionaryCompressionWithOnlyRangeTombstones) { - Options opts = CurrentOptions(); - opts.compression_opts.max_dict_bytes = 16384; - Reopen(opts); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", - "dr2")); - ASSERT_OK(db_->Flush(FlushOptions())); -} - -TEST_F(DBRangeDelTest, CompactionOutputHasOnlyRangeTombstone) { - do { - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.statistics = CreateDBStatistics(); - DestroyAndReopen(opts); - - // snapshot protects range tombstone from dropping due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Flush(FlushOptions())); - - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(0, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE)); - db_->ReleaseSnapshot(snapshot); - // Skip cuckoo memtables, which do not support snapshots. Skip non-leveled - // compactions as the above assertions about the number of files in a level - // do not hold true. - } while (ChangeOptions(kRangeDelSkipConfigs | kSkipUniversalCompaction | - kSkipFIFOCompaction)); -} - -TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) { - // regression test for exactly filled compaction output files. Previously - // another file would be generated containing all range deletions, which - // could invalidate the non-overlapping file boundary invariant. - const int kNumPerFile = 4, kNumFiles = 2, kFileBytes = 9 << 10; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.level0_file_num_compaction_trigger = kNumFiles; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - options.num_levels = 2; - options.target_file_size_base = kFileBytes; - BlockBasedTableOptions table_options; - table_options.block_size_deviation = 50; // each block holds two keys - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - - // snapshot protects range tombstone from dropping due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(1))); - - Random rnd(301); - for (int i = 0; i < kNumFiles; ++i) { - std::vector values; - // Write 12K (4 values, each 3K) - for (int j = 0; j < kNumPerFile; j++) { - values.push_back(rnd.RandomString(3 << 10)); - ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j])); - if (j == 0 && i > 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - } - } - // put extra key to trigger final flush - ASSERT_OK(Put("", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, MaxCompactionBytesCutsOutputFiles) { - // Ensures range deletion spanning multiple compaction output files that are - // cut by max_compaction_bytes will have non-overlapping key-ranges. - // https://github.com/facebook/rocksdb/issues/1778 - const int kNumFiles = 2, kNumPerFile = 1 << 8, kBytesPerVal = 1 << 12; - Options opts = CurrentOptions(); - opts.comparator = test::Uint64Comparator(); - opts.disable_auto_compactions = true; - opts.level0_file_num_compaction_trigger = kNumFiles; - opts.max_compaction_bytes = kNumPerFile * kBytesPerVal; - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - // Want max_compaction_bytes to trigger the end of compaction output file, not - // target_file_size_base, so make the latter much bigger - // opts.target_file_size_base = 100 * opts.max_compaction_bytes; - opts.target_file_size_base = 1; - DestroyAndReopen(opts); - - // snapshot protects range tombstone from dropping due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - - Random rnd(301); - - ASSERT_OK(Put(GetNumericStr(0), rnd.RandomString(kBytesPerVal))); - ASSERT_OK( - Put(GetNumericStr(kNumPerFile - 1), rnd.RandomString(kBytesPerVal))); - ASSERT_OK(Flush()); - ASSERT_OK(Put(GetNumericStr(kNumPerFile), rnd.RandomString(kBytesPerVal))); - ASSERT_OK( - Put(GetNumericStr(kNumPerFile * 2 - 1), rnd.RandomString(kBytesPerVal))); - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(NumTableFilesAtLevel(2), 2); - - ASSERT_OK( - db_->SetOptions(db_->DefaultColumnFamily(), - {{"target_file_size_base", - std::to_string(100 * opts.max_compaction_bytes)}})); - - // It spans the whole key-range, thus will be included in all output files - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - GetNumericStr(0), - GetNumericStr(kNumFiles * kNumPerFile - 1))); - - for (int i = 0; i < kNumFiles; ++i) { - std::vector values; - // Write 1MB (256 values, each 4K) - for (int j = 0; j < kNumPerFile; j++) { - values.push_back(rnd.RandomString(kBytesPerVal)); - ASSERT_OK(Put(GetNumericStr(kNumPerFile * i + j), values[j])); - } - // extra entry to trigger SpecialSkipListFactory's flush - ASSERT_OK(Put(GetNumericStr(kNumPerFile), "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(i + 1, NumTableFilesAtLevel(0)); - } - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, - /*column_family=*/nullptr, - /*disallow_trivial_move=*/true)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GE(NumTableFilesAtLevel(1), 2); - std::vector> files; - dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files); - - for (size_t i = 0; i + 1 < files[1].size(); ++i) { - ASSERT_TRUE(InternalKeyComparator(opts.comparator) - .Compare(files[1][i].largest, files[1][i + 1].smallest) < - 0); - } - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, SentinelsOmittedFromOutputFile) { - // Regression test for bug where sentinel range deletions (i.e., ones with - // sequence number of zero) were included in output files. - // snapshot protects range tombstone from dropping due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - - // gaps between ranges creates sentinels in our internal representation - std::vector> range_dels = { - {"a", "b"}, {"c", "d"}, {"e", "f"}}; - for (const auto& range_del : range_dels) { - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - range_del.first, range_del.second)); - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - std::vector> files; - dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files); - ASSERT_GT(files[0][0].fd.smallest_seqno, 0); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, FlushRangeDelsSameStartKey) { - ASSERT_OK(db_->Put(WriteOptions(), "b1", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "c")); - ASSERT_OK(db_->Put(WriteOptions(), "b2", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b")); - // first iteration verifies query correctness in memtable, second verifies - // query correctness for a single SST file - for (int i = 0; i < 2; ++i) { - if (i > 0) { - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - } - std::string value; - ASSERT_TRUE(db_->Get(ReadOptions(), "b1", &value).IsNotFound()); - ASSERT_OK(db_->Get(ReadOptions(), "b2", &value)); - } -} - -TEST_F(DBRangeDelTest, CompactRangeDelsSameStartKey) { - ASSERT_OK(db_->Put(WriteOptions(), "unused", - "val")); // prevents empty after compaction - ASSERT_OK(db_->Put(WriteOptions(), "b1", "val")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "c")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(3, NumTableFilesAtLevel(0)); - - for (int i = 0; i < 2; ++i) { - if (i > 0) { - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - } - std::string value; - ASSERT_TRUE(db_->Get(ReadOptions(), "b1", &value).IsNotFound()); - } -} - -TEST_F(DBRangeDelTest, FlushRemovesCoveredKeys) { - const int kNum = 300, kRangeBegin = 50, kRangeEnd = 250; - Options opts = CurrentOptions(); - opts.comparator = test::Uint64Comparator(); - DestroyAndReopen(opts); - - // Write a third before snapshot, a third between snapshot and tombstone, and - // a third after the tombstone. Keys older than snapshot or newer than the - // tombstone should be preserved. - const Snapshot* snapshot = nullptr; - for (int i = 0; i < kNum; ++i) { - if (i == kNum / 3) { - snapshot = db_->GetSnapshot(); - } else if (i == 2 * kNum / 3) { - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - GetNumericStr(kRangeBegin), - GetNumericStr(kRangeEnd))); - } - ASSERT_OK(db_->Put(WriteOptions(), GetNumericStr(i), "val")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - - for (int i = 0; i < kNum; ++i) { - ReadOptions read_opts; - read_opts.ignore_range_deletions = true; - std::string value; - if (i < kRangeBegin || i > kRangeEnd || i < kNum / 3 || i >= 2 * kNum / 3) { - ASSERT_OK(db_->Get(read_opts, GetNumericStr(i), &value)); - } else { - ASSERT_TRUE(db_->Get(read_opts, GetNumericStr(i), &value).IsNotFound()); - } - } - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, CompactionRemovesCoveredKeys) { - const int kNumPerFile = 100, kNumFiles = 4; - Options opts = CurrentOptions(); - opts.comparator = test::Uint64Comparator(); - opts.disable_auto_compactions = true; - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - opts.num_levels = 2; - opts.statistics = CreateDBStatistics(); - DestroyAndReopen(opts); - - for (int i = 0; i < kNumFiles; ++i) { - if (i > 0) { - // range tombstone covers first half of the previous file - ASSERT_OK(db_->DeleteRange( - WriteOptions(), db_->DefaultColumnFamily(), - GetNumericStr((i - 1) * kNumPerFile), - GetNumericStr((i - 1) * kNumPerFile + kNumPerFile / 2))); - } - // Make sure a given key appears in each file so compaction won't be able to - // use trivial move, which would happen if the ranges were non-overlapping. - // Also, we need an extra element since flush is only triggered when the - // number of keys is one greater than SpecialSkipListFactory's limit. - // We choose a key outside the key-range used by the test to avoid conflict. - ASSERT_OK(db_->Put(WriteOptions(), GetNumericStr(kNumPerFile * kNumFiles), - "val")); - - for (int j = 0; j < kNumPerFile; ++j) { - ASSERT_OK( - db_->Put(WriteOptions(), GetNumericStr(i * kNumPerFile + j), "val")); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(i + 1, NumTableFilesAtLevel(0)); - } - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(1), 0); - ASSERT_EQ((kNumFiles - 1) * kNumPerFile / 2, - TestGetTickerCount(opts, COMPACTION_KEY_DROP_RANGE_DEL)); - - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kNumPerFile; ++j) { - ReadOptions read_opts; - read_opts.ignore_range_deletions = true; - std::string value; - if (i == kNumFiles - 1 || j >= kNumPerFile / 2) { - ASSERT_OK( - db_->Get(read_opts, GetNumericStr(i * kNumPerFile + j), &value)); - } else { - ASSERT_TRUE( - db_->Get(read_opts, GetNumericStr(i * kNumPerFile + j), &value) - .IsNotFound()); - } - } - } -} - -TEST_F(DBRangeDelTest, ValidLevelSubcompactionBoundaries) { - const int kNumPerFile = 100, kNumFiles = 4, kFileBytes = 100 << 10; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.level0_file_num_compaction_trigger = kNumFiles; - options.max_bytes_for_level_base = 2 * kFileBytes; - options.max_subcompactions = 4; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - options.num_levels = 3; - options.target_file_size_base = kFileBytes; - options.target_file_size_multiplier = 1; - options.max_compaction_bytes = 1500; - Reopen(options); - - Random rnd(301); - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < kNumFiles; ++j) { - if (i > 0) { - // delete [95,105) in two files, [295,305) in next two - int mid = (j + (1 - j % 2)) * kNumPerFile; - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(mid - 5), Key(mid + 5))); - } - std::vector values; - // Write 100KB (100 values, each 1K) - for (int k = 0; k < kNumPerFile; k++) { - values.push_back(rnd.RandomString(990)); - ASSERT_OK(Put(Key(j * kNumPerFile + k), values[k])); - } - // put extra key to trigger flush - ASSERT_OK(Put("", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - if (j < kNumFiles - 1) { - // background compaction may happen early for kNumFiles'th file - ASSERT_EQ(NumTableFilesAtLevel(0), j + 1); - } - if (j == options.level0_file_num_compaction_trigger - 1) { - // When i == 1, compaction will output some files to L1, at which point - // L1 is not bottommost so range deletions cannot be compacted away. The - // new L1 files must be generated with non-overlapping key ranges even - // though multiple subcompactions see the same ranges deleted, else an - // assertion will fail. - // - // Only enable auto-compactions when we're ready; otherwise, the - // oversized L0 (relative to base_level) causes the compaction to run - // earlier. - ASSERT_OK(db_->EnableAutoCompaction({db_->DefaultColumnFamily()})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_OK(db_->SetOptions(db_->DefaultColumnFamily(), - {{"disable_auto_compactions", "true"}})); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - ASSERT_GT(NumTableFilesAtLevel(1), 0); - ASSERT_GT(NumTableFilesAtLevel(2), 0); - } - } - } -} - -TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) { - const int kNumPerFile = 100, kFilesPerLevel = 4, kNumLevels = 4; - Options options = CurrentOptions(); - options.compaction_options_universal.min_merge_width = kFilesPerLevel; - options.compaction_options_universal.max_merge_width = kFilesPerLevel; - options.compaction_options_universal.size_ratio = 10; - options.compaction_style = kCompactionStyleUniversal; - options.level0_file_num_compaction_trigger = kFilesPerLevel; - options.max_subcompactions = 4; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - options.num_levels = kNumLevels; - options.target_file_size_base = kNumPerFile << 10; - options.target_file_size_multiplier = 1; - Reopen(options); - - Random rnd(301); - for (int i = 0; i < kNumLevels - 1; ++i) { - for (int j = 0; j < kFilesPerLevel; ++j) { - if (i == kNumLevels - 2) { - // insert range deletions [95,105) in two files, [295,305) in next two - // to prepare L1 for later manual compaction. - int mid = (j + (1 - j % 2)) * kNumPerFile; - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(mid - 5), Key(mid + 5))); - } - std::vector values; - // Write 100KB (100 values, each 1K) - for (int k = 0; k < kNumPerFile; k++) { - // For the highest level, use smaller value size such that it does not - // prematurely cause auto compaction due to range tombstone adding - // additional compensated file size - values.push_back(rnd.RandomString((i == kNumLevels - 2) ? 600 : 990)); - ASSERT_OK(Put(Key(j * kNumPerFile + k), values[k])); - } - // put extra key to trigger flush - ASSERT_OK(Put("", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - if (j < kFilesPerLevel - 1) { - // background compaction may happen early for kFilesPerLevel'th file - ASSERT_EQ(NumTableFilesAtLevel(0), j + 1); - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - if (i == kNumLevels - 2) { - // For the highest level, value size is smaller (see Put() above), - // so output file number is smaller. - ASSERT_GT(NumTableFilesAtLevel(kNumLevels - 1 - i), kFilesPerLevel - 2); - } else { - ASSERT_GT(NumTableFilesAtLevel(kNumLevels - 1 - i), kFilesPerLevel - 1); - } - } - // Now L1-L3 are full, when we compact L1->L2 we should see (1) subcompactions - // happen since input level > 0; (2) range deletions are not dropped since - // output level is not bottommost. If no file boundary assertion fails, that - // probably means universal compaction + subcompaction + range deletion are - // compatible. - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 1 /* input_level */, 2 /* output_level */, CompactRangeOptions(), - nullptr /* begin */, nullptr /* end */, true /* exclusive */, - true /* disallow_trivial_move */, - std::numeric_limits::max() /* max_file_num_to_ignore */, - "" /*trim_ts*/)); -} - -TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) { - const int kNumPerFile = 3, kNumFiles = 3; - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(2 * kNumPerFile)); - opts.merge_operator = MergeOperators::CreateUInt64AddOperator(); - opts.num_levels = 2; - Reopen(opts); - - // Iterates kNumFiles * kNumPerFile + 1 times since flushing the last file - // requires an extra entry. - for (int i = 0; i <= kNumFiles * kNumPerFile; ++i) { - if (i % kNumPerFile == 0 && i / kNumPerFile == kNumFiles - 1) { - // Delete merge operands from all but the last file - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "key", "key_")); - } - std::string val; - PutFixed64(&val, i); - ASSERT_OK(db_->Merge(WriteOptions(), "key", val)); - // we need to prevent trivial move using Puts so compaction will actually - // process the merge operands. - ASSERT_OK(db_->Put(WriteOptions(), "prevent_trivial_move", "")); - if (i > 0 && i % kNumPerFile == 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - } - - ReadOptions read_opts; - read_opts.ignore_range_deletions = true; - std::string expected, actual; - ASSERT_OK(db_->Get(read_opts, "key", &actual)); - PutFixed64(&expected, 45); // 1+2+...+9 - ASSERT_EQ(expected, actual); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - expected.clear(); - ASSERT_OK(db_->Get(read_opts, "key", &actual)); - uint64_t tmp; - Slice tmp2(actual); - GetFixed64(&tmp2, &tmp); - PutFixed64(&expected, 30); // 6+7+8+9 (earlier operands covered by tombstone) - ASSERT_EQ(expected, actual); -} - -TEST_F(DBRangeDelTest, PutDeleteRangeMergeFlush) { - // Test the sequence of operations: (1) Put, (2) DeleteRange, (3) Merge, (4) - // Flush. The `CompactionIterator` previously had a bug where we forgot to - // check for covering range tombstones when processing the (1) Put, causing - // it to reappear after the flush. - Options opts = CurrentOptions(); - opts.merge_operator = MergeOperators::CreateUInt64AddOperator(); - Reopen(opts); - - std::string val; - PutFixed64(&val, 1); - ASSERT_OK(db_->Put(WriteOptions(), "key", val)); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "key", - "key_")); - ASSERT_OK(db_->Merge(WriteOptions(), "key", val)); - ASSERT_OK(db_->Flush(FlushOptions())); - - ReadOptions read_opts; - std::string expected, actual; - ASSERT_OK(db_->Get(read_opts, "key", &actual)); - PutFixed64(&expected, 1); - ASSERT_EQ(expected, actual); -} - -TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) { - // During compaction to bottommost level, verify range tombstones older than - // the oldest snapshot are removed, while others are preserved. - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.num_levels = 2; - opts.statistics = CreateDBStatistics(); - Reopen(opts); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", - "dr10")); // obsolete after compaction - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - ASSERT_OK(db_->Flush(FlushOptions())); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr2", - "dr20")); // protected by snapshot - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - ASSERT_OK(db_->Flush(FlushOptions())); - - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(1, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE)); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, TableEvictedDuringScan) { - // The RangeDelAggregator holds pointers into range deletion blocks created by - // table readers. This test ensures the aggregator can still access those - // blocks even if it outlives the table readers that created them. - // - // DBIter always keeps readers open for L0 files. So, in order to test - // aggregator outliving reader, we need to have deletions in L1 files, which - // are opened/closed on-demand during the scan. This is accomplished by - // setting kNumRanges > level0_stop_writes_trigger, which prevents deletions - // from all lingering in L0 (there is at most one range deletion per L0 file). - // - // The first L1 file will contain a range deletion since its begin key is 0. - // SeekToFirst() references that table's reader and adds its range tombstone - // to the aggregator. Upon advancing beyond that table's key-range via Next(), - // the table reader will be unreferenced by the iterator. Since we manually - // call Evict() on all readers before the full scan, this unreference causes - // the reader's refcount to drop to zero and thus be destroyed. - // - // When it is destroyed, we do not remove its range deletions from the - // aggregator. So, subsequent calls to Next() must be able to use these - // deletions to decide whether a key is covered. This will work as long as - // the aggregator properly references the range deletion block. - const int kNum = 25, kRangeBegin = 0, kRangeEnd = 7, kNumRanges = 5; - Options opts = CurrentOptions(); - opts.comparator = test::Uint64Comparator(); - opts.level0_file_num_compaction_trigger = 4; - opts.level0_stop_writes_trigger = 4; - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - opts.num_levels = 2; - BlockBasedTableOptions bbto; - bbto.cache_index_and_filter_blocks = true; - bbto.block_cache = NewLRUCache(8 << 20); - opts.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(opts); - - // Hold a snapshot so range deletions can't become obsolete during compaction - // to bottommost level (i.e., L1). - const Snapshot* snapshot = db_->GetSnapshot(); - for (int i = 0; i < kNum; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), GetNumericStr(i), "val")); - if (i > 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - if (i >= kNum / 2 && i < kNum / 2 + kNumRanges) { - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - GetNumericStr(kRangeBegin), - GetNumericStr(kRangeEnd))); - } - } - // Must be > 1 so the first L1 file can be closed before scan finishes - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_GT(NumTableFilesAtLevel(1), 1); - std::vector file_numbers = ListTableFiles(env_, dbname_); - - ReadOptions read_opts; - auto* iter = db_->NewIterator(read_opts); - ASSERT_OK(iter->status()); - int expected = kRangeEnd; - iter->SeekToFirst(); - for (auto file_number : file_numbers) { - // This puts table caches in the state of being externally referenced only - // so they are destroyed immediately upon iterator unreferencing. - TableCache::Evict(dbfull()->TEST_table_cache(), file_number); - } - for (; iter->Valid(); iter->Next()) { - ASSERT_EQ(GetNumericStr(expected), iter->key()); - ++expected; - // Keep clearing block cache's LRU so range deletion block can be freed as - // soon as its refcount drops to zero. - bbto.block_cache->EraseUnRefEntries(); - } - ASSERT_EQ(kNum, expected); - delete iter; - db_->ReleaseSnapshot(snapshot); - - // Also test proper cache handling in GetRangeTombstoneIterator, - // via TablesRangeTombstoneSummary. (This once triggered memory leak - // report with ASAN.) - opts.max_open_files = 1; - Reopen(opts); - - std::string str; - ASSERT_OK(dbfull()->TablesRangeTombstoneSummary(db_->DefaultColumnFamily(), - 100, &str)); -} - -TEST_F(DBRangeDelTest, GetCoveredKeyFromMutableMemtable) { - do { - DestroyAndReopen(CurrentOptions()); - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - - ReadOptions read_opts; - std::string value; - ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); - } while (ChangeOptions(kRangeDelSkipConfigs)); -} - -TEST_F(DBRangeDelTest, GetCoveredKeyFromImmutableMemtable) { - do { - Options opts = CurrentOptions(); - opts.max_write_buffer_number = 3; - opts.min_write_buffer_number_to_merge = 2; - // SpecialSkipListFactory lets us specify maximum number of elements the - // memtable can hold. It switches the active memtable to immutable (flush is - // prevented by the above options) upon inserting an element that would - // overflow the memtable. - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - DestroyAndReopen(opts); - - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Put(WriteOptions(), "blah", "val")); - - ReadOptions read_opts; - std::string value; - ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); - } while (ChangeOptions(kRangeDelSkipConfigs)); -} - -TEST_F(DBRangeDelTest, GetCoveredKeyFromSst) { - do { - DestroyAndReopen(CurrentOptions()); - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - // snapshot prevents key from being deleted during flush - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Flush(FlushOptions())); - - ReadOptions read_opts; - std::string value; - ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); - db_->ReleaseSnapshot(snapshot); - } while (ChangeOptions(kRangeDelSkipConfigs)); -} - -TEST_F(DBRangeDelTest, GetCoveredMergeOperandFromMemtable) { - const int kNumMergeOps = 10; - Options opts = CurrentOptions(); - opts.merge_operator = MergeOperators::CreateUInt64AddOperator(); - Reopen(opts); - - for (int i = 0; i < kNumMergeOps; ++i) { - std::string val; - PutFixed64(&val, i); - ASSERT_OK(db_->Merge(WriteOptions(), "key", val)); - if (i == kNumMergeOps / 2) { - // deletes [0, 5] - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "key", "key_")); - } - } - - ReadOptions read_opts; - std::string expected, actual; - ASSERT_OK(db_->Get(read_opts, "key", &actual)); - PutFixed64(&expected, 30); // 6+7+8+9 - ASSERT_EQ(expected, actual); - - expected.clear(); - read_opts.ignore_range_deletions = true; - ASSERT_OK(db_->Get(read_opts, "key", &actual)); - PutFixed64(&expected, 45); // 0+1+2+...+9 - ASSERT_EQ(expected, actual); -} - -TEST_F(DBRangeDelTest, GetIgnoresRangeDeletions) { - Options opts = CurrentOptions(); - opts.max_write_buffer_number = 4; - opts.min_write_buffer_number_to_merge = 3; - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - Reopen(opts); - - ASSERT_OK(db_->Put(WriteOptions(), "sst_key", "val")); - // snapshot prevents key from being deleted during flush - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK(db_->Put(WriteOptions(), "imm_key", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Put(WriteOptions(), "mem_key", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - - ReadOptions read_opts; - read_opts.ignore_range_deletions = true; - for (std::string key : {"sst_key", "imm_key", "mem_key"}) { - std::string value; - ASSERT_OK(db_->Get(read_opts, key, &value)); - } - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) { - const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25; - Options opts = CurrentOptions(); - opts.comparator = test::Uint64Comparator(); - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - DestroyAndReopen(opts); - - // Write half of the keys before the tombstone and half after the tombstone. - // Only covered keys (i.e., within the range and older than the tombstone) - // should be deleted. - for (int i = 0; i < kNum; ++i) { - if (i == kNum / 2) { - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - GetNumericStr(kRangeBegin), - GetNumericStr(kRangeEnd))); - } - ASSERT_OK(db_->Put(WriteOptions(), GetNumericStr(i), "val")); - } - ReadOptions read_opts; - auto* iter = db_->NewIterator(read_opts); - ASSERT_OK(iter->status()); - - int expected = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_EQ(GetNumericStr(expected), iter->key()); - if (expected == kRangeBegin - 1) { - expected = kNum / 2; - } else { - ++expected; - } - } - ASSERT_EQ(kNum, expected); - delete iter; -} - -TEST_F(DBRangeDelTest, IteratorOverUserSnapshot) { - const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25; - Options opts = CurrentOptions(); - opts.comparator = test::Uint64Comparator(); - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - DestroyAndReopen(opts); - - const Snapshot* snapshot = nullptr; - // Put a snapshot before the range tombstone, verify an iterator using that - // snapshot sees all inserted keys. - for (int i = 0; i < kNum; ++i) { - if (i == kNum / 2) { - snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - GetNumericStr(kRangeBegin), - GetNumericStr(kRangeEnd))); - } - ASSERT_OK(db_->Put(WriteOptions(), GetNumericStr(i), "val")); - } - ReadOptions read_opts; - read_opts.snapshot = snapshot; - auto* iter = db_->NewIterator(read_opts); - ASSERT_OK(iter->status()); - - int expected = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_EQ(GetNumericStr(expected), iter->key()); - ++expected; - } - ASSERT_EQ(kNum / 2, expected); - delete iter; - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, IteratorIgnoresRangeDeletions) { - Options opts = CurrentOptions(); - opts.max_write_buffer_number = 4; - opts.min_write_buffer_number_to_merge = 3; - opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - Reopen(opts); - - ASSERT_OK(db_->Put(WriteOptions(), "sst_key", "val")); - // snapshot prevents key from being deleted during flush - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK(db_->Put(WriteOptions(), "imm_key", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Put(WriteOptions(), "mem_key", "val")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - - ReadOptions read_opts; - read_opts.ignore_range_deletions = true; - auto* iter = db_->NewIterator(read_opts); - ASSERT_OK(iter->status()); - int i = 0; - std::string expected[] = {"imm_key", "mem_key", "sst_key"}; - for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++i) { - std::string key; - ASSERT_EQ(expected[i], iter->key()); - } - ASSERT_EQ(3, i); - delete iter; - db_->ReleaseSnapshot(snapshot); -} - -#ifndef ROCKSDB_UBSAN_RUN -TEST_F(DBRangeDelTest, TailingIteratorRangeTombstoneUnsupported) { - ASSERT_OK(db_->Put(WriteOptions(), "key", "val")); - // snapshot prevents key from being deleted during flush - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - - // iterations check unsupported in memtable, l0, and then l1 - for (int i = 0; i < 3; ++i) { - ReadOptions read_opts; - read_opts.tailing = true; - auto* iter = db_->NewIterator(read_opts); - if (i == 2) { - // For L1+, iterators over files are created on-demand, so need seek - iter->SeekToFirst(); - } - ASSERT_TRUE(iter->status().IsNotSupported()); - - delete iter; - if (i == 0) { - ASSERT_OK(db_->Flush(FlushOptions())); - } else if (i == 1) { - MoveFilesToLevel(1); - } - } - db_->ReleaseSnapshot(snapshot); -} -#endif // !ROCKSDB_UBSAN_RUN - -TEST_F(DBRangeDelTest, SubcompactionHasEmptyDedicatedRangeDelFile) { - const int kNumFiles = 2, kNumKeysPerFile = 4; - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.level0_file_num_compaction_trigger = kNumFiles; - options.max_subcompactions = 2; - options.num_levels = 2; - options.target_file_size_base = 4096; - Reopen(options); - - // need a L1 file for subcompaction to be triggered - ASSERT_OK( - db_->Put(WriteOptions(), db_->DefaultColumnFamily(), Key(0), "val")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - - // put enough keys to fill up the first subcompaction, and later range-delete - // them so that the first subcompaction outputs no key-values. In that case - // it'll consider making an SST file dedicated to range deletions. - for (int i = 0; i < kNumKeysPerFile; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), Key(i), - std::string(1024, 'a'))); - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(kNumKeysPerFile))); - - // the above range tombstone can be dropped, so that one alone won't cause a - // dedicated file to be opened. We can make one protected by snapshot that - // must be considered. Make its range outside the first subcompaction's range - // to exercise the tricky part of the code. - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(kNumKeysPerFile + 1), - Key(kNumKeysPerFile + 2))); - ASSERT_OK(db_->Flush(FlushOptions())); - - ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - ASSERT_OK(db_->EnableAutoCompaction({db_->DefaultColumnFamily()})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, MemtableBloomFilter) { - // regression test for #2743. the range delete tombstones in memtable should - // be added even when Get() skips searching due to its prefix bloom filter - const int kMemtableSize = 1 << 20; // 1MB - const int kMemtablePrefixFilterSize = 1 << 13; // 8KB - const int kNumKeys = 1000; - const int kPrefixLen = 8; - Options options = CurrentOptions(); - options.memtable_prefix_bloom_size_ratio = - static_cast(kMemtablePrefixFilterSize) / kMemtableSize; - options.prefix_extractor.reset( - ROCKSDB_NAMESPACE::NewFixedPrefixTransform(kPrefixLen)); - options.write_buffer_size = kMemtableSize; - Reopen(options); - - for (int i = 0; i < kNumKeys; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(kNumKeys))); - for (int i = 0; i < kNumKeys; ++i) { - std::string value; - ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &value).IsNotFound()); - } -} - -TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) { - // This test originally verified that compaction treated files containing a - // split range deletion in the input level as an atomic unit. I.e., - // compacting any input-level file(s) containing a portion of the range - // deletion causes all other input-level files containing portions of that - // same range deletion to be included in the compaction. Range deletion - // tombstones are now truncated to sstable boundaries which removed the need - // for that behavior (which could lead to excessively large - // compactions). - const int kNumFilesPerLevel = 4, kValueBytes = 4 << 10; - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = kNumFilesPerLevel; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(2 /* num_entries_flush */)); - // max file size could be 2x of target file size, so set it to half of that - options.target_file_size_base = kValueBytes / 2; - // disable dynamic_file_size, as it will cut L1 files into more files (than - // kNumFilesPerLevel). - options.level_compaction_dynamic_file_size = false; - options.max_compaction_bytes = 1500; - // i == 0: CompactFiles - // i == 1: CompactRange - // i == 2: automatic compaction - for (int i = 0; i < 3; ++i) { - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // snapshot protects range tombstone from dropping due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(0), Key(2 * kNumFilesPerLevel))); - - Random rnd(301); - std::string value = rnd.RandomString(kValueBytes); - for (int j = 0; j < kNumFilesPerLevel; ++j) { - // give files overlapping key-ranges to prevent trivial move - ASSERT_OK(Put(Key(j), value)); - ASSERT_OK(Put(Key(2 * kNumFilesPerLevel - 1 - j), value)); - if (j > 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(j, NumTableFilesAtLevel(0)); - } - } - // put extra key to trigger final flush - ASSERT_OK(Put("", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(kNumFilesPerLevel, NumTableFilesAtLevel(1)); - - ColumnFamilyMetaData meta; - db_->GetColumnFamilyMetaData(&meta); - if (i == 0) { - ASSERT_OK(db_->CompactFiles( - CompactionOptions(), {meta.levels[1].files[0].name}, 2 /* level */)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - } else if (i == 1) { - auto begin_str = Key(0), end_str = Key(1); - Slice begin = begin_str, end = end_str; - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin, &end)); - ASSERT_EQ(3, NumTableFilesAtLevel(1)); - } else if (i == 2) { - ASSERT_OK(db_->SetOptions(db_->DefaultColumnFamily(), - {{"max_bytes_for_level_base", "10000"}})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - } - ASSERT_GT(NumTableFilesAtLevel(2), 0); - - db_->ReleaseSnapshot(snapshot); - } -} - -TEST_F(DBRangeDelTest, RangeTombstoneEndKeyAsSstableUpperBound) { - // Test the handling of the range-tombstone end-key as the - // upper-bound for an sstable. - - const int kNumFilesPerLevel = 2, kValueBytes = 4 << 10; - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.level0_file_num_compaction_trigger = kNumFilesPerLevel; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(2 /* num_entries_flush */)); - options.target_file_size_base = kValueBytes; - options.disable_auto_compactions = true; - // disable it for now, otherwise the L1 files are going be cut before data 1: - // L1: [0] [1,4] - // L2: [0,0] - // because the grandparent file is between [0]->[1] and it's size is more than - // 1/8 of target size (4k). - options.level_compaction_dynamic_file_size = false; - - DestroyAndReopen(options); - - // Create an initial sstable at L2: - // [key000000#1,1, key000000#1,1] - ASSERT_OK(Put(Key(0), "")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // A snapshot protects the range tombstone from dropping due to - // becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(2 * kNumFilesPerLevel))); - - // Create 2 additional sstables in L0. Note that the first sstable - // contains the range tombstone. - // [key000000#3,1, key000004#72057594037927935,15] - // [key000001#5,1, key000002#6,1] - Random rnd(301); - std::string value = rnd.RandomString(kValueBytes); - for (int j = 0; j < kNumFilesPerLevel; ++j) { - // Give files overlapping key-ranges to prevent a trivial move when we - // compact from L0 to L1. - ASSERT_OK(Put(Key(j), value)); - ASSERT_OK(Put(Key(2 * kNumFilesPerLevel - 1 - j), value)); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(j + 1, NumTableFilesAtLevel(0)); - } - // Compact the 2 L0 sstables to L1, resulting in the following LSM. There - // are 2 sstables generated in L1 due to the target_file_size_base setting. - // L1: - // [key000000#3,1, key000002#72057594037927935,15] - // [key000002#6,1, key000004#72057594037927935,15] - // L2: - // [key000000#1,1, key000000#1,1] - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - { - // Compact the second sstable in L1: - // L1: - // [key000000#3,1, key000002#72057594037927935,15] - // L2: - // [key000000#1,1, key000000#1,1] - // [key000002#6,1, key000004#72057594037927935,15] - // - // At the same time, verify the compaction does not cause the key at the - // endpoint (key000002#6,1) to disappear. - ASSERT_EQ(value, Get(Key(2))); - auto begin_str = Key(3); - const ROCKSDB_NAMESPACE::Slice begin = begin_str; - ASSERT_OK(dbfull()->TEST_CompactRange(1, &begin, nullptr)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(2, NumTableFilesAtLevel(2)); - ASSERT_EQ(value, Get(Key(2))); - } - - { - // Compact the first sstable in L1. This should be copacetic, but - // was previously resulting in overlapping sstables in L2 due to - // mishandling of the range tombstone end-key when used as the - // largest key for an sstable. The resulting LSM structure should - // be: - // - // L2: - // [key000000#1,1, key000001#72057594037927935,15] - // [key000001#5,1, key000002#72057594037927935,15] - // [key000002#6,1, key000004#72057594037927935,15] - auto begin_str = Key(0); - const ROCKSDB_NAMESPACE::Slice begin = begin_str; - ASSERT_OK(dbfull()->TEST_CompactRange(1, &begin, &begin)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - ASSERT_EQ(3, NumTableFilesAtLevel(2)); - } - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, UnorderedTombstones) { - // Regression test for #2752. Range delete tombstones between - // different snapshot stripes are not stored in order, so the first - // tombstone of each snapshot stripe should be checked as a smallest - // candidate. - Options options = CurrentOptions(); - DestroyAndReopen(options); - - auto cf = db_->DefaultColumnFamily(); - - ASSERT_OK(db_->Put(WriteOptions(), cf, "a", "a")); - ASSERT_OK(db_->Flush(FlushOptions(), cf)); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), cf, "b", "c")); - // Hold a snapshot to separate these two delete ranges. - auto snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), cf, "a", "b")); - ASSERT_OK(db_->Flush(FlushOptions(), cf)); - db_->ReleaseSnapshot(snapshot); - - std::vector> files; - dbfull()->TEST_GetFilesMetaData(cf, &files); - ASSERT_EQ(1, files[0].size()); - ASSERT_EQ("a", files[0][0].smallest.user_key()); - ASSERT_EQ("c", files[0][0].largest.user_key()); - - std::string v; - auto s = db_->Get(ReadOptions(), "a", &v); - ASSERT_TRUE(s.IsNotFound()); -} - -class MockMergeOperator : public MergeOperator { - // Mock non-associative operator. Non-associativity is expressed by lack of - // implementation for any `PartialMerge*` functions. - public: - bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { - assert(merge_out != nullptr); - merge_out->new_value = merge_in.operand_list.back().ToString(); - return true; - } - - const char* Name() const override { return "MockMergeOperator"; } -}; - -TEST_F(DBRangeDelTest, KeyAtOverlappingEndpointReappears) { - // This test uses a non-associative merge operator since that is a convenient - // way to get compaction to write out files with overlapping user-keys at the - // endpoints. Note, however, overlapping endpoints can also occur with other - // value types (Put, etc.), assuming the right snapshots are present. - const int kFileBytes = 1 << 20; - const int kValueBytes = 1 << 10; - const int kNumFiles = 4; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.merge_operator.reset(new MockMergeOperator()); - options.target_file_size_base = kFileBytes; - Reopen(options); - - // Push dummy data to L3 so that our actual test files on L0-L2 - // will not be considered "bottommost" level, otherwise compaction - // may prevent us from creating overlapping user keys - // as on the bottommost layer MergeHelper - ASSERT_OK(db_->Merge(WriteOptions(), "key", "dummy")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(3); - - Random rnd(301); - const Snapshot* snapshot = nullptr; - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kFileBytes / kValueBytes; ++j) { - auto value = rnd.RandomString(kValueBytes); - ASSERT_OK(db_->Merge(WriteOptions(), "key", value)); - } - if (i == kNumFiles - 1) { - // Take snapshot to prevent covered merge operands from being dropped by - // compaction. - snapshot = db_->GetSnapshot(); - // The DeleteRange is the last write so all merge operands are covered. - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "key", "key_")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); - std::string value; - ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound()); - - ASSERT_OK(dbfull()->TEST_CompactRange( - 0 /* level */, nullptr /* begin */, nullptr /* end */, - nullptr /* column_family */, true /* disallow_trivial_move */)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - // Now we have multiple files at L1 all containing a single user key, thus - // guaranteeing overlap in the file endpoints. - ASSERT_GT(NumTableFilesAtLevel(1), 1); - - // Verify no merge operands reappeared after the compaction. - ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound()); - - // Compact and verify again. It's worthwhile because now the files have - // tighter endpoints, so we can verify that doesn't mess anything up. - ASSERT_OK(dbfull()->TEST_CompactRange( - 1 /* level */, nullptr /* begin */, nullptr /* end */, - nullptr /* column_family */, true /* disallow_trivial_move */)); - ASSERT_GT(NumTableFilesAtLevel(2), 1); - ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound()); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, UntruncatedTombstoneDoesNotDeleteNewerKey) { - // Verify a key newer than a range tombstone cannot be deleted by being - // compacted to the bottom level (and thus having its seqnum zeroed) before - // the range tombstone. This used to happen when range tombstones were - // untruncated on reads such that they extended past their file boundaries. - // - // Test summary: - // - // - L1 is bottommost. - // - A couple snapshots are strategically taken to prevent seqnums from being - // zeroed, range tombstone from being dropped, merge operands from being - // dropped, and merge operands from being combined. - // - Left half of files in L1 all have same user key, ensuring their file - // boundaries overlap. In the past this would cause range tombstones to be - // untruncated. - // - Right half of L1 files all have different keys, ensuring no overlap. - // - A range tombstone spans all L1 keys, so it is stored in every L1 file. - // - Keys in the right side of the key-range are overwritten. These are - // compacted down to L1 after releasing snapshots such that their seqnums - // will be zeroed. - // - A full range scan is performed. If the tombstone in the left L1 files - // were untruncated, it would now cover keys newer than it (but with zeroed - // seqnums) in the right L1 files. - const int kFileBytes = 1 << 20; - const int kValueBytes = 1 << 10; - const int kNumFiles = 4; - const int kMaxKey = kNumFiles * kFileBytes / kValueBytes; - const int kKeysOverwritten = 10; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.merge_operator.reset(new MockMergeOperator()); - options.num_levels = 2; - options.target_file_size_base = kFileBytes; - Reopen(options); - - Random rnd(301); - // - snapshots[0] prevents merge operands from being combined during - // compaction. - // - snapshots[1] prevents merge operands from being dropped due to the - // covering range tombstone. - const Snapshot* snapshots[] = {nullptr, nullptr}; - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kFileBytes / kValueBytes; ++j) { - auto value = rnd.RandomString(kValueBytes); - std::string key; - if (i < kNumFiles / 2) { - key = Key(0); - } else { - key = Key(1 + i * kFileBytes / kValueBytes + j); - } - ASSERT_OK(db_->Merge(WriteOptions(), key, value)); - } - if (i == 0) { - snapshots[0] = db_->GetSnapshot(); - } - if (i == kNumFiles - 1) { - snapshots[1] = db_->GetSnapshot(); - // The DeleteRange is the last write so all merge operands are covered. - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(0), Key(kMaxKey + 1))); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); - - auto get_key_count = [this]() -> int { - auto* iter = db_->NewIterator(ReadOptions()); - assert(iter->status().ok()); - iter->SeekToFirst(); - int keys_found = 0; - for (; iter->Valid(); iter->Next()) { - ++keys_found; - } - delete iter; - return keys_found; - }; - - // All keys should be covered - ASSERT_EQ(0, get_key_count()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr /* begin_key */, - nullptr /* end_key */)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - // Roughly the left half of L1 files should have overlapping boundary keys, - // while the right half should not. - ASSERT_GE(NumTableFilesAtLevel(1), kNumFiles); - - // Now overwrite a few keys that are in L1 files that definitely don't have - // overlapping boundary keys. - for (int i = kMaxKey; i > kMaxKey - kKeysOverwritten; --i) { - auto value = rnd.RandomString(kValueBytes); - ASSERT_OK(db_->Merge(WriteOptions(), Key(i), value)); - } - ASSERT_OK(db_->Flush(FlushOptions())); - - // The overwritten keys are in L0 now, so clearly aren't covered by the range - // tombstone in L1. - ASSERT_EQ(kKeysOverwritten, get_key_count()); - - // Release snapshots so seqnums can be zeroed when L0->L1 happens. - db_->ReleaseSnapshot(snapshots[0]); - db_->ReleaseSnapshot(snapshots[1]); - - auto begin_key_storage = Key(kMaxKey - kKeysOverwritten + 1); - auto end_key_storage = Key(kMaxKey); - Slice begin_key(begin_key_storage); - Slice end_key(end_key_storage); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin_key, &end_key)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GE(NumTableFilesAtLevel(1), kNumFiles); - - ASSERT_EQ(kKeysOverwritten, get_key_count()); -} - -TEST_F(DBRangeDelTest, DeletedMergeOperandReappearsIterPrev) { - // Exposes a bug where we were using - // `RangeDelPositioningMode::kBackwardTraversal` while scanning merge operands - // in the forward direction. Confusingly, this case happened during - // `DBIter::Prev`. It could cause assertion failure, or reappearing keys. - const int kFileBytes = 1 << 20; - const int kValueBytes = 1 << 10; - // Need multiple keys so we can get results when calling `Prev()` after - // `SeekToLast()`. - const int kNumKeys = 3; - const int kNumFiles = 4; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.merge_operator.reset(new MockMergeOperator()); - options.target_file_size_base = kFileBytes; - Reopen(options); - - Random rnd(301); - const Snapshot* snapshot = nullptr; - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kFileBytes / kValueBytes; ++j) { - auto value = rnd.RandomString(kValueBytes); - ASSERT_OK(db_->Merge(WriteOptions(), Key(j % kNumKeys), value)); - if (i == 0 && j == kNumKeys) { - // Take snapshot to prevent covered merge operands from being dropped or - // merged by compaction. - snapshot = db_->GetSnapshot(); - // Do a DeleteRange near the beginning so only the oldest merge operand - // for each key is covered. This ensures the sequence of events: - // - // - `DBIter::Prev()` is called - // - After several same versions of the same user key are encountered, - // it decides to seek using `DBIter::FindValueForCurrentKeyUsingSeek`. - // - Binary searches to the newest version of the key, which is in the - // leftmost file containing the user key. - // - Scans forwards to collect all merge operands. Eventually reaches - // the rightmost file containing the oldest merge operand, which - // should be covered by the `DeleteRange`. If `RangeDelAggregator` - // were not properly using `kForwardTraversal` here, that operand - // would reappear. - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(0), Key(kNumKeys + 1))); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr /* begin_key */, - nullptr /* end_key */)); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(1), 1); - - auto* iter = db_->NewIterator(ReadOptions()); - ASSERT_OK(iter->status()); - iter->SeekToLast(); - int keys_found = 0; - for (; iter->Valid(); iter->Prev()) { - ++keys_found; - } - delete iter; - ASSERT_EQ(kNumKeys, keys_found); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, SnapshotPreventsDroppedKeys) { - const int kFileBytes = 1 << 20; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = kFileBytes; - Reopen(options); - - ASSERT_OK(Put(Key(0), "a")); - const Snapshot* snapshot = db_->GetSnapshot(); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(10))); - - ASSERT_OK(db_->Flush(FlushOptions())); - - ReadOptions read_opts; - read_opts.snapshot = snapshot; - auto* iter = db_->NewIterator(read_opts); - ASSERT_OK(iter->status()); - - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(Key(0), iter->key()); - - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - delete iter; - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, SnapshotPreventsDroppedKeysInImmMemTables) { - const int kFileBytes = 1 << 20; - - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = kFileBytes; - Reopen(options); - - // block flush thread -> pin immtables in memory - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"SnapshotPreventsDroppedKeysInImmMemTables:AfterNewIterator", - "DBImpl::BGWorkFlush"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(0), "a")); - std::unique_ptr> - snapshot(db_->GetSnapshot(), - [this](const Snapshot* s) { db_->ReleaseSnapshot(s); }); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(10))); - - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - - ReadOptions read_opts; - read_opts.snapshot = snapshot.get(); - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_OK(iter->status()); - - TEST_SYNC_POINT("SnapshotPreventsDroppedKeysInImmMemTables:AfterNewIterator"); - - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(Key(0), iter->key()); - - iter->Next(); - ASSERT_FALSE(iter->Valid()); -} - -TEST_F(DBRangeDelTest, RangeTombstoneWrittenToMinimalSsts) { - // Adapted from - // https://github.com/cockroachdb/cockroach/blob/de8b3ea603dd1592d9dc26443c2cc92c356fbc2f/pkg/storage/engine/rocksdb_test.go#L1267-L1398. - // Regression test for issue where range tombstone was written to more files - // than necessary when it began exactly at the begin key in the next - // compaction output file. - const int kFileBytes = 1 << 20; - const int kValueBytes = 4 << 10; - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - // Have a bit of slack in the size limits but we enforce them more strictly - // when manually flushing/compacting. - options.max_compaction_bytes = 2 * kFileBytes; - options.target_file_size_base = 2 * kFileBytes; - options.write_buffer_size = 2 * kFileBytes; - Reopen(options); - - Random rnd(301); - for (char first_char : {'a', 'b', 'c'}) { - for (int i = 0; i < kFileBytes / kValueBytes; ++i) { - std::string key(1, first_char); - key.append(Key(i)); - std::string value = rnd.RandomString(kValueBytes); - ASSERT_OK(Put(key, value)); - } - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - } - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(3, NumTableFilesAtLevel(2)); - - // Populate the memtable lightly while spanning the whole key-space. The - // setting of `max_compaction_bytes` will cause the L0->L1 to output multiple - // files to prevent a large L1->L2 compaction later. - ASSERT_OK(Put("a", "val")); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "c" + Key(1), "d")); - // Our compaction output file cutting logic currently only considers point - // keys. So, in order for the range tombstone to have a chance at landing at - // the start of a new file, we need a point key at the range tombstone's - // start. - // TODO(ajkr): remove this `Put` after file cutting accounts for range - // tombstones (#3977). - ASSERT_OK(Put("c" + Key(1), "value")); - ASSERT_OK(db_->Flush(FlushOptions())); - - // Ensure manual L0->L1 compaction cuts the outputs before the range tombstone - // and the range tombstone is only placed in the second SST. - std::string begin_key_storage("c" + Key(1)); - Slice begin_key(begin_key_storage); - std::string end_key_storage("d"); - Slice end_key(end_key_storage); - ASSERT_OK(dbfull()->TEST_CompactRange( - 0 /* level */, &begin_key /* begin */, &end_key /* end */, - nullptr /* column_family */, true /* disallow_trivial_move */)); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - std::vector all_metadata; - std::vector l1_metadata; - db_->GetLiveFilesMetaData(&all_metadata); - for (const auto& metadata : all_metadata) { - if (metadata.level == 1) { - l1_metadata.push_back(metadata); - } - } - std::sort(l1_metadata.begin(), l1_metadata.end(), - [&](const LiveFileMetaData& a, const LiveFileMetaData& b) { - return options.comparator->Compare(a.smallestkey, b.smallestkey) < - 0; - }); - ASSERT_EQ("a", l1_metadata[0].smallestkey); - ASSERT_EQ("a", l1_metadata[0].largestkey); - ASSERT_EQ("c" + Key(1), l1_metadata[1].smallestkey); - ASSERT_EQ("d", l1_metadata[1].largestkey); - - TablePropertiesCollection all_table_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&all_table_props)); - int64_t num_range_deletions = 0; - for (const auto& name_and_table_props : all_table_props) { - const auto& name = name_and_table_props.first; - const auto& table_props = name_and_table_props.second; - // The range tombstone should only be output to the second L1 SST. - if (name.size() >= l1_metadata[1].name.size() && - name.substr(name.size() - l1_metadata[1].name.size()) - .compare(l1_metadata[1].name) == 0) { - ASSERT_EQ(1, table_props->num_range_deletions); - ++num_range_deletions; - } else { - ASSERT_EQ(0, table_props->num_range_deletions); - } - } - ASSERT_EQ(1, num_range_deletions); -} - -TEST_F(DBRangeDelTest, LevelCompactOutputCutAtRangeTombstoneForTtlFiles) { - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.compaction_pri = kMinOverlappingRatio; - options.disable_auto_compactions = true; - options.ttl = 24 * 60 * 60; // 24 hours - options.target_file_size_base = 8 << 10; - env_->SetMockSleep(); - options.env = env_; - DestroyAndReopen(options); - - Random rnd(301); - // Fill some data so that future compactions are not bottommost level - // compaction, and hence they would try cut around files for ttl - for (int i = 5; i < 10; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1 << 10))); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(3); - ASSERT_EQ("0,0,0,1", FilesPerLevel()); - - for (int i = 5; i < 10; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1 << 10))); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - ASSERT_EQ("0,1,0,1", FilesPerLevel()); - - env_->MockSleepForSeconds(20 * 60 * 60); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(11), Key(12))); - ASSERT_OK(Put(Key(0), rnd.RandomString(1 << 10))); - ASSERT_OK(Flush()); - ASSERT_EQ("1,1,0,1", FilesPerLevel()); - // L0 file is new, L1 and L3 file are old and qualified for TTL - env_->MockSleepForSeconds(10 * 60 * 60); - MoveFilesToLevel(1); - // L1 output should be cut into 3 files: - // File 0: Key(0) - // File 1: (qualified for TTL): Key(5) - Key(10) - // File 1: DeleteRange [11, 12) - ASSERT_EQ("0,3,0,1", FilesPerLevel()); -} - -// Test SST partitioner cut after every single key -class SingleKeySstPartitioner : public SstPartitioner { - public: - const char* Name() const override { return "SingleKeySstPartitioner"; } - - PartitionerResult ShouldPartition( - const PartitionerRequest& /*request*/) override { - return kRequired; - } - - bool CanDoTrivialMove(const Slice& /*smallest_user_key*/, - const Slice& /*largest_user_key*/) override { - return false; - } -}; - -class SingleKeySstPartitionerFactory : public SstPartitionerFactory { - public: - static const char* kClassName() { return "SingleKeySstPartitionerFactory"; } - const char* Name() const override { return kClassName(); } - - std::unique_ptr CreatePartitioner( - const SstPartitioner::Context& /* context */) const override { - return std::unique_ptr(new SingleKeySstPartitioner()); - } -}; - -TEST_F(DBRangeDelTest, CompactionEmitRangeTombstoneToSSTPartitioner) { - Options options = CurrentOptions(); - auto factory = std::make_shared(); - options.sst_partitioner_factory = factory; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - Random rnd(301); - // range deletion keys are not processed when compacting to bottommost level, - // so creating a file at older level to make the next compaction not - // bottommost level - ASSERT_OK(db_->Put(WriteOptions(), Key(4), rnd.RandomString(10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(5); - - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(10))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(5))); - ASSERT_OK(Flush()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(1); - // SSTPartitioner decides to cut when range tombstone start key is passed to - // it. Note that the range tombstone [2, 5) itself span multiple keys, but we - // are not able to partition within its range yet. - ASSERT_EQ(2, NumTableFilesAtLevel(1)); -} - -TEST_F(DBRangeDelTest, OversizeCompactionGapBetweenPointKeyAndTombstone) { - // L2 has 2 files - // L2_0: 0, 1, 2, 3, 4 - // L2_1: 5, 6, 7 - // L0 has 1 file - // L0: 0, [5, 6), 8 - // max_compaction_bytes is less than the size of L2_0 and L2_1. - // When compacting L0 into L1, it should split into 3 files: - // compaction output should cut before key 5 and key 8 to - // limit future compaction size. - const int kNumPerFile = 4, kNumFiles = 2; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.target_file_size_base = 9 * 1024; - options.max_compaction_bytes = 9 * 1024; - DestroyAndReopen(options); - Random rnd(301); - for (int i = 0; i < kNumFiles; ++i) { - std::vector values; - for (int j = 0; j < kNumPerFile; j++) { - values.push_back(rnd.RandomString(3 << 10)); - ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j])); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(2); - ASSERT_EQ(2, NumTableFilesAtLevel(2)); - ASSERT_OK(Put(Key(0), rnd.RandomString(1 << 10))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(5), - Key(6))); - ASSERT_OK(Put(Key(8), rnd.RandomString(1 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(3, NumTableFilesAtLevel(1)); -} - -TEST_F(DBRangeDelTest, OversizeCompactionGapBetweenTombstone) { - // L2 has two files - // L2_0: 0, 1, 2, 3, 4. L2_1: 5, 6, 7 - // L0 has two range tombstones [0, 1), [7, 8). - // max_compaction_bytes is less than the size of L2_0. - // When compacting L0 into L1, the two range tombstones should be - // split into two files. - const int kNumPerFile = 4, kNumFiles = 2; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.target_file_size_base = 9 * 1024; - options.max_compaction_bytes = 9 * 1024; - DestroyAndReopen(options); - Random rnd(301); - for (int i = 0; i < kNumFiles; ++i) { - std::vector values; - // Write 12K (4 values, each 3K) - for (int j = 0; j < kNumPerFile; j++) { - values.push_back(rnd.RandomString(3 << 10)); - ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j])); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(2); - ASSERT_EQ(2, NumTableFilesAtLevel(2)); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(1))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(7), - Key(8))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - // This is L0 -> L1 compaction - // The two range tombstones are broken up into two output files - // to limit compaction size. - ASSERT_EQ(2, NumTableFilesAtLevel(1)); -} - -TEST_F(DBRangeDelTest, OversizeCompactionPointKeyWithinRangetombstone) { - // L2 has two files - // L2_0: 0, 1, 2, 3, 4. L2_1: 6, 7, 8 - // L0 has [0, 9) and point key 5 - // max_compaction_bytes is less than the size of L2_0. - // When compacting L0 into L1, the compaction should cut at point key 5. - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.target_file_size_base = 9 * 1024; - options.max_compaction_bytes = 9 * 1024; - DestroyAndReopen(options); - Random rnd(301); - for (int i = 0; i < 9; ++i) { - if (i == 5) { - ++i; - } - ASSERT_OK(Put(Key(i), rnd.RandomString(3 << 10))); - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(2); - ASSERT_EQ(2, NumTableFilesAtLevel(2)); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(9))); - ASSERT_OK(Put(Key(5), rnd.RandomString(1 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); -} - -TEST_F(DBRangeDelTest, OverlappedTombstones) { - const int kNumPerFile = 4, kNumFiles = 2; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.target_file_size_base = 9 * 1024; - options.max_compaction_bytes = 9 * 1024; - DestroyAndReopen(options); - Random rnd(301); - for (int i = 0; i < kNumFiles; ++i) { - std::vector values; - // Write 12K (4 values, each 3K) - for (int j = 0; j < kNumPerFile; j++) { - values.push_back(rnd.RandomString(3 << 10)); - ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j])); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(2); - ASSERT_EQ(2, NumTableFilesAtLevel(2)); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(1), - Key((kNumFiles)*kNumPerFile + 1))); - ASSERT_OK(db_->Flush(FlushOptions())); - - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - - // The tombstone range is not broken up into multiple SSTs which may incur a - // large compaction with L2. - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - std::vector> files; - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); -} - -TEST_F(DBRangeDelTest, OverlappedKeys) { - const int kNumPerFile = 4, kNumFiles = 2; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.target_file_size_base = 9 * 1024; - options.max_compaction_bytes = 9 * 1024; - DestroyAndReopen(options); - Random rnd(301); - for (int i = 0; i < kNumFiles; ++i) { - std::vector values; - // Write 12K (4 values, each 3K) - for (int j = 0; j < kNumPerFile; j++) { - values.push_back(rnd.RandomString(3 << 10)); - ASSERT_OK(Put(Key(i * kNumPerFile + j), values[j])); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(2); - ASSERT_EQ(2, NumTableFilesAtLevel(2)); - - for (int i = 1; i < kNumFiles * kNumPerFile + 1; i++) { - ASSERT_OK(Put(Key(i), "0x123")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // The key range is broken up into three SSTs to avoid a future big compaction - // with the grandparent - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(3, NumTableFilesAtLevel(1)); - - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - // L1->L2 compaction size is limited to max_compaction_bytes - ASSERT_EQ(3, NumTableFilesAtLevel(2)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); -} - -TEST_F(DBRangeDelTest, IteratorRefresh) { - // Refreshing an iterator after a range tombstone is added should cause the - // deleted range of keys to disappear. - for (bool sv_changed : {false, true}) { - ASSERT_OK(db_->Put(WriteOptions(), "key1", "value1")); - ASSERT_OK(db_->Put(WriteOptions(), "key2", "value2")); - - auto* iter = db_->NewIterator(ReadOptions()); - ASSERT_OK(iter->status()); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "key2", "key3")); - - if (sv_changed) { - ASSERT_OK(db_->Flush(FlushOptions())); - } - - ASSERT_OK(iter->Refresh()); - ASSERT_OK(iter->status()); - iter->SeekToFirst(); - ASSERT_EQ("key1", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - delete iter; - } -} - -void VerifyIteratorReachesEnd(InternalIterator* iter) { - ASSERT_TRUE(!iter->Valid() && iter->status().ok()); -} - -void VerifyIteratorReachesEnd(Iterator* iter) { - ASSERT_TRUE(!iter->Valid() && iter->status().ok()); -} - -TEST_F(DBRangeDelTest, IteratorReseek) { - // Range tombstone triggers reseek (seeking to a range tombstone end key) in - // merging iterator. Test set up: - // one memtable: range tombstone [0, 1) - // one immutable memtable: range tombstone [1, 2) - // one L0 file with range tombstone [2, 3) - // one L1 file with range tombstone [3, 4) - // Seek(0) should trigger cascading reseeks at all levels below memtable. - // Seek(1) should trigger cascading reseeks at all levels below immutable - // memtable. SeekToFirst and SeekToLast trigger no reseek. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - - DestroyAndReopen(options); - // L1 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(3), - Key(4))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - // L0 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(3))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - // Immutable memtable - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(1), - Key(2))); - ASSERT_OK(static_cast_with_check(db_)->TEST_SwitchMemtable()); - std::string value; - ASSERT_TRUE(dbfull()->GetProperty(db_->DefaultColumnFamily(), - "rocksdb.num-immutable-mem-table", &value)); - ASSERT_EQ(1, std::stoi(value)); - // live memtable - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(1))); - // this memtable is still active - ASSERT_TRUE(dbfull()->GetProperty(db_->DefaultColumnFamily(), - "rocksdb.num-immutable-mem-table", &value)); - ASSERT_EQ(1, std::stoi(value)); - - auto iter = db_->NewIterator(ReadOptions()); - get_perf_context()->Reset(); - iter->Seek(Key(0)); - // Reseeked immutable memtable, L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 3); - VerifyIteratorReachesEnd(iter); - get_perf_context()->Reset(); - iter->SeekForPrev(Key(1)); - // Reseeked L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - VerifyIteratorReachesEnd(iter); - get_perf_context()->Reset(); - iter->SeekToFirst(); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 0); - VerifyIteratorReachesEnd(iter); - iter->SeekToLast(); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 0); - VerifyIteratorReachesEnd(iter); - delete iter; -} - -TEST_F(DBRangeDelTest, ReseekDuringNextAndPrev) { - // Range tombstone triggers reseek during Next()/Prev() in merging iterator. - // Test set up: - // memtable has: [0, 1) [2, 3) - // L0 has: 2 - // L1 has: 1, 2, 3 - // Seek(0) will reseek to 1 for L0 and L1. Seek(1) will not trigger any - // reseek. Then Next() determines 2 is covered by [2, 3), it will try to - // reseek to 3 for L0 and L1. Similar story for Prev() and SeekForPrev() is - // tested. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - - DestroyAndReopen(options); - // L1 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), "foo")); - ASSERT_OK(db_->Put(WriteOptions(), Key(2), "foo")); - ASSERT_OK(db_->Put(WriteOptions(), Key(3), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - // L0 - ASSERT_OK(db_->Put(WriteOptions(), Key(2), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // Memtable - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(1))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(3))); - - auto iter = db_->NewIterator(ReadOptions()); - auto iter_test_forward = [&] { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(1)); - - get_perf_context()->Reset(); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(3)); - // Reseeked L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - - // Next to Prev - get_perf_context()->Reset(); - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(1)); - // Reseeked L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - - // Prev to Next - get_perf_context()->Reset(); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(3)); - // Reseeked L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - - iter->Next(); - VerifyIteratorReachesEnd(iter); - }; - - get_perf_context()->Reset(); - iter->Seek(Key(0)); - // Reseeked L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - iter_test_forward(); - get_perf_context()->Reset(); - iter->Seek(Key(1)); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 0); - iter_test_forward(); - - get_perf_context()->Reset(); - iter->SeekForPrev(Key(2)); - // Reseeked L0 and L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - iter_test_forward(); - get_perf_context()->Reset(); - iter->SeekForPrev(Key(1)); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 0); - iter_test_forward(); - - get_perf_context()->Reset(); - iter->SeekToFirst(); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 0); - iter_test_forward(); - - iter->SeekToLast(); - iter->Prev(); - iter_test_forward(); - delete iter; -} - -TEST_F(DBRangeDelTest, TombstoneFromCurrentLevel) { - // Range tombstone triggers reseek when covering key from the same level. - // in merging iterator. Test set up: - // memtable has: [0, 1) - // L0 has: [2, 3), 2 - // L1 has: 1, 2, 3 - // Seek(0) will reseek to 1 for L0 and L1. - // Then Next() will reseek to 3 for L1 since 2 in L0 is covered by [2, 3) in - // L0. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - - DestroyAndReopen(options); - // L1 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), "foo")); - ASSERT_OK(db_->Put(WriteOptions(), Key(2), "foo")); - ASSERT_OK(db_->Put(WriteOptions(), Key(3), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - // L0 - ASSERT_OK(db_->Put(WriteOptions(), Key(2), "foo")); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(3))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // Memtable - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(1))); - - auto iter = db_->NewIterator(ReadOptions()); - get_perf_context()->Reset(); - iter->Seek(Key(0)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(1)); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 2); - - get_perf_context()->Reset(); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(3)); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 1); - - delete iter; -} - -class TombstoneTestSstPartitioner : public SstPartitioner { - public: - const char* Name() const override { return "SingleKeySstPartitioner"; } - - PartitionerResult ShouldPartition( - const PartitionerRequest& request) override { - if (cmp->Compare(*request.current_user_key, DBTestBase::Key(5)) == 0) { - return kRequired; - } else { - return kNotRequired; - } - } - - bool CanDoTrivialMove(const Slice& /*smallest_user_key*/, - const Slice& /*largest_user_key*/) override { - return false; - } - - const Comparator* cmp = BytewiseComparator(); -}; - -class TombstoneTestSstPartitionerFactory : public SstPartitionerFactory { - public: - static const char* kClassName() { - return "TombstoneTestSstPartitionerFactory"; - } - const char* Name() const override { return kClassName(); } - - std::unique_ptr CreatePartitioner( - const SstPartitioner::Context& /* context */) const override { - return std::unique_ptr(new TombstoneTestSstPartitioner()); - } -}; - -TEST_F(DBRangeDelTest, TombstoneAcrossFileBoundary) { - // Verify that a range tombstone across file boundary covers keys from older - // levels. Test set up: - // L1_0: 1, 3, [2, 6) L1_1: 5, 7, [2, 6) ([2, 6) is from compaction with - // L1_0) L2 has: 5 - // Seek(1) and then Next() should move the L1 level iterator to - // L1_1. Check if 5 is returned after Next(). - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 2 * 1024; - options.max_compaction_bytes = 2 * 1024; - - // Make sure L1 files are split before "5" - auto factory = std::make_shared(); - options.sst_partitioner_factory = factory; - - DestroyAndReopen(options); - - Random rnd(301); - // L2 - // the file should be smaller than max_compaction_bytes, otherwise the file - // will be cut before 7. - ASSERT_OK(db_->Put(WriteOptions(), Key(5), rnd.RandomString(1 << 9))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_1 - ASSERT_OK(db_->Put(WriteOptions(), Key(5), rnd.RandomString(1 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(7), rnd.RandomString(1 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // L1_0 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(1 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(3), rnd.RandomString(1 << 10))); - // Prevent keys being compacted away - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(6))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - get_perf_context()->Reset(); - iter->Seek(Key(1)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(1)); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(7)); - // 1 reseek into L2 when key 5 in L2 is covered by [2, 6) from L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 1); - - delete iter; - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, NonOverlappingTombstonAtBoundary) { - // Verify that a range tombstone across file boundary covers keys from older - // levels. - // Test set up: - // L1_0: 1, 3, [4, 7) L1_1: 6, 8, [4, 7) - // L2: 5 - // Note that [4, 7) is at end of L1_0 and not overlapping with any point key - // in L1_0. [4, 7) from L1_0 should cover 5 is sentinel works - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 2 * 1024; - options.level_compaction_dynamic_file_size = false; - DestroyAndReopen(options); - - Random rnd(301); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(5), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_1 - ASSERT_OK(db_->Put(WriteOptions(), Key(6), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(8), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // L1_0 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(3), rnd.RandomString(4 << 10))); - // Prevent keys being compacted away - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(4), - Key(7))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->Seek(Key(3)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(3)); - get_perf_context()->Reset(); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(8)); - // 1 reseek into L1 since 5 from L2 is covered by [4, 7) from L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 1); - for (auto& k : {4, 5, 6}) { - get_perf_context()->Reset(); - iter->Seek(Key(k)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(8)); - // 1 reseek into L1 - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, 1); - } - delete iter; -} - -TEST_F(DBRangeDelTest, OlderLevelHasNewerData) { - // L1_0: 1, 3, [2, 7) L1_1: 5, 6 at a newer sequence number than [2, 7) - // Compact L1_1 to L2. Seek(3) should not skip 5 or 6. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - DestroyAndReopen(options); - - Random rnd(301); - // L1_0 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(3), rnd.RandomString(4 << 10))); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(7))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - // L1_1 - ASSERT_OK(db_->Put(WriteOptions(), Key(5), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(6), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - auto key = Key(6); - Slice begin(key); - EXPECT_OK(dbfull()->TEST_CompactRange(1, &begin, nullptr)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->Seek(Key(3)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(5)); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), Key(6)); - delete iter; - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, LevelBoundaryDefinedByTombstone) { - // L1 has: 1, 2, [4, 5) - // L2 has: 4 - // Seek(3), which is over all points keys in L1, check whether - // sentinel key from L1 works in this case. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - DestroyAndReopen(options); - Random rnd(301); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(4), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - const Snapshot* snapshot = db_->GetSnapshot(); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_0 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(2), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(4), - Key(5))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->Seek(Key(3)); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - - get_perf_context()->Reset(); - iter->SeekForPrev(Key(5)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(2)); - db_->ReleaseSnapshot(snapshot); - delete iter; -} - -TEST_F(DBRangeDelTest, TombstoneOnlyFile) { - // L1_0: 1, 2, L1_1: [3, 5) - // L2: 3 - // Seek(2) then Next() should advance L1 iterator into L1_1. - // If sentinel works with tombstone only file, it should cover the key in L2. - // Similar story for SeekForPrev(4). - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - - DestroyAndReopen(options); - Random rnd(301); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(3), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_0 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(2), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_1 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(3), - Key(5))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->Seek(Key(2)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(2)); - iter->Next(); - VerifyIteratorReachesEnd(iter); - iter->SeekForPrev(Key(4)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(2)); - iter->Next(); - VerifyIteratorReachesEnd(iter); - delete iter; -} - -void VerifyIteratorKey(InternalIterator* iter, - const std::vector& expected_keys, - bool forward = true) { - for (auto& key : expected_keys) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->user_key(), key); - if (forward) { - iter->Next(); - } else { - iter->Prev(); - } - } -} - -TEST_F(DBRangeDelTest, TombstoneOnlyLevel) { - // L1 [3, 5) - // L2 has: 3, 4 - // Any kind of iterator seek should skip 3 and 4 in L2. - // L1 level iterator should produce sentinel key. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - - DestroyAndReopen(options); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(3), "foo")); - ASSERT_OK(db_->Put(WriteOptions(), Key(4), "bar")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(3), - Key(5))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - get_perf_context()->Reset(); - uint64_t expected_reseek = 0; - for (auto i = 0; i < 7; ++i) { - iter->Seek(Key(i)); - VerifyIteratorReachesEnd(iter); - if (i < 5) { - ++expected_reseek; - } - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, - expected_reseek); - iter->SeekForPrev(Key(i)); - VerifyIteratorReachesEnd(iter); - if (i > 2) { - ++expected_reseek; - } - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, - expected_reseek); - iter->SeekToFirst(); - VerifyIteratorReachesEnd(iter); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, - ++expected_reseek); - iter->SeekToLast(); - VerifyIteratorReachesEnd(iter); - ASSERT_EQ(get_perf_context()->internal_range_del_reseek_count, - ++expected_reseek); - } - delete iter; - - // Check L1 LevelIterator behavior - ColumnFamilyData* cfd = - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(); - SuperVersion* sv = cfd->GetSuperVersion(); - Arena arena; - ReadOptions read_options; - MergeIteratorBuilder merge_iter_builder(&cfd->internal_comparator(), &arena, - false /* prefix seek */); - InternalIterator* level_iter = sv->current->TEST_GetLevelIterator( - read_options, &merge_iter_builder, 1 /* level */, true); - // This is needed to make LevelIterator range tombstone aware - auto miter = merge_iter_builder.Finish(); - auto k = Key(3); - IterKey target; - target.SetInternalKey(k, kMaxSequenceNumber, kValueTypeForSeek); - level_iter->Seek(target.GetInternalKey()); - // sentinel key (file boundary as a fake key) - VerifyIteratorKey(level_iter, {Key(5)}); - VerifyIteratorReachesEnd(level_iter); - - k = Key(5); - target.SetInternalKey(k, 0, kValueTypeForSeekForPrev); - level_iter->SeekForPrev(target.GetInternalKey()); - VerifyIteratorKey(level_iter, {Key(3)}, false); - VerifyIteratorReachesEnd(level_iter); - - level_iter->SeekToFirst(); - VerifyIteratorKey(level_iter, {Key(5)}); - VerifyIteratorReachesEnd(level_iter); - - level_iter->SeekToLast(); - VerifyIteratorKey(level_iter, {Key(3)}, false); - VerifyIteratorReachesEnd(level_iter); - - miter->~InternalIterator(); -} - -TEST_F(DBRangeDelTest, TombstoneOnlyWithOlderVisibleKey) { - // L1: [3, 5) - // L2: 2, 4, 5 - // 2 and 5 should be visible - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - - DestroyAndReopen(options); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(2), "foo")); - ASSERT_OK(db_->Put(WriteOptions(), Key(4), "bar")); - ASSERT_OK(db_->Put(WriteOptions(), Key(5), "foobar")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // l1 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(3), - Key(5))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - auto iter_test_backward = [&] { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(5)); - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(2)); - iter->Prev(); - VerifyIteratorReachesEnd(iter); - }; - auto iter_test_forward = [&] { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(2)); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(5)); - iter->Next(); - VerifyIteratorReachesEnd(iter); - }; - iter->Seek(Key(4)); - iter_test_backward(); - iter->SeekForPrev(Key(4)); - iter->Next(); - iter_test_backward(); - - iter->Seek(Key(4)); - iter->Prev(); - iter_test_forward(); - iter->SeekForPrev(Key(4)); - iter_test_forward(); - - iter->SeekToFirst(); - iter_test_forward(); - iter->SeekToLast(); - iter_test_backward(); - - delete iter; -} - -TEST_F(DBRangeDelTest, TombstoneSentinelDirectionChange) { - // L1: 7 - // L2: [4, 6) - // L3: 4 - // Seek(5) will have sentinel key 6 at the top of minHeap in merging iterator. - // then do a prev, how would sentinel work? - // Redo the test after Put(5) into L1 so that there is a visible key in range - // [4, 6). - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - - DestroyAndReopen(options); - // L3 - ASSERT_OK(db_->Put(WriteOptions(), Key(4), "bar")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(3); - ASSERT_EQ(1, NumTableFilesAtLevel(3)); - // L2 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(4), - Key(6))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1 - ASSERT_OK(db_->Put(WriteOptions(), Key(7), "foobar")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->Seek(Key(5)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(7)); - iter->Prev(); - ASSERT_TRUE(!iter->Valid() && iter->status().ok()); - delete iter; - - ASSERT_OK(db_->Put(WriteOptions(), Key(5), "foobar")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - iter = db_->NewIterator(ReadOptions()); - iter->Seek(Key(5)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(5)); - iter->Prev(); - ASSERT_TRUE(!iter->Valid() && iter->status().ok()); - delete iter; -} - -// Right sentinel tested in many test cases above -TEST_F(DBRangeDelTest, LeftSentinelKeyTest) { - // L1_0: 0, 1 L1_1: [2, 3), 5 - // L2: 2 - // SeekForPrev(4) should give 1 due to sentinel key keeping [2, 3) alive. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - options.max_compaction_bytes = 2048; - - DestroyAndReopen(options); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(2), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_0 - Random rnd(301); - ASSERT_OK(db_->Put(WriteOptions(), Key(0), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - // L1_1 - ASSERT_OK(db_->Put(WriteOptions(), Key(5), "bar")); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(3))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->SeekForPrev(Key(4)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(1)); - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(0)); - iter->Prev(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - delete iter; -} - -TEST_F(DBRangeDelTest, LeftSentinelKeyTestWithNewerKey) { - // L1_0: 1, 2 newer than L1_1, L1_1: [2, 4), 5 - // L2: 3 - // SeekForPrev(4) then Prev() should give 2 and then 1. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - options.max_compaction_bytes = 3 * 1024; - - DestroyAndReopen(options); - // L2 - ASSERT_OK(db_->Put(WriteOptions(), Key(3), "foo")); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1_1 - ASSERT_OK(db_->Put(WriteOptions(), Key(5), "bar")); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(4))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - // L1_0 - Random rnd(301); - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(2), rnd.RandomString(4 << 10))); - // Used to verify sequence number of iterator key later. - auto seq = dbfull()->TEST_GetLastVisibleSequence(); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - Arena arena; - InternalKeyComparator icmp(options.comparator); - ReadOptions read_options; - ScopedArenaIterator iter; - iter.set( - dbfull()->NewInternalIterator(read_options, &arena, kMaxSequenceNumber)); - - auto k = Key(4); - IterKey target; - target.SetInternalKey(k, 0 /* sequence_number */, kValueTypeForSeekForPrev); - iter->SeekForPrev(target.GetInternalKey()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->user_key(), Key(2)); - SequenceNumber actual_seq; - ValueType type; - UnPackSequenceAndType(ExtractInternalKeyFooter(iter->key()), &actual_seq, - &type); - ASSERT_EQ(seq, actual_seq); - // might as well check type - ASSERT_EQ(type, kTypeValue); - - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->user_key(), Key(1)); - iter->Prev(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); -} - -TEST_F(DBRangeDelTest, SentinelKeyCommonCaseTest) { - // L1 has 3 files - // L1_0: 1, 2 L1_1: [3, 4) 5, 6, [7, 8) L1_2: 9 - // Check iterator operations on LevelIterator. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.target_file_size_base = 3 * 1024; - - DestroyAndReopen(options); - Random rnd(301); - // L1_0 - ASSERT_OK(db_->Put(WriteOptions(), Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(2), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - // L1_1 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(3), - Key(4))); - ASSERT_OK(db_->Put(WriteOptions(), Key(5), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Put(WriteOptions(), Key(6), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(7), - Key(8))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(2, NumTableFilesAtLevel(1)); - - // L1_2 - ASSERT_OK(db_->Put(WriteOptions(), Key(9), rnd.RandomString(4 << 10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(3, NumTableFilesAtLevel(1)); - - ColumnFamilyData* cfd = - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(); - SuperVersion* sv = cfd->GetSuperVersion(); - Arena arena; - ReadOptions read_options; - MergeIteratorBuilder merge_iter_builder(&cfd->internal_comparator(), &arena, - false /* prefix seek */); - InternalIterator* level_iter = sv->current->TEST_GetLevelIterator( - read_options, &merge_iter_builder, 1 /* level */, true); - // This is needed to make LevelIterator range tombstone aware - auto miter = merge_iter_builder.Finish(); - auto k = Key(7); - IterKey target; - target.SetInternalKey(k, kMaxSequenceNumber, kValueTypeForSeek); - level_iter->Seek(target.GetInternalKey()); - // The last Key(9) is a sentinel key. - VerifyIteratorKey(level_iter, {Key(8), Key(9), Key(9)}); - ASSERT_TRUE(!level_iter->Valid() && level_iter->status().ok()); - - k = Key(6); - target.SetInternalKey(k, kMaxSequenceNumber, kValueTypeForSeek); - level_iter->Seek(target.GetInternalKey()); - VerifyIteratorKey(level_iter, {Key(6), Key(8), Key(9), Key(9)}); - ASSERT_TRUE(!level_iter->Valid() && level_iter->status().ok()); - - k = Key(4); - target.SetInternalKey(k, 0, kValueTypeForSeekForPrev); - level_iter->SeekForPrev(target.GetInternalKey()); - VerifyIteratorKey(level_iter, {Key(3), Key(2), Key(1), Key(1)}, false); - ASSERT_TRUE(!level_iter->Valid() && level_iter->status().ok()); - - k = Key(5); - target.SetInternalKey(k, 0, kValueTypeForSeekForPrev); - level_iter->SeekForPrev(target.GetInternalKey()); - VerifyIteratorKey(level_iter, {Key(5), Key(3), Key(2), Key(1), Key(1)}, - false); - - level_iter->SeekToFirst(); - VerifyIteratorKey(level_iter, {Key(1), Key(2), Key(2), Key(5), Key(6), Key(8), - Key(9), Key(9)}); - ASSERT_TRUE(!level_iter->Valid() && level_iter->status().ok()); - - level_iter->SeekToLast(); - VerifyIteratorKey( - level_iter, - {Key(9), Key(9), Key(6), Key(5), Key(3), Key(2), Key(1), Key(1)}, false); - ASSERT_TRUE(!level_iter->Valid() && level_iter->status().ok()); - - miter->~InternalIterator(); -} - -TEST_F(DBRangeDelTest, PrefixSentinelKey) { - // L1: ['aaaa', 'aaad'), 'bbbb' - // L2: 'aaac', 'aaae' - // Prefix extracts first 3 chars - // Seek('aaab') should give 'aaae' as first key. - // This is to test a previous bug where prefix seek sees there is no prefix in - // the SST file, and will just set file iter to null in LevelIterator and may - // just skip to the next SST file. But in this case, we should keep the file's - // tombstone alive. - Options options = CurrentOptions(); - options.compression = kNoCompression; - options.disable_auto_compactions = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); - table_options.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DestroyAndReopen(options); - Random rnd(301); - - // L2: - ASSERT_OK(db_->Put(WriteOptions(), "aaac", rnd.RandomString(10))); - ASSERT_OK(db_->Put(WriteOptions(), "aaae", rnd.RandomString(10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(2); - ASSERT_EQ(1, NumTableFilesAtLevel(2)); - - // L1 - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "aaaa", - "aaad")); - ASSERT_OK(db_->Put(WriteOptions(), "bbbb", rnd.RandomString(10))); - ASSERT_OK(db_->Flush(FlushOptions())); - MoveFilesToLevel(1); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - - auto iter = db_->NewIterator(ReadOptions()); - iter->Seek("aaab"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), "aaae"); - delete iter; -} - -TEST_F(DBRangeDelTest, RefreshMemtableIter) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ReadOptions ro; - ro.read_tier = kMemtableTier; - std::unique_ptr iter{db_->NewIterator(ro)}; - ASSERT_OK(Flush()); - // First refresh reinits iter, which had a bug where - // iter.memtable_range_tombstone_iter_ was not set to nullptr, and caused - // subsequent refresh to double free. - ASSERT_OK(iter->Refresh()); - ASSERT_OK(iter->Refresh()); -} - -TEST_F(DBRangeDelTest, RangeTombstoneRespectIterateUpperBound) { - // Memtable: a, [b, bz) - // Do a Seek on `a` with iterate_upper_bound being az - // range tombstone [b, bz) should not be processed (added to and - // popped from the min_heap in MergingIterator). - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - ASSERT_OK(Put("a", "bar")); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "b", "bz")); - - // I could not find a cleaner way to test this without relying on - // implementation detail. Tried to test the value of - // `internal_range_del_reseek_count` but that did not work - // since BlockBasedTable iterator becomes !Valid() when point key - // is out of bound and that reseek only happens when a point key - // is covered by some range tombstone. - SyncPoint::GetInstance()->SetCallBack("MergeIterator::PopDeleteRangeStart", - [](void*) { - // there should not be any range - // tombstone in the heap. - FAIL(); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ReadOptions read_opts; - std::string upper_bound = "az"; - Slice upper_bound_slice = upper_bound; - read_opts.iterate_upper_bound = &upper_bound_slice; - std::unique_ptr iter{db_->NewIterator(read_opts)}; - iter->Seek("a"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), "a"); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); -} - -TEST_F(DBRangeDelTest, RangetombesoneCompensateFilesize) { - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - DestroyAndReopen(opts); - - std::vector values; - Random rnd(301); - // file in L2 - values.push_back(rnd.RandomString(1 << 10)); - ASSERT_OK(Put("a", values.back())); - values.push_back(rnd.RandomString(1 << 10)); - ASSERT_OK(Put("b", values.back())); - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - uint64_t l2_size = 0; - ASSERT_OK(Size("a", "c", 0 /* cf */, &l2_size)); - ASSERT_GT(l2_size, 0); - // file in L1 - values.push_back(rnd.RandomString(1 << 10)); - ASSERT_OK(Put("d", values.back())); - values.push_back(rnd.RandomString(1 << 10)); - ASSERT_OK(Put("e", values.back())); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - uint64_t l1_size = 0; - ASSERT_OK(Size("d", "f", 0 /* cf */, &l1_size)); - ASSERT_GT(l1_size, 0); - - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "f")); - ASSERT_OK(Flush()); - // Range deletion compensated size computed during flush time - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(level_to_files[0].size(), 1); - ASSERT_EQ(level_to_files[0][0].compensated_range_deletion_size, - l1_size + l2_size); - ASSERT_EQ(level_to_files[1].size(), 1); - ASSERT_EQ(level_to_files[1][0].compensated_range_deletion_size, 0); - ASSERT_EQ(level_to_files[2].size(), 1); - ASSERT_EQ(level_to_files[2][0].compensated_range_deletion_size, 0); - - // Range deletion compensated size computed during compaction time - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - ASSERT_EQ(NumTableFilesAtLevel(1), 1); - ASSERT_EQ(NumTableFilesAtLevel(2), 1); - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(level_to_files[1].size(), 1); - ASSERT_EQ(level_to_files[1][0].compensated_range_deletion_size, l2_size); - ASSERT_EQ(level_to_files[2].size(), 1); - ASSERT_EQ(level_to_files[2][0].compensated_range_deletion_size, 0); -} - -TEST_F(DBRangeDelTest, RangetombesoneCompensateFilesizePersistDuringReopen) { - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - DestroyAndReopen(opts); - - std::vector values; - Random rnd(301); - values.push_back(rnd.RandomString(1 << 10)); - ASSERT_OK(Put("a", values.back())); - values.push_back(rnd.RandomString(1 << 10)); - ASSERT_OK(Put("b", values.back())); - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "c")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(Flush()); - - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(level_to_files[0].size(), 1); - ASSERT_EQ(level_to_files[1].size(), 1); - ASSERT_EQ(level_to_files[2].size(), 1); - uint64_t l2_size = level_to_files[2][0].fd.GetFileSize(); - uint64_t l1_size = level_to_files[1][0].fd.GetFileSize(); - ASSERT_GT(l2_size, 0); - ASSERT_GT(l1_size, 0); - ASSERT_EQ(level_to_files[0][0].compensated_range_deletion_size, - l1_size + l2_size); - ASSERT_EQ(level_to_files[1][0].compensated_range_deletion_size, l2_size); - - Reopen(opts); - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(level_to_files[0].size(), 1); - ASSERT_EQ(level_to_files[0][0].compensated_range_deletion_size, - l1_size + l2_size); - ASSERT_EQ(level_to_files[1].size(), 1); - ASSERT_EQ(level_to_files[1][0].compensated_range_deletion_size, l2_size); -} - -TEST_F(DBRangeDelTest, SingleKeyFile) { - // Test for a bug fix where a range tombstone could be added - // to an SST file while is not within the file's key range. - // Create 3 files in L0 and then L1 where all keys have the same user key - // `Key(2)`. The middle file will contain Key(2)@6 and Key(2)@5. Before fix, - // the range tombstone [Key(2), Key(5))@2 would be added to this file during - // compaction, but it is not in this file's key range. - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.target_file_size_base = 1 << 10; - opts.level_compaction_dynamic_file_size = false; - DestroyAndReopen(opts); - - // prevent range tombstone drop - std::vector snapshots; - snapshots.push_back(db_->GetSnapshot()); - - // write a key to bottommost file so the compactions below - // are not bottommost compactions and will calculate - // compensated range tombstone size. Before bug fix, an assert would fail - // during this process. - Random rnd(301); - ASSERT_OK(Put(Key(2), rnd.RandomString(8 << 10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(6); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(5))); - snapshots.push_back(db_->GetSnapshot()); - std::vector values; - - values.push_back(rnd.RandomString(8 << 10)); - ASSERT_OK(Put(Key(2), rnd.RandomString(8 << 10))); - snapshots.push_back(db_->GetSnapshot()); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(2), rnd.RandomString(8 << 10))); - snapshots.push_back(db_->GetSnapshot()); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(2), rnd.RandomString(8 << 10))); - snapshots.push_back(db_->GetSnapshot()); - ASSERT_OK(Flush()); - - ASSERT_EQ(NumTableFilesAtLevel(0), 3); - CompactRangeOptions co; - co.bottommost_level_compaction = BottommostLevelCompaction::kForce; - - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 0, 1, co, nullptr, nullptr, true, true, - std::numeric_limits::max() /*max_file_num_to_ignore*/, - "" /*trim_ts*/)); - - for (const auto s : snapshots) { - db_->ReleaseSnapshot(s); - } -} - -TEST_F(DBRangeDelTest, DoubleCountRangeTombstoneCompensatedSize) { - // Test for a bug fix if a file has multiple range tombstones - // with same start and end key but with different sequence numbers, - // we should only calculate compensated range tombstone size - // for one of them. - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - DestroyAndReopen(opts); - - std::vector values; - Random rnd(301); - // file in L2 - ASSERT_OK(Put(Key(1), rnd.RandomString(1 << 10))); - ASSERT_OK(Put(Key(2), rnd.RandomString(1 << 10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - uint64_t l2_size = 0; - ASSERT_OK(Size(Key(1), Key(3), 0 /* cf */, &l2_size)); - ASSERT_GT(l2_size, 0); - - // file in L1 - ASSERT_OK(Put(Key(3), rnd.RandomString(1 << 10))); - ASSERT_OK(Put(Key(4), rnd.RandomString(1 << 10))); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - uint64_t l1_size = 0; - ASSERT_OK(Size(Key(3), Key(5), 0 /* cf */, &l1_size)); - ASSERT_GT(l1_size, 0); - - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(1), - Key(5))); - // so that the range tombstone above is not dropped - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(1), - Key(5))); - ASSERT_OK(Flush()); - // Range deletion compensated size computed during flush time - std::vector> level_to_files; - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(level_to_files[0].size(), 1); - // instead of 2 * (l1_size + l2_size) - ASSERT_EQ(level_to_files[0][0].compensated_range_deletion_size, - l1_size + l2_size); - - // Range deletion compensated size computed during compaction time - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */)); - dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(), - &level_to_files); - ASSERT_EQ(level_to_files[1].size(), 1); - ASSERT_EQ(level_to_files[1][0].compensated_range_deletion_size, l2_size); - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, AddRangeDelsSameLowerAndUpperBound) { - // Test for an edge case where CompactionOutputs::AddRangeDels() - // is called with an empty range: `range_tombstone_lower_bound_` is not empty - // and have the same user_key and sequence number as `next_table_min_key. - // This used to cause file's smallest and largest key to be incorrectly set - // such that smallest > largest, and fail some assertions in iterator and/or - // assertion in VersionSet::ApproximateSize(). - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.target_file_size_base = 1 << 10; - opts.level_compaction_dynamic_file_size = false; - DestroyAndReopen(opts); - - Random rnd(301); - // Create file at bottommost level so the manual compaction below is - // non-bottommost level and goes through code path like compensate range - // tombstone size. - ASSERT_OK(Put(Key(1), "v1")); - ASSERT_OK(Put(Key(4), "v2")); - ASSERT_OK(Flush()); - MoveFilesToLevel(6); - - ASSERT_OK(Put(Key(1), rnd.RandomString(4 << 10))); - ASSERT_OK(Put(Key(3), rnd.RandomString(4 << 10))); - // So Key(3) does not get dropped. - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(4))); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(3), rnd.RandomString(4 << 10))); - ASSERT_OK(Put(Key(4), rnd.RandomString(4 << 10))); - ASSERT_OK(Flush()); - - MoveFilesToLevel(1); - // Each file will have two keys, with Key(3) straddle between two files. - // File 1: Key(1)@1, Key(3)@6, DeleteRange ends at Key(3)@6 - // File 2: Key(3)@4, Key(4)@7, DeleteRange start from Key(3)@4 - ASSERT_EQ(NumTableFilesAtLevel(1), 2); - - // Manually update compaction output file cutting decisions - // to cut before range tombstone sentinel Key(3)@4 - // and the point key Key(3)@4 itself - SyncPoint::GetInstance()->SetCallBack( - "CompactionOutputs::ShouldStopBefore::manual_decision", [opts](void* p) { - auto* pair = (std::pair*)p; - if ((opts.comparator->Compare(ExtractUserKey(pair->second), Key(3)) == - 0) && - (GetInternalKeySeqno(pair->second) <= 4)) { - *(pair->first) = true; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - std::string begin_key = Key(0); - std::string end_key = Key(5); - Slice begin_slice{begin_key}; - Slice end_slice{end_key}; - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 1, 2, CompactRangeOptions(), &begin_slice, &end_slice, true, - true /* disallow_trivial_move */, - std::numeric_limits::max() /*max_file_num_to_ignore*/, - "" /*trim_ts*/)); - // iterate through to check if any assertion breaks - std::unique_ptr iter{db_->NewIterator(ReadOptions())}; - iter->SeekToFirst(); - std::vector expected{1, 3, 4}; - for (auto i : expected) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(i)); - iter->Next(); - } - ASSERT_TRUE(iter->status().ok() && !iter->Valid()); - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(DBRangeDelTest, AddRangeDelsSingleUserKeyTombstoneOnlyFile) { - // Test for an edge case where CompactionOutputs::AddRangeDels() - // is called with an SST file that has no point keys, and that - // the lower bound and upper bound have the same user key. - // This could cause a file's smallest and largest key to be incorrectly set - // such that smallest > largest, and fail some assertions in iterator and/or - // assertion in VersionSet::ApproximateSize(). - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.target_file_size_base = 1 << 10; - opts.level_compaction_dynamic_file_size = false; - DestroyAndReopen(opts); - - Random rnd(301); - // Create file at bottommost level so the manual compaction below is - // non-bottommost level and goes through code path like compensate range - // tombstone size. - ASSERT_OK(Put(Key(1), "v1")); - ASSERT_OK(Put(Key(4), "v2")); - ASSERT_OK(Flush()); - MoveFilesToLevel(6); - - ASSERT_OK(Put(Key(1), rnd.RandomString(10))); - // Key(3)@4 - ASSERT_OK(Put(Key(3), rnd.RandomString(10))); - const Snapshot* snapshot1 = db_->GetSnapshot(); - // Key(3)@5 - ASSERT_OK(Put(Key(3), rnd.RandomString(10))); - const Snapshot* snapshot2 = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(2), - Key(4))); - // Key(3)@7 - ASSERT_OK(Put(Key(3), rnd.RandomString(10))); - ASSERT_OK(Flush()); - - // L0 -> L1 compaction: cut output into two files: - // File 1: Key(1), Key(3)@7, Range tombstone ends at Key(3)@7 - // File 2: Key(3)@5, Key(3)@4, Range tombstone starts from Key(3)@5 - SyncPoint::GetInstance()->SetCallBack( - "CompactionOutputs::ShouldStopBefore::manual_decision", [opts](void* p) { - auto* pair = (std::pair*)p; - if ((opts.comparator->Compare(ExtractUserKey(pair->second), Key(3)) == - 0) && - (GetInternalKeySeqno(pair->second) <= 6)) { - *(pair->first) = true; - SyncPoint::GetInstance()->DisableProcessing(); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - std::string begin_key = Key(0); - std::string end_key = Key(5); - Slice begin_slice{begin_key}; - Slice end_slice{end_key}; - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 0, 1, CompactRangeOptions(), &begin_slice, &end_slice, true, - true /* disallow_trivial_move */, - std::numeric_limits::max() /*max_file_num_to_ignore*/, - "" /*trim_ts*/)); - ASSERT_EQ(NumTableFilesAtLevel(1), 2); - - // L1 -> L2 compaction, drop the snapshot protecting Key(3)@5. - // Let ShouldStopBefore() return true for Key(3)@5 (delete range sentinel) - // and Key(3)@4. - // Output should have two files: - // File 1: Key(1), Key(3)@7, range tombstone ends at Key(3)@7 - // File dropped: range tombstone only file (from Key(3)@5 to Key(3)@4) - // File 2: Range tombstone starting from Key(3)@4, Key(3)@4 - db_->ReleaseSnapshot(snapshot2); - SyncPoint::GetInstance()->SetCallBack( - "CompactionOutputs::ShouldStopBefore::manual_decision", [opts](void* p) { - auto* pair = (std::pair*)p; - if ((opts.comparator->Compare(ExtractUserKey(pair->second), Key(3)) == - 0) && - (GetInternalKeySeqno(pair->second) <= 6)) { - *(pair->first) = true; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 1, 2, CompactRangeOptions(), &begin_slice, &end_slice, true, - true /* disallow_trivial_move */, - std::numeric_limits::max() /*max_file_num_to_ignore*/, - "" /*trim_ts*/)); - ASSERT_EQ(NumTableFilesAtLevel(2), 2); - // iterate through to check if any assertion breaks - std::unique_ptr iter{db_->NewIterator(ReadOptions())}; - iter->SeekToFirst(); - std::vector expected{1, 3, 4}; - for (auto i : expected) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key(i)); - iter->Next(); - } - ASSERT_TRUE(iter->status().ok() && !iter->Valid()); - db_->ReleaseSnapshot(snapshot1); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_rate_limiter_test.cc b/db/db_rate_limiter_test.cc deleted file mode 100644 index acea673cb..000000000 --- a/db/db_rate_limiter_test.cc +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright (c) 2022-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include -#include - -#include "db/db_test_util.h" -#include "port/stack_trace.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "test_util/testharness.h" -#include "util/file_checksum_helper.h" - -namespace ROCKSDB_NAMESPACE { - -class DBRateLimiterOnReadTest - : public DBTestBase, - public ::testing::WithParamInterface> { - public: - explicit DBRateLimiterOnReadTest() - : DBTestBase("db_rate_limiter_on_read_test", /*env_do_fsync=*/false), - use_direct_io_(std::get<0>(GetParam())), - use_block_cache_(std::get<1>(GetParam())), - use_readahead_(std::get<2>(GetParam())) {} - - void Init() { - options_ = GetOptions(); - Reopen(options_); - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(Put(Key(i * kNumKeysPerFile + j), "val")); - } - ASSERT_OK(Flush()); - } - MoveFilesToLevel(1); - } - - BlockBasedTableOptions GetTableOptions() { - BlockBasedTableOptions table_options; - table_options.no_block_cache = !use_block_cache_; - return table_options; - } - - ReadOptions GetReadOptions() { - ReadOptions read_options; - read_options.rate_limiter_priority = Env::IO_USER; - read_options.readahead_size = use_readahead_ ? kReadaheadBytes : 0; - return read_options; - } - - Options GetOptions() { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.file_checksum_gen_factory.reset(new FileChecksumGenCrc32cFactory()); - options.rate_limiter.reset(NewGenericRateLimiter( - 1 << 20 /* rate_bytes_per_sec */, 100 * 1000 /* refill_period_us */, - 10 /* fairness */, RateLimiter::Mode::kAllIo)); - options.table_factory.reset(NewBlockBasedTableFactory(GetTableOptions())); - options.use_direct_reads = use_direct_io_; - return options; - } - - protected: - const static int kNumKeysPerFile = 1; - const static int kNumFiles = 3; - const static int kReadaheadBytes = 32 << 10; // 32KB - - Options options_; - const bool use_direct_io_; - const bool use_block_cache_; - const bool use_readahead_; -}; - -std::string GetTestNameSuffix( - ::testing::TestParamInfo> info) { - std::ostringstream oss; - if (std::get<0>(info.param)) { - oss << "DirectIO"; - } else { - oss << "BufferedIO"; - } - if (std::get<1>(info.param)) { - oss << "_BlockCache"; - } else { - oss << "_NoBlockCache"; - } - if (std::get<2>(info.param)) { - oss << "_Readahead"; - } else { - oss << "_NoReadahead"; - } - return oss.str(); -} - -INSTANTIATE_TEST_CASE_P(DBRateLimiterOnReadTest, DBRateLimiterOnReadTest, - ::testing::Combine(::testing::Bool(), ::testing::Bool(), - ::testing::Bool()), - GetTestNameSuffix); - -TEST_P(DBRateLimiterOnReadTest, Get) { - if (use_direct_io_ && !IsDirectIOSupported()) { - return; - } - Init(); - - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - int expected = 0; - for (int i = 0; i < kNumFiles; ++i) { - { - std::string value; - ASSERT_OK(db_->Get(GetReadOptions(), Key(i * kNumKeysPerFile), &value)); - ++expected; - } - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - { - std::string value; - ASSERT_OK(db_->Get(GetReadOptions(), Key(i * kNumKeysPerFile), &value)); - if (!use_block_cache_) { - ++expected; - } - } - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - } -} - -TEST_P(DBRateLimiterOnReadTest, NewMultiGet) { - if (use_direct_io_ && !IsDirectIOSupported()) { - return; - } - Init(); - - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - const int kNumKeys = kNumFiles * kNumKeysPerFile; - int64_t expected = 0; - { - std::vector key_bufs; - key_bufs.reserve(kNumKeys); - std::vector keys; - keys.reserve(kNumKeys); - for (int i = 0; i < kNumKeys; ++i) { - key_bufs.emplace_back(Key(i)); - keys.emplace_back(key_bufs[i]); - } - std::vector statuses(kNumKeys); - std::vector values(kNumKeys); - const int64_t prev_total_rl_req = options_.rate_limiter->GetTotalRequests(); - db_->MultiGet(GetReadOptions(), dbfull()->DefaultColumnFamily(), kNumKeys, - keys.data(), values.data(), statuses.data()); - const int64_t cur_total_rl_req = options_.rate_limiter->GetTotalRequests(); - for (int i = 0; i < kNumKeys; ++i) { - ASSERT_TRUE(statuses[i].ok()); - } - ASSERT_GT(cur_total_rl_req, prev_total_rl_req); - ASSERT_EQ(cur_total_rl_req - prev_total_rl_req, - options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - } - expected += kNumKeys; - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); -} - -TEST_P(DBRateLimiterOnReadTest, OldMultiGet) { - // The old `vector`-returning `MultiGet()` APIs use `Read()`, which - // supports rate limiting. - if (use_direct_io_ && !IsDirectIOSupported()) { - return; - } - Init(); - - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - const int kNumKeys = kNumFiles * kNumKeysPerFile; - int expected = 0; - { - std::vector key_bufs; - key_bufs.reserve(kNumKeys); - std::vector keys; - keys.reserve(kNumKeys); - for (int i = 0; i < kNumKeys; ++i) { - key_bufs.emplace_back(Key(i)); - keys.emplace_back(key_bufs[i]); - } - std::vector values; - std::vector statuses = - db_->MultiGet(GetReadOptions(), keys, &values); - for (int i = 0; i < kNumKeys; ++i) { - ASSERT_OK(statuses[i]); - } - } - expected += kNumKeys; - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); -} - -TEST_P(DBRateLimiterOnReadTest, Iterator) { - if (use_direct_io_ && !IsDirectIOSupported()) { - return; - } - Init(); - - std::unique_ptr iter(db_->NewIterator(GetReadOptions())); - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - int expected = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ++expected; - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - } - - for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { - // When `use_block_cache_ == true`, the reverse scan will access the blocks - // loaded to cache during the above forward scan, in which case no further - // file reads are expected. - if (!use_block_cache_) { - ++expected; - } - } - // Reverse scan does not read evenly (one block per iteration) due to - // descending seqno ordering, so wait until after the loop to check total. - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); -} - - -TEST_P(DBRateLimiterOnReadTest, VerifyChecksum) { - if (use_direct_io_ && !IsDirectIOSupported()) { - return; - } - Init(); - - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - ASSERT_OK(db_->VerifyChecksum(GetReadOptions())); - // The files are tiny so there should have just been one read per file. - int expected = kNumFiles; - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); -} - -TEST_P(DBRateLimiterOnReadTest, VerifyFileChecksums) { - if (use_direct_io_ && !IsDirectIOSupported()) { - return; - } - Init(); - - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - ASSERT_OK(db_->VerifyFileChecksums(GetReadOptions())); - // The files are tiny so there should have just been one read per file. - int expected = kNumFiles; - ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); -} - - -class DBRateLimiterOnWriteTest : public DBTestBase { - public: - explicit DBRateLimiterOnWriteTest() - : DBTestBase("db_rate_limiter_on_write_test", /*env_do_fsync=*/false) {} - - void Init() { - options_ = GetOptions(); - ASSERT_OK(TryReopenWithColumnFamilies({"default"}, options_)); - Random rnd(301); - for (int i = 0; i < kNumFiles; i++) { - ASSERT_OK(Put(0, kStartKey, rnd.RandomString(2))); - ASSERT_OK(Put(0, kEndKey, rnd.RandomString(2))); - ASSERT_OK(Flush(0)); - } - } - - Options GetOptions() { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.rate_limiter.reset(NewGenericRateLimiter( - 1 << 20 /* rate_bytes_per_sec */, 100 * 1000 /* refill_period_us */, - 10 /* fairness */, RateLimiter::Mode::kWritesOnly)); - options.table_factory.reset( - NewBlockBasedTableFactory(BlockBasedTableOptions())); - return options; - } - - protected: - inline const static int64_t kNumFiles = 3; - inline const static std::string kStartKey = "a"; - inline const static std::string kEndKey = "b"; - Options options_; -}; - -TEST_F(DBRateLimiterOnWriteTest, Flush) { - std::int64_t prev_total_request = 0; - - Init(); - - std::int64_t actual_flush_request = - options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) - - prev_total_request; - std::int64_t exepcted_flush_request = kNumFiles; - EXPECT_EQ(actual_flush_request, exepcted_flush_request); - EXPECT_EQ(actual_flush_request, - options_.rate_limiter->GetTotalRequests(Env::IO_HIGH)); -} - -TEST_F(DBRateLimiterOnWriteTest, Compact) { - Init(); - - // Pre-comaction: - // level-0 : `kNumFiles` SST files overlapping on [kStartKey, kEndKey] - std::string files_per_level_pre_compaction = std::to_string(kNumFiles); - ASSERT_EQ(files_per_level_pre_compaction, FilesPerLevel(0 /* cf */)); - - std::int64_t prev_total_request = - options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL); - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_LOW)); - - Compact(kStartKey, kEndKey); - - std::int64_t actual_compaction_request = - options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) - - prev_total_request; - - // Post-comaction: - // level-0 : 0 SST file - // level-1 : 1 SST file - std::string files_per_level_post_compaction = "0,1"; - ASSERT_EQ(files_per_level_post_compaction, FilesPerLevel(0 /* cf */)); - - std::int64_t exepcted_compaction_request = 1; - EXPECT_EQ(actual_compaction_request, exepcted_compaction_request); - EXPECT_EQ(actual_compaction_request, - options_.rate_limiter->GetTotalRequests(Env::IO_LOW)); -} - -class DBRateLimiterOnWriteWALTest - : public DBRateLimiterOnWriteTest, - public ::testing::WithParamInterface> { - public: - static std::string GetTestNameSuffix( - ::testing::TestParamInfo> info) { - std::ostringstream oss; - if (std::get<0>(info.param)) { - oss << "DisableWAL"; - } else { - oss << "EnableWAL"; - } - if (std::get<1>(info.param)) { - oss << "_ManualWALFlush"; - } else { - oss << "_AutoWALFlush"; - } - if (std::get<2>(info.param) == Env::IO_USER) { - oss << "_RateLimitAutoWALFlush"; - } else if (std::get<2>(info.param) == Env::IO_TOTAL) { - oss << "_NoRateLimitAutoWALFlush"; - } else { - oss << "_RateLimitAutoWALFlushWithIncorrectPriority"; - } - return oss.str(); - } - - explicit DBRateLimiterOnWriteWALTest() - : disable_wal_(std::get<0>(GetParam())), - manual_wal_flush_(std::get<1>(GetParam())), - rate_limiter_priority_(std::get<2>(GetParam())) {} - - void Init() { - options_ = GetOptions(); - options_.manual_wal_flush = manual_wal_flush_; - Reopen(options_); - } - - WriteOptions GetWriteOptions() { - WriteOptions write_options; - write_options.disableWAL = disable_wal_; - write_options.rate_limiter_priority = rate_limiter_priority_; - return write_options; - } - - protected: - bool disable_wal_; - bool manual_wal_flush_; - Env::IOPriority rate_limiter_priority_; -}; - -INSTANTIATE_TEST_CASE_P( - DBRateLimiterOnWriteWALTest, DBRateLimiterOnWriteWALTest, - ::testing::Values(std::make_tuple(false, false, Env::IO_TOTAL), - std::make_tuple(false, false, Env::IO_USER), - std::make_tuple(false, false, Env::IO_HIGH), - std::make_tuple(false, true, Env::IO_USER), - std::make_tuple(true, false, Env::IO_USER)), - DBRateLimiterOnWriteWALTest::GetTestNameSuffix); - -TEST_P(DBRateLimiterOnWriteWALTest, AutoWalFlush) { - Init(); - - const bool no_rate_limit_auto_wal_flush = - (rate_limiter_priority_ == Env::IO_TOTAL); - const bool valid_arg = (rate_limiter_priority_ == Env::IO_USER && - !disable_wal_ && !manual_wal_flush_); - - std::int64_t prev_total_request = - options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL); - ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_USER)); - - Status s = Put("foo", "v1", GetWriteOptions()); - - if (no_rate_limit_auto_wal_flush || valid_arg) { - EXPECT_TRUE(s.ok()); - } else { - EXPECT_TRUE(s.IsInvalidArgument()); - EXPECT_TRUE(s.ToString().find("WriteOptions::rate_limiter_priority") != - std::string::npos); - } - - std::int64_t actual_auto_wal_flush_request = - options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) - - prev_total_request; - std::int64_t expected_auto_wal_flush_request = valid_arg ? 1 : 0; - - EXPECT_EQ(actual_auto_wal_flush_request, expected_auto_wal_flush_request); - EXPECT_EQ(actual_auto_wal_flush_request, - options_.rate_limiter->GetTotalRequests(Env::IO_USER)); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_readonly_with_timestamp_test.cc b/db/db_readonly_with_timestamp_test.cc deleted file mode 100644 index 675e4943b..000000000 --- a/db/db_readonly_with_timestamp_test.cc +++ /dev/null @@ -1,956 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_with_timestamp_test_util.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { -class DBReadOnlyTestWithTimestamp : public DBBasicTestWithTimestampBase { - public: - DBReadOnlyTestWithTimestamp() - : DBBasicTestWithTimestampBase("db_readonly_test_with_timestamp") {} - - protected: - void CheckDBOpenedAsCompactedDBWithOneLevel0File() { - VersionSet* const versions = dbfull()->GetVersionSet(); - ASSERT_NE(versions, nullptr); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - // Only 1 L0 file. - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - // L0 is the max level. - ASSERT_EQ(storage_info->num_non_empty_levels(), 1); - } - - void CheckDBOpenedAsCompactedDBWithOnlyHighestNonEmptyLevelFiles() { - VersionSet* const versions = dbfull()->GetVersionSet(); - ASSERT_NE(versions, nullptr); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - // L0 has no files. - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - - // All other levels have no files except the highest level with files. - for (int i = 1; i < storage_info->num_non_empty_levels() - 1; ++i) { - ASSERT_FALSE(storage_info->LevelFilesBrief(i).num_files > 0); - } - - // The highest level with files have some files. - int highest_non_empty_level = storage_info->num_non_empty_levels() - 1; - ASSERT_TRUE( - storage_info->LevelFilesBrief(highest_non_empty_level).num_files > 0); - } -}; - -TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGetReadTimestampSizeMismatch) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - std::string different_size_read_timestamp; - PutFixed32(&different_size_read_timestamp, 2); - Slice different_size_read_ts = different_size_read_timestamp; - read_opts.timestamp = &different_size_read_ts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsInvalidArgument()); - } - - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - std::string timestamp; - ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - IteratorAndGetReadTimestampSpecifiedWithoutWriteTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - const std::string read_timestamp = Timestamp(2, 0); - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsInvalidArgument()); - } - - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - std::string timestamp; - ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - IteratorAndGetWriteWithTimestampReadWithoutTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsInvalidArgument()); - } - - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - ASSERT_TRUE( - db_->Get(read_opts, Key1(key), &value_from_get).IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, IteratorAndGet) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - - auto get_value_and_check = [](DB* db, ReadOptions read_opts, Slice key, - Slice expected_value, std::string expected_ts) { - std::string value_from_get; - std::string timestamp; - ASSERT_OK(db->Get(read_opts, key.ToString(), &value_from_get, ×tamp)); - ASSERT_EQ(expected_value, value_from_get); - ASSERT_EQ(expected_ts, timestamp); - }; - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = 0; - // Forward iterate. - for (it->Seek(Key1(0)), key = start_keys[i]; it->Valid(); - it->Next(), ++count, ++key) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - size_t expected_count = kMaxKey - start_keys[i] + 1; - ASSERT_EQ(expected_count, count); - - // Backward iterate. - count = 0; - for (it->SeekForPrev(Key1(kMaxKey)), key = kMaxKey; it->Valid(); - it->Prev(), ++count, --key) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - ASSERT_EQ(static_cast(kMaxKey) - start_keys[i] + 1, count); - - // SeekToFirst()/SeekToLast() with lower/upper bounds. - // Then iter with lower and upper bounds. - uint64_t l = 0; - uint64_t r = kMaxKey + 1; - while (l < r) { - std::string lb_str = Key1(l); - Slice lb = lb_str; - std::string ub_str = Key1(r); - Slice ub = ub_str; - read_opts.iterate_lower_bound = &lb; - read_opts.iterate_upper_bound = &ub; - it.reset(db_->NewIterator(read_opts)); - for (it->SeekToFirst(), key = std::max(l, start_keys[i]), count = 0; - it->Valid(); it->Next(), ++key, ++count) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - ASSERT_EQ(r - std::max(l, start_keys[i]), count); - - for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0; - it->Valid(); it->Prev(), --key, ++count) { - CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - l += (kMaxKey / 100); - r -= (kMaxKey / 100); - } - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, Iterators) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - const std::string read_timestamp = Timestamp(2, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - std::vector iters; - ASSERT_OK(db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters)); - ASSERT_EQ(static_cast(1), iters.size()); - - int count = 0; - uint64_t key = 0; - // Forward iterate. - for (iters[0]->Seek(Key1(0)), key = 0; iters[0]->Valid(); - iters[0]->Next(), ++count, ++key) { - CheckIterUserEntry(iters[0], Key1(key), kTypeValue, - "value" + std::to_string(key), write_timestamp); - } - - size_t expected_count = kMaxKey - 0 + 1; - ASSERT_EQ(expected_count, count); - delete iters[0]; - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, IteratorsReadTimestampSizeMismatch) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - std::string different_size_read_timestamp; - PutFixed32(&different_size_read_timestamp, 2); - Slice different_size_read_ts = different_size_read_timestamp; - read_opts.timestamp = &different_size_read_ts; - { - std::vector iters; - ASSERT_TRUE( - db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - IteratorsReadTimestampSpecifiedWithoutWriteTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - const std::string read_timestamp = Timestamp(2, 0); - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - { - std::vector iters; - ASSERT_TRUE( - db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - IteratorsWriteWithTimestampReadWithoutTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database in read only mode to test its timestamp support. - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - ReadOptions read_opts; - { - std::vector iters; - ASSERT_TRUE( - db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, CompactedDBGetReadTimestampSizeMismatch) { - const int kNumKeysPerFile = 1026; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(0)); - ASSERT_OK(s); - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - ReadOptions read_opts; - std::string different_size_read_timestamp; - PutFixed32(&different_size_read_timestamp, 2); - Slice different_size_read_ts = different_size_read_timestamp; - read_opts.timestamp = &different_size_read_ts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - std::string timestamp; - ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp) - .IsInvalidArgument()); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBGetReadTimestampSpecifiedWithoutWriteTimestamp) { - const int kNumKeysPerFile = 1026; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(0)); - ASSERT_OK(s); - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - ReadOptions read_opts; - const std::string read_timestamp = Timestamp(2, 0); - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - std::string timestamp; - ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp) - .IsInvalidArgument()); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBGetWriteWithTimestampReadWithoutTimestamp) { - const int kNumKeysPerFile = 1026; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(0)); - ASSERT_OK(s); - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - ReadOptions read_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - ASSERT_TRUE( - db_->Get(read_opts, Key1(key), &value_from_get).IsInvalidArgument()); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, CompactedDBGetWithOnlyOneL0File) { - const int kNumKeysPerFile = 1026 * 2; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - int count = 0; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key, ++count) { - std::string value_from_get; - std::string timestamp; - ASSERT_OK(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp)); - ASSERT_EQ("value" + std::to_string(i), value_from_get); - ASSERT_EQ(write_timestamps[i], timestamp); - } - size_t expected_count = kMaxKey - start_keys[i] + 1; - ASSERT_EQ(expected_count, count); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBGetWithOnlyHighestNonEmptyLevelFiles) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOnlyHighestNonEmptyLevelFiles(); - - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - int count = 0; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key, ++count) { - std::string value_from_get; - std::string timestamp; - ASSERT_OK(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp)); - ASSERT_EQ("value" + std::to_string(i), value_from_get); - ASSERT_EQ(write_timestamps[i], timestamp); - } - size_t expected_count = kMaxKey - start_keys[i] + 1; - ASSERT_EQ(expected_count, count); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBMultiGetReadTimestampSizeMismatch) { - const int kNumKeysPerFile = 1026; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(0)); - ASSERT_OK(s); - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - ReadOptions read_opts; - std::string different_size_read_timestamp; - PutFixed32(&different_size_read_timestamp, 2); - Slice different_size_read_ts = different_size_read_timestamp; - read_opts.timestamp = &different_size_read_ts; - std::vector key_strs; - std::vector keys; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - key_strs.push_back(Key1(key)); - } - for (const auto& key_str : key_strs) { - keys.emplace_back(key_str); - } - std::vector values; - std::vector timestamps; - std::vector status_list = - db_->MultiGet(read_opts, keys, &values, ×tamps); - for (const auto& status : status_list) { - ASSERT_TRUE(status.IsInvalidArgument()); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBMultiGetReadTimestampSpecifiedWithoutWriteTimestamp) { - const int kNumKeysPerFile = 1026; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(0)); - ASSERT_OK(s); - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - ReadOptions read_opts; - std::string read_timestamp = Timestamp(2, 0); - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - std::vector key_strs; - std::vector keys; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - key_strs.push_back(Key1(key)); - } - for (const auto& key_str : key_strs) { - keys.emplace_back(key_str); - } - std::vector values; - std::vector timestamps; - std::vector status_list = - db_->MultiGet(read_opts, keys, &values, ×tamps); - for (const auto& status : status_list) { - ASSERT_TRUE(status.IsInvalidArgument()); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBMultiGetWriteWithTimestampReadWithoutTimestamp) { - const int kNumKeysPerFile = 1026; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(0)); - ASSERT_OK(s); - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - ReadOptions read_opts; - std::vector key_strs; - std::vector keys; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - key_strs.push_back(Key1(key)); - } - for (const auto& key_str : key_strs) { - keys.emplace_back(key_str); - } - std::vector values; - std::vector status_list = db_->MultiGet(read_opts, keys, &values); - for (const auto& status : status_list) { - ASSERT_TRUE(status.IsInvalidArgument()); - } - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, CompactedDBMultiGetWithOnlyOneL0File) { - const int kNumKeysPerFile = 1026 * 2; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOneLevel0File(); - - for (size_t i = 0; i < write_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - std::vector key_strs; - std::vector keys; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - key_strs.push_back(Key1(key)); - } - for (const auto& key_str : key_strs) { - keys.emplace_back(key_str); - } - size_t batch_size = kMaxKey - start_keys[i] + 1; - std::vector values; - std::vector timestamps; - std::vector status_list = - db_->MultiGet(read_opts, keys, &values, ×tamps); - ASSERT_EQ(batch_size, values.size()); - ASSERT_EQ(batch_size, timestamps.size()); - for (uint64_t idx = 0; idx < values.size(); ++idx) { - ASSERT_EQ("value" + std::to_string(i), values[idx]); - ASSERT_EQ(write_timestamps[i], timestamps[idx]); - ASSERT_OK(status_list[idx]); - } - } - - Close(); -} - -TEST_F(DBReadOnlyTestWithTimestamp, - CompactedDBMultiGetWithOnlyHighestNonEmptyLevelFiles) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.disable_auto_compactions = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - Close(); - - // Reopen the database in read only mode as a Compacted DB to test its - // timestamp support. - options.max_open_files = -1; - ASSERT_OK(ReadOnlyReopen(options)); - CheckDBOpenedAsCompactedDBWithOnlyHighestNonEmptyLevelFiles(); - - for (size_t i = 0; i < write_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - std::vector key_strs; - std::vector keys; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - key_strs.push_back(Key1(key)); - } - for (const auto& key_str : key_strs) { - keys.emplace_back(key_str); - } - size_t batch_size = kMaxKey - start_keys[i] + 1; - std::vector values; - std::vector timestamps; - std::vector status_list = - db_->MultiGet(read_opts, keys, &values, ×tamps); - ASSERT_EQ(batch_size, values.size()); - ASSERT_EQ(batch_size, timestamps.size()); - for (uint64_t idx = 0; idx < values.size(); ++idx) { - ASSERT_EQ("value" + std::to_string(i), values[idx]); - ASSERT_EQ(write_timestamps[i], timestamps[idx]); - ASSERT_OK(status_list[idx]); - } - } - - Close(); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_secondary_test.cc b/db/db_secondary_test.cc deleted file mode 100644 index f3f0a8d05..000000000 --- a/db/db_secondary_test.cc +++ /dev/null @@ -1,1691 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_impl/db_impl_secondary.h" -#include "db/db_test_util.h" -#include "db/db_with_timestamp_test_util.h" -#include "port/stack_trace.h" -#include "rocksdb/utilities/transaction_db.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "utilities/fault_injection_env.h" - -namespace ROCKSDB_NAMESPACE { - -class DBSecondaryTestBase : public DBBasicTestWithTimestampBase { - public: - explicit DBSecondaryTestBase(const std::string& dbname) - : DBBasicTestWithTimestampBase(dbname), - secondary_path_(), - handles_secondary_(), - db_secondary_(nullptr) { - secondary_path_ = - test::PerThreadDBPath(env_, "/db_secondary_test_secondary"); - } - - ~DBSecondaryTestBase() override { - CloseSecondary(); - if (getenv("KEEP_DB") != nullptr) { - fprintf(stdout, "Secondary DB is still at %s\n", secondary_path_.c_str()); - } else { - Options options; - options.env = env_; - EXPECT_OK(DestroyDB(secondary_path_, options)); - } - } - - protected: - Status ReopenAsSecondary(const Options& options) { - return DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_); - } - - void OpenSecondary(const Options& options); - - Status TryOpenSecondary(const Options& options); - - void OpenSecondaryWithColumnFamilies( - const std::vector& column_families, const Options& options); - - void CloseSecondary() { - for (auto h : handles_secondary_) { - ASSERT_OK(db_secondary_->DestroyColumnFamilyHandle(h)); - } - handles_secondary_.clear(); - delete db_secondary_; - db_secondary_ = nullptr; - } - - DBImplSecondary* db_secondary_full() { - return static_cast(db_secondary_); - } - - void CheckFileTypeCounts(const std::string& dir, int expected_log, - int expected_sst, int expected_manifest) const; - - std::string secondary_path_; - std::vector handles_secondary_; - DB* db_secondary_; -}; - -void DBSecondaryTestBase::OpenSecondary(const Options& options) { - ASSERT_OK(TryOpenSecondary(options)); -} - -Status DBSecondaryTestBase::TryOpenSecondary(const Options& options) { - Status s = - DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_); - return s; -} - -void DBSecondaryTestBase::OpenSecondaryWithColumnFamilies( - const std::vector& column_families, const Options& options) { - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, options); - for (const auto& cf_name : column_families) { - cf_descs.emplace_back(cf_name, options); - } - Status s = DB::OpenAsSecondary(options, dbname_, secondary_path_, cf_descs, - &handles_secondary_, &db_secondary_); - ASSERT_OK(s); -} - -void DBSecondaryTestBase::CheckFileTypeCounts(const std::string& dir, - int expected_log, - int expected_sst, - int expected_manifest) const { - std::vector filenames; - ASSERT_OK(env_->GetChildren(dir, &filenames)); - - int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0; - for (auto file : filenames) { - uint64_t number; - FileType type; - if (ParseFileName(file, &number, &type)) { - log_cnt += (type == kWalFile); - sst_cnt += (type == kTableFile); - manifest_cnt += (type == kDescriptorFile); - } - } - ASSERT_EQ(expected_log, log_cnt); - ASSERT_EQ(expected_sst, sst_cnt); - ASSERT_EQ(expected_manifest, manifest_cnt); -} - -class DBSecondaryTest : public DBSecondaryTestBase { - public: - explicit DBSecondaryTest() : DBSecondaryTestBase("db_secondary_test") {} -}; - -TEST_F(DBSecondaryTest, FailOpenIfLoggerCreationFail) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - Reopen(options); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "rocksdb::CreateLoggerFromOptions:AfterGetPath", [&](void* arg) { - auto* s = reinterpret_cast(arg); - assert(s); - *s = Status::IOError("Injected"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - options.max_open_files = -1; - Status s = TryOpenSecondary(options); - ASSERT_EQ(nullptr, options.info_log); - ASSERT_TRUE(s.IsIOError()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBSecondaryTest, NonExistingDb) { - Destroy(last_options_); - - Options options = GetDefaultOptions(); - options.env = env_; - options.max_open_files = -1; - const std::string dbname = "/doesnt/exist"; - Status s = - DB::OpenAsSecondary(options, dbname, secondary_path_, &db_secondary_); - ASSERT_TRUE(s.IsIOError()); -} - -TEST_F(DBSecondaryTest, ReopenAsSecondary) { - Options options; - options.env = env_; - Reopen(options); - ASSERT_OK(Put("foo", "foo_value")); - ASSERT_OK(Put("bar", "bar_value")); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - Close(); - - ASSERT_OK(ReopenAsSecondary(options)); - ASSERT_EQ("foo_value", Get("foo")); - ASSERT_EQ("bar_value", Get("bar")); - ReadOptions ropts; - ropts.verify_checksums = true; - auto db1 = static_cast(db_); - ASSERT_NE(nullptr, db1); - Iterator* iter = db1->NewIterator(ropts); - ASSERT_NE(nullptr, iter); - size_t count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - if (0 == count) { - ASSERT_EQ("bar", iter->key().ToString()); - ASSERT_EQ("bar_value", iter->value().ToString()); - } else if (1 == count) { - ASSERT_EQ("foo", iter->key().ToString()); - ASSERT_EQ("foo_value", iter->value().ToString()); - } - ++count; - } - delete iter; - ASSERT_EQ(2, count); -} - -TEST_F(DBSecondaryTest, SimpleInternalCompaction) { - Options options; - options.env = env_; - Reopen(options); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - CompactionServiceInput input; - - ColumnFamilyMetaData meta; - db_->GetColumnFamilyMetaData(&meta); - for (auto& file : meta.levels[0].files) { - ASSERT_EQ(0, meta.levels[0].level); - input.input_files.push_back(file.name); - } - ASSERT_EQ(input.input_files.size(), 3); - - input.output_level = 1; - ASSERT_OK(db_->GetDbIdentity(input.db_id)); - Close(); - - options.max_open_files = -1; - OpenSecondary(options); - auto cfh = db_secondary_->DefaultColumnFamily(); - - CompactionServiceResult result; - ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input, &result)); - - ASSERT_EQ(result.output_files.size(), 1); - InternalKey smallest, largest; - smallest.DecodeFrom(result.output_files[0].smallest_internal_key); - largest.DecodeFrom(result.output_files[0].largest_internal_key); - ASSERT_EQ(smallest.user_key().ToString(), "bar"); - ASSERT_EQ(largest.user_key().ToString(), "foo"); - ASSERT_EQ(result.output_level, 1); - ASSERT_EQ(result.output_path, this->secondary_path_); - ASSERT_EQ(result.num_output_records, 2); - ASSERT_GT(result.bytes_written, 0); - ASSERT_OK(result.status); -} - -TEST_F(DBSecondaryTest, InternalCompactionMultiLevels) { - Options options; - options.env = env_; - options.disable_auto_compactions = true; - Reopen(options); - const int kRangeL2 = 10; - const int kRangeL1 = 30; - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i * kRangeL2), "value" + std::to_string(i))); - ASSERT_OK(Put(Key((i + 1) * kRangeL2 - 1), "value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - for (int i = 0; i < 5; i++) { - ASSERT_OK(Put(Key(i * kRangeL1), "value" + std::to_string(i))); - ASSERT_OK(Put(Key((i + 1) * kRangeL1 - 1), "value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(1); - for (int i = 0; i < 4; i++) { - ASSERT_OK(Put(Key(i * 30), "value" + std::to_string(i))); - ASSERT_OK(Put(Key(i * 30 + 50), "value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - - ColumnFamilyMetaData meta; - db_->GetColumnFamilyMetaData(&meta); - - // pick 2 files on level 0 for compaction, which has 3 overlap files on L1 - CompactionServiceInput input1; - input1.input_files.push_back(meta.levels[0].files[2].name); - input1.input_files.push_back(meta.levels[0].files[3].name); - input1.input_files.push_back(meta.levels[1].files[0].name); - input1.input_files.push_back(meta.levels[1].files[1].name); - input1.input_files.push_back(meta.levels[1].files[2].name); - - input1.output_level = 1; - ASSERT_OK(db_->GetDbIdentity(input1.db_id)); - - options.max_open_files = -1; - Close(); - - OpenSecondary(options); - auto cfh = db_secondary_->DefaultColumnFamily(); - CompactionServiceResult result; - ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input1, &result)); - ASSERT_OK(result.status); - - // pick 2 files on level 1 for compaction, which has 6 overlap files on L2 - CompactionServiceInput input2; - input2.input_files.push_back(meta.levels[1].files[1].name); - input2.input_files.push_back(meta.levels[1].files[2].name); - for (int i = 3; i < 9; i++) { - input2.input_files.push_back(meta.levels[2].files[i].name); - } - - input2.output_level = 2; - input2.db_id = input1.db_id; - ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input2, &result)); - ASSERT_OK(result.status); - - CloseSecondary(); - - // delete all l2 files, without update manifest - for (auto& file : meta.levels[2].files) { - ASSERT_OK(env_->DeleteFile(dbname_ + file.name)); - } - OpenSecondary(options); - cfh = db_secondary_->DefaultColumnFamily(); - Status s = db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input2, &result); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK(result.status); - - // TODO: L0 -> L1 compaction should success, currently version is not built - // if files is missing. - // ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation(OpenAndCompactOptions(), - // cfh, input1, &result)); -} - -TEST_F(DBSecondaryTest, InternalCompactionCompactedFiles) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - Reopen(options); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - CompactionServiceInput input; - - ColumnFamilyMetaData meta; - db_->GetColumnFamilyMetaData(&meta); - for (auto& file : meta.levels[0].files) { - ASSERT_EQ(0, meta.levels[0].level); - input.input_files.push_back(file.name); - } - ASSERT_EQ(input.input_files.size(), 3); - - input.output_level = 1; - ASSERT_OK(db_->GetDbIdentity(input.db_id)); - - // trigger compaction to delete the files for secondary instance compaction - ASSERT_OK(Put("foo", "foo_value" + std::to_string(3))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(3))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - Close(); - - options.max_open_files = -1; - OpenSecondary(options); - auto cfh = db_secondary_->DefaultColumnFamily(); - - CompactionServiceResult result; - Status s = db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input, &result); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK(result.status); -} - -TEST_F(DBSecondaryTest, InternalCompactionMissingFiles) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - Reopen(options); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - CompactionServiceInput input; - - ColumnFamilyMetaData meta; - db_->GetColumnFamilyMetaData(&meta); - for (auto& file : meta.levels[0].files) { - ASSERT_EQ(0, meta.levels[0].level); - input.input_files.push_back(file.name); - } - ASSERT_EQ(input.input_files.size(), 3); - - input.output_level = 1; - ASSERT_OK(db_->GetDbIdentity(input.db_id)); - - Close(); - - ASSERT_OK(env_->DeleteFile(dbname_ + input.input_files[0])); - - options.max_open_files = -1; - OpenSecondary(options); - auto cfh = db_secondary_->DefaultColumnFamily(); - - CompactionServiceResult result; - Status s = db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input, &result); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK(result.status); - - input.input_files.erase(input.input_files.begin()); - - ASSERT_OK(db_secondary_full()->TEST_CompactWithoutInstallation( - OpenAndCompactOptions(), cfh, input, &result)); - ASSERT_OK(result.status); -} - -TEST_F(DBSecondaryTest, OpenAsSecondary) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - Reopen(options); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - ASSERT_OK(Flush()); - } - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ReadOptions ropts; - ropts.verify_checksums = true; - const auto verify_db_func = [&](const std::string& foo_val, - const std::string& bar_val) { - std::string value; - ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); - ASSERT_EQ(foo_val, value); - ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); - ASSERT_EQ(bar_val, value); - Iterator* iter = db_secondary_->NewIterator(ropts); - ASSERT_NE(nullptr, iter); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - ASSERT_EQ(foo_val, iter->value().ToString()); - iter->Seek("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bar", iter->key().ToString()); - ASSERT_EQ(bar_val, iter->value().ToString()); - size_t count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ++count; - } - ASSERT_EQ(2, count); - delete iter; - }; - - verify_db_func("foo_value2", "bar_value2"); - - ASSERT_OK(Put("foo", "new_foo_value")); - ASSERT_OK(Put("bar", "new_bar_value")); - ASSERT_OK(Flush()); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db_func("new_foo_value", "new_bar_value"); -} - -namespace { -class TraceFileEnv : public EnvWrapper { - public: - explicit TraceFileEnv(Env* _target) : EnvWrapper(_target) {} - static const char* kClassName() { return "TraceFileEnv"; } - const char* Name() const override { return kClassName(); } - - Status NewRandomAccessFile(const std::string& f, - std::unique_ptr* r, - const EnvOptions& env_options) override { - class TracedRandomAccessFile : public RandomAccessFile { - public: - TracedRandomAccessFile(std::unique_ptr&& target, - std::atomic& counter) - : target_(std::move(target)), files_closed_(counter) {} - ~TracedRandomAccessFile() override { - files_closed_.fetch_add(1, std::memory_order_relaxed); - } - Status Read(uint64_t offset, size_t n, Slice* result, - char* scratch) const override { - return target_->Read(offset, n, result, scratch); - } - - private: - std::unique_ptr target_; - std::atomic& files_closed_; - }; - Status s = target()->NewRandomAccessFile(f, r, env_options); - if (s.ok()) { - r->reset(new TracedRandomAccessFile(std::move(*r), files_closed_)); - } - return s; - } - - int files_closed() const { - return files_closed_.load(std::memory_order_relaxed); - } - - private: - std::atomic files_closed_{0}; -}; -} // anonymous namespace - -TEST_F(DBSecondaryTest, SecondaryCloseFiles) { - Options options; - options.env = env_; - options.max_open_files = 1; - options.disable_auto_compactions = true; - Reopen(options); - Options options1; - std::unique_ptr traced_env(new TraceFileEnv(env_)); - options1.env = traced_env.get(); - OpenSecondary(options1); - - static const auto verify_db = [&]() { - std::unique_ptr iter1(dbfull()->NewIterator(ReadOptions())); - std::unique_ptr iter2(db_secondary_->NewIterator(ReadOptions())); - for (iter1->SeekToFirst(), iter2->SeekToFirst(); - iter1->Valid() && iter2->Valid(); iter1->Next(), iter2->Next()) { - ASSERT_EQ(iter1->key(), iter2->key()); - ASSERT_EQ(iter1->value(), iter2->value()); - } - ASSERT_FALSE(iter1->Valid()); - ASSERT_FALSE(iter2->Valid()); - }; - - ASSERT_OK(Put("a", "value")); - ASSERT_OK(Put("c", "value")); - ASSERT_OK(Flush()); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db(); - - ASSERT_OK(Put("b", "value")); - ASSERT_OK(Put("d", "value")); - ASSERT_OK(Flush()); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db(); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - ASSERT_EQ(2, static_cast(traced_env.get())->files_closed()); - - Status s = db_secondary_->SetDBOptions({{"max_open_files", "-1"}}); - ASSERT_TRUE(s.IsNotSupported()); - CloseSecondary(); -} - -TEST_F(DBSecondaryTest, OpenAsSecondaryWALTailing) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - Reopen(options); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - } - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - - ReadOptions ropts; - ropts.verify_checksums = true; - const auto verify_db_func = [&](const std::string& foo_val, - const std::string& bar_val) { - std::string value; - ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); - ASSERT_EQ(foo_val, value); - ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); - ASSERT_EQ(bar_val, value); - Iterator* iter = db_secondary_->NewIterator(ropts); - ASSERT_NE(nullptr, iter); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - ASSERT_EQ(foo_val, iter->value().ToString()); - iter->Seek("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bar", iter->key().ToString()); - ASSERT_EQ(bar_val, iter->value().ToString()); - size_t count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ++count; - } - ASSERT_EQ(2, count); - delete iter; - }; - - verify_db_func("foo_value2", "bar_value2"); - - ASSERT_OK(Put("foo", "new_foo_value")); - ASSERT_OK(Put("bar", "new_bar_value")); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db_func("new_foo_value", "new_bar_value"); - - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "new_foo_value_1")); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db_func("new_foo_value_1", "new_bar_value"); -} - -TEST_F(DBSecondaryTest, SecondaryTailingBug_ISSUE_8467) { - Options options; - options.env = env_; - Reopen(options); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - } - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - - const auto verify_db = [&](const std::string& foo_val, - const std::string& bar_val) { - std::string value; - ReadOptions ropts; - Status s = db_secondary_->Get(ropts, "foo", &value); - ASSERT_OK(s); - ASSERT_EQ(foo_val, value); - - s = db_secondary_->Get(ropts, "bar", &value); - ASSERT_OK(s); - ASSERT_EQ(bar_val, value); - }; - - for (int i = 0; i < 2; ++i) { - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db("foo_value2", "bar_value2"); - } -} - -TEST_F(DBSecondaryTest, RefreshIterator) { - Options options; - options.env = env_; - Reopen(options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - - std::unique_ptr it(db_secondary_->NewIterator(ReadOptions())); - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - if (0 == i) { - it->Seek("foo"); - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - - ASSERT_OK(it->Refresh()); - - it->Seek("foo"); - ASSERT_OK(it->status()); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("foo", it->key()); - ASSERT_EQ("foo_value0", it->value()); - } else { - it->Seek("foo"); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("foo", it->key()); - ASSERT_EQ("foo_value" + std::to_string(i - 1), it->value()); - ASSERT_OK(it->status()); - - ASSERT_OK(it->Refresh()); - - it->Seek("foo"); - ASSERT_OK(it->status()); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("foo", it->key()); - ASSERT_EQ("foo_value" + std::to_string(i), it->value()); - } - } -} - -TEST_F(DBSecondaryTest, OpenWithNonExistColumnFamily) { - Options options; - options.env = env_; - CreateAndReopenWithCF({"pikachu"}, options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, options1); - cf_descs.emplace_back("pikachu", options1); - cf_descs.emplace_back("eevee", options1); - Status s = DB::OpenAsSecondary(options1, dbname_, secondary_path_, cf_descs, - &handles_secondary_, &db_secondary_); - ASSERT_NOK(s); -} - -TEST_F(DBSecondaryTest, OpenWithSubsetOfColumnFamilies) { - Options options; - options.env = env_; - CreateAndReopenWithCF({"pikachu"}, options); - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - ASSERT_EQ(0, handles_secondary_.size()); - ASSERT_NE(nullptr, db_secondary_); - - ASSERT_OK(Put(0 /*cf*/, "foo", "foo_value")); - ASSERT_OK(Put(1 /*cf*/, "foo", "foo_value")); - ASSERT_OK(Flush(0 /*cf*/)); - ASSERT_OK(Flush(1 /*cf*/)); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - ReadOptions ropts; - ropts.verify_checksums = true; - std::string value; - ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); - ASSERT_EQ("foo_value", value); -} - -TEST_F(DBSecondaryTest, SwitchToNewManifestDuringOpen) { - Options options; - options.env = env_; - Reopen(options); - Close(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency( - {{"ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:0", - "VersionSet::ProcessManifestWrites:BeforeNewManifest"}, - {"DBImpl::Open:AfterDeleteFiles", - "ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:" - "1"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - port::Thread ro_db_thread([&]() { - Options options1; - options1.env = env_; - options1.max_open_files = -1; - Status s = TryOpenSecondary(options1); - ASSERT_TRUE(s.IsTryAgain()); - - // Try again - OpenSecondary(options1); - CloseSecondary(); - }); - Reopen(options); - ro_db_thread.join(); -} - -TEST_F(DBSecondaryTest, MissingTableFileDuringOpen) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - Reopen(options); - for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - ReadOptions ropts; - ropts.verify_checksums = true; - std::string value; - ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); - ASSERT_EQ("foo_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - value); - ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); - ASSERT_EQ("bar_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - value); - Iterator* iter = db_secondary_->NewIterator(ropts); - ASSERT_NE(nullptr, iter); - iter->Seek("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bar", iter->key().ToString()); - ASSERT_EQ("bar_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - iter->value().ToString()); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - ASSERT_EQ("foo_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - iter->value().ToString()); - size_t count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ++count; - } - ASSERT_EQ(2, count); - delete iter; -} - -TEST_F(DBSecondaryTest, MissingTableFile) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - Reopen(options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - - for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) { - ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); - ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_NE(nullptr, db_secondary_full()); - ReadOptions ropts; - ropts.verify_checksums = true; - std::string value; - ASSERT_NOK(db_secondary_->Get(ropts, "foo", &value)); - ASSERT_NOK(db_secondary_->Get(ropts, "bar", &value)); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); - ASSERT_EQ("foo_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - value); - ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); - ASSERT_EQ("bar_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - value); - Iterator* iter = db_secondary_->NewIterator(ropts); - ASSERT_NE(nullptr, iter); - iter->Seek("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bar", iter->key().ToString()); - ASSERT_EQ("bar_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - iter->value().ToString()); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key().ToString()); - ASSERT_EQ("foo_value" + - std::to_string(options.level0_file_num_compaction_trigger - 1), - iter->value().ToString()); - size_t count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ++count; - } - ASSERT_EQ(2, count); - delete iter; -} - -TEST_F(DBSecondaryTest, PrimaryDropColumnFamily) { - Options options; - options.env = env_; - const std::string kCfName1 = "pikachu"; - CreateAndReopenWithCF({kCfName1}, options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondaryWithColumnFamilies({kCfName1}, options1); - ASSERT_EQ(2, handles_secondary_.size()); - - ASSERT_OK(Put(1 /*cf*/, "foo", "foo_val_1")); - ASSERT_OK(Flush(1 /*cf*/)); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - ReadOptions ropts; - ropts.verify_checksums = true; - std::string value; - ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value)); - ASSERT_EQ("foo_val_1", value); - - ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); - Close(); - CheckFileTypeCounts(dbname_, 1, 0, 1); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - value.clear(); - ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value)); - ASSERT_EQ("foo_val_1", value); -} - -TEST_F(DBSecondaryTest, SwitchManifest) { - Options options; - options.env = env_; - options.level0_file_num_compaction_trigger = 4; - const std::string cf1_name("test_cf"); - CreateAndReopenWithCF({cf1_name}, options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondaryWithColumnFamilies({kDefaultColumnFamilyName, cf1_name}, - options1); - - const int kNumFiles = options.level0_file_num_compaction_trigger - 1; - // Keep it smaller than 10 so that key0, key1, ..., key9 are sorted as 0, 1, - // ..., 9. - const int kNumKeys = 10; - // Create two sst - for (int i = 0; i != kNumFiles; ++i) { - for (int j = 0; j != kNumKeys; ++j) { - ASSERT_OK(Put("key" + std::to_string(j), "value_" + std::to_string(i))); - } - ASSERT_OK(Flush()); - } - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - const auto& range_scan_db = [&]() { - ReadOptions tmp_ropts; - tmp_ropts.total_order_seek = true; - tmp_ropts.verify_checksums = true; - std::unique_ptr iter(db_secondary_->NewIterator(tmp_ropts)); - int cnt = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++cnt) { - ASSERT_EQ("key" + std::to_string(cnt), iter->key().ToString()); - ASSERT_EQ("value_" + std::to_string(kNumFiles - 1), - iter->value().ToString()); - } - }; - - range_scan_db(); - - // While secondary instance still keeps old MANIFEST open, we close primary, - // restart primary, performs full compaction, close again, restart again so - // that next time secondary tries to catch up with primary, the secondary - // will skip the MANIFEST in middle. - ReopenWithColumnFamilies({kDefaultColumnFamilyName, cf1_name}, options); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, cf1_name}, options); - ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}})); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - range_scan_db(); -} - -TEST_F(DBSecondaryTest, SwitchManifestTwice) { - Options options; - options.env = env_; - options.disable_auto_compactions = true; - const std::string cf1_name("test_cf"); - CreateAndReopenWithCF({cf1_name}, options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondaryWithColumnFamilies({kDefaultColumnFamilyName, cf1_name}, - options1); - - ASSERT_OK(Put("0", "value0")); - ASSERT_OK(Flush()); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - std::string value; - ReadOptions ropts; - ropts.verify_checksums = true; - ASSERT_OK(db_secondary_->Get(ropts, "0", &value)); - ASSERT_EQ("value0", value); - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, cf1_name}, options); - ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}})); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, cf1_name}, options); - ASSERT_OK(Put("0", "value1")); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - - ASSERT_OK(db_secondary_->Get(ropts, "0", &value)); - ASSERT_EQ("value1", value); -} - -TEST_F(DBSecondaryTest, DISABLED_SwitchWAL) { - const int kNumKeysPerMemtable = 1; - Options options; - options.env = env_; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 2; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerMemtable)); - Reopen(options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - - const auto& verify_db = [](DB* db1, DB* db2) { - ASSERT_NE(nullptr, db1); - ASSERT_NE(nullptr, db2); - ReadOptions read_opts; - read_opts.verify_checksums = true; - std::unique_ptr it1(db1->NewIterator(read_opts)); - std::unique_ptr it2(db2->NewIterator(read_opts)); - it1->SeekToFirst(); - it2->SeekToFirst(); - for (; it1->Valid() && it2->Valid(); it1->Next(), it2->Next()) { - ASSERT_EQ(it1->key(), it2->key()); - ASSERT_EQ(it1->value(), it2->value()); - } - ASSERT_FALSE(it1->Valid()); - ASSERT_FALSE(it2->Valid()); - - for (it1->SeekToFirst(); it1->Valid(); it1->Next()) { - std::string value; - ASSERT_OK(db2->Get(read_opts, it1->key(), &value)); - ASSERT_EQ(it1->value(), value); - } - for (it2->SeekToFirst(); it2->Valid(); it2->Next()) { - std::string value; - ASSERT_OK(db1->Get(read_opts, it2->key(), &value)); - ASSERT_EQ(it2->value(), value); - } - }; - for (int k = 0; k != 16; ++k) { - ASSERT_OK(Put("key" + std::to_string(k), "value" + std::to_string(k))); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db(dbfull(), db_secondary_); - } -} - -TEST_F(DBSecondaryTest, DISABLED_SwitchWALMultiColumnFamilies) { - const int kNumKeysPerMemtable = 1; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCallFlush:ContextCleanedUp", - "DBSecondaryTest::SwitchWALMultipleColumnFamilies:BeforeCatchUp"}}); - SyncPoint::GetInstance()->EnableProcessing(); - const std::string kCFName1 = "pikachu"; - Options options; - options.env = env_; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 2; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerMemtable)); - CreateAndReopenWithCF({kCFName1}, options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondaryWithColumnFamilies({kCFName1}, options1); - ASSERT_EQ(2, handles_secondary_.size()); - - const auto& verify_db = [](DB* db1, - const std::vector& handles1, - DB* db2, - const std::vector& handles2) { - ASSERT_NE(nullptr, db1); - ASSERT_NE(nullptr, db2); - ReadOptions read_opts; - read_opts.verify_checksums = true; - ASSERT_EQ(handles1.size(), handles2.size()); - for (size_t i = 0; i != handles1.size(); ++i) { - std::unique_ptr it1(db1->NewIterator(read_opts, handles1[i])); - std::unique_ptr it2(db2->NewIterator(read_opts, handles2[i])); - it1->SeekToFirst(); - it2->SeekToFirst(); - for (; it1->Valid() && it2->Valid(); it1->Next(), it2->Next()) { - ASSERT_EQ(it1->key(), it2->key()); - ASSERT_EQ(it1->value(), it2->value()); - } - ASSERT_FALSE(it1->Valid()); - ASSERT_FALSE(it2->Valid()); - - for (it1->SeekToFirst(); it1->Valid(); it1->Next()) { - std::string value; - ASSERT_OK(db2->Get(read_opts, handles2[i], it1->key(), &value)); - ASSERT_EQ(it1->value(), value); - } - for (it2->SeekToFirst(); it2->Valid(); it2->Next()) { - std::string value; - ASSERT_OK(db1->Get(read_opts, handles1[i], it2->key(), &value)); - ASSERT_EQ(it2->value(), value); - } - } - }; - for (int k = 0; k != 8; ++k) { - for (int j = 0; j < 2; ++j) { - ASSERT_OK(Put(0 /*cf*/, "key" + std::to_string(k), - "value" + std::to_string(k))); - ASSERT_OK(Put(1 /*cf*/, "key" + std::to_string(k), - "value" + std::to_string(k))); - } - TEST_SYNC_POINT( - "DBSecondaryTest::SwitchWALMultipleColumnFamilies:BeforeCatchUp"); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - verify_db(dbfull(), handles_, db_secondary_, handles_secondary_); - SyncPoint::GetInstance()->ClearTrace(); - } -} - -TEST_F(DBSecondaryTest, CatchUpAfterFlush) { - const int kNumKeysPerMemtable = 16; - Options options; - options.env = env_; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 2; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerMemtable)); - Reopen(options); - - Options options1; - options1.env = env_; - options1.max_open_files = -1; - OpenSecondary(options1); - - WriteOptions write_opts; - WriteBatch wb; - ASSERT_OK(wb.Put("key0", "value0")); - ASSERT_OK(wb.Put("key1", "value1")); - ASSERT_OK(dbfull()->Write(write_opts, &wb)); - ReadOptions read_opts; - std::unique_ptr iter1(db_secondary_->NewIterator(read_opts)); - iter1->Seek("key0"); - ASSERT_FALSE(iter1->Valid()); - iter1->Seek("key1"); - ASSERT_FALSE(iter1->Valid()); - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - iter1->Seek("key0"); - ASSERT_FALSE(iter1->Valid()); - iter1->Seek("key1"); - ASSERT_FALSE(iter1->Valid()); - ASSERT_OK(iter1->status()); - std::unique_ptr iter2(db_secondary_->NewIterator(read_opts)); - iter2->Seek("key0"); - ASSERT_TRUE(iter2->Valid()); - ASSERT_EQ("value0", iter2->value()); - iter2->Seek("key1"); - ASSERT_TRUE(iter2->Valid()); - ASSERT_OK(iter2->status()); - ASSERT_EQ("value1", iter2->value()); - - { - WriteBatch wb1; - ASSERT_OK(wb1.Put("key0", "value01")); - ASSERT_OK(wb1.Put("key1", "value11")); - ASSERT_OK(dbfull()->Write(write_opts, &wb1)); - } - - { - WriteBatch wb2; - ASSERT_OK(wb2.Put("key0", "new_value0")); - ASSERT_OK(wb2.Delete("key1")); - ASSERT_OK(dbfull()->Write(write_opts, &wb2)); - } - - ASSERT_OK(Flush()); - - ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); - std::unique_ptr iter3(db_secondary_->NewIterator(read_opts)); - // iter3 should not see value01 and value11 at all. - iter3->Seek("key0"); - ASSERT_TRUE(iter3->Valid()); - ASSERT_EQ("new_value0", iter3->value()); - iter3->Seek("key1"); - ASSERT_FALSE(iter3->Valid()); - ASSERT_OK(iter3->status()); -} - -TEST_F(DBSecondaryTest, CheckConsistencyWhenOpen) { - bool called = false; - Options options; - options.env = env_; - options.disable_auto_compactions = true; - Reopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "DBImplSecondary::CheckConsistency:AfterFirstAttempt", [&](void* arg) { - ASSERT_NE(nullptr, arg); - called = true; - auto* s = reinterpret_cast(arg); - ASSERT_NOK(*s); - }); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::CheckConsistency:AfterGetLiveFilesMetaData", - "BackgroundCallCompaction:0"}, - {"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles", - "DBImpl::CheckConsistency:BeforeGetFileSize"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("a", "value0")); - ASSERT_OK(Put("c", "value0")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("b", "value1")); - ASSERT_OK(Put("d", "value1")); - ASSERT_OK(Flush()); - port::Thread thread([this]() { - Options opts; - opts.env = env_; - opts.max_open_files = -1; - OpenSecondary(opts); - }); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - thread.join(); - ASSERT_TRUE(called); -} - -TEST_F(DBSecondaryTest, StartFromInconsistent) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Flush()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) { - ASSERT_NE(nullptr, arg); - *(reinterpret_cast(arg)) = - Status::Corruption("Inject corruption"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - Options options1; - options1.env = env_; - Status s = TryOpenSecondary(options1); - ASSERT_TRUE(s.IsCorruption()); -} - -TEST_F(DBSecondaryTest, InconsistencyDuringCatchUp) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(Flush()); - - Options options1; - options1.env = env_; - OpenSecondary(options1); - - { - std::string value; - ASSERT_OK(db_secondary_->Get(ReadOptions(), "foo", &value)); - ASSERT_EQ("value", value); - } - - ASSERT_OK(Put("bar", "value1")); - ASSERT_OK(Flush()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) { - ASSERT_NE(nullptr, arg); - *(reinterpret_cast(arg)) = - Status::Corruption("Inject corruption"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - Status s = db_secondary_->TryCatchUpWithPrimary(); - ASSERT_TRUE(s.IsCorruption()); -} - -TEST_F(DBSecondaryTest, OpenWithTransactionDB) { - Options options = CurrentOptions(); - options.create_if_missing = true; - - // Destroy the DB to recreate as a TransactionDB. - Close(); - Destroy(options, true); - - // Create a TransactionDB. - TransactionDB* txn_db = nullptr; - TransactionDBOptions txn_db_opts; - ASSERT_OK(TransactionDB::Open(options, txn_db_opts, dbname_, &txn_db)); - ASSERT_NE(txn_db, nullptr); - db_ = txn_db; - - std::vector cfs = {"new_CF"}; - CreateColumnFamilies(cfs, options); - ASSERT_EQ(handles_.size(), 1); - - WriteOptions wopts; - TransactionOptions txn_opts; - Transaction* txn1 = txn_db->BeginTransaction(wopts, txn_opts, nullptr); - ASSERT_NE(txn1, nullptr); - ASSERT_OK(txn1->Put(handles_[0], "k1", "v1")); - ASSERT_OK(txn1->Commit()); - delete txn1; - - options = CurrentOptions(); - options.max_open_files = -1; - ASSERT_OK(TryOpenSecondary(options)); -} - -class DBSecondaryTestWithTimestamp : public DBSecondaryTestBase { - public: - explicit DBSecondaryTestWithTimestamp() - : DBSecondaryTestBase("db_secondary_test_with_timestamp") {} -}; -TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGetReadTimestampSizeMismatch) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - std::string different_size_read_timestamp; - PutFixed32(&different_size_read_timestamp, 2); - Slice different_size_read_ts = different_size_read_timestamp; - read_opts.timestamp = &different_size_read_ts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsInvalidArgument()); - } - - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - std::string timestamp; - ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, - IteratorAndGetReadTimestampSpecifiedWithoutWriteTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - const std::string read_timestamp = Timestamp(2, 0); - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsInvalidArgument()); - } - - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - std::string timestamp; - ASSERT_TRUE(db_->Get(read_opts, Key1(key), &value_from_get, ×tamp) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, - IteratorAndGetWriteWithTimestampReadWithoutTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsInvalidArgument()); - } - - for (uint64_t key = 0; key <= kMaxKey; ++key) { - std::string value_from_get; - ASSERT_TRUE( - db_->Get(read_opts, Key1(key), &value_from_get).IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, IteratorAndGet) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - auto get_value_and_check = [](DB* db, ReadOptions read_opts, Slice key, - Slice expected_value, std::string expected_ts) { - std::string value_from_get; - std::string timestamp; - ASSERT_OK(db->Get(read_opts, key.ToString(), &value_from_get, ×tamp)); - ASSERT_EQ(expected_value, value_from_get); - ASSERT_EQ(expected_ts, timestamp); - }; - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = 0; - // Forward iterate. - for (it->Seek(Key1(0)), key = start_keys[i]; it->Valid(); - it->Next(), ++count, ++key) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - size_t expected_count = kMaxKey - start_keys[i] + 1; - ASSERT_EQ(expected_count, count); - - // Backward iterate. - count = 0; - for (it->SeekForPrev(Key1(kMaxKey)), key = kMaxKey; it->Valid(); - it->Prev(), ++count, --key) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - ASSERT_EQ(static_cast(kMaxKey) - start_keys[i] + 1, count); - - // SeekToFirst()/SeekToLast() with lower/upper bounds. - // Then iter with lower and upper bounds. - uint64_t l = 0; - uint64_t r = kMaxKey + 1; - while (l < r) { - std::string lb_str = Key1(l); - Slice lb = lb_str; - std::string ub_str = Key1(r); - Slice ub = ub_str; - read_opts.iterate_lower_bound = &lb; - read_opts.iterate_upper_bound = &ub; - it.reset(db_->NewIterator(read_opts)); - for (it->SeekToFirst(), key = std::max(l, start_keys[i]), count = 0; - it->Valid(); it->Next(), ++key, ++count) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - ASSERT_EQ(r - std::max(l, start_keys[i]), count); - - for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0; - it->Valid(); it->Prev(), --key, ++count) { - CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - get_value_and_check(db_, read_opts, it->key(), it->value(), - write_timestamps[i]); - } - l += (kMaxKey / 100); - r -= (kMaxKey / 100); - } - } - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, IteratorsReadTimestampSizeMismatch) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - std::string different_size_read_timestamp; - PutFixed32(&different_size_read_timestamp, 2); - Slice different_size_read_ts = different_size_read_timestamp; - read_opts.timestamp = &different_size_read_ts; - { - std::vector iters; - ASSERT_TRUE( - db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, - IteratorsReadTimestampSpecifiedWithoutWriteTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - const std::string read_timestamp = Timestamp(2, 0); - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - { - std::vector iters; - ASSERT_TRUE( - db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, - IteratorsWriteWithTimestampReadWithoutTimestamp) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - { - std::vector iters; - ASSERT_TRUE( - db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters) - .IsInvalidArgument()); - } - - Close(); -} - -TEST_F(DBSecondaryTestWithTimestamp, Iterators) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::string write_timestamp = Timestamp(1, 0); - const std::string read_timestamp = Timestamp(2, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamp, - "value" + std::to_string(key)); - ASSERT_OK(s); - } - - // Reopen the database as secondary instance to test its timestamp support. - Close(); - options.max_open_files = -1; - ASSERT_OK(ReopenAsSecondary(options)); - - ReadOptions read_opts; - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - std::vector iters; - ASSERT_OK(db_->NewIterators(read_opts, {db_->DefaultColumnFamily()}, &iters)); - ASSERT_EQ(static_cast(1), iters.size()); - - int count = 0; - uint64_t key = 0; - // Forward iterate. - for (iters[0]->Seek(Key1(0)), key = 0; iters[0]->Valid(); - iters[0]->Next(), ++count, ++key) { - CheckIterUserEntry(iters[0], Key1(key), kTypeValue, - "value" + std::to_string(key), write_timestamp); - } - - size_t expected_count = kMaxKey - 0 + 1; - ASSERT_EQ(expected_count, count); - delete iters[0]; - - Close(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_sst_test.cc b/db/db_sst_test.cc deleted file mode 100644 index 11e7f49fa..000000000 --- a/db/db_sst_test.cc +++ /dev/null @@ -1,1864 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "env/mock_env.h" -#include "file/sst_file_manager_impl.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/sst_file_manager.h" -#include "rocksdb/table.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class DBSSTTest : public DBTestBase { - public: - DBSSTTest() : DBTestBase("db_sst_test", /*env_do_fsync=*/true) {} -}; - -// A class which remembers the name of each flushed file. -class FlushedFileCollector : public EventListener { - public: - FlushedFileCollector() {} - ~FlushedFileCollector() override {} - - void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { - std::lock_guard lock(mutex_); - flushed_files_.push_back(info.file_path); - } - - std::vector GetFlushedFiles() { - std::lock_guard lock(mutex_); - std::vector result; - for (auto fname : flushed_files_) { - result.push_back(fname); - } - return result; - } - void ClearFlushedFiles() { - std::lock_guard lock(mutex_); - flushed_files_.clear(); - } - - private: - std::vector flushed_files_; - std::mutex mutex_; -}; - -TEST_F(DBSSTTest, DontDeletePendingOutputs) { - Options options; - options.env = env_; - options.create_if_missing = true; - DestroyAndReopen(options); - - // Every time we write to a table file, call FOF/POF with full DB scan. This - // will make sure our pending_outputs_ protection work correctly - std::function purge_obsolete_files_function = [&]() { - JobContext job_context(0); - dbfull()->TEST_LockMutex(); - dbfull()->FindObsoleteFiles(&job_context, true /*force*/); - dbfull()->TEST_UnlockMutex(); - dbfull()->PurgeObsoleteFiles(job_context); - job_context.Clean(); - }; - - env_->table_write_callback_ = &purge_obsolete_files_function; - - for (int i = 0; i < 2; ++i) { - ASSERT_OK(Put("a", "begin")); - ASSERT_OK(Put("z", "end")); - ASSERT_OK(Flush()); - } - - // If pending output guard does not work correctly, PurgeObsoleteFiles() will - // delete the file that Compaction is trying to create, causing this: error - // db/db_test.cc:975: IO error: - // /tmp/rocksdbtest-1552237650/db_test/000009.sst: No such file or directory - Compact("a", "b"); -} - -// 1 Create some SST files by inserting K-V pairs into DB -// 2 Close DB and change suffix from ".sst" to ".ldb" for every other SST file -// 3 Open DB and check if all key can be read -TEST_F(DBSSTTest, SSTsWithLdbSuffixHandling) { - Options options = CurrentOptions(); - options.write_buffer_size = 110 << 10; // 110KB - options.num_levels = 4; - DestroyAndReopen(options); - - Random rnd(301); - int key_id = 0; - for (int i = 0; i < 10; ++i) { - GenerateNewFile(&rnd, &key_id, false); - } - ASSERT_OK(Flush()); - Close(); - int const num_files = GetSstFileCount(dbname_); - ASSERT_GT(num_files, 0); - - Reopen(options); - std::vector values; - values.reserve(key_id); - for (int k = 0; k < key_id; ++k) { - values.push_back(Get(Key(k))); - } - Close(); - - std::vector filenames; - GetSstFiles(env_, dbname_, &filenames); - int num_ldb_files = 0; - for (size_t i = 0; i < filenames.size(); ++i) { - if (i & 1) { - continue; - } - std::string const rdb_name = dbname_ + "/" + filenames[i]; - std::string const ldb_name = Rocks2LevelTableFileName(rdb_name); - ASSERT_TRUE(env_->RenameFile(rdb_name, ldb_name).ok()); - ++num_ldb_files; - } - ASSERT_GT(num_ldb_files, 0); - ASSERT_EQ(num_files, GetSstFileCount(dbname_)); - - Reopen(options); - for (int k = 0; k < key_id; ++k) { - ASSERT_EQ(values[k], Get(Key(k))); - } - Destroy(options); -} - -// Check that we don't crash when opening DB with -// DBOptions::skip_checking_sst_file_sizes_on_db_open = true. -TEST_F(DBSSTTest, SkipCheckingSSTFileSizesOnDBOpen) { - ASSERT_OK(Put("pika", "choo")); - ASSERT_OK(Flush()); - - // Just open the DB with the option set to true and check that we don't crash. - Options options; - options.env = env_; - options.skip_checking_sst_file_sizes_on_db_open = true; - Reopen(options); - - ASSERT_EQ("choo", Get("pika")); -} - -TEST_F(DBSSTTest, DontDeleteMovedFile) { - // This test triggers move compaction and verifies that the file is not - // deleted when it's part of move compaction - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.max_bytes_for_level_base = 1024 * 1024; // 1 MB - options.level0_file_num_compaction_trigger = - 2; // trigger compaction when we have 2 files - DestroyAndReopen(options); - - Random rnd(301); - // Create two 1MB sst files - for (int i = 0; i < 2; ++i) { - // Create 1MB sst file - for (int j = 0; j < 100; ++j) { - ASSERT_OK(Put(Key(i * 50 + j), rnd.RandomString(10 * 1024))); - } - ASSERT_OK(Flush()); - } - // this should execute both L0->L1 and L1->(move)->L2 compactions - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,0,1", FilesPerLevel(0)); - - // If the moved file is actually deleted (the move-safeguard in - // ~Version::Version() is not there), we get this failure: - // Corruption: Can't access /000009.sst - Reopen(options); -} - -// This reproduces a bug where we don't delete a file because when it was -// supposed to be deleted, it was blocked by pending_outputs -// Consider: -// 1. current file_number is 13 -// 2. compaction (1) starts, blocks deletion of all files starting with 13 -// (pending outputs) -// 3. file 13 is created by compaction (2) -// 4. file 13 is consumed by compaction (3) and file 15 was created. Since file -// 13 has no references, it is put into VersionSet::obsolete_files_ -// 5. FindObsoleteFiles() gets file 13 from VersionSet::obsolete_files_. File 13 -// is deleted from obsolete_files_ set. -// 6. PurgeObsoleteFiles() tries to delete file 13, but this file is blocked by -// pending outputs since compaction (1) is still running. It is not deleted and -// it is not present in obsolete_files_ anymore. Therefore, we never delete it. -TEST_F(DBSSTTest, DeleteObsoleteFilesPendingOutputs) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 2 * 1024 * 1024; // 2 MB - options.max_bytes_for_level_base = 1024 * 1024; // 1 MB - options.level0_file_num_compaction_trigger = - 2; // trigger compaction when we have 2 files - options.max_background_flushes = 2; - options.max_background_compactions = 2; - - OnFileDeletionListener* listener = new OnFileDeletionListener(); - options.listeners.emplace_back(listener); - - Reopen(options); - - Random rnd(301); - // Create two 1MB sst files - for (int i = 0; i < 2; ++i) { - // Create 1MB sst file - for (int j = 0; j < 100; ++j) { - ASSERT_OK(Put(Key(i * 50 + j), rnd.RandomString(10 * 1024))); - } - ASSERT_OK(Flush()); - } - // this should execute both L0->L1 and L1->(move)->L2 compactions - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,0,1", FilesPerLevel(0)); - - test::SleepingBackgroundTask blocking_thread; - port::Mutex mutex_; - bool already_blocked(false); - - // block the flush - std::function block_first_time = [&]() { - bool blocking = false; - { - MutexLock l(&mutex_); - if (!already_blocked) { - blocking = true; - already_blocked = true; - } - } - if (blocking) { - blocking_thread.DoSleep(); - } - }; - env_->table_write_callback_ = &block_first_time; - // Insert 2.5MB data, which should trigger a flush because we exceed - // write_buffer_size. The flush will be blocked with block_first_time - // pending_file is protecting all the files created after - for (int j = 0; j < 256; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(10 * 1024))); - } - blocking_thread.WaitUntilSleeping(); - - ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr)); - - ASSERT_EQ("0,0,0,1", FilesPerLevel(0)); - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 1U); - auto file_on_L2 = metadata[0].name; - listener->SetExpectedFileName(dbname_ + file_on_L2); - - ASSERT_OK(dbfull()->TEST_CompactRange(3, nullptr, nullptr, nullptr, - true /* disallow trivial move */)); - ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0)); - - // finish the flush! - blocking_thread.WakeUp(); - blocking_thread.WaitUntilDone(); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - // File just flushed is too big for L0 and L1 so gets moved to L2. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,0,1,0,1", FilesPerLevel(0)); - - metadata.clear(); - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 2U); - - // This file should have been deleted during last compaction - ASSERT_EQ(Status::NotFound(), env_->FileExists(dbname_ + file_on_L2)); - listener->VerifyMatchedCount(1); -} - -// Test that producing an empty .sst file does not write it out to -// disk, and that the DeleteFile() env method is not called for -// removing the non-existing file later. -TEST_F(DBSSTTest, DeleteFileNotCalledForNotCreatedSSTFile) { - Options options = CurrentOptions(); - options.env = env_; - - OnFileDeletionListener* listener = new OnFileDeletionListener(); - options.listeners.emplace_back(listener); - - Reopen(options); - - // Flush the empty database. - ASSERT_OK(Flush()); - ASSERT_EQ("", FilesPerLevel(0)); - - // We expect no .sst files. - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 0U); - - // We expect no file deletions. - listener->VerifyMatchedCount(0); -} - -// Test that producing a non-empty .sst file does write it out to -// disk, and that the DeleteFile() env method is not called for removing -// the file later. -TEST_F(DBSSTTest, DeleteFileNotCalledForCreatedSSTFile) { - Options options = CurrentOptions(); - options.env = env_; - - OnFileDeletionListener* listener = new OnFileDeletionListener(); - options.listeners.emplace_back(listener); - - Reopen(options); - - ASSERT_OK(Put("pika", "choo")); - - // Flush the non-empty database. - ASSERT_OK(Flush()); - ASSERT_EQ("1", FilesPerLevel(0)); - - // We expect 1 .sst files. - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(metadata.size(), 1U); - - // We expect no file deletions. - listener->VerifyMatchedCount(0); -} - -TEST_F(DBSSTTest, DBWithSstFileManager) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - int files_added = 0; - int files_deleted = 0; - int files_moved = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnAddFile", [&](void* /*arg*/) { files_added++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnDeleteFile", - [&](void* /*arg*/) { files_deleted++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 25; i++) { - GenerateNewRandomFile(&rnd); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Verify that we are tracking all sst files in dbname_ - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db)); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - } - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db)); - // Verify that we are tracking all sst files in dbname_ - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - // Verify the total files size - uint64_t total_files_size = 0; - for (auto& file_to_size : files_in_db) { - total_files_size += file_to_size.second; - } - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - // We flushed at least 25 files - ASSERT_GE(files_added, 25); - // Compaction must have deleted some files - ASSERT_GT(files_deleted, 0); - // No files were moved - ASSERT_EQ(files_moved, 0); - - Close(); - Reopen(options); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - - // Verify that we track all the files again after the DB is closed and opened - Close(); - sst_file_manager.reset(NewSstFileManager(env_)); - options.sst_file_manager = sst_file_manager; - sfm = static_cast(sst_file_manager.get()); - - Reopen(options); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - int files_added = 0; - int files_deleted = 0; - int files_moved = 0; - int files_scheduled_to_delete = 0; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnAddFile", [&](void* arg) { - const std::string* const file_path = - static_cast(arg); - if (file_path->find(".blob") != std::string::npos) { - files_added++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnDeleteFile", [&](void* arg) { - const std::string* const file_path = - static_cast(arg); - if (file_path->find(".blob") != std::string::npos) { - files_deleted++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::ScheduleFileDeletion", [&](void* arg) { - assert(arg); - const std::string* const file_path = - static_cast(arg); - if (file_path->find(".blob") != std::string::npos) { - ++files_scheduled_to_delete; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.enable_blob_files = true; - options.blob_file_size = 32; // create one blob per file - DestroyAndReopen(options); - Random rnd(301); - - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put("Key_" + std::to_string(i), "Value_" + std::to_string(i))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Verify that we are tracking all sst and blob files in dbname_ - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db)); - ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db)); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - } - - std::vector blob_files = GetBlobFileNumbers(); - ASSERT_EQ(files_added, blob_files.size()); - // No blob file is obsoleted. - ASSERT_EQ(files_deleted, 0); - ASSERT_EQ(files_scheduled_to_delete, 0); - // No files were moved. - ASSERT_EQ(files_moved, 0); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db)); - ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db)); - - // Verify that we are tracking all sst and blob files in dbname_ - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - // Verify the total files size - uint64_t total_files_size = 0; - for (auto& file_to_size : files_in_db) { - total_files_size += file_to_size.second; - } - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - Close(); - - Reopen(options); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - - // Verify that we track all the files again after the DB is closed and opened. - Close(); - - sst_file_manager.reset(NewSstFileManager(env_)); - options.sst_file_manager = sst_file_manager; - sfm = static_cast(sst_file_manager.get()); - - Reopen(options); - - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - - // Destroy DB and it will remove all the blob files from sst file manager and - // blob files deletion will go through ScheduleFileDeletion. - ASSERT_EQ(files_deleted, 0); - ASSERT_EQ(files_scheduled_to_delete, 0); - Close(); - ASSERT_OK(DestroyDB(dbname_, options)); - ASSERT_EQ(files_deleted, blob_files.size()); - ASSERT_EQ(files_scheduled_to_delete, blob_files.size()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.enable_blob_files = true; - options.blob_file_size = 32; // create one blob per file - options.disable_auto_compactions = true; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0.5; - - int files_added = 0; - int files_deleted = 0; - int files_moved = 0; - int files_scheduled_to_delete = 0; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnAddFile", [&](void* arg) { - const std::string* const file_path = - static_cast(arg); - if (file_path->find(".blob") != std::string::npos) { - files_added++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnDeleteFile", [&](void* arg) { - const std::string* const file_path = - static_cast(arg); - if (file_path->find(".blob") != std::string::npos) { - files_deleted++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::ScheduleFileDeletion", [&](void* arg) { - assert(arg); - const std::string* const file_path = - static_cast(arg); - if (file_path->find(".blob") != std::string::npos) { - ++files_scheduled_to_delete; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - Random rnd(301); - - constexpr char first_key[] = "first_key"; - constexpr char first_value[] = "first_value"; - constexpr char second_key[] = "second_key"; - constexpr char second_value[] = "second_value"; - - ASSERT_OK(Put(first_key, first_value)); - ASSERT_OK(Put(second_key, second_value)); - ASSERT_OK(Flush()); - - constexpr char third_key[] = "third_key"; - constexpr char third_value[] = "third_value"; - constexpr char fourth_key[] = "fourth_key"; - constexpr char fourth_value[] = "fourth_value"; - constexpr char fifth_key[] = "fifth_key"; - constexpr char fifth_value[] = "fifth_value"; - - ASSERT_OK(Put(third_key, third_value)); - ASSERT_OK(Put(fourth_key, fourth_value)); - ASSERT_OK(Put(fifth_key, fifth_value)); - ASSERT_OK(Flush()); - - const std::vector original_blob_files = GetBlobFileNumbers(); - - ASSERT_EQ(original_blob_files.size(), 5); - ASSERT_EQ(files_added, 5); - ASSERT_EQ(files_deleted, 0); - ASSERT_EQ(files_scheduled_to_delete, 0); - ASSERT_EQ(files_moved, 0); - { - // Verify that we are tracking all sst and blob files in dbname_ - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db)); - ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db)); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - } - - const size_t cutoff_index = static_cast( - options.blob_garbage_collection_age_cutoff * original_blob_files.size()); - - size_t expected_number_of_files = original_blob_files.size(); - // Note: turning off enable_blob_files before the compaction results in - // garbage collected values getting inlined. - ASSERT_OK(db_->SetOptions({{"enable_blob_files", "false"}})); - expected_number_of_files -= cutoff_index; - files_added = 0; - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - sfm->WaitForEmptyTrash(); - - ASSERT_EQ(Get(first_key), first_value); - ASSERT_EQ(Get(second_key), second_value); - ASSERT_EQ(Get(third_key), third_value); - ASSERT_EQ(Get(fourth_key), fourth_value); - ASSERT_EQ(Get(fifth_key), fifth_value); - - const std::vector new_blob_files = GetBlobFileNumbers(); - - ASSERT_EQ(new_blob_files.size(), expected_number_of_files); - // No new file is added. - ASSERT_EQ(files_added, 0); - ASSERT_EQ(files_deleted, cutoff_index); - ASSERT_EQ(files_scheduled_to_delete, cutoff_index); - ASSERT_EQ(files_moved, 0); - - // Original blob files below the cutoff should be gone, original blob files at - // or above the cutoff should be still there - for (size_t i = cutoff_index; i < original_blob_files.size(); ++i) { - ASSERT_EQ(new_blob_files[i - cutoff_index], original_blob_files[i]); - } - - { - // Verify that we are tracking all sst and blob files in dbname_ - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db)); - ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db)); - ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db); - } - - Close(); - ASSERT_OK(DestroyDB(dbname_, options)); - sfm->WaitForEmptyTrash(); - ASSERT_EQ(files_deleted, 5); - ASSERT_EQ(files_scheduled_to_delete, 5); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -class DBSSTTestRateLimit : public DBSSTTest, - public ::testing::WithParamInterface { - public: - DBSSTTestRateLimit() : DBSSTTest() {} - ~DBSSTTestRateLimit() override {} -}; - -TEST_P(DBSSTTestRateLimit, RateLimitedDelete) { - Destroy(last_options_); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DBSSTTest::RateLimitedDelete:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - - std::vector penalties; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::BackgroundEmptyTrash:Wait", - [&](void* arg) { penalties.push_back(*(static_cast(arg))); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { - // Turn timed wait into a simulated sleep - uint64_t* abs_time_us = static_cast(arg); - uint64_t cur_time = env_->NowMicros(); - if (*abs_time_us > cur_time) { - env_->MockSleepForMicroseconds(*abs_time_us - cur_time); - } - - // Plus an additional short, random amount - env_->MockSleepForMicroseconds(Random::GetTLSInstance()->Uniform(10)); - - // Set wait until time to before (actual) current time to force not - // to sleep - *abs_time_us = Env::Default()->NowMicros(); - }); - - // Disable PeriodicTaskScheduler as it also has TimedWait, which could update - // the simulated sleep time - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::StartPeriodicTaskScheduler:DisableScheduler", [&](void* arg) { - bool* disable_scheduler = static_cast(arg); - *disable_scheduler = true; - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - bool different_wal_dir = GetParam(); - Options options = CurrentOptions(); - SetTimeElapseOnlySleepOnReopen(&options); - options.disable_auto_compactions = true; - options.env = env_; - options.statistics = CreateDBStatistics(); - if (different_wal_dir) { - options.wal_dir = alternative_wal_dir_; - } - - int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec - Status s; - options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); - ASSERT_OK(s); - options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec); - auto sfm = static_cast(options.sst_file_manager.get()); - sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1); - - WriteOptions wo; - if (!different_wal_dir) { - wo.disableWAL = true; - } - Reopen(options); - // Create 4 files in L0 - for (char v = 'a'; v <= 'd'; v++) { - ASSERT_OK(Put("Key2", DummyString(1024, v), wo)); - ASSERT_OK(Put("Key3", DummyString(1024, v), wo)); - ASSERT_OK(Put("Key4", DummyString(1024, v), wo)); - ASSERT_OK(Put("Key1", DummyString(1024, v), wo)); - ASSERT_OK(Put("Key4", DummyString(1024, v), wo)); - ASSERT_OK(Flush()); - } - // We created 4 sst files in L0 - ASSERT_EQ("4", FilesPerLevel(0)); - - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - - // Compaction will move the 4 files in L0 to trash and create 1 L1 file - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - ASSERT_EQ("0,1", FilesPerLevel(0)); - - uint64_t delete_start_time = env_->NowMicros(); - // Hold BackgroundEmptyTrash - TEST_SYNC_POINT("DBSSTTest::RateLimitedDelete:1"); - sfm->WaitForEmptyTrash(); - uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time; - - uint64_t total_files_size = 0; - uint64_t expected_penlty = 0; - ASSERT_EQ(penalties.size(), metadata.size()); - for (size_t i = 0; i < metadata.size(); i++) { - total_files_size += metadata[i].size; - expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec); - ASSERT_EQ(expected_penlty, penalties[i]); - } - ASSERT_GT(time_spent_deleting, expected_penlty * 0.9); - ASSERT_LT(time_spent_deleting, expected_penlty * 1.1); - ASSERT_EQ(4, options.statistics->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ( - 0, options.statistics->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -INSTANTIATE_TEST_CASE_P(RateLimitedDelete, DBSSTTestRateLimit, - ::testing::Bool()); - -TEST_F(DBSSTTest, RateLimitedWALDelete) { - Destroy(last_options_); - - std::vector penalties; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::BackgroundEmptyTrash:Wait", - [&](void* arg) { penalties.push_back(*(static_cast(arg))); }); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.compression = kNoCompression; - options.env = env_; - - int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec - Status s; - options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); - ASSERT_OK(s); - options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec); - auto sfm = static_cast(options.sst_file_manager.get()); - sfm->delete_scheduler()->SetMaxTrashDBRatio(3.1); - SetTimeElapseOnlySleepOnReopen(&options); - - ASSERT_OK(TryReopen(options)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Create 4 files in L0 - for (char v = 'a'; v <= 'd'; v++) { - ASSERT_OK(Put("Key2", DummyString(1024, v))); - ASSERT_OK(Put("Key3", DummyString(1024, v))); - ASSERT_OK(Put("Key4", DummyString(1024, v))); - ASSERT_OK(Put("Key1", DummyString(1024, v))); - ASSERT_OK(Put("Key4", DummyString(1024, v))); - ASSERT_OK(Flush()); - } - // We created 4 sst files in L0 - ASSERT_EQ("4", FilesPerLevel(0)); - - // Compaction will move the 4 files in L0 to trash and create 1 L1 file - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - ASSERT_EQ("0,1", FilesPerLevel(0)); - - sfm->WaitForEmptyTrash(); - ASSERT_EQ(penalties.size(), 8); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -class DBWALTestWithParam - : public DBTestBase, - public testing::WithParamInterface> { - public: - explicit DBWALTestWithParam() - : DBTestBase("db_wal_test_with_params", /*env_do_fsync=*/true) { - wal_dir_ = std::get<0>(GetParam()); - wal_dir_same_as_dbname_ = std::get<1>(GetParam()); - } - - std::string wal_dir_; - bool wal_dir_same_as_dbname_; -}; - -TEST_P(DBWALTestWithParam, WALTrashCleanupOnOpen) { - class MyEnv : public EnvWrapper { - public: - MyEnv(Env* t) : EnvWrapper(t), fake_log_delete(false) {} - const char* Name() const override { return "MyEnv"; } - Status DeleteFile(const std::string& fname) override { - if (fname.find(".log.trash") != std::string::npos && fake_log_delete) { - return Status::OK(); - } - - return target()->DeleteFile(fname); - } - - void set_fake_log_delete(bool fake) { fake_log_delete = fake; } - - private: - bool fake_log_delete; - }; - - std::unique_ptr env(new MyEnv(env_)); - Destroy(last_options_); - - env->set_fake_log_delete(true); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.compression = kNoCompression; - options.env = env.get(); - options.wal_dir = dbname_ + wal_dir_; - - int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec - Status s; - options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); - ASSERT_OK(s); - options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec); - auto sfm = static_cast(options.sst_file_manager.get()); - sfm->delete_scheduler()->SetMaxTrashDBRatio(3.1); - - Reopen(options); - - // Create 4 files in L0 - for (char v = 'a'; v <= 'd'; v++) { - if (v == 'c') { - // Maximize the change that the last log file will be preserved in trash - // before restarting the DB. - // We have to set this on the 2nd to last file for it to delay deletion - // on the last file. (Quirk of DeleteScheduler::BackgroundEmptyTrash()) - options.sst_file_manager->SetDeleteRateBytesPerSecond(1); - } - ASSERT_OK(Put("Key2", DummyString(1024, v))); - ASSERT_OK(Put("Key3", DummyString(1024, v))); - ASSERT_OK(Put("Key4", DummyString(1024, v))); - ASSERT_OK(Put("Key1", DummyString(1024, v))); - ASSERT_OK(Put("Key4", DummyString(1024, v))); - ASSERT_OK(Flush()); - } - // We created 4 sst files in L0 - ASSERT_EQ("4", FilesPerLevel(0)); - - Close(); - - options.sst_file_manager.reset(); - std::vector filenames; - int trash_log_count = 0; - if (!wal_dir_same_as_dbname_) { - // Forcibly create some trash log files - std::unique_ptr result; - ASSERT_OK(env->NewWritableFile(options.wal_dir + "/1000.log.trash", &result, - EnvOptions())); - result.reset(); - } - ASSERT_OK(env->GetChildren(options.wal_dir, &filenames)); - for (const std::string& fname : filenames) { - if (fname.find(".log.trash") != std::string::npos) { - trash_log_count++; - } - } - ASSERT_GE(trash_log_count, 1); - - env->set_fake_log_delete(false); - Reopen(options); - - filenames.clear(); - trash_log_count = 0; - ASSERT_OK(env->GetChildren(options.wal_dir, &filenames)); - for (const std::string& fname : filenames) { - if (fname.find(".log.trash") != std::string::npos) { - trash_log_count++; - } - } - ASSERT_EQ(trash_log_count, 0); - Close(); -} - -INSTANTIATE_TEST_CASE_P(DBWALTestWithParam, DBWALTestWithParam, - ::testing::Values(std::make_tuple("", true), - std::make_tuple("_wal_dir", false))); - -TEST_F(DBSSTTest, OpenDBWithExistingTrash) { - Options options = CurrentOptions(); - - options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */)); - auto sfm = static_cast(options.sst_file_manager.get()); - - Destroy(last_options_); - - // Add some trash files to the db directory so the DB can clean them up - ASSERT_OK(env_->CreateDirIfMissing(dbname_)); - ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash")); - ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash")); - ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash")); - - // Reopen the DB and verify that it deletes existing trash files - Reopen(options); - sfm->WaitForEmptyTrash(); - ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash")); - ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash")); - ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash")); -} - -// Create a DB with 2 db_paths, and generate multiple files in the 2 -// db_paths using CompactRangeOptions, make sure that files that were -// deleted from first db_path were deleted using DeleteScheduler and -// files in the second path were not. -TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) { - std::atomic bg_delete_file(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - // The deletion scheduler sometimes skips marking file as trash according to - // a heuristic. In that case the deletion will go through the below SyncPoint. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteFile", [&](void* /*arg*/) { bg_delete_file++; }); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.db_paths.emplace_back(dbname_, 1024 * 100); - options.db_paths.emplace_back(dbname_ + "_2", 1024 * 100); - options.env = env_; - - int64_t rate_bytes_per_sec = 1024 * 1024; // 1 Mb / Sec - Status s; - options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, "", rate_bytes_per_sec, false, &s, - /* max_trash_db_ratio= */ 1.1)); - - ASSERT_OK(s); - auto sfm = static_cast(options.sst_file_manager.get()); - - DestroyAndReopen(options); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.disableWAL = true; - - // Create 4 files in L0 - for (int i = 0; i < 4; i++) { - ASSERT_OK(Put("Key" + std::to_string(i), DummyString(1024, 'A'), wo)); - ASSERT_OK(Flush()); - } - // We created 4 sst files in L0 - ASSERT_EQ("4", FilesPerLevel(0)); - // Compaction will delete files from L0 in first db path and generate a new - // file in L1 in second db path - CompactRangeOptions compact_options; - compact_options.target_path_id = 1; - Slice begin("Key0"); - Slice end("Key3"); - ASSERT_OK(db_->CompactRange(compact_options, &begin, &end)); - ASSERT_EQ("0,1", FilesPerLevel(0)); - - // Create 4 files in L0 - for (int i = 4; i < 8; i++) { - ASSERT_OK(Put("Key" + std::to_string(i), DummyString(1024, 'B'), wo)); - ASSERT_OK(Flush()); - } - ASSERT_EQ("4,1", FilesPerLevel(0)); - - // Compaction will delete files from L0 in first db path and generate a new - // file in L1 in second db path - begin = "Key4"; - end = "Key7"; - ASSERT_OK(db_->CompactRange(compact_options, &begin, &end)); - ASSERT_EQ("0,2", FilesPerLevel(0)); - - sfm->WaitForEmptyTrash(); - ASSERT_EQ(bg_delete_file, 8); - - // Compaction will delete both files and regenerate a file in L1 in second - // db path. The deleted files should still be cleaned up via delete scheduler. - compact_options.bottommost_level_compaction = - BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,1", FilesPerLevel(0)); - - sfm->WaitForEmptyTrash(); - ASSERT_EQ(bg_delete_file, 10); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) { - int bg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Status s; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.env = env_; - options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); - ASSERT_OK(s); - DestroyAndReopen(options); - - // Create 4 files in L0 - for (int i = 0; i < 4; i++) { - ASSERT_OK(Put("Key" + std::to_string(i), DummyString(1024, 'A'))); - ASSERT_OK(Flush()); - } - // We created 4 sst files in L0 - ASSERT_EQ("4", FilesPerLevel(0)); - - // Close DB and destroy it using DeleteScheduler - Close(); - - int num_sst_files = 0; - int num_wal_files = 0; - std::vector db_files; - ASSERT_OK(env_->GetChildren(dbname_, &db_files)); - for (std::string f : db_files) { - if (f.substr(f.find_last_of(".") + 1) == "sst") { - num_sst_files++; - } else if (f.substr(f.find_last_of(".") + 1) == "log") { - num_wal_files++; - } - } - ASSERT_GT(num_sst_files, 0); - ASSERT_GT(num_wal_files, 0); - - auto sfm = static_cast(options.sst_file_manager.get()); - - sfm->SetDeleteRateBytesPerSecond(1024 * 1024); - // Set an extra high trash ratio to prevent immediate/non-rate limited - // deletions - sfm->delete_scheduler()->SetMaxTrashDBRatio(1000.0); - ASSERT_OK(DestroyDB(dbname_, options)); - sfm->WaitForEmptyTrash(); - ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files); -} - -TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - Random rnd(301); - - // Generate a file containing 100 keys. - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(50))); - } - ASSERT_OK(Flush()); - - uint64_t first_file_size = 0; - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &first_file_size)); - ASSERT_EQ(sfm->GetTotalSize(), first_file_size); - - // Set the maximum allowed space usage to the current total size - sfm->SetMaxAllowedSpaceUsage(first_file_size + 1); - - ASSERT_OK(Put("key1", "val1")); - // This flush will cause bg_error_ and will fail - ASSERT_NOK(Flush()); -} - -TEST_F(DBSSTTest, DBWithMaxSpaceAllowedWithBlobFiles) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.disable_auto_compactions = true; - options.enable_blob_files = true; - DestroyAndReopen(options); - - Random rnd(301); - - // Generate a file containing keys. - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(50))); - } - ASSERT_OK(Flush()); - - uint64_t files_size = 0; - uint64_t total_files_size = 0; - std::unordered_map files_in_db; - - ASSERT_OK(GetAllDataFiles(kBlobFile, &files_in_db, &files_size)); - // Make sure blob files are considered by SSTFileManage in size limits. - ASSERT_GT(files_size, 0); - total_files_size = files_size; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &files_size)); - total_files_size += files_size; - ASSERT_EQ(sfm->GetTotalSize(), total_files_size); - - // Set the maximum allowed space usage to the current total size. - sfm->SetMaxAllowedSpaceUsage(total_files_size + 1); - - bool max_allowed_space_reached = false; - bool delete_blob_file = false; - // Sync point called after blob file is closed and max allowed space is - // checked. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached", - [&](void* /*arg*/) { max_allowed_space_reached = true; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BuildTable::AfterDeleteFile", - [&](void* /*arg*/) { delete_blob_file = true; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - { - "BuildTable::AfterDeleteFile", - "DBSSTTest::DBWithMaxSpaceAllowedWithBlobFiles:1", - }, - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put("key1", "val1")); - // This flush will fail - ASSERT_NOK(Flush()); - ASSERT_TRUE(max_allowed_space_reached); - - TEST_SYNC_POINT("DBSSTTest::DBWithMaxSpaceAllowedWithBlobFiles:1"); - ASSERT_TRUE(delete_blob_file); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBSSTTest, CancellingCompactionsWorks) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.level0_file_num_compaction_trigger = 2; - options.statistics = CreateDBStatistics(); - DestroyAndReopen(options); - - int completed_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction():CancelledCompaction", [&](void* /*arg*/) { - sfm->SetMaxAllowedSpaceUsage(0); - ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", - [&](void* /*arg*/) { completed_compactions++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - - // Generate a file containing 10 keys. - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(50))); - } - ASSERT_OK(Flush()); - uint64_t total_file_size = 0; - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &total_file_size)); - // Set the maximum allowed space usage to the current total size - sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1); - - // Generate another file to trigger compaction. - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(50))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - - // Because we set a callback in CancelledCompaction, we actually - // let the compaction run - ASSERT_GT(completed_compactions, 0); - ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); - // Make sure the stat is bumped - ASSERT_GT(dbfull()->immutable_db_options().statistics.get()->getTickerCount( - COMPACTION_CANCELLED), - 0); - ASSERT_EQ(0, - dbfull()->immutable_db_options().statistics.get()->getTickerCount( - FILES_MARKED_TRASH)); - ASSERT_EQ(4, - dbfull()->immutable_db_options().statistics.get()->getTickerCount( - FILES_DELETED_IMMEDIATELY)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBSSTTest, CancellingManualCompactionsWorks) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.statistics = CreateDBStatistics(); - - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - DestroyAndReopen(options); - - Random rnd(301); - - // Generate a file containing 10 keys. - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(50))); - } - ASSERT_OK(Flush()); - uint64_t total_file_size = 0; - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &total_file_size)); - // Set the maximum allowed space usage to the current total size - sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1); - - // Generate another file to trigger compaction. - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(50))); - } - ASSERT_OK(Flush()); - - // OK, now trigger a manual compaction - ASSERT_TRUE(dbfull() - ->CompactRange(CompactRangeOptions(), nullptr, nullptr) - .IsCompactionTooLarge()); - - // Wait for manual compaction to get scheduled and finish - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - - ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); - // Make sure the stat is bumped - ASSERT_EQ(dbfull()->immutable_db_options().statistics.get()->getTickerCount( - COMPACTION_CANCELLED), - 1); - - // Now make sure CompactFiles also gets cancelled - auto l0_files = collector->GetFlushedFiles(); - ASSERT_TRUE( - dbfull() - ->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), l0_files, 0) - .IsCompactionTooLarge()); - - // Wait for manual compaction to get scheduled and finish - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - - ASSERT_EQ(dbfull()->immutable_db_options().statistics.get()->getTickerCount( - COMPACTION_CANCELLED), - 2); - ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); - - // Now let the flush through and make sure GetCompactionsReservedSize - // returns to normal - sfm->SetMaxAllowedSpaceUsage(0); - int completed_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactFilesImpl:End", [&](void* /*arg*/) { completed_compactions++; }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(dbfull()->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), - l0_files, 0)); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - - ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); - ASSERT_GT(completed_compactions, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) { - // This test will set a maximum allowed space for the DB, then it will - // keep filling the DB until the limit is reached and bg_error_ is set. - // When bg_error_ is set we will verify that the DB size is greater - // than the limit. - - std::vector max_space_limits_mbs = {1, 10}; - std::atomic bg_error_set(false); - - std::atomic reached_max_space_on_flush(0); - std::atomic reached_max_space_on_compaction(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached", - [&](void* arg) { - Status* bg_error = static_cast(arg); - bg_error_set = true; - reached_max_space_on_flush++; - // clear error to ensure compaction callback is called - *bg_error = Status::OK(); - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction():CancelledCompaction", [&](void* arg) { - bool* enough_room = static_cast(arg); - *enough_room = true; - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached", - [&](void* /*arg*/) { - bg_error_set = true; - reached_max_space_on_compaction++; - }); - - for (auto limit_mb : max_space_limits_mbs) { - bg_error_set = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.write_buffer_size = 1024 * 512; // 512 Kb - DestroyAndReopen(options); - Random rnd(301); - - sfm->SetMaxAllowedSpaceUsage(limit_mb * 1024 * 1024); - - // It is easy to detect if the test is stuck in a loop. No need for - // complex termination logic. - while (true) { - auto s = Put(rnd.RandomString(10), rnd.RandomString(50)); - if (!s.ok()) { - break; - } - } - ASSERT_TRUE(bg_error_set); - uint64_t total_sst_files_size = 0; - std::unordered_map files_in_db; - ASSERT_OK(GetAllDataFiles(kTableFile, &files_in_db, &total_sst_files_size)); - ASSERT_GE(total_sst_files_size, limit_mb * 1024 * 1024); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - - ASSERT_GT(reached_max_space_on_flush, 0); - ASSERT_GT(reached_max_space_on_compaction, 0); -} - -TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFiles) { - // Open DB with infinite max open files - // - First iteration use 1 thread to open files - // - Second iteration use 5 threads to open files - for (int iter = 0; iter < 2; iter++) { - Options options; - options.create_if_missing = true; - options.write_buffer_size = 100000; - options.disable_auto_compactions = true; - options.max_open_files = -1; - if (iter == 0) { - options.max_file_opening_threads = 1; - } else { - options.max_file_opening_threads = 5; - } - options = CurrentOptions(options); - DestroyAndReopen(options); - - // Create 12 Files in L0 (then move then to L2) - for (int i = 0; i < 12; i++) { - std::string k = "L2_" + Key(i); - ASSERT_OK(Put(k, k + std::string(1000, 'a'))); - ASSERT_OK(Flush()); - } - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - - // Create 12 Files in L0 - for (int i = 0; i < 12; i++) { - std::string k = "L0_" + Key(i); - ASSERT_OK(Put(k, k + std::string(1000, 'a'))); - ASSERT_OK(Flush()); - } - Close(); - - // Reopening the DB will load all existing files - Reopen(options); - ASSERT_EQ("12,0,12", FilesPerLevel(0)); - std::vector> files; - dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files); - - for (const auto& level : files) { - for (const auto& file : level) { - ASSERT_TRUE(file.table_reader_handle != nullptr); - } - } - - for (int i = 0; i < 12; i++) { - ASSERT_EQ(Get("L0_" + Key(i)), "L0_" + Key(i) + std::string(1000, 'a')); - ASSERT_EQ(Get("L2_" + Key(i)), "L2_" + Key(i) + std::string(1000, 'a')); - } - } -} - -TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFilesSubjectToMemoryLimit) { - for (CacheEntryRoleOptions::Decision charge_table_reader : - {CacheEntryRoleOptions::Decision::kEnabled, - CacheEntryRoleOptions::Decision::kDisabled}) { - // Open DB with infinite max open files - // - First iteration use 1 thread to open files - // - Second iteration use 5 threads to open files - for (int iter = 0; iter < 2; iter++) { - Options options; - options.create_if_missing = true; - options.write_buffer_size = 100000; - options.disable_auto_compactions = true; - options.max_open_files = -1; - - BlockBasedTableOptions table_options; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - if (iter == 0) { - options.max_file_opening_threads = 1; - } else { - options.max_file_opening_threads = 5; - } - - DestroyAndReopen(options); - - // Create 5 Files in L0 (then move then to L2) - for (int i = 0; i < 5; i++) { - std::string k = "L2_" + Key(i); - ASSERT_OK(Put(k, k + std::string(1000, 'a'))); - ASSERT_OK(Flush()) << i; - } - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - - // Create 5 Files in L0 - for (int i = 0; i < 5; i++) { - std::string k = "L0_" + Key(i); - ASSERT_OK(Put(k, k + std::string(1000, 'a'))); - ASSERT_OK(Flush()); - } - Close(); - - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kBlockBasedTableReader, - {/*.charged = */ charge_table_reader}}); - table_options.block_cache = - NewLRUCache(1024 /* capacity */, 0 /* num_shard_bits */, - true /* strict_capacity_limit */); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - // Reopening the DB will try to load all existing files, conditionally - // subject to memory limit - Status s = TryReopen(options); - - if (charge_table_reader == CacheEntryRoleOptions::Decision::kEnabled) { - EXPECT_TRUE(s.IsMemoryLimit()); - EXPECT_TRUE(s.ToString().find( - kCacheEntryRoleToCamelString[static_cast( - CacheEntryRole::kBlockBasedTableReader)]) != - std::string::npos); - EXPECT_TRUE(s.ToString().find("memory limit based on cache capacity") != - std::string::npos); - - } else { - EXPECT_TRUE(s.ok()); - ASSERT_EQ("5,0,5", FilesPerLevel(0)); - } - } - } -} - -TEST_F(DBSSTTest, GetTotalSstFilesSize) { - // We don't propagate oldest-key-time table property on compaction and - // just write 0 as default value. This affect the exact table size, since - // we encode table properties as varint64. Force time to be 0 to work around - // it. Should remove the workaround after we propagate the property on - // compaction. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FlushJob::WriteLevel0Table:oldest_ancester_time", [&](void* arg) { - uint64_t* current_time = static_cast(arg); - *current_time = 0; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.compression = kNoCompression; - DestroyAndReopen(options); - // Generate 5 files in L0 - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 10; j++) { - std::string val = "val_file_" + std::to_string(i); - ASSERT_OK(Put(Key(j), val)); - } - ASSERT_OK(Flush()); - } - ASSERT_EQ("5", FilesPerLevel(0)); - - std::vector live_files_meta; - dbfull()->GetLiveFilesMetaData(&live_files_meta); - ASSERT_EQ(live_files_meta.size(), 5); - uint64_t single_file_size = live_files_meta[0].size; - - uint64_t live_sst_files_size = 0; - uint64_t total_sst_files_size = 0; - for (const auto& file_meta : live_files_meta) { - live_sst_files_size += file_meta.size; - } - - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 5 - // Total SST files = 5 - ASSERT_EQ(live_sst_files_size, 5 * single_file_size); - ASSERT_EQ(total_sst_files_size, 5 * single_file_size); - - // hold current version - std::unique_ptr iter1(dbfull()->NewIterator(ReadOptions())); - ASSERT_OK(iter1->status()); - - // Compact 5 files into 1 file in L0 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("0,1", FilesPerLevel(0)); - - live_files_meta.clear(); - dbfull()->GetLiveFilesMetaData(&live_files_meta); - ASSERT_EQ(live_files_meta.size(), 1); - - live_sst_files_size = 0; - total_sst_files_size = 0; - for (const auto& file_meta : live_files_meta) { - live_sst_files_size += file_meta.size; - } - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 1 (compacted file) - // Total SST files = 6 (5 original files + compacted file) - ASSERT_EQ(live_sst_files_size, 1 * single_file_size); - ASSERT_EQ(total_sst_files_size, 6 * single_file_size); - - // hold current version - std::unique_ptr iter2(dbfull()->NewIterator(ReadOptions())); - ASSERT_OK(iter2->status()); - - // Delete all keys and compact, this will delete all live files - for (int i = 0; i < 10; i++) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("", FilesPerLevel(0)); - - live_files_meta.clear(); - dbfull()->GetLiveFilesMetaData(&live_files_meta); - ASSERT_EQ(live_files_meta.size(), 0); - - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 0 - // Total SST files = 6 (5 original files + compacted file) - ASSERT_EQ(total_sst_files_size, 6 * single_file_size); - - ASSERT_OK(iter1->status()); - iter1.reset(); - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 0 - // Total SST files = 1 (compacted file) - ASSERT_EQ(total_sst_files_size, 1 * single_file_size); - - ASSERT_OK(iter2->status()); - iter2.reset(); - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 0 - // Total SST files = 0 - ASSERT_EQ(total_sst_files_size, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBSSTTest, OpenDBWithoutGetFileSizeInvocations) { - Options options = CurrentOptions(); - std::unique_ptr env{MockEnv::Create(Env::Default())}; - options.env = env.get(); - options.disable_auto_compactions = true; - options.compression = kNoCompression; - options.enable_blob_files = true; - options.blob_file_size = 32; // create one blob per file - options.skip_checking_sst_file_sizes_on_db_open = true; - - DestroyAndReopen(options); - // Generate 5 files in L0 - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 10; j++) { - std::string val = "val_file_" + std::to_string(i); - ASSERT_OK(Put(Key(j), val)); - } - ASSERT_OK(Flush()); - } - Close(); - - bool is_get_file_size_called = false; - SyncPoint::GetInstance()->SetCallBack( - "MockFileSystem::GetFileSize:CheckFileType", [&](void* arg) { - std::string* filename = reinterpret_cast(arg); - if (filename->find(".blob") != std::string::npos) { - is_get_file_size_called = true; - } - }); - - SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); - ASSERT_FALSE(is_get_file_size_called); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - Destroy(options); -} - -TEST_F(DBSSTTest, GetTotalSstFilesSizeVersionsFilesShared) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.compression = kNoCompression; - DestroyAndReopen(options); - // Generate 5 files in L0 - for (int i = 0; i < 5; i++) { - ASSERT_OK(Put(Key(i), "val")); - ASSERT_OK(Flush()); - } - ASSERT_EQ("5", FilesPerLevel(0)); - - std::vector live_files_meta; - dbfull()->GetLiveFilesMetaData(&live_files_meta); - ASSERT_EQ(live_files_meta.size(), 5); - uint64_t single_file_size = live_files_meta[0].size; - - uint64_t live_sst_files_size = 0; - uint64_t total_sst_files_size = 0; - for (const auto& file_meta : live_files_meta) { - live_sst_files_size += file_meta.size; - } - - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - - // Live SST files = 5 - // Total SST files = 5 - ASSERT_EQ(live_sst_files_size, 5 * single_file_size); - ASSERT_EQ(total_sst_files_size, 5 * single_file_size); - - // hold current version - std::unique_ptr iter1(dbfull()->NewIterator(ReadOptions())); - ASSERT_OK(iter1->status()); - - // Compaction will do trivial move from L0 to L1 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("0,5", FilesPerLevel(0)); - - live_files_meta.clear(); - dbfull()->GetLiveFilesMetaData(&live_files_meta); - ASSERT_EQ(live_files_meta.size(), 5); - - live_sst_files_size = 0; - total_sst_files_size = 0; - for (const auto& file_meta : live_files_meta) { - live_sst_files_size += file_meta.size; - } - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 5 - // Total SST files = 5 (used in 2 version) - ASSERT_EQ(live_sst_files_size, 5 * single_file_size); - ASSERT_EQ(total_sst_files_size, 5 * single_file_size); - - // hold current version - std::unique_ptr iter2(dbfull()->NewIterator(ReadOptions())); - ASSERT_OK(iter2->status()); - - // Delete all keys and compact, this will delete all live files - for (int i = 0; i < 5; i++) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("", FilesPerLevel(0)); - - live_files_meta.clear(); - dbfull()->GetLiveFilesMetaData(&live_files_meta); - ASSERT_EQ(live_files_meta.size(), 0); - - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 0 - // Total SST files = 5 (used in 2 version) - ASSERT_EQ(total_sst_files_size, 5 * single_file_size); - - ASSERT_OK(iter1->status()); - iter1.reset(); - ASSERT_OK(iter2->status()); - iter2.reset(); - - ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size", - &total_sst_files_size)); - // Live SST files = 0 - // Total SST files = 0 - ASSERT_EQ(total_sst_files_size, 0); -} - -// This test if blob files are recorded by SST File Manager when Compaction job -// creates/delete them and in case of AtomicFlush. -TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) { - std::shared_ptr sst_file_manager(NewSstFileManager(env_)); - auto sfm = static_cast(sst_file_manager.get()); - Options options = CurrentOptions(); - options.sst_file_manager = sst_file_manager; - options.enable_blob_files = true; - options.min_blob_size = 0; - options.disable_auto_compactions = true; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0.5; - options.atomic_flush = true; - - int files_added = 0; - int files_deleted = 0; - int files_scheduled_to_delete = 0; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnAddFile", [&](void* arg) { - const std::string* const file_path = - static_cast(arg); - if (EndsWith(*file_path, ".blob")) { - files_added++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnDeleteFile", [&](void* arg) { - const std::string* const file_path = - static_cast(arg); - if (EndsWith(*file_path, ".blob")) { - files_deleted++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::ScheduleFileDeletion", [&](void* arg) { - assert(arg); - const std::string* const file_path = - static_cast(arg); - if (EndsWith(*file_path, ".blob")) { - ++files_scheduled_to_delete; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - Random rnd(301); - - ASSERT_OK(Put("key_1", "value_1")); - ASSERT_OK(Put("key_2", "value_2")); - ASSERT_OK(Put("key_3", "value_3")); - ASSERT_OK(Put("key_4", "value_4")); - ASSERT_OK(Flush()); - - // Overwrite will create the garbage data. - ASSERT_OK(Put("key_3", "new_value_3")); - ASSERT_OK(Put("key_4", "new_value_4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key5", "blob_value5")); - ASSERT_OK(Put("Key6", "blob_value6")); - ASSERT_OK(Flush()); - - ASSERT_EQ(files_added, 3); - ASSERT_EQ(files_deleted, 0); - ASSERT_EQ(files_scheduled_to_delete, 0); - files_added = 0; - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - // Compaction job will create a new file and delete the older files. - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(files_added, 1); - ASSERT_EQ(files_scheduled_to_delete, 1); - - sfm->WaitForEmptyTrash(); - - ASSERT_EQ(files_deleted, 1); - - Close(); - ASSERT_OK(DestroyDB(dbname_, options)); - - ASSERT_EQ(files_scheduled_to_delete, 4); - - sfm->WaitForEmptyTrash(); - - ASSERT_EQ(files_deleted, 4); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_statistics_test.cc b/db/db_statistics_test.cc deleted file mode 100644 index 85a54aa94..000000000 --- a/db/db_statistics_test.cc +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "db/db_test_util.h" -#include "monitoring/thread_status_util.h" -#include "port/stack_trace.h" -#include "rocksdb/statistics.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class DBStatisticsTest : public DBTestBase { - public: - DBStatisticsTest() - : DBTestBase("db_statistics_test", /*env_do_fsync=*/true) {} -}; - -TEST_F(DBStatisticsTest, CompressionStatsTest) { - CompressionType type; - - if (Snappy_Supported()) { - type = kSnappyCompression; - fprintf(stderr, "using snappy\n"); - } else if (Zlib_Supported()) { - type = kZlibCompression; - fprintf(stderr, "using zlib\n"); - } else if (BZip2_Supported()) { - type = kBZip2Compression; - fprintf(stderr, "using bzip2\n"); - } else if (LZ4_Supported()) { - type = kLZ4Compression; - fprintf(stderr, "using lz4\n"); - } else if (XPRESS_Supported()) { - type = kXpressCompression; - fprintf(stderr, "using xpress\n"); - } else if (ZSTD_Supported()) { - type = kZSTD; - fprintf(stderr, "using ZSTD\n"); - } else { - fprintf(stderr, "skipping test, compression disabled\n"); - return; - } - - Options options = CurrentOptions(); - options.compression = type; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex); - DestroyAndReopen(options); - - int kNumKeysWritten = 100000; - - // Check that compressions occur and are counted when compression is turned on - Random rnd(301); - for (int i = 0; i < kNumKeysWritten; ++i) { - // compressible string - ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a'))); - } - ASSERT_OK(Flush()); - ASSERT_GT(options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED), 0); - - for (int i = 0; i < kNumKeysWritten; ++i) { - auto r = Get(Key(i)); - } - ASSERT_GT(options.statistics->getTickerCount(NUMBER_BLOCK_DECOMPRESSED), 0); - - options.compression = kNoCompression; - DestroyAndReopen(options); - uint64_t currentCompressions = - options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED); - uint64_t currentDecompressions = - options.statistics->getTickerCount(NUMBER_BLOCK_DECOMPRESSED); - - // Check that compressions do not occur when turned off - for (int i = 0; i < kNumKeysWritten; ++i) { - // compressible string - ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a'))); - } - ASSERT_OK(Flush()); - ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED) - - currentCompressions, - 0); - - for (int i = 0; i < kNumKeysWritten; ++i) { - auto r = Get(Key(i)); - } - ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_DECOMPRESSED) - - currentDecompressions, - 0); -} - -TEST_F(DBStatisticsTest, MutexWaitStatsDisabledByDefault) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - CreateAndReopenWithCF({"pikachu"}, options); - const uint64_t kMutexWaitDelay = 100; - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, - kMutexWaitDelay); - ASSERT_OK(Put("hello", "rocksdb")); - ASSERT_EQ(TestGetTickerCount(options, DB_MUTEX_WAIT_MICROS), 0); - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, 0); -} - -TEST_F(DBStatisticsTest, MutexWaitStats) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - CreateAndReopenWithCF({"pikachu"}, options); - const uint64_t kMutexWaitDelay = 100; - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, - kMutexWaitDelay); - ASSERT_OK(Put("hello", "rocksdb")); - ASSERT_GE(TestGetTickerCount(options, DB_MUTEX_WAIT_MICROS), kMutexWaitDelay); - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, 0); -} - -TEST_F(DBStatisticsTest, ResetStats) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - for (int i = 0; i < 2; ++i) { - // pick arbitrary ticker and histogram. On first iteration they're zero - // because db is unused. On second iteration they're zero due to Reset(). - ASSERT_EQ(0, TestGetTickerCount(options, NUMBER_KEYS_WRITTEN)); - HistogramData histogram_data; - options.statistics->histogramData(DB_WRITE, &histogram_data); - ASSERT_EQ(0.0, histogram_data.max); - - if (i == 0) { - // The Put() makes some of the ticker/histogram stats nonzero until we - // Reset(). - ASSERT_OK(Put("hello", "rocksdb")); - ASSERT_EQ(1, TestGetTickerCount(options, NUMBER_KEYS_WRITTEN)); - options.statistics->histogramData(DB_WRITE, &histogram_data); - ASSERT_GT(histogram_data.max, 0.0); - ASSERT_OK(options.statistics->Reset()); - } - } -} - -TEST_F(DBStatisticsTest, ExcludeTickers) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - options.statistics->set_stats_level(StatsLevel::kExceptTickers); - ASSERT_OK(Put("foo", "value")); - ASSERT_EQ(0, options.statistics->getTickerCount(BYTES_WRITTEN)); - options.statistics->set_stats_level(StatsLevel::kExceptHistogramOrTimers); - Reopen(options); - ASSERT_EQ("value", Get("foo")); - ASSERT_GT(options.statistics->getTickerCount(BYTES_READ), 0); -} - - -TEST_F(DBStatisticsTest, VerifyChecksumReadStat) { - Options options = CurrentOptions(); - options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - Reopen(options); - - // Expected to be populated regardless of `PerfLevel` in user thread - SetPerfLevel(kDisable); - - { - // Scenario 0: only WAL data. Not verified so require ticker to be zero. - ASSERT_OK(Put("foo", "value")); - ASSERT_OK(db_->VerifyFileChecksums(ReadOptions())); - ASSERT_OK(db_->VerifyChecksum()); - ASSERT_EQ(0, - options.statistics->getTickerCount(VERIFY_CHECKSUM_READ_BYTES)); - } - - // Create one SST. - ASSERT_OK(Flush()); - std::unordered_map table_files; - uint64_t table_files_size = 0; - GetAllDataFiles(kTableFile, &table_files, &table_files_size); - - { - // Scenario 1: Table verified in `VerifyFileChecksums()`. This should read - // the whole file so we require the ticker stat exactly matches the file - // size. - ASSERT_OK(options.statistics->Reset()); - ASSERT_OK(db_->VerifyFileChecksums(ReadOptions())); - ASSERT_EQ(table_files_size, - options.statistics->getTickerCount(VERIFY_CHECKSUM_READ_BYTES)); - } - - { - // Scenario 2: Table verified in `VerifyChecksum()`. This opens a - // `TableReader` to verify each block. It can involve duplicate reads of the - // same data so we set a lower-bound only. - ASSERT_OK(options.statistics->Reset()); - ASSERT_OK(db_->VerifyChecksum()); - ASSERT_GE(options.statistics->getTickerCount(VERIFY_CHECKSUM_READ_BYTES), - table_files_size); - } -} - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_table_properties_test.cc b/db/db_table_properties_test.cc deleted file mode 100644 index 7be05e93c..000000000 --- a/db/db_table_properties_test.cc +++ /dev/null @@ -1,623 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include -#include -#include - -#include "db/db_test_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/db.h" -#include "rocksdb/types.h" -#include "rocksdb/utilities/table_properties_collectors.h" -#include "table/format.h" -#include "table/meta_blocks.h" -#include "table/table_properties_internal.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" - - -namespace ROCKSDB_NAMESPACE { - -// A helper function that ensures the table properties returned in -// `GetPropertiesOfAllTablesTest` is correct. -// This test assumes entries size is different for each of the tables. -namespace { - -void VerifyTableProperties(DB* db, uint64_t expected_entries_size) { - TablePropertiesCollection props; - ASSERT_OK(db->GetPropertiesOfAllTables(&props)); - - ASSERT_EQ(4U, props.size()); - std::unordered_set unique_entries; - - // Indirect test - uint64_t sum = 0; - for (const auto& item : props) { - unique_entries.insert(item.second->num_entries); - sum += item.second->num_entries; - } - - ASSERT_EQ(props.size(), unique_entries.size()); - ASSERT_EQ(expected_entries_size, sum); - - VerifySstUniqueIds(props); -} -} // anonymous namespace - -class DBTablePropertiesTest : public DBTestBase, - public testing::WithParamInterface { - public: - DBTablePropertiesTest() - : DBTestBase("db_table_properties_test", /*env_do_fsync=*/false) {} - TablePropertiesCollection TestGetPropertiesOfTablesInRange( - std::vector ranges, std::size_t* num_properties = nullptr, - std::size_t* num_files = nullptr); -}; - -TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) { - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 8; - // Part of strategy to prevent pinning table files - options.max_open_files = 42; - Reopen(options); - - // Create 4 tables - for (int table = 0; table < 4; ++table) { - // Use old meta name for table properties for one file - if (table == 3) { - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::WritePropertiesBlock:Meta", [&](void* meta) { - *reinterpret_cast(meta) = - &kPropertiesBlockOldName; - }); - SyncPoint::GetInstance()->EnableProcessing(); - } - // Build file - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK( - db_->Put(WriteOptions(), std::to_string(table * 100 + i), "val")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - SyncPoint::GetInstance()->DisableProcessing(); - std::string original_session_id; - ASSERT_OK(db_->GetDbSessionId(original_session_id)); - - // Part of strategy to prevent pinning table files - SyncPoint::GetInstance()->SetCallBack( - "VersionEditHandler::LoadTables:skip_load_table_files", - [&](void* skip_load) { *reinterpret_cast(skip_load) = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - // 1. Read table properties directly from file - Reopen(options); - // Clear out auto-opened files - dbfull()->TEST_table_cache()->EraseUnRefEntries(); - ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U); - VerifyTableProperties(db_, 10 + 11 + 12 + 13); - - // 2. Put two tables to table cache and - Reopen(options); - // Clear out auto-opened files - dbfull()->TEST_table_cache()->EraseUnRefEntries(); - ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U); - // fetch key from 1st and 2nd table, which will internally place that table to - // the table cache. - for (int i = 0; i < 2; ++i) { - Get(std::to_string(i * 100 + 0)); - } - - VerifyTableProperties(db_, 10 + 11 + 12 + 13); - - // 3. Put all tables to table cache - Reopen(options); - // fetch key from all tables, which will place them in table cache. - for (int i = 0; i < 4; ++i) { - Get(std::to_string(i * 100 + 0)); - } - VerifyTableProperties(db_, 10 + 11 + 12 + 13); - - // 4. Try to read CORRUPT properties (a) directly from file, and (b) - // through reader on Get - - // It's not practical to prevent table file read on Open, so we - // corrupt after open and after purging table cache. - for (bool direct : {true, false}) { - Reopen(options); - // Clear out auto-opened files - dbfull()->TEST_table_cache()->EraseUnRefEntries(); - ASSERT_EQ(dbfull()->TEST_table_cache()->GetUsage(), 0U); - - TablePropertiesCollection props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); - std::string sst_file = props.begin()->first; - - // Corrupt the file's TableProperties using session id - std::string contents; - ASSERT_OK( - ReadFileToString(env_->GetFileSystem().get(), sst_file, &contents)); - size_t pos = contents.find(original_session_id); - ASSERT_NE(pos, std::string::npos); - ASSERT_OK(test::CorruptFile(env_, sst_file, static_cast(pos), 1, - /*verify checksum fails*/ false)); - - // Try to read CORRUPT properties - if (direct) { - ASSERT_TRUE(db_->GetPropertiesOfAllTables(&props).IsCorruption()); - } else { - bool found_corruption = false; - for (int i = 0; i < 4; ++i) { - std::string result = Get(std::to_string(i * 100 + 0)); - if (result.find_first_of("Corruption: block checksum mismatch") != - std::string::npos) { - found_corruption = true; - } - } - ASSERT_TRUE(found_corruption); - } - - // UN-corrupt file for next iteration - ASSERT_OK(test::CorruptFile(env_, sst_file, static_cast(pos), 1, - /*verify checksum fails*/ false)); - } - - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBTablePropertiesTest, InvalidIgnored) { - // RocksDB versions 2.5 - 2.7 generate some properties that Block considers - // invalid in some way. This approximates that. - - // Inject properties block data that Block considers invalid - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::WritePropertiesBlock:BlockData", - [&](void* block_data) { - *reinterpret_cast(block_data) = Slice("X"); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Corrupting the table properties corrupts the unique id. - // Ignore the unique id recorded in the manifest. - auto options = CurrentOptions(); - options.verify_sst_unique_id_in_manifest = false; - Reopen(options); - - // Build file - for (int i = 0; i < 10; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), std::to_string(i), "val")); - } - ASSERT_OK(db_->Flush(FlushOptions())); - - SyncPoint::GetInstance()->DisableProcessing(); - - // Not crashing is good enough - TablePropertiesCollection props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); -} - -TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) { - ConfigOptions options; - options.ignore_unsupported_options = false; - - std::shared_ptr factory; - std::string id = CompactOnDeletionCollectorFactory::kClassName(); - ASSERT_OK( - TablePropertiesCollectorFactory::CreateFromString(options, id, &factory)); - auto del_factory = factory->CheckedCast(); - ASSERT_NE(del_factory, nullptr); - ASSERT_EQ(0U, del_factory->GetWindowSize()); - ASSERT_EQ(0U, del_factory->GetDeletionTrigger()); - ASSERT_EQ(0.0, del_factory->GetDeletionRatio()); - ASSERT_OK(TablePropertiesCollectorFactory::CreateFromString( - options, "window_size=100; deletion_trigger=90; id=" + id, &factory)); - del_factory = factory->CheckedCast(); - ASSERT_NE(del_factory, nullptr); - ASSERT_EQ(100U, del_factory->GetWindowSize()); - ASSERT_EQ(90U, del_factory->GetDeletionTrigger()); - ASSERT_EQ(0.0, del_factory->GetDeletionRatio()); - ASSERT_OK(TablePropertiesCollectorFactory::CreateFromString( - options, - "window_size=100; deletion_trigger=90; deletion_ratio=0.5; id=" + id, - &factory)); - del_factory = factory->CheckedCast(); - ASSERT_NE(del_factory, nullptr); - ASSERT_EQ(100U, del_factory->GetWindowSize()); - ASSERT_EQ(90U, del_factory->GetDeletionTrigger()); - ASSERT_EQ(0.5, del_factory->GetDeletionRatio()); -} - -TablePropertiesCollection -DBTablePropertiesTest::TestGetPropertiesOfTablesInRange( - std::vector ranges, std::size_t* num_properties, - std::size_t* num_files) { - // Since we deref zero element in the vector it can not be empty - // otherwise we pass an address to some random memory - EXPECT_GT(ranges.size(), 0U); - // run the query - TablePropertiesCollection props; - EXPECT_OK(db_->GetPropertiesOfTablesInRange( - db_->DefaultColumnFamily(), &ranges[0], ranges.size(), &props)); - - // Make sure that we've received properties for those and for those files - // only which fall within requested ranges - std::vector vmd; - db_->GetLiveFilesMetaData(&vmd); - for (auto& md : vmd) { - std::string fn = md.db_path + md.name; - bool in_range = false; - for (auto& r : ranges) { - // smallestkey < limit && largestkey >= start - if (r.limit.compare(md.smallestkey) >= 0 && - r.start.compare(md.largestkey) <= 0) { - in_range = true; - EXPECT_GT(props.count(fn), 0); - } - } - if (!in_range) { - EXPECT_EQ(props.count(fn), 0); - } - } - - if (num_properties) { - *num_properties = props.size(); - } - - if (num_files) { - *num_files = vmd.size(); - } - return props; -} - -TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesInRange) { - // Fixed random sead - Random rnd(301); - - Options options; - options.create_if_missing = true; - options.write_buffer_size = 4096; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 2; - options.target_file_size_base = 2048; - options.max_bytes_for_level_base = 40960; - options.max_bytes_for_level_multiplier = 4; - options.hard_pending_compaction_bytes_limit = 16 * 1024; - options.num_levels = 8; - options.env = env_; - - DestroyAndReopen(options); - - // build a decent LSM - for (int i = 0; i < 10000; i++) { - ASSERT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - if (NumTableFilesAtLevel(0) == 0) { - ASSERT_OK(Put(test::RandomKey(&rnd, 5), rnd.RandomString(102))); - ASSERT_OK(Flush()); - } - - ASSERT_OK(db_->PauseBackgroundWork()); - - // Ensure that we have at least L0, L1 and L2 - ASSERT_GT(NumTableFilesAtLevel(0), 0); - ASSERT_GT(NumTableFilesAtLevel(1), 0); - ASSERT_GT(NumTableFilesAtLevel(2), 0); - - // Query the largest range - std::size_t num_properties, num_files; - TestGetPropertiesOfTablesInRange( - {Range(test::RandomKey(&rnd, 5, test::RandomKeyType::SMALLEST), - test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST))}, - &num_properties, &num_files); - ASSERT_EQ(num_properties, num_files); - - // Query the empty range - TestGetPropertiesOfTablesInRange( - {Range(test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST), - test::RandomKey(&rnd, 5, test::RandomKeyType::SMALLEST))}, - &num_properties, &num_files); - ASSERT_GT(num_files, 0); - ASSERT_EQ(num_properties, 0); - - // Query the middle rangee - TestGetPropertiesOfTablesInRange( - {Range(test::RandomKey(&rnd, 5, test::RandomKeyType::MIDDLE), - test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST))}, - &num_properties, &num_files); - ASSERT_GT(num_files, 0); - ASSERT_GT(num_files, num_properties); - ASSERT_GT(num_properties, 0); - - // Query a bunch of random ranges - for (int j = 0; j < 100; j++) { - // create a bunch of ranges - std::vector random_keys; - // Random returns numbers with zero included - // when we pass empty ranges TestGetPropertiesOfTablesInRange() - // derefs random memory in the empty ranges[0] - // so want to be greater than zero and even since - // the below loop requires that random_keys.size() to be even. - auto n = 2 * (rnd.Uniform(50) + 1); - - for (uint32_t i = 0; i < n; ++i) { - random_keys.push_back(test::RandomKey(&rnd, 5)); - } - - ASSERT_GT(random_keys.size(), 0U); - ASSERT_EQ((random_keys.size() % 2), 0U); - - std::vector ranges; - auto it = random_keys.begin(); - while (it != random_keys.end()) { - ranges.push_back(Range(*it, *(it + 1))); - it += 2; - } - - TestGetPropertiesOfTablesInRange(std::move(ranges)); - } -} - -TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) { - std::string kExtraCfName = "pikachu"; - CreateAndReopenWithCF({kExtraCfName}, CurrentOptions()); - - // Create one table per CF, then verify it was created with the column family - // name property. - for (uint32_t cf = 0; cf < 2; ++cf) { - ASSERT_OK(Put(cf, "key", "val")); - ASSERT_OK(Flush(cf)); - - TablePropertiesCollection fname_to_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props)); - ASSERT_EQ(1U, fname_to_props.size()); - - std::string expected_cf_name; - if (cf > 0) { - expected_cf_name = kExtraCfName; - } else { - expected_cf_name = kDefaultColumnFamilyName; - } - ASSERT_EQ(expected_cf_name, - fname_to_props.begin()->second->column_family_name); - ASSERT_EQ(cf, static_cast( - fname_to_props.begin()->second->column_family_id)); - } -} - -TEST_F(DBTablePropertiesTest, GetDbIdentifiersProperty) { - CreateAndReopenWithCF({"goku"}, CurrentOptions()); - - for (uint32_t cf = 0; cf < 2; ++cf) { - ASSERT_OK(Put(cf, "key", "val")); - ASSERT_OK(Put(cf, "foo", "bar")); - ASSERT_OK(Flush(cf)); - - TablePropertiesCollection fname_to_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props)); - ASSERT_EQ(1U, fname_to_props.size()); - - std::string id, sid; - ASSERT_OK(db_->GetDbIdentity(id)); - ASSERT_OK(db_->GetDbSessionId(sid)); - ASSERT_EQ(id, fname_to_props.begin()->second->db_id); - ASSERT_EQ(sid, fname_to_props.begin()->second->db_session_id); - } -} - -class DBTableHostnamePropertyTest - : public DBTestBase, - public ::testing::WithParamInterface> { - public: - DBTableHostnamePropertyTest() - : DBTestBase("db_table_hostname_property_test", - /*env_do_fsync=*/false) {} -}; - -TEST_P(DBTableHostnamePropertyTest, DbHostLocationProperty) { - option_config_ = std::get<0>(GetParam()); - Options opts = CurrentOptions(); - std::string expected_host_id = std::get<1>(GetParam()); - ; - if (expected_host_id == kHostnameForDbHostId) { - ASSERT_OK(env_->GetHostNameString(&expected_host_id)); - } else { - opts.db_host_id = expected_host_id; - } - CreateAndReopenWithCF({"goku"}, opts); - - for (uint32_t cf = 0; cf < 2; ++cf) { - ASSERT_OK(Put(cf, "key", "val")); - ASSERT_OK(Put(cf, "foo", "bar")); - ASSERT_OK(Flush(cf)); - - TablePropertiesCollection fname_to_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props)); - ASSERT_EQ(1U, fname_to_props.size()); - - ASSERT_EQ(fname_to_props.begin()->second->db_host_id, expected_host_id); - } -} - -INSTANTIATE_TEST_CASE_P( - DBTableHostnamePropertyTest, DBTableHostnamePropertyTest, - ::testing::Values( - // OptionConfig, override db_host_location - std::make_tuple(DBTestBase::OptionConfig::kDefault, - kHostnameForDbHostId), - std::make_tuple(DBTestBase::OptionConfig::kDefault, "foobar"), - std::make_tuple(DBTestBase::OptionConfig::kDefault, ""), - std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix, - kHostnameForDbHostId), - std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix, - "foobar"), - std::make_tuple(DBTestBase::OptionConfig::kPlainTableFirstBytePrefix, - ""))); - -class DeletionTriggeredCompactionTestListener : public EventListener { - public: - void OnCompactionBegin(DB*, const CompactionJobInfo& ci) override { - ASSERT_EQ(ci.compaction_reason, - CompactionReason::kFilesMarkedForCompaction); - } - - void OnCompactionCompleted(DB*, const CompactionJobInfo& ci) override { - ASSERT_EQ(ci.compaction_reason, - CompactionReason::kFilesMarkedForCompaction); - } -}; - -TEST_P(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) { - int kNumKeys = 1000; - int kWindowSize = 100; - int kNumDelsTrigger = 90; - std::shared_ptr compact_on_del = - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger); - - Options opts = CurrentOptions(); - opts.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - opts.table_properties_collector_factories.emplace_back(compact_on_del); - - if (GetParam() == "kCompactionStyleUniversal") { - opts.compaction_style = kCompactionStyleUniversal; - } - Reopen(opts); - - // add an L1 file to prevent tombstones from dropping due to obsolescence - // during flush - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - - DeletionTriggeredCompactionTestListener* listener = - new DeletionTriggeredCompactionTestListener(); - opts.listeners.emplace_back(listener); - Reopen(opts); - - for (int i = 0; i < kNumKeys; ++i) { - if (i >= kNumKeys - kWindowSize && - i < kNumKeys - kWindowSize + kNumDelsTrigger) { - ASSERT_OK(Delete(Key(i))); - } else { - ASSERT_OK(Put(Key(i), "val")); - } - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - - // Change the window size and deletion trigger and ensure new values take - // effect - kWindowSize = 50; - kNumDelsTrigger = 40; - static_cast(compact_on_del.get()) - ->SetWindowSize(kWindowSize); - static_cast(compact_on_del.get()) - ->SetDeletionTrigger(kNumDelsTrigger); - for (int i = 0; i < kNumKeys; ++i) { - if (i >= kNumKeys - kWindowSize && - i < kNumKeys - kWindowSize + kNumDelsTrigger) { - ASSERT_OK(Delete(Key(i))); - } else { - ASSERT_OK(Put(Key(i), "val")); - } - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - - // Change the window size to disable delete triggered compaction - kWindowSize = 0; - static_cast(compact_on_del.get()) - ->SetWindowSize(kWindowSize); - static_cast(compact_on_del.get()) - ->SetDeletionTrigger(kNumDelsTrigger); - for (int i = 0; i < kNumKeys; ++i) { - if (i >= kNumKeys - kWindowSize && - i < kNumKeys - kWindowSize + kNumDelsTrigger) { - ASSERT_OK(Delete(Key(i))); - } else { - ASSERT_OK(Put(Key(i), "val")); - } - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_LT(0, opts.statistics->getTickerCount(COMPACT_WRITE_BYTES_MARKED)); - ASSERT_LT(0, opts.statistics->getTickerCount(COMPACT_READ_BYTES_MARKED)); -} - -TEST_P(DBTablePropertiesTest, RatioBasedDeletionTriggeredCompactionMarking) { - constexpr int kNumKeys = 1000; - constexpr int kWindowSize = 0; - constexpr int kNumDelsTrigger = 0; - constexpr double kDeletionRatio = 0.1; - std::shared_ptr compact_on_del = - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger, - kDeletionRatio); - - Options opts = CurrentOptions(); - opts.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - opts.table_properties_collector_factories.emplace_back(compact_on_del); - - Reopen(opts); - - // Add an L2 file to prevent tombstones from dropping due to obsolescence - // during flush - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - MoveFilesToLevel(2); - - auto* listener = new DeletionTriggeredCompactionTestListener(); - opts.listeners.emplace_back(listener); - Reopen(opts); - - // Generate one L0 with kNumKeys Put. - for (int i = 0; i < kNumKeys; ++i) { - ASSERT_OK(Put(Key(i), "not important")); - } - ASSERT_OK(Flush()); - - // Generate another L0 with kNumKeys Delete. - // This file, due to deletion ratio, will trigger compaction: 2@0 files to L1. - // The resulting L1 file has only one tombstone for user key 'Key(0)'. - // Again, due to deletion ratio, a compaction will be triggered: 1@1 + 1@2 - // files to L2. However, the resulting file is empty because the tombstone - // and value are both dropped. - for (int i = 0; i < kNumKeys; ++i) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - for (int i = 0; i < 3; ++i) { - ASSERT_EQ(0, NumTableFilesAtLevel(i)); - } -} - -INSTANTIATE_TEST_CASE_P(DBTablePropertiesTest, DBTablePropertiesTest, - ::testing::Values("kCompactionStyleLevel", - "kCompactionStyleUniversal")); - -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_tailing_iter_test.cc b/db/db_tailing_iter_test.cc deleted file mode 100644 index 964e06eb3..000000000 --- a/db/db_tailing_iter_test.cc +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -// Introduction of SyncPoint effectively disabled building and running this test -// in Release build. -// which is a pity, it is a good test - -#include "db/db_test_util.h" -#include "db/forward_iterator.h" -#include "port/stack_trace.h" - -namespace ROCKSDB_NAMESPACE { - -class DBTestTailingIterator : public DBTestBase, - public ::testing::WithParamInterface { - public: - DBTestTailingIterator() - : DBTestBase("db_tailing_iterator_test", /*env_do_fsync=*/true) {} -}; - -INSTANTIATE_TEST_CASE_P(DBTestTailingIterator, DBTestTailingIterator, - ::testing::Bool()); - -TEST_P(DBTestTailingIterator, TailingIteratorSingle) { - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - - std::unique_ptr iter(db_->NewIterator(read_options)); - iter->SeekToFirst(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - - // add a record and check that iter can see it - ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor")); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "mirko"); - - iter->Next(); - ASSERT_TRUE(!iter->Valid()); -} - -TEST_P(DBTestTailingIterator, TailingIteratorKeepAdding) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - std::unique_ptr iter(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iter->status()); - std::string value(1024, 'a'); - - const int num_records = 10000; - for (int i = 0; i < num_records; ++i) { - char buf[32]; - snprintf(buf, sizeof(buf), "%016d", i); - - Slice key(buf, 16); - ASSERT_OK(Put(1, key, value)); - - iter->Seek(key); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(key), 0); - } -} - -TEST_P(DBTestTailingIterator, TailingIteratorSeekToNext) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - std::unique_ptr iter(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iter->status()); - std::unique_ptr itern(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(itern->status()); - std::string value(1024, 'a'); - - const int num_records = 1000; - for (int i = 1; i < num_records; ++i) { - char buf1[32]; - char buf2[32]; - snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5); - - Slice key(buf1, 20); - ASSERT_OK(Put(1, key, value)); - - if (i % 100 == 99) { - ASSERT_OK(Flush(1)); - } - - snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2); - Slice target(buf2, 20); - iter->Seek(target); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(key), 0); - if (i == 1) { - itern->SeekToFirst(); - } else { - itern->Next(); - } - ASSERT_TRUE(itern->Valid()); - ASSERT_EQ(itern->key().compare(key), 0); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - for (int i = 2 * num_records; i > 0; --i) { - char buf1[32]; - char buf2[32]; - snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5); - - Slice key(buf1, 20); - ASSERT_OK(Put(1, key, value)); - - if (i % 100 == 99) { - ASSERT_OK(Flush(1)); - } - - snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2); - Slice target(buf2, 20); - iter->Seek(target); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(key), 0); - } -} - -TEST_P(DBTestTailingIterator, TailingIteratorTrimSeekToNext) { - const uint64_t k150KB = 150 * 1024; - Options options; - options.write_buffer_size = k150KB; - options.max_write_buffer_number = 3; - options.min_write_buffer_number_to_merge = 2; - options.env = env_; - CreateAndReopenWithCF({"pikachu"}, options); - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - int num_iters, deleted_iters; - - char bufe[32]; - snprintf(bufe, sizeof(bufe), "00b0%016d", 0); - Slice keyu(bufe, 20); - read_options.iterate_upper_bound = &keyu; - std::unique_ptr iter(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iter->status()); - std::unique_ptr itern(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(itern->status()); - std::unique_ptr iterh(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iterh->status()); - std::string value(1024, 'a'); - bool file_iters_deleted = false; - bool file_iters_renewed_null = false; - bool file_iters_renewed_copy = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ForwardIterator::SeekInternal:Return", [&](void* arg) { - ForwardIterator* fiter = reinterpret_cast(arg); - ASSERT_TRUE(!file_iters_deleted || - fiter->TEST_CheckDeletedIters(&deleted_iters, &num_iters)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ForwardIterator::Next:Return", [&](void* arg) { - ForwardIterator* fiter = reinterpret_cast(arg); - ASSERT_TRUE(!file_iters_deleted || - fiter->TEST_CheckDeletedIters(&deleted_iters, &num_iters)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ForwardIterator::RenewIterators:Null", - [&](void* /*arg*/) { file_iters_renewed_null = true; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ForwardIterator::RenewIterators:Copy", - [&](void* /*arg*/) { file_iters_renewed_copy = true; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - const int num_records = 1000; - for (int i = 1; i < num_records; ++i) { - char buf1[32]; - char buf2[32]; - char buf3[32]; - char buf4[32]; - snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5); - snprintf(buf3, sizeof(buf3), "00b0%016d", i * 5); - - Slice key(buf1, 20); - ASSERT_OK(Put(1, key, value)); - Slice keyn(buf3, 20); - ASSERT_OK(Put(1, keyn, value)); - - if (i % 100 == 99) { - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - if (i == 299) { - file_iters_deleted = true; - } - snprintf(buf4, sizeof(buf4), "00a0%016d", i * 5 / 2); - Slice target(buf4, 20); - iterh->Seek(target); - ASSERT_TRUE(iter->Valid()); - for (int j = (i + 1) * 5 / 2; j < i * 5; j += 5) { - iterh->Next(); - ASSERT_TRUE(iterh->Valid()); - } - if (i == 299) { - file_iters_deleted = false; - } - } - - file_iters_deleted = true; - snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2); - Slice target(buf2, 20); - iter->Seek(target); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(key), 0); - ASSERT_LE(num_iters, 1); - if (i == 1) { - itern->SeekToFirst(); - } else { - itern->Next(); - } - ASSERT_TRUE(itern->Valid()); - ASSERT_EQ(itern->key().compare(key), 0); - ASSERT_LE(num_iters, 1); - file_iters_deleted = false; - } - ASSERT_TRUE(file_iters_renewed_null); - ASSERT_TRUE(file_iters_renewed_copy); - iter = nullptr; - itern = nullptr; - iterh = nullptr; - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - read_options.read_tier = kBlockCacheTier; - std::unique_ptr iteri(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iteri->status()); - char buf5[32]; - snprintf(buf5, sizeof(buf5), "00a0%016d", (num_records / 2) * 5 - 2); - Slice target1(buf5, 20); - iteri->Seek(target1); - ASSERT_TRUE(iteri->status().IsIncomplete()); - iteri = nullptr; - - read_options.read_tier = kReadAllTier; - options.table_factory.reset(NewBlockBasedTableFactory()); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - iter.reset(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iter->status()); - for (int i = 2 * num_records; i > 0; --i) { - char buf1[32]; - char buf2[32]; - snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5); - - Slice key(buf1, 20); - ASSERT_OK(Put(1, key, value)); - - if (i % 100 == 99) { - ASSERT_OK(Flush(1)); - } - - snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2); - Slice target(buf2, 20); - iter->Seek(target); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(key), 0); - } -} - -TEST_P(DBTestTailingIterator, TailingIteratorDeletes) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - - std::unique_ptr iter(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iter->status()); - - // write a single record, read it using the iterator, then delete it - ASSERT_OK(Put(1, "0test", "test")); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "0test"); - ASSERT_OK(Delete(1, "0test")); - - // write many more records - const int num_records = 10000; - std::string value(1024, 'A'); - - for (int i = 0; i < num_records; ++i) { - char buf[32]; - snprintf(buf, sizeof(buf), "1%015d", i); - - Slice key(buf, 16); - ASSERT_OK(Put(1, key, value)); - } - - // force a flush to make sure that no records are read from memtable - ASSERT_OK(Flush(1)); - - // skip "0test" - iter->Next(); - - // make sure we can read all new records using the existing iterator - int count = 0; - for (; iter->Valid(); iter->Next(), ++count) - ; - - ASSERT_EQ(count, num_records); -} - -TEST_P(DBTestTailingIterator, TailingIteratorPrefixSeek) { - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - Options options = CurrentOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(2)); - options.memtable_factory.reset(NewHashSkipListRepFactory(16)); - options.allow_concurrent_memtable_write = false; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - std::unique_ptr iter(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(iter->status()); - ASSERT_OK(Put(1, "0101", "test")); - - ASSERT_OK(Flush(1)); - - ASSERT_OK(Put(1, "0202", "test")); - - // Seek(0102) shouldn't find any records since 0202 has a different prefix - iter->Seek("0102"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("0202"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "0202"); - - iter->Next(); - ASSERT_TRUE(!iter->Valid()); -} - -TEST_P(DBTestTailingIterator, TailingIteratorIncomplete) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - read_options.read_tier = kBlockCacheTier; - - std::string key("key"); - std::string value("value"); - - ASSERT_OK(db_->Put(WriteOptions(), key, value)); - - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_OK(iter->status()); - iter->SeekToFirst(); - // we either see the entry or it's not in cache - ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - iter->SeekToFirst(); - // should still be true after compaction - ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete()); -} - -TEST_P(DBTestTailingIterator, TailingIteratorSeekToSame) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 1000; - CreateAndReopenWithCF({"pikachu"}, options); - - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - const int NROWS = 10000; - // Write rows with keys 00000, 00002, 00004 etc. - for (int i = 0; i < NROWS; ++i) { - char buf[100]; - snprintf(buf, sizeof(buf), "%05d", 2 * i); - std::string key(buf); - std::string value("value"); - ASSERT_OK(db_->Put(WriteOptions(), key, value)); - } - - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_OK(iter->status()); - // Seek to 00001. We expect to find 00002. - std::string start_key = "00001"; - iter->Seek(start_key); - ASSERT_TRUE(iter->Valid()); - - std::string found = iter->key().ToString(); - ASSERT_EQ("00002", found); - - // Now seek to the same key. The iterator should remain in the same - // position. - iter->Seek(found); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(found, iter->key().ToString()); -} - -// Sets iterate_upper_bound and verifies that ForwardIterator doesn't call -// Seek() on immutable iterators when target key is >= prev_key and all -// iterators, including the memtable iterator, are over the upper bound. -TEST_P(DBTestTailingIterator, TailingIteratorUpperBound) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - - const Slice upper_bound("20", 3); - ReadOptions read_options; - read_options.tailing = true; - read_options.iterate_upper_bound = &upper_bound; - if (GetParam()) { - read_options.async_io = true; - } - ASSERT_OK(Put(1, "11", "11")); - ASSERT_OK(Put(1, "12", "12")); - ASSERT_OK(Put(1, "22", "22")); - ASSERT_OK(Flush(1)); // flush all those keys to an immutable SST file - - // Add another key to the memtable. - ASSERT_OK(Put(1, "21", "21")); - - std::unique_ptr it(db_->NewIterator(read_options, handles_[1])); - ASSERT_OK(it->status()); - it->Seek("12"); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("12", it->key().ToString()); - - it->Next(); - // Not valid since "21" is over the upper bound. - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - // This keeps track of the number of times NeedToSeekImmutable() was true. - int immutable_seeks = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ForwardIterator::SeekInternal:Immutable", - [&](void* /*arg*/) { ++immutable_seeks; }); - - // Seek to 13. This should not require any immutable seeks. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - it->Seek("13"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - if (GetParam()) { - ASSERT_EQ(1, immutable_seeks); - } else { - ASSERT_EQ(0, immutable_seeks); - } -} - -TEST_P(DBTestTailingIterator, TailingIteratorGap) { - // level 1: [20, 25] [35, 40] - // level 2: [10 - 15] [45 - 50] - // level 3: [20, 30, 40] - // Previously there is a bug in tailing_iterator that if there is a gap in - // lower level, the key will be skipped if it is within the range between - // the largest key of index n file and the smallest key of index n+1 file - // if both file fit in that gap. In this example, 25 < key < 35 - // https://github.com/facebook/rocksdb/issues/1372 - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - ASSERT_OK(Put(1, "20", "20")); - ASSERT_OK(Put(1, "30", "30")); - ASSERT_OK(Put(1, "40", "40")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(3, 1); - - ASSERT_OK(Put(1, "10", "10")); - ASSERT_OK(Put(1, "15", "15")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "45", "45")); - ASSERT_OK(Put(1, "50", "50")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - - ASSERT_OK(Put(1, "20", "20")); - ASSERT_OK(Put(1, "25", "25")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "35", "35")); - ASSERT_OK(Put(1, "40", "40")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(1, 1); - - ColumnFamilyMetaData meta; - db_->GetColumnFamilyMetaData(handles_[1], &meta); - - std::unique_ptr it(db_->NewIterator(read_options, handles_[1])); - it->Seek("30"); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("30", it->key().ToString()); - - it->Next(); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("35", it->key().ToString()); - - it->Next(); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ("40", it->key().ToString()); - - ASSERT_OK(it->status()); -} - -TEST_P(DBTestTailingIterator, SeekWithUpperBoundBug) { - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - const Slice upper_bound("cc", 3); - read_options.iterate_upper_bound = &upper_bound; - - // 1st L0 file - ASSERT_OK(db_->Put(WriteOptions(), "aa", "SEEN")); - ASSERT_OK(Flush()); - - // 2nd L0 file - ASSERT_OK(db_->Put(WriteOptions(), "zz", "NOT-SEEN")); - ASSERT_OK(Flush()); - - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_OK(iter->status()); - - iter->Seek("aa"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "aa"); -} - -TEST_P(DBTestTailingIterator, SeekToFirstWithUpperBoundBug) { - ReadOptions read_options; - read_options.tailing = true; - if (GetParam()) { - read_options.async_io = true; - } - const Slice upper_bound("cc", 3); - read_options.iterate_upper_bound = &upper_bound; - - // 1st L0 file - ASSERT_OK(db_->Put(WriteOptions(), "aa", "SEEN")); - ASSERT_OK(Flush()); - - // 2nd L0 file - ASSERT_OK(db_->Put(WriteOptions(), "zz", "NOT-SEEN")); - ASSERT_OK(Flush()); - - std::unique_ptr iter(db_->NewIterator(read_options)); - ASSERT_OK(iter->status()); - - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "aa"); - - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().ToString(), "aa"); -} - -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_test.cc b/db/db_test.cc deleted file mode 100644 index 05ee14fe2..000000000 --- a/db/db_test.cc +++ /dev/null @@ -1,7338 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -// Introduction of SyncPoint effectively disabled building and running this test -// in Release build. -// which is a pity, it is a good test -#include - -#include -#include -#include -#include -#include - -#ifndef OS_WIN -#include -#endif -#ifdef OS_SOLARIS -#include -#endif - -#include "cache/lru_cache.h" -#include "db/blob/blob_index.h" -#include "db/blob/blob_log_format.h" -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "db/job_context.h" -#include "db/version_set.h" -#include "db/write_batch_internal.h" -#include "env/mock_env.h" -#include "file/filename.h" -#include "monitoring/thread_status_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/compaction_filter.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/experimental.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/options.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/slice.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/snapshot.h" -#include "rocksdb/table.h" -#include "rocksdb/table_properties.h" -#include "rocksdb/thread_status.h" -#include "rocksdb/types.h" -#include "rocksdb/utilities/checkpoint.h" -#include "rocksdb/utilities/optimistic_transaction_db.h" -#include "rocksdb/utilities/write_batch_with_index.h" -#include "table/mock_table.h" -#include "table/scoped_arena_iterator.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/compression.h" -#include "util/mutexlock.h" -#include "util/random.h" -#include "util/rate_limiter.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -// Note that whole DBTest and its child classes disable fsync on files -// and directories for speed. -// If fsync needs to be covered in a test, put it in other places. -class DBTest : public DBTestBase { - public: - DBTest() : DBTestBase("db_test", /*env_do_fsync=*/false) {} -}; - -class DBTestWithParam - : public DBTest, - public testing::WithParamInterface> { - public: - DBTestWithParam() { - max_subcompactions_ = std::get<0>(GetParam()); - exclusive_manual_compaction_ = std::get<1>(GetParam()); - } - - // Required if inheriting from testing::WithParamInterface<> - static void SetUpTestCase() {} - static void TearDownTestCase() {} - - uint32_t max_subcompactions_; - bool exclusive_manual_compaction_; -}; - -TEST_F(DBTest, MockEnvTest) { - std::unique_ptr env{MockEnv::Create(Env::Default())}; - Options options; - options.create_if_missing = true; - options.env = env.get(); - DB* db; - - const Slice keys[] = {Slice("aaa"), Slice("bbb"), Slice("ccc")}; - const Slice vals[] = {Slice("foo"), Slice("bar"), Slice("baz")}; - - ASSERT_OK(DB::Open(options, "/dir/db", &db)); - for (size_t i = 0; i < 3; ++i) { - ASSERT_OK(db->Put(WriteOptions(), keys[i], vals[i])); - } - - for (size_t i = 0; i < 3; ++i) { - std::string res; - ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); - ASSERT_TRUE(res == vals[i]); - } - - Iterator* iterator = db->NewIterator(ReadOptions()); - iterator->SeekToFirst(); - for (size_t i = 0; i < 3; ++i) { - ASSERT_TRUE(iterator->Valid()); - ASSERT_TRUE(keys[i] == iterator->key()); - ASSERT_TRUE(vals[i] == iterator->value()); - iterator->Next(); - } - ASSERT_TRUE(!iterator->Valid()); - delete iterator; - - DBImpl* dbi = static_cast_with_check(db); - ASSERT_OK(dbi->TEST_FlushMemTable()); - - for (size_t i = 0; i < 3; ++i) { - std::string res; - ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); - ASSERT_TRUE(res == vals[i]); - } - - delete db; -} - -TEST_F(DBTest, MemEnvTest) { - std::unique_ptr env{NewMemEnv(Env::Default())}; - Options options; - options.create_if_missing = true; - options.env = env.get(); - DB* db; - - const Slice keys[] = {Slice("aaa"), Slice("bbb"), Slice("ccc")}; - const Slice vals[] = {Slice("foo"), Slice("bar"), Slice("baz")}; - - ASSERT_OK(DB::Open(options, "/dir/db", &db)); - for (size_t i = 0; i < 3; ++i) { - ASSERT_OK(db->Put(WriteOptions(), keys[i], vals[i])); - } - - for (size_t i = 0; i < 3; ++i) { - std::string res; - ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); - ASSERT_TRUE(res == vals[i]); - } - - Iterator* iterator = db->NewIterator(ReadOptions()); - iterator->SeekToFirst(); - for (size_t i = 0; i < 3; ++i) { - ASSERT_TRUE(iterator->Valid()); - ASSERT_TRUE(keys[i] == iterator->key()); - ASSERT_TRUE(vals[i] == iterator->value()); - iterator->Next(); - } - ASSERT_TRUE(!iterator->Valid()); - delete iterator; - - DBImpl* dbi = static_cast_with_check(db); - ASSERT_OK(dbi->TEST_FlushMemTable()); - - for (size_t i = 0; i < 3; ++i) { - std::string res; - ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); - ASSERT_TRUE(res == vals[i]); - } - - delete db; - - options.create_if_missing = false; - ASSERT_OK(DB::Open(options, "/dir/db", &db)); - for (size_t i = 0; i < 3; ++i) { - std::string res; - ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); - ASSERT_TRUE(res == vals[i]); - } - delete db; -} - -TEST_F(DBTest, WriteEmptyBatch) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - WriteOptions wo; - wo.sync = true; - wo.disableWAL = false; - WriteBatch empty_batch; - ASSERT_OK(dbfull()->Write(wo, &empty_batch)); - - // make sure we can re-open it. - ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options)); - ASSERT_EQ("bar", Get(1, "foo")); -} - -TEST_F(DBTest, SkipDelay) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; - CreateAndReopenWithCF({"pikachu"}, options); - - for (bool sync : {true, false}) { - for (bool disableWAL : {true, false}) { - if (sync && disableWAL) { - // sync and disableWAL is incompatible. - continue; - } - // Use a small number to ensure a large delay that is still effective - // when we do Put - // TODO(myabandeh): this is time dependent and could potentially make - // the test flaky - auto token = dbfull()->TEST_write_controler().GetDelayToken(1); - std::atomic sleep_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Sleep", - [&](void* /*arg*/) { sleep_count.fetch_add(1); }); - std::atomic wait_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Wait", - [&](void* /*arg*/) { wait_count.fetch_add(1); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.sync = sync; - wo.disableWAL = disableWAL; - wo.no_slowdown = true; - // Large enough to exceed allowance for one time interval - std::string large_value(1024, 'x'); - // Perhaps ideally this first write would fail because of delay, but - // the current implementation does not guarantee that. - dbfull()->Put(wo, "foo", large_value).PermitUncheckedError(); - // We need the 2nd write to trigger delay. This is because delay is - // estimated based on the last write size which is 0 for the first write. - ASSERT_NOK(dbfull()->Put(wo, "foo2", large_value)); - ASSERT_GE(sleep_count.load(), 0); - ASSERT_GE(wait_count.load(), 0); - token.reset(); - - token = dbfull()->TEST_write_controler().GetDelayToken(1000000); - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, "foo3", large_value)); - ASSERT_GE(sleep_count.load(), 1); - token.reset(); - } - } -} - -TEST_F(DBTest, MixedSlowdownOptions) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; - CreateAndReopenWithCF({"pikachu"}, options); - std::vector threads; - std::atomic thread_num(0); - - std::function write_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, key, "bar")); - }; - std::function write_no_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = true; - ASSERT_NOK(dbfull()->Put(wo, key, "bar")); - }; - // Use a small number to ensure a large delay that is still effective - // when we do Put - // TODO(myabandeh): this is time dependent and could potentially make - // the test flaky - auto token = dbfull()->TEST_write_controler().GetDelayToken(1); - std::atomic sleep_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:BeginWriteStallDone", [&](void* /*arg*/) { - sleep_count.fetch_add(1); - if (threads.empty()) { - for (int i = 0; i < 2; ++i) { - threads.emplace_back(write_slowdown_func); - } - for (int i = 0; i < 2; ++i) { - threads.emplace_back(write_no_slowdown_func); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.sync = false; - wo.disableWAL = false; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, "foo", "bar")); - // We need the 2nd write to trigger delay. This is because delay is - // estimated based on the last write size which is 0 for the first write. - ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2")); - token.reset(); - - for (auto& t : threads) { - t.join(); - } - ASSERT_GE(sleep_count.load(), 1); - - wo.no_slowdown = true; - ASSERT_OK(dbfull()->Put(wo, "foo3", "bar")); -} - -TEST_F(DBTest, MixedSlowdownOptionsInQueue) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; - CreateAndReopenWithCF({"pikachu"}, options); - std::vector threads; - std::atomic thread_num(0); - - std::function write_no_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = true; - ASSERT_NOK(dbfull()->Put(wo, key, "bar")); - }; - // Use a small number to ensure a large delay that is still effective - // when we do Put - // TODO(myabandeh): this is time dependent and could potentially make - // the test flaky - auto token = dbfull()->TEST_write_controler().GetDelayToken(1); - std::atomic sleep_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Sleep", [&](void* /*arg*/) { - sleep_count.fetch_add(1); - if (threads.empty()) { - for (int i = 0; i < 2; ++i) { - threads.emplace_back(write_no_slowdown_func); - } - // Sleep for 2s to allow the threads to insert themselves into the - // write queue - env_->SleepForMicroseconds(3000000ULL); - } - }); - std::atomic wait_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Wait", - [&](void* /*arg*/) { wait_count.fetch_add(1); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.sync = false; - wo.disableWAL = false; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, "foo", "bar")); - // We need the 2nd write to trigger delay. This is because delay is - // estimated based on the last write size which is 0 for the first write. - ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2")); - token.reset(); - - for (auto& t : threads) { - t.join(); - } - ASSERT_EQ(sleep_count.load(), 1); - ASSERT_GE(wait_count.load(), 0); -} - -TEST_F(DBTest, MixedSlowdownOptionsStop) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; - CreateAndReopenWithCF({"pikachu"}, options); - std::vector threads; - std::atomic thread_num(0); - - std::function write_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, key, "bar")); - }; - std::function write_no_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = true; - ASSERT_NOK(dbfull()->Put(wo, key, "bar")); - }; - std::function wakeup_writer = [&]() { - dbfull()->mutex_.Lock(); - dbfull()->bg_cv_.SignalAll(); - dbfull()->mutex_.Unlock(); - }; - // Use a small number to ensure a large delay that is still effective - // when we do Put - // TODO(myabandeh): this is time dependent and could potentially make - // the test flaky - auto token = dbfull()->TEST_write_controler().GetStopToken(); - std::atomic wait_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Wait", [&](void* /*arg*/) { - wait_count.fetch_add(1); - if (threads.empty()) { - for (int i = 0; i < 2; ++i) { - threads.emplace_back(write_slowdown_func); - } - for (int i = 0; i < 2; ++i) { - threads.emplace_back(write_no_slowdown_func); - } - // Sleep for 2s to allow the threads to insert themselves into the - // write queue - env_->SleepForMicroseconds(3000000ULL); - } - token.reset(); - threads.emplace_back(wakeup_writer); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - WriteOptions wo; - wo.sync = false; - wo.disableWAL = false; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, "foo", "bar")); - // We need the 2nd write to trigger delay. This is because delay is - // estimated based on the last write size which is 0 for the first write. - ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2")); - token.reset(); - - for (auto& t : threads) { - t.join(); - } - ASSERT_GE(wait_count.load(), 1); - - wo.no_slowdown = true; - ASSERT_OK(dbfull()->Put(wo, "foo3", "bar")); -} - -TEST_F(DBTest, LevelLimitReopen) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - - const std::string value(1024 * 1024, ' '); - int i = 0; - while (NumTableFilesAtLevel(2, 1) == 0) { - ASSERT_OK(Put(1, Key(i++), value)); - } - - options.num_levels = 1; - options.max_bytes_for_level_multiplier_additional.resize(1, 1); - Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ(s.IsInvalidArgument(), true); - ASSERT_EQ(s.ToString(), - "Invalid argument: db has more levels than options.num_levels"); - - options.num_levels = 10; - options.max_bytes_for_level_multiplier_additional.resize(10, 1); - ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options)); -} - -TEST_F(DBTest, LevelReopenWithFIFO) { - const int kLevelCount = 4; - const int kKeyCount = 5; - const int kTotalSstFileCount = kLevelCount * kKeyCount; - const int kCF = 1; - - Options options = CurrentOptions(); - // Config level0_file_num_compaction_trigger to prevent L0 files being - // automatically compacted while we are constructing a LSM tree structure - // to test multi-level FIFO compaction. - options.level0_file_num_compaction_trigger = kKeyCount + 1; - CreateAndReopenWithCF({"pikachu"}, options); - - // The expected number of files per level after each file creation. - const std::string expected_files_per_level[kLevelCount][kKeyCount] = { - {"0,0,0,1", "0,0,0,2", "0,0,0,3", "0,0,0,4", "0,0,0,5"}, - {"0,0,1,5", "0,0,2,5", "0,0,3,5", "0,0,4,5", "0,0,5,5"}, - {"0,1,5,5", "0,2,5,5", "0,3,5,5", "0,4,5,5", "0,5,5,5"}, - {"1,5,5,5", "2,5,5,5", "3,5,5,5", "4,5,5,5", "5,5,5,5"}, - }; - - const std::string expected_entries[kKeyCount][kLevelCount + 1] = { - {"[ ]", "[ a3 ]", "[ a2, a3 ]", "[ a1, a2, a3 ]", "[ a0, a1, a2, a3 ]"}, - {"[ ]", "[ b3 ]", "[ b2, b3 ]", "[ b1, b2, b3 ]", "[ b0, b1, b2, b3 ]"}, - {"[ ]", "[ c3 ]", "[ c2, c3 ]", "[ c1, c2, c3 ]", "[ c0, c1, c2, c3 ]"}, - {"[ ]", "[ d3 ]", "[ d2, d3 ]", "[ d1, d2, d3 ]", "[ d0, d1, d2, d3 ]"}, - {"[ ]", "[ e3 ]", "[ e2, e3 ]", "[ e1, e2, e3 ]", "[ e0, e1, e2, e3 ]"}, - }; - - // The loop below creates the following LSM tree where each (k, v) pair - // represents a file that contains that entry. When a file is created, - // the db is reopend with FIFO compaction and verified the LSM tree - // structure is still the same. - // - // The resulting LSM tree will contain 5 different keys. Each key as - // 4 different versions, located in different level. - // - // L0: (e, e0) (d, d0) (c, c0) (b, b0) (a, a0) - // L1: (a, a1) (b, b1) (c, c1) (d, d1) (e, e1) - // L2: (a, a2) (b, b2) (c, c2) (d, d2) (e, e2) - // L3: (a, a3) (b, b3) (c, c3) (d, d3) (e, e3) - for (int l = 0; l < kLevelCount; ++l) { - int level = kLevelCount - 1 - l; - for (int p = 0; p < kKeyCount; ++p) { - std::string put_key = std::string(1, char('a' + p)); - ASSERT_OK(Put(kCF, put_key, put_key + std::to_string(level))); - ASSERT_OK(Flush(kCF)); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - for (int g = 0; g < kKeyCount; ++g) { - int entry_count = (p >= g) ? l + 1 : l; - std::string get_key = std::string(1, char('a' + g)); - CheckAllEntriesWithFifoReopen(expected_entries[g][entry_count], get_key, - kCF, {"pikachu"}, options); - } - if (level != 0) { - MoveFilesToLevel(level, kCF); - for (int g = 0; g < kKeyCount; ++g) { - int entry_count = (p >= g) ? l + 1 : l; - std::string get_key = std::string(1, char('a' + g)); - CheckAllEntriesWithFifoReopen(expected_entries[g][entry_count], - get_key, kCF, {"pikachu"}, options); - } - } - ASSERT_EQ(expected_files_per_level[l][p], FilesPerLevel(kCF)); - } - } - - // The expected number of sst files in each level after each FIFO compaction - // that deletes the oldest sst file. - const std::string expected_files_per_level_after_fifo[] = { - "5,5,5,4", "5,5,5,3", "5,5,5,2", "5,5,5,1", "5,5,5", "5,5,4", "5,5,3", - "5,5,2", "5,5,1", "5,5", "5,4", "5,3", "5,2", "5,1", - "5", "4", "3", "2", "1", "", - }; - - // The expected value entries of each key after each FIFO compaction. - // This verifies whether FIFO removes the file with the smallest key in non-L0 - // files first then the oldest files in L0. - const std::string expected_entries_after_fifo[kKeyCount][kLevelCount + 1] = { - {"[ a0, a1, a2, a3 ]", "[ a0, a1, a2 ]", "[ a0, a1 ]", "[ a0 ]", "[ ]"}, - {"[ b0, b1, b2, b3 ]", "[ b0, b1, b2 ]", "[ b0, b1 ]", "[ b0 ]", "[ ]"}, - {"[ c0, c1, c2, c3 ]", "[ c0, c1, c2 ]", "[ c0, c1 ]", "[ c0 ]", "[ ]"}, - {"[ d0, d1, d2, d3 ]", "[ d0, d1, d2 ]", "[ d0, d1 ]", "[ d0 ]", "[ ]"}, - {"[ e0, e1, e2, e3 ]", "[ e0, e1, e2 ]", "[ e0, e1 ]", "[ e0 ]", "[ ]"}, - }; - - // In the 2nd phase, we reopen the DB with FIFO compaction. In each reopen, - // we config max_table_files_size so that FIFO will remove exactly one file - // at a time upon compaction, and we will use it to verify whether the sst - // files are deleted in the correct order. - for (int i = 0; i < kTotalSstFileCount; ++i) { - uint64_t total_sst_files_size = 0; - ASSERT_TRUE(dbfull()->GetIntProperty( - handles_[1], "rocksdb.total-sst-files-size", &total_sst_files_size)); - ASSERT_TRUE(total_sst_files_size > 0); - - Options fifo_options(options); - fifo_options.compaction_style = kCompactionStyleFIFO; - options.create_if_missing = false; - fifo_options.max_open_files = -1; - fifo_options.disable_auto_compactions = false; - // Config max_table_files_size to be total_sst_files_size - 1 so that - // FIFO will delete one file. - fifo_options.compaction_options_fifo.max_table_files_size = - total_sst_files_size - 1; - ASSERT_OK( - TryReopenWithColumnFamilies({"default", "pikachu"}, fifo_options)); - // For FIFO to pick a compaction - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_OK(dbfull()->TEST_WaitForCompact(false)); - for (int g = 0; g < kKeyCount; ++g) { - std::string get_key = std::string(1, char('a' + g)); - int status_index = i / kKeyCount; - if ((i % kKeyCount) >= g) { - // If true, then it means the sst file containing the get_key in the - // current level has already been deleted, so we need to move the - // status_index for checking the expected value. - status_index++; - } - CheckAllEntriesWithFifoReopen( - expected_entries_after_fifo[g][status_index], get_key, kCF, - {"pikachu"}, options); - } - ASSERT_EQ(expected_files_per_level_after_fifo[i], FilesPerLevel(kCF)); - } -} - -TEST_F(DBTest, PutSingleDeleteGet) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_OK(Put(1, "foo2", "v2")); - ASSERT_EQ("v2", Get(1, "foo2")); - ASSERT_OK(SingleDelete(1, "foo")); - ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - // Skip FIFO and universal compaction because they do not apply to the test - // case. Skip MergePut because single delete does not get removed when it - // encounters a merge. - } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | - kSkipMergePut)); -} - -TEST_F(DBTest, ReadFromPersistedTier) { - do { - Random rnd(301); - Options options = CurrentOptions(); - for (int disableWAL = 0; disableWAL <= 1; ++disableWAL) { - CreateAndReopenWithCF({"pikachu"}, options); - WriteOptions wopt; - wopt.disableWAL = (disableWAL == 1); - // 1st round: put but not flush - ASSERT_OK(db_->Put(wopt, handles_[1], "foo", "first")); - ASSERT_OK(db_->Put(wopt, handles_[1], "bar", "one")); - ASSERT_EQ("first", Get(1, "foo")); - ASSERT_EQ("one", Get(1, "bar")); - - // Read directly from persited data. - ReadOptions ropt; - ropt.read_tier = kPersistedTier; - std::string value; - if (wopt.disableWAL) { - // as data has not yet being flushed, we expect not found. - ASSERT_TRUE(db_->Get(ropt, handles_[1], "foo", &value).IsNotFound()); - ASSERT_TRUE(db_->Get(ropt, handles_[1], "bar", &value).IsNotFound()); - } else { - ASSERT_OK(db_->Get(ropt, handles_[1], "foo", &value)); - ASSERT_OK(db_->Get(ropt, handles_[1], "bar", &value)); - } - - // Multiget - std::vector multiget_cfs; - multiget_cfs.push_back(handles_[1]); - multiget_cfs.push_back(handles_[1]); - std::vector multiget_keys; - multiget_keys.push_back("foo"); - multiget_keys.push_back("bar"); - std::vector multiget_values; - auto statuses = - db_->MultiGet(ropt, multiget_cfs, multiget_keys, &multiget_values); - if (wopt.disableWAL) { - ASSERT_TRUE(statuses[0].IsNotFound()); - ASSERT_TRUE(statuses[1].IsNotFound()); - } else { - ASSERT_OK(statuses[0]); - ASSERT_OK(statuses[1]); - } - - // 2nd round: flush and put a new value in memtable. - ASSERT_OK(Flush(1)); - ASSERT_OK(db_->Put(wopt, handles_[1], "rocksdb", "hello")); - - // once the data has been flushed, we are able to get the - // data when kPersistedTier is used. - ASSERT_TRUE(db_->Get(ropt, handles_[1], "foo", &value).ok()); - ASSERT_EQ(value, "first"); - ASSERT_TRUE(db_->Get(ropt, handles_[1], "bar", &value).ok()); - ASSERT_EQ(value, "one"); - if (wopt.disableWAL) { - ASSERT_TRUE( - db_->Get(ropt, handles_[1], "rocksdb", &value).IsNotFound()); - } else { - ASSERT_OK(db_->Get(ropt, handles_[1], "rocksdb", &value)); - ASSERT_EQ(value, "hello"); - } - - // Expect same result in multiget - multiget_cfs.push_back(handles_[1]); - multiget_keys.push_back("rocksdb"); - statuses = - db_->MultiGet(ropt, multiget_cfs, multiget_keys, &multiget_values); - ASSERT_TRUE(statuses[0].ok()); - ASSERT_EQ("first", multiget_values[0]); - ASSERT_TRUE(statuses[1].ok()); - ASSERT_EQ("one", multiget_values[1]); - if (wopt.disableWAL) { - ASSERT_TRUE(statuses[2].IsNotFound()); - } else { - ASSERT_OK(statuses[2]); - } - - // 3rd round: delete and flush - ASSERT_OK(db_->Delete(wopt, handles_[1], "foo")); - Flush(1); - ASSERT_OK(db_->Delete(wopt, handles_[1], "bar")); - - ASSERT_TRUE(db_->Get(ropt, handles_[1], "foo", &value).IsNotFound()); - if (wopt.disableWAL) { - // Still expect finding the value as its delete has not yet being - // flushed. - ASSERT_TRUE(db_->Get(ropt, handles_[1], "bar", &value).ok()); - ASSERT_EQ(value, "one"); - } else { - ASSERT_TRUE(db_->Get(ropt, handles_[1], "bar", &value).IsNotFound()); - } - ASSERT_TRUE(db_->Get(ropt, handles_[1], "rocksdb", &value).ok()); - ASSERT_EQ(value, "hello"); - - statuses = - db_->MultiGet(ropt, multiget_cfs, multiget_keys, &multiget_values); - ASSERT_TRUE(statuses[0].IsNotFound()); - if (wopt.disableWAL) { - ASSERT_TRUE(statuses[1].ok()); - ASSERT_EQ("one", multiget_values[1]); - } else { - ASSERT_TRUE(statuses[1].IsNotFound()); - } - ASSERT_TRUE(statuses[2].ok()); - ASSERT_EQ("hello", multiget_values[2]); - if (wopt.disableWAL == 0) { - DestroyAndReopen(options); - } - } - } while (ChangeOptions()); -} - -TEST_F(DBTest, SingleDeleteFlush) { - // Test to check whether flushing preserves a single delete hidden - // behind a put. - do { - Random rnd(301); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"pikachu"}, options); - - // Put values on second level (so that they will not be in the same - // compaction as the other operations. - ASSERT_OK(Put(1, "foo", "first")); - ASSERT_OK(Put(1, "bar", "one")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - - // (Single) delete hidden by a put - ASSERT_OK(SingleDelete(1, "foo")); - ASSERT_OK(Put(1, "foo", "second")); - ASSERT_OK(Delete(1, "bar")); - ASSERT_OK(Put(1, "bar", "two")); - ASSERT_OK(Flush(1)); - - ASSERT_OK(SingleDelete(1, "foo")); - ASSERT_OK(Delete(1, "bar")); - ASSERT_OK(Flush(1)); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - - ASSERT_EQ("NOT_FOUND", Get(1, "bar")); - ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - // Skip FIFO and universal compaction beccaus they do not apply to the test - // case. Skip MergePut because single delete does not get removed when it - // encounters a merge. - } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | - kSkipMergePut)); -} - -TEST_F(DBTest, SingleDeletePutFlush) { - // Single deletes that encounter the matching put in a flush should get - // removed. - do { - Random rnd(301); - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", Slice())); - ASSERT_OK(Put(1, "a", Slice())); - ASSERT_OK(SingleDelete(1, "a")); - ASSERT_OK(Flush(1)); - - ASSERT_EQ("[ ]", AllEntriesFor("a", 1)); - // Skip FIFO and universal compaction because they do not apply to the test - // case. Skip MergePut because single delete does not get removed when it - // encounters a merge. - } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | - kSkipMergePut)); -} - -// Disable because not all platform can run it. -// It requires more than 9GB memory to run it, With single allocation -// of more than 3GB. -TEST_F(DBTest, DISABLED_SanitizeVeryVeryLargeValue) { - const size_t kValueSize = 4 * size_t{1024 * 1024 * 1024}; // 4GB value - std::string raw(kValueSize, 'v'); - Options options = CurrentOptions(); - options.env = env_; - options.merge_operator = MergeOperators::CreatePutOperator(); - options.write_buffer_size = 100000; // Small write buffer - options.paranoid_checks = true; - DestroyAndReopen(options); - - ASSERT_OK(Put("boo", "v1")); - ASSERT_TRUE(Put("foo", raw).IsInvalidArgument()); - ASSERT_TRUE(Merge("foo", raw).IsInvalidArgument()); - - WriteBatch wb; - ASSERT_TRUE(wb.Put("foo", raw).IsInvalidArgument()); - ASSERT_TRUE(wb.Merge("foo", raw).IsInvalidArgument()); - - Slice value_slice = raw; - Slice key_slice = "foo"; - SliceParts sp_key(&key_slice, 1); - SliceParts sp_value(&value_slice, 1); - - ASSERT_TRUE(wb.Put(sp_key, sp_value).IsInvalidArgument()); - ASSERT_TRUE(wb.Merge(sp_key, sp_value).IsInvalidArgument()); -} - -// Disable because not all platform can run it. -// It requires more than 9GB memory to run it, With single allocation -// of more than 3GB. -TEST_F(DBTest, DISABLED_VeryLargeValue) { - const size_t kValueSize = 3221225472u; // 3GB value - const size_t kKeySize = 8388608u; // 8MB key - std::string raw(kValueSize, 'v'); - std::string key1(kKeySize, 'c'); - std::string key2(kKeySize, 'd'); - - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; // Small write buffer - options.paranoid_checks = true; - DestroyAndReopen(options); - - ASSERT_OK(Put("boo", "v1")); - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Put(key1, raw)); - raw[0] = 'w'; - ASSERT_OK(Put(key2, raw)); - dbfull()->TEST_WaitForFlushMemTable(); - - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - std::string value; - Status s = db_->Get(ReadOptions(), key1, &value); - ASSERT_OK(s); - ASSERT_EQ(kValueSize, value.size()); - ASSERT_EQ('v', value[0]); - - s = db_->Get(ReadOptions(), key2, &value); - ASSERT_OK(s); - ASSERT_EQ(kValueSize, value.size()); - ASSERT_EQ('w', value[0]); - - // Compact all files. - Flush(); - db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); - - // Check DB is not in read-only state. - ASSERT_OK(Put("boo", "v1")); - - s = db_->Get(ReadOptions(), key1, &value); - ASSERT_OK(s); - ASSERT_EQ(kValueSize, value.size()); - ASSERT_EQ('v', value[0]); - - s = db_->Get(ReadOptions(), key2, &value); - ASSERT_OK(s); - ASSERT_EQ(kValueSize, value.size()); - ASSERT_EQ('w', value[0]); -} - -TEST_F(DBTest, GetFromImmutableLayer) { - do { - Options options = CurrentOptions(); - options.env = env_; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_EQ("v1", Get(1, "foo")); - - // Block sync calls - env_->delay_sstable_sync_.store(true, std::memory_order_release); - ASSERT_OK(Put(1, "k1", std::string(100000, 'x'))); // Fill memtable - ASSERT_OK(Put(1, "k2", std::string(100000, 'y'))); // Trigger flush - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("NOT_FOUND", Get(0, "foo")); - // Release sync calls - env_->delay_sstable_sync_.store(false, std::memory_order_release); - } while (ChangeOptions()); -} - -TEST_F(DBTest, GetLevel0Ordering) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - // Check that we process level-0 files in correct order. The code - // below generates two level-0 files where the earlier one comes - // before the later one in the level-0 file list since the earlier - // one has a smaller "smallest" key. - ASSERT_OK(Put(1, "bar", "b")); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "v2")); - ASSERT_OK(Flush(1)); - ASSERT_EQ("v2", Get(1, "foo")); - } while (ChangeOptions()); -} - -TEST_F(DBTest, WrongLevel0Config) { - Options options = CurrentOptions(); - Close(); - ASSERT_OK(DestroyDB(dbname_, options)); - options.level0_stop_writes_trigger = 1; - options.level0_slowdown_writes_trigger = 2; - options.level0_file_num_compaction_trigger = 3; - ASSERT_OK(DB::Open(options, dbname_, &db_)); -} - -TEST_F(DBTest, GetOrderedByLevels) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - Compact(1, "a", "z"); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_OK(Put(1, "foo", "v2")); - ASSERT_EQ("v2", Get(1, "foo")); - ASSERT_OK(Flush(1)); - ASSERT_EQ("v2", Get(1, "foo")); - } while (ChangeOptions()); -} - -TEST_F(DBTest, GetPicksCorrectFile) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - // Arrange to have multiple files in a non-level-0 level. - ASSERT_OK(Put(1, "a", "va")); - Compact(1, "a", "b"); - ASSERT_OK(Put(1, "x", "vx")); - Compact(1, "x", "y"); - ASSERT_OK(Put(1, "f", "vf")); - Compact(1, "f", "g"); - ASSERT_EQ("va", Get(1, "a")); - ASSERT_EQ("vf", Get(1, "f")); - ASSERT_EQ("vx", Get(1, "x")); - } while (ChangeOptions()); -} - -TEST_F(DBTest, GetEncountersEmptyLevel) { - do { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - // Arrange for the following to happen: - // * sstable A in level 0 - // * nothing in level 1 - // * sstable B in level 2 - // Then do enough Get() calls to arrange for an automatic compaction - // of sstable A. A bug would cause the compaction to be marked as - // occurring at level 1 (instead of the correct level 0). - - // Step 1: First place sstables in levels 0 and 2 - ASSERT_OK(Put(1, "a", "begin")); - ASSERT_OK(Put(1, "z", "end")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - ASSERT_OK(Put(1, "a", "begin")); - ASSERT_OK(Put(1, "z", "end")); - ASSERT_OK(Flush(1)); - ASSERT_GT(NumTableFilesAtLevel(0, 1), 0); - ASSERT_GT(NumTableFilesAtLevel(2, 1), 0); - - // Step 2: clear level 1 if necessary. - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 1); - ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2, 1), 1); - - // Step 3: read a bunch of times - for (int i = 0; i < 1000; i++) { - ASSERT_EQ("NOT_FOUND", Get(1, "missing")); - } - - // Step 4: Wait for compaction to finish - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 1); // XXX - } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction)); -} - -TEST_F(DBTest, FlushMultipleMemtable) { - do { - Options options = CurrentOptions(); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - options.max_write_buffer_number = 4; - options.min_write_buffer_number_to_merge = 3; - options.max_write_buffer_size_to_maintain = -1; - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1")); - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1")); - - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v1", Get(1, "bar")); - ASSERT_OK(Flush(1)); - } while (ChangeCompactOptions()); -} -TEST_F(DBTest, FlushSchedule) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.level0_stop_writes_trigger = 1 << 10; - options.level0_slowdown_writes_trigger = 1 << 10; - options.min_write_buffer_number_to_merge = 1; - options.max_write_buffer_size_to_maintain = - static_cast(options.write_buffer_size); - options.max_write_buffer_number = 2; - options.write_buffer_size = 120 * 1024; - auto flush_listener = std::make_shared(); - flush_listener->expected_flush_reason = FlushReason::kWriteBufferFull; - options.listeners.push_back(flush_listener); - CreateAndReopenWithCF({"pikachu"}, options); - std::vector threads; - - std::atomic thread_num(0); - // each column family will have 5 thread, each thread generating 2 memtables. - // each column family should end up with 10 table files - std::function fill_memtable_func = [&]() { - int a = thread_num.fetch_add(1); - Random rnd(a); - WriteOptions wo; - // this should fill up 2 memtables - for (int k = 0; k < 5000; ++k) { - ASSERT_OK(db_->Put(wo, handles_[a & 1], rnd.RandomString(13), "")); - } - }; - - for (int i = 0; i < 10; ++i) { - threads.emplace_back(fill_memtable_func); - } - - for (auto& t : threads) { - t.join(); - } - - auto default_tables = GetNumberOfSstFilesForColumnFamily(db_, "default"); - auto pikachu_tables = GetNumberOfSstFilesForColumnFamily(db_, "pikachu"); - ASSERT_LE(default_tables, static_cast(10)); - ASSERT_GT(default_tables, static_cast(0)); - ASSERT_LE(pikachu_tables, static_cast(10)); - ASSERT_GT(pikachu_tables, static_cast(0)); -} - -namespace { -class KeepFilter : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - return false; - } - - const char* Name() const override { return "KeepFilter"; } -}; - -class KeepFilterFactory : public CompactionFilterFactory { - public: - explicit KeepFilterFactory(bool check_context = false) - : check_context_(check_context) {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - if (check_context_) { - EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction); - EXPECT_EQ(expect_manual_compaction_.load(), context.is_manual_compaction); - } - return std::unique_ptr(new KeepFilter()); - } - - const char* Name() const override { return "KeepFilterFactory"; } - bool check_context_; - std::atomic_bool expect_full_compaction_; - std::atomic_bool expect_manual_compaction_; -}; - -class DelayFilter : public CompactionFilter { - public: - explicit DelayFilter(DBTestBase* d) : db_test(d) {} - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - db_test->env_->MockSleepForMicroseconds(1000); - return true; - } - - const char* Name() const override { return "DelayFilter"; } - - private: - DBTestBase* db_test; -}; - -class DelayFilterFactory : public CompactionFilterFactory { - public: - explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {} - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& /*context*/) override { - return std::unique_ptr(new DelayFilter(db_test)); - } - - const char* Name() const override { return "DelayFilterFactory"; } - - private: - DBTestBase* db_test; -}; -} // anonymous namespace - - -static std::string CompressibleString(Random* rnd, int len) { - std::string r; - test::CompressibleString(rnd, 0.8, len, &r); - return r; -} - -TEST_F(DBTest, FailMoreDbPaths) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 10000000); - options.db_paths.emplace_back(dbname_ + "_2", 1000000); - options.db_paths.emplace_back(dbname_ + "_3", 1000000); - options.db_paths.emplace_back(dbname_ + "_4", 1000000); - options.db_paths.emplace_back(dbname_ + "_5", 1000000); - ASSERT_TRUE(TryReopen(options).IsNotSupported()); -} - -void CheckColumnFamilyMeta( - const ColumnFamilyMetaData& cf_meta, const std::string& cf_name, - const std::vector>& files_by_level, - uint64_t start_time, uint64_t end_time) { - ASSERT_EQ(cf_meta.name, cf_name); - ASSERT_EQ(cf_meta.levels.size(), files_by_level.size()); - - uint64_t cf_size = 0; - size_t file_count = 0; - - for (size_t i = 0; i < cf_meta.levels.size(); ++i) { - const auto& level_meta_from_cf = cf_meta.levels[i]; - const auto& level_meta_from_files = files_by_level[i]; - - ASSERT_EQ(level_meta_from_cf.level, i); - ASSERT_EQ(level_meta_from_cf.files.size(), level_meta_from_files.size()); - - file_count += level_meta_from_cf.files.size(); - - uint64_t level_size = 0; - for (size_t j = 0; j < level_meta_from_cf.files.size(); ++j) { - const auto& file_meta_from_cf = level_meta_from_cf.files[j]; - const auto& file_meta_from_files = level_meta_from_files[j]; - - level_size += file_meta_from_cf.size; - - ASSERT_EQ(file_meta_from_cf.file_number, - file_meta_from_files.fd.GetNumber()); - ASSERT_EQ(file_meta_from_cf.file_number, - TableFileNameToNumber(file_meta_from_cf.name)); - ASSERT_EQ(file_meta_from_cf.size, file_meta_from_files.fd.file_size); - ASSERT_EQ(file_meta_from_cf.smallest_seqno, - file_meta_from_files.fd.smallest_seqno); - ASSERT_EQ(file_meta_from_cf.largest_seqno, - file_meta_from_files.fd.largest_seqno); - ASSERT_EQ(file_meta_from_cf.smallestkey, - file_meta_from_files.smallest.user_key().ToString()); - ASSERT_EQ(file_meta_from_cf.largestkey, - file_meta_from_files.largest.user_key().ToString()); - ASSERT_EQ(file_meta_from_cf.oldest_blob_file_number, - file_meta_from_files.oldest_blob_file_number); - ASSERT_EQ(file_meta_from_cf.oldest_ancester_time, - file_meta_from_files.oldest_ancester_time); - ASSERT_EQ(file_meta_from_cf.file_creation_time, - file_meta_from_files.file_creation_time); - ASSERT_GE(file_meta_from_cf.file_creation_time, start_time); - ASSERT_LE(file_meta_from_cf.file_creation_time, end_time); - ASSERT_EQ(file_meta_from_cf.epoch_number, - file_meta_from_files.epoch_number); - ASSERT_GE(file_meta_from_cf.oldest_ancester_time, start_time); - ASSERT_LE(file_meta_from_cf.oldest_ancester_time, end_time); - // More from FileStorageInfo - ASSERT_EQ(file_meta_from_cf.file_type, kTableFile); - ASSERT_EQ(file_meta_from_cf.name, - "/" + file_meta_from_cf.relative_filename); - ASSERT_EQ(file_meta_from_cf.directory, file_meta_from_cf.db_path); - } - - ASSERT_EQ(level_meta_from_cf.size, level_size); - cf_size += level_size; - } - - ASSERT_EQ(cf_meta.file_count, file_count); - ASSERT_EQ(cf_meta.size, cf_size); -} - -void CheckLiveFilesMeta( - const std::vector& live_file_meta, - const std::vector>& files_by_level) { - size_t total_file_count = 0; - for (const auto& f : files_by_level) { - total_file_count += f.size(); - } - - ASSERT_EQ(live_file_meta.size(), total_file_count); - - int level = 0; - int i = 0; - - for (const auto& meta : live_file_meta) { - if (level != meta.level) { - level = meta.level; - i = 0; - } - - ASSERT_LT(i, files_by_level[level].size()); - - const auto& expected_meta = files_by_level[level][i]; - - ASSERT_EQ(meta.column_family_name, kDefaultColumnFamilyName); - ASSERT_EQ(meta.file_number, expected_meta.fd.GetNumber()); - ASSERT_EQ(meta.file_number, TableFileNameToNumber(meta.name)); - ASSERT_EQ(meta.size, expected_meta.fd.file_size); - ASSERT_EQ(meta.smallest_seqno, expected_meta.fd.smallest_seqno); - ASSERT_EQ(meta.largest_seqno, expected_meta.fd.largest_seqno); - ASSERT_EQ(meta.smallestkey, expected_meta.smallest.user_key().ToString()); - ASSERT_EQ(meta.largestkey, expected_meta.largest.user_key().ToString()); - ASSERT_EQ(meta.oldest_blob_file_number, - expected_meta.oldest_blob_file_number); - ASSERT_EQ(meta.epoch_number, expected_meta.epoch_number); - - // More from FileStorageInfo - ASSERT_EQ(meta.file_type, kTableFile); - ASSERT_EQ(meta.name, "/" + meta.relative_filename); - ASSERT_EQ(meta.directory, meta.db_path); - - ++i; - } -} - -void AddBlobFile(const ColumnFamilyHandle* cfh, uint64_t blob_file_number, - uint64_t total_blob_count, uint64_t total_blob_bytes, - const std::string& checksum_method, - const std::string& checksum_value, - uint64_t garbage_blob_count = 0, - uint64_t garbage_blob_bytes = 0) { - ColumnFamilyData* cfd = - (static_cast(cfh))->cfd(); - assert(cfd); - - Version* const version = cfd->current(); - assert(version); - - VersionStorageInfo* const storage_info = version->storage_info(); - assert(storage_info); - - // Add a live blob file. - - auto shared_meta = SharedBlobFileMetaData::Create( - blob_file_number, total_blob_count, total_blob_bytes, checksum_method, - checksum_value); - - auto meta = BlobFileMetaData::Create(std::move(shared_meta), - BlobFileMetaData::LinkedSsts(), - garbage_blob_count, garbage_blob_bytes); - - storage_info->AddBlobFile(std::move(meta)); -} - -static void CheckBlobMetaData( - const BlobMetaData& bmd, uint64_t blob_file_number, - uint64_t total_blob_count, uint64_t total_blob_bytes, - const std::string& checksum_method, const std::string& checksum_value, - uint64_t garbage_blob_count = 0, uint64_t garbage_blob_bytes = 0) { - ASSERT_EQ(bmd.blob_file_number, blob_file_number); - ASSERT_EQ(bmd.blob_file_name, BlobFileName("", blob_file_number)); - ASSERT_EQ(bmd.blob_file_size, - total_blob_bytes + BlobLogHeader::kSize + BlobLogFooter::kSize); - - ASSERT_EQ(bmd.total_blob_count, total_blob_count); - ASSERT_EQ(bmd.total_blob_bytes, total_blob_bytes); - ASSERT_EQ(bmd.garbage_blob_count, garbage_blob_count); - ASSERT_EQ(bmd.garbage_blob_bytes, garbage_blob_bytes); - ASSERT_EQ(bmd.checksum_method, checksum_method); - ASSERT_EQ(bmd.checksum_value, checksum_value); -} - -TEST_F(DBTest, MetaDataTest) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - - int64_t temp_time = 0; - options.env->GetCurrentTime(&temp_time); - uint64_t start_time = static_cast(temp_time); - - DestroyAndReopen(options); - - Random rnd(301); - int key_index = 0; - for (int i = 0; i < 100; ++i) { - // Add a single blob reference to each file - std::string blob_index; - BlobIndex::EncodeBlob(&blob_index, /* blob_file_number */ i + 1000, - /* offset */ 1234, /* size */ 5678, kNoCompression); - - WriteBatch batch; - ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, Key(key_index), - blob_index)); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - - ++key_index; - - // Fill up the rest of the file with random values. - GenerateNewFile(&rnd, &key_index, /* nowait */ true); - - ASSERT_OK(Flush()); - } - - std::vector> files_by_level; - dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level); - - options.env->GetCurrentTime(&temp_time); - uint64_t end_time = static_cast(temp_time); - - ColumnFamilyMetaData cf_meta; - db_->GetColumnFamilyMetaData(&cf_meta); - CheckColumnFamilyMeta(cf_meta, kDefaultColumnFamilyName, files_by_level, - start_time, end_time); - std::vector live_file_meta; - db_->GetLiveFilesMetaData(&live_file_meta); - CheckLiveFilesMeta(live_file_meta, files_by_level); -} - -TEST_F(DBTest, AllMetaDataTest) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - constexpr uint64_t blob_file_number = 234; - constexpr uint64_t total_blob_count = 555; - constexpr uint64_t total_blob_bytes = 66666; - constexpr char checksum_method[] = "CRC32"; - constexpr char checksum_value[] = "\x3d\x87\xff\x57"; - - int64_t temp_time = 0; - options.env->GetCurrentTime(&temp_time).PermitUncheckedError(); - uint64_t start_time = static_cast(temp_time); - - Random rnd(301); - dbfull()->TEST_LockMutex(); - for (int cf = 0; cf < 2; cf++) { - AddBlobFile(handles_[cf], blob_file_number * (cf + 1), - total_blob_count * (cf + 1), total_blob_bytes * (cf + 1), - checksum_method, checksum_value); - } - dbfull()->TEST_UnlockMutex(); - - std::vector all_meta; - db_->GetAllColumnFamilyMetaData(&all_meta); - - std::vector> default_files_by_level; - std::vector> pikachu_files_by_level; - dbfull()->TEST_GetFilesMetaData(handles_[0], &default_files_by_level); - dbfull()->TEST_GetFilesMetaData(handles_[1], &pikachu_files_by_level); - - options.env->GetCurrentTime(&temp_time).PermitUncheckedError(); - uint64_t end_time = static_cast(temp_time); - - ASSERT_EQ(all_meta.size(), 2); - for (int cf = 0; cf < 2; cf++) { - const auto& cfmd = all_meta[cf]; - if (cf == 0) { - CheckColumnFamilyMeta(cfmd, "default", default_files_by_level, start_time, - end_time); - } else { - CheckColumnFamilyMeta(cfmd, "pikachu", pikachu_files_by_level, start_time, - end_time); - } - ASSERT_EQ(cfmd.blob_files.size(), 1U); - const auto& bmd = cfmd.blob_files[0]; - ASSERT_EQ(cfmd.blob_file_count, 1U); - ASSERT_EQ(cfmd.blob_file_size, bmd.blob_file_size); - ASSERT_EQ(NormalizePath(bmd.blob_file_path), NormalizePath(dbname_)); - CheckBlobMetaData(bmd, blob_file_number * (cf + 1), - total_blob_count * (cf + 1), total_blob_bytes * (cf + 1), - checksum_method, checksum_value); - } -} - -namespace { -void MinLevelHelper(DBTest* self, Options& options) { - Random rnd(301); - - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - std::vector values; - // Write 120KB (12 values, each 10K) - for (int i = 0; i < 12; i++) { - values.push_back(rnd.RandomString(10000)); - ASSERT_OK(self->Put(DBTestBase::Key(i), values[i])); - } - ASSERT_OK(self->dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(self->NumTableFilesAtLevel(0), num + 1); - } - - // generate one more file in level-0, and should trigger level-0 compaction - std::vector values; - for (int i = 0; i < 12; i++) { - values.push_back(rnd.RandomString(10000)); - ASSERT_OK(self->Put(DBTestBase::Key(i), values[i])); - } - ASSERT_OK(self->dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(self->NumTableFilesAtLevel(0), 0); - ASSERT_EQ(self->NumTableFilesAtLevel(1), 1); -} - -// returns false if the calling-Test should be skipped -bool MinLevelToCompress(CompressionType& type, Options& options, int wbits, - int lev, int strategy) { - fprintf(stderr, - "Test with compression options : window_bits = %d, level = %d, " - "strategy = %d}\n", - wbits, lev, strategy); - options.write_buffer_size = 100 << 10; // 100KB - options.arena_block_size = 4096; - options.num_levels = 3; - options.level0_file_num_compaction_trigger = 3; - options.create_if_missing = true; - - if (Snappy_Supported()) { - type = kSnappyCompression; - fprintf(stderr, "using snappy\n"); - } else if (Zlib_Supported()) { - type = kZlibCompression; - fprintf(stderr, "using zlib\n"); - } else if (BZip2_Supported()) { - type = kBZip2Compression; - fprintf(stderr, "using bzip2\n"); - } else if (LZ4_Supported()) { - type = kLZ4Compression; - fprintf(stderr, "using lz4\n"); - } else if (XPRESS_Supported()) { - type = kXpressCompression; - fprintf(stderr, "using xpress\n"); - } else if (ZSTD_Supported()) { - type = kZSTD; - fprintf(stderr, "using ZSTD\n"); - } else { - fprintf(stderr, "skipping test, compression disabled\n"); - return false; - } - options.compression_per_level.resize(options.num_levels); - - // do not compress L0 - for (int i = 0; i < 1; i++) { - options.compression_per_level[i] = kNoCompression; - } - for (int i = 1; i < options.num_levels; i++) { - options.compression_per_level[i] = type; - } - return true; -} -} // anonymous namespace - -TEST_F(DBTest, MinLevelToCompress1) { - Options options = CurrentOptions(); - CompressionType type = kSnappyCompression; - if (!MinLevelToCompress(type, options, -14, -1, 0)) { - return; - } - Reopen(options); - MinLevelHelper(this, options); - - // do not compress L0 and L1 - for (int i = 0; i < 2; i++) { - options.compression_per_level[i] = kNoCompression; - } - for (int i = 2; i < options.num_levels; i++) { - options.compression_per_level[i] = type; - } - DestroyAndReopen(options); - MinLevelHelper(this, options); -} - -TEST_F(DBTest, MinLevelToCompress2) { - Options options = CurrentOptions(); - CompressionType type = kSnappyCompression; - if (!MinLevelToCompress(type, options, 15, -1, 0)) { - return; - } - Reopen(options); - MinLevelHelper(this, options); - - // do not compress L0 and L1 - for (int i = 0; i < 2; i++) { - options.compression_per_level[i] = kNoCompression; - } - for (int i = 2; i < options.num_levels; i++) { - options.compression_per_level[i] = type; - } - DestroyAndReopen(options); - MinLevelHelper(this, options); -} - -// This test may fail because of a legit case that multiple L0 files -// are trivial moved to L1. -TEST_F(DBTest, DISABLED_RepeatedWritesToSameKey) { - do { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; // Small write buffer - CreateAndReopenWithCF({"pikachu"}, options); - - // We must have at most one file per level except for level-0, - // which may have up to kL0_StopWritesTrigger files. - const int kMaxFiles = - options.num_levels + options.level0_stop_writes_trigger; - - Random rnd(301); - std::string value = - rnd.RandomString(static_cast(2 * options.write_buffer_size)); - for (int i = 0; i < 5 * kMaxFiles; i++) { - ASSERT_OK(Put(1, "key", value)); - ASSERT_LE(TotalTableFiles(1), kMaxFiles); - } - } while (ChangeCompactOptions()); -} - -static bool Between(uint64_t val, uint64_t low, uint64_t high) { - bool result = (val >= low) && (val <= high); - if (!result) { - fprintf(stderr, "Value %llu is not in range [%llu, %llu]\n", - (unsigned long long)(val), (unsigned long long)(low), - (unsigned long long)(high)); - } - return result; -} - -TEST_F(DBTest, ApproximateSizesMemTable) { - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; // Large write buffer - options.compression = kNoCompression; - options.create_if_missing = true; - DestroyAndReopen(options); - auto default_cf = db_->DefaultColumnFamily(); - - const int N = 128; - Random rnd(301); - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1024))); - } - - uint64_t size; - std::string start = Key(50); - std::string end = Key(60); - Range r(start, end); - SizeApproximationOptions size_approx_options; - size_approx_options.include_memtables = true; - size_approx_options.include_files = true; - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_GT(size, 6000); - ASSERT_LT(size, 204800); - // Zero if not including mem table - ASSERT_OK(db_->GetApproximateSizes(&r, 1, &size)); - ASSERT_EQ(size, 0); - - start = Key(500); - end = Key(600); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, 0); - - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(Key(1000 + i), rnd.RandomString(1024))); - } - - start = Key(500); - end = Key(600); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, 0); - - start = Key(100); - end = Key(1020); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_GT(size, 6000); - - options.max_write_buffer_number = 8; - options.min_write_buffer_number_to_merge = 5; - options.write_buffer_size = 1024 * N; // Not very large - DestroyAndReopen(options); - default_cf = db_->DefaultColumnFamily(); - - int keys[N * 3]; - for (int i = 0; i < N; i++) { - keys[i * 3] = i * 5; - keys[i * 3 + 1] = i * 5 + 1; - keys[i * 3 + 2] = i * 5 + 2; - } - // MemTable entry counting is estimated and can vary greatly depending on - // layout. Thus, using deterministic seed for test stability. - RandomShuffle(std::begin(keys), std::end(keys), rnd.Next()); - - for (int i = 0; i < N * 3; i++) { - ASSERT_OK(Put(Key(keys[i] + 1000), rnd.RandomString(1024))); - } - - start = Key(100); - end = Key(300); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, 0); - - start = Key(1050); - end = Key(1080); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_GT(size, 6000); - - start = Key(2100); - end = Key(2300); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, 0); - - start = Key(1050); - end = Key(1080); - r = Range(start, end); - uint64_t size_with_mt, size_without_mt; - ASSERT_OK(db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, - &size_with_mt)); - ASSERT_GT(size_with_mt, 6000); - ASSERT_OK(db_->GetApproximateSizes(&r, 1, &size_without_mt)); - ASSERT_EQ(size_without_mt, 0); - - ASSERT_OK(Flush()); - - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(Key(i + 1000), rnd.RandomString(1024))); - } - - start = Key(1050); - end = Key(1080); - r = Range(start, end); - ASSERT_OK(db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, - &size_with_mt)); - ASSERT_OK(db_->GetApproximateSizes(&r, 1, &size_without_mt)); - ASSERT_GT(size_with_mt, size_without_mt); - ASSERT_GT(size_without_mt, 6000); - - // Check that include_memtables flag works as expected - size_approx_options.include_memtables = false; - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, size_without_mt); - - // Check that files_size_error_margin works as expected, when the heuristic - // conditions are not met - start = Key(1); - end = Key(1000 + N - 2); - r = Range(start, end); - size_approx_options.files_size_error_margin = -1.0; // disabled - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - uint64_t size2; - size_approx_options.files_size_error_margin = 0.5; // enabled, but not used - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size2)); - ASSERT_EQ(size, size2); -} - -TEST_F(DBTest, ApproximateSizesFilesWithErrorMargin) { - // Roughly 4 keys per data block, 1000 keys per file, - // with filter substantially larger than a data block - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(16)); - table_options.block_size = 100; - Options options = CurrentOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.write_buffer_size = 24 * 1024; - options.compression = kNoCompression; - options.create_if_missing = true; - options.target_file_size_base = 24 * 1024; - DestroyAndReopen(options); - const auto default_cf = db_->DefaultColumnFamily(); - - const int N = 64000; - Random rnd(301); - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(24))); - } - // Flush everything to files - ASSERT_OK(Flush()); - // Compact the entire key space into the next level - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), default_cf, nullptr, nullptr)); - - // Write more keys - for (int i = N; i < (N + N / 4); i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(24))); - } - // Flush everything to files again - ASSERT_OK(Flush()); - - // Wait for compaction to finish - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - { - const std::string start = Key(0); - const std::string end = Key(2 * N); - const Range r(start, end); - - SizeApproximationOptions size_approx_options; - size_approx_options.include_memtables = false; - size_approx_options.include_files = true; - size_approx_options.files_size_error_margin = -1.0; // disabled - - // Get the precise size without any approximation heuristic - uint64_t size; - ASSERT_OK(db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, - &size)); - ASSERT_NE(size, 0); - - // Get the size with an approximation heuristic - uint64_t size2; - const double error_margin = 0.2; - size_approx_options.files_size_error_margin = error_margin; - ASSERT_OK(db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, - &size2)); - ASSERT_LT(size2, size * (1 + error_margin)); - ASSERT_GT(size2, size * (1 - error_margin)); - } - - { - // Ensure that metadata is not falsely attributed only to the last data in - // the file. (In some applications, filters can be large portion of data - // size.) - // Perform many queries over small range, enough to ensure crossing file - // boundary, and make sure we never see a spike for large filter. - for (int i = 0; i < 3000; i += 10) { - const std::string start = Key(i); - const std::string end = Key(i + 11); // overlap by 1 key - const Range r(start, end); - uint64_t size; - ASSERT_OK(db_->GetApproximateSizes(&r, 1, &size)); - ASSERT_LE(size, 11 * 100); - } - } -} - -TEST_F(DBTest, GetApproximateMemTableStats) { - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; - options.compression = kNoCompression; - options.create_if_missing = true; - DestroyAndReopen(options); - - const int N = 128; - Random rnd(301); - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1024))); - } - - uint64_t count; - uint64_t size; - - std::string start = Key(50); - std::string end = Key(60); - Range r(start, end); - db_->GetApproximateMemTableStats(r, &count, &size); - ASSERT_GT(count, 0); - ASSERT_LE(count, N); - ASSERT_GT(size, 6000); - ASSERT_LT(size, 204800); - - start = Key(500); - end = Key(600); - r = Range(start, end); - db_->GetApproximateMemTableStats(r, &count, &size); - ASSERT_EQ(count, 0); - ASSERT_EQ(size, 0); - - ASSERT_OK(Flush()); - - start = Key(50); - end = Key(60); - r = Range(start, end); - db_->GetApproximateMemTableStats(r, &count, &size); - ASSERT_EQ(count, 0); - ASSERT_EQ(size, 0); - - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(Key(1000 + i), rnd.RandomString(1024))); - } - - start = Key(100); - end = Key(1020); - r = Range(start, end); - db_->GetApproximateMemTableStats(r, &count, &size); - ASSERT_GT(count, 20); - ASSERT_GT(size, 6000); -} - -TEST_F(DBTest, ApproximateSizes) { - do { - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; // Large write buffer - options.compression = kNoCompression; - options.create_if_missing = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - uint64_t size; - ASSERT_OK(Size("", "xyz", 1, &size)); - ASSERT_TRUE(Between(size, 0, 0)); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK(Size("", "xyz", 1, &size)); - ASSERT_TRUE(Between(size, 0, 0)); - - // Write 8MB (80 values, each 100K) - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - const int N = 80; - static const int S1 = 100000; - static const int S2 = 105000; // Allow some expansion from metadata - Random rnd(301); - for (int i = 0; i < N; i++) { - ASSERT_OK(Put(1, Key(i), rnd.RandomString(S1))); - } - - // 0 because GetApproximateSizes() does not account for memtable space - ASSERT_OK(Size("", Key(50), 1, &size)); - ASSERT_TRUE(Between(size, 0, 0)); - - // Check sizes across recovery by reopening a few times - for (int run = 0; run < 3; run++) { - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - for (int compact_start = 0; compact_start < N; compact_start += 10) { - for (int i = 0; i < N; i += 10) { - ASSERT_OK(Size("", Key(i), 1, &size)); - ASSERT_TRUE(Between(size, S1 * i, S2 * i)); - ASSERT_OK(Size("", Key(i) + ".suffix", 1, &size)); - ASSERT_TRUE(Between(size, S1 * (i + 1), S2 * (i + 1))); - ASSERT_OK(Size(Key(i), Key(i + 10), 1, &size)); - ASSERT_TRUE(Between(size, S1 * 10, S2 * 10)); - } - ASSERT_OK(Size("", Key(50), 1, &size)); - ASSERT_TRUE(Between(size, S1 * 50, S2 * 50)); - ASSERT_OK(Size("", Key(50) + ".suffix", 1, &size)); - ASSERT_TRUE(Between(size, S1 * 50, S2 * 50)); - - std::string cstart_str = Key(compact_start); - std::string cend_str = Key(compact_start + 9); - Slice cstart = cstart_str; - Slice cend = cend_str; - ASSERT_OK(dbfull()->TEST_CompactRange(0, &cstart, &cend, handles_[1])); - } - - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_GT(NumTableFilesAtLevel(1, 1), 0); - } - // ApproximateOffsetOf() is not yet implemented in plain table format. - } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction | - kSkipPlainTable | kSkipHashIndex)); -} - -TEST_F(DBTest, ApproximateSizes_MixOfSmallAndLarge) { - do { - Options options = CurrentOptions(); - options.compression = kNoCompression; - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - std::string big1 = rnd.RandomString(100000); - ASSERT_OK(Put(1, Key(0), rnd.RandomString(10000))); - ASSERT_OK(Put(1, Key(1), rnd.RandomString(10000))); - ASSERT_OK(Put(1, Key(2), big1)); - ASSERT_OK(Put(1, Key(3), rnd.RandomString(10000))); - ASSERT_OK(Put(1, Key(4), big1)); - ASSERT_OK(Put(1, Key(5), rnd.RandomString(10000))); - ASSERT_OK(Put(1, Key(6), rnd.RandomString(300000))); - ASSERT_OK(Put(1, Key(7), rnd.RandomString(10000))); - - // Check sizes across recovery by reopening a few times - uint64_t size; - for (int run = 0; run < 3; run++) { - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - ASSERT_OK(Size("", Key(0), 1, &size)); - ASSERT_TRUE(Between(size, 0, 0)); - ASSERT_OK(Size("", Key(1), 1, &size)); - ASSERT_TRUE(Between(size, 10000, 11000)); - ASSERT_OK(Size("", Key(2), 1, &size)); - ASSERT_TRUE(Between(size, 20000, 21000)); - ASSERT_OK(Size("", Key(3), 1, &size)); - ASSERT_TRUE(Between(size, 120000, 121000)); - ASSERT_OK(Size("", Key(4), 1, &size)); - ASSERT_TRUE(Between(size, 130000, 131000)); - ASSERT_OK(Size("", Key(5), 1, &size)); - ASSERT_TRUE(Between(size, 230000, 232000)); - ASSERT_OK(Size("", Key(6), 1, &size)); - ASSERT_TRUE(Between(size, 240000, 242000)); - // Ensure some overhead is accounted for, even without including all - ASSERT_OK(Size("", Key(7), 1, &size)); - ASSERT_TRUE(Between(size, 540500, 545000)); - ASSERT_OK(Size("", Key(8), 1, &size)); - ASSERT_TRUE(Between(size, 550500, 555000)); - - ASSERT_OK(Size(Key(3), Key(5), 1, &size)); - ASSERT_TRUE(Between(size, 110100, 111000)); - - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1])); - } - // ApproximateOffsetOf() is not yet implemented in plain table format. - } while (ChangeOptions(kSkipPlainTable)); -} - -TEST_F(DBTest, Snapshot) { - env_->SetMockSleep(); - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions(options_override)); - ASSERT_OK(Put(0, "foo", "0v1")); - ASSERT_OK(Put(1, "foo", "1v1")); - - const Snapshot* s1 = db_->GetSnapshot(); - ASSERT_EQ(1U, GetNumSnapshots()); - uint64_t time_snap1 = GetTimeOldestSnapshots(); - ASSERT_GT(time_snap1, 0U); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_EQ(GetTimeOldestSnapshots(), - static_cast(s1->GetUnixTime())); - ASSERT_OK(Put(0, "foo", "0v2")); - ASSERT_OK(Put(1, "foo", "1v2")); - - env_->MockSleepForSeconds(1); - - const Snapshot* s2 = db_->GetSnapshot(); - ASSERT_EQ(2U, GetNumSnapshots()); - ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_EQ(GetTimeOldestSnapshots(), - static_cast(s1->GetUnixTime())); - ASSERT_OK(Put(0, "foo", "0v3")); - ASSERT_OK(Put(1, "foo", "1v3")); - - { - ManagedSnapshot s3(db_); - ASSERT_EQ(3U, GetNumSnapshots()); - ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_EQ(GetTimeOldestSnapshots(), - static_cast(s1->GetUnixTime())); - - ASSERT_OK(Put(0, "foo", "0v4")); - ASSERT_OK(Put(1, "foo", "1v4")); - ASSERT_EQ("0v1", Get(0, "foo", s1)); - ASSERT_EQ("1v1", Get(1, "foo", s1)); - ASSERT_EQ("0v2", Get(0, "foo", s2)); - ASSERT_EQ("1v2", Get(1, "foo", s2)); - ASSERT_EQ("0v3", Get(0, "foo", s3.snapshot())); - ASSERT_EQ("1v3", Get(1, "foo", s3.snapshot())); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - } - - ASSERT_EQ(2U, GetNumSnapshots()); - ASSERT_EQ(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s1->GetSequenceNumber()); - ASSERT_EQ(GetTimeOldestSnapshots(), - static_cast(s1->GetUnixTime())); - ASSERT_EQ("0v1", Get(0, "foo", s1)); - ASSERT_EQ("1v1", Get(1, "foo", s1)); - ASSERT_EQ("0v2", Get(0, "foo", s2)); - ASSERT_EQ("1v2", Get(1, "foo", s2)); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - - db_->ReleaseSnapshot(s1); - ASSERT_EQ("0v2", Get(0, "foo", s2)); - ASSERT_EQ("1v2", Get(1, "foo", s2)); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - ASSERT_EQ(1U, GetNumSnapshots()); - ASSERT_LT(time_snap1, GetTimeOldestSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), s2->GetSequenceNumber()); - ASSERT_EQ(GetTimeOldestSnapshots(), - static_cast(s2->GetUnixTime())); - - db_->ReleaseSnapshot(s2); - ASSERT_EQ(0U, GetNumSnapshots()); - ASSERT_EQ(GetSequenceOldestSnapshots(), 0); - ASSERT_EQ("0v4", Get(0, "foo")); - ASSERT_EQ("1v4", Get(1, "foo")); - } while (ChangeOptions()); -} - -TEST_F(DBTest, HiddenValuesAreRemoved) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - uint64_t size; - do { - Options options = CurrentOptions(options_override); - CreateAndReopenWithCF({"pikachu"}, options); - Random rnd(301); - FillLevels("a", "z", 1); - - std::string big = rnd.RandomString(50000); - ASSERT_OK(Put(1, "foo", big)); - ASSERT_OK(Put(1, "pastfoo", "v")); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(Put(1, "foo", "tiny")); - ASSERT_OK(Put(1, "pastfoo2", "v2")); // Advance sequence number one more - - ASSERT_OK(Flush(1)); - ASSERT_GT(NumTableFilesAtLevel(0, 1), 0); - - ASSERT_EQ(big, Get(1, "foo", snapshot)); - ASSERT_OK(Size("", "pastfoo", 1, &size)); - ASSERT_TRUE(Between(size, 50000, 60000)); - db_->ReleaseSnapshot(snapshot); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny, " + big + " ]"); - Slice x("x"); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, &x, handles_[1])); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny ]"); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - ASSERT_GE(NumTableFilesAtLevel(1, 1), 1); - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, &x, handles_[1])); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ tiny ]"); - - ASSERT_OK(Size("", "pastfoo", 1, &size)); - ASSERT_TRUE(Between(size, 0, 1000)); - // ApproximateOffsetOf() is not yet implemented in plain table format, - // which is used by Size(). - } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction | - kSkipPlainTable)); -} - -TEST_F(DBTest, UnremovableSingleDelete) { - // If we compact: - // - // Put(A, v1) Snapshot SingleDelete(A) Put(A, v2) - // - // We do not want to end up with: - // - // Put(A, v1) Snapshot Put(A, v2) - // - // Because a subsequent SingleDelete(A) would delete the Put(A, v2) - // but not Put(A, v1), so Get(A) would return v1. - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - do { - Options options = CurrentOptions(options_override); - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "first")); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(SingleDelete(1, "foo")); - ASSERT_OK(Put(1, "foo", "second")); - ASSERT_OK(Flush(1)); - - ASSERT_EQ("first", Get(1, "foo", snapshot)); - ASSERT_EQ("second", Get(1, "foo")); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - ASSERT_EQ("[ second, SDEL, first ]", AllEntriesFor("foo", 1)); - - ASSERT_OK(SingleDelete(1, "foo")); - - ASSERT_EQ("first", Get(1, "foo", snapshot)); - ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1], - nullptr, nullptr)); - - ASSERT_EQ("first", Get(1, "foo", snapshot)); - ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - db_->ReleaseSnapshot(snapshot); - // Skip FIFO and universal compaction because they do not apply to the test - // case. Skip MergePut because single delete does not get removed when it - // encounters a merge. - } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | - kSkipMergePut)); -} - -TEST_F(DBTest, DeletionMarkers1) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Flush(1)); - const int last = 2; - MoveFilesToLevel(last, 1); - // foo => v1 is now in last level - ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1); - - // Place a table at level last-1 to prevent merging with preceding mutation - ASSERT_OK(Put(1, "a", "begin")); - ASSERT_OK(Put(1, "z", "end")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(last - 1, 1); - ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1); - ASSERT_EQ(NumTableFilesAtLevel(last - 1, 1), 1); - - ASSERT_OK(Delete(1, "foo")); - ASSERT_OK(Put(1, "foo", "v2")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, DEL, v1 ]"); - ASSERT_OK(Flush(1)); // Moves to level last-2 - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, v1 ]"); - Slice z("z"); - ASSERT_OK(dbfull()->TEST_CompactRange(last - 2, nullptr, &z, handles_[1])); - // DEL eliminated, but v1 remains because we aren't compacting that level - // (DEL can be eliminated because v2 hides v1). - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2, v1 ]"); - ASSERT_OK( - dbfull()->TEST_CompactRange(last - 1, nullptr, nullptr, handles_[1])); - // Merging last-1 w/ last, so we are the base level for "foo", so - // DEL is removed. (as is v1). - ASSERT_EQ(AllEntriesFor("foo", 1), "[ v2 ]"); -} - -TEST_F(DBTest, DeletionMarkers2) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Flush(1)); - const int last = 2; - MoveFilesToLevel(last, 1); - // foo => v1 is now in last level - ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1); - - // Place a table at level last-1 to prevent merging with preceding mutation - ASSERT_OK(Put(1, "a", "begin")); - ASSERT_OK(Put(1, "z", "end")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(last - 1, 1); - ASSERT_EQ(NumTableFilesAtLevel(last, 1), 1); - ASSERT_EQ(NumTableFilesAtLevel(last - 1, 1), 1); - - ASSERT_OK(Delete(1, "foo")); - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]"); - ASSERT_OK(Flush(1)); // Moves to level last-2 - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]"); - ASSERT_OK( - dbfull()->TEST_CompactRange(last - 2, nullptr, nullptr, handles_[1])); - // DEL kept: "last" file overlaps - ASSERT_EQ(AllEntriesFor("foo", 1), "[ DEL, v1 ]"); - ASSERT_OK( - dbfull()->TEST_CompactRange(last - 1, nullptr, nullptr, handles_[1])); - // Merging last-1 w/ last, so we are the base level for "foo", so - // DEL is removed. (as is v1). - ASSERT_EQ(AllEntriesFor("foo", 1), "[ ]"); -} - -TEST_F(DBTest, OverlapInLevel0) { - do { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - - // Fill levels 1 and 2 to disable the pushing of new memtables to levels > - // 0. - ASSERT_OK(Put(1, "100", "v100")); - ASSERT_OK(Put(1, "999", "v999")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(2, 1); - ASSERT_OK(Delete(1, "100")); - ASSERT_OK(Delete(1, "999")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(1, 1); - ASSERT_EQ("0,1,1", FilesPerLevel(1)); - - // Make files spanning the following ranges in level-0: - // files[0] 200 .. 900 - // files[1] 300 .. 500 - // Note that files are sorted by smallest key. - ASSERT_OK(Put(1, "300", "v300")); - ASSERT_OK(Put(1, "500", "v500")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "200", "v200")); - ASSERT_OK(Put(1, "600", "v600")); - ASSERT_OK(Put(1, "900", "v900")); - ASSERT_OK(Flush(1)); - ASSERT_EQ("2,1,1", FilesPerLevel(1)); - - // BEGIN addition to existing test - // Take this opportunity to verify SST unique ids (including Plain table) - TablePropertiesCollection tbc; - ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[1], &tbc)); - VerifySstUniqueIds(tbc); - // END addition to existing test - - // Compact away the placeholder files we created initially - ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1])); - ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr, handles_[1])); - ASSERT_EQ("2", FilesPerLevel(1)); - - // Do a memtable compaction. Before bug-fix, the compaction would - // not detect the overlap with level-0 files and would incorrectly place - // the deletion in a deeper level. - ASSERT_OK(Delete(1, "600")); - ASSERT_OK(Flush(1)); - ASSERT_EQ("3", FilesPerLevel(1)); - ASSERT_EQ("NOT_FOUND", Get(1, "600")); - } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction)); -} - -TEST_F(DBTest, ComparatorCheck) { - class NewComparator : public Comparator { - public: - const char* Name() const override { return "rocksdb.NewComparator"; } - int Compare(const Slice& a, const Slice& b) const override { - return BytewiseComparator()->Compare(a, b); - } - void FindShortestSeparator(std::string* s, const Slice& l) const override { - BytewiseComparator()->FindShortestSeparator(s, l); - } - void FindShortSuccessor(std::string* key) const override { - BytewiseComparator()->FindShortSuccessor(key); - } - }; - Options new_options, options; - NewComparator cmp; - do { - options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - new_options = CurrentOptions(); - new_options.comparator = &cmp; - // only the non-default column family has non-matching comparator - Status s = TryReopenWithColumnFamilies( - {"default", "pikachu"}, std::vector({options, new_options})); - ASSERT_TRUE(!s.ok()); - ASSERT_TRUE(s.ToString().find("comparator") != std::string::npos) - << s.ToString(); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTest, CustomComparator) { - class NumberComparator : public Comparator { - public: - const char* Name() const override { return "test.NumberComparator"; } - int Compare(const Slice& a, const Slice& b) const override { - return ToNumber(a) - ToNumber(b); - } - void FindShortestSeparator(std::string* s, const Slice& l) const override { - ToNumber(*s); // Check format - ToNumber(l); // Check format - } - void FindShortSuccessor(std::string* key) const override { - ToNumber(*key); // Check format - } - - private: - static int ToNumber(const Slice& x) { - // Check that there are no extra characters. - EXPECT_TRUE(x.size() >= 2 && x[0] == '[' && x[x.size() - 1] == ']') - << EscapeString(x); - int val; - char ignored; - EXPECT_TRUE(sscanf(x.ToString().c_str(), "[%i]%c", &val, &ignored) == 1) - << EscapeString(x); - return val; - } - }; - Options new_options; - NumberComparator cmp; - do { - new_options = CurrentOptions(); - new_options.create_if_missing = true; - new_options.comparator = &cmp; - new_options.write_buffer_size = 4096; // Compact more often - new_options.arena_block_size = 4096; - new_options = CurrentOptions(new_options); - DestroyAndReopen(new_options); - CreateAndReopenWithCF({"pikachu"}, new_options); - ASSERT_OK(Put(1, "[10]", "ten")); - ASSERT_OK(Put(1, "[0x14]", "twenty")); - for (int i = 0; i < 2; i++) { - ASSERT_EQ("ten", Get(1, "[10]")); - ASSERT_EQ("ten", Get(1, "[0xa]")); - ASSERT_EQ("twenty", Get(1, "[20]")); - ASSERT_EQ("twenty", Get(1, "[0x14]")); - ASSERT_EQ("NOT_FOUND", Get(1, "[15]")); - ASSERT_EQ("NOT_FOUND", Get(1, "[0xf]")); - Compact(1, "[0]", "[9999]"); - } - - for (int run = 0; run < 2; run++) { - for (int i = 0; i < 1000; i++) { - char buf[100]; - snprintf(buf, sizeof(buf), "[%d]", i * 10); - ASSERT_OK(Put(1, buf, buf)); - } - Compact(1, "[0]", "[1000000]"); - } - } while (ChangeCompactOptions()); -} - -TEST_F(DBTest, DBOpen_Options) { - Options options = CurrentOptions(); - std::string dbname = test::PerThreadDBPath("db_options_test"); - ASSERT_OK(DestroyDB(dbname, options)); - - // Does not exist, and create_if_missing == false: error - DB* db = nullptr; - options.create_if_missing = false; - Status s = DB::Open(options, dbname, &db); - ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr); - ASSERT_TRUE(db == nullptr); - - // Does not exist, and create_if_missing == true: OK - options.create_if_missing = true; - s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - delete db; - db = nullptr; - - // Does exist, and error_if_exists == true: error - options.create_if_missing = false; - options.error_if_exists = true; - s = DB::Open(options, dbname, &db); - ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr); - ASSERT_TRUE(db == nullptr); - - // Does exist, and error_if_exists == false: OK - options.create_if_missing = true; - options.error_if_exists = false; - s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_TRUE(db != nullptr); - - delete db; - db = nullptr; -} - -TEST_F(DBTest, DBOpen_Change_NumLevels) { - Options options = CurrentOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - ASSERT_TRUE(db_ != nullptr); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "a", "123")); - ASSERT_OK(Put(1, "b", "234")); - ASSERT_OK(Flush(1)); - MoveFilesToLevel(3, 1); - Close(); - - options.create_if_missing = false; - options.num_levels = 2; - Status s = TryReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_TRUE(strstr(s.ToString().c_str(), "Invalid argument") != nullptr); - ASSERT_TRUE(db_ == nullptr); -} - -TEST_F(DBTest, DestroyDBMetaDatabase) { - std::string dbname = test::PerThreadDBPath("db_meta"); - ASSERT_OK(env_->CreateDirIfMissing(dbname)); - std::string metadbname = MetaDatabaseName(dbname, 0); - ASSERT_OK(env_->CreateDirIfMissing(metadbname)); - std::string metametadbname = MetaDatabaseName(metadbname, 0); - ASSERT_OK(env_->CreateDirIfMissing(metametadbname)); - - // Destroy previous versions if they exist. Using the long way. - Options options = CurrentOptions(); - ASSERT_OK(DestroyDB(metametadbname, options)); - ASSERT_OK(DestroyDB(metadbname, options)); - ASSERT_OK(DestroyDB(dbname, options)); - - // Setup databases - DB* db = nullptr; - ASSERT_OK(DB::Open(options, dbname, &db)); - delete db; - db = nullptr; - ASSERT_OK(DB::Open(options, metadbname, &db)); - delete db; - db = nullptr; - ASSERT_OK(DB::Open(options, metametadbname, &db)); - delete db; - db = nullptr; - - // Delete databases - ASSERT_OK(DestroyDB(dbname, options)); - - // Check if deletion worked. - options.create_if_missing = false; - ASSERT_TRUE(!(DB::Open(options, dbname, &db)).ok()); - ASSERT_TRUE(!(DB::Open(options, metadbname, &db)).ok()); - ASSERT_TRUE(!(DB::Open(options, metametadbname, &db)).ok()); -} - -TEST_F(DBTest, SnapshotFiles) { - do { - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; // Large write buffer - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - - // Write 8MB (80 values, each 100K) - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - std::vector values; - for (int i = 0; i < 80; i++) { - values.push_back(rnd.RandomString(100000)); - ASSERT_OK(Put((i < 40), Key(i), values[i])); - } - - // assert that nothing makes it to disk yet. - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - - // get a file snapshot - uint64_t manifest_number = 0; - uint64_t manifest_size = 0; - std::vector files; - ASSERT_OK(dbfull()->DisableFileDeletions()); - ASSERT_OK(dbfull()->GetLiveFiles(files, &manifest_size)); - - // CURRENT, MANIFEST, OPTIONS, *.sst files (one for each CF) - ASSERT_EQ(files.size(), 5U); - - uint64_t number = 0; - FileType type; - - // copy these files to a new snapshot directory - std::string snapdir = dbname_ + ".snapdir/"; - if (env_->FileExists(snapdir).ok()) { - ASSERT_OK(DestroyDir(env_, snapdir)); - } - ASSERT_OK(env_->CreateDir(snapdir)); - - for (size_t i = 0; i < files.size(); i++) { - // our clients require that GetLiveFiles returns - // files with "/" as first character! - ASSERT_EQ(files[i][0], '/'); - std::string src = dbname_ + files[i]; - std::string dest = snapdir + files[i]; - - uint64_t size; - ASSERT_OK(env_->GetFileSize(src, &size)); - - // record the number and the size of the - // latest manifest file - if (ParseFileName(files[i].substr(1), &number, &type)) { - if (type == kDescriptorFile) { - ASSERT_EQ(manifest_number, 0); - manifest_number = number; - ASSERT_GE(size, manifest_size); - size = manifest_size; // copy only valid MANIFEST data - } - } - CopyFile(src, dest, size); - } - - // release file snapshot - ASSERT_OK(dbfull()->EnableFileDeletions(/*force*/ false)); - // overwrite one key, this key should not appear in the snapshot - std::vector extras; - for (unsigned int i = 0; i < 1; i++) { - extras.push_back(rnd.RandomString(100000)); - ASSERT_OK(Put(0, Key(i), extras[i])); - } - - // verify that data in the snapshot are correct - std::vector column_families; - column_families.emplace_back("default", ColumnFamilyOptions()); - column_families.emplace_back("pikachu", ColumnFamilyOptions()); - std::vector cf_handles; - DB* snapdb; - DBOptions opts; - opts.env = env_; - opts.create_if_missing = false; - Status stat = - DB::Open(opts, snapdir, column_families, &cf_handles, &snapdb); - ASSERT_OK(stat); - - ReadOptions roptions; - std::string val; - for (unsigned int i = 0; i < 80; i++) { - ASSERT_OK(snapdb->Get(roptions, cf_handles[i < 40], Key(i), &val)); - ASSERT_EQ(values[i].compare(val), 0); - } - for (auto cfh : cf_handles) { - delete cfh; - } - delete snapdb; - - // look at the new live files after we added an 'extra' key - // and after we took the first snapshot. - uint64_t new_manifest_number = 0; - uint64_t new_manifest_size = 0; - std::vector newfiles; - ASSERT_OK(dbfull()->DisableFileDeletions()); - ASSERT_OK(dbfull()->GetLiveFiles(newfiles, &new_manifest_size)); - - // find the new manifest file. assert that this manifest file is - // the same one as in the previous snapshot. But its size should be - // larger because we added an extra key after taking the - // previous shapshot. - for (size_t i = 0; i < newfiles.size(); i++) { - std::string src = dbname_ + "/" + newfiles[i]; - // record the lognumber and the size of the - // latest manifest file - if (ParseFileName(newfiles[i].substr(1), &number, &type)) { - if (type == kDescriptorFile) { - ASSERT_EQ(new_manifest_number, 0); - uint64_t size; - new_manifest_number = number; - ASSERT_OK(env_->GetFileSize(src, &size)); - ASSERT_GE(size, new_manifest_size); - } - } - } - ASSERT_EQ(manifest_number, new_manifest_number); - ASSERT_GT(new_manifest_size, manifest_size); - - // Also test GetLiveFilesStorageInfo - std::vector new_infos; - ASSERT_OK(db_->GetLiveFilesStorageInfo(LiveFilesStorageInfoOptions(), - &new_infos)); - - // Close DB (while deletions disabled) - Close(); - - // Validate - for (auto& info : new_infos) { - std::string path = info.directory + "/" + info.relative_filename; - uint64_t size; - ASSERT_OK(env_->GetFileSize(path, &size)); - if (info.trim_to_size) { - ASSERT_LE(info.size, size); - } else if (!info.replacement_contents.empty()) { - ASSERT_EQ(info.size, info.replacement_contents.size()); - } else { - ASSERT_EQ(info.size, size); - } - if (info.file_type == kDescriptorFile) { - ASSERT_EQ(info.file_number, manifest_number); - } - } - } while (ChangeCompactOptions()); -} - -TEST_F(DBTest, ReadonlyDBGetLiveManifestSize) { - do { - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 2; - DestroyAndReopen(options); - - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - - uint64_t manifest_size = 0; - std::vector files; - ASSERT_OK(dbfull()->GetLiveFiles(files, &manifest_size)); - - for (const std::string& f : files) { - uint64_t number = 0; - FileType type; - if (ParseFileName(f.substr(1), &number, &type)) { - if (type == kDescriptorFile) { - uint64_t size_on_disk; - ASSERT_OK(env_->GetFileSize(dbname_ + "/" + f, &size_on_disk)); - ASSERT_EQ(manifest_size, size_on_disk); - break; - } - } - } - Close(); - } while (ChangeCompactOptions()); -} - -TEST_F(DBTest, GetLiveBlobFiles) { - // Note: the following prevents an otherwise harmless data race between the - // test setup code (AddBlobFile) below and the periodic stat dumping thread. - Options options = CurrentOptions(); - options.stats_dump_period_sec = 0; - - constexpr uint64_t blob_file_number = 234; - constexpr uint64_t total_blob_count = 555; - constexpr uint64_t total_blob_bytes = 66666; - constexpr char checksum_method[] = "CRC32"; - constexpr char checksum_value[] = "\x3d\x87\xff\x57"; - constexpr uint64_t garbage_blob_count = 0; - constexpr uint64_t garbage_blob_bytes = 0; - - Reopen(options); - - AddBlobFile(db_->DefaultColumnFamily(), blob_file_number, total_blob_count, - total_blob_bytes, checksum_method, checksum_value, - garbage_blob_count, garbage_blob_bytes); - // Make sure it appears in the results returned by GetLiveFiles. - uint64_t manifest_size = 0; - std::vector files; - ASSERT_OK(dbfull()->GetLiveFiles(files, &manifest_size)); - - ASSERT_FALSE(files.empty()); - ASSERT_EQ(files[0], BlobFileName("", blob_file_number)); - - ColumnFamilyMetaData cfmd; - - db_->GetColumnFamilyMetaData(&cfmd); - ASSERT_EQ(cfmd.blob_files.size(), 1); - const BlobMetaData& bmd = cfmd.blob_files[0]; - - CheckBlobMetaData(bmd, blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value, garbage_blob_count, - garbage_blob_bytes); - ASSERT_EQ(NormalizePath(bmd.blob_file_path), NormalizePath(dbname_)); - ASSERT_EQ(cfmd.blob_file_count, 1U); - ASSERT_EQ(cfmd.blob_file_size, bmd.blob_file_size); -} - -TEST_F(DBTest, PurgeInfoLogs) { - Options options = CurrentOptions(); - options.keep_log_file_num = 5; - options.create_if_missing = true; - options.env = env_; - for (int mode = 0; mode <= 1; mode++) { - if (mode == 1) { - options.db_log_dir = dbname_ + "_logs"; - ASSERT_OK(env_->CreateDirIfMissing(options.db_log_dir)); - } else { - options.db_log_dir = ""; - } - for (int i = 0; i < 8; i++) { - Reopen(options); - } - - std::vector files; - ASSERT_OK(env_->GetChildren( - options.db_log_dir.empty() ? dbname_ : options.db_log_dir, &files)); - int info_log_count = 0; - for (std::string file : files) { - if (file.find("LOG") != std::string::npos) { - info_log_count++; - } - } - ASSERT_EQ(5, info_log_count); - - Destroy(options); - // For mode (1), test DestroyDB() to delete all the logs under DB dir. - // For mode (2), no info log file should have been put under DB dir. - // Since dbname_ has no children, there is no need to loop db_files - std::vector db_files; - ASSERT_TRUE(env_->GetChildren(dbname_, &db_files).IsNotFound()); - ASSERT_TRUE(db_files.empty()); - - if (mode == 1) { - // Cleaning up - ASSERT_OK(env_->GetChildren(options.db_log_dir, &files)); - for (std::string file : files) { - ASSERT_OK(env_->DeleteFile(options.db_log_dir + "/" + file)); - } - ASSERT_OK(env_->DeleteDir(options.db_log_dir)); - } - } -} - -// Multi-threaded test: -namespace { - -static const int kColumnFamilies = 10; -static const int kNumThreads = 10; -static const int kTestSeconds = 10; -static const int kNumKeys = 1000; - -struct MTState { - DBTest* test; - std::atomic counter[kNumThreads]; -}; - -struct MTThread { - MTState* state; - int id; - bool multiget_batched; -}; - -static void MTThreadBody(void* arg) { - MTThread* t = reinterpret_cast(arg); - int id = t->id; - DB* db = t->state->test->db_; - int counter = 0; - std::shared_ptr clock = SystemClock::Default(); - auto end_micros = clock->NowMicros() + kTestSeconds * 1000000U; - - fprintf(stderr, "... starting thread %d\n", id); - Random rnd(1000 + id); - char valbuf[1500]; - while (clock->NowMicros() < end_micros) { - t->state->counter[id].store(counter, std::memory_order_release); - - int key = rnd.Uniform(kNumKeys); - char keybuf[20]; - snprintf(keybuf, sizeof(keybuf), "%016d", key); - - if (rnd.OneIn(2)) { - // Write values of the form . - // into each of the CFs - // We add some padding for force compactions. - int unique_id = rnd.Uniform(1000000); - - // Half of the time directly use WriteBatch. Half of the time use - // WriteBatchWithIndex. - if (rnd.OneIn(2)) { - WriteBatch batch; - for (int cf = 0; cf < kColumnFamilies; ++cf) { - snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id, - static_cast(counter), cf, unique_id); - ASSERT_OK(batch.Put(t->state->test->handles_[cf], Slice(keybuf), - Slice(valbuf))); - } - ASSERT_OK(db->Write(WriteOptions(), &batch)); - } else { - WriteBatchWithIndex batch(db->GetOptions().comparator); - for (int cf = 0; cf < kColumnFamilies; ++cf) { - snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id, - static_cast(counter), cf, unique_id); - ASSERT_OK(batch.Put(t->state->test->handles_[cf], Slice(keybuf), - Slice(valbuf))); - } - ASSERT_OK(db->Write(WriteOptions(), batch.GetWriteBatch())); - } - } else { - // Read a value and verify that it matches the pattern written above - // and that writes to all column families were atomic (unique_id is the - // same) - std::vector keys(kColumnFamilies, Slice(keybuf)); - std::vector values; - std::vector statuses; - if (!t->multiget_batched) { - statuses = db->MultiGet(ReadOptions(), t->state->test->handles_, keys, - &values); - } else { - std::vector pin_values(keys.size()); - statuses.resize(keys.size()); - const Snapshot* snapshot = db->GetSnapshot(); - ReadOptions ro; - ro.snapshot = snapshot; - for (int cf = 0; cf < kColumnFamilies; ++cf) { - db->MultiGet(ro, t->state->test->handles_[cf], 1, &keys[cf], - &pin_values[cf], &statuses[cf]); - } - db->ReleaseSnapshot(snapshot); - values.resize(keys.size()); - for (int cf = 0; cf < kColumnFamilies; ++cf) { - if (statuses[cf].ok()) { - values[cf].assign(pin_values[cf].data(), pin_values[cf].size()); - } - } - } - Status s = statuses[0]; - // all statuses have to be the same - for (size_t i = 1; i < statuses.size(); ++i) { - // they are either both ok or both not-found - ASSERT_TRUE((s.ok() && statuses[i].ok()) || - (s.IsNotFound() && statuses[i].IsNotFound())); - } - if (s.IsNotFound()) { - // Key has not yet been written - } else { - // Check that the writer thread counter is >= the counter in the value - ASSERT_OK(s); - int unique_id = -1; - for (int i = 0; i < kColumnFamilies; ++i) { - int k, w, c, cf, u; - ASSERT_EQ(5, sscanf(values[i].c_str(), "%d.%d.%d.%d.%d", &k, &w, &c, - &cf, &u)) - << values[i]; - ASSERT_EQ(k, key); - ASSERT_GE(w, 0); - ASSERT_LT(w, kNumThreads); - ASSERT_LE(c, t->state->counter[w].load(std::memory_order_acquire)); - ASSERT_EQ(cf, i); - if (i == 0) { - unique_id = u; - } else { - // this checks that updates across column families happened - // atomically -- all unique ids are the same - ASSERT_EQ(u, unique_id); - } - } - } - } - counter++; - } - fprintf(stderr, "... stopping thread %d after %d ops\n", id, int(counter)); -} - -} // anonymous namespace - -class MultiThreadedDBTest - : public DBTest, - public ::testing::WithParamInterface> { - public: - void SetUp() override { - std::tie(option_config_, multiget_batched_) = GetParam(); - } - - static std::vector GenerateOptionConfigs() { - std::vector optionConfigs; - for (int optionConfig = kDefault; optionConfig < kEnd; ++optionConfig) { - optionConfigs.push_back(optionConfig); - } - return optionConfigs; - } - - bool multiget_batched_; -}; - -TEST_P(MultiThreadedDBTest, MultiThreaded) { - if (option_config_ == kPipelinedWrite) return; - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - Options options = CurrentOptions(options_override); - std::vector cfs; - for (int i = 1; i < kColumnFamilies; ++i) { - cfs.push_back(std::to_string(i)); - } - Reopen(options); - CreateAndReopenWithCF(cfs, options); - // Initialize state - MTState mt; - mt.test = this; - for (int id = 0; id < kNumThreads; id++) { - mt.counter[id].store(0, std::memory_order_release); - } - - // Start threads - MTThread thread[kNumThreads]; - for (int id = 0; id < kNumThreads; id++) { - thread[id].state = &mt; - thread[id].id = id; - thread[id].multiget_batched = multiget_batched_; - env_->StartThread(MTThreadBody, &thread[id]); - } - - env_->WaitForJoin(); -} - -INSTANTIATE_TEST_CASE_P( - MultiThreaded, MultiThreadedDBTest, - ::testing::Combine( - ::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()), - ::testing::Bool())); - -// Group commit test: -#if !defined(OS_WIN) -// Disable this test temporarily on Travis and appveyor as it fails -// intermittently. Github issue: #4151 -namespace { - -static const int kGCNumThreads = 4; -static const int kGCNumKeys = 1000; - -struct GCThread { - DB* db; - int id; - std::atomic done; -}; - -static void GCThreadBody(void* arg) { - GCThread* t = reinterpret_cast(arg); - int id = t->id; - DB* db = t->db; - WriteOptions wo; - - for (int i = 0; i < kGCNumKeys; ++i) { - std::string kv(std::to_string(i + id * kGCNumKeys)); - ASSERT_OK(db->Put(wo, kv, kv)); - } - t->done = true; -} - -} // anonymous namespace - -TEST_F(DBTest, GroupCommitTest) { - do { - Options options = CurrentOptions(); - options.env = env_; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - Reopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"WriteThread::JoinBatchGroup:BeganWaiting", - "DBImpl::WriteImpl:BeforeLeaderEnters"}, - {"WriteThread::AwaitState:BlockingWaiting", - "WriteThread::EnterAsBatchGroupLeader:End"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Start threads - GCThread thread[kGCNumThreads]; - for (int id = 0; id < kGCNumThreads; id++) { - thread[id].id = id; - thread[id].db = db_; - thread[id].done = false; - env_->StartThread(GCThreadBody, &thread[id]); - } - env_->WaitForJoin(); - - ASSERT_GT(TestGetTickerCount(options, WRITE_DONE_BY_OTHER), 0); - - std::vector expected_db; - for (int i = 0; i < kGCNumThreads * kGCNumKeys; ++i) { - expected_db.push_back(std::to_string(i)); - } - std::sort(expected_db.begin(), expected_db.end()); - - Iterator* itr = db_->NewIterator(ReadOptions()); - itr->SeekToFirst(); - for (auto x : expected_db) { - ASSERT_TRUE(itr->Valid()); - ASSERT_EQ(itr->key().ToString(), x); - ASSERT_EQ(itr->value().ToString(), x); - itr->Next(); - } - ASSERT_TRUE(!itr->Valid()); - delete itr; - - HistogramData hist_data; - options.statistics->histogramData(DB_WRITE, &hist_data); - ASSERT_GT(hist_data.average, 0.0); - } while (ChangeOptions(kSkipNoSeekToLast)); -} -#endif // OS_WIN - -namespace { -using KVMap = std::map; -} - -class ModelDB : public DB { - public: - class ModelSnapshot : public Snapshot { - public: - KVMap map_; - - SequenceNumber GetSequenceNumber() const override { - // no need to call this - assert(false); - return 0; - } - - int64_t GetUnixTime() const override { - // no need to call this - assert(false); - return 0; - } - - uint64_t GetTimestamp() const override { - // no need to call this - assert(false); - return 0; - } - }; - - explicit ModelDB(const Options& options) : options_(options) {} - using DB::Put; - Status Put(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k, - const Slice& v) override { - WriteBatch batch; - Status s = batch.Put(cf, k, v); - if (!s.ok()) { - return s; - } - return Write(o, &batch); - } - Status Put(const WriteOptions& /*o*/, ColumnFamilyHandle* /*cf*/, - const Slice& /*k*/, const Slice& /*ts*/, - const Slice& /*v*/) override { - return Status::NotSupported(); - } - - using DB::PutEntity; - Status PutEntity(const WriteOptions& /* options */, - ColumnFamilyHandle* /* column_family */, - const Slice& /* key */, - const WideColumns& /* columns */) override { - return Status::NotSupported(); - } - - using DB::Close; - Status Close() override { return Status::OK(); } - using DB::Delete; - Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf, - const Slice& key) override { - WriteBatch batch; - Status s = batch.Delete(cf, key); - if (!s.ok()) { - return s; - } - return Write(o, &batch); - } - Status Delete(const WriteOptions& /*o*/, ColumnFamilyHandle* /*cf*/, - const Slice& /*key*/, const Slice& /*ts*/) override { - return Status::NotSupported(); - } - using DB::SingleDelete; - Status SingleDelete(const WriteOptions& o, ColumnFamilyHandle* cf, - const Slice& key) override { - WriteBatch batch; - Status s = batch.SingleDelete(cf, key); - if (!s.ok()) { - return s; - } - return Write(o, &batch); - } - Status SingleDelete(const WriteOptions& /*o*/, ColumnFamilyHandle* /*cf*/, - const Slice& /*key*/, const Slice& /*ts*/) override { - return Status::NotSupported(); - } - using DB::Merge; - Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k, - const Slice& v) override { - WriteBatch batch; - Status s = batch.Merge(cf, k, v); - if (!s.ok()) { - return s; - } - return Write(o, &batch); - } - Status Merge(const WriteOptions& /*o*/, ColumnFamilyHandle* /*cf*/, - const Slice& /*k*/, const Slice& /*ts*/, - const Slice& /*value*/) override { - return Status::NotSupported(); - } - using DB::Get; - Status Get(const ReadOptions& /*options*/, ColumnFamilyHandle* /*cf*/, - const Slice& key, PinnableSlice* /*value*/) override { - return Status::NotSupported(key); - } - - using DB::GetMergeOperands; - virtual Status GetMergeOperands( - const ReadOptions& /*options*/, ColumnFamilyHandle* /*column_family*/, - const Slice& key, PinnableSlice* /*slice*/, - GetMergeOperandsOptions* /*merge_operands_options*/, - int* /*number_of_operands*/) override { - return Status::NotSupported(key); - } - - using DB::MultiGet; - std::vector MultiGet( - const ReadOptions& /*options*/, - const std::vector& /*column_family*/, - const std::vector& keys, - std::vector* /*values*/) override { - std::vector s(keys.size(), - Status::NotSupported("Not implemented.")); - return s; - } - - using DB::IngestExternalFile; - Status IngestExternalFile( - ColumnFamilyHandle* /*column_family*/, - const std::vector& /*external_files*/, - const IngestExternalFileOptions& /*options*/) override { - return Status::NotSupported("Not implemented."); - } - - using DB::IngestExternalFiles; - Status IngestExternalFiles( - const std::vector& /*args*/) override { - return Status::NotSupported("Not implemented"); - } - - using DB::CreateColumnFamilyWithImport; - virtual Status CreateColumnFamilyWithImport( - const ColumnFamilyOptions& /*options*/, - const std::string& /*column_family_name*/, - const ImportColumnFamilyOptions& /*import_options*/, - const ExportImportFilesMetaData& /*metadata*/, - ColumnFamilyHandle** /*handle*/) override { - return Status::NotSupported("Not implemented."); - } - - using DB::VerifyChecksum; - Status VerifyChecksum(const ReadOptions&) override { - return Status::NotSupported("Not implemented."); - } - - using DB::GetPropertiesOfAllTables; - Status GetPropertiesOfAllTables( - ColumnFamilyHandle* /*column_family*/, - TablePropertiesCollection* /*props*/) override { - return Status(); - } - - Status GetPropertiesOfTablesInRange( - ColumnFamilyHandle* /*column_family*/, const Range* /*range*/, - std::size_t /*n*/, TablePropertiesCollection* /*props*/) override { - return Status(); - } - - using DB::KeyMayExist; - bool KeyMayExist(const ReadOptions& /*options*/, - ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/, - std::string* /*value*/, - bool* value_found = nullptr) override { - if (value_found != nullptr) { - *value_found = false; - } - return true; // Not Supported directly - } - using DB::NewIterator; - Iterator* NewIterator(const ReadOptions& options, - ColumnFamilyHandle* /*column_family*/) override { - if (options.snapshot == nullptr) { - KVMap* saved = new KVMap; - *saved = map_; - return new ModelIter(saved, true); - } else { - const KVMap* snapshot_state = - &(reinterpret_cast(options.snapshot)->map_); - return new ModelIter(snapshot_state, false); - } - } - Status NewIterators(const ReadOptions& /*options*/, - const std::vector& /*column_family*/, - std::vector* /*iterators*/) override { - return Status::NotSupported("Not supported yet"); - } - const Snapshot* GetSnapshot() override { - ModelSnapshot* snapshot = new ModelSnapshot; - snapshot->map_ = map_; - return snapshot; - } - - void ReleaseSnapshot(const Snapshot* snapshot) override { - delete reinterpret_cast(snapshot); - } - - Status Write(const WriteOptions& /*options*/, WriteBatch* batch) override { - class Handler : public WriteBatch::Handler { - public: - KVMap* map_; - void Put(const Slice& key, const Slice& value) override { - (*map_)[key.ToString()] = value.ToString(); - } - void Merge(const Slice& /*key*/, const Slice& /*value*/) override { - // ignore merge for now - // (*map_)[key.ToString()] = value.ToString(); - } - void Delete(const Slice& key) override { map_->erase(key.ToString()); } - }; - Handler handler; - handler.map_ = &map_; - return batch->Iterate(&handler); - } - - using DB::GetProperty; - bool GetProperty(ColumnFamilyHandle* /*column_family*/, - const Slice& /*property*/, std::string* /*value*/) override { - return false; - } - using DB::GetIntProperty; - bool GetIntProperty(ColumnFamilyHandle* /*column_family*/, - const Slice& /*property*/, uint64_t* /*value*/) override { - return false; - } - using DB::GetMapProperty; - bool GetMapProperty(ColumnFamilyHandle* /*column_family*/, - const Slice& /*property*/, - std::map* /*value*/) override { - return false; - } - using DB::GetAggregatedIntProperty; - bool GetAggregatedIntProperty(const Slice& /*property*/, - uint64_t* /*value*/) override { - return false; - } - using DB::GetApproximateSizes; - Status GetApproximateSizes(const SizeApproximationOptions& /*options*/, - ColumnFamilyHandle* /*column_family*/, - const Range* /*range*/, int n, - uint64_t* sizes) override { - for (int i = 0; i < n; i++) { - sizes[i] = 0; - } - return Status::OK(); - } - using DB::GetApproximateMemTableStats; - void GetApproximateMemTableStats(ColumnFamilyHandle* /*column_family*/, - const Range& /*range*/, - uint64_t* const count, - uint64_t* const size) override { - *count = 0; - *size = 0; - } - using DB::CompactRange; - Status CompactRange(const CompactRangeOptions& /*options*/, - ColumnFamilyHandle* /*column_family*/, - const Slice* /*start*/, const Slice* /*end*/) override { - return Status::NotSupported("Not supported operation."); - } - - Status SetDBOptions( - const std::unordered_map& /*new_options*/) - override { - return Status::NotSupported("Not supported operation."); - } - - using DB::CompactFiles; - Status CompactFiles( - const CompactionOptions& /*compact_options*/, - ColumnFamilyHandle* /*column_family*/, - const std::vector& /*input_file_names*/, - const int /*output_level*/, const int /*output_path_id*/ = -1, - std::vector* const /*output_file_names*/ = nullptr, - CompactionJobInfo* /*compaction_job_info*/ = nullptr) override { - return Status::NotSupported("Not supported operation."); - } - - Status PauseBackgroundWork() override { - return Status::NotSupported("Not supported operation."); - } - - Status ContinueBackgroundWork() override { - return Status::NotSupported("Not supported operation."); - } - - Status EnableAutoCompaction( - const std::vector& /*column_family_handles*/) - override { - return Status::NotSupported("Not supported operation."); - } - - void EnableManualCompaction() override { return; } - - void DisableManualCompaction() override { return; } - - using DB::NumberLevels; - int NumberLevels(ColumnFamilyHandle* /*column_family*/) override { return 1; } - - using DB::MaxMemCompactionLevel; - int MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) override { - return 1; - } - - using DB::Level0StopWriteTrigger; - int Level0StopWriteTrigger(ColumnFamilyHandle* /*column_family*/) override { - return -1; - } - - const std::string& GetName() const override { return name_; } - - Env* GetEnv() const override { return nullptr; } - - using DB::GetOptions; - Options GetOptions(ColumnFamilyHandle* /*column_family*/) const override { - return options_; - } - - using DB::GetDBOptions; - DBOptions GetDBOptions() const override { return options_; } - - using DB::Flush; - Status Flush(const ROCKSDB_NAMESPACE::FlushOptions& /*options*/, - ColumnFamilyHandle* /*column_family*/) override { - Status ret; - return ret; - } - Status Flush( - const ROCKSDB_NAMESPACE::FlushOptions& /*options*/, - const std::vector& /*column_families*/) override { - return Status::OK(); - } - - Status SyncWAL() override { return Status::OK(); } - - Status DisableFileDeletions() override { return Status::OK(); } - - Status EnableFileDeletions(bool /*force*/) override { return Status::OK(); } - - Status GetLiveFiles(std::vector&, uint64_t* /*size*/, - bool /*flush_memtable*/ = true) override { - return Status::OK(); - } - - Status GetLiveFilesChecksumInfo( - FileChecksumList* /*checksum_list*/) override { - return Status::OK(); - } - - Status GetLiveFilesStorageInfo( - const LiveFilesStorageInfoOptions& /*opts*/, - std::vector* /*files*/) override { - return Status::OK(); - } - - Status GetSortedWalFiles(VectorLogPtr& /*files*/) override { - return Status::OK(); - } - - Status GetCurrentWalFile( - std::unique_ptr* /*current_log_file*/) override { - return Status::OK(); - } - - virtual Status GetCreationTimeOfOldestFile( - uint64_t* /*creation_time*/) override { - return Status::NotSupported(); - } - - Status DeleteFile(std::string /*name*/) override { return Status::OK(); } - - Status GetUpdatesSince( - ROCKSDB_NAMESPACE::SequenceNumber, - std::unique_ptr*, - const TransactionLogIterator::ReadOptions& /*read_options*/ = - TransactionLogIterator::ReadOptions()) override { - return Status::NotSupported("Not supported in Model DB"); - } - - void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/, - ColumnFamilyMetaData* /*metadata*/) override {} - - Status GetDbIdentity(std::string& /*identity*/) const override { - return Status::OK(); - } - - Status GetDbSessionId(std::string& /*session_id*/) const override { - return Status::OK(); - } - - SequenceNumber GetLatestSequenceNumber() const override { return 0; } - - Status IncreaseFullHistoryTsLow(ColumnFamilyHandle* /*cf*/, - std::string /*ts_low*/) override { - return Status::OK(); - } - - Status GetFullHistoryTsLow(ColumnFamilyHandle* /*cf*/, - std::string* /*ts_low*/) override { - return Status::OK(); - } - - ColumnFamilyHandle* DefaultColumnFamily() const override { return nullptr; } - - private: - class ModelIter : public Iterator { - public: - ModelIter(const KVMap* map, bool owned) - : map_(map), owned_(owned), iter_(map_->end()) {} - ~ModelIter() override { - if (owned_) delete map_; - } - bool Valid() const override { return iter_ != map_->end(); } - void SeekToFirst() override { iter_ = map_->begin(); } - void SeekToLast() override { - if (map_->empty()) { - iter_ = map_->end(); - } else { - iter_ = map_->find(map_->rbegin()->first); - } - } - void Seek(const Slice& k) override { - iter_ = map_->lower_bound(k.ToString()); - } - void SeekForPrev(const Slice& k) override { - iter_ = map_->upper_bound(k.ToString()); - Prev(); - } - void Next() override { ++iter_; } - void Prev() override { - if (iter_ == map_->begin()) { - iter_ = map_->end(); - return; - } - --iter_; - } - - Slice key() const override { return iter_->first; } - Slice value() const override { return iter_->second; } - Status status() const override { return Status::OK(); } - - private: - const KVMap* const map_; - const bool owned_; // Do we own map_ - KVMap::const_iterator iter_; - }; - const Options options_; - KVMap map_; - std::string name_ = ""; -}; - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -static std::string RandomKey(Random* rnd, int minimum = 0) { - int len; - do { - len = (rnd->OneIn(3) - ? 1 // Short sometimes to encourage collisions - : (rnd->OneIn(100) ? rnd->Skewed(10) : rnd->Uniform(10))); - } while (len < minimum); - return test::RandomKey(rnd, len); -} - -static bool CompareIterators(int step, DB* model, DB* db, - const Snapshot* model_snap, - const Snapshot* db_snap) { - ReadOptions options; - options.snapshot = model_snap; - Iterator* miter = model->NewIterator(options); - options.snapshot = db_snap; - Iterator* dbiter = db->NewIterator(options); - bool ok = true; - int count = 0; - for (miter->SeekToFirst(), dbiter->SeekToFirst(); - ok && miter->Valid() && dbiter->Valid(); miter->Next(), dbiter->Next()) { - count++; - if (miter->key().compare(dbiter->key()) != 0) { - fprintf(stderr, "step %d: Key mismatch: '%s' vs. '%s'\n", step, - EscapeString(miter->key()).c_str(), - EscapeString(dbiter->key()).c_str()); - ok = false; - break; - } - - if (miter->value().compare(dbiter->value()) != 0) { - fprintf(stderr, "step %d: Value mismatch for key '%s': '%s' vs. '%s'\n", - step, EscapeString(miter->key()).c_str(), - EscapeString(miter->value()).c_str(), - EscapeString(dbiter->value()).c_str()); - ok = false; - } - } - - if (ok) { - if (miter->Valid() != dbiter->Valid()) { - fprintf(stderr, "step %d: Mismatch at end of iterators: %d vs. %d\n", - step, miter->Valid(), dbiter->Valid()); - ok = false; - } - } - delete miter; - delete dbiter; - return ok; -} - -class DBTestRandomized : public DBTest, - public ::testing::WithParamInterface { - public: - void SetUp() override { option_config_ = GetParam(); } - - static std::vector GenerateOptionConfigs() { - std::vector option_configs; - // skip cuckoo hash as it does not support snapshot. - for (int option_config = kDefault; option_config < kEnd; ++option_config) { - if (!ShouldSkipOptions(option_config, - kSkipDeletesFilterFirst | kSkipNoSeekToLast)) { - option_configs.push_back(option_config); - } - } - option_configs.push_back(kBlockBasedTableWithIndexRestartInterval); - return option_configs; - } -}; - -INSTANTIATE_TEST_CASE_P( - DBTestRandomized, DBTestRandomized, - ::testing::ValuesIn(DBTestRandomized::GenerateOptionConfigs())); - -TEST_P(DBTestRandomized, Randomized) { - anon::OptionsOverride options_override; - options_override.skip_policy = kSkipNoSnapshot; - Options options = CurrentOptions(options_override); - DestroyAndReopen(options); - - Random rnd(test::RandomSeed() + GetParam()); - ModelDB model(options); - const int N = 10000; - const Snapshot* model_snap = nullptr; - const Snapshot* db_snap = nullptr; - std::string k, v; - for (int step = 0; step < N; step++) { - // TODO(sanjay): Test Get() works - int p = rnd.Uniform(100); - int minimum = 0; - if (option_config_ == kHashSkipList || option_config_ == kHashLinkList || - option_config_ == kPlainTableFirstBytePrefix || - option_config_ == kBlockBasedTableWithWholeKeyHashIndex || - option_config_ == kBlockBasedTableWithPrefixHashIndex) { - minimum = 1; - } - if (p < 45) { // Put - k = RandomKey(&rnd, minimum); - v = rnd.RandomString(rnd.OneIn(20) ? 100 + rnd.Uniform(100) - : rnd.Uniform(8)); - ASSERT_OK(model.Put(WriteOptions(), k, v)); - ASSERT_OK(db_->Put(WriteOptions(), k, v)); - } else if (p < 90) { // Delete - k = RandomKey(&rnd, minimum); - ASSERT_OK(model.Delete(WriteOptions(), k)); - ASSERT_OK(db_->Delete(WriteOptions(), k)); - } else { // Multi-element batch - WriteBatch b; - const int num = rnd.Uniform(8); - for (int i = 0; i < num; i++) { - if (i == 0 || !rnd.OneIn(10)) { - k = RandomKey(&rnd, minimum); - } else { - // Periodically re-use the same key from the previous iter, so - // we have multiple entries in the write batch for the same key - } - if (rnd.OneIn(2)) { - v = rnd.RandomString(rnd.Uniform(10)); - ASSERT_OK(b.Put(k, v)); - } else { - ASSERT_OK(b.Delete(k)); - } - } - ASSERT_OK(model.Write(WriteOptions(), &b)); - ASSERT_OK(db_->Write(WriteOptions(), &b)); - } - - if ((step % 100) == 0) { - // For DB instances that use the hash index + block-based table, the - // iterator will be invalid right when seeking a non-existent key, right - // than return a key that is close to it. - if (option_config_ != kBlockBasedTableWithWholeKeyHashIndex && - option_config_ != kBlockBasedTableWithPrefixHashIndex) { - ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr)); - ASSERT_TRUE(CompareIterators(step, &model, db_, model_snap, db_snap)); - } - - // Save a snapshot from each DB this time that we'll use next - // time we compare things, to make sure the current state is - // preserved with the snapshot - if (model_snap != nullptr) model.ReleaseSnapshot(model_snap); - if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap); - - Reopen(options); - ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr)); - - model_snap = model.GetSnapshot(); - db_snap = db_->GetSnapshot(); - } - } - if (model_snap != nullptr) model.ReleaseSnapshot(model_snap); - if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap); -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_F(DBTest, BlockBasedTablePrefixIndexTest) { - // create a DB with block prefix index - BlockBasedTableOptions table_options; - Options options = CurrentOptions(); - table_options.index_type = BlockBasedTableOptions::kHashSearch; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - - Reopen(options); - ASSERT_OK(Put("k1", "v1")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("k2", "v2")); - - // Reopen with different prefix extractor, make sure everything still works. - // RocksDB should just fall back to the binary index. - options.prefix_extractor.reset(NewFixedPrefixTransform(2)); - - Reopen(options); - ASSERT_EQ("v1", Get("k1")); - ASSERT_EQ("v2", Get("k2")); - - // Back to original - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:1"}})); - ASSERT_EQ("v1", Get("k1")); - ASSERT_EQ("v2", Get("k2")); - - // Same if there's a problem initally loading prefix transform - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Open::ForceNullTablePrefixExtractor", - [&](void* arg) { *static_cast(arg) = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); - ASSERT_EQ("v1", Get("k1")); - ASSERT_EQ("v2", Get("k2")); - - // Change again - ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}})); - ASSERT_EQ("v1", Get("k1")); - ASSERT_EQ("v2", Get("k2")); - SyncPoint::GetInstance()->DisableProcessing(); - - // Reopen with no prefix extractor, make sure everything still works. - // RocksDB should just fall back to the binary index. - table_options.index_type = BlockBasedTableOptions::kBinarySearch; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(); - - Reopen(options); - ASSERT_EQ("v1", Get("k1")); - ASSERT_EQ("v2", Get("k2")); -} - -TEST_F(DBTest, BlockBasedTablePrefixHashIndexTest) { - // create a DB with block prefix index - BlockBasedTableOptions table_options; - Options options = CurrentOptions(); - table_options.index_type = BlockBasedTableOptions::kHashSearch; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewCappedPrefixTransform(2)); - - Reopen(options); - ASSERT_OK(Put("kk1", "v1")); - ASSERT_OK(Put("kk2", "v2")); - ASSERT_OK(Put("kk", "v3")); - ASSERT_OK(Put("k", "v4")); - Flush(); - - ASSERT_EQ("v1", Get("kk1")); - ASSERT_EQ("v2", Get("kk2")); - - ASSERT_EQ("v3", Get("kk")); - ASSERT_EQ("v4", Get("k")); -} - -TEST_F(DBTest, BlockBasedTablePrefixIndexTotalOrderSeek) { - // create a DB with block prefix index - BlockBasedTableOptions table_options; - Options options = CurrentOptions(); - options.max_open_files = 10; - table_options.index_type = BlockBasedTableOptions::kHashSearch; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - - // RocksDB sanitize max open files to at least 20. Modify it back. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = static_cast(arg); - *max_open_files = 11; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Reopen(options); - ASSERT_OK(Put("k1", "v1")); - ASSERT_OK(Flush()); - - CompactRangeOptions cro; - cro.change_level = true; - cro.target_level = 1; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - // Force evict tables - dbfull()->TEST_table_cache()->SetCapacity(0); - // Make table cache to keep one entry. - dbfull()->TEST_table_cache()->SetCapacity(1); - - ReadOptions read_options; - read_options.total_order_seek = true; - { - std::unique_ptr iter(db_->NewIterator(read_options)); - iter->Seek("k1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("k1", iter->key().ToString()); - } - - // After total order seek, prefix index should still be used. - read_options.total_order_seek = false; - { - std::unique_ptr iter(db_->NewIterator(read_options)); - iter->Seek("k1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("k1", iter->key().ToString()); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBTest, ChecksumTest) { - BlockBasedTableOptions table_options; - Options options = CurrentOptions(); - - table_options.checksum = kCRC32c; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_OK(Put("a", "b")); - ASSERT_OK(Put("c", "d")); - ASSERT_OK(Flush()); // table with crc checksum - - table_options.checksum = kxxHash; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_OK(Put("e", "f")); - ASSERT_OK(Put("g", "h")); - ASSERT_OK(Flush()); // table with xxhash checksum - - table_options.checksum = kCRC32c; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_EQ("b", Get("a")); - ASSERT_EQ("d", Get("c")); - ASSERT_EQ("f", Get("e")); - ASSERT_EQ("h", Get("g")); - - table_options.checksum = kCRC32c; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_EQ("b", Get("a")); - ASSERT_EQ("d", Get("c")); - ASSERT_EQ("f", Get("e")); - ASSERT_EQ("h", Get("g")); -} - -TEST_P(DBTestWithParam, FIFOCompactionTest) { - for (int iter = 0; iter < 2; ++iter) { - // first iteration -- auto compaction - // second iteration -- manual compaction - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.write_buffer_size = 100 << 10; // 100KB - options.arena_block_size = 4096; - options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB - options.compression = kNoCompression; - options.create_if_missing = true; - options.max_subcompactions = max_subcompactions_; - if (iter == 1) { - options.disable_auto_compactions = true; - } - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 6; ++i) { - for (int j = 0; j < 110; ++j) { - ASSERT_OK(Put(std::to_string(i * 100 + j), rnd.RandomString(980))); - } - // flush should happen here - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - if (iter == 0) { - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } else { - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - // only 5 files should survive - ASSERT_EQ(NumTableFilesAtLevel(0), 5); - for (int i = 0; i < 50; ++i) { - // these keys should be deleted in previous compaction - ASSERT_EQ("NOT_FOUND", Get(std::to_string(i))); - } - } -} - -TEST_F(DBTest, FIFOCompactionTestWithCompaction) { - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.write_buffer_size = 20 << 10; // 20K - options.arena_block_size = 4096; - options.compaction_options_fifo.max_table_files_size = 1500 << 10; // 1MB - options.compaction_options_fifo.allow_compaction = true; - options.level0_file_num_compaction_trigger = 6; - options.compression = kNoCompression; - options.create_if_missing = true; - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 60; i++) { - // Generate and flush a file about 20KB. - for (int j = 0; j < 20; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - // It should be compacted to 10 files. - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - for (int i = 0; i < 60; i++) { - // Generate and flush a file about 20KB. - for (int j = 0; j < 20; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j + 2000), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - // It should be compacted to no more than 20 files. - ASSERT_GT(NumTableFilesAtLevel(0), 10); - ASSERT_LT(NumTableFilesAtLevel(0), 18); - // Size limit is still guaranteed. - ASSERT_LE(SizeAtLevel(0), - options.compaction_options_fifo.max_table_files_size); -} - -TEST_F(DBTest, FIFOCompactionStyleWithCompactionAndDelete) { - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.write_buffer_size = 20 << 10; // 20K - options.arena_block_size = 4096; - options.compaction_options_fifo.max_table_files_size = 1500 << 10; // 1MB - options.compaction_options_fifo.allow_compaction = true; - options.level0_file_num_compaction_trigger = 3; - options.compression = kNoCompression; - options.create_if_missing = true; - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 3; i++) { - // Each file contains a different key which will be dropped later. - ASSERT_OK(Put("a" + std::to_string(i), rnd.RandomString(500))); - ASSERT_OK(Put("key" + std::to_string(i), "")); - ASSERT_OK(Put("z" + std::to_string(i), rnd.RandomString(500))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 1); - for (int i = 0; i < 3; i++) { - ASSERT_EQ("", Get("key" + std::to_string(i))); - } - for (int i = 0; i < 3; i++) { - // Each file contains a different key which will be dropped later. - ASSERT_OK(Put("a" + std::to_string(i), rnd.RandomString(500))); - ASSERT_OK(Delete("key" + std::to_string(i))); - ASSERT_OK(Put("z" + std::to_string(i), rnd.RandomString(500))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 2); - for (int i = 0; i < 3; i++) { - ASSERT_EQ("NOT_FOUND", Get("key" + std::to_string(i))); - } -} - -// Check that FIFO-with-TTL is not supported with max_open_files != -1. -// Github issue #8014 -TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleFIFO; - options.create_if_missing = true; - options.ttl = 600; // seconds - - // TTL is not supported with max_open_files != -1. - options.max_open_files = 0; - ASSERT_TRUE(TryReopen(options).IsNotSupported()); - - options.max_open_files = 100; - ASSERT_TRUE(TryReopen(options).IsNotSupported()); - - // TTL is supported with unlimited max_open_files - options.max_open_files = -1; - ASSERT_OK(TryReopen(options)); -} - -// Check that FIFO-with-TTL is supported only with BlockBasedTableFactory. -TEST_F(DBTest, FIFOCompactionWithTTLAndVariousTableFormatsTest) { - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.create_if_missing = true; - options.ttl = 600; // seconds - - options = CurrentOptions(options); - options.table_factory.reset(NewBlockBasedTableFactory()); - ASSERT_OK(TryReopen(options)); - - Destroy(options); - options.table_factory.reset(NewPlainTableFactory()); - ASSERT_TRUE(TryReopen(options).IsNotSupported()); - - Destroy(options); - options.table_factory.reset(NewAdaptiveTableFactory()); - ASSERT_TRUE(TryReopen(options).IsNotSupported()); -} - -TEST_F(DBTest, FIFOCompactionWithTTLTest) { - Options options; - options.compaction_style = kCompactionStyleFIFO; - options.write_buffer_size = 10 << 10; // 10KB - options.arena_block_size = 4096; - options.compression = kNoCompression; - options.create_if_missing = true; - env_->SetMockSleep(); - options.env = env_; - - // Test to make sure that all files with expired ttl are deleted on next - // manual compaction. - { - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB - options.compaction_options_fifo.allow_compaction = false; - options.ttl = 1 * 60 * 60; // 1 hour - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // Sleep for 2 hours -- which is much greater than TTL. - env_->MockSleepForSeconds(2 * 60 * 60); - - // Since no flushes and compactions have run, the db should still be in - // the same state even after considerable time has passed. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - } - - // Test to make sure that all files with expired ttl are deleted on next - // automatic compaction. - { - options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB - options.compaction_options_fifo.allow_compaction = false; - options.ttl = 1 * 60 * 60; // 1 hour - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // Sleep for 2 hours -- which is much greater than TTL. - env_->MockSleepForSeconds(2 * 60 * 60); - // Just to make sure that we are in the same state even after sleeping. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - // Create 1 more file to trigger TTL compaction. The old files are dropped. - for (int i = 0; i < 1; i++) { - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Only the new 10 files remain. - ASSERT_EQ(NumTableFilesAtLevel(0), 1); - ASSERT_LE(SizeAtLevel(0), - options.compaction_options_fifo.max_table_files_size); - } - - // Test that shows the fall back to size-based FIFO compaction if TTL-based - // deletion doesn't move the total size to be less than max_table_files_size. - { - options.write_buffer_size = 10 << 10; // 10KB - options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB - options.compaction_options_fifo.allow_compaction = false; - options.ttl = 1 * 60 * 60; // 1 hour - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 3; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 3); - - // Sleep for 2 hours -- which is much greater than TTL. - env_->MockSleepForSeconds(2 * 60 * 60); - // Just to make sure that we are in the same state even after sleeping. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 3); - - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 140; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - // Size limit is still guaranteed. - ASSERT_LE(SizeAtLevel(0), - options.compaction_options_fifo.max_table_files_size); - } - - // Test with TTL + Intra-L0 compactions. - { - options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB - options.compaction_options_fifo.allow_compaction = true; - options.ttl = 1 * 60 * 60; // 1 hour - options.level0_file_num_compaction_trigger = 6; - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 10; i++) { - // Generate and flush a file about 10KB. - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - // With Intra-L0 compaction, out of 10 files, 6 files will be compacted to 1 - // (due to level0_file_num_compaction_trigger = 6). - // So total files = 1 + remaining 4 = 5. - ASSERT_EQ(NumTableFilesAtLevel(0), 5); - - // Sleep for 2 hours -- which is much greater than TTL. - env_->MockSleepForSeconds(2 * 60 * 60); - // Just to make sure that we are in the same state even after sleeping. - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 5); - - // Create 10 more files. The old 5 files are dropped as their ttl expired. - for (int i = 0; i < 10; i++) { - for (int j = 0; j < 10; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), 5); - ASSERT_LE(SizeAtLevel(0), - options.compaction_options_fifo.max_table_files_size); - } - - // Test with large TTL + Intra-L0 compactions. - // Files dropped based on size, as ttl doesn't kick in. - { - options.write_buffer_size = 20 << 10; // 20K - options.compaction_options_fifo.max_table_files_size = 1500 << 10; // 1.5MB - options.compaction_options_fifo.allow_compaction = true; - options.ttl = 1 * 60 * 60; // 1 hour - options.level0_file_num_compaction_trigger = 6; - options = CurrentOptions(options); - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < 60; i++) { - // Generate and flush a file about 20KB. - for (int j = 0; j < 20; j++) { - ASSERT_OK(Put(std::to_string(i * 20 + j), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - // It should be compacted to 10 files. - ASSERT_EQ(NumTableFilesAtLevel(0), 10); - - for (int i = 0; i < 60; i++) { - // Generate and flush a file about 20KB. - for (int j = 0; j < 20; j++) { - ASSERT_OK( - Put(std::to_string(i * 20 + j + 2000), rnd.RandomString(980))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - // It should be compacted to no more than 20 files. - ASSERT_GT(NumTableFilesAtLevel(0), 10); - ASSERT_LT(NumTableFilesAtLevel(0), 18); - // Size limit is still guaranteed. - ASSERT_LE(SizeAtLevel(0), - options.compaction_options_fifo.max_table_files_size); - } -} - -/* - * This test is not reliable enough as it heavily depends on disk behavior. - * Disable as it is flaky. - */ -TEST_F(DBTest, DISABLED_RateLimitingTest) { - Options options = CurrentOptions(); - options.write_buffer_size = 1 << 20; // 1MB - options.level0_file_num_compaction_trigger = 2; - options.target_file_size_base = 1 << 20; // 1MB - options.max_bytes_for_level_base = 4 << 20; // 4MB - options.max_bytes_for_level_multiplier = 4; - options.compression = kNoCompression; - options.create_if_missing = true; - options.env = env_; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.IncreaseParallelism(4); - DestroyAndReopen(options); - - WriteOptions wo; - wo.disableWAL = true; - - // # no rate limiting - Random rnd(301); - uint64_t start = env_->NowMicros(); - // Write ~96M data - for (int64_t i = 0; i < (96 << 10); ++i) { - ASSERT_OK(Put(rnd.RandomString(32), rnd.RandomString((1 << 10) + 1), wo)); - } - uint64_t elapsed = env_->NowMicros() - start; - double raw_rate = env_->bytes_written_ * 1000000.0 / elapsed; - uint64_t rate_limiter_drains = - TestGetTickerCount(options, NUMBER_RATE_LIMITER_DRAINS); - ASSERT_EQ(0, rate_limiter_drains); - Close(); - - // # rate limiting with 0.7 x threshold - options.rate_limiter.reset( - NewGenericRateLimiter(static_cast(0.7 * raw_rate))); - env_->bytes_written_ = 0; - DestroyAndReopen(options); - - start = env_->NowMicros(); - // Write ~96M data - for (int64_t i = 0; i < (96 << 10); ++i) { - ASSERT_OK(Put(rnd.RandomString(32), rnd.RandomString((1 << 10) + 1), wo)); - } - rate_limiter_drains = - TestGetTickerCount(options, NUMBER_RATE_LIMITER_DRAINS) - - rate_limiter_drains; - elapsed = env_->NowMicros() - start; - Close(); - ASSERT_EQ(options.rate_limiter->GetTotalBytesThrough(), env_->bytes_written_); - // Most intervals should've been drained (interval time is 100ms, elapsed is - // micros) - ASSERT_GT(rate_limiter_drains, 0); - ASSERT_LE(rate_limiter_drains, elapsed / 100000 + 1); - double ratio = env_->bytes_written_ * 1000000 / elapsed / raw_rate; - fprintf(stderr, "write rate ratio = %.2lf, expected 0.7\n", ratio); - ASSERT_TRUE(ratio < 0.8); - - // # rate limiting with half of the raw_rate - options.rate_limiter.reset( - NewGenericRateLimiter(static_cast(raw_rate / 2))); - env_->bytes_written_ = 0; - DestroyAndReopen(options); - - start = env_->NowMicros(); - // Write ~96M data - for (int64_t i = 0; i < (96 << 10); ++i) { - ASSERT_OK(Put(rnd.RandomString(32), rnd.RandomString((1 << 10) + 1), wo)); - } - elapsed = env_->NowMicros() - start; - rate_limiter_drains = - TestGetTickerCount(options, NUMBER_RATE_LIMITER_DRAINS) - - rate_limiter_drains; - Close(); - ASSERT_EQ(options.rate_limiter->GetTotalBytesThrough(), env_->bytes_written_); - // Most intervals should've been drained (interval time is 100ms, elapsed is - // micros) - ASSERT_GT(rate_limiter_drains, elapsed / 100000 / 2); - ASSERT_LE(rate_limiter_drains, elapsed / 100000 + 1); - ratio = env_->bytes_written_ * 1000000 / elapsed / raw_rate; - fprintf(stderr, "write rate ratio = %.2lf, expected 0.5\n", ratio); - ASSERT_LT(ratio, 0.6); -} - -// This is a mocked customed rate limiter without implementing optional APIs -// (e.g, RateLimiter::GetTotalPendingRequests()) -class MockedRateLimiterWithNoOptionalAPIImpl : public RateLimiter { - public: - MockedRateLimiterWithNoOptionalAPIImpl() {} - - ~MockedRateLimiterWithNoOptionalAPIImpl() override {} - - void SetBytesPerSecond(int64_t bytes_per_second) override { - (void)bytes_per_second; - } - - using RateLimiter::Request; - void Request(const int64_t bytes, const Env::IOPriority pri, - Statistics* stats) override { - (void)bytes; - (void)pri; - (void)stats; - } - - int64_t GetSingleBurstBytes() const override { return 200; } - - int64_t GetTotalBytesThrough( - const Env::IOPriority pri = Env::IO_TOTAL) const override { - (void)pri; - return 0; - } - - int64_t GetTotalRequests( - const Env::IOPriority pri = Env::IO_TOTAL) const override { - (void)pri; - return 0; - } - - int64_t GetBytesPerSecond() const override { return 0; } -}; - -// To test that customed rate limiter not implementing optional APIs (e.g, -// RateLimiter::GetTotalPendingRequests()) works fine with RocksDB basic -// operations (e.g, Put, Get, Flush) -TEST_F(DBTest, CustomedRateLimiterWithNoOptionalAPIImplTest) { - Options options = CurrentOptions(); - options.rate_limiter.reset(new MockedRateLimiterWithNoOptionalAPIImpl()); - DestroyAndReopen(options); - ASSERT_OK(Put("abc", "def")); - ASSERT_EQ(Get("abc"), "def"); - ASSERT_OK(Flush()); - ASSERT_EQ(Get("abc"), "def"); -} - -TEST_F(DBTest, TableOptionsSanitizeTest) { - Options options = CurrentOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - ASSERT_EQ(db_->GetOptions().allow_mmap_reads, false); - - options.table_factory.reset(NewPlainTableFactory()); - options.prefix_extractor.reset(NewNoopTransform()); - Destroy(options); - ASSERT_TRUE(!TryReopen(options).IsNotSupported()); - - // Test for check of prefix_extractor when hash index is used for - // block-based table - BlockBasedTableOptions to; - to.index_type = BlockBasedTableOptions::kHashSearch; - options = CurrentOptions(); - options.create_if_missing = true; - options.table_factory.reset(NewBlockBasedTableFactory(to)); - ASSERT_TRUE(TryReopen(options).IsInvalidArgument()); - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - ASSERT_OK(TryReopen(options)); -} - -TEST_F(DBTest, ConcurrentMemtableNotSupported) { - Options options = CurrentOptions(); - options.allow_concurrent_memtable_write = true; - options.soft_pending_compaction_bytes_limit = 0; - options.hard_pending_compaction_bytes_limit = 100; - options.create_if_missing = true; - - DestroyDB(dbname_, options); - options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true, 4)); - ASSERT_NOK(TryReopen(options)); - - options.memtable_factory.reset(new SkipListFactory); - ASSERT_OK(TryReopen(options)); - - ColumnFamilyOptions cf_options(options); - cf_options.memtable_factory.reset( - NewHashLinkListRepFactory(4, 0, 3, true, 4)); - ColumnFamilyHandle* handle; - ASSERT_NOK(db_->CreateColumnFamily(cf_options, "name", &handle)); -} - - -TEST_F(DBTest, SanitizeNumThreads) { - for (int attempt = 0; attempt < 2; attempt++) { - const size_t kTotalTasks = 8; - test::SleepingBackgroundTask sleeping_tasks[kTotalTasks]; - - Options options = CurrentOptions(); - if (attempt == 0) { - options.max_background_compactions = 3; - options.max_background_flushes = 2; - } - options.create_if_missing = true; - DestroyAndReopen(options); - - for (size_t i = 0; i < kTotalTasks; i++) { - // Insert 5 tasks to low priority queue and 5 tasks to high priority queue - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_tasks[i], - (i < 4) ? Env::Priority::LOW : Env::Priority::HIGH); - } - - // Wait until 10s for they are scheduled. - for (int i = 0; i < 10000; i++) { - if (options.env->GetThreadPoolQueueLen(Env::Priority::LOW) <= 1 && - options.env->GetThreadPoolQueueLen(Env::Priority::HIGH) <= 2) { - break; - } - env_->SleepForMicroseconds(1000); - } - - // pool size 3, total task 4. Queue size should be 1. - ASSERT_EQ(1U, options.env->GetThreadPoolQueueLen(Env::Priority::LOW)); - // pool size 2, total task 4. Queue size should be 2. - ASSERT_EQ(2U, options.env->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - for (size_t i = 0; i < kTotalTasks; i++) { - sleeping_tasks[i].WakeUp(); - sleeping_tasks[i].WaitUntilDone(); - } - - ASSERT_OK(Put("abc", "def")); - ASSERT_EQ("def", Get("abc")); - ASSERT_OK(Flush()); - ASSERT_EQ("def", Get("abc")); - } -} - -TEST_F(DBTest, WriteSingleThreadEntry) { - std::vector threads; - dbfull()->TEST_LockMutex(); - auto w = dbfull()->TEST_BeginWrite(); - threads.emplace_back([&] { ASSERT_OK(Put("a", "b")); }); - env_->SleepForMicroseconds(10000); - threads.emplace_back([&] { ASSERT_OK(Flush()); }); - env_->SleepForMicroseconds(10000); - dbfull()->TEST_UnlockMutex(); - dbfull()->TEST_LockMutex(); - dbfull()->TEST_EndWrite(w); - dbfull()->TEST_UnlockMutex(); - - for (auto& t : threads) { - t.join(); - } -} - -TEST_F(DBTest, ConcurrentFlushWAL) { - const size_t cnt = 100; - Options options; - options.env = env_; - WriteOptions wopt; - ReadOptions ropt; - for (bool two_write_queues : {false, true}) { - for (bool manual_wal_flush : {false, true}) { - options.two_write_queues = two_write_queues; - options.manual_wal_flush = manual_wal_flush; - options.create_if_missing = true; - DestroyAndReopen(options); - std::vector threads; - threads.emplace_back([&] { - for (size_t i = 0; i < cnt; i++) { - auto istr = std::to_string(i); - ASSERT_OK(db_->Put(wopt, db_->DefaultColumnFamily(), "a" + istr, - "b" + istr)); - } - }); - if (two_write_queues) { - threads.emplace_back([&] { - for (size_t i = cnt; i < 2 * cnt; i++) { - auto istr = std::to_string(i); - WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */, - wopt.protection_bytes_per_key, - 0 /* default_cf_ts_sz */); - ASSERT_OK(batch.Put("a" + istr, "b" + istr)); - ASSERT_OK( - dbfull()->WriteImpl(wopt, &batch, nullptr, nullptr, 0, true)); - } - }); - } - threads.emplace_back([&] { - for (size_t i = 0; i < cnt * 100; i++) { // FlushWAL is faster than Put - ASSERT_OK(db_->FlushWAL(false)); - } - }); - for (auto& t : threads) { - t.join(); - } - options.create_if_missing = false; - // Recover from the wal and make sure that it is not corrupted - Reopen(options); - for (size_t i = 0; i < cnt; i++) { - PinnableSlice pval; - auto istr = std::to_string(i); - ASSERT_OK( - db_->Get(ropt, db_->DefaultColumnFamily(), "a" + istr, &pval)); - ASSERT_TRUE(pval == ("b" + istr)); - } - } - } -} - -// This test failure will be caught with a probability -TEST_F(DBTest, ManualFlushWalAndWriteRace) { - Options options; - options.env = env_; - options.manual_wal_flush = true; - options.create_if_missing = true; - - DestroyAndReopen(options); - - WriteOptions wopts; - wopts.sync = true; - - port::Thread writeThread([&]() { - for (int i = 0; i < 100; i++) { - auto istr = std::to_string(i); - ASSERT_OK(dbfull()->Put(wopts, "key_" + istr, "value_" + istr)); - } - }); - port::Thread flushThread([&]() { - for (int i = 0; i < 100; i++) { - ASSERT_OK(dbfull()->FlushWAL(false)); - } - }); - - writeThread.join(); - flushThread.join(); - ASSERT_OK(dbfull()->Put(wopts, "foo1", "value1")); - ASSERT_OK(dbfull()->Put(wopts, "foo2", "value2")); - Reopen(options); - ASSERT_EQ("value1", Get("foo1")); - ASSERT_EQ("value2", Get("foo2")); -} - -TEST_F(DBTest, DynamicMemtableOptions) { - const uint64_t k64KB = 1 << 16; - const uint64_t k128KB = 1 << 17; - const uint64_t k5KB = 5 * 1024; - Options options; - options.env = env_; - options.create_if_missing = true; - options.compression = kNoCompression; - options.max_background_compactions = 1; - options.write_buffer_size = k64KB; - options.arena_block_size = 16 * 1024; - options.max_write_buffer_number = 2; - // Don't trigger compact/slowdown/stop - options.level0_file_num_compaction_trigger = 1024; - options.level0_slowdown_writes_trigger = 1024; - options.level0_stop_writes_trigger = 1024; - DestroyAndReopen(options); - - auto gen_l0_kb = [this](int size) { - const int kNumPutsBeforeWaitForFlush = 64; - Random rnd(301); - for (int i = 0; i < size; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1024))); - - // The following condition prevents a race condition between flush jobs - // acquiring work and this thread filling up multiple memtables. Without - // this, the flush might produce less files than expected because - // multiple memtables are flushed into a single L0 file. This race - // condition affects assertion (A). - if (i % kNumPutsBeforeWaitForFlush == kNumPutsBeforeWaitForFlush - 1) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - }; - - // Test write_buffer_size - gen_l0_kb(64); - ASSERT_EQ(NumTableFilesAtLevel(0), 1); - ASSERT_LT(SizeAtLevel(0), k64KB + k5KB); - ASSERT_GT(SizeAtLevel(0), k64KB - k5KB * 2); - - // Clean up L0 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - - // Increase buffer size - ASSERT_OK(dbfull()->SetOptions({ - {"write_buffer_size", "131072"}, - })); - - // The existing memtable inflated 64KB->128KB when we invoked SetOptions(). - // Write 192KB, we should have a 128KB L0 file and a memtable with 64KB data. - gen_l0_kb(192); - ASSERT_EQ(NumTableFilesAtLevel(0), 1); // (A) - ASSERT_LT(SizeAtLevel(0), k128KB + 2 * k5KB); - ASSERT_GT(SizeAtLevel(0), k128KB - 4 * k5KB); - - // Decrease buffer size below current usage - ASSERT_OK(dbfull()->SetOptions({ - {"write_buffer_size", "65536"}, - })); - // The existing memtable became eligible for flush when we reduced its - // capacity to 64KB. Two keys need to be added to trigger flush: first causes - // memtable to be marked full, second schedules the flush. Then we should have - // a 128KB L0 file, a 64KB L0 file, and a memtable with just one key. - gen_l0_kb(2); - ASSERT_EQ(NumTableFilesAtLevel(0), 2); - ASSERT_LT(SizeAtLevel(0), k128KB + k64KB + 2 * k5KB); - ASSERT_GT(SizeAtLevel(0), k128KB + k64KB - 4 * k5KB); - - // Test max_write_buffer_number - // Block compaction thread, which will also block the flushes because - // max_background_flushes == 0, so flushes are getting executed by the - // compaction thread - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - // Start from scratch and disable compaction/flush. Flush can only happen - // during compaction but trigger is pretty high - options.disable_auto_compactions = true; - DestroyAndReopen(options); - env_->SetBackgroundThreads(0, Env::HIGH); - - // Put until writes are stopped, bounded by 256 puts. We should see stop at - // ~128KB - int count = 0; - Random rnd(301); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Wait", - [&](void* /*arg*/) { sleeping_task_low.WakeUp(); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - while (!sleeping_task_low.WokenUp() && count < 256) { - ASSERT_OK(Put(Key(count), rnd.RandomString(1024), WriteOptions())); - count++; - } - ASSERT_GT(static_cast(count), 128 * 0.8); - ASSERT_LT(static_cast(count), 128 * 1.2); - - sleeping_task_low.WaitUntilDone(); - - // Increase - ASSERT_OK(dbfull()->SetOptions({ - {"max_write_buffer_number", "8"}, - })); - // Clean up memtable and L0 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - sleeping_task_low.Reset(); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - count = 0; - while (!sleeping_task_low.WokenUp() && count < 1024) { - ASSERT_OK(Put(Key(count), rnd.RandomString(1024), WriteOptions())); - count++; - } -// Windows fails this test. Will tune in the future and figure out -// approp number -#ifndef OS_WIN - ASSERT_GT(static_cast(count), 512 * 0.8); - ASSERT_LT(static_cast(count), 512 * 1.2); -#endif - sleeping_task_low.WaitUntilDone(); - - // Decrease - ASSERT_OK(dbfull()->SetOptions({ - {"max_write_buffer_number", "4"}, - })); - // Clean up memtable and L0 - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - sleeping_task_low.Reset(); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - count = 0; - while (!sleeping_task_low.WokenUp() && count < 1024) { - ASSERT_OK(Put(Key(count), rnd.RandomString(1024), WriteOptions())); - count++; - } -// Windows fails this test. Will tune in the future and figure out -// approp number -#ifndef OS_WIN - ASSERT_GT(static_cast(count), 256 * 0.8); - ASSERT_LT(static_cast(count), 266 * 1.2); -#endif - sleeping_task_low.WaitUntilDone(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -#ifdef ROCKSDB_USING_THREAD_STATUS -namespace { -void VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type, - int expected_count) { - int op_count = 0; - std::vector thread_list; - ASSERT_OK(env->GetThreadList(&thread_list)); - for (auto thread : thread_list) { - if (thread.operation_type == op_type) { - op_count++; - } - } - ASSERT_EQ(op_count, expected_count); -} -} // anonymous namespace - -TEST_F(DBTest, GetThreadStatus) { - Options options; - options.env = env_; - options.enable_thread_tracking = true; - TryReopen(options); - - std::vector thread_list; - Status s = env_->GetThreadList(&thread_list); - - for (int i = 0; i < 2; ++i) { - // repeat the test with differet number of high / low priority threads - const int kTestCount = 3; - const unsigned int kHighPriCounts[kTestCount] = {3, 2, 5}; - const unsigned int kLowPriCounts[kTestCount] = {10, 15, 3}; - const unsigned int kBottomPriCounts[kTestCount] = {2, 1, 4}; - for (int test = 0; test < kTestCount; ++test) { - // Change the number of threads in high / low priority pool. - env_->SetBackgroundThreads(kHighPriCounts[test], Env::HIGH); - env_->SetBackgroundThreads(kLowPriCounts[test], Env::LOW); - env_->SetBackgroundThreads(kBottomPriCounts[test], Env::BOTTOM); - // Wait to ensure the all threads has been registered - unsigned int thread_type_counts[ThreadStatus::NUM_THREAD_TYPES]; - // TODO(ajkr): it'd be better if SetBackgroundThreads returned only after - // all threads have been registered. - // Try up to 60 seconds. - for (int num_try = 0; num_try < 60000; num_try++) { - env_->SleepForMicroseconds(1000); - thread_list.clear(); - s = env_->GetThreadList(&thread_list); - ASSERT_OK(s); - memset(thread_type_counts, 0, sizeof(thread_type_counts)); - for (auto thread : thread_list) { - ASSERT_LT(thread.thread_type, ThreadStatus::NUM_THREAD_TYPES); - thread_type_counts[thread.thread_type]++; - } - if (thread_type_counts[ThreadStatus::HIGH_PRIORITY] == - kHighPriCounts[test] && - thread_type_counts[ThreadStatus::LOW_PRIORITY] == - kLowPriCounts[test] && - thread_type_counts[ThreadStatus::BOTTOM_PRIORITY] == - kBottomPriCounts[test]) { - break; - } - } - // Verify the number of high-priority threads - ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY], - kHighPriCounts[test]); - // Verify the number of low-priority threads - ASSERT_EQ(thread_type_counts[ThreadStatus::LOW_PRIORITY], - kLowPriCounts[test]); - // Verify the number of bottom-priority threads - ASSERT_EQ(thread_type_counts[ThreadStatus::BOTTOM_PRIORITY], - kBottomPriCounts[test]); - } - if (i == 0) { - // repeat the test with multiple column families - CreateAndReopenWithCF({"pikachu", "about-to-remove"}, options); - env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(handles_, - true); - } - } - ASSERT_OK(db_->DropColumnFamily(handles_[2])); - delete handles_[2]; - handles_.erase(handles_.begin() + 2); - env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(handles_, - true); - Close(); - env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(handles_, - true); -} - -TEST_F(DBTest, DisableThreadStatus) { - Options options; - options.env = env_; - options.enable_thread_tracking = false; - TryReopen(options); - CreateAndReopenWithCF({"pikachu", "about-to-remove"}, options); - // Verify non of the column family info exists - env_->GetThreadStatusUpdater()->TEST_VerifyColumnFamilyInfoMap(handles_, - false); -} - -TEST_F(DBTest, ThreadStatusFlush) { - Options options; - options.env = env_; - options.write_buffer_size = 100000; // Small write buffer - options.enable_thread_tracking = true; - options = CurrentOptions(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"FlushJob::FlushJob()", "DBTest::ThreadStatusFlush:1"}, - {"DBTest::ThreadStatusFlush:2", "FlushJob::WriteLevel0Table"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu"}, options); - VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0); - - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_EQ("v1", Get(1, "foo")); - VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 0); - - uint64_t num_running_flushes = 0; - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningFlushes, - &num_running_flushes)); - ASSERT_EQ(num_running_flushes, 0); - - ASSERT_OK(Put(1, "k1", std::string(100000, 'x'))); // Fill memtable - ASSERT_OK(Put(1, "k2", std::string(100000, 'y'))); // Trigger flush - - // The first sync point is to make sure there's one flush job - // running when we perform VerifyOperationCount(). - TEST_SYNC_POINT("DBTest::ThreadStatusFlush:1"); - VerifyOperationCount(env_, ThreadStatus::OP_FLUSH, 1); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningFlushes, - &num_running_flushes)); - ASSERT_EQ(num_running_flushes, 1); - // This second sync point is to ensure the flush job will not - // be completed until we already perform VerifyOperationCount(). - TEST_SYNC_POINT("DBTest::ThreadStatusFlush:2"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBTestWithParam, ThreadStatusSingleCompaction) { - const int kTestKeySize = 16; - const int kTestValueSize = 984; - const int kEntrySize = kTestKeySize + kTestValueSize; - const int kEntriesPerBuffer = 100; - Options options; - options.create_if_missing = true; - options.write_buffer_size = kEntrySize * kEntriesPerBuffer; - options.compaction_style = kCompactionStyleLevel; - options.target_file_size_base = options.write_buffer_size; - options.max_bytes_for_level_base = options.target_file_size_base * 2; - options.max_bytes_for_level_multiplier = 2; - options.compression = kNoCompression; - options = CurrentOptions(options); - options.env = env_; - options.enable_thread_tracking = true; - const int kNumL0Files = 4; - options.level0_file_num_compaction_trigger = kNumL0Files; - options.max_subcompactions = max_subcompactions_; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DBTest::ThreadStatusSingleCompaction:0", "DBImpl::BGWorkCompaction"}, - {"CompactionJob::Run():Start", "DBTest::ThreadStatusSingleCompaction:1"}, - {"DBTest::ThreadStatusSingleCompaction:2", "CompactionJob::Run():End"}, - }); - for (int tests = 0; tests < 2; ++tests) { - DestroyAndReopen(options); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - // The Put Phase. - for (int file = 0; file < kNumL0Files; ++file) { - for (int key = 0; key < kEntriesPerBuffer; ++key) { - ASSERT_OK(Put(std::to_string(key + file * kEntriesPerBuffer), - rnd.RandomString(kTestValueSize))); - } - ASSERT_OK(Flush()); - } - // This makes sure a compaction won't be scheduled until - // we have done with the above Put Phase. - uint64_t num_running_compactions = 0; - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningCompactions, - &num_running_compactions)); - ASSERT_EQ(num_running_compactions, 0); - TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:0"); - ASSERT_GE(NumTableFilesAtLevel(0), - options.level0_file_num_compaction_trigger); - - // This makes sure at least one compaction is running. - TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:1"); - - if (options.enable_thread_tracking) { - // expecting one single L0 to L1 compaction - VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 1); - } else { - // If thread tracking is not enabled, compaction count should be 0. - VerifyOperationCount(env_, ThreadStatus::OP_COMPACTION, 0); - } - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumRunningCompactions, - &num_running_compactions)); - ASSERT_EQ(num_running_compactions, 1); - // TODO(yhchiang): adding assert to verify each compaction stage. - TEST_SYNC_POINT("DBTest::ThreadStatusSingleCompaction:2"); - - // repeat the test with disabling thread tracking. - options.enable_thread_tracking = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_P(DBTestWithParam, PreShutdownManualCompaction) { - Options options = CurrentOptions(); - options.max_subcompactions = max_subcompactions_; - CreateAndReopenWithCF({"pikachu"}, options); - - // iter - 0 with 7 levels - // iter - 1 with 3 levels - for (int iter = 0; iter < 2; ++iter) { - MakeTables(3, "p", "q", 1); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - - // Compaction range falls before files - Compact(1, "", "c"); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - - // Compaction range falls after files - Compact(1, "r", "z"); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - - // Compaction range overlaps files - Compact(1, "p", "q"); - ASSERT_EQ("0,0,1", FilesPerLevel(1)); - - // Populate a different range - MakeTables(3, "c", "e", 1); - ASSERT_EQ("1,1,2", FilesPerLevel(1)); - - // Compact just the new range - Compact(1, "b", "f"); - ASSERT_EQ("0,0,2", FilesPerLevel(1)); - - // Compact all - MakeTables(1, "a", "z", 1); - ASSERT_EQ("1,0,2", FilesPerLevel(1)); - CancelAllBackgroundWork(db_); - ASSERT_TRUE( - db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr) - .IsShutdownInProgress()); - ASSERT_EQ("1,0,2", FilesPerLevel(1)); - - if (iter == 0) { - options = CurrentOptions(); - options.num_levels = 3; - options.create_if_missing = true; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - } - } -} - -TEST_F(DBTest, PreShutdownFlush) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(1, "key", "value")); - CancelAllBackgroundWork(db_); - Status s = - db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr); - ASSERT_TRUE(s.IsShutdownInProgress()); -} - -TEST_P(DBTestWithParam, PreShutdownMultipleCompaction) { - const int kTestKeySize = 16; - const int kTestValueSize = 984; - const int kEntrySize = kTestKeySize + kTestValueSize; - const int kEntriesPerBuffer = 40; - const int kNumL0Files = 4; - - const int kHighPriCount = 3; - const int kLowPriCount = 5; - env_->SetBackgroundThreads(kHighPriCount, Env::HIGH); - env_->SetBackgroundThreads(kLowPriCount, Env::LOW); - - Options options; - options.create_if_missing = true; - options.write_buffer_size = kEntrySize * kEntriesPerBuffer; - options.compaction_style = kCompactionStyleLevel; - options.target_file_size_base = options.write_buffer_size; - options.max_bytes_for_level_base = - options.target_file_size_base * kNumL0Files; - options.compression = kNoCompression; - options = CurrentOptions(options); - options.env = env_; - options.enable_thread_tracking = true; - options.level0_file_num_compaction_trigger = kNumL0Files; - options.max_bytes_for_level_multiplier = 2; - options.max_background_compactions = kLowPriCount; - options.level0_stop_writes_trigger = 1 << 10; - options.level0_slowdown_writes_trigger = 1 << 10; - options.max_subcompactions = max_subcompactions_; - - TryReopen(options); - Random rnd(301); - - std::vector thread_list; - // Delay both flush and compaction - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"FlushJob::FlushJob()", "CompactionJob::Run():Start"}, - {"CompactionJob::Run():Start", - "DBTest::PreShutdownMultipleCompaction:Preshutdown"}, - {"CompactionJob::Run():Start", - "DBTest::PreShutdownMultipleCompaction:VerifyCompaction"}, - {"DBTest::PreShutdownMultipleCompaction:Preshutdown", - "CompactionJob::Run():End"}, - {"CompactionJob::Run():End", - "DBTest::PreShutdownMultipleCompaction:VerifyPreshutdown"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Make rocksdb busy - int key = 0; - // check how many threads are doing compaction using GetThreadList - int operation_count[ThreadStatus::NUM_OP_TYPES] = {0}; - for (int file = 0; file < 16 * kNumL0Files; ++file) { - for (int k = 0; k < kEntriesPerBuffer; ++k) { - ASSERT_OK(Put(std::to_string(key++), rnd.RandomString(kTestValueSize))); - } - - ASSERT_OK(env_->GetThreadList(&thread_list)); - for (auto thread : thread_list) { - operation_count[thread.operation_type]++; - } - - // Speed up the test - if (operation_count[ThreadStatus::OP_FLUSH] > 1 && - operation_count[ThreadStatus::OP_COMPACTION] > - 0.6 * options.max_background_compactions) { - break; - } - if (file == 15 * kNumL0Files) { - TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:Preshutdown"); - } - } - - TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:Preshutdown"); - ASSERT_GE(operation_count[ThreadStatus::OP_COMPACTION], 1); - CancelAllBackgroundWork(db_); - TEST_SYNC_POINT("DBTest::PreShutdownMultipleCompaction:VerifyPreshutdown"); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Record the number of compactions at a time. - for (int i = 0; i < ThreadStatus::NUM_OP_TYPES; ++i) { - operation_count[i] = 0; - } - ASSERT_OK(env_->GetThreadList(&thread_list)); - for (auto thread : thread_list) { - operation_count[thread.operation_type]++; - } - ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0); -} - -TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) { - const int kTestKeySize = 16; - const int kTestValueSize = 984; - const int kEntrySize = kTestKeySize + kTestValueSize; - const int kEntriesPerBuffer = 40; - const int kNumL0Files = 4; - - const int kHighPriCount = 3; - const int kLowPriCount = 5; - env_->SetBackgroundThreads(kHighPriCount, Env::HIGH); - env_->SetBackgroundThreads(kLowPriCount, Env::LOW); - - Options options; - options.create_if_missing = true; - options.write_buffer_size = kEntrySize * kEntriesPerBuffer; - options.compaction_style = kCompactionStyleLevel; - options.target_file_size_base = options.write_buffer_size; - options.max_bytes_for_level_base = - options.target_file_size_base * kNumL0Files; - options.compression = kNoCompression; - options = CurrentOptions(options); - options.env = env_; - options.enable_thread_tracking = true; - options.level0_file_num_compaction_trigger = kNumL0Files; - options.max_bytes_for_level_multiplier = 2; - options.max_background_compactions = kLowPriCount; - options.level0_stop_writes_trigger = 1 << 10; - options.level0_slowdown_writes_trigger = 1 << 10; - options.max_subcompactions = max_subcompactions_; - - TryReopen(options); - Random rnd(301); - - std::vector thread_list; - // Delay both flush and compaction - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBTest::PreShutdownCompactionMiddle:Preshutdown", - "CompactionJob::Run():Inprogress"}, - {"CompactionJob::Run():Start", - "DBTest::PreShutdownCompactionMiddle:VerifyCompaction"}, - {"CompactionJob::Run():Inprogress", "CompactionJob::Run():End"}, - {"CompactionJob::Run():End", - "DBTest::PreShutdownCompactionMiddle:VerifyPreshutdown"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Make rocksdb busy - int key = 0; - // check how many threads are doing compaction using GetThreadList - int operation_count[ThreadStatus::NUM_OP_TYPES] = {0}; - for (int file = 0; file < 16 * kNumL0Files; ++file) { - for (int k = 0; k < kEntriesPerBuffer; ++k) { - ASSERT_OK(Put(std::to_string(key++), rnd.RandomString(kTestValueSize))); - } - - ASSERT_OK(env_->GetThreadList(&thread_list)); - for (auto thread : thread_list) { - operation_count[thread.operation_type]++; - } - - // Speed up the test - if (operation_count[ThreadStatus::OP_FLUSH] > 1 && - operation_count[ThreadStatus::OP_COMPACTION] > - 0.6 * options.max_background_compactions) { - break; - } - if (file == 15 * kNumL0Files) { - TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:VerifyCompaction"); - } - } - - ASSERT_GE(operation_count[ThreadStatus::OP_COMPACTION], 1); - CancelAllBackgroundWork(db_); - TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:Preshutdown"); - TEST_SYNC_POINT("DBTest::PreShutdownCompactionMiddle:VerifyPreshutdown"); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Record the number of compactions at a time. - for (int i = 0; i < ThreadStatus::NUM_OP_TYPES; ++i) { - operation_count[i] = 0; - } - ASSERT_OK(env_->GetThreadList(&thread_list)); - for (auto thread : thread_list) { - operation_count[thread.operation_type]++; - } - ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0); -} - -#endif // ROCKSDB_USING_THREAD_STATUS - -TEST_F(DBTest, FlushOnDestroy) { - WriteOptions wo; - wo.disableWAL = true; - ASSERT_OK(Put("foo", "v1", wo)); - CancelAllBackgroundWork(db_); -} - -TEST_F(DBTest, DynamicLevelCompressionPerLevel) { - if (!Snappy_Supported()) { - return; - } - const int kNKeys = 120; - int keys[kNKeys]; - for (int i = 0; i < kNKeys; i++) { - keys[i] = i; - } - RandomShuffle(std::begin(keys), std::end(keys)); - - Random rnd(301); - Options options; - options.env = env_; - options.create_if_missing = true; - options.db_write_buffer_size = 20480; - options.write_buffer_size = 20480; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 2; - options.target_file_size_base = 20480; - options.level_compaction_dynamic_level_bytes = true; - options.max_bytes_for_level_base = 102400; - options.max_bytes_for_level_multiplier = 4; - options.max_background_compactions = 1; - options.num_levels = 5; - - options.compression_per_level.resize(3); - options.compression_per_level[0] = kNoCompression; - options.compression_per_level[1] = kNoCompression; - options.compression_per_level[2] = kSnappyCompression; - - OnFileDeletionListener* listener = new OnFileDeletionListener(); - options.listeners.emplace_back(listener); - - DestroyAndReopen(options); - - // Insert more than 80K. L4 should be base level. Neither L0 nor L4 should - // be compressed, so total data size should be more than 80K. - for (int i = 0; i < 20; i++) { - ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2), 0); - ASSERT_EQ(NumTableFilesAtLevel(3), 0); - // Assuming each files' metadata is at least 50 bytes/ - ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(4), 20U * 4000U + 50U * 4); - - // Insert 400KB. Some data will be compressed - for (int i = 21; i < 120; i++) { - ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000))); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2), 0); - - ASSERT_LT(SizeAtLevel(0) + SizeAtLevel(3) + SizeAtLevel(4), - 120U * 4000U + 50U * 24); - // Make sure data in files in L3 is not compacted by removing all files - // in L4 and calculate number of rows - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "true"}, - })); - ColumnFamilyMetaData cf_meta; - db_->GetColumnFamilyMetaData(&cf_meta); - for (auto file : cf_meta.levels[4].files) { - listener->SetExpectedFileName(dbname_ + file.name); - ASSERT_OK(dbfull()->DeleteFile(file.name)); - } - listener->VerifyMatchedCount(cf_meta.levels[4].files.size()); - - int num_keys = 0; - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - num_keys++; - } - ASSERT_OK(iter->status()); - ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U + num_keys * 10U); -} - -TEST_F(DBTest, DynamicLevelCompressionPerLevel2) { - if (!Snappy_Supported() || !LZ4_Supported() || !Zlib_Supported()) { - return; - } - const int kNKeys = 500; - int keys[kNKeys]; - for (int i = 0; i < kNKeys; i++) { - keys[i] = i; - } - RandomShuffle(std::begin(keys), std::end(keys)); - - Random rnd(301); - Options options; - options.create_if_missing = true; - options.db_write_buffer_size = 6000000; - options.write_buffer_size = 600000; - options.max_write_buffer_number = 2; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 2; - options.soft_pending_compaction_bytes_limit = 1024 * 1024; - options.target_file_size_base = 20; - options.env = env_; - options.level_compaction_dynamic_level_bytes = true; - options.max_bytes_for_level_base = 200; - options.max_bytes_for_level_multiplier = 8; - options.max_background_compactions = 1; - options.num_levels = 5; - std::shared_ptr mtf(new mock::MockTableFactory); - options.table_factory = mtf; - - options.compression_per_level.resize(3); - options.compression_per_level[0] = kNoCompression; - options.compression_per_level[1] = kLZ4Compression; - options.compression_per_level[2] = kZlibCompression; - - DestroyAndReopen(options); - // When base level is L4, L4 is LZ4. - std::atomic num_zlib(0); - std::atomic num_lz4(0); - std::atomic num_no(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - if (compaction->output_level() == 4) { - ASSERT_TRUE(compaction->output_compression() == kLZ4Compression); - num_lz4.fetch_add(1); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FlushJob::WriteLevel0Table:output_compression", [&](void* arg) { - auto* compression = reinterpret_cast(arg); - ASSERT_TRUE(*compression == kNoCompression); - num_no.fetch_add(1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - for (int i = 0; i < 100; i++) { - std::string value = rnd.RandomString(200); - ASSERT_OK(Put(Key(keys[i]), value)); - if (i % 25 == 24) { - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - } - - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2), 0); - ASSERT_EQ(NumTableFilesAtLevel(3), 0); - ASSERT_GT(NumTableFilesAtLevel(4), 0); - ASSERT_GT(num_no.load(), 2); - ASSERT_GT(num_lz4.load(), 0); - int prev_num_files_l4 = NumTableFilesAtLevel(4); - - // After base level turn L4->L3, L3 becomes LZ4 and L4 becomes Zlib - num_lz4.store(0); - num_no.store(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - if (compaction->output_level() == 4 && compaction->start_level() == 3) { - ASSERT_TRUE(compaction->output_compression() == kZlibCompression); - num_zlib.fetch_add(1); - } else { - ASSERT_TRUE(compaction->output_compression() == kLZ4Compression); - num_lz4.fetch_add(1); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FlushJob::WriteLevel0Table:output_compression", [&](void* arg) { - auto* compression = reinterpret_cast(arg); - ASSERT_TRUE(*compression == kNoCompression); - num_no.fetch_add(1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - for (int i = 101; i < 500; i++) { - std::string value = rnd.RandomString(200); - ASSERT_OK(Put(Key(keys[i]), value)); - if (i % 100 == 99) { - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - ASSERT_EQ(NumTableFilesAtLevel(2), 0); - ASSERT_GT(NumTableFilesAtLevel(3), 0); - ASSERT_GT(NumTableFilesAtLevel(4), prev_num_files_l4); - ASSERT_GT(num_no.load(), 2); - ASSERT_GT(num_lz4.load(), 0); - ASSERT_GT(num_zlib.load(), 0); -} - -TEST_F(DBTest, DynamicCompactionOptions) { - // minimum write buffer size is enforced at 64KB - const uint64_t k32KB = 1 << 15; - const uint64_t k64KB = 1 << 16; - const uint64_t k128KB = 1 << 17; - const uint64_t k1MB = 1 << 20; - const uint64_t k4KB = 1 << 12; - Options options; - options.env = env_; - options.create_if_missing = true; - options.compression = kNoCompression; - options.soft_pending_compaction_bytes_limit = 1024 * 1024; - options.write_buffer_size = k64KB; - options.arena_block_size = 4 * k4KB; - options.max_write_buffer_number = 2; - // Compaction related options - options.level0_file_num_compaction_trigger = 3; - options.level0_slowdown_writes_trigger = 4; - options.level0_stop_writes_trigger = 8; - options.target_file_size_base = k64KB; - options.max_compaction_bytes = options.target_file_size_base * 10; - options.target_file_size_multiplier = 1; - options.max_bytes_for_level_base = k128KB; - options.max_bytes_for_level_multiplier = 4; - - // Block flush thread and disable compaction thread - env_->SetBackgroundThreads(1, Env::LOW); - env_->SetBackgroundThreads(1, Env::HIGH); - DestroyAndReopen(options); - - auto gen_l0_kb = [this](int start, int size, int stride) { - Random rnd(301); - for (int i = 0; i < size; i++) { - ASSERT_OK(Put(Key(start + stride * i), rnd.RandomString(1024))); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - }; - - // Write 3 files that have the same key range. - // Since level0_file_num_compaction_trigger is 3, compaction should be - // triggered. The compaction should result in one L1 file - gen_l0_kb(0, 64, 1); - ASSERT_EQ(NumTableFilesAtLevel(0), 1); - gen_l0_kb(0, 64, 1); - ASSERT_EQ(NumTableFilesAtLevel(0), 2); - gen_l0_kb(0, 64, 1); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,1", FilesPerLevel()); - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(1U, metadata.size()); - ASSERT_LE(metadata[0].size, k64KB + k4KB); - ASSERT_GE(metadata[0].size, k64KB - k4KB); - - // Test compaction trigger and target_file_size_base - // Reduce compaction trigger to 2, and reduce L1 file size to 32KB. - // Writing to 64KB L0 files should trigger a compaction. Since these - // 2 L0 files have the same key range, compaction merge them and should - // result in 2 32KB L1 files. - ASSERT_OK( - dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "2"}, - {"target_file_size_base", std::to_string(k32KB)}})); - - gen_l0_kb(0, 64, 1); - ASSERT_EQ("1,1", FilesPerLevel()); - gen_l0_kb(0, 64, 1); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ("0,2", FilesPerLevel()); - metadata.clear(); - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(2U, metadata.size()); - ASSERT_LE(metadata[0].size, k32KB + k4KB); - ASSERT_GE(metadata[0].size, k32KB - k4KB); - ASSERT_LE(metadata[1].size, k32KB + k4KB); - ASSERT_GE(metadata[1].size, k32KB - k4KB); - - // Test max_bytes_for_level_base - // Increase level base size to 256KB and write enough data that will - // fill L1 and L2. L1 size should be around 256KB while L2 size should be - // around 256KB x 4. - ASSERT_OK(dbfull()->SetOptions( - {{"max_bytes_for_level_base", std::to_string(k1MB)}})); - - // writing 96 x 64KB => 6 * 1024KB - // (L1 + L2) = (1 + 4) * 1024KB - for (int i = 0; i < 96; ++i) { - gen_l0_kb(i, 64, 96); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_GT(SizeAtLevel(1), k1MB / 2); - ASSERT_LT(SizeAtLevel(1), k1MB + k1MB / 2); - - // Within (0.5, 1.5) of 4MB. - ASSERT_GT(SizeAtLevel(2), 2 * k1MB); - ASSERT_LT(SizeAtLevel(2), 6 * k1MB); - - // Test max_bytes_for_level_multiplier and - // max_bytes_for_level_base. Now, reduce both mulitplier and level base, - // After filling enough data that can fit in L1 - L3, we should see L1 size - // reduces to 128KB from 256KB which was asserted previously. Same for L2. - ASSERT_OK(dbfull()->SetOptions( - {{"max_bytes_for_level_multiplier", "2"}, - {"max_bytes_for_level_base", std::to_string(k128KB)}})); - - // writing 20 x 64KB = 10 x 128KB - // (L1 + L2 + L3) = (1 + 2 + 4) * 128KB - for (int i = 0; i < 20; ++i) { - gen_l0_kb(i, 64, 32); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - uint64_t total_size = SizeAtLevel(1) + SizeAtLevel(2) + SizeAtLevel(3); - ASSERT_TRUE(total_size < k128KB * 7 * 1.5); - - // Test level0_stop_writes_trigger. - // Clean up memtable and L0. Block compaction threads. If continue to write - // and flush memtables. We should see put stop after 8 memtable flushes - // since level0_stop_writes_trigger = 8 - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // Block compaction - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - sleeping_task_low.WaitUntilSleeping(); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - int count = 0; - Random rnd(301); - WriteOptions wo; - while (count < 64) { - ASSERT_OK(Put(Key(count), rnd.RandomString(1024), wo)); - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - count++; - if (dbfull()->TEST_write_controler().IsStopped()) { - sleeping_task_low.WakeUp(); - break; - } - } - // Stop trigger = 8 - ASSERT_EQ(count, 8); - // Unblock - sleeping_task_low.WaitUntilDone(); - - // Now reduce level0_stop_writes_trigger to 6. Clear up memtables and L0. - // Block compaction thread again. Perform the put and memtable flushes - // until we see the stop after 6 memtable flushes. - ASSERT_OK(dbfull()->SetOptions({{"level0_stop_writes_trigger", "6"}})); - ASSERT_OK(dbfull()->TEST_FlushMemTable(true)); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - - // Block compaction again - sleeping_task_low.Reset(); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - sleeping_task_low.WaitUntilSleeping(); - count = 0; - while (count < 64) { - ASSERT_OK(Put(Key(count), rnd.RandomString(1024), wo)); - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - count++; - if (dbfull()->TEST_write_controler().IsStopped()) { - sleeping_task_low.WakeUp(); - break; - } - } - ASSERT_EQ(count, 6); - // Unblock - sleeping_task_low.WaitUntilDone(); - - // Test disable_auto_compactions - // Compaction thread is unblocked but auto compaction is disabled. Write - // 4 L0 files and compaction should be triggered. If auto compaction is - // disabled, then TEST_WaitForCompact will be waiting for nothing. Number of - // L0 files do not change after the call. - ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "true"}})); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - - for (int i = 0; i < 4; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1024))); - // Wait for compaction so that put won't stop - ASSERT_OK(dbfull()->TEST_FlushMemTable(true)); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumTableFilesAtLevel(0), 4); - - // Enable auto compaction and perform the same test, # of L0 files should be - // reduced after compaction. - ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}})); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - - for (int i = 0; i < 4; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1024))); - // Wait for compaction so that put won't stop - ASSERT_OK(dbfull()->TEST_FlushMemTable(true)); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_LT(NumTableFilesAtLevel(0), 4); -} - -// Test dynamic FIFO compaction options. -// This test covers just option parsing and makes sure that the options are -// correctly assigned. Also look at DBOptionsTest.SetFIFOCompactionOptions -// test which makes sure that the FIFO compaction funcionality is working -// as expected on dynamically changing the options. -// Even more FIFOCompactionTests are at DBTest.FIFOCompaction* . -TEST_F(DBTest, DynamicFIFOCompactionOptions) { - Options options; - options.ttl = 0; - options.create_if_missing = true; - options.env = env_; - DestroyAndReopen(options); - - // Initial defaults - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 1024 * 1024 * 1024); - ASSERT_EQ(dbfull()->GetOptions().ttl, 0); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - false); - - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", "{max_table_files_size=23;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 23); - ASSERT_EQ(dbfull()->GetOptions().ttl, 0); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - false); - - ASSERT_OK(dbfull()->SetOptions({{"ttl", "97"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 23); - ASSERT_EQ(dbfull()->GetOptions().ttl, 97); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - false); - - ASSERT_OK(dbfull()->SetOptions({{"ttl", "203"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 23); - ASSERT_EQ(dbfull()->GetOptions().ttl, 203); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - false); - - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", "{allow_compaction=true;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 23); - ASSERT_EQ(dbfull()->GetOptions().ttl, 203); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - true); - - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", "{max_table_files_size=31;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 31); - ASSERT_EQ(dbfull()->GetOptions().ttl, 203); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - true); - - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_fifo", - "{max_table_files_size=51;allow_compaction=true;}"}})); - ASSERT_OK(dbfull()->SetOptions({{"ttl", "49"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, - 51); - ASSERT_EQ(dbfull()->GetOptions().ttl, 49); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, - true); -} - -TEST_F(DBTest, DynamicUniversalCompactionOptions) { - Options options; - options.create_if_missing = true; - options.env = env_; - DestroyAndReopen(options); - - // Initial defaults - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 1U); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width, - 2u); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width, - UINT_MAX); - ASSERT_EQ(dbfull() - ->GetOptions() - .compaction_options_universal.max_size_amplification_percent, - 200u); - ASSERT_EQ(dbfull() - ->GetOptions() - .compaction_options_universal.compression_size_percent, - -1); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style, - kCompactionStopStyleTotalSize); - ASSERT_EQ( - dbfull()->GetOptions().compaction_options_universal.allow_trivial_move, - false); - - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_universal", "{size_ratio=7;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 7u); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width, - 2u); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width, - UINT_MAX); - ASSERT_EQ(dbfull() - ->GetOptions() - .compaction_options_universal.max_size_amplification_percent, - 200u); - ASSERT_EQ(dbfull() - ->GetOptions() - .compaction_options_universal.compression_size_percent, - -1); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style, - kCompactionStopStyleTotalSize); - ASSERT_EQ( - dbfull()->GetOptions().compaction_options_universal.allow_trivial_move, - false); - - ASSERT_OK(dbfull()->SetOptions( - {{"compaction_options_universal", "{min_merge_width=11;}"}})); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 7u); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width, - 11u); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width, - UINT_MAX); - ASSERT_EQ(dbfull() - ->GetOptions() - .compaction_options_universal.max_size_amplification_percent, - 200u); - ASSERT_EQ(dbfull() - ->GetOptions() - .compaction_options_universal.compression_size_percent, - -1); - ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style, - kCompactionStopStyleTotalSize); - ASSERT_EQ( - dbfull()->GetOptions().compaction_options_universal.allow_trivial_move, - false); -} - -TEST_F(DBTest, FileCreationRandomFailure) { - Options options; - options.env = env_; - options.create_if_missing = true; - options.write_buffer_size = 100000; // Small write buffer - options.target_file_size_base = 200000; - options.max_bytes_for_level_base = 1000000; - options.max_bytes_for_level_multiplier = 2; - - DestroyAndReopen(options); - Random rnd(301); - - constexpr int kCDTKeysPerBuffer = 4; - constexpr int kTestSize = kCDTKeysPerBuffer * 4096; - constexpr int kTotalIteration = 20; - // the second half of the test involves in random failure - // of file creation. - constexpr int kRandomFailureTest = kTotalIteration / 2; - - std::vector values; - for (int i = 0; i < kTestSize; ++i) { - values.push_back("NOT_FOUND"); - } - for (int j = 0; j < kTotalIteration; ++j) { - if (j == kRandomFailureTest) { - env_->non_writeable_rate_.store(90); - } - for (int k = 0; k < kTestSize; ++k) { - // here we expect some of the Put fails. - std::string value = rnd.RandomString(100); - Status s = Put(Key(k), Slice(value)); - if (s.ok()) { - // update the latest successful put - values[k] = value; - } - // But everything before we simulate the failure-test should succeed. - if (j < kRandomFailureTest) { - ASSERT_OK(s); - } - } - } - - // If rocksdb does not do the correct job, internal assert will fail here. - ASSERT_TRUE(dbfull()->TEST_WaitForFlushMemTable().IsIOError()); - ASSERT_TRUE(dbfull()->TEST_WaitForCompact().IsIOError()); - - // verify we have the latest successful update - for (int k = 0; k < kTestSize; ++k) { - auto v = Get(Key(k)); - ASSERT_EQ(v, values[k]); - } - - // reopen and reverify we have the latest successful update - env_->non_writeable_rate_.store(0); - Reopen(options); - for (int k = 0; k < kTestSize; ++k) { - auto v = Get(Key(k)); - ASSERT_EQ(v, values[k]); - } -} - - -TEST_F(DBTest, DynamicMiscOptions) { - // Test max_sequential_skip_in_iterations - Options options; - options.env = env_; - options.create_if_missing = true; - options.max_sequential_skip_in_iterations = 16; - options.compression = kNoCompression; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - - auto assert_reseek_count = [this, &options](int key_start, int num_reseek) { - int key0 = key_start; - int key1 = key_start + 1; - int key2 = key_start + 2; - Random rnd(301); - ASSERT_OK(Put(Key(key0), rnd.RandomString(8))); - for (int i = 0; i < 10; ++i) { - ASSERT_OK(Put(Key(key1), rnd.RandomString(8))); - } - ASSERT_OK(Put(Key(key2), rnd.RandomString(8))); - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - iter->Seek(Key(key1)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Key(key1)), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(Key(key2)), 0); - ASSERT_EQ(num_reseek, - TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION)); - }; - // No reseek - assert_reseek_count(100, 0); - - ASSERT_OK(dbfull()->SetOptions({{"max_sequential_skip_in_iterations", "4"}})); - // Clear memtable and make new option effective - ASSERT_OK(dbfull()->TEST_FlushMemTable(true)); - // Trigger reseek - assert_reseek_count(200, 1); - - ASSERT_OK( - dbfull()->SetOptions({{"max_sequential_skip_in_iterations", "16"}})); - // Clear memtable and make new option effective - ASSERT_OK(dbfull()->TEST_FlushMemTable(true)); - // No reseek - assert_reseek_count(300, 1); - - MutableCFOptions mutable_cf_options; - CreateAndReopenWithCF({"pikachu"}, options); - // Test soft_pending_compaction_bytes_limit, - // hard_pending_compaction_bytes_limit - ASSERT_OK(dbfull()->SetOptions( - handles_[1], {{"soft_pending_compaction_bytes_limit", "200"}, - {"hard_pending_compaction_bytes_limit", "300"}})); - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], - &mutable_cf_options)); - ASSERT_EQ(200, mutable_cf_options.soft_pending_compaction_bytes_limit); - ASSERT_EQ(300, mutable_cf_options.hard_pending_compaction_bytes_limit); - // Test report_bg_io_stats - ASSERT_OK( - dbfull()->SetOptions(handles_[1], {{"report_bg_io_stats", "true"}})); - // sanity check - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], - &mutable_cf_options)); - ASSERT_TRUE(mutable_cf_options.report_bg_io_stats); - // Test compression - // sanity check - ASSERT_OK(dbfull()->SetOptions({{"compression", "kNoCompression"}})); - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[0], - &mutable_cf_options)); - ASSERT_EQ(CompressionType::kNoCompression, mutable_cf_options.compression); - - if (Snappy_Supported()) { - ASSERT_OK(dbfull()->SetOptions({{"compression", "kSnappyCompression"}})); - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[0], - &mutable_cf_options)); - ASSERT_EQ(CompressionType::kSnappyCompression, - mutable_cf_options.compression); - } - - // Test paranoid_file_checks already done in db_block_cache_test - ASSERT_OK( - dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "true"}})); - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], - &mutable_cf_options)); - ASSERT_TRUE(mutable_cf_options.report_bg_io_stats); - ASSERT_TRUE(mutable_cf_options.check_flush_compaction_key_order); - - ASSERT_OK(dbfull()->SetOptions( - handles_[1], {{"check_flush_compaction_key_order", "false"}})); - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], - &mutable_cf_options)); - ASSERT_FALSE(mutable_cf_options.check_flush_compaction_key_order); -} - -TEST_F(DBTest, L0L1L2AndUpHitCounter) { - const int kNumLevels = 3; - const int kNumKeysPerLevel = 10000; - const int kNumKeysPerDb = kNumLevels * kNumKeysPerLevel; - - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - Reopen(options); - - // After the below loop there will be one file on each of L0, L1, and L2. - int key = 0; - for (int output_level = kNumLevels - 1; output_level >= 0; --output_level) { - for (int i = 0; i < kNumKeysPerLevel; ++i) { - ASSERT_OK(Put(Key(key), "val")); - key++; - } - ASSERT_OK(Flush()); - for (int input_level = 0; input_level < output_level; ++input_level) { - // `TEST_CompactRange(input_level, ...)` compacts from `input_level` to - // `input_level + 1`. - ASSERT_OK(dbfull()->TEST_CompactRange(input_level, nullptr, nullptr)); - } - } - assert(key == kNumKeysPerDb); - - ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L0)); - ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L1)); - ASSERT_EQ(0, TestGetTickerCount(options, GET_HIT_L2_AND_UP)); - - for (int i = 0; i < kNumKeysPerDb; i++) { - ASSERT_EQ(Get(Key(i)), "val"); - } - - ASSERT_EQ(kNumKeysPerLevel, TestGetTickerCount(options, GET_HIT_L0)); - ASSERT_EQ(kNumKeysPerLevel, TestGetTickerCount(options, GET_HIT_L1)); - ASSERT_EQ(kNumKeysPerLevel, TestGetTickerCount(options, GET_HIT_L2_AND_UP)); - - ASSERT_EQ(kNumKeysPerDb, TestGetTickerCount(options, GET_HIT_L0) + - TestGetTickerCount(options, GET_HIT_L1) + - TestGetTickerCount(options, GET_HIT_L2_AND_UP)); -} - -TEST_F(DBTest, EncodeDecompressedBlockSizeTest) { - // iter 0 -- zlib - // iter 1 -- bzip2 - // iter 2 -- lz4 - // iter 3 -- lz4HC - // iter 4 -- xpress - CompressionType compressions[] = {kZlibCompression, kBZip2Compression, - kLZ4Compression, kLZ4HCCompression, - kXpressCompression}; - for (auto comp : compressions) { - if (!CompressionTypeSupported(comp)) { - continue; - } - // first_table_version 1 -- generate with table_version == 1, read with - // table_version == 2 - // first_table_version 2 -- generate with table_version == 2, read with - // table_version == 1 - for (int first_table_version = 1; first_table_version <= 2; - ++first_table_version) { - BlockBasedTableOptions table_options; - table_options.format_version = first_table_version; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - Options options = CurrentOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.create_if_missing = true; - options.compression = comp; - DestroyAndReopen(options); - - int kNumKeysWritten = 1000; - - Random rnd(301); - for (int i = 0; i < kNumKeysWritten; ++i) { - // compressible string - ASSERT_OK(Put(Key(i), rnd.RandomString(128) + std::string(128, 'a'))); - } - - table_options.format_version = first_table_version == 1 ? 2 : 1; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - for (int i = 0; i < kNumKeysWritten; ++i) { - auto r = Get(Key(i)); - ASSERT_EQ(r.substr(128), std::string(128, 'a')); - } - } - } -} - -TEST_F(DBTest, CloseSpeedup) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleLevel; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 4; - options.max_bytes_for_level_base = 400 * 1024; - options.max_write_buffer_number = 16; - - // Block background threads - env_->SetBackgroundThreads(1, Env::LOW); - env_->SetBackgroundThreads(1, Env::HIGH); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - test::SleepingBackgroundTask sleeping_task_high; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_high, Env::Priority::HIGH); - - std::vector filenames; - ASSERT_OK(env_->GetChildren(dbname_, &filenames)); - // In Windows, LOCK file cannot be deleted because it is locked by db_test - // After closing db_test, the LOCK file is unlocked and can be deleted - // Delete archival files. - bool deleteDir = true; - for (size_t i = 0; i < filenames.size(); ++i) { - Status s = env_->DeleteFile(dbname_ + "/" + filenames[i]); - if (!s.ok()) { - deleteDir = false; - } - } - if (deleteDir) { - ASSERT_OK(env_->DeleteDir(dbname_)); - } - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - env_->SetBackgroundThreads(1, Env::LOW); - env_->SetBackgroundThreads(1, Env::HIGH); - Random rnd(301); - int key_idx = 0; - - // First three 110KB files are not going to level 2 - // After that, (100K, 200K) - for (int num = 0; num < 5; num++) { - GenerateNewFile(&rnd, &key_idx, true); - } - - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - Close(); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // Unblock background threads - sleeping_task_high.WakeUp(); - sleeping_task_high.WaitUntilDone(); - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - - Destroy(options); -} - -class DelayedMergeOperator : public MergeOperator { - private: - DBTest* db_test_; - - public: - explicit DelayedMergeOperator(DBTest* d) : db_test_(d) {} - - bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { - db_test_->env_->MockSleepForMicroseconds(1000 * - merge_in.operand_list.size()); - merge_out->new_value = ""; - return true; - } - - const char* Name() const override { return "DelayedMergeOperator"; } -}; - -TEST_F(DBTest, MergeTestTime) { - std::string one, two, three; - PutFixed64(&one, 1); - PutFixed64(&two, 2); - PutFixed64(&three, 3); - - // Enable time profiling - SetPerfLevel(kEnableTime); - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.merge_operator.reset(new DelayedMergeOperator(this)); - SetTimeElapseOnlySleepOnReopen(&options); - DestroyAndReopen(options); - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - ASSERT_EQ(TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME), 0); - ASSERT_OK(db_->Put(WriteOptions(), "foo", one)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->Merge(WriteOptions(), "foo", two)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->Merge(WriteOptions(), "foo", three)); - ASSERT_OK(Flush()); - - ReadOptions opt; - opt.verify_checksums = true; - opt.snapshot = nullptr; - std::string result; - ASSERT_OK(db_->Get(opt, "foo", &result)); - - ASSERT_EQ(2000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME)); - - ReadOptions read_options; - std::unique_ptr iter(db_->NewIterator(read_options)); - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - ++count; - } - - ASSERT_EQ(1, count); - ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME)); -#ifdef ROCKSDB_USING_THREAD_STATUS - ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0); -#endif // ROCKSDB_USING_THREAD_STATUS -} - -TEST_P(DBTestWithParam, MergeCompactionTimeTest) { - SetPerfLevel(kEnableTime); - Options options = CurrentOptions(); - options.compaction_filter_factory = std::make_shared(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.merge_operator.reset(new DelayedMergeOperator(this)); - options.disable_auto_compactions = true; - options.max_subcompactions = max_subcompactions_; - SetTimeElapseOnlySleepOnReopen(&options); - DestroyAndReopen(options); - - constexpr unsigned n = 1000; - for (unsigned i = 0; i < n; i++) { - ASSERT_OK(db_->Merge(WriteOptions(), "foo", "TEST")); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - ASSERT_EQ(uint64_t{n} * 1000000U, - TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME)); -} - -TEST_P(DBTestWithParam, FilterCompactionTimeTest) { - Options options = CurrentOptions(); - options.compaction_filter_factory = - std::make_shared(this); - options.disable_auto_compactions = true; - options.create_if_missing = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.statistics->set_stats_level(kExceptTimeForMutex); - options.max_subcompactions = max_subcompactions_; - SetTimeElapseOnlySleepOnReopen(&options); - DestroyAndReopen(options); - - unsigned n = 0; - // put some data - for (int table = 0; table < 4; ++table) { - for (int i = 0; i < 10 + table; ++i) { - ASSERT_OK(Put(std::to_string(table * 100 + i), "val")); - ++n; - } - ASSERT_OK(Flush()); - } - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_EQ(0U, CountLiveFiles()); - - Reopen(options); - - Iterator* itr = db_->NewIterator(ReadOptions()); - itr->SeekToFirst(); - ASSERT_OK(itr->status()); - ASSERT_EQ(uint64_t{n} * 1000000U, - TestGetTickerCount(options, FILTER_OPERATION_TOTAL_TIME)); - delete itr; -} - -TEST_F(DBTest, TestLogCleanup) { - Options options = CurrentOptions(); - options.write_buffer_size = 64 * 1024; // very small - // only two memtables allowed ==> only two log files - options.max_write_buffer_number = 2; - Reopen(options); - - for (int i = 0; i < 100000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - // only 2 memtables will be alive, so logs_to_free needs to always be below - // 2 - ASSERT_LT(dbfull()->TEST_LogsToFreeSize(), static_cast(3)); - } -} - -TEST_F(DBTest, EmptyCompactedDB) { - Options options = CurrentOptions(); - options.max_open_files = -1; - Close(); - ASSERT_OK(ReadOnlyReopen(options)); - Status s = Put("new", "value"); - ASSERT_TRUE(s.IsNotSupported()); - Close(); -} - -TEST_F(DBTest, SuggestCompactRangeTest) { - class CompactionFilterFactoryGetContext : public CompactionFilterFactory { - public: - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - saved_context = context; - std::unique_ptr empty_filter; - return empty_filter; - } - const char* Name() const override { - return "CompactionFilterFactoryGetContext"; - } - static bool IsManual(CompactionFilterFactory* compaction_filter_factory) { - return reinterpret_cast( - compaction_filter_factory) - ->saved_context.is_manual_compaction; - } - CompactionFilter::Context saved_context; - }; - - Options options = CurrentOptions(); - options.memtable_factory.reset(test::NewSpecialSkipListFactory( - DBTestBase::kNumKeysByGenerateNewRandomFile)); - options.compaction_style = kCompactionStyleLevel; - options.compaction_filter_factory.reset( - new CompactionFilterFactoryGetContext()); - options.write_buffer_size = 200 << 10; - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 4; - options.num_levels = 4; - options.compression = kNoCompression; - options.max_bytes_for_level_base = 450 << 10; - options.target_file_size_base = 98 << 10; - options.max_compaction_bytes = static_cast(1) << 60; // inf - - Reopen(options); - - Random rnd(301); - - for (int num = 0; num < 10; num++) { - GenerateNewRandomFile(&rnd); - } - - ASSERT_TRUE(!CompactionFilterFactoryGetContext::IsManual( - options.compaction_filter_factory.get())); - - // make sure either L0 or L1 has file - while (NumTableFilesAtLevel(0) == 0 && NumTableFilesAtLevel(1) == 0) { - GenerateNewRandomFile(&rnd); - } - - // compact it three times - for (int i = 0; i < 3; ++i) { - ASSERT_OK(experimental::SuggestCompactRange(db_, nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - // All files are compacted - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - - GenerateNewRandomFile(&rnd); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // nonoverlapping with the file on level 0 - Slice start("a"), end("b"); - ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // should not compact the level 0 file - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - start = Slice("j"); - end = Slice("m"); - ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // SuggestCompactRange() is not going to be reported as manual compaction - ASSERT_TRUE(!CompactionFilterFactoryGetContext::IsManual( - options.compaction_filter_factory.get())); - - // now it should compact the level 0 file - // as it's a trivial move to L1, it triggers another one to compact to L2 - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); -} - -TEST_F(DBTest, SuggestCompactRangeUniversal) { - Options options = CurrentOptions(); - options.memtable_factory.reset(test::NewSpecialSkipListFactory( - DBTestBase::kNumKeysByGenerateNewRandomFile)); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 200 << 10; - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 4; - options.num_levels = 4; - options.compression = kNoCompression; - options.max_bytes_for_level_base = 450 << 10; - options.target_file_size_base = 98 << 10; - options.max_compaction_bytes = static_cast(1) << 60; // inf - - Reopen(options); - - Random rnd(301); - - for (int num = 0; num < 10; num++) { - GenerateNewRandomFile(&rnd); - } - - ASSERT_EQ("1,2,3,4", FilesPerLevel()); - for (int i = 0; i < 3; i++) { - ASSERT_OK( - db_->SuggestCompactRange(db_->DefaultColumnFamily(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - // All files are compacted - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - ASSERT_EQ(0, NumTableFilesAtLevel(2)); - - GenerateNewRandomFile(&rnd); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - // nonoverlapping with the file on level 0 - Slice start("a"), end("b"); - ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // should not compact the level 0 file - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - - start = Slice("j"); - end = Slice("m"); - ASSERT_OK(experimental::SuggestCompactRange(db_, &start, &end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // now it should compact the level 0 file to the last level - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); -} - -TEST_F(DBTest, PromoteL0) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.write_buffer_size = 10 * 1024 * 1024; - DestroyAndReopen(options); - - // non overlapping ranges - std::vector> ranges = { - {81, 160}, {0, 80}, {161, 240}, {241, 320}}; - - int32_t value_size = 10 * 1024; // 10 KB - - Random rnd(301); - std::map values; - for (const auto& range : ranges) { - for (int32_t j = range.first; j < range.second; j++) { - values[j] = rnd.RandomString(value_size); - ASSERT_OK(Put(Key(j), values[j])); - } - ASSERT_OK(Flush()); - } - - int32_t level0_files = NumTableFilesAtLevel(0, 0); - ASSERT_EQ(level0_files, ranges.size()); - ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // No files in L1 - - // Promote L0 level to L2. - ASSERT_OK(experimental::PromoteL0(db_, db_->DefaultColumnFamily(), 2)); - // We expect that all the files were trivially moved from L0 to L2 - ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); - ASSERT_EQ(NumTableFilesAtLevel(2, 0), level0_files); - - for (const auto& kv : values) { - ASSERT_EQ(Get(Key(kv.first)), kv.second); - } -} - -TEST_F(DBTest, PromoteL0Failure) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.write_buffer_size = 10 * 1024 * 1024; - DestroyAndReopen(options); - - // Produce two L0 files with overlapping ranges. - ASSERT_OK(Put(Key(0), "")); - ASSERT_OK(Put(Key(3), "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "")); - ASSERT_OK(Flush()); - - Status status; - // Fails because L0 has overlapping files. - status = experimental::PromoteL0(db_, db_->DefaultColumnFamily()); - ASSERT_TRUE(status.IsInvalidArgument()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // Now there is a file in L1. - ASSERT_GE(NumTableFilesAtLevel(1, 0), 1); - - ASSERT_OK(Put(Key(5), "")); - ASSERT_OK(Flush()); - // Fails because L1 is non-empty. - status = experimental::PromoteL0(db_, db_->DefaultColumnFamily()); - ASSERT_TRUE(status.IsInvalidArgument()); -} - -// Github issue #596 -TEST_F(DBTest, CompactRangeWithEmptyBottomLevel) { - const int kNumLevels = 2; - const int kNumL0Files = 2; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.num_levels = kNumLevels; - DestroyAndReopen(options); - - Random rnd(301); - for (int i = 0; i < kNumL0Files; ++i) { - ASSERT_OK(Put(Key(0), rnd.RandomString(1024))); - ASSERT_OK(Flush()); - } - ASSERT_EQ(NumTableFilesAtLevel(0), kNumL0Files); - ASSERT_EQ(NumTableFilesAtLevel(1), 0); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - ASSERT_EQ(NumTableFilesAtLevel(1), kNumL0Files); -} - -TEST_F(DBTest, AutomaticConflictsWithManualCompaction) { - const int kNumL0Files = 50; - Options options = CurrentOptions(); - options.level0_file_num_compaction_trigger = 4; - // never slowdown / stop - options.level0_slowdown_writes_trigger = 999999; - options.level0_stop_writes_trigger = 999999; - options.max_background_compactions = 10; - DestroyAndReopen(options); - - // schedule automatic compactions after the manual one starts, but before it - // finishes to ensure conflict. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCompaction:Start", - "DBTest::AutomaticConflictsWithManualCompaction:PrePuts"}, - {"DBTest::AutomaticConflictsWithManualCompaction:PostPuts", - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}}); - std::atomic callback_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::MaybeScheduleFlushOrCompaction:Conflict", - [&](void* /*arg*/) { callback_count.fetch_add(1); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int i = 0; i < 2; ++i) { - // put two keys to ensure no trivial move - for (int j = 0; j < 2; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - port::Thread manual_compaction_thread([this]() { - CompactRangeOptions croptions; - croptions.exclusive_manual_compaction = true; - ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr)); - }); - - TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PrePuts"); - for (int i = 0; i < kNumL0Files; ++i) { - // put two keys to ensure no trivial move - for (int j = 0; j < 2; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PostPuts"); - - ASSERT_GE(callback_count.load(), 1); - for (int i = 0; i < 2; ++i) { - ASSERT_NE("NOT_FOUND", Get(Key(i))); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - manual_compaction_thread.join(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); -} - -TEST_F(DBTest, CompactFilesShouldTriggerAutoCompaction) { - Options options = CurrentOptions(); - options.max_background_compactions = 1; - options.level0_file_num_compaction_trigger = 4; - options.level0_slowdown_writes_trigger = 36; - options.level0_stop_writes_trigger = 36; - DestroyAndReopen(options); - - // generate files for manual compaction - Random rnd(301); - for (int i = 0; i < 2; ++i) { - // put two keys to ensure no trivial move - for (int j = 0; j < 2; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - - ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_meta_data; - db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data); - - std::vector input_files; - input_files.push_back(cf_meta_data.levels[0].files[0].name); - - SyncPoint::GetInstance()->LoadDependency({ - {"CompactFilesImpl:0", - "DBTest::CompactFilesShouldTriggerAutoCompaction:Begin"}, - {"DBTest::CompactFilesShouldTriggerAutoCompaction:End", - "CompactFilesImpl:1"}, - }); - - SyncPoint::GetInstance()->EnableProcessing(); - - port::Thread manual_compaction_thread([&]() { - auto s = db_->CompactFiles(CompactionOptions(), db_->DefaultColumnFamily(), - input_files, 0); - ASSERT_OK(s); - }); - - TEST_SYNC_POINT("DBTest::CompactFilesShouldTriggerAutoCompaction:Begin"); - // generate enough files to trigger compaction - for (int i = 0; i < 20; ++i) { - for (int j = 0; j < 2; ++j) { - ASSERT_OK(Put(Key(j), rnd.RandomString(1024))); - } - ASSERT_OK(Flush()); - } - db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data); - ASSERT_GT(cf_meta_data.levels[0].files.size(), - options.level0_file_num_compaction_trigger); - TEST_SYNC_POINT("DBTest::CompactFilesShouldTriggerAutoCompaction:End"); - - manual_compaction_thread.join(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data); - ASSERT_LE(cf_meta_data.levels[0].files.size(), - options.level0_file_num_compaction_trigger); -} - -// Github issue #595 -// Large write batch with column families -TEST_F(DBTest, LargeBatchWithColumnFamilies) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; // Small write buffer - CreateAndReopenWithCF({"pikachu"}, options); - int64_t j = 0; - for (int i = 0; i < 5; i++) { - for (int pass = 1; pass <= 3; pass++) { - WriteBatch batch; - size_t write_size = 1024 * 1024 * (5 + i); - fprintf(stderr, "prepare: %" ROCKSDB_PRIszt " MB, pass:%d\n", - (write_size / 1024 / 1024), pass); - for (;;) { - std::string data(3000, j++ % 127 + 20); - data += std::to_string(j); - ASSERT_OK(batch.Put(handles_[0], Slice(data), Slice(data))); - if (batch.GetDataSize() > write_size) { - break; - } - } - fprintf(stderr, "write: %" ROCKSDB_PRIszt " MB\n", - (batch.GetDataSize() / 1024 / 1024)); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - fprintf(stderr, "done\n"); - } - } - // make sure we can re-open it. - ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options)); -} - -// Make sure that Flushes can proceed in parallel with CompactRange() -TEST_F(DBTest, FlushesInParallelWithCompactRange) { - // iter == 0 -- leveled - // iter == 1 -- leveled, but throw in a flush between two levels compacting - // iter == 2 -- universal - for (int iter = 0; iter < 3; ++iter) { - Options options = CurrentOptions(); - if (iter < 2) { - options.compaction_style = kCompactionStyleLevel; - } else { - options.compaction_style = kCompactionStyleUniversal; - } - options.write_buffer_size = 110 << 10; - options.level0_file_num_compaction_trigger = 4; - options.num_levels = 4; - options.compression = kNoCompression; - options.max_bytes_for_level_base = 450 << 10; - options.target_file_size_base = 98 << 10; - options.max_write_buffer_number = 2; - - DestroyAndReopen(options); - - Random rnd(301); - for (int num = 0; num < 14; num++) { - GenerateNewRandomFile(&rnd); - } - - if (iter == 1) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::RunManualCompaction()::1", - "DBTest::FlushesInParallelWithCompactRange:1"}, - {"DBTest::FlushesInParallelWithCompactRange:2", - "DBImpl::RunManualCompaction()::2"}}); - } else { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"CompactionJob::Run():Start", - "DBTest::FlushesInParallelWithCompactRange:1"}, - {"DBTest::FlushesInParallelWithCompactRange:2", - "CompactionJob::Run():End"}}); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::vector threads; - threads.emplace_back([&]() { Compact("a", "z"); }); - - TEST_SYNC_POINT("DBTest::FlushesInParallelWithCompactRange:1"); - - // this has to start a flush. if flushes are blocked, this will try to - // create - // 3 memtables, and that will fail because max_write_buffer_number is 2 - for (int num = 0; num < 3; num++) { - GenerateNewRandomFile(&rnd, /* nowait */ true); - } - - TEST_SYNC_POINT("DBTest::FlushesInParallelWithCompactRange:2"); - - for (auto& t : threads) { - t.join(); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DBTest, DelayedWriteRate) { - const int kEntriesPerMemTable = 100; - const int kTotalFlushes = 12; - - Options options = CurrentOptions(); - env_->SetBackgroundThreads(1, Env::LOW); - options.env = env_; - options.write_buffer_size = 100000000; - options.max_write_buffer_number = 256; - options.max_background_compactions = 1; - options.level0_file_num_compaction_trigger = 3; - options.level0_slowdown_writes_trigger = 3; - options.level0_stop_writes_trigger = 999999; - options.delayed_write_rate = 20000000; // Start with 200MB/s - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kEntriesPerMemTable)); - - SetTimeElapseOnlySleepOnReopen(&options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Block compactions - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - for (int i = 0; i < 3; i++) { - ASSERT_OK(Put(Key(i), std::string(10000, 'x'))); - ASSERT_OK(Flush()); - } - - // These writes will be slowed down to 1KB/s - uint64_t estimated_sleep_time = 0; - Random rnd(301); - ASSERT_OK(Put("", "")); - uint64_t cur_rate = options.delayed_write_rate; - for (int i = 0; i < kTotalFlushes; i++) { - uint64_t size_memtable = 0; - for (int j = 0; j < kEntriesPerMemTable; j++) { - auto rand_num = rnd.Uniform(20); - // Spread the size range to more. - size_t entry_size = rand_num * rand_num * rand_num; - WriteOptions wo; - ASSERT_OK(Put(Key(i), std::string(entry_size, 'x'), wo)); - size_memtable += entry_size + 18; - // Occasionally sleep a while - if (rnd.Uniform(20) == 6) { - env_->SleepForMicroseconds(2666); - } - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - estimated_sleep_time += size_memtable * 1000000u / cur_rate; - // Slow down twice. One for memtable switch and one for flush finishes. - cur_rate = static_cast(static_cast(cur_rate) * - kIncSlowdownRatio * kIncSlowdownRatio); - } - // Estimate the total sleep time fall into the rough range. - ASSERT_GT(env_->NowMicros(), estimated_sleep_time / 2); - ASSERT_LT(env_->NowMicros(), estimated_sleep_time * 2); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); -} - -TEST_F(DBTest, HardLimit) { - Options options = CurrentOptions(); - options.env = env_; - env_->SetBackgroundThreads(1, Env::LOW); - options.max_write_buffer_number = 256; - options.write_buffer_size = 110 << 10; // 110KB - options.arena_block_size = 4 * 1024; - options.level0_file_num_compaction_trigger = 4; - options.level0_slowdown_writes_trigger = 999999; - options.level0_stop_writes_trigger = 999999; - options.hard_pending_compaction_bytes_limit = 800 << 10; - options.max_bytes_for_level_base = 10000000000u; - options.max_background_compactions = 1; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - CreateAndReopenWithCF({"pikachu"}, options); - - std::atomic callback_count(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DelayWrite:Wait", [&](void* /*arg*/) { - callback_count.fetch_add(1); - sleeping_task_low.WakeUp(); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - int key_idx = 0; - for (int num = 0; num < 5; num++) { - GenerateNewFile(&rnd, &key_idx, true); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - - ASSERT_EQ(0, callback_count.load()); - - for (int num = 0; num < 5; num++) { - GenerateNewFile(&rnd, &key_idx, true); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_GE(callback_count.load(), 1); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - sleeping_task_low.WaitUntilDone(); -} - -#if !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) -class WriteStallListener : public EventListener { - public: - WriteStallListener() : condition_(WriteStallCondition::kNormal) {} - void OnStallConditionsChanged(const WriteStallInfo& info) override { - MutexLock l(&mutex_); - condition_ = info.condition.cur; - } - bool CheckCondition(WriteStallCondition expected) { - MutexLock l(&mutex_); - return expected == condition_; - } - - private: - port::Mutex mutex_; - WriteStallCondition condition_; -}; - -TEST_F(DBTest, SoftLimit) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; // Small write buffer - options.max_write_buffer_number = 256; - options.level0_file_num_compaction_trigger = 1; - options.level0_slowdown_writes_trigger = 3; - options.level0_stop_writes_trigger = 999999; - options.delayed_write_rate = 20000; // About 200KB/s limited rate - options.soft_pending_compaction_bytes_limit = 160000; - options.target_file_size_base = 99999999; // All into one file - options.max_bytes_for_level_base = 50000; - options.max_bytes_for_level_multiplier = 10; - options.max_background_compactions = 1; - options.compression = kNoCompression; - WriteStallListener* listener = new WriteStallListener(); - options.listeners.emplace_back(listener); - - // FlushMemtable with opt.wait=true does not wait for - // `OnStallConditionsChanged` being called. The event listener is triggered - // on `JobContext::Clean`, which happens after flush result is installed. - // We use sync point to create a custom WaitForFlush that waits for - // context cleanup. - port::Mutex flush_mutex; - port::CondVar flush_cv(&flush_mutex); - bool flush_finished = false; - auto InstallFlushCallback = [&]() { - { - MutexLock l(&flush_mutex); - flush_finished = false; - } - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCallFlush:ContextCleanedUp", [&](void*) { - { - MutexLock l(&flush_mutex); - flush_finished = true; - } - flush_cv.SignalAll(); - }); - }; - auto WaitForFlush = [&]() { - { - MutexLock l(&flush_mutex); - while (!flush_finished) { - flush_cv.Wait(); - } - } - SyncPoint::GetInstance()->ClearCallBack( - "DBImpl::BackgroundCallFlush:ContextCleanedUp"); - }; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Reopen(options); - - // Generating 360KB in Level 3 - for (int i = 0; i < 72; i++) { - ASSERT_OK(Put(Key(i), std::string(5000, 'x'))); - if (i % 10 == 0) { - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - MoveFilesToLevel(3); - - // Generating 360KB in Level 2 - for (int i = 0; i < 72; i++) { - ASSERT_OK(Put(Key(i), std::string(5000, 'x'))); - if (i % 10 == 0) { - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - MoveFilesToLevel(2); - - ASSERT_OK(Put(Key(0), "")); - - test::SleepingBackgroundTask sleeping_task_low; - // Block compactions - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - sleeping_task_low.WaitUntilSleeping(); - - // Create 3 L0 files, making score of L0 to be 3. - for (int i = 0; i < 3; i++) { - ASSERT_OK(Put(Key(i), std::string(5000, 'x'))); - ASSERT_OK(Put(Key(100 - i), std::string(5000, 'x'))); - // Flush the file. File size is around 30KB. - InstallFlushCallback(); - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - WaitForFlush(); - } - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed)); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - sleeping_task_low.Reset(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Now there is one L1 file but doesn't trigger soft_rate_limit - // - // TODO: soft_rate_limit is depreciated. If this test - // relies on soft_rate_limit, then we need to change the test. - // - // The L1 file size is around 30KB. - ASSERT_EQ(NumTableFilesAtLevel(1), 1); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal)); - - // Only allow one compactin going through. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void* /*arg*/) { - // Schedule a sleeping task. - sleeping_task_low.Reset(); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_low, Env::Priority::LOW); - }); - - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - sleeping_task_low.WaitUntilSleeping(); - // Create 3 L0 files, making score of L0 to be 3 - for (int i = 0; i < 3; i++) { - ASSERT_OK(Put(Key(10 + i), std::string(5000, 'x'))); - ASSERT_OK(Put(Key(90 - i), std::string(5000, 'x'))); - // Flush the file. File size is around 30KB. - InstallFlushCallback(); - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - WaitForFlush(); - } - - // Wake up sleep task to enable compaction to run and waits - // for it to go to sleep state again to make sure one compaction - // goes through. - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilSleeping(); - - // Now there is one L1 file (around 60KB) which exceeds 50KB base by 10KB - // Given level multiplier 10, estimated pending compaction is around 100KB - // doesn't trigger soft_pending_compaction_bytes_limit - ASSERT_EQ(NumTableFilesAtLevel(1), 1); - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal)); - - // Create 3 L0 files, making score of L0 to be 3, higher than L0. - for (int i = 0; i < 3; i++) { - ASSERT_OK(Put(Key(20 + i), std::string(5000, 'x'))); - ASSERT_OK(Put(Key(80 - i), std::string(5000, 'x'))); - // Flush the file. File size is around 30KB. - InstallFlushCallback(); - ASSERT_OK(dbfull()->TEST_FlushMemTable(true, true)); - WaitForFlush(); - } - // Wake up sleep task to enable compaction to run and waits - // for it to go to sleep state again to make sure one compaction - // goes through. - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilSleeping(); - - // Now there is one L1 file (around 90KB) which exceeds 50KB base by 40KB - // L2 size is 360KB, so the estimated level fanout 4, estimated pending - // compaction is around 200KB - // triggerring soft_pending_compaction_bytes_limit - ASSERT_EQ(NumTableFilesAtLevel(1), 1); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed)); - - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilSleeping(); - - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal)); - - // shrink level base so L2 will hit soft limit easier. - ASSERT_OK(dbfull()->SetOptions({ - {"max_bytes_for_level_base", "5000"}, - })); - - ASSERT_OK(Put("", "")); - ASSERT_OK(Flush()); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed)); - - sleeping_task_low.WaitUntilSleeping(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); -} - -TEST_F(DBTest, LastWriteBufferDelay) { - Options options = CurrentOptions(); - options.env = env_; - options.write_buffer_size = 100000; - options.max_write_buffer_number = 4; - options.delayed_write_rate = 20000; - options.compression = kNoCompression; - options.disable_auto_compactions = true; - int kNumKeysPerMemtable = 3; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerMemtable)); - - Reopen(options); - test::SleepingBackgroundTask sleeping_task; - // Block flushes - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::HIGH); - sleeping_task.WaitUntilSleeping(); - - // Create 3 L0 files, making score of L0 to be 3. - for (int i = 0; i < 3; i++) { - // Fill one mem table - for (int j = 0; j < kNumKeysPerMemtable; j++) { - ASSERT_OK(Put(Key(j), "")); - } - ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); - } - // Inserting a new entry would create a new mem table, triggering slow down. - ASSERT_OK(Put(Key(0), "")); - ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); - - sleeping_task.WakeUp(); - sleeping_task.WaitUntilDone(); -} -#endif // !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) - -TEST_F(DBTest, FailWhenCompressionNotSupportedTest) { - CompressionType compressions[] = {kZlibCompression, kBZip2Compression, - kLZ4Compression, kLZ4HCCompression, - kXpressCompression}; - for (auto comp : compressions) { - if (!CompressionTypeSupported(comp)) { - // not supported, we should fail the Open() - Options options = CurrentOptions(); - options.compression = comp; - ASSERT_TRUE(!TryReopen(options).ok()); - // Try if CreateColumnFamily also fails - options.compression = kNoCompression; - ASSERT_OK(TryReopen(options)); - ColumnFamilyOptions cf_options(options); - cf_options.compression = comp; - ColumnFamilyHandle* handle; - ASSERT_TRUE(!db_->CreateColumnFamily(cf_options, "name", &handle).ok()); - } - } -} - -TEST_F(DBTest, CreateColumnFamilyShouldFailOnIncompatibleOptions) { - Options options = CurrentOptions(); - options.max_open_files = 100; - Reopen(options); - - ColumnFamilyOptions cf_options(options); - // ttl is now supported when max_open_files is -1. - cf_options.ttl = 3600; - ColumnFamilyHandle* handle; - ASSERT_OK(db_->CreateColumnFamily(cf_options, "pikachu", &handle)); - delete handle; -} - -TEST_F(DBTest, RowCache) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.row_cache = NewLRUCache(8192); - DestroyAndReopen(options); - - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - - ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0); - ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 0); - ASSERT_EQ(Get("foo"), "bar"); - ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 0); - ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1); - ASSERT_EQ(Get("foo"), "bar"); - ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_HIT), 1); - ASSERT_EQ(TestGetTickerCount(options, ROW_CACHE_MISS), 1); -} - -TEST_F(DBTest, PinnableSliceAndRowCache) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.row_cache = NewLRUCache(8192); - DestroyAndReopen(options); - - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - - ASSERT_EQ(Get("foo"), "bar"); - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 1); - - { - PinnableSlice pin_slice; - ASSERT_EQ(Get("foo", &pin_slice), Status::OK()); - ASSERT_EQ(pin_slice.ToString(), "bar"); - // Entry is already in cache, lookup will remove the element from lru - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 0); - } - // After PinnableSlice destruction element is added back in LRU - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 1); -} - -TEST_F(DBTest, ReusePinnableSlice) { - Options options = CurrentOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.row_cache = NewLRUCache(8192); - DestroyAndReopen(options); - - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - - ASSERT_EQ(Get("foo"), "bar"); - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 1); - - { - PinnableSlice pin_slice; - ASSERT_EQ(Get("foo", &pin_slice), Status::OK()); - ASSERT_EQ(Get("foo", &pin_slice), Status::OK()); - ASSERT_EQ(pin_slice.ToString(), "bar"); - - // Entry is already in cache, lookup will remove the element from lru - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 0); - } - // After PinnableSlice destruction element is added back in LRU - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 1); - - { - std::vector multiget_keys; - multiget_keys.push_back("foo"); - std::vector multiget_values(1); - std::vector statuses({Status::NotFound()}); - ReadOptions ropt; - dbfull()->MultiGet(ropt, dbfull()->DefaultColumnFamily(), - multiget_keys.size(), multiget_keys.data(), - multiget_values.data(), statuses.data()); - ASSERT_EQ(Status::OK(), statuses[0]); - dbfull()->MultiGet(ropt, dbfull()->DefaultColumnFamily(), - multiget_keys.size(), multiget_keys.data(), - multiget_values.data(), statuses.data()); - ASSERT_EQ(Status::OK(), statuses[0]); - - // Entry is already in cache, lookup will remove the element from lru - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 0); - } - // After PinnableSlice destruction element is added back in LRU - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 1); - - { - std::vector multiget_cfs; - multiget_cfs.push_back(dbfull()->DefaultColumnFamily()); - std::vector multiget_keys; - multiget_keys.push_back("foo"); - std::vector multiget_values(1); - std::vector statuses({Status::NotFound()}); - ReadOptions ropt; - dbfull()->MultiGet(ropt, multiget_keys.size(), multiget_cfs.data(), - multiget_keys.data(), multiget_values.data(), - statuses.data()); - ASSERT_EQ(Status::OK(), statuses[0]); - dbfull()->MultiGet(ropt, multiget_keys.size(), multiget_cfs.data(), - multiget_keys.data(), multiget_values.data(), - statuses.data()); - ASSERT_EQ(Status::OK(), statuses[0]); - - // Entry is already in cache, lookup will remove the element from lru - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 0); - } - // After PinnableSlice destruction element is added back in LRU - ASSERT_EQ( - reinterpret_cast(options.row_cache.get())->TEST_GetLRUSize(), - 1); -} - - -TEST_F(DBTest, DeletingOldWalAfterDrop) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"Test:AllowFlushes", "DBImpl::BGWorkFlush"}, - {"DBImpl::BGWorkFlush:done", "Test:WaitForFlush"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - Options options = CurrentOptions(); - options.max_total_wal_size = 8192; - options.compression = kNoCompression; - options.write_buffer_size = 1 << 20; - options.level0_file_num_compaction_trigger = (1 << 30); - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - CreateColumnFamilies({"cf1", "cf2"}, options); - ASSERT_OK(Put(0, "key1", DummyString(8192))); - ASSERT_OK(Put(0, "key2", DummyString(8192))); - // the oldest wal should now be getting_flushed - ASSERT_OK(db_->DropColumnFamily(handles_[0])); - // all flushes should now do nothing because their CF is dropped - TEST_SYNC_POINT("Test:AllowFlushes"); - TEST_SYNC_POINT("Test:WaitForFlush"); - uint64_t lognum1 = dbfull()->TEST_LogfileNumber(); - ASSERT_OK(Put(1, "key3", DummyString(8192))); - ASSERT_OK(Put(1, "key4", DummyString(8192))); - // new wal should have been created - uint64_t lognum2 = dbfull()->TEST_LogfileNumber(); - EXPECT_GT(lognum2, lognum1); -} - -TEST_F(DBTest, UnsupportedManualSync) { - DestroyAndReopen(CurrentOptions()); - env_->is_wal_sync_thread_safe_.store(false); - Status s = db_->SyncWAL(); - ASSERT_TRUE(s.IsNotSupported()); -} - -INSTANTIATE_TEST_CASE_P(DBTestWithParam, DBTestWithParam, - ::testing::Combine(::testing::Values(1, 4), - ::testing::Bool())); - -TEST_F(DBTest, PauseBackgroundWorkTest) { - Options options = CurrentOptions(); - options.write_buffer_size = 100000; // Small write buffer - Reopen(options); - - std::vector threads; - std::atomic done(false); - ASSERT_OK(db_->PauseBackgroundWork()); - threads.emplace_back([&]() { - Random rnd(301); - for (int i = 0; i < 10000; ++i) { - ASSERT_OK(Put(rnd.RandomString(10), rnd.RandomString(10))); - } - done.store(true); - }); - env_->SleepForMicroseconds(200000); - // make sure the thread is not done - ASSERT_FALSE(done.load()); - ASSERT_OK(db_->ContinueBackgroundWork()); - for (auto& t : threads) { - t.join(); - } - // now it's done - ASSERT_TRUE(done.load()); -} - -// Keep spawning short-living threads that create an iterator and quit. -// Meanwhile in another thread keep flushing memtables. -// This used to cause a deadlock. -TEST_F(DBTest, ThreadLocalPtrDeadlock) { - std::atomic flushes_done{0}; - std::atomic threads_destroyed{0}; - auto done = [&] { return flushes_done.load() > 10; }; - - port::Thread flushing_thread([&] { - for (int i = 0; !done(); ++i) { - ASSERT_OK(db_->Put(WriteOptions(), Slice("hi"), - Slice(std::to_string(i).c_str()))); - ASSERT_OK(db_->Flush(FlushOptions())); - int cnt = ++flushes_done; - fprintf(stderr, "Flushed %d times\n", cnt); - } - }); - - std::vector thread_spawning_threads(10); - for (auto& t : thread_spawning_threads) { - t = port::Thread([&] { - while (!done()) { - { - port::Thread tmp_thread([&] { - auto it = db_->NewIterator(ReadOptions()); - ASSERT_OK(it->status()); - delete it; - }); - tmp_thread.join(); - } - ++threads_destroyed; - } - }); - } - - for (auto& t : thread_spawning_threads) { - t.join(); - } - flushing_thread.join(); - fprintf(stderr, "Done. Flushed %d times, destroyed %d threads\n", - flushes_done.load(), threads_destroyed.load()); -} - -TEST_F(DBTest, LargeBlockSizeTest) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(0, "foo", "bar")); - BlockBasedTableOptions table_options; - table_options.block_size = 8LL * 1024 * 1024 * 1024LL; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - ASSERT_NOK(TryReopenWithColumnFamilies({"default", "pikachu"}, options)); -} - - -TEST_F(DBTest, CreationTimeOfOldestFile) { - const int kNumKeysPerFile = 32; - const int kNumLevelFiles = 2; - const int kValueSize = 100; - - Options options = CurrentOptions(); - options.max_open_files = -1; - env_->SetMockSleep(); - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - bool set_file_creation_time_to_zero = true; - int idx = 0; - - int64_t time_1 = 0; - env_->GetCurrentTime(&time_1); - const uint64_t uint_time_1 = static_cast(time_1); - - // Add 50 hours - env_->MockSleepForSeconds(50 * 60 * 60); - - int64_t time_2 = 0; - env_->GetCurrentTime(&time_2); - const uint64_t uint_time_2 = static_cast(time_2); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) { - TableProperties* props = reinterpret_cast(arg); - if (set_file_creation_time_to_zero) { - if (idx == 0) { - props->file_creation_time = 0; - idx++; - } else if (idx == 1) { - props->file_creation_time = uint_time_1; - idx = 0; - } - } else { - if (idx == 0) { - props->file_creation_time = uint_time_1; - idx++; - } else if (idx == 1) { - props->file_creation_time = uint_time_2; - } - } - }); - // Set file creation time in manifest all to 0. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "FileMetaData::FileMetaData", [&](void* arg) { - FileMetaData* meta = static_cast(arg); - meta->file_creation_time = 0; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - - // At this point there should be 2 files, one with file_creation_time = 0 and - // the other non-zero. GetCreationTimeOfOldestFile API should return 0. - uint64_t creation_time; - Status s1 = dbfull()->GetCreationTimeOfOldestFile(&creation_time); - ASSERT_EQ(0, creation_time); - ASSERT_EQ(s1, Status::OK()); - - // Testing with non-zero file creation time. - set_file_creation_time_to_zero = false; - options = CurrentOptions(); - options.max_open_files = -1; - options.env = env_; - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - DestroyAndReopen(options); - - for (int i = 0; i < kNumLevelFiles; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK( - Put(Key(i * kNumKeysPerFile + j), rnd.RandomString(kValueSize))); - } - ASSERT_OK(Flush()); - } - - // At this point there should be 2 files with non-zero file creation time. - // GetCreationTimeOfOldestFile API should return non-zero value. - uint64_t ctime; - Status s2 = dbfull()->GetCreationTimeOfOldestFile(&ctime); - ASSERT_EQ(uint_time_1, ctime); - ASSERT_EQ(s2, Status::OK()); - - // Testing with max_open_files != -1 - options = CurrentOptions(); - options.max_open_files = 10; - DestroyAndReopen(options); - Status s3 = dbfull()->GetCreationTimeOfOldestFile(&ctime); - ASSERT_EQ(s3, Status::NotSupported()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBTest, MemoryUsageWithMaxWriteBufferSizeToMaintain) { - Options options = CurrentOptions(); - options.max_write_buffer_size_to_maintain = 10000; - options.write_buffer_size = 160000; - Reopen(options); - Random rnd(301); - bool memory_limit_exceeded = false; - - ColumnFamilyData* cfd = - static_cast(db_->DefaultColumnFamily())->cfd(); - - for (int i = 0; i < 1000; i++) { - std::string value = rnd.RandomString(1000); - ASSERT_OK(Put("keykey_" + std::to_string(i), value)); - - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - const uint64_t cur_active_mem = cfd->mem()->ApproximateMemoryUsage(); - const uint64_t size_all_mem_table = - cur_active_mem + cfd->imm()->ApproximateMemoryUsage(); - - // Errors out if memory usage keeps on increasing beyond the limit. - // Once memory limit exceeds, memory_limit_exceeded is set and if - // size_all_mem_table doesn't drop out in the next write then it errors out - // (not expected behaviour). If memory usage drops then - // memory_limit_exceeded is set to false. - if ((size_all_mem_table > cur_active_mem) && - (cur_active_mem >= - static_cast(options.max_write_buffer_size_to_maintain)) && - (size_all_mem_table > - static_cast(options.max_write_buffer_size_to_maintain) + - options.write_buffer_size)) { - ASSERT_FALSE(memory_limit_exceeded); - memory_limit_exceeded = true; - } else { - memory_limit_exceeded = false; - } - } -} - -TEST_F(DBTest, ShuttingDownNotBlockStalledWrites) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - Reopen(options); - Random rnd(403); - - for (int i = 0; i < 20; i++) { - ASSERT_OK(Put("key_" + std::to_string(i), rnd.RandomString(10))); - ASSERT_OK(Flush()); - } - ASSERT_EQ(GetSstFileCount(dbname_), 20); - - // We need !disable_auto_compactions for writes to stall but also want to - // delay compaction so stalled writes unblocked due to kShutdownInProgress. BG - // compaction will first wait for the sync point - // DBTest::ShuttingDownNotBlockStalledWrites. Then it waits extra 2 sec to - // allow CancelAllBackgroundWork() to set shutting_down_. - SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", - [&](void* /* arg */) { env_->SleepForMicroseconds(2 * 1000 * 1000); }); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::DelayWrite:Wait", "DBTest::ShuttingDownNotBlockStalledWrites"}, - {"DBTest::ShuttingDownNotBlockStalledWrites", - "BackgroundCallCompaction:0"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - options.level0_stop_writes_trigger = 20; - options.disable_auto_compactions = false; - Reopen(options); - - std::thread thd([&]() { - Status s = Put("key_" + std::to_string(101), "101"); - ASSERT_EQ(s.code(), Status::kShutdownInProgress); - }); - - TEST_SYNC_POINT("DBTest::ShuttingDownNotBlockStalledWrites"); - CancelAllBackgroundWork(db_, true); - - thd.join(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_universal_compaction_test.cc b/db/db_universal_compaction_test.cc deleted file mode 100644 index bb6b67d9b..000000000 --- a/db/db_universal_compaction_test.cc +++ /dev/null @@ -1,2227 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "port/stack_trace.h" -#include "rocksdb/utilities/table_properties_collectors.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -static std::string CompressibleString(Random* rnd, int len) { - std::string r; - test::CompressibleString(rnd, 0.8, len, &r); - return r; -} - -class DBTestUniversalCompactionBase - : public DBTestBase, - public ::testing::WithParamInterface> { - public: - explicit DBTestUniversalCompactionBase(const std::string& path) - : DBTestBase(path, /*env_do_fsync=*/false) {} - void SetUp() override { - num_levels_ = std::get<0>(GetParam()); - exclusive_manual_compaction_ = std::get<1>(GetParam()); - } - int num_levels_; - bool exclusive_manual_compaction_; -}; - -class DBTestUniversalCompaction : public DBTestUniversalCompactionBase { - public: - DBTestUniversalCompaction() - : DBTestUniversalCompactionBase("/db_universal_compaction_test") {} -}; - -class DBTestUniversalCompaction2 : public DBTestBase { - public: - DBTestUniversalCompaction2() - : DBTestBase("db_universal_compaction_test2", /*env_do_fsync=*/false) {} -}; - -namespace { -void VerifyCompactionResult( - const ColumnFamilyMetaData& cf_meta, - const std::set& overlapping_file_numbers) { -#ifndef NDEBUG - for (auto& level : cf_meta.levels) { - for (auto& file : level.files) { - assert(overlapping_file_numbers.find(file.name) == - overlapping_file_numbers.end()); - } - } -#endif -} - -class KeepFilter : public CompactionFilter { - public: - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - return false; - } - - const char* Name() const override { return "KeepFilter"; } -}; - -class KeepFilterFactory : public CompactionFilterFactory { - public: - explicit KeepFilterFactory(bool check_context = false) - : check_context_(check_context) {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - if (check_context_) { - EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction); - EXPECT_EQ(expect_manual_compaction_.load(), context.is_manual_compaction); - } - return std::unique_ptr(new KeepFilter()); - } - - const char* Name() const override { return "KeepFilterFactory"; } - bool check_context_; - std::atomic_bool expect_full_compaction_; - std::atomic_bool expect_manual_compaction_; -}; -} // anonymous namespace - -// Make sure we don't trigger a problem if the trigger condtion is given -// to be 0, which is invalid. -TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) { - Options options = CurrentOptions(); - - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - // Config universal compaction to always compact to one single sorted run. - options.level0_file_num_compaction_trigger = 0; - options.compaction_options_universal.size_ratio = 10; - options.compaction_options_universal.min_merge_width = 2; - options.compaction_options_universal.max_size_amplification_percent = 0; - - options.write_buffer_size = 105 << 10; // 105KB - options.arena_block_size = 4 << 10; - options.target_file_size_base = 32 << 10; // 32KB - // trigger compaction if there are >= 4 files - KeepFilterFactory* filter = new KeepFilterFactory(true); - filter->expect_manual_compaction_.store(false); - options.compaction_filter_factory.reset(filter); - - DestroyAndReopen(options); - ASSERT_EQ(1, db_->GetOptions().level0_file_num_compaction_trigger); - - Random rnd(301); - int key_idx = 0; - - filter->expect_full_compaction_.store(true); - - for (int num = 0; num < 16; num++) { - // Write 100KB file. And immediately it should be compacted to one file. - GenerateNewFile(&rnd, &key_idx); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumSortedRuns(0), 1); - } - ASSERT_OK(Put(Key(key_idx), "")); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumSortedRuns(0), 1); -} - -TEST_P(DBTestUniversalCompaction, OptimizeFiltersForHits) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.size_ratio = 5; - options.num_levels = num_levels_; - options.write_buffer_size = 105 << 10; // 105KB - options.arena_block_size = 4 << 10; - options.target_file_size_base = 32 << 10; // 32KB - // trigger compaction if there are >= 4 files - options.level0_file_num_compaction_trigger = 4; - BlockBasedTableOptions bbto; - bbto.cache_index_and_filter_blocks = true; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.optimize_filters_for_hits = true; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.memtable_factory.reset(test::NewSpecialSkipListFactory(3)); - - DestroyAndReopen(options); - - // block compaction from happening - env_->SetBackgroundThreads(1, Env::LOW); - test::SleepingBackgroundTask sleeping_task_low; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::LOW); - - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - ASSERT_OK(Put(Key(num * 10), "val")); - if (num) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(Put(Key(30 + num * 10), "val")); - ASSERT_OK(Put(Key(60 + num * 10), "val")); - } - ASSERT_OK(Put("", "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - - // Query set of non existing keys - for (int i = 5; i < 90; i += 10) { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - - // Make sure bloom filter is used at least once. - ASSERT_GT(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); - auto prev_counter = TestGetTickerCount(options, BLOOM_FILTER_USEFUL); - - // Make sure bloom filter is used for all but the last L0 file when looking - // up a non-existent key that's in the range of all L0 files. - ASSERT_EQ(Get(Key(35)), "NOT_FOUND"); - ASSERT_EQ(prev_counter + NumTableFilesAtLevel(0) - 1, - TestGetTickerCount(options, BLOOM_FILTER_USEFUL)); - prev_counter = TestGetTickerCount(options, BLOOM_FILTER_USEFUL); - - // Unblock compaction and wait it for happening. - sleeping_task_low.WakeUp(); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // The same queries will not trigger bloom filter - for (int i = 5; i < 90; i += 10) { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - ASSERT_EQ(prev_counter, TestGetTickerCount(options, BLOOM_FILTER_USEFUL)); -} - -// TODO(kailiu) The tests on UniversalCompaction has some issues: -// 1. A lot of magic numbers ("11" or "12"). -// 2. Made assumption on the memtable flush conditions, which may change from -// time to time. -TEST_P(DBTestUniversalCompaction, UniversalCompactionTrigger) { - Options options; - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.size_ratio = 5; - options.num_levels = num_levels_; - options.write_buffer_size = 105 << 10; // 105KB - options.arena_block_size = 4 << 10; - options.target_file_size_base = 32 << 10; // 32KB - // trigger compaction if there are >= 4 files - options.level0_file_num_compaction_trigger = 4; - KeepFilterFactory* filter = new KeepFilterFactory(true); - filter->expect_manual_compaction_.store(false); - options.compaction_filter_factory.reset(filter); - - options = CurrentOptions(options); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBTestWritableFile.GetPreallocationStatus", [&](void* arg) { - ASSERT_TRUE(arg != nullptr); - size_t preallocation_size = *(static_cast(arg)); - if (num_levels_ > 3) { - ASSERT_LE(preallocation_size, options.target_file_size_base * 1.1); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - int key_idx = 0; - - filter->expect_full_compaction_.store(true); - // Stage 1: - // Generate a set of files at level 0, but don't trigger level-0 - // compaction. - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - // Write 100KB - GenerateNewFile(1, &rnd, &key_idx); - } - - // Generate one more file at level-0, which should trigger level-0 - // compaction. - GenerateNewFile(1, &rnd, &key_idx); - // Suppose each file flushed from mem table has size 1. Now we compact - // (level0_file_num_compaction_trigger+1)=4 files and should have a big - // file of size 4. - ASSERT_EQ(NumSortedRuns(1), 1); - - // Stage 2: - // Now we have one file at level 0, with size 4. We also have some data in - // mem table. Let's continue generating new files at level 0, but don't - // trigger level-0 compaction. - // First, clean up memtable before inserting new data. This will generate - // a level-0 file, with size around 0.4 (according to previously written - // data amount). - filter->expect_full_compaction_.store(false); - ASSERT_OK(Flush(1)); - for (int num = 0; num < options.level0_file_num_compaction_trigger - 3; - num++) { - GenerateNewFile(1, &rnd, &key_idx); - ASSERT_EQ(NumSortedRuns(1), num + 3); - } - - // Generate one more file at level-0, which should trigger level-0 - // compaction. - GenerateNewFile(1, &rnd, &key_idx); - // Before compaction, we have 4 files at level 0, with size 4, 0.4, 1, 1. - // After compaction, we should have 2 files, with size 4, 2.4. - ASSERT_EQ(NumSortedRuns(1), 2); - - // Stage 3: - // Now we have 2 files at level 0, with size 4 and 2.4. Continue - // generating new files at level 0. - for (int num = 0; num < options.level0_file_num_compaction_trigger - 3; - num++) { - GenerateNewFile(1, &rnd, &key_idx); - ASSERT_EQ(NumSortedRuns(1), num + 3); - } - - // Generate one more file at level-0, which should trigger level-0 - // compaction. - GenerateNewFile(1, &rnd, &key_idx); - // Before compaction, we have 4 files at level 0, with size 4, 2.4, 1, 1. - // After compaction, we should have 3 files, with size 4, 2.4, 2. - ASSERT_EQ(NumSortedRuns(1), 3); - - // Stage 4: - // Now we have 3 files at level 0, with size 4, 2.4, 2. Let's generate a - // new file of size 1. - GenerateNewFile(1, &rnd, &key_idx); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Level-0 compaction is triggered, but no file will be picked up. - ASSERT_EQ(NumSortedRuns(1), 4); - - // Stage 5: - // Now we have 4 files at level 0, with size 4, 2.4, 2, 1. Let's generate - // a new file of size 1. - filter->expect_full_compaction_.store(true); - GenerateNewFile(1, &rnd, &key_idx); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // All files at level 0 will be compacted into a single one. - ASSERT_EQ(NumSortedRuns(1), 1); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionSizeAmplification) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = 3; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - Random rnd(301); - int key_idx = 0; - - // Generate two files in Level 0. Both files are approx the same size. - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(1, Key(key_idx), rnd.RandomString(10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_EQ(NumSortedRuns(1), num + 1); - } - ASSERT_EQ(NumSortedRuns(1), 2); - - // Flush whatever is remaining in memtable. This is typically - // small, which should not trigger size ratio based compaction - // but will instead trigger size amplification. - ASSERT_OK(Flush(1)); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Verify that size amplification did occur - ASSERT_EQ(NumSortedRuns(1), 1); -} - -TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionSizeAmplification) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = 3; - // Initial setup of compaction_options_universal will prevent universal - // compaction from happening - options.compaction_options_universal.size_ratio = 100; - options.compaction_options_universal.min_merge_width = 100; - DestroyAndReopen(options); - - int total_picked_compactions = 0; - int total_size_amp_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) { - if (arg) { - total_picked_compactions++; - Compaction* c = static_cast(arg); - if (c->compaction_reason() == - CompactionReason::kUniversalSizeAmplification) { - total_size_amp_compactions++; - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - MutableCFOptions mutable_cf_options; - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - int key_idx = 0; - - // Generate two files in Level 0. Both files are approx the same size. - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(1, Key(key_idx), rnd.RandomString(10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_EQ(NumSortedRuns(1), num + 1); - } - ASSERT_EQ(NumSortedRuns(1), 2); - - // Flush whatever is remaining in memtable. This is typically - // small, which should not trigger size ratio based compaction - // but could instead trigger size amplification if it's set - // to 110. - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Verify compaction did not happen - ASSERT_EQ(NumSortedRuns(1), 3); - - // Trigger compaction if size amplification exceeds 110% without reopening DB - ASSERT_EQ(dbfull() - ->GetOptions(handles_[1]) - .compaction_options_universal.max_size_amplification_percent, - 200U); - ASSERT_OK(dbfull()->SetOptions(handles_[1], - {{"compaction_options_universal", - "{max_size_amplification_percent=110;}"}})); - ASSERT_EQ(dbfull() - ->GetOptions(handles_[1]) - .compaction_options_universal.max_size_amplification_percent, - 110u); - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], - &mutable_cf_options)); - ASSERT_EQ(110u, mutable_cf_options.compaction_options_universal - .max_size_amplification_percent); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Verify that size amplification did happen - ASSERT_EQ(NumSortedRuns(1), 1); - ASSERT_EQ(total_picked_compactions, 1); - ASSERT_EQ(total_size_amp_compactions, 1); -} - -TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionReadAmplification) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = 3; - // Initial setup of compaction_options_universal will prevent universal - // compaction from happening - options.compaction_options_universal.max_size_amplification_percent = 2000; - options.compaction_options_universal.size_ratio = 0; - options.compaction_options_universal.min_merge_width = 100; - DestroyAndReopen(options); - - int total_picked_compactions = 0; - int total_size_ratio_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) { - if (arg) { - total_picked_compactions++; - Compaction* c = static_cast(arg); - if (c->compaction_reason() == CompactionReason::kUniversalSizeRatio) { - total_size_ratio_compactions++; - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - MutableCFOptions mutable_cf_options; - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - int key_idx = 0; - - // Generate three files in Level 0. All files are approx the same size. - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(1, Key(key_idx), rnd.RandomString(10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_EQ(NumSortedRuns(1), num + 1); - } - ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger); - - // Flush whatever is remaining in memtable. This is typically small, about - // 30KB. - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Verify compaction did not happen - ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger + 1); - ASSERT_EQ(total_picked_compactions, 0); - - ASSERT_OK(dbfull()->SetOptions( - handles_[1], - {{"compaction_options_universal", - "{min_merge_width=2;max_merge_width=2;size_ratio=100;}"}})); - ASSERT_EQ(dbfull() - ->GetOptions(handles_[1]) - .compaction_options_universal.min_merge_width, - 2u); - ASSERT_EQ(dbfull() - ->GetOptions(handles_[1]) - .compaction_options_universal.max_merge_width, - 2u); - ASSERT_EQ( - dbfull()->GetOptions(handles_[1]).compaction_options_universal.size_ratio, - 100u); - - ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], - &mutable_cf_options)); - ASSERT_EQ(mutable_cf_options.compaction_options_universal.size_ratio, 100u); - ASSERT_EQ(mutable_cf_options.compaction_options_universal.min_merge_width, - 2u); - ASSERT_EQ(mutable_cf_options.compaction_options_universal.max_merge_width, - 2u); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Files in L0 are approx: 0.3 (30KB), 1, 1, 1. - // On compaction: the files are below the size amp threshold, so we - // fallthrough to checking read amp conditions. The configured size ratio is - // not big enough to take 0.3 into consideration. So the next files 1 and 1 - // are compacted together first as they satisfy size ratio condition and - // (min_merge_width, max_merge_width) condition, to give out a file size of 2. - // Next, the newly generated 2 and the last file 1 are compacted together. So - // at the end: #sortedRuns = 2, #picked_compactions = 2, and all the picked - // ones are size ratio based compactions. - ASSERT_EQ(NumSortedRuns(1), 2); - // If max_merge_width had not been changed dynamically above, and if it - // continued to be the default value of UINIT_MAX, total_picked_compactions - // would have been 1. - ASSERT_EQ(total_picked_compactions, 2); - ASSERT_EQ(total_size_ratio_compactions, 2); -} - -TEST_P(DBTestUniversalCompaction, CompactFilesOnUniversalCompaction) { - const int kTestKeySize = 16; - const int kTestValueSize = 984; - const int kEntrySize = kTestKeySize + kTestValueSize; - const int kEntriesPerBuffer = 10; - - ChangeCompactOptions(); - Options options; - options.create_if_missing = true; - options.compaction_style = kCompactionStyleLevel; - options.num_levels = 1; - options.target_file_size_base = options.write_buffer_size; - options.compression = kNoCompression; - options = CurrentOptions(options); - options.write_buffer_size = kEntrySize * kEntriesPerBuffer; - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_EQ(options.compaction_style, kCompactionStyleUniversal); - Random rnd(301); - for (int key = 1024 * kEntriesPerBuffer; key >= 0; --key) { - ASSERT_OK(Put(1, std::to_string(key), rnd.RandomString(kTestValueSize))); - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ColumnFamilyMetaData cf_meta; - dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta); - std::vector compaction_input_file_names; - for (auto file : cf_meta.levels[0].files) { - if (rnd.OneIn(2)) { - compaction_input_file_names.push_back(file.name); - } - } - - if (compaction_input_file_names.size() == 0) { - compaction_input_file_names.push_back(cf_meta.levels[0].files[0].name); - } - - // expect fail since universal compaction only allow L0 output - ASSERT_FALSE(dbfull() - ->CompactFiles(CompactionOptions(), handles_[1], - compaction_input_file_names, 1) - .ok()); - - // expect ok and verify the compacted files no longer exist. - ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), handles_[1], - compaction_input_file_names, 0)); - - dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta); - VerifyCompactionResult( - cf_meta, std::set(compaction_input_file_names.begin(), - compaction_input_file_names.end())); - - compaction_input_file_names.clear(); - - // Pick the first and the last file, expect everything is - // compacted into one single file. - compaction_input_file_names.push_back(cf_meta.levels[0].files[0].name); - compaction_input_file_names.push_back( - cf_meta.levels[0].files[cf_meta.levels[0].files.size() - 1].name); - ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), handles_[1], - compaction_input_file_names, 0)); - - dbfull()->GetColumnFamilyMetaData(handles_[1], &cf_meta); - ASSERT_EQ(cf_meta.levels[0].files.size(), 1U); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionTargetLevel) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 100 << 10; // 100KB - options.num_levels = 7; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - // Generate 3 overlapping files - Random rnd(301); - for (int i = 0; i < 210; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(100))); - } - ASSERT_OK(Flush()); - - for (int i = 200; i < 300; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(100))); - } - ASSERT_OK(Flush()); - - for (int i = 250; i < 260; i++) { - ASSERT_OK(Put(Key(i), rnd.RandomString(100))); - } - ASSERT_OK(Flush()); - - ASSERT_EQ("3", FilesPerLevel(0)); - // Compact all files into 1 file and put it in L4 - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 4; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); - ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0)); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -class DBTestUniversalCompactionMultiLevels - : public DBTestUniversalCompactionBase { - public: - DBTestUniversalCompactionMultiLevels() - : DBTestUniversalCompactionBase( - "/db_universal_compaction_multi_levels_test") {} -}; - -TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionMultiLevels) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - options.write_buffer_size = 100 << 10; // 100KB - options.level0_file_num_compaction_trigger = 8; - options.max_background_compactions = 3; - options.target_file_size_base = 32 * 1024; - CreateAndReopenWithCF({"pikachu"}, options); - - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - Random rnd(301); - int num_keys = 100000; - for (int i = 0; i < num_keys * 2; i++) { - ASSERT_OK(Put(1, Key(i % num_keys), Key(i))); - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - for (int i = num_keys; i < num_keys * 2; i++) { - ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i)); - } -} - -// Tests universal compaction with trivial move enabled -TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionTrivialMove) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", [&](void* arg) { - non_trivial_move++; - ASSERT_TRUE(arg != nullptr); - int output_level = *(static_cast(arg)); - ASSERT_EQ(output_level, 0); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.allow_trivial_move = true; - options.num_levels = 3; - options.write_buffer_size = 100 << 10; // 100KB - options.level0_file_num_compaction_trigger = 3; - options.max_background_compactions = 2; - options.target_file_size_base = 32 * 1024; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - Random rnd(301); - int num_keys = 150000; - for (int i = 0; i < num_keys; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - std::vector values; - - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(trivial_move, 0); - ASSERT_GT(non_trivial_move, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -INSTANTIATE_TEST_CASE_P(MultiLevels, DBTestUniversalCompactionMultiLevels, - ::testing::Combine(::testing::Values(3, 20), - ::testing::Bool())); - -class DBTestUniversalCompactionParallel : public DBTestUniversalCompactionBase { - public: - DBTestUniversalCompactionParallel() - : DBTestUniversalCompactionBase("/db_universal_compaction_prallel_test") { - } -}; - -TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - options.env = env_; - options.write_buffer_size = 1 << 10; // 1KB - options.level0_file_num_compaction_trigger = 3; - options.max_background_compactions = 3; - options.max_background_flushes = 3; - options.target_file_size_base = 1 * 1024; - options.compaction_options_universal.max_size_amplification_percent = 110; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Delay every compaction so multiple compactions will happen. - std::atomic num_compactions_running(0); - std::atomic has_parallel(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():Start", [&](void* /*arg*/) { - if (num_compactions_running.fetch_add(1) > 0) { - has_parallel.store(true); - return; - } - for (int nwait = 0; nwait < 20000; nwait++) { - if (has_parallel.load() || num_compactions_running.load() > 1) { - has_parallel.store(true); - break; - } - env_->SleepForMicroseconds(1000); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():End", - [&](void* /*arg*/) { num_compactions_running.fetch_add(-1); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - Random rnd(301); - int num_keys = 30000; - for (int i = 0; i < num_keys * 2; i++) { - ASSERT_OK(Put(1, Key(i % num_keys), Key(i))); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(num_compactions_running.load(), 0); - ASSERT_TRUE(has_parallel.load()); - - for (int i = num_keys; i < num_keys * 2; i++) { - ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i)); - } - - // Reopen and check. - ReopenWithColumnFamilies({"default", "pikachu"}, options); - for (int i = num_keys; i < num_keys * 2; i++) { - ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i)); - } -} - -TEST_P(DBTestUniversalCompactionParallel, PickByFileNumberBug) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - options.write_buffer_size = 1 * 1024; // 1KB - options.level0_file_num_compaction_trigger = 7; - options.max_background_compactions = 2; - options.target_file_size_base = 1024 * 1024; // 1MB - - // Disable size amplifiction compaction - options.compaction_options_universal.max_size_amplification_percent = - UINT_MAX; - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBTestUniversalCompactionParallel::PickByFileNumberBug:0", - "BackgroundCallCompaction:0"}, - {"UniversalCompactionBuilder::PickCompaction:Return", - "DBTestUniversalCompactionParallel::PickByFileNumberBug:1"}, - {"DBTestUniversalCompactionParallel::PickByFileNumberBug:2", - "CompactionJob::Run():Start"}}); - - int total_picked_compactions = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UniversalCompactionBuilder::PickCompaction:Return", [&](void* arg) { - if (arg) { - total_picked_compactions++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Write 7 files to trigger compaction - int key_idx = 1; - for (int i = 1; i <= 70; i++) { - std::string k = Key(key_idx++); - ASSERT_OK(Put(k, k)); - if (i % 10 == 0) { - ASSERT_OK(Flush()); - } - } - - // Wait for the 1st background compaction process to start - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0"); - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - - // Write 3 files while 1st compaction is held - // These 3 files have different sizes to avoid compacting based on size_ratio - int num_keys = 1000; - for (int i = 0; i < 3; i++) { - for (int j = 1; j <= num_keys; j++) { - std::string k = Key(key_idx++); - ASSERT_OK(Put(k, k)); - } - ASSERT_OK(Flush()); - num_keys -= 100; - } - - // Hold the 1st compaction from finishing - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:2"); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // There should only be one picked compaction as the score drops below one - // after the first one is picked. - EXPECT_EQ(total_picked_compactions, 1); - EXPECT_EQ(TotalTableFiles(), 4); - - // Stop SyncPoint and destroy the DB and reopen it again - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - key_idx = 1; - total_picked_compactions = 0; - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Write 7 files to trigger compaction - for (int i = 1; i <= 70; i++) { - std::string k = Key(key_idx++); - ASSERT_OK(Put(k, k)); - if (i % 10 == 0) { - ASSERT_OK(Flush()); - } - } - - // Wait for the 1st background compaction process to start - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0"); - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - - // Write 8 files while 1st compaction is held - // These 8 files have different sizes to avoid compacting based on size_ratio - num_keys = 1000; - for (int i = 0; i < 8; i++) { - for (int j = 1; j <= num_keys; j++) { - std::string k = Key(key_idx++); - ASSERT_OK(Put(k, k)); - } - ASSERT_OK(Flush()); - num_keys -= 100; - } - - // Wait for the 2nd background compaction process to start - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0"); - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1"); - - // Hold the 1st and 2nd compaction from finishing - TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:2"); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // This time we will trigger a compaction because of size ratio and - // another compaction because of number of files that are not compacted - // greater than 7 - EXPECT_GE(total_picked_compactions, 2); -} - -INSTANTIATE_TEST_CASE_P(Parallel, DBTestUniversalCompactionParallel, - ::testing::Combine(::testing::Values(1, 10), - ::testing::Values(false))); -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 105 << 10; // 105KB - options.arena_block_size = 4 << 10; // 4KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = 4; - options.num_levels = num_levels_; - options.compaction_options_universal.compression_size_percent = -1; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - Random rnd(301); - int key_idx = 0; - - for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { - // Write 100KB (100 values, each 1K) - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(1, Key(key_idx), rnd.RandomString(990))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - - if (num < options.level0_file_num_compaction_trigger - 1) { - ASSERT_EQ(NumSortedRuns(1), num + 1); - } - } - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(NumSortedRuns(1), 1); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionStopStyleSimilarSize) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 105 << 10; // 105KB - options.arena_block_size = 4 << 10; // 4KB - options.target_file_size_base = 32 << 10; // 32KB - // trigger compaction if there are >= 4 files - options.level0_file_num_compaction_trigger = 4; - options.compaction_options_universal.size_ratio = 10; - options.compaction_options_universal.stop_style = - kCompactionStopStyleSimilarSize; - options.num_levels = num_levels_; - DestroyAndReopen(options); - - Random rnd(301); - int key_idx = 0; - - // Stage 1: - // Generate a set of files at level 0, but don't trigger level-0 - // compaction. - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - // Write 100KB (100 values, each 1K) - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(key_idx), rnd.RandomString(990))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(NumSortedRuns(), num + 1); - } - - // Generate one more file at level-0, which should trigger level-0 - // compaction. - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(key_idx), rnd.RandomString(990))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Suppose each file flushed from mem table has size 1. Now we compact - // (level0_file_num_compaction_trigger+1)=4 files and should have a big - // file of size 4. - ASSERT_EQ(NumSortedRuns(), 1); - - // Stage 2: - // Now we have one file at level 0, with size 4. We also have some data in - // mem table. Let's continue generating new files at level 0, but don't - // trigger level-0 compaction. - // First, clean up memtable before inserting new data. This will generate - // a level-0 file, with size around 0.4 (according to previously written - // data amount). - ASSERT_OK(dbfull()->Flush(FlushOptions())); - for (int num = 0; num < options.level0_file_num_compaction_trigger - 3; - num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(key_idx), rnd.RandomString(990))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(NumSortedRuns(), num + 3); - } - - // Generate one more file at level-0, which should trigger level-0 - // compaction. - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(key_idx), rnd.RandomString(990))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Before compaction, we have 4 files at level 0, with size 4, 0.4, 1, 1. - // After compaction, we should have 3 files, with size 4, 0.4, 2. - ASSERT_EQ(NumSortedRuns(), 3); - // Stage 3: - // Now we have 3 files at level 0, with size 4, 0.4, 2. Generate one - // more file at level-0, which should trigger level-0 compaction. - for (int i = 0; i < 100; i++) { - ASSERT_OK(Put(Key(key_idx), rnd.RandomString(990))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Level-0 compaction is triggered, but no file will be picked up. - ASSERT_EQ(NumSortedRuns(), 4); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio1) { - if (!Snappy_Supported()) { - return; - } - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = 2; - options.num_levels = num_levels_; - options.compaction_options_universal.compression_size_percent = 70; - DestroyAndReopen(options); - - Random rnd(301); - int key_idx = 0; - - // The first compaction (2) is compressed. - for (int num = 0; num < 2; num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_LT(TotalSize(), 110000U * 2 * 0.9); - - // The second compaction (4) is compressed - for (int num = 0; num < 2; num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_LT(TotalSize(), 110000 * 4 * 0.9); - - // The third compaction (2 4) is compressed since this time it is - // (1 1 3.2) and 3.2/5.2 doesn't reach ratio. - for (int num = 0; num < 2; num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_LT(TotalSize(), 110000 * 6 * 0.9); - - // When we start for the compaction up to (2 4 8), the latest - // compressed is not compressed. - for (int num = 0; num < 8; num++) { - // Write 110KB (11 values, each 10K) - for (int i = 0; i < 11; i++) { - ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_GT(TotalSize(), 110000 * 11 * 0.8 + 110000 * 2); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio2) { - if (!Snappy_Supported()) { - return; - } - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = 2; - options.num_levels = num_levels_; - options.compaction_options_universal.compression_size_percent = 95; - DestroyAndReopen(options); - - Random rnd(301); - int key_idx = 0; - - // When we start for the compaction up to (2 4 8), the latest - // compressed is compressed given the size ratio to compress. - for (int num = 0; num < 14; num++) { - // Write 120KB (12 values, each 10K) - for (int i = 0; i < 12; i++) { - ASSERT_OK(Put(Key(key_idx), CompressibleString(&rnd, 10000))); - key_idx++; - } - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_LT(TotalSize(), 120000U * 12 * 0.82 + 120000 * 2); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -// Test that checks trivial move in universal compaction -TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest1) { - int32_t trivial_move = 0; - int32_t non_trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", [&](void* arg) { - non_trivial_move++; - ASSERT_TRUE(arg != nullptr); - int output_level = *(static_cast(arg)); - ASSERT_EQ(output_level, 0); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.allow_trivial_move = true; - options.num_levels = 2; - options.write_buffer_size = 100 << 10; // 100KB - options.level0_file_num_compaction_trigger = 3; - options.max_background_compactions = 1; - options.target_file_size_base = 32 * 1024; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - Random rnd(301); - int num_keys = 250000; - for (int i = 0; i < num_keys; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - std::vector values; - - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(trivial_move, 0); - ASSERT_GT(non_trivial_move, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} -// Test that checks trivial move in universal compaction -TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) { - int32_t trivial_move = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* /*arg*/) { trivial_move++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial", [&](void* arg) { - ASSERT_TRUE(arg != nullptr); - int output_level = *(static_cast(arg)); - ASSERT_EQ(output_level, 0); - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.allow_trivial_move = true; - options.num_levels = 15; - options.write_buffer_size = 100 << 10; // 100KB - options.level0_file_num_compaction_trigger = 8; - options.max_background_compactions = 2; - options.target_file_size_base = 64 * 1024; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - Random rnd(301); - int num_keys = 500000; - for (int i = 0; i < num_keys; i++) { - ASSERT_OK(Put(1, Key(i), Key(i))); - } - std::vector values; - - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(trivial_move, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 300 * 1024); - options.db_paths.emplace_back(dbname_ + "_2", 300 * 1024); - options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024); - options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.size_ratio = 5; - options.write_buffer_size = 111 << 10; // 114KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 1; - - std::vector filenames; - if (env_->GetChildren(options.db_paths[1].path, &filenames).ok()) { - // Delete archival files. - for (size_t i = 0; i < filenames.size(); ++i) { - ASSERT_OK( - env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i])); - } - ASSERT_OK(env_->DeleteDir(options.db_paths[1].path)); - } - Reopen(options); - - Random rnd(301); - int key_idx = 0; - - // First three 110KB files are not going to second path. - // After that, (100K, 200K) - for (int num = 0; num < 3; num++) { - GenerateNewFile(&rnd, &key_idx); - } - - // Another 110KB triggers a compaction to 400K file to second path - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - - // (1, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1,1,4) -> (2, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 2, 4) -> (3, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 3, 4) -> (8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path)); - - // (1, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 1, 8) -> (2, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - // (1, 2, 8) -> (3, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 3, 8) -> (4, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path)); - - // (1, 4, 8) -> (5, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Reopen(options); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Destroy(options); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionCFPathUse) { - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 300 * 1024); - options.db_paths.emplace_back(dbname_ + "_2", 300 * 1024); - options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024); - options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.size_ratio = 10; - options.write_buffer_size = 111 << 10; // 114KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 1; - - std::vector option_vector; - option_vector.emplace_back(options); - ColumnFamilyOptions cf_opt1(options), cf_opt2(options); - // Configure CF1 specific paths. - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 300 * 1024); - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 300 * 1024); - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_3", 500 * 1024); - cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_4", 1024 * 1024 * 1024); - option_vector.emplace_back(DBOptions(options), cf_opt1); - CreateColumnFamilies({"one"}, option_vector[1]); - - // Configura CF2 specific paths. - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2", 300 * 1024); - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_2", 300 * 1024); - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_3", 500 * 1024); - cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_4", 1024 * 1024 * 1024); - option_vector.emplace_back(DBOptions(options), cf_opt2); - CreateColumnFamilies({"two"}, option_vector[2]); - - ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); - - Random rnd(301); - int key_idx = 0; - int key_idx1 = 0; - int key_idx2 = 0; - - auto generate_file = [&]() { - GenerateNewFile(0, &rnd, &key_idx); - GenerateNewFile(1, &rnd, &key_idx1); - GenerateNewFile(2, &rnd, &key_idx2); - }; - - auto check_sstfilecount = [&](int path_id, int expected) { - ASSERT_EQ(expected, GetSstFileCount(options.db_paths[path_id].path)); - ASSERT_EQ(expected, GetSstFileCount(cf_opt1.cf_paths[path_id].path)); - ASSERT_EQ(expected, GetSstFileCount(cf_opt2.cf_paths[path_id].path)); - }; - - auto check_getvalues = [&]() { - for (int i = 0; i < key_idx; i++) { - auto v = Get(0, Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - for (int i = 0; i < key_idx1; i++) { - auto v = Get(1, Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - for (int i = 0; i < key_idx2; i++) { - auto v = Get(2, Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - }; - - // First three 110KB files are not going to second path. - // After that, (100K, 200K) - for (int num = 0; num < 3; num++) { - generate_file(); - } - - // Another 110KB triggers a compaction to 400K file to second path - generate_file(); - check_sstfilecount(2, 1); - - // (1, 4) - generate_file(); - check_sstfilecount(2, 1); - check_sstfilecount(0, 1); - - // (1,1,4) -> (2, 4) - generate_file(); - check_sstfilecount(2, 1); - check_sstfilecount(1, 1); - check_sstfilecount(0, 0); - - // (1, 2, 4) -> (3, 4) - generate_file(); - check_sstfilecount(2, 1); - check_sstfilecount(1, 1); - check_sstfilecount(0, 0); - - // (1, 3, 4) -> (8) - generate_file(); - check_sstfilecount(3, 1); - - // (1, 8) - generate_file(); - check_sstfilecount(3, 1); - check_sstfilecount(0, 1); - - // (1, 1, 8) -> (2, 8) - generate_file(); - check_sstfilecount(3, 1); - check_sstfilecount(1, 1); - - // (1, 2, 8) -> (3, 8) - generate_file(); - check_sstfilecount(3, 1); - check_sstfilecount(1, 1); - check_sstfilecount(0, 0); - - // (1, 3, 8) -> (4, 8) - generate_file(); - check_sstfilecount(2, 1); - check_sstfilecount(3, 1); - - // (1, 4, 8) -> (5, 8) - generate_file(); - check_sstfilecount(3, 1); - check_sstfilecount(2, 1); - check_sstfilecount(0, 0); - - check_getvalues(); - - ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); - - check_getvalues(); - - Destroy(options, true); -} - -TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) { - std::function verify_func = [&](int num_keys_in_db) { - std::string keys_in_db; - Iterator* iter = dbfull()->NewIterator(ReadOptions(), handles_[1]); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - keys_in_db.append(iter->key().ToString()); - keys_in_db.push_back(','); - } - delete iter; - - std::string expected_keys; - for (int i = 0; i <= num_keys_in_db; i++) { - expected_keys.append(Key(i)); - expected_keys.push_back(','); - } - - ASSERT_EQ(keys_in_db, expected_keys); - }; - - Random rnd(301); - int max_key1 = 200; - int max_key2 = 600; - int max_key3 = 800; - const int KNumKeysPerFile = 10; - - // Stage 1: open a DB with universal compaction, num_levels=1 - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - options.write_buffer_size = 200 << 10; // 200KB - options.level0_file_num_compaction_trigger = 3; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysPerFile)); - options = CurrentOptions(options); - CreateAndReopenWithCF({"pikachu"}, options); - - for (int i = 0; i <= max_key1; i++) { - // each value is 10K - ASSERT_OK(Put(1, Key(i), rnd.RandomString(10000))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // Stage 2: reopen with universal compaction, num_levels=4 - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 4; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - verify_func(max_key1); - - // Insert more keys - for (int i = max_key1 + 1; i <= max_key2; i++) { - // each value is 10K - ASSERT_OK(Put(1, Key(i), rnd.RandomString(10000))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - verify_func(max_key2); - // Compaction to non-L0 has happened. - ASSERT_GT(NumTableFilesAtLevel(options.num_levels - 1, 1), 0); - - // Stage 3: Revert it back to one level and revert to num_levels=1. - options.num_levels = 4; - options.target_file_size_base = INT_MAX; - ReopenWithColumnFamilies({"default", "pikachu"}, options); - // Compact all to level 0 - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 0; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK( - dbfull()->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - // Need to restart it once to remove higher level records in manifest. - ReopenWithColumnFamilies({"default", "pikachu"}, options); - // Final reopen - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 1; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - // Insert more keys - for (int i = max_key2 + 1; i <= max_key3; i++) { - // each value is 10K - ASSERT_OK(Put(1, Key(i), rnd.RandomString(10000))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - ASSERT_OK(Flush(1)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - verify_func(max_key3); -} - -TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) { - if (!Snappy_Supported()) { - return; - } - Options options = CurrentOptions(); - options.db_paths.emplace_back(dbname_, 500 * 1024); - options.db_paths.emplace_back(dbname_ + "_2", 1024 * 1024 * 1024); - options.compaction_style = kCompactionStyleUniversal; - options.compaction_options_universal.size_ratio = 5; - options.write_buffer_size = 111 << 10; // 114KB - options.arena_block_size = 4 << 10; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 1; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - - std::vector filenames; - if (env_->GetChildren(options.db_paths[1].path, &filenames).ok()) { - // Delete archival files. - for (size_t i = 0; i < filenames.size(); ++i) { - ASSERT_OK( - env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i])); - } - ASSERT_OK(env_->DeleteDir(options.db_paths[1].path)); - } - Reopen(options); - - Random rnd(301); - int key_idx = 0; - - // First three 110KB files are not going to second path. - // After that, (100K, 200K) - for (int num = 0; num < 3; num++) { - GenerateNewFile(&rnd, &key_idx); - } - - // Another 110KB triggers a compaction to 400K file to second path - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - // (1, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1,1,4) -> (2, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 2, 4) -> (3, 4) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 3, 4) -> (8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 1, 8) -> (2, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(1, GetSstFileCount(dbname_)); - - // (1, 2, 8) -> (3, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 3, 8) -> (4, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - // (1, 4, 8) -> (5, 8) - GenerateNewFile(&rnd, &key_idx); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); - ASSERT_EQ(0, GetSstFileCount(dbname_)); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Reopen(options); - - for (int i = 0; i < key_idx; i++) { - auto v = Get(Key(i)); - ASSERT_NE(v, "NOT_FOUND"); - ASSERT_TRUE(v.size() == 1 || v.size() == 990); - } - - Destroy(options); -} - -TEST_P(DBTestUniversalCompaction, ConcurrentBottomPriLowPriCompactions) { - if (num_levels_ == 1) { - // for single-level universal, everything's bottom level so nothing should - // be executed in bottom-pri thread pool. - return; - } - const int kNumFilesTrigger = 3; - Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM); - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.max_background_compactions = 2; - options.num_levels = num_levels_; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = kNumFilesTrigger; - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - DestroyAndReopen(options); - - // Need to get a token to enable compaction parallelism up to - // `max_background_compactions` jobs. - auto pressure_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {// wait for the full compaction to be picked before adding files intended - // for the second one. - {"DBImpl::BackgroundCompaction:ForwardToBottomPriPool", - "DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"}, - // the full (bottom-pri) compaction waits until a partial (low-pri) - // compaction has started to verify they can run in parallel. - {"DBImpl::BackgroundCompaction:NonTrivial", - "DBImpl::BGWorkBottomCompaction"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int i = 0; i < 2; ++i) { - for (int num = 0; num < kNumFilesTrigger; num++) { - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx, true /* no_wait */); - // use no_wait above because that one waits for flush and compaction. We - // don't want to wait for compaction because the full compaction is - // intentionally blocked while more files are flushed. - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - if (i == 0) { - TEST_SYNC_POINT( - "DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"); - } - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - // First compaction should output to bottom level. Second should output to L0 - // since older L0 files pending compaction prevent it from being placed lower. - ASSERT_EQ(NumSortedRuns(), 2); - ASSERT_GT(NumTableFilesAtLevel(0), 0); - ASSERT_GT(NumTableFilesAtLevel(num_levels_ - 1), 0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM); -} - -TEST_P(DBTestUniversalCompaction, RecalculateScoreAfterPicking) { - // Regression test for extra compactions scheduled. Once enough compactions - // have been scheduled to bring the score below one, we should stop - // scheduling more; otherwise, other CFs/DBs may be delayed unnecessarily. - const int kNumFilesTrigger = 8; - Options options = CurrentOptions(); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); - options.compaction_options_universal.max_merge_width = kNumFilesTrigger / 2; - options.compaction_options_universal.max_size_amplification_percent = - static_cast(-1); - options.compaction_style = kCompactionStyleUniversal; - options.level0_file_num_compaction_trigger = kNumFilesTrigger; - options.num_levels = num_levels_; - Reopen(options); - - std::atomic num_compactions_attempted(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Start", - [&](void* /*arg*/) { ++num_compactions_attempted; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int num = 0; num < kNumFilesTrigger; num++) { - ASSERT_EQ(NumSortedRuns(), num); - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Compacting the first four files was enough to bring the score below one so - // there's no need to schedule any more compactions. - ASSERT_EQ(1, num_compactions_attempted); - ASSERT_EQ(NumSortedRuns(), 5); -} - -TEST_P(DBTestUniversalCompaction, FinalSortedRunCompactFilesConflict) { - // Regression test for conflict between: - // (1) Running CompactFiles including file in the final sorted run; and - // (2) Picking universal size-amp-triggered compaction, which always includes - // the final sorted run. - if (exclusive_manual_compaction_) { - return; - } - - Options opts = CurrentOptions(); - opts.compaction_style = kCompactionStyleUniversal; - opts.compaction_options_universal.max_size_amplification_percent = 50; - opts.compaction_options_universal.min_merge_width = 2; - opts.compression = kNoCompression; - opts.level0_file_num_compaction_trigger = 2; - opts.max_background_compactions = 2; - opts.num_levels = num_levels_; - Reopen(opts); - - // make sure compaction jobs can be parallelized - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(NumTableFilesAtLevel(num_levels_ - 1), 1); - ColumnFamilyMetaData cf_meta; - ColumnFamilyHandle* default_cfh = db_->DefaultColumnFamily(); - dbfull()->GetColumnFamilyMetaData(default_cfh, &cf_meta); - ASSERT_EQ(1, cf_meta.levels[num_levels_ - 1].files.size()); - std::string first_sst_filename = - cf_meta.levels[num_levels_ - 1].files[0].name; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"CompactFilesImpl:0", - "DBTestUniversalCompaction:FinalSortedRunCompactFilesConflict:0"}, - {"DBImpl::BackgroundCompaction():AfterPickCompaction", - "CompactFilesImpl:1"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - port::Thread compact_files_thread([&]() { - ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), default_cfh, - {first_sst_filename}, num_levels_ - 1)); - }); - - TEST_SYNC_POINT( - "DBTestUniversalCompaction:FinalSortedRunCompactFilesConflict:0"); - for (int i = 0; i < 2; ++i) { - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - compact_files_thread.join(); -} - -INSTANTIATE_TEST_CASE_P(NumLevels, DBTestUniversalCompaction, - ::testing::Combine(::testing::Values(1, 3, 5), - ::testing::Bool())); - -class DBTestUniversalManualCompactionOutputPathId - : public DBTestUniversalCompactionBase { - public: - DBTestUniversalManualCompactionOutputPathId() - : DBTestUniversalCompactionBase( - "/db_universal_compaction_manual_pid_test") {} -}; - -TEST_P(DBTestUniversalManualCompactionOutputPathId, - ManualCompactionOutputPathId) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.db_paths.emplace_back(dbname_, 1000000000); - options.db_paths.emplace_back(dbname_ + "_2", 1000000000); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - options.target_file_size_base = 1 << 30; // Big size - options.level0_file_num_compaction_trigger = 10; - Destroy(options); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - MakeTables(3, "p", "q", 1); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(2, TotalLiveFiles(1)); - ASSERT_EQ(2, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[1].path)); - - // Full compaction to DB path 0 - CompactRangeOptions compact_options; - compact_options.target_path_id = 1; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - ASSERT_EQ(1, TotalLiveFiles(1)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - ASSERT_EQ(1, TotalLiveFiles(1)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - MakeTables(1, "p", "q", 1); - ASSERT_EQ(2, TotalLiveFiles(1)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); - ASSERT_EQ(2, TotalLiveFiles(1)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); - - // Full compaction to DB path 0 - compact_options.target_path_id = 0; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_OK(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - ASSERT_EQ(1, TotalLiveFiles(1)); - ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path)); - ASSERT_EQ(0, GetSstFileCount(options.db_paths[1].path)); - - // Fail when compacting to an invalid path ID - compact_options.target_path_id = 2; - compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; - ASSERT_TRUE(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr) - .IsInvalidArgument()); -} - -INSTANTIATE_TEST_CASE_P(OutputPathId, - DBTestUniversalManualCompactionOutputPathId, - ::testing::Combine(::testing::Values(1, 8), - ::testing::Bool())); - -TEST_F(DBTestUniversalCompaction2, BasicL0toL1) { - const int kNumKeys = 3000; - const int kWindowSize = 100; - const int kNumDelsTrigger = 90; - - Options opts = CurrentOptions(); - opts.table_properties_collector_factories.emplace_back( - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); - opts.compaction_style = kCompactionStyleUniversal; - opts.level0_file_num_compaction_trigger = 2; - opts.compression = kNoCompression; - opts.compaction_options_universal.size_ratio = 10; - opts.compaction_options_universal.min_merge_width = 2; - opts.compaction_options_universal.max_size_amplification_percent = 200; - Reopen(opts); - - // add an L1 file to prevent tombstones from dropping due to obsolescence - // during flush - int i; - for (i = 0; i < 2000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - // MoveFilesToLevel(6); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - for (i = 1999; i < kNumKeys; ++i) { - if (i >= kNumKeys - kWindowSize && - i < kNumKeys - kWindowSize + kNumDelsTrigger) { - ASSERT_OK(Delete(Key(i))); - } else { - ASSERT_OK(Put(Key(i), "val")); - } - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(6), 0); -} - -#if defined(ENABLE_SINGLE_LEVEL_DTC) -TEST_F(DBTestUniversalCompaction2, SingleLevel) { - const int kNumKeys = 3000; - const int kWindowSize = 100; - const int kNumDelsTrigger = 90; - - Options opts = CurrentOptions(); - opts.table_properties_collector_factories.emplace_back( - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); - opts.compaction_style = kCompactionStyleUniversal; - opts.level0_file_num_compaction_trigger = 2; - opts.compression = kNoCompression; - opts.num_levels = 1; - opts.compaction_options_universal.size_ratio = 10; - opts.compaction_options_universal.min_merge_width = 2; - opts.compaction_options_universal.max_size_amplification_percent = 200; - Reopen(opts); - - // add an L1 file to prevent tombstones from dropping due to obsolescence - // during flush - int i; - for (i = 0; i < 2000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - - for (i = 1999; i < kNumKeys; ++i) { - if (i >= kNumKeys - kWindowSize && - i < kNumKeys - kWindowSize + kNumDelsTrigger) { - ASSERT_OK(Delete(Key(i))); - } else { - ASSERT_OK(Put(Key(i), "val")); - } - } - ASSERT_OK(Flush()(; - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); -} -#endif // ENABLE_SINGLE_LEVEL_DTC - -TEST_F(DBTestUniversalCompaction2, MultipleLevels) { - const int kWindowSize = 100; - const int kNumDelsTrigger = 90; - - Options opts = CurrentOptions(); - opts.table_properties_collector_factories.emplace_back( - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); - opts.compaction_style = kCompactionStyleUniversal; - opts.level0_file_num_compaction_trigger = 4; - opts.compression = kNoCompression; - opts.compaction_options_universal.size_ratio = 10; - opts.compaction_options_universal.min_merge_width = 2; - opts.compaction_options_universal.max_size_amplification_percent = 200; - Reopen(opts); - - // add an L1 file to prevent tombstones from dropping due to obsolescence - // during flush - int i; - for (i = 0; i < 500; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 500; i < 1000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 1000; i < 1500; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 1500; i < 2000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(6), 0); - - for (i = 1999; i < 2333; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 2333; i < 2666; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 2666; i < 2999; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(6), 0); - ASSERT_GT(NumTableFilesAtLevel(5), 0); - - for (i = 1900; i < 2100; ++i) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - ASSERT_EQ(0, NumTableFilesAtLevel(2)); - ASSERT_EQ(0, NumTableFilesAtLevel(3)); - ASSERT_EQ(0, NumTableFilesAtLevel(4)); - ASSERT_EQ(0, NumTableFilesAtLevel(5)); - ASSERT_GT(NumTableFilesAtLevel(6), 0); -} - -TEST_F(DBTestUniversalCompaction2, OverlappingL0) { - const int kWindowSize = 100; - const int kNumDelsTrigger = 90; - - Options opts = CurrentOptions(); - opts.table_properties_collector_factories.emplace_back( - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); - opts.compaction_style = kCompactionStyleUniversal; - opts.level0_file_num_compaction_trigger = 5; - opts.compression = kNoCompression; - opts.compaction_options_universal.size_ratio = 10; - opts.compaction_options_universal.min_merge_width = 2; - opts.compaction_options_universal.max_size_amplification_percent = 200; - Reopen(opts); - - // add an L1 file to prevent tombstones from dropping due to obsolescence - // during flush - int i; - for (i = 0; i < 2000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 2000; i < 3000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 3500; i < 4000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - for (i = 2900; i < 3100; ++i) { - ASSERT_OK(Delete(Key(i))); - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - ASSERT_GT(NumTableFilesAtLevel(6), 0); -} - -TEST_F(DBTestUniversalCompaction2, IngestBehind) { - const int kNumKeys = 3000; - const int kWindowSize = 100; - const int kNumDelsTrigger = 90; - - Options opts = CurrentOptions(); - opts.table_properties_collector_factories.emplace_back( - NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); - opts.compaction_style = kCompactionStyleUniversal; - opts.level0_file_num_compaction_trigger = 2; - opts.compression = kNoCompression; - opts.allow_ingest_behind = true; - opts.compaction_options_universal.size_ratio = 10; - opts.compaction_options_universal.min_merge_width = 2; - opts.compaction_options_universal.max_size_amplification_percent = 200; - Reopen(opts); - - // add an L1 file to prevent tombstones from dropping due to obsolescence - // during flush - int i; - for (i = 0; i < 2000; ++i) { - ASSERT_OK(Put(Key(i), "val")); - } - ASSERT_OK(Flush()); - // MoveFilesToLevel(6); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - for (i = 1999; i < kNumKeys; ++i) { - if (i >= kNumKeys - kWindowSize && - i < kNumKeys - kWindowSize + kNumDelsTrigger) { - ASSERT_OK(Delete(Key(i))); - } else { - ASSERT_OK(Put(Key(i), "val")); - } - } - ASSERT_OK(Flush()); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(6)); - ASSERT_GT(NumTableFilesAtLevel(5), 0); -} - -TEST_F(DBTestUniversalCompaction2, PeriodicCompactionDefault) { - Options options; - options.compaction_style = kCompactionStyleUniversal; - options.env = env_; - KeepFilterFactory* filter = new KeepFilterFactory(true); - options.compaction_filter_factory.reset(filter); - Reopen(options); - ASSERT_EQ(30 * 24 * 60 * 60, - dbfull()->GetOptions().periodic_compaction_seconds); - - KeepFilter df; - options.compaction_filter_factory.reset(); - options.compaction_filter = &df; - Reopen(options); - ASSERT_EQ(30 * 24 * 60 * 60, - dbfull()->GetOptions().periodic_compaction_seconds); - - options.ttl = 60 * 24 * 60 * 60; - options.compaction_filter = nullptr; - Reopen(options); - ASSERT_EQ(60 * 24 * 60 * 60, - dbfull()->GetOptions().periodic_compaction_seconds); -} - -TEST_F(DBTestUniversalCompaction2, PeriodicCompaction) { - Options opts = CurrentOptions(); - opts.env = env_; - opts.compaction_style = kCompactionStyleUniversal; - opts.level0_file_num_compaction_trigger = 10; - opts.max_open_files = -1; - opts.compaction_options_universal.size_ratio = 10; - opts.compaction_options_universal.min_merge_width = 2; - opts.compaction_options_universal.max_size_amplification_percent = 200; - opts.periodic_compaction_seconds = 48 * 60 * 60; // 2 days - opts.num_levels = 5; - env_->SetMockSleep(); - Reopen(opts); - - // NOTE: Presumed unnecessary and removed: resetting mock time in env - - int periodic_compactions = 0; - int start_level = -1; - int output_level = -1; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UniversalCompactionPicker::PickPeriodicCompaction:Return", - [&](void* arg) { - Compaction* compaction = reinterpret_cast(arg); - ASSERT_TRUE(arg != nullptr); - ASSERT_TRUE(compaction->compaction_reason() == - CompactionReason::kPeriodicCompaction); - start_level = compaction->start_level(); - output_level = compaction->output_level(); - periodic_compactions++; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Case 1: Oldest flushed file excceeds periodic compaction threshold. - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - ASSERT_EQ(0, periodic_compactions); - // Move clock forward so that the flushed file would qualify periodic - // compaction. - env_->MockSleepForSeconds(48 * 60 * 60 + 100); - - // Another flush would trigger compaction the oldest file. - ASSERT_OK(Put("foo", "bar2")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(1, periodic_compactions); - ASSERT_EQ(0, start_level); - ASSERT_EQ(4, output_level); - - // Case 2: Oldest compacted file excceeds periodic compaction threshold - periodic_compactions = 0; - // A flush doesn't trigger a periodic compaction when threshold not hit - ASSERT_OK(Put("foo", "bar2")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, periodic_compactions); - - // After periodic compaction threshold hits, a flush will trigger - // a compaction - ASSERT_OK(Put("foo", "bar2")); - env_->MockSleepForSeconds(48 * 60 * 60 + 100); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(1, periodic_compactions); - ASSERT_EQ(0, start_level); - ASSERT_EQ(4, output_level); -} - -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_wal_test.cc b/db/db_wal_test.cc deleted file mode 100644 index 705f53f90..000000000 --- a/db/db_wal_test.cc +++ /dev/null @@ -1,2408 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "options/options_helper.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/file_system.h" -#include "test_util/sync_point.h" -#include "utilities/fault_injection_env.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { -class DBWALTestBase : public DBTestBase { - protected: - explicit DBWALTestBase(const std::string& dir_name) - : DBTestBase(dir_name, /*env_do_fsync=*/true) {} - -#if defined(ROCKSDB_PLATFORM_POSIX) - public: -#if defined(ROCKSDB_FALLOCATE_PRESENT) - bool IsFallocateSupported() { - // Test fallocate support of running file system. - // Skip this test if fallocate is not supported. - std::string fname_test_fallocate = dbname_ + "/preallocate_testfile"; - int fd = -1; - do { - fd = open(fname_test_fallocate.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644); - } while (fd < 0 && errno == EINTR); - assert(fd > 0); - int alloc_status = fallocate(fd, 0, 0, 1); - int err_number = errno; - close(fd); - assert(env_->DeleteFile(fname_test_fallocate) == Status::OK()); - if (err_number == ENOSYS || err_number == EOPNOTSUPP) { - fprintf(stderr, "Skipped preallocated space check: %s\n", - errnoStr(err_number).c_str()); - return false; - } - assert(alloc_status == 0); - return true; - } -#endif // ROCKSDB_FALLOCATE_PRESENT - - uint64_t GetAllocatedFileSize(std::string file_name) { - struct stat sbuf; - int err = stat(file_name.c_str(), &sbuf); - assert(err == 0); - return sbuf.st_blocks * 512; - } -#endif // ROCKSDB_PLATFORM_POSIX -}; - -class DBWALTest : public DBWALTestBase { - public: - DBWALTest() : DBWALTestBase("/db_wal_test") {} -}; - -// A SpecialEnv enriched to give more insight about deleted files -class EnrichedSpecialEnv : public SpecialEnv { - public: - explicit EnrichedSpecialEnv(Env* base) : SpecialEnv(base) {} - Status NewSequentialFile(const std::string& f, - std::unique_ptr* r, - const EnvOptions& soptions) override { - InstrumentedMutexLock l(&env_mutex_); - if (f == skipped_wal) { - deleted_wal_reopened = true; - if (IsWAL(f) && largest_deleted_wal.size() != 0 && - f.compare(largest_deleted_wal) <= 0) { - gap_in_wals = true; - } - } - return SpecialEnv::NewSequentialFile(f, r, soptions); - } - Status DeleteFile(const std::string& fname) override { - if (IsWAL(fname)) { - deleted_wal_cnt++; - InstrumentedMutexLock l(&env_mutex_); - // If this is the first WAL, remember its name and skip deleting it. We - // remember its name partly because the application might attempt to - // delete the file again. - if (skipped_wal.size() != 0 && skipped_wal != fname) { - if (largest_deleted_wal.size() == 0 || - largest_deleted_wal.compare(fname) < 0) { - largest_deleted_wal = fname; - } - } else { - skipped_wal = fname; - return Status::OK(); - } - } - return SpecialEnv::DeleteFile(fname); - } - bool IsWAL(const std::string& fname) { - // printf("iswal %s\n", fname.c_str()); - return fname.compare(fname.size() - 3, 3, "log") == 0; - } - - InstrumentedMutex env_mutex_; - // the wal whose actual delete was skipped by the env - std::string skipped_wal = ""; - // the largest WAL that was requested to be deleted - std::string largest_deleted_wal = ""; - // number of WALs that were successfully deleted - std::atomic deleted_wal_cnt = {0}; - // the WAL whose delete from fs was skipped is reopened during recovery - std::atomic deleted_wal_reopened = {false}; - // whether a gap in the WALs was detected during recovery - std::atomic gap_in_wals = {false}; -}; - -class DBWALTestWithEnrichedEnv : public DBTestBase { - public: - DBWALTestWithEnrichedEnv() - : DBTestBase("db_wal_test", /*env_do_fsync=*/true) { - enriched_env_ = new EnrichedSpecialEnv(env_->target()); - auto options = CurrentOptions(); - options.env = enriched_env_; - options.allow_2pc = true; - Reopen(options); - delete env_; - // to be deleted by the parent class - env_ = enriched_env_; - } - - protected: - EnrichedSpecialEnv* enriched_env_; -}; - -// Test that the recovery would successfully avoid the gaps between the logs. -// One known scenario that could cause this is that the application issue the -// WAL deletion out of order. For the sake of simplicity in the test, here we -// create the gap by manipulating the env to skip deletion of the first WAL but -// not the ones after it. -TEST_F(DBWALTestWithEnrichedEnv, SkipDeletedWALs) { - auto options = last_options_; - // To cause frequent WAL deletion - options.write_buffer_size = 128; - Reopen(options); - - WriteOptions writeOpt = WriteOptions(); - for (int i = 0; i < 128 * 5; i++) { - ASSERT_OK(dbfull()->Put(writeOpt, "foo", "v1")); - } - FlushOptions fo; - fo.wait = true; - ASSERT_OK(db_->Flush(fo)); - - // some wals are deleted - ASSERT_NE(0, enriched_env_->deleted_wal_cnt); - // but not the first one - ASSERT_NE(0, enriched_env_->skipped_wal.size()); - - // Test that the WAL that was not deleted will be skipped during recovery - options = last_options_; - Reopen(options); - ASSERT_FALSE(enriched_env_->deleted_wal_reopened); - ASSERT_FALSE(enriched_env_->gap_in_wals); -} - -TEST_F(DBWALTest, WAL) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1")); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1")); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v1", Get(1, "bar")); - - writeOpt.disableWAL = false; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v2")); - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v2")); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - // Both value's should be present. - ASSERT_EQ("v2", Get(1, "bar")); - ASSERT_EQ("v2", Get(1, "foo")); - - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v3")); - writeOpt.disableWAL = false; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v3")); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - // again both values should be present. - ASSERT_EQ("v3", Get(1, "foo")); - ASSERT_EQ("v3", Get(1, "bar")); - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, RollLog) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Put(1, "baz", "v5")); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - for (int i = 0; i < 10; i++) { - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - } - ASSERT_OK(Put(1, "foo", "v4")); - for (int i = 0; i < 10; i++) { - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - } - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, SyncWALNotBlockWrite) { - Options options = CurrentOptions(); - options.max_write_buffer_number = 4; - DestroyAndReopen(options); - - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("foo5", "bar5")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"WritableFileWriter::SyncWithoutFlush:1", - "DBWALTest::SyncWALNotBlockWrite:1"}, - {"DBWALTest::SyncWALNotBlockWrite:2", - "WritableFileWriter::SyncWithoutFlush:2"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread thread([&]() { ASSERT_OK(db_->SyncWAL()); }); - - TEST_SYNC_POINT("DBWALTest::SyncWALNotBlockWrite:1"); - ASSERT_OK(Put("foo2", "bar2")); - ASSERT_OK(Put("foo3", "bar3")); - FlushOptions fo; - fo.wait = false; - ASSERT_OK(db_->Flush(fo)); - ASSERT_OK(Put("foo4", "bar4")); - - TEST_SYNC_POINT("DBWALTest::SyncWALNotBlockWrite:2"); - - thread.join(); - - ASSERT_EQ(Get("foo1"), "bar1"); - ASSERT_EQ(Get("foo2"), "bar2"); - ASSERT_EQ(Get("foo3"), "bar3"); - ASSERT_EQ(Get("foo4"), "bar4"); - ASSERT_EQ(Get("foo5"), "bar5"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBWALTest, SyncWALNotWaitWrite) { - ASSERT_OK(Put("foo1", "bar1")); - ASSERT_OK(Put("foo3", "bar3")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"SpecialEnv::WalFile::Append:1", "DBWALTest::SyncWALNotWaitWrite:1"}, - {"DBWALTest::SyncWALNotWaitWrite:2", "SpecialEnv::WalFile::Append:2"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ROCKSDB_NAMESPACE::port::Thread thread( - [&]() { ASSERT_OK(Put("foo2", "bar2")); }); - // Moving this to SyncWAL before the actual fsync - // TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:1"); - ASSERT_OK(db_->SyncWAL()); - // Moving this to SyncWAL after actual fsync - // TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:2"); - - thread.join(); - - ASSERT_EQ(Get("foo1"), "bar1"); - ASSERT_EQ(Get("foo2"), "bar2"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBWALTest, Recover) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Put(1, "baz", "v5")); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v5", Get(1, "baz")); - ASSERT_OK(Put(1, "bar", "v2")); - ASSERT_OK(Put(1, "foo", "v3")); - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("v3", Get(1, "foo")); - ASSERT_OK(Put(1, "foo", "v4")); - ASSERT_EQ("v4", Get(1, "foo")); - ASSERT_EQ("v2", Get(1, "bar")); - ASSERT_EQ("v5", Get(1, "baz")); - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, RecoverWithTableHandle) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.avoid_flush_during_recovery = false; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Put(1, "bar", "v2")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "foo", "v3")); - ASSERT_OK(Put(1, "bar", "v4")); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(1, "big", std::string(100, 'a'))); - - options = CurrentOptions(); - const int kSmallMaxOpenFiles = 13; - if (option_config_ == kDBLogDir) { - // Use this option to check not preloading files - // Set the max open files to be small enough so no preload will - // happen. - options.max_open_files = kSmallMaxOpenFiles; - // RocksDB sanitize max open files to at least 20. Modify it back. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = static_cast(arg); - *max_open_files = kSmallMaxOpenFiles; - }); - - } else if (option_config_ == kWalDirAndMmapReads) { - // Use this option to check always loading all files. - options.max_open_files = 100; - } else { - options.max_open_files = -1; - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - std::vector> files; - dbfull()->TEST_GetFilesMetaData(handles_[1], &files); - size_t total_files = 0; - for (const auto& level : files) { - total_files += level.size(); - } - ASSERT_EQ(total_files, 3); - for (const auto& level : files) { - for (const auto& file : level) { - if (options.max_open_files == kSmallMaxOpenFiles) { - ASSERT_TRUE(file.table_reader_handle == nullptr); - } else { - ASSERT_TRUE(file.table_reader_handle != nullptr); - } - } - } - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, RecoverWithBlob) { - // Write a value that's below the prospective size limit for blobs and another - // one that's above. Note that blob files are not actually enabled at this - // point. - constexpr uint64_t min_blob_size = 10; - - constexpr char short_value[] = "short"; - static_assert(sizeof(short_value) - 1 < min_blob_size, - "short_value too long"); - - constexpr char long_value[] = "long_value"; - static_assert(sizeof(long_value) - 1 >= min_blob_size, - "long_value too short"); - - ASSERT_OK(Put("key1", short_value)); - ASSERT_OK(Put("key2", long_value)); - - // There should be no files just yet since we haven't flushed. - { - VersionSet* const versions = dbfull()->GetVersionSet(); - ASSERT_NE(versions, nullptr); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - ASSERT_EQ(storage_info->num_non_empty_levels(), 0); - ASSERT_TRUE(storage_info->GetBlobFiles().empty()); - } - - // Reopen the database with blob files enabled. A new table file/blob file - // pair should be written during recovery. - Options options; - options.enable_blob_files = true; - options.min_blob_size = min_blob_size; - options.avoid_flush_during_recovery = false; - options.disable_auto_compactions = true; - options.env = env_; - - Reopen(options); - - ASSERT_EQ(Get("key1"), short_value); - ASSERT_EQ(Get("key2"), long_value); - - VersionSet* const versions = dbfull()->GetVersionSet(); - ASSERT_NE(versions, nullptr); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - const auto& l0_files = storage_info->LevelFiles(0); - ASSERT_EQ(l0_files.size(), 1); - - const FileMetaData* const table_file = l0_files[0]; - ASSERT_NE(table_file, nullptr); - - const auto& blob_files = storage_info->GetBlobFiles(); - ASSERT_EQ(blob_files.size(), 1); - - const auto& blob_file = blob_files.front(); - ASSERT_NE(blob_file, nullptr); - - ASSERT_EQ(table_file->smallest.user_key(), "key1"); - ASSERT_EQ(table_file->largest.user_key(), "key2"); - ASSERT_EQ(table_file->fd.smallest_seqno, 1); - ASSERT_EQ(table_file->fd.largest_seqno, 2); - ASSERT_EQ(table_file->oldest_blob_file_number, - blob_file->GetBlobFileNumber()); - - ASSERT_EQ(blob_file->GetTotalBlobCount(), 1); - - const InternalStats* const internal_stats = cfd->internal_stats(); - ASSERT_NE(internal_stats, nullptr); - - const auto& compaction_stats = internal_stats->TEST_GetCompactionStats(); - ASSERT_FALSE(compaction_stats.empty()); - ASSERT_EQ(compaction_stats[0].bytes_written, table_file->fd.GetFileSize()); - ASSERT_EQ(compaction_stats[0].bytes_written_blob, - blob_file->GetTotalBlobBytes()); - ASSERT_EQ(compaction_stats[0].num_output_files, 1); - ASSERT_EQ(compaction_stats[0].num_output_files_blob, 1); - - const uint64_t* const cf_stats_value = internal_stats->TEST_GetCFStatsValue(); - ASSERT_EQ(cf_stats_value[InternalStats::BYTES_FLUSHED], - compaction_stats[0].bytes_written + - compaction_stats[0].bytes_written_blob); -} - -TEST_F(DBWALTest, RecoverWithBlobMultiSST) { - // Write several large (4 KB) values without flushing. Note that blob files - // are not actually enabled at this point. - std::string large_value(1 << 12, 'a'); - - constexpr int num_keys = 64; - - for (int i = 0; i < num_keys; ++i) { - ASSERT_OK(Put(Key(i), large_value)); - } - - // There should be no files just yet since we haven't flushed. - { - VersionSet* const versions = dbfull()->GetVersionSet(); - ASSERT_NE(versions, nullptr); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - ASSERT_EQ(storage_info->num_non_empty_levels(), 0); - ASSERT_TRUE(storage_info->GetBlobFiles().empty()); - } - - // Reopen the database with blob files enabled and write buffer size set to a - // smaller value. Multiple table files+blob files should be written and added - // to the Version during recovery. - Options options; - options.write_buffer_size = 1 << 16; // 64 KB - options.enable_blob_files = true; - options.avoid_flush_during_recovery = false; - options.disable_auto_compactions = true; - options.env = env_; - - Reopen(options); - - for (int i = 0; i < num_keys; ++i) { - ASSERT_EQ(Get(Key(i)), large_value); - } - - VersionSet* const versions = dbfull()->GetVersionSet(); - ASSERT_NE(versions, nullptr); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - ASSERT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - ASSERT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - ASSERT_NE(storage_info, nullptr); - - const auto& l0_files = storage_info->LevelFiles(0); - ASSERT_GT(l0_files.size(), 1); - - const auto& blob_files = storage_info->GetBlobFiles(); - ASSERT_GT(blob_files.size(), 1); - - ASSERT_EQ(l0_files.size(), blob_files.size()); -} - -TEST_F(DBWALTest, WALWithChecksumHandoff) { -#ifndef ROCKSDB_ASSERT_STATUS_CHECKED - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - do { - Options options = CurrentOptions(); - - options.checksum_handoff_file_types.Add(FileType::kWalFile); - options.env = fault_fs_env.get(); - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - - CreateAndReopenWithCF({"pikachu"}, options); - WriteOptions writeOpt = WriteOptions(); - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1")); - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1")); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("v1", Get(1, "foo")); - ASSERT_EQ("v1", Get(1, "bar")); - - writeOpt.disableWAL = false; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v2")); - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v2")); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - // Both value's should be present. - ASSERT_EQ("v2", Get(1, "bar")); - ASSERT_EQ("v2", Get(1, "foo")); - - writeOpt.disableWAL = true; - // This put, data is persisted by Flush - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v3")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - writeOpt.disableWAL = false; - // Data is persisted in the WAL - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "zoo", "v3")); - // The hash does not match, write fails - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash); - writeOpt.disableWAL = false; - ASSERT_NOK(dbfull()->Put(writeOpt, handles_[1], "foo", "v3")); - - ReopenWithColumnFamilies({"default", "pikachu"}, options); - // Due to the write failure, Get should not find - ASSERT_NE("v3", Get(1, "foo")); - ASSERT_EQ("v3", Get(1, "zoo")); - ASSERT_EQ("v3", Get(1, "bar")); - - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - // Each write will be similated as corrupted. - fault_fs->IngestDataCorruptionBeforeWrite(); - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v4")); - writeOpt.disableWAL = false; - ASSERT_NOK(dbfull()->Put(writeOpt, handles_[1], "foo", "v4")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_NE("v4", Get(1, "foo")); - ASSERT_NE("v4", Get(1, "bar")); - fault_fs->NoDataCorruptionBeforeWrite(); - - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kNoChecksum); - // The file system does not provide checksum method and verification. - writeOpt.disableWAL = true; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v5")); - writeOpt.disableWAL = false; - ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v5")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ("v5", Get(1, "foo")); - ASSERT_EQ("v5", Get(1, "bar")); - - Destroy(options); - } while (ChangeWalOptions()); -#endif // ROCKSDB_ASSERT_STATUS_CHECKED -} - -TEST_F(DBWALTest, LockWal) { - do { - Options options = CurrentOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - - ASSERT_OK(Put("foo", "v")); - ASSERT_OK(Put("bar", "v")); - - ASSERT_OK(db_->LockWAL()); - // Verify writes are stopped - WriteOptions wopts; - wopts.no_slowdown = true; - Status s = db_->Put(wopts, "foo", "dontcare"); - ASSERT_TRUE(s.IsIncomplete()); - { - VectorLogPtr wals; - ASSERT_OK(db_->GetSortedWalFiles(wals)); - ASSERT_FALSE(wals.empty()); - } - port::Thread worker([&]() { - Status tmp_s = db_->Flush(FlushOptions()); - ASSERT_OK(tmp_s); - }); - FlushOptions flush_opts; - flush_opts.wait = false; - s = db_->Flush(flush_opts); - ASSERT_TRUE(s.IsTryAgain()); - ASSERT_OK(db_->UnlockWAL()); - ASSERT_OK(db_->Put(WriteOptions(), "foo", "dontcare")); - - worker.join(); - } while (ChangeWalOptions()); -} - -class DBRecoveryTestBlobError - : public DBWALTest, - public testing::WithParamInterface { - public: - DBRecoveryTestBlobError() : sync_point_(GetParam()) {} - - std::string sync_point_; -}; - -INSTANTIATE_TEST_CASE_P(DBRecoveryTestBlobError, DBRecoveryTestBlobError, - ::testing::ValuesIn(std::vector{ - "BlobFileBuilder::WriteBlobToFile:AddRecord", - "BlobFileBuilder::WriteBlobToFile:AppendFooter"})); - -TEST_P(DBRecoveryTestBlobError, RecoverWithBlobError) { - // Write a value. Note that blob files are not actually enabled at this point. - ASSERT_OK(Put("key", "blob")); - - // Reopen with blob files enabled but make blob file writing fail during - // recovery. - SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) { - Status* const s = static_cast(arg); - assert(s); - - (*s) = Status::IOError(sync_point_); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Options options; - options.enable_blob_files = true; - options.avoid_flush_during_recovery = false; - options.disable_auto_compactions = true; - options.env = env_; - - ASSERT_NOK(TryReopen(options)); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - // Make sure the files generated by the failed recovery have been deleted. - std::vector files; - ASSERT_OK(env_->GetChildren(dbname_, &files)); - for (const auto& file : files) { - uint64_t number = 0; - FileType type = kTableFile; - - if (!ParseFileName(file, &number, &type)) { - continue; - } - - ASSERT_NE(type, kTableFile); - ASSERT_NE(type, kBlobFile); - } -} - -TEST_F(DBWALTest, IgnoreRecoveredLog) { - std::string backup_logs = dbname_ + "/backup_logs"; - - do { - // delete old files in backup_logs directory - ASSERT_OK(env_->CreateDirIfMissing(backup_logs)); - std::vector old_files; - ASSERT_OK(env_->GetChildren(backup_logs, &old_files)); - for (auto& file : old_files) { - ASSERT_OK(env_->DeleteFile(backup_logs + "/" + file)); - } - Options options = CurrentOptions(); - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreateUInt64AddOperator(); - options.wal_dir = dbname_ + "/logs"; - DestroyAndReopen(options); - - // fill up the DB - std::string one, two; - PutFixed64(&one, 1); - PutFixed64(&two, 2); - ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one))); - ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one))); - ASSERT_OK(db_->Merge(WriteOptions(), Slice("bar"), Slice(one))); - - // copy the logs to backup - std::vector logs; - ASSERT_OK(env_->GetChildren(options.wal_dir, &logs)); - for (auto& log : logs) { - CopyFile(options.wal_dir + "/" + log, backup_logs + "/" + log); - } - - // recover the DB - Reopen(options); - ASSERT_EQ(two, Get("foo")); - ASSERT_EQ(one, Get("bar")); - Close(); - - // copy the logs from backup back to wal dir - for (auto& log : logs) { - CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log); - } - // this should ignore the log files, recovery should not happen again - // if the recovery happens, the same merge operator would be called twice, - // leading to incorrect results - Reopen(options); - ASSERT_EQ(two, Get("foo")); - ASSERT_EQ(one, Get("bar")); - Close(); - Destroy(options); - Reopen(options); - Close(); - - // copy the logs from backup back to wal dir - ASSERT_OK(env_->CreateDirIfMissing(options.wal_dir)); - for (auto& log : logs) { - CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log); - } - // assert that we successfully recovered only from logs, even though we - // destroyed the DB - Reopen(options); - ASSERT_EQ(two, Get("foo")); - ASSERT_EQ(one, Get("bar")); - - // Recovery will fail if DB directory doesn't exist. - Destroy(options); - // copy the logs from backup back to wal dir - ASSERT_OK(env_->CreateDirIfMissing(options.wal_dir)); - for (auto& log : logs) { - CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log); - // we won't be needing this file no more - ASSERT_OK(env_->DeleteFile(backup_logs + "/" + log)); - } - Status s = TryReopen(options); - ASSERT_NOK(s); - Destroy(options); - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, RecoveryWithEmptyLog) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Put(1, "foo", "v2")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v3")); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - ASSERT_EQ("v3", Get(1, "foo")); - } while (ChangeWalOptions()); -} - -#if !(defined NDEBUG) || !defined(OS_WIN) -TEST_F(DBWALTest, PreallocateBlock) { - Options options = CurrentOptions(); - options.write_buffer_size = 10 * 1000 * 1000; - options.max_total_wal_size = 0; - - size_t expected_preallocation_size = static_cast( - options.write_buffer_size + options.write_buffer_size / 10); - - DestroyAndReopen(options); - - std::atomic called(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBTestWalFile.GetPreallocationStatus", [&](void* arg) { - ASSERT_TRUE(arg != nullptr); - size_t preallocation_size = *(static_cast(arg)); - ASSERT_EQ(expected_preallocation_size, preallocation_size); - called.fetch_add(1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put("", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("", "")); - Close(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(2, called.load()); - - options.max_total_wal_size = 1000 * 1000; - expected_preallocation_size = static_cast(options.max_total_wal_size); - Reopen(options); - called.store(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBTestWalFile.GetPreallocationStatus", [&](void* arg) { - ASSERT_TRUE(arg != nullptr); - size_t preallocation_size = *(static_cast(arg)); - ASSERT_EQ(expected_preallocation_size, preallocation_size); - called.fetch_add(1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put("", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("", "")); - Close(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(2, called.load()); - - options.db_write_buffer_size = 800 * 1000; - expected_preallocation_size = - static_cast(options.db_write_buffer_size); - Reopen(options); - called.store(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBTestWalFile.GetPreallocationStatus", [&](void* arg) { - ASSERT_TRUE(arg != nullptr); - size_t preallocation_size = *(static_cast(arg)); - ASSERT_EQ(expected_preallocation_size, preallocation_size); - called.fetch_add(1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put("", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("", "")); - Close(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(2, called.load()); - - expected_preallocation_size = 700 * 1000; - std::shared_ptr write_buffer_manager = - std::make_shared(static_cast(700 * 1000)); - options.write_buffer_manager = write_buffer_manager; - Reopen(options); - called.store(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBTestWalFile.GetPreallocationStatus", [&](void* arg) { - ASSERT_TRUE(arg != nullptr); - size_t preallocation_size = *(static_cast(arg)); - ASSERT_EQ(expected_preallocation_size, preallocation_size); - called.fetch_add(1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(Put("", "")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("", "")); - Close(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_EQ(2, called.load()); -} -#endif // !(defined NDEBUG) || !defined(OS_WIN) - -TEST_F(DBWALTest, DISABLED_FullPurgePreservesRecycledLog) { - // TODO(ajkr): Disabled until WAL recycling is fixed for - // `kPointInTimeRecovery`. - - // For github issue #1303 - for (int i = 0; i < 2; ++i) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.recycle_log_file_num = 2; - if (i != 0) { - options.wal_dir = alternative_wal_dir_; - } - - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "v1")); - VectorLogPtr log_files; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files)); - ASSERT_GT(log_files.size(), 0); - ASSERT_OK(Flush()); - - // Now the original WAL is in log_files[0] and should be marked for - // recycling. - // Verify full purge cannot remove this file. - JobContext job_context(0); - dbfull()->TEST_LockMutex(); - dbfull()->FindObsoleteFiles(&job_context, true /* force */); - dbfull()->TEST_UnlockMutex(); - dbfull()->PurgeObsoleteFiles(job_context); - - if (i == 0) { - ASSERT_OK( - env_->FileExists(LogFileName(dbname_, log_files[0]->LogNumber()))); - } else { - ASSERT_OK(env_->FileExists( - LogFileName(alternative_wal_dir_, log_files[0]->LogNumber()))); - } - } -} - -TEST_F(DBWALTest, DISABLED_FullPurgePreservesLogPendingReuse) { - // TODO(ajkr): Disabled until WAL recycling is fixed for - // `kPointInTimeRecovery`. - - // Ensures full purge cannot delete a WAL while it's in the process of being - // recycled. In particular, we force the full purge after a file has been - // chosen for reuse, but before it has been renamed. - for (int i = 0; i < 2; ++i) { - Options options = CurrentOptions(); - options.recycle_log_file_num = 1; - if (i != 0) { - options.wal_dir = alternative_wal_dir_; - } - DestroyAndReopen(options); - - // The first flush creates a second log so writes can continue before the - // flush finishes. - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - - // The second flush can recycle the first log. Sync points enforce the - // full purge happens after choosing the log to recycle and before it is - // renamed. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::CreateWAL:BeforeReuseWritableFile1", - "DBWALTest::FullPurgePreservesLogPendingReuse:PreFullPurge"}, - {"DBWALTest::FullPurgePreservesLogPendingReuse:PostFullPurge", - "DBImpl::CreateWAL:BeforeReuseWritableFile2"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ROCKSDB_NAMESPACE::port::Thread thread([&]() { - TEST_SYNC_POINT( - "DBWALTest::FullPurgePreservesLogPendingReuse:PreFullPurge"); - ASSERT_OK(db_->EnableFileDeletions(true)); - TEST_SYNC_POINT( - "DBWALTest::FullPurgePreservesLogPendingReuse:PostFullPurge"); - }); - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Flush()); - thread.join(); - } -} - -TEST_F(DBWALTest, GetSortedWalFiles) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - VectorLogPtr log_files; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files)); - ASSERT_EQ(0, log_files.size()); - - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files)); - ASSERT_EQ(1, log_files.size()); - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, GetCurrentWalFile) { - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - - std::unique_ptr* bad_log_file = nullptr; - ASSERT_NOK(dbfull()->GetCurrentWalFile(bad_log_file)); - - std::unique_ptr log_file; - ASSERT_OK(dbfull()->GetCurrentWalFile(&log_file)); - - // nothing has been written to the log yet - ASSERT_EQ(log_file->StartSequence(), 0); - ASSERT_EQ(log_file->SizeFileBytes(), 0); - ASSERT_EQ(log_file->Type(), kAliveLogFile); - ASSERT_GT(log_file->LogNumber(), 0); - - // add some data and verify that the file size actually moves foward - ASSERT_OK(Put(0, "foo", "v1")); - ASSERT_OK(Put(0, "foo2", "v2")); - ASSERT_OK(Put(0, "foo3", "v3")); - - ASSERT_OK(dbfull()->GetCurrentWalFile(&log_file)); - - ASSERT_EQ(log_file->StartSequence(), 0); - ASSERT_GT(log_file->SizeFileBytes(), 0); - ASSERT_EQ(log_file->Type(), kAliveLogFile); - ASSERT_GT(log_file->LogNumber(), 0); - - // force log files to cycle and add some more data, then check if - // log number moves forward - - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - for (int i = 0; i < 10; i++) { - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - } - - ASSERT_OK(Put(0, "foo4", "v4")); - ASSERT_OK(Put(0, "foo5", "v5")); - ASSERT_OK(Put(0, "foo6", "v6")); - - ASSERT_OK(dbfull()->GetCurrentWalFile(&log_file)); - - ASSERT_EQ(log_file->StartSequence(), 0); - ASSERT_GT(log_file->SizeFileBytes(), 0); - ASSERT_EQ(log_file->Type(), kAliveLogFile); - ASSERT_GT(log_file->LogNumber(), 0); - - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, RecoveryWithLogDataForSomeCFs) { - // Test for regression of WAL cleanup missing files that don't contain data - // for every column family. - do { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ASSERT_OK(Put(1, "foo", "v1")); - ASSERT_OK(Put(1, "foo", "v2")); - uint64_t earliest_log_nums[2]; - for (int i = 0; i < 2; ++i) { - if (i > 0) { - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); - } - VectorLogPtr log_files; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files)); - if (log_files.size() > 0) { - earliest_log_nums[i] = log_files[0]->LogNumber(); - } else { - earliest_log_nums[i] = std::numeric_limits::max(); - } - } - // Check at least the first WAL was cleaned up during the recovery. - ASSERT_LT(earliest_log_nums[0], earliest_log_nums[1]); - } while (ChangeWalOptions()); -} - -TEST_F(DBWALTest, RecoverWithLargeLog) { - do { - { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"pikachu"}, options); - ASSERT_OK(Put(1, "big1", std::string(200000, '1'))); - ASSERT_OK(Put(1, "big2", std::string(200000, '2'))); - ASSERT_OK(Put(1, "small3", std::string(10, '3'))); - ASSERT_OK(Put(1, "small4", std::string(10, '4'))); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0); - } - - // Make sure that if we re-open with a small write buffer size that - // we flush table files in the middle of a large log file. - Options options; - options.write_buffer_size = 100000; - options = CurrentOptions(options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ(NumTableFilesAtLevel(0, 1), 3); - ASSERT_EQ(std::string(200000, '1'), Get(1, "big1")); - ASSERT_EQ(std::string(200000, '2'), Get(1, "big2")); - ASSERT_EQ(std::string(10, '3'), Get(1, "small3")); - ASSERT_EQ(std::string(10, '4'), Get(1, "small4")); - ASSERT_GT(NumTableFilesAtLevel(0, 1), 1); - } while (ChangeWalOptions()); -} - -// In https://reviews.facebook.net/D20661 we change -// recovery behavior: previously for each log file each column family -// memtable was flushed, even it was empty. Now it's changed: -// we try to create the smallest number of table files by merging -// updates from multiple logs -TEST_F(DBWALTest, RecoverCheckFileAmountWithSmallWriteBuffer) { - Options options = CurrentOptions(); - options.write_buffer_size = 5000000; - CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options); - - // Since we will reopen DB with smaller write_buffer_size, - // each key will go to new SST file - ASSERT_OK(Put(1, Key(10), DummyString(1000000))); - ASSERT_OK(Put(1, Key(10), DummyString(1000000))); - ASSERT_OK(Put(1, Key(10), DummyString(1000000))); - ASSERT_OK(Put(1, Key(10), DummyString(1000000))); - - ASSERT_OK(Put(3, Key(10), DummyString(1))); - // Make 'dobrynia' to be flushed and new WAL file to be created - ASSERT_OK(Put(2, Key(10), DummyString(7500000))); - ASSERT_OK(Put(2, Key(1), DummyString(1))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[2])); - { - auto tables = ListTableFiles(env_, dbname_); - ASSERT_EQ(tables.size(), static_cast(1)); - // Make sure 'dobrynia' was flushed: check sst files amount - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"), - static_cast(1)); - } - // New WAL file - ASSERT_OK(Put(1, Key(1), DummyString(1))); - ASSERT_OK(Put(1, Key(1), DummyString(1))); - ASSERT_OK(Put(3, Key(10), DummyString(1))); - ASSERT_OK(Put(3, Key(10), DummyString(1))); - ASSERT_OK(Put(3, Key(10), DummyString(1))); - - options.write_buffer_size = 4096; - options.arena_block_size = 4096; - ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"}, - options); - { - // No inserts => default is empty - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"), - static_cast(0)); - // First 4 keys goes to separate SSTs + 1 more SST for 2 smaller keys - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), - static_cast(5)); - // 1 SST for big key + 1 SST for small one - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"), - static_cast(2)); - // 1 SST for all keys - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"), - static_cast(1)); - } -} - -// In https://reviews.facebook.net/D20661 we change -// recovery behavior: previously for each log file each column family -// memtable was flushed, even it wasn't empty. Now it's changed: -// we try to create the smallest number of table files by merging -// updates from multiple logs -TEST_F(DBWALTest, RecoverCheckFileAmount) { - Options options = CurrentOptions(); - options.write_buffer_size = 100000; - options.arena_block_size = 4 * 1024; - options.avoid_flush_during_recovery = false; - CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options); - - ASSERT_OK(Put(0, Key(1), DummyString(1))); - ASSERT_OK(Put(1, Key(1), DummyString(1))); - ASSERT_OK(Put(2, Key(1), DummyString(1))); - - // Make 'nikitich' memtable to be flushed - ASSERT_OK(Put(3, Key(10), DummyString(1002400))); - ASSERT_OK(Put(3, Key(1), DummyString(1))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[3])); - // 4 memtable are not flushed, 1 sst file - { - auto tables = ListTableFiles(env_, dbname_); - ASSERT_EQ(tables.size(), static_cast(1)); - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"), - static_cast(1)); - } - // Memtable for 'nikitich' has flushed, new WAL file has opened - // 4 memtable still not flushed - - // Write to new WAL file - ASSERT_OK(Put(0, Key(1), DummyString(1))); - ASSERT_OK(Put(1, Key(1), DummyString(1))); - ASSERT_OK(Put(2, Key(1), DummyString(1))); - - // Fill up 'nikitich' one more time - ASSERT_OK(Put(3, Key(10), DummyString(1002400))); - // make it flush - ASSERT_OK(Put(3, Key(1), DummyString(1))); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[3])); - // There are still 4 memtable not flushed, and 2 sst tables - ASSERT_OK(Put(0, Key(1), DummyString(1))); - ASSERT_OK(Put(1, Key(1), DummyString(1))); - ASSERT_OK(Put(2, Key(1), DummyString(1))); - - { - auto tables = ListTableFiles(env_, dbname_); - ASSERT_EQ(tables.size(), static_cast(2)); - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"), - static_cast(2)); - } - - ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"}, - options); - { - std::vector table_files = ListTableFiles(env_, dbname_); - // Check, that records for 'default', 'dobrynia' and 'pikachu' from - // first, second and third WALs went to the same SST. - // So, there is 6 SSTs: three for 'nikitich', one for 'default', one for - // 'dobrynia', one for 'pikachu' - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"), - static_cast(1)); - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"), - static_cast(3)); - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"), - static_cast(1)); - ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), - static_cast(1)); - } -} - -TEST_F(DBWALTest, SyncMultipleLogs) { - const uint64_t kNumBatches = 2; - const int kBatchSize = 1000; - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.write_buffer_size = 4096; - Reopen(options); - - WriteBatch batch; - WriteOptions wo; - wo.sync = true; - - for (uint64_t b = 0; b < kNumBatches; b++) { - batch.Clear(); - for (int i = 0; i < kBatchSize; i++) { - ASSERT_OK(batch.Put(Key(i), DummyString(128))); - } - - ASSERT_OK(dbfull()->Write(wo, &batch)); - } - - ASSERT_OK(dbfull()->SyncWAL()); -} - -// Github issue 1339. Prior the fix we read sequence id from the first log to -// a local variable, then keep increase the variable as we replay logs, -// ignoring actual sequence id of the records. This is incorrect if some writes -// come with WAL disabled. -TEST_F(DBWALTest, PartOfWritesWithWALDisabled) { - std::unique_ptr fault_env( - new FaultInjectionTestEnv(env_)); - Options options = CurrentOptions(); - options.env = fault_env.get(); - options.disable_auto_compactions = true; - WriteOptions wal_on, wal_off; - wal_on.sync = true; - wal_on.disableWAL = false; - wal_off.disableWAL = true; - CreateAndReopenWithCF({"dummy"}, options); - ASSERT_OK(Put(1, "dummy", "d1", wal_on)); // seq id 1 - ASSERT_OK(Put(1, "dummy", "d2", wal_off)); - ASSERT_OK(Put(1, "dummy", "d3", wal_off)); - ASSERT_OK(Put(0, "key", "v4", wal_on)); // seq id 4 - ASSERT_OK(Flush(0)); - ASSERT_OK(Put(0, "key", "v5", wal_on)); // seq id 5 - ASSERT_EQ("v5", Get(0, "key")); - ASSERT_OK(dbfull()->FlushWAL(false)); - // Simulate a crash. - fault_env->SetFilesystemActive(false); - Close(); - fault_env->ResetState(); - ReopenWithColumnFamilies({"default", "dummy"}, options); - // Prior to the fix, we may incorrectly recover "v5" with sequence id = 3. - ASSERT_EQ("v5", Get(0, "key")); - // Destroy DB before destruct fault_env. - Destroy(options); -} - -// -// Test WAL recovery for the various modes available -// -class RecoveryTestHelper { - public: - // Number of WAL files to generate - static constexpr int kWALFilesCount = 10; - // Starting number for the WAL file name like 00010.log - static constexpr int kWALFileOffset = 10; - // Keys to be written per WAL file - static constexpr int kKeysPerWALFile = 133; - // Size of the value - static constexpr int kValueSize = 96; - - // Create WAL files with values filled in - static void FillData(DBWALTestBase* test, const Options& options, - const size_t wal_count, size_t* count) { - // Calling internal functions requires sanitized options. - Options sanitized_options = SanitizeOptions(test->dbname_, options); - const ImmutableDBOptions db_options(sanitized_options); - - *count = 0; - - std::shared_ptr table_cache = NewLRUCache(50, 0); - FileOptions file_options; - WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size); - - std::unique_ptr versions; - std::unique_ptr wal_manager; - WriteController write_controller; - - versions.reset(new VersionSet( - test->dbname_, &db_options, file_options, table_cache.get(), - &write_buffer_manager, &write_controller, - /*block_cache_tracer=*/nullptr, - /*io_tracer=*/nullptr, /*db_id*/ "", /*db_session_id*/ "")); - - wal_manager.reset( - new WalManager(db_options, file_options, /*io_tracer=*/nullptr)); - - std::unique_ptr current_log_writer; - - for (size_t j = kWALFileOffset; j < wal_count + kWALFileOffset; j++) { - uint64_t current_log_number = j; - std::string fname = LogFileName(test->dbname_, current_log_number); - std::unique_ptr file_writer; - ASSERT_OK(WritableFileWriter::Create(db_options.env->GetFileSystem(), - fname, file_options, &file_writer, - nullptr)); - log::Writer* log_writer = - new log::Writer(std::move(file_writer), current_log_number, - db_options.recycle_log_file_num > 0, false, - db_options.wal_compression); - ASSERT_OK(log_writer->AddCompressionTypeRecord()); - current_log_writer.reset(log_writer); - - WriteBatch batch; - for (int i = 0; i < kKeysPerWALFile; i++) { - std::string key = "key" + std::to_string((*count)++); - std::string value = test->DummyString(kValueSize); - ASSERT_NE(current_log_writer.get(), nullptr); - uint64_t seq = versions->LastSequence() + 1; - batch.Clear(); - ASSERT_OK(batch.Put(key, value)); - WriteBatchInternal::SetSequence(&batch, seq); - ASSERT_OK(current_log_writer->AddRecord( - WriteBatchInternal::Contents(&batch))); - versions->SetLastAllocatedSequence(seq); - versions->SetLastPublishedSequence(seq); - versions->SetLastSequence(seq); - } - } - } - - // Recreate and fill the store with some data - static size_t FillData(DBWALTestBase* test, Options* options) { - options->create_if_missing = true; - test->DestroyAndReopen(*options); - test->Close(); - - size_t count = 0; - FillData(test, *options, kWALFilesCount, &count); - return count; - } - - // Read back all the keys we wrote and return the number of keys found - static size_t GetData(DBWALTestBase* test) { - size_t count = 0; - for (size_t i = 0; i < kWALFilesCount * kKeysPerWALFile; i++) { - if (test->Get("key" + std::to_string(i)) != "NOT_FOUND") { - ++count; - } - } - return count; - } - - // Manuall corrupt the specified WAL - static void CorruptWAL(DBWALTestBase* test, const Options& options, - const double off, const double len, - const int wal_file_id, const bool trunc = false) { - Env* env = options.env; - std::string fname = LogFileName(test->dbname_, wal_file_id); - uint64_t size; - ASSERT_OK(env->GetFileSize(fname, &size)); - ASSERT_GT(size, 0); -#ifdef OS_WIN - // Windows disk cache behaves differently. When we truncate - // the original content is still in the cache due to the original - // handle is still open. Generally, in Windows, one prohibits - // shared access to files and it is not needed for WAL but we allow - // it to induce corruption at various tests. - test->Close(); -#endif - if (trunc) { - ASSERT_OK( - test::TruncateFile(env, fname, static_cast(size * off))); - } else { - ASSERT_OK(test::CorruptFile(env, fname, static_cast(size * off + 8), - static_cast(size * len), false)); - } - } -}; - -class DBWALTestWithParams : public DBWALTestBase, - public ::testing::WithParamInterface< - std::tuple> { - public: - DBWALTestWithParams() : DBWALTestBase("/db_wal_test_with_params") {} -}; - -INSTANTIATE_TEST_CASE_P( - Wal, DBWALTestWithParams, - ::testing::Combine(::testing::Bool(), ::testing::Range(0, 4, 1), - ::testing::Range(RecoveryTestHelper::kWALFileOffset, - RecoveryTestHelper::kWALFileOffset + - RecoveryTestHelper::kWALFilesCount, - 1), - ::testing::Values(CompressionType::kNoCompression, - CompressionType::kZSTD))); - -class DBWALTestWithParamsVaryingRecoveryMode - : public DBWALTestBase, - public ::testing::WithParamInterface< - std::tuple> { - public: - DBWALTestWithParamsVaryingRecoveryMode() - : DBWALTestBase("/db_wal_test_with_params_mode") {} -}; - -INSTANTIATE_TEST_CASE_P( - Wal, DBWALTestWithParamsVaryingRecoveryMode, - ::testing::Combine( - ::testing::Bool(), ::testing::Range(0, 4, 1), - ::testing::Range(RecoveryTestHelper::kWALFileOffset, - RecoveryTestHelper::kWALFileOffset + - RecoveryTestHelper::kWALFilesCount, - 1), - ::testing::Values(WALRecoveryMode::kTolerateCorruptedTailRecords, - WALRecoveryMode::kAbsoluteConsistency, - WALRecoveryMode::kPointInTimeRecovery, - WALRecoveryMode::kSkipAnyCorruptedRecords), - ::testing::Values(CompressionType::kNoCompression, - CompressionType::kZSTD))); - -// Test scope: -// - We expect to open the data store when there is incomplete trailing writes -// at the end of any of the logs -// - We do not expect to open the data store for corruption -TEST_P(DBWALTestWithParams, kTolerateCorruptedTailRecords) { - bool trunc = std::get<0>(GetParam()); // Corruption style - // Corruption offset position - int corrupt_offset = std::get<1>(GetParam()); - int wal_file_id = std::get<2>(GetParam()); // WAL file - - // Fill data for testing - Options options = CurrentOptions(); - const size_t row_count = RecoveryTestHelper::FillData(this, &options); - // test checksum failure or parsing - RecoveryTestHelper::CorruptWAL(this, options, corrupt_offset * .3, - /*len%=*/.1, wal_file_id, trunc); - - options.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords; - if (trunc) { - options.create_if_missing = false; - ASSERT_OK(TryReopen(options)); - const size_t recovered_row_count = RecoveryTestHelper::GetData(this); - ASSERT_TRUE(corrupt_offset == 0 || recovered_row_count > 0); - ASSERT_LT(recovered_row_count, row_count); - } else { - ASSERT_NOK(TryReopen(options)); - } -} - -// Test scope: -// We don't expect the data store to be opened if there is any corruption -// (leading, middle or trailing -- incomplete writes or corruption) -TEST_P(DBWALTestWithParams, kAbsoluteConsistency) { - // Verify clean slate behavior - Options options = CurrentOptions(); - const size_t row_count = RecoveryTestHelper::FillData(this, &options); - options.create_if_missing = false; - ASSERT_OK(TryReopen(options)); - ASSERT_EQ(RecoveryTestHelper::GetData(this), row_count); - - bool trunc = std::get<0>(GetParam()); // Corruption style - // Corruption offset position - int corrupt_offset = std::get<1>(GetParam()); - int wal_file_id = std::get<2>(GetParam()); // WAL file - // WAL compression type - CompressionType compression_type = std::get<3>(GetParam()); - options.wal_compression = compression_type; - - if (trunc && corrupt_offset == 0) { - return; - } - - // fill with new date - RecoveryTestHelper::FillData(this, &options); - // corrupt the wal - RecoveryTestHelper::CorruptWAL(this, options, corrupt_offset * .33, - /*len%=*/.1, wal_file_id, trunc); - // verify - options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency; - options.create_if_missing = false; - ASSERT_NOK(TryReopen(options)); -} - -// Test scope: -// We don't expect the data store to be opened if there is any inconsistency -// between WAL and SST files -TEST_F(DBWALTest, kPointInTimeRecoveryCFConsistency) { - Options options = CurrentOptions(); - options.avoid_flush_during_recovery = true; - - // Create DB with multiple column families. - CreateAndReopenWithCF({"one", "two"}, options); - ASSERT_OK(Put(1, "key1", "val1")); - ASSERT_OK(Put(2, "key2", "val2")); - - // Record the offset at this point - Env* env = options.env; - uint64_t wal_file_id = dbfull()->TEST_LogfileNumber(); - std::string fname = LogFileName(dbname_, wal_file_id); - uint64_t offset_to_corrupt; - ASSERT_OK(env->GetFileSize(fname, &offset_to_corrupt)); - ASSERT_GT(offset_to_corrupt, 0); - - ASSERT_OK(Put(1, "key3", "val3")); - // Corrupt WAL at location of key3 - ASSERT_OK(test::CorruptFile(env, fname, static_cast(offset_to_corrupt), - 4, false)); - ASSERT_OK(Put(2, "key4", "val4")); - ASSERT_OK(Put(1, "key5", "val5")); - ASSERT_OK(Flush(2)); - - // PIT recovery & verify - options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - ASSERT_NOK(TryReopenWithColumnFamilies({"default", "one", "two"}, options)); -} - -TEST_F(DBWALTest, RaceInstallFlushResultsWithWalObsoletion) { - Options options = CurrentOptions(); - options.env = env_; - options.track_and_verify_wals_in_manifest = true; - // The following make sure there are two bg flush threads. - options.max_background_jobs = 8; - - DestroyAndReopen(options); - - const std::string cf1_name("cf1"); - CreateAndReopenWithCF({cf1_name}, options); - assert(handles_.size() == 2); - - { - dbfull()->TEST_LockMutex(); - ASSERT_LE(2, dbfull()->GetBGJobLimits().max_flushes); - dbfull()->TEST_UnlockMutex(); - } - - ASSERT_OK(dbfull()->PauseBackgroundWork()); - - ASSERT_OK(db_->Put(WriteOptions(), handles_[1], "foo", "value")); - ASSERT_OK(db_->Put(WriteOptions(), "foo", "value")); - - ASSERT_OK(dbfull()->TEST_FlushMemTable( - /*wait=*/false, /*allow_write_stall=*/true, handles_[1])); - - ASSERT_OK(db_->Put(WriteOptions(), "foo", "value")); - - ASSERT_OK(dbfull()->TEST_FlushMemTable( - /*wait=*/false, /*allow_write_stall=*/true, handles_[0])); - - bool called = false; - std::atomic bg_flush_threads{0}; - std::atomic wal_synced{false}; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCallFlush:start", [&](void* /*arg*/) { - int cur = bg_flush_threads.load(); - int desired = cur + 1; - if (cur > 0 || - !bg_flush_threads.compare_exchange_strong(cur, desired)) { - while (!wal_synced.load()) { - // Wait until the other bg flush thread finishes committing WAL sync - // operation to the MANIFEST. - } - } - }); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushMemTableToOutputFile:CommitWal:1", - [&](void* /*arg*/) { wal_synced.store(true); }); - // This callback will be called when the first bg flush thread reaches the - // point before entering the MANIFEST write queue after flushing the SST - // file. - // The purpose of the sync points here is to ensure both bg flush threads - // finish computing `min_wal_number_to_keep` before any of them updates the - // `log_number` for the column family that's being flushed. - SyncPoint::GetInstance()->SetCallBack( - "MemTableList::TryInstallMemtableFlushResults:AfterComputeMinWalToKeep", - [&](void* /*arg*/) { - dbfull()->mutex()->AssertHeld(); - if (!called) { - // We are the first bg flush thread in the MANIFEST write queue. - // We set up the dependency between sync points for two threads that - // will be executing the same code. - // For the interleaving of events, see - // https://github.com/facebook/rocksdb/pull/9715. - // bg flush thread1 will release the db mutex while in the MANIFEST - // write queue. In the meantime, bg flush thread2 locks db mutex and - // computes the min_wal_number_to_keep (before thread1 writes to - // MANIFEST thus before cf1->log_number is updated). Bg thread2 joins - // the MANIFEST write queue afterwards and bg flush thread1 proceeds - // with writing to MANIFEST. - called = true; - SyncPoint::GetInstance()->LoadDependency({ - {"VersionSet::LogAndApply:WriteManifestStart", - "DBWALTest::RaceInstallFlushResultsWithWalObsoletion:BgFlush2"}, - {"DBWALTest::RaceInstallFlushResultsWithWalObsoletion:BgFlush2", - "VersionSet::LogAndApply:WriteManifest"}, - }); - } else { - // The other bg flush thread has already been in the MANIFEST write - // queue, and we are after. - TEST_SYNC_POINT( - "DBWALTest::RaceInstallFlushResultsWithWalObsoletion:BgFlush2"); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(dbfull()->ContinueBackgroundWork()); - - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[0])); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1])); - - ASSERT_TRUE(called); - - Close(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - DB* db1 = nullptr; - Status s = DB::OpenForReadOnly(options, dbname_, &db1); - ASSERT_OK(s); - assert(db1); - delete db1; -} - -TEST_F(DBWALTest, FixSyncWalOnObseletedWalWithNewManifestCausingMissingWAL) { - Options options = CurrentOptions(); - // Small size to force manifest creation - options.max_manifest_file_size = 1; - options.track_and_verify_wals_in_manifest = true; - DestroyAndReopen(options); - - // Accumulate memtable m1 and create the 1st wal (i.e, 4.log) - ASSERT_OK(Put(Key(1), "")); - ASSERT_OK(Put(Key(2), "")); - ASSERT_OK(Put(Key(3), "")); - - const std::string wal_file_path = db_->GetName() + "/000004.log"; - - // Coerce the following sequence of events: - // (1) Flush() marks 4.log to be obsoleted, 8.log to be the latest (i.e, - // active) log and release the lock - // (2) SyncWAL() proceeds with the lock. It - // creates a new manifest and syncs all the inactive wals before the latest - // (i.e, active log), which is 4.log. Note that SyncWAL() is not aware of the - // fact that 4.log has marked as to be obseleted. Such wal - // sync will then add a WAL addition record of 4.log to the new manifest - // without any special treatment. Prior to the fix, there is no WAL deletion - // record to offset it. (3) BackgroundFlush() will eventually purge 4.log. - - bool wal_synced = false; - SyncPoint::GetInstance()->SetCallBack( - "FindObsoleteFiles::PostMutexUnlock", [&](void*) { - ASSERT_OK(env_->FileExists(wal_file_path)); - uint64_t pre_sync_wal_manifest_no = - dbfull()->TEST_Current_Manifest_FileNo(); - ASSERT_OK(db_->SyncWAL()); - uint64_t post_sync_wal_manifest_no = - dbfull()->TEST_Current_Manifest_FileNo(); - bool new_manifest_created = - post_sync_wal_manifest_no == pre_sync_wal_manifest_no + 1; - ASSERT_TRUE(new_manifest_created); - wal_synced = true; - }); - - - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork()); - - ASSERT_TRUE(wal_synced); - // BackgroundFlush() purged 4.log - // because the memtable associated with the WAL was flushed and new WAL was - // created (i.e, 8.log) - ASSERT_TRUE(env_->FileExists(wal_file_path).IsNotFound()); - - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - - // To verify the corruption of "Missing WAL with log number: 4" under - // `options.track_and_verify_wals_in_manifest = true` is fixed. - // - // Before the fix, `db_->SyncWAL()` will sync and record WAL addtion of the - // obseleted WAL 4.log in a new manifest without any special treament. - // This will result in missing-wal corruption in DB::Reopen(). - Status s = TryReopen(options); - EXPECT_OK(s); -} - -// Test scope: -// - We expect to open data store under all circumstances -// - We expect only data upto the point where the first error was encountered -TEST_P(DBWALTestWithParams, kPointInTimeRecovery) { - const int maxkeys = - RecoveryTestHelper::kWALFilesCount * RecoveryTestHelper::kKeysPerWALFile; - - bool trunc = std::get<0>(GetParam()); // Corruption style - // Corruption offset position - int corrupt_offset = std::get<1>(GetParam()); - int wal_file_id = std::get<2>(GetParam()); // WAL file - // WAL compression type - CompressionType compression_type = std::get<3>(GetParam()); - - // Fill data for testing - Options options = CurrentOptions(); - options.wal_compression = compression_type; - const size_t row_count = RecoveryTestHelper::FillData(this, &options); - - // Corrupt the wal - // The offset here was 0.3 which cuts off right at the end of a - // valid fragment after wal zstd compression checksum is enabled, - // so changed the value to 0.33. - RecoveryTestHelper::CorruptWAL(this, options, corrupt_offset * .33, - /*len%=*/.1, wal_file_id, trunc); - - // Verify - options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - options.create_if_missing = false; - ASSERT_OK(TryReopen(options)); - - // Probe data for invariants - size_t recovered_row_count = RecoveryTestHelper::GetData(this); - ASSERT_LT(recovered_row_count, row_count); - - // Verify a prefix of keys were recovered. But not in the case of full WAL - // truncation, because we have no way to know there was a corruption when - // truncation happened on record boundaries (preventing recovery holes in - // that case requires using `track_and_verify_wals_in_manifest`). - if (!trunc || corrupt_offset != 0) { - bool expect_data = true; - for (size_t k = 0; k < maxkeys; ++k) { - bool found = Get("key" + std::to_string(k)) != "NOT_FOUND"; - if (expect_data && !found) { - expect_data = false; - } - ASSERT_EQ(found, expect_data); - } - } - - const size_t min = RecoveryTestHelper::kKeysPerWALFile * - (wal_file_id - RecoveryTestHelper::kWALFileOffset); - ASSERT_GE(recovered_row_count, min); - if (!trunc && corrupt_offset != 0) { - const size_t max = RecoveryTestHelper::kKeysPerWALFile * - (wal_file_id - RecoveryTestHelper::kWALFileOffset + 1); - ASSERT_LE(recovered_row_count, max); - } -} - -// Test scope: -// - We expect to open the data store under all scenarios -// - We expect to have recovered records past the corruption zone -TEST_P(DBWALTestWithParams, kSkipAnyCorruptedRecords) { - bool trunc = std::get<0>(GetParam()); // Corruption style - // Corruption offset position - int corrupt_offset = std::get<1>(GetParam()); - int wal_file_id = std::get<2>(GetParam()); // WAL file - // WAL compression type - CompressionType compression_type = std::get<3>(GetParam()); - - // Fill data for testing - Options options = CurrentOptions(); - options.wal_compression = compression_type; - const size_t row_count = RecoveryTestHelper::FillData(this, &options); - - // Corrupt the WAL - RecoveryTestHelper::CorruptWAL(this, options, corrupt_offset * .3, - /*len%=*/.1, wal_file_id, trunc); - - // Verify behavior - options.wal_recovery_mode = WALRecoveryMode::kSkipAnyCorruptedRecords; - options.create_if_missing = false; - ASSERT_OK(TryReopen(options)); - - // Probe data for invariants - size_t recovered_row_count = RecoveryTestHelper::GetData(this); - ASSERT_LT(recovered_row_count, row_count); - - if (!trunc) { - ASSERT_TRUE(corrupt_offset != 0 || recovered_row_count > 0); - } -} - -TEST_F(DBWALTest, AvoidFlushDuringRecovery) { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.avoid_flush_during_recovery = false; - - // Test with flush after recovery. - Reopen(options); - ASSERT_OK(Put("foo", "v1")); - ASSERT_OK(Put("bar", "v2")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "v3")); - ASSERT_OK(Put("bar", "v4")); - ASSERT_EQ(1, TotalTableFiles()); - // Reopen DB. Check if WAL logs flushed. - Reopen(options); - ASSERT_EQ("v3", Get("foo")); - ASSERT_EQ("v4", Get("bar")); - ASSERT_EQ(2, TotalTableFiles()); - - // Test without flush after recovery. - options.avoid_flush_during_recovery = true; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "v5")); - ASSERT_OK(Put("bar", "v6")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "v7")); - ASSERT_OK(Put("bar", "v8")); - ASSERT_EQ(1, TotalTableFiles()); - // Reopen DB. WAL logs should not be flushed this time. - Reopen(options); - ASSERT_EQ("v7", Get("foo")); - ASSERT_EQ("v8", Get("bar")); - ASSERT_EQ(1, TotalTableFiles()); - - // Force flush with allow_2pc. - options.avoid_flush_during_recovery = true; - options.allow_2pc = true; - ASSERT_OK(Put("foo", "v9")); - ASSERT_OK(Put("bar", "v10")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo", "v11")); - ASSERT_OK(Put("bar", "v12")); - Reopen(options); - ASSERT_EQ("v11", Get("foo")); - ASSERT_EQ("v12", Get("bar")); - ASSERT_EQ(3, TotalTableFiles()); -} - -TEST_F(DBWALTest, WalCleanupAfterAvoidFlushDuringRecovery) { - // Verifies WAL files that were present during recovery, but not flushed due - // to avoid_flush_during_recovery, will be considered for deletion at a later - // stage. We check at least one such file is deleted during Flush(). - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.avoid_flush_during_recovery = true; - Reopen(options); - - ASSERT_OK(Put("foo", "v1")); - Reopen(options); - for (int i = 0; i < 2; ++i) { - if (i > 0) { - // Flush() triggers deletion of obsolete tracked files - ASSERT_OK(Flush()); - } - VectorLogPtr log_files; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files)); - if (i == 0) { - ASSERT_GT(log_files.size(), 0); - } else { - ASSERT_EQ(0, log_files.size()); - } - } -} - -TEST_F(DBWALTest, RecoverWithoutFlush) { - Options options = CurrentOptions(); - options.avoid_flush_during_recovery = true; - options.create_if_missing = false; - options.disable_auto_compactions = true; - options.write_buffer_size = 64 * 1024 * 1024; - - size_t count = RecoveryTestHelper::FillData(this, &options); - auto validateData = [this, count]() { - for (size_t i = 0; i < count; i++) { - ASSERT_NE(Get("key" + std::to_string(i)), "NOT_FOUND"); - } - }; - Reopen(options); - validateData(); - // Insert some data without flush - ASSERT_OK(Put("foo", "foo_v1")); - ASSERT_OK(Put("bar", "bar_v1")); - Reopen(options); - validateData(); - ASSERT_EQ(Get("foo"), "foo_v1"); - ASSERT_EQ(Get("bar"), "bar_v1"); - // Insert again and reopen - ASSERT_OK(Put("foo", "foo_v2")); - ASSERT_OK(Put("bar", "bar_v2")); - Reopen(options); - validateData(); - ASSERT_EQ(Get("foo"), "foo_v2"); - ASSERT_EQ(Get("bar"), "bar_v2"); - // manual flush and insert again - ASSERT_OK(Flush()); - ASSERT_EQ(Get("foo"), "foo_v2"); - ASSERT_EQ(Get("bar"), "bar_v2"); - ASSERT_OK(Put("foo", "foo_v3")); - ASSERT_OK(Put("bar", "bar_v3")); - Reopen(options); - validateData(); - ASSERT_EQ(Get("foo"), "foo_v3"); - ASSERT_EQ(Get("bar"), "bar_v3"); -} - -TEST_F(DBWALTest, RecoverWithoutFlushMultipleCF) { - const std::string kSmallValue = "v"; - const std::string kLargeValue = DummyString(1024); - Options options = CurrentOptions(); - options.avoid_flush_during_recovery = true; - options.create_if_missing = false; - options.disable_auto_compactions = true; - - auto countWalFiles = [this]() { - VectorLogPtr log_files; - if (!dbfull()->GetSortedWalFiles(log_files).ok()) { - return size_t{0}; - } - return log_files.size(); - }; - - // Create DB with multiple column families and multiple log files. - CreateAndReopenWithCF({"one", "two"}, options); - ASSERT_OK(Put(0, "key1", kSmallValue)); - ASSERT_OK(Put(1, "key2", kLargeValue)); - ASSERT_OK(Flush(1)); - ASSERT_EQ(1, countWalFiles()); - ASSERT_OK(Put(0, "key3", kSmallValue)); - ASSERT_OK(Put(2, "key4", kLargeValue)); - ASSERT_OK(Flush(2)); - ASSERT_EQ(2, countWalFiles()); - - // Reopen, insert and flush. - options.db_write_buffer_size = 64 * 1024 * 1024; - ReopenWithColumnFamilies({"default", "one", "two"}, options); - ASSERT_EQ(Get(0, "key1"), kSmallValue); - ASSERT_EQ(Get(1, "key2"), kLargeValue); - ASSERT_EQ(Get(0, "key3"), kSmallValue); - ASSERT_EQ(Get(2, "key4"), kLargeValue); - // Insert more data. - ASSERT_OK(Put(0, "key5", kLargeValue)); - ASSERT_OK(Put(1, "key6", kLargeValue)); - ASSERT_EQ(3, countWalFiles()); - ASSERT_OK(Flush(1)); - ASSERT_OK(Put(2, "key7", kLargeValue)); - ASSERT_OK(dbfull()->FlushWAL(false)); - ASSERT_EQ(4, countWalFiles()); - - // Reopen twice and validate. - for (int i = 0; i < 2; i++) { - ReopenWithColumnFamilies({"default", "one", "two"}, options); - ASSERT_EQ(Get(0, "key1"), kSmallValue); - ASSERT_EQ(Get(1, "key2"), kLargeValue); - ASSERT_EQ(Get(0, "key3"), kSmallValue); - ASSERT_EQ(Get(2, "key4"), kLargeValue); - ASSERT_EQ(Get(0, "key5"), kLargeValue); - ASSERT_EQ(Get(1, "key6"), kLargeValue); - ASSERT_EQ(Get(2, "key7"), kLargeValue); - ASSERT_EQ(4, countWalFiles()); - } -} - -// In this test we are trying to do the following: -// 1. Create a DB with corrupted WAL log; -// 2. Open with avoid_flush_during_recovery = true; -// 3. Append more data without flushing, which creates new WAL log. -// 4. Open again. See if it can correctly handle previous corruption. -TEST_P(DBWALTestWithParamsVaryingRecoveryMode, - RecoverFromCorruptedWALWithoutFlush) { - const int kAppendKeys = 100; - Options options = CurrentOptions(); - options.avoid_flush_during_recovery = true; - options.create_if_missing = false; - options.disable_auto_compactions = true; - options.write_buffer_size = 64 * 1024 * 1024; - - auto getAll = [this]() { - std::vector> data; - ReadOptions ropt; - Iterator* iter = dbfull()->NewIterator(ropt); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - data.push_back( - std::make_pair(iter->key().ToString(), iter->value().ToString())); - } - delete iter; - return data; - }; - - bool trunc = std::get<0>(GetParam()); // Corruption style - // Corruption offset position - int corrupt_offset = std::get<1>(GetParam()); - int wal_file_id = std::get<2>(GetParam()); // WAL file - WALRecoveryMode recovery_mode = std::get<3>(GetParam()); - // WAL compression type - CompressionType compression_type = std::get<4>(GetParam()); - - options.wal_recovery_mode = recovery_mode; - options.wal_compression = compression_type; - // Create corrupted WAL - RecoveryTestHelper::FillData(this, &options); - RecoveryTestHelper::CorruptWAL(this, options, corrupt_offset * .3, - /*len%=*/.1, wal_file_id, trunc); - // Skip the test if DB won't open. - if (!TryReopen(options).ok()) { - ASSERT_TRUE(options.wal_recovery_mode == - WALRecoveryMode::kAbsoluteConsistency || - (!trunc && options.wal_recovery_mode == - WALRecoveryMode::kTolerateCorruptedTailRecords)); - return; - } - ASSERT_OK(TryReopen(options)); - // Append some more data. - for (int k = 0; k < kAppendKeys; k++) { - std::string key = "extra_key" + std::to_string(k); - std::string value = DummyString(RecoveryTestHelper::kValueSize); - ASSERT_OK(Put(key, value)); - } - // Save data for comparison. - auto data = getAll(); - // Reopen. Verify data. - ASSERT_OK(TryReopen(options)); - auto actual_data = getAll(); - ASSERT_EQ(data, actual_data); -} - -// Tests that total log size is recovered if we set -// avoid_flush_during_recovery=true. -// Flush should trigger if max_total_wal_size is reached. -TEST_F(DBWALTest, RestoreTotalLogSizeAfterRecoverWithoutFlush) { - auto test_listener = std::make_shared(); - test_listener->expected_flush_reason = FlushReason::kWalFull; - - constexpr size_t kKB = 1024; - constexpr size_t kMB = 1024 * 1024; - Options options = CurrentOptions(); - options.avoid_flush_during_recovery = true; - options.max_total_wal_size = 1 * kMB; - options.listeners.push_back(test_listener); - // Have to open DB in multi-CF mode to trigger flush when - // max_total_wal_size is reached. - CreateAndReopenWithCF({"one"}, options); - // Write some keys and we will end up with one log file which is slightly - // smaller than 1MB. - std::string value_100k(100 * kKB, 'v'); - std::string value_300k(300 * kKB, 'v'); - ASSERT_OK(Put(0, "foo", "v1")); - for (int i = 0; i < 9; i++) { - ASSERT_OK(Put(1, "key" + std::to_string(i), value_100k)); - } - // Get log files before reopen. - VectorLogPtr log_files_before; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before)); - ASSERT_EQ(1, log_files_before.size()); - uint64_t log_size_before = log_files_before[0]->SizeFileBytes(); - ASSERT_GT(log_size_before, 900 * kKB); - ASSERT_LT(log_size_before, 1 * kMB); - ReopenWithColumnFamilies({"default", "one"}, options); - // Write one more value to make log larger than 1MB. - ASSERT_OK(Put(1, "bar", value_300k)); - // Get log files again. A new log file will be opened. - VectorLogPtr log_files_after_reopen; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after_reopen)); - ASSERT_EQ(2, log_files_after_reopen.size()); - ASSERT_EQ(log_files_before[0]->LogNumber(), - log_files_after_reopen[0]->LogNumber()); - ASSERT_GT(log_files_after_reopen[0]->SizeFileBytes() + - log_files_after_reopen[1]->SizeFileBytes(), - 1 * kMB); - // Write one more key to trigger flush. - ASSERT_OK(Put(0, "foo", "v2")); - for (auto* h : handles_) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(h)); - } - // Flushed two column families. - ASSERT_EQ(2, test_listener->count.load()); -} - -#if defined(ROCKSDB_PLATFORM_POSIX) -#if defined(ROCKSDB_FALLOCATE_PRESENT) -// Tests that we will truncate the preallocated space of the last log from -// previous. -TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) { - constexpr size_t kKB = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.avoid_flush_during_recovery = true; - if (mem_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem environment"); - return; - } - if (!IsFallocateSupported()) { - return; - } - - DestroyAndReopen(options); - size_t preallocated_size = - dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size); - ASSERT_OK(Put("foo", "v1")); - VectorLogPtr log_files_before; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before)); - ASSERT_EQ(1, log_files_before.size()); - auto& file_before = log_files_before[0]; - ASSERT_LT(file_before->SizeFileBytes(), 1 * kKB); - // The log file has preallocated space. - ASSERT_GE(GetAllocatedFileSize(dbname_ + file_before->PathName()), - preallocated_size); - Reopen(options); - VectorLogPtr log_files_after; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after)); - ASSERT_EQ(1, log_files_after.size()); - ASSERT_LT(log_files_after[0]->SizeFileBytes(), 1 * kKB); - // The preallocated space should be truncated. - ASSERT_LT(GetAllocatedFileSize(dbname_ + file_before->PathName()), - preallocated_size); -} -// Tests that we will truncate the preallocated space of the last log from -// previous. -TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithFlush) { - constexpr size_t kKB = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.avoid_flush_during_recovery = false; - options.avoid_flush_during_shutdown = true; - if (mem_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem environment"); - return; - } - if (!IsFallocateSupported()) { - return; - } - - DestroyAndReopen(options); - size_t preallocated_size = - dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size); - ASSERT_OK(Put("foo", "v1")); - VectorLogPtr log_files_before; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before)); - ASSERT_EQ(1, log_files_before.size()); - auto& file_before = log_files_before[0]; - ASSERT_LT(file_before->SizeFileBytes(), 1 * kKB); - ASSERT_GE(GetAllocatedFileSize(dbname_ + file_before->PathName()), - preallocated_size); - // The log file has preallocated space. - Close(); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::PurgeObsoleteFiles:Begin", - "DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover"}, - {"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate", - "DBImpl::DeleteObsoleteFileImpl::BeforeDeletion"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - port::Thread reopen_thread([&]() { Reopen(options); }); - - TEST_SYNC_POINT( - "DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover"); - // After the flush during Open, the log file should get deleted. However, - // if the process is in a crash loop, the log file may not get - // deleted and thte preallocated space will keep accumulating. So we need - // to ensure it gets trtuncated. - EXPECT_LT(GetAllocatedFileSize(dbname_ + file_before->PathName()), - preallocated_size); - TEST_SYNC_POINT( - "DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate"); - reopen_thread.join(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DBWALTest, TruncateLastLogAfterRecoverWALEmpty) { - Options options = CurrentOptions(); - options.env = env_; - options.avoid_flush_during_recovery = false; - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem/non-encrypted environment"); - return; - } - if (!IsFallocateSupported()) { - return; - } - - DestroyAndReopen(options); - size_t preallocated_size = - dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size); - Close(); - std::vector filenames; - std::string last_log; - uint64_t last_log_num = 0; - ASSERT_OK(env_->GetChildren(dbname_, &filenames)); - for (auto fname : filenames) { - uint64_t number; - FileType type; - if (ParseFileName(fname, &number, &type, nullptr)) { - if (type == kWalFile && number > last_log_num) { - last_log = fname; - } - } - } - ASSERT_NE(last_log, ""); - last_log = dbname_ + '/' + last_log; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::PurgeObsoleteFiles:Begin", - "DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover"}, - {"DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate", - "DBImpl::DeleteObsoleteFileImpl::BeforeDeletion"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "PosixWritableFile::Close", - [](void* arg) { *(reinterpret_cast(arg)) = 0; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - // Preallocate space for the empty log file. This could happen if WAL data - // was buffered in memory and the process crashed. - std::unique_ptr log_file; - ASSERT_OK(env_->ReopenWritableFile(last_log, &log_file, EnvOptions())); - log_file->SetPreallocationBlockSize(preallocated_size); - log_file->PrepareWrite(0, 4096); - log_file.reset(); - - ASSERT_GE(GetAllocatedFileSize(last_log), preallocated_size); - - port::Thread reopen_thread([&]() { Reopen(options); }); - - TEST_SYNC_POINT( - "DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterRecover"); - // The preallocated space should be truncated. - EXPECT_LT(GetAllocatedFileSize(last_log), preallocated_size); - TEST_SYNC_POINT( - "DBWALTest::TruncateLastLogAfterRecoverWithFlush:AfterTruncate"); - reopen_thread.join(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(DBWALTest, ReadOnlyRecoveryNoTruncate) { - constexpr size_t kKB = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.avoid_flush_during_recovery = true; - if (mem_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem environment"); - return; - } - if (!IsFallocateSupported()) { - return; - } - - // create DB and close with file truncate disabled - std::atomic_bool enable_truncate{false}; - - SyncPoint::GetInstance()->SetCallBack( - "PosixWritableFile::Close", [&](void* arg) { - if (!enable_truncate) { - *(reinterpret_cast(arg)) = 0; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - size_t preallocated_size = - dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size); - ASSERT_OK(Put("foo", "v1")); - VectorLogPtr log_files_before; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before)); - ASSERT_EQ(1, log_files_before.size()); - auto& file_before = log_files_before[0]; - ASSERT_LT(file_before->SizeFileBytes(), 1 * kKB); - // The log file has preallocated space. - auto db_size = GetAllocatedFileSize(dbname_ + file_before->PathName()); - ASSERT_GE(db_size, preallocated_size); - Close(); - - // enable truncate and open DB as readonly, the file should not be truncated - // and DB size is not changed. - enable_truncate = true; - ASSERT_OK(ReadOnlyReopen(options)); - VectorLogPtr log_files_after; - ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after)); - ASSERT_EQ(1, log_files_after.size()); - ASSERT_LT(log_files_after[0]->SizeFileBytes(), 1 * kKB); - ASSERT_EQ(log_files_after[0]->PathName(), file_before->PathName()); - // The preallocated space should NOT be truncated. - // the DB size is almost the same. - ASSERT_NEAR(GetAllocatedFileSize(dbname_ + file_before->PathName()), db_size, - db_size / 100); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} -#endif // ROCKSDB_FALLOCATE_PRESENT -#endif // ROCKSDB_PLATFORM_POSIX - -TEST_F(DBWALTest, WalInManifestButNotInSortedWals) { - Options options = CurrentOptions(); - options.track_and_verify_wals_in_manifest = true; - options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency; - - // Build a way to make wal files selectively go missing - bool wals_go_missing = false; - struct MissingWalFs : public FileSystemWrapper { - MissingWalFs(const std::shared_ptr& t, - bool* _wals_go_missing_flag) - : FileSystemWrapper(t), wals_go_missing_flag(_wals_go_missing_flag) {} - bool* wals_go_missing_flag; - IOStatus GetChildren(const std::string& dir, const IOOptions& io_opts, - std::vector* r, - IODebugContext* dbg) override { - IOStatus s = target_->GetChildren(dir, io_opts, r, dbg); - if (s.ok() && *wals_go_missing_flag) { - for (size_t i = 0; i < r->size();) { - if (EndsWith(r->at(i), ".log")) { - r->erase(r->begin() + i); - } else { - ++i; - } - } - } - return s; - } - const char* Name() const override { return "MissingWalFs"; } - }; - auto my_fs = - std::make_shared(env_->GetFileSystem(), &wals_go_missing); - std::unique_ptr my_env(NewCompositeEnv(my_fs)); - options.env = my_env.get(); - - CreateAndReopenWithCF({"blah"}, options); - - // Currently necessary to get a WAL tracked in manifest; see - // https://github.com/facebook/rocksdb/issues/10080 - ASSERT_OK(Put(0, "x", "y")); - ASSERT_OK(db_->SyncWAL()); - ASSERT_OK(Put(1, "x", "y")); - ASSERT_OK(db_->SyncWAL()); - ASSERT_OK(Flush(1)); - - ASSERT_FALSE(dbfull()->GetVersionSet()->GetWalSet().GetWals().empty()); - std::vector> wals; - ASSERT_OK(db_->GetSortedWalFiles(wals)); - wals_go_missing = true; - ASSERT_NOK(db_->GetSortedWalFiles(wals)); - wals_go_missing = false; - Close(); -} - - -TEST_F(DBWALTest, WalTermTest) { - Options options = CurrentOptions(); - options.env = env_; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(1, "foo", "bar")); - - WriteOptions wo; - wo.sync = true; - wo.disableWAL = false; - - WriteBatch batch; - ASSERT_OK(batch.Put("foo", "bar")); - batch.MarkWalTerminationPoint(); - ASSERT_OK(batch.Put("foo2", "bar2")); - - ASSERT_OK(dbfull()->Write(wo, &batch)); - - // make sure we can re-open it. - ASSERT_OK(TryReopenWithColumnFamilies({"default", "pikachu"}, options)); - ASSERT_EQ("bar", Get(1, "foo")); - ASSERT_EQ("NOT_FOUND", Get(1, "foo2")); -} - -TEST_F(DBWALTest, GetCompressedWalsAfterSync) { - if (db_->GetOptions().wal_compression == kNoCompression) { - ROCKSDB_GTEST_BYPASS("stream compression not present"); - return; - } - Options options = GetDefaultOptions(); - options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; - options.create_if_missing = true; - options.env = env_; - options.avoid_flush_during_recovery = true; - options.track_and_verify_wals_in_manifest = true; - // Enable WAL compression so that the newly-created WAL will be non-empty - // after DB open, even if point-in-time WAL recovery encounters no - // corruption. - options.wal_compression = kZSTD; - DestroyAndReopen(options); - - // Write something to memtable and WAL so that log_empty_ will be false after - // next DB::Open(). - ASSERT_OK(Put("a", "v")); - - Reopen(options); - - // New WAL is created, thanks to !log_empty_. - ASSERT_OK(dbfull()->TEST_SwitchWAL()); - - ASSERT_OK(Put("b", "v")); - - ASSERT_OK(db_->SyncWAL()); - - VectorLogPtr wals; - Status s = dbfull()->GetSortedWalFiles(wals); - ASSERT_OK(s); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_with_timestamp_basic_test.cc b/db/db_with_timestamp_basic_test.cc deleted file mode 100644 index 4b8132df3..000000000 --- a/db/db_with_timestamp_basic_test.cc +++ /dev/null @@ -1,3928 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_with_timestamp_test_util.h" -#include "port/stack_trace.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/utilities/debug.h" -#include "table/block_based/block_based_table_reader.h" -#include "table/block_based/block_builder.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "utilities/fault_injection_env.h" -#include "utilities/merge_operators/string_append/stringappend2.h" - -namespace ROCKSDB_NAMESPACE { -class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase { - public: - DBBasicTestWithTimestamp() - : DBBasicTestWithTimestampBase("db_basic_test_with_timestamp") {} -}; - -TEST_F(DBBasicTestWithTimestamp, SanityChecks) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.avoid_flush_during_shutdown = true; - options.merge_operator = MergeOperators::CreateStringAppendTESTOperator(); - DestroyAndReopen(options); - - Options options1 = CurrentOptions(); - options1.env = env_; - options1.comparator = test::BytewiseComparatorWithU64TsWrapper(); - options1.merge_operator = MergeOperators::CreateStringAppendTESTOperator(); - assert(options1.comparator && - options1.comparator->timestamp_size() == sizeof(uint64_t)); - ColumnFamilyHandle* handle = nullptr; - Status s = db_->CreateColumnFamily(options1, "data", &handle); - ASSERT_OK(s); - - std::string dummy_ts(sizeof(uint64_t), '\0'); - // Perform timestamp operations on default cf. - ASSERT_TRUE( - db_->Put(WriteOptions(), "key", dummy_ts, "value").IsInvalidArgument()); - ASSERT_TRUE(db_->Merge(WriteOptions(), db_->DefaultColumnFamily(), "key", - dummy_ts, "value") - .IsInvalidArgument()); - ASSERT_TRUE(db_->Delete(WriteOptions(), "key", dummy_ts).IsInvalidArgument()); - ASSERT_TRUE( - db_->SingleDelete(WriteOptions(), "key", dummy_ts).IsInvalidArgument()); - ASSERT_TRUE(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - "begin_key", "end_key", dummy_ts) - .IsInvalidArgument()); - - // Perform non-timestamp operations on "data" cf. - ASSERT_TRUE( - db_->Put(WriteOptions(), handle, "key", "value").IsInvalidArgument()); - ASSERT_TRUE(db_->Delete(WriteOptions(), handle, "key").IsInvalidArgument()); - ASSERT_TRUE( - db_->SingleDelete(WriteOptions(), handle, "key").IsInvalidArgument()); - - ASSERT_TRUE( - db_->Merge(WriteOptions(), handle, "key", "value").IsInvalidArgument()); - ASSERT_TRUE(db_->DeleteRange(WriteOptions(), handle, "begin_key", "end_key") - .IsInvalidArgument()); - - { - WriteBatch wb; - ASSERT_OK(wb.Put(handle, "key", "value")); - ASSERT_TRUE(db_->Write(WriteOptions(), &wb).IsInvalidArgument()); - } - { - WriteBatch wb; - ASSERT_OK(wb.Delete(handle, "key")); - ASSERT_TRUE(db_->Write(WriteOptions(), &wb).IsInvalidArgument()); - } - { - WriteBatch wb; - ASSERT_OK(wb.SingleDelete(handle, "key")); - ASSERT_TRUE(db_->Write(WriteOptions(), &wb).IsInvalidArgument()); - } - { - WriteBatch wb; - ASSERT_OK(wb.DeleteRange(handle, "begin_key", "end_key")); - ASSERT_TRUE(db_->Write(WriteOptions(), &wb).IsInvalidArgument()); - } - - // Perform timestamp operations with timestamps of incorrect size. - const std::string wrong_ts(sizeof(uint32_t), '\0'); - ASSERT_TRUE(db_->Put(WriteOptions(), handle, "key", wrong_ts, "value") - .IsInvalidArgument()); - ASSERT_TRUE(db_->Merge(WriteOptions(), handle, "key", wrong_ts, "value") - .IsInvalidArgument()); - ASSERT_TRUE( - db_->Delete(WriteOptions(), handle, "key", wrong_ts).IsInvalidArgument()); - ASSERT_TRUE(db_->SingleDelete(WriteOptions(), handle, "key", wrong_ts) - .IsInvalidArgument()); - ASSERT_TRUE( - db_->DeleteRange(WriteOptions(), handle, "begin_key", "end_key", wrong_ts) - .IsInvalidArgument()); - - delete handle; -} - -TEST_F(DBBasicTestWithTimestamp, MixedCfs) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.avoid_flush_during_shutdown = true; - DestroyAndReopen(options); - - Options options1 = CurrentOptions(); - options1.env = env_; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options1.comparator = &test_cmp; - ColumnFamilyHandle* handle = nullptr; - Status s = db_->CreateColumnFamily(options1, "data", &handle); - ASSERT_OK(s); - - WriteBatch wb; - ASSERT_OK(wb.Put("a", "value")); - ASSERT_OK(wb.Put(handle, "a", "value")); - { - std::string ts = Timestamp(1, 0); - const auto ts_sz_func = [kTimestampSize, handle](uint32_t cf_id) { - assert(handle); - if (cf_id == 0) { - return static_cast(0); - } else if (cf_id == handle->GetID()) { - return kTimestampSize; - } else { - assert(false); - return std::numeric_limits::max(); - } - }; - ASSERT_OK(wb.UpdateTimestamps(ts, ts_sz_func)); - ASSERT_OK(db_->Write(WriteOptions(), &wb)); - } - - const auto verify_db = [this](ColumnFamilyHandle* h, const std::string& key, - const std::string& ts, - const std::string& expected_value) { - ASSERT_EQ(expected_value, Get(key)); - Slice read_ts_slice(ts); - ReadOptions read_opts; - read_opts.timestamp = &read_ts_slice; - std::string value; - ASSERT_OK(db_->Get(read_opts, h, key, &value)); - ASSERT_EQ(expected_value, value); - }; - - verify_db(handle, "a", Timestamp(1, 0), "value"); - - delete handle; - Close(); - - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, options); - cf_descs.emplace_back("data", options1); - options.create_if_missing = false; - s = DB::Open(options, dbname_, cf_descs, &handles_, &db_); - ASSERT_OK(s); - - verify_db(handles_[1], "a", Timestamp(1, 0), "value"); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, CompactRangeWithSpecifiedRange) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo1", ts, "bar")); - ASSERT_OK(Flush()); - - ASSERT_OK(db_->Put(write_opts, "foo2", ts, "bar")); - ASSERT_OK(Flush()); - - std::string start_str = "foo"; - std::string end_str = "foo2"; - Slice start(start_str), end(end_str); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end)); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, GcPreserveLatestVersionBelowFullHistoryLow) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - std::string ts_str = Timestamp(1, 0); - WriteOptions wopts; - ASSERT_OK(db_->Put(wopts, "k1", ts_str, "v1")); - ASSERT_OK(db_->Put(wopts, "k2", ts_str, "v2")); - ASSERT_OK(db_->Put(wopts, "k3", ts_str, "v3")); - - ts_str = Timestamp(2, 0); - ASSERT_OK(db_->Delete(wopts, "k3", ts_str)); - - ts_str = Timestamp(4, 0); - ASSERT_OK(db_->Put(wopts, "k1", ts_str, "v5")); - - ts_str = Timestamp(5, 0); - ASSERT_OK( - db_->DeleteRange(wopts, db_->DefaultColumnFamily(), "k0", "k9", ts_str)); - - ts_str = Timestamp(3, 0); - Slice ts = ts_str; - CompactRangeOptions cro; - cro.full_history_ts_low = &ts; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - ASSERT_OK(Flush()); - - ReadOptions ropts; - ropts.timestamp = &ts; - std::string value; - Status s = db_->Get(ropts, "k1", &value); - ASSERT_OK(s); - ASSERT_EQ("v1", value); - - std::string key_ts; - ASSERT_TRUE(db_->Get(ropts, "k3", &value, &key_ts).IsNotFound()); - ASSERT_EQ(Timestamp(2, 0), key_ts); - - ts_str = Timestamp(5, 0); - ts = ts_str; - ropts.timestamp = &ts; - ASSERT_TRUE(db_->Get(ropts, "k2", &value, &key_ts).IsNotFound()); - ASSERT_EQ(Timestamp(5, 0), key_ts); - ASSERT_TRUE(db_->Get(ropts, "k2", &value).IsNotFound()); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, UpdateFullHistoryTsLow) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - const std::string kKey = "test kKey"; - - // Test set ts_low first and flush() - int current_ts_low = 5; - std::string ts_low_str = Timestamp(current_ts_low, 0); - Slice ts_low = ts_low_str; - CompactRangeOptions comp_opts; - comp_opts.full_history_ts_low = &ts_low; - comp_opts.bottommost_level_compaction = BottommostLevelCompaction::kForce; - - ASSERT_OK(db_->CompactRange(comp_opts, nullptr, nullptr)); - - auto* cfd = - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(); - auto result_ts_low = cfd->GetFullHistoryTsLow(); - - ASSERT_TRUE(test_cmp.CompareTimestamp(ts_low, result_ts_low) == 0); - - for (int i = 0; i < 10; i++) { - WriteOptions write_opts; - std::string ts = Timestamp(i, 0); - ASSERT_OK(db_->Put(write_opts, kKey, ts, Key(i))); - } - ASSERT_OK(Flush()); - - for (int i = 0; i < 10; i++) { - ReadOptions read_opts; - std::string ts_str = Timestamp(i, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::string value; - Status status = db_->Get(read_opts, kKey, &value); - if (i < current_ts_low) { - ASSERT_TRUE(status.IsInvalidArgument()); - } else { - ASSERT_OK(status); - ASSERT_TRUE(value.compare(Key(i)) == 0); - } - } - - // Test set ts_low and then trigger compaction - for (int i = 10; i < 20; i++) { - WriteOptions write_opts; - std::string ts = Timestamp(i, 0); - ASSERT_OK(db_->Put(write_opts, kKey, ts, Key(i))); - } - - ASSERT_OK(Flush()); - - current_ts_low = 15; - ts_low_str = Timestamp(current_ts_low, 0); - ts_low = ts_low_str; - comp_opts.full_history_ts_low = &ts_low; - ASSERT_OK(db_->CompactRange(comp_opts, nullptr, nullptr)); - result_ts_low = cfd->GetFullHistoryTsLow(); - ASSERT_TRUE(test_cmp.CompareTimestamp(ts_low, result_ts_low) == 0); - - for (int i = current_ts_low; i < 20; i++) { - ReadOptions read_opts; - std::string ts_str = Timestamp(i, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::string value; - Status status = db_->Get(read_opts, kKey, &value); - ASSERT_OK(status); - ASSERT_TRUE(value.compare(Key(i)) == 0); - } - - // Test invalid compaction with range - Slice start(kKey), end(kKey); - Status s = db_->CompactRange(comp_opts, &start, &end); - ASSERT_TRUE(s.IsInvalidArgument()); - s = db_->CompactRange(comp_opts, &start, nullptr); - ASSERT_TRUE(s.IsInvalidArgument()); - s = db_->CompactRange(comp_opts, nullptr, &end); - ASSERT_TRUE(s.IsInvalidArgument()); - - // Test invalid compaction with the decreasing ts_low - ts_low_str = Timestamp(current_ts_low - 1, 0); - ts_low = ts_low_str; - comp_opts.full_history_ts_low = &ts_low; - s = db_->CompactRange(comp_opts, nullptr, nullptr); - ASSERT_TRUE(s.IsInvalidArgument()); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, UpdateFullHistoryTsLowWithPublicAPI) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - std::string ts_low_str = Timestamp(9, 0); - ASSERT_OK( - db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_low_str)); - std::string result_ts_low; - ASSERT_OK(db_->GetFullHistoryTsLow(nullptr, &result_ts_low)); - ASSERT_TRUE(test_cmp.CompareTimestamp(ts_low_str, result_ts_low) == 0); - // test increase full_history_low backward - std::string ts_low_str_back = Timestamp(8, 0); - auto s = db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), - ts_low_str_back); - ASSERT_EQ(s, Status::InvalidArgument()); - // test IncreaseFullHistoryTsLow with a timestamp whose length is longger - // than the cf's timestamp size - std::string ts_low_str_long(Timestamp(0, 0).size() + 1, 'a'); - s = db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), - ts_low_str_long); - ASSERT_EQ(s, Status::InvalidArgument()); - // test IncreaseFullHistoryTsLow with a timestamp which is null - std::string ts_low_str_null = ""; - s = db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), - ts_low_str_null); - ASSERT_EQ(s, Status::InvalidArgument()); - // test IncreaseFullHistoryTsLow for a column family that does not enable - // timestamp - options.comparator = BytewiseComparator(); - DestroyAndReopen(options); - ts_low_str = Timestamp(10, 0); - s = db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_low_str); - ASSERT_EQ(s, Status::InvalidArgument()); - // test GetFullHistoryTsLow for a column family that does not enable - // timestamp - std::string current_ts_low; - s = db_->GetFullHistoryTsLow(db_->DefaultColumnFamily(), ¤t_ts_low); - ASSERT_EQ(s, Status::InvalidArgument()); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, GetApproximateSizes) { - Options options = CurrentOptions(); - options.write_buffer_size = 100000000; // Large write buffer - options.compression = kNoCompression; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - auto default_cf = db_->DefaultColumnFamily(); - - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - const int N = 128; - Random rnd(301); - for (int i = 0; i < N; i++) { - ASSERT_OK(db_->Put(write_opts, Key(i), ts, rnd.RandomString(1024))); - } - - uint64_t size; - std::string start = Key(50); - std::string end = Key(60); - Range r(start, end); - SizeApproximationOptions size_approx_options; - size_approx_options.include_memtables = true; - size_approx_options.include_files = true; - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_GT(size, 6000); - ASSERT_LT(size, 204800); - - // test multiple ranges - std::vector ranges; - std::string start_tmp = Key(10); - std::string end_tmp = Key(20); - ranges.emplace_back(Range(start_tmp, end_tmp)); - ranges.emplace_back(Range(start, end)); - uint64_t range_sizes[2]; - ASSERT_OK(db_->GetApproximateSizes(size_approx_options, default_cf, - ranges.data(), 2, range_sizes)); - - ASSERT_EQ(range_sizes[1], size); - - // Zero if not including mem table - ASSERT_OK(db_->GetApproximateSizes(&r, 1, &size)); - ASSERT_EQ(size, 0); - - start = Key(500); - end = Key(600); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, 0); - - // Test range boundaries - ASSERT_OK(db_->Put(write_opts, Key(1000), ts, rnd.RandomString(1024))); - // Should include start key - start = Key(1000); - end = Key(1100); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_GT(size, 0); - - // Should exclude end key - start = Key(900); - end = Key(1000); - r = Range(start, end); - ASSERT_OK( - db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1, &size)); - ASSERT_EQ(size, 0); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, SimpleIterate) { - const int kNumKeysPerFile = 128; - const uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector start_keys = {1, 0}; - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - read_opts.timestamp = &read_ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = 0; - // Forward iterate. - for (it->Seek(Key1(0)), key = start_keys[i]; it->Valid(); - it->Next(), ++count, ++key) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - } - size_t expected_count = kMaxKey - start_keys[i] + 1; - ASSERT_EQ(expected_count, count); - - // Backward iterate. - count = 0; - for (it->SeekForPrev(Key1(kMaxKey)), key = kMaxKey; it->Valid(); - it->Prev(), ++count, --key) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - } - ASSERT_EQ(static_cast(kMaxKey) - start_keys[i] + 1, count); - - // SeekToFirst()/SeekToLast() with lower/upper bounds. - // Then iter with lower and upper bounds. - uint64_t l = 0; - uint64_t r = kMaxKey + 1; - while (l < r) { - std::string lb_str = Key1(l); - Slice lb = lb_str; - std::string ub_str = Key1(r); - Slice ub = ub_str; - read_opts.iterate_lower_bound = &lb; - read_opts.iterate_upper_bound = &ub; - it.reset(db_->NewIterator(read_opts)); - for (it->SeekToFirst(), key = std::max(l, start_keys[i]), count = 0; - it->Valid(); it->Next(), ++key, ++count) { - CheckIterUserEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - } - ASSERT_EQ(r - std::max(l, start_keys[i]), count); - - for (it->SeekToLast(), key = std::min(r, kMaxKey + 1), count = 0; - it->Valid(); it->Prev(), --key, ++count) { - CheckIterUserEntry(it.get(), Key1(key - 1), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - } - l += (kMaxKey / 100); - r -= (kMaxKey / 100); - } - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, TrimHistoryTest) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - auto check_value_by_ts = [](DB* db, Slice key, std::string readTs, - Status status, std::string checkValue, - std::string expected_ts) { - ReadOptions ropts; - Slice ts = readTs; - ropts.timestamp = &ts; - std::string value; - std::string key_ts; - Status s = db->Get(ropts, key, &value, &key_ts); - ASSERT_TRUE(s == status); - if (s.ok()) { - ASSERT_EQ(checkValue, value); - } - if (s.ok() || s.IsNotFound()) { - ASSERT_EQ(expected_ts, key_ts); - } - }; - // Construct data of different versions with different ts - ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(2, 0), "v1")); - ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(4, 0), "v2")); - ASSERT_OK(db_->Delete(WriteOptions(), "k1", Timestamp(5, 0))); - ASSERT_OK(db_->Put(WriteOptions(), "k1", Timestamp(6, 0), "v3")); - check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v3", - Timestamp(6, 0)); - ASSERT_OK(Flush()); - Close(); - - ColumnFamilyOptions cf_options(options); - std::vector column_families; - column_families.push_back( - ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options)); - DBOptions db_options(options); - - // Trim data whose version > Timestamp(5, 0), read(k1, ts(7)) <- NOT_FOUND. - ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families, - &handles_, &db_, Timestamp(5, 0))); - check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::NotFound(), "", - Timestamp(5, 0)); - Close(); - - // Trim data whose timestamp > Timestamp(4, 0), read(k1, ts(7)) <- v2 - ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families, - &handles_, &db_, Timestamp(4, 0))); - check_value_by_ts(db_, "k1", Timestamp(7, 0), Status::OK(), "v2", - Timestamp(4, 0)); - Close(); - - Reopen(options); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "k1", - "k3", Timestamp(7, 0))); - check_value_by_ts(db_, "k1", Timestamp(8, 0), Status::NotFound(), "", - Timestamp(7, 0)); - Close(); - // Trim data whose timestamp > Timestamp(6, 0), read(k1, ts(8)) <- v2 - ASSERT_OK(DB::OpenAndTrimHistory(db_options, dbname_, column_families, - &handles_, &db_, Timestamp(6, 0))); - check_value_by_ts(db_, "k1", Timestamp(8, 0), Status::OK(), "v2", - Timestamp(4, 0)); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, OpenAndTrimHistoryInvalidOptionTest) { - Destroy(last_options_); - - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - - ColumnFamilyOptions cf_options(options); - std::vector column_families; - column_families.push_back( - ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options)); - DBOptions db_options(options); - - // OpenAndTrimHistory should not work with avoid_flush_during_recovery - db_options.avoid_flush_during_recovery = true; - ASSERT_TRUE(DB::OpenAndTrimHistory(db_options, dbname_, column_families, - &handles_, &db_, Timestamp(0, 0)) - .IsInvalidArgument()); -} - -TEST_F(DBBasicTestWithTimestamp, GetTimestampTableProperties) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - // Create 2 tables - for (int table = 0; table < 2; ++table) { - for (int i = 0; i < 10; i++) { - std::string ts = Timestamp(i, 0); - ASSERT_OK(db_->Put(WriteOptions(), "key", ts, Key(i))); - } - ASSERT_OK(Flush()); - } - - TablePropertiesCollection props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); - ASSERT_EQ(2U, props.size()); - for (const auto& item : props) { - auto& user_collected = item.second->user_collected_properties; - ASSERT_TRUE(user_collected.find("rocksdb.timestamp_min") != - user_collected.end()); - ASSERT_TRUE(user_collected.find("rocksdb.timestamp_max") != - user_collected.end()); - ASSERT_EQ(user_collected.at("rocksdb.timestamp_min"), Timestamp(0, 0)); - ASSERT_EQ(user_collected.at("rocksdb.timestamp_max"), Timestamp(9, 0)); - } - Close(); -} - -class DBBasicTestWithTimestampTableOptions - : public DBBasicTestWithTimestampBase, - public testing::WithParamInterface { - public: - explicit DBBasicTestWithTimestampTableOptions() - : DBBasicTestWithTimestampBase( - "db_basic_test_with_timestamp_table_options") {} -}; - -INSTANTIATE_TEST_CASE_P( - Timestamp, DBBasicTestWithTimestampTableOptions, - testing::Values( - BlockBasedTableOptions::IndexType::kBinarySearch, - BlockBasedTableOptions::IndexType::kHashSearch, - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch, - BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey)); - -TEST_P(DBBasicTestWithTimestampTableOptions, GetAndMultiGet) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - options.compression = kNoCompression; - BlockBasedTableOptions bbto; - bbto.index_type = GetParam(); - bbto.block_size = 100; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator cmp(kTimestampSize); - options.comparator = &cmp; - DestroyAndReopen(options); - constexpr uint64_t kNumKeys = 1024; - for (uint64_t k = 0; k < kNumKeys; ++k) { - WriteOptions write_opts; - ASSERT_OK(db_->Put(write_opts, Key1(k), Timestamp(1, 0), - "value" + std::to_string(k))); - } - ASSERT_OK(Flush()); - { - ReadOptions read_opts; - read_opts.total_order_seek = true; - std::string ts_str = Timestamp(2, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - // verify Get() - for (it->SeekToFirst(); it->Valid(); it->Next()) { - std::string value_from_get; - std::string key_str(it->key().data(), it->key().size()); - std::string timestamp; - ASSERT_OK(db_->Get(read_opts, key_str, &value_from_get, ×tamp)); - ASSERT_EQ(it->value(), value_from_get); - ASSERT_EQ(Timestamp(1, 0), timestamp); - } - - // verify MultiGet() - constexpr uint64_t step = 2; - static_assert(0 == (kNumKeys % step), - "kNumKeys must be a multiple of step"); - for (uint64_t k = 0; k < kNumKeys; k += 2) { - std::vector key_strs; - std::vector keys; - for (size_t i = 0; i < step; ++i) { - key_strs.push_back(Key1(k + i)); - } - for (size_t i = 0; i < step; ++i) { - keys.emplace_back(key_strs[i]); - } - std::vector values; - std::vector timestamps; - std::vector statuses = - db_->MultiGet(read_opts, keys, &values, ×tamps); - ASSERT_EQ(step, statuses.size()); - ASSERT_EQ(step, values.size()); - ASSERT_EQ(step, timestamps.size()); - for (uint64_t i = 0; i < step; ++i) { - ASSERT_OK(statuses[i]); - ASSERT_EQ("value" + std::to_string(k + i), values[i]); - ASSERT_EQ(Timestamp(1, 0), timestamps[i]); - } - } - } - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithPrefixLessThanKey) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - options.memtable_whole_key_filtering = true; - options.memtable_prefix_bloom_size_ratio = 0.1; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = true; - bbto.index_type = GetParam(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo1", ts, "bar")); - ASSERT_OK(Flush()); - - ASSERT_OK(db_->Put(write_opts, "foo2", ts, "bar")); - ASSERT_OK(Flush()); - - // Move sst file to next level - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - ASSERT_OK(db_->Put(write_opts, "foo3", ts, "bar")); - ASSERT_OK(Flush()); - - ReadOptions read_opts; - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - - iter->Seek("bbb"); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - } - - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithCappedPrefix) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - // All of the keys or this test must be longer than 3 characters - constexpr int kMinKeyLen = 3; - options.prefix_extractor.reset(NewCappedPrefixTransform(kMinKeyLen)); - options.memtable_whole_key_filtering = true; - options.memtable_prefix_bloom_size_ratio = 0.1; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = true; - bbto.index_type = GetParam(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo1", ts, "bar")); - ASSERT_OK(Flush()); - - ASSERT_OK(db_->Put(write_opts, "foo2", ts, "bar")); - ASSERT_OK(Flush()); - - // Move sst file to next level - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - ASSERT_OK(db_->Put(write_opts, "foo3", ts, "bar")); - ASSERT_OK(Flush()); - - ReadOptions read_opts; - ts = Timestamp(2, 0); - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - // Make sure the prefix extractor doesn't include timestamp, otherwise it - // may return invalid result. - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_OK(iter->status()); - } - - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithBound) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(2)); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = true; - bbto.index_type = GetParam(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo1", ts, "bar1")); - ASSERT_OK(Flush()); - - ASSERT_OK(db_->Put(write_opts, "foo2", ts, "bar2")); - ASSERT_OK(Flush()); - - // Move sst file to next level - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - for (int i = 3; i < 9; ++i) { - ASSERT_OK(db_->Put(write_opts, "foo" + std::to_string(i), ts, - "bar" + std::to_string(i))); - } - ASSERT_OK(Flush()); - - ReadOptions read_opts; - ts = Timestamp(2, 0); - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - std::string up_bound = "foo5"; // exclusive - Slice up_bound_slice = up_bound; - std::string lo_bound = "foo2"; // inclusive - Slice lo_bound_slice = lo_bound; - read_opts.iterate_upper_bound = &up_bound_slice; - read_opts.iterate_lower_bound = &lo_bound_slice; - read_opts.auto_prefix_mode = true; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - // Make sure the prefix extractor doesn't include timestamp, otherwise it - // may return invalid result. - iter->Seek("foo"); - CheckIterUserEntry(iter.get(), lo_bound, kTypeValue, "bar2", - Timestamp(1, 0)); - iter->SeekToFirst(); - CheckIterUserEntry(iter.get(), lo_bound, kTypeValue, "bar2", - Timestamp(1, 0)); - iter->SeekForPrev("g"); - CheckIterUserEntry(iter.get(), "foo4", kTypeValue, "bar4", Timestamp(1, 0)); - iter->SeekToLast(); - CheckIterUserEntry(iter.get(), "foo4", kTypeValue, "bar4", Timestamp(1, 0)); - } - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, ChangeIterationDirection) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.env = env_; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - const std::vector timestamps = {Timestamp(1, 1), Timestamp(0, 2), - Timestamp(4, 3)}; - const std::vector> kvs = { - std::make_tuple("aa", "value1"), std::make_tuple("ab", "value2")}; - for (const auto& ts : timestamps) { - WriteBatch wb(0, 0, 0, kTimestampSize); - for (const auto& kv : kvs) { - const std::string& key = std::get<0>(kv); - const std::string& value = std::get<1>(kv); - ASSERT_OK(wb.Put(key, value)); - } - - ASSERT_OK(wb.UpdateTimestamps( - ts, [kTimestampSize](uint32_t) { return kTimestampSize; })); - ASSERT_OK(db_->Write(WriteOptions(), &wb)); - } - std::string read_ts_str = Timestamp(5, 3); - Slice read_ts = read_ts_str; - ReadOptions read_opts; - read_opts.timestamp = &read_ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - - it->SeekToFirst(); - ASSERT_TRUE(it->Valid()); - it->Prev(); - ASSERT_FALSE(it->Valid()); - - it->SeekToLast(); - ASSERT_TRUE(it->Valid()); - uint64_t prev_reseek_count = - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION); - ASSERT_EQ(0, prev_reseek_count); - it->Next(); - ASSERT_FALSE(it->Valid()); - ASSERT_EQ(1 + prev_reseek_count, - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - - it->Seek(std::get<0>(kvs[0])); - CheckIterUserEntry(it.get(), std::get<0>(kvs[0]), kTypeValue, - std::get<1>(kvs[0]), Timestamp(4, 3)); - it->Next(); - CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue, - std::get<1>(kvs[1]), Timestamp(4, 3)); - it->Prev(); - CheckIterUserEntry(it.get(), std::get<0>(kvs[0]), kTypeValue, - std::get<1>(kvs[0]), Timestamp(4, 3)); - - prev_reseek_count = - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION); - ASSERT_EQ(1, prev_reseek_count); - it->Next(); - CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue, - std::get<1>(kvs[1]), Timestamp(4, 3)); - ASSERT_EQ(1 + prev_reseek_count, - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - - it->SeekForPrev(std::get<0>(kvs[1])); - CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue, - std::get<1>(kvs[1]), Timestamp(4, 3)); - it->Prev(); - CheckIterUserEntry(it.get(), std::get<0>(kvs[0]), kTypeValue, - std::get<1>(kvs[0]), Timestamp(4, 3)); - - prev_reseek_count = - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION); - it->Next(); - CheckIterUserEntry(it.get(), std::get<0>(kvs[1]), kTypeValue, - std::get<1>(kvs[1]), Timestamp(4, 3)); - ASSERT_EQ(1 + prev_reseek_count, - options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - - it.reset(); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterateLowerTsBound) { - constexpr int kNumKeysPerFile = 128; - constexpr uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - const std::vector read_timestamps_lb = {Timestamp(1, 0), - Timestamp(1, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - Slice read_ts_lb = read_timestamps_lb[i]; - read_opts.timestamp = &read_ts; - read_opts.iter_start_ts = &read_ts_lb; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = 0; - for (it->Seek(Key1(0)), key = 0; it->Valid(); it->Next(), ++count, ++key) { - CheckIterEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i), write_timestamps[i]); - if (i > 0) { - it->Next(); - CheckIterEntry(it.get(), Key1(key), kTypeValue, - "value" + std::to_string(i - 1), - write_timestamps[i - 1]); - } - } - size_t expected_count = kMaxKey + 1; - ASSERT_EQ(expected_count, count); - } - // Delete all keys@ts=5 and check iteration result with start ts set - { - std::string write_timestamp = Timestamp(5, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key < kMaxKey + 1; ++key) { - Status s = db_->Delete(write_opts, Key1(key), write_timestamp); - ASSERT_OK(s); - } - - std::string read_timestamp = Timestamp(6, 0); - ReadOptions read_opts; - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - std::string read_timestamp_lb = Timestamp(2, 0); - Slice read_ts_lb = read_timestamp_lb; - read_opts.iter_start_ts = &read_ts_lb; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = 0; - for (it->Seek(Key1(0)), key = 0; it->Valid(); it->Next(), ++count, ++key) { - CheckIterEntry(it.get(), Key1(key), kTypeDeletionWithTimestamp, Slice(), - write_timestamp); - // Skip key@ts=3 and land on tombstone key@ts=5 - it->Next(); - } - ASSERT_EQ(kMaxKey + 1, count); - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, BackwardIterateLowerTsBound) { - constexpr int kNumKeysPerFile = 128; - constexpr uint64_t kMaxKey = 1024; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - const std::vector write_timestamps = {Timestamp(1, 0), - Timestamp(3, 0)}; - const std::vector read_timestamps = {Timestamp(2, 0), - Timestamp(4, 0)}; - const std::vector read_timestamps_lb = {Timestamp(1, 0), - Timestamp(1, 0)}; - for (size_t i = 0; i < write_timestamps.size(); ++i) { - WriteOptions write_opts; - for (uint64_t key = 0; key <= kMaxKey; ++key) { - Status s = db_->Put(write_opts, Key1(key), write_timestamps[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - for (size_t i = 0; i < read_timestamps.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_timestamps[i]; - Slice read_ts_lb = read_timestamps_lb[i]; - read_opts.timestamp = &read_ts; - read_opts.iter_start_ts = &read_ts_lb; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = 0; - for (it->SeekForPrev(Key1(kMaxKey)), key = kMaxKey; it->Valid(); - it->Prev(), ++count, --key) { - CheckIterEntry(it.get(), Key1(key), kTypeValue, "value0", - write_timestamps[0]); - if (i > 0) { - it->Prev(); - CheckIterEntry(it.get(), Key1(key), kTypeValue, "value1", - write_timestamps[1]); - } - } - size_t expected_count = kMaxKey + 1; - ASSERT_EQ(expected_count, count); - } - // Delete all keys@ts=5 and check iteration result with start ts set - { - std::string write_timestamp = Timestamp(5, 0); - WriteOptions write_opts; - for (uint64_t key = 0; key < kMaxKey + 1; ++key) { - Status s = db_->Delete(write_opts, Key1(key), write_timestamp); - ASSERT_OK(s); - } - - std::string read_timestamp = Timestamp(6, 0); - ReadOptions read_opts; - Slice read_ts = read_timestamp; - read_opts.timestamp = &read_ts; - std::string read_timestamp_lb = Timestamp(2, 0); - Slice read_ts_lb = read_timestamp_lb; - read_opts.iter_start_ts = &read_ts_lb; - std::unique_ptr it(db_->NewIterator(read_opts)); - int count = 0; - uint64_t key = kMaxKey; - for (it->SeekForPrev(Key1(key)), key = kMaxKey; it->Valid(); - it->Prev(), ++count, --key) { - CheckIterEntry(it.get(), Key1(key), kTypeValue, "value1", - Timestamp(3, 0)); - it->Prev(); - CheckIterEntry(it.get(), Key1(key), kTypeDeletionWithTimestamp, Slice(), - write_timestamp); - } - ASSERT_EQ(kMaxKey + 1, count); - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, SimpleBackwardIterateLowerTsBound) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - std::string ts_ub_buf = Timestamp(5, 0); - Slice ts_ub = ts_ub_buf; - std::string ts_lb_buf = Timestamp(1, 0); - Slice ts_lb = ts_lb_buf; - - { - ReadOptions read_opts; - read_opts.timestamp = &ts_ub; - read_opts.iter_start_ts = &ts_lb; - std::unique_ptr it(db_->NewIterator(read_opts)); - it->SeekToLast(); - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - - it->SeekForPrev("foo"); - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - } - - // Test iterate_upper_bound - ASSERT_OK(db_->Put(WriteOptions(), "a", Timestamp(0, 0), "v0")); - ASSERT_OK(db_->SingleDelete(WriteOptions(), "a", Timestamp(1, 0))); - - for (int i = 0; i < 5; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), "b", Timestamp(i, 0), - "v" + std::to_string(i))); - } - - { - ReadOptions read_opts; - read_opts.timestamp = &ts_ub; - read_opts.iter_start_ts = &ts_lb; - std::string key_ub_str = "b"; // exclusive - Slice key_ub = key_ub_str; - read_opts.iterate_upper_bound = &key_ub; - std::unique_ptr it(db_->NewIterator(read_opts)); - it->SeekToLast(); - CheckIterEntry(it.get(), "a", kTypeSingleDeletion, Slice(), - Timestamp(1, 0)); - - key_ub_str = "a"; // exclusive - key_ub = key_ub_str; - read_opts.iterate_upper_bound = &key_ub; - it.reset(db_->NewIterator(read_opts)); - it->SeekToLast(); - ASSERT_FALSE(it->Valid()); - ASSERT_OK(it->status()); - } - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, BackwardIterateLowerTsBound_Reseek) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.max_sequential_skip_in_iterations = 2; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - for (int i = 0; i < 10; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), "a", Timestamp(i, 0), - "v" + std::to_string(i))); - } - - for (int i = 0; i < 10; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), "b", Timestamp(i, 0), - "v" + std::to_string(i))); - } - - { - std::string ts_ub_buf = Timestamp(6, 0); - Slice ts_ub = ts_ub_buf; - std::string ts_lb_buf = Timestamp(4, 0); - Slice ts_lb = ts_lb_buf; - - ReadOptions read_opts; - read_opts.timestamp = &ts_ub; - read_opts.iter_start_ts = &ts_lb; - std::unique_ptr it(db_->NewIterator(read_opts)); - it->SeekToLast(); - for (int i = 0; i < 3 && it->Valid(); it->Prev(), ++i) { - CheckIterEntry(it.get(), "b", kTypeValue, "v" + std::to_string(4 + i), - Timestamp(4 + i, 0)); - } - for (int i = 0; i < 3 && it->Valid(); it->Prev(), ++i) { - CheckIterEntry(it.get(), "a", kTypeValue, "v" + std::to_string(4 + i), - Timestamp(4 + i, 0)); - } - } - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, ReseekToTargetTimestamp) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - constexpr size_t kNumKeys = 16; - options.max_sequential_skip_in_iterations = kNumKeys / 2; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - // Insert kNumKeys - WriteOptions write_opts; - Status s; - for (size_t i = 0; i != kNumKeys; ++i) { - std::string ts = Timestamp(static_cast(i + 1), 0); - s = db_->Put(write_opts, "foo", ts, "value" + std::to_string(i)); - ASSERT_OK(s); - } - { - ReadOptions read_opts; - std::string ts_str = Timestamp(1, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->SeekToFirst(); - CheckIterUserEntry(iter.get(), "foo", kTypeValue, "value0", ts_str); - ASSERT_EQ( - 1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - - ts_str = Timestamp(kNumKeys, 0); - ts = ts_str; - read_opts.timestamp = &ts; - iter.reset(db_->NewIterator(read_opts)); - iter->SeekToLast(); - CheckIterUserEntry(iter.get(), "foo", kTypeValue, - "value" + std::to_string(kNumKeys - 1), ts_str); - ASSERT_EQ( - 2, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, ReseekToNextUserKey) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - constexpr size_t kNumKeys = 16; - options.max_sequential_skip_in_iterations = kNumKeys / 2; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - // Write kNumKeys + 1 keys - WriteOptions write_opts; - Status s; - for (size_t i = 0; i != kNumKeys; ++i) { - std::string ts = Timestamp(static_cast(i + 1), 0); - s = db_->Put(write_opts, "a", ts, "value" + std::to_string(i)); - ASSERT_OK(s); - } - { - std::string ts_str = Timestamp(static_cast(kNumKeys + 1), 0); - WriteBatch batch(0, 0, 0, kTimestampSize); - { ASSERT_OK(batch.Put("a", "new_value")); } - { ASSERT_OK(batch.Put("b", "new_value")); } - s = batch.UpdateTimestamps( - ts_str, [kTimestampSize](uint32_t) { return kTimestampSize; }); - ASSERT_OK(s); - s = db_->Write(write_opts, &batch); - ASSERT_OK(s); - } - { - ReadOptions read_opts; - std::string ts_str = Timestamp(static_cast(kNumKeys + 1), 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->Seek("a"); - iter->Next(); - CheckIterUserEntry(iter.get(), "b", kTypeValue, "new_value", ts_str); - ASSERT_EQ( - 1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, ReseekToUserKeyBeforeSavedKey) { - Options options = GetDefaultOptions(); - options.env = env_; - options.create_if_missing = true; - constexpr size_t kNumKeys = 16; - options.max_sequential_skip_in_iterations = kNumKeys / 2; - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - for (size_t i = 0; i < kNumKeys; ++i) { - std::string ts = Timestamp(static_cast(i + 1), 0); - WriteOptions write_opts; - Status s = db_->Put(write_opts, "b", ts, "value" + std::to_string(i)); - ASSERT_OK(s); - } - { - std::string ts = Timestamp(1, 0); - WriteOptions write_opts; - ASSERT_OK(db_->Put(write_opts, "a", ts, "value")); - } - { - ReadOptions read_opts; - std::string ts_str = Timestamp(1, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->SeekToLast(); - iter->Prev(); - CheckIterUserEntry(iter.get(), "a", kTypeValue, "value", ts_str); - ASSERT_EQ( - 1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION)); - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MultiGetWithFastLocalBloom) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - // Write any value - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo", ts, "bar")); - - ASSERT_OK(Flush()); - - // Read with MultiGet - ReadOptions read_opts; - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - size_t batch_size = 1; - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - std::vector timestamps(batch_size); - keys[0] = "foo"; - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - timestamps.data(), statuses.data(), true); - - ASSERT_OK(statuses[0]); - ASSERT_EQ(Timestamp(1, 0), timestamps[0]); - for (auto& elem : values) { - elem.Reset(); - } - - ASSERT_OK(db_->SingleDelete(WriteOptions(), "foo", Timestamp(2, 0))); - ts = Timestamp(3, 0); - read_ts = ts; - read_opts.timestamp = &read_ts; - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - timestamps.data(), statuses.data(), true); - ASSERT_TRUE(statuses[0].IsNotFound()); - ASSERT_EQ(Timestamp(2, 0), timestamps[0]); - - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetWithPrefix) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor.reset(NewCappedPrefixTransform(5)); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = false; - bbto.index_type = GetParam(); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - // Write any value - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo", ts, "bar")); - - ASSERT_OK(Flush()); - - // Read with MultiGet - ReadOptions read_opts; - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - size_t batch_size = 1; - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - std::vector timestamps(batch_size); - keys[0] = "foo"; - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - timestamps.data(), statuses.data(), true); - - ASSERT_OK(statuses[0]); - ASSERT_EQ(Timestamp(1, 0), timestamps[0]); - for (auto& elem : values) { - elem.Reset(); - } - - ASSERT_OK(db_->SingleDelete(WriteOptions(), "foo", Timestamp(2, 0))); - // TODO re-enable after fixing a bug of kHashSearch - if (GetParam() != BlockBasedTableOptions::IndexType::kHashSearch) { - ASSERT_OK(Flush()); - } - - ts = Timestamp(3, 0); - read_ts = ts; - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - timestamps.data(), statuses.data(), true); - ASSERT_TRUE(statuses[0].IsNotFound()); - ASSERT_EQ(Timestamp(2, 0), timestamps[0]); - - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetWithMemBloomFilter) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor.reset(NewCappedPrefixTransform(5)); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = false; - bbto.index_type = GetParam(); - options.memtable_prefix_bloom_size_ratio = 0.1; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - // Write any value - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo", ts, "bar")); - - // Read with MultiGet - ts = Timestamp(2, 0); - Slice read_ts = ts; - ReadOptions read_opts; - read_opts.timestamp = &read_ts; - size_t batch_size = 1; - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - keys[0] = "foo"; - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - statuses.data()); - - ASSERT_OK(statuses[0]); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MultiGetRangeFiltering) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = false; - options.memtable_prefix_bloom_size_ratio = 0.1; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - // Write any value - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - // random data - for (int i = 0; i < 3; i++) { - auto key = std::to_string(i * 10); - auto value = std::to_string(i * 10); - Slice key_slice = key; - Slice value_slice = value; - ASSERT_OK(db_->Put(write_opts, key_slice, ts, value_slice)); - ASSERT_OK(Flush()); - } - - // Make num_levels to 2 to do key range filtering of sst files - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - ASSERT_OK(db_->Put(write_opts, "foo", ts, "bar")); - - ASSERT_OK(Flush()); - - // Read with MultiGet - ts = Timestamp(2, 0); - Slice read_ts = ts; - ReadOptions read_opts; - read_opts.timestamp = &read_ts; - size_t batch_size = 1; - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - keys[0] = "foo"; - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - statuses.data()); - - ASSERT_OK(statuses[0]); - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, MultiGetPrefixFilter) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - options.prefix_extractor.reset(NewCappedPrefixTransform(3)); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = false; - bbto.index_type = GetParam(); - options.memtable_prefix_bloom_size_ratio = 0.1; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - ASSERT_OK(db_->Put(write_opts, "foo", ts, "bar")); - - ASSERT_OK(Flush()); - // Read with MultiGet - ts = Timestamp(2, 0); - Slice read_ts = ts; - ReadOptions read_opts; - read_opts.timestamp = &read_ts; - size_t batch_size = 1; - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector timestamps(batch_size); - keys[0] = "foo"; - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - std::vector cfhs(keys.size(), cfh); - std::vector statuses = - db_->MultiGet(read_opts, cfhs, keys, &values, ×tamps); - - ASSERT_OK(statuses[0]); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MaxKeysSkippedDuringNext) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - constexpr size_t max_skippable_internal_keys = 2; - const size_t kNumKeys = max_skippable_internal_keys + 2; - WriteOptions write_opts; - Status s; - { - std::string ts = Timestamp(1, 0); - ASSERT_OK(db_->Put(write_opts, "a", ts, "value")); - } - for (size_t i = 0; i < kNumKeys; ++i) { - std::string ts = Timestamp(static_cast(i + 1), 0); - s = db_->Put(write_opts, "b", ts, "value" + std::to_string(i)); - ASSERT_OK(s); - } - { - ReadOptions read_opts; - read_opts.max_skippable_internal_keys = max_skippable_internal_keys; - std::string ts_str = Timestamp(1, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->SeekToFirst(); - iter->Next(); - ASSERT_TRUE(iter->status().IsIncomplete()); - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MaxKeysSkippedDuringPrev) { - Options options = GetDefaultOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - constexpr size_t max_skippable_internal_keys = 2; - const size_t kNumKeys = max_skippable_internal_keys + 2; - WriteOptions write_opts; - Status s; - { - std::string ts = Timestamp(1, 0); - ASSERT_OK(db_->Put(write_opts, "b", ts, "value")); - } - for (size_t i = 0; i < kNumKeys; ++i) { - std::string ts = Timestamp(static_cast(i + 1), 0); - s = db_->Put(write_opts, "a", ts, "value" + std::to_string(i)); - ASSERT_OK(s); - } - { - ReadOptions read_opts; - read_opts.max_skippable_internal_keys = max_skippable_internal_keys; - std::string ts_str = Timestamp(1, 0); - Slice ts = ts_str; - read_opts.timestamp = &ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - iter->SeekToLast(); - iter->Prev(); - ASSERT_TRUE(iter->status().IsIncomplete()); - } - Close(); -} - -// Create two L0, and compact them to a new L1. In this test, L1 is L_bottom. -// Two L0s: -// f1 f2 -// ... -// Since f2.smallest < f1.largest < f2.largest -// f1 and f2 will be the inputs of a real compaction instead of trivial move. -TEST_F(DBBasicTestWithTimestamp, CompactDeletionWithTimestampMarkerToBottom) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.num_levels = 2; - options.level0_file_num_compaction_trigger = 2; - DestroyAndReopen(options); - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - ASSERT_OK(db_->Put(write_opts, "a", ts, "value0")); - ASSERT_OK(Flush()); - - ts = Timestamp(2, 0); - ASSERT_OK(db_->Put(write_opts, "b", ts, "value0")); - ts = Timestamp(3, 0); - ASSERT_OK(db_->Delete(write_opts, "a", ts)); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ReadOptions read_opts; - ts = Timestamp(1, 0); - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - std::string value; - Status s = db_->Get(read_opts, "a", &value); - ASSERT_OK(s); - ASSERT_EQ("value0", value); - - ts = Timestamp(3, 0); - read_ts = ts; - read_opts.timestamp = &read_ts; - std::string key_ts; - s = db_->Get(read_opts, "a", &value, &key_ts); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ(Timestamp(3, 0), key_ts); - - // Time-travel to the past before deletion - ts = Timestamp(2, 0); - read_ts = ts; - read_opts.timestamp = &read_ts; - s = db_->Get(read_opts, "a", &value); - ASSERT_OK(s); - ASSERT_EQ("value0", value); - Close(); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -class DBBasicTestWithTimestampFilterPrefixSettings - : public DBBasicTestWithTimestampBase, - public testing::WithParamInterface< - std::tuple, bool, bool, - std::shared_ptr, bool, double, - BlockBasedTableOptions::IndexType>> { - public: - DBBasicTestWithTimestampFilterPrefixSettings() - : DBBasicTestWithTimestampBase( - "db_basic_test_with_timestamp_filter_prefix") {} -}; - -TEST_P(DBBasicTestWithTimestampFilterPrefixSettings, GetAndMultiGet) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - BlockBasedTableOptions bbto; - bbto.filter_policy = std::get<0>(GetParam()); - bbto.whole_key_filtering = std::get<1>(GetParam()); - bbto.cache_index_and_filter_blocks = std::get<2>(GetParam()); - bbto.index_type = std::get<6>(GetParam()); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.prefix_extractor = std::get<3>(GetParam()); - options.memtable_whole_key_filtering = std::get<4>(GetParam()); - options.memtable_prefix_bloom_size_ratio = std::get<5>(GetParam()); - - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - const int kMaxKey = 1000; - - // Write any value - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - - int idx = 0; - for (; idx < kMaxKey / 4; idx++) { - ASSERT_OK(db_->Put(write_opts, Key1(idx), ts, "bar")); - ASSERT_OK(db_->Put(write_opts, KeyWithPrefix("foo", idx), ts, "bar")); - } - - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - for (; idx < kMaxKey / 2; idx++) { - ASSERT_OK(db_->Put(write_opts, Key1(idx), ts, "bar")); - ASSERT_OK(db_->Put(write_opts, KeyWithPrefix("foo", idx), ts, "bar")); - } - - ASSERT_OK(Flush()); - - for (; idx < kMaxKey; idx++) { - ASSERT_OK(db_->Put(write_opts, Key1(idx), ts, "bar")); - ASSERT_OK(db_->Put(write_opts, KeyWithPrefix("foo", idx), ts, "bar")); - } - - // Read with MultiGet - ReadOptions read_opts; - Slice read_ts = ts; - read_opts.timestamp = &read_ts; - - for (idx = 0; idx < kMaxKey; idx++) { - size_t batch_size = 4; - std::vector keys_str(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - - keys_str[0] = Key1(idx); - keys_str[1] = KeyWithPrefix("foo", idx); - keys_str[2] = Key1(kMaxKey + idx); - keys_str[3] = KeyWithPrefix("foo", kMaxKey + idx); - - auto keys = ConvertStrToSlice(keys_str); - - db_->MultiGet(read_opts, cfh, batch_size, keys.data(), values.data(), - statuses.data()); - - for (int i = 0; i < 2; i++) { - ASSERT_OK(statuses[i]); - } - for (int i = 2; i < 4; i++) { - ASSERT_TRUE(statuses[i].IsNotFound()); - } - - for (int i = 0; i < 2; i++) { - std::string value; - ASSERT_OK(db_->Get(read_opts, keys[i], &value)); - std::unique_ptr it1(db_->NewIterator(read_opts)); - ASSERT_NE(nullptr, it1); - ASSERT_OK(it1->status()); - it1->Seek(keys[i]); - ASSERT_TRUE(it1->Valid()); - } - - for (int i = 2; i < 4; i++) { - std::string value; - Status s = db_->Get(read_opts, keys[i], &value); - ASSERT_TRUE(s.IsNotFound()); - } - } - Close(); -} - -INSTANTIATE_TEST_CASE_P( - Timestamp, DBBasicTestWithTimestampFilterPrefixSettings, - ::testing::Combine( - ::testing::Values( - std::shared_ptr(nullptr), - std::shared_ptr(NewBloomFilterPolicy(10, true)), - std::shared_ptr(NewBloomFilterPolicy(10, - false))), - ::testing::Bool(), ::testing::Bool(), - ::testing::Values( - std::shared_ptr(NewFixedPrefixTransform(1)), - std::shared_ptr(NewFixedPrefixTransform(4)), - std::shared_ptr(NewFixedPrefixTransform(7)), - std::shared_ptr(NewFixedPrefixTransform(8))), - ::testing::Bool(), ::testing::Values(0, 0.1), - ::testing::Values( - BlockBasedTableOptions::IndexType::kBinarySearch, - BlockBasedTableOptions::IndexType::kHashSearch, - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch, - BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey))); -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -class DataVisibilityTest : public DBBasicTestWithTimestampBase { - public: - DataVisibilityTest() : DBBasicTestWithTimestampBase("data_visibility_test") { - // Initialize test data - for (int i = 0; i < kTestDataSize; i++) { - test_data_[i].key = "key" + std::to_string(i); - test_data_[i].value = "value" + std::to_string(i); - test_data_[i].timestamp = Timestamp(i, 0); - test_data_[i].ts = i; - test_data_[i].seq_num = kMaxSequenceNumber; - } - } - - protected: - struct TestData { - std::string key; - std::string value; - int ts; - std::string timestamp; - SequenceNumber seq_num; - }; - - constexpr static int kTestDataSize = 3; - TestData test_data_[kTestDataSize]; - - void PutTestData(int index, ColumnFamilyHandle* cfh = nullptr) { - ASSERT_LE(index, kTestDataSize); - WriteOptions write_opts; - - if (cfh == nullptr) { - ASSERT_OK(db_->Put(write_opts, test_data_[index].key, - test_data_[index].timestamp, test_data_[index].value)); - const Snapshot* snap = db_->GetSnapshot(); - test_data_[index].seq_num = snap->GetSequenceNumber(); - if (index > 0) { - ASSERT_GT(test_data_[index].seq_num, test_data_[index - 1].seq_num); - } - db_->ReleaseSnapshot(snap); - } else { - ASSERT_OK(db_->Put(write_opts, cfh, test_data_[index].key, - test_data_[index].timestamp, test_data_[index].value)); - } - } - - void AssertVisibility(int ts, SequenceNumber seq, - std::vector statuses) { - ASSERT_EQ(kTestDataSize, statuses.size()); - for (int i = 0; i < kTestDataSize; i++) { - if (test_data_[i].seq_num <= seq && test_data_[i].ts <= ts) { - ASSERT_OK(statuses[i]); - } else { - ASSERT_TRUE(statuses[i].IsNotFound()); - } - } - } - - std::vector GetKeys() { - std::vector ret(kTestDataSize); - for (int i = 0; i < kTestDataSize; i++) { - ret[i] = test_data_[i].key; - } - return ret; - } - - void VerifyDefaultCF(int ts, const Snapshot* snap = nullptr) { - ReadOptions read_opts; - std::string read_ts = Timestamp(ts, 0); - Slice read_ts_slice = read_ts; - read_opts.timestamp = &read_ts_slice; - read_opts.snapshot = snap; - - ColumnFamilyHandle* cfh = db_->DefaultColumnFamily(); - std::vector cfs(kTestDataSize, cfh); - SequenceNumber seq = - snap ? snap->GetSequenceNumber() : kMaxSequenceNumber - 1; - - // There're several MultiGet interfaces with not exactly the same - // implementations, query data with all of them. - auto keys = GetKeys(); - std::vector values; - auto s1 = db_->MultiGet(read_opts, cfs, keys, &values); - AssertVisibility(ts, seq, s1); - - auto s2 = db_->MultiGet(read_opts, keys, &values); - AssertVisibility(ts, seq, s2); - - std::vector timestamps; - auto s3 = db_->MultiGet(read_opts, cfs, keys, &values, ×tamps); - AssertVisibility(ts, seq, s3); - - auto s4 = db_->MultiGet(read_opts, keys, &values, ×tamps); - AssertVisibility(ts, seq, s4); - - std::vector values_ps5(kTestDataSize); - std::vector s5(kTestDataSize); - db_->MultiGet(read_opts, cfh, kTestDataSize, keys.data(), values_ps5.data(), - s5.data()); - AssertVisibility(ts, seq, s5); - - std::vector values_ps6(kTestDataSize); - std::vector s6(kTestDataSize); - std::vector timestamps_array(kTestDataSize); - db_->MultiGet(read_opts, cfh, kTestDataSize, keys.data(), values_ps6.data(), - timestamps_array.data(), s6.data()); - AssertVisibility(ts, seq, s6); - - std::vector values_ps7(kTestDataSize); - std::vector s7(kTestDataSize); - db_->MultiGet(read_opts, kTestDataSize, cfs.data(), keys.data(), - values_ps7.data(), s7.data()); - AssertVisibility(ts, seq, s7); - - std::vector values_ps8(kTestDataSize); - std::vector s8(kTestDataSize); - db_->MultiGet(read_opts, kTestDataSize, cfs.data(), keys.data(), - values_ps8.data(), timestamps_array.data(), s8.data()); - AssertVisibility(ts, seq, s8); - } - - void VerifyDefaultCF(const Snapshot* snap = nullptr) { - for (int i = 0; i <= kTestDataSize; i++) { - VerifyDefaultCF(i, snap); - } - } -}; -constexpr int DataVisibilityTest::kTestDataSize; - -// Application specifies timestamp but not snapshot. -// reader writer -// ts'=90 -// ts=100 -// seq=10 -// seq'=11 -// write finishes -// GetImpl(ts,seq) -// It is OK to return if ts>=t1 AND seq>=s1. If ts>=t1 but seqDisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::GetImpl:3", - "DataVisibilityTest::PointLookupWithoutSnapshot1:BeforePut"}, - {"DataVisibilityTest::PointLookupWithoutSnapshot1:AfterPut", - "DBImpl::GetImpl:4"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - std::string write_ts = Timestamp(1, 0); - WriteOptions write_opts; - TEST_SYNC_POINT( - "DataVisibilityTest::PointLookupWithoutSnapshot1:BeforePut"); - Status s = db_->Put(write_opts, "foo", write_ts, "value"); - ASSERT_OK(s); - TEST_SYNC_POINT("DataVisibilityTest::PointLookupWithoutSnapshot1:AfterPut"); - }); - ReadOptions read_opts; - std::string read_ts_str = Timestamp(3, 0); - Slice read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - std::string value; - Status s = db_->Get(read_opts, "foo", &value); - - writer_thread.join(); - ASSERT_TRUE(s.IsNotFound()); - Close(); -} - -// Application specifies timestamp but not snapshot. -// reader writer -// ts'=90 -// ts=100 -// seq=10 -// seq'=11 -// write finishes -// Flush -// GetImpl(ts,seq) -// It is OK to return if ts>=t1 AND seq>=s1. If ts>=t1 but seqDisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::GetImpl:3", - "DataVisibilityTest::PointLookupWithoutSnapshot2:BeforePut"}, - {"DataVisibilityTest::PointLookupWithoutSnapshot2:AfterPut", - "DBImpl::GetImpl:4"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - std::string write_ts = Timestamp(1, 0); - WriteOptions write_opts; - TEST_SYNC_POINT( - "DataVisibilityTest::PointLookupWithoutSnapshot2:BeforePut"); - Status s = db_->Put(write_opts, "foo", write_ts, "value"); - ASSERT_OK(s); - ASSERT_OK(Flush()); - - write_ts = Timestamp(2, 0); - s = db_->Put(write_opts, "bar", write_ts, "value"); - ASSERT_OK(s); - TEST_SYNC_POINT("DataVisibilityTest::PointLookupWithoutSnapshot2:AfterPut"); - }); - ReadOptions read_opts; - std::string read_ts_str = Timestamp(3, 0); - Slice read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - std::string value; - Status s = db_->Get(read_opts, "foo", &value); - writer_thread.join(); - ASSERT_TRUE(s.IsNotFound()); - Close(); -} - -// Application specifies both timestamp and snapshot. -// reader writer -// seq=10 -// ts'=90 -// ts=100 -// seq'=11 -// write finishes -// GetImpl(ts,seq) -// Since application specifies both timestamp and snapshot, application expects -// to see data that visible in BOTH timestamp and sequence number. Therefore, -// can be returned only if t1<=ts AND s1<=seq. -TEST_F(DataVisibilityTest, PointLookupWithSnapshot1) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DataVisibilityTest::PointLookupWithSnapshot1:AfterTakingSnap", - "DataVisibilityTest::PointLookupWithSnapshot1:BeforePut"}, - {"DataVisibilityTest::PointLookupWithSnapshot1:AfterPut", - "DBImpl::GetImpl:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - std::string write_ts = Timestamp(1, 0); - WriteOptions write_opts; - TEST_SYNC_POINT("DataVisibilityTest::PointLookupWithSnapshot1:BeforePut"); - Status s = db_->Put(write_opts, "foo", write_ts, "value"); - TEST_SYNC_POINT("DataVisibilityTest::PointLookupWithSnapshot1:AfterPut"); - ASSERT_OK(s); - }); - ReadOptions read_opts; - const Snapshot* snap = db_->GetSnapshot(); - TEST_SYNC_POINT( - "DataVisibilityTest::PointLookupWithSnapshot1:AfterTakingSnap"); - read_opts.snapshot = snap; - std::string read_ts_str = Timestamp(3, 0); - Slice read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - std::string value; - Status s = db_->Get(read_opts, "foo", &value); - writer_thread.join(); - - ASSERT_TRUE(s.IsNotFound()); - - db_->ReleaseSnapshot(snap); - Close(); -} - -// Application specifies both timestamp and snapshot. -// reader writer -// seq=10 -// ts'=90 -// ts=100 -// seq'=11 -// write finishes -// Flush -// GetImpl(ts,seq) -// Since application specifies both timestamp and snapshot, application expects -// to see data that visible in BOTH timestamp and sequence number. Therefore, -// can be returned only if t1<=ts AND s1<=seq. -TEST_F(DataVisibilityTest, PointLookupWithSnapshot2) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DataVisibilityTest::PointLookupWithSnapshot2:AfterTakingSnap", - "DataVisibilityTest::PointLookupWithSnapshot2:BeforePut"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - std::string write_ts = Timestamp(1, 0); - WriteOptions write_opts; - TEST_SYNC_POINT("DataVisibilityTest::PointLookupWithSnapshot2:BeforePut"); - Status s = db_->Put(write_opts, "foo", write_ts, "value1"); - ASSERT_OK(s); - ASSERT_OK(Flush()); - - write_ts = Timestamp(2, 0); - s = db_->Put(write_opts, "bar", write_ts, "value2"); - ASSERT_OK(s); - }); - const Snapshot* snap = db_->GetSnapshot(); - TEST_SYNC_POINT( - "DataVisibilityTest::PointLookupWithSnapshot2:AfterTakingSnap"); - writer_thread.join(); - std::string read_ts_str = Timestamp(3, 0); - Slice read_ts = read_ts_str; - ReadOptions read_opts; - read_opts.snapshot = snap; - read_opts.timestamp = &read_ts; - std::string value; - Status s = db_->Get(read_opts, "foo", &value); - ASSERT_TRUE(s.IsNotFound()); - db_->ReleaseSnapshot(snap); - Close(); -} - -// Application specifies timestamp but not snapshot. -// reader writer -// ts'=90 -// ts=100 -// seq=10 -// seq'=11 -// write finishes -// scan(ts,seq) -// can be seen in scan as long as ts>=t1 AND seq>=s1. If ts>=t1 but -// seqDisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::NewIterator:3", - "DataVisibilityTest::RangeScanWithoutSnapshot:BeforePut"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - WriteOptions write_opts; - TEST_SYNC_POINT("DataVisibilityTest::RangeScanWithoutSnapshot:BeforePut"); - for (int i = 0; i < 3; ++i) { - std::string write_ts = Timestamp(i + 1, 0); - Status s = db_->Put(write_opts, "key" + std::to_string(i), write_ts, - "value" + std::to_string(i)); - ASSERT_OK(s); - } - }); - std::string read_ts_str = Timestamp(10, 0); - Slice read_ts = read_ts_str; - ReadOptions read_opts; - read_opts.total_order_seek = true; - read_opts.timestamp = &read_ts; - Iterator* it = db_->NewIterator(read_opts); - ASSERT_NE(nullptr, it); - writer_thread.join(); - it->SeekToFirst(); - ASSERT_FALSE(it->Valid()); - delete it; - Close(); -} - -// Application specifies both timestamp and snapshot. -// reader writer -// seq=10 -// ts'=90 -// ts=100 seq'=11 -// write finishes -// scan(ts,seq) -// can be seen by the scan only if t1<=ts AND s1<=seq. If t1<=ts -// but s1>seq, then the key should not be returned. -TEST_F(DataVisibilityTest, RangeScanWithSnapshot) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DataVisibilityTest::RangeScanWithSnapshot:AfterTakingSnapshot", - "DataVisibilityTest::RangeScanWithSnapshot:BeforePut"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - WriteOptions write_opts; - TEST_SYNC_POINT("DataVisibilityTest::RangeScanWithSnapshot:BeforePut"); - for (int i = 0; i < 3; ++i) { - std::string write_ts = Timestamp(i + 1, 0); - Status s = db_->Put(write_opts, "key" + std::to_string(i), write_ts, - "value" + std::to_string(i)); - ASSERT_OK(s); - } - }); - const Snapshot* snap = db_->GetSnapshot(); - TEST_SYNC_POINT( - "DataVisibilityTest::RangeScanWithSnapshot:AfterTakingSnapshot"); - - writer_thread.join(); - - std::string read_ts_str = Timestamp(10, 0); - Slice read_ts = read_ts_str; - ReadOptions read_opts; - read_opts.snapshot = snap; - read_opts.total_order_seek = true; - read_opts.timestamp = &read_ts; - Iterator* it = db_->NewIterator(read_opts); - ASSERT_NE(nullptr, it); - it->Seek("key0"); - ASSERT_FALSE(it->Valid()); - - delete it; - db_->ReleaseSnapshot(snap); - Close(); -} - -// Application specifies both timestamp and snapshot. -// Query each combination and make sure for MultiGet key , only -// return keys that ts>=t1 AND seq>=s1. -TEST_F(DataVisibilityTest, MultiGetWithTimestamp) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - const Snapshot* snap0 = db_->GetSnapshot(); - PutTestData(0); - VerifyDefaultCF(); - VerifyDefaultCF(snap0); - - const Snapshot* snap1 = db_->GetSnapshot(); - PutTestData(1); - VerifyDefaultCF(); - VerifyDefaultCF(snap0); - VerifyDefaultCF(snap1); - - ASSERT_OK(Flush()); - - const Snapshot* snap2 = db_->GetSnapshot(); - PutTestData(2); - VerifyDefaultCF(); - VerifyDefaultCF(snap0); - VerifyDefaultCF(snap1); - VerifyDefaultCF(snap2); - - db_->ReleaseSnapshot(snap0); - db_->ReleaseSnapshot(snap1); - db_->ReleaseSnapshot(snap2); - - Close(); -} - -// Application specifies timestamp but not snapshot. -// reader writer -// ts'=0, 1 -// ts=3 -// seq=10 -// seq'=11, 12 -// write finishes -// MultiGet(ts,seq) -// For MultiGet , only return keys that ts>=t1 AND seq>=s1. -TEST_F(DataVisibilityTest, MultiGetWithoutSnapshot) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::MultiGet:AfterGetSeqNum1", - "DataVisibilityTest::MultiGetWithoutSnapshot:BeforePut"}, - {"DataVisibilityTest::MultiGetWithoutSnapshot:AfterPut", - "DBImpl::MultiGet:AfterGetSeqNum2"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer_thread([this]() { - TEST_SYNC_POINT("DataVisibilityTest::MultiGetWithoutSnapshot:BeforePut"); - PutTestData(0); - PutTestData(1); - TEST_SYNC_POINT("DataVisibilityTest::MultiGetWithoutSnapshot:AfterPut"); - }); - - ReadOptions read_opts; - std::string read_ts = Timestamp(kTestDataSize, 0); - Slice read_ts_slice = read_ts; - read_opts.timestamp = &read_ts_slice; - auto keys = GetKeys(); - std::vector values; - auto ss = db_->MultiGet(read_opts, keys, &values); - - writer_thread.join(); - for (auto s : ss) { - ASSERT_TRUE(s.IsNotFound()); - } - VerifyDefaultCF(); - Close(); -} - -TEST_F(DataVisibilityTest, MultiGetCrossCF) { - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - CreateAndReopenWithCF({"second"}, options); - ColumnFamilyHandle* second_cf = handles_[1]; - - const Snapshot* snap0 = db_->GetSnapshot(); - PutTestData(0); - PutTestData(0, second_cf); - VerifyDefaultCF(); - VerifyDefaultCF(snap0); - - const Snapshot* snap1 = db_->GetSnapshot(); - PutTestData(1); - PutTestData(1, second_cf); - VerifyDefaultCF(); - VerifyDefaultCF(snap0); - VerifyDefaultCF(snap1); - - ASSERT_OK(Flush()); - - const Snapshot* snap2 = db_->GetSnapshot(); - PutTestData(2); - PutTestData(2, second_cf); - VerifyDefaultCF(); - VerifyDefaultCF(snap0); - VerifyDefaultCF(snap1); - VerifyDefaultCF(snap2); - - ReadOptions read_opts; - std::string read_ts = Timestamp(kTestDataSize, 0); - Slice read_ts_slice = read_ts; - read_opts.timestamp = &read_ts_slice; - read_opts.snapshot = snap1; - auto keys = GetKeys(); - auto keys2 = GetKeys(); - keys.insert(keys.end(), keys2.begin(), keys2.end()); - std::vector cfs(kTestDataSize, - db_->DefaultColumnFamily()); - std::vector cfs2(kTestDataSize, second_cf); - cfs.insert(cfs.end(), cfs2.begin(), cfs2.end()); - - std::vector values; - auto ss = db_->MultiGet(read_opts, cfs, keys, &values); - for (int i = 0; i < 2 * kTestDataSize; i++) { - if (i % 3 == 0) { - // only the first key for each column family should be returned - ASSERT_OK(ss[i]); - } else { - ASSERT_TRUE(ss[i].IsNotFound()); - } - } - - db_->ReleaseSnapshot(snap0); - db_->ReleaseSnapshot(snap1); - db_->ReleaseSnapshot(snap2); - Close(); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -class DBBasicTestWithTimestampCompressionSettings - : public DBBasicTestWithTimestampBase, - public testing::WithParamInterface< - std::tuple, CompressionType, - uint32_t, uint32_t>> { - public: - DBBasicTestWithTimestampCompressionSettings() - : DBBasicTestWithTimestampBase( - "db_basic_test_with_timestamp_compression") {} -}; - -TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGet) { - const int kNumKeysPerFile = 1024; - const size_t kNumTimestamps = 4; - Options options = CurrentOptions(); - options.create_if_missing = true; - options.env = env_; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - size_t ts_sz = Timestamp(0, 0).size(); - TestComparator test_cmp(ts_sz); - options.comparator = &test_cmp; - BlockBasedTableOptions bbto; - bbto.filter_policy = std::get<0>(GetParam()); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - const CompressionType comp_type = std::get<1>(GetParam()); -#if LZ4_VERSION_NUMBER < 10400 // r124+ - if (comp_type == kLZ4Compression || comp_type == kLZ4HCCompression) { - return; - } -#endif // LZ4_VERSION_NUMBER >= 10400 - if (!ZSTD_Supported() && comp_type == kZSTD) { - return; - } - if (!Zlib_Supported() && comp_type == kZlibCompression) { - return; - } - - options.compression = comp_type; - options.compression_opts.max_dict_bytes = std::get<2>(GetParam()); - if (comp_type == kZSTD) { - options.compression_opts.zstd_max_train_bytes = std::get<2>(GetParam()); - } - options.compression_opts.parallel_threads = std::get<3>(GetParam()); - options.target_file_size_base = 1 << 26; // 64MB - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(2, num_cfs); - std::vector write_ts_list; - std::vector read_ts_list; - - for (size_t i = 0; i != kNumTimestamps; ++i) { - write_ts_list.push_back(Timestamp(i * 2, 0)); - read_ts_list.push_back(Timestamp(1 + i * 2, 0)); - const Slice write_ts = write_ts_list.back(); - WriteOptions wopts; - for (int cf = 0; cf != static_cast(num_cfs); ++cf) { - for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps; ++j) { - ASSERT_OK( - db_->Put(wopts, handles_[cf], Key1(j), write_ts, - "value_" + std::to_string(j) + "_" + std::to_string(i))); - } - } - } - const auto& verify_db_func = [&]() { - for (size_t i = 0; i != kNumTimestamps; ++i) { - ReadOptions ropts; - const Slice read_ts = read_ts_list[i]; - ropts.timestamp = &read_ts; - for (int cf = 0; cf != static_cast(num_cfs); ++cf) { - ColumnFamilyHandle* cfh = handles_[cf]; - for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps; ++j) { - std::string value; - ASSERT_OK(db_->Get(ropts, cfh, Key1(j), &value)); - ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i), - value); - } - } - } - }; - verify_db_func(); - Close(); -} - -TEST_P(DBBasicTestWithTimestampCompressionSettings, PutDeleteGet) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - const int kNumKeysPerFile = 1024; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - BlockBasedTableOptions bbto; - bbto.filter_policy = std::get<0>(GetParam()); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - const CompressionType comp_type = std::get<1>(GetParam()); -#if LZ4_VERSION_NUMBER < 10400 // r124+ - if (comp_type == kLZ4Compression || comp_type == kLZ4HCCompression) { - return; - } -#endif // LZ4_VERSION_NUMBER >= 10400 - if (!ZSTD_Supported() && comp_type == kZSTD) { - return; - } - if (!Zlib_Supported() && comp_type == kZlibCompression) { - return; - } - - options.compression = comp_type; - options.compression_opts.max_dict_bytes = std::get<2>(GetParam()); - if (comp_type == kZSTD) { - options.compression_opts.zstd_max_train_bytes = std::get<2>(GetParam()); - } - options.compression_opts.parallel_threads = std::get<3>(GetParam()); - options.target_file_size_base = 1 << 26; // 64MB - - DestroyAndReopen(options); - - const size_t kNumL0Files = - static_cast(Options().level0_file_num_compaction_trigger); - { - // Half of the keys will go through Deletion and remaining half with - // SingleDeletion. Generate enough L0 files with ts=1 to trigger compaction - // to L1 - std::string ts = Timestamp(1, 0); - WriteOptions wopts; - for (size_t i = 0; i < kNumL0Files; ++i) { - for (int j = 0; j < kNumKeysPerFile; ++j) { - ASSERT_OK(db_->Put(wopts, Key1(j), ts, "value" + std::to_string(i))); - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - // Generate another L0 at ts=3 - ts = Timestamp(3, 0); - for (int i = 0; i < kNumKeysPerFile; ++i) { - std::string key_str = Key1(i); - Slice key(key_str); - if ((i % 3) == 0) { - if (i < kNumKeysPerFile / 2) { - ASSERT_OK(db_->Delete(wopts, key, ts)); - } else { - ASSERT_OK(db_->SingleDelete(wopts, key, ts)); - } - } else { - ASSERT_OK(db_->Put(wopts, key, ts, "new_value")); - } - } - ASSERT_OK(db_->Flush(FlushOptions())); - // Populate memtable at ts=5 - ts = Timestamp(5, 0); - for (int i = 0; i != kNumKeysPerFile; ++i) { - std::string key_str = Key1(i); - Slice key(key_str); - if ((i % 3) == 1) { - if (i < kNumKeysPerFile / 2) { - ASSERT_OK(db_->Delete(wopts, key, ts)); - } else { - ASSERT_OK(db_->SingleDelete(wopts, key, ts)); - } - } else if ((i % 3) == 2) { - ASSERT_OK(db_->Put(wopts, key, ts, "new_value_2")); - } - } - } - { - std::string ts_str = Timestamp(6, 0); - Slice ts = ts_str; - ReadOptions ropts; - ropts.timestamp = &ts; - for (uint64_t i = 0; i != static_cast(kNumKeysPerFile); ++i) { - std::string value; - std::string key_ts; - Status s = db_->Get(ropts, Key1(i), &value, &key_ts); - if ((i % 3) == 2) { - ASSERT_OK(s); - ASSERT_EQ("new_value_2", value); - ASSERT_EQ(Timestamp(5, 0), key_ts); - } else if ((i % 3) == 1) { - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ(Timestamp(5, 0), key_ts); - } else { - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ(Timestamp(3, 0), key_ts); - } - } - } -} - -// A class which remembers the name of each flushed file. -class FlushedFileCollector : public EventListener { - public: - FlushedFileCollector() {} - ~FlushedFileCollector() override {} - - void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { - InstrumentedMutexLock lock(&mutex_); - flushed_files_.push_back(info.file_path); - } - - std::vector GetFlushedFiles() { - std::vector result; - { - InstrumentedMutexLock lock(&mutex_); - result = flushed_files_; - } - return result; - } - - void ClearFlushedFiles() { - InstrumentedMutexLock lock(&mutex_); - flushed_files_.clear(); - } - - private: - std::vector flushed_files_; - InstrumentedMutex mutex_; -}; - -TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) { - const int kNumKeysPerFile = 1024; - const size_t kNumTimestamps = 2; - const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps; - const size_t kSplitPosBase = kNumKeysPerTimestamp / 2; - Options options = CurrentOptions(); - options.create_if_missing = true; - options.env = env_; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - - FlushedFileCollector* collector = new FlushedFileCollector(); - options.listeners.emplace_back(collector); - - size_t ts_sz = Timestamp(0, 0).size(); - TestComparator test_cmp(ts_sz); - options.comparator = &test_cmp; - BlockBasedTableOptions bbto; - bbto.filter_policy = std::get<0>(GetParam()); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - const CompressionType comp_type = std::get<1>(GetParam()); -#if LZ4_VERSION_NUMBER < 10400 // r124+ - if (comp_type == kLZ4Compression || comp_type == kLZ4HCCompression) { - return; - } -#endif // LZ4_VERSION_NUMBER >= 10400 - if (!ZSTD_Supported() && comp_type == kZSTD) { - return; - } - if (!Zlib_Supported() && comp_type == kZlibCompression) { - return; - } - - options.compression = comp_type; - options.compression_opts.max_dict_bytes = std::get<2>(GetParam()); - if (comp_type == kZSTD) { - options.compression_opts.zstd_max_train_bytes = std::get<2>(GetParam()); - } - options.compression_opts.parallel_threads = std::get<3>(GetParam()); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - size_t num_cfs = handles_.size(); - ASSERT_EQ(2, num_cfs); - std::vector write_ts_list; - std::vector read_ts_list; - - const auto& verify_records_func = [&](size_t i, size_t begin, size_t end, - ColumnFamilyHandle* cfh) { - std::string value; - std::string timestamp; - - ReadOptions ropts; - const Slice read_ts = read_ts_list[i]; - ropts.timestamp = &read_ts; - std::string expected_timestamp = - std::string(write_ts_list[i].data(), write_ts_list[i].size()); - - for (size_t j = begin; j <= end; ++j) { - ASSERT_OK(db_->Get(ropts, cfh, Key1(j), &value, ×tamp)); - ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i), value); - ASSERT_EQ(expected_timestamp, timestamp); - } - }; - - for (size_t i = 0; i != kNumTimestamps; ++i) { - write_ts_list.push_back(Timestamp(i * 2, 0)); - read_ts_list.push_back(Timestamp(1 + i * 2, 0)); - const Slice write_ts = write_ts_list.back(); - WriteOptions wopts; - for (int cf = 0; cf != static_cast(num_cfs); ++cf) { - size_t memtable_get_start = 0; - for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) { - ASSERT_OK( - db_->Put(wopts, handles_[cf], Key1(j), write_ts, - "value_" + std::to_string(j) + "_" + std::to_string(i))); - if (j == kSplitPosBase + i || j == kNumKeysPerTimestamp - 1) { - verify_records_func(i, memtable_get_start, j, handles_[cf]); - memtable_get_start = j + 1; - - // flush all keys with the same timestamp to two sst files, split at - // incremental positions such that lowerlevel[1].smallest.userkey == - // higherlevel[0].largest.userkey - ASSERT_OK(Flush(cf)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); // wait for flush (which - // is also a compaction) - - // compact files (2 at each level) to a lower level such that all - // keys with the same timestamp is at one level, with newer versions - // at higher levels. - CompactionOptions compact_opt; - compact_opt.compression = kNoCompression; - ASSERT_OK(db_->CompactFiles(compact_opt, handles_[cf], - collector->GetFlushedFiles(), - static_cast(kNumTimestamps - i))); - collector->ClearFlushedFiles(); - } - } - } - } - const auto& verify_db_func = [&]() { - for (size_t i = 0; i != kNumTimestamps; ++i) { - ReadOptions ropts; - const Slice read_ts = read_ts_list[i]; - ropts.timestamp = &read_ts; - std::string expected_timestamp(write_ts_list[i].data(), - write_ts_list[i].size()); - for (int cf = 0; cf != static_cast(num_cfs); ++cf) { - ColumnFamilyHandle* cfh = handles_[cf]; - verify_records_func(i, 0, kNumKeysPerTimestamp - 1, cfh); - } - } - }; - verify_db_func(); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, BatchWriteAndMultiGet) { - const int kNumKeysPerFile = 8192; - const size_t kNumTimestamps = 2; - const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps; - Options options = CurrentOptions(); - options.create_if_missing = true; - options.env = env_; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - options.memtable_prefix_bloom_size_ratio = 0.1; - options.memtable_whole_key_filtering = true; - - size_t ts_sz = Timestamp(0, 0).size(); - TestComparator test_cmp(ts_sz); - options.comparator = &test_cmp; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy( - 10 /*bits_per_key*/, false /*use_block_based_builder*/)); - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - size_t num_cfs = handles_.size(); - ASSERT_EQ(2, num_cfs); - std::vector write_ts_list; - std::vector read_ts_list; - - const auto& verify_records_func = [&](size_t i, ColumnFamilyHandle* cfh) { - std::vector keys; - std::vector key_vals; - std::vector values; - std::vector timestamps; - - for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) { - key_vals.push_back(Key1(j)); - } - for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) { - keys.push_back(key_vals[j]); - } - - ReadOptions ropts; - const Slice read_ts = read_ts_list[i]; - ropts.timestamp = &read_ts; - std::string expected_timestamp(write_ts_list[i].data(), - write_ts_list[i].size()); - - std::vector cfhs(keys.size(), cfh); - std::vector statuses = - db_->MultiGet(ropts, cfhs, keys, &values, ×tamps); - for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) { - ASSERT_OK(statuses[j]); - ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i), - values[j]); - ASSERT_EQ(expected_timestamp, timestamps[j]); - } - }; - - const std::string dummy_ts(ts_sz, '\0'); - for (size_t i = 0; i != kNumTimestamps; ++i) { - write_ts_list.push_back(Timestamp(i * 2, 0)); - read_ts_list.push_back(Timestamp(1 + i * 2, 0)); - const Slice& write_ts = write_ts_list.back(); - for (int cf = 0; cf != static_cast(num_cfs); ++cf) { - WriteOptions wopts; - WriteBatch batch(0, 0, 0, ts_sz); - for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) { - const std::string key = Key1(j); - const std::string value = - "value_" + std::to_string(j) + "_" + std::to_string(i); - ASSERT_OK(batch.Put(handles_[cf], key, value)); - } - ASSERT_OK(batch.UpdateTimestamps(write_ts, - [ts_sz](uint32_t) { return ts_sz; })); - ASSERT_OK(db_->Write(wopts, &batch)); - - verify_records_func(i, handles_[cf]); - - ASSERT_OK(Flush(cf)); - } - } - - const auto& verify_db_func = [&]() { - for (size_t i = 0; i != kNumTimestamps; ++i) { - ReadOptions ropts; - const Slice read_ts = read_ts_list[i]; - ropts.timestamp = &read_ts; - for (int cf = 0; cf != static_cast(num_cfs); ++cf) { - ColumnFamilyHandle* cfh = handles_[cf]; - verify_records_func(i, cfh); - } - } - }; - verify_db_func(); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MultiGetNoReturnTs) { - Options options = CurrentOptions(); - options.env = env_; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - WriteOptions write_opts; - std::string ts = Timestamp(1, 0); - ASSERT_OK(db_->Put(write_opts, "foo", ts, "value")); - ASSERT_OK(db_->Put(write_opts, "bar", ts, "value")); - ASSERT_OK(db_->Put(write_opts, "fooxxxxxxxxxxxxxxxx", ts, "value")); - ASSERT_OK(db_->Put(write_opts, "barxxxxxxxxxxxxxxxx", ts, "value")); - ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily(); - ts = Timestamp(2, 0); - Slice read_ts = ts; - ReadOptions read_opts; - read_opts.timestamp = &read_ts; - { - ColumnFamilyHandle* column_families[] = {cfh, cfh}; - Slice keys[] = {"foo", "bar"}; - PinnableSlice values[] = {PinnableSlice(), PinnableSlice()}; - Status statuses[] = {Status::OK(), Status::OK()}; - dbfull()->MultiGet(read_opts, /*num_keys=*/2, &column_families[0], &keys[0], - &values[0], &statuses[0], /*sorted_input=*/false); - for (const auto& s : statuses) { - ASSERT_OK(s); - } - } - { - ColumnFamilyHandle* column_families[] = {cfh, cfh, cfh, cfh}; - // Make user keys longer than configured timestamp size (16 bytes) to - // verify RocksDB does not use the trailing bytes 'x' as timestamp. - Slice keys[] = {"fooxxxxxxxxxxxxxxxx", "barxxxxxxxxxxxxxxxx", "foo", "bar"}; - PinnableSlice values[] = {PinnableSlice(), PinnableSlice(), PinnableSlice(), - PinnableSlice()}; - Status statuses[] = {Status::OK(), Status::OK(), Status::OK(), - Status::OK()}; - dbfull()->MultiGet(read_opts, /*num_keys=*/4, &column_families[0], &keys[0], - &values[0], &statuses[0], /*sorted_input=*/false); - for (const auto& s : statuses) { - ASSERT_OK(s); - } - } - Close(); -} - - -INSTANTIATE_TEST_CASE_P( - Timestamp, DBBasicTestWithTimestampCompressionSettings, - ::testing::Combine( - ::testing::Values(std::shared_ptr(nullptr), - std::shared_ptr( - NewBloomFilterPolicy(10, false))), - ::testing::Values(kNoCompression, kZlibCompression, kLZ4Compression, - kLZ4HCCompression, kZSTD), - ::testing::Values(0, 1 << 14), ::testing::Values(1, 4))); - -class DBBasicTestWithTimestampPrefixSeek - : public DBBasicTestWithTimestampBase, - public testing::WithParamInterface< - std::tuple, - std::shared_ptr, bool, - BlockBasedTableOptions::IndexType>> { - public: - DBBasicTestWithTimestampPrefixSeek() - : DBBasicTestWithTimestampBase( - "/db_basic_test_with_timestamp_prefix_seek") {} -}; - -TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) { - const size_t kNumKeysPerFile = 128; - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.prefix_extractor = std::get<0>(GetParam()); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - BlockBasedTableOptions bbto; - bbto.filter_policy = std::get<1>(GetParam()); - bbto.index_type = std::get<3>(GetParam()); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - DestroyAndReopen(options); - - const uint64_t kMaxKey = 0xffffffffffffffff; - const uint64_t kMinKey = 0xfffffffffffff000; - const std::vector write_ts_list = {Timestamp(3, 0xffffffff), - Timestamp(6, 0xffffffff)}; - WriteOptions write_opts; - { - for (size_t i = 0; i != write_ts_list.size(); ++i) { - for (uint64_t key = kMaxKey; key >= kMinKey; --key) { - Status s = db_->Put(write_opts, Key1(key), write_ts_list[i], - "value" + std::to_string(i)); - ASSERT_OK(s); - } - } - } - const std::vector read_ts_list = {Timestamp(5, 0xffffffff), - Timestamp(9, 0xffffffff)}; - { - ReadOptions read_opts; - read_opts.total_order_seek = false; - read_opts.prefix_same_as_start = std::get<2>(GetParam()); - fprintf(stdout, "%s %s %d\n", options.prefix_extractor->Name(), - bbto.filter_policy ? bbto.filter_policy->Name() : "null", - static_cast(read_opts.prefix_same_as_start)); - for (size_t i = 0; i != read_ts_list.size(); ++i) { - Slice read_ts = read_ts_list[i]; - read_opts.timestamp = &read_ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - - // Seek to kMaxKey - iter->Seek(Key1(kMaxKey)); - CheckIterUserEntry(iter.get(), Key1(kMaxKey), kTypeValue, - "value" + std::to_string(i), write_ts_list[i]); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - - // Seek to kMinKey - iter->Seek(Key1(kMinKey)); - CheckIterUserEntry(iter.get(), Key1(kMinKey), kTypeValue, - "value" + std::to_string(i), write_ts_list[i]); - iter->Prev(); - ASSERT_FALSE(iter->Valid()); - } - const std::vector targets = {kMinKey, kMinKey + 0x10, - kMinKey + 0x100, kMaxKey}; - const SliceTransform* const pe = options.prefix_extractor.get(); - ASSERT_NE(nullptr, pe); - const size_t kPrefixShift = - 8 * (Key1(0).size() - pe->Transform(Key1(0)).size()); - const uint64_t kPrefixMask = - ~((static_cast(1) << kPrefixShift) - 1); - const uint64_t kNumKeysWithinPrefix = - (static_cast(1) << kPrefixShift); - for (size_t i = 0; i != read_ts_list.size(); ++i) { - Slice read_ts = read_ts_list[i]; - read_opts.timestamp = &read_ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - // Forward and backward iterate. - for (size_t j = 0; j != targets.size(); ++j) { - std::string start_key = Key1(targets[j]); - uint64_t expected_ub = - (targets[j] & kPrefixMask) - 1 + kNumKeysWithinPrefix; - uint64_t expected_key = targets[j]; - size_t count = 0; - it->Seek(Key1(targets[j])); - while (it->Valid()) { - std::string saved_prev_key; - saved_prev_key.assign(it->key().data(), it->key().size()); - - // Out of prefix - if (!read_opts.prefix_same_as_start && - pe->Transform(saved_prev_key) != pe->Transform(start_key)) { - break; - } - CheckIterUserEntry(it.get(), Key1(expected_key), kTypeValue, - "value" + std::to_string(i), write_ts_list[i]); - ++count; - ++expected_key; - it->Next(); - } - ASSERT_EQ(expected_ub - targets[j] + 1, count); - - count = 0; - expected_key = targets[j]; - it->SeekForPrev(start_key); - uint64_t expected_lb = (targets[j] & kPrefixMask); - while (it->Valid()) { - // Out of prefix - if (!read_opts.prefix_same_as_start && - pe->Transform(it->key()) != pe->Transform(start_key)) { - break; - } - CheckIterUserEntry(it.get(), Key1(expected_key), kTypeValue, - "value" + std::to_string(i), write_ts_list[i]); - ++count; - --expected_key; - it->Prev(); - } - ASSERT_EQ(targets[j] - std::max(expected_lb, kMinKey) + 1, count); - } - } - } - Close(); -} - -// TODO(yanqin): consider handling non-fixed-length prefix extractors, e.g. -// NoopTransform. -INSTANTIATE_TEST_CASE_P( - Timestamp, DBBasicTestWithTimestampPrefixSeek, - ::testing::Combine( - ::testing::Values( - std::shared_ptr(NewFixedPrefixTransform(1)), - std::shared_ptr(NewFixedPrefixTransform(4)), - std::shared_ptr(NewFixedPrefixTransform(7)), - std::shared_ptr(NewFixedPrefixTransform(8))), - ::testing::Values(std::shared_ptr(nullptr), - std::shared_ptr( - NewBloomFilterPolicy(10 /*bits_per_key*/, false)), - std::shared_ptr( - NewBloomFilterPolicy(20 /*bits_per_key*/, - false))), - ::testing::Bool(), - ::testing::Values( - BlockBasedTableOptions::IndexType::kBinarySearch, - BlockBasedTableOptions::IndexType::kHashSearch, - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch, - BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey))); - -class DBBasicTestWithTsIterTombstones - : public DBBasicTestWithTimestampBase, - public testing::WithParamInterface< - std::tuple, - std::shared_ptr, int, - BlockBasedTableOptions::IndexType>> { - public: - DBBasicTestWithTsIterTombstones() - : DBBasicTestWithTimestampBase("/db_basic_ts_iter_tombstones") {} -}; - -TEST_P(DBBasicTestWithTsIterTombstones, IterWithDelete) { - constexpr size_t kNumKeysPerFile = 128; - Options options = CurrentOptions(); - options.env = env_; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.prefix_extractor = std::get<0>(GetParam()); - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - BlockBasedTableOptions bbto; - bbto.filter_policy = std::get<1>(GetParam()); - bbto.index_type = std::get<3>(GetParam()); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.num_levels = std::get<2>(GetParam()); - DestroyAndReopen(options); - std::vector write_ts_strs = {Timestamp(2, 0), Timestamp(4, 0)}; - constexpr uint64_t kMaxKey = 0xffffffffffffffff; - constexpr uint64_t kMinKey = 0xfffffffffffff000; - // Insert kMinKey...kMaxKey - uint64_t key = kMinKey; - WriteOptions write_opts; - Slice ts = write_ts_strs[0]; - do { - Status s = db_->Put(write_opts, Key1(key), write_ts_strs[0], - "value" + std::to_string(key)); - ASSERT_OK(s); - if (kMaxKey == key) { - break; - } - ++key; - } while (true); - - for (key = kMaxKey; key >= kMinKey; --key) { - Status s; - if (0 != (key % 2)) { - s = db_->Put(write_opts, Key1(key), write_ts_strs[1], - "value1" + std::to_string(key)); - } else { - s = db_->Delete(write_opts, Key1(key), write_ts_strs[1]); - } - ASSERT_OK(s); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - { - std::string read_ts = Timestamp(4, 0); - ts = read_ts; - ReadOptions read_opts; - read_opts.total_order_seek = true; - read_opts.timestamp = &ts; - std::unique_ptr iter(db_->NewIterator(read_opts)); - size_t count = 0; - key = kMinKey + 1; - for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++count, key += 2) { - ASSERT_EQ(Key1(key), iter->key()); - ASSERT_EQ("value1" + std::to_string(key), iter->value()); - } - ASSERT_EQ((kMaxKey - kMinKey + 1) / 2, count); - - for (iter->SeekToLast(), count = 0, key = kMaxKey; iter->Valid(); - key -= 2, ++count, iter->Prev()) { - ASSERT_EQ(Key1(key), iter->key()); - ASSERT_EQ("value1" + std::to_string(key), iter->value()); - } - ASSERT_EQ((kMaxKey - kMinKey + 1) / 2, count); - } - Close(); -} - -INSTANTIATE_TEST_CASE_P( - Timestamp, DBBasicTestWithTsIterTombstones, - ::testing::Combine( - ::testing::Values( - std::shared_ptr(NewFixedPrefixTransform(7)), - std::shared_ptr(NewFixedPrefixTransform(8))), - ::testing::Values(std::shared_ptr(nullptr), - std::shared_ptr( - NewBloomFilterPolicy(10, false)), - std::shared_ptr( - NewBloomFilterPolicy(20, false))), - ::testing::Values(2, 6), - ::testing::Values( - BlockBasedTableOptions::IndexType::kBinarySearch, - BlockBasedTableOptions::IndexType::kHashSearch, - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch, - BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey))); -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -class UpdateFullHistoryTsLowTest : public DBBasicTestWithTimestampBase { - public: - UpdateFullHistoryTsLowTest() - : DBBasicTestWithTimestampBase("/update_full_history_ts_low_test") {} -}; - -TEST_F(UpdateFullHistoryTsLowTest, ConcurrentUpdate) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - std::string lower_ts_low = Timestamp(10, 0); - std::string higher_ts_low = Timestamp(25, 0); - const size_t kTimestampSize = lower_ts_low.size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - - DestroyAndReopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - // This workaround swaps `lower_ts_low` originally used for update by the - // caller to `higher_ts_low` after its writer is queued to make sure - // the caller will always get a TryAgain error. - // It mimics cases where two threads update full_history_ts_low concurrently - // with one thread writing a higher ts_low and one thread writing a lower - // ts_low. - VersionEdit* version_edit; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::IncreaseFullHistoryTsLowImpl:BeforeEdit", - [&](void* arg) { version_edit = reinterpret_cast(arg); }); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:BeforeWriterWaiting", - [&](void* /*arg*/) { version_edit->SetFullHistoryTsLow(higher_ts_low); }); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_TRUE( - db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), lower_ts_low) - .IsTryAgain()); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, - GCPreserveRangeTombstoneWhenNoOrSmallFullHistoryLow) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - std::string ts_str = Timestamp(1, 0); - WriteOptions wopts; - ASSERT_OK(db_->Put(wopts, "k1", ts_str, "v1")); - ASSERT_OK(db_->Put(wopts, "k2", ts_str, "v2")); - ASSERT_OK(db_->Put(wopts, "k3", ts_str, "v3")); - ts_str = Timestamp(2, 0); - ASSERT_OK( - db_->DeleteRange(wopts, db_->DefaultColumnFamily(), "k1", "k3", ts_str)); - - ts_str = Timestamp(3, 0); - Slice ts = ts_str; - ReadOptions ropts; - ropts.timestamp = &ts; - CompactRangeOptions cro; - cro.full_history_ts_low = nullptr; - std::string value, key_ts; - Status s; - auto verify = [&] { - s = db_->Get(ropts, "k1", &value); - ASSERT_TRUE(s.IsNotFound()); - - s = db_->Get(ropts, "k2", &value, &key_ts); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ(key_ts, Timestamp(2, 0)); - - ASSERT_OK(db_->Get(ropts, "k3", &value, &key_ts)); - ASSERT_EQ(value, "v3"); - ASSERT_EQ(Timestamp(1, 0), key_ts); - - size_t batch_size = 3; - std::vector key_strs = {"k1", "k2", "k3"}; - std::vector keys{key_strs.begin(), key_strs.end()}; - std::vector values(batch_size); - std::vector statuses(batch_size); - db_->MultiGet(ropts, db_->DefaultColumnFamily(), batch_size, keys.data(), - values.data(), statuses.data(), true /* sorted_input */); - ASSERT_TRUE(statuses[0].IsNotFound()); - ASSERT_TRUE(statuses[1].IsNotFound()); - ASSERT_OK(statuses[2]); - ; - ASSERT_EQ(values[2], "v3"); - }; - verify(); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - verify(); - std::string lb = Timestamp(0, 0); - Slice lb_slice = lb; - cro.full_history_ts_low = &lb_slice; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - verify(); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, - GCRangeTombstonesAndCoveredKeysRespectingTslow) { - Options options = CurrentOptions(); - options.env = env_; - options.create_if_missing = true; - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.cache_index_and_filter_blocks = true; - bbto.whole_key_filtering = true; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.num_levels = 2; - DestroyAndReopen(options); - - WriteOptions wopts; - ASSERT_OK(db_->Put(wopts, "k1", Timestamp(1, 0), "v1")); - ASSERT_OK(db_->Delete(wopts, "k2", Timestamp(2, 0))); - ASSERT_OK(db_->DeleteRange(wopts, db_->DefaultColumnFamily(), "k1", "k3", - Timestamp(3, 0))); - ASSERT_OK(db_->Put(wopts, "k3", Timestamp(4, 0), "v3")); - - ReadOptions ropts; - std::string read_ts = Timestamp(5, 0); - Slice read_ts_slice = read_ts; - ropts.timestamp = &read_ts_slice; - size_t batch_size = 3; - std::vector key_strs = {"k1", "k2", "k3"}; - std::vector keys = {key_strs.begin(), key_strs.end()}; - std::vector values(batch_size); - std::vector statuses(batch_size); - std::vector timestamps(batch_size); - db_->MultiGet(ropts, db_->DefaultColumnFamily(), batch_size, keys.data(), - values.data(), timestamps.data(), statuses.data(), - true /* sorted_input */); - ASSERT_TRUE(statuses[0].IsNotFound()); - ASSERT_EQ(timestamps[0], Timestamp(3, 0)); - ASSERT_TRUE(statuses[1].IsNotFound()); - // DeleteRange has a higher timestamp than Delete for "k2" - ASSERT_EQ(timestamps[1], Timestamp(3, 0)); - ASSERT_OK(statuses[2]); - ASSERT_EQ(values[2], "v3"); - ASSERT_EQ(timestamps[2], Timestamp(4, 0)); - - CompactRangeOptions cro; - // Range tombstone has timestamp >= full_history_ts_low, covered keys - // are not dropped. - std::string compaction_ts_str = Timestamp(2, 0); - Slice compaction_ts = compaction_ts_str; - cro.full_history_ts_low = &compaction_ts; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ropts.timestamp = &compaction_ts; - std::string value, ts; - ASSERT_OK(db_->Get(ropts, "k1", &value, &ts)); - ASSERT_EQ(value, "v1"); - // timestamp is below full_history_ts_low, zeroed out as the key goes into - // bottommost level - ASSERT_EQ(ts, Timestamp(0, 0)); - ASSERT_TRUE(db_->Get(ropts, "k2", &value, &ts).IsNotFound()); - ASSERT_EQ(ts, Timestamp(2, 0)); - - compaction_ts_str = Timestamp(4, 0); - compaction_ts = compaction_ts_str; - cro.full_history_ts_low = &compaction_ts; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ropts.timestamp = &read_ts_slice; - // k1, k2 and the range tombstone should be dropped - // k3 should still exist - db_->MultiGet(ropts, db_->DefaultColumnFamily(), batch_size, keys.data(), - values.data(), timestamps.data(), statuses.data(), - true /* sorted_input */); - ASSERT_TRUE(statuses[0].IsNotFound()); - ASSERT_TRUE(timestamps[0].empty()); - ASSERT_TRUE(statuses[1].IsNotFound()); - ASSERT_TRUE(timestamps[1].empty()); - ASSERT_OK(statuses[2]); - ASSERT_EQ(values[2], "v3"); - ASSERT_EQ(timestamps[2], Timestamp(4, 0)); - - Close(); -} - -TEST_P(DBBasicTestWithTimestampTableOptions, DeleteRangeBaiscReadAndIterate) { - const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25; - Options options = CurrentOptions(); - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - options.compression = kNoCompression; - BlockBasedTableOptions bbto; - bbto.index_type = GetParam(); - bbto.block_size = 100; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.env = env_; - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - DestroyAndReopen(options); - - // Write half of the keys before the tombstone and half after the tombstone. - // Only covered keys (i.e., within the range and older than the tombstone) - // should be deleted. - for (int i = 0; i < kNum; ++i) { - if (i == kNum / 2) { - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key1(kRangeBegin), Key1(kRangeEnd), - Timestamp(i, 0))); - } - ASSERT_OK(db_->Put(WriteOptions(), Key1(i), Timestamp(i, 0), - "val" + std::to_string(i))); - if (i == kNum - kNumPerFile) { - ASSERT_OK(Flush()); - } - } - - ReadOptions read_opts; - read_opts.total_order_seek = true; - std::string read_ts = Timestamp(kNum, 0); - Slice read_ts_slice = read_ts; - read_opts.timestamp = &read_ts_slice; - { - std::unique_ptr iter(db_->NewIterator(read_opts)); - ASSERT_OK(iter->status()); - - int expected = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_EQ(Key1(expected), iter->key()); - if (expected == kRangeBegin - 1) { - expected = kNum / 2; - } else { - ++expected; - } - } - ASSERT_EQ(kNum, expected); - - expected = kNum / 2; - for (iter->Seek(Key1(kNum / 2)); iter->Valid(); iter->Next()) { - ASSERT_EQ(Key1(expected), iter->key()); - ++expected; - } - ASSERT_EQ(kNum, expected); - - expected = kRangeBegin - 1; - for (iter->SeekForPrev(Key1(kNum / 2 - 1)); iter->Valid(); iter->Prev()) { - ASSERT_EQ(Key1(expected), iter->key()); - --expected; - } - ASSERT_EQ(-1, expected); - - read_ts = Timestamp(0, 0); - read_ts_slice = read_ts; - read_opts.timestamp = &read_ts_slice; - iter.reset(db_->NewIterator(read_opts)); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), Key1(0)); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - } - - read_ts = Timestamp(kNum, 0); - read_ts_slice = read_ts; - read_opts.timestamp = &read_ts_slice; - std::string value, timestamp; - Status s; - for (int i = 0; i < kNum; ++i) { - s = db_->Get(read_opts, Key1(i), &value, ×tamp); - if (i >= kRangeBegin && i < kNum / 2) { - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ(timestamp, Timestamp(kNum / 2, 0)); - } else { - ASSERT_OK(s); - ASSERT_EQ(value, "val" + std::to_string(i)); - ASSERT_EQ(timestamp, Timestamp(i, 0)); - } - } - - size_t batch_size = kNum; - std::vector key_strs(batch_size); - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - std::vector timestamps(batch_size); - for (int i = 0; i < kNum; ++i) { - key_strs[i] = Key1(i); - keys[i] = key_strs[i]; - } - db_->MultiGet(read_opts, db_->DefaultColumnFamily(), batch_size, keys.data(), - values.data(), timestamps.data(), statuses.data(), - true /* sorted_input */); - for (int i = 0; i < kNum; ++i) { - if (i >= kRangeBegin && i < kNum / 2) { - ASSERT_TRUE(statuses[i].IsNotFound()); - ASSERT_EQ(timestamps[i], Timestamp(kNum / 2, 0)); - } else { - ASSERT_OK(statuses[i]); - ASSERT_EQ(values[i], "val" + std::to_string(i)); - ASSERT_EQ(timestamps[i], Timestamp(i, 0)); - } - } - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, DeleteRangeGetIteratorWithSnapshot) { - // 4 keys 0, 1, 2, 3 at timestamps 0, 1, 2, 3 respectively. - // A range tombstone [1, 3) at timestamp 1 and has a sequence number between - // key 1 and 2. - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - WriteOptions write_opts; - std::string put_ts = Timestamp(0, 0); - const int kNum = 4, kNumPerFile = 1, kRangeBegin = 1, kRangeEnd = 3; - options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile)); - const Snapshot* before_tombstone = nullptr; - const Snapshot* after_tombstone = nullptr; - for (int i = 0; i < kNum; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), Key1(i), Timestamp(i, 0), - "val" + std::to_string(i))); - if (i == kRangeBegin) { - before_tombstone = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key1(kRangeBegin), Key1(kRangeEnd), - Timestamp(kRangeBegin, 0))); - } - if (i == kNum / 2) { - ASSERT_OK(Flush()); - } - } - assert(before_tombstone); - after_tombstone = db_->GetSnapshot(); - // snapshot and ts before tombstone - std::string read_ts_str = Timestamp(kRangeBegin - 1, 0); // (0, 0) - Slice read_ts = read_ts_str; - ReadOptions read_opts; - read_opts.timestamp = &read_ts; - read_opts.snapshot = before_tombstone; - std::vector expected_status = { - Status::OK(), Status::NotFound(), Status::NotFound(), Status::NotFound()}; - std::vector expected_values(kNum); - expected_values[0] = "val" + std::to_string(0); - std::vector expected_timestamps(kNum); - expected_timestamps[0] = Timestamp(0, 0); - - size_t batch_size = kNum; - std::vector key_strs(batch_size); - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - std::vector timestamps(batch_size); - for (int i = 0; i < kNum; ++i) { - key_strs[i] = Key1(i); - keys[i] = key_strs[i]; - } - - auto verify = [&] { - db_->MultiGet(read_opts, db_->DefaultColumnFamily(), batch_size, - keys.data(), values.data(), timestamps.data(), - statuses.data(), true /* sorted_input */); - std::string value, timestamp; - Status s; - for (int i = 0; i < kNum; ++i) { - s = db_->Get(read_opts, Key1(i), &value, ×tamp); - ASSERT_EQ(s, expected_status[i]); - ASSERT_EQ(statuses[i], expected_status[i]); - if (s.ok()) { - ASSERT_EQ(value, expected_values[i]); - ASSERT_EQ(values[i], expected_values[i]); - } - if (!timestamp.empty()) { - ASSERT_EQ(timestamp, expected_timestamps[i]); - ASSERT_EQ(timestamps[i], expected_timestamps[i]); - } else { - ASSERT_TRUE(timestamps[i].empty()); - } - } - std::unique_ptr iter(db_->NewIterator(read_opts)); - std::unique_ptr iter_for_seek(db_->NewIterator(read_opts)); - iter->SeekToFirst(); - for (int i = 0; i < kNum; ++i) { - if (expected_status[i].ok()) { - auto verify_iter = [&](Iterator* iter_ptr) { - ASSERT_TRUE(iter_ptr->Valid()); - ASSERT_EQ(iter_ptr->key(), keys[i]); - ASSERT_EQ(iter_ptr->value(), expected_values[i]); - ASSERT_EQ(iter_ptr->timestamp(), expected_timestamps[i]); - }; - verify_iter(iter.get()); - iter->Next(); - - iter_for_seek->Seek(keys[i]); - verify_iter(iter_for_seek.get()); - - iter_for_seek->SeekForPrev(keys[i]); - verify_iter(iter_for_seek.get()); - } - } - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - }; - - verify(); - - // snapshot before tombstone and ts after tombstone - read_ts_str = Timestamp(kNum, 0); // (4, 0) - read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - read_opts.snapshot = before_tombstone; - expected_status[1] = Status::OK(); - expected_timestamps[1] = Timestamp(1, 0); - expected_values[1] = "val" + std::to_string(1); - verify(); - - // snapshot after tombstone and ts before tombstone - read_ts_str = Timestamp(kRangeBegin - 1, 0); // (0, 0) - read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - read_opts.snapshot = after_tombstone; - expected_status[1] = Status::NotFound(); - expected_timestamps[1].clear(); - expected_values[1].clear(); - verify(); - - // snapshot and ts after tombstone - read_ts_str = Timestamp(kNum, 0); // (4, 0) - read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - read_opts.snapshot = after_tombstone; - for (int i = 0; i < kNum; ++i) { - if (i == kRangeBegin) { - expected_status[i] = Status::NotFound(); - expected_values[i].clear(); - } else { - expected_status[i] = Status::OK(); - expected_values[i] = "val" + std::to_string(i); - } - expected_timestamps[i] = Timestamp(i, 0); - } - verify(); - - db_->ReleaseSnapshot(before_tombstone); - db_->ReleaseSnapshot(after_tombstone); - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MergeBasic) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.merge_operator = std::make_shared('.'); - DestroyAndReopen(options); - - const std::array write_ts_strs = { - Timestamp(100, 0), Timestamp(200, 0), Timestamp(300, 0)}; - constexpr size_t kNumOfUniqKeys = 100; - ColumnFamilyHandle* default_cf = db_->DefaultColumnFamily(); - - for (size_t i = 0; i < write_ts_strs.size(); ++i) { - for (size_t j = 0; j < kNumOfUniqKeys; ++j) { - Status s; - if (i == 0) { - const std::string val = "v" + std::to_string(j) + "_0"; - s = db_->Put(WriteOptions(), Key1(j), write_ts_strs[i], val); - } else { - const std::string merge_op = std::to_string(i); - s = db_->Merge(WriteOptions(), default_cf, Key1(j), write_ts_strs[i], - merge_op); - } - ASSERT_OK(s); - } - } - - std::array read_ts_strs = { - Timestamp(150, 0), Timestamp(250, 0), Timestamp(350, 0)}; - - const auto verify_db_with_get = [&]() { - for (size_t i = 0; i < kNumOfUniqKeys; ++i) { - const std::string base_val = "v" + std::to_string(i) + "_0"; - const std::array expected_values = { - base_val, base_val + ".1", base_val + ".1.2"}; - const std::array& expected_ts = write_ts_strs; - ReadOptions read_opts; - for (size_t j = 0; j < read_ts_strs.size(); ++j) { - Slice read_ts = read_ts_strs[j]; - read_opts.timestamp = &read_ts; - std::string value; - std::string ts; - const Status s = db_->Get(read_opts, Key1(i), &value, &ts); - ASSERT_OK(s); - ASSERT_EQ(expected_values[j], value); - ASSERT_EQ(expected_ts[j], ts); - - // Do Seek/SeekForPrev - std::unique_ptr it(db_->NewIterator(read_opts)); - it->Seek(Key1(i)); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ(expected_values[j], it->value()); - ASSERT_EQ(expected_ts[j], it->timestamp()); - - it->SeekForPrev(Key1(i)); - ASSERT_TRUE(it->Valid()); - ASSERT_EQ(expected_values[j], it->value()); - ASSERT_EQ(expected_ts[j], it->timestamp()); - } - } - }; - - const auto verify_db_with_iterator = [&]() { - std::string value_suffix; - for (size_t i = 0; i < read_ts_strs.size(); ++i) { - ReadOptions read_opts; - Slice read_ts = read_ts_strs[i]; - read_opts.timestamp = &read_ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - size_t key_int_val = 0; - for (it->SeekToFirst(); it->Valid(); it->Next(), ++key_int_val) { - const std::string key = Key1(key_int_val); - const std::string value = - "v" + std::to_string(key_int_val) + "_0" + value_suffix; - ASSERT_EQ(key, it->key()); - ASSERT_EQ(value, it->value()); - ASSERT_EQ(write_ts_strs[i], it->timestamp()); - } - ASSERT_EQ(kNumOfUniqKeys, key_int_val); - - key_int_val = kNumOfUniqKeys - 1; - for (it->SeekToLast(); it->Valid(); it->Prev(), --key_int_val) { - const std::string key = Key1(key_int_val); - const std::string value = - "v" + std::to_string(key_int_val) + "_0" + value_suffix; - ASSERT_EQ(key, it->key()); - ASSERT_EQ(value, it->value()); - ASSERT_EQ(write_ts_strs[i], it->timestamp()); - } - ASSERT_EQ(std::numeric_limits::max(), key_int_val); - - value_suffix = value_suffix + "." + std::to_string(i + 1); - } - }; - - verify_db_with_get(); - verify_db_with_iterator(); - - ASSERT_OK(db_->Flush(FlushOptions())); - - verify_db_with_get(); - verify_db_with_iterator(); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, MergeAfterDeletion) { - Options options = GetDefaultOptions(); - options.create_if_missing = true; - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - options.merge_operator = std::make_shared('.'); - DestroyAndReopen(options); - - ColumnFamilyHandle* const column_family = db_->DefaultColumnFamily(); - - const size_t num_keys_per_file = 10; - const size_t num_merges_per_key = 2; - for (size_t i = 0; i < num_keys_per_file; ++i) { - std::string ts = Timestamp(i + 10000, 0); - Status s = db_->Delete(WriteOptions(), Key1(i), ts); - ASSERT_OK(s); - for (size_t j = 1; j <= num_merges_per_key; ++j) { - ts = Timestamp(i + 10000 + j, 0); - s = db_->Merge(WriteOptions(), column_family, Key1(i), ts, - std::to_string(j)); - ASSERT_OK(s); - } - } - - const auto verify_db = [&]() { - ReadOptions read_opts; - std::string read_ts_str = Timestamp(20000, 0); - Slice ts = read_ts_str; - read_opts.timestamp = &ts; - std::unique_ptr it(db_->NewIterator(read_opts)); - size_t count = 0; - for (it->SeekToFirst(); it->Valid(); it->Next(), ++count) { - std::string key = Key1(count); - ASSERT_EQ(key, it->key()); - std::string value; - for (size_t j = 1; j <= num_merges_per_key; ++j) { - value.append(std::to_string(j)); - if (j < num_merges_per_key) { - value.push_back('.'); - } - } - ASSERT_EQ(value, it->value()); - std::string ts1 = Timestamp(count + 10000 + num_merges_per_key, 0); - ASSERT_EQ(ts1, it->timestamp()); - } - ASSERT_OK(it->status()); - ASSERT_EQ(num_keys_per_file, count); - for (it->SeekToLast(); it->Valid(); it->Prev(), --count) { - std::string key = Key1(count - 1); - ASSERT_EQ(key, it->key()); - std::string value; - for (size_t j = 1; j <= num_merges_per_key; ++j) { - value.append(std::to_string(j)); - if (j < num_merges_per_key) { - value.push_back('.'); - } - } - ASSERT_EQ(value, it->value()); - std::string ts1 = Timestamp(count - 1 + 10000 + num_merges_per_key, 0); - ASSERT_EQ(ts1, it->timestamp()); - } - ASSERT_OK(it->status()); - ASSERT_EQ(0, count); - }; - - verify_db(); - - Close(); -} - -TEST_F(DBBasicTestWithTimestamp, RangeTombstoneApproximateSize) { - // Test code path for calculating range tombstone compensated size - // during flush and compaction. - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - // So that the compaction below is non-bottommost and will calcualte - // compensated range tombstone size. - ASSERT_OK(db_->Put(WriteOptions(), Key(1), Timestamp(1, 0), "val")); - ASSERT_OK(Flush()); - MoveFilesToLevel(5); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), - Key(1), Timestamp(1, 0))); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(1), - Key(2), Timestamp(2, 0))); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check(db_->DefaultColumnFamily()) - ->cfd(), - 0 /* input_level */, 1 /* output_level */, CompactRangeOptions(), - nullptr /* begin */, nullptr /* end */, true /* exclusive */, - true /* disallow_trivial_move */, - std::numeric_limits::max() /* max_file_num_to_ignore */, - "" /*trim_ts*/)); -} - -TEST_F(DBBasicTestWithTimestamp, IterSeekToLastWithIterateUpperbound) { - // Test for a bug fix where DBIter::SeekToLast() could fail when - // iterate_upper_bound and iter_start_ts are both set. - Options options = CurrentOptions(); - const size_t kTimestampSize = Timestamp(0, 0).size(); - TestComparator test_cmp(kTimestampSize); - options.comparator = &test_cmp; - DestroyAndReopen(options); - - ASSERT_OK(db_->Put(WriteOptions(), Key(1), Timestamp(2, 0), "val")); - ReadOptions ro; - std::string k = Key(1); - Slice k_slice = k; - ro.iterate_upper_bound = &k_slice; - std::string ts = Timestamp(3, 0); - Slice read_ts = ts; - ro.timestamp = &read_ts; - std::string start_ts = Timestamp(0, 0); - Slice start_ts_slice = start_ts; - ro.iter_start_ts = &start_ts_slice; - std::unique_ptr iter{db_->NewIterator(ro)}; - iter->SeekToLast(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_with_timestamp_compaction_test.cc b/db/db_with_timestamp_compaction_test.cc deleted file mode 100644 index 7d80c85c4..000000000 --- a/db/db_with_timestamp_compaction_test.cc +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/compaction/compaction.h" -#include "db/db_test_util.h" -#include "port/stack_trace.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -std::string Key1(uint64_t key) { - std::string ret; - PutFixed64(&ret, key); - std::reverse(ret.begin(), ret.end()); - return ret; -} - -std::string Timestamp(uint64_t ts) { - std::string ret; - PutFixed64(&ret, ts); - return ret; -} -} // anonymous namespace - -class TimestampCompatibleCompactionTest : public DBTestBase { - public: - TimestampCompatibleCompactionTest() - : DBTestBase("ts_compatible_compaction_test", /*env_do_fsync=*/true) {} - - std::string Get(const std::string& key, uint64_t ts) { - ReadOptions read_opts; - std::string ts_str = Timestamp(ts); - Slice ts_slice = ts_str; - read_opts.timestamp = &ts_slice; - std::string value; - Status s = db_->Get(read_opts, key, &value); - if (s.IsNotFound()) { - value.assign("NOT_FOUND"); - } else if (!s.ok()) { - value.assign(s.ToString()); - } - return value; - } -}; - -TEST_F(TimestampCompatibleCompactionTest, UserKeyCrossFileBoundary) { - Options options = CurrentOptions(); - options.env = env_; - options.compaction_style = kCompactionStyleLevel; - options.comparator = test::BytewiseComparatorWithU64TsWrapper(); - options.level0_file_num_compaction_trigger = 3; - constexpr size_t kNumKeysPerFile = 101; - options.memtable_factory.reset( - test::NewSpecialSkipListFactory(kNumKeysPerFile)); - DestroyAndReopen(options); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { - const auto* compaction = reinterpret_cast(arg); - ASSERT_NE(nullptr, compaction); - ASSERT_EQ(0, compaction->start_level()); - ASSERT_EQ(1, compaction->num_input_levels()); - // Check that all 3 L0 ssts are picked for level compaction. - ASSERT_EQ(3, compaction->num_input_files(0)); - }); - SyncPoint::GetInstance()->EnableProcessing(); - // Write a L0 with keys 0, 1, ..., 99 with ts from 100 to 199. - uint64_t ts = 100; - uint64_t key = 0; - WriteOptions write_opts; - for (; key < kNumKeysPerFile - 1; ++key, ++ts) { - std::string ts_str = Timestamp(ts); - ASSERT_OK( - db_->Put(write_opts, Key1(key), ts_str, "foo_" + std::to_string(key))); - } - // Write another L0 with keys 99 with newer ts. - ASSERT_OK(Flush()); - uint64_t saved_read_ts1 = ts++; - key = 99; - for (int i = 0; i < 4; ++i, ++ts) { - std::string ts_str = Timestamp(ts); - ASSERT_OK( - db_->Put(write_opts, Key1(key), ts_str, "bar_" + std::to_string(key))); - } - ASSERT_OK(Flush()); - uint64_t saved_read_ts2 = ts++; - // Write another L0 with keys 99, 100, 101, ..., 150 - for (; key <= 150; ++key, ++ts) { - std::string ts_str = Timestamp(ts); - ASSERT_OK( - db_->Put(write_opts, Key1(key), ts_str, "foo1_" + std::to_string(key))); - } - ASSERT_OK(Flush()); - // Wait for compaction to finish - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - uint64_t read_ts = ts; - ASSERT_EQ("foo_99", Get(Key1(99), saved_read_ts1)); - ASSERT_EQ("bar_99", Get(Key1(99), saved_read_ts2)); - ASSERT_EQ("foo1_99", Get(Key1(99), read_ts)); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(TimestampCompatibleCompactionTest, MultipleSubCompactions) { - Options options = CurrentOptions(); - options.env = env_; - options.compaction_style = kCompactionStyleUniversal; - options.comparator = test::BytewiseComparatorWithU64TsWrapper(); - options.level0_file_num_compaction_trigger = 3; - options.max_subcompactions = 3; - options.target_file_size_base = 1024; - options.statistics = CreateDBStatistics(); - DestroyAndReopen(options); - - uint64_t ts = 100; - uint64_t key = 0; - WriteOptions write_opts; - - // Write keys 0, 1, ..., 499 with ts from 100 to 599. - { - for (; key <= 499; ++key, ++ts) { - std::string ts_str = Timestamp(ts); - ASSERT_OK(db_->Put(write_opts, Key1(key), ts_str, - "foo_" + std::to_string(key))); - } - } - - // Write keys 500, ..., 999 with ts from 600 to 1099. - { - for (; key <= 999; ++key, ++ts) { - std::string ts_str = Timestamp(ts); - ASSERT_OK(db_->Put(write_opts, Key1(key), ts_str, - "foo_" + std::to_string(key))); - } - ASSERT_OK(Flush()); - } - - // Wait for compaction to finish - { - ASSERT_OK(dbfull()->RunManualCompaction( - static_cast_with_check( - db_->DefaultColumnFamily()) - ->cfd(), - 0 /* input_level */, 1 /* output_level */, CompactRangeOptions(), - nullptr /* begin */, nullptr /* end */, true /* exclusive */, - true /* disallow_trivial_move */, - std::numeric_limits::max() /* max_file_num_to_ignore */, - "" /*trim_ts*/)); - } - - // Check stats to make sure multiple subcompactions were scheduled for - // boundaries not to be nullptr. - { - HistogramData num_sub_compactions; - options.statistics->histogramData(NUM_SUBCOMPACTIONS_SCHEDULED, - &num_sub_compactions); - ASSERT_GT(num_sub_compactions.sum, 1); - } - - for (key = 0; key <= 999; ++key) { - ASSERT_EQ("foo_" + std::to_string(key), Get(Key1(key), ts)); - } -} - -class TestFilePartitioner : public SstPartitioner { - public: - explicit TestFilePartitioner() {} - ~TestFilePartitioner() override {} - - const char* Name() const override { return "TestFilePartitioner"; } - PartitionerResult ShouldPartition( - const PartitionerRequest& /*request*/) override { - return PartitionerResult::kRequired; - } - bool CanDoTrivialMove(const Slice& /*smallest_user_key*/, - const Slice& /*largest_user_key*/) override { - return false; - } -}; - -class TestFilePartitionerFactory : public SstPartitionerFactory { - public: - explicit TestFilePartitionerFactory() {} - std::unique_ptr CreatePartitioner( - const SstPartitioner::Context& /*context*/) const override { - std::unique_ptr ret = - std::make_unique(); - return ret; - } - const char* Name() const override { return "TestFilePartitionerFactory"; } -}; - -TEST_F(TimestampCompatibleCompactionTest, CompactFilesRangeCheckL0) { - Options options = CurrentOptions(); - options.env = env_; - options.sst_partitioner_factory = - std::make_shared(); - options.comparator = test::BytewiseComparatorWithU64TsWrapper(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - constexpr int kNumFiles = 10; - constexpr int kKeysPerFile = 2; - const std::string user_key = "foo"; - constexpr uint64_t start_ts = 10000; - - uint64_t cur_ts = start_ts; - for (int k = 0; k < kNumFiles; ++k) { - for (int i = 0; i < kKeysPerFile; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), user_key, Timestamp(cur_ts), - "v" + std::to_string(i))); - ++cur_ts; - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - - std::vector input_files{}; - { - std::vector files; - ASSERT_OK(env_->GetChildren(dbname_, &files)); - for (const auto& f : files) { - uint64_t file_num = 0; - FileType file_type = FileType::kWalFile; - if (!ParseFileName(f, &file_num, &file_type) || - file_type != FileType::kTableFile) { - continue; - } - input_files.emplace_back(f); - } - // sorting here by name, which also happens to sort by generation date. - std::sort(input_files.begin(), input_files.end()); - assert(kNumFiles == input_files.size()); - std::vector tmp; - tmp.emplace_back(input_files[input_files.size() / 2]); - input_files.swap(tmp); - } - - { - std::vector output_file_names; - CompactionJobInfo compaction_job_info; - ASSERT_OK(db_->CompactFiles(CompactionOptions(), input_files, - /*output_level=*/1, /*output_path_id=*/-1, - &output_file_names, &compaction_job_info)); - // We expect the L0 files older than the original provided input were all - // included in the compaction. - ASSERT_EQ(static_cast(kNumFiles / 2 + 1), - compaction_job_info.input_files.size()); - } -} - -TEST_F(TimestampCompatibleCompactionTest, CompactFilesRangeCheckL1) { - Options options = CurrentOptions(); - options.env = env_; - options.sst_partitioner_factory = - std::make_shared(); - options.comparator = test::BytewiseComparatorWithU64TsWrapper(); - - constexpr int kNumFiles = 4; - options.level0_file_num_compaction_trigger = kNumFiles; - - DestroyAndReopen(options); - - constexpr int kKeysPerFile = 2; - const std::string user_key = "foo"; - constexpr uint64_t start_ts = 10000; - - uint64_t cur_ts = start_ts; - // Generate some initial files in both L0 and L1. - for (int k = 0; k < kNumFiles; ++k) { - for (int i = 0; i < kKeysPerFile; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), user_key, Timestamp(cur_ts), - "v" + std::to_string(i))); - ++cur_ts; - } - ASSERT_OK(db_->Flush(FlushOptions())); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/0, /*cf=*/0)); - ASSERT_EQ(kNumFiles * kKeysPerFile, - NumTableFilesAtLevel(/*level=*/1, /*cf=*/0)); - - constexpr int additional_l0s = 2; - for (int i = 0; i < additional_l0s; ++i, ++cur_ts) { - ASSERT_OK(db_->Put(WriteOptions(), user_key, Timestamp(cur_ts), "v")); - ASSERT_OK(db_->Flush(FlushOptions())); - } - ASSERT_EQ(additional_l0s, NumTableFilesAtLevel(/*level=*/0, /*cf=*/0)); - - std::vector inputs; - { - std::vector fmetas; - db_->GetLiveFilesMetaData(&fmetas); - bool included_one_l1 = false; - for (const auto& meta : fmetas) { - if (meta.level == 0) { - inputs.emplace_back(meta.relative_filename); - } else if (!included_one_l1) { - inputs.emplace_back(meta.relative_filename); - included_one_l1 = true; - } - } - } - ASSERT_EQ(static_cast(3), inputs.size()); - { - std::vector output_file_names; - CompactionJobInfo compaction_job_info; - - ASSERT_OK(db_->CompactFiles(CompactionOptions(), inputs, /*output_level=*/1, - /*output_path_id=*/-1, &output_file_names, - &compaction_job_info)); - ASSERT_EQ(kNumFiles * kKeysPerFile + 2, output_file_names.size()); - ASSERT_EQ(kNumFiles * kKeysPerFile + 2, - static_cast(compaction_job_info.input_files.size())); - } -} - -TEST_F(TimestampCompatibleCompactionTest, EmptyCompactionOutput) { - Options options = CurrentOptions(); - options.env = env_; - options.comparator = test::BytewiseComparatorWithU64TsWrapper(); - DestroyAndReopen(options); - - std::string ts_str = Timestamp(1); - WriteOptions wopts; - ASSERT_OK( - db_->DeleteRange(wopts, db_->DefaultColumnFamily(), "k1", "k3", ts_str)); - ASSERT_OK(Flush()); - - ts_str = Timestamp(3); - Slice ts = ts_str; - CompactRangeOptions cro; - // range tombstone will be dropped during compaction - cro.full_history_ts_low = &ts; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_write_buffer_manager_test.cc b/db/db_write_buffer_manager_test.cc deleted file mode 100644 index 294244547..000000000 --- a/db/db_write_buffer_manager_test.cc +++ /dev/null @@ -1,860 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "db/write_thread.h" -#include "port/stack_trace.h" - -namespace ROCKSDB_NAMESPACE { - -class DBWriteBufferManagerTest : public DBTestBase, - public testing::WithParamInterface { - public: - DBWriteBufferManagerTest() - : DBTestBase("db_write_buffer_manager_test", /*env_do_fsync=*/false) {} - bool cost_cache_; -}; - -TEST_P(DBWriteBufferManagerTest, SharedBufferAcrossCFs1) { - Options options = CurrentOptions(); - options.arena_block_size = 4096; - options.write_buffer_size = 500000; // this is never hit - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); - ASSERT_LT(cache->GetUsage(), 256 * 1024); - cost_cache_ = GetParam(); - - if (cost_cache_) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, cache, true)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } - - WriteOptions wo; - wo.disableWAL = true; - - CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options); - ASSERT_OK(Put(3, Key(1), DummyString(1), wo)); - Flush(3); - ASSERT_OK(Put(3, Key(1), DummyString(1), wo)); - ASSERT_OK(Put(0, Key(1), DummyString(1), wo)); - Flush(0); - - // Write to "Default", "cf2" and "cf3". - ASSERT_OK(Put(3, Key(1), DummyString(30000), wo)); - ASSERT_OK(Put(0, Key(1), DummyString(40000), wo)); - ASSERT_OK(Put(2, Key(1), DummyString(1), wo)); - - ASSERT_OK(Put(3, Key(2), DummyString(40000), wo)); - // WriteBufferManager::buffer_size_ has exceeded after the previous write is - // completed. - - // This make sures write will go through and if stall was in effect, it will - // end. - ASSERT_OK(Put(0, Key(2), DummyString(1), wo)); -} - -// Test Single DB with multiple writer threads get blocked when -// WriteBufferManager execeeds buffer_size_ and flush is waiting to be -// finished. -TEST_P(DBWriteBufferManagerTest, SharedWriteBufferAcrossCFs2) { - Options options = CurrentOptions(); - options.arena_block_size = 4096; - options.write_buffer_size = 500000; // this is never hit - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); - ASSERT_LT(cache->GetUsage(), 256 * 1024); - cost_cache_ = GetParam(); - - if (cost_cache_) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, cache, true)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } - WriteOptions wo; - wo.disableWAL = true; - - CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options); - ASSERT_OK(Put(3, Key(1), DummyString(1), wo)); - Flush(3); - ASSERT_OK(Put(3, Key(1), DummyString(1), wo)); - ASSERT_OK(Put(0, Key(1), DummyString(1), wo)); - Flush(0); - - // Write to "Default", "cf2" and "cf3". No flush will be triggered. - ASSERT_OK(Put(3, Key(1), DummyString(30000), wo)); - ASSERT_OK(Put(0, Key(1), DummyString(40000), wo)); - ASSERT_OK(Put(2, Key(1), DummyString(1), wo)); - - ASSERT_OK(Put(3, Key(2), DummyString(40000), wo)); - // WriteBufferManager::buffer_size_ has exceeded after the previous write is - // completed. - - std::unordered_set w_set; - std::vector threads; - int wait_count_db = 0; - int num_writers = 4; - InstrumentedMutex mutex; - InstrumentedCondVar cv(&mutex); - std::atomic thread_num(0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0", - "DBImpl::BackgroundCallFlush:start"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WBMStallInterface::BlockDB", [&](void*) { - InstrumentedMutexLock lock(&mutex); - wait_count_db++; - cv.SignalAll(); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::WriteStall::Wait", [&](void* arg) { - InstrumentedMutexLock lock(&mutex); - WriteThread::Writer* w = reinterpret_cast(arg); - w_set.insert(w); - // Allow the flush to continue if all writer threads are blocked. - if (w_set.size() == (unsigned long)num_writers) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - bool s = true; - - std::function writer = [&](int cf) { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - Status tmp = Put(cf, Slice(key), DummyString(1), wo); - InstrumentedMutexLock lock(&mutex); - s = s && tmp.ok(); - }; - - // Flow: - // main_writer thread will write but will be blocked (as Flush will on hold, - // buffer_size_ has exceeded, thus will create stall in effect). - // | - // | - // multiple writer threads will be created to write across multiple columns - // and they will be blocked. - // | - // | - // Last writer thread will write and when its blocked it will signal Flush to - // continue to clear the stall. - - threads.emplace_back(writer, 1); - // Wait untill first thread (main_writer) writing to DB is blocked and then - // create the multiple writers which will be blocked from getting added to the - // queue because stall is in effect. - { - InstrumentedMutexLock lock(&mutex); - while (wait_count_db != 1) { - cv.Wait(); - } - } - for (int i = 0; i < num_writers; i++) { - threads.emplace_back(writer, i % 4); - } - for (auto& t : threads) { - t.join(); - } - - ASSERT_TRUE(s); - - // Number of DBs blocked. - ASSERT_EQ(wait_count_db, 1); - // Number of Writer threads blocked. - ASSERT_EQ(w_set.size(), num_writers); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// Test multiple DBs get blocked when WriteBufferManager limit exceeds and flush -// is waiting to be finished but DBs tries to write meanwhile. -TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB) { - std::vector dbnames; - std::vector dbs; - int num_dbs = 3; - - for (int i = 0; i < num_dbs; i++) { - dbs.push_back(nullptr); - dbnames.push_back( - test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i))); - } - - Options options = CurrentOptions(); - options.arena_block_size = 4096; - options.write_buffer_size = 500000; // this is never hit - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); - ASSERT_LT(cache->GetUsage(), 256 * 1024); - cost_cache_ = GetParam(); - - if (cost_cache_) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, cache, true)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } - CreateAndReopenWithCF({"cf1", "cf2"}, options); - - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(DestroyDB(dbnames[i], options)); - ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i]))); - } - WriteOptions wo; - wo.disableWAL = true; - - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000))); - } - // Insert to db_. - ASSERT_OK(Put(0, Key(1), DummyString(30000), wo)); - - // WriteBufferManager Limit exceeded. - std::vector threads; - int wait_count_db = 0; - InstrumentedMutex mutex; - InstrumentedCondVar cv(&mutex); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0", - "DBImpl::BackgroundCallFlush:start"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WBMStallInterface::BlockDB", [&](void*) { - { - InstrumentedMutexLock lock(&mutex); - wait_count_db++; - cv.Signal(); - // Since this is the last DB, signal Flush to continue. - if (wait_count_db == num_dbs + 1) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - bool s = true; - - // Write to DB. - std::function write_db = [&](DB* db) { - Status tmp = db->Put(wo, Key(3), DummyString(1)); - InstrumentedMutexLock lock(&mutex); - s = s && tmp.ok(); - }; - - // Flow: - // db_ will write and will be blocked (as Flush will on hold and will create - // stall in effect). - // | - // multiple dbs writers will be created to write to that db and they will be - // blocked. - // | - // | - // Last writer will write and when its blocked it will signal Flush to - // continue to clear the stall. - - threads.emplace_back(write_db, db_); - // Wait untill first DB is blocked and then create the multiple writers for - // different DBs which will be blocked from getting added to the queue because - // stall is in effect. - { - InstrumentedMutexLock lock(&mutex); - while (wait_count_db != 1) { - cv.Wait(); - } - } - for (int i = 0; i < num_dbs; i++) { - threads.emplace_back(write_db, dbs[i]); - } - for (auto& t : threads) { - t.join(); - } - - ASSERT_TRUE(s); - ASSERT_EQ(num_dbs + 1, wait_count_db); - // Clean up DBs. - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(dbs[i]->Close()); - ASSERT_OK(DestroyDB(dbnames[i], options)); - delete dbs[i]; - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// Test multiple threads writing across multiple DBs and multiple columns get -// blocked when stall by WriteBufferManager is in effect. -TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) { - std::vector dbnames; - std::vector dbs; - int num_dbs = 3; - - for (int i = 0; i < num_dbs; i++) { - dbs.push_back(nullptr); - dbnames.push_back( - test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i))); - } - - Options options = CurrentOptions(); - options.arena_block_size = 4096; - options.write_buffer_size = 500000; // this is never hit - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); - ASSERT_LT(cache->GetUsage(), 256 * 1024); - cost_cache_ = GetParam(); - - if (cost_cache_) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, cache, true)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } - CreateAndReopenWithCF({"cf1", "cf2"}, options); - - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(DestroyDB(dbnames[i], options)); - ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i]))); - } - WriteOptions wo; - wo.disableWAL = true; - - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000))); - } - // Insert to db_. - ASSERT_OK(Put(0, Key(1), DummyString(30000), wo)); - - // WriteBufferManager::buffer_size_ has exceeded after the previous write to - // dbs[0] is completed. - std::vector threads; - int wait_count_db = 0; - InstrumentedMutex mutex; - InstrumentedCondVar cv(&mutex); - std::unordered_set w_set; - std::vector writer_threads; - std::atomic thread_num(0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0", - "DBImpl::BackgroundCallFlush:start"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WBMStallInterface::BlockDB", [&](void*) { - { - InstrumentedMutexLock lock(&mutex); - wait_count_db++; - thread_num.fetch_add(1); - cv.Signal(); - // Allow the flush to continue if all writer threads are blocked. - if (thread_num.load(std::memory_order_relaxed) == 2 * num_dbs + 1) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::WriteStall::Wait", [&](void* arg) { - WriteThread::Writer* w = reinterpret_cast(arg); - { - InstrumentedMutexLock lock(&mutex); - w_set.insert(w); - thread_num.fetch_add(1); - // Allow the flush continue if all writer threads are blocked. - if (thread_num.load(std::memory_order_relaxed) == 2 * num_dbs + 1) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - bool s1 = true, s2 = true; - // Write to multiple columns of db_. - std::function write_cf = [&](int cf) { - Status tmp = Put(cf, Key(3), DummyString(1), wo); - InstrumentedMutexLock lock(&mutex); - s1 = s1 && tmp.ok(); - }; - // Write to multiple DBs. - std::function write_db = [&](DB* db) { - Status tmp = db->Put(wo, Key(3), DummyString(1)); - InstrumentedMutexLock lock(&mutex); - s2 = s2 && tmp.ok(); - }; - - // Flow: - // thread will write to db_ will be blocked (as Flush will on hold, - // buffer_size_ has exceeded and will create stall in effect). - // | - // | - // multiple writers threads writing to different DBs and to db_ across - // multiple columns will be created and they will be blocked due to stall. - // | - // | - // Last writer thread will write and when its blocked it will signal Flush to - // continue to clear the stall. - threads.emplace_back(write_db, db_); - // Wait untill first thread is blocked and then create the multiple writer - // threads. - { - InstrumentedMutexLock lock(&mutex); - while (wait_count_db != 1) { - cv.Wait(); - } - } - - for (int i = 0; i < num_dbs; i++) { - // Write to multiple columns of db_. - writer_threads.emplace_back(write_cf, i % 3); - // Write to different dbs. - threads.emplace_back(write_db, dbs[i]); - } - for (auto& t : threads) { - t.join(); - } - for (auto& t : writer_threads) { - t.join(); - } - - ASSERT_TRUE(s1); - ASSERT_TRUE(s2); - - // Number of DBs blocked. - ASSERT_EQ(num_dbs + 1, wait_count_db); - // Number of Writer threads blocked. - ASSERT_EQ(w_set.size(), num_dbs); - // Clean up DBs. - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(dbs[i]->Close()); - ASSERT_OK(DestroyDB(dbnames[i], options)); - delete dbs[i]; - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// Test multiple threads writing across multiple columns of db_ by passing -// different values to WriteOption.no_slown_down. -TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsSingleDB) { - Options options = CurrentOptions(); - options.arena_block_size = 4096; - options.write_buffer_size = 500000; // this is never hit - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); - ASSERT_LT(cache->GetUsage(), 256 * 1024); - cost_cache_ = GetParam(); - - if (cost_cache_) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, cache, true)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } - WriteOptions wo; - wo.disableWAL = true; - - CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options); - - ASSERT_OK(Put(3, Key(1), DummyString(1), wo)); - Flush(3); - ASSERT_OK(Put(3, Key(1), DummyString(1), wo)); - ASSERT_OK(Put(0, Key(1), DummyString(1), wo)); - Flush(0); - - // Write to "Default", "cf2" and "cf3". No flush will be triggered. - ASSERT_OK(Put(3, Key(1), DummyString(30000), wo)); - ASSERT_OK(Put(0, Key(1), DummyString(40000), wo)); - ASSERT_OK(Put(2, Key(1), DummyString(1), wo)); - ASSERT_OK(Put(3, Key(2), DummyString(40000), wo)); - - // WriteBufferManager::buffer_size_ has exceeded after the previous write to - // db_ is completed. - - std::unordered_set w_slowdown_set; - std::vector threads; - int wait_count_db = 0; - int num_writers = 4; - InstrumentedMutex mutex; - InstrumentedCondVar cv(&mutex); - std::atomic thread_num(0); - std::atomic w_no_slowdown(0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0", - "DBImpl::BackgroundCallFlush:start"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WBMStallInterface::BlockDB", [&](void*) { - { - InstrumentedMutexLock lock(&mutex); - wait_count_db++; - cv.SignalAll(); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::WriteStall::Wait", [&](void* arg) { - { - InstrumentedMutexLock lock(&mutex); - WriteThread::Writer* w = reinterpret_cast(arg); - w_slowdown_set.insert(w); - // Allow the flush continue if all writer threads are blocked. - if (w_slowdown_set.size() + (unsigned long)w_no_slowdown.load( - std::memory_order_relaxed) == - (unsigned long)num_writers) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - bool s1 = true, s2 = true; - - std::function write_slow_down = [&](int cf) { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions write_op; - write_op.no_slowdown = false; - Status tmp = Put(cf, Slice(key), DummyString(1), write_op); - InstrumentedMutexLock lock(&mutex); - s1 = s1 && tmp.ok(); - }; - - std::function write_no_slow_down = [&](int cf) { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions write_op; - write_op.no_slowdown = true; - Status tmp = Put(cf, Slice(key), DummyString(1), write_op); - { - InstrumentedMutexLock lock(&mutex); - s2 = s2 && !tmp.ok(); - w_no_slowdown.fetch_add(1); - // Allow the flush continue if all writer threads are blocked. - if (w_slowdown_set.size() + - (unsigned long)w_no_slowdown.load(std::memory_order_relaxed) == - (unsigned long)num_writers) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - } - }; - - // Flow: - // main_writer thread will write but will be blocked (as Flush will on hold, - // buffer_size_ has exceeded, thus will create stall in effect). - // | - // | - // multiple writer threads will be created to write across multiple columns - // with different values of WriteOptions.no_slowdown. Some of them will - // be blocked and some of them will return with Incomplete status. - // | - // | - // Last writer thread will write and when its blocked/return it will signal - // Flush to continue to clear the stall. - threads.emplace_back(write_slow_down, 1); - // Wait untill first thread (main_writer) writing to DB is blocked and then - // create the multiple writers which will be blocked from getting added to the - // queue because stall is in effect. - { - InstrumentedMutexLock lock(&mutex); - while (wait_count_db != 1) { - cv.Wait(); - } - } - - for (int i = 0; i < num_writers; i += 2) { - threads.emplace_back(write_no_slow_down, (i) % 4); - threads.emplace_back(write_slow_down, (i + 1) % 4); - } - for (auto& t : threads) { - t.join(); - } - - ASSERT_TRUE(s1); - ASSERT_TRUE(s2); - // Number of DBs blocked. - ASSERT_EQ(wait_count_db, 1); - // Number of Writer threads blocked. - ASSERT_EQ(w_slowdown_set.size(), num_writers / 2); - // Number of Writer threads with WriteOptions.no_slowdown = true. - ASSERT_EQ(w_no_slowdown.load(std::memory_order_relaxed), num_writers / 2); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// Test multiple threads writing across multiple columns of db_ and different -// dbs by passing different values to WriteOption.no_slown_down. -TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) { - std::vector dbnames; - std::vector dbs; - int num_dbs = 4; - - for (int i = 0; i < num_dbs; i++) { - dbs.push_back(nullptr); - dbnames.push_back( - test::PerThreadDBPath("db_shared_wb_db" + std::to_string(i))); - } - - Options options = CurrentOptions(); - options.arena_block_size = 4096; - options.write_buffer_size = 500000; // this is never hit - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); - ASSERT_LT(cache->GetUsage(), 256 * 1024); - cost_cache_ = GetParam(); - - if (cost_cache_) { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, cache, true)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(100000, nullptr, true)); - } - CreateAndReopenWithCF({"cf1", "cf2"}, options); - - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(DestroyDB(dbnames[i], options)); - ASSERT_OK(DB::Open(options, dbnames[i], &(dbs[i]))); - } - WriteOptions wo; - wo.disableWAL = true; - - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(dbs[i]->Put(wo, Key(1), DummyString(20000))); - } - // Insert to db_. - ASSERT_OK(Put(0, Key(1), DummyString(30000), wo)); - - // WriteBufferManager::buffer_size_ has exceeded after the previous write to - // dbs[0] is completed. - std::vector threads; - int wait_count_db = 0; - InstrumentedMutex mutex; - InstrumentedCondVar cv(&mutex); - std::unordered_set w_slowdown_set; - std::vector writer_threads; - std::atomic thread_num(0); - std::atomic w_no_slowdown(0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0", - "DBImpl::BackgroundCallFlush:start"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WBMStallInterface::BlockDB", [&](void*) { - InstrumentedMutexLock lock(&mutex); - wait_count_db++; - cv.Signal(); - // Allow the flush continue if all writer threads are blocked. - if (w_slowdown_set.size() + - (unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) + - wait_count_db) == - (unsigned long)(2 * num_dbs + 1)) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::WriteStall::Wait", [&](void* arg) { - WriteThread::Writer* w = reinterpret_cast(arg); - InstrumentedMutexLock lock(&mutex); - w_slowdown_set.insert(w); - // Allow the flush continue if all writer threads are blocked. - if (w_slowdown_set.size() + - (unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) + - wait_count_db) == - (unsigned long)(2 * num_dbs + 1)) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - bool s1 = true, s2 = true; - std::function write_slow_down = [&](DB* db) { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions write_op; - write_op.no_slowdown = false; - Status tmp = db->Put(write_op, Slice(key), DummyString(1)); - InstrumentedMutexLock lock(&mutex); - s1 = s1 && tmp.ok(); - }; - - std::function write_no_slow_down = [&](DB* db) { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions write_op; - write_op.no_slowdown = true; - Status tmp = db->Put(write_op, Slice(key), DummyString(1)); - { - InstrumentedMutexLock lock(&mutex); - s2 = s2 && !tmp.ok(); - w_no_slowdown.fetch_add(1); - if (w_slowdown_set.size() + - (unsigned long)(w_no_slowdown.load(std::memory_order_relaxed) + - wait_count_db) == - (unsigned long)(2 * num_dbs + 1)) { - TEST_SYNC_POINT( - "DBWriteBufferManagerTest::SharedWriteBufferAcrossCFs:0"); - } - } - }; - - // Flow: - // first thread will write but will be blocked (as Flush will on hold, - // buffer_size_ has exceeded, thus will create stall in effect). - // | - // | - // multiple writer threads will be created to write across multiple columns - // of db_ and different DBs with different values of - // WriteOptions.no_slowdown. Some of them will be blocked and some of them - // will return with Incomplete status. - // | - // | - // Last writer thread will write and when its blocked/return it will signal - // Flush to continue to clear the stall. - threads.emplace_back(write_slow_down, db_); - // Wait untill first thread writing to DB is blocked and then - // create the multiple writers. - { - InstrumentedMutexLock lock(&mutex); - while (wait_count_db != 1) { - cv.Wait(); - } - } - - for (int i = 0; i < num_dbs; i += 2) { - // Write to multiple columns of db_. - writer_threads.emplace_back(write_slow_down, db_); - writer_threads.emplace_back(write_no_slow_down, db_); - // Write to different DBs. - threads.emplace_back(write_slow_down, dbs[i]); - threads.emplace_back(write_no_slow_down, dbs[i + 1]); - } - - for (auto& t : threads) { - t.join(); - } - - for (auto& t : writer_threads) { - t.join(); - } - - ASSERT_TRUE(s1); - ASSERT_TRUE(s2); - // Number of DBs blocked. - ASSERT_EQ((num_dbs / 2) + 1, wait_count_db); - // Number of writer threads writing to db_ blocked from getting added to the - // queue. - ASSERT_EQ(w_slowdown_set.size(), num_dbs / 2); - // Number of threads with WriteOptions.no_slowdown = true. - ASSERT_EQ(w_no_slowdown.load(std::memory_order_relaxed), num_dbs); - - // Clean up DBs. - for (int i = 0; i < num_dbs; i++) { - ASSERT_OK(dbs[i]->Close()); - ASSERT_OK(DestroyDB(dbnames[i], options)); - delete dbs[i]; - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - - -// Tests a `WriteBufferManager` constructed with `allow_stall == false` does not -// thrash memtable switching when full and a CF receives multiple writes. -// Instead, we expect to switch a CF's memtable for flush only when that CF does -// not have any pending or running flush. -// -// This test uses multiple DBs each with a single CF instead of a single DB -// with multiple CFs. That way we can control which CF is considered for switch -// by writing to that CF's DB. -// -// Not supported in LITE mode due to `GetProperty()` unavailable. -TEST_P(DBWriteBufferManagerTest, StopSwitchingMemTablesOnceFlushing) { - Options options = CurrentOptions(); - options.arena_block_size = 4 << 10; // 4KB - options.write_buffer_size = 1 << 20; // 1MB - std::shared_ptr cache = - NewLRUCache(4 << 20 /* capacity (4MB) */, 2 /* num_shard_bits */); - ASSERT_LT(cache->GetUsage(), 256 << 10 /* 256KB */); - cost_cache_ = GetParam(); - if (cost_cache_) { - options.write_buffer_manager.reset(new WriteBufferManager( - 512 << 10 /* buffer_size (512KB) */, cache, false /* allow_stall */)); - } else { - options.write_buffer_manager.reset( - new WriteBufferManager(512 << 10 /* buffer_size (512KB) */, - nullptr /* cache */, false /* allow_stall */)); - } - - Reopen(options); - std::string dbname = test::PerThreadDBPath("db_shared_wbm_db"); - DB* shared_wbm_db = nullptr; - - ASSERT_OK(DestroyDB(dbname, options)); - ASSERT_OK(DB::Open(options, dbname, &shared_wbm_db)); - - // The last write will make WBM need flush, but it won't flush yet. - ASSERT_OK(Put(Key(1), DummyString(256 << 10 /* 256KB */), WriteOptions())); - ASSERT_FALSE(options.write_buffer_manager->ShouldFlush()); - ASSERT_OK(Put(Key(1), DummyString(256 << 10 /* 256KB */), WriteOptions())); - ASSERT_TRUE(options.write_buffer_manager->ShouldFlush()); - - // Flushes will be pending, not running because flush threads are blocked. - test::SleepingBackgroundTask sleeping_task_high; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_high, Env::Priority::HIGH); - - for (int i = 0; i < 3; ++i) { - ASSERT_OK( - shared_wbm_db->Put(WriteOptions(), Key(1), DummyString(1 /* len */))); - std::string prop; - ASSERT_TRUE( - shared_wbm_db->GetProperty("rocksdb.num-immutable-mem-table", &prop)); - ASSERT_EQ(std::to_string(i > 0 ? 1 : 0), prop); - ASSERT_TRUE( - shared_wbm_db->GetProperty("rocksdb.mem-table-flush-pending", &prop)); - ASSERT_EQ(std::to_string(i > 0 ? 1 : 0), prop); - } - - // Clean up DBs. - sleeping_task_high.WakeUp(); - sleeping_task_high.WaitUntilDone(); - ASSERT_OK(shared_wbm_db->Close()); - ASSERT_OK(DestroyDB(dbname, options)); - delete shared_wbm_db; -} - - -INSTANTIATE_TEST_CASE_P(DBWriteBufferManagerTest, DBWriteBufferManagerTest, - testing::Bool()); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/db_write_test.cc b/db/db_write_test.cc deleted file mode 100644 index d82c57376..000000000 --- a/db/db_write_test.cc +++ /dev/null @@ -1,790 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include -#include -#include -#include - -#include "db/db_test_util.h" -#include "db/write_batch_internal.h" -#include "db/write_thread.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "test_util/sync_point.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/fault_injection_env.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -// Test variations of WriteImpl. -class DBWriteTest : public DBTestBase, public testing::WithParamInterface { - public: - DBWriteTest() : DBTestBase("db_write_test", /*env_do_fsync=*/true) {} - - Options GetOptions() { return DBTestBase::GetOptions(GetParam()); } - - void Open() { DBTestBase::Reopen(GetOptions()); } -}; - -class DBWriteTestUnparameterized : public DBTestBase { - public: - explicit DBWriteTestUnparameterized() - : DBTestBase("pipelined_write_test", /*env_do_fsync=*/false) {} -}; - -// It is invalid to do sync write while disabling WAL. -TEST_P(DBWriteTest, SyncAndDisableWAL) { - WriteOptions write_options; - write_options.sync = true; - write_options.disableWAL = true; - ASSERT_TRUE(dbfull()->Put(write_options, "foo", "bar").IsInvalidArgument()); - WriteBatch batch; - ASSERT_OK(batch.Put("foo", "bar")); - ASSERT_TRUE(dbfull()->Write(write_options, &batch).IsInvalidArgument()); -} - -TEST_P(DBWriteTest, WriteStallRemoveNoSlowdownWrite) { - Options options = GetOptions(); - options.level0_stop_writes_trigger = options.level0_slowdown_writes_trigger = - 4; - std::vector threads; - std::atomic thread_num(0); - port::Mutex mutex; - port::CondVar cv(&mutex); - // Guarded by mutex - int writers = 0; - - Reopen(options); - - std::function write_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, key, "bar")); - }; - std::function write_no_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = true; - Status s = dbfull()->Put(wo, key, "bar"); - ASSERT_TRUE(s.ok() || s.IsIncomplete()); - }; - std::function unblock_main_thread_func = [&](void*) { - mutex.Lock(); - ++writers; - cv.SignalAll(); - mutex.Unlock(); - }; - - // Create 3 L0 files and schedule 4th without waiting - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Start", unblock_main_thread_func); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteTest::WriteStallRemoveNoSlowdownWrite:1", - "DBImpl::BackgroundCallFlush:start"}, - {"DBWriteTest::WriteStallRemoveNoSlowdownWrite:2", - "DBImplWrite::PipelinedWriteImpl:AfterJoinBatchGroup"}, - // Make compaction start wait for the write stall to be detected and - // implemented by a write group leader - {"DBWriteTest::WriteStallRemoveNoSlowdownWrite:3", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Schedule creation of 4th L0 file without waiting. This will seal the - // memtable and then wait for a sync point before writing the file. We need - // to do it this way because SwitchMemtable() needs to enter the - // write_thread - FlushOptions fopt; - fopt.wait = false; - ASSERT_OK(dbfull()->Flush(fopt)); - - // Create a mix of slowdown/no_slowdown write threads - mutex.Lock(); - // First leader - threads.emplace_back(write_slowdown_func); - while (writers != 1) { - cv.Wait(); - } - - // Second leader. Will stall writes - // Build a writers list with no slowdown in the middle: - // +-------------+ - // | slowdown +<----+ newest - // +--+----------+ - // | - // v - // +--+----------+ - // | no slowdown | - // +--+----------+ - // | - // v - // +--+----------+ - // | slowdown + - // +-------------+ - threads.emplace_back(write_slowdown_func); - while (writers != 2) { - cv.Wait(); - } - threads.emplace_back(write_no_slowdown_func); - while (writers != 3) { - cv.Wait(); - } - threads.emplace_back(write_slowdown_func); - while (writers != 4) { - cv.Wait(); - } - - mutex.Unlock(); - - TEST_SYNC_POINT("DBWriteTest::WriteStallRemoveNoSlowdownWrite:1"); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(nullptr)); - // This would have triggered a write stall. Unblock the write group leader - TEST_SYNC_POINT("DBWriteTest::WriteStallRemoveNoSlowdownWrite:2"); - // The leader is going to create missing newer links. When the leader - // finishes, the next leader is going to delay writes and fail writers with - // no_slowdown - - TEST_SYNC_POINT("DBWriteTest::WriteStallRemoveNoSlowdownWrite:3"); - for (auto& t : threads) { - t.join(); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(DBWriteTest, WriteThreadHangOnWriteStall) { - Options options = GetOptions(); - options.level0_stop_writes_trigger = options.level0_slowdown_writes_trigger = - 4; - std::vector threads; - std::atomic thread_num(0); - port::Mutex mutex; - port::CondVar cv(&mutex); - // Guarded by mutex - int writers = 0; - - Reopen(options); - - std::function write_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = false; - ASSERT_OK(dbfull()->Put(wo, key, "bar")); - }; - std::function write_no_slowdown_func = [&]() { - int a = thread_num.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - wo.no_slowdown = true; - Status s = dbfull()->Put(wo, key, "bar"); - ASSERT_TRUE(s.ok() || s.IsIncomplete()); - }; - std::function unblock_main_thread_func = [&](void*) { - mutex.Lock(); - ++writers; - cv.SignalAll(); - mutex.Unlock(); - }; - - // Create 3 L0 files and schedule 4th without waiting - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar")); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Start", unblock_main_thread_func); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBWriteTest::WriteThreadHangOnWriteStall:1", - "DBImpl::BackgroundCallFlush:start"}, - {"DBWriteTest::WriteThreadHangOnWriteStall:2", - "DBImpl::WriteImpl:BeforeLeaderEnters"}, - // Make compaction start wait for the write stall to be detected and - // implemented by a write group leader - {"DBWriteTest::WriteThreadHangOnWriteStall:3", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Schedule creation of 4th L0 file without waiting. This will seal the - // memtable and then wait for a sync point before writing the file. We need - // to do it this way because SwitchMemtable() needs to enter the - // write_thread - FlushOptions fopt; - fopt.wait = false; - ASSERT_OK(dbfull()->Flush(fopt)); - - // Create a mix of slowdown/no_slowdown write threads - mutex.Lock(); - // First leader - threads.emplace_back(write_slowdown_func); - while (writers != 1) { - cv.Wait(); - } - // Second leader. Will stall writes - threads.emplace_back(write_slowdown_func); - threads.emplace_back(write_no_slowdown_func); - threads.emplace_back(write_slowdown_func); - threads.emplace_back(write_no_slowdown_func); - threads.emplace_back(write_slowdown_func); - while (writers != 6) { - cv.Wait(); - } - mutex.Unlock(); - - TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:1"); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(nullptr)); - // This would have triggered a write stall. Unblock the write group leader - TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:2"); - // The leader is going to create missing newer links. When the leader - // finishes, the next leader is going to delay writes and fail writers with - // no_slowdown - - TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:3"); - for (auto& t : threads) { - t.join(); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(DBWriteTest, IOErrorOnWALWritePropagateToWriteThreadFollower) { - constexpr int kNumThreads = 5; - std::unique_ptr mock_env( - new FaultInjectionTestEnv(env_)); - Options options = GetOptions(); - options.env = mock_env.get(); - Reopen(options); - std::atomic ready_count{0}; - std::atomic leader_count{0}; - std::vector threads; - mock_env->SetFilesystemActive(false); - - // Wait until all threads linked to write threads, to make sure - // all threads join the same batch group. - SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait", [&](void* arg) { - ready_count++; - auto* w = reinterpret_cast(arg); - if (w->state == WriteThread::STATE_GROUP_LEADER) { - leader_count++; - while (ready_count < kNumThreads) { - // busy waiting - } - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - for (int i = 0; i < kNumThreads; i++) { - threads.push_back(port::Thread( - [&](int index) { - // All threads should fail. - auto res = Put("key" + std::to_string(index), "value"); - if (options.manual_wal_flush) { - ASSERT_TRUE(res.ok()); - // we should see fs error when we do the flush - - // TSAN reports a false alarm for lock-order-inversion but Open and - // FlushWAL are not run concurrently. Disabling this until TSAN is - // fixed. - // res = dbfull()->FlushWAL(false); - // ASSERT_FALSE(res.ok()); - } else { - ASSERT_FALSE(res.ok()); - } - }, - i)); - } - for (int i = 0; i < kNumThreads; i++) { - threads[i].join(); - } - ASSERT_EQ(1, leader_count); - - // The Failed PUT operations can cause a BG error to be set. - // Mark it as Checked for the ASSERT_STATUS_CHECKED - dbfull()->Resume().PermitUncheckedError(); - - // Close before mock_env destruct. - Close(); -} - -TEST_F(DBWriteTestUnparameterized, PipelinedWriteRace) { - // This test was written to trigger a race in ExitAsBatchGroupLeader in case - // enable_pipelined_write_ was true. - // Writers for which ShouldWriteToMemtable() evaluates to false are removed - // from the write_group via CompleteFollower/ CompleteLeader. Writers in the - // middle of the group are fully unlinked, but if that writers is the - // last_writer, then we did not update the predecessor's link_older, i.e., - // this writer was still reachable via newest_writer_. - // - // But the problem was, that CompleteFollower already wakes up the thread - // owning that writer before the writer has been removed. This resulted in a - // race - if the leader thread was fast enough, then everything was fine. - // However, if the woken up thread finished the current write operation and - // then performed yet another write, then a new writer instance was added - // to newest_writer_. It is possible that the new writer is located on the - // same address on stack, and if this happened, then we had a problem, - // because the old code tried to find the last_writer in the list to unlink - // it, which in this case produced a cycle in the list. - // Whether two invocations of PipelinedWriteImpl() by the same thread actually - // allocate the writer on the same address depends on the OS and/or compiler, - // so it is rather hard to create a deterministic test for this. - - Options options = GetDefaultOptions(); - options.create_if_missing = true; - options.enable_pipelined_write = true; - std::vector threads; - - std::atomic write_counter{0}; - std::atomic active_writers{0}; - std::atomic second_write_starting{false}; - std::atomic second_write_in_progress{false}; - std::atomic leader{nullptr}; - std::atomic finished_WAL_write{false}; - - DestroyAndReopen(options); - - auto write_one_doc = [&]() { - int a = write_counter.fetch_add(1); - std::string key = "foo" + std::to_string(a); - WriteOptions wo; - ASSERT_OK(dbfull()->Put(wo, key, "bar")); - --active_writers; - }; - - auto write_two_docs = [&]() { - write_one_doc(); - second_write_starting = true; - write_one_doc(); - }; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait", [&](void* arg) { - if (second_write_starting.load()) { - second_write_in_progress = true; - return; - } - auto* w = reinterpret_cast(arg); - if (w->state == WriteThread::STATE_GROUP_LEADER) { - active_writers++; - if (leader.load() == nullptr) { - leader.store(w); - while (active_writers.load() < 2) { - // wait for another thread to join the write_group - } - } - } else { - // we disable the memtable for all followers so that they they are - // removed from the write_group before enqueuing it for the memtable - // write - w->disable_memtable = true; - active_writers++; - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::ExitAsBatchGroupLeader:Start", [&](void* arg) { - auto* wg = reinterpret_cast(arg); - if (wg->leader == leader && !finished_WAL_write) { - finished_WAL_write = true; - while (active_writers.load() < 3) { - // wait for the new writer to be enqueued - } - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::ExitAsBatchGroupLeader:AfterCompleteWriters", - [&](void* arg) { - auto* wg = reinterpret_cast(arg); - if (wg->leader == leader) { - while (!second_write_in_progress.load()) { - // wait for the old follower thread to start the next write - } - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // start leader + one follower - threads.emplace_back(write_one_doc); - while (leader.load() == nullptr) { - // wait for leader - } - - // we perform two writes in the follower, so that for the second write - // the thread reinserts a Writer with the same address - threads.emplace_back(write_two_docs); - - // wait for the leader to enter ExitAsBatchGroupLeader - while (!finished_WAL_write.load()) { - // wait for write_group to have finished the WAL writes - } - - // start another writer thread to be enqueued before the leader can - // complete the writers from its write_group - threads.emplace_back(write_one_doc); - - for (auto& t : threads) { - t.join(); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(DBWriteTest, ManualWalFlushInEffect) { - Options options = GetOptions(); - Reopen(options); - // try the 1st WAL created during open - ASSERT_TRUE(Put("key" + std::to_string(0), "value").ok()); - ASSERT_TRUE(options.manual_wal_flush != dbfull()->WALBufferIsEmpty()); - ASSERT_TRUE(dbfull()->FlushWAL(false).ok()); - ASSERT_TRUE(dbfull()->WALBufferIsEmpty()); - // try the 2nd wal created during SwitchWAL - ASSERT_OK(dbfull()->TEST_SwitchWAL()); - ASSERT_TRUE(Put("key" + std::to_string(0), "value").ok()); - ASSERT_TRUE(options.manual_wal_flush != dbfull()->WALBufferIsEmpty()); - ASSERT_TRUE(dbfull()->FlushWAL(false).ok()); - ASSERT_TRUE(dbfull()->WALBufferIsEmpty()); -} - -TEST_P(DBWriteTest, UnflushedPutRaceWithTrackedWalSync) { - // Repro race condition bug where unflushed WAL data extended the synced size - // recorded to MANIFEST despite being unrecoverable. - Options options = GetOptions(); - std::unique_ptr fault_env( - new FaultInjectionTestEnv(env_)); - options.env = fault_env.get(); - options.manual_wal_flush = true; - options.track_and_verify_wals_in_manifest = true; - Reopen(options); - - ASSERT_OK(Put("key1", "val1")); - - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::SyncWAL:Begin", - [this](void* /* arg */) { ASSERT_OK(Put("key2", "val2")); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(db_->FlushWAL(true /* sync */)); - - // Ensure callback ran. - ASSERT_EQ("val2", Get("key2")); - - Close(); - - // Simulate full loss of unsynced data. This drops "key2" -> "val2" from the - // DB WAL. - fault_env->DropUnsyncedFileData(); - - Reopen(options); - - // Need to close before `fault_env` goes out of scope. - Close(); -} - -TEST_P(DBWriteTest, InactiveWalFullySyncedBeforeUntracked) { - // Repro bug where a WAL is appended and switched after - // `FlushWAL(true /* sync */)`'s sync finishes and before it untracks fully - // synced inactive logs. Previously such a WAL would be wrongly untracked - // so the final append would never be synced. - Options options = GetOptions(); - std::unique_ptr fault_env( - new FaultInjectionTestEnv(env_)); - options.env = fault_env.get(); - Reopen(options); - - ASSERT_OK(Put("key1", "val1")); - - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::SyncWAL:BeforeMarkLogsSynced:1", [this](void* /* arg */) { - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(dbfull()->TEST_SwitchMemtable()); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(db_->FlushWAL(true /* sync */)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - - ASSERT_OK(Put("key3", "val3")); - - ASSERT_OK(db_->FlushWAL(true /* sync */)); - - Close(); - - // Simulate full loss of unsynced data. This should drop nothing since we did - // `FlushWAL(true /* sync */)` before `Close()`. - fault_env->DropUnsyncedFileData(); - - Reopen(options); - - ASSERT_EQ("val1", Get("key1")); - ASSERT_EQ("val2", Get("key2")); - ASSERT_EQ("val3", Get("key3")); - - // Need to close before `fault_env` goes out of scope. - Close(); -} - -TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) { - std::unique_ptr mock_env( - new FaultInjectionTestEnv(env_)); - Options options = GetOptions(); - options.env = mock_env.get(); - Reopen(options); - for (int i = 0; i < 2; i++) { - // Forcibly fail WAL write for the first Put only. Subsequent Puts should - // fail due to read-only mode - mock_env->SetFilesystemActive(i != 0); - auto res = Put("key" + std::to_string(i), "value"); - // TSAN reports a false alarm for lock-order-inversion but Open and - // FlushWAL are not run concurrently. Disabling this until TSAN is - // fixed. - /* - if (options.manual_wal_flush && i == 0) { - // even with manual_wal_flush the 2nd Put should return error because of - // the read-only mode - ASSERT_TRUE(res.ok()); - // we should see fs error when we do the flush - res = dbfull()->FlushWAL(false); - } - */ - if (!options.manual_wal_flush) { - ASSERT_NOK(res); - } else { - ASSERT_OK(res); - } - } - // Close before mock_env destruct. - Close(); -} - -TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) { - Random rnd(301); - std::unique_ptr mock_env( - new FaultInjectionTestEnv(env_)); - Options options = GetOptions(); - options.env = mock_env.get(); - options.writable_file_max_buffer_size = 4 * 1024 * 1024; - options.write_buffer_size = 3 * 512 * 1024; - options.wal_bytes_per_sync = 256 * 1024; - options.manual_wal_flush = true; - Reopen(options); - mock_env->SetFilesystemActive(false, Status::IOError("Not active")); - Status s; - for (int i = 0; i < 4 * 512; ++i) { - s = Put(Key(i), rnd.RandomString(1024)); - if (!s.ok()) { - break; - } - } - ASSERT_EQ(s.severity(), Status::Severity::kFatalError); - - mock_env->SetFilesystemActive(true); - // Close before mock_env destruct. - Close(); -} - -// Test that db->LockWAL() flushes the WAL after locking, which can fail -TEST_P(DBWriteTest, LockWALInEffect) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - Options options = GetOptions(); - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - options.env = fault_fs_env.get(); - options.disable_auto_compactions = true; - options.paranoid_checks = false; - options.max_bgerror_resume_count = 0; // manual Resume() - Reopen(options); - // try the 1st WAL created during open - ASSERT_OK(Put("key0", "value")); - ASSERT_NE(options.manual_wal_flush, dbfull()->WALBufferIsEmpty()); - ASSERT_OK(db_->LockWAL()); - ASSERT_TRUE(dbfull()->WALBufferIsEmpty()); - ASSERT_OK(db_->UnlockWAL()); - // try the 2nd wal created during SwitchWAL - ASSERT_OK(dbfull()->TEST_SwitchWAL()); - ASSERT_OK(Put("key1", "value")); - ASSERT_NE(options.manual_wal_flush, dbfull()->WALBufferIsEmpty()); - ASSERT_OK(db_->LockWAL()); - ASSERT_TRUE(dbfull()->WALBufferIsEmpty()); - ASSERT_OK(db_->UnlockWAL()); - - // Fail the WAL flush if applicable - fault_fs->SetFilesystemActive(false); - Status s = Put("key2", "value"); - if (options.manual_wal_flush) { - ASSERT_OK(s); - // I/O failure - ASSERT_NOK(db_->LockWAL()); - // Should not need UnlockWAL after LockWAL fails - } else { - ASSERT_NOK(s); - ASSERT_OK(db_->LockWAL()); - ASSERT_OK(db_->UnlockWAL()); - } - fault_fs->SetFilesystemActive(true); - ASSERT_OK(db_->Resume()); - // Writes should work again - ASSERT_OK(Put("key3", "value")); - ASSERT_EQ(Get("key3"), "value"); - - // Should be extraneous, but allowed - ASSERT_NOK(db_->UnlockWAL()); - - // Close before mock_env destruct. - Close(); -} - -TEST_P(DBWriteTest, LockWALConcurrentRecursive) { - Options options = GetOptions(); - Reopen(options); - ASSERT_OK(Put("k1", "val")); - ASSERT_OK(db_->LockWAL()); // 0 -> 1 - auto frozen_seqno = db_->GetLatestSequenceNumber(); - std::atomic t1_completed{false}; - port::Thread t1{[&]() { - // Won't finish until WAL unlocked - ASSERT_OK(Put("k1", "val2")); - t1_completed = true; - }}; - - ASSERT_OK(db_->LockWAL()); // 1 -> 2 - // Read-only ops are OK - ASSERT_EQ(Get("k1"), "val"); - { - std::vector files; - LiveFilesStorageInfoOptions lf_opts; - // A DB flush could deadlock - lf_opts.wal_size_for_flush = UINT64_MAX; - ASSERT_OK(db_->GetLiveFilesStorageInfo({lf_opts}, &files)); - } - - port::Thread t2{[&]() { - ASSERT_OK(db_->LockWAL()); // 2 -> 3 or 1 -> 2 - }}; - - ASSERT_OK(db_->UnlockWAL()); // 2 -> 1 or 3 -> 2 - // Give t1 an extra chance to jump in case of bug - std::this_thread::yield(); - t2.join(); - ASSERT_FALSE(t1_completed.load()); - - // Should now have 2 outstanding LockWAL - ASSERT_EQ(Get("k1"), "val"); - - ASSERT_OK(db_->UnlockWAL()); // 2 -> 1 - - ASSERT_FALSE(t1_completed.load()); - ASSERT_EQ(Get("k1"), "val"); - ASSERT_EQ(frozen_seqno, db_->GetLatestSequenceNumber()); - - // Ensure final Unlock is concurrency safe and extra Unlock is safe but - // non-OK - std::atomic unlock_ok{0}; - port::Thread t3{[&]() { - if (db_->UnlockWAL().ok()) { - unlock_ok++; - } - ASSERT_OK(db_->LockWAL()); - if (db_->UnlockWAL().ok()) { - unlock_ok++; - } - }}; - - if (db_->UnlockWAL().ok()) { - unlock_ok++; - } - t3.join(); - - // There was one extra unlock, so just one non-ok - ASSERT_EQ(unlock_ok.load(), 2); - - // Write can proceed - t1.join(); - ASSERT_TRUE(t1_completed.load()); - ASSERT_EQ(Get("k1"), "val2"); - // And new writes - ASSERT_OK(Put("k2", "val")); - ASSERT_EQ(Get("k2"), "val"); -} - -TEST_P(DBWriteTest, ConcurrentlyDisabledWAL) { - Options options = GetOptions(); - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - Reopen(options); - std::string wal_key_prefix = "WAL_KEY_"; - std::string no_wal_key_prefix = "K_"; - // 100 KB value each for NO-WAL operation - std::string no_wal_value(1024 * 100, 'X'); - // 1B value each for WAL operation - std::string wal_value = "0"; - std::thread threads[10]; - for (int t = 0; t < 10; t++) { - threads[t] = std::thread([t, wal_key_prefix, wal_value, no_wal_key_prefix, - no_wal_value, this] { - for (int i = 0; i < 10; i++) { - ROCKSDB_NAMESPACE::WriteOptions write_option_disable; - write_option_disable.disableWAL = true; - ROCKSDB_NAMESPACE::WriteOptions write_option_default; - std::string no_wal_key = - no_wal_key_prefix + std::to_string(t) + "_" + std::to_string(i); - ASSERT_OK(this->Put(no_wal_key, no_wal_value, write_option_disable)); - std::string wal_key = - wal_key_prefix + std::to_string(i) + "_" + std::to_string(i); - ASSERT_OK(this->Put(wal_key, wal_value, write_option_default)); - ASSERT_OK(dbfull()->SyncWAL()); - } - return; - }); - } - for (auto& t : threads) { - t.join(); - } - uint64_t bytes_num = options.statistics->getTickerCount( - ROCKSDB_NAMESPACE::Tickers::WAL_FILE_BYTES); - // written WAL size should less than 100KB (even included HEADER & FOOTER - // overhead) - ASSERT_LE(bytes_num, 1024 * 100); -} - -INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest, - testing::Values(DBTestBase::kDefault, - DBTestBase::kConcurrentWALWrites, - DBTestBase::kPipelinedWrite)); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/dbformat_test.cc b/db/dbformat_test.cc deleted file mode 100644 index 8dc3387df..000000000 --- a/db/dbformat_test.cc +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/dbformat.h" - -#include "table/block_based/index_builder.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -static std::string IKey(const std::string& user_key, uint64_t seq, - ValueType vt) { - std::string encoded; - AppendInternalKey(&encoded, ParsedInternalKey(user_key, seq, vt)); - return encoded; -} - -static std::string Shorten(const std::string& s, const std::string& l) { - std::string result = s; - ShortenedIndexBuilder::FindShortestInternalKeySeparator(*BytewiseComparator(), - &result, l); - return result; -} - -static std::string ShortSuccessor(const std::string& s) { - std::string result = s; - ShortenedIndexBuilder::FindShortInternalKeySuccessor(*BytewiseComparator(), - &result); - return result; -} - -static void TestKey(const std::string& key, uint64_t seq, ValueType vt) { - std::string encoded = IKey(key, seq, vt); - - Slice in(encoded); - ParsedInternalKey decoded("", 0, kTypeValue); - - ASSERT_OK(ParseInternalKey(in, &decoded, true /* log_err_key */)); - ASSERT_EQ(key, decoded.user_key.ToString()); - ASSERT_EQ(seq, decoded.sequence); - ASSERT_EQ(vt, decoded.type); - - ASSERT_NOK(ParseInternalKey(Slice("bar"), &decoded, true /* log_err_key */)); -} - -class FormatTest : public testing::Test {}; - -TEST_F(FormatTest, InternalKey_EncodeDecode) { - const char* keys[] = {"", "k", "hello", "longggggggggggggggggggggg"}; - const uint64_t seq[] = {1, - 2, - 3, - (1ull << 8) - 1, - 1ull << 8, - (1ull << 8) + 1, - (1ull << 16) - 1, - 1ull << 16, - (1ull << 16) + 1, - (1ull << 32) - 1, - 1ull << 32, - (1ull << 32) + 1}; - for (unsigned int k = 0; k < sizeof(keys) / sizeof(keys[0]); k++) { - for (unsigned int s = 0; s < sizeof(seq) / sizeof(seq[0]); s++) { - TestKey(keys[k], seq[s], kTypeValue); - TestKey("hello", 1, kTypeDeletion); - } - } -} - -TEST_F(FormatTest, InternalKeyShortSeparator) { - // When user keys are same - ASSERT_EQ(IKey("foo", 100, kTypeValue), - Shorten(IKey("foo", 100, kTypeValue), IKey("foo", 99, kTypeValue))); - ASSERT_EQ( - IKey("foo", 100, kTypeValue), - Shorten(IKey("foo", 100, kTypeValue), IKey("foo", 101, kTypeValue))); - ASSERT_EQ( - IKey("foo", 100, kTypeValue), - Shorten(IKey("foo", 100, kTypeValue), IKey("foo", 100, kTypeValue))); - ASSERT_EQ( - IKey("foo", 100, kTypeValue), - Shorten(IKey("foo", 100, kTypeValue), IKey("foo", 100, kTypeDeletion))); - - // When user keys are misordered - ASSERT_EQ(IKey("foo", 100, kTypeValue), - Shorten(IKey("foo", 100, kTypeValue), IKey("bar", 99, kTypeValue))); - - // When user keys are different, but correctly ordered - ASSERT_EQ( - IKey("g", kMaxSequenceNumber, kValueTypeForSeek), - Shorten(IKey("foo", 100, kTypeValue), IKey("hello", 200, kTypeValue))); - - ASSERT_EQ(IKey("ABC2", kMaxSequenceNumber, kValueTypeForSeek), - Shorten(IKey("ABC1AAAAA", 100, kTypeValue), - IKey("ABC2ABB", 200, kTypeValue))); - - ASSERT_EQ(IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek), - Shorten(IKey("AAA1AAA", 100, kTypeValue), - IKey("AAA2AA", 200, kTypeValue))); - - ASSERT_EQ( - IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek), - Shorten(IKey("AAA1AAA", 100, kTypeValue), IKey("AAA4", 200, kTypeValue))); - - ASSERT_EQ( - IKey("AAA1B", kMaxSequenceNumber, kValueTypeForSeek), - Shorten(IKey("AAA1AAA", 100, kTypeValue), IKey("AAA2", 200, kTypeValue))); - - ASSERT_EQ(IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek), - Shorten(IKey("AAA1AAA", 100, kTypeValue), - IKey("AAA2A", 200, kTypeValue))); - - ASSERT_EQ( - IKey("AAA1", 100, kTypeValue), - Shorten(IKey("AAA1", 100, kTypeValue), IKey("AAA2", 200, kTypeValue))); - - // When start user key is prefix of limit user key - ASSERT_EQ( - IKey("foo", 100, kTypeValue), - Shorten(IKey("foo", 100, kTypeValue), IKey("foobar", 200, kTypeValue))); - - // When limit user key is prefix of start user key - ASSERT_EQ( - IKey("foobar", 100, kTypeValue), - Shorten(IKey("foobar", 100, kTypeValue), IKey("foo", 200, kTypeValue))); -} - -TEST_F(FormatTest, InternalKeyShortestSuccessor) { - ASSERT_EQ(IKey("g", kMaxSequenceNumber, kValueTypeForSeek), - ShortSuccessor(IKey("foo", 100, kTypeValue))); - ASSERT_EQ(IKey("\xff\xff", 100, kTypeValue), - ShortSuccessor(IKey("\xff\xff", 100, kTypeValue))); -} - -TEST_F(FormatTest, IterKeyOperation) { - IterKey k; - const char p[] = "abcdefghijklmnopqrstuvwxyz"; - const char q[] = "0123456789"; - - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("")); - - k.TrimAppend(0, p, 3); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("abc")); - - k.TrimAppend(1, p, 3); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("aabc")); - - k.TrimAppend(0, p, 26); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("abcdefghijklmnopqrstuvwxyz")); - - k.TrimAppend(26, q, 10); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("abcdefghijklmnopqrstuvwxyz0123456789")); - - k.TrimAppend(36, q, 1); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("abcdefghijklmnopqrstuvwxyz01234567890")); - - k.TrimAppend(26, q, 1); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("abcdefghijklmnopqrstuvwxyz0")); - - // Size going up, memory allocation is triggered - k.TrimAppend(27, p, 26); - ASSERT_EQ(std::string(k.GetUserKey().data(), k.GetUserKey().size()), - std::string("abcdefghijklmnopqrstuvwxyz0" - "abcdefghijklmnopqrstuvwxyz")); -} - -TEST_F(FormatTest, UpdateInternalKey) { - std::string user_key("abcdefghijklmnopqrstuvwxyz"); - uint64_t new_seq = 0x123456; - ValueType new_val_type = kTypeDeletion; - - std::string ikey; - AppendInternalKey(&ikey, ParsedInternalKey(user_key, 100U, kTypeValue)); - size_t ikey_size = ikey.size(); - UpdateInternalKey(&ikey, new_seq, new_val_type); - ASSERT_EQ(ikey_size, ikey.size()); - - Slice in(ikey); - ParsedInternalKey decoded; - ASSERT_OK(ParseInternalKey(in, &decoded, true /* log_err_key */)); - ASSERT_EQ(user_key, decoded.user_key.ToString()); - ASSERT_EQ(new_seq, decoded.sequence); - ASSERT_EQ(new_val_type, decoded.type); -} - -TEST_F(FormatTest, RangeTombstoneSerializeEndKey) { - RangeTombstone t("a", "b", 2); - InternalKey k("b", 3, kTypeValue); - const InternalKeyComparator cmp(BytewiseComparator()); - ASSERT_LT(cmp.Compare(t.SerializeEndKey(), k), 0); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/deletefile_test.cc b/db/deletefile_test.cc deleted file mode 100644 index 481eda7dd..000000000 --- a/db/deletefile_test.cc +++ /dev/null @@ -1,603 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - - -#include - -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/version_set.h" -#include "db/write_batch_internal.h" -#include "file/filename.h" -#include "port/stack_trace.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/transaction_log.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class DeleteFileTest : public DBTestBase { - public: - const int numlevels_; - const std::string wal_dir_; - - DeleteFileTest() - : DBTestBase("deletefile_test", /*env_do_fsync=*/true), - numlevels_(7), - wal_dir_(dbname_ + "/wal_files") {} - - void SetOptions(Options* options) { - ASSERT_NE(options, nullptr); - options->delete_obsolete_files_period_micros = 0; // always do full purge - options->enable_thread_tracking = true; - options->write_buffer_size = 1024 * 1024 * 1000; - options->target_file_size_base = 1024 * 1024 * 1000; - options->max_bytes_for_level_base = 1024 * 1024 * 1000; - options->WAL_ttl_seconds = 300; // Used to test log files - options->WAL_size_limit_MB = 1024; // Used to test log files - options->wal_dir = wal_dir_; - } - - void AddKeys(int numkeys, int startkey = 0) { - WriteOptions options; - options.sync = false; - ReadOptions roptions; - for (int i = startkey; i < (numkeys + startkey); i++) { - std::string temp = std::to_string(i); - Slice key(temp); - Slice value(temp); - ASSERT_OK(db_->Put(options, key, value)); - } - } - - int numKeysInLevels(std::vector& metadata, - std::vector* keysperlevel = nullptr) { - if (keysperlevel != nullptr) { - keysperlevel->resize(numlevels_); - } - - int numKeys = 0; - for (size_t i = 0; i < metadata.size(); i++) { - int startkey = atoi(metadata[i].smallestkey.c_str()); - int endkey = atoi(metadata[i].largestkey.c_str()); - int numkeysinfile = (endkey - startkey + 1); - numKeys += numkeysinfile; - if (keysperlevel != nullptr) { - (*keysperlevel)[(int)metadata[i].level] += numkeysinfile; - } - fprintf(stderr, "level %d name %s smallest %s largest %s\n", - metadata[i].level, metadata[i].name.c_str(), - metadata[i].smallestkey.c_str(), metadata[i].largestkey.c_str()); - } - return numKeys; - } - - void CreateTwoLevels() { - AddKeys(50000, 10000); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - for (int i = 0; i < 2; ++i) { - ASSERT_OK(dbfull()->TEST_CompactRange(i, nullptr, nullptr)); - } - - AddKeys(50000, 10000); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); - } - - void CheckFileTypeCounts(const std::string& dir, int required_log, - int required_sst, int required_manifest) { - std::vector filenames; - ASSERT_OK(env_->GetChildren(dir, &filenames)); - - int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0; - for (auto file : filenames) { - uint64_t number; - FileType type; - if (ParseFileName(file, &number, &type)) { - log_cnt += (type == kWalFile); - sst_cnt += (type == kTableFile); - manifest_cnt += (type == kDescriptorFile); - } - } - if (required_log >= 0) { - ASSERT_EQ(required_log, log_cnt); - } - if (required_sst >= 0) { - ASSERT_EQ(required_sst, sst_cnt); - } - if (required_manifest >= 0) { - ASSERT_EQ(required_manifest, manifest_cnt); - } - } - - static void DoSleep(void* arg) { - auto test = reinterpret_cast(arg); - test->env_->SleepForMicroseconds(2 * 1000 * 1000); - } - - // An empty job to guard all jobs are processed - static void GuardFinish(void* /*arg*/) { - TEST_SYNC_POINT("DeleteFileTest::GuardFinish"); - } -}; - -TEST_F(DeleteFileTest, AddKeysAndQueryLevels) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - CreateTwoLevels(); - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - - std::string level1file = ""; - int level1keycount = 0; - std::string level2file = ""; - int level2keycount = 0; - int level1index = 0; - int level2index = 1; - - ASSERT_EQ((int)metadata.size(), 2); - if (metadata[0].level == 2) { - level1index = 1; - level2index = 0; - } - - level1file = metadata[level1index].name; - int startkey = atoi(metadata[level1index].smallestkey.c_str()); - int endkey = atoi(metadata[level1index].largestkey.c_str()); - level1keycount = (endkey - startkey + 1); - level2file = metadata[level2index].name; - startkey = atoi(metadata[level2index].smallestkey.c_str()); - endkey = atoi(metadata[level2index].largestkey.c_str()); - level2keycount = (endkey - startkey + 1); - - // COntrolled setup. Levels 1 and 2 should both have 50K files. - // This is a little fragile as it depends on the current - // compaction heuristics. - ASSERT_EQ(level1keycount, 50000); - ASSERT_EQ(level2keycount, 50000); - - Status status = db_->DeleteFile("0.sst"); - ASSERT_TRUE(status.IsInvalidArgument()); - - // intermediate level files cannot be deleted. - status = db_->DeleteFile(level1file); - ASSERT_TRUE(status.IsInvalidArgument()); - - // Lowest level file deletion should succeed. - status = db_->DeleteFile(level2file); - ASSERT_OK(status); -} - -TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - CreateTwoLevels(); - // there should be only one (empty) log file because CreateTwoLevels() - // flushes the memtables to disk - CheckFileTypeCounts(wal_dir_, 1, 0, 0); - // 2 ssts, 1 manifest - CheckFileTypeCounts(dbname_, 0, 2, 1); - std::string first("0"), last("999999"); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - Slice first_slice(first), last_slice(last); - ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice)); - // 1 sst after compaction - CheckFileTypeCounts(dbname_, 0, 1, 1); - - // this time, we keep an iterator alive - Reopen(options); - Iterator* itr = nullptr; - CreateTwoLevels(); - itr = db_->NewIterator(ReadOptions()); - ASSERT_OK(itr->status()); - ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice)); - ASSERT_OK(itr->status()); - // 3 sst after compaction with live iterator - CheckFileTypeCounts(dbname_, 0, 3, 1); - delete itr; - // 1 sst after iterator deletion - CheckFileTypeCounts(dbname_, 0, 1, 1); -} - -TEST_F(DeleteFileTest, BackgroundPurgeIteratorTest) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - std::string first("0"), last("999999"); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - Slice first_slice(first), last_slice(last); - - // We keep an iterator alive - Iterator* itr = nullptr; - CreateTwoLevels(); - ReadOptions read_options; - read_options.background_purge_on_iterator_cleanup = true; - itr = db_->NewIterator(read_options); - ASSERT_OK(itr->status()); - ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice)); - // 3 sst after compaction with live iterator - CheckFileTypeCounts(dbname_, 0, 3, 1); - test::SleepingBackgroundTask sleeping_task_before; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_before, Env::Priority::HIGH); - delete itr; - test::SleepingBackgroundTask sleeping_task_after; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_after, Env::Priority::HIGH); - - // Make sure no purges are executed foreground - CheckFileTypeCounts(dbname_, 0, 3, 1); - sleeping_task_before.WakeUp(); - sleeping_task_before.WaitUntilDone(); - - // Make sure all background purges are executed - sleeping_task_after.WakeUp(); - sleeping_task_after.WaitUntilDone(); - // 1 sst after iterator deletion - CheckFileTypeCounts(dbname_, 0, 1, 1); -} - -TEST_F(DeleteFileTest, PurgeDuringOpen) { - Options options = CurrentOptions(); - CheckFileTypeCounts(dbname_, -1, 0, -1); - Close(); - std::unique_ptr file; - ASSERT_OK(options.env->NewWritableFile(dbname_ + "/000002.sst", &file, - EnvOptions())); - ASSERT_OK(file->Close()); - CheckFileTypeCounts(dbname_, -1, 1, -1); - options.avoid_unnecessary_blocking_io = false; - options.create_if_missing = false; - Reopen(options); - CheckFileTypeCounts(dbname_, -1, 0, -1); - Close(); - - // test background purge - options.avoid_unnecessary_blocking_io = true; - options.create_if_missing = false; - ASSERT_OK(options.env->NewWritableFile(dbname_ + "/000002.sst", &file, - EnvOptions())); - ASSERT_OK(file->Close()); - CheckFileTypeCounts(dbname_, -1, 1, -1); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency( - {{"DeleteFileTest::PurgeDuringOpen:1", "DBImpl::BGWorkPurge:start"}}); - SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); - // the obsolete file is not deleted until the background purge job is ran - CheckFileTypeCounts(dbname_, -1, 1, -1); - TEST_SYNC_POINT("DeleteFileTest::PurgeDuringOpen:1"); - ASSERT_OK(dbfull()->TEST_WaitForPurge()); - CheckFileTypeCounts(dbname_, -1, 0, -1); -} - -TEST_F(DeleteFileTest, BackgroundPurgeCFDropTest) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - auto do_test = [&](bool bg_purge) { - ColumnFamilyOptions co; - co.max_write_buffer_size_to_maintain = - static_cast(co.write_buffer_size); - WriteOptions wo; - FlushOptions fo; - ColumnFamilyHandle* cfh = nullptr; - - ASSERT_OK(db_->CreateColumnFamily(co, "dropme", &cfh)); - - ASSERT_OK(db_->Put(wo, cfh, "pika", "chu")); - ASSERT_OK(db_->Flush(fo, cfh)); - // Expect 1 sst file. - CheckFileTypeCounts(dbname_, 0, 1, 1); - - ASSERT_OK(db_->DropColumnFamily(cfh)); - // Still 1 file, it won't be deleted while ColumnFamilyHandle is alive. - CheckFileTypeCounts(dbname_, 0, 1, 1); - - delete cfh; - test::SleepingBackgroundTask sleeping_task_after; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_after, Env::Priority::HIGH); - // If background purge is enabled, the file should still be there. - CheckFileTypeCounts(dbname_, 0, bg_purge ? 1 : 0, 1); - TEST_SYNC_POINT("DeleteFileTest::BackgroundPurgeCFDropTest:1"); - - // Execute background purges. - sleeping_task_after.WakeUp(); - sleeping_task_after.WaitUntilDone(); - // The file should have been deleted. - CheckFileTypeCounts(dbname_, 0, 0, 1); - }; - - { - SCOPED_TRACE("avoid_unnecessary_blocking_io = false"); - do_test(false); - } - - options.avoid_unnecessary_blocking_io = true; - options.create_if_missing = false; - Reopen(options); - ASSERT_OK(dbfull()->TEST_WaitForPurge()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency( - {{"DeleteFileTest::BackgroundPurgeCFDropTest:1", - "DBImpl::BGWorkPurge:start"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - { - SCOPED_TRACE("avoid_unnecessary_blocking_io = true"); - do_test(true); - } -} - -// This test is to reproduce a bug that read invalid ReadOption in iterator -// cleanup function -TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - std::string first("0"), last("999999"); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - Slice first_slice(first), last_slice(last); - - // We keep an iterator alive - Iterator* itr = nullptr; - CreateTwoLevels(); - { - ReadOptions read_options; - read_options.background_purge_on_iterator_cleanup = true; - itr = db_->NewIterator(read_options); - ASSERT_OK(itr->status()); - // ReadOptions is deleted, but iterator cleanup function should not be - // affected - } - - ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice)); - // 3 sst after compaction with live iterator - CheckFileTypeCounts(dbname_, 0, 3, 1); - delete itr; - - test::SleepingBackgroundTask sleeping_task_after; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, - &sleeping_task_after, Env::Priority::HIGH); - - // Make sure all background purges are executed - sleeping_task_after.WakeUp(); - sleeping_task_after.WaitUntilDone(); - // 1 sst after iterator deletion - CheckFileTypeCounts(dbname_, 0, 1, 1); -} - -TEST_F(DeleteFileTest, BackgroundPurgeTestMultipleJobs) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - std::string first("0"), last("999999"); - CompactRangeOptions compact_options; - compact_options.change_level = true; - compact_options.target_level = 2; - Slice first_slice(first), last_slice(last); - - // We keep an iterator alive - CreateTwoLevels(); - ReadOptions read_options; - read_options.background_purge_on_iterator_cleanup = true; - Iterator* itr1 = db_->NewIterator(read_options); - ASSERT_OK(itr1->status()); - CreateTwoLevels(); - Iterator* itr2 = db_->NewIterator(read_options); - ASSERT_OK(itr2->status()); - ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice)); - // 5 sst files after 2 compactions with 2 live iterators - CheckFileTypeCounts(dbname_, 0, 5, 1); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - // ~DBImpl should wait until all BGWorkPurge are finished - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::~DBImpl:WaitJob", "DBImpl::BGWorkPurge"}, - {"DeleteFileTest::GuardFinish", - "DeleteFileTest::BackgroundPurgeTestMultipleJobs:DBClose"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - delete itr1; - env_->Schedule(&DeleteFileTest::DoSleep, this, Env::Priority::HIGH); - delete itr2; - env_->Schedule(&DeleteFileTest::GuardFinish, nullptr, Env::Priority::HIGH); - Close(); - - TEST_SYNC_POINT("DeleteFileTest::BackgroundPurgeTestMultipleJobs:DBClose"); - // 1 sst after iterator deletion - CheckFileTypeCounts(dbname_, 0, 1, 1); -} - -TEST_F(DeleteFileTest, DeleteFileWithIterator) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - CreateTwoLevels(); - ReadOptions read_options; - Iterator* it = db_->NewIterator(read_options); - ASSERT_OK(it->status()); - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - - std::string level2file; - - ASSERT_EQ(metadata.size(), static_cast(2)); - if (metadata[0].level == 1) { - level2file = metadata[1].name; - } else { - level2file = metadata[0].name; - } - - Status status = db_->DeleteFile(level2file); - fprintf(stdout, "Deletion status %s: %s\n", level2file.c_str(), - status.ToString().c_str()); - ASSERT_OK(status); - it->SeekToFirst(); - int numKeysIterated = 0; - while (it->Valid()) { - numKeysIterated++; - it->Next(); - } - ASSERT_EQ(numKeysIterated, 50000); - delete it; -} - -TEST_F(DeleteFileTest, DeleteLogFiles) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - - AddKeys(10, 0); - VectorLogPtr logfiles; - ASSERT_OK(db_->GetSortedWalFiles(logfiles)); - ASSERT_GT(logfiles.size(), 0UL); - // Take the last log file which is expected to be alive and try to delete it - // Should not succeed because live logs are not allowed to be deleted - std::unique_ptr alive_log = std::move(logfiles.back()); - ASSERT_EQ(alive_log->Type(), kAliveLogFile); - ASSERT_OK(env_->FileExists(wal_dir_ + "/" + alive_log->PathName())); - fprintf(stdout, "Deleting alive log file %s\n", - alive_log->PathName().c_str()); - ASSERT_NOK(db_->DeleteFile(alive_log->PathName())); - ASSERT_OK(env_->FileExists(wal_dir_ + "/" + alive_log->PathName())); - logfiles.clear(); - - // Call Flush to bring about a new working log file and add more keys - // Call Flush again to flush out memtable and move alive log to archived log - // and try to delete the archived log file - FlushOptions fopts; - ASSERT_OK(db_->Flush(fopts)); - AddKeys(10, 0); - ASSERT_OK(db_->Flush(fopts)); - ASSERT_OK(db_->GetSortedWalFiles(logfiles)); - ASSERT_GT(logfiles.size(), 0UL); - std::unique_ptr archived_log = std::move(logfiles.front()); - ASSERT_EQ(archived_log->Type(), kArchivedLogFile); - ASSERT_OK(env_->FileExists(wal_dir_ + "/" + archived_log->PathName())); - fprintf(stdout, "Deleting archived log file %s\n", - archived_log->PathName().c_str()); - ASSERT_OK(db_->DeleteFile(archived_log->PathName())); - ASSERT_TRUE( - env_->FileExists(wal_dir_ + "/" + archived_log->PathName()).IsNotFound()); -} - -TEST_F(DeleteFileTest, DeleteNonDefaultColumnFamily) { - Options options = CurrentOptions(); - SetOptions(&options); - Destroy(options); - options.create_if_missing = true; - Reopen(options); - CreateAndReopenWithCF({"new_cf"}, options); - - Random rnd(5); - for (int i = 0; i < 1000; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), handles_[1], test::RandomKey(&rnd, 10), - test::RandomKey(&rnd, 10))); - } - ASSERT_OK(db_->Flush(FlushOptions(), handles_[1])); - for (int i = 0; i < 1000; ++i) { - ASSERT_OK(db_->Put(WriteOptions(), handles_[1], test::RandomKey(&rnd, 10), - test::RandomKey(&rnd, 10))); - } - ASSERT_OK(db_->Flush(FlushOptions(), handles_[1])); - - std::vector metadata; - db_->GetLiveFilesMetaData(&metadata); - ASSERT_EQ(2U, metadata.size()); - ASSERT_EQ("new_cf", metadata[0].column_family_name); - ASSERT_EQ("new_cf", metadata[1].column_family_name); - auto old_file = metadata[0].smallest_seqno < metadata[1].smallest_seqno - ? metadata[0].name - : metadata[1].name; - auto new_file = metadata[0].smallest_seqno > metadata[1].smallest_seqno - ? metadata[0].name - : metadata[1].name; - ASSERT_TRUE(db_->DeleteFile(new_file).IsInvalidArgument()); - ASSERT_OK(db_->DeleteFile(old_file)); - - { - std::unique_ptr itr(db_->NewIterator(ReadOptions(), handles_[1])); - ASSERT_OK(itr->status()); - int count = 0; - for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { - ASSERT_OK(itr->status()); - ++count; - } - ASSERT_EQ(count, 1000); - } - - Close(); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "new_cf"}, options); - - { - std::unique_ptr itr(db_->NewIterator(ReadOptions(), handles_[1])); - int count = 0; - for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { - ASSERT_OK(itr->status()); - ++count; - } - ASSERT_EQ(count, 1000); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/error_handler_fs_test.cc b/db/error_handler_fs_test.cc deleted file mode 100644 index 82008705d..000000000 --- a/db/error_handler_fs_test.cc +++ /dev/null @@ -1,2862 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/db_test_util.h" -#include "file/sst_file_manager_impl.h" -#include "port/stack_trace.h" -#include "rocksdb/io_status.h" -#include "rocksdb/sst_file_manager.h" -#include "test_util/sync_point.h" -#include "util/random.h" -#include "utilities/fault_injection_env.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -class DBErrorHandlingFSTest : public DBTestBase { - public: - DBErrorHandlingFSTest() - : DBTestBase("db_error_handling_fs_test", /*env_do_fsync=*/true) { - fault_fs_.reset(new FaultInjectionTestFS(env_->GetFileSystem())); - fault_env_.reset(new CompositeEnvWrapper(env_, fault_fs_)); - } - - std::string GetManifestNameFromLiveFiles() { - std::vector live_files; - uint64_t manifest_size; - - Status s = dbfull()->GetLiveFiles(live_files, &manifest_size, false); - if (!s.ok()) { - return ""; - } - for (auto& file : live_files) { - uint64_t num = 0; - FileType type; - if (ParseFileName(file, &num, &type) && type == kDescriptorFile) { - return file; - } - } - return ""; - } - - std::shared_ptr fault_fs_; - std::unique_ptr fault_env_; -}; - -class ErrorHandlerFSListener : public EventListener { - public: - ErrorHandlerFSListener() - : mutex_(), - cv_(&mutex_), - no_auto_recovery_(false), - recovery_complete_(false), - file_creation_started_(false), - override_bg_error_(false), - file_count_(0), - fault_fs_(nullptr) {} - ~ErrorHandlerFSListener() { - file_creation_error_.PermitUncheckedError(); - bg_error_.PermitUncheckedError(); - new_bg_error_.PermitUncheckedError(); - } - - void OnTableFileCreationStarted( - const TableFileCreationBriefInfo& /*ti*/) override { - InstrumentedMutexLock l(&mutex_); - file_creation_started_ = true; - if (file_count_ > 0) { - if (--file_count_ == 0) { - fault_fs_->SetFilesystemActive(false, file_creation_error_); - file_creation_error_ = IOStatus::OK(); - } - } - cv_.SignalAll(); - } - - void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/, Status bg_error, - bool* auto_recovery) override { - bg_error.PermitUncheckedError(); - if (*auto_recovery && no_auto_recovery_) { - *auto_recovery = false; - } - } - - void OnErrorRecoveryEnd(const BackgroundErrorRecoveryInfo& info) override { - InstrumentedMutexLock l(&mutex_); - recovery_complete_ = true; - cv_.SignalAll(); - new_bg_error_ = info.new_bg_error; - } - - bool WaitForRecovery(uint64_t /*abs_time_us*/) { - InstrumentedMutexLock l(&mutex_); - while (!recovery_complete_) { - cv_.Wait(/*abs_time_us*/); - } - if (recovery_complete_) { - recovery_complete_ = false; - return true; - } - return false; - } - - void WaitForTableFileCreationStarted(uint64_t /*abs_time_us*/) { - InstrumentedMutexLock l(&mutex_); - while (!file_creation_started_) { - cv_.Wait(/*abs_time_us*/); - } - file_creation_started_ = false; - } - - void OnBackgroundError(BackgroundErrorReason /*reason*/, - Status* bg_error) override { - if (override_bg_error_) { - *bg_error = bg_error_; - override_bg_error_ = false; - } - } - - void EnableAutoRecovery(bool enable = true) { no_auto_recovery_ = !enable; } - - void OverrideBGError(Status bg_err) { - bg_error_ = bg_err; - override_bg_error_ = true; - } - - void InjectFileCreationError(FaultInjectionTestFS* fs, int file_count, - IOStatus io_s) { - fault_fs_ = fs; - file_count_ = file_count; - file_creation_error_ = io_s; - } - - Status new_bg_error() { return new_bg_error_; } - - private: - InstrumentedMutex mutex_; - InstrumentedCondVar cv_; - bool no_auto_recovery_; - bool recovery_complete_; - bool file_creation_started_; - bool override_bg_error_; - int file_count_; - IOStatus file_creation_error_; - Status bg_error_; - Status new_bg_error_; - FaultInjectionTestFS* fault_fs_; -}; - -TEST_F(DBErrorHandlingFSTest, FLushWriteError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs_->SetFilesystemActive(false, IOStatus::NoSpace("Out of space")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - Destroy(options); -} - -// All the NoSpace IOError will be handled as the regular BG Error no matter the -// retryable flag is set of not. So the auto resume for retryable IO Error will -// not be triggered. Also, it is mapped as hard error. -TEST_F(DBErrorHandlingFSTest, FLushWriteNoSpaceError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::NoSpace("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(1), "val1")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FLushWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(1), "val1")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - Reopen(options); - ASSERT_EQ("val1", Get(Key(1))); - - ASSERT_OK(Put(Key(2), "val2")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeSyncTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Reopen(options); - ASSERT_EQ("val2", Get(Key(2))); - - ASSERT_OK(Put(Key(3), "val3")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeCloseTableFile", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Reopen(options); - ASSERT_EQ("val3", Get(Key(3))); - - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FLushWriteFileScopeError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("File Scope Data Loss Error"); - error_msg.SetDataLoss(true); - error_msg.SetScope( - ROCKSDB_NAMESPACE::IOStatus::IOErrorScope::kIOErrorScopeFile); - error_msg.SetRetryable(false); - - ASSERT_OK(Put(Key(1), "val1")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Reopen(options); - ASSERT_EQ("val1", Get(Key(1))); - - ASSERT_OK(Put(Key(2), "val2")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeSyncTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Reopen(options); - ASSERT_EQ("val2", Get(Key(2))); - - ASSERT_OK(Put(Key(3), "val3")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeCloseTableFile", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Reopen(options); - ASSERT_EQ("val3", Get(Key(3))); - - // not file scope, but retyrable set - error_msg.SetDataLoss(false); - error_msg.SetScope( - ROCKSDB_NAMESPACE::IOStatus::IOErrorScope::kIOErrorScopeFileSystem); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(3), "val3")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeCloseTableFile", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Reopen(options); - ASSERT_EQ("val3", Get(Key(3))); - - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FLushWALWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - listener->EnableAutoRecovery(false); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::SyncClosedLogs:Start", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu, sdfsdfsdf"}, options); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = false; - ASSERT_OK(Put(Key(1), "val1", wo)); - - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - auto cfh = dbfull()->GetColumnFamilyHandle(1); - s = dbfull()->DropColumnFamily(cfh); - - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_OK(Put(Key(3), "val3", wo)); - ASSERT_EQ("val3", Get(Key(3))); - s = Flush(); - ASSERT_OK(s); - ASSERT_EQ("val3", Get(Key(3))); - - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FLushWALAtomicWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - options.atomic_flush = true; - Status s; - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - listener->EnableAutoRecovery(false); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::SyncClosedLogs:Start", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateAndReopenWithCF({"pikachu, sdfsdfsdf"}, options); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = false; - ASSERT_OK(Put(Key(1), "val1", wo)); - - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - auto cfh = dbfull()->GetColumnFamilyHandle(1); - s = dbfull()->DropColumnFamily(cfh); - - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_OK(Put(Key(3), "val3", wo)); - ASSERT_EQ("val3", Get(Key(3))); - s = Flush(); - ASSERT_OK(s); - ASSERT_EQ("val3", Get(Key(3))); - - Destroy(options); -} - -// The flush error is injected before we finish the table build -TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableError1) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - ASSERT_OK(Put(Key(1), "val1", wo)); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_OK(Put(Key(2), "val2", wo)); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - ASSERT_EQ("val2", Get(Key(2))); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ("val2", Get(Key(2))); - ASSERT_OK(Put(Key(3), "val3", wo)); - ASSERT_EQ("val3", Get(Key(3))); - s = Flush(); - ASSERT_OK(s); - ASSERT_EQ("val3", Get(Key(3))); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - - Destroy(options); -} - -// The retryable IO error is injected before we sync table -TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError2) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - - ASSERT_OK(Put(Key(1), "val1", wo)); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeSyncTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_OK(Put(Key(2), "val2", wo)); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - ASSERT_EQ("val2", Get(Key(2))); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ("val2", Get(Key(2))); - ASSERT_OK(Put(Key(3), "val3", wo)); - ASSERT_EQ("val3", Get(Key(3))); - s = Flush(); - ASSERT_OK(s); - ASSERT_EQ("val3", Get(Key(3))); - - Destroy(options); -} - -// The retryable IO error is injected before we close the table file -TEST_F(DBErrorHandlingFSTest, FLushWriteNoWALRetryableError3) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - - ASSERT_OK(Put(Key(1), "val1", wo)); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeCloseTableFile", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_OK(Put(Key(2), "val2", wo)); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - ASSERT_EQ("val2", Get(Key(2))); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ("val2", Get(Key(2))); - ASSERT_OK(Put(Key(3), "val3", wo)); - ASSERT_EQ("val3", Get(Key(3))); - s = Flush(); - ASSERT_OK(s); - ASSERT_EQ("val3", Get(Key(3))); - - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, ManifestWriteError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val")); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - fault_fs_->SetFilesystemActive(false, - IOStatus::NoSpace("Out of space")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val")); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, ManifestWriteFileScopeError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("File Scope Data Loss Error"); - error_msg.SetDataLoss(true); - error_msg.SetScope( - ROCKSDB_NAMESPACE::IOStatus::IOErrorScope::kIOErrorScopeFile); - error_msg.SetRetryable(false); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val")); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - ASSERT_OK(Put(Key(0), "val", wo)); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val", wo)); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val")); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - fault_fs_->SetFilesystemActive(false, - IOStatus::NoSpace("Out of space")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - fault_fs_->SetFilesystemActive(true); - - // This Resume() will attempt to create a new manifest file and fail again - s = dbfull()->Resume(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - - // A successful Resume() will create a new manifest file - s = dbfull()->Resume(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) { - if (mem_env_ != nullptr) { - ROCKSDB_GTEST_SKIP("Test requires non-mock environment"); - return; - } - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - Status s; - std::string old_manifest; - std::string new_manifest; - std::atomic fail_manifest(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Put(Key(2), "val")); - s = Flush(); - ASSERT_OK(s); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - // Wait for flush of 2nd L0 file before starting compaction - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}, - // Wait for compaction to detect manifest write error - {"BackgroundCallCompaction:1", "CompactionManifestWriteError:0"}, - // Make compaction thread wait for error to be cleared - {"CompactionManifestWriteError:1", - "DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"}, - // Wait for DB instance to clear bg_error before calling - // TEST_WaitForCompact - {"SstFileManagerImpl::ErrorCleared", "CompactionManifestWriteError:2"}}); - // trigger manifest write failure in compaction thread - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { fail_manifest.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - if (fail_manifest.load()) { - fault_fs_->SetFilesystemActive(false, - IOStatus::NoSpace("Out of space")); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - // This Flush will trigger a compaction, which will fail when appending to - // the manifest - s = Flush(); - ASSERT_OK(s); - - TEST_SYNC_POINT("CompactionManifestWriteError:0"); - // Clear all errors so when the compaction is retried, it will succeed - fault_fs_->SetFilesystemActive(true); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - TEST_SYNC_POINT("CompactionManifestWriteError:1"); - TEST_SYNC_POINT("CompactionManifestWriteError:2"); - - s = dbfull()->TEST_WaitForCompact(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - ASSERT_EQ("val", Get(Key(2))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - std::string old_manifest; - std::string new_manifest; - std::atomic fail_manifest(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Put(Key(2), "val")); - s = Flush(); - ASSERT_OK(s); - - listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - // Wait for flush of 2nd L0 file before starting compaction - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}, - // Wait for compaction to detect manifest write error - {"BackgroundCallCompaction:1", "CompactionManifestWriteError:0"}, - // Make compaction thread wait for error to be cleared - {"CompactionManifestWriteError:1", - "DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"}}); - // trigger manifest write failure in compaction thread - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { fail_manifest.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - if (fail_manifest.load()) { - fault_fs_->SetFilesystemActive(false, error_msg); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - TEST_SYNC_POINT("CompactionManifestWriteError:0"); - TEST_SYNC_POINT("CompactionManifestWriteError:1"); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - s = dbfull()->Resume(); - ASSERT_OK(s); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - ASSERT_EQ("val", Get(Key(2))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, CompactionWriteError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - Status s; - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "va;")); - ASSERT_OK(Put(Key(2), "va;")); - s = Flush(); - ASSERT_OK(s); - - listener->OverrideBGError( - Status(Status::NoSpace(), Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { - fault_fs_->SetFilesystemActive(false, - IOStatus::NoSpace("Out of space")); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_OK(s); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(0), "va;")); - ASSERT_OK(Put(Key(2), "va;")); - s = Flush(); - ASSERT_OK(s); - - listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::OpenCompactionOutputFile", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Finish", - [&](void*) { CancelAllBackgroundWork(dbfull()); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_GetBGError(); - ASSERT_OK(s); - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - s = dbfull()->Resume(); - ASSERT_OK(s); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, DISABLED_CompactionWriteFileScopeError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 0; - Status s; - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("File Scope Data Loss Error"); - error_msg.SetDataLoss(true); - error_msg.SetScope( - ROCKSDB_NAMESPACE::IOStatus::IOErrorScope::kIOErrorScopeFile); - error_msg.SetRetryable(false); - - ASSERT_OK(Put(Key(0), "va;")); - ASSERT_OK(Put(Key(2), "va;")); - s = Flush(); - ASSERT_OK(s); - - listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::OpenCompactionOutputFile", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Finish", - [&](void*) { CancelAllBackgroundWork(dbfull()); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_GetBGError(); - ASSERT_OK(s); - - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - s = dbfull()->Resume(); - ASSERT_OK(s); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, CorruptionError) { - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - Status s; - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "va;")); - ASSERT_OK(Put(Key(2), "va;")); - s = Flush(); - ASSERT_OK(s); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { - fault_fs_->SetFilesystemActive(false, - IOStatus::Corruption("Corruption")); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), - ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError); - - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_NOK(s); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) { - if (mem_env_ != nullptr) { - ROCKSDB_GTEST_SKIP("Test requires non-mock environment"); - return; - } - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(); - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs_->SetFilesystemActive(false, IOStatus::NoSpace("Out of space")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - - s = Put(Key(1), "val"); - ASSERT_OK(s); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_EQ(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FailRecoverFlushError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - Status s; - - listener->EnableAutoRecovery(); - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs_->SetFilesystemActive(false, IOStatus::NoSpace("Out of space")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - // We should be able to shutdown the database while auto recovery is going - // on in the background - Close(); - DestroyDB(dbname_, options).PermitUncheckedError(); -} - -TEST_F(DBErrorHandlingFSTest, WALWriteError) { - if (mem_env_ != nullptr) { - ROCKSDB_GTEST_SKIP("Test requires non-mock environment"); - return; - } - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.writable_file_max_buffer_size = 32768; - options.listeners.emplace_back(listener); - Status s; - Random rnd(301); - - listener->EnableAutoRecovery(); - DestroyAndReopen(options); - - { - WriteBatch batch; - - for (auto i = 0; i < 100; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - { - WriteBatch batch; - int write_error = 0; - - for (auto i = 100; i < 199; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { - write_error++; - if (write_error > 2) { - fault_fs_->SetFilesystemActive(false, - IOStatus::NoSpace("Out of space")); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - WriteOptions wopts; - wopts.sync = true; - s = dbfull()->Write(wopts, &batch); - ASSERT_EQ(s, s.NoSpace()); - } - SyncPoint::GetInstance()->DisableProcessing(); - // `ClearAllCallBacks()` is needed in addition to `DisableProcessing()` to - // drain all callbacks. Otherwise, a pending callback in the background - // could re-disable `fault_fs_` after we enable it below. - SyncPoint::GetInstance()->ClearAllCallBacks(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - for (auto i = 0; i < 199; ++i) { - if (i < 100) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - Reopen(options); - for (auto i = 0; i < 199; ++i) { - if (i < 100) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - Close(); -} - -TEST_F(DBErrorHandlingFSTest, WALWriteRetryableError) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.writable_file_max_buffer_size = 32768; - options.listeners.emplace_back(listener); - options.paranoid_checks = true; - options.max_bgerror_resume_count = 0; - Random rnd(301); - - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - // For the first batch, write is successful, require sync - { - WriteBatch batch; - - for (auto i = 0; i < 100; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - // For the second batch, the first 2 file Append are successful, then the - // following Append fails due to file system retryable IOError. - { - WriteBatch batch; - int write_error = 0; - - for (auto i = 100; i < 200; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { - write_error++; - if (write_error > 2) { - fault_fs_->SetFilesystemActive(false, error_msg); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - WriteOptions wopts; - wopts.sync = true; - Status s = dbfull()->Write(wopts, &batch); - ASSERT_TRUE(s.IsIOError()); - } - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - - // Data in corrupted WAL are not stored - for (auto i = 0; i < 199; ++i) { - if (i < 100) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - - // Resume and write a new batch, should be in the WAL - ASSERT_OK(dbfull()->Resume()); - { - WriteBatch batch; - - for (auto i = 200; i < 300; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - Reopen(options); - for (auto i = 0; i < 300; ++i) { - if (i < 100 || i >= 200) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - Close(); -} - -TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) { - if (mem_env_ != nullptr) { - ROCKSDB_GTEST_SKIP("Test requires non-mock environment"); - return; - } - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.writable_file_max_buffer_size = 32768; - options.listeners.emplace_back(listener); - Random rnd(301); - - listener->EnableAutoRecovery(); - CreateAndReopenWithCF({"one", "two", "three"}, options); - - { - WriteBatch batch; - - for (auto i = 1; i < 4; ++i) { - for (auto j = 0; j < 100; ++j) { - ASSERT_OK(batch.Put(handles_[i], Key(j), rnd.RandomString(1024))); - } - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - { - WriteBatch batch; - int write_error = 0; - - // Write to one CF - for (auto i = 100; i < 199; ++i) { - ASSERT_OK(batch.Put(handles_[2], Key(i), rnd.RandomString(1024))); - } - - SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { - write_error++; - if (write_error > 2) { - fault_fs_->SetFilesystemActive(false, - IOStatus::NoSpace("Out of space")); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - WriteOptions wopts; - wopts.sync = true; - Status s = dbfull()->Write(wopts, &batch); - ASSERT_TRUE(s.IsNoSpace()); - } - SyncPoint::GetInstance()->DisableProcessing(); - // `ClearAllCallBacks()` is needed in addition to `DisableProcessing()` to - // drain all callbacks. Otherwise, a pending callback in the background - // could re-disable `fault_fs_` after we enable it below. - SyncPoint::GetInstance()->ClearAllCallBacks(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - - for (auto i = 1; i < 4; ++i) { - // Every CF should have been flushed - ASSERT_EQ(NumTableFilesAtLevel(0, i), 1); - } - - for (auto i = 1; i < 4; ++i) { - for (auto j = 0; j < 199; ++j) { - if (j < 100) { - ASSERT_NE(Get(i, Key(j)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(i, Key(j)), "NOT_FOUND"); - } - } - } - ReopenWithColumnFamilies({"default", "one", "two", "three"}, options); - for (auto i = 1; i < 4; ++i) { - for (auto j = 0; j < 199; ++j) { - if (j < 100) { - ASSERT_NE(Get(i, Key(j)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(i, Key(j)), "NOT_FOUND"); - } - } - } - Close(); -} - -TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) { - if (mem_env_ != nullptr) { - ROCKSDB_GTEST_SKIP("Test requires non-mock environment"); - return; - } - FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(env_); - std::vector> fault_envs; - std::vector fault_fs; - std::vector options; - std::vector> listener; - std::vector db; - std::shared_ptr sfm(NewSstFileManager(def_env)); - int kNumDbInstances = 3; - Random rnd(301); - - for (auto i = 0; i < kNumDbInstances; ++i) { - listener.emplace_back(new ErrorHandlerFSListener()); - options.emplace_back(GetDefaultOptions()); - fault_fs.emplace_back(new FaultInjectionTestFS(env_->GetFileSystem())); - std::shared_ptr fs(fault_fs.back()); - fault_envs.emplace_back(new CompositeEnvWrapper(def_env, fs)); - options[i].env = fault_envs.back().get(); - options[i].create_if_missing = true; - options[i].level0_file_num_compaction_trigger = 2; - options[i].writable_file_max_buffer_size = 32768; - options[i].listeners.emplace_back(listener[i]); - options[i].sst_file_manager = sfm; - DB* dbptr; - char buf[16]; - - listener[i]->EnableAutoRecovery(); - // Setup for returning error for the 3rd SST, which would be level 1 - listener[i]->InjectFileCreationError(fault_fs[i], 3, - IOStatus::NoSpace("Out of space")); - snprintf(buf, sizeof(buf), "_%d", i); - ASSERT_OK(DestroyDB(dbname_ + std::string(buf), options[i])); - ASSERT_OK(DB::Open(options[i], dbname_ + std::string(buf), &dbptr)); - db.emplace_back(dbptr); - } - - for (auto i = 0; i < kNumDbInstances; ++i) { - WriteBatch batch; - - for (auto j = 0; j <= 100; ++j) { - ASSERT_OK(batch.Put(Key(j), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(db[i]->Write(wopts, &batch)); - ASSERT_OK(db[i]->Flush(FlushOptions())); - } - - def_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); - for (auto i = 0; i < kNumDbInstances; ++i) { - WriteBatch batch; - - // Write to one CF - for (auto j = 100; j < 199; ++j) { - ASSERT_OK(batch.Put(Key(j), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(db[i]->Write(wopts, &batch)); - ASSERT_OK(db[i]->Flush(FlushOptions())); - } - - for (auto i = 0; i < kNumDbInstances; ++i) { - Status s = static_cast(db[i])->TEST_WaitForCompact(true); - ASSERT_EQ(s.severity(), Status::Severity::kSoftError); - fault_fs[i]->SetFilesystemActive(true); - } - - def_env->SetFilesystemActive(true); - for (auto i = 0; i < kNumDbInstances; ++i) { - std::string prop; - ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true); - ASSERT_OK(static_cast(db[i])->TEST_WaitForCompact(true)); - EXPECT_TRUE(db[i]->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(0), &prop)); - EXPECT_EQ(atoi(prop.c_str()), 0); - EXPECT_TRUE(db[i]->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(1), &prop)); - EXPECT_EQ(atoi(prop.c_str()), 1); - } - - SstFileManagerImpl* sfmImpl = - static_cast_with_check(sfm.get()); - sfmImpl->Close(); - - for (auto i = 0; i < kNumDbInstances; ++i) { - char buf[16]; - snprintf(buf, sizeof(buf), "_%d", i); - delete db[i]; - fault_fs[i]->SetFilesystemActive(true); - if (getenv("KEEP_DB")) { - printf("DB is still at %s%s\n", dbname_.c_str(), buf); - } else { - ASSERT_OK(DestroyDB(dbname_ + std::string(buf), options[i])); - } - } - options.clear(); - sfm.reset(); - delete def_env; -} - -TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) { - if (mem_env_ != nullptr) { - ROCKSDB_GTEST_SKIP("Test requires non-mock environment"); - return; - } - FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(env_); - std::vector> fault_envs; - std::vector fault_fs; - std::vector options; - std::vector> listener; - std::vector db; - std::shared_ptr sfm(NewSstFileManager(def_env)); - int kNumDbInstances = 3; - Random rnd(301); - - for (auto i = 0; i < kNumDbInstances; ++i) { - listener.emplace_back(new ErrorHandlerFSListener()); - options.emplace_back(GetDefaultOptions()); - fault_fs.emplace_back(new FaultInjectionTestFS(env_->GetFileSystem())); - std::shared_ptr fs(fault_fs.back()); - fault_envs.emplace_back(new CompositeEnvWrapper(def_env, fs)); - options[i].env = fault_envs.back().get(); - options[i].create_if_missing = true; - options[i].level0_file_num_compaction_trigger = 2; - options[i].writable_file_max_buffer_size = 32768; - options[i].listeners.emplace_back(listener[i]); - options[i].sst_file_manager = sfm; - DB* dbptr; - char buf[16]; - - listener[i]->EnableAutoRecovery(); - switch (i) { - case 0: - // Setup for returning error for the 3rd SST, which would be level 1 - listener[i]->InjectFileCreationError(fault_fs[i], 3, - IOStatus::NoSpace("Out of space")); - break; - case 1: - // Setup for returning error after the 1st SST, which would result - // in a hard error - listener[i]->InjectFileCreationError(fault_fs[i], 2, - IOStatus::NoSpace("Out of space")); - break; - default: - break; - } - snprintf(buf, sizeof(buf), "_%d", i); - ASSERT_OK(DestroyDB(dbname_ + std::string(buf), options[i])); - ASSERT_OK(DB::Open(options[i], dbname_ + std::string(buf), &dbptr)); - db.emplace_back(dbptr); - } - - for (auto i = 0; i < kNumDbInstances; ++i) { - WriteBatch batch; - - for (auto j = 0; j <= 100; ++j) { - ASSERT_OK(batch.Put(Key(j), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(db[i]->Write(wopts, &batch)); - ASSERT_OK(db[i]->Flush(FlushOptions())); - } - - def_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); - for (auto i = 0; i < kNumDbInstances; ++i) { - WriteBatch batch; - - // Write to one CF - for (auto j = 100; j < 199; ++j) { - ASSERT_OK(batch.Put(Key(j), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(db[i]->Write(wopts, &batch)); - if (i != 1) { - ASSERT_OK(db[i]->Flush(FlushOptions())); - } else { - ASSERT_TRUE(db[i]->Flush(FlushOptions()).IsNoSpace()); - } - } - - for (auto i = 0; i < kNumDbInstances; ++i) { - Status s = static_cast(db[i])->TEST_WaitForCompact(true); - switch (i) { - case 0: - ASSERT_EQ(s.severity(), Status::Severity::kSoftError); - break; - case 1: - ASSERT_EQ(s.severity(), Status::Severity::kHardError); - break; - case 2: - ASSERT_OK(s); - break; - } - fault_fs[i]->SetFilesystemActive(true); - } - - def_env->SetFilesystemActive(true); - for (auto i = 0; i < kNumDbInstances; ++i) { - std::string prop; - if (i < 2) { - ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true); - } - if (i == 1) { - ASSERT_OK(static_cast(db[i])->TEST_WaitForCompact(true)); - } - EXPECT_TRUE(db[i]->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(0), &prop)); - EXPECT_EQ(atoi(prop.c_str()), 0); - EXPECT_TRUE(db[i]->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(1), &prop)); - EXPECT_EQ(atoi(prop.c_str()), 1); - } - - SstFileManagerImpl* sfmImpl = - static_cast_with_check(sfm.get()); - sfmImpl->Close(); - - for (auto i = 0; i < kNumDbInstances; ++i) { - char buf[16]; - snprintf(buf, sizeof(buf), "_%d", i); - fault_fs[i]->SetFilesystemActive(true); - delete db[i]; - if (getenv("KEEP_DB")) { - printf("DB is still at %s%s\n", dbname_.c_str(), buf); - } else { - EXPECT_OK(DestroyDB(dbname_ + std::string(buf), options[i])); - } - } - options.clear(); - delete def_env; -} - -// When Put the KV-pair, the write option is set to disable WAL. -// If retryable error happens in this condition, map the bg error -// to soft error and trigger auto resume. During auto resume, SwitchMemtable -// is disabled to avoid small SST tables. Write can still be applied before -// the bg error is cleaned unless the memtable is full. -TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover1) { - // Activate the FS before the first resume - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - ASSERT_OK(Put(Key(1), "val1", wo)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"RecoverFromRetryableBGIOError:LoopOut", - "FLushWritNoWALRetryableeErrorAutoRecover1:1"}}); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - TEST_SYNC_POINT("FLushWritNoWALRetryableeErrorAutoRecover1:1"); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ("val1", Get(Key(1))); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(3, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(3, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(3, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_LE(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_LE(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - HistogramData autoresume_retry; - options.statistics->histogramData(ERROR_HANDLER_AUTORESUME_RETRY_COUNT, - &autoresume_retry); - ASSERT_GE(autoresume_retry.max, 0); - ASSERT_OK(Put(Key(2), "val2", wo)); - s = Flush(); - // Since auto resume fails, the bg error is not cleand, flush will - // return the bg_error set before. - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - ASSERT_EQ("val2", Get(Key(2))); - - // call auto resume - ASSERT_OK(dbfull()->Resume()); - ASSERT_OK(Put(Key(3), "val3", wo)); - // After resume is successful, the flush should be ok. - ASSERT_OK(Flush()); - ASSERT_EQ("val3", Get(Key(3))); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FLushWritNoWALRetryableErrorAutoRecover2) { - // Activate the FS before the first resume - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - ASSERT_OK(Put(Key(1), "val1", wo)); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_LE(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - ASSERT_LE(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT)); - HistogramData autoresume_retry; - options.statistics->histogramData(ERROR_HANDLER_AUTORESUME_RETRY_COUNT, - &autoresume_retry); - ASSERT_GE(autoresume_retry.max, 0); - ASSERT_OK(Put(Key(2), "val2", wo)); - s = Flush(); - // Since auto resume is successful, the bg error is cleaned, flush will - // be successful. - ASSERT_OK(s); - ASSERT_EQ("val2", Get(Key(2))); - Destroy(options); -} - -// Auto resume fromt the flush retryable IO error. Activate the FS before the -// first resume. Resume is successful -TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover1) { - // Activate the FS before the first resume - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(1), "val1")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - - ASSERT_EQ("val1", Get(Key(1))); - Reopen(options); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_OK(Put(Key(2), "val2")); - ASSERT_OK(Flush()); - ASSERT_EQ("val2", Get(Key(2))); - - Destroy(options); -} - -// Auto resume fromt the flush retryable IO error and set the retry limit count. -// Never activate the FS and auto resume should fail at the end -TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAutoRecover2) { - // Fail all the resume and let user to resume - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(1), "val1")); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"FLushWritRetryableeErrorAutoRecover2:0", - "RecoverFromRetryableBGIOError:BeforeStart"}, - {"RecoverFromRetryableBGIOError:LoopOut", - "FLushWritRetryableeErrorAutoRecover2:1"}}); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:0"); - TEST_SYNC_POINT("FLushWritRetryableeErrorAutoRecover2:1"); - fault_fs_->SetFilesystemActive(true); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - - ASSERT_EQ("val1", Get(Key(1))); - // Auto resume fails due to FS does not recover during resume. User call - // resume manually here. - s = dbfull()->Resume(); - ASSERT_EQ("val1", Get(Key(1))); - ASSERT_OK(s); - ASSERT_OK(Put(Key(2), "val2")); - ASSERT_OK(Flush()); - ASSERT_EQ("val2", Get(Key(2))); - - Destroy(options); -} - -// Auto resume fromt the flush retryable IO error and set the retry limit count. -// Fail the first resume and let the second resume be successful. -TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableErrorAutoRecover) { - // Fail the first resume and let the second resume be successful - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val")); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"RecoverFromRetryableBGIOError:BeforeStart", - "ManifestWriteRetryableErrorAutoRecover:0"}, - {"ManifestWriteRetryableErrorAutoRecover:1", - "RecoverFromRetryableBGIOError:BeforeWait1"}, - {"RecoverFromRetryableBGIOError:RecoverSuccess", - "ManifestWriteRetryableErrorAutoRecover:2"}}); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - TEST_SYNC_POINT("ManifestWriteRetryableErrorAutoRecover:0"); - fault_fs_->SetFilesystemActive(true); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - TEST_SYNC_POINT("ManifestWriteRetryableErrorAutoRecover:1"); - TEST_SYNC_POINT("ManifestWriteRetryableErrorAutoRecover:2"); - SyncPoint::GetInstance()->DisableProcessing(); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, ManifestWriteNoWALRetryableErrorAutoRecover) { - // Fail the first resume and let the second resume be successful - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - WriteOptions wo = WriteOptions(); - wo.disableWAL = true; - ASSERT_OK(Put(Key(0), "val", wo)); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val", wo)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"RecoverFromRetryableBGIOError:BeforeStart", - "ManifestWriteNoWALRetryableErrorAutoRecover:0"}, - {"ManifestWriteNoWALRetryableErrorAutoRecover:1", - "RecoverFromRetryableBGIOError:BeforeWait1"}, - {"RecoverFromRetryableBGIOError:RecoverSuccess", - "ManifestWriteNoWALRetryableErrorAutoRecover:2"}}); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - TEST_SYNC_POINT("ManifestWriteNoWALRetryableErrorAutoRecover:0"); - fault_fs_->SetFilesystemActive(true); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - TEST_SYNC_POINT("ManifestWriteNoWALRetryableErrorAutoRecover:1"); - TEST_SYNC_POINT("ManifestWriteNoWALRetryableErrorAutoRecover:2"); - SyncPoint::GetInstance()->DisableProcessing(); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, - CompactionManifestWriteRetryableErrorAutoRecover) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - std::string old_manifest; - std::string new_manifest; - std::atomic fail_manifest(false); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Put(Key(2), "val")); - ASSERT_OK(Flush()); - - listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - // Wait for flush of 2nd L0 file before starting compaction - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}, - // Wait for compaction to detect manifest write error - {"BackgroundCallCompaction:1", "CompactionManifestWriteErrorAR:0"}, - // Make compaction thread wait for error to be cleared - {"CompactionManifestWriteErrorAR:1", - "DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"}, - {"CompactionManifestWriteErrorAR:2", - "RecoverFromRetryableBGIOError:BeforeStart"}, - // Fail the first resume, before the wait in resume - {"RecoverFromRetryableBGIOError:BeforeResume0", - "CompactionManifestWriteErrorAR:3"}, - // Activate the FS before the second resume - {"CompactionManifestWriteErrorAR:4", - "RecoverFromRetryableBGIOError:BeforeResume1"}, - // Wait the auto resume be sucessful - {"RecoverFromRetryableBGIOError:RecoverSuccess", - "CompactionManifestWriteErrorAR:5"}}); - // trigger manifest write failure in compaction thread - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { fail_manifest.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - if (fail_manifest.load()) { - fault_fs_->SetFilesystemActive(false, error_msg); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - TEST_SYNC_POINT("CompactionManifestWriteErrorAR:0"); - TEST_SYNC_POINT("CompactionManifestWriteErrorAR:1"); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - TEST_SYNC_POINT("CompactionManifestWriteErrorAR:2"); - TEST_SYNC_POINT("CompactionManifestWriteErrorAR:3"); - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - TEST_SYNC_POINT("CompactionManifestWriteErrorAR:4"); - TEST_SYNC_POINT("CompactionManifestWriteErrorAR:5"); - SyncPoint::GetInstance()->DisableProcessing(); - - new_manifest = GetManifestNameFromLiveFiles(); - ASSERT_NE(new_manifest, old_manifest); - - Reopen(options); - ASSERT_EQ("val", Get(Key(0))); - ASSERT_EQ("val", Get(Key(1))); - ASSERT_EQ("val", Get(Key(2))); - Close(); -} - -TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableErrorAutoRecover) { - // In this test, in the first round of compaction, the FS is set to error. - // So the first compaction fails due to retryable IO error and it is mapped - // to soft error. Then, compaction is rescheduled, in the second round of - // compaction, the FS is set to active and compaction is successful, so - // the test will hit the CompactionJob::FinishCompactionOutputFile1 sync - // point. - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - Status s; - std::atomic fail_first(false); - std::atomic fail_second(true); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(0), "va;")); - ASSERT_OK(Put(Key(2), "va;")); - s = Flush(); - ASSERT_OK(s); - - listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}, - {"CompactionJob::FinishCompactionOutputFile1", - "CompactionWriteRetryableErrorAutoRecover0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Start", - [&](void*) { fault_fs_->SetFilesystemActive(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { fail_first.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::OpenCompactionOutputFile", [&](void*) { - if (fail_first.load() && fail_second.load()) { - fault_fs_->SetFilesystemActive(false, error_msg); - fail_second.store(false); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_OK(s); - TEST_SYNC_POINT("CompactionWriteRetryableErrorAutoRecover0"); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover1) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.writable_file_max_buffer_size = 32768; - options.listeners.emplace_back(listener); - options.paranoid_checks = true; - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - Random rnd(301); - - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - // For the first batch, write is successful, require sync - { - WriteBatch batch; - - for (auto i = 0; i < 100; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - // For the second batch, the first 2 file Append are successful, then the - // following Append fails due to file system retryable IOError. - { - WriteBatch batch; - int write_error = 0; - - for (auto i = 100; i < 200; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"WALWriteErrorDone", "RecoverFromRetryableBGIOError:BeforeStart"}, - {"RecoverFromRetryableBGIOError:BeforeResume0", "WALWriteError1:0"}, - {"WALWriteError1:1", "RecoverFromRetryableBGIOError:BeforeResume1"}, - {"RecoverFromRetryableBGIOError:RecoverSuccess", "WALWriteError1:2"}}); - - SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { - write_error++; - if (write_error > 2) { - fault_fs_->SetFilesystemActive(false, error_msg); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - WriteOptions wopts; - wopts.sync = true; - s = dbfull()->Write(wopts, &batch); - ASSERT_EQ(true, s.IsIOError()); - TEST_SYNC_POINT("WALWriteErrorDone"); - - TEST_SYNC_POINT("WALWriteError1:0"); - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - TEST_SYNC_POINT("WALWriteError1:1"); - TEST_SYNC_POINT("WALWriteError1:2"); - } - SyncPoint::GetInstance()->DisableProcessing(); - - // Data in corrupted WAL are not stored - for (auto i = 0; i < 199; ++i) { - if (i < 100) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - - // Resume and write a new batch, should be in the WAL - { - WriteBatch batch; - - for (auto i = 200; i < 300; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - Reopen(options); - for (auto i = 0; i < 300; ++i) { - if (i < 100 || i >= 200) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - Close(); -} - -TEST_F(DBErrorHandlingFSTest, WALWriteRetryableErrorAutoRecover2) { - // Fail the first recover and try second time. - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.writable_file_max_buffer_size = 32768; - options.listeners.emplace_back(listener); - options.paranoid_checks = true; - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - Random rnd(301); - - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - // For the first batch, write is successful, require sync - { - WriteBatch batch; - - for (auto i = 0; i < 100; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - // For the second batch, the first 2 file Append are successful, then the - // following Append fails due to file system retryable IOError. - { - WriteBatch batch; - int write_error = 0; - - for (auto i = 100; i < 200; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"RecoverFromRetryableBGIOError:BeforeWait0", "WALWriteError2:0"}, - {"WALWriteError2:1", "RecoverFromRetryableBGIOError:BeforeWait1"}, - {"RecoverFromRetryableBGIOError:RecoverSuccess", "WALWriteError2:2"}}); - - SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { - write_error++; - if (write_error > 2) { - fault_fs_->SetFilesystemActive(false, error_msg); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - WriteOptions wopts; - wopts.sync = true; - s = dbfull()->Write(wopts, &batch); - ASSERT_EQ(true, s.IsIOError()); - - TEST_SYNC_POINT("WALWriteError2:0"); - fault_fs_->SetFilesystemActive(true); - SyncPoint::GetInstance()->ClearAllCallBacks(); - TEST_SYNC_POINT("WALWriteError2:1"); - TEST_SYNC_POINT("WALWriteError2:2"); - } - SyncPoint::GetInstance()->DisableProcessing(); - - // Data in corrupted WAL are not stored - for (auto i = 0; i < 199; ++i) { - if (i < 100) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - - // Resume and write a new batch, should be in the WAL - { - WriteBatch batch; - - for (auto i = 200; i < 300; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - Reopen(options); - for (auto i = 0; i < 300; ++i) { - if (i < 100 || i >= 200) { - ASSERT_NE(Get(Key(i)), "NOT_FOUND"); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } - Close(); -} - -// Fail auto resume from a flush retryable error and verify that -// OnErrorRecoveryEnd listener callback is called -TEST_F(DBErrorHandlingFSTest, FLushWritRetryableErrorAbortRecovery) { - // Activate the FS before the first resume - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.max_bgerror_resume_count = 2; - options.bgerror_resume_retry_interval = 100000; // 0.1 second - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - ASSERT_OK(Put(Key(1), "val1")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeFinishBuildTable", - [&](void*) { fault_fs_->SetFilesystemActive(false, error_msg); }); - - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - ASSERT_EQ(listener->new_bg_error(), Status::Aborted()); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - - Destroy(options); -} - -TEST_F(DBErrorHandlingFSTest, FlushReadError) { - std::shared_ptr listener = - std::make_shared(); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeOutputValidation", [&](void*) { - IOStatus st = IOStatus::IOError(); - st.SetRetryable(true); - st.SetScope(IOStatus::IOErrorScope::kIOErrorScopeFile); - fault_fs_->SetFilesystemActive(false, st); - }); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeDeleteFile", - [&](void*) { fault_fs_->SetFilesystemActive(true, IOStatus::OK()); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_LE(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_LE(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - s = dbfull()->TEST_GetBGError(); - ASSERT_OK(s); - - Reopen(GetDefaultOptions()); - ASSERT_EQ("val", Get(Key(0))); -} - -TEST_F(DBErrorHandlingFSTest, AtomicFlushReadError) { - std::shared_ptr listener = - std::make_shared(); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(false); - options.atomic_flush = true; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(0, Key(0), "val")); - ASSERT_OK(Put(1, Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeOutputValidation", [&](void*) { - IOStatus st = IOStatus::IOError(); - st.SetRetryable(true); - st.SetScope(IOStatus::IOErrorScope::kIOErrorScopeFile); - fault_fs_->SetFilesystemActive(false, st); - }); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeDeleteFile", - [&](void*) { fault_fs_->SetFilesystemActive(true, IOStatus::OK()); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush({0, 1}); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kSoftError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - ASSERT_EQ(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT)); - ASSERT_LE(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_COUNT)); - ASSERT_LE(0, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT)); - s = dbfull()->TEST_GetBGError(); - ASSERT_OK(s); - - TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, - GetDefaultOptions()); - ASSERT_EQ("val", Get(Key(0))); -} - -TEST_F(DBErrorHandlingFSTest, AtomicFlushNoSpaceError) { - std::shared_ptr listener = - std::make_shared(); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.statistics = CreateDBStatistics(); - Status s; - - listener->EnableAutoRecovery(true); - options.atomic_flush = true; - CreateAndReopenWithCF({"pikachu"}, options); - - ASSERT_OK(Put(0, Key(0), "val")); - ASSERT_OK(Put(1, Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack("BuildTable:create_file", [&](void*) { - IOStatus st = IOStatus::NoSpace(); - fault_fs_->SetFilesystemActive(false, st); - }); - SyncPoint::GetInstance()->SetCallBack( - "BuildTable:BeforeDeleteFile", - [&](void*) { fault_fs_->SetFilesystemActive(true, IOStatus::OK()); }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush({0, 1}); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - ASSERT_EQ(listener->WaitForRecovery(5000000), true); - ASSERT_LE(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_ERROR_COUNT)); - ASSERT_LE(1, options.statistics->getAndResetTickerCount( - ERROR_HANDLER_BG_IO_ERROR_COUNT)); - s = dbfull()->TEST_GetBGError(); - ASSERT_OK(s); - - TryReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, - GetDefaultOptions()); - ASSERT_EQ("val", Get(Key(0))); -} - -TEST_F(DBErrorHandlingFSTest, CompactionReadRetryableErrorAutoRecover) { - // In this test, in the first round of compaction, the FS is set to error. - // So the first compaction fails due to retryable IO error and it is mapped - // to soft error. Then, compaction is rescheduled, in the second round of - // compaction, the FS is set to active and compaction is successful, so - // the test will hit the CompactionJob::FinishCompactionOutputFile1 sync - // point. - std::shared_ptr listener = - std::make_shared(); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Status s; - std::atomic fail_first(false); - std::atomic fail_second(true); - Random rnd(301); - DestroyAndReopen(options); - - IOStatus error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put(Key(i), rnd.RandomString(1024))); - } - s = Flush(); - ASSERT_OK(s); - - listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError)); - listener->EnableAutoRecovery(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}, - {"CompactionJob::FinishCompactionOutputFile1", - "CompactionWriteRetryableErrorAutoRecover0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Start", - [&](void*) { fault_fs_->SetFilesystemActive(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { fail_first.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():PausingManualCompaction:2", [&](void*) { - if (fail_first.load() && fail_second.load()) { - fault_fs_->SetFilesystemActive(false, error_msg); - fail_second.store(false); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_OK(s); - TEST_SYNC_POINT("CompactionWriteRetryableErrorAutoRecover0"); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - - Reopen(GetDefaultOptions()); -} - -class DBErrorHandlingFencingTest : public DBErrorHandlingFSTest, - public testing::WithParamInterface {}; - -TEST_P(DBErrorHandlingFencingTest, FLushWriteFenced) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.paranoid_checks = GetParam(); - Status s; - - listener->EnableAutoRecovery(true); - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "val")); - SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { - fault_fs_->SetFilesystemActive(false, IOStatus::IOFenced("IO fenced")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - ASSERT_TRUE(s.IsIOFenced()); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_TRUE(s.IsIOFenced()); - Destroy(options); -} - -TEST_P(DBErrorHandlingFencingTest, ManifestWriteFenced) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.listeners.emplace_back(listener); - options.paranoid_checks = GetParam(); - Status s; - std::string old_manifest; - std::string new_manifest; - - listener->EnableAutoRecovery(true); - DestroyAndReopen(options); - old_manifest = GetManifestNameFromLiveFiles(); - - ASSERT_OK(Put(Key(0), "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put(Key(1), "val")); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void*) { - fault_fs_->SetFilesystemActive(false, IOStatus::IOFenced("IO fenced")); - }); - SyncPoint::GetInstance()->EnableProcessing(); - s = Flush(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - ASSERT_TRUE(s.IsIOFenced()); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_TRUE(s.IsIOFenced()); - Close(); -} - -TEST_P(DBErrorHandlingFencingTest, CompactionWriteFenced) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.listeners.emplace_back(listener); - options.paranoid_checks = GetParam(); - Status s; - DestroyAndReopen(options); - - ASSERT_OK(Put(Key(0), "va;")); - ASSERT_OK(Put(Key(2), "va;")); - s = Flush(); - ASSERT_OK(s); - - listener->EnableAutoRecovery(true); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::FlushMemTable:FlushMemTableFinished", - "BackgroundCallCompaction:0"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void*) { - fault_fs_->SetFilesystemActive(false, IOStatus::IOFenced("IO fenced")); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(Put(Key(1), "val")); - s = Flush(); - ASSERT_OK(s); - - s = dbfull()->TEST_WaitForCompact(); - ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kFatalError); - ASSERT_TRUE(s.IsIOFenced()); - - fault_fs_->SetFilesystemActive(true); - s = dbfull()->Resume(); - ASSERT_TRUE(s.IsIOFenced()); - Destroy(options); -} - -TEST_P(DBErrorHandlingFencingTest, WALWriteFenced) { - std::shared_ptr listener( - new ErrorHandlerFSListener()); - Options options = GetDefaultOptions(); - options.env = fault_env_.get(); - options.create_if_missing = true; - options.writable_file_max_buffer_size = 32768; - options.listeners.emplace_back(listener); - options.paranoid_checks = GetParam(); - Status s; - Random rnd(301); - - listener->EnableAutoRecovery(true); - DestroyAndReopen(options); - - { - WriteBatch batch; - - for (auto i = 0; i < 100; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - ASSERT_OK(dbfull()->Write(wopts, &batch)); - }; - - { - WriteBatch batch; - int write_error = 0; - - for (auto i = 100; i < 199; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { - write_error++; - if (write_error > 2) { - fault_fs_->SetFilesystemActive(false, - IOStatus::IOFenced("IO fenced")); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - WriteOptions wopts; - wopts.sync = true; - s = dbfull()->Write(wopts, &batch); - ASSERT_TRUE(s.IsIOFenced()); - } - SyncPoint::GetInstance()->DisableProcessing(); - fault_fs_->SetFilesystemActive(true); - { - WriteBatch batch; - - for (auto i = 0; i < 100; ++i) { - ASSERT_OK(batch.Put(Key(i), rnd.RandomString(1024))); - } - - WriteOptions wopts; - wopts.sync = true; - s = dbfull()->Write(wopts, &batch); - ASSERT_TRUE(s.IsIOFenced()); - } - Close(); -} - -INSTANTIATE_TEST_CASE_P(DBErrorHandlingFSTest, DBErrorHandlingFencingTest, - ::testing::Bool()); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/external_sst_file_basic_test.cc b/db/external_sst_file_basic_test.cc deleted file mode 100644 index 7fc5bc260..000000000 --- a/db/external_sst_file_basic_test.cc +++ /dev/null @@ -1,1999 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "db/db_test_util.h" -#include "db/version_edit.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/sst_file_writer.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" -#include "utilities/fault_injection_env.h" - -namespace ROCKSDB_NAMESPACE { - -class ExternalSSTFileBasicTest - : public DBTestBase, - public ::testing::WithParamInterface> { - public: - ExternalSSTFileBasicTest() - : DBTestBase("external_sst_file_basic_test", /*env_do_fsync=*/true) { - sst_files_dir_ = dbname_ + "_sst_files/"; - fault_injection_test_env_.reset(new FaultInjectionTestEnv(env_)); - DestroyAndRecreateExternalSSTFilesDir(); - - // Check if the Env supports RandomRWFile - std::string file_path = sst_files_dir_ + "test_random_rw_file"; - std::unique_ptr wfile; - assert(env_->NewWritableFile(file_path, &wfile, EnvOptions()).ok()); - wfile.reset(); - std::unique_ptr rwfile; - Status s = env_->NewRandomRWFile(file_path, &rwfile, EnvOptions()); - if (s.IsNotSupported()) { - random_rwfile_supported_ = false; - } else { - EXPECT_OK(s); - random_rwfile_supported_ = true; - } - rwfile.reset(); - EXPECT_OK(env_->DeleteFile(file_path)); - } - - void DestroyAndRecreateExternalSSTFilesDir() { - ASSERT_OK(DestroyDir(env_, sst_files_dir_)); - ASSERT_OK(env_->CreateDir(sst_files_dir_)); - } - - Status DeprecatedAddFile(const std::vector& files, - bool move_files = false, - bool skip_snapshot_check = false) { - IngestExternalFileOptions opts; - opts.move_files = move_files; - opts.snapshot_consistency = !skip_snapshot_check; - opts.allow_global_seqno = false; - opts.allow_blocking_flush = false; - return db_->IngestExternalFile(files, opts); - } - - Status AddFileWithFileChecksum( - const std::vector& files, - const std::vector& files_checksums, - const std::vector& files_checksum_func_names, - bool verify_file_checksum = true, bool move_files = false, - bool skip_snapshot_check = false, bool write_global_seqno = true) { - IngestExternalFileOptions opts; - opts.move_files = move_files; - opts.snapshot_consistency = !skip_snapshot_check; - opts.allow_global_seqno = false; - opts.allow_blocking_flush = false; - opts.write_global_seqno = write_global_seqno; - opts.verify_file_checksum = verify_file_checksum; - - IngestExternalFileArg arg; - arg.column_family = db_->DefaultColumnFamily(); - arg.external_files = files; - arg.options = opts; - arg.files_checksums = files_checksums; - arg.files_checksum_func_names = files_checksum_func_names; - return db_->IngestExternalFiles({arg}); - } - - Status GenerateAndAddExternalFile( - const Options options, std::vector keys, - const std::vector& value_types, - std::vector> range_deletions, int file_id, - bool write_global_seqno, bool verify_checksums_before_ingest, - std::map* true_data) { - assert(value_types.size() == 1 || keys.size() == value_types.size()); - std::string file_path = sst_files_dir_ + std::to_string(file_id); - SstFileWriter sst_file_writer(EnvOptions(), options); - - Status s = sst_file_writer.Open(file_path); - if (!s.ok()) { - return s; - } - for (size_t i = 0; i < range_deletions.size(); i++) { - // Account for the effect of range deletions on true_data before - // all point operators, even though sst_file_writer.DeleteRange - // must be called before other sst_file_writer methods. This is - // because point writes take precedence over range deletions - // in the same ingested sst. - std::string start_key = Key(range_deletions[i].first); - std::string end_key = Key(range_deletions[i].second); - s = sst_file_writer.DeleteRange(start_key, end_key); - if (!s.ok()) { - sst_file_writer.Finish(); - return s; - } - auto start_key_it = true_data->find(start_key); - if (start_key_it == true_data->end()) { - start_key_it = true_data->upper_bound(start_key); - } - auto end_key_it = true_data->find(end_key); - if (end_key_it == true_data->end()) { - end_key_it = true_data->upper_bound(end_key); - } - true_data->erase(start_key_it, end_key_it); - } - for (size_t i = 0; i < keys.size(); i++) { - std::string key = Key(keys[i]); - std::string value = Key(keys[i]) + std::to_string(file_id); - ValueType value_type = - (value_types.size() == 1 ? value_types[0] : value_types[i]); - switch (value_type) { - case ValueType::kTypeValue: - s = sst_file_writer.Put(key, value); - (*true_data)[key] = value; - break; - case ValueType::kTypeMerge: - s = sst_file_writer.Merge(key, value); - // we only use TestPutOperator in this test - (*true_data)[key] = value; - break; - case ValueType::kTypeDeletion: - s = sst_file_writer.Delete(key); - true_data->erase(key); - break; - default: - return Status::InvalidArgument("Value type is not supported"); - } - if (!s.ok()) { - sst_file_writer.Finish(); - return s; - } - } - s = sst_file_writer.Finish(); - - if (s.ok()) { - IngestExternalFileOptions ifo; - ifo.allow_global_seqno = true; - ifo.write_global_seqno = write_global_seqno; - ifo.verify_checksums_before_ingest = verify_checksums_before_ingest; - s = db_->IngestExternalFile({file_path}, ifo); - } - return s; - } - - Status GenerateAndAddExternalFile( - const Options options, std::vector keys, - const std::vector& value_types, int file_id, - bool write_global_seqno, bool verify_checksums_before_ingest, - std::map* true_data) { - return GenerateAndAddExternalFile( - options, keys, value_types, {}, file_id, write_global_seqno, - verify_checksums_before_ingest, true_data); - } - - Status GenerateAndAddExternalFile( - const Options options, std::vector keys, const ValueType value_type, - int file_id, bool write_global_seqno, bool verify_checksums_before_ingest, - std::map* true_data) { - return GenerateAndAddExternalFile( - options, keys, std::vector(1, value_type), file_id, - write_global_seqno, verify_checksums_before_ingest, true_data); - } - - ~ExternalSSTFileBasicTest() override { - DestroyDir(env_, sst_files_dir_).PermitUncheckedError(); - } - - protected: - std::string sst_files_dir_; - std::unique_ptr fault_injection_test_env_; - bool random_rwfile_supported_; -}; - -TEST_F(ExternalSSTFileBasicTest, Basic) { - Options options = CurrentOptions(); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // Current file size should be 0 after sst_file_writer init and before open a - // file. - ASSERT_EQ(sst_file_writer.FileSize(), 0); - - // file1.sst (0 => 99) - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 0; k < 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - Status s = sst_file_writer.Finish(&file1_info); - ASSERT_OK(s) << s.ToString(); - - // Current file size should be non-zero after success write. - ASSERT_GT(sst_file_writer.FileSize(), 0); - - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(0)); - ASSERT_EQ(file1_info.largest_key, Key(99)); - ASSERT_EQ(file1_info.num_range_del_entries, 0); - ASSERT_EQ(file1_info.smallest_range_del_key, ""); - ASSERT_EQ(file1_info.largest_range_del_key, ""); - ASSERT_EQ(file1_info.file_checksum, kUnknownFileChecksum); - ASSERT_EQ(file1_info.file_checksum_func_name, kUnknownFileChecksumFuncName); - // sst_file_writer already finished, cannot add this value - s = sst_file_writer.Put(Key(100), "bad_val"); - ASSERT_NOK(s) << s.ToString(); - s = sst_file_writer.DeleteRange(Key(100), Key(200)); - ASSERT_NOK(s) << s.ToString(); - - DestroyAndReopen(options); - // Add file using file path - s = DeprecatedAddFile({file1}); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - for (int k = 0; k < 100; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } - - DestroyAndRecreateExternalSSTFilesDir(); -} - -class ChecksumVerifyHelper { - private: - Options options_; - - public: - ChecksumVerifyHelper(Options& options) : options_(options) {} - ~ChecksumVerifyHelper() {} - - Status GetSingleFileChecksumAndFuncName( - const std::string& file_path, std::string* file_checksum, - std::string* file_checksum_func_name) { - Status s; - EnvOptions soptions; - std::unique_ptr file_reader; - s = options_.env->NewSequentialFile(file_path, &file_reader, soptions); - if (!s.ok()) { - return s; - } - std::unique_ptr scratch(new char[2048]); - Slice result; - FileChecksumGenFactory* file_checksum_gen_factory = - options_.file_checksum_gen_factory.get(); - if (file_checksum_gen_factory == nullptr) { - *file_checksum = kUnknownFileChecksum; - *file_checksum_func_name = kUnknownFileChecksumFuncName; - return Status::OK(); - } else { - FileChecksumGenContext gen_context; - std::unique_ptr file_checksum_gen = - file_checksum_gen_factory->CreateFileChecksumGenerator(gen_context); - *file_checksum_func_name = file_checksum_gen->Name(); - s = file_reader->Read(2048, &result, scratch.get()); - if (!s.ok()) { - return s; - } - while (result.size() != 0) { - file_checksum_gen->Update(scratch.get(), result.size()); - s = file_reader->Read(2048, &result, scratch.get()); - if (!s.ok()) { - return s; - } - } - file_checksum_gen->Finalize(); - *file_checksum = file_checksum_gen->GetChecksum(); - } - return Status::OK(); - } -}; - -TEST_F(ExternalSSTFileBasicTest, BasicWithFileChecksumCrc32c) { - Options options = CurrentOptions(); - options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - ChecksumVerifyHelper checksum_helper(options); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // Current file size should be 0 after sst_file_writer init and before open a - // file. - ASSERT_EQ(sst_file_writer.FileSize(), 0); - - // file1.sst (0 => 99) - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 0; k < 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - Status s = sst_file_writer.Finish(&file1_info); - ASSERT_OK(s) << s.ToString(); - std::string file_checksum, file_checksum_func_name; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file1, &file_checksum, &file_checksum_func_name)); - - // Current file size should be non-zero after success write. - ASSERT_GT(sst_file_writer.FileSize(), 0); - - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(0)); - ASSERT_EQ(file1_info.largest_key, Key(99)); - ASSERT_EQ(file1_info.num_range_del_entries, 0); - ASSERT_EQ(file1_info.smallest_range_del_key, ""); - ASSERT_EQ(file1_info.largest_range_del_key, ""); - ASSERT_EQ(file1_info.file_checksum, file_checksum); - ASSERT_EQ(file1_info.file_checksum_func_name, file_checksum_func_name); - // sst_file_writer already finished, cannot add this value - s = sst_file_writer.Put(Key(100), "bad_val"); - ASSERT_NOK(s) << s.ToString(); - s = sst_file_writer.DeleteRange(Key(100), Key(200)); - ASSERT_NOK(s) << s.ToString(); - - DestroyAndReopen(options); - // Add file using file path - s = DeprecatedAddFile({file1}); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - for (int k = 0; k < 100; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } - - DestroyAndRecreateExternalSSTFilesDir(); -} - -TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) { - Options old_options = CurrentOptions(); - Options options = CurrentOptions(); - options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - const ImmutableCFOptions ioptions(options); - ChecksumVerifyHelper checksum_helper(options); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file01.sst (1000 => 1099) - std::string file1 = sst_files_dir_ + "file01.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 1000; k < 1100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - Status s = sst_file_writer.Finish(&file1_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(1000)); - ASSERT_EQ(file1_info.largest_key, Key(1099)); - std::string file_checksum1, file_checksum_func_name1; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file1, &file_checksum1, &file_checksum_func_name1)); - ASSERT_EQ(file1_info.file_checksum, file_checksum1); - ASSERT_EQ(file1_info.file_checksum_func_name, file_checksum_func_name1); - - // file02.sst (1100 => 1299) - std::string file2 = sst_files_dir_ + "file02.sst"; - ASSERT_OK(sst_file_writer.Open(file2)); - for (int k = 1100; k < 1300; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file2_info; - s = sst_file_writer.Finish(&file2_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file2_info.file_path, file2); - ASSERT_EQ(file2_info.num_entries, 200); - ASSERT_EQ(file2_info.smallest_key, Key(1100)); - ASSERT_EQ(file2_info.largest_key, Key(1299)); - std::string file_checksum2, file_checksum_func_name2; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file2, &file_checksum2, &file_checksum_func_name2)); - ASSERT_EQ(file2_info.file_checksum, file_checksum2); - ASSERT_EQ(file2_info.file_checksum_func_name, file_checksum_func_name2); - - // file03.sst (1300 => 1499) - std::string file3 = sst_files_dir_ + "file03.sst"; - ASSERT_OK(sst_file_writer.Open(file3)); - for (int k = 1300; k < 1500; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file3_info; - s = sst_file_writer.Finish(&file3_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file3_info.file_path, file3); - ASSERT_EQ(file3_info.num_entries, 200); - ASSERT_EQ(file3_info.smallest_key, Key(1300)); - ASSERT_EQ(file3_info.largest_key, Key(1499)); - std::string file_checksum3, file_checksum_func_name3; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file3, &file_checksum3, &file_checksum_func_name3)); - ASSERT_EQ(file3_info.file_checksum, file_checksum3); - ASSERT_EQ(file3_info.file_checksum_func_name, file_checksum_func_name3); - - // file04.sst (1500 => 1799) - std::string file4 = sst_files_dir_ + "file04.sst"; - ASSERT_OK(sst_file_writer.Open(file4)); - for (int k = 1500; k < 1800; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file4_info; - s = sst_file_writer.Finish(&file4_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file4_info.file_path, file4); - ASSERT_EQ(file4_info.num_entries, 300); - ASSERT_EQ(file4_info.smallest_key, Key(1500)); - ASSERT_EQ(file4_info.largest_key, Key(1799)); - std::string file_checksum4, file_checksum_func_name4; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file4, &file_checksum4, &file_checksum_func_name4)); - ASSERT_EQ(file4_info.file_checksum, file_checksum4); - ASSERT_EQ(file4_info.file_checksum_func_name, file_checksum_func_name4); - - // file05.sst (1800 => 1899) - std::string file5 = sst_files_dir_ + "file05.sst"; - ASSERT_OK(sst_file_writer.Open(file5)); - for (int k = 1800; k < 2000; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file5_info; - s = sst_file_writer.Finish(&file5_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file5_info.file_path, file5); - ASSERT_EQ(file5_info.num_entries, 200); - ASSERT_EQ(file5_info.smallest_key, Key(1800)); - ASSERT_EQ(file5_info.largest_key, Key(1999)); - std::string file_checksum5, file_checksum_func_name5; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file5, &file_checksum5, &file_checksum_func_name5)); - ASSERT_EQ(file5_info.file_checksum, file_checksum5); - ASSERT_EQ(file5_info.file_checksum_func_name, file_checksum_func_name5); - - // file06.sst (2000 => 2199) - std::string file6 = sst_files_dir_ + "file06.sst"; - ASSERT_OK(sst_file_writer.Open(file6)); - for (int k = 2000; k < 2200; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file6_info; - s = sst_file_writer.Finish(&file6_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file6_info.file_path, file6); - ASSERT_EQ(file6_info.num_entries, 200); - ASSERT_EQ(file6_info.smallest_key, Key(2000)); - ASSERT_EQ(file6_info.largest_key, Key(2199)); - std::string file_checksum6, file_checksum_func_name6; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - file6, &file_checksum6, &file_checksum_func_name6)); - ASSERT_EQ(file6_info.file_checksum, file_checksum6); - ASSERT_EQ(file6_info.file_checksum_func_name, file_checksum_func_name6); - - s = AddFileWithFileChecksum({file1}, {file_checksum1, "xyz"}, - {file_checksum1}, true, false, false, false); - // does not care the checksum input since db does not enable file checksum - ASSERT_OK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file1)); - std::vector live_files; - dbfull()->GetLiveFilesMetaData(&live_files); - std::set set1; - for (auto f : live_files) { - set1.insert(f.name); - ASSERT_EQ(f.file_checksum, kUnknownFileChecksum); - ASSERT_EQ(f.file_checksum_func_name, kUnknownFileChecksumFuncName); - } - - // check the temperature of the file being ingested - ColumnFamilyMetaData metadata; - db_->GetColumnFamilyMetaData(&metadata); - ASSERT_EQ(1, metadata.file_count); - ASSERT_EQ(Temperature::kUnknown, metadata.levels[6].files[0].temperature); - auto size = GetSstSizeHelper(Temperature::kUnknown); - ASSERT_GT(size, 0); - size = GetSstSizeHelper(Temperature::kWarm); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kHot); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kCold); - ASSERT_EQ(size, 0); - - // Reopen Db with checksum enabled - Reopen(options); - // Enable verify_file_checksum option - // The checksum vector does not match, fail the ingestion - s = AddFileWithFileChecksum({file2}, {file_checksum2, "xyz"}, - {file_checksum_func_name2}, true, false, false, - false); - ASSERT_NOK(s) << s.ToString(); - - // Enable verify_file_checksum option - // The checksum name does not match, fail the ingestion - s = AddFileWithFileChecksum({file2}, {file_checksum2}, {"xyz"}, true, false, - false, false); - ASSERT_NOK(s) << s.ToString(); - - // Enable verify_file_checksum option - // The checksum itself does not match, fail the ingestion - s = AddFileWithFileChecksum({file2}, {"xyz"}, {file_checksum_func_name2}, - true, false, false, false); - ASSERT_NOK(s) << s.ToString(); - - // Enable verify_file_checksum option - // All matches, ingestion is successful - s = AddFileWithFileChecksum({file2}, {file_checksum2}, - {file_checksum_func_name2}, true, false, false, - false); - ASSERT_OK(s) << s.ToString(); - std::vector live_files1; - dbfull()->GetLiveFilesMetaData(&live_files1); - for (auto f : live_files1) { - if (set1.find(f.name) == set1.end()) { - ASSERT_EQ(f.file_checksum, file_checksum2); - ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name2); - set1.insert(f.name); - } - } - ASSERT_OK(env_->FileExists(file2)); - - // Enable verify_file_checksum option - // No checksum information is provided, generate it when ingesting - std::vector checksum, checksum_func; - s = AddFileWithFileChecksum({file3}, checksum, checksum_func, true, false, - false, false); - ASSERT_OK(s) << s.ToString(); - std::vector live_files2; - dbfull()->GetLiveFilesMetaData(&live_files2); - for (auto f : live_files2) { - if (set1.find(f.name) == set1.end()) { - ASSERT_EQ(f.file_checksum, file_checksum3); - ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name3); - set1.insert(f.name); - } - } - ASSERT_OK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file3)); - - // Does not enable verify_file_checksum options - // The checksum name does not match, fail the ingestion - s = AddFileWithFileChecksum({file4}, {file_checksum4}, {"xyz"}, false, false, - false, false); - ASSERT_NOK(s) << s.ToString(); - - // Does not enable verify_file_checksum options - // Checksum function name matches, store the checksum being ingested. - s = AddFileWithFileChecksum({file4}, {"asd"}, {file_checksum_func_name4}, - false, false, false, false); - ASSERT_OK(s) << s.ToString(); - std::vector live_files3; - dbfull()->GetLiveFilesMetaData(&live_files3); - for (auto f : live_files3) { - if (set1.find(f.name) == set1.end()) { - ASSERT_FALSE(f.file_checksum == file_checksum4); - ASSERT_EQ(f.file_checksum, "asd"); - ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name4); - set1.insert(f.name); - } - } - ASSERT_OK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file4)); - - // enable verify_file_checksum options, DB enable checksum, and enable - // write_global_seq. So the checksum stored is different from the one - // ingested due to the sequence number changes. - s = AddFileWithFileChecksum({file5}, {file_checksum5}, - {file_checksum_func_name5}, true, false, false, - true); - ASSERT_OK(s) << s.ToString(); - std::vector live_files4; - dbfull()->GetLiveFilesMetaData(&live_files4); - for (auto f : live_files4) { - if (set1.find(f.name) == set1.end()) { - std::string cur_checksum5, cur_checksum_func_name5; - ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName( - dbname_ + f.name, &cur_checksum5, &cur_checksum_func_name5)); - ASSERT_EQ(f.file_checksum, cur_checksum5); - ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name5); - set1.insert(f.name); - } - } - ASSERT_OK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file5)); - - // Does not enable verify_file_checksum options and also the ingested file - // checksum information is empty. DB will generate and store the checksum - // in Manifest. - std::vector files_c6, files_name6; - s = AddFileWithFileChecksum({file6}, files_c6, files_name6, false, false, - false, false); - ASSERT_OK(s) << s.ToString(); - std::vector live_files6; - dbfull()->GetLiveFilesMetaData(&live_files6); - for (auto f : live_files6) { - if (set1.find(f.name) == set1.end()) { - ASSERT_EQ(f.file_checksum, file_checksum6); - ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name6); - set1.insert(f.name); - } - } - ASSERT_OK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file6)); - db_->GetColumnFamilyMetaData(&metadata); - size = GetSstSizeHelper(Temperature::kUnknown); - ASSERT_GT(size, 0); - size = GetSstSizeHelper(Temperature::kWarm); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kHot); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kCold); - ASSERT_EQ(size, 0); -} - -TEST_F(ExternalSSTFileBasicTest, NoCopy) { - Options options = CurrentOptions(); - const ImmutableCFOptions ioptions(options); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file1.sst (0 => 99) - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 0; k < 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - Status s = sst_file_writer.Finish(&file1_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(0)); - ASSERT_EQ(file1_info.largest_key, Key(99)); - - // file2.sst (100 => 299) - std::string file2 = sst_files_dir_ + "file2.sst"; - ASSERT_OK(sst_file_writer.Open(file2)); - for (int k = 100; k < 300; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file2_info; - s = sst_file_writer.Finish(&file2_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file2_info.file_path, file2); - ASSERT_EQ(file2_info.num_entries, 200); - ASSERT_EQ(file2_info.smallest_key, Key(100)); - ASSERT_EQ(file2_info.largest_key, Key(299)); - - // file3.sst (110 => 124) .. overlap with file2.sst - std::string file3 = sst_files_dir_ + "file3.sst"; - ASSERT_OK(sst_file_writer.Open(file3)); - for (int k = 110; k < 125; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file3_info; - s = sst_file_writer.Finish(&file3_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file3_info.file_path, file3); - ASSERT_EQ(file3_info.num_entries, 15); - ASSERT_EQ(file3_info.smallest_key, Key(110)); - ASSERT_EQ(file3_info.largest_key, Key(124)); - - s = DeprecatedAddFile({file1}, true /* move file */); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(Status::NotFound(), env_->FileExists(file1)); - - s = DeprecatedAddFile({file2}, false /* copy file */); - ASSERT_OK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file2)); - - // This file has overlapping values with the existing data - s = DeprecatedAddFile({file3}, true /* move file */); - ASSERT_NOK(s) << s.ToString(); - ASSERT_OK(env_->FileExists(file3)); - - for (int k = 0; k < 300; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } -} - -TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - do { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - std::map true_data; - - int file_id = 1; - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 4, 6}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {11, 15, 19}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {120, 130}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 130}, ValueType::kTypeValue, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); - - // Write some keys through normal write path - for (int i = 0; i < 50; i++) { - ASSERT_OK(Put(Key(i), "memtable")); - true_data[Key(i)] = "memtable"; - } - SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {60, 61, 62}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {40, 41, 42}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {20, 30, 40}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); - - const Snapshot* snapshot = db_->GetSnapshot(); - - // We will need a seqno for the file regardless if the file overwrite - // keys in the DB or not because we have a snapshot - ASSERT_OK(GenerateAndAddExternalFile( - options, {1000, 1002}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {2000, 3002}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 20, 40, 100, 150}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); - - db_->ReleaseSnapshot(snapshot); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {5000, 5001}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // No snapshot anymore, no need to assign a seqno - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); - } while (ChangeOptionsForFileIngestionTest()); -} - -TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - do { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.merge_operator.reset(new TestPutOperator()); - DestroyAndReopen(options); - std::map true_data; - - int file_id = 1; - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 4, 6}, ValueType::kTypeMerge, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {11, 15, 19}, ValueType::kTypeDeletion, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {120, 130}, ValueType::kTypeMerge, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 130}, ValueType::kTypeDeletion, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {120}, {ValueType::kTypeValue}, {{120, 135}}, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {}, {}, {{110, 120}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - // The range deletion ends on a key, but it doesn't actually delete - // this key because the largest key in the range is exclusive. Still, - // it counts as an overlap so a new seqno will be assigned. - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {}, {}, {{100, 109}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); - - // Write some keys through normal write path - for (int i = 0; i < 50; i++) { - ASSERT_OK(Put(Key(i), "memtable")); - true_data[Key(i)] = "memtable"; - } - SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {60, 61, 62}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {40, 41, 42}, ValueType::kTypeMerge, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {20, 30, 40}, ValueType::kTypeDeletion, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); - - const Snapshot* snapshot = db_->GetSnapshot(); - - // We will need a seqno for the file regardless if the file overwrite - // keys in the DB or not because we have a snapshot - ASSERT_OK(GenerateAndAddExternalFile( - options, {1000, 1002}, ValueType::kTypeMerge, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {2000, 3002}, ValueType::kTypeMerge, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 20, 40, 100, 150}, ValueType::kTypeMerge, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); - - db_->ReleaseSnapshot(snapshot); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {5000, 5001}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data)); - // No snapshot anymore, no need to assign a seqno - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); - } while (ChangeOptionsForFileIngestionTest()); -} - -TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - do { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.merge_operator.reset(new TestPutOperator()); - DestroyAndReopen(options); - std::map true_data; - - int file_id = 1; - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 2, 3, 4, 5, 6}, - {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, - ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {10, 11, 12, 13}, - {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, - ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 4, 6}, - {ValueType::kTypeDeletion, ValueType::kTypeValue, - ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {11, 15, 19}, - {ValueType::kTypeDeletion, ValueType::kTypeMerge, - ValueType::kTypeValue}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {120, 130}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 130}, {ValueType::kTypeMerge, ValueType::kTypeDeletion}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {150, 151, 152}, - {ValueType::kTypeValue, ValueType::kTypeMerge, - ValueType::kTypeDeletion}, - {{150, 160}, {180, 190}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {150, 151, 152}, - {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue}, - {{200, 250}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {300, 301, 302}, - {ValueType::kTypeValue, ValueType::kTypeMerge, - ValueType::kTypeDeletion}, - {{1, 2}, {152, 154}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); - - // Write some keys through normal write path - for (int i = 0; i < 50; i++) { - ASSERT_OK(Put(Key(i), "memtable")); - true_data[Key(i)] = "memtable"; - } - SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {60, 61, 62}, - {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File doesn't overwrite any keys, no seqno needed - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {40, 41, 42}, - {ValueType::kTypeValue, ValueType::kTypeDeletion, - ValueType::kTypeDeletion}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {20, 30, 40}, - {ValueType::kTypeDeletion, ValueType::kTypeDeletion, - ValueType::kTypeDeletion}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // File overwrites some keys, a seqno will be assigned - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); - - const Snapshot* snapshot = db_->GetSnapshot(); - - // We will need a seqno for the file regardless if the file overwrite - // keys in the DB or not because we have a snapshot - ASSERT_OK(GenerateAndAddExternalFile( - options, {1000, 1002}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {2000, 3002}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 20, 40, 100, 150}, - {ValueType::kTypeDeletion, ValueType::kTypeDeletion, - ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // A global seqno will be assigned anyway because of the snapshot - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); - - db_->ReleaseSnapshot(snapshot); - - ASSERT_OK(GenerateAndAddExternalFile( - options, {5000, 5001}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - // No snapshot anymore, no need to assign a seqno - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); - } while (ChangeOptionsForFileIngestionTest()); -} - -TEST_F(ExternalSSTFileBasicTest, FadviseTrigger) { - Options options = CurrentOptions(); - const int kNumKeys = 10000; - - size_t total_fadvised_bytes = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "SstFileWriter::Rep::InvalidatePageCache", [&](void* arg) { - size_t fadvise_size = *(reinterpret_cast(arg)); - total_fadvised_bytes += fadvise_size; - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - std::unique_ptr sst_file_writer; - - std::string sst_file_path = sst_files_dir_ + "file_fadvise_disable.sst"; - sst_file_writer.reset( - new SstFileWriter(EnvOptions(), options, nullptr, false)); - ASSERT_OK(sst_file_writer->Open(sst_file_path)); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(sst_file_writer->Put(Key(i), Key(i))); - } - ASSERT_OK(sst_file_writer->Finish()); - // fadvise disabled - ASSERT_EQ(total_fadvised_bytes, 0); - - sst_file_path = sst_files_dir_ + "file_fadvise_enable.sst"; - sst_file_writer.reset( - new SstFileWriter(EnvOptions(), options, nullptr, true)); - ASSERT_OK(sst_file_writer->Open(sst_file_path)); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(sst_file_writer->Put(Key(i), Key(i))); - } - ASSERT_OK(sst_file_writer->Finish()); - // fadvise enabled - ASSERT_EQ(total_fadvised_bytes, sst_file_writer->FileSize()); - ASSERT_GT(total_fadvised_bytes, 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(ExternalSSTFileBasicTest, SyncFailure) { - Options options; - options.create_if_missing = true; - options.env = fault_injection_test_env_.get(); - - std::vector> test_cases = { - {"ExternalSstFileIngestionJob::BeforeSyncIngestedFile", - "ExternalSstFileIngestionJob::AfterSyncIngestedFile"}, - {"ExternalSstFileIngestionJob::BeforeSyncDir", - "ExternalSstFileIngestionJob::AfterSyncDir"}, - {"ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno", - "ExternalSstFileIngestionJob::AfterSyncGlobalSeqno"}}; - - for (size_t i = 0; i < test_cases.size(); i++) { - bool no_sync = false; - SyncPoint::GetInstance()->SetCallBack(test_cases[i].first, [&](void*) { - fault_injection_test_env_->SetFilesystemActive(false); - }); - SyncPoint::GetInstance()->SetCallBack(test_cases[i].second, [&](void*) { - fault_injection_test_env_->SetFilesystemActive(true); - }); - if (i == 0) { - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Prepare:Reopen", [&](void* s) { - Status* status = static_cast(s); - if (status->IsNotSupported()) { - no_sync = true; - } - }); - } - if (i == 2) { - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::NewRandomRWFile", [&](void* s) { - Status* status = static_cast(s); - if (status->IsNotSupported()) { - no_sync = true; - } - }); - } - SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - if (i == 2) { - ASSERT_OK(Put("foo", "v1")); - } - - Options sst_file_writer_options; - sst_file_writer_options.env = fault_injection_test_env_.get(); - std::unique_ptr sst_file_writer( - new SstFileWriter(EnvOptions(), sst_file_writer_options)); - std::string file_name = - sst_files_dir_ + "sync_failure_test_" + std::to_string(i) + ".sst"; - ASSERT_OK(sst_file_writer->Open(file_name)); - ASSERT_OK(sst_file_writer->Put("bar", "v2")); - ASSERT_OK(sst_file_writer->Finish()); - - IngestExternalFileOptions ingest_opt; - ASSERT_FALSE(ingest_opt.write_global_seqno); // new default - if (i == 0) { - ingest_opt.move_files = true; - } - const Snapshot* snapshot = db_->GetSnapshot(); - if (i == 2) { - ingest_opt.write_global_seqno = true; - } - Status s = db_->IngestExternalFile({file_name}, ingest_opt); - if (no_sync) { - ASSERT_OK(s); - } else { - ASSERT_NOK(s); - } - db_->ReleaseSnapshot(snapshot); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Destroy(options); - } -} - -TEST_F(ExternalSSTFileBasicTest, ReopenNotSupported) { - Options options; - options.create_if_missing = true; - options.env = env_; - - SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Prepare:Reopen", [&](void* arg) { - Status* s = static_cast(arg); - *s = Status::NotSupported(); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndReopen(options); - - Options sst_file_writer_options; - sst_file_writer_options.env = env_; - std::unique_ptr sst_file_writer( - new SstFileWriter(EnvOptions(), sst_file_writer_options)); - std::string file_name = - sst_files_dir_ + "reopen_not_supported_test_" + ".sst"; - ASSERT_OK(sst_file_writer->Open(file_name)); - ASSERT_OK(sst_file_writer->Put("bar", "v2")); - ASSERT_OK(sst_file_writer->Finish()); - - IngestExternalFileOptions ingest_opt; - ingest_opt.move_files = true; - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->IngestExternalFile({file_name}, ingest_opt)); - db_->ReleaseSnapshot(snapshot); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Destroy(options); -} - -TEST_F(ExternalSSTFileBasicTest, VerifyChecksumReadahead) { - Options options; - options.create_if_missing = true; - SpecialEnv senv(env_); - options.env = &senv; - DestroyAndReopen(options); - - Options sst_file_writer_options; - sst_file_writer_options.env = env_; - std::unique_ptr sst_file_writer( - new SstFileWriter(EnvOptions(), sst_file_writer_options)); - std::string file_name = sst_files_dir_ + "verify_checksum_readahead_test.sst"; - ASSERT_OK(sst_file_writer->Open(file_name)); - Random rnd(301); - std::string value = rnd.RandomString(4000); - for (int i = 0; i < 5000; i++) { - ASSERT_OK(sst_file_writer->Put(DBTestBase::Key(i), value)); - } - ASSERT_OK(sst_file_writer->Finish()); - - // Ingest it once without verifying checksums to see the baseline - // preads. - IngestExternalFileOptions ingest_opt; - ingest_opt.move_files = false; - senv.count_random_reads_ = true; - senv.random_read_bytes_counter_ = 0; - ASSERT_OK(db_->IngestExternalFile({file_name}, ingest_opt)); - - auto base_num_reads = senv.random_read_counter_.Read(); - // Make sure the counter is enabled. - ASSERT_GT(base_num_reads, 0); - - // Ingest again and observe the reads made for for readahead. - ingest_opt.move_files = false; - ingest_opt.verify_checksums_before_ingest = true; - ingest_opt.verify_checksums_readahead_size = size_t{2 * 1024 * 1024}; - - senv.count_random_reads_ = true; - senv.random_read_bytes_counter_ = 0; - ASSERT_OK(db_->IngestExternalFile({file_name}, ingest_opt)); - - // Make sure the counter is enabled. - ASSERT_GT(senv.random_read_counter_.Read() - base_num_reads, 0); - - // The SST file is about 20MB. Readahead size is 2MB. - // Give a conservative 15 reads for metadata blocks, the number - // of random reads should be within 20 MB / 2MB + 15 = 25. - ASSERT_LE(senv.random_read_counter_.Read() - base_num_reads, 40); - - Destroy(options); -} - -TEST_F(ExternalSSTFileBasicTest, IngestRangeDeletionTombstoneWithGlobalSeqno) { - for (int i = 5; i < 25; i++) { - ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), Key(i), - Key(i) + "_val")); - } - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - Reopen(options); - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file.sst (delete 0 => 30) - std::string file = sst_files_dir_ + "file.sst"; - ASSERT_OK(sst_file_writer.Open(file)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(0), Key(30))); - ExternalSstFileInfo file_info; - ASSERT_OK(sst_file_writer.Finish(&file_info)); - ASSERT_EQ(file_info.file_path, file); - ASSERT_EQ(file_info.num_entries, 0); - ASSERT_EQ(file_info.smallest_key, ""); - ASSERT_EQ(file_info.largest_key, ""); - ASSERT_EQ(file_info.num_range_del_entries, 1); - ASSERT_EQ(file_info.smallest_range_del_key, Key(0)); - ASSERT_EQ(file_info.largest_range_del_key, Key(30)); - - IngestExternalFileOptions ifo; - ifo.move_files = true; - ifo.snapshot_consistency = true; - ifo.allow_global_seqno = true; - ifo.write_global_seqno = true; - ifo.verify_checksums_before_ingest = false; - ASSERT_OK(db_->IngestExternalFile({file}, ifo)); - - for (int i = 5; i < 25; i++) { - std::string res; - ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &res).IsNotFound()); - } -} - -TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) { - int kNumLevels = 7; - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.num_levels = kNumLevels; - Reopen(options); - - std::map true_data; - int file_id = 1; - // prevent range deletions from being dropped due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - - // range del [0, 50) in L6 file, [50, 100) in L0 file, [100, 150) in memtable - for (int i = 0; i < 3; i++) { - if (i != 0) { - db_->Flush(FlushOptions()); - if (i == 1) { - MoveFilesToLevel(kNumLevels - 1); - } - } - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - Key(50 * i), Key(50 * (i + 1)))); - } - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2)); - ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 1)); - - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - // overlaps with L0 file but not memtable, so flush is skipped and file is - // ingested into L0 - SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); - ASSERT_OK(GenerateAndAddExternalFile( - options, {60, 90}, {ValueType::kTypeValue, ValueType::kTypeValue}, - {{65, 70}, {70, 85}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2)); - ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); - - // overlaps with L6 file but not memtable or L0 file, so flush is skipped and - // file is ingested into L5 - ASSERT_OK(GenerateAndAddExternalFile( - options, {10, 40}, {ValueType::kTypeValue, ValueType::kTypeValue}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); - ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); - - // overlaps with L5 file but not memtable or L0 file, so flush is skipped and - // file is ingested into L4 - ASSERT_OK(GenerateAndAddExternalFile( - options, {}, {}, {{5, 15}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); - ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 2)); - ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); - - // ingested file overlaps with memtable, so flush is triggered before the file - // is ingested such that the ingested data is considered newest. So L0 file - // count increases by two. - ASSERT_OK(GenerateAndAddExternalFile( - options, {100, 140}, {ValueType::kTypeValue, ValueType::kTypeValue}, - file_id++, write_global_seqno, verify_checksums_before_ingest, - &true_data)); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); - ASSERT_EQ(4, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); - ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); - - // snapshot unneeded now that all range deletions are persisted - db_->ReleaseSnapshot(snapshot); - - // overlaps with nothing, so places at bottom level and skips incrementing - // seqnum. - ASSERT_OK(GenerateAndAddExternalFile( - options, {151, 175}, {ValueType::kTypeValue, ValueType::kTypeValue}, - {{160, 200}}, file_id++, write_global_seqno, - verify_checksums_before_ingest, &true_data)); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); - ASSERT_EQ(4, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); - ASSERT_EQ(2, NumTableFilesAtLevel(options.num_levels - 1)); -} - -TEST_F(ExternalSSTFileBasicTest, AdjacentRangeDeletionTombstones) { - Options options = CurrentOptions(); - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file8.sst (delete 300 => 400) - std::string file8 = sst_files_dir_ + "file8.sst"; - ASSERT_OK(sst_file_writer.Open(file8)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(300), Key(400))); - ExternalSstFileInfo file8_info; - Status s = sst_file_writer.Finish(&file8_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file8_info.file_path, file8); - ASSERT_EQ(file8_info.num_entries, 0); - ASSERT_EQ(file8_info.smallest_key, ""); - ASSERT_EQ(file8_info.largest_key, ""); - ASSERT_EQ(file8_info.num_range_del_entries, 1); - ASSERT_EQ(file8_info.smallest_range_del_key, Key(300)); - ASSERT_EQ(file8_info.largest_range_del_key, Key(400)); - - // file9.sst (delete 400 => 500) - std::string file9 = sst_files_dir_ + "file9.sst"; - ASSERT_OK(sst_file_writer.Open(file9)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(400), Key(500))); - ExternalSstFileInfo file9_info; - s = sst_file_writer.Finish(&file9_info); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(file9_info.file_path, file9); - ASSERT_EQ(file9_info.num_entries, 0); - ASSERT_EQ(file9_info.smallest_key, ""); - ASSERT_EQ(file9_info.largest_key, ""); - ASSERT_EQ(file9_info.num_range_del_entries, 1); - ASSERT_EQ(file9_info.smallest_range_del_key, Key(400)); - ASSERT_EQ(file9_info.largest_range_del_key, Key(500)); - - // Range deletion tombstones are exclusive on their end key, so these SSTs - // should not be considered as overlapping. - s = DeprecatedAddFile({file8, file9}); - ASSERT_OK(s) << s.ToString(); - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - DestroyAndRecreateExternalSSTFilesDir(); -} - -TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) { - bool change_checksum_called = false; - const auto& change_checksum = [&](void* arg) { - if (!change_checksum_called) { - char* buf = reinterpret_cast(arg); - assert(nullptr != buf); - buf[0] ^= 0x1; - change_checksum_called = true; - } - }; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::WriteMaybeCompressedBlock:TamperWithChecksum", - change_checksum); - SyncPoint::GetInstance()->EnableProcessing(); - int file_id = 0; - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - do { - Options options = CurrentOptions(); - DestroyAndReopen(options); - std::map true_data; - Status s = GenerateAndAddExternalFile( - options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, - write_global_seqno, verify_checksums_before_ingest, &true_data); - if (verify_checksums_before_ingest) { - ASSERT_NOK(s); - } else { - ASSERT_OK(s); - } - change_checksum_called = false; - } while (ChangeOptionsForFileIngestionTest()); -} - -TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) { - if (!random_rwfile_supported_) { - ROCKSDB_GTEST_SKIP("Test requires NewRandomRWFile support"); - return; - } - SyncPoint::GetInstance()->DisableProcessing(); - int file_id = 0; - EnvOptions env_options; - do { - Options options = CurrentOptions(); - std::string file_path = sst_files_dir_ + std::to_string(file_id++); - SstFileWriter sst_file_writer(env_options, options); - Status s = sst_file_writer.Open(file_path); - ASSERT_OK(s); - for (int i = 0; i != 100; ++i) { - std::string key = Key(i); - std::string value = Key(i) + std::to_string(0); - ASSERT_OK(sst_file_writer.Put(key, value)); - } - ASSERT_OK(sst_file_writer.Finish()); - { - // Get file size - uint64_t file_size = 0; - ASSERT_OK(env_->GetFileSize(file_path, &file_size)); - ASSERT_GT(file_size, 8); - std::unique_ptr rwfile; - ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions())); - // Manually corrupt the file - // We deterministically corrupt the first byte because we currently - // cannot choose a random offset. The reason for this limitation is that - // we do not checksum property block at present. - const uint64_t offset = 0; - char scratch[8] = {0}; - Slice buf; - ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch)); - scratch[0] ^= 0xff; // flip one bit - ASSERT_OK(rwfile->Write(offset, buf)); - } - // Ingest file. - IngestExternalFileOptions ifo; - ifo.write_global_seqno = std::get<0>(GetParam()); - ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); - s = db_->IngestExternalFile({file_path}, ifo); - if (ifo.verify_checksums_before_ingest) { - ASSERT_NOK(s); - } else { - ASSERT_OK(s); - } - } while (ChangeOptionsForFileIngestionTest()); -} - -TEST_P(ExternalSSTFileBasicTest, IngestExternalFileWithCorruptedPropsBlock) { - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - if (!verify_checksums_before_ingest) { - ROCKSDB_GTEST_BYPASS("Bypassing test when !verify_checksums_before_ingest"); - return; - } - if (!random_rwfile_supported_) { - ROCKSDB_GTEST_SKIP("Test requires NewRandomRWFile support"); - return; - } - uint64_t props_block_offset = 0; - size_t props_block_size = 0; - const auto& get_props_block_offset = [&](void* arg) { - props_block_offset = *reinterpret_cast(arg); - }; - const auto& get_props_block_size = [&](void* arg) { - props_block_size = *reinterpret_cast(arg); - }; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockOffset", - get_props_block_offset); - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockSize", - get_props_block_size); - SyncPoint::GetInstance()->EnableProcessing(); - int file_id = 0; - Random64 rand(time(nullptr)); - do { - std::string file_path = sst_files_dir_ + std::to_string(file_id++); - Options options = CurrentOptions(); - SstFileWriter sst_file_writer(EnvOptions(), options); - Status s = sst_file_writer.Open(file_path); - ASSERT_OK(s); - for (int i = 0; i != 100; ++i) { - std::string key = Key(i); - std::string value = Key(i) + std::to_string(0); - ASSERT_OK(sst_file_writer.Put(key, value)); - } - ASSERT_OK(sst_file_writer.Finish()); - - { - std::unique_ptr rwfile; - ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions())); - // Manually corrupt the file - ASSERT_GT(props_block_size, 8); - uint64_t offset = - props_block_offset + rand.Next() % (props_block_size - 8); - char scratch[8] = {0}; - Slice buf; - ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch)); - scratch[0] ^= 0xff; // flip one bit - ASSERT_OK(rwfile->Write(offset, buf)); - } - - // Ingest file. - IngestExternalFileOptions ifo; - ifo.write_global_seqno = std::get<0>(GetParam()); - ifo.verify_checksums_before_ingest = true; - s = db_->IngestExternalFile({file_path}, ifo); - ASSERT_NOK(s); - } while (ChangeOptionsForFileIngestionTest()); -} - -TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) { - Options options = CurrentOptions(); - - std::vector files; - { - SstFileWriter sst_file_writer(EnvOptions(), options); - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - ASSERT_OK(sst_file_writer.Put("a", "z")); - ASSERT_OK(sst_file_writer.Put("i", "m")); - ExternalSstFileInfo file1_info; - ASSERT_OK(sst_file_writer.Finish(&file1_info)); - files.push_back(std::move(file1)); - } - { - SstFileWriter sst_file_writer(EnvOptions(), options); - std::string file2 = sst_files_dir_ + "file2.sst"; - ASSERT_OK(sst_file_writer.Open(file2)); - ASSERT_OK(sst_file_writer.Put("i", "k")); - ExternalSstFileInfo file2_info; - ASSERT_OK(sst_file_writer.Finish(&file2_info)); - files.push_back(std::move(file2)); - } - - IngestExternalFileOptions ifo; - ASSERT_OK(db_->IngestExternalFile(files, ifo)); - ASSERT_EQ(Get("a"), "z"); - ASSERT_EQ(Get("i"), "k"); - - int total_keys = 0; - Iterator* iter = db_->NewIterator(ReadOptions()); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - total_keys++; - } - delete iter; - ASSERT_EQ(total_keys, 2); - - ASSERT_EQ(2, NumTableFilesAtLevel(0)); -} - -TEST_F(ExternalSSTFileBasicTest, IngestFileAfterDBPut) { - // Repro https://github.com/facebook/rocksdb/issues/6245. - // Flush three files to L0. Ingest one more file to trigger L0->L1 compaction - // via trivial move. The bug happened when L1 files were incorrectly sorted - // resulting in an old value for "k" returned by `Get()`. - Options options = CurrentOptions(); - - ASSERT_OK(Put("k", "a")); - Flush(); - ASSERT_OK(Put("k", "a")); - Flush(); - ASSERT_OK(Put("k", "a")); - Flush(); - SstFileWriter sst_file_writer(EnvOptions(), options); - - // Current file size should be 0 after sst_file_writer init and before open a - // file. - ASSERT_EQ(sst_file_writer.FileSize(), 0); - - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - ASSERT_OK(sst_file_writer.Put("k", "b")); - - ExternalSstFileInfo file1_info; - Status s = sst_file_writer.Finish(&file1_info); - ASSERT_OK(s) << s.ToString(); - - // Current file size should be non-zero after success write. - ASSERT_GT(sst_file_writer.FileSize(), 0); - - IngestExternalFileOptions ifo; - s = db_->IngestExternalFile({file1}, ifo); - ASSERT_OK(s); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(Get("k"), "b"); -} - -TEST_F(ExternalSSTFileBasicTest, IngestWithTemperature) { - Options options = CurrentOptions(); - const ImmutableCFOptions ioptions(options); - options.bottommost_temperature = Temperature::kWarm; - SstFileWriter sst_file_writer(EnvOptions(), options); - options.level0_file_num_compaction_trigger = 2; - Reopen(options); - - auto size = GetSstSizeHelper(Temperature::kUnknown); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kWarm); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kHot); - ASSERT_EQ(size, 0); - - // create file01.sst (1000 => 1099) and ingest it - std::string file1 = sst_files_dir_ + "file01.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 1000; k < 1100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - Status s = sst_file_writer.Finish(&file1_info); - ASSERT_OK(s); - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(1000)); - ASSERT_EQ(file1_info.largest_key, Key(1099)); - - std::vector files; - std::vector files_checksums; - std::vector files_checksum_func_names; - Temperature file_temperature = Temperature::kWarm; - - files.push_back(file1); - IngestExternalFileOptions in_opts; - in_opts.move_files = false; - in_opts.snapshot_consistency = true; - in_opts.allow_global_seqno = false; - in_opts.allow_blocking_flush = false; - in_opts.write_global_seqno = true; - in_opts.verify_file_checksum = false; - IngestExternalFileArg arg; - arg.column_family = db_->DefaultColumnFamily(); - arg.external_files = files; - arg.options = in_opts; - arg.files_checksums = files_checksums; - arg.files_checksum_func_names = files_checksum_func_names; - arg.file_temperature = file_temperature; - s = db_->IngestExternalFiles({arg}); - ASSERT_OK(s); - - // check the temperature of the file being ingested - ColumnFamilyMetaData metadata; - db_->GetColumnFamilyMetaData(&metadata); - ASSERT_EQ(1, metadata.file_count); - ASSERT_EQ(Temperature::kWarm, metadata.levels[6].files[0].temperature); - size = GetSstSizeHelper(Temperature::kUnknown); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kWarm); - ASSERT_GT(size, 1); - - // non-bottommost file still has unknown temperature - ASSERT_OK(Put("foo", "bar")); - ASSERT_OK(Put("bar", "bar")); - ASSERT_OK(Flush()); - db_->GetColumnFamilyMetaData(&metadata); - ASSERT_EQ(2, metadata.file_count); - ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature); - size = GetSstSizeHelper(Temperature::kUnknown); - ASSERT_GT(size, 0); - size = GetSstSizeHelper(Temperature::kWarm); - ASSERT_GT(size, 0); - - // reopen and check the information is persisted - Reopen(options); - db_->GetColumnFamilyMetaData(&metadata); - ASSERT_EQ(2, metadata.file_count); - ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature); - ASSERT_EQ(Temperature::kWarm, metadata.levels[6].files[0].temperature); - size = GetSstSizeHelper(Temperature::kUnknown); - ASSERT_GT(size, 0); - size = GetSstSizeHelper(Temperature::kWarm); - ASSERT_GT(size, 0); - - // check other non-exist temperatures - size = GetSstSizeHelper(Temperature::kHot); - ASSERT_EQ(size, 0); - size = GetSstSizeHelper(Temperature::kCold); - ASSERT_EQ(size, 0); - std::string prop; - ASSERT_TRUE(dbfull()->GetProperty( - DB::Properties::kLiveSstFilesSizeAtTemperature + std::to_string(22), - &prop)); - ASSERT_EQ(std::atoi(prop.c_str()), 0); -} - -TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevel) { - Options options = GetDefaultOptions(); - - std::string file_path = sst_files_dir_ + std::to_string(1); - SstFileWriter sfw(EnvOptions(), options); - - ASSERT_OK(sfw.Open(file_path)); - ASSERT_OK(sfw.Put("b", "dontcare")); - ASSERT_OK(sfw.Finish()); - - // Test universal compaction + ingest with snapshot consistency - options.create_if_missing = true; - options.compaction_style = CompactionStyle::kCompactionStyleUniversal; - DestroyAndReopen(options); - { - const Snapshot* snapshot = db_->GetSnapshot(); - ManagedSnapshot snapshot_guard(db_, snapshot); - IngestExternalFileOptions ifo; - ifo.fail_if_not_bottommost_level = true; - ifo.snapshot_consistency = true; - const Status s = db_->IngestExternalFile({file_path}, ifo); - ASSERT_TRUE(s.IsTryAgain()); - } - - // Test level compaction - options.compaction_style = CompactionStyle::kCompactionStyleLevel; - options.num_levels = 2; - DestroyAndReopen(options); - ASSERT_OK(db_->Put(WriteOptions(), "a", "dontcare")); - ASSERT_OK(db_->Put(WriteOptions(), "c", "dontcare")); - ASSERT_OK(db_->Flush(FlushOptions())); - - ASSERT_OK(db_->Put(WriteOptions(), "b", "dontcare")); - ASSERT_OK(db_->Put(WriteOptions(), "d", "dontcare")); - ASSERT_OK(db_->Flush(FlushOptions())); - - { - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - IngestExternalFileOptions ifo; - ifo.fail_if_not_bottommost_level = true; - const Status s = db_->IngestExternalFile({file_path}, ifo); - ASSERT_TRUE(s.IsTryAgain()); - } -} - -TEST_F(ExternalSSTFileBasicTest, VerifyChecksum) { - const std::string kPutVal = "put_val"; - const std::string kIngestedVal = "ingested_val"; - - ASSERT_OK(Put("k", kPutVal, WriteOptions())); - ASSERT_OK(Flush()); - - std::string external_file = sst_files_dir_ + "/file_to_ingest.sst"; - { - SstFileWriter sst_file_writer{EnvOptions(), CurrentOptions()}; - - ASSERT_OK(sst_file_writer.Open(external_file)); - ASSERT_OK(sst_file_writer.Put("k", kIngestedVal)); - ASSERT_OK(sst_file_writer.Finish()); - } - - ASSERT_OK(db_->IngestExternalFile(db_->DefaultColumnFamily(), {external_file}, - IngestExternalFileOptions())); - - ASSERT_OK(db_->VerifyChecksum()); -} - -TEST_F(ExternalSSTFileBasicTest, VerifySstUniqueId) { - const std::string kPutVal = "put_val"; - const std::string kIngestedVal = "ingested_val"; - - ASSERT_OK(Put("k", kPutVal, WriteOptions())); - ASSERT_OK(Flush()); - - std::string external_file = sst_files_dir_ + "/file_to_ingest.sst"; - { - SstFileWriter sst_file_writer{EnvOptions(), CurrentOptions()}; - - ASSERT_OK(sst_file_writer.Open(external_file)); - ASSERT_OK(sst_file_writer.Put("k", kIngestedVal)); - ASSERT_OK(sst_file_writer.Finish()); - } - - ASSERT_OK(db_->IngestExternalFile(db_->DefaultColumnFamily(), {external_file}, - IngestExternalFileOptions())); - - // Test ingest file without session_id and db_id (for example generated by an - // older version of sst_writer) - SyncPoint::GetInstance()->SetCallBack( - "PropertyBlockBuilder::AddTableProperty:Start", [&](void* props_vs) { - auto props = static_cast(props_vs); - // update table property session_id to a different one - props->db_session_id = ""; - props->db_id = ""; - }); - std::atomic_int skipped = 0, passed = 0; - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Open::SkippedVerifyUniqueId", - [&](void* /*arg*/) { skipped++; }); - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Open::PassedVerifyUniqueId", - [&](void* /*arg*/) { passed++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - auto options = CurrentOptions(); - ASSERT_TRUE(options.verify_sst_unique_id_in_manifest); - Reopen(options); - ASSERT_EQ(skipped, 0); - ASSERT_EQ(passed, 2); // one flushed + one ingested - - external_file = sst_files_dir_ + "/file_to_ingest2.sst"; - { - SstFileWriter sst_file_writer{EnvOptions(), CurrentOptions()}; - - ASSERT_OK(sst_file_writer.Open(external_file)); - ASSERT_OK(sst_file_writer.Put("k", kIngestedVal)); - ASSERT_OK(sst_file_writer.Finish()); - } - - ASSERT_OK(db_->IngestExternalFile(db_->DefaultColumnFamily(), {external_file}, - IngestExternalFileOptions())); - - // Two table file opens skipping verification: - // * ExternalSstFileIngestionJob::GetIngestedFileInfo - // * TableCache::GetTableReader - ASSERT_EQ(skipped, 2); - ASSERT_EQ(passed, 2); - - // Check same after re-open (except no GetIngestedFileInfo) - skipped = 0; - passed = 0; - Reopen(options); - ASSERT_EQ(skipped, 1); - ASSERT_EQ(passed, 2); -} - -TEST_F(ExternalSSTFileBasicTest, StableSnapshotWhileLoggingToManifest) { - const std::string kPutVal = "put_val"; - const std::string kIngestedVal = "ingested_val"; - - ASSERT_OK(Put("k", kPutVal, WriteOptions())); - ASSERT_OK(Flush()); - - std::string external_file = sst_files_dir_ + "/file_to_ingest.sst"; - { - SstFileWriter sst_file_writer{EnvOptions(), CurrentOptions()}; - ASSERT_OK(sst_file_writer.Open(external_file)); - ASSERT_OK(sst_file_writer.Put("k", kIngestedVal)); - ASSERT_OK(sst_file_writer.Finish()); - } - - const Snapshot* snapshot = nullptr; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void* /* arg */) { - // prevent background compaction job to call this callback - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - snapshot = db_->GetSnapshot(); - ReadOptions read_opts; - read_opts.snapshot = snapshot; - std::string value; - ASSERT_OK(db_->Get(read_opts, "k", &value)); - ASSERT_EQ(kPutVal, value); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(db_->IngestExternalFile(db_->DefaultColumnFamily(), {external_file}, - IngestExternalFileOptions())); - auto ingested_file_seqno = db_->GetLatestSequenceNumber(); - ASSERT_NE(nullptr, snapshot); - // snapshot is taken before SST ingestion is done - ASSERT_EQ(ingested_file_seqno, snapshot->GetSequenceNumber() + 1); - - ReadOptions read_opts; - read_opts.snapshot = snapshot; - std::string value; - ASSERT_OK(db_->Get(read_opts, "k", &value)); - ASSERT_EQ(kPutVal, value); - db_->ReleaseSnapshot(snapshot); - - // After reopen, sequence number should be up current such that - // ingested value is read - Reopen(CurrentOptions()); - ASSERT_OK(db_->Get(ReadOptions(), "k", &value)); - ASSERT_EQ(kIngestedVal, value); - - // New write should get higher seqno compared to ingested file - ASSERT_OK(Put("k", kPutVal, WriteOptions())); - ASSERT_EQ(db_->GetLatestSequenceNumber(), ingested_file_seqno + 1); -} - -INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest, - testing::Values(std::make_tuple(true, true), - std::make_tuple(true, false), - std::make_tuple(false, true), - std::make_tuple(false, false))); - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/external_sst_file_test.cc b/db/external_sst_file_test.cc deleted file mode 100644 index 63627c27e..000000000 --- a/db/external_sst_file_test.cc +++ /dev/null @@ -1,2860 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include - -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "file/filename.h" -#include "options/options_helper.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/sst_file_reader.h" -#include "rocksdb/sst_file_writer.h" -#include "test_util/testutil.h" -#include "util/random.h" -#include "util/thread_guard.h" -#include "utilities/fault_injection_env.h" - -namespace ROCKSDB_NAMESPACE { - -// A test environment that can be configured to fail the Link operation. -class ExternalSSTTestFS : public FileSystemWrapper { - public: - ExternalSSTTestFS(const std::shared_ptr& t, bool fail_link) - : FileSystemWrapper(t), fail_link_(fail_link) {} - static const char* kClassName() { return "ExternalSSTTestFS"; } - const char* Name() const override { return kClassName(); } - - IOStatus LinkFile(const std::string& s, const std::string& t, - const IOOptions& options, IODebugContext* dbg) override { - if (fail_link_) { - return IOStatus::NotSupported("Link failed"); - } - return target()->LinkFile(s, t, options, dbg); - } - - void set_fail_link(bool fail_link) { fail_link_ = fail_link; } - - private: - bool fail_link_; -}; - -class ExternalSSTFileTestBase : public DBTestBase { - public: - ExternalSSTFileTestBase() - : DBTestBase("external_sst_file_test", /*env_do_fsync=*/true) { - sst_files_dir_ = dbname_ + "/sst_files/"; - DestroyAndRecreateExternalSSTFilesDir(); - } - - void DestroyAndRecreateExternalSSTFilesDir() { - ASSERT_OK(DestroyDir(env_, sst_files_dir_)); - ASSERT_OK(env_->CreateDir(sst_files_dir_)); - } - - ~ExternalSSTFileTestBase() override { - DestroyDir(env_, sst_files_dir_).PermitUncheckedError(); - } - - protected: - std::string sst_files_dir_; -}; - -class ExternSSTFileLinkFailFallbackTest - : public ExternalSSTFileTestBase, - public ::testing::WithParamInterface> { - public: - ExternSSTFileLinkFailFallbackTest() { - fs_ = std::make_shared(env_->GetFileSystem(), true); - test_env_.reset(new CompositeEnvWrapper(env_, fs_)); - options_ = CurrentOptions(); - options_.disable_auto_compactions = true; - options_.env = test_env_.get(); - } - - void TearDown() override { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, options_)); - } - - protected: - Options options_; - std::shared_ptr fs_; - std::unique_ptr test_env_; -}; - -class ExternalSSTFileTest - : public ExternalSSTFileTestBase, - public ::testing::WithParamInterface> { - public: - ExternalSSTFileTest() {} - - Status GenerateOneExternalFile( - const Options& options, ColumnFamilyHandle* cfh, - std::vector>& data, int file_id, - bool sort_data, std::string* external_file_path, - std::map* true_data) { - // Generate a file id if not provided - if (-1 == file_id) { - file_id = (++last_file_id_); - } - // Sort data if asked to do so - if (sort_data) { - std::sort(data.begin(), data.end(), - [&](const std::pair& e1, - const std::pair& e2) { - return options.comparator->Compare(e1.first, e2.first) < 0; - }); - auto uniq_iter = std::unique( - data.begin(), data.end(), - [&](const std::pair& e1, - const std::pair& e2) { - return options.comparator->Compare(e1.first, e2.first) == 0; - }); - data.resize(uniq_iter - data.begin()); - } - std::string file_path = sst_files_dir_ + std::to_string(file_id); - SstFileWriter sst_file_writer(EnvOptions(), options, cfh); - Status s = sst_file_writer.Open(file_path); - if (!s.ok()) { - return s; - } - for (const auto& entry : data) { - s = sst_file_writer.Put(entry.first, entry.second); - if (!s.ok()) { - sst_file_writer.Finish().PermitUncheckedError(); - return s; - } - } - s = sst_file_writer.Finish(); - if (s.ok() && external_file_path != nullptr) { - *external_file_path = file_path; - } - if (s.ok() && nullptr != true_data) { - for (const auto& entry : data) { - true_data->insert({entry.first, entry.second}); - } - } - return s; - } - - Status GenerateAndAddExternalFile( - const Options options, - std::vector> data, int file_id = -1, - bool allow_global_seqno = false, bool write_global_seqno = false, - bool verify_checksums_before_ingest = true, bool ingest_behind = false, - bool sort_data = false, - std::map* true_data = nullptr, - ColumnFamilyHandle* cfh = nullptr) { - // Generate a file id if not provided - if (file_id == -1) { - file_id = last_file_id_ + 1; - last_file_id_++; - } - - // Sort data if asked to do so - if (sort_data) { - std::sort(data.begin(), data.end(), - [&](const std::pair& e1, - const std::pair& e2) { - return options.comparator->Compare(e1.first, e2.first) < 0; - }); - auto uniq_iter = std::unique( - data.begin(), data.end(), - [&](const std::pair& e1, - const std::pair& e2) { - return options.comparator->Compare(e1.first, e2.first) == 0; - }); - data.resize(uniq_iter - data.begin()); - } - std::string file_path = sst_files_dir_ + std::to_string(file_id); - SstFileWriter sst_file_writer(EnvOptions(), options, cfh); - - Status s = sst_file_writer.Open(file_path); - if (!s.ok()) { - return s; - } - for (auto& entry : data) { - s = sst_file_writer.Put(entry.first, entry.second); - if (!s.ok()) { - sst_file_writer.Finish().PermitUncheckedError(); - return s; - } - } - s = sst_file_writer.Finish(); - - if (s.ok()) { - IngestExternalFileOptions ifo; - ifo.allow_global_seqno = allow_global_seqno; - ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false; - ifo.verify_checksums_before_ingest = verify_checksums_before_ingest; - ifo.ingest_behind = ingest_behind; - if (cfh) { - s = db_->IngestExternalFile(cfh, {file_path}, ifo); - } else { - s = db_->IngestExternalFile({file_path}, ifo); - } - } - - if (s.ok() && true_data) { - for (auto& entry : data) { - (*true_data)[entry.first] = entry.second; - } - } - - return s; - } - - Status GenerateAndAddExternalFiles( - const Options& options, - const std::vector& column_families, - const std::vector& ifos, - std::vector>>& data, - int file_id, bool sort_data, - std::vector>& true_data) { - if (-1 == file_id) { - file_id = (++last_file_id_); - } - // Generate external SST files, one for each column family - size_t num_cfs = column_families.size(); - assert(ifos.size() == num_cfs); - assert(data.size() == num_cfs); - std::vector args(num_cfs); - for (size_t i = 0; i != num_cfs; ++i) { - std::string external_file_path; - Status s = GenerateOneExternalFile( - options, column_families[i], data[i], file_id, sort_data, - &external_file_path, - true_data.size() == num_cfs ? &true_data[i] : nullptr); - if (!s.ok()) { - return s; - } - ++file_id; - - args[i].column_family = column_families[i]; - args[i].external_files.push_back(external_file_path); - args[i].options = ifos[i]; - } - return db_->IngestExternalFiles(args); - } - - Status GenerateAndAddExternalFile( - const Options options, std::vector> data, - int file_id = -1, bool allow_global_seqno = false, - bool write_global_seqno = false, - bool verify_checksums_before_ingest = true, bool ingest_behind = false, - bool sort_data = false, - std::map* true_data = nullptr, - ColumnFamilyHandle* cfh = nullptr) { - std::vector> file_data; - for (auto& entry : data) { - file_data.emplace_back(Key(entry.first), entry.second); - } - return GenerateAndAddExternalFile(options, file_data, file_id, - allow_global_seqno, write_global_seqno, - verify_checksums_before_ingest, - ingest_behind, sort_data, true_data, cfh); - } - - Status GenerateAndAddExternalFile( - const Options options, std::vector keys, int file_id = -1, - bool allow_global_seqno = false, bool write_global_seqno = false, - bool verify_checksums_before_ingest = true, bool ingest_behind = false, - bool sort_data = false, - std::map* true_data = nullptr, - ColumnFamilyHandle* cfh = nullptr) { - std::vector> file_data; - for (auto& k : keys) { - file_data.emplace_back(Key(k), Key(k) + std::to_string(file_id)); - } - return GenerateAndAddExternalFile(options, file_data, file_id, - allow_global_seqno, write_global_seqno, - verify_checksums_before_ingest, - ingest_behind, sort_data, true_data, cfh); - } - - Status DeprecatedAddFile(const std::vector& files, - bool move_files = false, - bool skip_snapshot_check = false, - bool skip_write_global_seqno = false) { - IngestExternalFileOptions opts; - opts.move_files = move_files; - opts.snapshot_consistency = !skip_snapshot_check; - opts.allow_global_seqno = false; - opts.allow_blocking_flush = false; - opts.write_global_seqno = !skip_write_global_seqno; - return db_->IngestExternalFile(files, opts); - } - - protected: - int last_file_id_ = 0; -}; - -TEST_F(ExternalSSTFileTest, Basic) { - do { - Options options = CurrentOptions(); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // Current file size should be 0 after sst_file_writer init and before open - // a file. - ASSERT_EQ(sst_file_writer.FileSize(), 0); - - // file1.sst (0 => 99) - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 0; k < 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - ASSERT_OK(sst_file_writer.Finish(&file1_info)); - - // Current file size should be non-zero after success write. - ASSERT_GT(sst_file_writer.FileSize(), 0); - - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(0)); - ASSERT_EQ(file1_info.largest_key, Key(99)); - ASSERT_EQ(file1_info.num_range_del_entries, 0); - ASSERT_EQ(file1_info.smallest_range_del_key, ""); - ASSERT_EQ(file1_info.largest_range_del_key, ""); - // sst_file_writer already finished, cannot add this value - ASSERT_NOK(sst_file_writer.Put(Key(100), "bad_val")); - - // file2.sst (100 => 199) - std::string file2 = sst_files_dir_ + "file2.sst"; - ASSERT_OK(sst_file_writer.Open(file2)); - for (int k = 100; k < 200; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - // Cannot add this key because it's not after last added key - ASSERT_NOK(sst_file_writer.Put(Key(99), "bad_val")); - ExternalSstFileInfo file2_info; - ASSERT_OK(sst_file_writer.Finish(&file2_info)); - ASSERT_EQ(file2_info.file_path, file2); - ASSERT_EQ(file2_info.num_entries, 100); - ASSERT_EQ(file2_info.smallest_key, Key(100)); - ASSERT_EQ(file2_info.largest_key, Key(199)); - - // file3.sst (195 => 299) - // This file values overlap with file2 values - std::string file3 = sst_files_dir_ + "file3.sst"; - ASSERT_OK(sst_file_writer.Open(file3)); - for (int k = 195; k < 300; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file3_info; - ASSERT_OK(sst_file_writer.Finish(&file3_info)); - - // Current file size should be non-zero after success finish. - ASSERT_GT(sst_file_writer.FileSize(), 0); - ASSERT_EQ(file3_info.file_path, file3); - ASSERT_EQ(file3_info.num_entries, 105); - ASSERT_EQ(file3_info.smallest_key, Key(195)); - ASSERT_EQ(file3_info.largest_key, Key(299)); - - // file4.sst (30 => 39) - // This file values overlap with file1 values - std::string file4 = sst_files_dir_ + "file4.sst"; - ASSERT_OK(sst_file_writer.Open(file4)); - for (int k = 30; k < 40; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file4_info; - ASSERT_OK(sst_file_writer.Finish(&file4_info)); - ASSERT_EQ(file4_info.file_path, file4); - ASSERT_EQ(file4_info.num_entries, 10); - ASSERT_EQ(file4_info.smallest_key, Key(30)); - ASSERT_EQ(file4_info.largest_key, Key(39)); - - // file5.sst (400 => 499) - std::string file5 = sst_files_dir_ + "file5.sst"; - ASSERT_OK(sst_file_writer.Open(file5)); - for (int k = 400; k < 500; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file5_info; - ASSERT_OK(sst_file_writer.Finish(&file5_info)); - ASSERT_EQ(file5_info.file_path, file5); - ASSERT_EQ(file5_info.num_entries, 100); - ASSERT_EQ(file5_info.smallest_key, Key(400)); - ASSERT_EQ(file5_info.largest_key, Key(499)); - - // file6.sst (delete 400 => 500) - std::string file6 = sst_files_dir_ + "file6.sst"; - ASSERT_OK(sst_file_writer.Open(file6)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(400), Key(500))); - ExternalSstFileInfo file6_info; - ASSERT_OK(sst_file_writer.Finish(&file6_info)); - ASSERT_EQ(file6_info.file_path, file6); - ASSERT_EQ(file6_info.num_entries, 0); - ASSERT_EQ(file6_info.smallest_key, ""); - ASSERT_EQ(file6_info.largest_key, ""); - ASSERT_EQ(file6_info.num_range_del_entries, 1); - ASSERT_EQ(file6_info.smallest_range_del_key, Key(400)); - ASSERT_EQ(file6_info.largest_range_del_key, Key(500)); - - // file7.sst (delete 500 => 570, put 520 => 599 divisible by 2) - std::string file7 = sst_files_dir_ + "file7.sst"; - ASSERT_OK(sst_file_writer.Open(file7)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(500), Key(550))); - for (int k = 520; k < 560; k += 2) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ASSERT_OK(sst_file_writer.DeleteRange(Key(525), Key(575))); - for (int k = 560; k < 600; k += 2) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file7_info; - ASSERT_OK(sst_file_writer.Finish(&file7_info)); - ASSERT_EQ(file7_info.file_path, file7); - ASSERT_EQ(file7_info.num_entries, 40); - ASSERT_EQ(file7_info.smallest_key, Key(520)); - ASSERT_EQ(file7_info.largest_key, Key(598)); - ASSERT_EQ(file7_info.num_range_del_entries, 2); - ASSERT_EQ(file7_info.smallest_range_del_key, Key(500)); - ASSERT_EQ(file7_info.largest_range_del_key, Key(575)); - - // file8.sst (delete 600 => 700) - std::string file8 = sst_files_dir_ + "file8.sst"; - ASSERT_OK(sst_file_writer.Open(file8)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(600), Key(700))); - ExternalSstFileInfo file8_info; - ASSERT_OK(sst_file_writer.Finish(&file8_info)); - ASSERT_EQ(file8_info.file_path, file8); - ASSERT_EQ(file8_info.num_entries, 0); - ASSERT_EQ(file8_info.smallest_key, ""); - ASSERT_EQ(file8_info.largest_key, ""); - ASSERT_EQ(file8_info.num_range_del_entries, 1); - ASSERT_EQ(file8_info.smallest_range_del_key, Key(600)); - ASSERT_EQ(file8_info.largest_range_del_key, Key(700)); - - // Cannot create an empty sst file - std::string file_empty = sst_files_dir_ + "file_empty.sst"; - ExternalSstFileInfo file_empty_info; - ASSERT_NOK(sst_file_writer.Finish(&file_empty_info)); - - DestroyAndReopen(options); - // Add file using file path - ASSERT_OK(DeprecatedAddFile({file1})); - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - for (int k = 0; k < 100; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } - - // Add file while holding a snapshot will fail - const Snapshot* s1 = db_->GetSnapshot(); - if (s1 != nullptr) { - ASSERT_NOK(DeprecatedAddFile({file2})); - db_->ReleaseSnapshot(s1); - } - // We can add the file after releaseing the snapshot - ASSERT_OK(DeprecatedAddFile({file2})); - - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - for (int k = 0; k < 200; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } - - // This file has overlapping values with the existing data - ASSERT_NOK(DeprecatedAddFile({file3})); - - // This file has overlapping values with the existing data - ASSERT_NOK(DeprecatedAddFile({file4})); - - // Overwrite values of keys divisible by 5 - for (int k = 0; k < 200; k += 5) { - ASSERT_OK(Put(Key(k), Key(k) + "_val_new")); - } - ASSERT_NE(db_->GetLatestSequenceNumber(), 0U); - - // Key range of file5 (400 => 499) don't overlap with any keys in DB - ASSERT_OK(DeprecatedAddFile({file5})); - - // This file has overlapping values with the existing data - ASSERT_NOK(DeprecatedAddFile({file6})); - - // Key range of file7 (500 => 598) don't overlap with any keys in DB - ASSERT_OK(DeprecatedAddFile({file7})); - - // Key range of file7 (600 => 700) don't overlap with any keys in DB - ASSERT_OK(DeprecatedAddFile({file8})); - - // Make sure values are correct before and after flush/compaction - for (int i = 0; i < 2; i++) { - for (int k = 0; k < 200; k++) { - std::string value = Key(k) + "_val"; - if (k % 5 == 0) { - value += "_new"; - } - ASSERT_EQ(Get(Key(k)), value); - } - for (int k = 400; k < 500; k++) { - std::string value = Key(k) + "_val"; - ASSERT_EQ(Get(Key(k)), value); - } - for (int k = 500; k < 600; k++) { - std::string value = Key(k) + "_val"; - if (k < 520 || k % 2 == 1) { - value = "NOT_FOUND"; - } - ASSERT_EQ(Get(Key(k)), value); - } - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - - Close(); - options.disable_auto_compactions = true; - Reopen(options); - - // Delete keys in range (400 => 499) - for (int k = 400; k < 500; k++) { - ASSERT_OK(Delete(Key(k))); - } - // We deleted range (400 => 499) but cannot add file5 because - // of the range tombstones - ASSERT_NOK(DeprecatedAddFile({file5})); - - // Compacting the DB will remove the tombstones - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // Now we can add the file - ASSERT_OK(DeprecatedAddFile({file5})); - - // Verify values of file5 in DB - for (int k = 400; k < 500; k++) { - std::string value = Key(k) + "_val"; - ASSERT_EQ(Get(Key(k)), value); - } - DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction | - kRangeDelSkipConfigs)); -} - -class SstFileWriterCollector : public TablePropertiesCollector { - public: - explicit SstFileWriterCollector(const std::string prefix) : prefix_(prefix) { - name_ = prefix_ + "_SstFileWriterCollector"; - } - - const char* Name() const override { return name_.c_str(); } - - Status Finish(UserCollectedProperties* properties) override { - std::string count = std::to_string(count_); - *properties = UserCollectedProperties{ - {prefix_ + "_SstFileWriterCollector", "YES"}, - {prefix_ + "_Count", count}, - }; - return Status::OK(); - } - - Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/, - EntryType /*type*/, SequenceNumber /*seq*/, - uint64_t /*file_size*/) override { - ++count_; - return Status::OK(); - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - private: - uint32_t count_ = 0; - std::string prefix_; - std::string name_; -}; - -class SstFileWriterCollectorFactory : public TablePropertiesCollectorFactory { - public: - explicit SstFileWriterCollectorFactory(std::string prefix) - : prefix_(prefix), num_created_(0) {} - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - num_created_++; - return new SstFileWriterCollector(prefix_); - } - const char* Name() const override { return "SstFileWriterCollectorFactory"; } - - std::string prefix_; - uint32_t num_created_; -}; - -TEST_F(ExternalSSTFileTest, AddList) { - do { - Options options = CurrentOptions(); - - auto abc_collector = std::make_shared("abc"); - auto xyz_collector = std::make_shared("xyz"); - - options.table_properties_collector_factories.emplace_back(abc_collector); - options.table_properties_collector_factories.emplace_back(xyz_collector); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file1.sst (0 => 99) - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 0; k < 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - ASSERT_OK(sst_file_writer.Finish(&file1_info)); - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(0)); - ASSERT_EQ(file1_info.largest_key, Key(99)); - // sst_file_writer already finished, cannot add this value - ASSERT_NOK(sst_file_writer.Put(Key(100), "bad_val")); - - // file2.sst (100 => 199) - std::string file2 = sst_files_dir_ + "file2.sst"; - ASSERT_OK(sst_file_writer.Open(file2)); - for (int k = 100; k < 200; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - // Cannot add this key because it's not after last added key - ASSERT_NOK(sst_file_writer.Put(Key(99), "bad_val")); - ExternalSstFileInfo file2_info; - ASSERT_OK(sst_file_writer.Finish(&file2_info)); - ASSERT_EQ(file2_info.file_path, file2); - ASSERT_EQ(file2_info.num_entries, 100); - ASSERT_EQ(file2_info.smallest_key, Key(100)); - ASSERT_EQ(file2_info.largest_key, Key(199)); - - // file3.sst (195 => 199) - // This file values overlap with file2 values - std::string file3 = sst_files_dir_ + "file3.sst"; - ASSERT_OK(sst_file_writer.Open(file3)); - for (int k = 195; k < 200; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file3_info; - ASSERT_OK(sst_file_writer.Finish(&file3_info)); - ASSERT_EQ(file3_info.file_path, file3); - ASSERT_EQ(file3_info.num_entries, 5); - ASSERT_EQ(file3_info.smallest_key, Key(195)); - ASSERT_EQ(file3_info.largest_key, Key(199)); - - // file4.sst (30 => 39) - // This file values overlap with file1 values - std::string file4 = sst_files_dir_ + "file4.sst"; - ASSERT_OK(sst_file_writer.Open(file4)); - for (int k = 30; k < 40; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); - } - ExternalSstFileInfo file4_info; - ASSERT_OK(sst_file_writer.Finish(&file4_info)); - ASSERT_EQ(file4_info.file_path, file4); - ASSERT_EQ(file4_info.num_entries, 10); - ASSERT_EQ(file4_info.smallest_key, Key(30)); - ASSERT_EQ(file4_info.largest_key, Key(39)); - - // file5.sst (200 => 299) - std::string file5 = sst_files_dir_ + "file5.sst"; - ASSERT_OK(sst_file_writer.Open(file5)); - for (int k = 200; k < 300; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file5_info; - ASSERT_OK(sst_file_writer.Finish(&file5_info)); - ASSERT_EQ(file5_info.file_path, file5); - ASSERT_EQ(file5_info.num_entries, 100); - ASSERT_EQ(file5_info.smallest_key, Key(200)); - ASSERT_EQ(file5_info.largest_key, Key(299)); - - // file6.sst (delete 0 => 100) - std::string file6 = sst_files_dir_ + "file6.sst"; - ASSERT_OK(sst_file_writer.Open(file6)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(0), Key(75))); - ASSERT_OK(sst_file_writer.DeleteRange(Key(25), Key(100))); - ExternalSstFileInfo file6_info; - ASSERT_OK(sst_file_writer.Finish(&file6_info)); - ASSERT_EQ(file6_info.file_path, file6); - ASSERT_EQ(file6_info.num_entries, 0); - ASSERT_EQ(file6_info.smallest_key, ""); - ASSERT_EQ(file6_info.largest_key, ""); - ASSERT_EQ(file6_info.num_range_del_entries, 2); - ASSERT_EQ(file6_info.smallest_range_del_key, Key(0)); - ASSERT_EQ(file6_info.largest_range_del_key, Key(100)); - - // file7.sst (delete 99 => 201) - std::string file7 = sst_files_dir_ + "file7.sst"; - ASSERT_OK(sst_file_writer.Open(file7)); - ASSERT_OK(sst_file_writer.DeleteRange(Key(99), Key(201))); - ExternalSstFileInfo file7_info; - ASSERT_OK(sst_file_writer.Finish(&file7_info)); - ASSERT_EQ(file7_info.file_path, file7); - ASSERT_EQ(file7_info.num_entries, 0); - ASSERT_EQ(file7_info.smallest_key, ""); - ASSERT_EQ(file7_info.largest_key, ""); - ASSERT_EQ(file7_info.num_range_del_entries, 1); - ASSERT_EQ(file7_info.smallest_range_del_key, Key(99)); - ASSERT_EQ(file7_info.largest_range_del_key, Key(201)); - - // list 1 has internal key range conflict - std::vector file_list0({file1, file2}); - std::vector file_list1({file3, file2, file1}); - std::vector file_list2({file5}); - std::vector file_list3({file3, file4}); - std::vector file_list4({file5, file7}); - std::vector file_list5({file6, file7}); - - DestroyAndReopen(options); - - // These lists of files have key ranges that overlap with each other - ASSERT_NOK(DeprecatedAddFile(file_list1)); - // Both of the following overlap on the range deletion tombstone. - ASSERT_NOK(DeprecatedAddFile(file_list4)); - ASSERT_NOK(DeprecatedAddFile(file_list5)); - - // Add files using file path list - ASSERT_OK(DeprecatedAddFile(file_list0)); - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - for (int k = 0; k < 200; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } - - TablePropertiesCollection props; - ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); - ASSERT_EQ(props.size(), 2); - for (auto file_props : props) { - auto user_props = file_props.second->user_collected_properties; - ASSERT_EQ(user_props["abc_SstFileWriterCollector"], "YES"); - ASSERT_EQ(user_props["xyz_SstFileWriterCollector"], "YES"); - ASSERT_EQ(user_props["abc_Count"], "100"); - ASSERT_EQ(user_props["xyz_Count"], "100"); - } - - // Add file while holding a snapshot will fail - const Snapshot* s1 = db_->GetSnapshot(); - if (s1 != nullptr) { - ASSERT_NOK(DeprecatedAddFile(file_list2)); - db_->ReleaseSnapshot(s1); - } - // We can add the file after releaseing the snapshot - ASSERT_OK(DeprecatedAddFile(file_list2)); - ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); - for (int k = 0; k < 300; k++) { - ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); - } - - ASSERT_OK(db_->GetPropertiesOfAllTables(&props)); - ASSERT_EQ(props.size(), 3); - for (auto file_props : props) { - auto user_props = file_props.second->user_collected_properties; - ASSERT_EQ(user_props["abc_SstFileWriterCollector"], "YES"); - ASSERT_EQ(user_props["xyz_SstFileWriterCollector"], "YES"); - ASSERT_EQ(user_props["abc_Count"], "100"); - ASSERT_EQ(user_props["xyz_Count"], "100"); - } - - // This file list has overlapping values with the existing data - ASSERT_NOK(DeprecatedAddFile(file_list3)); - - // Overwrite values of keys divisible by 5 - for (int k = 0; k < 200; k += 5) { - ASSERT_OK(Put(Key(k), Key(k) + "_val_new")); - } - ASSERT_NE(db_->GetLatestSequenceNumber(), 0U); - - // Make sure values are correct before and after flush/compaction - for (int i = 0; i < 2; i++) { - for (int k = 0; k < 200; k++) { - std::string value = Key(k) + "_val"; - if (k % 5 == 0) { - value += "_new"; - } - ASSERT_EQ(Get(Key(k)), value); - } - for (int k = 200; k < 300; k++) { - std::string value = Key(k) + "_val"; - ASSERT_EQ(Get(Key(k)), value); - } - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - - // Delete keys in range (200 => 299) - for (int k = 200; k < 300; k++) { - ASSERT_OK(Delete(Key(k))); - } - // We deleted range (200 => 299) but cannot add file5 because - // of the range tombstones - ASSERT_NOK(DeprecatedAddFile(file_list2)); - - // Compacting the DB will remove the tombstones - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // Now we can add the file - ASSERT_OK(DeprecatedAddFile(file_list2)); - - // Verify values of file5 in DB - for (int k = 200; k < 300; k++) { - std::string value = Key(k) + "_val"; - ASSERT_EQ(Get(Key(k)), value); - } - DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction | - kRangeDelSkipConfigs)); -} - -TEST_F(ExternalSSTFileTest, AddListAtomicity) { - do { - Options options = CurrentOptions(); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // files[0].sst (0 => 99) - // files[1].sst (100 => 199) - // ... - // file[8].sst (800 => 899) - int n = 9; - std::vector files(n); - std::vector files_info(n); - for (int i = 0; i < n; i++) { - files[i] = sst_files_dir_ + "file" + std::to_string(i) + ".sst"; - ASSERT_OK(sst_file_writer.Open(files[i])); - for (int k = i * 100; k < (i + 1) * 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ASSERT_OK(sst_file_writer.Finish(&files_info[i])); - ASSERT_EQ(files_info[i].file_path, files[i]); - ASSERT_EQ(files_info[i].num_entries, 100); - ASSERT_EQ(files_info[i].smallest_key, Key(i * 100)); - ASSERT_EQ(files_info[i].largest_key, Key((i + 1) * 100 - 1)); - } - files.push_back(sst_files_dir_ + "file" + std::to_string(n) + ".sst"); - ASSERT_NOK(DeprecatedAddFile(files)); - for (int k = 0; k < n * 100; k++) { - ASSERT_EQ("NOT_FOUND", Get(Key(k))); - } - files.pop_back(); - ASSERT_OK(DeprecatedAddFile(files)); - for (int k = 0; k < n * 100; k++) { - std::string value = Key(k) + "_val"; - ASSERT_EQ(Get(Key(k)), value); - } - DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction)); -} -// This test reporduce a bug that can happen in some cases if the DB started -// purging obsolete files when we are adding an external sst file. -// This situation may result in deleting the file while it's being added. -TEST_F(ExternalSSTFileTest, PurgeObsoleteFilesBug) { - Options options = CurrentOptions(); - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file1.sst (0 => 500) - std::string sst_file_path = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(sst_file_path)); - for (int i = 0; i < 500; i++) { - std::string k = Key(i); - ASSERT_OK(sst_file_writer.Put(k, k + "_val")); - } - - ExternalSstFileInfo sst_file_info; - ASSERT_OK(sst_file_writer.Finish(&sst_file_info)); - - options.delete_obsolete_files_period_micros = 0; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Prepare:FileAdded", [&](void* /* arg */) { - ASSERT_OK(Put("aaa", "bbb")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("aaa", "xxx")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(DeprecatedAddFile({sst_file_path})); - - for (int i = 0; i < 500; i++) { - std::string k = Key(i); - std::string v = k + "_val"; - ASSERT_EQ(Get(k), v); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(ExternalSSTFileTest, SkipSnapshot) { - Options options = CurrentOptions(); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // file1.sst (0 => 99) - std::string file1 = sst_files_dir_ + "file1.sst"; - ASSERT_OK(sst_file_writer.Open(file1)); - for (int k = 0; k < 100; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file1_info; - ASSERT_OK(sst_file_writer.Finish(&file1_info)); - ASSERT_EQ(file1_info.file_path, file1); - ASSERT_EQ(file1_info.num_entries, 100); - ASSERT_EQ(file1_info.smallest_key, Key(0)); - ASSERT_EQ(file1_info.largest_key, Key(99)); - - // file2.sst (100 => 299) - std::string file2 = sst_files_dir_ + "file2.sst"; - ASSERT_OK(sst_file_writer.Open(file2)); - for (int k = 100; k < 300; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file2_info; - ASSERT_OK(sst_file_writer.Finish(&file2_info)); - ASSERT_EQ(file2_info.file_path, file2); - ASSERT_EQ(file2_info.num_entries, 200); - ASSERT_EQ(file2_info.smallest_key, Key(100)); - ASSERT_EQ(file2_info.largest_key, Key(299)); - - ASSERT_OK(DeprecatedAddFile({file1})); - - // Add file will fail when holding snapshot and use the default - // skip_snapshot_check to false - const Snapshot* s1 = db_->GetSnapshot(); - if (s1 != nullptr) { - ASSERT_NOK(DeprecatedAddFile({file2})); - } - - // Add file will success when set skip_snapshot_check to true even db holding - // snapshot - if (s1 != nullptr) { - ASSERT_OK(DeprecatedAddFile({file2}, false, true)); - db_->ReleaseSnapshot(s1); - } - - // file3.sst (300 => 399) - std::string file3 = sst_files_dir_ + "file3.sst"; - ASSERT_OK(sst_file_writer.Open(file3)); - for (int k = 300; k < 400; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); - } - ExternalSstFileInfo file3_info; - ASSERT_OK(sst_file_writer.Finish(&file3_info)); - ASSERT_EQ(file3_info.file_path, file3); - ASSERT_EQ(file3_info.num_entries, 100); - ASSERT_EQ(file3_info.smallest_key, Key(300)); - ASSERT_EQ(file3_info.largest_key, Key(399)); - - // check that we have change the old key - ASSERT_EQ(Get(Key(300)), "NOT_FOUND"); - const Snapshot* s2 = db_->GetSnapshot(); - ASSERT_OK(DeprecatedAddFile({file3}, false, true)); - ASSERT_EQ(Get(Key(300)), Key(300) + ("_val")); - ASSERT_EQ(Get(Key(300), s2), Key(300) + ("_val")); - - db_->ReleaseSnapshot(s2); -} - -TEST_F(ExternalSSTFileTest, MultiThreaded) { - env_->skip_fsync_ = true; - // Bulk load 10 files every file contain 1000 keys - int num_files = 10; - int keys_per_file = 1000; - - // Generate file names - std::vector file_names; - for (int i = 0; i < num_files; i++) { - std::string file_name = "file_" + std::to_string(i) + ".sst"; - file_names.push_back(sst_files_dir_ + file_name); - } - - do { - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - std::atomic thread_num(0); - std::function write_file_func = [&]() { - int file_idx = thread_num.fetch_add(1); - int range_start = file_idx * keys_per_file; - int range_end = range_start + keys_per_file; - - SstFileWriter sst_file_writer(EnvOptions(), options); - - ASSERT_OK(sst_file_writer.Open(file_names[file_idx])); - - for (int k = range_start; k < range_end; k++) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k))); - } - - ASSERT_OK(sst_file_writer.Finish()); - }; - // Write num_files files in parallel - std::vector sst_writer_threads; - for (int i = 0; i < num_files; ++i) { - sst_writer_threads.emplace_back(write_file_func); - } - - for (auto& t : sst_writer_threads) { - t.join(); - } - - fprintf(stderr, "Wrote %d files (%d keys)\n", num_files, - num_files * keys_per_file); - - thread_num.store(0); - std::atomic files_added(0); - // Thread 0 -> Load {f0,f1} - // Thread 1 -> Load {f0,f1} - // Thread 2 -> Load {f2,f3} - // Thread 3 -> Load {f2,f3} - // Thread 4 -> Load {f4,f5} - // Thread 5 -> Load {f4,f5} - // ... - std::function load_file_func = [&]() { - // We intentionally add every file twice, and assert that it was added - // only once and the other add failed - int thread_id = thread_num.fetch_add(1); - int file_idx = (thread_id / 2) * 2; - // sometimes we use copy, sometimes link .. the result should be the same - bool move_file = (thread_id % 3 == 0); - - std::vector files_to_add; - - files_to_add = {file_names[file_idx]}; - if (static_cast(file_idx + 1) < file_names.size()) { - files_to_add.push_back(file_names[file_idx + 1]); - } - - Status s = DeprecatedAddFile(files_to_add, move_file); - if (s.ok()) { - files_added += static_cast(files_to_add.size()); - } - }; - - // Bulk load num_files files in parallel - std::vector add_file_threads; - DestroyAndReopen(options); - for (int i = 0; i < num_files; ++i) { - add_file_threads.emplace_back(load_file_func); - } - - for (auto& t : add_file_threads) { - t.join(); - } - ASSERT_EQ(files_added.load(), num_files); - fprintf(stderr, "Loaded %d files (%d keys)\n", num_files, - num_files * keys_per_file); - - // Overwrite values of keys divisible by 100 - for (int k = 0; k < num_files * keys_per_file; k += 100) { - std::string key = Key(k); - ASSERT_OK(Put(key, key + "_new")); - } - - for (int i = 0; i < 2; i++) { - // Make sure the values are correct before and after flush/compaction - for (int k = 0; k < num_files * keys_per_file; ++k) { - std::string key = Key(k); - std::string value = (k % 100 == 0) ? (key + "_new") : key; - ASSERT_EQ(Get(key), value); - } - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - - fprintf(stderr, "Verified %d values\n", num_files * keys_per_file); - DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction)); -} - -TEST_F(ExternalSSTFileTest, OverlappingRanges) { - env_->skip_fsync_ = true; - Random rnd(301); - SequenceNumber assigned_seqno = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Run", [&assigned_seqno](void* arg) { - ASSERT_TRUE(arg != nullptr); - assigned_seqno = *(static_cast(arg)); - }); - bool need_flush = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::IngestExternalFile:NeedFlush", [&need_flush](void* arg) { - ASSERT_TRUE(arg != nullptr); - need_flush = *(static_cast(arg)); - }); - bool overlap_with_db = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile", - [&overlap_with_db](void* arg) { - ASSERT_TRUE(arg != nullptr); - overlap_with_db = *(static_cast(arg)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - do { - Options options = CurrentOptions(); - env_->skip_fsync_ = true; - DestroyAndReopen(options); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - printf("Option config = %d\n", option_config_); - std::vector> key_ranges; - for (int i = 0; i < 100; i++) { - int range_start = rnd.Uniform(20000); - int keys_per_range = 10 + rnd.Uniform(41); - - key_ranges.emplace_back(range_start, range_start + keys_per_range); - } - - int memtable_add = 0; - int success_add_file = 0; - int failed_add_file = 0; - std::map true_data; - for (size_t i = 0; i < key_ranges.size(); i++) { - int range_start = key_ranges[i].first; - int range_end = key_ranges[i].second; - - Status s; - std::string range_val = "range_" + std::to_string(i); - - // For 20% of ranges we use DB::Put, for 80% we use DB::AddFile - if (i && i % 5 == 0) { - // Use DB::Put to insert range (insert into memtable) - range_val += "_put"; - for (int k = range_start; k <= range_end; k++) { - s = Put(Key(k), range_val); - ASSERT_OK(s); - } - memtable_add++; - } else { - // Use DB::AddFile to insert range - range_val += "_add_file"; - - // Generate the file containing the range - std::string file_name = sst_files_dir_ + env_->GenerateUniqueId(); - s = sst_file_writer.Open(file_name); - ASSERT_OK(s); - for (int k = range_start; k <= range_end; k++) { - s = sst_file_writer.Put(Key(k), range_val); - ASSERT_OK(s); - } - ExternalSstFileInfo file_info; - s = sst_file_writer.Finish(&file_info); - ASSERT_OK(s); - - // Insert the generated file - s = DeprecatedAddFile({file_name}); - auto it = true_data.lower_bound(Key(range_start)); - if (option_config_ != kUniversalCompaction && - option_config_ != kUniversalCompactionMultiLevel && - option_config_ != kUniversalSubcompactions) { - if (it != true_data.end() && it->first <= Key(range_end)) { - // This range overlap with data already exist in DB - ASSERT_NOK(s); - failed_add_file++; - } else { - ASSERT_OK(s); - success_add_file++; - } - } else { - if ((it != true_data.end() && it->first <= Key(range_end)) || - need_flush || assigned_seqno > 0 || overlap_with_db) { - // This range overlap with data already exist in DB - ASSERT_NOK(s); - failed_add_file++; - } else { - ASSERT_OK(s); - success_add_file++; - } - } - } - - if (s.ok()) { - // Update true_data map to include the new inserted data - for (int k = range_start; k <= range_end; k++) { - true_data[Key(k)] = range_val; - } - } - - // Flush / Compact the DB - if (i && i % 50 == 0) { - ASSERT_OK(Flush()); - } - if (i && i % 75 == 0) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - } - - printf("Total: %" ROCKSDB_PRIszt - " ranges\n" - "AddFile()|Success: %d ranges\n" - "AddFile()|RangeConflict: %d ranges\n" - "Put(): %d ranges\n", - key_ranges.size(), success_add_file, failed_add_file, memtable_add); - - // Verify the correctness of the data - for (const auto& kv : true_data) { - ASSERT_EQ(Get(kv.first), kv.second); - } - printf("keys/values verified\n"); - DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction)); -} - -TEST_P(ExternalSSTFileTest, PickedLevel) { - env_->skip_fsync_ = true; - Options options = CurrentOptions(); - options.disable_auto_compactions = false; - options.level0_file_num_compaction_trigger = 4; - options.num_levels = 4; - DestroyAndReopen(options); - - std::map true_data; - - // File 0 will go to last level (L3) - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false, true, - false, false, &true_data)); - EXPECT_EQ(FilesPerLevel(), "0,0,0,1"); - - // File 1 will go to level L2 (since it overlap with file 0 in L3) - ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, true, - false, false, &true_data)); - EXPECT_EQ(FilesPerLevel(), "0,0,1,1"); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"ExternalSSTFileTest::PickedLevel:0", "BackgroundCallCompaction:0"}, - {"DBImpl::BackgroundCompaction:Start", - "ExternalSSTFileTest::PickedLevel:1"}, - {"ExternalSSTFileTest::PickedLevel:2", - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Flush 4 files containing the same keys - for (int i = 0; i < 4; i++) { - ASSERT_OK(Put(Key(3), Key(3) + "put")); - ASSERT_OK(Put(Key(8), Key(8) + "put")); - true_data[Key(3)] = Key(3) + "put"; - true_data[Key(8)] = Key(8) + "put"; - ASSERT_OK(Flush()); - } - - // Wait for BackgroundCompaction() to be called - TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevel:0"); - TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevel:1"); - - EXPECT_EQ(FilesPerLevel(), "4,0,1,1"); - - // This file overlaps with file 0 (L3), file 1 (L2) and the - // output of compaction going to L1 - ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1, - true /* allow_global_seqno */, false, - true, false, false, &true_data)); - EXPECT_EQ(FilesPerLevel(), "5,0,1,1"); - - // This file does not overlap with any file or with the running compaction - ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false, - false, false, false, &true_data)); - EXPECT_EQ(FilesPerLevel(), "5,0,1,2"); - - // Hold compaction from finishing - TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevel:2"); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - EXPECT_EQ(FilesPerLevel(), "1,1,1,2"); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(ExternalSSTFileTest, IngestNonExistingFile) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - - Status s = db_->IngestExternalFile({"non_existing_file"}, - IngestExternalFileOptions()); - ASSERT_NOK(s); - - // Verify file deletion is not impacted (verify a bug fix) - ASSERT_OK(Put(Key(1), Key(1))); - ASSERT_OK(Put(Key(9), Key(9))); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(1), Key(1))); - ASSERT_OK(Put(Key(9), Key(9))); - ASSERT_OK(Flush()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); - - // After full compaction, there should be only 1 file. - std::vector files; - ASSERT_OK(env_->GetChildren(dbname_, &files)); - int num_sst_files = 0; - for (auto& f : files) { - uint64_t number; - FileType type; - if (ParseFileName(f, &number, &type) && type == kTableFile) { - num_sst_files++; - } - } - ASSERT_EQ(1, num_sst_files); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) { - env_->skip_fsync_ = true; - Options options = CurrentOptions(); - options.disable_auto_compactions = false; - options.level0_file_num_compaction_trigger = 2; - options.num_levels = 2; - DestroyAndReopen(options); - - std::function bg_compact = [&]() { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - }; - - int range_id = 0; - std::vector file_keys; - std::function bg_addfile = [&]() { - ASSERT_OK(GenerateAndAddExternalFile(options, file_keys, range_id, - true /* allow_global_seqno */)); - }; - - const int num_of_ranges = 1000; - std::vector threads; - while (range_id < num_of_ranges) { - int range_start = range_id * 10; - int range_end = range_start + 10; - - file_keys.clear(); - for (int k = range_start + 1; k < range_end; k++) { - file_keys.push_back(k); - } - ASSERT_OK(Put(Key(range_start), Key(range_start))); - ASSERT_OK(Put(Key(range_end), Key(range_end))); - ASSERT_OK(Flush()); - - if (range_id % 10 == 0) { - threads.emplace_back(bg_compact); - } - threads.emplace_back(bg_addfile); - - for (auto& t : threads) { - t.join(); - } - threads.clear(); - - range_id++; - } - - for (int rid = 0; rid < num_of_ranges; rid++) { - int range_start = rid * 10; - int range_end = range_start + 10; - - ASSERT_EQ(Get(Key(range_start)), Key(range_start)) << rid; - ASSERT_EQ(Get(Key(range_end)), Key(range_end)) << rid; - for (int k = range_start + 1; k < range_end; k++) { - std::string v = Key(k) + std::to_string(rid); - ASSERT_EQ(Get(Key(k)), v) << rid; - } - } -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_F(ExternalSSTFileTest, PickedLevelDynamic) { - env_->skip_fsync_ = true; - Options options = CurrentOptions(); - options.disable_auto_compactions = false; - options.level0_file_num_compaction_trigger = 4; - options.level_compaction_dynamic_level_bytes = true; - options.num_levels = 4; - DestroyAndReopen(options); - std::map true_data; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"ExternalSSTFileTest::PickedLevelDynamic:0", - "BackgroundCallCompaction:0"}, - {"DBImpl::BackgroundCompaction:Start", - "ExternalSSTFileTest::PickedLevelDynamic:1"}, - {"ExternalSSTFileTest::PickedLevelDynamic:2", - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Flush 4 files containing the same keys - for (int i = 0; i < 4; i++) { - for (int k = 20; k <= 30; k++) { - ASSERT_OK(Put(Key(k), Key(k) + "put")); - true_data[Key(k)] = Key(k) + "put"; - } - for (int k = 50; k <= 60; k++) { - ASSERT_OK(Put(Key(k), Key(k) + "put")); - true_data[Key(k)] = Key(k) + "put"; - } - ASSERT_OK(Flush()); - } - - // Wait for BackgroundCompaction() to be called - TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelDynamic:0"); - TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelDynamic:1"); - - // This file overlaps with the output of the compaction (going to L3) - // so the file will be added to L0 since L3 is the base level - ASSERT_OK(GenerateAndAddExternalFile(options, {31, 32, 33, 34}, -1, - true /* allow_global_seqno */, false, - true, false, false, &true_data)); - EXPECT_EQ(FilesPerLevel(), "5"); - - // This file does not overlap with the current running compactiong - ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false, - true, false, false, &true_data)); - EXPECT_EQ(FilesPerLevel(), "5,0,0,1"); - - // Hold compaction from finishing - TEST_SYNC_POINT("ExternalSSTFileTest::PickedLevelDynamic:2"); - - // Output of the compaction will go to L3 - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - EXPECT_EQ(FilesPerLevel(), "1,0,0,2"); - - Close(); - options.disable_auto_compactions = true; - Reopen(options); - - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 15, 19}, -1, false, false, - true, false, false, &true_data)); - ASSERT_EQ(FilesPerLevel(), "1,0,0,3"); - - ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1001, 1002}, -1, false, - false, true, false, false, &true_data)); - ASSERT_EQ(FilesPerLevel(), "1,0,0,4"); - - ASSERT_OK(GenerateAndAddExternalFile(options, {500, 600, 700}, -1, false, - false, true, false, false, &true_data)); - ASSERT_EQ(FilesPerLevel(), "1,0,0,5"); - - // File 5 overlaps with file 2 (L3 / base level) - ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false, true, - false, false, &true_data)); - ASSERT_EQ(FilesPerLevel(), "2,0,0,5"); - - // File 6 overlaps with file 2 (L3 / base level) and file 5 (L0) - ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, true, - false, false, &true_data)); - ASSERT_EQ(FilesPerLevel(), "3,0,0,5"); - - // Verify data in files - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); - - // Write range [5 => 10] to L0 - for (int i = 5; i <= 10; i++) { - std::string k = Key(i); - std::string v = k + "put"; - ASSERT_OK(Put(k, v)); - true_data[k] = v; - } - ASSERT_OK(Flush()); - ASSERT_EQ(FilesPerLevel(), "4,0,0,5"); - - // File 7 overlaps with file 4 (L3) - ASSERT_OK(GenerateAndAddExternalFile(options, {650, 651, 652}, -1, false, - false, true, false, false, &true_data)); - ASSERT_EQ(FilesPerLevel(), "5,0,0,5"); - - VerifyDBFromMap(true_data, &kcnt, false); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(ExternalSSTFileTest, AddExternalSstFileWithCustomCompartor) { - Options options = CurrentOptions(); - options.comparator = ReverseBytewiseComparator(); - DestroyAndReopen(options); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // Generate files with these key ranges - // {14 -> 0} - // {24 -> 10} - // {34 -> 20} - // {44 -> 30} - // .. - std::vector generated_files; - for (int i = 0; i < 10; i++) { - std::string file_name = sst_files_dir_ + env_->GenerateUniqueId(); - ASSERT_OK(sst_file_writer.Open(file_name)); - - int range_end = i * 10; - int range_start = range_end + 15; - for (int k = (range_start - 1); k >= range_end; k--) { - ASSERT_OK(sst_file_writer.Put(Key(k), Key(k))); - } - ExternalSstFileInfo file_info; - ASSERT_OK(sst_file_writer.Finish(&file_info)); - generated_files.push_back(file_name); - } - - std::vector in_files; - - // These 2nd and 3rd files overlap with each other - in_files = {generated_files[0], generated_files[4], generated_files[5], - generated_files[7]}; - ASSERT_NOK(DeprecatedAddFile(in_files)); - - // These 2 files don't overlap with each other - in_files = {generated_files[0], generated_files[2]}; - ASSERT_OK(DeprecatedAddFile(in_files)); - - // These 2 files don't overlap with each other but overlap with keys in DB - in_files = {generated_files[3], generated_files[7]}; - ASSERT_NOK(DeprecatedAddFile(in_files)); - - // Files don't overlap and don't overlap with DB key range - in_files = {generated_files[4], generated_files[6], generated_files[8]}; - ASSERT_OK(DeprecatedAddFile(in_files)); - - for (int i = 0; i < 100; i++) { - if (i % 20 <= 14) { - ASSERT_EQ(Get(Key(i)), Key(i)); - } else { - ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); - } - } -} - -TEST_F(ExternalSSTFileTest, AddFileTrivialMoveBug) { - Options options = CurrentOptions(); - options.num_levels = 3; - options.IncreaseParallelism(20); - DestroyAndReopen(options); - - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 4}, 1)); // L3 - ASSERT_OK(GenerateAndAddExternalFile(options, {2, 3}, 2)); // L2 - - ASSERT_OK(GenerateAndAddExternalFile(options, {10, 14}, 3)); // L3 - ASSERT_OK(GenerateAndAddExternalFile(options, {12, 13}, 4)); // L2 - - ASSERT_OK(GenerateAndAddExternalFile(options, {20, 24}, 5)); // L3 - ASSERT_OK(GenerateAndAddExternalFile(options, {22, 23}, 6)); // L2 - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():Start", [&](void* /*arg*/) { - // Fit in L3 but will overlap with the compaction output so will be - // added to L2. Prior to the fix, a compaction will then trivially move - // this file to L3 and break LSM consistency - static std::atomic called = {false}; - if (!called) { - called = true; - ASSERT_OK(dbfull()->SetOptions({{"max_bytes_for_level_base", "1"}})); - ASSERT_OK(GenerateAndAddExternalFile(options, {15, 16}, 7, - true /* allow_global_seqno */)); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = false; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(ExternalSSTFileTest, CompactAddedFiles) { - Options options = CurrentOptions(); - options.num_levels = 3; - DestroyAndReopen(options); - - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, 1)); // L3 - ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, 2)); // L2 - ASSERT_OK(GenerateAndAddExternalFile(options, {3, 8}, 3)); // L1 - ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, 4)); // L0 - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); -} - -TEST_F(ExternalSSTFileTest, SstFileWriterNonSharedKeys) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - std::string file_path = sst_files_dir_ + "/not_shared"; - SstFileWriter sst_file_writer(EnvOptions(), options); - - std::string suffix(100, 'X'); - ASSERT_OK(sst_file_writer.Open(file_path)); - ASSERT_OK(sst_file_writer.Put("A" + suffix, "VAL")); - ASSERT_OK(sst_file_writer.Put("BB" + suffix, "VAL")); - ASSERT_OK(sst_file_writer.Put("CC" + suffix, "VAL")); - ASSERT_OK(sst_file_writer.Put("CXD" + suffix, "VAL")); - ASSERT_OK(sst_file_writer.Put("CZZZ" + suffix, "VAL")); - ASSERT_OK(sst_file_writer.Put("ZAAAX" + suffix, "VAL")); - - ASSERT_OK(sst_file_writer.Finish()); - ASSERT_OK(DeprecatedAddFile({file_path})); -} - -TEST_F(ExternalSSTFileTest, WithUnorderedWrite) { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL", - "ExternalSSTFileTest::WithUnorderedWrite:WaitWriteWAL"}, - {"DBImpl::WaitForPendingWrites:BeforeBlock", - "DBImpl::WriteImpl:BeforeUnorderedWriteMemtable"}}); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::IngestExternalFile:NeedFlush", [&](void* need_flush) { - ASSERT_TRUE(*reinterpret_cast(need_flush)); - }); - - Options options = CurrentOptions(); - options.unordered_write = true; - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "v1")); - SyncPoint::GetInstance()->EnableProcessing(); - port::Thread writer([&]() { ASSERT_OK(Put("bar", "v2")); }); - - TEST_SYNC_POINT("ExternalSSTFileTest::WithUnorderedWrite:WaitWriteWAL"); - ASSERT_OK(GenerateAndAddExternalFile(options, {{"bar", "v3"}}, -1, - true /* allow_global_seqno */)); - ASSERT_EQ(Get("bar"), "v3"); - - writer.join(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) { - env_->skip_fsync_ = true; - Options options = CurrentOptions(); - options.IncreaseParallelism(20); - options.level0_slowdown_writes_trigger = 256; - options.level0_stop_writes_trigger = 256; - - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - for (int iter = 0; iter < 2; iter++) { - bool write_to_memtable = (iter == 0); - DestroyAndReopen(options); - - Random rnd(301); - std::map true_data; - for (int i = 0; i < 500; i++) { - std::vector> random_data; - for (int j = 0; j < 100; j++) { - std::string k = rnd.RandomString(rnd.Next() % 20); - std::string v = rnd.RandomString(rnd.Next() % 50); - random_data.emplace_back(k, v); - } - - if (write_to_memtable && rnd.OneIn(4)) { - // 25% of writes go through memtable - for (auto& entry : random_data) { - ASSERT_OK(Put(entry.first, entry.second)); - true_data[entry.first] = entry.second; - } - } else { - ASSERT_OK(GenerateAndAddExternalFile( - options, random_data, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, true, &true_data)); - } - } - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - VerifyDBFromMap(true_data, &kcnt, false); - } -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { - Options options = CurrentOptions(); - options.num_levels = 5; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - std::vector> file_data; - std::map true_data; - - // Insert 100 -> 200 into the memtable - for (int i = 100; i <= 200; i++) { - ASSERT_OK(Put(Key(i), "memtable")); - true_data[Key(i)] = "memtable"; - } - - // Insert 0 -> 20 using AddFile - file_data.clear(); - for (int i = 0; i <= 20; i++) { - file_data.emplace_back(Key(i), "L4"); - } - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - ASSERT_OK(GenerateAndAddExternalFile( - options, file_data, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - - // This file don't overlap with anything in the DB, will go to L4 - ASSERT_EQ("0,0,0,0,1", FilesPerLevel()); - - // Insert 80 -> 130 using AddFile - file_data.clear(); - for (int i = 80; i <= 130; i++) { - file_data.emplace_back(Key(i), "L0"); - } - ASSERT_OK(GenerateAndAddExternalFile( - options, file_data, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - - // This file overlap with the memtable, so it will flush it and add - // it self to L0 - ASSERT_EQ("2,0,0,0,1", FilesPerLevel()); - - // Insert 30 -> 50 using AddFile - file_data.clear(); - for (int i = 30; i <= 50; i++) { - file_data.emplace_back(Key(i), "L4"); - } - ASSERT_OK(GenerateAndAddExternalFile( - options, file_data, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - - // This file don't overlap with anything in the DB and fit in L4 as well - ASSERT_EQ("2,0,0,0,2", FilesPerLevel()); - - // Insert 10 -> 40 using AddFile - file_data.clear(); - for (int i = 10; i <= 40; i++) { - file_data.emplace_back(Key(i), "L3"); - } - ASSERT_OK(GenerateAndAddExternalFile( - options, file_data, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - - // This file overlap with files in L4, we will ingest it in L3 - ASSERT_EQ("2,0,0,1,2", FilesPerLevel()); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); -} - -TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - uint64_t entries_in_memtable; - std::map true_data; - - for (int k : {10, 20, 40, 80}) { - ASSERT_OK(Put(Key(k), "memtable")); - true_data[Key(k)] = "memtable"; - } - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &entries_in_memtable)); - ASSERT_GE(entries_in_memtable, 1); - - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - // No need for flush - ASSERT_OK(GenerateAndAddExternalFile( - options, {90, 100, 110}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &entries_in_memtable)); - ASSERT_GE(entries_in_memtable, 1); - - // This file will flush the memtable - ASSERT_OK(GenerateAndAddExternalFile( - options, {19, 20, 21}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &entries_in_memtable)); - ASSERT_EQ(entries_in_memtable, 0); - - for (int k : {200, 201, 205, 206}) { - ASSERT_OK(Put(Key(k), "memtable")); - true_data[Key(k)] = "memtable"; - } - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &entries_in_memtable)); - ASSERT_GE(entries_in_memtable, 1); - - // No need for flush, this file keys fit between the memtable keys - ASSERT_OK(GenerateAndAddExternalFile( - options, {202, 203, 204}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &entries_in_memtable)); - ASSERT_GE(entries_in_memtable, 1); - - // This file will flush the memtable - ASSERT_OK(GenerateAndAddExternalFile( - options, {206, 207}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false, &true_data)); - ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, - &entries_in_memtable)); - ASSERT_EQ(entries_in_memtable, 0); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); -} - -TEST_P(ExternalSSTFileTest, L0SortingIssue) { - Options options = CurrentOptions(); - options.num_levels = 2; - DestroyAndReopen(options); - std::map true_data; - - ASSERT_OK(Put(Key(1), "memtable")); - ASSERT_OK(Put(Key(10), "memtable")); - - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - // No Flush needed, No global seqno needed, Ingest in L1 - ASSERT_OK( - GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false)); - // No Flush needed, but need a global seqno, Ingest in L0 - ASSERT_OK( - GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, false)); - printf("%s\n", FilesPerLevel().c_str()); - - // Overwrite what we added using external files - ASSERT_OK(Put(Key(7), "memtable")); - ASSERT_OK(Put(Key(8), "memtable")); - - // Read values from memtable - ASSERT_EQ(Get(Key(7)), "memtable"); - ASSERT_EQ(Get(Key(8)), "memtable"); - - // Flush and read from L0 - ASSERT_OK(Flush()); - printf("%s\n", FilesPerLevel().c_str()); - ASSERT_EQ(Get(Key(7)), "memtable"); - ASSERT_EQ(Get(Key(8)), "memtable"); -} - -TEST_F(ExternalSSTFileTest, CompactionDeadlock) { - Options options = CurrentOptions(); - options.num_levels = 2; - options.level0_file_num_compaction_trigger = 4; - options.level0_slowdown_writes_trigger = 4; - options.level0_stop_writes_trigger = 4; - DestroyAndReopen(options); - - // atomic conter of currently running bg threads - std::atomic running_threads(0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::DelayWrite:Wait", "ExternalSSTFileTest::DeadLock:0"}, - {"ExternalSSTFileTest::DeadLock:1", "DBImpl::AddFile:Start"}, - {"DBImpl::AddFile:MutexLock", "ExternalSSTFileTest::DeadLock:2"}, - {"ExternalSSTFileTest::DeadLock:3", "BackgroundCallCompaction:0"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Start ingesting and extrnal file in the background - ROCKSDB_NAMESPACE::port::Thread bg_ingest_file([&]() { - running_threads += 1; - ASSERT_OK(GenerateAndAddExternalFile(options, {5, 6})); - running_threads -= 1; - }); - - ASSERT_OK(Put(Key(1), "memtable")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(2), "memtable")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(3), "memtable")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put(Key(4), "memtable")); - ASSERT_OK(Flush()); - - // This thread will try to insert into the memtable but since we have 4 L0 - // files this thread will be blocked and hold the writer thread - ROCKSDB_NAMESPACE::port::Thread bg_block_put([&]() { - running_threads += 1; - ASSERT_OK(Put(Key(10), "memtable")); - running_threads -= 1; - }); - - // Make sure DelayWrite is called first - TEST_SYNC_POINT("ExternalSSTFileTest::DeadLock:0"); - - // `DBImpl::AddFile:Start` will wait until we be here - TEST_SYNC_POINT("ExternalSSTFileTest::DeadLock:1"); - - // Wait for IngestExternalFile() to start and aquire mutex - TEST_SYNC_POINT("ExternalSSTFileTest::DeadLock:2"); - - // Now let compaction start - TEST_SYNC_POINT("ExternalSSTFileTest::DeadLock:3"); - - // Wait for max 5 seconds, if we did not finish all bg threads - // then we hit the deadlock bug - for (int i = 0; i < 10; i++) { - if (running_threads.load() == 0) { - break; - } - // Make sure we do a "real sleep", not a mock one. - SystemClock::Default()->SleepForMicroseconds(500000); - } - - ASSERT_EQ(running_threads.load(), 0); - - bg_ingest_file.join(); - bg_block_put.join(); -} - -TEST_F(ExternalSSTFileTest, DirtyExit) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - std::string file_path = sst_files_dir_ + "/dirty_exit"; - std::unique_ptr sst_file_writer; - - // Destruct SstFileWriter without calling Finish() - sst_file_writer.reset(new SstFileWriter(EnvOptions(), options)); - ASSERT_OK(sst_file_writer->Open(file_path)); - sst_file_writer.reset(); - - // Destruct SstFileWriter with a failing Finish - sst_file_writer.reset(new SstFileWriter(EnvOptions(), options)); - ASSERT_OK(sst_file_writer->Open(file_path)); - ASSERT_NOK(sst_file_writer->Finish()); -} - -TEST_F(ExternalSSTFileTest, FileWithCFInfo) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko", "toto"}, options); - - SstFileWriter sfw_default(EnvOptions(), options, handles_[0]); - SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]); - SstFileWriter sfw_cf2(EnvOptions(), options, handles_[2]); - SstFileWriter sfw_unknown(EnvOptions(), options); - - // default_cf.sst - const std::string cf_default_sst = sst_files_dir_ + "/default_cf.sst"; - ASSERT_OK(sfw_default.Open(cf_default_sst)); - ASSERT_OK(sfw_default.Put("K1", "V1")); - ASSERT_OK(sfw_default.Put("K2", "V2")); - ASSERT_OK(sfw_default.Finish()); - - // cf1.sst - const std::string cf1_sst = sst_files_dir_ + "/cf1.sst"; - ASSERT_OK(sfw_cf1.Open(cf1_sst)); - ASSERT_OK(sfw_cf1.Put("K3", "V1")); - ASSERT_OK(sfw_cf1.Put("K4", "V2")); - ASSERT_OK(sfw_cf1.Finish()); - - // cf_unknown.sst - const std::string unknown_sst = sst_files_dir_ + "/cf_unknown.sst"; - ASSERT_OK(sfw_unknown.Open(unknown_sst)); - ASSERT_OK(sfw_unknown.Put("K5", "V1")); - ASSERT_OK(sfw_unknown.Put("K6", "V2")); - ASSERT_OK(sfw_unknown.Finish()); - - IngestExternalFileOptions ifo; - - // SST CF don't match - ASSERT_NOK(db_->IngestExternalFile(handles_[0], {cf1_sst}, ifo)); - // SST CF don't match - ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf1_sst}, ifo)); - // SST CF match - ASSERT_OK(db_->IngestExternalFile(handles_[1], {cf1_sst}, ifo)); - - // SST CF don't match - ASSERT_NOK(db_->IngestExternalFile(handles_[1], {cf_default_sst}, ifo)); - // SST CF don't match - ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf_default_sst}, ifo)); - // SST CF match - ASSERT_OK(db_->IngestExternalFile(handles_[0], {cf_default_sst}, ifo)); - - // SST CF unknown - ASSERT_OK(db_->IngestExternalFile(handles_[1], {unknown_sst}, ifo)); - // SST CF unknown - ASSERT_OK(db_->IngestExternalFile(handles_[2], {unknown_sst}, ifo)); - // SST CF unknown - ASSERT_OK(db_->IngestExternalFile(handles_[0], {unknown_sst}, ifo)); - - // Cannot ingest a file into a dropped CF - ASSERT_OK(db_->DropColumnFamily(handles_[1])); - ASSERT_NOK(db_->IngestExternalFile(handles_[1], {unknown_sst}, ifo)); - - // CF was not dropped, ok to Ingest - ASSERT_OK(db_->IngestExternalFile(handles_[2], {unknown_sst}, ifo)); -} - -/* - * Test and verify the functionality of ingestion_options.move_files and - * ingestion_options.failed_move_fall_back_to_copy - */ -TEST_P(ExternSSTFileLinkFailFallbackTest, LinkFailFallBackExternalSst) { - const bool fail_link = std::get<0>(GetParam()); - const bool failed_move_fall_back_to_copy = std::get<1>(GetParam()); - fs_->set_fail_link(fail_link); - const EnvOptions env_options; - DestroyAndReopen(options_); - const int kNumKeys = 10000; - IngestExternalFileOptions ifo; - ifo.move_files = true; - ifo.failed_move_fall_back_to_copy = failed_move_fall_back_to_copy; - - std::string file_path = sst_files_dir_ + "file1.sst"; - // Create SstFileWriter for default column family - SstFileWriter sst_file_writer(env_options, options_); - ASSERT_OK(sst_file_writer.Open(file_path)); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(sst_file_writer.Put(Key(i), Key(i) + "_value")); - } - ASSERT_OK(sst_file_writer.Finish()); - uint64_t file_size = 0; - ASSERT_OK(env_->GetFileSize(file_path, &file_size)); - - bool copyfile = false; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Prepare:CopyFile", - [&](void* /* arg */) { copyfile = true; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - const Status s = db_->IngestExternalFile({file_path}, ifo); - - ColumnFamilyHandleImpl* cfh = - static_cast(dbfull()->DefaultColumnFamily()); - ColumnFamilyData* cfd = cfh->cfd(); - const InternalStats* internal_stats_ptr = cfd->internal_stats(); - const std::vector& comp_stats = - internal_stats_ptr->TEST_GetCompactionStats(); - uint64_t bytes_copied = 0; - uint64_t bytes_moved = 0; - for (const auto& stats : comp_stats) { - bytes_copied += stats.bytes_written; - bytes_moved += stats.bytes_moved; - } - - if (!fail_link) { - // Link operation succeeds. External SST should be moved. - ASSERT_OK(s); - ASSERT_EQ(0, bytes_copied); - ASSERT_EQ(file_size, bytes_moved); - ASSERT_FALSE(copyfile); - } else { - // Link operation fails. - ASSERT_EQ(0, bytes_moved); - if (failed_move_fall_back_to_copy) { - ASSERT_OK(s); - // Copy file is true since a failed link falls back to copy file. - ASSERT_TRUE(copyfile); - ASSERT_EQ(file_size, bytes_copied); - } else { - ASSERT_TRUE(s.IsNotSupported()); - // Copy file is false since a failed link does not fall back to copy file. - ASSERT_FALSE(copyfile); - ASSERT_EQ(0, bytes_copied); - } - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -class TestIngestExternalFileListener : public EventListener { - public: - void OnExternalFileIngested(DB* /*db*/, - const ExternalFileIngestionInfo& info) override { - ingested_files.push_back(info); - } - - std::vector ingested_files; -}; - -TEST_P(ExternalSSTFileTest, IngestionListener) { - Options options = CurrentOptions(); - TestIngestExternalFileListener* listener = - new TestIngestExternalFileListener(); - options.listeners.emplace_back(listener); - CreateAndReopenWithCF({"koko", "toto"}, options); - - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - // Ingest into default cf - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 2}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, true, nullptr, handles_[0])); - ASSERT_EQ(listener->ingested_files.size(), 1); - ASSERT_EQ(listener->ingested_files.back().cf_name, "default"); - ASSERT_EQ(listener->ingested_files.back().global_seqno, 0); - ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_id, - 0); - ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_name, - "default"); - - // Ingest into cf1 - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 2}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, true, nullptr, handles_[1])); - ASSERT_EQ(listener->ingested_files.size(), 2); - ASSERT_EQ(listener->ingested_files.back().cf_name, "koko"); - ASSERT_EQ(listener->ingested_files.back().global_seqno, 0); - ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_id, - 1); - ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_name, - "koko"); - - // Ingest into cf2 - ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 2}, -1, true, write_global_seqno, - verify_checksums_before_ingest, false, true, nullptr, handles_[2])); - ASSERT_EQ(listener->ingested_files.size(), 3); - ASSERT_EQ(listener->ingested_files.back().cf_name, "toto"); - ASSERT_EQ(listener->ingested_files.back().global_seqno, 0); - ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_id, - 2); - ASSERT_EQ(listener->ingested_files.back().table_properties.column_family_name, - "toto"); -} - -TEST_F(ExternalSSTFileTest, SnapshotInconsistencyBug) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - const int kNumKeys = 10000; - - // Insert keys using normal path and take a snapshot - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(i), Key(i) + "_V1")); - } - const Snapshot* snap = db_->GetSnapshot(); - - // Overwrite all keys using IngestExternalFile - std::string sst_file_path = sst_files_dir_ + "file1.sst"; - SstFileWriter sst_file_writer(EnvOptions(), options); - ASSERT_OK(sst_file_writer.Open(sst_file_path)); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(sst_file_writer.Put(Key(i), Key(i) + "_V2")); - } - ASSERT_OK(sst_file_writer.Finish()); - - IngestExternalFileOptions ifo; - ifo.move_files = true; - ASSERT_OK(db_->IngestExternalFile({sst_file_path}, ifo)); - - for (int i = 0; i < kNumKeys; i++) { - ASSERT_EQ(Get(Key(i), snap), Key(i) + "_V1"); - ASSERT_EQ(Get(Key(i)), Key(i) + "_V2"); - } - - db_->ReleaseSnapshot(snap); -} - -TEST_P(ExternalSSTFileTest, IngestBehind) { - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = 3; - options.disable_auto_compactions = false; - DestroyAndReopen(options); - std::vector> file_data; - std::map true_data; - - // Insert 100 -> 200 into the memtable - for (int i = 100; i <= 200; i++) { - ASSERT_OK(Put(Key(i), "memtable")); - true_data[Key(i)] = "memtable"; - } - - // Insert 100 -> 200 using IngestExternalFile - file_data.clear(); - for (int i = 0; i <= 20; i++) { - file_data.emplace_back(Key(i), "ingest_behind"); - } - - bool allow_global_seqno = true; - bool ingest_behind = true; - bool write_global_seqno = std::get<0>(GetParam()); - bool verify_checksums_before_ingest = std::get<1>(GetParam()); - - // Can't ingest behind since allow_ingest_behind isn't set to true - ASSERT_NOK(GenerateAndAddExternalFile( - options, file_data, -1, allow_global_seqno, write_global_seqno, - verify_checksums_before_ingest, ingest_behind, false /*sort_data*/, - &true_data)); - - options.allow_ingest_behind = true; - // check that we still can open the DB, as num_levels should be - // sanitized to 3 - options.num_levels = 2; - DestroyAndReopen(options); - - options.num_levels = 3; - DestroyAndReopen(options); - // Insert 100 -> 200 into the memtable - for (int i = 100; i <= 200; i++) { - ASSERT_OK(Put(Key(i), "memtable")); - true_data[Key(i)] = "memtable"; - } - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // Universal picker should go at second from the bottom level - ASSERT_EQ("0,1", FilesPerLevel()); - ASSERT_OK(GenerateAndAddExternalFile( - options, file_data, -1, allow_global_seqno, write_global_seqno, - verify_checksums_before_ingest, true /*ingest_behind*/, - false /*sort_data*/, &true_data)); - ASSERT_EQ("0,1,1", FilesPerLevel()); - // this time ingest should fail as the file doesn't fit to the bottom level - ASSERT_NOK(GenerateAndAddExternalFile( - options, file_data, -1, allow_global_seqno, write_global_seqno, - verify_checksums_before_ingest, true /*ingest_behind*/, - false /*sort_data*/, &true_data)); - ASSERT_EQ("0,1,1", FilesPerLevel()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // bottom level should be empty - ASSERT_EQ("0,1", FilesPerLevel()); - - size_t kcnt = 0; - VerifyDBFromMap(true_data, &kcnt, false); -} - -TEST_F(ExternalSSTFileTest, SkipBloomFilter) { - Options options = CurrentOptions(); - - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - table_options.cache_index_and_filter_blocks = true; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - // Create external SST file and include bloom filters - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - { - std::string file_path = sst_files_dir_ + "sst_with_bloom.sst"; - SstFileWriter sst_file_writer(EnvOptions(), options); - ASSERT_OK(sst_file_writer.Open(file_path)); - ASSERT_OK(sst_file_writer.Put("Key1", "Value1")); - ASSERT_OK(sst_file_writer.Finish()); - - ASSERT_OK( - db_->IngestExternalFile({file_path}, IngestExternalFileOptions())); - - ASSERT_EQ(Get("Key1"), "Value1"); - ASSERT_GE( - options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 1); - } - - // Create external SST file but skip bloom filters - options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - DestroyAndReopen(options); - { - std::string file_path = sst_files_dir_ + "sst_with_no_bloom.sst"; - SstFileWriter sst_file_writer(EnvOptions(), options, nullptr, true, - Env::IOPriority::IO_TOTAL, - true /* skip_filters */); - ASSERT_OK(sst_file_writer.Open(file_path)); - ASSERT_OK(sst_file_writer.Put("Key1", "Value1")); - ASSERT_OK(sst_file_writer.Finish()); - - ASSERT_OK( - db_->IngestExternalFile({file_path}, IngestExternalFileOptions())); - - ASSERT_EQ(Get("Key1"), "Value1"); - ASSERT_EQ( - options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 0); - } -} - -TEST_F(ExternalSSTFileTest, IngestFileWrittenWithCompressionDictionary) { - if (!ZSTD_Supported()) { - return; - } - const int kNumEntries = 1 << 10; - const int kNumBytesPerEntry = 1 << 10; - Options options = CurrentOptions(); - options.compression = kZSTD; - options.compression_opts.max_dict_bytes = 1 << 14; // 16KB - options.compression_opts.zstd_max_train_bytes = 1 << 18; // 256KB - DestroyAndReopen(options); - - std::atomic num_compression_dicts(0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict", - [&](void* /* arg */) { ++num_compression_dicts; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - std::vector> random_data; - for (int i = 0; i < kNumEntries; i++) { - std::string val = rnd.RandomString(kNumBytesPerEntry); - random_data.emplace_back(Key(i), std::move(val)); - } - ASSERT_OK(GenerateAndAddExternalFile(options, std::move(random_data))); - ASSERT_EQ(1, num_compression_dicts); -} - -class ExternalSSTBlockChecksumTest - : public ExternalSSTFileTestBase, - public testing::WithParamInterface {}; - -INSTANTIATE_TEST_CASE_P(FormatVersions, ExternalSSTBlockChecksumTest, - testing::ValuesIn(test::kFooterFormatVersionsToTest)); - -// Very slow, not worth the cost to run regularly -TEST_P(ExternalSSTBlockChecksumTest, DISABLED_HugeBlockChecksum) { - BlockBasedTableOptions table_options; - table_options.format_version = GetParam(); - for (auto t : GetSupportedChecksums()) { - table_options.checksum = t; - Options options = CurrentOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - SstFileWriter sst_file_writer(EnvOptions(), options); - - // 2^32 - 1, will lead to data block with more than 2^32 bytes - size_t huge_size = std::numeric_limits::max(); - - std::string f = sst_files_dir_ + "f.sst"; - ASSERT_OK(sst_file_writer.Open(f)); - { - Random64 r(123); - std::string huge(huge_size, 0); - for (size_t j = 0; j + 7 < huge_size; j += 8) { - EncodeFixed64(&huge[j], r.Next()); - } - ASSERT_OK(sst_file_writer.Put("Huge", huge)); - } - - ExternalSstFileInfo f_info; - ASSERT_OK(sst_file_writer.Finish(&f_info)); - ASSERT_GT(f_info.file_size, uint64_t{huge_size} + 10); - - SstFileReader sst_file_reader(options); - ASSERT_OK(sst_file_reader.Open(f)); - ASSERT_OK(sst_file_reader.VerifyChecksum()); - } -} - -TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options = CurrentOptions(); - options.env = fault_injection_env.get(); - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - - // Exercise different situations in different column families: two are empty - // (so no new sequence number is needed), but at least one overlaps with the - // DB and needs to bump the sequence number. - ASSERT_OK(db_->Put(WriteOptions(), "foo1", "oldvalue")); - - std::vector column_families; - column_families.push_back(handles_[0]); - column_families.push_back(handles_[1]); - column_families.push_back(handles_[2]); - std::vector ifos(column_families.size()); - for (auto& ifo : ifos) { - ifo.allow_global_seqno = true; // Always allow global_seqno - // May or may not write global_seqno - ifo.write_global_seqno = std::get<0>(GetParam()); - // Whether to verify checksums before ingestion - ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); - } - std::vector>> data; - data.push_back( - {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); - data.push_back( - {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); - data.push_back( - {std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")}); - - // Resize the true_data vector upon construction to avoid re-alloc - std::vector> true_data( - column_families.size()); - ASSERT_OK(GenerateAndAddExternalFiles(options, column_families, ifos, data, - -1, true, true_data)); - Close(); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"}, - options); - ASSERT_EQ(3, handles_.size()); - int cf = 0; - for (const auto& verify_map : true_data) { - for (const auto& elem : verify_map) { - const std::string& key = elem.first; - const std::string& value = elem.second; - ASSERT_EQ(value, Get(cf, key)); - } - ++cf; - } - Close(); - Destroy(options, true /* delete_cf_paths */); -} - -TEST_P(ExternalSSTFileTest, - IngestFilesIntoMultipleColumnFamilies_NoMixedStateWithSnapshot) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::IngestExternalFiles:InstallSVForFirstCF:0", - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" - "BeforeRead"}, - {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" - "AfterRead", - "DBImpl::IngestExternalFiles:InstallSVForFirstCF:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Options options = CurrentOptions(); - options.env = fault_injection_env.get(); - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - const std::vector> data_before_ingestion = - {{{"foo1", "fv1_0"}, {"foo2", "fv2_0"}, {"foo3", "fv3_0"}}, - {{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}}, - {{"bar4", "bv4_0"}, {"bar5", "bv5_0"}, {"bar6", "bv6_0"}}}; - for (size_t i = 0; i != handles_.size(); ++i) { - int cf = static_cast(i); - const auto& orig_data = data_before_ingestion[i]; - for (const auto& kv : orig_data) { - ASSERT_OK(Put(cf, kv.first, kv.second)); - } - ASSERT_OK(Flush(cf)); - } - - std::vector column_families; - column_families.push_back(handles_[0]); - column_families.push_back(handles_[1]); - column_families.push_back(handles_[2]); - std::vector ifos(column_families.size()); - for (auto& ifo : ifos) { - ifo.allow_global_seqno = true; // Always allow global_seqno - // May or may not write global_seqno - ifo.write_global_seqno = std::get<0>(GetParam()); - // Whether to verify checksums before ingestion - ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); - } - std::vector>> data; - data.push_back( - {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); - data.push_back( - {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); - data.push_back( - {std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")}); - // Resize the true_data vector upon construction to avoid re-alloc - std::vector> true_data( - column_families.size()); - // Take snapshot before ingestion starts - ReadOptions read_opts; - read_opts.total_order_seek = true; - read_opts.snapshot = dbfull()->GetSnapshot(); - std::vector iters(handles_.size()); - - // Range scan checks first kv of each CF before ingestion starts. - for (size_t i = 0; i != handles_.size(); ++i) { - iters[i] = dbfull()->NewIterator(read_opts, handles_[i]); - iters[i]->SeekToFirst(); - ASSERT_TRUE(iters[i]->Valid()); - const std::string& key = iters[i]->key().ToString(); - const std::string& value = iters[i]->value().ToString(); - const std::map& orig_data = - data_before_ingestion[i]; - std::map::const_iterator it = orig_data.find(key); - ASSERT_NE(orig_data.end(), it); - ASSERT_EQ(it->second, value); - iters[i]->Next(); - } - port::Thread ingest_thread([&]() { - ASSERT_OK(GenerateAndAddExternalFiles(options, column_families, ifos, data, - -1, true, true_data)); - }); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" - "BeforeRead"); - // Should see only data before ingestion - for (size_t i = 0; i != handles_.size(); ++i) { - const auto& orig_data = data_before_ingestion[i]; - for (; iters[i]->Valid(); iters[i]->Next()) { - const std::string& key = iters[i]->key().ToString(); - const std::string& value = iters[i]->value().ToString(); - std::map::const_iterator it = - orig_data.find(key); - ASSERT_NE(orig_data.end(), it); - ASSERT_EQ(it->second, value); - } - } - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" - "AfterRead"); - ingest_thread.join(); - for (auto* iter : iters) { - delete iter; - } - iters.clear(); - dbfull()->ReleaseSnapshot(read_opts.snapshot); - - Close(); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"}, - options); - // Should see consistent state after ingestion for all column families even - // without snapshot. - ASSERT_EQ(3, handles_.size()); - int cf = 0; - for (const auto& verify_map : true_data) { - for (const auto& elem : verify_map) { - const std::string& key = elem.first; - const std::string& value = elem.second; - ASSERT_EQ(value, Get(cf, key)); - } - ++cf; - } - Close(); - Destroy(options, true /* delete_cf_paths */); -} - -TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options = CurrentOptions(); - options.env = fault_injection_env.get(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::IngestExternalFiles:BeforeLastJobPrepare:0", - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_PrepareFail:" - "0"}, - {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies:PrepareFail:" - "1", - "DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - std::vector column_families; - column_families.push_back(handles_[0]); - column_families.push_back(handles_[1]); - column_families.push_back(handles_[2]); - std::vector ifos(column_families.size()); - for (auto& ifo : ifos) { - ifo.allow_global_seqno = true; // Always allow global_seqno - // May or may not write global_seqno - ifo.write_global_seqno = std::get<0>(GetParam()); - // Whether to verify block checksums before ingest - ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); - } - std::vector>> data; - data.push_back( - {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); - data.push_back( - {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); - data.push_back( - {std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")}); - - // Resize the true_data vector upon construction to avoid re-alloc - std::vector> true_data( - column_families.size()); - port::Thread ingest_thread([&]() { - ASSERT_NOK(GenerateAndAddExternalFiles(options, column_families, ifos, data, - -1, true, true_data)); - }); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_PrepareFail:" - "0"); - fault_injection_env->SetFilesystemActive(false); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies:PrepareFail:" - "1"); - ingest_thread.join(); - - fault_injection_env->SetFilesystemActive(true); - Close(); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"}, - options); - ASSERT_EQ(3, handles_.size()); - int cf = 0; - for (const auto& verify_map : true_data) { - for (const auto& elem : verify_map) { - const std::string& key = elem.first; - ASSERT_EQ("NOT_FOUND", Get(cf, key)); - } - ++cf; - } - Close(); - Destroy(options, true /* delete_cf_paths */); -} - -TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options = CurrentOptions(); - options.env = fault_injection_env.get(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::IngestExternalFiles:BeforeJobsRun:0", - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" - "0"}, - {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" - "1", - "DBImpl::IngestExternalFiles:BeforeJobsRun:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - std::vector column_families; - column_families.push_back(handles_[0]); - column_families.push_back(handles_[1]); - column_families.push_back(handles_[2]); - std::vector ifos(column_families.size()); - for (auto& ifo : ifos) { - ifo.allow_global_seqno = true; // Always allow global_seqno - // May or may not write global_seqno - ifo.write_global_seqno = std::get<0>(GetParam()); - // Whether to verify block checksums before ingestion - ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); - } - std::vector>> data; - data.push_back( - {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); - data.push_back( - {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); - data.push_back( - {std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")}); - // Resize the true_data vector upon construction to avoid re-alloc - std::vector> true_data( - column_families.size()); - port::Thread ingest_thread([&]() { - ASSERT_NOK(GenerateAndAddExternalFiles(options, column_families, ifos, data, - -1, true, true_data)); - }); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" - "0"); - fault_injection_env->SetFilesystemActive(false); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" - "1"); - ingest_thread.join(); - - fault_injection_env->SetFilesystemActive(true); - Close(); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"}, - options); - ASSERT_EQ(3, handles_.size()); - int cf = 0; - for (const auto& verify_map : true_data) { - for (const auto& elem : verify_map) { - const std::string& key = elem.first; - ASSERT_EQ("NOT_FOUND", Get(cf, key)); - } - ++cf; - } - Close(); - Destroy(options, true /* delete_cf_paths */); -} - -TEST_P(ExternalSSTFileTest, - IngestFilesIntoMultipleColumnFamilies_PartialManifestWriteFail) { - std::unique_ptr fault_injection_env( - new FaultInjectionTestEnv(env_)); - Options options = CurrentOptions(); - options.env = fault_injection_env.get(); - - CreateAndReopenWithCF({"pikachu", "eevee"}, options); - - SyncPoint::GetInstance()->ClearTrace(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->LoadDependency({ - {"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0", - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" - "PartialManifestWriteFail:0"}, - {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" - "PartialManifestWriteFail:1", - "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - std::vector column_families; - column_families.push_back(handles_[0]); - column_families.push_back(handles_[1]); - column_families.push_back(handles_[2]); - std::vector ifos(column_families.size()); - for (auto& ifo : ifos) { - ifo.allow_global_seqno = true; // Always allow global_seqno - // May or may not write global_seqno - ifo.write_global_seqno = std::get<0>(GetParam()); - // Whether to verify block checksums before ingestion - ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); - } - std::vector>> data; - data.push_back( - {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); - data.push_back( - {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); - data.push_back( - {std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")}); - // Resize the true_data vector upon construction to avoid re-alloc - std::vector> true_data( - column_families.size()); - port::Thread ingest_thread([&]() { - ASSERT_NOK(GenerateAndAddExternalFiles(options, column_families, ifos, data, - -1, true, true_data)); - }); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" - "PartialManifestWriteFail:0"); - fault_injection_env->SetFilesystemActive(false); - TEST_SYNC_POINT( - "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" - "PartialManifestWriteFail:1"); - ingest_thread.join(); - - ASSERT_OK(fault_injection_env->DropUnsyncedFileData()); - fault_injection_env->SetFilesystemActive(true); - Close(); - ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"}, - options); - ASSERT_EQ(3, handles_.size()); - int cf = 0; - for (const auto& verify_map : true_data) { - for (const auto& elem : verify_map) { - const std::string& key = elem.first; - ASSERT_EQ("NOT_FOUND", Get(cf, key)); - } - ++cf; - } - Close(); - Destroy(options, true /* delete_cf_paths */); -} - -TEST_P(ExternalSSTFileTest, IngestFilesTriggerFlushingWithTwoWriteQueue) { - Options options = CurrentOptions(); - // Use large buffer to avoid memtable flush - options.write_buffer_size = 1024 * 1024; - options.two_write_queues = true; - DestroyAndReopen(options); - - ASSERT_OK(dbfull()->Put(WriteOptions(), "1000", "v1")); - ASSERT_OK(dbfull()->Put(WriteOptions(), "1001", "v1")); - ASSERT_OK(dbfull()->Put(WriteOptions(), "9999", "v1")); - - // Put one key which is overlap with keys in memtable. - // It will trigger flushing memtable and require this thread is - // currently at the front of the 2nd writer queue. We must make - // sure that it won't enter the 2nd writer queue for the second time. - std::vector> data; - data.push_back(std::make_pair("1001", "v2")); - ASSERT_OK(GenerateAndAddExternalFile(options, data, -1, true)); -} - -TEST_P(ExternalSSTFileTest, DeltaEncodingWhileGlobalSeqnoPresent) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - constexpr size_t kValueSize = 8; - Random rnd(301); - std::string value = rnd.RandomString(kValueSize); - - // Write some key to make global seqno larger than zero - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put("ab" + Key(i), value)); - } - // Get a Snapshot to make RocksDB assign global seqno to ingested sst files. - auto snap = dbfull()->GetSnapshot(); - - std::string fname = sst_files_dir_ + "test_file"; - ROCKSDB_NAMESPACE::SstFileWriter writer(EnvOptions(), options); - ASSERT_OK(writer.Open(fname)); - std::string key1 = "ab"; - std::string key2 = "ab"; - - // Make the prefix of key2 is same with key1 add zero seqno. The tail of every - // key is composed as (seqno << 8 | value_type), and here `1` represents - // ValueType::kTypeValue - - PutFixed64(&key2, PackSequenceAndType(0, kTypeValue)); - key2 += "cdefghijkl"; - - ASSERT_OK(writer.Put(key1, value)); - ASSERT_OK(writer.Put(key2, value)); - - ExternalSstFileInfo info; - ASSERT_OK(writer.Finish(&info)); - - ASSERT_OK(dbfull()->IngestExternalFile({info.file_path}, - IngestExternalFileOptions())); - dbfull()->ReleaseSnapshot(snap); - ASSERT_EQ(value, Get(key1)); - // You will get error here - ASSERT_EQ(value, Get(key2)); -} - -TEST_P(ExternalSSTFileTest, - DeltaEncodingWhileGlobalSeqnoPresentIteratorSwitch) { - // Regression test for bug where global seqno corrupted the shared bytes - // buffer when switching from reverse iteration to forward iteration. - constexpr size_t kValueSize = 8; - Options options = CurrentOptions(); - - Random rnd(301); - std::string value = rnd.RandomString(kValueSize); - - std::string key0 = "aa"; - std::string key1 = "ab"; - // Make the prefix of key2 is same with key1 add zero seqno. The tail of every - // key is composed as (seqno << 8 | value_type), and here `1` represents - // ValueType::kTypeValue - std::string key2 = "ab"; - PutFixed64(&key2, PackSequenceAndType(0, kTypeValue)); - key2 += "cdefghijkl"; - std::string key3 = key2 + "_"; - - // Write some key to make global seqno larger than zero - ASSERT_OK(Put(key0, value)); - - std::string fname = sst_files_dir_ + "test_file"; - ROCKSDB_NAMESPACE::SstFileWriter writer(EnvOptions(), options); - ASSERT_OK(writer.Open(fname)); - - // key0 is a dummy to ensure the turnaround point (key1) comes from Prev - // cache rather than block (restart keys are pinned in block). - ASSERT_OK(writer.Put(key0, value)); - ASSERT_OK(writer.Put(key1, value)); - ASSERT_OK(writer.Put(key2, value)); - ASSERT_OK(writer.Put(key3, value)); - - ExternalSstFileInfo info; - ASSERT_OK(writer.Finish(&info)); - - ASSERT_OK(dbfull()->IngestExternalFile({info.file_path}, - IngestExternalFileOptions())); - ReadOptions read_opts; - // Prevents Seek() when switching directions, which circumvents the bug. - read_opts.total_order_seek = true; - Iterator* iter = db_->NewIterator(read_opts); - // Scan backwards to key2. File iterator will then be positioned at key1. - iter->Seek(key3); - ASSERT_EQ(key3, iter->key()); - iter->Prev(); - ASSERT_EQ(key2, iter->key()); - // Scan forwards and make sure key3 is present. Previously key3 would be - // corrupted by the global seqno from key1. - iter->Next(); - ASSERT_EQ(key3, iter->key()); - delete iter; -} - -INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest, - testing::Values(std::make_tuple(false, false), - std::make_tuple(false, true), - std::make_tuple(true, false), - std::make_tuple(true, true))); - -INSTANTIATE_TEST_CASE_P(ExternSSTFileLinkFailFallbackTest, - ExternSSTFileLinkFailFallbackTest, - testing::Values(std::make_tuple(true, false), - std::make_tuple(true, true), - std::make_tuple(false, false))); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/fault_injection_test.cc b/db/fault_injection_test.cc deleted file mode 100644 index ddd4b47cc..000000000 --- a/db/fault_injection_test.cc +++ /dev/null @@ -1,637 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright 2014 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -// This test uses a custom Env to keep track of the state of a filesystem as of -// the last "sync". It then checks for data loss errors by purposely dropping -// file data (or entire files) not protected by a "sync". - -#include "db/db_impl/db_impl.h" -#include "db/log_format.h" -#include "db/version_set.h" -#include "env/mock_env.h" -#include "file/filename.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/table.h" -#include "rocksdb/write_batch.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/mutexlock.h" -#include "util/random.h" -#include "utilities/fault_injection_env.h" -#ifndef NDEBUG -#include "utilities/fault_injection_fs.h" -#endif - -namespace ROCKSDB_NAMESPACE { - -static const int kValueSize = 1000; -static const int kMaxNumValues = 2000; -static const size_t kNumIterations = 3; - -enum FaultInjectionOptionConfig { - kDefault, - kDifferentDataDir, - kWalDir, - kSyncWal, - kWalDirSyncWal, - kMultiLevels, - kEnd, -}; -class FaultInjectionTest - : public testing::Test, - public testing::WithParamInterface> { - protected: - int option_config_; - int non_inclusive_end_range_; // kEnd or equivalent to that - // When need to make sure data is persistent, sync WAL - bool sync_use_wal_; - // When need to make sure data is persistent, call DB::CompactRange() - bool sync_use_compact_; - - bool sequential_order_; - - public: - enum ExpectedVerifResult { kValExpectFound, kValExpectNoError }; - enum ResetMethod { - kResetDropUnsyncedData, - kResetDropRandomUnsyncedData, - kResetDeleteUnsyncedFiles, - kResetDropAndDeleteUnsynced - }; - - std::unique_ptr base_env_; - FaultInjectionTestEnv* env_; - std::string dbname_; - std::shared_ptr tiny_cache_; - Options options_; - DB* db_; - - FaultInjectionTest() - : option_config_(std::get<1>(GetParam())), - non_inclusive_end_range_(std::get<2>(GetParam())), - sync_use_wal_(false), - sync_use_compact_(true), - base_env_(nullptr), - env_(nullptr), - db_(nullptr) { - EXPECT_OK( - test::CreateEnvFromSystem(ConfigOptions(), &system_env_, &env_guard_)); - EXPECT_NE(system_env_, nullptr); - } - - ~FaultInjectionTest() override { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - } - - bool ChangeOptions() { - option_config_++; - if (option_config_ >= non_inclusive_end_range_) { - return false; - } else { - if (option_config_ == kMultiLevels) { - base_env_.reset(MockEnv::Create(system_env_)); - } - return true; - } - } - - // Return the current option configuration. - Options CurrentOptions() { - sync_use_wal_ = false; - sync_use_compact_ = true; - Options options; - switch (option_config_) { - case kWalDir: - options.wal_dir = test::PerThreadDBPath(env_, "fault_test_wal"); - break; - case kDifferentDataDir: - options.db_paths.emplace_back( - test::PerThreadDBPath(env_, "fault_test_data"), 1000000U); - break; - case kSyncWal: - sync_use_wal_ = true; - sync_use_compact_ = false; - break; - case kWalDirSyncWal: - options.wal_dir = test::PerThreadDBPath(env_, "/fault_test_wal"); - sync_use_wal_ = true; - sync_use_compact_ = false; - break; - case kMultiLevels: - options.write_buffer_size = 64 * 1024; - options.target_file_size_base = 64 * 1024; - options.level0_file_num_compaction_trigger = 2; - options.level0_slowdown_writes_trigger = 2; - options.level0_stop_writes_trigger = 4; - options.max_bytes_for_level_base = 128 * 1024; - options.max_write_buffer_number = 2; - options.max_background_compactions = 8; - options.max_background_flushes = 8; - sync_use_wal_ = true; - sync_use_compact_ = false; - break; - default: - break; - } - return options; - } - - Status NewDB() { - assert(db_ == nullptr); - assert(tiny_cache_ == nullptr); - assert(env_ == nullptr); - - env_ = new FaultInjectionTestEnv(base_env_ ? base_env_.get() : system_env_); - - options_ = CurrentOptions(); - options_.env = env_; - options_.paranoid_checks = true; - - BlockBasedTableOptions table_options; - tiny_cache_ = NewLRUCache(100); - table_options.block_cache = tiny_cache_; - options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - dbname_ = test::PerThreadDBPath("fault_test"); - - EXPECT_OK(DestroyDB(dbname_, options_)); - - options_.create_if_missing = true; - Status s = OpenDB(); - options_.create_if_missing = false; - return s; - } - - void SetUp() override { - sequential_order_ = std::get<0>(GetParam()); - ASSERT_OK(NewDB()); - } - - void TearDown() override { - CloseDB(); - - Status s = DestroyDB(dbname_, options_); - - delete env_; - env_ = nullptr; - - tiny_cache_.reset(); - - ASSERT_OK(s); - } - - void Build(const WriteOptions& write_options, int start_idx, int num_vals) { - std::string key_space, value_space; - WriteBatch batch; - for (int i = start_idx; i < start_idx + num_vals; i++) { - Slice key = Key(i, &key_space); - batch.Clear(); - ASSERT_OK(batch.Put(key, Value(i, &value_space))); - ASSERT_OK(db_->Write(write_options, &batch)); - } - } - - Status ReadValue(int i, std::string* val) const { - std::string key_space, value_space; - Slice key = Key(i, &key_space); - Value(i, &value_space); - ReadOptions options; - return db_->Get(options, key, val); - } - - Status Verify(int start_idx, int num_vals, - ExpectedVerifResult expected) const { - std::string val; - std::string value_space; - Status s; - for (int i = start_idx; i < start_idx + num_vals && s.ok(); i++) { - Value(i, &value_space); - s = ReadValue(i, &val); - if (s.ok()) { - EXPECT_EQ(value_space, val); - } - if (expected == kValExpectFound) { - if (!s.ok()) { - fprintf(stderr, "Error when read %dth record (expect found): %s\n", i, - s.ToString().c_str()); - return s; - } - } else if (!s.ok() && !s.IsNotFound()) { - fprintf(stderr, "Error when read %dth record: %s\n", i, - s.ToString().c_str()); - return s; - } - } - return Status::OK(); - } - - // Return the ith key - Slice Key(int i, std::string* storage) const { - unsigned long long num = i; - if (!sequential_order_) { - // random transfer - const int m = 0x5bd1e995; - num *= m; - num ^= num << 24; - } - char buf[100]; - snprintf(buf, sizeof(buf), "%016d", static_cast(num)); - storage->assign(buf, strlen(buf)); - return Slice(*storage); - } - - // Return the value to associate with the specified key - Slice Value(int k, std::string* storage) const { - Random r(k); - *storage = r.RandomString(kValueSize); - return Slice(*storage); - } - - void CloseDB() { - delete db_; - db_ = nullptr; - } - - Status OpenDB() { - CloseDB(); - env_->ResetState(); - Status s = DB::Open(options_, dbname_, &db_); - assert(db_ != nullptr); - return s; - } - - void DeleteAllData() { - Iterator* iter = db_->NewIterator(ReadOptions()); - WriteOptions options; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(db_->Delete(WriteOptions(), iter->key())); - } - ASSERT_OK(iter->status()); - delete iter; - - FlushOptions flush_options; - flush_options.wait = true; - ASSERT_OK(db_->Flush(flush_options)); - } - - // rnd cannot be null for kResetDropRandomUnsyncedData - void ResetDBState(ResetMethod reset_method, Random* rnd = nullptr) { - env_->AssertNoOpenFile(); - switch (reset_method) { - case kResetDropUnsyncedData: - ASSERT_OK(env_->DropUnsyncedFileData()); - break; - case kResetDropRandomUnsyncedData: - ASSERT_OK(env_->DropRandomUnsyncedFileData(rnd)); - break; - case kResetDeleteUnsyncedFiles: - ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync()); - break; - case kResetDropAndDeleteUnsynced: - ASSERT_OK(env_->DropUnsyncedFileData()); - ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync()); - break; - default: - assert(false); - } - } - - void PartialCompactTestPreFault(int num_pre_sync, int num_post_sync) { - DeleteAllData(); - - WriteOptions write_options; - write_options.sync = sync_use_wal_; - - Build(write_options, 0, num_pre_sync); - if (sync_use_compact_) { - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - write_options.sync = false; - Build(write_options, num_pre_sync, num_post_sync); - } - - void PartialCompactTestReopenWithFault(ResetMethod reset_method, - int num_pre_sync, int num_post_sync, - Random* rnd = nullptr) { - env_->SetFilesystemActive(false); - CloseDB(); - ResetDBState(reset_method, rnd); - ASSERT_OK(OpenDB()); - ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::kValExpectFound)); - ASSERT_OK(Verify(num_pre_sync, num_post_sync, - FaultInjectionTest::kValExpectNoError)); - WaitCompactionFinish(); - ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::kValExpectFound)); - ASSERT_OK(Verify(num_pre_sync, num_post_sync, - FaultInjectionTest::kValExpectNoError)); - } - - void NoWriteTestPreFault() {} - - void NoWriteTestReopenWithFault(ResetMethod reset_method) { - CloseDB(); - ResetDBState(reset_method); - ASSERT_OK(OpenDB()); - } - - void WaitCompactionFinish() { - ASSERT_OK(static_cast(db_->GetRootDB())->TEST_WaitForCompact()); - ASSERT_OK(db_->Put(WriteOptions(), "", "")); - } - - private: - Env* system_env_; - std::shared_ptr env_guard_; -}; - -class FaultInjectionTestSplitted : public FaultInjectionTest {}; - -TEST_P(FaultInjectionTestSplitted, FaultTest) { - do { - Random rnd(301); - - for (size_t idx = 0; idx < kNumIterations; idx++) { - int num_pre_sync = rnd.Uniform(kMaxNumValues); - int num_post_sync = rnd.Uniform(kMaxNumValues); - - PartialCompactTestPreFault(num_pre_sync, num_post_sync); - PartialCompactTestReopenWithFault(kResetDropUnsyncedData, num_pre_sync, - num_post_sync); - NoWriteTestPreFault(); - NoWriteTestReopenWithFault(kResetDropUnsyncedData); - - PartialCompactTestPreFault(num_pre_sync, num_post_sync); - PartialCompactTestReopenWithFault(kResetDropRandomUnsyncedData, - num_pre_sync, num_post_sync, &rnd); - NoWriteTestPreFault(); - NoWriteTestReopenWithFault(kResetDropUnsyncedData); - - // Setting a separate data path won't pass the test as we don't sync - // it after creating new files, - PartialCompactTestPreFault(num_pre_sync, num_post_sync); - PartialCompactTestReopenWithFault(kResetDropAndDeleteUnsynced, - num_pre_sync, num_post_sync); - NoWriteTestPreFault(); - NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced); - - PartialCompactTestPreFault(num_pre_sync, num_post_sync); - // No new files created so we expect all values since no files will be - // dropped. - PartialCompactTestReopenWithFault(kResetDeleteUnsyncedFiles, num_pre_sync, - num_post_sync); - NoWriteTestPreFault(); - NoWriteTestReopenWithFault(kResetDeleteUnsyncedFiles); - } - } while (ChangeOptions()); -} - -// Previous log file is not fsynced if sync is forced after log rolling. -TEST_P(FaultInjectionTest, WriteOptionSyncTest) { - test::SleepingBackgroundTask sleeping_task_low; - env_->SetBackgroundThreads(1, Env::HIGH); - // Block the job queue to prevent flush job from running. - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::HIGH); - sleeping_task_low.WaitUntilSleeping(); - - WriteOptions write_options; - write_options.sync = false; - - std::string key_space, value_space; - ASSERT_OK( - db_->Put(write_options, Key(1, &key_space), Value(1, &value_space))); - FlushOptions flush_options; - flush_options.wait = false; - ASSERT_OK(db_->Flush(flush_options)); - write_options.sync = true; - ASSERT_OK( - db_->Put(write_options, Key(2, &key_space), Value(2, &value_space))); - ASSERT_OK(db_->FlushWAL(false)); - - env_->SetFilesystemActive(false); - NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced); - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - - ASSERT_OK(OpenDB()); - std::string val; - Value(2, &value_space); - ASSERT_OK(ReadValue(2, &val)); - ASSERT_EQ(value_space, val); - - Value(1, &value_space); - ASSERT_OK(ReadValue(1, &val)); - ASSERT_EQ(value_space, val); -} - -TEST_P(FaultInjectionTest, UninstalledCompaction) { - options_.target_file_size_base = 32 * 1024; - options_.write_buffer_size = 100 << 10; // 100KB - options_.level0_file_num_compaction_trigger = 6; - options_.level0_stop_writes_trigger = 1 << 10; - options_.level0_slowdown_writes_trigger = 1 << 10; - options_.max_background_compactions = 1; - OpenDB(); - - if (!sequential_order_) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"FaultInjectionTest::FaultTest:0", "DBImpl::BGWorkCompaction"}, - {"CompactionJob::Run():End", "FaultInjectionTest::FaultTest:1"}, - {"FaultInjectionTest::FaultTest:2", - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}, - }); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - int kNumKeys = 1000; - Build(WriteOptions(), 0, kNumKeys); - FlushOptions flush_options; - flush_options.wait = true; - ASSERT_OK(db_->Flush(flush_options)); - ASSERT_OK(db_->Put(WriteOptions(), "", "")); - TEST_SYNC_POINT("FaultInjectionTest::FaultTest:0"); - TEST_SYNC_POINT("FaultInjectionTest::FaultTest:1"); - env_->SetFilesystemActive(false); - TEST_SYNC_POINT("FaultInjectionTest::FaultTest:2"); - CloseDB(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ResetDBState(kResetDropUnsyncedData); - - std::atomic opened(false); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::Open:Opened", [&](void* /*arg*/) { opened.store(true); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkCompaction", - [&](void* /*arg*/) { ASSERT_TRUE(opened.load()); }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(OpenDB()); - ASSERT_OK(Verify(0, kNumKeys, FaultInjectionTest::kValExpectFound)); - WaitCompactionFinish(); - ASSERT_OK(Verify(0, kNumKeys, FaultInjectionTest::kValExpectFound)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_P(FaultInjectionTest, ManualLogSyncTest) { - test::SleepingBackgroundTask sleeping_task_low; - env_->SetBackgroundThreads(1, Env::HIGH); - // Block the job queue to prevent flush job from running. - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, - Env::Priority::HIGH); - sleeping_task_low.WaitUntilSleeping(); - - WriteOptions write_options; - write_options.sync = false; - - std::string key_space, value_space; - ASSERT_OK( - db_->Put(write_options, Key(1, &key_space), Value(1, &value_space))); - FlushOptions flush_options; - flush_options.wait = false; - ASSERT_OK(db_->Flush(flush_options)); - ASSERT_OK( - db_->Put(write_options, Key(2, &key_space), Value(2, &value_space))); - ASSERT_OK(db_->FlushWAL(true)); - - env_->SetFilesystemActive(false); - NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced); - sleeping_task_low.WakeUp(); - sleeping_task_low.WaitUntilDone(); - - ASSERT_OK(OpenDB()); - std::string val; - Value(2, &value_space); - ASSERT_OK(ReadValue(2, &val)); - ASSERT_EQ(value_space, val); - - Value(1, &value_space); - ASSERT_OK(ReadValue(1, &val)); - ASSERT_EQ(value_space, val); -} - -TEST_P(FaultInjectionTest, WriteBatchWalTerminationTest) { - ReadOptions ro; - Options options = CurrentOptions(); - options.env = env_; - - WriteOptions wo; - wo.sync = true; - wo.disableWAL = false; - WriteBatch batch; - ASSERT_OK(batch.Put("cats", "dogs")); - batch.MarkWalTerminationPoint(); - ASSERT_OK(batch.Put("boys", "girls")); - ASSERT_OK(db_->Write(wo, &batch)); - - env_->SetFilesystemActive(false); - NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced); - ASSERT_OK(OpenDB()); - - std::string val; - ASSERT_OK(db_->Get(ro, "cats", &val)); - ASSERT_EQ("dogs", val); - ASSERT_EQ(db_->Get(ro, "boys", &val), Status::NotFound()); -} - -TEST_P(FaultInjectionTest, NoDuplicateTrailingEntries) { - auto fault_fs = std::make_shared(FileSystem::Default()); - fault_fs->EnableWriteErrorInjection(); - fault_fs->SetFilesystemDirectWritable(false); - const std::string file_name = NormalizePath(dbname_ + "/test_file"); - std::unique_ptr log_writer = nullptr; - constexpr uint64_t log_number = 0; - { - std::unique_ptr file; - const Status s = - fault_fs->NewWritableFile(file_name, FileOptions(), &file, nullptr); - ASSERT_OK(s); - std::unique_ptr fwriter( - new WritableFileWriter(std::move(file), file_name, FileOptions())); - log_writer.reset(new log::Writer(std::move(fwriter), log_number, - /*recycle_log_files=*/false)); - } - - fault_fs->SetRandomWriteError( - 0xdeadbeef, /*one_in=*/1, IOStatus::IOError("Injected IOError"), - /*inject_for_all_file_types=*/true, /*types=*/{}); - - { - VersionEdit edit; - edit.SetColumnFamily(0); - std::string buf; - assert(edit.EncodeTo(&buf)); - const Status s = log_writer->AddRecord(buf); - ASSERT_NOK(s); - } - - fault_fs->DisableWriteErrorInjection(); - - // Closing the log writer will cause WritableFileWriter::Close() and flush - // remaining data from its buffer to underlying file. - log_writer.reset(); - - { - std::unique_ptr file; - Status s = - fault_fs->NewSequentialFile(file_name, FileOptions(), &file, nullptr); - ASSERT_OK(s); - std::unique_ptr freader( - new SequentialFileReader(std::move(file), file_name)); - Status log_read_s; - class LogReporter : public log::Reader::Reporter { - public: - Status* status_; - explicit LogReporter(Status* _s) : status_(_s) {} - void Corruption(size_t /*bytes*/, const Status& _s) override { - if (status_->ok()) { - *status_ = _s; - } - } - } reporter(&log_read_s); - std::unique_ptr log_reader(new log::Reader( - nullptr, std::move(freader), &reporter, /*checksum=*/true, log_number)); - Slice record; - std::string data; - size_t count = 0; - while (log_reader->ReadRecord(&record, &data) && log_read_s.ok()) { - VersionEdit edit; - ASSERT_OK(edit.DecodeFrom(data)); - ++count; - } - // Verify that only one version edit exists in the file. - ASSERT_EQ(1, count); - } -} - -INSTANTIATE_TEST_CASE_P( - FaultTest, FaultInjectionTest, - ::testing::Values(std::make_tuple(false, kDefault, kEnd), - std::make_tuple(true, kDefault, kEnd))); - -INSTANTIATE_TEST_CASE_P( - FaultTest, FaultInjectionTestSplitted, - ::testing::Values(std::make_tuple(false, kDefault, kSyncWal), - std::make_tuple(true, kDefault, kSyncWal), - std::make_tuple(false, kSyncWal, kEnd), - std::make_tuple(true, kSyncWal, kEnd))); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/file_indexer_test.cc b/db/file_indexer_test.cc deleted file mode 100644 index 5c82189ef..000000000 --- a/db/file_indexer_test.cc +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/file_indexer.h" - -#include - -#include "db/dbformat.h" -#include "db/version_edit.h" -#include "port/stack_trace.h" -#include "rocksdb/comparator.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -class IntComparator : public Comparator { - public: - int Compare(const Slice& a, const Slice& b) const override { - assert(a.size() == 8); - assert(b.size() == 8); - int64_t diff = *reinterpret_cast(a.data()) - - *reinterpret_cast(b.data()); - if (diff < 0) { - return -1; - } else if (diff == 0) { - return 0; - } else { - return 1; - } - } - - const char* Name() const override { return "IntComparator"; } - - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; - -class FileIndexerTest : public testing::Test { - public: - FileIndexerTest() - : kNumLevels(4), files(new std::vector[kNumLevels]) {} - - ~FileIndexerTest() override { - ClearFiles(); - delete[] files; - } - - void AddFile(int level, int64_t smallest, int64_t largest) { - auto* f = new FileMetaData(); - f->smallest = IntKey(smallest); - f->largest = IntKey(largest); - files[level].push_back(f); - } - - InternalKey IntKey(int64_t v) { - return InternalKey(Slice(reinterpret_cast(&v), 8), 0, kTypeValue); - } - - void ClearFiles() { - for (uint32_t i = 0; i < kNumLevels; ++i) { - for (auto* f : files[i]) { - delete f; - } - files[i].clear(); - } - } - - void GetNextLevelIndex(const uint32_t level, const uint32_t file_index, - const int cmp_smallest, const int cmp_largest, - int32_t* left_index, int32_t* right_index) { - *left_index = 100; - *right_index = 100; - indexer->GetNextLevelIndex(level, file_index, cmp_smallest, cmp_largest, - left_index, right_index); - } - - int32_t left = 100; - int32_t right = 100; - const uint32_t kNumLevels; - IntComparator ucmp; - FileIndexer* indexer; - - std::vector* files; -}; - -// Case 0: Empty -TEST_F(FileIndexerTest, Empty) { - Arena arena; - indexer = new FileIndexer(&ucmp); - indexer->UpdateIndex(&arena, 0, files); - delete indexer; -} - -// Case 1: no overlap, files are on the left of next level files -TEST_F(FileIndexerTest, no_overlap_left) { - Arena arena; - indexer = new FileIndexer(&ucmp); - // level 1 - AddFile(1, 100, 200); - AddFile(1, 300, 400); - AddFile(1, 500, 600); - // level 2 - AddFile(2, 1500, 1600); - AddFile(2, 1601, 1699); - AddFile(2, 1700, 1800); - // level 3 - AddFile(3, 2500, 2600); - AddFile(3, 2601, 2699); - AddFile(3, 2700, 2800); - indexer->UpdateIndex(&arena, kNumLevels, files); - for (uint32_t level = 1; level < 3; ++level) { - for (uint32_t f = 0; f < 3; ++f) { - GetNextLevelIndex(level, f, -1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(level, f, 0, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(level, f, 1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(level, f, 1, 0, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(level, f, 1, 1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(2, right); - } - } - delete indexer; - ClearFiles(); -} - -// Case 2: no overlap, files are on the right of next level files -TEST_F(FileIndexerTest, no_overlap_right) { - Arena arena; - indexer = new FileIndexer(&ucmp); - // level 1 - AddFile(1, 2100, 2200); - AddFile(1, 2300, 2400); - AddFile(1, 2500, 2600); - // level 2 - AddFile(2, 1500, 1600); - AddFile(2, 1501, 1699); - AddFile(2, 1700, 1800); - // level 3 - AddFile(3, 500, 600); - AddFile(3, 501, 699); - AddFile(3, 700, 800); - indexer->UpdateIndex(&arena, kNumLevels, files); - for (uint32_t level = 1; level < 3; ++level) { - for (uint32_t f = 0; f < 3; ++f) { - GetNextLevelIndex(level, f, -1, -1, &left, &right); - ASSERT_EQ(f == 0 ? 0 : 3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(level, f, 0, -1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(level, f, 1, -1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(level, f, 1, -1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(level, f, 1, 0, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(level, f, 1, 1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - } - } - delete indexer; -} - -// Case 3: empty L2 -TEST_F(FileIndexerTest, empty_L2) { - Arena arena; - indexer = new FileIndexer(&ucmp); - for (uint32_t i = 1; i < kNumLevels; ++i) { - ASSERT_EQ(0U, indexer->LevelIndexSize(i)); - } - // level 1 - AddFile(1, 2100, 2200); - AddFile(1, 2300, 2400); - AddFile(1, 2500, 2600); - // level 3 - AddFile(3, 500, 600); - AddFile(3, 501, 699); - AddFile(3, 700, 800); - indexer->UpdateIndex(&arena, kNumLevels, files); - for (uint32_t f = 0; f < 3; ++f) { - GetNextLevelIndex(1, f, -1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(1, f, 0, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(1, f, 1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(1, f, 1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(1, f, 1, 0, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - GetNextLevelIndex(1, f, 1, 1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(-1, right); - } - delete indexer; - ClearFiles(); -} - -// Case 4: mixed -TEST_F(FileIndexerTest, mixed) { - Arena arena; - indexer = new FileIndexer(&ucmp); - // level 1 - AddFile(1, 100, 200); - AddFile(1, 250, 400); - AddFile(1, 450, 500); - // level 2 - AddFile(2, 100, 150); // 0 - AddFile(2, 200, 250); // 1 - AddFile(2, 251, 300); // 2 - AddFile(2, 301, 350); // 3 - AddFile(2, 500, 600); // 4 - // level 3 - AddFile(3, 0, 50); - AddFile(3, 100, 200); - AddFile(3, 201, 250); - indexer->UpdateIndex(&arena, kNumLevels, files); - // level 1, 0 - GetNextLevelIndex(1, 0, -1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(0, right); - GetNextLevelIndex(1, 0, 0, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(0, right); - GetNextLevelIndex(1, 0, 1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(1, 0, 1, 0, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(1, 0, 1, 1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(4, right); - // level 1, 1 - GetNextLevelIndex(1, 1, -1, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(1, 1, 0, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(1, 1, 1, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(3, right); - GetNextLevelIndex(1, 1, 1, 0, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(3, right); - GetNextLevelIndex(1, 1, 1, 1, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(4, right); - // level 1, 2 - GetNextLevelIndex(1, 2, -1, -1, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(3, right); - GetNextLevelIndex(1, 2, 0, -1, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(3, right); - GetNextLevelIndex(1, 2, 1, -1, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(4, right); - GetNextLevelIndex(1, 2, 1, 0, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(4, right); - GetNextLevelIndex(1, 2, 1, 1, &left, &right); - ASSERT_EQ(4, left); - ASSERT_EQ(4, right); - // level 2, 0 - GetNextLevelIndex(2, 0, -1, -1, &left, &right); - ASSERT_EQ(0, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(2, 0, 0, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(2, 0, 1, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(2, 0, 1, 0, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(2, 0, 1, 1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(2, right); - // level 2, 1 - GetNextLevelIndex(2, 1, -1, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(2, 1, 0, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(1, right); - GetNextLevelIndex(2, 1, 1, -1, &left, &right); - ASSERT_EQ(1, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(2, 1, 1, 0, &left, &right); - ASSERT_EQ(2, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(2, 1, 1, 1, &left, &right); - ASSERT_EQ(2, left); - ASSERT_EQ(2, right); - // level 2, [2 - 4], no overlap - for (uint32_t f = 2; f <= 4; ++f) { - GetNextLevelIndex(2, f, -1, -1, &left, &right); - ASSERT_EQ(f == 2 ? 2 : 3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(2, f, 0, -1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(2, f, 1, -1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(2, f, 1, 0, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - GetNextLevelIndex(2, f, 1, 1, &left, &right); - ASSERT_EQ(3, left); - ASSERT_EQ(2, right); - } - delete indexer; - ClearFiles(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/filename_test.cc b/db/filename_test.cc deleted file mode 100644 index 04c81b333..000000000 --- a/db/filename_test.cc +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "file/filename.h" - -#include "db/dbformat.h" -#include "port/port.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class FileNameTest : public testing::Test {}; - -TEST_F(FileNameTest, Parse) { - Slice db; - FileType type; - uint64_t number; - - char kDefautInfoLogDir = 1; - char kDifferentInfoLogDir = 2; - char kNoCheckLogDir = 4; - char kAllMode = kDefautInfoLogDir | kDifferentInfoLogDir | kNoCheckLogDir; - - // Successful parses - static struct { - const char* fname; - uint64_t number; - FileType type; - char mode; - } cases[] = { - {"100.log", 100, kWalFile, kAllMode}, - {"0.log", 0, kWalFile, kAllMode}, - {"0.sst", 0, kTableFile, kAllMode}, - {"CURRENT", 0, kCurrentFile, kAllMode}, - {"LOCK", 0, kDBLockFile, kAllMode}, - {"MANIFEST-2", 2, kDescriptorFile, kAllMode}, - {"MANIFEST-7", 7, kDescriptorFile, kAllMode}, - {"METADB-2", 2, kMetaDatabase, kAllMode}, - {"METADB-7", 7, kMetaDatabase, kAllMode}, - {"LOG", 0, kInfoLogFile, kDefautInfoLogDir}, - {"LOG.old", 0, kInfoLogFile, kDefautInfoLogDir}, - {"LOG.old.6688", 6688, kInfoLogFile, kDefautInfoLogDir}, - {"rocksdb_dir_LOG", 0, kInfoLogFile, kDifferentInfoLogDir}, - {"rocksdb_dir_LOG.old", 0, kInfoLogFile, kDifferentInfoLogDir}, - {"rocksdb_dir_LOG.old.6688", 6688, kInfoLogFile, kDifferentInfoLogDir}, - {"18446744073709551615.log", 18446744073709551615ull, kWalFile, kAllMode}, - }; - for (char mode : {kDifferentInfoLogDir, kDefautInfoLogDir, kNoCheckLogDir}) { - for (unsigned int i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { - InfoLogPrefix info_log_prefix(mode != kDefautInfoLogDir, "/rocksdb/dir"); - if (cases[i].mode & mode) { - std::string f = cases[i].fname; - if (mode == kNoCheckLogDir) { - ASSERT_TRUE(ParseFileName(f, &number, &type)) << f; - } else { - ASSERT_TRUE(ParseFileName(f, &number, info_log_prefix.prefix, &type)) - << f; - } - ASSERT_EQ(cases[i].type, type) << f; - ASSERT_EQ(cases[i].number, number) << f; - } - } - } - - // Errors - static const char* errors[] = {"", - "foo", - "foo-dx-100.log", - ".log", - "", - "manifest", - "CURREN", - "CURRENTX", - "MANIFES", - "MANIFEST", - "MANIFEST-", - "XMANIFEST-3", - "MANIFEST-3x", - "META", - "METADB", - "METADB-", - "XMETADB-3", - "METADB-3x", - "LOC", - "LOCKx", - "LO", - "LOGx", - "18446744073709551616.log", - "184467440737095516150.log", - "100", - "100.", - "100.lop"}; - for (unsigned int i = 0; i < sizeof(errors) / sizeof(errors[0]); i++) { - std::string f = errors[i]; - ASSERT_TRUE(!ParseFileName(f, &number, &type)) << f; - }; -} - -TEST_F(FileNameTest, InfoLogFileName) { - std::string dbname = ("/data/rocksdb"); - std::string db_absolute_path; - ASSERT_OK(Env::Default()->GetAbsolutePath(dbname, &db_absolute_path)); - - ASSERT_EQ("/data/rocksdb/LOG", InfoLogFileName(dbname, db_absolute_path, "")); - ASSERT_EQ("/data/rocksdb/LOG.old.666", - OldInfoLogFileName(dbname, 666u, db_absolute_path, "")); - - ASSERT_EQ("/data/rocksdb_log/data_rocksdb_LOG", - InfoLogFileName(dbname, db_absolute_path, "/data/rocksdb_log")); - ASSERT_EQ( - "/data/rocksdb_log/data_rocksdb_LOG.old.666", - OldInfoLogFileName(dbname, 666u, db_absolute_path, "/data/rocksdb_log")); -} - -TEST_F(FileNameTest, Construction) { - uint64_t number; - FileType type; - std::string fname; - - fname = CurrentFileName("foo"); - ASSERT_EQ("foo/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(0U, number); - ASSERT_EQ(kCurrentFile, type); - - fname = LockFileName("foo"); - ASSERT_EQ("foo/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(0U, number); - ASSERT_EQ(kDBLockFile, type); - - fname = LogFileName("foo", 192); - ASSERT_EQ("foo/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(192U, number); - ASSERT_EQ(kWalFile, type); - - fname = TableFileName({DbPath("bar", 0)}, 200, 0); - std::string fname1 = - TableFileName({DbPath("foo", 0), DbPath("bar", 0)}, 200, 1); - ASSERT_EQ(fname, fname1); - ASSERT_EQ("bar/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(200U, number); - ASSERT_EQ(kTableFile, type); - - fname = DescriptorFileName("bar", 100); - ASSERT_EQ("bar/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(100U, number); - ASSERT_EQ(kDescriptorFile, type); - - fname = TempFileName("tmp", 999); - ASSERT_EQ("tmp/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(999U, number); - ASSERT_EQ(kTempFile, type); - - fname = MetaDatabaseName("met", 100); - ASSERT_EQ("met/", std::string(fname.data(), 4)); - ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type)); - ASSERT_EQ(100U, number); - ASSERT_EQ(kMetaDatabase, type); -} - -TEST_F(FileNameTest, NormalizePath) { - // No leading slash - const std::string sep = std::string(1, kFilePathSeparator); - - std::string expected = "FOLDER" + sep + "filename.ext"; - std::string given = "FOLDER" + sep + "filename.ext"; - - ASSERT_EQ(expected, NormalizePath(given)); - - // Two chars /a - - expected = sep + "a"; - given = expected; - ASSERT_EQ(expected, NormalizePath(given)); - - // Two chars a/ - expected = "a" + sep; - given = expected; - ASSERT_EQ(expected, NormalizePath(given)); - - // Server only - expected = sep + sep + "a"; - given = expected; - ASSERT_EQ(expected, NormalizePath(given)); - - // Two slashes after character - expected = "a" + sep; - given = "a" + sep + sep; - - ASSERT_EQ(expected, NormalizePath(given)); - - // slash only / - expected = sep; - given = expected; - ASSERT_EQ(expected, NormalizePath(given)); - - // UNC only // - expected = sep; - given = sep + sep; - - ASSERT_EQ(expected, NormalizePath(given)); - - // 3 slashesy // - expected = sep + sep; - given = sep + sep + sep; - ASSERT_EQ(expected, NormalizePath(given)); - - // 3 slashes // - expected = sep + sep + "a" + sep; - given = sep + sep + sep + "a" + sep; - ASSERT_EQ(expected, NormalizePath(given)); - - // 2 separators in the middle - expected = "a" + sep + "b"; - given = "a" + sep + sep + "b"; - ASSERT_EQ(expected, NormalizePath(given)); - - // UNC with duplicate slashes - expected = sep + sep + "SERVER" + sep + "a" + sep + "b" + sep + "c"; - given = sep + sep + "SERVER" + sep + "a" + sep + sep + "b" + sep + "c"; - ASSERT_EQ(expected, NormalizePath(given)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/flush_job_test.cc b/db/flush_job_test.cc deleted file mode 100644 index 72332fc3a..000000000 --- a/db/flush_job_test.cc +++ /dev/null @@ -1,743 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/flush_job.h" - -#include -#include -#include -#include - -#include "db/blob/blob_index.h" -#include "db/column_family.h" -#include "db/db_impl/db_impl.h" -#include "db/version_set.h" -#include "file/writable_file_writer.h" -#include "rocksdb/cache.h" -#include "rocksdb/file_system.h" -#include "rocksdb/write_buffer_manager.h" -#include "table/mock_table.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -// TODO(icanadi) Mock out everything else: -// 1. VersionSet -// 2. Memtable -class FlushJobTestBase : public testing::Test { - protected: - FlushJobTestBase(std::string dbname, const Comparator* ucmp) - : env_(Env::Default()), - fs_(env_->GetFileSystem()), - dbname_(std::move(dbname)), - ucmp_(ucmp), - options_(), - db_options_(options_), - column_family_names_({kDefaultColumnFamilyName, "foo", "bar"}), - table_cache_(NewLRUCache(50000, 16)), - write_buffer_manager_(db_options_.db_write_buffer_size), - shutting_down_(false), - mock_table_factory_(new mock::MockTableFactory()) {} - - virtual ~FlushJobTestBase() { - if (getenv("KEEP_DB")) { - fprintf(stdout, "db is still in %s\n", dbname_.c_str()); - } else { - // destroy versions_ to release all file handles - versions_.reset(); - EXPECT_OK(DestroyDir(env_, dbname_)); - } - } - - void NewDB() { - ASSERT_OK(SetIdentityFile(env_, dbname_)); - VersionEdit new_db; - - new_db.SetLogNumber(0); - new_db.SetNextFile(2); - new_db.SetLastSequence(0); - - autovector new_cfs; - SequenceNumber last_seq = 1; - uint32_t cf_id = 1; - for (size_t i = 1; i != column_family_names_.size(); ++i) { - VersionEdit new_cf; - new_cf.AddColumnFamily(column_family_names_[i]); - new_cf.SetColumnFamily(cf_id++); - new_cf.SetComparatorName(ucmp_->Name()); - new_cf.SetLogNumber(0); - new_cf.SetNextFile(2); - new_cf.SetLastSequence(last_seq++); - new_cfs.emplace_back(new_cf); - } - - const std::string manifest = DescriptorFileName(dbname_, 1); - const auto& fs = env_->GetFileSystem(); - std::unique_ptr file_writer; - Status s = WritableFileWriter::Create( - fs, manifest, fs->OptimizeForManifestWrite(env_options_), &file_writer, - nullptr); - ASSERT_OK(s); - - { - log::Writer log(std::move(file_writer), 0, false); - std::string record; - new_db.EncodeTo(&record); - s = log.AddRecord(record); - ASSERT_OK(s); - - for (const auto& e : new_cfs) { - record.clear(); - e.EncodeTo(&record); - s = log.AddRecord(record); - ASSERT_OK(s); - } - } - ASSERT_OK(s); - // Make "CURRENT" file that points to the new manifest file. - s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr); - ASSERT_OK(s); - } - - void SetUp() override { - EXPECT_OK(env_->CreateDirIfMissing(dbname_)); - - // TODO(icanadi) Remove this once we mock out VersionSet - NewDB(); - - db_options_.env = env_; - db_options_.fs = fs_; - db_options_.db_paths.emplace_back(dbname_, - std::numeric_limits::max()); - db_options_.statistics = CreateDBStatistics(); - - cf_options_.comparator = ucmp_; - - std::vector column_families; - cf_options_.table_factory = mock_table_factory_; - for (const auto& cf_name : column_family_names_) { - column_families.emplace_back(cf_name, cf_options_); - } - - versions_.reset( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - EXPECT_OK(versions_->Recover(column_families, false)); - } - - Env* env_; - std::shared_ptr fs_; - std::string dbname_; - const Comparator* const ucmp_; - EnvOptions env_options_; - Options options_; - ImmutableDBOptions db_options_; - const std::vector column_family_names_; - std::shared_ptr table_cache_; - WriteController write_controller_; - WriteBufferManager write_buffer_manager_; - ColumnFamilyOptions cf_options_; - std::unique_ptr versions_; - InstrumentedMutex mutex_; - std::atomic shutting_down_; - std::shared_ptr mock_table_factory_; - - SeqnoToTimeMapping empty_seqno_to_time_mapping_; -}; - -class FlushJobTest : public FlushJobTestBase { - public: - FlushJobTest() - : FlushJobTestBase(test::PerThreadDBPath("flush_job_test"), - BytewiseComparator()) {} -}; - -TEST_F(FlushJobTest, Empty) { - JobContext job_context(0); - auto cfd = versions_->GetColumnFamilySet()->GetDefault(); - EventLogger event_logger(db_options_.info_log.get()); - SnapshotChecker* snapshot_checker = nullptr; // not relavant - FlushJob flush_job( - dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, - *cfd->GetLatestMutableCFOptions(), - std::numeric_limits::max() /* memtable_id */, env_options_, - versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, nullptr, &event_logger, false, - true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_); - { - InstrumentedMutexLock l(&mutex_); - flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run()); - } - job_context.Clean(); -} - -TEST_F(FlushJobTest, NonEmpty) { - JobContext job_context(0); - auto cfd = versions_->GetColumnFamilySet()->GetDefault(); - auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(), - kMaxSequenceNumber); - new_mem->Ref(); - auto inserted_keys = mock::MakeMockFile(); - // Test data: - // seqno [ 1, 2 ... 8998, 8999, 9000, 9001, 9002 ... 9999 ] - // key [ 1001, 1002 ... 9998, 9999, 0, 1, 2 ... 999 ] - // range-delete "9995" -> "9999" at seqno 10000 - // blob references with seqnos 10001..10006 - for (int i = 1; i < 10000; ++i) { - std::string key(std::to_string((i + 1000) % 10000)); - std::string value("value" + key); - ASSERT_OK(new_mem->Add(SequenceNumber(i), kTypeValue, key, value, - nullptr /* kv_prot_info */)); - if ((i + 1000) % 10000 < 9995) { - InternalKey internal_key(key, SequenceNumber(i), kTypeValue); - inserted_keys.push_back({internal_key.Encode().ToString(), value}); - } - } - - { - ASSERT_OK(new_mem->Add(SequenceNumber(10000), kTypeRangeDeletion, "9995", - "9999a", nullptr /* kv_prot_info */)); - InternalKey internal_key("9995", SequenceNumber(10000), kTypeRangeDeletion); - inserted_keys.push_back({internal_key.Encode().ToString(), "9999a"}); - } - - // Note: the first two blob references will not be considered when resolving - // the oldest blob file referenced (the first one is inlined TTL, while the - // second one is TTL and thus points to a TTL blob file). - constexpr std::array blob_file_numbers{ - {kInvalidBlobFileNumber, 5, 103, 17, 102, 101}}; - for (size_t i = 0; i < blob_file_numbers.size(); ++i) { - std::string key(std::to_string(i + 10001)); - std::string blob_index; - if (i == 0) { - BlobIndex::EncodeInlinedTTL(&blob_index, /* expiration */ 1234567890ULL, - "foo"); - } else if (i == 1) { - BlobIndex::EncodeBlobTTL(&blob_index, /* expiration */ 1234567890ULL, - blob_file_numbers[i], /* offset */ i << 10, - /* size */ i << 20, kNoCompression); - } else { - BlobIndex::EncodeBlob(&blob_index, blob_file_numbers[i], - /* offset */ i << 10, /* size */ i << 20, - kNoCompression); - } - - const SequenceNumber seq(i + 10001); - ASSERT_OK(new_mem->Add(seq, kTypeBlobIndex, key, blob_index, - nullptr /* kv_prot_info */)); - - InternalKey internal_key(key, seq, kTypeBlobIndex); - inserted_keys.push_back({internal_key.Encode().ToString(), blob_index}); - } - mock::SortKVVector(&inserted_keys); - - autovector to_delete; - new_mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(new_mem, &to_delete); - for (auto& m : to_delete) { - delete m; - } - - EventLogger event_logger(db_options_.info_log.get()); - SnapshotChecker* snapshot_checker = nullptr; // not relavant - FlushJob flush_job( - dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, - *cfd->GetLatestMutableCFOptions(), - std::numeric_limits::max() /* memtable_id */, env_options_, - versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, db_options_.statistics.get(), &event_logger, - true, true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_); - - HistogramData hist; - FileMetaData file_meta; - mutex_.Lock(); - flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run(nullptr, &file_meta)); - mutex_.Unlock(); - db_options_.statistics->histogramData(FLUSH_TIME, &hist); - ASSERT_GT(hist.average, 0.0); - - ASSERT_EQ(std::to_string(0), file_meta.smallest.user_key().ToString()); - ASSERT_EQ("9999a", file_meta.largest.user_key().ToString()); - ASSERT_EQ(1, file_meta.fd.smallest_seqno); - ASSERT_EQ(10006, file_meta.fd.largest_seqno); - ASSERT_EQ(17, file_meta.oldest_blob_file_number); - mock_table_factory_->AssertSingleFile(inserted_keys); - job_context.Clean(); -} - -TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) { - const size_t num_mems = 2; - const size_t num_mems_to_flush = 1; - const size_t num_keys_per_table = 100; - JobContext job_context(0); - ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault(); - std::vector memtable_ids; - std::vector new_mems; - for (size_t i = 0; i != num_mems; ++i) { - MemTable* mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(), - kMaxSequenceNumber); - mem->SetID(i); - mem->Ref(); - new_mems.emplace_back(mem); - memtable_ids.push_back(mem->GetID()); - - for (size_t j = 0; j < num_keys_per_table; ++j) { - std::string key(std::to_string(j + i * num_keys_per_table)); - std::string value("value" + key); - ASSERT_OK(mem->Add(SequenceNumber(j + i * num_keys_per_table), kTypeValue, - key, value, nullptr /* kv_prot_info */)); - } - } - - autovector to_delete; - for (auto mem : new_mems) { - mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(mem, &to_delete); - } - - EventLogger event_logger(db_options_.info_log.get()); - SnapshotChecker* snapshot_checker = nullptr; // not relavant - - assert(memtable_ids.size() == num_mems); - uint64_t smallest_memtable_id = memtable_ids.front(); - uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1; - FlushJob flush_job( - dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, - *cfd->GetLatestMutableCFOptions(), flush_memtable_id, env_options_, - versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, db_options_.statistics.get(), &event_logger, - true, true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_); - HistogramData hist; - FileMetaData file_meta; - mutex_.Lock(); - flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run(nullptr /* prep_tracker */, &file_meta)); - mutex_.Unlock(); - db_options_.statistics->histogramData(FLUSH_TIME, &hist); - ASSERT_GT(hist.average, 0.0); - - ASSERT_EQ(std::to_string(0), file_meta.smallest.user_key().ToString()); - ASSERT_EQ("99", file_meta.largest.user_key().ToString()); - ASSERT_EQ(0, file_meta.fd.smallest_seqno); - ASSERT_EQ(SequenceNumber(num_mems_to_flush * num_keys_per_table - 1), - file_meta.fd.largest_seqno); - ASSERT_EQ(kInvalidBlobFileNumber, file_meta.oldest_blob_file_number); - - for (auto m : to_delete) { - delete m; - } - to_delete.clear(); - job_context.Clean(); -} - -TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) { - autovector all_cfds; - for (auto cfd : *versions_->GetColumnFamilySet()) { - all_cfds.push_back(cfd); - } - const std::vector num_memtables = {2, 1, 3}; - assert(num_memtables.size() == column_family_names_.size()); - const size_t num_keys_per_memtable = 1000; - JobContext job_context(0); - std::vector memtable_ids; - std::vector smallest_seqs; - std::vector largest_seqs; - autovector to_delete; - SequenceNumber curr_seqno = 0; - size_t k = 0; - for (auto cfd : all_cfds) { - smallest_seqs.push_back(curr_seqno); - for (size_t i = 0; i != num_memtables[k]; ++i) { - MemTable* mem = cfd->ConstructNewMemtable( - *cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber); - mem->SetID(i); - mem->Ref(); - - for (size_t j = 0; j != num_keys_per_memtable; ++j) { - std::string key(std::to_string(j + i * num_keys_per_memtable)); - std::string value("value" + key); - ASSERT_OK(mem->Add(curr_seqno++, kTypeValue, key, value, - nullptr /* kv_prot_info */)); - } - mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(mem, &to_delete); - } - largest_seqs.push_back(curr_seqno - 1); - memtable_ids.push_back(num_memtables[k++] - 1); - } - - EventLogger event_logger(db_options_.info_log.get()); - SnapshotChecker* snapshot_checker = nullptr; // not relevant - std::vector> flush_jobs; - k = 0; - for (auto cfd : all_cfds) { - std::vector snapshot_seqs; - flush_jobs.emplace_back(new FlushJob( - dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(), - memtable_ids[k], env_options_, versions_.get(), &mutex_, - &shutting_down_, snapshot_seqs, kMaxSequenceNumber, snapshot_checker, - &job_context, FlushReason::kTest, nullptr, nullptr, nullptr, - kNoCompression, db_options_.statistics.get(), &event_logger, true, - false /* sync_output_directory */, false /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, - empty_seqno_to_time_mapping_)); - k++; - } - HistogramData hist; - std::vector file_metas; - // Call reserve to avoid auto-resizing - file_metas.reserve(flush_jobs.size()); - mutex_.Lock(); - for (auto& job : flush_jobs) { - job->PickMemTable(); - } - for (auto& job : flush_jobs) { - FileMetaData meta; - // Run will release and re-acquire mutex - ASSERT_OK(job->Run(nullptr /**/, &meta)); - file_metas.emplace_back(meta); - } - autovector file_meta_ptrs; - for (auto& meta : file_metas) { - file_meta_ptrs.push_back(&meta); - } - autovector*> mems_list; - for (size_t i = 0; i != all_cfds.size(); ++i) { - const auto& mems = flush_jobs[i]->GetMemTables(); - mems_list.push_back(&mems); - } - autovector mutable_cf_options_list; - for (auto cfd : all_cfds) { - mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions()); - } - autovector>*> - committed_flush_jobs_info; - for (auto& job : flush_jobs) { - committed_flush_jobs_info.push_back(job->GetCommittedFlushJobsInfo()); - } - - Status s = InstallMemtableAtomicFlushResults( - nullptr /* imm_lists */, all_cfds, mutable_cf_options_list, mems_list, - versions_.get(), nullptr /* prep_tracker */, &mutex_, file_meta_ptrs, - committed_flush_jobs_info, &job_context.memtables_to_free, - nullptr /* db_directory */, nullptr /* log_buffer */); - ASSERT_OK(s); - - mutex_.Unlock(); - db_options_.statistics->histogramData(FLUSH_TIME, &hist); - ASSERT_GT(hist.average, 0.0); - k = 0; - for (const auto& file_meta : file_metas) { - ASSERT_EQ(std::to_string(0), file_meta.smallest.user_key().ToString()); - ASSERT_EQ("999", file_meta.largest.user_key() - .ToString()); // max key by bytewise comparator - ASSERT_EQ(smallest_seqs[k], file_meta.fd.smallest_seqno); - ASSERT_EQ(largest_seqs[k], file_meta.fd.largest_seqno); - // Verify that imm is empty - ASSERT_EQ(std::numeric_limits::max(), - all_cfds[k]->imm()->GetEarliestMemTableID()); - ASSERT_EQ(0, all_cfds[k]->imm()->GetLatestMemTableID()); - ++k; - } - - for (auto m : to_delete) { - delete m; - } - to_delete.clear(); - job_context.Clean(); -} - -TEST_F(FlushJobTest, Snapshots) { - JobContext job_context(0); - auto cfd = versions_->GetColumnFamilySet()->GetDefault(); - auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(), - kMaxSequenceNumber); - - std::set snapshots_set; - int keys = 10000; - int max_inserts_per_keys = 8; - - Random rnd(301); - for (int i = 0; i < keys / 2; ++i) { - snapshots_set.insert(rnd.Uniform(keys * (max_inserts_per_keys / 2)) + 1); - } - // set has already removed the duplicate snapshots - std::vector snapshots(snapshots_set.begin(), - snapshots_set.end()); - - new_mem->Ref(); - SequenceNumber current_seqno = 0; - auto inserted_keys = mock::MakeMockFile(); - for (int i = 1; i < keys; ++i) { - std::string key(std::to_string(i)); - int insertions = rnd.Uniform(max_inserts_per_keys); - for (int j = 0; j < insertions; ++j) { - std::string value(rnd.HumanReadableString(10)); - auto seqno = ++current_seqno; - ASSERT_OK(new_mem->Add(SequenceNumber(seqno), kTypeValue, key, value, - nullptr /* kv_prot_info */)); - // a key is visible only if: - // 1. it's the last one written (j == insertions - 1) - // 2. there's a snapshot pointing at it - bool visible = (j == insertions - 1) || - (snapshots_set.find(seqno) != snapshots_set.end()); - if (visible) { - InternalKey internal_key(key, seqno, kTypeValue); - inserted_keys.push_back({internal_key.Encode().ToString(), value}); - } - } - } - mock::SortKVVector(&inserted_keys); - - autovector to_delete; - new_mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(new_mem, &to_delete); - for (auto& m : to_delete) { - delete m; - } - - EventLogger event_logger(db_options_.info_log.get()); - SnapshotChecker* snapshot_checker = nullptr; // not relavant - FlushJob flush_job( - dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, - *cfd->GetLatestMutableCFOptions(), - std::numeric_limits::max() /* memtable_id */, env_options_, - versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, db_options_.statistics.get(), &event_logger, - true, true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_); - mutex_.Lock(); - flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run()); - mutex_.Unlock(); - mock_table_factory_->AssertSingleFile(inserted_keys); - HistogramData hist; - db_options_.statistics->histogramData(FLUSH_TIME, &hist); - ASSERT_GT(hist.average, 0.0); - job_context.Clean(); -} - -TEST_F(FlushJobTest, GetRateLimiterPriorityForWrite) { - // Prepare a FlushJob that flush MemTables of Single Column Family. - const size_t num_mems = 2; - const size_t num_mems_to_flush = 1; - const size_t num_keys_per_table = 100; - JobContext job_context(0); - ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault(); - std::vector memtable_ids; - std::vector new_mems; - for (size_t i = 0; i != num_mems; ++i) { - MemTable* mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(), - kMaxSequenceNumber); - mem->SetID(i); - mem->Ref(); - new_mems.emplace_back(mem); - memtable_ids.push_back(mem->GetID()); - - for (size_t j = 0; j < num_keys_per_table; ++j) { - std::string key(std::to_string(j + i * num_keys_per_table)); - std::string value("value" + key); - ASSERT_OK(mem->Add(SequenceNumber(j + i * num_keys_per_table), kTypeValue, - key, value, nullptr /* kv_prot_info */)); - } - } - - autovector to_delete; - for (auto mem : new_mems) { - mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(mem, &to_delete); - } - - EventLogger event_logger(db_options_.info_log.get()); - SnapshotChecker* snapshot_checker = nullptr; // not relavant - - assert(memtable_ids.size() == num_mems); - uint64_t smallest_memtable_id = memtable_ids.front(); - uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1; - FlushJob flush_job( - dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, - *cfd->GetLatestMutableCFOptions(), flush_memtable_id, env_options_, - versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, db_options_.statistics.get(), &event_logger, - true, true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_); - - // When the state from WriteController is normal. - ASSERT_EQ(flush_job.GetRateLimiterPriorityForWrite(), Env::IO_HIGH); - - WriteController* write_controller = - flush_job.versions_->GetColumnFamilySet()->write_controller(); - - { - // When the state from WriteController is Delayed. - std::unique_ptr delay_token = - write_controller->GetDelayToken(1000000); - ASSERT_EQ(flush_job.GetRateLimiterPriorityForWrite(), Env::IO_USER); - } - - { - // When the state from WriteController is Stopped. - std::unique_ptr stop_token = - write_controller->GetStopToken(); - ASSERT_EQ(flush_job.GetRateLimiterPriorityForWrite(), Env::IO_USER); - } -} - -class FlushJobTimestampTest : public FlushJobTestBase { - public: - FlushJobTimestampTest() - : FlushJobTestBase(test::PerThreadDBPath("flush_job_ts_gc_test"), - test::BytewiseComparatorWithU64TsWrapper()) {} - - void AddKeyValueToMemtable(MemTable* memtable, std::string key, uint64_t ts, - SequenceNumber seq, ValueType value_type, - Slice value) { - std::string key_str(std::move(key)); - PutFixed64(&key_str, ts); - ASSERT_OK(memtable->Add(seq, value_type, key_str, value, - nullptr /* kv_prot_info */)); - } - - protected: - static constexpr uint64_t kStartTs = 10; - static constexpr SequenceNumber kStartSeq = 0; - SequenceNumber curr_seq_{kStartSeq}; - std::atomic curr_ts_{kStartTs}; -}; - -TEST_F(FlushJobTimestampTest, AllKeysExpired) { - ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault(); - autovector to_delete; - - { - MemTable* new_mem = cfd->ConstructNewMemtable( - *cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber); - new_mem->Ref(); - for (int i = 0; i < 100; ++i) { - uint64_t ts = curr_ts_.fetch_add(1); - SequenceNumber seq = (curr_seq_++); - AddKeyValueToMemtable(new_mem, test::EncodeInt(0), ts, seq, - ValueType::kTypeValue, "0_value"); - } - uint64_t ts = curr_ts_.fetch_add(1); - SequenceNumber seq = (curr_seq_++); - AddKeyValueToMemtable(new_mem, test::EncodeInt(0), ts, seq, - ValueType::kTypeDeletionWithTimestamp, ""); - new_mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(new_mem, &to_delete); - } - - std::vector snapshots; - constexpr SnapshotChecker* const snapshot_checker = nullptr; - JobContext job_context(0); - EventLogger event_logger(db_options_.info_log.get()); - std::string full_history_ts_low; - PutFixed64(&full_history_ts_low, std::numeric_limits::max()); - FlushJob flush_job( - dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(), - std::numeric_limits::max() /* memtable_id */, env_options_, - versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, db_options_.statistics.get(), &event_logger, - true, true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_, - /*db_id=*/"", - /*db_session_id=*/"", full_history_ts_low); - - FileMetaData fmeta; - mutex_.Lock(); - flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run(/*prep_tracker=*/nullptr, &fmeta)); - mutex_.Unlock(); - - { - std::string key = test::EncodeInt(0); - key.append(test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1)); - InternalKey ikey(key, curr_seq_ - 1, ValueType::kTypeDeletionWithTimestamp); - ASSERT_EQ(ikey.Encode(), fmeta.smallest.Encode()); - ASSERT_EQ(ikey.Encode(), fmeta.largest.Encode()); - } - - job_context.Clean(); - ASSERT_TRUE(to_delete.empty()); -} - -TEST_F(FlushJobTimestampTest, NoKeyExpired) { - ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault(); - autovector to_delete; - - { - MemTable* new_mem = cfd->ConstructNewMemtable( - *cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber); - new_mem->Ref(); - for (int i = 0; i < 100; ++i) { - uint64_t ts = curr_ts_.fetch_add(1); - SequenceNumber seq = (curr_seq_++); - AddKeyValueToMemtable(new_mem, test::EncodeInt(0), ts, seq, - ValueType::kTypeValue, "0_value"); - } - new_mem->ConstructFragmentedRangeTombstones(); - cfd->imm()->Add(new_mem, &to_delete); - } - - std::vector snapshots; - SnapshotChecker* const snapshot_checker = nullptr; - JobContext job_context(0); - EventLogger event_logger(db_options_.info_log.get()); - std::string full_history_ts_low; - PutFixed64(&full_history_ts_low, 0); - FlushJob flush_job( - dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(), - std::numeric_limits::max() /* memtable_id */, env_options_, - versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber, - snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr, - nullptr, kNoCompression, db_options_.statistics.get(), &event_logger, - true, true /* sync_output_directory */, true /* write_manifest */, - Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_, - /*db_id=*/"", - /*db_session_id=*/"", full_history_ts_low); - - FileMetaData fmeta; - mutex_.Lock(); - flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run(/*prep_tracker=*/nullptr, &fmeta)); - mutex_.Unlock(); - - { - std::string ukey = test::EncodeInt(0); - std::string smallest_key = - ukey + test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1); - std::string largest_key = ukey + test::EncodeInt(kStartTs); - InternalKey smallest(smallest_key, curr_seq_ - 1, ValueType::kTypeValue); - InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue); - ASSERT_EQ(smallest.Encode(), fmeta.smallest.Encode()); - ASSERT_EQ(largest.Encode(), fmeta.largest.Encode()); - } - job_context.Clean(); - ASSERT_TRUE(to_delete.empty()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/import_column_family_test.cc b/db/import_column_family_test.cc deleted file mode 100644 index c7940a374..000000000 --- a/db/import_column_family_test.cc +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include - -#include "db/db_test_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/sst_file_writer.h" -#include "test_util/testutil.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class ImportColumnFamilyTest : public DBTestBase { - public: - ImportColumnFamilyTest() - : DBTestBase("import_column_family_test", /*env_do_fsync=*/true) { - sst_files_dir_ = dbname_ + "/sst_files/"; - export_files_dir_ = test::PerThreadDBPath(env_, "export"); - DestroyAndRecreateExternalSSTFilesDir(); - import_cfh_ = nullptr; - import_cfh2_ = nullptr; - metadata_ptr_ = nullptr; - } - - ~ImportColumnFamilyTest() { - if (import_cfh_) { - EXPECT_OK(db_->DropColumnFamily(import_cfh_)); - EXPECT_OK(db_->DestroyColumnFamilyHandle(import_cfh_)); - import_cfh_ = nullptr; - } - if (import_cfh2_) { - EXPECT_OK(db_->DropColumnFamily(import_cfh2_)); - EXPECT_OK(db_->DestroyColumnFamilyHandle(import_cfh2_)); - import_cfh2_ = nullptr; - } - if (metadata_ptr_) { - delete metadata_ptr_; - metadata_ptr_ = nullptr; - } - EXPECT_OK(DestroyDir(env_, sst_files_dir_)); - EXPECT_OK(DestroyDir(env_, export_files_dir_)); - } - - void DestroyAndRecreateExternalSSTFilesDir() { - EXPECT_OK(DestroyDir(env_, sst_files_dir_)); - EXPECT_OK(env_->CreateDir(sst_files_dir_)); - EXPECT_OK(DestroyDir(env_, export_files_dir_)); - } - - LiveFileMetaData LiveFileMetaDataInit(std::string name, std::string path, - int level, - SequenceNumber smallest_seqno, - SequenceNumber largest_seqno) { - LiveFileMetaData metadata; - metadata.name = name; - metadata.db_path = path; - metadata.smallest_seqno = smallest_seqno; - metadata.largest_seqno = largest_seqno; - metadata.level = level; - return metadata; - } - - protected: - std::string sst_files_dir_; - std::string export_files_dir_; - ColumnFamilyHandle* import_cfh_; - ColumnFamilyHandle* import_cfh2_; - ExportImportFilesMetaData* metadata_ptr_; -}; - -TEST_F(ImportColumnFamilyTest, ImportSSTFileWriterFiles) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko"}, options); - - SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]); - SstFileWriter sfw_unknown(EnvOptions(), options); - - // cf1.sst - const std::string cf1_sst_name = "cf1.sst"; - const std::string cf1_sst = sst_files_dir_ + cf1_sst_name; - ASSERT_OK(sfw_cf1.Open(cf1_sst)); - ASSERT_OK(sfw_cf1.Put("K1", "V1")); - ASSERT_OK(sfw_cf1.Put("K2", "V2")); - ASSERT_OK(sfw_cf1.Finish()); - - // cf_unknown.sst - const std::string unknown_sst_name = "cf_unknown.sst"; - const std::string unknown_sst = sst_files_dir_ + unknown_sst_name; - ASSERT_OK(sfw_unknown.Open(unknown_sst)); - ASSERT_OK(sfw_unknown.Put("K3", "V1")); - ASSERT_OK(sfw_unknown.Put("K4", "V2")); - ASSERT_OK(sfw_unknown.Finish()); - - { - // Import sst file corresponding to cf1 onto a new cf and verify - ExportImportFilesMetaData metadata; - metadata.files.push_back( - LiveFileMetaDataInit(cf1_sst_name, sst_files_dir_, 0, 10, 19)); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_OK(db_->CreateColumnFamilyWithImport( - options, "toto", ImportColumnFamilyOptions(), metadata, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, "K1", &value)); - ASSERT_EQ(value, "V1"); - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, "K2", &value)); - ASSERT_EQ(value, "V2"); - ASSERT_OK(db_->DropColumnFamily(import_cfh_)); - ASSERT_OK(db_->DestroyColumnFamilyHandle(import_cfh_)); - import_cfh_ = nullptr; - } - - { - // Import sst file corresponding to unknown cf onto a new cf and verify - ExportImportFilesMetaData metadata; - metadata.files.push_back( - LiveFileMetaDataInit(unknown_sst_name, sst_files_dir_, 0, 20, 29)); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_OK(db_->CreateColumnFamilyWithImport( - options, "yoyo", ImportColumnFamilyOptions(), metadata, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, "K3", &value)); - ASSERT_EQ(value, "V1"); - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, "K4", &value)); - ASSERT_EQ(value, "V2"); - } - EXPECT_OK(db_->DestroyColumnFamilyHandle(import_cfh_)); - import_cfh_ = nullptr; - - // verify sst unique id during reopen - options.verify_sst_unique_id_in_manifest = true; - ReopenWithColumnFamilies({"default", "koko", "yoyo"}, options); -} - -TEST_F(ImportColumnFamilyTest, ImportSSTFileWriterFilesWithOverlap) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko"}, options); - - SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]); - - // file3.sst - const std::string file3_sst_name = "file3.sst"; - const std::string file3_sst = sst_files_dir_ + file3_sst_name; - ASSERT_OK(sfw_cf1.Open(file3_sst)); - for (int i = 0; i < 100; ++i) { - ASSERT_OK(sfw_cf1.Put(Key(i), Key(i) + "_val")); - } - ASSERT_OK(sfw_cf1.Finish()); - - // file2.sst - const std::string file2_sst_name = "file2.sst"; - const std::string file2_sst = sst_files_dir_ + file2_sst_name; - ASSERT_OK(sfw_cf1.Open(file2_sst)); - for (int i = 0; i < 100; i += 2) { - ASSERT_OK(sfw_cf1.Put(Key(i), Key(i) + "_overwrite1")); - } - ASSERT_OK(sfw_cf1.Finish()); - - // file1a.sst - const std::string file1a_sst_name = "file1a.sst"; - const std::string file1a_sst = sst_files_dir_ + file1a_sst_name; - ASSERT_OK(sfw_cf1.Open(file1a_sst)); - for (int i = 0; i < 52; i += 4) { - ASSERT_OK(sfw_cf1.Put(Key(i), Key(i) + "_overwrite2")); - } - ASSERT_OK(sfw_cf1.Finish()); - - // file1b.sst - const std::string file1b_sst_name = "file1b.sst"; - const std::string file1b_sst = sst_files_dir_ + file1b_sst_name; - ASSERT_OK(sfw_cf1.Open(file1b_sst)); - for (int i = 52; i < 100; i += 4) { - ASSERT_OK(sfw_cf1.Put(Key(i), Key(i) + "_overwrite2")); - } - ASSERT_OK(sfw_cf1.Finish()); - - // file0a.sst - const std::string file0a_sst_name = "file0a.sst"; - const std::string file0a_sst = sst_files_dir_ + file0a_sst_name; - ASSERT_OK(sfw_cf1.Open(file0a_sst)); - for (int i = 0; i < 100; i += 16) { - ASSERT_OK(sfw_cf1.Put(Key(i), Key(i) + "_overwrite3")); - } - ASSERT_OK(sfw_cf1.Finish()); - - // file0b.sst - const std::string file0b_sst_name = "file0b.sst"; - const std::string file0b_sst = sst_files_dir_ + file0b_sst_name; - ASSERT_OK(sfw_cf1.Open(file0b_sst)); - for (int i = 0; i < 100; i += 16) { - ASSERT_OK(sfw_cf1.Put(Key(i), Key(i) + "_overwrite4")); - } - ASSERT_OK(sfw_cf1.Finish()); - - // Import sst files and verify - ExportImportFilesMetaData metadata; - metadata.files.push_back( - LiveFileMetaDataInit(file3_sst_name, sst_files_dir_, 3, 10, 19)); - metadata.files.push_back( - LiveFileMetaDataInit(file2_sst_name, sst_files_dir_, 2, 20, 29)); - metadata.files.push_back( - LiveFileMetaDataInit(file1a_sst_name, sst_files_dir_, 1, 30, 34)); - metadata.files.push_back( - LiveFileMetaDataInit(file1b_sst_name, sst_files_dir_, 1, 35, 39)); - metadata.files.push_back( - LiveFileMetaDataInit(file0a_sst_name, sst_files_dir_, 0, 40, 49)); - metadata.files.push_back( - LiveFileMetaDataInit(file0b_sst_name, sst_files_dir_, 0, 50, 59)); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_OK(db_->CreateColumnFamilyWithImport( - options, "toto", ImportColumnFamilyOptions(), metadata, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - - for (int i = 0; i < 100; i++) { - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value)); - if (i % 16 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite4"); - } else if (i % 4 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite2"); - } else if (i % 2 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite1"); - } else { - ASSERT_EQ(value, Key(i) + "_val"); - } - } - - for (int i = 0; i < 100; i += 5) { - ASSERT_OK( - db_->Put(WriteOptions(), import_cfh_, Key(i), Key(i) + "_overwrite5")); - } - - // Flush and check again - ASSERT_OK(db_->Flush(FlushOptions(), import_cfh_)); - for (int i = 0; i < 100; i++) { - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value)); - if (i % 5 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite5"); - } else if (i % 16 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite4"); - } else if (i % 4 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite2"); - } else if (i % 2 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite1"); - } else { - ASSERT_EQ(value, Key(i) + "_val"); - } - } - - // Compact and check again. - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), import_cfh_, nullptr, nullptr)); - for (int i = 0; i < 100; i++) { - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value)); - if (i % 5 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite5"); - } else if (i % 16 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite4"); - } else if (i % 4 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite2"); - } else if (i % 2 == 0) { - ASSERT_EQ(value, Key(i) + "_overwrite1"); - } else { - ASSERT_EQ(value, Key(i) + "_val"); - } - } -} - -TEST_F(ImportColumnFamilyTest, ImportSSTFileWriterFilesWithRangeTombstone) { - // Test for a bug where import file's smallest and largest key did not - // consider range tombstone. - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko"}, options); - - SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]); - // cf1.sst - const std::string cf1_sst_name = "cf1.sst"; - const std::string cf1_sst = sst_files_dir_ + cf1_sst_name; - ASSERT_OK(sfw_cf1.Open(cf1_sst)); - ASSERT_OK(sfw_cf1.Put("K1", "V1")); - ASSERT_OK(sfw_cf1.Put("K2", "V2")); - ASSERT_OK(sfw_cf1.DeleteRange("K3", "K4")); - ASSERT_OK(sfw_cf1.Finish()); - - // Import sst file corresponding to cf1 onto a new cf and verify - ExportImportFilesMetaData metadata; - metadata.files.push_back( - LiveFileMetaDataInit(cf1_sst_name, sst_files_dir_, 0, 0, 19)); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_OK(db_->CreateColumnFamilyWithImport( - options, "toto", ImportColumnFamilyOptions(), metadata, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - - ColumnFamilyMetaData import_cf_meta; - db_->GetColumnFamilyMetaData(import_cfh_, &import_cf_meta); - ASSERT_EQ(import_cf_meta.file_count, 1); - const SstFileMetaData* file_meta = nullptr; - for (const auto& level_meta : import_cf_meta.levels) { - if (!level_meta.files.empty()) { - file_meta = &(level_meta.files[0]); - break; - } - } - ASSERT_TRUE(file_meta != nullptr); - InternalKey largest; - largest.DecodeFrom(file_meta->largest); - ASSERT_EQ(largest.user_key(), "K4"); - - std::string value; - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, "K1", &value)); - ASSERT_EQ(value, "V1"); - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, "K2", &value)); - ASSERT_EQ(value, "V2"); - ASSERT_OK(db_->DropColumnFamily(import_cfh_)); - ASSERT_OK(db_->DestroyColumnFamilyHandle(import_cfh_)); - import_cfh_ = nullptr; -} - -TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherCF) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko"}, options); - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_val")); - } - ASSERT_OK(Flush(1)); - - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr)); - - // Overwrite the value in the same set of keys. - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_overwrite")); - } - - // Flush to create L0 file. - ASSERT_OK(Flush(1)); - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_overwrite2")); - } - - // Flush again to create another L0 file. It should have higher sequencer. - ASSERT_OK(Flush(1)); - - Checkpoint* checkpoint; - ASSERT_OK(Checkpoint::Create(db_, &checkpoint)); - ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_, - &metadata_ptr_)); - ASSERT_NE(metadata_ptr_, nullptr); - delete checkpoint; - - ImportColumnFamilyOptions import_options; - import_options.move_files = false; - ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "toto", import_options, - *metadata_ptr_, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - - import_options.move_files = true; - ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "yoyo", import_options, - *metadata_ptr_, &import_cfh2_)); - ASSERT_NE(import_cfh2_, nullptr); - delete metadata_ptr_; - metadata_ptr_ = NULL; - - std::string value1, value2; - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value1)); - ASSERT_EQ(Get(1, Key(i)), value1); - } - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh2_, Key(i), &value2)); - ASSERT_EQ(Get(1, Key(i)), value2); - } - - // Modify keys in cf1 and verify. - for (int i = 0; i < 25; i++) { - ASSERT_OK(db_->Delete(WriteOptions(), import_cfh_, Key(i))); - } - for (int i = 25; i < 50; i++) { - ASSERT_OK( - db_->Put(WriteOptions(), import_cfh_, Key(i), Key(i) + "_overwrite3")); - } - for (int i = 0; i < 25; ++i) { - ASSERT_TRUE( - db_->Get(ReadOptions(), import_cfh_, Key(i), &value1).IsNotFound()); - } - for (int i = 25; i < 50; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value1)); - ASSERT_EQ(Key(i) + "_overwrite3", value1); - } - for (int i = 50; i < 100; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value1)); - ASSERT_EQ(Key(i) + "_overwrite2", value1); - } - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh2_, Key(i), &value2)); - ASSERT_EQ(Get(1, Key(i)), value2); - } - - // Compact and check again. - ASSERT_OK(db_->Flush(FlushOptions(), import_cfh_)); - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), import_cfh_, nullptr, nullptr)); - - for (int i = 0; i < 25; ++i) { - ASSERT_TRUE( - db_->Get(ReadOptions(), import_cfh_, Key(i), &value1).IsNotFound()); - } - for (int i = 25; i < 50; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value1)); - ASSERT_EQ(Key(i) + "_overwrite3", value1); - } - for (int i = 50; i < 100; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh_, Key(i), &value1)); - ASSERT_EQ(Key(i) + "_overwrite2", value1); - } - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(db_->Get(ReadOptions(), import_cfh2_, Key(i), &value2)); - ASSERT_EQ(Get(1, Key(i)), value2); - } -} - -TEST_F(ImportColumnFamilyTest, ImportExportedSSTFromAnotherDB) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko"}, options); - - for (int i = 0; i < 100; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_val")); - } - ASSERT_OK(Flush(1)); - - // Compact to create a L1 file. - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr)); - - // Overwrite the value in the same set of keys. - for (int i = 0; i < 50; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_overwrite")); - } - - // Flush to create L0 file. - ASSERT_OK(Flush(1)); - - for (int i = 0; i < 25; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_overwrite2")); - } - - // Flush again to create another L0 file. It should have higher sequencer. - ASSERT_OK(Flush(1)); - - Checkpoint* checkpoint; - ASSERT_OK(Checkpoint::Create(db_, &checkpoint)); - ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_, - &metadata_ptr_)); - ASSERT_NE(metadata_ptr_, nullptr); - delete checkpoint; - - // Create a new db and import the files. - DB* db_copy; - ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy")); - ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy)); - ColumnFamilyHandle* cfh = nullptr; - ASSERT_OK(db_copy->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - *metadata_ptr_, &cfh)); - ASSERT_NE(cfh, nullptr); - - for (int i = 0; i < 100; ++i) { - std::string value; - ASSERT_OK(db_copy->Get(ReadOptions(), cfh, Key(i), &value)); - ASSERT_EQ(Get(1, Key(i)), value); - } - ASSERT_OK(db_copy->DropColumnFamily(cfh)); - ASSERT_OK(db_copy->DestroyColumnFamilyHandle(cfh)); - delete db_copy; - ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy")); -} - -TEST_F(ImportColumnFamilyTest, - ImportExportedSSTFromAnotherCFWithRangeTombstone) { - // Test for a bug where import file's smallest and largest key did not - // consider range tombstone. - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - CreateAndReopenWithCF({"koko"}, options); - - for (int i = 10; i < 20; ++i) { - ASSERT_OK(Put(1, Key(i), Key(i) + "_val")); - } - ASSERT_OK(Flush(1 /* cf */)); - MoveFilesToLevel(1 /* level */, 1 /* cf */); - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK(db_->DeleteRange(WriteOptions(), handles_[1], Key(0), Key(25))); - ASSERT_OK(Put(1, Key(1), "t")); - ASSERT_OK(Flush(1)); - // Tests importing a range tombstone only file - ASSERT_OK(db_->DeleteRange(WriteOptions(), handles_[1], Key(0), Key(2))); - - Checkpoint* checkpoint; - ASSERT_OK(Checkpoint::Create(db_, &checkpoint)); - ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_, - &metadata_ptr_)); - ASSERT_NE(metadata_ptr_, nullptr); - delete checkpoint; - - ImportColumnFamilyOptions import_options; - import_options.move_files = false; - ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "toto", import_options, - *metadata_ptr_, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - - import_options.move_files = true; - ASSERT_OK(db_->CreateColumnFamilyWithImport(options, "yoyo", import_options, - *metadata_ptr_, &import_cfh2_)); - ASSERT_NE(import_cfh2_, nullptr); - delete metadata_ptr_; - metadata_ptr_ = nullptr; - - std::string value1, value2; - ReadOptions ro_latest; - ReadOptions ro_snapshot; - ro_snapshot.snapshot = snapshot; - - for (int i = 10; i < 20; ++i) { - ASSERT_TRUE(db_->Get(ro_latest, import_cfh_, Key(i), &value1).IsNotFound()); - ASSERT_OK(db_->Get(ro_snapshot, import_cfh_, Key(i), &value1)); - ASSERT_EQ(Get(1, Key(i), snapshot), value1); - } - ASSERT_TRUE(db_->Get(ro_latest, import_cfh_, Key(1), &value1).IsNotFound()); - - for (int i = 10; i < 20; ++i) { - ASSERT_TRUE( - db_->Get(ro_latest, import_cfh2_, Key(i), &value1).IsNotFound()); - - ASSERT_OK(db_->Get(ro_snapshot, import_cfh2_, Key(i), &value2)); - ASSERT_EQ(Get(1, Key(i), snapshot), value2); - } - ASSERT_TRUE(db_->Get(ro_latest, import_cfh2_, Key(1), &value1).IsNotFound()); - - db_->ReleaseSnapshot(snapshot); -} - -TEST_F(ImportColumnFamilyTest, LevelFilesOverlappingAtEndpoints) { - // Imports a column family containing a level where two files overlap at their - // endpoints. "Overlap" means the largest user key in one file is the same as - // the smallest user key in the second file. - const int kFileBytes = 128 << 10; // 128KB - const int kValueBytes = 1 << 10; // 1KB - const int kNumFiles = 4; - - Options options = CurrentOptions(); - options.disable_auto_compactions = true; - options.num_levels = 2; - CreateAndReopenWithCF({"koko"}, options); - - Random rnd(301); - // Every key is snapshot protected to ensure older versions will not be - // dropped during compaction. - std::vector snapshots; - snapshots.reserve(kFileBytes / kValueBytes * kNumFiles); - for (int i = 0; i < kNumFiles; ++i) { - for (int j = 0; j < kFileBytes / kValueBytes; ++j) { - auto value = rnd.RandomString(kValueBytes); - ASSERT_OK(Put(1, "key", value)); - snapshots.push_back(db_->GetSnapshot()); - } - ASSERT_OK(Flush(1)); - } - - // Compact to create overlapping L1 files. - ASSERT_OK( - db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr)); - ASSERT_GT(NumTableFilesAtLevel(1, 1), 1); - - Checkpoint* checkpoint; - ASSERT_OK(Checkpoint::Create(db_, &checkpoint)); - ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir_, - &metadata_ptr_)); - ASSERT_NE(metadata_ptr_, nullptr); - delete checkpoint; - - // Create a new db and import the files. - DB* db_copy; - ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy")); - ASSERT_OK(DB::Open(options, dbname_ + "/db_copy", &db_copy)); - ColumnFamilyHandle* cfh = nullptr; - ASSERT_OK(db_copy->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - *metadata_ptr_, &cfh)); - ASSERT_NE(cfh, nullptr); - - { - std::string value; - ASSERT_OK(db_copy->Get(ReadOptions(), cfh, "key", &value)); - } - ASSERT_OK(db_copy->DropColumnFamily(cfh)); - ASSERT_OK(db_copy->DestroyColumnFamilyHandle(cfh)); - delete db_copy; - ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy")); - for (const Snapshot* snapshot : snapshots) { - db_->ReleaseSnapshot(snapshot); - } -} - -TEST_F(ImportColumnFamilyTest, ImportColumnFamilyNegativeTest) { - Options options = CurrentOptions(); - CreateAndReopenWithCF({"koko"}, options); - - { - // Create column family with existing cf name. - ExportImportFilesMetaData metadata; - - ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "koko", - ImportColumnFamilyOptions(), - metadata, &import_cfh_), - Status::InvalidArgument("Column family already exists")); - ASSERT_EQ(import_cfh_, nullptr); - } - - { - // Import with no files specified. - ExportImportFilesMetaData metadata; - - ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - metadata, &import_cfh_), - Status::InvalidArgument("The list of files is empty")); - ASSERT_EQ(import_cfh_, nullptr); - } - - { - // Import with overlapping keys in sst files. - ExportImportFilesMetaData metadata; - SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]); - const std::string file1_sst_name = "file1.sst"; - const std::string file1_sst = sst_files_dir_ + file1_sst_name; - ASSERT_OK(sfw_cf1.Open(file1_sst)); - ASSERT_OK(sfw_cf1.Put("K1", "V1")); - ASSERT_OK(sfw_cf1.Put("K2", "V2")); - ASSERT_OK(sfw_cf1.Finish()); - const std::string file2_sst_name = "file2.sst"; - const std::string file2_sst = sst_files_dir_ + file2_sst_name; - ASSERT_OK(sfw_cf1.Open(file2_sst)); - ASSERT_OK(sfw_cf1.Put("K2", "V2")); - ASSERT_OK(sfw_cf1.Put("K3", "V3")); - ASSERT_OK(sfw_cf1.Finish()); - - metadata.files.push_back( - LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 10, 19)); - metadata.files.push_back( - LiveFileMetaDataInit(file2_sst_name, sst_files_dir_, 1, 10, 19)); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_NOK(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - metadata, &import_cfh_)); - ASSERT_EQ(import_cfh_, nullptr); - } - - { - // Import with a mismatching comparator, should fail with appropriate error. - ExportImportFilesMetaData metadata; - Options mismatch_options = CurrentOptions(); - mismatch_options.comparator = ReverseBytewiseComparator(); - SstFileWriter sfw_cf1(EnvOptions(), mismatch_options, handles_[1]); - const std::string file1_sst_name = "file1.sst"; - const std::string file1_sst = sst_files_dir_ + file1_sst_name; - ASSERT_OK(sfw_cf1.Open(file1_sst)); - ASSERT_OK(sfw_cf1.Put("K2", "V2")); - ASSERT_OK(sfw_cf1.Put("K1", "V1")); - ASSERT_OK(sfw_cf1.Finish()); - - metadata.files.push_back( - LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 10, 19)); - metadata.db_comparator_name = mismatch_options.comparator->Name(); - - ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "coco", - ImportColumnFamilyOptions(), - metadata, &import_cfh_), - Status::InvalidArgument("Comparator name mismatch")); - ASSERT_EQ(import_cfh_, nullptr); - } - - { - // Import with non existent sst file should fail with appropriate error - ExportImportFilesMetaData metadata; - SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]); - const std::string file1_sst_name = "file1.sst"; - const std::string file1_sst = sst_files_dir_ + file1_sst_name; - ASSERT_OK(sfw_cf1.Open(file1_sst)); - ASSERT_OK(sfw_cf1.Put("K1", "V1")); - ASSERT_OK(sfw_cf1.Put("K2", "V2")); - ASSERT_OK(sfw_cf1.Finish()); - const std::string file3_sst_name = "file3.sst"; - - metadata.files.push_back( - LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 10, 19)); - metadata.files.push_back( - LiveFileMetaDataInit(file3_sst_name, sst_files_dir_, 1, 10, 19)); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - metadata, &import_cfh_), - Status::IOError("No such file or directory")); - ASSERT_EQ(import_cfh_, nullptr); - - // Test successful import after a failure with the same CF name. Ensures - // there is no side effect with CF when there is a failed import - metadata.files.pop_back(); - metadata.db_comparator_name = options.comparator->Name(); - - ASSERT_OK(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo", - ImportColumnFamilyOptions(), - metadata, &import_cfh_)); - ASSERT_NE(import_cfh_, nullptr); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/listener_test.cc b/db/listener_test.cc deleted file mode 100644 index 7c96bfd34..000000000 --- a/db/listener_test.cc +++ /dev/null @@ -1,1598 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "db/blob/blob_index.h" -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "db/version_set.h" -#include "db/write_batch_internal.h" -#include "file/filename.h" -#include "monitoring/statistics.h" -#include "rocksdb/cache.h" -#include "rocksdb/compaction_filter.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/options.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/slice.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/table.h" -#include "rocksdb/table_properties.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/hash.h" -#include "util/mutexlock.h" -#include "util/rate_limiter.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - - -namespace ROCKSDB_NAMESPACE { - -class EventListenerTest : public DBTestBase { - public: - EventListenerTest() : DBTestBase("listener_test", /*env_do_fsync=*/true) {} - - static std::string BlobStr(uint64_t blob_file_number, uint64_t offset, - uint64_t size) { - std::string blob_index; - BlobIndex::EncodeBlob(&blob_index, blob_file_number, offset, size, - kNoCompression); - return blob_index; - } - - const size_t k110KB = 110 << 10; -}; - -struct TestPropertiesCollector - : public ROCKSDB_NAMESPACE::TablePropertiesCollector { - ROCKSDB_NAMESPACE::Status AddUserKey( - const ROCKSDB_NAMESPACE::Slice& /*key*/, - const ROCKSDB_NAMESPACE::Slice& /*value*/, - ROCKSDB_NAMESPACE::EntryType /*type*/, - ROCKSDB_NAMESPACE::SequenceNumber /*seq*/, - uint64_t /*file_size*/) override { - return Status::OK(); - } - ROCKSDB_NAMESPACE::Status Finish( - ROCKSDB_NAMESPACE::UserCollectedProperties* properties) override { - properties->insert({"0", "1"}); - return Status::OK(); - } - - const char* Name() const override { return "TestTablePropertiesCollector"; } - - ROCKSDB_NAMESPACE::UserCollectedProperties GetReadableProperties() - const override { - ROCKSDB_NAMESPACE::UserCollectedProperties ret; - ret["2"] = "3"; - return ret; - } -}; - -class TestPropertiesCollectorFactory : public TablePropertiesCollectorFactory { - public: - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - return new TestPropertiesCollector; - } - const char* Name() const override { return "TestTablePropertiesCollector"; } -}; - -class TestCompactionListener : public EventListener { - public: - explicit TestCompactionListener(EventListenerTest* test) : test_(test) {} - - void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override { - std::lock_guard lock(mutex_); - compacted_dbs_.push_back(db); - ASSERT_GT(ci.input_files.size(), 0U); - ASSERT_EQ(ci.input_files.size(), ci.input_file_infos.size()); - - for (size_t i = 0; i < ci.input_file_infos.size(); ++i) { - ASSERT_EQ(ci.input_file_infos[i].level, ci.base_input_level); - ASSERT_EQ(ci.input_file_infos[i].file_number, - TableFileNameToNumber(ci.input_files[i])); - } - - ASSERT_GT(ci.output_files.size(), 0U); - ASSERT_EQ(ci.output_files.size(), ci.output_file_infos.size()); - - ASSERT_TRUE(test_); - ASSERT_EQ(test_->db_, db); - - std::vector> files_by_level; - test_->dbfull()->TEST_GetFilesMetaData(test_->handles_[ci.cf_id], - &files_by_level); - ASSERT_GT(files_by_level.size(), ci.output_level); - - for (size_t i = 0; i < ci.output_file_infos.size(); ++i) { - ASSERT_EQ(ci.output_file_infos[i].level, ci.output_level); - ASSERT_EQ(ci.output_file_infos[i].file_number, - TableFileNameToNumber(ci.output_files[i])); - - auto it = std::find_if( - files_by_level[ci.output_level].begin(), - files_by_level[ci.output_level].end(), [&](const FileMetaData& meta) { - return meta.fd.GetNumber() == ci.output_file_infos[i].file_number; - }); - ASSERT_NE(it, files_by_level[ci.output_level].end()); - - ASSERT_EQ(ci.output_file_infos[i].oldest_blob_file_number, - it->oldest_blob_file_number); - } - - ASSERT_EQ(db->GetEnv()->GetThreadID(), ci.thread_id); - ASSERT_GT(ci.thread_id, 0U); - - for (auto fl : {ci.input_files, ci.output_files}) { - for (auto fn : fl) { - auto it = ci.table_properties.find(fn); - ASSERT_NE(it, ci.table_properties.end()); - auto tp = it->second; - ASSERT_TRUE(tp != nullptr); - ASSERT_EQ(tp->user_collected_properties.find("0")->second, "1"); - } - } - } - - EventListenerTest* test_; - std::vector compacted_dbs_; - std::mutex mutex_; -}; - -TEST_F(EventListenerTest, OnSingleDBCompactionTest) { - const int kTestKeySize = 16; - const int kTestValueSize = 984; - const int kEntrySize = kTestKeySize + kTestValueSize; - const int kEntriesPerBuffer = 100; - const int kNumL0Files = 4; - - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.write_buffer_size = kEntrySize * kEntriesPerBuffer; - options.compaction_style = kCompactionStyleLevel; - options.target_file_size_base = options.write_buffer_size; - options.max_bytes_for_level_base = options.target_file_size_base * 2; - options.max_bytes_for_level_multiplier = 2; - options.compression = kNoCompression; -#ifdef ROCKSDB_USING_THREAD_STATUS - options.enable_thread_tracking = true; -#endif // ROCKSDB_USING_THREAD_STATUS - options.level0_file_num_compaction_trigger = kNumL0Files; - options.table_properties_collector_factories.push_back( - std::make_shared()); - - TestCompactionListener* listener = new TestCompactionListener(this); - options.listeners.emplace_back(listener); - std::vector cf_names = {"pikachu", "ilya", "muromec", - "dobrynia", "nikitich", "alyosha", - "popovich"}; - CreateAndReopenWithCF(cf_names, options); - ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p'))); - - WriteBatch batch; - ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 1, "ditto", - BlobStr(123, 0, 1 << 10))); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - - ASSERT_OK(Put(2, "ilya", std::string(90000, 'i'))); - ASSERT_OK(Put(3, "muromec", std::string(90000, 'm'))); - ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd'))); - ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n'))); - ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a'))); - ASSERT_OK(Put(7, "popovich", std::string(90000, 'p'))); - for (int i = 1; i < 8; ++i) { - ASSERT_OK(Flush(i)); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[i], - nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - } - - ASSERT_EQ(listener->compacted_dbs_.size(), cf_names.size()); - for (size_t i = 0; i < cf_names.size(); ++i) { - ASSERT_EQ(listener->compacted_dbs_[i], db_); - } -} - -// This simple Listener can only handle one flush at a time. -class TestFlushListener : public EventListener { - public: - TestFlushListener(Env* env, EventListenerTest* test) - : slowdown_count(0), stop_count(0), db_closed(), env_(env), test_(test) { - db_closed = false; - } - - virtual ~TestFlushListener() { - prev_fc_info_.status.PermitUncheckedError(); // Ignore the status - } - void OnTableFileCreated(const TableFileCreationInfo& info) override { - // remember the info for later checking the FlushJobInfo. - prev_fc_info_ = info; - ASSERT_GT(info.db_name.size(), 0U); - ASSERT_GT(info.cf_name.size(), 0U); - ASSERT_GT(info.file_path.size(), 0U); - ASSERT_GT(info.job_id, 0); - ASSERT_GT(info.table_properties.data_size, 0U); - ASSERT_GT(info.table_properties.raw_key_size, 0U); - ASSERT_GT(info.table_properties.raw_value_size, 0U); - ASSERT_GT(info.table_properties.num_data_blocks, 0U); - ASSERT_GT(info.table_properties.num_entries, 0U); - ASSERT_EQ(info.file_checksum, kUnknownFileChecksum); - ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName); - -#ifdef ROCKSDB_USING_THREAD_STATUS - // Verify the id of the current thread that created this table - // file matches the id of any active flush or compaction thread. - uint64_t thread_id = env_->GetThreadID(); - std::vector thread_list; - ASSERT_OK(env_->GetThreadList(&thread_list)); - bool found_match = false; - for (auto thread_status : thread_list) { - if (thread_status.operation_type == ThreadStatus::OP_FLUSH || - thread_status.operation_type == ThreadStatus::OP_COMPACTION) { - if (thread_id == thread_status.thread_id) { - found_match = true; - break; - } - } - } - ASSERT_TRUE(found_match); -#endif // ROCKSDB_USING_THREAD_STATUS - } - - void OnFlushCompleted(DB* db, const FlushJobInfo& info) override { - flushed_dbs_.push_back(db); - flushed_column_family_names_.push_back(info.cf_name); - if (info.triggered_writes_slowdown) { - slowdown_count++; - } - if (info.triggered_writes_stop) { - stop_count++; - } - // verify whether the previously created file matches the flushed file. - ASSERT_EQ(prev_fc_info_.db_name, db->GetName()); - ASSERT_EQ(prev_fc_info_.cf_name, info.cf_name); - ASSERT_EQ(prev_fc_info_.job_id, info.job_id); - ASSERT_EQ(prev_fc_info_.file_path, info.file_path); - ASSERT_EQ(TableFileNameToNumber(info.file_path), info.file_number); - - // Note: the following chunk relies on the notification pertaining to the - // database pointed to by DBTestBase::db_, and is thus bypassed when - // that assumption does not hold (see the test case MultiDBMultiListeners - // below). - ASSERT_TRUE(test_); - if (db == test_->db_) { - std::vector> files_by_level; - ASSERT_LT(info.cf_id, test_->handles_.size()); - ASSERT_GE(info.cf_id, 0u); - ASSERT_NE(test_->handles_[info.cf_id], nullptr); - test_->dbfull()->TEST_GetFilesMetaData(test_->handles_[info.cf_id], - &files_by_level); - - ASSERT_FALSE(files_by_level.empty()); - auto it = std::find_if(files_by_level[0].begin(), files_by_level[0].end(), - [&](const FileMetaData& meta) { - return meta.fd.GetNumber() == info.file_number; - }); - ASSERT_NE(it, files_by_level[0].end()); - ASSERT_EQ(info.oldest_blob_file_number, it->oldest_blob_file_number); - } - - ASSERT_EQ(db->GetEnv()->GetThreadID(), info.thread_id); - ASSERT_GT(info.thread_id, 0U); - ASSERT_EQ(info.table_properties.user_collected_properties.find("0")->second, - "1"); - } - - std::vector flushed_column_family_names_; - std::vector flushed_dbs_; - int slowdown_count; - int stop_count; - bool db_closing; - std::atomic_bool db_closed; - TableFileCreationInfo prev_fc_info_; - - protected: - Env* env_; - EventListenerTest* test_; -}; - -TEST_F(EventListenerTest, OnSingleDBFlushTest) { - Options options; - options.env = CurrentOptions().env; - options.write_buffer_size = k110KB; -#ifdef ROCKSDB_USING_THREAD_STATUS - options.enable_thread_tracking = true; -#endif // ROCKSDB_USING_THREAD_STATUS - TestFlushListener* listener = new TestFlushListener(options.env, this); - options.listeners.emplace_back(listener); - std::vector cf_names = {"pikachu", "ilya", "muromec", - "dobrynia", "nikitich", "alyosha", - "popovich"}; - options.table_properties_collector_factories.push_back( - std::make_shared()); - CreateAndReopenWithCF(cf_names, options); - - ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p'))); - - WriteBatch batch; - ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 1, "ditto", - BlobStr(456, 0, 1 << 10))); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - - ASSERT_OK(Put(2, "ilya", std::string(90000, 'i'))); - ASSERT_OK(Put(3, "muromec", std::string(90000, 'm'))); - ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd'))); - ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n'))); - ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a'))); - ASSERT_OK(Put(7, "popovich", std::string(90000, 'p'))); - for (int i = 1; i < 8; ++i) { - ASSERT_OK(Flush(i)); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork()); - ASSERT_EQ(listener->flushed_dbs_.size(), i); - ASSERT_EQ(listener->flushed_column_family_names_.size(), i); - } - - // make sure callback functions are called in the right order - for (size_t i = 0; i < cf_names.size(); ++i) { - ASSERT_EQ(listener->flushed_dbs_[i], db_); - ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]); - } -} - -TEST_F(EventListenerTest, MultiCF) { - Options options; - options.env = CurrentOptions().env; - options.write_buffer_size = k110KB; -#ifdef ROCKSDB_USING_THREAD_STATUS - options.enable_thread_tracking = true; -#endif // ROCKSDB_USING_THREAD_STATUS - for (auto atomic_flush : {false, true}) { - options.atomic_flush = atomic_flush; - options.create_if_missing = true; - DestroyAndReopen(options); - TestFlushListener* listener = new TestFlushListener(options.env, this); - options.listeners.emplace_back(listener); - options.table_properties_collector_factories.push_back( - std::make_shared()); - std::vector cf_names = {"pikachu", "ilya", "muromec", - "dobrynia", "nikitich", "alyosha", - "popovich"}; - CreateAndReopenWithCF(cf_names, options); - - ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p'))); - ASSERT_OK(Put(2, "ilya", std::string(90000, 'i'))); - ASSERT_OK(Put(3, "muromec", std::string(90000, 'm'))); - ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd'))); - ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n'))); - ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a'))); - ASSERT_OK(Put(7, "popovich", std::string(90000, 'p'))); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - for (int i = 1; i < 8; ++i) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::NotifyOnFlushCompleted::PostAllOnFlushCompleted", - "EventListenerTest.MultiCF:PreVerifyListener"}}); - ASSERT_OK(Flush(i)); - TEST_SYNC_POINT("EventListenerTest.MultiCF:PreVerifyListener"); - ASSERT_EQ(listener->flushed_dbs_.size(), i); - ASSERT_EQ(listener->flushed_column_family_names_.size(), i); - // make sure callback functions are called in the right order - if (i == 7) { - for (size_t j = 0; j < cf_names.size(); j++) { - ASSERT_EQ(listener->flushed_dbs_[j], db_); - ASSERT_EQ(listener->flushed_column_family_names_[j], cf_names[j]); - } - } - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - Close(); - } -} - -TEST_F(EventListenerTest, MultiDBMultiListeners) { - Options options; - options.env = CurrentOptions().env; -#ifdef ROCKSDB_USING_THREAD_STATUS - options.enable_thread_tracking = true; -#endif // ROCKSDB_USING_THREAD_STATUS - options.table_properties_collector_factories.push_back( - std::make_shared()); - std::vector listeners; - const int kNumDBs = 5; - const int kNumListeners = 10; - for (int i = 0; i < kNumListeners; ++i) { - listeners.emplace_back(new TestFlushListener(options.env, this)); - } - - std::vector cf_names = {"pikachu", "ilya", "muromec", - "dobrynia", "nikitich", "alyosha", - "popovich"}; - - options.create_if_missing = true; - for (int i = 0; i < kNumListeners; ++i) { - options.listeners.emplace_back(listeners[i]); - } - DBOptions db_opts(options); - ColumnFamilyOptions cf_opts(options); - - std::vector dbs; - std::vector> vec_handles; - - for (int d = 0; d < kNumDBs; ++d) { - ASSERT_OK(DestroyDB(dbname_ + std::to_string(d), options)); - DB* db; - std::vector handles; - ASSERT_OK(DB::Open(options, dbname_ + std::to_string(d), &db)); - for (size_t c = 0; c < cf_names.size(); ++c) { - ColumnFamilyHandle* handle; - ASSERT_OK(db->CreateColumnFamily(cf_opts, cf_names[c], &handle)); - handles.push_back(handle); - } - - vec_handles.push_back(std::move(handles)); - dbs.push_back(db); - } - - for (int d = 0; d < kNumDBs; ++d) { - for (size_t c = 0; c < cf_names.size(); ++c) { - ASSERT_OK(dbs[d]->Put(WriteOptions(), vec_handles[d][c], cf_names[c], - cf_names[c])); - } - } - - for (size_t c = 0; c < cf_names.size(); ++c) { - for (int d = 0; d < kNumDBs; ++d) { - ASSERT_OK(dbs[d]->Flush(FlushOptions(), vec_handles[d][c])); - ASSERT_OK( - static_cast_with_check(dbs[d])->TEST_WaitForFlushMemTable()); - } - } - - for (int d = 0; d < kNumDBs; ++d) { - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK( - static_cast_with_check(dbs[d])->TEST_WaitForBackgroundWork()); - } - - for (auto* listener : listeners) { - int pos = 0; - for (size_t c = 0; c < cf_names.size(); ++c) { - for (int d = 0; d < kNumDBs; ++d) { - ASSERT_EQ(listener->flushed_dbs_[pos], dbs[d]); - ASSERT_EQ(listener->flushed_column_family_names_[pos], cf_names[c]); - pos++; - } - } - } - - for (auto handles : vec_handles) { - for (auto h : handles) { - delete h; - } - handles.clear(); - } - vec_handles.clear(); - - for (auto db : dbs) { - delete db; - } -} - -TEST_F(EventListenerTest, DisableBGCompaction) { - Options options; - options.env = CurrentOptions().env; -#ifdef ROCKSDB_USING_THREAD_STATUS - options.enable_thread_tracking = true; -#endif // ROCKSDB_USING_THREAD_STATUS - TestFlushListener* listener = new TestFlushListener(options.env, this); - const int kCompactionTrigger = 1; - const int kSlowdownTrigger = 5; - const int kStopTrigger = 100; - options.level0_file_num_compaction_trigger = kCompactionTrigger; - options.level0_slowdown_writes_trigger = kSlowdownTrigger; - options.level0_stop_writes_trigger = kStopTrigger; - options.max_write_buffer_number = 10; - options.listeners.emplace_back(listener); - // BG compaction is disabled. Number of L0 files will simply keeps - // increasing in this test. - options.compaction_style = kCompactionStyleNone; - options.compression = kNoCompression; - options.write_buffer_size = 100000; // Small write buffer - options.table_properties_collector_factories.push_back( - std::make_shared()); - - CreateAndReopenWithCF({"pikachu"}, options); - ColumnFamilyMetaData cf_meta; - db_->GetColumnFamilyMetaData(handles_[1], &cf_meta); - - // keep writing until writes are forced to stop. - for (int i = 0; static_cast(cf_meta.file_count) < kSlowdownTrigger * 10; - ++i) { - ASSERT_OK( - Put(1, std::to_string(i), std::string(10000, 'x'), WriteOptions())); - FlushOptions fo; - fo.allow_write_stall = true; - ASSERT_OK(db_->Flush(fo, handles_[1])); - db_->GetColumnFamilyMetaData(handles_[1], &cf_meta); - } - // Ensure background work is fully finished including listener callbacks - // before accessing listener state. - ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork()); - ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9); -} - -class TestCompactionReasonListener : public EventListener { - public: - void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override { - std::lock_guard lock(mutex_); - compaction_reasons_.push_back(ci.compaction_reason); - } - - std::vector compaction_reasons_; - std::mutex mutex_; -}; - -TEST_F(EventListenerTest, CompactionReasonLevel) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.memtable_factory.reset(test::NewSpecialSkipListFactory( - DBTestBase::kNumKeysByGenerateNewRandomFile)); - - TestCompactionReasonListener* listener = new TestCompactionReasonListener(); - options.listeners.emplace_back(listener); - - options.level0_file_num_compaction_trigger = 4; - options.compaction_style = kCompactionStyleLevel; - - DestroyAndReopen(options); - Random rnd(301); - - // Write 4 files in L0 - for (int i = 0; i < 4; i++) { - GenerateNewRandomFile(&rnd); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(listener->compaction_reasons_.size(), 1); - ASSERT_EQ(listener->compaction_reasons_[0], - CompactionReason::kLevelL0FilesNum); - - DestroyAndReopen(options); - - // Write 3 non-overlapping files in L0 - for (int k = 1; k <= 30; k++) { - ASSERT_OK(Put(Key(k), Key(k))); - if (k % 10 == 0) { - Flush(); - } - } - - // Do a trivial move from L0 -> L1 - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - options.max_bytes_for_level_base = 1; - Close(); - listener->compaction_reasons_.clear(); - Reopen(options); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_GT(listener->compaction_reasons_.size(), 1); - - for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kLevelMaxLevelSize); - } - - options.disable_auto_compactions = true; - Close(); - listener->compaction_reasons_.clear(); - Reopen(options); - - ASSERT_OK(Put("key", "value")); - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_GT(listener->compaction_reasons_.size(), 0); - for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction); - } -} - -TEST_F(EventListenerTest, CompactionReasonUniversal) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.memtable_factory.reset(test::NewSpecialSkipListFactory( - DBTestBase::kNumKeysByGenerateNewRandomFile)); - - TestCompactionReasonListener* listener = new TestCompactionReasonListener(); - options.listeners.emplace_back(listener); - - options.compaction_style = kCompactionStyleUniversal; - - Random rnd(301); - - options.level0_file_num_compaction_trigger = 8; - options.compaction_options_universal.max_size_amplification_percent = 100000; - options.compaction_options_universal.size_ratio = 100000; - DestroyAndReopen(options); - listener->compaction_reasons_.clear(); - - // Write 8 files in L0 - for (int i = 0; i < 8; i++) { - GenerateNewRandomFile(&rnd); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(listener->compaction_reasons_.size(), 0); - for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeRatio); - } - - options.level0_file_num_compaction_trigger = 8; - options.compaction_options_universal.max_size_amplification_percent = 1; - options.compaction_options_universal.size_ratio = 100000; - - DestroyAndReopen(options); - listener->compaction_reasons_.clear(); - - // Write 8 files in L0 - for (int i = 0; i < 8; i++) { - GenerateNewRandomFile(&rnd); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(listener->compaction_reasons_.size(), 0); - for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeAmplification); - } - - options.disable_auto_compactions = true; - Close(); - listener->compaction_reasons_.clear(); - Reopen(options); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - ASSERT_GT(listener->compaction_reasons_.size(), 0); - for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction); - } -} - -TEST_F(EventListenerTest, CompactionReasonFIFO) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.memtable_factory.reset(test::NewSpecialSkipListFactory( - DBTestBase::kNumKeysByGenerateNewRandomFile)); - - TestCompactionReasonListener* listener = new TestCompactionReasonListener(); - options.listeners.emplace_back(listener); - - options.level0_file_num_compaction_trigger = 4; - options.compaction_style = kCompactionStyleFIFO; - options.compaction_options_fifo.max_table_files_size = 1; - - DestroyAndReopen(options); - Random rnd(301); - - // Write 4 files in L0 - for (int i = 0; i < 4; i++) { - GenerateNewRandomFile(&rnd); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_GT(listener->compaction_reasons_.size(), 0); - for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kFIFOMaxSize); - } -} - -class TableFileCreationListener : public EventListener { - public: - class TestFS : public FileSystemWrapper { - public: - explicit TestFS(const std::shared_ptr& t) - : FileSystemWrapper(t) {} - static const char* kClassName() { return "TestEnv"; } - const char* Name() const override { return kClassName(); } - - void SetStatus(IOStatus s) { status_ = s; } - - IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts, - std::unique_ptr* result, - IODebugContext* dbg) override { - if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".sst") { - if (!status_.ok()) { - return status_; - } - } - return target()->NewWritableFile(fname, opts, result, dbg); - } - - private: - IOStatus status_; - }; - - TableFileCreationListener() { - for (int i = 0; i < 2; i++) { - started_[i] = finished_[i] = failure_[i] = 0; - } - } - - int Index(TableFileCreationReason reason) { - int idx; - switch (reason) { - case TableFileCreationReason::kFlush: - idx = 0; - break; - case TableFileCreationReason::kCompaction: - idx = 1; - break; - default: - idx = -1; - } - return idx; - } - - void CheckAndResetCounters(int flush_started, int flush_finished, - int flush_failure, int compaction_started, - int compaction_finished, int compaction_failure) { - ASSERT_EQ(started_[0], flush_started); - ASSERT_EQ(finished_[0], flush_finished); - ASSERT_EQ(failure_[0], flush_failure); - ASSERT_EQ(started_[1], compaction_started); - ASSERT_EQ(finished_[1], compaction_finished); - ASSERT_EQ(failure_[1], compaction_failure); - for (int i = 0; i < 2; i++) { - started_[i] = finished_[i] = failure_[i] = 0; - } - } - - void OnTableFileCreationStarted( - const TableFileCreationBriefInfo& info) override { - int idx = Index(info.reason); - if (idx >= 0) { - started_[idx]++; - } - ASSERT_GT(info.db_name.size(), 0U); - ASSERT_GT(info.cf_name.size(), 0U); - ASSERT_GT(info.file_path.size(), 0U); - ASSERT_GT(info.job_id, 0); - } - - void OnTableFileCreated(const TableFileCreationInfo& info) override { - int idx = Index(info.reason); - if (idx >= 0) { - finished_[idx]++; - } - ASSERT_GT(info.db_name.size(), 0U); - ASSERT_GT(info.cf_name.size(), 0U); - ASSERT_GT(info.file_path.size(), 0U); - ASSERT_GT(info.job_id, 0); - ASSERT_EQ(info.file_checksum, kUnknownFileChecksum); - ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName); - if (info.status.ok()) { - if (info.table_properties.num_range_deletions == 0U) { - ASSERT_GT(info.table_properties.data_size, 0U); - ASSERT_GT(info.table_properties.raw_key_size, 0U); - ASSERT_GT(info.table_properties.raw_value_size, 0U); - ASSERT_GT(info.table_properties.num_data_blocks, 0U); - ASSERT_GT(info.table_properties.num_entries, 0U); - } - } else { - if (idx >= 0) { - failure_[idx]++; - last_failure_ = info.status; - } - } - } - - int started_[2]; - int finished_[2]; - int failure_[2]; - Status last_failure_; -}; - -TEST_F(EventListenerTest, TableFileCreationListenersTest) { - auto listener = std::make_shared(); - Options options; - std::shared_ptr test_fs = - std::make_shared( - CurrentOptions().env->GetFileSystem()); - std::unique_ptr test_env = NewCompositeEnv(test_fs); - options.create_if_missing = true; - options.listeners.push_back(listener); - options.env = test_env.get(); - DestroyAndReopen(options); - - ASSERT_OK(Put("foo", "aaa")); - ASSERT_OK(Put("bar", "bbb")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0); - ASSERT_OK(Put("foo", "aaa1")); - ASSERT_OK(Put("bar", "bbb1")); - test_fs->SetStatus(IOStatus::NotSupported("not supported")); - ASSERT_NOK(Flush()); - listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0); - ASSERT_TRUE(listener->last_failure_.IsNotSupported()); - test_fs->SetStatus(IOStatus::OK()); - - Reopen(options); - ASSERT_OK(Put("foo", "aaa2")); - ASSERT_OK(Put("bar", "bbb2")); - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0); - - const Slice kRangeStart = "a"; - const Slice kRangeEnd = "z"; - ASSERT_OK( - dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - listener->CheckAndResetCounters(0, 0, 0, 1, 1, 0); - - ASSERT_OK(Put("foo", "aaa3")); - ASSERT_OK(Put("bar", "bbb3")); - ASSERT_OK(Flush()); - test_fs->SetStatus(IOStatus::NotSupported("not supported")); - ASSERT_NOK( - dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd)); - ASSERT_NOK(dbfull()->TEST_WaitForCompact()); - listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1); - ASSERT_TRUE(listener->last_failure_.IsNotSupported()); - - // Reset - test_fs->SetStatus(IOStatus::OK()); - DestroyAndReopen(options); - - // Verify that an empty table file that is immediately deleted gives Aborted - // status to listener. - ASSERT_OK(Put("baz", "z")); - ASSERT_OK(SingleDelete("baz")); - ASSERT_OK(Flush()); - listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0); - ASSERT_TRUE(listener->last_failure_.IsAborted()); - - // Also in compaction - ASSERT_OK(Put("baz", "z")); - ASSERT_OK(Flush()); - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), - kRangeStart, kRangeEnd)); - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - listener->CheckAndResetCounters(2, 2, 0, 1, 1, 1); - ASSERT_TRUE(listener->last_failure_.IsAborted()); - - Close(); // Avoid UAF on listener -} - -class MemTableSealedListener : public EventListener { - private: - SequenceNumber latest_seq_number_; - - public: - MemTableSealedListener() {} - void OnMemTableSealed(const MemTableInfo& info) override { - latest_seq_number_ = info.first_seqno; - } - - void OnFlushCompleted(DB* /*db*/, - const FlushJobInfo& flush_job_info) override { - ASSERT_LE(flush_job_info.smallest_seqno, latest_seq_number_); - } -}; - -TEST_F(EventListenerTest, MemTableSealedListenerTest) { - auto listener = std::make_shared(); - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.listeners.push_back(listener); - DestroyAndReopen(options); - - for (unsigned int i = 0; i < 10; i++) { - std::string tag = std::to_string(i); - ASSERT_OK(Put("foo" + tag, "aaa")); - ASSERT_OK(Put("bar" + tag, "bbb")); - - ASSERT_OK(Flush()); - } -} - -class ColumnFamilyHandleDeletionStartedListener : public EventListener { - private: - std::vector cfs_; - int counter; - - public: - explicit ColumnFamilyHandleDeletionStartedListener( - const std::vector& cfs) - : cfs_(cfs), counter(0) { - cfs_.insert(cfs_.begin(), kDefaultColumnFamilyName); - } - void OnColumnFamilyHandleDeletionStarted( - ColumnFamilyHandle* handle) override { - ASSERT_EQ(cfs_[handle->GetID()], handle->GetName()); - counter++; - } - int getCounter() { return counter; } -}; - -TEST_F(EventListenerTest, ColumnFamilyHandleDeletionStartedListenerTest) { - std::vector cfs{"pikachu", "eevee", "Mewtwo"}; - auto listener = - std::make_shared(cfs); - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - options.listeners.push_back(listener); - CreateAndReopenWithCF(cfs, options); - ASSERT_EQ(handles_.size(), 4); - delete handles_[3]; - delete handles_[2]; - delete handles_[1]; - handles_.resize(1); - ASSERT_EQ(listener->getCounter(), 3); -} - -class BackgroundErrorListener : public EventListener { - private: - SpecialEnv* env_; - int counter_; - - public: - BackgroundErrorListener(SpecialEnv* env) : env_(env), counter_(0) {} - - void OnBackgroundError(BackgroundErrorReason /*reason*/, - Status* bg_error) override { - if (counter_ == 0) { - // suppress the first error and disable write-dropping such that a retry - // can succeed. - *bg_error = Status::OK(); - env_->drop_writes_.store(false, std::memory_order_release); - env_->SetMockSleep(false); - } - ++counter_; - } - - int counter() { return counter_; } -}; - -TEST_F(EventListenerTest, BackgroundErrorListenerFailedFlushTest) { - auto listener = std::make_shared(env_); - Options options; - options.create_if_missing = true; - options.env = env_; - options.listeners.push_back(listener); - options.memtable_factory.reset(test::NewSpecialSkipListFactory(1)); - options.paranoid_checks = true; - DestroyAndReopen(options); - - // the usual TEST_WaitForFlushMemTable() doesn't work for failed flushes, so - // forge a custom one for the failed flush case. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BGWorkFlush:done", - "EventListenerTest:BackgroundErrorListenerFailedFlushTest:1"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - env_->drop_writes_.store(true, std::memory_order_release); - env_->SetMockSleep(); - - ASSERT_OK(Put("key0", "val")); - ASSERT_OK(Put("key1", "val")); - TEST_SYNC_POINT("EventListenerTest:BackgroundErrorListenerFailedFlushTest:1"); - ASSERT_EQ(1, listener->counter()); - ASSERT_OK(Put("key2", "val")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); -} - -TEST_F(EventListenerTest, BackgroundErrorListenerFailedCompactionTest) { - auto listener = std::make_shared(env_); - Options options; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.env = env_; - options.level0_file_num_compaction_trigger = 2; - options.listeners.push_back(listener); - options.memtable_factory.reset(test::NewSpecialSkipListFactory(2)); - options.paranoid_checks = true; - DestroyAndReopen(options); - - // third iteration triggers the second memtable's flush - for (int i = 0; i < 3; ++i) { - ASSERT_OK(Put("key0", "val")); - if (i > 0) { - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - } - ASSERT_OK(Put("key1", "val")); - } - ASSERT_EQ(2, NumTableFilesAtLevel(0)); - - env_->drop_writes_.store(true, std::memory_order_release); - env_->SetMockSleep(); - ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}})); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(1, listener->counter()); - - // trigger flush so compaction is triggered again; this time it succeeds - // The previous failed compaction may get retried automatically, so we may - // be left with 0 or 1 files in level 1, depending on when the retry gets - // scheduled - ASSERT_OK(Put("key0", "val")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_LE(1, NumTableFilesAtLevel(0)); -} - -class TestFileOperationListener : public EventListener { - public: - TestFileOperationListener() { - file_reads_.store(0); - file_reads_success_.store(0); - file_writes_.store(0); - file_writes_success_.store(0); - file_flushes_.store(0); - file_flushes_success_.store(0); - file_closes_.store(0); - file_closes_success_.store(0); - file_syncs_.store(0); - file_syncs_success_.store(0); - file_truncates_.store(0); - file_truncates_success_.store(0); - file_seq_reads_.store(0); - blob_file_reads_.store(0); - blob_file_writes_.store(0); - blob_file_flushes_.store(0); - blob_file_closes_.store(0); - blob_file_syncs_.store(0); - blob_file_truncates_.store(0); - } - - void OnFileReadFinish(const FileOperationInfo& info) override { - ++file_reads_; - if (info.status.ok()) { - ++file_reads_success_; - } - if (info.path.find("MANIFEST") != std::string::npos) { - ++file_seq_reads_; - } - if (EndsWith(info.path, ".blob")) { - ++blob_file_reads_; - } - ReportDuration(info); - } - - void OnFileWriteFinish(const FileOperationInfo& info) override { - ++file_writes_; - if (info.status.ok()) { - ++file_writes_success_; - } - if (EndsWith(info.path, ".blob")) { - ++blob_file_writes_; - } - ReportDuration(info); - } - - void OnFileFlushFinish(const FileOperationInfo& info) override { - ++file_flushes_; - if (info.status.ok()) { - ++file_flushes_success_; - } - if (EndsWith(info.path, ".blob")) { - ++blob_file_flushes_; - } - ReportDuration(info); - } - - void OnFileCloseFinish(const FileOperationInfo& info) override { - ++file_closes_; - if (info.status.ok()) { - ++file_closes_success_; - } - if (EndsWith(info.path, ".blob")) { - ++blob_file_closes_; - } - ReportDuration(info); - } - - void OnFileSyncFinish(const FileOperationInfo& info) override { - ++file_syncs_; - if (info.status.ok()) { - ++file_syncs_success_; - } - if (EndsWith(info.path, ".blob")) { - ++blob_file_syncs_; - } - ReportDuration(info); - } - - void OnFileTruncateFinish(const FileOperationInfo& info) override { - ++file_truncates_; - if (info.status.ok()) { - ++file_truncates_success_; - } - if (EndsWith(info.path, ".blob")) { - ++blob_file_truncates_; - } - ReportDuration(info); - } - - bool ShouldBeNotifiedOnFileIO() override { return true; } - - std::atomic file_reads_; - std::atomic file_reads_success_; - std::atomic file_writes_; - std::atomic file_writes_success_; - std::atomic file_flushes_; - std::atomic file_flushes_success_; - std::atomic file_closes_; - std::atomic file_closes_success_; - std::atomic file_syncs_; - std::atomic file_syncs_success_; - std::atomic file_truncates_; - std::atomic file_truncates_success_; - std::atomic file_seq_reads_; - std::atomic blob_file_reads_; - std::atomic blob_file_writes_; - std::atomic blob_file_flushes_; - std::atomic blob_file_closes_; - std::atomic blob_file_syncs_; - std::atomic blob_file_truncates_; - - private: - void ReportDuration(const FileOperationInfo& info) const { - ASSERT_GT(info.duration.count(), 0); - } -}; - -TEST_F(EventListenerTest, OnFileOperationTest) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - - TestFileOperationListener* listener = new TestFileOperationListener(); - options.listeners.emplace_back(listener); - - options.use_direct_io_for_flush_and_compaction = false; - Status s = TryReopen(options); - if (s.IsInvalidArgument()) { - options.use_direct_io_for_flush_and_compaction = false; - } else { - ASSERT_OK(s); - } - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "aaa")); - ASSERT_OK(dbfull()->Flush(FlushOptions())); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_GE(listener->file_writes_.load(), - listener->file_writes_success_.load()); - ASSERT_GT(listener->file_writes_.load(), 0); - ASSERT_GE(listener->file_flushes_.load(), - listener->file_flushes_success_.load()); - ASSERT_GT(listener->file_flushes_.load(), 0); - Close(); - - Reopen(options); - ASSERT_GE(listener->file_reads_.load(), listener->file_reads_success_.load()); - ASSERT_GT(listener->file_reads_.load(), 0); - ASSERT_GE(listener->file_closes_.load(), - listener->file_closes_success_.load()); - ASSERT_GT(listener->file_closes_.load(), 0); - ASSERT_GE(listener->file_syncs_.load(), listener->file_syncs_success_.load()); - ASSERT_GT(listener->file_syncs_.load(), 0); - if (true == options.use_direct_io_for_flush_and_compaction) { - ASSERT_GE(listener->file_truncates_.load(), - listener->file_truncates_success_.load()); - ASSERT_GT(listener->file_truncates_.load(), 0); - } -} - -TEST_F(EventListenerTest, OnBlobFileOperationTest) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - TestFileOperationListener* listener = new TestFileOperationListener(); - options.listeners.emplace_back(listener); - options.disable_auto_compactions = true; - options.enable_blob_files = true; - options.min_blob_size = 0; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0.5; - - DestroyAndReopen(options); - - ASSERT_OK(Put("Key1", "blob_value1")); - ASSERT_OK(Put("Key2", "blob_value2")); - ASSERT_OK(Put("Key3", "blob_value3")); - ASSERT_OK(Put("Key4", "blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key3", "new_blob_value3")); - ASSERT_OK(Put("Key4", "new_blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key5", "blob_value5")); - ASSERT_OK(Put("Key6", "blob_value6")); - ASSERT_OK(Flush()); - - ASSERT_GT(listener->blob_file_writes_.load(), 0U); - ASSERT_GT(listener->blob_file_flushes_.load(), 0U); - Close(); - - Reopen(options); - ASSERT_GT(listener->blob_file_closes_.load(), 0U); - ASSERT_GT(listener->blob_file_syncs_.load(), 0U); - if (true == options.use_direct_io_for_flush_and_compaction) { - ASSERT_GT(listener->blob_file_truncates_.load(), 0U); - } -} - -TEST_F(EventListenerTest, ReadManifestAndWALOnRecovery) { - Options options; - options.env = CurrentOptions().env; - options.create_if_missing = true; - - TestFileOperationListener* listener = new TestFileOperationListener(); - options.listeners.emplace_back(listener); - - options.use_direct_io_for_flush_and_compaction = false; - Status s = TryReopen(options); - if (s.IsInvalidArgument()) { - options.use_direct_io_for_flush_and_compaction = false; - } else { - ASSERT_OK(s); - } - DestroyAndReopen(options); - ASSERT_OK(Put("foo", "aaa")); - Close(); - - size_t seq_reads = listener->file_seq_reads_.load(); - Reopen(options); - ASSERT_GT(listener->file_seq_reads_.load(), seq_reads); -} - -class BlobDBJobLevelEventListenerTest : public EventListener { - public: - explicit BlobDBJobLevelEventListenerTest(EventListenerTest* test) - : test_(test), call_count_(0) {} - - const VersionStorageInfo* GetVersionStorageInfo() const { - VersionSet* const versions = test_->dbfull()->GetVersionSet(); - assert(versions); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - EXPECT_NE(cfd, nullptr); - - Version* const current = cfd->current(); - EXPECT_NE(current, nullptr); - - const VersionStorageInfo* const storage_info = current->storage_info(); - EXPECT_NE(storage_info, nullptr); - - return storage_info; - } - - void CheckBlobFileAdditions( - const std::vector& blob_file_addition_infos) const { - const auto* vstorage = GetVersionStorageInfo(); - - EXPECT_FALSE(blob_file_addition_infos.empty()); - - for (const auto& blob_file_addition_info : blob_file_addition_infos) { - const auto meta = vstorage->GetBlobFileMetaData( - blob_file_addition_info.blob_file_number); - - EXPECT_NE(meta, nullptr); - EXPECT_EQ(meta->GetBlobFileNumber(), - blob_file_addition_info.blob_file_number); - EXPECT_EQ(meta->GetTotalBlobBytes(), - blob_file_addition_info.total_blob_bytes); - EXPECT_EQ(meta->GetTotalBlobCount(), - blob_file_addition_info.total_blob_count); - EXPECT_FALSE(blob_file_addition_info.blob_file_path.empty()); - } - } - - std::vector GetFlushedFiles() { - std::lock_guard lock(mutex_); - std::vector result; - for (const auto& fname : flushed_files_) { - result.push_back(fname); - } - return result; - } - - void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { - call_count_++; - - { - std::lock_guard lock(mutex_); - flushed_files_.push_back(info.file_path); - } - - EXPECT_EQ(info.blob_compression_type, kNoCompression); - - CheckBlobFileAdditions(info.blob_file_addition_infos); - } - - void OnCompactionCompleted(DB* /*db*/, - const CompactionJobInfo& info) override { - call_count_++; - - EXPECT_EQ(info.blob_compression_type, kNoCompression); - - CheckBlobFileAdditions(info.blob_file_addition_infos); - - EXPECT_FALSE(info.blob_file_garbage_infos.empty()); - - for (const auto& blob_file_garbage_info : info.blob_file_garbage_infos) { - EXPECT_GT(blob_file_garbage_info.blob_file_number, 0U); - EXPECT_GT(blob_file_garbage_info.garbage_blob_count, 0U); - EXPECT_GT(blob_file_garbage_info.garbage_blob_bytes, 0U); - EXPECT_FALSE(blob_file_garbage_info.blob_file_path.empty()); - } - } - - EventListenerTest* test_; - uint32_t call_count_; - - private: - std::vector flushed_files_; - std::mutex mutex_; -}; - -// Test OnFlushCompleted EventListener called for blob files -TEST_F(EventListenerTest, BlobDBOnFlushCompleted) { - Options options; - options.env = CurrentOptions().env; - options.enable_blob_files = true; - options.create_if_missing = true; - options.disable_auto_compactions = true; - - options.min_blob_size = 0; - BlobDBJobLevelEventListenerTest* blob_event_listener = - new BlobDBJobLevelEventListenerTest(this); - options.listeners.emplace_back(blob_event_listener); - - DestroyAndReopen(options); - - ASSERT_OK(Put("Key1", "blob_value1")); - ASSERT_OK(Put("Key2", "blob_value2")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key3", "blob_value3")); - ASSERT_OK(Flush()); - - ASSERT_EQ(Get("Key1"), "blob_value1"); - ASSERT_EQ(Get("Key2"), "blob_value2"); - ASSERT_EQ(Get("Key3"), "blob_value3"); - - ASSERT_GT(blob_event_listener->call_count_, 0U); -} - -// Test OnCompactionCompleted EventListener called for blob files -TEST_F(EventListenerTest, BlobDBOnCompactionCompleted) { - Options options; - options.env = CurrentOptions().env; - options.enable_blob_files = true; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.min_blob_size = 0; - BlobDBJobLevelEventListenerTest* blob_event_listener = - new BlobDBJobLevelEventListenerTest(this); - options.listeners.emplace_back(blob_event_listener); - - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0.5; - - DestroyAndReopen(options); - - ASSERT_OK(Put("Key1", "blob_value1")); - ASSERT_OK(Put("Key2", "blob_value2")); - ASSERT_OK(Put("Key3", "blob_value3")); - ASSERT_OK(Put("Key4", "blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key3", "new_blob_value3")); - ASSERT_OK(Put("Key4", "new_blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key5", "blob_value5")); - ASSERT_OK(Put("Key6", "blob_value6")); - ASSERT_OK(Flush()); - - blob_event_listener->call_count_ = 0; - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - // On compaction, because of blob_garbage_collection_age_cutoff, it will - // delete the oldest blob file and create new blob file during compaction. - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end)); - - // Make sure, OnCompactionCompleted is called. - ASSERT_GT(blob_event_listener->call_count_, 0U); -} - -// Test CompactFiles calls OnCompactionCompleted EventListener for blob files -// and populate the blob files info. -TEST_F(EventListenerTest, BlobDBCompactFiles) { - Options options; - options.env = CurrentOptions().env; - options.enable_blob_files = true; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.min_blob_size = 0; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0.5; - - BlobDBJobLevelEventListenerTest* blob_event_listener = - new BlobDBJobLevelEventListenerTest(this); - options.listeners.emplace_back(blob_event_listener); - - DestroyAndReopen(options); - - ASSERT_OK(Put("Key1", "blob_value1")); - ASSERT_OK(Put("Key2", "blob_value2")); - ASSERT_OK(Put("Key3", "blob_value3")); - ASSERT_OK(Put("Key4", "blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key3", "new_blob_value3")); - ASSERT_OK(Put("Key4", "new_blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key5", "blob_value5")); - ASSERT_OK(Put("Key6", "blob_value6")); - ASSERT_OK(Flush()); - - std::vector output_file_names; - CompactionJobInfo compaction_job_info; - - // On compaction, because of blob_garbage_collection_age_cutoff, it will - // delete the oldest blob file and create new blob file during compaction - // which will be populated in output_files_names. - ASSERT_OK(dbfull()->CompactFiles( - CompactionOptions(), blob_event_listener->GetFlushedFiles(), 1, -1, - &output_file_names, &compaction_job_info)); - - bool is_blob_in_output = false; - for (const auto& file : output_file_names) { - if (EndsWith(file, ".blob")) { - is_blob_in_output = true; - } - } - ASSERT_TRUE(is_blob_in_output); - - for (const auto& blob_file_addition_info : - compaction_job_info.blob_file_addition_infos) { - EXPECT_GT(blob_file_addition_info.blob_file_number, 0U); - EXPECT_GT(blob_file_addition_info.total_blob_bytes, 0U); - EXPECT_GT(blob_file_addition_info.total_blob_count, 0U); - EXPECT_FALSE(blob_file_addition_info.blob_file_path.empty()); - } - - for (const auto& blob_file_garbage_info : - compaction_job_info.blob_file_garbage_infos) { - EXPECT_GT(blob_file_garbage_info.blob_file_number, 0U); - EXPECT_GT(blob_file_garbage_info.garbage_blob_count, 0U); - EXPECT_GT(blob_file_garbage_info.garbage_blob_bytes, 0U); - EXPECT_FALSE(blob_file_garbage_info.blob_file_path.empty()); - } -} - -class BlobDBFileLevelEventListener : public EventListener { - public: - void OnBlobFileCreationStarted( - const BlobFileCreationBriefInfo& info) override { - files_started_++; - EXPECT_FALSE(info.db_name.empty()); - EXPECT_FALSE(info.cf_name.empty()); - EXPECT_FALSE(info.file_path.empty()); - EXPECT_GT(info.job_id, 0); - } - - void OnBlobFileCreated(const BlobFileCreationInfo& info) override { - files_created_++; - EXPECT_FALSE(info.db_name.empty()); - EXPECT_FALSE(info.cf_name.empty()); - EXPECT_FALSE(info.file_path.empty()); - EXPECT_GT(info.job_id, 0); - EXPECT_GT(info.total_blob_count, 0U); - EXPECT_GT(info.total_blob_bytes, 0U); - EXPECT_EQ(info.file_checksum, kUnknownFileChecksum); - EXPECT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName); - EXPECT_TRUE(info.status.ok()); - } - - void OnBlobFileDeleted(const BlobFileDeletionInfo& info) override { - files_deleted_++; - EXPECT_FALSE(info.db_name.empty()); - EXPECT_FALSE(info.file_path.empty()); - EXPECT_GT(info.job_id, 0); - EXPECT_TRUE(info.status.ok()); - } - - void CheckCounters() { - EXPECT_EQ(files_started_, files_created_); - EXPECT_GT(files_started_, 0U); - EXPECT_GT(files_deleted_, 0U); - EXPECT_LT(files_deleted_, files_created_); - } - - private: - std::atomic files_started_{}; - std::atomic files_created_{}; - std::atomic files_deleted_{}; -}; - -TEST_F(EventListenerTest, BlobDBFileTest) { - Options options; - options.env = CurrentOptions().env; - options.enable_blob_files = true; - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.min_blob_size = 0; - options.enable_blob_garbage_collection = true; - options.blob_garbage_collection_age_cutoff = 0.5; - - BlobDBFileLevelEventListener* blob_event_listener = - new BlobDBFileLevelEventListener(); - options.listeners.emplace_back(blob_event_listener); - - DestroyAndReopen(options); - - ASSERT_OK(Put("Key1", "blob_value1")); - ASSERT_OK(Put("Key2", "blob_value2")); - ASSERT_OK(Put("Key3", "blob_value3")); - ASSERT_OK(Put("Key4", "blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key3", "new_blob_value3")); - ASSERT_OK(Put("Key4", "new_blob_value4")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("Key5", "blob_value5")); - ASSERT_OK(Put("Key6", "blob_value6")); - ASSERT_OK(Flush()); - - constexpr Slice* begin = nullptr; - constexpr Slice* end = nullptr; - - // On compaction, because of blob_garbage_collection_age_cutoff, it will - // delete the oldest blob file and create new blob file during compaction. - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end)); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - blob_event_listener->CheckCounters(); -} - -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/log_test.cc b/db/log_test.cc deleted file mode 100644 index f4d388f41..000000000 --- a/db/log_test.cc +++ /dev/null @@ -1,1095 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/log_reader.h" -#include "db/log_writer.h" -#include "file/sequence_file_reader.h" -#include "file/writable_file_writer.h" -#include "rocksdb/env.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding.h" -#include "util/crc32c.h" -#include "util/random.h" -#include "utilities/memory_allocators.h" - -namespace ROCKSDB_NAMESPACE { -namespace log { - -// Construct a string of the specified length made out of the supplied -// partial string. -static std::string BigString(const std::string& partial_string, size_t n) { - std::string result; - while (result.size() < n) { - result.append(partial_string); - } - result.resize(n); - return result; -} - -// Construct a string from a number -static std::string NumberString(int n) { - char buf[50]; - snprintf(buf, sizeof(buf), "%d.", n); - return std::string(buf); -} - -// Return a skewed potentially long string -static std::string RandomSkewedString(int i, Random* rnd) { - return BigString(NumberString(i), rnd->Skewed(17)); -} - -// Param type is tuple -// get<0>(tuple): non-zero if recycling log, zero if regular log -// get<1>(tuple): true if allow retry after read EOF, false otherwise -class LogTest - : public ::testing::TestWithParam> { - private: - class StringSource : public FSSequentialFile { - public: - Slice& contents_; - bool force_error_; - size_t force_error_position_; - bool force_eof_; - size_t force_eof_position_; - bool returned_partial_; - bool fail_after_read_partial_; - explicit StringSource(Slice& contents, bool fail_after_read_partial) - : contents_(contents), - force_error_(false), - force_error_position_(0), - force_eof_(false), - force_eof_position_(0), - returned_partial_(false), - fail_after_read_partial_(fail_after_read_partial) {} - - IOStatus Read(size_t n, const IOOptions& /*opts*/, Slice* result, - char* scratch, IODebugContext* /*dbg*/) override { - if (fail_after_read_partial_) { - EXPECT_TRUE(!returned_partial_) << "must not Read() after eof/error"; - } - - if (force_error_) { - if (force_error_position_ >= n) { - force_error_position_ -= n; - } else { - *result = Slice(contents_.data(), force_error_position_); - contents_.remove_prefix(force_error_position_); - force_error_ = false; - returned_partial_ = true; - return IOStatus::Corruption("read error"); - } - } - - if (contents_.size() < n) { - n = contents_.size(); - returned_partial_ = true; - } - - if (force_eof_) { - if (force_eof_position_ >= n) { - force_eof_position_ -= n; - } else { - force_eof_ = false; - n = force_eof_position_; - returned_partial_ = true; - } - } - - // By using scratch we ensure that caller has control over the - // lifetime of result.data() - memcpy(scratch, contents_.data(), n); - *result = Slice(scratch, n); - - contents_.remove_prefix(n); - return IOStatus::OK(); - } - - IOStatus Skip(uint64_t n) override { - if (n > contents_.size()) { - contents_.clear(); - return IOStatus::NotFound("in-memory file skipepd past end"); - } - - contents_.remove_prefix(n); - - return IOStatus::OK(); - } - }; - - class ReportCollector : public Reader::Reporter { - public: - size_t dropped_bytes_; - std::string message_; - - ReportCollector() : dropped_bytes_(0) {} - void Corruption(size_t bytes, const Status& status) override { - dropped_bytes_ += bytes; - message_.append(status.ToString()); - } - }; - - std::string& dest_contents() { return sink_->contents_; } - - const std::string& dest_contents() const { return sink_->contents_; } - - void reset_source_contents() { source_->contents_ = dest_contents(); } - - Slice reader_contents_; - test::StringSink* sink_; - StringSource* source_; - ReportCollector report_; - - protected: - std::unique_ptr writer_; - std::unique_ptr reader_; - bool allow_retry_read_; - CompressionType compression_type_; - - public: - LogTest() - : reader_contents_(), - sink_(new test::StringSink(&reader_contents_)), - source_(new StringSource(reader_contents_, !std::get<1>(GetParam()))), - allow_retry_read_(std::get<1>(GetParam())), - compression_type_(std::get<2>(GetParam())) { - std::unique_ptr sink_holder(sink_); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(sink_holder), "" /* don't care */, FileOptions())); - Writer* writer = - new Writer(std::move(file_writer), 123, std::get<0>(GetParam()), false, - compression_type_); - writer_.reset(writer); - std::unique_ptr source_holder(source_); - std::unique_ptr file_reader( - new SequentialFileReader(std::move(source_holder), "" /* file name */)); - if (allow_retry_read_) { - reader_.reset(new FragmentBufferedReader(nullptr, std::move(file_reader), - &report_, true /* checksum */, - 123 /* log_number */)); - } else { - reader_.reset(new Reader(nullptr, std::move(file_reader), &report_, - true /* checksum */, 123 /* log_number */)); - } - } - - Slice* get_reader_contents() { return &reader_contents_; } - - void Write(const std::string& msg) { - ASSERT_OK(writer_->AddRecord(Slice(msg))); - } - - size_t WrittenBytes() const { return dest_contents().size(); } - - std::string Read(const WALRecoveryMode wal_recovery_mode = - WALRecoveryMode::kTolerateCorruptedTailRecords) { - std::string scratch; - Slice record; - bool ret = false; - uint64_t record_checksum; - ret = reader_->ReadRecord(&record, &scratch, wal_recovery_mode, - &record_checksum); - if (ret) { - if (!allow_retry_read_) { - // allow_retry_read_ means using FragmentBufferedReader which does not - // support record checksum yet. - uint64_t actual_record_checksum = - XXH3_64bits(record.data(), record.size()); - assert(actual_record_checksum == record_checksum); - } - return record.ToString(); - } else { - return "EOF"; - } - } - - void IncrementByte(int offset, char delta) { - dest_contents()[offset] += delta; - } - - void SetByte(int offset, char new_byte) { - dest_contents()[offset] = new_byte; - } - - void ShrinkSize(int bytes) { sink_->Drop(bytes); } - - void FixChecksum(int header_offset, int len, bool recyclable) { - // Compute crc of type/len/data - int header_size = recyclable ? kRecyclableHeaderSize : kHeaderSize; - uint32_t crc = crc32c::Value(&dest_contents()[header_offset + 6], - header_size - 6 + len); - crc = crc32c::Mask(crc); - EncodeFixed32(&dest_contents()[header_offset], crc); - } - - void ForceError(size_t position = 0) { - source_->force_error_ = true; - source_->force_error_position_ = position; - } - - size_t DroppedBytes() const { return report_.dropped_bytes_; } - - std::string ReportMessage() const { return report_.message_; } - - void ForceEOF(size_t position = 0) { - source_->force_eof_ = true; - source_->force_eof_position_ = position; - } - - void UnmarkEOF() { - source_->returned_partial_ = false; - reader_->UnmarkEOF(); - } - - bool IsEOF() { return reader_->IsEOF(); } - - // Returns OK iff recorded error message contains "msg" - std::string MatchError(const std::string& msg) const { - if (report_.message_.find(msg) == std::string::npos) { - return report_.message_; - } else { - return "OK"; - } - } -}; - -TEST_P(LogTest, Empty) { ASSERT_EQ("EOF", Read()); } - -TEST_P(LogTest, ReadWrite) { - Write("foo"); - Write("bar"); - Write(""); - Write("xxxx"); - ASSERT_EQ("foo", Read()); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("", Read()); - ASSERT_EQ("xxxx", Read()); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ("EOF", Read()); // Make sure reads at eof work -} - -TEST_P(LogTest, ManyBlocks) { - for (int i = 0; i < 100000; i++) { - Write(NumberString(i)); - } - for (int i = 0; i < 100000; i++) { - ASSERT_EQ(NumberString(i), Read()); - } - ASSERT_EQ("EOF", Read()); -} - -TEST_P(LogTest, Fragmentation) { - Write("small"); - Write(BigString("medium", 50000)); - Write(BigString("large", 100000)); - ASSERT_EQ("small", Read()); - ASSERT_EQ(BigString("medium", 50000), Read()); - ASSERT_EQ(BigString("large", 100000), Read()); - ASSERT_EQ("EOF", Read()); -} - -TEST_P(LogTest, MarginalTrailer) { - // Make a trailer that is exactly the same length as an empty record. - int header_size = - std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; - const int n = kBlockSize - 2 * header_size; - Write(BigString("foo", n)); - ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes()); - Write(""); - Write("bar"); - ASSERT_EQ(BigString("foo", n), Read()); - ASSERT_EQ("", Read()); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); -} - -TEST_P(LogTest, MarginalTrailer2) { - // Make a trailer that is exactly the same length as an empty record. - int header_size = - std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; - const int n = kBlockSize - 2 * header_size; - Write(BigString("foo", n)); - ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes()); - Write("bar"); - ASSERT_EQ(BigString("foo", n), Read()); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(0U, DroppedBytes()); - ASSERT_EQ("", ReportMessage()); -} - -TEST_P(LogTest, ShortTrailer) { - int header_size = - std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; - const int n = kBlockSize - 2 * header_size + 4; - Write(BigString("foo", n)); - ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes()); - Write(""); - Write("bar"); - ASSERT_EQ(BigString("foo", n), Read()); - ASSERT_EQ("", Read()); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); -} - -TEST_P(LogTest, AlignedEof) { - int header_size = - std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; - const int n = kBlockSize - 2 * header_size + 4; - Write(BigString("foo", n)); - ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes()); - ASSERT_EQ(BigString("foo", n), Read()); - ASSERT_EQ("EOF", Read()); -} - -TEST_P(LogTest, RandomRead) { - const int N = 500; - Random write_rnd(301); - for (int i = 0; i < N; i++) { - Write(RandomSkewedString(i, &write_rnd)); - } - Random read_rnd(301); - for (int i = 0; i < N; i++) { - ASSERT_EQ(RandomSkewedString(i, &read_rnd), Read()); - } - ASSERT_EQ("EOF", Read()); -} - -// Tests of all the error paths in log_reader.cc follow: - -TEST_P(LogTest, ReadError) { - Write("foo"); - ForceError(); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ((unsigned int)kBlockSize, DroppedBytes()); - ASSERT_EQ("OK", MatchError("read error")); -} - -TEST_P(LogTest, BadRecordType) { - Write("foo"); - // Type is stored in header[6] - IncrementByte(6, 100); - FixChecksum(0, 3, false); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(3U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("unknown record type")); -} - -TEST_P(LogTest, TruncatedTrailingRecordIsIgnored) { - Write("foo"); - ShrinkSize(4); // Drop all payload as well as a header byte - ASSERT_EQ("EOF", Read()); - // Truncated last record is ignored, not treated as an error - ASSERT_EQ(0U, DroppedBytes()); - ASSERT_EQ("", ReportMessage()); -} - -TEST_P(LogTest, TruncatedTrailingRecordIsNotIgnored) { - if (allow_retry_read_) { - // If read retry is allowed, then truncated trailing record should not - // raise an error. - return; - } - Write("foo"); - ShrinkSize(4); // Drop all payload as well as a header byte - ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency)); - // Truncated last record is ignored, not treated as an error - ASSERT_GT(DroppedBytes(), 0U); - ASSERT_EQ("OK", MatchError("Corruption: truncated header")); -} - -TEST_P(LogTest, BadLength) { - if (allow_retry_read_) { - // If read retry is allowed, then we should not raise an error when the - // record length specified in header is longer than data currently - // available. It's possible that the body of the record is not written yet. - return; - } - bool recyclable_log = (std::get<0>(GetParam()) != 0); - int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize; - const int kPayloadSize = kBlockSize - header_size; - Write(BigString("bar", kPayloadSize)); - Write("foo"); - // Least significant size byte is stored in header[4]. - IncrementByte(4, 1); - if (!recyclable_log) { - ASSERT_EQ("foo", Read()); - ASSERT_EQ(kBlockSize, DroppedBytes()); - ASSERT_EQ("OK", MatchError("bad record length")); - } else { - ASSERT_EQ("EOF", Read()); - } -} - -TEST_P(LogTest, BadLengthAtEndIsIgnored) { - if (allow_retry_read_) { - // If read retry is allowed, then we should not raise an error when the - // record length specified in header is longer than data currently - // available. It's possible that the body of the record is not written yet. - return; - } - Write("foo"); - ShrinkSize(1); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(0U, DroppedBytes()); - ASSERT_EQ("", ReportMessage()); -} - -TEST_P(LogTest, BadLengthAtEndIsNotIgnored) { - if (allow_retry_read_) { - // If read retry is allowed, then we should not raise an error when the - // record length specified in header is longer than data currently - // available. It's possible that the body of the record is not written yet. - return; - } - Write("foo"); - ShrinkSize(1); - ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency)); - ASSERT_GT(DroppedBytes(), 0U); - ASSERT_EQ("OK", MatchError("Corruption: truncated record body")); -} - -TEST_P(LogTest, ChecksumMismatch) { - Write("foooooo"); - IncrementByte(0, 14); - ASSERT_EQ("EOF", Read()); - bool recyclable_log = (std::get<0>(GetParam()) != 0); - if (!recyclable_log) { - ASSERT_EQ(14U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("checksum mismatch")); - } else { - ASSERT_EQ(0U, DroppedBytes()); - ASSERT_EQ("", ReportMessage()); - } -} - -TEST_P(LogTest, UnexpectedMiddleType) { - Write("foo"); - bool recyclable_log = (std::get<0>(GetParam()) != 0); - SetByte(6, static_cast(recyclable_log ? kRecyclableMiddleType - : kMiddleType)); - FixChecksum(0, 3, !!recyclable_log); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(3U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("missing start")); -} - -TEST_P(LogTest, UnexpectedLastType) { - Write("foo"); - bool recyclable_log = (std::get<0>(GetParam()) != 0); - SetByte(6, - static_cast(recyclable_log ? kRecyclableLastType : kLastType)); - FixChecksum(0, 3, !!recyclable_log); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(3U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("missing start")); -} - -TEST_P(LogTest, UnexpectedFullType) { - Write("foo"); - Write("bar"); - bool recyclable_log = (std::get<0>(GetParam()) != 0); - SetByte( - 6, static_cast(recyclable_log ? kRecyclableFirstType : kFirstType)); - FixChecksum(0, 3, !!recyclable_log); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(3U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("partial record without end")); -} - -TEST_P(LogTest, UnexpectedFirstType) { - Write("foo"); - Write(BigString("bar", 100000)); - bool recyclable_log = (std::get<0>(GetParam()) != 0); - SetByte( - 6, static_cast(recyclable_log ? kRecyclableFirstType : kFirstType)); - FixChecksum(0, 3, !!recyclable_log); - ASSERT_EQ(BigString("bar", 100000), Read()); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(3U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("partial record without end")); -} - -TEST_P(LogTest, MissingLastIsIgnored) { - Write(BigString("bar", kBlockSize)); - // Remove the LAST block, including header. - ShrinkSize(14); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ("", ReportMessage()); - ASSERT_EQ(0U, DroppedBytes()); -} - -TEST_P(LogTest, MissingLastIsNotIgnored) { - if (allow_retry_read_) { - // If read retry is allowed, then truncated trailing record should not - // raise an error. - return; - } - Write(BigString("bar", kBlockSize)); - // Remove the LAST block, including header. - ShrinkSize(14); - ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency)); - ASSERT_GT(DroppedBytes(), 0U); - ASSERT_EQ("OK", MatchError("Corruption: error reading trailing data")); -} - -TEST_P(LogTest, PartialLastIsIgnored) { - Write(BigString("bar", kBlockSize)); - // Cause a bad record length in the LAST block. - ShrinkSize(1); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ("", ReportMessage()); - ASSERT_EQ(0U, DroppedBytes()); -} - -TEST_P(LogTest, PartialLastIsNotIgnored) { - if (allow_retry_read_) { - // If read retry is allowed, then truncated trailing record should not - // raise an error. - return; - } - Write(BigString("bar", kBlockSize)); - // Cause a bad record length in the LAST block. - ShrinkSize(1); - ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency)); - ASSERT_GT(DroppedBytes(), 0U); - ASSERT_EQ("OK", MatchError("Corruption: truncated record body")); -} - -TEST_P(LogTest, ErrorJoinsRecords) { - // Consider two fragmented records: - // first(R1) last(R1) first(R2) last(R2) - // where the middle two fragments disappear. We do not want - // first(R1),last(R2) to get joined and returned as a valid record. - - // Write records that span two blocks - Write(BigString("foo", kBlockSize)); - Write(BigString("bar", kBlockSize)); - Write("correct"); - - // Wipe the middle block - for (unsigned int offset = kBlockSize; offset < 2 * kBlockSize; offset++) { - SetByte(offset, 'x'); - } - - bool recyclable_log = (std::get<0>(GetParam()) != 0); - if (!recyclable_log) { - ASSERT_EQ("correct", Read()); - ASSERT_EQ("EOF", Read()); - size_t dropped = DroppedBytes(); - ASSERT_LE(dropped, 2 * kBlockSize + 100); - ASSERT_GE(dropped, 2 * kBlockSize); - } else { - ASSERT_EQ("EOF", Read()); - } -} - -TEST_P(LogTest, ClearEofSingleBlock) { - Write("foo"); - Write("bar"); - bool recyclable_log = (std::get<0>(GetParam()) != 0); - int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize; - ForceEOF(3 + header_size + 2); - ASSERT_EQ("foo", Read()); - UnmarkEOF(); - ASSERT_EQ("bar", Read()); - ASSERT_TRUE(IsEOF()); - ASSERT_EQ("EOF", Read()); - Write("xxx"); - UnmarkEOF(); - ASSERT_EQ("xxx", Read()); - ASSERT_TRUE(IsEOF()); -} - -TEST_P(LogTest, ClearEofMultiBlock) { - size_t num_full_blocks = 5; - bool recyclable_log = (std::get<0>(GetParam()) != 0); - int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize; - size_t n = (kBlockSize - header_size) * num_full_blocks + 25; - Write(BigString("foo", n)); - Write(BigString("bar", n)); - ForceEOF(n + num_full_blocks * header_size + header_size + 3); - ASSERT_EQ(BigString("foo", n), Read()); - ASSERT_TRUE(IsEOF()); - UnmarkEOF(); - ASSERT_EQ(BigString("bar", n), Read()); - ASSERT_TRUE(IsEOF()); - Write(BigString("xxx", n)); - UnmarkEOF(); - ASSERT_EQ(BigString("xxx", n), Read()); - ASSERT_TRUE(IsEOF()); -} - -TEST_P(LogTest, ClearEofError) { - // If an error occurs during Read() in UnmarkEOF(), the records contained - // in the buffer should be returned on subsequent calls of ReadRecord() - // until no more full records are left, whereafter ReadRecord() should return - // false to indicate that it cannot read any further. - - Write("foo"); - Write("bar"); - UnmarkEOF(); - ASSERT_EQ("foo", Read()); - ASSERT_TRUE(IsEOF()); - Write("xxx"); - ForceError(0); - UnmarkEOF(); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); -} - -TEST_P(LogTest, ClearEofError2) { - Write("foo"); - Write("bar"); - UnmarkEOF(); - ASSERT_EQ("foo", Read()); - Write("xxx"); - ForceError(3); - UnmarkEOF(); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ(3U, DroppedBytes()); - ASSERT_EQ("OK", MatchError("read error")); -} - -TEST_P(LogTest, Recycle) { - bool recyclable_log = (std::get<0>(GetParam()) != 0); - if (!recyclable_log) { - return; // test is only valid for recycled logs - } - Write("foo"); - Write("bar"); - Write("baz"); - Write("bif"); - Write("blitz"); - while (get_reader_contents()->size() < log::kBlockSize * 2) { - Write("xxxxxxxxxxxxxxxx"); - } - std::unique_ptr sink( - new test::OverwritingStringSink(get_reader_contents())); - std::unique_ptr dest_holder(new WritableFileWriter( - std::move(sink), "" /* don't care */, FileOptions())); - Writer recycle_writer(std::move(dest_holder), 123, true); - ASSERT_OK(recycle_writer.AddRecord(Slice("foooo"))); - ASSERT_OK(recycle_writer.AddRecord(Slice("bar"))); - ASSERT_GE(get_reader_contents()->size(), log::kBlockSize * 2); - ASSERT_EQ("foooo", Read()); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("EOF", Read()); -} - -// Do NOT enable compression for this instantiation. -INSTANTIATE_TEST_CASE_P( - Log, LogTest, - ::testing::Combine(::testing::Values(0, 1), ::testing::Bool(), - ::testing::Values(CompressionType::kNoCompression))); - -class RetriableLogTest : public ::testing::TestWithParam { - private: - class ReportCollector : public Reader::Reporter { - public: - size_t dropped_bytes_; - std::string message_; - - ReportCollector() : dropped_bytes_(0) {} - void Corruption(size_t bytes, const Status& status) override { - dropped_bytes_ += bytes; - message_.append(status.ToString()); - } - }; - - Slice contents_; - test::StringSink* sink_; - std::unique_ptr log_writer_; - Env* env_; - const std::string test_dir_; - const std::string log_file_; - std::unique_ptr writer_; - std::unique_ptr reader_; - ReportCollector report_; - std::unique_ptr log_reader_; - - public: - RetriableLogTest() - : contents_(), - sink_(new test::StringSink(&contents_)), - log_writer_(nullptr), - env_(Env::Default()), - test_dir_(test::PerThreadDBPath("retriable_log_test")), - log_file_(test_dir_ + "/log"), - writer_(nullptr), - reader_(nullptr), - log_reader_(nullptr) { - std::unique_ptr sink_holder(sink_); - std::unique_ptr wfw(new WritableFileWriter( - std::move(sink_holder), "" /* file name */, FileOptions())); - log_writer_.reset(new Writer(std::move(wfw), 123, GetParam())); - } - - Status SetupTestEnv() { - Status s; - FileOptions fopts; - auto fs = env_->GetFileSystem(); - s = fs->CreateDirIfMissing(test_dir_, IOOptions(), nullptr); - std::unique_ptr writable_file; - if (s.ok()) { - s = fs->NewWritableFile(log_file_, fopts, &writable_file, nullptr); - } - if (s.ok()) { - writer_.reset( - new WritableFileWriter(std::move(writable_file), log_file_, fopts)); - EXPECT_NE(writer_, nullptr); - } - std::unique_ptr seq_file; - if (s.ok()) { - s = fs->NewSequentialFile(log_file_, fopts, &seq_file, nullptr); - } - if (s.ok()) { - reader_.reset(new SequentialFileReader(std::move(seq_file), log_file_)); - EXPECT_NE(reader_, nullptr); - log_reader_.reset(new FragmentBufferedReader( - nullptr, std::move(reader_), &report_, true /* checksum */, - 123 /* log_number */)); - EXPECT_NE(log_reader_, nullptr); - } - return s; - } - - std::string contents() { return sink_->contents_; } - - void Encode(const std::string& msg) { - ASSERT_OK(log_writer_->AddRecord(Slice(msg))); - } - - void Write(const Slice& data) { - ASSERT_OK(writer_->Append(data)); - ASSERT_OK(writer_->Sync(true)); - } - - bool TryRead(std::string* result) { - assert(result != nullptr); - result->clear(); - std::string scratch; - Slice record; - bool r = log_reader_->ReadRecord(&record, &scratch); - if (r) { - result->assign(record.data(), record.size()); - return true; - } else { - return false; - } - } -}; - -TEST_P(RetriableLogTest, TailLog_PartialHeader) { - ASSERT_OK(SetupTestEnv()); - std::vector remaining_bytes_in_last_record; - size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - bool eof = false; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"RetriableLogTest::TailLog:AfterPart1", - "RetriableLogTest::TailLog:BeforeReadRecord"}, - {"FragmentBufferedLogReader::TryReadMore:FirstEOF", - "RetriableLogTest::TailLog:BeforePart2"}}); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "FragmentBufferedLogReader::TryReadMore:FirstEOF", - [&](void* /*arg*/) { eof = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - size_t delta = header_size - 1; - port::Thread log_writer_thread([&]() { - size_t old_sz = contents().size(); - Encode("foo"); - size_t new_sz = contents().size(); - std::string part1 = contents().substr(old_sz, delta); - std::string part2 = - contents().substr(old_sz + delta, new_sz - old_sz - delta); - Write(Slice(part1)); - TEST_SYNC_POINT("RetriableLogTest::TailLog:AfterPart1"); - TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforePart2"); - Write(Slice(part2)); - }); - - std::string record; - port::Thread log_reader_thread([&]() { - TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforeReadRecord"); - while (!TryRead(&record)) { - } - }); - log_reader_thread.join(); - log_writer_thread.join(); - ASSERT_EQ("foo", record); - ASSERT_TRUE(eof); -} - -TEST_P(RetriableLogTest, TailLog_FullHeader) { - ASSERT_OK(SetupTestEnv()); - std::vector remaining_bytes_in_last_record; - size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - bool eof = false; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency( - {{"RetriableLogTest::TailLog:AfterPart1", - "RetriableLogTest::TailLog:BeforeReadRecord"}, - {"FragmentBufferedLogReader::TryReadMore:FirstEOF", - "RetriableLogTest::TailLog:BeforePart2"}}); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "FragmentBufferedLogReader::TryReadMore:FirstEOF", - [&](void* /*arg*/) { eof = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - size_t delta = header_size + 1; - port::Thread log_writer_thread([&]() { - size_t old_sz = contents().size(); - Encode("foo"); - size_t new_sz = contents().size(); - std::string part1 = contents().substr(old_sz, delta); - std::string part2 = - contents().substr(old_sz + delta, new_sz - old_sz - delta); - Write(Slice(part1)); - TEST_SYNC_POINT("RetriableLogTest::TailLog:AfterPart1"); - TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforePart2"); - Write(Slice(part2)); - ASSERT_TRUE(eof); - }); - - std::string record; - port::Thread log_reader_thread([&]() { - TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforeReadRecord"); - while (!TryRead(&record)) { - } - }); - log_reader_thread.join(); - log_writer_thread.join(); - ASSERT_EQ("foo", record); -} - -TEST_P(RetriableLogTest, NonBlockingReadFullRecord) { - // Clear all sync point callbacks even if this test does not use sync point. - // It is necessary, otherwise the execute of this test may hit a sync point - // with which a callback is registered. The registered callback may access - // some dead variable, causing segfault. - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - ASSERT_OK(SetupTestEnv()); - size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - size_t delta = header_size - 1; - size_t old_sz = contents().size(); - Encode("foo-bar"); - size_t new_sz = contents().size(); - std::string part1 = contents().substr(old_sz, delta); - std::string part2 = - contents().substr(old_sz + delta, new_sz - old_sz - delta); - Write(Slice(part1)); - std::string record; - ASSERT_FALSE(TryRead(&record)); - ASSERT_TRUE(record.empty()); - Write(Slice(part2)); - ASSERT_TRUE(TryRead(&record)); - ASSERT_EQ("foo-bar", record); -} - -INSTANTIATE_TEST_CASE_P(bool, RetriableLogTest, ::testing::Values(0, 2)); - -class CompressionLogTest : public LogTest { - public: - Status SetupTestEnv() { return writer_->AddCompressionTypeRecord(); } -}; - -TEST_P(CompressionLogTest, Empty) { - CompressionType compression_type = std::get<2>(GetParam()); - if (!StreamingCompressionTypeSupported(compression_type)) { - ROCKSDB_GTEST_SKIP("Test requires support for compression type"); - return; - } - ASSERT_OK(SetupTestEnv()); - const bool compression_enabled = - std::get<2>(GetParam()) == kNoCompression ? false : true; - // If WAL compression is enabled, a record is added for the compression type - const int compression_record_size = compression_enabled ? kHeaderSize + 4 : 0; - ASSERT_EQ(compression_record_size, WrittenBytes()); - ASSERT_EQ("EOF", Read()); -} - -TEST_P(CompressionLogTest, ReadWrite) { - CompressionType compression_type = std::get<2>(GetParam()); - if (!StreamingCompressionTypeSupported(compression_type)) { - ROCKSDB_GTEST_SKIP("Test requires support for compression type"); - return; - } - ASSERT_OK(SetupTestEnv()); - Write("foo"); - Write("bar"); - Write(""); - Write("xxxx"); - ASSERT_EQ("foo", Read()); - ASSERT_EQ("bar", Read()); - ASSERT_EQ("", Read()); - ASSERT_EQ("xxxx", Read()); - ASSERT_EQ("EOF", Read()); - ASSERT_EQ("EOF", Read()); // Make sure reads at eof work -} - -TEST_P(CompressionLogTest, ManyBlocks) { - CompressionType compression_type = std::get<2>(GetParam()); - if (!StreamingCompressionTypeSupported(compression_type)) { - ROCKSDB_GTEST_SKIP("Test requires support for compression type"); - return; - } - ASSERT_OK(SetupTestEnv()); - for (int i = 0; i < 100000; i++) { - Write(NumberString(i)); - } - for (int i = 0; i < 100000; i++) { - ASSERT_EQ(NumberString(i), Read()); - } - ASSERT_EQ("EOF", Read()); -} - -TEST_P(CompressionLogTest, Fragmentation) { - CompressionType compression_type = std::get<2>(GetParam()); - if (!StreamingCompressionTypeSupported(compression_type)) { - ROCKSDB_GTEST_SKIP("Test requires support for compression type"); - return; - } - ASSERT_OK(SetupTestEnv()); - Random rnd(301); - const std::vector wal_entries = { - "small", - rnd.RandomBinaryString(3 * kBlockSize / 2), // Spans into block 2 - rnd.RandomBinaryString(3 * kBlockSize), // Spans into block 5 - }; - for (const std::string& wal_entry : wal_entries) { - Write(wal_entry); - } - - for (const std::string& wal_entry : wal_entries) { - ASSERT_EQ(wal_entry, Read()); - } - ASSERT_EQ("EOF", Read()); -} - -TEST_P(CompressionLogTest, AlignedFragmentation) { - CompressionType compression_type = std::get<2>(GetParam()); - if (!StreamingCompressionTypeSupported(compression_type)) { - ROCKSDB_GTEST_SKIP("Test requires support for compression type"); - return; - } - ASSERT_OK(SetupTestEnv()); - Random rnd(301); - int num_filler_records = 0; - // Keep writing small records until the next record will be aligned at the - // beginning of the block. - while ((WrittenBytes() & (kBlockSize - 1)) >= kHeaderSize) { - char entry = 'a'; - ASSERT_OK(writer_->AddRecord(Slice(&entry, 1))); - num_filler_records++; - } - const std::vector wal_entries = { - rnd.RandomBinaryString(3 * kBlockSize), - }; - for (const std::string& wal_entry : wal_entries) { - Write(wal_entry); - } - - for (int i = 0; i < num_filler_records; ++i) { - ASSERT_EQ("a", Read()); - } - for (const std::string& wal_entry : wal_entries) { - ASSERT_EQ(wal_entry, Read()); - } - ASSERT_EQ("EOF", Read()); -} - -INSTANTIATE_TEST_CASE_P( - Compression, CompressionLogTest, - ::testing::Combine(::testing::Values(0, 1), ::testing::Bool(), - ::testing::Values(CompressionType::kNoCompression, - CompressionType::kZSTD))); - -class StreamingCompressionTest - : public ::testing::TestWithParam> {}; - -TEST_P(StreamingCompressionTest, Basic) { - size_t input_size = std::get<0>(GetParam()); - CompressionType compression_type = std::get<1>(GetParam()); - if (!StreamingCompressionTypeSupported(compression_type)) { - ROCKSDB_GTEST_SKIP("Test requires support for compression type"); - return; - } - CompressionOptions opts; - constexpr uint32_t compression_format_version = 2; - StreamingCompress* compress = StreamingCompress::Create( - compression_type, opts, compression_format_version, kBlockSize); - StreamingUncompress* uncompress = StreamingUncompress::Create( - compression_type, compression_format_version, kBlockSize); - MemoryAllocator* allocator = new DefaultMemoryAllocator(); - std::string input_buffer = BigString("abc", input_size); - std::vector compressed_buffers; - size_t remaining; - // Call compress till the entire input is consumed - do { - char* output_buffer = (char*)allocator->Allocate(kBlockSize); - size_t output_pos; - remaining = compress->Compress(input_buffer.c_str(), input_size, - output_buffer, &output_pos); - if (output_pos > 0) { - std::string compressed_buffer; - compressed_buffer.assign(output_buffer, output_pos); - compressed_buffers.emplace_back(std::move(compressed_buffer)); - } - allocator->Deallocate((void*)output_buffer); - } while (remaining > 0); - std::string uncompressed_buffer = ""; - int ret_val = 0; - size_t output_pos; - char* uncompressed_output_buffer = (char*)allocator->Allocate(kBlockSize); - // Uncompress the fragments and concatenate them. - for (int i = 0; i < (int)compressed_buffers.size(); i++) { - // Call uncompress till either the entire input is consumed or the output - // buffer size is equal to the allocated output buffer size. - const char* input = compressed_buffers[i].c_str(); - do { - ret_val = uncompress->Uncompress(input, compressed_buffers[i].size(), - uncompressed_output_buffer, &output_pos); - input = nullptr; - if (output_pos > 0) { - std::string uncompressed_fragment; - uncompressed_fragment.assign(uncompressed_output_buffer, output_pos); - uncompressed_buffer += uncompressed_fragment; - } - } while (ret_val > 0 || output_pos == kBlockSize); - } - allocator->Deallocate((void*)uncompressed_output_buffer); - delete allocator; - delete compress; - delete uncompress; - // The final return value from uncompress() should be 0. - ASSERT_EQ(ret_val, 0); - ASSERT_EQ(input_buffer, uncompressed_buffer); -} - -INSTANTIATE_TEST_CASE_P( - StreamingCompression, StreamingCompressionTest, - ::testing::Combine(::testing::Values(10, 100, 1000, kBlockSize, - kBlockSize * 2), - ::testing::Values(CompressionType::kZSTD))); - -} // namespace log -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/manual_compaction_test.cc b/db/manual_compaction_test.cc deleted file mode 100644 index b92cb794b..000000000 --- a/db/manual_compaction_test.cc +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Test for issue 178: a manual compaction causes deleted data to reappear. -#include - -#include "port/port.h" -#include "rocksdb/compaction_filter.h" -#include "rocksdb/db.h" -#include "rocksdb/slice.h" -#include "rocksdb/write_batch.h" -#include "test_util/testharness.h" - -using ROCKSDB_NAMESPACE::CompactionFilter; -using ROCKSDB_NAMESPACE::CompactionStyle; -using ROCKSDB_NAMESPACE::CompactRangeOptions; -using ROCKSDB_NAMESPACE::CompressionType; -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::DestroyDB; -using ROCKSDB_NAMESPACE::FlushOptions; -using ROCKSDB_NAMESPACE::Iterator; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Slice; -using ROCKSDB_NAMESPACE::WriteBatch; -using ROCKSDB_NAMESPACE::WriteOptions; - -namespace { - -// Reasoning: previously the number was 1100000. Since the keys are written to -// the batch in one write each write will result into one SST file. each write -// will result into one SST file. We reduced the write_buffer_size to 1K to -// basically have the same effect with however less number of keys, which -// results into less test runtime. -const int kNumKeys = 1100; - -std::string Key1(int i) { - char buf[100]; - snprintf(buf, sizeof(buf), "my_key_%d", i); - return buf; -} - -std::string Key2(int i) { return Key1(i) + "_xxx"; } - -class ManualCompactionTest : public testing::Test { - public: - ManualCompactionTest() { - // Get rid of any state from an old run. - dbname_ = ROCKSDB_NAMESPACE::test::PerThreadDBPath( - "rocksdb_manual_compaction_test"); - EXPECT_OK(DestroyDB(dbname_, Options())); - } - - std::string dbname_; -}; - -class DestroyAllCompactionFilter : public CompactionFilter { - public: - DestroyAllCompactionFilter() {} - - bool Filter(int /*level*/, const Slice& /*key*/, const Slice& existing_value, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - return existing_value.ToString() == "destroy"; - } - - const char* Name() const override { return "DestroyAllCompactionFilter"; } -}; - -class LogCompactionFilter : public CompactionFilter { - public: - const char* Name() const override { return "LogCompactionFilter"; } - - bool Filter(int level, const Slice& key, const Slice& /*existing_value*/, - std::string* /*new_value*/, - bool* /*value_changed*/) const override { - key_level_[key.ToString()] = level; - return false; - } - - void Reset() { key_level_.clear(); } - - size_t NumKeys() const { return key_level_.size(); } - - int KeyLevel(const Slice& key) { - auto it = key_level_.find(key.ToString()); - if (it == key_level_.end()) { - return -1; - } - return it->second; - } - - private: - mutable std::map key_level_; -}; - -TEST_F(ManualCompactionTest, CompactTouchesAllKeys) { - for (int iter = 0; iter < 2; ++iter) { - DB* db; - Options options; - if (iter == 0) { // level compaction - options.num_levels = 3; - options.compaction_style = CompactionStyle::kCompactionStyleLevel; - } else { // universal compaction - options.compaction_style = CompactionStyle::kCompactionStyleUniversal; - } - options.create_if_missing = true; - options.compression = CompressionType::kNoCompression; - options.compaction_filter = new DestroyAllCompactionFilter(); - ASSERT_OK(DB::Open(options, dbname_, &db)); - - ASSERT_OK(db->Put(WriteOptions(), Slice("key1"), Slice("destroy"))); - ASSERT_OK(db->Put(WriteOptions(), Slice("key2"), Slice("destroy"))); - ASSERT_OK(db->Put(WriteOptions(), Slice("key3"), Slice("value3"))); - ASSERT_OK(db->Put(WriteOptions(), Slice("key4"), Slice("destroy"))); - - Slice key4("key4"); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, &key4)); - Iterator* itr = db->NewIterator(ReadOptions()); - itr->SeekToFirst(); - ASSERT_TRUE(itr->Valid()); - ASSERT_EQ("key3", itr->key().ToString()); - itr->Next(); - ASSERT_TRUE(!itr->Valid()); - delete itr; - - delete options.compaction_filter; - delete db; - ASSERT_OK(DestroyDB(dbname_, options)); - } -} - -TEST_F(ManualCompactionTest, Test) { - // Open database. Disable compression since it affects the creation - // of layers and the code below is trying to test against a very - // specific scenario. - DB* db; - Options db_options; - db_options.write_buffer_size = 1024; - db_options.create_if_missing = true; - db_options.compression = CompressionType::kNoCompression; - ASSERT_OK(DB::Open(db_options, dbname_, &db)); - - // create first key range - WriteBatch batch; - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(Key1(i), "value for range 1 key")); - } - ASSERT_OK(db->Write(WriteOptions(), &batch)); - - // create second key range - batch.Clear(); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(Key2(i), "value for range 2 key")); - } - ASSERT_OK(db->Write(WriteOptions(), &batch)); - - // delete second key range - batch.Clear(); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Delete(Key2(i))); - } - ASSERT_OK(db->Write(WriteOptions(), &batch)); - - // compact database - std::string start_key = Key1(0); - std::string end_key = Key1(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - // commenting out the line below causes the example to work correctly - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &least, &greatest)); - - // count the keys - Iterator* iter = db->NewIterator(ReadOptions()); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - num_keys++; - } - delete iter; - ASSERT_EQ(kNumKeys, num_keys) << "Bad number of keys"; - - // close database - delete db; - ASSERT_OK(DestroyDB(dbname_, Options())); -} - -TEST_F(ManualCompactionTest, SkipLevel) { - DB* db; - Options options; - options.num_levels = 3; - // Initially, flushed L0 files won't exceed 100. - options.level0_file_num_compaction_trigger = 100; - options.compaction_style = CompactionStyle::kCompactionStyleLevel; - options.create_if_missing = true; - options.compression = CompressionType::kNoCompression; - LogCompactionFilter* filter = new LogCompactionFilter(); - options.compaction_filter = filter; - ASSERT_OK(DB::Open(options, dbname_, &db)); - - WriteOptions wo; - FlushOptions fo; - ASSERT_OK(db->Put(wo, "1", "")); - ASSERT_OK(db->Flush(fo)); - ASSERT_OK(db->Put(wo, "2", "")); - ASSERT_OK(db->Flush(fo)); - ASSERT_OK(db->Put(wo, "4", "")); - ASSERT_OK(db->Put(wo, "8", "")); - ASSERT_OK(db->Flush(fo)); - - { - // L0: 1, 2, [4, 8] - // no file has keys in range [5, 7] - Slice start("5"); - Slice end("7"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &start, &end)); - ASSERT_EQ(0, filter->NumKeys()); - } - - { - // L0: 1, 2, [4, 8] - // [3, 7] overlaps with 4 in L0 - Slice start("3"); - Slice end("7"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &start, &end)); - ASSERT_EQ(2, filter->NumKeys()); - ASSERT_EQ(0, filter->KeyLevel("4")); - ASSERT_EQ(0, filter->KeyLevel("8")); - } - - { - // L0: 1, 2 - // L1: [4, 8] - // no file has keys in range (-inf, 0] - Slice end("0"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, &end)); - ASSERT_EQ(0, filter->NumKeys()); - } - - { - // L0: 1, 2 - // L1: [4, 8] - // no file has keys in range [9, inf) - Slice start("9"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &start, nullptr)); - ASSERT_EQ(0, filter->NumKeys()); - } - - { - // L0: 1, 2 - // L1: [4, 8] - // [2, 2] overlaps with 2 in L0 - Slice start("2"); - Slice end("2"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &start, &end)); - ASSERT_EQ(1, filter->NumKeys()); - ASSERT_EQ(0, filter->KeyLevel("2")); - } - - { - // L0: 1 - // L1: 2, [4, 8] - // [2, 5] overlaps with 2 and [4, 8) in L1, skip L0 - Slice start("2"); - Slice end("5"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &start, &end)); - ASSERT_EQ(3, filter->NumKeys()); - ASSERT_EQ(1, filter->KeyLevel("2")); - ASSERT_EQ(1, filter->KeyLevel("4")); - ASSERT_EQ(1, filter->KeyLevel("8")); - } - - { - // L0: 1 - // L1: [2, 4, 8] - // [0, inf) overlaps all files - Slice start("0"); - filter->Reset(); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), &start, nullptr)); - ASSERT_EQ(4, filter->NumKeys()); - // 1 is first compacted to L1 and then further compacted into [2, 4, 8], - // so finally the logged level for 1 is L1. - ASSERT_EQ(1, filter->KeyLevel("1")); - ASSERT_EQ(1, filter->KeyLevel("2")); - ASSERT_EQ(1, filter->KeyLevel("4")); - ASSERT_EQ(1, filter->KeyLevel("8")); - } - - delete filter; - delete db; - ASSERT_OK(DestroyDB(dbname_, options)); -} - -} // anonymous namespace - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/memtable_list_test.cc b/db/memtable_list_test.cc deleted file mode 100644 index c63952b12..000000000 --- a/db/memtable_list_test.cc +++ /dev/null @@ -1,1037 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/memtable_list.h" - -#include -#include -#include - -#include "db/merge_context.h" -#include "db/version_set.h" -#include "db/write_controller.h" -#include "rocksdb/db.h" -#include "rocksdb/status.h" -#include "rocksdb/write_buffer_manager.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class MemTableListTest : public testing::Test { - public: - std::string dbname; - DB* db; - Options options; - std::vector handles; - std::atomic file_number; - - MemTableListTest() : db(nullptr), file_number(1) { - dbname = test::PerThreadDBPath("memtable_list_test"); - options.create_if_missing = true; - EXPECT_OK(DestroyDB(dbname, options)); - } - - // Create a test db if not yet created - void CreateDB() { - if (db == nullptr) { - options.create_if_missing = true; - EXPECT_OK(DestroyDB(dbname, options)); - // Open DB only with default column family - ColumnFamilyOptions cf_options; - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, cf_options); - Status s = DB::Open(options, dbname, cf_descs, &handles, &db); - EXPECT_OK(s); - - ColumnFamilyOptions cf_opt1, cf_opt2; - cf_opt1.cf_paths.emplace_back(dbname + "_one_1", - std::numeric_limits::max()); - cf_opt2.cf_paths.emplace_back(dbname + "_two_1", - std::numeric_limits::max()); - int sz = static_cast(handles.size()); - handles.resize(sz + 2); - s = db->CreateColumnFamily(cf_opt1, "one", &handles[1]); - EXPECT_OK(s); - s = db->CreateColumnFamily(cf_opt2, "two", &handles[2]); - EXPECT_OK(s); - - cf_descs.emplace_back("one", cf_options); - cf_descs.emplace_back("two", cf_options); - } - } - - ~MemTableListTest() override { - if (db) { - std::vector cf_descs(handles.size()); - for (int i = 0; i != static_cast(handles.size()); ++i) { - EXPECT_OK(handles[i]->GetDescriptor(&cf_descs[i])); - } - for (auto h : handles) { - if (h) { - EXPECT_OK(db->DestroyColumnFamilyHandle(h)); - } - } - handles.clear(); - delete db; - db = nullptr; - EXPECT_OK(DestroyDB(dbname, options, cf_descs)); - } - } - - // Calls MemTableList::TryInstallMemtableFlushResults() and sets up all - // structures needed to call this function. - Status Mock_InstallMemtableFlushResults( - MemTableList* list, const MutableCFOptions& mutable_cf_options, - const autovector& m, autovector* to_delete) { - // Create a mock Logger - test::NullLogger logger; - LogBuffer log_buffer(DEBUG_LEVEL, &logger); - - CreateDB(); - // Create a mock VersionSet - DBOptions db_options; - ImmutableDBOptions immutable_db_options(db_options); - EnvOptions env_options; - std::shared_ptr table_cache(NewLRUCache(50000, 16)); - WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size); - WriteController write_controller(10000000u); - - VersionSet versions(dbname, &immutable_db_options, env_options, - table_cache.get(), &write_buffer_manager, - &write_controller, /*block_cache_tracer=*/nullptr, - /*io_tracer=*/nullptr, /*db_id*/ "", - /*db_session_id*/ ""); - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions()); - cf_descs.emplace_back("one", ColumnFamilyOptions()); - cf_descs.emplace_back("two", ColumnFamilyOptions()); - - EXPECT_OK(versions.Recover(cf_descs, false)); - - // Create mock default ColumnFamilyData - auto column_family_set = versions.GetColumnFamilySet(); - LogsWithPrepTracker dummy_prep_tracker; - auto cfd = column_family_set->GetDefault(); - EXPECT_TRUE(nullptr != cfd); - uint64_t file_num = file_number.fetch_add(1); - IOStatus io_s; - // Create dummy mutex. - InstrumentedMutex mutex; - InstrumentedMutexLock l(&mutex); - std::list> flush_jobs_info; - Status s = list->TryInstallMemtableFlushResults( - cfd, mutable_cf_options, m, &dummy_prep_tracker, &versions, &mutex, - file_num, to_delete, nullptr, &log_buffer, &flush_jobs_info); - EXPECT_OK(io_s); - return s; - } - - // Calls MemTableList::InstallMemtableFlushResults() and sets up all - // structures needed to call this function. - Status Mock_InstallMemtableAtomicFlushResults( - autovector& lists, const autovector& cf_ids, - const autovector& mutable_cf_options_list, - const autovector*>& mems_list, - autovector* to_delete) { - // Create a mock Logger - test::NullLogger logger; - LogBuffer log_buffer(DEBUG_LEVEL, &logger); - - CreateDB(); - // Create a mock VersionSet - DBOptions db_options; - - ImmutableDBOptions immutable_db_options(db_options); - EnvOptions env_options; - std::shared_ptr table_cache(NewLRUCache(50000, 16)); - WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size); - WriteController write_controller(10000000u); - - VersionSet versions(dbname, &immutable_db_options, env_options, - table_cache.get(), &write_buffer_manager, - &write_controller, /*block_cache_tracer=*/nullptr, - /*io_tracer=*/nullptr, /*db_id*/ "", - /*db_session_id*/ ""); - std::vector cf_descs; - cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions()); - cf_descs.emplace_back("one", ColumnFamilyOptions()); - cf_descs.emplace_back("two", ColumnFamilyOptions()); - EXPECT_OK(versions.Recover(cf_descs, false)); - - // Create mock default ColumnFamilyData - - auto column_family_set = versions.GetColumnFamilySet(); - - LogsWithPrepTracker dummy_prep_tracker; - autovector cfds; - for (int i = 0; i != static_cast(cf_ids.size()); ++i) { - cfds.emplace_back(column_family_set->GetColumnFamily(cf_ids[i])); - EXPECT_NE(nullptr, cfds[i]); - } - std::vector file_metas; - file_metas.reserve(cf_ids.size()); - for (size_t i = 0; i != cf_ids.size(); ++i) { - FileMetaData meta; - uint64_t file_num = file_number.fetch_add(1); - meta.fd = FileDescriptor(file_num, 0, 0); - file_metas.emplace_back(meta); - } - autovector file_meta_ptrs; - for (auto& meta : file_metas) { - file_meta_ptrs.push_back(&meta); - } - std::vector>> - committed_flush_jobs_info_storage(cf_ids.size()); - autovector>*> - committed_flush_jobs_info; - for (int i = 0; i < static_cast(cf_ids.size()); ++i) { - committed_flush_jobs_info.push_back( - &committed_flush_jobs_info_storage[i]); - } - - InstrumentedMutex mutex; - InstrumentedMutexLock l(&mutex); - return InstallMemtableAtomicFlushResults( - &lists, cfds, mutable_cf_options_list, mems_list, &versions, - nullptr /* prep_tracker */, &mutex, file_meta_ptrs, - committed_flush_jobs_info, to_delete, nullptr, &log_buffer); - } -}; - -TEST_F(MemTableListTest, Empty) { - // Create an empty MemTableList and validate basic functions. - MemTableList list(1, 0, 0); - - ASSERT_EQ(0, list.NumNotFlushed()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - ASSERT_FALSE(list.IsFlushPending()); - - autovector mems; - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &mems); - ASSERT_EQ(0, mems.size()); - - autovector to_delete; - list.current()->Unref(&to_delete); - ASSERT_EQ(0, to_delete.size()); -} - -TEST_F(MemTableListTest, GetTest) { - // Create MemTableList - int min_write_buffer_number_to_merge = 2; - int max_write_buffer_number_to_maintain = 0; - int64_t max_write_buffer_size_to_maintain = 0; - MemTableList list(min_write_buffer_number_to_merge, - max_write_buffer_number_to_maintain, - max_write_buffer_size_to_maintain); - - SequenceNumber seq = 1; - std::string value; - Status s; - MergeContext merge_context; - InternalKeyComparator ikey_cmp(options.comparator); - SequenceNumber max_covering_tombstone_seq = 0; - autovector to_delete; - - LookupKey lkey("key1", seq); - bool found = list.current()->Get(lkey, &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - // Create a MemTable - InternalKeyComparator cmp(BytewiseComparator()); - auto factory = std::make_shared(); - options.memtable_factory = factory; - ImmutableOptions ioptions(options); - - WriteBufferManager wb(options.db_write_buffer_size); - MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - mem->Ref(); - - // Write some keys to this memtable. - ASSERT_OK( - mem->Add(++seq, kTypeDeletion, "key1", "", nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "key2", "value2", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "key1", "value1", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "key2", "value2.2", - nullptr /* kv_prot_info */)); - - // Fetch the newly written keys - merge_context.Clear(); - found = mem->Get(LookupKey("key1", seq), &value, /*columns*/ nullptr, - /*timestamp*/ nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions(), - false /* immutable_memtable */); - ASSERT_TRUE(s.ok() && found); - ASSERT_EQ(value, "value1"); - - merge_context.Clear(); - found = mem->Get(LookupKey("key1", 2), &value, /*columns*/ nullptr, - /*timestamp*/ nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions(), - false /* immutable_memtable */); - // MemTable found out that this key is *not* found (at this sequence#) - ASSERT_TRUE(found && s.IsNotFound()); - - merge_context.Clear(); - found = mem->Get(LookupKey("key2", seq), &value, /*columns*/ nullptr, - /*timestamp*/ nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions(), - false /* immutable_memtable */); - ASSERT_TRUE(s.ok() && found); - ASSERT_EQ(value, "value2.2"); - - ASSERT_EQ(4, mem->num_entries()); - ASSERT_EQ(1, mem->num_deletes()); - - // Add memtable to list - // This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed()) - // in MemTableListVersion::GetFromList work. - mem->ConstructFragmentedRangeTombstones(); - list.Add(mem, &to_delete); - - SequenceNumber saved_seq = seq; - - // Create another memtable and write some keys to it - WriteBufferManager wb2(options.db_write_buffer_size); - MemTable* mem2 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb2, - kMaxSequenceNumber, 0 /* column_family_id */); - mem2->Ref(); - - ASSERT_OK( - mem2->Add(++seq, kTypeDeletion, "key1", "", nullptr /* kv_prot_info */)); - ASSERT_OK(mem2->Add(++seq, kTypeValue, "key2", "value2.3", - nullptr /* kv_prot_info */)); - - // Add second memtable to list - // This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed()) - // in MemTableListVersion::GetFromList work. - mem2->ConstructFragmentedRangeTombstones(); - list.Add(mem2, &to_delete); - - // Fetch keys via MemTableList - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_TRUE(found && s.IsNotFound()); - - merge_context.Clear(); - found = list.current()->Get(LookupKey("key1", saved_seq), &value, - /*columns=*/nullptr, /*timestamp=*/nullptr, &s, - &merge_context, &max_covering_tombstone_seq, - ReadOptions()); - ASSERT_TRUE(s.ok() && found); - ASSERT_EQ("value1", value); - - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_TRUE(s.ok() && found); - ASSERT_EQ(value, "value2.3"); - - merge_context.Clear(); - found = list.current()->Get(LookupKey("key2", 1), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - ASSERT_EQ(2, list.NumNotFlushed()); - - list.current()->Unref(&to_delete); - for (MemTable* m : to_delete) { - delete m; - } -} - -TEST_F(MemTableListTest, GetFromHistoryTest) { - // Create MemTableList - int min_write_buffer_number_to_merge = 2; - int max_write_buffer_number_to_maintain = 2; - int64_t max_write_buffer_size_to_maintain = 2 * Arena::kInlineSize; - MemTableList list(min_write_buffer_number_to_merge, - max_write_buffer_number_to_maintain, - max_write_buffer_size_to_maintain); - - SequenceNumber seq = 1; - std::string value; - Status s; - MergeContext merge_context; - InternalKeyComparator ikey_cmp(options.comparator); - SequenceNumber max_covering_tombstone_seq = 0; - autovector to_delete; - - LookupKey lkey("key1", seq); - bool found = list.current()->Get(lkey, &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - // Create a MemTable - InternalKeyComparator cmp(BytewiseComparator()); - auto factory = std::make_shared(); - options.memtable_factory = factory; - ImmutableOptions ioptions(options); - - WriteBufferManager wb(options.db_write_buffer_size); - MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - mem->Ref(); - - // Write some keys to this memtable. - ASSERT_OK( - mem->Add(++seq, kTypeDeletion, "key1", "", nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "key2", "value2", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "key2", "value2.2", - nullptr /* kv_prot_info */)); - - // Fetch the newly written keys - merge_context.Clear(); - found = mem->Get(LookupKey("key1", seq), &value, /*columns*/ nullptr, - /*timestamp*/ nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions(), - false /* immutable_memtable */); - // MemTable found out that this key is *not* found (at this sequence#) - ASSERT_TRUE(found && s.IsNotFound()); - - merge_context.Clear(); - found = mem->Get(LookupKey("key2", seq), &value, /*columns*/ nullptr, - /*timestamp*/ nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions(), - false /* immutable_memtable */); - ASSERT_TRUE(s.ok() && found); - ASSERT_EQ(value, "value2.2"); - - // Add memtable to list - // This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed()) - // in MemTableListVersion::GetFromList work. - mem->ConstructFragmentedRangeTombstones(); - list.Add(mem, &to_delete); - ASSERT_EQ(0, to_delete.size()); - - // Fetch keys via MemTableList - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_TRUE(found && s.IsNotFound()); - - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_TRUE(s.ok() && found); - ASSERT_EQ("value2.2", value); - - // Flush this memtable from the list. - // (It will then be a part of the memtable history). - autovector to_flush; - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - ASSERT_EQ(1, to_flush.size()); - - MutableCFOptions mutable_cf_options(options); - s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush, - &to_delete); - ASSERT_OK(s); - ASSERT_EQ(0, list.NumNotFlushed()); - ASSERT_EQ(1, list.NumFlushed()); - ASSERT_EQ(0, to_delete.size()); - - // Verify keys are no longer in MemTableList - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - // Verify keys are present in history - merge_context.Clear(); - found = list.current()->GetFromHistory( - LookupKey("key1", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq, - ReadOptions()); - ASSERT_TRUE(found && s.IsNotFound()); - - merge_context.Clear(); - found = list.current()->GetFromHistory( - LookupKey("key2", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq, - ReadOptions()); - ASSERT_TRUE(found); - ASSERT_EQ("value2.2", value); - - // Create another memtable and write some keys to it - WriteBufferManager wb2(options.db_write_buffer_size); - MemTable* mem2 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb2, - kMaxSequenceNumber, 0 /* column_family_id */); - mem2->Ref(); - - ASSERT_OK( - mem2->Add(++seq, kTypeDeletion, "key1", "", nullptr /* kv_prot_info */)); - ASSERT_OK(mem2->Add(++seq, kTypeValue, "key3", "value3", - nullptr /* kv_prot_info */)); - - // Add second memtable to list - // This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed()) - // in MemTableListVersion::GetFromList work. - mem2->ConstructFragmentedRangeTombstones(); - list.Add(mem2, &to_delete); - ASSERT_EQ(0, to_delete.size()); - - to_flush.clear(); - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - ASSERT_EQ(1, to_flush.size()); - - // Flush second memtable - s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush, - &to_delete); - ASSERT_OK(s); - ASSERT_EQ(0, list.NumNotFlushed()); - ASSERT_EQ(2, list.NumFlushed()); - ASSERT_EQ(0, to_delete.size()); - - // Add a third memtable to push the first memtable out of the history - WriteBufferManager wb3(options.db_write_buffer_size); - MemTable* mem3 = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb3, - kMaxSequenceNumber, 0 /* column_family_id */); - mem3->Ref(); - // This is to make assert(memtable->IsFragmentedRangeTombstonesConstructed()) - // in MemTableListVersion::GetFromList work. - mem3->ConstructFragmentedRangeTombstones(); - list.Add(mem3, &to_delete); - ASSERT_EQ(1, list.NumNotFlushed()); - ASSERT_EQ(1, list.NumFlushed()); - ASSERT_EQ(1, to_delete.size()); - - // Verify keys are no longer in MemTableList - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key3", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - // Verify that the second memtable's keys are in the history - merge_context.Clear(); - found = list.current()->GetFromHistory( - LookupKey("key1", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq, - ReadOptions()); - ASSERT_TRUE(found && s.IsNotFound()); - - merge_context.Clear(); - found = list.current()->GetFromHistory( - LookupKey("key3", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq, - ReadOptions()); - ASSERT_TRUE(found); - ASSERT_EQ("value3", value); - - // Verify that key2 from the first memtable is no longer in the history - merge_context.Clear(); - found = - list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr, - /*timestamp=*/nullptr, &s, &merge_context, - &max_covering_tombstone_seq, ReadOptions()); - ASSERT_FALSE(found); - - // Cleanup - list.current()->Unref(&to_delete); - ASSERT_EQ(3, to_delete.size()); - for (MemTable* m : to_delete) { - delete m; - } -} - -TEST_F(MemTableListTest, FlushPendingTest) { - const int num_tables = 6; - SequenceNumber seq = 1; - Status s; - - auto factory = std::make_shared(); - options.memtable_factory = factory; - ImmutableOptions ioptions(options); - InternalKeyComparator cmp(BytewiseComparator()); - WriteBufferManager wb(options.db_write_buffer_size); - autovector to_delete; - - // Create MemTableList - int min_write_buffer_number_to_merge = 3; - int max_write_buffer_number_to_maintain = 7; - int64_t max_write_buffer_size_to_maintain = - 7 * static_cast(options.write_buffer_size); - MemTableList list(min_write_buffer_number_to_merge, - max_write_buffer_number_to_maintain, - max_write_buffer_size_to_maintain); - - // Create some MemTables - uint64_t memtable_id = 0; - std::vector tables; - MutableCFOptions mutable_cf_options(options); - for (int i = 0; i < num_tables; i++) { - MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - mem->SetID(memtable_id++); - mem->Ref(); - - std::string value; - MergeContext merge_context; - - ASSERT_OK(mem->Add(++seq, kTypeValue, "key1", std::to_string(i), - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "keyN" + std::to_string(i), "valueN", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "keyX" + std::to_string(i), "value", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "keyM" + std::to_string(i), "valueM", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeDeletion, "keyX" + std::to_string(i), "", - nullptr /* kv_prot_info */)); - - tables.push_back(mem); - } - - // Nothing to flush - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - autovector to_flush; - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - ASSERT_EQ(0, to_flush.size()); - - // Request a flush even though there is nothing to flush - list.FlushRequested(); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Attempt to 'flush' to clear request for flush - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - ASSERT_EQ(0, to_flush.size()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Request a flush again - list.FlushRequested(); - // No flush pending since the list is empty. - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Add 2 tables - list.Add(tables[0], &to_delete); - list.Add(tables[1], &to_delete); - ASSERT_EQ(2, list.NumNotFlushed()); - ASSERT_EQ(0, to_delete.size()); - - // Even though we have less than the minimum to flush, a flush is - // pending since we had previously requested a flush and never called - // PickMemtablesToFlush() to clear the flush. - ASSERT_TRUE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Pick tables to flush - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - ASSERT_EQ(2, to_flush.size()); - ASSERT_EQ(2, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Revert flush - list.RollbackMemtableFlush(to_flush, 0); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - to_flush.clear(); - - // Add another table - list.Add(tables[2], &to_delete); - // We now have the minimum to flush regardles of whether FlushRequested() - // was called. - ASSERT_TRUE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - ASSERT_EQ(0, to_delete.size()); - - // Pick tables to flush - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - ASSERT_EQ(3, to_flush.size()); - ASSERT_EQ(3, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Pick tables to flush again - autovector to_flush2; - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush2); - ASSERT_EQ(0, to_flush2.size()); - ASSERT_EQ(3, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Add another table - list.Add(tables[3], &to_delete); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - ASSERT_EQ(0, to_delete.size()); - - // Request a flush again - list.FlushRequested(); - ASSERT_TRUE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Pick tables to flush again - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush2); - ASSERT_EQ(1, to_flush2.size()); - ASSERT_EQ(4, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Rollback first pick of tables - list.RollbackMemtableFlush(to_flush, 0); - ASSERT_TRUE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - to_flush.clear(); - - // Add another tables - list.Add(tables[4], &to_delete); - ASSERT_EQ(5, list.NumNotFlushed()); - // We now have the minimum to flush regardles of whether FlushRequested() - ASSERT_TRUE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - ASSERT_EQ(0, to_delete.size()); - - // Pick tables to flush - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush); - // Picks three oldest memtables. The fourth oldest is picked in `to_flush2` so - // must be excluded. The newest (fifth oldest) is non-consecutive with the - // three oldest due to omitting the fourth oldest so must not be picked. - ASSERT_EQ(3, to_flush.size()); - ASSERT_EQ(5, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Pick tables to flush again - autovector to_flush3; - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush3); - // Picks newest (fifth oldest) - ASSERT_EQ(1, to_flush3.size()); - ASSERT_EQ(5, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Nothing left to flush - autovector to_flush4; - list.PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, &to_flush4); - ASSERT_EQ(0, to_flush4.size()); - ASSERT_EQ(5, list.NumNotFlushed()); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Flush the 3 memtables that were picked in to_flush - s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush, - &to_delete); - ASSERT_OK(s); - - // Note: now to_flush contains tables[0,1,2]. to_flush2 contains - // tables[3]. to_flush3 contains tables[4]. - // Current implementation will only commit memtables in the order they were - // created. So TryInstallMemtableFlushResults will install the first 3 tables - // in to_flush and stop when it encounters a table not yet flushed. - ASSERT_EQ(2, list.NumNotFlushed()); - int num_in_history = - std::min(3, static_cast(max_write_buffer_size_to_maintain) / - static_cast(options.write_buffer_size)); - ASSERT_EQ(num_in_history, list.NumFlushed()); - ASSERT_EQ(5 - list.NumNotFlushed() - num_in_history, to_delete.size()); - - // Request a flush again. Should be nothing to flush - list.FlushRequested(); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - - // Flush the 1 memtable (tables[4]) that was picked in to_flush3 - s = MemTableListTest::Mock_InstallMemtableFlushResults( - &list, mutable_cf_options, to_flush3, &to_delete); - ASSERT_OK(s); - - // This will install 0 tables since tables[4] flushed while tables[3] has not - // yet flushed. - ASSERT_EQ(2, list.NumNotFlushed()); - ASSERT_EQ(0, to_delete.size()); - - // Flush the 1 memtable (tables[3]) that was picked in to_flush2 - s = MemTableListTest::Mock_InstallMemtableFlushResults( - &list, mutable_cf_options, to_flush2, &to_delete); - ASSERT_OK(s); - - // This will actually install 2 tables. The 1 we told it to flush, and also - // tables[4] which has been waiting for tables[3] to commit. - ASSERT_EQ(0, list.NumNotFlushed()); - num_in_history = - std::min(5, static_cast(max_write_buffer_size_to_maintain) / - static_cast(options.write_buffer_size)); - ASSERT_EQ(num_in_history, list.NumFlushed()); - ASSERT_EQ(5 - list.NumNotFlushed() - num_in_history, to_delete.size()); - - for (const auto& m : to_delete) { - // Refcount should be 0 after calling TryInstallMemtableFlushResults. - // Verify this, by Ref'ing then UnRef'ing: - m->Ref(); - ASSERT_EQ(m, m->Unref()); - delete m; - } - to_delete.clear(); - - // Add another table - list.Add(tables[5], &to_delete); - ASSERT_EQ(1, list.NumNotFlushed()); - ASSERT_EQ(5, list.GetLatestMemTableID()); - memtable_id = 4; - // Pick tables to flush. The tables to pick must have ID smaller than or - // equal to 4. Therefore, no table will be selected in this case. - autovector to_flush5; - list.FlushRequested(); - ASSERT_TRUE(list.HasFlushRequested()); - list.PickMemtablesToFlush(memtable_id, &to_flush5); - ASSERT_TRUE(to_flush5.empty()); - ASSERT_EQ(1, list.NumNotFlushed()); - ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); - ASSERT_FALSE(list.IsFlushPending()); - ASSERT_FALSE(list.HasFlushRequested()); - - // Pick tables to flush. The tables to pick must have ID smaller than or - // equal to 5. Therefore, only tables[5] will be selected. - memtable_id = 5; - list.FlushRequested(); - list.PickMemtablesToFlush(memtable_id, &to_flush5); - ASSERT_EQ(1, static_cast(to_flush5.size())); - ASSERT_EQ(1, list.NumNotFlushed()); - ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); - ASSERT_FALSE(list.IsFlushPending()); - to_delete.clear(); - - list.current()->Unref(&to_delete); - int to_delete_size = - std::min(num_tables, static_cast(max_write_buffer_size_to_maintain) / - static_cast(options.write_buffer_size)); - ASSERT_EQ(to_delete_size, to_delete.size()); - - for (const auto& m : to_delete) { - // Refcount should be 0 after calling TryInstallMemtableFlushResults. - // Verify this, by Ref'ing then UnRef'ing: - m->Ref(); - ASSERT_EQ(m, m->Unref()); - delete m; - } - to_delete.clear(); -} - -TEST_F(MemTableListTest, EmptyAtomicFlusTest) { - autovector lists; - autovector cf_ids; - autovector options_list; - autovector*> to_flush; - autovector to_delete; - Status s = Mock_InstallMemtableAtomicFlushResults(lists, cf_ids, options_list, - to_flush, &to_delete); - ASSERT_OK(s); - ASSERT_TRUE(to_delete.empty()); -} - -TEST_F(MemTableListTest, AtomicFlusTest) { - const int num_cfs = 3; - const int num_tables_per_cf = 2; - SequenceNumber seq = 1; - - auto factory = std::make_shared(); - options.memtable_factory = factory; - ImmutableOptions ioptions(options); - InternalKeyComparator cmp(BytewiseComparator()); - WriteBufferManager wb(options.db_write_buffer_size); - - // Create MemTableLists - int min_write_buffer_number_to_merge = 3; - int max_write_buffer_number_to_maintain = 7; - int64_t max_write_buffer_size_to_maintain = - 7 * static_cast(options.write_buffer_size); - autovector lists; - for (int i = 0; i != num_cfs; ++i) { - lists.emplace_back(new MemTableList(min_write_buffer_number_to_merge, - max_write_buffer_number_to_maintain, - max_write_buffer_size_to_maintain)); - } - - autovector cf_ids; - std::vector> tables(num_cfs); - autovector mutable_cf_options_list; - uint32_t cf_id = 0; - for (auto& elem : tables) { - mutable_cf_options_list.emplace_back(new MutableCFOptions(options)); - uint64_t memtable_id = 0; - for (int i = 0; i != num_tables_per_cf; ++i) { - MemTable* mem = - new MemTable(cmp, ioptions, *(mutable_cf_options_list.back()), &wb, - kMaxSequenceNumber, cf_id); - mem->SetID(memtable_id++); - mem->Ref(); - - std::string value; - - ASSERT_OK(mem->Add(++seq, kTypeValue, "key1", std::to_string(i), - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "keyN" + std::to_string(i), - "valueN", nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "keyX" + std::to_string(i), "value", - nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeValue, "keyM" + std::to_string(i), - "valueM", nullptr /* kv_prot_info */)); - ASSERT_OK(mem->Add(++seq, kTypeDeletion, "keyX" + std::to_string(i), "", - nullptr /* kv_prot_info */)); - - elem.push_back(mem); - } - cf_ids.push_back(cf_id++); - } - - std::vector> flush_candidates(num_cfs); - - // Nothing to flush - for (auto i = 0; i != num_cfs; ++i) { - auto* list = lists[i]; - ASSERT_FALSE(list->IsFlushPending()); - ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire)); - list->PickMemtablesToFlush( - std::numeric_limits::max() /* memtable_id */, - &flush_candidates[i]); - ASSERT_EQ(0, flush_candidates[i].size()); - } - // Request flush even though there is nothing to flush - for (auto i = 0; i != num_cfs; ++i) { - auto* list = lists[i]; - list->FlushRequested(); - ASSERT_FALSE(list->IsFlushPending()); - ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire)); - } - autovector to_delete; - // Add tables to the immutable memtalbe lists associated with column families - for (auto i = 0; i != num_cfs; ++i) { - for (auto j = 0; j != num_tables_per_cf; ++j) { - lists[i]->Add(tables[i][j], &to_delete); - } - ASSERT_EQ(num_tables_per_cf, lists[i]->NumNotFlushed()); - ASSERT_TRUE(lists[i]->IsFlushPending()); - ASSERT_TRUE(lists[i]->imm_flush_needed.load(std::memory_order_acquire)); - } - std::vector flush_memtable_ids = {1, 1, 0}; - // +----+ - // list[0]: |0 1| - // list[1]: |0 1| - // | +--+ - // list[2]: |0| 1 - // +-+ - // Pick memtables to flush - for (auto i = 0; i != num_cfs; ++i) { - flush_candidates[i].clear(); - lists[i]->PickMemtablesToFlush(flush_memtable_ids[i], &flush_candidates[i]); - ASSERT_EQ(flush_memtable_ids[i] - 0 + 1, - static_cast(flush_candidates[i].size())); - } - autovector tmp_lists; - autovector tmp_cf_ids; - autovector tmp_options_list; - autovector*> to_flush; - for (auto i = 0; i != num_cfs; ++i) { - if (!flush_candidates[i].empty()) { - to_flush.push_back(&flush_candidates[i]); - tmp_lists.push_back(lists[i]); - tmp_cf_ids.push_back(i); - tmp_options_list.push_back(mutable_cf_options_list[i]); - } - } - Status s = Mock_InstallMemtableAtomicFlushResults( - tmp_lists, tmp_cf_ids, tmp_options_list, to_flush, &to_delete); - ASSERT_OK(s); - - for (auto i = 0; i != num_cfs; ++i) { - for (auto j = 0; j != num_tables_per_cf; ++j) { - if (static_cast(j) <= flush_memtable_ids[i]) { - ASSERT_LT(0, tables[i][j]->GetFileNumber()); - } - } - ASSERT_EQ( - static_cast(num_tables_per_cf) - flush_candidates[i].size(), - lists[i]->NumNotFlushed()); - } - - to_delete.clear(); - for (auto list : lists) { - list->current()->Unref(&to_delete); - delete list; - } - for (auto& mutable_cf_options : mutable_cf_options_list) { - if (mutable_cf_options != nullptr) { - delete mutable_cf_options; - mutable_cf_options = nullptr; - } - } - // All memtables in tables array must have been flushed, thus ready to be - // deleted. - ASSERT_EQ(to_delete.size(), tables.size() * tables.front().size()); - for (const auto& m : to_delete) { - // Refcount should be 0 after calling InstallMemtableFlushResults. - // Verify this by Ref'ing and then Unref'ing. - m->Ref(); - ASSERT_EQ(m, m->Unref()); - delete m; - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/merge_helper_test.cc b/db/merge_helper_test.cc deleted file mode 100644 index 05408d5b9..000000000 --- a/db/merge_helper_test.cc +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/merge_helper.h" - -#include -#include -#include - -#include "db/dbformat.h" -#include "rocksdb/comparator.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding.h" -#include "util/vector_iterator.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -class MergeHelperTest : public testing::Test { - public: - MergeHelperTest() : icmp_(BytewiseComparator()) { env_ = Env::Default(); } - - ~MergeHelperTest() override = default; - - Status Run(SequenceNumber stop_before, bool at_bottom, - SequenceNumber latest_snapshot = 0) { - iter_.reset(new VectorIterator(ks_, vs_, &icmp_)); - iter_->SeekToFirst(); - merge_helper_.reset(new MergeHelper(env_, icmp_.user_comparator(), - merge_op_.get(), filter_.get(), nullptr, - false, latest_snapshot)); - return merge_helper_->MergeUntil( - iter_.get(), nullptr /* range_del_agg */, stop_before, at_bottom, - false /* allow_data_in_errors */, nullptr /* blob_fetcher */, - nullptr /* full_history_ts_low */, nullptr /* prefetch_buffers */, - nullptr /* c_iter_stats */); - } - - void AddKeyVal(const std::string& user_key, const SequenceNumber& seq, - const ValueType& t, const std::string& val, - bool corrupt = false) { - InternalKey ikey(user_key, seq, t); - if (corrupt) { - test::CorruptKeyType(&ikey); - } - ks_.push_back(ikey.Encode().ToString()); - vs_.push_back(val); - } - - Env* env_; - InternalKeyComparator icmp_; - std::unique_ptr iter_; - std::shared_ptr merge_op_; - std::unique_ptr merge_helper_; - std::vector ks_; - std::vector vs_; - std::unique_ptr filter_; -}; - -// If MergeHelper encounters a new key on the last level, we know that -// the key has no more history and it can merge keys. -TEST_F(MergeHelperTest, MergeAtBottomSuccess) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - - AddKeyVal("a", 20, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 10, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("b", 10, kTypeMerge, test::EncodeInt(4U)); // <- iter_ after merge - - ASSERT_TRUE(Run(0, true).ok()); - ASSERT_EQ(ks_[2], iter_->key()); - ASSERT_EQ(test::KeyStr("a", 20, kTypeValue), merge_helper_->keys()[0]); - ASSERT_EQ(test::EncodeInt(4U), merge_helper_->values()[0]); - ASSERT_EQ(1U, merge_helper_->keys().size()); - ASSERT_EQ(1U, merge_helper_->values().size()); -} - -// Merging with a value results in a successful merge. -TEST_F(MergeHelperTest, MergeValue) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - - AddKeyVal("a", 40, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 20, kTypeValue, test::EncodeInt(4U)); // <- iter_ after merge - AddKeyVal("a", 10, kTypeMerge, test::EncodeInt(1U)); - - ASSERT_TRUE(Run(0, false).ok()); - ASSERT_EQ(ks_[3], iter_->key()); - ASSERT_EQ(test::KeyStr("a", 40, kTypeValue), merge_helper_->keys()[0]); - ASSERT_EQ(test::EncodeInt(8U), merge_helper_->values()[0]); - ASSERT_EQ(1U, merge_helper_->keys().size()); - ASSERT_EQ(1U, merge_helper_->values().size()); -} - -// Merging stops before a snapshot. -TEST_F(MergeHelperTest, SnapshotBeforeValue) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - - AddKeyVal("a", 50, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 40, kTypeMerge, test::EncodeInt(3U)); // <- iter_ after merge - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 20, kTypeValue, test::EncodeInt(4U)); - AddKeyVal("a", 10, kTypeMerge, test::EncodeInt(1U)); - - ASSERT_TRUE(Run(31, true).IsMergeInProgress()); - ASSERT_EQ(ks_[2], iter_->key()); - ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), merge_helper_->keys()[0]); - ASSERT_EQ(test::EncodeInt(4U), merge_helper_->values()[0]); - ASSERT_EQ(1U, merge_helper_->keys().size()); - ASSERT_EQ(1U, merge_helper_->values().size()); -} - -// MergeHelper preserves the operand stack for merge operators that -// cannot do a partial merge. -TEST_F(MergeHelperTest, NoPartialMerge) { - merge_op_ = MergeOperators::CreateStringAppendTESTOperator(); - - AddKeyVal("a", 50, kTypeMerge, "v2"); - AddKeyVal("a", 40, kTypeMerge, "v"); // <- iter_ after merge - AddKeyVal("a", 30, kTypeMerge, "v"); - - ASSERT_TRUE(Run(31, true).IsMergeInProgress()); - ASSERT_EQ(ks_[2], iter_->key()); - ASSERT_EQ(test::KeyStr("a", 40, kTypeMerge), merge_helper_->keys()[0]); - ASSERT_EQ("v", merge_helper_->values()[0]); - ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), merge_helper_->keys()[1]); - ASSERT_EQ("v2", merge_helper_->values()[1]); - ASSERT_EQ(2U, merge_helper_->keys().size()); - ASSERT_EQ(2U, merge_helper_->values().size()); -} - -// A single operand can not be merged. -TEST_F(MergeHelperTest, SingleOperand) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - - AddKeyVal("a", 50, kTypeMerge, test::EncodeInt(1U)); - - ASSERT_TRUE(Run(31, false).IsMergeInProgress()); - ASSERT_FALSE(iter_->Valid()); - ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), merge_helper_->keys()[0]); - ASSERT_EQ(test::EncodeInt(1U), merge_helper_->values()[0]); - ASSERT_EQ(1U, merge_helper_->keys().size()); - ASSERT_EQ(1U, merge_helper_->values().size()); -} - -// Merging with a deletion turns the deletion into a value -TEST_F(MergeHelperTest, MergeDeletion) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 20, kTypeDeletion, ""); - - ASSERT_TRUE(Run(15, false).ok()); - ASSERT_FALSE(iter_->Valid()); - ASSERT_EQ(test::KeyStr("a", 30, kTypeValue), merge_helper_->keys()[0]); - ASSERT_EQ(test::EncodeInt(3U), merge_helper_->values()[0]); - ASSERT_EQ(1U, merge_helper_->keys().size()); - ASSERT_EQ(1U, merge_helper_->values().size()); -} - -// The merge helper stops upon encountering a corrupt key -TEST_F(MergeHelperTest, CorruptKey) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 25, kTypeMerge, test::EncodeInt(1U)); - // Corrupt key - AddKeyVal("a", 20, kTypeDeletion, "", true); // <- iter_ after merge - - ASSERT_TRUE(Run(15, false).IsMergeInProgress()); - ASSERT_EQ(ks_[2], iter_->key()); - ASSERT_EQ(test::KeyStr("a", 30, kTypeMerge), merge_helper_->keys()[0]); - ASSERT_EQ(test::EncodeInt(4U), merge_helper_->values()[0]); - ASSERT_EQ(1U, merge_helper_->keys().size()); - ASSERT_EQ(1U, merge_helper_->values().size()); -} - -// The compaction filter is called on every merge operand -TEST_F(MergeHelperTest, FilterMergeOperands) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - filter_.reset(new test::FilterNumber(5U)); - - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 29, kTypeMerge, test::EncodeInt(5U)); // Filtered - AddKeyVal("a", 28, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 27, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 26, kTypeMerge, test::EncodeInt(5U)); // Filtered - AddKeyVal("a", 25, kTypeValue, test::EncodeInt(1U)); - - ASSERT_TRUE(Run(15, false).ok()); - ASSERT_FALSE(iter_->Valid()); - MergeOutputIterator merge_output_iter(merge_helper_.get()); - merge_output_iter.SeekToFirst(); - ASSERT_EQ(test::KeyStr("a", 30, kTypeValue), - merge_output_iter.key().ToString()); - ASSERT_EQ(test::EncodeInt(8U), merge_output_iter.value().ToString()); - merge_output_iter.Next(); - ASSERT_FALSE(merge_output_iter.Valid()); -} - -TEST_F(MergeHelperTest, FilterAllMergeOperands) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - filter_.reset(new test::FilterNumber(5U)); - - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 29, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 28, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 27, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 26, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 25, kTypeMerge, test::EncodeInt(5U)); - - // filtered out all - ASSERT_TRUE(Run(15, false).ok()); - ASSERT_FALSE(iter_->Valid()); - MergeOutputIterator merge_output_iter(merge_helper_.get()); - merge_output_iter.SeekToFirst(); - ASSERT_FALSE(merge_output_iter.Valid()); - - // we have one operand that will survive because it's a delete - AddKeyVal("a", 24, kTypeDeletion, test::EncodeInt(5U)); - AddKeyVal("b", 23, kTypeValue, test::EncodeInt(5U)); - ASSERT_TRUE(Run(15, true).ok()); - merge_output_iter = MergeOutputIterator(merge_helper_.get()); - ASSERT_TRUE(iter_->Valid()); - merge_output_iter.SeekToFirst(); - ASSERT_FALSE(merge_output_iter.Valid()); - - // when all merge operands are filtered out, we leave the iterator pointing to - // the Put/Delete that survived - ASSERT_EQ(test::KeyStr("a", 24, kTypeDeletion), iter_->key().ToString()); - ASSERT_EQ(test::EncodeInt(5U), iter_->value().ToString()); -} - -// Make sure that merge operands are filtered at the beginning -TEST_F(MergeHelperTest, FilterFirstMergeOperand) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - filter_.reset(new test::FilterNumber(5U)); - - AddKeyVal("a", 31, kTypeMerge, test::EncodeInt(5U)); // Filtered - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(5U)); // Filtered - AddKeyVal("a", 29, kTypeMerge, test::EncodeInt(2U)); - AddKeyVal("a", 28, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 27, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 26, kTypeMerge, test::EncodeInt(5U)); // Filtered - AddKeyVal("a", 25, kTypeMerge, test::EncodeInt(5U)); // Filtered - AddKeyVal("b", 24, kTypeValue, test::EncodeInt(5U)); // next user key - - ASSERT_OK(Run(15, true)); - ASSERT_TRUE(iter_->Valid()); - MergeOutputIterator merge_output_iter(merge_helper_.get()); - merge_output_iter.SeekToFirst(); - // sequence number is 29 here, because the first merge operand got filtered - // out - ASSERT_EQ(test::KeyStr("a", 29, kTypeValue), - merge_output_iter.key().ToString()); - ASSERT_EQ(test::EncodeInt(6U), merge_output_iter.value().ToString()); - merge_output_iter.Next(); - ASSERT_FALSE(merge_output_iter.Valid()); - - // make sure that we're passing user keys into the filter - ASSERT_EQ("a", filter_->last_merge_operand_key()); -} - -// Make sure that merge operands are not filtered out if there's a snapshot -// pointing at them -TEST_F(MergeHelperTest, DontFilterMergeOperandsBeforeSnapshotTest) { - merge_op_ = MergeOperators::CreateUInt64AddOperator(); - filter_.reset(new test::FilterNumber(5U)); - - AddKeyVal("a", 31, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 30, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 29, kTypeMerge, test::EncodeInt(2U)); - AddKeyVal("a", 28, kTypeMerge, test::EncodeInt(1U)); - AddKeyVal("a", 27, kTypeMerge, test::EncodeInt(3U)); - AddKeyVal("a", 26, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("a", 25, kTypeMerge, test::EncodeInt(5U)); - AddKeyVal("b", 24, kTypeValue, test::EncodeInt(5U)); - - ASSERT_OK(Run(15, true, 32)); - ASSERT_TRUE(iter_->Valid()); - MergeOutputIterator merge_output_iter(merge_helper_.get()); - merge_output_iter.SeekToFirst(); - ASSERT_EQ(test::KeyStr("a", 31, kTypeValue), - merge_output_iter.key().ToString()); - ASSERT_EQ(test::EncodeInt(26U), merge_output_iter.value().ToString()); - merge_output_iter.Next(); - ASSERT_FALSE(merge_output_iter.Valid()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/merge_test.cc b/db/merge_test.cc deleted file mode 100644 index 6d1333e55..000000000 --- a/db/merge_test.cc +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include - -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/dbformat.h" -#include "db/write_batch_internal.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/comparator.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/merge_operator.h" -#include "rocksdb/utilities/db_ttl.h" -#include "test_util/testharness.h" -#include "util/coding.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -bool use_compression; - -class MergeTest : public testing::Test {}; - -size_t num_merge_operator_calls; -void resetNumMergeOperatorCalls() { num_merge_operator_calls = 0; } - -size_t num_partial_merge_calls; -void resetNumPartialMergeCalls() { num_partial_merge_calls = 0; } - -class CountMergeOperator : public AssociativeMergeOperator { - public: - CountMergeOperator() { - mergeOperator_ = MergeOperators::CreateUInt64AddOperator(); - } - - bool Merge(const Slice& key, const Slice* existing_value, const Slice& value, - std::string* new_value, Logger* logger) const override { - assert(new_value->empty()); - ++num_merge_operator_calls; - if (existing_value == nullptr) { - new_value->assign(value.data(), value.size()); - return true; - } - - return mergeOperator_->PartialMerge(key, *existing_value, value, new_value, - logger); - } - - bool PartialMergeMulti(const Slice& key, - const std::deque& operand_list, - std::string* new_value, - Logger* logger) const override { - assert(new_value->empty()); - ++num_partial_merge_calls; - return mergeOperator_->PartialMergeMulti(key, operand_list, new_value, - logger); - } - - const char* Name() const override { return "UInt64AddOperator"; } - - private: - std::shared_ptr mergeOperator_; -}; - -class EnvMergeTest : public EnvWrapper { - public: - EnvMergeTest() : EnvWrapper(Env::Default()) {} - static const char* kClassName() { return "MergeEnv"; } - const char* Name() const override { return kClassName(); } - // ~EnvMergeTest() override {} - - uint64_t NowNanos() override { - ++now_nanos_count_; - return target()->NowNanos(); - } - - static uint64_t now_nanos_count_; - - static std::unique_ptr singleton_; - - static EnvMergeTest* GetInstance() { - if (nullptr == singleton_) singleton_.reset(new EnvMergeTest); - return singleton_.get(); - } -}; - -uint64_t EnvMergeTest::now_nanos_count_{0}; -std::unique_ptr EnvMergeTest::singleton_; - -std::shared_ptr OpenDb(const std::string& dbname, const bool ttl = false, - const size_t max_successive_merges = 0) { - DB* db; - Options options; - options.create_if_missing = true; - options.merge_operator = std::make_shared(); - options.max_successive_merges = max_successive_merges; - options.env = EnvMergeTest::GetInstance(); - EXPECT_OK(DestroyDB(dbname, Options())); - Status s; - if (ttl) { - DBWithTTL* db_with_ttl; - s = DBWithTTL::Open(options, dbname, &db_with_ttl); - db = db_with_ttl; - } else { - s = DB::Open(options, dbname, &db); - } - EXPECT_OK(s); - assert(s.ok()); - // Allowed to call NowNanos during DB creation (in GenerateRawUniqueId() for - // session ID) - EnvMergeTest::now_nanos_count_ = 0; - return std::shared_ptr(db); -} - -// Imagine we are maintaining a set of uint64 counters. -// Each counter has a distinct name. And we would like -// to support four high level operations: -// set, add, get and remove -// This is a quick implementation without a Merge operation. -class Counters { - protected: - std::shared_ptr db_; - - WriteOptions put_option_; - ReadOptions get_option_; - WriteOptions delete_option_; - - uint64_t default_; - - public: - explicit Counters(std::shared_ptr db, uint64_t defaultCount = 0) - : db_(db), - put_option_(), - get_option_(), - delete_option_(), - default_(defaultCount) { - assert(db_); - } - - virtual ~Counters() {} - - // public interface of Counters. - // All four functions return false - // if the underlying level db operation failed. - - // mapped to a levedb Put - bool set(const std::string& key, uint64_t value) { - // just treat the internal rep of int64 as the string - char buf[sizeof(value)]; - EncodeFixed64(buf, value); - Slice slice(buf, sizeof(value)); - auto s = db_->Put(put_option_, key, slice); - - if (s.ok()) { - return true; - } else { - std::cerr << s.ToString() << std::endl; - return false; - } - } - - // mapped to a rocksdb Delete - bool remove(const std::string& key) { - auto s = db_->Delete(delete_option_, key); - - if (s.ok()) { - return true; - } else { - std::cerr << s.ToString() << std::endl; - return false; - } - } - - // mapped to a rocksdb Get - bool get(const std::string& key, uint64_t* value) { - std::string str; - auto s = db_->Get(get_option_, key, &str); - - if (s.IsNotFound()) { - // return default value if not found; - *value = default_; - return true; - } else if (s.ok()) { - // deserialization - if (str.size() != sizeof(uint64_t)) { - std::cerr << "value corruption\n"; - return false; - } - *value = DecodeFixed64(&str[0]); - return true; - } else { - std::cerr << s.ToString() << std::endl; - return false; - } - } - - // 'add' is implemented as get -> modify -> set - // An alternative is a single merge operation, see MergeBasedCounters - virtual bool add(const std::string& key, uint64_t value) { - uint64_t base = default_; - return get(key, &base) && set(key, base + value); - } - - // convenience functions for testing - void assert_set(const std::string& key, uint64_t value) { - assert(set(key, value)); - } - - void assert_remove(const std::string& key) { assert(remove(key)); } - - uint64_t assert_get(const std::string& key) { - uint64_t value = default_; - int result = get(key, &value); - assert(result); - if (result == 0) exit(1); // Disable unused variable warning. - return value; - } - - void assert_add(const std::string& key, uint64_t value) { - int result = add(key, value); - assert(result); - if (result == 0) exit(1); // Disable unused variable warning. - } -}; - -// Implement 'add' directly with the new Merge operation -class MergeBasedCounters : public Counters { - private: - WriteOptions merge_option_; // for merge - - public: - explicit MergeBasedCounters(std::shared_ptr db, uint64_t defaultCount = 0) - : Counters(db, defaultCount), merge_option_() {} - - // mapped to a rocksdb Merge operation - bool add(const std::string& key, uint64_t value) override { - char encoded[sizeof(uint64_t)]; - EncodeFixed64(encoded, value); - Slice slice(encoded, sizeof(uint64_t)); - auto s = db_->Merge(merge_option_, key, slice); - - if (s.ok()) { - return true; - } else { - std::cerr << s.ToString() << std::endl; - return false; - } - } -}; - -void dumpDb(DB* db) { - auto it = std::unique_ptr(db->NewIterator(ReadOptions())); - for (it->SeekToFirst(); it->Valid(); it->Next()) { - // uint64_t value = DecodeFixed64(it->value().data()); - // std::cout << it->key().ToString() << ": " << value << std::endl; - } - assert(it->status().ok()); // Check for any errors found during the scan -} - -void testCounters(Counters& counters, DB* db, bool test_compaction) { - FlushOptions o; - o.wait = true; - - counters.assert_set("a", 1); - - if (test_compaction) { - ASSERT_OK(db->Flush(o)); - } - - ASSERT_EQ(counters.assert_get("a"), 1); - - counters.assert_remove("b"); - - // defaut value is 0 if non-existent - ASSERT_EQ(counters.assert_get("b"), 0); - - counters.assert_add("a", 2); - - if (test_compaction) { - ASSERT_OK(db->Flush(o)); - } - - // 1+2 = 3 - ASSERT_EQ(counters.assert_get("a"), 3); - - dumpDb(db); - - // 1+...+49 = ? - uint64_t sum = 0; - for (int i = 1; i < 50; i++) { - counters.assert_add("b", i); - sum += i; - } - ASSERT_EQ(counters.assert_get("b"), sum); - - dumpDb(db); - - if (test_compaction) { - ASSERT_OK(db->Flush(o)); - - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - dumpDb(db); - - ASSERT_EQ(counters.assert_get("a"), 3); - ASSERT_EQ(counters.assert_get("b"), sum); - } -} - -void testCountersWithFlushAndCompaction(Counters& counters, DB* db) { - ASSERT_OK(db->Put({}, "1", "1")); - ASSERT_OK(db->Flush(FlushOptions())); - - std::atomic cnt{0}; - const auto get_thread_id = [&cnt]() { - thread_local int thread_id{cnt++}; - return thread_id; - }; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:BeforeWriterWaiting", [&](void* /*arg*/) { - int thread_id = get_thread_id(); - if (1 == thread_id) { - TEST_SYNC_POINT( - "testCountersWithFlushAndCompaction::bg_compact_thread:0"); - } else if (2 == thread_id) { - TEST_SYNC_POINT( - "testCountersWithFlushAndCompaction::bg_flush_thread:0"); - } - }); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WriteManifest", [&](void* /*arg*/) { - int thread_id = get_thread_id(); - if (0 == thread_id) { - TEST_SYNC_POINT( - "testCountersWithFlushAndCompaction::set_options_thread:0"); - TEST_SYNC_POINT( - "testCountersWithFlushAndCompaction::set_options_thread:1"); - } - }); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::LogAndApply:WakeUpAndDone", [&](void* arg) { - auto* mutex = reinterpret_cast(arg); - mutex->AssertHeld(); - int thread_id = get_thread_id(); - ASSERT_EQ(2, thread_id); - mutex->Unlock(); - TEST_SYNC_POINT( - "testCountersWithFlushAndCompaction::bg_flush_thread:1"); - TEST_SYNC_POINT( - "testCountersWithFlushAndCompaction::bg_flush_thread:2"); - mutex->Lock(); - }); - SyncPoint::GetInstance()->LoadDependency({ - {"testCountersWithFlushAndCompaction::set_options_thread:0", - "testCountersWithCompactionAndFlush:BeforeCompact"}, - {"testCountersWithFlushAndCompaction::bg_compact_thread:0", - "testCountersWithFlushAndCompaction:BeforeIncCounters"}, - {"testCountersWithFlushAndCompaction::bg_flush_thread:0", - "testCountersWithFlushAndCompaction::set_options_thread:1"}, - {"testCountersWithFlushAndCompaction::bg_flush_thread:1", - "testCountersWithFlushAndCompaction:BeforeVerification"}, - {"testCountersWithFlushAndCompaction:AfterGet", - "testCountersWithFlushAndCompaction::bg_flush_thread:2"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - port::Thread set_options_thread([&]() { - ASSERT_OK(reinterpret_cast(db)->SetOptions( - {{"disable_auto_compactions", "false"}})); - }); - TEST_SYNC_POINT("testCountersWithCompactionAndFlush:BeforeCompact"); - port::Thread compact_thread([&]() { - ASSERT_OK(reinterpret_cast(db)->CompactRange( - CompactRangeOptions(), db->DefaultColumnFamily(), nullptr, nullptr)); - }); - - TEST_SYNC_POINT("testCountersWithFlushAndCompaction:BeforeIncCounters"); - counters.add("test-key", 1); - - FlushOptions flush_opts; - flush_opts.wait = false; - ASSERT_OK(db->Flush(flush_opts)); - - TEST_SYNC_POINT("testCountersWithFlushAndCompaction:BeforeVerification"); - std::string expected; - PutFixed64(&expected, 1); - std::string actual; - Status s = db->Get(ReadOptions(), "test-key", &actual); - TEST_SYNC_POINT("testCountersWithFlushAndCompaction:AfterGet"); - set_options_thread.join(); - compact_thread.join(); - ASSERT_OK(s); - ASSERT_EQ(expected, actual); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -void testSuccessiveMerge(Counters& counters, size_t max_num_merges, - size_t num_merges) { - counters.assert_remove("z"); - uint64_t sum = 0; - - for (size_t i = 1; i <= num_merges; ++i) { - resetNumMergeOperatorCalls(); - counters.assert_add("z", i); - sum += i; - - if (i % (max_num_merges + 1) == 0) { - ASSERT_EQ(num_merge_operator_calls, max_num_merges + 1); - } else { - ASSERT_EQ(num_merge_operator_calls, 0); - } - - resetNumMergeOperatorCalls(); - ASSERT_EQ(counters.assert_get("z"), sum); - ASSERT_EQ(num_merge_operator_calls, i % (max_num_merges + 1)); - } -} - -void testPartialMerge(Counters* counters, DB* db, size_t max_merge, - size_t min_merge, size_t count) { - FlushOptions o; - o.wait = true; - - // Test case 1: partial merge should be called when the number of merge - // operands exceeds the threshold. - uint64_t tmp_sum = 0; - resetNumPartialMergeCalls(); - for (size_t i = 1; i <= count; i++) { - counters->assert_add("b", i); - tmp_sum += i; - } - ASSERT_OK(db->Flush(o)); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(tmp_sum, counters->assert_get("b")); - if (count > max_merge) { - // in this case, FullMerge should be called instead. - ASSERT_EQ(num_partial_merge_calls, 0U); - } else { - // if count >= min_merge, then partial merge should be called once. - ASSERT_EQ((count >= min_merge), (num_partial_merge_calls == 1)); - } - - // Test case 2: partial merge should not be called when a put is found. - resetNumPartialMergeCalls(); - tmp_sum = 0; - ASSERT_OK(db->Put(ROCKSDB_NAMESPACE::WriteOptions(), "c", "10")); - for (size_t i = 1; i <= count; i++) { - counters->assert_add("c", i); - tmp_sum += i; - } - ASSERT_OK(db->Flush(o)); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(tmp_sum, counters->assert_get("c")); - ASSERT_EQ(num_partial_merge_calls, 0U); - // NowNanos was previously called in MergeHelper::FilterMerge(), which - // harmed performance. - ASSERT_EQ(EnvMergeTest::now_nanos_count_, 0U); -} - -void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges, - size_t num_merges) { - ASSERT_GT(num_merges, max_num_merges); - - Slice key("BatchSuccessiveMerge"); - uint64_t merge_value = 1; - char buf[sizeof(merge_value)]; - EncodeFixed64(buf, merge_value); - Slice merge_value_slice(buf, sizeof(merge_value)); - - // Create the batch - WriteBatch batch; - for (size_t i = 0; i < num_merges; ++i) { - ASSERT_OK(batch.Merge(key, merge_value_slice)); - } - - // Apply to memtable and count the number of merges - resetNumMergeOperatorCalls(); - ASSERT_OK(db->Write(WriteOptions(), &batch)); - ASSERT_EQ( - num_merge_operator_calls, - static_cast(num_merges - (num_merges % (max_num_merges + 1)))); - - // Get the value - resetNumMergeOperatorCalls(); - std::string get_value_str; - ASSERT_OK(db->Get(ReadOptions(), key, &get_value_str)); - assert(get_value_str.size() == sizeof(uint64_t)); - uint64_t get_value = DecodeFixed64(&get_value_str[0]); - ASSERT_EQ(get_value, num_merges * merge_value); - ASSERT_EQ(num_merge_operator_calls, - static_cast((num_merges % (max_num_merges + 1)))); -} - -void runTest(const std::string& dbname, const bool use_ttl = false) { - { - auto db = OpenDb(dbname, use_ttl); - - { - Counters counters(db, 0); - testCounters(counters, db.get(), true); - } - - { - MergeBasedCounters counters(db, 0); - testCounters(counters, db.get(), use_compression); - } - } - - ASSERT_OK(DestroyDB(dbname, Options())); - - { - size_t max_merge = 5; - auto db = OpenDb(dbname, use_ttl, max_merge); - MergeBasedCounters counters(db, 0); - testCounters(counters, db.get(), use_compression); - testSuccessiveMerge(counters, max_merge, max_merge * 2); - testSingleBatchSuccessiveMerge(db.get(), 5, 7); - ASSERT_OK(db->Close()); - ASSERT_OK(DestroyDB(dbname, Options())); - } - - { - size_t max_merge = 100; - // Min merge is hard-coded to 2. - uint32_t min_merge = 2; - for (uint32_t count = min_merge - 1; count <= min_merge + 1; count++) { - auto db = OpenDb(dbname, use_ttl, max_merge); - MergeBasedCounters counters(db, 0); - testPartialMerge(&counters, db.get(), max_merge, min_merge, count); - ASSERT_OK(db->Close()); - ASSERT_OK(DestroyDB(dbname, Options())); - } - { - auto db = OpenDb(dbname, use_ttl, max_merge); - MergeBasedCounters counters(db, 0); - testPartialMerge(&counters, db.get(), max_merge, min_merge, - min_merge * 10); - ASSERT_OK(db->Close()); - ASSERT_OK(DestroyDB(dbname, Options())); - } - } - - { - { - auto db = OpenDb(dbname); - MergeBasedCounters counters(db, 0); - counters.add("test-key", 1); - counters.add("test-key", 1); - counters.add("test-key", 1); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - } - - DB* reopen_db; - ASSERT_OK(DB::Open(Options(), dbname, &reopen_db)); - std::string value; - ASSERT_NOK(reopen_db->Get(ReadOptions(), "test-key", &value)); - delete reopen_db; - ASSERT_OK(DestroyDB(dbname, Options())); - } - - /* Temporary remove this test - { - std::cout << "Test merge-operator not set after reopen (recovery case)\n"; - { - auto db = OpenDb(dbname); - MergeBasedCounters counters(db, 0); - counters.add("test-key", 1); - counters.add("test-key", 1); - counters.add("test-key", 1); - } - - DB* reopen_db; - ASSERT_TRUE(DB::Open(Options(), dbname, &reopen_db).IsInvalidArgument()); - } - */ -} - -TEST_F(MergeTest, MergeDbTest) { - runTest(test::PerThreadDBPath("merge_testdb")); -} - -TEST_F(MergeTest, MergeDbTtlTest) { - runTest(test::PerThreadDBPath("merge_testdbttl"), - true); // Run test on TTL database -} - -TEST_F(MergeTest, MergeWithCompactionAndFlush) { - const std::string dbname = - test::PerThreadDBPath("merge_with_compaction_and_flush"); - { - auto db = OpenDb(dbname); - { - MergeBasedCounters counters(db, 0); - testCountersWithFlushAndCompaction(counters, db.get()); - } - } - ASSERT_OK(DestroyDB(dbname, Options())); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::use_compression = false; - if (argc > 1) { - ROCKSDB_NAMESPACE::use_compression = true; - } - - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/obsolete_files_test.cc b/db/obsolete_files_test.cc deleted file mode 100644 index 03f38c09f..000000000 --- a/db/obsolete_files_test.cc +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - - -#include - -#include -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/version_set.h" -#include "db/write_batch_internal.h" -#include "file/filename.h" -#include "port/stack_trace.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/transaction_log.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class ObsoleteFilesTest : public DBTestBase { - public: - ObsoleteFilesTest() - : DBTestBase("obsolete_files_test", /*env_do_fsync=*/true), - wal_dir_(dbname_ + "/wal_files") {} - - void AddKeys(int numkeys, int startkey) { - WriteOptions options; - options.sync = false; - for (int i = startkey; i < (numkeys + startkey); i++) { - std::string temp = std::to_string(i); - Slice key(temp); - Slice value(temp); - ASSERT_OK(db_->Put(options, key, value)); - } - } - - void createLevel0Files(int numFiles, int numKeysPerFile) { - int startKey = 0; - for (int i = 0; i < numFiles; i++) { - AddKeys(numKeysPerFile, startKey); - startKey += numKeysPerFile; - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_OK( - dbfull()->TEST_WaitForCompact()); // wait for background flush (flush - // is also a kind of compaction). - } - } - - void CheckFileTypeCounts(const std::string& dir, int required_log, - int required_sst, int required_manifest) { - std::vector filenames; - ASSERT_OK(env_->GetChildren(dir, &filenames)); - - int log_cnt = 0; - int sst_cnt = 0; - int manifest_cnt = 0; - for (auto file : filenames) { - uint64_t number; - FileType type; - if (ParseFileName(file, &number, &type)) { - log_cnt += (type == kWalFile); - sst_cnt += (type == kTableFile); - manifest_cnt += (type == kDescriptorFile); - } - } - ASSERT_EQ(required_log, log_cnt); - ASSERT_EQ(required_sst, sst_cnt); - ASSERT_EQ(required_manifest, manifest_cnt); - } - - void ReopenDB() { - Options options = CurrentOptions(); - // Trigger compaction when the number of level 0 files reaches 2. - options.create_if_missing = true; - options.level0_file_num_compaction_trigger = 2; - options.disable_auto_compactions = false; - options.delete_obsolete_files_period_micros = 0; // always do full purge - options.enable_thread_tracking = true; - options.write_buffer_size = 1024 * 1024 * 1000; - options.target_file_size_base = 1024 * 1024 * 1000; - options.max_bytes_for_level_base = 1024 * 1024 * 1000; - options.WAL_ttl_seconds = 300; // Used to test log files - options.WAL_size_limit_MB = 1024; // Used to test log files - options.wal_dir = wal_dir_; - - // Note: the following prevents an otherwise harmless data race between the - // test setup code (AddBlobFile) in ObsoleteFilesTest.BlobFiles and the - // periodic stat dumping thread. - options.stats_dump_period_sec = 0; - - Destroy(options); - Reopen(options); - } - - const std::string wal_dir_; -}; - -TEST_F(ObsoleteFilesTest, RaceForObsoleteFileDeletion) { - ReopenDB(); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"DBImpl::BackgroundCallCompaction:FoundObsoleteFiles", - "ObsoleteFilesTest::RaceForObsoleteFileDeletion:1"}, - {"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles", - "ObsoleteFilesTest::RaceForObsoleteFileDeletion:2"}, - }); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DeleteObsoleteFileImpl:AfterDeletion", [&](void* arg) { - Status* p_status = reinterpret_cast(arg); - ASSERT_OK(*p_status); - }); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::CloseHelper:PendingPurgeFinished", [&](void* arg) { - std::unordered_set* files_grabbed_for_purge_ptr = - reinterpret_cast*>(arg); - ASSERT_TRUE(files_grabbed_for_purge_ptr->empty()); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - createLevel0Files(2, 50000); - CheckFileTypeCounts(wal_dir_, 1, 0, 0); - - port::Thread user_thread([this]() { - JobContext jobCxt(0); - TEST_SYNC_POINT("ObsoleteFilesTest::RaceForObsoleteFileDeletion:1"); - dbfull()->TEST_LockMutex(); - dbfull()->FindObsoleteFiles(&jobCxt, true /* force=true */, - false /* no_full_scan=false */); - dbfull()->TEST_UnlockMutex(); - TEST_SYNC_POINT("ObsoleteFilesTest::RaceForObsoleteFileDeletion:2"); - dbfull()->PurgeObsoleteFiles(jobCxt); - jobCxt.Clean(); - }); - - user_thread.join(); -} - -TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) { - ReopenDB(); - - createLevel0Files(2, 50000); - CheckFileTypeCounts(wal_dir_, 1, 0, 0); - - ASSERT_OK(dbfull()->DisableFileDeletions()); - for (int i = 0; i != 4; ++i) { - if (i % 2) { - ASSERT_OK(dbfull()->SetOptions(dbfull()->DefaultColumnFamily(), - {{"paranoid_file_checks", "false"}})); - } else { - ASSERT_OK(dbfull()->SetOptions(dbfull()->DefaultColumnFamily(), - {{"paranoid_file_checks", "true"}})); - } - } - ASSERT_OK(dbfull()->EnableFileDeletions(true /* force */)); - - Close(); - - std::vector files; - int opts_file_count = 0; - ASSERT_OK(env_->GetChildren(dbname_, &files)); - for (const auto& file : files) { - uint64_t file_num; - Slice dummy_info_log_name_prefix; - FileType type; - WalFileType log_type; - if (ParseFileName(file, &file_num, dummy_info_log_name_prefix, &type, - &log_type) && - type == kOptionsFile) { - opts_file_count++; - } - } - ASSERT_EQ(2, opts_file_count); -} - -TEST_F(ObsoleteFilesTest, BlobFiles) { - ReopenDB(); - - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - assert(versions->GetColumnFamilySet()); - - ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - const ImmutableCFOptions* const ioptions = cfd->ioptions(); - assert(ioptions); - assert(!ioptions->cf_paths.empty()); - - const std::string& path = ioptions->cf_paths.front().path; - - // Add an obsolete blob file. - constexpr uint64_t first_blob_file_number = 234; - versions->AddObsoleteBlobFile(first_blob_file_number, path); - - // Add a live blob file. - Version* const version = cfd->current(); - assert(version); - - VersionStorageInfo* const storage_info = version->storage_info(); - assert(storage_info); - - constexpr uint64_t second_blob_file_number = 456; - constexpr uint64_t second_total_blob_count = 100; - constexpr uint64_t second_total_blob_bytes = 2000000; - constexpr char second_checksum_method[] = "CRC32B"; - constexpr char second_checksum_value[] = "\x6d\xbd\xf2\x3a"; - - auto shared_meta = SharedBlobFileMetaData::Create( - second_blob_file_number, second_total_blob_count, second_total_blob_bytes, - second_checksum_method, second_checksum_value); - - constexpr uint64_t second_garbage_blob_count = 0; - constexpr uint64_t second_garbage_blob_bytes = 0; - - auto meta = BlobFileMetaData::Create( - std::move(shared_meta), BlobFileMetaData::LinkedSsts(), - second_garbage_blob_count, second_garbage_blob_bytes); - - storage_info->AddBlobFile(std::move(meta)); - - // Check for obsolete files and make sure the first blob file is picked up - // and grabbed for purge. The second blob file should be on the live list. - constexpr int job_id = 0; - JobContext job_context{job_id}; - - dbfull()->TEST_LockMutex(); - constexpr bool force_full_scan = false; - dbfull()->FindObsoleteFiles(&job_context, force_full_scan); - dbfull()->TEST_UnlockMutex(); - - ASSERT_TRUE(job_context.HaveSomethingToDelete()); - ASSERT_EQ(job_context.blob_delete_files.size(), 1); - ASSERT_EQ(job_context.blob_delete_files[0].GetBlobFileNumber(), - first_blob_file_number); - - const auto& files_grabbed_for_purge = - dbfull()->TEST_GetFilesGrabbedForPurge(); - ASSERT_NE(files_grabbed_for_purge.find(first_blob_file_number), - files_grabbed_for_purge.end()); - - ASSERT_EQ(job_context.blob_live.size(), 1); - ASSERT_EQ(job_context.blob_live[0], second_blob_file_number); - - // Hack the job context a bit by adding a few files to the full scan - // list and adjusting the pending file number. We add the two files - // above as well as two additional ones, where one is old - // and should be cleaned up, and the other is still pending. - constexpr uint64_t old_blob_file_number = 123; - constexpr uint64_t pending_blob_file_number = 567; - - job_context.full_scan_candidate_files.emplace_back( - BlobFileName(old_blob_file_number), path); - job_context.full_scan_candidate_files.emplace_back( - BlobFileName(first_blob_file_number), path); - job_context.full_scan_candidate_files.emplace_back( - BlobFileName(second_blob_file_number), path); - job_context.full_scan_candidate_files.emplace_back( - BlobFileName(pending_blob_file_number), path); - - job_context.min_pending_output = pending_blob_file_number; - - // Purge obsolete files and make sure we purge the old file and the first file - // (and keep the second file and the pending file). - std::vector deleted_files; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::DeleteObsoleteFileImpl::BeforeDeletion", [&](void* arg) { - const std::string* file = static_cast(arg); - assert(file); - - constexpr char blob_extension[] = ".blob"; - - if (file->find(blob_extension) != std::string::npos) { - deleted_files.emplace_back(*file); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - dbfull()->PurgeObsoleteFiles(job_context); - job_context.Clean(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - ASSERT_EQ(files_grabbed_for_purge.find(first_blob_file_number), - files_grabbed_for_purge.end()); - - std::sort(deleted_files.begin(), deleted_files.end()); - const std::vector expected_deleted_files{ - BlobFileName(path, old_blob_file_number), - BlobFileName(path, first_blob_file_number)}; - - ASSERT_EQ(deleted_files, expected_deleted_files); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/options_file_test.cc b/db/options_file_test.cc deleted file mode 100644 index c3adbeb64..000000000 --- a/db/options_file_test.cc +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "rocksdb/options.h" -#include "rocksdb/table.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { -class OptionsFileTest : public testing::Test { - public: - OptionsFileTest() : dbname_(test::PerThreadDBPath("options_file_test")) {} - - std::string dbname_; -}; - -namespace { -void UpdateOptionsFiles(DB* db, - std::unordered_set* filename_history, - int* options_files_count) { - std::vector filenames; - EXPECT_OK(db->GetEnv()->GetChildren(db->GetName(), &filenames)); - uint64_t number; - FileType type; - *options_files_count = 0; - for (auto filename : filenames) { - if (ParseFileName(filename, &number, &type) && type == kOptionsFile) { - filename_history->insert(filename); - (*options_files_count)++; - } - } -} - -// Verify whether the current Options Files are the latest ones. -void VerifyOptionsFileName( - DB* db, const std::unordered_set& past_filenames) { - std::vector filenames; - std::unordered_set current_filenames; - EXPECT_OK(db->GetEnv()->GetChildren(db->GetName(), &filenames)); - uint64_t number; - FileType type; - for (auto filename : filenames) { - if (ParseFileName(filename, &number, &type) && type == kOptionsFile) { - current_filenames.insert(filename); - } - } - for (auto past_filename : past_filenames) { - if (current_filenames.find(past_filename) != current_filenames.end()) { - continue; - } - for (auto filename : current_filenames) { - ASSERT_GT(filename, past_filename); - } - } -} -} // anonymous namespace - -TEST_F(OptionsFileTest, NumberOfOptionsFiles) { - const int kReopenCount = 20; - Options opt; - opt.create_if_missing = true; - ASSERT_OK(DestroyDB(dbname_, opt)); - std::unordered_set filename_history; - DB* db; - for (int i = 0; i < kReopenCount; ++i) { - ASSERT_OK(DB::Open(opt, dbname_, &db)); - int num_options_files = 0; - UpdateOptionsFiles(db, &filename_history, &num_options_files); - ASSERT_GT(num_options_files, 0); - ASSERT_LE(num_options_files, 2); - // Make sure we always keep the latest option files. - VerifyOptionsFileName(db, filename_history); - delete db; - } -} - -TEST_F(OptionsFileTest, OptionsFileName) { - const uint64_t kOptionsFileNum = 12345; - uint64_t number; - FileType type; - - auto options_file_name = OptionsFileName("", kOptionsFileNum); - ASSERT_TRUE(ParseFileName(options_file_name, &number, &type, nullptr)); - ASSERT_EQ(type, kOptionsFile); - ASSERT_EQ(number, kOptionsFileNum); - - const uint64_t kTempOptionsFileNum = 54352; - auto temp_options_file_name = TempOptionsFileName("", kTempOptionsFileNum); - ASSERT_TRUE(ParseFileName(temp_options_file_name, &number, &type, nullptr)); - ASSERT_NE(temp_options_file_name.find(kTempFileNameSuffix), - std::string::npos); - ASSERT_EQ(type, kTempFile); - ASSERT_EQ(number, kTempOptionsFileNum); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { -#if !(defined NDEBUG) || !defined(OS_WIN) - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -#else - return 0; -#endif // !(defined NDEBUG) || !defined(OS_WIN) -} diff --git a/db/perf_context_test.cc b/db/perf_context_test.cc deleted file mode 100644 index 3e78dbe27..000000000 --- a/db/perf_context_test.cc +++ /dev/null @@ -1,1157 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include "rocksdb/perf_context.h" - -#include -#include -#include -#include - -#include "monitoring/histogram.h" -#include "monitoring/instrumented_mutex.h" -#include "monitoring/perf_context_imp.h" -#include "monitoring/thread_status_util.h" -#include "port/port.h" -#include "rocksdb/db.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/system_clock.h" -#include "test_util/testharness.h" -#include "util/stop_watch.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -bool FLAGS_random_key = false; -bool FLAGS_use_set_based_memetable = false; -int FLAGS_total_keys = 100; -int FLAGS_write_buffer_size = 1000000000; -int FLAGS_max_write_buffer_number = 8; -int FLAGS_min_write_buffer_number_to_merge = 7; -bool FLAGS_verbose = false; - -// Path to the database on file system -const std::string kDbName = - ROCKSDB_NAMESPACE::test::PerThreadDBPath("perf_context_test"); - -namespace ROCKSDB_NAMESPACE { - -std::shared_ptr OpenDb(bool read_only = false) { - DB* db; - Options options; - options.create_if_missing = true; - options.max_open_files = -1; - options.write_buffer_size = FLAGS_write_buffer_size; - options.max_write_buffer_number = FLAGS_max_write_buffer_number; - options.min_write_buffer_number_to_merge = - FLAGS_min_write_buffer_number_to_merge; - - if (FLAGS_use_set_based_memetable) { - options.prefix_extractor.reset( - ROCKSDB_NAMESPACE::NewFixedPrefixTransform(0)); - options.memtable_factory.reset(NewHashSkipListRepFactory()); - } - - Status s; - if (!read_only) { - s = DB::Open(options, kDbName, &db); - } else { - s = DB::OpenForReadOnly(options, kDbName, &db); - } - EXPECT_OK(s); - return std::shared_ptr(db); -} - -class PerfContextTest : public testing::Test {}; - -TEST_F(PerfContextTest, SeekIntoDeletion) { - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - - for (int i = 0; i < FLAGS_total_keys; ++i) { - std::string key = "k" + std::to_string(i); - std::string value = "v" + std::to_string(i); - - ASSERT_OK(db->Put(write_options, key, value)); - } - - for (int i = 0; i < FLAGS_total_keys - 1; ++i) { - std::string key = "k" + std::to_string(i); - ASSERT_OK(db->Delete(write_options, key)); - } - - HistogramImpl hist_get; - HistogramImpl hist_get_time; - for (int i = 0; i < FLAGS_total_keys - 1; ++i) { - std::string key = "k" + std::to_string(i); - std::string value; - - get_perf_context()->Reset(); - StopWatchNano timer(SystemClock::Default().get()); - timer.Start(); - auto status = db->Get(read_options, key, &value); - auto elapsed_nanos = timer.ElapsedNanos(); - ASSERT_TRUE(status.IsNotFound()); - hist_get.Add(get_perf_context()->user_key_comparison_count); - hist_get_time.Add(elapsed_nanos); - } - - if (FLAGS_verbose) { - std::cout << "Get user key comparison: \n" - << hist_get.ToString() << "Get time: \n" - << hist_get_time.ToString(); - } - - { - HistogramImpl hist_seek_to_first; - std::unique_ptr iter(db->NewIterator(read_options)); - - get_perf_context()->Reset(); - StopWatchNano timer(SystemClock::Default().get(), true); - iter->SeekToFirst(); - hist_seek_to_first.Add(get_perf_context()->user_key_comparison_count); - auto elapsed_nanos = timer.ElapsedNanos(); - - if (FLAGS_verbose) { - std::cout << "SeekToFirst user key comparison: \n" - << hist_seek_to_first.ToString() << "ikey skipped: " - << get_perf_context()->internal_key_skipped_count << "\n" - << "idelete skipped: " - << get_perf_context()->internal_delete_skipped_count << "\n" - << "elapsed: " << elapsed_nanos << "\n"; - } - } - - HistogramImpl hist_seek; - for (int i = 0; i < FLAGS_total_keys; ++i) { - std::unique_ptr iter(db->NewIterator(read_options)); - std::string key = "k" + std::to_string(i); - - get_perf_context()->Reset(); - StopWatchNano timer(SystemClock::Default().get(), true); - iter->Seek(key); - auto elapsed_nanos = timer.ElapsedNanos(); - hist_seek.Add(get_perf_context()->user_key_comparison_count); - if (FLAGS_verbose) { - std::cout << "seek cmp: " << get_perf_context()->user_key_comparison_count - << " ikey skipped " - << get_perf_context()->internal_key_skipped_count - << " idelete skipped " - << get_perf_context()->internal_delete_skipped_count - << " elapsed: " << elapsed_nanos << "ns\n"; - } - - get_perf_context()->Reset(); - ASSERT_TRUE(iter->Valid()); - StopWatchNano timer2(SystemClock::Default().get(), true); - iter->Next(); - auto elapsed_nanos2 = timer2.ElapsedNanos(); - if (FLAGS_verbose) { - std::cout << "next cmp: " << get_perf_context()->user_key_comparison_count - << "elapsed: " << elapsed_nanos2 << "ns\n"; - } - } - - if (FLAGS_verbose) { - std::cout << "Seek user key comparison: \n" << hist_seek.ToString(); - } -} - -TEST_F(PerfContextTest, StopWatchNanoOverhead) { - // profile the timer cost by itself! - const int kTotalIterations = 1000000; - std::vector timings(kTotalIterations); - - StopWatchNano timer(SystemClock::Default().get(), true); - for (auto& timing : timings) { - timing = timer.ElapsedNanos(true /* reset */); - } - - HistogramImpl histogram; - for (const auto timing : timings) { - histogram.Add(timing); - } - - if (FLAGS_verbose) { - std::cout << histogram.ToString(); - } -} - -TEST_F(PerfContextTest, StopWatchOverhead) { - // profile the timer cost by itself! - const int kTotalIterations = 1000000; - uint64_t elapsed = 0; - std::vector timings(kTotalIterations); - - StopWatch timer(SystemClock::Default().get(), nullptr, 0, &elapsed); - for (auto& timing : timings) { - timing = elapsed; - } - - HistogramImpl histogram; - uint64_t prev_timing = 0; - for (const auto timing : timings) { - histogram.Add(timing - prev_timing); - prev_timing = timing; - } - - if (FLAGS_verbose) { - std::cout << histogram.ToString(); - } -} - -void ProfileQueries(bool enabled_time = false) { - ASSERT_OK(DestroyDB(kDbName, Options())); // Start this test with a fresh DB - - auto db = OpenDb(); - - WriteOptions write_options; - ReadOptions read_options; - - HistogramImpl hist_put; - - HistogramImpl hist_get; - HistogramImpl hist_get_snapshot; - HistogramImpl hist_get_memtable; - HistogramImpl hist_get_files; - HistogramImpl hist_get_post_process; - HistogramImpl hist_num_memtable_checked; - - HistogramImpl hist_mget; - HistogramImpl hist_mget_snapshot; - HistogramImpl hist_mget_memtable; - HistogramImpl hist_mget_files; - HistogramImpl hist_mget_post_process; - HistogramImpl hist_mget_num_memtable_checked; - - HistogramImpl hist_write_pre_post; - HistogramImpl hist_write_wal_time; - HistogramImpl hist_write_memtable_time; - HistogramImpl hist_write_delay_time; - HistogramImpl hist_write_thread_wait_nanos; - HistogramImpl hist_write_scheduling_time; - - uint64_t total_db_mutex_nanos = 0; - - if (FLAGS_verbose) { - std::cout << "Inserting " << FLAGS_total_keys << " key/value pairs\n...\n"; - } - - std::vector keys; - const int kFlushFlag = -1; - for (int i = 0; i < FLAGS_total_keys; ++i) { - keys.push_back(i); - if (i == FLAGS_total_keys / 2) { - // Issuing a flush in the middle. - keys.push_back(kFlushFlag); - } - } - - if (FLAGS_random_key) { - RandomShuffle(std::begin(keys), std::end(keys)); - } -#ifndef NDEBUG - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, 1U); -#endif - int num_mutex_waited = 0; - for (const int i : keys) { - if (i == kFlushFlag) { - FlushOptions fo; - db->Flush(fo); - continue; - } - - std::string key = "k" + std::to_string(i); - std::string value = "v" + std::to_string(i); - - std::vector values; - - get_perf_context()->Reset(); - ASSERT_OK(db->Put(write_options, key, value)); - if (++num_mutex_waited > 3) { -#ifndef NDEBUG - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, 0U); -#endif - } - hist_write_pre_post.Add( - get_perf_context()->write_pre_and_post_process_time); - hist_write_wal_time.Add(get_perf_context()->write_wal_time); - hist_write_memtable_time.Add(get_perf_context()->write_memtable_time); - hist_write_delay_time.Add(get_perf_context()->write_delay_time); - hist_write_thread_wait_nanos.Add( - get_perf_context()->write_thread_wait_nanos); - hist_write_scheduling_time.Add( - get_perf_context()->write_scheduling_flushes_compactions_time); - hist_put.Add(get_perf_context()->user_key_comparison_count); - total_db_mutex_nanos += get_perf_context()->db_mutex_lock_nanos; - } -#ifndef NDEBUG - ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, 0U); -#endif - - for (const int i : keys) { - if (i == kFlushFlag) { - continue; - } - std::string key = "k" + std::to_string(i); - std::string expected_value = "v" + std::to_string(i); - std::string value; - - std::vector multiget_keys = {Slice(key)}; - std::vector values; - - get_perf_context()->Reset(); - ASSERT_OK(db->Get(read_options, key, &value)); - ASSERT_EQ(expected_value, value); - hist_get_snapshot.Add(get_perf_context()->get_snapshot_time); - hist_get_memtable.Add(get_perf_context()->get_from_memtable_time); - hist_get_files.Add(get_perf_context()->get_from_output_files_time); - hist_num_memtable_checked.Add(get_perf_context()->get_from_memtable_count); - hist_get_post_process.Add(get_perf_context()->get_post_process_time); - hist_get.Add(get_perf_context()->user_key_comparison_count); - - get_perf_context()->Reset(); - auto statuses = db->MultiGet(read_options, multiget_keys, &values); - for (const auto& s : statuses) { - ASSERT_OK(s); - } - hist_mget_snapshot.Add(get_perf_context()->get_snapshot_time); - hist_mget_memtable.Add(get_perf_context()->get_from_memtable_time); - hist_mget_files.Add(get_perf_context()->get_from_output_files_time); - hist_mget_num_memtable_checked.Add( - get_perf_context()->get_from_memtable_count); - hist_mget_post_process.Add(get_perf_context()->get_post_process_time); - hist_mget.Add(get_perf_context()->user_key_comparison_count); - } - - if (FLAGS_verbose) { - std::cout << "Put user key comparison: \n" - << hist_put.ToString() << "Get user key comparison: \n" - << hist_get.ToString() << "MultiGet user key comparison: \n" - << hist_get.ToString(); - std::cout << "Put(): Pre and Post Process Time: \n" - << hist_write_pre_post.ToString() << " Writing WAL time: \n" - << hist_write_wal_time.ToString() << "\n" - << " Writing Mem Table time: \n" - << hist_write_memtable_time.ToString() << "\n" - << " Write Delay: \n" - << hist_write_delay_time.ToString() << "\n" - << " Waiting for Batch time: \n" - << hist_write_thread_wait_nanos.ToString() << "\n" - << " Scheduling Flushes and Compactions Time: \n" - << hist_write_scheduling_time.ToString() << "\n" - << " Total DB mutex nanos: \n" - << total_db_mutex_nanos << "\n"; - - std::cout << "Get(): Time to get snapshot: \n" - << hist_get_snapshot.ToString() - << " Time to get value from memtables: \n" - << hist_get_memtable.ToString() << "\n" - << " Time to get value from output files: \n" - << hist_get_files.ToString() << "\n" - << " Number of memtables checked: \n" - << hist_num_memtable_checked.ToString() << "\n" - << " Time to post process: \n" - << hist_get_post_process.ToString() << "\n"; - - std::cout << "MultiGet(): Time to get snapshot: \n" - << hist_mget_snapshot.ToString() - << " Time to get value from memtables: \n" - << hist_mget_memtable.ToString() << "\n" - << " Time to get value from output files: \n" - << hist_mget_files.ToString() << "\n" - << " Number of memtables checked: \n" - << hist_mget_num_memtable_checked.ToString() << "\n" - << " Time to post process: \n" - << hist_mget_post_process.ToString() << "\n"; - } - - if (enabled_time) { - ASSERT_GT(hist_get.Average(), 0); - ASSERT_GT(hist_get_snapshot.Average(), 0); - ASSERT_GT(hist_get_memtable.Average(), 0); - ASSERT_GT(hist_get_files.Average(), 0); - ASSERT_GT(hist_get_post_process.Average(), 0); - ASSERT_GT(hist_num_memtable_checked.Average(), 0); - - ASSERT_GT(hist_mget.Average(), 0); - ASSERT_GT(hist_mget_snapshot.Average(), 0); - ASSERT_GT(hist_mget_memtable.Average(), 0); - ASSERT_GT(hist_mget_files.Average(), 0); - ASSERT_GT(hist_mget_post_process.Average(), 0); - ASSERT_GT(hist_mget_num_memtable_checked.Average(), 0); - - EXPECT_GT(hist_write_pre_post.Average(), 0); - EXPECT_GT(hist_write_wal_time.Average(), 0); - EXPECT_GT(hist_write_memtable_time.Average(), 0); - EXPECT_EQ(hist_write_delay_time.Average(), 0); - EXPECT_EQ(hist_write_thread_wait_nanos.Average(), 0); - EXPECT_GT(hist_write_scheduling_time.Average(), 0); - -#ifndef NDEBUG - ASSERT_LT(total_db_mutex_nanos, 100U); -#endif - } - - db.reset(); - db = OpenDb(true); - - hist_get.Clear(); - hist_get_snapshot.Clear(); - hist_get_memtable.Clear(); - hist_get_files.Clear(); - hist_get_post_process.Clear(); - hist_num_memtable_checked.Clear(); - - hist_mget.Clear(); - hist_mget_snapshot.Clear(); - hist_mget_memtable.Clear(); - hist_mget_files.Clear(); - hist_mget_post_process.Clear(); - hist_mget_num_memtable_checked.Clear(); - - for (const int i : keys) { - if (i == kFlushFlag) { - continue; - } - std::string key = "k" + std::to_string(i); - std::string expected_value = "v" + std::to_string(i); - std::string value; - - std::vector multiget_keys = {Slice(key)}; - std::vector values; - - get_perf_context()->Reset(); - ASSERT_OK(db->Get(read_options, key, &value)); - ASSERT_EQ(expected_value, value); - hist_get_snapshot.Add(get_perf_context()->get_snapshot_time); - hist_get_memtable.Add(get_perf_context()->get_from_memtable_time); - hist_get_files.Add(get_perf_context()->get_from_output_files_time); - hist_num_memtable_checked.Add(get_perf_context()->get_from_memtable_count); - hist_get_post_process.Add(get_perf_context()->get_post_process_time); - hist_get.Add(get_perf_context()->user_key_comparison_count); - - get_perf_context()->Reset(); - auto statuses = db->MultiGet(read_options, multiget_keys, &values); - for (const auto& s : statuses) { - ASSERT_OK(s); - } - hist_mget_snapshot.Add(get_perf_context()->get_snapshot_time); - hist_mget_memtable.Add(get_perf_context()->get_from_memtable_time); - hist_mget_files.Add(get_perf_context()->get_from_output_files_time); - hist_mget_num_memtable_checked.Add( - get_perf_context()->get_from_memtable_count); - hist_mget_post_process.Add(get_perf_context()->get_post_process_time); - hist_mget.Add(get_perf_context()->user_key_comparison_count); - } - - if (FLAGS_verbose) { - std::cout << "ReadOnly Get user key comparison: \n" - << hist_get.ToString() - << "ReadOnly MultiGet user key comparison: \n" - << hist_mget.ToString(); - - std::cout << "ReadOnly Get(): Time to get snapshot: \n" - << hist_get_snapshot.ToString() - << " Time to get value from memtables: \n" - << hist_get_memtable.ToString() << "\n" - << " Time to get value from output files: \n" - << hist_get_files.ToString() << "\n" - << " Number of memtables checked: \n" - << hist_num_memtable_checked.ToString() << "\n" - << " Time to post process: \n" - << hist_get_post_process.ToString() << "\n"; - - std::cout << "ReadOnly MultiGet(): Time to get snapshot: \n" - << hist_mget_snapshot.ToString() - << " Time to get value from memtables: \n" - << hist_mget_memtable.ToString() << "\n" - << " Time to get value from output files: \n" - << hist_mget_files.ToString() << "\n" - << " Number of memtables checked: \n" - << hist_mget_num_memtable_checked.ToString() << "\n" - << " Time to post process: \n" - << hist_mget_post_process.ToString() << "\n"; - } - - if (enabled_time) { - ASSERT_GT(hist_get.Average(), 0); - ASSERT_GT(hist_get_memtable.Average(), 0); - ASSERT_GT(hist_get_files.Average(), 0); - ASSERT_GT(hist_num_memtable_checked.Average(), 0); - // In read-only mode Get(), no super version operation is needed - ASSERT_EQ(hist_get_post_process.Average(), 0); - ASSERT_GT(hist_get_snapshot.Average(), 0); - - ASSERT_GT(hist_mget.Average(), 0); - ASSERT_GT(hist_mget_snapshot.Average(), 0); - ASSERT_GT(hist_mget_memtable.Average(), 0); - ASSERT_GT(hist_mget_files.Average(), 0); - ASSERT_GT(hist_mget_post_process.Average(), 0); - ASSERT_GT(hist_mget_num_memtable_checked.Average(), 0); - } -} - -TEST_F(PerfContextTest, KeyComparisonCount) { - SetPerfLevel(kEnableCount); - ProfileQueries(); - - SetPerfLevel(kDisable); - ProfileQueries(); - - SetPerfLevel(kEnableTime); - ProfileQueries(true); -} - -// make perf_context_test -// export ROCKSDB_TESTS=PerfContextTest.SeekKeyComparison -// For one memtable: -// ./perf_context_test --write_buffer_size=500000 --total_keys=10000 -// For two memtables: -// ./perf_context_test --write_buffer_size=250000 --total_keys=10000 -// Specify --random_key=1 to shuffle the key before insertion -// Results show that, for sequential insertion, worst-case Seek Key comparison -// is close to the total number of keys (linear), when there is only one -// memtable. When there are two memtables, even the avg Seek Key comparison -// starts to become linear to the input size. - -TEST_F(PerfContextTest, SeekKeyComparison) { - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - - if (FLAGS_verbose) { - std::cout << "Inserting " << FLAGS_total_keys << " key/value pairs\n...\n"; - } - - std::vector keys; - for (int i = 0; i < FLAGS_total_keys; ++i) { - keys.push_back(i); - } - - if (FLAGS_random_key) { - RandomShuffle(std::begin(keys), std::end(keys)); - } - - HistogramImpl hist_put_time; - HistogramImpl hist_wal_time; - HistogramImpl hist_time_diff; - - SetPerfLevel(kEnableTime); - StopWatchNano timer(SystemClock::Default().get()); - for (const int i : keys) { - std::string key = "k" + std::to_string(i); - std::string value = "v" + std::to_string(i); - - get_perf_context()->Reset(); - timer.Start(); - ASSERT_OK(db->Put(write_options, key, value)); - auto put_time = timer.ElapsedNanos(); - hist_put_time.Add(put_time); - hist_wal_time.Add(get_perf_context()->write_wal_time); - hist_time_diff.Add(put_time - get_perf_context()->write_wal_time); - } - - if (FLAGS_verbose) { - std::cout << "Put time:\n" - << hist_put_time.ToString() << "WAL time:\n" - << hist_wal_time.ToString() << "time diff:\n" - << hist_time_diff.ToString(); - } - - HistogramImpl hist_seek; - HistogramImpl hist_next; - - for (int i = 0; i < FLAGS_total_keys; ++i) { - std::string key = "k" + std::to_string(i); - std::string value = "v" + std::to_string(i); - - std::unique_ptr iter(db->NewIterator(read_options)); - get_perf_context()->Reset(); - iter->Seek(key); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->value().ToString(), value); - hist_seek.Add(get_perf_context()->user_key_comparison_count); - } - - std::unique_ptr iter(db->NewIterator(read_options)); - for (iter->SeekToFirst(); iter->Valid();) { - get_perf_context()->Reset(); - iter->Next(); - hist_next.Add(get_perf_context()->user_key_comparison_count); - } - ASSERT_OK(iter->status()); - if (FLAGS_verbose) { - std::cout << "Seek:\n" - << hist_seek.ToString() << "Next:\n" - << hist_next.ToString(); - } -} - -TEST_F(PerfContextTest, DBMutexLockCounter) { - int stats_code[] = {0, static_cast(DB_MUTEX_WAIT_MICROS)}; - for (PerfLevel perf_level_test : - {PerfLevel::kEnableTimeExceptForMutex, PerfLevel::kEnableTime}) { - for (int c = 0; c < 2; ++c) { - InstrumentedMutex mutex(nullptr, SystemClock::Default().get(), - stats_code[c]); - mutex.Lock(); - ROCKSDB_NAMESPACE::port::Thread child_thread([&] { - SetPerfLevel(perf_level_test); - get_perf_context()->Reset(); - ASSERT_EQ(get_perf_context()->db_mutex_lock_nanos, 0); - mutex.Lock(); - mutex.Unlock(); - if (perf_level_test == PerfLevel::kEnableTimeExceptForMutex || - stats_code[c] != DB_MUTEX_WAIT_MICROS) { - ASSERT_EQ(get_perf_context()->db_mutex_lock_nanos, 0); - } else { - // increment the counter only when it's a DB Mutex - ASSERT_GT(get_perf_context()->db_mutex_lock_nanos, 0); - } - }); - SystemClock::Default()->SleepForMicroseconds(100); - mutex.Unlock(); - child_thread.join(); - } - } -} - -TEST_F(PerfContextTest, FalseDBMutexWait) { - SetPerfLevel(kEnableTime); - int stats_code[] = {0, static_cast(DB_MUTEX_WAIT_MICROS)}; - for (int c = 0; c < 2; ++c) { - InstrumentedMutex mutex(nullptr, SystemClock::Default().get(), - stats_code[c]); - InstrumentedCondVar lock(&mutex); - get_perf_context()->Reset(); - mutex.Lock(); - lock.TimedWait(100); - mutex.Unlock(); - if (stats_code[c] == static_cast(DB_MUTEX_WAIT_MICROS)) { - // increment the counter only when it's a DB Mutex - ASSERT_GT(get_perf_context()->db_condition_wait_nanos, 0); - } else { - ASSERT_EQ(get_perf_context()->db_condition_wait_nanos, 0); - } - } -} - -TEST_F(PerfContextTest, ToString) { - get_perf_context()->Reset(); - get_perf_context()->block_read_count = 12345; - - std::string zero_included = get_perf_context()->ToString(); - ASSERT_NE(std::string::npos, zero_included.find("= 0")); - ASSERT_NE(std::string::npos, zero_included.find("= 12345")); - - std::string zero_excluded = get_perf_context()->ToString(true); - ASSERT_EQ(std::string::npos, zero_excluded.find("= 0")); - ASSERT_NE(std::string::npos, zero_excluded.find("= 12345")); -} - -TEST_F(PerfContextTest, MergeOperatorTime) { - ASSERT_OK(DestroyDB(kDbName, Options())); - DB* db; - Options options; - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - Status s = DB::Open(options, kDbName, &db); - EXPECT_OK(s); - - std::string val; - ASSERT_OK(db->Merge(WriteOptions(), "k1", "val1")); - ASSERT_OK(db->Merge(WriteOptions(), "k1", "val2")); - ASSERT_OK(db->Merge(WriteOptions(), "k1", "val3")); - ASSERT_OK(db->Merge(WriteOptions(), "k1", "val4")); - - SetPerfLevel(kEnableTime); - get_perf_context()->Reset(); - ASSERT_OK(db->Get(ReadOptions(), "k1", &val)); -#ifdef OS_SOLARIS - for (int i = 0; i < 100; i++) { - ASSERT_OK(db->Get(ReadOptions(), "k1", &val)); - } -#endif - EXPECT_GT(get_perf_context()->merge_operator_time_nanos, 0); - - ASSERT_OK(db->Flush(FlushOptions())); - - get_perf_context()->Reset(); - ASSERT_OK(db->Get(ReadOptions(), "k1", &val)); -#ifdef OS_SOLARIS - for (int i = 0; i < 100; i++) { - ASSERT_OK(db->Get(ReadOptions(), "k1", &val)); - } -#endif - EXPECT_GT(get_perf_context()->merge_operator_time_nanos, 0); - - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - get_perf_context()->Reset(); - ASSERT_OK(db->Get(ReadOptions(), "k1", &val)); -#ifdef OS_SOLARIS - for (int i = 0; i < 100; i++) { - ASSERT_OK(db->Get(ReadOptions(), "k1", &val)); - } -#endif - EXPECT_GT(get_perf_context()->merge_operator_time_nanos, 0); - - delete db; -} - -TEST_F(PerfContextTest, CopyAndMove) { - // Assignment operator - { - get_perf_context()->Reset(); - get_perf_context()->EnablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); - ASSERT_EQ( - 1, - (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); - PerfContext perf_context_assign; - perf_context_assign = *get_perf_context(); - ASSERT_EQ( - 1, - (*(perf_context_assign.level_to_perf_context))[5].bloom_filter_useful); - get_perf_context()->ClearPerLevelPerfContext(); - get_perf_context()->Reset(); - ASSERT_EQ( - 1, - (*(perf_context_assign.level_to_perf_context))[5].bloom_filter_useful); - perf_context_assign.ClearPerLevelPerfContext(); - perf_context_assign.Reset(); - } - // Copy constructor - { - get_perf_context()->Reset(); - get_perf_context()->EnablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); - ASSERT_EQ( - 1, - (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); - PerfContext perf_context_copy(*get_perf_context()); - ASSERT_EQ( - 1, (*(perf_context_copy.level_to_perf_context))[5].bloom_filter_useful); - get_perf_context()->ClearPerLevelPerfContext(); - get_perf_context()->Reset(); - ASSERT_EQ( - 1, (*(perf_context_copy.level_to_perf_context))[5].bloom_filter_useful); - perf_context_copy.ClearPerLevelPerfContext(); - perf_context_copy.Reset(); - } - // Move constructor - { - get_perf_context()->Reset(); - get_perf_context()->EnablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); - ASSERT_EQ( - 1, - (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); - PerfContext perf_context_move = std::move(*get_perf_context()); - ASSERT_EQ( - 1, (*(perf_context_move.level_to_perf_context))[5].bloom_filter_useful); - get_perf_context()->ClearPerLevelPerfContext(); - get_perf_context()->Reset(); - ASSERT_EQ( - 1, (*(perf_context_move.level_to_perf_context))[5].bloom_filter_useful); - perf_context_move.ClearPerLevelPerfContext(); - perf_context_move.Reset(); - } -} - -TEST_F(PerfContextTest, PerfContextDisableEnable) { - get_perf_context()->Reset(); - get_perf_context()->EnablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_positive, 1, 0); - get_perf_context()->DisablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); - get_perf_context()->EnablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1, 0); - get_perf_context()->DisablePerLevelPerfContext(); - PerfContext perf_context_copy(*get_perf_context()); - ASSERT_EQ(1, (*(perf_context_copy.level_to_perf_context))[0] - .bloom_filter_full_positive); - // this was set when per level perf context is disabled, should not be copied - ASSERT_NE( - 1, (*(perf_context_copy.level_to_perf_context))[5].bloom_filter_useful); - ASSERT_EQ( - 1, (*(perf_context_copy.level_to_perf_context))[0].block_cache_hit_count); - perf_context_copy.ClearPerLevelPerfContext(); - perf_context_copy.Reset(); - get_perf_context()->ClearPerLevelPerfContext(); - get_perf_context()->Reset(); -} - -TEST_F(PerfContextTest, PerfContextByLevelGetSet) { - get_perf_context()->Reset(); - get_perf_context()->EnablePerLevelPerfContext(); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_positive, 1, 0); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 7); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 7); - PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_true_positive, 1, 2); - PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1, 0); - PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 5, 2); - PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 2, 3); - PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 4, 1); - ASSERT_EQ( - 0, (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); - ASSERT_EQ( - 1, (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); - ASSERT_EQ( - 2, (*(get_perf_context()->level_to_perf_context))[7].bloom_filter_useful); - ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[0] - .bloom_filter_full_positive); - ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[2] - .bloom_filter_full_true_positive); - ASSERT_EQ( - 1, - (*(get_perf_context()->level_to_perf_context))[0].block_cache_hit_count); - ASSERT_EQ( - 5, - (*(get_perf_context()->level_to_perf_context))[2].block_cache_hit_count); - ASSERT_EQ( - 2, - (*(get_perf_context()->level_to_perf_context))[3].block_cache_miss_count); - ASSERT_EQ( - 4, - (*(get_perf_context()->level_to_perf_context))[1].block_cache_miss_count); - std::string zero_excluded = get_perf_context()->ToString(true); - ASSERT_NE(std::string::npos, - zero_excluded.find("bloom_filter_useful = 1@level5, 2@level7")); - ASSERT_NE(std::string::npos, - zero_excluded.find("bloom_filter_full_positive = 1@level0")); - ASSERT_NE(std::string::npos, - zero_excluded.find("bloom_filter_full_true_positive = 1@level2")); - ASSERT_NE(std::string::npos, - zero_excluded.find("block_cache_hit_count = 1@level0, 5@level2")); - ASSERT_NE(std::string::npos, - zero_excluded.find("block_cache_miss_count = 4@level1, 2@level3")); -} - -TEST_F(PerfContextTest, CPUTimer) { - if (SystemClock::Default()->CPUNanos() == 0) { - ROCKSDB_GTEST_SKIP("Target without CPUNanos support"); - return; - } - - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); - - std::string max_str = "0"; - for (int i = 0; i < FLAGS_total_keys; ++i) { - std::string i_str = std::to_string(i); - std::string key = "k" + i_str; - std::string value = "v" + i_str; - max_str = max_str > i_str ? max_str : i_str; - - ASSERT_OK(db->Put(write_options, key, value)); - } - std::string last_key = "k" + max_str; - std::string last_value = "v" + max_str; - - { - // Get - get_perf_context()->Reset(); - std::string value; - ASSERT_OK(db->Get(read_options, "k0", &value)); - ASSERT_EQ(value, "v0"); - - if (FLAGS_verbose) { - std::cout << "Get CPU time nanos: " << get_perf_context()->get_cpu_nanos - << "ns\n"; - } - - // Iter - std::unique_ptr iter(db->NewIterator(read_options)); - - // Seek - get_perf_context()->Reset(); - iter->Seek(last_key); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(last_value, iter->value().ToString()); - - if (FLAGS_verbose) { - std::cout << "Iter Seek CPU time nanos: " - << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; - } - - // SeekForPrev - get_perf_context()->Reset(); - iter->SeekForPrev(last_key); - ASSERT_TRUE(iter->Valid()); - - if (FLAGS_verbose) { - std::cout << "Iter SeekForPrev CPU time nanos: " - << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; - } - - // SeekToLast - get_perf_context()->Reset(); - iter->SeekToLast(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(last_value, iter->value().ToString()); - - if (FLAGS_verbose) { - std::cout << "Iter SeekToLast CPU time nanos: " - << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; - } - - // SeekToFirst - get_perf_context()->Reset(); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("v0", iter->value().ToString()); - - if (FLAGS_verbose) { - std::cout << "Iter SeekToFirst CPU time nanos: " - << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; - } - - // Next - get_perf_context()->Reset(); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("v1", iter->value().ToString()); - - if (FLAGS_verbose) { - std::cout << "Iter Next CPU time nanos: " - << get_perf_context()->iter_next_cpu_nanos << "ns\n"; - } - - // Prev - get_perf_context()->Reset(); - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("v0", iter->value().ToString()); - - if (FLAGS_verbose) { - std::cout << "Iter Prev CPU time nanos: " - << get_perf_context()->iter_prev_cpu_nanos << "ns\n"; - } - - // monotonically increasing - get_perf_context()->Reset(); - auto count = get_perf_context()->iter_seek_cpu_nanos; - for (int i = 0; i < FLAGS_total_keys; ++i) { - iter->Seek("k" + std::to_string(i)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("v" + std::to_string(i), iter->value().ToString()); - auto next_count = get_perf_context()->iter_seek_cpu_nanos; - ASSERT_GT(next_count, count); - count = next_count; - } - - // iterator creation/destruction; multiple iterators - { - std::unique_ptr iter2(db->NewIterator(read_options)); - ASSERT_EQ(count, get_perf_context()->iter_seek_cpu_nanos); - iter2->Seek(last_key); - ASSERT_TRUE(iter2->Valid()); - ASSERT_EQ(last_value, iter2->value().ToString()); - ASSERT_GT(get_perf_context()->iter_seek_cpu_nanos, count); - count = get_perf_context()->iter_seek_cpu_nanos; - } - ASSERT_EQ(count, get_perf_context()->iter_seek_cpu_nanos); - } -} - -TEST_F(PerfContextTest, MergeOperandCount) { - ASSERT_OK(DestroyDB(kDbName, Options())); - - DB* db = nullptr; - Options options; - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - - ASSERT_OK(DB::Open(options, kDbName, &db)); - std::unique_ptr db_guard(db); - - constexpr size_t num_keys = 3; - const std::string key_prefix("key"); - const std::string value_prefix("value"); - - std::vector keys; - keys.reserve(num_keys); - - for (size_t i = 0; i < num_keys; ++i) { - keys.emplace_back(key_prefix + std::to_string(i)); - } - - // Write three keys with one Put each followed by 1, 2, and 3 - // Merge operations respectively. - constexpr size_t total_merges = num_keys * (num_keys + 1) / 2; - - std::vector snapshots; - snapshots.reserve(total_merges); - - for (size_t i = 0; i < num_keys; ++i) { - const std::string suffix = std::to_string(i); - const std::string value = value_prefix + suffix; - - ASSERT_OK(db->Put(WriteOptions(), keys[i], value)); - - for (size_t j = 0; j <= i; ++j) { - // Take a snapshot before each Merge so they are preserved and not - // collapsed during flush. - snapshots.emplace_back(db); - - ASSERT_OK(db->Merge(WriteOptions(), keys[i], value + std::to_string(j))); - } - } - - auto verify = [&]() { - get_perf_context()->Reset(); - - for (size_t i = 0; i < num_keys; ++i) { - // Get - { - PinnableSlice result; - ASSERT_OK(db->Get(ReadOptions(), db->DefaultColumnFamily(), keys[i], - &result)); - ASSERT_EQ(get_perf_context()->internal_merge_point_lookup_count, i + 1); - - get_perf_context()->Reset(); - } - - // GetEntity - { - PinnableWideColumns result; - ASSERT_OK(db->GetEntity(ReadOptions(), db->DefaultColumnFamily(), - keys[i], &result)); - ASSERT_EQ(get_perf_context()->internal_merge_point_lookup_count, i + 1); - - get_perf_context()->Reset(); - } - } - - { - std::vector key_slices; - key_slices.reserve(num_keys); - - for (size_t i = 0; i < num_keys; ++i) { - key_slices.emplace_back(keys[i]); - } - - // MultiGet - { - std::vector results(num_keys); - std::vector statuses(num_keys); - - db->MultiGet(ReadOptions(), db->DefaultColumnFamily(), num_keys, - &key_slices[0], &results[0], &statuses[0]); - - for (size_t i = 0; i < num_keys; ++i) { - ASSERT_OK(statuses[i]); - } - - ASSERT_EQ(get_perf_context()->internal_merge_point_lookup_count, - total_merges); - - get_perf_context()->Reset(); - } - - // MultiGetEntity - { - std::vector results(num_keys); - std::vector statuses(num_keys); - - db->MultiGetEntity(ReadOptions(), db->DefaultColumnFamily(), num_keys, - &key_slices[0], &results[0], &statuses[0]); - - for (size_t i = 0; i < num_keys; ++i) { - ASSERT_OK(statuses[i]); - } - - ASSERT_EQ(get_perf_context()->internal_merge_point_lookup_count, - total_merges); - - get_perf_context()->Reset(); - } - } - - std::unique_ptr it(db->NewIterator(ReadOptions())); - - // Forward iteration - { - size_t i = 0; - - for (it->SeekToFirst(); it->Valid(); it->Next(), ++i) { - ASSERT_EQ(it->key(), keys[i]); - ASSERT_EQ(get_perf_context()->internal_merge_count, i + 1); - - get_perf_context()->Reset(); - } - } - - // Backward iteration - { - size_t i = num_keys - 1; - - for (it->SeekToLast(); it->Valid(); it->Prev(), --i) { - ASSERT_EQ(it->key(), keys[i]); - ASSERT_EQ(get_perf_context()->internal_merge_count, i + 1); - - get_perf_context()->Reset(); - } - } - }; - - // Verify counters when reading from memtable - verify(); - - // Verify counters when reading from table files - db->Flush(FlushOptions()); - - verify(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - for (int i = 1; i < argc; i++) { - int n; - char junk; - - if (sscanf(argv[i], "--write_buffer_size=%d%c", &n, &junk) == 1) { - FLAGS_write_buffer_size = n; - } - - if (sscanf(argv[i], "--total_keys=%d%c", &n, &junk) == 1) { - FLAGS_total_keys = n; - } - - if (sscanf(argv[i], "--random_key=%d%c", &n, &junk) == 1 && - (n == 0 || n == 1)) { - FLAGS_random_key = n; - } - - if (sscanf(argv[i], "--use_set_based_memetable=%d%c", &n, &junk) == 1 && - (n == 0 || n == 1)) { - FLAGS_use_set_based_memetable = n; - } - - if (sscanf(argv[i], "--verbose=%d%c", &n, &junk) == 1 && - (n == 0 || n == 1)) { - FLAGS_verbose = n; - } - } - - if (FLAGS_verbose) { - std::cout << kDbName << "\n"; - } - - return RUN_ALL_TESTS(); -} diff --git a/db/periodic_task_scheduler_test.cc b/db/periodic_task_scheduler_test.cc deleted file mode 100644 index c1205bcf6..000000000 --- a/db/periodic_task_scheduler_test.cc +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/periodic_task_scheduler.h" - -#include "db/db_test_util.h" -#include "env/composite_env_wrapper.h" -#include "test_util/mock_time_env.h" - -namespace ROCKSDB_NAMESPACE { - -class PeriodicTaskSchedulerTest : public DBTestBase { - public: - PeriodicTaskSchedulerTest() - : DBTestBase("periodic_task_scheduler_test", /*env_do_fsync=*/true) { - mock_clock_ = std::make_shared(env_->GetSystemClock()); - mock_env_.reset(new CompositeEnvWrapper(env_, mock_clock_)); - } - - protected: - std::unique_ptr mock_env_; - std::shared_ptr mock_clock_; - - void SetUp() override { - mock_clock_->InstallTimedWaitFixCallback(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::StartPeriodicTaskScheduler:Init", [&](void* arg) { - auto periodic_task_scheduler_ptr = - reinterpret_cast(arg); - periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock_.get()); - }); - } -}; - -TEST_F(PeriodicTaskSchedulerTest, Basic) { - constexpr unsigned int kPeriodSec = 10; - Close(); - Options options; - options.stats_dump_period_sec = kPeriodSec; - options.stats_persist_period_sec = kPeriodSec; - options.create_if_missing = true; - options.env = mock_env_.get(); - - int dump_st_counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::DumpStats:StartRunning", - [&](void*) { dump_st_counter++; }); - - int pst_st_counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::PersistStats:StartRunning", - [&](void*) { pst_st_counter++; }); - - int flush_info_log_counter = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::FlushInfoLog:StartRunning", - [&](void*) { flush_info_log_counter++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Reopen(options); - - ASSERT_EQ(kPeriodSec, dbfull()->GetDBOptions().stats_dump_period_sec); - ASSERT_EQ(kPeriodSec, dbfull()->GetDBOptions().stats_persist_period_sec); - - ASSERT_GT(kPeriodSec, 1u); - dbfull()->TEST_WaitForPeriodicTaskRun([&] { - mock_clock_->MockSleepForSeconds(static_cast(kPeriodSec) - 1); - }); - - const PeriodicTaskScheduler& scheduler = - dbfull()->TEST_GetPeriodicTaskScheduler(); - ASSERT_EQ(3, scheduler.TEST_GetValidTaskNum()); - - ASSERT_EQ(1, dump_st_counter); - ASSERT_EQ(1, pst_st_counter); - ASSERT_EQ(1, flush_info_log_counter); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(kPeriodSec)); }); - - ASSERT_EQ(2, dump_st_counter); - ASSERT_EQ(2, pst_st_counter); - ASSERT_EQ(2, flush_info_log_counter); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(kPeriodSec)); }); - - ASSERT_EQ(3, dump_st_counter); - ASSERT_EQ(3, pst_st_counter); - ASSERT_EQ(3, flush_info_log_counter); - - // Disable scheduler with SetOption - ASSERT_OK(dbfull()->SetDBOptions( - {{"stats_dump_period_sec", "0"}, {"stats_persist_period_sec", "0"}})); - ASSERT_EQ(0u, dbfull()->GetDBOptions().stats_dump_period_sec); - ASSERT_EQ(0u, dbfull()->GetDBOptions().stats_persist_period_sec); - - // Info log flush should still run. - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(kPeriodSec)); }); - ASSERT_EQ(3, dump_st_counter); - ASSERT_EQ(3, pst_st_counter); - ASSERT_EQ(4, flush_info_log_counter); - - ASSERT_EQ(1u, scheduler.TEST_GetValidTaskNum()); - - // Re-enable one task - ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "5"}})); - ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_dump_period_sec); - ASSERT_EQ(0u, dbfull()->GetDBOptions().stats_persist_period_sec); - - ASSERT_EQ(2, scheduler.TEST_GetValidTaskNum()); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(kPeriodSec)); }); - ASSERT_EQ(4, dump_st_counter); - ASSERT_EQ(3, pst_st_counter); - ASSERT_EQ(5, flush_info_log_counter); - - Close(); -} - -TEST_F(PeriodicTaskSchedulerTest, MultiInstances) { - constexpr int kPeriodSec = 5; - const int kInstanceNum = 10; - - Close(); - Options options; - options.stats_dump_period_sec = kPeriodSec; - options.stats_persist_period_sec = kPeriodSec; - options.create_if_missing = true; - options.env = mock_env_.get(); - - int dump_st_counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::DumpStats:2", - [&](void*) { dump_st_counter++; }); - - int pst_st_counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::PersistStats:StartRunning", - [&](void*) { pst_st_counter++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - auto dbs = std::vector(kInstanceNum); - for (int i = 0; i < kInstanceNum; i++) { - ASSERT_OK( - DB::Open(options, test::PerThreadDBPath(std::to_string(i)), &(dbs[i]))); - } - - auto dbi = static_cast_with_check(dbs[kInstanceNum - 1]); - - const PeriodicTaskScheduler& scheduler = dbi->TEST_GetPeriodicTaskScheduler(); - ASSERT_EQ(kInstanceNum * 3, scheduler.TEST_GetValidTaskNum()); - - int expected_run = kInstanceNum; - dbi->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - ASSERT_EQ(expected_run, dump_st_counter); - ASSERT_EQ(expected_run, pst_st_counter); - - expected_run += kInstanceNum; - dbi->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - ASSERT_EQ(expected_run, dump_st_counter); - ASSERT_EQ(expected_run, pst_st_counter); - - expected_run += kInstanceNum; - dbi->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - ASSERT_EQ(expected_run, dump_st_counter); - ASSERT_EQ(expected_run, pst_st_counter); - - int half = kInstanceNum / 2; - for (int i = 0; i < half; i++) { - delete dbs[i]; - } - - expected_run += (kInstanceNum - half) * 2; - - dbi->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - dbi->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - ASSERT_EQ(expected_run, dump_st_counter); - ASSERT_EQ(expected_run, pst_st_counter); - - for (int i = half; i < kInstanceNum; i++) { - ASSERT_OK(dbs[i]->Close()); - delete dbs[i]; - } -} - -TEST_F(PeriodicTaskSchedulerTest, MultiEnv) { - constexpr int kDumpPeriodSec = 5; - constexpr int kPersistPeriodSec = 10; - Close(); - Options options1; - options1.stats_dump_period_sec = kDumpPeriodSec; - options1.stats_persist_period_sec = kPersistPeriodSec; - options1.create_if_missing = true; - options1.env = mock_env_.get(); - - Reopen(options1); - - std::unique_ptr mock_env2( - new CompositeEnvWrapper(Env::Default(), mock_clock_)); - Options options2; - options2.stats_dump_period_sec = kDumpPeriodSec; - options2.stats_persist_period_sec = kPersistPeriodSec; - options2.create_if_missing = true; - options1.env = mock_env2.get(); - - std::string dbname = test::PerThreadDBPath("multi_env_test"); - DB* db; - ASSERT_OK(DB::Open(options2, dbname, &db)); - - ASSERT_OK(db->Close()); - delete db; - Close(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/db/plain_table_db_test.cc b/db/plain_table_db_test.cc deleted file mode 100644 index 737ad4ed2..000000000 --- a/db/plain_table_db_test.cc +++ /dev/null @@ -1,1347 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - - -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/version_set.h" -#include "db/write_batch_internal.h" -#include "file/filename.h" -#include "rocksdb/cache.h" -#include "rocksdb/compaction_filter.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/table.h" -#include "table/meta_blocks.h" -#include "table/plain/plain_table_bloom.h" -#include "table/plain/plain_table_factory.h" -#include "table/plain/plain_table_key_coding.h" -#include "table/plain/plain_table_reader.h" -#include "table/table_builder.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/cast_util.h" -#include "util/hash.h" -#include "util/mutexlock.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { -class PlainTableKeyDecoderTest : public testing::Test {}; - -TEST_F(PlainTableKeyDecoderTest, ReadNonMmap) { - Random rnd(301); - const uint32_t kLength = 2222; - std::string tmp = rnd.RandomString(kLength); - Slice contents(tmp); - test::StringSource* string_source = - new test::StringSource(contents, 0, false); - std::unique_ptr holder(string_source); - std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(holder), "test")); - std::unique_ptr file_info( - new PlainTableReaderFileInfo(std::move(file_reader), EnvOptions(), - kLength)); - - { - PlainTableFileReader reader(file_info.get()); - - const uint32_t kReadSize = 77; - for (uint32_t pos = 0; pos < kLength; pos += kReadSize) { - uint32_t read_size = std::min(kLength - pos, kReadSize); - Slice out; - ASSERT_TRUE(reader.Read(pos, read_size, &out)); - ASSERT_EQ(0, out.compare(tmp.substr(pos, read_size))); - } - - ASSERT_LT(uint32_t(string_source->total_reads()), kLength / kReadSize / 2); - } - - std::vector>> reads = { - {{600, 30}, {590, 30}, {600, 20}, {600, 40}}, - {{800, 20}, {100, 20}, {500, 20}, {1500, 20}, {100, 20}, {80, 20}}, - {{1000, 20}, {500, 20}, {1000, 50}}, - {{1000, 20}, {500, 20}, {500, 20}}, - {{1000, 20}, {500, 20}, {200, 20}, {500, 20}}, - {{1000, 20}, {500, 20}, {200, 20}, {1000, 50}}, - {{600, 500}, {610, 20}, {100, 20}}, - {{500, 100}, {490, 100}, {550, 50}}, - }; - - std::vector num_file_reads = {2, 6, 2, 2, 4, 3, 2, 2}; - - for (size_t i = 0; i < reads.size(); i++) { - string_source->set_total_reads(0); - PlainTableFileReader reader(file_info.get()); - for (auto p : reads[i]) { - Slice out; - ASSERT_TRUE(reader.Read(p.first, p.second, &out)); - ASSERT_EQ(0, out.compare(tmp.substr(p.first, p.second))); - } - ASSERT_EQ(num_file_reads[i], string_source->total_reads()); - } -} - -class PlainTableDBTest : public testing::Test, - public testing::WithParamInterface { - protected: - private: - std::string dbname_; - Env* env_; - DB* db_; - - bool mmap_mode_; - Options last_options_; - - public: - PlainTableDBTest() : env_(Env::Default()) {} - - ~PlainTableDBTest() override { - delete db_; - EXPECT_OK(DestroyDB(dbname_, Options())); - } - - void SetUp() override { - mmap_mode_ = GetParam(); - dbname_ = test::PerThreadDBPath("plain_table_db_test"); - EXPECT_OK(DestroyDB(dbname_, Options())); - db_ = nullptr; - Reopen(); - } - - // Return the current option configuration. - Options CurrentOptions() { - Options options; - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 0; - plain_table_options.bloom_bits_per_key = 2; - plain_table_options.hash_table_ratio = 0.8; - plain_table_options.index_sparseness = 3; - plain_table_options.huge_page_tlb_size = 0; - plain_table_options.encoding_type = kPrefix; - plain_table_options.full_scan_mode = false; - plain_table_options.store_index_in_file = false; - - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true)); - - options.prefix_extractor.reset(NewFixedPrefixTransform(8)); - options.allow_mmap_reads = mmap_mode_; - options.allow_concurrent_memtable_write = false; - options.unordered_write = false; - return options; - } - - DBImpl* dbfull() { return static_cast_with_check(db_); } - - void Reopen(Options* options = nullptr) { ASSERT_OK(TryReopen(options)); } - - void Close() { - delete db_; - db_ = nullptr; - } - - bool mmap_mode() const { return mmap_mode_; } - - void DestroyAndReopen(Options* options = nullptr) { - // Destroy using last options - Destroy(&last_options_); - ASSERT_OK(TryReopen(options)); - } - - void Destroy(Options* options) { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, *options)); - } - - Status PureReopen(Options* options, DB** db) { - return DB::Open(*options, dbname_, db); - } - - Status ReopenForReadOnly(Options* options) { - delete db_; - db_ = nullptr; - return DB::OpenForReadOnly(*options, dbname_, &db_); - } - - Status TryReopen(Options* options = nullptr) { - delete db_; - db_ = nullptr; - Options opts; - if (options != nullptr) { - opts = *options; - } else { - opts = CurrentOptions(); - opts.create_if_missing = true; - } - last_options_ = opts; - - return DB::Open(opts, dbname_, &db_); - } - - Status Put(const Slice& k, const Slice& v) { - return db_->Put(WriteOptions(), k, v); - } - - Status Delete(const std::string& k) { return db_->Delete(WriteOptions(), k); } - - std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) { - ReadOptions options; - options.snapshot = snapshot; - std::string result; - Status s = db_->Get(options, k, &result); - if (s.IsNotFound()) { - result = "NOT_FOUND"; - } else if (!s.ok()) { - result = s.ToString(); - } - return result; - } - - int NumTableFilesAtLevel(int level) { - std::string property; - EXPECT_TRUE(db_->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(level), &property)); - return atoi(property.c_str()); - } - - // Return spread of files per level - std::string FilesPerLevel() { - std::string result; - size_t last_non_zero_offset = 0; - for (int level = 0; level < db_->NumberLevels(); level++) { - int f = NumTableFilesAtLevel(level); - char buf[100]; - snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f); - result += buf; - if (f > 0) { - last_non_zero_offset = result.size(); - } - } - result.resize(last_non_zero_offset); - return result; - } - - std::string IterStatus(Iterator* iter) { - std::string result; - if (iter->Valid()) { - result = iter->key().ToString() + "->" + iter->value().ToString(); - } else { - result = "(invalid)"; - } - return result; - } -}; - -TEST_P(PlainTableDBTest, Empty) { - ASSERT_TRUE(dbfull() != nullptr); - ASSERT_EQ("NOT_FOUND", Get("0000000000000foo")); -} - -extern const uint64_t kPlainTableMagicNumber; - -class TestPlainTableReader : public PlainTableReader { - public: - TestPlainTableReader( - const EnvOptions& env_options, const InternalKeyComparator& icomparator, - EncodingType encoding_type, uint64_t file_size, int bloom_bits_per_key, - double hash_table_ratio, size_t index_sparseness, - std::unique_ptr&& props, - std::unique_ptr&& file, - const ImmutableOptions& ioptions, const SliceTransform* prefix_extractor, - bool* expect_bloom_not_match, bool store_index_in_file, - uint32_t column_family_id, const std::string& column_family_name) - : PlainTableReader(ioptions, std::move(file), env_options, icomparator, - encoding_type, file_size, props.get(), - prefix_extractor), - expect_bloom_not_match_(expect_bloom_not_match) { - Status s = MmapDataIfNeeded(); - EXPECT_TRUE(s.ok()); - - s = PopulateIndex(props.get(), bloom_bits_per_key, hash_table_ratio, - index_sparseness, 2 * 1024 * 1024); - EXPECT_TRUE(s.ok()); - - EXPECT_EQ(column_family_id, static_cast(props->column_family_id)); - EXPECT_EQ(column_family_name, props->column_family_name); - if (store_index_in_file) { - auto bloom_version_ptr = props->user_collected_properties.find( - PlainTablePropertyNames::kBloomVersion); - EXPECT_TRUE(bloom_version_ptr != props->user_collected_properties.end()); - EXPECT_EQ(bloom_version_ptr->second, std::string("1")); - if (ioptions.bloom_locality > 0) { - auto num_blocks_ptr = props->user_collected_properties.find( - PlainTablePropertyNames::kNumBloomBlocks); - EXPECT_TRUE(num_blocks_ptr != props->user_collected_properties.end()); - } - } - table_properties_ = std::move(props); - } - - ~TestPlainTableReader() override {} - - private: - bool MatchBloom(uint32_t hash) const override { - bool ret = PlainTableReader::MatchBloom(hash); - if (*expect_bloom_not_match_) { - EXPECT_TRUE(!ret); - } else { - EXPECT_TRUE(ret); - } - return ret; - } - bool* expect_bloom_not_match_; -}; - -extern const uint64_t kPlainTableMagicNumber; -class TestPlainTableFactory : public PlainTableFactory { - public: - explicit TestPlainTableFactory(bool* expect_bloom_not_match, - const PlainTableOptions& options, - uint32_t column_family_id, - std::string column_family_name) - : PlainTableFactory(options), - bloom_bits_per_key_(options.bloom_bits_per_key), - hash_table_ratio_(options.hash_table_ratio), - index_sparseness_(options.index_sparseness), - store_index_in_file_(options.store_index_in_file), - expect_bloom_not_match_(expect_bloom_not_match), - column_family_id_(column_family_id), - column_family_name_(std::move(column_family_name)) {} - - using PlainTableFactory::NewTableReader; - Status NewTableReader( - const ReadOptions& /*ro*/, const TableReaderOptions& table_reader_options, - std::unique_ptr&& file, uint64_t file_size, - std::unique_ptr* table, - bool /*prefetch_index_and_filter_in_cache*/) const override { - std::unique_ptr props; - auto s = ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber, - table_reader_options.ioptions, &props); - EXPECT_TRUE(s.ok()); - - if (store_index_in_file_) { - BlockHandle bloom_block_handle; - s = FindMetaBlockInFile(file.get(), file_size, kPlainTableMagicNumber, - table_reader_options.ioptions, - BloomBlockBuilder::kBloomBlock, - &bloom_block_handle); - EXPECT_TRUE(s.ok()); - - BlockHandle index_block_handle; - s = FindMetaBlockInFile(file.get(), file_size, kPlainTableMagicNumber, - table_reader_options.ioptions, - PlainTableIndexBuilder::kPlainTableIndexBlock, - &index_block_handle); - EXPECT_TRUE(s.ok()); - } - - auto& user_props = props->user_collected_properties; - auto encoding_type_prop = - user_props.find(PlainTablePropertyNames::kEncodingType); - assert(encoding_type_prop != user_props.end()); - EncodingType encoding_type = static_cast( - DecodeFixed32(encoding_type_prop->second.c_str())); - - std::unique_ptr new_reader(new TestPlainTableReader( - table_reader_options.env_options, - table_reader_options.internal_comparator, encoding_type, file_size, - bloom_bits_per_key_, hash_table_ratio_, index_sparseness_, - std::move(props), std::move(file), table_reader_options.ioptions, - table_reader_options.prefix_extractor.get(), expect_bloom_not_match_, - store_index_in_file_, column_family_id_, column_family_name_)); - - *table = std::move(new_reader); - return s; - } - - private: - int bloom_bits_per_key_; - double hash_table_ratio_; - size_t index_sparseness_; - bool store_index_in_file_; - bool* expect_bloom_not_match_; - const uint32_t column_family_id_; - const std::string column_family_name_; -}; - -TEST_P(PlainTableDBTest, BadOptions1) { - // Build with a prefix extractor - ASSERT_OK(Put("1000000000000foo", "v1")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - // Bad attempt to re-open without a prefix extractor - Options options = CurrentOptions(); - options.prefix_extractor.reset(); - ASSERT_EQ( - "Invalid argument: Prefix extractor is missing when opening a PlainTable " - "built using a prefix extractor", - TryReopen(&options).ToString()); - - // Bad attempt to re-open with different prefix extractor - options.prefix_extractor.reset(NewFixedPrefixTransform(6)); - ASSERT_EQ( - "Invalid argument: Prefix extractor given doesn't match the one used to " - "build PlainTable", - TryReopen(&options).ToString()); - - // Correct prefix extractor - options.prefix_extractor.reset(NewFixedPrefixTransform(8)); - Reopen(&options); - ASSERT_EQ("v1", Get("1000000000000foo")); -} - -TEST_P(PlainTableDBTest, BadOptions2) { - Options options = CurrentOptions(); - options.prefix_extractor.reset(); - options.create_if_missing = true; - DestroyAndReopen(&options); - // Build without a prefix extractor - // (apparently works even if hash_table_ratio > 0) - ASSERT_OK(Put("1000000000000foo", "v1")); - // Build without a prefix extractor, this call will fail and returns the - // status for this bad attempt. - ASSERT_NOK(dbfull()->TEST_FlushMemTable()); - - // Bad attempt to re-open with hash_table_ratio > 0 and no prefix extractor - Status s = TryReopen(&options); - ASSERT_EQ( - "Not implemented: PlainTable requires a prefix extractor enable prefix " - "hash mode.", - s.ToString()); - - // OK to open with hash_table_ratio == 0 and no prefix extractor - PlainTableOptions plain_table_options; - plain_table_options.hash_table_ratio = 0; - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - Reopen(&options); - ASSERT_EQ("v1", Get("1000000000000foo")); - - // OK to open newly with a prefix_extractor and hash table; builds index - // in memory. - options = CurrentOptions(); - Reopen(&options); - ASSERT_EQ("v1", Get("1000000000000foo")); -} - -TEST_P(PlainTableDBTest, Flush) { - for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024; - huge_page_tlb_size += 2 * 1024 * 1024) { - for (EncodingType encoding_type : {kPlain, kPrefix}) { - for (int bloom = -1; bloom <= 117; bloom += 117) { - const int bloom_bits = std::max(bloom, 0); - const bool full_scan_mode = bloom < 0; - for (int total_order = 0; total_order <= 1; total_order++) { - for (int store_index_in_file = 0; store_index_in_file <= 1; - ++store_index_in_file) { - Options options = CurrentOptions(); - options.create_if_missing = true; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - if (total_order) { - options.prefix_extractor.reset(); - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 0; - plain_table_options.bloom_bits_per_key = bloom_bits; - plain_table_options.hash_table_ratio = 0; - plain_table_options.index_sparseness = 2; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - plain_table_options.encoding_type = encoding_type; - plain_table_options.full_scan_mode = full_scan_mode; - plain_table_options.store_index_in_file = store_index_in_file; - - options.table_factory.reset( - NewPlainTableFactory(plain_table_options)); - } else { - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 0; - plain_table_options.bloom_bits_per_key = bloom_bits; - plain_table_options.hash_table_ratio = 0.75; - plain_table_options.index_sparseness = 16; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - plain_table_options.encoding_type = encoding_type; - plain_table_options.full_scan_mode = full_scan_mode; - plain_table_options.store_index_in_file = store_index_in_file; - - options.table_factory.reset( - NewPlainTableFactory(plain_table_options)); - } - DestroyAndReopen(&options); - uint64_t int_num; - ASSERT_TRUE(dbfull()->GetIntProperty( - "rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_EQ(int_num, 0U); - - ASSERT_OK(Put("1000000000000foo", "v1")); - ASSERT_OK(Put("0000000000000bar", "v2")); - ASSERT_OK(Put("1000000000000foo", "v3")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_TRUE(dbfull()->GetIntProperty( - "rocksdb.estimate-table-readers-mem", &int_num)); - ASSERT_GT(int_num, 0U); - - TablePropertiesCollection ptc; - ASSERT_OK(reinterpret_cast(dbfull())->GetPropertiesOfAllTables( - &ptc)); - ASSERT_EQ(1U, ptc.size()); - auto row = ptc.begin(); - auto tp = row->second; - - if (full_scan_mode) { - // Does not support Get/Seek - std::unique_ptr iter( - dbfull()->NewIterator(ReadOptions())); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("0000000000000bar", iter->key().ToString()); - ASSERT_EQ("v2", iter->value().ToString()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000000foo", iter->key().ToString()); - ASSERT_EQ("v3", iter->value().ToString()); - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_TRUE(iter->status().ok()); - } else { - if (!store_index_in_file) { - ASSERT_EQ(total_order ? "4" : "12", - (tp->user_collected_properties) - .at("plain_table_hash_table_size")); - ASSERT_EQ("0", (tp->user_collected_properties) - .at("plain_table_sub_index_size")); - } else { - ASSERT_EQ("0", (tp->user_collected_properties) - .at("plain_table_hash_table_size")); - ASSERT_EQ("0", (tp->user_collected_properties) - .at("plain_table_sub_index_size")); - } - ASSERT_EQ("v3", Get("1000000000000foo")); - ASSERT_EQ("v2", Get("0000000000000bar")); - } - } - } - } - } - } -} - -TEST_P(PlainTableDBTest, Flush2) { - for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024; - huge_page_tlb_size += 2 * 1024 * 1024) { - for (EncodingType encoding_type : {kPlain, kPrefix}) { - for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) { - for (int total_order = 0; total_order <= 1; total_order++) { - for (int store_index_in_file = 0; store_index_in_file <= 1; - ++store_index_in_file) { - if (encoding_type == kPrefix && total_order) { - continue; - } - if (!bloom_bits && store_index_in_file) { - continue; - } - if (total_order && store_index_in_file) { - continue; - } - bool expect_bloom_not_match = false; - Options options = CurrentOptions(); - options.create_if_missing = true; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - PlainTableOptions plain_table_options; - if (total_order) { - options.prefix_extractor = nullptr; - plain_table_options.hash_table_ratio = 0; - plain_table_options.index_sparseness = 2; - } else { - plain_table_options.hash_table_ratio = 0.75; - plain_table_options.index_sparseness = 16; - } - plain_table_options.user_key_len = kPlainTableVariableLength; - plain_table_options.bloom_bits_per_key = bloom_bits; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - plain_table_options.encoding_type = encoding_type; - plain_table_options.store_index_in_file = store_index_in_file; - options.table_factory.reset(new TestPlainTableFactory( - &expect_bloom_not_match, plain_table_options, - 0 /* column_family_id */, kDefaultColumnFamilyName)); - - DestroyAndReopen(&options); - ASSERT_OK(Put("0000000000000bar", "b")); - ASSERT_OK(Put("1000000000000foo", "v1")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_OK(Put("1000000000000foo", "v2")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v2", Get("1000000000000foo")); - - ASSERT_OK(Put("0000000000000eee", "v3")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v3", Get("0000000000000eee")); - - ASSERT_OK(Delete("0000000000000bar")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("NOT_FOUND", Get("0000000000000bar")); - - ASSERT_OK(Put("0000000000000eee", "v5")); - ASSERT_OK(Put("9000000000000eee", "v5")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v5", Get("0000000000000eee")); - - // Test Bloom Filter - if (bloom_bits > 0) { - // Neither key nor value should exist. - expect_bloom_not_match = true; - ASSERT_EQ("NOT_FOUND", Get("5_not00000000bar")); - // Key doesn't exist any more but prefix exists. - if (total_order) { - ASSERT_EQ("NOT_FOUND", Get("1000000000000not")); - ASSERT_EQ("NOT_FOUND", Get("0000000000000not")); - } - expect_bloom_not_match = false; - } - } - } - } - } - } -} - -TEST_P(PlainTableDBTest, Immortal) { - for (EncodingType encoding_type : {kPlain, kPrefix}) { - Options options = CurrentOptions(); - options.create_if_missing = true; - options.max_open_files = -1; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - PlainTableOptions plain_table_options; - plain_table_options.hash_table_ratio = 0.75; - plain_table_options.index_sparseness = 16; - plain_table_options.user_key_len = kPlainTableVariableLength; - plain_table_options.bloom_bits_per_key = 10; - plain_table_options.encoding_type = encoding_type; - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - - DestroyAndReopen(&options); - ASSERT_OK(Put("0000000000000bar", "b")); - ASSERT_OK(Put("1000000000000foo", "v1")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - int copied = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "GetContext::SaveValue::PinSelf", [&](void* /*arg*/) { copied++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_EQ("b", Get("0000000000000bar")); - ASSERT_EQ("v1", Get("1000000000000foo")); - ASSERT_EQ(2, copied); - copied = 0; - - Close(); - ASSERT_OK(ReopenForReadOnly(&options)); - - ASSERT_EQ("b", Get("0000000000000bar")); - ASSERT_EQ("v1", Get("1000000000000foo")); - ASSERT_EQ("NOT_FOUND", Get("1000000000000bar")); - if (mmap_mode()) { - ASSERT_EQ(0, copied); - } else { - ASSERT_EQ(2, copied); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_P(PlainTableDBTest, Iterator) { - for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024; - huge_page_tlb_size += 2 * 1024 * 1024) { - for (EncodingType encoding_type : {kPlain, kPrefix}) { - for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) { - for (int total_order = 0; total_order <= 1; total_order++) { - if (encoding_type == kPrefix && total_order == 1) { - continue; - } - bool expect_bloom_not_match = false; - Options options = CurrentOptions(); - options.create_if_missing = true; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - if (total_order) { - options.prefix_extractor = nullptr; - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = bloom_bits; - plain_table_options.hash_table_ratio = 0; - plain_table_options.index_sparseness = 2; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - plain_table_options.encoding_type = encoding_type; - - options.table_factory.reset(new TestPlainTableFactory( - &expect_bloom_not_match, plain_table_options, - 0 /* column_family_id */, kDefaultColumnFamilyName)); - } else { - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = bloom_bits; - plain_table_options.hash_table_ratio = 0.75; - plain_table_options.index_sparseness = 16; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - plain_table_options.encoding_type = encoding_type; - - options.table_factory.reset(new TestPlainTableFactory( - &expect_bloom_not_match, plain_table_options, - 0 /* column_family_id */, kDefaultColumnFamilyName)); - } - DestroyAndReopen(&options); - - ASSERT_OK(Put("1000000000foo002", "v_2")); - ASSERT_OK(Put("0000000000000bar", "random")); - ASSERT_OK(Put("1000000000foo001", "v1")); - ASSERT_OK(Put("3000000000000bar", "bar_v")); - ASSERT_OK(Put("1000000000foo003", "v__3")); - ASSERT_OK(Put("1000000000foo004", "v__4")); - ASSERT_OK(Put("1000000000foo005", "v__5")); - ASSERT_OK(Put("1000000000foo007", "v__7")); - ASSERT_OK(Put("1000000000foo008", "v__8")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v1", Get("1000000000foo001")); - ASSERT_EQ("v__3", Get("1000000000foo003")); - Iterator* iter = dbfull()->NewIterator(ReadOptions()); - iter->Seek("1000000000foo000"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo001", iter->key().ToString()); - ASSERT_EQ("v1", iter->value().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo002", iter->key().ToString()); - ASSERT_EQ("v_2", iter->value().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo003", iter->key().ToString()); - ASSERT_EQ("v__3", iter->value().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo004", iter->key().ToString()); - ASSERT_EQ("v__4", iter->value().ToString()); - - iter->Seek("3000000000000bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("3000000000000bar", iter->key().ToString()); - ASSERT_EQ("bar_v", iter->value().ToString()); - - iter->Seek("1000000000foo000"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo001", iter->key().ToString()); - ASSERT_EQ("v1", iter->value().ToString()); - - iter->Seek("1000000000foo005"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo005", iter->key().ToString()); - ASSERT_EQ("v__5", iter->value().ToString()); - - iter->Seek("1000000000foo006"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo007", iter->key().ToString()); - ASSERT_EQ("v__7", iter->value().ToString()); - - iter->Seek("1000000000foo008"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo008", iter->key().ToString()); - ASSERT_EQ("v__8", iter->value().ToString()); - - if (total_order == 0) { - iter->Seek("1000000000foo009"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("3000000000000bar", iter->key().ToString()); - } - - // Test Bloom Filter - if (bloom_bits > 0) { - if (!total_order) { - // Neither key nor value should exist. - expect_bloom_not_match = true; - iter->Seek("2not000000000bar"); - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ("NOT_FOUND", Get("2not000000000bar")); - expect_bloom_not_match = false; - } else { - expect_bloom_not_match = true; - ASSERT_EQ("NOT_FOUND", Get("2not000000000bar")); - expect_bloom_not_match = false; - } - } - ASSERT_OK(iter->status()); - delete iter; - } - } - } - } -} - -namespace { -std::string NthKey(size_t n, char filler) { - std::string rv(16, filler); - rv[0] = n % 10; - rv[1] = (n / 10) % 10; - rv[2] = (n / 100) % 10; - rv[3] = (n / 1000) % 10; - return rv; -} -} // anonymous namespace - -TEST_P(PlainTableDBTest, BloomSchema) { - Options options = CurrentOptions(); - options.create_if_missing = true; - for (int bloom_locality = 0; bloom_locality <= 1; bloom_locality++) { - options.bloom_locality = bloom_locality; - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = 3; // high FP rate for test - plain_table_options.hash_table_ratio = 0.75; - plain_table_options.index_sparseness = 16; - plain_table_options.huge_page_tlb_size = 0; - plain_table_options.encoding_type = kPlain; - - bool expect_bloom_not_match = false; - options.table_factory.reset(new TestPlainTableFactory( - &expect_bloom_not_match, plain_table_options, 0 /* column_family_id */, - kDefaultColumnFamilyName)); - DestroyAndReopen(&options); - - for (unsigned i = 0; i < 2345; ++i) { - ASSERT_OK(Put(NthKey(i, 'y'), "added")); - } - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("added", Get(NthKey(42, 'y'))); - - for (unsigned i = 0; i < 32; ++i) { - // Known pattern of Bloom filter false positives can detect schema change - // with high probability. Known FPs stuffed into bits: - uint32_t pattern; - if (!bloom_locality) { - pattern = 1785868347UL; - } else if (CACHE_LINE_SIZE == 64U) { - pattern = 2421694657UL; - } else if (CACHE_LINE_SIZE == 128U) { - pattern = 788710956UL; - } else { - ASSERT_EQ(CACHE_LINE_SIZE, 256U); - pattern = 163905UL; - } - bool expect_fp = pattern & (1UL << i); - // fprintf(stderr, "expect_fp@%u: %d\n", i, (int)expect_fp); - expect_bloom_not_match = !expect_fp; - ASSERT_EQ("NOT_FOUND", Get(NthKey(i, 'n'))); - } - } -} - -namespace { -std::string MakeLongKey(size_t length, char c) { - return std::string(length, c); -} -} // anonymous namespace - -TEST_P(PlainTableDBTest, IteratorLargeKeys) { - Options options = CurrentOptions(); - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 0; - plain_table_options.bloom_bits_per_key = 0; - plain_table_options.hash_table_ratio = 0; - - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - options.create_if_missing = true; - options.prefix_extractor.reset(); - DestroyAndReopen(&options); - - std::string key_list[] = {MakeLongKey(30, '0'), MakeLongKey(16, '1'), - MakeLongKey(32, '2'), MakeLongKey(60, '3'), - MakeLongKey(90, '4'), MakeLongKey(50, '5'), - MakeLongKey(26, '6')}; - - for (size_t i = 0; i < 7; i++) { - ASSERT_OK(Put(key_list[i], std::to_string(i))); - } - - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - Iterator* iter = dbfull()->NewIterator(ReadOptions()); - iter->Seek(key_list[0]); - - for (size_t i = 0; i < 7; i++) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(key_list[i], iter->key().ToString()); - ASSERT_EQ(std::to_string(i), iter->value().ToString()); - iter->Next(); - } - - ASSERT_TRUE(!iter->Valid()); - - delete iter; -} - -namespace { -std::string MakeLongKeyWithPrefix(size_t length, char c) { - return "00000000" + std::string(length - 8, c); -} -} // anonymous namespace - -TEST_P(PlainTableDBTest, IteratorLargeKeysWithPrefix) { - Options options = CurrentOptions(); - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = 0; - plain_table_options.hash_table_ratio = 0.8; - plain_table_options.index_sparseness = 3; - plain_table_options.huge_page_tlb_size = 0; - plain_table_options.encoding_type = kPrefix; - - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - options.create_if_missing = true; - DestroyAndReopen(&options); - - std::string key_list[] = { - MakeLongKeyWithPrefix(30, '0'), MakeLongKeyWithPrefix(16, '1'), - MakeLongKeyWithPrefix(32, '2'), MakeLongKeyWithPrefix(60, '3'), - MakeLongKeyWithPrefix(90, '4'), MakeLongKeyWithPrefix(50, '5'), - MakeLongKeyWithPrefix(26, '6')}; - - for (size_t i = 0; i < 7; i++) { - ASSERT_OK(Put(key_list[i], std::to_string(i))); - } - - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - Iterator* iter = dbfull()->NewIterator(ReadOptions()); - iter->Seek(key_list[0]); - - for (size_t i = 0; i < 7; i++) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(key_list[i], iter->key().ToString()); - ASSERT_EQ(std::to_string(i), iter->value().ToString()); - iter->Next(); - } - - ASSERT_TRUE(!iter->Valid()); - - delete iter; -} - -TEST_P(PlainTableDBTest, IteratorReverseSuffixComparator) { - Options options = CurrentOptions(); - options.create_if_missing = true; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - test::SimpleSuffixReverseComparator comp; - options.comparator = ∁ - DestroyAndReopen(&options); - - ASSERT_OK(Put("1000000000foo002", "v_2")); - ASSERT_OK(Put("0000000000000bar", "random")); - ASSERT_OK(Put("1000000000foo001", "v1")); - ASSERT_OK(Put("3000000000000bar", "bar_v")); - ASSERT_OK(Put("1000000000foo003", "v__3")); - ASSERT_OK(Put("1000000000foo004", "v__4")); - ASSERT_OK(Put("1000000000foo005", "v__5")); - ASSERT_OK(Put("1000000000foo007", "v__7")); - ASSERT_OK(Put("1000000000foo008", "v__8")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v1", Get("1000000000foo001")); - ASSERT_EQ("v__3", Get("1000000000foo003")); - Iterator* iter = dbfull()->NewIterator(ReadOptions()); - iter->Seek("1000000000foo009"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo008", iter->key().ToString()); - ASSERT_EQ("v__8", iter->value().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo007", iter->key().ToString()); - ASSERT_EQ("v__7", iter->value().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo005", iter->key().ToString()); - ASSERT_EQ("v__5", iter->value().ToString()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo004", iter->key().ToString()); - ASSERT_EQ("v__4", iter->value().ToString()); - - iter->Seek("3000000000000bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("3000000000000bar", iter->key().ToString()); - ASSERT_EQ("bar_v", iter->value().ToString()); - - iter->Seek("1000000000foo005"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo005", iter->key().ToString()); - ASSERT_EQ("v__5", iter->value().ToString()); - - iter->Seek("1000000000foo006"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo005", iter->key().ToString()); - ASSERT_EQ("v__5", iter->value().ToString()); - - iter->Seek("1000000000foo008"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("1000000000foo008", iter->key().ToString()); - ASSERT_EQ("v__8", iter->value().ToString()); - - iter->Seek("1000000000foo000"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("3000000000000bar", iter->key().ToString()); - - delete iter; -} - -TEST_P(PlainTableDBTest, HashBucketConflict) { - for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024; - huge_page_tlb_size += 2 * 1024 * 1024) { - for (unsigned char i = 1; i <= 3; i++) { - Options options = CurrentOptions(); - options.create_if_missing = true; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = 0; - plain_table_options.hash_table_ratio = 0; - plain_table_options.index_sparseness = 2 ^ i; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - - DestroyAndReopen(&options); - ASSERT_OK(Put("5000000000000fo0", "v1")); - ASSERT_OK(Put("5000000000000fo1", "v2")); - ASSERT_OK(Put("5000000000000fo2", "v")); - ASSERT_OK(Put("2000000000000fo0", "v3")); - ASSERT_OK(Put("2000000000000fo1", "v4")); - ASSERT_OK(Put("2000000000000fo2", "v")); - ASSERT_OK(Put("2000000000000fo3", "v")); - - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_EQ("v1", Get("5000000000000fo0")); - ASSERT_EQ("v2", Get("5000000000000fo1")); - ASSERT_EQ("v3", Get("2000000000000fo0")); - ASSERT_EQ("v4", Get("2000000000000fo1")); - - ASSERT_EQ("NOT_FOUND", Get("5000000000000bar")); - ASSERT_EQ("NOT_FOUND", Get("2000000000000bar")); - ASSERT_EQ("NOT_FOUND", Get("5000000000000fo8")); - ASSERT_EQ("NOT_FOUND", Get("2000000000000fo8")); - - ReadOptions ro; - Iterator* iter = dbfull()->NewIterator(ro); - - iter->Seek("5000000000000fo0"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo0", iter->key().ToString()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo1", iter->key().ToString()); - - iter->Seek("5000000000000fo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo1", iter->key().ToString()); - - iter->Seek("2000000000000fo0"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo0", iter->key().ToString()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo1", iter->key().ToString()); - - iter->Seek("2000000000000fo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo1", iter->key().ToString()); - - iter->Seek("2000000000000bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo0", iter->key().ToString()); - - iter->Seek("5000000000000bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo0", iter->key().ToString()); - - iter->Seek("2000000000000fo8"); - ASSERT_TRUE(!iter->Valid() || - options.comparator->Compare(iter->key(), "20000001") > 0); - - iter->Seek("5000000000000fo8"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("1000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("3000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("8000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - ASSERT_OK(iter->status()); - delete iter; - } - } -} - -TEST_P(PlainTableDBTest, HashBucketConflictReverseSuffixComparator) { - for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024; - huge_page_tlb_size += 2 * 1024 * 1024) { - for (unsigned char i = 1; i <= 3; i++) { - Options options = CurrentOptions(); - options.create_if_missing = true; - test::SimpleSuffixReverseComparator comp; - options.comparator = ∁ - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = 0; - plain_table_options.hash_table_ratio = 0; - plain_table_options.index_sparseness = 2 ^ i; - plain_table_options.huge_page_tlb_size = huge_page_tlb_size; - - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - DestroyAndReopen(&options); - ASSERT_OK(Put("5000000000000fo0", "v1")); - ASSERT_OK(Put("5000000000000fo1", "v2")); - ASSERT_OK(Put("5000000000000fo2", "v")); - ASSERT_OK(Put("2000000000000fo0", "v3")); - ASSERT_OK(Put("2000000000000fo1", "v4")); - ASSERT_OK(Put("2000000000000fo2", "v")); - ASSERT_OK(Put("2000000000000fo3", "v")); - - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_EQ("v1", Get("5000000000000fo0")); - ASSERT_EQ("v2", Get("5000000000000fo1")); - ASSERT_EQ("v3", Get("2000000000000fo0")); - ASSERT_EQ("v4", Get("2000000000000fo1")); - - ASSERT_EQ("NOT_FOUND", Get("5000000000000bar")); - ASSERT_EQ("NOT_FOUND", Get("2000000000000bar")); - ASSERT_EQ("NOT_FOUND", Get("5000000000000fo8")); - ASSERT_EQ("NOT_FOUND", Get("2000000000000fo8")); - - ReadOptions ro; - Iterator* iter = dbfull()->NewIterator(ro); - - iter->Seek("5000000000000fo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo1", iter->key().ToString()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo0", iter->key().ToString()); - - iter->Seek("5000000000000fo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo1", iter->key().ToString()); - - iter->Seek("2000000000000fo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo1", iter->key().ToString()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo0", iter->key().ToString()); - - iter->Seek("2000000000000fo1"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo1", iter->key().ToString()); - - iter->Seek("2000000000000var"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("2000000000000fo3", iter->key().ToString()); - - iter->Seek("5000000000000var"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo2", iter->key().ToString()); - - std::string seek_key = "2000000000000bar"; - iter->Seek(seek_key); - ASSERT_TRUE(!iter->Valid() || - options.prefix_extractor->Transform(iter->key()) != - options.prefix_extractor->Transform(seek_key)); - - iter->Seek("1000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("3000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("8000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - ASSERT_OK(iter->status()); - delete iter; - } - } -} - -TEST_P(PlainTableDBTest, NonExistingKeyToNonEmptyBucket) { - Options options = CurrentOptions(); - options.create_if_missing = true; - // Set only one bucket to force bucket conflict. - // Test index interval for the same prefix to be 1, 2 and 4 - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 16; - plain_table_options.bloom_bits_per_key = 0; - plain_table_options.hash_table_ratio = 0; - plain_table_options.index_sparseness = 5; - - options.table_factory.reset(NewPlainTableFactory(plain_table_options)); - DestroyAndReopen(&options); - ASSERT_OK(Put("5000000000000fo0", "v1")); - ASSERT_OK(Put("5000000000000fo1", "v2")); - ASSERT_OK(Put("5000000000000fo2", "v3")); - - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - ASSERT_EQ("v1", Get("5000000000000fo0")); - ASSERT_EQ("v2", Get("5000000000000fo1")); - ASSERT_EQ("v3", Get("5000000000000fo2")); - - ASSERT_EQ("NOT_FOUND", Get("8000000000000bar")); - ASSERT_EQ("NOT_FOUND", Get("1000000000000bar")); - - Iterator* iter = dbfull()->NewIterator(ReadOptions()); - - iter->Seek("5000000000000bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("5000000000000fo0", iter->key().ToString()); - - iter->Seek("5000000000000fo8"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("1000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - iter->Seek("8000000000000fo2"); - ASSERT_TRUE(!iter->Valid()); - - ASSERT_OK(iter->status()); - delete iter; -} - -static std::string Key(int i) { - char buf[100]; - snprintf(buf, sizeof(buf), "key_______%06d", i); - return std::string(buf); -} - -TEST_P(PlainTableDBTest, CompactionTrigger) { - Options options = CurrentOptions(); - options.write_buffer_size = 120 << 10; // 120KB - options.num_levels = 3; - options.level0_file_num_compaction_trigger = 3; - Reopen(&options); - - Random rnd(301); - - for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; - num++) { - std::vector values; - // Write 120KB (10 values, each 12K) - for (int i = 0; i < 10; i++) { - values.push_back(rnd.RandomString(12 << 10)); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Put(Key(999), "")); - ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); - ASSERT_EQ(NumTableFilesAtLevel(0), num + 1); - } - - // generate one more file in level-0, and should trigger level-0 compaction - std::vector values; - for (int i = 0; i < 12; i++) { - values.push_back(rnd.RandomString(10000)); - ASSERT_OK(Put(Key(i), values[i])); - } - ASSERT_OK(Put(Key(999), "")); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - ASSERT_EQ(NumTableFilesAtLevel(0), 0); - ASSERT_EQ(NumTableFilesAtLevel(1), 1); -} - -TEST_P(PlainTableDBTest, AdaptiveTable) { - Options options = CurrentOptions(); - options.create_if_missing = true; - - options.table_factory.reset(NewPlainTableFactory()); - DestroyAndReopen(&options); - - ASSERT_OK(Put("1000000000000foo", "v1")); - ASSERT_OK(Put("0000000000000bar", "v2")); - ASSERT_OK(Put("1000000000000foo", "v3")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - - options.create_if_missing = false; - std::shared_ptr block_based_factory( - NewBlockBasedTableFactory()); - std::shared_ptr plain_table_factory(NewPlainTableFactory()); - std::shared_ptr dummy_factory; - options.table_factory.reset(NewAdaptiveTableFactory( - block_based_factory, block_based_factory, plain_table_factory)); - Reopen(&options); - ASSERT_EQ("v3", Get("1000000000000foo")); - ASSERT_EQ("v2", Get("0000000000000bar")); - - ASSERT_OK(Put("2000000000000foo", "v4")); - ASSERT_OK(Put("3000000000000bar", "v5")); - ASSERT_OK(dbfull()->TEST_FlushMemTable()); - ASSERT_EQ("v4", Get("2000000000000foo")); - ASSERT_EQ("v5", Get("3000000000000bar")); - - Reopen(&options); - ASSERT_EQ("v3", Get("1000000000000foo")); - ASSERT_EQ("v2", Get("0000000000000bar")); - ASSERT_EQ("v4", Get("2000000000000foo")); - ASSERT_EQ("v5", Get("3000000000000bar")); - - options.paranoid_checks = false; - options.table_factory.reset(NewBlockBasedTableFactory()); - Reopen(&options); - ASSERT_NE("v3", Get("1000000000000foo")); - - options.paranoid_checks = false; - options.table_factory.reset(NewPlainTableFactory()); - Reopen(&options); - ASSERT_NE("v5", Get("3000000000000bar")); -} - -INSTANTIATE_TEST_CASE_P(PlainTableDBTest, PlainTableDBTest, ::testing::Bool()); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/prefix_test.cc b/db/prefix_test.cc deleted file mode 100644 index a8ae04035..000000000 --- a/db/prefix_test.cc +++ /dev/null @@ -1,894 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#ifndef GFLAGS -#include -int main() { - fprintf(stderr, "Please install gflags to run this test... Skipping...\n"); - return 0; -} -#else - -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "monitoring/histogram.h" -#include "rocksdb/comparator.h" -#include "rocksdb/db.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/system_clock.h" -#include "rocksdb/table.h" -#include "test_util/testharness.h" -#include "util/cast_util.h" -#include "util/coding.h" -#include "util/gflags_compat.h" -#include "util/random.h" -#include "util/stop_watch.h" -#include "util/string_util.h" -#include "utilities/merge_operators.h" - -using GFLAGS_NAMESPACE::ParseCommandLineFlags; - -DEFINE_bool(trigger_deadlock, false, - "issue delete in range scan to trigger PrefixHashMap deadlock"); -DEFINE_int32(bucket_count, 100000, "number of buckets"); -DEFINE_uint64(num_locks, 10001, "number of locks"); -DEFINE_bool(random_prefix, false, "randomize prefix"); -DEFINE_uint64(total_prefixes, 100000, "total number of prefixes"); -DEFINE_uint64(items_per_prefix, 1, "total number of values per prefix"); -DEFINE_int64(write_buffer_size, 33554432, ""); -DEFINE_int32(max_write_buffer_number, 2, ""); -DEFINE_int32(min_write_buffer_number_to_merge, 1, ""); -DEFINE_int32(skiplist_height, 4, ""); -DEFINE_double(memtable_prefix_bloom_size_ratio, 0.1, ""); -DEFINE_int32(memtable_huge_page_size, 2 * 1024 * 1024, ""); -DEFINE_int32(value_size, 40, ""); -DEFINE_bool(enable_print, false, "Print options generated to console."); - -// Path to the database on file system -const std::string kDbName = - ROCKSDB_NAMESPACE::test::PerThreadDBPath("prefix_test"); - -namespace ROCKSDB_NAMESPACE { - -struct TestKey { - uint64_t prefix; - uint64_t sorted; - - TestKey(uint64_t _prefix, uint64_t _sorted) - : prefix(_prefix), sorted(_sorted) {} -}; - -// return a slice backed by test_key -inline Slice TestKeyToSlice(std::string& s, const TestKey& test_key) { - s.clear(); - PutFixed64(&s, test_key.prefix); - PutFixed64(&s, test_key.sorted); - return Slice(s.c_str(), s.size()); -} - -inline const TestKey SliceToTestKey(const Slice& slice) { - return TestKey(DecodeFixed64(slice.data()), DecodeFixed64(slice.data() + 8)); -} - -class TestKeyComparator : public Comparator { - public: - // Compare needs to be aware of the possibility of a and/or b is - // prefix only - int Compare(const Slice& a, const Slice& b) const override { - const TestKey kkey_a = SliceToTestKey(a); - const TestKey kkey_b = SliceToTestKey(b); - const TestKey* key_a = &kkey_a; - const TestKey* key_b = &kkey_b; - if (key_a->prefix != key_b->prefix) { - if (key_a->prefix < key_b->prefix) return -1; - if (key_a->prefix > key_b->prefix) return 1; - } else { - EXPECT_TRUE(key_a->prefix == key_b->prefix); - // note, both a and b could be prefix only - if (a.size() != b.size()) { - // one of them is prefix - EXPECT_TRUE( - (a.size() == sizeof(uint64_t) && b.size() == sizeof(TestKey)) || - (b.size() == sizeof(uint64_t) && a.size() == sizeof(TestKey))); - if (a.size() < b.size()) return -1; - if (a.size() > b.size()) return 1; - } else { - // both a and b are prefix - if (a.size() == sizeof(uint64_t)) { - return 0; - } - - // both a and b are whole key - EXPECT_TRUE(a.size() == sizeof(TestKey) && b.size() == sizeof(TestKey)); - if (key_a->sorted < key_b->sorted) return -1; - if (key_a->sorted > key_b->sorted) return 1; - if (key_a->sorted == key_b->sorted) return 0; - } - } - return 0; - } - - bool operator()(const TestKey& a, const TestKey& b) const { - std::string sa, sb; - return Compare(TestKeyToSlice(sa, a), TestKeyToSlice(sb, b)) < 0; - } - - const char* Name() const override { return "TestKeyComparator"; } - - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const override {} - - void FindShortSuccessor(std::string* /*key*/) const override {} -}; - -namespace { -void PutKey(DB* db, WriteOptions write_options, uint64_t prefix, - uint64_t suffix, const Slice& value) { - TestKey test_key(prefix, suffix); - std::string s; - Slice key = TestKeyToSlice(s, test_key); - ASSERT_OK(db->Put(write_options, key, value)); -} - -void PutKey(DB* db, WriteOptions write_options, const TestKey& test_key, - const Slice& value) { - std::string s; - Slice key = TestKeyToSlice(s, test_key); - ASSERT_OK(db->Put(write_options, key, value)); -} - -void MergeKey(DB* db, WriteOptions write_options, const TestKey& test_key, - const Slice& value) { - std::string s; - Slice key = TestKeyToSlice(s, test_key); - ASSERT_OK(db->Merge(write_options, key, value)); -} - -void DeleteKey(DB* db, WriteOptions write_options, const TestKey& test_key) { - std::string s; - Slice key = TestKeyToSlice(s, test_key); - ASSERT_OK(db->Delete(write_options, key)); -} - -void SeekIterator(Iterator* iter, uint64_t prefix, uint64_t suffix) { - TestKey test_key(prefix, suffix); - std::string s; - Slice key = TestKeyToSlice(s, test_key); - iter->Seek(key); -} - -const std::string kNotFoundResult = "NOT_FOUND"; - -std::string Get(DB* db, const ReadOptions& read_options, uint64_t prefix, - uint64_t suffix) { - TestKey test_key(prefix, suffix); - std::string s2; - Slice key = TestKeyToSlice(s2, test_key); - - std::string result; - Status s = db->Get(read_options, key, &result); - if (s.IsNotFound()) { - result = kNotFoundResult; - } else if (!s.ok()) { - result = s.ToString(); - } - return result; -} - -class SamePrefixTransform : public SliceTransform { - private: - const Slice prefix_; - std::string name_; - - public: - explicit SamePrefixTransform(const Slice& prefix) - : prefix_(prefix), name_("rocksdb.SamePrefix." + prefix.ToString()) {} - - const char* Name() const override { return name_.c_str(); } - - Slice Transform(const Slice& src) const override { - assert(InDomain(src)); - return prefix_; - } - - bool InDomain(const Slice& src) const override { - if (src.size() >= prefix_.size()) { - return Slice(src.data(), prefix_.size()) == prefix_; - } - return false; - } - - bool InRange(const Slice& dst) const override { return dst == prefix_; } - - bool FullLengthEnabled(size_t* /*len*/) const override { return false; } -}; - -} // anonymous namespace - -class PrefixTest : public testing::Test { - public: - std::shared_ptr OpenDb() { - DB* db; - - options.create_if_missing = true; - options.write_buffer_size = FLAGS_write_buffer_size; - options.max_write_buffer_number = FLAGS_max_write_buffer_number; - options.min_write_buffer_number_to_merge = - FLAGS_min_write_buffer_number_to_merge; - - options.memtable_prefix_bloom_size_ratio = - FLAGS_memtable_prefix_bloom_size_ratio; - options.memtable_huge_page_size = FLAGS_memtable_huge_page_size; - - options.prefix_extractor.reset(NewFixedPrefixTransform(8)); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - options.allow_concurrent_memtable_write = false; - - Status s = DB::Open(options, kDbName, &db); - EXPECT_OK(s); - return std::shared_ptr(db); - } - - void FirstOption() { option_config_ = kBegin; } - - bool NextOptions(int bucket_count) { - // skip some options - option_config_++; - if (option_config_ < kEnd) { - options.prefix_extractor.reset(NewFixedPrefixTransform(8)); - switch (option_config_) { - case kHashSkipList: - options.memtable_factory.reset( - NewHashSkipListRepFactory(bucket_count, FLAGS_skiplist_height)); - return true; - case kHashLinkList: - options.memtable_factory.reset( - NewHashLinkListRepFactory(bucket_count)); - return true; - case kHashLinkListHugePageTlb: - options.memtable_factory.reset( - NewHashLinkListRepFactory(bucket_count, 2 * 1024 * 1024)); - return true; - case kHashLinkListTriggerSkipList: - options.memtable_factory.reset( - NewHashLinkListRepFactory(bucket_count, 0, 3)); - return true; - default: - return false; - } - } - return false; - } - - PrefixTest() : option_config_(kBegin) { - options.comparator = new TestKeyComparator(); - } - ~PrefixTest() override { delete options.comparator; } - - protected: - enum OptionConfig { - kBegin, - kHashSkipList, - kHashLinkList, - kHashLinkListHugePageTlb, - kHashLinkListTriggerSkipList, - kEnd - }; - int option_config_; - Options options; -}; - -TEST(SamePrefixTest, InDomainTest) { - DB* db; - Options options; - options.create_if_missing = true; - options.prefix_extractor.reset(new SamePrefixTransform("HHKB")); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.whole_key_filtering = false; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - WriteOptions write_options; - ReadOptions read_options; - { - ASSERT_OK(DestroyDB(kDbName, Options())); - ASSERT_OK(DB::Open(options, kDbName, &db)); - ASSERT_OK(db->Put(write_options, "HHKB pro2", "Mar 24, 2006")); - ASSERT_OK(db->Put(write_options, "HHKB pro2 Type-S", "June 29, 2011")); - ASSERT_OK(db->Put(write_options, "Realforce 87u", "idk")); - ASSERT_OK(db->Flush(FlushOptions())); - std::string result; - auto db_iter = db->NewIterator(ReadOptions()); - - db_iter->Seek("Realforce 87u"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - ASSERT_EQ(db_iter->key(), "Realforce 87u"); - ASSERT_EQ(db_iter->value(), "idk"); - - delete db_iter; - delete db; - ASSERT_OK(DestroyDB(kDbName, Options())); - } - - { - ASSERT_OK(DB::Open(options, kDbName, &db)); - ASSERT_OK(db->Put(write_options, "pikachu", "1")); - ASSERT_OK(db->Put(write_options, "Meowth", "1")); - ASSERT_OK(db->Put(write_options, "Mewtwo", "idk")); - ASSERT_OK(db->Flush(FlushOptions())); - std::string result; - auto db_iter = db->NewIterator(ReadOptions()); - - db_iter->Seek("Mewtwo"); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - delete db_iter; - delete db; - ASSERT_OK(DestroyDB(kDbName, Options())); - } -} - -TEST_F(PrefixTest, TestResult) { - for (int num_buckets = 1; num_buckets <= 2; num_buckets++) { - FirstOption(); - while (NextOptions(num_buckets)) { - std::cout << "*** Mem table: " << options.memtable_factory->Name() - << " number of buckets: " << num_buckets << std::endl; - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - - // 1. Insert one row. - Slice v16("v16"); - PutKey(db.get(), write_options, 1, 6, v16); - std::unique_ptr iter(db->NewIterator(read_options)); - SeekIterator(iter.get(), 1, 6); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - SeekIterator(iter.get(), 1, 5); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - SeekIterator(iter.get(), 1, 5); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - - SeekIterator(iter.get(), 2, 0); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - - ASSERT_EQ(v16.ToString(), Get(db.get(), read_options, 1, 6)); - ASSERT_EQ(kNotFoundResult, Get(db.get(), read_options, 1, 5)); - ASSERT_EQ(kNotFoundResult, Get(db.get(), read_options, 1, 7)); - ASSERT_EQ(kNotFoundResult, Get(db.get(), read_options, 0, 6)); - ASSERT_EQ(kNotFoundResult, Get(db.get(), read_options, 2, 6)); - - // 2. Insert an entry for the same prefix as the last entry in the bucket. - Slice v17("v17"); - PutKey(db.get(), write_options, 1, 7, v17); - iter.reset(db->NewIterator(read_options)); - SeekIterator(iter.get(), 1, 7); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - SeekIterator(iter.get(), 1, 6); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - iter->Next(); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - - SeekIterator(iter.get(), 2, 0); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - - // 3. Insert an entry for the same prefix as the head of the bucket. - Slice v15("v15"); - PutKey(db.get(), write_options, 1, 5, v15); - iter.reset(db->NewIterator(read_options)); - - SeekIterator(iter.get(), 1, 7); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - SeekIterator(iter.get(), 1, 5); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v15 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - SeekIterator(iter.get(), 1, 5); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v15 == iter->value()); - - ASSERT_EQ(v15.ToString(), Get(db.get(), read_options, 1, 5)); - ASSERT_EQ(v16.ToString(), Get(db.get(), read_options, 1, 6)); - ASSERT_EQ(v17.ToString(), Get(db.get(), read_options, 1, 7)); - - // 4. Insert an entry with a larger prefix - Slice v22("v22"); - PutKey(db.get(), write_options, 2, 2, v22); - iter.reset(db->NewIterator(read_options)); - - SeekIterator(iter.get(), 2, 2); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v22 == iter->value()); - SeekIterator(iter.get(), 2, 0); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v22 == iter->value()); - - SeekIterator(iter.get(), 1, 5); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v15 == iter->value()); - - SeekIterator(iter.get(), 1, 7); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - // 5. Insert an entry with a smaller prefix - Slice v02("v02"); - PutKey(db.get(), write_options, 0, 2, v02); - iter.reset(db->NewIterator(read_options)); - - SeekIterator(iter.get(), 0, 2); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v02 == iter->value()); - SeekIterator(iter.get(), 0, 0); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v02 == iter->value()); - - SeekIterator(iter.get(), 2, 0); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v22 == iter->value()); - - SeekIterator(iter.get(), 1, 5); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v15 == iter->value()); - - SeekIterator(iter.get(), 1, 7); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - // 6. Insert to the beginning and the end of the first prefix - Slice v13("v13"); - Slice v18("v18"); - PutKey(db.get(), write_options, 1, 3, v13); - PutKey(db.get(), write_options, 1, 8, v18); - iter.reset(db->NewIterator(read_options)); - SeekIterator(iter.get(), 1, 7); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - SeekIterator(iter.get(), 1, 3); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v13 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v15 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v18 == iter->value()); - - SeekIterator(iter.get(), 0, 0); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v02 == iter->value()); - - SeekIterator(iter.get(), 2, 0); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v22 == iter->value()); - - ASSERT_EQ(v22.ToString(), Get(db.get(), read_options, 2, 2)); - ASSERT_EQ(v02.ToString(), Get(db.get(), read_options, 0, 2)); - ASSERT_EQ(v13.ToString(), Get(db.get(), read_options, 1, 3)); - ASSERT_EQ(v15.ToString(), Get(db.get(), read_options, 1, 5)); - ASSERT_EQ(v16.ToString(), Get(db.get(), read_options, 1, 6)); - ASSERT_EQ(v17.ToString(), Get(db.get(), read_options, 1, 7)); - ASSERT_EQ(v18.ToString(), Get(db.get(), read_options, 1, 8)); - } - } -} - -// Show results in prefix -TEST_F(PrefixTest, PrefixValid) { - for (int num_buckets = 1; num_buckets <= 2; num_buckets++) { - FirstOption(); - while (NextOptions(num_buckets)) { - std::cout << "*** Mem table: " << options.memtable_factory->Name() - << " number of buckets: " << num_buckets << std::endl; - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - - // Insert keys with common prefix and one key with different - Slice v16("v16"); - Slice v17("v17"); - Slice v18("v18"); - Slice v19("v19"); - PutKey(db.get(), write_options, 12345, 6, v16); - PutKey(db.get(), write_options, 12345, 7, v17); - PutKey(db.get(), write_options, 12345, 8, v18); - PutKey(db.get(), write_options, 12345, 9, v19); - PutKey(db.get(), write_options, 12346, 8, v16); - ASSERT_OK(db->Flush(FlushOptions())); - TestKey test_key(12346, 8); - std::string s; - ASSERT_OK(db->Delete(write_options, TestKeyToSlice(s, test_key))); - ASSERT_OK(db->Flush(FlushOptions())); - read_options.prefix_same_as_start = true; - std::unique_ptr iter(db->NewIterator(read_options)); - SeekIterator(iter.get(), 12345, 6); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v16 == iter->value()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v17 == iter->value()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v18 == iter->value()); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_TRUE(v19 == iter->value()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_EQ(kNotFoundResult, Get(db.get(), read_options, 12346, 8)); - - // Verify seeking past the prefix won't return a result. - SeekIterator(iter.get(), 12345, 10); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - } - } -} - -TEST_F(PrefixTest, DynamicPrefixIterator) { - while (NextOptions(FLAGS_bucket_count)) { - std::cout << "*** Mem table: " << options.memtable_factory->Name() - << std::endl; - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - - std::vector prefixes; - for (uint64_t i = 0; i < FLAGS_total_prefixes; ++i) { - prefixes.push_back(i); - } - - if (FLAGS_random_prefix) { - RandomShuffle(prefixes.begin(), prefixes.end()); - } - - HistogramImpl hist_put_time; - HistogramImpl hist_put_comparison; - // insert x random prefix, each with y continuous element. - for (auto prefix : prefixes) { - for (uint64_t sorted = 0; sorted < FLAGS_items_per_prefix; sorted++) { - TestKey test_key(prefix, sorted); - - std::string s; - Slice key = TestKeyToSlice(s, test_key); - std::string value(FLAGS_value_size, 0); - - get_perf_context()->Reset(); - StopWatchNano timer(SystemClock::Default().get(), true); - ASSERT_OK(db->Put(write_options, key, value)); - hist_put_time.Add(timer.ElapsedNanos()); - hist_put_comparison.Add(get_perf_context()->user_key_comparison_count); - } - } - - std::cout << "Put key comparison: \n" - << hist_put_comparison.ToString() << "Put time: \n" - << hist_put_time.ToString(); - - // test seek existing keys - HistogramImpl hist_seek_time; - HistogramImpl hist_seek_comparison; - - std::unique_ptr iter(db->NewIterator(read_options)); - - for (auto prefix : prefixes) { - TestKey test_key(prefix, FLAGS_items_per_prefix / 2); - std::string s; - Slice key = TestKeyToSlice(s, test_key); - std::string value = "v" + std::to_string(0); - - get_perf_context()->Reset(); - StopWatchNano timer(SystemClock::Default().get(), true); - auto key_prefix = options.prefix_extractor->Transform(key); - uint64_t total_keys = 0; - for (iter->Seek(key); - iter->Valid() && iter->key().starts_with(key_prefix); iter->Next()) { - if (FLAGS_trigger_deadlock) { - std::cout << "Behold the deadlock!\n"; - db->Delete(write_options, iter->key()); - } - total_keys++; - } - hist_seek_time.Add(timer.ElapsedNanos()); - hist_seek_comparison.Add(get_perf_context()->user_key_comparison_count); - ASSERT_EQ(total_keys, - FLAGS_items_per_prefix - FLAGS_items_per_prefix / 2); - } - - std::cout << "Seek key comparison: \n" - << hist_seek_comparison.ToString() << "Seek time: \n" - << hist_seek_time.ToString(); - - // test non-existing keys - HistogramImpl hist_no_seek_time; - HistogramImpl hist_no_seek_comparison; - - for (auto prefix = FLAGS_total_prefixes; - prefix < FLAGS_total_prefixes + 10000; prefix++) { - TestKey test_key(prefix, 0); - std::string s; - Slice key = TestKeyToSlice(s, test_key); - - get_perf_context()->Reset(); - StopWatchNano timer(SystemClock::Default().get(), true); - iter->Seek(key); - hist_no_seek_time.Add(timer.ElapsedNanos()); - hist_no_seek_comparison.Add( - get_perf_context()->user_key_comparison_count); - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - } - - std::cout << "non-existing Seek key comparison: \n" - << hist_no_seek_comparison.ToString() - << "non-existing Seek time: \n" - << hist_no_seek_time.ToString(); - } -} - -TEST_F(PrefixTest, PrefixSeekModePrev) { - // Only for SkipListFactory - options.memtable_factory.reset(new SkipListFactory); - options.merge_operator = MergeOperators::CreatePutOperator(); - options.write_buffer_size = 1024 * 1024; - Random rnd(1); - for (size_t m = 1; m < 100; m++) { - std::cout << "[" + std::to_string(m) + "]" + "*** Mem table: " - << options.memtable_factory->Name() << std::endl; - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - std::map entry_maps[3], whole_map; - for (uint64_t i = 0; i < 10; i++) { - int div = i % 3 + 1; - for (uint64_t j = 0; j < 10; j++) { - whole_map[TestKey(i, j)] = entry_maps[rnd.Uniform(div)][TestKey(i, j)] = - 'v' + std::to_string(i) + std::to_string(j); - } - } - - std::map type_map; - for (size_t i = 0; i < 3; i++) { - for (auto& kv : entry_maps[i]) { - if (rnd.OneIn(3)) { - PutKey(db.get(), write_options, kv.first, kv.second); - type_map[kv.first] = "value"; - } else { - MergeKey(db.get(), write_options, kv.first, kv.second); - type_map[kv.first] = "merge"; - } - } - if (i < 2) { - ASSERT_OK(db->Flush(FlushOptions())); - } - } - - for (size_t i = 0; i < 2; i++) { - for (auto& kv : entry_maps[i]) { - if (rnd.OneIn(10)) { - whole_map.erase(kv.first); - DeleteKey(db.get(), write_options, kv.first); - entry_maps[2][kv.first] = "delete"; - } - } - } - - if (FLAGS_enable_print) { - for (size_t i = 0; i < 3; i++) { - for (auto& kv : entry_maps[i]) { - std::cout << "[" << i << "]" << kv.first.prefix << kv.first.sorted - << " " << kv.second + " " + type_map[kv.first] << std::endl; - } - } - } - - std::unique_ptr iter(db->NewIterator(read_options)); - for (uint64_t prefix = 0; prefix < 10; prefix++) { - uint64_t start_suffix = rnd.Uniform(9); - SeekIterator(iter.get(), prefix, start_suffix); - auto it = whole_map.find(TestKey(prefix, start_suffix)); - if (it == whole_map.end()) { - continue; - } - ASSERT_NE(it, whole_map.end()); - ASSERT_TRUE(iter->Valid()); - if (FLAGS_enable_print) { - std::cout << "round " << prefix - << " iter: " << SliceToTestKey(iter->key()).prefix - << SliceToTestKey(iter->key()).sorted - << " | map: " << it->first.prefix << it->first.sorted << " | " - << iter->value().ToString() << " " << it->second << std::endl; - } - ASSERT_EQ(iter->value(), it->second); - uint64_t stored_prefix = prefix; - for (size_t k = 0; k < 9; k++) { - if (rnd.OneIn(2) || it == whole_map.begin()) { - iter->Next(); - ++it; - if (FLAGS_enable_print) { - std::cout << "Next >> "; - } - } else { - iter->Prev(); - it--; - if (FLAGS_enable_print) { - std::cout << "Prev >> "; - } - } - if (!iter->Valid() || - SliceToTestKey(iter->key()).prefix != stored_prefix) { - break; - } - ASSERT_OK(iter->status()); - stored_prefix = SliceToTestKey(iter->key()).prefix; - ASSERT_TRUE(iter->Valid()); - ASSERT_NE(it, whole_map.end()); - ASSERT_EQ(iter->value(), it->second); - if (FLAGS_enable_print) { - std::cout << "iter: " << SliceToTestKey(iter->key()).prefix - << SliceToTestKey(iter->key()).sorted - << " | map: " << it->first.prefix << it->first.sorted - << " | " << iter->value().ToString() << " " << it->second - << std::endl; - } - } - } - } -} - -TEST_F(PrefixTest, PrefixSeekModePrev2) { - // Only for SkipListFactory - // test the case - // iter1 iter2 - // | prefix | suffix | | prefix | suffix | - // | 1 | 1 | | 1 | 2 | - // | 1 | 3 | | 1 | 4 | - // | 2 | 1 | | 3 | 3 | - // | 2 | 2 | | 3 | 4 | - // after seek(15), iter1 will be at 21 and iter2 will be 33. - // Then if call Prev() in prefix mode where SeekForPrev(21) gets called, - // iter2 should turn to invalid state because of bloom filter. - options.memtable_factory.reset(new SkipListFactory); - options.write_buffer_size = 1024 * 1024; - std::string v13("v13"); - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - PutKey(db.get(), write_options, TestKey(1, 2), "v12"); - PutKey(db.get(), write_options, TestKey(1, 4), "v14"); - PutKey(db.get(), write_options, TestKey(3, 3), "v33"); - PutKey(db.get(), write_options, TestKey(3, 4), "v34"); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK( - static_cast_with_check(db.get())->TEST_WaitForFlushMemTable()); - PutKey(db.get(), write_options, TestKey(1, 1), "v11"); - PutKey(db.get(), write_options, TestKey(1, 3), "v13"); - PutKey(db.get(), write_options, TestKey(2, 1), "v21"); - PutKey(db.get(), write_options, TestKey(2, 2), "v22"); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK( - static_cast_with_check(db.get())->TEST_WaitForFlushMemTable()); - std::unique_ptr iter(db->NewIterator(read_options)); - SeekIterator(iter.get(), 1, 5); - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->value(), v13); -} - -TEST_F(PrefixTest, PrefixSeekModePrev3) { - // Only for SkipListFactory - // test SeekToLast() with iterate_upper_bound_ in prefix_seek_mode - options.memtable_factory.reset(new SkipListFactory); - options.write_buffer_size = 1024 * 1024; - std::string v14("v14"); - TestKey upper_bound_key = TestKey(1, 5); - std::string s; - Slice upper_bound = TestKeyToSlice(s, upper_bound_key); - - { - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - read_options.iterate_upper_bound = &upper_bound; - PutKey(db.get(), write_options, TestKey(1, 2), "v12"); - PutKey(db.get(), write_options, TestKey(1, 4), "v14"); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK( - static_cast_with_check(db.get())->TEST_WaitForFlushMemTable()); - PutKey(db.get(), write_options, TestKey(1, 1), "v11"); - PutKey(db.get(), write_options, TestKey(1, 3), "v13"); - PutKey(db.get(), write_options, TestKey(2, 1), "v21"); - PutKey(db.get(), write_options, TestKey(2, 2), "v22"); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK( - static_cast_with_check(db.get())->TEST_WaitForFlushMemTable()); - std::unique_ptr iter(db->NewIterator(read_options)); - iter->SeekToLast(); - ASSERT_EQ(iter->value(), v14); - } - { - ASSERT_OK(DestroyDB(kDbName, Options())); - auto db = OpenDb(); - WriteOptions write_options; - ReadOptions read_options; - read_options.iterate_upper_bound = &upper_bound; - PutKey(db.get(), write_options, TestKey(1, 2), "v12"); - PutKey(db.get(), write_options, TestKey(1, 4), "v14"); - PutKey(db.get(), write_options, TestKey(3, 3), "v33"); - PutKey(db.get(), write_options, TestKey(3, 4), "v34"); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK( - static_cast_with_check(db.get())->TEST_WaitForFlushMemTable()); - PutKey(db.get(), write_options, TestKey(1, 1), "v11"); - PutKey(db.get(), write_options, TestKey(1, 3), "v13"); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK( - static_cast_with_check(db.get())->TEST_WaitForFlushMemTable()); - std::unique_ptr iter(db->NewIterator(read_options)); - iter->SeekToLast(); - ASSERT_EQ(iter->value(), v14); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - ParseCommandLineFlags(&argc, &argv, true); - return RUN_ALL_TESTS(); -} - -#endif // GFLAGS - diff --git a/db/range_del_aggregator_test.cc b/db/range_del_aggregator_test.cc deleted file mode 100644 index 89391c924..000000000 --- a/db/range_del_aggregator_test.cc +++ /dev/null @@ -1,713 +0,0 @@ -// Copyright (c) 2018-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/range_del_aggregator.h" - -#include -#include -#include - -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "db/range_tombstone_fragmenter.h" -#include "test_util/testutil.h" -#include "util/vector_iterator.h" - -namespace ROCKSDB_NAMESPACE { - -class RangeDelAggregatorTest : public testing::Test {}; - -namespace { - -static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator()); - -std::unique_ptr MakeRangeDelIter( - const std::vector& range_dels) { - std::vector keys, values; - for (const auto& range_del : range_dels) { - auto key_and_value = range_del.Serialize(); - keys.push_back(key_and_value.first.Encode().ToString()); - values.push_back(key_and_value.second.ToString()); - } - return std::unique_ptr( - new VectorIterator(keys, values, &bytewise_icmp)); -} - -std::vector> -MakeFragmentedTombstoneLists( - const std::vector>& range_dels_list) { - std::vector> fragment_lists; - for (const auto& range_dels : range_dels_list) { - auto range_del_iter = MakeRangeDelIter(range_dels); - fragment_lists.emplace_back(new FragmentedRangeTombstoneList( - std::move(range_del_iter), bytewise_icmp)); - } - return fragment_lists; -} - -struct TruncatedIterScanTestCase { - ParsedInternalKey start; - ParsedInternalKey end; - SequenceNumber seq; -}; - -struct TruncatedIterSeekTestCase { - Slice target; - ParsedInternalKey start; - ParsedInternalKey end; - SequenceNumber seq; - bool invalid; -}; - -struct ShouldDeleteTestCase { - ParsedInternalKey lookup_key; - bool result; -}; - -struct IsRangeOverlappedTestCase { - Slice start; - Slice end; - bool result; -}; - -ParsedInternalKey UncutEndpoint(const Slice& s) { - return ParsedInternalKey(s, kMaxSequenceNumber, kTypeRangeDeletion); -} - -ParsedInternalKey InternalValue(const Slice& key, SequenceNumber seq, - ValueType type = kTypeValue) { - return ParsedInternalKey(key, seq, type); -} - -void VerifyIterator( - TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp, - const std::vector& expected_range_dels) { - // Test forward iteration. - iter->SeekToFirst(); - for (size_t i = 0; i < expected_range_dels.size(); i++, iter->Next()) { - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(0, icmp.Compare(iter->start_key(), expected_range_dels[i].start)); - EXPECT_EQ(0, icmp.Compare(iter->end_key(), expected_range_dels[i].end)); - EXPECT_EQ(expected_range_dels[i].seq, iter->seq()); - } - EXPECT_FALSE(iter->Valid()); - - // Test reverse iteration. - iter->SeekToLast(); - std::vector reverse_expected_range_dels( - expected_range_dels.rbegin(), expected_range_dels.rend()); - for (size_t i = 0; i < reverse_expected_range_dels.size(); - i++, iter->Prev()) { - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(0, icmp.Compare(iter->start_key(), - reverse_expected_range_dels[i].start)); - EXPECT_EQ( - 0, icmp.Compare(iter->end_key(), reverse_expected_range_dels[i].end)); - EXPECT_EQ(reverse_expected_range_dels[i].seq, iter->seq()); - } - EXPECT_FALSE(iter->Valid()); -} - -void VerifySeek(TruncatedRangeDelIterator* iter, - const InternalKeyComparator& icmp, - const std::vector& test_cases) { - for (const auto& test_case : test_cases) { - iter->Seek(test_case.target); - if (test_case.invalid) { - ASSERT_FALSE(iter->Valid()); - } else { - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start)); - EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end)); - EXPECT_EQ(test_case.seq, iter->seq()); - } - } -} - -void VerifySeekForPrev( - TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp, - const std::vector& test_cases) { - for (const auto& test_case : test_cases) { - iter->SeekForPrev(test_case.target); - if (test_case.invalid) { - ASSERT_FALSE(iter->Valid()); - } else { - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start)); - EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end)); - EXPECT_EQ(test_case.seq, iter->seq()); - } - } -} - -void VerifyShouldDelete(RangeDelAggregator* range_del_agg, - const std::vector& test_cases) { - for (const auto& test_case : test_cases) { - EXPECT_EQ( - test_case.result, - range_del_agg->ShouldDelete( - test_case.lookup_key, RangeDelPositioningMode::kForwardTraversal)); - } - for (auto it = test_cases.rbegin(); it != test_cases.rend(); ++it) { - const auto& test_case = *it; - EXPECT_EQ( - test_case.result, - range_del_agg->ShouldDelete( - test_case.lookup_key, RangeDelPositioningMode::kBackwardTraversal)); - } -} - -void VerifyIsRangeOverlapped( - ReadRangeDelAggregator* range_del_agg, - const std::vector& test_cases) { - for (const auto& test_case : test_cases) { - EXPECT_EQ(test_case.result, - range_del_agg->IsRangeOverlapped(test_case.start, test_case.end)); - } -} - -void CheckIterPosition(const RangeTombstone& tombstone, - const FragmentedRangeTombstoneIterator* iter) { - // Test InternalIterator interface. - EXPECT_EQ(tombstone.start_key_, ExtractUserKey(iter->key())); - EXPECT_EQ(tombstone.end_key_, iter->value()); - EXPECT_EQ(tombstone.seq_, iter->seq()); - - // Test FragmentedRangeTombstoneIterator interface. - EXPECT_EQ(tombstone.start_key_, iter->start_key()); - EXPECT_EQ(tombstone.end_key_, iter->end_key()); - EXPECT_EQ(tombstone.seq_, GetInternalKeySeqno(iter->key())); -} - -void VerifyFragmentedRangeDels( - FragmentedRangeTombstoneIterator* iter, - const std::vector& expected_tombstones) { - iter->SeekToFirst(); - for (size_t i = 0; i < expected_tombstones.size(); i++, iter->Next()) { - ASSERT_TRUE(iter->Valid()); - CheckIterPosition(expected_tombstones[i], iter); - } - EXPECT_FALSE(iter->Valid()); -} - -} // anonymous namespace - -TEST_F(RangeDelAggregatorTest, EmptyTruncatedIter) { - auto range_del_iter = MakeRangeDelIter({}); - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, - kMaxSequenceNumber)); - - TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, - nullptr); - - iter.SeekToFirst(); - ASSERT_FALSE(iter.Valid()); - - iter.SeekToLast(); - ASSERT_FALSE(iter.Valid()); -} - -TEST_F(RangeDelAggregatorTest, UntruncatedIter) { - auto range_del_iter = - MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, - kMaxSequenceNumber)); - - TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, - nullptr); - - VerifyIterator( - &iter, bytewise_icmp, - {{InternalValue("a", 10, kTypeRangeDeletion), UncutEndpoint("e"), 10}, - {InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {InternalValue("j", 4, kTypeRangeDeletion), UncutEndpoint("n"), 4}}); - - VerifySeek( - &iter, bytewise_icmp, - {{"d", InternalValue("a", 10, kTypeRangeDeletion), UncutEndpoint("e"), - 10}, - {"e", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"ia", InternalValue("j", 4, kTypeRangeDeletion), UncutEndpoint("n"), 4}, - {"n", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}, - {"", InternalValue("a", 10, kTypeRangeDeletion), UncutEndpoint("e"), - 10}}); - - VerifySeekForPrev( - &iter, bytewise_icmp, - {{"d", InternalValue("a", 10, kTypeRangeDeletion), UncutEndpoint("e"), - 10}, - {"e", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"ia", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"n", InternalValue("j", 4, kTypeRangeDeletion), UncutEndpoint("n"), 4}, - {"", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}}); -} - -TEST_F(RangeDelAggregatorTest, UntruncatedIterWithSnapshot) { - auto range_del_iter = - MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, - 9 /* snapshot */)); - - TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, - nullptr); - - VerifyIterator( - &iter, bytewise_icmp, - {{InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {InternalValue("j", 4, kTypeRangeDeletion), UncutEndpoint("n"), 4}}); - - VerifySeek( - &iter, bytewise_icmp, - {{"d", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"e", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"ia", InternalValue("j", 4, kTypeRangeDeletion), UncutEndpoint("n"), 4}, - {"n", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}, - {"", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}}); - - VerifySeekForPrev( - &iter, bytewise_icmp, - {{"d", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}, - {"e", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"ia", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"n", InternalValue("j", 4, kTypeRangeDeletion), UncutEndpoint("n"), 4}, - {"", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}}); -} - -TEST_F(RangeDelAggregatorTest, TruncatedIterPartiallyCutTombstones) { - auto range_del_iter = - MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, - kMaxSequenceNumber)); - - InternalKey smallest("d", 7, kTypeValue); - InternalKey largest("m", 9, kTypeValue); - TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, - &smallest, &largest); - - VerifyIterator( - &iter, bytewise_icmp, - {{InternalValue("d", 7, kTypeMaxValid), UncutEndpoint("e"), 10}, - {InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {InternalValue("j", 4, kTypeRangeDeletion), - InternalValue("m", 8, kTypeMaxValid), 4}}); - - VerifySeek( - &iter, bytewise_icmp, - {{"d", InternalValue("d", 7, kTypeMaxValid), UncutEndpoint("e"), 10}, - {"e", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"ia", InternalValue("j", 4, kTypeRangeDeletion), - InternalValue("m", 8, kTypeMaxValid), 4, false /* invalid */}, - {"n", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}, - {"", InternalValue("d", 7, kTypeMaxValid), UncutEndpoint("e"), 10}}); - - VerifySeekForPrev( - &iter, bytewise_icmp, - {{"d", InternalValue("d", 7, kTypeMaxValid), UncutEndpoint("e"), 10}, - {"e", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"ia", InternalValue("e", 8, kTypeRangeDeletion), UncutEndpoint("g"), 8}, - {"n", InternalValue("j", 4, kTypeRangeDeletion), - InternalValue("m", 8, kTypeMaxValid), 4, false /* invalid */}, - {"", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}}); -} - -TEST_F(RangeDelAggregatorTest, TruncatedIterFullyCutTombstones) { - auto range_del_iter = - MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, - kMaxSequenceNumber)); - - InternalKey smallest("f", 7, kTypeValue); - InternalKey largest("i", 9, kTypeValue); - TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, - &smallest, &largest); - - VerifyIterator( - &iter, bytewise_icmp, - {{InternalValue("f", 7, kTypeMaxValid), UncutEndpoint("g"), 8}}); - - VerifySeek( - &iter, bytewise_icmp, - {{"d", InternalValue("f", 7, kTypeMaxValid), UncutEndpoint("g"), 8}, - {"f", InternalValue("f", 7, kTypeMaxValid), UncutEndpoint("g"), 8}, - {"j", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}}); - - VerifySeekForPrev( - &iter, bytewise_icmp, - {{"d", InternalValue("", 0, kTypeRangeDeletion), UncutEndpoint(""), 0, - true /* invalid */}, - {"f", InternalValue("f", 7, kTypeMaxValid), UncutEndpoint("g"), 8}, - {"j", InternalValue("f", 7, kTypeMaxValid), UncutEndpoint("g"), 8}}); -} - -TEST_F(RangeDelAggregatorTest, SingleIterInAggregator) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, {"c", "g", 8}}); - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, - kMaxSequenceNumber)); - - ReadRangeDelAggregator range_del_agg(&bytewise_icmp, kMaxSequenceNumber); - range_del_agg.AddTombstones(std::move(input_iter)); - - VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false}, - {InternalValue("b", 9), true}, - {InternalValue("d", 9), true}, - {InternalValue("e", 7), true}, - {InternalValue("g", 7), false}}); - - VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, - {"_", "a", true}, - {"a", "c", true}, - {"d", "f", true}, - {"g", "l", false}}); -} - -TEST_F(RangeDelAggregatorTest, MultipleItersInAggregator) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - ReadRangeDelAggregator range_del_agg(&bytewise_icmp, kMaxSequenceNumber); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), true}, - {InternalValue("b", 19), false}, - {InternalValue("b", 9), true}, - {InternalValue("d", 9), true}, - {InternalValue("e", 7), true}, - {InternalValue("g", 7), false}, - {InternalValue("h", 24), true}, - {InternalValue("i", 24), false}, - {InternalValue("ii", 14), true}, - {InternalValue("j", 14), false}}); - - VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, - {"_", "a", true}, - {"a", "c", true}, - {"d", "f", true}, - {"g", "l", true}, - {"x", "y", false}}); -} - -TEST_F(RangeDelAggregatorTest, MultipleItersInAggregatorWithUpperBound) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - ReadRangeDelAggregator range_del_agg(&bytewise_icmp, 19); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - 19 /* snapshot */)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false}, - {InternalValue("a", 9), true}, - {InternalValue("b", 9), true}, - {InternalValue("d", 9), true}, - {InternalValue("e", 7), true}, - {InternalValue("g", 7), false}, - {InternalValue("h", 24), false}, - {InternalValue("i", 24), false}, - {InternalValue("ii", 14), true}, - {InternalValue("j", 14), false}}); - - VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, - {"_", "a", true}, - {"a", "c", true}, - {"d", "f", true}, - {"g", "l", true}, - {"x", "y", false}}); -} - -TEST_F(RangeDelAggregatorTest, MultipleTruncatedItersInAggregator) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}}); - std::vector> iter_bounds = { - {InternalKey("a", 4, kTypeValue), - InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)}, - {InternalKey("m", 20, kTypeValue), - InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)}, - {InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}}; - - ReadRangeDelAggregator range_del_agg(&bytewise_icmp, 19); - for (size_t i = 0; i < fragment_lists.size(); i++) { - const auto& fragment_list = fragment_lists[i]; - const auto& bounds = iter_bounds[i]; - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - 19 /* snapshot */)); - range_del_agg.AddTombstones(std::move(input_iter), &bounds.first, - &bounds.second); - } - - VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false}, - {InternalValue("a", 9), false}, - {InternalValue("a", 4), true}, - {InternalValue("m", 10), false}, - {InternalValue("m", 9), true}, - {InternalValue("x", 10), false}, - {InternalValue("x", 9), false}, - {InternalValue("x", 5), true}, - {InternalValue("z", 9), false}}); - - VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, - {"_", "a", true}, - {"a", "n", true}, - {"l", "x", true}, - {"w", "z", true}, - {"zzz", "zz", false}, - {"zz", "zzz", false}}); -} - -TEST_F(RangeDelAggregatorTest, MultipleTruncatedItersInAggregatorSameLevel) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}}); - std::vector> iter_bounds = { - {InternalKey("a", 4, kTypeValue), - InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)}, - {InternalKey("m", 20, kTypeValue), - InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)}, - {InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}}; - - ReadRangeDelAggregator range_del_agg(&bytewise_icmp, 19); - - auto add_iter_to_agg = [&](size_t i) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_lists[i].get(), - bytewise_icmp, 19 /* snapshot */)); - range_del_agg.AddTombstones(std::move(input_iter), &iter_bounds[i].first, - &iter_bounds[i].second); - }; - - add_iter_to_agg(0); - VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false}, - {InternalValue("a", 9), false}, - {InternalValue("a", 4), true}}); - - add_iter_to_agg(1); - VerifyShouldDelete(&range_del_agg, {{InternalValue("m", 10), false}, - {InternalValue("m", 9), true}}); - - add_iter_to_agg(2); - VerifyShouldDelete(&range_del_agg, {{InternalValue("x", 10), false}, - {InternalValue("x", 9), false}, - {InternalValue("x", 5), true}, - {InternalValue("z", 9), false}}); - - VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, - {"_", "a", true}, - {"a", "n", true}, - {"l", "x", true}, - {"w", "z", true}, - {"zzz", "zz", false}, - {"zz", "zzz", false}}); -} - -TEST_F(RangeDelAggregatorTest, CompactionAggregatorNoSnapshots) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - std::vector snapshots; - CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), true}, - {InternalValue("b", 19), false}, - {InternalValue("b", 9), true}, - {InternalValue("d", 9), true}, - {InternalValue("e", 7), true}, - {InternalValue("g", 7), false}, - {InternalValue("h", 24), true}, - {InternalValue("i", 24), false}, - {InternalValue("ii", 14), true}, - {InternalValue("j", 14), false}}); - - auto range_del_compaction_iter = range_del_agg.NewIterator(); - VerifyFragmentedRangeDels(range_del_compaction_iter.get(), {{"a", "b", 20}, - {"b", "c", 10}, - {"c", "e", 10}, - {"e", "g", 8}, - {"h", "i", 25}, - {"ii", "j", 15}}); -} - -TEST_F(RangeDelAggregatorTest, CompactionAggregatorWithSnapshots) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - std::vector snapshots{9, 19}; - CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - VerifyShouldDelete( - &range_del_agg, - { - {InternalValue("a", 19), false}, // [10, 19] - {InternalValue("a", 9), false}, // [0, 9] - {InternalValue("b", 9), false}, // [0, 9] - {InternalValue("d", 9), false}, // [0, 9] - {InternalValue("d", 7), true}, // [0, 9] - {InternalValue("e", 7), true}, // [0, 9] - {InternalValue("g", 7), false}, // [0, 9] - {InternalValue("h", 24), true}, // [20, kMaxSequenceNumber] - {InternalValue("i", 24), false}, // [20, kMaxSequenceNumber] - {InternalValue("ii", 14), true}, // [10, 19] - {InternalValue("j", 14), false} // [10, 19] - }); - - auto range_del_compaction_iter = range_del_agg.NewIterator(); - VerifyFragmentedRangeDels(range_del_compaction_iter.get(), {{"a", "b", 20}, - {"a", "b", 10}, - {"b", "c", 10}, - {"c", "e", 10}, - {"c", "e", 8}, - {"e", "g", 8}, - {"h", "i", 25}, - {"ii", "j", 15}}); -} - -TEST_F(RangeDelAggregatorTest, CompactionAggregatorEmptyIteratorLeft) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - std::vector snapshots{9, 19}; - CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - Slice start("_"); - Slice end("__"); -} - -TEST_F(RangeDelAggregatorTest, CompactionAggregatorEmptyIteratorRight) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - std::vector snapshots{9, 19}; - CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - InternalKey start_buf("p", 0, kTypeRangeDeletion); - InternalKey end_buf("q", 0, kTypeRangeDeletion); - Slice start = start_buf.Encode(); - Slice end = end_buf.Encode(); - auto range_del_compaction_iter = range_del_agg.NewIterator(&start, &end); - VerifyFragmentedRangeDels(range_del_compaction_iter.get(), {}); -} - -TEST_F(RangeDelAggregatorTest, CompactionAggregatorBoundedIterator) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "e", 10}, {"c", "g", 8}}, - {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); - - std::vector snapshots{9, 19}; - CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - InternalKey start_buf("bb", 0, kTypeRangeDeletion); - InternalKey end_buf("e", 9, kTypeRangeDeletion); - Slice start = start_buf.Encode(); - Slice end = end_buf.Encode(); - auto range_del_compaction_iter = range_del_agg.NewIterator(&start, &end); - VerifyFragmentedRangeDels(range_del_compaction_iter.get(), - {{"a", "c", 10}, {"c", "e", 10}, {"c", "e", 8}}); -} - -TEST_F(RangeDelAggregatorTest, - CompactionAggregatorBoundedIteratorExtraFragments) { - auto fragment_lists = MakeFragmentedTombstoneLists( - {{{"a", "d", 10}, {"c", "g", 8}}, - {{"b", "c", 20}, {"d", "f", 30}, {"h", "i", 25}, {"ii", "j", 15}}}); - - std::vector snapshots{9, 19}; - CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); - for (const auto& fragment_list : fragment_lists) { - std::unique_ptr input_iter( - new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, - kMaxSequenceNumber)); - range_del_agg.AddTombstones(std::move(input_iter)); - } - - InternalKey start_buf("bb", 0, kTypeRangeDeletion); - InternalKey end_buf("e", 0, kTypeRangeDeletion); - Slice start = start_buf.Encode(); - Slice end = end_buf.Encode(); - auto range_del_compaction_iter = range_del_agg.NewIterator(&start, &end); - VerifyFragmentedRangeDels(range_del_compaction_iter.get(), {{"a", "b", 10}, - {"b", "c", 20}, - {"b", "c", 10}, - {"c", "d", 10}, - {"c", "d", 8}, - {"d", "f", 30}, - {"d", "f", 8}, - {"f", "g", 8}}); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/range_tombstone_fragmenter_test.cc b/db/range_tombstone_fragmenter_test.cc deleted file mode 100644 index eee2ca2ca..000000000 --- a/db/range_tombstone_fragmenter_test.cc +++ /dev/null @@ -1,555 +0,0 @@ -// Copyright (c) 2018-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/range_tombstone_fragmenter.h" - -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "rocksdb/comparator.h" -#include "test_util/testutil.h" -#include "util/vector_iterator.h" - -namespace ROCKSDB_NAMESPACE { - -class RangeTombstoneFragmenterTest : public testing::Test {}; - -namespace { - -static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator()); - -std::unique_ptr MakeRangeDelIter( - const std::vector& range_dels) { - std::vector keys, values; - for (const auto& range_del : range_dels) { - auto key_and_value = range_del.Serialize(); - keys.push_back(key_and_value.first.Encode().ToString()); - values.push_back(key_and_value.second.ToString()); - } - return std::unique_ptr( - new VectorIterator(keys, values, &bytewise_icmp)); -} - -void CheckIterPosition(const RangeTombstone& tombstone, - const FragmentedRangeTombstoneIterator* iter) { - // Test InternalIterator interface. - EXPECT_EQ(tombstone.start_key_, ExtractUserKey(iter->key())); - EXPECT_EQ(tombstone.end_key_, iter->value()); - EXPECT_EQ(tombstone.seq_, iter->seq()); - - // Test FragmentedRangeTombstoneIterator interface. - EXPECT_EQ(tombstone.start_key_, iter->start_key()); - EXPECT_EQ(tombstone.end_key_, iter->end_key()); - EXPECT_EQ(tombstone.seq_, GetInternalKeySeqno(iter->key())); -} - -void VerifyFragmentedRangeDels( - FragmentedRangeTombstoneIterator* iter, - const std::vector& expected_tombstones) { - iter->SeekToFirst(); - for (size_t i = 0; i < expected_tombstones.size(); i++, iter->Next()) { - ASSERT_TRUE(iter->Valid()); - CheckIterPosition(expected_tombstones[i], iter); - } - EXPECT_FALSE(iter->Valid()); -} - -void VerifyVisibleTombstones( - FragmentedRangeTombstoneIterator* iter, - const std::vector& expected_tombstones) { - iter->SeekToTopFirst(); - for (size_t i = 0; i < expected_tombstones.size(); i++, iter->TopNext()) { - ASSERT_TRUE(iter->Valid()); - CheckIterPosition(expected_tombstones[i], iter); - } - EXPECT_FALSE(iter->Valid()); -} - -struct SeekTestCase { - Slice seek_target; - RangeTombstone expected_position; - bool out_of_range; -}; - -void VerifySeek(FragmentedRangeTombstoneIterator* iter, - const std::vector& cases) { - for (const auto& testcase : cases) { - iter->Seek(testcase.seek_target); - if (testcase.out_of_range) { - ASSERT_FALSE(iter->Valid()); - } else { - ASSERT_TRUE(iter->Valid()); - CheckIterPosition(testcase.expected_position, iter); - } - } -} - -void VerifySeekForPrev(FragmentedRangeTombstoneIterator* iter, - const std::vector& cases) { - for (const auto& testcase : cases) { - iter->SeekForPrev(testcase.seek_target); - if (testcase.out_of_range) { - ASSERT_FALSE(iter->Valid()); - } else { - ASSERT_TRUE(iter->Valid()); - CheckIterPosition(testcase.expected_position, iter); - } - } -} - -struct MaxCoveringTombstoneSeqnumTestCase { - Slice user_key; - SequenceNumber result; -}; - -void VerifyMaxCoveringTombstoneSeqnum( - FragmentedRangeTombstoneIterator* iter, - const std::vector& cases) { - for (const auto& testcase : cases) { - EXPECT_EQ(testcase.result, - iter->MaxCoveringTombstoneSeqnum(testcase.user_key)); - } -} - -} // anonymous namespace - -TEST_F(RangeTombstoneFragmenterTest, NonOverlappingTombstones) { - auto range_del_iter = MakeRangeDelIter({{"a", "b", 10}, {"c", "d", 5}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); - VerifyFragmentedRangeDels(&iter, {{"a", "b", 10}, {"c", "d", 5}}); - VerifyMaxCoveringTombstoneSeqnum(&iter, - {{"", 0}, {"a", 10}, {"b", 0}, {"c", 5}}); -} - -TEST_F(RangeTombstoneFragmenterTest, OverlappingTombstones) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, {"c", "g", 15}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); - VerifyFragmentedRangeDels( - &iter, {{"a", "c", 10}, {"c", "e", 15}, {"c", "e", 10}, {"e", "g", 15}}); - VerifyMaxCoveringTombstoneSeqnum(&iter, - {{"a", 10}, {"c", 15}, {"e", 15}, {"g", 0}}); -} - -TEST_F(RangeTombstoneFragmenterTest, ContiguousTombstones) { - auto range_del_iter = MakeRangeDelIter( - {{"a", "c", 10}, {"c", "e", 20}, {"c", "e", 5}, {"e", "g", 15}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); - VerifyFragmentedRangeDels( - &iter, {{"a", "c", 10}, {"c", "e", 20}, {"c", "e", 5}, {"e", "g", 15}}); - VerifyMaxCoveringTombstoneSeqnum(&iter, - {{"a", 10}, {"c", 20}, {"e", 15}, {"g", 0}}); -} - -TEST_F(RangeTombstoneFragmenterTest, RepeatedStartAndEndKey) { - auto range_del_iter = - MakeRangeDelIter({{"a", "c", 10}, {"a", "c", 7}, {"a", "c", 3}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); - VerifyFragmentedRangeDels(&iter, - {{"a", "c", 10}, {"a", "c", 7}, {"a", "c", 3}}); - VerifyMaxCoveringTombstoneSeqnum(&iter, {{"a", 10}, {"b", 10}, {"c", 0}}); -} - -TEST_F(RangeTombstoneFragmenterTest, RepeatedStartKeyDifferentEndKeys) { - auto range_del_iter = - MakeRangeDelIter({{"a", "e", 10}, {"a", "g", 7}, {"a", "c", 3}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); - VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, - {"a", "c", 7}, - {"a", "c", 3}, - {"c", "e", 10}, - {"c", "e", 7}, - {"e", "g", 7}}); - VerifyMaxCoveringTombstoneSeqnum(&iter, - {{"a", 10}, {"c", 10}, {"e", 7}, {"g", 0}}); -} - -TEST_F(RangeTombstoneFragmenterTest, RepeatedStartKeyMixedEndKeys) { - auto range_del_iter = MakeRangeDelIter({{"a", "c", 30}, - {"a", "g", 20}, - {"a", "e", 10}, - {"a", "g", 7}, - {"a", "c", 3}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); - VerifyFragmentedRangeDels(&iter, {{"a", "c", 30}, - {"a", "c", 20}, - {"a", "c", 10}, - {"a", "c", 7}, - {"a", "c", 3}, - {"c", "e", 20}, - {"c", "e", 10}, - {"c", "e", 7}, - {"e", "g", 20}, - {"e", "g", 7}}); - VerifyMaxCoveringTombstoneSeqnum(&iter, - {{"a", 30}, {"c", 20}, {"e", 20}, {"g", 0}}); -} - -TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"c", "g", 8}, - {"c", "i", 6}, - {"j", "n", 4}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, - 9 /* upper_bound */); - FragmentedRangeTombstoneIterator iter3(&fragment_list, bytewise_icmp, - 7 /* upper_bound */); - FragmentedRangeTombstoneIterator iter4(&fragment_list, bytewise_icmp, - 5 /* upper_bound */); - FragmentedRangeTombstoneIterator iter5(&fragment_list, bytewise_icmp, - 3 /* upper_bound */); - for (auto* iter : {&iter1, &iter2, &iter3, &iter4, &iter5}) { - VerifyFragmentedRangeDels(iter, {{"a", "c", 10}, - {"c", "e", 10}, - {"c", "e", 8}, - {"c", "e", 6}, - {"e", "g", 8}, - {"e", "g", 6}, - {"g", "i", 6}, - {"j", "l", 4}, - {"j", "l", 2}, - {"l", "n", 4}}); - } - - ASSERT_EQ(0, iter1.lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, iter1.upper_bound()); - VerifyVisibleTombstones(&iter1, {{"a", "c", 10}, - {"c", "e", 10}, - {"e", "g", 8}, - {"g", "i", 6}, - {"j", "l", 4}, - {"l", "n", 4}}); - VerifyMaxCoveringTombstoneSeqnum( - &iter1, {{"a", 10}, {"c", 10}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}}); - - ASSERT_EQ(0, iter2.lower_bound()); - ASSERT_EQ(9, iter2.upper_bound()); - VerifyVisibleTombstones(&iter2, {{"c", "e", 8}, - {"e", "g", 8}, - {"g", "i", 6}, - {"j", "l", 4}, - {"l", "n", 4}}); - VerifyMaxCoveringTombstoneSeqnum( - &iter2, {{"a", 0}, {"c", 8}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}}); - - ASSERT_EQ(0, iter3.lower_bound()); - ASSERT_EQ(7, iter3.upper_bound()); - VerifyVisibleTombstones(&iter3, {{"c", "e", 6}, - {"e", "g", 6}, - {"g", "i", 6}, - {"j", "l", 4}, - {"l", "n", 4}}); - VerifyMaxCoveringTombstoneSeqnum( - &iter3, {{"a", 0}, {"c", 6}, {"e", 6}, {"i", 0}, {"j", 4}, {"m", 4}}); - - ASSERT_EQ(0, iter4.lower_bound()); - ASSERT_EQ(5, iter4.upper_bound()); - VerifyVisibleTombstones(&iter4, {{"j", "l", 4}, {"l", "n", 4}}); - VerifyMaxCoveringTombstoneSeqnum( - &iter4, {{"a", 0}, {"c", 0}, {"e", 0}, {"i", 0}, {"j", 4}, {"m", 4}}); - - ASSERT_EQ(0, iter5.lower_bound()); - ASSERT_EQ(3, iter5.upper_bound()); - VerifyVisibleTombstones(&iter5, {{"j", "l", 2}}); - VerifyMaxCoveringTombstoneSeqnum( - &iter5, {{"a", 0}, {"c", 0}, {"e", 0}, {"i", 0}, {"j", 2}, {"m", 0}}); -} - -TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyUnordered) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"j", "n", 4}, - {"c", "i", 6}, - {"c", "g", 8}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - 9 /* upper_bound */); - ASSERT_EQ(0, iter.lower_bound()); - ASSERT_EQ(9, iter.upper_bound()); - VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, - {"c", "e", 10}, - {"c", "e", 8}, - {"c", "e", 6}, - {"e", "g", 8}, - {"e", "g", 6}, - {"g", "i", 6}, - {"j", "l", 4}, - {"j", "l", 2}, - {"l", "n", 4}}); - VerifyMaxCoveringTombstoneSeqnum( - &iter, {{"a", 0}, {"c", 8}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}}); -} - -TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyForCompaction) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"j", "n", 4}, - {"c", "i", 6}, - {"c", "g", 8}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list( - std::move(range_del_iter), bytewise_icmp, true /* for_compaction */, - {} /* snapshots */); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber /* upper_bound */); - VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, - {"c", "e", 10}, - {"e", "g", 8}, - {"g", "i", 6}, - {"j", "l", 4}, - {"l", "n", 4}}); -} - -TEST_F(RangeTombstoneFragmenterTest, - OverlapAndRepeatedStartKeyForCompactionWithSnapshot) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"j", "n", 4}, - {"c", "i", 6}, - {"c", "g", 8}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list( - std::move(range_del_iter), bytewise_icmp, true /* for_compaction */, - {9, 20} /* snapshots */); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber /* upper_bound */); - VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, - {"c", "e", 10}, - {"c", "e", 8}, - {"e", "g", 8}, - {"g", "i", 6}, - {"j", "l", 4}, - {"l", "n", 4}}); -} - -TEST_F(RangeTombstoneFragmenterTest, IteratorSplitNoSnapshots) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"j", "n", 4}, - {"c", "i", 6}, - {"c", "g", 8}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber /* upper_bound */); - - auto split_iters = iter.SplitBySnapshot({} /* snapshots */); - ASSERT_EQ(1, split_iters.size()); - - auto* split_iter = split_iters[kMaxSequenceNumber].get(); - ASSERT_EQ(0, split_iter->lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, split_iter->upper_bound()); - VerifyVisibleTombstones(split_iter, {{"a", "c", 10}, - {"c", "e", 10}, - {"e", "g", 8}, - {"g", "i", 6}, - {"j", "l", 4}, - {"l", "n", 4}}); -} - -TEST_F(RangeTombstoneFragmenterTest, IteratorSplitWithSnapshots) { - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"j", "n", 4}, - {"c", "i", 6}, - {"c", "g", 8}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber /* upper_bound */); - - auto split_iters = iter.SplitBySnapshot({3, 5, 7, 9} /* snapshots */); - ASSERT_EQ(5, split_iters.size()); - - auto* split_iter1 = split_iters[3].get(); - ASSERT_EQ(0, split_iter1->lower_bound()); - ASSERT_EQ(3, split_iter1->upper_bound()); - VerifyVisibleTombstones(split_iter1, {{"j", "l", 2}}); - - auto* split_iter2 = split_iters[5].get(); - ASSERT_EQ(4, split_iter2->lower_bound()); - ASSERT_EQ(5, split_iter2->upper_bound()); - VerifyVisibleTombstones(split_iter2, {{"j", "l", 4}, {"l", "n", 4}}); - - auto* split_iter3 = split_iters[7].get(); - ASSERT_EQ(6, split_iter3->lower_bound()); - ASSERT_EQ(7, split_iter3->upper_bound()); - VerifyVisibleTombstones(split_iter3, - {{"c", "e", 6}, {"e", "g", 6}, {"g", "i", 6}}); - - auto* split_iter4 = split_iters[9].get(); - ASSERT_EQ(8, split_iter4->lower_bound()); - ASSERT_EQ(9, split_iter4->upper_bound()); - VerifyVisibleTombstones(split_iter4, {{"c", "e", 8}, {"e", "g", 8}}); - - auto* split_iter5 = split_iters[kMaxSequenceNumber].get(); - ASSERT_EQ(10, split_iter5->lower_bound()); - ASSERT_EQ(kMaxSequenceNumber, split_iter5->upper_bound()); - VerifyVisibleTombstones(split_iter5, {{"a", "c", 10}, {"c", "e", 10}}); -} - -TEST_F(RangeTombstoneFragmenterTest, SeekStartKey) { - // Same tombstones as OverlapAndRepeatedStartKey. - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"c", "g", 8}, - {"c", "i", 6}, - {"j", "n", 4}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - - FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - VerifySeek( - &iter1, - {{"a", {"a", "c", 10}}, {"e", {"e", "g", 8}}, {"l", {"l", "n", 4}}}); - VerifySeekForPrev( - &iter1, - {{"a", {"a", "c", 10}}, {"e", {"e", "g", 8}}, {"l", {"l", "n", 4}}}); - - FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, - 3 /* upper_bound */); - VerifySeek(&iter2, {{"a", {"j", "l", 2}}, - {"e", {"j", "l", 2}}, - {"l", {}, true /* out of range */}}); - VerifySeekForPrev(&iter2, {{"a", {}, true /* out of range */}, - {"e", {}, true /* out of range */}, - {"l", {"j", "l", 2}}}); -} - -TEST_F(RangeTombstoneFragmenterTest, SeekCovered) { - // Same tombstones as OverlapAndRepeatedStartKey. - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"c", "g", 8}, - {"c", "i", 6}, - {"j", "n", 4}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - - FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - VerifySeek( - &iter1, - {{"b", {"a", "c", 10}}, {"f", {"e", "g", 8}}, {"m", {"l", "n", 4}}}); - VerifySeekForPrev( - &iter1, - {{"b", {"a", "c", 10}}, {"f", {"e", "g", 8}}, {"m", {"l", "n", 4}}}); - - FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, - 3 /* upper_bound */); - VerifySeek(&iter2, {{"b", {"j", "l", 2}}, - {"f", {"j", "l", 2}}, - {"m", {}, true /* out of range */}}); - VerifySeekForPrev(&iter2, {{"b", {}, true /* out of range */}, - {"f", {}, true /* out of range */}, - {"m", {"j", "l", 2}}}); -} - -TEST_F(RangeTombstoneFragmenterTest, SeekEndKey) { - // Same tombstones as OverlapAndRepeatedStartKey. - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"c", "g", 8}, - {"c", "i", 6}, - {"j", "n", 4}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - - FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - VerifySeek(&iter1, {{"c", {"c", "e", 10}}, - {"g", {"g", "i", 6}}, - {"i", {"j", "l", 4}}, - {"n", {}, true /* out of range */}}); - VerifySeekForPrev(&iter1, {{"c", {"c", "e", 10}}, - {"g", {"g", "i", 6}}, - {"i", {"g", "i", 6}}, - {"n", {"l", "n", 4}}}); - - FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, - 3 /* upper_bound */); - VerifySeek(&iter2, {{"c", {"j", "l", 2}}, - {"g", {"j", "l", 2}}, - {"i", {"j", "l", 2}}, - {"n", {}, true /* out of range */}}); - VerifySeekForPrev(&iter2, {{"c", {}, true /* out of range */}, - {"g", {}, true /* out of range */}, - {"i", {}, true /* out of range */}, - {"n", {"j", "l", 2}}}); -} - -TEST_F(RangeTombstoneFragmenterTest, SeekOutOfBounds) { - // Same tombstones as OverlapAndRepeatedStartKey. - auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, - {"c", "g", 8}, - {"c", "i", 6}, - {"j", "n", 4}, - {"j", "l", 2}}); - - FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), - bytewise_icmp); - - FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, - kMaxSequenceNumber); - VerifySeek(&iter, {{"", {"a", "c", 10}}, {"z", {}, true /* out of range */}}); - VerifySeekForPrev(&iter, - {{"", {}, true /* out of range */}, {"z", {"l", "n", 4}}}); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/repair_test.cc b/db/repair_test.cc deleted file mode 100644 index 47482699d..000000000 --- a/db/repair_test.cc +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/options.h" - -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "file/file_util.h" -#include "rocksdb/comparator.h" -#include "rocksdb/db.h" -#include "rocksdb/transaction_log.h" -#include "table/unique_id_impl.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class RepairTest : public DBTestBase { - public: - RepairTest() : DBTestBase("repair_test", /*env_do_fsync=*/true) {} - - Status GetFirstSstPath(std::string* first_sst_path) { - assert(first_sst_path != nullptr); - first_sst_path->clear(); - uint64_t manifest_size; - std::vector files; - Status s = db_->GetLiveFiles(files, &manifest_size); - if (s.ok()) { - auto sst_iter = - std::find_if(files.begin(), files.end(), [](const std::string& file) { - uint64_t number; - FileType type; - bool ok = ParseFileName(file, &number, &type); - return ok && type == kTableFile; - }); - *first_sst_path = sst_iter == files.end() ? "" : dbname_ + *sst_iter; - } - return s; - } - - void ReopenWithSstIdVerify() { - std::atomic_int verify_passed{0}; - SyncPoint::GetInstance()->SetCallBack( - "BlockBasedTable::Open::PassedVerifyUniqueId", [&](void* arg) { - // override job status - auto id = static_cast(arg); - assert(*id != kNullUniqueId64x2); - verify_passed++; - }); - SyncPoint::GetInstance()->EnableProcessing(); - auto options = CurrentOptions(); - options.verify_sst_unique_id_in_manifest = true; - Reopen(options); - - ASSERT_GT(verify_passed, 0); - SyncPoint::GetInstance()->DisableProcessing(); - } - - std::vector GetLevelFileMetadatas(int level, int cf = 0) { - VersionSet* const versions = dbfull()->GetVersionSet(); - assert(versions); - ColumnFamilyData* const cfd = - versions->GetColumnFamilySet()->GetColumnFamily(cf); - assert(cfd); - Version* const current = cfd->current(); - assert(current); - VersionStorageInfo* const storage_info = current->storage_info(); - assert(storage_info); - return storage_info->LevelFiles(level); - } -}; - -TEST_F(RepairTest, SortRepairedDBL0ByEpochNumber) { - Options options = CurrentOptions(); - DestroyAndReopen(options); - - ASSERT_OK(Put("k1", "oldest")); - ASSERT_OK(Put("k1", "older")); - ASSERT_OK(Flush()); - MoveFilesToLevel(1); - - ASSERT_OK(Put("k1", "old")); - ASSERT_OK(Flush()); - - ASSERT_OK(Put("k1", "new")); - - std::vector level0_files = GetLevelFileMetadatas(0 /* level*/); - ASSERT_EQ(level0_files.size(), 1); - ASSERT_EQ(level0_files[0]->epoch_number, 2); - std::vector level1_files = GetLevelFileMetadatas(1 /* level*/); - ASSERT_EQ(level1_files.size(), 1); - ASSERT_EQ(level1_files[0]->epoch_number, 1); - - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - ASSERT_OK(env_->DeleteFile(manifest_path)); - - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - ReopenWithSstIdVerify(); - - EXPECT_EQ(Get("k1"), "new"); - - level0_files = GetLevelFileMetadatas(0 /* level*/); - ASSERT_EQ(level0_files.size(), 3); - EXPECT_EQ(level0_files[0]->epoch_number, 3); - EXPECT_EQ(level0_files[1]->epoch_number, 2); - EXPECT_EQ(level0_files[2]->epoch_number, 1); - level1_files = GetLevelFileMetadatas(1 /* level*/); - ASSERT_EQ(level1_files.size(), 0); -} - -TEST_F(RepairTest, LostManifest) { - // Add a couple SST files, delete the manifest, and verify RepairDB() saves - // the day. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - // Need to get path before Close() deletes db_, but delete it after Close() to - // ensure Close() didn't change the manifest. - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - ASSERT_OK(env_->DeleteFile(manifest_path)); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - ReopenWithSstIdVerify(); - - ASSERT_EQ(Get("key"), "val"); - ASSERT_EQ(Get("key2"), "val2"); -} - -TEST_F(RepairTest, LostManifestMoreDbFeatures) { - // Add a couple SST files, delete the manifest, and verify RepairDB() saves - // the day. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Put("key3", "val3")); - ASSERT_OK(Put("key4", "val4")); - ASSERT_OK(Flush()); - // Test an SST file containing only a range tombstone - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "key2", - "key3z")); - ASSERT_OK(Flush()); - // Need to get path before Close() deletes db_, but delete it after Close() to - // ensure Close() didn't change the manifest. - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - ASSERT_OK(env_->DeleteFile(manifest_path)); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - - // repair from sst should work with unique_id verification - ReopenWithSstIdVerify(); - - ASSERT_EQ(Get("key"), "val"); - ASSERT_EQ(Get("key2"), "NOT_FOUND"); - ASSERT_EQ(Get("key3"), "NOT_FOUND"); - ASSERT_EQ(Get("key4"), "val4"); -} - -TEST_F(RepairTest, CorruptManifest) { - // Manifest is in an invalid format. Expect a full recovery. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - // Need to get path before Close() deletes db_, but overwrite it after Close() - // to ensure Close() didn't change the manifest. - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - - ASSERT_OK(CreateFile(env_->GetFileSystem(), manifest_path, "blah", - false /* use_fsync */)); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - - ReopenWithSstIdVerify(); - - ASSERT_EQ(Get("key"), "val"); - ASSERT_EQ(Get("key2"), "val2"); -} - -TEST_F(RepairTest, IncompleteManifest) { - // In this case, the manifest is valid but does not reference all of the SST - // files. Expect a full recovery. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - std::string orig_manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - CopyFile(orig_manifest_path, orig_manifest_path + ".tmp"); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - // Need to get path before Close() deletes db_, but overwrite it after Close() - // to ensure Close() didn't change the manifest. - std::string new_manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - - Close(); - ASSERT_OK(env_->FileExists(new_manifest_path)); - // Replace the manifest with one that is only aware of the first SST file. - CopyFile(orig_manifest_path + ".tmp", new_manifest_path); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - - ReopenWithSstIdVerify(); - - ASSERT_EQ(Get("key"), "val"); - ASSERT_EQ(Get("key2"), "val2"); -} - -TEST_F(RepairTest, PostRepairSstFileNumbering) { - // Verify after a DB is repaired, new files will be assigned higher numbers - // than old files. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - uint64_t pre_repair_file_num = dbfull()->TEST_Current_Next_FileNo(); - Close(); - - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - - ReopenWithSstIdVerify(); - - uint64_t post_repair_file_num = dbfull()->TEST_Current_Next_FileNo(); - ASSERT_GE(post_repair_file_num, pre_repair_file_num); -} - -TEST_F(RepairTest, LostSst) { - // Delete one of the SST files but preserve the manifest that refers to it, - // then verify the DB is still usable for the intact SST. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - std::string sst_path; - ASSERT_OK(GetFirstSstPath(&sst_path)); - ASSERT_FALSE(sst_path.empty()); - ASSERT_OK(env_->DeleteFile(sst_path)); - - Close(); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - ReopenWithSstIdVerify(); - - // Exactly one of the key-value pairs should be in the DB now. - ASSERT_TRUE((Get("key") == "val") != (Get("key2") == "val2")); -} - -TEST_F(RepairTest, CorruptSst) { - // Corrupt one of the SST files but preserve the manifest that refers to it, - // then verify the DB is still usable for the intact SST. - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - ASSERT_OK(Put("key2", "val2")); - ASSERT_OK(Flush()); - std::string sst_path; - ASSERT_OK(GetFirstSstPath(&sst_path)); - ASSERT_FALSE(sst_path.empty()); - - ASSERT_OK(CreateFile(env_->GetFileSystem(), sst_path, "blah", - false /* use_fsync */)); - - Close(); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - ReopenWithSstIdVerify(); - - // Exactly one of the key-value pairs should be in the DB now. - ASSERT_TRUE((Get("key") == "val") != (Get("key2") == "val2")); -} - -TEST_F(RepairTest, UnflushedSst) { - // This test case invokes repair while some data is unflushed, then verifies - // that data is in the db. - ASSERT_OK(Put("key", "val")); - VectorLogPtr wal_files; - ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files)); - ASSERT_EQ(wal_files.size(), 1); - { - uint64_t total_ssts_size; - std::unordered_map sst_files; - ASSERT_OK(GetAllDataFiles(kTableFile, &sst_files, &total_ssts_size)); - ASSERT_EQ(total_ssts_size, 0); - } - // Need to get path before Close() deletes db_, but delete it after Close() to - // ensure Close() didn't change the manifest. - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - ASSERT_OK(env_->DeleteFile(manifest_path)); - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - ReopenWithSstIdVerify(); - - ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files)); - ASSERT_EQ(wal_files.size(), 0); - { - uint64_t total_ssts_size; - std::unordered_map sst_files; - ASSERT_OK(GetAllDataFiles(kTableFile, &sst_files, &total_ssts_size)); - ASSERT_GT(total_ssts_size, 0); - } - ASSERT_EQ(Get("key"), "val"); -} - -TEST_F(RepairTest, SeparateWalDir) { - do { - Options options = CurrentOptions(); - DestroyAndReopen(options); - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Put("foo", "bar")); - VectorLogPtr wal_files; - ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files)); - ASSERT_EQ(wal_files.size(), 1); - { - uint64_t total_ssts_size; - std::unordered_map sst_files; - ASSERT_OK(GetAllDataFiles(kTableFile, &sst_files, &total_ssts_size)); - ASSERT_EQ(total_ssts_size, 0); - } - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - ASSERT_OK(env_->DeleteFile(manifest_path)); - ASSERT_OK(RepairDB(dbname_, options)); - - // make sure that all WALs are converted to SSTables. - options.wal_dir = ""; - - ReopenWithSstIdVerify(); - ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files)); - ASSERT_EQ(wal_files.size(), 0); - { - uint64_t total_ssts_size; - std::unordered_map sst_files; - ASSERT_OK(GetAllDataFiles(kTableFile, &sst_files, &total_ssts_size)); - ASSERT_GT(total_ssts_size, 0); - } - ASSERT_EQ(Get("key"), "val"); - ASSERT_EQ(Get("foo"), "bar"); - - } while (ChangeWalOptions()); -} - -TEST_F(RepairTest, RepairMultipleColumnFamilies) { - // Verify repair logic associates SST files with their original column - // families. - const int kNumCfs = 3; - const int kEntriesPerCf = 2; - DestroyAndReopen(CurrentOptions()); - CreateAndReopenWithCF({"pikachu1", "pikachu2"}, CurrentOptions()); - for (int i = 0; i < kNumCfs; ++i) { - for (int j = 0; j < kEntriesPerCf; ++j) { - ASSERT_OK(Put(i, "key" + std::to_string(j), "val" + std::to_string(j))); - if (j == kEntriesPerCf - 1 && i == kNumCfs - 1) { - // Leave one unflushed so we can verify WAL entries are properly - // associated with column families. - continue; - } - ASSERT_OK(Flush(i)); - } - } - - // Need to get path before Close() deletes db_, but delete it after Close() to - // ensure Close() doesn't re-create the manifest. - std::string manifest_path = - DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); - Close(); - ASSERT_OK(env_->FileExists(manifest_path)); - ASSERT_OK(env_->DeleteFile(manifest_path)); - - ASSERT_OK(RepairDB(dbname_, CurrentOptions())); - - ReopenWithColumnFamilies({"default", "pikachu1", "pikachu2"}, - CurrentOptions()); - for (int i = 0; i < kNumCfs; ++i) { - for (int j = 0; j < kEntriesPerCf; ++j) { - ASSERT_EQ(Get(i, "key" + std::to_string(j)), "val" + std::to_string(j)); - } - } -} - -TEST_F(RepairTest, RepairColumnFamilyOptions) { - // Verify repair logic uses correct ColumnFamilyOptions when repairing a - // database with different options for column families. - const int kNumCfs = 2; - const int kEntriesPerCf = 2; - - Options opts(CurrentOptions()), rev_opts(CurrentOptions()); - opts.comparator = BytewiseComparator(); - rev_opts.comparator = ReverseBytewiseComparator(); - - DestroyAndReopen(opts); - CreateColumnFamilies({"reverse"}, rev_opts); - ReopenWithColumnFamilies({"default", "reverse"}, - std::vector{opts, rev_opts}); - for (int i = 0; i < kNumCfs; ++i) { - for (int j = 0; j < kEntriesPerCf; ++j) { - ASSERT_OK(Put(i, "key" + std::to_string(j), "val" + std::to_string(j))); - if (i == kNumCfs - 1 && j == kEntriesPerCf - 1) { - // Leave one unflushed so we can verify RepairDB's flush logic - continue; - } - ASSERT_OK(Flush(i)); - } - } - Close(); - - // RepairDB() records the comparator in the manifest, and DB::Open would fail - // if a different comparator were used. - ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}, {"reverse", rev_opts}}, - opts /* unknown_cf_opts */)); - ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"}, - std::vector{opts, rev_opts})); - for (int i = 0; i < kNumCfs; ++i) { - for (int j = 0; j < kEntriesPerCf; ++j) { - ASSERT_EQ(Get(i, "key" + std::to_string(j)), "val" + std::to_string(j)); - } - } - - // Examine table properties to verify RepairDB() used the right options when - // converting WAL->SST - TablePropertiesCollection fname_to_props; - ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[1], &fname_to_props)); - ASSERT_EQ(fname_to_props.size(), 2U); - for (const auto& fname_and_props : fname_to_props) { - std::string comparator_name(rev_opts.comparator->Name()); - ASSERT_EQ(comparator_name, fname_and_props.second->comparator_name); - } - Close(); - - // Also check comparator when it's provided via "unknown" CF options - ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}}, - rev_opts /* unknown_cf_opts */)); - ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"}, - std::vector{opts, rev_opts})); - for (int i = 0; i < kNumCfs; ++i) { - for (int j = 0; j < kEntriesPerCf; ++j) { - ASSERT_EQ(Get(i, "key" + std::to_string(j)), "val" + std::to_string(j)); - } - } -} - -TEST_F(RepairTest, DbNameContainsTrailingSlash) { - { - bool tmp; - if (env_->AreFilesSame("", "", &tmp).IsNotSupported()) { - fprintf(stderr, - "skipping RepairTest.DbNameContainsTrailingSlash due to " - "unsupported Env::AreFilesSame\n"); - return; - } - } - - ASSERT_OK(Put("key", "val")); - ASSERT_OK(Flush()); - Close(); - - ASSERT_OK(RepairDB(dbname_ + "/", CurrentOptions())); - ReopenWithSstIdVerify(); - ASSERT_EQ(Get("key"), "val"); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/seqno_time_test.cc b/db/seqno_time_test.cc deleted file mode 100644 index dd93be7af..000000000 --- a/db/seqno_time_test.cc +++ /dev/null @@ -1,994 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/db_test_util.h" -#include "db/periodic_task_scheduler.h" -#include "db/seqno_to_time_mapping.h" -#include "port/stack_trace.h" -#include "rocksdb/iostats_context.h" -#include "rocksdb/utilities/debug.h" -#include "test_util/mock_time_env.h" - - -namespace ROCKSDB_NAMESPACE { - -class SeqnoTimeTest : public DBTestBase { - public: - SeqnoTimeTest() : DBTestBase("seqno_time_test", /*env_do_fsync=*/false) { - mock_clock_ = std::make_shared(env_->GetSystemClock()); - mock_env_ = std::make_unique(env_, mock_clock_); - } - - protected: - std::unique_ptr mock_env_; - std::shared_ptr mock_clock_; - - void SetUp() override { - mock_clock_->InstallTimedWaitFixCallback(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::StartPeriodicTaskScheduler:Init", [&](void* arg) { - auto periodic_task_scheduler_ptr = - reinterpret_cast(arg); - periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock_.get()); - }); - } - - // make sure the file is not in cache, otherwise it won't have IO info - void AssertKeyTemperature(int key_id, Temperature expected_temperature) { - get_iostats_context()->Reset(); - IOStatsContext* iostats = get_iostats_context(); - std::string result = Get(Key(key_id)); - ASSERT_FALSE(result.empty()); - ASSERT_GT(iostats->bytes_read, 0); - switch (expected_temperature) { - case Temperature::kUnknown: - ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_read_count, - 0); - ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, - 0); - break; - case Temperature::kCold: - ASSERT_GT(iostats->file_io_stats_by_temperature.cold_file_read_count, - 0); - ASSERT_GT(iostats->file_io_stats_by_temperature.cold_file_bytes_read, - 0); - break; - default: - // the test only support kCold now for the bottommost temperature - FAIL(); - } - } -}; - -TEST_F(SeqnoTimeTest, TemperatureBasicUniversal) { - const int kNumTrigger = 4; - const int kNumLevels = 7; - const int kNumKeys = 100; - const int kKeyPerSec = 10; - - Options options = CurrentOptions(); - options.compaction_style = kCompactionStyleUniversal; - options.preclude_last_level_data_seconds = 10000; - options.env = mock_env_.get(); - options.bottommost_temperature = Temperature::kCold; - options.num_levels = kNumLevels; - DestroyAndReopen(options); - - // pass some time first, otherwise the first a few keys write time are going - // to be zero, and internally zero has special meaning: kUnknownSeqnoTime - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(kKeyPerSec)); }); - - int sst_num = 0; - // Write files that are overlap and enough to trigger compaction - for (; sst_num < kNumTrigger; sst_num++) { - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun([&] { - mock_clock_->MockSleepForSeconds(static_cast(kKeyPerSec)); - }); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(dbfull()->WaitForCompact(true)); - - // All data is hot, only output to penultimate level - ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel()); - ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0); - ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0); - - // read a random key, which should be hot (kUnknown) - AssertKeyTemperature(20, Temperature::kUnknown); - - // Write more data, but still all hot until the 10th SST, as: - // write a key every 10 seconds, 100 keys per SST, each SST takes 1000 seconds - // The preclude_last_level_data_seconds is 10k - for (; sst_num < kNumTrigger * 2; sst_num++) { - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun([&] { - mock_clock_->MockSleepForSeconds(static_cast(kKeyPerSec)); - }); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->WaitForCompact(true)); - ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0); - ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0); - } - - // Now we have both hot data and cold data - for (; sst_num < kNumTrigger * 3; sst_num++) { - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun([&] { - mock_clock_->MockSleepForSeconds(static_cast(kKeyPerSec)); - }); - } - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->WaitForCompact(true)); - } - - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - uint64_t hot_data_size = GetSstSizeHelper(Temperature::kUnknown); - uint64_t cold_data_size = GetSstSizeHelper(Temperature::kCold); - ASSERT_GT(hot_data_size, 0); - ASSERT_GT(cold_data_size, 0); - // the first a few key should be cold - AssertKeyTemperature(20, Temperature::kCold); - - for (int i = 0; i < 30; i++) { - dbfull()->TEST_WaitForPeriodicTaskRun([&] { - mock_clock_->MockSleepForSeconds(static_cast(20 * kKeyPerSec)); - }); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - // the hot/cold data cut off range should be between i * 20 + 200 -> 250 - AssertKeyTemperature(i * 20 + 250, Temperature::kUnknown); - AssertKeyTemperature(i * 20 + 200, Temperature::kCold); - } - - ASSERT_LT(GetSstSizeHelper(Temperature::kUnknown), hot_data_size); - ASSERT_GT(GetSstSizeHelper(Temperature::kCold), cold_data_size); - - // Wait again, the most of the data should be cold after that - // but it may not be all cold, because if there's no new data write to SST, - // the compaction will not get the new seqno->time sampling to decide the last - // a few data's time. - for (int i = 0; i < 5; i++) { - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(1000)); }); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - - // any random data close to the end should be cold - AssertKeyTemperature(1000, Temperature::kCold); - - // close explicitly, because the env is local variable which will be released - // first. - Close(); -} - -TEST_F(SeqnoTimeTest, TemperatureBasicLevel) { - const int kNumLevels = 7; - const int kNumKeys = 100; - - Options options = CurrentOptions(); - options.preclude_last_level_data_seconds = 10000; - options.env = mock_env_.get(); - options.bottommost_temperature = Temperature::kCold; - options.num_levels = kNumLevels; - options.level_compaction_dynamic_level_bytes = true; - // TODO(zjay): for level compaction, auto-compaction may stuck in deadloop, if - // the penultimate level score > 1, but the hot is not cold enough to compact - // to last level, which will keep triggering compaction. - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - // pass some time first, otherwise the first a few keys write time are going - // to be zero, and internally zero has special meaning: kUnknownSeqnoTime - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - - int sst_num = 0; - // Write files that are overlap - for (; sst_num < 4; sst_num++) { - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - } - ASSERT_OK(Flush()); - } - - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - // All data is hot, only output to penultimate level - ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel()); - ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0); - ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0); - - // read a random key, which should be hot (kUnknown) - AssertKeyTemperature(20, Temperature::kUnknown); - - // Adding more data to have mixed hot and cold data - for (; sst_num < 14; sst_num++) { - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - } - ASSERT_OK(Flush()); - } - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0); - ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0); - - // Compact the files to the last level which should split the hot/cold data - MoveFilesToLevel(6); - uint64_t hot_data_size = GetSstSizeHelper(Temperature::kUnknown); - uint64_t cold_data_size = GetSstSizeHelper(Temperature::kCold); - ASSERT_GT(hot_data_size, 0); - ASSERT_GT(cold_data_size, 0); - // the first a few key should be cold - AssertKeyTemperature(20, Temperature::kCold); - - // Wait some time, with each wait, the cold data is increasing and hot data is - // decreasing - for (int i = 0; i < 30; i++) { - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(200)); }); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - uint64_t pre_hot = hot_data_size; - uint64_t pre_cold = cold_data_size; - hot_data_size = GetSstSizeHelper(Temperature::kUnknown); - cold_data_size = GetSstSizeHelper(Temperature::kCold); - ASSERT_LT(hot_data_size, pre_hot); - ASSERT_GT(cold_data_size, pre_cold); - - // the hot/cold cut_off key should be around i * 20 + 400 -> 450 - AssertKeyTemperature(i * 20 + 450, Temperature::kUnknown); - AssertKeyTemperature(i * 20 + 400, Temperature::kCold); - } - - // Wait again, the most of the data should be cold after that - // hot data might not be empty, because if we don't write new data, there's - // no seqno->time sampling available to the compaction - for (int i = 0; i < 5; i++) { - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(1000)); }); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - } - - // any random data close to the end should be cold - AssertKeyTemperature(1000, Temperature::kCold); - - Close(); -} - -enum class SeqnoTimeTestType : char { - kTrackInternalTimeSeconds = 0, - kPrecludeLastLevel = 1, - kBothSetTrackSmaller = 2, -}; - -class SeqnoTimeTablePropTest - : public SeqnoTimeTest, - public ::testing::WithParamInterface { - public: - SeqnoTimeTablePropTest() : SeqnoTimeTest() {} - - void SetTrackTimeDurationOptions(uint64_t track_time_duration, - Options& options) const { - // either option set will enable the time tracking feature - switch (GetParam()) { - case SeqnoTimeTestType::kTrackInternalTimeSeconds: - options.preclude_last_level_data_seconds = 0; - options.preserve_internal_time_seconds = track_time_duration; - break; - case SeqnoTimeTestType::kPrecludeLastLevel: - options.preclude_last_level_data_seconds = track_time_duration; - options.preserve_internal_time_seconds = 0; - break; - case SeqnoTimeTestType::kBothSetTrackSmaller: - options.preclude_last_level_data_seconds = track_time_duration; - options.preserve_internal_time_seconds = track_time_duration / 10; - break; - } - } -}; - -INSTANTIATE_TEST_CASE_P( - SeqnoTimeTablePropTest, SeqnoTimeTablePropTest, - ::testing::Values(SeqnoTimeTestType::kTrackInternalTimeSeconds, - SeqnoTimeTestType::kPrecludeLastLevel, - SeqnoTimeTestType::kBothSetTrackSmaller)); - -TEST_P(SeqnoTimeTablePropTest, BasicSeqnoToTimeMapping) { - Options options = CurrentOptions(); - SetTrackTimeDurationOptions(10000, options); - - options.env = mock_env_.get(); - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - std::set checked_file_nums; - SequenceNumber start_seq = dbfull()->GetLatestSequenceNumber(); - // Write a key every 10 seconds - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - } - ASSERT_OK(Flush()); - TablePropertiesCollection tables_props; - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 1); - auto it = tables_props.begin(); - SeqnoToTimeMapping tp_mapping; - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - ASSERT_FALSE(tp_mapping.Empty()); - auto seqs = tp_mapping.TEST_GetInternalMapping(); - // about ~20 seqs->time entries, because the sample rate is 10000/100, and it - // passes 2k time. - ASSERT_GE(seqs.size(), 19); - ASSERT_LE(seqs.size(), 21); - SequenceNumber seq_end = dbfull()->GetLatestSequenceNumber(); - for (auto i = start_seq; i < start_seq + 10; i++) { - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i + 1) * 10); - } - start_seq += 10; - for (auto i = start_seq; i < seq_end; i++) { - // The result is within the range - ASSERT_GE(tp_mapping.GetOldestApproximateTime(i), (i - 10) * 10); - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i + 10) * 10); - } - checked_file_nums.insert(it->second->orig_file_number); - start_seq = seq_end; - - // Write a key every 1 seconds - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(Key(i + 190), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(1)); }); - } - seq_end = dbfull()->GetLatestSequenceNumber(); - ASSERT_OK(Flush()); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 2); - it = tables_props.begin(); - while (it != tables_props.end()) { - if (!checked_file_nums.count(it->second->orig_file_number)) { - break; - } - it++; - } - ASSERT_TRUE(it != tables_props.end()); - - tp_mapping.Clear(); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - seqs = tp_mapping.TEST_GetInternalMapping(); - // There only a few time sample - ASSERT_GE(seqs.size(), 1); - ASSERT_LE(seqs.size(), 3); - for (auto i = start_seq; i < seq_end; i++) { - // The result is not very accurate, as there is more data write within small - // range of time - ASSERT_GE(tp_mapping.GetOldestApproximateTime(i), (i - start_seq) + 1000); - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i - start_seq) + 3000); - } - checked_file_nums.insert(it->second->orig_file_number); - start_seq = seq_end; - - // Write a key every 200 seconds - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(Key(i + 380), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(200)); }); - } - seq_end = dbfull()->GetLatestSequenceNumber(); - ASSERT_OK(Flush()); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 3); - it = tables_props.begin(); - while (it != tables_props.end()) { - if (!checked_file_nums.count(it->second->orig_file_number)) { - break; - } - it++; - } - ASSERT_TRUE(it != tables_props.end()); - - tp_mapping.Clear(); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - seqs = tp_mapping.TEST_GetInternalMapping(); - // The sequence number -> time entries should be maxed - ASSERT_GE(seqs.size(), 99); - ASSERT_LE(seqs.size(), 101); - for (auto i = start_seq; i < seq_end - 99; i++) { - // likely the first 100 entries reports 0 - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), (i - start_seq) + 3000); - } - start_seq += 101; - - for (auto i = start_seq; i < seq_end; i++) { - ASSERT_GE(tp_mapping.GetOldestApproximateTime(i), - (i - start_seq) * 200 + 22200); - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), - (i - start_seq) * 200 + 22600); - } - checked_file_nums.insert(it->second->orig_file_number); - start_seq = seq_end; - - // Write a key every 100 seconds - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(Key(i + 570), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - seq_end = dbfull()->GetLatestSequenceNumber(); - ASSERT_OK(Flush()); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 4); - it = tables_props.begin(); - while (it != tables_props.end()) { - if (!checked_file_nums.count(it->second->orig_file_number)) { - break; - } - it++; - } - ASSERT_TRUE(it != tables_props.end()); - tp_mapping.Clear(); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - seqs = tp_mapping.TEST_GetInternalMapping(); - ASSERT_GE(seqs.size(), 99); - ASSERT_LE(seqs.size(), 101); - - checked_file_nums.insert(it->second->orig_file_number); - - // re-enable compaction - ASSERT_OK(dbfull()->SetOptions({ - {"disable_auto_compactions", "false"}, - })); - - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_GE(tables_props.size(), 1); - it = tables_props.begin(); - while (it != tables_props.end()) { - if (!checked_file_nums.count(it->second->orig_file_number)) { - break; - } - it++; - } - ASSERT_TRUE(it != tables_props.end()); - tp_mapping.Clear(); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - seqs = tp_mapping.TEST_GetInternalMapping(); - ASSERT_GE(seqs.size(), 99); - ASSERT_LE(seqs.size(), 101); - for (auto i = start_seq; i < seq_end - 99; i++) { - // likely the first 100 entries reports 0 - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), - (i - start_seq) * 100 + 50000); - } - start_seq += 101; - - for (auto i = start_seq; i < seq_end; i++) { - ASSERT_GE(tp_mapping.GetOldestApproximateTime(i), - (i - start_seq) * 100 + 52200); - ASSERT_LE(tp_mapping.GetOldestApproximateTime(i), - (i - start_seq) * 100 + 52400); - } - ASSERT_OK(db_->Close()); -} - -TEST_P(SeqnoTimeTablePropTest, MultiCFs) { - Options options = CurrentOptions(); - options.preclude_last_level_data_seconds = 0; - options.preserve_internal_time_seconds = 0; - options.env = mock_env_.get(); - options.stats_dump_period_sec = 0; - options.stats_persist_period_sec = 0; - ReopenWithColumnFamilies({"default"}, options); - - const PeriodicTaskScheduler& scheduler = - dbfull()->TEST_GetPeriodicTaskScheduler(); - ASSERT_FALSE(scheduler.TEST_HasTask(PeriodicTaskType::kRecordSeqnoTime)); - - // Write some data and increase the current time - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - ASSERT_OK(Flush()); - TablePropertiesCollection tables_props; - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 1); - auto it = tables_props.begin(); - ASSERT_TRUE(it->second->seqno_to_time_mapping.empty()); - - ASSERT_TRUE(dbfull()->TEST_GetSeqnoToTimeMapping().Empty()); - - Options options_1 = options; - SetTrackTimeDurationOptions(10000, options_1); - CreateColumnFamilies({"one"}, options_1); - ASSERT_TRUE(scheduler.TEST_HasTask(PeriodicTaskType::kRecordSeqnoTime)); - - // Write some data to the default CF (without preclude_last_level feature) - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - ASSERT_OK(Flush()); - - // Write some data to the CF one - for (int i = 0; i < 20; i++) { - ASSERT_OK(Put(1, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - } - ASSERT_OK(Flush(1)); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(handles_[1], &tables_props)); - ASSERT_EQ(tables_props.size(), 1); - it = tables_props.begin(); - SeqnoToTimeMapping tp_mapping; - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - ASSERT_FALSE(tp_mapping.Empty()); - auto seqs = tp_mapping.TEST_GetInternalMapping(); - ASSERT_GE(seqs.size(), 1); - ASSERT_LE(seqs.size(), 4); - - // Create one more CF with larger preclude_last_level time - Options options_2 = options; - SetTrackTimeDurationOptions(1000000, options_2); // 1m - CreateColumnFamilies({"two"}, options_2); - - // Add more data to CF "two" to fill the in memory mapping - for (int i = 0; i < 2000; i++) { - ASSERT_OK(Put(2, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - seqs = dbfull()->TEST_GetSeqnoToTimeMapping().TEST_GetInternalMapping(); - ASSERT_GE(seqs.size(), 1000 - 1); - ASSERT_LE(seqs.size(), 1000 + 1); - - ASSERT_OK(Flush(2)); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(handles_[2], &tables_props)); - ASSERT_EQ(tables_props.size(), 1); - it = tables_props.begin(); - tp_mapping.Clear(); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - seqs = tp_mapping.TEST_GetInternalMapping(); - // the max encoded entries is 100 - ASSERT_GE(seqs.size(), 100 - 1); - ASSERT_LE(seqs.size(), 100 + 1); - - // Write some data to default CF, as all memtable with preclude_last_level - // enabled have flushed, the in-memory seqno->time mapping should be cleared - for (int i = 0; i < 10; i++) { - ASSERT_OK(Put(0, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - seqs = dbfull()->TEST_GetSeqnoToTimeMapping().TEST_GetInternalMapping(); - ASSERT_OK(Flush(0)); - - // trigger compaction for CF "two" and make sure the compaction output has - // seqno_to_time_mapping - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(2, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - ASSERT_OK(Flush(2)); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(handles_[2], &tables_props)); - ASSERT_EQ(tables_props.size(), 1); - it = tables_props.begin(); - tp_mapping.Clear(); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - seqs = tp_mapping.TEST_GetInternalMapping(); - ASSERT_GE(seqs.size(), 99); - ASSERT_LE(seqs.size(), 101); - - for (int j = 0; j < 2; j++) { - for (int i = 0; i < 200; i++) { - ASSERT_OK(Put(0, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - ASSERT_OK(Flush(0)); - } - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(handles_[0], &tables_props)); - ASSERT_EQ(tables_props.size(), 1); - it = tables_props.begin(); - ASSERT_TRUE(it->second->seqno_to_time_mapping.empty()); - - // Write some data to CF "two", but don't flush to accumulate - for (int i = 0; i < 1000; i++) { - ASSERT_OK(Put(2, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - ASSERT_GE( - dbfull()->TEST_GetSeqnoToTimeMapping().TEST_GetInternalMapping().size(), - 500); - // After dropping CF "one", the in-memory mapping will be change to only - // follow CF "two" options. - ASSERT_OK(db_->DropColumnFamily(handles_[1])); - ASSERT_LE( - dbfull()->TEST_GetSeqnoToTimeMapping().TEST_GetInternalMapping().size(), - 100 + 5); - - // After dropping CF "two", the in-memory mapping is also clear. - ASSERT_OK(db_->DropColumnFamily(handles_[2])); - ASSERT_EQ( - dbfull()->TEST_GetSeqnoToTimeMapping().TEST_GetInternalMapping().size(), - 0); - - // And the timer worker is stopped - ASSERT_FALSE(scheduler.TEST_HasTask(PeriodicTaskType::kRecordSeqnoTime)); - Close(); -} - -TEST_P(SeqnoTimeTablePropTest, MultiInstancesBasic) { - const int kInstanceNum = 2; - - Options options = CurrentOptions(); - SetTrackTimeDurationOptions(10000, options); - options.env = mock_env_.get(); - options.stats_dump_period_sec = 0; - options.stats_persist_period_sec = 0; - - auto dbs = std::vector(kInstanceNum); - for (int i = 0; i < kInstanceNum; i++) { - ASSERT_OK( - DB::Open(options, test::PerThreadDBPath(std::to_string(i)), &(dbs[i]))); - } - - // Make sure the second instance has the worker enabled - auto dbi = static_cast_with_check(dbs[1]); - WriteOptions wo; - for (int i = 0; i < 200; i++) { - ASSERT_OK(dbi->Put(wo, Key(i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(100)); }); - } - SeqnoToTimeMapping seqno_to_time_mapping = dbi->TEST_GetSeqnoToTimeMapping(); - ASSERT_GT(seqno_to_time_mapping.Size(), 10); - - for (int i = 0; i < kInstanceNum; i++) { - ASSERT_OK(dbs[i]->Close()); - delete dbs[i]; - } -} - -TEST_P(SeqnoTimeTablePropTest, SeqnoToTimeMappingUniversal) { - const int kNumTrigger = 4; - const int kNumLevels = 7; - const int kNumKeys = 100; - - Options options = CurrentOptions(); - SetTrackTimeDurationOptions(10000, options); - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = kNumLevels; - options.env = mock_env_.get(); - - DestroyAndReopen(options); - - std::atomic_uint64_t num_seqno_zeroing{0}; - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "CompactionIterator::PrepareOutput:ZeroingSeq", - [&](void* /*arg*/) { num_seqno_zeroing++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - int sst_num = 0; - for (; sst_num < kNumTrigger - 1; sst_num++) { - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - } - ASSERT_OK(Flush()); - } - TablePropertiesCollection tables_props; - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 3); - for (const auto& props : tables_props) { - ASSERT_FALSE(props.second->seqno_to_time_mapping.empty()); - SeqnoToTimeMapping tp_mapping; - ASSERT_OK(tp_mapping.Add(props.second->seqno_to_time_mapping)); - ASSERT_OK(tp_mapping.Sort()); - ASSERT_FALSE(tp_mapping.Empty()); - auto seqs = tp_mapping.TEST_GetInternalMapping(); - ASSERT_GE(seqs.size(), 10 - 1); - ASSERT_LE(seqs.size(), 10 + 1); - } - - // Trigger a compaction - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(Put(Key(sst_num * (kNumKeys - 1) + i), "value")); - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(static_cast(10)); }); - } - sst_num++; - ASSERT_OK(Flush()); - ASSERT_OK(dbfull()->TEST_WaitForCompact()); - tables_props.clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - ASSERT_EQ(tables_props.size(), 1); - - auto it = tables_props.begin(); - SeqnoToTimeMapping tp_mapping; - ASSERT_FALSE(it->second->seqno_to_time_mapping.empty()); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - - // compact to the last level - CompactRangeOptions cro; - cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - // make sure the data is all compacted to penultimate level if the feature is - // on, otherwise, compacted to the last level. - if (options.preclude_last_level_data_seconds > 0) { - ASSERT_GT(NumTableFilesAtLevel(5), 0); - ASSERT_EQ(NumTableFilesAtLevel(6), 0); - } else { - ASSERT_EQ(NumTableFilesAtLevel(5), 0); - ASSERT_GT(NumTableFilesAtLevel(6), 0); - } - - // regardless the file is on the last level or not, it should keep the time - // information and sequence number are not set - tables_props.clear(); - tp_mapping.Clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - - ASSERT_EQ(tables_props.size(), 1); - ASSERT_EQ(num_seqno_zeroing, 0); - - it = tables_props.begin(); - ASSERT_FALSE(it->second->seqno_to_time_mapping.empty()); - ASSERT_OK(tp_mapping.Add(it->second->seqno_to_time_mapping)); - - // make half of the data expired - mock_clock_->MockSleepForSeconds(static_cast(8000)); - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - tables_props.clear(); - tp_mapping.Clear(); - ASSERT_OK(dbfull()->GetPropertiesOfAllTables(&tables_props)); - - if (options.preclude_last_level_data_seconds > 0) { - ASSERT_EQ(tables_props.size(), 2); - } else { - ASSERT_EQ(tables_props.size(), 1); - } - ASSERT_GT(num_seqno_zeroing, 0); - std::vector key_versions; - ASSERT_OK(GetAllKeyVersions(db_, Slice(), Slice(), - std::numeric_limits::max(), - &key_versions)); - // make sure there're more than 300 keys and first 100 keys are having seqno - // zeroed out, the last 100 key seqno not zeroed out - ASSERT_GT(key_versions.size(), 300); - for (int i = 0; i < 100; i++) { - ASSERT_EQ(key_versions[i].sequence, 0); - } - auto rit = key_versions.rbegin(); - for (int i = 0; i < 100; i++) { - ASSERT_GT(rit->sequence, 0); - rit++; - } - - // make all data expired and compact again to push it to the last level - // regardless if the tiering feature is enabled or not - mock_clock_->MockSleepForSeconds(static_cast(20000)); - - ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); - - ASSERT_GT(num_seqno_zeroing, 0); - ASSERT_GT(NumTableFilesAtLevel(6), 0); - - Close(); -} - -TEST_F(SeqnoTimeTest, MappingAppend) { - SeqnoToTimeMapping test(/*max_time_duration=*/100, /*max_capacity=*/10); - - // ignore seqno == 0, as it may mean the seqno is zeroed out - ASSERT_FALSE(test.Append(0, 9)); - - ASSERT_TRUE(test.Append(3, 10)); - auto size = test.Size(); - // normal add - ASSERT_TRUE(test.Append(10, 11)); - size++; - ASSERT_EQ(size, test.Size()); - - // Append unsorted - ASSERT_FALSE(test.Append(8, 12)); - ASSERT_EQ(size, test.Size()); - - // Append with the same seqno, newer time will be accepted - ASSERT_TRUE(test.Append(10, 12)); - ASSERT_EQ(size, test.Size()); - // older time will be ignored - ASSERT_FALSE(test.Append(10, 9)); - ASSERT_EQ(size, test.Size()); - - // new seqno with old time will be ignored - ASSERT_FALSE(test.Append(12, 8)); - ASSERT_EQ(size, test.Size()); -} - -TEST_F(SeqnoTimeTest, GetOldestApproximateTime) { - SeqnoToTimeMapping test(/*max_time_duration=*/100, /*max_capacity=*/10); - - ASSERT_EQ(test.GetOldestApproximateTime(10), kUnknownSeqnoTime); - - test.Append(3, 10); - - ASSERT_EQ(test.GetOldestApproximateTime(2), kUnknownSeqnoTime); - ASSERT_EQ(test.GetOldestApproximateTime(3), 10); - ASSERT_EQ(test.GetOldestApproximateTime(10), 10); - - test.Append(10, 100); - - test.Append(100, 1000); - ASSERT_EQ(test.GetOldestApproximateTime(10), 100); - ASSERT_EQ(test.GetOldestApproximateTime(40), 100); - ASSERT_EQ(test.GetOldestApproximateTime(111), 1000); -} - -TEST_F(SeqnoTimeTest, Sort) { - SeqnoToTimeMapping test; - - // single entry - test.Add(10, 11); - ASSERT_OK(test.Sort()); - ASSERT_EQ(test.Size(), 1); - - // duplicate, should be removed by sort - test.Add(10, 11); - // same seqno, but older time, should be removed - test.Add(10, 9); - - // unuseful ones, should be removed by sort - test.Add(11, 9); - test.Add(9, 8); - - // Good ones - test.Add(1, 10); - test.Add(100, 100); - - ASSERT_OK(test.Sort()); - - auto seqs = test.TEST_GetInternalMapping(); - - std::deque expected; - expected.emplace_back(1, 10); - expected.emplace_back(10, 11); - expected.emplace_back(100, 100); - - ASSERT_EQ(expected, seqs); -} - -TEST_F(SeqnoTimeTest, EncodeDecodeBasic) { - SeqnoToTimeMapping test(0, 1000); - - std::string output; - test.Encode(output, 0, 1000, 100); - ASSERT_TRUE(output.empty()); - - for (int i = 1; i <= 1000; i++) { - ASSERT_TRUE(test.Append(i, i * 10)); - } - test.Encode(output, 0, 1000, 100); - - ASSERT_FALSE(output.empty()); - - SeqnoToTimeMapping decoded; - ASSERT_OK(decoded.Add(output)); - ASSERT_OK(decoded.Sort()); - ASSERT_EQ(decoded.Size(), SeqnoToTimeMapping::kMaxSeqnoTimePairsPerSST); - ASSERT_EQ(test.Size(), 1000); - - for (SequenceNumber seq = 0; seq <= 1000; seq++) { - // test has the more accurate time mapping, encode only pick - // kMaxSeqnoTimePairsPerSST number of entries, which is less accurate - uint64_t target_time = test.GetOldestApproximateTime(seq); - ASSERT_GE(decoded.GetOldestApproximateTime(seq), - target_time < 200 ? 0 : target_time - 200); - ASSERT_LE(decoded.GetOldestApproximateTime(seq), target_time); - } -} - -TEST_F(SeqnoTimeTest, EncodeDecodePerferNewTime) { - SeqnoToTimeMapping test(0, 10); - - test.Append(1, 10); - test.Append(5, 17); - test.Append(6, 25); - test.Append(8, 30); - - std::string output; - test.Encode(output, 1, 10, 0, 3); - - SeqnoToTimeMapping decoded; - ASSERT_OK(decoded.Add(output)); - ASSERT_OK(decoded.Sort()); - - ASSERT_EQ(decoded.Size(), 3); - - auto seqs = decoded.TEST_GetInternalMapping(); - std::deque expected; - expected.emplace_back(1, 10); - expected.emplace_back(6, 25); - expected.emplace_back(8, 30); - ASSERT_EQ(expected, seqs); - - // Add a few large time number - test.Append(10, 100); - test.Append(13, 200); - test.Append(16, 300); - - output.clear(); - test.Encode(output, 1, 20, 0, 4); - decoded.Clear(); - ASSERT_OK(decoded.Add(output)); - ASSERT_OK(decoded.Sort()); - ASSERT_EQ(decoded.Size(), 4); - - expected.clear(); - expected.emplace_back(1, 10); - // entry #6, #8 are skipped as they are too close to #1. - // entry #100 is also within skip range, but if it's skipped, there not enough - // number to fill 4 entries, so select it. - expected.emplace_back(10, 100); - expected.emplace_back(13, 200); - expected.emplace_back(16, 300); - seqs = decoded.TEST_GetInternalMapping(); - ASSERT_EQ(expected, seqs); -} - -} // namespace ROCKSDB_NAMESPACE - - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/table_properties_collector_test.cc b/db/table_properties_collector_test.cc deleted file mode 100644 index 20f37e0c9..000000000 --- a/db/table_properties_collector_test.cc +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/table_properties_collector.h" - -#include -#include -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/dbformat.h" -#include "file/sequence_file_reader.h" -#include "file/writable_file_writer.h" -#include "options/cf_options.h" -#include "rocksdb/flush_block_policy.h" -#include "rocksdb/table.h" -#include "table/block_based/block_based_table_factory.h" -#include "table/meta_blocks.h" -#include "table/plain/plain_table_factory.h" -#include "table/table_builder.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding.h" - -namespace ROCKSDB_NAMESPACE { - -class TablePropertiesTest : public testing::Test, - public testing::WithParamInterface { - public: - void SetUp() override { backward_mode_ = GetParam(); } - - bool backward_mode_; -}; - -// Utilities test functions -namespace { -static const uint32_t kTestColumnFamilyId = 66; -static const std::string kTestColumnFamilyName = "test_column_fam"; -static const int kTestLevel = 1; - -void MakeBuilder( - const Options& options, const ImmutableOptions& ioptions, - const MutableCFOptions& moptions, - const InternalKeyComparator& internal_comparator, - const IntTblPropCollectorFactories* int_tbl_prop_collector_factories, - std::unique_ptr* writable, - std::unique_ptr* builder) { - std::unique_ptr wf(new test::StringSink); - writable->reset( - new WritableFileWriter(std::move(wf), "" /* don't care */, EnvOptions())); - TableBuilderOptions tboptions( - ioptions, moptions, internal_comparator, int_tbl_prop_collector_factories, - options.compression, options.compression_opts, kTestColumnFamilyId, - kTestColumnFamilyName, kTestLevel); - builder->reset(NewTableBuilder(tboptions, writable->get())); -} -} // namespace - -// Collects keys that starts with "A" in a table. -class RegularKeysStartWithA : public TablePropertiesCollector { - public: - const char* Name() const override { return "RegularKeysStartWithA"; } - - Status Finish(UserCollectedProperties* properties) override { - std::string encoded; - std::string encoded_num_puts; - std::string encoded_num_deletes; - std::string encoded_num_single_deletes; - std::string encoded_num_size_changes; - PutVarint32(&encoded, count_); - PutVarint32(&encoded_num_puts, num_puts_); - PutVarint32(&encoded_num_deletes, num_deletes_); - PutVarint32(&encoded_num_single_deletes, num_single_deletes_); - PutVarint32(&encoded_num_size_changes, num_size_changes_); - *properties = UserCollectedProperties{ - {"TablePropertiesTest", message_}, - {"Count", encoded}, - {"NumPuts", encoded_num_puts}, - {"NumDeletes", encoded_num_deletes}, - {"NumSingleDeletes", encoded_num_single_deletes}, - {"NumSizeChanges", encoded_num_size_changes}, - }; - return Status::OK(); - } - - Status AddUserKey(const Slice& user_key, const Slice& /*value*/, - EntryType type, SequenceNumber /*seq*/, - uint64_t file_size) override { - // simply asssume all user keys are not empty. - if (user_key.data()[0] == 'A') { - ++count_; - } - if (type == kEntryPut) { - num_puts_++; - } else if (type == kEntryDelete) { - num_deletes_++; - } else if (type == kEntrySingleDelete) { - num_single_deletes_++; - } - if (file_size < file_size_) { - message_ = "File size should not decrease."; - } else if (file_size != file_size_) { - num_size_changes_++; - } - - return Status::OK(); - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - private: - std::string message_ = "Rocksdb"; - uint32_t count_ = 0; - uint32_t num_puts_ = 0; - uint32_t num_deletes_ = 0; - uint32_t num_single_deletes_ = 0; - uint32_t num_size_changes_ = 0; - uint64_t file_size_ = 0; -}; - -// Collects keys that starts with "A" in a table. Backward compatible mode -// It is also used to test internal key table property collector -class RegularKeysStartWithABackwardCompatible - : public TablePropertiesCollector { - public: - const char* Name() const override { return "RegularKeysStartWithA"; } - - Status Finish(UserCollectedProperties* properties) override { - std::string encoded; - PutVarint32(&encoded, count_); - *properties = UserCollectedProperties{{"TablePropertiesTest", "Rocksdb"}, - {"Count", encoded}}; - return Status::OK(); - } - - Status Add(const Slice& user_key, const Slice& /*value*/) override { - // simply asssume all user keys are not empty. - if (user_key.data()[0] == 'A') { - ++count_; - } - return Status::OK(); - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - private: - uint32_t count_ = 0; -}; - -class RegularKeysStartWithAInternal : public IntTblPropCollector { - public: - const char* Name() const override { return "RegularKeysStartWithA"; } - - Status Finish(UserCollectedProperties* properties) override { - std::string encoded; - PutVarint32(&encoded, count_); - *properties = UserCollectedProperties{{"TablePropertiesTest", "Rocksdb"}, - {"Count", encoded}}; - return Status::OK(); - } - - Status InternalAdd(const Slice& user_key, const Slice& /*value*/, - uint64_t /*file_size*/) override { - // simply asssume all user keys are not empty. - if (user_key.data()[0] == 'A') { - ++count_; - } - return Status::OK(); - } - - void BlockAdd(uint64_t /* block_uncomp_bytes */, - uint64_t /* block_compressed_bytes_fast */, - uint64_t /* block_compressed_bytes_slow */) override { - // Nothing to do. - return; - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - private: - uint32_t count_ = 0; -}; - -class RegularKeysStartWithAFactory : public IntTblPropCollectorFactory, - public TablePropertiesCollectorFactory { - public: - explicit RegularKeysStartWithAFactory(bool backward_mode) - : backward_mode_(backward_mode) {} - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) override { - EXPECT_EQ(kTestColumnFamilyId, context.column_family_id); - EXPECT_EQ(kTestLevel, context.level_at_creation); - if (!backward_mode_) { - return new RegularKeysStartWithA(); - } else { - return new RegularKeysStartWithABackwardCompatible(); - } - } - IntTblPropCollector* CreateIntTblPropCollector( - uint32_t /*column_family_id*/, int /* level_at_creation */) override { - return new RegularKeysStartWithAInternal(); - } - const char* Name() const override { return "RegularKeysStartWithA"; } - - bool backward_mode_; -}; - -class FlushBlockEveryThreePolicy : public FlushBlockPolicy { - public: - bool Update(const Slice& /*key*/, const Slice& /*value*/) override { - return (++count_ % 3U == 0); - } - - private: - uint64_t count_ = 0; -}; - -class FlushBlockEveryThreePolicyFactory : public FlushBlockPolicyFactory { - public: - explicit FlushBlockEveryThreePolicyFactory() {} - - const char* Name() const override { - return "FlushBlockEveryThreePolicyFactory"; - } - - FlushBlockPolicy* NewFlushBlockPolicy( - const BlockBasedTableOptions& /*table_options*/, - const BlockBuilder& /*data_block_builder*/) const override { - return new FlushBlockEveryThreePolicy; - } -}; - -extern const uint64_t kBlockBasedTableMagicNumber; -extern const uint64_t kPlainTableMagicNumber; -namespace { -void TestCustomizedTablePropertiesCollector( - bool backward_mode, uint64_t magic_number, bool test_int_tbl_prop_collector, - const Options& options, const InternalKeyComparator& internal_comparator) { - // make sure the entries will be inserted with order. - std::map, std::string> kvs = { - {{"About ", kTypeValue}, "val5"}, // starts with 'A' - {{"Abstract", kTypeValue}, "val2"}, // starts with 'A' - {{"Around ", kTypeValue}, "val7"}, // starts with 'A' - {{"Beyond ", kTypeValue}, "val3"}, - {{"Builder ", kTypeValue}, "val1"}, - {{"Love ", kTypeDeletion}, ""}, - {{"Cancel ", kTypeValue}, "val4"}, - {{"Find ", kTypeValue}, "val6"}, - {{"Rocks ", kTypeDeletion}, ""}, - {{"Foo ", kTypeSingleDeletion}, ""}, - }; - - // -- Step 1: build table - std::unique_ptr builder; - std::unique_ptr writer; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - if (test_int_tbl_prop_collector) { - int_tbl_prop_collector_factories.emplace_back( - new RegularKeysStartWithAFactory(backward_mode)); - } else { - GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories); - } - MakeBuilder(options, ioptions, moptions, internal_comparator, - &int_tbl_prop_collector_factories, &writer, &builder); - - SequenceNumber seqNum = 0U; - for (const auto& kv : kvs) { - InternalKey ikey(kv.first.first, seqNum++, kv.first.second); - builder->Add(ikey.Encode(), kv.second); - } - ASSERT_OK(builder->Finish()); - ASSERT_OK(writer->Flush()); - - // -- Step 2: Read properties - test::StringSink* fwf = - static_cast(writer->writable_file()); - std::unique_ptr source( - new test::StringSource(fwf->contents())); - std::unique_ptr fake_file_reader( - new RandomAccessFileReader(std::move(source), "test")); - - std::unique_ptr props; - Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(), - magic_number, ioptions, &props); - ASSERT_OK(s); - - auto user_collected = props->user_collected_properties; - - ASSERT_NE(user_collected.find("TablePropertiesTest"), user_collected.end()); - ASSERT_EQ("Rocksdb", user_collected.at("TablePropertiesTest")); - - uint32_t starts_with_A = 0; - ASSERT_NE(user_collected.find("Count"), user_collected.end()); - Slice key(user_collected.at("Count")); - ASSERT_TRUE(GetVarint32(&key, &starts_with_A)); - ASSERT_EQ(3u, starts_with_A); - - if (!backward_mode && !test_int_tbl_prop_collector) { - uint32_t num_puts; - ASSERT_NE(user_collected.find("NumPuts"), user_collected.end()); - Slice key_puts(user_collected.at("NumPuts")); - ASSERT_TRUE(GetVarint32(&key_puts, &num_puts)); - ASSERT_EQ(7u, num_puts); - - uint32_t num_deletes; - ASSERT_NE(user_collected.find("NumDeletes"), user_collected.end()); - Slice key_deletes(user_collected.at("NumDeletes")); - ASSERT_TRUE(GetVarint32(&key_deletes, &num_deletes)); - ASSERT_EQ(2u, num_deletes); - - uint32_t num_single_deletes; - ASSERT_NE(user_collected.find("NumSingleDeletes"), user_collected.end()); - Slice key_single_deletes(user_collected.at("NumSingleDeletes")); - ASSERT_TRUE(GetVarint32(&key_single_deletes, &num_single_deletes)); - ASSERT_EQ(1u, num_single_deletes); - - uint32_t num_size_changes; - ASSERT_NE(user_collected.find("NumSizeChanges"), user_collected.end()); - Slice key_size_changes(user_collected.at("NumSizeChanges")); - ASSERT_TRUE(GetVarint32(&key_size_changes, &num_size_changes)); - ASSERT_GE(num_size_changes, 2u); - } -} -} // namespace - -TEST_P(TablePropertiesTest, CustomizedTablePropertiesCollector) { - // Test properties collectors with internal keys or regular keys - // for block based table - for (bool encode_as_internal : {true, false}) { - Options options; - BlockBasedTableOptions table_options; - table_options.flush_block_policy_factory = - std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - test::PlainInternalKeyComparator ikc(options.comparator); - std::shared_ptr collector_factory( - new RegularKeysStartWithAFactory(backward_mode_)); - options.table_properties_collector_factories.resize(1); - options.table_properties_collector_factories[0] = collector_factory; - - TestCustomizedTablePropertiesCollector(backward_mode_, - kBlockBasedTableMagicNumber, - encode_as_internal, options, ikc); - - // test plain table - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 8; - plain_table_options.bloom_bits_per_key = 8; - plain_table_options.hash_table_ratio = 0; - - options.table_factory = - std::make_shared(plain_table_options); - TestCustomizedTablePropertiesCollector(backward_mode_, - kPlainTableMagicNumber, - encode_as_internal, options, ikc); - } -} - -namespace { -void TestInternalKeyPropertiesCollector( - bool backward_mode, uint64_t magic_number, bool sanitized, - std::shared_ptr table_factory) { - InternalKey keys[] = { - InternalKey("A ", 0, ValueType::kTypeValue), - InternalKey("B ", 1, ValueType::kTypeValue), - InternalKey("C ", 2, ValueType::kTypeValue), - InternalKey("W ", 3, ValueType::kTypeDeletion), - InternalKey("X ", 4, ValueType::kTypeDeletion), - InternalKey("Y ", 5, ValueType::kTypeDeletion), - InternalKey("Z ", 6, ValueType::kTypeDeletion), - InternalKey("a ", 7, ValueType::kTypeSingleDeletion), - InternalKey("b ", 8, ValueType::kTypeMerge), - InternalKey("c ", 9, ValueType::kTypeMerge), - }; - - std::unique_ptr builder; - std::unique_ptr writable; - Options options; - test::PlainInternalKeyComparator pikc(options.comparator); - - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - options.table_factory = table_factory; - if (sanitized) { - options.table_properties_collector_factories.emplace_back( - new RegularKeysStartWithAFactory(backward_mode)); - // with sanitization, even regular properties collector will be able to - // handle internal keys. - auto comparator = options.comparator; - // HACK: Set options.info_log to avoid writing log in - // SanitizeOptions(). - options.info_log = std::make_shared(); - options = SanitizeOptions("db", // just a place holder - options); - ImmutableOptions ioptions(options); - GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories); - options.comparator = comparator; - } - const ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - - for (int iter = 0; iter < 2; ++iter) { - MakeBuilder(options, ioptions, moptions, pikc, - &int_tbl_prop_collector_factories, &writable, &builder); - for (const auto& k : keys) { - builder->Add(k.Encode(), "val"); - } - - ASSERT_OK(builder->Finish()); - ASSERT_OK(writable->Flush()); - - test::StringSink* fwf = - static_cast(writable->writable_file()); - std::unique_ptr source( - new test::StringSource(fwf->contents())); - std::unique_ptr reader( - new RandomAccessFileReader(std::move(source), "test")); - - std::unique_ptr props; - Status s = ReadTableProperties(reader.get(), fwf->contents().size(), - magic_number, ioptions, &props); - ASSERT_OK(s); - - auto user_collected = props->user_collected_properties; - uint64_t deleted = GetDeletedKeys(user_collected); - ASSERT_EQ(5u, deleted); // deletes + single-deletes - - bool property_present; - uint64_t merges = GetMergeOperands(user_collected, &property_present); - ASSERT_TRUE(property_present); - ASSERT_EQ(2u, merges); - - if (sanitized) { - uint32_t starts_with_A = 0; - ASSERT_NE(user_collected.find("Count"), user_collected.end()); - Slice key(user_collected.at("Count")); - ASSERT_TRUE(GetVarint32(&key, &starts_with_A)); - ASSERT_EQ(1u, starts_with_A); - - if (!backward_mode) { - uint32_t num_puts; - ASSERT_NE(user_collected.find("NumPuts"), user_collected.end()); - Slice key_puts(user_collected.at("NumPuts")); - ASSERT_TRUE(GetVarint32(&key_puts, &num_puts)); - ASSERT_EQ(3u, num_puts); - - uint32_t num_deletes; - ASSERT_NE(user_collected.find("NumDeletes"), user_collected.end()); - Slice key_deletes(user_collected.at("NumDeletes")); - ASSERT_TRUE(GetVarint32(&key_deletes, &num_deletes)); - ASSERT_EQ(4u, num_deletes); - - uint32_t num_single_deletes; - ASSERT_NE(user_collected.find("NumSingleDeletes"), - user_collected.end()); - Slice key_single_deletes(user_collected.at("NumSingleDeletes")); - ASSERT_TRUE(GetVarint32(&key_single_deletes, &num_single_deletes)); - ASSERT_EQ(1u, num_single_deletes); - } - } - } -} -} // namespace - -TEST_P(TablePropertiesTest, InternalKeyPropertiesCollector) { - TestInternalKeyPropertiesCollector( - backward_mode_, kBlockBasedTableMagicNumber, true /* sanitize */, - std::make_shared()); - if (backward_mode_) { - TestInternalKeyPropertiesCollector( - backward_mode_, kBlockBasedTableMagicNumber, false /* not sanitize */, - std::make_shared()); - } - - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 8; - plain_table_options.bloom_bits_per_key = 8; - plain_table_options.hash_table_ratio = 0; - - TestInternalKeyPropertiesCollector( - backward_mode_, kPlainTableMagicNumber, false /* not sanitize */, - std::make_shared(plain_table_options)); -} - -INSTANTIATE_TEST_CASE_P(InternalKeyPropertiesCollector, TablePropertiesTest, - ::testing::Bool()); - -INSTANTIATE_TEST_CASE_P(CustomizedTablePropertiesCollector, TablePropertiesTest, - ::testing::Bool()); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/version_builder_test.cc b/db/version_builder_test.cc deleted file mode 100644 index 611dee774..000000000 --- a/db/version_builder_test.cc +++ /dev/null @@ -1,1820 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include -#include -#include - -#include "db/version_edit.h" -#include "db/version_set.h" -#include "rocksdb/advanced_options.h" -#include "table/unique_id_impl.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class VersionBuilderTest : public testing::Test { - public: - const Comparator* ucmp_; - InternalKeyComparator icmp_; - Options options_; - ImmutableOptions ioptions_; - MutableCFOptions mutable_cf_options_; - VersionStorageInfo vstorage_; - uint32_t file_num_; - CompactionOptionsFIFO fifo_options_; - std::vector size_being_compacted_; - - VersionBuilderTest() - : ucmp_(BytewiseComparator()), - icmp_(ucmp_), - ioptions_(options_), - mutable_cf_options_(options_), - vstorage_(&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, - nullptr, false), - file_num_(1) { - mutable_cf_options_.RefreshDerivedOptions(ioptions_); - size_being_compacted_.resize(options_.num_levels); - } - - ~VersionBuilderTest() override { - for (int i = 0; i < vstorage_.num_levels(); i++) { - for (auto* f : vstorage_.LevelFiles(i)) { - if (--f->refs == 0) { - delete f; - } - } - } - } - - InternalKey GetInternalKey(const char* ukey, - SequenceNumber smallest_seq = 100) { - return InternalKey(ukey, smallest_seq, kTypeValue); - } - - void Add(int level, uint64_t file_number, const char* smallest, - const char* largest, uint64_t file_size = 0, uint32_t path_id = 0, - SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100, - uint64_t num_entries = 0, uint64_t num_deletions = 0, - bool sampled = false, SequenceNumber smallest_seqno = 0, - SequenceNumber largest_seqno = 0, - uint64_t oldest_blob_file_number = kInvalidBlobFileNumber, - uint64_t epoch_number = kUnknownEpochNumber) { - assert(level < vstorage_.num_levels()); - FileMetaData* f = new FileMetaData( - file_number, path_id, file_size, GetInternalKey(smallest, smallest_seq), - GetInternalKey(largest, largest_seq), smallest_seqno, largest_seqno, - /* marked_for_compact */ false, Temperature::kUnknown, - oldest_blob_file_number, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - f->compensated_file_size = file_size; - f->num_entries = num_entries; - f->num_deletions = num_deletions; - vstorage_.AddFile(level, f); - if (sampled) { - f->init_stats_from_file = true; - vstorage_.UpdateAccumulatedStats(f); - } - } - - void AddBlob(uint64_t blob_file_number, uint64_t total_blob_count, - uint64_t total_blob_bytes, std::string checksum_method, - std::string checksum_value, - BlobFileMetaData::LinkedSsts linked_ssts, - uint64_t garbage_blob_count, uint64_t garbage_blob_bytes) { - auto shared_meta = SharedBlobFileMetaData::Create( - blob_file_number, total_blob_count, total_blob_bytes, - std::move(checksum_method), std::move(checksum_value)); - auto meta = - BlobFileMetaData::Create(std::move(shared_meta), std::move(linked_ssts), - garbage_blob_count, garbage_blob_bytes); - - vstorage_.AddBlobFile(std::move(meta)); - } - - void AddDummyFile(uint64_t table_file_number, uint64_t blob_file_number, - uint64_t epoch_number) { - constexpr int level = 0; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr uint64_t file_size = 100; - constexpr uint32_t path_id = 0; - constexpr SequenceNumber smallest_seq = 0; - constexpr SequenceNumber largest_seq = 0; - constexpr uint64_t num_entries = 0; - constexpr uint64_t num_deletions = 0; - constexpr bool sampled = false; - - Add(level, table_file_number, smallest, largest, file_size, path_id, - smallest_seq, largest_seq, num_entries, num_deletions, sampled, - smallest_seq, largest_seq, blob_file_number, epoch_number); - } - - void AddDummyFileToEdit(VersionEdit* edit, uint64_t table_file_number, - uint64_t blob_file_number, uint64_t epoch_number) { - assert(edit); - - constexpr int level = 0; - constexpr uint32_t path_id = 0; - constexpr uint64_t file_size = 100; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr SequenceNumber smallest_seqno = 100; - constexpr SequenceNumber largest_seqno = 300; - constexpr bool marked_for_compaction = false; - - edit->AddFile(level, table_file_number, path_id, file_size, - GetInternalKey(smallest), GetInternalKey(largest), - smallest_seqno, largest_seqno, marked_for_compaction, - Temperature::kUnknown, blob_file_number, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - epoch_number, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - } - - void UpdateVersionStorageInfo(VersionStorageInfo* vstorage) { - assert(vstorage); - - vstorage->PrepareForVersionAppend(ioptions_, mutable_cf_options_); - vstorage->SetFinalized(); - } - - void UpdateVersionStorageInfo() { UpdateVersionStorageInfo(&vstorage_); } -}; - -void UnrefFilesInVersion(VersionStorageInfo* new_vstorage) { - for (int i = 0; i < new_vstorage->num_levels(); i++) { - for (auto* f : new_vstorage->LevelFiles(i)) { - if (--f->refs == 0) { - delete f; - } - } - } -} - -TEST_F(VersionBuilderTest, ApplyAndSaveTo) { - Add(0, 1U, "150", "200", 100U, /*path_id*/ 0, - /*smallest_seq*/ 100, /*largest_seq*/ 100, - /*num_entries*/ 0, /*num_deletions*/ 0, - /*sampled*/ false, /*smallest_seqno*/ 0, - /*largest_seqno*/ 0, - /*oldest_blob_file_number*/ kInvalidBlobFileNumber, - /*epoch_number*/ 1); - - Add(1, 66U, "150", "200", 100U); - Add(1, 88U, "201", "300", 100U); - - Add(2, 6U, "150", "179", 100U); - Add(2, 7U, "180", "220", 100U); - Add(2, 8U, "221", "300", 100U); - - Add(3, 26U, "150", "170", 100U); - Add(3, 27U, "171", "179", 100U); - Add(3, 28U, "191", "220", 100U); - Add(3, 29U, "221", "300", 100U); - - UpdateVersionStorageInfo(); - - VersionEdit version_edit; - version_edit.AddFile( - 2, 666, 0, 100U, GetInternalKey("301"), GetInternalKey("350"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.DeleteFile(3, 27U); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder version_builder(env_options, &ioptions_, table_cache, - &vstorage_, version_set); - - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, false); - ASSERT_OK(version_builder.Apply(&version_edit)); - ASSERT_OK(version_builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_EQ(400U, new_vstorage.NumLevelBytes(2)); - ASSERT_EQ(300U, new_vstorage.NumLevelBytes(3)); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic) { - ioptions_.level_compaction_dynamic_level_bytes = true; - - Add(0, 1U, "150", "200", 100U, 0, 200U, 200U, 0, 0, false, 200U, 200U, - /*oldest_blob_file_number*/ kInvalidBlobFileNumber, - /*epoch_number*/ 2); - Add(0, 88U, "201", "300", 100U, 0, 100U, 100U, 0, 0, false, 100U, 100U, - /*oldest_blob_file_number*/ kInvalidBlobFileNumber, - /*epoch_number*/ 1); - - Add(4, 6U, "150", "179", 100U); - Add(4, 7U, "180", "220", 100U); - Add(4, 8U, "221", "300", 100U); - - Add(5, 26U, "150", "170", 100U); - Add(5, 27U, "171", "179", 100U); - - UpdateVersionStorageInfo(); - - VersionEdit version_edit; - version_edit.AddFile( - 3, 666, 0, 100U, GetInternalKey("301"), GetInternalKey("350"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - version_edit.DeleteFile(0, 1U); - version_edit.DeleteFile(0, 88U); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder version_builder(env_options, &ioptions_, table_cache, - &vstorage_, version_set); - - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, false); - ASSERT_OK(version_builder.Apply(&version_edit)); - ASSERT_OK(version_builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_EQ(0U, new_vstorage.NumLevelBytes(0)); - ASSERT_EQ(100U, new_vstorage.NumLevelBytes(3)); - ASSERT_EQ(300U, new_vstorage.NumLevelBytes(4)); - ASSERT_EQ(200U, new_vstorage.NumLevelBytes(5)); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic2) { - ioptions_.level_compaction_dynamic_level_bytes = true; - - Add(0, 1U, "150", "200", 100U, 0, 200U, 200U, 0, 0, false, 200U, 200U, - /*oldest_blob_file_number*/ kInvalidBlobFileNumber, - /*epoch_number*/ 2); - Add(0, 88U, "201", "300", 100U, 0, 100U, 100U, 0, 0, false, 100U, 100U, - /*oldest_blob_file_number*/ kInvalidBlobFileNumber, - /*epoch_number*/ 1); - - Add(4, 6U, "150", "179", 100U); - Add(4, 7U, "180", "220", 100U); - Add(4, 8U, "221", "300", 100U); - - Add(5, 26U, "150", "170", 100U); - Add(5, 27U, "171", "179", 100U); - - UpdateVersionStorageInfo(); - - VersionEdit version_edit; - version_edit.AddFile( - 4, 666, 0, 100U, GetInternalKey("301"), GetInternalKey("350"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.DeleteFile(0, 1U); - version_edit.DeleteFile(0, 88U); - version_edit.DeleteFile(4, 6U); - version_edit.DeleteFile(4, 7U); - version_edit.DeleteFile(4, 8U); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder version_builder(env_options, &ioptions_, table_cache, - &vstorage_, version_set); - - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, false); - ASSERT_OK(version_builder.Apply(&version_edit)); - ASSERT_OK(version_builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_EQ(0U, new_vstorage.NumLevelBytes(0)); - ASSERT_EQ(100U, new_vstorage.NumLevelBytes(4)); - ASSERT_EQ(200U, new_vstorage.NumLevelBytes(5)); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyMultipleAndSaveTo) { - UpdateVersionStorageInfo(); - - VersionEdit version_edit; - version_edit.AddFile( - 2, 666, 0, 100U, GetInternalKey("301"), GetInternalKey("350"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 676, 0, 100U, GetInternalKey("401"), GetInternalKey("450"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 636, 0, 100U, GetInternalKey("601"), GetInternalKey("650"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 616, 0, 100U, GetInternalKey("501"), GetInternalKey("550"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 606, 0, 100U, GetInternalKey("701"), GetInternalKey("750"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder version_builder(env_options, &ioptions_, table_cache, - &vstorage_, version_set); - - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, false); - ASSERT_OK(version_builder.Apply(&version_edit)); - ASSERT_OK(version_builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_EQ(500U, new_vstorage.NumLevelBytes(2)); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) { - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder version_builder(env_options, &ioptions_, table_cache, - &vstorage_, version_set); - - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, false); - - VersionEdit version_edit; - version_edit.AddFile( - 2, 666, 0, 100U, GetInternalKey("301"), GetInternalKey("350"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 676, 0, 100U, GetInternalKey("401"), GetInternalKey("450"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 636, 0, 100U, GetInternalKey("601"), GetInternalKey("650"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 616, 0, 100U, GetInternalKey("501"), GetInternalKey("550"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit.AddFile( - 2, 606, 0, 100U, GetInternalKey("701"), GetInternalKey("750"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - ASSERT_OK(version_builder.Apply(&version_edit)); - - VersionEdit version_edit2; - version_edit.AddFile( - 2, 808, 0, 100U, GetInternalKey("901"), GetInternalKey("950"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - version_edit2.DeleteFile(2, 616); - version_edit2.DeleteFile(2, 636); - version_edit.AddFile( - 2, 806, 0, 100U, GetInternalKey("801"), GetInternalKey("850"), 200, 200, - false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - ASSERT_OK(version_builder.Apply(&version_edit2)); - ASSERT_OK(version_builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_EQ(300U, new_vstorage.NumLevelBytes(2)); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyFileDeletionIncorrectLevel) { - constexpr int level = 1; - constexpr uint64_t file_number = 2345; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr uint64_t file_size = 100; - - Add(level, file_number, smallest, largest, file_size); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr int incorrect_level = 3; - - edit.DeleteFile(incorrect_level, file_number); - - const Status s = builder.Apply(&edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), - "Cannot delete table file #2345 from level 3 since " - "it is on level 1")); -} - -TEST_F(VersionBuilderTest, ApplyFileDeletionNotInLSMTree) { - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr int level = 3; - constexpr uint64_t file_number = 1234; - - edit.DeleteFile(level, file_number); - - const Status s = builder.Apply(&edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), - "Cannot delete table file #1234 from level 3 since " - "it is not in the LSM tree")); -} - -TEST_F(VersionBuilderTest, ApplyFileDeletionAndAddition) { - constexpr int level = 1; - constexpr uint64_t file_number = 2345; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr uint64_t file_size = 10000; - constexpr uint32_t path_id = 0; - constexpr SequenceNumber smallest_seq = 100; - constexpr SequenceNumber largest_seq = 500; - constexpr uint64_t num_entries = 0; - constexpr uint64_t num_deletions = 0; - constexpr bool sampled = false; - constexpr SequenceNumber smallest_seqno = 1; - constexpr SequenceNumber largest_seqno = 1000; - - Add(level, file_number, smallest, largest, file_size, path_id, smallest_seq, - largest_seq, num_entries, num_deletions, sampled, smallest_seqno, - largest_seqno); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit deletion; - - deletion.DeleteFile(level, file_number); - - ASSERT_OK(builder.Apply(&deletion)); - - VersionEdit addition; - - constexpr bool marked_for_compaction = false; - - addition.AddFile( - level, file_number, path_id, file_size, - GetInternalKey(smallest, smallest_seq), - GetInternalKey(largest, largest_seq), smallest_seqno, largest_seqno, - marked_for_compaction, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - ASSERT_OK(builder.Apply(&addition)); - - constexpr bool force_consistency_checks = false; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_EQ(new_vstorage.GetFileLocation(file_number).GetLevel(), level); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyFileAdditionAlreadyInBase) { - constexpr int level = 1; - constexpr uint64_t file_number = 2345; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr uint64_t file_size = 10000; - - Add(level, file_number, smallest, largest, file_size); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr int new_level = 2; - constexpr uint32_t path_id = 0; - constexpr SequenceNumber smallest_seqno = 100; - constexpr SequenceNumber largest_seqno = 1000; - constexpr bool marked_for_compaction = false; - - edit.AddFile( - new_level, file_number, path_id, file_size, GetInternalKey(smallest), - GetInternalKey(largest), smallest_seqno, largest_seqno, - marked_for_compaction, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - const Status s = builder.Apply(&edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), - "Cannot add table file #2345 to level 2 since it is " - "already in the LSM tree on level 1")); -} - -TEST_F(VersionBuilderTest, ApplyFileAdditionAlreadyApplied) { - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr int level = 3; - constexpr uint64_t file_number = 2345; - constexpr uint32_t path_id = 0; - constexpr uint64_t file_size = 10000; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr SequenceNumber smallest_seqno = 100; - constexpr SequenceNumber largest_seqno = 1000; - constexpr bool marked_for_compaction = false; - - edit.AddFile( - level, file_number, path_id, file_size, GetInternalKey(smallest), - GetInternalKey(largest), smallest_seqno, largest_seqno, - marked_for_compaction, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - ASSERT_OK(builder.Apply(&edit)); - - VersionEdit other_edit; - - constexpr int new_level = 2; - - other_edit.AddFile( - new_level, file_number, path_id, file_size, GetInternalKey(smallest), - GetInternalKey(largest), smallest_seqno, largest_seqno, - marked_for_compaction, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - const Status s = builder.Apply(&other_edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), - "Cannot add table file #2345 to level 2 since it is " - "already in the LSM tree on level 3")); -} - -TEST_F(VersionBuilderTest, ApplyFileAdditionAndDeletion) { - UpdateVersionStorageInfo(); - - constexpr int level = 1; - constexpr uint64_t file_number = 2345; - constexpr uint32_t path_id = 0; - constexpr uint64_t file_size = 10000; - constexpr char smallest[] = "bar"; - constexpr char largest[] = "foo"; - constexpr SequenceNumber smallest_seqno = 100; - constexpr SequenceNumber largest_seqno = 1000; - constexpr bool marked_for_compaction = false; - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit addition; - - addition.AddFile( - level, file_number, path_id, file_size, GetInternalKey(smallest), - GetInternalKey(largest), smallest_seqno, largest_seqno, - marked_for_compaction, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - ASSERT_OK(builder.Apply(&addition)); - - VersionEdit deletion; - - deletion.DeleteFile(level, file_number); - - ASSERT_OK(builder.Apply(&deletion)); - - constexpr bool force_consistency_checks = false; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - ASSERT_FALSE(new_vstorage.GetFileLocation(file_number).IsValid()); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyBlobFileAddition) { - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t total_blob_count = 5678; - constexpr uint64_t total_blob_bytes = 999999; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52" - "\x5c\xbd"; - - edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - // Add dummy table file to ensure the blob file is referenced. - constexpr uint64_t table_file_number = 1; - AddDummyFileToEdit(&edit, table_file_number, blob_file_number, - 1 /*epoch_number*/); - - ASSERT_OK(builder.Apply(&edit)); - - constexpr bool force_consistency_checks = false; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - const auto& new_blob_files = new_vstorage.GetBlobFiles(); - ASSERT_EQ(new_blob_files.size(), 1); - - const auto new_meta = new_vstorage.GetBlobFileMetaData(blob_file_number); - - ASSERT_NE(new_meta, nullptr); - ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number); - ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count); - ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes); - ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method); - ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value); - ASSERT_EQ(new_meta->GetLinkedSsts(), - BlobFileMetaData::LinkedSsts{table_file_number}); - ASSERT_EQ(new_meta->GetGarbageBlobCount(), 0); - ASSERT_EQ(new_meta->GetGarbageBlobBytes(), 0); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyInBase) { - // Attempt to add a blob file that is already present in the base version. - - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t total_blob_count = 5678; - constexpr uint64_t total_blob_bytes = 999999; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52" - "\x5c\xbd"; - constexpr uint64_t garbage_blob_count = 123; - constexpr uint64_t garbage_blob_bytes = 456789; - - AddBlob(blob_file_number, total_blob_count, total_blob_bytes, checksum_method, - checksum_value, BlobFileMetaData::LinkedSsts(), garbage_blob_count, - garbage_blob_bytes); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - const Status s = builder.Apply(&edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 already added")); -} - -TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyApplied) { - // Attempt to add the same blob file twice using version edits. - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t total_blob_count = 5678; - constexpr uint64_t total_blob_bytes = 999999; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52" - "\x5c\xbd"; - - edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - ASSERT_OK(builder.Apply(&edit)); - - const Status s = builder.Apply(&edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 already added")); -} - -TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) { - // Increase the amount of garbage for a blob file present in the base version. - - constexpr uint64_t table_file_number = 1; - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t total_blob_count = 5678; - constexpr uint64_t total_blob_bytes = 999999; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52" - "\x5c\xbd"; - constexpr uint64_t garbage_blob_count = 123; - constexpr uint64_t garbage_blob_bytes = 456789; - - AddBlob(blob_file_number, total_blob_count, total_blob_bytes, checksum_method, - checksum_value, BlobFileMetaData::LinkedSsts{table_file_number}, - garbage_blob_count, garbage_blob_bytes); - - const auto meta = vstorage_.GetBlobFileMetaData(blob_file_number); - ASSERT_NE(meta, nullptr); - - // Add dummy table file to ensure the blob file is referenced. - AddDummyFile(table_file_number, blob_file_number, 1 /*epoch_number*/); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr uint64_t new_garbage_blob_count = 456; - constexpr uint64_t new_garbage_blob_bytes = 111111; - - edit.AddBlobFileGarbage(blob_file_number, new_garbage_blob_count, - new_garbage_blob_bytes); - - ASSERT_OK(builder.Apply(&edit)); - - constexpr bool force_consistency_checks = false; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - const auto& new_blob_files = new_vstorage.GetBlobFiles(); - ASSERT_EQ(new_blob_files.size(), 1); - - const auto new_meta = new_vstorage.GetBlobFileMetaData(blob_file_number); - - ASSERT_NE(new_meta, nullptr); - ASSERT_EQ(new_meta->GetSharedMeta(), meta->GetSharedMeta()); - ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number); - ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count); - ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes); - ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method); - ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value); - ASSERT_EQ(new_meta->GetLinkedSsts(), - BlobFileMetaData::LinkedSsts{table_file_number}); - ASSERT_EQ(new_meta->GetGarbageBlobCount(), - garbage_blob_count + new_garbage_blob_count); - ASSERT_EQ(new_meta->GetGarbageBlobBytes(), - garbage_blob_bytes + new_garbage_blob_bytes); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) { - // Increase the amount of garbage for a blob file added using a version edit. - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit addition; - - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t total_blob_count = 5678; - constexpr uint64_t total_blob_bytes = 999999; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52" - "\x5c\xbd"; - - addition.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - // Add dummy table file to ensure the blob file is referenced. - constexpr uint64_t table_file_number = 1; - AddDummyFileToEdit(&addition, table_file_number, blob_file_number, - 1 /*epoch_number*/); - - ASSERT_OK(builder.Apply(&addition)); - - constexpr uint64_t garbage_blob_count = 123; - constexpr uint64_t garbage_blob_bytes = 456789; - - VersionEdit garbage; - - garbage.AddBlobFileGarbage(blob_file_number, garbage_blob_count, - garbage_blob_bytes); - - ASSERT_OK(builder.Apply(&garbage)); - - constexpr bool force_consistency_checks = false; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - const auto& new_blob_files = new_vstorage.GetBlobFiles(); - ASSERT_EQ(new_blob_files.size(), 1); - - const auto new_meta = new_vstorage.GetBlobFileMetaData(blob_file_number); - - ASSERT_NE(new_meta, nullptr); - ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number); - ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count); - ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes); - ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method); - ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value); - ASSERT_EQ(new_meta->GetLinkedSsts(), - BlobFileMetaData::LinkedSsts{table_file_number}); - ASSERT_EQ(new_meta->GetGarbageBlobCount(), garbage_blob_count); - ASSERT_EQ(new_meta->GetGarbageBlobBytes(), garbage_blob_bytes); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileNotFound) { - // Attempt to increase the amount of garbage for a blob file that is - // neither in the base version, nor was it added using a version edit. - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t garbage_blob_count = 5678; - constexpr uint64_t garbage_blob_bytes = 999999; - - edit.AddBlobFileGarbage(blob_file_number, garbage_blob_count, - garbage_blob_bytes); - - const Status s = builder.Apply(&edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 not found")); -} - -TEST_F(VersionBuilderTest, BlobFileGarbageOverflow) { - // Test that VersionEdits that would result in the count/total size of garbage - // exceeding the count/total size of all blobs are rejected. - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit addition; - - constexpr uint64_t blob_file_number = 1234; - constexpr uint64_t total_blob_count = 5678; - constexpr uint64_t total_blob_bytes = 999999; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52" - "\x5c\xbd"; - - addition.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - // Add dummy table file to ensure the blob file is referenced. - constexpr uint64_t table_file_number = 1; - AddDummyFileToEdit(&addition, table_file_number, blob_file_number, - 1 /*epoch_number*/); - - ASSERT_OK(builder.Apply(&addition)); - - { - // Garbage blob count overflow - constexpr uint64_t garbage_blob_count = 5679; - constexpr uint64_t garbage_blob_bytes = 999999; - - VersionEdit garbage; - - garbage.AddBlobFileGarbage(blob_file_number, garbage_blob_count, - garbage_blob_bytes); - - const Status s = builder.Apply(&garbage); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE( - std::strstr(s.getState(), "Garbage overflow for blob file #1234")); - } - - { - // Garbage blob bytes overflow - constexpr uint64_t garbage_blob_count = 5678; - constexpr uint64_t garbage_blob_bytes = 1000000; - - VersionEdit garbage; - - garbage.AddBlobFileGarbage(blob_file_number, garbage_blob_count, - garbage_blob_bytes); - - const Status s = builder.Apply(&garbage); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE( - std::strstr(s.getState(), "Garbage overflow for blob file #1234")); - } -} - -TEST_F(VersionBuilderTest, SaveBlobFilesTo) { - // Add three blob files to base version. - for (uint64_t i = 1; i <= 3; ++i) { - const uint64_t table_file_number = 2 * i; - const uint64_t blob_file_number = 2 * i + 1; - const uint64_t total_blob_count = i * 1000; - const uint64_t total_blob_bytes = i * 1000000; - const uint64_t garbage_blob_count = i * 100; - const uint64_t garbage_blob_bytes = i * 20000; - - AddBlob(blob_file_number, total_blob_count, total_blob_bytes, - /* checksum_method */ std::string(), - /* checksum_value */ std::string(), - BlobFileMetaData::LinkedSsts{table_file_number}, garbage_blob_count, - garbage_blob_bytes); - } - - // Add dummy table files to ensure the blob files are referenced. - // Note: files are added to L0, so they have to be added in reverse order - // (newest first). - for (uint64_t i = 3; i >= 1; --i) { - const uint64_t table_file_number = 2 * i; - const uint64_t blob_file_number = 2 * i + 1; - - AddDummyFile(table_file_number, blob_file_number, i /*epoch_number*/); - } - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - // Add some garbage to the second and third blob files. The second blob file - // remains valid since it does not consist entirely of garbage yet. The third - // blob file is all garbage after the edit and will not be part of the new - // version. The corresponding dummy table file is also removed for - // consistency. - edit.AddBlobFileGarbage(/* blob_file_number */ 5, - /* garbage_blob_count */ 200, - /* garbage_blob_bytes */ 100000); - edit.AddBlobFileGarbage(/* blob_file_number */ 7, - /* garbage_blob_count */ 2700, - /* garbage_blob_bytes */ 2940000); - edit.DeleteFile(/* level */ 0, /* file_number */ 6); - - // Add a fourth blob file. - edit.AddBlobFile(/* blob_file_number */ 9, /* total_blob_count */ 4000, - /* total_blob_bytes */ 4000000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string()); - - ASSERT_OK(builder.Apply(&edit)); - - constexpr bool force_consistency_checks = false; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - const auto& new_blob_files = new_vstorage.GetBlobFiles(); - ASSERT_EQ(new_blob_files.size(), 3); - - const auto meta3 = new_vstorage.GetBlobFileMetaData(/* blob_file_number */ 3); - - ASSERT_NE(meta3, nullptr); - ASSERT_EQ(meta3->GetBlobFileNumber(), 3); - ASSERT_EQ(meta3->GetTotalBlobCount(), 1000); - ASSERT_EQ(meta3->GetTotalBlobBytes(), 1000000); - ASSERT_EQ(meta3->GetGarbageBlobCount(), 100); - ASSERT_EQ(meta3->GetGarbageBlobBytes(), 20000); - - const auto meta5 = new_vstorage.GetBlobFileMetaData(/* blob_file_number */ 5); - - ASSERT_NE(meta5, nullptr); - ASSERT_EQ(meta5->GetBlobFileNumber(), 5); - ASSERT_EQ(meta5->GetTotalBlobCount(), 2000); - ASSERT_EQ(meta5->GetTotalBlobBytes(), 2000000); - ASSERT_EQ(meta5->GetGarbageBlobCount(), 400); - ASSERT_EQ(meta5->GetGarbageBlobBytes(), 140000); - - const auto meta9 = new_vstorage.GetBlobFileMetaData(/* blob_file_number */ 9); - - ASSERT_NE(meta9, nullptr); - ASSERT_EQ(meta9->GetBlobFileNumber(), 9); - ASSERT_EQ(meta9->GetTotalBlobCount(), 4000); - ASSERT_EQ(meta9->GetTotalBlobBytes(), 4000000); - ASSERT_EQ(meta9->GetGarbageBlobCount(), 0); - ASSERT_EQ(meta9->GetGarbageBlobBytes(), 0); - - // Delete the first table file, which makes the first blob file obsolete - // since it's at the head and unreferenced. - VersionBuilder second_builder(env_options, &ioptions_, table_cache, - &new_vstorage, version_set); - - VersionEdit second_edit; - second_edit.DeleteFile(/* level */ 0, /* file_number */ 2); - - ASSERT_OK(second_builder.Apply(&second_edit)); - - VersionStorageInfo newer_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &new_vstorage, - force_consistency_checks); - - ASSERT_OK(second_builder.SaveTo(&newer_vstorage)); - - UpdateVersionStorageInfo(&newer_vstorage); - - const auto& newer_blob_files = newer_vstorage.GetBlobFiles(); - ASSERT_EQ(newer_blob_files.size(), 2); - - const auto newer_meta3 = - newer_vstorage.GetBlobFileMetaData(/* blob_file_number */ 3); - - ASSERT_EQ(newer_meta3, nullptr); - - UnrefFilesInVersion(&newer_vstorage); - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, SaveBlobFilesToConcurrentJobs) { - // When multiple background jobs (flushes/compactions) are executing in - // parallel, it is possible for the VersionEdit adding blob file K to be - // applied *after* the VersionEdit adding blob file N (for N > K). This test - // case makes sure this is handled correctly. - - // Add blob file #4 (referenced by table file #3) to base version. - constexpr uint64_t base_table_file_number = 3; - constexpr uint64_t base_blob_file_number = 4; - constexpr uint64_t base_total_blob_count = 100; - constexpr uint64_t base_total_blob_bytes = 1 << 20; - - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = "\xfa\xce\xb0\x0c"; - constexpr uint64_t garbage_blob_count = 0; - constexpr uint64_t garbage_blob_bytes = 0; - - AddDummyFile(base_table_file_number, base_blob_file_number, - 1 /*epoch_number*/); - AddBlob(base_blob_file_number, base_total_blob_count, base_total_blob_bytes, - checksum_method, checksum_value, - BlobFileMetaData::LinkedSsts{base_table_file_number}, - garbage_blob_count, garbage_blob_bytes); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - // Add blob file #2 (referenced by table file #1). - constexpr int level = 0; - constexpr uint64_t table_file_number = 1; - constexpr uint32_t path_id = 0; - constexpr uint64_t file_size = 1 << 12; - constexpr char smallest[] = "key1"; - constexpr char largest[] = "key987"; - constexpr SequenceNumber smallest_seqno = 0; - constexpr SequenceNumber largest_seqno = 0; - constexpr bool marked_for_compaction = false; - - constexpr uint64_t blob_file_number = 2; - static_assert(blob_file_number < base_blob_file_number, - "Added blob file should have a smaller file number"); - - constexpr uint64_t total_blob_count = 234; - constexpr uint64_t total_blob_bytes = 1 << 22; - - edit.AddFile( - level, table_file_number, path_id, file_size, GetInternalKey(smallest), - GetInternalKey(largest), smallest_seqno, largest_seqno, - marked_for_compaction, Temperature::kUnknown, blob_file_number, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, 2 /*epoch_number*/, - checksum_value, checksum_method, kNullUniqueId64x2, 0); - edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - ASSERT_OK(builder.Apply(&edit)); - - constexpr bool force_consistency_checks = true; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - const auto& new_blob_files = new_vstorage.GetBlobFiles(); - ASSERT_EQ(new_blob_files.size(), 2); - - const auto base_meta = - new_vstorage.GetBlobFileMetaData(base_blob_file_number); - - ASSERT_NE(base_meta, nullptr); - ASSERT_EQ(base_meta->GetBlobFileNumber(), base_blob_file_number); - ASSERT_EQ(base_meta->GetTotalBlobCount(), base_total_blob_count); - ASSERT_EQ(base_meta->GetTotalBlobBytes(), base_total_blob_bytes); - ASSERT_EQ(base_meta->GetGarbageBlobCount(), garbage_blob_count); - ASSERT_EQ(base_meta->GetGarbageBlobBytes(), garbage_blob_bytes); - ASSERT_EQ(base_meta->GetChecksumMethod(), checksum_method); - ASSERT_EQ(base_meta->GetChecksumValue(), checksum_value); - - const auto added_meta = new_vstorage.GetBlobFileMetaData(blob_file_number); - - ASSERT_NE(added_meta, nullptr); - ASSERT_EQ(added_meta->GetBlobFileNumber(), blob_file_number); - ASSERT_EQ(added_meta->GetTotalBlobCount(), total_blob_count); - ASSERT_EQ(added_meta->GetTotalBlobBytes(), total_blob_bytes); - ASSERT_EQ(added_meta->GetGarbageBlobCount(), garbage_blob_count); - ASSERT_EQ(added_meta->GetGarbageBlobBytes(), garbage_blob_bytes); - ASSERT_EQ(added_meta->GetChecksumMethod(), checksum_method); - ASSERT_EQ(added_meta->GetChecksumValue(), checksum_value); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) { - // Initialize base version. The first table file points to a valid blob file - // in this version; the second one does not refer to any blob files. - - Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150", - /* largest */ "200", /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100, - /* oldest_blob_file_number */ 16); - Add(/* level */ 1, /* file_number */ 23, /* smallest */ "201", - /* largest */ "300", /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ 200, /* largest_seq */ 200, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ 200, /* largest_seqno */ 200, - kInvalidBlobFileNumber); - - AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000, - /* total_blob_bytes */ 1000000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1}, - /* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000); - - UpdateVersionStorageInfo(); - - // Add a new table file that points to the existing blob file, and add a - // new table file--blob file pair. - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - edit.AddFile(/* level */ 1, /* file_number */ 606, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("701"), - /* largest */ GetInternalKey("750"), /* smallest_seqno */ 200, - /* largest_seqno */ 200, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ 16, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - - edit.AddFile(/* level */ 1, /* file_number */ 700, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("801"), - /* largest */ GetInternalKey("850"), /* smallest_seqno */ 200, - /* largest_seqno */ 200, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ 1000, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - edit.AddBlobFile(/* blob_file_number */ 1000, /* total_blob_count */ 2000, - /* total_blob_bytes */ 200000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string()); - - ASSERT_OK(builder.Apply(&edit)); - - // Save to a new version in order to trigger consistency checks. - constexpr bool force_consistency_checks = true; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesInconsistentLinks) { - // Initialize base version. Links between the table file and the blob file - // are inconsistent. - - Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150", - /* largest */ "200", /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100, - /* oldest_blob_file_number */ 256); - - AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000, - /* total_blob_bytes */ 1000000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1}, - /* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - // Save to a new version in order to trigger consistency checks. - constexpr bool force_consistency_checks = true; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - const Status s = builder.SaveTo(&new_vstorage); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(std::strstr( - s.getState(), - "Links are inconsistent between table files and blob file #16")); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) { - // Initialize base version. The table file points to a blob file that is - // all garbage. - - Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150", - /* largest */ "200", /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100, - /* oldest_blob_file_number */ 16); - - AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000, - /* total_blob_bytes */ 1000000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1}, - /* garbage_blob_count */ 1000, /* garbage_blob_bytes */ 1000000); - - UpdateVersionStorageInfo(); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - // Save to a new version in order to trigger consistency checks. - constexpr bool force_consistency_checks = true; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - const Status s = builder.SaveTo(&new_vstorage); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE( - std::strstr(s.getState(), "Blob file #16 consists entirely of garbage")); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbageLinkedSsts) { - // Initialize base version, with a table file pointing to a blob file - // that has no garbage at this point. - - Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150", - /* largest */ "200", /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100, - /* oldest_blob_file_number */ 16); - - AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000, - /* total_blob_bytes */ 1000000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1}, - /* garbage_blob_count */ 0, /* garbage_blob_bytes */ 0); - - UpdateVersionStorageInfo(); - - // Mark the entire blob file garbage but do not remove the linked SST. - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - VersionEdit edit; - - edit.AddBlobFileGarbage(/* blob_file_number */ 16, - /* garbage_blob_count */ 1000, - /* garbage_blob_bytes */ 1000000); - - ASSERT_OK(builder.Apply(&edit)); - - // Save to a new version in order to trigger consistency checks. - constexpr bool force_consistency_checks = true; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - const Status s = builder.SaveTo(&new_vstorage); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE( - std::strstr(s.getState(), "Blob file #16 consists entirely of garbage")); - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) { - // Initialize base version. Table files 1..10 are linked to blob files 1..5, - // while table files 11..20 are not linked to any blob files. - - for (uint64_t i = 1; i <= 10; ++i) { - std::ostringstream oss; - oss << std::setw(2) << std::setfill('0') << i; - - const std::string key = oss.str(); - - Add(/* level */ 1, /* file_number */ i, /* smallest */ key.c_str(), - /* largest */ key.c_str(), /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ i * 100, /* largest_seq */ i * 100, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ i * 100, - /* largest_seqno */ i * 100, - /* oldest_blob_file_number */ ((i - 1) % 5) + 1); - } - - for (uint64_t i = 1; i <= 5; ++i) { - AddBlob(/* blob_file_number */ i, /* total_blob_count */ 2000, - /* total_blob_bytes */ 2000000, - /* checksum_method */ std::string(), - /* checksum_value */ std::string(), - BlobFileMetaData::LinkedSsts{i, i + 5}, - /* garbage_blob_count */ 1000, /* garbage_blob_bytes */ 1000000); - } - - for (uint64_t i = 11; i <= 20; ++i) { - std::ostringstream oss; - oss << std::setw(2) << std::setfill('0') << i; - - const std::string key = oss.str(); - - Add(/* level */ 1, /* file_number */ i, /* smallest */ key.c_str(), - /* largest */ key.c_str(), /* file_size */ 100, - /* path_id */ 0, /* smallest_seq */ i * 100, /* largest_seq */ i * 100, - /* num_entries */ 0, /* num_deletions */ 0, - /* sampled */ false, /* smallest_seqno */ i * 100, - /* largest_seqno */ i * 100, kInvalidBlobFileNumber); - } - - UpdateVersionStorageInfo(); - - { - const auto& blob_files = vstorage_.GetBlobFiles(); - ASSERT_EQ(blob_files.size(), 5); - - const std::vector expected_linked_ssts{ - {1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}}; - - for (size_t i = 0; i < 5; ++i) { - const auto meta = - vstorage_.GetBlobFileMetaData(/* blob_file_number */ i + 1); - ASSERT_NE(meta, nullptr); - ASSERT_EQ(meta->GetLinkedSsts(), expected_linked_ssts[i]); - } - } - - VersionEdit edit; - - // Add an SST that references a blob file. - edit.AddFile( - /* level */ 1, /* file_number */ 21, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("21", 2100), - /* largest */ GetInternalKey("21", 2100), /* smallest_seqno */ 2100, - /* largest_seqno */ 2100, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ 1, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - // Add an SST that does not reference any blob files. - edit.AddFile( - /* level */ 1, /* file_number */ 22, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("22", 2200), - /* largest */ GetInternalKey("22", 2200), /* smallest_seqno */ 2200, - /* largest_seqno */ 2200, /* marked_for_compaction */ false, - Temperature::kUnknown, kInvalidBlobFileNumber, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - // Delete a file that references a blob file. - edit.DeleteFile(/* level */ 1, /* file_number */ 6); - - // Delete a file that does not reference any blob files. - edit.DeleteFile(/* level */ 1, /* file_number */ 16); - - // Trivially move a file that references a blob file. Note that we save - // the original BlobFileMetaData object so we can check that no new object - // gets created. - auto meta3 = vstorage_.GetBlobFileMetaData(/* blob_file_number */ 3); - - edit.DeleteFile(/* level */ 1, /* file_number */ 3); - edit.AddFile(/* level */ 2, /* file_number */ 3, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("03", 300), - /* largest */ GetInternalKey("03", 300), - /* smallest_seqno */ 300, - /* largest_seqno */ 300, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ 3, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - - // Trivially move a file that does not reference any blob files. - edit.DeleteFile(/* level */ 1, /* file_number */ 13); - edit.AddFile(/* level */ 2, /* file_number */ 13, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("13", 1300), - /* largest */ GetInternalKey("13", 1300), - /* smallest_seqno */ 1300, - /* largest_seqno */ 1300, /* marked_for_compaction */ false, - Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - kUnknownEpochNumber, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - // Add one more SST file that references a blob file, then promptly - // delete it in a second version edit before the new version gets saved. - // This file should not show up as linked to the blob file in the new version. - edit.AddFile(/* level */ 1, /* file_number */ 23, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("23", 2300), - /* largest */ GetInternalKey("23", 2300), - /* smallest_seqno */ 2300, - /* largest_seqno */ 2300, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ 5, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, - kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - - VersionEdit edit2; - - edit2.DeleteFile(/* level */ 1, /* file_number */ 23); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_, - version_set); - - ASSERT_OK(builder.Apply(&edit)); - ASSERT_OK(builder.Apply(&edit2)); - - constexpr bool force_consistency_checks = true; - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, &vstorage_, - force_consistency_checks); - - ASSERT_OK(builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - { - const auto& blob_files = new_vstorage.GetBlobFiles(); - ASSERT_EQ(blob_files.size(), 5); - - const std::vector expected_linked_ssts{ - {1, 21}, {2, 7}, {3, 8}, {4, 9}, {5, 10}}; - - for (size_t i = 0; i < 5; ++i) { - const auto meta = - new_vstorage.GetBlobFileMetaData(/* blob_file_number */ i + 1); - ASSERT_NE(meta, nullptr); - ASSERT_EQ(meta->GetLinkedSsts(), expected_linked_ssts[i]); - } - - // Make sure that no new BlobFileMetaData got created for the blob file - // affected by the trivial move. - ASSERT_EQ(new_vstorage.GetBlobFileMetaData(/* blob_file_number */ 3), - meta3); - } - - UnrefFilesInVersion(&new_vstorage); -} - -TEST_F(VersionBuilderTest, CheckConsistencyForFileDeletedTwice) { - Add(0, 1U, "150", "200", 100, /*path_id*/ 0, - /*smallest_seq*/ 100, /*largest_seq*/ 100, - /*num_entries*/ 0, /*num_deletions*/ 0, - /*sampled*/ false, /*smallest_seqno*/ 0, - /*largest_seqno*/ 0, - /*oldest_blob_file_number*/ kInvalidBlobFileNumber, - /*epoch_number*/ 1); - - UpdateVersionStorageInfo(); - - VersionEdit version_edit; - version_edit.DeleteFile(0, 1U); - - EnvOptions env_options; - constexpr TableCache* table_cache = nullptr; - constexpr VersionSet* version_set = nullptr; - - VersionBuilder version_builder(env_options, &ioptions_, table_cache, - &vstorage_, version_set); - VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, - true /* force_consistency_checks */); - ASSERT_OK(version_builder.Apply(&version_edit)); - ASSERT_OK(version_builder.SaveTo(&new_vstorage)); - - UpdateVersionStorageInfo(&new_vstorage); - - VersionBuilder version_builder2(env_options, &ioptions_, table_cache, - &new_vstorage, version_set); - VersionStorageInfo new_vstorage2(&icmp_, ucmp_, options_.num_levels, - kCompactionStyleLevel, nullptr, - true /* force_consistency_checks */); - ASSERT_NOK(version_builder2.Apply(&version_edit)); - - UnrefFilesInVersion(&new_vstorage); - UnrefFilesInVersion(&new_vstorage2); -} - -TEST_F(VersionBuilderTest, CheckConsistencyForL0FilesSortedByEpochNumber) { - Status s; - // To verify files of same epoch number of overlapping ranges are caught as - // corrupted - VersionEdit version_edit_1; - version_edit_1.AddFile( - /* level */ 0, /* file_number */ 1U, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("a", 1), - /* largest */ GetInternalKey("c", 3), /* smallest_seqno */ 1, - /* largest_seqno */ 3, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 1 /* epoch_number */, kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - version_edit_1.AddFile( - /* level */ 0, /* file_number */ 2U, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("b", 2), - /* largest */ GetInternalKey("d", 4), /* smallest_seqno */ 2, - /* largest_seqno */ 4, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 1 /* epoch_number */, kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - - VersionBuilder version_builder_1(EnvOptions(), &ioptions_, - nullptr /* table_cache */, &vstorage_, - nullptr /* file_metadata_cache_res_mgr */); - VersionStorageInfo new_vstorage_1( - &icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, - nullptr /* src_vstorage */, true /* force_consistency_checks */); - - ASSERT_OK(version_builder_1.Apply(&version_edit_1)); - s = version_builder_1.SaveTo(&new_vstorage_1); - EXPECT_TRUE(s.IsCorruption()); - EXPECT_TRUE(std::strstr( - s.getState(), "L0 files of same epoch number but overlapping range")); - UnrefFilesInVersion(&new_vstorage_1); - - // To verify L0 files not sorted by epoch_number are caught as corrupted - VersionEdit version_edit_2; - version_edit_2.AddFile( - /* level */ 0, /* file_number */ 1U, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("a", 1), - /* largest */ GetInternalKey("a", 1), /* smallest_seqno */ 1, - /* largest_seqno */ 1, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 1 /* epoch_number */, kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - version_edit_2.AddFile( - /* level */ 0, /* file_number */ 2U, /* path_id */ 0, - /* file_size */ 100, /* smallest */ GetInternalKey("b", 2), - /* largest */ GetInternalKey("b", 2), /* smallest_seqno */ 2, - /* largest_seqno */ 2, /* marked_for_compaction */ false, - Temperature::kUnknown, - /* oldest_blob_file_number */ kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 2 /* epoch_number */, kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, 0); - - VersionBuilder version_builder_2(EnvOptions(), &ioptions_, - nullptr /* table_cache */, &vstorage_, - nullptr /* file_metadata_cache_res_mgr */); - VersionStorageInfo new_vstorage_2( - &icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, - nullptr /* src_vstorage */, true /* force_consistency_checks */); - - ASSERT_OK(version_builder_2.Apply(&version_edit_2)); - s = version_builder_2.SaveTo(&new_vstorage_2); - ASSERT_TRUE(s.ok()); - - const std::vector& l0_files = new_vstorage_2.LevelFiles(0); - ASSERT_EQ(l0_files.size(), 2); - // Manually corrupt L0 files's epoch_number - l0_files[0]->epoch_number = 1; - l0_files[1]->epoch_number = 2; - - // To surface corruption error by applying dummy version edit - VersionEdit dummy_version_edit; - VersionBuilder dummy_version_builder( - EnvOptions(), &ioptions_, nullptr /* table_cache */, &vstorage_, - nullptr /* file_metadata_cache_res_mgr */); - ASSERT_OK(dummy_version_builder.Apply(&dummy_version_edit)); - s = dummy_version_builder.SaveTo(&new_vstorage_2); - EXPECT_TRUE(s.IsCorruption()); - EXPECT_TRUE(std::strstr(s.getState(), "L0 files are not sorted properly")); - - UnrefFilesInVersion(&new_vstorage_2); -} - -TEST_F(VersionBuilderTest, EstimatedActiveKeys) { - const uint32_t kTotalSamples = 20; - const uint32_t kNumLevels = 5; - const uint32_t kFilesPerLevel = 8; - const uint32_t kNumFiles = kNumLevels * kFilesPerLevel; - const uint32_t kEntriesPerFile = 1000; - const uint32_t kDeletionsPerFile = 100; - for (uint32_t i = 0; i < kNumFiles; ++i) { - Add(static_cast(i / kFilesPerLevel), i + 1, - std::to_string((i + 100) * 1000).c_str(), - std::to_string((i + 100) * 1000 + 999).c_str(), 100U, 0, 100, 100, - kEntriesPerFile, kDeletionsPerFile, (i < kTotalSamples)); - } - // minus 2X for the number of deletion entries because: - // 1x for deletion entry does not count as a data entry. - // 1x for each deletion entry will actually remove one data entry. - ASSERT_EQ(vstorage_.GetEstimatedActiveKeys(), - (kEntriesPerFile - 2 * kDeletionsPerFile) * kNumFiles); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/version_edit_test.cc b/db/version_edit_test.cc deleted file mode 100644 index 1fa6c0054..000000000 --- a/db/version_edit_test.cc +++ /dev/null @@ -1,732 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/version_edit.h" - -#include "db/blob/blob_index.h" -#include "rocksdb/advanced_options.h" -#include "table/unique_id_impl.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -static void TestEncodeDecode(const VersionEdit& edit) { - std::string encoded, encoded2; - edit.EncodeTo(&encoded); - VersionEdit parsed; - Status s = parsed.DecodeFrom(encoded); - ASSERT_TRUE(s.ok()) << s.ToString(); - parsed.EncodeTo(&encoded2); - ASSERT_EQ(encoded, encoded2); -} - -class VersionEditTest : public testing::Test {}; - -TEST_F(VersionEditTest, EncodeDecode) { - static const uint64_t kBig = 1ull << 50; - static const uint32_t kBig32Bit = 1ull << 30; - - VersionEdit edit; - for (int i = 0; i < 4; i++) { - TestEncodeDecode(edit); - edit.AddFile(3, kBig + 300 + i, kBig32Bit + 400 + i, 0, - InternalKey("foo", kBig + 500 + i, kTypeValue), - InternalKey("zoo", kBig + 600 + i, kTypeDeletion), - kBig + 500 + i, kBig + 600 + i, false, Temperature::kUnknown, - kInvalidBlobFileNumber, 888, 678, - kBig + 300 + i /* epoch_number */, "234", "crc32c", - kNullUniqueId64x2, 0); - edit.DeleteFile(4, kBig + 700 + i); - } - - edit.SetComparatorName("foo"); - edit.SetLogNumber(kBig + 100); - edit.SetNextFile(kBig + 200); - edit.SetLastSequence(kBig + 1000); - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, EncodeDecodeNewFile4) { - static const uint64_t kBig = 1ull << 50; - - VersionEdit edit; - edit.AddFile(3, 300, 3, 100, InternalKey("foo", kBig + 500, kTypeValue), - InternalKey("zoo", kBig + 600, kTypeDeletion), kBig + 500, - kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 300 /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - edit.AddFile(4, 301, 3, 100, InternalKey("foo", kBig + 501, kTypeValue), - InternalKey("zoo", kBig + 601, kTypeDeletion), kBig + 501, - kBig + 601, false, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 301 /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - edit.AddFile(5, 302, 0, 100, InternalKey("foo", kBig + 502, kTypeValue), - InternalKey("zoo", kBig + 602, kTypeDeletion), kBig + 502, - kBig + 602, true, Temperature::kUnknown, kInvalidBlobFileNumber, - 666, 888, 302 /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - edit.AddFile(5, 303, 0, 100, InternalKey("foo", kBig + 503, kTypeBlobIndex), - InternalKey("zoo", kBig + 603, kTypeBlobIndex), kBig + 503, - kBig + 603, true, Temperature::kUnknown, 1001, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 303 /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - edit.DeleteFile(4, 700); - - edit.SetComparatorName("foo"); - edit.SetLogNumber(kBig + 100); - edit.SetNextFile(kBig + 200); - edit.SetLastSequence(kBig + 1000); - TestEncodeDecode(edit); - - std::string encoded, encoded2; - edit.EncodeTo(&encoded); - VersionEdit parsed; - Status s = parsed.DecodeFrom(encoded); - ASSERT_TRUE(s.ok()) << s.ToString(); - auto& new_files = parsed.GetNewFiles(); - ASSERT_TRUE(new_files[0].second.marked_for_compaction); - ASSERT_TRUE(!new_files[1].second.marked_for_compaction); - ASSERT_TRUE(new_files[2].second.marked_for_compaction); - ASSERT_TRUE(new_files[3].second.marked_for_compaction); - ASSERT_EQ(3u, new_files[0].second.fd.GetPathId()); - ASSERT_EQ(3u, new_files[1].second.fd.GetPathId()); - ASSERT_EQ(0u, new_files[2].second.fd.GetPathId()); - ASSERT_EQ(0u, new_files[3].second.fd.GetPathId()); - ASSERT_EQ(kInvalidBlobFileNumber, - new_files[0].second.oldest_blob_file_number); - ASSERT_EQ(kInvalidBlobFileNumber, - new_files[1].second.oldest_blob_file_number); - ASSERT_EQ(kInvalidBlobFileNumber, - new_files[2].second.oldest_blob_file_number); - ASSERT_EQ(1001, new_files[3].second.oldest_blob_file_number); -} - -TEST_F(VersionEditTest, ForwardCompatibleNewFile4) { - static const uint64_t kBig = 1ull << 50; - VersionEdit edit; - edit.AddFile(3, 300, 3, 100, InternalKey("foo", kBig + 500, kTypeValue), - InternalKey("zoo", kBig + 600, kTypeDeletion), kBig + 500, - kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 300 /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - edit.AddFile(4, 301, 3, 100, InternalKey("foo", kBig + 501, kTypeValue), - InternalKey("zoo", kBig + 601, kTypeDeletion), kBig + 501, - kBig + 601, false, Temperature::kUnknown, kInvalidBlobFileNumber, - 686, 868, 301 /* epoch_number */, "234", "crc32c", - kNullUniqueId64x2, 0); - edit.DeleteFile(4, 700); - - edit.SetComparatorName("foo"); - edit.SetLogNumber(kBig + 100); - edit.SetNextFile(kBig + 200); - edit.SetLastSequence(kBig + 1000); - - std::string encoded; - - // Call back function to add extra customized builds. - bool first = true; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionEdit::EncodeTo:NewFile4:CustomizeFields", [&](void* arg) { - std::string* str = reinterpret_cast(arg); - PutVarint32(str, 33); - const std::string str1 = "random_string"; - PutLengthPrefixedSlice(str, str1); - if (first) { - first = false; - PutVarint32(str, 22); - const std::string str2 = "s"; - PutLengthPrefixedSlice(str, str2); - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - edit.EncodeTo(&encoded); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - VersionEdit parsed; - Status s = parsed.DecodeFrom(encoded); - ASSERT_TRUE(s.ok()) << s.ToString(); - ASSERT_TRUE(!first); - auto& new_files = parsed.GetNewFiles(); - ASSERT_TRUE(new_files[0].second.marked_for_compaction); - ASSERT_TRUE(!new_files[1].second.marked_for_compaction); - ASSERT_EQ(3u, new_files[0].second.fd.GetPathId()); - ASSERT_EQ(3u, new_files[1].second.fd.GetPathId()); - ASSERT_EQ(1u, parsed.GetDeletedFiles().size()); -} - -TEST_F(VersionEditTest, NewFile4NotSupportedField) { - static const uint64_t kBig = 1ull << 50; - VersionEdit edit; - edit.AddFile(3, 300, 3, 100, InternalKey("foo", kBig + 500, kTypeValue), - InternalKey("zoo", kBig + 600, kTypeDeletion), kBig + 500, - kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 300 /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - - edit.SetComparatorName("foo"); - edit.SetLogNumber(kBig + 100); - edit.SetNextFile(kBig + 200); - edit.SetLastSequence(kBig + 1000); - - std::string encoded; - - // Call back function to add extra customized builds. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "VersionEdit::EncodeTo:NewFile4:CustomizeFields", [&](void* arg) { - std::string* str = reinterpret_cast(arg); - const std::string str1 = "s"; - PutLengthPrefixedSlice(str, str1); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - edit.EncodeTo(&encoded); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - VersionEdit parsed; - Status s = parsed.DecodeFrom(encoded); - ASSERT_NOK(s); -} - -TEST_F(VersionEditTest, EncodeEmptyFile) { - VersionEdit edit; - edit.AddFile(0, 0, 0, 0, InternalKey(), InternalKey(), 0, 0, false, - Temperature::kUnknown, kInvalidBlobFileNumber, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - 1 /*epoch_number*/, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - std::string buffer; - ASSERT_TRUE(!edit.EncodeTo(&buffer)); -} - -TEST_F(VersionEditTest, ColumnFamilyTest) { - VersionEdit edit; - edit.SetColumnFamily(2); - edit.AddColumnFamily("column_family"); - edit.SetMaxColumnFamily(5); - TestEncodeDecode(edit); - - edit.Clear(); - edit.SetColumnFamily(3); - edit.DropColumnFamily(); - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, MinLogNumberToKeep) { - VersionEdit edit; - edit.SetMinLogNumberToKeep(13); - TestEncodeDecode(edit); - - edit.Clear(); - edit.SetMinLogNumberToKeep(23); - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, AtomicGroupTest) { - VersionEdit edit; - edit.MarkAtomicGroup(1); - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, IgnorableField) { - VersionEdit ve; - std::string encoded; - - // Size of ignorable field is too large - PutVarint32Varint64(&encoded, 2 /* kLogNumber */, 66); - // This is a customized ignorable tag - PutVarint32Varint64(&encoded, - 0x2710 /* A field with kTagSafeIgnoreMask set */, - 5 /* fieldlength 5 */); - encoded += "abc"; // Only fills 3 bytes, - ASSERT_NOK(ve.DecodeFrom(encoded)); - - encoded.clear(); - // Error when seeing unidentified tag that is not ignorable - PutVarint32Varint64(&encoded, 2 /* kLogNumber */, 66); - // This is a customized ignorable tag - PutVarint32Varint64(&encoded, 666 /* A field with kTagSafeIgnoreMask unset */, - 3 /* fieldlength 3 */); - encoded += "abc"; // Fill 3 bytes - PutVarint32Varint64(&encoded, 3 /* next file number */, 88); - ASSERT_NOK(ve.DecodeFrom(encoded)); - - // Safely ignore an identified but safely ignorable entry - encoded.clear(); - PutVarint32Varint64(&encoded, 2 /* kLogNumber */, 66); - // This is a customized ignorable tag - PutVarint32Varint64(&encoded, - 0x2710 /* A field with kTagSafeIgnoreMask set */, - 3 /* fieldlength 3 */); - encoded += "abc"; // Fill 3 bytes - PutVarint32Varint64(&encoded, 3 /* kNextFileNumber */, 88); - - ASSERT_OK(ve.DecodeFrom(encoded)); - - ASSERT_TRUE(ve.HasLogNumber()); - ASSERT_TRUE(ve.HasNextFile()); - ASSERT_EQ(66, ve.GetLogNumber()); - ASSERT_EQ(88, ve.GetNextFile()); -} - -TEST_F(VersionEditTest, DbId) { - VersionEdit edit; - edit.SetDBId("ab34-cd12-435f-er00"); - TestEncodeDecode(edit); - - edit.Clear(); - edit.SetDBId("34ba-cd12-435f-er01"); - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, BlobFileAdditionAndGarbage) { - VersionEdit edit; - - const std::string checksum_method_prefix = "Hash"; - const std::string checksum_value_prefix = "Value"; - - for (uint64_t blob_file_number = 1; blob_file_number <= 10; - ++blob_file_number) { - const uint64_t total_blob_count = blob_file_number << 10; - const uint64_t total_blob_bytes = blob_file_number << 20; - - std::string checksum_method(checksum_method_prefix); - AppendNumberTo(&checksum_method, blob_file_number); - - std::string checksum_value(checksum_value_prefix); - AppendNumberTo(&checksum_value, blob_file_number); - - edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - const uint64_t garbage_blob_count = total_blob_count >> 2; - const uint64_t garbage_blob_bytes = total_blob_bytes >> 1; - - edit.AddBlobFileGarbage(blob_file_number, garbage_blob_count, - garbage_blob_bytes); - } - - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, AddWalEncodeDecode) { - VersionEdit edit; - for (uint64_t log_number = 1; log_number <= 20; log_number++) { - WalMetadata meta; - bool has_size = rand() % 2 == 0; - if (has_size) { - meta.SetSyncedSizeInBytes(rand() % 1000); - } - edit.AddWal(log_number, meta); - } - TestEncodeDecode(edit); -} - -static std::string PrefixEncodedWalAdditionWithLength( - const std::string& encoded) { - std::string ret; - PutVarint32(&ret, Tag::kWalAddition2); - PutLengthPrefixedSlice(&ret, encoded); - return ret; -} - -TEST_F(VersionEditTest, AddWalDecodeBadLogNumber) { - std::string encoded; - - { - // No log number. - std::string encoded_edit = PrefixEncodedWalAdditionWithLength(encoded); - VersionEdit edit; - Status s = edit.DecodeFrom(encoded_edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("Error decoding WAL log number") != - std::string::npos) - << s.ToString(); - } - - { - // log number should be varint64, - // but we only encode 128 which is not a valid representation of varint64. - char c = 0; - unsigned char* ptr = reinterpret_cast(&c); - *ptr = 128; - encoded.append(1, c); - - std::string encoded_edit = PrefixEncodedWalAdditionWithLength(encoded); - VersionEdit edit; - Status s = edit.DecodeFrom(encoded_edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("Error decoding WAL log number") != - std::string::npos) - << s.ToString(); - } -} - -TEST_F(VersionEditTest, AddWalDecodeBadTag) { - constexpr WalNumber kLogNumber = 100; - constexpr uint64_t kSizeInBytes = 100; - - std::string encoded; - PutVarint64(&encoded, kLogNumber); - - { - // No tag. - std::string encoded_edit = PrefixEncodedWalAdditionWithLength(encoded); - VersionEdit edit; - Status s = edit.DecodeFrom(encoded_edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("Error decoding tag") != std::string::npos) - << s.ToString(); - } - - { - // Only has size tag, no terminate tag. - std::string encoded_with_size = encoded; - PutVarint32(&encoded_with_size, - static_cast(WalAdditionTag::kSyncedSize)); - PutVarint64(&encoded_with_size, kSizeInBytes); - - std::string encoded_edit = - PrefixEncodedWalAdditionWithLength(encoded_with_size); - VersionEdit edit; - Status s = edit.DecodeFrom(encoded_edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("Error decoding tag") != std::string::npos) - << s.ToString(); - } - - { - // Only has terminate tag. - std::string encoded_with_terminate = encoded; - PutVarint32(&encoded_with_terminate, - static_cast(WalAdditionTag::kTerminate)); - - std::string encoded_edit = - PrefixEncodedWalAdditionWithLength(encoded_with_terminate); - VersionEdit edit; - ASSERT_OK(edit.DecodeFrom(encoded_edit)); - auto& wal_addition = edit.GetWalAdditions()[0]; - ASSERT_EQ(wal_addition.GetLogNumber(), kLogNumber); - ASSERT_FALSE(wal_addition.GetMetadata().HasSyncedSize()); - } -} - -TEST_F(VersionEditTest, AddWalDecodeNoSize) { - constexpr WalNumber kLogNumber = 100; - - std::string encoded; - PutVarint64(&encoded, kLogNumber); - PutVarint32(&encoded, static_cast(WalAdditionTag::kSyncedSize)); - // No real size after the size tag. - - { - // Without terminate tag. - std::string encoded_edit = PrefixEncodedWalAdditionWithLength(encoded); - VersionEdit edit; - Status s = edit.DecodeFrom(encoded_edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("Error decoding WAL file size") != - std::string::npos) - << s.ToString(); - } - - { - // With terminate tag. - PutVarint32(&encoded, static_cast(WalAdditionTag::kTerminate)); - - std::string encoded_edit = PrefixEncodedWalAdditionWithLength(encoded); - VersionEdit edit; - Status s = edit.DecodeFrom(encoded_edit); - ASSERT_TRUE(s.IsCorruption()); - // The terminate tag is misunderstood as the size. - ASSERT_TRUE(s.ToString().find("Error decoding tag") != std::string::npos) - << s.ToString(); - } -} - -TEST_F(VersionEditTest, AddWalDebug) { - constexpr int n = 2; - constexpr std::array kLogNumbers{{10, 20}}; - constexpr std::array kSizeInBytes{{100, 200}}; - - VersionEdit edit; - for (int i = 0; i < n; i++) { - edit.AddWal(kLogNumbers[i], WalMetadata(kSizeInBytes[i])); - } - - const WalAdditions& wals = edit.GetWalAdditions(); - - ASSERT_TRUE(edit.IsWalAddition()); - ASSERT_EQ(wals.size(), n); - for (int i = 0; i < n; i++) { - const WalAddition& wal = wals[i]; - ASSERT_EQ(wal.GetLogNumber(), kLogNumbers[i]); - ASSERT_EQ(wal.GetMetadata().GetSyncedSizeInBytes(), kSizeInBytes[i]); - } - - std::string expected_str = "VersionEdit {\n"; - for (int i = 0; i < n; i++) { - std::stringstream ss; - ss << " WalAddition: log_number: " << kLogNumbers[i] - << " synced_size_in_bytes: " << kSizeInBytes[i] << "\n"; - expected_str += ss.str(); - } - expected_str += " ColumnFamily: 0\n}\n"; - ASSERT_EQ(edit.DebugString(true), expected_str); - - std::string expected_json = "{\"EditNumber\": 4, \"WalAdditions\": ["; - for (int i = 0; i < n; i++) { - std::stringstream ss; - ss << "{\"LogNumber\": " << kLogNumbers[i] << ", " - << "\"SyncedSizeInBytes\": " << kSizeInBytes[i] << "}"; - if (i < n - 1) ss << ", "; - expected_json += ss.str(); - } - expected_json += "], \"ColumnFamily\": 0}"; - ASSERT_EQ(edit.DebugJSON(4, true), expected_json); -} - -TEST_F(VersionEditTest, DeleteWalEncodeDecode) { - VersionEdit edit; - edit.DeleteWalsBefore(rand() % 100); - TestEncodeDecode(edit); -} - -TEST_F(VersionEditTest, DeleteWalDebug) { - constexpr int n = 2; - constexpr std::array kLogNumbers{{10, 20}}; - - VersionEdit edit; - edit.DeleteWalsBefore(kLogNumbers[n - 1]); - - const WalDeletion& wal = edit.GetWalDeletion(); - - ASSERT_TRUE(edit.IsWalDeletion()); - ASSERT_EQ(wal.GetLogNumber(), kLogNumbers[n - 1]); - - std::string expected_str = "VersionEdit {\n"; - { - std::stringstream ss; - ss << " WalDeletion: log_number: " << kLogNumbers[n - 1] << "\n"; - expected_str += ss.str(); - } - expected_str += " ColumnFamily: 0\n}\n"; - ASSERT_EQ(edit.DebugString(true), expected_str); - - std::string expected_json = "{\"EditNumber\": 4, \"WalDeletion\": "; - { - std::stringstream ss; - ss << "{\"LogNumber\": " << kLogNumbers[n - 1] << "}"; - expected_json += ss.str(); - } - expected_json += ", \"ColumnFamily\": 0}"; - ASSERT_EQ(edit.DebugJSON(4, true), expected_json); -} - -TEST_F(VersionEditTest, FullHistoryTsLow) { - VersionEdit edit; - ASSERT_FALSE(edit.HasFullHistoryTsLow()); - std::string ts = test::EncodeInt(0); - edit.SetFullHistoryTsLow(ts); - TestEncodeDecode(edit); -} - -// Tests that if RocksDB is downgraded, the new types of VersionEdits -// that have a tag larger than kTagSafeIgnoreMask can be safely ignored. -TEST_F(VersionEditTest, IgnorableTags) { - SyncPoint::GetInstance()->SetCallBack( - "VersionEdit::EncodeTo:IgnoreIgnorableTags", [&](void* arg) { - bool* ignore = static_cast(arg); - *ignore = true; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - constexpr uint64_t kPrevLogNumber = 100; - constexpr uint64_t kLogNumber = 200; - constexpr uint64_t kNextFileNumber = 300; - constexpr uint64_t kColumnFamilyId = 400; - - VersionEdit edit; - // Add some ignorable entries. - for (int i = 0; i < 2; i++) { - edit.AddWal(i + 1, WalMetadata(i + 2)); - } - edit.SetDBId("db_id"); - // Add unignorable entries. - edit.SetPrevLogNumber(kPrevLogNumber); - edit.SetLogNumber(kLogNumber); - // Add more ignorable entries. - edit.DeleteWalsBefore(100); - // Add unignorable entry. - edit.SetNextFile(kNextFileNumber); - // Add more ignorable entries. - edit.SetFullHistoryTsLow("ts"); - // Add unignorable entry. - edit.SetColumnFamily(kColumnFamilyId); - - std::string encoded; - ASSERT_TRUE(edit.EncodeTo(&encoded)); - - VersionEdit decoded; - ASSERT_OK(decoded.DecodeFrom(encoded)); - - // Check that all ignorable entries are ignored. - ASSERT_FALSE(decoded.HasDbId()); - ASSERT_FALSE(decoded.HasFullHistoryTsLow()); - ASSERT_FALSE(decoded.IsWalAddition()); - ASSERT_FALSE(decoded.IsWalDeletion()); - ASSERT_TRUE(decoded.GetWalAdditions().empty()); - ASSERT_TRUE(decoded.GetWalDeletion().IsEmpty()); - - // Check that unignorable entries are still present. - ASSERT_EQ(edit.GetPrevLogNumber(), kPrevLogNumber); - ASSERT_EQ(edit.GetLogNumber(), kLogNumber); - ASSERT_EQ(edit.GetNextFile(), kNextFileNumber); - ASSERT_EQ(edit.GetColumnFamily(), kColumnFamilyId); - - SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST(FileMetaDataTest, UpdateBoundariesBlobIndex) { - FileMetaData meta; - - { - constexpr uint64_t file_number = 10; - constexpr uint32_t path_id = 0; - constexpr uint64_t file_size = 0; - - meta.fd = FileDescriptor(file_number, path_id, file_size); - } - - constexpr char key[] = "foo"; - - constexpr uint64_t expected_oldest_blob_file_number = 20; - - // Plain old value (does not affect oldest_blob_file_number) - { - constexpr char value[] = "value"; - constexpr SequenceNumber seq = 200; - - ASSERT_OK(meta.UpdateBoundaries(key, value, seq, kTypeValue)); - ASSERT_EQ(meta.oldest_blob_file_number, kInvalidBlobFileNumber); - } - - // Non-inlined, non-TTL blob index (sets oldest_blob_file_number) - { - constexpr uint64_t blob_file_number = 25; - static_assert(blob_file_number > expected_oldest_blob_file_number, - "unexpected"); - - constexpr uint64_t offset = 1000; - constexpr uint64_t size = 100; - - std::string blob_index; - BlobIndex::EncodeBlob(&blob_index, blob_file_number, offset, size, - kNoCompression); - - constexpr SequenceNumber seq = 201; - - ASSERT_OK(meta.UpdateBoundaries(key, blob_index, seq, kTypeBlobIndex)); - ASSERT_EQ(meta.oldest_blob_file_number, blob_file_number); - } - - // Another one, with the oldest blob file number (updates - // oldest_blob_file_number) - { - constexpr uint64_t offset = 2000; - constexpr uint64_t size = 300; - - std::string blob_index; - BlobIndex::EncodeBlob(&blob_index, expected_oldest_blob_file_number, offset, - size, kNoCompression); - - constexpr SequenceNumber seq = 202; - - ASSERT_OK(meta.UpdateBoundaries(key, blob_index, seq, kTypeBlobIndex)); - ASSERT_EQ(meta.oldest_blob_file_number, expected_oldest_blob_file_number); - } - - // Inlined TTL blob index (does not affect oldest_blob_file_number) - { - constexpr uint64_t expiration = 9876543210; - constexpr char value[] = "value"; - - std::string blob_index; - BlobIndex::EncodeInlinedTTL(&blob_index, expiration, value); - - constexpr SequenceNumber seq = 203; - - ASSERT_OK(meta.UpdateBoundaries(key, blob_index, seq, kTypeBlobIndex)); - ASSERT_EQ(meta.oldest_blob_file_number, expected_oldest_blob_file_number); - } - - // Non-inlined TTL blob index (does not affect oldest_blob_file_number, even - // though file number is smaller) - { - constexpr uint64_t expiration = 9876543210; - constexpr uint64_t blob_file_number = 15; - static_assert(blob_file_number < expected_oldest_blob_file_number, - "unexpected"); - - constexpr uint64_t offset = 2000; - constexpr uint64_t size = 500; - - std::string blob_index; - BlobIndex::EncodeBlobTTL(&blob_index, expiration, blob_file_number, offset, - size, kNoCompression); - - constexpr SequenceNumber seq = 204; - - ASSERT_OK(meta.UpdateBoundaries(key, blob_index, seq, kTypeBlobIndex)); - ASSERT_EQ(meta.oldest_blob_file_number, expected_oldest_blob_file_number); - } - - // Corrupt blob index - { - constexpr char corrupt_blob_index[] = "!corrupt!"; - constexpr SequenceNumber seq = 205; - - ASSERT_TRUE( - meta.UpdateBoundaries(key, corrupt_blob_index, seq, kTypeBlobIndex) - .IsCorruption()); - ASSERT_EQ(meta.oldest_blob_file_number, expected_oldest_blob_file_number); - } - - // Invalid blob file number - { - constexpr uint64_t offset = 10000; - constexpr uint64_t size = 1000; - - std::string blob_index; - BlobIndex::EncodeBlob(&blob_index, kInvalidBlobFileNumber, offset, size, - kNoCompression); - - constexpr SequenceNumber seq = 206; - - ASSERT_TRUE(meta.UpdateBoundaries(key, blob_index, seq, kTypeBlobIndex) - .IsCorruption()); - ASSERT_EQ(meta.oldest_blob_file_number, expected_oldest_blob_file_number); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/version_set_test.cc b/db/version_set_test.cc deleted file mode 100644 index a83fabcd0..000000000 --- a/db/version_set_test.cc +++ /dev/null @@ -1,3619 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db/version_set.h" - -#include - -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/log_writer.h" -#include "db/version_edit.h" -#include "rocksdb/advanced_options.h" -#include "rocksdb/convenience.h" -#include "rocksdb/file_system.h" -#include "table/block_based/block_based_table_factory.h" -#include "table/mock_table.h" -#include "table/unique_id_impl.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class GenerateLevelFilesBriefTest : public testing::Test { - public: - std::vector files_; - LevelFilesBrief file_level_; - Arena arena_; - - GenerateLevelFilesBriefTest() {} - - ~GenerateLevelFilesBriefTest() override { - for (size_t i = 0; i < files_.size(); i++) { - delete files_[i]; - } - } - - void Add(const char* smallest, const char* largest, - SequenceNumber smallest_seq = 100, - SequenceNumber largest_seq = 100) { - FileMetaData* f = new FileMetaData( - files_.size() + 1, 0, 0, - InternalKey(smallest, smallest_seq, kTypeValue), - InternalKey(largest, largest_seq, kTypeValue), smallest_seq, - largest_seq, /* marked_for_compact */ false, Temperature::kUnknown, - kInvalidBlobFileNumber, kUnknownOldestAncesterTime, - kUnknownFileCreationTime, kUnknownEpochNumber, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - files_.push_back(f); - } - - int Compare() { - int diff = 0; - for (size_t i = 0; i < files_.size(); i++) { - if (file_level_.files[i].fd.GetNumber() != files_[i]->fd.GetNumber()) { - diff++; - } - } - return diff; - } -}; - -TEST_F(GenerateLevelFilesBriefTest, Empty) { - DoGenerateLevelFilesBrief(&file_level_, files_, &arena_); - ASSERT_EQ(0u, file_level_.num_files); - ASSERT_EQ(0, Compare()); -} - -TEST_F(GenerateLevelFilesBriefTest, Single) { - Add("p", "q"); - DoGenerateLevelFilesBrief(&file_level_, files_, &arena_); - ASSERT_EQ(1u, file_level_.num_files); - ASSERT_EQ(0, Compare()); -} - -TEST_F(GenerateLevelFilesBriefTest, Multiple) { - Add("150", "200"); - Add("200", "250"); - Add("300", "350"); - Add("400", "450"); - DoGenerateLevelFilesBrief(&file_level_, files_, &arena_); - ASSERT_EQ(4u, file_level_.num_files); - ASSERT_EQ(0, Compare()); -} - -class CountingLogger : public Logger { - public: - CountingLogger() : log_count(0) {} - using Logger::Logv; - void Logv(const char* /*format*/, va_list /*ap*/) override { log_count++; } - int log_count; -}; - -Options GetOptionsWithNumLevels(int num_levels, - std::shared_ptr logger) { - Options opt; - opt.num_levels = num_levels; - opt.info_log = logger; - return opt; -} - -class VersionStorageInfoTestBase : public testing::Test { - public: - const Comparator* ucmp_; - InternalKeyComparator icmp_; - std::shared_ptr logger_; - Options options_; - ImmutableOptions ioptions_; - MutableCFOptions mutable_cf_options_; - VersionStorageInfo vstorage_; - - InternalKey GetInternalKey(const char* ukey, - SequenceNumber smallest_seq = 100) { - return InternalKey(ukey, smallest_seq, kTypeValue); - } - - explicit VersionStorageInfoTestBase(const Comparator* ucmp) - : ucmp_(ucmp), - icmp_(ucmp_), - logger_(new CountingLogger()), - options_(GetOptionsWithNumLevels(6, logger_)), - ioptions_(options_), - mutable_cf_options_(options_), - vstorage_(&icmp_, ucmp_, 6, kCompactionStyleLevel, - /*src_vstorage=*/nullptr, - /*_force_consistency_checks=*/false) {} - - ~VersionStorageInfoTestBase() override { - for (int i = 0; i < vstorage_.num_levels(); ++i) { - for (auto* f : vstorage_.LevelFiles(i)) { - if (--f->refs == 0) { - delete f; - } - } - } - } - - void Add(int level, uint32_t file_number, const char* smallest, - const char* largest, uint64_t file_size = 0, - uint64_t oldest_blob_file_number = kInvalidBlobFileNumber, - uint64_t compensated_range_deletion_size = 0) { - constexpr SequenceNumber dummy_seq = 0; - - Add(level, file_number, GetInternalKey(smallest, dummy_seq), - GetInternalKey(largest, dummy_seq), file_size, oldest_blob_file_number, - compensated_range_deletion_size); - } - - void Add(int level, uint32_t file_number, const InternalKey& smallest, - const InternalKey& largest, uint64_t file_size = 0, - uint64_t oldest_blob_file_number = kInvalidBlobFileNumber, - uint64_t compensated_range_deletion_size = 0) { - assert(level < vstorage_.num_levels()); - FileMetaData* f = new FileMetaData( - file_number, 0, file_size, smallest, largest, /* smallest_seq */ 0, - /* largest_seq */ 0, /* marked_for_compact */ false, - Temperature::kUnknown, oldest_blob_file_number, - kUnknownOldestAncesterTime, kUnknownFileCreationTime, - kUnknownEpochNumber, kUnknownFileChecksum, kUnknownFileChecksumFuncName, - kNullUniqueId64x2, compensated_range_deletion_size); - vstorage_.AddFile(level, f); - } - - void AddBlob(uint64_t blob_file_number, uint64_t total_blob_count, - uint64_t total_blob_bytes, - BlobFileMetaData::LinkedSsts linked_ssts, - uint64_t garbage_blob_count, uint64_t garbage_blob_bytes) { - auto shared_meta = SharedBlobFileMetaData::Create( - blob_file_number, total_blob_count, total_blob_bytes, - /* checksum_method */ std::string(), - /* checksum_value */ std::string()); - auto meta = - BlobFileMetaData::Create(std::move(shared_meta), std::move(linked_ssts), - garbage_blob_count, garbage_blob_bytes); - - vstorage_.AddBlobFile(std::move(meta)); - } - - void UpdateVersionStorageInfo() { - vstorage_.PrepareForVersionAppend(ioptions_, mutable_cf_options_); - vstorage_.SetFinalized(); - } - - std::string GetOverlappingFiles(int level, const InternalKey& begin, - const InternalKey& end) { - std::vector inputs; - vstorage_.GetOverlappingInputs(level, &begin, &end, &inputs); - - std::string result; - for (size_t i = 0; i < inputs.size(); ++i) { - if (i > 0) { - result += ","; - } - AppendNumberTo(&result, inputs[i]->fd.GetNumber()); - } - return result; - } -}; - -class VersionStorageInfoTest : public VersionStorageInfoTestBase { - public: - VersionStorageInfoTest() : VersionStorageInfoTestBase(BytewiseComparator()) {} - - ~VersionStorageInfoTest() override {} -}; - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelStatic) { - ioptions_.level_compaction_dynamic_level_bytes = false; - mutable_cf_options_.max_bytes_for_level_base = 10; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - - Add(4, 100U, "1", "2", 100U); - Add(5, 101U, "1", "2", 100U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(vstorage_.MaxBytesForLevel(1), 10U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(2), 50U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(3), 250U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1250U); - - ASSERT_EQ(0, logger_->log_count); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamic_1) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 1000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - - Add(5, 1U, "1", "2", 500U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(0, logger_->log_count); - ASSERT_EQ(vstorage_.base_level(), 5); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamic_2) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 1000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - - Add(5, 1U, "1", "2", 500U); - Add(5, 2U, "3", "4", 550U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(0, logger_->log_count); - ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1000U); - ASSERT_EQ(vstorage_.base_level(), 4); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamic_3) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 1000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - - Add(5, 1U, "1", "2", 500U); - Add(5, 2U, "3", "4", 550U); - Add(4, 3U, "3", "4", 550U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(0, logger_->log_count); - ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1000U); - ASSERT_EQ(vstorage_.base_level(), 4); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamic_4) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 1000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - - Add(5, 1U, "1", "2", 500U); - Add(5, 2U, "3", "4", 550U); - Add(4, 3U, "3", "4", 550U); - Add(3, 4U, "3", "4", 250U); - Add(3, 5U, "5", "7", 300U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(1, logger_->log_count); - ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 1005U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(3), 1000U); - ASSERT_EQ(vstorage_.base_level(), 3); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamic_5) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 1000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - - Add(5, 1U, "1", "2", 500U); - Add(5, 2U, "3", "4", 550U); - Add(4, 3U, "3", "4", 550U); - Add(3, 4U, "3", "4", 250U); - Add(3, 5U, "5", "7", 300U); - Add(1, 6U, "3", "4", 5U); - Add(1, 7U, "8", "9", 5U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(1, logger_->log_count); - ASSERT_GT(vstorage_.MaxBytesForLevel(4), 1005U); - ASSERT_GT(vstorage_.MaxBytesForLevel(3), 1005U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(2), 1005U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(1), 1000U); - ASSERT_EQ(vstorage_.base_level(), 1); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicLotsOfData) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 100; - mutable_cf_options_.max_bytes_for_level_multiplier = 2; - - Add(0, 1U, "1", "2", 50U); - Add(1, 2U, "1", "2", 50U); - Add(2, 3U, "1", "2", 500U); - Add(3, 4U, "1", "2", 500U); - Add(4, 5U, "1", "2", 1700U); - Add(5, 6U, "1", "2", 500U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 800U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(3), 400U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(2), 200U); - ASSERT_EQ(vstorage_.MaxBytesForLevel(1), 100U); - ASSERT_EQ(vstorage_.base_level(), 1); - ASSERT_EQ(0, logger_->log_count); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicLargeLevel) { - uint64_t kOneGB = 1000U * 1000U * 1000U; - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 10U * kOneGB; - mutable_cf_options_.max_bytes_for_level_multiplier = 10; - - Add(0, 1U, "1", "2", 50U); - Add(3, 4U, "1", "2", 32U * kOneGB); - Add(4, 5U, "1", "2", 500U * kOneGB); - Add(5, 6U, "1", "2", 3000U * kOneGB); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(vstorage_.MaxBytesForLevel(5), 3000U * kOneGB); - ASSERT_EQ(vstorage_.MaxBytesForLevel(4), 300U * kOneGB); - ASSERT_EQ(vstorage_.MaxBytesForLevel(3), 30U * kOneGB); - ASSERT_EQ(vstorage_.MaxBytesForLevel(2), 10U * kOneGB); - ASSERT_EQ(vstorage_.base_level(), 2); - ASSERT_EQ(0, logger_->log_count); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_1) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 40000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - mutable_cf_options_.level0_file_num_compaction_trigger = 2; - - Add(0, 1U, "1", "2", 10000U); - Add(0, 2U, "1", "2", 10000U); - Add(0, 3U, "1", "2", 10000U); - - Add(5, 4U, "1", "2", 1286250U); - Add(4, 5U, "1", "2", 200000U); - Add(3, 6U, "1", "2", 40000U); - Add(2, 7U, "1", "2", 8000U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(0, logger_->log_count); - ASSERT_EQ(2, vstorage_.base_level()); - // level multiplier should be 3.5 - ASSERT_EQ(vstorage_.level_multiplier(), 5.0); - ASSERT_EQ(40000U, vstorage_.MaxBytesForLevel(2)); - ASSERT_EQ(51450U, vstorage_.MaxBytesForLevel(3)); - ASSERT_EQ(257250U, vstorage_.MaxBytesForLevel(4)); - - vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_); - // Only L0 hits compaction. - ASSERT_EQ(vstorage_.CompactionScoreLevel(0), 0); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_2) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 10000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - mutable_cf_options_.level0_file_num_compaction_trigger = 4; - - Add(0, 11U, "1", "2", 10000U); - Add(0, 12U, "1", "2", 10000U); - Add(0, 13U, "1", "2", 10000U); - - // Level size should be around 10,000, 10,290, 51,450, 257,250 - Add(5, 4U, "1", "2", 1286250U); - Add(4, 5U, "1", "2", 258000U); // unadjusted score 1.003 - Add(3, 6U, "1", "2", 53000U); // unadjusted score 1.03 - Add(2, 7U, "1", "2", 20000U); // unadjusted score 1.94 - - UpdateVersionStorageInfo(); - - ASSERT_EQ(0, logger_->log_count); - ASSERT_EQ(1, vstorage_.base_level()); - ASSERT_EQ(10000U, vstorage_.MaxBytesForLevel(1)); - ASSERT_EQ(10290U, vstorage_.MaxBytesForLevel(2)); - ASSERT_EQ(51450U, vstorage_.MaxBytesForLevel(3)); - ASSERT_EQ(257250U, vstorage_.MaxBytesForLevel(4)); - - vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_); - // Although L2 and l3 have higher unadjusted compaction score, considering - // a relatively large L0 being compacted down soon, L4 is picked up for - // compaction. - // L0 is still picked up for oversizing. - ASSERT_EQ(0, vstorage_.CompactionScoreLevel(0)); - ASSERT_EQ(4, vstorage_.CompactionScoreLevel(1)); -} - -TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_3) { - ioptions_.level_compaction_dynamic_level_bytes = true; - mutable_cf_options_.max_bytes_for_level_base = 20000; - mutable_cf_options_.max_bytes_for_level_multiplier = 5; - mutable_cf_options_.level0_file_num_compaction_trigger = 5; - - Add(0, 11U, "1", "2", 2500U); - Add(0, 12U, "1", "2", 2500U); - Add(0, 13U, "1", "2", 2500U); - Add(0, 14U, "1", "2", 2500U); - - // Level size should be around 20,000, 53000, 258000 - Add(5, 4U, "1", "2", 1286250U); - Add(4, 5U, "1", "2", 260000U); // Unadjusted score 1.01, adjusted about 4.3 - Add(3, 6U, "1", "2", 85000U); // Unadjusted score 1.42, adjusted about 11.6 - Add(2, 7U, "1", "2", 30000); // Unadjusted score 1.5, adjusted about 10.0 - - UpdateVersionStorageInfo(); - - ASSERT_EQ(0, logger_->log_count); - ASSERT_EQ(2, vstorage_.base_level()); - ASSERT_EQ(20000U, vstorage_.MaxBytesForLevel(2)); - - vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_); - // Although L2 has higher unadjusted compaction score, considering - // a relatively large L0 being compacted down soon, L3 is picked up for - // compaction. - - ASSERT_EQ(3, vstorage_.CompactionScoreLevel(0)); - ASSERT_EQ(2, vstorage_.CompactionScoreLevel(1)); - ASSERT_EQ(4, vstorage_.CompactionScoreLevel(2)); -} - -TEST_F(VersionStorageInfoTest, EstimateLiveDataSize) { - // Test whether the overlaps are detected as expected - Add(1, 1U, "4", "7", 1U); // Perfect overlap with last level - Add(2, 2U, "3", "5", 1U); // Partial overlap with last level - Add(2, 3U, "6", "8", 1U); // Partial overlap with last level - Add(3, 4U, "1", "9", 1U); // Contains range of last level - Add(4, 5U, "4", "5", 1U); // Inside range of last level - Add(4, 6U, "6", "7", 1U); // Inside range of last level - Add(5, 7U, "4", "7", 10U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(10U, vstorage_.EstimateLiveDataSize()); -} - -TEST_F(VersionStorageInfoTest, EstimateLiveDataSize2) { - Add(0, 1U, "9", "9", 1U); // Level 0 is not ordered - Add(0, 2U, "5", "6", 1U); // Ignored because of [5,6] in l1 - Add(1, 3U, "1", "2", 1U); // Ignored because of [2,3] in l2 - Add(1, 4U, "3", "4", 1U); // Ignored because of [2,3] in l2 - Add(1, 5U, "5", "6", 1U); - Add(2, 6U, "2", "3", 1U); - Add(3, 7U, "7", "8", 1U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(4U, vstorage_.EstimateLiveDataSize()); -} - -TEST_F(VersionStorageInfoTest, GetOverlappingInputs) { - // Two files that overlap at the range deletion tombstone sentinel. - Add(1, 1U, {"a", 0, kTypeValue}, - {"b", kMaxSequenceNumber, kTypeRangeDeletion}, 1); - Add(1, 2U, {"b", 0, kTypeValue}, {"c", 0, kTypeValue}, 1); - // Two files that overlap at the same user key. - Add(1, 3U, {"d", 0, kTypeValue}, {"e", kMaxSequenceNumber, kTypeValue}, 1); - Add(1, 4U, {"e", 0, kTypeValue}, {"f", 0, kTypeValue}, 1); - // Two files that do not overlap. - Add(1, 5U, {"g", 0, kTypeValue}, {"h", 0, kTypeValue}, 1); - Add(1, 6U, {"i", 0, kTypeValue}, {"j", 0, kTypeValue}, 1); - - UpdateVersionStorageInfo(); - - ASSERT_EQ("1,2", - GetOverlappingFiles(1, {"a", 0, kTypeValue}, {"b", 0, kTypeValue})); - ASSERT_EQ("1", - GetOverlappingFiles(1, {"a", 0, kTypeValue}, - {"b", kMaxSequenceNumber, kTypeRangeDeletion})); - ASSERT_EQ("2", GetOverlappingFiles(1, {"b", kMaxSequenceNumber, kTypeValue}, - {"c", 0, kTypeValue})); - ASSERT_EQ("3,4", - GetOverlappingFiles(1, {"d", 0, kTypeValue}, {"e", 0, kTypeValue})); - ASSERT_EQ("3", - GetOverlappingFiles(1, {"d", 0, kTypeValue}, - {"e", kMaxSequenceNumber, kTypeRangeDeletion})); - ASSERT_EQ("3,4", GetOverlappingFiles(1, {"e", kMaxSequenceNumber, kTypeValue}, - {"f", 0, kTypeValue})); - ASSERT_EQ("3,4", - GetOverlappingFiles(1, {"e", 0, kTypeValue}, {"f", 0, kTypeValue})); - ASSERT_EQ("5", - GetOverlappingFiles(1, {"g", 0, kTypeValue}, {"h", 0, kTypeValue})); - ASSERT_EQ("6", - GetOverlappingFiles(1, {"i", 0, kTypeValue}, {"j", 0, kTypeValue})); -} - -TEST_F(VersionStorageInfoTest, FileLocationAndMetaDataByNumber) { - Add(0, 11U, "1", "2", 5000U); - Add(0, 12U, "1", "2", 5000U); - - Add(2, 7U, "1", "2", 8000U); - - UpdateVersionStorageInfo(); - - ASSERT_EQ(vstorage_.GetFileLocation(11U), - VersionStorageInfo::FileLocation(0, 0)); - ASSERT_NE(vstorage_.GetFileMetaDataByNumber(11U), nullptr); - - ASSERT_EQ(vstorage_.GetFileLocation(12U), - VersionStorageInfo::FileLocation(0, 1)); - ASSERT_NE(vstorage_.GetFileMetaDataByNumber(12U), nullptr); - - ASSERT_EQ(vstorage_.GetFileLocation(7U), - VersionStorageInfo::FileLocation(2, 0)); - ASSERT_NE(vstorage_.GetFileMetaDataByNumber(7U), nullptr); - - ASSERT_FALSE(vstorage_.GetFileLocation(999U).IsValid()); - ASSERT_EQ(vstorage_.GetFileMetaDataByNumber(999U), nullptr); -} - -TEST_F(VersionStorageInfoTest, ForcedBlobGCEmpty) { - // No SST or blob files in VersionStorageInfo - UpdateVersionStorageInfo(); - - constexpr double age_cutoff = 0.5; - constexpr double force_threshold = 0.75; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); -} - -TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) { - // Test the edge case when all blob files are part of the oldest batch. - // We have one L0 SST file #1, and four blob files #10, #11, #12, and #13. - // The oldest blob file used by SST #1 is blob file #10. - - constexpr int level = 0; - - constexpr uint64_t sst = 1; - - constexpr uint64_t first_blob = 10; - constexpr uint64_t second_blob = 11; - constexpr uint64_t third_blob = 12; - constexpr uint64_t fourth_blob = 13; - - { - constexpr char smallest[] = "bar1"; - constexpr char largest[] = "foo1"; - constexpr uint64_t file_size = 1000; - - Add(level, sst, smallest, largest, file_size, first_blob); - } - - { - constexpr uint64_t total_blob_count = 10; - constexpr uint64_t total_blob_bytes = 100000; - constexpr uint64_t garbage_blob_count = 2; - constexpr uint64_t garbage_blob_bytes = 15000; - - AddBlob(first_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{sst}, garbage_blob_count, - garbage_blob_bytes); - } - - { - constexpr uint64_t total_blob_count = 4; - constexpr uint64_t total_blob_bytes = 400000; - constexpr uint64_t garbage_blob_count = 3; - constexpr uint64_t garbage_blob_bytes = 235000; - - AddBlob(second_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{}, garbage_blob_count, - garbage_blob_bytes); - } - - { - constexpr uint64_t total_blob_count = 20; - constexpr uint64_t total_blob_bytes = 1000000; - constexpr uint64_t garbage_blob_count = 8; - constexpr uint64_t garbage_blob_bytes = 400000; - - AddBlob(third_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{}, garbage_blob_count, - garbage_blob_bytes); - } - - { - constexpr uint64_t total_blob_count = 128; - constexpr uint64_t total_blob_bytes = 1000000; - constexpr uint64_t garbage_blob_count = 67; - constexpr uint64_t garbage_blob_bytes = 600000; - - AddBlob(fourth_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{}, garbage_blob_count, - garbage_blob_bytes); - } - - UpdateVersionStorageInfo(); - - assert(vstorage_.num_levels() > 0); - const auto& level_files = vstorage_.LevelFiles(level); - - assert(level_files.size() == 1); - assert(level_files[0] && level_files[0]->fd.GetNumber() == sst); - - // No blob files eligible for GC due to the age cutoff - - { - constexpr double age_cutoff = 0.1; - constexpr double force_threshold = 0.0; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Part of the oldest batch of blob files (specifically, #12 and #13) is - // ineligible for GC due to the age cutoff - - { - constexpr double age_cutoff = 0.5; - constexpr double force_threshold = 0.0; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Oldest batch is eligible based on age cutoff but its overall garbage ratio - // is below threshold - - { - constexpr double age_cutoff = 1.0; - constexpr double force_threshold = 0.6; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Oldest batch is eligible based on age cutoff and its overall garbage ratio - // meets threshold - - { - constexpr double age_cutoff = 1.0; - constexpr double force_threshold = 0.5; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC(); - ASSERT_EQ(ssts_to_be_compacted.size(), 1); - - const autovector> - expected_ssts_to_be_compacted{{level, level_files[0]}}; - - ASSERT_EQ(ssts_to_be_compacted[0], expected_ssts_to_be_compacted[0]); - } -} - -TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) { - // Add three L0 SSTs (1, 2, and 3) and four blob files (10, 11, 12, and 13). - // The first two SSTs have the same oldest blob file, namely, the very oldest - // one (10), while the third SST's oldest blob file reference points to the - // third blob file (12). Thus, the oldest batch of blob files contains the - // first two blob files 10 and 11, and assuming they are eligible for GC based - // on the age cutoff, compacting away the SSTs 1 and 2 will eliminate them. - - constexpr int level = 0; - - constexpr uint64_t first_sst = 1; - constexpr uint64_t second_sst = 2; - constexpr uint64_t third_sst = 3; - - constexpr uint64_t first_blob = 10; - constexpr uint64_t second_blob = 11; - constexpr uint64_t third_blob = 12; - constexpr uint64_t fourth_blob = 13; - - { - constexpr char smallest[] = "bar1"; - constexpr char largest[] = "foo1"; - constexpr uint64_t file_size = 1000; - - Add(level, first_sst, smallest, largest, file_size, first_blob); - } - - { - constexpr char smallest[] = "bar2"; - constexpr char largest[] = "foo2"; - constexpr uint64_t file_size = 2000; - - Add(level, second_sst, smallest, largest, file_size, first_blob); - } - - { - constexpr char smallest[] = "bar3"; - constexpr char largest[] = "foo3"; - constexpr uint64_t file_size = 3000; - - Add(level, third_sst, smallest, largest, file_size, third_blob); - } - - { - constexpr uint64_t total_blob_count = 10; - constexpr uint64_t total_blob_bytes = 100000; - constexpr uint64_t garbage_blob_count = 2; - constexpr uint64_t garbage_blob_bytes = 15000; - - AddBlob(first_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{first_sst, second_sst}, - garbage_blob_count, garbage_blob_bytes); - } - - { - constexpr uint64_t total_blob_count = 4; - constexpr uint64_t total_blob_bytes = 400000; - constexpr uint64_t garbage_blob_count = 3; - constexpr uint64_t garbage_blob_bytes = 235000; - - AddBlob(second_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{}, garbage_blob_count, - garbage_blob_bytes); - } - - { - constexpr uint64_t total_blob_count = 20; - constexpr uint64_t total_blob_bytes = 1000000; - constexpr uint64_t garbage_blob_count = 8; - constexpr uint64_t garbage_blob_bytes = 123456; - - AddBlob(third_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{third_sst}, garbage_blob_count, - garbage_blob_bytes); - } - - { - constexpr uint64_t total_blob_count = 128; - constexpr uint64_t total_blob_bytes = 789012345; - constexpr uint64_t garbage_blob_count = 67; - constexpr uint64_t garbage_blob_bytes = 88888888; - - AddBlob(fourth_blob, total_blob_count, total_blob_bytes, - BlobFileMetaData::LinkedSsts{}, garbage_blob_count, - garbage_blob_bytes); - } - - UpdateVersionStorageInfo(); - - assert(vstorage_.num_levels() > 0); - const auto& level_files = vstorage_.LevelFiles(level); - - assert(level_files.size() == 3); - assert(level_files[0] && level_files[0]->fd.GetNumber() == first_sst); - assert(level_files[1] && level_files[1]->fd.GetNumber() == second_sst); - assert(level_files[2] && level_files[2]->fd.GetNumber() == third_sst); - - // No blob files eligible for GC due to the age cutoff - - { - constexpr double age_cutoff = 0.1; - constexpr double force_threshold = 0.0; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Part of the oldest batch of blob files (specifically, the second file) is - // ineligible for GC due to the age cutoff - - { - constexpr double age_cutoff = 0.25; - constexpr double force_threshold = 0.0; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Oldest batch is eligible based on age cutoff but its overall garbage ratio - // is below threshold - - { - constexpr double age_cutoff = 0.5; - constexpr double force_threshold = 0.6; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Oldest batch is eligible based on age cutoff and its overall garbage ratio - // meets threshold - - { - constexpr double age_cutoff = 0.5; - constexpr double force_threshold = 0.5; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC(); - ASSERT_EQ(ssts_to_be_compacted.size(), 2); - - std::sort(ssts_to_be_compacted.begin(), ssts_to_be_compacted.end(), - [](const std::pair& lhs, - const std::pair& rhs) { - assert(lhs.second); - assert(rhs.second); - return lhs.second->fd.GetNumber() < rhs.second->fd.GetNumber(); - }); - - const autovector> - expected_ssts_to_be_compacted{{level, level_files[0]}, - {level, level_files[1]}}; - - ASSERT_EQ(ssts_to_be_compacted[0], expected_ssts_to_be_compacted[0]); - ASSERT_EQ(ssts_to_be_compacted[1], expected_ssts_to_be_compacted[1]); - } - - // Now try the last two cases again with a greater than necessary age cutoff - - // Oldest batch is eligible based on age cutoff but its overall garbage ratio - // is below threshold - - { - constexpr double age_cutoff = 0.75; - constexpr double force_threshold = 0.6; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty()); - } - - // Oldest batch is eligible based on age cutoff and its overall garbage ratio - // meets threshold - - { - constexpr double age_cutoff = 0.75; - constexpr double force_threshold = 0.5; - vstorage_.ComputeFilesMarkedForForcedBlobGC(age_cutoff, force_threshold); - - auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC(); - ASSERT_EQ(ssts_to_be_compacted.size(), 2); - - std::sort(ssts_to_be_compacted.begin(), ssts_to_be_compacted.end(), - [](const std::pair& lhs, - const std::pair& rhs) { - assert(lhs.second); - assert(rhs.second); - return lhs.second->fd.GetNumber() < rhs.second->fd.GetNumber(); - }); - - const autovector> - expected_ssts_to_be_compacted{{level, level_files[0]}, - {level, level_files[1]}}; - - ASSERT_EQ(ssts_to_be_compacted[0], expected_ssts_to_be_compacted[0]); - ASSERT_EQ(ssts_to_be_compacted[1], expected_ssts_to_be_compacted[1]); - } -} - -class VersionStorageInfoTimestampTest : public VersionStorageInfoTestBase { - public: - VersionStorageInfoTimestampTest() - : VersionStorageInfoTestBase(test::BytewiseComparatorWithU64TsWrapper()) { - } - ~VersionStorageInfoTimestampTest() override {} - std::string Timestamp(uint64_t ts) const { - std::string ret; - PutFixed64(&ret, ts); - return ret; - } - std::string PackUserKeyAndTimestamp(const Slice& ukey, uint64_t ts) const { - std::string ret; - ret.assign(ukey.data(), ukey.size()); - PutFixed64(&ret, ts); - return ret; - } -}; - -TEST_F(VersionStorageInfoTimestampTest, GetOverlappingInputs) { - Add(/*level=*/1, /*file_number=*/1, /*smallest=*/ - {PackUserKeyAndTimestamp("a", /*ts=*/9), /*s=*/0, kTypeValue}, - /*largest=*/ - {PackUserKeyAndTimestamp("a", /*ts=*/8), /*s=*/0, kTypeValue}, - /*file_size=*/100); - Add(/*level=*/1, /*file_number=*/2, /*smallest=*/ - {PackUserKeyAndTimestamp("a", /*ts=*/5), /*s=*/0, kTypeValue}, - /*largest=*/ - {PackUserKeyAndTimestamp("b", /*ts=*/10), /*s=*/0, kTypeValue}, - /*file_size=*/100); - Add(/*level=*/1, /*file_number=*/3, /*smallest=*/ - {PackUserKeyAndTimestamp("c", /*ts=*/12), /*s=*/0, kTypeValue}, - /*largest=*/ - {PackUserKeyAndTimestamp("d", /*ts=*/1), /*s=*/0, kTypeValue}, - /*file_size=*/100); - - UpdateVersionStorageInfo(); - - ASSERT_EQ( - "1,2", - GetOverlappingFiles( - /*level=*/1, - {PackUserKeyAndTimestamp("a", /*ts=*/12), /*s=*/0, kTypeValue}, - {PackUserKeyAndTimestamp("a", /*ts=*/11), /*s=*/0, kTypeValue})); - ASSERT_EQ("3", - GetOverlappingFiles( - /*level=*/1, - {PackUserKeyAndTimestamp("c", /*ts=*/15), /*s=*/0, kTypeValue}, - {PackUserKeyAndTimestamp("c", /*ts=*/2), /*s=*/0, kTypeValue})); -} - -class FindLevelFileTest : public testing::Test { - public: - LevelFilesBrief file_level_; - bool disjoint_sorted_files_; - Arena arena_; - - FindLevelFileTest() : disjoint_sorted_files_(true) {} - - ~FindLevelFileTest() override {} - - void LevelFileInit(size_t num = 0) { - char* mem = arena_.AllocateAligned(num * sizeof(FdWithKeyRange)); - file_level_.files = new (mem) FdWithKeyRange[num]; - file_level_.num_files = 0; - } - - void Add(const char* smallest, const char* largest, - SequenceNumber smallest_seq = 100, - SequenceNumber largest_seq = 100) { - InternalKey smallest_key = InternalKey(smallest, smallest_seq, kTypeValue); - InternalKey largest_key = InternalKey(largest, largest_seq, kTypeValue); - - Slice smallest_slice = smallest_key.Encode(); - Slice largest_slice = largest_key.Encode(); - - char* mem = - arena_.AllocateAligned(smallest_slice.size() + largest_slice.size()); - memcpy(mem, smallest_slice.data(), smallest_slice.size()); - memcpy(mem + smallest_slice.size(), largest_slice.data(), - largest_slice.size()); - - // add to file_level_ - size_t num = file_level_.num_files; - auto& file = file_level_.files[num]; - file.fd = FileDescriptor(num + 1, 0, 0); - file.smallest_key = Slice(mem, smallest_slice.size()); - file.largest_key = Slice(mem + smallest_slice.size(), largest_slice.size()); - file_level_.num_files++; - } - - int Find(const char* key) { - InternalKey target(key, 100, kTypeValue); - InternalKeyComparator cmp(BytewiseComparator()); - return FindFile(cmp, file_level_, target.Encode()); - } - - bool Overlaps(const char* smallest, const char* largest) { - InternalKeyComparator cmp(BytewiseComparator()); - Slice s(smallest != nullptr ? smallest : ""); - Slice l(largest != nullptr ? largest : ""); - return SomeFileOverlapsRange(cmp, disjoint_sorted_files_, file_level_, - (smallest != nullptr ? &s : nullptr), - (largest != nullptr ? &l : nullptr)); - } -}; - -TEST_F(FindLevelFileTest, LevelEmpty) { - LevelFileInit(0); - - ASSERT_EQ(0, Find("foo")); - ASSERT_TRUE(!Overlaps("a", "z")); - ASSERT_TRUE(!Overlaps(nullptr, "z")); - ASSERT_TRUE(!Overlaps("a", nullptr)); - ASSERT_TRUE(!Overlaps(nullptr, nullptr)); -} - -TEST_F(FindLevelFileTest, LevelSingle) { - LevelFileInit(1); - - Add("p", "q"); - ASSERT_EQ(0, Find("a")); - ASSERT_EQ(0, Find("p")); - ASSERT_EQ(0, Find("p1")); - ASSERT_EQ(0, Find("q")); - ASSERT_EQ(1, Find("q1")); - ASSERT_EQ(1, Find("z")); - - ASSERT_TRUE(!Overlaps("a", "b")); - ASSERT_TRUE(!Overlaps("z1", "z2")); - ASSERT_TRUE(Overlaps("a", "p")); - ASSERT_TRUE(Overlaps("a", "q")); - ASSERT_TRUE(Overlaps("a", "z")); - ASSERT_TRUE(Overlaps("p", "p1")); - ASSERT_TRUE(Overlaps("p", "q")); - ASSERT_TRUE(Overlaps("p", "z")); - ASSERT_TRUE(Overlaps("p1", "p2")); - ASSERT_TRUE(Overlaps("p1", "z")); - ASSERT_TRUE(Overlaps("q", "q")); - ASSERT_TRUE(Overlaps("q", "q1")); - - ASSERT_TRUE(!Overlaps(nullptr, "j")); - ASSERT_TRUE(!Overlaps("r", nullptr)); - ASSERT_TRUE(Overlaps(nullptr, "p")); - ASSERT_TRUE(Overlaps(nullptr, "p1")); - ASSERT_TRUE(Overlaps("q", nullptr)); - ASSERT_TRUE(Overlaps(nullptr, nullptr)); -} - -TEST_F(FindLevelFileTest, LevelMultiple) { - LevelFileInit(4); - - Add("150", "200"); - Add("200", "250"); - Add("300", "350"); - Add("400", "450"); - ASSERT_EQ(0, Find("100")); - ASSERT_EQ(0, Find("150")); - ASSERT_EQ(0, Find("151")); - ASSERT_EQ(0, Find("199")); - ASSERT_EQ(0, Find("200")); - ASSERT_EQ(1, Find("201")); - ASSERT_EQ(1, Find("249")); - ASSERT_EQ(1, Find("250")); - ASSERT_EQ(2, Find("251")); - ASSERT_EQ(2, Find("299")); - ASSERT_EQ(2, Find("300")); - ASSERT_EQ(2, Find("349")); - ASSERT_EQ(2, Find("350")); - ASSERT_EQ(3, Find("351")); - ASSERT_EQ(3, Find("400")); - ASSERT_EQ(3, Find("450")); - ASSERT_EQ(4, Find("451")); - - ASSERT_TRUE(!Overlaps("100", "149")); - ASSERT_TRUE(!Overlaps("251", "299")); - ASSERT_TRUE(!Overlaps("451", "500")); - ASSERT_TRUE(!Overlaps("351", "399")); - - ASSERT_TRUE(Overlaps("100", "150")); - ASSERT_TRUE(Overlaps("100", "200")); - ASSERT_TRUE(Overlaps("100", "300")); - ASSERT_TRUE(Overlaps("100", "400")); - ASSERT_TRUE(Overlaps("100", "500")); - ASSERT_TRUE(Overlaps("375", "400")); - ASSERT_TRUE(Overlaps("450", "450")); - ASSERT_TRUE(Overlaps("450", "500")); -} - -TEST_F(FindLevelFileTest, LevelMultipleNullBoundaries) { - LevelFileInit(4); - - Add("150", "200"); - Add("200", "250"); - Add("300", "350"); - Add("400", "450"); - ASSERT_TRUE(!Overlaps(nullptr, "149")); - ASSERT_TRUE(!Overlaps("451", nullptr)); - ASSERT_TRUE(Overlaps(nullptr, nullptr)); - ASSERT_TRUE(Overlaps(nullptr, "150")); - ASSERT_TRUE(Overlaps(nullptr, "199")); - ASSERT_TRUE(Overlaps(nullptr, "200")); - ASSERT_TRUE(Overlaps(nullptr, "201")); - ASSERT_TRUE(Overlaps(nullptr, "400")); - ASSERT_TRUE(Overlaps(nullptr, "800")); - ASSERT_TRUE(Overlaps("100", nullptr)); - ASSERT_TRUE(Overlaps("200", nullptr)); - ASSERT_TRUE(Overlaps("449", nullptr)); - ASSERT_TRUE(Overlaps("450", nullptr)); -} - -TEST_F(FindLevelFileTest, LevelOverlapSequenceChecks) { - LevelFileInit(1); - - Add("200", "200", 5000, 3000); - ASSERT_TRUE(!Overlaps("199", "199")); - ASSERT_TRUE(!Overlaps("201", "300")); - ASSERT_TRUE(Overlaps("200", "200")); - ASSERT_TRUE(Overlaps("190", "200")); - ASSERT_TRUE(Overlaps("200", "210")); -} - -TEST_F(FindLevelFileTest, LevelOverlappingFiles) { - LevelFileInit(2); - - Add("150", "600"); - Add("400", "500"); - disjoint_sorted_files_ = false; - ASSERT_TRUE(!Overlaps("100", "149")); - ASSERT_TRUE(!Overlaps("601", "700")); - ASSERT_TRUE(Overlaps("100", "150")); - ASSERT_TRUE(Overlaps("100", "200")); - ASSERT_TRUE(Overlaps("100", "300")); - ASSERT_TRUE(Overlaps("100", "400")); - ASSERT_TRUE(Overlaps("100", "500")); - ASSERT_TRUE(Overlaps("375", "400")); - ASSERT_TRUE(Overlaps("450", "450")); - ASSERT_TRUE(Overlaps("450", "500")); - ASSERT_TRUE(Overlaps("450", "700")); - ASSERT_TRUE(Overlaps("600", "700")); -} - -class VersionSetTestBase { - public: - const static std::string kColumnFamilyName1; - const static std::string kColumnFamilyName2; - const static std::string kColumnFamilyName3; - int num_initial_edits_; - - explicit VersionSetTestBase(const std::string& name) - : env_(nullptr), - dbname_(test::PerThreadDBPath(name)), - options_(), - db_options_(options_), - cf_options_(options_), - immutable_options_(db_options_, cf_options_), - mutable_cf_options_(cf_options_), - table_cache_(NewLRUCache(50000, 16)), - write_buffer_manager_(db_options_.db_write_buffer_size), - shutting_down_(false), - mock_table_factory_(std::make_shared()) { - EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env_, &env_guard_)); - if (env_ == Env::Default() && getenv("MEM_ENV")) { - env_guard_.reset(NewMemEnv(Env::Default())); - env_ = env_guard_.get(); - } - EXPECT_NE(nullptr, env_); - - fs_ = env_->GetFileSystem(); - EXPECT_OK(fs_->CreateDirIfMissing(dbname_, IOOptions(), nullptr)); - - options_.env = env_; - db_options_.env = env_; - db_options_.fs = fs_; - immutable_options_.env = env_; - immutable_options_.fs = fs_; - immutable_options_.clock = env_->GetSystemClock().get(); - - versions_.reset( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - reactive_versions_ = std::make_shared( - dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, nullptr); - db_options_.db_paths.emplace_back(dbname_, - std::numeric_limits::max()); - } - - virtual ~VersionSetTestBase() { - if (getenv("KEEP_DB")) { - fprintf(stdout, "DB is still at %s\n", dbname_.c_str()); - } else { - Options options; - options.env = env_; - EXPECT_OK(DestroyDB(dbname_, options)); - } - } - - protected: - virtual void PrepareManifest( - std::vector* column_families, - SequenceNumber* last_seqno, std::unique_ptr* log_writer) { - assert(column_families != nullptr); - assert(last_seqno != nullptr); - assert(log_writer != nullptr); - VersionEdit new_db; - if (db_options_.write_dbid_to_manifest) { - DBOptions tmp_db_options; - tmp_db_options.env = env_; - std::unique_ptr impl(new DBImpl(tmp_db_options, dbname_)); - std::string db_id; - impl->GetDbIdentityFromIdentityFile(&db_id); - new_db.SetDBId(db_id); - } - new_db.SetLogNumber(0); - new_db.SetNextFile(2); - new_db.SetLastSequence(0); - - const std::vector cf_names = { - kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2, - kColumnFamilyName3}; - const int kInitialNumOfCfs = static_cast(cf_names.size()); - autovector new_cfs; - uint64_t last_seq = 1; - uint32_t cf_id = 1; - for (int i = 1; i != kInitialNumOfCfs; ++i) { - VersionEdit new_cf; - new_cf.AddColumnFamily(cf_names[i]); - new_cf.SetColumnFamily(cf_id++); - new_cf.SetLogNumber(0); - new_cf.SetNextFile(2); - new_cf.SetLastSequence(last_seq++); - new_cfs.emplace_back(new_cf); - } - *last_seqno = last_seq; - num_initial_edits_ = static_cast(new_cfs.size() + 1); - std::unique_ptr file_writer; - const std::string manifest = DescriptorFileName(dbname_, 1); - const auto& fs = env_->GetFileSystem(); - Status s = WritableFileWriter::Create( - fs, manifest, fs->OptimizeForManifestWrite(env_options_), &file_writer, - nullptr); - ASSERT_OK(s); - { - log_writer->reset(new log::Writer(std::move(file_writer), 0, false)); - std::string record; - new_db.EncodeTo(&record); - s = (*log_writer)->AddRecord(record); - for (const auto& e : new_cfs) { - record.clear(); - e.EncodeTo(&record); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - } - } - ASSERT_OK(s); - - cf_options_.table_factory = mock_table_factory_; - for (const auto& cf_name : cf_names) { - column_families->emplace_back(cf_name, cf_options_); - } - } - - // Create DB with 3 column families. - void NewDB() { - SequenceNumber last_seqno; - std::unique_ptr log_writer; - SetIdentityFile(env_, dbname_); - PrepareManifest(&column_families_, &last_seqno, &log_writer); - log_writer.reset(); - // Make "CURRENT" file point to the new manifest file. - Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr); - ASSERT_OK(s); - - EXPECT_OK(versions_->Recover(column_families_, false)); - EXPECT_EQ(column_families_.size(), - versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - } - - void ReopenDB() { - versions_.reset( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - EXPECT_OK(versions_->Recover(column_families_, false)); - } - - void VerifyManifest(std::string* manifest_path) const { - assert(manifest_path != nullptr); - uint64_t manifest_file_number = 0; - Status s = versions_->GetCurrentManifestPath( - dbname_, fs_.get(), manifest_path, &manifest_file_number); - ASSERT_OK(s); - ASSERT_EQ(1, manifest_file_number); - } - - Status LogAndApplyToDefaultCF(VersionEdit& edit) { - mutex_.Lock(); - Status s = - versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(), - mutable_cf_options_, &edit, &mutex_, nullptr); - mutex_.Unlock(); - return s; - } - - Status LogAndApplyToDefaultCF( - const autovector>& edits) { - autovector vedits; - for (auto& e : edits) { - vedits.push_back(e.get()); - } - mutex_.Lock(); - Status s = - versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(), - mutable_cf_options_, vedits, &mutex_, nullptr); - mutex_.Unlock(); - return s; - } - - void CreateNewManifest() { - constexpr FSDirectory* db_directory = nullptr; - constexpr bool new_descriptor_log = true; - mutex_.Lock(); - VersionEdit dummy; - ASSERT_OK(versions_->LogAndApply( - versions_->GetColumnFamilySet()->GetDefault(), mutable_cf_options_, - &dummy, &mutex_, db_directory, new_descriptor_log)); - mutex_.Unlock(); - } - - ColumnFamilyData* CreateColumnFamily(const std::string& cf_name, - const ColumnFamilyOptions& cf_options) { - VersionEdit new_cf; - new_cf.AddColumnFamily(cf_name); - uint32_t new_id = versions_->GetColumnFamilySet()->GetNextColumnFamilyID(); - new_cf.SetColumnFamily(new_id); - new_cf.SetLogNumber(0); - new_cf.SetComparatorName(cf_options.comparator->Name()); - Status s; - mutex_.Lock(); - s = versions_->LogAndApply(/*column_family_data=*/nullptr, - MutableCFOptions(cf_options), &new_cf, &mutex_, - /*db_directory=*/nullptr, - /*new_descriptor_log=*/false, &cf_options); - mutex_.Unlock(); - EXPECT_OK(s); - ColumnFamilyData* cfd = - versions_->GetColumnFamilySet()->GetColumnFamily(cf_name); - EXPECT_NE(nullptr, cfd); - return cfd; - } - - Env* mem_env_; - Env* env_; - std::shared_ptr env_guard_; - std::shared_ptr fs_; - const std::string dbname_; - EnvOptions env_options_; - Options options_; - ImmutableDBOptions db_options_; - ColumnFamilyOptions cf_options_; - ImmutableOptions immutable_options_; - MutableCFOptions mutable_cf_options_; - std::shared_ptr table_cache_; - WriteController write_controller_; - WriteBufferManager write_buffer_manager_; - std::shared_ptr versions_; - std::shared_ptr reactive_versions_; - InstrumentedMutex mutex_; - std::atomic shutting_down_; - std::shared_ptr mock_table_factory_; - std::vector column_families_; -}; - -const std::string VersionSetTestBase::kColumnFamilyName1 = "alice"; -const std::string VersionSetTestBase::kColumnFamilyName2 = "bob"; -const std::string VersionSetTestBase::kColumnFamilyName3 = "charles"; - -class VersionSetTest : public VersionSetTestBase, public testing::Test { - public: - VersionSetTest() : VersionSetTestBase("version_set_test") {} -}; - -TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) { - NewDB(); - const int kGroupSize = 5; - autovector edits; - for (int i = 0; i != kGroupSize; ++i) { - edits.emplace_back(VersionEdit()); - } - autovector cfds; - autovector all_mutable_cf_options; - autovector> edit_lists; - for (int i = 0; i != kGroupSize; ++i) { - cfds.emplace_back(versions_->GetColumnFamilySet()->GetDefault()); - all_mutable_cf_options.emplace_back(&mutable_cf_options_); - autovector edit_list; - edit_list.emplace_back(&edits[i]); - edit_lists.emplace_back(edit_list); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - int count = 0; - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:SameColumnFamily", [&](void* arg) { - uint32_t* cf_id = reinterpret_cast(arg); - EXPECT_EQ(0u, *cf_id); - ++count; - }); - SyncPoint::GetInstance()->EnableProcessing(); - mutex_.Lock(); - Status s = versions_->LogAndApply(cfds, all_mutable_cf_options, edit_lists, - &mutex_, nullptr); - mutex_.Unlock(); - EXPECT_OK(s); - EXPECT_EQ(kGroupSize - 1, count); -} - -TEST_F(VersionSetTest, PersistBlobFileStateInNewManifest) { - // Initialize the database and add a couple of blob files, one with some - // garbage in it, and one without any garbage. - NewDB(); - - assert(versions_); - assert(versions_->GetColumnFamilySet()); - - ColumnFamilyData* const cfd = versions_->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - Version* const version = cfd->current(); - assert(version); - - VersionStorageInfo* const storage_info = version->storage_info(); - assert(storage_info); - - { - constexpr uint64_t blob_file_number = 123; - constexpr uint64_t total_blob_count = 456; - constexpr uint64_t total_blob_bytes = 77777777; - constexpr char checksum_method[] = "SHA1"; - constexpr char checksum_value[] = - "\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c" - "\x52\x5c\xbd"; - - auto shared_meta = SharedBlobFileMetaData::Create( - blob_file_number, total_blob_count, total_blob_bytes, checksum_method, - checksum_value); - - constexpr uint64_t garbage_blob_count = 89; - constexpr uint64_t garbage_blob_bytes = 1000000; - - auto meta = BlobFileMetaData::Create( - std::move(shared_meta), BlobFileMetaData::LinkedSsts(), - garbage_blob_count, garbage_blob_bytes); - - storage_info->AddBlobFile(std::move(meta)); - } - - { - constexpr uint64_t blob_file_number = 234; - constexpr uint64_t total_blob_count = 555; - constexpr uint64_t total_blob_bytes = 66666; - constexpr char checksum_method[] = "CRC32"; - constexpr char checksum_value[] = "\x3d\x87\xff\x57"; - - auto shared_meta = SharedBlobFileMetaData::Create( - blob_file_number, total_blob_count, total_blob_bytes, checksum_method, - checksum_value); - - constexpr uint64_t garbage_blob_count = 0; - constexpr uint64_t garbage_blob_bytes = 0; - - auto meta = BlobFileMetaData::Create( - std::move(shared_meta), BlobFileMetaData::LinkedSsts(), - garbage_blob_count, garbage_blob_bytes); - - storage_info->AddBlobFile(std::move(meta)); - } - - // Force the creation of a new manifest file and make sure metadata for - // the blob files is re-persisted. - size_t addition_encoded = 0; - SyncPoint::GetInstance()->SetCallBack( - "BlobFileAddition::EncodeTo::CustomFields", - [&](void* /* arg */) { ++addition_encoded; }); - - size_t garbage_encoded = 0; - SyncPoint::GetInstance()->SetCallBack( - "BlobFileGarbage::EncodeTo::CustomFields", - [&](void* /* arg */) { ++garbage_encoded; }); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateNewManifest(); - - ASSERT_EQ(addition_encoded, 2); - ASSERT_EQ(garbage_encoded, 1); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(VersionSetTest, AddLiveBlobFiles) { - // Initialize the database and add a blob file. - NewDB(); - - assert(versions_); - assert(versions_->GetColumnFamilySet()); - - ColumnFamilyData* const cfd = versions_->GetColumnFamilySet()->GetDefault(); - assert(cfd); - - Version* const first_version = cfd->current(); - assert(first_version); - - VersionStorageInfo* const first_storage_info = first_version->storage_info(); - assert(first_storage_info); - - constexpr uint64_t first_blob_file_number = 234; - constexpr uint64_t first_total_blob_count = 555; - constexpr uint64_t first_total_blob_bytes = 66666; - constexpr char first_checksum_method[] = "CRC32"; - constexpr char first_checksum_value[] = "\x3d\x87\xff\x57"; - - auto first_shared_meta = SharedBlobFileMetaData::Create( - first_blob_file_number, first_total_blob_count, first_total_blob_bytes, - first_checksum_method, first_checksum_value); - - constexpr uint64_t garbage_blob_count = 0; - constexpr uint64_t garbage_blob_bytes = 0; - - auto first_meta = BlobFileMetaData::Create( - std::move(first_shared_meta), BlobFileMetaData::LinkedSsts(), - garbage_blob_count, garbage_blob_bytes); - - first_storage_info->AddBlobFile(first_meta); - - // Reference the version so it stays alive even after the following version - // edit. - first_version->Ref(); - - // Get live files directly from version. - std::vector version_table_files; - std::vector version_blob_files; - - first_version->AddLiveFiles(&version_table_files, &version_blob_files); - - ASSERT_EQ(version_blob_files.size(), 1); - ASSERT_EQ(version_blob_files[0], first_blob_file_number); - - // Create a new version containing an additional blob file. - versions_->TEST_CreateAndAppendVersion(cfd); - - Version* const second_version = cfd->current(); - assert(second_version); - assert(second_version != first_version); - - VersionStorageInfo* const second_storage_info = - second_version->storage_info(); - assert(second_storage_info); - - constexpr uint64_t second_blob_file_number = 456; - constexpr uint64_t second_total_blob_count = 100; - constexpr uint64_t second_total_blob_bytes = 2000000; - constexpr char second_checksum_method[] = "CRC32B"; - constexpr char second_checksum_value[] = "\x6d\xbd\xf2\x3a"; - - auto second_shared_meta = SharedBlobFileMetaData::Create( - second_blob_file_number, second_total_blob_count, second_total_blob_bytes, - second_checksum_method, second_checksum_value); - - auto second_meta = BlobFileMetaData::Create( - std::move(second_shared_meta), BlobFileMetaData::LinkedSsts(), - garbage_blob_count, garbage_blob_bytes); - - second_storage_info->AddBlobFile(std::move(first_meta)); - second_storage_info->AddBlobFile(std::move(second_meta)); - - // Get all live files from version set. Note that the result contains - // duplicates. - std::vector all_table_files; - std::vector all_blob_files; - - versions_->AddLiveFiles(&all_table_files, &all_blob_files); - - ASSERT_EQ(all_blob_files.size(), 3); - ASSERT_EQ(all_blob_files[0], first_blob_file_number); - ASSERT_EQ(all_blob_files[1], first_blob_file_number); - ASSERT_EQ(all_blob_files[2], second_blob_file_number); - - // Clean up previous version. - first_version->Unref(); -} - -TEST_F(VersionSetTest, ObsoleteBlobFile) { - // Initialize the database and add a blob file that is entirely garbage - // and thus can immediately be marked obsolete. - NewDB(); - - VersionEdit edit; - - constexpr uint64_t blob_file_number = 234; - constexpr uint64_t total_blob_count = 555; - constexpr uint64_t total_blob_bytes = 66666; - constexpr char checksum_method[] = "CRC32"; - constexpr char checksum_value[] = "\x3d\x87\xff\x57"; - - edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes, - checksum_method, checksum_value); - - edit.AddBlobFileGarbage(blob_file_number, total_blob_count, total_blob_bytes); - - mutex_.Lock(); - Status s = - versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(), - mutable_cf_options_, &edit, &mutex_, nullptr); - mutex_.Unlock(); - - ASSERT_OK(s); - - // Make sure blob files from the pending number range are not returned - // as obsolete. - { - std::vector table_files; - std::vector blob_files; - std::vector manifest_files; - constexpr uint64_t min_pending_output = blob_file_number; - - versions_->GetObsoleteFiles(&table_files, &blob_files, &manifest_files, - min_pending_output); - - ASSERT_TRUE(blob_files.empty()); - } - - // Make sure the blob file is returned as obsolete if it's not in the pending - // range. - { - std::vector table_files; - std::vector blob_files; - std::vector manifest_files; - constexpr uint64_t min_pending_output = blob_file_number + 1; - - versions_->GetObsoleteFiles(&table_files, &blob_files, &manifest_files, - min_pending_output); - - ASSERT_EQ(blob_files.size(), 1); - ASSERT_EQ(blob_files[0].GetBlobFileNumber(), blob_file_number); - } - - // Make sure it's not returned a second time. - { - std::vector table_files; - std::vector blob_files; - std::vector manifest_files; - constexpr uint64_t min_pending_output = blob_file_number + 1; - - versions_->GetObsoleteFiles(&table_files, &blob_files, &manifest_files, - min_pending_output); - - ASSERT_TRUE(blob_files.empty()); - } -} - -TEST_F(VersionSetTest, WalEditsNotAppliedToVersion) { - NewDB(); - - constexpr uint64_t kNumWals = 5; - - autovector> edits; - // Add some WALs. - for (uint64_t i = 1; i <= kNumWals; i++) { - edits.emplace_back(new VersionEdit); - // WAL's size equals its log number. - edits.back()->AddWal(i, WalMetadata(i)); - } - // Delete the first half of the WALs. - edits.emplace_back(new VersionEdit); - edits.back()->DeleteWalsBefore(kNumWals / 2 + 1); - - autovector versions; - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:NewVersion", - [&](void* arg) { versions.push_back(reinterpret_cast(arg)); }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(LogAndApplyToDefaultCF(edits)); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - // Since the edits are all WAL edits, no version should be created. - ASSERT_EQ(versions.size(), 1); - ASSERT_EQ(versions[0], nullptr); -} - -// Similar to WalEditsNotAppliedToVersion, but contains a non-WAL edit. -TEST_F(VersionSetTest, NonWalEditsAppliedToVersion) { - NewDB(); - - const std::string kDBId = "db_db"; - constexpr uint64_t kNumWals = 5; - - autovector> edits; - // Add some WALs. - for (uint64_t i = 1; i <= kNumWals; i++) { - edits.emplace_back(new VersionEdit); - // WAL's size equals its log number. - edits.back()->AddWal(i, WalMetadata(i)); - } - // Delete the first half of the WALs. - edits.emplace_back(new VersionEdit); - edits.back()->DeleteWalsBefore(kNumWals / 2 + 1); - edits.emplace_back(new VersionEdit); - edits.back()->SetDBId(kDBId); - - autovector versions; - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:NewVersion", - [&](void* arg) { versions.push_back(reinterpret_cast(arg)); }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_OK(LogAndApplyToDefaultCF(edits)); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - // Since the edits are all WAL edits, no version should be created. - ASSERT_EQ(versions.size(), 1); - ASSERT_NE(versions[0], nullptr); -} - -TEST_F(VersionSetTest, WalAddition) { - NewDB(); - - constexpr WalNumber kLogNumber = 10; - constexpr uint64_t kSizeInBytes = 111; - - // A WAL is just created. - { - VersionEdit edit; - edit.AddWal(kLogNumber); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_FALSE(wals.at(kLogNumber).HasSyncedSize()); - } - - // The WAL is synced for several times before closing. - { - for (uint64_t size_delta = 100; size_delta > 0; size_delta /= 2) { - uint64_t size = kSizeInBytes - size_delta; - WalMetadata wal(size); - VersionEdit edit; - edit.AddWal(kLogNumber, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_TRUE(wals.at(kLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kLogNumber).GetSyncedSizeInBytes(), size); - } - } - - // The WAL is closed. - { - WalMetadata wal(kSizeInBytes); - VersionEdit edit; - edit.AddWal(kLogNumber, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_TRUE(wals.at(kLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kLogNumber).GetSyncedSizeInBytes(), kSizeInBytes); - } - - // Recover a new VersionSet. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(new_versions->Recover(column_families_, /*read_only=*/false)); - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_TRUE(wals.at(kLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kLogNumber).GetSyncedSizeInBytes(), kSizeInBytes); - } -} - -TEST_F(VersionSetTest, WalCloseWithoutSync) { - NewDB(); - - constexpr WalNumber kLogNumber = 10; - constexpr uint64_t kSizeInBytes = 111; - constexpr uint64_t kSyncedSizeInBytes = kSizeInBytes / 2; - - // A WAL is just created. - { - VersionEdit edit; - edit.AddWal(kLogNumber); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_FALSE(wals.at(kLogNumber).HasSyncedSize()); - } - - // The WAL is synced before closing. - { - WalMetadata wal(kSyncedSizeInBytes); - VersionEdit edit; - edit.AddWal(kLogNumber, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_TRUE(wals.at(kLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kLogNumber).GetSyncedSizeInBytes(), kSyncedSizeInBytes); - } - - // A new WAL with larger log number is created, - // implicitly marking the current WAL closed. - { - VersionEdit edit; - edit.AddWal(kLogNumber + 1); - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 2); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_TRUE(wals.at(kLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kLogNumber).GetSyncedSizeInBytes(), kSyncedSizeInBytes); - ASSERT_TRUE(wals.find(kLogNumber + 1) != wals.end()); - ASSERT_FALSE(wals.at(kLogNumber + 1).HasSyncedSize()); - } - - // Recover a new VersionSet. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(new_versions->Recover(column_families_, false)); - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 2); - ASSERT_TRUE(wals.find(kLogNumber) != wals.end()); - ASSERT_TRUE(wals.at(kLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kLogNumber).GetSyncedSizeInBytes(), kSyncedSizeInBytes); - } -} - -TEST_F(VersionSetTest, WalDeletion) { - NewDB(); - - constexpr WalNumber kClosedLogNumber = 10; - constexpr WalNumber kNonClosedLogNumber = 20; - constexpr uint64_t kSizeInBytes = 111; - - // Add a non-closed and a closed WAL. - { - VersionEdit edit; - edit.AddWal(kClosedLogNumber, WalMetadata(kSizeInBytes)); - edit.AddWal(kNonClosedLogNumber); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 2); - ASSERT_TRUE(wals.find(kNonClosedLogNumber) != wals.end()); - ASSERT_TRUE(wals.find(kClosedLogNumber) != wals.end()); - ASSERT_FALSE(wals.at(kNonClosedLogNumber).HasSyncedSize()); - ASSERT_TRUE(wals.at(kClosedLogNumber).HasSyncedSize()); - ASSERT_EQ(wals.at(kClosedLogNumber).GetSyncedSizeInBytes(), kSizeInBytes); - } - - // Delete the closed WAL. - { - VersionEdit edit; - edit.DeleteWalsBefore(kNonClosedLogNumber); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - const auto& wals = versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kNonClosedLogNumber) != wals.end()); - ASSERT_FALSE(wals.at(kNonClosedLogNumber).HasSyncedSize()); - } - - // Recover a new VersionSet, only the non-closed WAL should show up. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(new_versions->Recover(column_families_, false)); - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kNonClosedLogNumber) != wals.end()); - ASSERT_FALSE(wals.at(kNonClosedLogNumber).HasSyncedSize()); - } - - // Force the creation of a new MANIFEST file, - // only the non-closed WAL should be written to the new MANIFEST. - { - std::vector wal_additions; - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::WriteCurrentStateToManifest:SaveWal", [&](void* arg) { - VersionEdit* edit = reinterpret_cast(arg); - ASSERT_TRUE(edit->IsWalAddition()); - for (auto& addition : edit->GetWalAdditions()) { - wal_additions.push_back(addition); - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - CreateNewManifest(); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - ASSERT_EQ(wal_additions.size(), 1); - ASSERT_EQ(wal_additions[0].GetLogNumber(), kNonClosedLogNumber); - ASSERT_FALSE(wal_additions[0].GetMetadata().HasSyncedSize()); - } - - // Recover from the new MANIFEST, only the non-closed WAL should show up. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(new_versions->Recover(column_families_, false)); - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kNonClosedLogNumber) != wals.end()); - ASSERT_FALSE(wals.at(kNonClosedLogNumber).HasSyncedSize()); - } -} - -TEST_F(VersionSetTest, WalCreateTwice) { - NewDB(); - - constexpr WalNumber kLogNumber = 10; - - VersionEdit edit; - edit.AddWal(kLogNumber); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - - Status s = LogAndApplyToDefaultCF(edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("WAL 10 is created more than once") != - std::string::npos) - << s.ToString(); -} - -TEST_F(VersionSetTest, WalCreateAfterClose) { - NewDB(); - - constexpr WalNumber kLogNumber = 10; - constexpr uint64_t kSizeInBytes = 111; - - { - // Add a closed WAL. - VersionEdit edit; - edit.AddWal(kLogNumber); - WalMetadata wal(kSizeInBytes); - edit.AddWal(kLogNumber, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - } - - { - // Create the same WAL again. - VersionEdit edit; - edit.AddWal(kLogNumber); - - Status s = LogAndApplyToDefaultCF(edit); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("WAL 10 is created more than once") != - std::string::npos) - << s.ToString(); - } -} - -TEST_F(VersionSetTest, AddWalWithSmallerSize) { - NewDB(); - assert(versions_); - - constexpr WalNumber kLogNumber = 10; - constexpr uint64_t kSizeInBytes = 111; - - { - // Add a closed WAL. - VersionEdit edit; - WalMetadata wal(kSizeInBytes); - edit.AddWal(kLogNumber, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - } - // Copy for future comparison. - const std::map wals1 = - versions_->GetWalSet().GetWals(); - - { - // Add the same WAL with smaller synced size. - VersionEdit edit; - WalMetadata wal(kSizeInBytes / 2); - edit.AddWal(kLogNumber, wal); - - Status s = LogAndApplyToDefaultCF(edit); - ASSERT_OK(s); - } - const std::map wals2 = - versions_->GetWalSet().GetWals(); - ASSERT_EQ(wals1, wals2); -} - -TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) { - NewDB(); - - constexpr WalNumber kLogNumber0 = 10; - constexpr WalNumber kLogNumber1 = 20; - constexpr WalNumber kNonExistingNumber = 15; - constexpr uint64_t kSizeInBytes = 111; - - { - // Add closed WALs. - VersionEdit edit; - WalMetadata wal(kSizeInBytes); - edit.AddWal(kLogNumber0, wal); - edit.AddWal(kLogNumber1, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - } - - { - // Delete WALs before a non-existing WAL. - VersionEdit edit; - edit.DeleteWalsBefore(kNonExistingNumber); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - } - - // Recover a new VersionSet, WAL0 is deleted, WAL1 is not. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(new_versions->Recover(column_families_, false)); - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kLogNumber1) != wals.end()); - } -} - -TEST_F(VersionSetTest, DeleteAllWals) { - NewDB(); - - constexpr WalNumber kMaxLogNumber = 10; - constexpr uint64_t kSizeInBytes = 111; - - { - // Add a closed WAL. - VersionEdit edit; - WalMetadata wal(kSizeInBytes); - edit.AddWal(kMaxLogNumber, wal); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - } - - { - VersionEdit edit; - edit.DeleteWalsBefore(kMaxLogNumber + 10); - - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - } - - // Recover a new VersionSet, all WALs are deleted. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(new_versions->Recover(column_families_, false)); - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 0); - } -} - -TEST_F(VersionSetTest, AtomicGroupWithWalEdits) { - NewDB(); - - constexpr int kAtomicGroupSize = 7; - constexpr uint64_t kNumWals = 5; - const std::string kDBId = "db_db"; - - int remaining = kAtomicGroupSize; - autovector> edits; - // Add 5 WALs. - for (uint64_t i = 1; i <= kNumWals; i++) { - edits.emplace_back(new VersionEdit); - // WAL's size equals its log number. - edits.back()->AddWal(i, WalMetadata(i)); - edits.back()->MarkAtomicGroup(--remaining); - } - // One edit with the min log number set. - edits.emplace_back(new VersionEdit); - edits.back()->SetDBId(kDBId); - edits.back()->MarkAtomicGroup(--remaining); - // Delete the first added 4 WALs. - edits.emplace_back(new VersionEdit); - edits.back()->DeleteWalsBefore(kNumWals); - edits.back()->MarkAtomicGroup(--remaining); - ASSERT_EQ(remaining, 0); - - ASSERT_OK(LogAndApplyToDefaultCF(edits)); - - // Recover a new VersionSet, the min log number and the last WAL should be - // kept. - { - std::unique_ptr new_versions( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - std::string db_id; - ASSERT_OK( - new_versions->Recover(column_families_, /*read_only=*/false, &db_id)); - - ASSERT_EQ(db_id, kDBId); - - const auto& wals = new_versions->GetWalSet().GetWals(); - ASSERT_EQ(wals.size(), 1); - ASSERT_TRUE(wals.find(kNumWals) != wals.end()); - ASSERT_TRUE(wals.at(kNumWals).HasSyncedSize()); - ASSERT_EQ(wals.at(kNumWals).GetSyncedSizeInBytes(), kNumWals); - } -} - -TEST_F(VersionStorageInfoTest, AddRangeDeletionCompensatedFileSize) { - // Tests that compensated range deletion size is added to compensated file - // size. - Add(4, 100U, "1", "2", 100U, kInvalidBlobFileNumber, 1000U); - - UpdateVersionStorageInfo(); - - auto meta = vstorage_.GetFileMetaDataByNumber(100U); - ASSERT_EQ(meta->compensated_file_size, 100U + 1000U); -} - -class VersionSetWithTimestampTest : public VersionSetTest { - public: - static const std::string kNewCfName; - - explicit VersionSetWithTimestampTest() : VersionSetTest() {} - - void SetUp() override { - NewDB(); - Options options; - options.comparator = test::BytewiseComparatorWithU64TsWrapper(); - cfd_ = CreateColumnFamily(kNewCfName, options); - EXPECT_NE(nullptr, cfd_); - EXPECT_NE(nullptr, cfd_->GetLatestMutableCFOptions()); - column_families_.emplace_back(kNewCfName, options); - } - - void TearDown() override { - for (auto* e : edits_) { - delete e; - } - edits_.clear(); - } - - void GenVersionEditsToSetFullHistoryTsLow( - const std::vector& ts_lbs) { - for (const auto ts_lb : ts_lbs) { - VersionEdit* edit = new VersionEdit; - edit->SetColumnFamily(cfd_->GetID()); - std::string ts_str = test::EncodeInt(ts_lb); - edit->SetFullHistoryTsLow(ts_str); - edits_.emplace_back(edit); - } - } - - void VerifyFullHistoryTsLow(uint64_t expected_ts_low) { - std::unique_ptr vset( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - ASSERT_OK(vset->Recover(column_families_, /*read_only=*/false, - /*db_id=*/nullptr)); - for (auto* cfd : *(vset->GetColumnFamilySet())) { - ASSERT_NE(nullptr, cfd); - if (cfd->GetName() == kNewCfName) { - ASSERT_EQ(test::EncodeInt(expected_ts_low), cfd->GetFullHistoryTsLow()); - } else { - ASSERT_TRUE(cfd->GetFullHistoryTsLow().empty()); - } - } - } - - void DoTest(const std::vector& ts_lbs) { - if (ts_lbs.empty()) { - return; - } - - GenVersionEditsToSetFullHistoryTsLow(ts_lbs); - - Status s; - mutex_.Lock(); - s = versions_->LogAndApply(cfd_, *(cfd_->GetLatestMutableCFOptions()), - edits_, &mutex_, nullptr); - mutex_.Unlock(); - ASSERT_OK(s); - VerifyFullHistoryTsLow(*std::max_element(ts_lbs.begin(), ts_lbs.end())); - } - - protected: - ColumnFamilyData* cfd_{nullptr}; - // edits_ must contain and own pointers to heap-alloc VersionEdit objects. - autovector edits_; -}; - -const std::string VersionSetWithTimestampTest::kNewCfName("new_cf"); - -TEST_F(VersionSetWithTimestampTest, SetFullHistoryTsLbOnce) { - constexpr uint64_t kTsLow = 100; - DoTest({kTsLow}); -} - -// Simulate the application increasing full_history_ts_low. -TEST_F(VersionSetWithTimestampTest, IncreaseFullHistoryTsLb) { - const std::vector ts_lbs = {100, 101, 102, 103}; - DoTest(ts_lbs); -} - -// Simulate the application trying to decrease full_history_ts_low -// unsuccessfully. If the application calls public API sequentially to -// decrease the lower bound ts, RocksDB will return an InvalidArgument -// status before involving VersionSet. Only when multiple threads trying -// to decrease the lower bound concurrently will this case ever happen. Even -// so, the lower bound cannot be decreased. The application will be notified -// via return value of the API. -TEST_F(VersionSetWithTimestampTest, TryDecreaseFullHistoryTsLb) { - const std::vector ts_lbs = {103, 102, 101, 100}; - DoTest(ts_lbs); -} - -class VersionSetAtomicGroupTest : public VersionSetTestBase, - public testing::Test { - public: - VersionSetAtomicGroupTest() - : VersionSetTestBase("version_set_atomic_group_test") {} - - void SetUp() override { - PrepareManifest(&column_families_, &last_seqno_, &log_writer_); - SetupTestSyncPoints(); - } - - void SetupValidAtomicGroup(int atomic_group_size) { - edits_.resize(atomic_group_size); - int remaining = atomic_group_size; - for (size_t i = 0; i != edits_.size(); ++i) { - edits_[i].SetLogNumber(0); - edits_[i].SetNextFile(2); - edits_[i].MarkAtomicGroup(--remaining); - edits_[i].SetLastSequence(last_seqno_++); - } - ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr)); - } - - void SetupIncompleteTrailingAtomicGroup(int atomic_group_size) { - edits_.resize(atomic_group_size); - int remaining = atomic_group_size; - for (size_t i = 0; i != edits_.size(); ++i) { - edits_[i].SetLogNumber(0); - edits_[i].SetNextFile(2); - edits_[i].MarkAtomicGroup(--remaining); - edits_[i].SetLastSequence(last_seqno_++); - } - ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr)); - } - - void SetupCorruptedAtomicGroup(int atomic_group_size) { - edits_.resize(atomic_group_size); - int remaining = atomic_group_size; - for (size_t i = 0; i != edits_.size(); ++i) { - edits_[i].SetLogNumber(0); - edits_[i].SetNextFile(2); - if (i != ((size_t)atomic_group_size / 2)) { - edits_[i].MarkAtomicGroup(--remaining); - } - edits_[i].SetLastSequence(last_seqno_++); - } - ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr)); - } - - void SetupIncorrectAtomicGroup(int atomic_group_size) { - edits_.resize(atomic_group_size); - int remaining = atomic_group_size; - for (size_t i = 0; i != edits_.size(); ++i) { - edits_[i].SetLogNumber(0); - edits_[i].SetNextFile(2); - if (i != 1) { - edits_[i].MarkAtomicGroup(--remaining); - } else { - edits_[i].MarkAtomicGroup(remaining--); - } - edits_[i].SetLastSequence(last_seqno_++); - } - ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr)); - } - - void SetupTestSyncPoints() { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "AtomicGroupReadBuffer::AddEdit:FirstInAtomicGroup", [&](void* arg) { - VersionEdit* e = reinterpret_cast(arg); - EXPECT_EQ(edits_.front().DebugString(), - e->DebugString()); // compare based on value - first_in_atomic_group_ = true; - }); - SyncPoint::GetInstance()->SetCallBack( - "AtomicGroupReadBuffer::AddEdit:LastInAtomicGroup", [&](void* arg) { - VersionEdit* e = reinterpret_cast(arg); - EXPECT_EQ(edits_.back().DebugString(), - e->DebugString()); // compare based on value - EXPECT_TRUE(first_in_atomic_group_); - last_in_atomic_group_ = true; - }); - SyncPoint::GetInstance()->SetCallBack( - "VersionEditHandlerBase::Iterate:Finish", [&](void* arg) { - num_recovered_edits_ = *reinterpret_cast(arg); - }); - SyncPoint::GetInstance()->SetCallBack( - "AtomicGroupReadBuffer::AddEdit:AtomicGroup", - [&](void* /* arg */) { ++num_edits_in_atomic_group_; }); - SyncPoint::GetInstance()->SetCallBack( - "AtomicGroupReadBuffer::AddEdit:AtomicGroupMixedWithNormalEdits", - [&](void* arg) { - corrupted_edit_ = *reinterpret_cast(arg); - }); - SyncPoint::GetInstance()->SetCallBack( - "AtomicGroupReadBuffer::AddEdit:IncorrectAtomicGroupSize", - [&](void* arg) { - edit_with_incorrect_group_size_ = - *reinterpret_cast(arg); - }); - SyncPoint::GetInstance()->EnableProcessing(); - } - - void AddNewEditsToLog(int num_edits) { - for (int i = 0; i < num_edits; i++) { - std::string record; - edits_[i].EncodeTo(&record); - ASSERT_OK(log_writer_->AddRecord(record)); - } - } - - void TearDown() override { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - log_writer_.reset(); - } - - protected: - std::vector column_families_; - SequenceNumber last_seqno_; - std::vector edits_; - bool first_in_atomic_group_ = false; - bool last_in_atomic_group_ = false; - int num_edits_in_atomic_group_ = 0; - size_t num_recovered_edits_ = 0; - VersionEdit corrupted_edit_; - VersionEdit edit_with_incorrect_group_size_; - std::unique_ptr log_writer_; -}; - -TEST_F(VersionSetAtomicGroupTest, HandleValidAtomicGroupWithVersionSetRecover) { - const int kAtomicGroupSize = 3; - SetupValidAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kAtomicGroupSize); - EXPECT_OK(versions_->Recover(column_families_, false)); - EXPECT_EQ(column_families_.size(), - versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_TRUE(first_in_atomic_group_); - EXPECT_TRUE(last_in_atomic_group_); - EXPECT_EQ(num_initial_edits_ + kAtomicGroupSize, num_recovered_edits_); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleValidAtomicGroupWithReactiveVersionSetRecover) { - const int kAtomicGroupSize = 3; - SetupValidAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kAtomicGroupSize); - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - EXPECT_EQ(column_families_.size(), - reactive_versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_TRUE(first_in_atomic_group_); - EXPECT_TRUE(last_in_atomic_group_); - // The recover should clean up the replay buffer. - EXPECT_TRUE(reactive_versions_->TEST_read_edits_in_atomic_group() == 0); - EXPECT_TRUE(reactive_versions_->replay_buffer().size() == 0); - EXPECT_EQ(num_initial_edits_ + kAtomicGroupSize, num_recovered_edits_); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleValidAtomicGroupWithReactiveVersionSetReadAndApply) { - const int kAtomicGroupSize = 3; - SetupValidAtomicGroup(kAtomicGroupSize); - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - EXPECT_EQ(num_initial_edits_, num_recovered_edits_); - AddNewEditsToLog(kAtomicGroupSize); - InstrumentedMutex mu; - std::unordered_set cfds_changed; - mu.Lock(); - EXPECT_OK(reactive_versions_->ReadAndApply( - &mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed)); - mu.Unlock(); - EXPECT_TRUE(first_in_atomic_group_); - EXPECT_TRUE(last_in_atomic_group_); - // The recover should clean up the replay buffer. - EXPECT_TRUE(reactive_versions_->TEST_read_edits_in_atomic_group() == 0); - EXPECT_TRUE(reactive_versions_->replay_buffer().size() == 0); - EXPECT_EQ(kAtomicGroupSize, num_recovered_edits_); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleIncompleteTrailingAtomicGroupWithVersionSetRecover) { - const int kAtomicGroupSize = 4; - const int kNumberOfPersistedVersionEdits = kAtomicGroupSize - 1; - SetupIncompleteTrailingAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kNumberOfPersistedVersionEdits); - EXPECT_OK(versions_->Recover(column_families_, false)); - EXPECT_EQ(column_families_.size(), - versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_TRUE(first_in_atomic_group_); - EXPECT_FALSE(last_in_atomic_group_); - EXPECT_EQ(kNumberOfPersistedVersionEdits, num_edits_in_atomic_group_); - EXPECT_EQ(num_initial_edits_, num_recovered_edits_); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleIncompleteTrailingAtomicGroupWithReactiveVersionSetRecover) { - const int kAtomicGroupSize = 4; - const int kNumberOfPersistedVersionEdits = kAtomicGroupSize - 1; - SetupIncompleteTrailingAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kNumberOfPersistedVersionEdits); - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - EXPECT_EQ(column_families_.size(), - reactive_versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_TRUE(first_in_atomic_group_); - EXPECT_FALSE(last_in_atomic_group_); - EXPECT_EQ(kNumberOfPersistedVersionEdits, num_edits_in_atomic_group_); - // Reactive version set should store the edits in the replay buffer. - EXPECT_TRUE(reactive_versions_->TEST_read_edits_in_atomic_group() == - kNumberOfPersistedVersionEdits); - EXPECT_TRUE(reactive_versions_->replay_buffer().size() == kAtomicGroupSize); - // Write the last record. The reactive version set should now apply all - // edits. - std::string last_record; - edits_[kAtomicGroupSize - 1].EncodeTo(&last_record); - EXPECT_OK(log_writer_->AddRecord(last_record)); - InstrumentedMutex mu; - std::unordered_set cfds_changed; - mu.Lock(); - EXPECT_OK(reactive_versions_->ReadAndApply( - &mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed)); - mu.Unlock(); - // Reactive version set should be empty now. - EXPECT_TRUE(reactive_versions_->TEST_read_edits_in_atomic_group() == 0); - EXPECT_TRUE(reactive_versions_->replay_buffer().size() == 0); - EXPECT_EQ(num_initial_edits_, num_recovered_edits_); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleIncompleteTrailingAtomicGroupWithReactiveVersionSetReadAndApply) { - const int kAtomicGroupSize = 4; - const int kNumberOfPersistedVersionEdits = kAtomicGroupSize - 1; - SetupIncompleteTrailingAtomicGroup(kAtomicGroupSize); - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - // No edits in an atomic group. - EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - EXPECT_EQ(column_families_.size(), - reactive_versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_EQ(num_initial_edits_, num_recovered_edits_); - // Write a few edits in an atomic group. - AddNewEditsToLog(kNumberOfPersistedVersionEdits); - InstrumentedMutex mu; - std::unordered_set cfds_changed; - mu.Lock(); - EXPECT_OK(reactive_versions_->ReadAndApply( - &mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed)); - mu.Unlock(); - EXPECT_TRUE(first_in_atomic_group_); - EXPECT_FALSE(last_in_atomic_group_); - EXPECT_EQ(kNumberOfPersistedVersionEdits, num_edits_in_atomic_group_); - // Reactive version set should store the edits in the replay buffer. - EXPECT_TRUE(reactive_versions_->TEST_read_edits_in_atomic_group() == - kNumberOfPersistedVersionEdits); - EXPECT_TRUE(reactive_versions_->replay_buffer().size() == kAtomicGroupSize); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleCorruptedAtomicGroupWithVersionSetRecover) { - const int kAtomicGroupSize = 4; - SetupCorruptedAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kAtomicGroupSize); - EXPECT_NOK(versions_->Recover(column_families_, false)); - EXPECT_EQ(column_families_.size(), - versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_EQ(edits_[kAtomicGroupSize / 2].DebugString(), - corrupted_edit_.DebugString()); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleCorruptedAtomicGroupWithReactiveVersionSetRecover) { - const int kAtomicGroupSize = 4; - SetupCorruptedAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kAtomicGroupSize); - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_NOK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - EXPECT_EQ(column_families_.size(), - reactive_versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_EQ(edits_[kAtomicGroupSize / 2].DebugString(), - corrupted_edit_.DebugString()); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleCorruptedAtomicGroupWithReactiveVersionSetReadAndApply) { - const int kAtomicGroupSize = 4; - SetupCorruptedAtomicGroup(kAtomicGroupSize); - InstrumentedMutex mu; - std::unordered_set cfds_changed; - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - // Write the corrupted edits. - AddNewEditsToLog(kAtomicGroupSize); - mu.Lock(); - EXPECT_NOK(reactive_versions_->ReadAndApply( - &mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed)); - mu.Unlock(); - EXPECT_EQ(edits_[kAtomicGroupSize / 2].DebugString(), - corrupted_edit_.DebugString()); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleIncorrectAtomicGroupSizeWithVersionSetRecover) { - const int kAtomicGroupSize = 4; - SetupIncorrectAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kAtomicGroupSize); - EXPECT_NOK(versions_->Recover(column_families_, false)); - EXPECT_EQ(column_families_.size(), - versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_EQ(edits_[1].DebugString(), - edit_with_incorrect_group_size_.DebugString()); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleIncorrectAtomicGroupSizeWithReactiveVersionSetRecover) { - const int kAtomicGroupSize = 4; - SetupIncorrectAtomicGroup(kAtomicGroupSize); - AddNewEditsToLog(kAtomicGroupSize); - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_NOK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - EXPECT_EQ(column_families_.size(), - reactive_versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - EXPECT_EQ(edits_[1].DebugString(), - edit_with_incorrect_group_size_.DebugString()); -} - -TEST_F(VersionSetAtomicGroupTest, - HandleIncorrectAtomicGroupSizeWithReactiveVersionSetReadAndApply) { - const int kAtomicGroupSize = 4; - SetupIncorrectAtomicGroup(kAtomicGroupSize); - InstrumentedMutex mu; - std::unordered_set cfds_changed; - std::unique_ptr manifest_reader; - std::unique_ptr manifest_reporter; - std::unique_ptr manifest_reader_status; - EXPECT_OK(reactive_versions_->Recover(column_families_, &manifest_reader, - &manifest_reporter, - &manifest_reader_status)); - AddNewEditsToLog(kAtomicGroupSize); - mu.Lock(); - EXPECT_NOK(reactive_versions_->ReadAndApply( - &mu, &manifest_reader, manifest_reader_status.get(), &cfds_changed)); - mu.Unlock(); - EXPECT_EQ(edits_[1].DebugString(), - edit_with_incorrect_group_size_.DebugString()); -} - -class VersionSetTestDropOneCF : public VersionSetTestBase, - public testing::TestWithParam { - public: - VersionSetTestDropOneCF() - : VersionSetTestBase("version_set_test_drop_one_cf") {} -}; - -// This test simulates the following execution sequence -// Time thread1 bg_flush_thr -// | Prepare version edits (e1,e2,e3) for atomic -// | flush cf1, cf2, cf3 -// | Enqueue e to drop cfi -// | to manifest_writers_ -// | Enqueue (e1,e2,e3) to manifest_writers_ -// | -// | Apply e, -// | cfi.IsDropped() is true -// | Apply (e1,e2,e3), -// | since cfi.IsDropped() == true, we need to -// | drop ei and write the rest to MANIFEST. -// V -// -// Repeat the test for i = 1, 2, 3 to simulate dropping the first, middle and -// last column family in an atomic group. -TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) { - std::vector column_families; - SequenceNumber last_seqno; - std::unique_ptr log_writer; - PrepareManifest(&column_families, &last_seqno, &log_writer); - Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr); - ASSERT_OK(s); - - EXPECT_OK(versions_->Recover(column_families, false /* read_only */)); - EXPECT_EQ(column_families.size(), - versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); - - const int kAtomicGroupSize = 3; - const std::vector non_default_cf_names = { - kColumnFamilyName1, kColumnFamilyName2, kColumnFamilyName3}; - - // Drop one column family - VersionEdit drop_cf_edit; - drop_cf_edit.DropColumnFamily(); - const std::string cf_to_drop_name(GetParam()); - auto cfd_to_drop = - versions_->GetColumnFamilySet()->GetColumnFamily(cf_to_drop_name); - ASSERT_NE(nullptr, cfd_to_drop); - // Increase its refcount because cfd_to_drop is used later, and we need to - // prevent it from being deleted. - cfd_to_drop->Ref(); - drop_cf_edit.SetColumnFamily(cfd_to_drop->GetID()); - mutex_.Lock(); - s = versions_->LogAndApply(cfd_to_drop, - *cfd_to_drop->GetLatestMutableCFOptions(), - &drop_cf_edit, &mutex_, nullptr); - mutex_.Unlock(); - ASSERT_OK(s); - - std::vector edits(kAtomicGroupSize); - uint32_t remaining = kAtomicGroupSize; - size_t i = 0; - autovector cfds; - autovector mutable_cf_options_list; - autovector> edit_lists; - for (const auto& cf_name : non_default_cf_names) { - auto cfd = (cf_name != cf_to_drop_name) - ? versions_->GetColumnFamilySet()->GetColumnFamily(cf_name) - : cfd_to_drop; - ASSERT_NE(nullptr, cfd); - cfds.push_back(cfd); - mutable_cf_options_list.emplace_back(cfd->GetLatestMutableCFOptions()); - edits[i].SetColumnFamily(cfd->GetID()); - edits[i].SetLogNumber(0); - edits[i].SetNextFile(2); - edits[i].MarkAtomicGroup(--remaining); - edits[i].SetLastSequence(last_seqno++); - autovector tmp_edits; - tmp_edits.push_back(&edits[i]); - edit_lists.emplace_back(tmp_edits); - ++i; - } - int called = 0; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->SetCallBack( - "VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", [&](void* arg) { - std::vector* tmp_edits = - reinterpret_cast*>(arg); - EXPECT_EQ(kAtomicGroupSize - 1, tmp_edits->size()); - for (const auto e : *tmp_edits) { - bool found = false; - for (const auto& e2 : edits) { - if (&e2 == e) { - found = true; - break; - } - } - ASSERT_TRUE(found); - } - ++called; - }); - SyncPoint::GetInstance()->EnableProcessing(); - mutex_.Lock(); - s = versions_->LogAndApply(cfds, mutable_cf_options_list, edit_lists, &mutex_, - nullptr); - mutex_.Unlock(); - ASSERT_OK(s); - ASSERT_EQ(1, called); - cfd_to_drop->UnrefAndTryDelete(); -} - -INSTANTIATE_TEST_CASE_P( - AtomicGroup, VersionSetTestDropOneCF, - testing::Values(VersionSetTestBase::kColumnFamilyName1, - VersionSetTestBase::kColumnFamilyName2, - VersionSetTestBase::kColumnFamilyName3)); - -class EmptyDefaultCfNewManifest : public VersionSetTestBase, - public testing::Test { - public: - EmptyDefaultCfNewManifest() : VersionSetTestBase("version_set_new_db_test") {} - // Emulate DBImpl::NewDB() - void PrepareManifest(std::vector* /*column_families*/, - SequenceNumber* /*last_seqno*/, - std::unique_ptr* log_writer) override { - assert(log_writer != nullptr); - VersionEdit new_db; - new_db.SetLogNumber(0); - const std::string manifest_path = DescriptorFileName(dbname_, 1); - const auto& fs = env_->GetFileSystem(); - std::unique_ptr file_writer; - Status s = WritableFileWriter::Create( - fs, manifest_path, fs->OptimizeForManifestWrite(env_options_), - &file_writer, nullptr); - ASSERT_OK(s); - log_writer->reset(new log::Writer(std::move(file_writer), 0, true)); - std::string record; - ASSERT_TRUE(new_db.EncodeTo(&record)); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - // Create new column family - VersionEdit new_cf; - new_cf.AddColumnFamily(VersionSetTestBase::kColumnFamilyName1); - new_cf.SetColumnFamily(1); - new_cf.SetLastSequence(2); - new_cf.SetNextFile(2); - record.clear(); - ASSERT_TRUE(new_cf.EncodeTo(&record)); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - } - - protected: - bool write_dbid_to_manifest_ = false; - std::unique_ptr log_writer_; -}; - -// Create db, create column family. Cf creation will switch to a new MANIFEST. -// Then reopen db, trying to recover. -TEST_F(EmptyDefaultCfNewManifest, Recover) { - PrepareManifest(nullptr, nullptr, &log_writer_); - log_writer_.reset(); - Status s = - SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr); - ASSERT_OK(s); - std::string manifest_path; - VerifyManifest(&manifest_path); - std::vector column_families; - column_families.emplace_back(kDefaultColumnFamilyName, cf_options_); - column_families.emplace_back(VersionSetTestBase::kColumnFamilyName1, - cf_options_); - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest( - manifest_path, column_families, false, &db_id, &has_missing_table_file); - ASSERT_OK(s); - ASSERT_FALSE(has_missing_table_file); -} - -class VersionSetTestEmptyDb - : public VersionSetTestBase, - public testing::TestWithParam< - std::tuple>> { - public: - static const std::string kUnknownColumnFamilyName; - VersionSetTestEmptyDb() : VersionSetTestBase("version_set_test_empty_db") {} - - protected: - void PrepareManifest(std::vector* /*column_families*/, - SequenceNumber* /*last_seqno*/, - std::unique_ptr* log_writer) override { - assert(nullptr != log_writer); - VersionEdit new_db; - if (db_options_.write_dbid_to_manifest) { - DBOptions tmp_db_options; - tmp_db_options.env = env_; - std::unique_ptr impl(new DBImpl(tmp_db_options, dbname_)); - std::string db_id; - impl->GetDbIdentityFromIdentityFile(&db_id); - new_db.SetDBId(db_id); - } - const std::string manifest_path = DescriptorFileName(dbname_, 1); - const auto& fs = env_->GetFileSystem(); - std::unique_ptr file_writer; - Status s = WritableFileWriter::Create( - fs, manifest_path, fs->OptimizeForManifestWrite(env_options_), - &file_writer, nullptr); - ASSERT_OK(s); - { - log_writer->reset(new log::Writer(std::move(file_writer), 0, false)); - std::string record; - new_db.EncodeTo(&record); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - } - } - - std::unique_ptr log_writer_; -}; - -const std::string VersionSetTestEmptyDb::kUnknownColumnFamilyName = "unknown"; - -TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) { - db_options_.write_dbid_to_manifest = std::get<0>(GetParam()); - PrepareManifest(nullptr, nullptr, &log_writer_); - log_writer_.reset(); - Status s = - SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr); - ASSERT_OK(s); - - std::string manifest_path; - VerifyManifest(&manifest_path); - - bool read_only = std::get<1>(GetParam()); - const std::vector cf_names = std::get<2>(GetParam()); - - std::vector column_families; - for (const auto& cf_name : cf_names) { - column_families.emplace_back(cf_name, cf_options_); - } - - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families, - read_only, &db_id, - &has_missing_table_file); - auto iter = - std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName); - if (iter == cf_names.end()) { - ASSERT_TRUE(s.IsInvalidArgument()); - } else { - ASSERT_NE(s.ToString().find(manifest_path), std::string::npos); - ASSERT_TRUE(s.IsCorruption()); - } -} - -TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) { - db_options_.write_dbid_to_manifest = std::get<0>(GetParam()); - PrepareManifest(nullptr, nullptr, &log_writer_); - // Only a subset of column families in the MANIFEST. - VersionEdit new_cf1; - new_cf1.AddColumnFamily(VersionSetTestBase::kColumnFamilyName1); - new_cf1.SetColumnFamily(1); - Status s; - { - std::string record; - new_cf1.EncodeTo(&record); - s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - log_writer_.reset(); - s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr); - ASSERT_OK(s); - - std::string manifest_path; - VerifyManifest(&manifest_path); - - bool read_only = std::get<1>(GetParam()); - const std::vector& cf_names = std::get<2>(GetParam()); - std::vector column_families; - for (const auto& cf_name : cf_names) { - column_families.emplace_back(cf_name, cf_options_); - } - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families, - read_only, &db_id, - &has_missing_table_file); - auto iter = - std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName); - if (iter == cf_names.end()) { - ASSERT_TRUE(s.IsInvalidArgument()); - } else { - ASSERT_NE(s.ToString().find(manifest_path), std::string::npos); - ASSERT_TRUE(s.IsCorruption()); - } -} - -TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) { - db_options_.write_dbid_to_manifest = std::get<0>(GetParam()); - PrepareManifest(nullptr, nullptr, &log_writer_); - // Write all column families but no log_number, next_file_number and - // last_sequence. - const std::vector all_cf_names = { - kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2, - kColumnFamilyName3}; - uint32_t cf_id = 1; - Status s; - for (size_t i = 1; i != all_cf_names.size(); ++i) { - VersionEdit new_cf; - new_cf.AddColumnFamily(all_cf_names[i]); - new_cf.SetColumnFamily(cf_id++); - std::string record; - ASSERT_TRUE(new_cf.EncodeTo(&record)); - s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - log_writer_.reset(); - s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr); - ASSERT_OK(s); - - std::string manifest_path; - VerifyManifest(&manifest_path); - - bool read_only = std::get<1>(GetParam()); - const std::vector& cf_names = std::get<2>(GetParam()); - std::vector column_families; - for (const auto& cf_name : cf_names) { - column_families.emplace_back(cf_name, cf_options_); - } - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families, - read_only, &db_id, - &has_missing_table_file); - auto iter = - std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName); - if (iter == cf_names.end()) { - ASSERT_TRUE(s.IsInvalidArgument()); - } else { - ASSERT_NE(s.ToString().find(manifest_path), std::string::npos); - ASSERT_TRUE(s.IsCorruption()); - } -} - -TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) { - db_options_.write_dbid_to_manifest = std::get<0>(GetParam()); - PrepareManifest(nullptr, nullptr, &log_writer_); - // Write all column families but no log_number, next_file_number and - // last_sequence. - const std::vector all_cf_names = { - kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2, - kColumnFamilyName3}; - uint32_t cf_id = 1; - Status s; - for (size_t i = 1; i != all_cf_names.size(); ++i) { - VersionEdit new_cf; - new_cf.AddColumnFamily(all_cf_names[i]); - new_cf.SetColumnFamily(cf_id++); - std::string record; - ASSERT_TRUE(new_cf.EncodeTo(&record)); - s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - { - VersionEdit tmp_edit; - tmp_edit.SetColumnFamily(4); - tmp_edit.SetLogNumber(0); - tmp_edit.SetNextFile(2); - tmp_edit.SetLastSequence(0); - std::string record; - ASSERT_TRUE(tmp_edit.EncodeTo(&record)); - s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - log_writer_.reset(); - s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr); - ASSERT_OK(s); - - std::string manifest_path; - VerifyManifest(&manifest_path); - - bool read_only = std::get<1>(GetParam()); - const std::vector& cf_names = std::get<2>(GetParam()); - std::vector column_families; - for (const auto& cf_name : cf_names) { - column_families.emplace_back(cf_name, cf_options_); - } - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families, - read_only, &db_id, - &has_missing_table_file); - auto iter = - std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName); - if (iter == cf_names.end()) { - ASSERT_TRUE(s.IsInvalidArgument()); - } else { - ASSERT_NE(s.ToString().find(manifest_path), std::string::npos); - ASSERT_TRUE(s.IsCorruption()); - } -} - -TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) { - db_options_.write_dbid_to_manifest = std::get<0>(GetParam()); - PrepareManifest(nullptr, nullptr, &log_writer_); - // Write all column families but no log_number, next_file_number and - // last_sequence. - const std::vector all_cf_names = { - kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2, - kColumnFamilyName3}; - uint32_t cf_id = 1; - Status s; - for (size_t i = 1; i != all_cf_names.size(); ++i) { - VersionEdit new_cf; - new_cf.AddColumnFamily(all_cf_names[i]); - new_cf.SetColumnFamily(cf_id++); - std::string record; - ASSERT_TRUE(new_cf.EncodeTo(&record)); - s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - { - VersionEdit tmp_edit; - tmp_edit.SetLogNumber(0); - tmp_edit.SetNextFile(2); - tmp_edit.SetLastSequence(0); - std::string record; - ASSERT_TRUE(tmp_edit.EncodeTo(&record)); - s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - log_writer_.reset(); - s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr); - ASSERT_OK(s); - - std::string manifest_path; - VerifyManifest(&manifest_path); - - bool read_only = std::get<1>(GetParam()); - const std::vector& cf_names = std::get<2>(GetParam()); - std::vector column_families; - for (const auto& cf_name : cf_names) { - column_families.emplace_back(cf_name, cf_options_); - } - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families, - read_only, &db_id, - &has_missing_table_file); - auto iter = - std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName); - if (iter == cf_names.end()) { - ASSERT_TRUE(s.IsInvalidArgument()); - } else if (read_only) { - ASSERT_OK(s); - ASSERT_FALSE(has_missing_table_file); - } else if (cf_names.size() == all_cf_names.size()) { - ASSERT_OK(s); - ASSERT_FALSE(has_missing_table_file); - } else if (cf_names.size() < all_cf_names.size()) { - ASSERT_TRUE(s.IsInvalidArgument()); - } else { - ASSERT_OK(s); - ASSERT_FALSE(has_missing_table_file); - ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetColumnFamily( - kUnknownColumnFamilyName); - ASSERT_EQ(nullptr, cfd); - } -} - -INSTANTIATE_TEST_CASE_P( - BestEffortRecovery, VersionSetTestEmptyDb, - testing::Combine( - /*write_dbid_to_manifest=*/testing::Bool(), - /*read_only=*/testing::Bool(), - /*cf_names=*/ - testing::Values( - std::vector(), - std::vector({kDefaultColumnFamilyName}), - std::vector({VersionSetTestBase::kColumnFamilyName1, - VersionSetTestBase::kColumnFamilyName2, - VersionSetTestBase::kColumnFamilyName3}), - std::vector({kDefaultColumnFamilyName, - VersionSetTestBase::kColumnFamilyName1}), - std::vector({kDefaultColumnFamilyName, - VersionSetTestBase::kColumnFamilyName1, - VersionSetTestBase::kColumnFamilyName2, - VersionSetTestBase::kColumnFamilyName3}), - std::vector( - {kDefaultColumnFamilyName, - VersionSetTestBase::kColumnFamilyName1, - VersionSetTestBase::kColumnFamilyName2, - VersionSetTestBase::kColumnFamilyName3, - VersionSetTestEmptyDb::kUnknownColumnFamilyName})))); - -class VersionSetTestMissingFiles : public VersionSetTestBase, - public testing::Test { - public: - VersionSetTestMissingFiles() - : VersionSetTestBase("version_set_test_missing_files"), - block_based_table_options_(), - table_factory_(std::make_shared( - block_based_table_options_)), - internal_comparator_( - std::make_shared(options_.comparator)) {} - - protected: - void PrepareManifest(std::vector* column_families, - SequenceNumber* last_seqno, - std::unique_ptr* log_writer) override { - assert(column_families != nullptr); - assert(last_seqno != nullptr); - assert(log_writer != nullptr); - const std::string manifest = DescriptorFileName(dbname_, 1); - const auto& fs = env_->GetFileSystem(); - std::unique_ptr file_writer; - Status s = WritableFileWriter::Create( - fs, manifest, fs->OptimizeForManifestWrite(env_options_), &file_writer, - nullptr); - ASSERT_OK(s); - log_writer->reset(new log::Writer(std::move(file_writer), 0, false)); - VersionEdit new_db; - if (db_options_.write_dbid_to_manifest) { - DBOptions tmp_db_options; - tmp_db_options.env = env_; - std::unique_ptr impl(new DBImpl(tmp_db_options, dbname_)); - std::string db_id; - impl->GetDbIdentityFromIdentityFile(&db_id); - new_db.SetDBId(db_id); - } - { - std::string record; - ASSERT_TRUE(new_db.EncodeTo(&record)); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - } - const std::vector cf_names = { - kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2, - kColumnFamilyName3}; - uint32_t cf_id = 1; // default cf id is 0 - cf_options_.table_factory = table_factory_; - for (const auto& cf_name : cf_names) { - column_families->emplace_back(cf_name, cf_options_); - if (cf_name == kDefaultColumnFamilyName) { - continue; - } - VersionEdit new_cf; - new_cf.AddColumnFamily(cf_name); - new_cf.SetColumnFamily(cf_id); - std::string record; - ASSERT_TRUE(new_cf.EncodeTo(&record)); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - - VersionEdit cf_files; - cf_files.SetColumnFamily(cf_id); - cf_files.SetLogNumber(0); - record.clear(); - ASSERT_TRUE(cf_files.EncodeTo(&record)); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - ++cf_id; - } - SequenceNumber seq = 2; - { - VersionEdit edit; - edit.SetNextFile(7); - edit.SetLastSequence(seq); - std::string record; - ASSERT_TRUE(edit.EncodeTo(&record)); - s = (*log_writer)->AddRecord(record); - ASSERT_OK(s); - } - *last_seqno = seq + 1; - } - - struct SstInfo { - uint64_t file_number; - std::string column_family; - std::string key; // the only key - int level = 0; - uint64_t epoch_number; - SstInfo(uint64_t file_num, const std::string& cf_name, - const std::string& _key, - uint64_t _epoch_number = kUnknownEpochNumber) - : SstInfo(file_num, cf_name, _key, 0, _epoch_number) {} - SstInfo(uint64_t file_num, const std::string& cf_name, - const std::string& _key, int lvl, - uint64_t _epoch_number = kUnknownEpochNumber) - : file_number(file_num), - column_family(cf_name), - key(_key), - level(lvl), - epoch_number(_epoch_number) {} - }; - - // Create dummy sst, return their metadata. Note that only file name and size - // are used. - void CreateDummyTableFiles(const std::vector& file_infos, - std::vector* file_metas) { - assert(file_metas != nullptr); - for (const auto& info : file_infos) { - uint64_t file_num = info.file_number; - std::string fname = MakeTableFileName(dbname_, file_num); - std::unique_ptr file; - Status s = fs_->NewWritableFile(fname, FileOptions(), &file, nullptr); - ASSERT_OK(s); - std::unique_ptr fwriter(new WritableFileWriter( - std::move(file), fname, FileOptions(), env_->GetSystemClock().get())); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - - std::unique_ptr builder(table_factory_->NewTableBuilder( - TableBuilderOptions( - immutable_options_, mutable_cf_options_, *internal_comparator_, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), - TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, - info.column_family, info.level), - fwriter.get())); - InternalKey ikey(info.key, 0, ValueType::kTypeValue); - builder->Add(ikey.Encode(), "value"); - ASSERT_OK(builder->Finish()); - ASSERT_OK(fwriter->Flush()); - uint64_t file_size = 0; - s = fs_->GetFileSize(fname, IOOptions(), &file_size, nullptr); - ASSERT_OK(s); - ASSERT_NE(0, file_size); - file_metas->emplace_back(file_num, /*file_path_id=*/0, file_size, ikey, - ikey, 0, 0, false, Temperature::kUnknown, 0, 0, - 0, info.epoch_number, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, - 0); - } - } - - // This method updates last_sequence_. - void WriteFileAdditionAndDeletionToManifest( - uint32_t cf, const std::vector>& added_files, - const std::vector>& deleted_files) { - VersionEdit edit; - edit.SetColumnFamily(cf); - for (const auto& elem : added_files) { - int level = elem.first; - edit.AddFile(level, elem.second); - } - for (const auto& elem : deleted_files) { - int level = elem.first; - edit.DeleteFile(level, elem.second); - } - edit.SetLastSequence(last_seqno_); - ++last_seqno_; - assert(log_writer_.get() != nullptr); - std::string record; - ASSERT_TRUE(edit.EncodeTo(&record)); - Status s = log_writer_->AddRecord(record); - ASSERT_OK(s); - } - - BlockBasedTableOptions block_based_table_options_; - std::shared_ptr table_factory_; - std::shared_ptr internal_comparator_; - std::vector column_families_; - SequenceNumber last_seqno_; - std::unique_ptr log_writer_; -}; - -TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) { - std::vector existing_files = { - SstInfo(100, kDefaultColumnFamilyName, "a", 100 /* epoch_number */), - SstInfo(102, kDefaultColumnFamilyName, "b", 102 /* epoch_number */), - SstInfo(103, kDefaultColumnFamilyName, "c", 103 /* epoch_number */), - SstInfo(107, kDefaultColumnFamilyName, "d", 107 /* epoch_number */), - SstInfo(110, kDefaultColumnFamilyName, "e", 110 /* epoch_number */)}; - std::vector file_metas; - CreateDummyTableFiles(existing_files, &file_metas); - - PrepareManifest(&column_families_, &last_seqno_, &log_writer_); - std::vector> added_files; - for (uint64_t file_num = 10; file_num < 15; ++file_num) { - std::string smallest_ukey = "a"; - std::string largest_ukey = "b"; - InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue); - InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue); - - FileMetaData meta = FileMetaData( - file_num, /*file_path_id=*/0, /*file_size=*/12, smallest_ikey, - largest_ikey, 0, 0, false, Temperature::kUnknown, 0, 0, 0, - file_num /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - added_files.emplace_back(0, meta); - } - WriteFileAdditionAndDeletionToManifest( - /*cf=*/0, added_files, std::vector>()); - std::vector> deleted_files; - deleted_files.emplace_back(0, 10); - WriteFileAdditionAndDeletionToManifest( - /*cf=*/0, std::vector>(), deleted_files); - log_writer_.reset(); - Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr); - ASSERT_OK(s); - std::string manifest_path; - VerifyManifest(&manifest_path); - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_, - /*read_only=*/false, &db_id, - &has_missing_table_file); - ASSERT_OK(s); - ASSERT_TRUE(has_missing_table_file); - for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) { - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - const std::vector& files = vstorage->LevelFiles(0); - ASSERT_TRUE(files.empty()); - } -} - -TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) { - std::vector existing_files = { - SstInfo(100, kDefaultColumnFamilyName, "a", 0 /* level */, - 100 /* epoch_number */), - SstInfo(102, kDefaultColumnFamilyName, "b", 0 /* level */, - 102 /* epoch_number */), - SstInfo(103, kDefaultColumnFamilyName, "c", 0 /* level */, - 103 /* epoch_number */), - SstInfo(107, kDefaultColumnFamilyName, "d", 0 /* level */, - 107 /* epoch_number */), - SstInfo(110, kDefaultColumnFamilyName, "e", 0 /* level */, - 110 /* epoch_number */)}; - std::vector file_metas; - CreateDummyTableFiles(existing_files, &file_metas); - - PrepareManifest(&column_families_, &last_seqno_, &log_writer_); - std::vector> added_files; - for (size_t i = 3; i != 5; ++i) { - added_files.emplace_back(0, file_metas[i]); - } - WriteFileAdditionAndDeletionToManifest( - /*cf=*/0, added_files, std::vector>()); - - added_files.clear(); - for (uint64_t file_num = 120; file_num < 130; ++file_num) { - std::string smallest_ukey = "a"; - std::string largest_ukey = "b"; - InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue); - InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue); - FileMetaData meta = FileMetaData( - file_num, /*file_path_id=*/0, /*file_size=*/12, smallest_ikey, - largest_ikey, 0, 0, false, Temperature::kUnknown, 0, 0, 0, - file_num /* epoch_number */, kUnknownFileChecksum, - kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0); - added_files.emplace_back(0, meta); - } - WriteFileAdditionAndDeletionToManifest( - /*cf=*/0, added_files, std::vector>()); - log_writer_.reset(); - Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr); - ASSERT_OK(s); - std::string manifest_path; - VerifyManifest(&manifest_path); - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_, - /*read_only=*/false, &db_id, - &has_missing_table_file); - ASSERT_OK(s); - ASSERT_TRUE(has_missing_table_file); - for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) { - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - const std::vector& files = vstorage->LevelFiles(0); - if (cfd->GetName() == kDefaultColumnFamilyName) { - ASSERT_EQ(2, files.size()); - for (const auto* fmeta : files) { - if (fmeta->fd.GetNumber() != 107 && fmeta->fd.GetNumber() != 110) { - ASSERT_FALSE(true); - } - } - } else { - ASSERT_TRUE(files.empty()); - } - } -} - -TEST_F(VersionSetTestMissingFiles, NoFileMissing) { - std::vector existing_files = { - SstInfo(100, kDefaultColumnFamilyName, "a", 0 /* level */, - 100 /* epoch_number */), - SstInfo(102, kDefaultColumnFamilyName, "b", 0 /* level */, - 102 /* epoch_number */), - SstInfo(103, kDefaultColumnFamilyName, "c", 0 /* level */, - 103 /* epoch_number */), - SstInfo(107, kDefaultColumnFamilyName, "d", 0 /* level */, - 107 /* epoch_number */), - SstInfo(110, kDefaultColumnFamilyName, "e", 0 /* level */, - 110 /* epoch_number */)}; - std::vector file_metas; - CreateDummyTableFiles(existing_files, &file_metas); - - PrepareManifest(&column_families_, &last_seqno_, &log_writer_); - std::vector> added_files; - for (const auto& meta : file_metas) { - added_files.emplace_back(0, meta); - } - WriteFileAdditionAndDeletionToManifest( - /*cf=*/0, added_files, std::vector>()); - std::vector> deleted_files; - deleted_files.emplace_back(/*level=*/0, 100); - WriteFileAdditionAndDeletionToManifest( - /*cf=*/0, std::vector>(), deleted_files); - log_writer_.reset(); - Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr); - ASSERT_OK(s); - std::string manifest_path; - VerifyManifest(&manifest_path); - std::string db_id; - bool has_missing_table_file = false; - s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_, - /*read_only=*/false, &db_id, - &has_missing_table_file); - ASSERT_OK(s); - ASSERT_FALSE(has_missing_table_file); - for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) { - VersionStorageInfo* vstorage = cfd->current()->storage_info(); - const std::vector& files = vstorage->LevelFiles(0); - if (cfd->GetName() == kDefaultColumnFamilyName) { - ASSERT_EQ(existing_files.size() - deleted_files.size(), files.size()); - bool has_deleted_file = false; - for (const auto* fmeta : files) { - if (fmeta->fd.GetNumber() == 100) { - has_deleted_file = true; - break; - } - } - ASSERT_FALSE(has_deleted_file); - } else { - ASSERT_TRUE(files.empty()); - } - } -} - -TEST_F(VersionSetTestMissingFiles, MinLogNumberToKeep2PC) { - db_options_.allow_2pc = true; - NewDB(); - - SstInfo sst(100, kDefaultColumnFamilyName, "a", 0 /* level */, - 100 /* epoch_number */); - std::vector file_metas; - CreateDummyTableFiles({sst}, &file_metas); - - constexpr WalNumber kMinWalNumberToKeep2PC = 10; - VersionEdit edit; - edit.AddFile(0, file_metas[0]); - edit.SetMinLogNumberToKeep(kMinWalNumberToKeep2PC); - ASSERT_OK(LogAndApplyToDefaultCF(edit)); - ASSERT_EQ(versions_->min_log_number_to_keep(), kMinWalNumberToKeep2PC); - - for (int i = 0; i < 3; i++) { - CreateNewManifest(); - ReopenDB(); - ASSERT_EQ(versions_->min_log_number_to_keep(), kMinWalNumberToKeep2PC); - } -} - -class ChargeFileMetadataTest : public DBTestBase { - public: - ChargeFileMetadataTest() - : DBTestBase("charge_file_metadata_test", /*env_do_fsync=*/true) {} -}; - -class ChargeFileMetadataTestWithParam - : public ChargeFileMetadataTest, - public testing::WithParamInterface { - public: - ChargeFileMetadataTestWithParam() {} -}; - -INSTANTIATE_TEST_CASE_P( - ChargeFileMetadataTestWithParam, ChargeFileMetadataTestWithParam, - ::testing::Values(CacheEntryRoleOptions::Decision::kEnabled, - CacheEntryRoleOptions::Decision::kDisabled)); - -TEST_P(ChargeFileMetadataTestWithParam, Basic) { - Options options; - BlockBasedTableOptions table_options; - CacheEntryRoleOptions::Decision charge_file_metadata = GetParam(); - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kFileMetadata, {/*.charged = */ charge_file_metadata}}); - std::shared_ptr> - file_metadata_charge_only_cache = std::make_shared< - TargetCacheChargeTrackingCache>( - NewLRUCache( - 4 * CacheReservationManagerImpl< - CacheEntryRole::kFileMetadata>::GetDummyEntrySize(), - 0 /* num_shard_bits */, true /* strict_capacity_limit */)); - table_options.block_cache = file_metadata_charge_only_cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.create_if_missing = true; - options.disable_auto_compactions = true; - DestroyAndReopen(options); - - // Create 128 file metadata, each of which is roughly 1024 bytes. - // This results in 1 * - // CacheReservationManagerImpl::GetDummyEntrySize() - // cache reservation for file metadata. - for (int i = 1; i <= 128; ++i) { - ASSERT_OK(Put(std::string(1024, 'a'), "va")); - ASSERT_OK(Put("b", "vb")); - ASSERT_OK(Flush()); - } - if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) { - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), - 1 * CacheReservationManagerImpl< - CacheEntryRole::kFileMetadata>::GetDummyEntrySize()); - - } else { - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), 0); - } - - // Create another 128 file metadata. - // This increases the file metadata cache reservation to 2 * - // CacheReservationManagerImpl::GetDummyEntrySize(). - for (int i = 1; i <= 128; ++i) { - ASSERT_OK(Put(std::string(1024, 'a'), "vva")); - ASSERT_OK(Put("b", "vvb")); - ASSERT_OK(Flush()); - } - if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) { - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), - 2 * CacheReservationManagerImpl< - CacheEntryRole::kFileMetadata>::GetDummyEntrySize()); - } else { - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), 0); - } - // Compaction will create 1 new file metadata, obsolete and delete all 256 - // file metadata above. This results in 1 * - // CacheReservationManagerImpl::GetDummyEntrySize() - // cache reservation for file metadata. - SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles", - "ChargeFileMetadataTestWithParam::" - "PreVerifyingCacheReservationRelease"}}); - SyncPoint::GetInstance()->EnableProcessing(); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ("0,1", FilesPerLevel(0)); - TEST_SYNC_POINT( - "ChargeFileMetadataTestWithParam::PreVerifyingCacheReservationRelease"); - if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) { - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), - 1 * CacheReservationManagerImpl< - CacheEntryRole::kFileMetadata>::GetDummyEntrySize()); - } else { - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), 0); - } - SyncPoint::GetInstance()->DisableProcessing(); - - // Destroying the db will delete the remaining 1 new file metadata - // This results in no cache reservation for file metadata. - Destroy(options); - EXPECT_EQ(file_metadata_charge_only_cache->GetCacheCharge(), - 0 * CacheReservationManagerImpl< - CacheEntryRole::kFileMetadata>::GetDummyEntrySize()); - - // Reopen the db with a smaller cache in order to test failure in allocating - // file metadata due to memory limit based on cache capacity - file_metadata_charge_only_cache = std::make_shared< - TargetCacheChargeTrackingCache>( - NewLRUCache(1 * CacheReservationManagerImpl< - CacheEntryRole::kFileMetadata>::GetDummyEntrySize(), - 0 /* num_shard_bits */, true /* strict_capacity_limit */)); - table_options.block_cache = file_metadata_charge_only_cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Reopen(options); - ASSERT_OK(Put(std::string(1024, 'a'), "va")); - ASSERT_OK(Put("b", "vb")); - Status s = Flush(); - if (charge_file_metadata == CacheEntryRoleOptions::Decision::kEnabled) { - EXPECT_TRUE(s.IsMemoryLimit()); - EXPECT_TRUE(s.ToString().find( - kCacheEntryRoleToCamelString[static_cast( - CacheEntryRole::kFileMetadata)]) != std::string::npos); - EXPECT_TRUE(s.ToString().find("memory limit based on cache capacity") != - std::string::npos); - } else { - EXPECT_TRUE(s.ok()); - } -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/wal_edit_test.cc b/db/wal_edit_test.cc deleted file mode 100644 index 0c18fb125..000000000 --- a/db/wal_edit_test.cc +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/wal_edit.h" - -#include "db/db_test_util.h" -#include "file/file_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -TEST(WalSet, AddDeleteReset) { - WalSet wals; - ASSERT_TRUE(wals.GetWals().empty()); - - // Create WAL 1 - 10. - for (WalNumber log_number = 1; log_number <= 10; log_number++) { - wals.AddWal(WalAddition(log_number)); - } - ASSERT_EQ(wals.GetWals().size(), 10); - - // Delete WAL 1 - 5. - wals.DeleteWalsBefore(6); - ASSERT_EQ(wals.GetWals().size(), 5); - - WalNumber expected_log_number = 6; - for (auto it : wals.GetWals()) { - WalNumber log_number = it.first; - ASSERT_EQ(log_number, expected_log_number++); - } - - wals.Reset(); - ASSERT_TRUE(wals.GetWals().empty()); -} - -TEST(WalSet, Overwrite) { - constexpr WalNumber kNumber = 100; - constexpr uint64_t kBytes = 200; - WalSet wals; - wals.AddWal(WalAddition(kNumber)); - ASSERT_FALSE(wals.GetWals().at(kNumber).HasSyncedSize()); - wals.AddWal(WalAddition(kNumber, WalMetadata(kBytes))); - ASSERT_TRUE(wals.GetWals().at(kNumber).HasSyncedSize()); - ASSERT_EQ(wals.GetWals().at(kNumber).GetSyncedSizeInBytes(), kBytes); -} - -TEST(WalSet, SmallerSyncedSize) { - constexpr WalNumber kNumber = 100; - constexpr uint64_t kBytes = 100; - WalSet wals; - ASSERT_OK(wals.AddWal(WalAddition(kNumber, WalMetadata(kBytes)))); - const auto wals1 = wals.GetWals(); - Status s = wals.AddWal(WalAddition(kNumber, WalMetadata(0))); - const auto wals2 = wals.GetWals(); - ASSERT_OK(s); - ASSERT_EQ(wals1, wals2); -} - -TEST(WalSet, CreateTwice) { - constexpr WalNumber kNumber = 100; - WalSet wals; - ASSERT_OK(wals.AddWal(WalAddition(kNumber))); - Status s = wals.AddWal(WalAddition(kNumber)); - ASSERT_TRUE(s.IsCorruption()); - ASSERT_TRUE(s.ToString().find("WAL 100 is created more than once") != - std::string::npos); -} - -TEST(WalSet, DeleteAllWals) { - constexpr WalNumber kMaxWalNumber = 10; - WalSet wals; - for (WalNumber i = 1; i <= kMaxWalNumber; i++) { - wals.AddWal(WalAddition(i)); - } - ASSERT_OK(wals.DeleteWalsBefore(kMaxWalNumber + 1)); -} - -TEST(WalSet, AddObsoleteWal) { - constexpr WalNumber kNumber = 100; - WalSet wals; - ASSERT_OK(wals.DeleteWalsBefore(kNumber + 1)); - ASSERT_OK(wals.AddWal(WalAddition(kNumber))); - ASSERT_TRUE(wals.GetWals().empty()); -} - -TEST(WalSet, MinWalNumberToKeep) { - constexpr WalNumber kNumber = 100; - WalSet wals; - ASSERT_EQ(wals.GetMinWalNumberToKeep(), 0); - ASSERT_OK(wals.DeleteWalsBefore(kNumber)); - ASSERT_EQ(wals.GetMinWalNumberToKeep(), kNumber); - ASSERT_OK(wals.DeleteWalsBefore(kNumber - 1)); - ASSERT_EQ(wals.GetMinWalNumberToKeep(), kNumber); - ASSERT_OK(wals.DeleteWalsBefore(kNumber + 1)); - ASSERT_EQ(wals.GetMinWalNumberToKeep(), kNumber + 1); -} - -class WalSetTest : public DBTestBase { - public: - WalSetTest() : DBTestBase("WalSetTest", /* env_do_fsync */ true) {} - - void SetUp() override { - test_dir_ = test::PerThreadDBPath("wal_set_test"); - ASSERT_OK(env_->CreateDir(test_dir_)); - } - - void TearDown() override { - EXPECT_OK(DestroyDir(env_, test_dir_)); - logs_on_disk_.clear(); - wals_.Reset(); - } - - void CreateWalOnDisk(WalNumber number, const std::string& fname, - uint64_t size_bytes) { - std::unique_ptr f; - std::string fpath = Path(fname); - ASSERT_OK(env_->NewWritableFile(fpath, &f, EnvOptions())); - std::string content(size_bytes, '0'); - ASSERT_OK(f->Append(content)); - ASSERT_OK(f->Close()); - - logs_on_disk_[number] = fpath; - } - - void AddWalToWalSet(WalNumber number, uint64_t size_bytes) { - // Create WAL. - ASSERT_OK(wals_.AddWal(WalAddition(number))); - // Close WAL. - WalMetadata wal(size_bytes); - ASSERT_OK(wals_.AddWal(WalAddition(number, wal))); - } - - Status CheckWals() const { return wals_.CheckWals(env_, logs_on_disk_); } - - private: - std::string test_dir_; - std::unordered_map logs_on_disk_; - WalSet wals_; - - std::string Path(const std::string& fname) { return test_dir_ + "/" + fname; } -}; - -TEST_F(WalSetTest, CheckEmptyWals) { ASSERT_OK(CheckWals()); } - -TEST_F(WalSetTest, CheckWals) { - for (int number = 1; number < 10; number++) { - uint64_t size = rand() % 100; - std::stringstream ss; - ss << "log" << number; - std::string fname = ss.str(); - CreateWalOnDisk(number, fname, size); - // log 0 - 5 are obsolete. - if (number > 5) { - AddWalToWalSet(number, size); - } - } - ASSERT_OK(CheckWals()); -} - -TEST_F(WalSetTest, CheckMissingWals) { - for (int number = 1; number < 10; number++) { - uint64_t size = rand() % 100; - AddWalToWalSet(number, size); - // logs with even number are missing from disk. - if (number % 2) { - std::stringstream ss; - ss << "log" << number; - std::string fname = ss.str(); - CreateWalOnDisk(number, fname, size); - } - } - - Status s = CheckWals(); - ASSERT_TRUE(s.IsCorruption()) << s.ToString(); - // The first log with even number is missing. - std::stringstream expected_err; - expected_err << "Missing WAL with log number: " << 2; - ASSERT_TRUE(s.ToString().find(expected_err.str()) != std::string::npos) - << s.ToString(); -} - -TEST_F(WalSetTest, CheckWalsWithShrinkedSize) { - for (int number = 1; number < 10; number++) { - uint64_t size = rand() % 100 + 1; - AddWalToWalSet(number, size); - // logs with even number have shrinked size. - std::stringstream ss; - ss << "log" << number; - std::string fname = ss.str(); - CreateWalOnDisk(number, fname, (number % 2) ? size : size - 1); - } - - Status s = CheckWals(); - ASSERT_TRUE(s.IsCorruption()) << s.ToString(); - // The first log with even number has wrong size. - std::stringstream expected_err; - expected_err << "Size mismatch: WAL (log number: " << 2 << ")"; - ASSERT_TRUE(s.ToString().find(expected_err.str()) != std::string::npos) - << s.ToString(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/wal_manager_test.cc b/db/wal_manager_test.cc deleted file mode 100644 index 0144e1846..000000000 --- a/db/wal_manager_test.cc +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "db/wal_manager.h" - -#include -#include - -#include "db/column_family.h" -#include "db/db_impl/db_impl.h" -#include "db/log_writer.h" -#include "db/version_set.h" -#include "env/mock_env.h" -#include "file/writable_file_writer.h" -#include "rocksdb/cache.h" -#include "rocksdb/file_system.h" -#include "rocksdb/write_batch.h" -#include "rocksdb/write_buffer_manager.h" -#include "table/mock_table.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -// TODO(icanadi) mock out VersionSet -// TODO(icanadi) move other WalManager-specific tests from db_test here -class WalManagerTest : public testing::Test { - public: - WalManagerTest() - : dbname_(test::PerThreadDBPath("wal_manager_test")), - db_options_(), - table_cache_(NewLRUCache(50000, 16)), - write_buffer_manager_(db_options_.db_write_buffer_size), - current_log_number_(0) { - env_.reset(MockEnv::Create(Env::Default())); - EXPECT_OK(DestroyDB(dbname_, Options())); - } - - void Init() { - ASSERT_OK(env_->CreateDirIfMissing(dbname_)); - ASSERT_OK(env_->CreateDirIfMissing(ArchivalDirectory(dbname_))); - db_options_.db_paths.emplace_back(dbname_, - std::numeric_limits::max()); - db_options_.wal_dir = dbname_; - db_options_.env = env_.get(); - db_options_.fs = env_->GetFileSystem(); - db_options_.clock = env_->GetSystemClock().get(); - - versions_.reset( - new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(), - &write_buffer_manager_, &write_controller_, - /*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr, - /*db_id*/ "", /*db_session_id*/ "")); - - wal_manager_.reset( - new WalManager(db_options_, env_options_, nullptr /*IOTracer*/)); - } - - void Reopen() { - wal_manager_.reset( - new WalManager(db_options_, env_options_, nullptr /*IOTracer*/)); - } - - // NOT thread safe - void Put(const std::string& key, const std::string& value) { - assert(current_log_writer_.get() != nullptr); - uint64_t seq = versions_->LastSequence() + 1; - WriteBatch batch; - ASSERT_OK(batch.Put(key, value)); - WriteBatchInternal::SetSequence(&batch, seq); - ASSERT_OK( - current_log_writer_->AddRecord(WriteBatchInternal::Contents(&batch))); - versions_->SetLastAllocatedSequence(seq); - versions_->SetLastPublishedSequence(seq); - versions_->SetLastSequence(seq); - } - - // NOT thread safe - void RollTheLog(bool /*archived*/) { - current_log_number_++; - std::string fname = ArchivedLogFileName(dbname_, current_log_number_); - const auto& fs = env_->GetFileSystem(); - std::unique_ptr file_writer; - ASSERT_OK(WritableFileWriter::Create(fs, fname, env_options_, &file_writer, - nullptr)); - current_log_writer_.reset( - new log::Writer(std::move(file_writer), 0, false)); - } - - void CreateArchiveLogs(int num_logs, int entries_per_log) { - for (int i = 1; i <= num_logs; ++i) { - RollTheLog(true); - for (int k = 0; k < entries_per_log; ++k) { - Put(std::to_string(k), std::string(1024, 'a')); - } - } - } - - std::unique_ptr OpenTransactionLogIter( - const SequenceNumber seq) { - std::unique_ptr iter; - Status status = wal_manager_->GetUpdatesSince( - seq, &iter, TransactionLogIterator::ReadOptions(), versions_.get()); - EXPECT_OK(status); - return iter; - } - - std::unique_ptr env_; - std::string dbname_; - ImmutableDBOptions db_options_; - WriteController write_controller_; - EnvOptions env_options_; - std::shared_ptr table_cache_; - WriteBufferManager write_buffer_manager_; - std::unique_ptr versions_; - std::unique_ptr wal_manager_; - - std::unique_ptr current_log_writer_; - uint64_t current_log_number_; -}; - -TEST_F(WalManagerTest, ReadFirstRecordCache) { - Init(); - std::string path = dbname_ + "/000001.log"; - std::unique_ptr file; - ASSERT_OK(env_->GetFileSystem()->NewWritableFile(path, FileOptions(), &file, - nullptr)); - - SequenceNumber s; - ASSERT_OK(wal_manager_->TEST_ReadFirstLine(path, 1 /* number */, &s)); - ASSERT_EQ(s, 0U); - - ASSERT_OK( - wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1 /* number */, &s)); - ASSERT_EQ(s, 0U); - - std::unique_ptr file_writer( - new WritableFileWriter(std::move(file), path, FileOptions())); - log::Writer writer(std::move(file_writer), 1, - db_options_.recycle_log_file_num > 0); - WriteBatch batch; - ASSERT_OK(batch.Put("foo", "bar")); - WriteBatchInternal::SetSequence(&batch, 10); - ASSERT_OK(writer.AddRecord(WriteBatchInternal::Contents(&batch))); - - // TODO(icanadi) move SpecialEnv outside of db_test, so we can reuse it here. - // Waiting for lei to finish with db_test - // env_->count_sequential_reads_ = true; - // sequential_read_counter_ sanity test - // ASSERT_EQ(env_->sequential_read_counter_.Read(), 0); - - ASSERT_OK(wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1, &s)); - ASSERT_EQ(s, 10U); - // did a read - // TODO(icanadi) move SpecialEnv outside of db_test, so we can reuse it here - // ASSERT_EQ(env_->sequential_read_counter_.Read(), 1); - - ASSERT_OK(wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1, &s)); - ASSERT_EQ(s, 10U); - // no new reads since the value is cached - // TODO(icanadi) move SpecialEnv outside of db_test, so we can reuse it here - // ASSERT_EQ(env_->sequential_read_counter_.Read(), 1); -} - -namespace { -uint64_t GetLogDirSize(std::string dir_path, Env* env) { - uint64_t dir_size = 0; - std::vector files; - EXPECT_OK(env->GetChildren(dir_path, &files)); - for (auto& f : files) { - uint64_t number; - FileType type; - if (ParseFileName(f, &number, &type) && type == kWalFile) { - std::string const file_path = dir_path + "/" + f; - uint64_t file_size; - EXPECT_OK(env->GetFileSize(file_path, &file_size)); - dir_size += file_size; - } - } - return dir_size; -} -std::vector ListSpecificFiles( - Env* env, const std::string& path, const FileType expected_file_type) { - std::vector files; - std::vector file_numbers; - uint64_t number; - FileType type; - EXPECT_OK(env->GetChildren(path, &files)); - for (size_t i = 0; i < files.size(); ++i) { - if (ParseFileName(files[i], &number, &type)) { - if (type == expected_file_type) { - file_numbers.push_back(number); - } - } - } - return file_numbers; -} - -int CountRecords(TransactionLogIterator* iter) { - int count = 0; - SequenceNumber lastSequence = 0; - BatchResult res; - while (iter->Valid()) { - res = iter->GetBatch(); - EXPECT_TRUE(res.sequence > lastSequence); - ++count; - lastSequence = res.sequence; - EXPECT_OK(iter->status()); - iter->Next(); - } - EXPECT_OK(iter->status()); - return count; -} -} // anonymous namespace - -TEST_F(WalManagerTest, WALArchivalSizeLimit) { - db_options_.WAL_ttl_seconds = 0; - db_options_.WAL_size_limit_MB = 1000; - Init(); - - // TEST : Create WalManager with huge size limit and no ttl. - // Create some archived files and call PurgeObsoleteWALFiles(). - // Count the archived log files that survived. - // Assert that all of them did. - // Change size limit. Re-open WalManager. - // Assert that archive is not greater than WAL_size_limit_MB after - // PurgeObsoleteWALFiles() - // Set ttl and time_to_check_ to small values. Re-open db. - // Assert that there are no archived logs left. - - std::string archive_dir = ArchivalDirectory(dbname_); - CreateArchiveLogs(20, 5000); - - std::vector log_files = - ListSpecificFiles(env_.get(), archive_dir, kWalFile); - ASSERT_EQ(log_files.size(), 20U); - - db_options_.WAL_size_limit_MB = 8; - Reopen(); - wal_manager_->PurgeObsoleteWALFiles(); - - uint64_t archive_size = GetLogDirSize(archive_dir, env_.get()); - ASSERT_TRUE(archive_size <= db_options_.WAL_size_limit_MB * 1024 * 1024); - - db_options_.WAL_ttl_seconds = 1; - env_->SleepForMicroseconds(2 * 1000 * 1000); - Reopen(); - wal_manager_->PurgeObsoleteWALFiles(); - - log_files = ListSpecificFiles(env_.get(), archive_dir, kWalFile); - ASSERT_TRUE(log_files.empty()); -} - -TEST_F(WalManagerTest, WALArchivalTtl) { - db_options_.WAL_ttl_seconds = 1000; - Init(); - - // TEST : Create WalManager with a ttl and no size limit. - // Create some archived log files and call PurgeObsoleteWALFiles(). - // Assert that files are not deleted - // Reopen db with small ttl. - // Assert that all archived logs was removed. - - std::string archive_dir = ArchivalDirectory(dbname_); - CreateArchiveLogs(20, 5000); - - std::vector log_files = - ListSpecificFiles(env_.get(), archive_dir, kWalFile); - ASSERT_GT(log_files.size(), 0U); - - db_options_.WAL_ttl_seconds = 1; - env_->SleepForMicroseconds(3 * 1000 * 1000); - Reopen(); - wal_manager_->PurgeObsoleteWALFiles(); - - log_files = ListSpecificFiles(env_.get(), archive_dir, kWalFile); - ASSERT_TRUE(log_files.empty()); -} - -TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) { - Init(); - RollTheLog(false); - Put("key1", std::string(1024, 'a')); - // Create a zero record WAL file. - RollTheLog(false); - RollTheLog(false); - - Put("key2", std::string(1024, 'a')); - - auto iter = OpenTransactionLogIter(0); - ASSERT_EQ(2, CountRecords(iter.get())); -} - -TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) { - Init(); - RollTheLog(false); - auto iter = OpenTransactionLogIter(0); - // Check that an empty iterator is returned - ASSERT_TRUE(!iter->Valid()); -} - -TEST_F(WalManagerTest, TransactionLogIteratorNewFileWhileScanning) { - Init(); - CreateArchiveLogs(2, 100); - auto iter = OpenTransactionLogIter(0); - CreateArchiveLogs(1, 100); - int i = 0; - for (; iter->Valid(); iter->Next()) { - i++; - } - ASSERT_EQ(i, 200); - // A new log file was added after the iterator was created. - // TryAgain indicates a new iterator is needed to fetch the new data - ASSERT_TRUE(iter->status().IsTryAgain()); - - iter = OpenTransactionLogIter(0); - i = 0; - for (; iter->Valid(); iter->Next()) { - i++; - } - ASSERT_EQ(i, 300); - ASSERT_TRUE(iter->status().ok()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/write_batch_test.cc b/db/write_batch_test.cc deleted file mode 100644 index 4bd74f71e..000000000 --- a/db/write_batch_test.cc +++ /dev/null @@ -1,1112 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include "db/column_family.h" -#include "db/db_test_util.h" -#include "db/memtable.h" -#include "db/write_batch_internal.h" -#include "rocksdb/comparator.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/utilities/write_batch_with_index.h" -#include "rocksdb/write_buffer_manager.h" -#include "table/scoped_arena_iterator.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -static std::string PrintContents(WriteBatch* b, - bool merge_operator_supported = true) { - InternalKeyComparator cmp(BytewiseComparator()); - auto factory = std::make_shared(); - Options options; - options.memtable_factory = factory; - if (merge_operator_supported) { - options.merge_operator.reset(new TestPutOperator()); - } - ImmutableOptions ioptions(options); - WriteBufferManager wb(options.db_write_buffer_size); - MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, - kMaxSequenceNumber, 0 /* column_family_id */); - mem->Ref(); - std::string state; - ColumnFamilyMemTablesDefault cf_mems_default(mem); - Status s = - WriteBatchInternal::InsertInto(b, &cf_mems_default, nullptr, nullptr); - uint32_t count = 0; - int put_count = 0; - int delete_count = 0; - int single_delete_count = 0; - int delete_range_count = 0; - int merge_count = 0; - for (int i = 0; i < 2; ++i) { - Arena arena; - ScopedArenaIterator arena_iter_guard; - std::unique_ptr iter_guard; - InternalIterator* iter; - if (i == 0) { - iter = mem->NewIterator(ReadOptions(), &arena); - arena_iter_guard.set(iter); - } else { - iter = mem->NewRangeTombstoneIterator(ReadOptions(), - kMaxSequenceNumber /* read_seq */, - false /* immutable_memtable */); - iter_guard.reset(iter); - } - if (iter == nullptr) { - continue; - } - EXPECT_OK(iter->status()); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ParsedInternalKey ikey; - ikey.clear(); - EXPECT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */)); - switch (ikey.type) { - case kTypeValue: - state.append("Put("); - state.append(ikey.user_key.ToString()); - state.append(", "); - state.append(iter->value().ToString()); - state.append(")"); - count++; - put_count++; - break; - case kTypeDeletion: - state.append("Delete("); - state.append(ikey.user_key.ToString()); - state.append(")"); - count++; - delete_count++; - break; - case kTypeSingleDeletion: - state.append("SingleDelete("); - state.append(ikey.user_key.ToString()); - state.append(")"); - count++; - single_delete_count++; - break; - case kTypeRangeDeletion: - state.append("DeleteRange("); - state.append(ikey.user_key.ToString()); - state.append(", "); - state.append(iter->value().ToString()); - state.append(")"); - count++; - delete_range_count++; - break; - case kTypeMerge: - state.append("Merge("); - state.append(ikey.user_key.ToString()); - state.append(", "); - state.append(iter->value().ToString()); - state.append(")"); - count++; - merge_count++; - break; - default: - assert(false); - break; - } - state.append("@"); - state.append(std::to_string(ikey.sequence)); - } - EXPECT_OK(iter->status()); - } - if (s.ok()) { - EXPECT_EQ(b->HasPut(), put_count > 0); - EXPECT_EQ(b->HasDelete(), delete_count > 0); - EXPECT_EQ(b->HasSingleDelete(), single_delete_count > 0); - EXPECT_EQ(b->HasDeleteRange(), delete_range_count > 0); - EXPECT_EQ(b->HasMerge(), merge_count > 0); - if (count != WriteBatchInternal::Count(b)) { - state.append("CountMismatch()"); - } - } else { - state.append(s.ToString()); - } - delete mem->Unref(); - return state; -} - -class WriteBatchTest : public testing::Test {}; - -TEST_F(WriteBatchTest, Empty) { - WriteBatch batch; - ASSERT_EQ("", PrintContents(&batch)); - ASSERT_EQ(0u, WriteBatchInternal::Count(&batch)); - ASSERT_EQ(0u, batch.Count()); -} - -TEST_F(WriteBatchTest, Multiple) { - WriteBatch batch; - ASSERT_OK(batch.Put(Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Delete(Slice("box"))); - ASSERT_OK(batch.DeleteRange(Slice("bar"), Slice("foo"))); - ASSERT_OK(batch.Put(Slice("baz"), Slice("boo"))); - WriteBatchInternal::SetSequence(&batch, 100); - ASSERT_EQ(100U, WriteBatchInternal::Sequence(&batch)); - ASSERT_EQ(4u, WriteBatchInternal::Count(&batch)); - ASSERT_EQ( - "Put(baz, boo)@103" - "Delete(box)@101" - "Put(foo, bar)@100" - "DeleteRange(bar, foo)@102", - PrintContents(&batch)); - ASSERT_EQ(4u, batch.Count()); -} - -TEST_F(WriteBatchTest, Corruption) { - WriteBatch batch; - ASSERT_OK(batch.Put(Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Delete(Slice("box"))); - WriteBatchInternal::SetSequence(&batch, 200); - Slice contents = WriteBatchInternal::Contents(&batch); - ASSERT_OK(WriteBatchInternal::SetContents( - &batch, Slice(contents.data(), contents.size() - 1))); - ASSERT_EQ( - "Put(foo, bar)@200" - "Corruption: bad WriteBatch Delete", - PrintContents(&batch)); -} - -TEST_F(WriteBatchTest, Append) { - WriteBatch b1, b2; - WriteBatchInternal::SetSequence(&b1, 200); - WriteBatchInternal::SetSequence(&b2, 300); - ASSERT_OK(WriteBatchInternal::Append(&b1, &b2)); - ASSERT_EQ("", PrintContents(&b1)); - ASSERT_EQ(0u, b1.Count()); - ASSERT_OK(b2.Put("a", "va")); - ASSERT_OK(WriteBatchInternal::Append(&b1, &b2)); - ASSERT_EQ("Put(a, va)@200", PrintContents(&b1)); - ASSERT_EQ(1u, b1.Count()); - b2.Clear(); - ASSERT_OK(b2.Put("b", "vb")); - ASSERT_OK(WriteBatchInternal::Append(&b1, &b2)); - ASSERT_EQ( - "Put(a, va)@200" - "Put(b, vb)@201", - PrintContents(&b1)); - ASSERT_EQ(2u, b1.Count()); - ASSERT_OK(b2.Delete("foo")); - ASSERT_OK(WriteBatchInternal::Append(&b1, &b2)); - ASSERT_EQ( - "Put(a, va)@200" - "Put(b, vb)@202" - "Put(b, vb)@201" - "Delete(foo)@203", - PrintContents(&b1)); - ASSERT_EQ(4u, b1.Count()); - b2.Clear(); - ASSERT_OK(b2.Put("c", "cc")); - ASSERT_OK(b2.Put("d", "dd")); - b2.MarkWalTerminationPoint(); - ASSERT_OK(b2.Put("e", "ee")); - ASSERT_OK(WriteBatchInternal::Append(&b1, &b2, /*wal only*/ true)); - ASSERT_EQ( - "Put(a, va)@200" - "Put(b, vb)@202" - "Put(b, vb)@201" - "Put(c, cc)@204" - "Put(d, dd)@205" - "Delete(foo)@203", - PrintContents(&b1)); - ASSERT_EQ(6u, b1.Count()); - ASSERT_EQ( - "Put(c, cc)@0" - "Put(d, dd)@1" - "Put(e, ee)@2", - PrintContents(&b2)); - ASSERT_EQ(3u, b2.Count()); -} - -TEST_F(WriteBatchTest, SingleDeletion) { - WriteBatch batch; - WriteBatchInternal::SetSequence(&batch, 100); - ASSERT_EQ("", PrintContents(&batch)); - ASSERT_EQ(0u, batch.Count()); - ASSERT_OK(batch.Put("a", "va")); - ASSERT_EQ("Put(a, va)@100", PrintContents(&batch)); - ASSERT_EQ(1u, batch.Count()); - ASSERT_OK(batch.SingleDelete("a")); - ASSERT_EQ( - "SingleDelete(a)@101" - "Put(a, va)@100", - PrintContents(&batch)); - ASSERT_EQ(2u, batch.Count()); -} - -namespace { -struct TestHandler : public WriteBatch::Handler { - std::string seen; - Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { - if (column_family_id == 0) { - seen += "Put(" + key.ToString() + ", " + value.ToString() + ")"; - } else { - seen += "PutCF(" + std::to_string(column_family_id) + ", " + - key.ToString() + ", " + value.ToString() + ")"; - } - return Status::OK(); - } - Status DeleteCF(uint32_t column_family_id, const Slice& key) override { - if (column_family_id == 0) { - seen += "Delete(" + key.ToString() + ")"; - } else { - seen += "DeleteCF(" + std::to_string(column_family_id) + ", " + - key.ToString() + ")"; - } - return Status::OK(); - } - Status SingleDeleteCF(uint32_t column_family_id, const Slice& key) override { - if (column_family_id == 0) { - seen += "SingleDelete(" + key.ToString() + ")"; - } else { - seen += "SingleDeleteCF(" + std::to_string(column_family_id) + ", " + - key.ToString() + ")"; - } - return Status::OK(); - } - Status DeleteRangeCF(uint32_t column_family_id, const Slice& begin_key, - const Slice& end_key) override { - if (column_family_id == 0) { - seen += "DeleteRange(" + begin_key.ToString() + ", " + - end_key.ToString() + ")"; - } else { - seen += "DeleteRangeCF(" + std::to_string(column_family_id) + ", " + - begin_key.ToString() + ", " + end_key.ToString() + ")"; - } - return Status::OK(); - } - Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { - if (column_family_id == 0) { - seen += "Merge(" + key.ToString() + ", " + value.ToString() + ")"; - } else { - seen += "MergeCF(" + std::to_string(column_family_id) + ", " + - key.ToString() + ", " + value.ToString() + ")"; - } - return Status::OK(); - } - void LogData(const Slice& blob) override { - seen += "LogData(" + blob.ToString() + ")"; - } - Status MarkBeginPrepare(bool unprepare) override { - seen += - "MarkBeginPrepare(" + std::string(unprepare ? "true" : "false") + ")"; - return Status::OK(); - } - Status MarkEndPrepare(const Slice& xid) override { - seen += "MarkEndPrepare(" + xid.ToString() + ")"; - return Status::OK(); - } - Status MarkNoop(bool empty_batch) override { - seen += "MarkNoop(" + std::string(empty_batch ? "true" : "false") + ")"; - return Status::OK(); - } - Status MarkCommit(const Slice& xid) override { - seen += "MarkCommit(" + xid.ToString() + ")"; - return Status::OK(); - } - Status MarkCommitWithTimestamp(const Slice& xid, const Slice& ts) override { - seen += "MarkCommitWithTimestamp(" + xid.ToString() + ", " + - ts.ToString(true) + ")"; - return Status::OK(); - } - Status MarkRollback(const Slice& xid) override { - seen += "MarkRollback(" + xid.ToString() + ")"; - return Status::OK(); - } -}; -} // anonymous namespace - -TEST_F(WriteBatchTest, PutNotImplemented) { - WriteBatch batch; - ASSERT_OK(batch.Put(Slice("k1"), Slice("v1"))); - ASSERT_EQ(1u, batch.Count()); - ASSERT_EQ("Put(k1, v1)@0", PrintContents(&batch)); - - WriteBatch::Handler handler; - ASSERT_OK(batch.Iterate(&handler)); -} - -TEST_F(WriteBatchTest, DeleteNotImplemented) { - WriteBatch batch; - ASSERT_OK(batch.Delete(Slice("k2"))); - ASSERT_EQ(1u, batch.Count()); - ASSERT_EQ("Delete(k2)@0", PrintContents(&batch)); - - WriteBatch::Handler handler; - ASSERT_OK(batch.Iterate(&handler)); -} - -TEST_F(WriteBatchTest, SingleDeleteNotImplemented) { - WriteBatch batch; - ASSERT_OK(batch.SingleDelete(Slice("k2"))); - ASSERT_EQ(1u, batch.Count()); - ASSERT_EQ("SingleDelete(k2)@0", PrintContents(&batch)); - - WriteBatch::Handler handler; - ASSERT_OK(batch.Iterate(&handler)); -} - -TEST_F(WriteBatchTest, MergeNotImplemented) { - WriteBatch batch; - ASSERT_OK(batch.Merge(Slice("foo"), Slice("bar"))); - ASSERT_EQ(1u, batch.Count()); - ASSERT_EQ("Merge(foo, bar)@0", PrintContents(&batch)); - - WriteBatch::Handler handler; - ASSERT_OK(batch.Iterate(&handler)); -} - -TEST_F(WriteBatchTest, MergeWithoutOperatorInsertionFailure) { - WriteBatch batch; - ASSERT_OK(batch.Merge(Slice("foo"), Slice("bar"))); - ASSERT_EQ(1u, batch.Count()); - ASSERT_EQ( - "Invalid argument: Merge requires `ColumnFamilyOptions::merge_operator " - "!= nullptr`", - PrintContents(&batch, false /* merge_operator_supported */)); -} - -TEST_F(WriteBatchTest, Blob) { - WriteBatch batch; - ASSERT_OK(batch.Put(Slice("k1"), Slice("v1"))); - ASSERT_OK(batch.Put(Slice("k2"), Slice("v2"))); - ASSERT_OK(batch.Put(Slice("k3"), Slice("v3"))); - ASSERT_OK(batch.PutLogData(Slice("blob1"))); - ASSERT_OK(batch.Delete(Slice("k2"))); - ASSERT_OK(batch.SingleDelete(Slice("k3"))); - ASSERT_OK(batch.PutLogData(Slice("blob2"))); - ASSERT_OK(batch.Merge(Slice("foo"), Slice("bar"))); - ASSERT_EQ(6u, batch.Count()); - ASSERT_EQ( - "Merge(foo, bar)@5" - "Put(k1, v1)@0" - "Delete(k2)@3" - "Put(k2, v2)@1" - "SingleDelete(k3)@4" - "Put(k3, v3)@2", - PrintContents(&batch)); - - TestHandler handler; - ASSERT_OK(batch.Iterate(&handler)); - ASSERT_EQ( - "Put(k1, v1)" - "Put(k2, v2)" - "Put(k3, v3)" - "LogData(blob1)" - "Delete(k2)" - "SingleDelete(k3)" - "LogData(blob2)" - "Merge(foo, bar)", - handler.seen); -} - -TEST_F(WriteBatchTest, PrepareCommit) { - WriteBatch batch; - ASSERT_OK(WriteBatchInternal::InsertNoop(&batch)); - ASSERT_OK(batch.Put(Slice("k1"), Slice("v1"))); - ASSERT_OK(batch.Put(Slice("k2"), Slice("v2"))); - batch.SetSavePoint(); - ASSERT_OK(WriteBatchInternal::MarkEndPrepare(&batch, Slice("xid1"))); - Status s = batch.RollbackToSavePoint(); - ASSERT_EQ(s, Status::NotFound()); - ASSERT_OK(WriteBatchInternal::MarkCommit(&batch, Slice("xid1"))); - ASSERT_OK(WriteBatchInternal::MarkRollback(&batch, Slice("xid1"))); - ASSERT_EQ(2u, batch.Count()); - - TestHandler handler; - ASSERT_OK(batch.Iterate(&handler)); - ASSERT_EQ( - "MarkBeginPrepare(false)" - "Put(k1, v1)" - "Put(k2, v2)" - "MarkEndPrepare(xid1)" - "MarkCommit(xid1)" - "MarkRollback(xid1)", - handler.seen); -} - -// It requires more than 30GB of memory to run the test. With single memory -// allocation of more than 30GB. -// Not all platform can run it. Also it runs a long time. So disable it. -TEST_F(WriteBatchTest, DISABLED_ManyUpdates) { - // Insert key and value of 3GB and push total batch size to 12GB. - static const size_t kKeyValueSize = 4u; - static const uint32_t kNumUpdates = uint32_t{3} << 30; - std::string raw(kKeyValueSize, 'A'); - WriteBatch batch(kNumUpdates * (4 + kKeyValueSize * 2) + 1024u); - char c = 'A'; - for (uint32_t i = 0; i < kNumUpdates; i++) { - if (c > 'Z') { - c = 'A'; - } - raw[0] = c; - raw[raw.length() - 1] = c; - c++; - ASSERT_OK(batch.Put(raw, raw)); - } - - ASSERT_EQ(kNumUpdates, batch.Count()); - - struct NoopHandler : public WriteBatch::Handler { - uint32_t num_seen = 0; - char expected_char = 'A'; - Status PutCF(uint32_t /*column_family_id*/, const Slice& key, - const Slice& value) override { - EXPECT_EQ(kKeyValueSize, key.size()); - EXPECT_EQ(kKeyValueSize, value.size()); - EXPECT_EQ(expected_char, key[0]); - EXPECT_EQ(expected_char, value[0]); - EXPECT_EQ(expected_char, key[kKeyValueSize - 1]); - EXPECT_EQ(expected_char, value[kKeyValueSize - 1]); - expected_char++; - if (expected_char > 'Z') { - expected_char = 'A'; - } - ++num_seen; - return Status::OK(); - } - Status DeleteCF(uint32_t /*column_family_id*/, - const Slice& /*key*/) override { - ADD_FAILURE(); - return Status::OK(); - } - Status SingleDeleteCF(uint32_t /*column_family_id*/, - const Slice& /*key*/) override { - ADD_FAILURE(); - return Status::OK(); - } - Status MergeCF(uint32_t /*column_family_id*/, const Slice& /*key*/, - const Slice& /*value*/) override { - ADD_FAILURE(); - return Status::OK(); - } - void LogData(const Slice& /*blob*/) override { ADD_FAILURE(); } - bool Continue() override { return num_seen < kNumUpdates; } - } handler; - - ASSERT_OK(batch.Iterate(&handler)); - ASSERT_EQ(kNumUpdates, handler.num_seen); -} - -// The test requires more than 18GB memory to run it, with single memory -// allocation of more than 12GB. Not all the platform can run it. So disable it. -TEST_F(WriteBatchTest, DISABLED_LargeKeyValue) { - // Insert key and value of 3GB and push total batch size to 12GB. - static const size_t kKeyValueSize = 3221225472u; - std::string raw(kKeyValueSize, 'A'); - WriteBatch batch(size_t(12884901888ull + 1024u)); - for (char i = 0; i < 2; i++) { - raw[0] = 'A' + i; - raw[raw.length() - 1] = 'A' - i; - ASSERT_OK(batch.Put(raw, raw)); - } - - ASSERT_EQ(2u, batch.Count()); - - struct NoopHandler : public WriteBatch::Handler { - int num_seen = 0; - Status PutCF(uint32_t /*column_family_id*/, const Slice& key, - const Slice& value) override { - EXPECT_EQ(kKeyValueSize, key.size()); - EXPECT_EQ(kKeyValueSize, value.size()); - EXPECT_EQ('A' + num_seen, key[0]); - EXPECT_EQ('A' + num_seen, value[0]); - EXPECT_EQ('A' - num_seen, key[kKeyValueSize - 1]); - EXPECT_EQ('A' - num_seen, value[kKeyValueSize - 1]); - ++num_seen; - return Status::OK(); - } - Status DeleteCF(uint32_t /*column_family_id*/, - const Slice& /*key*/) override { - ADD_FAILURE(); - return Status::OK(); - } - Status SingleDeleteCF(uint32_t /*column_family_id*/, - const Slice& /*key*/) override { - ADD_FAILURE(); - return Status::OK(); - } - Status MergeCF(uint32_t /*column_family_id*/, const Slice& /*key*/, - const Slice& /*value*/) override { - ADD_FAILURE(); - return Status::OK(); - } - void LogData(const Slice& /*blob*/) override { ADD_FAILURE(); } - bool Continue() override { return num_seen < 2; } - } handler; - - ASSERT_OK(batch.Iterate(&handler)); - ASSERT_EQ(2, handler.num_seen); -} - -TEST_F(WriteBatchTest, Continue) { - WriteBatch batch; - - struct Handler : public TestHandler { - int num_seen = 0; - Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { - ++num_seen; - return TestHandler::PutCF(column_family_id, key, value); - } - Status DeleteCF(uint32_t column_family_id, const Slice& key) override { - ++num_seen; - return TestHandler::DeleteCF(column_family_id, key); - } - Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key) override { - ++num_seen; - return TestHandler::SingleDeleteCF(column_family_id, key); - } - Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { - ++num_seen; - return TestHandler::MergeCF(column_family_id, key, value); - } - void LogData(const Slice& blob) override { - ++num_seen; - TestHandler::LogData(blob); - } - bool Continue() override { return num_seen < 5; } - } handler; - - ASSERT_OK(batch.Put(Slice("k1"), Slice("v1"))); - ASSERT_OK(batch.Put(Slice("k2"), Slice("v2"))); - ASSERT_OK(batch.PutLogData(Slice("blob1"))); - ASSERT_OK(batch.Delete(Slice("k1"))); - ASSERT_OK(batch.SingleDelete(Slice("k2"))); - ASSERT_OK(batch.PutLogData(Slice("blob2"))); - ASSERT_OK(batch.Merge(Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Iterate(&handler)); - ASSERT_EQ( - "Put(k1, v1)" - "Put(k2, v2)" - "LogData(blob1)" - "Delete(k1)" - "SingleDelete(k2)", - handler.seen); -} - -TEST_F(WriteBatchTest, PutGatherSlices) { - WriteBatch batch; - ASSERT_OK(batch.Put(Slice("foo"), Slice("bar"))); - - { - // Try a write where the key is one slice but the value is two - Slice key_slice("baz"); - Slice value_slices[2] = {Slice("header"), Slice("payload")}; - ASSERT_OK( - batch.Put(SliceParts(&key_slice, 1), SliceParts(value_slices, 2))); - } - - { - // One where the key is composite but the value is a single slice - Slice key_slices[3] = {Slice("key"), Slice("part2"), Slice("part3")}; - Slice value_slice("value"); - ASSERT_OK( - batch.Put(SliceParts(key_slices, 3), SliceParts(&value_slice, 1))); - } - - WriteBatchInternal::SetSequence(&batch, 100); - ASSERT_EQ( - "Put(baz, headerpayload)@101" - "Put(foo, bar)@100" - "Put(keypart2part3, value)@102", - PrintContents(&batch)); - ASSERT_EQ(3u, batch.Count()); -} - -namespace { -class ColumnFamilyHandleImplDummy : public ColumnFamilyHandleImpl { - public: - explicit ColumnFamilyHandleImplDummy(int id) - : ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), id_(id) {} - explicit ColumnFamilyHandleImplDummy(int id, const Comparator* ucmp) - : ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), - id_(id), - ucmp_(ucmp) {} - uint32_t GetID() const override { return id_; } - const Comparator* GetComparator() const override { return ucmp_; } - - private: - uint32_t id_; - const Comparator* const ucmp_ = BytewiseComparator(); -}; -} // anonymous namespace - -TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) { - WriteBatch batch; - ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8); - ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Put(&two, Slice("twofoo"), Slice("bar2"))); - ASSERT_OK(batch.Put(&eight, Slice("eightfoo"), Slice("bar8"))); - ASSERT_OK(batch.Delete(&eight, Slice("eightfoo"))); - ASSERT_OK(batch.SingleDelete(&two, Slice("twofoo"))); - ASSERT_OK(batch.DeleteRange(&two, Slice("3foo"), Slice("4foo"))); - ASSERT_OK(batch.Merge(&three, Slice("threethree"), Slice("3three"))); - ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Merge(Slice("omom"), Slice("nom"))); - - TestHandler handler; - ASSERT_OK(batch.Iterate(&handler)); - ASSERT_EQ( - "Put(foo, bar)" - "PutCF(2, twofoo, bar2)" - "PutCF(8, eightfoo, bar8)" - "DeleteCF(8, eightfoo)" - "SingleDeleteCF(2, twofoo)" - "DeleteRangeCF(2, 3foo, 4foo)" - "MergeCF(3, threethree, 3three)" - "Put(foo, bar)" - "Merge(omom, nom)", - handler.seen); -} - -TEST_F(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) { - WriteBatchWithIndex batch; - ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8); - ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Put(&two, Slice("twofoo"), Slice("bar2"))); - ASSERT_OK(batch.Put(&eight, Slice("eightfoo"), Slice("bar8"))); - ASSERT_OK(batch.Delete(&eight, Slice("eightfoo"))); - ASSERT_OK(batch.SingleDelete(&two, Slice("twofoo"))); - ASSERT_OK(batch.Merge(&three, Slice("threethree"), Slice("3three"))); - ASSERT_OK(batch.Put(&zero, Slice("foo"), Slice("bar"))); - ASSERT_OK(batch.Merge(Slice("omom"), Slice("nom"))); - - std::unique_ptr iter; - - iter.reset(batch.NewIterator(&eight)); - iter->Seek("eightfoo"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type); - ASSERT_EQ("eightfoo", iter->Entry().key.ToString()); - ASSERT_EQ("bar8", iter->Entry().value.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kDeleteRecord, iter->Entry().type); - ASSERT_EQ("eightfoo", iter->Entry().key.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - - iter.reset(batch.NewIterator(&two)); - iter->Seek("twofoo"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type); - ASSERT_EQ("twofoo", iter->Entry().key.ToString()); - ASSERT_EQ("bar2", iter->Entry().value.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kSingleDeleteRecord, iter->Entry().type); - ASSERT_EQ("twofoo", iter->Entry().key.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - - iter.reset(batch.NewIterator()); - iter->Seek("gggg"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kMergeRecord, iter->Entry().type); - ASSERT_EQ("omom", iter->Entry().key.ToString()); - ASSERT_EQ("nom", iter->Entry().value.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - - iter.reset(batch.NewIterator(&zero)); - iter->Seek("foo"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type); - ASSERT_EQ("foo", iter->Entry().key.ToString()); - ASSERT_EQ("bar", iter->Entry().value.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type); - ASSERT_EQ("foo", iter->Entry().key.ToString()); - ASSERT_EQ("bar", iter->Entry().value.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(WriteType::kMergeRecord, iter->Entry().type); - ASSERT_EQ("omom", iter->Entry().key.ToString()); - ASSERT_EQ("nom", iter->Entry().value.ToString()); - - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - - TestHandler handler; - ASSERT_OK(batch.GetWriteBatch()->Iterate(&handler)); - ASSERT_EQ( - "Put(foo, bar)" - "PutCF(2, twofoo, bar2)" - "PutCF(8, eightfoo, bar8)" - "DeleteCF(8, eightfoo)" - "SingleDeleteCF(2, twofoo)" - "MergeCF(3, threethree, 3three)" - "Put(foo, bar)" - "Merge(omom, nom)", - handler.seen); -} - -TEST_F(WriteBatchTest, SavePointTest) { - Status s; - WriteBatch batch; - batch.SetSavePoint(); - - ASSERT_OK(batch.Put("A", "a")); - ASSERT_OK(batch.Put("B", "b")); - batch.SetSavePoint(); - - ASSERT_OK(batch.Put("C", "c")); - ASSERT_OK(batch.Delete("A")); - batch.SetSavePoint(); - batch.SetSavePoint(); - - ASSERT_OK(batch.RollbackToSavePoint()); - ASSERT_EQ( - "Delete(A)@3" - "Put(A, a)@0" - "Put(B, b)@1" - "Put(C, c)@2", - PrintContents(&batch)); - - ASSERT_OK(batch.RollbackToSavePoint()); - ASSERT_OK(batch.RollbackToSavePoint()); - ASSERT_EQ( - "Put(A, a)@0" - "Put(B, b)@1", - PrintContents(&batch)); - - ASSERT_OK(batch.Delete("A")); - ASSERT_OK(batch.Put("B", "bb")); - - ASSERT_OK(batch.RollbackToSavePoint()); - ASSERT_EQ("", PrintContents(&batch)); - - s = batch.RollbackToSavePoint(); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ("", PrintContents(&batch)); - - ASSERT_OK(batch.Put("D", "d")); - ASSERT_OK(batch.Delete("A")); - - batch.SetSavePoint(); - - ASSERT_OK(batch.Put("A", "aaa")); - - ASSERT_OK(batch.RollbackToSavePoint()); - ASSERT_EQ( - "Delete(A)@1" - "Put(D, d)@0", - PrintContents(&batch)); - - batch.SetSavePoint(); - - ASSERT_OK(batch.Put("D", "d")); - ASSERT_OK(batch.Delete("A")); - - ASSERT_OK(batch.RollbackToSavePoint()); - ASSERT_EQ( - "Delete(A)@1" - "Put(D, d)@0", - PrintContents(&batch)); - - s = batch.RollbackToSavePoint(); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ( - "Delete(A)@1" - "Put(D, d)@0", - PrintContents(&batch)); - - WriteBatch batch2; - - s = batch2.RollbackToSavePoint(); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ("", PrintContents(&batch2)); - - ASSERT_OK(batch2.Delete("A")); - batch2.SetSavePoint(); - - s = batch2.RollbackToSavePoint(); - ASSERT_OK(s); - ASSERT_EQ("Delete(A)@0", PrintContents(&batch2)); - - batch2.Clear(); - ASSERT_EQ("", PrintContents(&batch2)); - - batch2.SetSavePoint(); - - ASSERT_OK(batch2.Delete("B")); - ASSERT_EQ("Delete(B)@0", PrintContents(&batch2)); - - batch2.SetSavePoint(); - s = batch2.RollbackToSavePoint(); - ASSERT_OK(s); - ASSERT_EQ("Delete(B)@0", PrintContents(&batch2)); - - s = batch2.RollbackToSavePoint(); - ASSERT_OK(s); - ASSERT_EQ("", PrintContents(&batch2)); - - s = batch2.RollbackToSavePoint(); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ("", PrintContents(&batch2)); - - WriteBatch batch3; - - s = batch3.PopSavePoint(); - ASSERT_TRUE(s.IsNotFound()); - ASSERT_EQ("", PrintContents(&batch3)); - - batch3.SetSavePoint(); - ASSERT_OK(batch3.Delete("A")); - - s = batch3.PopSavePoint(); - ASSERT_OK(s); - ASSERT_EQ("Delete(A)@0", PrintContents(&batch3)); -} - -TEST_F(WriteBatchTest, MemoryLimitTest) { - Status s; - // The header size is 12 bytes. The two Puts take 8 bytes which gives total - // of 12 + 8 * 2 = 28 bytes. - WriteBatch batch(0, 28); - - ASSERT_OK(batch.Put("a", "....")); - ASSERT_OK(batch.Put("b", "....")); - s = batch.Put("c", "...."); - ASSERT_TRUE(s.IsMemoryLimit()); -} - -namespace { -class TimestampChecker : public WriteBatch::Handler { - public: - explicit TimestampChecker( - std::unordered_map cf_to_ucmps, Slice ts) - : cf_to_ucmps_(std::move(cf_to_ucmps)), timestamp_(std::move(ts)) {} - Status PutCF(uint32_t cf, const Slice& key, const Slice& /*value*/) override { - auto cf_iter = cf_to_ucmps_.find(cf); - if (cf_iter == cf_to_ucmps_.end()) { - return Status::Corruption(); - } - const Comparator* const ucmp = cf_iter->second; - assert(ucmp); - size_t ts_sz = ucmp->timestamp_size(); - if (ts_sz == 0) { - return Status::OK(); - } - if (key.size() < ts_sz) { - return Status::Corruption(); - } - Slice ts = ExtractTimestampFromUserKey(key, ts_sz); - if (ts.compare(timestamp_) != 0) { - return Status::Corruption(); - } - return Status::OK(); - } - - private: - std::unordered_map cf_to_ucmps_; - Slice timestamp_; -}; - -Status CheckTimestampsInWriteBatch( - WriteBatch& wb, Slice timestamp, - std::unordered_map cf_to_ucmps) { - TimestampChecker ts_checker(cf_to_ucmps, timestamp); - return wb.Iterate(&ts_checker); -} -} // anonymous namespace - -TEST_F(WriteBatchTest, SanityChecks) { - ColumnFamilyHandleImplDummy cf0(0, - test::BytewiseComparatorWithU64TsWrapper()); - ColumnFamilyHandleImplDummy cf4(4); - - WriteBatch wb(0, 0, 0, /*default_cf_ts_sz=*/sizeof(uint64_t)); - - // Sanity checks for the new WriteBatch APIs with extra 'ts' arg. - ASSERT_TRUE(wb.Put(nullptr, "key", "ts", "value").IsInvalidArgument()); - ASSERT_TRUE(wb.Delete(nullptr, "key", "ts").IsInvalidArgument()); - ASSERT_TRUE(wb.SingleDelete(nullptr, "key", "ts").IsInvalidArgument()); - ASSERT_TRUE(wb.Merge(nullptr, "key", "ts", "value").IsInvalidArgument()); - ASSERT_TRUE(wb.DeleteRange(nullptr, "begin_key", "end_key", "ts") - .IsInvalidArgument()); - - ASSERT_TRUE(wb.Put(&cf4, "key", "ts", "value").IsInvalidArgument()); - ASSERT_TRUE(wb.Delete(&cf4, "key", "ts").IsInvalidArgument()); - ASSERT_TRUE(wb.SingleDelete(&cf4, "key", "ts").IsInvalidArgument()); - ASSERT_TRUE(wb.Merge(&cf4, "key", "ts", "value").IsInvalidArgument()); - ASSERT_TRUE( - wb.DeleteRange(&cf4, "begin_key", "end_key", "ts").IsInvalidArgument()); - - constexpr size_t wrong_ts_sz = 1 + sizeof(uint64_t); - std::string ts(wrong_ts_sz, '\0'); - - ASSERT_TRUE(wb.Put(&cf0, "key", ts, "value").IsInvalidArgument()); - ASSERT_TRUE(wb.Delete(&cf0, "key", ts).IsInvalidArgument()); - ASSERT_TRUE(wb.SingleDelete(&cf0, "key", ts).IsInvalidArgument()); - ASSERT_TRUE(wb.Merge(&cf0, "key", ts, "value").IsInvalidArgument()); - ASSERT_TRUE( - wb.DeleteRange(&cf0, "begin_key", "end_key", ts).IsInvalidArgument()); - - // Sanity checks for the new WriteBatch APIs without extra 'ts' arg. - WriteBatch wb1(0, 0, 0, wrong_ts_sz); - ASSERT_TRUE(wb1.Put(&cf0, "key", "value").IsInvalidArgument()); - ASSERT_TRUE(wb1.Delete(&cf0, "key").IsInvalidArgument()); - ASSERT_TRUE(wb1.SingleDelete(&cf0, "key").IsInvalidArgument()); - ASSERT_TRUE(wb1.Merge(&cf0, "key", "value").IsInvalidArgument()); - ASSERT_TRUE( - wb1.DeleteRange(&cf0, "begin_key", "end_key").IsInvalidArgument()); -} - -TEST_F(WriteBatchTest, UpdateTimestamps) { - // We assume the last eight bytes of each key is reserved for timestamps. - // Therefore, we must make sure each key is longer than eight bytes. - constexpr size_t key_size = 16; - constexpr size_t num_of_keys = 10; - std::vector key_strs(num_of_keys, std::string(key_size, '\0')); - - ColumnFamilyHandleImplDummy cf0(0); - ColumnFamilyHandleImplDummy cf4(4, - test::BytewiseComparatorWithU64TsWrapper()); - ColumnFamilyHandleImplDummy cf5(5, - test::BytewiseComparatorWithU64TsWrapper()); - - const std::unordered_map cf_to_ucmps = { - {0, cf0.GetComparator()}, - {4, cf4.GetComparator()}, - {5, cf5.GetComparator()}}; - - static constexpr size_t timestamp_size = sizeof(uint64_t); - - { - WriteBatch wb1, wb2, wb3, wb4, wb5, wb6, wb7; - ASSERT_OK(wb1.Put(&cf0, "key", "value")); - ASSERT_FALSE(WriteBatchInternal::HasKeyWithTimestamp(wb1)); - ASSERT_OK(wb2.Put(&cf4, "key", "value")); - ASSERT_TRUE(WriteBatchInternal::HasKeyWithTimestamp(wb2)); - ASSERT_OK(wb3.Put(&cf4, "key", /*ts=*/std::string(timestamp_size, '\xfe'), - "value")); - ASSERT_TRUE(WriteBatchInternal::HasKeyWithTimestamp(wb3)); - ASSERT_OK(wb4.Delete(&cf4, "key", - /*ts=*/std::string(timestamp_size, '\xfe'))); - ASSERT_TRUE(WriteBatchInternal::HasKeyWithTimestamp(wb4)); - ASSERT_OK(wb5.Delete(&cf4, "key")); - ASSERT_TRUE(WriteBatchInternal::HasKeyWithTimestamp(wb5)); - ASSERT_OK(wb6.SingleDelete(&cf4, "key")); - ASSERT_TRUE(WriteBatchInternal::HasKeyWithTimestamp(wb6)); - ASSERT_OK(wb7.SingleDelete(&cf4, "key", - /*ts=*/std::string(timestamp_size, '\xfe'))); - ASSERT_TRUE(WriteBatchInternal::HasKeyWithTimestamp(wb7)); - } - - WriteBatch batch; - // Write to the batch. We will assign timestamps later. - for (const auto& key_str : key_strs) { - ASSERT_OK(batch.Put(&cf0, key_str, "value")); - ASSERT_OK(batch.Put(&cf4, key_str, "value")); - ASSERT_OK(batch.Put(&cf5, key_str, "value")); - } - - const auto checker1 = [](uint32_t cf) { - if (cf == 4 || cf == 5) { - return timestamp_size; - } else if (cf == 0) { - return static_cast(0); - } else { - return std::numeric_limits::max(); - } - }; - ASSERT_OK( - batch.UpdateTimestamps(std::string(timestamp_size, '\xfe'), checker1)); - ASSERT_OK(CheckTimestampsInWriteBatch( - batch, std::string(timestamp_size, '\xfe'), cf_to_ucmps)); - - // We use indexed_cf_to_ucmps, non_indexed_cfs_with_ts and timestamp_size to - // simulate the case in which a transaction enables indexing for some writes - // while disables indexing for other writes. A transaction uses a - // WriteBatchWithIndex object to buffer writes (we consider Write-committed - // policy only). If indexing is enabled, then writes go through - // WriteBatchWithIndex API populating a WBWI internal data structure, i.e. a - // mapping from cf to user comparators. If indexing is disabled, a transaction - // writes directly to the underlying raw WriteBatch. We will need to track the - // comparator information for the column families to which un-indexed writes - // are performed. When calling UpdateTimestamp API of WriteBatch, we need - // indexed_cf_to_ucmps, non_indexed_cfs_with_ts, and timestamp_size to perform - // checking. - std::unordered_map indexed_cf_to_ucmps = { - {0, cf0.GetComparator()}, {4, cf4.GetComparator()}}; - std::unordered_set non_indexed_cfs_with_ts = {cf5.GetID()}; - const auto checker2 = [&indexed_cf_to_ucmps, - &non_indexed_cfs_with_ts](uint32_t cf) { - if (non_indexed_cfs_with_ts.count(cf) > 0) { - return timestamp_size; - } - auto cf_iter = indexed_cf_to_ucmps.find(cf); - if (cf_iter == indexed_cf_to_ucmps.end()) { - assert(false); - return std::numeric_limits::max(); - } - const Comparator* const ucmp = cf_iter->second; - assert(ucmp); - return ucmp->timestamp_size(); - }; - ASSERT_OK( - batch.UpdateTimestamps(std::string(timestamp_size, '\xef'), checker2)); - ASSERT_OK(CheckTimestampsInWriteBatch( - batch, std::string(timestamp_size, '\xef'), cf_to_ucmps)); -} - -TEST_F(WriteBatchTest, CommitWithTimestamp) { - WriteBatch wb; - const std::string txn_name = "xid1"; - std::string ts; - constexpr uint64_t commit_ts = 23; - PutFixed64(&ts, commit_ts); - ASSERT_OK(WriteBatchInternal::MarkCommitWithTimestamp(&wb, txn_name, ts)); - TestHandler handler; - ASSERT_OK(wb.Iterate(&handler)); - ASSERT_EQ("MarkCommitWithTimestamp(" + txn_name + ", " + - Slice(ts).ToString(true) + ")", - handler.seen); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db/write_callback_test.cc b/db/write_callback_test.cc deleted file mode 100644 index 1be8593f1..000000000 --- a/db/write_callback_test.cc +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "db/write_callback.h" - -#include -#include -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "port/port.h" -#include "rocksdb/db.h" -#include "rocksdb/write_batch.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "util/random.h" - -using std::string; - -namespace ROCKSDB_NAMESPACE { - -class WriteCallbackTest : public testing::Test { - public: - string dbname; - - WriteCallbackTest() { - dbname = test::PerThreadDBPath("write_callback_testdb"); - } -}; - -class WriteCallbackTestWriteCallback1 : public WriteCallback { - public: - bool was_called = false; - - Status Callback(DB* db) override { - was_called = true; - - // Make sure db is a DBImpl - DBImpl* db_impl = dynamic_cast(db); - if (db_impl == nullptr) { - return Status::InvalidArgument(""); - } - - return Status::OK(); - } - - bool AllowWriteBatching() override { return true; } -}; - -class WriteCallbackTestWriteCallback2 : public WriteCallback { - public: - Status Callback(DB* /*db*/) override { return Status::Busy(); } - bool AllowWriteBatching() override { return true; } -}; - -class MockWriteCallback : public WriteCallback { - public: - bool should_fail_ = false; - bool allow_batching_ = false; - std::atomic was_called_{false}; - - MockWriteCallback() {} - - MockWriteCallback(const MockWriteCallback& other) { - should_fail_ = other.should_fail_; - allow_batching_ = other.allow_batching_; - was_called_.store(other.was_called_.load()); - } - - Status Callback(DB* /*db*/) override { - was_called_.store(true); - if (should_fail_) { - return Status::Busy(); - } else { - return Status::OK(); - } - } - - bool AllowWriteBatching() override { return allow_batching_; } -}; - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -class WriteCallbackPTest - : public WriteCallbackTest, - public ::testing::WithParamInterface< - std::tuple> { - public: - WriteCallbackPTest() { - std::tie(unordered_write_, seq_per_batch_, two_queues_, allow_parallel_, - allow_batching_, enable_WAL_, enable_pipelined_write_) = - GetParam(); - } - - protected: - bool unordered_write_; - bool seq_per_batch_; - bool two_queues_; - bool allow_parallel_; - bool allow_batching_; - bool enable_WAL_; - bool enable_pipelined_write_; -}; - -TEST_P(WriteCallbackPTest, WriteWithCallbackTest) { - struct WriteOP { - WriteOP(bool should_fail = false) { callback_.should_fail_ = should_fail; } - - void Put(const string& key, const string& val) { - kvs_.push_back(std::make_pair(key, val)); - ASSERT_OK(write_batch_.Put(key, val)); - } - - void Clear() { - kvs_.clear(); - write_batch_.Clear(); - callback_.was_called_.store(false); - } - - MockWriteCallback callback_; - WriteBatch write_batch_; - std::vector> kvs_; - }; - - // In each scenario we'll launch multiple threads to write. - // The size of each array equals to number of threads, and - // each boolean in it denote whether callback of corresponding - // thread should succeed or fail. - std::vector> write_scenarios = { - {true}, - {false}, - {false, false}, - {true, true}, - {true, false}, - {false, true}, - {false, false, false}, - {true, true, true}, - {false, true, false}, - {true, false, true}, - {true, false, false, false, false}, - {false, false, false, false, true}, - {false, false, true, false, true}, - }; - - for (auto& write_group : write_scenarios) { - Options options; - options.create_if_missing = true; - options.unordered_write = unordered_write_; - options.allow_concurrent_memtable_write = allow_parallel_; - options.enable_pipelined_write = enable_pipelined_write_; - options.two_write_queues = two_queues_; - // Skip unsupported combinations - if (options.enable_pipelined_write && seq_per_batch_) { - continue; - } - if (options.enable_pipelined_write && options.two_write_queues) { - continue; - } - if (options.unordered_write && !options.allow_concurrent_memtable_write) { - continue; - } - if (options.unordered_write && options.enable_pipelined_write) { - continue; - } - - ReadOptions read_options; - DB* db; - DBImpl* db_impl; - - ASSERT_OK(DestroyDB(dbname, options)); - - DBOptions db_options(options); - ColumnFamilyOptions cf_options(options); - std::vector column_families; - column_families.push_back( - ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options)); - std::vector handles; - auto open_s = DBImpl::Open(db_options, dbname, column_families, &handles, - &db, seq_per_batch_, true /* batch_per_txn */); - ASSERT_OK(open_s); - assert(handles.size() == 1); - delete handles[0]; - - db_impl = dynamic_cast(db); - ASSERT_TRUE(db_impl); - - // Writers that have called JoinBatchGroup. - std::atomic threads_joining(0); - // Writers that have linked to the queue - std::atomic threads_linked(0); - // Writers that pass WriteThread::JoinBatchGroup:Wait sync-point. - std::atomic threads_verified(0); - - std::atomic seq(db_impl->GetLatestSequenceNumber()); - ASSERT_EQ(db_impl->GetLatestSequenceNumber(), 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Start", [&](void*) { - uint64_t cur_threads_joining = threads_joining.fetch_add(1); - // Wait for the last joined writer to link to the queue. - // In this way the writers link to the queue one by one. - // This allows us to confidently detect the first writer - // who increases threads_linked as the leader. - while (threads_linked.load() < cur_threads_joining) { - } - }); - - // Verification once writers call JoinBatchGroup. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:Wait", [&](void* arg) { - uint64_t cur_threads_linked = threads_linked.fetch_add(1); - bool is_leader = false; - bool is_last = false; - - // who am i - is_leader = (cur_threads_linked == 0); - is_last = (cur_threads_linked == write_group.size() - 1); - - // check my state - auto* writer = reinterpret_cast(arg); - - if (is_leader) { - ASSERT_TRUE(writer->state == - WriteThread::State::STATE_GROUP_LEADER); - } else { - ASSERT_TRUE(writer->state == WriteThread::State::STATE_INIT); - } - - // (meta test) the first WriteOP should indeed be the first - // and the last should be the last (all others can be out of - // order) - if (is_leader) { - ASSERT_TRUE(writer->callback->Callback(nullptr).ok() == - !write_group.front().callback_.should_fail_); - } else if (is_last) { - ASSERT_TRUE(writer->callback->Callback(nullptr).ok() == - !write_group.back().callback_.should_fail_); - } - - threads_verified.fetch_add(1); - // Wait here until all verification in this sync-point - // callback finish for all writers. - while (threads_verified.load() < write_group.size()) { - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::JoinBatchGroup:DoneWaiting", [&](void* arg) { - // check my state - auto* writer = reinterpret_cast(arg); - - if (!allow_batching_) { - // no batching so everyone should be a leader - ASSERT_TRUE(writer->state == - WriteThread::State::STATE_GROUP_LEADER); - } else if (!allow_parallel_) { - ASSERT_TRUE(writer->state == WriteThread::State::STATE_COMPLETED || - (enable_pipelined_write_ && - writer->state == - WriteThread::State::STATE_MEMTABLE_WRITER_LEADER)); - } - }); - - std::atomic thread_num(0); - std::atomic dummy_key(0); - - // Each write thread create a random write batch and write to DB - // with a write callback. - std::function write_with_callback_func = [&]() { - uint32_t i = thread_num.fetch_add(1); - Random rnd(i); - - // leaders gotta lead - while (i > 0 && threads_verified.load() < 1) { - } - - // loser has to lose - while (i == write_group.size() - 1 && - threads_verified.load() < write_group.size() - 1) { - } - - auto& write_op = write_group.at(i); - write_op.Clear(); - write_op.callback_.allow_batching_ = allow_batching_; - - // insert some keys - for (uint32_t j = 0; j < rnd.Next() % 50; j++) { - // grab unique key - char my_key = dummy_key.fetch_add(1); - - string skey(5, my_key); - string sval(10, my_key); - write_op.Put(skey, sval); - - if (!write_op.callback_.should_fail_ && !seq_per_batch_) { - seq.fetch_add(1); - } - } - if (!write_op.callback_.should_fail_ && seq_per_batch_) { - seq.fetch_add(1); - } - - WriteOptions woptions; - woptions.disableWAL = !enable_WAL_; - woptions.sync = enable_WAL_; - if (woptions.protection_bytes_per_key > 0) { - ASSERT_OK(WriteBatchInternal::UpdateProtectionInfo( - &write_op.write_batch_, woptions.protection_bytes_per_key)); - } - Status s; - if (seq_per_batch_) { - class PublishSeqCallback : public PreReleaseCallback { - public: - PublishSeqCallback(DBImpl* db_impl_in) : db_impl_(db_impl_in) {} - Status Callback(SequenceNumber last_seq, bool /*not used*/, uint64_t, - size_t /*index*/, size_t /*total*/) override { - db_impl_->SetLastPublishedSequence(last_seq); - return Status::OK(); - } - DBImpl* db_impl_; - } publish_seq_callback(db_impl); - // seq_per_batch_ requires a natural batch separator or Noop - ASSERT_OK(WriteBatchInternal::InsertNoop(&write_op.write_batch_)); - const size_t ONE_BATCH = 1; - s = db_impl->WriteImpl(woptions, &write_op.write_batch_, - &write_op.callback_, nullptr, 0, false, nullptr, - ONE_BATCH, - two_queues_ ? &publish_seq_callback : nullptr); - } else { - s = db_impl->WriteWithCallback(woptions, &write_op.write_batch_, - &write_op.callback_); - } - - if (write_op.callback_.should_fail_) { - ASSERT_TRUE(s.IsBusy()); - } else { - ASSERT_OK(s); - } - }; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // do all the writes - std::vector threads; - for (uint32_t i = 0; i < write_group.size(); i++) { - threads.emplace_back(write_with_callback_func); - } - for (auto& t : threads) { - t.join(); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - - // check for keys - string value; - for (auto& w : write_group) { - ASSERT_TRUE(w.callback_.was_called_.load()); - for (auto& kvp : w.kvs_) { - if (w.callback_.should_fail_) { - ASSERT_TRUE(db->Get(read_options, kvp.first, &value).IsNotFound()); - } else { - ASSERT_OK(db->Get(read_options, kvp.first, &value)); - ASSERT_EQ(value, kvp.second); - } - } - } - - ASSERT_EQ(seq.load(), db_impl->TEST_GetLastVisibleSequence()); - - delete db; - ASSERT_OK(DestroyDB(dbname, options)); - } -} - -INSTANTIATE_TEST_CASE_P(WriteCallbackPTest, WriteCallbackPTest, - ::testing::Combine(::testing::Bool(), ::testing::Bool(), - ::testing::Bool(), ::testing::Bool(), - ::testing::Bool(), ::testing::Bool(), - ::testing::Bool())); -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -TEST_F(WriteCallbackTest, WriteCallBackTest) { - Options options; - WriteOptions write_options; - ReadOptions read_options; - string value; - DB* db; - DBImpl* db_impl; - - ASSERT_OK(DestroyDB(dbname, options)); - - options.create_if_missing = true; - Status s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - - db_impl = dynamic_cast(db); - ASSERT_TRUE(db_impl); - - WriteBatch wb; - - ASSERT_OK(wb.Put("a", "value.a")); - ASSERT_OK(wb.Delete("x")); - - // Test a simple Write - s = db->Write(write_options, &wb); - ASSERT_OK(s); - - s = db->Get(read_options, "a", &value); - ASSERT_OK(s); - ASSERT_EQ("value.a", value); - - // Test WriteWithCallback - WriteCallbackTestWriteCallback1 callback1; - WriteBatch wb2; - - ASSERT_OK(wb2.Put("a", "value.a2")); - - s = db_impl->WriteWithCallback(write_options, &wb2, &callback1); - ASSERT_OK(s); - ASSERT_TRUE(callback1.was_called); - - s = db->Get(read_options, "a", &value); - ASSERT_OK(s); - ASSERT_EQ("value.a2", value); - - // Test WriteWithCallback for a callback that fails - WriteCallbackTestWriteCallback2 callback2; - WriteBatch wb3; - - ASSERT_OK(wb3.Put("a", "value.a3")); - - s = db_impl->WriteWithCallback(write_options, &wb3, &callback2); - ASSERT_NOK(s); - - s = db->Get(read_options, "a", &value); - ASSERT_OK(s); - ASSERT_EQ("value.a2", value); - - delete db; - ASSERT_OK(DestroyDB(dbname, options)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/db/write_controller_test.cc b/db/write_controller_test.cc deleted file mode 100644 index b6321a3bc..000000000 --- a/db/write_controller_test.cc +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include "db/write_controller.h" - -#include -#include - -#include "rocksdb/system_clock.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { -namespace { -class TimeSetClock : public SystemClockWrapper { - public: - explicit TimeSetClock() : SystemClockWrapper(nullptr) {} - const char* Name() const override { return "TimeSetClock"; } - uint64_t now_micros_ = 6666; - uint64_t NowNanos() override { return now_micros_ * std::milli::den; } -}; -} // anonymous namespace -class WriteControllerTest : public testing::Test { - public: - WriteControllerTest() { clock_ = std::make_shared(); } - std::shared_ptr clock_; -}; - -// Make tests easier to read -#define MILLION *1000000u -#define MB MILLION -#define MBPS MILLION -#define SECS MILLION // in microseconds - -TEST_F(WriteControllerTest, BasicAPI) { - WriteController controller(40 MBPS); // also set max delayed rate - EXPECT_EQ(controller.delayed_write_rate(), 40 MBPS); - EXPECT_FALSE(controller.IsStopped()); - EXPECT_FALSE(controller.NeedsDelay()); - EXPECT_EQ(0, controller.GetDelay(clock_.get(), 100 MB)); - - // set, get - controller.set_delayed_write_rate(20 MBPS); - EXPECT_EQ(controller.delayed_write_rate(), 20 MBPS); - EXPECT_FALSE(controller.IsStopped()); - EXPECT_FALSE(controller.NeedsDelay()); - EXPECT_EQ(0, controller.GetDelay(clock_.get(), 100 MB)); - - { - // set with token, get - auto delay_token_0 = controller.GetDelayToken(10 MBPS); - EXPECT_EQ(controller.delayed_write_rate(), 10 MBPS); - EXPECT_FALSE(controller.IsStopped()); - EXPECT_TRUE(controller.NeedsDelay()); - // test with delay - EXPECT_EQ(2 SECS, controller.GetDelay(clock_.get(), 20 MB)); - clock_->now_micros_ += 2 SECS; // pay the "debt" - - auto delay_token_1 = controller.GetDelayToken(2 MBPS); - EXPECT_EQ(10 SECS, controller.GetDelay(clock_.get(), 20 MB)); - clock_->now_micros_ += 10 SECS; // pay the "debt" - - auto delay_token_2 = controller.GetDelayToken(1 MBPS); - EXPECT_EQ(20 SECS, controller.GetDelay(clock_.get(), 20 MB)); - clock_->now_micros_ += 20 SECS; // pay the "debt" - - auto delay_token_3 = controller.GetDelayToken(20 MBPS); - EXPECT_EQ(1 SECS, controller.GetDelay(clock_.get(), 20 MB)); - clock_->now_micros_ += 1 SECS; // pay the "debt" - - // 60M is more than the max rate of 40M. Max rate will be used. - EXPECT_EQ(controller.delayed_write_rate(), 20 MBPS); - auto delay_token_4 = - controller.GetDelayToken(controller.delayed_write_rate() * 3); - EXPECT_EQ(controller.delayed_write_rate(), 40 MBPS); - EXPECT_EQ(static_cast(0.5 SECS), - controller.GetDelay(clock_.get(), 20 MB)); - - EXPECT_FALSE(controller.IsStopped()); - EXPECT_TRUE(controller.NeedsDelay()); - - // Test stop tokens - { - auto stop_token_1 = controller.GetStopToken(); - EXPECT_TRUE(controller.IsStopped()); - EXPECT_EQ(0, controller.GetDelay(clock_.get(), 100 MB)); - { - auto stop_token_2 = controller.GetStopToken(); - EXPECT_TRUE(controller.IsStopped()); - EXPECT_EQ(0, controller.GetDelay(clock_.get(), 100 MB)); - } - EXPECT_TRUE(controller.IsStopped()); - EXPECT_EQ(0, controller.GetDelay(clock_.get(), 100 MB)); - } - // Stop tokens released - EXPECT_FALSE(controller.IsStopped()); - EXPECT_TRUE(controller.NeedsDelay()); - EXPECT_EQ(controller.delayed_write_rate(), 40 MBPS); - // pay the previous "debt" - clock_->now_micros_ += static_cast(0.5 SECS); - EXPECT_EQ(1 SECS, controller.GetDelay(clock_.get(), 40 MB)); - } - - // Delay tokens released - EXPECT_FALSE(controller.NeedsDelay()); -} - -TEST_F(WriteControllerTest, StartFilled) { - WriteController controller(10 MBPS); - - // Attempt to write two things that combined would be allowed within - // a single refill interval - auto delay_token_0 = - controller.GetDelayToken(controller.delayed_write_rate()); - - // Verify no delay because write rate has not been exceeded within - // refill interval. - EXPECT_EQ(0U, controller.GetDelay(clock_.get(), 2000u /*bytes*/)); - EXPECT_EQ(0U, controller.GetDelay(clock_.get(), 2000u /*bytes*/)); - - // Allow refill (kMicrosPerRefill) - clock_->now_micros_ += 1000; - - // Again - EXPECT_EQ(0U, controller.GetDelay(clock_.get(), 2000u /*bytes*/)); - EXPECT_EQ(0U, controller.GetDelay(clock_.get(), 2000u /*bytes*/)); - - // Control: something bigger that would exceed write rate within interval - uint64_t delay = controller.GetDelay(clock_.get(), 10 MB); - EXPECT_GT(1.0 * delay, 0.999 SECS); - EXPECT_LT(1.0 * delay, 1.001 SECS); -} - -TEST_F(WriteControllerTest, DebtAccumulation) { - WriteController controller(10 MBPS); - - std::array, 10> tokens; - - // Accumulate a time delay debt with no passage of time, like many column - // families delaying writes simultaneously. (Old versions of WriteController - // would reset the debt on every GetDelayToken.) - uint64_t debt = 0; - for (unsigned i = 0; i < tokens.size(); ++i) { - tokens[i] = controller.GetDelayToken((i + 1u) MBPS); - uint64_t delay = controller.GetDelay(clock_.get(), 63 MB); - ASSERT_GT(delay, debt); - uint64_t incremental = delay - debt; - ASSERT_EQ(incremental, (63 SECS) / (i + 1u)); - debt += incremental; - } - - // Pay down the debt - clock_->now_micros_ += debt; - debt = 0; - - // Now accumulate debt with some passage of time. - for (unsigned i = 0; i < tokens.size(); ++i) { - // Debt is accumulated in time, not in bytes, so this new write - // limit is not applied to prior requested delays, even it they are - // in progress. - tokens[i] = controller.GetDelayToken((i + 1u) MBPS); - uint64_t delay = controller.GetDelay(clock_.get(), 63 MB); - ASSERT_GT(delay, debt); - uint64_t incremental = delay - debt; - ASSERT_EQ(incremental, (63 SECS) / (i + 1u)); - debt += incremental; - uint64_t credit = debt / 2; - clock_->now_micros_ += credit; - debt -= credit; - } - - // Pay down the debt - clock_->now_micros_ += debt; - debt = 0; // consistent state - (void)debt; // appease clang-analyze - - // Verify paid down - EXPECT_EQ(0U, controller.GetDelay(clock_.get(), 100u /*small bytes*/)); - - // Accumulate another debt, without accounting, and releasing tokens - for (unsigned i = 0; i < tokens.size(); ++i) { - // Big and small are delayed - ASSERT_LT(0U, controller.GetDelay(clock_.get(), 63 MB)); - ASSERT_LT(0U, controller.GetDelay(clock_.get(), 100u /*small bytes*/)); - tokens[i].reset(); - } - // All tokens released. - // Verify that releasing all tokens pays down debt, even with no time passage. - tokens[0] = controller.GetDelayToken(1 MBPS); - ASSERT_EQ(0U, controller.GetDelay(clock_.get(), 100u /*small bytes*/)); -} - -// This may or may not be a "good" feature, but it's an old feature -TEST_F(WriteControllerTest, CreditAccumulation) { - WriteController controller(10 MBPS); - - std::array, 10> tokens; - - // Ensure started - tokens[0] = controller.GetDelayToken(1 MBPS); - ASSERT_EQ(10 SECS, controller.GetDelay(clock_.get(), 10 MB)); - clock_->now_micros_ += 10 SECS; - - // Accumulate a credit - uint64_t credit = 1000 SECS /* see below: * 1 MB / 1 SEC */; - clock_->now_micros_ += credit; - - // Spend some credit (burst of I/O) - for (unsigned i = 0; i < tokens.size(); ++i) { - tokens[i] = controller.GetDelayToken((i + 1u) MBPS); - ASSERT_EQ(0U, controller.GetDelay(clock_.get(), 63 MB)); - // In WriteController, credit is accumulated in bytes, not in time. - // After an "unnecessary" delay, all of our time credit will be - // translated to bytes on the next operation, in this case with - // setting 1 MBPS. So regardless of the rate at delay time, we just - // account for the bytes. - credit -= 63 MB; - } - // Spend remaining credit - tokens[0] = controller.GetDelayToken(1 MBPS); - ASSERT_EQ(0U, controller.GetDelay(clock_.get(), credit)); - // Verify - ASSERT_EQ(10 SECS, controller.GetDelay(clock_.get(), 10 MB)); - clock_->now_micros_ += 10 SECS; - - // Accumulate a credit, no accounting - clock_->now_micros_ += 1000 SECS; - - // Spend a small amount, releasing tokens - for (unsigned i = 0; i < tokens.size(); ++i) { - ASSERT_EQ(0U, controller.GetDelay(clock_.get(), 3 MB)); - tokens[i].reset(); - } - - // All tokens released. - // Verify credit is wiped away on new delay. - tokens[0] = controller.GetDelayToken(1 MBPS); - ASSERT_EQ(10 SECS, controller.GetDelay(clock_.get(), 10 MB)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/db_stress_tool/CMakeLists.txt b/db_stress_tool/CMakeLists.txt deleted file mode 100644 index 96d70dd0e..000000000 --- a/db_stress_tool/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -add_executable(db_stress${ARTIFACT_SUFFIX} - batched_ops_stress.cc - cf_consistency_stress.cc - db_stress.cc - db_stress_common.cc - db_stress_driver.cc - db_stress_gflags.cc - db_stress_listener.cc - db_stress_shared_state.cc - db_stress_stat.cc - db_stress_test_base.cc - db_stress_tool.cc - expected_state.cc - multi_ops_txns_stress.cc - no_batched_ops_stress.cc) -target_link_libraries(db_stress${ARTIFACT_SUFFIX} ${ROCKSDB_LIB} ${THIRDPARTY_LIBS}) -list(APPEND tool_deps db_stress) diff --git a/db_stress_tool/batched_ops_stress.cc b/db_stress_tool/batched_ops_stress.cc deleted file mode 100644 index 62a8290e9..000000000 --- a/db_stress_tool/batched_ops_stress.cc +++ /dev/null @@ -1,501 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" - -namespace ROCKSDB_NAMESPACE { -class BatchedOpsStressTest : public StressTest { - public: - BatchedOpsStressTest() {} - virtual ~BatchedOpsStressTest() {} - - bool IsStateTracked() const override { return false; } - - // Given a key K and value V, this puts ("0"+K, V+"0"), ("1"+K, V+"1"), ..., - // ("9"+K, V+"9") in DB atomically i.e in a single batch. - // Also refer BatchedOpsStressTest::TestGet - Status TestPut(ThreadState* thread, WriteOptions& write_opts, - const ReadOptions& /* read_opts */, - const std::vector& rand_column_families, - const std::vector& rand_keys, - char (&value)[100]) override { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - const std::string key_body = Key(rand_keys[0]); - - const uint32_t value_base = - thread->rand.Next() % thread->shared->UNKNOWN_SENTINEL; - const size_t sz = GenerateValue(value_base, value, sizeof(value)); - const std::string value_body = Slice(value, sz).ToString(); - - WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */, - FLAGS_batch_protection_bytes_per_key, - FLAGS_user_timestamp_size); - - ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]]; - assert(cfh); - - for (int i = 9; i >= 0; --i) { - const std::string num = std::to_string(i); - - // Note: the digit in num is prepended to the key; however, it is appended - // to the value because we want the "value base" to be encoded uniformly - // at the beginning of the value for all types of stress tests (e.g. - // batched, non-batched, CF consistency). - const std::string k = num + key_body; - const std::string v = value_body + num; - - if (FLAGS_use_merge) { - batch.Merge(cfh, k, v); - } else if (FLAGS_use_put_entity_one_in > 0 && - (value_base % FLAGS_use_put_entity_one_in) == 0) { - batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v)); - } else { - batch.Put(cfh, k, v); - } - } - - const Status s = db_->Write(write_opts, &batch); - - if (!s.ok()) { - fprintf(stderr, "multiput error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else { - // we did 10 writes each of size sz + 1 - thread->stats.AddBytesForWrites(10, (sz + 1) * 10); - } - - return s; - } - - // Given a key K, this deletes ("0"+K), ("1"+K), ..., ("9"+K) - // in DB atomically i.e in a single batch. Also refer MultiGet. - Status TestDelete(ThreadState* thread, WriteOptions& writeoptions, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - std::string keys[10] = {"9", "7", "5", "3", "1", "8", "6", "4", "2", "0"}; - - WriteBatch batch(0 /* reserved_bytes */, 0 /* max_bytes */, - FLAGS_batch_protection_bytes_per_key, - FLAGS_user_timestamp_size); - Status s; - auto cfh = column_families_[rand_column_families[0]]; - std::string key_str = Key(rand_keys[0]); - for (int i = 0; i < 10; i++) { - keys[i] += key_str; - batch.Delete(cfh, keys[i]); - } - - s = db_->Write(writeoptions, &batch); - if (!s.ok()) { - fprintf(stderr, "multidelete error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else { - thread->stats.AddDeletes(10); - } - - return s; - } - - Status TestDeleteRange(ThreadState* /* thread */, - WriteOptions& /* write_opts */, - const std::vector& /* rand_column_families */, - const std::vector& /* rand_keys */) override { - assert(false); - return Status::NotSupported( - "BatchedOpsStressTest does not support " - "TestDeleteRange"); - } - - void TestIngestExternalFile( - ThreadState* /* thread */, - const std::vector& /* rand_column_families */, - const std::vector& /* rand_keys */) override { - assert(false); - fprintf(stderr, - "BatchedOpsStressTest does not support " - "TestIngestExternalFile\n"); - std::terminate(); - } - - // Given a key K, this gets values for "0"+K, "1"+K, ..., "9"+K - // in the same snapshot, and verifies that all the values are of the form - // V+"0", V+"1", ..., V+"9". - // ASSUMES that BatchedOpsStressTest::TestPut was used to put (K, V) into - // the DB. - Status TestGet(ThreadState* thread, const ReadOptions& readoptions, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - std::string keys[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; - Slice key_slices[10]; - std::string values[10]; - ReadOptions readoptionscopy = readoptions; - readoptionscopy.snapshot = db_->GetSnapshot(); - std::string key_str = Key(rand_keys[0]); - Slice key = key_str; - auto cfh = column_families_[rand_column_families[0]]; - std::string from_db; - Status s; - for (int i = 0; i < 10; i++) { - keys[i] += key.ToString(); - key_slices[i] = keys[i]; - s = db_->Get(readoptionscopy, cfh, key_slices[i], &from_db); - if (!s.ok() && !s.IsNotFound()) { - fprintf(stderr, "get error: %s\n", s.ToString().c_str()); - values[i] = ""; - thread->stats.AddErrors(1); - // we continue after error rather than exiting so that we can - // find more errors if any - } else if (s.IsNotFound()) { - values[i] = ""; - thread->stats.AddGets(1, 0); - } else { - values[i] = from_db; - - assert(!keys[i].empty()); - assert(!values[i].empty()); - - const char expected = keys[i].front(); - const char actual = values[i].back(); - - if (expected != actual) { - fprintf(stderr, "get error expected = %c actual = %c\n", expected, - actual); - } - - values[i].pop_back(); // get rid of the differing character - - thread->stats.AddGets(1, 1); - } - } - db_->ReleaseSnapshot(readoptionscopy.snapshot); - - // Now that we retrieved all values, check that they all match - for (int i = 1; i < 10; i++) { - if (values[i] != values[0]) { - fprintf(stderr, "get error: inconsistent values for key %s: %s, %s\n", - key.ToString(true).c_str(), StringToHex(values[0]).c_str(), - StringToHex(values[i]).c_str()); - // we continue after error rather than exiting so that we can - // find more errors if any - } - } - - return s; - } - - std::vector TestMultiGet( - ThreadState* thread, const ReadOptions& readoptions, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - size_t num_keys = rand_keys.size(); - std::vector ret_status(num_keys); - std::array keys = { - {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}}; - size_t num_prefixes = keys.size(); - for (size_t rand_key = 0; rand_key < num_keys; ++rand_key) { - std::vector key_slices; - std::vector values(num_prefixes); - std::vector statuses(num_prefixes); - ReadOptions readoptionscopy = readoptions; - readoptionscopy.snapshot = db_->GetSnapshot(); - readoptionscopy.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - std::vector key_str; - key_str.reserve(num_prefixes); - key_slices.reserve(num_prefixes); - std::string from_db; - ColumnFamilyHandle* cfh = column_families_[rand_column_families[0]]; - - for (size_t key = 0; key < num_prefixes; ++key) { - key_str.emplace_back(keys[key] + Key(rand_keys[rand_key])); - key_slices.emplace_back(key_str.back()); - } - db_->MultiGet(readoptionscopy, cfh, num_prefixes, key_slices.data(), - values.data(), statuses.data()); - for (size_t i = 0; i < num_prefixes; i++) { - Status s = statuses[i]; - if (!s.ok() && !s.IsNotFound()) { - fprintf(stderr, "multiget error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - ret_status[rand_key] = s; - // we continue after error rather than exiting so that we can - // find more errors if any - } else if (s.IsNotFound()) { - thread->stats.AddGets(1, 0); - ret_status[rand_key] = s; - } else { - assert(!keys[i].empty()); - assert(!values[i].empty()); - - const char expected = keys[i][0]; - const char actual = values[i][values[i].size() - 1]; - - if (expected != actual) { - fprintf(stderr, "multiget error expected = %c actual = %c\n", - expected, actual); - } - - values[i].remove_suffix(1); // get rid of the differing character - - thread->stats.AddGets(1, 1); - } - } - db_->ReleaseSnapshot(readoptionscopy.snapshot); - - // Now that we retrieved all values, check that they all match - for (size_t i = 1; i < num_prefixes; i++) { - if (values[i] != values[0]) { - fprintf(stderr, - "multiget error: inconsistent values for key %s: %s, %s\n", - StringToHex(key_str[i]).c_str(), - StringToHex(values[0].ToString()).c_str(), - StringToHex(values[i].ToString()).c_str()); - // we continue after error rather than exiting so that we can - // find more errors if any - } - } - } - - return ret_status; - } - - void TestGetEntity(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - assert(thread); - - ManagedSnapshot snapshot_guard(db_); - - ReadOptions read_opts_copy(read_opts); - read_opts_copy.snapshot = snapshot_guard.snapshot(); - - assert(!rand_keys.empty()); - - const std::string key_suffix = Key(rand_keys[0]); - - assert(!rand_column_families.empty()); - assert(rand_column_families[0] >= 0); - assert(rand_column_families[0] < static_cast(column_families_.size())); - - ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]]; - assert(cfh); - - constexpr size_t num_keys = 10; - - std::array results; - - for (size_t i = 0; i < num_keys; ++i) { - const std::string key = std::to_string(i) + key_suffix; - - const Status s = db_->GetEntity(read_opts_copy, cfh, key, &results[i]); - - if (!s.ok() && !s.IsNotFound()) { - fprintf(stderr, "GetEntity error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else if (s.IsNotFound()) { - thread->stats.AddGets(1, 0); - } else { - thread->stats.AddGets(1, 1); - } - } - - // Compare columns ignoring the last character of column values - auto compare = [](const WideColumns& lhs, const WideColumns& rhs) { - if (lhs.size() != rhs.size()) { - return false; - } - - for (size_t i = 0; i < lhs.size(); ++i) { - if (lhs[i].name() != rhs[i].name()) { - return false; - } - - if (lhs[i].value().size() != rhs[i].value().size()) { - return false; - } - - if (lhs[i].value().difference_offset(rhs[i].value()) < - lhs[i].value().size() - 1) { - return false; - } - } - - return true; - }; - - for (size_t i = 0; i < num_keys; ++i) { - const WideColumns& columns = results[i].columns(); - - if (!compare(results[0].columns(), columns)) { - fprintf(stderr, - "GetEntity error: inconsistent entities for key %s: %s, %s\n", - StringToHex(key_suffix).c_str(), - WideColumnsToHex(results[0].columns()).c_str(), - WideColumnsToHex(columns).c_str()); - } - - if (!columns.empty()) { - // The last character of each column value should be 'i' as a decimal - // digit - const char expected = static_cast('0' + i); - - for (const auto& column : columns) { - const Slice& value = column.value(); - - if (value.empty() || value[value.size() - 1] != expected) { - fprintf(stderr, - "GetEntity error: incorrect column value for key " - "%s, entity %s, column value %s, expected %c\n", - StringToHex(key_suffix).c_str(), - WideColumnsToHex(columns).c_str(), - value.ToString(/* hex */ true).c_str(), expected); - } - } - - if (!VerifyWideColumns(columns)) { - fprintf( - stderr, - "GetEntity error: inconsistent columns for key %s, entity %s\n", - StringToHex(key_suffix).c_str(), - WideColumnsToHex(columns).c_str()); - } - } - } - } - - // Given a key, this does prefix scans for "0"+P, "1"+P, ..., "9"+P - // in the same snapshot where P is the first FLAGS_prefix_size - 1 bytes - // of the key. Each of these 10 scans returns a series of values; - // each series should be the same length, and it is verified for each - // index i that all the i'th values are of the form V+"0", V+"1", ..., V+"9". - // ASSUMES that MultiPut was used to put (K, V) - Status TestPrefixScan(ThreadState* thread, const ReadOptions& readoptions, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - const std::string key = Key(rand_keys[0]); - - assert(FLAGS_prefix_size > 0); - const size_t prefix_to_use = static_cast(FLAGS_prefix_size); - - constexpr size_t num_prefixes = 10; - - std::array prefixes; - std::array prefix_slices; - std::array ro_copies; - std::array upper_bounds; - std::array ub_slices; - std::array, num_prefixes> iters; - - const Snapshot* const snapshot = db_->GetSnapshot(); - - ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]]; - assert(cfh); - - for (size_t i = 0; i < num_prefixes; ++i) { - prefixes[i] = std::to_string(i) + key; - prefix_slices[i] = Slice(prefixes[i].data(), prefix_to_use); - - ro_copies[i] = readoptions; - ro_copies[i].snapshot = snapshot; - if (thread->rand.OneIn(2) && - GetNextPrefix(prefix_slices[i], &(upper_bounds[i]))) { - // For half of the time, set the upper bound to the next prefix - ub_slices[i] = upper_bounds[i]; - ro_copies[i].iterate_upper_bound = &(ub_slices[i]); - } - - iters[i].reset(db_->NewIterator(ro_copies[i], cfh)); - iters[i]->Seek(prefix_slices[i]); - } - - uint64_t count = 0; - - while (iters[0]->Valid() && iters[0]->key().starts_with(prefix_slices[0])) { - ++count; - - std::array values; - - // get list of all values for this iteration - for (size_t i = 0; i < num_prefixes; ++i) { - // no iterator should finish before the first one - assert(iters[i]->Valid() && - iters[i]->key().starts_with(prefix_slices[i])); - values[i] = iters[i]->value().ToString(); - - // make sure the last character of the value is the expected digit - assert(!prefixes[i].empty()); - assert(!values[i].empty()); - - const char expected = prefixes[i].front(); - const char actual = values[i].back(); - - if (expected != actual) { - fprintf(stderr, "prefix scan error expected = %c actual = %c\n", - expected, actual); - } - - values[i].pop_back(); // get rid of the differing character - - // make sure all values are equivalent - if (values[i] != values[0]) { - fprintf(stderr, - "prefix scan error : %" ROCKSDB_PRIszt - ", inconsistent values for prefix %s: %s, %s\n", - i, prefix_slices[i].ToString(/* hex */ true).c_str(), - StringToHex(values[0]).c_str(), - StringToHex(values[i]).c_str()); - // we continue after error rather than exiting so that we can - // find more errors if any - } - - // make sure value() and columns() are consistent - if (!VerifyWideColumns(iters[i]->value(), iters[i]->columns())) { - fprintf(stderr, - "prefix scan error : %" ROCKSDB_PRIszt - ", value and columns inconsistent for prefix %s: value: %s, " - "columns: %s\n", - i, prefix_slices[i].ToString(/* hex */ true).c_str(), - iters[i]->value().ToString(/* hex */ true).c_str(), - WideColumnsToHex(iters[i]->columns()).c_str()); - } - - iters[i]->Next(); - } - } - - // cleanup iterators and snapshot - for (size_t i = 0; i < num_prefixes; ++i) { - // if the first iterator finished, they should have all finished - assert(!iters[i]->Valid() || - !iters[i]->key().starts_with(prefix_slices[i])); - assert(iters[i]->status().ok()); - } - - db_->ReleaseSnapshot(snapshot); - - thread->stats.AddPrefixes(1, count); - - return Status::OK(); - } - - void VerifyDb(ThreadState* /* thread */) const override {} - - void ContinuouslyVerifyDb(ThreadState* /* thread */) const override {} -}; - -StressTest* CreateBatchedOpsStressTest() { return new BatchedOpsStressTest(); } - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/cf_consistency_stress.cc b/db_stress_tool/cf_consistency_stress.cc deleted file mode 100644 index 883a17b6e..000000000 --- a/db_stress_tool/cf_consistency_stress.cc +++ /dev/null @@ -1,769 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" -#include "file/file_util.h" - -namespace ROCKSDB_NAMESPACE { -class CfConsistencyStressTest : public StressTest { - public: - CfConsistencyStressTest() : batch_id_(0) {} - - ~CfConsistencyStressTest() override {} - - bool IsStateTracked() const override { return false; } - - Status TestPut(ThreadState* thread, WriteOptions& write_opts, - const ReadOptions& /* read_opts */, - const std::vector& rand_column_families, - const std::vector& rand_keys, - char (&value)[100]) override { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - const std::string k = Key(rand_keys[0]); - - const uint32_t value_base = batch_id_.fetch_add(1); - const size_t sz = GenerateValue(value_base, value, sizeof(value)); - const Slice v(value, sz); - - WriteBatch batch; - - const bool use_put_entity = !FLAGS_use_merge && - FLAGS_use_put_entity_one_in > 0 && - (value_base % FLAGS_use_put_entity_one_in) == 0; - - for (auto cf : rand_column_families) { - ColumnFamilyHandle* const cfh = column_families_[cf]; - assert(cfh); - - if (FLAGS_use_merge) { - batch.Merge(cfh, k, v); - } else if (use_put_entity) { - batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v)); - } else { - batch.Put(cfh, k, v); - } - } - - Status s = db_->Write(write_opts, &batch); - - if (!s.ok()) { - fprintf(stderr, "multi put or merge error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else { - auto num = static_cast(rand_column_families.size()); - thread->stats.AddBytesForWrites(num, (sz + 1) * num); - } - - return s; - } - - Status TestDelete(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - std::string key_str = Key(rand_keys[0]); - Slice key = key_str; - WriteBatch batch; - for (auto cf : rand_column_families) { - ColumnFamilyHandle* cfh = column_families_[cf]; - batch.Delete(cfh, key); - } - Status s = db_->Write(write_opts, &batch); - if (!s.ok()) { - fprintf(stderr, "multidel error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else { - thread->stats.AddDeletes(static_cast(rand_column_families.size())); - } - return s; - } - - Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - int64_t rand_key = rand_keys[0]; - auto shared = thread->shared; - int64_t max_key = shared->GetMaxKey(); - if (rand_key > max_key - FLAGS_range_deletion_width) { - rand_key = - thread->rand.Next() % (max_key - FLAGS_range_deletion_width + 1); - } - std::string key_str = Key(rand_key); - Slice key = key_str; - std::string end_key_str = Key(rand_key + FLAGS_range_deletion_width); - Slice end_key = end_key_str; - WriteBatch batch; - for (auto cf : rand_column_families) { - ColumnFamilyHandle* cfh = column_families_[rand_column_families[cf]]; - batch.DeleteRange(cfh, key, end_key); - } - Status s = db_->Write(write_opts, &batch); - if (!s.ok()) { - fprintf(stderr, "multi del range error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else { - thread->stats.AddRangeDeletions( - static_cast(rand_column_families.size())); - } - return s; - } - - void TestIngestExternalFile( - ThreadState* /* thread */, - const std::vector& /* rand_column_families */, - const std::vector& /* rand_keys */) override { - assert(false); - fprintf(stderr, - "CfConsistencyStressTest does not support TestIngestExternalFile " - "because it's not possible to verify the result\n"); - std::terminate(); - } - - Status TestGet(ThreadState* thread, const ReadOptions& readoptions, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - std::string key_str = Key(rand_keys[0]); - Slice key = key_str; - Status s; - bool is_consistent = true; - - if (thread->rand.OneIn(2)) { - // 1/2 chance, does a random read from random CF - auto cfh = - column_families_[rand_column_families[thread->rand.Next() % - rand_column_families.size()]]; - std::string from_db; - s = db_->Get(readoptions, cfh, key, &from_db); - } else { - // 1/2 chance, comparing one key is the same across all CFs - const Snapshot* snapshot = db_->GetSnapshot(); - ReadOptions readoptionscopy = readoptions; - readoptionscopy.snapshot = snapshot; - - std::string value0; - s = db_->Get(readoptionscopy, column_families_[rand_column_families[0]], - key, &value0); - if (s.ok() || s.IsNotFound()) { - bool found = s.ok(); - for (size_t i = 1; i < rand_column_families.size(); i++) { - std::string value1; - s = db_->Get(readoptionscopy, - column_families_[rand_column_families[i]], key, &value1); - if (!s.ok() && !s.IsNotFound()) { - break; - } - if (!found && s.ok()) { - fprintf(stderr, "Get() return different results with key %s\n", - Slice(key_str).ToString(true).c_str()); - fprintf(stderr, "CF %s is not found\n", - column_family_names_[0].c_str()); - fprintf(stderr, "CF %s returns value %s\n", - column_family_names_[i].c_str(), - Slice(value1).ToString(true).c_str()); - is_consistent = false; - } else if (found && s.IsNotFound()) { - fprintf(stderr, "Get() return different results with key %s\n", - Slice(key_str).ToString(true).c_str()); - fprintf(stderr, "CF %s returns value %s\n", - column_family_names_[0].c_str(), - Slice(value0).ToString(true).c_str()); - fprintf(stderr, "CF %s is not found\n", - column_family_names_[i].c_str()); - is_consistent = false; - } else if (s.ok() && value0 != value1) { - fprintf(stderr, "Get() return different results with key %s\n", - Slice(key_str).ToString(true).c_str()); - fprintf(stderr, "CF %s returns value %s\n", - column_family_names_[0].c_str(), - Slice(value0).ToString(true).c_str()); - fprintf(stderr, "CF %s returns value %s\n", - column_family_names_[i].c_str(), - Slice(value1).ToString(true).c_str()); - is_consistent = false; - } - if (!is_consistent) { - break; - } - } - } - - db_->ReleaseSnapshot(snapshot); - } - if (!is_consistent) { - fprintf(stderr, "TestGet error: is_consistent is false\n"); - thread->stats.AddErrors(1); - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - } else if (s.ok()) { - thread->stats.AddGets(1, 1); - } else if (s.IsNotFound()) { - thread->stats.AddGets(1, 0); - } else { - fprintf(stderr, "TestGet error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } - return s; - } - - std::vector TestMultiGet( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - size_t num_keys = rand_keys.size(); - std::vector key_str; - std::vector keys; - keys.reserve(num_keys); - key_str.reserve(num_keys); - std::vector values(num_keys); - std::vector statuses(num_keys); - ColumnFamilyHandle* cfh = column_families_[rand_column_families[0]]; - ReadOptions readoptionscopy = read_opts; - readoptionscopy.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - - for (size_t i = 0; i < num_keys; ++i) { - key_str.emplace_back(Key(rand_keys[i])); - keys.emplace_back(key_str.back()); - } - db_->MultiGet(readoptionscopy, cfh, num_keys, keys.data(), values.data(), - statuses.data()); - for (auto s : statuses) { - if (s.ok()) { - // found case - thread->stats.AddGets(1, 1); - } else if (s.IsNotFound()) { - // not found case - thread->stats.AddGets(1, 0); - } else { - // errors case - fprintf(stderr, "MultiGet error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } - } - return statuses; - } - - void TestGetEntity(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - assert(thread); - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - const std::string key = Key(rand_keys[0]); - - Status s; - bool is_consistent = true; - - if (thread->rand.OneIn(2)) { - // With a 1/2 chance, do a random read from a random CF - const size_t cf_id = thread->rand.Next() % rand_column_families.size(); - - assert(rand_column_families[cf_id] >= 0); - assert(rand_column_families[cf_id] < - static_cast(column_families_.size())); - - ColumnFamilyHandle* const cfh = - column_families_[rand_column_families[cf_id]]; - assert(cfh); - - PinnableWideColumns result; - s = db_->GetEntity(read_opts, cfh, key, &result); - - if (s.ok()) { - if (!VerifyWideColumns(result.columns())) { - fprintf( - stderr, - "GetEntity error: inconsistent columns for key %s, entity %s\n", - StringToHex(key).c_str(), - WideColumnsToHex(result.columns()).c_str()); - is_consistent = false; - } - } - } else { - // With a 1/2 chance, compare one key across all CFs - ManagedSnapshot snapshot_guard(db_); - - ReadOptions read_opts_copy = read_opts; - read_opts_copy.snapshot = snapshot_guard.snapshot(); - - assert(rand_column_families[0] >= 0); - assert(rand_column_families[0] < - static_cast(column_families_.size())); - - PinnableWideColumns cmp_result; - s = db_->GetEntity(read_opts_copy, - column_families_[rand_column_families[0]], key, - &cmp_result); - - if (s.ok() || s.IsNotFound()) { - const bool cmp_found = s.ok(); - - if (cmp_found) { - if (!VerifyWideColumns(cmp_result.columns())) { - fprintf(stderr, - "GetEntity error: inconsistent columns for key %s, " - "entity %s\n", - StringToHex(key).c_str(), - WideColumnsToHex(cmp_result.columns()).c_str()); - is_consistent = false; - } - } - - if (is_consistent) { - for (size_t i = 1; i < rand_column_families.size(); ++i) { - assert(rand_column_families[i] >= 0); - assert(rand_column_families[i] < - static_cast(column_families_.size())); - - PinnableWideColumns result; - s = db_->GetEntity(read_opts_copy, - column_families_[rand_column_families[i]], key, - &result); - - if (!s.ok() && !s.IsNotFound()) { - break; - } - - const bool found = s.ok(); - - assert(!column_family_names_.empty()); - assert(i < column_family_names_.size()); - - if (!cmp_found && found) { - fprintf(stderr, - "GetEntity returns different results for key %s: CF %s " - "returns not found, CF %s returns entity %s\n", - StringToHex(key).c_str(), column_family_names_[0].c_str(), - column_family_names_[i].c_str(), - WideColumnsToHex(result.columns()).c_str()); - is_consistent = false; - break; - } - - if (cmp_found && !found) { - fprintf(stderr, - "GetEntity returns different results for key %s: CF %s " - "returns entity %s, CF %s returns not found\n", - StringToHex(key).c_str(), column_family_names_[0].c_str(), - WideColumnsToHex(cmp_result.columns()).c_str(), - column_family_names_[i].c_str()); - is_consistent = false; - break; - } - - if (found && result != cmp_result) { - fprintf(stderr, - "GetEntity returns different results for key %s: CF %s " - "returns entity %s, CF %s returns entity %s\n", - StringToHex(key).c_str(), column_family_names_[0].c_str(), - WideColumnsToHex(cmp_result.columns()).c_str(), - column_family_names_[i].c_str(), - WideColumnsToHex(result.columns()).c_str()); - is_consistent = false; - break; - } - } - } - } - } - - if (!is_consistent) { - fprintf(stderr, "TestGetEntity error: results are not consistent\n"); - thread->stats.AddErrors(1); - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - } else if (s.ok()) { - thread->stats.AddGets(1, 1); - } else if (s.IsNotFound()) { - thread->stats.AddGets(1, 0); - } else { - fprintf(stderr, "TestGetEntity error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } - } - - Status TestPrefixScan(ThreadState* thread, const ReadOptions& readoptions, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - const std::string key = Key(rand_keys[0]); - - const size_t prefix_to_use = - (FLAGS_prefix_size < 0) ? 7 : static_cast(FLAGS_prefix_size); - - const Slice prefix(key.data(), prefix_to_use); - - std::string upper_bound; - Slice ub_slice; - - ReadOptions ro_copy = readoptions; - - // Get the next prefix first and then see if we want to set upper bound. - // We'll use the next prefix in an assertion later on - if (GetNextPrefix(prefix, &upper_bound) && thread->rand.OneIn(2)) { - ub_slice = Slice(upper_bound); - ro_copy.iterate_upper_bound = &ub_slice; - } - - ColumnFamilyHandle* const cfh = - column_families_[rand_column_families[thread->rand.Uniform( - static_cast(rand_column_families.size()))]]; - assert(cfh); - - std::unique_ptr iter(db_->NewIterator(ro_copy, cfh)); - - uint64_t count = 0; - Status s; - - for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix); - iter->Next()) { - ++count; - - if (!VerifyWideColumns(iter->value(), iter->columns())) { - s = Status::Corruption("Value and columns inconsistent", - DebugString(iter->value(), iter->columns())); - break; - } - } - - assert(prefix_to_use == 0 || - count <= GetPrefixKeyCount(prefix.ToString(), upper_bound)); - - if (s.ok()) { - s = iter->status(); - } - - if (!s.ok()) { - fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - - return s; - } - - thread->stats.AddPrefixes(1, count); - - return Status::OK(); - } - - ColumnFamilyHandle* GetControlCfh(ThreadState* thread, - int /*column_family_id*/ - ) override { - // All column families should contain the same data. Randomly pick one. - return column_families_[thread->rand.Next() % column_families_.size()]; - } - - void VerifyDb(ThreadState* thread) const override { - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions options(FLAGS_verify_checksum, true); - - // We must set total_order_seek to true because we are doing a SeekToFirst - // on a column family whose memtables may support (by default) prefix-based - // iterator. In this case, NewIterator with options.total_order_seek being - // false returns a prefix-based iterator. Calling SeekToFirst using this - // iterator causes the iterator to become invalid. That means we cannot - // iterate the memtable using this iterator any more, although the memtable - // contains the most up-to-date key-values. - options.total_order_seek = true; - - ManagedSnapshot snapshot_guard(db_); - options.snapshot = snapshot_guard.snapshot(); - - const size_t num = column_families_.size(); - - std::vector> iters; - iters.reserve(num); - - for (size_t i = 0; i < num; ++i) { - iters.emplace_back(db_->NewIterator(options, column_families_[i])); - iters.back()->SeekToFirst(); - } - - std::vector statuses(num, Status::OK()); - - assert(thread); - - auto shared = thread->shared; - assert(shared); - - do { - if (shared->HasVerificationFailedYet()) { - break; - } - - size_t valid_cnt = 0; - - for (size_t i = 0; i < num; ++i) { - const auto& iter = iters[i]; - assert(iter); - - if (iter->Valid()) { - if (!VerifyWideColumns(iter->value(), iter->columns())) { - statuses[i] = - Status::Corruption("Value and columns inconsistent", - DebugString(iter->value(), iter->columns())); - } else { - ++valid_cnt; - } - } else { - statuses[i] = iter->status(); - } - } - - if (valid_cnt == 0) { - for (size_t i = 0; i < num; ++i) { - const auto& s = statuses[i]; - if (!s.ok()) { - fprintf(stderr, "Iterator on cf %s has error: %s\n", - column_families_[i]->GetName().c_str(), - s.ToString().c_str()); - shared->SetVerificationFailure(); - } - } - - break; - } - - if (valid_cnt < num) { - shared->SetVerificationFailure(); - - for (size_t i = 0; i < num; ++i) { - assert(iters[i]); - - if (!iters[i]->Valid()) { - if (statuses[i].ok()) { - fprintf(stderr, "Finished scanning cf %s\n", - column_families_[i]->GetName().c_str()); - } else { - fprintf(stderr, "Iterator on cf %s has error: %s\n", - column_families_[i]->GetName().c_str(), - statuses[i].ToString().c_str()); - } - } else { - fprintf(stderr, "cf %s has remaining data to scan\n", - column_families_[i]->GetName().c_str()); - } - } - - break; - } - - if (shared->HasVerificationFailedYet()) { - break; - } - - // If the program reaches here, then all column families' iterators are - // still valid. - assert(valid_cnt == num); - - if (shared->PrintingVerificationResults()) { - continue; - } - - assert(iters[0]); - - const Slice key = iters[0]->key(); - const Slice value = iters[0]->value(); - - int num_mismatched_cfs = 0; - - for (size_t i = 1; i < num; ++i) { - assert(iters[i]); - - const int cmp = key.compare(iters[i]->key()); - - if (cmp != 0) { - ++num_mismatched_cfs; - - if (1 == num_mismatched_cfs) { - fprintf(stderr, "Verification failed\n"); - fprintf(stderr, "Latest Sequence Number: %" PRIu64 "\n", - db_->GetLatestSequenceNumber()); - fprintf(stderr, "[%s] %s => %s\n", - column_families_[0]->GetName().c_str(), - key.ToString(true /* hex */).c_str(), - value.ToString(true /* hex */).c_str()); - } - - fprintf(stderr, "[%s] %s => %s\n", - column_families_[i]->GetName().c_str(), - iters[i]->key().ToString(true /* hex */).c_str(), - iters[i]->value().ToString(true /* hex */).c_str()); - - Slice begin_key; - Slice end_key; - if (cmp < 0) { - begin_key = key; - end_key = iters[i]->key(); - } else { - begin_key = iters[i]->key(); - end_key = key; - } - - const auto print_key_versions = [&](ColumnFamilyHandle* cfh) { - constexpr size_t kMaxNumIKeys = 8; - - std::vector versions; - const Status s = GetAllKeyVersions(db_, cfh, begin_key, end_key, - kMaxNumIKeys, &versions); - if (!s.ok()) { - fprintf(stderr, "%s\n", s.ToString().c_str()); - return; - } - - assert(cfh); - - fprintf(stderr, - "Internal keys in CF '%s', [%s, %s] (max %" ROCKSDB_PRIszt - ")\n", - cfh->GetName().c_str(), - begin_key.ToString(true /* hex */).c_str(), - end_key.ToString(true /* hex */).c_str(), kMaxNumIKeys); - - for (const KeyVersion& kv : versions) { - fprintf(stderr, " key %s seq %" PRIu64 " type %d\n", - Slice(kv.user_key).ToString(true).c_str(), kv.sequence, - kv.type); - } - }; - - if (1 == num_mismatched_cfs) { - print_key_versions(column_families_[0]); - } - - print_key_versions(column_families_[i]); - - shared->SetVerificationFailure(); - } - } - - shared->FinishPrintingVerificationResults(); - - for (auto& iter : iters) { - assert(iter); - iter->Next(); - } - } while (true); - } - - void ContinuouslyVerifyDb(ThreadState* thread) const override { - assert(thread); - Status status; - - DB* db_ptr = cmp_db_ ? cmp_db_ : db_; - const auto& cfhs = cmp_db_ ? cmp_cfhs_ : column_families_; - - // Take a snapshot to preserve the state of primary db. - ManagedSnapshot snapshot_guard(db_); - - SharedState* shared = thread->shared; - assert(shared); - - if (cmp_db_) { - status = cmp_db_->TryCatchUpWithPrimary(); - if (!status.ok()) { - fprintf(stderr, "TryCatchUpWithPrimary: %s\n", - status.ToString().c_str()); - shared->SetShouldStopTest(); - assert(false); - return; - } - } - - const auto checksum_column_family = [](Iterator* iter, - uint32_t* checksum) -> Status { - assert(nullptr != checksum); - - uint32_t ret = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ret = crc32c::Extend(ret, iter->key().data(), iter->key().size()); - ret = crc32c::Extend(ret, iter->value().data(), iter->value().size()); - - for (const auto& column : iter->columns()) { - ret = crc32c::Extend(ret, column.name().data(), column.name().size()); - ret = - crc32c::Extend(ret, column.value().data(), column.value().size()); - } - } - - *checksum = ret; - return iter->status(); - }; - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ropts(FLAGS_verify_checksum, true); - ropts.total_order_seek = true; - if (nullptr == cmp_db_) { - ropts.snapshot = snapshot_guard.snapshot(); - } - uint32_t crc = 0; - { - // Compute crc for all key-values of default column family. - std::unique_ptr it(db_ptr->NewIterator(ropts)); - status = checksum_column_family(it.get(), &crc); - if (!status.ok()) { - fprintf(stderr, "Computing checksum of default cf: %s\n", - status.ToString().c_str()); - assert(false); - } - } - // Since we currently intentionally disallow reading from the secondary - // instance with snapshot, we cannot achieve cross-cf consistency if WAL is - // enabled because there is no guarantee that secondary instance replays - // the primary's WAL to a consistent point where all cfs have the same - // data. - if (status.ok() && FLAGS_disable_wal) { - uint32_t tmp_crc = 0; - for (ColumnFamilyHandle* cfh : cfhs) { - if (cfh == db_ptr->DefaultColumnFamily()) { - continue; - } - std::unique_ptr it(db_ptr->NewIterator(ropts, cfh)); - status = checksum_column_family(it.get(), &tmp_crc); - if (!status.ok() || tmp_crc != crc) { - break; - } - } - if (!status.ok()) { - fprintf(stderr, "status: %s\n", status.ToString().c_str()); - shared->SetShouldStopTest(); - assert(false); - } else if (tmp_crc != crc) { - fprintf(stderr, "tmp_crc=%" PRIu32 " crc=%" PRIu32 "\n", tmp_crc, crc); - shared->SetShouldStopTest(); - assert(false); - } - } - } - - std::vector GenerateColumnFamilies( - const int /* num_column_families */, - int /* rand_column_family */) const override { - std::vector ret; - int num = static_cast(column_families_.size()); - int k = 0; - std::generate_n(back_inserter(ret), num, [&k]() -> int { return k++; }); - return ret; - } - - private: - std::atomic batch_id_; -}; - -StressTest* CreateCfConsistencyStressTest() { - return new CfConsistencyStressTest(); -} - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress.cc b/db_stress_tool/db_stress.cc deleted file mode 100644 index 2d03f5d26..000000000 --- a/db_stress_tool/db_stress.cc +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifndef GFLAGS -#include - -int main() { - fprintf(stderr, "Please install gflags to run rocksdb tools\n"); - return 1; -} -#else -#include "port/stack_trace.h" -#include "rocksdb/db_stress_tool.h" - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - return ROCKSDB_NAMESPACE::db_stress_tool(argc, argv); -} -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_common.cc b/db_stress_tool/db_stress_common.cc deleted file mode 100644 index 93436d0f8..000000000 --- a/db_stress_tool/db_stress_common.cc +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" - -#include - -#include "util/file_checksum_helper.h" -#include "util/xxhash.h" - -ROCKSDB_NAMESPACE::Env* db_stress_listener_env = nullptr; -ROCKSDB_NAMESPACE::Env* db_stress_env = nullptr; -// If non-null, injects read error at a rate specified by the -// read_fault_one_in or write_fault_one_in flag -std::shared_ptr fault_fs_guard; -enum ROCKSDB_NAMESPACE::CompressionType compression_type_e = - ROCKSDB_NAMESPACE::kSnappyCompression; -enum ROCKSDB_NAMESPACE::CompressionType bottommost_compression_type_e = - ROCKSDB_NAMESPACE::kSnappyCompression; -enum ROCKSDB_NAMESPACE::ChecksumType checksum_type_e = - ROCKSDB_NAMESPACE::kCRC32c; -enum RepFactory FLAGS_rep_factory = kSkipList; -std::vector sum_probs(100001); -constexpr int64_t zipf_sum_size = 100000; - -namespace ROCKSDB_NAMESPACE { - -// Zipfian distribution is generated based on a pre-calculated array. -// It should be used before start the stress test. -// First, the probability distribution function (PDF) of this Zipfian follows -// power low. P(x) = 1/(x^alpha). -// So we calculate the PDF when x is from 0 to zipf_sum_size in first for loop -// and add the PDF value togetger as c. So we get the total probability in c. -// Next, we calculate inverse CDF of Zipfian and store the value of each in -// an array (sum_probs). The rank is from 0 to zipf_sum_size. For example, for -// integer k, its Zipfian CDF value is sum_probs[k]. -// Third, when we need to get an integer whose probability follows Zipfian -// distribution, we use a rand_seed [0,1] which follows uniform distribution -// as a seed and search it in the sum_probs via binary search. When we find -// the closest sum_probs[i] of rand_seed, i is the integer that in -// [0, zipf_sum_size] following Zipfian distribution with parameter alpha. -// Finally, we can scale i to [0, max_key] scale. -// In order to avoid that hot keys are close to each other and skew towards 0, -// we use Rando64 to shuffle it. -void InitializeHotKeyGenerator(double alpha) { - double c = 0; - for (int64_t i = 1; i <= zipf_sum_size; i++) { - c = c + (1.0 / std::pow(static_cast(i), alpha)); - } - c = 1.0 / c; - - sum_probs[0] = 0; - for (int64_t i = 1; i <= zipf_sum_size; i++) { - sum_probs[i] = - sum_probs[i - 1] + c / std::pow(static_cast(i), alpha); - } -} - -// Generate one key that follows the Zipfian distribution. The skewness -// is decided by the parameter alpha. Input is the rand_seed [0,1] and -// the max of the key to be generated. If we directly return tmp_zipf_seed, -// the closer to 0, the higher probability will be. To randomly distribute -// the hot keys in [0, max_key], we use Random64 to shuffle it. -int64_t GetOneHotKeyID(double rand_seed, int64_t max_key) { - int64_t low = 1, mid, high = zipf_sum_size, zipf = 0; - while (low <= high) { - mid = (low + high) / 2; - if (sum_probs[mid] >= rand_seed && sum_probs[mid - 1] < rand_seed) { - zipf = mid; - break; - } else if (sum_probs[mid] >= rand_seed) { - high = mid - 1; - } else { - low = mid + 1; - } - } - int64_t tmp_zipf_seed = zipf * max_key / zipf_sum_size; - Random64 rand_local(tmp_zipf_seed); - return rand_local.Next() % max_key; -} - -void PoolSizeChangeThread(void* v) { - assert(FLAGS_compaction_thread_pool_adjust_interval > 0); - ThreadState* thread = reinterpret_cast(v); - SharedState* shared = thread->shared; - - while (true) { - { - MutexLock l(shared->GetMutex()); - if (shared->ShouldStopBgThread()) { - shared->IncBgThreadsFinished(); - if (shared->BgThreadsFinished()) { - shared->GetCondVar()->SignalAll(); - } - return; - } - } - - auto thread_pool_size_base = FLAGS_max_background_compactions; - auto thread_pool_size_var = FLAGS_compaction_thread_pool_variations; - int new_thread_pool_size = - thread_pool_size_base - thread_pool_size_var + - thread->rand.Next() % (thread_pool_size_var * 2 + 1); - if (new_thread_pool_size < 1) { - new_thread_pool_size = 1; - } - db_stress_env->SetBackgroundThreads(new_thread_pool_size, - ROCKSDB_NAMESPACE::Env::Priority::LOW); - // Sleep up to 3 seconds - db_stress_env->SleepForMicroseconds( - thread->rand.Next() % FLAGS_compaction_thread_pool_adjust_interval * - 1000 + - 1); - } -} - -void DbVerificationThread(void* v) { - assert(FLAGS_continuous_verification_interval > 0); - auto* thread = reinterpret_cast(v); - SharedState* shared = thread->shared; - StressTest* stress_test = shared->GetStressTest(); - assert(stress_test != nullptr); - while (true) { - { - MutexLock l(shared->GetMutex()); - if (shared->ShouldStopBgThread()) { - shared->IncBgThreadsFinished(); - if (shared->BgThreadsFinished()) { - shared->GetCondVar()->SignalAll(); - } - return; - } - } - if (!shared->HasVerificationFailedYet()) { - stress_test->ContinuouslyVerifyDb(thread); - } - db_stress_env->SleepForMicroseconds( - thread->rand.Next() % FLAGS_continuous_verification_interval * 1000 + - 1); - } -} - -void PrintKeyValue(int cf, uint64_t key, const char* value, size_t sz) { - if (!FLAGS_verbose) { - return; - } - std::string tmp; - tmp.reserve(sz * 2 + 16); - char buf[4]; - for (size_t i = 0; i < sz; i++) { - snprintf(buf, 4, "%X", value[i]); - tmp.append(buf); - } - auto key_str = Key(key); - Slice key_slice = key_str; - fprintf(stdout, "[CF %d] %s (%" PRIi64 ") == > (%" ROCKSDB_PRIszt ") %s\n", - cf, key_slice.ToString(true).c_str(), key, sz, tmp.c_str()); -} - -// Note that if hot_key_alpha != 0, it generates the key based on Zipfian -// distribution. Keys are randomly scattered to [0, FLAGS_max_key]. It does -// not ensure the order of the keys being generated and the keys does not have -// the active range which is related to FLAGS_active_width. -int64_t GenerateOneKey(ThreadState* thread, uint64_t iteration) { - const double completed_ratio = - static_cast(iteration) / FLAGS_ops_per_thread; - const int64_t base_key = static_cast( - completed_ratio * (FLAGS_max_key - FLAGS_active_width)); - int64_t rand_seed = base_key + thread->rand.Next() % FLAGS_active_width; - int64_t cur_key = rand_seed; - if (FLAGS_hot_key_alpha != 0) { - // If set the Zipfian distribution Alpha to non 0, use Zipfian - double float_rand = - (static_cast(thread->rand.Next() % FLAGS_max_key)) / - FLAGS_max_key; - cur_key = GetOneHotKeyID(float_rand, FLAGS_max_key); - } - return cur_key; -} - -// Note that if hot_key_alpha != 0, it generates the key based on Zipfian -// distribution. Keys being generated are in random order. -// If user want to generate keys based on uniform distribution, user needs to -// set hot_key_alpha == 0. It will generate the random keys in increasing -// order in the key array (ensure key[i] >= key[i+1]) and constrained in a -// range related to FLAGS_active_width. -std::vector GenerateNKeys(ThreadState* thread, int num_keys, - uint64_t iteration) { - const double completed_ratio = - static_cast(iteration) / FLAGS_ops_per_thread; - const int64_t base_key = static_cast( - completed_ratio * (FLAGS_max_key - FLAGS_active_width)); - std::vector keys; - keys.reserve(num_keys); - int64_t next_key = base_key + thread->rand.Next() % FLAGS_active_width; - keys.push_back(next_key); - for (int i = 1; i < num_keys; ++i) { - // Generate the key follows zipfian distribution - if (FLAGS_hot_key_alpha != 0) { - double float_rand = - (static_cast(thread->rand.Next() % FLAGS_max_key)) / - FLAGS_max_key; - next_key = GetOneHotKeyID(float_rand, FLAGS_max_key); - } else { - // This may result in some duplicate keys - next_key = next_key + thread->rand.Next() % - (FLAGS_active_width - (next_key - base_key)); - } - keys.push_back(next_key); - } - return keys; -} - -size_t GenerateValue(uint32_t rand, char* v, size_t max_sz) { - size_t value_sz = - ((rand % kRandomValueMaxFactor) + 1) * FLAGS_value_size_mult; - assert(value_sz <= max_sz && value_sz >= sizeof(uint32_t)); - (void)max_sz; - PutUnaligned(reinterpret_cast(v), rand); - for (size_t i = sizeof(uint32_t); i < value_sz; i++) { - v[i] = (char)(rand ^ i); - } - v[value_sz] = '\0'; - return value_sz; // the size of the value set. -} - -uint32_t GetValueBase(Slice s) { - assert(s.size() >= sizeof(uint32_t)); - uint32_t res; - GetUnaligned(reinterpret_cast(s.data()), &res); - return res; -} - -WideColumns GenerateWideColumns(uint32_t value_base, const Slice& slice) { - WideColumns columns; - - constexpr size_t max_columns = 4; - const size_t num_columns = (value_base % max_columns) + 1; - - columns.reserve(num_columns); - - assert(slice.size() >= num_columns); - - columns.emplace_back(kDefaultWideColumnName, slice); - - for (size_t i = 1; i < num_columns; ++i) { - const Slice name(slice.data(), i); - const Slice value(slice.data() + i, slice.size() - i); - - columns.emplace_back(name, value); - } - - return columns; -} - -WideColumns GenerateExpectedWideColumns(uint32_t value_base, - const Slice& slice) { - if (FLAGS_use_put_entity_one_in == 0 || - (value_base % FLAGS_use_put_entity_one_in) != 0) { - return WideColumns{{kDefaultWideColumnName, slice}}; - } - - WideColumns columns = GenerateWideColumns(value_base, slice); - - std::sort(columns.begin(), columns.end(), - [](const WideColumn& lhs, const WideColumn& rhs) { - return lhs.name().compare(rhs.name()) < 0; - }); - - return columns; -} - -bool VerifyWideColumns(const Slice& value, const WideColumns& columns) { - if (value.size() < sizeof(uint32_t)) { - return false; - } - - const uint32_t value_base = GetValueBase(value); - - const WideColumns expected_columns = - GenerateExpectedWideColumns(value_base, value); - - if (columns != expected_columns) { - return false; - } - - return true; -} - -bool VerifyWideColumns(const WideColumns& columns) { - if (columns.empty()) { - return false; - } - - if (columns.front().name() != kDefaultWideColumnName) { - return false; - } - - const Slice& value_of_default = columns.front().value(); - - return VerifyWideColumns(value_of_default, columns); -} - -std::string GetNowNanos() { - uint64_t t = db_stress_env->NowNanos(); - std::string ret; - PutFixed64(&ret, t); - return ret; -} - -namespace { - -class MyXXH64Checksum : public FileChecksumGenerator { - public: - explicit MyXXH64Checksum(bool big) : big_(big) { - state_ = XXH64_createState(); - XXH64_reset(state_, 0); - } - - virtual ~MyXXH64Checksum() override { XXH64_freeState(state_); } - - void Update(const char* data, size_t n) override { - XXH64_update(state_, data, n); - } - - void Finalize() override { - assert(str_.empty()); - uint64_t digest = XXH64_digest(state_); - // Store as little endian raw bytes - PutFixed64(&str_, digest); - if (big_) { - // Throw in some more data for stress testing (448 bits total) - PutFixed64(&str_, GetSliceHash64(str_)); - PutFixed64(&str_, GetSliceHash64(str_)); - PutFixed64(&str_, GetSliceHash64(str_)); - PutFixed64(&str_, GetSliceHash64(str_)); - PutFixed64(&str_, GetSliceHash64(str_)); - PutFixed64(&str_, GetSliceHash64(str_)); - } - } - - std::string GetChecksum() const override { - assert(!str_.empty()); - return str_; - } - - const char* Name() const override { - return big_ ? "MyBigChecksum" : "MyXXH64Checksum"; - } - - private: - bool big_; - XXH64_state_t* state_; - std::string str_; -}; - -class DbStressChecksumGenFactory : public FileChecksumGenFactory { - std::string default_func_name_; - - std::unique_ptr CreateFromFuncName( - const std::string& func_name) { - std::unique_ptr rv; - if (func_name == "FileChecksumCrc32c") { - rv.reset(new FileChecksumGenCrc32c(FileChecksumGenContext())); - } else if (func_name == "MyXXH64Checksum") { - rv.reset(new MyXXH64Checksum(false /* big */)); - } else if (func_name == "MyBigChecksum") { - rv.reset(new MyXXH64Checksum(true /* big */)); - } else { - // Should be a recognized function when we get here - assert(false); - } - return rv; - } - - public: - explicit DbStressChecksumGenFactory(const std::string& default_func_name) - : default_func_name_(default_func_name) {} - - std::unique_ptr CreateFileChecksumGenerator( - const FileChecksumGenContext& context) override { - if (context.requested_checksum_func_name.empty()) { - return CreateFromFuncName(default_func_name_); - } else { - return CreateFromFuncName(context.requested_checksum_func_name); - } - } - - const char* Name() const override { return "FileChecksumGenCrc32cFactory"; } -}; - -} // namespace - -std::shared_ptr GetFileChecksumImpl( - const std::string& name) { - // Translate from friendly names to internal names - std::string internal_name; - if (name == "crc32c") { - internal_name = "FileChecksumCrc32c"; - } else if (name == "xxh64") { - internal_name = "MyXXH64Checksum"; - } else if (name == "big") { - internal_name = "MyBigChecksum"; - } else { - assert(name.empty() || name == "none"); - return nullptr; - } - return std::make_shared(internal_name); -} - -Status DeleteFilesInDirectory(const std::string& dirname) { - std::vector filenames; - Status s = Env::Default()->GetChildren(dirname, &filenames); - for (size_t i = 0; s.ok() && i < filenames.size(); ++i) { - s = Env::Default()->DeleteFile(dirname + "/" + filenames[i]); - } - return s; -} - -Status SaveFilesInDirectory(const std::string& src_dirname, - const std::string& dst_dirname) { - std::vector filenames; - Status s = Env::Default()->GetChildren(src_dirname, &filenames); - for (size_t i = 0; s.ok() && i < filenames.size(); ++i) { - bool is_dir = false; - s = Env::Default()->IsDirectory(src_dirname + "/" + filenames[i], &is_dir); - if (s.ok()) { - if (is_dir) { - continue; - } - s = Env::Default()->LinkFile(src_dirname + "/" + filenames[i], - dst_dirname + "/" + filenames[i]); - } - } - return s; -} - -Status InitUnverifiedSubdir(const std::string& dirname) { - Status s = Env::Default()->FileExists(dirname); - if (s.IsNotFound()) { - return Status::OK(); - } - - const std::string kUnverifiedDirname = dirname + "/unverified"; - if (s.ok()) { - s = Env::Default()->CreateDirIfMissing(kUnverifiedDirname); - } - if (s.ok()) { - // It might already exist with some stale contents. Delete any such - // contents. - s = DeleteFilesInDirectory(kUnverifiedDirname); - } - if (s.ok()) { - s = SaveFilesInDirectory(dirname, kUnverifiedDirname); - } - return s; -} - -Status DestroyUnverifiedSubdir(const std::string& dirname) { - Status s = Env::Default()->FileExists(dirname); - if (s.IsNotFound()) { - return Status::OK(); - } - - const std::string kUnverifiedDirname = dirname + "/unverified"; - if (s.ok()) { - s = Env::Default()->FileExists(kUnverifiedDirname); - } - if (s.IsNotFound()) { - return Status::OK(); - } - - if (s.ok()) { - s = DeleteFilesInDirectory(kUnverifiedDirname); - } - if (s.ok()) { - s = Env::Default()->DeleteDir(kUnverifiedDirname); - } - return s; -} - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_common.h b/db_stress_tool/db_stress_common.h deleted file mode 100644 index 062b6b98c..000000000 --- a/db_stress_tool/db_stress_common.h +++ /dev/null @@ -1,670 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// -// The test uses an array to compare against values written to the database. -// Keys written to the array are in 1:1 correspondence to the actual values in -// the database according to the formula in the function GenerateValue. - -// Space is reserved in the array from 0 to FLAGS_max_key and values are -// randomly written/deleted/read from those positions. During verification we -// compare all the positions in the array. To shorten/elongate the running -// time, you could change the settings: FLAGS_max_key, FLAGS_ops_per_thread, -// (sometimes also FLAGS_threads). -// -// NOTE that if FLAGS_test_batches_snapshots is set, the test will have -// different behavior. See comment of the flag for details. - -#ifdef GFLAGS -#pragma once -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "db/db_impl/db_impl.h" -#include "db/version_set.h" -#include "db_stress_tool/db_stress_env_wrapper.h" -#include "db_stress_tool/db_stress_listener.h" -#include "db_stress_tool/db_stress_shared_state.h" -#include "db_stress_tool/db_stress_test_base.h" -#include "logging/logging.h" -#include "monitoring/histogram.h" -#include "options/options_helper.h" -#include "port/port.h" -#include "rocksdb/cache.h" -#include "rocksdb/env.h" -#include "rocksdb/slice.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/statistics.h" -#include "rocksdb/utilities/backup_engine.h" -#include "rocksdb/utilities/checkpoint.h" -#include "rocksdb/utilities/db_ttl.h" -#include "rocksdb/utilities/debug.h" -#include "rocksdb/utilities/options_util.h" -#include "rocksdb/utilities/transaction.h" -#include "rocksdb/utilities/transaction_db.h" -#include "rocksdb/write_batch.h" -#include "test_util/testutil.h" -#include "util/coding.h" -#include "util/compression.h" -#include "util/crc32c.h" -#include "util/gflags_compat.h" -#include "util/mutexlock.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/blob_db/blob_db.h" -#include "utilities/fault_injection_fs.h" -#include "utilities/merge_operators.h" - -using GFLAGS_NAMESPACE::ParseCommandLineFlags; -using GFLAGS_NAMESPACE::RegisterFlagValidator; -using GFLAGS_NAMESPACE::SetUsageMessage; - -DECLARE_uint64(seed); -DECLARE_bool(read_only); -DECLARE_int64(max_key); -DECLARE_double(hot_key_alpha); -DECLARE_int32(max_key_len); -DECLARE_string(key_len_percent_dist); -DECLARE_int32(key_window_scale_factor); -DECLARE_int32(column_families); -DECLARE_string(options_file); -DECLARE_int64(active_width); -DECLARE_bool(test_batches_snapshots); -DECLARE_bool(atomic_flush); -DECLARE_int32(manual_wal_flush_one_in); -DECLARE_int32(lock_wal_one_in); -DECLARE_bool(test_cf_consistency); -DECLARE_bool(test_multi_ops_txns); -DECLARE_int32(threads); -DECLARE_int32(ttl); -DECLARE_int32(value_size_mult); -DECLARE_int32(compaction_readahead_size); -DECLARE_bool(enable_pipelined_write); -DECLARE_bool(verify_before_write); -DECLARE_bool(histogram); -DECLARE_bool(destroy_db_initially); -DECLARE_bool(verbose); -DECLARE_bool(progress_reports); -DECLARE_uint64(db_write_buffer_size); -DECLARE_int32(write_buffer_size); -DECLARE_int32(max_write_buffer_number); -DECLARE_int32(min_write_buffer_number_to_merge); -DECLARE_int32(max_write_buffer_number_to_maintain); -DECLARE_int64(max_write_buffer_size_to_maintain); -DECLARE_double(memtable_prefix_bloom_size_ratio); -DECLARE_bool(memtable_whole_key_filtering); -DECLARE_int32(open_files); -DECLARE_int64(compressed_cache_size); -DECLARE_int32(compressed_cache_numshardbits); -DECLARE_int32(compaction_style); -DECLARE_int32(compaction_pri); -DECLARE_int32(num_levels); -DECLARE_int32(level0_file_num_compaction_trigger); -DECLARE_int32(level0_slowdown_writes_trigger); -DECLARE_int32(level0_stop_writes_trigger); -DECLARE_int32(block_size); -DECLARE_int32(format_version); -DECLARE_int32(index_block_restart_interval); -DECLARE_bool(disable_auto_compactions); -DECLARE_int32(max_background_compactions); -DECLARE_int32(num_bottom_pri_threads); -DECLARE_int32(compaction_thread_pool_adjust_interval); -DECLARE_int32(compaction_thread_pool_variations); -DECLARE_int32(max_background_flushes); -DECLARE_int32(universal_size_ratio); -DECLARE_int32(universal_min_merge_width); -DECLARE_int32(universal_max_merge_width); -DECLARE_int32(universal_max_size_amplification_percent); -DECLARE_int32(clear_column_family_one_in); -DECLARE_int32(get_live_files_one_in); -DECLARE_int32(get_sorted_wal_files_one_in); -DECLARE_int32(get_current_wal_file_one_in); -DECLARE_int32(set_options_one_in); -DECLARE_int32(set_in_place_one_in); -DECLARE_int64(cache_size); -DECLARE_int32(cache_numshardbits); -DECLARE_bool(cache_index_and_filter_blocks); -DECLARE_bool(charge_compression_dictionary_building_buffer); -DECLARE_bool(charge_filter_construction); -DECLARE_bool(charge_table_reader); -DECLARE_bool(charge_file_metadata); -DECLARE_bool(charge_blob_cache); -DECLARE_int32(top_level_index_pinning); -DECLARE_int32(partition_pinning); -DECLARE_int32(unpartitioned_pinning); -DECLARE_string(cache_type); -DECLARE_uint64(subcompactions); -DECLARE_uint64(periodic_compaction_seconds); -DECLARE_uint64(compaction_ttl); -DECLARE_bool(fifo_allow_compaction); -DECLARE_bool(allow_concurrent_memtable_write); -DECLARE_double(experimental_mempurge_threshold); -DECLARE_bool(enable_write_thread_adaptive_yield); -DECLARE_int32(reopen); -DECLARE_double(bloom_bits); -DECLARE_int32(ribbon_starting_level); -DECLARE_bool(partition_filters); -DECLARE_bool(optimize_filters_for_memory); -DECLARE_bool(detect_filter_construct_corruption); -DECLARE_int32(index_type); -DECLARE_int32(data_block_index_type); -DECLARE_string(db); -DECLARE_string(secondaries_base); -DECLARE_bool(test_secondary); -DECLARE_string(expected_values_dir); -DECLARE_bool(verify_checksum); -DECLARE_bool(mmap_read); -DECLARE_bool(mmap_write); -DECLARE_bool(use_direct_reads); -DECLARE_bool(use_direct_io_for_flush_and_compaction); -DECLARE_bool(mock_direct_io); -DECLARE_bool(statistics); -DECLARE_bool(sync); -DECLARE_bool(use_fsync); -DECLARE_uint64(stats_dump_period_sec); -DECLARE_uint64(bytes_per_sync); -DECLARE_uint64(wal_bytes_per_sync); -DECLARE_int32(kill_random_test); -DECLARE_string(kill_exclude_prefixes); -DECLARE_bool(disable_wal); -DECLARE_uint64(recycle_log_file_num); -DECLARE_int64(target_file_size_base); -DECLARE_int32(target_file_size_multiplier); -DECLARE_uint64(max_bytes_for_level_base); -DECLARE_double(max_bytes_for_level_multiplier); -DECLARE_int32(range_deletion_width); -DECLARE_uint64(rate_limiter_bytes_per_sec); -DECLARE_bool(rate_limit_bg_reads); -DECLARE_bool(rate_limit_user_ops); -DECLARE_bool(rate_limit_auto_wal_flush); -DECLARE_uint64(sst_file_manager_bytes_per_sec); -DECLARE_uint64(sst_file_manager_bytes_per_truncate); -DECLARE_bool(use_txn); -DECLARE_uint64(txn_write_policy); -DECLARE_bool(unordered_write); -DECLARE_int32(backup_one_in); -DECLARE_uint64(backup_max_size); -DECLARE_int32(checkpoint_one_in); -DECLARE_int32(ingest_external_file_one_in); -DECLARE_int32(ingest_external_file_width); -DECLARE_int32(compact_files_one_in); -DECLARE_int32(compact_range_one_in); -DECLARE_int32(mark_for_compaction_one_file_in); -DECLARE_int32(flush_one_in); -DECLARE_int32(pause_background_one_in); -DECLARE_int32(compact_range_width); -DECLARE_int32(acquire_snapshot_one_in); -DECLARE_bool(compare_full_db_state_snapshot); -DECLARE_uint64(snapshot_hold_ops); -DECLARE_bool(long_running_snapshots); -DECLARE_bool(use_multiget); -DECLARE_bool(use_get_entity); -DECLARE_int32(readpercent); -DECLARE_int32(prefixpercent); -DECLARE_int32(writepercent); -DECLARE_int32(delpercent); -DECLARE_int32(delrangepercent); -DECLARE_int32(nooverwritepercent); -DECLARE_int32(iterpercent); -DECLARE_uint64(num_iterations); -DECLARE_int32(customopspercent); -DECLARE_string(compression_type); -DECLARE_string(bottommost_compression_type); -DECLARE_int32(compression_max_dict_bytes); -DECLARE_int32(compression_zstd_max_train_bytes); -DECLARE_int32(compression_parallel_threads); -DECLARE_uint64(compression_max_dict_buffer_bytes); -DECLARE_bool(compression_use_zstd_dict_trainer); -DECLARE_string(checksum_type); -DECLARE_string(env_uri); -DECLARE_string(fs_uri); -DECLARE_uint64(ops_per_thread); -DECLARE_uint64(log2_keys_per_lock); -DECLARE_uint64(max_manifest_file_size); -DECLARE_bool(in_place_update); -DECLARE_string(memtablerep); -DECLARE_int32(prefix_size); -DECLARE_bool(use_merge); -DECLARE_uint32(use_put_entity_one_in); -DECLARE_bool(use_full_merge_v1); -DECLARE_int32(sync_wal_one_in); -DECLARE_bool(avoid_unnecessary_blocking_io); -DECLARE_bool(write_dbid_to_manifest); -DECLARE_bool(avoid_flush_during_recovery); -DECLARE_uint64(max_write_batch_group_size_bytes); -DECLARE_bool(level_compaction_dynamic_level_bytes); -DECLARE_int32(verify_checksum_one_in); -DECLARE_int32(verify_db_one_in); -DECLARE_int32(continuous_verification_interval); -DECLARE_int32(get_property_one_in); -DECLARE_string(file_checksum_impl); - -// Options for StackableDB-based BlobDB -DECLARE_bool(use_blob_db); -DECLARE_uint64(blob_db_min_blob_size); -DECLARE_uint64(blob_db_bytes_per_sync); -DECLARE_uint64(blob_db_file_size); -DECLARE_bool(blob_db_enable_gc); -DECLARE_double(blob_db_gc_cutoff); - -// Options for integrated BlobDB -DECLARE_bool(allow_setting_blob_options_dynamically); -DECLARE_bool(enable_blob_files); -DECLARE_uint64(min_blob_size); -DECLARE_uint64(blob_file_size); -DECLARE_string(blob_compression_type); -DECLARE_bool(enable_blob_garbage_collection); -DECLARE_double(blob_garbage_collection_age_cutoff); -DECLARE_double(blob_garbage_collection_force_threshold); -DECLARE_uint64(blob_compaction_readahead_size); -DECLARE_int32(blob_file_starting_level); -DECLARE_bool(use_blob_cache); -DECLARE_bool(use_shared_block_and_blob_cache); -DECLARE_uint64(blob_cache_size); -DECLARE_int32(blob_cache_numshardbits); -DECLARE_int32(prepopulate_blob_cache); - -DECLARE_int32(approximate_size_one_in); -DECLARE_bool(sync_fault_injection); - -DECLARE_bool(best_efforts_recovery); -DECLARE_bool(skip_verifydb); -DECLARE_bool(enable_compaction_filter); -DECLARE_bool(paranoid_file_checks); -DECLARE_bool(fail_if_options_file_error); -DECLARE_uint64(batch_protection_bytes_per_key); -DECLARE_uint32(memtable_protection_bytes_per_key); - -DECLARE_uint64(user_timestamp_size); -DECLARE_string(secondary_cache_uri); -DECLARE_int32(secondary_cache_fault_one_in); - -DECLARE_int32(prepopulate_block_cache); - -DECLARE_bool(two_write_queues); -DECLARE_bool(use_only_the_last_commit_time_batch_for_recovery); -DECLARE_uint64(wp_snapshot_cache_bits); -DECLARE_uint64(wp_commit_cache_bits); - -DECLARE_bool(adaptive_readahead); -DECLARE_bool(async_io); -DECLARE_string(wal_compression); -DECLARE_bool(verify_sst_unique_id_in_manifest); - -DECLARE_int32(create_timestamped_snapshot_one_in); - -DECLARE_bool(allow_data_in_errors); - -// Tiered storage -DECLARE_bool(enable_tiered_storage); // set last_level_temperature -DECLARE_int64(preclude_last_level_data_seconds); -DECLARE_int64(preserve_internal_time_seconds); - -DECLARE_int32(verify_iterator_with_expected_state_one_in); -DECLARE_bool(preserve_unverified_changes); - -DECLARE_uint64(readahead_size); -DECLARE_uint64(initial_auto_readahead_size); -DECLARE_uint64(max_auto_readahead_size); -DECLARE_uint64(num_file_reads_for_auto_readahead); -DECLARE_bool(use_io_uring); - -constexpr long KB = 1024; -constexpr int kRandomValueMaxFactor = 3; -constexpr int kValueMaxLen = 100; - -// wrapped posix environment -extern ROCKSDB_NAMESPACE::Env* db_stress_env; -extern ROCKSDB_NAMESPACE::Env* db_stress_listener_env; -extern std::shared_ptr fault_fs_guard; - -extern enum ROCKSDB_NAMESPACE::CompressionType compression_type_e; -extern enum ROCKSDB_NAMESPACE::CompressionType bottommost_compression_type_e; -extern enum ROCKSDB_NAMESPACE::ChecksumType checksum_type_e; - -enum RepFactory { kSkipList, kHashSkipList, kVectorRep }; - -inline enum RepFactory StringToRepFactory(const char* ctype) { - assert(ctype); - - if (!strcasecmp(ctype, "skip_list")) - return kSkipList; - else if (!strcasecmp(ctype, "prefix_hash")) - return kHashSkipList; - else if (!strcasecmp(ctype, "vector")) - return kVectorRep; - - fprintf(stdout, "Cannot parse memreptable %s\n", ctype); - return kSkipList; -} - -extern enum RepFactory FLAGS_rep_factory; - -namespace ROCKSDB_NAMESPACE { -inline enum ROCKSDB_NAMESPACE::CompressionType StringToCompressionType( - const char* ctype) { - assert(ctype); - - ROCKSDB_NAMESPACE::CompressionType ret_compression_type; - - if (!strcasecmp(ctype, "disable")) { - ret_compression_type = ROCKSDB_NAMESPACE::kDisableCompressionOption; - } else if (!strcasecmp(ctype, "none")) { - ret_compression_type = ROCKSDB_NAMESPACE::kNoCompression; - } else if (!strcasecmp(ctype, "snappy")) { - ret_compression_type = ROCKSDB_NAMESPACE::kSnappyCompression; - } else if (!strcasecmp(ctype, "zlib")) { - ret_compression_type = ROCKSDB_NAMESPACE::kZlibCompression; - } else if (!strcasecmp(ctype, "bzip2")) { - ret_compression_type = ROCKSDB_NAMESPACE::kBZip2Compression; - } else if (!strcasecmp(ctype, "lz4")) { - ret_compression_type = ROCKSDB_NAMESPACE::kLZ4Compression; - } else if (!strcasecmp(ctype, "lz4hc")) { - ret_compression_type = ROCKSDB_NAMESPACE::kLZ4HCCompression; - } else if (!strcasecmp(ctype, "xpress")) { - ret_compression_type = ROCKSDB_NAMESPACE::kXpressCompression; - } else if (!strcasecmp(ctype, "zstd")) { - ret_compression_type = ROCKSDB_NAMESPACE::kZSTD; - } else { - fprintf(stderr, "Cannot parse compression type '%s'\n", ctype); - ret_compression_type = - ROCKSDB_NAMESPACE::kSnappyCompression; // default value - } - if (ret_compression_type != ROCKSDB_NAMESPACE::kDisableCompressionOption && - !CompressionTypeSupported(ret_compression_type)) { - // Use no compression will be more portable but considering this is - // only a stress test and snappy is widely available. Use snappy here. - ret_compression_type = ROCKSDB_NAMESPACE::kSnappyCompression; - } - return ret_compression_type; -} - -inline enum ROCKSDB_NAMESPACE::ChecksumType StringToChecksumType( - const char* ctype) { - assert(ctype); - auto iter = ROCKSDB_NAMESPACE::checksum_type_string_map.find(ctype); - if (iter != ROCKSDB_NAMESPACE::checksum_type_string_map.end()) { - return iter->second; - } - fprintf(stderr, "Cannot parse checksum type '%s'\n", ctype); - return ROCKSDB_NAMESPACE::kCRC32c; -} - -inline std::string ChecksumTypeToString(ROCKSDB_NAMESPACE::ChecksumType ctype) { - auto iter = std::find_if( - ROCKSDB_NAMESPACE::checksum_type_string_map.begin(), - ROCKSDB_NAMESPACE::checksum_type_string_map.end(), - [&](const std::pair& - name_and_enum_val) { return name_and_enum_val.second == ctype; }); - assert(iter != ROCKSDB_NAMESPACE::checksum_type_string_map.end()); - return iter->first; -} - -inline std::vector SplitString(std::string src) { - std::vector ret; - if (src.empty()) { - return ret; - } - size_t pos = 0; - size_t pos_comma; - while ((pos_comma = src.find(',', pos)) != std::string::npos) { - ret.push_back(src.substr(pos, pos_comma - pos)); - pos = pos_comma + 1; - } - ret.push_back(src.substr(pos, src.length())); - return ret; -} - -#ifdef _MSC_VER -#pragma warning(push) -// truncation of constant value on static_cast -#pragma warning(disable : 4309) -#endif -inline bool GetNextPrefix(const ROCKSDB_NAMESPACE::Slice& src, std::string* v) { - std::string ret = src.ToString(); - for (int i = static_cast(ret.size()) - 1; i >= 0; i--) { - if (ret[i] != static_cast(255)) { - ret[i] = ret[i] + 1; - break; - } else if (i != 0) { - ret[i] = 0; - } else { - // all FF. No next prefix - return false; - } - } - *v = ret; - return true; -} -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// Append `val` to `*key` in fixed-width big-endian format -extern inline void AppendIntToString(uint64_t val, std::string* key) { - // PutFixed64 uses little endian - PutFixed64(key, val); - // Reverse to get big endian - char* int_data = &((*key)[key->size() - sizeof(uint64_t)]); - for (size_t i = 0; i < sizeof(uint64_t) / 2; ++i) { - std::swap(int_data[i], int_data[sizeof(uint64_t) - 1 - i]); - } -} - -// A struct for maintaining the parameters for generating variable length keys -struct KeyGenContext { - // Number of adjacent keys in one cycle of key lengths - uint64_t window; - // Number of keys of each possible length in a given window - std::vector weights; -}; -extern KeyGenContext key_gen_ctx; - -// Generate a variable length key string from the given int64 val. The -// order of the keys is preserved. The key could be anywhere from 8 to -// max_key_len * 8 bytes. -// The algorithm picks the length based on the -// offset of the val within a configured window and the distribution of the -// number of keys of various lengths in that window. For example, if x, y, x are -// the weights assigned to each possible key length, the keys generated would be -// - {0}...{x-1} -// {(x-1),0}..{(x-1),(y-1)},{(x-1),(y-1),0}..{(x-1),(y-1),(z-1)} and so on. -// Additionally, a trailer of 0-7 bytes could be appended. -extern inline std::string Key(int64_t val) { - uint64_t window = key_gen_ctx.window; - size_t levels = key_gen_ctx.weights.size(); - std::string key; - // Over-reserve and for now do not bother `shrink_to_fit()` since the key - // strings are transient. - key.reserve(FLAGS_max_key_len * 8); - - uint64_t window_idx = static_cast(val) / window; - uint64_t offset = static_cast(val) % window; - for (size_t level = 0; level < levels; ++level) { - uint64_t weight = key_gen_ctx.weights[level]; - uint64_t pfx; - if (level == 0) { - pfx = window_idx * weight; - } else { - pfx = 0; - } - pfx += offset >= weight ? weight - 1 : offset; - AppendIntToString(pfx, &key); - if (offset < weight) { - // Use the bottom 3 bits of offset as the number of trailing 'x's in the - // key. If the next key is going to be of the next level, then skip the - // trailer as it would break ordering. If the key length is already at - // max, skip the trailer. - if (offset < weight - 1 && level < levels - 1) { - size_t trailer_len = offset & 0x7; - key.append(trailer_len, 'x'); - } - break; - } - offset -= weight; - } - - return key; -} - -// Given a string key, map it to an index into the expected values buffer -extern inline bool GetIntVal(std::string big_endian_key, uint64_t* key_p) { - size_t size_key = big_endian_key.size(); - std::vector prefixes; - - assert(size_key <= key_gen_ctx.weights.size() * sizeof(uint64_t)); - - std::string little_endian_key; - little_endian_key.resize(size_key); - for (size_t start = 0; start + sizeof(uint64_t) <= size_key; - start += sizeof(uint64_t)) { - size_t end = start + sizeof(uint64_t); - for (size_t i = 0; i < sizeof(uint64_t); ++i) { - little_endian_key[start + i] = big_endian_key[end - 1 - i]; - } - Slice little_endian_slice = - Slice(&little_endian_key[start], sizeof(uint64_t)); - uint64_t pfx; - if (!GetFixed64(&little_endian_slice, &pfx)) { - return false; - } - prefixes.emplace_back(pfx); - } - - uint64_t key = 0; - for (size_t i = 0; i < prefixes.size(); ++i) { - uint64_t pfx = prefixes[i]; - key += (pfx / key_gen_ctx.weights[i]) * key_gen_ctx.window + - pfx % key_gen_ctx.weights[i]; - if (i < prefixes.size() - 1) { - // The encoding writes a `key_gen_ctx.weights[i] - 1` that counts for - // `key_gen_ctx.weights[i]` when there are more prefixes to come. So we - // need to add back the one here as we're at a non-last prefix. - ++key; - } - } - *key_p = key; - return true; -} - -// Given a string prefix, map it to the first corresponding index in the -// expected values buffer. -inline bool GetFirstIntValInPrefix(std::string big_endian_prefix, - uint64_t* key_p) { - size_t size_key = big_endian_prefix.size(); - // Pad with zeros to make it a multiple of 8. This function may be called - // with a prefix, in which case we return the first index that falls - // inside or outside that prefix, dependeing on whether the prefix is - // the start of upper bound of a scan - unsigned int pad = sizeof(uint64_t) - (size_key % sizeof(uint64_t)); - if (pad < sizeof(uint64_t)) { - big_endian_prefix.append(pad, '\0'); - } - return GetIntVal(std::move(big_endian_prefix), key_p); -} - -extern inline uint64_t GetPrefixKeyCount(const std::string& prefix, - const std::string& ub) { - uint64_t start = 0; - uint64_t end = 0; - - if (!GetFirstIntValInPrefix(prefix, &start) || - !GetFirstIntValInPrefix(ub, &end)) { - return 0; - } - - return end - start; -} - -extern inline std::string StringToHex(const std::string& str) { - std::string result = "0x"; - result.append(Slice(str).ToString(true)); - return result; -} - -inline std::string WideColumnsToHex(const WideColumns& columns) { - if (columns.empty()) { - return std::string(); - } - - std::ostringstream oss; - - oss << std::hex; - - auto it = columns.begin(); - oss << *it; - for (++it; it != columns.end(); ++it) { - oss << ' ' << *it; - } - - return oss.str(); -} - -// Unified output format for double parameters -extern inline std::string FormatDoubleParam(double param) { - return std::to_string(param); -} - -// Make sure that double parameter is a value we can reproduce by -// re-inputting the value printed. -extern inline void SanitizeDoubleParam(double* param) { - *param = std::atof(FormatDoubleParam(*param).c_str()); -} - -extern void PoolSizeChangeThread(void* v); - -extern void DbVerificationThread(void* v); - -extern void TimestampedSnapshotsThread(void* v); - -extern void PrintKeyValue(int cf, uint64_t key, const char* value, size_t sz); - -extern int64_t GenerateOneKey(ThreadState* thread, uint64_t iteration); - -extern std::vector GenerateNKeys(ThreadState* thread, int num_keys, - uint64_t iteration); - -extern size_t GenerateValue(uint32_t rand, char* v, size_t max_sz); -extern uint32_t GetValueBase(Slice s); - -extern WideColumns GenerateWideColumns(uint32_t value_base, const Slice& slice); -extern WideColumns GenerateExpectedWideColumns(uint32_t value_base, - const Slice& slice); -extern bool VerifyWideColumns(const Slice& value, const WideColumns& columns); -extern bool VerifyWideColumns(const WideColumns& columns); - -extern StressTest* CreateCfConsistencyStressTest(); -extern StressTest* CreateBatchedOpsStressTest(); -extern StressTest* CreateNonBatchedOpsStressTest(); -extern StressTest* CreateMultiOpsTxnsStressTest(); -extern void CheckAndSetOptionsForMultiOpsTxnStressTest(); -extern void InitializeHotKeyGenerator(double alpha); -extern int64_t GetOneHotKeyID(double rand_seed, int64_t max_key); - -extern std::string GetNowNanos(); - -std::shared_ptr GetFileChecksumImpl( - const std::string& name); - -Status DeleteFilesInDirectory(const std::string& dirname); -Status SaveFilesInDirectory(const std::string& src_dirname, - const std::string& dst_dirname); -Status DestroyUnverifiedSubdir(const std::string& dirname); -Status InitUnverifiedSubdir(const std::string& dirname); -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_compaction_filter.h b/db_stress_tool/db_stress_compaction_filter.h deleted file mode 100644 index 408bb48f3..000000000 --- a/db_stress_tool/db_stress_compaction_filter.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#pragma once - -#include "db_stress_tool/db_stress_common.h" -#include "db_stress_tool/db_stress_shared_state.h" -#include "rocksdb/compaction_filter.h" - -namespace ROCKSDB_NAMESPACE { - -// DbStressCompactionFilter is safe to use with db_stress as it does not perform -// any mutation. It only makes `kRemove` decisions for keys that are already -// non-existent according to the `SharedState`. -class DbStressCompactionFilter : public CompactionFilter { - public: - DbStressCompactionFilter(SharedState* state, int cf_id) - : state_(state), cf_id_(cf_id) {} - - Decision FilterV2(int /*level*/, const Slice& key, ValueType /*value_type*/, - const Slice& /*existing_value*/, std::string* /*new_value*/, - std::string* /*skip_until*/) const override { - if (state_ == nullptr) { - return Decision::kKeep; - } - if (key.empty() || ('0' <= key[0] && key[0] <= '9')) { - // It is likely leftover from a test_batches_snapshots run. Below this - // conditional, the test_batches_snapshots key format is not handled - // properly. Just keep it to be safe. - return Decision::kKeep; - } - uint64_t key_num = 0; - { - Slice ukey_without_ts = key; - assert(ukey_without_ts.size() >= FLAGS_user_timestamp_size); - ukey_without_ts.remove_suffix(FLAGS_user_timestamp_size); - [[maybe_unused]] bool ok = - GetIntVal(ukey_without_ts.ToString(), &key_num); - assert(ok); - } - port::Mutex* key_mutex = state_->GetMutexForKey(cf_id_, key_num); - if (!key_mutex->TryLock()) { - return Decision::kKeep; - } - // Reaching here means we acquired the lock. - - bool key_exists = state_->Exists(cf_id_, key_num); - const bool allow_overwrite = state_->AllowsOverwrite(key_num); - - key_mutex->Unlock(); - - if (!key_exists) { - return allow_overwrite ? Decision::kRemove : Decision::kPurge; - } - return Decision::kKeep; - } - - const char* Name() const override { return "DbStressCompactionFilter"; } - - private: - SharedState* const state_; - const int cf_id_; -}; - -class DbStressCompactionFilterFactory : public CompactionFilterFactory { - public: - DbStressCompactionFilterFactory() : state_(nullptr) {} - - void SetSharedState(SharedState* state) { - MutexLock state_mutex_guard(&state_mutex_); - state_ = state; - } - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { - MutexLock state_mutex_guard(&state_mutex_); - return std::unique_ptr( - new DbStressCompactionFilter(state_, context.column_family_id)); - } - - const char* Name() const override { - return "DbStressCompactionFilterFactory"; - } - - private: - port::Mutex state_mutex_; - SharedState* state_; -}; - -} // namespace ROCKSDB_NAMESPACE diff --git a/db_stress_tool/db_stress_driver.cc b/db_stress_tool/db_stress_driver.cc deleted file mode 100644 index 2c8dcf610..000000000 --- a/db_stress_tool/db_stress_driver.cc +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { -void ThreadBody(void* v) { - ThreadState* thread = reinterpret_cast(v); - SharedState* shared = thread->shared; - - if (!FLAGS_skip_verifydb && shared->ShouldVerifyAtBeginning()) { - thread->shared->GetStressTest()->VerifyDb(thread); - } - { - MutexLock l(shared->GetMutex()); - shared->IncInitialized(); - if (shared->AllInitialized()) { - shared->GetCondVar()->SignalAll(); - } - while (!shared->Started()) { - shared->GetCondVar()->Wait(); - } - } - thread->shared->GetStressTest()->OperateDb(thread); - - { - MutexLock l(shared->GetMutex()); - shared->IncOperated(); - if (shared->AllOperated()) { - shared->GetCondVar()->SignalAll(); - } - while (!shared->VerifyStarted()) { - shared->GetCondVar()->Wait(); - } - } - - if (!FLAGS_skip_verifydb) { - thread->shared->GetStressTest()->VerifyDb(thread); - } - - { - MutexLock l(shared->GetMutex()); - shared->IncDone(); - if (shared->AllDone()) { - shared->GetCondVar()->SignalAll(); - } - } -} - -bool RunStressTest(SharedState* shared) { - SystemClock* clock = db_stress_env->GetSystemClock().get(); - StressTest* stress = shared->GetStressTest(); - - if (shared->ShouldVerifyAtBeginning() && FLAGS_preserve_unverified_changes) { - Status s = InitUnverifiedSubdir(FLAGS_db); - if (s.ok() && !FLAGS_expected_values_dir.empty()) { - s = InitUnverifiedSubdir(FLAGS_expected_values_dir); - } - if (!s.ok()) { - fprintf(stderr, "Failed to setup unverified state dir: %s\n", - s.ToString().c_str()); - exit(1); - } - } - - stress->InitDb(shared); - stress->FinishInitDb(shared); - - if (FLAGS_sync_fault_injection) { - fault_fs_guard->SetFilesystemDirectWritable(false); - } - if (FLAGS_write_fault_one_in) { - fault_fs_guard->EnableWriteErrorInjection(); - } - - uint32_t n = FLAGS_threads; - uint64_t now = clock->NowMicros(); - fprintf(stdout, "%s Initializing worker threads\n", - clock->TimeToString(now / 1000000).c_str()); - - shared->SetThreads(n); - - if (FLAGS_compaction_thread_pool_adjust_interval > 0) { - shared->IncBgThreads(); - } - - if (FLAGS_continuous_verification_interval > 0) { - shared->IncBgThreads(); - } - - std::vector threads(n); - for (uint32_t i = 0; i < n; i++) { - threads[i] = new ThreadState(i, shared); - db_stress_env->StartThread(ThreadBody, threads[i]); - } - - ThreadState bg_thread(0, shared); - if (FLAGS_compaction_thread_pool_adjust_interval > 0) { - db_stress_env->StartThread(PoolSizeChangeThread, &bg_thread); - } - - ThreadState continuous_verification_thread(0, shared); - if (FLAGS_continuous_verification_interval > 0) { - db_stress_env->StartThread(DbVerificationThread, - &continuous_verification_thread); - } - - // Each thread goes through the following states: - // initializing -> wait for others to init -> read/populate/depopulate - // wait for others to operate -> verify -> done - - { - MutexLock l(shared->GetMutex()); - while (!shared->AllInitialized()) { - shared->GetCondVar()->Wait(); - } - if (shared->ShouldVerifyAtBeginning()) { - if (shared->HasVerificationFailedYet()) { - fprintf(stderr, "Crash-recovery verification failed :(\n"); - } else { - fprintf(stdout, "Crash-recovery verification passed :)\n"); - Status s = DestroyUnverifiedSubdir(FLAGS_db); - if (s.ok() && !FLAGS_expected_values_dir.empty()) { - s = DestroyUnverifiedSubdir(FLAGS_expected_values_dir); - } - if (!s.ok()) { - fprintf(stderr, "Failed to cleanup unverified state dir: %s\n", - s.ToString().c_str()); - exit(1); - } - } - } - - // This is after the verification step to avoid making all those `Get()`s - // and `MultiGet()`s contend on the DB-wide trace mutex. - if (!FLAGS_expected_values_dir.empty()) { - stress->TrackExpectedState(shared); - } - - now = clock->NowMicros(); - fprintf(stdout, "%s Starting database operations\n", - clock->TimeToString(now / 1000000).c_str()); - - shared->SetStart(); - shared->GetCondVar()->SignalAll(); - while (!shared->AllOperated()) { - shared->GetCondVar()->Wait(); - } - - now = clock->NowMicros(); - if (FLAGS_test_batches_snapshots) { - fprintf(stdout, "%s Limited verification already done during gets\n", - clock->TimeToString((uint64_t)now / 1000000).c_str()); - } else if (FLAGS_skip_verifydb) { - fprintf(stdout, "%s Verification skipped\n", - clock->TimeToString((uint64_t)now / 1000000).c_str()); - } else { - fprintf(stdout, "%s Starting verification\n", - clock->TimeToString((uint64_t)now / 1000000).c_str()); - } - - shared->SetStartVerify(); - shared->GetCondVar()->SignalAll(); - while (!shared->AllDone()) { - shared->GetCondVar()->Wait(); - } - } - - for (unsigned int i = 1; i < n; i++) { - threads[0]->stats.Merge(threads[i]->stats); - } - threads[0]->stats.Report("Stress Test"); - - for (unsigned int i = 0; i < n; i++) { - delete threads[i]; - threads[i] = nullptr; - } - now = clock->NowMicros(); - if (!FLAGS_skip_verifydb && !FLAGS_test_batches_snapshots && - !shared->HasVerificationFailedYet()) { - fprintf(stdout, "%s Verification successful\n", - clock->TimeToString(now / 1000000).c_str()); - } - stress->PrintStatistics(); - - if (FLAGS_compaction_thread_pool_adjust_interval > 0 || - FLAGS_continuous_verification_interval > 0) { - MutexLock l(shared->GetMutex()); - shared->SetShouldStopBgThread(); - while (!shared->BgThreadsFinished()) { - shared->GetCondVar()->Wait(); - } - } - - if (shared->HasVerificationFailedYet()) { - fprintf(stderr, "Verification failed :(\n"); - return false; - } - return true; -} -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_driver.h b/db_stress_tool/db_stress_driver.h deleted file mode 100644 index a173470ff..000000000 --- a/db_stress_tool/db_stress_driver.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "db_stress_tool/db_stress_shared_state.h" -#ifdef GFLAGS -#pragma once -#include "db_stress_tool/db_stress_test_base.h" -namespace ROCKSDB_NAMESPACE { -extern void ThreadBody(void* /*thread_state*/); -extern bool RunStressTest(SharedState*); -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_env_wrapper.h b/db_stress_tool/db_stress_env_wrapper.h deleted file mode 100644 index af60df9bc..000000000 --- a/db_stress_tool/db_stress_env_wrapper.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#pragma once -#include "db_stress_tool/db_stress_common.h" - -namespace ROCKSDB_NAMESPACE { -class DbStressFSWrapper : public FileSystemWrapper { - public: - explicit DbStressFSWrapper(const std::shared_ptr& t) - : FileSystemWrapper(t) {} - static const char* kClassName() { return "DbStressFS"; } - const char* Name() const override { return kClassName(); } - - IOStatus DeleteFile(const std::string& f, const IOOptions& opts, - IODebugContext* dbg) override { - // We determine whether it is a manifest file by searching a strong, - // so that there will be false positive if the directory path contains the - // keyword but it is unlikely. - // Checkpoint, backup, and restore directories needs to be exempted. - if (!if_preserve_all_manifests || - f.find("MANIFEST-") == std::string::npos || - f.find("checkpoint") != std::string::npos || - f.find(".backup") != std::string::npos || - f.find(".restore") != std::string::npos) { - return target()->DeleteFile(f, opts, dbg); - } - // Rename the file instead of deletion to keep the history, and - // at the same time it is not visible to RocksDB. - return target()->RenameFile(f, f + "_renamed_", opts, dbg); - } - - // If true, all manifest files will not be delted in DeleteFile(). - bool if_preserve_all_manifests = true; -}; -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_gflags.cc b/db_stress_tool/db_stress_gflags.cc deleted file mode 100644 index d7cf8b10f..000000000 --- a/db_stress_tool/db_stress_gflags.cc +++ /dev/null @@ -1,1081 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" - -static bool ValidateUint32Range(const char* flagname, uint64_t value) { - if (value > std::numeric_limits::max()) { - fprintf(stderr, "Invalid value for --%s: %lu, overflow\n", flagname, - (unsigned long)value); - return false; - } - return true; -} - -DEFINE_uint64(seed, 2341234, - "Seed for PRNG. When --nooverwritepercent is " - "nonzero and --expected_values_dir is nonempty, this value " - "must be fixed across invocations."); -static const bool FLAGS_seed_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_seed, &ValidateUint32Range); - -DEFINE_bool(read_only, false, "True if open DB in read-only mode during tests"); - -DEFINE_int64(max_key, 1 * KB * KB, - "Max number of key/values to place in database"); - -DEFINE_int32(max_key_len, 3, "Maximum length of a key in 8-byte units"); - -DEFINE_string(key_len_percent_dist, "", - "Percentages of keys of various lengths. For example, 1,30,69 " - "means 1% of keys are 8 bytes, 30% are 16 bytes, and 69% are " - "24 bytes. If not specified, it will be evenly distributed"); - -DEFINE_int32(key_window_scale_factor, 10, - "This value will be multiplied by 100 to come up with a window " - "size for varying the key length"); - -DEFINE_int32(column_families, 10, "Number of column families"); - -DEFINE_double( - hot_key_alpha, 0, - "Use Zipfian distribution to generate the key " - "distribution. If it is not specified, write path will use random " - "distribution to generate the keys. The parameter is [0, double_max]). " - "However, the larger alpha is, the more shewed will be. If alpha is " - "larger than 2, it is likely that only 1 key will be accessed. The " - "Recommended value is [0.8-1.5]. The distribution is also related to " - "max_key and total iterations of generating the hot key. "); - -DEFINE_string( - options_file, "", - "The path to a RocksDB options file. If specified, then db_stress will " - "run with the RocksDB options in the default column family of the " - "specified options file. Note that, when an options file is provided, " - "db_stress will ignore the flag values for all options that may be passed " - "via options file."); - -DEFINE_int64( - active_width, 0, - "Number of keys in active span of the key-range at any given time. The " - "span begins with its left endpoint at key 0, gradually moves rightwards, " - "and ends with its right endpoint at max_key. If set to 0, active_width " - "will be sanitized to be equal to max_key."); - -// TODO(noetzli) Add support for single deletes -DEFINE_bool(test_batches_snapshots, false, - "If set, the test uses MultiGet(), MultiPut() and MultiDelete()" - " which read/write/delete multiple keys in a batch. In this mode," - " we do not verify db content by comparing the content with the " - "pre-allocated array. Instead, we do partial verification inside" - " MultiGet() by checking various values in a batch. Benefit of" - " this mode:\n" - "\t(a) No need to acquire mutexes during writes (less cache " - "flushes in multi-core leading to speed up)\n" - "\t(b) No long validation at the end (more speed up)\n" - "\t(c) Test snapshot and atomicity of batch writes"); - -DEFINE_bool(atomic_flush, false, - "If set, enables atomic flush in the options.\n"); - -DEFINE_int32( - manual_wal_flush_one_in, 0, - "If non-zero, then `FlushWAL(bool sync)`, where `bool sync` is randomly " - "decided, will be explictly called in db stress once for every N ops " - "on average. Setting `manual_wal_flush_one_in` to be greater than 0 " - "implies `Options::manual_wal_flush = true` is set."); - -DEFINE_int32(lock_wal_one_in, 1000000, - "If non-zero, then `LockWAL()` + `UnlockWAL()` will be called in " - "db_stress once for every N ops on average."); - -DEFINE_bool(test_cf_consistency, false, - "If set, runs the stress test dedicated to verifying writes to " - "multiple column families are consistent. Setting this implies " - "`atomic_flush=true` is set true if `disable_wal=false`.\n"); - -DEFINE_bool(test_multi_ops_txns, false, - "If set, runs stress test dedicated to verifying multi-ops " - "transactions on a simple relational table with primary and " - "secondary index."); - -DEFINE_int32(threads, 32, "Number of concurrent threads to run."); - -DEFINE_int32(ttl, -1, - "Opens the db with this ttl value if this is not -1. " - "Carefully specify a large value such that verifications on " - "deleted values don't fail"); - -DEFINE_int32(value_size_mult, 8, - "Size of value will be this number times rand_int(1,3) bytes"); - -DEFINE_int32(compaction_readahead_size, 0, "Compaction readahead size"); - -DEFINE_bool(enable_pipelined_write, false, "Pipeline WAL/memtable writes"); - -DEFINE_bool(verify_before_write, false, "Verify before write"); - -DEFINE_bool(histogram, false, "Print histogram of operation timings"); - -DEFINE_bool(destroy_db_initially, true, - "Destroys the database dir before start if this is true"); - -DEFINE_bool(verbose, false, "Verbose"); - -DEFINE_bool(progress_reports, true, - "If true, db_stress will report number of finished operations"); - -DEFINE_uint64(db_write_buffer_size, - ROCKSDB_NAMESPACE::Options().db_write_buffer_size, - "Number of bytes to buffer in all memtables before compacting"); - -DEFINE_int32( - write_buffer_size, - static_cast(ROCKSDB_NAMESPACE::Options().write_buffer_size), - "Number of bytes to buffer in memtable before compacting"); - -DEFINE_int32(max_write_buffer_number, - ROCKSDB_NAMESPACE::Options().max_write_buffer_number, - "The number of in-memory memtables. " - "Each memtable is of size FLAGS_write_buffer_size."); - -DEFINE_int32(min_write_buffer_number_to_merge, - ROCKSDB_NAMESPACE::Options().min_write_buffer_number_to_merge, - "The minimum number of write buffers that will be merged together " - "before writing to storage. This is cheap because it is an " - "in-memory merge. If this feature is not enabled, then all these " - "write buffers are flushed to L0 as separate files and this " - "increases read amplification because a get request has to check " - "in all of these files. Also, an in-memory merge may result in " - "writing less data to storage if there are duplicate records in" - " each of these individual write buffers."); - -DEFINE_int32(max_write_buffer_number_to_maintain, - ROCKSDB_NAMESPACE::Options().max_write_buffer_number_to_maintain, - "The total maximum number of write buffers to maintain in memory " - "including copies of buffers that have already been flushed. " - "Unlike max_write_buffer_number, this parameter does not affect " - "flushing. This controls the minimum amount of write history " - "that will be available in memory for conflict checking when " - "Transactions are used. If this value is too low, some " - "transactions may fail at commit time due to not being able to " - "determine whether there were any write conflicts. Setting this " - "value to 0 will cause write buffers to be freed immediately " - "after they are flushed. If this value is set to -1, " - "'max_write_buffer_number' will be used."); - -DEFINE_int64(max_write_buffer_size_to_maintain, - ROCKSDB_NAMESPACE::Options().max_write_buffer_size_to_maintain, - "The total maximum size of write buffers to maintain in memory " - "including copies of buffers that have already been flushed. " - "Unlike max_write_buffer_number, this parameter does not affect " - "flushing. This controls the minimum amount of write history " - "that will be available in memory for conflict checking when " - "Transactions are used. If this value is too low, some " - "transactions may fail at commit time due to not being able to " - "determine whether there were any write conflicts. Setting this " - "value to 0 will cause write buffers to be freed immediately " - "after they are flushed. If this value is set to -1, " - "'max_write_buffer_number' will be used."); - -DEFINE_double(memtable_prefix_bloom_size_ratio, - ROCKSDB_NAMESPACE::Options().memtable_prefix_bloom_size_ratio, - "creates prefix blooms for memtables, each with size " - "`write_buffer_size * memtable_prefix_bloom_size_ratio`."); - -DEFINE_bool(memtable_whole_key_filtering, - ROCKSDB_NAMESPACE::Options().memtable_whole_key_filtering, - "Enable whole key filtering in memtables."); - -DEFINE_int32(open_files, ROCKSDB_NAMESPACE::Options().max_open_files, - "Maximum number of files to keep open at the same time " - "(use default if == 0)"); - -DEFINE_int64(compressed_cache_size, 0, - "Number of bytes to use as a cache of compressed data." - " 0 means use default settings."); - -DEFINE_int32( - compressed_cache_numshardbits, -1, - "Number of shards for the compressed block cache is 2 ** " - "compressed_cache_numshardbits. Negative value means default settings. " - "This is applied only if compressed_cache_size is greater than 0."); - -DEFINE_int32(compaction_style, ROCKSDB_NAMESPACE::Options().compaction_style, - ""); - -DEFINE_int32(compaction_pri, ROCKSDB_NAMESPACE::Options().compaction_pri, - "Which file from a level should be picked to merge to the next " - "level in level-based compaction"); - -DEFINE_int32(num_levels, ROCKSDB_NAMESPACE::Options().num_levels, - "Number of levels in the DB"); - -DEFINE_int32(level0_file_num_compaction_trigger, - ROCKSDB_NAMESPACE::Options().level0_file_num_compaction_trigger, - "Level0 compaction start trigger"); - -DEFINE_int32(level0_slowdown_writes_trigger, - ROCKSDB_NAMESPACE::Options().level0_slowdown_writes_trigger, - "Number of files in level-0 that will slow down writes"); - -DEFINE_int32(level0_stop_writes_trigger, - ROCKSDB_NAMESPACE::Options().level0_stop_writes_trigger, - "Number of files in level-0 that will trigger put stop."); - -DEFINE_int32(block_size, - static_cast( - ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_size), - "Number of bytes in a block."); - -DEFINE_int32(format_version, - static_cast( - ROCKSDB_NAMESPACE::BlockBasedTableOptions().format_version), - "Format version of SST files."); - -DEFINE_int32( - index_block_restart_interval, - ROCKSDB_NAMESPACE::BlockBasedTableOptions().index_block_restart_interval, - "Number of keys between restart points " - "for delta encoding of keys in index block."); - -DEFINE_bool(disable_auto_compactions, - ROCKSDB_NAMESPACE::Options().disable_auto_compactions, - "If true, RocksDB internally will not trigger compactions."); - -DEFINE_int32(max_background_compactions, - ROCKSDB_NAMESPACE::Options().max_background_compactions, - "The maximum number of concurrent background compactions " - "that can occur in parallel."); - -DEFINE_int32(num_bottom_pri_threads, 0, - "The number of threads in the bottom-priority thread pool (used " - "by universal compaction only)."); - -DEFINE_int32(compaction_thread_pool_adjust_interval, 0, - "The interval (in milliseconds) to adjust compaction thread pool " - "size. Don't change it periodically if the value is 0."); - -DEFINE_int32(compaction_thread_pool_variations, 2, - "Range of background thread pool size variations when adjusted " - "periodically."); - -DEFINE_int32(max_background_flushes, - ROCKSDB_NAMESPACE::Options().max_background_flushes, - "The maximum number of concurrent background flushes " - "that can occur in parallel."); - -DEFINE_int32(universal_size_ratio, 0, - "The ratio of file sizes that trigger" - " compaction in universal style"); - -DEFINE_int32(universal_min_merge_width, 0, - "The minimum number of files to " - "compact in universal style compaction"); - -DEFINE_int32(universal_max_merge_width, 0, - "The max number of files to compact" - " in universal style compaction"); - -DEFINE_int32(universal_max_size_amplification_percent, 0, - "The max size amplification for universal style compaction"); - -DEFINE_int32(clear_column_family_one_in, 1000000, - "With a chance of 1/N, delete a column family and then recreate " - "it again. If N == 0, never drop/create column families. " - "When test_batches_snapshots is true, this flag has no effect"); - -DEFINE_int32(get_live_files_one_in, 1000000, - "With a chance of 1/N, call GetLiveFiles to verify if it returns " - "correctly. If N == 0, do not call the interface."); - -DEFINE_int32( - get_sorted_wal_files_one_in, 1000000, - "With a chance of 1/N, call GetSortedWalFiles to verify if it returns " - "correctly. (Note that this API may legitimately return an error.) If N == " - "0, do not call the interface."); - -DEFINE_int32( - get_current_wal_file_one_in, 1000000, - "With a chance of 1/N, call GetCurrentWalFile to verify if it returns " - "correctly. (Note that this API may legitimately return an error.) If N == " - "0, do not call the interface."); - -DEFINE_int32(set_options_one_in, 0, - "With a chance of 1/N, change some random options"); - -DEFINE_int32(set_in_place_one_in, 0, - "With a chance of 1/N, toggle in place support option"); - -DEFINE_int64(cache_size, 2LL * KB * KB * KB, - "Number of bytes to use as a cache of uncompressed data."); - -DEFINE_int32(cache_numshardbits, 6, - "Number of shards for the block cache" - " is 2 ** cache_numshardbits. Negative means use default settings." - " This is applied only if FLAGS_cache_size is greater than 0."); - -DEFINE_bool(cache_index_and_filter_blocks, false, - "True if indexes/filters should be cached in block cache."); - -DEFINE_bool(charge_compression_dictionary_building_buffer, false, - "Setting for " - "CacheEntryRoleOptions::charged of " - "CacheEntryRole::kCompressionDictionaryBuildingBuffer"); - -DEFINE_bool(charge_filter_construction, false, - "Setting for " - "CacheEntryRoleOptions::charged of " - "CacheEntryRole::kFilterConstruction"); - -DEFINE_bool(charge_table_reader, false, - "Setting for " - "CacheEntryRoleOptions::charged of " - "CacheEntryRole::kBlockBasedTableReader"); - -DEFINE_bool(charge_file_metadata, false, - "Setting for " - "CacheEntryRoleOptions::charged of " - "kFileMetadata"); - -DEFINE_bool(charge_blob_cache, false, - "Setting for " - "CacheEntryRoleOptions::charged of " - "kBlobCache"); - -DEFINE_int32( - top_level_index_pinning, - static_cast(ROCKSDB_NAMESPACE::PinningTier::kFallback), - "Type of pinning for top-level indexes into metadata partitions (see " - "`enum PinningTier` in table.h)"); - -DEFINE_int32( - partition_pinning, - static_cast(ROCKSDB_NAMESPACE::PinningTier::kFallback), - "Type of pinning for metadata partitions (see `enum PinningTier` in " - "table.h)"); - -DEFINE_int32( - unpartitioned_pinning, - static_cast(ROCKSDB_NAMESPACE::PinningTier::kFallback), - "Type of pinning for unpartitioned metadata blocks (see `enum PinningTier` " - "in table.h)"); - -DEFINE_string(cache_type, "lru_cache", "Type of block cache."); - -DEFINE_uint64(subcompactions, 1, - "Maximum number of subcompactions to divide L0-L1 compactions " - "into."); - -DEFINE_uint64(periodic_compaction_seconds, 1000, - "Files older than this value will be picked up for compaction."); - -DEFINE_uint64(compaction_ttl, 1000, - "Files older than TTL will be compacted to the next level."); - -DEFINE_bool(fifo_allow_compaction, false, - "If true, set `Options::compaction_options_fifo.allow_compaction = " - "true`. It only take effect when FIFO compaction is used."); - -DEFINE_bool(allow_concurrent_memtable_write, false, - "Allow multi-writers to update mem tables in parallel."); - -DEFINE_double(experimental_mempurge_threshold, 0.0, - "Maximum estimated useful payload that triggers a " - "mempurge process to collect memtable garbage bytes."); - -DEFINE_bool(enable_write_thread_adaptive_yield, true, - "Use a yielding spin loop for brief writer thread waits."); - -// Options for StackableDB-based BlobDB -DEFINE_bool(use_blob_db, false, "[Stacked BlobDB] Use BlobDB."); - -DEFINE_uint64( - blob_db_min_blob_size, - ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().min_blob_size, - "[Stacked BlobDB] Smallest blob to store in a file. Blobs " - "smaller than this will be inlined with the key in the LSM tree."); - -DEFINE_uint64( - blob_db_bytes_per_sync, - ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().bytes_per_sync, - "[Stacked BlobDB] Sync blob files once per every N bytes written."); - -DEFINE_uint64(blob_db_file_size, - ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().blob_file_size, - "[Stacked BlobDB] Target size of each blob file."); - -DEFINE_bool( - blob_db_enable_gc, - ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().enable_garbage_collection, - "[Stacked BlobDB] Enable BlobDB garbage collection."); - -DEFINE_double( - blob_db_gc_cutoff, - ROCKSDB_NAMESPACE::blob_db::BlobDBOptions().garbage_collection_cutoff, - "[Stacked BlobDB] Cutoff ratio for BlobDB garbage collection."); - -// Options for integrated BlobDB -DEFINE_bool(allow_setting_blob_options_dynamically, false, - "[Integrated BlobDB] Allow setting blob options dynamically."); - -DEFINE_bool( - enable_blob_files, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions().enable_blob_files, - "[Integrated BlobDB] Enable writing large values to separate blob files."); - -DEFINE_uint64(min_blob_size, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions().min_blob_size, - "[Integrated BlobDB] The size of the smallest value to be stored " - "separately in a blob file."); - -DEFINE_uint64(blob_file_size, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions().blob_file_size, - "[Integrated BlobDB] The size limit for blob files."); - -DEFINE_string(blob_compression_type, "none", - "[Integrated BlobDB] The compression algorithm to use for large " - "values stored in blob files."); - -DEFINE_bool(enable_blob_garbage_collection, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions() - .enable_blob_garbage_collection, - "[Integrated BlobDB] Enable blob garbage collection."); - -DEFINE_double(blob_garbage_collection_age_cutoff, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions() - .blob_garbage_collection_age_cutoff, - "[Integrated BlobDB] The cutoff in terms of blob file age for " - "garbage collection."); - -DEFINE_double(blob_garbage_collection_force_threshold, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions() - .blob_garbage_collection_force_threshold, - "[Integrated BlobDB] The threshold for the ratio of garbage in " - "the oldest blob files for forcing garbage collection."); - -DEFINE_uint64(blob_compaction_readahead_size, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions() - .blob_compaction_readahead_size, - "[Integrated BlobDB] Compaction readahead for blob files."); - -DEFINE_int32( - blob_file_starting_level, - ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions().blob_file_starting_level, - "[Integrated BlobDB] Enable writing blob files during flushes and " - "compactions starting from the specified level."); - -DEFINE_bool(use_blob_cache, false, "[Integrated BlobDB] Enable blob cache."); - -DEFINE_bool( - use_shared_block_and_blob_cache, true, - "[Integrated BlobDB] Use a shared backing cache for both block " - "cache and blob cache. It only takes effect if use_blob_cache is enabled."); - -DEFINE_uint64( - blob_cache_size, 2LL * KB * KB * KB, - "[Integrated BlobDB] Number of bytes to use as a cache of blobs. It only " - "takes effect if the block and blob caches are different " - "(use_shared_block_and_blob_cache = false)."); - -DEFINE_int32(blob_cache_numshardbits, 6, - "[Integrated BlobDB] Number of shards for the blob cache is 2 ** " - "blob_cache_numshardbits. Negative means use default settings. " - "It only takes effect if blob_cache_size is greater than 0, and " - "the block and blob caches are different " - "(use_shared_block_and_blob_cache = false)."); - -DEFINE_int32(prepopulate_blob_cache, 0, - "[Integrated BlobDB] Pre-populate hot/warm blobs in blob cache. 0 " - "to disable and 1 to insert during flush."); - -DEFINE_bool(enable_tiered_storage, false, "Set last_level_temperature"); - -DEFINE_int64(preclude_last_level_data_seconds, 0, - "Preclude data from the last level. Used with tiered storage " - "feature to preclude new data from comacting to the last level."); - -DEFINE_int64( - preserve_internal_time_seconds, 0, - "Preserve internal time information which is attached to each SST."); - -static const bool FLAGS_subcompactions_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_subcompactions, &ValidateUint32Range); - -static bool ValidateInt32Positive(const char* flagname, int32_t value) { - if (value < 0) { - fprintf(stderr, "Invalid value for --%s: %d, must be >=0\n", flagname, - value); - return false; - } - return true; -} -DEFINE_int32(reopen, 10, "Number of times database reopens"); -static const bool FLAGS_reopen_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_reopen, &ValidateInt32Positive); - -DEFINE_double(bloom_bits, 10, - "Bloom filter bits per key. " - "Negative means use default settings."); - -DEFINE_int32( - ribbon_starting_level, 999, - "Use Bloom filter on levels below specified and Ribbon beginning on level " - "specified. Flush is considered level -1. 999 or more -> always Bloom. 0 " - "-> Ribbon except Bloom for flush. -1 -> always Ribbon."); - -DEFINE_bool(partition_filters, false, - "use partitioned filters " - "for block-based table"); - -DEFINE_bool( - optimize_filters_for_memory, - ROCKSDB_NAMESPACE::BlockBasedTableOptions().optimize_filters_for_memory, - "Minimize memory footprint of filters"); - -DEFINE_bool( - detect_filter_construct_corruption, - ROCKSDB_NAMESPACE::BlockBasedTableOptions() - .detect_filter_construct_corruption, - "Detect corruption during new Bloom Filter and Ribbon Filter construction"); - -DEFINE_int32( - index_type, - static_cast( - ROCKSDB_NAMESPACE::BlockBasedTableOptions().index_type), - "Type of block-based table index (see `enum IndexType` in table.h)"); - -DEFINE_int32( - data_block_index_type, - static_cast( - ROCKSDB_NAMESPACE::BlockBasedTableOptions().data_block_index_type), - "Index type for data blocks (see `enum DataBlockIndexType` in table.h)"); - -DEFINE_string(db, "", "Use the db with the following name."); - -DEFINE_string(secondaries_base, "", - "Use this path as the base path for secondary instances."); - -DEFINE_bool(test_secondary, false, - "If true, start an additional secondary instance which can be used " - "for verification."); - -DEFINE_string( - expected_values_dir, "", - "Dir where files containing info about the latest/historical values will " - "be stored. If provided and non-empty, the DB state will be verified " - "against values from these files after recovery. --max_key and " - "--column_family must be kept the same across invocations of this program " - "that use the same --expected_values_dir. Currently historical values are " - "only tracked when --sync_fault_injection is set. See --seed and " - "--nooverwritepercent for further requirements."); - -DEFINE_bool(verify_checksum, false, - "Verify checksum for every block read from storage"); - -DEFINE_bool(mmap_read, ROCKSDB_NAMESPACE::Options().allow_mmap_reads, - "Allow reads to occur via mmap-ing files"); - -DEFINE_bool(mmap_write, ROCKSDB_NAMESPACE::Options().allow_mmap_writes, - "Allow writes to occur via mmap-ing files"); - -DEFINE_bool(use_direct_reads, ROCKSDB_NAMESPACE::Options().use_direct_reads, - "Use O_DIRECT for reading data"); - -DEFINE_bool(use_direct_io_for_flush_and_compaction, - ROCKSDB_NAMESPACE::Options().use_direct_io_for_flush_and_compaction, - "Use O_DIRECT for writing data"); - -DEFINE_bool(mock_direct_io, false, - "Mock direct IO by not using O_DIRECT for direct IO read"); - -DEFINE_bool(statistics, false, "Create database statistics"); - -DEFINE_bool(sync, false, "Sync all writes to disk"); - -DEFINE_bool(use_fsync, false, "If true, issue fsync instead of fdatasync"); - -DEFINE_uint64(bytes_per_sync, ROCKSDB_NAMESPACE::Options().bytes_per_sync, - "If nonzero, sync SST file data incrementally after every " - "`bytes_per_sync` bytes are written"); - -DEFINE_uint64(wal_bytes_per_sync, - ROCKSDB_NAMESPACE::Options().wal_bytes_per_sync, - "If nonzero, sync WAL file data incrementally after every " - "`bytes_per_sync` bytes are written"); - -DEFINE_int32(kill_random_test, 0, - "If non-zero, kill at various points in source code with " - "probability 1/this"); -static const bool FLAGS_kill_random_test_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_kill_random_test, &ValidateInt32Positive); - -DEFINE_string(kill_exclude_prefixes, "", - "If non-empty, kill points with prefix in the list given will be" - " skipped. Items are comma-separated."); -extern std::vector rocksdb_kill_exclude_prefixes; - -DEFINE_bool(disable_wal, false, "If true, do not write WAL for write."); - -DEFINE_uint64(recycle_log_file_num, - ROCKSDB_NAMESPACE::Options().recycle_log_file_num, - "Number of old WAL files to keep around for later recycling"); - -DEFINE_int64(target_file_size_base, - ROCKSDB_NAMESPACE::Options().target_file_size_base, - "Target level-1 file size for compaction"); - -DEFINE_int32(target_file_size_multiplier, 1, - "A multiplier to compute target level-N file size (N >= 2)"); - -DEFINE_uint64(max_bytes_for_level_base, - ROCKSDB_NAMESPACE::Options().max_bytes_for_level_base, - "Max bytes for level-1"); - -DEFINE_double(max_bytes_for_level_multiplier, 2, - "A multiplier to compute max bytes for level-N (N >= 2)"); - -DEFINE_int32(range_deletion_width, 10, - "The width of the range deletion intervals."); - -DEFINE_uint64(rate_limiter_bytes_per_sec, 0, "Set options.rate_limiter value."); - -DEFINE_bool(rate_limit_bg_reads, false, - "Use options.rate_limiter on compaction reads"); - -DEFINE_bool(rate_limit_user_ops, false, - "When true use Env::IO_USER priority level to charge internal rate " - "limiter for reads associated with user operations."); - -DEFINE_bool(rate_limit_auto_wal_flush, false, - "When true use Env::IO_USER priority level to charge internal rate " - "limiter for automatic WAL flush (`Options::manual_wal_flush` == " - "false) after the user " - "write operation."); - -DEFINE_uint64(sst_file_manager_bytes_per_sec, 0, - "Set `Options::sst_file_manager` to delete at this rate. By " - "default the deletion rate is unbounded."); - -DEFINE_uint64(sst_file_manager_bytes_per_truncate, 0, - "Set `Options::sst_file_manager` to delete in chunks of this " - "many bytes. By default whole files will be deleted."); - -DEFINE_bool(use_txn, false, - "Use TransactionDB. Currently the default write policy is " - "TxnDBWritePolicy::WRITE_PREPARED"); - -DEFINE_uint64(txn_write_policy, 0, - "The transaction write policy. Default is " - "TxnDBWritePolicy::WRITE_COMMITTED. Note that this should not be " - "changed accross crashes."); - -DEFINE_bool(unordered_write, false, - "Turn on the unordered_write feature. This options is currently " - "tested only in combination with use_txn=true and " - "txn_write_policy=TxnDBWritePolicy::WRITE_PREPARED."); - -DEFINE_int32(backup_one_in, 0, - "If non-zero, then CreateNewBackup() will be called once for " - "every N operations on average. 0 indicates CreateNewBackup() " - "is disabled."); - -DEFINE_uint64(backup_max_size, 100 * 1024 * 1024, - "If non-zero, skip checking backup/restore when DB size in " - "bytes exceeds this setting."); - -DEFINE_int32(checkpoint_one_in, 0, - "If non-zero, then CreateCheckpoint() will be called once for " - "every N operations on average. 0 indicates CreateCheckpoint() " - "is disabled."); - -DEFINE_int32(ingest_external_file_one_in, 0, - "If non-zero, then IngestExternalFile() will be called once for " - "every N operations on average. 0 indicates IngestExternalFile() " - "is disabled."); - -DEFINE_int32(ingest_external_file_width, 100, - "The width of the ingested external files."); - -DEFINE_int32(compact_files_one_in, 0, - "If non-zero, then CompactFiles() will be called once for every N " - "operations on average. 0 indicates CompactFiles() is disabled."); - -DEFINE_int32(compact_range_one_in, 0, - "If non-zero, then CompactRange() will be called once for every N " - "operations on average. 0 indicates CompactRange() is disabled."); - -DEFINE_int32(mark_for_compaction_one_file_in, 0, - "A `TablePropertiesCollectorFactory` will be registered, which " - "creates a `TablePropertiesCollector` with `NeedCompact()` " - "returning true once for every N files on average. 0 or negative " - "mean `NeedCompact()` always returns false."); - -DEFINE_int32(flush_one_in, 0, - "If non-zero, then Flush() will be called once for every N ops " - "on average. 0 indicates calls to Flush() are disabled."); - -DEFINE_int32(pause_background_one_in, 0, - "If non-zero, then PauseBackgroundWork()+Continue will be called " - "once for every N ops on average. 0 disables."); - -DEFINE_int32(compact_range_width, 10000, - "The width of the ranges passed to CompactRange()."); - -DEFINE_int32(acquire_snapshot_one_in, 0, - "If non-zero, then acquires a snapshot once every N operations on " - "average."); - -DEFINE_bool(compare_full_db_state_snapshot, false, - "If set we compare state of entire db (in one of the threads) with" - "each snapshot."); - -DEFINE_uint64(snapshot_hold_ops, 0, - "If non-zero, then releases snapshots N operations after they're " - "acquired."); - -DEFINE_bool(long_running_snapshots, false, - "If set, hold on some some snapshots for much longer time."); - -DEFINE_bool(use_multiget, false, - "If set, use the batched MultiGet API for reads"); - -DEFINE_bool(use_get_entity, false, "If set, use the GetEntity API for reads"); - -static bool ValidateInt32Percent(const char* flagname, int32_t value) { - if (value < 0 || value > 100) { - fprintf(stderr, "Invalid value for --%s: %d, 0<= pct <=100 \n", flagname, - value); - return false; - } - return true; -} - -DEFINE_int32(readpercent, 10, - "Ratio of reads to total workload (expressed as a percentage)"); -static const bool FLAGS_readpercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_readpercent, &ValidateInt32Percent); - -DEFINE_int32(prefixpercent, 20, - "Ratio of prefix iterators to total workload (expressed as a" - " percentage)"); -static const bool FLAGS_prefixpercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_prefixpercent, &ValidateInt32Percent); - -DEFINE_int32(writepercent, 45, - "Ratio of writes to total workload (expressed as a percentage)"); -static const bool FLAGS_writepercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_writepercent, &ValidateInt32Percent); - -DEFINE_int32(delpercent, 15, - "Ratio of deletes to total workload (expressed as a percentage)"); -static const bool FLAGS_delpercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_delpercent, &ValidateInt32Percent); - -DEFINE_int32(delrangepercent, 0, - "Ratio of range deletions to total workload (expressed as a " - "percentage). Cannot be used with test_batches_snapshots"); -static const bool FLAGS_delrangepercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_delrangepercent, &ValidateInt32Percent); - -DEFINE_int32(nooverwritepercent, 60, - "Ratio of keys without overwrite to total workload (expressed as " - "a percentage). When --expected_values_dir is nonempty, must " - "keep this value constant across invocations."); -static const bool FLAGS_nooverwritepercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_nooverwritepercent, &ValidateInt32Percent); - -DEFINE_int32(iterpercent, 10, - "Ratio of iterations to total workload" - " (expressed as a percentage)"); -static const bool FLAGS_iterpercent_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_iterpercent, &ValidateInt32Percent); - -DEFINE_uint64(num_iterations, 10, "Number of iterations per MultiIterate run"); -static const bool FLAGS_num_iterations_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_num_iterations, &ValidateUint32Range); - -DEFINE_int32( - customopspercent, 0, - "Ratio of custom operations to total workload (expressed as a percentage)"); - -DEFINE_string(compression_type, "snappy", - "Algorithm to use to compress the database"); - -DEFINE_int32(compression_max_dict_bytes, 0, - "Maximum size of dictionary used to prime the compression " - "library."); - -DEFINE_int32(compression_zstd_max_train_bytes, 0, - "Maximum size of training data passed to zstd's dictionary " - "trainer."); - -DEFINE_int32(compression_parallel_threads, 1, - "Number of threads for parallel compression."); - -DEFINE_uint64(compression_max_dict_buffer_bytes, 0, - "Buffering limit for SST file data to sample for dictionary " - "compression."); - -DEFINE_bool( - compression_use_zstd_dict_trainer, true, - "Use zstd's trainer to generate dictionary. If the options is false, " - "zstd's finalizeDictionary() API is used to generate dictionary. " - "ZSTD 1.4.5+ is required. If ZSTD 1.4.5+ is not linked with the binary, " - "this flag will have the default value true."); - -DEFINE_string(bottommost_compression_type, "disable", - "Algorithm to use to compress bottommost level of the database. " - "\"disable\" means disabling the feature"); - -DEFINE_string(checksum_type, "kCRC32c", "Algorithm to use to checksum blocks"); - -DEFINE_string(env_uri, "", - "URI for env lookup. Mutually exclusive with --fs_uri"); - -DEFINE_string(fs_uri, "", - "URI for registry Filesystem lookup. Mutually exclusive" - " with --env_uri." - " Creates a default environment with the specified filesystem."); - -DEFINE_uint64(ops_per_thread, 1200000, "Number of operations per thread."); -static const bool FLAGS_ops_per_thread_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_ops_per_thread, &ValidateUint32Range); - -DEFINE_uint64(log2_keys_per_lock, 2, "Log2 of number of keys per lock"); -static const bool FLAGS_log2_keys_per_lock_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_log2_keys_per_lock, &ValidateUint32Range); - -DEFINE_uint64(max_manifest_file_size, 16384, "Maximum size of a MANIFEST file"); - -DEFINE_bool(in_place_update, false, "On true, does inplace update in memtable"); - -DEFINE_string(memtablerep, "skip_list", ""); - -inline static bool ValidatePrefixSize(const char* flagname, int32_t value) { - if (value < -1 || value > 8) { - fprintf(stderr, "Invalid value for --%s: %d. -1 <= PrefixSize <= 8\n", - flagname, value); - return false; - } - return true; -} -DEFINE_int32(prefix_size, 7, - "Control the prefix size for HashSkipListRep. " - "-1 is disabled."); -static const bool FLAGS_prefix_size_dummy __attribute__((__unused__)) = - RegisterFlagValidator(&FLAGS_prefix_size, &ValidatePrefixSize); - -DEFINE_bool(use_merge, false, - "On true, replaces all writes with a Merge " - "that behaves like a Put"); - -DEFINE_uint32(use_put_entity_one_in, 0, - "If greater than zero, PutEntity will be used once per every N " - "write ops on average."); - -DEFINE_bool(use_full_merge_v1, false, - "On true, use a merge operator that implement the deprecated " - "version of FullMerge"); - -DEFINE_int32(sync_wal_one_in, 0, - "If non-zero, then SyncWAL() will be called once for every N ops " - "on average. 0 indicates that calls to SyncWAL() are disabled."); - -DEFINE_bool(avoid_unnecessary_blocking_io, - ROCKSDB_NAMESPACE::Options().avoid_unnecessary_blocking_io, - "If true, some expensive cleaning up operations will be moved from " - "user reads to high-pri background threads."); - -DEFINE_bool(write_dbid_to_manifest, - ROCKSDB_NAMESPACE::Options().write_dbid_to_manifest, - "Write DB_ID to manifest"); - -DEFINE_bool(avoid_flush_during_recovery, - ROCKSDB_NAMESPACE::Options().avoid_flush_during_recovery, - "Avoid flush during recovery"); - -DEFINE_uint64(max_write_batch_group_size_bytes, - ROCKSDB_NAMESPACE::Options().max_write_batch_group_size_bytes, - "Max write batch group size"); - -DEFINE_bool(level_compaction_dynamic_level_bytes, - ROCKSDB_NAMESPACE::Options().level_compaction_dynamic_level_bytes, - "Use dynamic level"); - -DEFINE_int32(verify_checksum_one_in, 0, - "If non-zero, then DB::VerifyChecksum() will be called to do" - " checksum verification of all the files in the database once for" - " every N ops on average. 0 indicates that calls to" - " VerifyChecksum() are disabled."); -DEFINE_int32(verify_db_one_in, 0, - "If non-zero, call VerifyDb() once for every N ops. 0 indicates " - "that VerifyDb() will not be called in OperateDb(). Note that " - "enabling this can slow down tests."); - -DEFINE_int32(continuous_verification_interval, 1000, - "While test is running, verify db every N milliseconds. 0 " - "disables continuous verification."); - -DEFINE_int32(approximate_size_one_in, 64, - "If non-zero, DB::GetApproximateSizes() will be called against" - " random key ranges."); - -DEFINE_int32(read_fault_one_in, 1000, - "On non-zero, enables fault injection on read"); - -DEFINE_int32(get_property_one_in, 1000, - "If non-zero, then DB::GetProperty() will be called to get various" - " properties for every N ops on average. 0 indicates that" - " GetProperty() will be not be called."); - -DEFINE_bool(sync_fault_injection, false, - "If true, FaultInjectionTestFS will be used for write operations, " - "and unsynced data in DB will lost after crash. In such a case we " - "track DB changes in a trace file (\"*.trace\") in " - "--expected_values_dir for verifying there are no holes in the " - "recovered data."); - -DEFINE_bool(best_efforts_recovery, false, - "If true, use best efforts recovery."); -DEFINE_bool(skip_verifydb, false, - "If true, skip VerifyDb() calls and Get()/Iterator verifications" - "against expected state."); - -DEFINE_bool(enable_compaction_filter, false, - "If true, configures a compaction filter that returns a kRemove " - "decision for deleted keys."); - -DEFINE_bool(paranoid_file_checks, true, - "After writing every SST file, reopen it and read all the keys " - "and validate checksums"); - -DEFINE_bool(fail_if_options_file_error, false, - "Fail operations that fail to detect or properly persist options " - "file."); - -DEFINE_uint64(batch_protection_bytes_per_key, 0, - "If nonzero, enables integrity protection in `WriteBatch` at the " - "specified number of bytes per key. Currently the only supported " - "nonzero value is eight."); - -DEFINE_uint32( - memtable_protection_bytes_per_key, 0, - "If nonzero, enables integrity protection in memtable entries at the " - "specified number of bytes per key. Currently the supported " - "nonzero values are 1, 2, 4 and 8."); - -DEFINE_string(file_checksum_impl, "none", - "Name of an implementation for file_checksum_gen_factory, or " - "\"none\" for null."); - -DEFINE_int32(write_fault_one_in, 0, - "On non-zero, enables fault injection on write"); - -DEFINE_uint64(user_timestamp_size, 0, - "Number of bytes for a user-defined timestamp. Currently, only " - "8-byte is supported"); - -DEFINE_int32(open_metadata_write_fault_one_in, 0, - "On non-zero, enables fault injection on file metadata write " - "during DB reopen."); - -DEFINE_string(secondary_cache_uri, "", - "Full URI for creating a customized secondary cache object"); -DEFINE_int32(secondary_cache_fault_one_in, 0, - "On non-zero, enables fault injection in secondary cache inserts" - " and lookups"); -DEFINE_int32(open_write_fault_one_in, 0, - "On non-zero, enables fault injection on file writes " - "during DB reopen."); -DEFINE_int32(open_read_fault_one_in, 0, - "On non-zero, enables fault injection on file reads " - "during DB reopen."); -DEFINE_int32(injest_error_severity, 1, - "The severity of the injested IO Error. 1 is soft error (e.g. " - "retryable error), 2 is fatal error, and the default is " - "retryable error."); -DEFINE_int32(prepopulate_block_cache, - static_cast(ROCKSDB_NAMESPACE::BlockBasedTableOptions:: - PrepopulateBlockCache::kDisable), - "Options related to cache warming (see `enum " - "PrepopulateBlockCache` in table.h)"); - -DEFINE_bool(two_write_queues, false, - "Set to true to enable two write queues. Default: false"); - -DEFINE_bool(use_only_the_last_commit_time_batch_for_recovery, false, - "If true, the commit-time write batch will not be immediately " - "inserted into the memtables. Default: false"); - -DEFINE_uint64( - wp_snapshot_cache_bits, 7ull, - "Number of bits to represent write-prepared transaction db's snapshot " - "cache. Default: 7 (128 entries)"); - -DEFINE_uint64(wp_commit_cache_bits, 23ull, - "Number of bits to represent write-prepared transaction db's " - "commit cache. Default: 23 (8M entries)"); - -DEFINE_bool(adaptive_readahead, false, - "Carry forward internal auto readahead size from one file to next " - "file at each level during iteration"); -DEFINE_bool( - async_io, false, - "Does asynchronous prefetching when internal auto readahead is enabled"); - -DEFINE_string(wal_compression, "none", - "Algorithm to use for WAL compression. none to disable."); - -DEFINE_bool( - verify_sst_unique_id_in_manifest, false, - "Enable DB options `verify_sst_unique_id_in_manifest`, if true, during " - "DB-open try verifying the SST unique id between MANIFEST and SST " - "properties."); - -DEFINE_int32( - create_timestamped_snapshot_one_in, 0, - "On non-zero, create timestamped snapshots upon transaction commits."); - -DEFINE_bool(allow_data_in_errors, - ROCKSDB_NAMESPACE::Options().allow_data_in_errors, - "If true, allow logging data, e.g. key, value in LOG files."); - -DEFINE_int32(verify_iterator_with_expected_state_one_in, 0, - "If non-zero, when TestIterate() is to be called, there is a " - "1/verify_iterator_with_expected_state_one_in " - "chance that the iterator is verified against the expected state " - "file, instead of comparing keys between two iterators."); - -DEFINE_uint64(readahead_size, 0, "Iterator readahead size"); -DEFINE_uint64(initial_auto_readahead_size, 0, - "Initial auto readahead size for prefetching during Iteration"); -DEFINE_uint64(max_auto_readahead_size, 0, - "Max auto readahead size for prefetching during Iteration"); -DEFINE_uint64( - num_file_reads_for_auto_readahead, 0, - "Num of sequential reads to enable auto prefetching during Iteration"); - -DEFINE_bool( - preserve_unverified_changes, false, - "DB files of the current run will all be preserved in `FLAGS_db`. DB files " - "from the last run will be preserved in `FLAGS_db/unverified` until the " - "first verification succeeds. Expected state files from the last run will " - "be preserved similarly under `FLAGS_expected_values_dir/unverified` when " - "`--expected_values_dir` is nonempty."); - -DEFINE_uint64(stats_dump_period_sec, - ROCKSDB_NAMESPACE::Options().stats_dump_period_sec, - "Gap between printing stats to log in seconds"); - -DEFINE_bool(use_io_uring, false, "Enable the use of IO uring on Posix"); -extern "C" bool RocksDbIOUringEnable() { return FLAGS_use_io_uring; } - -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_listener.cc b/db_stress_tool/db_stress_listener.cc deleted file mode 100644 index e2838c582..000000000 --- a/db_stress_tool/db_stress_listener.cc +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db_stress_tool/db_stress_listener.h" - -#include - -#include "file/file_util.h" -#include "rocksdb/file_system.h" -#include "util/coding_lean.h" - -namespace ROCKSDB_NAMESPACE { - -#ifdef GFLAGS - -// TODO: consider using expected_values_dir instead, but this is more -// convenient for now. -UniqueIdVerifier::UniqueIdVerifier(const std::string& db_name, Env* env) - : path_(db_name + "/.unique_ids") { - // We expect such a small number of files generated during this test - // (thousands?), checking full 192-bit IDs for uniqueness is a very - // weak check. For a stronger check, we pick a specific 64-bit - // subsequence from the ID to check for uniqueness. All bits of the - // ID should be high quality, and 64 bits should be unique with - // very good probability for the quantities in this test. - offset_ = Random::GetTLSInstance()->Uniform(17); // 0 to 16 - - const std::shared_ptr fs = env->GetFileSystem(); - IOOptions opts; - - Status st = fs->CreateDirIfMissing(db_name, opts, nullptr); - if (!st.ok()) { - fprintf(stderr, "Failed to create directory %s: %s\n", db_name.c_str(), - st.ToString().c_str()); - exit(1); - } - - // Avoid relying on ReopenWritableFile which is not supported by all - // file systems. Create a new file and copy the old file contents to it. - std::string tmp_path = path_ + ".tmp"; - st = fs->FileExists(tmp_path, opts, /*dbg*/ nullptr); - if (st.IsNotFound()) { - st = fs->RenameFile(path_, tmp_path, opts, /*dbg*/ nullptr); - // Either it should succeed or fail because src path doesn't exist - assert(st.ok() || st.IsPathNotFound()); - } else { - // If path_ and tmp_path both exist, retain tmp_path as its - // guaranteed to be more complete. The order of operations are - - // 1. Rename path_ to tmp_path - // 2. Parse tmp_path contents - // 3. Create path_ - // 4. Copy tmp_path contents to path_ - // 5. Delete tmp_path - st = fs->DeleteFile(path_, opts, /*dbg*/ nullptr); - assert(st.ok() || st.IsPathNotFound()); - } - - uint64_t size = 0; - { - std::unique_ptr reader; - Status s = fs->NewSequentialFile(tmp_path, FileOptions(), &reader, - /*dbg*/ nullptr); - if (s.ok()) { - // Load from file - std::string id(24U, '\0'); - Slice result; - for (;;) { - s = reader->Read(id.size(), opts, &result, &id[0], /*dbg*/ nullptr); - if (!s.ok()) { - fprintf(stderr, "Error reading unique id file: %s\n", - s.ToString().c_str()); - assert(false); - } - if (result.size() < id.size()) { - // EOF - if (result.size() != 0) { - // Corrupt file. Not a DB bug but could happen if OS doesn't provide - // good guarantees on process crash. - fprintf(stdout, "Warning: clearing corrupt unique id file\n"); - id_set_.clear(); - reader.reset(); - s = fs->DeleteFile(tmp_path, opts, /*dbg*/ nullptr); - assert(s.ok()); - size = 0; - } - break; - } - size += 24U; - VerifyNoWrite(id); - } - } else { - // Newly created is ok. - // But FileSystem doesn't tell us whether non-existence was the cause of - // the failure. (Issue #9021) - Status s2 = fs->FileExists(tmp_path, opts, /*dbg*/ nullptr); - if (!s2.IsNotFound()) { - fprintf(stderr, "Error opening unique id file: %s\n", - s.ToString().c_str()); - assert(false); - } - size = 0; - } - } - fprintf(stdout, "(Re-)verified %zu unique IDs\n", id_set_.size()); - - std::unique_ptr file_writer; - st = fs->NewWritableFile(path_, FileOptions(), &file_writer, /*dbg*/ nullptr); - if (!st.ok()) { - fprintf(stderr, "Error creating the unique ids file: %s\n", - st.ToString().c_str()); - assert(false); - } - data_file_writer_.reset( - new WritableFileWriter(std::move(file_writer), path_, FileOptions())); - - if (size > 0) { - st = CopyFile(fs.get(), tmp_path, data_file_writer_, size, - /*use_fsync*/ true, /*io_tracer*/ nullptr, - /*temparature*/ Temperature::kHot); - if (!st.ok()) { - fprintf(stderr, "Error copying contents of old unique id file: %s\n", - st.ToString().c_str()); - assert(false); - } - } - st = fs->DeleteFile(tmp_path, opts, /*dbg*/ nullptr); - assert(st.ok() || st.IsPathNotFound()); -} - -UniqueIdVerifier::~UniqueIdVerifier() { - IOStatus s = data_file_writer_->Close(); - assert(s.ok()); -} - -void UniqueIdVerifier::VerifyNoWrite(const std::string& id) { - assert(id.size() == 24); - bool is_new = id_set_.insert(DecodeFixed64(&id[offset_])).second; - if (!is_new) { - fprintf(stderr, - "Duplicate partial unique ID found (offset=%zu, count=%zu)\n", - offset_, id_set_.size()); - assert(false); - } -} - -void UniqueIdVerifier::Verify(const std::string& id) { - assert(id.size() == 24); - std::lock_guard lock(mutex_); - // If we accumulate more than ~4 million IDs, there would be > 1 in 1M - // natural chance of collision. Thus, simply stop checking at that point. - if (id_set_.size() >= 4294967) { - return; - } - IOStatus s = data_file_writer_->Append(Slice(id)); - if (!s.ok()) { - fprintf(stderr, "Error writing to unique id file: %s\n", - s.ToString().c_str()); - assert(false); - } - s = data_file_writer_->Flush(); - if (!s.ok()) { - fprintf(stderr, "Error flushing unique id file: %s\n", - s.ToString().c_str()); - assert(false); - } - VerifyNoWrite(id); -} - -void DbStressListener::VerifyTableFileUniqueId( - const TableProperties& new_file_properties, const std::string& file_path) { - // Verify unique ID - std::string id; - // Unit tests verify that GetUniqueIdFromTableProperties returns just a - // substring of this, and we're only going to pull out 64 bits, so using - // GetExtendedUniqueIdFromTableProperties is arguably stronger testing here. - Status s = GetExtendedUniqueIdFromTableProperties(new_file_properties, &id); - if (!s.ok()) { - fprintf(stderr, "Error getting SST unique id for %s: %s\n", - file_path.c_str(), s.ToString().c_str()); - assert(false); - } - unique_ids_.Verify(id); -} - -#endif // GFLAGS - -} // namespace ROCKSDB_NAMESPACE diff --git a/db_stress_tool/db_stress_listener.h b/db_stress_tool/db_stress_listener.h deleted file mode 100644 index 97bbdaefa..000000000 --- a/db_stress_tool/db_stress_listener.h +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#ifdef GFLAGS -#pragma once - -#include -#include - -#include "file/filename.h" -#include "file/writable_file_writer.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/file_system.h" -#include "rocksdb/listener.h" -#include "rocksdb/table_properties.h" -#include "rocksdb/unique_id.h" -#include "util/gflags_compat.h" -#include "util/random.h" - -DECLARE_int32(compact_files_one_in); - -namespace ROCKSDB_NAMESPACE { - -// Verify across process executions that all seen IDs are unique -class UniqueIdVerifier { - public: - explicit UniqueIdVerifier(const std::string& db_name, Env* env); - ~UniqueIdVerifier(); - - void Verify(const std::string& id); - - private: - void VerifyNoWrite(const std::string& id); - - private: - std::mutex mutex_; - // IDs persisted to a hidden file inside DB dir - std::string path_; - std::unique_ptr data_file_writer_; - // Starting byte for which 8 bytes to check in memory within 24 byte ID - size_t offset_; - // Working copy of the set of 8 byte pieces - std::unordered_set id_set_; -}; - -class DbStressListener : public EventListener { - public: - DbStressListener(const std::string& db_name, - const std::vector& db_paths, - const std::vector& column_families, - Env* env) - : db_name_(db_name), - db_paths_(db_paths), - column_families_(column_families), - num_pending_file_creations_(0), - unique_ids_(db_name, env) {} - - const char* Name() const override { return kClassName(); } - static const char* kClassName() { return "DBStressListener"; } - - ~DbStressListener() override { assert(num_pending_file_creations_ == 0); } - void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { - assert(IsValidColumnFamilyName(info.cf_name)); - VerifyFilePath(info.file_path); - // pretending doing some work here - RandomSleep(); - } - - void OnFlushBegin(DB* /*db*/, - const FlushJobInfo& /*flush_job_info*/) override { - RandomSleep(); - } - - void OnTableFileDeleted(const TableFileDeletionInfo& /*info*/) override { - RandomSleep(); - } - - void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& /*ci*/) override { - RandomSleep(); - } - - void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override { - assert(IsValidColumnFamilyName(ci.cf_name)); - assert(ci.input_files.size() + ci.output_files.size() > 0U); - for (const auto& file_path : ci.input_files) { - VerifyFilePath(file_path); - } - for (const auto& file_path : ci.output_files) { - VerifyFilePath(file_path); - } - // pretending doing some work here - RandomSleep(); - } - - void OnTableFileCreationStarted( - const TableFileCreationBriefInfo& /*info*/) override { - ++num_pending_file_creations_; - } - - void OnTableFileCreated(const TableFileCreationInfo& info) override { - assert(info.db_name == db_name_); - assert(IsValidColumnFamilyName(info.cf_name)); - assert(info.job_id > 0 || FLAGS_compact_files_one_in > 0); - if (info.status.ok()) { - assert(info.file_size > 0); - VerifyFilePath(info.file_path); - assert(info.table_properties.data_size > 0 || - info.table_properties.num_range_deletions > 0); - assert(info.table_properties.raw_key_size > 0); - assert(info.table_properties.num_entries > 0); - VerifyTableFileUniqueId(info.table_properties, info.file_path); - } - --num_pending_file_creations_; - } - - void OnMemTableSealed(const MemTableInfo& /*info*/) override { - RandomSleep(); - } - - void OnColumnFamilyHandleDeletionStarted( - ColumnFamilyHandle* /*handle*/) override { - RandomSleep(); - } - - void OnExternalFileIngested(DB* /*db*/, - const ExternalFileIngestionInfo& info) override { - RandomSleep(); - // Here we assume that each generated external file is ingested - // exactly once (or thrown away in case of crash) - VerifyTableFileUniqueId(info.table_properties, info.internal_file_path); - } - - void OnBackgroundError(BackgroundErrorReason /* reason */, - Status* /* bg_error */) override { - RandomSleep(); - } - - void OnStallConditionsChanged(const WriteStallInfo& /*info*/) override { - RandomSleep(); - } - - void OnFileReadFinish(const FileOperationInfo& info) override { - // Even empty callback is valuable because sometimes some locks are - // released in order to make the callback. - - // Sleep carefully here as it is a frequent operation and we don't want - // to slow down the tests. We always sleep when the read is large. - // When read is small, sleep in a small chance. - size_t length_read = info.length; - if (length_read >= 1000000 || Random::GetTLSInstance()->OneIn(1000)) { - RandomSleep(); - } - } - - void OnFileWriteFinish(const FileOperationInfo& info) override { - // Even empty callback is valuable because sometimes some locks are - // released in order to make the callback. - - // Sleep carefully here as it is a frequent operation and we don't want - // to slow down the tests. When the write is large, always sleep. - // Otherwise, sleep in a relatively small chance. - size_t length_write = info.length; - if (length_write >= 1000000 || Random::GetTLSInstance()->OneIn(64)) { - RandomSleep(); - } - } - - bool ShouldBeNotifiedOnFileIO() override { - RandomSleep(); - return static_cast(Random::GetTLSInstance()->OneIn(1)); - } - - void OnErrorRecoveryBegin(BackgroundErrorReason /* reason */, - Status /* bg_error */, - bool* /* auto_recovery */) override { - RandomSleep(); - } - - void OnErrorRecoveryCompleted(Status /* old_bg_error */) override { - RandomSleep(); - } - - protected: - bool IsValidColumnFamilyName(const std::string& cf_name) const { - if (cf_name == kDefaultColumnFamilyName) { - return true; - } - // The column family names in the stress tests are numbers. - for (size_t i = 0; i < cf_name.size(); ++i) { - if (cf_name[i] < '0' || cf_name[i] > '9') { - return false; - } - } - return true; - } - - void VerifyFileDir(const std::string& file_dir) { -#ifndef NDEBUG - if (db_name_ == file_dir) { - return; - } - for (const auto& db_path : db_paths_) { - if (db_path.path == file_dir) { - return; - } - } - for (auto& cf : column_families_) { - for (const auto& cf_path : cf.options.cf_paths) { - if (cf_path.path == file_dir) { - return; - } - } - } - assert(false); -#else - (void)file_dir; -#endif // !NDEBUG - } - - void VerifyFileName(const std::string& file_name) { -#ifndef NDEBUG - uint64_t file_number; - FileType file_type; - bool result = ParseFileName(file_name, &file_number, &file_type); - assert(result); - assert(file_type == kTableFile); -#else - (void)file_name; -#endif // !NDEBUG - } - - void VerifyFilePath(const std::string& file_path) { -#ifndef NDEBUG - size_t pos = file_path.find_last_of("/"); - if (pos == std::string::npos) { - VerifyFileName(file_path); - } else { - if (pos > 0) { - VerifyFileDir(file_path.substr(0, pos)); - } - VerifyFileName(file_path.substr(pos)); - } -#else - (void)file_path; -#endif // !NDEBUG - } - - // Unique id is verified using the TableProperties. file_path is only used - // for reporting. - void VerifyTableFileUniqueId(const TableProperties& new_file_properties, - const std::string& file_path); - - void RandomSleep() { - std::this_thread::sleep_for( - std::chrono::microseconds(Random::GetTLSInstance()->Uniform(5000))); - } - - private: - std::string db_name_; - std::vector db_paths_; - std::vector column_families_; - std::atomic num_pending_file_creations_; - UniqueIdVerifier unique_ids_; -}; -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_shared_state.cc b/db_stress_tool/db_stress_shared_state.cc deleted file mode 100644 index a27f6ac73..000000000 --- a/db_stress_tool/db_stress_shared_state.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_shared_state.h" - -namespace ROCKSDB_NAMESPACE { -thread_local bool SharedState::ignore_read_error; -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_shared_state.h b/db_stress_tool/db_stress_shared_state.h deleted file mode 100644 index 5565c6221..000000000 --- a/db_stress_tool/db_stress_shared_state.h +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors - -#ifdef GFLAGS -#pragma once - -#include "db_stress_tool/db_stress_stat.h" -#include "db_stress_tool/expected_state.h" -// SyncPoint is not supported in Released Windows Mode. -#if !(defined NDEBUG) || !defined(OS_WIN) -#include "test_util/sync_point.h" -#endif // !(defined NDEBUG) || !defined(OS_WIN) -#include "util/gflags_compat.h" - -DECLARE_uint64(seed); -DECLARE_int64(max_key); -DECLARE_uint64(log2_keys_per_lock); -DECLARE_int32(threads); -DECLARE_int32(column_families); -DECLARE_int32(nooverwritepercent); -DECLARE_string(expected_values_dir); -DECLARE_int32(clear_column_family_one_in); -DECLARE_bool(test_batches_snapshots); -DECLARE_int32(compaction_thread_pool_adjust_interval); -DECLARE_int32(continuous_verification_interval); -DECLARE_int32(read_fault_one_in); -DECLARE_int32(write_fault_one_in); -DECLARE_int32(open_metadata_write_fault_one_in); -DECLARE_int32(open_write_fault_one_in); -DECLARE_int32(open_read_fault_one_in); - -DECLARE_int32(injest_error_severity); - -namespace ROCKSDB_NAMESPACE { -class StressTest; - -// State shared by all concurrent executions of the same benchmark. -class SharedState { - public: - // indicates a key may have any value (or not be present) as an operation on - // it is incomplete. - static constexpr uint32_t UNKNOWN_SENTINEL = 0xfffffffe; - // indicates a key should definitely be deleted - static constexpr uint32_t DELETION_SENTINEL = 0xffffffff; - - // Errors when reading filter blocks are ignored, so we use a thread - // local variable updated via sync points to keep track of errors injected - // while reading filter blocks in order to ignore the Get/MultiGet result - // for those calls - static thread_local bool ignore_read_error; - - SharedState(Env* /*env*/, StressTest* stress_test) - : cv_(&mu_), - seed_(static_cast(FLAGS_seed)), - max_key_(FLAGS_max_key), - log2_keys_per_lock_(static_cast(FLAGS_log2_keys_per_lock)), - num_threads_(0), - num_initialized_(0), - num_populated_(0), - vote_reopen_(0), - num_done_(0), - start_(false), - start_verify_(false), - num_bg_threads_(0), - should_stop_bg_thread_(false), - bg_thread_finished_(0), - stress_test_(stress_test), - verification_failure_(false), - should_stop_test_(false), - no_overwrite_ids_(GenerateNoOverwriteIds()), - expected_state_manager_(nullptr), - printing_verification_results_(false), - start_timestamp_(Env::Default()->NowNanos()) { - Status status; - // TODO: We should introduce a way to explicitly disable verification - // during shutdown. When that is disabled and FLAGS_expected_values_dir - // is empty (disabling verification at startup), we can skip tracking - // expected state. Only then should we permit bypassing the below feature - // compatibility checks. - if (!FLAGS_expected_values_dir.empty()) { - if (!std::atomic{}.is_lock_free()) { - status = Status::InvalidArgument( - "Cannot use --expected_values_dir on platforms without lock-free " - "std::atomic"); - } - if (status.ok() && FLAGS_clear_column_family_one_in > 0) { - status = Status::InvalidArgument( - "Cannot use --expected_values_dir on when " - "--clear_column_family_one_in is greater than zero."); - } - } - if (status.ok()) { - if (FLAGS_expected_values_dir.empty()) { - expected_state_manager_.reset( - new AnonExpectedStateManager(FLAGS_max_key, FLAGS_column_families)); - } else { - expected_state_manager_.reset(new FileExpectedStateManager( - FLAGS_max_key, FLAGS_column_families, FLAGS_expected_values_dir)); - } - status = expected_state_manager_->Open(); - } - if (!status.ok()) { - fprintf(stderr, "Failed setting up expected state with error: %s\n", - status.ToString().c_str()); - exit(1); - } - - if (FLAGS_test_batches_snapshots) { - fprintf(stdout, "No lock creation because test_batches_snapshots set\n"); - return; - } - - long num_locks = static_cast(max_key_ >> log2_keys_per_lock_); - if (max_key_ & ((1 << log2_keys_per_lock_) - 1)) { - num_locks++; - } - fprintf(stdout, "Creating %ld locks\n", num_locks * FLAGS_column_families); - key_locks_.resize(FLAGS_column_families); - - for (int i = 0; i < FLAGS_column_families; ++i) { - key_locks_[i].reset(new port::Mutex[num_locks]); - } - if (FLAGS_read_fault_one_in) { -#ifdef NDEBUG - // Unsupported in release mode because it relies on - // `IGNORE_STATUS_IF_ERROR` to distinguish faults not expected to lead to - // failure. - fprintf(stderr, - "Cannot set nonzero value for --read_fault_one_in in " - "release mode."); - exit(1); -#else // NDEBUG - SyncPoint::GetInstance()->SetCallBack("FaultInjectionIgnoreError", - IgnoreReadErrorCallback); - SyncPoint::GetInstance()->EnableProcessing(); -#endif // NDEBUG - } - } - - ~SharedState() { -#ifndef NDEBUG - if (FLAGS_read_fault_one_in) { - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - } -#endif - } - - port::Mutex* GetMutex() { return &mu_; } - - port::CondVar* GetCondVar() { return &cv_; } - - StressTest* GetStressTest() const { return stress_test_; } - - int64_t GetMaxKey() const { return max_key_; } - - uint32_t GetNumThreads() const { return num_threads_; } - - void SetThreads(int num_threads) { num_threads_ = num_threads; } - - void IncInitialized() { num_initialized_++; } - - void IncOperated() { num_populated_++; } - - void IncDone() { num_done_++; } - - void IncVotedReopen() { vote_reopen_ = (vote_reopen_ + 1) % num_threads_; } - - bool AllInitialized() const { return num_initialized_ >= num_threads_; } - - bool AllOperated() const { return num_populated_ >= num_threads_; } - - bool AllDone() const { return num_done_ >= num_threads_; } - - bool AllVotedReopen() { return (vote_reopen_ == 0); } - - void SetStart() { start_ = true; } - - void SetStartVerify() { start_verify_ = true; } - - bool Started() const { return start_; } - - bool VerifyStarted() const { return start_verify_; } - - void SetVerificationFailure() { verification_failure_.store(true); } - - bool HasVerificationFailedYet() const { return verification_failure_.load(); } - - void SetShouldStopTest() { should_stop_test_.store(true); } - - bool ShouldStopTest() const { return should_stop_test_.load(); } - - // Returns a lock covering `key` in `cf`. - port::Mutex* GetMutexForKey(int cf, int64_t key) { - return &key_locks_[cf][key >> log2_keys_per_lock_]; - } - - // Acquires locks for all keys in `cf`. - void LockColumnFamily(int cf) { - for (int i = 0; i < max_key_ >> log2_keys_per_lock_; ++i) { - key_locks_[cf][i].Lock(); - } - } - - // Releases locks for all keys in `cf`. - void UnlockColumnFamily(int cf) { - for (int i = 0; i < max_key_ >> log2_keys_per_lock_; ++i) { - key_locks_[cf][i].Unlock(); - } - } - - // Returns a collection of mutex locks covering the key range [start, end) in - // `cf`. - std::vector> GetLocksForKeyRange(int cf, - int64_t start, - int64_t end) { - std::vector> range_locks; - - if (start >= end) { - return range_locks; - } - - const int64_t start_idx = start >> log2_keys_per_lock_; - - int64_t end_idx = end >> log2_keys_per_lock_; - if ((end & ((1 << log2_keys_per_lock_) - 1)) == 0) { - --end_idx; - } - - for (int64_t idx = start_idx; idx <= end_idx; ++idx) { - range_locks.emplace_back( - std::make_unique(&key_locks_[cf][idx])); - } - - return range_locks; - } - - Status SaveAtAndAfter(DB* db) { - return expected_state_manager_->SaveAtAndAfter(db); - } - - bool HasHistory() { return expected_state_manager_->HasHistory(); } - - Status Restore(DB* db) { return expected_state_manager_->Restore(db); } - - // Requires external locking covering all keys in `cf`. - void ClearColumnFamily(int cf) { - return expected_state_manager_->ClearColumnFamily(cf); - } - - // @param pending True if the update may have started but is not yet - // guaranteed finished. This is useful for crash-recovery testing when the - // process may crash before updating the expected values array. - // - // Requires external locking covering `key` in `cf`. - void Put(int cf, int64_t key, uint32_t value_base, bool pending) { - return expected_state_manager_->Put(cf, key, value_base, pending); - } - - // Requires external locking covering `key` in `cf`. - uint32_t Get(int cf, int64_t key) const { - return expected_state_manager_->Get(cf, key); - } - - // @param pending See comment above Put() - // Returns true if the key was not yet deleted. - // - // Requires external locking covering `key` in `cf`. - bool Delete(int cf, int64_t key, bool pending) { - return expected_state_manager_->Delete(cf, key, pending); - } - - // @param pending See comment above Put() - // Returns true if the key was not yet deleted. - // - // Requires external locking covering `key` in `cf`. - bool SingleDelete(int cf, int64_t key, bool pending) { - return expected_state_manager_->Delete(cf, key, pending); - } - - // @param pending See comment above Put() - // Returns number of keys deleted by the call. - // - // Requires external locking covering keys in `[begin_key, end_key)` in `cf`. - int DeleteRange(int cf, int64_t begin_key, int64_t end_key, bool pending) { - return expected_state_manager_->DeleteRange(cf, begin_key, end_key, - pending); - } - - bool AllowsOverwrite(int64_t key) const { - return no_overwrite_ids_.find(key) == no_overwrite_ids_.end(); - } - - // Requires external locking covering `key` in `cf`. - bool Exists(int cf, int64_t key) { - return expected_state_manager_->Exists(cf, key); - } - - uint32_t GetSeed() const { return seed_; } - - void SetShouldStopBgThread() { should_stop_bg_thread_ = true; } - - bool ShouldStopBgThread() { return should_stop_bg_thread_; } - - void IncBgThreads() { ++num_bg_threads_; } - - void IncBgThreadsFinished() { ++bg_thread_finished_; } - - bool BgThreadsFinished() const { - return bg_thread_finished_ == num_bg_threads_; - } - - bool ShouldVerifyAtBeginning() const { - return !FLAGS_expected_values_dir.empty(); - } - - bool PrintingVerificationResults() { - bool tmp = false; - return !printing_verification_results_.compare_exchange_strong( - tmp, true, std::memory_order_relaxed); - } - - void FinishPrintingVerificationResults() { - printing_verification_results_.store(false, std::memory_order_relaxed); - } - - uint64_t GetStartTimestamp() const { return start_timestamp_; } - - private: - static void IgnoreReadErrorCallback(void*) { ignore_read_error = true; } - - // Pick random keys in each column family that will not experience overwrite. - std::unordered_set GenerateNoOverwriteIds() const { - fprintf(stdout, "Choosing random keys with no overwrite\n"); - // Start with the identity permutation. Subsequent iterations of - // for loop below will start with perm of previous for loop - std::vector permutation(max_key_); - for (int64_t i = 0; i < max_key_; ++i) { - permutation[i] = i; - } - // Now do the Knuth shuffle - const int64_t num_no_overwrite_keys = - (max_key_ * FLAGS_nooverwritepercent) / 100; - // Only need to figure out first num_no_overwrite_keys of permutation - std::unordered_set ret; - ret.reserve(num_no_overwrite_keys); - Random64 rnd(seed_); - for (int64_t i = 0; i < num_no_overwrite_keys; i++) { - assert(i < max_key_); - int64_t rand_index = i + rnd.Next() % (max_key_ - i); - // Swap i and rand_index; - int64_t temp = permutation[i]; - permutation[i] = permutation[rand_index]; - permutation[rand_index] = temp; - // Fill no_overwrite_ids_ with the first num_no_overwrite_keys of - // permutation - ret.insert(permutation[i]); - } - return ret; - } - - port::Mutex mu_; - port::CondVar cv_; - const uint32_t seed_; - const int64_t max_key_; - const uint32_t log2_keys_per_lock_; - int num_threads_; - long num_initialized_; - long num_populated_; - long vote_reopen_; - long num_done_; - bool start_; - bool start_verify_; - int num_bg_threads_; - bool should_stop_bg_thread_; - int bg_thread_finished_; - StressTest* stress_test_; - std::atomic verification_failure_; - std::atomic should_stop_test_; - - // Keys that should not be overwritten - const std::unordered_set no_overwrite_ids_; - - std::unique_ptr expected_state_manager_; - // Cannot store `port::Mutex` directly in vector since it is not copyable - // and storing it in the container may require copying depending on the impl. - std::vector> key_locks_; - std::atomic printing_verification_results_; - const uint64_t start_timestamp_; -}; - -// Per-thread state for concurrent executions of the same benchmark. -struct ThreadState { - uint32_t tid; // 0..n-1 - Random rand; // Has different seeds for different threads - SharedState* shared; - Stats stats; - struct SnapshotState { - const Snapshot* snapshot; - // The cf from which we did a Get at this snapshot - int cf_at; - // The name of the cf at the time that we did a read - std::string cf_at_name; - // The key with which we did a Get at this snapshot - std::string key; - // The status of the Get - Status status; - // The value of the Get - std::string value; - // optional state of all keys in the db - std::vector* key_vec; - - std::string timestamp; - }; - std::queue> snapshot_queue; - - ThreadState(uint32_t index, SharedState* _shared) - : tid(index), rand(1000 + index + _shared->GetSeed()), shared(_shared) {} -}; -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_stat.cc b/db_stress_tool/db_stress_stat.cc deleted file mode 100644 index 6a7883a52..000000000 --- a/db_stress_tool/db_stress_stat.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#ifdef GFLAGS - -#include "db_stress_tool/db_stress_stat.h" - -namespace ROCKSDB_NAMESPACE { - -std::shared_ptr dbstats; -std::shared_ptr dbstats_secondaries; - -} // namespace ROCKSDB_NAMESPACE - -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_stat.h b/db_stress_tool/db_stress_stat.h deleted file mode 100644 index 5b38c6e2b..000000000 --- a/db_stress_tool/db_stress_stat.h +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#pragma once -#include -#include -#include -#include - -#include "monitoring/histogram.h" -#include "port/port.h" -#include "rocksdb/snapshot.h" -#include "rocksdb/statistics.h" -#include "rocksdb/system_clock.h" -#include "util/gflags_compat.h" -#include "util/random.h" - -DECLARE_bool(histogram); -DECLARE_bool(progress_reports); - -namespace ROCKSDB_NAMESPACE { - -// Database statistics -extern std::shared_ptr dbstats; -extern std::shared_ptr dbstats_secondaries; - -class Stats { - private: - uint64_t start_; - uint64_t finish_; - double seconds_; - long done_; - long gets_; - long prefixes_; - long writes_; - long deletes_; - size_t single_deletes_; - long iterator_size_sums_; - long founds_; - long iterations_; - long range_deletions_; - long covered_by_range_deletions_; - long errors_; - long verified_errors_; - long num_compact_files_succeed_; - long num_compact_files_failed_; - int next_report_; - size_t bytes_; - uint64_t last_op_finish_; - HistogramImpl hist_; - - public: - Stats() {} - - void Start() { - next_report_ = 100; - hist_.Clear(); - done_ = 0; - gets_ = 0; - prefixes_ = 0; - writes_ = 0; - deletes_ = 0; - single_deletes_ = 0; - iterator_size_sums_ = 0; - founds_ = 0; - iterations_ = 0; - range_deletions_ = 0; - covered_by_range_deletions_ = 0; - errors_ = 0; - verified_errors_ = 0; - bytes_ = 0; - seconds_ = 0; - num_compact_files_succeed_ = 0; - num_compact_files_failed_ = 0; - start_ = SystemClock::Default()->NowMicros(); - last_op_finish_ = start_; - finish_ = start_; - } - - void Merge(const Stats& other) { - hist_.Merge(other.hist_); - done_ += other.done_; - gets_ += other.gets_; - prefixes_ += other.prefixes_; - writes_ += other.writes_; - deletes_ += other.deletes_; - single_deletes_ += other.single_deletes_; - iterator_size_sums_ += other.iterator_size_sums_; - founds_ += other.founds_; - iterations_ += other.iterations_; - range_deletions_ += other.range_deletions_; - covered_by_range_deletions_ = other.covered_by_range_deletions_; - errors_ += other.errors_; - verified_errors_ += other.verified_errors_; - bytes_ += other.bytes_; - seconds_ += other.seconds_; - num_compact_files_succeed_ += other.num_compact_files_succeed_; - num_compact_files_failed_ += other.num_compact_files_failed_; - if (other.start_ < start_) start_ = other.start_; - if (other.finish_ > finish_) finish_ = other.finish_; - } - - void Stop() { - finish_ = SystemClock::Default()->NowMicros(); - seconds_ = (finish_ - start_) * 1e-6; - } - - void FinishedSingleOp() { - if (FLAGS_histogram) { - auto now = SystemClock::Default()->NowMicros(); - auto micros = now - last_op_finish_; - hist_.Add(micros); - if (micros > 20000) { - fprintf(stdout, "long op: %" PRIu64 " micros%30s\r", micros, ""); - } - last_op_finish_ = now; - } - - done_++; - if (FLAGS_progress_reports) { - if (done_ >= next_report_) { - if (next_report_ < 1000) - next_report_ += 100; - else if (next_report_ < 5000) - next_report_ += 500; - else if (next_report_ < 10000) - next_report_ += 1000; - else if (next_report_ < 50000) - next_report_ += 5000; - else if (next_report_ < 100000) - next_report_ += 10000; - else if (next_report_ < 500000) - next_report_ += 50000; - else - next_report_ += 100000; - fprintf(stdout, "... finished %ld ops%30s\r", done_, ""); - } - } - } - - void AddBytesForWrites(long nwrites, size_t nbytes) { - writes_ += nwrites; - bytes_ += nbytes; - } - - void AddGets(long ngets, long nfounds) { - founds_ += nfounds; - gets_ += ngets; - } - - void AddPrefixes(long nprefixes, long count) { - prefixes_ += nprefixes; - iterator_size_sums_ += count; - } - - void AddIterations(long n) { iterations_ += n; } - - void AddDeletes(long n) { deletes_ += n; } - - void AddSingleDeletes(size_t n) { single_deletes_ += n; } - - void AddRangeDeletions(long n) { range_deletions_ += n; } - - void AddCoveredByRangeDeletions(long n) { covered_by_range_deletions_ += n; } - - void AddErrors(long n) { errors_ += n; } - - void AddVerifiedErrors(long n) { verified_errors_ += n; } - - void AddNumCompactFilesSucceed(long n) { num_compact_files_succeed_ += n; } - - void AddNumCompactFilesFailed(long n) { num_compact_files_failed_ += n; } - - void Report(const char* name) { - std::string extra; - if (bytes_ < 1 || done_ < 1) { - fprintf(stderr, "No writes or ops?\n"); - return; - } - - double elapsed = (finish_ - start_) * 1e-6; - double bytes_mb = bytes_ / 1048576.0; - double rate = bytes_mb / elapsed; - double throughput = (double)done_ / elapsed; - - fprintf(stdout, "%-12s: ", name); - fprintf(stdout, "%.3f micros/op %ld ops/sec\n", seconds_ * 1e6 / done_, - (long)throughput); - fprintf(stdout, "%-12s: Wrote %.2f MB (%.2f MB/sec) (%ld%% of %ld ops)\n", - "", bytes_mb, rate, (100 * writes_) / done_, done_); - fprintf(stdout, "%-12s: Wrote %ld times\n", "", writes_); - fprintf(stdout, "%-12s: Deleted %ld times\n", "", deletes_); - fprintf(stdout, "%-12s: Single deleted %" ROCKSDB_PRIszt " times\n", "", - single_deletes_); - fprintf(stdout, "%-12s: %ld read and %ld found the key\n", "", gets_, - founds_); - fprintf(stdout, "%-12s: Prefix scanned %ld times\n", "", prefixes_); - fprintf(stdout, "%-12s: Iterator size sum is %ld\n", "", - iterator_size_sums_); - fprintf(stdout, "%-12s: Iterated %ld times\n", "", iterations_); - fprintf(stdout, "%-12s: Deleted %ld key-ranges\n", "", range_deletions_); - fprintf(stdout, "%-12s: Range deletions covered %ld keys\n", "", - covered_by_range_deletions_); - - fprintf(stdout, "%-12s: Got errors %ld times\n", "", errors_); - fprintf(stdout, "%-12s: %ld CompactFiles() succeed\n", "", - num_compact_files_succeed_); - fprintf(stdout, "%-12s: %ld CompactFiles() did not succeed\n", "", - num_compact_files_failed_); - - if (FLAGS_histogram) { - fprintf(stdout, "Microseconds per op:\n%s\n", hist_.ToString().c_str()); - } - fflush(stdout); - } -}; -} // namespace ROCKSDB_NAMESPACE diff --git a/db_stress_tool/db_stress_table_properties_collector.h b/db_stress_tool/db_stress_table_properties_collector.h deleted file mode 100644 index d1758cbb4..000000000 --- a/db_stress_tool/db_stress_table_properties_collector.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#pragma once - -#include "rocksdb/table.h" -#include "util/gflags_compat.h" -#include "util/random.h" - -DECLARE_int32(mark_for_compaction_one_file_in); - -namespace ROCKSDB_NAMESPACE { - -// A `DbStressTablePropertiesCollector` ignores what keys/values were added to -// the table, adds no properties to the table, and decides at random whether the -// table will be marked for compaction according to -// `FLAGS_mark_for_compaction_one_file_in`. -class DbStressTablePropertiesCollector : public TablePropertiesCollector { - public: - DbStressTablePropertiesCollector() - : need_compact_(Random::GetTLSInstance()->OneInOpt( - FLAGS_mark_for_compaction_one_file_in)) {} - - virtual Status AddUserKey(const Slice& /* key */, const Slice& /* value */, - EntryType /*type*/, SequenceNumber /*seq*/, - uint64_t /*file_size*/) override { - return Status::OK(); - } - - virtual Status Finish(UserCollectedProperties* /* properties */) override { - return Status::OK(); - } - - virtual UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } - - virtual const char* Name() const override { - return "DbStressTablePropertiesCollector"; - } - - virtual bool NeedCompact() const override { return need_compact_; } - - private: - const bool need_compact_; -}; - -// A `DbStressTablePropertiesCollectorFactory` creates -// `DbStressTablePropertiesCollectorFactory`s. -class DbStressTablePropertiesCollectorFactory - : public TablePropertiesCollectorFactory { - public: - virtual TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /* context */) override { - return new DbStressTablePropertiesCollector(); - } - - virtual const char* Name() const override { - return "DbStressTablePropertiesCollectorFactory"; - } -}; - -} // namespace ROCKSDB_NAMESPACE diff --git a/db_stress_tool/db_stress_test_base.cc b/db_stress_tool/db_stress_test_base.cc deleted file mode 100644 index 610826f4b..000000000 --- a/db_stress_tool/db_stress_test_base.cc +++ /dev/null @@ -1,3282 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// - -#include -#include - -#include "util/compression.h" -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" -#include "db_stress_tool/db_stress_compaction_filter.h" -#include "db_stress_tool/db_stress_driver.h" -#include "db_stress_tool/db_stress_table_properties_collector.h" -#include "rocksdb/convenience.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/secondary_cache.h" -#include "rocksdb/sst_file_manager.h" -#include "rocksdb/types.h" -#include "rocksdb/utilities/object_registry.h" -#include "rocksdb/utilities/write_batch_with_index.h" -#include "test_util/testutil.h" -#include "util/cast_util.h" -#include "utilities/backup/backup_engine_impl.h" -#include "utilities/fault_injection_fs.h" -#include "utilities/fault_injection_secondary_cache.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { - -std::shared_ptr CreateFilterPolicy() { - if (FLAGS_bloom_bits < 0) { - return BlockBasedTableOptions().filter_policy; - } - const FilterPolicy* new_policy; - if (FLAGS_ribbon_starting_level >= 999) { - // Use Bloom API - new_policy = NewBloomFilterPolicy(FLAGS_bloom_bits, false); - } else { - new_policy = NewRibbonFilterPolicy( - FLAGS_bloom_bits, /* bloom_before_level */ FLAGS_ribbon_starting_level); - } - return std::shared_ptr(new_policy); -} - -} // namespace - -StressTest::StressTest() - : cache_(NewCache(FLAGS_cache_size, FLAGS_cache_numshardbits)), - filter_policy_(CreateFilterPolicy()), - db_(nullptr), - txn_db_(nullptr), - - db_aptr_(nullptr), - clock_(db_stress_env->GetSystemClock().get()), - new_column_family_name_(1), - num_times_reopened_(0), - db_preload_finished_(false), - cmp_db_(nullptr), - is_db_stopped_(false) { - if (FLAGS_destroy_db_initially) { - std::vector files; - db_stress_env->GetChildren(FLAGS_db, &files); - for (unsigned int i = 0; i < files.size(); i++) { - if (Slice(files[i]).starts_with("heap-")) { - db_stress_env->DeleteFile(FLAGS_db + "/" + files[i]); - } - } - - Options options; - options.env = db_stress_env; - // Remove files without preserving manfiest files - const Status s = !FLAGS_use_blob_db - ? DestroyDB(FLAGS_db, options) - : blob_db::DestroyBlobDB(FLAGS_db, options, - blob_db::BlobDBOptions()); - - if (!s.ok()) { - fprintf(stderr, "Cannot destroy original db: %s\n", s.ToString().c_str()); - exit(1); - } - } -} - -StressTest::~StressTest() { - for (auto cf : column_families_) { - delete cf; - } - column_families_.clear(); - delete db_; - - for (auto* cf : cmp_cfhs_) { - delete cf; - } - cmp_cfhs_.clear(); - delete cmp_db_; -} - -std::shared_ptr StressTest::NewCache(size_t capacity, - int32_t num_shard_bits) { - ConfigOptions config_options; - if (capacity <= 0) { - return nullptr; - } - - std::shared_ptr secondary_cache; - if (!FLAGS_secondary_cache_uri.empty()) { - Status s = SecondaryCache::CreateFromString( - config_options, FLAGS_secondary_cache_uri, &secondary_cache); - if (secondary_cache == nullptr) { - fprintf(stderr, - "No secondary cache registered matching string: %s status=%s\n", - FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str()); - exit(1); - } - if (FLAGS_secondary_cache_fault_one_in > 0) { - secondary_cache = std::make_shared( - secondary_cache, static_cast(FLAGS_seed), - FLAGS_secondary_cache_fault_one_in); - } - } - - if (FLAGS_cache_type == "clock_cache") { - fprintf(stderr, "Old clock cache implementation has been removed.\n"); - exit(1); - } else if (FLAGS_cache_type == "hyper_clock_cache") { - HyperClockCacheOptions opts(static_cast(capacity), - FLAGS_block_size /*estimated_entry_charge*/, - num_shard_bits); - opts.secondary_cache = std::move(secondary_cache); - return opts.MakeSharedCache(); - } else if (FLAGS_cache_type == "lru_cache") { - LRUCacheOptions opts; - opts.capacity = capacity; - opts.num_shard_bits = num_shard_bits; - opts.secondary_cache = std::move(secondary_cache); - return NewLRUCache(opts); - } else { - fprintf(stderr, "Cache type not supported."); - exit(1); - } -} - -std::vector StressTest::GetBlobCompressionTags() { - std::vector compression_tags{"kNoCompression"}; - - if (Snappy_Supported()) { - compression_tags.emplace_back("kSnappyCompression"); - } - if (LZ4_Supported()) { - compression_tags.emplace_back("kLZ4Compression"); - } - if (ZSTD_Supported()) { - compression_tags.emplace_back("kZSTD"); - } - - return compression_tags; -} - -bool StressTest::BuildOptionsTable() { - if (FLAGS_set_options_one_in <= 0) { - return true; - } - - std::unordered_map> options_tbl = { - {"write_buffer_size", - {std::to_string(options_.write_buffer_size), - std::to_string(options_.write_buffer_size * 2), - std::to_string(options_.write_buffer_size * 4)}}, - {"max_write_buffer_number", - {std::to_string(options_.max_write_buffer_number), - std::to_string(options_.max_write_buffer_number * 2), - std::to_string(options_.max_write_buffer_number * 4)}}, - {"arena_block_size", - { - std::to_string(options_.arena_block_size), - std::to_string(options_.write_buffer_size / 4), - std::to_string(options_.write_buffer_size / 8), - }}, - {"memtable_huge_page_size", {"0", std::to_string(2 * 1024 * 1024)}}, - {"max_successive_merges", {"0", "2", "4"}}, - {"inplace_update_num_locks", {"100", "200", "300"}}, - // TODO: re-enable once internal task T124324915 is fixed. - // {"experimental_mempurge_threshold", {"0.0", "1.0"}}, - // TODO(ljin): enable test for this option - // {"disable_auto_compactions", {"100", "200", "300"}}, - {"level0_file_num_compaction_trigger", - { - std::to_string(options_.level0_file_num_compaction_trigger), - std::to_string(options_.level0_file_num_compaction_trigger + 2), - std::to_string(options_.level0_file_num_compaction_trigger + 4), - }}, - {"level0_slowdown_writes_trigger", - { - std::to_string(options_.level0_slowdown_writes_trigger), - std::to_string(options_.level0_slowdown_writes_trigger + 2), - std::to_string(options_.level0_slowdown_writes_trigger + 4), - }}, - {"level0_stop_writes_trigger", - { - std::to_string(options_.level0_stop_writes_trigger), - std::to_string(options_.level0_stop_writes_trigger + 2), - std::to_string(options_.level0_stop_writes_trigger + 4), - }}, - {"max_compaction_bytes", - { - std::to_string(options_.target_file_size_base * 5), - std::to_string(options_.target_file_size_base * 15), - std::to_string(options_.target_file_size_base * 100), - }}, - {"target_file_size_base", - { - std::to_string(options_.target_file_size_base), - std::to_string(options_.target_file_size_base * 2), - std::to_string(options_.target_file_size_base * 4), - }}, - {"target_file_size_multiplier", - { - std::to_string(options_.target_file_size_multiplier), - "1", - "2", - }}, - {"max_bytes_for_level_base", - { - std::to_string(options_.max_bytes_for_level_base / 2), - std::to_string(options_.max_bytes_for_level_base), - std::to_string(options_.max_bytes_for_level_base * 2), - }}, - {"max_bytes_for_level_multiplier", - { - std::to_string(options_.max_bytes_for_level_multiplier), - "1", - "2", - }}, - {"max_sequential_skip_in_iterations", {"4", "8", "12"}}, - }; - - if (FLAGS_allow_setting_blob_options_dynamically) { - options_tbl.emplace("enable_blob_files", - std::vector{"false", "true"}); - options_tbl.emplace("min_blob_size", - std::vector{"0", "8", "16"}); - options_tbl.emplace("blob_file_size", - std::vector{"1M", "16M", "256M", "1G"}); - options_tbl.emplace("blob_compression_type", GetBlobCompressionTags()); - options_tbl.emplace("enable_blob_garbage_collection", - std::vector{"false", "true"}); - options_tbl.emplace( - "blob_garbage_collection_age_cutoff", - std::vector{"0.0", "0.25", "0.5", "0.75", "1.0"}); - options_tbl.emplace("blob_garbage_collection_force_threshold", - std::vector{"0.5", "0.75", "1.0"}); - options_tbl.emplace("blob_compaction_readahead_size", - std::vector{"0", "1M", "4M"}); - options_tbl.emplace("blob_file_starting_level", - std::vector{"0", "1", "2"}); - options_tbl.emplace("prepopulate_blob_cache", - std::vector{"kDisable", "kFlushOnly"}); - } - - options_table_ = std::move(options_tbl); - - for (const auto& iter : options_table_) { - options_index_.push_back(iter.first); - } - return true; -} - -void StressTest::InitDb(SharedState* shared) { - uint64_t now = clock_->NowMicros(); - fprintf(stdout, "%s Initializing db_stress\n", - clock_->TimeToString(now / 1000000).c_str()); - PrintEnv(); - Open(shared); - BuildOptionsTable(); -} - -void StressTest::FinishInitDb(SharedState* shared) { - if (FLAGS_read_only) { - uint64_t now = clock_->NowMicros(); - fprintf(stdout, "%s Preloading db with %" PRIu64 " KVs\n", - clock_->TimeToString(now / 1000000).c_str(), FLAGS_max_key); - PreloadDbAndReopenAsReadOnly(FLAGS_max_key, shared); - } - - if (shared->HasHistory()) { - // The way it works right now is, if there's any history, that means the - // previous run mutating the DB had all its operations traced, in which case - // we should always be able to `Restore()` the expected values to match the - // `db_`'s current seqno. - Status s = shared->Restore(db_); - if (!s.ok()) { - fprintf(stderr, "Error restoring historical expected values: %s\n", - s.ToString().c_str()); - exit(1); - } - } - if (FLAGS_use_txn) { - // It's OK here without sync because unsynced data cannot be lost at this - // point - // - even with sync_fault_injection=1 as the - // file is still directly writable until after FinishInitDb() - ProcessRecoveredPreparedTxns(shared); - } - - if (FLAGS_enable_compaction_filter) { - auto* compaction_filter_factory = - reinterpret_cast( - options_.compaction_filter_factory.get()); - assert(compaction_filter_factory); - // This must be called only after any potential `SharedState::Restore()` has - // completed in order for the `compaction_filter_factory` to operate on the - // correct latest values file. - compaction_filter_factory->SetSharedState(shared); - fprintf(stdout, "Compaction filter factory: %s\n", - compaction_filter_factory->Name()); - } -} - -void StressTest::TrackExpectedState(SharedState* shared) { - // For `FLAGS_manual_wal_flush_one_inWAL` - // data can be lost when `manual_wal_flush_one_in > 0` and `FlushWAL()` is not - // explictly called by users of RocksDB (in our case, db stress). - // Therefore recovery from such potential WAL data loss is a prefix recovery - // that requires tracing - if ((FLAGS_sync_fault_injection || FLAGS_disable_wal || - FLAGS_manual_wal_flush_one_in > 0) && - IsStateTracked()) { - Status s = shared->SaveAtAndAfter(db_); - if (!s.ok()) { - fprintf(stderr, "Error enabling history tracing: %s\n", - s.ToString().c_str()); - exit(1); - } - } -} - -Status StressTest::AssertSame(DB* db, ColumnFamilyHandle* cf, - ThreadState::SnapshotState& snap_state) { - Status s; - if (cf->GetName() != snap_state.cf_at_name) { - return s; - } - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ropt; - ropt.snapshot = snap_state.snapshot; - Slice ts; - if (!snap_state.timestamp.empty()) { - ts = snap_state.timestamp; - ropt.timestamp = &ts; - } - PinnableSlice exp_v(&snap_state.value); - exp_v.PinSelf(); - PinnableSlice v; - s = db->Get(ropt, cf, snap_state.key, &v); - if (!s.ok() && !s.IsNotFound()) { - return s; - } - if (snap_state.status != s) { - return Status::Corruption( - "The snapshot gave inconsistent results for key " + - std::to_string(Hash(snap_state.key.c_str(), snap_state.key.size(), 0)) + - " in cf " + cf->GetName() + ": (" + snap_state.status.ToString() + - ") vs. (" + s.ToString() + ")"); - } - if (s.ok()) { - if (exp_v != v) { - return Status::Corruption("The snapshot gave inconsistent values: (" + - exp_v.ToString() + ") vs. (" + v.ToString() + - ")"); - } - } - if (snap_state.key_vec != nullptr) { - // When `prefix_extractor` is set, seeking to beginning and scanning - // across prefixes are only supported with `total_order_seek` set. - ropt.total_order_seek = true; - std::unique_ptr iterator(db->NewIterator(ropt)); - std::unique_ptr> tmp_bitvec( - new std::vector(FLAGS_max_key)); - for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { - uint64_t key_val; - if (GetIntVal(iterator->key().ToString(), &key_val)) { - (*tmp_bitvec.get())[key_val] = true; - } - } - if (!std::equal(snap_state.key_vec->begin(), snap_state.key_vec->end(), - tmp_bitvec.get()->begin())) { - return Status::Corruption("Found inconsistent keys at this snapshot"); - } - } - return Status::OK(); -} - -void StressTest::VerificationAbort(SharedState* shared, std::string msg, - Status s) const { - fprintf(stderr, "Verification failed: %s. Status is %s\n", msg.c_str(), - s.ToString().c_str()); - shared->SetVerificationFailure(); -} - -void StressTest::VerificationAbort(SharedState* shared, std::string msg, int cf, - int64_t key) const { - auto key_str = Key(key); - Slice key_slice = key_str; - fprintf(stderr, - "Verification failed for column family %d key %s (%" PRIi64 "): %s\n", - cf, key_slice.ToString(true).c_str(), key, msg.c_str()); - shared->SetVerificationFailure(); -} - -void StressTest::VerificationAbort(SharedState* shared, std::string msg, int cf, - int64_t key, Slice value_from_db, - Slice value_from_expected) const { - auto key_str = Key(key); - fprintf(stderr, - "Verification failed for column family %d key %s (%" PRIi64 - "): value_from_db: %s, value_from_expected: %s, msg: %s\n", - cf, Slice(key_str).ToString(true).c_str(), key, - value_from_db.ToString(true).c_str(), - value_from_expected.ToString(true).c_str(), msg.c_str()); - shared->SetVerificationFailure(); -} - -void StressTest::VerificationAbort(SharedState* shared, int cf, int64_t key, - const Slice& value, - const WideColumns& columns) const { - assert(shared); - - auto key_str = Key(key); - - fprintf(stderr, - "Verification failed for column family %d key %s (%" PRIi64 - "): Value and columns inconsistent: value: %s, columns: %s\n", - cf, Slice(key_str).ToString(/* hex */ true).c_str(), key, - value.ToString(/* hex */ true).c_str(), - WideColumnsToHex(columns).c_str()); - - shared->SetVerificationFailure(); -} - -std::string StressTest::DebugString(const Slice& value, - const WideColumns& columns) { - std::ostringstream oss; - - oss << "value: " << value.ToString(/* hex */ true) - << ", columns: " << WideColumnsToHex(columns); - - return oss.str(); -} - -void StressTest::PrintStatistics() { - if (dbstats) { - fprintf(stdout, "STATISTICS:\n%s\n", dbstats->ToString().c_str()); - } - if (dbstats_secondaries) { - fprintf(stdout, "Secondary instances STATISTICS:\n%s\n", - dbstats_secondaries->ToString().c_str()); - } -} - -// Currently PreloadDb has to be single-threaded. -void StressTest::PreloadDbAndReopenAsReadOnly(int64_t number_of_keys, - SharedState* shared) { - WriteOptions write_opts; - write_opts.disableWAL = FLAGS_disable_wal; - if (FLAGS_sync) { - write_opts.sync = true; - } - if (FLAGS_rate_limit_auto_wal_flush) { - write_opts.rate_limiter_priority = Env::IO_USER; - } - char value[100]; - int cf_idx = 0; - Status s; - for (auto cfh : column_families_) { - for (int64_t k = 0; k != number_of_keys; ++k) { - const std::string key = Key(k); - - constexpr uint32_t value_base = 0; - const size_t sz = GenerateValue(value_base, value, sizeof(value)); - - const Slice v(value, sz); - - shared->Put(cf_idx, k, value_base, true /* pending */); - - std::string ts; - if (FLAGS_user_timestamp_size > 0) { - ts = GetNowNanos(); - } - - if (FLAGS_use_merge) { - if (!FLAGS_use_txn) { - if (FLAGS_user_timestamp_size > 0) { - s = db_->Merge(write_opts, cfh, key, ts, v); - } else { - s = db_->Merge(write_opts, cfh, key, v); - } - } else { - Transaction* txn; - s = NewTxn(write_opts, &txn); - if (s.ok()) { - s = txn->Merge(cfh, key, v); - if (s.ok()) { - s = CommitTxn(txn); - } - } - } - } else if (FLAGS_use_put_entity_one_in > 0) { - s = db_->PutEntity(write_opts, cfh, key, - GenerateWideColumns(value_base, v)); - } else { - if (!FLAGS_use_txn) { - if (FLAGS_user_timestamp_size > 0) { - s = db_->Put(write_opts, cfh, key, ts, v); - } else { - s = db_->Put(write_opts, cfh, key, v); - } - } else { - Transaction* txn; - s = NewTxn(write_opts, &txn); - if (s.ok()) { - s = txn->Put(cfh, key, v); - if (s.ok()) { - s = CommitTxn(txn); - } - } - } - } - - shared->Put(cf_idx, k, value_base, false /* pending */); - if (!s.ok()) { - break; - } - } - if (!s.ok()) { - break; - } - ++cf_idx; - } - if (s.ok()) { - s = db_->Flush(FlushOptions(), column_families_); - } - if (s.ok()) { - for (auto cf : column_families_) { - delete cf; - } - column_families_.clear(); - delete db_; - db_ = nullptr; - txn_db_ = nullptr; - - db_preload_finished_.store(true); - auto now = clock_->NowMicros(); - fprintf(stdout, "%s Reopening database in read-only\n", - clock_->TimeToString(now / 1000000).c_str()); - // Reopen as read-only, can ignore all options related to updates - Open(shared); - } else { - fprintf(stderr, "Failed to preload db"); - exit(1); - } -} - -Status StressTest::SetOptions(ThreadState* thread) { - assert(FLAGS_set_options_one_in > 0); - std::unordered_map opts; - std::string name = - options_index_[thread->rand.Next() % options_index_.size()]; - int value_idx = thread->rand.Next() % options_table_[name].size(); - if (name == "level0_file_num_compaction_trigger" || - name == "level0_slowdown_writes_trigger" || - name == "level0_stop_writes_trigger") { - opts["level0_file_num_compaction_trigger"] = - options_table_["level0_file_num_compaction_trigger"][value_idx]; - opts["level0_slowdown_writes_trigger"] = - options_table_["level0_slowdown_writes_trigger"][value_idx]; - opts["level0_stop_writes_trigger"] = - options_table_["level0_stop_writes_trigger"][value_idx]; - } else { - opts[name] = options_table_[name][value_idx]; - } - - int rand_cf_idx = thread->rand.Next() % FLAGS_column_families; - auto cfh = column_families_[rand_cf_idx]; - return db_->SetOptions(cfh, opts); -} - -void StressTest::ProcessRecoveredPreparedTxns(SharedState* shared) { - assert(txn_db_); - std::vector recovered_prepared_trans; - txn_db_->GetAllPreparedTransactions(&recovered_prepared_trans); - for (Transaction* txn : recovered_prepared_trans) { - ProcessRecoveredPreparedTxnsHelper(txn, shared); - delete txn; - } - recovered_prepared_trans.clear(); - txn_db_->GetAllPreparedTransactions(&recovered_prepared_trans); - assert(recovered_prepared_trans.size() == 0); -} - -void StressTest::ProcessRecoveredPreparedTxnsHelper(Transaction* txn, - SharedState* shared) { - thread_local Random rand(static_cast(FLAGS_seed)); - for (size_t i = 0; i < column_families_.size(); ++i) { - std::unique_ptr wbwi_iter( - txn->GetWriteBatch()->NewIterator(column_families_[i])); - for (wbwi_iter->SeekToFirst(); wbwi_iter->Valid(); wbwi_iter->Next()) { - uint64_t key_val; - if (GetIntVal(wbwi_iter->Entry().key.ToString(), &key_val)) { - shared->Put(static_cast(i) /* cf_idx */, key_val, - 0 /* value_base */, true /* pending */); - } - } - } - if (rand.OneIn(2)) { - Status s = txn->Commit(); - assert(s.ok()); - } else { - Status s = txn->Rollback(); - assert(s.ok()); - } -} - -Status StressTest::NewTxn(WriteOptions& write_opts, Transaction** txn) { - if (!FLAGS_use_txn) { - return Status::InvalidArgument("NewTxn when FLAGS_use_txn is not set"); - } - write_opts.disableWAL = FLAGS_disable_wal; - static std::atomic txn_id = {0}; - TransactionOptions txn_options; - txn_options.use_only_the_last_commit_time_batch_for_recovery = - FLAGS_use_only_the_last_commit_time_batch_for_recovery; - txn_options.lock_timeout = 600000; // 10 min - txn_options.deadlock_detect = true; - *txn = txn_db_->BeginTransaction(write_opts, txn_options); - auto istr = std::to_string(txn_id.fetch_add(1)); - Status s = (*txn)->SetName("xid" + istr); - return s; -} - -Status StressTest::CommitTxn(Transaction* txn, ThreadState* thread) { - if (!FLAGS_use_txn) { - return Status::InvalidArgument("CommitTxn when FLAGS_use_txn is not set"); - } - assert(txn_db_); - Status s = txn->Prepare(); - std::shared_ptr timestamped_snapshot; - if (s.ok()) { - if (thread && FLAGS_create_timestamped_snapshot_one_in && - thread->rand.OneIn(FLAGS_create_timestamped_snapshot_one_in)) { - uint64_t ts = db_stress_env->NowNanos(); - s = txn->CommitAndTryCreateSnapshot(/*notifier=*/nullptr, ts, - ×tamped_snapshot); - - std::pair> res; - if (thread->tid == 0) { - uint64_t now = db_stress_env->NowNanos(); - res = txn_db_->CreateTimestampedSnapshot(now); - if (res.first.ok()) { - assert(res.second); - assert(res.second->GetTimestamp() == now); - if (timestamped_snapshot) { - assert(res.second->GetTimestamp() > - timestamped_snapshot->GetTimestamp()); - } - } else { - assert(!res.second); - } - } - } else { - s = txn->Commit(); - } - } - if (thread && FLAGS_create_timestamped_snapshot_one_in > 0 && - thread->rand.OneInOpt(50000)) { - uint64_t now = db_stress_env->NowNanos(); - constexpr uint64_t time_diff = static_cast(1000) * 1000 * 1000; - txn_db_->ReleaseTimestampedSnapshotsOlderThan(now - time_diff); - } - delete txn; - return s; -} - -Status StressTest::RollbackTxn(Transaction* txn) { - if (!FLAGS_use_txn) { - return Status::InvalidArgument( - "RollbackTxn when FLAGS_use_txn is not" - " set"); - } - Status s = txn->Rollback(); - delete txn; - return s; -} - -void StressTest::OperateDb(ThreadState* thread) { - ReadOptions read_opts(FLAGS_verify_checksum, true); - read_opts.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - read_opts.async_io = FLAGS_async_io; - read_opts.adaptive_readahead = FLAGS_adaptive_readahead; - read_opts.readahead_size = FLAGS_readahead_size; - WriteOptions write_opts; - if (FLAGS_rate_limit_auto_wal_flush) { - write_opts.rate_limiter_priority = Env::IO_USER; - } - auto shared = thread->shared; - char value[100]; - std::string from_db; - if (FLAGS_sync) { - write_opts.sync = true; - } - write_opts.disableWAL = FLAGS_disable_wal; - write_opts.protection_bytes_per_key = FLAGS_batch_protection_bytes_per_key; - const int prefix_bound = static_cast(FLAGS_readpercent) + - static_cast(FLAGS_prefixpercent); - const int write_bound = prefix_bound + static_cast(FLAGS_writepercent); - const int del_bound = write_bound + static_cast(FLAGS_delpercent); - const int delrange_bound = - del_bound + static_cast(FLAGS_delrangepercent); - const int iterate_bound = - delrange_bound + static_cast(FLAGS_iterpercent); - - const uint64_t ops_per_open = FLAGS_ops_per_thread / (FLAGS_reopen + 1); - -#ifndef NDEBUG - if (FLAGS_read_fault_one_in) { - fault_fs_guard->SetThreadLocalReadErrorContext(thread->shared->GetSeed(), - FLAGS_read_fault_one_in); - } -#endif // NDEBUG - if (FLAGS_write_fault_one_in) { - IOStatus error_msg; - if (FLAGS_injest_error_severity <= 1 || FLAGS_injest_error_severity > 2) { - error_msg = IOStatus::IOError("Retryable IO Error"); - error_msg.SetRetryable(true); - } else if (FLAGS_injest_error_severity == 2) { - // Ingest the fatal error - error_msg = IOStatus::IOError("Fatal IO Error"); - error_msg.SetDataLoss(true); - } - std::vector types = {FileType::kTableFile, - FileType::kDescriptorFile, - FileType::kCurrentFile}; - fault_fs_guard->SetRandomWriteError( - thread->shared->GetSeed(), FLAGS_write_fault_one_in, error_msg, - /*inject_for_all_file_types=*/false, types); - } - thread->stats.Start(); - for (int open_cnt = 0; open_cnt <= FLAGS_reopen; ++open_cnt) { - if (thread->shared->HasVerificationFailedYet() || - thread->shared->ShouldStopTest()) { - break; - } - if (open_cnt != 0) { - thread->stats.FinishedSingleOp(); - MutexLock l(thread->shared->GetMutex()); - while (!thread->snapshot_queue.empty()) { - db_->ReleaseSnapshot(thread->snapshot_queue.front().second.snapshot); - delete thread->snapshot_queue.front().second.key_vec; - thread->snapshot_queue.pop(); - } - thread->shared->IncVotedReopen(); - if (thread->shared->AllVotedReopen()) { - thread->shared->GetStressTest()->Reopen(thread); - thread->shared->GetCondVar()->SignalAll(); - } else { - thread->shared->GetCondVar()->Wait(); - } - // Commenting this out as we don't want to reset stats on each open. - // thread->stats.Start(); - } - - for (uint64_t i = 0; i < ops_per_open; i++) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - // Change Options - if (thread->rand.OneInOpt(FLAGS_set_options_one_in)) { - SetOptions(thread); - } - - if (thread->rand.OneInOpt(FLAGS_set_in_place_one_in)) { - options_.inplace_update_support ^= options_.inplace_update_support; - } - - if (thread->tid == 0 && FLAGS_verify_db_one_in > 0 && - thread->rand.OneIn(FLAGS_verify_db_one_in)) { - ContinuouslyVerifyDb(thread); - if (thread->shared->ShouldStopTest()) { - break; - } - } - - MaybeClearOneColumnFamily(thread); - - if (thread->rand.OneInOpt(FLAGS_manual_wal_flush_one_in)) { - bool sync = thread->rand.OneIn(2) ? true : false; - Status s = db_->FlushWAL(sync); - if (!s.ok() && !(sync && s.IsNotSupported())) { - fprintf(stderr, "FlushWAL(sync=%s) failed: %s\n", - (sync ? "true" : "false"), s.ToString().c_str()); - } - } - - if (thread->rand.OneInOpt(FLAGS_lock_wal_one_in)) { - Status s = db_->LockWAL(); - if (!s.ok()) { - fprintf(stderr, "LockWAL() failed: %s\n", s.ToString().c_str()); - } else { - auto old_seqno = db_->GetLatestSequenceNumber(); - // Yield for a while - do { - std::this_thread::yield(); - } while (thread->rand.OneIn(2)); - // Latest seqno should not have changed - auto new_seqno = db_->GetLatestSequenceNumber(); - if (old_seqno != new_seqno) { - fprintf( - stderr, - "Failure: latest seqno changed from %u to %u with WAL locked\n", - (unsigned)old_seqno, (unsigned)new_seqno); - } - s = db_->UnlockWAL(); - if (!s.ok()) { - fprintf(stderr, "UnlockWAL() failed: %s\n", s.ToString().c_str()); - } - } - } - - if (thread->rand.OneInOpt(FLAGS_sync_wal_one_in)) { - Status s = db_->SyncWAL(); - if (!s.ok() && !s.IsNotSupported()) { - fprintf(stderr, "SyncWAL() failed: %s\n", s.ToString().c_str()); - } - } - - int rand_column_family = thread->rand.Next() % FLAGS_column_families; - ColumnFamilyHandle* column_family = column_families_[rand_column_family]; - - if (thread->rand.OneInOpt(FLAGS_compact_files_one_in)) { - TestCompactFiles(thread, column_family); - } - - int64_t rand_key = GenerateOneKey(thread, i); - std::string keystr = Key(rand_key); - Slice key = keystr; - - if (thread->rand.OneInOpt(FLAGS_compact_range_one_in)) { - TestCompactRange(thread, rand_key, key, column_family); - if (thread->shared->HasVerificationFailedYet()) { - break; - } - } - - std::vector rand_column_families = - GenerateColumnFamilies(FLAGS_column_families, rand_column_family); - - if (thread->rand.OneInOpt(FLAGS_flush_one_in)) { - Status status = TestFlush(rand_column_families); - if (!status.ok()) { - fprintf(stdout, "Unable to perform Flush(): %s\n", - status.ToString().c_str()); - } - } - - // Verify GetLiveFiles with a 1 in N chance. - if (thread->rand.OneInOpt(FLAGS_get_live_files_one_in) && - !FLAGS_write_fault_one_in) { - Status status = VerifyGetLiveFiles(); - if (!status.ok()) { - VerificationAbort(shared, "VerifyGetLiveFiles status not OK", status); - } - } - - // Verify GetSortedWalFiles with a 1 in N chance. - if (thread->rand.OneInOpt(FLAGS_get_sorted_wal_files_one_in)) { - Status status = VerifyGetSortedWalFiles(); - if (!status.ok()) { - VerificationAbort(shared, "VerifyGetSortedWalFiles status not OK", - status); - } - } - - // Verify GetCurrentWalFile with a 1 in N chance. - if (thread->rand.OneInOpt(FLAGS_get_current_wal_file_one_in)) { - Status status = VerifyGetCurrentWalFile(); - if (!status.ok()) { - VerificationAbort(shared, "VerifyGetCurrentWalFile status not OK", - status); - } - } - - if (thread->rand.OneInOpt(FLAGS_pause_background_one_in)) { - Status status = TestPauseBackground(thread); - if (!status.ok()) { - VerificationAbort( - shared, "Pause/ContinueBackgroundWork status not OK", status); - } - } - - if (thread->rand.OneInOpt(FLAGS_verify_checksum_one_in)) { - Status status = db_->VerifyChecksum(); - if (!status.ok()) { - VerificationAbort(shared, "VerifyChecksum status not OK", status); - } - } - - if (thread->rand.OneInOpt(FLAGS_get_property_one_in)) { - TestGetProperty(thread); - } - - std::vector rand_keys = GenerateKeys(rand_key); - - if (thread->rand.OneInOpt(FLAGS_ingest_external_file_one_in)) { - TestIngestExternalFile(thread, rand_column_families, rand_keys); - } - - if (thread->rand.OneInOpt(FLAGS_backup_one_in)) { - // Beyond a certain DB size threshold, this test becomes heavier than - // it's worth. - uint64_t total_size = 0; - if (FLAGS_backup_max_size > 0) { - std::vector files; - db_stress_env->GetChildrenFileAttributes(FLAGS_db, &files); - for (auto& file : files) { - total_size += file.size_bytes; - } - } - - if (total_size <= FLAGS_backup_max_size) { - Status s = TestBackupRestore(thread, rand_column_families, rand_keys); - if (!s.ok()) { - VerificationAbort(shared, "Backup/restore gave inconsistent state", - s); - } - } - } - - if (thread->rand.OneInOpt(FLAGS_checkpoint_one_in)) { - Status s = TestCheckpoint(thread, rand_column_families, rand_keys); - if (!s.ok()) { - VerificationAbort(shared, "Checkpoint gave inconsistent state", s); - } - } - - if (thread->rand.OneInOpt(FLAGS_approximate_size_one_in)) { - Status s = - TestApproximateSize(thread, i, rand_column_families, rand_keys); - if (!s.ok()) { - VerificationAbort(shared, "ApproximateSize Failed", s); - } - } - if (thread->rand.OneInOpt(FLAGS_acquire_snapshot_one_in)) { - TestAcquireSnapshot(thread, rand_column_family, keystr, i); - } - - /*always*/ { - Status s = MaybeReleaseSnapshots(thread, i); - if (!s.ok()) { - VerificationAbort(shared, "Snapshot gave inconsistent state", s); - } - } - - // Assign timestamps if necessary. - std::string read_ts_str; - Slice read_ts; - if (FLAGS_user_timestamp_size > 0) { - read_ts_str = GetNowNanos(); - read_ts = read_ts_str; - read_opts.timestamp = &read_ts; - } - - int prob_op = thread->rand.Uniform(100); - // Reset this in case we pick something other than a read op. We don't - // want to use a stale value when deciding at the beginning of the loop - // whether to vote to reopen - if (prob_op >= 0 && prob_op < static_cast(FLAGS_readpercent)) { - assert(0 <= prob_op); - // OPERATION read - if (FLAGS_use_get_entity) { - TestGetEntity(thread, read_opts, rand_column_families, rand_keys); - } else if (FLAGS_use_multiget) { - // Leave room for one more iteration of the loop with a single key - // batch. This is to ensure that each thread does exactly the same - // number of ops - int multiget_batch_size = static_cast( - std::min(static_cast(thread->rand.Uniform(64)), - FLAGS_ops_per_thread - i - 1)); - // If its the last iteration, ensure that multiget_batch_size is 1 - multiget_batch_size = std::max(multiget_batch_size, 1); - rand_keys = GenerateNKeys(thread, multiget_batch_size, i); - TestMultiGet(thread, read_opts, rand_column_families, rand_keys); - i += multiget_batch_size - 1; - } else { - TestGet(thread, read_opts, rand_column_families, rand_keys); - } - } else if (prob_op < prefix_bound) { - assert(static_cast(FLAGS_readpercent) <= prob_op); - // OPERATION prefix scan - // keys are 8 bytes long, prefix size is FLAGS_prefix_size. There are - // (8 - FLAGS_prefix_size) bytes besides the prefix. So there will - // be 2 ^ ((8 - FLAGS_prefix_size) * 8) possible keys with the same - // prefix - TestPrefixScan(thread, read_opts, rand_column_families, rand_keys); - } else if (prob_op < write_bound) { - assert(prefix_bound <= prob_op); - // OPERATION write - TestPut(thread, write_opts, read_opts, rand_column_families, rand_keys, - value); - } else if (prob_op < del_bound) { - assert(write_bound <= prob_op); - // OPERATION delete - TestDelete(thread, write_opts, rand_column_families, rand_keys); - } else if (prob_op < delrange_bound) { - assert(del_bound <= prob_op); - // OPERATION delete range - TestDeleteRange(thread, write_opts, rand_column_families, rand_keys); - } else if (prob_op < iterate_bound) { - assert(delrange_bound <= prob_op); - // OPERATION iterate - if (!FLAGS_skip_verifydb && - thread->rand.OneInOpt( - FLAGS_verify_iterator_with_expected_state_one_in)) { - TestIterateAgainstExpected(thread, read_opts, rand_column_families, - rand_keys); - } else { - int num_seeks = static_cast(std::min( - std::max(static_cast(thread->rand.Uniform(4)), - static_cast(1)), - std::max(static_cast(FLAGS_ops_per_thread - i - 1), - static_cast(1)))); - rand_keys = GenerateNKeys(thread, num_seeks, i); - i += num_seeks - 1; - TestIterate(thread, read_opts, rand_column_families, rand_keys); - } - } else { - assert(iterate_bound <= prob_op); - TestCustomOperations(thread, rand_column_families); - } - thread->stats.FinishedSingleOp(); - } - } - while (!thread->snapshot_queue.empty()) { - db_->ReleaseSnapshot(thread->snapshot_queue.front().second.snapshot); - delete thread->snapshot_queue.front().second.key_vec; - thread->snapshot_queue.pop(); - } - - thread->stats.Stop(); -} - -// Generated a list of keys that close to boundaries of SST keys. -// If there isn't any SST file in the DB, return empty list. -std::vector StressTest::GetWhiteBoxKeys(ThreadState* thread, - DB* db, - ColumnFamilyHandle* cfh, - size_t num_keys) { - ColumnFamilyMetaData cfmd; - db->GetColumnFamilyMetaData(cfh, &cfmd); - std::vector boundaries; - for (const LevelMetaData& lmd : cfmd.levels) { - for (const SstFileMetaData& sfmd : lmd.files) { - // If FLAGS_user_timestamp_size > 0, then both smallestkey and largestkey - // have timestamps. - const auto& skey = sfmd.smallestkey; - const auto& lkey = sfmd.largestkey; - assert(skey.size() >= FLAGS_user_timestamp_size); - assert(lkey.size() >= FLAGS_user_timestamp_size); - boundaries.push_back( - skey.substr(0, skey.size() - FLAGS_user_timestamp_size)); - boundaries.push_back( - lkey.substr(0, lkey.size() - FLAGS_user_timestamp_size)); - } - } - if (boundaries.empty()) { - return {}; - } - - std::vector ret; - for (size_t j = 0; j < num_keys; j++) { - std::string k = - boundaries[thread->rand.Uniform(static_cast(boundaries.size()))]; - if (thread->rand.OneIn(3)) { - // Reduce one byte from the string - for (int i = static_cast(k.length()) - 1; i >= 0; i--) { - uint8_t cur = k[i]; - if (cur > 0) { - k[i] = static_cast(cur - 1); - break; - } else if (i > 0) { - k[i] = 0xFFu; - } - } - } else if (thread->rand.OneIn(2)) { - // Add one byte to the string - for (int i = static_cast(k.length()) - 1; i >= 0; i--) { - uint8_t cur = k[i]; - if (cur < 255) { - k[i] = static_cast(cur + 1); - break; - } else if (i > 0) { - k[i] = 0x00; - } - } - } - ret.push_back(k); - } - return ret; -} - -// Given a key K, this creates an iterator which scans to K and then -// does a random sequence of Next/Prev operations. -Status StressTest::TestIterate(ThreadState* thread, - const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - ManagedSnapshot snapshot_guard(db_); - - ReadOptions ro = read_opts; - ro.snapshot = snapshot_guard.snapshot(); - - std::string read_ts_str; - Slice read_ts_slice; - MaybeUseOlderTimestampForRangeScan(thread, read_ts_str, read_ts_slice, ro); - - bool expect_total_order = false; - if (thread->rand.OneIn(16)) { - // When prefix extractor is used, it's useful to cover total order seek. - ro.total_order_seek = true; - expect_total_order = true; - } else if (thread->rand.OneIn(4)) { - ro.total_order_seek = false; - ro.auto_prefix_mode = true; - expect_total_order = true; - } else if (options_.prefix_extractor.get() == nullptr) { - expect_total_order = true; - } - - std::string upper_bound_str; - Slice upper_bound; - if (thread->rand.OneIn(16)) { - // With a 1/16 chance, set an iterator upper bound. - // Note: upper_bound can be smaller than the seek key. - const int64_t rand_upper_key = GenerateOneKey(thread, FLAGS_ops_per_thread); - upper_bound_str = Key(rand_upper_key); - upper_bound = Slice(upper_bound_str); - ro.iterate_upper_bound = &upper_bound; - } - std::string lower_bound_str; - Slice lower_bound; - if (thread->rand.OneIn(16)) { - // With a 1/16 chance, enable iterator lower bound. - // Note: lower_bound can be greater than the seek key. - const int64_t rand_lower_key = GenerateOneKey(thread, FLAGS_ops_per_thread); - lower_bound_str = Key(rand_lower_key); - lower_bound = Slice(lower_bound_str); - ro.iterate_lower_bound = &lower_bound; - } - - ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]]; - assert(cfh); - - std::unique_ptr iter(db_->NewIterator(ro, cfh)); - - std::vector key_strs; - if (thread->rand.OneIn(16)) { - // Generate keys close to lower or upper bound of SST files. - key_strs = GetWhiteBoxKeys(thread, db_, cfh, rand_keys.size()); - } - if (key_strs.empty()) { - // Use the random keys passed in. - for (int64_t rkey : rand_keys) { - key_strs.push_back(Key(rkey)); - } - } - - std::string op_logs; - constexpr size_t kOpLogsLimit = 10000; - - for (const std::string& key_str : key_strs) { - if (op_logs.size() > kOpLogsLimit) { - // Shouldn't take too much memory for the history log. Clear it. - op_logs = "(cleared...)\n"; - } - - if (ro.iterate_upper_bound != nullptr && thread->rand.OneIn(2)) { - // With a 1/2 chance, change the upper bound. - // It is possible that it is changed before first use, but there is no - // problem with that. - const int64_t rand_upper_key = - GenerateOneKey(thread, FLAGS_ops_per_thread); - upper_bound_str = Key(rand_upper_key); - upper_bound = Slice(upper_bound_str); - } - if (ro.iterate_lower_bound != nullptr && thread->rand.OneIn(4)) { - // With a 1/4 chance, change the lower bound. - // It is possible that it is changed before first use, but there is no - // problem with that. - const int64_t rand_lower_key = - GenerateOneKey(thread, FLAGS_ops_per_thread); - lower_bound_str = Key(rand_lower_key); - lower_bound = Slice(lower_bound_str); - } - - // Record some options to op_logs - op_logs += "total_order_seek: "; - op_logs += (ro.total_order_seek ? "1 " : "0 "); - op_logs += "auto_prefix_mode: "; - op_logs += (ro.auto_prefix_mode ? "1 " : "0 "); - if (ro.iterate_upper_bound != nullptr) { - op_logs += "ub: " + upper_bound.ToString(true) + " "; - } - if (ro.iterate_lower_bound != nullptr) { - op_logs += "lb: " + lower_bound.ToString(true) + " "; - } - - // Set up an iterator, perform the same operations without bounds and with - // total order seek, and compare the results. This is to identify bugs - // related to bounds, prefix extractor, or reseeking. Sometimes we are - // comparing iterators with the same set-up, and it doesn't hurt to check - // them to be equal. - // - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions cmp_ro; - cmp_ro.timestamp = ro.timestamp; - cmp_ro.iter_start_ts = ro.iter_start_ts; - cmp_ro.snapshot = snapshot_guard.snapshot(); - cmp_ro.total_order_seek = true; - - ColumnFamilyHandle* const cmp_cfh = - GetControlCfh(thread, rand_column_families[0]); - assert(cmp_cfh); - - std::unique_ptr cmp_iter(db_->NewIterator(cmp_ro, cmp_cfh)); - - bool diverged = false; - - Slice key(key_str); - - const bool support_seek_first_or_last = expect_total_order; - - LastIterateOp last_op; - if (support_seek_first_or_last && thread->rand.OneIn(100)) { - iter->SeekToFirst(); - cmp_iter->SeekToFirst(); - last_op = kLastOpSeekToFirst; - op_logs += "STF "; - } else if (support_seek_first_or_last && thread->rand.OneIn(100)) { - iter->SeekToLast(); - cmp_iter->SeekToLast(); - last_op = kLastOpSeekToLast; - op_logs += "STL "; - } else if (thread->rand.OneIn(8)) { - iter->SeekForPrev(key); - cmp_iter->SeekForPrev(key); - last_op = kLastOpSeekForPrev; - op_logs += "SFP " + key.ToString(true) + " "; - } else { - iter->Seek(key); - cmp_iter->Seek(key); - last_op = kLastOpSeek; - op_logs += "S " + key.ToString(true) + " "; - } - - VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op, - key, op_logs, &diverged); - - const bool no_reverse = - (FLAGS_memtablerep == "prefix_hash" && !expect_total_order); - for (uint64_t i = 0; i < FLAGS_num_iterations && iter->Valid(); ++i) { - if (no_reverse || thread->rand.OneIn(2)) { - iter->Next(); - if (!diverged) { - assert(cmp_iter->Valid()); - cmp_iter->Next(); - } - op_logs += "N"; - } else { - iter->Prev(); - if (!diverged) { - assert(cmp_iter->Valid()); - cmp_iter->Prev(); - } - op_logs += "P"; - } - - last_op = kLastOpNextOrPrev; - - VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op, - key, op_logs, &diverged); - } - - thread->stats.AddIterations(1); - - op_logs += "; "; - } - - return Status::OK(); -} - -// Test the return status of GetLiveFiles. -Status StressTest::VerifyGetLiveFiles() const { - std::vector live_file; - uint64_t manifest_size = 0; - return db_->GetLiveFiles(live_file, &manifest_size); -} - -// Test the return status of GetSortedWalFiles. -Status StressTest::VerifyGetSortedWalFiles() const { - VectorLogPtr log_ptr; - return db_->GetSortedWalFiles(log_ptr); -} - -// Test the return status of GetCurrentWalFile. -Status StressTest::VerifyGetCurrentWalFile() const { - std::unique_ptr cur_wal_file; - return db_->GetCurrentWalFile(&cur_wal_file); -} - -// Compare the two iterator, iter and cmp_iter are in the same position, -// unless iter might be made invalidate or undefined because of -// upper or lower bounds, or prefix extractor. -// Will flag failure if the verification fails. -// diverged = true if the two iterator is already diverged. -// True if verification passed, false if not. -void StressTest::VerifyIterator(ThreadState* thread, - ColumnFamilyHandle* cmp_cfh, - const ReadOptions& ro, Iterator* iter, - Iterator* cmp_iter, LastIterateOp op, - const Slice& seek_key, - const std::string& op_logs, bool* diverged) { - assert(diverged); - - if (*diverged) { - return; - } - - if (ro.iter_start_ts != nullptr) { - assert(FLAGS_user_timestamp_size > 0); - // We currently do not verify iterator when dumping history of internal - // keys. - *diverged = true; - return; - } - - if (op == kLastOpSeekToFirst && ro.iterate_lower_bound != nullptr) { - // SeekToFirst() with lower bound is not well defined. - *diverged = true; - return; - } else if (op == kLastOpSeekToLast && ro.iterate_upper_bound != nullptr) { - // SeekToLast() with higher bound is not well defined. - *diverged = true; - return; - } else if (op == kLastOpSeek && ro.iterate_lower_bound != nullptr && - (options_.comparator->CompareWithoutTimestamp( - *ro.iterate_lower_bound, /*a_has_ts=*/false, seek_key, - /*b_has_ts=*/false) >= 0 || - (ro.iterate_upper_bound != nullptr && - options_.comparator->CompareWithoutTimestamp( - *ro.iterate_lower_bound, /*a_has_ts=*/false, - *ro.iterate_upper_bound, /*b_has_ts*/ false) >= 0))) { - // Lower bound behavior is not well defined if it is larger than - // seek key or upper bound. Disable the check for now. - *diverged = true; - return; - } else if (op == kLastOpSeekForPrev && ro.iterate_upper_bound != nullptr && - (options_.comparator->CompareWithoutTimestamp( - *ro.iterate_upper_bound, /*a_has_ts=*/false, seek_key, - /*b_has_ts=*/false) <= 0 || - (ro.iterate_lower_bound != nullptr && - options_.comparator->CompareWithoutTimestamp( - *ro.iterate_lower_bound, /*a_has_ts=*/false, - *ro.iterate_upper_bound, /*b_has_ts=*/false) >= 0))) { - // Uppder bound behavior is not well defined if it is smaller than - // seek key or lower bound. Disable the check for now. - *diverged = true; - return; - } - - const SliceTransform* pe = (ro.total_order_seek || ro.auto_prefix_mode) - ? nullptr - : options_.prefix_extractor.get(); - const Comparator* cmp = options_.comparator; - - if (iter->Valid() && !cmp_iter->Valid()) { - if (pe != nullptr) { - if (!pe->InDomain(seek_key)) { - // Prefix seek a non-in-domain key is undefined. Skip checking for - // this scenario. - *diverged = true; - return; - } else if (!pe->InDomain(iter->key())) { - // out of range is iterator key is not in domain anymore. - *diverged = true; - return; - } else if (pe->Transform(iter->key()) != pe->Transform(seek_key)) { - *diverged = true; - return; - } - } - fprintf(stderr, - "Control interator is invalid but iterator has key %s " - "%s\n", - iter->key().ToString(true).c_str(), op_logs.c_str()); - - *diverged = true; - } else if (cmp_iter->Valid()) { - // Iterator is not valid. It can be legimate if it has already been - // out of upper or lower bound, or filtered out by prefix iterator. - const Slice& total_order_key = cmp_iter->key(); - - if (pe != nullptr) { - if (!pe->InDomain(seek_key)) { - // Prefix seek a non-in-domain key is undefined. Skip checking for - // this scenario. - *diverged = true; - return; - } - - if (!pe->InDomain(total_order_key) || - pe->Transform(total_order_key) != pe->Transform(seek_key)) { - // If the prefix is exhausted, the only thing needs to check - // is the iterator isn't return a position in prefix. - // Either way, checking can stop from here. - *diverged = true; - if (!iter->Valid() || !pe->InDomain(iter->key()) || - pe->Transform(iter->key()) != pe->Transform(seek_key)) { - return; - } - fprintf(stderr, - "Iterator stays in prefix but contol doesn't" - " iterator key %s control iterator key %s %s\n", - iter->key().ToString(true).c_str(), - cmp_iter->key().ToString(true).c_str(), op_logs.c_str()); - } - } - // Check upper or lower bounds. - if (!*diverged) { - if ((iter->Valid() && iter->key() != cmp_iter->key()) || - (!iter->Valid() && - (ro.iterate_upper_bound == nullptr || - cmp->CompareWithoutTimestamp(total_order_key, /*a_has_ts=*/false, - *ro.iterate_upper_bound, - /*b_has_ts=*/false) < 0) && - (ro.iterate_lower_bound == nullptr || - cmp->CompareWithoutTimestamp(total_order_key, /*a_has_ts=*/false, - *ro.iterate_lower_bound, - /*b_has_ts=*/false) > 0))) { - fprintf(stderr, - "Iterator diverged from control iterator which" - " has value %s %s\n", - total_order_key.ToString(true).c_str(), op_logs.c_str()); - if (iter->Valid()) { - fprintf(stderr, "iterator has value %s\n", - iter->key().ToString(true).c_str()); - } else { - fprintf(stderr, "iterator is not valid\n"); - } - *diverged = true; - } - } - } - - if (!*diverged && iter->Valid()) { - if (!VerifyWideColumns(iter->value(), iter->columns())) { - fprintf(stderr, - "Value and columns inconsistent for iterator: value: %s, " - "columns: %s\n", - iter->value().ToString(/* hex */ true).c_str(), - WideColumnsToHex(iter->columns()).c_str()); - - *diverged = true; - } - } - - if (*diverged) { - fprintf(stderr, "Control CF %s\n", cmp_cfh->GetName().c_str()); - thread->stats.AddErrors(1); - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - } -} - -Status StressTest::TestBackupRestore( - ThreadState* thread, const std::vector& rand_column_families, - const std::vector& rand_keys) { - std::vector> locks; - if (ShouldAcquireMutexOnKey()) { - for (int rand_column_family : rand_column_families) { - // `rand_keys[0]` on each chosen CF will be verified. - locks.emplace_back(new MutexLock( - thread->shared->GetMutexForKey(rand_column_family, rand_keys[0]))); - } - } - - const std::string backup_dir = - FLAGS_db + "/.backup" + std::to_string(thread->tid); - const std::string restore_dir = - FLAGS_db + "/.restore" + std::to_string(thread->tid); - BackupEngineOptions backup_opts(backup_dir); - // For debugging, get info_log from live options - backup_opts.info_log = db_->GetDBOptions().info_log.get(); - if (thread->rand.OneIn(10)) { - backup_opts.share_table_files = false; - } else { - backup_opts.share_table_files = true; - if (thread->rand.OneIn(5)) { - backup_opts.share_files_with_checksum = false; - } else { - backup_opts.share_files_with_checksum = true; - if (thread->rand.OneIn(2)) { - // old - backup_opts.share_files_with_checksum_naming = - BackupEngineOptions::kLegacyCrc32cAndFileSize; - } else { - // new - backup_opts.share_files_with_checksum_naming = - BackupEngineOptions::kUseDbSessionId; - } - if (thread->rand.OneIn(2)) { - backup_opts.share_files_with_checksum_naming = - backup_opts.share_files_with_checksum_naming | - BackupEngineOptions::kFlagIncludeFileSize; - } - } - } - if (thread->rand.OneIn(2)) { - backup_opts.schema_version = 1; - } else { - backup_opts.schema_version = 2; - } - BackupEngine* backup_engine = nullptr; - std::string from = "a backup/restore operation"; - Status s = BackupEngine::Open(db_stress_env, backup_opts, &backup_engine); - if (!s.ok()) { - from = "BackupEngine::Open"; - } - if (s.ok()) { - if (backup_opts.schema_version >= 2 && thread->rand.OneIn(2)) { - TEST_BackupMetaSchemaOptions test_opts; - test_opts.crc32c_checksums = thread->rand.OneIn(2) == 0; - test_opts.file_sizes = thread->rand.OneIn(2) == 0; - TEST_SetBackupMetaSchemaOptions(backup_engine, test_opts); - } - CreateBackupOptions create_opts; - if (FLAGS_disable_wal) { - // The verification can only work when latest value of `key` is backed up, - // which requires flushing in case of WAL disabled. - // - // Note this triggers a flush with a key lock held. Meanwhile, operations - // like flush/compaction may attempt to grab key locks like in - // `DbStressCompactionFilter`. The philosophy around preventing deadlock - // is the background operation key lock acquisition only tries but does - // not wait for the lock. So here in the foreground it is OK to hold the - // lock and wait on a background operation (flush). - create_opts.flush_before_backup = true; - } - s = backup_engine->CreateNewBackup(create_opts, db_); - if (!s.ok()) { - from = "BackupEngine::CreateNewBackup"; - } - } - if (s.ok()) { - delete backup_engine; - backup_engine = nullptr; - s = BackupEngine::Open(db_stress_env, backup_opts, &backup_engine); - if (!s.ok()) { - from = "BackupEngine::Open (again)"; - } - } - std::vector backup_info; - // If inplace_not_restore, we verify the backup by opening it as a - // read-only DB. If !inplace_not_restore, we restore it to a temporary - // directory for verification. - bool inplace_not_restore = thread->rand.OneIn(3); - if (s.ok()) { - backup_engine->GetBackupInfo(&backup_info, - /*include_file_details*/ inplace_not_restore); - if (backup_info.empty()) { - s = Status::NotFound("no backups found"); - from = "BackupEngine::GetBackupInfo"; - } - } - if (s.ok() && thread->rand.OneIn(2)) { - s = backup_engine->VerifyBackup( - backup_info.front().backup_id, - thread->rand.OneIn(2) /* verify_with_checksum */); - if (!s.ok()) { - from = "BackupEngine::VerifyBackup"; - } - } - const bool allow_persistent = thread->tid == 0; // not too many - bool from_latest = false; - int count = static_cast(backup_info.size()); - if (s.ok() && !inplace_not_restore) { - if (count > 1) { - s = backup_engine->RestoreDBFromBackup( - RestoreOptions(), backup_info[thread->rand.Uniform(count)].backup_id, - restore_dir /* db_dir */, restore_dir /* wal_dir */); - if (!s.ok()) { - from = "BackupEngine::RestoreDBFromBackup"; - } - } else { - from_latest = true; - s = backup_engine->RestoreDBFromLatestBackup(RestoreOptions(), - restore_dir /* db_dir */, - restore_dir /* wal_dir */); - if (!s.ok()) { - from = "BackupEngine::RestoreDBFromLatestBackup"; - } - } - } - if (s.ok() && !inplace_not_restore) { - // Purge early if restoring, to ensure the restored directory doesn't - // have some secret dependency on the backup directory. - uint32_t to_keep = 0; - if (allow_persistent) { - // allow one thread to keep up to 2 backups - to_keep = thread->rand.Uniform(3); - } - s = backup_engine->PurgeOldBackups(to_keep); - if (!s.ok()) { - from = "BackupEngine::PurgeOldBackups"; - } - } - DB* restored_db = nullptr; - std::vector restored_cf_handles; - // Not yet implemented: opening restored BlobDB or TransactionDB - if (s.ok() && !FLAGS_use_txn && !FLAGS_use_blob_db) { - Options restore_options(options_); - restore_options.best_efforts_recovery = false; - restore_options.listeners.clear(); - // Avoid dangling/shared file descriptors, for reliable destroy - restore_options.sst_file_manager = nullptr; - std::vector cf_descriptors; - // TODO(ajkr): `column_family_names_` is not safe to access here when - // `clear_column_family_one_in != 0`. But we can't easily switch to - // `ListColumnFamilies` to get names because it won't necessarily give - // the same order as `column_family_names_`. - assert(FLAGS_clear_column_family_one_in == 0); - for (auto name : column_family_names_) { - cf_descriptors.emplace_back(name, ColumnFamilyOptions(restore_options)); - } - if (inplace_not_restore) { - BackupInfo& info = backup_info[thread->rand.Uniform(count)]; - restore_options.env = info.env_for_open.get(); - s = DB::OpenForReadOnly(DBOptions(restore_options), info.name_for_open, - cf_descriptors, &restored_cf_handles, - &restored_db); - if (!s.ok()) { - from = "DB::OpenForReadOnly in backup/restore"; - } - } else { - s = DB::Open(DBOptions(restore_options), restore_dir, cf_descriptors, - &restored_cf_handles, &restored_db); - if (!s.ok()) { - from = "DB::Open in backup/restore"; - } - } - } - // Note the column families chosen by `rand_column_families` cannot be - // dropped while the locks for `rand_keys` are held. So we should not have - // to worry about accessing those column families throughout this function. - // - // For simplicity, currently only verifies existence/non-existence of a - // single key - for (size_t i = 0; restored_db && s.ok() && i < rand_column_families.size(); - ++i) { - std::string key_str = Key(rand_keys[0]); - Slice key = key_str; - std::string restored_value; - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions read_opts; - std::string ts_str; - Slice ts; - if (FLAGS_user_timestamp_size > 0) { - ts_str = GetNowNanos(); - ts = ts_str; - read_opts.timestamp = &ts; - } - Status get_status = restored_db->Get( - read_opts, restored_cf_handles[rand_column_families[i]], key, - &restored_value); - bool exists = thread->shared->Exists(rand_column_families[i], rand_keys[0]); - if (get_status.ok()) { - if (!exists && from_latest && ShouldAcquireMutexOnKey()) { - std::ostringstream oss; - oss << "0x" << key.ToString(true) - << " exists in restore but not in original db"; - s = Status::Corruption(oss.str()); - } - } else if (get_status.IsNotFound()) { - if (exists && from_latest && ShouldAcquireMutexOnKey()) { - std::ostringstream oss; - oss << "0x" << key.ToString(true) - << " exists in original db but not in restore"; - s = Status::Corruption(oss.str()); - } - } else { - s = get_status; - if (!s.ok()) { - from = "DB::Get in backup/restore"; - } - } - } - if (restored_db != nullptr) { - for (auto* cf_handle : restored_cf_handles) { - restored_db->DestroyColumnFamilyHandle(cf_handle); - } - delete restored_db; - restored_db = nullptr; - } - if (s.ok() && inplace_not_restore) { - // Purge late if inplace open read-only - uint32_t to_keep = 0; - if (allow_persistent) { - // allow one thread to keep up to 2 backups - to_keep = thread->rand.Uniform(3); - } - s = backup_engine->PurgeOldBackups(to_keep); - if (!s.ok()) { - from = "BackupEngine::PurgeOldBackups"; - } - } - if (backup_engine != nullptr) { - delete backup_engine; - backup_engine = nullptr; - } - if (s.ok()) { - // Preserve directories on failure, or allowed persistent backup - if (!allow_persistent) { - s = DestroyDir(db_stress_env, backup_dir); - if (!s.ok()) { - from = "Destroy backup dir"; - } - } - } - if (s.ok()) { - s = DestroyDir(db_stress_env, restore_dir); - if (!s.ok()) { - from = "Destroy restore dir"; - } - } - if (!s.ok()) { - fprintf(stderr, "Failure in %s with: %s\n", from.c_str(), - s.ToString().c_str()); - } - return s; -} - -Status StressTest::TestApproximateSize( - ThreadState* thread, uint64_t iteration, - const std::vector& rand_column_families, - const std::vector& rand_keys) { - // rand_keys likely only has one key. Just use the first one. - assert(!rand_keys.empty()); - assert(!rand_column_families.empty()); - int64_t key1 = rand_keys[0]; - int64_t key2; - if (thread->rand.OneIn(2)) { - // Two totally random keys. This tends to cover large ranges. - key2 = GenerateOneKey(thread, iteration); - if (key2 < key1) { - std::swap(key1, key2); - } - } else { - // Unless users pass a very large FLAGS_max_key, it we should not worry - // about overflow. It is for testing, so we skip the overflow checking - // for simplicity. - key2 = key1 + static_cast(thread->rand.Uniform(1000)); - } - std::string key1_str = Key(key1); - std::string key2_str = Key(key2); - Range range{Slice(key1_str), Slice(key2_str)}; - SizeApproximationOptions sao; - sao.include_memtables = thread->rand.OneIn(2); - if (sao.include_memtables) { - sao.include_files = thread->rand.OneIn(2); - } - if (thread->rand.OneIn(2)) { - if (thread->rand.OneIn(2)) { - sao.files_size_error_margin = 0.0; - } else { - sao.files_size_error_margin = - static_cast(thread->rand.Uniform(3)); - } - } - uint64_t result; - return db_->GetApproximateSizes( - sao, column_families_[rand_column_families[0]], &range, 1, &result); -} - -Status StressTest::TestCheckpoint(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys) { - std::vector> locks; - if (ShouldAcquireMutexOnKey()) { - for (int rand_column_family : rand_column_families) { - // `rand_keys[0]` on each chosen CF will be verified. - locks.emplace_back(new MutexLock( - thread->shared->GetMutexForKey(rand_column_family, rand_keys[0]))); - } - } - - std::string checkpoint_dir = - FLAGS_db + "/.checkpoint" + std::to_string(thread->tid); - Options tmp_opts(options_); - tmp_opts.listeners.clear(); - tmp_opts.env = db_stress_env; - - DestroyDB(checkpoint_dir, tmp_opts); - - if (db_stress_env->FileExists(checkpoint_dir).ok()) { - // If the directory might still exist, try to delete the files one by one. - // Likely a trash file is still there. - Status my_s = DestroyDir(db_stress_env, checkpoint_dir); - if (!my_s.ok()) { - fprintf(stderr, "Fail to destory directory before checkpoint: %s", - my_s.ToString().c_str()); - } - } - - Checkpoint* checkpoint = nullptr; - Status s = Checkpoint::Create(db_, &checkpoint); - if (s.ok()) { - s = checkpoint->CreateCheckpoint(checkpoint_dir); - if (!s.ok()) { - fprintf(stderr, "Fail to create checkpoint to %s\n", - checkpoint_dir.c_str()); - std::vector files; - Status my_s = db_stress_env->GetChildren(checkpoint_dir, &files); - if (my_s.ok()) { - for (const auto& f : files) { - fprintf(stderr, " %s\n", f.c_str()); - } - } else { - fprintf(stderr, "Fail to get files under the directory to %s\n", - my_s.ToString().c_str()); - } - } - } - delete checkpoint; - checkpoint = nullptr; - std::vector cf_handles; - DB* checkpoint_db = nullptr; - if (s.ok()) { - Options options(options_); - options.best_efforts_recovery = false; - options.listeners.clear(); - // Avoid race condition in trash handling after delete checkpoint_db - options.sst_file_manager.reset(); - std::vector cf_descs; - // TODO(ajkr): `column_family_names_` is not safe to access here when - // `clear_column_family_one_in != 0`. But we can't easily switch to - // `ListColumnFamilies` to get names because it won't necessarily give - // the same order as `column_family_names_`. - assert(FLAGS_clear_column_family_one_in == 0); - if (FLAGS_clear_column_family_one_in == 0) { - for (const auto& name : column_family_names_) { - cf_descs.emplace_back(name, ColumnFamilyOptions(options)); - } - s = DB::OpenForReadOnly(DBOptions(options), checkpoint_dir, cf_descs, - &cf_handles, &checkpoint_db); - } - } - if (checkpoint_db != nullptr) { - // Note the column families chosen by `rand_column_families` cannot be - // dropped while the locks for `rand_keys` are held. So we should not have - // to worry about accessing those column families throughout this function. - for (size_t i = 0; s.ok() && i < rand_column_families.size(); ++i) { - std::string key_str = Key(rand_keys[0]); - Slice key = key_str; - std::string ts_str; - Slice ts; - ReadOptions read_opts; - if (FLAGS_user_timestamp_size > 0) { - ts_str = GetNowNanos(); - ts = ts_str; - read_opts.timestamp = &ts; - } - std::string value; - Status get_status = checkpoint_db->Get( - read_opts, cf_handles[rand_column_families[i]], key, &value); - bool exists = - thread->shared->Exists(rand_column_families[i], rand_keys[0]); - if (get_status.ok()) { - if (!exists && ShouldAcquireMutexOnKey()) { - std::ostringstream oss; - oss << "0x" << key.ToString(true) << " exists in checkpoint " - << checkpoint_dir << " but not in original db"; - s = Status::Corruption(oss.str()); - } - } else if (get_status.IsNotFound()) { - if (exists && ShouldAcquireMutexOnKey()) { - std::ostringstream oss; - oss << "0x" << key.ToString(true) - << " exists in original db but not in checkpoint " - << checkpoint_dir; - s = Status::Corruption(oss.str()); - } - } else { - s = get_status; - } - } - for (auto cfh : cf_handles) { - delete cfh; - } - cf_handles.clear(); - delete checkpoint_db; - checkpoint_db = nullptr; - } - - if (!s.ok()) { - fprintf(stderr, "A checkpoint operation failed with: %s\n", - s.ToString().c_str()); - } else { - DestroyDB(checkpoint_dir, tmp_opts); - } - return s; -} - -void StressTest::TestGetProperty(ThreadState* thread) const { - std::unordered_set levelPropertyNames = { - DB::Properties::kAggregatedTablePropertiesAtLevel, - DB::Properties::kCompressionRatioAtLevelPrefix, - DB::Properties::kNumFilesAtLevelPrefix, - }; - std::unordered_set unknownPropertyNames = { - DB::Properties::kEstimateOldestKeyTime, - DB::Properties::kOptionsStatistics, - DB::Properties:: - kLiveSstFilesSizeAtTemperature, // similar to levelPropertyNames, it - // requires a number suffix - }; - unknownPropertyNames.insert(levelPropertyNames.begin(), - levelPropertyNames.end()); - - std::unordered_set blobCachePropertyNames = { - DB::Properties::kBlobCacheCapacity, - DB::Properties::kBlobCacheUsage, - DB::Properties::kBlobCachePinnedUsage, - }; - if (db_->GetOptions().blob_cache == nullptr) { - unknownPropertyNames.insert(blobCachePropertyNames.begin(), - blobCachePropertyNames.end()); - } - - std::string prop; - for (const auto& ppt_name_and_info : InternalStats::ppt_name_to_info) { - bool res = db_->GetProperty(ppt_name_and_info.first, &prop); - if (unknownPropertyNames.find(ppt_name_and_info.first) == - unknownPropertyNames.end()) { - if (!res) { - fprintf(stderr, "Failed to get DB property: %s\n", - ppt_name_and_info.first.c_str()); - thread->shared->SetVerificationFailure(); - } - if (ppt_name_and_info.second.handle_int != nullptr) { - uint64_t prop_int; - if (!db_->GetIntProperty(ppt_name_and_info.first, &prop_int)) { - fprintf(stderr, "Failed to get Int property: %s\n", - ppt_name_and_info.first.c_str()); - thread->shared->SetVerificationFailure(); - } - } - if (ppt_name_and_info.second.handle_map != nullptr) { - std::map prop_map; - if (!db_->GetMapProperty(ppt_name_and_info.first, &prop_map)) { - fprintf(stderr, "Failed to get Map property: %s\n", - ppt_name_and_info.first.c_str()); - thread->shared->SetVerificationFailure(); - } - } - } - } - - ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_meta_data; - db_->GetColumnFamilyMetaData(&cf_meta_data); - int level_size = static_cast(cf_meta_data.levels.size()); - for (int level = 0; level < level_size; level++) { - for (const auto& ppt_name : levelPropertyNames) { - bool res = db_->GetProperty(ppt_name + std::to_string(level), &prop); - if (!res) { - fprintf(stderr, "Failed to get DB property: %s\n", - (ppt_name + std::to_string(level)).c_str()); - thread->shared->SetVerificationFailure(); - } - } - } - - // Test for an invalid property name - if (thread->rand.OneIn(100)) { - if (db_->GetProperty("rocksdb.invalid_property_name", &prop)) { - fprintf(stderr, "Failed to return false for invalid property name\n"); - thread->shared->SetVerificationFailure(); - } - } -} - -void StressTest::TestCompactFiles(ThreadState* thread, - ColumnFamilyHandle* column_family) { - ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_meta_data; - db_->GetColumnFamilyMetaData(column_family, &cf_meta_data); - - if (cf_meta_data.levels.empty()) { - return; - } - - // Randomly compact up to three consecutive files from a level - const int kMaxRetry = 3; - for (int attempt = 0; attempt < kMaxRetry; ++attempt) { - size_t random_level = - thread->rand.Uniform(static_cast(cf_meta_data.levels.size())); - - const auto& files = cf_meta_data.levels[random_level].files; - if (files.size() > 0) { - size_t random_file_index = - thread->rand.Uniform(static_cast(files.size())); - if (files[random_file_index].being_compacted) { - // Retry as the selected file is currently being compacted - continue; - } - - std::vector input_files; - input_files.push_back(files[random_file_index].name); - if (random_file_index > 0 && - !files[random_file_index - 1].being_compacted) { - input_files.push_back(files[random_file_index - 1].name); - } - if (random_file_index + 1 < files.size() && - !files[random_file_index + 1].being_compacted) { - input_files.push_back(files[random_file_index + 1].name); - } - - size_t output_level = - std::min(random_level + 1, cf_meta_data.levels.size() - 1); - auto s = db_->CompactFiles(CompactionOptions(), column_family, - input_files, static_cast(output_level)); - if (!s.ok()) { - fprintf(stdout, "Unable to perform CompactFiles(): %s\n", - s.ToString().c_str()); - thread->stats.AddNumCompactFilesFailed(1); - } else { - thread->stats.AddNumCompactFilesSucceed(1); - } - break; - } - } -} - -Status StressTest::TestFlush(const std::vector& rand_column_families) { - FlushOptions flush_opts; - if (FLAGS_atomic_flush) { - return db_->Flush(flush_opts, column_families_); - } - std::vector cfhs; - std::for_each(rand_column_families.begin(), rand_column_families.end(), - [this, &cfhs](int k) { cfhs.push_back(column_families_[k]); }); - return db_->Flush(flush_opts, cfhs); -} - -Status StressTest::TestPauseBackground(ThreadState* thread) { - Status status = db_->PauseBackgroundWork(); - if (!status.ok()) { - return status; - } - // To avoid stalling/deadlocking ourself in this thread, just - // sleep here during pause and let other threads do db operations. - // Sleep up to ~16 seconds (2**24 microseconds), but very skewed - // toward short pause. (1 chance in 25 of pausing >= 1s; - // 1 chance in 625 of pausing full 16s.) - int pwr2_micros = - std::min(thread->rand.Uniform(25), thread->rand.Uniform(25)); - clock_->SleepForMicroseconds(1 << pwr2_micros); - return db_->ContinueBackgroundWork(); -} - -void StressTest::TestAcquireSnapshot(ThreadState* thread, - int rand_column_family, - const std::string& keystr, uint64_t i) { - Slice key = keystr; - ColumnFamilyHandle* column_family = column_families_[rand_column_family]; - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ropt; - auto db_impl = static_cast_with_check(db_->GetRootDB()); - const bool ww_snapshot = thread->rand.OneIn(10); - const Snapshot* snapshot = - ww_snapshot ? db_impl->GetSnapshotForWriteConflictBoundary() - : db_->GetSnapshot(); - ropt.snapshot = snapshot; - - // Ideally, we want snapshot taking and timestamp generation to be atomic - // here, so that the snapshot corresponds to the timestamp. However, it is - // not possible with current GetSnapshot() API. - std::string ts_str; - Slice ts; - if (FLAGS_user_timestamp_size > 0) { - ts_str = GetNowNanos(); - ts = ts_str; - ropt.timestamp = &ts; - } - - std::string value_at; - // When taking a snapshot, we also read a key from that snapshot. We - // will later read the same key before releasing the snapshot and - // verify that the results are the same. - auto status_at = db_->Get(ropt, column_family, key, &value_at); - std::vector* key_vec = nullptr; - - if (FLAGS_compare_full_db_state_snapshot && (thread->tid == 0)) { - key_vec = new std::vector(FLAGS_max_key); - // When `prefix_extractor` is set, seeking to beginning and scanning - // across prefixes are only supported with `total_order_seek` set. - ropt.total_order_seek = true; - std::unique_ptr iterator(db_->NewIterator(ropt)); - for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { - uint64_t key_val; - if (GetIntVal(iterator->key().ToString(), &key_val)) { - (*key_vec)[key_val] = true; - } - } - } - - ThreadState::SnapshotState snap_state = {snapshot, - rand_column_family, - column_family->GetName(), - keystr, - status_at, - value_at, - key_vec, - ts_str}; - uint64_t hold_for = FLAGS_snapshot_hold_ops; - if (FLAGS_long_running_snapshots) { - // Hold 10% of snapshots for 10x more - if (thread->rand.OneIn(10)) { - assert(hold_for < std::numeric_limits::max() / 10); - hold_for *= 10; - // Hold 1% of snapshots for 100x more - if (thread->rand.OneIn(10)) { - assert(hold_for < std::numeric_limits::max() / 10); - hold_for *= 10; - } - } - } - uint64_t release_at = std::min(FLAGS_ops_per_thread - 1, i + hold_for); - thread->snapshot_queue.emplace(release_at, snap_state); -} - -Status StressTest::MaybeReleaseSnapshots(ThreadState* thread, uint64_t i) { - while (!thread->snapshot_queue.empty() && - i >= thread->snapshot_queue.front().first) { - auto snap_state = thread->snapshot_queue.front().second; - assert(snap_state.snapshot); - // Note: this is unsafe as the cf might be dropped concurrently. But - // it is ok since unclean cf drop is cunnrently not supported by write - // prepared transactions. - Status s = AssertSame(db_, column_families_[snap_state.cf_at], snap_state); - db_->ReleaseSnapshot(snap_state.snapshot); - delete snap_state.key_vec; - thread->snapshot_queue.pop(); - if (!s.ok()) { - return s; - } - } - return Status::OK(); -} - -void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key, - const Slice& start_key, - ColumnFamilyHandle* column_family) { - int64_t end_key_num; - if (std::numeric_limits::max() - rand_key < - FLAGS_compact_range_width) { - end_key_num = std::numeric_limits::max(); - } else { - end_key_num = FLAGS_compact_range_width + rand_key; - } - std::string end_key_buf = Key(end_key_num); - Slice end_key(end_key_buf); - - CompactRangeOptions cro; - cro.exclusive_manual_compaction = static_cast(thread->rand.Next() % 2); - cro.change_level = static_cast(thread->rand.Next() % 2); - std::vector bottom_level_styles = { - BottommostLevelCompaction::kSkip, - BottommostLevelCompaction::kIfHaveCompactionFilter, - BottommostLevelCompaction::kForce, - BottommostLevelCompaction::kForceOptimized}; - cro.bottommost_level_compaction = - bottom_level_styles[thread->rand.Next() % - static_cast(bottom_level_styles.size())]; - cro.allow_write_stall = static_cast(thread->rand.Next() % 2); - cro.max_subcompactions = static_cast(thread->rand.Next() % 4); - std::vector blob_gc_policies = { - BlobGarbageCollectionPolicy::kForce, - BlobGarbageCollectionPolicy::kDisable, - BlobGarbageCollectionPolicy::kUseDefault}; - cro.blob_garbage_collection_policy = - blob_gc_policies[thread->rand.Next() % - static_cast(blob_gc_policies.size())]; - cro.blob_garbage_collection_age_cutoff = - static_cast(thread->rand.Next() % 100) / 100.0; - - const Snapshot* pre_snapshot = nullptr; - uint32_t pre_hash = 0; - if (thread->rand.OneIn(2)) { - // Do some validation by declaring a snapshot and compare the data before - // and after the compaction - pre_snapshot = db_->GetSnapshot(); - pre_hash = - GetRangeHash(thread, pre_snapshot, column_family, start_key, end_key); - } - - Status status = db_->CompactRange(cro, column_family, &start_key, &end_key); - - if (!status.ok()) { - fprintf(stdout, "Unable to perform CompactRange(): %s\n", - status.ToString().c_str()); - } - - if (pre_snapshot != nullptr) { - uint32_t post_hash = - GetRangeHash(thread, pre_snapshot, column_family, start_key, end_key); - if (pre_hash != post_hash) { - fprintf(stderr, - "Data hash different before and after compact range " - "start_key %s end_key %s\n", - start_key.ToString(true).c_str(), end_key.ToString(true).c_str()); - thread->stats.AddErrors(1); - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - } - db_->ReleaseSnapshot(pre_snapshot); - } -} - -uint32_t StressTest::GetRangeHash(ThreadState* thread, const Snapshot* snapshot, - ColumnFamilyHandle* column_family, - const Slice& start_key, - const Slice& end_key) { - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ro; - ro.snapshot = snapshot; - ro.total_order_seek = true; - std::string ts_str; - Slice ts; - if (FLAGS_user_timestamp_size > 0) { - ts_str = GetNowNanos(); - ts = ts_str; - ro.timestamp = &ts; - } - - std::unique_ptr it(db_->NewIterator(ro, column_family)); - - constexpr char kCrcCalculatorSepearator = ';'; - - uint32_t crc = 0; - - for (it->Seek(start_key); - it->Valid() && options_.comparator->Compare(it->key(), end_key) <= 0; - it->Next()) { - crc = crc32c::Extend(crc, it->key().data(), it->key().size()); - crc = crc32c::Extend(crc, &kCrcCalculatorSepearator, sizeof(char)); - crc = crc32c::Extend(crc, it->value().data(), it->value().size()); - crc = crc32c::Extend(crc, &kCrcCalculatorSepearator, sizeof(char)); - - for (const auto& column : it->columns()) { - crc = crc32c::Extend(crc, column.name().data(), column.name().size()); - crc = crc32c::Extend(crc, &kCrcCalculatorSepearator, sizeof(char)); - crc = crc32c::Extend(crc, column.value().data(), column.value().size()); - crc = crc32c::Extend(crc, &kCrcCalculatorSepearator, sizeof(char)); - } - } - - if (!it->status().ok()) { - fprintf(stderr, "Iterator non-OK when calculating range CRC: %s\n", - it->status().ToString().c_str()); - thread->stats.AddErrors(1); - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - } - - return crc; -} - -void StressTest::PrintEnv() const { - fprintf(stdout, "RocksDB version : %d.%d\n", kMajorVersion, - kMinorVersion); - fprintf(stdout, "Format version : %d\n", FLAGS_format_version); - fprintf(stdout, "TransactionDB : %s\n", - FLAGS_use_txn ? "true" : "false"); - - if (FLAGS_use_txn) { - fprintf(stdout, "Two write queues: : %s\n", - FLAGS_two_write_queues ? "true" : "false"); - fprintf(stdout, "Write policy : %d\n", - static_cast(FLAGS_txn_write_policy)); - if (static_cast(TxnDBWritePolicy::WRITE_PREPARED) == - FLAGS_txn_write_policy || - static_cast(TxnDBWritePolicy::WRITE_UNPREPARED) == - FLAGS_txn_write_policy) { - fprintf(stdout, "Snapshot cache bits : %d\n", - static_cast(FLAGS_wp_snapshot_cache_bits)); - fprintf(stdout, "Commit cache bits : %d\n", - static_cast(FLAGS_wp_commit_cache_bits)); - } - fprintf(stdout, "last cwb for recovery : %s\n", - FLAGS_use_only_the_last_commit_time_batch_for_recovery ? "true" - : "false"); - } - - fprintf(stdout, "Stacked BlobDB : %s\n", - FLAGS_use_blob_db ? "true" : "false"); - fprintf(stdout, "Read only mode : %s\n", - FLAGS_read_only ? "true" : "false"); - fprintf(stdout, "Atomic flush : %s\n", - FLAGS_atomic_flush ? "true" : "false"); - fprintf(stdout, "Manual WAL flush : %s\n", - FLAGS_manual_wal_flush_one_in > 0 ? "true" : "false"); - fprintf(stdout, "Column families : %d\n", FLAGS_column_families); - if (!FLAGS_test_batches_snapshots) { - fprintf(stdout, "Clear CFs one in : %d\n", - FLAGS_clear_column_family_one_in); - } - fprintf(stdout, "Number of threads : %d\n", FLAGS_threads); - fprintf(stdout, "Ops per thread : %lu\n", - (unsigned long)FLAGS_ops_per_thread); - std::string ttl_state("unused"); - if (FLAGS_ttl > 0) { - ttl_state = std::to_string(FLAGS_ttl); - } - fprintf(stdout, "Time to live(sec) : %s\n", ttl_state.c_str()); - fprintf(stdout, "Read percentage : %d%%\n", FLAGS_readpercent); - fprintf(stdout, "Prefix percentage : %d%%\n", FLAGS_prefixpercent); - fprintf(stdout, "Write percentage : %d%%\n", FLAGS_writepercent); - fprintf(stdout, "Delete percentage : %d%%\n", FLAGS_delpercent); - fprintf(stdout, "Delete range percentage : %d%%\n", FLAGS_delrangepercent); - fprintf(stdout, "No overwrite percentage : %d%%\n", - FLAGS_nooverwritepercent); - fprintf(stdout, "Iterate percentage : %d%%\n", FLAGS_iterpercent); - fprintf(stdout, "Custom ops percentage : %d%%\n", FLAGS_customopspercent); - fprintf(stdout, "DB-write-buffer-size : %" PRIu64 "\n", - FLAGS_db_write_buffer_size); - fprintf(stdout, "Write-buffer-size : %d\n", FLAGS_write_buffer_size); - fprintf(stdout, "Iterations : %lu\n", - (unsigned long)FLAGS_num_iterations); - fprintf(stdout, "Max key : %lu\n", - (unsigned long)FLAGS_max_key); - fprintf(stdout, "Ratio #ops/#keys : %f\n", - (1.0 * FLAGS_ops_per_thread * FLAGS_threads) / FLAGS_max_key); - fprintf(stdout, "Num times DB reopens : %d\n", FLAGS_reopen); - fprintf(stdout, "Batches/snapshots : %d\n", - FLAGS_test_batches_snapshots); - fprintf(stdout, "Do update in place : %d\n", FLAGS_in_place_update); - fprintf(stdout, "Num keys per lock : %d\n", - 1 << FLAGS_log2_keys_per_lock); - std::string compression = CompressionTypeToString(compression_type_e); - fprintf(stdout, "Compression : %s\n", compression.c_str()); - std::string bottommost_compression = - CompressionTypeToString(bottommost_compression_type_e); - fprintf(stdout, "Bottommost Compression : %s\n", - bottommost_compression.c_str()); - std::string checksum = ChecksumTypeToString(checksum_type_e); - fprintf(stdout, "Checksum type : %s\n", checksum.c_str()); - fprintf(stdout, "File checksum impl : %s\n", - FLAGS_file_checksum_impl.c_str()); - fprintf(stdout, "Bloom bits / key : %s\n", - FormatDoubleParam(FLAGS_bloom_bits).c_str()); - fprintf(stdout, "Max subcompactions : %" PRIu64 "\n", - FLAGS_subcompactions); - fprintf(stdout, "Use MultiGet : %s\n", - FLAGS_use_multiget ? "true" : "false"); - fprintf(stdout, "Use GetEntity : %s\n", - FLAGS_use_get_entity ? "true" : "false"); - - const char* memtablerep = ""; - switch (FLAGS_rep_factory) { - case kSkipList: - memtablerep = "skip_list"; - break; - case kHashSkipList: - memtablerep = "prefix_hash"; - break; - case kVectorRep: - memtablerep = "vector"; - break; - } - - fprintf(stdout, "Memtablerep : %s\n", memtablerep); - -#ifndef NDEBUG - KillPoint* kp = KillPoint::GetInstance(); - fprintf(stdout, "Test kill odd : %d\n", kp->rocksdb_kill_odds); - if (!kp->rocksdb_kill_exclude_prefixes.empty()) { - fprintf(stdout, "Skipping kill points prefixes:\n"); - for (auto& p : kp->rocksdb_kill_exclude_prefixes) { - fprintf(stdout, " %s\n", p.c_str()); - } - } -#endif - fprintf(stdout, "Periodic Compaction Secs : %" PRIu64 "\n", - FLAGS_periodic_compaction_seconds); - fprintf(stdout, "Compaction TTL : %" PRIu64 "\n", - FLAGS_compaction_ttl); - const char* compaction_pri = ""; - switch (FLAGS_compaction_pri) { - case kByCompensatedSize: - compaction_pri = "kByCompensatedSize"; - break; - case kOldestLargestSeqFirst: - compaction_pri = "kOldestLargestSeqFirst"; - break; - case kOldestSmallestSeqFirst: - compaction_pri = "kOldestSmallestSeqFirst"; - break; - case kMinOverlappingRatio: - compaction_pri = "kMinOverlappingRatio"; - break; - case kRoundRobin: - compaction_pri = "kRoundRobin"; - break; - } - fprintf(stdout, "Compaction Pri : %s\n", compaction_pri); - fprintf(stdout, "Background Purge : %d\n", - static_cast(FLAGS_avoid_unnecessary_blocking_io)); - fprintf(stdout, "Write DB ID to manifest : %d\n", - static_cast(FLAGS_write_dbid_to_manifest)); - fprintf(stdout, "Max Write Batch Group Size: %" PRIu64 "\n", - FLAGS_max_write_batch_group_size_bytes); - fprintf(stdout, "Use dynamic level : %d\n", - static_cast(FLAGS_level_compaction_dynamic_level_bytes)); - fprintf(stdout, "Read fault one in : %d\n", FLAGS_read_fault_one_in); - fprintf(stdout, "Write fault one in : %d\n", FLAGS_write_fault_one_in); - fprintf(stdout, "Open metadata write fault one in:\n"); - fprintf(stdout, " %d\n", - FLAGS_open_metadata_write_fault_one_in); - fprintf(stdout, "Sync fault injection : %d\n", - FLAGS_sync_fault_injection); - fprintf(stdout, "Best efforts recovery : %d\n", - static_cast(FLAGS_best_efforts_recovery)); - fprintf(stdout, "Fail if OPTIONS file error: %d\n", - static_cast(FLAGS_fail_if_options_file_error)); - fprintf(stdout, "User timestamp size bytes : %d\n", - static_cast(FLAGS_user_timestamp_size)); - fprintf(stdout, "WAL compression : %s\n", - FLAGS_wal_compression.c_str()); - fprintf(stdout, "Try verify sst unique id : %d\n", - static_cast(FLAGS_verify_sst_unique_id_in_manifest)); - - fprintf(stdout, "------------------------------------------------\n"); -} - -void StressTest::Open(SharedState* shared) { - assert(db_ == nullptr); - assert(txn_db_ == nullptr); - if (!InitializeOptionsFromFile(options_)) { - InitializeOptionsFromFlags(cache_, filter_policy_, options_); - } - InitializeOptionsGeneral(cache_, filter_policy_, options_); - - if (FLAGS_prefix_size == 0 && FLAGS_rep_factory == kHashSkipList) { - fprintf(stderr, - "prefeix_size cannot be zero if memtablerep == prefix_hash\n"); - exit(1); - } - if (FLAGS_prefix_size != 0 && FLAGS_rep_factory != kHashSkipList) { - fprintf(stderr, - "WARNING: prefix_size is non-zero but " - "memtablerep != prefix_hash\n"); - } - - if ((options_.enable_blob_files || options_.enable_blob_garbage_collection || - FLAGS_allow_setting_blob_options_dynamically) && - FLAGS_best_efforts_recovery) { - fprintf(stderr, - "Integrated BlobDB is currently incompatible with best-effort " - "recovery\n"); - exit(1); - } - - fprintf(stdout, - "Integrated BlobDB: blob files enabled %d, min blob size %" PRIu64 - ", blob file size %" PRIu64 - ", blob compression type %s, blob GC enabled %d, cutoff %f, force " - "threshold %f, blob compaction readahead size %" PRIu64 - ", blob file starting level %d\n", - options_.enable_blob_files, options_.min_blob_size, - options_.blob_file_size, - CompressionTypeToString(options_.blob_compression_type).c_str(), - options_.enable_blob_garbage_collection, - options_.blob_garbage_collection_age_cutoff, - options_.blob_garbage_collection_force_threshold, - options_.blob_compaction_readahead_size, - options_.blob_file_starting_level); - - if (FLAGS_use_blob_cache) { - fprintf(stdout, - "Integrated BlobDB: blob cache enabled" - ", block and blob caches shared: %d", - FLAGS_use_shared_block_and_blob_cache); - if (!FLAGS_use_shared_block_and_blob_cache) { - fprintf(stdout, - ", blob cache size %" PRIu64 ", blob cache num shard bits: %d", - FLAGS_blob_cache_size, FLAGS_blob_cache_numshardbits); - } - fprintf(stdout, ", blob cache prepopulated: %d\n", - FLAGS_prepopulate_blob_cache); - } else { - fprintf(stdout, "Integrated BlobDB: blob cache disabled\n"); - } - - fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str()); - - Status s; - - if (FLAGS_ttl == -1) { - std::vector existing_column_families; - s = DB::ListColumnFamilies(DBOptions(options_), FLAGS_db, - &existing_column_families); // ignore errors - if (!s.ok()) { - // DB doesn't exist - assert(existing_column_families.empty()); - assert(column_family_names_.empty()); - column_family_names_.push_back(kDefaultColumnFamilyName); - } else if (column_family_names_.empty()) { - // this is the first call to the function Open() - column_family_names_ = existing_column_families; - } else { - // this is a reopen. just assert that existing column_family_names are - // equivalent to what we remember - auto sorted_cfn = column_family_names_; - std::sort(sorted_cfn.begin(), sorted_cfn.end()); - std::sort(existing_column_families.begin(), - existing_column_families.end()); - if (sorted_cfn != existing_column_families) { - fprintf(stderr, "Expected column families differ from the existing:\n"); - fprintf(stderr, "Expected: {"); - for (auto cf : sorted_cfn) { - fprintf(stderr, "%s ", cf.c_str()); - } - fprintf(stderr, "}\n"); - fprintf(stderr, "Existing: {"); - for (auto cf : existing_column_families) { - fprintf(stderr, "%s ", cf.c_str()); - } - fprintf(stderr, "}\n"); - } - assert(sorted_cfn == existing_column_families); - } - std::vector cf_descriptors; - for (auto name : column_family_names_) { - if (name != kDefaultColumnFamilyName) { - new_column_family_name_ = - std::max(new_column_family_name_.load(), std::stoi(name) + 1); - } - cf_descriptors.emplace_back(name, ColumnFamilyOptions(options_)); - } - while (cf_descriptors.size() < (size_t)FLAGS_column_families) { - std::string name = std::to_string(new_column_family_name_.load()); - new_column_family_name_++; - cf_descriptors.emplace_back(name, ColumnFamilyOptions(options_)); - column_family_names_.push_back(name); - } - - options_.listeners.clear(); - options_.listeners.emplace_back(new DbStressListener( - FLAGS_db, options_.db_paths, cf_descriptors, db_stress_listener_env)); - RegisterAdditionalListeners(); - - if (!FLAGS_use_txn) { - // Determine whether we need to ingest file metadata write failures - // during DB reopen. If it does, enable it. - // Only ingest metadata error if it is reopening, as initial open - // failure doesn't need to be handled. - // TODO cover transaction DB is not covered in this fault test too. - bool ingest_meta_error = false; - bool ingest_write_error = false; - bool ingest_read_error = false; - if ((FLAGS_open_metadata_write_fault_one_in || - FLAGS_open_write_fault_one_in || FLAGS_open_read_fault_one_in) && - fault_fs_guard - ->FileExists(FLAGS_db + "/CURRENT", IOOptions(), nullptr) - .ok()) { - if (!FLAGS_sync) { - // When DB Stress is not sync mode, we expect all WAL writes to - // WAL is durable. Buffering unsynced writes will cause false - // positive in crash tests. Before we figure out a way to - // solve it, skip WAL from failure injection. - fault_fs_guard->SetSkipDirectWritableTypes({kWalFile}); - } - ingest_meta_error = FLAGS_open_metadata_write_fault_one_in; - ingest_write_error = FLAGS_open_write_fault_one_in; - ingest_read_error = FLAGS_open_read_fault_one_in; - if (ingest_meta_error) { - fault_fs_guard->EnableMetadataWriteErrorInjection(); - fault_fs_guard->SetRandomMetadataWriteError( - FLAGS_open_metadata_write_fault_one_in); - } - if (ingest_write_error) { - fault_fs_guard->SetFilesystemDirectWritable(false); - fault_fs_guard->EnableWriteErrorInjection(); - fault_fs_guard->SetRandomWriteError( - static_cast(FLAGS_seed), FLAGS_open_write_fault_one_in, - IOStatus::IOError("Injected Open Error"), - /*inject_for_all_file_types=*/true, /*types=*/{}); - } - if (ingest_read_error) { - fault_fs_guard->SetRandomReadError(FLAGS_open_read_fault_one_in); - } - } - while (true) { - // StackableDB-based BlobDB - if (FLAGS_use_blob_db) { - blob_db::BlobDBOptions blob_db_options; - blob_db_options.min_blob_size = FLAGS_blob_db_min_blob_size; - blob_db_options.bytes_per_sync = FLAGS_blob_db_bytes_per_sync; - blob_db_options.blob_file_size = FLAGS_blob_db_file_size; - blob_db_options.enable_garbage_collection = FLAGS_blob_db_enable_gc; - blob_db_options.garbage_collection_cutoff = FLAGS_blob_db_gc_cutoff; - - blob_db::BlobDB* blob_db = nullptr; - s = blob_db::BlobDB::Open(options_, blob_db_options, FLAGS_db, - cf_descriptors, &column_families_, - &blob_db); - if (s.ok()) { - db_ = blob_db; - } - } else - { - if (db_preload_finished_.load() && FLAGS_read_only) { - s = DB::OpenForReadOnly(DBOptions(options_), FLAGS_db, - cf_descriptors, &column_families_, &db_); - } else { - s = DB::Open(DBOptions(options_), FLAGS_db, cf_descriptors, - &column_families_, &db_); - } - } - - if (ingest_meta_error || ingest_write_error || ingest_read_error) { - fault_fs_guard->SetFilesystemDirectWritable(true); - fault_fs_guard->DisableMetadataWriteErrorInjection(); - fault_fs_guard->DisableWriteErrorInjection(); - fault_fs_guard->SetSkipDirectWritableTypes({}); - fault_fs_guard->SetRandomReadError(0); - if (s.ok()) { - // Ingested errors might happen in background compactions. We - // wait for all compactions to finish to make sure DB is in - // clean state before executing queries. - s = static_cast_with_check(db_->GetRootDB()) - ->WaitForCompact(true /* wait_unscheduled */); - if (!s.ok()) { - for (auto cf : column_families_) { - delete cf; - } - column_families_.clear(); - delete db_; - db_ = nullptr; - } - } - if (!s.ok()) { - // After failure to opening a DB due to IO error, retry should - // successfully open the DB with correct data if no IO error shows - // up. - ingest_meta_error = false; - ingest_write_error = false; - ingest_read_error = false; - - Random rand(static_cast(FLAGS_seed)); - if (rand.OneIn(2)) { - fault_fs_guard->DeleteFilesCreatedAfterLastDirSync(IOOptions(), - nullptr); - } - if (rand.OneIn(3)) { - fault_fs_guard->DropUnsyncedFileData(); - } else if (rand.OneIn(2)) { - fault_fs_guard->DropRandomUnsyncedFileData(&rand); - } - continue; - } - } - break; - } - } else { - TransactionDBOptions txn_db_options; - assert(FLAGS_txn_write_policy <= TxnDBWritePolicy::WRITE_UNPREPARED); - txn_db_options.write_policy = - static_cast(FLAGS_txn_write_policy); - if (FLAGS_unordered_write) { - assert(txn_db_options.write_policy == TxnDBWritePolicy::WRITE_PREPARED); - options_.unordered_write = true; - options_.two_write_queues = true; - txn_db_options.skip_concurrency_control = true; - } else { - options_.two_write_queues = FLAGS_two_write_queues; - } - txn_db_options.wp_snapshot_cache_bits = - static_cast(FLAGS_wp_snapshot_cache_bits); - txn_db_options.wp_commit_cache_bits = - static_cast(FLAGS_wp_commit_cache_bits); - PrepareTxnDbOptions(shared, txn_db_options); - s = TransactionDB::Open(options_, txn_db_options, FLAGS_db, - cf_descriptors, &column_families_, &txn_db_); - if (!s.ok()) { - fprintf(stderr, "Error in opening the TransactionDB [%s]\n", - s.ToString().c_str()); - fflush(stderr); - } - assert(s.ok()); - - // Do not swap the order of the following. - { - db_ = txn_db_; - db_aptr_.store(txn_db_, std::memory_order_release); - } - } - if (!s.ok()) { - fprintf(stderr, "Error in opening the DB [%s]\n", s.ToString().c_str()); - fflush(stderr); - } - assert(s.ok()); - assert(column_families_.size() == - static_cast(FLAGS_column_families)); - - // Secondary instance does not support write-prepared/write-unprepared - // transactions, thus just disable secondary instance if we use - // transaction. - if (s.ok() && FLAGS_test_secondary && !FLAGS_use_txn) { - Options tmp_opts; - // TODO(yanqin) support max_open_files != -1 for secondary instance. - tmp_opts.max_open_files = -1; - tmp_opts.env = db_stress_env; - const std::string& secondary_path = FLAGS_secondaries_base; - s = DB::OpenAsSecondary(tmp_opts, FLAGS_db, secondary_path, - cf_descriptors, &cmp_cfhs_, &cmp_db_); - assert(s.ok()); - assert(cmp_cfhs_.size() == static_cast(FLAGS_column_families)); - } - } else { - DBWithTTL* db_with_ttl; - s = DBWithTTL::Open(options_, FLAGS_db, &db_with_ttl, FLAGS_ttl); - db_ = db_with_ttl; - } - - if (FLAGS_preserve_unverified_changes) { - // Up until now, no live file should have become obsolete due to these - // options. After `DisableFileDeletions()` we can reenable auto compactions - // since, even if live files become obsolete, they won't be deleted. - assert(options_.avoid_flush_during_recovery); - assert(options_.disable_auto_compactions); - if (s.ok()) { - s = db_->DisableFileDeletions(); - } - if (s.ok()) { - s = db_->EnableAutoCompaction(column_families_); - } - } - - if (!s.ok()) { - fprintf(stderr, "open error: %s\n", s.ToString().c_str()); - exit(1); - } -} - -void StressTest::Reopen(ThreadState* thread) { - // BG jobs in WritePrepared must be canceled first because i) they can access - // the db via a callbac ii) they hold on to a snapshot and the upcoming - // ::Close would complain about it. - const bool write_prepared = FLAGS_use_txn && FLAGS_txn_write_policy != 0; - bool bg_canceled __attribute__((unused)) = false; - if (write_prepared || thread->rand.OneIn(2)) { - const bool wait = - write_prepared || static_cast(thread->rand.OneIn(2)); - CancelAllBackgroundWork(db_, wait); - bg_canceled = wait; - } - assert(!write_prepared || bg_canceled); - - for (auto cf : column_families_) { - delete cf; - } - column_families_.clear(); - - if (thread->rand.OneIn(2)) { - Status s = db_->Close(); - if (!s.ok()) { - fprintf(stderr, "Non-ok close status: %s\n", s.ToString().c_str()); - fflush(stderr); - } - assert(s.ok()); - } - assert(txn_db_ == nullptr || db_ == txn_db_); - delete db_; - db_ = nullptr; - txn_db_ = nullptr; - - num_times_reopened_++; - auto now = clock_->NowMicros(); - fprintf(stdout, "%s Reopening database for the %dth time\n", - clock_->TimeToString(now / 1000000).c_str(), num_times_reopened_); - Open(thread->shared); - - if ((FLAGS_sync_fault_injection || FLAGS_disable_wal || - FLAGS_manual_wal_flush_one_in > 0) && - IsStateTracked()) { - Status s = thread->shared->SaveAtAndAfter(db_); - if (!s.ok()) { - fprintf(stderr, "Error enabling history tracing: %s\n", - s.ToString().c_str()); - exit(1); - } - } -} - -bool StressTest::MaybeUseOlderTimestampForPointLookup(ThreadState* thread, - std::string& ts_str, - Slice& ts_slice, - ReadOptions& read_opts) { - if (FLAGS_user_timestamp_size == 0) { - return false; - } - - assert(thread); - if (!thread->rand.OneInOpt(3)) { - return false; - } - - const SharedState* const shared = thread->shared; - assert(shared); - const uint64_t start_ts = shared->GetStartTimestamp(); - - uint64_t now = db_stress_env->NowNanos(); - - assert(now > start_ts); - uint64_t time_diff = now - start_ts; - uint64_t ts = start_ts + (thread->rand.Next64() % time_diff); - ts_str.clear(); - PutFixed64(&ts_str, ts); - ts_slice = ts_str; - read_opts.timestamp = &ts_slice; - return true; -} - -void StressTest::MaybeUseOlderTimestampForRangeScan(ThreadState* thread, - std::string& ts_str, - Slice& ts_slice, - ReadOptions& read_opts) { - if (FLAGS_user_timestamp_size == 0) { - return; - } - - assert(thread); - if (!thread->rand.OneInOpt(3)) { - return; - } - - const Slice* const saved_ts = read_opts.timestamp; - assert(saved_ts != nullptr); - - const SharedState* const shared = thread->shared; - assert(shared); - const uint64_t start_ts = shared->GetStartTimestamp(); - - uint64_t now = db_stress_env->NowNanos(); - - assert(now > start_ts); - uint64_t time_diff = now - start_ts; - uint64_t ts = start_ts + (thread->rand.Next64() % time_diff); - ts_str.clear(); - PutFixed64(&ts_str, ts); - ts_slice = ts_str; - read_opts.timestamp = &ts_slice; - - // TODO (yanqin): support Merge with iter_start_ts - if (!thread->rand.OneInOpt(3) || FLAGS_use_merge || FLAGS_use_full_merge_v1) { - return; - } - - ts_str.clear(); - PutFixed64(&ts_str, start_ts); - ts_slice = ts_str; - read_opts.iter_start_ts = &ts_slice; - read_opts.timestamp = saved_ts; -} - -void CheckAndSetOptionsForUserTimestamp(Options& options) { - assert(FLAGS_user_timestamp_size > 0); - const Comparator* const cmp = test::BytewiseComparatorWithU64TsWrapper(); - assert(cmp); - if (FLAGS_user_timestamp_size != cmp->timestamp_size()) { - fprintf(stderr, - "Only -user_timestamp_size=%d is supported in stress test.\n", - static_cast(cmp->timestamp_size())); - exit(1); - } - if (FLAGS_use_txn) { - fprintf(stderr, "TransactionDB does not support timestamp yet.\n"); - exit(1); - } - if (FLAGS_test_cf_consistency || FLAGS_test_batches_snapshots) { - fprintf(stderr, - "Due to per-key ts-seq ordering constraint, only the (default) " - "non-batched test is supported with timestamp.\n"); - exit(1); - } - if (FLAGS_ingest_external_file_one_in > 0) { - fprintf(stderr, "Bulk loading may not support timestamp yet.\n"); - exit(1); - } - options.comparator = cmp; -} - -bool InitializeOptionsFromFile(Options& options) { - DBOptions db_options; - ConfigOptions config_options; - config_options.ignore_unknown_options = false; - config_options.input_strings_escaped = true; - config_options.env = db_stress_env; - std::vector cf_descriptors; - if (!FLAGS_options_file.empty()) { - Status s = LoadOptionsFromFile(config_options, FLAGS_options_file, - &db_options, &cf_descriptors); - if (!s.ok()) { - fprintf(stderr, "Unable to load options file %s --- %s\n", - FLAGS_options_file.c_str(), s.ToString().c_str()); - exit(1); - } - db_options.env = new CompositeEnvWrapper(db_stress_env); - options = Options(db_options, cf_descriptors[0].options); - return true; - } - return false; -} - -void InitializeOptionsFromFlags( - const std::shared_ptr& cache, - const std::shared_ptr& filter_policy, - Options& options) { - BlockBasedTableOptions block_based_options; - block_based_options.block_cache = cache; - block_based_options.cache_index_and_filter_blocks = - FLAGS_cache_index_and_filter_blocks; - block_based_options.metadata_cache_options.top_level_index_pinning = - static_cast(FLAGS_top_level_index_pinning); - block_based_options.metadata_cache_options.partition_pinning = - static_cast(FLAGS_partition_pinning); - block_based_options.metadata_cache_options.unpartitioned_pinning = - static_cast(FLAGS_unpartitioned_pinning); - block_based_options.checksum = checksum_type_e; - block_based_options.block_size = FLAGS_block_size; - block_based_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kCompressionDictionaryBuildingBuffer, - {/*.charged = */ FLAGS_charge_compression_dictionary_building_buffer - ? CacheEntryRoleOptions::Decision::kEnabled - : CacheEntryRoleOptions::Decision::kDisabled}}); - block_based_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kFilterConstruction, - {/*.charged = */ FLAGS_charge_filter_construction - ? CacheEntryRoleOptions::Decision::kEnabled - : CacheEntryRoleOptions::Decision::kDisabled}}); - block_based_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kBlockBasedTableReader, - {/*.charged = */ FLAGS_charge_table_reader - ? CacheEntryRoleOptions::Decision::kEnabled - : CacheEntryRoleOptions::Decision::kDisabled}}); - block_based_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kFileMetadata, - {/*.charged = */ FLAGS_charge_file_metadata - ? CacheEntryRoleOptions::Decision::kEnabled - : CacheEntryRoleOptions::Decision::kDisabled}}); - block_based_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kBlobCache, - {/*.charged = */ FLAGS_charge_blob_cache - ? CacheEntryRoleOptions::Decision::kEnabled - : CacheEntryRoleOptions::Decision::kDisabled}}); - block_based_options.format_version = - static_cast(FLAGS_format_version); - block_based_options.index_block_restart_interval = - static_cast(FLAGS_index_block_restart_interval); - block_based_options.filter_policy = filter_policy; - block_based_options.partition_filters = FLAGS_partition_filters; - block_based_options.optimize_filters_for_memory = - FLAGS_optimize_filters_for_memory; - block_based_options.detect_filter_construct_corruption = - FLAGS_detect_filter_construct_corruption; - block_based_options.index_type = - static_cast(FLAGS_index_type); - block_based_options.data_block_index_type = - static_cast( - FLAGS_data_block_index_type); - block_based_options.prepopulate_block_cache = - static_cast( - FLAGS_prepopulate_block_cache); - block_based_options.initial_auto_readahead_size = - FLAGS_initial_auto_readahead_size; - block_based_options.max_auto_readahead_size = FLAGS_max_auto_readahead_size; - block_based_options.num_file_reads_for_auto_readahead = - FLAGS_num_file_reads_for_auto_readahead; - options.table_factory.reset(NewBlockBasedTableFactory(block_based_options)); - options.db_write_buffer_size = FLAGS_db_write_buffer_size; - options.write_buffer_size = FLAGS_write_buffer_size; - options.max_write_buffer_number = FLAGS_max_write_buffer_number; - options.min_write_buffer_number_to_merge = - FLAGS_min_write_buffer_number_to_merge; - options.max_write_buffer_number_to_maintain = - FLAGS_max_write_buffer_number_to_maintain; - options.max_write_buffer_size_to_maintain = - FLAGS_max_write_buffer_size_to_maintain; - options.memtable_prefix_bloom_size_ratio = - FLAGS_memtable_prefix_bloom_size_ratio; - options.memtable_whole_key_filtering = FLAGS_memtable_whole_key_filtering; - options.disable_auto_compactions = FLAGS_disable_auto_compactions; - options.max_background_compactions = FLAGS_max_background_compactions; - options.max_background_flushes = FLAGS_max_background_flushes; - options.compaction_style = - static_cast(FLAGS_compaction_style); - if (options.compaction_style == - ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleFIFO) { - options.compaction_options_fifo.allow_compaction = - FLAGS_fifo_allow_compaction; - } - options.compaction_pri = - static_cast(FLAGS_compaction_pri); - options.num_levels = FLAGS_num_levels; - if (FLAGS_prefix_size >= 0) { - options.prefix_extractor.reset(NewFixedPrefixTransform(FLAGS_prefix_size)); - } - options.max_open_files = FLAGS_open_files; - options.statistics = dbstats; - options.env = db_stress_env; - options.use_fsync = FLAGS_use_fsync; - options.compaction_readahead_size = FLAGS_compaction_readahead_size; - options.allow_mmap_reads = FLAGS_mmap_read; - options.allow_mmap_writes = FLAGS_mmap_write; - options.use_direct_reads = FLAGS_use_direct_reads; - options.use_direct_io_for_flush_and_compaction = - FLAGS_use_direct_io_for_flush_and_compaction; - options.recycle_log_file_num = - static_cast(FLAGS_recycle_log_file_num); - options.target_file_size_base = FLAGS_target_file_size_base; - options.target_file_size_multiplier = FLAGS_target_file_size_multiplier; - options.max_bytes_for_level_base = FLAGS_max_bytes_for_level_base; - options.max_bytes_for_level_multiplier = FLAGS_max_bytes_for_level_multiplier; - options.level0_stop_writes_trigger = FLAGS_level0_stop_writes_trigger; - options.level0_slowdown_writes_trigger = FLAGS_level0_slowdown_writes_trigger; - options.level0_file_num_compaction_trigger = - FLAGS_level0_file_num_compaction_trigger; - options.compression = compression_type_e; - options.bottommost_compression = bottommost_compression_type_e; - options.compression_opts.max_dict_bytes = FLAGS_compression_max_dict_bytes; - options.compression_opts.zstd_max_train_bytes = - FLAGS_compression_zstd_max_train_bytes; - options.compression_opts.parallel_threads = - FLAGS_compression_parallel_threads; - options.compression_opts.max_dict_buffer_bytes = - FLAGS_compression_max_dict_buffer_bytes; - if (ZSTD_FinalizeDictionarySupported()) { - options.compression_opts.use_zstd_dict_trainer = - FLAGS_compression_use_zstd_dict_trainer; - } else if (!FLAGS_compression_use_zstd_dict_trainer) { - fprintf( - stderr, - "WARNING: use_zstd_dict_trainer is false but zstd finalizeDictionary " - "cannot be used because ZSTD 1.4.5+ is not linked with the binary." - " zstd dictionary trainer will be used.\n"); - } - options.max_manifest_file_size = FLAGS_max_manifest_file_size; - options.inplace_update_support = FLAGS_in_place_update; - options.max_subcompactions = static_cast(FLAGS_subcompactions); - options.allow_concurrent_memtable_write = - FLAGS_allow_concurrent_memtable_write; - options.experimental_mempurge_threshold = - FLAGS_experimental_mempurge_threshold; - options.periodic_compaction_seconds = FLAGS_periodic_compaction_seconds; - options.stats_dump_period_sec = - static_cast(FLAGS_stats_dump_period_sec); - options.ttl = FLAGS_compaction_ttl; - options.enable_pipelined_write = FLAGS_enable_pipelined_write; - options.enable_write_thread_adaptive_yield = - FLAGS_enable_write_thread_adaptive_yield; - options.compaction_options_universal.size_ratio = FLAGS_universal_size_ratio; - options.compaction_options_universal.min_merge_width = - FLAGS_universal_min_merge_width; - options.compaction_options_universal.max_merge_width = - FLAGS_universal_max_merge_width; - options.compaction_options_universal.max_size_amplification_percent = - FLAGS_universal_max_size_amplification_percent; - options.atomic_flush = FLAGS_atomic_flush; - options.manual_wal_flush = FLAGS_manual_wal_flush_one_in > 0 ? true : false; - options.avoid_unnecessary_blocking_io = FLAGS_avoid_unnecessary_blocking_io; - options.write_dbid_to_manifest = FLAGS_write_dbid_to_manifest; - options.avoid_flush_during_recovery = FLAGS_avoid_flush_during_recovery; - options.max_write_batch_group_size_bytes = - FLAGS_max_write_batch_group_size_bytes; - options.level_compaction_dynamic_level_bytes = - FLAGS_level_compaction_dynamic_level_bytes; - options.track_and_verify_wals_in_manifest = true; - options.verify_sst_unique_id_in_manifest = - FLAGS_verify_sst_unique_id_in_manifest; - options.memtable_protection_bytes_per_key = - FLAGS_memtable_protection_bytes_per_key; - - // Integrated BlobDB - options.enable_blob_files = FLAGS_enable_blob_files; - options.min_blob_size = FLAGS_min_blob_size; - options.blob_file_size = FLAGS_blob_file_size; - options.blob_compression_type = - StringToCompressionType(FLAGS_blob_compression_type.c_str()); - options.enable_blob_garbage_collection = FLAGS_enable_blob_garbage_collection; - options.blob_garbage_collection_age_cutoff = - FLAGS_blob_garbage_collection_age_cutoff; - options.blob_garbage_collection_force_threshold = - FLAGS_blob_garbage_collection_force_threshold; - options.blob_compaction_readahead_size = FLAGS_blob_compaction_readahead_size; - options.blob_file_starting_level = FLAGS_blob_file_starting_level; - - if (FLAGS_use_blob_cache) { - if (FLAGS_use_shared_block_and_blob_cache) { - options.blob_cache = cache; - } else { - if (FLAGS_blob_cache_size > 0) { - LRUCacheOptions co; - co.capacity = FLAGS_blob_cache_size; - co.num_shard_bits = FLAGS_blob_cache_numshardbits; - options.blob_cache = NewLRUCache(co); - } else { - fprintf(stderr, - "Unable to create a standalone blob cache if blob_cache_size " - "<= 0.\n"); - exit(1); - } - } - switch (FLAGS_prepopulate_blob_cache) { - case 0: - options.prepopulate_blob_cache = PrepopulateBlobCache::kDisable; - break; - case 1: - options.prepopulate_blob_cache = PrepopulateBlobCache::kFlushOnly; - break; - default: - fprintf(stderr, "Unknown prepopulate blob cache mode\n"); - exit(1); - } - } - - options.wal_compression = - StringToCompressionType(FLAGS_wal_compression.c_str()); - - if (FLAGS_enable_tiered_storage) { - options.bottommost_temperature = Temperature::kCold; - } - options.preclude_last_level_data_seconds = - FLAGS_preclude_last_level_data_seconds; - options.preserve_internal_time_seconds = FLAGS_preserve_internal_time_seconds; - - switch (FLAGS_rep_factory) { - case kSkipList: - // no need to do anything - break; - case kHashSkipList: - options.memtable_factory.reset(NewHashSkipListRepFactory(10000)); - break; - case kVectorRep: - options.memtable_factory.reset(new VectorRepFactory()); - break; - } - if (FLAGS_use_full_merge_v1) { - options.merge_operator = MergeOperators::CreateDeprecatedPutOperator(); - } else { - options.merge_operator = MergeOperators::CreatePutOperator(); - } - - if (FLAGS_enable_compaction_filter) { - options.compaction_filter_factory = - std::make_shared(); - } - - options.best_efforts_recovery = FLAGS_best_efforts_recovery; - options.paranoid_file_checks = FLAGS_paranoid_file_checks; - options.fail_if_options_file_error = FLAGS_fail_if_options_file_error; - - if (FLAGS_user_timestamp_size > 0) { - CheckAndSetOptionsForUserTimestamp(options); - } - - options.allow_data_in_errors = FLAGS_allow_data_in_errors; -} - -void InitializeOptionsGeneral( - const std::shared_ptr& cache, - const std::shared_ptr& filter_policy, - Options& options) { - options.create_missing_column_families = true; - options.create_if_missing = true; - - if (!options.statistics) { - options.statistics = dbstats; - } - - if (options.env == Options().env) { - options.env = db_stress_env; - } - - assert(options.table_factory); - auto table_options = - options.table_factory->GetOptions(); - if (table_options) { - if (FLAGS_cache_size > 0) { - table_options->block_cache = cache; - } - if (!table_options->filter_policy) { - table_options->filter_policy = filter_policy; - } - } - - // TODO: row_cache, thread-pool IO priority, CPU priority. - - if (!options.rate_limiter) { - if (FLAGS_rate_limiter_bytes_per_sec > 0) { - options.rate_limiter.reset(NewGenericRateLimiter( - FLAGS_rate_limiter_bytes_per_sec, 1000 /* refill_period_us */, - 10 /* fairness */, - FLAGS_rate_limit_bg_reads ? RateLimiter::Mode::kReadsOnly - : RateLimiter::Mode::kWritesOnly)); - } - } - - if (!options.file_checksum_gen_factory) { - options.file_checksum_gen_factory = - GetFileChecksumImpl(FLAGS_file_checksum_impl); - } - - if (FLAGS_sst_file_manager_bytes_per_sec > 0 || - FLAGS_sst_file_manager_bytes_per_truncate > 0) { - Status status; - options.sst_file_manager.reset(NewSstFileManager( - db_stress_env, options.info_log, "" /* trash_dir */, - static_cast(FLAGS_sst_file_manager_bytes_per_sec), - true /* delete_existing_trash */, &status, - 0.25 /* max_trash_db_ratio */, - FLAGS_sst_file_manager_bytes_per_truncate)); - if (!status.ok()) { - fprintf(stderr, "SstFileManager creation failed: %s\n", - status.ToString().c_str()); - exit(1); - } - } - - if (FLAGS_preserve_unverified_changes) { - if (!options.avoid_flush_during_recovery) { - fprintf(stderr, - "WARNING: flipping `avoid_flush_during_recovery` to true for " - "`preserve_unverified_changes` to keep all files\n"); - options.avoid_flush_during_recovery = true; - } - // Together with `avoid_flush_during_recovery == true`, this will prevent - // live files from becoming obsolete and deleted between `DB::Open()` and - // `DisableFileDeletions()` due to flush or compaction. We do not need to - // warn the user since we will reenable compaction soon. - options.disable_auto_compactions = true; - } - - options.table_properties_collector_factories.emplace_back( - std::make_shared()); -} - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_test_base.h b/db_stress_tool/db_stress_test_base.h deleted file mode 100644 index e6de74d7b..000000000 --- a/db_stress_tool/db_stress_test_base.h +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#pragma once - -#include "db_stress_tool/db_stress_common.h" -#include "db_stress_tool/db_stress_shared_state.h" - -namespace ROCKSDB_NAMESPACE { -class SystemClock; -class Transaction; -class TransactionDB; -struct TransactionDBOptions; - -class StressTest { - public: - StressTest(); - - virtual ~StressTest(); - - std::shared_ptr NewCache(size_t capacity, int32_t num_shard_bits); - - static std::vector GetBlobCompressionTags(); - - bool BuildOptionsTable(); - - void InitDb(SharedState*); - // The initialization work is split into two parts to avoid a circular - // dependency with `SharedState`. - virtual void FinishInitDb(SharedState*); - void TrackExpectedState(SharedState* shared); - void OperateDb(ThreadState* thread); - virtual void VerifyDb(ThreadState* thread) const = 0; - virtual void ContinuouslyVerifyDb(ThreadState* /*thread*/) const = 0; - void PrintStatistics(); - - protected: - Status AssertSame(DB* db, ColumnFamilyHandle* cf, - ThreadState::SnapshotState& snap_state); - - // Currently PreloadDb has to be single-threaded. - void PreloadDbAndReopenAsReadOnly(int64_t number_of_keys, - SharedState* shared); - - Status SetOptions(ThreadState* thread); - - // For transactionsDB, there can be txns prepared but not yet committeed - // right before previous stress run crash. - // They will be recovered and processed through - // ProcessRecoveredPreparedTxnsHelper on the start of current stress run. - void ProcessRecoveredPreparedTxns(SharedState* shared); - - // Default implementation will first update ExpectedState to be - // `SharedState::UNKNOWN` for each keys in `txn` and then randomly - // commit or rollback `txn`. - virtual void ProcessRecoveredPreparedTxnsHelper(Transaction* txn, - SharedState* shared); - - Status NewTxn(WriteOptions& write_opts, Transaction** txn); - - Status CommitTxn(Transaction* txn, ThreadState* thread = nullptr); - - Status RollbackTxn(Transaction* txn); - - virtual void MaybeClearOneColumnFamily(ThreadState* /* thread */) {} - - virtual bool ShouldAcquireMutexOnKey() const { return false; } - - // Returns true if DB state is tracked by the stress test. - virtual bool IsStateTracked() const = 0; - - virtual std::vector GenerateColumnFamilies( - const int /* num_column_families */, int rand_column_family) const { - return {rand_column_family}; - } - - virtual std::vector GenerateKeys(int64_t rand_key) const { - return {rand_key}; - } - - virtual Status TestGet(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - virtual std::vector TestMultiGet( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - virtual void TestGetEntity(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - virtual Status TestPrefixScan(ThreadState* thread, - const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - virtual Status TestPut(ThreadState* thread, WriteOptions& write_opts, - const ReadOptions& read_opts, - const std::vector& cf_ids, - const std::vector& keys, - char (&value)[100]) = 0; - - virtual Status TestDelete(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - virtual Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - virtual void TestIngestExternalFile( - ThreadState* thread, const std::vector& rand_column_families, - const std::vector& rand_keys) = 0; - - // Issue compact range, starting with start_key, whose integer value - // is rand_key. - virtual void TestCompactRange(ThreadState* thread, int64_t rand_key, - const Slice& start_key, - ColumnFamilyHandle* column_family); - - // Calculate a hash value for all keys in range [start_key, end_key] - // at a certain snapshot. - uint32_t GetRangeHash(ThreadState* thread, const Snapshot* snapshot, - ColumnFamilyHandle* column_family, - const Slice& start_key, const Slice& end_key); - - // Return a column family handle that mirrors what is pointed by - // `column_family_id`, which will be used to validate data to be correct. - // By default, the column family itself will be returned. - virtual ColumnFamilyHandle* GetControlCfh(ThreadState* /* thread*/, - int column_family_id) { - return column_families_[column_family_id]; - } - - // Generated a list of keys that close to boundaries of SST keys. - // If there isn't any SST file in the DB, return empty list. - std::vector GetWhiteBoxKeys(ThreadState* thread, DB* db, - ColumnFamilyHandle* cfh, - size_t num_keys); - - // Given a key K, this creates an iterator which scans to K and then - // does a random sequence of Next/Prev operations. - virtual Status TestIterate(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys); - - virtual Status TestIterateAgainstExpected( - ThreadState* /* thread */, const ReadOptions& /* read_opts */, - const std::vector& /* rand_column_families */, - const std::vector& /* rand_keys */) { - return Status::NotSupported(); - } - - // Enum used by VerifyIterator() to identify the mode to validate. - enum LastIterateOp { - kLastOpSeek, - kLastOpSeekForPrev, - kLastOpNextOrPrev, - kLastOpSeekToFirst, - kLastOpSeekToLast - }; - - // Compare the two iterator, iter and cmp_iter are in the same position, - // unless iter might be made invalidate or undefined because of - // upper or lower bounds, or prefix extractor. - // Will flag failure if the verification fails. - // diverged = true if the two iterator is already diverged. - // True if verification passed, false if not. - // op_logs is the information to print when validation fails. - void VerifyIterator(ThreadState* thread, ColumnFamilyHandle* cmp_cfh, - const ReadOptions& ro, Iterator* iter, Iterator* cmp_iter, - LastIterateOp op, const Slice& seek_key, - const std::string& op_logs, bool* diverged); - - virtual Status TestBackupRestore(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys); - - virtual Status TestCheckpoint(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys); - - void TestCompactFiles(ThreadState* thread, ColumnFamilyHandle* column_family); - - Status TestFlush(const std::vector& rand_column_families); - - Status TestPauseBackground(ThreadState* thread); - - void TestAcquireSnapshot(ThreadState* thread, int rand_column_family, - const std::string& keystr, uint64_t i); - - Status MaybeReleaseSnapshots(ThreadState* thread, uint64_t i); - Status VerifyGetLiveFiles() const; - Status VerifyGetSortedWalFiles() const; - Status VerifyGetCurrentWalFile() const; - void TestGetProperty(ThreadState* thread) const; - - virtual Status TestApproximateSize( - ThreadState* thread, uint64_t iteration, - const std::vector& rand_column_families, - const std::vector& rand_keys); - - virtual Status TestCustomOperations( - ThreadState* /*thread*/, - const std::vector& /*rand_column_families*/) { - return Status::NotSupported("TestCustomOperations() must be overridden"); - } - - void VerificationAbort(SharedState* shared, std::string msg, Status s) const; - - void VerificationAbort(SharedState* shared, std::string msg, int cf, - int64_t key) const; - - void VerificationAbort(SharedState* shared, std::string msg, int cf, - int64_t key, Slice value_from_db, - Slice value_from_expected) const; - - void VerificationAbort(SharedState* shared, int cf, int64_t key, - const Slice& value, const WideColumns& columns) const; - - static std::string DebugString(const Slice& value, - const WideColumns& columns); - - void PrintEnv() const; - - void Open(SharedState* shared); - - void Reopen(ThreadState* thread); - - virtual void RegisterAdditionalListeners() {} - - virtual void PrepareTxnDbOptions(SharedState* /*shared*/, - TransactionDBOptions& /*txn_db_opts*/) {} - - // Returns whether the timestamp of read_opts is updated. - bool MaybeUseOlderTimestampForPointLookup(ThreadState* thread, - std::string& ts_str, - Slice& ts_slice, - ReadOptions& read_opts); - - void MaybeUseOlderTimestampForRangeScan(ThreadState* thread, - std::string& ts_str, Slice& ts_slice, - ReadOptions& read_opts); - - std::shared_ptr cache_; - std::shared_ptr compressed_cache_; - std::shared_ptr filter_policy_; - DB* db_; - TransactionDB* txn_db_; - - // Currently only used in MultiOpsTxnsStressTest - std::atomic db_aptr_; - - Options options_; - SystemClock* clock_; - std::vector column_families_; - std::vector column_family_names_; - std::atomic new_column_family_name_; - int num_times_reopened_; - std::unordered_map> options_table_; - std::vector options_index_; - std::atomic db_preload_finished_; - - // Fields used for continuous verification from another thread - DB* cmp_db_; - std::vector cmp_cfhs_; - bool is_db_stopped_; -}; - -// Load options from OPTIONS file and populate `options`. -extern bool InitializeOptionsFromFile(Options& options); - -// Initialize `options` using command line arguments. -// When this function is called, `cache`, `block_cache_compressed`, -// `filter_policy` have all been initialized. Therefore, we just pass them as -// input arguments. -extern void InitializeOptionsFromFlags( - const std::shared_ptr& cache, - const std::shared_ptr& filter_policy, Options& options); - -// Initialize `options` on which `InitializeOptionsFromFile()` and -// `InitializeOptionsFromFlags()` have both been called already. -// There are two cases. -// Case 1: OPTIONS file is not specified. Command line arguments have been used -// to initialize `options`. InitializeOptionsGeneral() will use -// `cache` and `filter_policy` to initialize -// corresponding fields of `options`. InitializeOptionsGeneral() will -// also set up other fields of `options` so that stress test can run. -// Examples include `create_if_missing` and -// `create_missing_column_families`, etc. -// Case 2: OPTIONS file is specified. It is possible that, after loading from -// the given OPTIONS files, some shared object fields are still not -// initialized because they are not set in the OPTIONS file. In this -// case, if command line arguments indicate that the user wants to set -// up such shared objects, e.g. block cache, compressed block cache, -// row cache, filter policy, then InitializeOptionsGeneral() will honor -// the user's choice, thus passing `cache`, -// `filter_policy` as input arguments. -// -// InitializeOptionsGeneral() must not overwrite fields of `options` loaded -// from OPTIONS file. -extern void InitializeOptionsGeneral( - const std::shared_ptr& cache, - const std::shared_ptr& filter_policy, Options& options); - -// If no OPTIONS file is specified, set up `options` so that we can test -// user-defined timestamp which requires `-user_timestamp_size=8`. -// This function also checks for known (currently) incompatible features with -// user-defined timestamp. -extern void CheckAndSetOptionsForUserTimestamp(Options& options); - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/db_stress_tool.cc b/db_stress_tool/db_stress_tool.cc deleted file mode 100644 index c37117921..000000000 --- a/db_stress_tool/db_stress_tool.cc +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// -// The test uses an array to compare against values written to the database. -// Keys written to the array are in 1:1 correspondence to the actual values in -// the database according to the formula in the function GenerateValue. - -// Space is reserved in the array from 0 to FLAGS_max_key and values are -// randomly written/deleted/read from those positions. During verification we -// compare all the positions in the array. To shorten/elongate the running -// time, you could change the settings: FLAGS_max_key, FLAGS_ops_per_thread, -// (sometimes also FLAGS_threads). -// -// NOTE that if FLAGS_test_batches_snapshots is set, the test will have -// different behavior. See comment of the flag for details. - -#include "db_stress_tool/db_stress_shared_state.h" -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" -#include "db_stress_tool/db_stress_driver.h" -#include "rocksdb/convenience.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { -namespace { -static std::shared_ptr env_guard; -static std::shared_ptr env_wrapper_guard; -static std::shared_ptr - dbsl_env_wrapper_guard; -static std::shared_ptr fault_env_guard; -} // namespace - -KeyGenContext key_gen_ctx; - -int db_stress_tool(int argc, char** argv) { - SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) + - " [OPTIONS]..."); - ParseCommandLineFlags(&argc, &argv, true); - - SanitizeDoubleParam(&FLAGS_bloom_bits); - SanitizeDoubleParam(&FLAGS_memtable_prefix_bloom_size_ratio); - SanitizeDoubleParam(&FLAGS_max_bytes_for_level_multiplier); - -#ifndef NDEBUG - if (FLAGS_mock_direct_io) { - SetupSyncPointsToMockDirectIO(); - } -#endif - if (FLAGS_statistics) { - dbstats = ROCKSDB_NAMESPACE::CreateDBStatistics(); - if (FLAGS_test_secondary) { - dbstats_secondaries = ROCKSDB_NAMESPACE::CreateDBStatistics(); - } - } - compression_type_e = StringToCompressionType(FLAGS_compression_type.c_str()); - bottommost_compression_type_e = - StringToCompressionType(FLAGS_bottommost_compression_type.c_str()); - checksum_type_e = StringToChecksumType(FLAGS_checksum_type.c_str()); - - Env* raw_env; - - int env_opts = !FLAGS_env_uri.empty() + !FLAGS_fs_uri.empty(); - if (env_opts > 1) { - fprintf(stderr, "Error: --env_uri and --fs_uri are mutually exclusive\n"); - exit(1); - } - - Status s = Env::CreateFromUri(ConfigOptions(), FLAGS_env_uri, FLAGS_fs_uri, - &raw_env, &env_guard); - if (!s.ok()) { - fprintf(stderr, "Error Creating Env URI: %s: %s\n", FLAGS_env_uri.c_str(), - s.ToString().c_str()); - exit(1); - } - dbsl_env_wrapper_guard = std::make_shared(raw_env); - db_stress_listener_env = dbsl_env_wrapper_guard.get(); - - if (FLAGS_read_fault_one_in || FLAGS_sync_fault_injection || - FLAGS_write_fault_one_in || FLAGS_open_metadata_write_fault_one_in || - FLAGS_open_write_fault_one_in || FLAGS_open_read_fault_one_in) { - FaultInjectionTestFS* fs = - new FaultInjectionTestFS(raw_env->GetFileSystem()); - fault_fs_guard.reset(fs); - if (FLAGS_write_fault_one_in) { - fault_fs_guard->SetFilesystemDirectWritable(false); - } else { - fault_fs_guard->SetFilesystemDirectWritable(true); - } - fault_env_guard = - std::make_shared(raw_env, fault_fs_guard); - raw_env = fault_env_guard.get(); - } - - env_wrapper_guard = std::make_shared( - raw_env, std::make_shared(raw_env->GetFileSystem())); - if (!env_opts && !FLAGS_use_io_uring) { - // If using the default Env (Posix), wrap DbStressEnvWrapper with the - // legacy EnvWrapper. This is a workaround to prevent MultiGet and scans - // from failing when IO uring is disabled. The EnvWrapper - // has a default implementation of ReadAsync that redirects to Read. - env_wrapper_guard = std::make_shared(env_wrapper_guard); - } - db_stress_env = env_wrapper_guard.get(); - - FLAGS_rep_factory = StringToRepFactory(FLAGS_memtablerep.c_str()); - - // The number of background threads should be at least as much the - // max number of concurrent compactions. - db_stress_env->SetBackgroundThreads(FLAGS_max_background_compactions, - ROCKSDB_NAMESPACE::Env::Priority::LOW); - db_stress_env->SetBackgroundThreads(FLAGS_num_bottom_pri_threads, - ROCKSDB_NAMESPACE::Env::Priority::BOTTOM); - if (FLAGS_prefixpercent > 0 && FLAGS_prefix_size < 0) { - fprintf(stderr, - "Error: prefixpercent is non-zero while prefix_size is " - "not positive!\n"); - exit(1); - } - if (FLAGS_test_batches_snapshots && FLAGS_prefix_size <= 0) { - fprintf(stderr, - "Error: please specify prefix_size for " - "test_batches_snapshots test!\n"); - exit(1); - } - if (FLAGS_memtable_prefix_bloom_size_ratio > 0.0 && FLAGS_prefix_size < 0 && - !FLAGS_memtable_whole_key_filtering) { - fprintf(stderr, - "Error: please specify positive prefix_size or enable whole key " - "filtering in order to use memtable_prefix_bloom_size_ratio\n"); - exit(1); - } - if ((FLAGS_readpercent + FLAGS_prefixpercent + FLAGS_writepercent + - FLAGS_delpercent + FLAGS_delrangepercent + FLAGS_iterpercent + - FLAGS_customopspercent) != 100) { - fprintf( - stderr, - "Error: " - "Read(-readpercent=%d)+Prefix(-prefixpercent=%d)+Write(-writepercent=%" - "d)+Delete(-delpercent=%d)+DeleteRange(-delrangepercent=%d)" - "+Iterate(-iterpercent=%d)+CustomOps(-customopspercent=%d) percents != " - "100!\n", - FLAGS_readpercent, FLAGS_prefixpercent, FLAGS_writepercent, - FLAGS_delpercent, FLAGS_delrangepercent, FLAGS_iterpercent, - FLAGS_customopspercent); - exit(1); - } - if (FLAGS_disable_wal == 1 && FLAGS_reopen > 0) { - fprintf(stderr, "Error: Db cannot reopen safely with disable_wal set!\n"); - exit(1); - } - if ((unsigned)FLAGS_reopen >= FLAGS_ops_per_thread) { - fprintf(stderr, - "Error: #DB-reopens should be < ops_per_thread\n" - "Provided reopens = %d and ops_per_thread = %lu\n", - FLAGS_reopen, (unsigned long)FLAGS_ops_per_thread); - exit(1); - } - if (FLAGS_test_batches_snapshots && FLAGS_delrangepercent > 0) { - fprintf(stderr, - "Error: nonzero delrangepercent unsupported in " - "test_batches_snapshots mode\n"); - exit(1); - } - if (FLAGS_active_width > FLAGS_max_key) { - fprintf(stderr, "Error: active_width can be at most max_key\n"); - exit(1); - } else if (FLAGS_active_width == 0) { - FLAGS_active_width = FLAGS_max_key; - } - if (FLAGS_value_size_mult * kRandomValueMaxFactor > kValueMaxLen) { - fprintf(stderr, "Error: value_size_mult can be at most %d\n", - kValueMaxLen / kRandomValueMaxFactor); - exit(1); - } - if (FLAGS_use_merge && FLAGS_nooverwritepercent == 100) { - fprintf( - stderr, - "Error: nooverwritepercent must not be 100 when using merge operands"); - exit(1); - } - if (FLAGS_ingest_external_file_one_in > 0 && - FLAGS_nooverwritepercent == 100) { - fprintf( - stderr, - "Error: nooverwritepercent must not be 100 when using file ingestion"); - exit(1); - } - if (FLAGS_clear_column_family_one_in > 0 && FLAGS_backup_one_in > 0) { - fprintf(stderr, - "Error: clear_column_family_one_in must be 0 when using backup\n"); - exit(1); - } - if (FLAGS_test_cf_consistency && FLAGS_disable_wal) { - FLAGS_atomic_flush = true; - } - - if (FLAGS_read_only) { - if (FLAGS_writepercent != 0 || FLAGS_delpercent != 0 || - FLAGS_delrangepercent != 0) { - fprintf(stderr, "Error: updates are not supported in read only mode\n"); - exit(1); - } else if (FLAGS_checkpoint_one_in > 0 && - FLAGS_clear_column_family_one_in > 0) { - fprintf(stdout, - "Warn: checkpoint won't be validated since column families may " - "be dropped.\n"); - } - } - - // Choose a location for the test database if none given with --db= - if (FLAGS_db.empty()) { - std::string default_db_path; - db_stress_env->GetTestDirectory(&default_db_path); - default_db_path += "/dbstress"; - FLAGS_db = default_db_path; - } - - if ((FLAGS_test_secondary || FLAGS_continuous_verification_interval > 0) && - FLAGS_secondaries_base.empty()) { - std::string default_secondaries_path; - db_stress_env->GetTestDirectory(&default_secondaries_path); - default_secondaries_path += "/dbstress_secondaries"; - s = db_stress_env->CreateDirIfMissing(default_secondaries_path); - if (!s.ok()) { - fprintf(stderr, "Failed to create directory %s: %s\n", - default_secondaries_path.c_str(), s.ToString().c_str()); - exit(1); - } - FLAGS_secondaries_base = default_secondaries_path; - } - - if (FLAGS_best_efforts_recovery && !FLAGS_skip_verifydb && - !FLAGS_disable_wal) { - fprintf(stderr, - "With best-efforts recovery, either skip_verifydb or disable_wal " - "should be set to true.\n"); - exit(1); - } - if (FLAGS_skip_verifydb) { - if (FLAGS_verify_db_one_in > 0) { - fprintf(stderr, - "Must set -verify_db_one_in=0 if skip_verifydb is true.\n"); - exit(1); - } - if (FLAGS_continuous_verification_interval > 0) { - fprintf(stderr, - "Must set -continuous_verification_interval=0 if skip_verifydb " - "is true.\n"); - exit(1); - } - } - if (FLAGS_enable_compaction_filter && - (FLAGS_acquire_snapshot_one_in > 0 || FLAGS_compact_range_one_in > 0 || - FLAGS_iterpercent > 0 || FLAGS_test_batches_snapshots || - FLAGS_test_cf_consistency)) { - fprintf( - stderr, - "Error: acquire_snapshot_one_in, compact_range_one_in, iterpercent, " - "test_batches_snapshots must all be 0 when using compaction filter\n"); - exit(1); - } - if (FLAGS_test_multi_ops_txns) { - CheckAndSetOptionsForMultiOpsTxnStressTest(); - } - - if (FLAGS_create_timestamped_snapshot_one_in > 0) { - if (!FLAGS_use_txn) { - fprintf(stderr, "timestamped snapshot supported only in TransactionDB\n"); - exit(1); - } else if (FLAGS_txn_write_policy != 0) { - fprintf(stderr, - "timestamped snapshot supported only in write-committed\n"); - exit(1); - } - } - - if (FLAGS_preserve_unverified_changes && FLAGS_reopen != 0) { - fprintf(stderr, - "Reopen DB is incompatible with preserving unverified changes\n"); - exit(1); - } - - if (FLAGS_use_txn && FLAGS_sync_fault_injection && - FLAGS_txn_write_policy != 0) { - fprintf(stderr, - "For TransactionDB, correctness testing with unsync data loss is " - "currently compatible with only write committed policy\n"); - exit(1); - } - - if (FLAGS_use_put_entity_one_in > 0 && - (FLAGS_ingest_external_file_one_in > 0 || FLAGS_use_merge || - FLAGS_use_full_merge_v1 || FLAGS_use_txn || FLAGS_test_multi_ops_txns || - FLAGS_user_timestamp_size > 0)) { - fprintf(stderr, - "PutEntity is currently incompatible with SstFileWriter, Merge," - " transactions, and user-defined timestamps\n"); - exit(1); - } - -#ifndef NDEBUG - KillPoint* kp = KillPoint::GetInstance(); - kp->rocksdb_kill_odds = FLAGS_kill_random_test; - kp->rocksdb_kill_exclude_prefixes = SplitString(FLAGS_kill_exclude_prefixes); -#endif - - unsigned int levels = FLAGS_max_key_len; - std::vector weights; - uint64_t scale_factor = FLAGS_key_window_scale_factor; - key_gen_ctx.window = scale_factor * 100; - if (!FLAGS_key_len_percent_dist.empty()) { - weights = SplitString(FLAGS_key_len_percent_dist); - if (weights.size() != levels) { - fprintf(stderr, - "Number of weights in key_len_dist should be equal to" - " max_key_len"); - exit(1); - } - - uint64_t total_weight = 0; - for (std::string& weight : weights) { - uint64_t val = std::stoull(weight); - key_gen_ctx.weights.emplace_back(val * scale_factor); - total_weight += val; - } - if (total_weight != 100) { - fprintf(stderr, "Sum of all weights in key_len_dist should be 100"); - exit(1); - } - } else { - uint64_t keys_per_level = key_gen_ctx.window / levels; - for (unsigned int level = 0; level + 1 < levels; ++level) { - key_gen_ctx.weights.emplace_back(keys_per_level); - } - key_gen_ctx.weights.emplace_back(key_gen_ctx.window - - keys_per_level * (levels - 1)); - } - std::unique_ptr shared; - std::unique_ptr stress; - if (FLAGS_test_cf_consistency) { - stress.reset(CreateCfConsistencyStressTest()); - } else if (FLAGS_test_batches_snapshots) { - stress.reset(CreateBatchedOpsStressTest()); - } else if (FLAGS_test_multi_ops_txns) { - stress.reset(CreateMultiOpsTxnsStressTest()); - } else { - stress.reset(CreateNonBatchedOpsStressTest()); - } - // Initialize the Zipfian pre-calculated array - InitializeHotKeyGenerator(FLAGS_hot_key_alpha); - shared.reset(new SharedState(db_stress_env, stress.get())); - if (RunStressTest(shared.get())) { - return 0; - } else { - return 1; - } -} - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/expected_state.cc b/db_stress_tool/expected_state.cc deleted file mode 100644 index 0d921c712..000000000 --- a/db_stress_tool/expected_state.cc +++ /dev/null @@ -1,745 +0,0 @@ -// Copyright (c) 2021-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#ifdef GFLAGS - -#include "db_stress_tool/expected_state.h" - -#include "db/wide/wide_column_serialization.h" -#include "db_stress_tool/db_stress_common.h" -#include "db_stress_tool/db_stress_shared_state.h" -#include "rocksdb/trace_reader_writer.h" -#include "rocksdb/trace_record_result.h" - -namespace ROCKSDB_NAMESPACE { - -ExpectedState::ExpectedState(size_t max_key, size_t num_column_families) - : max_key_(max_key), - num_column_families_(num_column_families), - values_(nullptr) {} - -void ExpectedState::ClearColumnFamily(int cf) { - std::fill(&Value(cf, 0 /* key */), &Value(cf + 1, 0 /* key */), - SharedState::DELETION_SENTINEL); -} - -void ExpectedState::Put(int cf, int64_t key, uint32_t value_base, - bool pending) { - if (!pending) { - // prevent expected-value update from reordering before Write - std::atomic_thread_fence(std::memory_order_release); - } - Value(cf, key).store(pending ? SharedState::UNKNOWN_SENTINEL : value_base, - std::memory_order_relaxed); - if (pending) { - // prevent Write from reordering before expected-value update - std::atomic_thread_fence(std::memory_order_release); - } -} - -uint32_t ExpectedState::Get(int cf, int64_t key) const { - return Value(cf, key); -} - -bool ExpectedState::Delete(int cf, int64_t key, bool pending) { - if (Value(cf, key) == SharedState::DELETION_SENTINEL) { - return false; - } - Put(cf, key, SharedState::DELETION_SENTINEL, pending); - return true; -} - -bool ExpectedState::SingleDelete(int cf, int64_t key, bool pending) { - return Delete(cf, key, pending); -} - -int ExpectedState::DeleteRange(int cf, int64_t begin_key, int64_t end_key, - bool pending) { - int covered = 0; - for (int64_t key = begin_key; key < end_key; ++key) { - if (Delete(cf, key, pending)) { - ++covered; - } - } - return covered; -} - -bool ExpectedState::Exists(int cf, int64_t key) { - // UNKNOWN_SENTINEL counts as exists. That assures a key for which overwrite - // is disallowed can't be accidentally added a second time, in which case - // SingleDelete wouldn't be able to properly delete the key. It does allow - // the case where a SingleDelete might be added which covers nothing, but - // that's not a correctness issue. - uint32_t expected_value = Value(cf, key).load(); - return expected_value != SharedState::DELETION_SENTINEL; -} - -void ExpectedState::Reset() { - for (size_t i = 0; i < num_column_families_; ++i) { - for (size_t j = 0; j < max_key_; ++j) { - Value(static_cast(i), j) - .store(SharedState::DELETION_SENTINEL, std::memory_order_relaxed); - } - } -} - -FileExpectedState::FileExpectedState(std::string expected_state_file_path, - size_t max_key, size_t num_column_families) - : ExpectedState(max_key, num_column_families), - expected_state_file_path_(expected_state_file_path) {} - -Status FileExpectedState::Open(bool create) { - size_t expected_values_size = GetValuesLen(); - - Env* default_env = Env::Default(); - - Status status; - if (create) { - std::unique_ptr wfile; - const EnvOptions soptions; - status = default_env->NewWritableFile(expected_state_file_path_, &wfile, - soptions); - if (status.ok()) { - std::string buf(expected_values_size, '\0'); - status = wfile->Append(buf); - } - } - if (status.ok()) { - status = default_env->NewMemoryMappedFileBuffer( - expected_state_file_path_, &expected_state_mmap_buffer_); - } - if (status.ok()) { - assert(expected_state_mmap_buffer_->GetLen() == expected_values_size); - values_ = static_cast*>( - expected_state_mmap_buffer_->GetBase()); - assert(values_ != nullptr); - if (create) { - Reset(); - } - } else { - assert(values_ == nullptr); - } - return status; -} - -AnonExpectedState::AnonExpectedState(size_t max_key, size_t num_column_families) - : ExpectedState(max_key, num_column_families) {} - -#ifndef NDEBUG -Status AnonExpectedState::Open(bool create) { -#else -Status AnonExpectedState::Open(bool /* create */) { -#endif - // AnonExpectedState only supports being freshly created. - assert(create); - values_allocation_.reset( - new std::atomic[GetValuesLen() / - sizeof(std::atomic)]); - values_ = &values_allocation_[0]; - Reset(); - return Status::OK(); -} - -ExpectedStateManager::ExpectedStateManager(size_t max_key, - size_t num_column_families) - : max_key_(max_key), - num_column_families_(num_column_families), - latest_(nullptr) {} - -ExpectedStateManager::~ExpectedStateManager() {} - -const std::string FileExpectedStateManager::kLatestBasename = "LATEST"; -const std::string FileExpectedStateManager::kStateFilenameSuffix = ".state"; -const std::string FileExpectedStateManager::kTraceFilenameSuffix = ".trace"; -const std::string FileExpectedStateManager::kTempFilenamePrefix = "."; -const std::string FileExpectedStateManager::kTempFilenameSuffix = ".tmp"; - -FileExpectedStateManager::FileExpectedStateManager( - size_t max_key, size_t num_column_families, - std::string expected_state_dir_path) - : ExpectedStateManager(max_key, num_column_families), - expected_state_dir_path_(std::move(expected_state_dir_path)) { - assert(!expected_state_dir_path_.empty()); -} - -Status FileExpectedStateManager::Open() { - // Before doing anything, sync directory state with ours. That is, determine - // `saved_seqno_`, and create any necessary missing files. - std::vector expected_state_dir_children; - Status s = Env::Default()->GetChildren(expected_state_dir_path_, - &expected_state_dir_children); - bool found_trace = false; - if (s.ok()) { - for (size_t i = 0; i < expected_state_dir_children.size(); ++i) { - const auto& filename = expected_state_dir_children[i]; - if (filename.size() >= kStateFilenameSuffix.size() && - filename.rfind(kStateFilenameSuffix) == - filename.size() - kStateFilenameSuffix.size() && - filename.rfind(kLatestBasename, 0) == std::string::npos) { - SequenceNumber found_seqno = ParseUint64( - filename.substr(0, filename.size() - kStateFilenameSuffix.size())); - if (saved_seqno_ == kMaxSequenceNumber || found_seqno > saved_seqno_) { - saved_seqno_ = found_seqno; - } - } - } - // Check if crash happened after creating state file but before creating - // trace file. - if (saved_seqno_ != kMaxSequenceNumber) { - std::string saved_seqno_trace_path = GetPathForFilename( - std::to_string(saved_seqno_) + kTraceFilenameSuffix); - Status exists_status = Env::Default()->FileExists(saved_seqno_trace_path); - if (exists_status.ok()) { - found_trace = true; - } else if (exists_status.IsNotFound()) { - found_trace = false; - } else { - s = exists_status; - } - } - } - if (s.ok() && saved_seqno_ != kMaxSequenceNumber && !found_trace) { - // Create an empty trace file so later logic does not need to distinguish - // missing vs. empty trace file. - std::unique_ptr wfile; - const EnvOptions soptions; - std::string saved_seqno_trace_path = - GetPathForFilename(std::to_string(saved_seqno_) + kTraceFilenameSuffix); - s = Env::Default()->NewWritableFile(saved_seqno_trace_path, &wfile, - soptions); - } - - if (s.ok()) { - s = Clean(); - } - - std::string expected_state_file_path = - GetPathForFilename(kLatestBasename + kStateFilenameSuffix); - bool found = false; - if (s.ok()) { - Status exists_status = Env::Default()->FileExists(expected_state_file_path); - if (exists_status.ok()) { - found = true; - } else if (exists_status.IsNotFound()) { - found = false; - } else { - s = exists_status; - } - } - - if (!found) { - // Initialize the file in a temp path and then rename it. That way, in case - // this process is killed during setup, `Clean()` will take care of removing - // the incomplete expected values file. - std::string temp_expected_state_file_path = - GetTempPathForFilename(kLatestBasename + kStateFilenameSuffix); - FileExpectedState temp_expected_state(temp_expected_state_file_path, - max_key_, num_column_families_); - if (s.ok()) { - s = temp_expected_state.Open(true /* create */); - } - if (s.ok()) { - s = Env::Default()->RenameFile(temp_expected_state_file_path, - expected_state_file_path); - } - } - - if (s.ok()) { - latest_.reset(new FileExpectedState(std::move(expected_state_file_path), - max_key_, num_column_families_)); - s = latest_->Open(false /* create */); - } - return s; -} - -Status FileExpectedStateManager::SaveAtAndAfter(DB* db) { - SequenceNumber seqno = db->GetLatestSequenceNumber(); - - std::string state_filename = std::to_string(seqno) + kStateFilenameSuffix; - std::string state_file_temp_path = GetTempPathForFilename(state_filename); - std::string state_file_path = GetPathForFilename(state_filename); - - std::string latest_file_path = - GetPathForFilename(kLatestBasename + kStateFilenameSuffix); - - std::string trace_filename = std::to_string(seqno) + kTraceFilenameSuffix; - std::string trace_file_path = GetPathForFilename(trace_filename); - - // Populate a tempfile and then rename it to atomically create ".state" - // with contents from "LATEST.state" - Status s = CopyFile(FileSystem::Default(), latest_file_path, - state_file_temp_path, 0 /* size */, false /* use_fsync */, - nullptr /* io_tracer */, Temperature::kUnknown); - if (s.ok()) { - s = FileSystem::Default()->RenameFile(state_file_temp_path, state_file_path, - IOOptions(), nullptr /* dbg */); - } - SequenceNumber old_saved_seqno = 0; - if (s.ok()) { - old_saved_seqno = saved_seqno_; - saved_seqno_ = seqno; - } - - // If there is a crash now, i.e., after ".state" was created but before - // ".trace" is created, it will be treated as if ".trace" were - // present but empty. - - // Create ".trace" directly. It is initially empty so no need for - // tempfile. - std::unique_ptr trace_writer; - if (s.ok()) { - EnvOptions soptions; - // Disable buffering so traces will not get stuck in application buffer. - soptions.writable_file_max_buffer_size = 0; - s = NewFileTraceWriter(Env::Default(), soptions, trace_file_path, - &trace_writer); - } - if (s.ok()) { - TraceOptions trace_opts; - trace_opts.filter |= kTraceFilterGet; - trace_opts.filter |= kTraceFilterMultiGet; - trace_opts.filter |= kTraceFilterIteratorSeek; - trace_opts.filter |= kTraceFilterIteratorSeekForPrev; - trace_opts.preserve_write_order = true; - s = db->StartTrace(trace_opts, std::move(trace_writer)); - } - - // Delete old state/trace files. Deletion order does not matter since we only - // delete after successfully saving new files, so old files will never be used - // again, even if we crash. - if (s.ok() && old_saved_seqno != kMaxSequenceNumber && - old_saved_seqno != saved_seqno_) { - s = Env::Default()->DeleteFile(GetPathForFilename( - std::to_string(old_saved_seqno) + kStateFilenameSuffix)); - } - if (s.ok() && old_saved_seqno != kMaxSequenceNumber && - old_saved_seqno != saved_seqno_) { - s = Env::Default()->DeleteFile(GetPathForFilename( - std::to_string(old_saved_seqno) + kTraceFilenameSuffix)); - } - return s; -} - -bool FileExpectedStateManager::HasHistory() { - return saved_seqno_ != kMaxSequenceNumber; -} - - -namespace { - -// An `ExpectedStateTraceRecordHandler` applies a configurable number of -// write operation trace records to the configured expected state. It is used in -// `FileExpectedStateManager::Restore()` to sync the expected state with the -// DB's post-recovery state. -class ExpectedStateTraceRecordHandler : public TraceRecord::Handler, - public WriteBatch::Handler { - public: - ExpectedStateTraceRecordHandler(uint64_t max_write_ops, ExpectedState* state) - : max_write_ops_(max_write_ops), - state_(state), - buffered_writes_(nullptr) {} - - ~ExpectedStateTraceRecordHandler() { assert(IsDone()); } - - // True if we have already reached the limit on write operations to apply. - bool IsDone() { return num_write_ops_ == max_write_ops_; } - - Status Handle(const WriteQueryTraceRecord& record, - std::unique_ptr* /* result */) override { - if (IsDone()) { - return Status::OK(); - } - WriteBatch batch(record.GetWriteBatchRep().ToString()); - return batch.Iterate(this); - } - - // Ignore reads. - Status Handle(const GetQueryTraceRecord& /* record */, - std::unique_ptr* /* result */) override { - return Status::OK(); - } - - // Ignore reads. - Status Handle(const IteratorSeekQueryTraceRecord& /* record */, - std::unique_ptr* /* result */) override { - return Status::OK(); - } - - // Ignore reads. - Status Handle(const MultiGetQueryTraceRecord& /* record */, - std::unique_ptr* /* result */) override { - return Status::OK(); - } - - // Below are the WriteBatch::Handler overrides. We could use a separate - // object, but it's convenient and works to share state with the - // `TraceRecord::Handler`. - - Status PutCF(uint32_t column_family_id, const Slice& key_with_ts, - const Slice& value) override { - Slice key = - StripTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size); - uint64_t key_id; - if (!GetIntVal(key.ToString(), &key_id)) { - return Status::Corruption("unable to parse key", key.ToString()); - } - uint32_t value_id = GetValueBase(value); - - bool should_buffer_write = !(buffered_writes_ == nullptr); - if (should_buffer_write) { - return WriteBatchInternal::Put(buffered_writes_.get(), column_family_id, - key, value); - } - - state_->Put(column_family_id, static_cast(key_id), value_id, - false /* pending */); - ++num_write_ops_; - return Status::OK(); - } - - Status PutEntityCF(uint32_t column_family_id, const Slice& key_with_ts, - const Slice& entity) override { - Slice key = - StripTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size); - - uint64_t key_id = 0; - if (!GetIntVal(key.ToString(), &key_id)) { - return Status::Corruption("Unable to parse key", key.ToString()); - } - - Slice entity_copy = entity; - WideColumns columns; - if (!WideColumnSerialization::Deserialize(entity_copy, columns).ok()) { - return Status::Corruption("Unable to deserialize entity", - entity.ToString(/* hex */ true)); - } - - if (!VerifyWideColumns(columns)) { - return Status::Corruption("Wide columns in entity inconsistent", - entity.ToString(/* hex */ true)); - } - - if (buffered_writes_) { - return WriteBatchInternal::PutEntity(buffered_writes_.get(), - column_family_id, key, columns); - } - - assert(!columns.empty()); - assert(columns.front().name() == kDefaultWideColumnName); - - const uint32_t value_base = GetValueBase(columns.front().value()); - - state_->Put(column_family_id, static_cast(key_id), value_base, - false /* pending */); - - ++num_write_ops_; - - return Status::OK(); - } - - Status DeleteCF(uint32_t column_family_id, - const Slice& key_with_ts) override { - Slice key = - StripTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size); - uint64_t key_id; - if (!GetIntVal(key.ToString(), &key_id)) { - return Status::Corruption("unable to parse key", key.ToString()); - } - - bool should_buffer_write = !(buffered_writes_ == nullptr); - if (should_buffer_write) { - return WriteBatchInternal::Delete(buffered_writes_.get(), - column_family_id, key); - } - - state_->Delete(column_family_id, static_cast(key_id), - false /* pending */); - ++num_write_ops_; - return Status::OK(); - } - - Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key_with_ts) override { - bool should_buffer_write = !(buffered_writes_ == nullptr); - if (should_buffer_write) { - Slice key = - StripTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size); - Slice ts = - ExtractTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size); - std::array key_with_ts_arr{{key, ts}}; - return WriteBatchInternal::SingleDelete( - buffered_writes_.get(), column_family_id, - SliceParts(key_with_ts_arr.data(), 2)); - } - - return DeleteCF(column_family_id, key_with_ts); - } - - Status DeleteRangeCF(uint32_t column_family_id, - const Slice& begin_key_with_ts, - const Slice& end_key_with_ts) override { - Slice begin_key = - StripTimestampFromUserKey(begin_key_with_ts, FLAGS_user_timestamp_size); - Slice end_key = - StripTimestampFromUserKey(end_key_with_ts, FLAGS_user_timestamp_size); - uint64_t begin_key_id, end_key_id; - if (!GetIntVal(begin_key.ToString(), &begin_key_id)) { - return Status::Corruption("unable to parse begin key", - begin_key.ToString()); - } - if (!GetIntVal(end_key.ToString(), &end_key_id)) { - return Status::Corruption("unable to parse end key", end_key.ToString()); - } - - bool should_buffer_write = !(buffered_writes_ == nullptr); - if (should_buffer_write) { - return WriteBatchInternal::DeleteRange( - buffered_writes_.get(), column_family_id, begin_key, end_key); - } - - state_->DeleteRange(column_family_id, static_cast(begin_key_id), - static_cast(end_key_id), false /* pending */); - ++num_write_ops_; - return Status::OK(); - } - - Status MergeCF(uint32_t column_family_id, const Slice& key_with_ts, - const Slice& value) override { - Slice key = - StripTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size); - - bool should_buffer_write = !(buffered_writes_ == nullptr); - if (should_buffer_write) { - return WriteBatchInternal::Merge(buffered_writes_.get(), column_family_id, - key, value); - } - - return PutCF(column_family_id, key, value); - } - - Status MarkBeginPrepare(bool = false) override { - assert(!buffered_writes_); - buffered_writes_.reset(new WriteBatch()); - return Status::OK(); - } - - Status MarkEndPrepare(const Slice& xid) override { - assert(buffered_writes_); - std::string xid_str = xid.ToString(); - assert(xid_to_buffered_writes_.find(xid_str) == - xid_to_buffered_writes_.end()); - - xid_to_buffered_writes_[xid_str].swap(buffered_writes_); - - buffered_writes_.reset(); - - return Status::OK(); - } - - Status MarkCommit(const Slice& xid) override { - std::string xid_str = xid.ToString(); - assert(xid_to_buffered_writes_.find(xid_str) != - xid_to_buffered_writes_.end()); - assert(xid_to_buffered_writes_.at(xid_str)); - - Status s = xid_to_buffered_writes_.at(xid_str)->Iterate(this); - xid_to_buffered_writes_.erase(xid_str); - - return s; - } - - Status MarkRollback(const Slice& xid) override { - std::string xid_str = xid.ToString(); - assert(xid_to_buffered_writes_.find(xid_str) != - xid_to_buffered_writes_.end()); - assert(xid_to_buffered_writes_.at(xid_str)); - xid_to_buffered_writes_.erase(xid_str); - - return Status::OK(); - } - - private: - uint64_t num_write_ops_ = 0; - uint64_t max_write_ops_; - ExpectedState* state_; - std::unordered_map> - xid_to_buffered_writes_; - std::unique_ptr buffered_writes_; -}; - -} // anonymous namespace - -Status FileExpectedStateManager::Restore(DB* db) { - assert(HasHistory()); - SequenceNumber seqno = db->GetLatestSequenceNumber(); - if (seqno < saved_seqno_) { - return Status::Corruption("DB is older than any restorable expected state"); - } - - std::string state_filename = - std::to_string(saved_seqno_) + kStateFilenameSuffix; - std::string state_file_path = GetPathForFilename(state_filename); - - std::string latest_file_temp_path = - GetTempPathForFilename(kLatestBasename + kStateFilenameSuffix); - std::string latest_file_path = - GetPathForFilename(kLatestBasename + kStateFilenameSuffix); - - std::string trace_filename = - std::to_string(saved_seqno_) + kTraceFilenameSuffix; - std::string trace_file_path = GetPathForFilename(trace_filename); - - std::unique_ptr trace_reader; - Status s = NewFileTraceReader(Env::Default(), EnvOptions(), trace_file_path, - &trace_reader); - - if (s.ok()) { - // We are going to replay on top of "`seqno`.state" to create a new - // "LATEST.state". Start off by creating a tempfile so we can later make the - // new "LATEST.state" appear atomically using `RenameFile()`. - s = CopyFile(FileSystem::Default(), state_file_path, latest_file_temp_path, - 0 /* size */, false /* use_fsync */, nullptr /* io_tracer */, - Temperature::kUnknown); - } - - { - std::unique_ptr replayer; - std::unique_ptr state; - std::unique_ptr handler; - if (s.ok()) { - state.reset(new FileExpectedState(latest_file_temp_path, max_key_, - num_column_families_)); - s = state->Open(false /* create */); - } - if (s.ok()) { - handler.reset(new ExpectedStateTraceRecordHandler(seqno - saved_seqno_, - state.get())); - // TODO(ajkr): An API limitation requires we provide `handles` although - // they will be unused since we only use the replayer for reading records. - // Just give a default CFH for now to satisfy the requirement. - s = db->NewDefaultReplayer({db->DefaultColumnFamily()} /* handles */, - std::move(trace_reader), &replayer); - } - - if (s.ok()) { - s = replayer->Prepare(); - } - for (;;) { - std::unique_ptr record; - s = replayer->Next(&record); - if (!s.ok()) { - break; - } - std::unique_ptr res; - record->Accept(handler.get(), &res); - } - if (s.IsCorruption() && handler->IsDone()) { - // There could be a corruption reading the tail record of the trace due to - // `db_stress` crashing while writing it. It shouldn't matter as long as - // we already found all the write ops we need to catch up the expected - // state. - s = Status::OK(); - } - if (s.IsIncomplete()) { - // OK because `Status::Incomplete` is expected upon finishing all the - // trace records. - s = Status::OK(); - } - } - - if (s.ok()) { - s = FileSystem::Default()->RenameFile(latest_file_temp_path, - latest_file_path, IOOptions(), - nullptr /* dbg */); - } - if (s.ok()) { - latest_.reset(new FileExpectedState(latest_file_path, max_key_, - num_column_families_)); - s = latest_->Open(false /* create */); - } - - // Delete old state/trace files. We must delete the state file first. - // Otherwise, a crash-recovery immediately after deleting the trace file could - // lead to `Restore()` unable to replay to `seqno`. - if (s.ok()) { - s = Env::Default()->DeleteFile(state_file_path); - } - if (s.ok()) { - saved_seqno_ = kMaxSequenceNumber; - s = Env::Default()->DeleteFile(trace_file_path); - } - return s; -} - -Status FileExpectedStateManager::Clean() { - std::vector expected_state_dir_children; - Status s = Env::Default()->GetChildren(expected_state_dir_path_, - &expected_state_dir_children); - // An incomplete `Open()` or incomplete `SaveAtAndAfter()` could have left - // behind invalid temporary files. An incomplete `SaveAtAndAfter()` could have - // also left behind stale state/trace files. An incomplete `Restore()` could - // have left behind stale trace files. - for (size_t i = 0; s.ok() && i < expected_state_dir_children.size(); ++i) { - const auto& filename = expected_state_dir_children[i]; - if (filename.rfind(kTempFilenamePrefix, 0 /* pos */) == 0 && - filename.size() >= kTempFilenameSuffix.size() && - filename.rfind(kTempFilenameSuffix) == - filename.size() - kTempFilenameSuffix.size()) { - // Delete all temp files. - s = Env::Default()->DeleteFile(GetPathForFilename(filename)); - } else if (filename.size() >= kStateFilenameSuffix.size() && - filename.rfind(kStateFilenameSuffix) == - filename.size() - kStateFilenameSuffix.size() && - filename.rfind(kLatestBasename, 0) == std::string::npos && - ParseUint64(filename.substr( - 0, filename.size() - kStateFilenameSuffix.size())) < - saved_seqno_) { - assert(saved_seqno_ != kMaxSequenceNumber); - // Delete stale state files. - s = Env::Default()->DeleteFile(GetPathForFilename(filename)); - } else if (filename.size() >= kTraceFilenameSuffix.size() && - filename.rfind(kTraceFilenameSuffix) == - filename.size() - kTraceFilenameSuffix.size() && - ParseUint64(filename.substr( - 0, filename.size() - kTraceFilenameSuffix.size())) < - saved_seqno_) { - // Delete stale trace files. - s = Env::Default()->DeleteFile(GetPathForFilename(filename)); - } - } - return s; -} - -std::string FileExpectedStateManager::GetTempPathForFilename( - const std::string& filename) { - assert(!expected_state_dir_path_.empty()); - std::string expected_state_dir_path_slash = - expected_state_dir_path_.back() == '/' ? expected_state_dir_path_ - : expected_state_dir_path_ + "/"; - return expected_state_dir_path_slash + kTempFilenamePrefix + filename + - kTempFilenameSuffix; -} - -std::string FileExpectedStateManager::GetPathForFilename( - const std::string& filename) { - assert(!expected_state_dir_path_.empty()); - std::string expected_state_dir_path_slash = - expected_state_dir_path_.back() == '/' ? expected_state_dir_path_ - : expected_state_dir_path_ + "/"; - return expected_state_dir_path_slash + filename; -} - -AnonExpectedStateManager::AnonExpectedStateManager(size_t max_key, - size_t num_column_families) - : ExpectedStateManager(max_key, num_column_families) {} - -Status AnonExpectedStateManager::Open() { - latest_.reset(new AnonExpectedState(max_key_, num_column_families_)); - return latest_->Open(true /* create */); -} - -} // namespace ROCKSDB_NAMESPACE - -#endif // GFLAGS diff --git a/db_stress_tool/expected_state.h b/db_stress_tool/expected_state.h deleted file mode 100644 index 41d747e76..000000000 --- a/db_stress_tool/expected_state.h +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) 2021-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#ifdef GFLAGS - -#pragma once - -#include - -#include -#include - -#include "db/dbformat.h" -#include "file/file_util.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/file_system.h" -#include "rocksdb/rocksdb_namespace.h" -#include "rocksdb/types.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -// An `ExpectedState` provides read/write access to expected values for every -// key. -class ExpectedState { - public: - explicit ExpectedState(size_t max_key, size_t num_column_families); - - virtual ~ExpectedState() {} - - // Requires external locking preventing concurrent execution with any other - // member function. - virtual Status Open(bool create) = 0; - - // Requires external locking covering all keys in `cf`. - void ClearColumnFamily(int cf); - - // @param pending True if the update may have started but is not yet - // guaranteed finished. This is useful for crash-recovery testing when the - // process may crash before updating the expected values array. - // - // Requires external locking covering `key` in `cf`. - void Put(int cf, int64_t key, uint32_t value_base, bool pending); - - // Requires external locking covering `key` in `cf`. - uint32_t Get(int cf, int64_t key) const; - - // @param pending See comment above Put() - // Returns true if the key was not yet deleted. - // - // Requires external locking covering `key` in `cf`. - bool Delete(int cf, int64_t key, bool pending); - - // @param pending See comment above Put() - // Returns true if the key was not yet deleted. - // - // Requires external locking covering `key` in `cf`. - bool SingleDelete(int cf, int64_t key, bool pending); - - // @param pending See comment above Put() - // Returns number of keys deleted by the call. - // - // Requires external locking covering keys in `[begin_key, end_key)` in `cf`. - int DeleteRange(int cf, int64_t begin_key, int64_t end_key, bool pending); - - // Requires external locking covering `key` in `cf`. - bool Exists(int cf, int64_t key); - - private: - // Requires external locking covering `key` in `cf`. - std::atomic& Value(int cf, int64_t key) const { - return values_[cf * max_key_ + key]; - } - - const size_t max_key_; - const size_t num_column_families_; - - protected: - size_t GetValuesLen() const { - return sizeof(std::atomic) * num_column_families_ * max_key_; - } - - // Requires external locking preventing concurrent execution with any other - // member function. - void Reset(); - - std::atomic* values_; -}; - -// A `FileExpectedState` implements `ExpectedState` backed by a file. -class FileExpectedState : public ExpectedState { - public: - explicit FileExpectedState(std::string expected_state_file_path, - size_t max_key, size_t num_column_families); - - // Requires external locking preventing concurrent execution with any other - // member function. - Status Open(bool create) override; - - private: - const std::string expected_state_file_path_; - std::unique_ptr expected_state_mmap_buffer_; -}; - -// An `AnonExpectedState` implements `ExpectedState` backed by a memory -// allocation. -class AnonExpectedState : public ExpectedState { - public: - explicit AnonExpectedState(size_t max_key, size_t num_column_families); - - // Requires external locking preventing concurrent execution with any other - // member function. - Status Open(bool create) override; - - private: - std::unique_ptr[]> values_allocation_; -}; - -// An `ExpectedStateManager` manages data about the expected state of the -// database. It exposes operations for reading and modifying the latest -// expected state. -class ExpectedStateManager { - public: - explicit ExpectedStateManager(size_t max_key, size_t num_column_families); - - virtual ~ExpectedStateManager(); - - // Requires external locking preventing concurrent execution with any other - // member function. - virtual Status Open() = 0; - - // Saves expected values for the current state of `db` and begins tracking - // changes. Following a successful `SaveAtAndAfter()`, `Restore()` can be - // called on the same DB, as long as its state does not roll back to before - // its current state. - // - // Requires external locking preventing concurrent execution with any other - // member function. Furthermore, `db` must not be mutated while this function - // is executing. - virtual Status SaveAtAndAfter(DB* db) = 0; - - // Returns true if at least one state of historical expected values can be - // restored. - // - // Requires external locking preventing concurrent execution with any other - // member function. - virtual bool HasHistory() = 0; - - // Restores expected values according to the current state of `db`. See - // `SaveAtAndAfter()` for conditions where this can be called. - // - // Requires external locking preventing concurrent execution with any other - // member function. Furthermore, `db` must not be mutated while this function - // is executing. - virtual Status Restore(DB* db) = 0; - - // Requires external locking covering all keys in `cf`. - void ClearColumnFamily(int cf) { return latest_->ClearColumnFamily(cf); } - - // @param pending True if the update may have started but is not yet - // guaranteed finished. This is useful for crash-recovery testing when the - // process may crash before updating the expected values array. - // - // Requires external locking covering `key` in `cf`. - void Put(int cf, int64_t key, uint32_t value_base, bool pending) { - return latest_->Put(cf, key, value_base, pending); - } - - // Requires external locking covering `key` in `cf`. - uint32_t Get(int cf, int64_t key) const { return latest_->Get(cf, key); } - - // @param pending See comment above Put() - // Returns true if the key was not yet deleted. - // - // Requires external locking covering `key` in `cf`. - bool Delete(int cf, int64_t key, bool pending) { - return latest_->Delete(cf, key, pending); - } - - // @param pending See comment above Put() - // Returns true if the key was not yet deleted. - // - // Requires external locking covering `key` in `cf`. - bool SingleDelete(int cf, int64_t key, bool pending) { - return latest_->SingleDelete(cf, key, pending); - } - - // @param pending See comment above Put() - // Returns number of keys deleted by the call. - // - // Requires external locking covering keys in `[begin_key, end_key)` in `cf`. - int DeleteRange(int cf, int64_t begin_key, int64_t end_key, bool pending) { - return latest_->DeleteRange(cf, begin_key, end_key, pending); - } - - // Requires external locking covering `key` in `cf`. - bool Exists(int cf, int64_t key) { return latest_->Exists(cf, key); } - - protected: - const size_t max_key_; - const size_t num_column_families_; - std::unique_ptr latest_; -}; - -// A `FileExpectedStateManager` implements an `ExpectedStateManager` backed by -// a directory of files containing data about the expected state of the -// database. -class FileExpectedStateManager : public ExpectedStateManager { - public: - explicit FileExpectedStateManager(size_t max_key, size_t num_column_families, - std::string expected_state_dir_path); - - // Requires external locking preventing concurrent execution with any other - // member function. - Status Open() override; - - // See `ExpectedStateManager::SaveAtAndAfter()` API doc. - // - // This implementation makes a copy of "LATEST.state" into - // ".state", and starts a trace in ".trace". - // Due to using external files, a following `Restore()` can happen even - // from a different process. - Status SaveAtAndAfter(DB* db) override; - - // See `ExpectedStateManager::HasHistory()` API doc. - bool HasHistory() override; - - // See `ExpectedStateManager::Restore()` API doc. - // - // Say `db->GetLatestSequenceNumber()` was `a` last time `SaveAtAndAfter()` - // was called and now it is `b`. Then this function replays `b - a` write - // operations from "`a`.trace" onto "`a`.state", and then copies the resulting - // file into "LATEST.state". - Status Restore(DB* db) override; - - private: - // Requires external locking preventing concurrent execution with any other - // member function. - Status Clean(); - - std::string GetTempPathForFilename(const std::string& filename); - std::string GetPathForFilename(const std::string& filename); - - static const std::string kLatestBasename; - static const std::string kStateFilenameSuffix; - static const std::string kTraceFilenameSuffix; - static const std::string kTempFilenamePrefix; - static const std::string kTempFilenameSuffix; - - const std::string expected_state_dir_path_; - SequenceNumber saved_seqno_ = kMaxSequenceNumber; -}; - -// An `AnonExpectedStateManager` implements an `ExpectedStateManager` backed by -// a memory allocation containing data about the expected state of the database. -class AnonExpectedStateManager : public ExpectedStateManager { - public: - explicit AnonExpectedStateManager(size_t max_key, size_t num_column_families); - - // See `ExpectedStateManager::SaveAtAndAfter()` API doc. - // - // This implementation returns `Status::NotSupported` since we do not - // currently have a need to keep history of expected state within a process. - Status SaveAtAndAfter(DB* /* db */) override { - return Status::NotSupported(); - } - - // See `ExpectedStateManager::HasHistory()` API doc. - bool HasHistory() override { return false; } - - // See `ExpectedStateManager::Restore()` API doc. - // - // This implementation returns `Status::NotSupported` since we do not - // currently have a need to keep history of expected state within a process. - Status Restore(DB* /* db */) override { return Status::NotSupported(); } - - // Requires external locking preventing concurrent execution with any other - // member function. - Status Open() override; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // GFLAGS diff --git a/db_stress_tool/multi_ops_txns_stress.cc b/db_stress_tool/multi_ops_txns_stress.cc deleted file mode 100644 index b543b0246..000000000 --- a/db_stress_tool/multi_ops_txns_stress.cc +++ /dev/null @@ -1,1753 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#include "db_stress_tool/multi_ops_txns_stress.h" - -#include "rocksdb/utilities/write_batch_with_index.h" -#include "util/defer.h" -#include "utilities/fault_injection_fs.h" -#include "utilities/transactions/write_prepared_txn_db.h" - -namespace ROCKSDB_NAMESPACE { - -// The description of A and C can be found in multi_ops_txns_stress.h -DEFINE_int32(lb_a, 0, "(Inclusive) lower bound of A"); -DEFINE_int32(ub_a, 1000, "(Exclusive) upper bound of A"); -DEFINE_int32(lb_c, 0, "(Inclusive) lower bound of C"); -DEFINE_int32(ub_c, 1000, "(Exclusive) upper bound of C"); - -DEFINE_string(key_spaces_path, "", - "Path to file describing the lower and upper bounds of A and C"); - -DEFINE_int32(delay_snapshot_read_one_in, 0, - "With a chance of 1/N, inject a random delay between taking " - "snapshot and read."); - -DEFINE_int32(rollback_one_in, 0, - "If non-zero, rollback non-read-only transactions with a " - "probability of 1/N."); - -DEFINE_int32(clear_wp_commit_cache_one_in, 0, - "If non-zero, evict all commit entries from commit cache with a " - "probability of 1/N. This options applies to write-prepared and " - "write-unprepared transactions."); - -extern "C" bool rocksdb_write_prepared_TEST_ShouldClearCommitCache(void) { - static Random rand(static_cast(db_stress_env->NowMicros())); - return FLAGS_clear_wp_commit_cache_one_in > 0 && - rand.OneIn(FLAGS_clear_wp_commit_cache_one_in); -} - -// MultiOpsTxnsStressTest can either operate on a database with pre-populated -// data (possibly from previous ones), or create a new db and preload it with -// data specified via `-lb_a`, `-ub_a`, `-lb_c`, `-ub_c`, etc. Among these, we -// define the test key spaces as two key ranges: [lb_a, ub_a) and [lb_c, ub_c). -// The key spaces specification is persisted in a file whose absolute path can -// be specified via `-key_spaces_path`. -// -// Whether an existing db is used or a new one is created, key_spaces_path will -// be used. In the former case, the test reads the key spaces specification -// from `-key_spaces_path` and decodes [lb_a, ub_a) and [lb_c, ub_c). In the -// latter case, the test writes a key spaces specification to a file at the -// location, and this file will be used by future runs until a new db is -// created. -// -// Create a fresh new database (-destroy_db_initially=1 or there is no database -// in the location specified by -db). See PreloadDb(). -// -// Use an existing, non-empty database. See ScanExistingDb(). -// -// This test is multi-threaded, and thread count can be specified via -// `-threads`. For simplicity, we partition the key ranges and each thread -// operates on a subrange independently. -// Within each subrange, a KeyGenerator object is responsible for key -// generation. A KeyGenerator maintains two sets: set of existing keys within -// [low, high), set of non-existing keys within [low, high). [low, high) is the -// subrange. The test initialization makes sure there is at least one -// non-existing key, otherwise the test will return an error and exit before -// any test thread is spawned. - -void MultiOpsTxnsStressTest::KeyGenerator::FinishInit() { - assert(existing_.empty()); - assert(!existing_uniq_.empty()); - assert(low_ < high_); - for (auto v : existing_uniq_) { - assert(low_ <= v); - assert(high_ > v); - existing_.push_back(v); - } - if (non_existing_uniq_.empty()) { - fprintf( - stderr, - "Cannot allocate key in [%u, %u)\nStart with a new DB or try change " - "the number of threads for testing via -threads=<#threads>\n", - static_cast(low_), static_cast(high_)); - fflush(stdout); - fflush(stderr); - assert(false); - } - initialized_ = true; -} - -std::pair -MultiOpsTxnsStressTest::KeyGenerator::ChooseExisting() { - assert(initialized_); - const size_t N = existing_.size(); - assert(N > 0); - uint32_t rnd = rand_.Uniform(static_cast(N)); - assert(rnd < N); - return std::make_pair(existing_[rnd], rnd); -} - -uint32_t MultiOpsTxnsStressTest::KeyGenerator::Allocate() { - assert(initialized_); - auto it = non_existing_uniq_.begin(); - assert(non_existing_uniq_.end() != it); - uint32_t ret = *it; - // Remove this element from non_existing_. - // Need to call UndoAllocation() if the calling transaction does not commit. - non_existing_uniq_.erase(it); - return ret; -} - -void MultiOpsTxnsStressTest::KeyGenerator::Replace(uint32_t old_val, - uint32_t old_pos, - uint32_t new_val) { - assert(initialized_); - { - auto it = existing_uniq_.find(old_val); - assert(it != existing_uniq_.end()); - existing_uniq_.erase(it); - } - - { - assert(0 == existing_uniq_.count(new_val)); - existing_uniq_.insert(new_val); - existing_[old_pos] = new_val; - } - - { - assert(0 == non_existing_uniq_.count(old_val)); - non_existing_uniq_.insert(old_val); - } -} - -void MultiOpsTxnsStressTest::KeyGenerator::UndoAllocation(uint32_t new_val) { - assert(initialized_); - assert(0 == non_existing_uniq_.count(new_val)); - non_existing_uniq_.insert(new_val); -} - -std::string MultiOpsTxnsStressTest::Record::EncodePrimaryKey(uint32_t a) { - std::string ret; - PutFixed32(&ret, kPrimaryIndexId); - PutFixed32(&ret, a); - - char* const buf = &ret[0]; - std::reverse(buf, buf + sizeof(kPrimaryIndexId)); - std::reverse(buf + sizeof(kPrimaryIndexId), - buf + sizeof(kPrimaryIndexId) + sizeof(a)); - return ret; -} - -std::string MultiOpsTxnsStressTest::Record::EncodeSecondaryKey(uint32_t c) { - std::string ret; - PutFixed32(&ret, kSecondaryIndexId); - PutFixed32(&ret, c); - - char* const buf = &ret[0]; - std::reverse(buf, buf + sizeof(kSecondaryIndexId)); - std::reverse(buf + sizeof(kSecondaryIndexId), - buf + sizeof(kSecondaryIndexId) + sizeof(c)); - return ret; -} - -std::string MultiOpsTxnsStressTest::Record::EncodeSecondaryKey(uint32_t c, - uint32_t a) { - std::string ret; - PutFixed32(&ret, kSecondaryIndexId); - PutFixed32(&ret, c); - PutFixed32(&ret, a); - - char* const buf = &ret[0]; - std::reverse(buf, buf + sizeof(kSecondaryIndexId)); - std::reverse(buf + sizeof(kSecondaryIndexId), - buf + sizeof(kSecondaryIndexId) + sizeof(c)); - std::reverse(buf + sizeof(kSecondaryIndexId) + sizeof(c), - buf + sizeof(kSecondaryIndexId) + sizeof(c) + sizeof(a)); - return ret; -} - -std::tuple -MultiOpsTxnsStressTest::Record::DecodePrimaryIndexValue( - Slice primary_index_value) { - if (primary_index_value.size() != 8) { - return std::tuple{Status::Corruption(""), 0, 0}; - } - uint32_t b = 0; - uint32_t c = 0; - if (!GetFixed32(&primary_index_value, &b) || - !GetFixed32(&primary_index_value, &c)) { - assert(false); - return std::tuple{Status::Corruption(""), 0, 0}; - } - return std::tuple{Status::OK(), b, c}; -} - -std::pair -MultiOpsTxnsStressTest::Record::DecodeSecondaryIndexValue( - Slice secondary_index_value) { - if (secondary_index_value.size() != 4) { - return std::make_pair(Status::Corruption(""), 0); - } - uint32_t crc = 0; - bool result __attribute__((unused)) = - GetFixed32(&secondary_index_value, &crc); - assert(result); - return std::make_pair(Status::OK(), crc); -} - -std::pair -MultiOpsTxnsStressTest::Record::EncodePrimaryIndexEntry() const { - std::string primary_index_key = EncodePrimaryKey(); - std::string primary_index_value = EncodePrimaryIndexValue(); - return std::make_pair(primary_index_key, primary_index_value); -} - -std::string MultiOpsTxnsStressTest::Record::EncodePrimaryKey() const { - return EncodePrimaryKey(a_); -} - -std::string MultiOpsTxnsStressTest::Record::EncodePrimaryIndexValue() const { - std::string ret; - PutFixed32(&ret, b_); - PutFixed32(&ret, c_); - return ret; -} - -std::pair -MultiOpsTxnsStressTest::Record::EncodeSecondaryIndexEntry() const { - std::string secondary_index_key = EncodeSecondaryKey(c_, a_); - - // Secondary index value is always 4-byte crc32 of the secondary key - std::string secondary_index_value; - uint32_t crc = - crc32c::Value(secondary_index_key.data(), secondary_index_key.size()); - PutFixed32(&secondary_index_value, crc); - return std::make_pair(std::move(secondary_index_key), secondary_index_value); -} - -std::string MultiOpsTxnsStressTest::Record::EncodeSecondaryKey() const { - return EncodeSecondaryKey(c_, a_); -} - -Status MultiOpsTxnsStressTest::Record::DecodePrimaryIndexEntry( - Slice primary_index_key, Slice primary_index_value) { - if (primary_index_key.size() != 8) { - assert(false); - return Status::Corruption("Primary index key length is not 8"); - } - - uint32_t index_id = 0; - - [[maybe_unused]] bool res = GetFixed32(&primary_index_key, &index_id); - assert(res); - index_id = EndianSwapValue(index_id); - - if (index_id != kPrimaryIndexId) { - std::ostringstream oss; - oss << "Unexpected primary index id: " << index_id; - return Status::Corruption(oss.str()); - } - - res = GetFixed32(&primary_index_key, &a_); - assert(res); - a_ = EndianSwapValue(a_); - assert(primary_index_key.empty()); - - if (primary_index_value.size() != 8) { - return Status::Corruption("Primary index value length is not 8"); - } - GetFixed32(&primary_index_value, &b_); - GetFixed32(&primary_index_value, &c_); - return Status::OK(); -} - -Status MultiOpsTxnsStressTest::Record::DecodeSecondaryIndexEntry( - Slice secondary_index_key, Slice secondary_index_value) { - if (secondary_index_key.size() != 12) { - return Status::Corruption("Secondary index key length is not 12"); - } - uint32_t crc = - crc32c::Value(secondary_index_key.data(), secondary_index_key.size()); - - uint32_t index_id = 0; - - [[maybe_unused]] bool res = GetFixed32(&secondary_index_key, &index_id); - assert(res); - index_id = EndianSwapValue(index_id); - - if (index_id != kSecondaryIndexId) { - std::ostringstream oss; - oss << "Unexpected secondary index id: " << index_id; - return Status::Corruption(oss.str()); - } - - assert(secondary_index_key.size() == 8); - res = GetFixed32(&secondary_index_key, &c_); - assert(res); - c_ = EndianSwapValue(c_); - - assert(secondary_index_key.size() == 4); - res = GetFixed32(&secondary_index_key, &a_); - assert(res); - a_ = EndianSwapValue(a_); - assert(secondary_index_key.empty()); - - if (secondary_index_value.size() != 4) { - return Status::Corruption("Secondary index value length is not 4"); - } - uint32_t val = 0; - GetFixed32(&secondary_index_value, &val); - if (val != crc) { - std::ostringstream oss; - oss << "Secondary index key checksum mismatch, stored: " << val - << ", recomputed: " << crc; - return Status::Corruption(oss.str()); - } - return Status::OK(); -} - -void MultiOpsTxnsStressTest::FinishInitDb(SharedState* shared) { - if (FLAGS_enable_compaction_filter) { - // TODO (yanqin) enable compaction filter - } - ProcessRecoveredPreparedTxns(shared); - - ReopenAndPreloadDbIfNeeded(shared); - // TODO (yanqin) parallelize if key space is large - for (auto& key_gen : key_gen_for_a_) { - assert(key_gen); - key_gen->FinishInit(); - } - // TODO (yanqin) parallelize if key space is large - for (auto& key_gen : key_gen_for_c_) { - assert(key_gen); - key_gen->FinishInit(); - } -} - -void MultiOpsTxnsStressTest::ReopenAndPreloadDbIfNeeded(SharedState* shared) { - (void)shared; - bool db_empty = false; - { - std::unique_ptr iter(db_->NewIterator(ReadOptions())); - iter->SeekToFirst(); - if (!iter->Valid()) { - db_empty = true; - } - } - - if (db_empty) { - PreloadDb(shared, FLAGS_threads, FLAGS_lb_a, FLAGS_ub_a, FLAGS_lb_c, - FLAGS_ub_c); - } else { - fprintf(stdout, - "Key ranges will be read from %s.\n-lb_a, -ub_a, -lb_c, -ub_c will " - "be ignored\n", - FLAGS_key_spaces_path.c_str()); - fflush(stdout); - ScanExistingDb(shared, FLAGS_threads); - } -} - -// Used for point-lookup transaction -Status MultiOpsTxnsStressTest::TestGet( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& /*rand_column_families*/, - const std::vector& /*rand_keys*/) { - uint32_t a = 0; - uint32_t pos = 0; - std::tie(a, pos) = ChooseExistingA(thread); - return PointLookupTxn(thread, read_opts, a); -} - -// Not used. -std::vector MultiOpsTxnsStressTest::TestMultiGet( - ThreadState* /*thread*/, const ReadOptions& /*read_opts*/, - const std::vector& /*rand_column_families*/, - const std::vector& /*rand_keys*/) { - return std::vector{Status::NotSupported()}; -} - -// Wide columns are currently not supported by transactions. -void MultiOpsTxnsStressTest::TestGetEntity( - ThreadState* /* thread */, const ReadOptions& /* read_opts */, - const std::vector& /* rand_column_families */, - const std::vector& /* rand_keys */) {} - -Status MultiOpsTxnsStressTest::TestPrefixScan( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) { - (void)thread; - (void)read_opts; - (void)rand_column_families; - (void)rand_keys; - return Status::OK(); -} - -// Given a key K, this creates an iterator which scans to K and then -// does a random sequence of Next/Prev operations. -Status MultiOpsTxnsStressTest::TestIterate( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& /*rand_column_families*/, - const std::vector& /*rand_keys*/) { - uint32_t c = 0; - uint32_t pos = 0; - std::tie(c, pos) = ChooseExistingC(thread); - return RangeScanTxn(thread, read_opts, c); -} - -// Not intended for use. -Status MultiOpsTxnsStressTest::TestPut(ThreadState* /*thread*/, - WriteOptions& /*write_opts*/, - const ReadOptions& /*read_opts*/, - const std::vector& /*cf_ids*/, - const std::vector& /*keys*/, - char (&value)[100]) { - (void)value; - return Status::NotSupported(); -} - -// Not intended for use. -Status MultiOpsTxnsStressTest::TestDelete( - ThreadState* /*thread*/, WriteOptions& /*write_opts*/, - const std::vector& /*rand_column_families*/, - const std::vector& /*rand_keys*/) { - return Status::NotSupported(); -} - -// Not intended for use. -Status MultiOpsTxnsStressTest::TestDeleteRange( - ThreadState* /*thread*/, WriteOptions& /*write_opts*/, - const std::vector& /*rand_column_families*/, - const std::vector& /*rand_keys*/) { - return Status::NotSupported(); -} - -void MultiOpsTxnsStressTest::TestIngestExternalFile( - ThreadState* thread, const std::vector& rand_column_families, - const std::vector& /*rand_keys*/) { - // TODO (yanqin) - (void)thread; - (void)rand_column_families; -} - -void MultiOpsTxnsStressTest::TestCompactRange( - ThreadState* thread, int64_t /*rand_key*/, const Slice& /*start_key*/, - ColumnFamilyHandle* column_family) { - // TODO (yanqin). - // May use GetRangeHash() for validation before and after DB::CompactRange() - // completes. - (void)thread; - (void)column_family; -} - -Status MultiOpsTxnsStressTest::TestBackupRestore( - ThreadState* thread, const std::vector& rand_column_families, - const std::vector& /*rand_keys*/) { - // TODO (yanqin) - (void)thread; - (void)rand_column_families; - return Status::OK(); -} - -Status MultiOpsTxnsStressTest::TestCheckpoint( - ThreadState* thread, const std::vector& rand_column_families, - const std::vector& /*rand_keys*/) { - // TODO (yanqin) - (void)thread; - (void)rand_column_families; - return Status::OK(); -} - -Status MultiOpsTxnsStressTest::TestApproximateSize( - ThreadState* thread, uint64_t iteration, - const std::vector& rand_column_families, - const std::vector& /*rand_keys*/) { - // TODO (yanqin) - (void)thread; - (void)iteration; - (void)rand_column_families; - return Status::OK(); -} - -Status MultiOpsTxnsStressTest::TestCustomOperations( - ThreadState* thread, const std::vector& rand_column_families) { - (void)rand_column_families; - // Randomly choose from 0, 1, and 2. - // TODO (yanqin) allow user to configure probability of each operation. - uint32_t rand = thread->rand.Uniform(3); - Status s; - if (0 == rand) { - // Update primary key. - uint32_t old_a = 0; - uint32_t pos = 0; - std::tie(old_a, pos) = ChooseExistingA(thread); - uint32_t new_a = GenerateNextA(thread); - s = PrimaryKeyUpdateTxn(thread, old_a, pos, new_a); - } else if (1 == rand) { - // Update secondary key. - uint32_t old_c = 0; - uint32_t pos = 0; - std::tie(old_c, pos) = ChooseExistingC(thread); - uint32_t new_c = GenerateNextC(thread); - s = SecondaryKeyUpdateTxn(thread, old_c, pos, new_c); - } else if (2 == rand) { - // Update primary index value. - uint32_t a = 0; - uint32_t pos = 0; - std::tie(a, pos) = ChooseExistingA(thread); - s = UpdatePrimaryIndexValueTxn(thread, a, /*b_delta=*/1); - } else { - // Should never reach here. - assert(false); - } - - return s; -} - -void MultiOpsTxnsStressTest::RegisterAdditionalListeners() { - options_.listeners.emplace_back(new MultiOpsTxnsStressListener(this)); -} - -void MultiOpsTxnsStressTest::PrepareTxnDbOptions( - SharedState* /*shared*/, TransactionDBOptions& txn_db_opts) { - // MultiOpsTxnStressTest uses SingleDelete to delete secondary keys, thus we - // register this callback to let TxnDb know that when rolling back - // a transaction, use only SingleDelete to cancel prior Put from the same - // transaction if applicable. - txn_db_opts.rollback_deletion_type_callback = - [](TransactionDB* /*db*/, ColumnFamilyHandle* /*column_family*/, - const Slice& key) { - Slice ks = key; - uint32_t index_id = 0; - [[maybe_unused]] bool res = GetFixed32(&ks, &index_id); - assert(res); - index_id = EndianSwapValue(index_id); - assert(index_id <= Record::kSecondaryIndexId); - return index_id == Record::kSecondaryIndexId; - }; -} - -Status MultiOpsTxnsStressTest::PrimaryKeyUpdateTxn(ThreadState* thread, - uint32_t old_a, - uint32_t old_a_pos, - uint32_t new_a) { - std::string old_pk = Record::EncodePrimaryKey(old_a); - std::string new_pk = Record::EncodePrimaryKey(new_a); - Transaction* txn = nullptr; - WriteOptions wopts; - Status s = NewTxn(wopts, &txn); - if (!s.ok()) { - assert(!txn); - thread->stats.AddErrors(1); - return s; - } - - assert(txn); - txn->SetSnapshotOnNextOperation(/*notifier=*/nullptr); - - const Defer cleanup([new_a, &s, thread, txn, this]() { - if (s.ok()) { - // Two gets, one for existing pk, one for locking potential new pk. - thread->stats.AddGets(/*ngets=*/2, /*nfounds=*/1); - thread->stats.AddDeletes(1); - thread->stats.AddBytesForWrites( - /*nwrites=*/2, - Record::kPrimaryIndexEntrySize + Record::kSecondaryIndexEntrySize); - thread->stats.AddSingleDeletes(1); - return; - } - if (s.IsNotFound()) { - thread->stats.AddGets(/*ngets=*/1, /*nfounds=*/0); - } else if (s.IsBusy() || s.IsIncomplete()) { - // ignore. - // Incomplete also means rollback by application. See the transaction - // implementations. - } else { - thread->stats.AddErrors(1); - } - auto& key_gen = key_gen_for_a_[thread->tid]; - key_gen->UndoAllocation(new_a); - RollbackTxn(txn).PermitUncheckedError(); - }); - - ReadOptions ropts; - ropts.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - std::string value; - s = txn->GetForUpdate(ropts, old_pk, &value); - if (!s.ok()) { - return s; - } - std::string empty_value; - s = txn->GetForUpdate(ropts, new_pk, &empty_value); - if (s.ok()) { - assert(!empty_value.empty()); - s = Status::Busy(); - return s; - } else if (!s.IsNotFound()) { - return s; - } - - auto result = Record::DecodePrimaryIndexValue(value); - s = std::get<0>(result); - if (!s.ok()) { - return s; - } - uint32_t b = std::get<1>(result); - uint32_t c = std::get<2>(result); - - ColumnFamilyHandle* cf = db_->DefaultColumnFamily(); - s = txn->Delete(cf, old_pk, /*assume_tracked=*/true); - if (!s.ok()) { - return s; - } - s = txn->Put(cf, new_pk, value, /*assume_tracked=*/true); - if (!s.ok()) { - return s; - } - - auto* wb = txn->GetWriteBatch(); - assert(wb); - - std::string old_sk = Record::EncodeSecondaryKey(c, old_a); - s = wb->SingleDelete(old_sk); - if (!s.ok()) { - return s; - } - - Record record(new_a, b, c); - std::string new_sk; - std::string new_crc; - std::tie(new_sk, new_crc) = record.EncodeSecondaryIndexEntry(); - s = wb->Put(new_sk, new_crc); - if (!s.ok()) { - return s; - } - - s = txn->Prepare(); - - if (!s.ok()) { - return s; - } - - if (FLAGS_rollback_one_in > 0 && thread->rand.OneIn(FLAGS_rollback_one_in)) { - s = Status::Incomplete(); - return s; - } - - s = WriteToCommitTimeWriteBatch(*txn); - if (!s.ok()) { - return s; - } - - s = CommitAndCreateTimestampedSnapshotIfNeeded(thread, *txn); - - auto& key_gen = key_gen_for_a_.at(thread->tid); - if (s.ok()) { - delete txn; - key_gen->Replace(old_a, old_a_pos, new_a); - } - return s; -} - -Status MultiOpsTxnsStressTest::SecondaryKeyUpdateTxn(ThreadState* thread, - uint32_t old_c, - uint32_t old_c_pos, - uint32_t new_c) { - Transaction* txn = nullptr; - WriteOptions wopts; - Status s = NewTxn(wopts, &txn); - if (!s.ok()) { - assert(!txn); - thread->stats.AddErrors(1); - return s; - } - - assert(txn); - - Iterator* it = nullptr; - long iterations = 0; - const Defer cleanup([new_c, &s, thread, &it, txn, this, &iterations]() { - delete it; - if (s.ok()) { - thread->stats.AddIterations(iterations); - thread->stats.AddGets(/*ngets=*/1, /*nfounds=*/1); - thread->stats.AddSingleDeletes(1); - thread->stats.AddBytesForWrites( - /*nwrites=*/2, - Record::kPrimaryIndexEntrySize + Record::kSecondaryIndexEntrySize); - return; - } else if (s.IsBusy() || s.IsTimedOut() || s.IsTryAgain() || - s.IsMergeInProgress() || s.IsIncomplete()) { - // ww-conflict detected, or - // lock cannot be acquired, or - // memtable history is not large enough for conflict checking, or - // Merge operation cannot be resolved, or - // application rollback. - // TODO (yanqin) add stats for other cases? - } else if (s.IsNotFound()) { - // ignore. - } else { - thread->stats.AddErrors(1); - } - auto& key_gen = key_gen_for_c_[thread->tid]; - key_gen->UndoAllocation(new_c); - RollbackTxn(txn).PermitUncheckedError(); - }); - - // TODO (yanqin) try SetSnapshotOnNextOperation(). We currently need to take - // a snapshot here because we will later verify that point lookup in the - // primary index using GetForUpdate() returns the same value for 'c' as the - // iterator. The iterator does not need a snapshot though, because it will be - // assigned the current latest (published) sequence in the db, which will be - // no smaller than the snapshot created here. The GetForUpdate will perform - // ww conflict checking to ensure GetForUpdate() (using the snapshot) sees - // the same data as this iterator. - txn->SetSnapshot(); - std::string old_sk_prefix = Record::EncodeSecondaryKey(old_c); - std::string iter_ub_str = Record::EncodeSecondaryKey(old_c + 1); - Slice iter_ub = iter_ub_str; - ReadOptions ropts; - ropts.snapshot = txn->GetSnapshot(); - ropts.total_order_seek = true; - ropts.iterate_upper_bound = &iter_ub; - ropts.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - it = txn->GetIterator(ropts); - - assert(it); - it->Seek(old_sk_prefix); - if (!it->Valid()) { - s = Status::NotFound(); - return s; - } - auto* wb = txn->GetWriteBatch(); - assert(wb); - - do { - ++iterations; - Record record; - s = record.DecodeSecondaryIndexEntry(it->key(), it->value()); - if (!s.ok()) { - fprintf(stderr, "Cannot decode secondary key (%s => %s): %s\n", - it->key().ToString(true).c_str(), - it->value().ToString(true).c_str(), s.ToString().c_str()); - assert(false); - break; - } - // At this point, record.b is not known yet, thus we need to access - // primary index. - std::string pk = Record::EncodePrimaryKey(record.a_value()); - std::string value; - ReadOptions read_opts; - read_opts.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - read_opts.snapshot = txn->GetSnapshot(); - s = txn->GetForUpdate(read_opts, pk, &value); - if (s.IsBusy() || s.IsTimedOut() || s.IsTryAgain() || - s.IsMergeInProgress()) { - // Write conflict, or cannot acquire lock, or memtable size is not large - // enough, or merge cannot be resolved. - break; - } else if (s.IsNotFound()) { - // We can also fail verification here. - std::ostringstream oss; - auto* dbimpl = static_cast_with_check(db_->GetRootDB()); - assert(dbimpl); - oss << "snap " << read_opts.snapshot->GetSequenceNumber() - << " (published " << dbimpl->GetLastPublishedSequence() - << "), pk should exist: " << Slice(pk).ToString(true); - fprintf(stderr, "%s\n", oss.str().c_str()); - assert(false); - break; - } - if (!s.ok()) { - std::ostringstream oss; - auto* dbimpl = static_cast_with_check(db_->GetRootDB()); - assert(dbimpl); - oss << "snap " << read_opts.snapshot->GetSequenceNumber() - << " (published " << dbimpl->GetLastPublishedSequence() << "), " - << s.ToString(); - fprintf(stderr, "%s\n", oss.str().c_str()); - assert(false); - break; - } - auto result = Record::DecodePrimaryIndexValue(value); - s = std::get<0>(result); - if (!s.ok()) { - fprintf(stderr, "Cannot decode primary index value %s: %s\n", - Slice(value).ToString(true).c_str(), s.ToString().c_str()); - assert(false); - break; - } - uint32_t b = std::get<1>(result); - uint32_t c = std::get<2>(result); - if (c != old_c) { - std::ostringstream oss; - auto* dbimpl = static_cast_with_check(db_->GetRootDB()); - assert(dbimpl); - oss << "snap " << read_opts.snapshot->GetSequenceNumber() - << " (published " << dbimpl->GetLastPublishedSequence() - << "), pk/sk mismatch. pk: (a=" << record.a_value() << ", " - << "c=" << c << "), sk: (c=" << old_c << ")"; - s = Status::Corruption(); - fprintf(stderr, "%s\n", oss.str().c_str()); - assert(false); - break; - } - Record new_rec(record.a_value(), b, new_c); - std::string new_primary_index_value = new_rec.EncodePrimaryIndexValue(); - ColumnFamilyHandle* cf = db_->DefaultColumnFamily(); - s = txn->Put(cf, pk, new_primary_index_value, /*assume_tracked=*/true); - if (!s.ok()) { - break; - } - std::string old_sk = it->key().ToString(/*hex=*/false); - std::string new_sk; - std::string new_crc; - std::tie(new_sk, new_crc) = new_rec.EncodeSecondaryIndexEntry(); - s = wb->SingleDelete(old_sk); - if (!s.ok()) { - break; - } - s = wb->Put(new_sk, new_crc); - if (!s.ok()) { - break; - } - - it->Next(); - } while (it->Valid()); - - if (!s.ok()) { - return s; - } - - s = txn->Prepare(); - - if (!s.ok()) { - return s; - } - - if (FLAGS_rollback_one_in > 0 && thread->rand.OneIn(FLAGS_rollback_one_in)) { - s = Status::Incomplete(); - return s; - } - - s = WriteToCommitTimeWriteBatch(*txn); - if (!s.ok()) { - return s; - } - - s = CommitAndCreateTimestampedSnapshotIfNeeded(thread, *txn); - - if (s.ok()) { - delete txn; - auto& key_gen = key_gen_for_c_.at(thread->tid); - key_gen->Replace(old_c, old_c_pos, new_c); - } - - return s; -} - -Status MultiOpsTxnsStressTest::UpdatePrimaryIndexValueTxn(ThreadState* thread, - uint32_t a, - uint32_t b_delta) { - std::string pk_str = Record::EncodePrimaryKey(a); - Transaction* txn = nullptr; - WriteOptions wopts; - Status s = NewTxn(wopts, &txn); - if (!s.ok()) { - assert(!txn); - thread->stats.AddErrors(1); - return s; - } - - assert(txn); - - const Defer cleanup([&s, thread, txn, this]() { - if (s.ok()) { - thread->stats.AddGets(/*ngets=*/1, /*nfounds=*/1); - thread->stats.AddBytesForWrites( - /*nwrites=*/1, /*nbytes=*/Record::kPrimaryIndexEntrySize); - return; - } - if (s.IsNotFound()) { - thread->stats.AddGets(/*ngets=*/1, /*nfounds=*/0); - } else if (s.IsInvalidArgument()) { - // ignored. - } else if (s.IsBusy() || s.IsTimedOut() || s.IsTryAgain() || - s.IsMergeInProgress() || s.IsIncomplete()) { - // ignored. - } else { - thread->stats.AddErrors(1); - } - RollbackTxn(txn).PermitUncheckedError(); - }); - ReadOptions ropts; - ropts.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - std::string value; - s = txn->GetForUpdate(ropts, pk_str, &value); - if (!s.ok()) { - return s; - } - auto result = Record::DecodePrimaryIndexValue(value); - if (!std::get<0>(result).ok()) { - s = std::get<0>(result); - fprintf(stderr, "Cannot decode primary index value %s: %s\n", - Slice(value).ToString(true).c_str(), s.ToString().c_str()); - assert(false); - return s; - } - uint32_t b = std::get<1>(result) + b_delta; - uint32_t c = std::get<2>(result); - Record record(a, b, c); - std::string primary_index_value = record.EncodePrimaryIndexValue(); - ColumnFamilyHandle* cf = db_->DefaultColumnFamily(); - s = txn->Put(cf, pk_str, primary_index_value, /*assume_tracked=*/true); - if (!s.ok()) { - return s; - } - s = txn->Prepare(); - if (!s.ok()) { - return s; - } - - if (FLAGS_rollback_one_in > 0 && thread->rand.OneIn(FLAGS_rollback_one_in)) { - s = Status::Incomplete(); - return s; - } - - s = WriteToCommitTimeWriteBatch(*txn); - if (!s.ok()) { - return s; - } - - s = CommitAndCreateTimestampedSnapshotIfNeeded(thread, *txn); - - if (s.ok()) { - delete txn; - } - return s; -} - -Status MultiOpsTxnsStressTest::PointLookupTxn(ThreadState* thread, - ReadOptions ropts, uint32_t a) { - std::string pk_str = Record::EncodePrimaryKey(a); - // pk may or may not exist - PinnableSlice value; - - Transaction* txn = nullptr; - WriteOptions wopts; - Status s = NewTxn(wopts, &txn); - if (!s.ok()) { - assert(!txn); - thread->stats.AddErrors(1); - return s; - } - - assert(txn); - - const Defer cleanup([&s, thread, txn, this]() { - if (s.ok()) { - thread->stats.AddGets(/*ngets=*/1, /*nfounds=*/1); - return; - } else if (s.IsNotFound()) { - thread->stats.AddGets(/*ngets=*/1, /*nfounds=*/0); - } else { - thread->stats.AddErrors(1); - } - RollbackTxn(txn).PermitUncheckedError(); - }); - - std::shared_ptr snapshot; - SetupSnapshot(thread, ropts, *txn, snapshot); - - if (FLAGS_delay_snapshot_read_one_in > 0 && - thread->rand.OneIn(FLAGS_delay_snapshot_read_one_in)) { - uint64_t delay_ms = thread->rand.Uniform(100) + 1; - db_->GetDBOptions().env->SleepForMicroseconds( - static_cast(delay_ms * 1000)); - } - - s = txn->Get(ropts, db_->DefaultColumnFamily(), pk_str, &value); - if (s.ok()) { - s = txn->Commit(); - } - if (s.ok()) { - delete txn; - } - return s; -} - -Status MultiOpsTxnsStressTest::RangeScanTxn(ThreadState* thread, - ReadOptions ropts, uint32_t c) { - std::string sk = Record::EncodeSecondaryKey(c); - - Transaction* txn = nullptr; - WriteOptions wopts; - Status s = NewTxn(wopts, &txn); - if (!s.ok()) { - assert(!txn); - thread->stats.AddErrors(1); - return s; - } - - assert(txn); - - const Defer cleanup([&s, thread, txn, this]() { - if (s.ok()) { - thread->stats.AddIterations(1); - return; - } - thread->stats.AddErrors(1); - RollbackTxn(txn).PermitUncheckedError(); - }); - - std::shared_ptr snapshot; - SetupSnapshot(thread, ropts, *txn, snapshot); - - if (FLAGS_delay_snapshot_read_one_in > 0 && - thread->rand.OneIn(FLAGS_delay_snapshot_read_one_in)) { - uint64_t delay_ms = thread->rand.Uniform(100) + 1; - db_->GetDBOptions().env->SleepForMicroseconds( - static_cast(delay_ms * 1000)); - } - - std::unique_ptr iter(txn->GetIterator(ropts)); - - constexpr size_t total_nexts = 10; - size_t nexts = 0; - for (iter->Seek(sk); - iter->Valid() && nexts < total_nexts && iter->status().ok(); - iter->Next(), ++nexts) { - } - - if (iter->status().ok()) { - s = txn->Commit(); - } else { - s = iter->status(); - } - - if (s.ok()) { - delete txn; - } - - return s; -} - -void MultiOpsTxnsStressTest::VerifyDb(ThreadState* thread) const { - if (thread->shared->HasVerificationFailedYet()) { - return; - } - const Snapshot* const snapshot = db_->GetSnapshot(); - assert(snapshot); - ManagedSnapshot snapshot_guard(db_, snapshot); - - std::ostringstream oss; - oss << "[snap=" << snapshot->GetSequenceNumber() << ","; - - auto* dbimpl = static_cast_with_check(db_->GetRootDB()); - assert(dbimpl); - - oss << " last_published=" << dbimpl->GetLastPublishedSequence() << "] "; - - if (FLAGS_delay_snapshot_read_one_in > 0 && - thread->rand.OneIn(FLAGS_delay_snapshot_read_one_in)) { - uint64_t delay_ms = thread->rand.Uniform(100) + 1; - db_->GetDBOptions().env->SleepForMicroseconds( - static_cast(delay_ms * 1000)); - } - - // TODO (yanqin) with a probability, we can use either forward or backward - // iterator in subsequent checks. We can also use more advanced features in - // range scan. For now, let's just use simple forward iteration with - // total_order_seek = true. - - // First, iterate primary index. - size_t primary_index_entries_count = 0; - { - std::string iter_ub_str; - PutFixed32(&iter_ub_str, Record::kPrimaryIndexId + 1); - std::reverse(iter_ub_str.begin(), iter_ub_str.end()); - Slice iter_ub = iter_ub_str; - - std::string start_key; - PutFixed32(&start_key, Record::kPrimaryIndexId); - std::reverse(start_key.begin(), start_key.end()); - - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ropts; - ropts.snapshot = snapshot; - ropts.total_order_seek = true; - ropts.iterate_upper_bound = &iter_ub; - - std::unique_ptr it(db_->NewIterator(ropts)); - for (it->Seek(start_key); it->Valid(); it->Next()) { - Record record; - Status s = record.DecodePrimaryIndexEntry(it->key(), it->value()); - if (!s.ok()) { - oss << "Cannot decode primary index entry " << it->key().ToString(true) - << "=>" << it->value().ToString(true); - VerificationAbort(thread->shared, oss.str(), s); - assert(false); - return; - } - ++primary_index_entries_count; - - // Search secondary index. - uint32_t a = record.a_value(); - uint32_t c = record.c_value(); - char sk_buf[12]; - EncodeFixed32(sk_buf, Record::kSecondaryIndexId); - std::reverse(sk_buf, sk_buf + sizeof(uint32_t)); - EncodeFixed32(sk_buf + sizeof(uint32_t), c); - std::reverse(sk_buf + sizeof(uint32_t), sk_buf + 2 * sizeof(uint32_t)); - EncodeFixed32(sk_buf + 2 * sizeof(uint32_t), a); - std::reverse(sk_buf + 2 * sizeof(uint32_t), sk_buf + sizeof(sk_buf)); - Slice sk(sk_buf, sizeof(sk_buf)); - std::string value; - s = db_->Get(ropts, sk, &value); - if (!s.ok()) { - oss << "Cannot find secondary index entry " << sk.ToString(true); - VerificationAbort(thread->shared, oss.str(), s); - assert(false); - return; - } - } - } - - // Second, iterate secondary index. - size_t secondary_index_entries_count = 0; - { - std::string start_key; - PutFixed32(&start_key, Record::kSecondaryIndexId); - std::reverse(start_key.begin(), start_key.end()); - - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ropts; - ropts.snapshot = snapshot; - ropts.total_order_seek = true; - - std::unique_ptr it(db_->NewIterator(ropts)); - for (it->Seek(start_key); it->Valid(); it->Next()) { - ++secondary_index_entries_count; - Record record; - Status s = record.DecodeSecondaryIndexEntry(it->key(), it->value()); - if (!s.ok()) { - oss << "Cannot decode secondary index entry " - << it->key().ToString(true) << "=>" << it->value().ToString(true); - VerificationAbort(thread->shared, oss.str(), s); - assert(false); - return; - } - // After decoding secondary index entry, we know a and c. Crc is verified - // in decoding phase. - // - // Form a primary key and search in the primary index. - std::string pk = Record::EncodePrimaryKey(record.a_value()); - std::string value; - s = db_->Get(ropts, pk, &value); - if (!s.ok()) { - oss << "Error searching pk " << Slice(pk).ToString(true) << ". " - << s.ToString() << ". sk " << it->key().ToString(true); - VerificationAbort(thread->shared, oss.str(), s); - assert(false); - return; - } - auto result = Record::DecodePrimaryIndexValue(value); - s = std::get<0>(result); - if (!s.ok()) { - oss << "Error decoding primary index value " - << Slice(value).ToString(true) << ". " << s.ToString(); - VerificationAbort(thread->shared, oss.str(), s); - assert(false); - return; - } - uint32_t c_in_primary = std::get<2>(result); - if (c_in_primary != record.c_value()) { - oss << "Pk/sk mismatch. pk: " << Slice(pk).ToString(true) << "=>" - << Slice(value).ToString(true) << " (a=" << record.a_value() - << ", c=" << c_in_primary << "), sk: " << it->key().ToString(true) - << " (c=" << record.c_value() << ")"; - VerificationAbort(thread->shared, oss.str(), s); - assert(false); - return; - } - } - } - - if (secondary_index_entries_count != primary_index_entries_count) { - oss << "Pk/sk mismatch: primary index has " << primary_index_entries_count - << " entries. Secondary index has " << secondary_index_entries_count - << " entries."; - VerificationAbort(thread->shared, oss.str(), Status::OK()); - assert(false); - return; - } -} - -// VerifyPkSkFast() can be called by MultiOpsTxnsStressListener's callbacks -// which can be called before TransactionDB::Open() returns to caller. -// Therefore, at that time, db_ and txn_db_ may still be nullptr. -// Caller has to make sure that the race condition does not happen. -void MultiOpsTxnsStressTest::VerifyPkSkFast(int job_id) { - DB* const db = db_aptr_.load(std::memory_order_acquire); - if (db == nullptr) { - return; - } - - assert(db_ == db); - assert(db_ != nullptr); - - const Snapshot* const snapshot = db_->GetSnapshot(); - assert(snapshot); - ManagedSnapshot snapshot_guard(db_, snapshot); - - std::ostringstream oss; - auto* dbimpl = static_cast_with_check(db_->GetRootDB()); - assert(dbimpl); - - oss << "Job " << job_id << ": [" << snapshot->GetSequenceNumber() << "," - << dbimpl->GetLastPublishedSequence() << "] "; - - std::string start_key; - PutFixed32(&start_key, Record::kSecondaryIndexId); - std::reverse(start_key.begin(), start_key.end()); - - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions ropts; - ropts.snapshot = snapshot; - ropts.total_order_seek = true; - - std::unique_ptr it(db_->NewIterator(ropts)); - for (it->Seek(start_key); it->Valid(); it->Next()) { - Record record; - Status s = record.DecodeSecondaryIndexEntry(it->key(), it->value()); - if (!s.ok()) { - oss << "Cannot decode secondary index entry " << it->key().ToString(true) - << "=>" << it->value().ToString(true); - fprintf(stderr, "%s\n", oss.str().c_str()); - fflush(stderr); - assert(false); - } - // After decoding secondary index entry, we know a and c. Crc is verified - // in decoding phase. - // - // Form a primary key and search in the primary index. - std::string pk = Record::EncodePrimaryKey(record.a_value()); - std::string value; - s = db_->Get(ropts, pk, &value); - if (!s.ok()) { - oss << "Error searching pk " << Slice(pk).ToString(true) << ". " - << s.ToString() << ". sk " << it->key().ToString(true); - fprintf(stderr, "%s\n", oss.str().c_str()); - fflush(stderr); - assert(false); - } - auto result = Record::DecodePrimaryIndexValue(value); - s = std::get<0>(result); - if (!s.ok()) { - oss << "Error decoding primary index value " - << Slice(value).ToString(true) << ". " << s.ToString(); - fprintf(stderr, "%s\n", oss.str().c_str()); - fflush(stderr); - assert(false); - } - uint32_t c_in_primary = std::get<2>(result); - if (c_in_primary != record.c_value()) { - oss << "Pk/sk mismatch. pk: " << Slice(pk).ToString(true) << "=>" - << Slice(value).ToString(true) << " (a=" << record.a_value() - << ", c=" << c_in_primary << "), sk: " << it->key().ToString(true) - << " (c=" << record.c_value() << ")"; - fprintf(stderr, "%s\n", oss.str().c_str()); - fflush(stderr); - assert(false); - } - } -} - -std::pair MultiOpsTxnsStressTest::ChooseExistingA( - ThreadState* thread) { - uint32_t tid = thread->tid; - auto& key_gen = key_gen_for_a_.at(tid); - return key_gen->ChooseExisting(); -} - -uint32_t MultiOpsTxnsStressTest::GenerateNextA(ThreadState* thread) { - uint32_t tid = thread->tid; - auto& key_gen = key_gen_for_a_.at(tid); - return key_gen->Allocate(); -} - -std::pair MultiOpsTxnsStressTest::ChooseExistingC( - ThreadState* thread) { - uint32_t tid = thread->tid; - auto& key_gen = key_gen_for_c_.at(tid); - return key_gen->ChooseExisting(); -} - -uint32_t MultiOpsTxnsStressTest::GenerateNextC(ThreadState* thread) { - uint32_t tid = thread->tid; - auto& key_gen = key_gen_for_c_.at(tid); - return key_gen->Allocate(); -} - -void MultiOpsTxnsStressTest::ProcessRecoveredPreparedTxnsHelper( - Transaction* txn, SharedState*) { - thread_local Random rand(static_cast(FLAGS_seed)); - if (rand.OneIn(2)) { - Status s = txn->Commit(); - assert(s.ok()); - } else { - Status s = txn->Rollback(); - assert(s.ok()); - } -} - -Status MultiOpsTxnsStressTest::WriteToCommitTimeWriteBatch(Transaction& txn) { - WriteBatch* ctwb = txn.GetCommitTimeWriteBatch(); - assert(ctwb); - // Do not change the content in key_buf. - static constexpr char key_buf[sizeof(Record::kMetadataPrefix) + 4] = { - '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\xff'}; - - uint64_t counter_val = counter_.Next(); - char val_buf[sizeof(counter_val)]; - EncodeFixed64(val_buf, counter_val); - return ctwb->Put(Slice(key_buf, sizeof(key_buf)), - Slice(val_buf, sizeof(val_buf))); -} - -Status MultiOpsTxnsStressTest::CommitAndCreateTimestampedSnapshotIfNeeded( - ThreadState* thread, Transaction& txn) { - Status s; - if (FLAGS_create_timestamped_snapshot_one_in > 0 && - thread->rand.OneInOpt(FLAGS_create_timestamped_snapshot_one_in)) { - uint64_t ts = db_stress_env->NowNanos(); - std::shared_ptr snapshot; - s = txn.CommitAndTryCreateSnapshot(/*notifier=*/nullptr, ts, &snapshot); - } else { - s = txn.Commit(); - } - assert(txn_db_); - if (FLAGS_create_timestamped_snapshot_one_in > 0 && - thread->rand.OneInOpt(50000)) { - uint64_t now = db_stress_env->NowNanos(); - constexpr uint64_t time_diff = static_cast(1000) * 1000 * 1000; - txn_db_->ReleaseTimestampedSnapshotsOlderThan(now - time_diff); - } - return s; -} - -void MultiOpsTxnsStressTest::SetupSnapshot( - ThreadState* thread, ReadOptions& read_opts, Transaction& txn, - std::shared_ptr& snapshot) { - if (thread->rand.OneInOpt(2)) { - snapshot = txn_db_->GetLatestTimestampedSnapshot(); - } - - if (snapshot) { - read_opts.snapshot = snapshot.get(); - } else { - txn.SetSnapshot(); - read_opts.snapshot = txn.GetSnapshot(); - } -} - -std::string MultiOpsTxnsStressTest::KeySpaces::EncodeTo() const { - std::string result; - PutFixed32(&result, lb_a); - PutFixed32(&result, ub_a); - PutFixed32(&result, lb_c); - PutFixed32(&result, ub_c); - return result; -} - -bool MultiOpsTxnsStressTest::KeySpaces::DecodeFrom(Slice data) { - if (!GetFixed32(&data, &lb_a) || !GetFixed32(&data, &ub_a) || - !GetFixed32(&data, &lb_c) || !GetFixed32(&data, &ub_c)) { - return false; - } - return true; -} - -void MultiOpsTxnsStressTest::PersistKeySpacesDesc( - const std::string& key_spaces_path, uint32_t lb_a, uint32_t ub_a, - uint32_t lb_c, uint32_t ub_c) { - KeySpaces key_spaces(lb_a, ub_a, lb_c, ub_c); - std::string key_spaces_rep = key_spaces.EncodeTo(); - - std::unique_ptr wfile; - Status s1 = - Env::Default()->NewWritableFile(key_spaces_path, &wfile, EnvOptions()); - assert(s1.ok()); - assert(wfile); - s1 = wfile->Append(key_spaces_rep); - assert(s1.ok()); -} - -MultiOpsTxnsStressTest::KeySpaces MultiOpsTxnsStressTest::ReadKeySpacesDesc( - const std::string& key_spaces_path) { - KeySpaces key_spaces; - std::unique_ptr sfile; - Status s1 = - Env::Default()->NewSequentialFile(key_spaces_path, &sfile, EnvOptions()); - assert(s1.ok()); - assert(sfile); - char buf[16]; - Slice result; - s1 = sfile->Read(sizeof(buf), &result, buf); - assert(s1.ok()); - if (!key_spaces.DecodeFrom(result)) { - assert(false); - } - return key_spaces; -} - -// Create an empty database if necessary and preload it with initial test data. -// Key range [lb_a, ub_a), [lb_c, ub_c). The key ranges will be shared by -// 'threads' threads. -// PreloadDb() also sets up KeyGenerator objects for each sub key range -// operated on by each thread. -// Both [lb_a, ub_a) and [lb_c, ub_c) are partitioned. Each thread operates on -// one sub range, using KeyGenerators to generate keys. -// For example, we choose a from [0, 10000) and c from [0, 100). Number of -// threads is 32, their tids range from 0 to 31. -// Thread k chooses a from [312*k,312*(k+1)) and c from [3*k,3*(k+1)) if k<31. -// Thread 31 chooses a from [9672, 10000) and c from [93, 100). -// Within each subrange: a from [low1, high1), c from [low2, high2). -// high1 - low1 > high2 - low2 -// We reserve {high1 - 1} and {high2 - 1} as unallocated. -// The records are , , ..., -// , ... -void MultiOpsTxnsStressTest::PreloadDb(SharedState* shared, int threads, - uint32_t lb_a, uint32_t ub_a, - uint32_t lb_c, uint32_t ub_c) { - key_gen_for_a_.resize(threads); - key_gen_for_c_.resize(threads); - - assert(ub_a > lb_a && ub_a > lb_a + threads); - assert(ub_c > lb_c && ub_c > lb_c + threads); - - PersistKeySpacesDesc(FLAGS_key_spaces_path, lb_a, ub_a, lb_c, ub_c); - - fprintf(stdout, "a from [%u, %u), c from [%u, %u)\n", - static_cast(lb_a), static_cast(ub_a), - static_cast(lb_c), static_cast(ub_c)); - - const uint32_t num_c = ub_c - lb_c; - const uint32_t num_c_per_thread = num_c / threads; - const uint32_t num_a = ub_a - lb_a; - const uint32_t num_a_per_thread = num_a / threads; - - WriteOptions wopts; - wopts.disableWAL = FLAGS_disable_wal; - Random rnd(shared->GetSeed()); - assert(txn_db_); - - std::vector existing_a_uniqs(threads); - std::vector non_existing_a_uniqs(threads); - std::vector existing_c_uniqs(threads); - std::vector non_existing_c_uniqs(threads); - - for (uint32_t a = lb_a; a < ub_a; ++a) { - uint32_t tid = (a - lb_a) / num_a_per_thread; - if (tid >= static_cast(threads)) { - tid = threads - 1; - } - - uint32_t a_base = lb_a + tid * num_a_per_thread; - uint32_t a_hi = (tid < static_cast(threads - 1)) - ? (a_base + num_a_per_thread) - : ub_a; - uint32_t a_delta = a - a_base; - - if (a == a_hi - 1) { - non_existing_a_uniqs[tid].insert(a); - continue; - } - - uint32_t c_base = lb_c + tid * num_c_per_thread; - uint32_t c_hi = (tid < static_cast(threads - 1)) - ? (c_base + num_c_per_thread) - : ub_c; - uint32_t c_delta = a_delta % (c_hi - c_base - 1); - uint32_t c = c_base + c_delta; - - uint32_t b = rnd.Next(); - Record record(a, b, c); - WriteBatch wb; - const auto primary_index_entry = record.EncodePrimaryIndexEntry(); - Status s = wb.Put(primary_index_entry.first, primary_index_entry.second); - assert(s.ok()); - - const auto secondary_index_entry = record.EncodeSecondaryIndexEntry(); - s = wb.Put(secondary_index_entry.first, secondary_index_entry.second); - assert(s.ok()); - - s = txn_db_->Write(wopts, &wb); - assert(s.ok()); - - // TODO (yanqin): make the following check optional, especially when data - // size is large. - Record tmp_rec; - tmp_rec.SetB(record.b_value()); - s = tmp_rec.DecodeSecondaryIndexEntry(secondary_index_entry.first, - secondary_index_entry.second); - assert(s.ok()); - assert(tmp_rec == record); - - existing_a_uniqs[tid].insert(a); - existing_c_uniqs[tid].insert(c); - } - - for (int i = 0; i < threads; ++i) { - uint32_t my_seed = i + shared->GetSeed(); - - auto& key_gen_for_a = key_gen_for_a_[i]; - assert(!key_gen_for_a); - uint32_t low = lb_a + i * num_a_per_thread; - uint32_t high = (i < threads - 1) ? (low + num_a_per_thread) : ub_a; - assert(existing_a_uniqs[i].size() == high - low - 1); - assert(non_existing_a_uniqs[i].size() == 1); - key_gen_for_a = std::make_unique( - my_seed, low, high, std::move(existing_a_uniqs[i]), - std::move(non_existing_a_uniqs[i])); - - auto& key_gen_for_c = key_gen_for_c_[i]; - assert(!key_gen_for_c); - low = lb_c + i * num_c_per_thread; - high = (i < threads - 1) ? (low + num_c_per_thread) : ub_c; - non_existing_c_uniqs[i].insert(high - 1); - assert(existing_c_uniqs[i].size() == high - low - 1); - assert(non_existing_c_uniqs[i].size() == 1); - key_gen_for_c = std::make_unique( - my_seed, low, high, std::move(existing_c_uniqs[i]), - std::move(non_existing_c_uniqs[i])); - } -} - -// Scan an existing, non-empty database. -// Set up [lb_a, ub_a) and [lb_c, ub_c) as test key ranges. -// Set up KeyGenerator objects for each sub key range operated on by each -// thread. -// Scan the entire database and for each subrange, populate the existing keys -// and non-existing keys. We currently require the non-existing keys be -// non-empty after initialization. -void MultiOpsTxnsStressTest::ScanExistingDb(SharedState* shared, int threads) { - key_gen_for_a_.resize(threads); - key_gen_for_c_.resize(threads); - - KeySpaces key_spaces = ReadKeySpacesDesc(FLAGS_key_spaces_path); - - const uint32_t lb_a = key_spaces.lb_a; - const uint32_t ub_a = key_spaces.ub_a; - const uint32_t lb_c = key_spaces.lb_c; - const uint32_t ub_c = key_spaces.ub_c; - - assert(lb_a < ub_a && lb_c < ub_c); - - fprintf(stdout, "a from [%u, %u), c from [%u, %u)\n", - static_cast(lb_a), static_cast(ub_a), - static_cast(lb_c), static_cast(ub_c)); - - assert(ub_a > lb_a && ub_a > lb_a + threads); - assert(ub_c > lb_c && ub_c > lb_c + threads); - - const uint32_t num_c = ub_c - lb_c; - const uint32_t num_c_per_thread = num_c / threads; - const uint32_t num_a = ub_a - lb_a; - const uint32_t num_a_per_thread = num_a / threads; - - assert(db_); - ReadOptions ropts; - std::vector existing_a_uniqs(threads); - std::vector non_existing_a_uniqs(threads); - std::vector existing_c_uniqs(threads); - std::vector non_existing_c_uniqs(threads); - { - std::string pk_lb_str = Record::EncodePrimaryKey(0); - std::string pk_ub_str = - Record::EncodePrimaryKey(std::numeric_limits::max()); - Slice pk_lb = pk_lb_str; - Slice pk_ub = pk_ub_str; - ropts.iterate_lower_bound = &pk_lb; - ropts.iterate_upper_bound = &pk_ub; - ropts.total_order_seek = true; - std::unique_ptr it(db_->NewIterator(ropts)); - - for (it->SeekToFirst(); it->Valid(); it->Next()) { - Record record; - Status s = record.DecodePrimaryIndexEntry(it->key(), it->value()); - if (!s.ok()) { - fprintf(stderr, "Cannot decode primary index entry (%s => %s): %s\n", - it->key().ToString(true).c_str(), - it->value().ToString(true).c_str(), s.ToString().c_str()); - assert(false); - } - uint32_t a = record.a_value(); - assert(a >= lb_a); - assert(a < ub_a); - uint32_t tid = (a - lb_a) / num_a_per_thread; - if (tid >= static_cast(threads)) { - tid = threads - 1; - } - - existing_a_uniqs[tid].insert(a); - - uint32_t c = record.c_value(); - assert(c >= lb_c); - assert(c < ub_c); - tid = (c - lb_c) / num_c_per_thread; - if (tid >= static_cast(threads)) { - tid = threads - 1; - } - auto& existing_c_uniq = existing_c_uniqs[tid]; - existing_c_uniq.insert(c); - } - - for (uint32_t a = lb_a; a < ub_a; ++a) { - uint32_t tid = (a - lb_a) / num_a_per_thread; - if (tid >= static_cast(threads)) { - tid = threads - 1; - } - if (0 == existing_a_uniqs[tid].count(a)) { - non_existing_a_uniqs[tid].insert(a); - } - } - - for (uint32_t c = lb_c; c < ub_c; ++c) { - uint32_t tid = (c - lb_c) / num_c_per_thread; - if (tid >= static_cast(threads)) { - tid = threads - 1; - } - if (0 == existing_c_uniqs[tid].count(c)) { - non_existing_c_uniqs[tid].insert(c); - } - } - - for (int i = 0; i < threads; ++i) { - uint32_t my_seed = i + shared->GetSeed(); - auto& key_gen_for_a = key_gen_for_a_[i]; - assert(!key_gen_for_a); - uint32_t low = lb_a + i * num_a_per_thread; - uint32_t high = (i < threads - 1) ? (low + num_a_per_thread) : ub_a; - - // The following two assertions assume the test thread count and key - // space remain the same across different runs. Will need to relax. - assert(existing_a_uniqs[i].size() == high - low - 1); - assert(non_existing_a_uniqs[i].size() == 1); - - key_gen_for_a = std::make_unique( - my_seed, low, high, std::move(existing_a_uniqs[i]), - std::move(non_existing_a_uniqs[i])); - - auto& key_gen_for_c = key_gen_for_c_[i]; - assert(!key_gen_for_c); - low = lb_c + i * num_c_per_thread; - high = (i < threads - 1) ? (low + num_c_per_thread) : ub_c; - - // The following two assertions assume the test thread count and key - // space remain the same across different runs. Will need to relax. - assert(existing_c_uniqs[i].size() == high - low - 1); - assert(non_existing_c_uniqs[i].size() == 1); - - key_gen_for_c = std::make_unique( - my_seed, low, high, std::move(existing_c_uniqs[i]), - std::move(non_existing_c_uniqs[i])); - } - } -} - -StressTest* CreateMultiOpsTxnsStressTest() { - return new MultiOpsTxnsStressTest(); -} - -void CheckAndSetOptionsForMultiOpsTxnStressTest() { - if (FLAGS_test_batches_snapshots || FLAGS_test_cf_consistency) { - fprintf(stderr, - "-test_multi_ops_txns is not compatible with " - "-test_bathces_snapshots and -test_cf_consistency\n"); - exit(1); - } - if (!FLAGS_use_txn) { - fprintf(stderr, "-use_txn must be true if -test_multi_ops_txns\n"); - exit(1); - } else if (FLAGS_test_secondary > 0) { - fprintf( - stderr, - "secondary instance does not support replaying logs (MANIFEST + WAL) " - "of TransactionDB with write-prepared/write-unprepared policy\n"); - exit(1); - } - if (FLAGS_clear_column_family_one_in > 0) { - fprintf(stderr, - "-test_multi_ops_txns is not compatible with clearing column " - "families\n"); - exit(1); - } - if (FLAGS_column_families > 1) { - // TODO (yanqin) support separating primary index and secondary index in - // different column families. - fprintf(stderr, - "-test_multi_ops_txns currently does not use more than one column " - "family\n"); - exit(1); - } - if (FLAGS_writepercent > 0 || FLAGS_delpercent > 0 || - FLAGS_delrangepercent > 0) { - fprintf(stderr, - "-test_multi_ops_txns requires that -writepercent, -delpercent and " - "-delrangepercent be 0\n"); - exit(1); - } - if (FLAGS_key_spaces_path.empty()) { - fprintf(stderr, - "Must specify a file to store ranges of A and C via " - "-key_spaces_path\n"); - exit(1); - } - if (FLAGS_create_timestamped_snapshot_one_in > 0) { - if (FLAGS_txn_write_policy != - static_cast(TxnDBWritePolicy::WRITE_COMMITTED)) { - fprintf(stderr, - "Timestamped snapshot is not yet supported by " - "write-prepared/write-unprepared transactions\n"); - exit(1); - } - } - if (FLAGS_sync_fault_injection == 1) { - fprintf(stderr, - "Sync fault injection is currently not supported in " - "-test_multi_ops_txns\n"); - exit(1); - } -} -} // namespace ROCKSDB_NAMESPACE - -#endif // GFLAGS diff --git a/db_stress_tool/multi_ops_txns_stress.h b/db_stress_tool/multi_ops_txns_stress.h deleted file mode 100644 index 479344643..000000000 --- a/db_stress_tool/multi_ops_txns_stress.h +++ /dev/null @@ -1,440 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" - -namespace ROCKSDB_NAMESPACE { - -// This file defines MultiOpsTxnsStress so that we can stress test RocksDB -// transactions on a simple, emulated relational table. -// -// The record format is similar to the example found at -// https://github.com/facebook/mysql-5.6/wiki/MyRocks-record-format. -// -// The table is created by -// ``` -// create table t1 ( -// a int primary key, -// b int, -// c int, -// key(c), -// ) -// ``` -// -// (For simplicity, we use uint32_t for int here.) -// -// For this table, there is a primary index using `a`, as well as a secondary -// index using `c` and `a`. -// -// Primary key format: -// | index id | M(a) | -// Primary index value: -// | b | c | -// M(a) represents the big-endian format of a. -// -// Secondary key format: -// | index id | M(c) | M(a) | -// Secondary index value: -// | crc32 | -// Similarly to M(a), M(c) is the big-endian format of c. -// -// The in-memory representation of a record is defined in class -// MultiOpsTxnsStress:Record that includes a number of helper methods to -// encode/decode primary index keys, primary index values, secondary index keys, -// secondary index values, etc. -// -// Sometimes primary index and secondary index reside on different column -// families, but sometimes they colocate in the same column family. Current -// implementation puts them in the same (default) column family, and this is -// subject to future change if we find it interesting to test the other case. -// -// Class MultiOpsTxnsStressTest has the following transactions for testing. -// -// 1. Primary key update -// UPDATE t1 SET a = 3 WHERE a = 2; -// ``` -// tx->GetForUpdate(primary key a=2) -// tx->GetForUpdate(primary key a=3) -// tx->Delete(primary key a=2) -// tx->Put(primary key a=3, value) -// tx->batch->SingleDelete(secondary key a=2) -// tx->batch->Put(secondary key a=3, value) -// tx->Prepare() -// Tx->Commit() -// ``` -// -// 2. Secondary key update -// UPDATE t1 SET c = 3 WHERE c = 2; -// ``` -// iter->Seek(secondary key) -// // Get corresponding primary key value(s) from iterator -// tx->GetForUpdate(primary key) -// tx->Put(primary key, value c=3) -// tx->batch->SingleDelete(secondary key c=2) -// tx->batch->Put(secondary key c=3) -// tx->Prepare() -// tx->Commit() -// ``` -// -// 3. Primary index value update -// UPDATE t1 SET b = b + 1 WHERE a = 2; -// ``` -// tx->GetForUpdate(primary key a=2) -// tx->Put(primary key a=2, value b=b+1) -// tx->Prepare() -// tx->Commit() -// ``` -// -// 4. Point lookup -// SELECT * FROM t1 WHERE a = 3; -// ``` -// tx->Get(primary key a=3) -// tx->Commit() -// ``` -// -// 5. Range scan -// SELECT * FROM t1 WHERE c = 2; -// ``` -// it = tx->GetIterator() -// it->Seek(secondary key c=2) -// tx->Commit() -// ``` - -class MultiOpsTxnsStressTest : public StressTest { - public: - class Record { - public: - static constexpr uint32_t kMetadataPrefix = 0; - static constexpr uint32_t kPrimaryIndexId = 1; - static constexpr uint32_t kSecondaryIndexId = 2; - - static constexpr size_t kPrimaryIndexEntrySize = 8 + 8; - static constexpr size_t kSecondaryIndexEntrySize = 12 + 4; - - static_assert(kPrimaryIndexId < kSecondaryIndexId, - "kPrimaryIndexId must be smaller than kSecondaryIndexId"); - - static_assert(sizeof(kPrimaryIndexId) == sizeof(uint32_t), - "kPrimaryIndexId must be 4 bytes"); - static_assert(sizeof(kSecondaryIndexId) == sizeof(uint32_t), - "kSecondaryIndexId must be 4 bytes"); - - // Used for generating search key to probe primary index. - static std::string EncodePrimaryKey(uint32_t a); - // Used for generating search prefix to probe secondary index. - static std::string EncodeSecondaryKey(uint32_t c); - // Used for generating search key to probe secondary index. - static std::string EncodeSecondaryKey(uint32_t c, uint32_t a); - - static std::tuple DecodePrimaryIndexValue( - Slice primary_index_value); - - static std::pair DecodeSecondaryIndexValue( - Slice secondary_index_value); - - Record() = default; - Record(uint32_t _a, uint32_t _b, uint32_t _c) : a_(_a), b_(_b), c_(_c) {} - - bool operator==(const Record& other) const { - return a_ == other.a_ && b_ == other.b_ && c_ == other.c_; - } - - bool operator!=(const Record& other) const { return !(*this == other); } - - std::pair EncodePrimaryIndexEntry() const; - - std::string EncodePrimaryKey() const; - - std::string EncodePrimaryIndexValue() const; - - std::pair EncodeSecondaryIndexEntry() const; - - std::string EncodeSecondaryKey() const; - - Status DecodePrimaryIndexEntry(Slice primary_index_key, - Slice primary_index_value); - - Status DecodeSecondaryIndexEntry(Slice secondary_index_key, - Slice secondary_index_value); - - uint32_t a_value() const { return a_; } - uint32_t b_value() const { return b_; } - uint32_t c_value() const { return c_; } - - void SetA(uint32_t _a) { a_ = _a; } - void SetB(uint32_t _b) { b_ = _b; } - void SetC(uint32_t _c) { c_ = _c; } - - std::string ToString() const { - std::string ret("("); - ret.append(std::to_string(a_)); - ret.append(","); - ret.append(std::to_string(b_)); - ret.append(","); - ret.append(std::to_string(c_)); - ret.append(")"); - return ret; - } - - private: - friend class InvariantChecker; - - uint32_t a_{0}; - uint32_t b_{0}; - uint32_t c_{0}; - }; - - MultiOpsTxnsStressTest() {} - - ~MultiOpsTxnsStressTest() override {} - - void FinishInitDb(SharedState*) override; - - void ReopenAndPreloadDbIfNeeded(SharedState* shared); - - bool IsStateTracked() const override { return false; } - - Status TestGet(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - std::vector TestMultiGet( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - void TestGetEntity(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - Status TestPrefixScan(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - // Given a key K, this creates an iterator which scans to K and then - // does a random sequence of Next/Prev operations. - Status TestIterate(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - Status TestPut(ThreadState* thread, WriteOptions& write_opts, - const ReadOptions& read_opts, const std::vector& cf_ids, - const std::vector& keys, char (&value)[100]) override; - - Status TestDelete(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - void TestIngestExternalFile(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - void TestCompactRange(ThreadState* thread, int64_t rand_key, - const Slice& start_key, - ColumnFamilyHandle* column_family) override; - - Status TestBackupRestore(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - Status TestCheckpoint(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - Status TestApproximateSize(ThreadState* thread, uint64_t iteration, - const std::vector& rand_column_families, - const std::vector& rand_keys) override; - - Status TestCustomOperations( - ThreadState* thread, - const std::vector& rand_column_families) override; - - void RegisterAdditionalListeners() override; - - void PrepareTxnDbOptions(SharedState* /*shared*/, - TransactionDBOptions& txn_db_opts) override; - - Status PrimaryKeyUpdateTxn(ThreadState* thread, uint32_t old_a, - uint32_t old_a_pos, uint32_t new_a); - - Status SecondaryKeyUpdateTxn(ThreadState* thread, uint32_t old_c, - uint32_t old_c_pos, uint32_t new_c); - - Status UpdatePrimaryIndexValueTxn(ThreadState* thread, uint32_t a, - uint32_t b_delta); - - Status PointLookupTxn(ThreadState* thread, ReadOptions ropts, uint32_t a); - - Status RangeScanTxn(ThreadState* thread, ReadOptions ropts, uint32_t c); - - void VerifyDb(ThreadState* thread) const override; - - void ContinuouslyVerifyDb(ThreadState* thread) const override { - VerifyDb(thread); - } - - void VerifyPkSkFast(int job_id); - - protected: - class Counter { - public: - uint64_t Next() { return value_.fetch_add(1); } - - private: - std::atomic value_ = Env::Default()->NowNanos(); - }; - - using KeySet = std::set; - class KeyGenerator { - public: - explicit KeyGenerator(uint32_t s, uint32_t low, uint32_t high, - KeySet&& existing_uniq, KeySet&& non_existing_uniq) - : rand_(s), - low_(low), - high_(high), - existing_uniq_(std::move(existing_uniq)), - non_existing_uniq_(std::move(non_existing_uniq)) {} - ~KeyGenerator() { - assert(!existing_uniq_.empty()); - assert(!non_existing_uniq_.empty()); - } - void FinishInit(); - - std::pair ChooseExisting(); - void Replace(uint32_t old_val, uint32_t old_pos, uint32_t new_val); - uint32_t Allocate(); - void UndoAllocation(uint32_t new_val); - - std::string ToString() const { - std::ostringstream oss; - oss << "[" << low_ << ", " << high_ << "): " << existing_.size() - << " elements, " << existing_uniq_.size() << " unique values, " - << non_existing_uniq_.size() << " unique non-existing values"; - return oss.str(); - } - - private: - Random rand_; - uint32_t low_ = 0; - uint32_t high_ = 0; - std::vector existing_{}; - KeySet existing_uniq_{}; - KeySet non_existing_uniq_{}; - bool initialized_ = false; - }; - - // Return - std::pair ChooseExistingA(ThreadState* thread); - - uint32_t GenerateNextA(ThreadState* thread); - - // Return - std::pair ChooseExistingC(ThreadState* thread); - - uint32_t GenerateNextC(ThreadState* thread); - - // Randomly commit or rollback `txn` - void ProcessRecoveredPreparedTxnsHelper(Transaction* txn, - SharedState*) override; - - // Some applications, e.g. MyRocks writes a KV pair to the database via - // commit-time-write-batch (ctwb) in additional to the transaction's regular - // write batch. The key is usually constant representing some system - // metadata, while the value is monoticailly increasing which represents the - // actual value of the metadata. Method WriteToCommitTimeWriteBatch() - // emulates this scenario. - Status WriteToCommitTimeWriteBatch(Transaction& txn); - - Status CommitAndCreateTimestampedSnapshotIfNeeded(ThreadState* thread, - Transaction& txn); - - void SetupSnapshot(ThreadState* thread, ReadOptions& read_opts, - Transaction& txn, - std::shared_ptr& snapshot); - - std::vector> key_gen_for_a_; - std::vector> key_gen_for_c_; - - Counter counter_{}; - - private: - struct KeySpaces { - uint32_t lb_a = 0; - uint32_t ub_a = 0; - uint32_t lb_c = 0; - uint32_t ub_c = 0; - - explicit KeySpaces() = default; - explicit KeySpaces(uint32_t _lb_a, uint32_t _ub_a, uint32_t _lb_c, - uint32_t _ub_c) - : lb_a(_lb_a), ub_a(_ub_a), lb_c(_lb_c), ub_c(_ub_c) {} - - std::string EncodeTo() const; - bool DecodeFrom(Slice data); - }; - - void PersistKeySpacesDesc(const std::string& key_spaces_path, uint32_t lb_a, - uint32_t ub_a, uint32_t lb_c, uint32_t ub_c); - - KeySpaces ReadKeySpacesDesc(const std::string& key_spaces_path); - - void PreloadDb(SharedState* shared, int threads, uint32_t lb_a, uint32_t ub_a, - uint32_t lb_c, uint32_t ub_c); - - void ScanExistingDb(SharedState* shared, int threads); -}; - -class InvariantChecker { - public: - static_assert(sizeof(MultiOpsTxnsStressTest::Record().a_) == sizeof(uint32_t), - "MultiOpsTxnsStressTest::Record::a_ must be 4 bytes"); - static_assert(sizeof(MultiOpsTxnsStressTest::Record().b_) == sizeof(uint32_t), - "MultiOpsTxnsStressTest::Record::b_ must be 4 bytes"); - static_assert(sizeof(MultiOpsTxnsStressTest::Record().c_) == sizeof(uint32_t), - "MultiOpsTxnsStressTest::Record::c_ must be 4 bytes"); -}; - -class MultiOpsTxnsStressListener : public EventListener { - public: - explicit MultiOpsTxnsStressListener(MultiOpsTxnsStressTest* stress_test) - : stress_test_(stress_test) { - assert(stress_test_); - } - - ~MultiOpsTxnsStressListener() override {} - - void OnFlushCompleted(DB* db, const FlushJobInfo& info) override { - assert(db); -#ifdef NDEBUG - (void)db; -#endif - assert(info.cf_id == 0); - stress_test_->VerifyPkSkFast(info.job_id); - } - - void OnCompactionCompleted(DB* db, const CompactionJobInfo& info) override { - assert(db); -#ifdef NDEBUG - (void)db; -#endif - assert(info.cf_id == 0); - stress_test_->VerifyPkSkFast(info.job_id); - } - - private: - MultiOpsTxnsStressTest* const stress_test_ = nullptr; -}; - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/db_stress_tool/no_batched_ops_stress.cc b/db_stress_tool/no_batched_ops_stress.cc deleted file mode 100644 index 716ea3802..000000000 --- a/db_stress_tool/no_batched_ops_stress.cc +++ /dev/null @@ -1,1676 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifdef GFLAGS -#include "db_stress_tool/db_stress_common.h" -#include "rocksdb/utilities/transaction_db.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { -class NonBatchedOpsStressTest : public StressTest { - public: - NonBatchedOpsStressTest() {} - - virtual ~NonBatchedOpsStressTest() {} - - void VerifyDb(ThreadState* thread) const override { - // This `ReadOptions` is for validation purposes. Ignore - // `FLAGS_rate_limit_user_ops` to avoid slowing any validation. - ReadOptions options(FLAGS_verify_checksum, true); - std::string ts_str; - Slice ts; - if (FLAGS_user_timestamp_size > 0) { - ts_str = GetNowNanos(); - ts = ts_str; - options.timestamp = &ts; - } - - auto shared = thread->shared; - const int64_t max_key = shared->GetMaxKey(); - const int64_t keys_per_thread = max_key / shared->GetNumThreads(); - int64_t start = keys_per_thread * thread->tid; - int64_t end = start + keys_per_thread; - uint64_t prefix_to_use = - (FLAGS_prefix_size < 0) ? 1 : static_cast(FLAGS_prefix_size); - - if (thread->tid == shared->GetNumThreads() - 1) { - end = max_key; - } - - for (size_t cf = 0; cf < column_families_.size(); ++cf) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - enum class VerificationMethod { - kIterator, - kGet, - kGetEntity, - kMultiGet, - kMultiGetEntity, - kGetMergeOperands, - // Add any new items above kNumberOfMethods - kNumberOfMethods - }; - - constexpr int num_methods = - static_cast(VerificationMethod::kNumberOfMethods); - - const VerificationMethod method = - static_cast(thread->rand.Uniform( - (FLAGS_user_timestamp_size > 0) ? num_methods - 1 : num_methods)); - - if (method == VerificationMethod::kIterator) { - std::unique_ptr iter( - db_->NewIterator(options, column_families_[cf])); - - std::string seek_key = Key(start); - iter->Seek(seek_key); - - Slice prefix(seek_key.data(), prefix_to_use); - - for (int64_t i = start; i < end; ++i) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - const std::string key = Key(i); - const Slice k(key); - const Slice pfx(key.data(), prefix_to_use); - - // Reseek when the prefix changes - if (prefix_to_use > 0 && prefix.compare(pfx) != 0) { - iter->Seek(k); - seek_key = key; - prefix = Slice(seek_key.data(), prefix_to_use); - } - - Status s = iter->status(); - - std::string from_db; - - if (iter->Valid()) { - const int diff = iter->key().compare(k); - - if (diff > 0) { - s = Status::NotFound(); - } else if (diff == 0) { - if (!VerifyWideColumns(iter->value(), iter->columns())) { - VerificationAbort(shared, static_cast(cf), i, - iter->value(), iter->columns()); - } - - from_db = iter->value().ToString(); - iter->Next(); - } else { - assert(diff < 0); - - VerificationAbort(shared, "An out of range key was found", - static_cast(cf), i); - } - } else { - // The iterator found no value for the key in question, so do not - // move to the next item in the iterator - s = Status::NotFound(); - } - - VerifyOrSyncValue(static_cast(cf), i, options, shared, from_db, - /* msg_prefix */ "Iterator verification", s, - /* strict */ true); - - if (!from_db.empty()) { - PrintKeyValue(static_cast(cf), static_cast(i), - from_db.data(), from_db.size()); - } - } - } else if (method == VerificationMethod::kGet) { - for (int64_t i = start; i < end; ++i) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - const std::string key = Key(i); - std::string from_db; - - Status s = db_->Get(options, column_families_[cf], key, &from_db); - - VerifyOrSyncValue(static_cast(cf), i, options, shared, from_db, - /* msg_prefix */ "Get verification", s, - /* strict */ true); - - if (!from_db.empty()) { - PrintKeyValue(static_cast(cf), static_cast(i), - from_db.data(), from_db.size()); - } - } - } else if (method == VerificationMethod::kGetEntity) { - for (int64_t i = start; i < end; ++i) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - const std::string key = Key(i); - PinnableWideColumns result; - - Status s = - db_->GetEntity(options, column_families_[cf], key, &result); - - std::string from_db; - - if (s.ok()) { - const WideColumns& columns = result.columns(); - - if (!columns.empty() && - columns.front().name() == kDefaultWideColumnName) { - from_db = columns.front().value().ToString(); - } - - if (!VerifyWideColumns(columns)) { - VerificationAbort(shared, static_cast(cf), i, from_db, - columns); - } - } - - VerifyOrSyncValue(static_cast(cf), i, options, shared, from_db, - /* msg_prefix */ "GetEntity verification", s, - /* strict */ true); - - if (!from_db.empty()) { - PrintKeyValue(static_cast(cf), static_cast(i), - from_db.data(), from_db.size()); - } - } - } else if (method == VerificationMethod::kMultiGet) { - for (int64_t i = start; i < end;) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - // Keep the batch size to some reasonable value - size_t batch_size = thread->rand.Uniform(128) + 1; - batch_size = std::min(batch_size, end - i); - - std::vector key_strs(batch_size); - std::vector keys(batch_size); - std::vector values(batch_size); - std::vector statuses(batch_size); - - for (size_t j = 0; j < batch_size; ++j) { - key_strs[j] = Key(i + j); - keys[j] = Slice(key_strs[j]); - } - - db_->MultiGet(options, column_families_[cf], batch_size, keys.data(), - values.data(), statuses.data()); - - for (size_t j = 0; j < batch_size; ++j) { - const std::string from_db = values[j].ToString(); - - VerifyOrSyncValue(static_cast(cf), i + j, options, shared, - from_db, /* msg_prefix */ "MultiGet verification", - statuses[j], /* strict */ true); - - if (!from_db.empty()) { - PrintKeyValue(static_cast(cf), static_cast(i + j), - from_db.data(), from_db.size()); - } - } - - i += batch_size; - } - } else if (method == VerificationMethod::kMultiGetEntity) { - for (int64_t i = start; i < end;) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - // Keep the batch size to some reasonable value - size_t batch_size = thread->rand.Uniform(128) + 1; - batch_size = std::min(batch_size, end - i); - - std::vector key_strs(batch_size); - std::vector keys(batch_size); - std::vector results(batch_size); - std::vector statuses(batch_size); - - for (size_t j = 0; j < batch_size; ++j) { - key_strs[j] = Key(i + j); - keys[j] = Slice(key_strs[j]); - } - - db_->MultiGetEntity(options, column_families_[cf], batch_size, - keys.data(), results.data(), statuses.data()); - - for (size_t j = 0; j < batch_size; ++j) { - std::string from_db; - - if (statuses[j].ok()) { - const WideColumns& columns = results[j].columns(); - - if (!columns.empty() && - columns.front().name() == kDefaultWideColumnName) { - from_db = columns.front().value().ToString(); - } - - if (!VerifyWideColumns(columns)) { - VerificationAbort(shared, static_cast(cf), i, from_db, - columns); - } - } - - VerifyOrSyncValue(static_cast(cf), i + j, options, shared, - from_db, - /* msg_prefix */ "MultiGetEntity verification", - statuses[j], /* strict */ true); - - if (!from_db.empty()) { - PrintKeyValue(static_cast(cf), static_cast(i + j), - from_db.data(), from_db.size()); - } - } - - i += batch_size; - } - } else { - assert(method == VerificationMethod::kGetMergeOperands); - - // Start off with small size that will be increased later if necessary - std::vector values(4); - - GetMergeOperandsOptions merge_operands_info; - merge_operands_info.expected_max_number_of_operands = - static_cast(values.size()); - - for (int64_t i = start; i < end; ++i) { - if (thread->shared->HasVerificationFailedYet()) { - break; - } - - const std::string key = Key(i); - const Slice k(key); - std::string from_db; - int number_of_operands = 0; - - Status s = db_->GetMergeOperands(options, column_families_[cf], k, - values.data(), &merge_operands_info, - &number_of_operands); - - if (s.IsIncomplete()) { - // Need to resize values as there are more than values.size() merge - // operands on this key. Should only happen a few times when we - // encounter a key that had more merge operands than any key seen so - // far - values.resize(number_of_operands); - merge_operands_info.expected_max_number_of_operands = - static_cast(number_of_operands); - s = db_->GetMergeOperands(options, column_families_[cf], k, - values.data(), &merge_operands_info, - &number_of_operands); - } - // Assumed here that GetMergeOperands always sets number_of_operand - if (number_of_operands) { - from_db = values[number_of_operands - 1].ToString(); - } - - VerifyOrSyncValue(static_cast(cf), i, options, shared, from_db, - /* msg_prefix */ "GetMergeOperands verification", s, - /* strict */ true); - - if (!from_db.empty()) { - PrintKeyValue(static_cast(cf), static_cast(i), - from_db.data(), from_db.size()); - } - } - } - } - } - - void ContinuouslyVerifyDb(ThreadState* thread) const override { - if (!cmp_db_) { - return; - } - assert(cmp_db_); - assert(!cmp_cfhs_.empty()); - Status s = cmp_db_->TryCatchUpWithPrimary(); - if (!s.ok()) { - assert(false); - exit(1); - } - - const auto checksum_column_family = [](Iterator* iter, - uint32_t* checksum) -> Status { - assert(nullptr != checksum); - uint32_t ret = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ret = crc32c::Extend(ret, iter->key().data(), iter->key().size()); - ret = crc32c::Extend(ret, iter->value().data(), iter->value().size()); - } - *checksum = ret; - return iter->status(); - }; - - auto* shared = thread->shared; - assert(shared); - const int64_t max_key = shared->GetMaxKey(); - ReadOptions read_opts(FLAGS_verify_checksum, true); - std::string ts_str; - Slice ts; - if (FLAGS_user_timestamp_size > 0) { - ts_str = GetNowNanos(); - ts = ts_str; - read_opts.timestamp = &ts; - } - - static Random64 rand64(shared->GetSeed()); - - { - uint32_t crc = 0; - std::unique_ptr it(cmp_db_->NewIterator(read_opts)); - s = checksum_column_family(it.get(), &crc); - if (!s.ok()) { - fprintf(stderr, "Computing checksum of default cf: %s\n", - s.ToString().c_str()); - assert(false); - } - } - - for (auto* handle : cmp_cfhs_) { - if (thread->rand.OneInOpt(3)) { - // Use Get() - uint64_t key = rand64.Uniform(static_cast(max_key)); - std::string key_str = Key(key); - std::string value; - std::string key_ts; - s = cmp_db_->Get(read_opts, handle, key_str, &value, - FLAGS_user_timestamp_size > 0 ? &key_ts : nullptr); - s.PermitUncheckedError(); - } else { - // Use range scan - std::unique_ptr iter(cmp_db_->NewIterator(read_opts, handle)); - uint32_t rnd = (thread->rand.Next()) % 4; - if (0 == rnd) { - // SeekToFirst() + Next()*5 - read_opts.total_order_seek = true; - iter->SeekToFirst(); - for (int i = 0; i < 5 && iter->Valid(); ++i, iter->Next()) { - } - } else if (1 == rnd) { - // SeekToLast() + Prev()*5 - read_opts.total_order_seek = true; - iter->SeekToLast(); - for (int i = 0; i < 5 && iter->Valid(); ++i, iter->Prev()) { - } - } else if (2 == rnd) { - // Seek() +Next()*5 - uint64_t key = rand64.Uniform(static_cast(max_key)); - std::string key_str = Key(key); - iter->Seek(key_str); - for (int i = 0; i < 5 && iter->Valid(); ++i, iter->Next()) { - } - } else { - // SeekForPrev() + Prev()*5 - uint64_t key = rand64.Uniform(static_cast(max_key)); - std::string key_str = Key(key); - iter->SeekForPrev(key_str); - for (int i = 0; i < 5 && iter->Valid(); ++i, iter->Prev()) { - } - } - } - } - } - - void MaybeClearOneColumnFamily(ThreadState* thread) override { - if (FLAGS_column_families > 1) { - if (thread->rand.OneInOpt(FLAGS_clear_column_family_one_in)) { - // drop column family and then create it again (can't drop default) - int cf = thread->rand.Next() % (FLAGS_column_families - 1) + 1; - std::string new_name = - std::to_string(new_column_family_name_.fetch_add(1)); - { - MutexLock l(thread->shared->GetMutex()); - fprintf( - stdout, - "[CF %d] Dropping and recreating column family. new name: %s\n", - cf, new_name.c_str()); - } - thread->shared->LockColumnFamily(cf); - Status s = db_->DropColumnFamily(column_families_[cf]); - delete column_families_[cf]; - if (!s.ok()) { - fprintf(stderr, "dropping column family error: %s\n", - s.ToString().c_str()); - std::terminate(); - } - s = db_->CreateColumnFamily(ColumnFamilyOptions(options_), new_name, - &column_families_[cf]); - column_family_names_[cf] = new_name; - thread->shared->ClearColumnFamily(cf); - if (!s.ok()) { - fprintf(stderr, "creating column family error: %s\n", - s.ToString().c_str()); - std::terminate(); - } - thread->shared->UnlockColumnFamily(cf); - } - } - } - - bool ShouldAcquireMutexOnKey() const override { return true; } - - bool IsStateTracked() const override { return true; } - - Status TestGet(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - auto cfh = column_families_[rand_column_families[0]]; - std::string key_str = Key(rand_keys[0]); - Slice key = key_str; - std::string from_db; - int error_count = 0; - - if (fault_fs_guard) { - fault_fs_guard->EnableErrorInjection(); - SharedState::ignore_read_error = false; - } - - std::unique_ptr lock(new MutexLock( - thread->shared->GetMutexForKey(rand_column_families[0], rand_keys[0]))); - - ReadOptions read_opts_copy = read_opts; - std::string read_ts_str; - Slice read_ts_slice; - if (FLAGS_user_timestamp_size > 0) { - read_ts_str = GetNowNanos(); - read_ts_slice = read_ts_str; - read_opts_copy.timestamp = &read_ts_slice; - } - bool read_older_ts = MaybeUseOlderTimestampForPointLookup( - thread, read_ts_str, read_ts_slice, read_opts_copy); - - Status s = db_->Get(read_opts_copy, cfh, key, &from_db); - if (fault_fs_guard) { - error_count = fault_fs_guard->GetAndResetErrorCount(); - } - if (s.ok()) { - if (fault_fs_guard) { - if (error_count && !SharedState::ignore_read_error) { - // Grab mutex so multiple thread don't try to print the - // stack trace at the same time - MutexLock l(thread->shared->GetMutex()); - fprintf(stderr, "Didn't get expected error from Get\n"); - fprintf(stderr, "Callstack that injected the fault\n"); - fault_fs_guard->PrintFaultBacktrace(); - std::terminate(); - } - } - // found case - thread->stats.AddGets(1, 1); - // we only have the latest expected state - if (!FLAGS_skip_verifydb && !read_older_ts && - thread->shared->Get(rand_column_families[0], rand_keys[0]) == - SharedState::DELETION_SENTINEL) { - thread->shared->SetVerificationFailure(); - fprintf(stderr, - "error : inconsistent values for key %s: Get returns %s, " - "expected state does not have the key.\n", - key.ToString(true).c_str(), StringToHex(from_db).c_str()); - } - } else if (s.IsNotFound()) { - // not found case - thread->stats.AddGets(1, 0); - if (!FLAGS_skip_verifydb && !read_older_ts) { - auto expected = - thread->shared->Get(rand_column_families[0], rand_keys[0]); - if (expected != SharedState::DELETION_SENTINEL && - expected != SharedState::UNKNOWN_SENTINEL) { - thread->shared->SetVerificationFailure(); - fprintf(stderr, - "error : inconsistent values for key %s: expected state has " - "the key, Get() returns NotFound.\n", - key.ToString(true).c_str()); - } - } - } else { - if (error_count == 0) { - // errors case - thread->stats.AddErrors(1); - } else { - thread->stats.AddVerifiedErrors(1); - } - } - if (fault_fs_guard) { - fault_fs_guard->DisableErrorInjection(); - } - return s; - } - - std::vector TestMultiGet( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - size_t num_keys = rand_keys.size(); - std::vector key_str; - std::vector keys; - key_str.reserve(num_keys); - keys.reserve(num_keys); - std::vector values(num_keys); - std::vector statuses(num_keys); - ColumnFamilyHandle* cfh = column_families_[rand_column_families[0]]; - int error_count = 0; - // Do a consistency check between Get and MultiGet. Don't do it too - // often as it will slow db_stress down - bool do_consistency_check = thread->rand.OneIn(4); - - ReadOptions readoptionscopy = read_opts; - if (do_consistency_check) { - readoptionscopy.snapshot = db_->GetSnapshot(); - } - - std::string read_ts_str; - Slice read_ts_slice; - MaybeUseOlderTimestampForPointLookup(thread, read_ts_str, read_ts_slice, - readoptionscopy); - - readoptionscopy.rate_limiter_priority = - FLAGS_rate_limit_user_ops ? Env::IO_USER : Env::IO_TOTAL; - - // To appease clang analyzer - const bool use_txn = FLAGS_use_txn; - - // Create a transaction in order to write some data. The purpose is to - // exercise WriteBatchWithIndex::MultiGetFromBatchAndDB. The transaction - // will be rolled back once MultiGet returns. - Transaction* txn = nullptr; - if (use_txn) { - WriteOptions wo; - if (FLAGS_rate_limit_auto_wal_flush) { - wo.rate_limiter_priority = Env::IO_USER; - } - Status s = NewTxn(wo, &txn); - if (!s.ok()) { - fprintf(stderr, "NewTxn: %s\n", s.ToString().c_str()); - std::terminate(); - } - } - for (size_t i = 0; i < num_keys; ++i) { - key_str.emplace_back(Key(rand_keys[i])); - keys.emplace_back(key_str.back()); - if (use_txn) { - // With a 1 in 10 probability, insert the just added key in the batch - // into the transaction. This will create an overlap with the MultiGet - // keys and exercise some corner cases in the code - if (thread->rand.OneIn(10)) { - int op = thread->rand.Uniform(2); - Status s; - switch (op) { - case 0: - case 1: { - uint32_t value_base = - thread->rand.Next() % thread->shared->UNKNOWN_SENTINEL; - char value[100]; - size_t sz = GenerateValue(value_base, value, sizeof(value)); - Slice v(value, sz); - if (op == 0) { - s = txn->Put(cfh, keys.back(), v); - } else { - s = txn->Merge(cfh, keys.back(), v); - } - break; - } - case 2: - s = txn->Delete(cfh, keys.back()); - break; - default: - assert(false); - } - if (!s.ok()) { - fprintf(stderr, "Transaction put: %s\n", s.ToString().c_str()); - std::terminate(); - } - } - } - } - - if (!use_txn) { - if (fault_fs_guard) { - fault_fs_guard->EnableErrorInjection(); - SharedState::ignore_read_error = false; - } - db_->MultiGet(readoptionscopy, cfh, num_keys, keys.data(), values.data(), - statuses.data()); - if (fault_fs_guard) { - error_count = fault_fs_guard->GetAndResetErrorCount(); - } - } else { - txn->MultiGet(readoptionscopy, cfh, num_keys, keys.data(), values.data(), - statuses.data()); - } - - if (fault_fs_guard && error_count && !SharedState::ignore_read_error) { - int stat_nok = 0; - for (const auto& s : statuses) { - if (!s.ok() && !s.IsNotFound()) { - stat_nok++; - } - } - - if (stat_nok < error_count) { - // Grab mutex so multiple thread don't try to print the - // stack trace at the same time - MutexLock l(thread->shared->GetMutex()); - fprintf(stderr, "Didn't get expected error from MultiGet. \n"); - fprintf(stderr, "num_keys %zu Expected %d errors, seen %d\n", num_keys, - error_count, stat_nok); - fprintf(stderr, "Callstack that injected the fault\n"); - fault_fs_guard->PrintFaultBacktrace(); - std::terminate(); - } - } - if (fault_fs_guard) { - fault_fs_guard->DisableErrorInjection(); - } - - for (size_t i = 0; i < statuses.size(); ++i) { - Status s = statuses[i]; - bool is_consistent = true; - // Only do the consistency check if no error was injected and MultiGet - // didn't return an unexpected error - if (do_consistency_check && !error_count && (s.ok() || s.IsNotFound())) { - Status tmp_s; - std::string value; - - if (use_txn) { - tmp_s = txn->Get(readoptionscopy, cfh, keys[i], &value); - } else { - tmp_s = db_->Get(readoptionscopy, cfh, keys[i], &value); - } - if (!tmp_s.ok() && !tmp_s.IsNotFound()) { - fprintf(stderr, "Get error: %s\n", s.ToString().c_str()); - is_consistent = false; - } else if (!s.ok() && tmp_s.ok()) { - fprintf(stderr, "MultiGet returned different results with key %s\n", - keys[i].ToString(true).c_str()); - fprintf(stderr, "Get returned ok, MultiGet returned not found\n"); - is_consistent = false; - } else if (s.ok() && tmp_s.IsNotFound()) { - fprintf(stderr, "MultiGet returned different results with key %s\n", - keys[i].ToString(true).c_str()); - fprintf(stderr, "MultiGet returned ok, Get returned not found\n"); - is_consistent = false; - } else if (s.ok() && value != values[i].ToString()) { - fprintf(stderr, "MultiGet returned different results with key %s\n", - keys[i].ToString(true).c_str()); - fprintf(stderr, "MultiGet returned value %s\n", - values[i].ToString(true).c_str()); - fprintf(stderr, "Get returned value %s\n", - Slice(value).ToString(true /* hex */).c_str()); - is_consistent = false; - } - } - - if (!is_consistent) { - fprintf(stderr, "TestMultiGet error: is_consistent is false\n"); - thread->stats.AddErrors(1); - // Fail fast to preserve the DB state - thread->shared->SetVerificationFailure(); - break; - } else if (s.ok()) { - // found case - thread->stats.AddGets(1, 1); - } else if (s.IsNotFound()) { - // not found case - thread->stats.AddGets(1, 0); - } else if (s.IsMergeInProgress() && use_txn) { - // With txn this is sometimes expected. - thread->stats.AddGets(1, 1); - } else { - if (error_count == 0) { - // errors case - fprintf(stderr, "MultiGet error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - } else { - thread->stats.AddVerifiedErrors(1); - } - } - } - - if (readoptionscopy.snapshot) { - db_->ReleaseSnapshot(readoptionscopy.snapshot); - } - if (use_txn) { - RollbackTxn(txn); - } - return statuses; - } - - void TestGetEntity(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - if (fault_fs_guard) { - fault_fs_guard->EnableErrorInjection(); - SharedState::ignore_read_error = false; - } - - assert(thread); - - SharedState* const shared = thread->shared; - assert(shared); - - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - std::unique_ptr lock(new MutexLock( - shared->GetMutexForKey(rand_column_families[0], rand_keys[0]))); - - assert(rand_column_families[0] >= 0); - assert(rand_column_families[0] < static_cast(column_families_.size())); - - ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]]; - assert(cfh); - - const std::string key = Key(rand_keys[0]); - - PinnableWideColumns from_db; - - const Status s = db_->GetEntity(read_opts, cfh, key, &from_db); - - int error_count = 0; - - if (fault_fs_guard) { - error_count = fault_fs_guard->GetAndResetErrorCount(); - } - - if (s.ok()) { - if (fault_fs_guard) { - if (error_count && !SharedState::ignore_read_error) { - // Grab mutex so multiple threads don't try to print the - // stack trace at the same time - MutexLock l(shared->GetMutex()); - fprintf(stderr, "Didn't get expected error from GetEntity\n"); - fprintf(stderr, "Call stack that injected the fault\n"); - fault_fs_guard->PrintFaultBacktrace(); - std::terminate(); - } - } - - thread->stats.AddGets(1, 1); - - if (!FLAGS_skip_verifydb) { - const WideColumns& columns = from_db.columns(); - - if (!VerifyWideColumns(columns)) { - shared->SetVerificationFailure(); - fprintf(stderr, - "error : inconsistent columns returned by GetEntity for key " - "%s: %s\n", - StringToHex(key).c_str(), WideColumnsToHex(columns).c_str()); - } else if (shared->Get(rand_column_families[0], rand_keys[0]) == - SharedState::DELETION_SENTINEL) { - shared->SetVerificationFailure(); - fprintf( - stderr, - "error : inconsistent values for key %s: GetEntity returns %s, " - "expected state does not have the key.\n", - StringToHex(key).c_str(), WideColumnsToHex(columns).c_str()); - } - } - } else if (s.IsNotFound()) { - thread->stats.AddGets(1, 0); - - if (!FLAGS_skip_verifydb) { - auto expected = shared->Get(rand_column_families[0], rand_keys[0]); - if (expected != SharedState::DELETION_SENTINEL && - expected != SharedState::UNKNOWN_SENTINEL) { - shared->SetVerificationFailure(); - fprintf(stderr, - "error : inconsistent values for key %s: expected state has " - "the key, GetEntity returns NotFound.\n", - StringToHex(key).c_str()); - } - } - } else { - if (error_count == 0) { - thread->stats.AddErrors(1); - } else { - thread->stats.AddVerifiedErrors(1); - } - } - - if (fault_fs_guard) { - fault_fs_guard->DisableErrorInjection(); - } - } - - Status TestPrefixScan(ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]]; - assert(cfh); - - const std::string key = Key(rand_keys[0]); - const Slice prefix(key.data(), FLAGS_prefix_size); - - std::string upper_bound; - Slice ub_slice; - ReadOptions ro_copy = read_opts; - - // Get the next prefix first and then see if we want to set upper bound. - // We'll use the next prefix in an assertion later on - if (GetNextPrefix(prefix, &upper_bound) && thread->rand.OneIn(2)) { - // For half of the time, set the upper bound to the next prefix - ub_slice = Slice(upper_bound); - ro_copy.iterate_upper_bound = &ub_slice; - } - - std::string read_ts_str; - Slice read_ts_slice; - MaybeUseOlderTimestampForRangeScan(thread, read_ts_str, read_ts_slice, - ro_copy); - - std::unique_ptr iter(db_->NewIterator(ro_copy, cfh)); - - uint64_t count = 0; - Status s; - - if (fault_fs_guard) { - fault_fs_guard->EnableErrorInjection(); - SharedState::ignore_read_error = false; - } - - for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix); - iter->Next()) { - ++count; - - // When iter_start_ts is set, iterator exposes internal keys, including - // tombstones; however, we want to perform column validation only for - // value-like types. - if (ro_copy.iter_start_ts) { - const ValueType value_type = ExtractValueType(iter->key()); - if (value_type != kTypeValue && value_type != kTypeBlobIndex && - value_type != kTypeWideColumnEntity) { - continue; - } - } - - if (!VerifyWideColumns(iter->value(), iter->columns())) { - s = Status::Corruption("Value and columns inconsistent", - DebugString(iter->value(), iter->columns())); - break; - } - } - - if (ro_copy.iter_start_ts == nullptr) { - assert(count <= GetPrefixKeyCount(prefix.ToString(), upper_bound)); - } - - if (s.ok()) { - s = iter->status(); - } - - uint64_t error_count = 0; - if (fault_fs_guard) { - error_count = fault_fs_guard->GetAndResetErrorCount(); - } - if (!s.ok() && (!fault_fs_guard || (fault_fs_guard && !error_count))) { - fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str()); - thread->stats.AddErrors(1); - - return s; - } - - if (fault_fs_guard) { - fault_fs_guard->DisableErrorInjection(); - } - thread->stats.AddPrefixes(1, count); - - return Status::OK(); - } - - Status TestPut(ThreadState* thread, WriteOptions& write_opts, - const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys, - char (&value)[100]) override { - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - auto shared = thread->shared; - assert(shared); - - const int64_t max_key = shared->GetMaxKey(); - - int64_t rand_key = rand_keys[0]; - int rand_column_family = rand_column_families[0]; - std::string write_ts; - - std::unique_ptr lock( - new MutexLock(shared->GetMutexForKey(rand_column_family, rand_key))); - while (!shared->AllowsOverwrite(rand_key) && - (FLAGS_use_merge || shared->Exists(rand_column_family, rand_key))) { - lock.reset(); - - rand_key = thread->rand.Next() % max_key; - rand_column_family = thread->rand.Next() % FLAGS_column_families; - - lock.reset( - new MutexLock(shared->GetMutexForKey(rand_column_family, rand_key))); - if (FLAGS_user_timestamp_size > 0) { - write_ts = GetNowNanos(); - } - } - - if (write_ts.empty() && FLAGS_user_timestamp_size) { - write_ts = GetNowNanos(); - } - - const std::string k = Key(rand_key); - - ColumnFamilyHandle* const cfh = column_families_[rand_column_family]; - assert(cfh); - - if (FLAGS_verify_before_write) { - std::string from_db; - Status s = db_->Get(read_opts, cfh, k, &from_db); - if (!VerifyOrSyncValue(rand_column_family, rand_key, read_opts, shared, - /* msg_prefix */ "Pre-Put Get verification", - from_db, s, /* strict */ true)) { - return s; - } - } - - const uint32_t value_base = thread->rand.Next() % shared->UNKNOWN_SENTINEL; - const size_t sz = GenerateValue(value_base, value, sizeof(value)); - const Slice v(value, sz); - - shared->Put(rand_column_family, rand_key, value_base, true /* pending */); - - Status s; - - if (FLAGS_use_merge) { - if (!FLAGS_use_txn) { - if (FLAGS_user_timestamp_size == 0) { - s = db_->Merge(write_opts, cfh, k, v); - } else { - s = db_->Merge(write_opts, cfh, k, write_ts, v); - } - } else { - Transaction* txn; - s = NewTxn(write_opts, &txn); - if (s.ok()) { - s = txn->Merge(cfh, k, v); - if (s.ok()) { - s = CommitTxn(txn, thread); - } - } - } - } else if (FLAGS_use_put_entity_one_in > 0 && - (value_base % FLAGS_use_put_entity_one_in) == 0) { - s = db_->PutEntity(write_opts, cfh, k, - GenerateWideColumns(value_base, v)); - } else { - if (!FLAGS_use_txn) { - if (FLAGS_user_timestamp_size == 0) { - s = db_->Put(write_opts, cfh, k, v); - } else { - s = db_->Put(write_opts, cfh, k, write_ts, v); - } - } else { - Transaction* txn; - s = NewTxn(write_opts, &txn); - if (s.ok()) { - s = txn->Put(cfh, k, v); - if (s.ok()) { - s = CommitTxn(txn, thread); - } - } - } - } - - shared->Put(rand_column_family, rand_key, value_base, false /* pending */); - - if (!s.ok()) { - if (FLAGS_injest_error_severity >= 2) { - if (!is_db_stopped_ && s.severity() >= Status::Severity::kFatalError) { - is_db_stopped_ = true; - } else if (!is_db_stopped_ || - s.severity() < Status::Severity::kFatalError) { - fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } else { - fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } - - thread->stats.AddBytesForWrites(1, sz); - PrintKeyValue(rand_column_family, static_cast(rand_key), value, - sz); - return s; - } - - Status TestDelete(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - int64_t rand_key = rand_keys[0]; - int rand_column_family = rand_column_families[0]; - auto shared = thread->shared; - - std::unique_ptr lock( - new MutexLock(shared->GetMutexForKey(rand_column_family, rand_key))); - - // OPERATION delete - std::string write_ts_str = GetNowNanos(); - Slice write_ts = write_ts_str; - - std::string key_str = Key(rand_key); - Slice key = key_str; - auto cfh = column_families_[rand_column_family]; - - // Use delete if the key may be overwritten and a single deletion - // otherwise. - Status s; - if (shared->AllowsOverwrite(rand_key)) { - shared->Delete(rand_column_family, rand_key, true /* pending */); - if (!FLAGS_use_txn) { - if (FLAGS_user_timestamp_size == 0) { - s = db_->Delete(write_opts, cfh, key); - } else { - s = db_->Delete(write_opts, cfh, key, write_ts); - } - } else { - Transaction* txn; - s = NewTxn(write_opts, &txn); - if (s.ok()) { - s = txn->Delete(cfh, key); - if (s.ok()) { - s = CommitTxn(txn, thread); - } - } - } - shared->Delete(rand_column_family, rand_key, false /* pending */); - thread->stats.AddDeletes(1); - if (!s.ok()) { - if (FLAGS_injest_error_severity >= 2) { - if (!is_db_stopped_ && - s.severity() >= Status::Severity::kFatalError) { - is_db_stopped_ = true; - } else if (!is_db_stopped_ || - s.severity() < Status::Severity::kFatalError) { - fprintf(stderr, "delete error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } else { - fprintf(stderr, "delete error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } - } else { - shared->SingleDelete(rand_column_family, rand_key, true /* pending */); - if (!FLAGS_use_txn) { - if (FLAGS_user_timestamp_size == 0) { - s = db_->SingleDelete(write_opts, cfh, key); - } else { - s = db_->SingleDelete(write_opts, cfh, key, write_ts); - } - } else { - Transaction* txn; - s = NewTxn(write_opts, &txn); - if (s.ok()) { - s = txn->SingleDelete(cfh, key); - if (s.ok()) { - s = CommitTxn(txn, thread); - } - } - } - shared->SingleDelete(rand_column_family, rand_key, false /* pending */); - thread->stats.AddSingleDeletes(1); - if (!s.ok()) { - if (FLAGS_injest_error_severity >= 2) { - if (!is_db_stopped_ && - s.severity() >= Status::Severity::kFatalError) { - is_db_stopped_ = true; - } else if (!is_db_stopped_ || - s.severity() < Status::Severity::kFatalError) { - fprintf(stderr, "single delete error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } else { - fprintf(stderr, "single delete error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } - } - return s; - } - - Status TestDeleteRange(ThreadState* thread, WriteOptions& write_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - // OPERATION delete range - std::vector> range_locks; - // delete range does not respect disallowed overwrites. the keys for - // which overwrites are disallowed are randomly distributed so it - // could be expensive to find a range where each key allows - // overwrites. - int64_t rand_key = rand_keys[0]; - int rand_column_family = rand_column_families[0]; - auto shared = thread->shared; - int64_t max_key = shared->GetMaxKey(); - if (rand_key > max_key - FLAGS_range_deletion_width) { - rand_key = - thread->rand.Next() % (max_key - FLAGS_range_deletion_width + 1); - } - for (int j = 0; j < FLAGS_range_deletion_width; ++j) { - if (j == 0 || - ((rand_key + j) & ((1 << FLAGS_log2_keys_per_lock) - 1)) == 0) { - range_locks.emplace_back(new MutexLock( - shared->GetMutexForKey(rand_column_family, rand_key + j))); - } - } - shared->DeleteRange(rand_column_family, rand_key, - rand_key + FLAGS_range_deletion_width, - true /* pending */); - - std::string keystr = Key(rand_key); - Slice key = keystr; - auto cfh = column_families_[rand_column_family]; - std::string end_keystr = Key(rand_key + FLAGS_range_deletion_width); - Slice end_key = end_keystr; - std::string write_ts_str; - Slice write_ts; - Status s; - if (FLAGS_user_timestamp_size) { - write_ts_str = GetNowNanos(); - write_ts = write_ts_str; - s = db_->DeleteRange(write_opts, cfh, key, end_key, write_ts); - } else { - s = db_->DeleteRange(write_opts, cfh, key, end_key); - } - if (!s.ok()) { - if (FLAGS_injest_error_severity >= 2) { - if (!is_db_stopped_ && s.severity() >= Status::Severity::kFatalError) { - is_db_stopped_ = true; - } else if (!is_db_stopped_ || - s.severity() < Status::Severity::kFatalError) { - fprintf(stderr, "delete range error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } else { - fprintf(stderr, "delete range error: %s\n", s.ToString().c_str()); - std::terminate(); - } - } - int covered = shared->DeleteRange(rand_column_family, rand_key, - rand_key + FLAGS_range_deletion_width, - false /* pending */); - thread->stats.AddRangeDeletions(1); - thread->stats.AddCoveredByRangeDeletions(covered); - return s; - } - - void TestIngestExternalFile(ThreadState* thread, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - const std::string sst_filename = - FLAGS_db + "/." + std::to_string(thread->tid) + ".sst"; - Status s; - if (db_stress_env->FileExists(sst_filename).ok()) { - // Maybe we terminated abnormally before, so cleanup to give this file - // ingestion a clean slate - s = db_stress_env->DeleteFile(sst_filename); - } - - SstFileWriter sst_file_writer(EnvOptions(options_), options_); - if (s.ok()) { - s = sst_file_writer.Open(sst_filename); - } - int64_t key_base = rand_keys[0]; - int column_family = rand_column_families[0]; - std::vector> range_locks; - range_locks.reserve(FLAGS_ingest_external_file_width); - std::vector keys; - keys.reserve(FLAGS_ingest_external_file_width); - std::vector values; - values.reserve(FLAGS_ingest_external_file_width); - SharedState* shared = thread->shared; - - assert(FLAGS_nooverwritepercent < 100); - // Grab locks, set pending state on expected values, and add keys - for (int64_t key = key_base; - s.ok() && key < shared->GetMaxKey() && - static_cast(keys.size()) < FLAGS_ingest_external_file_width; - ++key) { - if (key == key_base || - (key & ((1 << FLAGS_log2_keys_per_lock) - 1)) == 0) { - range_locks.emplace_back( - new MutexLock(shared->GetMutexForKey(column_family, key))); - } - if (!shared->AllowsOverwrite(key)) { - // We could alternatively include `key` on the condition its current - // value is `DELETION_SENTINEL`. - continue; - } - keys.push_back(key); - - uint32_t value_base = thread->rand.Next() % shared->UNKNOWN_SENTINEL; - values.push_back(value_base); - shared->Put(column_family, key, value_base, true /* pending */); - - char value[100]; - size_t value_len = GenerateValue(value_base, value, sizeof(value)); - auto key_str = Key(key); - s = sst_file_writer.Put(Slice(key_str), Slice(value, value_len)); - } - - if (s.ok() && keys.empty()) { - return; - } - - if (s.ok()) { - s = sst_file_writer.Finish(); - } - if (s.ok()) { - s = db_->IngestExternalFile(column_families_[column_family], - {sst_filename}, IngestExternalFileOptions()); - } - if (!s.ok()) { - fprintf(stderr, "file ingestion error: %s\n", s.ToString().c_str()); - std::terminate(); - } - for (size_t i = 0; i < keys.size(); ++i) { - shared->Put(column_family, keys[i], values[i], false /* pending */); - } - } - - // Given a key K, this creates an iterator which scans the range - // [K, K + FLAGS_num_iterations) forward and backward. - // Then does a random sequence of Next/Prev operations. - Status TestIterateAgainstExpected( - ThreadState* thread, const ReadOptions& read_opts, - const std::vector& rand_column_families, - const std::vector& rand_keys) override { - assert(thread); - assert(!rand_column_families.empty()); - assert(!rand_keys.empty()); - - auto shared = thread->shared; - assert(shared); - - int64_t max_key = shared->GetMaxKey(); - - const int64_t num_iter = static_cast(FLAGS_num_iterations); - - int64_t lb = rand_keys[0]; - if (lb > max_key - num_iter) { - lb = thread->rand.Next() % (max_key - num_iter + 1); - } - - const int64_t ub = lb + num_iter; - - // Lock the whole range over which we might iterate to ensure it doesn't - // change under us. - const int rand_column_family = rand_column_families[0]; - std::vector> range_locks = - shared->GetLocksForKeyRange(rand_column_family, lb, ub); - - ReadOptions ro(read_opts); - ro.total_order_seek = true; - - std::string read_ts_str; - Slice read_ts; - if (FLAGS_user_timestamp_size > 0) { - read_ts_str = GetNowNanos(); - read_ts = read_ts_str; - ro.timestamp = &read_ts; - } - - std::string max_key_str; - Slice max_key_slice; - if (!FLAGS_destroy_db_initially) { - max_key_str = Key(max_key); - max_key_slice = max_key_str; - // to restrict iterator from reading keys written in batched_op_stress - // that do not have expected state updated and may not be parseable by - // GetIntVal(). - ro.iterate_upper_bound = &max_key_slice; - } - - ColumnFamilyHandle* const cfh = column_families_[rand_column_family]; - assert(cfh); - - std::unique_ptr iter(db_->NewIterator(ro, cfh)); - - std::string op_logs; - - auto check_columns = [&]() { - assert(iter); - assert(iter->Valid()); - - if (!VerifyWideColumns(iter->value(), iter->columns())) { - shared->SetVerificationFailure(); - - fprintf(stderr, - "Verification failed for key %s: " - "Value and columns inconsistent: value: %s, columns: %s\n", - Slice(iter->key()).ToString(/* hex */ true).c_str(), - iter->value().ToString(/* hex */ true).c_str(), - WideColumnsToHex(iter->columns()).c_str()); - fprintf(stderr, "Column family: %s, op_logs: %s\n", - cfh->GetName().c_str(), op_logs.c_str()); - - thread->stats.AddErrors(1); - - return false; - } - - return true; - }; - - auto check_no_key_in_range = [&](int64_t start, int64_t end) { - for (auto j = std::max(start, lb); j < std::min(end, ub); ++j) { - auto expected_value = - shared->Get(rand_column_family, static_cast(j)); - if (expected_value != shared->DELETION_SENTINEL && - expected_value != shared->UNKNOWN_SENTINEL) { - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - if (iter->Valid()) { - fprintf(stderr, - "Expected state has key %s, iterator is at key %s\n", - Slice(Key(j)).ToString(true).c_str(), - iter->key().ToString(true).c_str()); - } else { - fprintf(stderr, "Expected state has key %s, iterator is invalid\n", - Slice(Key(j)).ToString(true).c_str()); - } - fprintf(stderr, "Column family: %s, op_logs: %s\n", - cfh->GetName().c_str(), op_logs.c_str()); - thread->stats.AddErrors(1); - return false; - } - } - return true; - }; - - // Forward and backward scan to ensure we cover the entire range [lb, ub). - // The random sequence Next and Prev test below tends to be very short - // ranged. - int64_t last_key = lb - 1; - - std::string key_str = Key(lb); - iter->Seek(key_str); - - op_logs += "S " + Slice(key_str).ToString(true) + " "; - - uint64_t curr = 0; - while (true) { - if (!iter->Valid()) { - if (!iter->status().ok()) { - thread->shared->SetVerificationFailure(); - fprintf(stderr, "TestIterate against expected state error: %s\n", - iter->status().ToString().c_str()); - fprintf(stderr, "Column family: %s, op_logs: %s\n", - cfh->GetName().c_str(), op_logs.c_str()); - thread->stats.AddErrors(1); - return iter->status(); - } - if (!check_no_key_in_range(last_key + 1, ub)) { - return Status::OK(); - } - break; - } - - if (!check_columns()) { - return Status::OK(); - } - - // iter is valid, the range (last_key, current key) was skipped - GetIntVal(iter->key().ToString(), &curr); - if (!check_no_key_in_range(last_key + 1, static_cast(curr))) { - return Status::OK(); - } - - last_key = static_cast(curr); - if (last_key >= ub - 1) { - break; - } - - iter->Next(); - - op_logs += "N"; - } - - // backward scan - key_str = Key(ub - 1); - iter->SeekForPrev(key_str); - - op_logs += " SFP " + Slice(key_str).ToString(true) + " "; - - last_key = ub; - while (true) { - if (!iter->Valid()) { - if (!iter->status().ok()) { - thread->shared->SetVerificationFailure(); - fprintf(stderr, "TestIterate against expected state error: %s\n", - iter->status().ToString().c_str()); - fprintf(stderr, "Column family: %s, op_logs: %s\n", - cfh->GetName().c_str(), op_logs.c_str()); - thread->stats.AddErrors(1); - return iter->status(); - } - if (!check_no_key_in_range(lb, last_key)) { - return Status::OK(); - } - break; - } - - if (!check_columns()) { - return Status::OK(); - } - - // the range (current key, last key) was skipped - GetIntVal(iter->key().ToString(), &curr); - if (!check_no_key_in_range(static_cast(curr + 1), last_key)) { - return Status::OK(); - } - - last_key = static_cast(curr); - if (last_key <= lb) { - break; - } - - iter->Prev(); - - op_logs += "P"; - } - - if (thread->rand.OneIn(2)) { - // Refresh after forward/backward scan to allow higher chance of SV - // change. It is safe to refresh since the testing key range is locked. - iter->Refresh(); - } - - // start from middle of [lb, ub) otherwise it is easy to iterate out of - // locked range - const int64_t mid = lb + num_iter / 2; - - key_str = Key(mid); - const Slice key(key_str); - - if (thread->rand.OneIn(2)) { - iter->Seek(key); - op_logs += " S " + key.ToString(true) + " "; - if (!iter->Valid() && iter->status().ok()) { - if (!check_no_key_in_range(mid, ub)) { - return Status::OK(); - } - } - } else { - iter->SeekForPrev(key); - op_logs += " SFP " + key.ToString(true) + " "; - if (!iter->Valid() && iter->status().ok()) { - // iterator says nothing <= mid - if (!check_no_key_in_range(lb, mid + 1)) { - return Status::OK(); - } - } - } - - for (int64_t i = 0; i < num_iter && iter->Valid(); ++i) { - if (!check_columns()) { - return Status::OK(); - } - - GetIntVal(iter->key().ToString(), &curr); - if (static_cast(curr) < lb) { - iter->Next(); - op_logs += "N"; - } else if (static_cast(curr) >= ub) { - iter->Prev(); - op_logs += "P"; - } else { - const uint32_t expected_value = - shared->Get(rand_column_family, static_cast(curr)); - if (expected_value == shared->DELETION_SENTINEL) { - // Fail fast to preserve the DB state. - thread->shared->SetVerificationFailure(); - fprintf(stderr, "Iterator has key %s, but expected state does not.\n", - iter->key().ToString(true).c_str()); - fprintf(stderr, "Column family: %s, op_logs: %s\n", - cfh->GetName().c_str(), op_logs.c_str()); - thread->stats.AddErrors(1); - break; - } - - if (thread->rand.OneIn(2)) { - iter->Next(); - op_logs += "N"; - if (!iter->Valid()) { - break; - } - uint64_t next = 0; - GetIntVal(iter->key().ToString(), &next); - if (!check_no_key_in_range(static_cast(curr + 1), - static_cast(next))) { - return Status::OK(); - } - } else { - iter->Prev(); - op_logs += "P"; - if (!iter->Valid()) { - break; - } - uint64_t prev = 0; - GetIntVal(iter->key().ToString(), &prev); - if (!check_no_key_in_range(static_cast(prev + 1), - static_cast(curr))) { - return Status::OK(); - } - } - } - } - - if (!iter->status().ok()) { - thread->shared->SetVerificationFailure(); - fprintf(stderr, "TestIterate against expected state error: %s\n", - iter->status().ToString().c_str()); - fprintf(stderr, "Column family: %s, op_logs: %s\n", - cfh->GetName().c_str(), op_logs.c_str()); - thread->stats.AddErrors(1); - return iter->status(); - } - - thread->stats.AddIterations(1); - - return Status::OK(); - } - - bool VerifyOrSyncValue(int cf, int64_t key, const ReadOptions& /*opts*/, - SharedState* shared, const std::string& value_from_db, - std::string msg_prefix, const Status& s, - bool strict = false) const { - if (shared->HasVerificationFailedYet()) { - return false; - } - - // compare value_from_db with the value in the shared state - uint32_t value_base = shared->Get(cf, key); - if (value_base == SharedState::UNKNOWN_SENTINEL) { - if (s.ok()) { - // Value exists in db, update state to reflect that - Slice slice(value_from_db); - value_base = GetValueBase(slice); - shared->Put(cf, key, value_base, false); - } else if (s.IsNotFound()) { - // Value doesn't exist in db, update state to reflect that - shared->SingleDelete(cf, key, false); - } - return true; - } - if (value_base == SharedState::DELETION_SENTINEL && !strict) { - return true; - } - - if (s.ok()) { - char value[kValueMaxLen]; - if (value_base == SharedState::DELETION_SENTINEL) { - VerificationAbort(shared, msg_prefix + ": Unexpected value found", cf, - key, value_from_db, ""); - return false; - } - size_t sz = GenerateValue(value_base, value, sizeof(value)); - if (value_from_db.length() != sz) { - VerificationAbort(shared, - msg_prefix + ": Length of value read is not equal", - cf, key, value_from_db, Slice(value, sz)); - return false; - } - if (memcmp(value_from_db.data(), value, sz) != 0) { - VerificationAbort(shared, - msg_prefix + ": Contents of value read don't match", - cf, key, value_from_db, Slice(value, sz)); - return false; - } - } else { - if (value_base != SharedState::DELETION_SENTINEL) { - char value[kValueMaxLen]; - size_t sz = GenerateValue(value_base, value, sizeof(value)); - VerificationAbort(shared, - msg_prefix + ": Value not found: " + s.ToString(), cf, - key, "", Slice(value, sz)); - return false; - } - } - return true; - } - - void PrepareTxnDbOptions(SharedState* shared, - TransactionDBOptions& txn_db_opts) override { - txn_db_opts.rollback_deletion_type_callback = - [shared](TransactionDB*, ColumnFamilyHandle*, const Slice& key) { - assert(shared); - uint64_t key_num = 0; - bool ok = GetIntVal(key.ToString(), &key_num); - assert(ok); - (void)ok; - return !shared->AllowsOverwrite(key_num); - }; - } -}; - -StressTest* CreateNonBatchedOpsStressTest() { - return new NonBatchedOpsStressTest(); -} - -} // namespace ROCKSDB_NAMESPACE -#endif // GFLAGS diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 3938549cb..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_STORE -_site/ -*.swo -*.swp -_site -.sass-cache -*.psd -*~ diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 827d1c0ed..000000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -rocksdb.org \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md deleted file mode 100644 index 2c5842fb4..000000000 --- a/docs/CONTRIBUTING.md +++ /dev/null @@ -1,115 +0,0 @@ -This provides guidance on how to contribute various content to `rocksdb.org`. - -## Getting started - -You should only have to do these one time. - -- Rename this file to `CONTRIBUTING.md`. -- Rename `EXAMPLE-README-FOR-RUNNING-DOCS.md` to `README.md` (replacing the existing `README.md` that came with the template). -- Rename `EXAMPLE-LICENSE` to `LICENSE`. -- Review the [template information](./TEMPLATE-INFORMATION.md). -- Review `./_config.yml`. -- Make sure you update `title`, `description`, `tagline` and `gacode` (Google Analytics) in `./_config.yml`. - -## Basic Structure - -Most content is written in markdown. You name the file `something.md`, then have a header that looks like this: - -``` ---- -docid: getting-started -title: Getting started with ProjectName -layout: docs -permalink: /docs/getting-started.html ---- -``` - -Customize these values for each document, blog post, etc. - -> The filename of the `.md` file doesn't actually matter; what is important is the `docid` being unique and the `permalink` correct and unique too). - -## Landing page - -Modify `index.md` with your new or updated content. - -If you want a `GridBlock` as part of your content, you can do so directly with HTML: - -``` -
-
-
-

Your Features

- -
-
- -
-
-

More information

-

- Stuff here -

-
-
-
-``` - -or with a combination of changing `./_data/features.yml` and adding some Liquid to `index.md`, such as: - -``` -{% include content/gridblocks.html data_source=site.data.features imagealign="bottom"%} -``` - -## Blog - -To modify a blog post, edit the appopriate markdown file in `./_posts/`. - -Adding a new blog post is a four-step process. - -> Some posts have a `permalink` and `comments` in the blog post YAML header. You will not need these for new blog posts. These are an artifact of migrating the blog from Wordpress to gh-pages. - -1. Create your blog post in `./_posts/` in markdown (file extension `.md` or `.markdown`). See current posts in that folder or `./doc-type-examples/2016-04-07-blog-post-example.md` for an example of the YAML format. **If the `./_posts` directory does not exist, create it**. - - You can add a `` tag in the middle of your post such that you show only the excerpt above that tag in the main `/blog` index on your page. -1. If you have not authored a blog post before, modify the `./_data/authors.yml` file with the `author` id you used in your blog post, along with your full name and Facebook ID to get your profile picture. -1. [Run the site locally](./README.md) to test your changes. It will be at `http://127.0.0.1/blog/your-new-blog-post-title.html` -1. Push your changes to GitHub. - -## Docs - -To modify docs, edit the appropriate markdown file in `./_docs/`. - -To add docs to the site.... - -1. Add your markdown file to the `./_docs/` folder. See `./doc-type-examples/docs-hello-world.md` for an example of the YAML header format. **If the `./_docs/` directory does not exist, create it**. - - You can use folders in the `./_docs/` directory to organize your content if you want. -1. Update `_data/nav_docs.yml` to add your new document to the navigation bar. Use the `docid` you put in your doc markdown in as the `id` in the `_data/nav_docs.yml` file. -1. [Run the site locally](./README.md) to test your changes. It will be at `http://127.0.0.1/docs/your-new-doc-permalink.html` -1. Push your changes to GitHub. - -## Header Bar - -To modify the header bar, change `./_data/nav.yml`. - -## Top Level Page - -To modify a top-level page, edit the appropriate markdown file in `./top-level/` - -If you want a top-level page (e.g., http://your-site.com/top-level.html) -- not in `/blog/` or `/docs/`.... - -1. Create a markdown file in the root `./top-level/`. See `./doc-type-examples/top-level-example.md` for more information. -1. If you want a visible link to that file, update `_data/nav.yml` to add a link to your new top-level document in the header bar. - - > This is not necessary if you just want to have a page that is linked to from another page, but not exposed as direct link to the user. - -1. [Run the site locally](./README.md) to test your changes. It will be at `http://127.0.0.1/your-top-level-page-permalink.html` -1. Push your changes to GitHub. - -## Other Changes - -- CSS: `./css/main.css` or `./_sass/*.scss`. -- Images: `./static/images/[docs | posts]/....` -- Main Blog post HTML: `./_includes/post.html` -- Main Docs HTML: `./_includes/doc.html` diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index dfb1cfdd4..000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source 'https://rubygems.org' -gem 'github-pages', '~> 227' - -gem "webrick", "~> 1.7" diff --git a/docs/LICENSE-DOCUMENTATION b/docs/LICENSE-DOCUMENTATION deleted file mode 100644 index 1f255c9f3..000000000 --- a/docs/LICENSE-DOCUMENTATION +++ /dev/null @@ -1,385 +0,0 @@ -Attribution 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More_considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution 4.0 International Public License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution 4.0 International Public License ("Public License"). To the -extent this Public License may be interpreted as a contract, You are -granted the Licensed Rights in consideration of Your acceptance of -these terms and conditions, and the Licensor grants You such rights in -consideration of benefits the Licensor receives from making the -Licensed Material available under these terms and conditions. - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - -b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - -c. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - -d. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - -e. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - -f. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - -g. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - -h. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - -i. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - -j. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - -k. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - -Section 2 -- Scope. - -a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - -b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - -a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - 4. If You Share Adapted Material You produce, the Adapter's - License You apply must not prevent recipients of the Adapted - Material from complying with this Public License. - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - -a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - -b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material; and - -c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - -a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - -b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - -c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - -Section 6 -- Term and Termination. - -a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - -b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - -c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - -d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - -Section 7 -- Other Terms and Conditions. - -a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - -b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - -Section 8 -- Interpretation. - -a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - -b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - -c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - -d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - -======================================================================= - -Creative Commons is not a party to its public licenses. -Notwithstanding, Creative Commons may elect to apply one of its public -licenses to material it publishes and in those instances will be -considered the "Licensor." Except for the limited purpose of indicating -that material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the public -licenses. - -Creative Commons may be contacted at creativecommons.org. - diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 0ae8978bc..000000000 --- a/docs/README.md +++ /dev/null @@ -1,80 +0,0 @@ -## User Documentation for rocksdb.org - -This directory will contain the user and feature documentation for RocksDB. The documentation will be hosted on GitHub pages. - -### Contributing - -See [CONTRIBUTING.md](./CONTRIBUTING.md) for details on how to add or modify content. - -### Run the Site Locally - -The requirements for running a GitHub pages site locally is described in [GitHub help](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/#requirements). The steps below summarize these steps. - -> If you have run the site before, you can start with step 1 and then move on to step 5. - -1. Ensure that you are in the `/docs` directory in your local RocksDB clone (i.e., the same directory where this `README.md` exists). The below RubyGems commands, etc. must be run from there. - -1. Make sure you have Ruby and [RubyGems](https://rubygems.org/) installed. - - > Ruby >= 2.2 is required for the gems. On the latest versions of Mac OS X, Ruby 2.0 is the - > default. Use `brew install ruby` (or your preferred upgrade mechanism) to install a newer - > version of Ruby for your Mac OS X system. - -1. Make sure you have [Bundler](http://bundler.io/) installed. - - ``` - # may require sudo - gem install bundler - ``` -1. Install the project's dependencies - - ``` - # run this in the 'docs' directory - bundle install - ``` - - > If you get an error when installing `nokogiri`, you may be running into the problem described - > in [this nokogiri issue](https://github.com/sparklemotion/nokogiri/issues/1483). You can - > either `brew uninstall xz` (and then `brew install xz` after the bundle is installed) or - > `xcode-select --install` (although this may not work if you have already installed command - > line tools). - -1. Run Jekyll's server. - - - On first runs or for structural changes to the documentation (e.g., new sidebar menu item), do a full build. - - ``` - bundle exec jekyll serve - ``` - - - For content changes only, you can use `--incremental` for faster builds. - - ``` - bundle exec jekyll serve --incremental - ``` - - > We use `bundle exec` instead of running straight `jekyll` because `bundle exec` will always use the version of Jekyll from our `Gemfile`. Just running `jekyll` will use the system version and may not necessarily be compatible. - - - To run using an actual IP address, you can use `--host=0.0.0.0` - - ``` - bundle exec jekyll serve --host=0.0.0.0 - ``` - - This will allow you to use the IP address associated with your machine in the URL. That way you could share it with other people. - - e.g., on a Mac, you can your IP address with something like `ifconfig | grep "inet " | grep -v 127.0.0.1`. - -1. Either of commands in the previous step will serve up the site on your local device at http://127.0.0.1:4000/ or http://localhost:4000. - -### Updating the Bundle - -The site depends on Github Pages and the installed bundle is based on the `github-pages` gem. -Occasionally that gem might get updated with new or changed functionality. If that is the case, -you can run: - -``` -bundle update -``` - -to get the latest packages for the installation. diff --git a/docs/TEMPLATE-INFORMATION.md b/docs/TEMPLATE-INFORMATION.md deleted file mode 100644 index 9175bc0c2..000000000 --- a/docs/TEMPLATE-INFORMATION.md +++ /dev/null @@ -1,17 +0,0 @@ -## Template Details - -First, go through `_config.yml` and adjust the available settings to your project's standard. When you make changes here, you'll have to kill the `jekyll serve` instance and restart it to see those changes, but that's only the case with the config file. - -Next, update some image assets - you'll want to update `favicon.png`, `logo.svg`, and `og_image.png` (used for Like button stories and Shares on Facbeook) in the `static` folder with your own logos. - -Next, if you're going to have docs on your site, keep the `_docs` and `docs` folders, if not, you can safely remove them (or you can safely leave them and not include them in your navigation - Jekyll renders all of this before a client views the site anyway, so there's no performance hit from just leaving it there for a future expansion). - -Same thing with a blog section, either keep or delete the `_posts` and `blog` folders. - -You can customize your homepage in three parts - the first in the homepage header, which is mostly automatically derived from the elements you insert into your config file. However, you can also specify a series of 'promotional' elements in `_data/promo.yml`. You can read that file for more information. - -The second place for your homepage is in `index.md` which contains the bulk of the main content below the header. This is all markdown if you want, but you can use HTML and Jekyll's template tags (called Liquid) in there too. Checkout this folder's index.md for an example of one common template tag that we use on our sites called gridblocks. - -The third and last place is in the `_data/powered_by.yml` and `_data/powered_by_highlight.yml` files. Both these files combine to create a section on the homepage that is intended to show a list of companies or apps that are using your project. The `powered_by_highlight` file is a list of curated companies/apps that you want to show as a highlight at the top of this section, including their logos in whatever format you want. The `powered_by` file is a more open list that is just text links to the companies/apps and can be updated via Pull Request by the community. If you don't want these sections on your homepage, just empty out both files and leave them blank. - -The last thing you'll want to do is setup your top level navigation bar. You can do this by editing `nav.yml` and keeping the existing title/href/category structure used there. Although the nav is responsive and fairly flexible design-wise, no more than 5 or 6 nav items is recommended. diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index a4055fd1f..000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Site settings -permalink: /blog/:year/:month/:day/:title.html -title: RocksDB -tagline: A persistent key-value store for fast storage environments -description: > - RocksDB is an embeddable persistent key-value store for fast storage. -fbappid: "1615782811974223" -gacode: "UA-49459723-1" -# baseurl determines the subpath of your site. For example if you're using an -# organisation.github.io/reponame/ basic site URL, then baseurl would be set -# as "/reponame" but leave blank if you have a top-level domain URL as it is -# now set to "" by default as discussed in: -# http://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/ -baseurl: "" - -# the base hostname & protocol for your site -# If baseurl is set, then the absolute url for your site would be url/baseurl -# This was also be set to the right thing automatically for local development -# https://github.com/blog/2277-what-s-new-in-github-pages-with-jekyll-3-3 -# http://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/ -url: "http://rocksdb.org" - -# Note: There are new filters in Jekyll 3.3 to help with absolute and relative urls -# absolute_url -# relative_url -# So you will see these used throughout the Jekyll code in this template. -# no more need for | prepend: site.url | prepend: site.baseurl -# http://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/ -#https://github.com/blog/2277-what-s-new-in-github-pages-with-jekyll-3-3 - -# The GitHub repo for your project -ghrepo: "facebook/rocksdb" - -# Use these color settings to determine your colour scheme for the site. -color: - # primary should be a vivid color that reflects the project's brand - primary: "#2a2a2a" - # secondary should be a subtle light or dark color used on page backgrounds - secondary: "#f9f9f9" - # Use the following to specify whether the previous two colours are 'light' - # or 'dark' and therefore what colors can be overlaid on them - primary-overlay: "dark" - secondary-overlay: "light" - -#Uncomment this if you want to enable Algolia doc search with your own values -#searchconfig: -# apikey: "" -# indexname: "" - -# Blog posts are builtin to Jekyll by default, with the `_posts` directory. -# Here you can specify other types of documentation. The names here are `docs` -# and `top-level`. This means their content will be in `_docs` and `_top-level`. -# The permalink format is also given. -# http://ben.balter.com/2015/02/20/jekyll-collections/ -collections: - docs: - output: true - permalink: /docs/:name/ - top-level: - output: true - permalink: :name.html - -# DO NOT ADJUST BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE CHANGING - -markdown: kramdown -kramdown: - input: GFM - syntax_highlighter: rouge - - syntax_highlighter_opts: - css_class: 'rougeHighlight' - span: - line_numbers: false - block: - line_numbers: true - start_line: 1 - -sass: - style: :compressed - -redcarpet: - extensions: [with_toc_data] - -plugins: - - jekyll-redirect-from diff --git a/docs/_data/authors.yml b/docs/_data/authors.yml deleted file mode 100644 index 210987c0b..000000000 --- a/docs/_data/authors.yml +++ /dev/null @@ -1,81 +0,0 @@ -icanadi: - full_name: Igor Canadi - fbid: 706165749 - -xjin: - full_name: Xing Jin - fbid: 100000739847320 - -leijin: - full_name: Lei Jin - fbid: 634570164 - -yhciang: - full_name: Yueh-Hsuan Chiang - fbid: 1619020986 - -radheshyam: - full_name: Radheshyam Balasundaram - fbid: 800837305 - -zagfox: - full_name: Feng Zhu - fbid: 100006493823622 - -lgalanis: - full_name: Leonidas Galanis - fbid: 8649950 - -sdong: - full_name: Siying Dong - fbid: 9805119 - -dmitrism: - full_name: Dmitri Smirnov - -rven2: - full_name: Venkatesh Radhakrishnan - fbid: 100008352697325 - -yiwu: - full_name: Yi Wu - fbid: 100000476362039 - -maysamyabandeh: - full_name: Maysam Yabandeh - fbid: 100003482360101 - -IslamAbdelRahman: - full_name: Islam AbdelRahman - fbid: 642759407 - -ajkr: - full_name: Andrew Kryczka - fbid: 568694102 - -abhimadan: - full_name: Abhishek Madan - fbid: 1850247869 - -sagar0: - full_name: Sagar Vemuri - fbid: 2419111 - -lightmark: - full_name: Aaron Gao - fbid: 1351549072 - -fgwu: - full_name: Fenggang Wu - fbid: 100002297362180 - -ltamasi: - full_name: Levi Tamasi - -cbi42: - full_name: Changyu Bi - fbid: 100078474793041 - -zjay: - full_name: Jay Zhuang - fbid: 100032386042884 diff --git a/docs/_data/features.yml b/docs/_data/features.yml deleted file mode 100644 index d692c1849..000000000 --- a/docs/_data/features.yml +++ /dev/null @@ -1,19 +0,0 @@ -- title: High Performance - text: | - RocksDB uses a log structured database engine, written entirely in C++, for maximum performance. Keys and values are just arbitrarily-sized byte streams. - image: images/promo-performance.svg - -- title: Optimized for Fast Storage - text: | - RocksDB is optimized for fast, low latency storage such as flash drives and high-speed disk drives. RocksDB exploits the full potential of high read/write rates offered by flash or RAM. - image: images/promo-flash.svg - -- title: Adaptable - text: | - RocksDB is adaptable to different workloads. From database storage engines such as [MyRocks](https://github.com/facebook/mysql-5.6) to [application data caching](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) to embedded workloads, RocksDB can be used for a variety of data needs. - image: images/promo-adapt.svg - -- title: Basic and Advanced Database Operations - text: | - RocksDB provides basic operations such as opening and closing a database, reading and writing to more advanced operations such as merging and compaction filters. - image: images/promo-operations.svg diff --git a/docs/_data/nav.yml b/docs/_data/nav.yml deleted file mode 100644 index b70c65ff7..000000000 --- a/docs/_data/nav.yml +++ /dev/null @@ -1,30 +0,0 @@ -- title: Docs - href: /docs/ - category: docs - -- title: GitHub - href: https://github.com/facebook/rocksdb/ - category: external - -- title: API (C++) - href: https://github.com/facebook/rocksdb/tree/main/include/rocksdb - category: external - -- title: API (Java) - href: https://github.com/facebook/rocksdb/tree/main/java/src/main/java/org/rocksdb - category: external - -- title: Support - href: /support.html - category: support - -- title: Blog - href: /blog/ - category: blog - -- title: Facebook - href: https://www.facebook.com/groups/rocksdb.dev/ - category: external - -# Use external for external links not associated with the paths of the current site. -# If a category is external, site urls, for example, are not prepended to the href, etc.. diff --git a/docs/_data/nav_docs.yml b/docs/_data/nav_docs.yml deleted file mode 100644 index 8cdfd2d04..000000000 --- a/docs/_data/nav_docs.yml +++ /dev/null @@ -1,3 +0,0 @@ -- title: Quick Start - items: - - id: getting-started diff --git a/docs/_data/powered_by.yml b/docs/_data/powered_by.yml deleted file mode 100644 index a780cfe40..000000000 --- a/docs/_data/powered_by.yml +++ /dev/null @@ -1 +0,0 @@ -# Fill in later if desired diff --git a/docs/_data/powered_by_highlight.yml b/docs/_data/powered_by_highlight.yml deleted file mode 100644 index a780cfe40..000000000 --- a/docs/_data/powered_by_highlight.yml +++ /dev/null @@ -1 +0,0 @@ -# Fill in later if desired diff --git a/docs/_data/promo.yml b/docs/_data/promo.yml deleted file mode 100644 index 9a72aa844..000000000 --- a/docs/_data/promo.yml +++ /dev/null @@ -1,6 +0,0 @@ -# This file determines the list of promotional elements added to the header of \ -# your site's homepage. Full list of plugins are shown - -- type: button - href: docs/getting-started.html - text: Get Started diff --git a/docs/_docs/faq.md b/docs/_docs/faq.md deleted file mode 100644 index 0887a0987..000000000 --- a/docs/_docs/faq.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -docid: support-faq -title: FAQ -layout: docs -permalink: /docs/support/faq.html ---- - -Here is an ever-growing list of frequently asked questions around RocksDB - -## What is RocksDB? - -RocksDB is an embeddable persistent key-value store for fast storage. RocksDB can also be the foundation for a client-server database but our current focus is on embedded workloads. - -RocksDB builds on [LevelDB](https://code.google.com/p/leveldb/) to be scalable to run on servers with many CPU cores, to efficiently use fast storage, to support IO-bound, in-memory and write-once workloads, and to be flexible to allow for innovation. - -For the latest details, watch [Mark Callaghan’s and Igor Canadi’s talk at CMU on 10/2015](https://scs.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=f4e0eb37-ae18-468f-9248-cb73edad3e56). [Dhruba Borthakur’s introductory talk](https://github.com/facebook/rocksdb/blob/gh-pages-old/intro.pdf?raw=true) from the Data @ Scale 2013 conference provides some perspective about how RocksDB has evolved. - -## How does performance compare? - -We benchmarked LevelDB and found that it was unsuitable for our server workloads. The [benchmark results](http://leveldb.googlecode.com/svn/trunk/doc/benchmark.html) look awesome at first sight, but we quickly realized that those results were for a database whose size was smaller than the size of RAM on the test machine – where the entire database could fit in the OS page cache. When we performed the same benchmarks on a database that was at least 5 times larger than main memory, the performance results were dismal. - -By contrast, we’ve published the [RocksDB benchmark results](https://github.com/facebook/rocksdb/wiki/Performance-Benchmarks) for server side workloads on Flash. We also measured the performance of LevelDB on these server-workload benchmarks and found that RocksDB solidly outperforms LevelDB for these IO bound workloads. We found that LevelDB’s single-threaded compaction process was insufficient to drive server workloads. We saw frequent write-stalls with LevelDB that caused 99-percentile latency to be tremendously large. We found that mmap-ing a file into the OS cache introduced performance bottlenecks for reads. We could not make LevelDB consume all the IOs offered by the underlying Flash storage. - -## What is RocksDB suitable for? - -RocksDB can be used by applications that need low latency database accesses. Possibilities include: - -* A user-facing application that stores the viewing history and state of users of a website. -* A spam detection application that needs fast access to big data sets. -* A graph-search query that needs to scan a data set in realtime. -* A cache data from Hadoop, thereby allowing applications to query Hadoop data in realtime. -* A message-queue that supports a high number of inserts and deletes. - -## How big is RocksDB adoption? - -RocksDB is an embedded storage engine that is used in a number of backend systems at Facebook. In the Facebook newsfeed’s backend, it replaced another internal storage engine called Centrifuge and is one of the many components used. ZippyDB, a distributed key value store service used by Facebook products relies RocksDB. Details on ZippyDB are in [Muthu Annamalai’s talk at Data@Scale in Seattle](https://youtu.be/DfiN7pG0D0k). Dragon, a distributed graph query engine part of the social graph infrastructure, is using RocksDB to store data. Parse has been running [MongoDB on RocksDB in production](http://blog.parse.com/announcements/mongodb-rocksdb-parse/) since early 2015. - -RocksDB is proving to be a useful component for a lot of other groups in the industry. For a list of projects currently using RocksDB, take a look at our USERS.md list on github. - -## How good is RocksDB as a database storage engine? - -Our engineering team at Facebook firmly believes that RocksDB has great potential as storage engine for databases. It has been proven in production with MongoDB: [MongoRocks](https://github.com/mongodb-partners/mongo-rocks) is the RocksDB based storage engine for MongoDB. - -[MyRocks](https://code.facebook.com/posts/190251048047090/myrocks-a-space-and-write-optimized-mysql-database/) is the RocksDB based storage engine for MySQL. Using RocksDB we have managed to achieve 2x better compression and 10x less write amplification for our benchmarks compared to our existing MySQL setup. Given our current results, work is currently underway to develop MyRocks into a production ready solution for web-scale MySQL workloads. Follow along on [GitHub](https://github.com/facebook/mysql-5.6)! - -## Why is RocksDB open sourced? - -We are open sourcing this project on [GitHub](http://github.com/facebook/rocksdb) because we think it will be useful beyond Facebook. We are hoping that software programmers and database developers will use, enhance, and customize RocksDB for their use-cases. We would also like to engage with the academic community on topics related to efficiency for modern database algorithms. diff --git a/docs/_docs/getting-started.md b/docs/_docs/getting-started.md deleted file mode 100644 index efd17c031..000000000 --- a/docs/_docs/getting-started.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -docid: getting-started -title: Getting started -layout: docs -permalink: /docs/getting-started.html ---- - -## Overview - -The RocksDB library provides a persistent key value store. Keys and values are arbitrary byte arrays. The keys are ordered within the key value store according to a user-specified comparator function. - -The library is maintained by the Facebook Database Engineering Team, and is based on [LevelDB](https://github.com/google/leveldb), by Sanjay Ghemawat and Jeff Dean at Google. - -This overview gives some simple examples of how RocksDB is used. For the story of why RocksDB was created in the first place, see [Dhruba Borthakur’s introductory talk](https://github.com/facebook/rocksdb/blob/gh-pages-old/intro.pdf?raw=true) from the Data @ Scale 2013 conference. - -## Opening A Database - -A rocksdb database has a name which corresponds to a file system directory. All of the contents of database are stored in this directory. The following example shows how to open a database, creating it if necessary: - -```c++ -#include -#include "rocksdb/db.h" - -rocksdb::DB* db; -rocksdb::Options options; -options.create_if_missing = true; -rocksdb::Status status = - rocksdb::DB::Open(options, "/tmp/testdb", &db); -assert(status.ok()); -... -``` - -If you want to raise an error if the database already exists, add the following line before the rocksdb::DB::Open call: - -```c++ -options.error_if_exists = true; -``` - -## Status - -You may have noticed the `rocksdb::Status` type above. Values of this type are returned by most functions in RocksDB that may encounter -an error. You can check if such a result is ok, and also print an associated error message: - -```c++ -rocksdb::Status s = ...; -if (!s.ok()) cerr << s.ToString() << endl; -``` - -## Closing A Database - -When you are done with a database, just delete the database object. For example: - -```c++ -/* open the db as described above */ -/* do something with db */ -delete db; -``` - -## Reads And Writes - -The database provides Put, Delete, and Get methods to modify/query the database. For example, the following code moves the value stored under `key1` to `key2`. - -```c++ -std::string value; -rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value); -if (s.ok()) s = db->Put(rocksdb::WriteOptions(), key2, value); -if (s.ok()) s = db->Delete(rocksdb::WriteOptions(), key1); -``` - -## Further documentation - -These are just simple examples of how RocksDB is used. The full documentation is currently on the [GitHub wiki](https://github.com/facebook/rocksdb/wiki). - -Here are some specific details about the RocksDB implementation: - -- [RocksDB Overview](https://github.com/facebook/rocksdb/wiki/RocksDB-Overview) -- [Immutable BlockBased Table file format](https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format) -- [Log file format](https://github.com/facebook/rocksdb/wiki/Write-Ahead-Log-File-Format) diff --git a/docs/_includes/blog_pagination.html b/docs/_includes/blog_pagination.html deleted file mode 100644 index 6a1f33436..000000000 --- a/docs/_includes/blog_pagination.html +++ /dev/null @@ -1,28 +0,0 @@ - -{% if paginator.total_pages > 1 %} -
- -
-{% endif %} diff --git a/docs/_includes/content/gridblocks.html b/docs/_includes/content/gridblocks.html deleted file mode 100644 index 49c5e5917..000000000 --- a/docs/_includes/content/gridblocks.html +++ /dev/null @@ -1,5 +0,0 @@ -
-{% for item in {{include.data_source}} %} - {% include content/items/gridblock.html item=item layout=include.layout imagealign=include.imagealign align=include.align %} -{% endfor %} -
\ No newline at end of file diff --git a/docs/_includes/content/items/gridblock.html b/docs/_includes/content/items/gridblock.html deleted file mode 100644 index 58c9e7fda..000000000 --- a/docs/_includes/content/items/gridblock.html +++ /dev/null @@ -1,37 +0,0 @@ -{% if include.layout == "fourColumn" %} - {% assign layout = "fourByGridBlock" %} -{% else %} - {% assign layout = "twoByGridBlock" %} -{% endif %} - -{% if include.imagealign == "side" %} - {% assign imagealign = "imageAlignSide" %} -{% else %} - {% if item.image %} - {% assign imagealign = "imageAlignTop" %} - {% else %} - {% assign imagealign = "" %} - {% endif %} -{% endif %} - -{% if include.align == "right" %} - {% assign align = "alignRight" %} -{% elsif include.align == "center" %} - {% assign align = "alignCenter" %} -{% else %} - {% assign align = "alignLeft" %} -{% endif %} - -
- {% if item.image %} -
- {{ item.title }} -
- {% endif %} -
-

{{ item.title }}

- {% if item.text %} - {{ item.text | markdownify }} - {% endif %} -
-
diff --git a/docs/_includes/doc.html b/docs/_includes/doc.html deleted file mode 100644 index 31e365ffe..000000000 --- a/docs/_includes/doc.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
-

{% if include.truncate %}{{ page.title }}{% else %}{{ page.title }}{% endif %}

-
- -
- {% if include.truncate %} - {% if page.content contains '' %} - {{ page.content | split:'' | first }} - - {% else %} - {{ page.content }} - {% endif %} - {% else %} - {{ content }} - -

Edit on GitHub

- {% endif %} -
- {% include doc_paging.html %} -
diff --git a/docs/_includes/doc_paging.html b/docs/_includes/doc_paging.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html deleted file mode 100644 index f560172d1..000000000 --- a/docs/_includes/footer.html +++ /dev/null @@ -1,34 +0,0 @@ -
- -
- diff --git a/docs/_includes/head.html b/docs/_includes/head.html deleted file mode 100644 index 10845ec1d..000000000 --- a/docs/_includes/head.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - {% if site.searchconfig %} - - {% endif %} - - {% if page.title %}{{ page.title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %} - - - - - diff --git a/docs/_includes/header.html b/docs/_includes/header.html deleted file mode 100644 index 8108d222b..000000000 --- a/docs/_includes/header.html +++ /dev/null @@ -1,19 +0,0 @@ -
-
-
- -

{{ site.title }}

-

{{ site.tagline }}

- -
-

{% if page.excerpt %}{{ page.excerpt | strip_html }}{% else %}{{ site.description }}{% endif %}

-
-
- {% for promo in site.data.promo %} - {% include plugins/{{promo.type}}.html button_href=promo.href button_text=promo.text %} -
- {% endfor %} -
-
-
-
diff --git a/docs/_includes/hero.html b/docs/_includes/hero.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/_includes/home_header.html b/docs/_includes/home_header.html deleted file mode 100644 index 90880d17c..000000000 --- a/docs/_includes/home_header.html +++ /dev/null @@ -1,22 +0,0 @@ -
-
-
-
-

{{ site.tagline }}

-
-

{% if page.excerpt %}{{ page.excerpt | strip_html }}{% else %}{{ site.description }}{% endif %}

-
-
- {% for promo in site.data.promo %} -
- {% include plugins/{{promo.type}}.html href=promo.href text=promo.text children=promo.children %} -
- {% endfor %} -
-
- -
-
-
diff --git a/docs/_includes/katex_import.html b/docs/_includes/katex_import.html deleted file mode 100644 index 6d6b7cf44..000000000 --- a/docs/_includes/katex_import.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/_includes/katex_render.html b/docs/_includes/katex_render.html deleted file mode 100644 index 56e2e8974..000000000 --- a/docs/_includes/katex_render.html +++ /dev/null @@ -1,210 +0,0 @@ - diff --git a/docs/_includes/nav.html b/docs/_includes/nav.html deleted file mode 100644 index 9c6fed06b..000000000 --- a/docs/_includes/nav.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
-
- - -

{{ site.title }}

-
- - - -
-
-
diff --git a/docs/_includes/nav/collection_nav.html b/docs/_includes/nav/collection_nav.html deleted file mode 100644 index a3c7a2dd3..000000000 --- a/docs/_includes/nav/collection_nav.html +++ /dev/null @@ -1,64 +0,0 @@ -
- -
- diff --git a/docs/_includes/nav/collection_nav_group.html b/docs/_includes/nav/collection_nav_group.html deleted file mode 100644 index b236ac5e3..000000000 --- a/docs/_includes/nav/collection_nav_group.html +++ /dev/null @@ -1,19 +0,0 @@ - \ No newline at end of file diff --git a/docs/_includes/nav/collection_nav_group_item.html b/docs/_includes/nav/collection_nav_group_item.html deleted file mode 100644 index fbb063deb..000000000 --- a/docs/_includes/nav/collection_nav_group_item.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_includes/nav/header_nav.html b/docs/_includes/nav/header_nav.html deleted file mode 100644 index 0fe945cdc..000000000 --- a/docs/_includes/nav/header_nav.html +++ /dev/null @@ -1,30 +0,0 @@ -
- - -
- \ No newline at end of file diff --git a/docs/_includes/nav_search.html b/docs/_includes/nav_search.html deleted file mode 100644 index 84956b9f7..000000000 --- a/docs/_includes/nav_search.html +++ /dev/null @@ -1,15 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/_includes/plugins/all_share.html b/docs/_includes/plugins/all_share.html deleted file mode 100644 index 59b00d615..000000000 --- a/docs/_includes/plugins/all_share.html +++ /dev/null @@ -1,3 +0,0 @@ -
- {% include plugins/like_button.html %}{% include plugins/twitter_share.html %}{% include plugins/google_share.html %} -
\ No newline at end of file diff --git a/docs/_includes/plugins/ascii_cinema.html b/docs/_includes/plugins/ascii_cinema.html deleted file mode 100644 index 7d3f97148..000000000 --- a/docs/_includes/plugins/ascii_cinema.html +++ /dev/null @@ -1,2 +0,0 @@ -
- \ No newline at end of file diff --git a/docs/_includes/plugins/button.html b/docs/_includes/plugins/button.html deleted file mode 100644 index 9e499fe3f..000000000 --- a/docs/_includes/plugins/button.html +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/docs/_includes/plugins/github_star.html b/docs/_includes/plugins/github_star.html deleted file mode 100644 index 6aea70fc7..000000000 --- a/docs/_includes/plugins/github_star.html +++ /dev/null @@ -1,4 +0,0 @@ -
- Star -
- \ No newline at end of file diff --git a/docs/_includes/plugins/github_watch.html b/docs/_includes/plugins/github_watch.html deleted file mode 100644 index 64233b57b..000000000 --- a/docs/_includes/plugins/github_watch.html +++ /dev/null @@ -1,4 +0,0 @@ -
- Watch -
- \ No newline at end of file diff --git a/docs/_includes/plugins/google_share.html b/docs/_includes/plugins/google_share.html deleted file mode 100644 index 1b557db86..000000000 --- a/docs/_includes/plugins/google_share.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
- - diff --git a/docs/_includes/plugins/iframe.html b/docs/_includes/plugins/iframe.html deleted file mode 100644 index 525b59f22..000000000 --- a/docs/_includes/plugins/iframe.html +++ /dev/null @@ -1,6 +0,0 @@ -
- -
-
- {% include plugins/button.html href=include.href text=include.text %} -
\ No newline at end of file diff --git a/docs/_includes/plugins/like_button.html b/docs/_includes/plugins/like_button.html deleted file mode 100644 index bcb8a7bee..000000000 --- a/docs/_includes/plugins/like_button.html +++ /dev/null @@ -1,18 +0,0 @@ -
- \ No newline at end of file diff --git a/docs/_includes/plugins/plugin_row.html b/docs/_includes/plugins/plugin_row.html deleted file mode 100644 index 800f50b82..000000000 --- a/docs/_includes/plugins/plugin_row.html +++ /dev/null @@ -1,5 +0,0 @@ -
-{% for child in include.children %} - {% include plugins/{{child.type}}.html href=child.href text=child.text %} -{% endfor %} -
\ No newline at end of file diff --git a/docs/_includes/plugins/post_social_plugins.html b/docs/_includes/plugins/post_social_plugins.html deleted file mode 100644 index a2ecb90ee..000000000 --- a/docs/_includes/plugins/post_social_plugins.html +++ /dev/null @@ -1,41 +0,0 @@ -
- -
-
- - - diff --git a/docs/_includes/plugins/slideshow.html b/docs/_includes/plugins/slideshow.html deleted file mode 100644 index 69fa2b300..000000000 --- a/docs/_includes/plugins/slideshow.html +++ /dev/null @@ -1,88 +0,0 @@ -
- - - \ No newline at end of file diff --git a/docs/_includes/plugins/twitter_follow.html b/docs/_includes/plugins/twitter_follow.html deleted file mode 100644 index b0f25dc60..000000000 --- a/docs/_includes/plugins/twitter_follow.html +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/docs/_includes/plugins/twitter_share.html b/docs/_includes/plugins/twitter_share.html deleted file mode 100644 index a60f2a8df..000000000 --- a/docs/_includes/plugins/twitter_share.html +++ /dev/null @@ -1,11 +0,0 @@ -
- -
- diff --git a/docs/_includes/post.html b/docs/_includes/post.html deleted file mode 100644 index 3ae0a2a80..000000000 --- a/docs/_includes/post.html +++ /dev/null @@ -1,40 +0,0 @@ -
-
-
- {% for author_idx in page.author %} -
- {% assign author = site.data.authors[author_idx] %} - {% if author.fbid %} -
- {{ author.fullname }} -
- {% endif %} - {% if author.full_name %} - - {% endif %} -
- {% endfor %} -
-

{% if include.truncate %}{{ page.title }}{% else %}{{ page.title }}{% endif %}

- -
-
- {% if include.truncate %} - {% if page.content contains '' %} - {{ page.content | split:'' | first | markdownify }} - - {% else %} - {{ page.content | markdownify }} - {% endif %} - {% else %} - {{ content }} - {% endif %} - {% unless include.truncate %} - {% include plugins/like_button.html %} - {% endunless %} -
-
diff --git a/docs/_includes/powered_by.html b/docs/_includes/powered_by.html deleted file mode 100644 index c629429cd..000000000 --- a/docs/_includes/powered_by.html +++ /dev/null @@ -1,28 +0,0 @@ -{% if site.data.powered_by.first.items or site.data.powered_by_highlight.first.items %} -
-
- {% if site.data.powered_by_highlight.first.title %} -

{{ site.data.powered_by_highlight.first.title }}

- {% else %} -

{{ site.data.powered_by.first.title }}

- {% endif %} - {% if site.data.powered_by_highlight.first.items %} -
- {% for item in site.data.powered_by_highlight.first.items %} -
- {{ item.name }} -
- {% endfor %} -
- {% endif %} -
- {% for item in site.data.powered_by.first.items %} - - {% endfor %} -
-
Does your app use {{ site.title }}? Add it to this list with a pull request!
-
-
-{% endif %} diff --git a/docs/_includes/social_plugins.html b/docs/_includes/social_plugins.html deleted file mode 100644 index 9b36580dc..000000000 --- a/docs/_includes/social_plugins.html +++ /dev/null @@ -1,31 +0,0 @@ - -
- -
- - - diff --git a/docs/_includes/ui/button.html b/docs/_includes/ui/button.html deleted file mode 100644 index 729ccc33b..000000000 --- a/docs/_includes/ui/button.html +++ /dev/null @@ -1 +0,0 @@ -{{ include.button_text }} \ No newline at end of file diff --git a/docs/_layouts/basic.html b/docs/_layouts/basic.html deleted file mode 100644 index 65bd21060..000000000 --- a/docs/_layouts/basic.html +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: doc_default ---- - -
-
-
- {{ content }} -
-
-
- diff --git a/docs/_layouts/blog.html b/docs/_layouts/blog.html deleted file mode 100644 index 1b0da4135..000000000 --- a/docs/_layouts/blog.html +++ /dev/null @@ -1,11 +0,0 @@ ---- -category: blog -layout: blog_default ---- - -
-
- {{ content }} -
-
- diff --git a/docs/_layouts/blog_default.html b/docs/_layouts/blog_default.html deleted file mode 100644 index a29d58d3d..000000000 --- a/docs/_layouts/blog_default.html +++ /dev/null @@ -1,14 +0,0 @@ - - - {% include head.html %} - - {% include nav.html alwayson=true %} - - - diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index 0167d9fd9..000000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,12 +0,0 @@ - - - {% include head.html %} - - {% include nav.html alwayson=true %} - - - - diff --git a/docs/_layouts/doc_default.html b/docs/_layouts/doc_default.html deleted file mode 100644 index 4a4139247..000000000 --- a/docs/_layouts/doc_default.html +++ /dev/null @@ -1,14 +0,0 @@ - - - {% include head.html %} - - {% include nav.html alwayson=true %} - - - diff --git a/docs/_layouts/doc_page.html b/docs/_layouts/doc_page.html deleted file mode 100644 index dba761e7d..000000000 --- a/docs/_layouts/doc_page.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: doc_default ---- - -
-
- {{ content }} -
-
- diff --git a/docs/_layouts/docs.html b/docs/_layouts/docs.html deleted file mode 100644 index 749dafabb..000000000 --- a/docs/_layouts/docs.html +++ /dev/null @@ -1,5 +0,0 @@ ---- -layout: doc_page ---- - -{% include doc.html %} \ No newline at end of file diff --git a/docs/_layouts/home.html b/docs/_layouts/home.html deleted file mode 100644 index b17732fa1..000000000 --- a/docs/_layouts/home.html +++ /dev/null @@ -1,26 +0,0 @@ - - - {% include head.html %} -
-
- Support Ukraine 🇺🇦 - - Help Provide Humanitarian Aid to Ukraine - - . -
-
- - {% include nav.html alwayson=true %} - - - diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html deleted file mode 100644 index bec36805b..000000000 --- a/docs/_layouts/page.html +++ /dev/null @@ -1,3 +0,0 @@ ---- -layout: blog ---- diff --git a/docs/_layouts/plain.html b/docs/_layouts/plain.html deleted file mode 100644 index fccc02ce1..000000000 --- a/docs/_layouts/plain.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: default ---- - -
-
- {{ content }} -
-
- diff --git a/docs/_layouts/post.html b/docs/_layouts/post.html deleted file mode 100644 index 4c92cf214..000000000 --- a/docs/_layouts/post.html +++ /dev/null @@ -1,8 +0,0 @@ ---- -collection: blog -layout: blog ---- - -
-{% include post.html %} -
\ No newline at end of file diff --git a/docs/_layouts/redirect.html b/docs/_layouts/redirect.html deleted file mode 100644 index c24f81748..000000000 --- a/docs/_layouts/redirect.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/_layouts/top-level.html b/docs/_layouts/top-level.html deleted file mode 100644 index fccc02ce1..000000000 --- a/docs/_layouts/top-level.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: default ---- - -
-
- {{ content }} -
-
- diff --git a/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown b/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown deleted file mode 100644 index f9e4a5444..000000000 --- a/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: How to backup RocksDB? -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/191/how-to-backup-rocksdb/ ---- - -In RocksDB, we have implemented an easy way to backup your DB. Here is a simple example: - - - - #include "rocksdb/db.h" - #include "utilities/backupable_db.h" - using namespace rocksdb; - - DB* db; - DB::Open(Options(), "/tmp/rocksdb", &db); - BackupableDB* backupable_db = new BackupableDB(db, BackupableDBOptions("/tmp/rocksdb_backup")); - backupable_db->Put(...); // do your thing - backupable_db->CreateNewBackup(); - delete backupable_db; // no need to also delete db - - - - -This simple example will create a backup of your DB in "/tmp/rocksdb_backup". Creating new BackupableDB consumes DB* and you should be calling all the DB methods on object `backupable_db` going forward. - -Restoring is also easy: - - - - RestoreBackupableDB* restore = new RestoreBackupableDB(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); - restore->RestoreDBFromLatestBackup("/tmp/rocksdb", "/tmp/rocksdb"); - delete restore; - - - - -This code will restore the backup back to "/tmp/rocksdb". The second parameter is the location of log files (In some DBs they are different from DB directory, but usually they are the same. See Options::wal_dir for more info). - -An alternative API for backups is to use BackupEngine directly: - - - - #include "rocksdb/db.h" - #include "utilities/backupable_db.h" - using namespace rocksdb; - - DB* db; - DB::Open(Options(), "/tmp/rocksdb", &db); - db->Put(...); // do your thing - BackupEngine* backup_engine = BackupEngine::NewBackupEngine(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); - backup_engine->CreateNewBackup(db); - delete db; - delete backup_engine; - - - - -Restoring with BackupEngine is similar to RestoreBackupableDB: - - - - BackupEngine* backup_engine = BackupEngine::NewBackupEngine(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); - backup_engine->RestoreDBFromLatestBackup("/tmp/rocksdb", "/tmp/rocksdb"); - delete backup_engine; - - - - -Backups are incremental. You can create a new backup with `CreateNewBackup()` and only the new data will be copied to backup directory (for more details on what gets copied, see "Under the hood"). Checksum is always calculated for any backuped file (including sst, log, and etc). It is used to make sure files are kept sound in the file system. Checksum is also verified for files from the previous backups even though they do not need to be copied. A checksum mismatch aborts the current backup (see "Under the hood" for more details). Once you have more backups saved, you can issue `GetBackupInfo()` call to get a list of all backups together with information on timestamp of the backup and the size (please note that sum of all backups' sizes is bigger than the actual size of the backup directory because some data is shared by multiple backups). Backups are identified by their always-increasing IDs. `GetBackupInfo()` is available both in `BackupableDB` and `RestoreBackupableDB`. - -You probably want to keep around only small number of backups. To delete old backups, just call `PurgeOldBackups(N)`, where N is how many backups you'd like to keep. All backups except the N newest ones will be deleted. You can also choose to delete arbitrary backup with call `DeleteBackup(id)`. - -`RestoreDBFromLatestBackup()` will restore the DB from the latest consistent backup. An alternative is `RestoreDBFromBackup()` which takes a backup ID and restores that particular backup. Checksum is calculated for any restored file and compared against the one stored during the backup time. If a checksum mismatch is detected, the restore process is aborted and `Status::Corruption` is returned. Very important thing to note here: Let's say you have backups 1, 2, 3, 4. If you restore from backup 2 and start writing more data to your database, newly created backup will delete old backups 3 and 4 and create new backup 3 on top of 2. - - - -## Advanced usage - - -Let's say you want to backup your DB to HDFS. There is an option in `BackupableDBOptions` to set `backup_env`, which will be used for all file I/O related to backup dir (writes when backuping, reads when restoring). If you set it to HDFS Env, all the backups will be stored in HDFS. - -`BackupableDBOptions::info_log` is a Logger object that is used to print out LOG messages if not-nullptr. - -If `BackupableDBOptions::sync` is true, we will sync data to disk after every file write, guaranteeing that backups will be consistent after a reboot or if machine crashes. Setting it to false will speed things up a bit, but some (newer) backups might be inconsistent. In most cases, everything should be fine, though. - -If you set `BackupableDBOptions::destroy_old_data` to true, creating new `BackupableDB` will delete all the old backups in the backup directory. - -`BackupableDB::CreateNewBackup()` method takes a parameter `flush_before_backup`, which is false by default. When `flush_before_backup` is true, `BackupableDB` will first issue a memtable flush and only then copy the DB files to the backup directory. Doing so will prevent log files from being copied to the backup directory (since flush will delete them). If `flush_before_backup` is false, backup will not issue flush before starting the backup. In that case, the backup will also include log files corresponding to live memtables. Backup will be consistent with current state of the database regardless of `flush_before_backup` parameter. - - - -## Under the hood - - -`BackupableDB` implements `DB` interface and adds four methods to it: `CreateNewBackup()`, `GetBackupInfo()`, `PurgeOldBackups()`, `DeleteBackup()`. Any `DB` interface calls will get forwarded to underlying `DB` object. - -When you call `BackupableDB::CreateNewBackup()`, it does the following: - - - - - - 1. Disable file deletions - - - - 2. Get live files (this includes table files, current and manifest file). - - - - 3. Copy live files to the backup directory. Since table files are immutable and filenames unique, we don't copy a table file that is already present in the backup directory. For example, if there is a file `00050.sst` already backed up and `GetLiveFiles()` returns `00050.sst`, we will not copy that file to the backup directory. However, checksum is calculated for all files regardless if a file needs to be copied or not. If a file is already present, the calculated checksum is compared against previously calculated checksum to make sure nothing crazy happened between backups. If a mismatch is detected, backup is aborted and the system is restored back to the state before `BackupableDB::CreateNewBackup()` is called. One thing to note is that a backup abortion could mean a corruption from a file in backup directory or the corresponding live file in current DB. Both manifest and current files are copied, since they are not immutable. - - - - 4. If `flush_before_backup` was set to false, we also need to copy log files to the backup directory. We call `GetSortedWalFiles()` and copy all live files to the backup directory. - - - - 5. Enable file deletions - - - - -Backup IDs are always increasing and we have a file `LATEST_BACKUP` that contains the ID of the latest backup. If we crash in middle of backing up, on a restart we will detect that there are newer backup files than `LATEST_BACKUP` claims there are. In that case, we will delete any backup newer than `LATEST_BACKUP` and clean up all the files since some of the table files might be corrupted. Having corrupted table files in the backup directory is dangerous because of our deduplication strategy. - - - -## Further reading - - -For the API details, see `include/utilities/backupable_db.h`. For the implementation, see `utilities/backupable/backupable_db.cc`. diff --git a/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown b/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown deleted file mode 100644 index 89ffb2d97..000000000 --- a/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: How to persist in-memory RocksDB database? -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/245/how-to-persist-in-memory-rocksdb-database/ ---- - -In recent months, we have focused on optimizing RocksDB for in-memory workloads. With growing RAM sizes and strict low-latency requirements, lots of applications decide to keep their entire data in memory. Running in-memory database with RocksDB is easy -- just mount your RocksDB directory on tmpfs or ramfs [1]. Even if the process crashes, RocksDB can recover all of your data from in-memory filesystem. However, what happens if the machine reboots? - - - -In this article we will explain how you can recover your in-memory RocksDB database even after a machine reboot. - -Every update to RocksDB is written to two places - one is an in-memory data structure called memtable and second is write-ahead log. Write-ahead log can be used to completely recover the data in memtable. By default, when we flush the memtable to table file, we also delete the current log, since we don't need it anymore for recovery (the data from the log is "persisted" in the table file -- we say that the log file is obsolete). However, if your table file is stored in in-memory file system, you may need the obsolete write-ahead log to recover the data after the machine reboots. Here's how you can do that. - -Options::wal_dir is the directory where RocksDB stores write-ahead log files. If you configure this directory to be on flash or disk, you will not lose current log file on machine reboot. -Options::WAL_ttl_seconds is the timeout when we delete the archived log files. If the timeout is non-zero, obsolete log files will be moved to `archive/` directory under Options::wal_dir. Those archived log files will only be deleted after the specified timeout. - -Let's assume Options::wal_dir is a directory on persistent storage and Options::WAL_ttl_seconds is set to one day. To fully recover the DB, we also need to backup the current snapshot of the database (containing table and metadata files) with a frequency of less than one day. RocksDB provides an utility that enables you to easily backup the snapshot of your database. You can learn more about it here: [How to backup RocksDB?](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F) - -You should configure the backup process to avoid backing up log files, since they are already stored in persistent storage. To do that, set BackupableDBOptions::backup_log_files to false. - -Restore process by default cleans up entire DB and WAL directory. Since we didn't include log files in the backup, we need to make sure that restoring the database doesn't delete log files in WAL directory. When restoring, configure RestoreOptions::keep_log_file to true. That option will also move any archived log files back to WAL directory, enabling RocksDB to replay all archived log files and rebuild the in-memory database state. - -To reiterate, here's what you have to do: - - - - - * Set DB directory to tmpfs or ramfs mounted drive - - - - * Set Options::wal_log to a directory on persistent storage - - - - * Set Options::WAL_ttl_seconds to T seconds - - - - * Backup RocksDB every T/2 seconds, with BackupableDBOptions::backup_log_files = false - - - - * When you lose data, restore from backup with RestoreOptions::keep_log_file = true - - - - - -[1] You might also want to consider using [PlainTable format](https://github.com/facebook/rocksdb/wiki/PlainTable-Format) for table files diff --git a/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown b/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown deleted file mode 100644 index 7ccbdbaad..000000000 --- a/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: The 1st RocksDB Local Meetup Held on March 27, 2014 -layout: post -author: xjin -category: blog -redirect_from: - - /blog/323/the-1st-rocksdb-local-meetup-held-on-march-27-2014/ ---- - -On Mar 27, 2014, RocksDB team @ Facebook held the 1st RocksDB local meetup in FB HQ (Menlo Park, California). We invited around 80 guests from 20+ local companies, including LinkedIn, Twitter, Dropbox, Square, Pinterest, MapR, Microsoft and IBM. Finally around 50 guests showed up, totaling around 60% show-up rate. - - - -[![Resize of 20140327_200754](/static/images/Resize-of-20140327_200754-300x225.jpg)](/static/images/Resize-of-20140327_200754-300x225.jpg) - -RocksDB team @ Facebook gave four talks about the latest progress and experience on RocksDB: - - - - - * [Supporting a 1PB In-Memory Workload](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Haobo-RocksDB-In-Memory.pdf) - - - - - * [Column Families in RocksDB](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Igor-Column-Families.pdf) - - - - - * ["Lockless" Get() in RocksDB?](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf) - - - - - * [Prefix Hashing in RocksDB](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Siying-Prefix-Hash.pdf) - - -A very interesting question asked by a massive number of guests is: does RocksDB plan to provide replication functionality? Obviously, many applications need a resilient and distributed storage solution, not just single-node storage. We are considering how to approach this issue. - -When will be the next meetup? We haven't decided yet. We will see whether the community is interested in it and how it can help RocksDB grow. - -If you have any questions or feedback for the meetup or RocksDB, please let us know in [our Facebook group](https://www.facebook.com/groups/rocksdb.dev/). - -### Comments - -**[Rajiv](geetasen@gmail.com)** - -Have any of these talks been recorded and if so will they be published? - -**[Igor Canadi](icanadi@fb.com)** - -Yes, I think we plan to publish them soon. diff --git a/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown b/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown deleted file mode 100644 index 7be7842a5..000000000 --- a/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: RocksDB 2.8 release -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/371/rocksdb-2-8-release/ ---- - -Check out the new RocksDB 2.8 release on [Github](https://github.com/facebook/rocksdb/releases/tag/2.8.fb). - -RocksDB 2.8. is mostly focused on improving performance for in-memory workloads. We are seeing read QPS as high as 5M (we will write a separate blog post on this). - - - -Here is the summary of new features: - - * Added a new table format called PlainTable, which is optimized for RAM storage (ramfs or tmpfs). You can read more details about it on [our wiki](https://github.com/facebook/rocksdb/wiki/PlainTable-Format). - - - * New prefixed memtable format HashLinkedList, which is optimized for cases where there are only a few keys for each prefix. - - - * Merge operator supports a new function PartialMergeMulti() that allows users to do partial merges against multiple operands. This function enables big speedups for workloads that use merge operators. - - - * Added a V2 compaction filter interface. It buffers the kv-pairs sharing the same key prefix, process them in batches, and return the batched results back to DB. - - - * Geo-spatial support for locations and radial-search. - - - * Improved read performance using thread local cache for frequently accessed data. - - - * Stability improvements -- we're now ignoring partially written tailing record to MANIFEST or WAL files. - - - -We have also introduced small incompatible API changes (mostly for advanced users). You can see full release notes in our [HISTORY.my](https://github.com/facebook/rocksdb/blob/2.8.fb/HISTORY.md) file. diff --git a/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown b/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown deleted file mode 100644 index 368055d2c..000000000 --- a/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Indexing SST Files for Better Lookup Performance -layout: post -author: leijin -category: blog -redirect_from: - - /blog/431/indexing-sst-files-for-better-lookup-performance/ ---- - -For a `Get()` request, RocksDB goes through mutable memtable, list of immutable memtables, and SST files to look up the target key. SST files are organized in levels. - -On level 0, files are sorted based on the time they are flushed. Their key range (as defined by FileMetaData.smallest and FileMetaData.largest) are mostly overlapped with each other. So it needs to look up every L0 file. - - - -Compaction is scheduled periodically to pick up files from an upper level and merges them with files from lower level. As a result, key/values are moved from L0 down the LSM tree gradually. Compaction sorts key/values and split them into files. From level 1 and below, SST files are sorted based on key. Their key range are mutually exclusive. Instead of scanning through each SST file and checking if a key falls into its range, RocksDB performs a binary search based on FileMetaData.largest to locate a candidate file that can potentially contain the target key. This reduces complexity from O(N) to O(log(N)). However, log(N) can still be large for bottom levels. For a fan-out ratio of 10, level 3 can have 1000 files. That requires 10 comparisons to locate a candidate file. This is a significant cost for an in-memory database when you can do [several million gets per second](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks). - -One observation to this problem is that: after the LSM tree is built, an SST file's position in its level is fixed. Furthermore, its order relative to files from the next level is also fixed. Based on this idea, we can perform [fractional cascading](http://en.wikipedia.org/wiki/Fractional_cascading) kind of optimization to narrow down the binary search range. Here is an example: - -[![tree_example](/static/images/tree_example1.png)](/static/images/tree_example1.png) - -Level 1 has 2 files and level 2 has 8 files. Now, we want to look up key 80. A binary search based FileMetaData.largest tells you file 1 is the candidate. Then key 80 is compared with its FileMetaData.smallest and FileMetaData.largest to decide if it falls into the range. The comparison shows 80 is less than FileMetaData.smallest (100), so file 1 does not possibly contain key 80. We to proceed to check level 2. Usually, we need to do binary search among all 8 files on level 2. But since we already know target key 80 is less than 100 and only file 1 to file 3 can contain key less than 100, we can safely exclude other files from the search. As a result we cut down the search space from 8 files to 3 files. - -Let's look at another example. We want to get key 230. A binary search on level 1 locates to file 2 (this also implies key 230 is larger than file 1's FileMetaData.largest 200). A comparison with file 2's range shows the target key is smaller than file 2's FileMetaData.smallest 300. Even though, we couldn't find key on level 1, we have derived hints that target key is in range between 200 and 300. Any files on level 2 that cannot overlap with [200, 300] can be safely excluded. As a result, we only need to look at file 5 and file 6 on level 2. - -Inspired by this concept, we pre-build pointers at compaction time on level 1 files that point to a range of files on level 2. For example, file 1 on level 1 points to file 3 (on level 2) on the left and file 4 on the right. File 2 will point to level 2 files 6 and 7. At query time, these pointers are used to determine the actual binary search range based on comparison result. - -Our benchmark shows that this optimization improves lookup QPS by ~5% for similar setup mentioned [here](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks). diff --git a/docs/_posts/2014-05-14-lock.markdown b/docs/_posts/2014-05-14-lock.markdown deleted file mode 100644 index 12009cc88..000000000 --- a/docs/_posts/2014-05-14-lock.markdown +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Reducing Lock Contention in RocksDB -layout: post -author: sdong -category: blog -redirect_from: - - /blog/521/lock/ ---- - -In this post, we briefly introduce the recent improvements we did to RocksDB to improve the issue of lock contention costs. - -RocksDB has a simple thread synchronization mechanism (See [RocksDB Architecture Guide](https://github.com/facebook/rocksdb/wiki/Rocksdb-Architecture-Guide)  to understand terms used below, like SST tables or mem tables). SST tables are immutable after being written and mem tables are lock-free data structures supporting single writer and multiple readers. There is only one single major lock, the DB mutex (DBImpl.mutex_) protecting all the meta operations, including: - - - - * Increase or decrease reference counters of mem tables and SST tables - - - * Change and check meta data structures, before and after finishing compactions, flushes and new mem table creations - - - * Coordinating writers - - -This DB mutex used to be scalability bottleneck preventing us from scaling to more than 16 threads. To address the issue, we improved RocksDB in several ways. - -1. Consolidate reference counters and introduce "super version". For every read operation, mutex was acquired, and reference counters for each mem table and each SST table were increased. One such operation is not expensive but if you are building a high throughput server with lots of reads, the lock contention will become the bottleneck. This is especially true if you store all your data in RAM. - -To solve this problem, we created a meta-meta data structure called “[super version](https://reviews.facebook.net/rROCKSDB1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c)”, which holds reference counters to all those mem table and SST tables, so that readers only need to increase the reference counters for this single data structure. In RocksDB, list of live mem tables and SST tables only changes infrequently, which would happen when new mem tables are created or flush/compaction happens. Now, at those times, a new super version is created with their reference counters increased. A super version lists live mem tables and SST tables so a reader only needs acquire the lock in order to find the latest super version and increase its reference counter. From the super version, the reader can find all the mem and SST tables which are safety accessible as long as the reader holds the reference count for the super version. - -2. We replace some reference counters to stc::atomic objects, so that decreasing reference count of an object usually doesn’t need to be inside the mutex any more. - -3. Make fetching super version and reference counting lock-free in read queries. After consolidating reference counting to one single super version and removing the locking for decreasing reference counts, in read case, we only acquire mutex for one thing: fetch the latest super version and increase the reference count for that (dereference the counter is done in an atomic decrease). We designed and implemented a (mostly) lock-free approach to do it. See [details](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf). We will write a separate blog post for that. - -4. Avoid disk I/O inside the mutex. As we know, each disk I/O to hard drives takes several milliseconds. It can be even longer if file system journal is involved or I/Os are queued. Even occasional disk I/O within mutex can cause huge performance outliers. -We identified in two situations, we might do disk I/O inside mutex and we removed them: -(1) Opening and closing transactional log files. We moved those operations out of the mutex. -(2) Information logging. In multiple places we write to logs within mutex. There is a chance that file write will wait for disk I/O to finish before finishing, even if fsync() is not issued, especially in EXT systems. We occasionally see 100+ milliseconds write() latency on EXT. Instead of removing those logging, we came up with a solution of delay logging. When inside mutex, instead of directly writing to the log file, we write to a log buffer, with the timing information. As soon as mutex is released, we flush the log buffer to log files. - -5. Reduce object creation inside the mutex. -Object creation can be slow because it involves malloc (in our case). Malloc sometimes is slow because it needs to lock some shared data structures. Allocating can also be slow because we sometimes do expensive operations in some of our classes' constructors. For these reasons, we try to reduce object creations inside the mutex. Here are two examples: - -(1) std::vector uses malloc inside. We introduced “[autovector](https://reviews.facebook.net/rROCKSDBc01676e46d3be08c3c140361ef1f5884f47d3b3c)” data structure, in which memory for first a few elements are pre-allocated as members of the autovector class. When an autovector is used as a stack variable, no malloc will be needed unless the pre-allocated buffer is used up. This autovector is quite useful for manipulating those meta data structures. Those meta operations are often locked inside DB mutex. - -(2) When building an iterator, we used to creating iterator of every live men table and SST table within the mutex and a merging iterator on top of them. Besides malloc, some of those iterators can be quite expensive to create, like sorting. Now, instead of doing that, we simply increase the reference counters of them, and release the mutex before creating any iterator. - -6. Deal with mutexes in LRU caches. -When I said there was only one single major lock, I was lying. In RocksDB, all LRU caches had exclusive mutexes within to protect writes to the LRU lists, which are done in both of read and write operations. LRU caches are used in block cache and table cache. Both of them are accessed more frequently than DB data structures. Lock contention of these two locks are as intense as the DB mutex. Even if LRU cache is sharded into ShardedLRUCache, we can still see lock contentions, especially table caches. We further address this issue in two way: -(1) Bypassing table caches. A table cache maintains list of SST table’s read handlers. Those handlers contain SST files’ descriptors, table metadata, and possibly data indexes, as well as bloom filters. When the table handler needs to be evicted based on LRU, those information is cleared. When the SST table needs to be read and its table handler is not in LRU cache, the table is opened and those metadata is loaded. In some cases, users want to tune the system in a way that table handler evictions should never happen. It is common for high-throughput, low-latency servers. We introduce a mode where table cache is bypassed in read queries. In this mode, all table handlers are cached and accessed directly, so there is no need to query and adjust table caches for reading the database. It is the users’ responsibility to reserve enough resource for it. This mode can be turned on by setting options.max_open_files=-1. - -(2) [New PlainTable format](//github.com/facebook/rocksdb/wiki/PlainTable-Format) (optimized for SST in ramfs/tmpfs) does not organize data by blocks. Data are located by memory addresses so no block cache is needed. - -With all of those improvements, lock contention is not a bottleneck anymore, which is shown in our [memory-only benchmark](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks) . Furthermore, lock contentions are not causing some huge (50 milliseconds+) latency outliers they used to cause. - -### Comments - -**[Lee Hounshell](lee@apsalar.com)** - -Please post an example of reading the same rocksdb concurrently. - -We are using the latest 3.0 rocksdb; however, when two separate processes -try and open the same rocksdb for reading, only one of the open requests -succeed. The other open always fails with “db/LOCK: Resource temporarily unavailable” So far we have not found an option that allows sharing the rocksdb for reads. An example would be most appreciated. - -**[Siying Dong](siying.d@fb.com)** - -Sorry for the delay. We don’t have feature support for this scenario yet. Here is an example you can work around this problem. You can build a snapshot of the DB by doing this: - -1. create a separate directory on the same host for a snapshot of the DB. -1. call `DB::DisableFileDeletions()` -1. call `DB::GetLiveFiles()` to get a full list of the files. -1. for all the files except manifest, add a hardlink file in your new directory pointing to the original file -1. copy the manifest file and truncate the size (you can read the comments of `DB::GetLiveFiles()` for more information) -1. call `DB::EnableFileDeletions()` -1. now you can open the snapshot directory in another process to access those files. Please remember to delete the directory after reading the data to allow those files to be recycled. - -By the way, the best way to ask those questions is in our [facebook group](https://www.facebook.com/groups/rocksdb.dev/). Let us know if you need any further help. - -**[Darshan](darshan.ghumare@gmail.com)** - -Will this consistency problem of RocksDB all occurs in case of single put/write? -What all ACID properties is supported by RocksDB, only durability irrespective of single or batch write? - -**[Siying Dong](siying.d@fb.com)** - -We recently [introduced optimistic transaction](https://reviews.facebook.net/D33435) which can help you ensure all of ACID. - -This blog post is mainly about optimizations in implementation. The RocksDB consistency semantic is not changed. diff --git a/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown b/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown deleted file mode 100644 index 61c90dc93..000000000 --- a/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: RocksDB 3.0 release -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/557/rocksdb-3-0-release/ ---- - -Check out new RocksDB release on [Github](https://github.com/facebook/rocksdb/releases/tag/3.0.fb)! - -New features in RocksDB 3.0: - - * [Column Family support](https://github.com/facebook/rocksdb/wiki/Column-Families) - - - * [Ability to chose different checksum function](https://github.com/facebook/rocksdb/commit/0afc8bc29a5800e3212388c327c750d32e31f3d6) - - - * Deprecated ReadOptions::prefix_seek and ReadOptions::prefix - - - -Check out the full [change log](https://github.com/facebook/rocksdb/blob/3.0.fb/HISTORY.md). diff --git a/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown b/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown deleted file mode 100644 index 30156742b..000000000 --- a/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: RocksDB 3.1 release -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/575/rocksdb-3-1-release/ ---- - -Check out the new release on [Github](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.1)! - -New features in RocksDB 3.1: - - * [Materialized hash index](https://github.com/facebook/rocksdb/commit/0b3d03d026a7248e438341264b4c6df339edc1d7) - - - * [FIFO compaction style](https://github.com/facebook/rocksdb/wiki/FIFO-compaction-style) - - -We released 3.1 so fast after 3.0 because one of our internal customers needed materialized hash index. diff --git a/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown b/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown deleted file mode 100644 index 6a641f233..000000000 --- a/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: PlainTable — A New File Format -layout: post -author: sdong -category: blog -redirect_from: - - /blog/599/plaintable-a-new-file-format/ ---- - -In this post, we are introducing "PlainTable" -- a file format we designed for RocksDB, initially to satisfy a production use case at Facebook. - -Design goals: - -1. All data stored in memory, in files stored in tmpfs/ramfs. Support DBs larger than 100GB (may be sharded across multiple RocksDB instance). -1. Optimize for [prefix hashing](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Siying-Prefix-Hash.pdf) -1. Less than or around 1 micro-second average latency for single Get() or Seek(). -1. Minimize memory consumption. -1. Queries efficiently return empty results - - - -Notice that our priority was not to maximize query performance, but to strike a balance between query performance and memory consumption. PlainTable query performance is not as good as you would see with a nicely-designed hash table, but they are of the same order of magnitude, while keeping memory overhead to a minimum. - -Since we are targeting micro-second latency, it is on the level of the number of CPU cache misses (if they cannot be parallellized, which are usually the case for index look-ups). On our target hardware with Intel CPUs of multiple sockets with NUMA, we can only allow 4-5 CPU cache misses (including costs of data TLB). - -To meet our requirements, given that only hash prefix iterating is needed, we made two decisions: - -1. to use a hash index, which is -1. directly addressed to rows, with no block structure. - -Having addressed our latency goal, the next task was to design a very compact hash index to minimize memory consumption. Some tricks we used to meet this goal: - -1. We only use 32-bit integers for data and index offsets.The first bit serves as a flag, so we can avoid using 8-byte pointers. -1. We never copy keys or parts of keys to index search structures. We store only offsets from which keys can be retrieved, to make comparisons with search keys. -1. Since our file is immutable, we can accurately estimate the number of hash buckets needed. - -To make sure the format works efficiently with empty queries, we added a bloom filter check before the query. This adds only one cache miss for non-empty cases [1], but avoids multiple cache misses for most empty results queries. This is a good trade-off for use cases with a large percentage of empty results. - -These are the design goals and basic ideas of PlainTable file format. For detailed information, see [this wiki page](https://github.com/facebook/rocksdb/wiki/PlainTable-Format). - -[1] Bloom filter checks typically require multiple memory access. However, because they are independent, they usually do not make the CPU pipeline stale. In any case, we improved the bloom filter to improve data locality - we may cover this further in a future blog post. - -### Comments - -**[Siying Dong](siying.d@fb.com)** - -Does [http://rocksdb.org/feed/](http://rocksdb.org/feed/) work? diff --git a/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown b/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown deleted file mode 100644 index 4411c7ae3..000000000 --- a/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: Avoid Expensive Locks in Get() -layout: post -author: leijin -category: blog -redirect_from: - - /blog/677/avoid-expensive-locks-in-get/ ---- - -As promised in the previous [blog post](blog/2014/05/14/lock.html)! - -RocksDB employs a multiversion concurrency control strategy. Before reading data, it needs to grab the current version, which is encapsulated in a data structure called [SuperVersion](https://reviews.facebook.net/rROCKSDB1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c). - - - -At the beginning of `GetImpl()`, it used to do this: - - - mutex_.Lock(); - auto* s = super_version_->Ref(); - mutex_.Unlock(); - - -The lock is necessary because pointer super_version_ may be updated, the corresponding SuperVersion may be deleted while Ref() is in progress. - - -`Ref()` simply increases the reference counter and returns “this” pointer. However, this simple operation posed big challenges for in-memory workload and stopped RocksDB from scaling read throughput beyond 8 cores. Running 32 read threads on a 32-core CPU leads to [70% system CPU usage](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf). This is outrageous! - - - - -Luckily, we found a way to circumvent this problem by using [thread local storage](http://en.wikipedia.org/wiki/Thread-local_storage). Version change is a rare event comparable to millions of read requests. On the very first Get() request, each thread pays the mutex cost to acquire a reference to the new super version. Instead of releasing the reference after use, the reference is cached in thread’s local storage. An atomic variable is used to track global super version number. Subsequent reads simply compare the local super version number against the global super version number. If they are the same, the cached super version reference may be used directly, at no cost. If a version change is detected, mutex must be acquired to update the reference. The cost of mutex lock is amortized among millions of reads and becomes negligible. - - - - -The code looks something like this: - - - - - - SuperVersion* s = thread_local_->Get(); - if (s->version_number != super_version_number_.load()) { - // slow path, cleanup of current super version is omitted - mutex_.Lock(); - s = super_version_->Ref(); - mutex_.Unlock(); - } - - - - -The result is quite amazing. RocksDB can nicely [scale to 32 cores](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf) and most CPU time is spent in user land. - - - - -Daryl Grove gives a pretty good [comparison between mutex and atomic](https://blogs.oracle.com/d/entry/the_cost_of_mutexes). However, the real cost difference lies beyond what is shown in the assembly code. Mutex can keep threads spinning on CPU or even trigger thread context switches in which all readers compete to access the critical area. Our approach prevents mutual competition by directing threads to check against a global version which does not change at high frequency, and is therefore much more cache-friendly. - - - - -The new approach entails one issue: a thread can visit GetImpl() once but can never come back again. SuperVersion is referenced and cached in its thread local storage. All resources (e.g., memtables, files) which belong to that version are frozen. A “supervisor” is required to visit each thread’s local storage and free its resources without incurring a lock. We designed a lockless sweep using CAS (compare and switch instruction). Here is how it works: - - - - -(1) A reader thread uses CAS to acquire SuperVersion from its local storage and to put in a special flag (SuperVersion::kSVInUse). - - - - -(2) Upon completion of GetImpl(), the reader thread tries to return SuperVersion to local storage by CAS, expecting the special flag (SuperVersion::kSVInUse) in its local storage. If it does not see SuperVersion::kSVInUse, that means a “sweep” was done and the reader thread is responsible for cleanup (this is expensive, but does not happen often on the hot path). - - - - -(3) After any flush/compaction, the background thread performs a sweep (CAS) across all threads’ local storage and frees encountered SuperVersion. A reader thread must re-acquire a new SuperVersion reference on its next visit. - -### Comments - -**[David Barbour](dmbarbour@gmail.com)** - -Please post an example of reading the same rocksdb concurrently. - -We are using the latest 3.0 rocksdb; however, when two separate processes -try and open the same rocksdb for reading, only one of the open requests -succeed. The other open always fails with “db/LOCK: Resource temporarily unavailable” So far we have not found an option that allows sharing the rocksdb for reads. An example would be most appreciated. diff --git a/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown b/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown deleted file mode 100644 index e4eba6af4..000000000 --- a/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: RocksDB 3.2 release -layout: post -author: leijin -category: blog -redirect_from: - - /blog/647/rocksdb-3-2-release/ ---- - -Check out new RocksDB release on [GitHub](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.2)! - -New Features in RocksDB 3.2: - - * PlainTable now supports a new key encoding: for keys of the same prefix, the prefix is only written once. It can be enabled through encoding_type paramter of NewPlainTableFactory() - - - * Add AdaptiveTableFactory, which is used to convert from a DB of PlainTable to BlockBasedTabe, or vise versa. It can be created using NewAdaptiveTableFactory() - - - -Public API changes: - - - * We removed seek compaction as a concept from RocksDB - - - * Add two paramters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing - - - * Added new option BlockBasedTableOptions::hash_index_allow_collision. When enabled, prefix hash index for block-based table will not store prefix and allow hash collision, reducing memory consumption diff --git a/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown b/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown deleted file mode 100644 index d858e4faf..000000000 --- a/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: RocksDB 3.3 Release -layout: post -author: yhciang -category: blog -redirect_from: - - /blog/1301/rocksdb-3-3-release/ ---- - -Check out new RocksDB release on [GitHub](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.3)! - -New Features in RocksDB 3.3: - - * **JSON API prototype**. - - - * **Performance improvement on HashLinkList**: We addressed performance outlier of HashLinkList caused by skewed bucket by switching data in the bucket from linked list to skip list. Add parameter threshold_use_skiplist in NewHashLinkListRepFactory(). - - - - * **More effective on storage space reclaim**: RocksDB is now able to reclaim storage space more effectively during the compaction process. This is done by compensating the size of each deletion entry by the 2X average value size, which makes compaction to be triggerred by deletion entries more easily. - - - * **TimeOut API to write**: Now WriteOptions have a variable called timeout_hint_us. With timeout_hint_us set to non-zero, any write associated with this timeout_hint_us may be aborted when it runs longer than the specified timeout_hint_us, and it is guaranteed that any write completes earlier than the specified time-out will not be aborted due to the time-out condition. - - - * **rate_limiter option**: We added an option that controls total throughput of flush and compaction. The throughput is specified in bytes/sec. Flush always has precedence over compaction when available bandwidth is constrained. - - - -Public API changes: - - - * Removed NewTotalOrderPlainTableFactory because it is not used and implemented semantically incorrect. diff --git a/docs/_posts/2014-09-12-cuckoo.markdown b/docs/_posts/2014-09-12-cuckoo.markdown deleted file mode 100644 index 22178f7ca..000000000 --- a/docs/_posts/2014-09-12-cuckoo.markdown +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Cuckoo Hashing Table Format -layout: post -author: radheshyam -category: blog -redirect_from: - - /blog/1427/new-bloom-filter-format/ ---- - -## Introduction - -We recently introduced a new [Cuckoo Hashing](http://en.wikipedia.org/wiki/Cuckoo_hashing) based SST file format which is optimized for fast point lookups. The new format was built for applications which require very high point lookup rates (~4Mqps) in read only mode but do not use operations like range scan, merge operator, etc. But, the existing RocksDB file formats were built to support range scan and other operations and the current best point lookup in RocksDB is 1.2 Mqps given by [PlainTable](https://github.com/facebook/rocksdb/wiki/PlainTable-Format)[ format](https://github.com/facebook/rocksdb/wiki/PlainTable-Format). This prompted a hashing based file format, which we present here. The new table format uses a cache friendly version of Cuckoo Hashing algorithm with only 1 or 2 memory accesses per lookup. - - - -Goals: - - * Reduce memory accesses per lookup to 1 or 2 - - - * Get an end to end point lookup rate of at least 4 Mqps - - - * Minimize database size - - -Assumptions: - - * Key length and value length are fixed - - - * The database is operated in read only mode - - -Non-goals: - - - * While optimizing the performance of Get() operation was our primary goal, compaction and build times were secondary. We may work on improving them in future. - - -Details for setting up the table format can be found in [GitHub](https://github.com/facebook/rocksdb/wiki/CuckooTable-Format). - - -## Cuckoo Hashing Algorithm - -In order to achieve high lookup speeds, we did multiple optimizations, including a cache friendly cuckoo hash algorithm. Cuckoo Hashing uses multiple hash functions, _h1, ..., __hn._ - -### Original Cuckoo Hashing - -To insert any new key _k_, we compute hashes of the key _h1(k), ..., __hn__(k)_. We insert the key in the first hash location that is free. If all the locations are blocked, we try to move one of the colliding keys to a different location by trying to re-insert it. - -Finding smallest set of keys to displace in order to accommodate the new key is naturally a shortest path problem in a directed graph where nodes are buckets of hash table and there is an edge from bucket _A_ to bucket _B_ if the element stored in bucket _A_ can be accommodated in bucket _B_ using one of the hash functions. The source nodes are the possible hash locations for the given key _k_ and destination is any one of the empty buckets. We use this algorithm to handle collision. - -To retrieve a key _k_, we compute hashes, _h1(k), ..., __hn__(k)_ and the key must be present in one of these locations. - -Our goal is to minimize average (and maximum) number of hash functions required and hence the number of memory accesses. In our experiments, with a hash utilization of 90%, we found that the average number of lookups is 1.8 and maximum is 3. Around 44% of keys are accommodated in first hash location and 33% in second location. - - -### Cache Friendly Cuckoo Hashing - -We noticed the following two sub-optimal properties in original Cuckoo implementation: - - - * If the key is not present in first hash location, we jump to second hash location which may not be in cache. This results in many cache misses. - - - * Because only 44% of keys are located in first cuckoo block, we couldn't have an optimal prefetching strategy - prefetching all hash locations for a key is wasteful. But prefetching only the first hash location helps only 44% of cases. - - - -The solution is to insert more keys near first location. In case of collision in the first hash location - _h1(k)_, we try to insert it in next few buckets, _h1(k)+1, _h1(k)+2, _..., h1(k)+t-1_. If all of these _t_ locations are occupied, we skip over to next hash function _h2_ and repeat the process. We call the set of _t_ buckets as a _Cuckoo Block_. We chose _t_ such that size of a block is not bigger than a cache line and we prefetch the first cuckoo block. - - -With the new algorithm, for 90% hash utilization, we found that 85% of keys are accommodated in first Cuckoo Block. Prefetching the first cuckoo block yields best results. For a database of 100 million keys with key length 8 and value length 4, the hash algorithm alone can achieve 9.6 Mqps and we are working on improving it further. End to end RocksDB performance results can be found [here](https://github.com/facebook/rocksdb/wiki/CuckooTable-Format). diff --git a/docs/_posts/2014-09-12-new-bloom-filter-format.markdown b/docs/_posts/2014-09-12-new-bloom-filter-format.markdown deleted file mode 100644 index 96fa50a40..000000000 --- a/docs/_posts/2014-09-12-new-bloom-filter-format.markdown +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: New Bloom Filter Format -layout: post -author: zagfox -category: blog -redirect_from: - - /blog/1367/cuckoo/ ---- - -## Introduction - -In this post, we are introducing "full filter block" --- a new bloom filter format for [block based table](https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format). This could bring about 40% of improvement for key query under in-memory (all data stored in memory, files stored in tmpfs/ramfs, an [example](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks) workload. The main idea behind is to generate a big filter that covers all the keys in SST file to avoid lots of unnecessary memory look ups. - - - - -## What is Bloom Filter - -In brief, [bloom filter](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter) is a bits array generated for a set of keys that could tell if an arbitrary key may exist in that set. - -In RocksDB, we generate such a bloom filter for each SST file. When we conduct a query for a key, we first goes to the bloom filter block of SST file. If key may exist in filter, we goes into data block in SST file to search for the key. If not, we would return directly. So it could help speed up point look up operation a lot. - -## Original Bloom Filter Format - -Original bloom filter creates filters for each individual data block in SST file. It has complex structure (ref [here](https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format#filter-meta-block)) which results in a lot of non-adjacent memory look ups. - -Here's the work flow for checking original bloom filter in block based table: - -1. Given the target key, we goes to the index block to get the "data block ID" where this key may reside. -1. Using the "data block ID", we goes to the filter block and get the correct "offset of filter". -1. Using the "offset of filter", we goes to the actual filter and do the checking. - -## New Bloom Filter Format - -New bloom filter creates filter for all keys in SST file and we name it "full filter". The data structure of full filter is very simple, there is just one big filter: - -    [ full filter ] - -In this way, the work flow of bloom filter checking is much simplified. - -(1) Given the target key, we goes directly to the filter block and conduct the filter checking. - -To be specific, there would be no checking for index block and no address jumping inside of filter block. - -Though it is a big filter, the total filter size would be the same as the original filter. - -One little draw back is that the new bloom filter introduces more memory consumption when building SST file because we need to buffer keys (or their hashes) before generating filter. Original filter just creates a bunch of small filters so it just buffer a small amount of keys. For full filter, we buffer hashes of all keys, which would take more memory when SST file size increases. - - -## Usage & Customization - -You can refer to the document here for [usage](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#usage-of-new-bloom-filter) and [customization](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#customize-your-own-filterpolicy). diff --git a/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown b/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown deleted file mode 100644 index 1878a5a56..000000000 --- a/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: RocksDB 3.5 Release! -layout: post -author: leijin -category: blog -redirect_from: - - /blog/1547/rocksdb-3-5-release/ ---- - -New RocksDB release - 3.5! - - -**New Features** - - - 1. Add include/utilities/write_batch_with_index.h, providing a utility class to query data out of WriteBatch when building it. - - - 2. new ReadOptions.total_order_seek to force total order seek when block-based table is built with hash index. - - - -**Public API changes** - - - 1. The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key. - - - 2. Move BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface. Options affected include: no_block_cache, block_cache, block_cache_compressed, block_size, block_size_deviation, block_restart_interval, filter_policy, whole_key_filtering. filter_policy is changed to shared_ptr from a raw pointer. - - - 3. Remove deprecated options: disable_seek_compaction and db_stats_log_interval - - - 4. OptimizeForPointLookup() takes one parameter for block cache size. It now builds hash index, bloom filter, and block cache. - - -[https://github.com/facebook/rocksdb/releases/tag/v3.5](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.5) diff --git a/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown b/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown deleted file mode 100644 index f18de0bbc..000000000 --- a/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Migrating from LevelDB to RocksDB -layout: post -author: lgalanis -category: blog -redirect_from: - - /blog/1811/migrating-from-leveldb-to-rocksdb-2/ ---- - -If you have an existing application that uses LevelDB and would like to migrate to using RocksDB, one problem you need to overcome is to map the options for LevelDB to proper options for RocksDB. As of release 3.9 this can be automatically done by using our option conversion utility found in rocksdb/utilities/leveldb_options.h. What is needed, is to first replace `leveldb::Options` with `rocksdb::LevelDBOptions`. Then, use `rocksdb::ConvertOptions( )` to convert the `LevelDBOptions` struct into appropriate RocksDB options. Here is an example: - - - -LevelDB code: - -```c++ -#include -#include "leveldb/db.h" - -using namespace leveldb; - -int main(int argc, char** argv) { - DB *db; - - Options opt; - opt.create_if_missing = true; - opt.max_open_files = 1000; - opt.block_size = 4096; - - Status s = DB::Open(opt, "/tmp/mydb", &db); - - delete db; -} -``` - -RocksDB code: - -```c++ -#include -#include "rocksdb/db.h" -#include "rocksdb/utilities/leveldb_options.h" - -using namespace rocksdb; - -int main(int argc, char** argv) { - DB *db; - - LevelDBOptions opt; - opt.create_if_missing = true; - opt.max_open_files = 1000; - opt.block_size = 4096; - - Options rocksdb_options = ConvertOptions(opt); - // add rocksdb specific options here - - Status s = DB::Open(rocksdb_options, "/tmp/mydb_rocks", &db); - - delete db; -} -``` - -The difference is: - -```diff --#include "leveldb/db.h" -+#include "rocksdb/db.h" -+#include "rocksdb/utilities/leveldb_options.h" - --using namespace leveldb; -+using namespace rocksdb; - -- Options opt; -+ LevelDBOptions opt; - -- Status s = DB::Open(opt, "/tmp/mydb", &db); -+ Options rocksdb_options = ConvertOptions(opt); -+ // add rockdb specific options here -+ -+ Status s = DB::Open(rocksdb_options, "/tmp/mydb_rocks", &db); -``` - -Once you get up and running with RocksDB you can then focus on tuning RocksDB further by modifying the converted options struct. - -The reason why ConvertOptions is handy is because a lot of individual options in RocksDB have moved to other structures in different components. For example, block_size is not available in struct rocksdb::Options. It resides in struct rocksdb::BlockBasedTableOptions, which is used to create a TableFactory object that RocksDB uses internally to create the proper TableBuilder objects. If you were to write your application from scratch it would look like this: - -RocksDB code from scratch: - -```c++ -#include -#include "rocksdb/db.h" -#include "rocksdb/table.h" - -using namespace rocksdb; - -int main(int argc, char** argv) { - DB *db; - - Options opt; - opt.create_if_missing = true; - opt.max_open_files = 1000; - - BlockBasedTableOptions topt; - topt.block_size = 4096; - opt.table_factory.reset(NewBlockBasedTableFactory(topt)); - - Status s = DB::Open(opt, "/tmp/mydb_rocks", &db); - - delete db; -} -``` - -The LevelDBOptions utility can ease migration to RocksDB from LevelDB and allows us to break down the various options across classes as it is needed. diff --git a/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown b/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown deleted file mode 100644 index cddc0dd01..000000000 --- a/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Reading RocksDB options from a file -layout: post -author: lgalanis -category: blog -redirect_from: - - /blog/1883/reading-rocksdb-options-from-a-file/ ---- - -RocksDB options can be provided using a file or any string to RocksDB. The format is straightforward: `write_buffer_size=1024;max_write_buffer_number=2`. Any whitespace around `=` and `;` is OK. Moreover, options can be nested as necessary. For example `BlockBasedTableOptions` can be nested as follows: `write_buffer_size=1024; max_write_buffer_number=2; block_based_table_factory={block_size=4k};`. Similarly any white space around `{` or `}` is ok. Here is what it looks like in code: - - - -```c++ -#include -#include "rocksdb/db.h" -#include "rocksdb/table.h" -#include "rocksdb/utilities/convenience.h" - -using namespace rocksdb; - -int main(int argc, char** argv) { - DB *db; - - Options opt; - - std::string options_string = - "create_if_missing=true;max_open_files=1000;" - "block_based_table_factory={block_size=4096}"; - - Status s = GetDBOptionsFromString(opt, options_string, &opt); - - s = DB::Open(opt, "/tmp/mydb_rocks", &db); - - // use db - - delete db; -} -``` - -Using `GetDBOptionsFromString` is a convenient way of changing options for your RocksDB application without needing to resort to recompilation or tedious command line parsing. diff --git a/docs/_posts/2015-02-27-write-batch-with-index.markdown b/docs/_posts/2015-02-27-write-batch-with-index.markdown deleted file mode 100644 index 7f9f77653..000000000 --- a/docs/_posts/2015-02-27-write-batch-with-index.markdown +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: 'WriteBatchWithIndex: Utility for Implementing Read-Your-Own-Writes' -layout: post -author: sdong -category: blog -redirect_from: - - /blog/1901/write-batch-with-index/ ---- - -RocksDB can be used as a storage engine of a higher level database. In fact, we are currently plugging RocksDB into MySQL and MongoDB as one of their storage engines. RocksDB can help with guaranteeing some of the ACID properties: durability is guaranteed by RocksDB by design; while consistency and isolation need to be enforced by concurrency controls on top of RocksDB; Atomicity can be implemented by committing a transaction's writes with one write batch to RocksDB in the end. - - - -However, if we enforce atomicity by only committing all writes in the end of the transaction in one batch, you cannot get the updated value from RocksDB previously written by the same transaction (read-your-own-write). To read the updated value, the databases on top of RocksDB need to maintain an internal buffer for all the written keys, and when a read happens they need to merge the result from RocksDB and from this buffer. This is a problem we faced when building the RocksDB storage engine in MongoDB. We solved it by creating a utility class, WriteBatchWithIndex (a write batch with a searchable index) and made it part of public API so that the community can also benefit from it. - -Before talking about the index part, let me introduce write batch first. The write batch class, `WriteBatch`, is a RocksDB data structure for atomic writes of multiple keys. Users can buffer their updates to a `WriteBatch` by calling `write_batch.Put("key1", "value1")` or `write_batch.Delete("key2")`, similar as calling RocksDB's functions of the same names. In the end, they call `db->Write(write_batch)` to atomically update all those batched operations to the DB. It is how a database can guarantee atomicity, as shown above. Adding a searchable index to `WriteBatch`, we now have `WriteBatchWithIndex`. Users can put updates to WriteBatchIndex in the same way as to `WriteBatch`. In the end, users can get a `WriteBatch` object from it and issue `db->Write()`. Additionally, users can create an iterator of a WriteBatchWithIndex, seek to any key location and iterate from there. - -To implement read-your-own-write using `WriteBatchWithIndex`, every time the user creates a transaction, we create a `WriteBatchWithIndex` attached to it. All the writes of the transaction go to the `WriteBatchWithIndex` first. When we commit the transaction, we atomically write the batch to RocksDB. When the user wants to call `Get()`, we first check if the value exists in the `WriteBatchWithIndex` and return the value if existing, by seeking and reading from an iterator of the write batch, before checking data in RocksDB. For example, here is the we implement it in MongoDB's RocksDB storage engine: [link](https://github.com/mongodb/mongo/blob/a31cc114a89a3645e97645805ba77db32c433dce/src/mongo/db/storage/rocks/rocks_recovery_unit.cpp#L245-L260). If a range query comes, we pass a DB's iterator to `WriteBatchWithIndex`, which creates a super iterator which combines the results from the DB iterator with the batch's iterator. Using this super iterator, we can iterate the DB with the transaction's own writes. Here is the iterator creation codes in MongoDB's RocksDB storage engine: [link](https://github.com/mongodb/mongo/blob/a31cc114a89a3645e97645805ba77db32c433dce/src/mongo/db/storage/rocks/rocks_recovery_unit.cpp#L266-L269). In this way, the database can solve the read-your-own-write problem by using RocksDB to handle a transaction's uncommitted writes. - -Using `WriteBatchWithIndex`, we successfully implemented read-your-own-writes in the RocksDB storage engine of MongoDB. If you also have a read-your-own-write problem, `WriteBatchWithIndex` can help you implement it quickly and correctly. diff --git a/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown b/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown deleted file mode 100644 index 1ffe2c532..000000000 --- a/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Integrating RocksDB with MongoDB -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/1967/integrating-rocksdb-with-mongodb-2/ ---- - -Over the last couple of years, we have been busy integrating RocksDB with various services here at Facebook that needed to store key-value pairs locally. We have also seen other companies using RocksDB as local storage components of their distributed systems. - - - -The next big challenge for us is to bring RocksDB storage engine to general purpose databases. Today we have an exciting milestone to share with our community! We're running MongoDB with RocksDB in production and seeing great results! You can read more about it here: [http://blog.parse.com/announcements/mongodb-rocksdb-parse/](http://blog.parse.com/announcements/mongodb-rocksdb-parse/) - -Keep tuned for benchmarks and more stability and performance improvements. diff --git a/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown b/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown deleted file mode 100644 index f3a55faae..000000000 --- a/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: RocksDB in osquery -layout: post -author: icanadi -category: lgalanis -redirect_from: - - /blog/1997/rocksdb-in-osquery/ ---- - -Check out [this](https://code.facebook.com/posts/1411870269134471/how-rocksdb-is-used-in-osquery/) blog post by [Mike Arpaia](https://www.facebook.com/mike.arpaia) and [Ted Reed](https://www.facebook.com/treeded) about how osquery leverages RocksDB to build an embedded pub-sub system. This article is a great read and contains insights on how to properly use RocksDB. diff --git a/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown b/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown deleted file mode 100644 index b3e2703fc..000000000 --- a/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: RocksDB 2015 H2 roadmap -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/2015/rocksdb-2015-h2-roadmap/ ---- - -Every 6 months, RocksDB team gets together to prioritize the work ahead of us. We just went through this exercise and we wanted to share the results with the community. Here's what RocksDB team will be focusing on for the next 6 months: - - - -**MyRocks** - -As you might know, we're working hard to integrate RocksDB as a storage engine for MySQL. This project is pretty important for us because we're heavy users of MySQL. We're already getting pretty good performance results, but there is more work to be done. We need to focus on both performance and stability. The most high priority items on are list are: - - - - - 1. Reduce CPU costs of RocksDB as a MySQL storage engine - - - 2. Implement pessimistic concurrency control to support repeatable read isolation level in MyRocks - - - 3. Reduce P99 read latency, which is high mostly because of lingering tombstones - - - 4. Port ZSTD compression - - -**MongoRocks** - -Another database that we're working on is MongoDB. The project of integrating MongoDB with RocksDB storage engine is called MongoRocks. It's already running in production at Parse [1] and we're seeing surprisingly few issues. Our plans for the next half: - - - - - 1. Keep improving performance and stability, possibly reuse work done on MyRocks (workloads are pretty similar). - - - 2. Increase internal and external adoption. - - - 3. Support new MongoDB 3.2. - - -**RocksDB on cheaper storage media** - -Up to now, our mission was to build the best key-value store “for fast storage” (flash and in-memory). However, there are some use-cases at Facebook that don't need expensive high-end storage. In the next six months, we plan to deploy RocksDB on cheaper storage media. We will optimize performance to RocksDB on either or both: - - - - - 1. Hard drive storage array. - - - 2. Tiered Storage. - - -**Quality of Service** - -When talking to our customers, there are couple of issues that keep reoccurring. We need to fix them to make our customers happy. We will improve RocksDB to provide better assurance of performance and resource usage. Non-exhaustive list includes: - - - - - 1. Iterate P99 can be high due to the presence of tombstones. - - - 2. Write stalls can happen during high write loads. - - - 3. Better control of memory and disk usage. - - - 4. Service quality and performance of backup engine. - - -**Operation's user experience** - -As we increase deployment of RocksDB, engineers are spending more time on debugging RocksDB issues. We plan to improve user experience when running RocksDB. The goal is to reduce TTD (time-to-debug). The work includes monitoring, visualizations and documentations. - -[1]( http://blog.parse.com/announcements/mongodb-rocksdb-parse/](http://blog.parse.com/announcements/mongodb-rocksdb-parse/) - - -### Comments - -**[Mike](allspace2012@outlook.com)** - -What’s the status of this roadmap? “RocksDB on cheaper storage media”, has this been implemented? diff --git a/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown b/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown deleted file mode 100644 index 53c1f5a90..000000000 --- a/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Spatial indexing in RocksDB -layout: post -author: icanadi -category: blog -redirect_from: - - /blog/2039/spatial-indexing-in-rocksdb/ ---- - -About a year ago, there was a need to develop a spatial database at Facebook. We needed to store and index Earth's map data. Before building our own, we looked at the existing spatial databases. They were all very good technology, but also general purpose. We could sacrifice a general-purpose API, so we thought we could build a more performant database, since it would be specifically designed for our use-case. Furthermore, we decided to build the spatial database on top of RocksDB, because we have a lot of operational experience with running and tuning RocksDB at a large scale. - - - -When we started looking at this project, the first thing that surprised us was that our planet is not that big. Earth's entire map data can fit in memory on a reasonably high-end machine. Thus, we also decided to build a spatial database optimized for memory-resident dataset. - -The first use-case of our spatial database was an experimental map renderer. As part of our project, we successfully loaded [Open Street Maps](https://www.openstreetmap.org/) dataset and hooked it up with [Mapnik](http://mapnik.org/), a map rendering engine. - -The usual Mapnik workflow is to load the map data into a SQL-based database and then define map layers with SQL statements. To render a tile, Mapnik needs to execute a couple of SQL queries. The benefit of this approach is that you don't need to reload your database when you change your map style. You can just change your SQL query and Mapnik picks it up. In our model, we decided to precompute the features we need for each tile. We need to know the map style before we create the database. However, when rendering the map tile, we only fetch the features that we need to render. - -We haven't open sourced the RocksDB Mapnik plugin or the database loading pipeline. However, the spatial indexing is available in RocksDB under a name [SpatialDB](https://github.com/facebook/rocksdb/blob/main/include/rocksdb/utilities/spatial_db.h). The API is focused on map rendering use-case, but we hope that it can also be used for other spatial-based applications. - -Let's take a tour of the API. When you create a spatial database, you specify the spatial indexes that need to be built. Each spatial index is defined by a bounding box and granularity. For map rendering, we create a spatial index for each zoom levels. Higher zoom levels have more granularity. - - - - SpatialDB::Create( - SpatialDBOptions(), - "/data/map", { - SpatialIndexOptions("zoom10", BoundingBox(0, 0, 100, 100), 10), - SpatialIndexOptions("zoom16", BoundingBox(0, 0, 100, 100), 16) - } - ); - - - - -When you insert a feature (building, street, country border) into SpatialDB, you need to specify the list of spatial indexes that will index the feature. In the loading phase we process the map style to determine the list of zoom levels on which we'll render the feature. For example, we will not render the building on zoom level that shows an entire country. Building will only be indexed on higher zoom level's index. Country borders will be indexes on all zoom levels. - - - - FeatureSet feature; - feature.Set("type", "building"); - feature.Set("height", 6); - db->Insert(WriteOptions(), BoundingBox(5, 5, 10, 10), - well_known_binary_blob, feature, {"zoom16"}); - - - - -The indexing part is pretty simple. For each feature, we first find a list of index tiles that it intersects. Then, we add a link from the tile's [quad key](https://msdn.microsoft.com/en-us/library/bb259689.aspx) to the feature's primary key. Using quad keys improves data locality, i.e. features closer together geographically will have similar quad keys. Even though we're optimizing for a memory-resident dataset, data locality is still very important due to different caching effects. - -After you're done inserting all the features, you can call an API Compact() that will compact the dataset and speed up read queries. - - - - db->Compact(); - - - - -SpatialDB's query specifies: 1) bounding box we're interested in, and 2) a zoom level. We find all tiles that intersect with the query's bounding box and return all features in those tiles. - - - - - Cursor* c = db_->Query(ReadOptions(), BoundingBox(1, 1, 7, 7), "zoom16"); - for (c->Valid(); c->Next()) { - Render(c->blob(), c->feature_set()); - } - - - - -Note: `Render()` function is not part of RocksDB. You will need to use one of many open source map renderers, for example check out [Mapnik](http://mapnik.org/). - -TL;DR If you need an embedded spatial database, check out RocksDB's SpatialDB. [Let us know](https://www.facebook.com/groups/rocksdb.dev/) how we can make it better. - -If you're interested in learning more, check out this [talk](https://www.youtube.com/watch?v=T1jWsDMONM8). diff --git a/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown b/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown deleted file mode 100644 index b6bb47d53..000000000 --- a/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: RocksDB is now available in Windows Platform -layout: post -author: dmitrism -category: blog -redirect_from: - - /blog/2033/rocksdb-is-now-available-in-windows-platform/ ---- - -Over the past 6 months we have seen a number of use cases where RocksDB is successfully used by the community and various companies to achieve high throughput and volume in a modern server environment. - -We at Microsoft Bing could not be left behind. As a result we are happy to [announce](http://bit.ly/1OmWBT9) the availability of the Windows Port created here at Microsoft which we intend to use as a storage option for one of our key/value data stores. - - - -We are happy to make this available for the community. Keep tuned for more announcements to come. - -### Comments - -**[Siying Dong](siying.d@fb.com)** - -Appreciate your contributions to RocksDB project! I believe it will benefits many users! - -**[empresas sevilla](oxofkx@gmail.com)** - -Magnifico artículo|, un placer leer el blog - -**[jak usunac](tomogedac@o2.pl)** - -I believe it will benefits too diff --git a/docs/_posts/2015-07-23-dynamic-level.markdown b/docs/_posts/2015-07-23-dynamic-level.markdown deleted file mode 100644 index 0ff3a0542..000000000 --- a/docs/_posts/2015-07-23-dynamic-level.markdown +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Dynamic Level Size for Level-Based Compaction -layout: post -author: sdong -category: blog -redirect_from: - - /blog/2207/dynamic-level/ ---- - -In this article, we follow up on the first part of an answer to one of the questions in our [AMA](https://www.reddit.com/r/IAmA/comments/3de3cv/we_are_rocksdb_engineering_team_ask_us_anything/ct4a8tb), the dynamic level size in level-based compaction. - - - -Level-based compaction is the original LevelDB compaction style and one of the two major compaction styles in RocksDB (See [our wiki](https://github.com/facebook/rocksdb/wiki/RocksDB-Basics#multi-threaded-compactions)). In RocksDB we introduced parallelism and more configurable options to it but the main algorithm stayed the same, until we recently introduced the dynamic level size mode. - - -In level-based compaction, we organize data to different sorted runs, called levels. Each level has a target size.  Usually target size of levels increases by the same size multiplier. For example, you can set target size of level 1 to be 1GB, and size multiplier to be 10, and the target size of level 1, 2, 3, 4 will be 1GB, 10GB, 100GB and 1000GB. Before level 1, there will be some staging file flushed from mem tables, called Level 0 files, which will later be merged to level 1. Compactions will be triggered as soon as actual size of a level exceeds its target size. We will merge a subset of data of that level to next level, to reduce size of the level. More compactions will be triggered until sizes of all the levels are lower than their target sizes. In a steady state, the size of each level will be around the same size of the size of level targets. - - -Level-based compaction’s advantage is its good space efficiency. We usually use the metric space amplification to measure the space efficiency. In this article ignore the effects of data compression so space amplification= size_on_file_system / size_of_user_data. - - -How do we estimate space amplification of level-based compaction? We focus specifically on the databases in steady state, which means database size is stable or grows slowly over time. This means updates will add roughly the same or little more data than what is removed by deletes. Given that, if we compact all the data all to the last level, the size of level will be equal as the size of last level before the compaction. On the other hand, the size of user data will be approximately the size of DB if we compact all the levels down to the last level. So the size of the last level will be a good estimation of user data size. So total size of the DB divided by the size of the last level will be a good estimation of space amplification. - - -Applying the equation, if we have four non-zero levels, their sizes are 1GB, 10GB, 100GB, 1000GB, the size amplification will be approximately (1000GB + 100GB + 10GB + 1GB) / 1000GB = 1.111, which is a very good number. However, there is a catch here: how to make sure the last level’s size is 1000GB, the same as the level’s size target? A user has to fine tune level sizes to achieve this number and will need to re-tune if DB size changes. The theoretic number 1.11 is hard to achieve in practice. In a worse case, if you have the target size of last level to be 1000GB but the user data is only 200GB, then the actual space amplification will be (200GB + 100GB + 10GB + 1GB) / 200GB = 1.555, a much worse number. - - -To solve this problem, my colleague Igor Kabiljo came up with a solution of dynamic level size target mode. You can enable it by setting options.level_compaction_dynamic_level_bytes=true. In this mode, size target of levels are changed dynamically based on size of the last level. Suppose the level size multiplier to be 10, and the DB size is 200GB. The target size of the last level is automatically set to be the actual size of the level, which is 200GB, the second to last level’s size target will be automatically set to be size_last_level / 10 = 20GB, the third last level’s will be size_last_level/100 = 2GB, and next level to be size_last_level/1000 = 200MB. We stop here because 200MB is within the range of the first level. In this way, we can achieve the 1.111 space amplification, without fine tuning of the level size targets. More details can be found in [code comments of the option](https://github.com/facebook/rocksdb/blob/v3.11/include/rocksdb/options.h#L366-L423) in the header file. diff --git a/docs/_posts/2015-10-27-getthreadlist.markdown b/docs/_posts/2015-10-27-getthreadlist.markdown deleted file mode 100644 index 92f743adc..000000000 --- a/docs/_posts/2015-10-27-getthreadlist.markdown +++ /dev/null @@ -1,193 +0,0 @@ ---- -title: GetThreadList -layout: post -author: yhciang -category: blog -redirect_from: - - /blog/2261/getthreadlist/ ---- - -We recently added a new API, called `GetThreadList()`, that exposes the RocksDB background thread activity. With this feature, developers will be able to obtain the real-time information about the currently running compactions and flushes such as the input / output size, elapsed time, the number of bytes it has written. Below is an example output of `GetThreadList`. To better illustrate the example, we have put a sample output of `GetThreadList` into a table where each column represents a thread status: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ThreadID -140716395198208 -140716416169728 -
DB -db1 -db2 -
CF -default -picachu -
ThreadType -High Pri -Low Pri -
Operation -Flush -Compaction -
ElapsedTime -143.459 ms -607.538 ms -
Stage -FlushJob::WriteLevel0Table -CompactionJob::Install -
OperationProperties - -BytesMemtables 4092938 -BytesWritten 1050701 - -BaseInputLevel 1 -BytesRead 4876417 -BytesWritten 4140109 -IsDeletion 0 -IsManual 0 -IsTrivialMove 0 -JobID 146 -OutputLevel 2 -TotalInputBytes 4883044 -
- -In the above output, we can see `GetThreadList()` reports the activity of two threads: one thread running flush job (middle column) and the other thread running a compaction job (right-most column). In each thread status, it shows basic information about the thread such as thread id, it's target db / column family, and the job it is currently doing and the current status of the job. For instance, we can see thread 140716416169728 is doing compaction on the `picachu` column family in database `db2`. In addition, we can see the compaction has been running for 600 ms, and it has read 4876417 bytes out of 4883044 bytes. This indicates the compaction is about to complete. The stage property indicates which code block the thread is currently executing. For instance, thread 140716416169728 is currently running `CompactionJob::Install`, which further indicates the compaction job is almost done. - -Below we briefly describe its API. - - -## How to Enable it? - - -To enable thread-tracking of a rocksdb instance, simply set `enable_thread_tracking` to true in its DBOptions: - -```c++ -// If true, then the status of the threads involved in this DB will -// be tracked and available via GetThreadList() API. -// -// Default: false -bool enable_thread_tracking; -``` - - - -## The API - - -The GetThreadList API is defined in [include/rocksdb/env.h](https://github.com/facebook/rocksdb/blob/main/include/rocksdb/env.h#L317-L318), which is an Env -function: - -```c++ -virtual Status GetThreadList(std::vector* thread_list) -``` - -Since an Env can be shared across multiple rocksdb instances, the output of -`GetThreadList()` include the background activity of all the rocksdb instances -that using the same Env. - -The `GetThreadList()` API simply returns a vector of `ThreadStatus`, each describes -the current status of a thread. The `ThreadStatus` structure, defined in -[include/rocksdb/thread_status.h](https://github.com/facebook/rocksdb/blob/main/include/rocksdb/thread_status.h), contains the following information: - -```c++ -// An unique ID for the thread. -const uint64_t thread_id; - -// The type of the thread, it could be HIGH_PRIORITY, -// LOW_PRIORITY, and USER -const ThreadType thread_type; - -// The name of the DB instance where the thread is currently -// involved with. It would be set to empty string if the thread -// does not involve in any DB operation. -const std::string db_name; - -// The name of the column family where the thread is currently -// It would be set to empty string if the thread does not involve -// in any column family. -const std::string cf_name; - -// The operation (high-level action) that the current thread is involved. -const OperationType operation_type; - -// The elapsed time in micros of the current thread operation. -const uint64_t op_elapsed_micros; - -// An integer showing the current stage where the thread is involved -// in the current operation. -const OperationStage operation_stage; - -// A list of properties that describe some details about the current -// operation. Same field in op_properties[] might have different -// meanings for different operations. -uint64_t op_properties[kNumOperationProperties]; - -// The state (lower-level action) that the current thread is involved. -const StateType state_type; -``` - -If you are interested in the background thread activity of your RocksDB application, please feel free to give `GetThreadList()` a try :) diff --git a/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown b/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown deleted file mode 100644 index 6852b8ffa..000000000 --- a/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Use Checkpoints for Efficient Snapshots -layout: post -author: rven2 -category: blog -redirect_from: - - /blog/2609/use-checkpoints-for-efficient-snapshots/ ---- - -**Checkpoint** is a feature in RocksDB which provides the ability to take a snapshot of a running RocksDB database in a separate directory. Checkpoints can be used as a point in time snapshot, which can be opened Read-only to query rows as of the point in time or as a Writeable snapshot by opening it Read-Write. Checkpoints can be used for both full and incremental backups. - - - - -The Checkpoint feature enables RocksDB to create a consistent snapshot of a given RocksDB database in the specified directory. If the snapshot is on the same filesystem as the original database, the SST files will be hard-linked, otherwise SST files will be copied. The manifest and CURRENT files will be copied. In addition, if there are multiple column families, log files will be copied for the period covering the start and end of the checkpoint, in order to provide a consistent snapshot across column families. - - - - -A Checkpoint object needs to be created for a database before checkpoints are created. The API is as follows: - - - - -`Status Create(DB* db, Checkpoint** checkpoint_ptr);` - - - - -Given a checkpoint object and a directory, the CreateCheckpoint function creates a consistent snapshot of the database in the given directory. - - - - -`Status CreateCheckpoint(const std::string& checkpoint_dir);` - - - - -The directory should not already exist and will be created by this API. The directory will be an absolute path. The checkpoint can be used as a ​read-only copy of the DB or can be opened as a standalone DB. When opened read/write, the SST files continue to be hard links and these links are removed when the files are obsoleted. When the user is done with the snapshot, the user can delete the directory to remove the snapshot. - - - - -Checkpoints are used for online backup in ​MyRocks. which is MySQL using RocksDB as the storage engine . ([MySQL on RocksDB](https://github.com/facebook/mysql-5.6)) ​ diff --git a/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown b/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown deleted file mode 100644 index b21b04fe3..000000000 --- a/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown +++ /dev/null @@ -1,244 +0,0 @@ ---- -title: Analysis File Read Latency by Level -layout: post -author: sdong -category: blog -redirect_from: - - /blog/2537/analysis-file-read-latency-by-level/ ---- - -In many use cases of RocksDB, people rely on OS page cache for caching compressed data. With this approach, verifying effective of the OS page caching is challenging, because file system is a black box to users. - -As an example, a user can tune the DB as following: use level-based compaction, with L1 - L4 sizes to be 1GB, 10GB, 100GB and 1TB. And they reserve about 20GB memory as OS page cache, expecting level 0, 1 and 2 are mostly cached in memory, leaving only reads from level 3 and 4 requiring disk I/Os. However, in practice, it's not easy to verify whether OS page cache does exactly what we expect. For example, if we end up with doing 4 instead of 2 I/Os per query, it's not easy for users to figure out whether the it's because of efficiency of OS page cache or reading multiple blocks for a level. Analysis like it is especially important if users run RocksDB on hard drive disks, for the gap of latency between hard drives and memory is much higher than flash-based SSDs. - - - -In order to make tuning easier, we added new instrumentation to help users analysis latency distribution of file reads in different levels. If users turn DB statistics on, we always keep track of distribution of file read latency for each level. Users can retrieve the information by querying DB property “rocksdb.stats” ( [https://github.com/facebook/rocksdb/blob/v3.13.1/include/rocksdb/db.h#L315-L316](https://github.com/facebook/rocksdb/blob/v3.13.1/include/rocksdb/db.h#L315-L316) ). It will also printed out as a part of compaction summary in info logs periodically. - -The output looks like this: - - -``` -** Level 0 read latency histogram (micros): -Count: 696 Average: 489.8118 StdDev: 222.40 -Min: 3.0000 Median: 452.3077 Max: 1896.0000 -Percentiles: P50: 452.31 P75: 641.30 P99: 1068.00 P99.9: 1860.80 P99.99: 1896.00 ------------------------------------------------------- -[ 2, 3 ) 1 0.144% 0.144% -[ 18, 20 ) 1 0.144% 0.287% -[ 45, 50 ) 5 0.718% 1.006% -[ 50, 60 ) 26 3.736% 4.741% # -[ 60, 70 ) 6 0.862% 5.603% -[ 90, 100 ) 1 0.144% 5.747% -[ 120, 140 ) 2 0.287% 6.034% -[ 140, 160 ) 1 0.144% 6.178% -[ 160, 180 ) 1 0.144% 6.322% -[ 200, 250 ) 9 1.293% 7.615% -[ 250, 300 ) 45 6.466% 14.080% # -[ 300, 350 ) 88 12.644% 26.724% ### -[ 350, 400 ) 88 12.644% 39.368% ### -[ 400, 450 ) 71 10.201% 49.569% ## -[ 450, 500 ) 65 9.339% 58.908% ## -[ 500, 600 ) 74 10.632% 69.540% ## -[ 600, 700 ) 92 13.218% 82.759% ### -[ 700, 800 ) 64 9.195% 91.954% ## -[ 800, 900 ) 35 5.029% 96.983% # -[ 900, 1000 ) 12 1.724% 98.707% -[ 1000, 1200 ) 6 0.862% 99.569% -[ 1200, 1400 ) 2 0.287% 99.856% -[ 1800, 2000 ) 1 0.144% 100.000% - -** Level 1 read latency histogram (micros): -(......not pasted.....) - -** Level 2 read latency histogram (micros): -(......not pasted.....) - -** Level 3 read latency histogram (micros): -(......not pasted.....) - -** Level 4 read latency histogram (micros): -(......not pasted.....) - -** Level 5 read latency histogram (micros): -Count: 25583746 Average: 421.1326 StdDev: 385.11 -Min: 1.0000 Median: 376.0011 Max: 202444.0000 -Percentiles: P50: 376.00 P75: 438.00 P99: 1421.68 P99.9: 4164.43 P99.99: 9056.52 ------------------------------------------------------- -[ 0, 1 ) 2351 0.009% 0.009% -[ 1, 2 ) 6077 0.024% 0.033% -[ 2, 3 ) 8471 0.033% 0.066% -[ 3, 4 ) 788 0.003% 0.069% -[ 4, 5 ) 393 0.002% 0.071% -[ 5, 6 ) 786 0.003% 0.074% -[ 6, 7 ) 1709 0.007% 0.080% -[ 7, 8 ) 1769 0.007% 0.087% -[ 8, 9 ) 1573 0.006% 0.093% -[ 9, 10 ) 1495 0.006% 0.099% -[ 10, 12 ) 3043 0.012% 0.111% -[ 12, 14 ) 2259 0.009% 0.120% -[ 14, 16 ) 1233 0.005% 0.125% -[ 16, 18 ) 762 0.003% 0.128% -[ 18, 20 ) 451 0.002% 0.130% -[ 20, 25 ) 794 0.003% 0.133% -[ 25, 30 ) 1279 0.005% 0.138% -[ 30, 35 ) 1172 0.005% 0.142% -[ 35, 40 ) 1363 0.005% 0.148% -[ 40, 45 ) 409 0.002% 0.149% -[ 45, 50 ) 105 0.000% 0.150% -[ 50, 60 ) 80 0.000% 0.150% -[ 60, 70 ) 280 0.001% 0.151% -[ 70, 80 ) 1583 0.006% 0.157% -[ 80, 90 ) 4245 0.017% 0.174% -[ 90, 100 ) 6572 0.026% 0.200% -[ 100, 120 ) 9724 0.038% 0.238% -[ 120, 140 ) 3713 0.015% 0.252% -[ 140, 160 ) 2383 0.009% 0.261% -[ 160, 180 ) 18344 0.072% 0.333% -[ 180, 200 ) 51873 0.203% 0.536% -[ 200, 250 ) 631722 2.469% 3.005% -[ 250, 300 ) 2721970 10.639% 13.644% ## -[ 300, 350 ) 5909249 23.098% 36.742% ##### -[ 350, 400 ) 6522507 25.495% 62.237% ##### -[ 400, 450 ) 4296332 16.793% 79.030% ### -[ 450, 500 ) 2130323 8.327% 87.357% ## -[ 500, 600 ) 1553208 6.071% 93.428% # -[ 600, 700 ) 642129 2.510% 95.938% # -[ 700, 800 ) 372428 1.456% 97.394% -[ 800, 900 ) 187561 0.733% 98.127% -[ 900, 1000 ) 85858 0.336% 98.462% -[ 1000, 1200 ) 82730 0.323% 98.786% -[ 1200, 1400 ) 50691 0.198% 98.984% -[ 1400, 1600 ) 38026 0.149% 99.133% -[ 1600, 1800 ) 32991 0.129% 99.261% -[ 1800, 2000 ) 30200 0.118% 99.380% -[ 2000, 2500 ) 62195 0.243% 99.623% -[ 2500, 3000 ) 36684 0.143% 99.766% -[ 3000, 3500 ) 21317 0.083% 99.849% -[ 3500, 4000 ) 10216 0.040% 99.889% -[ 4000, 4500 ) 8351 0.033% 99.922% -[ 4500, 5000 ) 4152 0.016% 99.938% -[ 5000, 6000 ) 6328 0.025% 99.963% -[ 6000, 7000 ) 3253 0.013% 99.976% -[ 7000, 8000 ) 2082 0.008% 99.984% -[ 8000, 9000 ) 1546 0.006% 99.990% -[ 9000, 10000 ) 1055 0.004% 99.994% -[ 10000, 12000 ) 1566 0.006% 100.000% -[ 12000, 14000 ) 761 0.003% 100.003% -[ 14000, 16000 ) 462 0.002% 100.005% -[ 16000, 18000 ) 226 0.001% 100.006% -[ 18000, 20000 ) 126 0.000% 100.006% -[ 20000, 25000 ) 107 0.000% 100.007% -[ 25000, 30000 ) 43 0.000% 100.007% -[ 30000, 35000 ) 15 0.000% 100.007% -[ 35000, 40000 ) 14 0.000% 100.007% -[ 40000, 45000 ) 16 0.000% 100.007% -[ 45000, 50000 ) 1 0.000% 100.007% -[ 50000, 60000 ) 22 0.000% 100.007% -[ 60000, 70000 ) 10 0.000% 100.007% -[ 70000, 80000 ) 5 0.000% 100.007% -[ 80000, 90000 ) 14 0.000% 100.007% -[ 90000, 100000 ) 11 0.000% 100.007% -[ 100000, 120000 ) 33 0.000% 100.007% -[ 120000, 140000 ) 6 0.000% 100.007% -[ 140000, 160000 ) 3 0.000% 100.007% -[ 160000, 180000 ) 7 0.000% 100.007% -[ 200000, 250000 ) 2 0.000% 100.007% -``` - - -In this example, you can see we only issued 696 reads from level 0 while issued 25 million reads from level 5. The latency distribution is also clearly shown among those reads. This will be helpful for users to analysis OS page cache efficiency. - -Currently the read latency per level includes reads from data blocks, index blocks, as well as bloom filter blocks. We are also working on a feature to break down those three type of blocks. - -### Comments - -**[Tao Feng](fengtao04@gmail.com)** - -Is this feature also included in RocksJava? - -**[Siying Dong](siying.d@fb.com)** - -Should be. As long as you enable statistics, you should be able to get the value from `RocksDB.getProperty()` with property `rocksdb.dbstats`. Let me know if you can’t find it. - -**[chiddu](cnbscience@gmail.com)** - -> In this example, you can see we only issued 696 reads from level 0 while issued 256K reads from level 5. - -Isn’t it 2.5 M of reads instead of 256K ? . - -Also could anyone please provide more description on the histogram ? especially - -> Count: 25583746 Average: 421.1326 StdDev: 385.11 -> Min: 1.0000 Median: 376.0011 Max: 202444.0000 -> Percentiles: P50: 376.00 P75: 438.00 P99: 1421.68 P99.9: 4164.43 P99.99: 9056.52 - -and - -> [ 0, 1 ) 2351 0.009% 0.009% -> [ 1, 2 ) 6077 0.024% 0.033% -> [ 2, 3 ) 8471 0.033% 0.066% -> [ 3, 4 ) 788 0.003% 0.069%” - -thanks in advance - -**[Siying Dong](siying.d@fb.com)** - -Thank you for pointing out the mistake. I fixed it now. - -In this output, there are 2.5 million samples, average latency is 421 micro seconds, with standard deviation 385. Median is 376, max value is 202 milliseconds. 0.009% has value of 1, 0.024% has value of 1, 0.033% has value of 2. Accumulated value from 0 to 2 is 0.066%. - -Hope it helps. - -**[chiddu](cnbscience@gmail.com)** - -Thank you Siying for the quick reply, I was running couple of benchmark testing to check the performance of rocksdb on SSD. One of the test is similar to what is mentioned in the wiki, TEST 4 : Random read , except the key_size is 10 and value_size is 20. I am inserting 1 billion hashes and reading 1 billion hashes with 32 threads. The histogram shows something like this - -``` -Level 5 read latency histogram (micros): -Count: 7133903059 Average: 480.4357 StdDev: 309.18 -Min: 0.0000 Median: 551.1491 Max: 224142.0000 -Percentiles: P50: 551.15 P75: 651.44 P99: 996.52 P99.9: 2073.07 P99.99: 3196.32 -—————————————————— -[ 0, 1 ) 28587385 0.401% 0.401% -[ 1, 2 ) 686572516 9.624% 10.025% ## -[ 2, 3 ) 567317522 7.952% 17.977% ## -[ 3, 4 ) 44979472 0.631% 18.608% -[ 4, 5 ) 50379685 0.706% 19.314% -[ 5, 6 ) 64930061 0.910% 20.224% -[ 6, 7 ) 22613561 0.317% 20.541% -…………more…………. -``` - -If I understand your previous comment correctly, - -1. How is it that the count is around 7 billion when I have only inserted 1 billion hashes ? is the stat broken ? -1. What does the percentiles and the numbers signify ? -1. 0, 1 ) 28587385 0.401% 0.401% what does this “28587385” stand for in the histogram row ? - -**[Siying Dong](siying.d@fb.com)** - -If I remember correctly, with db_bench, if you specify –num=1000000000 –threads=32, it is every thread reading one billion keys, total of 32 billions. Is it the case you ran into? - -28,587,385 means that number of data points take the value [0,1) -28,587,385 / 7,133,903,058 = 0.401% provides percentage. - -**[chiddu](cnbscience@gmail.com)** - -I do have `num=1000000000` and `t=32`. The script says reading 1 billion hashes and not 32 billion hashes. - -this is the script on which I have used - -``` -echo “Load 1B keys sequentially into database…..” -bpl=10485760;overlap=10;mcz=2;del=300000000;levels=6;ctrig=4; delay=8; stop=12; wbn=3; mbc=20; mb=67108864;wbs=134217728; dds=1; sync=0; r=1000000000; t=1; vs=20; bs=4096; cs=1048576; of=500000; si=1000000; ./db_bench –benchmarks=fillseq –disable_seek_compaction=1 –mmap_read=0 –statistics=1 –histogram=1 –num=$r –threads=$t –value_size=$vs –block_size=$bs –cache_size=$cs –bloom_bits=10 –cache_numshardbits=6 –open_files=$of –verify_checksum=1 –db=/data/mysql/leveldb/test –sync=$sync –disable_wal=1 –compression_type=none –stats_interval=$si –compression_ratio=0.5 –disable_data_sync=$dds –write_buffer_size=$wbs –target_file_size_base=$mb –max_write_buffer_number=$wbn –max_background_compactions=$mbc –level0_file_num_compaction_trigger=$ctrig –level0_slowdown_writes_trigger=$delay –level0_stop_writes_trigger=$stop –num_levels=$levels –delete_obsolete_files_period_micros=$del –min_level_to_compress=$mcz –max_grandparent_overlap_factor=$overlap –stats_per_interval=1 –max_bytes_for_level_base=$bpl –use_existing_db=0 –key_size=10 - -echo “Reading 1B keys in database in random order….” -bpl=10485760;overlap=10;mcz=2;del=300000000;levels=6;ctrig=4; delay=8; stop=12; wbn=3; mbc=20; mb=67108864;wbs=134217728; dds=0; sync=0; r=1000000000; t=32; vs=20; bs=4096; cs=1048576; of=500000; si=1000000; ./db_bench –benchmarks=readrandom –disable_seek_compaction=1 –mmap_read=0 –statistics=1 –histogram=1 –num=$r –threads=$t –value_size=$vs –block_size=$bs –cache_size=$cs –bloom_bits=10 –cache_numshardbits=6 –open_files=$of –verify_checksum=1 –db=/some_data_base –sync=$sync –disable_wal=1 –compression_type=none –stats_interval=$si –compression_ratio=0.5 –disable_data_sync=$dds –write_buffer_size=$wbs –target_file_size_base=$mb –max_write_buffer_number=$wbn –max_background_compactions=$mbc –level0_file_num_compaction_trigger=$ctrig –level0_slowdown_writes_trigger=$delay –level0_stop_writes_trigger=$stop –num_levels=$levels –delete_obsolete_files_period_micros=$del –min_level_to_compress=$mcz –max_grandparent_overlap_factor=$overlap –stats_per_interval=1 –max_bytes_for_level_base=$bpl –use_existing_db=1 –key_size=10 -``` - -After running this script, there were no issues wrt to loading billion hashes , but when it came to reading part, its been almost 4 days and still I have only read 7 billion hashes and have read 200 million hashes in 2 and half days. Is there something which is missing in db_bench or something which I am missing ? - -**[Siying Dong](siying.d@fb.com)** - -It’s a printing error then. If you have `num=1000000000` and `t=32`, it will be 32 threads, and each reads 1 billion keys. diff --git a/docs/_posts/2016-01-29-compaction_pri.markdown b/docs/_posts/2016-01-29-compaction_pri.markdown deleted file mode 100644 index ba9ee627c..000000000 --- a/docs/_posts/2016-01-29-compaction_pri.markdown +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Option of Compaction Priority -layout: post -author: sdong -category: blog -redirect_from: - - /blog/2921/compaction_pri/ ---- - -The most popular compaction style of RocksDB is level-based compaction, which is an improved version of LevelDB's compaction algorithm. Page 9- 16 of this [slides](https://github.com/facebook/rocksdb/blob/gh-pages/talks/2015-09-29-HPTS-Siying-RocksDB.pdf) gives an illustrated introduction of this compaction style. The basic idea that: data is organized by multiple levels with exponential increasing target size. Except a special level 0, every level is key-range partitioned into many files. When size of a level exceeds its target size, we pick one or more of its files, and merge the file into the next level. - - - -Which file to pick to compact is an interesting question. LevelDB only uses one thread for compaction and it always picks files in round robin manner. We implemented multi-thread compaction in RocksDB by picking multiple files from the same level and compact them in parallel. We had to move away from LevelDB's file picking approach. Recently, we created an option [options.compaction_pri](https://github.com/facebook/rocksdb/blob/d6c838f1e130d8860407bc771fa6d4ac238859ba/include/rocksdb/options.h#L83-L93), which indicated three different algorithms to pick files to compact. - -Why do we need to multiple algorithms to choose from? Because there are different factors to consider when picking the files, and we now don't yet know how to balance them automatically, so we expose it to users to choose. Here are factors to consider: - -**Write amplification** - -When we estimate write amplification, we usually simplify the problem by assuming keys are uniformly distributed inside each level. In reality, it is not the case, even if user updates are uniformly distributed across the whole key range. For instance, when we compact one file of a level to the next level, it creates a hole. Over time, incoming compaction will fill data to the hole, but the density will still be lower for a while. Picking a file with keys least densely populated is more expensive to get the file to the next level, because there will be more overlapping files in the next level so we need to rewrite more data. For example, assume a file is 100MB, if an L2 file overlaps with 8 L3 files, we need to rewrite about 800MB of data to get the file to L3. If the file overlaps with 12 L3 files, we'll need to rewrite about 1200MB to get a file of the same size out of L2. It uses 50% more writes. (This analysis ignores the key density of the next level, because the range covers N times of files in that level so one hole only impacts write amplification by 1/N) - -If all the updates are uniformly distributed, LevelDB's approach optimizes write amplification, because a file being picked covers a range whose last compaction time to the next level is the oldest, so the range will accumulated keys from incoming compactions for the longest and the density is the highest. - -We created a compaction priority **kOldestSmallestSeqFirst** for the same effect. With this mode, we always pick the file covers the oldest updates in the level, which usually is contains the densest key range. If you have a use case where writes are uniformly distributed across the key space and you want to reduce write amplification, you should set options.compaction_pri=kOldestSmallestSeqFirst. - -**Optimize for small working set** - -We are assuming updates are uniformly distributed across the whole key space in previous analysis. However, in many use cases, there are subset of keys that are frequently updated while other key ranges are very cold. In this case, keeping hot key ranges from compacting to deeper levels will benefit write amplification, as well as space amplification. For example, if in a DB only key 150-160 are updated and other keys are seldom updated. If level 1 contains 20 keys, we want to keep 150-160 all stay in level 1. Because when next level 0 -> 1 compaction comes, it will simply overwrite existing keys so size level 1 doesn't increase, so no need to schedule further compaction for level 1->2. On the other hand, if we compact key 150-155 to level2, when a new Level 1->2 compaction comes, it increases the size of level 1, making size of level 1 exceed target size and more compactions will be needed, which generates more writes. - -The compaction priority **kOldestLargestSeqFirst** optimizes this use case. In this mode, we will pick a file whose latest update is the oldest. It means there is no incoming data for the range for the longest. Usually it is the coldest range. By compacting coldest range first, we leave the hot ranges in the level. If your use case is to overwrite existing keys in a small range, try options.compaction_pri=kOldestLargestSeqFirst**.** - -**Drop delete marker sooner** - -If one file contains a lot of delete markers, it may slow down iterating over this area, because we still need to iterate those deleted keys just to ignore them. Furthermore, the sooner we compact delete keys into the last level, the sooner the disk space is reclaimed, so it is good for space efficiency. - -Our default compaction priority **kByCompensatedSize** considers the case. If number of deletes in a file exceeds number of inserts, it is more likely to be picked for compaction. The more number of deletes exceed inserts, the more likely it is being compacted. The optimization is added to avoid the worst performance of space efficiency and query performance when a large percentage of the DB is deleted. - -**Efficiency of compaction filter** - -Usually people use [compaction filters](https://github.com/facebook/rocksdb/blob/v4.1/include/rocksdb/options.h#L201-L226) to clean up old data to free up space. Picking files to compact may impact space efficiency. We don't yet have a a compaction priority to optimize this case. In some of our use cases, we solved the problem in a different way: we have an external service checking modify time of all SST files. If any of the files is too old, we force the single file to compaction by calling DB::CompactFiles() using the single file. In this way, we can provide a time bound of data passing through compaction filters. - - -In all, there three choices of compaction priority modes optimizing different scenarios. if you have a new use case, we suggest you start with `options.compaction_pri=kOldestSmallestSeqFirst` (note it is not the default one for backward compatible reason). If you want to further optimize your use case, you can try other two use cases if your use cases apply. - -If you have good ideas about better compaction picker approach, you are welcome to implement and benchmark it. We'll be glad to review and merge your a pull requests. - -### Comments - -**[Mark Callaghan](mdcallag@gmail.com)** - -Performance results for compaction_pri values and linkbench are explained at [http://smalldatum.blogspot.com/2016/02/compaction-priority-in-rocksdb.html](http://smalldatum.blogspot.com/2016/02/compaction-priority-in-rocksdb.html) diff --git a/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown b/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown deleted file mode 100644 index 409015cc8..000000000 --- a/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: RocksDB 4.2 Release! -layout: post -author: sdong -category: blog -redirect_from: - - /blog/3017/rocksdb-4-2-release/ ---- - -New RocksDB release - 4.2! - - -**New Features** - - 1. Introduce CreateLoggerFromOptions(), this function create a Logger for provided DBOptions. - - - 2. Add GetAggregatedIntProperty(), which returns the sum of the GetIntProperty of all the column families. - - - 3. Add MemoryUtil in rocksdb/utilities/memory.h. It currently offers a way to get the memory usage by type from a list rocksdb instances. - - - - - -**Public API changes** - - 1. CompactionFilter::Context includes information of Column Family ID - - - 2. The need-compaction hint given by TablePropertiesCollector::NeedCompact() will be persistent and recoverable after DB recovery. This introduces a breaking format change. If you use this experimental feature, including NewCompactOnDeletionCollectorFactory() in the new version, you may not be able to directly downgrade the DB back to version 4.0 or lower. - - - 3. TablePropertiesCollectorFactory::CreateTablePropertiesCollector() now takes an option Context, containing the information of column family ID for the file being written. - - - 4. Remove DefaultCompactionFilterFactory. - - -[https://github.com/facebook/rocksdb/releases/tag/v4.2](https://github.com/facebook/rocksdb/releases/tag/v4.2) diff --git a/docs/_posts/2016-02-25-rocksdb-ama.markdown b/docs/_posts/2016-02-25-rocksdb-ama.markdown deleted file mode 100644 index 2ba04f39a..000000000 --- a/docs/_posts/2016-02-25-rocksdb-ama.markdown +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: RocksDB AMA -layout: post -author: yhchiang -category: blog -redirect_from: - - /blog/3065/rocksdb-ama/ ---- - -RocksDB developers are doing a Reddit Ask-Me-Anything now at 10AM – 11AM PDT! We welcome you to stop by and ask any RocksDB related questions, including existing / upcoming features, tuning tips, or database design. - -Here are some enhancements that we'd like to focus on over the next six months: - -* 2-Phase Commit -* Lua support in some custom functions -* Backup and repair tools -* Direct I/O to bypass OS cache -* RocksDB Java API - -[https://www.reddit.com/r/IAmA/comments/47k1si/we_are_rocksdb_developers_ask_us_anything/](https://www.reddit.com/r/IAmA/comments/47k1si/we_are_rocksdb_developers_ask_us_anything/) diff --git a/docs/_posts/2016-03-07-rocksdb-options-file.markdown b/docs/_posts/2016-03-07-rocksdb-options-file.markdown deleted file mode 100644 index 703449b01..000000000 --- a/docs/_posts/2016-03-07-rocksdb-options-file.markdown +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: RocksDB Options File -layout: post -author: yhciang -category: blog -redirect_from: - - /blog/3089/rocksdb-options-file/ ---- - -In RocksDB 4.3, we added a new set of features that makes managing RocksDB options easier. Specifically: - - * **Persisting Options Automatically**: Each RocksDB database will now automatically persist its current set of options into an INI file on every successful call of DB::Open(), SetOptions(), and CreateColumnFamily() / DropColumnFamily(). - - - - * **Load Options from File**: We added [LoadLatestOptions() / LoadOptionsFromFile()](https://github.com/facebook/rocksdb/blob/4.3.fb/include/rocksdb/utilities/options_util.h#L48-L58) that enables developers to construct RocksDB options object from an options file. - - - - * **Sanity Check Options**: We added [CheckOptionsCompatibility](https://github.com/facebook/rocksdb/blob/4.3.fb/include/rocksdb/utilities/options_util.h#L64-L77) that performs compatibility check on two sets of RocksDB options. - - - -Want to know more about how to use this new features? Check out the [RocksDB Options File wiki page](https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File) and start using this new feature today! diff --git a/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown b/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown deleted file mode 100644 index 247768d30..000000000 --- a/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: RocksDB 4.5.1 Released! -layout: post -author: sdong -category: blog -redirect_from: - - /blog/3179/rocksdb-4-5-1-released/ ---- - -## 4.5.1 (3/25/2016) - -### Bug Fixes - - *  Fix failures caused by the destorying order of singleton objects. - -
- -## 4.5.0 (2/5/2016) - -### Public API Changes - - * Add a new perf context level between kEnableCount and kEnableTime. Level 2 now does not include timers for mutexes. - * Statistics of mutex operation durations will not be measured by default. If you want to have them enabled, you need to set Statistics::stats_level_ to kAll. - * DBOptions::delete_scheduler and NewDeleteScheduler() are removed, please use DBOptions::sst_file_manager and NewSstFileManager() instead - -### New Features - * ldb tool now supports operations to non-default column families. - * Add kPersistedTier to ReadTier. This option allows Get and MultiGet to read only the persited data and skip mem-tables if writes were done with disableWAL = true. - * Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate. - -
- - - -## 4.4.0 (1/14/2016) - -### Public API Changes - - * Change names in CompactionPri and add a new one. - * Deprecate options.soft_rate_limit and add options.soft_pending_compaction_bytes_limit. - * If options.max_write_buffer_number > 3, writes will be slowed down when writing to the last write buffer to delay a full stop. - * Introduce CompactionJobInfo::compaction_reason, this field include the reason to trigger the compaction. - * After slow down is triggered, if estimated pending compaction bytes keep increasing, slowdown more. - * Increase default options.delayed_write_rate to 2MB/s. - * Added a new parameter --path to ldb tool. --path accepts the name of either MANIFEST, SST or a WAL file. Either --db or --path can be used when calling ldb. - -
- -## 4.3.0 (12/8/2015) - -### New Features - - * CompactionFilter has new member function called IgnoreSnapshots which allows CompactionFilter to be called even if there are snapshots later than the key. - * RocksDB will now persist options under the same directory as the RocksDB database on successful DB::Open, CreateColumnFamily, DropColumnFamily, and SetOptions. - * Introduce LoadLatestOptions() in rocksdb/utilities/options_util.h. This function can construct the latest DBOptions / ColumnFamilyOptions used by the specified RocksDB intance. - * Introduce CheckOptionsCompatibility() in rocksdb/utilities/options_util.h. This function checks whether the input set of options is able to open the specified DB successfully. - -### Public API Changes - - * When options.db_write_buffer_size triggers, only the column family with the largest column family size will be flushed, not all the column families. diff --git a/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown b/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown deleted file mode 100644 index 0db275ddf..000000000 --- a/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: RocksDB 4.8 Released! -layout: post -author: yiwu -category: blog -redirect_from: - - /blog/3239/rocksdb-4-8-released/ ---- - -## 4.8.0 (5/2/2016) - -### [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#public-api-change-1)Public API Change - - * Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes. - * Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see [https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F) - * Expose estimate of per-level compression ratio via DB property: "rocksdb.compression-ratio-at-levelN". - * Added EventListener::OnTableFileCreationStarted. EventListener::OnTableFileCreated will be called on failure case. User can check creation status via TableFileCreationInfo::status. - -### [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#new-features-2)New Features - - * Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size. - -
- - - -## [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#470-482016)4.7.0 (4/8/2016) - -### [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#public-api-change-2)Public API Change - - * rename options compaction_measure_io_stats to report_bg_io_stats and include flush too. - * Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See [https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File](https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File) for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options. - -
- -## [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#460-3102016)4.6.0 (3/10/2016) - -### [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#public-api-changes-1)Public API Changes - - * Change default of BlockBasedTableOptions.format_version to 2. It means default DB created by 4.6 or up cannot be opened by RocksDB version 3.9 or earlier - * Added strict_capacity_limit option to NewLRUCache. If the flag is set to true, insert to cache will fail if no enough capacity can be free. Signature of Cache::Insert() is updated accordingly. - * Tickers [NUMBER_DB_NEXT, NUMBER_DB_PREV, NUMBER_DB_NEXT_FOUND, NUMBER_DB_PREV_FOUND, ITER_BYTES_READ] are not updated immediately. The are updated when the Iterator is deleted. - * Add monotonically increasing counter (DB property "rocksdb.current-super-version-number") that increments upon any change to the LSM tree. - -### [](https://github.com/facebook/rocksdb/blob/main/HISTORY.md#new-features-3)New Features - - * Add CompactionPri::kMinOverlappingRatio, a compaction picking mode friendly to write amplification. - * Deprecate Iterator::IsKeyPinned() and replace it with Iterator::GetProperty() with prop_name="rocksdb.iterator.is.key.pinned" diff --git a/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown b/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown deleted file mode 100644 index 87c20eb47..000000000 --- a/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: RocksDB 4.11.2 Released! -layout: post -author: sdong -category: blog ---- -We abandoned release candidates 4.10.x and directly go to 4.11.2 from 4.9, to make sure the latest release is stable. In 4.11.2, we fixed several data corruption related bugs introduced in 4.9.0. - -## 4.11.2 (9/15/2016) - -### Bug fixes - - * Segfault when failing to open an SST file for read-ahead iterators. - * WAL without data for all CFs is not deleted after recovery. - - - -## 4.11.1 (8/30/2016) - -### Bug Fixes - - * Mitigate the regression bug of deadlock condition during recovery when options.max_successive_merges hits. - * Fix data race condition related to hash index in block based table when putting indexes in the block cache. - -## 4.11.0 (8/1/2016) - -### Public API Change - - * options.memtable_prefix_bloom_huge_page_tlb_size => memtable_huge_page_size. When it is set, RocksDB will try to allocate memory from huge page for memtable too, rather than just memtable bloom filter. - -### New Features - - * A tool to migrate DB after options change. See include/rocksdb/utilities/option_change_migration.h. - * Add ReadOptions.background_purge_on_iterator_cleanup. If true, we avoid file deletion when destorying iterators. - -## 4.10.0 (7/5/2016) - -### Public API Change - - * options.memtable_prefix_bloom_bits changes to options.memtable_prefix_bloom_bits_ratio and deprecate options.memtable_prefix_bloom_probes - * enum type CompressionType and PerfLevel changes from char to unsigned char. Value of all PerfLevel shift by one. - * Deprecate options.filter_deletes. - -### New Features - - * Add avoid_flush_during_recovery option. - * Add a read option background_purge_on_iterator_cleanup to avoid deleting files in foreground when destroying iterators. Instead, a job is scheduled in high priority queue and would be executed in a separate background thread. - * RepairDB support for column families. RepairDB now associates data with non-default column families using information embedded in the SST/WAL files (4.7 or later). For data written by 4.6 or earlier, RepairDB associates it with the default column family. - * Add options.write_buffer_manager which allows users to control total memtable sizes across multiple DB instances. diff --git a/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown b/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown deleted file mode 100644 index fb0413055..000000000 --- a/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: RocksDB 5.0.1 Released! -layout: post -author: yiwu -category: blog ---- - -### Public API Change - - * Options::max_bytes_for_level_multiplier is now a double along with all getters and setters. - * Support dynamically change `delayed_write_rate` and `max_total_wal_size` options via SetDBOptions(). - * Introduce DB::DeleteRange for optimized deletion of large ranges of contiguous keys. - * Support dynamically change `delayed_write_rate` option via SetDBOptions(). - * Options::allow_concurrent_memtable_write and Options::enable_write_thread_adaptive_yield are now true by default. - * Remove Tickers::SEQUENCE_NUMBER to avoid confusion if statistics object is shared among RocksDB instance. Alternatively DB::GetLatestSequenceNumber() can be used to get the same value. - * Options.level0_stop_writes_trigger default value changes from 24 to 32. - * New compaction filter API: CompactionFilter::FilterV2(). Allows to drop ranges of keys. - * Removed flashcache support. - * DB::AddFile() is deprecated and is replaced with DB::IngestExternalFile(). DB::IngestExternalFile() remove all the restrictions that existed for DB::AddFile. - -### New Features - - * Add avoid_flush_during_shutdown option, which speeds up DB shutdown by not flushing unpersisted data (i.e. with disableWAL = true). Unpersisted data will be lost. The options is dynamically changeable via SetDBOptions(). - * Add memtable_insert_with_hint_prefix_extractor option. The option is mean to reduce CPU usage for inserting keys into memtable, if keys can be group by prefix and insert for each prefix are sequential or almost sequential. See include/rocksdb/options.h for more details. - * Add LuaCompactionFilter in utilities. This allows developers to write compaction filters in Lua. To use this feature, LUA_PATH needs to be set to the root directory of Lua. - * No longer populate "LATEST_BACKUP" file in backup directory, which formerly contained the number of the latest backup. The latest backup can be determined by finding the highest numbered file in the "meta/" subdirectory. diff --git a/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown b/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown deleted file mode 100644 index 35bafb219..000000000 --- a/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: RocksDB 5.1.2 Released! -layout: post -author: maysamyabandeh -category: blog ---- - -### Public API Change -* Support dynamically change `delete_obsolete_files_period_micros` option via SetDBOptions(). -* Added EventListener::OnExternalFileIngested which will be called when IngestExternalFile() add a file successfully. -* BackupEngine::Open and BackupEngineReadOnly::Open now always return error statuses matching those of the backup Env. - -### Bug Fixes -* Fix the bug that if 2PC is enabled, checkpoints may loss some recent transactions. -* When file copying is needed when creating checkpoints or bulk loading files, fsync the file after the file copying. diff --git a/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown b/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown deleted file mode 100644 index 9a43a846a..000000000 --- a/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Bulkloading by ingesting external SST files -layout: post -author: IslamAbdelRahman -category: blog ---- - -## Introduction - -One of the basic operations of RocksDB is writing to RocksDB, Writes happen when user call (DB::Put, DB::Write, DB::Delete ... ), but what happens when you write to RocksDB ? .. this is a brief description of what happens. -- User insert a new key/value by calling DB::Put() (or DB::Write()) -- We create a new entry for the new key/value in our in-memory structure (memtable / SkipList by default) and we assign it a new sequence number. -- When the memtable exceeds a specific size (64 MB for example), we convert this memtable to a SST file, and put this file in level 0 of our LSM-Tree -- Later, compaction will kick in and move data from level 0 to level 1, and then from level 1 to level 2 .. and so on - -But what if we can skip these steps and add data to the lowest possible level directly ? This is what bulk-loading does - -## Bulkloading - -- Write all of our keys and values into SST file outside of the DB -- Add the SST file into the LSM directly - -This is bulk-loading, and in specific use-cases it allow users to achieve faster data loading and better write-amplification. - -and doing it is as simple as -```cpp -Options options; -SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator); -Status s = sst_file_writer.Open(file_path); -assert(s.ok()); - -// Insert rows into the SST file, note that inserted keys must be -// strictly increasing (based on options.comparator) -for (...) { - s = sst_file_writer.Add(key, value); - assert(s.ok()); -} - -// Ingest the external SST file into the DB -s = db_->IngestExternalFile({"/home/usr/file1.sst"}, IngestExternalFileOptions()); -assert(s.ok()); -``` - -You can find more details about how to generate SST files and ingesting them into RocksDB in this [wiki page](https://github.com/facebook/rocksdb/wiki/Creating-and-Ingesting-SST-files) - -## Use cases -There are multiple use cases where bulkloading could be useful, for example -- Generating SST files in offline jobs in Hadoop, then downloading and ingesting the SST files into RocksDB -- Migrating shards between machines by dumping key-range in SST File and loading the file in a different machine -- Migrating from a different storage (InnoDB to RocksDB migration in MyRocks) diff --git a/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown b/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown deleted file mode 100644 index c6ce27d64..000000000 --- a/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: RocksDB 5.2.1 Released! -layout: post -author: sdong -category: blog ---- - -### Public API Change -* NewLRUCache() will determine number of shard bits automatically based on capacity, if the user doesn't pass one. This also impacts the default block cache when the user doesn't explict provide one. -* Change the default of delayed slowdown value to 16MB/s and further increase the L0 stop condition to 36 files. - -### New Features -* Added new overloaded function GetApproximateSizes that allows to specify if memtable stats should be computed only without computing SST files' stats approximations. -* Added new function GetApproximateMemTableStats that approximates both number of records and size of memtables. -* (Experimental) Two-level indexing that partition the index and creates a 2nd level index on the partitions. The feature can be enabled by setting kTwoLevelIndexSearch as IndexType and configuring index_per_partition. - -### Bug Fixes -* RangeSync() should work if ROCKSDB_FALLOCATE_PRESENT is not set -* Fix wrong results in a data race case in Get() -* Some fixes related to 2PC. -* Fix several bugs in Direct I/O supports. -* Fix a regression bug which can cause Seek() to miss some keys if the return key has been updated many times after the snapshot which is used by the iterator. diff --git a/docs/_posts/2017-05-12-partitioned-index-filter.markdown b/docs/_posts/2017-05-12-partitioned-index-filter.markdown deleted file mode 100644 index a537feb0c..000000000 --- a/docs/_posts/2017-05-12-partitioned-index-filter.markdown +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Partitioned Index/Filters -layout: post -author: maysamyabandeh -category: blog ---- - -As DB/mem ratio gets larger, the memory footprint of filter/index blocks becomes non-trivial. Although `cache_index_and_filter_blocks` allows storing only a subset of them in block cache, their relatively large size negatively affects the performance by i) occupying the block cache space that could otherwise be used for caching data, ii) increasing the load on the disk storage by loading them into the cache after a miss. Here we illustrate these problems in more detail and explain how partitioning index/filters alleviates the overhead. - -### How large are the index/filter blocks? - -RocksDB has by default one index/filter block per SST file. The size of the index/filter varies based on the configuration but for a SST of size 256MB the index/filter block of size 0.5/5MB is typical, which is much larger than the typical data block size of 4-32KB. That is fine when all index/filters fit perfectly into memory and hence are read once per SST lifetime, not so much when they compete with data blocks for the block cache space and are also likely to be re-read many times from the disk. - -### What is the big deal with large index/filter blocks? - -When index/filter blocks are stored in block cache they are effectively competing with data blocks (as well as with each other) on this scarce resource. A filter of size 5MB is occupying the space that could otherwise be used to cache 1000s of data blocks (of size 4KB). This would result in more cache misses for data blocks. The large index/filters also kick each other out of the block cache more often and exacerbate their own cache miss rate too. This is while only a small part of the index/filter block might have been actually used during its lifetime in the cache. - -After the cache miss of an index/filter, it has to be reloaded from the disk, and its large size is not helping in reducing the IO cost. While a simple point lookup might need at most a couple of data block reads (of size 4KB) one from each layer of LSM, it might end up also loading multiple megabytes of index/filter blocks. If that happens often then the disk is spending more time serving index/filters rather than the actual data blocks. - -## What is partitioned index/filters? - -With partitioning, the index/filter of a SST file is partitioned into smaller blocks with an additional top-level index on them. When reading an index/filter, only top-level index is loaded into memory. The partitioned index/filter then uses the top-level index to load on demand into the block cache the partitions that are required to perform the index/filter query. The top-level index, which has much smaller memory footprint, can be stored in heap or block cache depending on the `cache_index_and_filter_blocks` setting. - -### Success stories - -#### HDD, 100TB DB - -In this example we have a DB of size 86G on HDD and emulate the small memory that is present to a node with 100TB of data by using direct IO (skipping OS file cache) and a very small block cache of size 60MB. Partitioning improves throughput by 11x from 5 op/s to 55 op/s. - -#### SSD, Linkbench - -In this example we have a DB of size 300G on SSD and emulate the small memory that would be available in presence of other DBs on the same node by by using direct IO (skipping OS file cache) and block cache of size 6G and 2G. Without partitioning the linkbench throughput drops from 38k tps to 23k when reducing block cache size from 6G to 2G. With partitioning the throughput drops from 38k to only 30k. - -Learn more [here](https://github.com/facebook/rocksdb/wiki/Partitioned-Index-Filters). diff --git a/docs/_posts/2017-05-14-core-local-stats.markdown b/docs/_posts/2017-05-14-core-local-stats.markdown deleted file mode 100644 index a806541fc..000000000 --- a/docs/_posts/2017-05-14-core-local-stats.markdown +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Core-local Statistics -layout: post -author: ajkr -category: blog ---- - -## Origins: Global Atomics - -Until RocksDB 4.12, ticker/histogram statistics were implemented with std::atomic values shared across the entire program. A ticker consists of a single atomic, while a histogram consists of several atomics to represent things like min/max/per-bucket counters. These statistics could be updated by all user/background threads. - -For concurrent/high-throughput workloads, cache line bouncing of atomics caused high CPU utilization. For example, we have tickers that count block cache hits and misses. Almost every user read increments these tickers a few times. Many concurrent user reads would cause the cache lines containing these atomics to bounce between cores. - -### Performance - -Here are perf results for 32 reader threads where most reads (99%+) are served by uncompressed block cache. Such a scenario stresses the statistics code heavily. - -Benchmark command: `TEST_TMPDIR=/dev/shm/ perf record -g ./db_bench -statistics -use_existing_db=true -benchmarks=readrandom -threads=32 -cache_size=1048576000 -num=1000000 -reads=1000000 && perf report -g --children` - -Perf snippet for "cycles" event: - -``` - Children Self Command Shared Object Symbol -+ 30.33% 30.17% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick -+ 3.65% 0.98% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime -``` - -Perf snippet for "cache-misses" event: - -``` - Children Self Command Shared Object Symbol -+ 19.54% 19.50% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick -+ 3.44% 0.57% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime -``` - -The high CPU overhead for updating tickers and histograms corresponds well to the high cache misses. - -## Thread-locals: Faster Updates - -Since RocksDB 4.12, ticker/histogram statistics use thread-local storage. Each thread has a local set of atomic values that no other thread can update. This prevents the cache line bouncing problem described above. Even though updates to a given value are always made by the same thread, atomics are still useful to synchronize with aggregations for querying statistics. - -Implementing this approach involved a couple challenges. First, each query for a statistic's global value must aggregate all threads' local values. This adds some overhead, which may pass unnoticed if statistics are queried infrequently. Second, exited threads' local values are still needed to provide accurate statistics. We handle this by merging a thread's local values into process-wide variables upon thread exit. - -### Performance - -Update benchmark setup is same as before. CPU overhead improved 7.8x compared to global atomics, corresponding to a 17.8x reduction in cache-misses overhead. - -Perf snippet for "cycles" event: - -``` - Children Self Command Shared Object Symbol -+ 2.96% 0.87% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick -+ 1.37% 0.10% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime -``` - -Perf snippet for "cache-misses" event: - -``` - Children Self Command Shared Object Symbol -+ 1.21% 0.65% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick - 0.08% 0.00% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime -``` - -To measure statistics query latency, we ran sysbench with 4K OLTP clients concurrently with one client that queries statistics repeatedly. Times shown are in milliseconds. - -``` - min: 18.45 - avg: 27.91 - max: 231.65 - 95th percentile: 55.82 -``` - -## Core-locals: Faster Querying - -The thread-local approach is working well for applications calling RocksDB from only a few threads, or polling statistics infrequently. Eventually, though, we found use cases where those assumptions do not hold. For example, one application has per-connection threads and typically runs into performance issues when connection count grows very high. For debugging such issues, they want high-frequency statistics polling to correlate issues in their application with changes in RocksDB's state. - -Once [PR #2258](https://github.com/facebook/rocksdb/pull/2258) lands, ticker/histogram statistics will be local to each CPU core. Similarly to thread-local, each core updates only its local values, thus avoiding cache line bouncing. Local values are still atomics to make aggregation possible. With this change, query work depends only on number of cores, not the number of threads. So, applications with many more threads than cores can no longer impact statistics query latency. - -### Performance - -Update benchmark setup is same as before. CPU overhead worsened ~23% compared to thread-local, while cache performance was unchanged. - -Perf snippet for "cycles" event: - -``` - Children Self Command Shared Object Symbol -+ 2.96% 0.87% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick -+ 1.37% 0.10% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime -``` - -Perf snippet for "cache-misses" event: - -``` - Children Self Command Shared Object Symbol -+ 1.21% 0.65% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick - 0.08% 0.00% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime -``` - -Query latency is measured same as before with times in milliseconds. Average latency improved by 6.3x compared to thread-local. - -``` - min: 2.47 - avg: 4.45 - max: 91.13 - 95th percentile: 7.56 -``` diff --git a/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown b/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown deleted file mode 100644 index 561dab4c2..000000000 --- a/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: RocksDB 5.4.5 Released! -layout: post -author: sagar0 -category: blog ---- - -### Public API Change -* Support dynamically changing `stats_dump_period_sec` option via SetDBOptions(). -* Added ReadOptions::max_skippable_internal_keys to set a threshold to fail a request as incomplete when too many keys are being skipped while using iterators. -* DB::Get in place of std::string accepts PinnableSlice, which avoids the extra memcpy of value to std::string in most of cases. - * PinnableSlice releases the pinned resources that contain the value when it is destructed or when ::Reset() is called on it. - * The old API that accepts std::string, although discouraged, is still supported. -* Replace Options::use_direct_writes with Options::use_direct_io_for_flush_and_compaction. See Direct IO wiki for details. - -### New Features -* Memtable flush can be avoided during checkpoint creation if total log file size is smaller than a threshold specified by the user. -* Introduce level-based L0->L0 compactions to reduce file count, so write delays are incurred less often. -* (Experimental) Partitioning filters which creates an index on the partitions. The feature can be enabled by setting partition_filters when using kFullFilter. Currently the feature also requires two-level indexing to be enabled. Number of partitions is the same as the number of partitions for indexes, which is controlled by metadata_block_size. -* DB::ResetStats() to reset internal stats. -* Added CompactionEventListener and EventListener::OnFlushBegin interfaces. -* Added DB::CreateColumnFamilie() and DB::DropColumnFamilies() to bulk create/drop column families. -* Facility for cross-building RocksJava using Docker. - -### Bug Fixes -* Fix WriteBatchWithIndex address use after scope error. -* Fix WritableFile buffer size in direct IO. -* Add prefetch to PosixRandomAccessFile in buffered io. -* Fix PinnableSlice access invalid address when row cache is enabled. -* Fix huge fallocate calls fail and make XFS unhappy. -* Fix memory alignment with logical sector size. -* Fix alignment in ReadaheadRandomAccessFile. -* Fix bias with read amplification stats (READ_AMP_ESTIMATE_USEFUL_BYTES and READ_AMP_TOTAL_READ_BYTES). -* Fix a manual / auto compaction data race. -* Fix CentOS 5 cross-building of RocksJava. -* Build and link with ZStd when creating the static RocksJava build. -* Fix snprintf's usage to be cross-platform. -* Fix build errors with blob DB. -* Fix readamp test type inconsistency. diff --git a/docs/_posts/2017-06-26-17-level-based-changes.markdown b/docs/_posts/2017-06-26-17-level-based-changes.markdown deleted file mode 100644 index 9e838eb7f..000000000 --- a/docs/_posts/2017-06-26-17-level-based-changes.markdown +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Level-based Compaction Changes -layout: post -author: ajkr -category: blog ---- - -### Introduction - -RocksDB provides an option to limit the number of L0 files, which bounds read-amplification. Since L0 files (unlike files at lower levels) can span the entire key-range, a key might be in any file, thus reads need to check them one-by-one. Users often wish to configure a low limit to improve their read latency. - -Although, the mechanism with which we enforce L0's file count limit may be unappealing. When the limit is reached, RocksDB intentionally delays user writes. This slows down accumulation of files in L0, and frees up resources for compacting files down to lower levels. But adding delays will significantly increase user-visible write latency jitter. - -Also, due to how L0 files can span the entire key-range, compaction parallelization is limited. Files at L0 or L1 may be locked due to involvement in pending L0->L1 or L1->L2 compactions. We can only schedule a parallel L0->L1 compaction if it does not require any of the locked files, which is typically not the case. - -To handle these constraints better, we added a new type of compaction, L0->L0. It quickly reduces file count in L0 and can be scheduled even when L1 files are locked, unlike L0->L1. We also changed the L0->L1 picking algorithm to increase opportunities for parallelism. - -### Old L0->L1 Picking Logic - -Previously, our logic for picking which L0 file to compact was the same as every other level: pick the largest file in the level. One special property of L0->L1 compaction is that files can overlap in the input level, so those overlapping files must be pulled in as well. For example, a compaction may look like this: - -![full-range.png](/static/images/compaction/full-range.png) - -This compaction pulls in every L0 and L1 file. This happens regardless of which L0 file is initially chosen as each file overlaps with every other file. - -Users may insert their data less uniformly in the key-range. For example, a database may look like this during L0->L1 compaction: - -![part-range-old.png](/static/images/compaction/part-range-old.png) - -Let's say the third file from the top is the largest, and let's say the top two files are created after the compaction started. When the compaction is picked, the fourth L0 file and six rightmost L1 files are pulled in due to overlap. Notice this leaves the database in a state where we might not be able to schedule parallel compactions. For example, if the sixth file from the top is the next largest, we can't compact it because it overlaps with the top two files, which overlap with the locked L0 files. - -We can now see the high-level problems with this approach more clearly. First, locked files in L0 or L1 prevent us from parallelizing compactions. When locked files block L0->L1 compaction, there is nothing we can do to eliminate L0 files. Second, L0->L1 compactions are relatively slow. As we saw, when keys are uniformly distributed, L0->L1 compacts two entire levels. While this is happening, new files are being flushed to L0, advancing towards the file count limit. - -### New L0->L0 Algorithm - -We introduced compaction within L0 to improve both parallelization and speed of reducing L0 file count. An L0->L0 compaction may look like this: - -![l1-l2-contend.png](/static/images/compaction/l1-l2-contend.png) - -Say the L1->L2 compaction started first. Now L0->L1 is prevented by the locked L1 file. In this case, we compact files within L0. This allows us to start the work for eliminating L0 files earlier. It also lets us do less work since we don't pull in any L1 files, whereas L0->L1 compaction would've pulled in all of them. This lets us quickly reduce L0 file count to keep read-amp low while sustaining large bursts of writes (i.e., fast accumulation of L0 files). - -The tradeoff is this increases total compaction work, as we're now compacting files without contributing towards our eventual goal of moving them towards lower levels. Our benchmarks, though, consistently show less compaction stalls and improved write throughput. One justification is that L0 file data is highly likely in page cache and/or block cache due to it being recently written and frequently accessed. So, this type of compaction is relatively cheap compared to compactions at lower levels. - -This feature is available since RocksDB 5.4. - -### New L0->L1 Picking Logic - -Recall how the old L0->L1 picking algorithm chose the largest L0 file for compaction. This didn't fit well with L0->L0 compaction, which operates on a span of files. That span begins at the newest L0 file, and expands towards older files as long as they're not being compacted. Since the largest file may be anywhere, the old L0->L1 picking logic could arbitrarily prevent us from getting a long span of files. See the second illustration in this post for a scenario where this would happen. - -So, we changed the L0->L1 picking algorithm to start from the oldest file and expand towards newer files as long as they're not being compacted. For example: - -![l0-l1-contend.png](/static/images/compaction/l0-l1-contend.png) - -Now, there can never be L0 files unreachable for L0->L0 due to L0->L1 selecting files in the middle. When longer spans of files are available for L0->L0, we perform less compaction work per deleted L0 file, thus improving efficiency. - -This feature will be available in RocksDB 5.7. - -### Performance Changes - -Mark Callaghan did the most extensive benchmarking of this feature's impact on MyRocks. See his results [here](http://smalldatum.blogspot.com/2017/05/innodb-myrocks-and-tokudb-on-insert.html). Note the primary change between his March 17 and April 14 builds is the latter performs L0->L0 compaction. diff --git a/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown b/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown deleted file mode 100644 index d7856088b..000000000 --- a/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: RocksDB 5.5.1 Released! -layout: post -author: lightmark -category: blog ---- - -### New Features -* FIFO compaction to support Intra L0 compaction too with CompactionOptionsFIFO.allow_compaction=true. -* Statistics::Reset() to reset user stats. -* ldb add option --try_load_options, which will open DB with its own option file. -* Introduce WriteBatch::PopSavePoint to pop the most recent save point explicitly. -* Support dynamically change `max_open_files` option via SetDBOptions() -* Added DB::CreateColumnFamilie() and DB::DropColumnFamilies() to bulk create/drop column families. -* Add debugging function `GetAllKeyVersions` to see internal versions of a range of keys. -* Support file ingestion with universal compaction style -* Support file ingestion behind with option `allow_ingest_behind` -* New option enable_pipelined_write which may improve write throughput in case writing from multiple threads and WAL enabled. - -### Bug Fixes -* Fix the bug that Direct I/O uses direct reads for non-SST file -* Fix the bug that flush doesn't respond to fsync result diff --git a/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown b/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown deleted file mode 100644 index 3b54ffd5a..000000000 --- a/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: RocksDB 5.6.1 Released! -layout: post -author: yiwu -category: blog ---- - -### Public API Change -* Scheduling flushes and compactions in the same thread pool is no longer supported by setting `max_background_flushes=0`. Instead, users can achieve this by configuring their high-pri thread pool to have zero threads. See https://github.com/facebook/rocksdb/wiki/Thread-Pool for more details. -* Replace `Options::max_background_flushes`, `Options::max_background_compactions`, and `Options::base_background_compactions` all with `Options::max_background_jobs`, which automatically decides how many threads to allocate towards flush/compaction. -* options.delayed_write_rate by default take the value of options.rate_limiter rate. -* Replace global variable `IOStatsContext iostats_context` with `IOStatsContext* get_iostats_context()`; replace global variable `PerfContext perf_context` with `PerfContext* get_perf_context()`. - -### New Features -* Change ticker/histogram statistics implementations to use core-local storage. This improves aggregation speed compared to our previous thread-local approach, particularly for applications with many threads. See http://rocksdb.org/blog/2017/05/14/core-local-stats.html for more details. -* Users can pass a cache object to write buffer manager, so that they can cap memory usage for memtable and block cache using one single limit. -* Flush will be triggered when 7/8 of the limit introduced by write_buffer_manager or db_write_buffer_size is triggered, so that the hard threshold is hard to hit. See https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager for more details. -* Introduce WriteOptions.low_pri. If it is true, low priority writes will be throttled if the compaction is behind. See https://github.com/facebook/rocksdb/wiki/Low-Priority-Write for more details. -* `DB::IngestExternalFile()` now supports ingesting files into a database containing range deletions. - -### Bug Fixes -* Shouldn't ignore return value of fsync() in flush. diff --git a/docs/_posts/2017-08-24-pinnableslice.markdown b/docs/_posts/2017-08-24-pinnableslice.markdown deleted file mode 100644 index 06e0bcb2f..000000000 --- a/docs/_posts/2017-08-24-pinnableslice.markdown +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: PinnableSlice; less memcpy with point lookups -layout: post -author: maysamyabandeh -category: blog ---- - -The classic API for [DB::Get](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L310) receives a std::string as argument to which it will copy the value. The memcpy overhead could be non-trivial when the value is large. The [new API](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L322) receives a PinnableSlice instead, which avoids memcpy in most of the cases. - -### What is PinnableSlice? - -Similarly to Slice, PinnableSlice refers to some in-memory data so it does not incur the memcpy cost. To ensure that the data will not be erased while it is being processed by the user, PinnableSlice, as its name suggests, has the data pinned in memory. The pinned data are released when PinnableSlice object is destructed or when ::Reset is invoked explicitly on it. - -### How good is it? - -Here are the improvements in throughput for an [in-memory benchmark](https://github.com/facebook/rocksdb/pull/1756#issuecomment-286201693): -* value 1k byte: 14% -* value 10k byte: 34% - -### Any limitations? - -PinnableSlice tries to avoid memcpy as much as possible. The primary gain is when reading large values from the block cache. There are however cases that it would still have to copy the data into its internal buffer. The reason is mainly the complexity of implementation and if there is enough motivation on the application side. the scope of PinnableSlice could be extended to such cases too. These include: -* Merged values -* Reads from memtables - -### How to use it? - -```cpp -PinnableSlice pinnable_val; -while (!stopped) { - auto s = db->Get(opt, cf, key, &pinnable_val); - // ... use it - pinnable_val.Reset(); // then release it immediately -} -``` - -You can also [initialize the internal buffer](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L314) of PinnableSlice by passing your own string in the constructor. [simple_example.cc](https://github.com/facebook/rocksdb/blob/main/examples/simple_example.cc) demonstrates that with more examples. diff --git a/docs/_posts/2017-08-25-flushwal.markdown b/docs/_posts/2017-08-25-flushwal.markdown deleted file mode 100644 index 751fe5249..000000000 --- a/docs/_posts/2017-08-25-flushwal.markdown +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: FlushWAL; less fwrite, faster writes -layout: post -author: maysamyabandeh -category: blog ---- - -When `DB::Put` is called, the data is written to both memtable (to be flushed to SST files later) and the WAL (write-ahead log) if it is enabled. In the case of a crash, RocksDB can recover as much as the memtable state that is reflected into the WAL. By default RocksDB automatically flushes the WAL from the application memory to the OS buffer after each `::Put`. It however can be configured to perform the flush manually after an explicit call to `::FlushWAL`. Not doing fwrite syscall after each `::Put` offers a tradeoff between reliability and write latency for the general case. As we explain below, some applications such as MyRocks benefit from this API to gain higher write throughput with however no compromise in reliability. - -### How much is the gain? - -Using `::FlushWAL` API along with setting `DBOptions.concurrent_prepare`, MyRocks achieves 40% higher throughput in Sysbench's [update-nonindex](https://github.com/akopytov/sysbench/blob/master/src/lua/oltp_update_non_index.lua) benchmark. - -### Write, Flush, and Sync - -The write to the WAL is first written to the application memory buffer. The buffer in the next step is "flushed" to OS buffer by calling fwrite syscall. The OS buffer is later "synced" to the persistent storage. The data in the OS buffer, although not persisted yet, will survive the application crash. By default, the flush occurs automatically upon each call to `DB::Put` or `DB::Write`. The user can additionally request sync after each write by setting `WriteOptions::sync`. - -### FlushWAL API - -The user can turn off the automatic flush of the WAL by setting `DBOptions::manual_wal_flush`. In that case, the WAL buffer is flushed when it is either full or `DB::FlushWAL` is called by the user. The API also accepts a boolean argument should we want to sync right after the flush: `::FlushWAL(true)`. - -### Success story: MyRocks - -Some applications that use RocksDB, already have other machinsims in place to provide reliability. MySQL for example uses 2PC (two-phase commit) to write to both binlog as well as the storage engine such as InnoDB and MyRocks. The group commit logic in MySQL allows the 1st phase (Prepare) to be run in parallel but after a commit group is formed performs the 2nd phase (Commit) in a serial manner. This makes low commit latency in the storage engine essential for achieving high throughput. The commit in MyRocks includes writing to the RocksDB WAL, which as explaiend above, by default incures the latency of flushing the WAL new appends to the OS buffer. - -Since binlog helps in recovering from some failure scenarios, MySQL can provide reliability without however needing a storage WAL flush after each individual commit. MyRocks benefits from this property, disables automatic WAL flush in RocksDB, and manually calls `::FlushWAL` when requested by MySQL. diff --git a/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown b/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown deleted file mode 100644 index a22dcaa1c..000000000 --- a/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: RocksDB 5.8 Released! -layout: post -author: maysamyabandeh -category: blog ---- - -### Public API Change -* Users of `Statistics::getHistogramString()` will see fewer histogram buckets and different bucket endpoints. -* `Slice::compare` and BytewiseComparator `Compare` no longer accept `Slice`s containing nullptr. -* `Transaction::Get` and `Transaction::GetForUpdate` variants with `PinnableSlice` added. - -### New Features -* Add Iterator::Refresh(), which allows users to update the iterator state so that they can avoid some initialization costs of recreating iterators. -* Replace dynamic_cast<> (except unit test) so people can choose to build with RTTI off. With make, release mode is by default built with -fno-rtti and debug mode is built without it. Users can override it by setting USE_RTTI=0 or 1. -* Universal compactions including the bottom level can be executed in a dedicated thread pool. This alleviates head-of-line blocking in the compaction queue, which cause write stalling, particularly in multi-instance use cases. Users can enable this feature via `Env::SetBackgroundThreads(N, Env::Priority::BOTTOM)`, where `N > 0`. -* Allow merge operator to be called even with a single merge operand during compactions, by appropriately overriding `MergeOperator::AllowSingleOperand`. -* Add `DB::VerifyChecksum()`, which verifies the checksums in all SST files in a running DB. -* Block-based table support for disabling checksums by setting `BlockBasedTableOptions::checksum = kNoChecksum`. - -### Bug Fixes -* Fix wrong latencies in `rocksdb.db.get.micros`, `rocksdb.db.write.micros`, and `rocksdb.sst.read.micros`. -* Fix incorrect dropping of deletions during intra-L0 compaction. -* Fix transient reappearance of keys covered by range deletions when memtable prefix bloom filter is enabled. -* Fix potentially wrong file smallest key when range deletions separated by snapshot are written together. diff --git a/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown b/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown deleted file mode 100644 index d2e6204e1..000000000 --- a/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Auto-tuned Rate Limiter -layout: post -author: ajkr -category: blog ---- - -### Introduction - -Our rate limiter has been hard to configure since users need to pick a value that is low enough to prevent background I/O spikes, which can impact user-visible read/write latencies. Meanwhile, picking too low a value can cause memtables and L0 files to pile up, eventually leading to writes stalling. Tuning the rate limiter has been especially difficult for users whose DB instances have different workloads, or have workloads that vary over time, or commonly both. - -To address this, in RocksDB 5.9 we released a dynamic rate limiter that adjusts itself over time according to demand for background I/O. It can be enabled simply by passing `auto_tuned=true` in the `NewGenericRateLimiter()` call. In this case `rate_bytes_per_sec` will indicate the upper-bound of the window within which a rate limit will be picked dynamically. The chosen rate limit will be much lower unless absolutely necessary, so setting this to the device's maximum throughput is a reasonable choice on dedicated hosts. - -### Algorithm - -We use a simple multiplicative-increase, multiplicative-decrease algorithm. We measure demand for background I/O as the ratio of intervals where the rate limiter is drained. There are low and high watermarks for this ratio, which will trigger a change in rate limit when breached. The rate limit can move within a window bounded by the user-specified upper-bound, and a lower-bound that we derive internally. Users can expect this lower bound to be 1-2 orders of magnitude less than the provided upper-bound (so don't provide INT64_MAX as your upper-bound), although it's subject to change. - -### Benchmark Results - -Data is ingested at 10MB/s and the rate limiter was created with 1000MB/s as its upper bound. The dynamically chosen rate limit hovers around 125MB/s. The other clustering of points at 50MB/s is due to number of compaction threads being reduced to one when there's no compaction pressure. - -![](/static/images/rate-limiter/write-KBps-series.png) - -![](/static/images/rate-limiter/auto-tuned-write-KBps-series.png) - -The following graph summarizes the above two time series graphs in CDF form. In particular, notice the p90 - p100 for background write rate are significantly lower with auto-tuned rate limiter enabled. - -![](/static/images/rate-limiter/write-KBps-cdf.png) diff --git a/docs/_posts/2017-12-19-write-prepared-txn.markdown b/docs/_posts/2017-12-19-write-prepared-txn.markdown deleted file mode 100644 index 439b3f83c..000000000 --- a/docs/_posts/2017-12-19-write-prepared-txn.markdown +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: WritePrepared Transactions -layout: post -author: maysamyabandeh -category: blog ---- - -RocksDB supports both optimistic and pessimistic concurrency controls. The pessimistic transactions make use of locks to provide isolation between the transactions. The default write policy in pessimistic transactions is _WriteCommitted_, which means that the data is written to the DB, i.e., the memtable, only after the transaction is committed. This policy simplified the implementation but came with some limitations in throughput, transaction size, and variety in supported isolation levels. In the below, we explain these in detail and present the other write policies, _WritePrepared_ and _WriteUnprepared_. We then dive into the design of _WritePrepared_ transactions. - -### WriteCommitted, Pros and Cons - -With _WriteCommitted_ write policy, the data is written to the memtable only after the transaction commits. This greatly simplifies the read path as any data that is read by other transactions can be assumed to be committed. This write policy, however, implies that the writes are buffered in memory in the meanwhile. This makes memory a bottleneck for large transactions. The delay of the commit phase in 2PC (two-phase commit) also becomes noticeable since most of the work, i.e., writing to memtable, is done at the commit phase. When the commit of multiple transactions are done in a serial fashion, such as in 2PC implementation of MySQL, the lengthy commit latency becomes a major contributor to lower throughput. Moreover this write policy cannot provide weaker isolation levels, such as READ UNCOMMITTED, that could potentially provide higher throughput for some applications. - -### Alternatives: _WritePrepared_ and _WriteUnprepared_ - -To tackle the lengthy commit issue, we should do memtable writes at earlier phases of 2PC so that the commit phase become lightweight and fast. 2PC is composed of Write stage, where the transaction `::Put` is invoked, the prepare phase, where `::Prepare` is invoked (upon which the DB promises to commit the transaction if later is requested), and commit phase, where `::Commit` is invoked and the transaction writes become visible to all readers. To make the commit phase lightweight, the memtable write could be done at either `::Prepare` or `::Put` stages, resulting into _WritePrepared_ and _WriteUnprepared_ write policies respectively. The downside is that when another transaction is reading data, it would need a way to tell apart which data is committed, and if they are, whether they are committed before the transaction's start, i.e., in the read snapshot of the transaction. _WritePrepared_ would still have the issue of buffering the data, which makes the memory the bottleneck for large transactions. It however provides a good milestone for transitioning from _WriteCommitted_ to _WriteUnprepared_ write policy. Here we explain the design of _WritePrepared_ policy. We will cover the changes that make the design to also supported _WriteUnprepared_ in an upcoming post. - -### _WritePrepared_ in a nutshell - -These are the primary design questions that needs to be addressed: -1) How do we identify the key/values in the DB with transactions that wrote them? -2) How do we figure if a key/value written by transaction Txn_w is in the read snapshot of the reading transaction Txn_r? -3) How do we rollback the data written by aborted transactions? - -With _WritePrepared_, a transaction still buffers the writes in a write batch object in memory. When 2PC `::Prepare` is called, it writes the in-memory write batch to the WAL (write-ahead log) as well as to the memtable(s) (one memtable per column family); We reuse the existing notion of sequence numbers in RocksDB to tag all the key/values in the same write batch with the same sequence number, `prepare_seq`, which is also used as the identifier for the transaction. At commit time, it writes a commit marker to the WAL, whose sequence number, `commit_seq`, will be used as the commit timestamp of the transaction. Before releasing the commit sequence number to the readers, it stores a mapping from `prepare_seq` to `commit_seq` in an in-memory data structure that we call _CommitCache_. When a transaction reading values from the DB (tagged with `prepare_seq`) it makes use of the _CommitCache_ to figure if `commit_seq` of the value is in its read snapshot. To rollback an aborted transaction, we apply the status before the transaction by making another write that cancels out the writes of the aborted transaction. - -The _CommitCache_ is a lock-free data structure that caches the recent commit entries. Looking up the entries in the cache must be enough for almost all th transactions that commit in a timely manner. When evicting the older entries from the cache, it still maintains some other data structures to cover the corner cases for transactions that takes abnormally too long to finish. We will cover them in the design details below. - -### Benchmark Results -Here we presents the improvements observed in MyRocks with sysbench and linkbench: -* benchmark...........tps.........p95 latency....cpu/query -* insert...................68% -* update-noindex...30%......38% -* update-index.......61%.......28% -* read-write............6%........3.5% -* read-only...........-1.2%.....-1.8% -* linkbench.............1.9%......+overall........0.6% - -Here are also the detailed results for [In-Memory Sysbench](https://gist.github.com/maysamyabandeh/bdb868091b2929a6d938615fdcf58424) and [SSD Sysbench](https://gist.github.com/maysamyabandeh/ff94f378ab48925025c34c47eff99306) curtesy of [@mdcallag](https://github.com/mdcallag). - -Learn more [here](https://github.com/facebook/rocksdb/wiki/WritePrepared-Transactions). diff --git a/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown b/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown deleted file mode 100644 index 9f32d3f94..000000000 --- a/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: RocksDB 5.10.2 Released! -layout: post -author: siying -category: blog ---- - -### Public API Change -* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features. - -### New Features -* CRC32C is now using the 3-way pipelined SSE algorithm `crc32c_3way` on supported platforms to improve performance. The system will choose to use this algorithm on supported platforms automatically whenever possible. If PCLMULQDQ is not supported it will fall back to the old Fast_CRC32 algorithm. -* Provide lifetime hints when writing files on Linux. This reduces hardware write-amp on storage devices supporting multiple streams. -* Add a DB stat, `NUMBER_ITER_SKIP`, which returns how many internal keys were skipped during iterations (e.g., due to being tombstones or duplicate versions of a key). -* Add PerfContext counters, `key_lock_wait_count` and `key_lock_wait_time`, which measure the number of times transactions wait on key locks and total amount of time waiting. - -### Bug Fixes -* Fix IOError on WAL write doesn't propagate to write group follower -* Make iterator invalid on merge error. -* Fix performance issue in `IngestExternalFile()` affecting databases with large number of SST files. -* Fix possible corruption to LSM structure when `DeleteFilesInRange()` deletes a subset of files spanned by a `DeleteRange()` marker. -* Fix DB::Flush() keep waiting after flush finish under certain condition. diff --git a/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown b/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown deleted file mode 100644 index ff9b1e464..000000000 --- a/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Rocksdb Tuning Advisor -layout: post -author: poojam23 -category: blog ---- - -The performance of Rocksdb is contingent on its tuning. However, because -of the complexity of its underlying technology and a large number of -configurable parameters, a good configuration is sometimes hard to obtain. The aim of -the python command-line tool, Rocksdb Advisor, is to automate the process of -suggesting improvements in the configuration based on advice from Rocksdb -experts. - -### Overview - -Experts share their wisdom as rules comprising of conditions and suggestions in the INI format (refer -[rules.ini](https://github.com/facebook/rocksdb/blob/main/tools/advisor/advisor/rules.ini)). -Users provide the Rocksdb configuration that they want to improve upon (as the -familiar Rocksdb OPTIONS file — -[example](https://github.com/facebook/rocksdb/blob/main/examples/rocksdb_option_file_example.ini)) -and the path of the file which contains Rocksdb logs and statistics. -The [Advisor](https://github.com/facebook/rocksdb/blob/main/tools/advisor/advisor/rule_parser_example.py) -creates appropriate DataSource objects (for Rocksdb -[logs](https://github.com/facebook/rocksdb/blob/main/tools/advisor/advisor/db_log_parser.py), -[options](https://github.com/facebook/rocksdb/blob/main/tools/advisor/advisor/db_options_parser.py), -[statistics](https://github.com/facebook/rocksdb/blob/main/tools/advisor/advisor/db_stats_fetcher.py) etc.) -and provides them to the [Rules Engine](https://github.com/facebook/rocksdb/blob/main/tools/advisor/advisor/rule_parser.py). -The Rules uses rules from experts to parse data-sources and trigger appropriate rules. -The Advisor's output gives information about which rules were triggered, -why they were triggered and what each of them suggests. Each suggestion -provided by a triggered rule advises some action on a Rocksdb -configuration option, for example, increase CFOptions.write_buffer_size, -set bloom_bits to 2 etc. - -### Usage - -An example command to run the tool: - -```shell -cd rocksdb/tools/advisor -python3 -m advisor.rule_parser_example --rules_spec=advisor/rules.ini --rocksdb_options=test/input_files/OPTIONS-000005 --log_files_path_prefix=test/input_files/LOG-0 --stats_dump_period_sec=20 -``` - -Sample output where a Rocksdb log-based rule has been triggered : - -```shell -Rule: stall-too-many-memtables -LogCondition: stall-too-many-memtables regex: Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+ -Suggestion: inc-bg-flush option : DBOptions.max_background_flushes action : increase suggested_values : ['2'] -Suggestion: inc-write-buffer option : CFOptions.max_write_buffer_number action : increase -scope: col_fam: -{'default'} -``` - -### Read more - -For more information, refer to [advisor](https://github.com/facebook/rocksdb/tree/main/tools/advisor/README.md). diff --git a/docs/_posts/2018-08-23-data-block-hash-index.markdown b/docs/_posts/2018-08-23-data-block-hash-index.markdown deleted file mode 100644 index c4b24ec2a..000000000 --- a/docs/_posts/2018-08-23-data-block-hash-index.markdown +++ /dev/null @@ -1,118 +0,0 @@ ---- -title: Improving Point-Lookup Using Data Block Hash Index -layout: post -author: fgwu -category: blog ---- -We've designed and implemented a _data block hash index_ in RocksDB that has the benefit of both reducing the CPU util and increasing the throughput for point lookup queries with a reasonable and tunable space overhead. - -Specifially, we append a compact hash table to the end of the data block for efficient indexing. It is backward compatible with the data base created without this feature. After turned on the hash index feature, existing data will be gradually converted to the hash index format. - -Benchmarks with `db_bench` show the CPU utilization of one of the main functions in the point lookup code path, `DataBlockIter::Seek()`, is reduced by 21.8%, and the overall RocksDB throughput is increased by 10% under purely cached workloads, at an overhead of 4.6% more space. Shadow testing with Facebook production traffic shows good CPU improvements too. - - -### How to use it -Two new options are added as part of this feature: `BlockBasedTableOptions::data_block_index_type` and `BlockBasedTableOptions::data_block_hash_table_util_ratio`. - -The hash index is disabled by default unless `BlockBasedTableOptions::data_block_index_type` is set to `data_block_index_type = kDataBlockBinaryAndHash`. The hash table utilization ratio is adjustable using `BlockBasedTableOptions::data_block_hash_table_util_ratio`, which is valid only if `data_block_index_type = kDataBlockBinaryAndHash`. - - -``` -// the definitions can be found in include/rocksdb/table.h - -// The index type that will be used for the data block. -enum DataBlockIndexType : char { - kDataBlockBinarySearch = 0, // traditional block type - kDataBlockBinaryAndHash = 1, // additional hash index -}; - -// Set to kDataBlockBinaryAndHash to enable hash index -DataBlockIndexType data_block_index_type = kDataBlockBinarySearch; - -// #entries/#buckets. It is valid only when data_block_hash_index_type is -// kDataBlockBinaryAndHash. -double data_block_hash_table_util_ratio = 0.75; - -``` - - -### Data Block Hash Index Design - -Current data block format groups adjacent keys together as a restart interval. One block consists of multiple restart intervals. The byte offset of the beginning of each restart interval, i.e. a restart point, is stored in an array called restart interval index or binary seek index. RocksDB does a binary search when performing point lookup for keys in data blocks to find the right restart interval the key may reside. We will use binary seek and binary search interchangeably in this post. - -In order to find the right location where the key may reside using binary search, multiple key parsing and comparison are needed. Each binary search branching triggers CPU cache miss, causing much CPU utilization. We have seen that this binary search takes up considerable CPU in production use-cases. - -![](/static/images/data-block-hash-index/block-format-binary-seek.png) - -We implemented a hash map at the end of the block to index the key to reduce the CPU overhead of the binary search. The hash index is just an array of pointers pointing into the binary seek index. - -![](/static/images/data-block-hash-index/block-format-hash-index.png) - - -Each array element is considered as a hash bucket when storing the location of a key (or more precisely, the restart index of the restart interval where the key resides). When multiple keys happen to hash into the same bucket (hash collision), we just mark the bucket as “collision”. So that when later querying on that key, the hash table lookup knows that there was a hash collision happened so it can fall back to the traditional binary search to find the location of the key. - -We define hash table utilization ratio as the #keys/#buckets. If a utilization ratio is 0.5 and there are 100 buckets, 50 keys are stored in the bucket. The less the util ratio, the less hash collision, and the less chance for a point lookup falls back to binary seek (fall back ratio) due to the collision. So a small util ratio has more benefit to reduce the CPU time but introduces more space overhead. - -Space overhead depends on the util ratio. Each bucket is a `uint8_t` (i.e. one byte). For a util ratio of 1, the space overhead is 1Byte per key, the fall back ratio observed is ~52%. - -![](/static/images/data-block-hash-index/hash-index-data-structure.png) - -### Things that Need Attention - -**Customized Comparator** - -Hash index will hash different keys (keys with different content, or byte sequence) into different hash values. This assumes the comparator will not treat different keys as equal if they have different content. - -The default bytewise comparator orders the keys in alphabetical order and works well with hash index, as different keys will never be regarded as equal. However, some specially crafted comparators will do. For example, say, a `StringToIntComparator` can convert a string into an integer, and use the integer to perform the comparison. Key string “16” and “0x10” is equal to each other as seen by this `StringToIntComparator`, but they probably hash to different value. Later queries to one form of the key will not be able to find the existing key been stored in the other format. - -We add a new function member to the comparator interface: - -``` -virtual bool CanKeysWithDifferentByteContentsBeEqual() const { return true; } -``` - - -Every comparator implementation should override this function and specify the behavior of the comparator. If a comparator can regard different keys equal, the function returns true, and as a result the hash index feature will not be enabled, and vice versa. - -NOTE: to use the hash index feature, one should 1) have a comparator that can never treat different keys as equal; and 2) override the `CanKeysWithDifferentByteContentsBeEqual()` function to return `false`, so the hash index can be enabled. - - -**Util Ratio's Impact on Data Block Cache** - -Adding the hash index to the end of the data block essentially takes up the data block cache space, making the effective data block cache size smaller and increasing the data block cache miss ratio. Therefore, a very small util ratio will result in a large data block cache miss ratio, and the extra I/O may drag down the throughput gain achieved by the hash index lookup. Besides, when compression is enabled, cache miss also incurs data block decompression, which is CPU-consuming. Therefore the CPU may even increase if using a too small util ratio. The best util ratio depends on workloads, cache to data ratio, disk bandwidth/latency etc. In our experiment, we found util ratio = 0.5 ~ 1 is a good range to explore that brings both CPU and throughput gains. - - -### Limitations - -As we use `uint8_t` to store binary seek index, i.e. restart interval index, the total number of restart intervals cannot be more than 253 (we reserved 255 and 254 as special flags). For blocks having a larger number of restart intervals, the hash index will not be created and the point lookup will be done by traditional binary seek. - -Data block hash index only supports point lookup. We do not support range lookup. Range lookup request will fall back to BinarySeek. - -RocksDB supports many types of records, such as `Put`, `Delete`, `Merge`, etc (visit [here](https://github.com/facebook/rocksdb/wiki/rocksdb-basics) for more information). Currently we only support `Put` and `Delete`, but not `Merge`. Internally we have a limited set of supported record types: - - -``` -kPutRecord, <=== supported -kDeleteRecord, <=== supported -kSingleDeleteRecord, <=== supported -kTypeBlobIndex, <=== supported -``` - -For records not supported, the searching process will fall back to the traditional binary seek. - - - -### Evaluation -To evaluate the CPU util reduction and isolate other factors such as disk I/O and block decompression, we first evaluate the hash idnex in a purely cached workload. We observe that the CPU utilization of one of the main functions in the point lookup code path, DataBlockIter::Seek(), is reduced by 21.8% and the overall throughput is increased by 10% at an overhead of 4.6% more space. - -However, general worload is not always purely cached. So we also evaluate the performance under different cache space pressure. In the following test, we use `db_bench` with RocksDB deployed on SSDs. The total DB size is 5~6GB, and it is about 14GB if decompressed. Different block cache sizes are used, ranging from 14GB down to 2GB, with an increasing cache miss ratio. - -Orange bars are representing our hash index performance. We use a hash util ratio of 1.0 in this test. Block size are set to 16KiB with the restart interval as 16. - -![](/static/images/data-block-hash-index/perf-throughput.png) -![](/static/images/data-block-hash-index/perf-cache-miss.png) - -We can see that if cache size is greater than 8GB, hash index can bring throughput gain. Cache size greater than 8GB can be translated to a cache miss ratio smaller than 40%. So if the workload has a cache miss ratio smaller than 40%, hash index is able to increase the throughput. - -Besides, shadow testing with Facebook production traffic shows good CPU improvements too. - diff --git a/docs/_posts/2018-11-21-delete-range.markdown b/docs/_posts/2018-11-21-delete-range.markdown deleted file mode 100644 index 96fc3562d..000000000 --- a/docs/_posts/2018-11-21-delete-range.markdown +++ /dev/null @@ -1,292 +0,0 @@ ---- -title: "DeleteRange: A New Native RocksDB Operation" -layout: post -author: -- abhimadan -- ajkr -category: blog ---- -## Motivation - -### Deletion patterns in LSM - -Deleting a range of keys is a common pattern in RocksDB. Most systems built on top of -RocksDB have multi-component key schemas, where keys sharing a common prefix are -logically related. Here are some examples. - -MyRocks is a MySQL fork using RocksDB as its storage engine. Each key's first -four bytes identify the table or index to which that key belongs. Thus dropping -a table or index involves deleting all the keys with that prefix. - -Rockssandra is a Cassandra variant that uses RocksDB as its storage engine. One -of its admin tool commands, `nodetool cleanup`, removes key-ranges that have been migrated -to other nodes in the cluster. - -Marketplace uses RocksDB to store product data. Its key begins with product ID, -and it stores various data associated with the product in separate keys. When a -product is removed, all these keys must be deleted. - -When we decide what to improve, we try to find a use case that's common across -users, since we want to build a generally useful system, not one that has many -one-off features for individual users. The range deletion pattern is common as -illustrated above, so from this perspective it's a good target for optimization. - -### Existing mechanisms: challenges and opportunities - -The most common pattern we see is scan-and-delete, i.e., advance an iterator -through the to-be-deleted range, and issue a `Delete` for each key. This is -slow (involves read I/O) so cannot be done in any critical path. Additionally, -it creates many tombstones, which slows down iterators and doesn't offer a deadline -for space reclamation. - -Another common pattern is using a custom compaction filter that drops keys in -the deleted range(s). This deletes the range asynchronously, so cannot be used -in cases where readers must not see keys in deleted ranges. Further, it has the -disadvantage of outputting tombstones to all but the bottom level. That's -because compaction cannot detect whether dropping a key would cause an older -version at a lower level to reappear. - -If space reclamation time is important, or it is important that the deleted -range not affect iterators, the user can trigger `CompactRange` on the deleted -range. This can involve arbitrarily long waits in the compaction queue, and -increases write-amp. By the time it's finished, however, the range is completely -gone from the LSM. - -`DeleteFilesInRange` can be used prior to compacting the deleted range as long -as snapshot readers do not need to access them. It drops files that are -completely contained in the deleted range. That saves write-amp because, in -`CompactRange`, the file data would have to be rewritten several times before it -reaches the bottom of the LSM, where tombstones can finally be dropped. - -In addition to the above approaches having various drawbacks, they are quite -complicated to reason about and implement. In an ideal world, deleting a range -of keys would be (1) simple, i.e., a single API call; (2) synchronous, i.e., -when the call finishes, the keys are guaranteed to be wiped from the DB; (3) low -latency so it can be used in critical paths; and (4) a first-class operation -with all the guarantees of any other write, like atomicity, crash-recovery, etc. - -## v1: Getting it to work - -### Where to persist them? - -The first place we thought about storing them is inline with the data blocks. -We could not think of a good way to do it, however, since the start of a range -tombstone covering a key could be anywhere, making binary search impossible. -So, we decided to investigate segregated storage. - -A second solution we considered is appending to the manifest. This file is -append-only, periodically compacted, and stores metadata like the level to which -each SST belongs. This is tempting because it leverages an existing file, which -is maintained in the background and fully read when the DB is opened. However, -it conceptually violates the manifest's purpose, which is to store metadata. It -also has no way to detect when a range tombstone no longer covers anything and -is droppable. Further, it'd be possible for keys above a range tombstone to disappear -when they have their seqnums zeroed upon compaction to the bottommost level. - -A third candidate is using a separate column family. This has similar problems -to the manifest approach. That is, we cannot easily detect when a range -tombstone is obsolete, and seqnum zeroing can cause a key -to go from above a range tombstone to below, i.e., disappearing. The upside is -we can reuse logic for memory buffering, consistent reads/writes, etc. - -The problems with the second and third solutions indicate a need for range -tombstones to be aware of flush/compaction. An easy way to achieve this is put -them in the SST files themselves - but not in the data blocks, as explained for -the first solution. So, we introduced a separate meta-block for range tombstones. -This resolved the problem of when to obsolete range tombstones, as it's simple: -when they're compacted to the bottom level. We also reused the LSM invariants -that newer versions of a key are always in a higher level to prevent the seqnum -zeroing problem. This approach has the side benefit of constraining the range -tombstones seen during reads to ones in a similar key-range. - -![](/static/images/delrange/delrange_sst_blocks.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*When there are range tombstones in an SST, they are segregated in a separate meta-block* -{: style="text-align: center"} - -![](/static/images/delrange/delrange_key_schema.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*Logical range tombstones (left) and their corresponding physical key-value representation (right)* -{: style="text-align: center"} - -### Write path - -`WriteBatch` stores range tombstones in its buffer which are logged to the WAL and -then applied to a dedicated range tombstone memtable during `Write`. Later in -the background the range tombstone memtable and its corresponding data memtable -are flushed together into a single SST with a range tombstone meta-block. SSTs -periodically undergo compaction which rewrites SSTs with point data and range -tombstones dropped or merged wherever possible. - -We chose to use a dedicated memtable for range tombstones. The memtable -representation is always skiplist in order to minimize overhead in the usual -case, which is the memtable contains zero or a small number of range tombstones. -The range tombstones are segregated to a separate memtable for the same reason -we segregated range tombstones in SSTs. That is, we did not know how to -interleave the range tombstone with point data in a way that we would be able to -find it for arbitrary keys that it covers. - -![](/static/images/delrange/delrange_write_path.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 70%"} - -*Lifetime of point keys and range tombstones in RocksDB* -{: style="text-align: center"} - -During flush and compaction, we chose to write out all non-obsolete range -tombstones unsorted. Sorting by a single dimension is easy to implement, but -doesn't bring asymptotic improvement to queries over range data. Ideally, we -want to store skylines (see “Read Path” subsection below) computed over our ranges so we can binary search. -However, a couple of concerns cause doing this in flush and compaction to feel -unsatisfactory: (1) we need to store multiple skylines, one for each snapshot, -which further complicates the range tombstone meta-block encoding; and (2) even -if we implement this, the range tombstone memtable still needs to be linearly -scanned. Given these concerns we decided to defer collapsing work to the read -side, hoping a good caching strategy could optimize this at some future point. - - -### Read path - -In point lookups, we aggregate range tombstones in an unordered vector as we -search through live memtable, immutable memtables, and then SSTs. When a key is -found that matches the lookup key, we do a scan through the vector, checking -whether the key is deleted. - -In iterators, we aggregate range tombstones into a skyline as we visit live -memtable, immutable memtables, and SSTs. The skyline is expensive to construct but fast to determine whether a key is covered. The skyline keeps track of the most recent range tombstone found to optimize `Next` and `Prev`. - -|![](/static/images/delrange/delrange_uncollapsed.png) |![](/static/images/delrange/delrange_collapsed.png) | - -*([Image source: Leetcode](https://leetcode.com/problems/the-skyline-problem/description/)) The skyline problem involves taking building location/height data in the -unsearchable form of A and converting it to the form of B, which is -binary-searchable. With overlapping range tombstones, to achieve efficient -searching we need to solve an analogous problem, where the x-axis is the -key-space and the y-axis is the sequence number.* -{: style="text-align: center"} - -### Performance characteristics - -For the v1 implementation, writes are much faster compared to the scan and -delete (optionally within a transaction) pattern. `DeleteRange` only logs to WAL -and applies to memtable. Logging to WAL always `fflush`es, and optionally -`fsync`s or `fdatasync`s. Applying to memtable is always an in-memory operation. -Since range tombstones have a dedicated skiplist memtable, the complexity of inserting is O(log(T)), where T is the number of existing buffered range tombstones. - -Reading in the presence of v1 range tombstones, however, is much slower than reads -in a database where scan-and-delete has happened, due to the linear scan over -range tombstone memtables/meta-blocks. - -Iterating in a database with v1 range tombstones is usually slower than in a -scan-and-delete database, although the gap lessens as iterations grow longer. -When an iterator is first created and seeked, we construct a skyline over its -tombstones. This operation is O(T\*log(T)) where T is the number of tombstones -found across live memtable, immutable memtable, L0 files, and one file from each -of the L1+ levels. However, moving the iterator forwards or backwards is simply -a constant-time operation (excluding edge cases, e.g., many range tombstones -between consecutive point keys). - -## v2: Making it fast - -`DeleteRange`’s negative impact on read perf is a barrier to its adoption. The -root cause is range tombstones are not stored or cached in a format that can be -efficiently searched. We needed to design DeleteRange so that we could maintain -write performance while making read performance competitive with workarounds -used in production (e.g., scan-and-delete). - -### Representations - -The key idea of the redesign is that, instead of globally collapsing range tombstones, - we can locally “fragment” them for each SST file and memtable to guarantee that: - -* no range tombstones overlap; and -* range tombstones are ordered by start key. - -Combined, these properties make range tombstones binary searchable. This - fragmentation will happen on the read path, but unlike the previous design, we can - easily cache many of these range tombstone fragments on the read path. - -### Write path - -The write path remains unchanged. - -### Read path - -When an SST file is opened, its range tombstones are fragmented and cached. For point - lookups, we binary search each file's fragmented range tombstones for one that covers - the lookup key. Unlike the old design, once we find a tombstone, we no longer need to - search for the key in lower levels, since we know that any keys on those levels will be - covered (though we do still check the current level since there may be keys written after - the range tombstone). - -For range scans, we create iterators over all the fragmented range - tombstones and store them in a list, seeking each one to cover the start key of the range - scan (if possible), and query each encountered key in this structure as in the old design, - advancing range tombstone iterators as necessary. In effect, we implicitly create a skyline. - This requires significantly less work on iterator creation, but since each memtable/SST has -its own range tombstone iterator, querying range tombstones requires key comparisons (and -possibly iterator increments) for several iterators (as opposed to v1, where we had a global -collapsed representation of all range tombstones). As a result, very long range scans may become - slower than before, but short range scans are an order of magnitude faster, which are the - more common class of range scan. - -## Benchmarks - -To understand the performance of this new design, we used `db_bench` to compare point lookup, short range scan, - and long range scan performance across: - -* the v1 DeleteRange design, -* the scan-and-delete workaround, and -* the v2 DeleteRange design. - -In these benchmarks, we used a database with 5 million data keys, and 10000 range tombstones (ignoring -those dropped during compaction) that were written in regular intervals after 4.5 million data keys were written. -Writing the range tombstones ensures that most of them are not compacted away, and we have more tombstones -in higher levels that cover keys in lower levels, which allows the benchmarks to exercise more interesting behavior -when reading deleted keys. - -Point lookup benchmarks read 100000 keys from a database using `readwhilewriting`. Range scan benchmarks used -`seekrandomwhilewriting` and seeked 100000 times, and advanced up to 10 keys away from the seek position for short range scans, and advanced up to 1000 keys away from the seek position for long range scans. - -The results are summarized in the tables below, averaged over 10 runs (note the -different SHAs for v1 benchmarks are due to a new `db_bench` flag that was added in order to compare performance with databases with no tombstones; for brevity, those results are not reported here). Also note that the block cache was large enough to hold the entire db, so the large throughput is due to limited I/Os and little time spent on decompression. The range tombstone blocks are always pinned uncompressed in memory. We believe these setup details should not affect relative performance between versions. - -### Point Lookups - -|Name |SHA |avg micros/op |avg ops/sec | -|v1 |35cd754a6 |1.3179 |759,830.90 | -|scan-del |7528130e3 |0.6036 |1,667,237.70 | -|v2 |7528130e3 |0.6128 |1,634,633.40 | - -### Short Range Scans - -|Name |SHA |avg micros/op |avg ops/sec | -|v1 |0ed738fdd |6.23 |176,562.00 | -|scan-del |PR 4677 |2.6844 |377,313.00 | -|v2 |PR 4677 |2.8226 |361,249.70 | - -### Long Range scans - -|Name |SHA |avg micros/op |avg ops/sec | -|v1 |0ed738fdd |52.7066 |19,074.00 | -|scan-del |PR 4677 |38.0325 |26,648.60 | -|v2 |PR 4677 |41.2882 |24,714.70 | - -## Future Work - -Note that memtable range tombstones are fragmented every read; for now this is acceptable, - since we expect there to be relatively few range tombstones in memtables (and users can - enforce this by keeping track of the number of memtable range deletions and manually flushing - after it passes a threshold). In the future, a specialized data structure can be used for storing - range tombstones in memory to avoid this work. - -Another future optimization is to create a new format version that requires range tombstones to - be stored in a fragmented form. This would save time when opening SST files, and when `max_open_files` -is not -1 (i.e., files may be opened several times). - -## Acknowledgements - -Special thanks to Peter Mattis and Nikhil Benesch from Cockroach Labs, who were early users of -DeleteRange v1 in production, contributed the cleanest/most efficient v1 aggregation implementation, found and fixed bugs, and provided initial DeleteRange v2 design and continued help. - -Thanks to Huachao Huang and Jinpeng Zhang from PingCAP for early DeleteRange v1 adoption, bug reports, and fixes. diff --git a/docs/_posts/2019-03-08-format-version-4.markdown b/docs/_posts/2019-03-08-format-version-4.markdown deleted file mode 100644 index ce657696c..000000000 --- a/docs/_posts/2019-03-08-format-version-4.markdown +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: format_version 4 -layout: post -author: maysamyabandeh -category: blog ---- - -The data blocks in RocksDB consist of a sequence of key/values pairs sorted by key, where the pairs are grouped into _restart intervals_ specified by `block_restart_interval`. Up to RocksDB version 5.14, where the latest and default value of `BlockBasedTableOptions::format_version` is 2, the format of index and data blocks are the same: index blocks use the same key format of <`user_key`,`seq`> and encode pointers to data blocks, <`offset`,`size`>, to a byte string and use them as values. The only difference is that the index blocks use `index_block_restart_interval` for the size of _restart intervals_. `format_version=`3,4 offer more optimized, backward-compatible, yet forward-incompatible format for index blocks. - -### Pros - -Using `format_version`=4 significantly reduces the index block size, in some cases around 4-5x. This frees more space in block cache, which would result in higher hit rate for data and filter blocks, or offer the same performance with a smaller block cache size. - -### Cons - -Being _forward-incompatible_ means that if you enable `format_version=`4 you cannot downgrade to a RocksDB version lower than 5.16. - -### How to use it? - -- `BlockBasedTableOptions::format_version` = 4 -- `BlockBasedTableOptions::index_block_restart_interval` = 16 - -### What is format_version 3? -(Since RocksDB 5.15) In most cases, the sequence number `seq` is not necessary for keys in the index blocks. In such cases, `format_version`=3 skips encoding the sequence number and sets `index_key_is_user_key` in TableProperties, which is used by the reader to know how to decode the index block. - -### What is format_version 4? -(Since RocksDB 5.16) Changes the format of index blocks by delta encoding the index values, which are the block handles. This saves the encoding of `BlockHandle::offset` of the non-head index entries in each restart interval. If used, `TableProperties::index_value_is_delta_encoded` is set, which is used by the reader to know how to decode the index block. The format of each key is (shared_size, non_shared_size, shared, non_shared). The format of each value, i.e., block handle, is (offset, size) whenever the shared_size is 0, which included the first entry in each restart point. Otherwise the format is delta-size = block handle size - size of last block handle. - -The index format in `format_version=4` would be as follows: - - restart_point 0: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) - restart_point 1: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) - ... - restart_point n-1: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) - where, k is key, v is value, and its encoding is in parenthesis. - diff --git a/docs/_posts/2019-08-15-unordered-write.markdown b/docs/_posts/2019-08-15-unordered-write.markdown deleted file mode 100644 index 5f0eb2880..000000000 --- a/docs/_posts/2019-08-15-unordered-write.markdown +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Higher write throughput with `unordered_write` feature -layout: post -author: maysamyabandeh -category: blog ---- - -Since RocksDB 6.3, The `unordered_write=`true option together with WritePrepared transactions offers 34-42% higher write throughput compared to vanilla RocksDB. If the application can handle more relaxed ordering guarantees, the gain in throughput would increase to 63-131%. - -### Background - -Currently RocksDB API delivers the following powerful guarantees: -- Atomic reads: Either all of a write batch is visible to reads or none of it. -- Read-your-own writes: When a write thread returns to the user, a subsequent read by the same thread will be able to see its own writes. -- Immutable Snapshots: The reads visible to the snapshot are immutable in the sense that it will not be affected by any in-flight or future writes. - -### `unordered_write` - -The `unordered_write` feature, when turned on, relaxes the default guarantees of RocksDB. While it still gives read-your-own-write property, neither atomic reads nor the immutable snapshot properties are provided any longer. However, RocksDB users could still get read-your-own-write and immutable snapshots when using this feature in conjunction with TransactionDB configured with WritePrepared transactions and `two_write_queues`. You can read [here](https://github.com/facebook/rocksdb/wiki/unordered_write) to learn about the design of `unordered_write` and [here](https://github.com/facebook/rocksdb/wiki/WritePrepared-Transactions) to learn more about WritePrepared transactions. - -### How to use it? - -To get the same guarantees as vanilla RocksdB: - - DBOptions db_options; - db_options.unordered_write = true; - db_options.two_write_queues = true; - DB* db; - { - TransactionDBOptions txn_db_options; - txn_db_options.write_policy = TxnDBWritePolicy::WRITE_PREPARED; - txn_db_options.skip_concurrency_control = true; - TransactionDB* txn_db; - TransactionDB::Open(options, txn_db_options, kDBPath, &txn_db); - db = txn_db; - } - db->Write(...); - -To get relaxed guarantees: - - DBOptions db_options; - db_options.unordered_write = true; - DB* db; - DB::Open(db_options, kDBPath, &db); - db->Write(...); - -# Benchmarks - - TEST_TMPDIR=/dev/shm/ ~/db_bench --benchmarks=fillrandom --threads=32 --num=10000000 -max_write_buffer_number=16 --max_background_jobs=64 --batch_size=8 --writes=3000000 -level0_file_num_compaction_trigger=99999 --level0_slowdown_writes_trigger=99999 --level0_stop_writes_trigger=99999 -enable_pipelined_write=false -disable_auto_compactions --transaction_db=true --unordered_write=1 --disable_wal=0 - -Throughput with `unordered_write`=true and using WritePrepared transaction: -- WAL: +42% -- No-WAL: +34% -Throughput with `unordered_write`=true -- WAL: +63% -- NoWAL: +131% diff --git a/docs/_posts/2021-04-12-universal-improvements.markdown b/docs/_posts/2021-04-12-universal-improvements.markdown deleted file mode 100644 index fa4e9d463..000000000 --- a/docs/_posts/2021-04-12-universal-improvements.markdown +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: (Call For Contribution) Make Universal Compaction More Incremental -layout: post -author: sdong -category: blog ---- - -### Motivation - -Universal Compaction is an important compaction style, but few changes were made after we made the structure multi-leveled. Yet the major restriction of always compacting full sorted run is not relaxed. Compared to Leveled Compaction, where we usually only compile several SST files together, in universal compaction, we frequently compact GBs of data. Two issues with this gap: 1. it makes it harder to unify universal and leveled compaction; 2. periodically data is fully compacted, and in the mean time space is doubled. To ease the problem, we can break the restriction and do similar as leveled compaction, and bring it closer to unified compaction. - -We call for help for making following improvements. - - -### How Universal Compaction Works - -In universal, whole levels are compacted together to satisfy two conditions (See [wiki page](https://github.com/facebook/rocksdb/wiki/Universal-Compaction) for more details): - -1. total size / bottommost level size > a threshold, or -2. total number of sorted runs (non-0 levels + L0 files) is within a threshold - -1 is to limit extra space overhead used for dead data and 2 is for read performance. - -If 1 is triggered, likely a full compaction will be triggered. If 2 is triggered, RocksDB compact some sorted runs to bring the number down. It does it by using a simple heuristic so that less writes needed for that purpose over time: it starts from compacting smaller files, but if total size to compact is similar to or larger than size of the next level, it will take that level together, as soon on (whether it is the best heuristic is another question and we’ve never seriously looked at it). - -### How We Can Improve? - -Let’s start from condition 1. Here we do full compaction but is not necessary. A simple optimization would be to compact so that just enough files are merged into the bottommost level (Lmax) to satisfy condition 1. It would work if we only need to pick some files from Lmax-1, or if it is cheaper over time, we can pick some files from other levels too. - -Then condition 2. If we finish condition 1, there might be holes in some ranges in older levels. These holes might make it possible that only by compacting some sub ranges, we can fix the LSM-tree for condition 2. RocksDB can take single files into consideration and apply more sophisticated heuristic. - -This new approach makes universal compaction closer to leveled compaction. The operation for 1 is closer to how Leveled compaction triggeres Lmax-1 to Lmax compaction. And 2 can potentially be implemented as something similar to level picking in Leveled Compaction. In fact, all those file picking can co-existing in one single compaction style and there isn’t fundamental conflicts to that. - -### Limitation - -There are two limitations: - -* Periodic automatic full compaction is unpleasant but at the same time is pleasant in another way. Some users might uses it to reason that everything is periodically collapsed so dead data is gone and old data is rewritten. We need to make sure periodic compaction works to continue with that. -* L0 to the first non-L0 level compaction is the first time data is partitioned in LSM-tree so that incremental compaction by range is possible. We might need to do more of these compactions in order to make incremental possible, which will increase compaction slightly. -* Compacting subset of a level would introduce some extra overhead for unaligned files, just as in leveled compaction. More SST boundary cutting heuristic can reduce this overhead but it will be there. - -But I believe the benefits would outweight the limitations. Reducing temporary space doubling and moving towards to unified compaction would be important achievements. - -### Interested in Help? - -Compaction is the core of LSM-tree, but its improvements are far overdue. If you are a user of universal compaction and would be able to benefit from those improvements, we will be happy to work with you on speeding up the project and bring them to RocksDB sooner. Feel free to communicate with us in [this issue](https://github.com/facebook/rocksdb/issues/8181). diff --git a/docs/_posts/2021-05-26-integrated-blob-db.markdown b/docs/_posts/2021-05-26-integrated-blob-db.markdown deleted file mode 100644 index 9f3a22fa2..000000000 --- a/docs/_posts/2021-05-26-integrated-blob-db.markdown +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: Integrated BlobDB -layout: post -author: ltamasi -category: blog ---- -## Background - -BlobDB is essentially RocksDB for large-value use cases. The basic idea, which was proposed in the [WiscKey paper](https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf), is key-value separation: by storing large values in dedicated blob files and storing only small pointers to them in the LSM tree, we avoid copying the values over and over again during compaction, thus reducing write amplification. Historically, BlobDB supported only FIFO and TTL based use cases that can tolerate some data loss. In addition, it was incompatible with many widely used RocksDB features, and required users to adopt a custom API. In 2020, we decided to rearchitect BlobDB from the ground up, taking the lessons learned from WiscKey and the original BlobDB but also drawing inspiration and incorporating ideas from other similar systems. Our goals were to eliminate the above limitations and to create a new integrated version that enables customers to use the well-known RocksDB API, has feature parity with the core of RocksDB, and offers better performance. This new implementation is now available and provides the following improvements over the original: - -* **API.** In contrast with the legacy BlobDB implementation, which had its own `StackableDB`-based interface (`rocksdb::blob_db::BlobDB`), the new version can be used via the well-known `rocksdb::DB` API, and can be configured simply by using a few column family options. -* **Consistency.** With the integrated BlobDB implementation, RocksDB’s consistency guarantees and various write options (like using the WAL or synchronous writes) now apply to blobs as well. Moreover, the new BlobDB keeps track of blob files in the RocksDB MANIFEST. -* **Write performance.** When using the old BlobDB, blobs are extracted and immediately written to blob files by the BlobDB layer *in the application thread*. This has multiple drawbacks from a performance perspective: first, it requires synchronization; second, it means that expensive operations like compression are performed in the application thread; and finally, it involves flushing the blob file after each blob. The new code takes a completely different approach by *offloading blob file building to RocksDB’s background jobs*, i.e. flushes and compactions. This means that similarly to SSTs, any given blob file is now written by a single background thread, eliminating the need for locking, flushing, or performing compression in the foreground. Note that this approach is also a better fit for network-based file systems where small writes might be expensive and opens up the possibility of file format optimizations that involve buffering (like dictionary compression). -* **Read performance.** The old code relies on each read (i.e. `Get`, `MultiGet`, or iterator) taking a snapshot and uses those snapshots when deciding which obsolete blob files can be removed. The new BlobDB improves this by generalizing RocksDB’s Version concept, which historically referred to the set of live SST files at a given point in time, to include the set of live blob files as well. This has performance benefits like [making the read path mostly lock-free by utilizing thread-local storage](https://rocksdb.org/blog/2014/06/27/avoid-expensive-locks-in-get.html). We have also introduced a blob file cache that can be utilized to keep frequently accessed blob files open. -* **Garbage collection.** Key-value separation means that if a key pointing to a blob gets overwritten or deleted, the blob becomes unreferenced garbage. To be able to reclaim this space, BlobDB now has garbage collection capabilities. GC is integrated into the compaction process and works by relocating valid blobs residing in old blob files as they are encountered during compaction. Blob files can be marked obsolete (and eventually deleted in one shot) once they contain nothing but garbage. This is more efficient than the method used by WiscKey, which involves performing a `Get` operation to find out whether a blob is still referenced followed by a `Put` to update the reference, which in turn results in garbage collection competing and potentially conflicting with the application’s writes. -* **Feature parity with the RocksDB core.** The new BlobDB supports way more features than the original and is near feature parity with vanilla RocksDB. In particular, we support all basic read/write APIs (with the exception of `Merge`, which is coming soon), recovery, compression, atomic flush, column families, compaction filters, checkpoints, backup/restore, transactions, per-file checksums, and the SST file manager. In addition, the new BlobDB’s options can be dynamically adjusted using the `SetOptions` interface. - -## API - -The new BlobDB can be configured (on a per-column family basis if needed) simply by using the following options: - -* `enable_blob_files`: set it to `true` to enable key-value separation. -* `min_blob_size`: values at or above this threshold will be written to blob files during flush or compaction. -* `blob_file_size`: the size limit for blob files. -* `blob_compression_type`: the compression type to use for blob files. All blobs in the same file are compressed using the same algorithm. -* `enable_blob_garbage_collection`: set this to `true` to make BlobDB actively relocate valid blobs from the oldest blob files as they are encountered during compaction. -* `blob_garbage_collection_age_cutoff`: the threshold that the GC logic uses to determine which blob files should be considered “old.” For example, the default value of 0.25 signals to RocksDB that blobs residing in the oldest 25% of blob files should be relocated by GC. This parameter can be tuned to adjust the trade-off between write amplification and space amplification. - -The above options are all dynamically adjustable via the `SetOptions` API; changing them will affect subsequent flushes and compactions but not ones that are already in progress. - -In terms of compaction styles, we recommend using leveled compaction with BlobDB. The rationale behind universal compaction in general is to provide lower write amplification at the expense of higher read amplification; however, as we will see later in the Performance section, BlobDB can provide very low write amp and good read performance with leveled compaction. Therefore, there is really no reason to take the hit in read performance that comes with universal compaction. - -In addition to the above, consider tuning the following non-BlobDB specific options: - -* `write_buffer_size`: this is the memtable size. You might want to increase it for large-value workloads to ensure that SST and blob files contain a decent number of keys. -* `target_file_size_base`: the target size of SST files. Note that even when using BlobDB, it is important to have an LSM tree with a “nice” shape and multiple levels and files per level to prevent heavy compactions. Since BlobDB extracts and writes large values to blob files, it makes sense to make this parameter significantly smaller than the memtable size. One guideline is to set `blob_file_size` to the same value as `write_buffer_size` (adjusted for compression if needed) and make `target_file_size_base` proportionally smaller based on the ratio of key size to value size. -* `max_bytes_for_level_base`: consider setting this to a multiple (e.g. 8x or 10x) of `target_file_size_base`. - -As mentioned above, the new BlobDB now also supports compaction filters. Key-value separation actually enables an optimization here: if the compaction filter of an application can make a decision about a key-value solely based on the key, it is unnecessary to read the value from the blob file. Applications can take advantage of this optimization by implementing the new `FilterBlobByKey` method of the `CompactionFilter` interface. This method gets called by RocksDB first whenever it encounters a key-value where the value is stored in a blob file. If this method returns a “final” decision like `kKeep`, `kRemove`, `kChangeValue`, or `kRemoveAndSkipUntil`, RocksDB will honor that decision; on the other hand, if the method returns `kUndetermined`, RocksDB will read the blob from the blob file and call `FilterV2` with the value in the usual fashion. - -## Performance - -We tested the performance of the new BlobDB for six different value sizes between 1 KB and 1 MB using a customized version of our [standard benchmark suite](https://github.com/facebook/rocksdb/wiki/Performance-Benchmarks) on a box with an 18-core Skylake DE CPU (running at 1.6 GHz, with hyperthreading enabled), 64 GB RAM, a 512 GB boot SSD, and two 1.88 TB M.2 SSDs in a RAID0 configuration for data. The RocksDB version used was equivalent to 6.18.1, with some benchmarking and statistics related enhancements. Leveled and universal compaction without key-value separation were used as reference points. Note that for simplicity, we use “leveled compaction” and “universal compaction” as shorthand for leveled and universal compaction without key-value separation, respectively, and “BlobDB” for BlobDB with leveled compaction. - -Our benchmarks cycled through six different workloads: two write-only ones (initial load and overwrite), two read/write ones (point lookup/write mix and range scan/write mix), and finally two read-only ones (point lookups and range scans). The first two phases performed a fixed amount of work (see below), while the final four were run for a fixed amount of time, namely 30 minutes each. Each phase other than the first one started with the database state left behind by the previous one. Here’s a brief description of the workloads: - -* **Initial load**: this workload has two distinct stages, a single-threaded random write stage during which compactions are disabled (so all data is flushed to L0, where it remains for the rest of the stage), followed by a full manual compaction. The random writes are performed with load-optimized settings, namely using the vector memtable implementation and with concurrent memtable writes and WAL disabled. This stage was used to populate the database with 1 TB worth of raw values, e.g. 2^30 (~1 billion) 1 KB values or 2^20 (~1 million) 1 MB values. -* **Overwrite**: this is a multi-threaded random write workload using the usual skiplist memtable, with compactions, WAL, and concurrent memtable writes enabled. In our tests, 16 writer threads were used. The total number of writes was set to the same number as in the initial load stage and split up evenly between the writer threads. For instance, for the 1 MB value size, we had 2^20 writes divided up between the 16 threads, resulting in each thread performing 2^16 write operations. At the end of this phase, a “wait for compactions” step was added to prevent this workload from exhibiting artificially low write amp or conversely, the next phase showing inflated write amp. -* **Point lookup/write mix**: a single writer thread performing random writes while N (in our case, 16) threads perform random point lookups. WAL is enabled and all writes are synced. -* **Range scan/write mix**: similar to the above, with one writer thread and N reader threads (where N was again set to 16 in our tests). The reader threads perform random range scans, with 10 `Next` calls per `Seek`. Again, WAL is enabled, and sync writes are used. -* **Point lookups (read-only)**: N=16 threads perform random point lookups. -* **Range scans (read-only)**: N=16 threads execute random range scans, with 10 `Next`s per `Seek` like above. - -With that out of the way, let’s see how the new BlobDB performs against traditional leveled and universal compaction. In the next few sections, we’ll be looking at write amplification as well as read and write performance. We’ll also briefly compare the write performance of the new BlobDB with the legacy implementation. - -### Write amplification - -Reducing write amp is the original motivation for key-value separation. Here, we follow RocksDB’s definition of write amplification (as used in compaction statistics and the info log). That is, we define write amp as the total amount of data written by flushes and compactions divided by the amount of data written by flushes, where “data written” includes SST files and blob files as well (if applicable). The following charts show that BlobDB significantly reduces write amplification for all of our (non-read only) workloads. - -For the initial load, where due to the nature of the workload both leveled and universal already have a low write amp factor of 1.6, BlobDB has a write amp close to the theoretical minimum of 1.0, namely in the 1.0..1.02 range, depending on value size. How is this possible? Well, the trick is that when key-value separation is used, the full compaction step only has to sort the keys but not the values. This results in a write amp that is about **36% lower** than the already low write amp you get with either leveled or universal. - -In the case of the overwrite workload, BlobDB had a write amp between 1.4 and 1.7 depending on value size. This is around **75-78% lower** than the write amp of leveled compaction (6.1 to 6.8) and **70-77% lower** than universal (5.7 to 6.2); for this workload, there wasn’t a huge difference between the performance of leveled and universal. - -When it comes to the point lookup/write mix workload, BlobDB had a write amp between 1.4 and 1.8. This is **83-88% lower** than the write amp of leveled compaction, which had values between 10.8 and 12.5. Universal fared much better than leveled under this workload, and had write amp in the 2.2..6.6 range; however, BlobDB still provided significant gains for all value sizes we tested: namely, write amp was **18-77% lower** than that of universal, depending on value size. - -As for the range scan/write mix workload, BlobDB again had a write amp between 1.4 and 1.8, while leveled had values between 13.6 and 14.9, and universal was between 2.8 and 5.0. In other words, BlobDB’s write amp was **88-90% lower** than that of leveled, and **46-70% lower** than that of universal. - -![Write amplification](/static/images/integrated-blob-db/BlobDB_Benchmarks_Write_Amp.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -### Write performance - -In terms of write performance, there are other factors to consider besides write amplification. The following charts show some interesting metrics for the two write-only workloads (initial load and overwrite). As discussed earlier, these two workloads perform a fixed amount of work; the two charts in the top row show how long it took BlobDB, leveled, and universal to complete that work. Note that each bar is broken down into two, corresponding to the two stages of each workload (random write and full compaction for initial load, and random write and waiting for compactions for overwrite). - -For initial load, note that the random write stage takes the same amount of time regardless of which algorithm is used. This is not surprising considering the fact that compactions are disabled during this stage and thus RocksDB is simply writing L0 files (and in BlobDB’s case, blob files) as fast as it can. The second stage, on the other hand, is very different: as mentioned above, BlobDB essentially only needs to read, sort, and rewrite the keys during compaction, which can be done much much faster (with 1 MB values, more than a hundred times faster) than doing the same for large key-values. Due to this, initial load completed **2.3x to 4.7x faster** overall when using BlobDB. - -As for the overwrite workload, BlobDB performs much better during both stages. The two charts in the bottom row help explain why. In the case of both leveled and universal compaction, compactions can’t keep up with the write rate, which eventually leads to back pressure in the form of write stalls. As shown in the chart below, both leveled and universal stall between ~40% and ~70% of the time; on the other hand, BlobDB is stall-free except for the largest value size tested (1 MB). This naturally leads to higher throughput, namely **2.1x to 3.5x higher** throughput compared to leveled, and **1.6x to 3.0x higher** throughput compared to universal. The overwrite time chart also shows that the catch-up stage that waits for all compactions to finish is much shorter (and in fact, at larger value sizes, negligible) with BlobDB. - -![Write performance](/static/images/integrated-blob-db/BlobDB_Benchmarks_Write_Perf.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -### Read/write and read-only performance - -The charts below show the read performance (in terms of operations per second) of BlobDB versus leveled and universal compaction under the two read/write workloads and the two read-only workloads. BlobDB meets or exceeds the read performance of leveled compaction, except for workloads involving range scans at the two smallest value sizes tested (1 KB and 4 KB). It also provides better (in some cases, much better) read performance than universal across the board. In particular, BlobDB provides up **1.4x higher** read performance than leveled (for larger values), and up to **5.6x higher** than universal. - -![Read-write and read-only performance](/static/images/integrated-blob-db/BlobDB_Benchmarks_RW_RO_Perf.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -### Comparing the two BlobDB implementations - -To compare the write performance of the new BlobDB with the legacy implementation, we ran two versions of the first (single-threaded random write) stage of the initial load benchmark using 1 KB values: one with WAL disabled, and one with WAL enabled. The new implementation completed the load **4.6x faster** than the old one without WAL, and **2.3x faster** with WAL. - -![Comparing the two BlobDB implementations](/static/images/integrated-blob-db/BlobDB_Benchmarks_Legacy_Vs_Integrated.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -## Future work - -There are a few remaining features that are not yet supported by the new BlobDB. The most important one is `Merge` (and the related `GetMergeOperands` API); in addition, we don’t currently support the `EventListener` interface, the `GetLiveFilesMetaData` and `GetColumnFamilyMetaData` APIs, secondary instances, and ingestion of blob files. We will continue to work on closing this gap. - -We also have further plans when it comes to performance. These include optimizing garbage collection, introducing a dedicated cache for blobs, improving iterator and `MultiGet` performance, and evolving the blob file format amongst others. - diff --git a/docs/_posts/2021-05-26-online-validation.markdown b/docs/_posts/2021-05-26-online-validation.markdown deleted file mode 100644 index 33e9dfc15..000000000 --- a/docs/_posts/2021-05-26-online-validation.markdown +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Online Validation -layout: post -author: sdong -category: blog ---- -To prevent or mitigate data corrution in RocksDB when some software or hardware issues happens, we keep adding online consistency checks and improving existing ones. - -We improved ColumnFamilyOptions::force_consistency_checks and enabled it by default. The option does some basic consistency checks to LSM-tree, e.g., files in one level are not overlapping. The DB will be frozen from new writes if a violation is detected. Previously, the feature’s check was too limited and didn’t always freeze the DB in a timely manner. Last year, we made the checking stricter so that it can [catch much more corrupted LSM-tree structures](https://github.com/facebook/rocksdb/pull/6901). We also fixed several issues where the checking failure was swallowed without freezing the DB. After making force_consistency_checks more reliable, we changed the default value to be on. - -ColumnFamilyOptions::paranoid_file_checks does some more expensive extra checking when generating a new SST file. Last year, we advanced coverage to this feature: after every SST file is generated, the SST file is created, read back keys one by one and check two things: (1) the keys are in comparator order (also available and enabled by default during file write via ColumnFamilyOptions::check_flush_compaction_key_order); (2) the hash of all the KVs is the same as calculated when we add KVs into it. These checks detect certain corruptions so we can prevent the corrupt files from being applied to the DB. We suggest users turn it on at least in shadow environments, and consider to run it in production too if you can afford the overheads. - -A recent feature is added to check the count of entries added into memtable while flushing it into an SST file. This feature is to have some online coverage to memtable corruption, caused by either software bug or hardware issue. This feature will be released in the coming release (6.21) and by default on. In the future, we will check more counters during memtables, e.g. number of puts or number of deletes. - -We also improved the reporting of online validation errors to improve debuggability. For example, failure to parse a corrupt key now reports details about the corrupt key. Since we did not want to expose key data in logs, error messages, etc., by default, this reporting is opt-in via DBOptions::allow_data_in_errors. - -More online checking features are planned and some are more sophisticated, including key/value checksums and sample based query validation. diff --git a/docs/_posts/2021-05-27-rocksdb-secondary-cache.markdown b/docs/_posts/2021-05-27-rocksdb-secondary-cache.markdown deleted file mode 100644 index 3ad1141bf..000000000 --- a/docs/_posts/2021-05-27-rocksdb-secondary-cache.markdown +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: RocksDB Secondary Cache -layout: post -author: anand1976 -category: blog ---- -## Introduction - -The RocksDB team is implementing support for a block cache on non-volatile media, such as a local flash device or NVM/SCM. It can be viewed as an extension of RocksDB’s current volatile block cache (LRUCache or ClockCache). The non-volatile block cache acts as a second tier cache that contains blocks evicted from the volatile cache. Those blocks are then promoted to the volatile cache as they become hotter due to access. - -This feature is meant for cases where the DB is located on remote storage or cloud storage. The non-volatile cache is officially referred to in RocksDB as the SecondaryCache. By maintaining a SecondaryCache that’s an order of magnitude larger than DRAM, fewer reads would be required from remote storage, thus reducing read latency as well as network bandwidth consumption. - -From the user point of view, the local flash cache will support the following requirements - - -1. Provide a pointer to a secondary cache when opening a DB -2. Be able to share the secondary cache across DBs in the same process -3. Have multiple secondary caches on a host -4. Support persisting the cache across process restarts and reboots by ensuring repeatability of the cache key - -![Architecture](/static/images/rocksdb-secondary-cache/arch_diagram.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -## Design - -When designing the API for a SecondaryCache, we had a choice between making it visible to the RocksDB code (table reader) or hiding it behind the RocksDB block cache. There are several advantages of hiding it behind the block cache - - -* Allows flexibility in insertion of blocks into the secondary cache. A block can be inserted on eviction from the RAM tier, or it could be eagerly inserted. -* It makes the rest of the RocksDB code less complex by providing a uniform interface regardless of whether a secondary cache is configured or not -* Makes parallel reads, peeking in the cache for prefetching, failure handling etc. easier -* Makes it easier to extend to compressed data if needed, and allows other persistent media, such as PM, to be added as an additional tier - - -We decided to make the secondary cache transparent to the rest of RocksDB code by hiding it behind the block cache. A key issue that we needed to address was the allocation and ownership of memory of the cached items - insertion into the secondary cache may require that memory be allocated by the same. This means that parts of the cached object that can be transferred to the secondary cache needs to be copied out (referred to as **unpacking**), and on a lookup the data stored in the secondary cache needs to be provided to the object constructor (referred to as **packing**). For RocksDB cached objects such as data blocks, index and filter blocks, and compression dictionaries, unpacking involves copying out the raw uncompressed BlockContents of the block, and packing involves constructing the corresponding block/index/filter/dictionary object using the raw uncompressed data. - -Another alternative we considered was the existing PersistentCache interface. However, we decided to not pursue it and eventually deprecate it for the following reasons - -* It is exposed directly to the table reader code, which makes it more difficult to implement different policies such as inclusive/exclusive cache, as well as extending it to more sophisticated admission control policies -* The interface does not allow for custom memory allocation and object packing/unpacking, so new APIs would have to be defined anyway -* The current PersistentCache implementation is very simple and does not have any admission control policies - -## API - -The interface between RocksDB’s block cache and the secondary cache is designed to allow pluggable implementations. For FB internal usage, we plan to use Cachelib with a wrapper to provide the plug-in implementation and use folly and other fbcode libraries, which cannot be used directly by RocksDB, to efficiently implement the cache operations. The following diagrams show the flow of insertion and lookup of a block. - -![Insert flow](/static/images/rocksdb-secondary-cache/insert_flow.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -![Lookup flow](/static/images/rocksdb-secondary-cache/lookup_flow.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -An item in the secondary cache is referenced by a SecondaryCacheHandle. The handle may not be immediately ready or have a valid value. The caller can call IsReady() to determine if its ready, and can call Wait() in order to block until it becomes ready. The caller must call Value() after it becomes ready to determine if the item was successfully read. Value() must return nullptr on failure. - -``` -class SecondaryCacheHandle { - public: - virtual ~SecondaryCacheHandle() {} - - // Returns whether the handle is ready or not - virtual bool IsReady() = 0; - - // Block until handle becomes ready - virtual void Wait() = 0; - - // Return the value. If nullptr, it means the lookup was unsuccessful - virtual void* Value() = 0; - - // Return the size of value - virtual size_t Size() = 0; -}; -``` - -The user of the secondary cache (for example, BlockBasedTableReader indirectly through LRUCache) must implement the callbacks defined in CacheItemHelper, in order to facilitate the unpacking/packing of objects for saving to and restoring from the secondary cache. The CreateCallback must be implemented to construct a cacheable object from the raw data in secondary cache. - -``` - // The SizeCallback takes a void* pointer to the object and returns the size - // of the persistable data. It can be used by the secondary cache to allocate - // memory if needed. - using SizeCallback = size_t (*)(void* obj); - - // The SaveToCallback takes a void* object pointer and saves the persistable - // data into a buffer. The secondary cache may decide to not store it in a - // contiguous buffer, in which case this callback will be called multiple - // times with increasing offset - using SaveToCallback = Status (*)(void* from_obj, size_t from_offset, - size_t length, void* out); - - // A function pointer type for custom destruction of an entry's - // value. The Cache is responsible for copying and reclaiming space - // for the key, but values are managed by the caller. - using DeleterFn = void (*)(const Slice& key, void* value); - - // A struct with pointers to helper functions for spilling items from the - // cache into the secondary cache. May be extended in the future. An - // instance of this struct is expected to outlive the cache. - struct CacheItemHelper { - SizeCallback size_cb; - SaveToCallback saveto_cb; - DeleterFn del_cb; - - CacheItemHelper() : size_cb(nullptr), saveto_cb(nullptr), del_cb(nullptr) {} - CacheItemHelper(SizeCallback _size_cb, SaveToCallback _saveto_cb, - DeleterFn _del_cb) - : size_cb(_size_cb), saveto_cb(_saveto_cb), del_cb(_del_cb) {} - }; - - // The CreateCallback is passed by the block cache user to Lookup(). It - // takes in a buffer from the NVM cache and constructs an object using - // it. The callback doesn't have ownership of the buffer and should - // copy the contents into its own buffer. - // typedef std::function - // CreateCallback; - using CreateCallback = std::function; -``` - -The secondary cache provider must provide a concrete implementation of the SecondaryCache abstract class. - -``` -// SecondaryCache -// -// Cache interface for caching blocks on a secondary tier (which can include -// non-volatile media, or alternate forms of caching such as compressed data) -class SecondaryCache { - public: - virtual ~SecondaryCache() {} - - virtual std::string Name() = 0; - - static const std::string Type() { return "SecondaryCache"; } - - // Insert the given value into this cache. The value is not written - // directly. Rather, the SaveToCallback provided by helper_cb will be - // used to extract the persistable data in value, which will be written - // to this tier. The implementation may or may not write it to cache - // depending on the admission control policy, even if the return status is - // success. - virtual Status Insert(const Slice& key, void* value, - const Cache::CacheItemHelper* helper) = 0; - - // Lookup the data for the given key in this cache. The create_cb - // will be used to create the object. The handle returned may not be - // ready yet, unless wait=true, in which case Lookup() will block until - // the handle is ready - virtual std::unique_ptr Lookup( - const Slice& key, const Cache::CreateCallback& create_cb, bool wait) = 0; - - // At the discretion of the implementation, erase the data associated - // with key - virtual void Erase(const Slice& key) = 0; - - // Wait for a collection of handles to become ready. This would be used - // by MultiGet, for example, to read multitple data blocks in parallel - virtual void WaitAll(std::vector handles) = 0; - - virtual std::string GetPrintableOptions() const = 0; -}; -``` - -A SecondaryCache is configured by the user by providing a pointer to it in LRUCacheOptions - -``` -struct LRUCacheOptions { - ... - // A SecondaryCache instance to use as an additional cache tier - std::shared_ptr secondary_cache; - ... -}; -``` - -## Current Status - -The initial RocksDB support for the secondary cache has been merged into the main branch, and will be available in the 6.21 release. This includes providing a way for the user to configure a secondary cache when instantiating RocksDB’s LRU cache (volatile block cache), spilling blocks evicted from the LRU cache to the flash cache, promoting a block read from the SecondaryCache to the LRU cache, update tools such as cache_bench and db_bench to specify a flash cache. The relevant PRs are [#8271](https://github.com/facebook/rocksdb/pull/8271), [#8191](https://github.com/facebook/rocksdb/pull/8191), and [#8312](https://github.com/facebook/rocksdb/pull/8312). - -We prototyped an end-to-end solution, with the above PRs as well as a Cachelib based implementation of the SecondaryCache. We ran a mixgraph benchmark to simulate a realistic read/write workload. The results showed a 15% gain with the local flash cache over no local cache, and a ~25-30% reduction in network reads with a corresponding decrease in cache misses. - -![Throughput](/static/images/rocksdb-secondary-cache/Mixgraph_throughput.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -![Hit Rate](/static/images/rocksdb-secondary-cache/Mixgraph_hit_rate.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -## Future Work - -In the short term, we plan to do the following in order to fully integrate the SecondaryCache with RocksDB - - -1. Use DB session ID as the cache key prefix to ensure uniqueness and repeatability -2. Optimize flash cache usage of MultiGet and iterator workloads -3. Stress testing -4. More benchmarking - -Longer term, we plan to deploy this in production at Facebook. - -## Call to Action - -We are hoping for a community contribution of a secondary cache implementation, which would make this feature usable by the broader RocksDB userbase. If you are interested in contributing, please reach out to us in [this issue](https://github.com/facebook/rocksdb/issues/8347). - diff --git a/docs/_posts/2021-05-31-dictionary-compression.markdown b/docs/_posts/2021-05-31-dictionary-compression.markdown deleted file mode 100644 index 9b0f45293..000000000 --- a/docs/_posts/2021-05-31-dictionary-compression.markdown +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: Preset Dictionary Compression -layout: post -author: ajkr -category: blog ---- - -## Summary - -Compression algorithms relying on an adaptive dictionary, such as LZ4, zstd, and zlib, struggle to achieve good compression ratios on small inputs when using the basic compress API. -With the basic compress API, the compressor starts with an empty dictionary. -With small inputs, not much content gets added to the dictionary during the compression. -Combined, these factors suggest the dictionary will never have enough contents to achieve great compression ratios. - -RocksDB groups key-value pairs into data blocks before storing them in files. -For use cases that are heavy on random accesses, smaller data block size is sometimes desirable for reducing I/O and CPU spent reading blocks. -However, as explained above, smaller data block size comes with the downside of worse compression ratio when using the basic compress API. - -Fortunately, zstd and other libraries offer advanced compress APIs that preset the dictionary. -A preset dictionary makes it possible for the compressor to start from a useful state instead of from an empty one, making compression immediately effective. - -RocksDB now optionally takes advantage of these dictionary presetting APIs. -The challenges in integrating this feature into the storage engine were more substantial than apparent on the surface. -First, we need to target a preset dictionary to the relevant data. -Second, preset dictionaries need to be trained from data samples, which need to be gathered. -Third, preset dictionaries need to be persisted since they are needed at decompression time. -Fourth, overhead in accessing the preset dictionary must be minimized to prevent regression in critical code paths. -Fifth, we need easy-to-use measurement to evaluate candidate use cases and production impact. - -In production, we have deployed dictionary presetting to save space in multiple RocksDB use cases with data block size 8KB or smaller. -We have measured meaningful benefit to compression ratio in use cases with data block size up to 16KB. -We have also measured a use case that can save both CPU and space by reducing data block size and turning on dictionary presetting at the same time. - -## Feature design -#### Targeting - -Over time we have considered a few possibilities for the scope of a dictionary. - -- Subcompaction -- SST file -- Column family - -The original choice was subcompaction scope. -This enabled an approach with minimal buffering overhead because we could collect samples while generating the first output SST file. -The dictionary could then be trained and applied to subsequent SST files in the same subcompaction. - -However, we found a large use case where the proximity of data in the keyspace was more correlated with its similarity than we had predicted. -In particular, the approach of training a dictionary on an adjacent file yielded substantially worse ratios than training the dictionary on the same file it would be used to compress. -In response to this finding, we changed the preset dictionary scope to per SST file. - -With this change in approach, we had to face the problem we had hoped to avoid: how can we compress all of an SST file's data blocks with the same preset dictionary while that dictionary can only be trained after many data blocks have been sampled? -The solutions we considered both involved a new overhead. -We could read the input more than once and introduce I/O overhead, or we could buffer the uncompressed output file data blocks until a dictionary is trained, introducing memory overhead. -We chose to take the hit on memory overhead. - -Another approach that we considered was associating multiple dictionaries with a column family. -For example, in MyRocks there could be a dictionary trained on data from each large table. -When compressing a data block, we would look at the table to which its data belongs and pick the corresponding dictionary. -However, this approach would introduce many challenges. -RocksDB would need to be aware of the key schema to know where are the table boundaries. -RocksDB would also need to periodically update the dictionaries to account for changes in data pattern. -It would need somewhere to store dictionaries at column family scope. -Overall, we thought these challenges were too difficult to pursue the approach. - -#### Training - -![](/static/images/dictcmp/dictcmp_raw_sampled.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} -

-Raw samples mode (`zstd_max_train_bytes == 0`) -

- -As mentioned earlier, the approach we took is to build the dictionary from buffered uncompressed data blocks. -The first row of data blocks in these diagrams illustrate this buffering. -The second row illustrates training samples selected from the buffered blocks. -In raw samples mode (above), the final dictionary is simply the concatenation of these samples. -Whereas, in zstd training mode (below), these samples will be passed to the trainer to produce the final dictionary. - -![](/static/images/dictcmp/dictcmp_zstd_trained.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} -

-zstd training mode (`zstd_max_train_bytes > 0`) -

- -#### Compression path - -Once the preset dictionary is generated by the above process, we apply it to the buffered data blocks and write them to the output file. -Thereafter, newly generated data blocks are immediately compressed and written out. - -One optimization here is available to zstd v0.7.0+ users. -Instead of deserializing the dictionary on each compress invocation, we can do that work once and reuse it. -A `ZSTD_CDict` holds this digested dictionary state and is passed to the compress API. - -#### Persistence - -When an SST file's data blocks are compressed using a preset dictionary, that dictionary is stored inside the file for later use in decompression. - -![](/static/images/dictcmp/dictcmp_sst_blocks.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} -

-SST file layout with the preset dictionary in its own (uncompressed) block -

- -#### Decompression path - -To decompress, we need to provide both the data block and the dictionary used to compress it. -Since dictionaries are just blocks in a file, we access them through block cache. -However this additional load on block cache can be problematic. -It can be alleviated by pinning the dictionaries to avoid going through the LRU locks. - -An optimization analogous to the digested dictionary exists for certain zstd users (see User API section for details). -When enabled, the block cache stores the digested dictionary state for decompression (`ZSTD_DDict`) instead of the block contents. -In some cases we have seen decompression CPU decrease overall when enabling dictionary thanks to this optimization. - -#### Measurement - -Typically our first step in evaluating a candidate use case is an offline analysis of the data. -This gives us a quick idea whether presetting dictionary will be beneficial without any code, config, or data changes. -Our `sst_dump` tool reports what size SST files would have been using specified compression libraries and options. -We can select random SST files and compare the size with vs. without dictionary. - -When that goes well, the next step is to see how it works in a live DB, like a production shadow or canary. -There we can observe how it affects application/system metrics. - -Even after dictionary is enabled, there is the question of how much space was finally saved. -We provide a way to A/B test size with vs. without dictionary while running in production. -This feature picks a sample of data blocks to compress in multiple ways -- one of the outputs is stored, while the other outputs are thrown away after counting their size. -Due to API limitations, the stored output always has to be the dictionary-compressed one, so this feature can only be used after enabling dictionary. -The size with and without dictionary are stored in the SST file as table properties. -These properties can be aggregated across all SST files in a DB (and across all DBs in a tier) to learn the final space saving. - -## User API - -RocksDB allows presetting compression dictionary for users of LZ4, zstd, and zlib. -The most advanced capabilities are available to zstd v1.1.4+ users who statically link (see below). -Newer versions of zstd (v1.3.6+) have internal changes to the dictionary trainer and digested dictionary management, which significantly improve memory and CPU efficiency. - -Run-time settings: - -- `CompressionOptions::max_dict_bytes`: Limit on per-SST file dictionary size. Increasing this causes dictionaries to consume more space and memory for the possibility of better data block compression. A typical value we use is 16KB. -- (**zstd only**) `CompressionOptions::zstd_max_train_bytes`: Limit on training data passed to zstd dictionary trainer. Larger values cause the training to consume more CPU (and take longer) while generating more effective dictionaries. The starting point guidance we received from zstd team is to set it to 100x `CompressionOptions::max_dict_bytes`. -- `CompressionOptions::max_dict_buffer_bytes`: Limit on data buffering from which training samples are gathered. By default we buffer up to the target file size per ongoing background job. If this amount of memory is concerning, this option can constrain the buffering with the downside that training samples will cover a smaller portion of the SST file. Work is ongoing to charge this memory usage to block cache so it will not need to be accounted for separately. -- `BlockBasedTableOptions::cache_index_and_filter_blocks`: Controls whether metadata blocks including dictionary are accessed through block cache or held in table reader memory (yes, its name is outdated). -- `BlockBasedTableOptions::metadata_cache_options`: Controls what metadata blocks are pinned in block cache. Pinning avoids LRU contention at the risk of cold blocks holding memory. -- `ColumnFamilyOptions::sample_for_compression`: Controls frequency of measuring extra compressions on data blocks using various libraries with default settings (i.e., without preset dictionary). - -Compile-time setting: - -- (**zstd only**) `EXTRA_CXXFLAGS=-DZSTD_STATIC_LINKING_ONLY`: Hold digested dictionaries in block cache to save repetitive deserialization overhead. This saves a lot of CPU for read-heavy workloads. This compiler flag is necessary because one of the digested dictionary APIs we use is marked as experimental. We still use it in production, however. - -Function: - -- `DB::GetPropertiesOfAllTables()`: The properties `kSlowCompressionEstimatedDataSize` and `kFastCompressionEstimatedDataSize` estimate what the data block size (`kDataSize`) would have been if the corresponding compression library had been used. These properties are only present when `ColumnFamilyOptions::sample_for_compression` causes one or more samples to be measured, and they become more accurate with higher sampling frequency. - -Tool: - -- `sst_dump --command=recompress`: Offline analysis tool that reports what the SST file size would have been using the specified compression library and options. diff --git a/docs/_posts/2021-12-29-ribbon-filter.markdown b/docs/_posts/2021-12-29-ribbon-filter.markdown deleted file mode 100644 index c6a52ce84..000000000 --- a/docs/_posts/2021-12-29-ribbon-filter.markdown +++ /dev/null @@ -1,281 +0,0 @@ ---- -title: Ribbon Filter -layout: post -author: pdillinger -category: blog ---- - -## Summary -Since version 6.15 last year, RocksDB supports Ribbon filters, a new -alternative to Bloom filters that save space, especially memory, at -the cost of more CPU usage, mostly in constructing the filters in the -background. Most applications with long-lived data (many hours or -longer) will likely benefit from adopting a Ribbon+Bloom hybrid filter -policy. Here we explain why and how. - -[Ribbon filter on RocksDB wiki](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#ribbon-filter) - -[Ribbon filter paper](https://arxiv.org/abs/2103.02515) - -## Problem & background -Bloom filters play a critical role in optimizing point queries and -some range queries in LSM-tree storage systems like RocksDB. Very -large DBs can use 10% or more of their RAM memory for (Bloom) filters, -so that (average case) read performance can be very good despite high -(worst case) read amplification, [which is useful for lowering write -and/or space -amplification](http://smalldatum.blogspot.com/2015/11/read-write-space-amplification-pick-2_23.html). -Although the `format_version=5` Bloom filter in RocksDB is extremely -fast, all Bloom filters use around 50% more space than is -theoretically possible for a hashed structure configured for the same -false positive (FP) rate and number of keys added. What would it take -to save that significant share of “wasted” filter memory, and when -does it make sense to use such a Bloom alternative? - -A number of alternatives to Bloom filters were known, especially for -static filters (not modified after construction), but all the -previously known structures were unsatisfying for SSTs because of some -combination of -* Not enough space savings for CPU increase. For example, [Xor - filters](https://arxiv.org/abs/1912.08258) use 3-4x more CPU than - Bloom but only save 15-20% of - space. [GOV](https://arxiv.org/pdf/1603.04330.pdf) can save around - 30% space but requires around 10x more CPU than Bloom. -* Inconsistent space savings. [Cuckoo - filters](https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf) - and Xor+ filters offer significant space savings for very low FP - rates (high bits per key) but little or no savings for higher FP - rates (low bits per key). ([Higher FP rates are considered best for - largest levels of - LSM.](https://stratos.seas.harvard.edu/files/stratos/files/monkeykeyvaluestore.pdf)) - [Spatially-coupled Xor - filters](https://arxiv.org/pdf/2001.10500.pdf) require very large - number of keys per filter for large space savings. -* Inflexible configuration. No published alternatives offered the same - continuous configurability of Bloom filters, where any FP rate and - any fractional bits per key could be chosen. This flexibility - improves memory efficiency with the `optimize_filters_for_memory` - option that minimizes internal fragmentation on filters. - -## Ribbon filter development and implementation -The Ribbon filter came about when I developed a faster, simpler, and -more adaptable algorithm for constructing a little-known [Xor-based -structure from Dietzfelbinger and -Walzer](https://arxiv.org/pdf/1907.04750.pdf). It has very good space -usage for required CPU time (~30% space savings for 3-4x CPU) and, -with some engineering, Bloom-like configurability. The complications -were managable for use in RocksDB: -* Ribbon space efficiency does not naturally scale to very large - number of keys in a single filter (whole SST file or partition), but - with the current 128-bit Ribbon implementation in RocksDB, even 100 - million keys in one filter saves 27% space vs. Bloom rather than 30% - for 100,000 keys in a filter. -* More temporary memory is required during construction, ~230 bits per - key for 128-bit Ribbon vs. ~75 bits per key for Bloom filter. A - quick calculation shows that if you are saving 3 bits per key on the - generated filter, you only need about 50 generated filters in memory - to offset this temporary memory usage. (Thousands of filters in - memory is typical.) Starting in RocksDB version 6.27, this temporary - memory can be accounted for under block cache using - `BlockBasedTableOptions::reserve_table_builder_memory`. -* Ribbon filter queries use relatively more CPU for lower FP rates - (but still O(1) relative to number of keys added to filter). This - should be OK because lower FP rates are only appropriate when then - cost of a false positive is very high (worth extra query time) or - memory is not so constrained (can use Bloom instead). - -Future: data in [the paper](https://arxiv.org/abs/2103.02515) suggests -that 32-bit Balanced Ribbon (new name: [Bump-Once -Ribbon](https://arxiv.org/pdf/2109.01892.pdf)) would improve all of -these issues and be better all around (except for code complexity). - -## Ribbon vs. Bloom in RocksDB configuration -Different applications and hardware configurations have different -constraints, but we can use hardware costs to examine and better -understand the trade-off between Bloom and Ribbon. - -### Same FP rate, RAM vs. CPU hardware cost -Under ideal conditions where we can adjust our hardware to suit the -application, in terms of dollars, how much does it cost to construct, -query, and keep in memory a Bloom filter vs. a Ribbon filter? The -Ribbon filter costs more for CPU but less for RAM. Importantly, the -RAM cost directly depends on how long the filter is kept in memory, -which in RocksDB is essentially the lifetime of the filter. -(Temporary RAM during construction is so short-lived that it is -ignored.) Using some consumer hardware and electricity prices and a -predicted balance between construction and queries, we can compute a -“break even” duration in memory. To minimize cost, filters with a -lifetime shorter than this should be Bloom and filters with a lifetime -longer than this should be Ribbon. (Python code) - -``` -# Commodity prices based roughly on consumer prices and rough guesses -# Upfront cost of a CPU per hardware thread -upfront_dollars_per_cpu_thread = 30.0 - -# CPU average power usage per hardware thread -watts_per_cpu_thread = 3.5 - -# Upfront cost of a GB of RAM -upfront_dollars_per_gb_ram = 8.0 - -# RAM average power usage per GB -# https://www.crucial.com/support/articles-faq-memory/how-much-power-does-memory-use -watts_per_gb_ram = 0.375 - -# Estimated price of power per kilowatt-hour, including overheads like conversion losses and cooling -dollars_per_kwh = 0.35 - -# Assume 3 year hardware lifetime -hours_per_lifetime = 3 * 365 * 24 -seconds_per_lifetime = hours_per_lifetime * 60 * 60 - -# Number of filter queries per key added in filter construction is heavily dependent on workload. -# When replication is in layer above RocksDB, it will be low, likely < 1. When replication is in -# storage layer below RocksDB, it will likely be > 1. Using a rough and general guesstimate. -key_query_per_construct = 1.0 - -#================================== -# Bloom & Ribbon filter performance -typical_bloom_bits_per_key = 10.0 -typical_ribbon_bits_per_key = 7.0 - -# Speeds here are sensitive to many variables, especially query speed because it -# is so dependent on memory latency. Using this benchmark here: -# for IMPL in 2 3; do -# ./filter_bench -impl=$IMPL -quick -m_keys_total_max=200 -use_full_block_reader -# done -# and "Random filter" queries. -nanoseconds_per_construct_bloom_key = 32.0 -nanoseconds_per_construct_ribbon_key = 140.0 - -nanoseconds_per_query_bloom_key = 500.0 -nanoseconds_per_query_ribbon_key = 600.0 - -#================================== -# Some constants -kwh_per_watt_lifetime = hours_per_lifetime / 1000.0 -bits_per_gb = 8 * 1024 * 1024 * 1024 - -#================================== -# Crunching the numbers -# on CPU for constructing filters -dollars_per_cpu_thread_lifetime = upfront_dollars_per_cpu_thread + watts_per_cpu_thread * kwh_per_watt_lifetime * dollars_per_kwh -dollars_per_cpu_thread_second = dollars_per_cpu_thread_lifetime / seconds_per_lifetime - -dollars_per_construct_bloom_key = dollars_per_cpu_thread_second * nanoseconds_per_construct_bloom_key / 10**9 -dollars_per_construct_ribbon_key = dollars_per_cpu_thread_second * nanoseconds_per_construct_ribbon_key / 10**9 - -dollars_per_query_bloom_key = dollars_per_cpu_thread_second * nanoseconds_per_query_bloom_key / 10**9 -dollars_per_query_ribbon_key = dollars_per_cpu_thread_second * nanoseconds_per_query_ribbon_key / 10**9 - -dollars_per_bloom_key_cpu = dollars_per_construct_bloom_key + key_query_per_construct * dollars_per_query_bloom_key -dollars_per_ribbon_key_cpu = dollars_per_construct_ribbon_key + key_query_per_construct * dollars_per_query_ribbon_key - -# on holding filters in RAM -dollars_per_gb_ram_lifetime = upfront_dollars_per_gb_ram + watts_per_gb_ram * kwh_per_watt_lifetime * dollars_per_kwh -dollars_per_gb_ram_second = dollars_per_gb_ram_lifetime / seconds_per_lifetime - -dollars_per_bloom_key_in_ram_second = dollars_per_gb_ram_second / bits_per_gb * typical_bloom_bits_per_key -dollars_per_ribbon_key_in_ram_second = dollars_per_gb_ram_second / bits_per_gb * typical_ribbon_bits_per_key - -#================================== -# How many seconds does it take for the added cost of constructing a ribbon filter instead -# of bloom to be offset by the added cost of holding the bloom filter in memory? -break_even_seconds = (dollars_per_ribbon_key_cpu - dollars_per_bloom_key_cpu) / (dollars_per_bloom_key_in_ram_second - dollars_per_ribbon_key_in_ram_second) -print(break_even_seconds) -# -> 3235.1647730256936 -``` - -So roughly speaking, filters that live in memory for more than an hour -should be Ribbon, and filters that live less than an hour should be -Bloom. This is very interesting, but how long do filters live in -RocksDB? - -First let's consider the average case. Write-heavy RocksDB loads are -often backed by flash storage, which has some specified write -endurance for its intended lifetime. This can be expressed as *device -writes per day* (DWPD), and supported DWPD is typically < 10.0 even -for high end devices (excluding NVRAM). Roughly speaking, the DB would -need to be writing at a rate of 20+ DWPD for data to have an average -lifetime of less than one hour. Thus, unless you are prematurely -burning out your flash or massively under-utilizing available storage, -using the Ribbon filter has the better cost profile *on average*. - -### Predictable lifetime -But we can do even better than optimizing for the average case. LSM -levels give us very strong data lifetime hints. Data in L0 might live -for minutes or a small number of hours. Data in Lmax might live for -days or weeks. So even if Ribbon filters weren't the best choice on -average for a workload, they almost certainly make sense for the -larger, longer-lived levels of the LSM. As of RocksDB 6.24, you can -specify a minimum LSM level for Ribbon filters with -`NewRibbonFilterPolicy`, and earlier levels will use Bloom filters. - -### Resident filter memory -The above analysis assumes that nearly all filters for all live SST -files are resident in memory. This is true if using -`cache_index_and_filter_blocks=0` and `max_open_files=-1` (defaults), -but `cache_index_and_filter_blocks=1` is popular. In that case, -if you use `optimize_filters_for_hits=1` and non-partitioned filters -(a popular MyRocks configuration), it is also likely that nearly all -live filters are in memory. However, if you don't use -`optimize_filters_for_hits` and use partitioned filters, then -cold data (by age or by key range) can lead to only a portion of -filters being resident in memory. In that case, benefit from Ribbon -filter is not as clear, though because Ribbon filters are smaller, -they are more efficient to read into memory. - -RocksDB version 6.21 and later include a rough feature to determine -block cache usage for data blocks, filter blocks, index blocks, etc. -Data like this is periodically dumped to LOG file -(`stats_dump_period_sec`): - -``` -Block cache entry stats(count,size,portion): DataBlock(441761,6.82 GB,75.765%) FilterBlock(3002,1.27 GB,14.1387%) IndexBlock(17777,887.75 MB,9.63267%) Misc(1,0.00 KB,0%) -Block cache LRUCache@0x7fdd08104290#7004432 capacity: 9.00 GB collections: 2573 last_copies: 10 last_secs: 0.143248 secs_since: 0 -``` - -This indicates that at this moment in time, the block cache object -identified by `LRUCache@0x7fdd08104290#7004432` (potentially used -by multiple DBs) uses roughly 14% of its 9GB, about 1.27 GB, on filter -blocks. This same data is available through `DB::GetMapProperty` with -`DB::Properties::kBlockCacheEntryStats`, and (with some effort) can -be compared to total size of all filters (not necessarily in memory) -using `rocksdb.filter.size` from -`DB::Properties::kAggregatedTableProperties`. - -### Sanity checking lifetime -Can we be sure that using filters even makes sense for such long-lived -data? We can apply [the current 5 minute rule for caching SSD data in -RAM](http://renata.borovica-gajic.com/data/adms2017_5minuterule.pdf). A -4KB filter page holds data for roughly 4K keys. If we assume at least -one negative (useful) filter query in its lifetime per added key, it -can satisfy the 5 minute rule with a lifetime of up to about two -weeks. Thus, the lifetime threshold for “no filter” is about 300x -higher than the lifetime threshold for Ribbon filter. - -### What to do with saved memory -The default way to improve overall RocksDB performance with more -available memory is to use more space for caching, which improves -latency, CPU load, read IOs, etc. With -`cache_index_and_filter_blocks=1`, savings in filters will -automatically make room for caching more data blocks in block -cache. With `cache_index_and_filter_blocks=0`, consider increasing -block cache size. - -Using the space savings to lower filter FP rates is also an option, -but there is less evidence for this commonly improving existing -*optimized* configurations. - -## Generic recommendation -If using `NewBloomFilterPolicy(bpk)` for a large persistent DB using -compression, try using `NewRibbonFilterPolicy(bpk)` instead, which -will generate Ribbon filters during compaction and Bloom filters -for flush, both with the same FP rate as the old setting. Once new SST -files are generated under the new policy, this should free up some -memory for more caching without much effect on burst or sustained -write speed. Both kinds of filters can be read under either policy, so -there's always an option to adjust settings or gracefully roll back to -using Bloom filter only (keeping in mind that SST files must be -replaced to see effect of that change). diff --git a/docs/_posts/2022-07-18-per-key-value-checksum.markdown b/docs/_posts/2022-07-18-per-key-value-checksum.markdown deleted file mode 100644 index 6b9ad801c..000000000 --- a/docs/_posts/2022-07-18-per-key-value-checksum.markdown +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: "Per Key-Value Checksum" -layout: post -author: -- cbi42 -- ajkr -category: blog ---- - -## Summary - -Silent data corruptions can severely impact RocksDB users. As a key-value library, RocksDB resides at the bottom of the user space software stack for many diverse applications. Returning wrong query results can cause unpredictable consequences for our users so must be avoided. - -To prevent and detect corruption, RocksDB has several consistency checks [1], especially focusing on the storage layer. For example, SST files contain block checksums that are verified during reads, and each SST file has a full file checksum that can be verified when files are transferred. - -Other sources of corruptions, such as those from faulty CPU/memory or heap corruptions, pose risks for which protections are relatively underdeveloped. Meanwhile, recent work [2] suggests one per thousand machines in our fleet will at some point experience a hardware error that is exposed to an application. Additionally, software bugs can increase the risk of heap corruptions at any time. - -Hardware/heap corruptions are naturally difficult to detect in the application layer since they can compromise any data or control flow. Some factors we take into account when choosing where to add protection are the volume of data, the importance of the data, the CPU instructions that operate on the data, and the duration it resides in memory. One recently added protection, `detect_filter_construct_corruption`, has proven itself useful in preventing corrupt filters from being persisted. We have seen hardware encounter machine-check exceptions a few hours after we detected a corrupt filter. - -The next way we intend to detect hardware and heap corruptions before they cause queries to return wrong results is through developing a new feature: per key-value checksum. This feature will eventually provide optional end-to-end integrity protection for every key-value pair. RocksDB 7.4 offers substantial coverage of the user write and recovery paths with per key-value checksum protection. - -## User API - -For integrity protection during recovery, no change is required. Recovery is always protected. - -For user write protection, RocksDB allows the user to specify per key-value protection through `WriteOptions::protection_bytes_per_key` or pass in `protection_bytes_per_key` to `WriteBatch` constructor when creating a `WriteBatch` directly. Currently, only 0 (default, no protection) and 8 bytes per key are supported. This should be fine for write batches as they do not usually contain a huge number of keys. We are working on supporting more settings as 8 bytes per key might cause considerable memory overhead when the protection is extended to memtable entries. - -## Feature Design - -### Data Structures - -#### Protection info - -For protecting key-value pairs, we chose to use a hashing algorithm, xxh3 [3], for its good efficiency without relying on special hardware. While algorithms like crc32c can guarantee detection of certain patterns of bit flips, xxh3 offers no such guarantees. This is acceptable for us as we do not expect any particular error pattern [4], and even if we did, xxh3 can achieve a collision probability close enough to zero for us by tuning the number of protection bytes per key-value. - -Key-value pairs have multiple representations in RocksDB: in [WriteBatch](https://github.com/facebook/rocksdb/blob/7d0ecab570742c7280628b08ddc03cfd692f484f/db/write_batch.cc#L14-L31), in memtable [entries](https://github.com/facebook/rocksdb/blob/fc51b7f33adcba7ac725ed0e7fe8b8155aaeaee4/db/memtable.cc#L541-L545) and in [data blocks](https://github.com/facebook/rocksdb/blob/fc51b7f33adcba7ac725ed0e7fe8b8155aaeaee4/table/block_based/block_builder.cc#L21-L27). In this post we focus on key-values in write batches and memtable as in-memory data blocks are not yet protected. - -Besides user key and value, RocksDB includes internal metadata in the per key-value checksum calculation. Depending on the representation, internal metadata consists of some combination of sequence number, operation type, and column family ID. Note that since timestamp (when enabled) is part of the user key it is protected as well. - -The protection info consists of the XOR’d result of the xxh3 hash for all the protected components. This allows us to efficiently transform protection info for different representations. See below for an example converting WriteBatch protection info to memtable protection info. - -A risk of using XOR is the possibility of swapping corruptions (e.g., key becomes the value and the value becomes the key). To mitigate this risk, we use an independent seed for hashing each type of component. - -The following two figures illustrate how protection info in WriteBatch and memtable are calculated from a key-value’s components. - -![](/static/images/kv-checksum/ProtInfo-Writebatch.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*Protection info for a key-value in a WriteBatch* -{: style="text-align: center"} - -![](/static/images/kv-checksum/ProtInfo-Memtable.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*Protection info for a key-value in a memtable* -{: style="text-align: center"} - -The next figure illustrates how protection info for a key-value can be transformed to protect that same key-value in a different representation. Note this is done without recalculating the hash for all the key-value’s components. - -![](/static/images/kv-checksum/ProtInfo-Writebatch-to-Memtable.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*Protection info for a key-value in a memtable derived from an existing WriteBatch protection info* -{: style="text-align: center"} - -Above, we see two (small) components are hashed: column family ID and sequence number. When a key-value is inserted from WriteBatch into memtable, it is assigned a sequence number and drops the column family ID since each memtable is associated with one column family. Recall the xxh3 of column family ID was included in the WriteBatch protection info, which is canceled out by the column family ID xxh3 included in the XOR. - -#### WAL fragment - -WAL (Write-ahead-log) persists write batches that correspond to operations in memtables and enables consistent database recovery after restart. RocksDB writes to WAL in chunks of some [fixed block size](https://github.com/facebook/rocksdb/blob/fc51b7f33adcba7ac725ed0e7fe8b8155aaeaee4/db/log_writer.h#L44) for efficiency. It is possible that some write batch does not fit into the space left in the current block and/or is larger than the fixed block size. Thus, serialized write batches (WAL records) are divided into WAL fragments before being written to WAL. The format of a WAL fragment is in the following diagram (there is another legacy format detailed in code [comments](https://github.com/facebook/rocksdb/blob/fc51b7f33adcba7ac725ed0e7fe8b8155aaeaee4/db/log_writer.h#L47-L59)). Roughly, the `Type` field indicates whether a fragment is at the beginning, middle or end of a record, and is used to group fragments. - -![](/static/images/kv-checksum/WAL-fragment.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -Note that each fragment is prefixed by a crc32c checksum that is calculated over `Type`, `Log #` and `Payload`. This ensures that RocksDB can detect corruptions that happened to the WAL in the storage layer. - -#### Write batch - -As mentioned above, a WAL record is a serialized `WriteBatch` that is split into physical fragments during writes to WAL. During DB recovery, once a WAL record is reconstructed from one or more fragments, it is [copied](https://github.com/facebook/rocksdb/blob/fc51b7f33adcba7ac725ed0e7fe8b8155aaeaee4/db/db_impl/db_impl_open.cc#L1127) into the content of a `WriteBatch`. The write batch will then be used to restore the memtable states. - -Besides the recovery path, a write batch is always constructed during user writes. Firstly, RocksDB allows users to construct a write batch directly, and pass it to DB through `DB::Write()` API for execution. Higher-level buffered write APIs like Transaction rely on a write batch to buffer writes prior to executing them. For unbuffered write APIs like `DB::Put()`, RocksDB constructs a write batch internally with the input user key and value. - -![](/static/images/kv-checksum/Write-batch.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -The above diagram shows a rough representation of a write batch in memory. `Contents` is the concatenation of serialized user operations in this write batch. Each operation consists of user key, value, op_type and optionally column family ID. With per key-value checksum protection enabled, a vector of ProtectionInfo is stored in the write batch, one for each user operation. - -#### Memtable entry - -![](/static/images/kv-checksum/Memtable-entry.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -A memtable entry is similar to write batch content, except that it captures only a single user operation and that it does not contain column family ID (since memtable is per column family). User key and value are length-prefixed, and seqno and optype are combined in a fixed 8 bytes representation. - -### Processes - -In order to protect user writes and recovery, per key-value checksum is covered in the following code paths. - -#### WriteBatch write - -Per key-value checksum coverage starts with the user buffers that contain user key and/or value. When users call DB Write APIs (e.g., `DB::Put()`), or when users add operations into write batches directly (e.g. `WriteBatch::Put()`), RocksDB constructs `ProtectionInfo` from the user buffer (e.g. [here](https://github.com/facebook/rocksdb/blob/96206531bc0bb56d87012921c5458c8a3047a6b3/db/write_batch.cc#L813)) and [stores](https://github.com/facebook/rocksdb/blob/96206531bc0bb56d87012921c5458c8a3047a6b3/include/rocksdb/write_batch.h#L478) the protection information within the corresponding `WriteBatch` object as diagramed below. Then the user key and/or value are copied into the `WriteBatch`, thus starting per key-value checksum protection from user buffer. - -![](/static/images/kv-checksum/Writebatch-write.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - - -#### WAL write - -Before a `WriteBatch` leaves RocksDB and be persisted in a WAL file, it is verified against its `ProtectionInfo` to ensure its content is not corrupted. We added `WriteBatch::VerifyChecksum()` for this purpose. Once we verify the content of a `WriteBatch`, it is then divided into potentially multiple WAL fragments and persisted in the underlying file system. From that point on, the integrity protection is handed off to the per fragment crc32c checksum that is persisted in WAL too. - -![](/static/images/kv-checksum/WAL-write.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -#### Memtable write - -Similar to the WAL write path, `ProtectionInfo` is verified before an entry is inserted into a memtable. The difference here is that an memtable entry has its own buffer, and the content of a `WriteBatch` is copied into the memtable entry. So the `ProtectionInfo` is verified against the memtable entry buffer instead. The current per key-value checksum protection ends at this verification on the buffer containing a memtable entry, and one of the future work is to extend the coverage to key-value pairs in memtables. - -![](/static/images/kv-checksum/Memtable-write.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -#### WAL read - -This is for the DB recovery path: WAL fragments are read into memory, concatenated together to form WAL records, and then `WriteBatch`es are constructed from WAL records and added to memtables. In RocksDB 7.4, once a `WriteBatch` copies its content from a WAL record, `ProtectionInfo` is constructed from the `WriteBatch` content and per key-value protection starts. However, this copy operation is not protected, neither is the reconstruction of a WAL record from WAL fragments. To provide protection from silent data corruption during these memory copying operations, we added checksum handshake detailed below in RocksDB 7.5. - -When a WAL fragment is first read into memory, its crc32c checksum is [verified](https://github.com/facebook/rocksdb/blob/2f13f5f7d09c589d5adebf0cbc42fadf0da0f00e/db/log_reader.cc#L483). The WAL fragment is then appended to the buffer containing a WAL record. RocksDB uses xxh3’s streaming API to calculate the checksum of the WAL record and updates the streaming hash state with the new WAL fragment content whenever it is appended to the WAL record buffer (e.g. [here](https://github.com/facebook/rocksdb/blob/2f13f5f7d09c589d5adebf0cbc42fadf0da0f00e/db/log_reader.cc#L135)). After the WAL record is constructed, it is copied into a `WriteBatch` and `ProtectionInfo` is constructed from the write batch content. Then, the xxh3 checksum of the WAL record is [verified](https://github.com/facebook/rocksdb/blob/2f13f5f7d09c589d5adebf0cbc42fadf0da0f00e/db/write_batch.cc#L3081-L3085) against the write batch content to complete the checksum handshake. If the checksum verification succeeds, then we are more confident that `ProtectionInfo` is calculated based on uncorrupted data, and the protection coverage continues with the newly constructed `ProtectionInfo` along the write code paths mentioned above. - -![](/static/images/kv-checksum/WAL-read.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -## Future work - -Future coverage expansion will cover memtable KVs, flush, compaction and user reads etc. - -## References - -[1] http://rocksdb.org/blog/2021/05/26/online-validation.html - -[2] H. D. Dixit, L. Boyle, G. Vunnam, S. Pendharkar, M. Beadon, and S. Sankar, ‘Detecting silent data corruptions in the wild’. arXiv, 2022. - -[3] https://github.com/Cyan4973/xxHash - -[4] https://github.com/Cyan4973/xxHash/issues/229#issuecomment-511956403 diff --git a/docs/_posts/2022-10-05-lost-buffered-write-recovery.markdown b/docs/_posts/2022-10-05-lost-buffered-write-recovery.markdown deleted file mode 100644 index fca3ea739..000000000 --- a/docs/_posts/2022-10-05-lost-buffered-write-recovery.markdown +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: "Verifying crash-recovery with lost buffered writes" -layout: post -author: -- ajkr -category: blog ---- - -## Introduction - -Writes to a RocksDB instance go through multiple layers before they are fully persisted. -Those layers may buffer writes, delaying their persistence. -Depending on the layer, buffered writes may be lost in a process or system crash. -A process crash loses writes buffered in process memory only. -A system crash additionally loses writes buffered in OS memory. - -The new test coverage introduced in this post verifies there is no hole in the recovered data in either type of crash. -A hole would exist if any recovered write were newer than any lost write, as illustrated below. -This guarantee is important for many applications, such as those that use the newest recovered write to determine the starting point for replication. - -![](/static/images/lost-buffered-write-recovery/happy-cat.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*Valid (no hole) recovery: all recovered writes (1 and 2) are older than all lost writes (3 and 4)* -{: style="text-align: center"} - -![](/static/images/lost-buffered-write-recovery/angry-cat.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -*Invalid (hole) recovery: a recovered write (4) is newer than a lost write (3)* -{: style="text-align: center"} - -The new test coverage assumes all writes use the same options related to buffering/persistence. -For example, we do not cover the case of alternating writes with WAL disabled and WAL enabled (`WriteOptions::disableWAL`). -It also assumes the crash does not have any unexpected consequences like corrupting persisted data. - -Testing for holes in the recovery is challenging because there are many valid recovery outcomes. -Our solution involves tracing all the writes and then verifying the recovery matches a prefix of the trace. -This proves there are no holes in the recovery. -See "Extensions for lost buffered writes" subsection below for more details. - -Testing actual system crashes would be operationally difficult. -Our solution simulates system crash by buffering written but unsynced data in process memory such that it is lost in a process crash. -See "Simulating system crash" subsection below for more details. - -## Scenarios covered - -We began testing recovery has no hole in the following new scenarios. -This coverage is included in our internal CI that periodically runs against the latest commit on the main branch. - -1. **Process crash with WAL disabled** (`WriteOptions::disableWAL=1`), which loses writes since the last memtable flush. -2. **System crash with WAL enabled** (`WriteOptions::disableWAL=0`), which loses writes since the last memtable flush or WAL sync (`WriteOptions::sync=1`, `SyncWAL()`, or `FlushWAL(true /* sync */)`). -3. **Process crash with manual WAL flush** (`DBOptions::manual_wal_flush=1`), which loses writes since the last memtable flush or manual WAL flush (`FlushWAL()`). -4. **System crash with manual WAL flush** (`DBOptions::manual_wal_flush=1`), which loses writes since the last memtable flush or synced manual WAL flush (`FlushWAL(true /* sync */)`, or `FlushWAL(false /* sync */)` followed by WAL sync). - -## Issues found - -* [False detection of corruption after system crash due to race condition with WAL sync and `track_and_verify_wals_in_manifest](https://github.com/facebook/rocksdb/pull/10185) -* [Undetected hole in recovery after system crash due to race condition in WAL sync](https://github.com/facebook/rocksdb/pull/10560) -* [Recovery failure after system crash due to missing directory sync for critical metadata file](https://github.com/facebook/rocksdb/pull/10573) - -## Solution details - -### Basic setup - -![](/static/images/lost-buffered-write-recovery/basic-setup.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -Our correctness testing framework consists of a stress test program (`db_stress`) and a wrapper script (`db_crashtest.py`). -`db_crashtest.py` manages instances of `db_stress`, starting them and injecting crashes. -`db_stress` operates a DB and test oracle ("Latest values file"). - -At startup, `db_stress` verifies the DB using the test oracle, skipping keys that had pending writes when the last crash happened. -`db_stress` then stresses the DB with random operations, keeping the test oracle up-to-date. - -As the name "Latest values file" implies, this test oracle only tracks the latest value for each key. -As a result, this setup is unable to verify recoveries involving lost buffered writes, where recovering older values is tolerated as long as there is no hole. - -### Extensions for lost buffered writes - -To accommodate lost buffered writes, we extended the test oracle to include two new files: "`verifiedSeqno`.state" and "`verifiedSeqno`.trace". -`verifiedSeqno` is the sequence number of the last successful verification. -"`verifiedSeqno`.state" is the expected values file at that sequence number, and "`verifiedSeqno`.trace" is the trace file of all operations that happened after that sequence number. - -![](/static/images/lost-buffered-write-recovery/replay-extension.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -When buffered writes may have been lost by the previous `db_stress` instance, the current `db_stress` instance must reconstruct the latest values file before startup verification. -M is the recovery sequence number of the current `db_stress` instance and N is the recovery sequence number of the previous `db_stress` instance. -M is learned from the DB, while N is learned from the filesystem by parsing the "*.{trace,state}" filenames. -Then, the latest values file ("LATEST.state") can be reconstructed by replaying the first M-N traced operations (in "N.trace") on top of the last instance's starting point ("N.state"). - -![](/static/images/lost-buffered-write-recovery/trace-extension.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -When buffered writes may be lost by the current `db_stress` instance, we save the current expected values into "M.state" and begin tracing newer operations in "M.trace". - -### Simulating system crash - -When simulating system crash, we send file writes to a `TestFSWritableFile`, which buffers unsynced writes in process memory. -That way, the existing `db_stress` process crash mechanism will lose unsynced writes. - -![](/static/images/lost-buffered-write-recovery/test-fs-writable-file.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -`TestFSWritableFile` is implemented as follows. - -* `Append()` buffers the write in a local `std::string` rather than calling `write()`. -* `Sync()` transfers the local `std::string`s content to `PosixWritableFile::Append()`, which will then `write()` it to the OS page cache. - -## Next steps -An untested guarantee is that RocksDB recovers all writes that the user explicitly flushed out of the buffers lost in the crash. -We may recover more writes than these due to internal flushing of buffers, but never less. -Our test oracle needs to be further extended to track the lower bound on the sequence number that is expected to survive a crash. - -We would also like to make our system crash simulation more realistic. -Currently we only drop unsynced regular file data, but we should drop unsynced directory entries as well. - -## Acknowledgements - -Hui Xiao added the manual WAL flush coverage and compatibility with `TransactionDB`. -Zhichao Cao added the system crash simulation. -Several RocksDB team members contributed to this feature's dependencies. diff --git a/docs/_posts/2022-10-07-asynchronous-io-in-rocksdb.markdown b/docs/_posts/2022-10-07-asynchronous-io-in-rocksdb.markdown deleted file mode 100644 index 0586f1c3d..000000000 --- a/docs/_posts/2022-10-07-asynchronous-io-in-rocksdb.markdown +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Asynchronous IO in RocksDB -layout: post -author: -- akankshamahajan15 -- anand1976 -category: blog ---- -## Summary - -RocksDB provides several APIs to read KV pairs from a database, including Get and MultiGet for point lookups and Iterator for sequential scanning. These APIs may result in RocksDB reading blocks from SST files on disk storage. The types of blocks and the frequency with which they are read from storage is workload dependent. Some workloads may have a small working set and thus may be able to cache most of the data required, while others may have large working sets and have to read from disk more often. In the latter case, the latency would be much higher and throughput would be lower than the former. They would also be dependent on the characteristics of the underlying storage media, making it difficult to migrate from one medium to another, for example, local flash to disaggregated flash. - -One way to mitigate the impact of storage latency is to read asynchronously and in parallel as much as possible, in order to hide IO latency. We have implemented this in RocksDB in Iterators and MultiGet. In Iterators, we prefetch data asynchronously in the background for each file being iterated on, unlike the current implementation that does prefetching synchronously, thus blocking the iterator thread. In MultiGet, we determine the set of files that a given batch of keys overlaps, and read the necessary data blocks from those files in parallel using an asynchronous file system API. These optimizations have significantly decreased the overall latency of the RocksDB MultiGet and iteration APIs on slower storage compared to local flash. - -The optimizations described here are in the internal implementation of Iterator and MultiGet in RocksDB. The user API is still synchronous, so existing code can easily benefit from it. We might consider async user APIs in the future. - - -## Design - -### API - -A new flag in `ReadOptions`, `async_io`, controls the usage of async IO. This flag, when set, enables async IO in Iterators and MultiGet. For MultiGet, an additional `ReadOptions` flag, `optimize_multiget_for_io` (defaults to true), controls how aggressively to use async IO. If the flag is not set, files in the same level are read in parallel but not different levels. If the flag is set, the level restriction is removed and as many files as possible are read in parallel, regardless of level. The latter might have a higher CPU cost depending on the workload. - -At the FileSystem layer, we use the `FSRandomAccessFile::ReadAsync` API to start an async read, providing a completion callback. - -### Scan - -A RocksDB scan usually involves the allocation of a new iterator, followed by a Seek call with a target key to position the iterator, followed by multiple Next calls to iterate through the keys sequentially. Both the Seek and Next operations present opportunities to read asynchronously, thereby reducing the scan latency. - -A scan usually involves iterating through keys in multiple entities - the active memtable, sealed and unflushed memtables, every L0 file, and every non-empty non-zero level. The first two are completely in memory and thus not impacted by IO latency. The latter two involve reading from SST files. This means that an increase in IO latency has a multiplier effect, since multiple L0 files and levels have to be iterated on. - -Some factors, such as block cache and prefix bloom filters, can reduce the number of files to iterate and number of reads from the files. Nevertheless, even a few reads from disk can dominate the overall latency. RocksDB uses async IO in both Seek and Next to mitigate the latency impact, as described below. - - -#### Seek - -A RocksDB iterator maintains a collection of child iterators, one for each L0 file and for each non-empty non-zero levels. For a Seek operation every child iterator has to Seek to the target key. This is normally done serially, by doing synchronous reads from SST files when the required data blocks are not in cache. When the async_io option is enabled, RocksDB performs the Seek in 2 phases - 1) Locate the data block required for Seek in each file/level and issue an async read, and 2) in the second phase, reseek with the same key, which will wait for the async read to finish at each level and position the table iterator. Phase 1 reads multiple blocks in parallel, reducing overall Seek latency. - - -#### Next - -For the iterator Next operation, RocksDB tries to reduce the latency due to IO by prefetching data from the file. This prefetching occurs when a data block required by Next is not present in the cache. The reads from file and prefetching is managed by the FilePrefetchBuffer, which is an object that’s created per table iterator (BlockBasedTableIterator). The FilePrefetchBuffer reads the required data block, and an additional amount of data that varies depending on the options provided by the user in ReadOptions and BlockBasedTableOptions. The default behavior is to start prefetching on the third read from a file, with an initial prefetch size of 8KB and doubling it on every subsequent read, upto a max of 256KB. - -While the prefetching in the previous paragraph helps, it is still synchronous and contributes to the iterator latency. When the async_io option is enabled, RocksDB prefetches in the background, i.e while the iterator is scanning KV pairs. This is accomplished in FilePrefetchBuffer by maintaining two prefetch buffers. The prefetch size is calculated as usual, but its then split across the two buffers. As the iteration proceeds and data in the first buffer is consumed, the buffer is cleared and an async read is scheduled to prefetch additional data. This read continues in the background while the iterator continues to process data in the second buffer. At this point, the roles of the two buffers are reversed. This does not completely hide the IO latency, since the iterator would have to wait for an async read to complete after the data in memory has been consumed. However, it does hide some of it by overlapping CPU and IO, and async prefetch can be happening on multiple levels in parallel, further reducing the latency. - -![Scan flow](/static/images/asynchronous-io/scan_async.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -### MultiGet - -The MultiGet API accepts a batch of keys as input. Its a more efficient way of looking up multiple keys compared to a loop of Gets. One way MultiGet is more efficient is by reading multiple data blocks from an SST file in a batch, for keys in the same file. This greatly reduces the latency of the request, compared to a loop of Gets. The MultiRead FileSystem API is used to read a batch of data blocks. - -![MultiGet flow](/static/images/asynchronous-io/mget_async.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -Even with the MultiRead optimization, subset of keys that are in different files still need to be read serially. We can take this one step further and read multiple files in parallel. In order to do this, a few fundamental changes were required in the MultiGet implementation - - -1. Coroutines - A MultiGet involves determining the set of keys in a batch that overlap an SST file, and then calling TableReader::MultiGet to do the actual lookup. The TableReader probes the bloom filter, traverses the index block, looks up the block cache for the necessary, reads the missing data blocks from the SST file, and then searches for the keys in the data blocks. There is a significant amount of context that’s accumulated at each stage, and it would be rather complex to interleave data blocks reads by multiple TableReaders. In order to simplify it, we used async IO with C++ coroutines. The TableReader::MultiGet is implemented as a coroutine, and the coroutine is suspended after issuing async reads for missing data blocks. This allows the top-level MultiGet to iterate through the TableReaders for all the keys, before waiting for the reads to finish and resuming the coroutines. -2. Filtering - The downside of using coroutines is the CPU overhead, which is non-trivial. To minimize the overhead, its desirable to not use coroutines as much as possible. One scenario in which we can completely avoid the call to a TableReader::MultiGet coroutine is if we know that none of the overlapping keys are actually present in the SST file. This can easily determined by probing the bloom filter. In the previous implementation, the bloom filter lookup was embedded in TableReader::MultiGet. However, we could easily implement is as a separate step, before calling TableReader::MultiGet. -3. Splitting batches - The default strategy of MultiGet is to lookup keys in one level (or L0 file), before moving on to the next. This limits the amount of IO parallelism we can exploit. For example, the keys in a batch may not be clustered together, and may be scattered over multiple files. Even if they are clustered together in the key space, they may not all be in the same level. In order to optimize for these situations, we determine the subset of keys that are likely to be in a given level, and then split the MultiGet batch into 2 - the subset in that level, and the remainder. The batch containing the remainder can then be processed in parallel. The subset of keys likely to be in a level is determined by the filtering step. - -Together, these changes enabled two types of latency optimization in MultiGet using async IO - single-level and multi-level. The former reads data blocks in parallel from multiple files in the same LSM level, while the latter reads in parallel from multiple files in multiple levels. - -## Results - -Command used to generate the database: - -`buck-out/opt/gen/rocks/tools/rocks_db_bench —db=/rocks_db_team/prefix_scan —env_uri=ws://ws.flash.ftw3preprod1 -logtostderr=false -benchmarks="fillseqdeterministic" -key_size=32 -value_size=512 -num=5000000 -num_levels=4 -multiread_batched=true -use_direct_reads=false -adaptive_readahead=true -threads=1 -cache_size=10485760000 -async_io=false -multiread_stride=40000 -disable_auto_compactions=true -compaction_style=1 -bloom_bits=10` - -Structure of the database: - -`Level[0]: /000233.sst(size: 24828520 bytes)` -`Level[0]: /000232.sst(size: 49874113 bytes)` -`Level[0]: /000231.sst(size: 100243447 bytes)` -`Level[0]: /000230.sst(size: 201507232 bytes)` -`Level[1]: /000224.sst - /000229.sst(total size: 405046844 bytes)` -`Level[2]: /000211.sst - /000223.sst(total size: 814190051 bytes)` -`Level[3]: /000188.sst - /000210.sst(total size: 1515327216 bytes)` - - -### MultiGet - -MultiGet benchmark command: - -`buck-out/opt/gen/rocks/tools/rocks_db_bench -use_existing_db=true —db=/rocks_db_team/prefix_scan -benchmarks="multireadrandom" -key_size=32 -value_size=512 -num=5000000 -batch_size=8 -multiread_batched=true -use_direct_reads=false -duration=60 -ops_between_duration_checks=1 -readonly=true -threads=4 -cache_size=300000000 -async_io=true -multiread_stride=40000 -statistics —env_uri=ws://ws.flash.ftw3preprod1 -logtostderr=false -adaptive_readahead=true -bloom_bits=10` - -#### Single-file - -The default MultiGet implementation of reading from one file at a time had a latency of 1292 micros/op. - -`multireadrandom : 1291.992 micros/op 3095 ops/sec 60.007 seconds 185768 operations; 1.6 MB/s (46768 of 46768 found) ` -`rocksdb.db.multiget.micros P50 : 9664.419795 P95 : 20757.097056 P99 : 29329.444444 P100 : 46162.000000 COUNT : 23221 SUM : 239839394` - -#### Single-level - -MultiGet with async_io=true and optimize_multiget_for_io=false had a latency of 775 micros/op. - -`multireadrandom : 774.587 micros/op 5163 ops/sec 60.009 seconds 309864 operations; 2.7 MB/s (77816 of 77816 found)` -`rocksdb.db.multiget.micros P50 : [6029.601964](tel:6029601964) P95 : 10727.467932 P99 : 13986.683940 P100 : 47466.000000 COUNT : 38733 SUM : 239750172` - -#### Multi-level - -With all optimizations turned on, MultiGet had the lowest latency of 508 micros/op. - -`multireadrandom : 507.533 micros/op 7881 ops/sec 60.003 seconds 472896 operations; 4.1 MB/s (117536 of 117536 found)` -`rocksdb.db.multiget.micros P50 : 3923.819467 P95 : 7356.182075 P99 : 10880.728723 P100 : 28511.000000 COUNT : 59112 SUM : 239642721` - -### Scan - -Benchmark command: - -`buck-out/opt/gen/rocks/tools/rocks_db_bench -use_existing_db=true —db=/rocks_db_team/prefix_scan -ben``chmarks="seekrandom" -key_size=32 -value_size=512 -num=5000000 -batch_size=8 -multiread_batched=true -use_direct_reads=false -duration=60 -ops_between_duration_che``cks=1 -readonly=true -threads=4 -cache_size=300000000 -async_io=true -multiread_stride=40000 -statistics —env_uri=ws://ws.flash.ftw3preprod1 -logtostderr=false -a``daptive_readahead=true -bloom_bits=10 -seek_nexts=65536` - -### With async scan - -`seekrandom : 414442.303 micros/op 9 ops/sec 60.288 seconds 581 operations; 326.2 MB/s (145 of 145 found)` - -### Without async scan - -`seekrandom : 848858.669 micros/op 4 ops/sec 60.529 seconds 284 operations; 158.1 MB/s (74 of 74 found)` - -## Known Limitations - -These optimizations apply only to block based table SSTs. File system support for the `ReadAsync` and `Poll` interfaces is required. Currently, it is available only for `PosixFileSystem`. - -The MultiGet async IO optimization has a few additional limitations - - -1. Depends on folly, which introduces a few additional build steps -2. Higher CPU overhead due to coroutines. The CPU overhead of MultiGet may increase 6-15%, with the worst case being a single threaded MultiGet batch of keys with 1 key/file intersection and 100% cache hit rate. A more realistic case of multiple threads with a few keys (~4) overlap per file should see ~6% higher CPU util. -3. No parallelization of metadata reads. A metadata read will block the thread. -4. A few other cases will also be in serial, such as additional block reads for merge operands. - - diff --git a/docs/_posts/2022-10-31-align-compaction-output-file.markdown b/docs/_posts/2022-10-31-align-compaction-output-file.markdown deleted file mode 100644 index a2db41bc3..000000000 --- a/docs/_posts/2022-10-31-align-compaction-output-file.markdown +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Reduce Write Amplification by Aligning Compaction Output File Boundaries -layout: post -author: -- zjay -category: blog ---- -## TL;DR -By cutting the compaction output file earlier and allowing larger than targeted_file_size to align the compaction output files to the next level files, it can **reduce WA (Write Amplification) by more than 10%**. The feature is **enabled by default** after the user upgrades RocksDB to version `7.8.0+`. - -## Background -RocksDB level compaction picks one file from the source level and compacts to the next level, which is a typical partial merge compaction algorithm. Compared to the full merge compaction strategy for example [universal compaction](https://github.com/facebook/rocksdb/wiki/Universal-Compaction), it has the benefits of smaller compaction size, better parallelism, etc. But it also has a larger write amplification (typically 20-30 times user data). One of the problems is wasted compaction at the beginning and ending: - -![](/static/images/align-compaction-output/file_cut_normal.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -In the diagram above, `SST11` is selected for the compaction, it overlaps with `SST20` to `SST23`, so all these files are selected for compaction. But the beginning and ending of the SST on Level 2 are wasted, which also means it will be compacted again when `SST10` is compacting down. If the file boundaries are aligned, then the wasted compaction size could be reduced. On average, the wasted compaction is `1` file size: `0.5` at the beginning, and `0.5` at the end. Typically the average compaction fan-out is about 6 (with the default max_bytes_for_level_multiplier = 10), then `1 / (6 + 1) ~= 14%` of compaction is wasted. -## implementation -To reduce such wasted compaction, RocksDB now tries to align the compaction output file to the next level's file. So future compactions will have fewer wasted compaction. For example, the above case might be cut like this: - -![](/static/images/align-compaction-output/file_cut_align.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -The trade-off is the file won't be cut exactly after it exceeds target_file_size_base, instead, it will be more likely cut when it's aligned with the next level file's boundary, so the file size might be more varied. It could be as small as 50% of `target_file_size` or as large as `2x target_file_size`. It will only impact non-bottommost-level files, which should be only `~11%` of the data. -Internally, RocksDB tries to cut the file so its size is close to the `target_file_size` setting but also aligned with the next level boundary. When the compaction output file hit a next-level file boundary, either the beginning or ending boundary, it will cut if: -``` -current_size > ((5 * min(bounderies_num, 8) + 50) / 100) * target_file_size -``` -([details](https://github.com/facebook/rocksdb/blob/23fa5b7789d6acd0c211d6bdd41448bbf1513bb6/db/compaction/compaction_outputs.cc#L270-L290)) - -The file size is also capped at `2x target_file_size`: [details](https://github.com/facebook/rocksdb/blob/f726d29a8268ae4e2ffeec09172383cff2ab4db9/db/compaction/compaction.cc#L273-L277). -Another benefit of cutting the file earlier is having more trivial move compaction, which is moving the file from a high level to a low level without compacting anything. Based on a compaction simulator test, the trivial move data is increased by 30% (but still less than 1% compaction data is trivial move): - -![](/static/images/align-compaction-output/file_cut_trival_move.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -Based on the db_bench test, it can save `~12%` compaction load, here is the test command and result: -``` -TEST_TMPDIR=/data/dbbench ./db_bench --benchmarks=fillrandom,readrandom -max_background_jobs=12 -num=400000000 -target_file_size_base=33554432 - -# baseline: -Flush(GB): cumulative 25.882, interval 7.216 -Cumulative compaction: 285.90 GB write, 162.36 MB/s write, 269.68 GB read, 153.15 MB/s read, 2926.7 seconds - -# with this change: -Flush(GB): cumulative 25.882, interval 7.753 -Cumulative compaction: 249.97 GB write, 141.96 MB/s write, 233.74 GB read, 132.74 MB/s read, 2534.9 seconds -``` - -The feature is enabled by default by upgrading to RocksDB 7.8 or later versions, as the feature should have a limited impact on the file size and have great write amplification improvements. If in a rare case, it needs to opt out, set -``` -options.level_compaction_dynamic_file_size = false; -``` - -## Other Options and Benchmark -We also tested a few other options, starting with a fixed threshold: 75% of the target_file_size and 50%. Then with a dynamic threshold that is explained, but still limiting file size smaller than the target_file_size. -1. Baseline (main branch before [PR#10655](https://github.com/facebook/rocksdb/pull/10655)); -2. Fixed Threshold `75%`: after 75% of target file size, cut the file whenever it aligns with a low level file boundary; -3. Fixed Threshold `50%`: reduce the threshold to 50% of target file size; -4. Dynamic Threshold `(5*bounderies_num + 50)` percent of target file size and maxed at 90%; -5. Dynamic Threshold + allow 2x the target file size (chosen option). - -### Test Environment and Data -To speed up the benchmark, we introduced a compaction simulator within Rocksdb ([details](https://github.com/jay-zhuang/rocksdb/tree/compaction_sim)), which replaced the physical SST with in-memory data (a large bitset). Which can test compaction more consistently. As it's a simulator, it has its limitations: - -it assumes each key-value has the same size; -1. no deletion (but has override); -2. doesn't consider data compression; -3. single-threaded and finish all compactions before the next flush (so no write stall). - -We use 3 kinds of the dataset for tests: -1. Random Data, has an override, evenly distributed; -2. Zipf distribution with alpha = 1.01, moderately skewed; -3. Zipf distribution with alpha = 1.2, highly skewed. - -#### Write Amplification - -![](/static/images/align-compaction-output/write_amp_compare.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 100%"} - -As we can see, all options are better than the baseline. Option5 (brown) and option3 (green) have similar WA improvements. (The sudden WA drop during ~40G Random Dataset is because we enabled `level_compaction_dynamic_level_bytes` and the level number was increased from 3 to 4, the similar test result without enabling `level_compaction_dynamic_level_bytes`). - -#### File Size Distribution at the End of Test -This is the file size distribution at the end of the test, which loads about 100G data. As this change only impacts the non-bottommost file size, and the majority of the SST files are bottommost, there're no significant differences: - -![](/static/images/align-compaction-output/file_size_compare.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 100%"} - -#### All Compaction Generated File Sizes -The high-level files are much more likely to be compacted, so all compaction-generated files size has more significant change: - -![](/static/images/align-compaction-output/compaction_output_file_size_compare.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 100%"} - -Overall option5 has most of the file size close to the target file size. vs. option3 has a much smaller size. Here are more detailed stats for compaction output file size: -``` - base 50p 75p dynamic 2xdynamic -count 1.656000e+03 1.960000e+03 1.770000e+03 1.687000e+03 1.705000e+03 -mean 3.116062e+07 2.634125e+07 2.917876e+07 3.060135e+07 3.028076e+07 -std 7.145242e+06 1.065134e+07 8.800474e+06 7.612939e+06 8.046139e+06 -``` - -## Summary -Allowing more dynamic file size and aligning the compaction output file to the next level file's boundary improves the RocksDB write amplification by more than 10%, which will be enabled by default in `7.8.0` release. We picked a simple algorithm to decide when to cut the output file, which can be further improved. For example, by estimating output file size with index information. Any suggestions or PR are welcomed. - -## Acknowledgements -We thank Siying Dong for initializing the file-cutting idea and thank Andrew Kryczka, Mark Callaghan for contributing to the ideas. And Changyu Bi for the detailed code review. diff --git a/docs/_posts/2022-11-09-time-aware-tiered-storage.markdown b/docs/_posts/2022-11-09-time-aware-tiered-storage.markdown deleted file mode 100644 index 03a6b02ef..000000000 --- a/docs/_posts/2022-11-09-time-aware-tiered-storage.markdown +++ /dev/null @@ -1,121 +0,0 @@ ---- -title: Time-Aware Tiered Storage in RocksDB -layout: post -author: -- zjay -category: blog ---- -## TL:DR -Tiered storage is now natively supported in the RocksDB with the option [`last_level_temperature`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L910), time-aware Tiered storage feature guarantees the recently written data are put in the hot tier storage with the option [`preclude_last_level_data_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L927). - -## Background -RocksDB Tiered Storage assigns a data temperature when creating the new SST which [hints the file system](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/file_system.h#L162) to put the data on the corresponding storage media, so the data in a single DB instance can be placed on different storage media. Before the feature, the user typically creates multiple DB instances for different storage media, for example, one DB instance stores the recent hot data and migrates the data to another cold DB instance when the data becomes cold. Tracking and migrating the data could be challenging. With the RocksDB tiered storage feature, RocksDB compaction migrates the data from hot storage to cold storage. - -![](/static/images/time-aware-tiered-storage/tiered_storage_overview.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -Currently, RocksDB supports assigning the last level file temperature. In an LSM tree, typically the last level data is most likely the coldest. As the most recent data is on the higher level and gradually compacted to the lower level. The higher level data is more likely to be read, because: -1. RocksDB read always queries from the higher level to the lower level until it finds the data; -2. The high-level data is much more likely to be read and written by the compactions. - -### Problem -Generally in the LSM tree, hotter data is likely on the higher levels as mentioned before, **but it is not always the case**, for example for the skewed dataset, the recent data could be compacted to the last level first. For the universal compaction, a major compaction would compact all data to the last level (the cold tier) which includes both recent data that should be cataloged as hot data. In production, **we found the majority of the compaction load is actually major compaction (more than 80%)**. - -![](/static/images/time-aware-tiered-storage/tiered_storage_problem.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -### Goal and Non-goals -It’s hard to predict the hot and cold data. The most frequently accessed data should be cataloged as hot data. But it is hard to predict which key is going to be accessed most, it is also hard to track the per-key based access history. The time-aware tiered storage feature is only **focusing on the use cases that the more recent data is more likely to be accessed**. Which is the majority of the cases, but not all. - -## User APIs -Here are the 3 main tiered storage options: -```c++ -Temperature last_level_temperature = Temperature::kUnknown; -uint64_t preclude_last_level_data_seconds = 0; -uint64_t preserve_internal_time_seconds = 0; -``` -[`last_level_temperature`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L910) defines the data temperature for the last level SST files, which is typically kCold or kWarm. RocksDB doesn’t check the option value, instead it just passes that to the file_system API with [`FileOptions.temperature`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/file_system.h#L162) when creating the last level SST files. For all the other files, non-last-level SST files, and non-SST files like manifest files, the temperature is set to kUnknown, which typically maps to hot data. -The user can also get each SST’s temperature information through APIs: -```c++ -db.GetLiveFilesStorageInfo(); -db.GetLiveFilesMetaData(); -db.GetColumnFamilyMetaData(); -``` - -### User Metrics -Here are the tiered storage related statistics: -```c++ -HOT_FILE_READ_BYTES, -WARM_FILE_READ_BYTES, -COLD_FILE_READ_BYTES, -HOT_FILE_READ_COUNT, -WARM_FILE_READ_COUNT, -COLD_FILE_READ_COUNT, -// Last level and non-last level statistics -LAST_LEVEL_READ_BYTES, -LAST_LEVEL_READ_COUNT, -NON_LAST_LEVEL_READ_BYTES, -NON_LAST_LEVEL_READ_COUNT, -``` - -And more details from `IOStats`: -```c++ -struct FileIOByTemperature { -// the number of bytes read to Temperature::kHot file -uint64_t hot_file_bytes_read; -// the number of bytes read to Temperature::kWarm file -uint64_t warm_file_bytes_read; -// the number of bytes read to Temperature::kCold file -uint64_t cold_file_bytes_read; -// total number of reads to Temperature::kHot file -uint64_t hot_file_read_count; -// total number of reads to Temperature::kWarm file -uint64_t warm_file_read_count; -// total number of reads to Temperature::kCold file -uint64_t cold_file_read_count; -``` - -## Implementation -There are 2 main components for this feature. One is the **time-tracking**, and another is the **per-key based placement compaction**. These 2 components are relatively independent and linked together during the compaction initialization phase which gets the sequence number for splitting the hot and cold data. The time-tracking components can even be enabled independently by setting the option [`preserve_internal_time_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L950). The purpose of that is before migrating existing user cases to the tiered storage feature and avoid compacting the existing hot data to the cold tier (detailed in the migration session below). - -Unlike the user-defined timestamp feature, the time tracking feature doesn’t have accurate time information for each key. It only samples the time information and gives a rough estimation for the key write time. Here is the high-level graph for the implementation: - -![](/static/images/time-aware-tiered-storage/tiered_storage_design.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -### Time Tracking -Time tracking information is recorded by a [periodic task](https://github.com/facebook/rocksdb/blob/d9e71fb2c53726d9c5ed73b4ec962a7ed6ef15ec/db/periodic_task_scheduler.cc#L36) which gets the latest sequence number and the current time and then stores it in an in-memory data structure. The interval of the periodic task is determined by the user setting [`preserve_internal_time_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L950) and dividing that by 100. For example, if 3 days of data should be precluded from the last level, then the interval of the periodic task is about 0.7 hours (3 * 24 / 100 ~= 0.72), which also means only the latest 100 seq->time pairs needed in memory. - -Currently, the in-memory seq_time_mapping is only used during Flush() and encoded to the SST property. The data is delta encoded and again maximum 100 pairs are stored, so the extra data size is pretty small (far less than 1KB per SST) and only non-last-level SSTs need to have that information. Internally, RocksDB also uses the minimal sequence number and SST creation time from the SST metadata to improve the time accuracy. -**The sequence number to time information is distributed in each SST**, ranging from the min seqno to max seqno for that SST file, so each SST has its self-contained time information. This also means there could be redundancy for the time information, for example, if 2 SSTs have an overlapped sequence number (which is very likely for non-L0 files), the same seq->time pair may exist in both SSTs. -For the future, the time information could also be useful for other potential features like a better estimate of the oldest timestamp for an SST which is critical for the RocksDB TTL feature. - -### Per-Key Placement Compaction - -![](/static/images/time-aware-tiered-storage/per_key_placement_compaction.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -Compare to normal compaction which only outputs the data to a single level, Per-key placement compaction can output data to 2 different levels, as per-per placement compaction is only for the last level compaction, so the 2 output levels would **always be the penultimate level, and the last level**. The compaction places the key to its corresponding tier by simply checking the key’s sequence number. - -At the beginning of the compaction, the compaction job collects all seq to time information from every input SSTs and merges them together, then based on the current time to get the oldest sequence number that should be put into non-last-level (hot tier). During the last level compaction, as long as the key is newer than the oldest_sequence_number, it will be placed in the penultimate level (hot tier) instead of the last level (cold tier). - -Note, RocksDB also places the keys that are within the user snapshot in the hot tier, there’re a few reasons for that: -1. It’s reasonable to assume snapshot-protected data are hot data; -2. Avoid mixing the sequence number not zeroed out data with old last-level data, which is desirable to reduce the oldest obsolete data time (it’s defined as the oldest SST time that has a non-zero sequence number). It also means tombstones are always placed in the hot tier, which is also desirable as it should be pretty small. -3. The original motivation was to avoid moving data from the lower level to a higher level in case the user increases the [`preclude_last_level_data_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L927), so the snapshot-protected data in the last level will become hot again, and moving data to a higher level. It’s not always safe to move data from a lower level to a higher level in the LSM tree which could cause key conflict. Later we added a conflict check to allow the data to move up as long as there’s no key conflict, but then the movement is not guaranteed (see Migration for details) - -### Migration -Once the user enables the feature, it enables both time tracking and per-key placement compaction **at the same time**. As the existing data, it can still be mismarked as cold data. To have a smooth migration to the feature. The user can enable the time-tracking feature first. For example, if the user plans to set [`preclude_last_level_data_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L927) to 3 days, the user can enable time tracking 3 days earlier with [`preserve_internal_time_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L950). Then when enabling the tiered storage feature, it already has the time information for the last 3 days' hot data, then per-key placement compaction won’t compact them to the last level. - -Just preserving the time information won’t prevent the data from compacting to the last level (which should be still on the hot tier). Once the [`preclude_last_level_data_seconds`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L927) and [`last_level_temperature`](https://github.com/facebook/rocksdb/blob/b0d9776b704af01c2b5385e9d53754e0c8176373/include/rocksdb/advanced_options.h#L910) features are enabled, some of the last-level data might need to move up. Currently, RocksDB just does a conflict check, the hot/cold split in this case is not guaranteed. - -![](/static/images/time-aware-tiered-storage/compaction_moving_up_conflict.png) -{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} - -## Summary -Time-aware tired storage feature guarantees the new data is placed in the hot tier, which **is ideal for the tiering use cases where the most recent data is likely the hot data**. It’s done by tracking the write time information and per-key placement compaction to split the hot/cold data. - -The tiered storage feature is actively being developed, any suggestions or PRs will be welcomed. - -## Acknowledgements -We thank Siying Dong and Andrew Kryczka for brainstorming and reviewing the feature design and implementation. And it was my fortune to work with the RocksDB team members! \ No newline at end of file diff --git a/docs/_sass/_base.scss b/docs/_sass/_base.scss deleted file mode 100644 index 6d26d9feb..000000000 --- a/docs/_sass/_base.scss +++ /dev/null @@ -1,492 +0,0 @@ -body { - background: $secondary-bg; - color: $text; - font: normal #{$base-font-size}/#{$base-line-height} $base-font-family; - height: 100vh; - text-align: left; - text-rendering: optimizeLegibility; -} - -img { - max-width: 100%; -} - -article { - p { - img { - max-width: 100%; - display:block; - margin-left: auto; - margin-right: auto; - } - } -} - -a { - border-bottom: 1px dotted $primary-bg; - color: $text; - text-decoration: none; - -webkit-transition: background 0.3s, color 0.3s; - transition: background 0.3s, color 0.3s; -} - -blockquote { - padding: 15px 30px 15px 15px; - margin: 20px 0 0 10px; - background-color: rgba(204, 122, 111, 0.1); - border-left: 10px solid rgba(191, 87, 73, 0.2); -} - -#fb_oss a { - border: 0; -} - -h1, h2, h3, h4 { - font-family: $header-font-family; - font-weight: 900; -} - -.navPusher { - border-top: $header-height + $header-ptop + $header-pbot solid $primary-bg; - height: 100%; - left: 0; - position: relative; - z-index: 99; -} - -.homeContainer { - background: $primary-bg; - color: $primary-overlay; - - a { - color: $primary-overlay; - } - - .homeSplashFade { - color: white; - } - - .homeWrapper { - padding: 2em 10px; - text-align: left; - - .wrapper { - margin: 0px auto; - max-width: $content-width; - padding: 0 20px; - } - - .projectLogo { - img { - height: 100px; - margin-bottom: 0px; - } - } - - h1#project_title { - font-family: $header-font-family; - font-size: 300%; - letter-spacing: -0.08em; - line-height: 1em; - margin-bottom: 80px; - } - - h2#project_tagline { - font-family: $header-font-family; - font-size: 200%; - letter-spacing: -0.04em; - line-height: 1em; - } - } -} - -.wrapper { - margin: 0px auto; - max-width: $content-width; - padding: 0 10px; -} - -.projectLogo { - display: none; - - img { - height: 100px; - margin-bottom: 0px; - } -} - -section#intro { - margin: 40px 0; -} - -.fbossFontLight { - font-family: $base-font-family; - font-weight: 300; - font-style: normal; -} - -.fb-like { - display: block; - margin-bottom: 20px; - width: 100%; -} - -.center { - display: block; - text-align: center; -} - -.mainContainer { - background: $secondary-bg; - overflow: auto; - - .mainWrapper { - padding: 4vh 10px; - text-align: left; - - .allShareBlock { - padding: 10px 0; - - .pluginBlock { - margin: 12px 0; - padding: 0; - } - } - - a { - &:hover, - &:focus { - background: $primary-bg; - color: $primary-overlay; - } - } - - em, i { - font-style: italic; - } - - strong, b { - font-weight: bold; - } - - h1 { - font-size: 300%; - line-height: 1em; - padding: 1.4em 0 1em; - text-align: left; - } - - h2 { - font-size: 250%; - line-height: 1em; - margin-bottom: 20px; - padding: 1.4em 0 20px; - text-align: left; - - & { - border-bottom: 1px solid darken($primary-bg, 10%); - color: darken($primary-bg, 10%); - font-size: 22px; - padding: 10px 0; - } - - &.blockHeader { - border-bottom: 1px solid white; - color: white; - font-size: 22px; - margin-bottom: 20px; - padding: 10px 0; - } - } - - h3 { - font-size: 150%; - line-height: 1.2em; - padding: 1em 0 0.8em; - } - - h4 { - font-size: 130%; - line-height: 1.2em; - padding: 1em 0 0.8em; - } - - p { - padding: 0.8em 0; - } - - ul { - list-style: disc; - } - - ol, ul { - padding-left: 24px; - li { - padding-bottom: 4px; - padding-left: 6px; - } - } - - strong { - font-weight: bold; - } - - .post { - position: relative; - - .katex { - font-weight: 700; - } - - &.basicPost { - margin-top: 30px; - } - - a { - color: $primary-bg; - - &:hover, - &:focus { - color: #fff; - } - } - - h2 { - border-bottom: 4px solid $primary-bg; - font-size: 130%; - } - - h3 { - border-bottom: 1px solid $primary-bg; - font-size: 110%; - } - - ol { - list-style: decimal outside none; - } - - .post-header { - padding: 1em 0; - - h1 { - font-size: 150%; - line-height: 1em; - padding: 0.4em 0 0; - - a { - border: none; - } - } - - .post-meta { - color: $primary-bg; - font-family: $header-font-family; - text-align: center; - } - } - - .postSocialPlugins { - padding-top: 1em; - } - - .docPagination { - background: $primary-bg; - bottom: 0px; - left: 0px; - position: absolute; - right: 0px; - - .pager { - display: inline-block; - width: 50%; - } - - .pagingNext { - float: right; - text-align: right; - } - - a { - border: none; - color: $primary-overlay; - display: block; - padding: 4px 12px; - - &:hover { - background-color: $secondary-bg; - color: $text; - } - - .pagerLabel { - display: inline; - } - - .pagerTitle { - display: none; - } - } - } - } - - .posts { - .post { - margin-bottom: 6vh; - } - } - } -} - -#integrations_title { - font-size: 250%; - margin: 80px 0; -} - -.ytVideo { - height: 0; - overflow: hidden; - padding-bottom: 53.4%; /* 16:9 */ - padding-top: 25px; - position: relative; -} - -.ytVideo iframe, -.ytVideo object, -.ytVideo embed { - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; -} - -@media only screen and (min-width: 480px) { - h1#project_title { - font-size: 500%; - } - - h2#project_tagline { - font-size: 250%; - } - - .projectLogo { - img { - margin-bottom: 10px; - height: 200px; - } - } - - .homeContainer .homeWrapper { - padding-left: 10px; - padding-right: 10px; - } - - .mainContainer { - .mainWrapper { - .post { - h2 { - font-size: 180%; - } - - h3 { - font-size: 120%; - } - - .docPagination { - a { - .pagerLabel { - display: none; - } - .pagerTitle { - display: inline; - } - } - } - } - } - } -} - -@media only screen and (min-width: 900px) { - .homeContainer { - .homeWrapper { - position: relative; - - #inner { - box-sizing: border-box; - max-width: 600px; - padding-right: 40px; - } - - .projectLogo { - align-items: center; - bottom: 0; - display: flex; - justify-content: flex-end; - left: 0; - padding: 2em 20px 4em; - position: absolute; - right: 20px; - top: 0; - - img { - height: 100%; - max-height: 250px; - } - } - } - } -} - -@media only screen and (min-width: 1024px) { - .mainContainer { - .mainWrapper { - .post { - box-sizing: border-box; - display: block; - - .post-header { - h1 { - font-size: 250%; - } - } - } - - .posts { - .post { - margin-bottom: 4vh; - width: 100%; - } - } - } - } -} - -@media only screen and (min-width: 1200px) { - .homeContainer { - .homeWrapper { - #inner { - max-width: 750px; - } - } - } - - .wrapper { - max-width: 1100px; - } -} - -@media only screen and (min-width: 1500px) { - .homeContainer { - .homeWrapper { - #inner { - max-width: 1100px; - padding-bottom: 40px; - padding-top: 40px; - } - } - } - - .wrapper { - max-width: 1400px; - } -} diff --git a/docs/_sass/_blog.scss b/docs/_sass/_blog.scss deleted file mode 100644 index 12a73c1fc..000000000 --- a/docs/_sass/_blog.scss +++ /dev/null @@ -1,47 +0,0 @@ -.blogContainer { - .posts { - margin-top: 60px; - - .post { - border: 1px solid $primary-bg; - border-radius: 3px; - padding: 10px 20px 20px; - } - } - - .lonePost { - margin-top: 60px; - - .post { - padding: 10px 0px 0px; - } - } - - .post-header { - h1 { - text-align: center; - } - - .post-authorName { - color: rgba($text, 0.7); - font-size: 14px; - font-weight: 900; - margin-top: 0; - padding: 0; - text-align: center; - } - - .authorPhoto { - border-radius: 50%; - height: 50px; - left: 50%; - margin-left: auto; - margin-right: auto; - display: inline-block; - overflow: hidden; - position: static; - top: -25px; - width: 50px; - } - } -} diff --git a/docs/_sass/_buttons.scss b/docs/_sass/_buttons.scss deleted file mode 100644 index a0371618f..000000000 --- a/docs/_sass/_buttons.scss +++ /dev/null @@ -1,47 +0,0 @@ -.button { - border: 1px solid $primary-bg; - border-radius: 3px; - color: $primary-bg; - display: inline-block; - font-size: 14px; - font-weight: 900; - line-height: 1.2em; - padding: 10px; - text-transform: uppercase; - transition: background 0.3s, color 0.3s; - - &:hover { - background: $primary-bg; - color: $primary-overlay; - } -} - -.homeContainer { - .button { - border-color: $primary-overlay; - border-width: 1px; - color: $primary-overlay; - - &:hover { - background: $primary-overlay; - color: $primary-bg; - } - } -} - -.blockButton { - display: block; -} - -.edit-page-link { - float: right; - font-size: 14px; - font-weight: normal; - line-height: 20px; - opacity: 0.6; - transition: opacity 0.5s; -} - -.edit-page-link:hover { - opacity: 1; -} diff --git a/docs/_sass/_footer.scss b/docs/_sass/_footer.scss deleted file mode 100644 index 5b7439517..000000000 --- a/docs/_sass/_footer.scss +++ /dev/null @@ -1,82 +0,0 @@ -.footerContainer { - background: $secondary-bg; - color: $primary-bg; - overflow: hidden; - padding: 0 10px; - text-align: left; - - .footerWrapper { - border-top: 1px solid $primary-bg; - padding: 0; - - .footerBlocks { - align-items: center; - align-content: center; - display: flex; - flex-flow: row wrap; - margin: 0 -20px; - padding: 10px 0; - } - - .footerSection { - box-sizing: border-box; - flex: 1 1 25%; - font-size: 14px; - min-width: 275px; - padding: 0px 20px; - - a { - border: 0; - color: inherit; - display: inline-block; - line-height: 1.2em; - } - - .footerLink { - padding-right: 20px; - } - } - - .fbOpenSourceFooter { - align-items: center; - display: flex; - flex-flow: row nowrap; - max-width: 25%; - - .facebookOSSLogoSvg { - flex: 0 0 31px; - height: 30px; - margin-right: 10px; - width: 31px; - - path { - fill: $primary-bg; - } - - .middleRing { - opacity: 0.7; - } - - .innerRing { - opacity: 0.45; - } - } - - h2 { - display: block; - font-weight: 900; - line-height: 1em; - } - } - } -} - -@media only screen and (min-width: 900px) { - .footerSection { - &.rightAlign { - margin-left: auto; - max-width: 25%; - text-align: right; - } - } -} \ No newline at end of file diff --git a/docs/_sass/_gridBlock.scss b/docs/_sass/_gridBlock.scss deleted file mode 100644 index 679b31c14..000000000 --- a/docs/_sass/_gridBlock.scss +++ /dev/null @@ -1,115 +0,0 @@ -.gridBlock { - margin: -5px 0; - padding: 0; - padding-bottom: 20px; - - .blockElement { - padding: 5px 0; - - img { - max-width: 100%; - } - - h3 { - border-bottom: 1px solid rgba($primary-bg, 0.5); - color: $primary-bg; - font-size: 18px; - margin: 0; - padding: 10px 0; - } - } - - .gridClear { - clear: both; - } - -} - -.gridBlock .alignCenter { - text-align: center; -} -.gridBlock .alignRight { - text-align: right; -} -.gridBlock .imageAlignSide { - align-items: center; - display: flex; - flex-flow: row wrap; -} -.blockImage { - max-width: 150px; - width: 50%; -} -.imageAlignTop .blockImage { - margin-bottom: 20px; -} -.imageAlignTop.alignCenter .blockImage { - margin-left: auto; - margin-right: auto; -} -.imageAlignSide .blockImage { - flex: 0 1 100px; - margin-right: 20px; -} -.imageAlignSide .blockContent { - flex: 1 1; -} - -@media only screen and (max-width: 1023px) { - .responsiveList .blockContent { - position: relative; - } - .responsiveList .blockContent > div { - padding-left: 20px; - } - .responsiveList .blockContent::before { - content: "\2022"; - position: absolute; - } -} - -@media only screen and (min-width: 1024px) { - .gridBlock { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: -10px -10px 10px -10px; - - .twoByGridBlock { - box-sizing: border-box; - flex: 1 0 50%; - padding: 10px; - } - - .fourByGridBlock { - box-sizing: border-box; - flex: 1 0 25%; - padding: 10px; - } - } - - h2 + .gridBlock { - padding-top: 20px; - } -} - -@media only screen and (min-width: 1400px) { - .gridBlock { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: -10px -20px 10px -20px; - - .twoByGridBlock { - box-sizing: border-box; - flex: 1 0 50%; - padding: 10px 20px; - } - - .fourByGridBlock { - box-sizing: border-box; - flex: 1 0 25%; - padding: 10px 20px; - } - } -} \ No newline at end of file diff --git a/docs/_sass/_header.scss b/docs/_sass/_header.scss deleted file mode 100644 index ac79390f4..000000000 --- a/docs/_sass/_header.scss +++ /dev/null @@ -1,139 +0,0 @@ -.fixedHeaderContainer { - background: $primary-bg; - color: $primary-overlay; - height: $header-height; - padding: $header-ptop 0 $header-pbot; - position: sticky; - top: 0; - width: 100%; - z-index: 9999; - - a { - align-items: center; - border: 0; - color: $primary-overlay; - display: flex; - flex-flow: row nowrap; - height: $header-height; - } - - header { - display: flex; - flex-flow: row nowrap; - position: relative; - text-align: left; - - img { - height: 24px; - margin-right: 10px; - } - - h2 { - display: block; - font-family: $header-font-family; - font-weight: 900; - line-height: 18px; - position: relative; - } - } -} - -.navigationFull { - height: 34px; - margin-left: auto; - - nav { - position: relative; - - ul { - display: flex; - flex-flow: row nowrap; - margin: 0 -10px; - - li { - padding: 0 10px; - display: block; - - a { - border: 0; - color: $primary-overlay-special; - font-size: 16px; - font-weight: 400; - line-height: 1.2em; - - &:hover { - border-bottom: 2px solid $primary-overlay; - color: $primary-overlay; - } - } - - &.navItemActive { - a { - color: $primary-overlay; - } - } - } - } - } -} - -/* 900px - - - .fixedHeaderContainer { - .navigationWrapper { - nav { - padding: 0 1em; - position: relative; - top: -9px; - - ul { - margin: 0 -0.4em; - li { - display: inline-block; - - a { - padding: 14px 0.4em; - border: 0; - color: $primary-overlay-special; - display: inline-block; - - &:hover { - color: $primary-overlay; - } - } - - &.navItemActive { - a { - color: $primary-overlay; - } - } - } - } - } - - &.navigationFull { - display: inline-block; - } - - &.navigationSlider { - display: none; - } - } - } - - 1200px - - .fixedHeaderContainer { - header { - max-width: 1100px; - } - } - - 1500px - .fixedHeaderContainer { - header { - max-width: 1400px; - } - } - */ diff --git a/docs/_sass/_poweredby.scss b/docs/_sass/_poweredby.scss deleted file mode 100644 index 4155b6053..000000000 --- a/docs/_sass/_poweredby.scss +++ /dev/null @@ -1,69 +0,0 @@ -.poweredByContainer { - background: $primary-bg; - color: $primary-overlay; - margin-bottom: 20px; - - a { - color: $primary-overlay; - } - - .poweredByWrapper { - h2 { - border-color: $primary-overlay-special; - color: $primary-overlay-special; - } - } - - .poweredByMessage { - color: $primary-overlay-special; - font-size: 14px; - padding-top: 20px; - } -} - -.poweredByItems { - display: flex; - flex-flow: row wrap; - margin: 0 -10px; -} - -.poweredByItem { - box-sizing: border-box; - flex: 1 0 50%; - line-height: 1.1em; - padding: 5px 10px; - - &.itemLarge { - flex-basis: 100%; - padding: 10px; - text-align: center; - - &:nth-child(4) { - padding-bottom: 20px; - } - - img { - max-height: 30px; - } - } -} - -@media only screen and (min-width: 480px) { - .itemLarge { - flex-basis: 50%; - max-width: 50%; - } -} - -@media only screen and (min-width: 1024px) { - .poweredByItem { - flex-basis: 25%; - max-width: 25%; - - &.itemLarge { - padding-bottom: 20px; - text-align: left; - } - } -} - diff --git a/docs/_sass/_promo.scss b/docs/_sass/_promo.scss deleted file mode 100644 index 8c9a809dc..000000000 --- a/docs/_sass/_promo.scss +++ /dev/null @@ -1,55 +0,0 @@ -.promoSection { - display: flex; - flex-flow: column wrap; - font-size: 125%; - line-height: 1.6em; - margin: -10px 0; - position: relative; - z-index: 99; - - .promoRow { - padding: 10px 0; - - .pluginWrapper { - display: block; - - &.ghWatchWrapper, &.ghStarWrapper { - height: 28px; - } - } - - .pluginRowBlock { - display: flex; - flex-flow: row wrap; - margin: 0 -2px; - - .pluginWrapper { - padding: 0 2px; - } - } - } -} - -iframe.pluginIframe { - height: 500px; - margin-top: 20px; - width: 100%; -} - -.iframeContent { - display: none; -} - -.iframePreview { - display: inline-block; - margin-top: 20px; -} - -@media only screen and (min-width: 1024px) { - .iframeContent { - display: block; - } - .iframePreview { - display: none; - } -} \ No newline at end of file diff --git a/docs/_sass/_react_docs_nav.scss b/docs/_sass/_react_docs_nav.scss deleted file mode 100644 index f0a651e7f..000000000 --- a/docs/_sass/_react_docs_nav.scss +++ /dev/null @@ -1,332 +0,0 @@ -.docsNavContainer { - background: $sidenav; - height: 35px; - left: 0; - position: fixed; - width: 100%; - z-index: 100; -} - -.docMainWrapper { - .wrapper { - &.mainWrapper { - padding-left: 0; - padding-right: 0; - padding-top: 10px; - } - } -} - -.docsSliderActive { - .docsNavContainer { - box-sizing: border-box; - height: 100%; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding-bottom: 50px; - } - - .mainContainer { - display: none; - } -} - -.navBreadcrumb { - box-sizing: border-box; - display: flex; - flex-flow: row nowrap; - font-size: 12px; - height: 35px; - overflow: hidden; - padding: 5px 10px; - - a, span { - border: 0; - color: $sidenav-text; - } - - i { - padding: 0 3px; - } -} - -nav.toc { - position: relative; - - section { - padding: 0px; - position: relative; - - .navGroups { - display: none; - padding: 40px 10px 10px; - } - } - - .toggleNav { - background: $sidenav; - color: $sidenav-text; - position: relative; - transition: background-color 0.3s, color 0.3s; - - .navToggle { - cursor: pointer; - height: 24px; - margin-right: 10px; - position: relative; - text-align: left; - width: 18px; - - &::before, &::after { - content: ""; - position: absolute; - top: 50%; - left: 0; - left: 8px; - width: 3px; - height: 6px; - border: 5px solid $sidenav-text; - border-width: 5px 0; - margin-top: -8px; - transform: rotate(45deg); - z-index: 1; - } - - &::after { - transform: rotate(-45deg); - } - - i { - &::before, &::after { - content: ""; - position: absolute; - top: 50%; - left: 2px; - background: transparent; - border-width: 0 5px 5px; - border-style: solid; - border-color: transparent $sidenav-text; - height: 0; - margin-top: -7px; - opacity: 1; - width: 5px; - z-index: 10; - } - - &::after { - border-width: 5px 5px 0; - margin-top: 2px; - } - } - } - - .navGroup { - background: $sidenav-overlay; - margin: 1px 0; - - ul { - display: none; - } - - h3 { - background: $sidenav-overlay; - color: $sidenav-text; - cursor: pointer; - font-size: 14px; - font-weight: 400; - line-height: 1.2em; - padding: 10px; - transition: color 0.2s; - - i:not(:empty) { - width: 16px; - height: 16px; - display: inline-block; - box-sizing: border-box; - text-align: center; - color: rgba($sidenav-text, 0.5); - margin-right: 10px; - transition: color 0.2s; - } - - &:hover { - color: $primary-bg; - - i:not(:empty) { - color: $primary-bg; - } - } - } - - &.navGroupActive { - background: $sidenav-active; - color: $sidenav-text; - - ul { - display: block; - padding-bottom: 10px; - padding-top: 10px; - } - - h3 { - background: $primary-bg; - color: $primary-overlay; - - i { - display: none; - } - } - } - } - - ul { - padding-left: 0; - padding-right: 24px; - - li { - list-style-type: none; - padding-bottom: 0; - padding-left: 0; - - a { - border: none; - color: $sidenav-text; - display: inline-block; - font-size: 14px; - line-height: 1.1em; - margin: 2px 10px 5px; - padding: 5px 0 2px; - transition: color 0.3s; - - &:hover, - &:focus { - color: $primary-bg; - } - - &.navItemActive { - color: $primary-bg; - font-weight: 900; - } - } - } - } - } - - .toggleNavActive { - .navBreadcrumb { - background: $sidenav; - margin-bottom: 20px; - position: fixed; - width: 100%; - } - - section { - .navGroups { - display: block; - } - } - - - .navToggle { - &::before, &::after { - border-width: 6px 0; - height: 0px; - margin-top: -6px; - } - - i { - opacity: 0; - } - } - } -} - -.docsNavVisible { - .navPusher { - .mainContainer { - padding-top: 35px; - } - } -} - -@media only screen and (min-width: 900px) { - .navBreadcrumb { - padding: 5px 0; - } - - nav.toc { - section { - .navGroups { - padding: 40px 0 0; - } - } - } -} - -@media only screen and (min-width: 1024px) { - .navToggle { - display: none; - } - - .docsSliderActive { - .mainContainer { - display: block; - } - } - - .docsNavVisible { - .navPusher { - .mainContainer { - padding-top: 0; - } - } - } - - .docsNavContainer { - background: none; - box-sizing: border-box; - height: auto; - margin: 40px 40px 0 0; - overflow-y: auto; - position: relative; - width: 300px; - } - - nav.toc { - section { - .navGroups { - display: block; - padding-top: 0px; - } - } - - .toggleNavActive { - .navBreadcrumb { - margin-bottom: 0; - position: relative; - } - } - } - - .docMainWrapper { - display: flex; - flex-flow: row nowrap; - margin-bottom: 40px; - - .wrapper { - padding-left: 0; - padding-right: 0; - - &.mainWrapper { - padding-top: 0; - } - } - } - - .navBreadcrumb { - display: none; - h2 { - padding: 0 10px; - } - } -} \ No newline at end of file diff --git a/docs/_sass/_react_header_nav.scss b/docs/_sass/_react_header_nav.scss deleted file mode 100644 index 13c0e562b..000000000 --- a/docs/_sass/_react_header_nav.scss +++ /dev/null @@ -1,141 +0,0 @@ -.navigationFull { - display: none; -} - -.navigationSlider { - position: absolute; - right: 0px; - - .navSlideout { - cursor: pointer; - padding-top: 4px; - position: absolute; - right: 10px; - top: 0; - transition: top 0.3s; - z-index: 101; - } - - .slidingNav { - background: $secondary-bg; - box-sizing: border-box; - height: 0px; - overflow-x: hidden; - padding: 0; - position: absolute; - right: 0px; - top: 0; - transition: height 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55), width 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); - width: 0; - - ul { - flex-flow: column nowrap; - list-style: none; - padding: 10px; - - li { - margin: 0; - padding: 2px 0; - - a { - color: $primary-bg; - display: inline; - margin: 3px 5px; - padding: 2px 0px; - transition: background-color 0.3s; - - &:focus, - &:hover { - border-bottom: 2px solid $primary-bg; - } - } - } - } - } - - .navSlideoutActive { - .slidingNav { - height: auto; - padding-top: $header-height + $header-pbot; - width: 300px; - } - - .navSlideout { - top: -2px; - .menuExpand { - span:nth-child(1) { - background-color: $text; - top: 16px; - transform: rotate(45deg); - } - span:nth-child(2) { - opacity: 0; - } - span:nth-child(3) { - background-color: $text; - transform: rotate(-45deg); - } - } - } - } -} - -.menuExpand { - display: flex; - flex-flow: column nowrap; - height: 20px; - justify-content: space-between; - - span { - background: $primary-overlay; - border-radius: 3px; - display: block; - flex: 0 0 4px; - height: 4px; - position: relative; - top: 0; - transition: background-color 0.3s, top 0.3s, opacity 0.3s, transform 0.3s; - width: 20px; - } -} - -.navPusher { - border-top: $header-height + $header-ptop + $header-pbot solid $primary-bg; - position: relative; - left: 0; - z-index: 99; - height: 100%; - - &::after { - position: absolute; - top: 0; - right: 0; - width: 0; - height: 0; - background: rgba(0,0,0,0.4); - content: ''; - opacity: 0; - -webkit-transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s; - transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s; - } - - .sliderActive &::after { - width: 100%; - height: 100%; - opacity: 1; - -webkit-transition: opacity 0.5s; - transition: opacity 0.5s; - z-index: 100; - } -} - - -@media only screen and (min-width: 1024px) { - .navigationFull { - display: block; - } - - .navigationSlider { - display: none; - } -} \ No newline at end of file diff --git a/docs/_sass/_reset.scss b/docs/_sass/_reset.scss deleted file mode 100644 index 0e5f2e0c1..000000000 --- a/docs/_sass/_reset.scss +++ /dev/null @@ -1,43 +0,0 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} diff --git a/docs/_sass/_search.scss b/docs/_sass/_search.scss deleted file mode 100644 index eadfa11d1..000000000 --- a/docs/_sass/_search.scss +++ /dev/null @@ -1,142 +0,0 @@ -input[type="search"] { - -moz-appearance: none; - -webkit-appearance: none; -} - -.navSearchWrapper { - align-self: center; - position: relative; - - &::before { - border: 3px solid $primary-overlay-special; - border-radius: 50%; - content: " "; - display: block; - height: 6px; - left: 15px; - width: 6px; - position: absolute; - top: 4px; - z-index: 1; - } - - &::after { - background: $primary-overlay-special; - content: " "; - height: 7px; - left: 24px; - position: absolute; - transform: rotate(-45deg); - top: 12px; - width: 3px; - z-index: 1; - } - - .aa-dropdown-menu { - background: $secondary-bg; - border: 3px solid rgba($text, 0.25); - color: $text; - font-size: 14px; - left: auto !important; - line-height: 1.2em; - right: 0 !important; - - .algolia-docsearch-suggestion--category-header { - background: $primary-overlay-special; - color: $primary-bg; - - .algolia-docsearch-suggestion--highlight { - background-color: $primary-bg; - color: $primary-overlay; - } - } - - .algolia-docsearch-suggestion--title .algolia-docsearch-suggestion--highlight, - .algolia-docsearch-suggestion--subcategory-column .algolia-docsearch-suggestion--highlight { - color: $primary-bg; - } - - .algolia-docsearch-suggestion__secondary, - .algolia-docsearch-suggestion--subcategory-column { - border-color: rgba($text, 0.3); - } - } -} - -input#search_input { - padding-left: 25px; - font-size: 14px; - line-height: 20px; - border-radius: 20px; - background-color: rgba($primary-overlay-special, 0.25); - border: none; - color: rgba($primary-overlay-special, 0); - outline: none; - position: relative; - transition: background-color .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), width .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), color .2s ease; - width: 60px; - - &:focus, &:active { - background-color: $secondary-bg; - color: $text; - width: 240px; - } -} - -.navigationSlider { - .navSearchWrapper { - &::before { - left: 6px; - top: 6px; - } - - &::after { - left: 15px; - top: 14px; - } - } - - input#search_input_react { - box-sizing: border-box; - padding-left: 25px; - font-size: 14px; - line-height: 20px; - border-radius: 20px; - background-color: rgba($primary-overlay-special, 0.25); - border: none; - color: $text; - outline: none; - position: relative; - transition: background-color .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), width .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), color .2s ease; - width: 100%; - - &:focus, &:active { - background-color: $primary-bg; - color: $primary-overlay; - } - } - - .algolia-docsearch-suggestion--subcategory-inline { - display: none; - } - - & > span { - width: 100%; - } - - .aa-dropdown-menu { - background: $secondary-bg; - border: 0px solid $secondary-bg; - color: $text; - font-size: 12px; - line-height: 2em; - max-height: 140px; - min-width: auto; - overflow-y: scroll; - -webkit-overflow-scrolling: touch; - padding: 0; - border-radius: 0; - position: relative !important; - width: 100%; - } -} \ No newline at end of file diff --git a/docs/_sass/_slideshow.scss b/docs/_sass/_slideshow.scss deleted file mode 100644 index cd98a6cdb..000000000 --- a/docs/_sass/_slideshow.scss +++ /dev/null @@ -1,48 +0,0 @@ -.slideshow { - position: relative; - - .slide { - display: none; - - img { - display: block; - margin: 0 auto; - } - - &.slideActive { - display: block; - } - - a { - border: none; - display: block; - } - } - - .pagination { - display: block; - margin: -10px; - padding: 1em 0; - text-align: center; - width: 100%; - - .pager { - background: transparent; - border: 2px solid rgba(255, 255, 255, 0.5); - border-radius: 50%; - cursor: pointer; - display: inline-block; - height: 12px; - margin: 10px; - transition: background-color 0.3s, border-color 0.3s; - width: 12px; - - &.pagerActive { - background: rgba(255, 255, 255, 0.5); - border-width: 4px; - height: 8px; - width: 8px; - } - } - } -} diff --git a/docs/_sass/_syntax-highlighting.scss b/docs/_sass/_syntax-highlighting.scss deleted file mode 100644 index e55c88a2e..000000000 --- a/docs/_sass/_syntax-highlighting.scss +++ /dev/null @@ -1,129 +0,0 @@ - - -.rougeHighlight { background-color: $code-bg; color: #93a1a1 } -.rougeHighlight .c { color: #586e75 } /* Comment */ -.rougeHighlight .err { color: #93a1a1 } /* Error */ -.rougeHighlight .g { color: #93a1a1 } /* Generic */ -.rougeHighlight .k { color: #859900 } /* Keyword */ -.rougeHighlight .l { color: #93a1a1 } /* Literal */ -.rougeHighlight .n { color: #93a1a1 } /* Name */ -.rougeHighlight .o { color: #859900 } /* Operator */ -.rougeHighlight .x { color: #cb4b16 } /* Other */ -.rougeHighlight .p { color: #93a1a1 } /* Punctuation */ -.rougeHighlight .cm { color: #586e75 } /* Comment.Multiline */ -.rougeHighlight .cp { color: #859900 } /* Comment.Preproc */ -.rougeHighlight .c1 { color: #72c02c; } /* Comment.Single */ -.rougeHighlight .cs { color: #859900 } /* Comment.Special */ -.rougeHighlight .gd { color: #2aa198 } /* Generic.Deleted */ -.rougeHighlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ -.rougeHighlight .gr { color: #dc322f } /* Generic.Error */ -.rougeHighlight .gh { color: #cb4b16 } /* Generic.Heading */ -.rougeHighlight .gi { color: #859900 } /* Generic.Inserted */ -.rougeHighlight .go { color: #93a1a1 } /* Generic.Output */ -.rougeHighlight .gp { color: #93a1a1 } /* Generic.Prompt */ -.rougeHighlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ -.rougeHighlight .gu { color: #cb4b16 } /* Generic.Subheading */ -.rougeHighlight .gt { color: #93a1a1 } /* Generic.Traceback */ -.rougeHighlight .kc { color: #cb4b16 } /* Keyword.Constant */ -.rougeHighlight .kd { color: #268bd2 } /* Keyword.Declaration */ -.rougeHighlight .kn { color: #859900 } /* Keyword.Namespace */ -.rougeHighlight .kp { color: #859900 } /* Keyword.Pseudo */ -.rougeHighlight .kr { color: #268bd2 } /* Keyword.Reserved */ -.rougeHighlight .kt { color: #dc322f } /* Keyword.Type */ -.rougeHighlight .ld { color: #93a1a1 } /* Literal.Date */ -.rougeHighlight .m { color: #2aa198 } /* Literal.Number */ -.rougeHighlight .s { color: #2aa198 } /* Literal.String */ -.rougeHighlight .na { color: #93a1a1 } /* Name.Attribute */ -.rougeHighlight .nb { color: #B58900 } /* Name.Builtin */ -.rougeHighlight .nc { color: #268bd2 } /* Name.Class */ -.rougeHighlight .no { color: #cb4b16 } /* Name.Constant */ -.rougeHighlight .nd { color: #268bd2 } /* Name.Decorator */ -.rougeHighlight .ni { color: #cb4b16 } /* Name.Entity */ -.rougeHighlight .ne { color: #cb4b16 } /* Name.Exception */ -.rougeHighlight .nf { color: #268bd2 } /* Name.Function */ -.rougeHighlight .nl { color: #93a1a1 } /* Name.Label */ -.rougeHighlight .nn { color: #93a1a1 } /* Name.Namespace */ -.rougeHighlight .nx { color: #93a1a1 } /* Name.Other */ -.rougeHighlight .py { color: #93a1a1 } /* Name.Property */ -.rougeHighlight .nt { color: #268bd2 } /* Name.Tag */ -.rougeHighlight .nv { color: #268bd2 } /* Name.Variable */ -.rougeHighlight .ow { color: #859900 } /* Operator.Word */ -.rougeHighlight .w { color: #93a1a1 } /* Text.Whitespace */ -.rougeHighlight .mf { color: #2aa198 } /* Literal.Number.Float */ -.rougeHighlight .mh { color: #2aa198 } /* Literal.Number.Hex */ -.rougeHighlight .mi { color: #2aa198 } /* Literal.Number.Integer */ -.rougeHighlight .mo { color: #2aa198 } /* Literal.Number.Oct */ -.rougeHighlight .sb { color: #586e75 } /* Literal.String.Backtick */ -.rougeHighlight .sc { color: #2aa198 } /* Literal.String.Char */ -.rougeHighlight .sd { color: #93a1a1 } /* Literal.String.Doc */ -.rougeHighlight .s2 { color: #2aa198 } /* Literal.String.Double */ -.rougeHighlight .se { color: #cb4b16 } /* Literal.String.Escape */ -.rougeHighlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */ -.rougeHighlight .si { color: #2aa198 } /* Literal.String.Interpol */ -.rougeHighlight .sx { color: #2aa198 } /* Literal.String.Other */ -.rougeHighlight .sr { color: #dc322f } /* Literal.String.Regex */ -.rougeHighlight .s1 { color: #2aa198 } /* Literal.String.Single */ -.rougeHighlight .ss { color: #2aa198 } /* Literal.String.Symbol */ -.rougeHighlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ -.rougeHighlight .vc { color: #268bd2 } /* Name.Variable.Class */ -.rougeHighlight .vg { color: #268bd2 } /* Name.Variable.Global */ -.rougeHighlight .vi { color: #268bd2 } /* Name.Variable.Instance */ -.rougeHighlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */ - -.highlighter-rouge { - color: darken(#72c02c, 8%); - font: 800 12px/1.5em Hack, monospace; - max-width: 100%; - - .rougeHighlight { - border-radius: 3px; - margin: 20px 0; - padding: 0px; - overflow-x: scroll; - -webkit-overflow-scrolling: touch; - - table { - background: none; - border: none; - - tbody { - tr { - background: none; - display: flex; - flex-flow: row nowrap; - - td { - display: block; - flex: 1 1; - - &.gutter { - border-right: 1px solid lighten($code-bg, 10%); - color: lighten($code-bg, 15%); - margin-right: 10px; - max-width: 40px; - padding-right: 10px; - - pre { - max-width: 20px; - } - } - } - } - } - } - } -} - -p > .highlighter-rouge, -li > .highlighter-rouge, -a > .highlighter-rouge { - font-size: 16px; - font-weight: 400; - line-height: inherit; -} - -a:hover { - .highlighter-rouge { - color: white; - } -} \ No newline at end of file diff --git a/docs/_sass/_tables.scss b/docs/_sass/_tables.scss deleted file mode 100644 index f847c7013..000000000 --- a/docs/_sass/_tables.scss +++ /dev/null @@ -1,47 +0,0 @@ -table { - background: $lightergrey; - border: 1px solid $lightgrey; - border-collapse: collapse; - display:table; - margin: 20px 0; - - thead { - border-bottom: 1px solid $lightgrey; - display: table-header-group; - } - tbody { - display: table-row-group; - } - tr { - display: table-row; - &:nth-of-type(odd) { - background: $greyish; - } - - th, td { - border-right: 1px dotted $lightgrey; - display: table-cell; - font-size: 14px; - line-height: 1.3em; - padding: 10px; - text-align: left; - - &:last-of-type { - border-right: 0; - } - - code { - color: $green; - display: inline-block; - font-size: 12px; - } - } - - th { - color: #000000; - font-weight: bold; - font-family: $header-font-family; - text-transform: uppercase; - } - } -} \ No newline at end of file diff --git a/docs/_top-level/support.md b/docs/_top-level/support.md deleted file mode 100644 index 05c39befd..000000000 --- a/docs/_top-level/support.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -layout: top-level -title: Support -id: support -category: support ---- - -## Need help? - -Do not hesitate to ask questions if you are having trouble with RocksDB. - -### GitHub issues - -Use [GitHub issues](https://github.com/facebook/rocksdb/issues) to report bugs, issues and feature requests for the RocksDB codebase. - -### Facebook Group - -Use the [RocksDB Facebook group](https://www.facebook.com/groups/rocksdb.dev/) for general questions and discussion about RocksDB. - -### FAQ - -Check out a list of [commonly asked questions](https://github.com/facebook/rocksdb/wiki/RocksDB-FAQ) about RocksDB. diff --git a/docs/blog/all.html b/docs/blog/all.html deleted file mode 100644 index 3be2d3bff..000000000 --- a/docs/blog/all.html +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: all -layout: blog -category: blog ---- - -
-
-

All Posts

- {% for post in site.posts %} - {% assign author = site.data.authors[post.author] %} -

- - {{ post.title }} - - on {{ post.date | date: "%B %e, %Y" }} by {{ author.display_name }} -

- {% endfor %} -
-
diff --git a/docs/blog/index.html b/docs/blog/index.html deleted file mode 100644 index 9f6b25d03..000000000 --- a/docs/blog/index.html +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: blog -title: Blog -layout: blog -category: blog ---- - -
- {% for page in site.posts %} - {% include post.html truncate=true %} - {% endfor %} -
diff --git a/docs/css/main.scss b/docs/css/main.scss deleted file mode 100644 index 88ab4e811..000000000 --- a/docs/css/main.scss +++ /dev/null @@ -1,159 +0,0 @@ ---- -# Only the main Sass file needs front matter (the dashes are enough) ---- -@charset "utf-8"; - -@font-face { - font-family: 'Lato'; - src: url("{{ '/static/fonts/LatoLatin-Italic.woff2' }}") format('woff2'), - url("{{ '/static/fonts/LatoLatin-Italic.woff' }}") format('woff'); - font-weight: normal; - font-style: italic; -} - -@font-face { - font-family: 'Lato'; - src: url("{{ '/static/fonts/LatoLatin-Black.woff2' }}") format('woff2'), - url("{{ '/static/fonts/LatoLatin-Black.woff' }}") format('woff'); - font-weight: 900; - font-style: normal; -} - -@font-face { - font-family: 'Lato'; - src: url("{{ '/static/fonts/LatoLatin-BlackItalic.woff2' }}") format('woff2'), - url("{{ '/static/fonts/LatoLatin-BlackItalic.woff' }}") format('woff'); - font-weight: 900; - font-style: italic; -} - -@font-face { - font-family: 'Lato'; - src: url("{{ '/static/fonts/LatoLatin-Light.woff2' }}") format('woff2'), - url("{{ '/static/fonts/LatoLatin-Light.woff' }}") format('woff'); - font-weight: 300; - font-style: normal; -} - -@font-face { - font-family: 'Lato'; - src: url("{{ '/static/fonts/LatoLatin-Regular.woff2' }}") format('woff2'), - url("{{ '/static/fonts/LatoLatin-Regular.woff' }}") format('woff'); - font-weight: normal; - font-style: normal; -} - -// Our variables -$base-font-family: 'Lato', Calibri, Arial, sans-serif; -$header-font-family: 'Lato', 'Helvetica Neue', Arial, sans-serif; -$base-font-size: 18px; -$small-font-size: $base-font-size * 0.875; -$base-line-height: 1.4em; - -$spacing-unit: 12px; - -// Two configured colors (see _config.yml) -$primary-bg: {{ site.color.primary }}; -$secondary-bg: {{ site.color.secondary }}; - -// $primary-bg overlays -{% if site.color.primary-overlay == 'light' %} -$primary-overlay: darken($primary-bg, 70%); -$primary-overlay-special: darken($primary-bg, 40%); -{% else %} -$primary-overlay: #fff; -$primary-overlay-special: lighten($primary-bg, 30%); -{% endif %} - -// $secondary-bg overlays -{% if site.color.secondary-overlay == 'light' %} -$text: #393939; -$sidenav: darken($secondary-bg, 20%); -$sidenav-text: $text; -$sidenav-overlay: darken($sidenav, 10%); -$sidenav-active: lighten($sidenav, 10%); -{% else %} -$text: #fff; -$sidenav: lighten($secondary-bg, 20%); -$sidenav-text: $text; -$sidenav-overlay: lighten($sidenav, 10%); -$sidenav-active: darken($sidenav, 10%); -{% endif %} - -$code-bg: #002b36; - -$header-height: 34px; -$header-ptop: 10px; -$header-pbot: 8px; - -// Width of the content area -$content-width: 900px; - -// Table setting variables -$lightergrey: #F8F8F8; -$greyish: #E8E8E8; -$lightgrey: #B0B0B0; -$green: #2db04b; - -// Using media queries with like this: -// @include media-query($on-palm) { -// .wrapper { -// padding-right: $spacing-unit / 2; -// padding-left: $spacing-unit / 2; -// } -// } -@mixin media-query($device) { - @media screen and (max-width: $device) { - @content; - } -} - - - -// Import partials from `sass_dir` (defaults to `_sass`) -@import - "reset", - "base", - "header", - "search", - "syntax-highlighting", - "promo", - "buttons", - "gridBlock", - "poweredby", - "footer", - "react_header_nav", - "react_docs_nav", - "tables", - "blog" -; - -// Anchor links -// http://ben.balter.com/2014/03/13/pages-anchor-links/ -.header-link { - position: absolute; - margin-left: 0.2em; - opacity: 0; - - -webkit-transition: opacity 0.2s ease-in-out 0.1s; - -moz-transition: opacity 0.2s ease-in-out 0.1s; - -ms-transition: opacity 0.2s ease-in-out 0.1s; -} - -h2:hover .header-link, -h3:hover .header-link, -h4:hover .header-link, -h5:hover .header-link, -h6:hover .header-link { - opacity: 1; -} - -/* Social Banner */ -.socialBanner { - font-weight: bold; - font-size: 20px; - padding: 20px; - max-width: 768px; - margin: 0 auto; - text-align: center; - } diff --git a/docs/doc-type-examples/2016-04-07-blog-post-example.md b/docs/doc-type-examples/2016-04-07-blog-post-example.md deleted file mode 100644 index ef954d63a..000000000 --- a/docs/doc-type-examples/2016-04-07-blog-post-example.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Blog Post Example -layout: post -author: exampleauthor -category: blog ---- - -Any local blog posts would go in the `_posts` directory. - -This is an example blog post introduction, try to keep it short and about a paragraph long, to encourage people to click through to read the entire post. - - - -Everything below the `` tag will only show on the actual blog post page, not on the `/blog/` index. - -Author is defined in `_data/authors.yml` - - -## No posts? - -If you have no blog for your site, you can remove the entire `_posts` folder. Otherwise add markdown files in here. See CONTRIBUTING.md for details. diff --git a/docs/doc-type-examples/docs-hello-world.md b/docs/doc-type-examples/docs-hello-world.md deleted file mode 100644 index c7094ba5a..000000000 --- a/docs/doc-type-examples/docs-hello-world.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -docid: hello-world -title: Hello, World! -layout: docs -permalink: /docs/hello-world.html ---- - -Any local docs would go in the `_docs` directory. - -## No documentation? - -If you have no documentation for your site, you can remove the entire `_docs` folder. Otherwise add markdown files in here. See CONTRIBUTING.md for details. diff --git a/docs/doc-type-examples/top-level-example.md b/docs/doc-type-examples/top-level-example.md deleted file mode 100644 index 67b1fa711..000000000 --- a/docs/doc-type-examples/top-level-example.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: top-level -title: Support Example -id: top-level-example -category: top-level ---- - -This is a static page disconnected from the blog or docs collections that can be added at a top-level (i.e., the same level as `index.md`). diff --git a/docs/docs/index.html b/docs/docs/index.html deleted file mode 100644 index fa6ec8b5a..000000000 --- a/docs/docs/index.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -id: docs -title: Docs -layout: redirect -destination: getting-started.html ---- diff --git a/docs/feed.xml b/docs/feed.xml deleted file mode 100644 index 725f00566..000000000 --- a/docs/feed.xml +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: null ---- - - - - {{ site.title | xml_escape }} - {{ site.description | xml_escape }} - https://rocksdb.org/feed.xml - - {{ site.time | date_to_rfc822 }} - {{ site.time | date_to_rfc822 }} - Jekyll v{{ jekyll.version }} - {% for post in site.posts limit:10 %} - - {{ post.title | xml_escape }} - {{ post.content | xml_escape }} - {{ post.date | date_to_rfc822 }} - {{ post.url | absolute_url }} - {{ post.url | absolute_url }} - {% for tag in post.tags %} - {{ tag | xml_escape }} - {% endfor %} - {% for cat in post.categories %} - {{ cat | xml_escape }} - {% endfor %} - - {% endfor %} - - diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 2b9570d23..000000000 --- a/docs/index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: home -title: RocksDB | A persistent key-value store -id: home ---- - -## Features - -{% include content/gridblocks.html data_source=site.data.features align="center" %} diff --git a/docs/static/favicon.png b/docs/static/favicon.png deleted file mode 100644 index 7f668f38f7a5084a442278e780e43de74501fcb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3927 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Ν%2WK4ZWpd2mKlgAy2;XyY54zOhsvRN( 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 diff --git a/docs/static/fonts/LatoLatin-Black.woff2 b/docs/static/fonts/LatoLatin-Black.woff2 deleted file mode 100644 index 4127b4d0b9f0977d1a366a5ec6537ded003fcac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43456 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 F{{S|x{Ez?u diff --git a/docs/static/fonts/LatoLatin-BlackItalic.woff b/docs/static/fonts/LatoLatin-BlackItalic.woff deleted file mode 100644 index 142c1c9c48d42989e9829318ea1024fd2e08c370..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72372 zcmb@t1ymi~mM*$+2oAyBgS!O}?g6rKcXxMpcL?qp+#Q0uy9IaMxLp2o`gWi0drrT8 z_q`h5{MKCGoU3-#s6AG#nl;KrUQ7%C1^@uSaO(l6A1$~{_uu*-?tk6HzsoCqB>DmX zjLHCjTJIE?2EMow@EZWYwDh5me+*tLeotVPS7KxV09Z;s#%q3q<=^x6*U91%Sc-wLjv&3<4F5&sxvQ=tHacIJ|HG0P70hPrJ?5#=#K)_L=MB zJYE3+xS9(~n=@N`qmQw;9~!{p!@}<(Um098)-zmOe*o_<7Lhsf0O+@^~E2`-`^2h1TugW++VLZFpL>JO_CBa&@Ystd+DwDp8ujV zyUqdan}Uwd{VU&O2j{`#1h<3nMZ>-JN!v&kH?fq+@ra;g!ttvO9@F>qm@ICm_97k7XN(CuDila>Fl~*Gc%SN0el1|JO`HW8IsAtyN8(FH(#w|!?>2Op!)oZ_T zEhbhwN1!$7aF*IE(XWa&oY?F#sPN5qqQJS48>$cSMmyoh1iF6nE?=lrpEQ3JoJED|Hb zD|E25sjs>k8EA9$Q9As39)<8qQGq;IQhlYT)VHShPSy^5uxj6d&BlCFbk!628ES-@ zow5mshhdk#6B8+R0h=<*o zf@_|o(m?(jq(@PAc0+eLDG-ekixGexyTaD{4}yc8XHXzS5K^xQ>#XiZ$I`A zdDDT?P?$og2XGK0O{sw9#pm-gh#&%bTsyR60qA~82c?#_*t1yGYHRpw!AE42v0(WnP0%7pZoM@9i5jI-%X%YmY^qDOHk~6gjj= zjmqm2kcEQkJJ9H#3%l@rTT~L%;i-dIzP(pqN@9V^>Q$;Fm68~&=F!6ZI)KQoF;~@2UH5ScM=m!OZdM@er2s9pMuiNpOjLW zSQm6*^Q3P^Qbnse*g)J8ds>Hdw_Q)T8PCC5?BKo!^AZxUg+D~LhHCO#?IGBbv&Cx0 zLjc_Sdh~d0d0zrtQFtMtN$>S0Vr&V2KPpVb9PO~Ax*>rH_b|N%Ai$7yOI&hc_rFd* zd8m3T5BS?@3V;m+zzwCsDt41^$nW1!YYGg5l%9CpaAk}tOqw|nE%@QHAZB|In9#?- zCj+p$Bep2Kq>KZ&(I^}WOUF_q)Nt_Pn9YfSjfvl>uvyhDT7j-U+&4?;FaRYlN-s`7KZ z{OSrp2WFEOQ>csMD(zFX0e#`?(qq3{1;Ejza7AuMR`#3Uf>@~lv*`KCYr(e!;P97S zsp5cuSpskLNABUcL|sDShvw;)yM!BpZ0j!GVy(vWjOM%ZW$f0jMm~kn@rT$V=6}W* z0uS+3A_a1AA!t6;kAR5_5_;C^k5Ionu<>hTTK<%|CCCl9yg-;PO+6F|j16|ZQ*|_c zw87(TZJVRQ#OFZ$0=%c(08wrvp&Ue`9_)`~`Lu-D@Woc+!L@}kb4pvx=2vbAk+1mq zWDj(8Jh*fxvtUvq<52&+gs(_bDA)rg+(r(p*ZsLw6ODSEWtmxbyL$qj*ch<1R~ zPQEfKRDX%5Ar?8fUbg*njb~ay&@N5#tB}}>U(E<(#T^(uHBebM%y5AU@q`8;w(}lv z`O0(o8u##K>-l2+@QCs<_O0zcf1NPWmddS~Thl74$fjSO=r^Oo)!^dgAm~9b7@w21 zq-eHA@JYuwe}ah_&Jj#2O^9tzIY7*r#r;YTtoawwJ{$qQ~7B= z>o9+sVYW=IJmoS~!fAr!on5RK%kaM?{6p@4>rT!|Sw%0D8Ycf>k)}zbNE(axr%)WC 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 z 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

5&YZdc{QvR?Y)4dlq;cxh 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 diff --git a/docs/static/fonts/LatoLatin-BlackItalic.woff2 b/docs/static/fonts/LatoLatin-BlackItalic.woff2 deleted file mode 100644 index e9862e69094b4e51bcda9cc1727287dd5abb6935..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44316 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 diff --git a/docs/static/fonts/LatoLatin-Italic.woff b/docs/static/fonts/LatoLatin-Italic.woff deleted file mode 100644 index d8cf84c8b96e712c12d2af8efefd8bf240316ebd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74708 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 WZ$y@6puWu(W|-GhiY^Fm1^hoW?zidy diff --git a/docs/static/fonts/LatoLatin-Light.woff b/docs/static/fonts/LatoLatin-Light.woff deleted file mode 100644 index e7d4278cce83e38c533b9d4ea283b4ae1bba7631..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72604 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 K1|xeC+W!Mlu4Ui= diff --git a/docs/static/fonts/LatoLatin-Light.woff2 b/docs/static/fonts/LatoLatin-Light.woff2 deleted file mode 100644 index b6d028836e469fba8fb861d9186259e8a693054f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43468 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
{nb?=En2Z3RItY zO*wP8wD+jE>lG*~mR0l`g@j<$iXbP~pT>04vY+F)TCV#ZayBC6UWaAmtds1K8X4o6P zvd>mmOJ92L92h3l$FTDIpOxiC%o$WDarg%d)6j@#x#+S@jl)qQp|Nt>FYVgN6}X=} zcP{)%akFi%uN92~dj3l?2gND3*+DaZYJ!=e;Tgm=$4PoSo?AP@zG+Z>UY<^&8l|UA{EGkWYlotFrE^C{Ks3_Deg`Dr-W2H6pbCq;a=T> zZ&F5x?vu&)U%u=u+C%8#kos^TkQUn+VJmYG7+)Zx?YPzS4A9%2Isp8U5uw+c7M?8J z*8L~jReJ5uRVZ>7h^H7q8fRK|K<}G+Y1bt;13uqgVMYHM@`F0=95sVDqCEUo^%U$% zHfipa7rB?F4NF#2wFU*b2#RJ<`m~2{Uk>6;%MEy;1=%yo>6h+ME?J?yZ=L9i8nkTL zGO6XK8&_rgidP7&SbI$L`WxDsBhr^^Em~z9xXjFy`t(|#=n z|DvKiCvt8Y3X{fS5H*|s)BZ5erc%N#&te%xE5Uw#HlC|Nt0z6AYxIQaV{nus=cAcT z@IZO321?r=uJoGsGpLM>uIL$1Ydz%Ip8Dqdk10PyQ;KLhE5;|~%CdQJ3+b8_#3Opx zDZ^>?B7qdO-lFO;W!6XHT{+$IbvL=@D| zsVi-v?=IDci(7IhvB7KFqPutpz#g3pmO+2cXO9S75cYiQBk3&Xb@{;WsayEv5R} zl|0?gdJSwM%7iNq;zZLIYFw#Prw_WncdF$B3Axn0jq~R$-|zW*D=pXDL>Cp&gwbz2 zH9AhDEBrm09ejs&+&137d~NKyPFHR^H^uo`TwPpyYwm&a@waP}jL}Mr)OW{hgb*#Q zUymy2q^&Zaqo`(z$t#6khv)6R6c+CMZqs0*;Y-p&30USCEoDh(iX9 z{pai>ia!HnGJno4`28-%W+6>E8H@q9`jNx)7tH$aE}vf z)@ylW$}fHxET8r)%pl;@t1#zejn)2ovGdlyTk`y_(pW41fe~vSRCx~gcC{og*t$z{ zPEGZDnb5B1%3F7-kFDx^vrv=+K!wMLM+Ae&zxebbhS{Ixo!g+od1A^O;l=8a?|%80 z7Jx)9s$h(&*48^;PE2++cw_ilfn;I0#R-Q^qj2RS5GKd@hg!%-E4U4~SY!Voy|Jxq zzk|Ex3`^U4;GOoIIeQB1iaW0{HT5=p&cSx5*VrUC&8FTPH`wGSP}KPc1Q=NP`H8^I zOG;KKPE9;4Z|$+l#>4DDO{B6{tYn9w%$y|U`odYzgwG_oAwV;U3Y<|y$5QYWqq z88c#kjjfcEVPj7xJ{lYMst~3cUYJs!`sh(6g+>Ut{b|GEH*bV1zkXBR$?||Ch2g{R ze=3{_GUi=ZI8)fyFtX%UQ6CH?n(7ljTC@HjbNxh1+@X>ye91b5YUQl0$6BIZhf+m{!zRv3_@uaJv3U#?S=_^K@p-+Lh`+ zkTkVOQ+YXBG87P)P~z=W__6g9WPL|3I+4sS%7)w8qNus#V?q3Kl4kB>|EV@(g3;?_ zepGsVhor%D)Xi@9k^(rpUGL1~R!7X=|A@Y(mAww;qi~L_zpD~^^JcmKNuzOw)|6R~ zy~if2b=TLy*GjTY7vXv>`(#aJPoS5h7cX8+ZNTR-c=F;5Q=8EPPql0xNPS1TW|m)5 zb+BMLyz{7Mk(2xNl+y_gL_l~d7@{%zQ`1BR_<@U{W!|<+rB+}tKT&cttpTGZ3^J;} zNv_a4yOZWu4K1x>u9Cf1e>KX+9%ixkm^)*(=Pv`1FpcQZz57MF*0SB6OLd;Rb1|k^ z;d-wxXMEdhddgbzqIDF#9Fg@%{xs2TBOt77g<@~^J1|o zzaV-f+-sxv?3!#mFC!(|rCP|dlaYf2A0Iw;B0V6<7TO%|Xe<_UbVk0>a(NRM^odAu znilp&*>r4~TsPvH8?g10%waQIAYb}iUq5MiVbaF~fB23`^J&Zgf@b{7+L!<`d9x}A z`l}?sr-ErvEBvVRe>2*CES}&)f6VyHxANA~*M$bx;i%|lLEre&t@{FE)TH3GJ;xn9 zBjjb^XX|3#L4*jE?{Qz>js8KEb!iHTfmnJ>ah_qfb>?$2iAgDX+vpn;(@9+|A{i{8-O|A6_nc0%%{ejykFFU_H3x^{(rlgn&w&woObF4OxV)vHI{3fx<@3 zz4i&>{H{k87ygoG;Dy+UzPLr%)>}S43<2fc>+0Gkw8(BKP z2w(emy^ls?Lois{TPu?#*LQ#;b#oLs|Hvg;wK)4c%+9WDDcgpZQRJ89bd9~-y`$yH z`*_8vQ*&24xhGo{VH2#Vk9g+_g`H)#Gmua`yJBOul7`L8+}twQzlr*Z4s~+%Ydh{l zm|Kw-`+W0a)cL^^4qfBpp_G5{DehrTjxHI_1j$xWmjs{4#x!-hv#af%&?a|{7kYE& zx?(!F@kqkl(w<&xo{T+Mnd9B~{UfIdwo#|_>*Xa8a)B#MJ6cDT5n>qoY(tY^YdOPx zEE5wgo(CKPrrHoOphu4Ywz_-m#2dS&ve53c&<^KMIf-Q*%gmCc`LemQi4bSfcN#Jw zL&YjzD+Y~88JyKv!|vU>tvq2w4w8*I?^S$Mj#`ku<2bn}dc(_S@=ETz;Ign%(1t+P zZ_Z)3436>s=Tqd5(QJ9FqR+xTDl$qAnWp4wGg-)rj?9DkkAD__i)ycxBUx|BI|6{5 zQ<1Q<;AoFr28X#g8>GdkWLNi2k@>Kx+W~Wxgsf;qZr6+JteIk?1PNgyZ^j}>w*@p?j`s+=ivO`<@$}}FX zcuGN<>@Rb{7p@(4SGyRKgyf7-bRh1r_FUtKVzv-W>Qt zvYd7H9C6;G>?**qNs|86H8q-4kdL#w20*-jP-*$*$tx`i<-pLIKY#z$sshVc77WUDvAkZaBLrij(G1%3MW z4`GS9*NOuYsJGXEl*@}w`N7jf)~YR9lnFTVD59sECko~4F%Qarz^0~`>e|U&5{}V; z5v@NcG4*o8Y*XIf(%*a$nM4+YVg2CMd<-?|7n8ui)x+()KJU;wkpPa>eb?xk8khZRi34nY2Rv8K2y`jaU^7ZTUkk;jl zV|=tN(eG}4{t9#gS=^2jZ(aCm)F+e0t85_dLpUHd9|Bc?aW?x>FgIs2V8FG8#Qy*1 zlKo@hhX3;KL*ssYt8zRJCu4$%cll~ZOKxo<-w`otW3B*ojN5m~sYu zj+eBbcp1bk9;;|!xqQ9VgchBI?cX*S-(+2-X?F`B0fys4dwr3Q@ELzqr4YP{*D$kJ z!cS`jRmcSWZJRegn0V_;6}tVmA3y%sKXsw-cw2puy zrj6w!7M~~~tBBajsD##?iXUDT-7*6Q-WGkCvxQ@WzH__wUHU!vQa1&I!R_@kpCGe% zIA+d#FG|M=C?nSoQiIF=ztZ z|J4C4?z!m~S!ly8ICT`$>^C585oL#a+fz;V-p6Zu-P>hNK)sWblQZVgTWU}r26#HB zcnx);gFcShK>y3EY7NxU$<_y0pXL}E(f1yiyJ&B>S#Eq%3gAeeP}_T&I^Lvw6t zTrC*fcb+p}-+b_MC@jlf-!=W_{7T#9Jm8Y+Z?yFL(!xf{wkR9AcK!A8&u3&0hJAB! zWCq)!Y#=?0Bfi7bj;RBsEY3!ex9N-T+|sqwQ)Z4UggL;=b~e&$uqRpnBK$(C1FV3U z4!GTTq>v}|aBOPwXBmJ#`29%y6*y^`d zqPvXDrsH92;QxkO09|j{ygB@LiAA{GkqE@%@IXy?p=ggWm))Nx>ORILh>aR)v)2i2{X(! z*wCiC#u%ouVtE|6UFp_$H0&aw=$s?wN4U-$*YfWDds(_xkJgc=eIVebS3B#+;!O&r zx3YRh(C@?4*5SnCY9Z{YI`^Ndg*4Z!;w*Rm^5zpHZ{ER#8|l=iUn#-+X1f5G9b%Zu ztM)tkPJv%SiD34%Q48+|u*SEs?LQ0CmGuR;VvBsBK;!XQ>N9O?u=cK?QPO=f=q*Sd z7{V6DziZ^+!AXOgTDAb=#)C8aU~Qt>C?)(T0@RMlu8K%NB*1w;9}P^8)1qld5Sm7I zMQttf~=m7C%-oQFpxljgsgUR+C3V2w zqSUM+2P!T$F3u;|h3Tkbf>nY8;Phg=NQ@G`$aWO2ipdS|V-xH?39NNwjw8s3h?~6r zOrw;vvBLVGkK)74kFkiGK#P!j(}4NU=*q8SY}i#U&ps|-Qe4mB)i-+eD$Xq&$1cXR zcwmU$wWQS-Qm_aWvwIh3Gik^CNWv5=&M)yu!2XBlFtbaG=Cf;@$ib6OnHx@8Q-uwi zNFkh?>U7=fr;>1r$Hl?bC)AOKpPH<-0CXs5y^9)e(Q_iqA8y~iy}d4}Y0%)ov&cAE z%w9!P>0g2ld-}17o)*cfqkUHVZL1CYd-XO?h!ONKFpMP23YqX&J~;w(Jqb%eTD-xZRavIM~v7XCT$|A+?oFne;m`jpvNgEgUL5EP3T(}qm|3JpO+b8dH zrk(r)M^{>1!Aqsuuei;zE}D*&I}I~OedIvo6D8zyC+CfIA74HnW2f%r-282=Qq80B zW>Z4Nqa=AND~P{Yz)%cB7iS^3o^3}tp^>rH*yl)*`N<69j1fC=#b8bTpE~r5~`;KI1x`FW{4jee(gQo_pnaP*< zVX4GGAhU_nT@JaqE#okZM#4h@ayn!i-HF2U3O}wu(maNI``??TP3NEXmG@BR6AdGQ zp&5B&(f|?mICt8_i8{i;g3lL9)@hC?(=5NRPsFsI!e)eN^uCwFZzHS^5I`Fu(LTQjJ?lzg!W5I7sa?7lh{$%2%>*w#?y^FoCxyahg{KODrGc5&gRFYS6SCg=8ar@YWMd5)ATB1M3+p7&o^~~)yNHB zHQOj<&e)Xb#J9q~1EG-^Um(n4B3nzi;NPp@Gvy%-gU|chjLo|xX&=tk)1yhtwRRbmmHa1KXvo# z9So+uLUcK8Xab8aEJyj5?zGuuGW(J~=F5cAiStTrct_~blP^ngeo0I%Ohb1YL{el{ z^=~7WL?Wr)?PDj09~tGp{Xe}F!1r1vH}?)55!ilQi$7~t1f|plc#ljjpQ82q-aUKX z6KVC^_5Hqr?33E-5>Ny2cQN5qlY+#$|9x+)1^|^UX!bIQG&@9v{ds(|lDGr_!ZIH9 z@En?tW<=MO#(J@cK1PuC-xswV`XHZ`{Zk;yK;xZT$i-yu_vY^$P|<1bJLSlA%uJ`}WP7 z)pnBduqr!UJ78&NKtKL?$zuZ4qOJ@8J1L{oKMuT5eORFX)D%BcQwz@$U@j6v)u*PY$xl1WDqT{!!-F|v9vQGQUO zXb41wc&PXI`z1bvZz?MdVX4Yrhf(Sn;I|T6i7Cc;(L_OX&c%QZOr~bM!?jvwd97#P z5BCgub}lRy@x~P*lsLPHGeaHe4cfF3(M=Y*ZMr*$b6%!@ydabPuC(+r%g>x+YPO>C1pz@`3@JsN(L6 zRF`C0GW{{>VG#OqvsX94-AKGc010JjuT67cMT$sK!EO{Iggj591RZQOl%NY5v6guixwS3>=H`Zv5vj!+G_sZ zmm&>ddwc+(ILa>L(k}sTs?&a}d*JtBTjC3O;ItG^70|$xj=zXSvFF$B5lWYDL}BRC zD;uiMkG(TePVV8G$dY$HkC^n$g3Vx_p-Hb^AO{m|-#?uR=9Pn9IgmA0>JW;03M*?Z zn)IHr&)rL^b2x5@TUc2;9bY5Fd^Go+dfC{nUArGCls+y7$0_tfxbQ$+H$H#)astJ5 z(KQmj1st8@^{eo)pg(0pgfmm)#OVWlcZ^dYbwky7HJ5gOEMbUun&Y^HgakKF&q>?+ z_t0jDM+T2v($k#vo54(>;oM5G<+<{cc!=QKg9Xx6oP}20veOW@AasWXlIxkqrgMCU z;1uTG4>ll)uK=#WO02?{ugj-@cgyj?Dh9Llme7rx&-NGf!&BG&`lwOhKk14BqV8iM_ zz*;D)3=6~D-Q1oD9pEVAguF4CS%rlvOH)QzqyCI!g{_G&qJCqXhz=)wkHnrQ^X=~> zTICv%h!s^IK8PHtBe5p zM0%s2cDwsVWFa~%U4rGu4LJb;0dklqfb&ucXo3)A5%aY}479;Zy)alhbM~xw%$&@_ zh7J|;_S_C0%*1{on!9f#?L^^ed9Lih_RVHX3n3CAfdnN%W3%eK|)OQ&f6$&Rngv&t7 zLDctR$qPUNdX)X4I&U5bwPr5h8o}oH#iyxkva1xw_CC*16p?*A1)KT@3bt-tAd-nI ztf7af4}cxK_U+q;`107XW0%PAKHz01UPquiTfzi6hjUh>-nvat0~0WTrVPU~R2cWD zeE+mtKriG6G;&5SV-ai9#b^(v%SL1UPy=a`>9ef-NepBu-hwY6lc=94d5T8HOlpuQ zx;6V%QCPld-8p0w{z=L$->pTVH)_=5k~luih@nI0kXh>R87QZ^Ax3?ixcDu5s_7HOnI(JlW|E=(IAj>$S^sQA!;}PmFJ+2HdL_eZbN#m1XPg| zT>ryFed<&L!HOC7{Oz|3m|dXyVJ_TUSO#56hRSAnKxm$lO?{~qs?W2^P7RgmL%_+^ zjE~eEgnt_orTe26z@KO~x=bzDU7n57aoi!HWaFF~um2c8pzLHImK>$)hR;f3^n&N~ z&-?Gqa%5b~1pCjXq!no;J|r2v+MR8Bg{9*aV(8m-mKrLfMb|S^Q7+KjCYb|5Wx%gf{$EdH`r&7pj z#7T9v_F;wMOV+@7YO`_yjo#XJ{KSc^^#yteH?lAVq`J`J%Cy+EZ)wj!p$U610t$@h z*}!wJY~Fxd!HSYu*%6-^GImO`B0t9JqNuYY>uam4HFyl2)5mYhi@5-*Q(~7+Na__@ z&qHP1Mw_H`IchcPooIaV>Xn7? z=cj!0C{<8nVTf!`lq`HGR|`6Vp$#5QucP*HEpTqMJS3x1P~wy4YSet1Fvdh1F``2_ zZ4A%JXDfqqW^%S-3U}(}1#dP*LZkABk`kMGrEy#*N#|2~d29i0&rY`=H?Q8mMhvhN zV|*pssEe|KRJYVF9UQ)l(Jm8G>||TVe7WHP4E6y&)o0uqH_k^fdW7J&kW=-F`h{I| zxjLm6mQcO~l891AtiXmv?|M!!-3@dR96X-j=eTB)F&HLem{qSn^2)cLsS~sD;aQR< zL6>BZvJBx_Pzx<)DV;cXt}s7wd`sQA`1q#$#&m82*vA>B$6SDnKP~B-?#9YQ4tuqu zv|NdOB{0O;S2aBs_%N8%qq$&Xm)KYiCE;Kd+`Oq3(KQIJwsgc-WtLuE2C-vtHh*Fj zSr32y@Bd_F{|+qt@ef)Ux|#_|Arls7KRl@rMlyVKrZzVBxhxJ*f4@z2OUyBH6dzvj zJ*SJ+^96nN;#%^UsKF`(UZ=9HcfnTvBr(Jr}Lyk0IAp0E(n{Z@1X)=zPyK%XNxH*OWv_08rApjNWGd*pa!29D)l{m|l0nMreK|e-SMnl({`=6AanHf@a-d94=u1ID zkh-OBUI90ZU3VC*=%sP{1jMxDjCa*18!2OaOAlYXXyOriN>fwQ7F*>0lQ-Y)cvM=< z{_dfYz%1QG;7>hXwKy-MTS72hbm2XtdXHG)Li7{i5i0L`lFiK-Nlq!B1mCB7=$0b$_FkBbCF+X>8BUOYx za=F8#UiTcn55uRBTsg{4W{7H2OgEH;L}&dCE!zs6bVZ$Z@5{W5wd9?i$_?{3#n+vH zX6@+E@4Gvn=f0;*70X>IFTKbh(b*~f+q=^}UcQpz zzHniWETzeg6fP%7!@>3Kur4_+W3h}djuCV8c+T0wq5 z9MP7qvgI4~UlDvlabl6K?IoKO^F<+-zNcE|$&?3O_LY0D;Wj<=bjQm)uMsR>(h&Lg z8aoWFHgA3^h)&=Qefe36E$_o$Brfs3B@BGkt+vgZXme`$=yVM%bqfCN?T)(Qzr+h$WEN!+`;WmPNTAgn@j^TAmO#@UN@r|mF;?x zWCvhS$B)4{_$F;oX`gqF)#$^syhJKzy?C9O;UwuU3`ByXJdC6% zlx)36?c^CA@Y_O|BMv=GBu0}&xYr42mbDcT40ZDRU)P{p|G(4FZHQN{by%Y;H~0D| zge~o~L?%To5LGavK8QK=i^x>6K|i!`eRuz|#mWjF+4x$V$we==ItrCX*S86!Gnx^^ z^Gi7*C%#G$1qro325j==f6q2FCPh%@iF@HaY&v5Lj1=N)y}-prM(#U?AZ7(*_)|K1 z*Pm#j`*;F|@YL?veqHS*z9}lYAkJpML5h*_jm^x-L;G_Ga43I!h463{7)=AHmVBSD zE4qP<{rG5rDvB2S7a{x-7`_u@q;|fvo|D~AaB8q2 zbHDA^glXOtlH7G*$UQozve=G*!;28bD!)RK+@oCknAn-zeKGQyIVe-y>#S^Sw5S2Z z4q(ppQo;4!bI|u;0g7~u8DHLS<6pgesZV!noN)HYwtjPlK{ZctO8r4eAuRaTQ98}j z&)HCPb&6v^2w6Fnz0WT#F<=o!Q*4OD;M+V|zgQ@a4;-nP6wP8d78p#%>iExP z_j(#Rj!=X%NGAd0R_<%KQt`*SP`iwOOG`b<$(R%03od(Ea$$TwOG>J?Kc}KZO zpVORG7gIZmhdGKK8t8BP+>4~S1~Hp2h|Wg}?BFrl!g{?fg!{ z@Elw$KsfOsdf+6vZkH~=ZhlWc8{%vP#ue`A{~6pRRxWN^6V?AO-_(1|5si!Y*kr|k zBf39 zl&MLro7+#klbx+Y##Tmr61V2lr%xMP{)oWk^dd_}9A3*{hC9lCm-?m&%C-3HRBeuz zOpp$oc?%4k60m;41D!dDV;~9*);&~dGhVG@kFtBgmM{JR=)9OT%5@6I(FzFYWt4z>Z8F3Sgj3k z(o!6J;-PZKzU4-uiC)i`J+954t2Y?+H73y7#wL^LJGEr-7T{<6=s1htA2hqv!kGMn z^v8e~(Bb3&yV@9Vpp!2qZAQ-D*3`Yvm`zIb6sBcMQxlhe%-6ZWoXNu0`mXVDH*cPv z-gC^n$sY^Fm}a`n)j1ag^~|~~;R4F)0vk!ktJ!Ec0SCP?S8# z@ukjPk6BNC&UWesw+-s=1mex2l(&G{k5+BdiqIY?4hE6&33Y)W{5c(;As-6mz!e|_ zeMMklX65=+7sT~#EUvT7jfE}m8lqRSuBkZz)2>p7>l-cA<%95^`p(W)Qwy6GVy_T| zs~k0n5o+v<ZkoYJ<)10Yvr z$BxyYtWvEs>J{v6(Q<2~uu^yUFicEm7R8WYxB+zpMfHqKr>5zqP&tSdgA`IMl~Vz( zBZyR~$;q?DF=6D=WZQ&=fMAuuW}9zTjeTdgl;T?hV`y*zL}?YLU5JO&6!k+HY1|7k6{HeV@OCtF*|}N9ip^f2nRHUMF-% zz$}(0Ja8P^83AxJ2W!}{VIyd(MO}GN^W;FW2?k{6w8QU+Wgz4YTbeR>H8vdUgVw}S1>O4XgqtX=s4jit>c}^w2u)Z zr6~~6ISP<8D@(iU^iMm;N<|TSg|e|nXX%M2+2ZB!j8kc41a+~hxAXG$4n)s`D(?Y* zLNz5G6n`}w_X}_}O+lUP+c$5f%de^hz)J!{7agkTyq|Iu3ILD4M&eu0R3OSFK_d!8 z9GQ4hf43m2;~$F%SXN3}+8^O!IVmS9Of4XzjfDxL2%Ve}4@n%zNVIS<&?4s%yOA?d zOKfC-0@48K+LP9zL0?czf}6vpjRqorY7J&2FBecSj2h~qLEs2

9}GygjBcH5?r>#7dtix&Sy^?J6v{FTb=4-ds=8_&2~{MX z1SF^?y%c^IFN!ge46J1I!Y+qX?NqG5KSO?t2$#D2*1f1O80JD)EMNk~!`Q zSA!7b`mci3ikg%Yebn!t+VQJpV4qXBe+@_994g>_en}86+$7ZbLwfc+c{9Zth??b= z*Q9iX_B4vWWlxP=U&6(|M>+??p+)BulXF~t%suo2tUL|~J?Zx4IR}0hj}(uJix5z$ zDJkC)Tmd6=;Z_ov*~bK2y|@4?gM4tJBYn1Tow!0Ivr7S+U-=EnR+MMQ7>&7M345XWv= zBUV2^(0cR&_gb5{pXLO>%o7r!qVk(}e7uu~@}%Lz=Ms>{=_6@nJuxZ6;p? zaA`JQKI%77{5Fu+qsATv3nV*Y#D|UDg1JUM9>()q>o@fg7&SR44yf9o4Kygz7qlzkEyL{QR_~>#7nF>E(Bp{8~QqHE;EIFWF`5P2V2)%e%{`Uq9deFW-}pI3G?qtL#SqRlYzq5#4)v!AF+ft! zP4hYT1m^T+4zDz$=#!{RIsEIl=RwJX=PT^^>vId<_tfYwZ~w3VJiL|#o&^`;yT%e6 z`Qd{H;h*Qk6p8`uP+Y^yIU|U?{=t)f<1f@df#y){<;hzx-51xxtl;V5`~UWDhwX2F zt-=5N>E|`5T()f6mRMTMI@6#9vDKnINhP>g5vnFMCJ4Nq15a=}$+qmd@s6AL+wLt-FsX|lgbX}+X{ zVs9|ExpR)r6f2Q0CXexkgAc(|IoDxL_ z6aeuAPW%47c~RMjcj@QE@aTe?YMd9|lOf3d{b8pk{A82<^CPLA6F~W5pG#08)xJKU z4_{xobch)j4Kn z0ThPPBv5>$)3#OgZoC)0Z{@!H!3>SDXz+Ip_=`IG{O$j~26wD3rc%#@D(~cQuu+ZP zcqC699S&{)2S_dOO$m|Gz18I}uO}gKyRbT}{%_y?=SNmA;p9h-tiQ8|4vIX#EJ=G%jk>E*K?PUlp;!r?xkB@nIiFO+Zb=V~hI&(iW zmKh-GTTIFvAP({K(T)z1|NJbmujwZhrPD9JNY%>z z`2qj$3%5b9cKwCm_`e_i^egLY^dCKve?RD_Km9M?9Mk^)@3;Byr@_zOLw&>F(DtAG z+MnN(o%oX*_~%dm{)JTE|NglD{@W4~Zk_)Iv;OSY{`}s(cKQKj#9E7GxxRLZ9b`=_3yp9+w^(i zDnIo`_?umEt#*$XYQ1;ZQ0rckjuyRfJ#fEmyX3_-F};Tj9=&ns)PW0Mj2m&MgJs65 zZujlXXW5%y2sxH;M&(KG(psql51$v*mX#i@J!f3i7?)UNFS{Vtw;ZZu#<*i&{vwL{ zk549XSknK$Wg`Fh#i6s!|CS&A$4jKD|JSql$M5~`BZ%AZ|LMD6qiOl{Us`~FJ^TNy z3vnm@cNhM57yh>+{&ffbw-)|&C;k@~{OyzQ@(jIJsm~8Q*h_Euw|w8s4K#Z5+uZ}*i;*Oz z4$N?D{CqO8A7tb`T-;!b77xr%X7p; z!_&4CBzAngr{J6s5I$+}8w7Y&5qsmm-=KNrJ^(F!`^7RB6Bze8St4HtK`{*J;IgtZ z;qr>7LN_4+0F*TCKgt@&+P%uD%CY+C?5@$LXtGJJ-m9>EkPzgmwfpfg4!d&Mfmg4( zGo^3cx^+ToVC|^aSo`6q?R1Irr!CCQ#mvs@BR4_rJD-&Q_|^qpb@BkjTy}4d>%KuP zp`X&~{nkEx`uH(doloHhMJ_NXAMv6p|Fm><=V@v_w-2kLtC|sf)1g;>LqF|i5gI!@ zH2zC&FU9p*C2!x_ZgChmlCf2mT?X4FqAs%ED6bjr% zI%oTr$a9BKQ%wX9MS{Ci-pRXW9OPE+$5o?bW%sk-yQp=z^l@)bqx}ybKAafo>GaV= z7|b91{YWR5rgGzAwz#{Gk5BMLiF`fYlt=!@!?d)Ead8c|au^;kmd%%$n!4p`Z0r=d zkt0#V_P+)b`2rct$NZa}_ZC#1vXu-bhTCpGQyS8+h8p z_qHfarNw?jkHHuXQykk;{!sar+DpZGyP?%R=_&I&UV6t3tByR^6r8`EUd^#g<&xyi z5|G@`(A`C>5Xs^hkAcsAF3sAQJLS>P(9o$s6A{27^G$gC2OB3UBa!US@5H>0Cgg4u z5p~U)HB8AJuH5utX$o5C)dckZzBS7n&$7Es@YAs>bgGX&6=oPaj*h_rY={1z4Sy`1 zf>#9F!kWBbL=!3de;YEjB$g)Z?dgTbnJ9ao`3ciXVxML;A^q_v^>rMLzUkg%Mh-V7 zgarf)U%!6+YBY3~?^v21@Iv?K1$+~5dZQaHnI%)lZT>Yi%l;Jf;#kJ&$;!#O(Q~GX zR%Uyd<~g@?r%G$_{eDB+F4J7TysxLy7rmSIXmyx_VtRPLq1ZVnmG99KAU4zx4vK05 zbJWYe-!Mv0H8Ck}TP28iFaLN*GEb!Hib&r2a3eI9FKh69jpid?rD>Rb^n*{Ck~WzW&F*vRA0}+@QZoe7hfr2rf2Gh-!gT2m>~eGJI!nt# zr~vPy-nfVzr>{d<5X0(?^R~XS9E;g4tg(bp40tf86)z z-xulAq2(K6{g=ypXH##w&uYnioN}=D(MN+cZ4M$tm`WF8*2dxPu1&{I9v^Ul5_Wdp*66P2*5ZD_nx+GcIC>I)pU~+m_54MwlEq_8ui$w?QcgKA3#_=8A+hE z7rKij#*Mi-t2wtVA9|FRm92(ztH#vO%=;hw$LnWVFJAOYY{&|Gb0e|Ik`MiMLq0Eg z55mU;ZHIV5P*ZM>Rq>P7mbWpVO5d55wzev#1w_n^Zw1G7je65IYLe$r9)g#^o--$Q z(hPA@L8WRt<9N=V*%NAta6m&xCwrr^)tkGc^(TClSvtLX4kx1D+)=FEw<^jy>&wr>2mrT?dIL-k2^qz==49*TS;uW*y#MGA3N>+?`7rX zCQygh;LE)gEp+6KZcQfM9krF;VNEFLjx^&(`XFAqp*+m9iYPzU{&Pw2McX(!(#F&J zm9dfWM$i1aZn#uTd+J2uTBi8Uo5OlPwz9;77o0xgNmcU#+rCD^a%C5#OFY|(fKCtA zRD=$3kZvomUn}+jeLiPwqn7+XWSx0Dm+RKXHIFKdqKRfyBt>aLr9r7EROTicP>M(? zMMCq0RGQ5yJ1KK$rU4DgSVV;+M7-a-ea=4bdEfpxpS|~~{C>}K-)mj#TGzVP+Ue~F z&-Qjnl981y&P{A~yxTA-vEgZb*_`SAGvysDP*9gn4IC#g1~o+RV0_6-F(OtTW#0A= zZItKu%49!Rac>)D5~DzJ4{-FEuf-))GKv}!5*j*gN4U6|ObUKALKekr)0g1f3Wdm} z7_e9|h*SOYT=T7`Vo%*6mv47Qj@;Gv@2@hNA-P535s5$9ZRbJ)-`(+3cXvmEv=ayX zgnDKb26L?sGH8=FaC@EC#l!CMP9o$N_;WS;6Xw+*E`=ivcj)rqQu}l8iZC%u^4BR z8MOS$O)PTnFsLl5mN_OV+%ON}P@tSp?Yp_e`e`KDu^Lz~o&)vlDUZ?$Lx5;(zXCPe zNIdWzWc){%;xwJyS;Wws-`#IR_`wLdXE^(Rh|#imICq zYU;-qe*D>gRWj1RK}7aVs#Ca%VdRdBLejynQ&#|0Y24zk-Nt=sSJsiXt>2tM&chPG zsp-f~g0Ej6U7P4NB6qm0S12N2FpQMUged^$JbUl`= zp(uTyh#^WD%1guEGnRoJi{?!KwOAyzkrf|HOEVB9ynD)Om^5|lr4|OYk|>!S8TLmS z>DmMPY!%cQPT%g73d9Krby_4+=jNRiD^$3SP6h5A27LS1bDeR@f$YdCLpL^Xm+Z+E zwt7V6zULaAthK~>MuovdX=U>n;$oBFittpi=Rb>(=) zXqPvlx-`e!Ri_+>oj*2V!i4*q-D(21)e!fGqX!A%7Oe&dm7X1Ed}A0^EpH#Lmv{l# z@7I#{VP{5dY~n77T`_eLOM7d~rsF;QqV?qsbF2?}ca$=78PIL~6t4{<2e?>|80v9J z=fSm6p_eXRh_9&gob$WtPUSD3yT4o=rVc(-li{35|1PrpI!8TH5&GS*4DNx& z>nIEM^m-ECz}kn8ANSyOiEkyA;}$#f^7pdPjxi0lGqaClIVG*SrYoZBpQ&-|NfO(} z2pO-Itnuif)lu$0|5H@V1z>lsHd|jWXPh~G`aEcN-G_S`{u4KL*3;9w780U0Z(e6U zME$Q?#c8{meRzQ0GC2TWnO~zi%9X6pYbJseR#qMYd8FRIf9bEK@*b}a(}O2o?^*l) z;ll$vKF^DK@nQ*%K;yqLAk>pXp%y45=h0*WPJfO8l2~fW&tLaCN>1!f0Ak2bn$(Y< zDZUzN8GCed{MP7nX4R&4?b;P5sv!=hIPxVC8d+Ij7`@EEpe?G=FpL{Y>czJ!Y%lqB z_`koVZVb<3&ygci6>f9qc4E2AVV1!nz!WQYW1Xty6Wo43+Od$$&je?8=AjJdJn}ef z#$K~_-IzK3tb$IA%4Y^k8Ib+$!QA1V+iw|m7~neJ7OzExsfl4>T@h_3p{6IhcEcU& z(Btmz|NW%Jhq*oxJx&@&+Ne&XAh4pr}{5b}xnON+L{+i<1aEC9_HZZ&t zV)-N#D0iBBbdH`8~spZ|<+n??9#(YXG@Jd)G#yrd-F(7poUJK*eaPwcOmMW!f{0 z`kK{RoVQ2zpYGlNSTVu1-j2$s4XWlow$w#j`ytHFnbgZ^_6k1Q~(&6LyW$uwOL zL~UV>q}Wt6d|9Y@bF6;U)?SE=>`vY1y<-nYNz<)Knt=m!mkL1k>Cd)|^w1sT_G|UY z+^8#;E*0=taDSh}3mt@Bc>U9zcW~06H0jCH-^U&{&o{la?h9EY8@H=cg{Pp<=*{nM z|MDbAWvl&PS_nQAG&+bBr z<&$weG*o-ls(}oSmQmX=&mN73F`>yr?I&2koLvD9iR(}fQ@}_NMH(%BgUTT`y8Za^ zL&tlOcov(ovP}PZD6bRVXGy7A)5 zIgcLl3P52f32i++N#?nv8svcjay`V!d|R?Cfp@uXP>y*f3yTueJ>K=evZrojZ?i! zBaab8StMzXP;_t(5au8-McdFY=H&RH|NShk@5I|+hLMqUaS$JksUSaUpH6*Nn3%|* zjnolkF@v#fJ9J3R%6G z92`WL;9*E&=!Sw(hGBrc)H4h8^lYb0T{v1nA%%@R7#hss=(d|?MnT49@FbEkkmDH~ zEFWaW4*SBdUwxTF_YRD_cyVZE)(8IkfT=qe-5DgGAr6CAS=mGFe-5J8Kt7~c1`SwX zk)cJ9>Vxh7)&ktS&`N@EcIAq($6*s{J72);c}nxDA3yFh5o^zS!5oWXT&nIf=P1^7 z*#v9<=al{HpLD{QoZoHVLq;MtF3u7V9k?p=Uc(#8L8(4{(u`MXJ&2se7oo^Y#s;Lv zfB`i0Q)K|8_4eWKS{ZO+uE{}~(Oa%O9DMhjTwWlL(Q_yr;uS+KwFk&$IeFUvn-1IFyPxbg-aeaB@pp~mu6@K~RgObe2#bv09%8}~V#5NY)u!~BD*~sgK z3p&ZErRC?Ji_SS>V>H~1#j?edv!i-yh3rKX%t+uKq>{^Ce*^DD}gA7Z7 z%e)X8s^~zX`Xxs$r69A+OJu(yafvC@sYfDSa*r``?@1bKviQx{I6ZNr$=Q}T5tWb5 zi2JM8kbiE1#Y?~v+qyg)rw<#}mguZ@n(VFoGAAb;hYvffYNTalZw{*a`E`X7b2;r* z|IZ~7QNHa`_FFNMq5O?8jgCytgq*CH$jU_qC9``5^!RSe(z|48;ezsVlh5{qh-6@} z%_v1JUA-yn1kCj33q$)X>GGz>SS0h~BM8;6>_(8LF=_8&#}*f!p%s($to}G=)P2v# zB?bn)p~0lC8W(BtR$6I51S@4Bh+T{`eaPEx1|BI>xT3!!Gz$)fR5-VfvT5;l6PmQ>AsVxBD zszss4D+}?rW%|RdKqeWA54~7YQSQL;S_)|4i=@H?oFk^M-z%2pG*X3be;8L+tj{%D}d2 zV(|`kopPIp8Z4OGH2L9!2g(yIf7e&DIb7aGjt}~u>vMp)uLS8~m)j@L&sCOpg4RWma#JbVm`1NggAhKE=T_jomIZkTUx zta979X3t;V4a~W-gWxVQ!$u5dv<&6{aur} zQ2p!9vGALAO0L~9j~*!t2|aj!xgR^$>qFwMOs6MA(&u-#Zw3**vfK90CHd#u=ze6( z(5Q~|VyoOstE1F=mXd){_rU>;|HjmS3_$Vxem{>_DmY$itzSPnbH}-9J8k3w;vz|o zfL;b>-AOjDUcEYh`EuIaFJPCT7^Xqetyr4Z|4_@B{rlSkh-N+WwF_m3fJcwyK7IQZ zA2+Ti8nK{G(#kl=6!wo~s7dD>qR=5hS82`u?~lKWWGg-@ zIa%QT`SUAI4H%{!yWCjP7JBesUkA-T%eoGbPse}Dzq`FV6SIF})sv2L*`KL?eJTu6 zO{PL7DcW>L(S82<^-l0wM~YdTSV&(*Q*12r$ymE)&3D7pkc$`F3>-M{NX^i^kceC< zIh}hGHh&nvyYR^zde61t=rP-}ii(Rwdnb}+_X;NJ>zf)>sQ}dPbQ2HULtERW)4!j* z{7>9ljp|;vx9mK0+_>)eWIHu{9}c+fF?jGH!kj?08X6iB0?3KHoR!ssl4TC0t@-NJ z?ch3|eeaP&JI1c&lb+73S#r~+4FZ9|4DRPhZQZ)ne8YyW1QMeaE3_9aik$606{$t* z0Nr>skx%4&0ss@TS=rgMm_q{zyORQQchrPp`i`B(kn!VtQl)9L+M21I#rX1piPx=P ze*q?G-J2W3`Sjw0XlUFqd3>9Kf|QZM9j>ArxVN*hFB*8mQ7|*RI)~^_-=@=XLr;6os`%ovPeE2Yk<%8F*99rVA-qtqt-8)$j zppL!0N~RkzZXvsF0rdx5!{54A{^FX#8!9xLy^;^Eq$=zfxMMWBnLW3A2OF&Y~E@lkjBQ6`0b$C1;gy}&vU%QFaE z)bz^Z$M1on5D#wSMoA*Zg@_XY0gsnn(l;=;@AL&=a+rVJzCl6f|7Iket=u`w_ZL0) z+*xd7VM7bp+<2`aw zPyx(zA4e??6hATSS})dAYm;4sA%=|YsQ&%>c@nvb8DtPu8gtnEtd1PCb+)z_>4>aj z@`%2dBpkTR!|P+&Ki`^q#fv|p`mOQTFOb}kV>_a-UM_~xb*lL!$ysJtPYet;>knSi zJe8R_tJ7!BT%eZ3pZkDrZlBvbe7L-#JY{$B_4<{=h7B7sWr{3gj1<*y#jRguXIq$? z@20(To@-S`f2pgaWV^VNQN;bdFoXAY)ON;PRhJX~U=h(As)4WOA=-esk=?bmwNIZu zodoa7ItKUS#|>gi43{j~1LH2oeqh2%2B7W8(W84<^(soySJV5;%Ch%$0G*kB*Y_{V zvq_3|z%nmL4J39%CJ33nJR{1-Z?%=j`Zs5K3%+FbButO}`{lWQn%V(F9|4Ajl=hwKICo@ee1yt=f&XhOM=(&?I zeH`qhmn~aHDL04m%*m1$S;V}FCM#~4)0m-#%Bx?38seIJA^+^3GynELo7RK zwup1Kw)1OK2ha`eKS6T`hx%$6VIXGRLcQjOix^)A)$GaYLN?(C}qn*B*VP zs(%z7SmMy&>9avq#myv1js9;}1ULo|lte@}%jj(4xsEl!c9m*#uRSF)rqFDuD$iEL z!HEEQF?jIVu&@oYBts)2#&X~ke|7K=2)Ib#%RaeWZ+dAJbmidkvu|uB(D`n+k-Q2*-+ivO6hVQmW)c7R;Mud;&;r)0S6?99Pn{a? zJ?I(x;vo4Vqu1W-czprAb(UIRxq5Zzq)Dfa4XOdXe&o6HmGLAtXRP*LCJ%cQrqH17m= z-|Xx4djS9nWx`%h&y4O*hxO{&GYgCKpkg{+_Tj#xslN&qw1z@B?RyM^=op7s7ua7vPyy_eq4# z0Ty;z8@Ve2L+V~QifUH$?c0|n;&w5nE_@nwy8?;g5BMSY;X%;Ru!rK<%2l)nv{_E0 zU%G7gh!OiEi`GekQiuu%i1WExU+vDzDHL#Kb|Ml(7 zgSj6@o`a*;=SB>qmTU-VF)#Y`X+PPZI*i4?bFR%^MBtCQb*p6kJUN>@Qm2uL$@jdL z+*hye1K&J0X(D~tE$BNs<>VLN8KvcAW#$lkA{Bu@J>uL9gQoBD111#%q+QQh(M5$S z5oR;f!vg&M#}CTfJZ83|W$2OCyLo8IFJ9bu8*!HSPfsGMTj=|H&Z?AOL+tX9bwK=g zAgaxQOrf6ec$9hnep}v!*x3stP^@Yw(m%v%#=WRS^6okJ0zQEtJ@+7H*AX-44%OxpL)Za`j3zM)i*R0^23P1Qe~p6pOTi=4p7`P z_K2_gtKIfvNG#Y0YF3OGF#_^8ILlr!^i)K*rkqC(wF#L&P3jwdH!vqn5u^oimLE<0 zp-S(HcEDYBS@cfV;A`O*;(_V0+rG*O$OQ}kebv>*sbn|X}bO`!Fa>#9SW^dYM&O=O*^X$UWvjOJCH%i^UV|CrS2mg zFkiD~S#gN&)clUcq7yge zJ*aU3#4kaDIrw{9c^!o!r7MxaY1&f_Man!;k zJKB4P{N?toO@2ZP^gzn5n`JQGM9_Zf5wWi}E?YKONx|)0ryiwm!yL$^^>labKRxN6 z;G#PolrxhAK`%jq$=`+ThoBVewjojRK*+@W?d&S(!am3#M-0-~Jw3lHc0=YVL&dEQ>j0$j~kRO9AM_ za}UHM)vH`PxY~MPxwM*s0|`-}Qg^qL*)}>1 z-+pm%NmjkCy;e|Bk-~Z(LT=V&Nq?vYAH3F6@RK-id6SX{E5pH}7m#1tq(C4fn?8vZ zY?P*Jz5aP8$HMr7ifEtQNo=&reV{Sr6cpxNYj2%&yY8h3TkIOA!Y};Ncn2uJT8l~I zw-Fb+OpWSSzbx!KIEE~DFwa0nf+D6JE=s%`Iot~i4rxrEo>*inR+L|uUiaVCG*7sC z>6<8B3)u$UV4U8~f!G9c$`rBtJ)y(TPbDSh@V8MnZ>9q9+ikLLBLQl6zk7EI@O+qo zmCRr18@dnD?4jOQf^Txz(L8dLeJYez<;)e-5mWD}(jg*ur(>DYcF^lC5-@WjPs1^& zM;^ijMu=1j*wd7iWGWH6rL-GOj#j{e3o@onHd`GXh1q1zZ?>Ft>>YXjyaW`X7K{Y@ zl>JbjbrN5FA_U=$)ihBhf;64ww+6n2L}`r|2<`B8El7#%IN?X!Olj;1)>e3{>FPan z0~)A`H^ST6r`yJ*)eu$HUZCLnG>GX0sT{F{u1Lwv?I%7u8cFfdU2V(Ihia)$p4{1B zk;!s=@;vS1CwJJ}qJWKxip{H0?(M@{&j2gAy1G^YKZcAQ8{vPWml<*6^!U}Cam)3C zC@Y4U(Nf#jHX*y`2Z#xVf1#_Bd7l~fe`POPVmha2$)9k1X_KXmQa1_gK8h}#Id|Yd zCyIONy3_v2xvMw$K7HeM^*?t*{FBW*ZXR&c1CdLoy!;UqiWJWGX+ngIzWa#uV3=oi z4-cXlwT2KBDB`3m%rpFzx0dMxt82nWju;_7Y?wz)Mf|RopxWjv#98WzrmJ7uKmz{$ z^*FCsNl`JCX1mDl>ABx9=Go6jK2>RQ`qe9gCafG&G0m~3)#KjylOHAr1uN2KpvIQN zK|y1)8YJq}!stAl_|`@C)Yp3i%`SZ+fs?c*v_ZAiM7%DBEdi9y5Kxxbd4=U)O*wig z$o*&p`d35}gDaXLh1z+K9PJpOHgk#1O=90cp|;|u$nWw4klVxXihd#~w&&=R>YAnc zx%!f}wb+;>!!D|@RZYB}{QWy3O=3EWoIy3Bwt7^8xTjDG7bp)!HpOisYiHoWg9k;0 z$E&tvjfs41PMGA;ACuv&(@8v0G*h@3t@DHQL>Cd?P#w25ttB{7d%1WP48M!^A@%n@ zH#ad-Ek@dFH4b z^#|Z(S5|0}e|J|4s|}00|B0|QufO-%CcVT^@@cG};(}B(X{|*Q10pz3Gb;!*h`7gs z(-f%bfs@oBz?Bu%lGdbNO2jBYLbQ4E9ho3YCAmuG*`-a!@jHYbOyNbPD(^fU3pVxZtBwuhpJ&_I zDVN50Acjc8zf=$l3yYVHoa;RBz|!!9*xbiQ<3!oWZ(NhdmM+P)xhkrvRx3-3G`i1C znRu%X%1bTqA7=8;^Zwp~7f4$`g=@q2S!L3+!e0ZAk=8u5SYxONv*+pAfo&>&Y{9u; zdHm!_(pv}2xC_*$Gz-263fsqpVxg#MhbS4s+}A@HR!luxp0X?z2gx7v^X4chDcvV5 z*wJ)L7e!k!T^&)bm{ki}iHuo9$2qX1UymNUsaI}(SY(~Ltj(iZyZ7bZiVGxdDd0$+ z)urclbyjOzvli_^5`TfQnd}}BE*#UipGI}UI7@$E)Ua0@b9z(hWB%2fKXl~q;iQ>s z@}y3c4k(uivG{08ZHy!4K1cxX-XjT^q9;X}uJLHWc(R4Orex z`eH$_TP|$_t#bMQbIXTnLL|yO@t986APun$>cr(^s$af%q0A-&`suSvj40WVP>$u5 zja(84aV09!i3_%JmBiT}8SS?1N7-V*e?UhCrpG&0^kvIO=xV#ol>2;lID>HxVS;7{ zWK9oSmFPB5fR?xi_g3;0a8k*fd+cSLHS*>#7YhG~J31ly?<-dwErCoB4215i0o)YJ zH>2=(>q673Z$snnwUC~6+qTVyW4&ts-^Ylvq_$}J@>7nLC>+&J&wVP6&fmfx={gd} zc6a}?vvhQ5i5{p~)0H~K&e3s}laqRWDb#vk^qKekh$`@w`<81V7tTc4zuEu0^V=#V zt{T+$e%x4-Gz?Y~Q-!IruF4}>H-YDl3M;;7wn{Fv%wCPUVDIJ2!;4Mr?W146+JO|o zie_mn(v^)@msCJ)t89Eb;fTyL)GI+ZFGCwjHS#I(!zLMfjJeH-k=}9wk*-$&vUc=4+J)6%?31IXC^>%@4z< z+SY_WEH zBP^aidbH=}P1T5%cT!?EZ`$MsG!UEYUDL34zK>am`p$TX{BNbgJNbNLIh@4dlb;59 z4tXMRN07@E4?Pa7ppwN3F36Gk1}oFW=Z!GOPZkkqa5}D?doZCh=QYTu9TAsru3+yr zY2anP&qGxb&?J7gVK9KTZ z`+UmVQ+twR_8v2WC&~Wa_&l#J_WoR#$<$7athfF%Uo z9!E3oD_f>#f{IvUwUa+6kSXkEM@h*vl$J*V0vb~_NUk57Dr9^2HdvjyfZh~@i(tyL za&tXl)@guNR~OGgo>c^B3M++K6VUf{yvrm+pX!fYFbVha_5E$3H)~cqR3F+Db@1&H zfD!ApYlYTM0C5;dlt)R@nS^+#h->J1q4t$`HM`OdX|h{DDQ;nFdk|mLnJ}5Olyvp< z(gb%XE&aeG@6_3|zIQcghVPzK@4Z8fwR*fD3cFJl$*$^(@LY#GOkE2C%SH|MQGB#8-jzp@a8D zN2?(JqOkHo2gElSN)v|xoH+{?bcH`te7dv;B)Q?V`<6B~osp>B+W4+7vqxE3Spu~4 z!g?T2Lm7RuuQAL}ZX^k4kJXZOZ+ z8{k8uY;(sZR8}X#xB_N0)l~A4APDDLM{Kr@jRGAbLBNYk7T2xdoqjs9 z@dARTSXgnQK~VHBUL3vJ-+~YTkISfuS;Dlj1uIm~(&CtRNNU6;=Z%jt#c^e?P(ral zP*0!wMmw8ma5-*m&S#Z-Uu8A5eLyGV?bW;yZ~rRN9noX&piw6!=CbPV*DF4abJN6)GH+xsb+&fnXTHNsLKKm`jNjaE(Td~TvT z;BW@Do%plVN80n}cR^jEk02kZi1@Rgp2}+SPWZE;{qKVXng@8TkVNZ~`b0teE4yu? zx}9y4ko{N?T%z-H;ewojfx!@@SnRtN#{qsDA`4C7Od13b9c>@EKSUm*O!VfQ?W>ih z>Vmi6In1rC4L)bq#)(mr>Mi&R?sPYs zUP1{Yz2WVx18d`vU`f*hqQfpkfee&z^$Nt*a;_PHznKppTQwa!B~ft}R92d;wvxS) zuffk&W?gQOBW%yJ{V6$Z>5T{h48l`o6%}_r5_jW5aPS`Sm8`9YOr9(wh?g#1tagrj z%wI;|+YWFq+Wu8@lFz+$8(2MmrETpQFYS1x>vt7oz23740z8uABP{`z@We9NZrd1p zp~gs`y#YG2XJg%eVxq`O4}TC!h-u?tW1qCf)HPh|l z=;0afQ|e~6c(K&8ps8ITG#5r}!GN5nR}}^Wn>#Oj-RhXJyte%?B{WrSxOqnc1G@?$ zY?{?a+C~QwTbgC*?vUrYbxVibQXH7Dk?_W!M#Fod$mPzhTfHGd34yZ!(6)&&2pJZf zo!m#zFITRtInlsW;zh=RG*pD#XD`<+#+c3(vTwDDBvWRUgp!WNTs`sfj|EnqF9Y$%YbPwQD{^$#6J=wj3@b3qlA= zV!`xAsJ27|R!cPs8LLI;sB|l8KLOw#A5oqcu@YbFS7vlek z>ryUISmy6J-}@d^^D``H$aUz@A))h^M*-jC(JGJ@=FpWZwr8)Gw5F4CSXh|Ov*74RR1lW4D&ybqa?DkGJl) zdvoPZ3f|RY9NvLP*wt}}+fZ|Mz9{MASfMb4odV7LtR(ehPY$wKBjS#oAiB%yi+1jsUH(V3bd$96K8T-NSfn^73OHu3U&u zy8TOOrp$J#wCJ1ygE=d~AqiFzNlm64}%SLg+4_7}r8Dvxaae^%xnB4#joe6I8~ z-EoqGR)NeJbRY%ib~RhPr%#c=c{i@;BdB0hyD8{aU>wL{2$XyYd(C$~bc zb?wGXW9O%q6yMN6-I!rWue|+zjS6V5->7pCv4u3>d|bd9Mr?PO;oF~9VGk)Oce-RN z*>d;j)1d{-Hi*mdR(cM(9T#mltB@Zt6CkKuo16NB%hY!@Yg$DxKa;XS^gOedIOCX4uwBL0^jbuk7D)UVRwur(-KCPUwrvU949t?s}C-oTQ_ku%oJA zCM__P1XJ)?H0yWSc^hi-z3+)j_x$chFKclrG8i|%eBzaSxyNi$IDE6hAm_B2oLu24 znYSoG9;x3t^>w@K|0?P_%3X>RY!jVM9~aW5(LE;>0`dKulpEc5?%W9mXivv54M|P` z=E{p#h!!_t%fR*j ztnLR7cA@hrT{nlJ2?9dZs+)~KpLrdAPPEPG%9Wb~A#7y3h~106M89GR3}{o!0UrVm zAKp#9Y{mI={g{mwH(e&4liJZB)zElRO1akxMTu!~;DTqAY{!r8f=vhy6d%t?6$E zb=B3$(0a~(0V-DQOKStNFHE*I$_-zU;XJUC#T`*>IY^&sf8F9e@1}c8WJH7?b5|B?y{5L+`*UKvj4pgJ@tjQ7-J1Mkl_yyk3KB2v>v9_Xvgi}lN zR5rf((neDr)UR*f{roxaua#v-zfXS4i|?)$M!-AGYC@WCM&~*R*1NebvBiR;EaG{w z3c2SrgL&+f9=pVibS45T9cREmyuzXFTes>^Bv2v=;XHE8^kRyt4zvbQ?;SWkSguK^ z_A6A6>}mR%aflqR(%dKt5MFluuW9lk71pg=H}0Q$_`V4Nqe60C!HSm?ERYjhB5tG! z6w0+f2D}%&5h%VCqKx#x4Rk!z2fl6mEolH=WsqiXWIg!$^n~?25o8@A;i*m>GkMD= zpCsSi{C9B+bAB^dWR4WtFn#foB`I)z7O3NhXb9&dBy6RwD?>W^{!uuQj6^#+dqz6V zcrn04E6I+{cj;DwiGiR(`CY~N?CPs5>XE)>WO%MkymqZ41pj@KSc6{!0+sZ-u}FCsjW)XWX)!`BG2tds zFC_n;-giw4vp_jl?ve0C6kz>R+Hy*Y(OqRGbR<3}Gu1AgZ$>J#U@&It)T!>@|6lp( z)d4if@Arz4(+4Mm;XP=One*Jy7$nh~S(a+-Fp@MxoQ1CMRh&Q@mOToivvwB&WTSK9 z#XJyke=oJRS+tPolV&Vwy)$q)GP9X;DSBYk5?3A>ZRmc;bN~MDC+2HhNw?-hPw#r@ zqr%uBueB?|8S2}r>IDnHG#N4+t5MB4|A_?AFQ;0~zD!ZT=e|Y`!W8N_m;p4aM@vsbmdUds<%_K|n4Bee>Vlo6&~AM$^1=moK1(pc z9lj`opy}*)$F8t7`$p?!mKd=Z=CD0R;;oRo^>nJg7Z^AIcqfV+dQU>6fmXK9sB}tx z!LX;t6E%cP!8I`mxtlo4VlogUS8~1G`~?d<4jt;wi1rYDzoTK3WrOU$qMgGrW}=nk zIVM=>TG+@bn&wZkB7UUPMs5h%%L;NaG{x|hANXv%TOenAH?8>xdgq6^b8K-C@Ii9k zCHu)jq@K_cbD-P`>43sO2U#&oDGJzx^4+%nB0GgIOAJ)cdF-|sMNU;+UBB`?f38$p z=RA&pzS*+VgIdk!r?}hzx9`vF5pos zPnb}mm_wI=fYyP%_i;tj2E0=?k7#lo?8piR(cm;I{mGaDmnDTb-ruug6*C9WKcNow zgL9ER>;WSnd9L)V)CL0a;=xy9o~bI={;#U zSCJ_QKSK@TkH&ftXQ3ykdXr_IjConV6Lyq9`cjf{^+~{W6WK4-+$2xAM z6@U3e_bcF4K(=(1l?_m^;#2o0H{?KfOm zG0}E!Y|ff!$p^HhZ|FOor6MiEz(f?NguVi3{?^^qd*?!gcAgtz!37)(3Qda@Vm>mB z;7$`-WVIN(6irfO4I!F~;7OVEp&YXMtqy8Xfv-0UHVbBEl%uR{{h$XIrju&|tPaER z2O+V5^T+I~ZbwFr10k*~-lbZtO@}!@H>&*eXA4e|Yj(QA=+PGR{5j*R!>3Odr5-zp z?G{3ZA!gl2o1ZzZF;xvhz7rEOi+)4&j#``aLC?U-qRqJ`GiZ#Rs{{<~0QjkWlW&jc z^y}`RLQ|Ro&A;o+(uLxPHNRVSOht!t~!2v(OvhVOmX-*@)yk#-B5WnF$XfVxv6GRy4Wyi_}Etkkv2=3rnjC{gzhet zimQJeDe%b2lOy!+K~+^vxTrMAvD_fRyD}jcWhw;4^+#8Z?O{Mvsv^k%TILJuOU}2t z%gGV!F+n#Y)(pWJ9KwogE8kFyTEW}7N`4u0H6+BF=&6#BPV=*%qGCmL3rcmkzYc8` z+TXl!BL)3Y|2oue(w+^?Ap)kwjnutx{Zis~`}Gt~>Y(T>$p??-@;8zXm}a)hOLX|y zxuS8vF_7u_h((09Ywq#WZE1#y-HllqAfmMcOvVl!x)*k{dTLaiOI%!>IzzTTzP`>s zA?BixXt-Pb|4ylAXsGQ8$ve~BnuL~%SpZsAQiz=HQ$$n&(qtYj*(!wl5cx(OdW!jN z(b8FW%}Q6dABc?HUDix^ah&Ka(vLE8cf`Omb|ZtArz z22;dlc%(~R1YL8kCX}eB4RVvNqd`Miv+D}aoX>5Jz(Y&&%WsisKu}NL`c(3;<@+-BykEI+B`v#VorO+N6&WN{Q34x_4Wx?$T-GNTtY@jGu!ohcom~jV*HS6!iD?P z7>JHm;QVi_&XWSI_AalT;83C~#>ng|iF!VgCFKf#xj$%{csFb`T*+yS#H?O&Wkzp! z9p!#(hw4}gJadvk>r!INIFekTedMiM2Itc+91S6W2$>pvxP3OA8{#3#EugaLfP8xM z_~An#y`JT5_I}t$$6;e*e(kQ$poACZdA9G@0gx1^Q~)d)bmvLCYs6@5 zrJzt+m#VSUI47|S=MsylL1SO^vV}g$;H4L$U3!)Dp^_CZ0zvtxStD-m*sWXN(Qb~% zj^`fX$I(Sd&*iu10Z8EYR_EPC(W(F2oe&<)wYt#$r`sYZ7$AgbCG@XDUp4pl!MQQ- zD2FQYwc+192=S%4l)@G(cM&>EbN}G=U1etDI~^IqxfJJ&XVDJfR8Y%l|BnA+IJ|~U zfd~;&yPV&=s4i1)y}^nM(eC_-$)P0+c0bI z1}Fh>lhfm=Bdq8jX?@J4{cxeZ?y@x~Bf?zxygCRb+WC!Uq?VfdBI*~PMlo#w6l?4E zcump$1Fa{ywU$s-y{XXQH_lx`z8SX9^@KgaPwDgmw6Bd9BGnFjb!;ucVx~`1{6w%k zP+>V1`Bu8@TlmyJf;s!woz311+I?-45DCthfmj+)M(;^+>joHmL~p7Sm8Uv?A7-UJ z_kZ0GFQ?#NI^y4(766;r?Cd&Fh|-~gMCKEXL!?4k7w2@Rozx3u-AqKJPzfO#R>`70 zjzrdSz8w+X`RIN(%xgAvDBNKTG?YjlARpE$W5&9+gcWXlssCi}A+&A(;RdFwCR zk(`vw8nUujv^k>a%Z#HHk+0Soet9M>;LJE!GbCfPb~q)F>9J86RQy3eLBG_AVK;e* znAiB~M|NkB4%%UUL5%zlkmoLBNP(;%Yz*HO8drM`(0p$Z{06#J9d>6<@jzJBv2+isbmEV@XxnRraj zC6>}dw931S=AgJz!tnqtuLqKHiK3^F(>`s_y+t|gMPFPy+)cwi{M2wq*%eF2)3sX| zc`+p9(c61xK5wp#^ZDw$mntDWamznKg1K6?2dGMiv79X4+{VUaObb2z(7&c@4x$T$ zbHduZ0F5mp$Vc)9d6cu&YP(O&M`lfh9IRi;rokRpu3YhPY062le~Qe&{x?jSyyP{6 zIbwqMZ@js6_n@&(YN>o9yJ$O`g9A(zLs`=;=@kRpF##LjTA zJk{ses@w}GYzTQ$)< zb7>lkCky4sW&Q z7WoU@;PjJrN))SwP6q_!YOOgz;UmM2%i*u?Ao2*?dkh)Mm>kDnE0vX%3x4d(fNN#5 zmXAwOv#NlP-22{f+m3(T-b~+w(V5Nz4n3Ax#-|a?TGsRDg>M*7n1vj1ZLWL?*KMlR z?{{-)1fh-1c3Ab-+brSLVetwNY5 zi5%eK>_`pz&M1s>%hdipAPw0l6$jx?cKudT%KiHuF{f|+#ck<=Gf$y$_fDOX2@x81 z7lND7X(;_@V0v1xd{#k*gF^nT1&9#tD7@u*?=4J!Z~Ql+X`8MpPovDu2S z)`N>PIt#K#UTq@2cqx3y{s$%J&zon5p##2FE6VCWq7Bfo30n_z{KeoV!rSyu^PB21 zG!lsv7m(9yD@hbu{f56%L#r0%!lP0<-6_(4M`h3LA16IJBQqA4$a9tFG&YzmS}fDz zT||u~1~rVu&fcu%?5x@YPp(Zn!hb7d zf6Or6i?GOSeJ%hG9&~0a?{1S!x?OlubmK5u5Cace+f0uTjtV%q^(+2uK-LqXwA-|> zM-YZ$)+Rlk#P2=9M@oW878Q0m<9zV_OG9I0jpL!eV10!vP3Dfjb*Ba&8y5I>sHG82 z-M*FD3e;Szw8=T4Nw~rk{{ors8x;3b%uW}R>Y0M zOY!>7{OvamtnxC_TDQk*gZ#UuX1lb5&Or~K1gai8Q_^@RqWO90nJwos9zHztFt9Q2 z_Oa)-6;NjrH@r^r{8@WR@l+{yYt71s73@lc{V^#3Tt@n`#BHd$x-=~8le7Ci?!hy{ z9EVzyeHQ;3f2=rBIik_{(~rA02)|Wctnng@>}5xB(N3R%Q~ClXJ!W5Y-slP@!0hvQ zQT@rW6DKY+u{L}<;@vzkQ-^EYeU8Pxn!!7NNKqeKelq;{gq?}@)4pKAwzvDVdI{Qp zk-lruutTb1qjf8|zTqhbl~E+6@N|DzEgehC;BsHJZ1XXtM=qC?2Y8z_+&F&wVdsv0 z6%2H1ce3ZQOWi3JSpG^-GCm?dtDvOB1HEqe9muHl$#a;L6~E@EHtDE*8rf6oh+2=2 z$6L+V*SSv?8w_VfgCo5}FO}tI-bH1p^sBJJUEI5v2Bc`!-AZ3w_e(FF8SQHrXVKGc zdh5hB7YLD}NTyF6Lk=c)?2-=dj~?t=<-S-Y`v(ia#egxy;drNaO!iS22zpb(H1Ajl z`VzQ|ESS)hn*%4&0>Y-jK)tlQ+?!J$pn-Q}4UicNaY@*KC&xJ*VuU4>mhVG}$34gdlDoeec=*db=1`ka;(OjnPf zb8l*Uk~XiH?`z0hdu}HmW#YPz4zx67WPE2=mo8iyb+Sh6aeZEC!Gv$Y=g$X#Z{c?c zz~|u&=bgNI8s<{;e!$*_w%>RV>sWz0Hq85boRF5vPFh&ue}d_+O(R0jj&bpu_2c#w zWJ_l{1pMr2>_w6ncx+s+CpweHd`^=r=Sv1So}>J-e9@oZCUvAWmBMU1D7H>RNCTE3 z>1Hq;Cq_?!H{p9MA{`tZ(*$+P+HLRQvf)6`QMis_Enq4Ogqy=E;Ueqf8^pvNUBqB% zR2fozTaO>6;zU+Rv8Rtj#F34kC?UMPz?7E{T;9*f*ts9+uX!9-)ZI86(R2bx0ot~k z;2uIIeEo`S!LQSiI1qYyVCB2pkWtXeYA_5GJcr;`j}r2ufKl7 z|52-uHC*1-j&{SAw1x?9UBzS!7(ge zv*f^ecUkzUUz~&U2c>muH}URsY~FUc_rUDoB0ZOl8#V}4TX@mtq=W5W&+|?kK60d@ zvHx>?KINaR4qI}$GrinPkESv2@|=PZYqX-6YPbn`t}OUDYbQ+jlYMDnH~_lwokvlzk5p0MbhB>j8@DQ*ROUj+(1wh ziG$yAe7@qbo1E|^4z+r7eO-+oy@O@)C}#DP{!TOgx5vb$@Dy=yMknA<>Poumq9->vDu*vTY zNeP(0)K-&Uxib17$@!;c|3WKzG2kawCNtGPB`<~%*9$?oGFx;E$BT96zhmD2AGWrcsxQSZ3?=Ecxw2|4N#7PrhV6@gjFy&>Dzk^_<-Y{MC?lOsWa_K`U=?TCH+`n(c^< z);uuNiC@(YL-%3B!H9hnRSA-#FtmPaO8v^q~a$K=3z&WIPzFT5thH=>o>C*$z&X`fwQj^JJU zTVG56C2=0~95SpR-q>~QyLX!teX$vxWA9opOX@n^CR^WpqAaQ`J|FAguD#Hc=E2yvEUB*ka`?KDTyA?rFLuQof<1F zf@RLmx$u0Q(90JFnmm=hskSN4DD<=*#}3m$jNi7LeRp#7()Oz#9e@6)!pqMu9UFr! zN=BpN`sJZ9k|-*&o>%n9(p#u-)W20!9PBxKURfV)VHxp4ga)Hs3-w%J97YeQd>wsJ zVPA_-&D&C+Xt&A(d zIcv{|QJ{eE**ZD}f(|mh7pu_ta2(yQn9ka*$3+}F`TAC_$+MyJA7oEbQ~SQ!tQNEX zqGOAuE#7iqgvDsdym0p~n>#Ocbvqhg_c0^U)|;iGO&|6>^`lF`uLnm}rayO3nSR8} ztE<$YL4Ni1PT}@!)CQj#pV%o-Dkyf}?2C(s2l>d<4d)=XWydJentKz2fX)2&^$Wd6 zi%x;+nkQKyLwliVfDV<(+c0k8#J(`leV)NZy)1JKp~URO-!%)gSDoL|+>g@FV8BvB zH91-IU2SqBe2FKsVuA`qULe8X?mz1=H$DxB* zA5$%wT4>EPnl7P;N`3fnZm7y<5Yl0P|IT<(eEtg+VZVvrcf_o(M3UImk7;KGp9<7E z1oE<`I_qN}_s6V%IlDALGMj)NF5XR^@+v)-@at>L-OL}>mL31J@zd~mTW(s%l+kz( z-mx<$(CG1&ZaE82<}VI2Qc;c6*#HY@Qknlogmju{{@Z8nU}^i}uknK4G&$EtVhG~n zD z%Hb>NFGh7Yp?d=%F*5Kb7F)F3^)Km49s8R9{{1TTW=3rgU60;0Vj)3e|IH^?Cmrxo z(qZFneUA|$8ywk(x6e#KrLujI&cU${hOJn!b?Yq!-*LQ+(0NjG-{@^vyk!oDpd%Vm z1NB@&{l(nckS}X)&_(ht^4e6=CW&Bv)#Somz-*_0pRH%CTX&6vG#uYR?^&evH;y#t!?!dde=;kD;*#Zp&Gx zxlvn(*htNz4=?a%#f9DAuA$8I=SPZ<{`ap=%HoKI>5<^0fF&E=$Mu1bUlv~V41z}m zLRu)CaqCfivo{^oP*2C>a@sTE7Ge$(S&9ih2De}$OFPv$SjGUfZ*dP)|@P1F>le#b9X%x5QjA5iOkWZ4D>PM!Mv=;On$ zSK@D^EI7GjNLt|WK?lW8(7&LiyKte~Monb%Ag>Gn@pUKPU-NWzvuu1$>wT>_e(2C4 zJI$+vjUkWS#~4MOKeIe|BXGYShwyz{#7u31s|KX)lY0uk8ET!Isi++TZv)$tBL4)j zQ%K9S634jj1IPHr1kO2mG<0*fc{Que3^C(!%RQ4pvDG<4Z}I6ReQTb|*EhI-QGFb6 zZY`MZ&o74*5uL{$SGszE)#y%*UvHCgSkldhZWlgcla1;v=Ws8&PV9!?N5WYD6q&)t zc`r>l+~TcJw&wc3gQ7@o`Gx|&Q)Pw$J8G3AH%QhdcNi=B5z><(uY+ap zZjTfFP{<8hoy$(dZr;IA01`59G+TX{>!iT->ME zLWkz4)k{-&1|cP<(Ba8~NExq0b{f6)9TCb#ZNeD+^GT0u5?L)U$|$P9R`TwT1F|wQ z3Yy!dO`9e_aYJRR=G=}Z_m?=NhW$GloKF2UT?7NkUVHcKi2@xVJW4H#>Ho+-XiL;-0>teczM$&$pUB`rC=k*3P{E)!v+GH4mH;dfl?k@Ch z_ItE3$6-+R^`&!4OWW==p5Lt^K+gsLjl+izo7;5@jVR3?wd-AsZ9l5-VP}O*5VXAJ zg&Om4b4wvr-`qOo%+yTn{G5-K}}Sa_wA|t`lN$Je?UP0DOY1}IKP>k&v@Q`VqW1&BIOV73*+DgErRe# zI{xg+65>uux&s2GkPjKl{WqU5GjbSv=nupp4X-hs`|a4r>M|E4;UQ8{p%}O#ZtG|& z&VBUaq~oV)uS{_5Pfy3Bm}2~z!S$mdjgK;&H6#1xxf*V2F*T#_4>sr*)P~_^BmN{n z=4=%+&hIkx;fKd_7k+kp5eT-taeU>@E#}{pKg%mNt@WNAcjNR) zla8;BTc0zwK4<^0o9bmP0RJUFFp(u~uMrKKaYVWEJJZGY%d~*EwR0CNt}HQxyA652 z>(u+2V`^5ao{;ICGN2nbX2t9J&fbf9v~VS*r>{wX1~1r(8H?ArLI|Z1RV5Xq_KDZ* z`bgq9Z~b(le+A%#({<{W#b#6uf_0fC>C(!%n=Q)*4DXtq(_8O$G+HApxPjc9crWu4 zhR+*pY|97a5GUr=oXbx5)@PjQN~h_Qu3Bv#qt>RT%c@-eyiIRc>ow?Jx$?uPH@Et2 z>~^XIL1?`3ms|0Y%==`?79+&SUS@6$v(%U0bFxRfu%NoM$I=kvn^$b26_!UR#XlSG znQfQzBgkS*sniyuGpX~+J2BCP0MaA#d#iEf?RPbNJT=11GvZ1-lDHB1(|OzLdrcps zt2z2Mo({gaQ@!Yp>_PoDN^eIt2{$fO^7|Hev zT~+de*+Iw%wH+>RCbz2Hq`Q_zdJ0;*t|oh@AzwP;H(uX4W4&qp)>Ttp z>0Y_nJV+^GyXMr=_%&Q8m&GkO(f+>2r%6=-zplQ1BAzdc_V z0*e-+@S%li=&fXwbxrM~&Pk>4Z5I~JXm0M^tJlR}3I9G@Ub+LHEOH(Ar`GQ(z&yaM z10+on9yEgRAa50u%TTG$fe}eo!Qwy}jgbIv=pqbPpM@loMi`E#zef!hI|Ie4;b=SS zXD+vHxOU%J`TSAm1MS+kFV9L*iVmTw zs!)6V^Et-d7GIus!eHa<)#WKCo%cnolv&kI>W3q`9CoO;BfzH8#HlWOJ`_Be*tF|(;3HbJrtjn#4lXBh&@&GcTe5X1UI~BEgdKApah`XOA*+mROpN8Vx}_S_4RFx zHfU*af~)i1+>71cxac%|{|K#fwLIbbmO+QY)*X;Z%uV;Ex_}$qXG^amjW0MR!RYea zJIP#!UTzcb?ri)kq%pcZ3LbT2B*Jxyb0G$45es~5{2!{$1gz(L4f`20jBUmkSrP_= zN{JK-nX!{7v`R@OZK5od(#()0Dx_#3YqV2Bv=C`8(jrpQCfVB1ruXx0=DgSYc3o%A zxy}^*f4}edS?>FO?#E+#NLa6I*L(qK(eSq(f6)U~27X4!NRTwh>3nuW?{k0$-D1x> z$&i{pzj0^I>fgu5u9B2n_XjC?<&gEd5tDC~rMR2S-s)+%Ml|OtwGoBkPrpB1cQ)ia zr}zL@ae}=3yZA=I^HIONd3wdicsoK^FTUH_YmS%{6nbFFQV-X}?ArGnxUg;NxdC1fG~O&EltsiPd(7{WUUj zanS!67xHkG=qFo5z7OZ8vW9{g^U_ zh*~-|a;3Xx08vb8J z#5iu~{xq=aV=2$$HEf1reh5Q`C1(Rnf0|Mg3bgp@>FW7@duNI8z&!CEt(MMb>no~NBv zM4!K?C6w6^@!5o4P(UkuzKCQW)Sc{U8qPOAAPO8!PPlU9`k)yVmx?Q7V@EA)AJ!Y4 zi=?+6yGY)s9G20nJs$NGl^)@1?a&TQo$z(P2z=;I6Z^f28|6#$6R8C}&9eK~NUb$e zit+UEIh8lE<2MhL4R=>6pn5>{2aqslFbjW9IOZQSo4&WxX`u9MEyG1}cr6J(6`^4i zgOtdyoa)Z()?%a!J?YgoEn=9TuhayXQW@_U#SL}pJpobtBVc&*uem-Vt)DbGIdy{+ zs(&%)*DEn&xDcjHHBLN)3Z92kNrrJr6C)~Uv=3p&Wz={F@_j#Kq_t#dRxyaXE5&EH z%8&k%Q|CHVA9}ku%Dg3MX~TyPKdoN9ntPNy^BcF_2yPk~lH}IO*2ynWAC`QpK=n^4 z93#1-KvM7GlON|Fgzm+Ku#ZdE9we`q9qps@PoiVLOy4)LLN4WT>)kciHvV~WQ#l|B z?MW(+r6sv`WU674lf)|mhZtHBLS>rI@G%v~%mm&fTmiwrP9{%MRvt>J`4z5Dn*uwr zc{=Tni(PDkFp zIwbZrC5LzT$Lxa4=+U8D{8#pzuSJ(;)=mx_D}whj%~UWhQroYUo%`&zIkihrLsuG_+|j=1)&L4U?8*)v)lc+5)bBlOl+nsc=~j$ zpUS_5M1g}o)2}{g>zB~{14V13{`K#W8E-c5 zNlHpo*tpd_oi}pQ}H4Vp;Ge0;LZOpjt#73IwfCmDXQ%#;ZZ{BFSO2x5o3VJ>1IAuFe(RmgE$cSNPS z@nKq;DGl3KEHjL4%A#g2^hraoDvS@Q+2*8|psL?UWE8U(66_+7WbXG5NFc#o4^=QM zJUwy!!<|_}H9yOHMQ$|RI`rf6)vMFNK?OBm>57tu6*j*$Pt96pJ^a$~G~Xn>+KH5B z50L0?=k5A_NFP!$LlpSciPjo9r!FUIeBPZEv^dVMV2g6W#~+)_%tjiN$D{iT&|_*) zd|jGfJGu6!9zOE9x!20VYVpGmKwgGYF|5-jn*b4ZhKOVkW=dwU<)jp5YD&USMFJDDvLv8#9{{EuPo-~p0ymv-ir zP<`4bwHDiWsqIV~g!F6X3KtGk>(9rORo_LskDMm*WVbmw0*$N`!*Zd?UE1mIK1j> z?${&oCIz_=^q3@n81qJk3V?vVsZoS z<;knr{@c!M`YECfIDR8=d4ZZA_c;}tvD`b8CZ67N)tjlc-elYv&JT<=(wZ}zDAB%t zt@06j>|CFe1xA(tLWtnh(=V9NNq0g*$gumtpd^hd7V1z?Q!*7qA}Su=ZFv( zL{PtAi!e`^CSN{$URdIyuN*qLZx?_1B!v$FC7DA^%rnP7a@e&?_ePTKnb6zQC$6{I zyKKmX;Ckz8>qA$LOse|yymbMaFvR2!Lh9grgUbXCx4c z_7Q)P$In`xBThWrx}t4f09e<5Urm!m9Q-8{96>szvuS`h8fRi=)O3kyIkSg{ei6o5 z;oB8z)T5G76ypDVQDw}KA^%WWM{UlW zW;n>2Sgv{&BwwTESCYBcJ$HkI6#|)pk3WR##dyQQJplfRv{|#@{TDfdM2>>a7qjKO zaSyQK+RRNx?XR89rEM}v30JTtOKk+m3~x`UCA9U48K(%q`CoP|=sCR^!7WkSFDPn% z8!__3MIZGcwcp;euZfSLcFRxYVk?6#k8*6X{1uMy;!K&kQ!}}R($P1nQrN<<3v*q! z+qIjWzZ-aC0DTU4&_?daIH_5`M@K&!@9*$VVcbC-E9lf$yr6=RgX7}gM2@q6Y7{wc zRs;ZuKyYy>Jb+W>i>o7mR79?1S&{Jko9p!HkB@c=J6iL+^;^ULSX3-1X^4cS2=+yzjXfNDuqM#vP#rt5j#KNV ztpOHpy{U5-K^%B(UA3t{p=QW1v7+hJ%)Vm3#rW~f!qsv0{I30+MOqZA+`d2jU-OAMd6*u!c7a~g_|McX$aUWsZC?NCjb8XD?Z89wO8`8uQOMV^^{H8G0BO| zt*sts&b+i)Ja6D(8%xWBf|35{endrjk7LdHfboiQF@!zO8?(i`cUqn} zvXFJ>z|;s$$`CqzaIyfb8Vs*m;P?ftRf)}Lf4n4rVjrPL-hv?#S_uvo0=N^X2!kb+ zxG692=zku6I^ON@VO@01X@p6g4fZs5!o?M%dLd0m-6DfDXQ215Lx=8%cN2Si-%vkz z$J{yq_42B=c9JyfpBCuq`Wx*1NeneImA2oJf<>$#)uI&ejajIy%!zo&C!@ z9*joS%OAD2u^9kkF3y`M5yIA7Ha4DoCODYYUXv47u~O?7WCKbytkP)Cm|c8A5oUOw zxa~u-PjbXh*X8}Pw(--aV|k!_QtM>&Pl2glEIy_;@%P_miwfQ3DFDt2sb2&X;c~xv{ht=tStK4u}CxCNgIuV`pA$)3!)}IUzrU5?sL3Yk58^;H-Sg zLjRvP^#`L$r$zN}q(DjI6|L)dXF4@!-nvO0hUbr zbEX_;CtArm4LwmjfNKpJKGo=Pn-s~pFlzm9{xlEfAE3~^!RH^8mX2gHzCgMdXy$FF zk@+F;BEz(}b$nay`-#;&|K%f;%nyCCpCf?@oJe7nH8mEseo4P&$Lw-&_*W^nkSr^K z+U)o*!u~?29KbCL=Ck8qt?1 zAx(mrM{hfdyFYZ2lvwV@rtLpax)1W~5MHiqp)F{GHQI!oBuULfIY587!Tp%TCA(}3 zR~^+2yloe~f7^!XUaCGa_Rh>uV5YF&kvrz{GO;GU!JUh74Rg5q#E_7&f}BH(C*?O{ zbNI(7D%>Z4Gc64zKa*~l>=3Stz!lrr_m~v)l@Z_9AZBSeGK4kv%7=zuQ73Rip=|54 zpY|x?@LN$V|NEtCx_3N{?>8lcW<=7(?Vq;)%QSOugp~v9>;Evl{~W{!h41&gXuX;l z`)Q80B~rY9eH#%oG`-~D`5(s6klqOV{M|jkBV(KBJv2j6A-+`OQp#I?JKFI2^*=la zYe`F|86WtAJrYc5la$_Aj-T^c;u?O?agxc8i*9EbHbphnS;ZMS*R`@#EeP#_=?%G)Z$gqkMf1yr< zu*69Adk~`3%^%0ZwLBmJSMnP{(Y9sW9laUD~+%g&@N7t=^)6EXF?3s#^lg+*Z0orDUkE^1_^P8G) z>ojI$USN|&T~5G0Ad84$`RjffGxw@tTxsH}lI?fkyRFk3sc4r(jf&{z9}UJYZ}FzBzApI3T`syYTmp&kZ+6e`GUHq6#Pmm#h1+SCNJ;^W;i-0@ z*a+)6ff|d$GFnir!kb6zTQoiUjwFM+;BVer*omq))z!a8npDI2fjh`~v!mog=?mO7 zp!Q(0_VkGQ=s8#m_B8vt&{n!ib1Tg{1nB*s*(OxG~5NMZjSm?LdKz zYy}qAjVUB`A9+sDI_in`JvZD#pzdUx5^6Dc@B3jz^ zbJ2xkE6hrgcHNRyxkd{8@+UmlK#-^9DQKgBGGmi4kl?VHJM#`l*WDW^i+~Oisfnt~z=4M87*0-hp!wv83u$mFGpXxUj1nHOh_YBDimj&@zD+i?o z3lA{*q6G{A?fuqf2`KsO`-sAP5p-GE(8KNJCQ9|xHYc{D+hrZJhwR$WErPT+WrPXu zp};?<-(umc;rN#kf(Rjl$*y=iSn_zz5%bKY*4-U`lU`*O2b?_lz1I@6!rTo3z*75) z|7c4m-Z!EWt2ClMYF!ciikN{0Zt}dd$Y9c_7KV0h4XV7HS7eaH+TS-;r zvWYhY={D2FEp|z*zYJZb;h{8^LPB*frlL9OZ69q`Szq4|keV~t_ui4(?ZO#gZ|7`q z<EqHU)(Q~{wmH$zWr*BQnPJG zR=_w&fKC?fwa;ALc2n5C6N5Y)2{rf@{e2-dz4&BDW^=|i=W!@AyEz_w@8l zLCC*8bvrHTx=4L(KR5j&Ose2Lw`lg2|0^N=rRJ!>>lUf{zFgf6IH~h8AF6Mi*QpQ` zQ+?2!7&`l71JomQ4-A;}`|lgM7^(Vgt&WTM)PfwK@EcH9lwm5?qYDbzXZ|;RC&ev% zTLv0F=1&+6yt>5%Mmlv*J(*@$P)K!M-4)`>U%HMPw4<%SQg)Qjuz!567w$_|+sfYU z9Jv7K7k8HF>E(vjeQI}#A?sc5PVX=dkuFX=aVzXV;;|qLjQ2!(Cq}W9bFjoKY5}s+ zBZzxpF?}gCbj2+(B$)j5^#ITYy}FJ=D(ZC`z0BK2i`_@u6)L^Wv2N_!GzE%J-Q4mo zO&?Y?v#OZu;3^yQzez z5R9igy>A~|dY#&E6uVfXZ;#x)ReAujIwrnM`RvbPEe3FC{~EXOz&++N?&Ol7g)F1q z_3)61tn5(?jtdQ$^&i~y%P%!RRLTdt7QC4oPA_V=wRytG_7X0_41Pyblnks4u;)p*O_H6n2;E2|i( zl+0t6<071n>LaMi$ZNla;Q++c+vTgKiW^nyBGu`newXiqS^s{_1!Uwp`&5@-msx*P zUeuOIp@pTS7&I(*t{Yw7Kt<7rXtw^SmDk5#iZe<}4GiDOOq%q_yu+=jx*yMCYS%j@ zqIQzw4+`4m=9j`7%|GfmPp{Y|Wc!p{N6avnV6ohH#XAR8I-B24Jgulvd=b7y?3c*P zR9`)+3eWY_Mj`p5EeU_0E+yu~23+}0J`bs<_+ksCkkz-)A&;zN!t!=_hxQ!fy=W-@ zB2N#f197^l0yQzy^sEn6sno2kk?rk~9;y!dDC4~6cXzH0oUo0G7AuPO__7sd8B&cP zOMlDjwd9tqiFeR%Jse@PLFrDQvihFSIp_34Q>AsYw=vBM8s4ui>y)AoX|-m=gqT`8 zDgLopC)#MFIF_^nV)e#KM&fZ=C@5ht%j9c$w_NSDz=P49Kdr6327EJIIO{{Q$Jg50 z*|G9{CEZxIdB-f3+A&hA{rAzjTL`ha_5yNVVMCkju> zi;F-VS2x_fdBb!iNYmc0m3?i?6HC`+7t6A*WcIhpgJUrv%KHz|8ozTPm&mnI;s<_-U-U$t)O5B$v}B3ebO zd^b^An5(N*@MfgVz_{j%TYlgL^rO&?$aP%XZ>A0O;M!WG_Y8YCAy|uX2BXSz%*GIj zVsv;OuBl7?c7kgC_a)mSj~*|F_IJlrS=r_BUEr$yS+4pw5q;Y?L38277v35e?0Ld@&mQ5?ZCNIiaCUVJXea4SozRi+ zoub1m`M>24z9BZGUdVd1$yDPdWbec?SS}l78u$t$Q(e zJ&>>7u~Nz%y#QuTA;i{wS~>lH6I+gEqK!EIEqq*}r_Z;A%GhY{;k~hgd(J;{2 z#ihT`ay(ICi+d&BcAskP>A&`;h%MiWZZp}Ix@QNLLN{-0_B|SHq^hC%P4u!c9aOrP z9oq{p^ln;O9-7>=aH}q^s85pi?B);}82U&`kki6uc=x0pprXrOahiS)sJPY+9Idvl zT{;J~lHk;Q-@Py@bd8}V@U4AQ?N|0dkGB2Cbo5q2;GEW|P^lKCcy{T51l9rzt+2`m z)XhX>8SfB0`>}6G+b-jdnOLfrnZxm^Uma5}pJ>P`lfINwRX#K?8Bpq|NW?LBv3;(w zNKH`D;Dd_xGD(?~-=dnt*pU&Ke?)r~?xOfNxP9#7sndH`PD90b|T>}aT;~3kW zSJ`T2*4y3PJucd#2dQlli;7VUEuAeedbs+xl6Cj@^-aCB2;>ObP{weA1c~{r>&D57 zVbpuu=BlX~76pZ1rMTvX8>ngJ+qZJTcehpkgB0hBCpvqo-tz6~^Ddxs?=V ziip30%?3h}EZy~>6fu=}FR#BcR&o5>)Vc%YK4Fwm@O^iWCeZkEIevMOPIf)}o~$f22)AMxg>PcCd}Z)#?bfcq^`s8#LAAKb9AZ|Z(#SaH zmfwcSMRNaGYL@)WxyiI|i!~20cb_6g42ZR!RVL7efIO)QbZv6p_IBnWI_4pE{{imH zX7v=`BSUHCQX7Fqi$2`qV~NFL;SVniXL?A?w?+xDdJ|VpvAdhLF;!XxaAHS8aqcCX z8#^w9rk57xnKBCdkl$SkjYMT97|JyEy_q`7yJa-_3f~NDDK2gb6uv(Dt4b3>FSM^< z%5e$R){aNQ3VmA4Iz6Ke4NuZ|%fMHu2|xn-ssjFrFC3VYjR)h@T&tVd@h}?S;^|w_ zG;?R>>I4G}Dup$udi$FRl~iGMJCoh@8))q^f>k30;>g5R%WjcP2&l*2cBtGsGF&ly zSOclL;LbVcC}-jCNb;Q=ytYA2Haq0zB2w3cyV1(lh0FX>e91TmLGi_X3uKI(3^3_T zh?0e8nrjL3DVIWeG2>uYTg3Iyo;maV#PMgoHHUQn=$i_DBJ|8~&V9u2F%y*A_5g7T z#t?U=1>m^xwTK>snlA2}X(B49N++l(Z3=_9sC_-}W#9}n@Rd$jqk{rR>J2p5qz}lr z|A$`&Ne&ru!EpLG4Ly|iVtgw#fh-;#>%)Od8l}|m`u%$a=?Z)im8HC1f0Yp8`ubog zO`S}&(X?cB2?E$jdaeI7Uc>)Tw63ec-;-7PAM2$$>wfMa2wne{KQn{JEIBTj-T&vG z^;8TD2ZgHd{_mhLT+-BoM;?;+jk#X6y4u>%_HV$q2N1}M+B~%lP+S;eFHyM$Au@&- z=;6q*nK}DxLUrK{y&Z}hDjwar@Y*6&#;H$6_h>-`%AI}QfA~~+1(#6WWF1%6#Kj>t z39{4)0DvckEfs;IyzZ>L`{Lr+&$SpD41d_mLGjd5FEeOXi|LxTOfbKk&t8}QbvtUB zgs#G{@MXbX`>1(7>A^qbL!wTx>tl?pOSno{Zr8Ip4wFnf5I&@#=)%1iz(~K^4#OF1 zYv)dyIMG1o{93Nh^_#AxC{cynFz##j*U?d?RATbdC0mpcX`Flgl@Z}-P8H4p@8VV< zDa}b7($jmRbpE@ixjGQGE^G*@i*dm(lHKmgbv!Q8@;Ex#rs?Zj%f6*`hseVkqcX&} z;$qF?4-AEzWc{sGE+4;$&J|N!l;4BA6|&FgWM#!>1geuS^)Y|oeImXLcg`O?DqK;@&+Hy1GvcPgfko|p9bOV zQLzCP*9bavUFmMB0kN`8GJX2+o+l<yZ2PgeW3>D4vmj&>l0zSka-7q>E^CtezrBnahK3I@@mIc5d|eE5$!#yxy}Qe<@+${v$JP@3rjgKX=^0uFC<$k4V60;u zv1qe#zyB3{zXbH|sr_X{?B2l#a+p+V32P&a#jL7=(3qd&+TdtCU1lp-zX-UMW%MzZ zau+xT(S2U)Rl|eF#Pq_|R{?K!mTtH3TJ9z5)JKA03rTOJ1}``D&to^nvf0Tz9`Z<9 zBXVX-l^+i0yk{-fLw@*DGq_>N9RGJCvK___6g%+tdw9rt`{ow5jR>79hWBq|EKsL< zF4&}|TGNyfy_Q3@WWj=eUdM-}6{>Cw)xEWKL#2~wjXau>jqE3Q_pcKJd;E_TN6HK) zJoz%)`cW9wx!mG=@?NrzV|su0-MT3ot)|ZPX*p%mY>^pDS-|Fz4YRM@xVjgZwPkk# zvyW}DvlAm_Lf*yoD5Q8|nLQ~xV|Hxf0aPJqo|kLC1CkO&miE9Q!YhowHA-a+&ba)F z;~_*0(<9JWg&Lnt%l48khPm0qizw)G22cjP=-U01-96=;w(tASb1^8=e^I!-yLqFA@tPtn9UaO~wN z2aA{x$%s&mw4P;Sgr_tf0CK?*v?>^L4Px#<=Sx6Pp_9)Oq4{ZI@x2flq;*{TJv8sn z8;_o6^GqzWnuYQM4AHYn>suWCNu*)?3bk$lR8p>OHqy>B|JW^*#_HM2xh=K#X^(!t z^kmC4CUT>;_wCykyMV$sqYgr(3@yLcQROrLsHY|E(JnSgEVe1j%^gJcnVv1x6TdT@ zLwgdE1NQ#dCqmD>{=FJ+kV z?D1D?=_4GzhLxA{H7`86E8!sj7Q0xvt~?uF)3TK-*Co!3A1b?M(d_8r1VNwt`{myw zE?aa`fowoa=$BU#wl>|oV=A;u&b(L^n;~86gl!GRtz1F3d17V3FSI5iv?T)enSuGgEo}O|yZ2 z_F9CdXI@|#G9cae#0mNEX`x|`_g3aIj01c5SW4hjh$ADj)P+JD*M(KHh2)4=IdtNR zlUf!g{+sw5j25pMiiz*C=cg0JEbpfEa%(pnWIdAn+dJoJ4%)M0DDn(w#>IY(N~3Mt zu3h+21c9b8U`PsDuO;d6x}xHXQ1pLv*7Tf%Lgui#71gr>@)YNPQ;epE5tcX8;_ew7;W~ z#BWS1fR;^X=6j&$l!{uh#K?L^V^!N=3ZCvOF;SB-R@ zH!J%Y{Gu}>;5JXFL{{sMn#R-}pKOiASln{%Q?7dazm%@dXA9YH?G1|i<_}M@0&OmM zRP8MLWm?d?-Sv#S7aWLe_dIgs0eH(rnI&6aIbWK;R((bs91^xR`rDa|aX~HM)eXCK&DFuYA1yKj;+> z8$9?o>F!tier6lwmZmPDcKGGE5|?5jr_aN2&aQX&;Cyy$HMy}xyYee<*Rf3Zl-RD& z)mp+j5hb+)Dzx5yH-3A}-$X@v)O#MDLC7LTC3Qt0rn}rPRhS3ke1Fut3nag@DO)jB z6^z)G`TQm6lNxSN9byQ+-ttK~ZXg$k#xChMLTye(etnO2@OVH#*z9EdUAA>nTQko% zQ*A2=;l`m06K}Iuahp*{syEgA5O19HhH^vVFWAHfn)hpf1p5DSpHxrolds^f5S)eY z$2wFY4|$y)j*M_0A2wmF2H@IthK)!NKh}LLOW4P6(xVAz=BILvbvEY1jVxNb(VuOm(xxwc$Vt7q%o-exL9oJ-Q;HwzXr z-vGWKbwTuhf%JC<^11v~VieKvXG=g4#?@_7(HP|q%vATRQ;SYyg{k1u7=+vQjr%3Kt3P)>yR+-PTvEkc zym^nd=$S#9VGSZ;N3lN*L)1ACZ0kdJ|QnNCx?d0CF> z+fU85u$!;*pYwyl|K-Angum;Kf7_9iF4vahMDcpD`6t=#{y+YBtk$RZy`^K__7evK zw=Zt@P5P33cDGQ_HDAk_Ghxxm2H`yzHJ$8?)^Wl1cOcgNj?|6V)7#jD+gX0KbKneNxKwCGgVrk3bK&U0wasVDa-{K?g=n6!ER+ z>$u0p9yqTCg~b^4j(0PJJ2z)gB~u-d!llJA5~rh<`{48g)z^zjw#rU=+{bYANsbyq z&q9G#Sw@2(2E}=;6G8E|gUwvLbxLyXU2x;#XK)Q>T4o!xZMDG^9+zMtb;-kpci}-4 zo#Lr@!rypfsWF%qj|3_8K-^Zzqbn#8W;0M1 z0E)9@_A=MGze-dkGaYj?ng6-;!^xA=cB(IdLG-9XJUdOszFLR!^R$YM{|Sqv9T>Bs z@YMFxFNLI+k)QhJ)vI%Qacw-J0Rzs4Ub#}mq)biTZKx&*|4nGPxCsZD zUzyI*iH$T$YhE_L_?V)Gza>}hs%})^r}<{x##fmFf%TdaJO{r9A$Z-E1C_FDz^BhF zi9n*(C;i4Qn(ox?!#4k`o*Sjo4He`u5$7zqA0}3zg9m`n=(KE5JJsB#m?&%19?HD z-az%aE@_;c#P1U))}Lvx(^TmlRVq#jsyZ>)S(o1#b*nrT4bi2#qem~-*(J-}a?n?p zILiiXD|}WUyAOP)m5@6(Jijq7D1L68NFLHGfVlI-`FZW6>@Ql+Z_L6Rh;e}hE-rV9 z`8!9mWqd+cQpNyA-faq7C*0l45sloIjKy1OVtwKtyVOUlPZ=uV&nkHs5ve4%tyRvK z#+J^Q!An!z_3oMJx*TC$HF{3*oBS!}ZNf=CSL|T$)t*i<+fyR8M{eK>e*%D>VsJB) zk0x|^hE=q6OSpOes?$$;iJC(LUPO@4D&;t#FwC&d&kYUpA2vDbW-W-z9Mc(r=NOo( zZGqdr)rJdIyo`eFO>kuC0p>HU3Kq>CGkEaEPq``NCl5BoMPZ3 zRQTQBK6VTBKkoM%zPe>apy2t7Pa>F`S8fK`@!6z8)H_4+a!*fRsDG_sF#m%6*}vo| z7$@o*URySaVfzV7jy^UxSENOiHpB3y?O|bC&*}55%7uk;iW}|pMA~R9&O{2HLuJPY zug8BM9_lbylvL6qTPWsd%z>zXebrlNju6nf*f$8+?C1XdmDDm>|M=gR{J%z6ksA1< z0-YWp7We%21Ml>`nhiAnDRXkjE0Ba#XRHxWORe^Es~0M=l&;UCkRSXW%(ew}4(gTN z6Viw!=XuDf2`Gj{`Q;hZXY*ZlqeIai1a7EgWOYe8kJY<;aR}|=7A@O_o~LCMoCLqg z|J`43SYk6cGB7>AvP*!eLb?Y6l}ZWPQvLzBei4ZDzf~jU;2=X2W+Q~fe3x66qTq^< zKq6%yL)OcfzB-MEj8pxLx513P)9+N`&8K|(swtlo$R=WT%#w!y1F;v@u*zGgum(aWerR;#pku3)Dk+SE%4p&_nK{aD;%x6an4 ziRN|_9ZMg(ILbtpPp+A*hm?j@!YjNk8~k3nhsQYU6W=5yjYxB+m>PZEV8BH?Im0I7 z)a6byu=vzT1rj(J&3}bF%O$-X#lMidRvLlBOt*d39k;t;yUwZZ?(~p@mu z#UR&yyw<&=T^(oFC5F_w49eB<@;H-FVYN1>p|^ziYR;Jaoy4)- z{N6L~o?4EpA&a46C?o56NqIZY(8aAMscUzC>zYv=D15b^xHP(hviiM$!E8^WR;P(T zMBLh2v;|5AkaWb{6Xfp(D+ZG&^fx=Ya9wqHX3egV@0wa$i|F"KX3Dr%7shyMDj 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 rZe?JkZD3$!V9bP0l+XkKn+(zQ diff --git a/docs/static/images/align-compaction-output/file_cut_trival_move.png b/docs/static/images/align-compaction-output/file_cut_trival_move.png deleted file mode 100644 index 7aca9aeb5439be722f87b3f2035c45bdb4cdaf7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14648 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 diff --git a/docs/static/images/align-compaction-output/file_size_compare.png b/docs/static/images/align-compaction-output/file_size_compare.png deleted file mode 100644 index 5f39a806f5167b471432038b5d9eec55a1d9aed5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 430123 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

GURQIXH}gYsc8)MJSpLor1h{ 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%0DEy)YlGcBqc*gW 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%0A&aySU-i&`z$|uJ>cxM@ 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 z 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 z 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�$vncUj99Mg47J%ST)_ZTk|V8XZq-F`WmifXw7m2 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[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 z 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

-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Μ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 z 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 diff --git a/docs/static/images/asynchronous-io/scan_async.png b/docs/static/images/asynchronous-io/scan_async.png deleted file mode 100644 index ee84189f450fd55c5315da6ad7a4cf884804499b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78433 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

  • gqx%2EhWZ zkX}_Pa7vlPe62!l0r~_h#zMzLVU=!PW?#%QRA3*m9~%?~fM$)wZiR=zpo0*I?cxkU zA+Sn~-1&Tvl$0b|0sUOI5 zYmEC5z)5n_KZz^uy5vFg`{VI&hq^52fC>0TVy5^`?)%G&;YHOah{)V)Me&l57dLr&SW8Cwerr zXPkhWK!QXJv_>g!yy8+DYVK@%=zC;hz^)$MbjE=c_9iy}D`q@i&L=YIL+yVdq`w{6 zIyYtD&fZ1+BhLnq73bn}9dQbch@&a)f1O``WJG<7WlnUn{4aSLlN0}gJ#$YmaN zkaqL#QKT)6Xt?Ajv;D02vAfiWNNLX`3_vsTQKG;gc$Ik8JsS!nLWIw`M5mq^bwb?ULeBdyP5i#3x#o|8gLHn>jnS8)Yml^Yy zC?k~VvX_C8Y;Ve#*|08i+M#NpkOQPA`8I)!tMwn7zrb_I`jTBu;{AKb!m2X7D_Y)) z_XFi6YZvGUBN0luorrI0Ya(LZ;Oc`+~=&)-qW$^MB|2Ndo9tkvx zNb+&OLEU=Hw8+sui8NSblbD#uYC440)^sMma@Xm$MATgbx~!7Vp9|5fJ509* zWkBbFfc*N-dqZH$t98cq0H9PKCh@VDKViuALl32SQ*GX(u$-tI6AOA5h8lmmnAIf6 z3EB*ajrLR}D$ordig_C=9ll5YUO6#FAy3P>^xqv$;D2rJL2R~KZ6N3{`^EQhi`X?6 zsYTY&q{FB;#9wy6=d2sn^##6J<_@kY0o`9;f@I|=HJ;0ow_uDPzz-g-#b+0dQJlRW z5cU8%nmQWjB^^VPmVzCkqWGX`=0)@|nb^yS(9kPWcWDFT33^m})X=EH#%`u;3bPt%WH zKVgUBFXU7J^{w6((V{SCWFNk$V#0ef!!tX+!Q-V6eN@7KR`^%bK}!R}XT{Q{1cCe66PTZk_G^VFa_DKAYF0qOvWJX^4_62JtO+q-#g*=! zGif|_4xadeCj<_gu4m5VUo?HWGu6tjx$OFL2F5^_Gi2RIbUC=}FqQ*E-_4$B|1uz1 zc;McS0Y|Ukr_X~e*dlP)f3n)@I33Mxv~XtRN_~t;-%%;?^TD4aaft10 z7tCmK(ye(DMF*QJM5~PD;BOTZPUqkc>fA`wemDuq;65|DqAvm66IR-qC(3C+Hb}(w zZhwwsW%-2E)BAW2;{RE2`0rE(93G^PXX3}rn{2nA;Q$%Khgw)v)UVCqb60M#3FN6Z z^4Y*5QDUn?iFH{(HTBu1XiagAcsVIVYi|jtDXIsBczed#rF2CfPgl!&S`pg&yQabK z5UUCVe+!Wl${eJcj>10zvCG`^_!w8U zalJGSxK!)002*@5C_#=f8Bi`x&$3Ydwt>#sU}8(DP*SUZb5HQ4Ir~ViKfu!@3P=kD zEC2JZ?eSPD0IlT*2tiurr$}zvU$nffzOJei?)Ot)w<@GJzSTgVH$SOs9Q`W|FY^x^w-+N19Y4!Y{02_o_*>C3-}@k0V7N$bYMy}@#8vdSZu6d zgECzMT$qp|CLDLlL|vi+YlK9IW9|sBehiX#nbifu3@({mht3qSZ)1moD2Q3$L|@Vz z7t|UN>gDc^r(_*bg;P?-Z3o3+|6x$pHNng^IK)#%W)y0KE6?}kndjlCwp)6=PS+u@ zBPa+87>YiQb*x}4x+Oz9f&yZa>}wozWmr#C-nm|tE-B1q==I#>+s(UvR><8-l->|E zmX~nsSzOSyy*>ibayWJBjQ$>q4jO{MOLVAGVuOLObUM-kOSFN3VVt1UW{Tiye?1>i8}nDH#AH37gG&_KUKocu4;9$ zwQ?+tjThcI(->fp>-hZLFyul3sZ}J&kDd;pbc-r|wQDTsY~{Jc-Qr!@e0$)R0`C%_ zO|?`pCtLb}v$uV|1y75vm6ql}H83#2^8XQlCCO6$%~&uAihe2CDvI2hlagf0Q0lEo)R28J40M9vy`QZzXDaA_&GqoMZY zkM1Elh_~2xZyHmnGLN~x;!Z_X-x}ldMAsU*-AFZ|F>ef`SasH~YtlB%<EES|1@;Io#*uQ;i;vSbzEFCB?we0DQ1Xd0}=o2bgF;+`&Huw9DLj> z@a4|oUtVrQjcn2%fz9K*kM`5^^gT$V14v`WucfKf$KBALQl74PG)OT40%aBR`;vE7 z-PSk*#sE|v4#WaK0=7wNea|P(4;lRMM#37$EgF^DW>pWsZHDjfgS>89*Z%k!0djp~ z(IPiE!{7U=+&~;i{{~Z1qmL5MpO*N;;r4tBfHxeGH->lT^QK<+KWJ23{d;E}W+!#! z8kJR4(DCuXdLGzoXw7Z^t+9H(R49MGG!Lu_JOc67V&2~E=oCH|iMe=kAqY4s#qyjc z-zO$Qd27puXBupmqXu??H4F|pb*VoyJ>`B$@MA`YLx>f69KEZdpe!_fkWfAONs(`- zkuS^^JUFTqwSTT6=Unz|{;`=Ab(vnj`=AW<$BMF35g!?iUd3|o(wxkPWE%Abzp&>M zs%7ebc4aEMLra{|X(_Lpg}M2X!S%Ai*kpbN{6m33?jb9u^)JoKG%AF)YhD+54b0M* zfIusTh=>S9MMb~MOJ}E81S&e4{rU_)#cB&+n>z?V2%bkq;PGOlG(nZ97pm6dQ=_CS zHeNyFqO;pF*?ephtj8a;Y9Mbk-%^7RWtC!yS{i^PtR5G!8lpj}mv;~#oRfkC_9bxH ziFBEXgk(mY^1|9je=2$=nKvh#HzA6A;%yx$GO1(muF7~bruz(z9KUrdVIEvQZL=M% zc=JAb1pTYxahCd;?SSWNSyn`1OmFyN22~@bOL=|Jth|}?X6+MQnXIu*637r-X zHAIv-x@c>CJ9Em!Sk`TJjtqJKM#ENMZ-skT$9M3m`|djWZcfQnce%xQMxWWVWUJYV z=qLlxgSSguiNs(0jvW+MVfz{xuh3hdfQFfoyOx> zFqyC)>!^WQJXjY~HQsFe@u5YYD77)BvRdntjOWw)J&AHs4|bc^%9CDfm@crc`BWCf zbn-;dAyFa=pVQ%pxe)v@j`FJ`yhbP)>FFk8F%Qj7duZ##eh|a2Sv;mp+b%C;cX?+O_gzc|qFC_} zqs5B8D6(6O2b~-N)e2TNl}Q{Rzby=5oyHLUi2#78^rGoRZ=b@BuidUEahBw z3m9u>_YZCpWJ3y@ykgU2XZ7968F|nTzLLk;$ab7$@e%(x+tzkY4C&{FOG-(}h&#!0 zT1&G>wkbp%Z?qq6j8W$9a8X%R>o*gTFfk#|@wvAwB+~5kdG7;6)Zu<&TsD~d{N4QSJL>!3O+YYDn95yw!c|Sspw5% ztbi3%0oV==9{D*3XDO%g6;Tx9Q9B}!m78ned&|@lWk`w^S|1ocXge`z$dMdULSQtB zbQwH$k%h(z@B4A6n#qfKTyv?Oth!QG58Ty;W!LY^=tm3uu!@4q4t@+FZ_;g*>;Ode zACvkpFhNF&`!?spc)LlK)}TsJ;VrFg?i>k~ayV~{db%+-j0~H&;6DY<59&TqsLNN{ zQ5=@zTT)1%UWZpR{LLpIp9BB68=QT;U!aHk=&dvZp@aKmb@KTc8|Wl)aYhaN^43YR z1bu%V^_Ag>I2rD{v^W*Q-<-7lmS>0qlB7yBe_7|b?7N`8e?*gr#t#P0@vU1uH8nF= zPe7TCd^qh~OH;VJyQw5{H>hb-BCxXhQgM*zNG|i!MLtp8iYdM0o?(rl{S~WYoy;Ze zVORHNB6{>dg2>F<<=7TP50G8T(a+7|{AO<5W^^=kyhufOt>ty<0hJ3lgc?$QQ<9Ki z#*`0RjidrRSLUJ9$1a?V*bCJ1+kEuTF^Ly9ZDLg|ioS4Wj74y`MxqbvqXlbD+LxZO zz~)%+W^=}j?tg$U5e!+S>wb{@j!p-`nX1eU>4NGYex3-^!bRv>QxCk70TT!od3il+ zydUu7x%TiDEK$n+NX^c+^}^S^BloqvQL8OD_MRbYdTQy)JN%Y>h+Bm~L>J`D5TTg8 z<=j1+YVvC#^x4PfXn~d7VN$oO)1u&XNdr@?ku~*~`9h^!bP10u9%TY!o7D^OGC6E6 zAADnrNOe&%Rp=(oa-}6z673N7gs5uuIwVIagts;r7cSGQZ;1p{+u;dhd}BvK*r`4y zKkfRxqL+{uGMBjwlph-ucB17oTD!C6PQ_<44Le0z3Y9GTqMX5?#jT{}73h~COT^>Q zb&J(%+{eV8sw`>c-D|hfG?|9Cv%9-FQ>tP<|9eSFz6x)l-lfdJfqh3}-lfr`JV4xS z&+dzyz9=NcIzHsHaCg{SZupcSv82zR3vHIQRgW}dLn8MS`J5n(DRi3Xy*Z5o67osd$1Nj(*M z&sSnUaMStXEn#?Z!Hp9t33(#ZmBIx_%Ia7Xc9%4a!z8~EY_BKb{p`^Go+~qL(@|)x~;{Q?6^q;RI`PJa%eqLe-$TNbAdHa6pgr*X52xxKS)NK@C2cHuG zgvjJCq=UCLvs{OOoXSk*dZH{AogHR4hI2AtCbsXK#F0H%`U{n! z)jeGd-ZJ@-pEL|%SjhZVnF0pYx#VWdR*r`ZKdN^2a2Xn@vZk^US`FEQN4*{AHKR!! z2HQ-&>k;>SooYcHEpZ0xdpeeGo)K#0$;Cpy6Z*E46(2@Y!B4UfGJ99Nfi9$UlcaBWFgwc#&MjO!p3YnQj=)S!O_&>T_q<48C>Ug zJoX=*dFV_zA}}BB?g>p2)D|2>E{eE^c@p{wt?@%b<*|)=oAcdU@sLpC{~+roKfmc< zq%rdIUgBr8j8XX~Rv=}`YV*u0O~V^Fz0#2aneY(HDF8s{{}vN2CQ70pOZ`a{tu~D*2CzcVG0&hC#O0 zCk?WnDEFZ$>|;Px4&{ zA|bBb2Ci_NKF>efJ-^zm@+N-M#q`!~a_HVpPkZMSGE6)9`{V)z`~!eNqV&ugPOGe- zS~-!@dn=nUk=YJg5FNTfG|)M*OLx?YZEaM^Z;(EZ;|BCVl8HygF|6%%A4k5#Y*!}O zuu%Q@3MGG)Ah_**%rzX<(da~Ae6O~g7Q4ks5Rc9a|2lx^hWTK@P2m3#uETqX)HiLr z+uK6Db5^FbvmnqCKrf=gxkz6nljL^5FR9in)n5d?_iJh)7bGx+`OU>bNsYH4W!P4C+fRN|Or`_5I0Jw#O0)9vbw>(2|q zz~bbRtP?Xln__;^hj*dxz~5P(FZBA2kIutHYM#RrY*D7-{#l{s2TMJ&66(6t6+YH% z4#%#FQ3?+dR%fjA#&>j_^)x51On1YQ-cN4*S-7Gs|89Bmbv9DbdK@4DZRlN2AkagZ zjAd{tCYFBt#>O$j?f#bbIt{2R#c)AX#bmd0vC@#8&eH3t!5M`QKR{VM6Ok&n{6Q#E zE;_{y7II0mf=HOXxH4F!@jS{k1R0iIQb#lt(5NCHp`hL|F_?&9H3wd%yVD~ZrYobd z20y4i*mpRT-#6sHLH>CF{q=#di@1F4sJc21n}r&PNL=QgSl>sJ?Y%vd^)COg@MA)k zGL83pa^N*wH-_||Q|yz=w9V>?Z2VGSyeE-V;USvULs>lVSQLgUN>eIG<^e6}%rOra>_3S|$g4uQoK0?8C$qmgl7fN7 zsmFnz4hrwX#!RA!x#*TBK&2=9(-}3%fcuYoy$`tpt754c?>)J|W*{)|1HUp!Q*&V! z2r>oty-n}@Yy1PzF}!0zoD?&aAkH$>6O2lUXY68!F*2qOLWXv7mLWErF(uTkn(b8@ zTV7izzaL%h!*l}3TRk}4Z{&~bE7f@PT8ZB!+!XfJO@5^OPzA?RXj@Ck9LjUWR|-;P zLVj7Mdztb0IjPtq{|l!aOepvZPy;_H`JQGB#ZR5U!MC2H+oNEOADqjOlU6+;IRf+N zsDOYJ-awa1YnQ^fEh=z7)K)dO7v5O*l@xszAI9pyYw#Cc5m0G*-RrhkiQ~vJfs{#3 zrB(RhC$iq>K^V%+(Au8!-C3gU-EVt}R2v_~^^6CUcT)pTlhbq$6u&)$HN#~qiGV<$4d#P{CyND~VB9d$};yZkeNdr2TPFn{!Uq4!FXQpjW6 zhAV_nozX9U!$*e3MdR~L=LuF2Qf#v43Fgam38Gk?4y}ze#}L$N#oW1~1`dbmkZs#C zYq}oVNxLYl=#WBltvh6kn$pMWCdJzN_oAPK{Gf3UQH@i0CQwnckIsJl(zbS}dMj+c&OtI{BqkoZmPHHNL-3Q1H(5}#hKRxzU`xU*wCvh6! zV3yWm^p{2U1(1Pe>aaFHs^o}@gk+cyRW3&Tp3US9-mK z@NT!IwaZWtOlGW`!!L-ghDarJkfC$Yx!S~i*r?9WKf~A*FGuHc$Ac6Rv1{PfpU;I_ zBDB_(OJ(8&?%#01+Q!y4Ntc8=nl?EE>cqCxidIzq*xWHra7mhcdTEJ5WKfB)LOYst zb8R!K#Gw7SuywU~@`p^uh0>%h#Rei|6nqZvLoM*wTp~lH!oR_2owl{Mii?hZ2ug|jq^w*qu-wiv}{-!Z&pMvDjJL>x?=eJ`VIeWXa}SRfn|SasbV zU4(y@-rkY(vtUb;2J#V|93bkAay7anE1t>7F>Y2*k}Qr9(H4zC(^&CLN_W-F7jMzw z+R7p11Csb`v9>=Xg z;_dm;gWYvKf>~%e_m=9hx+**$xhWKCKOQ_43z;(lYKHk zu&%VXtDfw*)D9YFZVL*H-c?@I8igAu~f=V0N#UQNsNg{udsmPcC!^s$hBkYi5o6m1f zHY2!_Z!{BmI-+zQy{LZ;JHx4OS6yC53QM{99fQIsGBjA;O7LV{;g>kidmT44kIiM3 zpI*}UcIf%;P;3>Lavk^UxWN%y&;`j%~Js3@5JHu<;rro4slbCZaW z_a1XnE@jy>Hx<-42yb$DtdH2?CQYb?$BH8ol@&W|iDbV06bm#(h&Avc zAY+s05?o;SrCC97Mdjm=!jWOztaT8>Q7-znRgHKJ7jDSO;GDu>Wic$@V)D!$j{(WO z$_tNs=$fBjTBmXv0NE2qwZ}0ZMlPPcNgk-IMCS8m9tS(EhvGD}(6%lNEs?rsVPHzI zU;)ll17j~G$&9>n18-N-R{|KIj8a+^*;4{VP+3Bv8oq%2M}w zjT};o$;QzD-JiTt!^OZ?g#cfiTxC@`#;_9&eYCKuv#*<+7xymOq9~dT{iK&7-6BG? zLvh}7js zWvifDc$|{1ZWKV~beY$Z0u(b&q|HFMZ7oQ=Y7FxZaa3o_F z+Rc$+dXHe_id9MwcfRMC9{??qDl|q3e@(+Wh;0fPo{icXr)l}lqg+Q>|LxHIoB4aI ze%M#v@V-hty{?=#n`|51Kx@eOPEPI|0y4cGIq`zc4o~w6jz}@p+}8~qEToc`liV#d7 z;U1rR(|^8K^FIgz4PJ=R1HTPL{EwdPc8(E{Y1UVv`TowB9iiDzlaEJn6Nd02kUeGs z62@K&j8tN-r=NVTm{zlzNVGe$*{FjzVbz#(Q_&`q24o3T$W9WzMs1wWBF6ui5-Q|b0Jh9Q3`ydMRESft3j#9(OqgSX2L_MJBR#|-sn-8$6s?ouL!t9_4XxL4S6 z#7sF=%__JJ*kibgjC}nld?2^O(dtj-uhMf51E+MSvk5v>1+%7I9w=W%h~?^-UCuNZ zd*MA|vvJuAp(jx@^)3n2FVLy!&6KogA}^|BoMef{r9kGAK}d3VrC+RPqxB{vBcl%5 z5VG7qmou$gvSPEK%-eL&O$p|GBh@%b97f-`RW-z7TEH>UI_ey;==;i9kdbU#?M@Ur zVi1ourSX)DU=|~@^W>`W*hnbfQfg~M=LzAVX1zRE)$_3^s9L=w|K=LKSN4t6e*qRM4@8y=Z57c8A@54>daehw6nz8JFOOq4QwaEC$gAKZGh-r z;C-BeKt?cXUR4ze_{E-JK@AAEB+h`&`or9L`TOn8gb}7&0{X@@Orh7jIiL+hwM-8x zvAduYP>=aBGU|OO!Sf%{`H8B&m^~H;r+n`qu9R5%Cf+T4&NxG?d?cAtI`ixIc|zit z3@=OHS(r90*S%KyUzk%r=AQ0OP7d95pO_!}DoVbS?LA)Q29B>8`A>#nZ=4vbz(!Am zBEy8u(W(=6iJGM@$19UY{`OWoEof>8L?emp9@s_5N?v^d!PC@G?2^J}#5u>X5i@=h zIxX8D_bv>QcP)|f)uFTmB=I#PZr-)0Y@&&h5F5!m#BX?lo>w(+9#-1!-##V7?IwyZ zH}3yxek(Ao1j71Co^0<7a7Fl>@!wOS(%+HAjfFL}hjJRU>jqDWpsSC|1~ppCW@gc2 z7@Y;BHONKYrdlhkU0ScE@z863m4^&@vP&<&|6=@R&^0s|$(FkHmp7}{BVw7DPQtct zWIJ~8i_3kP?_dlOI3o=F21r1O7VO%eECdH;Cmt^!v|9xK=}>l|7&egv42O@Uo@QP` z_wO1DDz!v1I$rRZ+U}{b?6gAfMTS>a#7peCiE&!z6`(i}KNY}XZ}sekzZvo}N!dbz z5DhiKbSgRP+Gdjc;zOKo{9I(N6N7bS5RN;zQx)+N3Mmw{kjJN7Kv}R*Qqx}pBip2n zu6xwKCJ6V{n2npY?FLcUQ!m+gMAgvs?pU(wc{ck}mvwQnU-vYUSQS(s5mHgU$$T0J znoN49teO^y6vn;lsyDmE%(pU(fwLH2fFzUooAS+WDO0(ntu0z-;vw#dp8m?1@M@0{OIGXT{A4Qih)HvK2=NPI;Tczmc+w|8&2*~pBAz&UyGyngDOn!q3>*2 zEOBGux%gf?>gyP5#bfpm(QP^>_3AB2PA$*x10HLYU6nZ`#k%T#YJQ2 zx6iYH^sEw!Cd;FyTiP#Be3O;hZ`t1I+x2N{qs#e`i;K_Z#lme>#CHt)Z84*ZYb+MQRuO%bzQ;<-zt#F>+B~Z41 zyDLyVU?%l}B~z(G9Y=dr7mMEv`m{}y0PUUg)oH#B@7K|3$#JJQFddg)uqZ90CR_ES z_+a2`Nl(VozoAQLgn{qT!gJ3R5xx}MvI+vl!>MaGZetE(*^Wvr*IjdYK8O{P%f&WL z!ebHyg&SPE4lcd`crN| zV1XD9Q)d;O85fW9h0CDlN1|hg-2q%Gd$J4RB zD0=|7eTnm%K#wCv)Uo2Nz%U!(GR2c=v=_SCXs7WxRhx-0ai6*Lph8fSvI$uHeEv>= z(4iAUG4!xmDHp3VVVFn92g{|tekdxF6tN;-K61AD8Eva?#yaRWC22T7k`p(BL#a1| zx31tJXH7~2^Mh3JiG+;5%rG(2f}GP&*^{kJ zsh=WtWe(d5;*~Z&`iqW%2=Mdi*f+NduCy!*?9&l;TGhu!Fc>f?Ks`vI%Rd~NFv(8` zpCdX6Uf?_lNH@C#78<0ozEShcgrKqZoVTShhoOF7Q>4u3FrQ3;j(CMhN|i88c8YC~ z3l+N5bde+DRuYr5a^y^E0$4kCLzN0YSSsZrF#_osxU;S`e-@S*^9&VUcr>UKPL3ex zTRwTTiTm++V2PkFOudEq8_Y5m)=C6~t&k|@PKA4uBP%-&v8?&RU&@qTR?SCMD2~$H z9CZ!}m?G^hpPh)6O3hbu=wkX@jDI=lUlejgqzFH$_n49L_B4ob(9{YRgk%a<;(z%7 ze|R`j&>;Zte^7!cn~hX%(yUl@Tx=C13~2Au%DJ_jVzjjRf-ZW7G9||a#3V0PxcY~SvoQOpLTyT08R%A{0_4Rv2@x3~ zi1T$PkEuS!#k*AE_p_rph(_pe$h1!baxDr3rXq9q5O_UcpL?bJ_mzB%o9U;;cB;5P=CyuAWmFTDkL zC&X=RY^a5MY1=I*|D4p&P#i0^eLv9Qh~lEyhLQAJOkP{Xo=9$F`iiaw^Y={{gq$JW z$=5ps4j_KX6g?w@Y>^ctU2!zPW)_*+QJncet?TuHaBTOd*$xp(8ETk>!E|VOL`*+j z2Bvk5$uObbY-enl#uyxR9GKr7a=yV|;o;Kq?t0%9<_h!E%_zOr2>Y(saiB3UzsRm@5M z4!6FT_cL@~gh`X!BM+W`g#AC%up8An20#ttU5BD{Y&m(aP6~<|{3&p+;GL=Rz9@Z7 zwCJIOlpIDG#$%#VEjs)uvD!QxmfjAfWI`3DF8XN1d)w+#cFsl?;uS1e^5xX$YqanhaN^u-rHV zR+vQL$qIkpxMJmip%o`TNMKP7Obnf2L(1c_arC+YA+BKWFdh>yw-Aa##D>bJ>H=kf zMaGBxhb#0az*MZ1SizdK0vs~#hrH*r3qe!$_W37|T7BdCQ9~alt;Ced9Ze$mW&dAQ zOoajpeud^&$nPnPts8hiM5~>94)y;F8&#xv(vN+(#vRuA?EtsKkxxOg% zd$#VQfrx+5LxpqgeNX^lxD4o6x7}z%Qj)$_gHNucP#B z_X^dU6UTeMoD{uezkI{%wj%z|x~+c!i)c*;DITPM{8U2KaJP34vOlBH;lgEw!a29PuRN$qN?n8G9(k*%b5tNjcR3w$|1_|kq zylaE+_q*d8iJSW$QPQ zawu5NjD$`Wi@$UM4QW6XC6K-#q1G`iRaG8^1Pw@cWou)sLF?!YOhpO&p`plsp$CS_aEZ_ovg`ynBPlZtc)*RfJ@V;0ic z4rK`j298RVBs^r&||*^!T9FJGdQ?8!pGtV-PrSaF02m zfjrr}CeJXlZR4ftJ?jyPcBM?VKsj_xX0v~xc{;ec@$=}#?_WWyaGC~>5aaP+es4~v z#rwRLKeSA`bZMB`XHlBow!KF`INYoNm$0uv`a)RCHXFL2A@NoSW?7~-_H{n=1MXu_ z9*aI%s|iy=hgo>{bcXt88~XMBEb*Y5v9t!hw{wRix>#?{q zntlp-*M@&3pCtdLD3MiU)1+Uk)G&*ty7;yDirdSc4G zNq7BNcs(At-kz)V5>jenH2CH1ozGfW-Dz#tc zZdq%9lWE|V23>xdek&xpiK*}C`c_$JR<#BZA}>B~Xy_!K*u8eF5kMVaz(W+XnMnO3 zndO9u6C3Has$TB!S3xMjC1gZZZ)IHPCEXbu!P?m3@!w5j@GAsvj!f&caf1gjPhvZ} z#a^;nEz*M6e}sD!KR4cqHNm*a-o}Q@{q!3fEe5oT9vDa^C;z;WtDSs(f|S_qz&gQ} zN!+!QjChh9lnPR*0$vULpDyCpO6p$8Zyx{ zGp`8-zEjQV{P?V^#}!lvovXGX>YUP@LL*T=%kZ14q*Ut zI`;8d{rj`JPLFHVFB}XHt;gykjYwVN4>s(U{%pmwu0kb#*qGh z0yz3?Dv66fLnYjRcyp%}!~aX6VoG#pfd=SD0G&L>GA*i=pXU=F2-|f8$z3j4;=LBQ z69Y8sPMqlC0tI`)=8g!rmX_BzLVyy|hqM9N_&Z+rpi1OaKn6JY_)TG<8DKs9+SdmV zYR~R#Q}EfhgTC++W%YnsFvc;zH9Xs(!Gi-JjH3mTM%;Ji#JRb7bKkr{0cDk_EB!eR zl?+)d%NWqTcb+1rbJ6>)2xI!p?T6R9-U(d5SO4gIo2}{K5gTB&N#Fl$WoN(lW6)Xm zMwDr!TQ)+X7Z%K)&Ae$M)E^p>zjOPrVomp;Zfs0<(b=q7Mfu&3u!3^Fxw-j7e@9nW z#xwmc0DFs6^*JXfAGdY!*@PuQSwNpaIFCGKn}bQfkg7j*0vB;^VKsS(Lvt|3Lld!I zGg(%T*bmXLDH*PHhL|O?@%F1cKN2?(F!|6g)KtB=_FK@qJt_8hZU14{1um(_K1}Oj z{)*0#oaRw=r#$Wxw!yVp3>%p?kpt zUNo(GzJi50HlXc-)_)~VIZP3rbTPG*MZqbIMmmD*#l=KtPX)H!`lj?Z-95oWQv4Ar^j|tC<#Tg2dij#`z4pV_wl<+* zbzR+Xq}zApsGew`j91Z(TC3M>)`n4Onvl3gZ$}I)n@K)*FS}>TCon|ANW6XDbXE+3 zK-7LQy)aGVzAc_;Q1I)kF*nt8*urpGffzS=z~b94ygrq|^)X?muI06&NEgw3JUsKA z&c81gO>rDz;eIL5K+je@JlnJgIVm%xu^wb3C%?#-m6nz^1Hmm)4)b6=OcEj@vY6m> ziGOq_a70d84BwTx|DK9~@R*L3ub7lMRp!^D2B(H>TgQv9WOU3N9fpiz1eZ$_-f0*qPi` zbbexP8DT;13PYB7M5BkrzGi}4S5TTjhKfTy)l6l!;VY3_T}|xi>6y7LlD9=2Gp4~^ zTg+!%_D#3Wr6_{w5K!$dUt@@YmP?k{4*q!fqJZ^Ln`OltMiful)k;pihM`JwXa|mCaEP8+z zVlfjscR~SU2q|l@!d5ZM5818_ZqHf3c01{pg-&~4HM<=YM#VVp0WO{Ia;H=m+z@L= zaBMDZne0RXA1ON%*o}6`Bf$&|SKqtLD7Xnj^tN+XzEUqWd!Zfv`HsuyjKp0~E9%SR zbEe!n2w|!ikwo=OqaNlQlN^Q(Xj(;M0Z*eBDOsK7b*xx4{1G)LLD6d3i=r;3#yY z1Mfkcmiq3hS%G+BffGFm?VC?q^H^}2 zuRaoQ59ffzM*c3d|73KnW|2@~YF{01Q9E4Q7WivVkecCQ!G$;htaiBTpv}cM6?w=^ z7)Es?4Jfeq{j^;nm}LXby*pYf@M?7CIMn^-Y~z0gOhU^a&y8;8_Jk?-+aHNnKTSVh zPzT3-mD=qbMu;?^c5F8UP7uiA3Hg)J$;3#@DZNMeZA#ZneR~D#dI~s@opalTSN_^p z0Z1mS7Zf~RnRxsPZgEn-Mye1`Tv4z{oYlFWA(=}};=Sf2O%DpXF{fJ^fhF&_{l{$k z+E!VGA!uZNEcho_X|$}h$x+1%`x}{3g8|J~D6psF0)=aA(L)ji# zo~Iu-_m#Qz2D;-&fK13a^_*@D>-t*Y6T-@M@%ZejVU$7Y3@Y^YW*E|VjeE&-8?P@GH+kD+g@8XaxG~n+!P+`S;us?Ys`dYCX9M*IJs+E zDeB&PsloL|Eb`}^c=%MehvC-&EMRTm06*-aEvxjeFo?B?GLv#FzH51%9=kx5p;m22 zWgWXEeevffBCU!PI8yS70B^Nm>6A+}qj2Mw8h|NyDaIb(AOH`90^-L!7})DE%*g1~ zE6s?Cab6TWKeRz;nZ$&A#}lxo1SrMS_Oe;aNGj++=K)vy*mVj(N)3O96WI@6kLBeO zDG2WPkp9-rSIznc%n?sS)@Rtq3X%wI$;6Y8V2|;yH>QT83AB9f_G4T(%SVknu=Z3Q z!$Ep%E|1x;23-r-<}OXVM&Gh!N%S|W421jBoja6WD1IxP)i)nYGP2Nlch`fkAaA#Cigiw{CRhUtx zWfn$`*mn>gi#J;+`FQQd`Rt$J2dp!j6F*8}@Y67hP&TbK*!H7vbO9G!fV5?BbYdcY zrm!deDW8Z4^|P+nkYVda?9yye*L6Zb65s0z3|UxJr~*QA?)=X_zw-*_`uckO>(`-x zl?2$qu+q{}ey0VLzAPbB|8YhNz-$hc^5Ei{uGcxvBkSwyD;ebF<)sRHat4yUyAV7Q zz7Dzg%8ONzoBfhBy;y2vCP3X;h)HkXhM~`%U|36yg{KQr;7Q7Y&`YY?w_}07Hs(Ie zBP4!MZ59p{d5tyui-Incc^DT^8yvtIpMZzISxZh%z5^<{(l^+Q>Yjf%106BrI-*Dl zK7GOmY)soR@p=7=3k%l8vKKmVaFEJyIaM-+0{|TmISULyJn!CoMg^5InRZ5#0TLzK9M)l%wa*Z% zHE6_wp6$6pb`FN#1xRK-g61o>6P0Mqr^98q6cjcn(SUn`2qLpno@U8i^9Afo9&-Vx zkKOwWr`*o$Kn+{H=M&d;O?oTD5reRBL!+|Djmz$cgZw&};mwQ_4qcau)b7e7;54Ex z8;C+7`0&gsIVFSzEjMG#CHZd-qs==&AB6#ne&;gdH)vCX0l1z3Vb*JofK$+s`JYM* zEiEl88yg#%h2KW?F-PKMigI!-kwg#69cD$=T%o3^nF7v_&D&v^fQcHSSaI8)u~T~Y z>C-2uqrlHjK>h-ibZt&H<8(-O@cW)t4hBN~0013~EnkMULy{__f`Lj41rCxHLD9jP zNZ3#qwQMkdP)${J?owki|M5GM#l6#G^(^LtR&^tRE@ zPLYRvwJ%4~#f4kD%-HUnOLNWx6us{PUrH^*jCoHK+To6>$VkK(z zYNMXxjhtd~MgYzUiJnYG7)=72VkUoBlmqpR^osRE(wi43p1Y|s9wqqP<`<%4V06^j zl7#u7&Zd8SW8kcE%|`SPJ0*W~{R#1(1h1t8*`1GZzCBiQL+nz@<(dO93=A6*`yS=J z9P~%0wbdp^``PC@RI?=|C4)mlOW)c|XT8P5R`r7z5V(B{7bpkyicx%hH80LJ+d(D4 z^HnCz?djNJ0$H{DW)5wg%CbubpJA_^FY!wi@NGS=q})mK{F)A(f5$k_n5Fuh-UEj5 zwZ-l}ga5%e&e$l;`?1Eb9OPND&uD$m4TM|MZ0ciDWn1$f2*wZ>7A^w#5#Y6S5PheQ z>AIVsf9&mfh0BQg8eaHNb{(RucVtSf8qJPAno$*7HHdnWn0*{Gh9j=ALich9#!WL5 z^!inpdX@XbZtUL@ljXZGavtiDAr};BDYsLjT@h>@S-^)M{C0&2)Ylt5`D$sbC?-02 zn$`E`XyQSWy(uB*2g#Qg0eO;PX(|D=rgI}Y4xsu2b3lFUePl}?OeyFB%lXq|sr1Aj@CfZf zb#}p(!vnSJ2c@k_I#F8)1oK)O#di=eh%PUD1nNav0Q;V@=^dgKy@njP%;?X2Cd7@cr$lxTNGO)1=Q`>K?q!Z~%tp?&-&u za4|c(QoYeN{RmKiv-Y6uS-# zRDzI!w3nCQjL*LE+Jao$(7J2Qv|Gk!l{>e2+W9-yv( zKb@Cy-{nS5wiawUC}dA7uc-?vvCsw)_>NF4iXM_(ou)k^P$PNfU2Fbh>Z1xe*>C=b zAGQcWFh}=XYbJc4?+W8m2DX$tq9{;f zJ3J!fY;e-9ry&T5{ypxoL8gO2=J%szn6!*qMZGukKvCV7S@6`{Q0P5{(YtzHuQH3x zrfTeqgkht}Sd|t18amB)mUg3~!5BgYprXnwdgZAb-ON?W%F`LqE-KkIp_QjpveSP& zea!+G_6@=!H(9NEMA;{z)uehq-`U-_58!cvOXZ>e_6Z7W&i|YgrfP~)@4%PxBe?ZZ zlX#0g8;{e1R0`%;$w7nl$Q$)6k>aT}L7uC%4=$EWxvg^Ut92z&UC;>#)NB-7e#L#Y zF+BS*7D&IyBPOSrdqE|r=evaRO*Sb^iWu4$i5pNY!>uxWPW~LoItM><3dnwSX&_WA z!T3YdbA^Gh>FTbvDrIX&t0Ka{2X*0XuXy;<$j;g0)x?Kqt##L?bQWZnua29|{yh{D z;ioq3(K*P&_J)04Bt(;=c{{z$d6x?U3^OlR^C*ezr$T6OrAUy$F#?3OtRKdlsF-V|TG%N6)Jtj5>}#DmMpb zj5{5;MV3X7@6GAGV}(5OhVYH=g1D5t#ZG=qiMsQ1n(mtSucyfCae18a^56AHSiNKk zY-K=CmSTE-eUSqrxP^h`>%|)ze~korose3<9JBRDST5b_<4C?z@vU7+X1^;M#8d^= zpz~{5h1NN1`xvoF*)XH+6!CKP>$5H%_+_X_dv4dJEl4v&LRDL-Xc~ATE)3n+&kLUK zqaymB1hn!QbFXL)Iy;DKu1M)F5af-1g1lNd9~8D$uK4!x#>cIt3ZQkuR84KfRe#o5 zz~D{MUF{%t@%bn7$r+6rhYPp4R0ST#%SVr#$Ri@n9H#A&$M1nio-w$|;6vMnn~*dp zppl{FXSsX(^;9Tugw!kTZnL;Kh+m8^)_O3Z5O| zeGnfS$`b%g01hvJva5ZaGq{fa19?16h37Pj+CI3ASGRCaQ#I=uow^pyL{K-?2IHn3 zZ@$GNu}VsuWSc;GI z@^1Xg!&u74W)2%F)Z)97>;Op0*3MpU;3L2kg{IXroN*F;ZC9m&cgmMd=@tWhl544f zq3BEE3B6#V1C2d?%@{F{SCSttkox^vwp4TfM*x3?okxT!J?h3$o=O83yJu4!{k`}; z8cK*Pozo2dw~rjiDHSu~9UqjL^)3Y0LF#CCB#z$vgqdk?|C{?=;G$$pf~l|e5P9GD zY;%gBWGs5oec}mD+iMPdX`2}-yFR~6_F8zbkVwc$+WrHplOJaR&lr(#=z%P^F-1?` z1Q~W%kpuF08G7`dw@$1yACt3G)a0u9@Jys<;0n1`FAayAAq-}{6dfxW=I~I?pMVJD zO+fS(+6h0B!2~U4!*NBp!I4v?rgrWejelpz=v8PpLDztx#`2;IzYnzk=*5&n>w95m zpeplPbxq?Ap!n`ClnhR&#qE{SZ^0kxsuG@K#L+5wssY&%!W@=%w{ee#DVA%iHhM9e(h}Z6Tv$Ym?E2PmINI=YLW{EXA66%UFAP_Kwy z5WLN2-mn|WA|@!-$(CIHV#_cEd~7m$TtYl)0+t-#6vsJ+Iejn$!|y3oH;Mu*As!SB zxv%%7jY}rqYC|x|?wbV2Z}I~>n%z26E-dZON92#iPr}uR@1okNdE5c+It|A zqWfm*$O&Xkp69D+ZB530a&^c9K}wFiy4!>KTd>$zVC-~YdH1t{QG3vVSjb-l|Fx<* zh==F+-)g=4qUE$@Rb9l199~hP(LWZXHcNYj$Kl4AUSK}Bz8Y6O zqJ3)TaVQiRM2|EI#ihtUyifisk5lPlaxR#ODYL9hn)o5s7v2bwPdL3qg^00_*hB^+ zxvZRbM|OXzzd(CEQ1cI_@5V^Qx4s1U8FUQzIk!^Zyq0*e9Nd8=pXz|1$0ze$gSzUW zdy?MjPP7Jzf2d@gUy!X=fA}&^5QbZ`q|&aG77@S3Z-KM@D$R_ZVeStQP>)N>mneUA z-`r!QURd^HSvr+o{qWEq=7gIwp{-jKgXuX@fys$ad>{?jh=JE8BfmB&m#!wDlt~m1 zcZZ@?)BE2C{~b;o4B^&>p8^m6wLcKg2}|hs!)$!aX01xq`A4{ynl12ag8{>V9lGS~ zQTQVebDo8c=bDv_f*XBA8<4?-OGq5>=HnusfEOiOnJ*4eJ#W5kn-CZP&Vv1C0FtHBQ|xK?tz`2e$BWjM>YiztaHJGIVpRs5Vd0@A~bTz44#VfMxJwuxc&ipB~|S z^}QTrCJ>G7jC`3}TBUhTqFBVZZS ze^crK3!*+FZtVzGaJF4)KET|&JU7s!6bgUajh?RN*%SN;TBlBI{W#W88BSoWjKw-}*;sJ@Y9 z=*5j`1oGPSrV*-A6ZffG<^TtC_&4VPL+xNic9EDV62Gv>%r>jy;x}l?VZ9FkY@m@* zOj*X^+pfr8{YD$G0YkLaSlq!;CJX=JTd!8RqC7;|E%UpmAUwO7(y4+Z{kcDPUL7_S zPCQ%(8oAcbZ(sghGjCiNX4O4$6!gRK+6F(OZosv3a^au*au&N?k**oBs}YSxMDXWx zm(NEMhB;eEIVvX|{*~HWXq5zzcAS_#&KRh^Q!=Fb>>WoQOMf;z_yvvRN&LK8K=38` z*4L9@08Ig>c3G+nHmFp%iLIo7=rwICmrTp)xWDAD>>_5v3)LaY>97Va0bp~Ws1v-U z*e@3Q_5}|Yf1&BNSwFbU9?Irt$aG-*6&W}RExjC^|co&`wh(^BITJv--Rw)N;3a>f+_MpS5= z5_i~5vH;Zo$$<m zo2bF#u|YF`+Z({hfhJzl;~h0F5ZUrUhfXtLDH?;%Ew8hWj6)#(A7f7>5}wYzt1?% z4MZh+665kpi|`or9OixqJsi|DiU0>_%?6i{6!MG`JRQ>^ds>9j0N&0r8m#cBNwUSn z4fpWN0tE{HXcI?2m28pohuN)cZIF`<(jkpjARV0G6yg_fTVe|_{z&-`eA z@nxv@f{%$PKJ{lkLcBq;g%- zj*bncnkb~v8yTl>cDzc#4B*P9f<4&3g0B71GH{O3k&ko(N2t9O$! zzBYltO-|U5Ldy@QKf8B--B@4GO(^7M329_tVxp6xbASA%G%MWw+|Js%b@_XWG9gC{ z!`ni@W+_Eqgg`vrrvm+7%F7>zRv*Sbkk!&60u0-D)YP#918Q6>PqYD!W(3C>d(G0Q z07tJ>o1Lu60lVPi$HATwzjrpI8d)0N3>;4YQ}o>n?1BCozdkbgfr{Dtk$z>quRu3prQRAK*kuoWzw!|<1roV~-ACSo3J>4i< zY>yxS~Jwf7|~;eud`Su-cnfZ*&E+CcE{s7uaVEHr|f4? zPyJx%=+@1zRfVI(jE$)w()xvD9b<8js`6evY;-(5_y_8x`H1_lOU}LCbu_BO84a-$ zpIkN4vj_;NaP|fKe-)-gmqTRrfK`715LXGyM3b6{>^Gk;v+w#}9s3U%d#)vI1nz@` znFWD3A8e&B3nT0J>Ll)}1-!Ce_d0u8U+{)I0V7_o`)6xGUEvhCbvEGd3jvKg=Spj4 zI4RnGzEy4m8K680bkWnnxahgGxUF?3$P_mNMiq~|0z*7PYa~2l_TwLQpO=f4t?9+ zm}pmB2Dq0s=R0!NVGq!611gB*o4(8LLW5j4rwH<-Jk zzS@^12|~^U2cP>74vX&>yBdFb9)ZVv==@$EYnt|HfX1o;AO6+BJ5h1bGr?^@bI}Q+ zy0_5D8e4laGR4K35ugMj$aQ069`u#qj>7YORxI`2MB&klxamNy_zA=hOh`m*28cSg zf}Yl^^xl1MqUn}g%=Yv^Q8BKYrybG)i>Bpe<8NEwO8ce=u|OwVu~yT-4pWHx46RC23UtKO_;;|LM(sUj3^ z5BqgoCvxC!SVayY>hI!E+xpe>*-fBay!Zd5Xu{dN!4iKCvSu7_HKo?lP=?B6{ByK& z2YOpfF4lEqQUSL~_8)4UJjs5WL7QPZ>Dk+{#4;%_lX7up%lgbu=?^M$E_x87!xrvf z?JPzONNB)p#r)%4NKpK;x(Zr@(MhP37Z0j@E#K@9`z*_&xTpyAl{9YG-RAYb^m)cv z@vIB`nMxi8Vb!YTt`Bl?me&RY{zClJLCHzkV{#No!>#cMwW^QBp1bMLyvp8e$I?C(ZDzpfYxOYWbO)pn9L ziX}+-8E8lO{jM>qR(bLB%;GdnCbPk+!n?s7MFZ2US{+!F`~xt`I*QJPX%rWC#XIOk z%!g$nLrF5qmR2H*S+1Uqh+jX$kOqut*8Pd~KUK_HF?Ew1Ey;{&i^y1)Wr<6qV zWLs-%%y?3I$FD%mDC-BVAODF{SR5}nnx5J4Dc0Tt#3=MbS29(@))BeQ8>gTB_9j*7 zczA|4txDvR(|d}4J&Ll#FTWBgnaiE9JS=*uVR_MD@SeEB$#hg@y^1`9ML=MD{>#&^ zj-+|p+PTG*kf!DLSt}nO{z3DgYBn}1s^DiCP6Mk;jiO}6I!tzm)gKe1-05f1{PR#K zr-KU(k(P9Ke_-3e_rvV$x_4f>)v${7EgIL8k;PMLKq(c@k>l)A>2mINElZ-D5R_`) z-)n~dj!mslaxl+c^t9h-*sjOLfR=Y*!h<1O89RaD2f8E=*D<^07>v0usCt%$#63Va z8K$K|>++#W8TUp&OxtMH(!`gv*cF$dHdNP=D(K4c>G(MX*MsSA+F_JE;dZULfL)S^1>i8MF*dKiWOyR+Y{f^7(Q$xy93u%$vd!%)*t!SI8Z8YUvP%C$fZ|0HggkA5pZpZ?q#TZ~JKxnr`hVE3%i#d96FysWCHr#{{hx%i-E`iF9?DtvW(mAhezzU9wmL*BGUO=?A z1Kevbz+Or$Lwr5^RKU<9|Dj!`wO{0aR^z{qCpaUZba63aNcVgDNY*Y2uZ zE`;x?lWrsUuR%5fW3hm5$OGrUo>~J2=@J!s|L>r0ZUUmIAObDpfY_z}ucz8W(K2Ez z)xU1}zeeZ(WohCES;@1Z5&v&LYgk}Tjd`8k|Mk>5kcufV;1<3HGXKv61}MOsyd-0e z|LdtPz%G{ge=hu*4zit{-2m!ebc{DX%%$J{qt4Ckvz2l|OG#H(*PY|zuwm;h?xfJr zyWD(y^b8Cretv#dcK=9=nZiF;{|w>tPzPPN*5ks%;T`52I=sBR`9($FhA2qC=G0Kk zEk1xI=qP|>&1o2$igj(wZ{zX%UkYCrcmt-K2Kya{#zW$! zA_Sn*pG~|;$MA*hO#nfX0~nK%vZlx<-bb{IjCYEPiev~taqh6=SecHUTvxtpF>b*G zZezc3rSfpD)u6M!s+btONY-g(4)hNnVk|E&Lz{3nt>5P^*r5>S2%=o?6WTTfP2S&j0!A__ z0C;M#DQmP)dM^TK1{R%j&J6F1ORzV1?9u^)@-cwA(mytwq_Wf5IG>!o40T%Uyly-N zx`gb2&%~B}nO}gFw>$PM4m8HR`%vdY``nkH-LLd74+Amvmkt1dE84g@r{80H*=E!F;|oM7P>D z4NpuZlRx0qeN~VjKMCQe`Z3DlVrVc_sjxw{dsX9(T9&kJM%hK9(Z`ySFQ+}lVJTzF z&%pSga7_1s<(D6#Ei!u~%&$p=(jHV)q}n{dNc@x-{uvWP8Vw_1+AwF;t=s8$Rv$;j ziQmS>BxUpd*+tW;P?;-#h zXs9qVzp(JZ-UF;WC2?u#5G`$OuU|>&wOaaxwvvj9QOc^SlPxV`Bkzjd$d3-67@3&l zxGLXY-&KpxKkMx5d|y{LaddRFd3riAF*(VarsVZm+sWypXby!_p<>ljB zMUruFD2s`Uv-JJdTwlNc2msq7EGdm+3uJb_e^;z;Y&>kjxWWo11`s zm~8u4P2+S=L! zjlFluDk`V0$yPL-kJbhK|7M`A( zx?_}I>05ewuTuJbAlBCAo4IFZW?#6_y9F_Ea14M5Sn~h)b+1`9ryx44yC{jw^Xw=t zGj61oW$=fMs%l)}(+4>ALuZ47gJwCR>;^hIDFEE)S>S17Qxcaa0{GLqt+{!5SNU|amudReXi+*XVMCb26eDJ~vBX1do_1#=-gYxzHIk~Ki zIM_G1;Ywn9(bLmL+Z_W()!v?-H|9vPBEoLBE`G&kqF@L`Uz9?3Uj6yd50gFrW-<#oz(JCMXM*sglyz|>*-`ZrI Tk|xw{fq(MSDpDULOauQH#=}QX diff --git a/docs/static/images/compaction/full-range.png b/docs/static/images/compaction/full-range.png deleted file mode 100644 index 5b2c9fc61e4df0c44ae77813e1f95df8b5195c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 193353 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

    4Mes8fxIYZNnDzUJ6+fmz&eplyd^%CK?$omA+ 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�qDP58K;*m(?t=5{Vf#9L6eCayDHZdYgu7d}pw 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 I`4`at0RR}-WdHyG diff --git a/docs/static/images/data-block-hash-index/perf-throughput.png b/docs/static/images/data-block-hash-index/perf-throughput.png deleted file mode 100644 index 54948af2f8ea9406af7b11d8548d5e164c61e7e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35170 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 diff --git a/docs/static/images/delrange/delrange_collapsed.png b/docs/static/images/delrange/delrange_collapsed.png deleted file mode 100644 index 52246c2c1d655da30d203e0fdc05d5c06eb68f3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29265 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%0A#-z5D#gmDmAfrG-&!l5_NtS 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 N?VGA{1vg9s{|{KkL?i$J diff --git a/docs/static/images/delrange/delrange_write_path.png b/docs/static/images/delrange/delrange_write_path.png deleted file mode 100644 index 229dfb349ac12ff5f72581f141ea7dedee6453e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109609 zcmeFZXIPWj7B;L1f}$uQO%zl{QE4I~N*6~^QLxcV5QZkwq$h-kQbuVy(!0_`N`%lu z0#cRGq=g;t6f$ZC&lXyASQ& zwr$(q>(_q0yKURf`EA>FjC1V-|M_s}%CT+Rj&8gD>*f1C+vkS4Uz+#8C1y_Ktg)$v zvDdP3f4F=Ad9m?;kbLdLgPrpAYzOsrw(|$DD;MlySCRR&{=&*{o?Wl;?WsiGd$Dq1 zZeIt?B(|qN=ay%W-)b)k`O3KR0uQi`++s?+$wg0Hb79TXa*c&h8+wA4r^66hcyv@6RfO_@6 zeWT;I?QhIqt3R38we>LHU&J-P$@5buT>XdnxL-_)$|oI|cgCC*)Qcgf8eNwWz;Se+8oML+iyHvF^g+$XKpw5+*=0M{K2g! zlK>oQ5B7Hkn2BZJ?neW~Irvv_5{xjr=AW);SYXyam7XHL<)An&u6dJ*dsAG%#c9RX z3{|*u?}+I4I&$PlkmHU^72^@gyOGi+#qV4f0{`WP{^5f1PXQ0wfPAY5+^i4>n?#C_ zRpsU~j$7Qk;LJ(YY`BtS%_yZrh4!ay5rlpT&pTJtiqFj-M&G%@=I#}|12Y4ZOR zMp`EOfXRDBr7NsAr|TY#`B1ggD3UuK(f=41Bb}R~2>swkU9L?RwD5T_>IczdDOn1f z++Xa_mGw|xBFR-VK^@_rpbGO^GZE*MG%U#LMg8{q4^fh~Y4s*iwyQVaRI=x)GPj`2 z&3jolr%N`LVJz*;M8Gk4$INQ1u>4U)mysk<*j!=l_zoxW8T4cd?2Kn?yu1X_&b%@2 zvw~x92KdGM$LfuUMq#`AgP7R@^zI8(SWgJ_(x)<2_{QqNYRP90x^-SVZ?VFudmz%1 zc=bIRvqbE!KM+$GMQjWg$!%5HT&xRvnFUR6yBw&WYltiu3GatJ`PAFcGdk`vMjz7rawn)9f@ZhN{TuiV@3&bGAr?6ix6ATHoXd}yd-mw7HumCS zk_LI#j75TiMJ_&nBFFZn$|WDa;kZ9wINH#{IR8Bgg?fD0`Vu%%xp>sGtDyp!+G6nY z4Q1GGf9Mq9qEmTKk|i$ElL^%1TA$-bAFU4ZAM)BFFt*&!Ar*P)_qg{!+qMM+?23vl z3QNmVC&CCci_xV}S!}th+RN8mA-d}H)f*a7`wlwx@WYlGZkC%mS7i3it05a&ie&wl zLZd&tFDtbY81tP>jvb?(T3Q)!M9!~ZxSGZ)7*TQI0^^ZS3XwH3Gp&294jVH!x-3*g zwm-c?MuPo?uTg5Sh{-KIOS}+D{a0k zo6GRfY2N+BSbUfSN+jkDnR(MSy;uZ1MrPTMeLnjjboV2 zJ{U|r#j)5$j#0;>oJSasBwHV*S{9b65lIbu(93)3H|d*hm%h;#ysHzdhi8|r>{Gp8 zt{#Uv$IY*N>}TuIp|>4pP3CJEXHD$vIvdamVl2dhtKhhqaixbtYIwd$$@|5dmpV=l zJ^K2KW({E@pYWdg8@r135zqTXi$Es|0fC6LlzfWro6P_6^{j1sw2o1dN9Xlu_~1hM zc!VgST+0Xq_kHHS`Ts>a}3%EZPap%0^t-kxKt=;M@WuJoMk6pvKtOha>D1T9#jQoRTc$H1RjCTeh&3vzB=phrJ;+8n+Kw>NIG;l^G*teAl>AE;k!V z$2HT#C<|39ZI#=tR^`~T{KT5AaRY7s5yJ8(m8CYrZqGHTGu$HS{Z#yNepcP zEh}wNRo>yu$mm_SR&m3OVBHFn9DGwN-l57c6Aw#Xuch}YzBnsat@l8xE!>Yaujsr#QLtDAbypz z{e`UGtYfX!k-e2-^Rq0z-!3}Ks0Q5UxwC+!Em-JdDHsy}0%`Vc2r=_Qx_Y=&g;2m< zU*opN8uzE{r+;}`$TiXY@jj#QG2Knpwq}ijnM_5fL7Ya*RT$;eWRhR@;!VtJSCc}k ztD{~mGqZZng}uUkw3C5H&>V@&?>9Xy8&0LRBd}#n!&|t-vl$@$IY#UiVx@emCvWrI zvAX^#j;^0uzqa|uE4i+G6;os{3}Y_Yt|>{$F7u95UMR1f2X61J>&W1B@sCCojCZ7V z=4LxAxAZai=9+~`#bcXMgTNRJ$Mc|&dp8OXI$(c&**g_hXP?`<2ki$Zj>p-=$*R+* z^;WWj#;3IX4Qw~D=MU`{IP-Wb!==8fcs?7duk+oFZAkAwc4MwRW}l%I+0fTO|GrrY zzt>uiAq9)ijKdOLv|7I~LMVLh>|=JKQBATohoytUoC=bKS^&8VuKofVJM^hXHJM?Ejll1IpI zB`MNkF^w3L%MYl@%hcx*DMnoLDZUf&+k5U6l=b=sRuNyGjro+PFkV!3e!_AS+dcDo z>wztOTLPMWBUwk699k13XkqkD@oC0hkqZY4^3Bnu`}Z`YxYBoJ?~cACd_Mqaapi3S z@T}aE&)|1cD}eP(NK|WFcDUib|Cr(!U({35`djm(-ujpbz60 zK1-xZx!y`vDsjcC>&l?#iZtzG=>@-<7M`E@9vd`DwSC#VmAjwk=OImWYnJ1rDc3!I zX9-L5Yp{s(DUIJynOJJ8i$FnathUt9+~jALD)?B_H`anyUilx8?_DMqHi_a=$-YA) zBNkkr#)25Z{_qQT#{*DpO1Z8Iwhmg5w^6Z;W*~4JHJ12sd%-{^SHVKhKs41^GfF$U z2&<;Rf>w+eXKim2?QUKDbPy-bV{n@D2@$ihGdI~F-)^)vD5wBVK@;W+)y+l&IzQi1 z9KK(EcO>zHtNVUEtBD~6yxF~XOK(C%-zV8UaPhilAA5cM`oAg6pHl942uz9r?fAy` z0Fgg{Yg|Cu!Y8PE(Z&Gl%H!TR4v<_+EE0el#2w9%C%lom{hni4|%uj#jU*bzaw^O*qRc& zg(=haMzrbz6rPr4b>0P`ten58!rWZpp4>{wivQ3smGsnt zd{+hyi?k|3V}^xqJ9-lXzr;YP(@+O>+$L}!#ET!(V~6rK-%3@(K$T-2NU1|Gm-g%{ zKrd8^jz3o&8`BF@R~K;`uJnwaEeWQki_zcp66sKBTMI2F>Hxg$20PR)$xX{^M&i%C z|9A5g5W}~Bpkzqm%Q4pB`@~*^X>P{JG}4z-y!(}D8}hb^!`=jVj*7d~Ot@>ot0yw* za=GJrsPs%(BlF|4JP6PLICcE$HIjz&*_#>4%qB;jk z9J?fuRd?sTheBQ4<_ep5PVL7bVqL2iYq>dB&6nXV5qcmLF9ymsS9~B##=Nq4%S=u~ zayQ6lmvX20{*=twqXAqy_9@SOs71@|a6Nk2>CJ(yOamDO;w6gwhnYWN?SMCYpKO1# zmA3Gw64}su>hyk;^0t5&fwNr&nd*^ke^+yXsRA)N|eoW&!4+^>RNclZFmJg-teArVju4sN-GDG7-leX#rpB33A_R z025W@b4j7)qm=9Krja@ z9IHjeLi{Ug3XYEb{F}5MjsX+QvkmRPEZ&sP*RfDp-S5$T;dEDaHn(im2c+T97HD+G zUACB}M>*|p8G_33)oX_(wN55sR4f^u=4#PaH^@JF=?{eeHXB zUA)xieh1F~*x~?A{>-?K_w2V~dh%xn+N{`3S#`w|$)#TxSNjoeZ@0Pp>Sy!p$J|T{ zlwF;FL6}>MYgl&gDt76QajqJFQ#>7R*cqbhcM@(t;^?qhKb+q7@yV|JQwMwy?R~4w zjmFbGdz&(@Cx>J^$|_5=sy;p``m;9_02a37tM-=@;L)C6zxGxeCHs?^s?ihDCb4Sg zDLn!AM~?tR<%32`J#1r?V_Gpn?Tj?7v%;Zv-A&x5I9TgPDGB)l^EXOqU1fz>90BHN zf*z!rn4M$YIX*r0I#qXkfGn`U0;M!8zGg~9-gI$Mxdf+43T6t6T|A1wZQ)A!Shdl5 z{CQxB$@)iQ>NW?w{r^x~PprNvmcZ1bnIuzo?H4-1=2CS|RTcH{&PJDd<)_E->E9AX zPX5Dq??2kH0p`}r)Pi0EkyrwoT-`J$Wa(dAfCU{UeqWcI-R7;0nmq4^YU9VjuV;46 zO~t(wt)|FNqnV>)=V>o<)}+=wU9I0`Jk!lTp9)Q zEBNpJ-;FDI8BGZGw4XiHc|(vPnIZCle@i2yqxe~Hk|Gi zQ@`=_U+ex``f1PEoNhfrYj=4Mdm2^(82ddQ2}1v3%a2cIeA78u#gt0wL}G3v1fa#! zVe)P70J`4s<;yGVax^kSk>H0~ZvNwhPG@JPjtCqBX)h@&%T9EzJgo{66Nkny;ps@` zdSq9X@5+XLNC=QOm2Ea5%EU>B@WSQFBL1%P6bY$vdyj_a1j|YJMr@v^^n2<=MNy(XE zf7*OdCE3v4o4$q@9Dsg%rQI34S7f&nONVP9D(jT#E7ePTxLD_>vSzf&>82zTj|}vB zCX=iA<8jllW|J;av2FPc7yH;UrrMBHZqQKz0^@`7{TrsumkG5&+%(gxb?tU2YLVat z=b^YG8z3nScu`X@Ov-Lb?MvcGr5)n^bEKBKvMfAdAZDQKk;rPLKG5hNmAyG6$7QT*;vs7e%i5eH$M^X`SOua8H1r=IumAiyd7DJt{DUAre+)hfsXj`!G;mn32Y z^g&6epA7&4d6x#~Z4`^tQD1oN-`8-e7oDIF^Ccz>aEAXBNonP>#w!dNWL2UFZ!NeC zeNK5Fp!N5hAsV}vqnW2q0PC=hgkI?{^KyIle|I@!nK`yZbqRkg;%U;CEP=Xmw~!P;dZH zg+qUl%`^QHC|LM0Hq()rYp=8B4joIE+XDu@W4XLtxl_cH;M7ns{a1SOcetlvS_`^E~?JdVVw?G`Oh?|~!KaWh~&WObd>bBmOdHD9e1 zfD`acv+@X5J;`exfGal&Cru$Is-tY@E_?SH+wogGra@Q=p*}>@}I>uKOU0_u86%GivX0vm1Qf7oIU-#ya2shN>0drqsbdYzm1Xb-DX^sEB zZNP&HdR~Md2Gq*zPieG{tiArHu>YP1_>krj5MwGTU#Y@Eb5vzH9f+J@HoBLHQO)9$ z$=(*DU$kNdq(Tf!$o-P{O~!wTnULc2QV@0A$c&aW%wkoFv@MO0*1U%s!>|AWfJT-B z=dztm+{lua__`|#qMN_@+;>xK2esA#i1gOgLhW=2)OgW*p<+@% zlvM%Yo+JV(VnSsq-<`8YZkoAF7KRZ464o~;zN6A`WY$F^`kMtXUC2yz_ejHi))T{q zz}C-H>|j|%`6C06TujdqPk%47IlI{C<-2i(_ibAa0?EW&GO_Tn+qr!ohp3UBru%%RDMqlB%l+ljaSSAg7blswM9fa>qe-v!RuwA9T&n>t?SZ`F^efeQa@WBCMCNLQt zFW4{~US9}gPwTt1O0Le6*&NmCGPmN_M*Kw@Q_R=u>OMMqG%3ICgt>(4j1MSv71x#R z^IDtPq5`8~`I5uL%RHAWyBT*`tIr@e%C0H;S?S~3y@PiXsjjjE@A<8IX0A|8@@on< zZ=%|)W6XW2ZCrvd5l8k&P^r=@G_yCbnlQg~wsnEZa(o5{*XzX=ZuY0XW?C9gE=ZI<-eF#yaeSbT!h{*UQZJ zbW7e-Zkh7555IgHk5nJARxe%NQw$J$Nf`WBx>>b9{DF$0ncLOTs0v~j)2I1YG z(3VgEn|x${;)st7xm3qtvb1peiSU|sk=o3W}ls<>i=IqFG8VFk_a*u)U zqc0ZX}Ktwz2hb6L(*iO?l8e|(MdgAhXS=%UZen4SaW7hH~{spwl(YYVK~Nbcm3laf&?CjKPp#oXzCQdRT^! zQG47Qcz!amBMq$qs#i)MSXj%o}Ag%eeyetV6oYi{@3FC^GoU~XED zSZWXy{I1|EZd4!LnUYswA|C#7x}PbRv;W)?w}Fw>S17VUY;PDP#rTp8eO&yWqy879 zBf2zPx>%8x(Wa@)d)RryrZBm1oW>(c>v_Ii<*;=-8@f-@bzpQ5)_ovGWP!w^F5mPa z%uVb0QiJ@`BZI6KDDvUUAh&*ui8{*eZJXb^;jA(K=h&U=ylEQIZXlXs8VXoCv9icY zVqKXDt>0!};~xp30@63A#)PrW7hvA4zgWcCi*Dns83n)!N&{Mn0am`7S)v#U!>w78!k zPP3tfU{OVPuq7?$$h@1MjM2ID_7v*Z=jQS|>y&NBTQAwzc}cb^VPV2uAKvlrZme^Y zl?*vNxB|1KZ2km3fH3R~o3z-#)xHbf3quFNs!wna9vW_!fsp`zK{C*bwQ{gGC|rRZ zDdJnIV{e}D;$kz6ho}6wEQl;Ymd3mI8>-IAz`&P@QcO|)aHy}LD2!SoGwW3)PpWEL zEn{XnwpYdx72Fr<;R&PqlIt&C&ZI-8ENoHZsZDE@OpD%XI(?-iJNx{>KB9Ew`nMF( z0P|J(#)~A>y)!zdT8lnYyM`mW)QTlCyGyiLa>@m{ZB^ER z=_tK~+q;ad4G2kkH{Z2swhTPSaH50}g{!uM^&12!SkupShV5*v0#g>;dHt%>ne@kf zG4s~3^cIVGM_^eIRZ{OHNp@vEOJjM>yi|9Q1Jx4yXeEg2s0fCWU) z!%=JuvHcRroIe$Vbo#aw|zEmk)nyu9Y7d+8aPN6&FoRAT>VE&gB5a@-a6!T$H z!xd_LF!1}aJW56D5B(il`X=?ZaI~SQr3uB?YWFD`}FdG;g6q%tJ+a>G!D6mz#z-LWanPJfEnl7lIcJA|EeAYZKSy29V#JYfm zwGi3WHiE|Ta^0Mg9XdHjNS?cJ!z9o47?DBuyM~c+Uae0P%_>A`-&&Q+n#83&kFlYl z4{SX=n%`K@F2pN3m{1UMEwR?O@2q+sWn{*}w%BpsM=PON#0A9Ag*WKra?wVy zOO95VggfnbLMtg%IN5Mvov<_iJV4(w#^BJJ4_xzXsKce^t{*u##_#D~%5=3LWo(+U zd$Ce7HUAOcN&U!>!H;WrNGf(EnUo>b5$fvH^IkuvYRGP=dLqHG69n)^N37ZCcl`&x4Vr{J@; zP4~jU$?LImy0Z&&H{?@kseGN!QwgsS84GSLEs32?2jcs2=JK;esDf0^BmDG)+n4%# zM`CP60XZ-UatP%JsTzZ|{lhr?nGF}eulcZRzWITva4`}DN0YdA;tsUW#E`qG-;G}{ zyo`djsD+)79oK8Q5%!04e1Cucidc9xV+|>&iJqg*CC9n>d^#3#-xOD1D0>lP*fpnk zfA9A(?x#BwmrFl2i-_nNXOmgXACob=clUua7rR)bwp!A;x&b3jWoJ(uc&dS<{ zcxc+XTSDxhJfV`2tu3WcDSPZ8z9`oGjk#3FrIomXlc-EN@w53QLk_JG6+c5V8fhHt zGtBHY>5d9N!Q+n-mA#!Ach}aJak5BJ#mDH0W~(@XWr#f?^=#D9ShgCjnYa0IZv;tEv4>jmvn)hg{QA+vFZ|XI5pQ8s z+_I3eMPgFX*)z5&3hus*n^M*0qzFlG&yIHSm!^v}^!qjJhlhU6viu3dpW2@|GDA-H z6Svd%5FP~+9Q7h^^>@a(NsE>8yn8JBBap>1n^pk})7!2LEovl$3VX`QOEG@40iNP? z40KNKdD_FlQl}u|F;;>6AfEGXLHXDVkC~io1B+xO&zUE+c}6kvwI8MoqZm?M!t+h3 zWHR~vEkqOFd_yYNc4Z}{-4}Ko>C%wG0AdTsvf|uU3(l{^U5{2BJRUWOvVN$vTCdu3 zTc$Rr400$n#kl`S#F3@X3)^yqtrO0x#)QnxwCC!TW?s=tN@^_i%u-cu%qYE1ljwc{ zpNKG3DHvO9bhqx5bd<>0hjqYd2`i2vuNJ?snR`}dhwlbsIpcgz+U{KFQ<7=O zYglBg;!Ar^B>Aq=a^U8snb^5E2TKUrq2@ zCxdAexaOg&&1b~uwJGNv1{hQ}-ewGKqq}*;>&rp_r(O=bC$)DXxeWDC_{|V^?u_XK zA=gs{-nI~RcL~wvVV=8IycMO*#L&`6I4q>___@~gi%tCp}!1UPh?V$YKpk-VB%Q976;n0 zeAjn&2t%-bv=>G{!7_yhj8C{m|`IM9=pY1cqa)+f|ci;|5A#4 znGf{V&p=o3Qnf}Y-JNmyjE2?k1j2u~jbD_}9h-E|`_RL0EF zAHz^^7NDR&1U<)c?JSj(mj1v>T1V~7-wd}{D|JN~lS(`go)O^D5B^uf1^jTtW9Tnb z#KgdoHal#((x&9^f+OE+`p2yPROa)?JLpLBaFu!Czuu)0%5@X?oQ--h-S?}uoyA9_ zJ0*vI0)v4;JqzkIHA7Wb|8k5^o_r|aPATcM>aWmp9ylv$lmaPV=DhUMe}r7M!e)Zf_bQ9n4cx29ckO9==$0WQs9 zC&-WwC}`acFt6qz`7bN#Wpb;#Ynp6S5N^t=xz@ZSU$$mXIL*@c>F~))#$;oQH8}$$ zoQi1+dkj=}JRH*5;^n~_fbhcpDFNuulf^btjXi%~h0`PG1ATlw<8(;GEzTC9xO&ST z`47FAzx3#DVS^8rOu$|uR~HI^9l6cFe?ju>3&?E@*r=-l|_GL{G|7ukyz+fuxrStsn9 zd)pY--?KFE&EF8qGCegiXHMaVNjFUAf`rCRWsk&-_%@u&mnJ)Sg#08g%P25aEvoF3 zQ9hw?HpSu9GKacgMH8#3TJ%Ty9+cipo4Hw>$W;(=RhN~__*>Rj_qvp{p&M^>hTk;iT#m^WM(>ew_J+fZ95PjQY51r z_EeaYbRU(EsVYlk>{XvN5daYtOvJza*%pEUetk~>L+^@Em_g5FOLcCkjk2hxk-tcVB3={~6mg3=O)3 z?AftzWM#H;#*iP@ay9B1DumWl|_%pGEcfvKN4E`XHersn3e=*lzvY;m7o) zo8p9Y1M`9{{DRQ#fU#6Bu5@WYc=O%xW-bsiK@`18(QjuJ#JYF?>QlBb-8yaE z)GAuCbBFNyTqGuS#HS$tz>sIvg-cgo;!U619sd@3{n*tD|Kb8X{!|Eq!it+PTpEYEBa zbli?TLmU3kJGGJOeS!Y?VK_G1qu#-3dXb|ff0;z_s*)w7mqE8`e)v$$v2kReK?)rZ zHRw{U3Zgs`E5VENRowq83HXfn>=UBCt{WJF#mn!^7KMe6LUXH^3af1J5I55lC!Kq? zYZkMnvRoH*oQm4joZDP zL=r6+g9T}F1y{|fiwlP84|TZx5swpGzmAAH#iS}z_nj29tqDIuh*#)BrXp2UPpG1b z98W@evmg1HLPYNgdj}@Fs3;yw-sH^bu+0zls!}+qN?k90kDq&8xe?9rR4&3Bk{B03 zH?tcSMwHC77VMp%e41I^!pGFy-nFV+41(f8wKeId=(SmVHjPvbaV^vPLwuYF!e5Ve zRm%OHe7GD7pV6Ra%do$tWv=1-wZ5Dgq`c;@``^&Hq~-#(@m5$6k;#J?{|? ze!!37noPQzw1hXs^kY75iW(>T|6D-cFLWlrKg6p9rzDqLtbtTIk(+o^0Q@*+VD7zE|53_>4-?b(3=b5Pw^pb6qZrt=-M@gPPU z3Z#7R$a2Lu))_$H`h-V7C#El5!YPPWE=|5=pt6n2?2V#qw1 zSW4_KJ#S-BIFIlznnix>E0ZR}9gFAS)yjT@slefWb=~WWr~^$uZ9h*9^n0yN99^j>WHa1&b$$2?s;C| zY{{*6>WKxfyb_mMQj+;P6WjH!wdc)=y322TF-=ULhOJI~75ynKs96*c>g;gdSe<_M zw!~|GV6{cQJtN<=%wSe|t@~crCUu3=CbH@=oz+TgQttX7vhrUn`{}yOSc`#i|K5s6 zkm@A>20H|&46DwB(m-9LO>Iqq5NyqMMfzx-$ipQo zSfLoe3h#{gJNPAdZniZqnaX-F`7gqCTRw>xJ;y9&trq@jYp~M2kEbd*FXGLn@W%;O zrNr}eM_;vU%#K!BcVYQ?3nK*|)I8bKPn-&y%MAzQydR@06IL}&DkLpc7Q1fsCYr1E zkTfrO2w=uR@z9bH3=4)dtlsEJ2)L6!xq^4DAjYuH?}RLWgiHBoFbkE`udU$fk^y}< zGjy(Btao*o5INP?FGoVHQ>(RZ6#Ifgo`G2S%e*gX^P?eR1#v_I(qMJ=S%v##mWWGL zA>@H&;1k7Rk9$PSV>oq%)rt(9CZ>bX*YrVgd`c(32-rzY)shsqaE)AG{Y=I~S((|5 zoLTilzVp+C4!ujK8|Df@sF!f*0w_Rdmp(^t^z?t`Y-p(}sMg#USE$IU2GgO4a+Rcg zbGntkdCwF{Q5ZE*onJ$|=<(4PO|aU=BbRl{t^nRs zX934?n#P&g}oRbbOr+zj>@8Y>{m5&*ej#>eULK&6A9=l-t9DMDx5(paC{FwV>w z;eL&ICZ7yLh}BD>7hfFkFWG_qIDgeY9=G`ji!~A6OGua9v4tuGSJ%9x>x`FS*YXc; zzk^Nk>@}u#B{UVX+XU*-rI&PDf@gDoz3mBr>rAe=L#O3}je=JKI7RKhEgeIg6wzyO z1zn<(2#PnN3~y)^Y#s)O+>pZMu=0y{99eLCy^_z;?WLJy#8iTfAWl{E>^NoyqqWZp zSry;p!Qq`>eEzp|P>u@~4q3aQrXhl`^uA{7@9&~+u<1w39AVlu#n_=Kt+NTLhR*q9 z>NTopiS{I`fcS5j;&-dY+egBIURifQ5j2ds-jd$Gs9Y$3y>)f{8SN}%@ z%LX-*KZV*TS_?of9SkEXTHBhq%-8>sSi*(|G@4V%;A(;Blj)4*mZCUM{0pKagnFZO z$qclzOlQ2-eGjTHA)+&K+_EuW!cJ(;N)J=%Bt-Rwzt?^pORcGRcqjn50WAhUccqH& zvXdVz&kr=fN&A%+`m^KT2TM9eb*M{OVQ z8K7!kAo<%v0969w!bVDd5@xU^l`q?a(-l2(BzE9r??UVPXIQSHSM{L6cmZ@2&pAj) zGTW{_F(?wW3~dIkDN#u#ZlyMS{Yab`cb{Dsw+UpWwC9C zZ_^R)Z@Jghnbh_!VY{R|JCc9T-CWum-qn1yne9G^;tU zW*htQG)IWxrlZRYCr$2FUiU=axN;{TFy%@e6Q+&N_bVv0s@Up1n6PO1ZLrYO3_9CT zdQg5@jb4(UX%fuv=v?I*udHn}Onsyil5+7iHIg3SBXl0rc+DGGHNbMag+kGtYzM}_1omXF;b zKibSwaL+TajK!clraL{FPiJSul^8gBS&+?8{=rVzA;68&YK28y#rUB5A8qikx*;WIA^%+88inrNWhpHmqf_qsywx*4{sN=bWwNfD+7X z_d6Y*dJ*p(&hGVP4zvuK+taJ*Wz1ej%UD$w$?nFxm}KpagSbb%XNd9Eg^#SO2&T_z zg`3ua2Ko1`dM(wyg)!Cy`iL~Z!rp8G)G8VmS^s!(SUi-)0SgY*2)xbPZ9`0p^dPy% zn&X&ZCl00m2E!9cHcf@G1BI!Bua4WlrV(o)y!lKUNdQpX1>W8b%k8H%m1U1nG? zeA3#lmPd!nLYnGxhIOnO*FYYrnC$U+A;FsV$r#op!L-+ICJA2jGNbquL09N|k$mb* z-3D=VDt%A3y`*hR&lxIoxru5=S%ZC>ef;x2e3wi1;kn5eYxNQnoJp_a1@b#w?1-%= zF*S62bHvGwF^6g=bB!N&ZEimtI(cYVQQG!Z!?}q3^MeaZO=7ZVzc+>_QM~T*oDuW= z_9`K=New}@T=WJ;xGR-OO;tRY)tlFGm1SuD&`8JWHe%gT>b*wkD{XKIPz5E~iSJ4B z^xM4d061m4Ws7eO4bDEo#;uX3$8K~4QBbcx`peYj`%W(0)Rs<&BV!3A5Oa*`x3}|r z>X5^xzGfh2qSGfIuBkq)5Z2ba2J`ZwmtEl{UrmKdmdxa=ZDRc*P1Y)Y>PlcKB>T=2 z#i7CvsRw9L#Jc2iK^=SS`dDZ*W^%c`SGkSzVc~h(eE7-?Mr3{xmvt*~IXimXXs0~H z%64F`AGH!2+~ejOrl+#>>kBvs=iOy8m4d9Cd0*=t?y|}9zbyK4Ol`}ROD9>FLpL|p zgg*nBCPg596g3TWbmCi)-ose3r1&nv-f8{lK!70}W~D9Bl(5hhqXIeqi8?fwcZAZ+ zyGmG#&F~BQ+%h=DjJd>EMA^s^tJ|{N6O=O#wuKB{zw~a;>_-{7!KDPpf}B7PS{-|EGk5JecAo=Qy7NTbM4SmM5eg{>?qns3-{5H9QNN{s>(q&wU1R;8y908u8t2;ge+ zNus}Jp0PgN84v|-j>>MwkD(j$&`1P*)&Z2jrz4aXPDuqF;{Q#@qtP9lXPV4er-Aa~ zbZgCo^EP;_Vb;zq#0Iv}{&WCcbfCdL7ThSpH9uy;uF6Vxp1buu2;nafgHFAHFFZ5! zc@yyZo}OySVt0(R>F&w8`EOI{kuo0C&6Re&Le61^3~yDVcbIzNFB2@1EUQ`eA&Pg% z+;6GD;p0<|9!JoNFBDgEx$9)skwJ-F^E(?SrPA+JLMOQB#6XItciYnUumM}YcLEZ zs*h2;&&k^|j9}7L$#Q;Q12}`di2v7PmZqg%A3 z?7`H$4D^!ATRsrY`v)sJUHBb$i~w#Zm{n7LDFUeXvr8r|49ARO(CL9DVbQO&CEE`J zd@{(+y1h*6#`^h6KZ`K87fy6LBGalg4fL_ z8oFO!KFi9YjI2dYUpf#1Oiz(UOK~VuL*wUSfDkm`827wYPGeu941{%K2~m%`iI^!b z3l7b}2gZmStfJ!1^l+^0;8l2`pB=)M8Ev=LAO!AnD6j}wUaZ@fOLI_x)K=VGFbO(^HZG9+~|M>mMhGog((|)tW*88I4}8engCuFaGRS&UHj$x z+wCQ4O99tQ>&z7>G;_rhg0*Pw$6IIadN)>0VwGcE>; zc<;By3PH~RyMF*K!_=+!rJA^SmTOyP?Dm`awl6z2Rn;ehr@b7UhV23P6y@XahY#FR zhXkf#elAOa+j$0WM?3}@?R2vwQtG_0&fVVK?N;#(fRu|m;7o-4epIIUs-SrktB607 z&&rm`_axh&z*brnbK9f#y&aBA7C?0t9Uy666vDaNKImfIBQ#$|NKrkPmdg7sx(Vw# zTHDNU0avHEWwybcCOdUxZxu?}`m1~)t)uL}yk!;2vo>2wq4OY3%3lylUzij57Csi# zmy7IuoU$}L8_lPC@l8{HT)a=+Ud4<)?!^W{$W7jb0;9s?EM!SF#WSGB{y^O0n!wdp z;@O#ai)CSdcaGdGaT%89v(5zR`j8Y?Y} zmfeoUzor@R%#RIo?ZREQHn2ksK2Jl9_&^;c9A#<3Z*0Uf71QVFT!b++UXs_W_YA!- ze+}!%uJyjAdP7j`7J^={^=9}Qy{7Rhpan8l=R!I!Wh-FMd-I{+JN#(sYJNW`)Jc@P zvdE%9YiPyYcf8a%_l9fDDyB!{M$6mpu4Xp3aA+KW3;0=iYN8x^j>W8MVZ%X4fD%~( z09neK(HcWz@cGiO%Dn4)GrGC^tzs+?IB*rmYhV<6XB|g7#h4b7?ytcma^;KiP1zI+ z)iW6SK=GZkCRcu8IdCtJo7d*sTXbSAT$gVvH_k``wMIL~^NRx~i$}>E^ElkM;klPMk?$WridZO`* zsWNXVeREPBIjAen_|gV$n=zSo5g#>I;d@}dns4!`6g0F2S6>cu0ewTK+3>(lQTT_F zfjL+CV z3;OYuwza&P)|GnTfr;9?P}O#*sb--WE<blJfe^c*S0F}$=3G}!^#n%MnHA?rMT=mZTidvV}}#savLMRF*@ zMG?W++#d9@1bcZ-PjGE*(o{ex>h22=TOR&IGOW;@WY>CIBjz{0;4#%CR(x~f+Gcs< zua>`aSjnOlDE=qui-WOQyT+ULy3Hp1Y{EXV?>snQBO5sZ+SUfbZI!Ko*&}c$m6C2i zHaD2B)yNf`T>)K$LCWqPkYT+$*oSfVgKmzXx>D(1s;_H{k^}BISa$p*WCPwQ3b-qw z(~WiiRE@pB?!)R@xL*a-K)x4&|KE4* z1h)_?eoA`}W({fw^?7l*jMnO#2=Dgz(=lb=8=l^21kxQ1{Lj$IZ&6*s5@N$sj$2&C z*cZv>L@RXGXHf=f`~}v(HK`@>s7-lyDmo{}MHsJMOYmLqr4PLY7gPu>u<_+ z3JRu*e-K#^yT|+i9Sh(Eg=CRCsa=-9;fJ%iM<&HPGsZ)^3|*4qEG|tooF{cdGOyqB z3jcrDd#|`Amo8p-i=bE$6;P0(Y(+qdO0RJ%pdcb5HB^z_YmfjjBA`?oU5X$@sz9ir z2nZ;>cS4gAdJiqhH^E&Eyx+Mvm&Y6P3s0VzS+m-#S?j-K4;x;B#4;emba30fhOJC| z^@<4lv60invYX^K&y*R|mQK4Jkvw|~890l6Ftd?BiW!6JWk}Je5;4fJ$&)CKACmJ) zq={PiBf_D=5Bmi>A3#qr`a2{x@JnFeA#6-%(;ebEZn^qaHBOw%6*fq<70$7-8z~>C?S&=>eDJrn4DR8Iru&&v*St3m(M3P>>F)94vLMg zuXt{GgnMikfGkAte>0a(ViGnX5R$M}lHJtLb^p zJO1UDIbtyuiL$_Lk;<8|((bF;6D?VVrQ@t<( z*tk1LPm@5Uoa0h#%(&so*0Sv%+m03eH=)@vV&u2!_d$gt&J|NEnSoC9ua%3ZTPLvO* zhQ3#ul&x8dx?`)(X16C5|3cL|Uh-xBJ&p;BL%?r*FeukSRv!A5+y5G>`F9{+Z-)f? z&LIbhvtG~8gV(FGj7r*<*1310?*C_1bC;b%?EcX!UNlO*{Lf zPYsIhZ@x(X)Oq?uf9^P}g`3RQ{v4h^g0kAt+1WWTaUTsXix*@w(0hLdSF615#m^Re zK@d{~v1P=L-an{Rj)#R2>Q-YEfKJma*aMT6G&X-2g>mn(#Ka=++^1(@m7B}hy)fR- zbOL2#fZuTR_)J9!^W;g9+GCQCSX!__?DElPRRMvNR|}7Y>_H&ZbC5B1QR?WF3iPjB z0MC1UJMslawdK5b7fr31kT6 zZa2EulMR4#1#5ItD{li<&!0&=_+Tr$TqV~HpF=^m0R00Lrdj;gUSyOaU{_VKm1@Y@vSlyBIc|Au@<{m4MYKUOB~MO=+M zEGu2Ztc3NrmS5iki(Kcr#Wq(n;n@}?ZkE|RI_|X>q4vt3)OZ6lG7hmF^1A&;#YNnA zqsECcZ&;^Mr&dm9eS{OfG^S}rbAC%|-eE(kjyXavdg8A|`^y0v%p%yd^6|@j$%g;R zR4B8$fOEMHt

    SpFbJ%J=k-*iYIBpR2fBX0Ye$WNton41mnv`04a4TSbc2ZGF!oP zwWi>uJ@}(p3$TX;J{R)=u*dNzkpXgCPSQT_qp%`j6}P=?Nnc|CVnIBIJ^!~5{`bS= zY4FCR`gb0A34nKal4O$p)?)wt0I2~mNMhEtjs*jhl(1PL_McdDyC_RBC7?H+I3(6s zfz?=4-r0fd*iEfB-vA^!9&ts-%4T1=4!kJ{vzLZ(2K^6mmuDuSU=nkrN$diPw!_CL zFTh(oi8nZk?m%q26m!h3>@q>p!eT|i*yAD=2{m9fl0w7YKhZA#yJpfWF9EX?$rf94 z=;6r|{i<*5$RRcUi6q;3&-z5QLM@#7fm9koUm&pVQcDvVH z%+Y&y9Yu}O2174#HY0WEPV}!%)iVkIm$`q+0^oo%B)w-fau}Hi-WVa9etxehk`#4b zG)vSp6__GlefHJ8<~Ri&oRXS5Y3d8ahO?fL+?V}R5V?UM_I+x}(me&H*z3>4x5pH} zf%L#7dMR4dLh!;dmDs(Sio{+7BZqvpcn2{8Q#4|Nk+ba1H;{awQ;M9715)FG&0&^w z7u&1v#}p8*4#ja$2S$6MU$91V_Z(0lGRz>+aF+kbCM5P5m?MRM-CnX)2OiWB`rzH> zPN8WFo0>|H@r0HQe_ELf^03Ts zJcYoi0hb%HtdL{X{M0N0e*iA0mT@{^`Wld3W+CD)tMsc*Jx1yhbLOEFWdskNuN_b_7u>&QTz&^EfCp0~toM@aJfMAY8y-$lBfryrdxoY{ zpy4zh;($qq5EfJh?|5zbJ<*99ND#z(_qkzg~AeG|p zMwERNiVR@qqO?6Jwb%g+eG;+8%g7TQqzAXK5KBqnGeED&hdhSw}`s{8y3ErzO0a$o3`H45g2e5~j8-){j zy3)=vitd5MI;ogLD9wR#>lpuddUx(PVd@Q}>}+i>TXQ0yy-?Jd0TL!Y`NPO)aLiim zf5*g+d<6I?%jfzYYi~Xb7?Zm__fBjBU?VG`^F=$rx8w_w1T>m12!NIeDgRO)wirtEpBzaa>Xu$G($iwSrlBu_C4e31aZhTZe2 zfu^Ln>1r$j_#?Y<(U-@-l3s#p;3 zYkqsznXolK08~=g5WE-b0C*!x9Ok+k@C0DfS-?aVWuh(Y6wK_v#R>b=s=UYI9KahR zm0r!!n!*78qMKe3cAq1J0}x)!AY|n=BofG2uiG<6@}i&}I``@V8Cz5N=t(Jd6RhU! z^2g9URuc^vbL~At$%W?EK*$2$3q|dII6w*kR?jW}eUbuc4cN%3&64rR?wci`0LH#z zHhW_7JXj5lV%}b=^}*BKBFIYO!HTARIMqW+$*zyeuvY4>AVEFPGoS!p@f+p!juDO51xb|%iL zLFCw864DM_zf|?RAnWDQ@Ao+i(Bns>?+$JfJWr+cRqjO~6);4Un8T)GtS1#06^ylP zO|4w#U{mT}g?#nuOel&mi_k9$nPMqjh{H-bemnZ(zvj3>9W>K(3jp5vqyK6$^ zcW{hR%*gBoQDDGu!UtaU?eWG_fZw9Sj6HvE#(+1jMYEQW$JI#!iu?|Yn_?-cMkg6W z9n|-~xv<9@b4jW-N^3tm@aDiAffKj~2Ui33U>r@R2RF;V15f9~NAulO4bX$}fH9c` zb;J~6g#jOxTy2si$4=kTAy0tdVk$T&D0^=MDU*HsVGk#m9}DJqhocjabrhUA0@NEF z_E?M=c<^52Rs6+fL$DZ@NZZO?2@VM&aiQbLu}BEVpG22$ha7ZR_0bHHN z10w(AkN+yt0T4i)CWs3CL7{W;K2O2c_kkd%4vk}b9K&4YVl!dh<{v4k9sPh1+I{T| z^QvL>m=eb}Wr#>ui7&X~HQQ%BDlp_U;c5aqC`tRpDvNp`AQdGqDn2k=jq(Kab)b!M<2&y+gfePbyOKvlcm z86uJFp>3D6yH?Wy7%l^G4c}Ilp_o6gp&_KtsXHm!*pS_neG2iIUL(ezZsh~$= zc(HkvB2t*QKEQ4w=XzVlgmI#dQVE-to9R8VX%u^FDxvQVMR5l4z!|n*)MT?2*yH1o z${%!m6vO836+Io|O;Cj9C0P*W5?8uAYua9O48#O#=ZY$_b_-+I7y(1#DUZ*@M6l?U ziAWY&8%11oyyZ%?DZ9puex$tR^e~8guVuk5%f3e8s6OljNOEOkLp{+J#KpXoSp-@* zT?}p|w$#l5y}nQDA|#fF>d{4vdwz6?2R1CeRkaN6;T!zzIGL?NuF-|{mg=;TysP$0 zpm_s&YDrMW>mZK#_F^D9rXR1-vT7~*Jz$>C`ZmrIf6uiiVUVudsqQP|LF9qMUqcEk zFiRyULjtT#TxyNRtnWISQ3IVqK6gfN>b5{7Z-R@6mk>TGSQhj6JYg}ktvq2+0l<}T zC{BxapL7T~$<}<9g8N?Yt3ZiSI2K1-sWZcSr$@Dzm5{;8ti%F+e~@F~)gi}J2p=UZ zt76U*vDjob7J*QdW5MYShy_>6JcW2t(K9bvis4rW6<9axR||3aLItiJtnFR&7S`$F!UHR}R4 zSc{-3IS+{i`B9AhI$J^c3GNPEi3!}SHc#JhQJmzd`k`ILkS7WeYq5WNK=R3gHsT8!^(z#m`*n=>s-c-tJGN02_0lWix&kq1i zHd7<556=<0)DdBUV)!|M^m%=D)Ak!rAwfsZ!N;3gbM^2BLELAgeqm-vYza`7vx2Oy zH%SC6I$r<7(>^JFHb{&LeHoRXloo6h~lZ-cZ2h8+NU{{0(`WUS#C?D%N|R znC2-LJN>IpMb4cBr?)OQ#$)>^=qNFp-fiO73jAzI+H{NU)Cu}`|di0 zoi%F|CT|KqHHm1C0%skjwYxtH z$p5WhOGt;25>eqt6q0--hhX!%aKb86US4ohND}PhS%(9CDM|p09fG5-f1k@?z3)E?`=-tjKO!Gx1cY}!p0RRSJneug>56(?Svu{THo9ACy1Aa!cB1Z zWILwF1nERF=(wiN5jFx=5(MF(ge`_*)q0(pV2!t_QITI}HqdPxITw_6#jjx{!;OJy2rU zh1m&axsO*eBpRw;VJyVVvCeHIJu#cUslw&#&{F#W<+55XF#ip2R$VMfS68SU`6V%? z@y>LqWO`nOkvy~eTsdmSf7WQO03as+DvD1b`A60{76zm$tL2`4DWEJes?y2*!mB?* zzzg41klsi|qEnv(O`AyKx1u$%PbZy@Fj(3m^)~UxID$!2$*Ikj+jU>$$Tx===Y*#V=XM`B-bI zoO4&)Z6_eT%k?NneFOn-wcmjzQS%5aXv2k`nm%LGZ_`;Sm^f;>_F*eEO^w?+4#n5D z>HUD%?^)&bK;COM7<0u+dakR@r*-qvv>OpaKuv!ty@11Br%61!O!Ki6S)S4m;xL$N zWo+%&?!w=1%v@AvDY-Z^my|R%m8q6te(-^OLSwFnNgReb(j?Ixy-aLOZq4Zt!Oefi zT|$TGV3|ZToZt80WyM1S(U^HP>~c$!>-4wkaq{pu1b*ZPF$7hz^$7+<%e3%|YG~&7 z)elflbk@@t3_p63*(G6&A^m-HpqPnXL{5R53#f~pcY^?4s$ z6NxBhnRw||7GHI5!(-gZjXeGomJIIf~oOC2lbyrw)Xp$ z59}X&e|5=n#R${tX=W?8Vlee>wc_gCD5+w+i8BwyYbW93{_l9sHG!@DsA!S&V{>7q z8i|^E)}B3-fr{~33x{i?iQZ;r6DkgEN18sJbCfN#9b6wRjk9C$hS8=(uXTohLadc5 zOC^lyF0F+dHZDhlH^Jw^gj;p%9nK9i$+Z_cEJMTy6(5vS2QNIY`vcQmDyZh!sO7>x z+bbj}EUa8(P=gDoSHzy1U`$>lh94zSNnxukW3Y+w2_5-~E?F69QTBI_0JnUjEaEBf zgYZJXJFXOty5g)mDu(h9IhWoHaNYv{G2FbV|lI~cqiN{)F2ovtOxRQ%W zGt8edZF3(|!u-?%k9!~9g7-oX`l+q)JW-aHCYJYWpE-S_X^~;>>?ibR4o!Uqqd+Ox zVDbW%isM*j@8M95fczQ%60acPS%=Oh(}m|atU}wsd~=ZEuTl3Ve0Nv z;=o^-N{#YmF(0`@Dd8ne%a*p)SlrK*J}H|v{t0!_h*E;~DUU{0jwLlc^ty3s@?rJI zs!Y1i@3-g#Kp(p3&}> zE6y*WtPzqYAMi>$5R<>~MhAG@(x(H3L|IFpdgKg1+2&YYnLlhby@-wVvg~akHN8s7 zXC;5&DmMJ+EDh8Z3bT3-=h!cwg9aKezdKvA_O6F^ux;|@IUioYjsxOM1Li8Qj$@NM(eD`o`#zoi~8dWR9Y#Z4j6>B>6lG#R$``efT^Cop} zD9dg)A<|Yk_Ja>RTSL zTAahR@js%-XUr*x!2Sl@Noo)oD1`MJanDKNrFriv9FG(?{|Y1!YK~bM^=SjX{ATjC zfYOCl*pGbW{xTDJ*Oh~94*NP6oZmW39iF=|^Fr+K%}8?t<@oB82KVGEp{i0j2B63F zBgY1>)NNy)dY?a(v-wy02tV3U#I+WO>qXV?)@X!;QAhe_E;HYBE?IyXd?`SG-)Cqb zJ%#VrTew%$sv5jV_?k|0jA$aCc38)wv){3MajVYyrIrm^bHmwT?%lk}_{1)T%@9P! z0#+Y_#$u9O_mynb&va9%TLzt#<_=-G_Q|yp*|n@#uYmn1Jl9y##+&!ef5^J?!}Zhy z7Zw|{tY~mS#VXbh#GwgG8fVYo17H(8V#aDM^r0;^iqZ{yXCq5Q$|!hJ`tgng<|ma< z&+*DEF2%%BLa#clT53`!+{?gp_;7)I6HO*2A{@;;s^#R7@FvPZ5~B&5O2}!gX2p$F zjD^hyNKYyGsohiZQ)f-%ew-mN^>aBFmAc4|Rg{-5fi#4$m?ZEQ{tNdNY}FaNjc97< z!y7cLLUTj$x*p^Dy(njwvEEY_NkF&FH(Jx-13N5e-18BSExx-9s#)@B7T^uiQ1Bn) z+w?v%bOH;HF|W5&*rV zXzu1Y)L+^zg)BtsE z$ot-yBR3y;b+)S1W#(}oow--ysMj);=N6TtBL9}P*N7W?LBwAwG$YK9|6-n+f3Q|U zO+8iClJ$B}X}5E>c6h6amYq~OUs<-zU}Kj0{%Uk{wjp6@JEtf2-nx#Jpkcj=r5EF1 z6LqLzBYQD?B@Yc>VVvJAY#> z545fLw+hwBU@;++#kmP}&qk(0mO)~0{r=S1-iAVuj2}E+Lz3tJswU)1;Dd+QJX&>$ zARRdlQ(h>5uhs=}7hGGb>AsLdCr5d{M-7ox^0=5GW8v~qwZx{3bJWcJ>g(-4+%RZK zVom1)y#K+Rq+2C-mb2QinK(Io4vs#+$=+3TQR_(lsz0nSxbcqJ5!yWCn)eK+%WC-0 z7~jDr%WqBUg=>a&bu(vljY6VSiSFHt8&_E*?n}0(8FyJXMlIpfq{QdQuPy!6V>bsA z1M=0HNTC~u@6g=)Ha(4;Tb8eTQVbWTJlPd(-~}S6NU7_H%vQC4EDGy3rR9C~w~dYw z?stdA5cREH=N2_N)hI#;4=wU+(;_{@lC)YvFn+25Jz3bf?X}RJ+!CTvFQmx1sdFZ_$~mT^BClfJV0a4DsZ?}$dy1zb`1*F}9G*M) zE0yBx__UM8b(=GN@UtMk#$Bo1vZa>i+5~f7GK?GaibpQ1^wNrG~pB9bPp@6J@x4 z@-H={KkdIR%ymyy&n7<`Kj$ZC>3c(~)h zN$BI!)niHO+}Aj9kAmCYsBAfK$Bf{evRt_-vPB*-)q<0$)XD6$5^Gm^GeWY*SKovy zch5TIAQ?i6P4#-$<7>H_vJ)>%y8akPI`%@7{QWPY^QWGbm<As9@mipB@!8w8Bt;AMR|;lbDs8Rrt2Q`BqU@4%#kj4u z?eusjYX|QX(pmY$h=h3$@(2|ded-VKYTI0Lzr3xjp}rv#nww#wP=CqB%?sAI@8wQ9@y&Rfu={q2sH&j2x5*9f)Cy4I6J7Ic# zi}&$tD1moIDW^0h!QGZD+{V`IhUC0?^JXq_)?j%nsp=#$2Qk_k$Ng8tpBkm6e*vk} zxlF`G=qUlq{zg22*g)K5brVN-(3g$k?Rr{-Z9hNKco%H;W4^{ZCKCGO^oN=|Hf|Z_ z9bPwD6b7^Mr?^lRS7j0!Skya^r~9wWnhe8-4*TYg)pXlC5;4QGciGzm^ix9EpY;wy zTnZdZ40+v3J57M}qoe}}9)x*mgzD1Zsa!XvV6w}KyBSY|o!?HUI)muZ?O0l*j%}{F zU%ggRO6O2~=jIeA1C*-zDq^FXh^92IgNi1m(d2H=z4HiRQn1AJ2(M)%#D*v640^GP?v;|pXoE{l&)0rO^54dE> zjN6p1wXZR1Fs+Oy=im~{XZMprZLLK%N(!7F4+zZg1RaUt+=H!g!Qu*_iQIqhvwkX{ ze==Zx12^AiY@XsO(L&U-wQjT{_{TMRBJaTA$klh2&#*(M(6A%*_(v+ad8NFnD@mh_ zzv?ei$ZnQYk5Hr80xk`U9>hlfHji0V8qfPSBmd&i@e~_zV1_zwG;z7V)BEos5SNs8g-Zw{o z9(}=qZdv*Nn)RQPYbKCxA!67iK_yHbRih|A`LwjUPV@tVrwq`?dxgG)DHGSK`2$E< z_)+Ki&tcN<3=$9?;&|KXPbYR`BXIGH0UNqn zEx~kgH}d_tCrrQ(>~*ns{9mQi!r)@!f85=Y-qc(TRxcENPKVrE`+IVe$G{zlO?{3s zxu@|rK~Y)Yf+>JQuIoSM>c0_M*PCFgn3gTy{agZ>aU(r#b)~th+PzKiCjnzCDM``k zJSlIhHPJM%AVQM%)Q<>LBS!c{xur?(F=$Ih_KYucR2%FH!so(KxKq zmgY6eWEMYlR&P3*+_nIn36{Cskh_ykbL#Z^2F-Y4fOPHrf*Z+u$iAKzl-k*VYq*0O zP|Xy3#blB8 zim6}p{&E3sepU%Y+>3d}M>-B#c-*_OD?i@_*=g3-9(L@c1}L5iLpzF~{0_wQ+}|?K zo-O+pD*3poG;vjZ_hhTf9QTR3kH*aHeljrXwtnNtfLTi|c1JypZzU#HrCLmX8-*Rs z%!Yb&mbXj8ZRE{lt!Sk5xYSmGW4|8M(&ICSD9xxI)^dZtcHPu8i1y}16|j!>?F6KW@6uct z!{Gc4S_SYQ#nChgx~D#+YNHcY<-)bbeTHoby?+x!k*#ulNEh0|)ieCzIi{$0eWGAb@{m@7J-_+A^`0RH=0C3KK@M#K3tD zZ?BA17<}E@Zgvy9h~8AOoYi!@tHhyqO%jB#uERw0miirWkZ>WtIwL0uz$w}DpSQm2 z(b=%NE?wMedq!6^tiNJcx4*45F?ZuLZ>q3|9K^Lm#=G@QjmME zhDll2FpuH5#@aFEw2MuF z3b&i@Q&Zp^vLp4ZLl!{^rOy25CM((xMxS49sLOZ$c z!qpFil{fiGaYA*1s0l15Hd=fZ=2krcF-$`-@# zk_FSu1%y2HNA6Bi{;^Xp(ti()5qx!|8tN=-iPj`&zy}t@SJg_UAYwDm%e75cD;yqh9c_%(5SKh=tyI{Y zs#QoVGIRy;3lArQGy%7sgFa11)C<8aPgBTz{jxgE(Q5gk4~}0Ij(;rV>sCMgkw-kf zzy{d*(73XWK&L^L4=)p&0Tvv*Pi~q>f{?yV1R4V*&Rrk_$T+rc} zk(W>8X5?uzs%XpgDW7))p3;T0@jPh>e9g~Y%d1<$PM2}p?|y2CyjeUN*B~Ulu=K*VwHqQ0?NQZ|N7#Y(NKn+Uan&+O55q&C<@v zP^-;pINmJ{)T%o+mnJc8Z}kpM&te!}FS$MREgl{X_poVx&0WQs0$^m&SBCtPoK>^9 zGMv`aT9%bFj_qco1XT1#5S3l^7<0cJx;>=TD0UUO@7rM}F4S~RbqU-J2Bd{RZQBXT zIBuO6s+P(o=e$0s7B1m)mXXlnw)EDkhBwA=jfy-Pmo+R3-O-WDa;~|AcY#i6**P@a z)W5}~mk{4xWleC0kvpM~*dB`D(BA~m9KAp7@Tt=xKzY7vUGOdVDRX|@+f|a4h?y$i zVvTV1X}bl8l-r--z(PUEA_PBCTagB(`9b4OKBDQ}!!y)#F4=3gR%yd4O$|QIa9F!_ zN9yv`kfbg0t%xGsG|z0b!AVN~@`)Eb4B)CLX2jiM(c!Dveonv@``Qz8E6bbTFpQ~J z(n;tpj?N|dcz(IeuPtY_Pel(g*5OFUWU(0FlHZ@_v(<(d{!!CE!W&>-8ai&+KE#2o zIEC15K&=Gvz5m#&N?r|f-w#+L`in*+LltXCIpS_h+g-I-?~OHEoEmPP!^$loel+OO zb1zlHzM>b#PZxKkYwa?`7rNah(Rg-n7Tel-B;H(M#cP-_z%DFoojY)= zrnO=Ds&b@j^x(>C1Z{7}{=_T4It}_KDVGqQqKAT0f0k4VK=m=S=m+x44h_~n0}Tlp zl6iA|mE1KXai3y3la>m-&hWLicv$+H36De=xaigFT8?^9!2el%wbk9MI~#=qLAC&z z*(uH7Zti7eu6govd@P!Zk(h5iFl*#U+K}db6dc9QE?af!G>Ufszuj$;5_138wH(@& zFK8?w;-uvfS*O!iv>en3Vq&^>&-FIGSU=A?&w4n&@QQYWu-aG&Kda$cflWdRl#nT_ z{)xOy5-CPiv~0sNeQR=MBxbWcNZg%z6V$aJy${!m+Y@iQLs=#jK$xevmNyUnKGzoa z4K(Tc3SXoLhxEB#GmCFpTQR|YbUwj>6~9?+&bZyy)FS9Ji+f1reAq-7TRveUVv2`5 z91_xlrRPI7E=w2D>quJN@nh^Hqc`Y?cOP%lmi@qECxsPx{3Nj4O1@8d16|5=@}A9n z9-Y=R3Oxn~VXB@Q6gJ607GRb7AVoKRv$i#n`ki|gw-UawINK*$Jd%N`&#<~KN7390 zA8V{6Oh|`8=hR4%E3>xVVdqB~j~b^$#hj$3&Kh2?o9oVs4{tF0Dck>YsvxocfcuWh zC+WC0Sg1K^g|3p+=38*75*xYoOf2MO@QN+C3};WB9l^T<4M+*gyij%dDxcRKczw>D zc&c)~gS&Xpx9R%$L}dZTJhpCCG_S(nqk4WO?5Z@YDF#uA(oPN;0LZvDVNrAIk%ZU3 zp%!!#nzF>MK0`YVhKE}4xgCHV|1qTn*YT)pO-SmL=8F_E-xx$QizG<+yNW9_@EOGlYHFm(D4&bG(no zO=v1Ir+~nt5FeOE(74mXxvVU*=Db;K`kEMGnzYf(@QZI4D@?V7i0p|R6*edRW%2=% z`h!|YigZ;0VtBM{3Sz)?uesCR`%$HS=3g#OJt6Bv(JVsBL^1gpBAvX7JiEN+*#2VrP%Wcm zp2kMEFn^(Tz})hWHMQNURD7wtiQnOif+plv;9uVefUD}RTb7FCd4K;1&Sj92#ho;= z>jD4ISAp)s)Uy#U(m4Sev~%;@U{EL%IYdbi=N|QR%;2VG`tzYS9T4?V&@V4WqD;E{ zF^s4dkmo3XT$kIo7_|S~zoz-;WYR&Fbgswo71HUEj*9-=wMu!kub4Hs?os|f{n2qK zzwj&u%h%3LQF!C&m*?cAt#}HV27YmR%zjC5HipO`L(@DAT*qG~(9NSk z78mj*wdGb7z#g*WzVyJ$gp7AcIoGH^bFTT%QcM*8@G=|S@AGGx8Kif+dC9R6Tl4Dg zbNAmLO!ot)mFezI2awn|U*2otv6FPmt>XblxaoH4YDZ6YZ_Ly?>f6eSIakHmNzR}D zMJU1Ss=;nBPJa>te-Y3g>LAYkK$#6FAIntt0*PIDUl5YEk6p(!YgUDxwU8ATfaGq| zzmmHzb1Z;#p?l~U2`E`ErxjHT}eJh!rGtM0yEOt?&#>vf^<5q+lJE7O zU`;wGD9dfstplT+$=@kcQbG(hV0|qPw2mHOQB3vNFfprsUvAemaRY?$l-Sg|B;C4d zQLC>F+vEmUdruRI-sE|KvF<>8X2pd`O_M-1;SrkfV8Wad5ne=q6T=AvZl5G}CAZ&s z#1~rx;Dou9rouZ2f3N1kp~r$hP4Q_RXLePnGyp=3hwwVvD5A>G_Qo zaj+r&oVJVH2+k7YhN%_YV1XnaL3BYxbe3`O=oVgOcKs2eO#+~Kdw(AVGtY@Wz3Qgx zkrN3VU72^qqtD*X6mc-x>?TSmXmqt)PhI|HBuM6=yJs008K-h@z?NK^2muy;MTovo zK0Yy0g(H%bgu&XsVY~TVGU#K2(yY9a)xbe7kaS5e;@Gl+T5s0GK4;)nEOQ;V!+tfS znICCkBvYiZaH=%aG^SAS8raT~$_T4-UqRm|%*Zm<^V?v+u(Y z@LOnGlz01WB=vZ9EU(9I>7ajCtddlqI`v)9bEK-NztBPDx${99mb-A=TmwE()n$>r ze%kj_2K*M-b_+bD0?!@dF+$I|LPI-7F1kCY7Z7!NKdT9w`Or4Pv0tR zJ*bQGJ>@W;!(Fn=MdaVg0#s%InHJ_Si%XqN%Lb?w9T!O(`HMRF`3-~)*fF|u`Fs3% z=iDtoQu9a0#QMS}>BIA?(@tsc9``4W0PzDG%oF`oz99(acNc1?cG782lnJ)xQc|2h zib?W+MY>5SGLiRIT=Ku61dSu_C0p<91EqjabIK5EQ>_I0xR)`f<9Z^kq2s;p+@<R!krB+?=`!3$TP2$nde?~IOBi3fUkkI^BO z;NfHO@8A2H4_s*L40}78skZmS*TKUL_d5@eOW)t$Ah`f3xkP5|Ez)?dSq1d)UQb}% zYw+KYi=YyxdZM#Esk)amx?GKuL|Il6RqbBuq!9BB;pG~z>ltdP8VpQce4n+B*M`CGPr z`Je@=2`m;VO`~_%cK7FiFnX5mPuSTa&r3?`drwmpP+irwcTp{mG|N6aXHeYnH@$!P zz%Kogs1^?_L z?~DI3;?OU^Uh zuQ9k%Tmk?XrPJYq*^?R=oKljw8U?yGm^Ho^9rG<%EB*p{T?Z2YuBX_9YyH)lkExlZ z5#ctw7{Ncu9ea~9f~6OaA0)bO0*FezQ%zlEgw@yf>bg&iibj07Ti;w%kj>b5#qdHu zXJ!2dhqR{ack(6^e@_>}bXdS0ULR>)WTt(zOWQ$_)F-uQ*qf<()h$zU1t8txdS3fPEd08NkFmm0r7i?DRT-o_&mV$qEWS@pidtRxC2 z^jV|Rlk01w*0M-VMd);%*^W?n&c8`pO-S1cxR`;ruw8AJu1AF+>6`sQnbwsXFhS-As2D zJAPT3=uouU32g&ywpuH^jBA-PxpV3u@T-MVw+20yg%LT)2;qL) zo14Ql2$5?^R|(X<7G@^dJicO6dpb>%^2or$v&TUP-6D?j6-6|N^-An=4AHH}1V~k8 zkT!9~L^=|)(FhBCufAp5AlT*KAE?jq2f90BSupd7BIvq<|5mbo!b%TxtdD;dSHOU5 z>H}cR%1w{yPOr^}K&?5MVr6DD-p%w9-CPsaJlqhrLI zO0OHb^IfWovrN(^uR;4$LP7R~VP4Hqd_{9YNY4w1F}5{pUp??$e5f_7n&HNtS#=m)5&UfQlXxB-bJJDW zTz_^@0G(@y`{e}fp|hz>(}%S3EqmUO@a`ij`%T4*N5oa(#9km zHRBVYqO#&y_(zlvaf1j%5rC|BnD?%$t(ixx`yeh$G`I&|jRtLO3MWSX=qv;6hV1(1 zmzX+k%VYV7OMHI3tf0vVwq)MjW^+uBF!p*nNSfd>$80mWUM=ivnN2bOqzT@-F%c6J zmDoCzvVeDC_j-_0U}O>&%yPAc)ZkET`1vVW>nfpG0~qMT5uoiids}+xbQ)iFh_k3&S3Dkxa@Q9Ra!su)uKHe0mn=T)F$Z3 zbb9_c=BoOc#Hxq4njZp2wM|92r9Dig7n3rZFtB}Zup)GcSR zz?4JUV}JeZg#w^N4=dGdpPQ|Q#lOJ=q;|=aKn?63i8~}KE}#`V_Cd$8<4kI-JmOJq zYn>ipDNglC&kzxC!^JI;DVUk91JIlu=bVENXbb|;knPaJ{$XjQ$$2|%<*u`2o<*84 zTYYpo4u%yUdt}k!^N|4NNMO#V?du%{#p4JIQ zoc#VO7sQ9XjY_jAGNi1Hmm;#F**I-qwRxU7ciAH+-C;Z6ywF9f;op}`^Kzp=7qgks zurgBbi>q~~>0j7#<;~Owb`_5ca#nmZH>|6@_XB2zHz0=O7hiN=^wUdY)K~o2VBUoB z*qW8epFSj|p#F^x7@>z|;cr`UCqg*iph?YUP7kE4WR%AJu@|feFt<*JQ4*-s+_J1| zQ>S9qzlMG2^rVr}csM3LxX~E5Y|a6!4rfsb@VMfZWux7O_Oo6-JbOVxe6C>Bo*V4} zq!?Cjckd%#*5>C0aEa(#b-1|O`kGp1u(!b}GmA76VbHGb;qPi?Ra0foqW46GpF3lH zJ$CbZcD!AkE6trBbc)e)aeln=HXh577MN>S^CGTDOli8SuJ=Di1k%}Lne$hxyL{bR zAK|Db$RSqbuuhq9ol5mDRa--Ipu?B)Jb`2lf=4(gTD!SW(W*9HhRG8S*xKCIjnpJw zqrJ}qQC!VS&Agbi^dGdbw5g3LB{pd&H;!nz%k&>QE{1AfGxuxOcoLfK?MG)@whArl zxF4j^LJyTTwmQ8j(|lH%_MGg~ZvTah&+ajMk5O`oKeuDQ)Kqf3ed^>r@mW;d;OvQR zk^}p;@zkhpzO{~ZhfbO;H|*3I^;TQfO**?MAoA5G3E#Ex;#R!BZ#T{h^j}lEPG1VM1z|J6va-*HmsI8U zCb1zk$_-wBbyFQT+uETnyEkBSmvNThXMU%;`8c@ zM+3!c>0+95$Lc~EBoV-mIvuq)UM2O)Gf0=0b5_;k;YF+xqG$m%Dn%sB6Frn;Sb^Uc zxBOk~W%B~b8Y!=mx(c>D8NP$v`a$Y-QPD>y9RnIo%HST!=*15XsJBB--K(}}d7fdm z=V{ASg+#_iNnHPhsczRr(EY`wCD)vT_Kf?SLrfk1A-C{rcs|C}!j-r+7z0NPXsk*Q z)<Nva>o)`b%z3#>;f@LG#ikix+-;t~|~yp_qW@Ez-0-nYt-|Gb6uv zjK>L}UKmZ1ypLyE&@H=S`t;e{&b{c2R6FdbW6Hq&HKTZ`_ClLRiPK^gLlSiz7jxFN zD-|WAu7I$TX`R9tc_R*2JO)1BP?E}v$!8cI0F5PT)y^8UgKiWL9p~Sv+~X9>#QjK& zw5|={sXn_81G)sss=lTQB^{nxN&p6b>11*5oqCY8cQxAHz~Ibj7Fra^YicYh%hcYu z%w(*$*&6zw*oV(w3bY?f(o)CK<#qTst!fCwOZWbBu=?+DnzC0ZHWObYu*gDk2Mh0~ zeNLo)Ho_68(W6?x$c5I`DAkN%KT}tcMLGgduP3z){USB(ff93U-)DBV%wC=F) zukwV+$(BPMmi*EE9 z^lN-`bRpa+$%^+bJ!-jhvw2U%=-BTRI%j*U%+aKFf}}pvMxu3I4-%*p@-45FNS~cB z^in4II1l{OTjVGoFRIM0f8~%;o^`gqX!!hTLXC!DZ7l+ri@OZkwl{jdd=4SVNCuCK zTf}RY8LCd)x)RZ$G`puVo@H-#>N7M(WpT~CON9N zIKdM4(T|$7UEeEA0WmouAc+zm4>R=rI^Gnc=ZN|Cmii6J@}7oX3n`t*Kt)kckxriD z6e%voX|GaNHe4Iy^%n2tQ5W)}XR>{RWY5sK_m;ZD2av#10v+&vOre4F+4ntHGnY_f zlqJT+r-SMo!l)d&Ayrt%hx};ojn~g*b zIF?zsTE@`}3dZe{0{b(P&@A|;t0x%&M&s^!Ov#aZ;E#Z*(LiQ#`*LlyL6@!1K;LQ? zoL>E!=YMJ-FZ*-pVyCEX7j?;nGxdSyQS7f|3wk_?NI*8C$Js z<;m+Uc^u}-7BRb&c>T%a066O62OSeyEYJT4Bmu}Q)6KQ8x8mn=$!;9~Ioh6>lmOwK ziH2~N|M8GxI0&`XU23=e*YAE6H?hCqmG|0f;DP9n?x3>|37*?Kw1sR05k)7=d+gP{ zmj($1k*y-jKaP=sfczu*vCAOj7bR@sx=RnYzoG!mMj-md8+pHytVH4;N%?=|`G4fu zoumKnaSuc2TZ(Eb2ul9L+2DHO`-y`2(ydlf z6bbYq7dYFI`d3GH{zM1@fzT>(bkwg{QN$qH53M2JnB1J~h^~y?9 zEZ-IW8TCvFaro;EI|KcH?0tn-Roffw6;Vk+KoO9X5|Hi&mF})Xr<8zzv>>1eNOyyD zcN{{H?ru1Amvr;i(Hq|Teq+3U;5mjv1|HaZuf4vSbAIbHK!)R0Goa*Hhh1;O6uj*l zKp1XyZvG1+6dbyveVB@yt|-Gr0Ux_+w%i#ZZLRYGzEt4!m(KqFez`Nv1Sflt1FVCY zN0OB@4w{hLRm+i@gRyMp8_ptn%Ow|ze`x`J_Xo{1HKz>%pz8QibF+)-{B!Z=ZosK4 z@6&U7kFF@b&okOZQ{8$XB!uWq;zy3#pEdnhNk6qXU;##Yg5PN#5F0LHO(7zgriv{Y zmNXPN5dNY-=v6M`7gITTAsb<#mx7$#i+a3Bmz$8bk-XVDsEcD#V&8!>n~R1EdY?ue zSKaKD8!7@OHM?gbpNqziozul24y*%4B*8zxH;FtyV{PN0lsfLtst@ok2C#Go;ISyo z@_1akvN-N&#fgxzX9BXAnGcurk6S-~OLR0hK6gIH4lfyTzh;zQB3S05u_{=D^DNZ|;#kR1uw zEf@(ntXRRmM9vQtHqi#N^&ALm`w`>4e?DM5cGlwo8SOU)tpR?wzcR+a(Zs={jk28? zP-U9l;e83^eES5xgn#~0@Z~K86i=s<8Aqx-MO6sI@=lG_ObwpXpx)mfNsdQqzb!jo zPcbLnjvAX9nBZ}JCZhfBF<4OW;8B7tpB%s#>ac2nAsLi?;aosUJK9c-MiiJ>8%R^6 zY#XAdAfgxk?UKo4$kdn1V8oUQC~z>x>UU+VAR9O?duVMQ?lsw7waOKuDT_HHyhX7Y7sSH0zp_InL2yvs?o3x79|I3HEEUW8Y{yxxp58SJ+LtbWElOywG+C5N?^cp; zfY}Xr7Ds+;*-kJ_TeFwg@e2k?gEIHjaT)_0ZE{U1H= z*850k?0m9={^ak~=>LkewoCHr@!C*k&>6t%wPb=O?~q!&fS`gjg1m(0`z|@D3GE^A7H$*30)>j?IA0^1XiZQPx89_pa0x^j}^fq zHe796Qys1@b%YsMw^3>Zyb-qcUz$9uC}Ia~B_QP_D{> zuD{)N%ouM^mal+` zh-`rFAnr~U`Vh-Y{0ID%nWA}qez1)wv=C2>udqtKus#jOW9z|ZLIyNL{Tb@V(H=9B zw5raU)WeybYds5(!?l58H;+}0hBN6bU8-VvvPfFv3v6Ita^r^@I%CE55{zb4Q&ulfeu*hF1r4us5PVl35#sfsADhRY^DR62| z!>&_MA40<*IP=uA>x5|7O;+IQ?I7DuQ>RS?09rm`H?umWuM4Qd-tK^bv$=R2R$3Y% zikpLQhT$`I2l+P&>__FT@xm(S$6zRCG%_~jY37!@+HA*QP2BfMkHtWaSO5sor6kmd zT1K?vqAADrYW7F8Jp$oKuozj(VS?_Ul|UypCue0Pkb|Di!Je8J{$CRh`u%OeX!L~q zNPPT6p&7*Usxb7K@Hp|!sc4fwZte?eT(F)l2oFm$5;l>2 zI^sF36CXBDb9vCp(m2gZCvcC5%{>bj=iHa}ngNqr;}mehsA2}G^Q{39o$m42Oh;HJ z`b1N%NYR*^O19K6(Zb!+`S#ha#|_kjM&_G8R5#!Z@ArAo8tFwo5dQ51KQ$nmS!nU^0W(6aj<_>VH@y+xKp?24kf&E9C(GRZk;=Kq(#(aAhmo(i2-G7?3PV)OPAMxEjeZKrp0oK zb$3S@VR4I*K649u7uy+L$$Jv-&?vV93A8J=`w}(}ZX}k0Y7{WKmpEi)(gf#u;J35H2Y!%9c+;59E z$CcHv&F=r50#HBp1Ubfde{_K$7wOuvi%(zkFZajo={rW8n#lkSm#O6eQ+2XwrJna*7MvxXMJCLW=T5@c-fUp?0W8Rd()5;#BC`cr( zHMGjBgtOn{UA;l&>Z^IY#m>SU+n7 zN52p2h{8VX=m+gxMl?iVPYQvPOTB$bHMF!dyl>uU6{ zGZ1WJbS@WgGr;gyU!;50M$%T`MwTC#mZT=sC@g68Z%S^KN2HqKYKh9snw>E_k@-%X z{#XnYcDeh)o@N`k!Zyy`>Y|I8>dgXZ@3zRSL=hAyAD(8a3eRAxj8Vt5V`G2#Cxm3t zA%HpJItJ(pOqftf#Q#cCYf!&Y*jUXH|@qEET&0a zWIpQ%(JiP34&Ujy2{oW0`_S-w4t55i=Atro*KOA&WXB)s_glH-`I(%Af#<|AYj#iO zsM7KkLb5!3Sp%3rBFa2fVOdIde!SgP8Y~ae?WMr?-duI$6xKWw0@pt<>;Taby**Wl zoycoF+yUyUEJOrp?K0YzR5Vtl4Bp~%5-UBCdHnE@H%;MGyZjxVQ&w5@aW10S-CUit zC75?N0_!oTgaHmC@`GA;k^oGs`)$U}aKn#4Ys16aWNXT*Ioe?bffm0R1&Zh#Nc2BF z9BuIMnBDs_0AEs52}B}~%O6!M+CchY)Aq9-Yp1wXM_&GVgr?Nx#OkRv6z&=Vo^TD8 znU8&GXt70(tp(-lXEM15sR^UHVVDj()vLzCT+XH6afja8(J;|HmQd6-(;#3-Kqp|Q zqxb6!a4xip=dv^Q!qrR}bqJe_rd5n{<2hRjGx+#g)?f+;%`7`OA?9;hF-kE!1i{S@ z>;pL_L{O9IKBCt$ok|l=RX8jevIceTS@UQy+ygQ7#4-dDd%+7(P>6nVdi1OfUM%{o z461ztw>SM@FUf#j)eYvwFkWLGIFXYFuiH>=(I?#}kG**T;&K;edSnQr;WGK{$nk#`iuvyD~M0qsqxjG+3x7OQ%S1T1$R{oNyMKR?SwfPcb>_CeiWvL3FD?FV98FrTLqEaf4s%6R!< zNgv;}Ox;p&_q4NHM}b}aJ~E=teC{DUz|r) zTG~XziaiV&i>MveqV@$-*C&aMc0{2;nr5K*biGk(x&|fV0&geIioz^JhI%lr7%LSg zjf-0DnY9NWhf#qub>EN0HqBdw25ny|q^|mh@g1Bm$W5_s_VIc}H{0(>qbawQ_VG_? zOSCY(v))tN-sihFA_7V{sVf%CNmjeYJGfsh8aQd`GWulPQiCRw_cE<`v!upM8u!n> zlZts8{9|f~h@*Mx2CRB?x!KuzSK<3xtzzY)yICMaQY%dAlKD`gUp!wL>~OIjNPSf- z=L(8-oWv*hWt#J~zrLN{Lo#^$Km(6L@`T_VQ!y=NK$w-A?niG1o|5|tALIa8!} zMYQgCL2W33A`_JBNZBeauq zLOkq_5Y3g+M6L{UL!QXLS~n$hS_;AQNSdj$@E4r}61EEY$6ZPpY^YZz<)~)y8$|hL zZO-Z0NDp^oh4peg9Cl|daVjn|1+KF5y8&S&l_`_(S(QlwUJ}cBA<_t!g{j8XPPtz$ z61O?2y_$c%ON*Pq1aXddI!~8_`M6tF+v&X65r8emTC(7XGtDBqqY?Z!3wW3E8wK|& z_4ffA%@B&2S-DUEf}8FmJi;zThVHllqXka$89|TGR)}n6gz3aMG1hwL9QSX&Mh~g> z`h2t4>oAf-B?fU8lyBa?W?Wtmo{D%Eq%HHVE?q6Y^zlZk#f~)u$t%dZFQ*!=G^cBu zM2X#o*I<*@bw?_BSXegXm#0lKTVy;G(t>vM@+*+b`t8{|7m1*?P%FlWQp7{YaZsXf z#X6wk(zCw!c%RKVMkIx~yp<4yau5vA;R~RsNSp=RCO04EGUr4xs@#O_RRKTDW4ce+j z!ysIK1fUT{Y)UCHwKxX4C!UBrS8wj4m|=BjnaVZHGSnT0@}Xg4Ff?4+ruq1X#c*yq zQ!WbGOX9k-s5`}f{3lJJ=0iBYPz)1hu4~C>+b-%P-}})<+{PxQSE=CCq^ztwA?Nn} zBSs~I%JKRT)s8#xMxL$MNX`m%XDe|c!in`B6VbwR=VZ`*VYxnE@)N;iPQ-ZA%)y6b zim5XIhz!*J3KzWE(7eiq2Stfs!IrDn5lERq?#W%bmHgQfvLq9-gibdnrD3b~Um}2O$&#wAuWy4j^ zCD23rfi5v@9=wvp)(=f@KH2fNz7Q*p_xf#lZXsAVTva3>DJUN<5UNxoPS^Mp_<;RU zHYE*}J{mNdj1vc_4>?70HHJ^-(O9x#`*({5JLilGN_ob|^g|83{J;h@5X%y?wUf^` zati}qUc!BzirlQjCx`SA)#1CKu%`F?jJ^Ft=E{b8fRLal^_)R zJrPkuaBqJpZ15&|S9Q)~ubwlR`3b zoQzd`7|0=9aaA9k(ISqt1`-5Pc3&3R0{(;r0(I!=Ag@Z*LOhVYCmJHceC8HRFHlzw zQ@a2nbAx~-!X?eUaR_$Xt=o62zK^R&sT63L1p&D?LFJ{V`=)7N5&&iTbW+Cscg#?HfM(X&@pad-P(;tmS6CYg@~FS2Yl z{r6%5aFgmb;w2bu%?UT44_Y~+qj>95k3BDTs%?^6bJ3U%)5g7p{oW`nwo7|MYe7|* zQ075q7!yV=G`s(KWL%8hD)eEEZlhO~B#LF6BMdfO^^LLo0!n_5+de=L-$YHRqC#^{nWb?iE}sjVhCt>7&tKI2Et7fOae0LI$@N1jq8qiptuK}m>}|(-M7ndAfcH2 zwW2{itT-TmNrOtZ5A_DRtTX0b!bBvs`sM1n{rF;M|MNcKTeI&Kdaz908dpZ7NcnDq zK#m(HaF+Ay6M*oC%asEuObie`P{g0eF2%)M%Z^kXw$PmMWBCMoriL{%tNfUM|? z@++_GL>J~dI!{yF7vBLy(%+0sGRgMimR$Ra4yb!pJns>Gdguf|c|14rZ-fB+L-u&P zDY_24h#^1Fzm4-*txlR3+CzV@bx48*1rfeb@)%ts$_qE42&DKm00xE4s)<)QtVJ3j zi1K;E&DAc|Lyk*catU65kl3Wgc+_ED?63TIbxZnN!(J1r%KPq&%bJ=R3#+a*qLIGB zQjbzfW`D~L%CZkfwgw^#%^*i6!cJhfo_!*vfz@^{W2lc0bA-7$4r;Qhgi1$KVH=X%L{Gx z`aG^#w*ApMrvq(#mU!WHk^yg+SLs>3=sHYl15--V^lsz6zf=5Y%V^Hs4Gkn?`J}p> zuxpHgRZts9Ml0Ic)0gMpkVPC+DH5`{u}5h@voPT4xfYgSSr>)$zJ&Okz|Dcc;!B>W zagVTp1j+KcAFTuj6)Ji;w$;Ca?ym}rAMsM0!^11fRipnj_BFy*h$s>A(S3<1=&5*? z*mA^IUL_G4p{Ee8>KdZMCHeZ;_V71{yFd*m{wl(7nbuX6UpnO`4B6p~j^p)gHf34Z zGHHquLNT`Q>({SCDW1n2pgfC}!I2z|Rw9!wahX2ZQEfO^*3hFK;val>@M3{7_XU4% zSE)P$!fBt;h`?iR7WD~tQ>AqIcf+k}6@4Q4J(cET1<{!n!`y}kZc+D)DhwDT>QH(} zBoa^}L{in*El?ZjhIY#hDD`HY?n3R-YmROx4|L=nzyL%`;KSMmuMAl2fhUl5L&Pb% zN)ruY2Nic2*LK-Z3H{OhM`CtB&svS1*ggF%DH?kE2B6f@z;%X0z7uGgAX8tHSKm&S zvyP?P9nKEpD>v$T1f87(!t(Xd<_Z>*UMW0EDI>(^(KmqQ7OqpLl!-STI~=^)AABw4 z0#E|;@2Kab{o#e)n)Y?9t&iPyw?vTrM*VpJ z^i9McsSw5OGqQ3Q(DURF0)u~irSTpy8oC=^-sA((Cwf#U@D7M}b9Tgoxqn!1!DO=s z{RC6JIXOAgR$&vJl257eO&6>;hO@=+yU1t7Om8)nqA zTkQ=rue`5tlsgJ>UXD=3+6i%tXmHSyE9<(`;4OUcPMEAffKtiB?VeX3ZbI1FO596r zCN$Kv?4y;k*{^+~2GX!LK+Bv>G#`II7cPmS6?ZYxa6kh>5nl zg)y^6uRGUzTExwePwz+ygnlS6UipO|^$4wQP)(!uUW^5Hf2VAKAW@PXvWT+wFR5$Qvqu0m8g0BXrYnzPeVnNh}z z7{LevXh`ezX%ua(d-60O%8-c@#_XlBsNG74=^})DRQ>{tfHg0L3htpFYOO%-J3V_6 zXB~SfiOVhNVNDx2c%H%Np+{Nx2aH_G6pO2yRVY(jM8F!EPcJM@??jX@lbO7LoCVnt zs4PD^0WqtAIT58~-0nf_H-Xi7JKEmHG~UQEfXtuLQ}=UFkQ|uic=GLynZ=qToAF)#V&l*hMFVc1b0#xv7#` zIF$iu^Xvgm!R2M@*1e>_g#xB0qfwo*s}y;6Hw!TRW7H^NCjdilGs>@*9JM+s^!9+c z8ecnA>d@USxKx5EWtR{`9et+Gk0s)UI@;6ls3NK3gzuDZ81+H6R8zv_ z0v+9OF;qqBZo&QZqLG8n6GR#1Ot8^DA7Xt@ySzzeKlLiPZs={h$<01p+K&}B*8dsT z-kY{P{tN&91q{)|3o2r7dtukdcK0k9ai$;fEwJ+9+UXU^JirSq)KSvb1 z6L|X>*U72mn4C|IKQn0@eUg^W2_Ij$BMwvhh|nTsn9a%#?Q(uSkNiu?vAfbc7uxl) zQhi^OJ}d<6qfP<=^n#4I)MpJ5$^`>0QZ8;JXMev4;Y$jZe1?^&_K>Gn-vdrQ^fA>@ zKN;~tkj-(vcVMw~!uR^-ZWlTa{i6>>H8`8`O)19vc{GSD`SPopd(Z_P)SZh>#tr;b zNHAs_{0TdQ1c#iL%P!A9;sby8^O=Eh!0n{{)3?!l&BZ}tz>V3o$=@%#4&Plb1;v2( zRGC$%b&}h$`XHv2L}rnDqxk9Z_SA*?k}J&5aj`Y9G^Op*{=1eQRt|{XSfjV?#tw>m zmdRb|H*s;i0r?Z#wbeym2n1-xN~<7gsyc)^Y?pKiH77{fU@jG2viiCyKIVg!-F4-P zLBtktH7HpX>4%DY83(`0*uKx-Hd+8NKi--MGh=73W9ZH3A{p9+B2wrGMmZYMG%Aw? z?m}=~*P9A;iXWfaQy;tA+uMsMQzNo?@MhmI|3JW~&%(~L772WAy8r?+PW4?Jc>bQu zibOIQAtE4$`u9HnwG3ROnDN8ALz%LzXAT>~&vp`lOt_hFlVr__ETm90h$v-#g?yY3 z?)$XGVxuj#$X$igL*wPR_M^FhFxi3+DBA0)!gHX_K~l8@3(Q%;w>=irq&u4l(uFZu zbMP92i?M(hDqTLt!_7`^yh{nQ^A#a}Sev{k;X|s)btqL(uP)C@Zvug;l)(!Fn$qN| z`G#mrRkaX|8pmBC)emjN9@D-_=okDFMJ_8bCJTd`>HdZ)^k_dTn}Z&@Q;&+o?|KZ; zIBDDA$Uom}3+a1P)h9dZQ8oH4pT{vESE2<5Fwk;=RWs|IMA>-_L&zV``Z z_eAZ(UB!PZaKWO4^LqkZyjfX+=!H-iW=6m3b@Pd$ET56K%Qjn@xB=Cr_#WzK!_G~h z!9z8UIWa)KtwAfMO&g1JB0VcGlp%c}Ta4iu`Vwghcc%e>4>VHs9PVS&)vhZ z$guWZ0oCwVG_s|F4~#srDmK#ZHWcnQ+_-m1+p+a3O6e;^Q}bGk7nPU@-u@G=m5u>H z8u{qj>kg_#d$bQK?xa9(JV~a()qsYWA|w^wD!N4+etwINN!CMaBc6wtjd<{9iTyYB zkS;{BasGBc7+I#kJeBJm{Wy*EWY=cCT88)!-DrM4r1P1Fho{d}2%q61$hYcqeNoS+ zSoL=}SnUhK-p08<$l<0M7rVaf1;;=OCiGYA{Gq2up4wlL4%$&AyQ(|MF^_I`J1zL~ z;g!1_9wO#8%5;!(m(uK;bI!tQ{ZF>l78R58h^>G#m%;V5c?sHnIZdo<95a14(R>%QNIpu8sW$h z+2zrFWF>{>lE(WekMEuPye~w=%t*3gi+yVIB$H0WGU|>;|0aet)m*h@XFwIwZ?*O< zBtt2|O$#HMm+;Q7CL)!QPwss0{0u2*gL(@Jkn5+U?JuP!EeUbH9?q&NszynnZ4_Tf*p<#WUfFwjT6@ zN^AnnSwwn~TeV3J^S(0~?l=!I3E%hf>i0z_kPt3I^>yhomioxbv6|@> zwu{AQXW3{&;z|2iEo{@7m0iH;_L(_^=4(OI_W7z(>!$}#@impUu9H<0ct1;qBrx=g z2iWzg1X8)}CWw=dma<{Xmufw(VvlTMhjn{ht@@9q_q%WW^0z)z?@h?=EqGLBorblLJjp0U>P$yF%PFU^50h)9_$oW`spWP z?-MyKJs_YLU6=ttNjIRC{fYQ9vCcoS|CanI@!#?pIgmz3BzEovsY1iM9}9@q)r{7( z9=2C&X+d9%(lH(ByCGYKM2@T>&#lv>sNl(LqFio~1C4zD2Z@H;sFoV9zg1@0%Xx^f zmBVmdbzHW~+fzmlv2t70OWcDq(}|rZ!`|Z3E;ki14-Roi?>C{pJK)UCiQVcO+GijD`9X`6|ngX?|7a(qV!xYFjcU zj`KA01!_!sHsJl#+yKF) zNWq7VjgQAl7p!Kn-CjgW`+~G(cj|h`WA9R(ey~@FuG96nJ9G*_(nD!7O`H_brghNk zq<8VH0R{u89n{0LC9ewm*?k-tCe!r|3i)H8x>4M#s3pGFz_1_Nxdcrqd#zdG;)URK zE_M~)3*zpp15moAORDs53Nh5^UNtl4?Uv?$)us%VTa(&4ylih~B@VaRG78mUv@C+T z1%Y`~7GJtNh1j<{SNVK&AK|PWPLHZ}HIpBvUkH$fU5fpZ^|m~Hp9#6U^>9alNEc2E ztP%Hrc&!mrTsIl}!);2_x(2PpszjV(=`7J{zug7ukm=-d_%!iE1wzNYr%(vJdpAVK zw;7X9C!R{TUikWMTWM$bAJ099bTpNSM(UHZS1tCq=Eh(!2K&425J@8eSa%bz#1UDu zx=BLB;G#?)6iyoAYQJlAX5Qqn1H@M(3j4+{QL{S0xcvW+5kLcd6GEF1Tq*k9BE`E7 zWr=ng{`bwF+;UnYMeN^r7Ka!1u$~zUU#7!mm^e_!#pKZI38$%Xhu8x3sE8iQvh?wH;%mks z8c~TtcccQYO#YGPf{ooKO83J7QDiT}uL$)rUy*g3=+7xVrHIG0A&T#Mb#E?C5Kmgh za37R=0Kq(gwC}~)vs7s{9Ym@g`yhfodS3Gkdz4Wo+_oI(`FE4WdYAqEP`UMUiDin5 z>uNUA?;D3RyY3u9x~ezS@!8mptdvopLpQ4!Q^kS4&rE95g@#{f=&WcL!U)M|b7t>k z8TXjYE3TnwTb^3gfb#Y(GZ;?=c3K}S6dR0jLjCco5j;KipxV!sS&*rRx4SI)+l=e{{>fM5qokY5RTWm0*|;6G zg+zWJT@I)vQZ39yhp-BMomuz6_EO*GO2WxS05or zNXMa$SIu4c<11!P<8I94rvIz#PSk?5wagJmK5DcLXKov+q2jryn8GV-w@pnOga}&E z)dRhAdh?jDqW={9_Wm%&ewK~Tk$@0KQwS5 z8K=;T3L4V5kR^KO!JXXz!H^YBEQKfFM|U0~-1$wR^T0+ z#@$E?*)jQ9$a)FsuzKMJ!`&M(H>_4*wsNJaL6 zh#m@|8FzBBF=OV>d9~f9>%QTF%o`K^vPWqlb0g6#(furzb?n`j*9F#B!CvyamKNM2 z%8N7+pJH;*iqTM`R$)&^C2&Vrtx{bx(^jH#-*!yKY`M~R+V_e0)l5Wdvx_ZvL`o-K z^Th_e&g-YccZXL?TJfw)(Os<(;A zqk|aBoE27WXvpz&{yr`Jt$e~$=gYBrM?-aM-bC@3v#^BW6)-20)-Qyb(u%V%bon2a4Khq9GWIyWc zjXd-Ho#ANY15PFN&KIc?Z|ZnegIWVvqC~^rt1?j zJXF5jqszd3G?~bIzl5F1Wk^qK=YYs2T{(SWOk|!h>D>ad<^#XCW5lqjfbEfr(}!ty zkrB&!XSR3qic?tZNT~Y5Y;lA^77(ssmO-?g8clzBUNzdJY9pK@Q2A-042pi z7{kp2#lnQl;6rU4sp!(g*Xl)lg+yvpBK5umon=+YS@_&(65!+TX5 z7AS6Ghak&t(8$12#c-?pNR-m)VtOP9iy_P{%FF2Pu5#71#krIg=pfODDXM;%OtHZ$ z=dUSbf;l=7U62}3JQ6v&dmsBvA#{g(EWB(=i&;!l%^T4jM&ncg6ZPme;jSN4F2(F- zO=M-Cag?+lER|Yh4l^;XsleGk2l_69GWw)MMGv4^F?f~h5#9Go!D-qAib{lp!pC!; ztqTP1NU?yjX0U*<9PVm@p8|S@3qhP6NUB;MP>T)} z#B=L`-Wr=(1r3j9F(!&?P<$5;wZwS%hvjw<;oXv)3{Y?eOcWlnZ(v=+^qKt4J_$fa zh^}5fC;h{7;Q`60!TY+Vw_-(M30zFzf>y|;%;UJTOEZ9VbbOrO@b%~iP(0zO&H<8Y z%UM&10h0pg32jB@VGGPFa0pTG&V$Enufu;z4WBz7^l$|7JFkom4@ZNx#g+myQFG-2 z&B|WW?5N~GVgdelpZ^w@b-o7yJ~{00_RvHcu{AS{V7#L&w~2Is>DkD3H-%>Rm%0ms zkr!qDDZdGPfvs{;F&ef9_w!P8`XJk-_yrb|!GKyW6tcn#Uwz6~x}a6YCN;sl_9oPu zC+ani&BDu}T-8D)@v6TFOhG@83ZlN10vwxxNyXNL3B8Az&)Ik6E6g2zB?;@*>h!7Z zZR^74b)V&b>z(rqB}q2LcYhfenxzz@D$LLU0XX{#5R9N6?R^^Lm8S7nB!DGbr+yadj zi8r;GoE){nPLKQk+wu9;N`d1TzMsS2Z}b<)poRb!Z{kAUhNzdBhQQk*pSp|#hOifn zfE|q?xXA$*E=MsE*4>+HTmdbD8~{i52FHiqjWp~Tg33!K=G7@Xj&jzogAL$ZubJ3^!QKb?>?RHjX8fs zM*G*Q{({$Vd{>Hg7tZP_ zK3A(8(qc7_IjV}YeM!Yq&tT1v{w?Z25=(Yj`J_2BE&%-W!U_A;m5aLLLYx_PLRoujf2T< znXlwcTkSxKX7k2Xtg*$LhcZp3B2}t*jC4h7cd>pfF9pQc0*ubTgG(y!e~fn0q-r?L_%K z$uc7%=EZDmVI*TDgWK@*{fhxktMK&FGN+lK3rmShToL(C@BMFY`0sTylKo?#-~ohM z$gDG2=D4)Z-Q$H=+sIUpS223H&P}M5pMoFvhiF;8sDeXIX};aQW0mf#}lS;pN5mWBUevpEARF{?dn- zsOpZLzlqiU$8!CF&_WIZuGG0sRQ_J%IXNo&x=X`Z%CoVpEDrOEHdMO_?<}+zy5DUo zERY(rH&uc+C%Ge)NT1uw7=O2SCv0VjeE(0!8T@7+y>3zYOAGLCI_u}vJ=@_xbsCMf zunwS+{Z*g-6wdySr~GFpw^Mi;S9b@1K8D}w{Qv!>XE$))M!f+%|L@NRE5ehN7IEYM zzPkVWD(&$gDz0=_-ToIl_dget3+@*cni{MR^tWzyRGN{OvV0r-R}kU zkhUhvyN1Wcrkct|Z2FIPX3W8f!Vh>8j;lcJ3YP{VuY>+7OCasoa2ikuhH7usUcN$7XKK{dNsAHV+8%ix2R}o@+49VBc<%kwCb%v%k5% z$|xwHXT1V_Ny2@Z-0I*QgG1!lM5yItS#c&?0XWHxSgPqyAD-NSHCI?gId#PPBpcQ%GdJ|LlrpKflVT3h zkb^(rQ;5V`J6j+CB}~H836g3${D!ekFN@>leaY!*5u97aStH-&nG`hZ$0SxeAFoU? zyS3)<@2GFQd`-1?X21nIqV)JPZD9iY{$Uuu(SI1^tTza;Wk;{R2%|iy7Rl|{RSN6x zHE6~?oGds0n5kXBLl$%;B@p@-5cznI_6M-f zx)c|c8Td@&xiMr;x_fl8&$`s@9UoCDNDhas(G@CEt(_%i{M|0+X$y=%wSZ5w?l;5~ zZjj-RZ*e%~iK|MJU4#|JsMwp1y;eLbU)A`NnMx|;o2p>to1(aR##7?*qP^U3`7ds& zczv)C1bFuCNfV4;o{7VoeY)&|HqAU+%F8gCaiN)4nq9K_MZ4sy;x}XQ@AGVGET`CM zM~yb!h6{3H@P^lQAAbwsyZ_|{Ph=c1)?`ud$n$_!M;gsigTeu-nCEIPZ+ID|i@Hn$ zx*{zmEGxz`kJXf2j*!1-`Z;0kvZ_#gB3YtH(-}Y5pye}R(`K_e8sO}0-LfmMnOC2z zwznH9d_fx*NZ7@OexY~vI-XXVMzczW$Wp3+F~M z-3LAosFv(EhC2Y(RFo0+A+9Z-mx67eFzBU`AF;fpoHQ+u2t}(Inps|r%@ejDiy`%t z8q0-AfBFV$gV8`~WODolnQ2MNP);UmpfxCX3}uh;#`JrKU#e#w(SlVPhK;ZvI{a>? z%q)zsa33*H-yy)k%rgG*s-Kg%Txt z2ZM&R7XSN;8ETT-bVVWH?P}`5!N=VvCGkhQC*xwdj7IDhTjDcJ(l;WG07TXePPr7=wNAi zp-MK>PoJM(6u(h#TeafdPy~!GX&E`+B(FA;V&hJa)6>z>(Md_HJwOfUB^9F>{L0Ni zKS2lxL(h&;?Ee$d<%AKJ)>Fg{ksUgOi@7|lc0v>M{oLsT9=K872d3t1av8Jpe-a095{pD9NaA$$h!e#M@C7-9kuJO zo-$sMt+~ByuN1+N0?^zeLVcvIcQYkZ&Y>I2$O4BMSX>XRJ3=Xnamf$!{@VHJFO$u# zceN$>1R?*4-Qgs+kcmMrbusq7-YiipBjFS@5N|3s0sOU`WcwmfHv zuUf}b9~8Q@ly04W)p>vBNOJm!HIs&x5;jZ6Nw1oURL1%O+_FiM^@~xa7H1o&9$nAs+(jRkksL~ z1wsGg@RiVRIl0hjlu_%Ese@PdGIFnx-1fR=N}9fl=cWE9%BW=uvZ3aZW*(ZKxUtf>2hy*29()Tp8#LVzod1OPbgX{EbY211A;U|q@7#+iOD~>% ztM`^i{(3HkR&9RGO}mh7>NG2kgvV+cj2@%@ek0uQ!9D_<#$ISbk=TlMkixa=)FXTl zg*t7%ug_~@YH|@Wmj}!~2ItxaytEbYN%oPEkvj$I>o`;?J43Ppfd{%ORnw9p4llIm zg*p7{_~At}k$TktZ?S3V!`(v*^{O{1595yF|FRz+N)U5R7s5f={Cnb=+>Athm|7bV zZzi3r6S!%#bmjRl>V0!C%q=rW9Ym;Vr=8cO`kHk|NFkM9@_?GEC13Y}#Msw!p!n1+ z^G#qu78&w&e3fS>__@lSC@qL?+K}k-&4-7=J6Xh)60D0)n*Ch|($D-QC>+B4yCs-Q6Y9-8F=C4b9M;dwhMv@0|0;Yy82?^W3px z?Y-C1qVpIGh>5(buj9Yx#IUHI zfwg`!ltKH)ocrZhMzi)=_=gMmqf&Ev}xmD>a=}Cg@~?L7~&> zu=0WQc1G33@Uo3#<=vGrk|5E@pg)T8p1eq?hVje>3PIai6lTjm zo((_#ATEsjrTyy%{b-8GYg=ocmP+Nv>8GJ}cGs3~cx=`Wy7MEmTF0iQl!wFKeohyS zYy$*0`^RUC_dP{y%2-D>!n}yYR>yjKM+@`HC~;V8fFH`pLGM3!5377%sN#0XZL<>X z!A8Iaqdm%b&jXlz+L7t1b;|}RAMlrTe~^z(i*I!GskR!cT>%c4yl`*xPGrd1?BI<3 z$}1+lx7SNX`~@>y=Q>Q6gN00J)d?`16gBoYs{fg_^W?okEGfQ|SI5$2GRs!c5M`2- zIRYoDk9_9c;gg-6hg$)BuHtQBUHKEKyfU#=!u}1Myn(ZJTJ!z*(w;bS6A=RB zUl{hk+20VhC9$Z;L^TV~yBd1tjr`Xc1l=kuF@j0H_xn_U(tqvO%da3_kj6m8?7-F`d|TRN1%F# znnS(L)p_G}?;#Azy=n*Mx`L}$6J&a0Em$2bSL$vs0%|sqkfwTgUw@6n1$FmhwRNcD za2~v;T(bU~+>fPjwrcZ;({VYO_p=_Sixy8O(x0`y`|o`Uqx8DN(Ma9@iEU8XV#=bcZgiAn#$k0Lg3jw` z_Bud(9bct}>+4+1bJOLE)AE(mRUWqCjNl$ot^J?fk(3(h)M6Q6)l&iR!h)`$H^b>- zWtOGM<}M{^bIf+Pa&x>oqVG~WUu~1_5x2~&&Hl*wz6ICn;F?k=GmOqZe4!jw?*gBy-~q_!!Fhn24&lUeHOK}{W+f{KCv0hA z7GqA$pfv`GPEJnogEjVwMh_;`8|OIdPm&Z0uRZjP#^a{+fG67W+pRBbsNu9oQLC_9 z4}4W{x5|lr`O3;`aqzNMSxe&0T_ zHJxon*{~b-Z6`6hvK(W3FqG5NO+iG2Cm;h!e#;Ra{3n)_&exPEb!B{wWOEv*5yKPb zNY$cdWGiX-;@O1S)ew5>mGxLy9WO|JdqDJMI!w+y$LYYb`JviNDrNdM&x|3l(nL`~ zY!1~zd3H=&?%2t2jFF-d_GisZ1JmtezsQ@iqFfF|T@YqoBh|m^B@6fX;$M#h^jf>sE-MV1L9uK24lzd_6So_S;9X8~H;l4R_ zCo@#!-W-BKH<a`AV}cJlv1Kj; zuDs&!`mvgQl46&ud@0vUkX8$@SQT!EUJZk1I9fl*Nwq{~?XY>6?cc+~uEV*@@*9{gfh}JZQVx@#$ zXTCRe`gD`ni*oEN1s5@E*VPCgj4}dZ==R5XyXkDMnUY&k@m&=2Z**$pYp|kWQ3zr? zuQw0o%fGyN&~N3W)iNQ5iO?&{>-VB(Rz6gDbIh(0XO2?n8jDHQV|3#M|oHT3kEG#Pz^L2Te zF!s=U;}-x)?>*FUd1SUXz!32rG|^2Q4J9a+b$%ko-g)M^2GDH+CV+Btn3EG~Ra27A zVsotIqT9ZSeU`<$`>oK8ZjgnnpqsWh%U9HB;=Fa@}w#o z4aMyUM+;UoljxYSL=5F9QU{RmYp28yg;pE+Ih_rC>kYeqxd4GOCdmO~NN|AE7m zia-1#$FFMNzt9<&Z{L48AUJEcA7N$UYXp%=5IZ|GohF?R-iCgzI8>HaXL*m>TxsGk z5wKJgpdFBg%^lStxuxLS5yx5=_~ljF-q{SEiVm@;gk=QjQzVOv?+xRN*G~8hdpzAm@oRNxb~xU02>O5%B4r4W zSzikElx3q}c8}hgBIAVX-e&!?)WUW(_HD`QvUUktRgFpx=0vg@2j*rL*iXiveP0hXq(epeO zC_O8pMR6q)vZj0Lr?xu72f=1pdP0`Gwv0O+R9EAQG=k<>!~bUJSDhrrICYP@{~hgX zBs4g7RD|iQS$pn5%bnRL3;fzp5c59bB=E7t4rPJ4^As1lG5idWWK(IL=Yr%vKXYP(2o@GEHKv;ts~Pd~sa0Wi^u#NWy8C~)ry+#CxZvOZy_O?V2CBUu&P4qP0ERvA{@Su*ALll=5zYf95Oo#^TE zann`Zw3B7+Aqqs9jK%Mx41!fS`DDK2T$pqM>k-k7anIuS=@3Gkjx;sTlBM;CU~od_ z1mHUxBvWp_J(zxVVnR5tp4{U=vyUz)#q03n|8F$Jo792K{CcMq%gKuw)o$7>s&0PD zB<5sX@Cj@Dy1Tdp+;qI<&{Rba&QWP@PMOfH6o(y1u3{f6UVE$*8qLD7(Hpa^F?mUf z*WNN`JT!rxs^S0G?9%;ckvmUmi z*d4AMlbSgv_Dx?jE{$!bO0(_~yvz~#bL>_>s6K!cIqX^dd{kWp%r&kXnd@AK`hvEh z8J(YELvBy|AOpdSZlBvvTt0MQdVyI4-Xv1Qx$%DO;agT15_&I@u2oM_{9$1*-d4-d z+Dzz<$8-Djmc>x!)er*R0vBg>!&hRy(--RwA&i-!gqkg5J%M_y|1#nMceO8u=;rJM%$32&}==)n6A1uv!VNU6!&k39j zp2@7Q3nOu-j}^TN+^?@*^+)==5UwA`5-Dp=6*cR7_lU2}(RihXdhZQMz8j@XqKK*Z za{MBvy|&K6cc0~n;>qkMc^SCyoS5lAY%OxJ?l>v8-GYnlQ-~k#S1t=Or=HHZs085` zBe#NO@UB?#=J?u7%7`u1`$V?Y?+K^1j+ zSR*tqF4Onf0C^#8aG~P-N33WhQbNF7fIZc@K*W zF;+~Noz|0hhtOK$8Ds3TB%;Fs4o5mGdW96Bni}#5!tEf6?<`uTD4}%9{C5h|443v3 zbND`f(&z^Fo1hf?{(H1reZ#^mc5j5klvTV6^?aJ}$lXeqqr;+UL+70+-<~~Zy!m()Zpi%dRulXI#4+#(y

    *k;LSor>D#q?0leb-^+15hL0 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 diff --git a/docs/static/images/dictcmp/dictcmp_raw_sampled.png b/docs/static/images/dictcmp/dictcmp_raw_sampled.png deleted file mode 100644 index 2eb6463c24807d383732d80bdd9dca09acf78edb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 247385 zcmeFYbyQT*+dm43q%;m74I&`j-QA^h2@D}QbayvOr_u=0(j_h3NGmmTHv{|*`ptXS zU3b0jpLg9^>&!W`&)Iv=e)f}}=Q$Co$}$*eFVNuN;4tK5CDq~JP)Fe45cyG%fEG70 zZv!~EXYZ^eBvj=jBq&rJ?ai%hz;JM~5lLFe+8RGV*?Q`7mI12y!ON1{i1E1MQ4|z! zv6JEvapj{?#d=1X$^xwk)EF6>UgvJxf1j&6QI8yXwt6yeDdmLW%=#ECFaWF zwbSO19~#Hy2;8U1?7 zwcDGrTMiqGg)X(M(}~hG_<}vWUSeqECXxec57Uu=dHarFYeKmw)>$LOlSgyz{gOBR z6vWyb$HIhcII9F-izSJa{f=q*G5%UHs6j9NIdRb!uI@t`ahD}{(GnBzo9cNoE;~;)A0e;ugf> z@E2@mWiAmiYKCYP^}Yy;%@div(zWkVqR)N>vUVWI|E%sXi${NHyM(cHl;Zgk;V6J) z9S+A~+7ihkA2V9^HwO-WkO&1Xe9u_RSb!Q1UEs~Agzov|b2F@H5XMmdlQ>FdxN!>d zdri!t511Yb@Nu7{Pp}RV2(5)}zEt_`@T%hFjng4}=`@QwYMv z1&m_<6m;M|;33>Mx(MzIcI|x9`BohH_vTBv2v~-8uRmr#`R5^M`m)KEX za*bwXNUdj7-!@86uiQX=-&#=a<+Xq71{vR0YW!r|d*+H_4ac}gHxr)Khgo_w_QI31 zG8Czm=2`bgWRI5*M}4zbZ{Di^*4&-hxRB#OLX(UkQ2~)rY`#Z3{=ni-pkMydOKkBziyK$_2zGd);Y)M<>~l8v9-AX+nbdP}*MOei#4q9T`c%c+*& zNh+wfyNp#UItEh3%SbxdyZ+Or>{)5Ymn!};)cYb9Q&Mf73wYYqXPF_*om?a#v}*|7 zXu`xs!4*CAYv=9^zC`VDm%ofqoCArzhx}6ch0}}Uh@yd75RBHT*$Ll?+K|DIY9Ap< z^>It|ETow&CRHI*OkYx8Wl_oIRUIBVu9w`)52!yv@MR;ZkL5!0)hAv|C{L(O(5TaH z3+Y7J$X!s##83WEcSLOrYzguX_KtuBFUpJ;{8WR{L+~lV{9sftdO?I^z=Yg(-i{h+ zalq8e_slM2-#+Nd2#K{y_=o zp-t7V?cV%u?FNg8uq*UC7=>_!t&UZhl^-J<2@^>$NI%Fi$N^!T45z(L*(?kaBo)&^byxrPn`sOVQS6_~0@RH>k zzAFc6qJf`|D_M|7zulPEwhsW?WL*+r*@a z_aEP8yv_L*9PAPN{BKJ_D3Nrl+vLR>xs$ZEHWLw)fN3q`;av`ZYFPO%nwPz4qOmV{ZP3gYEwhiuCt?)C0| z7suxbw^P^7*Yh{#H|3|TBizP)H3U^Ew>U8IyUi=oliOd1Yerk`vk#+iv;Hmqr0^;- ze9^cu^XxnCKDkhcIA|ElU^CEhY!!WqH7+&wb#OTf!p^{c*FO_aOG2y6Y(Wc^D%L9& zJQLj#C>L=9)$=_=k9x*{ZjC;RrGjZh?90;0Peq%{ol83*;3#e8giraJ|GmzXOE#ZM zN9phL=nGOIYiDatCVn96Fh6=@5K^6ixlNy&wMr>{ny9Y#+?TjPaYQ5piL1EHPRzvbVh(5cqypS=`d_42rw%r5(3Y8i?dSW3NRW?{D1GxYO)e}91BC}#d$tfY-oWuAY|uV|8NV&mGDa9D@Xq}YMOq|2zl$S;x0DoSZh_UX%<+qI{YSw$?F_xZfJ zS5Ts>XWyff(=<7t!!06F9M+RIX09`P$$Z>So1@#A#C&eIf;%lO9_>jDNsKHWrv^K2 zN~gon(X5A~+x|mqg>MQQ1+#uASMLCY7hoo~AU+ym4f3VOOU`0)W2m5esFkhU+KW37 z(Y*iJ&g%tPz|El{PR31#fUR==JS&?fXmq8W;50Ry`JdKP!p;(59G8;z1r?nB2)^j8a0z zPU(Ir12-NIfvs-~4S295XHI0+hHFW;?fjH`yDoMgbwSVC+xoq0>b|ym)XZz^W>@pQ zmIekJEz>OPrNX75r46qRvW;wucC&j&Tl=%+WS>1Bm)re|3V{w@Rn535m5M_vY#wLM zLasV;5}(Qf`cK;(71X{v6k=85hD0X(YOgHqYswmLwMVMLm_F*-k(0Z@ zDjTIZo%$)=~1S;oJHrw!*gO1l}y1 zhG)$`r-Z-U^%wk7=wl8u5cUXh+gP0bS-L;_p<<~a8`8QKdel3aam{12{JLq{&Fg@) zGpo9}$&KM4ZK?ErY5Q`CRG2jEFlV3m%#Ky<;A~F>$ z7b#~0Z|-{?sMFkO+rfm$*}e8<=W)-yh{V0%3P*X-X#ouUP%2fSWN7cTc3xP(({{PM z5LoYXIIx`2vT`4A_j{^uDo*95(3i^R3vBPKaa-XmMd0q~-5q;dricT6hp|LW4ZQm| z3^>mhaM4jmFjhk!C^5FAj|!>iFOhfWtq!8zaK%AcI}IbYNXA$;7fzIJC~#l$Jk?P| zg@nS=rSa#o$=)-+XgS7SzPxPbM)sdV?6C0g^IN&OzaKq{ja)+f$C0LeSC7oL^m1`XAkaUqV!t&dv_}EG%wr zZp>~R%=V5JENpyyd@QW&EbQz|z!OYP9(K;}+?nj0sQ(V~&v7KdPNt4l4$fBgb`+1} zzB93RaTcPYdYtHApTGYT>~8h%ne3eYu`FPLERT0s*qB*a{xvqxRq(NuU)9PTY@;n{ zWed;@m_wMIi;Y$APlx~8t$$DXx9eLcu%m>%Ezr|h_}^6jN9X^%`G4*BXHK1e&&kHh z%l@Bp{>QCF2};xD29v;veCMiXTD*RBbpag|8u0~<+VC8_cTI0J0<_(32A zzUcm*folX><%e-=X*f7hI5|nNH}3FzS*VM?64wvN30b&O$>CHucQqF1lylXkE2HUV za>=-Jv$AGNxbkX|Hxcp!AHFdrRnVxb=}oQ8x(8xL=7E>9OnFRzC#&%H+* zN7*elIXGsA^iR`v)2V8H;RpL;VHnvF|2-cW zJPz~?&i`=ge|Hx*7N?|GT?~G02iE*wt>Vv6l9?K$|7$v+1fYiahdbeYX-LxfpHuN) zOMxi6{O|VR&j_#Z5vWF#idQwwL;m+$+RZopzu<#^t{_zvC7hS%bEIC{bAgR4A}o)WroyDZ1;mk=psW!%W4hh`IJU8kwxaX65mGRM-5OC0VV4cO3TIkF7QaU zOGWENx(ZZpcVQCR2CQ(y7*~&e=bE}Fty*X7E4t*1Fv!Thx>bp5Tbh!ApFWV_!^48Zv*OEHkgh# zG)E5GZtdJoe$YaRX_E~-tuEUiZrfw+U!%ZdZ)vhL@(goM7MH_y!y%Yyu7BUHXwSye z3t}`j#e6fq3e&$FWizAw zm&gW0_&@5R&xuJg|EY@teqaXlk^3zUv-OdXUN_zBlrEt$l#M}@L;ztUBtalBa8A&3 zTXGQ)KAO@{Y1FaE`nHzt`&Y6%)5>{wLF6Vm!NZX`R^40G#w z%Rw=4KHafhNPrv=Z$*nkio>P)iOf^KD+%n^W~bu&I(4fbQy`c%LUa6

    o8?qyql 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

    2zg`0wBi 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 DGx8zP diff --git a/docs/static/images/dictcmp/dictcmp_sst_blocks.png b/docs/static/images/dictcmp/dictcmp_sst_blocks.png deleted file mode 100644 index 551860b2e9bf564b797a8f00e31bab5e0ad4e1f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55789 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¯SnYbem^+FhgCyfJ17~zI^537?# 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 z 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 diff --git a/docs/static/images/integrated-blob-db/BlobDB_Benchmarks_RW_RO_Perf.png b/docs/static/images/integrated-blob-db/BlobDB_Benchmarks_RW_RO_Perf.png deleted file mode 100644 index f412ee60f09f160bb4ed30e35cbaa9654cdde1ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179656 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 diff --git a/docs/static/images/integrated-blob-db/BlobDB_Benchmarks_Write_Amp.png b/docs/static/images/integrated-blob-db/BlobDB_Benchmarks_Write_Amp.png deleted file mode 100644 index 19f40b035ae02cb1591620ee87c2a246ad3ed0e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148777 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%0AqI&(R;<{wL;71u_7l4m%Y>;GWFG|>2otoE#DFg9 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℞*8JrHCr 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 diff --git a/docs/static/images/kv-checksum/Memtable-entry.png b/docs/static/images/kv-checksum/Memtable-entry.png deleted file mode 100644 index 31eb7278ab8daf37837cca7d9abbddf654aca199..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23812 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 GdiGx`eI-Qz diff --git a/docs/static/images/kv-checksum/Memtable-write.png b/docs/static/images/kv-checksum/Memtable-write.png deleted file mode 100644 index 32f526fdf52f0b076e839d3a1c4090973b52a76a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48417 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�rfeGw)0G%2F9Yn$-zjfn!Iyr!)Uj+nee`Z1S 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 B>gfOg diff --git a/docs/static/images/kv-checksum/ProtInfo-Writebatch-to-Memtable.png b/docs/static/images/kv-checksum/ProtInfo-Writebatch-to-Memtable.png deleted file mode 100644 index 91ad93b2b76b45798656231971aa8ddc2a8931d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62330 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 NgywB&H^=HYanzljpt*U5>R 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 ! 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 z zvMoaLqP^`nE+wt zmC=gAqs8m3UVV3ot7<|Cd&7QAO1ne^->(+hxEAMI>jhH! zPM9p+>$y+$>t~yp=D>U35UWF;^*Owwv)M|fDBiwWzmuSp7KAInX9Gp%IpdXU)veaynpe>E;2s&qVK|#5=O6UW@=hhllwZ2Z^k0$ ziUZUx)dW4-w8!2nT0H(3So46tqjWib=DqJKF&?J~x^bdlsg%O5Lu@&%TBKo~I`Fmii00CoA`GkInn)t6j!^D23CnZ~8k*ry$ z{F3LBikZngDs=MEGLZs~Rm9R2*Q15no3U&^XHsMxeJ(YKQna{cPxBIATj9x!rl0M> zXWM`g-5EEM?jcQoqLOX>LmAxgGk7 zjTPTpedVBS74_IiqXy?^qRCl7-1OoPrG@BH==m1jR4*?>#~_uEuSoCtTEblPvYsCI z1pA&VFkKeDzoDUHY0%Oz#;jq{oY;Vy`5IrG#1=3@M@x!szqne~9!kt$hYL7IlQt z6b&~|`ir{)cB##FYHpc{&DI*_4_t5~zmU`*QB|I? zKFET%S)H#C|80@AkOg$)jyV zZu&d9ls!=H70fW&QJ;job}W*75sESY(sPK}R0jfQ z6{sd${IT@){Wd-$Qaz5rT6Xhoj-+-wMsTVlV`YmA<;WztCWjaV$9Q71-R~W%tOe$f$9HZ!=oaRpa^l$9AFE z1C+A^nWbM{1-g*{V*0h6cqgml_03qDfv|Z5;p?lgf8Y#_l9f=Yr}41OIC}VJj&9jI zYLKU2zW=MX)?gq*h{KH9EDmJl~mNRB$noil@Cd9OtjsgW}m^zsVUHG3;vjzcw^~ z8H&C2lLCTG#uXa4RCX&&3C6n#r54I_R);zwy}yc(m;Q#Uzw7eAuvEA^-LK)@irS;j zB-e>Ix8nE!P)e;*BUg zuXOyM^NHe>)aU7I0*VT73HYjMMhziq?&ELOSPm$`0xAo6g5NndKzW*oAW0r`ZEf*a ztPMQp;(8(*<~x&;09z4_`kH$$3DG=tDypz!ID>Ygs;Mmt*({QxlY3r@w8?BHmfVM9 zV6Kz$yZbB2M`N4Mr89Yb>qp?;qM8>s19X?}73f!8_?9GSZB}FYwjvpFmMn0`W(^-= zBDJs-DXyH^5alN(QS84<&DGY(S0Z9^;e0hx>m0P5zx-OGkhNst_=;b#wZPe2;;8Md&iO%YH>Tz3M5`Y~chhn!?-Rgaa2Q>BFt)58G%GVwC#JNwgq7AKA3b#(l zYfo5a%5fZ;=!Rfl=-E2Qfc7dBR-ghqE3h=2Q3%UUw&HT+IU4H)FbNU_t|Bi@p?Oop zxu3sw++uIN2H>*&^s!(U>Q=X>^33ibRATO|z0YlBWq+>*Mrxcx;z!Rm4@EBheNADi zk0+IVRCZ)A{3*^^K)Dr>Dw>UgE111+*q5bT?(?GJ%`JC;Wtx*Ze^kT!GXDxf|2Dsr z?yd|Q^P6O;q*$GrgmjfOB*t*emm*S4NEwZ!?C_CaQgJb!V}G9w;lkw|+USEDa@TQo zkjPqR&_gFdnLcdT2`mTF56hWRVr}BUa#6r!|NH*`wV077A)?bEF z#APw2cZlC$AoA=k;8ip@CR<+w*dqBJqO*!E?P`zQ^dh!^j=w>f1iNb&`*}z>R!TwU zV0Ux1I&HIxT?Lv|)C5cqKu9uR#*9SuDM%+!UeOJr*Ii~)O|?Oc9k7JcZ{_{BUnXHE zM2aHClb*fJ8I+_I0!~6;hq2LxCy$unM}@wS9qX7^R3J?34KsdlLJD)tGpzO(+`?@a zzElEvhB+=!!%{g;L8JT&xSZLKlv$g<)5=eLHrR3V|7-xSKVq2nsh`k0{k^LpH$u{% z!FTV|X=v?r%B-#^GDQQz#-j+&>B2rm~R#Q1dO^1j? zC|hJ-qr_-mY*`{HODRP(lqGA~4MNEpCuOgYN+mwem!;0B>H80S&kyG@k9dvOec#ve zysqbUU3U$F&{nnf$?chR;bO|{-tq-!E2Dk}{qvSaiuhbU5YtNAd)FW)@%H(2 zoH`8mhxYOrS$D7mGKq3?hsyV0iqlc;A8EUZ@AB^@tsA*@H@BRiWO_Twzx+(@-Js{f zEb6PK)F+!4Wk&b*TE+AVdIZ@|DC*DEi0mgjaM)IV+?p;mxfC|~Ywo&q$6IAdVe1^t z&=Y~i456aft*5xweq+r0btj$XCgp|4qs;s7c=uOb4te@x2SWYIMRX6091Y3VDGpxj zpFzSr0DA!n@=bnjixF`r*UOcr#ObFy{2m)nMHZ0Nguf=-Y&&I7`?+le5=qDo@F#_> zceZ2(cHzR^vLtwXOIBadOco0ddFLA1Gj5l#-1F+l7)-ghtRcmsh6zux&F>{}E`L%S zyxAee-?!F+#9=G_*4Hs>@&%~KYZfOk1<8j8vrupKbZLrh3k2lup1azto-ZMu|MJf9 z?T_c#IkHE-9_CM~f8=W0d9C50H3S44zIbeD41M7&!}2RD|u7SghTPI*hk} zl^VTb@&){BNyvBGv4^kHdw9~|uD5LIy{_g*sTzH!f5o(Hq;tH`%)Nhq`tg$+LAmIK z?NN(H4pe-wY0p#|yO4+0LZ2VM@Ix(50b|DYS=Gz@_`5-*9U+nsJ6x+fXH(JFk3ElO zhQlqM`eaO={GA_ryi6Oe>fok&p~;{42a%x0j_g1vwI{C`N33_YS|@8-mtx&WNm(Q@ zxm7>(A+nEy^Ci!)_=Ol-$k?YlEe+i)$wU z8ntjbGhDvXw9cwILvHXZ00|IfmRVN+T*6D^RsJUb-*=sDc zjazIdB<@sP84;@!@-EWF0mRiUZH4I!{O-rG+s>j@@UtvEo7ZoDxO%!IYD7)z;tjIa z4-SKycq?rM?8?td&b+7xU?s$N?*Bx@5X4dnmM0x7;NnlxO?NQubQ5-|J0SHWcH5cR zthbvd7?K`+sa;`WLqN2^l{&ye5B+oIY1^JegJ!cj>{@t5#D+7hoP>zFFeLr_wg}^K zMD9g>%mLSm<3_;Em(j9v&mEXN1ckHS7IFQitJFxsukw6e&^k2n0{-T7jKyvADt{KI zSq?k3Ej4lP{b-4Es66s}di>V_f!Oa$s<1WE&9f@V6Sih685C6~faksxu@u8E18?@9 zrlBZ@wXAO^aCeEjxx(s+ms_Lv(8?a5S8^}+%H6R$1>j=fnZ-4tl@1yHhKjJ?C4{ck zzvu2aGj;&f)6*fU&rRzfwyOub;cdH+t1{Bekj<$*dGI&f4O6M+bxT;>`H_O|_zP5P z8Ws62kTQ}1J`Cj#i->p&Ls=Spft8{3qORmhYxPkJ$X}^)={$eYx-qRW3CV5EJ0a8K ztvPJz`nKVXS~NinuT6|q@mokSikTVC#k(?}mF1+0JRifEK8qAzc1}jH&&kVohWdA3 z_OI>8aeZ$fM8M5EVVgbNnTOw^Jry|E;Y}$E9E`)*zJmR-Ti`DKf(K(dW^&Jqu|1i= zkmeVeo_GviU+Re&w4y!rv+~wThGRzsY!ck^AI`~~uemXR~0<^7sc9HHBoW?%D*Q zTAyEYsQ7(qA%IEAkRpj~-0=9j(otVuh#XtP0|}>T3Hu&E`?_BwE@G+fY@|`Z!4HNr zVkf-4$mv|70zx%q!3LL;pOE!S2cklnn6+)@RN@yoGQauM&50N93(svzcLbsWirq@P zB5U!AMDY=lpUqNN+2~DB*4H*lbHJb$hVG#x;gwWh;3PshT; z`Lo8YMFx9J_f<2+kB-Nt zB5@qBkkiJs;R~`0+bR>_$05Pm+N{DKLKb=M!PY+hX!?G9^LZ|ep;^@wUJ31!*S7fx z%ITk`LG`!&9-R}0EwYn9(-Cqi;{kf4I$1l>;4Q42VfMzsy$HIT*~6vwXPjhdq`j#9 z1ejjv>N^hTs9QPUn~;@5`?YJ-w|K3%OE9sR$HLmga*G{@_7!*vCU-H}*cPl&RobF8 zM`6mi7z`{!pW9TOdDi`}3ZfddZ>#5-)*&@0!_q^;TrCj}Km>7SF$H-tedA32Zy?QmA5#JP~<9Mi=gi%LimUM~2mX2w*!i9KpTj9j&;h z*{s|J_jY0}C7yl?+lcq4&zx76?eSbA`e zWda3fTo-qgJ2QKMG`i{^x(!yTl)oyt5OeJ_N@J|4MS8jk`{ZO$gYSq| z?<>#%w+n#--_X{==-3ONdt;;GE-vI6b`iM-ijjnfkZb7x?zDJ%T1-UR)PCaP#qU>9+R~mkZINsVg_kKh!3{!BmQ8JODxKL4A3hQcF8!zw&>l#J`K9294ck6fMpPOHf5#d&t zvBdxJm|Bq7fqkhq)`IdjRzTl}wRT%WSJ!>bMhU4|RrFtDMWjG_<@d7{_=tXhc65WR z5j=7gf)pkm>E*+pqA>y7FCWmvf&E?4d$qK+taYgceWg((vR zmAP#836ea}z>Gl+whr{i%3-K^bQN2RLCycxp~yN@K=F7c;_0l^w7P7?q1GuR`GVL= z`dqQi?B68^q^*V(W0q8IS8PF|!#ZS^u3NzRUzw0G+c*R>KO`3_B}+%R*Rb0nV44Cb z|MIhQmmZv5m>_u-g;teKPBk=2@*Bg^ua_Ikkw+-U{jioO4Gk3DvIrx{pjsk%q-3*l z2ljBVH|w0`*3K~ zZHTwJp!PB~BTP0jgCN?t5)_nik*^%^s&m<^2=F%OzzSdW_a|H+lU|NNeX5Scd77_R8z;rLBnTs-9+v-|h`o02&ffq?!84S^uBhpJuX&bkCTgJydpO6X& zXTo{X@a8_H3c-@P(owhKJR}`MX<;WIn<~kaL7?e z8z{b)9XH=Uvj$_I=85J8K37s@T)#~s8t%(4^=q#*vO_=8k>oEU;HMbrqa)wSVey+b zW1Pxp-LwQiHixpzbI|#QL&t<4#Q1G)&<8i>vahW}EbYdblo5m=XTpeCp6D0irEvaZ z*m3>ZngibW9i86jQ#1I#>V$!osM5NlRkA9ufFQ6yv-N~H_tsUJ%}XcS?;Iv-X$5Vc z93K6aGkO@v>ah8PQA~k(tY=CLjEkA)%(YW|N;gVmWk-71O}Bu2uJ9F-wQHqn0>GzQ zXU<2)CPGgAwD(LN;oUXaz@WXiaK|apCffh*(s53n^jsJN3S1x7z+NtMnFo>pRtORvf|&B*O}XNzM!j-zZK;KJKsT+F;+ z@A1CYEub8x5tLnn<8w9g)ze`7lZ{9b+sx2obD-$kI=fb{w^i95NBPsLNaWkY9Wgyb z1#~%x=!KjOM;^UcC*HYa2Km$ohD}}B6|vs!uH$#@uJxw7-?ro^su;dK`mQPclZ;K1 zLQJc_uLc09u!pV})t*zV?{;oN;r)K|{=cqW@QwjGc(^5;V%?y0&+M1V;_=)I{p5^g z4-Ixd{@ZUsEF%|Hst3)Op>xwt;=(7&8bg19;McpA&*X>)E>De(d<=7OWvLxzt#b7$ zJ;Z#vP>k=)?{w#C804<=tXR)azWU0Vi{eHXvW9nQ4Gr(k8-%@Ez}Y0m$(xWLSSY&w zO^_xN;}GULUf&m2eaJk*IV2q|sL@kxWGNaZnzerKJWWD0O%&VbitVHmCDUS|x8v8F z77^@|#uWL|Rw3MHG^@#X;id-tKD|N_didi^nO>oZ81mm*0lU=hV|b$LBp37ArbaP^ ze{5kMK34J_`I4)?j4z6Yz3A41U# zlS#A?3SM*+WAZcq`t&WOW|w2wN`itd?dxwqxB0=(UFDZ0bSieu7Ld+AhVc94!411A ziIFJn#DhW<9D(7rUTBzk+FYB}Yu7gW_aR z&MD-i>2^CQ*1;JB!<_id7>#prqt@vJqQ(o}>S1PhX$CPR{$V0Xi6D11w0EZrxX>3! zpP+hk1TDVRD@dLVI$9t@CwK?5qT-=0rS#H7DZ|gOYV4D`J`w-#*gVDeuE^OOIf&6e zqRIPG4*jEevRtkHp|u`U+Qfhwhjgp5uH1-~2w7>+m>}PijpDz_{QY~7dky`D!yDA8%TkFc71J`rwk^LFJCj6?!`qI!l#1b6Kr5r2i-ND$ z9FYs4g{>o|DjW5KiVs+7*Y-Y=`W&X$@`Tb$2cH$Pwl6o@hS+kETG5Hx8T|K)O}fo#lyVzWU`3~X#q~l@~ z@@usCR#l5RYzW@2`N=0IPD|fJGqO-I>5+86aM+H3?^$j>$uZO3p!I-bAkkQm?#&sm zo4mZJ9U+k{(fY$ADs*`^3U5AkL?klQcnaTm?Dne&UL-_tu440?EJW_PqL#uzO7djU zB{FJg_v_G~gS#QyERe8FTZge_`Z~dj(HC6VbnN&F27_^Nvw?iAecH`pCsw4!itIt1phJ(@3*zcX`f;Mp#>ha%pi9Q~R7;^A-zN_s6M^8pAFh<@ zRuOqXpSL$!qe~a9oS~9e2aw9szPKK>87$s<`dqL&+{!NVXjEg>p+Oowl?r`io9BE>;@Rou6)PbkkNj zY-<0*)3byc?jOk<0mncI_{>yv*oTHdn6@2DDEeoaz5QL%+HUm{Y`yi}n^ zL5nDJD5>^dlj1h_vMgJl zz!>>~Hft`+A-zoF?xECk6c1Rcy4?p$+cjzwWQbV}kvqdfB#PG_c+5d-sD({vo8x_? z3>B`|j;3rLG3`$D3Vp$&-(R?Lw{i`tMY8rNoAxbq?_4aus4^c2XG*ivsBHV}S#%{X zv8BxG^&!O`$&=~2@r>)Z145v&6~Uo~n0e>gB`3t(?Go9jOUiD2*>TQ|nNFU9^w4W( zpPmiUYzzcJX%EX10t*wW(l)%TlOFhjZeE&+_S4rTfj*wYk>Eo+9v)8JtoUL*G%7W# zA`XJ(vRPU^*0K733S^6h4s@dF`tMI##qw|gMO{civ>lK1u59GzS!I`W?90H(Yepu0 z%IpD63`<#Gn!!H!SyJlqy!e{$1eN zDtjRQwY3ZcQvY^KlWXQK-pMX)EBzsxzhuu{s{pm2FFQ<$u-zJ?n6 zPG_ow*CWbIDTpc??nvAvXZeitZTJgb!N6FX<_t6No-Q>AsERhC(Jl61!Fd-0QRZfZ z5UGvmvkN^^IM`3w{`!G7Kw4wMccS;93zYyyx(4R!Q5^Du*6H`3*zu>t-^c>yP01-J zaHSLI8e4wvr%WGuJ~P@#7`MT9~aac=7lK930NHS z1%$8PZ+R>nWu0M!URE3yz6uKF`2wlcqBSX1u(vxgbD-%{I!y^os;$9cuD zuh@{}Ex>xy5c!j)t5XuF$E>b~dy(WuD30wrWsVOY8I3;nw;zVTao4Gr$eJ=<*DjgD zlm1bMEx%=+KxWnPrBkHQYkh4)6T-vh6zSu*eT@TRs$`IeLh=Xn=C4I_)?*G1X-g`3 z245)h?&PmBC;qTCBI)kbJlVMeXK$bj_ODBCt*J0(j>`58KUg1T)p4c6@y*D zw3QC={}u5PbqMt-7MjnTiHu3wnxvuIF}_?(ey3Qt$5=!OHu6MOUukYY6_9k3gK1k3 zGAiXoG69#@o<`2cTr0NxFo@&nL1!iQXQXJv$tHFU53D!yk^Cs$`TcVR-S!Xrg&G7D zr?%~FAnv43jVr0VNq?Avupd&5jd43y*8s=j9!cC zY2juJnOd-yFENHv?L13Z;i)jD1uKiz0bLwGs)4s|k<7m>)qlJ~A;)#fQg7;FPAA0V zL_9*@G}^XkbPdW(4~r zHVOl82vx9osi*VoW0zUU+44n+v*UGBZ4W#A|;QiC~WEi>$_!U_fPZsq3RGRTm z{_XH<)x%R)l)Y(2xUJ02>3I|dX+GY3{hNn9(OO2m_OUdcz{gF;UMrdOnZYXr?BCh}uE%m@@Rnlw$H3(@Mwt9d zZ^S-sfo>z(eeKY-Sj5-V5VAwPiRua}bn)0C^i&U|xk@5Ixoc3`{5~6O%DULfH&$ zoe%SCAsL5a|GsWB@+mbZtmXSVSLHe19xz*i77U;TV2Hn9(j6OHuLz2$~{w3}~0N!mQR z3^tpvy7wtQ>ATuGigh{|hhIp&_>A!D0X+%UkE5_Q4)t5<7@DP@Q+#gjQsqzpD)e=D zuWt`EW}@mds7=s$;{pJ)%2|6+M=#(Rr;sYMoYf|b{o789ncQM4=`dYah_Yv~8Rkgj z(Vwi8S*q=_J@SLCOsO}-Rn5!y6<#d8 zN%RV?{oIU*H`kqPIBfjQ2~jgqV)nF1F@|kBta>>Tb>AEHBVjrx?#9*HGY>#Eh7;xR?L@gH5O{KPP>B-~Q4RKIK+q61 zxR!(^;ahERHRhN;k~~ag^lX;M1V8pIa$tG%$OhGL6la#CY%pNH0EEyDKNof1WFN3Z zVse#&c&S;5ZL9LMh>dOzkr%^B!q<^#d6rv_(PJiig2;)zNEtz6Y;gdYcwrVy;F!RirIScyDRo-YWtBk;(DD^T}UpZNNHRK z>z)5qEJmfCk};AL8|hPe#{>BL|CQk)+)iIODjSE4)vZYGb)HT3MfH^!tY3~yT5l)? z-C!vo#2eZmWp*9!lPF7Sl&YfY5Se$kji2q3AP*k zPJ>Y_!7cIauMH8weMvRm!7)#24FEuDCD_Dv3AWcRxqmXuUQ{)rHm9VqQ1wXoE4| zbt8u@Ouoz0Fa2@L*mV*$4AUKlH5exSMUtrGv5DOoc92M3RmOpTFzaj0YY)-0SxcnP zKX1B=cC4B(sd_L#6){?~29)FZ4 zV*TPoQ9yyM$gMx2V&a#&h=!W1cPn)Mh!6ii_$KxB1H+dkbq^A^wH6n!q$9?`5Fw&G zbP(@8zHw}`f3JD__rXSXlCA1f;^X6^6{S_i3EN0+jv@I3ZuR=#tG$rV4dAL{3T^5X z+<3$;U}5Nq@v{Y;K$1VlXX^P=A%1=;Sgii)scnM7Z06dDsvnk`$IjuvHLSR)lk+B5 T>8vC+{HOZYE~R^0jlBO4=QS6N diff --git a/docs/static/images/kv-checksum/WAL-fragment.png b/docs/static/images/kv-checksum/WAL-fragment.png deleted file mode 100644 index 9bbacca0d801e4905827a47592f5a44badb21de9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18832 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 J3UsXx{tpa2S0Vra diff --git a/docs/static/images/kv-checksum/WAL-write.png b/docs/static/images/kv-checksum/WAL-write.png deleted file mode 100644 index fb9fd8fd508bbe2ce5c6531edd63b82560241013..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53414 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 Mnr50|vOVN~07fIO?*IS* diff --git a/docs/static/images/lost-buffered-write-recovery/angry-cat.png b/docs/static/images/lost-buffered-write-recovery/angry-cat.png deleted file mode 100644 index e956fb6e07ab91e5a4447b6549e467c76db31bbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44801 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

    bRBa=h^Xdg4tIJY;>(p3i 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 hC8Xy2g=KoU51Rjd;jW z(}}vw5kXUsM$!5@69w$Ef;uJOGM0>GY1sIQ>8k}y?k1<#ZdTtsPbG|`w@E)Pd%$a pJ7v7+N-=odFY5G{4-@mnwld~&J3bCyhcB9QXurwN*?a9S{~vQ<2Z{gy diff --git a/docs/static/images/lost-buffered-write-recovery/happy-cat.png b/docs/static/images/lost-buffered-write-recovery/happy-cat.png deleted file mode 100644 index 155b5341d97f2ae83bb66943f941210c8de12f30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45197 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�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 diff --git a/docs/static/images/lost-buffered-write-recovery/replay-extension.png b/docs/static/images/lost-buffered-write-recovery/replay-extension.png deleted file mode 100644 index 5bedd949fd5e7cb7e55a620a8c23923d5b115ec5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105737 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 diff --git a/docs/static/images/lost-buffered-write-recovery/test-fs-writable-file.png b/docs/static/images/lost-buffered-write-recovery/test-fs-writable-file.png deleted file mode 100644 index 58db8e2a8eed5648571551b83c1f43600430f487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30462 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 diff --git a/docs/static/images/lost-buffered-write-recovery/trace-extension.png b/docs/static/images/lost-buffered-write-recovery/trace-extension.png deleted file mode 100644 index f782955b61448b7062dd39d63c729fb80b43fd7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36632 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� zJ=y?)LvD`2&sPSho*`lB+>BNUd!dI;AgDXb2l4_5q`8HL<8@Hf^d<) z8g@e)s}*)^|zJ!G- z(lC%I@{Xo%9eW!_r~?hfj@gt)N>r{dh)UX>*%*=js7sTN`DP+L#{{lmqb=zZyAae{^t6}Y)kd~&4T*2Jh0y|06cVEIt>zT)=14vRe|ODvSVR379REF2H7 zIW|z@@EfD%*sC!X^!iD`G~K&VjsG`p`Y-nmS9knfU#*h^_?2KUh?o;E6;86``V1=Z zkFeO^R5^H4e#DyUTuh&jdwcwwG0d_Whp7B<`EjC`aji77aEwwZLZ8R<(ie1RckR3A z(Ym1{?~RMU^J_-==}c7lnUE*|QR5dpo@5TL^WsvFVmFY1lx4sxjSQPXM3c>O!0-j> zV_wsS6a63Z9ROy+W<|+ip?oqD?)p*fWSlzdz7cR62e!*%-A%i z*skDBp|N^_t2%xJ!&RRkTbX*Un&tbO{;(}M0!ERjhoNw}8+Skrdr6bNfX_xCD>p$f z+@w-xBsl~$;5OoZV=5DTF<{MQ0?1>?CqCyGK8`r#L8(>4TI@hA56?RXfonj_!|6da zjODF{t#Xhys9_`H#IXl+cdEn0PB^H&ks5Y?FGraV0>Pi9rnD!(5J&ko1zhLBC90Nj zD(K+$WI?)C z>aQvQ#HPSaG-uXRrH1K9t4hU#)?;iHH9`$z6v5NqqU4?OO_575EwUE8^O$T__8h3+ zsf0%8WwrFhj2f2Gsd^W#hW$DDTQm1}+gaa!>qA-QrIsP&iLtMkT}6IVic-X7hdIMa zzKu1wr}oXn?E7I=Dt?_I)%dS_({5g-gK+#@)BbQN5^?m-1WOv$0t{5am4))~kgH#! z%_>5s(>OAH7JDpDfjY#KI?JvR=JWxdETDG`{FWzC!=fjns__>}csbXT?c!6_uupcF zr>|(J?945_3=q|HgvNbFT`LuP3ru7n3#dd`7{>A_xFi_wwQ(mU>COu?P=Zj-OXn!} zP)V(!Um`7m7TM=e<$gjvLavYW$$@%n4%vg|3{o5wH~V?X-o+ypCA`e*%Yd2|HH`g? z*}_65R|l!WoryZb6jX&^M7&GtKy!PW2R*D8VQxL-W|EbT2=?kAVX_SUYMM}AdC1tK z*|hlr@{PTmK_r1EW~U;8sIL?jAxezL1jC2Lrk<0tn1;w}h#2^c8OVUkGBA}0iEI=} z81{J6ye^;bpoVD_P}Q(LHlDNR-UN?bNx@F`Ail7wM+HH#kV*cELqM;IBz-4a1XK#`!7t)<)#Ko&0l=QHa=0~{~r z(=R85!$ZRc#mo3-*qun_V>Uh;UHSI59c9o04~+ukN=N-v1xT?e?IsokbZhpRk>0T9 z?7-z9Aq+Z;r-m(?Q`IH+T5&^F%YcecYFITx_0A5QecZ2G)EbUosfKNnhhto?`2iS^ zp26&F8HakfG*d(YV+brSXjY)4O-d2oI=KL8(45F_dX>@5AFHhbcM0_*DqJnnJ`5|c zQP|3ka5)p*iX*t+M4?I0z$d~R`xnae#okS}Yr1LNcWRhl;t|EJ2K+jaPMlfEsxyk^ zh2lEJDXL-(@tWTqspH|Msiu)HQ&{6Y#(iA>+({a#nLb47R2&6qB{al))smvWNzebu z;MJXf+daOZ`J?7c02Tvm%j7kKMRZ-)U|O^rSW8#K?7cil=OS+ng({I6Nee9mId*sM z*U{1jdop}RI-;a(W>m_J+2#n9jfpI1#Gq=#W}$7s>uNJL(@*Y+k2H44)Bxzid?A}Q zgMMxf@aal5Y@nER#-?T#Bn{6>BOBl#WKJUnAC&Jr_eLQ}o}l}lTK~x*CnRBx4+1C0 z9(4B6NYAz9Ku)%bCjZtJ^r;W0hLH$vR7GuWI9sl?qIJ%`r4cD-_sK40{c=Y@NVf=h zUY*YlNQayfss__7MU6z3Jf&pU^3gky%|;{&kIzX-8Ld4NA*E&rNc{v(!0g)`c1VcF zETgE0KYTT%#~C0@gFtFfBfo&wLv`asdQ_3~;~-+1Gol=^;L*0mY#mrK_MnHu9GOFS zu=_Mc5&^iuyL2&ZcHjVO?7^6v){12cwN$`~Gin%r3xQPn8}GyXY&Gn_66PtxG>|qB z6orkXcF>2}C_7>szr+1|;jZal{^Pcv`T!3+hJ=27fIkx2!^R5Dhz=z;$!-Y`h_*_e zO;m(8+_gRQ>R^=@2~7*-P_wa!vv8tP-v_7-N&bU2lGImqFZtBfd0Jb4zu;DY@TL&I zE7UO0lFQ5CC_ubHBg^|c;bX>Fe7B#UL;%zxseQq3wLySB2fB1?ktC|%xlp%?vZv)^ zwSrk{6@QrM*ts2@L#@Gktz9P{8cOM`+BgBdM^7agHA#!S(INnWZEa$c)Enh|u8?AXQ1l zJbhNoP>v8VwT#N+`}iFZFKGc)>`A^1nMtXE{3cl=-;^Z{(YK-@xY@WIpNt`?yt=~Jk`4U z>Hb{1_4B4qOcbv#K4^7_&Cm*}=m9Cf4>sFlhRo98lT-M?m3^+&Xv=9_2UdXv8!4w7 zZ770B1p+l+y!9(et63Pm>X_;NQrU3KdFA=B-A)Ol4Ue7$UHWqJ3FT{&{wg)>x9*v~ z$o1(nUk2NAbkgl#m34iVf!4iVNVMZQs3k&2{y12%1lO zziqcON*)|f($md5f71?JR46O;M2Zk*9a`2v4ibmD(oHqOnSI4A)@w8l9n`xwgNZR2 ztJ*`>YVh}H%z-m-iw`6Y+V9>Z`cbSGFGO%f(_>Z2s?zi|@vrS(>pPy2XwQF_#w`_e zdTv@=mGwBQ&w)HK=Rw%tce}TOcfCJjt8`a^)RVpm0$&{}$VNvekk{|ufozn?#UfAR z&gvX&zI~8A=J~b$iZfFO!|Xi=nY33iTS@-WMpk}lMLnhu$=&;tOA8Y-rM;>If!8k= zK_AtXjMDE_oEEyr#4cL?Fr z--7rLO%?+yYWGG&1W{(7z{8!@0Ce~=AcbDJtH25iAgA4Xl}gEn1PY543RcY7IqtU6 znHy2Vx*w0lJMLkJDg{$2|J(NeY*d6mGZ~&_1w$d|mqJmUtE9c)QN+QA_$sUlt*S10 zMdPY4r4TYM>2GdT!zvORn-kq2mY$Ss-GNaC)^xoTw>7>>HfV|$8Px%os$}HX>n`sy zE)UCPpZ$to?ZZ5?H;3*jd*WSuc%5nS1HG5gvm5x;?vG~?JvG7!NGH+RT}1YGv9ju0 zwI{xq*=AQd8+Ef{s$7B5#y7|l>=pa*ZKB393$9F86tnfwyobpxBCXiGsZ^~mRQK2L zJxl(8?Tq>&-f^X?+&U^Jes7aqZC0R}GoP9bS^DzkA*Zsi@1EI$goY9{#ib~S*^1Ao zc5fcjdmyonqu#uVcxmCqfO#$ZHt*o@p(eMP-NaQQ1-rMHKh+mRu5hfTMIIU3>k>k9 z2v)LCbSE@rwlclx022eD$oszdru2uri5`N;VXjppAsn*-d0oq>)MBJm=Y8K}K?_e1 zmh{jVkBbWHovSM9!|+0>6uWPY=oOw_dK|9_q3rzPekhB=Y2;N}-ZGik*A=vhb{T9* zpE9;4I0{)z3dT0cQlTBhMEzib+7xV6^A|!TD(NZk5WfOd0Z*3}s~(Ohper=f1T=;9 zckd0q{1(5}d;ECkuARLe4_6Ig8ec9Wut|%0e}?r^!Pgh3eOZ2Ox=c)zvBHlr{6T_I z?w&8a1qcVlYy*pR(zWB3D_CmS)z-N+IjDkJfghVhuv;%(GgeAyBPsO)IKj!Oz)ga| zR!D49Zp;av1AJ-UoNxhJpp6>lFYNs>kaf#`-tH%a+s7BZDH;w~fl39|JUjqKh!Sa2 z0ZfL2XUfiH9**I;ReWJS%|8N1u`Plu>u&a>j!KEP@*-;5)*ILz*2he|05&sPX8Khn zk-^iWJak>38DowN4Y`0l`EGzv5t`uUEJEFTbF?u5GD*(F)dNDUZGfNfUT4r+qlZJ@ z-$#7h2mYKIIim5Xh!=2TS9NHHYo7<6!tP$zoy%J|QCiYbJ)WN0Q)}x+PNt@ltOMzI zY9B;stu--v%V?x(37_`G+OsBySy?;Fp!-uoXYm359RwxAiIA%ao)vK92p1dwaT;k{ zC1)8)W~E9wzD$0l>gd0yR>RfZ{|`OQ2{mjB5rf+chDi+k=FZxfavTsgrcBBJ*jjFT zPYrXB&q)Fltl4KACUHY>`>k8YQBKs4!4N2NX3|KljLtqQd+oVo@ff{-tl25&RiNkI z2sV>6tn#D%rU&_7LaWjWLcILeDdh~s(|Yn~&m$#`#Q*W;*|QCBHOy_=Rwl>0FtqZa zgTrHyCJ4TqJ*9ZWFVI2R@kf}TPV1$4hwq<{cA5XQVS(m`rzn_4%huN)`Hex1quEqU zw;aeIcS3N)I2AHSWHbHvcf7kQ=bZ95(>hAwfPP?1YG@Hy8;er@T<3wjhu5m^x-6oW z+<*^j4#vg_l@4MH^pT;KD6T*7vC>>@-ET-)8R3E{KPOT|BJm}uLUhOvM^9v5b6V<$ ziY#CIZGS%5;Yo>_ad5f8i9-1G){IvG)e891;Q&wHr7U$se)H$E%F$ zgt09D$?Q_)CDFo^sB3qKVGed>Iqj2-GvfIs?2Byrsz$S#j#t0`nUHgMg(p?dxZXRZ z%_QB~sI8Cz_Wn71_|?=z&}G*LP2J~;&Sn+<>XbFWvvB(5*~P2*juy6eL~>J;#a`vw z1A#?I*4kd@%=#;*-%meDm$-4k}NomYdVep4yVMP^aIvMy?L75Tmv4`|EaDy7w5WAHI7O=$%p%+_vE=1nGipjSC zKu;AA!c1>h!^m0!f=J6l=Ep5Iw&LmB74F%j%0*r^c0b{-wDUH7bQ#hrSgix4wMCv~ zyC24xs!SZKyY{?7?sn~&`XXC-p#Rj({jtY4y!dhGjOq@;(V1Qrk!Z)4VSUWkdTn^N zqfWBZvGI7S#g|vZYmeWK3HIqc<9KB+X|^Khe3li^R6uP*Kk92e`qZDix_1VpG&i3Z zX;z}?u`dK_n66DS9M!)9x$40uLE3~g9FqOm->t^Lw?h!g?NWpbX1~+Cj{$!=L0vkJ z#mp%=c*@MXX^P;G!dt1)1Fo0(wmQk`2drA-fE&rZ+OALaft9^&o6m;<(lXgDl5hIW z_)VLaAx|*^B-({Kt-9ozGU|LsN{T*ehji~y`_%em{)^kbIgu${zDP{{j<%+?EiH$$ z5NBU~Kj-6|P~`af_??*G%g2s+tc(sVw!7(MK;t>PRruDX1UQWy+s+<#vg<&KzH`b6 zaB-xqp*KZ&PI}QWOUE4U6h0uH%`ES|s)o(jaD~bf`yl~lJh*07A)Fj7{L1EAATcGE zD>)!w&MBWRI|!`AcLNJ;xANgyI9;yJqy80QqqtxK>w)yxn)Q26qJ};20Ba$Z)48^X-)-0d zhN;3O0uIqIUYV~I%wSAYt2o1@ZJa*2p`)qu^y|$j2W)phiR7aGu#<|@Z=Y~!B6?lo{vMknJb1#!oc&L~eP_u| zUK0Va9csbJM9Muq?8FYHsDLGjv-(4jZECw9LvRoV7idFB!n^k>kyfipnm+`+qCYw1ajsna z0_8R|Z=3Z3uIyXXi2k#}rNvjEiR0%Nd7F?2>U1N!6TjcG3bI~)V8$kPgX&Mh3*hjVzyrhGv)!a^ z*rzkt{2A}gOGSdvNQmr7%ceV7oYH1v#hPiw2CxMJf_XP&s|ZBpPRxEPW)s| zx>CntgLjJLqkJf9h-cPeK%o(oIu609inv=4Lij__95A;rcv7zj;SDhgIbY@D2O~4| znAj#xuy9sbT4|EQ2iJ3w(Qnd(zXFuQnTz$pIxb}jQ$5k<_@g9(k*#S+G6PXZUD1XX;SMJoakPA zGVyHmZcABiE!iI!o)|3A%A=JLZ%lsh-dWf`=-#L5$HcmkSSp<&fL{6AET1VF=_y#= z4xx7vL6TP;h#Zjj(h!cO)l=oj7o2a^Qo1UA2KAEyl|C56vK$5wu@X-mu6KROfoZ26 z5TUf`#J-ulX}u+n^}V;>y6WMK{6~)FtlM%UW6_0WxKht(x>1I=A=9Ys zrK9({gxBZNJQ5NuE3PFJjdc;t?GL{)(p%@8b+zjcq3+rXgxHRk$!S)5saosvB+840 zZCt3=4d*HbAB zzYBz8;1|5L2noUbOTu1{X0(}@5N*9_ARgWuD-C^56(>K&9m|tN)>Yo}C63+k z42yNEA$J<*c9A67+PfBCt3Fp#T~kp>@wxuto&hsqB%$H(-mD*DUFOy+-!omdv(C7s zcDnUm@U5J+l_Tc^_3+OVNNxq-MWu;);N|uQ>B@9$bZzY+F$y;`qP6V~lpE92%ad1b z89AJ*JZP%uqqvp9O(RLQfTyL@bOyuXNS(#o-@THB*3&AO6SE}Q15DRMkf-+H4!kMu zK(|@8-}&x<+_#-GiAJa1iMKUQ<`s^cGgn;rcE0zJS$E%`?+#&?memCI@O9!6hj;4c ztuFmu=(sgaK+)eyx&5QVm-0@}t<%BtjeNGFSV|_;$S=y*LBZ%_)19oViXgMV5P&&c zK)S|mm+G|0=f;9qAr6W{)hof|XX0QPu+B?KM~EP{kBe2rIE z%H3V1nKduLZ9OVCl7#JrsFqU-z(}bh8tKFgyZ?7;J^#D>{u-Du`mIj9pJjVLljZTFjS+HU~sZ z1wI~oP%cs7p1cey0g0RHoH8XF#To}n_2K4mKe4ArR zpNQ8`Ao-#)Rz&y|k=T?j*5Xk=s9{Suhq`a->w&99g2w0`q8Kr_ta@7Nqg*whZG0Ol zBC2|UZ$TtZTEp|&`P<6_NMo0}!t>p8&k|$No%X5^o;%M8N>urFhS!8kH1`=#+x z`Mb*dEZyr@wmdqt^}g?izT+d?hj-Y2abyWv`W(W;BE1Z5xIokOlhFTd(1sxu^VMO5+=iMH~6^m{@SFlPxbT2 z(Yw!vr`ghvB{$jS^{mZ!5)$g^JT}DgA^dL15xlPsY8-UQ6u9ShG97Qs&atG(6V~v; zC&RY`r{q!ti)O8Zn)fYB_xt@NmjzVuds;lPG_*j_64^R-%LPJ<6c7AKR~peDq&HT& zVXi>RjUU+Lc6Wdc@%c{(VjtZQ)2!2buPl&&YG$Xnr5s_2&DKXw^TaFd{lu2F2mIvT z-;Uimyld{69tVqQ71~1n`=RPX+vnNV9Geg8M!KF%EY>fUq9$^(5-|!|acv1z2=rKZsy0g_BckXu39DyKAHg$- zR-zi|V~tI`>23j9EVR~*Z1A!$oY>#n!s>=3>oI&S%}OO57hbiKsQl-mp!B&!9wNAR zccC3^WAMlCWQLYEUj=wp7qfnwP(%TApk5B8kt%cZB7NqRZ$1g&udqKw9*Wg zVuJBIhz0dW$mz%MwFkEuAm#NEM(3}BES3nB9OwW| z;yEmLF{D=(ioFMonxVD;w^l=fy+>H6jr-cdUNcQLb}))2X@(Hid$ z>6S70)iO7ax4(aS^Ld+OC(`R=+WLOwvp`*Cz6E|Tvjf@Jk$!-%l<&06*7eU&e`yB} za#4u%7ZUFg)g3-(&WpCWK*s^VWGd5BC2JugAm&M?Aw$#}ehXljMCCr2iDGeBHUjY0 zxfepcgt#V3=4OA;|NZG^(e;FV_dZZhFFa(GUi$4^E~Rex!W+LIbHjW5zQlxQob5B8 z_PJ!U9tVZbjVJ{cHtX__EJ@m^laNb$d!XJlvE$7de=FMNEWaIzT{qV!#Db>QOPe z*gqHe^egRJ;&thum6f9jWZa@te13f4^R2EO9=L0&7uTNp7`&Z#ytpRiE{v`#QEoaM zMm_lD*37{8xdv~iXFD3v&ROFwj~@or4)s;IOGoi3F%OH*)iWLkIA^bYmCE*S`qB!R zfu|rQ7V}uN200Y=HHyX<;H7-CpBe90UdjMJLV?%>kn0Z8MCWL87|FD4io!(KPlYCe z+Q6_}GKKrD)c{Y#>xhrwjJ^hObjQ|5Pv$93gUyB5YyRP@+78A_OJruZzr-0l>fM0Y zG3TLiUYwjc^qwC*y<2Os_w%Z`s0h>7+|46rgND~OXVk9U&e=@<{?vW4vvXv-xdu9E zO^N;4EHA_3Lj&C@g=OgrHwVC~CZW6x|!gWgnX^J z_4kv^DQm+@p$eD$lC6|5OZwY@AtnS&HrN1kM$#W4L-IQbkiZQQLmGExEWhd^(nimt zI;y97+I^S1`8MY#$-}~C#`LGPGoJ%q+gSQ^az<)ZR>IqMaUNmrgfOtyco8 zk;`z)3c~W5-<0jD4-V$?jkm>Rp1TL{{8aZ#p}_a}%q?+nQKR73#{4C~?D|(P9RoVk z%^jMq&y@8$wYFBz83W6SZ)CeZ$+hY^S~AQ>s_r{a^Lq61ucm}$H(Yl%9r4e;S-n!DQ+dW=n*ynGzH^>-1>*9OzRp0Pgr?XhUw#pgzfEkBl#3TA1PAmi8 zAjl|GfdrBfY27@^1E!oKuT_M4oG<_ZIcEqwU;5mZz$7Wnc{Nb!1xPcQ8&xGk`6R?Q z4qBBBYWS_EPvj{~{oG57MPA&1ykg%7;zv?+Mp9mpuN!*N*B@O7vNoT#q~%U{RB-rK zM!%=0<5tG^maXOXCgo=Sc4iGV%^T8oRwo30d;ePDn|5;T<0pX>oAg~y2iOj1jQ5mV z!kc&R$%3NWZ_t~OjTx>k*WaY4mgFmfD}Y0|9g5{(-E=rQbmFpNdt27LYpN_z=ktlC zABujR>+LqDD=n;!Ye!AGB^27CeZF{LUVQK$7Z#)|58mvZv9O$edeS+iyl2Xvs&e!6 zRHkKE$SD(r14zvWJbX1$n)S{UZs2x?JP5M%JpjN~ko<<(aqRFd{%^Lz+ z<_|4(E!KV1`Ry?u9BgOF+l=NSM(a8zs66MRs;9_n$Yp#>R9uafVjoD4(26rPaB6n{ zO72@VK&g*HT<9=M+&Qi&395_$Ia9a`2_)A2dZ@8y+U zU&eZh7>=fpP)N*k?Y6N}PL$qnEmI`F1mtrzK=Vd#=KSnAi_N8b=&kZ;Pa8Ln$XB(N z7siMjrlZ`aY}g*nYi1a7K*(1RoKhKDB&98(KuGy+Ts7AxsbnkKd^-6a#xA_gGdAxJ z+Tf{KI)(f!C>IrFi>D1=ac55|Dxg9pH7tPDGu(ZHfTZTkhO5tn8t z5*3o>AlZ6`56OoY1DWf1DNpaTe`lULAXEYFe4uXAw3{IBe|j_0G$dA+enD{x)G!)c zwUZO8G<@Y445g#+A=G|g50N3^ulpAlqGj^R827nLqYfw@;CYc8a_9Vnh}5}`TlabuhA@k>Fjq>OxlA{3bNK?_(v4#LgQ!^jW*3FQa<^`6)T@&;QRci%M4>Fe2!?`#ww(x-8KM% zh?G`24K?jrb@VCo9GYVsj+GsNiYy{n8}X1Mz;Ab;_0k58Rf?%}u3r85*gtQ(BDF@v z`xF1P`@H27Yx3N+i*+xrU=mtQ&NJJ}t1ikr+NaO&sueuHvgK-KeqXa)N#8pcud5S1S=1;Oo*$e@{i)aDdYd}33I_|wQ4gnHUA3iy^tjej!6u6Q{_%E89~Q!uGHludrj}DWPh; zY6U*~^@A#qiXFk1%()8|K|veC5J@zX2E1<2E#_5um4Q@LKaJ4aF{b*8M5`=Sha`Q% z2eW}n&ohz8oMJhJ=ieD}BAtOTqLp-4lB;_q?WT!yGVY-Qe>cZQ5F=t0NGa!1`aL#5 z72>78sqmr+oy!R;v;5J|Z2*!CX!WZt`BhJ(T5J%(8*aBB@E%KY2s#AG1T@`9jwAfb zgiZ#{A3kS7V6}p6dH*4j00f3v?;*w4sVu;(dTbhl&rPpW=@9w7d;d%K_*+%CI)AJ7 z$GS?$)43lcd@NPN7LpxWvk^^}$-##ME-z_eH0)d+7K$163rsr}GswEA`j3ti3wP~hlO=1j zj(f+-D_5-`ZrdHDmwLtTT%Akw`qaGw*SeZhK3975y8K3Tik86UGNn-LNHJHHASZgM z*1-51vCB1#Oqa&*MyWO>2GpLJ=mS39N!piWi@BWRR%raRSQ)KHBuGe{>dB}zOr_9o zPU1J**2YNtUZ(&Lew06HzNk~`;BDk%#?Z^1nuhY6cgZq*E=0Mz`^dxj7uydcGN6=%wwDuemD$;qg<9!XG9Pw61#0TaLEWUv(*2dn zYD09u&|Bxgl!OmlZ~emHY8ffgrZQ?hd3ZR-Sd|Q1Te~y{-WxBCc$hHID8g`SqT=TJ zi#|J@*s^tRyM5CF!1LNcyoaruHBECXlT~+_80_zo4arL%%f|68FRtwS{l`$_;FWH0 zeYcmN!0#MohSjlUgr==rEjuJ)NQ+dzlS#3_61;wxwrj1dg{Rs+eNN`7#)}7_oczp8j_GoW9cCB}PvVq%F#}My}sKLDAv%`F^`ttr=5V zezDA!6|+j`BxyI%Im5Oy+oHXl`7G#cltE^K=zxiV$D?Z}sU~{v=~Ddmli3%K`9AA< zHu@{ozOAt$b2!A`sB=W1!T_;B!8q^NEP0Qo#rVv;alKk zT5(A4bBwLZPp`^qt6@o0MPZn~^QNZ)%mPI&zv?11)!SOJ`Ef zwg!d<2b(f3es|c~*<$i8RG6Q6rT=nbY>(LbUDM#DJOTf9Aak)}@*eIISDEGs+7^0|t?D?>>)s$z&!HNtNU>FLak5a!FK zu0U3MhdyX&w-Q{@ShsWeF5&(f{|i&o-C0nqyq*3-NG6fcX*k-E2MtcHKeC3pDVbQN z8^7A~z`GIBt1$dDxHwp`F${3~TH)_jN1;L_;r`Vd8N?W}@%}H0dU=NXPJqvEhQ^+_Pjllyr%~{#Dl6g}h!GriMLTDWW7R$<>CIhEaY} z%*07Q(9?PDu#F0Sl$P&)+k{)?%04%@ALq0Pw7E~ zmNmT_?W{Qi#qNjw^F;Kx85#xOgIrLdP{Sq!*kdA9TE3T5uxD}>Rjn}VI{c$)@<*rh zHgZD;Ej@eqR{ik7Xcwm)^~;=HbjN}^T{(G7lG)d6LrjFcfCSC9F_OaM&@OZSszs?NI6K+k=l7^Z~S zKRbS$CAXRtM6ysx`Y!5r0sXo0!ESoncK=!Aex6R_P~(*RAVYBzvLl~p@0Uv zB^%#^k@~|8AOX%*_Bwb&h%iMSJpp;Ka2>H7gbyD~O_F?!%4fX0bOq1l?*7_1<=0m6uEMA7*Gh z3^q3(9Aq@wZi#j648WOld;OUg#JZ&=`JOi_&3jm>&nU1BH2@`fVlIjYX!@|sla<;f zxXoh91Vc=h65tiK1fFoIy(s;N2=x&ehgn253~MrQYe4#fZIlC5>5bRZ&vhAfm6RwC znm-KgLtJ>wIc=BpU9*}&y9xAqxyxQ)MIJ|GDiP5|_9^lb0-~%jAym^2fV-cXsx*K% zSj>c(+Eshh4;~rV@lsqvUAr%NB|FUWoY=7`b3{?Qg@2a;Sbcx=!3vr(4dE${E%J{2B0xZ#|^DTU1fad8|z7N!5Hl@ z*U02GQWblmmEb^P&58u2!Eq3nqM)1l`| z*Da6GM*CZC<62iraP-}Ihs~yUQUk|a$?cNY!>#x>N~x@7Y9kDHL((^qkKoGUq-v)*jy$(;2G z2Zy?RUcb5o)ZfBI&B)B4S2*1S>99&PLNq(-5P=cTe{Tc=(9%r9{W;Y@n;GafQQ`eU z4O=S@h{R%D7-S!Cy&Em%j;4^Or zv{=Bq6W4#wAm`VV+Q;wMPZ$T3$T2gN`v?eO{kYD^P}pGnxD z4{3^aEl)?5Ox+08XqRe3X{UwZYFJ0u|Niuw2=$`&eRj%2dlo~KSLwzE?be~7LN;5B z$c3~8+#-xaPV?_W6ZTUMFoTARzQf^Bw#xu+jhHqD6~e_UZREc+|7UB66UHVGOF7ey zK&rxc?f>cFmxAz^z{kkJ#kWupDHx=3rYyby@^w>?rI&=m#c&*itO*zOprKw0jZE%z z9eyLuL5yky)?5s0Z4Nbrd%A%ug-d0jD9nOUIXz|zBc+bTc_dL;xl#zH#n?yWwUYN_ z_$mkE5>>V|=cfCYPKU6@x|+6GoK^igw-Jr7+?nR*w&ZLjvP>tcuYeyVx=tk+CO^nvEiRxvl*qEka zzvX3rhFp7oS$wN7uI-NX(ZIJ2*PXL1XZ+4c(RI%Xn@pT;tF%< zy@p>m28%}GuC8FQa7W+%f2_TCR8!geE{-yaiVzj)B{~*VN=5}iN-_>AB5G6=1cZzQ z5fBh*0Yaikk(yBy6oiO~h?Iy@6hcC06cI5%1cbCfIypy3IFjPsem=MSKEL%{cip?z zUH+x$cAvBN+53H;=XsyE>ek<57Xu8di_^*ig?{0IJ2VejdJ_NPSX|Cp)>}2K|9zpA zwR-d~8^*rBPUannd%|u%r)_`uji!I*XqwN~FO#G9{4H0TZ}pb1e9hl^%q1*9z$zkb zg)iA1m?jdEOWDP(RVCHg!qK;fscSzLs%$^JGbO=hZ`-Pn(Drk`-radhXd0ou>S2gA zNqblmc_ST|vzjRZI7G12gEH`n1n#&#cfs>_EXahyc}`n&&>M~}9AK$GnBG0nePWyv zeIV0`u@)-R zpW!eQBUpom!zi>3;%_0aBpt(wpN9>58XnrC(&h^2=Lz5F%MGux#G zl|lukBcsE*Tb6^H!S2HmGg|Kut#z!oGAMe!6!o2|il=Y9v+ERFj@p=2WX(*@*6c-$ z3>e3_^54(1KMhI`8&Sy8!KoQ?0ock4)|gm4d-#WvPSYffbi3TOD&Y?KS|33?rjVzC~TST>5oj7#s60 zF+7JoQ57PdsCYhOJy5lPl#OLe z@lk|5N{t_4=)+T7l}%~OHzaJhNhk%N!MEYT;Rrxx<<>|RB0q80lUd5AC+PHvVge{p zc=JvBR`rUiZe$B#x~Qbv!u8{4Bv`RBH_~+OQ00Mj4sfpI!tEz_4uW5_qLxWPMH+wyt`KZ@Iy{Y>fNI4z0dES3K|NUd^dbLleK-(YsH(G6;FolKHL1b zLa}Y)X@%6&)2e@ViwN;D0ufRH?GM1}wq=mxW1ncm*<7F>iCacF3y=+K?xIo~P>Wf5 zvli6lsDMdLn@`WVWJk7Vap%QZX_Q~79arGnrlvti;$O-Ppifw3YtyPg^hvxuG&Gr& ziQi6@(2?D?zohKF7`gGhdD|)ea>3G@wWCDguWf%KF1D?`&F8l_XRY0PtZ3C=abL?1 zYv28{&h1+D&~W)|=y~vD{4uLzAt(L`IvIb5z({fn@SccXsHP6$$&9fdO8uw`M+4rs z*JF|2T8)@2PB?Q2_x{^5#e=CdY4i=?|B_z#`j?;dYHcj#sd(P`gJk!7-4Lt$6`7jz zDdN+_9n@Sq&fy`Ml5g@D&FQ3nkPp{wi9l!59S6H+f*2*D&C_qZ_Zq(LE}nWQrGzcv zShs~WO-#rG$|dvcOXAT7VX=yU(Z`~%A|}UhF-B-Nwzs46;k+_#R`}J7tzBH)?ImW> zb7bo;elM1QFfP+ZUdEap#M;JE{>eg0>4XbI8Syt#rVC;A4c_A@A7v!~mW|Hj9*bNAkk5TtO?(NwW61X>G z%(;&q*!uBU=HS~MUJlGdr}d|0xWB(CHVfXfwsv7F#RlH`HhFIGC3q2)U30ZFk@Lmu5zd4f>>=LNvv*Hb3|Ab?+Hoi1pK|B5e?ZpkXAWT!l$n#)}T`KW@@N>xmDxh@WbeirtS>A<@K<}A4s0yGI%MR zRFzsaW z+rJRE7VM6sc7<(U5qyaSa_iN8G?dI%idFPrjS-=5xolgYCE0k8SSSJ3K+Sn>LRQ=b zBuZY(AA`$9X$pgJEhF93HtgEmGj&x-ZA|^xyZSA+di08 zEZ`=L=pd;Sl@z8X=@jG${(orhM9)xb$mb!y{9k+8+AD$JpP2QrBDc(4TgkT(6jc~E z0o))NYp58)X>txDJ9$)1k_j7R%g+ZF-0NV?q}Elpze@x2Vk7|WrdiNOO| zd5_F1K@~+rwCla1vy*OFS+B0|r$NW@17{GN3mnh1fV&>nIkwvC^DW0(V(~omj7kyt zqZ+)aHD5QoeZ&X~Oq%X4A(>Un3&erJUXXn`xKzSG4#rRTHc4LbDV(J<;AE9pWluQ$ zQfW-Y;sIj4G9fXKZ|?;@y_6qHhhgs|m1LjZc}yI^h~ab&lJ27ktZp&afaNOZCMk6M zkCQ({WKq_W421+Ge>w~+^g$UlOPM%zpfBsgLX)>i>LI1CBo%-FS;_NdCkkW^{S4EL^$^iELh=yc(O)rjve0N`$yyn#%n{JE3|$GaZU1+%m#tE7o?aAvfEM zHhq$z_|)}Bd4?*E4ZSko&?f^2&2^Of8QY~q@;kCE9NgT!1Thxe&BwshcFJl;FehSg z3w9}zT`^gkuR&Og(3j!!YT007IR_8W8LHIn6)o z3FCvk`l@1a$CVamuj79Ftc6e(Qic{JfgDNzeE=`eo|f__7$%wv8H@u^^z1aTf6ny< zG|Wkoq2O11PAf*skF#K7lvzIMXI{b&UKL;ylxv_DmW(Y8zYrWAT0GDh@@()r$q=qp ztlaV^$qhcDc$-C71#TT%gr1qSiU$YE$n?51EfQTgb*|^}Ngcj~A z?IeX2g6^Ow(e2EnyZykof!z`Sr~?Lp$Gu4a>lBDjuqnP}=L?SrCDA}Znj9ud9eg9N zVK|o|__MF%wCb+iEz$U9Am}^!&3Tj~whwLjQ-@}R<7E@{aDLtEWTr@-5bOv|WFKt8 z&u-}+3~d|HtwfBC)65jQUs}M$X*E%sB8_X-A7?OA&{{0Ip70a5}*t z2&=95$TA>>^KgdvaOgZv8L4f#O@7DKQmpnTs9SJT%ITVrm^R}8b$76Sk$DR6%l$lp zQ-Fn(^U+IVZjjcaC3#X?ax1OowfrGaFQW!jC21hE@!=|w4CKia{#`MBJ@SgjYR9Lr z^z9m9|9Dx4<nZFR2qV2aQ_VWh+-AOzJ;OIyav|2DYt_)*xTO{3jo?bWH8g~VJfaecV;4U)P0Zm_P?;ScN5?tOPj%XF)=#v`C+cv0PtMVPV0 z#`wyKQ}eRY5ygkrA4(rBgJo+0V^Iwe=5-S}ipSdt1guXr*KOz-+GrfWrmYJ{_Wn?M zc8~VVeio;m*U)rYo}0{;=dctjY2O2i7DWwJU$uhcJ|XXcu;KMQ>4y?i)H7Q<`H|8b z`v{C5dUO#mI|Fk=xTy|8@hDzutMSuof(7lrDJ1}X^uGInl*wQ3iRZq0dWmD}ZosoQ zyJh&W7TRL6`{Khts3O&qkf?+wr1gTPXKl=j{=zQJP82x2iW~Jrb+wVySHa*U+-T zw!>!1-$uKcrWUYg;iG@ex2|)g2%FGDUq`$`GKgLfUQBXkSqp*-(XBTCsQ5*1Ka4x_Os$cD%b(ed#->N6t1altdma$bv8kmyR`vU|oXs=Jpu2I5hyUByJZuUysI)q>YiXjNeg8zzlg$3Ewrk(r z+xk}pnH6_0&-sLpsV*1!zKn<-C~TK`N>?h{e*2z}pN^H9JM^MVfIH+-BkK(pjMkj` zC+*sb4^HD&ls`wUkF5WG79u(s(;JcsY$qz_MaW41u?5_Y_pr|E&}-)_clOCFpC9a6 zJi6r@%QcTmlWmk*k-KTDz?$PekAcs6HS-slJ`yg#AYSM-^L#pj;wQzCHkPaEcMlj# z?g!MbeNLqO!4@6JYjIeTKQyyc*Y18=Wz9#oMwqU0qk#Y6*Qff+;tSlQE+%Quds=#m zPqd%D^u>U`;%(b&<85!;UqAiy@><67whJa9rX`%V^QA6|?$)<1rQX$twr~IF^z6g6 zR6i@o0QUIzjekd6)VvcyQ7iGXpNXLTB zS(N?s4_j9tdL)4PM3>|G5?74y)Nu@ax3jL zI87`)o#V8FeRz9>tJ4;rp@UyozcY-bYsf7ww`8Kv-9Nd;P|WlUl>7}vXy7lmTfOG+ z9h=<|^oDnEwQpzl$JqvXa#l_w&YNHAAhtRP5aFb?RRnJj9`=Cn8 zQLVh_4`$b@COUH4=q`66wXDm3l}7)JsD9FU z&)U$(-ss3}L)s6$Z4xyGRFzT8t{O;I<~a)>O)J7=)qNLF{u{-aU%upbbS|s5BaP(3&!h0#vTlGKpiA`b zKGmbx;pM-R=m@Iyq2%76s(Nn1$BCEz>yUucpPn{o?ewq98SDt%T=_?EkfEmgKi7KiILR)d3gYrz3oU-0G23B&eJ`)$4=vS+=$J z3T1cf#@!6zj&|s@!K=L=UOqp=u+lu;+F{%^XIdoiKj#m%Uj5YSY~FUR$o+VR$-0t* zWkotM(HYzKT48SD_?M6$N{W=)c7RgPd0@BGT7~WCtTWd_KCGmF0T6eFK3lPqtO?=^ z0-#RDg7F_Frn|@J;@TA4I9-Sb4yrV~tTS31bEOOoy<$*+ZIY+ZN?}Az$&)+Ewkpqy zhJSBEPj;vQw#BTONlkdUj7*z1`#xS3GyK~RB~R-@Marzj_mFH28Aq1R#N%K8@1|5J zKf?zO-`aeF?7%maB4S=PoBtlyKLY5Y$ESBIA z6cId>!UDXr&SdcO_qBAlns-e=|6()=gxn7nuO2}Rjeu6*4W zlX1jzgBxIx{@~`PIySIT;#c4;R|xUhx*@nO%HKo(%uCbcF73>%oIu>G>K{r*vZL_w z`$6;42@Bd9(n%z#VnSQn=2$_%^NE)uem3-Ek;PP#Z^?iM!LuVW*dic2cy+#~g@0d9 z{M%imWtRn4b+u8vkP`|1SKIz1QnogqFKJnO@TpN<(XEz0{al|+9xrvhn`d)7Gk$FO z^gkJAFH!O?Sr}>T6c`!5zj~)MsnkW~eh8M5(bQWK-+Ho*KkB(M<^3doYR8$J^`(DU zp0j>_x4`_A;9(FkK!8gosS09uw<}b858e~RZm`(-*8F?e%!MOaheqom(XBEeF{7Y> zd!N&l8vW(-u&GUcu6>^Tp+@he@5?<~bpuH>>qoGTfy3C@nhfKWw^BxnG8+5tWLLJG zI7t@g*}S+5iTNiJoLV`?f+IsoyRYntxqTj6`hf3}FxF+a$5wUwcGu2Oy&cX|Iy+{J zw-xK%kAAN6(Da$W&*gzM0NNznlL~YAVp{rqEOz101B}SRjN;hf_LracrcYU@8D|rF z$9E|l54JGiR(|*C%J;h+jc)!HL+t)4N^fcFO)X3nqIx3MkLfg`iqt&1zQCHfNA@SQ5LZRlCv#l$ZC~eiO5*V8 zi`pQ!GQ$3}_%;Zj8CG*w`r81XZ$p_bo8^aIjZhkkdA^AGjpW3H0v0;R-+LKz-;1sApsVp_mc5wz_vka1I>fV((Z-O z??18=DWUM!mYpRTB72{c44``zH+fzrCtgC5Joy6wH-%faJ6aAF7&S*w7TWX zo>`}a+JS_F(fw)s4|)8fZU>OJP^J@73P6B(RR|@(B;qca%xn)d$7&!fXL$1h*C;G6 zKsc5XPD=@dRe`}b9ffopM0|kj`&@J7ky_?9XynG@0AWieaz=T|@B)bRii=@d3xVq4 zI75IG_`%>NfLYH#V`pKFliKBMF(xY z0!`Wvob9`;&eTJ-ZJ5sjT%rdjUJR~bB%-5raqG_`T%9;1K(!>390fpSwSZGEs07GO zikP+v!MlFM1Xj^C-IZbkrN5Cs&@>b%;+W(wq!>;Ts>Cx*kp&4A?!H0td2W1Nv5r96 z00qaD%T&O3$5jpMR!1lY`mx(c9-L}hcW~URdQNFaKYb&p&;1_mDTVc#ier=T0Mf95 z9Az3bNnQ`mCwL{fooptrz%HMnELZ4XCMyvV7sujR7wCpW2f^2LAt8>t#%?EQl|oM? zISBG%Bp;ERJiNwdBvfD=$H0^jInA)lw8xAQS>Q40jjrmo+YoNqTXx+>?x0vHh6_EA zTL$+Sj%X5H;x{bg62|D7UIxwvH$o5VxkTJ&wjB~#W2kaWm_5588`W!6$qWMS$y)~?Wi><5v~ zX4ZQ0)dOxHnBp$u)cn1z*kZ%8pNYBru@ur40V<)6bK^-* z)yyWZn*w#(p4W9BRSL-Oa4$%NS5~b7q7*CIdVDKxJwoFH)&*nm#fcX{S=CW4b3&@6 zCuITha;ynav_A!U2w`81j2?epw%YFfEj|K;t*KAPi0u1Ca|6BRE!47Qp%iyGhZ3T@3}L zWhr*09I_ZToL1NqnNzMdSF*A>nVu8RvagzMd8RyOrm9$F3yktL5Fx0+kD0`7hkd76 zFk12!^!?b*@&&=ln&Anq(L8FPQ*s^&l)4}>ZAQ8f?G`DdgqYMgva64*3jq0sc&6${ zOOdvp2er)vw`RP%;=3L? z#j47{dJ72gbxG3?r2A!;tC>hBY|9I#GPe+YPxrIi0fS-q$a~x-uor7XZG8cX4$O)# z>s8ZJNVXKZ5;=EJ(KBN3>4(xKFJDOsQp%@lkTfAwqC%HQ7h;m+yCPuGi1yqIaXfmI?6pcQVo`KhdeP%x>6V&Tfw4aROTGt74ZwZnVi`lf zL8;142KlvGOcU7-p+hII$}*Q~(8FL_p$%&lJ}06j?L7=@+Aqvi<2C)>o4rsckODq| zT1&>tvz{>f#KU?Wo_!>3K1&bD0SSa;#Qb%DlKKcxA9=!#Ercp=2O)u`iMvEgWoq@! z)0Qfl^R*C^)W$Is@^k|sXDcBcxR9lSl9yaMxEIjK?Z>hsbx3Fk1iJ6gUVTWdn2Kg4 zGYAEaylUhEymuozSkxA811&?v(;xtc+*+izNEGQ#Qb;dzE5I6SaB1YFvbBgw z!4OHm2FAoOY@vF|PI)TX3i%d%i@YaS5Ke1(K-!DBMYaNjSQ1}kNYgnBplU>I-d_92 zi`=$_5go|&$r4Rq66h;AP=B+nuI*{MU^uu%i-N-LCOIK~&xls-EG1c}fRL((X&f@i zBeZGNaMkS0;MC(tDpa@7YNT7)%LwMxQ`5-?NYvwDqVb_VF-?1NWF_N8_Mu1WLN~x{ z)yZr=0xn2HfGvV3gjz~6LAQdeO56%FtreYSkY{W36SUf9q$E%fYa*fH)6*d%3M#;3 z|8B&gWs$_(d9!_y)hZ33sSF|Zfd#ST>$qb~E#w;}1*gVPfqzA*5ot}8L{YPcN~%V) zw`CRk3d4(E#Hs2Gbe5u8vj)veE5=L9{3w^DhSKqz+1kflc6jXVGXI>gngx!M;pqRb zI}J${?vyyN5V_QuNNHE>U{Udi!y}m`^dYJ2^ME(Qdjm|aEkfuWHjI3uo6{f{%`DJA z8qp-qJKMdy&@N~&h^psou~?BSbyPuU&T+3*4e$C?`FJM7ZzMR~1iI^;ELD?cx6YSo zyV~C#o6<;=yguBinmBq-=dj7zu;UMcYg5tQZp{QK{qU!%F4ZIKc`vQz?!%uf zN^N6-otTA`SZ;d7H1O9mW`SmA1*Z~6Mn={jdYoc$YX9-)N=sRWt%4Ap_XnKmQIuIKzPW zb|!}4I~G*jUiaP>=ZF8yZy?(u)5`5vDHkb&q&TAW^fi3v!!_I}Ad?!UsKKiwH39b~ zSTaUQk*vWoa`62ak9ZBXrHGfLP6U6Ca_E*VdeN&FlNGTI-9b)2?Xd3zATR$%ITvaW z+_zZacj{LaCMfd4Pr-`n@480=C{v5y7YE0q|7Gw9@=)yOv8lf+?%Ug==`;d@GrNUb zg*!*kaEy`ek*&CH&Gm&+`DHp^l2n6v4eNk@HlGx9#Mc-dKKxlH`|Hd1pDgFjPSPxg zR|6aL(o}_!O|?B)yN(-lC&&XG*8^OPfjUZ@=#!B#H7)ROsfuy;a-SZ&Q^h(a14b18 z@aBg;P+srajTofSRsK?HqW67!c=Wru*fS^@mqsDYBS!yt}efdKPu*OF6I~;;%TMHfkZRgtw4sPZ`&2nc@UQFw* zkcwHnGRAbWtiikcadm&zH+)p!`F$J!ugHlN&l+50QiUo7XBoI`Q4CJdq{;s0z#(IJ z)OcQK-M#n2p^edFkM^RQ4?2O0IWi?XQ)WgF>{02$)lg**I5QW(UD^Y4Yvm`o>%~*u z4l=uIyVKl!RHTCxVFu0eY4*2ZPrve32{~;sk$J0eoAO+XWCqy@2M8*PA#k!2cyDEY z=Ic}02!SNA+kpo4Ij3sd?1k4xM!kLMnO_)ctD@fO(vCZ_S@pzvX@EgPIf#e%1W1++P*h#VK?KDQysibu9jMaWc=rMPag4&!i|V~ zNnqVqXG_n3>?OgtG#1CVMGkZ*E*=S&=z=ycLpZ?>BoM$(J}B8P7~}Or=_^QhQFFMZ z;3~Bju;gorpk@^UgzA1Q>lvkG3%MUXzhCSEKIN6%W>Y!tpMr<6l;00t27kSjUn|$V zi-S5_>nZ$Pg#k{0BGSP>^fmz_GByDS3nhL5hEVhyQut1jTs(42u@>Z)4>Sy86CI!a z=MW8%;0IPR-24Ax6#66@dP}HIlCQNt1xh)ZBcW6r{`)pxG~wggod0+rb1njSm?RW( za|BGHM;j;(T0|=VjN~A{0>+?JClQd7(Y0)a1}pw7(`6g?JP6;1k4Lta2 z*tb}r#oSeQx-#k#8l_@@H7es&moKw2I`yS!J@Na`9CE|e_S3gM%*|@G{kf}b%Mn9U zSF^8QtddW8+v+EFLbIo?y7wDvT6b!u7Ou28wZpn3^zF8TC#fOQo#YS9E%L|Wr@g`X z(%+Gzh2Hn=90WwwAucg-cDQ5E&V;XxC}7_q(l&A$i$WH7WAOqv%kFnttB`$sFSBgthfx%aKh> znu$9`x_7?kM~A=C>m8N8FWp9y$)q~dq^N=$@eTJderbMSIU*TW{05b#MfH1pit5B3 z5ACFR+ihWTV<&dO8Pn^=NAQrTbrwox6WN z;KI(=@7VO@R!(l7N8csU_QUwuw38>|PsVT3IdBj+@*v3b`r8*eN9ZQa#~Vixz25*O zc>nT_u*)ans?M#bC?9con49PH?(_~&RDZW->aSVsY7pJs2(X&sp|6i9)zrb*%wp1c zc_qt-nnd2tnKyKH`)E;$RzanDpwjJ5JR)|q=BUkNloyVpHL55oUu3_&s;#~E2zEcD>XI!SEJloXdy{LPJq1a|<%n%!RxHLk}aFyjTj5@CAnR z*2^=2E`l3JTS{74h~S!{=`-75G+#n}dd4p?$_@=2oAPWuK@+nnvrT_|tbPDc9oY$( z)Al>6$4gnUYOmBOfKc_6{@7i%EZ1F51IhXzO&B~~mSdjB^Yk(nZ0Rq70$PCZ4Xw}! zUM&N-#f>P)}sk8G6AZVJXV=4*pmyuk(B{CzDafjUYD^4yjky=$_vHc-Zsb=+?DR3Qod+L z4Zg`QZdD^R=BP}^JRx3@?PA+`LZU|3Z1QgqP++XX2l_zS?LU;(D==Op9Nb9P6kfC2m>*TjD$ty^K z0{ozM3)uobm95x40tWQZ^6 zPF#FBxx4;4d7tb!%$Z4+I?CK&LaUduKZ#EW>yF6^wN>)a^ zq>cj!S};<=+f+ufk-s8qjn}!BVoRRaB*WBJvLWP{C<}yb4<%AoP&E)-v%13Y5$dTK zP#rv{Rf(=M(Xvx8rX90Xw!H!F6E&x+3W6K)N!S%gl+ZfNEb<_X1A;_4&y3ZIS|KAs z3+=2l+%lX$ATrnc>>Woa_>;%~fsXg%<=P--?p%kJwOPIfDGgXuZp~x8*X}Ii! z{28##5G8q=^vl*lna4oay$~TQ3B7(^N{#aX_~=(~it!S9-a>~Qh|h;>Ry?k+FIniHk`>}jr$mCRad3hKge zw`y)S=MY+kt=f%#g~UnmVmYNP-xlJ=m7AJXgm)XsEI!&f3QUs2-%O!rV^M$ zs*o~`PU=_efy&2j@%yKhaNENOLsNoF^+LlnM5%z##7#i2A#2Ju$r%Eq2v!BVMH%NO zT0tc6Bg&x|5NATi=)htJ)f)U+dR$&;u_C-%7s=!o&!`l46{Kr}73?fcu4XO&iDPU# zPy~QDZcFewr7$C7c@0>R$K>UoaK3~jAWJ<532=id!j3o`xG1S8Od{)&$Z%?q5~o;0 z+5j6jqfrQ|5#Xr1!=>jg4`$tjJzHzg+EtWwvNQejBC5CPM=a{g;9H&)y-IcfT(0qz zzhiI66Ue)fOhHnIC{?e$9CVj!(IV)vE>b@SOXv*f06pDJ;0V6xgnS%Q|Cr?2Lki{F zqBPI=*Ci!@v|3DL=<-X4_R4e$TbRE={mJKqI=`&*U(I=heqNqKMhn{8^5~qayNdkg8 zUb=s3kk%HcEg%j0wxjfAj`CueIY`7Yg0U3lCXb1=Qxb4WmH(OYZlQ58>E;-epTJA} zWMQWIJ0!1-n^)6su2C|e#^ZQd=wIgPw;TkVR{Sl3hGGxNsUB&7=7gwZ#sMK0rRdH# zVlzU@NM?vLkj;(V3?t|g(R}SjdLmh=KWyC2Ub>(3M}K&Eq+;!T>;__Ls~Mxp@{<I_>teEbr^U| zO148kS%W(w+YDPZ8~P9N{(@*NrKmN2i`lK1M2Z$*K8||WVpe*et!)jy!O~D@5-A<% z<)jln;Xc4eu%BK+%o%cj0pzL`XZq$q`T>&UjlD${Q$@L+Ia4z~rp3c*QYAy%{fZ~l6?8GRVvOty!{l7Squw25g6 z%{D|C74xh5#KCJP7kMh-kfUPdFf_$uwUsDr&XUd{Vi0jZErJPx;`ZP*#9~2yEz~~> znnUAL!E~6)vR`1E;fefZh&#{TyZC3o2p;Gj_yT(V`M zg{z5Z@bQVv%}AZ7$!Omvh0z&VG|XxzpokY*7PLK9d=!Pf9UQs7vxRU#3ifxLJC~sM+DzNQ#Pes>ZA-%+ZI(MtB?De zJqMWe(*hwZh=~b2kJf9>`sluYiKKXX6w+wqz7#cz02&5n@7BKs!ZmlRc_O|ob5jd? zvF)$IgV!%;M{4IJNw5zJ#v@*h5WI%HD3_4PKR%MIo;-P5goOUlV_|EnX5OnjIR0N* zMf@LlCfvN8M!rA2M65fel$pQls`{Y9`e(YRsO7$WEw>gC#?pj#b7`RA*VMy@dgDo6 zWUmI>FJ0r3=^65nC;nZxv%B|LJFZ&PLQ4WBS`0_&Mxrt3XHL?G(M^NYnVs~r1n^e= zyIN@|-B#i#e;$0rnDJ4z@4_=bH!i|T^U5<5$?_|B}KLXRW6wS2C zBSyZNQX@s@UvS4gDNbg5OL)7P9{+o{LEMv|$!og_pC264Scdf`8Le{y?a++kMNezL z#&H{Fvjb>%zY^2V?tQcK3`j8b`p$#M!;x@c;-Xyi|5YsEfntgKUkaA&HkQZ6El8Z? zPdz68_RmLuoR{)_uYPP9^NbSclJ38+d~?bN#OeB1iK~nZ+)9%g`P)uKRk-P%E=npq zTzIp@YTMrCwRbZ^j-6o2%7>r5wAR;7D{uW&Owdd&Kk9=#*byG0>Aw#fy=vF-hfi`- z>Qbx10)gF(pecuonSUlqieLaGVnZZ-l>r!dt=mDOu_hbN?+_ ziv1~B8g_Q=v!wh>vgCZKmkuaYeC)GHZ4Og2YZ7>F&N@{D%~m|g3P}FvPLz?8VNtMu zR$!{)V|&wi*nCa9pg>0&&8SQZ(MU<5THZZ>?@o`q`+?Hk-y+j1D(~Md?HkQ7S^LX9 z_a}D)j&3V@{_^FC6Ei-67s)q>EtdSoL^M#fI#oj2^nhfX|D1 zc97YVSFc}2nfb&wiuAv~v9M=poh%vCa6477>uO^Qo&nv%NrPyVT(H6!BI!0pkWSoUunO%i~Gl?7RcIl>cTQ7b)bpF^*0(IY1 z@KkZmOixt(fEI>7H6*j{KD%zozw}EgY#Au1V-Xm@>vQ8zm#t{=;tlbJv4~^;+ZKeN zijCIKy$67#28SSKWNcuKV$LJ1uD|m%@oyV4=|+A-=oGc&t?iq3lczRfCJ3%>EO>gd zTpbjQnIc7?y&2j8SQ0xbj!a1^LEhnqh}aJ)ZRPOs>79#9mELY%S30y$A;}zC_-jt; z1TuY2U~&xZV5|e-(4YU=_8^MZj!FQ{m^sjbJK%ci^GJ0#ij&Kl9&0H8{)DHCrx@bC^zc=xz z-g&aAH{D==LH4Elrk^a%xgIZ>F7$r7%3?=v{OL12B`13FcD&QvG(Ze($w9?i7#g=2 z>3q5phCz+H{^nPA@b=TjtbSK5*NK<@doMDnug%WrxUk#qlJRfv zcUo57|Kj7vX%z%?^_F^+J4PM57F{!vY;thtVel*M!u05yAZ6pmbGsPyl@7|;yvq)1 z@lE`a=V8CiZ!i60Js;ihb_68JU%!lQclLjy{_zn?1E{JXXJ%OQQYDh68|97kAD?X334kRainJgX2IYO1Zt?x*O>oPC7&bgk?x zj*aPyFGD16I3u?hc_oO~*wiNcp|tL^djQka%kxxq+=P;DpwynE{0ga#guk(Q)Mi^k zMovQUXAE<h((>H9b#|ooep}!o-tBFY4qUE;|GkJ_b9YpK`PS|kSD*08c*JcI0~LV z@P*j?$m_C|>zR9@)mUR{Q;zr_rXJ8(L~dC~GKQ(QDyoM5Ha81)kzP2%9<9qgz>zsV z4cK6G1NN1K%4t~pE`BaGgP@E>$$VZzUR_;dH^-0?h||Pc;{#RqO$|83;F6xe-sz>- z^ezA71m$fR^83o2Iq4_ag>RUi8Qh?rv%q%*SHbB9jwL3Msnl!P;M|H`14k5=c_X$h zwH^QoPEnvYpFmVw7wX~{!?Gj2H$wZbI-uh%OTNZBw>YhKF7$FQ^IxyrCEG3i6&S&< zq6y5pZvqsTaviHp6DS^(UZ!b|0(C+YfVKu5NQZkYrMdMpks!1BY-g_oCRfe{Qdvc1 zQ2;HFEw+_A9%nYx=1(rp`u%QpneY;{Yv)tc5UVDpzC4$yd>OkA35Qp82*yAjv>K#d zE6C`HL6sW{lZo3->Ul7(+2#N}N$>(lWIR{+WSQXf$02tlXK?T4L*bTgR~z-P2Ec`J zB{7(H7x(HeLCsFLjHIErr|1!ZWC5cSWVkY9)0t1z4TtC4`>W(`&DtL4psK}7#ijn* zwd~7R$7>dsm7a~wKJuw73^BXHQxX)*Z{aSe(v>|)I~(M;!E*FF1d`pVSOwQ6Ym)ZH@MF?90Fcx{h)d+E%Lo-&Rc2+x!bYqry!>%cc(Cf_$Or)0 zeD;-W!z#Z-KmoyOiqxFmVrJV`>y2eA3_Bnd{t@rxm2j8jUT^_{*v@VaK~99}2e0Rj zyJigoGPf!e^WY{!)+VK8eFD6!TUX$ z$Txu8;uPyI54PXq(Nz!^AdbniIZ#=d+d8tfw}LK?$-|$h%XQ88u(!NwcsIW4mh$jr zrAMg$i^l2y2bRq3#Y5cMQJF&p>gkd#x6l~4%)8qw@Rex6Y9Y3R4e)OC5rs9N4(tpv zQhrhg_O6TbV(*&qx@u05a2;2cSIhby)SJnyM_ZC2yto>y3kN_w_-&X_DU{gBOFyXZ z&TMSd-^W{>mLPX*esB$Tm;n_fQzJEcl9{PsXCrU_H!rTu-8JKQMf4G3g2cmOnv>Av z%J)R2IG_4g5wL&mZXLmLx8vy9BO9WLDc{3 zC7aDpn#KrD)ys3qONX?VOp^iUa8v)y&JJF~dX2gD&56Ptd-7QgTM~%wLB4te6`{^F z58ZxWyjy8nNZ-A|s)%T>%Ut)G@04XlDP!+<>&&^c--lGz)SDhXJ!oV6^b6g`mmJKd zs!UQQnZdJ5uc=3TL!E9?&UaLy-Cg4;K2oef_KNYJnG`SN)MO7xUe;6l|MZvHh%Soc z(WKkowBC^FB4z7ZB*%Ka`Ym5U3o?yeqj{fqJWCbgrrSp!P~U;_t=4CaV$B&S-HJfn zHMo7R&6~b6o2!F!!cV%gf&f0>M=R<{y#u;4{4-uo@3$}G-m73oaxTwvp&HYWA4(f- zkHrQF#3JeYhkr)c!+-rwpQ28$V$GvYf<7aWGQgJHk@tViri($2u}NH2&Ms$_SNA*K zC!I!;{mR`*zp;O1)!v&0`K0LcjH|_)ixOVf^*MA%q&a?88*#^$bfuGahgP_lEaJxX}mQ03o`yU@X& zQ0Ul4&rjOK*;sw6^6s7=Ui7Pd3r2n&Ae!#UOZ54QN3vPhA#28A?E|)bz)q} z8W5|2!BZkioQJ>YA||(guvDRMHTU&jtz-{1;Lt~e+?rVHy@r)JpaX(DToyPNvu(UFV-MpFHVZ-*2l$jRFI+~o6q`1dX=!;%$oR;f~ z)nfNv|G#c>^Z(SLx!GoZ3OKJyl;+y;SN;jDMc$zfj4mjG|Cyf&L=|rq&rCX6|2-kI z#~F-;d{_YvonNJ78m33qW>&+jm9j7#pEoddCI%VhgQX!2NJMpSdo|n-| ziO!woJN%t<*1d!Mok~kEfeHQ-rS>QNC*KYf6;bv7SJE8Q5b5zW_h>O}++$+?-&%X` zs3y~OZy3jM7{|g`5Rj4?QBe>O1*K&UyEnosCn2DhCttL8?34SyX_7uT3CZ>N^v-0HXXP2Cf3wmZLJZ@_u|*C z0q(|1*FffU^s{BR;CmfyMLFvCGkL>!Z(}6g4+K>`n_9VHyV*5Utpp}rWC_51)0@yA zl)CZvl7l6wW#3c~0D=J`AAP*y*5cnY^W@6R+`G?|O#ORi*1ox(eCq1C&lS~jUWUYW zjoJo*`VEQBuAi#ABO4lPZ!CqJjfmO(S!bG_UhMxMGOfBXt)@Bxhj{GJ29|NK@uwBX$6-pkyur+v%@<$pK}UR`k({9EmF zxl3k~{0mBDIhDpym1|DDx-_(kEH>)w$C}#t(t>$C9*uy=QABR6Wu8%9MQ&5uWaW>E ziiIZ-iIqGg2=G?09TWxj5Q!?F@xMaM5=Yf^jM-iqtktJJX?#Gz5!o}r$dN-G#} z9^N|ckvsL%A32p?_P#BhPPJc(ysJs`ge|kv@}Ivh?~uW-Fk-^$S@2JwSlr#JxJUWg z1HBTCT|&Tq6aevOs%>JhjH+u?Wz`ctjDbIr3qBP9w^^SPGvdJyRl&ncOmV#eTox?& z&iSzTB+)@dmfWc7tI~i^nJtwaibu4M=nm|&0M-+!MRe{ln+^&FKJ@a0@fCwD1#*yX#4VA zA3h0r)P-?YCe#dba3v9Ig6<0ILZWeD_gVfT1_jO@nV!US!8=B!f`{osszj(fzO<4< zJ*SAJOobJZ?u6NXQ?UstwcY0iQYbFFWbe|D9oEEmHJ;G;GFZ!AR1o<_+AG7()Bv z+^7`A!JdTCJ2UPo3?qvO!+MlP2_sCUpksys=o;C?1ee9~K#nPBy*Q9cf%@BscmJt9UP^ zfI^iuB1qq|EY?AuT=QCf>fFypG6*10!KO&e5#W;3Ykz++APVJENbmDgLKJoNk2!4bkb>RNW&siWUi%Im#ik^nly ze86a&XW)kiGQ)pJHQhpyX^ctzm4T;;C=7}SJ%dyr8@R=zqYH^KLX}_(xr;^xIu0vb zSYY-kUco+~v)oW(H|uDj{)3s6!4VSLlp7#b0hww_7zac|>EY3SXqPHX+z$A0C9u0( z^mc~bg}tKO-@GWgE?K97#&m%$qD?ziCw&db@z{T0{FX+=?=O$6?lJ6aPA4ABNTyre z${Sukb>O-3ocw5jcj2|UtMdgmZjPg8UpvRkJ8hZt4`#2ErmuT>m8G3P@QKkmAvH_m zEz&0n292t_R%mxHi^JolS3gjA?rk4F23mYOFNK&}1N^}}x%(|p+N9Y!*20osSeSGR zsv{Npnl}3giN1=y`F#`@5Y#EDI#ErI(sZ@j573iW$Ynl!~lu4+7x8qM%Yda*oU4dYM3p|Ig5dK@t97aJojf@K-TUV)$Oy6ZQ+$l*y z)ct__O2D}RZx-@`Lq)@a=87+?1wLXP{7ap_p$YW$c8KMGF!;GFU~FB*z+P2A4mV2! z%!I(-7;R4`GM4CkHEvRJRdIb?op;hZn_ZA1^olPvfk|bFKnBmJV5Jk{0h!z$bd9S2 z%f%$XTkd=Yd{04xISro>CpT(!1%DNR)8aW}D6J{p^WW_p{=dHn{il4Y|2Dt=XF|Nl z%~M1JK2>v^jw_!rlGK|F4zOI4q+K-> z(U&RAfRQsC+5^ZI`9;nI<=A(-$YqTlGlzN~_c3R-zG-k=X6x)fcO81Sp(2<3nzoN_ zm?CQU9KK_%bzgq&Yt09>&rD zBc(r?&sbdLX&7kP98Pf9r>GAsRvHi(R;w+i%B>7+FH!D~Ed_)zrL6f7xqRu3?6?0K z+?vUZr7N{p$cHozZ&?OH*wO}~DO@q$d|}c5{#@N$RnfEpojA>9k*1le^LKcGTl5Awj_# zyIfrS{o|kBw<|u_;_>0NvdiTC-Zw#l@F-mFl|M?H8iiI<9yeN}`pXZ;dBo^cxcTIM zVKq~Ge+LdgwA+&1zc3bN(`KQ=L zza*k}WVkKGDJ9R!Z|Rz`7xT%9XyqK#-`8eW>G1L!lAeEwfpTq*?ALSCjZ+Wg(2dVV z*%AY_l)4Q*t;+oZ_dB*8$!24;`>+~piu}w)__+H~w!Y4Y^Ec|sTk(c(@(*mr?I}Nk1FAvo$alMQ{3BT|0SD*mL}n9> z;pTlcOgSl9t{i7VImhHXfzMuzC|7F~-5IR>1U+9Yk1=2s2lr7fd{a3aNs(^jXei!h ziiNPOvckV;L;cDSm>ZGDC`nin*3nEQuSeC^$}RhB*NhRqldwg%Y6R<{wWhQ9r}}VM z9c=FKxUnA3L4ck+$j@m}?&-6DtpldBlO#?C!ga9Ow2JFzC(NvQ#%8&|ktUb6{1|9@ z*qYN!S(`hldGErFr76b^-VwPWC!9?N-Av}Yk@>Uq>8Bf=ABLXqlRPb9@xqXkLX=;X z3|kt+SOjDB6d#JUSl)pITbUFwPRttofN)>QXSEQk=_vJ5UddZ#67;jq6wahD7&t7f zkWtqt`oolw<;hqQ(ZHIb`o^*7BSXvRKrH`o+{DQ>|0RfaKve-?zYSrqr4J)TOEt^O zvr^-#M&qCerII~W7A1l7tk7?l6gK_>QrjZkwaymEE{; z3PM^+Hsod=K=K^}j~E3s_{e6ULQzSrQp&9-;ZcpB@8coQ7?ctW#jerUZ$X+#ZkYU+ za|Tx2p#Q0w|KHVb%PessFsYs-C?Ed1;+zqW7N)+CS3^%-SpC=cK7#Xl7;M%%;slHC zIsHz908t>?D(qX8Nui}-vkAVfkA>dS8J6}5~a%x!RVQPZzf^*0etTH zH_A|rEZuF`PK5J%`0GPX(`W|&; zbWTg9`CuvDp@=Z{gTM1$qK`FUYj#t(XH~Xe04Z^o#1B^5 zn|0uc-uwgJpdL6{gM-%JR3v_l-smqzSHQvm`^k~6^n1w4wokR}LId^MX3w5pj8cVx~O1e+-VDmP){ z6;S4kSo z#;|wEyZHt8jk|$YrvI4Tw)u~tT~`u;&!My8;naL3lo6ts{^8q7p&-UT5_8qly^FCJ8g!4g2%>uZS;qw?i#Lw+Axl?`8Vk z7|;BE+{`GEBwB)f41KhpYz*xXa|tEN-AOe?}kGgV>lmeV*t)KSAXvU zMUt9eLAeUeXsEom@L=d_aLIT??L`m3UWZ#cV|L_e?Tvb281V&vJ6rVN?Cg|7OmWWX zH=4;fVk@NH$lPh{YNs!WYElcCy1NZK6qFsVz zNAhi5Y!!A;*%Upjmm{wgE;77`1y37~kC2tv-=)F!Z>S%CChZ?#Ghp0OrFXU(j8E&2 zo+9WbE@>_|UR0nlibK6IgVOy_Ox7o{=$Hgjp`@W##oQ;5ZT()}MO3r=z@Mi_$x5x!@<~fxaKV+I zb6WlrEldr7W$^A^dx8N%-Q0iJm>7>#iGb^b5<@|Yx2qZaMOYfx7@k$nIVpA zQYI0svyr(Ivu8`K4ktKA=00bIy?bM+ybV%8j3rzC?ik%NT2$vL;V@SCCLpJZk{xwe zrIJi)x)YSaesU$Utt(%0eg}!Xez%?;c8z(#bWE_(C#kJYxl4iAd>ga#j=DIKE~|~L z%TqE!_^U_7ZL|ys-yJcO!f|XTZRECNz8?T9NZL?-*fD4OUj_DAXbakHKqV1(1nM-y zHh;D5DHuyjQkb^eLwbeh2y-9|{py=)&luZj<5u?t)BT-S5+<%wO*(v9f^Iy{++C%u zaredarTxMnzY4EC{dY>;o|`NiZ1i<>I?*wNjd6A}!|icCoqKkd)={?uX-?+jdur)! zgAs}0gs8(x-2#~wy4O@h!em*&62f~s zXaL~qehfcIk&WyC4kbxsVtr%f?hNE=<=S^L2yk0KX$b=J_^fu|u8Gy`(f5-Nmv=qovbV$-Z}C7H1oSN_+}4G8x{+#_CTqHscr7<3+b*TUDi_&4rgs9#pZOY7q< z>$%1_4lGk{{SkWb}8{6K0?_Pv&G=KC)C+E*|~2jL$&f)pwt4epS-dONFbPd z>?lZg+Fa?BBDU^+L7S=L1lkTc#jZ#)@|)@(5IOGomsd25e@}P$j~jpnQ@*lqYk-_K zs|_x`NV)%~2vq`;Z&R3(#r@J$Hd%p&NKAUBM;JSG0p4v|PGxWz^t|bmfe?ytgq;?U z7`W$;NGMfn8u-2GARSlRrr()P8Aij(*nCF#TN}A_v3Tlx)ALKJSseYH#8Y})npEDb z^cGi#((rW)y;aj>)&jS=s(|!YJcwT+>qv@5DO=)IJLn0+pJy4#}ey)_P*(N&WC;6SY@>y z>3QRpq+Q$SjbUpmD{Y2`cGk)27L|+cNr4NCroW@C1$wW`fczQPp7vrMWtyx&hr=deHz1XV`}%)ur{9X^ zessMkSld&Kh;ws(U$5s88-DikvS)Vlv~!6g;@T=b?Z$0{03@z8e&5M(QxeGW;5y`udC zO1y}UHbL)JE55{I~qL0v&Q_-GsL zR7^`xFX-rg{1S-@!Ztd|GNk#Q_bJAu@Z@IVWGE9ZA)YoKqlUeJK?MPk;Zoq$PB(&0S{0`tkE&nt>LQ=BxxG3-$A^j0vjxXU+v zQ|UXi)Nx!F=z4)dH4t2(D_O|Jl|yTVOCiy{%iOR=>U*vSf)0eR&__~` zAV`^FGyLIrIFVissv%Q$)__0}IlNy3 z?4+*Oi-@@=AQb|X3Xrw?cclmz!x9$d+@>GDsi2R&uQOE`Bfz9fL^>6;84KO3u%(0b zj3S>f?Qoc<53~4RZWllPTZ_g2Q8Fc&CS0IIokCE$ydMiaSd&fMGU*Ii7A<}gZsiW6 zF=p)IX_diNsyQp6gIqpE*n9nmp-^8Jtq6RiiaC%J_x*#fX>}Mpl0igDmlG<7Rlpm^ zoj#bp$kMgPGpsBv23FG1kqYwsbC)D(0+iuHrx&<<1~bg>a}U!as6v&?*=zl#QwE1Y z5Y$6$G^SD5G(?aWD)#jSe*n%FuKq33f>a7%F|7$<6c*f5YJ3_AVgH>%Ax+?Fb$&GcT_pQG9*2bZpPLQ~?_20)SIl)?(OlI3+P%)dFWKek zwEeFiEwo)>M;WzUy1^#~YNxZusAv0P{Ldkx=N#To9_T&q7szzq&|UkrPqV^8hrM@H?}e$kw~giy@?-*INWQ%GQY*6fcu4E`bMnl$h3q3cMl${&;+W|3c^2w-(ZE zKKpF($tGK3_?%aK_K9%k)5Ij-=_^&~D@~gl`^}&AgXY($^An}Z22bm8W>34KN&yq@ z0OhhV)RkfD#gW87RKXW4leN(m@vu(4y?pEgwY#>yf`g(cKKg;V47Oc@@a>Zaly%`H zLFWdd{T1%+4C9!PS>d|ok<^kAGJ4Mnz?XbuU>X2?LzqKbaF*q*eq(;;^J{8+{ERG~ z`RUK0Cq5*ElbEsf(ghJ^N{j(?g`ZS8riu-)+V~|Xp9!glAYc+QrOY8I$U#_%xu;G`j zdoL{>Bx`rQdh8H%zx4MudWnv)Z|j*S-*b$7oot4k4i)xsW8g`xp04&G$!A<`N-OKd z+@}LbMlu^LH9UoK=;LYbyU$1s!6zF6SGveeTK2P%ll{Od>v`Q=Y2V1g!$dU3>}JYa zz!Geee-i&Yb2VM96{Cj!~HkyQhO%4MMjs+ zaj%}Fq!Plh1>a}hxM8EWiWOj%h(A0>SN_Bh_Kli?$6`Q`d|59Y&7l~8_>KT_$Q2Ju}VZnNc zP>J9VBp8JvVeVe_guo^Qv-^EtEVtXh6h=r6q8DG*i@GU(m8hcDO;;_Y&}b84Q~$?V zkf|~UH1Emu9QBO2piGVDCuR_YWhCB>UH=!)Z?;|mwJ(ChS@4j(`7C=D3kAf)Ub}`> zIdr?bNUIq;HGX>3Uz&k0NIsZM8aaz1tiwmzhXt|%q%3I;pcRRTtS7y#d+Htj6{7IBw@MTXFqz2}~@@GjYz#^?i*YnS3i zLXr$=un~uYIZ^F<9?#D8av_8EvBL==y;xrWY+awKvYc7j;$AN7{7)l{pP*{!i-b+P z*s~1&MvKTm+UUwQ*2Dh#O~h!%PY0k`Paq}{3kwflv}b6=25(=+Ak=pQ)bGIaCG-MM z%kPk+cEBdIs_u+)A9NM&!4!4U5b(5vGO}>HcU183lr{}fcAOiu45c<;_9bKJZJ^>e z9pT%lTQ@$Bt0pY_3vB@(&o!2q#|S`9aES_1Y^m_kcGe*=8%A@rpKAIn5qmYsAo9&q zBA4bKcFk8b|5W)RrpVr0`61(T>{}(Tyk~zCKi7_U&nm_ypr2;8pD4lI3q(Rr5RTVF zPd+DXHw0Q?Z(+oeT*=kT0yx|{3NwqqC|-#B!%F2#81-1w_Y5Ih1Qn|{4GqBnI-e)X zdLl&3b6MR9iT2-AVpDVE7X$?&OdfxbI=4pID|1s)U5GLk6b`-BcUdmKfIF(GLY7rd z#Iub>ItG}*FT~wHxD$L^4=vOUi(xU(@)B)pCyc`Oz$vV|jsA`Gn0i+%o-)+yqdgQNM6p5k$s85d#kU-=sNlC(aIsWx(N&yFpyJ6Y#m*(5Q1CZ;P zXg{#!4Y`U>QbCcVG13%n6I*qDT+(<+ga$t=25i!zOR{$@G2ZdGF=6;{5i)$Ge4tmV zhrSYz8z(9&cee|0BNL|6UdR$1vBnpd(;F>0c;s=zT-+0*Gr41A`G;_1WaS#&?JJyCP0e>`YFk_CF1a(0^0Rpx^SvL znm6~AbQ-yD#Vo^pap~jnl!p=>30cvx&z>cwsLtywbm^esD(A%N??E<@=9ali)|;VX-n7dmTaS z!xy3UnJo*$t|@u`cO z#yg#^d|yg)b2|IX^K8W#%qvVmO$ajrz2@ZmjZU3M-MF-?O%~v>nHncP6KoOLDgICb zR&NdMZNMcsFY8cTBdvvU*RSlUxhZp8^qN1B78q*3?&Caf1168d%V-SDD`Q&pFFcA? z9O&$6?LNd}viy&1jTJ7%2j9Xy3L@pha$CA|)1pdjar@F|oW??}oSzSBU`n0b7Ro)M zqKa9$lanl{w#T!-;kFdmTs$UZpWdeT?yUZ>{(@?c;?JaMx5&uc;-bi$WcHK5t-wlb zjkCTQ0Y&e^fCVj07Jj714Cn}t{5gCx)9eq+-wwc+_Enurh-7cL%?><&NKc!|UbDwf zQE(`e6^h)wsjr0Ke2Y=~li2|JZayW>weIzxl7{`Ud6ghbxmGMQl^aPio(0CIwrt1E zvSkN82Tqrdxo^GJm|GC2#s?U*x+Gj`x!fSOE(_9?Aj7|Cp3;??CiD6Fw&`X&v3||PUk+S zPJ_T^dOCN^K%?0pu=dYcx{pUuT8&dB!R|V;e1(`hKFuJGcPGyVk_cr@@Rh|aY4!>@ z;^CAjlG4W|)+!zf`w&ajNnH5NPVx>+S`4X@02dM8_dUM;O~s;Sk%B1<5cyO;T%qmQ z*U?q+lqw9IcmiDdm!Wcul}b|NK?as;RP1xg`kP9TydFfts}aT$m6##j9;e})Ti_$5 z6oNz-;y=Qx^<>duTReimRx`aS){;F#e@z z$+#&+6qmgmMA^~!=TabSphGHBsLKwK=_Gm`&os||Cm?I5OK=wjV;`3x8|{B}0^^X1 z98C-~j&>0i8bdf)oe5jMv-COI)RYSTsoiK%+^kgrAn5HSAFQEm2kN*S?BEOAgM)P@ zi4B1Z-jjs8WMb3%E?=*m~l4`%=gOf;L5_%m$n~jkfKwv}9Mjg5Q zrdGE51U308*?m8nk9%YXWk&$a$Ecr+urF>*Jf{GgT8{EZ^t1uJA`+@VE<g( zNl_(G8eoOhEKM`$F0ihFz;{{}R~cA>P;owfinvTAM^KZab@_*ZJfc=M2LQGtsLMUr ze@S9IdsvAVhoFtZrjk}I!sf65{YsFXOG0(SbV&yRJm1uDPN>D}Waq}UsIybhbCyWx zgwPbr(+9_8C%%%DUdr#1n}Uo+2HYO*wA2_34mb5*KivKQ&MECReR(uiQ<^UPk`6d6 zO3jo(_89QxrUU8Fh+5;vNx*PMx+ZBs<80HVgcZE z0pXFm%QuyPZEu|zM54}kdQetw;`-4{9N?s;4J|3_>M>1;yKwuuC~~zDabO|4v~W_5 z{{?u%feZtcB%*O~Nm=8cvkEkUJKsnuh48JLnnDIeO-qS>z0K!1pV216um4b$jMJYk z9r-(q_KWEtOURAGGZ3{{SC1+Wjg2uq`vI5(zWOaW(Z5^J%QQIiLz#mL|Ha(c=mRI@}TNgJM zg!?qzc(nwfUUC1 zL*3Vv=Uf-s-CyqUd~n-hF%aSIu9}FejI)htTizLLowl!~A}`o5&tSH|e9Fq|y=Q)= zM=mS(d4JU~GZ>*d>%+HaDD8x5%*`9WsWd78@e@AKsiLWkvLb{Z%)zAEIPd~9BeHOi z$WqM&s_HFRzOKJJ1!>ZssGmFWN^&Dwe_A_in#i7bvIM);tjnRLqBa7160j@|p?p$q z;vc>n4N;Z&JIMl+PZR|l+WKklLX-PAB<1WIQa=IJQReH%R1nUPnkjDEo^LkF& zYrRTBbMR;0oUODvTW)m1YNb|1z;$UiW(7|4d;) zXb|AjRZIm)*We}nhz2DCI<*W&D+*mnahK=+Mwwzk??$nf1%r(jgh~pn>*?a)$jqYL z7*A1U3!LX_O$iJ0W^*3}m_z}#zy@BX*A%U+MaN>|G!V3B%S)tuOzio=NIhI;QXH2~ zgq#=mAn$>j$XJnxA|cR5j#kD93A&9t(Kop0<^i567BZy^?ztJWyVGfna;Pa+FmsYe z7)!82ZyZi6&S`kv0eEtBDT&8L+x@m&`JcT4be2iB0jswP0I0hGIl+x2{zfLR^{}uQ zS`z9LKoTy)rH}gqXGxH-Iq^~eZ-xM=s9f5T{?8G${?}Xw{q}Dncl|xFwMo)|$(4Zw z&}>_QXZ5c5?5>uB@l+1F!#ypQd_dra`%@Cd5v6gkx5?I$4g4usy#VqcX82O;1_R## zU^(R=x2@)r%IgamXQ8a0-ywsM8w-Lf01kKYfIQGh&1cJ9oZ)*Afp9x-`{{DeP1X&K znzP4stxu;HbAC#Q-Qg9$(?0tCOjIta%ro83bz#KB2mRBTT+b%*T(^l&lAX!?a+BJ6 zd64A9I4qR`+t#craOcp{?A1T9;_pgiDVtIr)iw%m+b?WkXJb^^cB_$Fhdjr{ll)DX0KtNmsybwN1*sYwr9Qzy_Nt2GFoW3`o9xI4cH zuKSr|^b1-SBsb-5jn45I$#z>C1-;v6LxWFv51b%;TGdeX;)*Y>_AIpKwQ{J9SphxU zp5*9C85{u4g&mxnAEkifvdD?6IGM@RU@?o;Nur3sk;i=xGTYGr|C} z$sU?PtHwk8`{W15q3Ue{ET^5U&BxxipvKp_tHtL8WVKg^B$~W=>7w&6#LxR#96aeo z_tsZKEw&GBLwa?H2Rw036Oswv@^g#r@nZ-s)SQm{nsRsY|0(ZgBORH@k)Qd%8Bcv$9 zTKgoP7v6NSAn3~M6-RKQ=U#EQ=5NpQ>)BfBjn{0f*vi(AdS;LJ8_Z+xywYPm;^ey^ z>oJ_~3X7?(9!^R~wXL=NUjrQAl4)e3H%WM_W;O~JaiK1xH$ z6@v~~+Q=%&-cm`{n92Q!F}Mxk2Z>{wy$J}C?xJYJ3%}N6wJ|lAy!D6UBAC+*(z)wI z-2nQRHt%#JPl8AKl=I7ruum6f#?J5Q%62YwqCc~6+fT8r6r6vtczdbnqj94fFQ z)05j%fJfNfobq$<*w^t4ncs$^om|&?dR2@zJnd{>{`SzOwo5x-#i$d66)~e@U!HQb zj+Sh%3+mP~(3ma0P7m8%T$DXAnMI~Fj?=qqv25|z__#KxWDo2uR4-nwP0v)|^?|Zl z?miCo(e>X{H0(9ynQQwH)%`wYorURl(004kRMZAZ0sdUg*c? zxz7wX0*dtpx02G`%uJJzp}CkCjjnEZp!Coz!|`ez-LsnS2*4F{xl6D8HaL5|awl+D zISx^`4dXykUk$~t?f|X^cS}bh>Js`}JtN7(=hLvQ#^!2U*Z_UdjoX$oIvhD7>I*U9 zaRx4I`^Y@c!Ve6S39Hhc&ve%9?Tfx>T~PVcA6^%Kdi8w51+$K6u(y-7yM$r<=J@lH zm%OiZ;1M!{fu(qev+iQGP(RB<`h<3fQC{2xs`d}G-1;c0y1(D}sw;_rO;)c*^5K2^Y>cM0n7 z$h^T~=D9z>vO!;Hr92-qW%0MX$Uog847JzyE9^t(tbLgAj(suL9h7|b~ojWg=gsI zdiN^-G9GljVlTLb-YRSuE?b_&HtQvlyUbLMf9)j(V-KISsNxzG!5f5-7F5=AE@-b zQ0ZJ9{o3M&I(&4w{|z)^*Zcem?~@+UI>9nY(&xe2!Q%IW5nh){hog2ePS!u{j$h){!1 z>pb55T4d7fK8#kOXQ|_}nR~Psl&ji`i%({C;^Ph#zc}6Ams;C09;|BXa8W0*($aRH zfBF?odbxX{P@{Qj-JVSbC)nctx!stYtdo&wn-G#x_fYN2&kg9`!C(o!47)vYKypQ( z+5zF8Q5&kEVvHma_-k&a;V9#o#*c%|1{}!mFbMg_c>mvA2pJ&0lR+&(v}wd1Kz$?| z3r@{(!h%onFR_l3HF}d*KUGwV+YgGyrZAU6F702EAPzlo*|Z%q^R^+Ym%xeN#!WJ-!_D-No5UbWuOc6;A7Qh=8g4!g-_weD8_#Ic@BT;j2e6ML>DEVvI}y}L0cz~G5nQX zt9Dy>EKH!<0;^ZOxAS8ya!Y68p=ZF8MsHoODXe|u@W;BaptjAk6UE-=X3k}5_vq@h zX*?dQG>ncojaPqkDe}YF5PGHUhLaXvd?JVq z6~;)B(DUm+p}W7%4@5pogpio=SC1(l@?oNPiW2)BsE@Es7UxIhjoIUQTmN3+ZN1%F z1?szwIPAGQ-Fe_lx$C=y+J4USr}G$m;NJSVq!&2#?hnOek|cw}lNe~U!|p<=5iJ|w zqDPAC7+wmcnvvNI8M~*1$WMTtsResK(V#s;?Jal-_PdgQ>9w^POEUdAo@9=rXaw5Q zElV=^a7SXUn*!TZ8s5JTq)4)O_57 z-DR+|u+;GU!`#j3!)5&mKZU2A&a$&VXFmJo`SaZKG0jyNV&i0!w^Oo*gfgx+nWUm2!uZX*zk^jCEE@bcu_H=te{F}e6 z2RGf7rg_cuwY$Y!xkX5OSv+E3+HNstU-M%*{YJaTS#(T*!^1@`_)E6Zo{SH4wnL!KK~L9?QW;wUZ%= zSLk3ibKJWKSZ>~@gkm+5E%?gv${F_eF_0fW$5(O?ZG=?4or1GuixetWgGV-s$F@F~ z+-ddA-Q$oOxt?Bfx;)h@^mOIk)?&WqOVXbAT1Z3sgUsCr&vL!-KN_OC)O5@ZWvOqH zWas?E)geaZopD4PAF*Gfvz(DU=r{~rPu_>3VmI2Z6$G^y$wHOgeid?eU{u5!`>kJN zjM_S97rk4;8V3PuodG)MvLGWHPDv7Bn_NPr1}qoU7{_zz>ldfNgCT=Stmo$P^(io^RMI0p&L*P`>m3Q7)WOH zv_(dgbF+W}dg6O}hxpsxRHWJ2P`4BAM9WOoPR#dJW~JZ_{up28dUc+S6nWCQUb-i% zLPia}6s(E#JnPzWuXKc=bL6C1c1E`7RdU7NQ1bg43MI_D=@2$a*P(>3EeaB-7u+;P znW0-wK`*m^hPJWquzysp3E3>)1yXqUNCawYvVkO6c0_Jl?=K-huc7bVh1|2Qk)!%! zirlw^)c8vLTb(#x=(dOX%6>_TPe1&GZHqOzZTE)@6@!F(1w@=5snWahPQ|RU+(itZ zd2eEDMJsXkg6ue6V+YEF z4=9wYod^^v{?Z8|KdyD1srnI)&39OBN+^M;?^o9#LuAO^(Z;EF_=HRl|Y*5uzG z?Y0|j?zh;}(f1)D`GHm7q1<1s2dq#p7?gmPZoef)L`q%t6U}{iGw@%lN8H=JU@!N^PhJm@7Pcr6?rP!3IQyG zIX;km7Z#(Tm&_KUscSMQva-GC&m@@Af#D}4#y%{tMPs1J=us5x2Jb&UNEKZZL-{1K z271wxC77DrdGo=D<*7^P`Gz7i<3S_(3js-7tf`r_8kY-XW627cpK_30jk^!M;?9J( zk#!Yi=wK4MeLL_tSr%YVd|Wct7>BkYea;QWlz4Ke_xDA21ABgR;LQ9vFw`3JEGc3` z$(@YiEde6|E?c(=Y_p$-v2V9s3cB~_L@GhEqQ59S!+&SRiF(DSj-7R3Bx}~(DPW#q pa!qb}wJ8A|V2v%U6H_(+<$m&?W|RLf2<%^!L;nALPVL*!{{!cjjK2T? diff --git a/docs/static/images/pcache-fileindex.jpg b/docs/static/images/pcache-fileindex.jpg deleted file mode 100644 index 51f4e095ce67aa2878c248b111bee57eeee22cc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54922 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∓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 z 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 z z6qj#ao~X1>V1dJ~O;z9yGsD*@Ep2h@?6%$-S=KkeIY?*+X0~GOLu)ohCV$SgnqJ8D zxpZ?CLn3`^@wohdC@t!Dh>p!sb;^lM_tnQH#F6(VDCBPs0IQ)CAZTK7{$@CY2p>ba zr{eiVmH0h}sgcxH=61-113VaHmaeA9^F$R3uF^?!~PQjO}HJnRe3c%qlE<} z0#ID~8?thpY_fG%d+#~Y&S00cSS%pGZFobhA|P;Ko?C{PsR?rz`&aUJzx&w8XWs{Gfx&C%FE`Q?Vxh`%y&G2Rf zBW)vnG~k{3bT^KWkIUsP-W<>JVGE`TrA)Liyw)^Z&2x1qU55rjQ(%BTePCqiQmZgN zQ^aN>VO8mzA3q}@oW0#ng0pu zrh_2qjGAvC6WY!qKdQkF+=wH?!XY$rYac{S96S8~rvqICUrtwZZF%KZNANu(xIUpq zZcgtS^8$XCf>tKyN5kPU%ucs}>tYgZEzgCue5qZ~MR5#>y~HYahUqKQs*n)j$RL>| z&|MnTtrs%FZ~DZhL;`w4d#tzM3M|#RO8G!>t^Gl!*QU4`7FM@vgI;+9=OM^{3sVk! z<(F64Jo#Jt!VstxT>e!$zXJdN8mEjyVe7@EBO%`Ni|Ef$K)S9m#{y>vabH!a0`>q% zI*rbXb2jdc0b%)8D)M(V9pr2^j1Pm`BJ`IjD+=|d*rVQsr>9>L=S#D%Q|AtW<-s9~ z#=GZ&`vWDK9-+fSoI2VJM8j!-0g#_ATAA|AN%eNz#5mE$h~DaHXj& z|1V69#l}l1C%ewVL*+sac7cq=p8G${2VhsD%VJoHW1pA~lK>&b^%9kj3X-g;m)VyX z8A{z?Ri`-r3?H$(uB;sCpsvIzY!I+q%+L!`&wV{z-15>e>tO9z0fz$2BIKZV1N=v{g7?nnAn|Y*5*RF;*@37?9r`QfnA@@^ z`!096!r=Qfy$T-+g&WFq0bhu7)6MV3Pdhy(87H4PxcPUNSGLSC51#vb0~ z*twgo3S^u>$Tb%G&%CV2S+tL4jQ1enX7uYm_TMO!!3{V-GZ9MD>k6p}6To9~FhwHv zJ&t$;IB%xT{tLrWs0Di+&g#FKD%Jm@gshY64TUSn;gkoyUcfQ88$5Z-W$=@(*3J_| zs65$0dT;$iS*=gcNyq)zu`1yUQC>B5KqDBy%9TpK4>?$ul9e?^{S|?eOx8l_q4wbx z2x@qttIn)ocs3N#%a@qr^iooj5BxYz=rG?sqp7?Lew+mN(W$8mzSUHPf%)wCF@cel zXRpzR%U06v<KJf+0{GxB&`f1>O2Tui>6g}wryt$CZ@0bNe0+>fd(SKl*@PW!_L6?2 zsyuqJbbO<{tUL^8|WJx1~Am6yofc{tL_B>3F?PWRY#Dsp*@qiIy)%#~k zGkjId7f3&}{_g|aLInFOEC0cgl^?NW<-5tIOjX{ILR~fIWPnBRcGzGMyme^8@P7&3 zt^ja^#xSa(WqH+tzU76kg_d-j@H@?XwkV*y&<_FF@2&*i#=J%=HWV(MXZKT+PBrA@#EeyK0}O8 zJC8#g`8eOO-0;AFj=3g~TzE)H!9wM+$I#+gcd<9b0?tJ$K0cMQXudTdiAorEA-fjRoW{vGpwtJ?p+Nqx&g1Y;7T4Jo zp4Ew<6`}4-@XMv+@+AEtGPr{Mx=aKPr(EBDGMVdhiaVi-gu%+NyBT*v6MlNp3})I~ zFQk^b6Yd!n4t+F%JnuP>FSSxlw!Y8?67iub@m4-zSG)4)r!2Z(h+)ieT{|HaUxB|h zj&C96v1suoE&g^uzg{XHb@j=Td>*GO_2+=N!wuIJX8a`-HhPH(=$#3rQMG%Mz()TU zU+JhSpflp`tFYV#bjrkK+- zGMe}ZP0{FlQ+h^UXG!Kw{Cv{ipq6i8bD9fxRLKi}`sI82sZsN4g-DS;RylGGMbkJq zX2}1)YG_`X$|9}|&b4aar6gfpiNexS2JS)ArAQ*%?(7AW{`8{fjHwRc> zi11N6OGrxQ8WKNog;l)&=$>w*f~_%97}#la1Kn@%=i4NgOo@`URN#|!_v|xY@#>CcJ z0%)mRMh;)WJ(jhrB?W%M;YrJzD1kPJ<5pj91pxg;re=(5nlZs-!{mF zLI%zE`0z1ZO2ij*u-}0*Qa4G*8|S$`v@YakWkuu){5XYd*38<`lwrkDJFZCwoTy@& zr8*X3#e1Xyv%VdyVpE;60H*d624!{^sQrQYh0TJiU<0*KJzSN?F5A8E|CiE$#5Yv4 zavD_fLxKln5_>sdQa|uKLx7Gnpv z_S`@)3%v3Lk=J>VedU(EX?yF?${I}lPwc7udNuAf&J*?5^^lZFp|lkK**%dGhj1dLI2ovpYe(l61c=!Wh+=9H`xI2HobU zh5FP5BF<0*O5wjieEP0>Zz8062=(TsIhW|Gdno6EeoH`|nWs*X9eRE+dz0Gg_2=T~ z1i|MUt$*JUL!Fu?OkfpObI?`StKXAW><&u^+*rmUaZbEevR;X#_&OxxlDc?^Z=+V?3tHM&iWOz&q#cCcn5l8 zr|6GwtaLrMkYl@``P#P^W5xT#l@iq1LQnk@Fl1Z5m1^?T@AaFhC-rg8`&kj&as_T%-QAo%pUT2Fs<|1z3T@hfty}gAg75Xd6#Q{4tu^!F#?Gv4q9&Wa18qh(u|*nr8{sKQzrphq=p5$p&DBEZOc1FM6SMAu4YkiS(I1Fw~ zK^}pT4)dPQ1j4(gywiR*n!4#AJ=>+$9YI*={uHt5=eA zbZ?HfDr9Rj>3*^sXyAgg?_&==HG(-%9(BIAP2mF56t@PnmhjB^hu2TiJuZCJ|33Ko zc>a-Fxv`qK%u0~{<8VTA_w|%Um+$qJJyLTa(Ir$Lt?R{}7_*y|d{S4h!lj(iT}S{! z6cBs9$B^rpRbO;$t9pdqzUq~_wLdFA{D!VLwbo6BqYk}!R3(#A6`=i)zS885evQh& z(zV%Yw3e$^8eIp%r`FBJe&*+F@=*uT)Y~=2<11gmjo~YsDP_ran^k>LcL=WaE?^#q zO=);?87^sc?pbOd9@8*gc7wtGy3HfT+VO`KOkG~ZP)_K0tt=*|Rl)|((;PC)MwA_( z#tt6Geg#X4MqvS?eZKJEOO5~Y6d$@T+KVd!8rjqM)U z(iK&a(%rINua)Pk<@~w7HA9)~*2(xHMwl_wbmJ0|hdc0u=C((S!{Xhk?c$A!>ed|> z{%f!;4wQoyn3I?>BFXP1Ftg=YAlfNf4CsgeI|`@tWs9n zaYRzY_;F$mZEuoJqM5@x60iK|-{wFy)}Ri}7yB+>F-%l`K4V2|btb|NRGMeeYCw;5 z?MG1%XgvKn|FVFA(8qD$SNF+mP>U6#<6&C z^XORAu9E6k(79_)5`)vb)clE8F2kos^Jgprj_w{YpFJop%z1bQA!d+nnb!K6*ZWLr zMH8?i9B~409wycO1{yju{7x0x@ln6y*FknXAr#CX8J^NdCy}Kxd~Nkudhp;I>v4lb zrGbVd>SrMcnE-(*arQaT9uk8N(EGc9N8X=dVT6MbZ9H0Ym!PD|&o2%id~ z1fZQGz5fPysPVEAnY2WC3mDuDrVRABwb0j#j15|LIcV#V&&442fqo*+Uws+w7n`UD z`H|@!{B@Rm_oGv_*=;AV<10bfcWOWjGEYc43XB{uBoC#ntd*r&gW5?NXVn5ZI8`X_ zd?`aR8arFUCby57Q{zLp3U^=oaU8ykDuUlt2L+64);Kf&N>4$1RZx(|?m=q9*qrp* zJ%;W8kY1Y2quI~L)^t7jbrb$OGvv{vN&QHb!M2wdmWB&L5^^Q*@ia~2R zU)nY%ziPMUpA;65L?e8egPa#S`qiN-*fx91pW}f~p+9sDn3-u)%q4JSlOl<`J$Z$e zg>nJK{e#gratlwe7ro{xX7;i+)FZ3?tXX*tGowNxwY$jod7fuDj4{Ft^Ck8E?A_nI zU7~H(G*(@c{k+A!MVnSzWzGxo>7jz7=4xIU1Pq-iQ4MgYpsaFsgbU}jC$wqP#vYn^w71O!@LP{;^*pO-RZi366BSE z_AG3na%0$GeNlZBtVZp5SfO5clTuEPr0Mt3(YDc!8@}QfqYrLw&Z+Fr)8?dQXOfoh zJRUAcyf934t0|4It;8@!x(C(rcm2iL0WIMUdS_Y(AilevK;|Q_IZnhiGI)Bto6`1e zbog3O#`f&N%t#L2+#X+m31}BS$<+<@t5SQyZ{HoX*BX8=SXKKd!JQDmdH9f}8ZIj0 zIXCQu4(DYR1$}iaNq4*x?~7I}@TJmo4*jke!K+Mbt&(l;7Y8XH)}_jw>$$ zV>3l!56+Zx@@P+unM`gZPsbzE1`NaJRU8Ggym)oqVdB?m^YplTIHqWrQo=5d>$CeN zs(TUhbF)+DFE3A9qjJukS@?Kq$Yprn#BDcJ+&pFG9PMg9xXQMU`q{!~HR`P1F>;+WHAR z+wbeN4SGUJ@`Yw-%A4+}!X>Rf_P20eX*$FE+)C&ST@=xcX_g%_QVdOkWFoW&6b-@_57PwQjH>d>(~NW2$cy6q$U@+3|L7n(CQ81_2q4 zj8KspQ2R+6nYn$V8_7Fs)fegP0{oFqR^x4tyze)KY@M+=?{8RtkVRgK<$I=4wuvxaofVl~+z$1|eakfyuz% z|6$th_s67!u+Y7&*8yKbXNAx2RWB`)r}@`rCcH3p(PrY7d%yMin*q7xmuU4_2AVu^Np-j1W_;YAm>O zSeyIvyuiTo@z(=PsPhAj6bNuY;_pBp(>ld$ zHG-+Anr^8iR;&$JTbKuvIEn80XP1{*H+smtefEqs?9@H6BbxmUWQWHgi)(|6V^@o Q_!9h>RknKkw+|iv2Mcm$5dZ)H diff --git a/docs/static/images/pcache-readiopath.jpg b/docs/static/images/pcache-readiopath.jpg deleted file mode 100644 index 4993f0072af5c3e56cb43b8d694931dfcb6824fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16381 zcmdse3s@6ZyY3(gDq;i_6eLkmQ4#S1Vj(78s;CsBrOHi+lp=%_QVWzQAwdwrMNHKS z6)I5?Q4ynZYq=yqxYSw=_duxR*2>+?R0vZtnVg~jzyJ1}efEF)oV}lYp8tf$%$Q8p z%v$T4Z@usPy<_MzD1aqL4jw)T7#RV;A?yz@D1omJFllE1z{dyJ1OUK%z{JP|Fvj+b zfH~Nk<=^+Q9~jvJ^M3yv08Sd&{bN7GXu}`Jb7{cbzaN9O`TGky3)a@``DVAqx3GV| z?_ru(wV+rE(%_B~*j0CpXVWW+IIBN?Bq+OcITuxsC8pSiQGW3S)$*Zsb` zq}%>dKM!~|cO-ei*W>U$wxI#An2$A5Z){`%%&{;swlFd@0|e~s^NfCf{r)rT&1jDC z+<7LZ^A{{M!w#ri0?aWoHl8!rc;39((-{G z!@j4D%RcM?*Y5cE^OOY(t!Q}vt9eWXMe3@DgReJ`$xzA=+^+S9}Cj|+{=J9@}Fw~`?cXU7M9Bm zGGLK0c0*VgTL5GLeJ$LEb`$2a=fXve`1#P3q&Y~k9610QfXhgbl3>Q#4%Rb^= z$w)uXORnJA!&h~OVFz$bWj@WFbTJ?0FC`k4gp3ZZ;U0N%b-KkPyz=g}mFsiBw3KPn z7P15`P>2?=-Uo3Raq?xbLmPW#2NJBrS<)A~MzMXD~3+8(z1`XKN(le%8 zude!PZAZ(7xMTBK-jH!4it9!3aBTDW?!cHz$hXx`ghPx!?v+YiRJi3dISx4s9i2XK zImk08JBj2^GzR6Z!Zq$56}>xM7H4SBz9y9<-s=xeKCG7Gnchn71%wVcw4MJk&@@G8 z4|_F-5TU|q=lY4!;X+HW@Jf<^nHrGW_CZyB_ght0#Z3?GMA@Xyu6E#p@qSwLZt>bJ zM!zC_@R&>gnSrLkwNOZjN|S*n@S2_>TX@GoUY00ABy(!wSToeH6tX%1dxLj$P5ila z-eA#TxJ6y&*v7Xa?k|D~Vwog`Wu`J;|E@WK(=U&{Os7{S8+Q=hb$+ac5T!|zO;|q8 z@+%u5nZiqi8yS~GU_SC5TmWiXsf*-|h4VYnb_0?~LcRz&+O9bbCq9l0aeMNl z4onFd6E2s=?WXTd^?lMCH<10rj4+=ZMM&kZtjviJv*&dq(P642(>)zX05-Xq2EdfP zon;PvB(#)1W{k)2tw%`9;Qs*3zs#5qa z6f7(*iwd!S)+5u6i~2t;AM+p;g=ycfkKVVlYWs_*++Pp4-m=dDmIB9&_RRbD8&g9& z+Cq+`^v|Kl%(P#_I4CF(;^we6DsXAka?OMv*ye8l()=KS8rMk3%nR8H|DrYk{Z`HF zCAD3Zl8b?ov@KJV1+;vZ2EM%kXz~gxgYP*dCb6d*rgKB=rE4#%KLqM5@x?N+!4KU{}Dn6f7J+Ycz#!RKa^`O;Z{a0@M#XgWjg8?vN?Vy8!1BdEB z(S&Ic91%dVTua&pAHHj60CLDy6U0y@&JGUp>8jhU`fTw@DZ0Q7DhPq;V5+ialss2- zhGGHF;g|)m4(W?HX9P>(&S^Eb?PieJ06gPQq=D{1uv#sE4+~eojD36+u9;xRN&-tV zh`x8|mT+DhL-{&UJvSEZm?`{@kh*215s_g4=8E-2#4r^n6J6WRTCHr1gt|AZh!s^2d%HAA z%wI=wwKT>h*%;jB4NP%l`?ag|WumBHtG$p*P8=_pJr`O zbH|n$msC!)px;hg0zWTfmS``5(s4bgQ~5O+fF)6EJN;KgvXXG|Dnf>-f>CraWL^E3 znMJV)vJd__H+BC5p^^Yed*~bwQ_`0o)lS8g3F1Z*l z!J{uxJJ=at4LM;kB2fh6)CtW2m1P`pudKLftkA0WXE;_-8*I}cG%q*-PID`Q*MD!^ zMj~<=ok@<&)MGASwu)W!h#BMy5*l9bXbrJ~HBep)$3{>`DMJWw=3_YhUE_Jb3{j*e zjanJPQ;O1rn>*_Q95XAaxKZ{3h;xy_Ud7r5Gd3H5MH6od4M5AxR?{n^a`1>m_H~p2 zFkaWr+7AW}4k?#nkrP97ht_bGM}CPMhQseN_iMj`G%b=$5&(vjM(Au0+~=9o@J7&2 zg+_2t1en@PcqSLW-oK~oH0YO2S~7y-s-8aEgL@)6E$B;aGXUk}7U%t~s3RP#+m3AM zl8o1b2?|mvxq-ZtSxuFawjg`qXcfg~bZ{+}s%*_UN`lxs(I=s`cSiq>)RG#-UqTFb z{@HyST+qf|=U*{a@ENXW2H1e1o#ci8euFb~py=PfY;CXGe;Y3RA<0`TE6XhpjeZ1Y zi={T_StDoXdJJ7;?R^0IHqU%?b@irdnN4O~aM1hR3i9s9#U;T5hvt6y?J@KGf8YN9 zd{UgD^B-sXtw8H-0Imk81iJV51HJ|z$U*%QNZRqg%N>7CC`N{^zto12QdBX0iRA@q zv{Cxo!c|Pq<7gX{a6xE-1VX&DO2#w)C_+Em0hA=i>*@Qu*^Lha<+MD}$KB&)kJ-=f z_lNkO`Ypugby-X8b1NX04BjDaqZ;8m>qfTMKMtuz3W z>)VYCz(NOAo*th!vCRNn6pY6jfcAntrpN|f7IpGGwW1BR6g`y~fPK*b;a{fFIC(h^ z8%)Rw8_YuXDrVbpv=2(S%r-&RLATm8<&M$6Zzp2m3o2f5!;R_d^IyL zVg2l-jcoH~pI?q~lD9yEc->c5ipId(3>vYRR zlZvimy$7e6SGFayI10haph0GsxX`_v--vE%uk$gNryyZxAIq5gTY5W(tEEpRO5UB+saHUig(OLO|x0wE9BroB^$eEOZ?&q-Y5t{}^B3da_*HC)|5Uc`1sR>L`J0Z?}Z z)-;p+;C4P4#$;r{zS(X1S^S??+J5yyIoyP&AvYL8E{9|Z(_J!O}e?PwNj*0u*< zzh{NVdeLoWZo_Ed%H9h85?YD@@aw!>bSG-(`Lg70gj^ajY^%*@Y(DdCwav`>+Ivxf zTLckrl8StKDs-&`$?FU%4j({x;z{BdM3Zu!E*_iJA zM~tCyNEo(;@7G@!#b|6`hL#T*03JD#Z^8xw<~$y)DNF^SDSOW*ZSTuR$T|uzx^*niuh6x|qa^FGAl~i?uD@D;owWS$blq{? zA>K6kQ@(75tha$)l1th@+c0OH{u*kt@q36e;cNir=*tbjr|yJntOSQi1CT6mHKLKj z1?jXLp`*TNF;s zmAC)oMAYEm8vr-_1{+1>wO+)wWyL|fOXN>*cF2+rp$GK^n5UXJ~l!2Bt?ncM=thJ}9i7tea!#^`3x85J)g1N$}34P;ND z1^PX;%1@lg$3@KyHOX^Kl`X%Bn#OS1A!hJ#NTaM4lVubY|AH&$vIz7eWLj4hu7!rg zf=i-B$i};@pr;USVx!Ix3D@7L(4-jvDmqmK-5S#o%5%L_Y}mWW)O#&hq;;AAk$sa3wE2(4EIR9;OhiU`t4z9a9yM&80ez z@?rUkxR*hV_+1n{lh^G07*1^1v(ma)L6AkMwNY>osBREFY0)%EGRc4_Lhqz_#uOic z^T2EGLkA1!=Tt5igci^=*8qGXvPE3sUzCjHVufG{lOQHJ!PyCp*+`Ji70zq<3^rEE zQ;T|p_N+b7+(Ab;#OMuSA!^2!@2m=3e+|0UNUoG-P6p#XgrA3lxzYwvC3+34^#chR zQ<}>HH6MTn)JN;jVtgt{ikt%nUntq-O34D4BgSuq{3fXJibRGHYcF{DHmGa0^~)q~ zM;(FeX2MhXL#v;*t^GS3;cw_kJXPYbQYdi2O0t z6o~@e#|^;kVKTmwXE`d|MO}ux-@RD@UH`F$n>C3u87+KIDnM+M^|2>vV2SSiQQL21 zUfhyrSxre)HMjY7l+-I}$B|y5-~CaKc=0OJ-mkEn|DyqT8iCws8^>0#GZN6j0Gwc? zMH-J}VHw9D{YP5)XRyiWzig>&|8G9uA0YIf_D{^Bz?>qMKTL;k%7~1|@t!U?#2a+a zW)sG$^EdZa$zI;eQ9BzS1K*a!-u;Oh5Os2E)Mo)vzAr-E-rVHSmvuHzTl*a=ZBXF% z{(lM6{xYC66(Cz@htd(_uGs(_QadxR(La%?%Z`mt3CPT*`8$km$M7se;=^_5A=oKIl_MyRFak1 ztE8Wgkw0>i1Pvbs3_$v3{bkN@7x@ea*=hjH$iL;?GDeq|Yu_^fMIY#~8x|BJ7+ZW3 zl8l2tmh=swIXkow20)Ahv6c76dEj5H*}njw{|R~fuggMzfGM~f!-BYU(IS$i{1isC z(-eGeFN#SB27oS_&y3>O!lnDcijdV1HNyZz3vy&+F0$jcusZ4En#ZED(dypwG1u?< zuh3{TF=Z2Fx31EzEH|F;nB}XuRFup*2RlF#?G}AG16$BTU>X!66>|U#tSyEwQ@Lz+ zP=3jjJ2gR2N-w!iP`sozd%6Yk!`+Vr(Qm2?UmOccObh@1QjybE)4`=ipbfILOIeal z+RFaHQ7KI&+7-hFz#p=iaM#V#mp2L5fTUu`v2l9Ps*}vX^_K@j6Y^#kd#;XYF z9)t@O5(~QAYGPf@v#yWfqk8Psyxn&iwnis@*1u!}Ohy7_tCIq1z$>x{IOGw^vo zYKFY0FPNn3qRCCvg(Od=q={V4Nki;a_Ey@x`dh5`wd`H+*?Tl}4cH@X3APdASHham zbGJr!iYge5-#fS7hL#%dN3A0!T9o7r(W1$Et0VA;5{D_;+H3Gg#AB8#h#UXqZsZxY zn6v}Cj9j!ma0z_j5&Q&wLA4>-k3Pq4PXmys;NhdHF#uAc+wMG+{&set=@3DCp&JQ* zK>Gr~M}YJ;KKLPzi=oD6c}?|;sh9Ih(&PAZu_>y2%}`A@iI)0Dg=~X)vATn-gP8hf zqtIk^MRJ}9vd72kHYzx2HI(Fg@Hw@U<`SXY`o{f8(%Kq0=Pv62Jn~5%2h0ZINiGEN zm=8yp&``f$g==BRMu(aCa;*bA1RWV@IOa>+p#R={})kPEe@%az9!I(D)WK|R}!bwKCQCl98d z4<4*n*+n~7OdOORI#8bOos7#zEOdJ~jo!pvIa-_%yZxkaA?Vkz!p+5;^&UhKW87;@ zwWU;1W~Vi;f=W`RYWw3UeNu70S0*{ijxRg7dxx04sXbmId+FvwZE;djxp|!O^#!qK zX{w}E#`oc~7~|27_7qDIG8EKGuChr*Z6Xc8GSVj5FkuB^q+1h3utc{o^E=7O)Mq6b z!UfEeLwg|HxD|NM*;kpDMcTkD(AH)*<2|eNU$gN)jf6P-zFxha=DAKdG@p0RAbw^v z)1otpB&`PpczkiY2rk$Q?c{;n#u;|~9XR`N z&+V$7xcUU|p6IvZ@kyS()v3pxb+7uisy^N6>$?Yl8DTOhLZ40k`4#ySOkCw$Ak37q zoc0htz{G9BpWqmJK3)#5!HvJCi)EJj=EB+o62c%cux)!SL_m0}l;A*blwcGi6A5 z@TpS(`?tR-|3<&bRr1T2`j#zRg?6bGXKw4d7&6t)P*`A2jSz67uXvHnL~}X#S$*!snpvXhgYb~XVr5D{jEA<-L^hvf;fLm zHdLhx08L6CBmVGhg{QLxC(@a$c=;8&LORfzkk?DH?I3#H9|2YfS7FU(F}aQ8W$`|^ zHuAISHp*fav0Z4%B0|#3c#SvgN4QK{OofZoL(SN+HLW6O8L#y>qMM4ry+@cIiQf#q zlr$a|I?`)I=|j@^TF>AQTIfa1Mx*3%7i6ig_MH9^+Yv5O`ZX1djlHDrZ6{Jx6OJD; zELj^Mb8c3hqe5fr&om2>hIO*>h&Lq4VdKA!ucwQ^oe|v~>y@RaD)l~b zyk%|5c3j=x#oXWcS`3j~+oiuo9`8rzaz@L*G9YOirmk|ZLd?HOzW)?${~y={?glYI zM_?JjSJYT0bL+PSjN9Whebw5St~kO{IG$+wK;8$do4zPq2o(?3LUyWzjJmz5`h{I8mrJD0w;|K4 zO8N3A()ynGQeH;LZcHI`!z~RSeFpPCDmb~*_Oc-MZr@0i^&WdplcZmuG;K`c1YvPn zZ_}LbGgy&dUw1y{0py^fW)n9mqdlq6+`^5$6;v*1KE%nM(wHDy+TF-Y4%OtmNd<@Y zfvs1zm3KaR$=80ty(9An66^m~JR|HtTll|55S~To3&;&T{R!c+Tey3(h~-b3{eNZx z{}*i@gR%b+@cV1v-snbIWQ}`fTy&6FxTZ7iB}Y7i-r(FBtFf*Or9T{&_2xX3y$E|S z;!$-YX{pvD_r`+&%I=%`w4bd42LoR^*EfH--=PR#O<}1mU{*};xX1p{0KlQBHRqiR zCZKPNnAOs;G7^Relw3H#0Q^{wrMedWQ;FU-VjPpy(+Nrpc5j~qIAUvd8+nq(FDIkR zIca?4#qSyOU#D5q_L?o(MNIm^PL`+gwc`ivMDi_KuI-kz+mTh)H79fK49iYDI_Yud z+TD@dZF|G+jpRpvwIra=KcLaP?B=6wK?5x-x82!ba&wlN{k3|@e+8}j=WYB+fB$Fn z;YV27N?HjgDCE}29_VtBo~I-jGb2L&R%f7&Vnv&k;H&HLW%PVrpKEQ<#L?;T!0_uY zCH<~rPA_zPr{@)w-+s$foyM7C5m62YVWk$qY%L}uNEgO=cM+p*wh|>nzfgpgVpcOI zaty#LON_YD$!Vg=O(<61NfTAM8-QI$Fy8hEwZ_UfrC5PSWsUa95#@$iI{2^AsPRvA z%hZI$n2&>T@o)97K9F~rwc{4t)+DR$cfnN43Cdz-!I8?|irrr0w#8+aOTx9Uw`*lX zT7FC2dmvj3jE^Z!ovLeHV=IgMB{iD1Bv376}fbQ_RU zu#b*1##teBV)t?ECWE&~--oT#FP$1s%1m1&17<3dN+rXF7UBt!t`rT+EjP#1tx)T6 z%`?GyO)fCyZTvZ`Sa>ngEidC@?@k5bsj2n>{ILg2)qY8)^r^LV485u1Gn9H0ILL|8 zq^SjIm{=^R})O zu~uub3Ry}PYmvU5y#O{=V3X{uz-5pZvlp^^@ymMztROI2TbPVvMfh`^M%*hC8kZBg$6)z2X(22Z>Xsk2g#i-q}|NB?v3V-%AIh%Gapy7eHMg#(`GA`#-P= zaR)!0Ie6D-lA0o7XyzdwK#Q}L0t|`IB}bB1Mr}QCn}2D+?MQ<8C4PC?+j6K}#T}oD zFG5Z{5y2&)rS$XFX~In`E4a1@7RnWuZ$fNy<3li~6n>ak0uDY<`gk|*A#7ziVq@Nn zI6{8o*Fll?vJ_dNVl3lC$q*|H0#TMA9^E7>*bDPWa3To=#E5#Cyrj;|F-Q0wD+)B> zDzFko^Bl3b8g+ngsFKXYQlSeZN@nAkVJ#s$AmQytw3=fkRi!!x+8)Y@;`8u$o}x!H z-j8_%!r;}40$$pAkyv6oB45c00N<&o+0Jv6nlC>3m zRjvPuL!+ixhb-hNc9uiuar1v-Qnh@zM6Izv7U_$Lm@~;;8FAww%N?fNrm@!sZ&J4o z?yj^hw1s_DweG-_3#QFf*|Zq|fZbz*&gq@R#Qq(MZt4os3f4BzO>l{{2su-etxo5v zH4B;e6_tVoXdk)~N|TSP!BSN}T&tR(D)TOgB3yFt3&Eib;Tn2~Rp*4OWF>6`a@f~B zd3ru+8HOK^S5U23n?MSWy$Y3(ZCTrx@`XZs}~fZ``9Kbrpt*9Ko*+}RbyLY zSkh|q0=hItTGd`zE8vpcpv^T2;@u7^+$G`;>=s)5nvUNAO1O^Qu1@x+jGd_1bpL?< z7-GrYUk7nh(M?RJqtLwSOk{t_DdxaQeIY`4KxKJ=oJ&DUv=VaJ1M)ISB%06zs!AW+ z-c=V|6e+X>ZPMyaK#XPxwhQTHxqJTj=zv6)E}6lvI)B zBo#hPT8a3BF4;m`CN}sYSzr}UFH-RBhi$oO$ca0cax-i*bF#LW6Ez%IrsrvMvmcDq zRPy-Rn>*20zixdw@GlK+#*-jql+qPbdg>2fYoF5Ond$7wNz_G1`8^H9ZN%xtQ;O@zwTZ=s@ z`iq?K35O5Clcjf=roo*tb>`v9M6*Mak}SE+_@_jwO^R?0oT|b+YB8j(v|Nk7Ymmo8)47Ka%65DeDf^m`%y2Oz$7TDn{wR6Gxv%Wb}xqATmUTP>^SA4 zs^4iJlk7p}8h|H)@gA_P4UdOYl@xQ<-e`Iwx*fg%Sv3ZdvrsbHE?R=(nRadbrMydC zm+L|m)Qh4e?2RyA8Ie+VxGV^HA9{NeFHkZ}M*L(Xo|>|BIDe@uN#kcW^;DuW@i~M;XzmXcV>Un5c7x!c@~2*YqG8 z1d1zqLK4{jMXPZ;Adut{CF7JnxR4#}Kp|X2BU#AI-iU8R(qWTl#1$@Kq@&Z?5_~ z5uqeKh&q3L9Q>V#xb^(f-GLF2wVi9WnnWOO{3iL#G7gQ0?2`}J8GvurVce~kW5yXi zp}$9l%s8Ji(9gcYm|MVXNZMw}uvA zl<;4rQCrTlo!O6Iy7svKYs6FK?C9H0+tz#{SvnPu{10cG%y) z`;&|1uN*y#(v6!@N3>I3p4aG1e;+e*b9h)^pyo`9TGL2b%A$h$-lyRL447zID09X* zmT}&a#ou=@Q@Z@XodtqmCf}!~z1S<;KjocwO7!;kKdHZLvS@>GPka(*ABZf770?5Z zW->O^ZTl%|=6Yel+|;98E|v9r?pBxYwKKD~^X{%pY$_JMxJQ#p-;GyRe0v>mrp=_K z3qr#lj`;PSFU)(;KM-XbQ(W?sI5G6Te@Ii_&V*j~T**wr5r#H{s4D!N0lcHyA_*`795?_`GD&N&uK6kmi($Lw{D@Q*T>wi}go`ZaJ21J` zwwHUG$@c7UWDfhB>oH9J0F0b$4K*&xZzJKV+R&8Yp`aT1uO?O8x>} z22s=M;*~FLT1g9_^%vBH`EcIRs$Qa}iobBoBO!zJo?yiNY38R>J~pR1W427b^gCLc zkr^%cG$x{|a65iuz#&(oU+^CK`itmQ!@$Q6ZblCk2G$~KJ0y|D}5~3*Q+-!=cvr-wfl+AO3xJ$&$?YF_Pt`pj5Ep{ z^JQ;k{v{~^YvR5Dly%!?=P##j56e*@$;NPAu<8m;74%LiSj5yc$*^@|9xs*XWs_PL zdOj}N=|pY!fs!<0FcH5Da&D~tHO|H2r8W1V+vEEq^gZ}I%$xTH^X7&56a+x)EAPUI zUe)V7nNbEjF(k(mMSu3PytC_T~tji_6&c~ZBb@I+$iSSkIG@lO=XttO5c zq}8x-6XnqXm0%_8Dc%kzH02c%p}Fi;(9X;vQko*#gLb-`81ZO*QPQWfh_ua)>5&ZL z0oJOHi8ERLLv>zWfxr;U43a4LEx5U(*s8Yz35A0o8|AC?nMdcPJY)f6+{E4jp1iU? z?M!}pHNHe8z>U^i=ANF}eevnwXl8tp9Zh?6-OIjoYp>Ibv51hmQmuw-$eAp+M>O_o zjH|@elTWp~>2b#@dMbir5A>9+5p`DP5KcVFRkitcPI=m8xWwuA{cvu=CE79TjVxgG z6?-hp&r0>i@9eaB`s>2otTXyz3|+c#3D&G=klb{>di+EgYZbQ@Z`mQ7%M`w77VhpL ztzvRdRDt{~mJ<})h$nXsykF7fGBM?pU2oDR-a06bKU?x#b^LQnyU%dR3fLGkL>*|~ z@^Kx=;35f%m+oQ?fH;;Qn_(Y}_hcXMgRL56M>BPEhS7Q7>c3$SPdsGH@=&ZGILOz1 z6bNVeSgDev%8!?Fk@ZHM7#CcpJ2%orpGj!p%whNAx5JL6 zEtIU9wp8Lg%zRcuNax$Ac`cafEZoOVNyuc>F;tX+F;z*9pR(-&Myy=4PxD&HixFQP zRw7;p`L#&6D7hM3Us6PKClSE;ruSGrdd$s_MIDA!OAsrp=9ti4G0a*3?NIW@Z>l+| zR4ca^{H4R$bnsdm-U$PjbXK7Lo5``UdWu-~`sJJ2h+@_$D299I^(eu7NUFJ0og18Z zsK$phpgZ%E@hBsmZ3!1+W#wGh7&@ZUhF}%S+t}dr>lCIfjI^1Tf=uKvNcpIsRN z1e39Um&;=|v0| zIKU&}N^&;eB3@(7+SgqdkJ;Aq^db(8&>yCa?~0eCgy6umX&$} z)SFo-ZlG59We`KbAqr*^-RNA2UQtjpx^|b{9?>+z z&0_6PF|yDd-(C}=B*$4u=#>02m9q0YHY-eVII<1=P=3+NSu*PUsQPAEJWcjzzE(gIn1F+zBePYi3 z#t@61ui?U`V>r6Lfasv}g|P=OTCTF&(Vh6sPivobb~SbIvj~gPHD-2w_v)jM?q?_% zeRS;}{s&h3v{B*RZVbD&_+NheLThU$Uia#j5qR&45|=THk+Ip|1Gz;K;Z-Id7BJY zY$$nRfjVe+Lc@$Gr_KU0dDYdpzl~bqnBkg3@dJl4!>hnI7tYa?ytLCRrsV*Z(RS%8 zNt@7~=b-l~xJ_ZCqpIY1n-?2b(k&%c7GLNuB4<)U^p0gB5|8 zh9Iy-Uy1C5=2V!|jBWtAIpH9sS+sl%wRze*S^IkDiba!k9|BRa!RCll)8i-v{oL2JeeZF@C{t=wV~x9~M{Oq%hUhB*Cz>#w5*@A6iTe z_F@~oqhg_x!l}+{;-_=WNJfXjT2*eI@?QPyr+3+FK=(E(Fha714}j%L_m-O^7nxaO z;rd+irF`n8Je5s`&}#ULYR=M+;lsD!Y^<#%@jNTKc5oSlf6ACc`^rd(V#U-HCMv~l zc*HLDA+o9xZc%EiCF1Sn8v!1raA(fsJ!WQy=pRk#!+b#W zI_6e@{Z9(rJEmOh#~KI`Yz)5uFs#__vzYSmw=1m-Ga8{AMnteZ0<|_K8ssripsa+FZ2e z`pcWAuKeO-Gu{QB9;veQe;LZT4WFR`!@f+i*h5kHp^TS_I4j?Cr7mWcHe1LdbA^u3 e`n-qC%P*P+Ey{bGk!^P|3&9@>Tk{r>}QsP3u& diff --git a/docs/static/images/pcache-tieredstorage.jpg b/docs/static/images/pcache-tieredstorage.jpg deleted file mode 100644 index c362a2d693327fd53a6b9a0673e99bfb38058095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78208 zcmeFZcU)8JwkR4zMJYylCreRLst8CC$g&^;0@9Hd5s@Y$O#+0(B?1BhOR7>uIspRG zq(-`m80k$22_n)Yp@a!(Z@SLD`|N$*JNKO5z3-2A-*3&j4$j7_8jD> z@l~U%5DpFq>Zx9D*F=I0)ec?>Hd)z&C-v-Gd!CBp?U= zdJciua!CID-i71zzqgO^g6#iW8*t3OE^robtY7o}`s92C{{3|aK6|zAf3)VPd&T)b z?jaw*?;-3ph}I2HzaYOrPrnDhsVSd@Xk9Wg-T!NJ@cQfi1x&OdHu0w~pdB6ts zM_kO_z;`86Y0^k1)vi|@Vhy;#(`}cG1=lTT~ z$G(tXfb;J^a7^`}z(or#SAW6d=l(b(bm@71O~+wbHA{-{?SMgUkrV2qlhj|J{RP>7 z53tAoOOX8?uz!bZ8gdyV(*M1S4G!|ZHv-t3Jp+=ZCVL9P%Ly_BKPNu~4q>rQgQWY2 z8}c7MI5$7Z_;P(V7k2N|jSW{At3J6*Jny zy{w`qjir^IjpkCPe1HD&NPn85(hWVpxc=oQ%47~E;FT-?| z6hiM58`6~|^W)q#%X1n~ZeX#q{Wzt!QsnF9a?hPVu{yodk1j|+PIEpRMjr!Y?Wv3L zXwfC~sUaNtH$bo}mzx^$O!wq|`gRf&=|R?5e^$57>En>r@b0%7s@_f)QPR=Na@|iK z!RP%bYUAJ>G)rHO4e7XuzfFjQ3Elzps3x>Tz;_P!^n6%Sbe(U=wJ{BoC&R7M708bY zK1qjuupviLRtHj90ze=ZnL9@7-T8f3H@FH2quW*Et49=nd;6xnF82nsY!zik^0n&L zVMCtO5e0roB*ih#_`F6SOS;bMe9j3W!}unVFEtM9YKP!{H0+%7cF@B;l^qH;Qa4Gs zIyI$aqds%7LWXp&iuZTPEyxXy-w!~9{_77^1a*4!oj(jd^I(0S6xztX#=A*XnG@cNpe)Xr@H(y?f1476eJ6W3#lGaD1&@M^*?bh z!wYBRIVB1cIo4AlH@L*-;ZJi90mybyj3kg{O!Y)fOy$L3^;gy3r7*p0$P{!gZ*`g^iIGMH0~sVlw2K&i>>f(95P{6N zanuLEl>X!s zI=dhPyPNN(RXR=*?Oi_HZfG2gL%qO;I5d)^A?RbsX<9zBpk5Et#gYbUsPxM~C`HCk1|(>Un8&Q+ zj5}<|<0p)J%t!Z7=F~t^9z$4?mIok6#QjTn$#xNxAkc?pTp43=F?cDBUE#1OOzq-R z4JFA=hrFnIB^NIg4^_>Sk}4v@57{;~>_bp)U__Ccw3on3(Z2O~nG?V^T?3r#1m2aH zsLu`fQmR(^+Mz(iJh*XkeDSpl)sFQPS$dM(8jL&QIAQ4ufD@Qe3A@!~KkxT<=re1J2B*qI7i?3xv z)`)ZiOvj~GKe{ebtq)&kS`|qxA%Bjg8!#N0S^DB#lnd5Wt_~t#bQ>s|bB6Xv(26L6 zT@LOUz2#6VfYl94H}G!t9&do3n0?wua1y__Uv+vLW3nLZ|?6uG^T7%le`K zPcKWFS;4qjhB`~hi27{|?;(je0?fZ7!nQH{(IYZPDSc1W{Wx8aF7$cZ43BxgI}jB9+(UC$a)R+^&b|_+9@baJdgI0WvZYD4=U%I+Rct@_KXt!;Ol6#yW-) zV`k_dLwUq^Vj6aO;L(_a8cav~A&nNoq#>!e1UI=fd!E9EJi;lqANCO>_UHE$n3G#( zL=li5?Z+7&)RVo9R6|nIswCZyaR*4D%da3L;d9CQd_!!Amy*gnt%+_jgHi! zjer|W;DnMjv#40 z(!#|e1lbVpyl8yAXb&-R&u~rlHBya?>r|P#JZIj8n3U~=%fRPeu_5(RFrju?l)z{Z zA<|AKVii$i7sZC$#l(k+u9cD2pG}RmQ3cI`6e_NJR-Gyr%Q#SKG%u|@+=JgAgp0%m z7Cpi^jQrO!78Ju=-?1Xu8&!0&N>{-hTyoFS*4ucV8EHiKH^j)DW-=o6>b7> z&BOS;Xe<~rmJM;s6LMLi0tGH#>R>Um!qoA#`FWKXe4Ssfk7T7}N1nh>hGZ4sMhlJU z&P>Nr)vf@3^M3qD*-MmG7nR{w1u_yCnK6 zjF)wUAqm(}4QX4<;```RKtIj2n(EPoKk~!E8Pfr;Q<=;lLnB$bNS}GTjU=jFud4{ zGE-GM_q?ntaDF=6~5yE)kCne|XlJNHlPcTv- ziq_|L)((^VBV-*~$JN&b(c^pOF({j0$_feE1?Tp3kpbZIc~fenhz2}4dW_-dMoA(4 zZ1EuvNPioG=${3I=?-6kOy3J6=o$yz!v_?eAeF&*(KEz4#N*a*Dz*dPP}0M+)Z^?F zg}qsPhlq#23F)l#pwX#4gf2poDFs3Hi(>>+YrA34cJ-CnQxsET&kCLcY1Qc>NKM3& zlx(|<#v%F*6nH1`FnSnXPgqqW4P2nTWa2=XT}-6?1`4h`7XU#9@u|2j!4L+y-R_+P zbS&D8!3mUf_*B1VnjN`~Zd12NL2iQnZJqHo~wdLSnmR zdwzp)dxM;uv1UMq?L&Wlh>*Z|>2Z?AA7#)Ee`z(KG?LeQ@YBS3(bba`3|r=Fb=b2! zX_gGbkZOv1+Rn}Tj1eO<%h75`g?Fq2fM|K+yCkt5W_-C5a(1WNi!w}37*EhRHunI! z!b$TTVS)5ii+|_qHgEbprIc}d6wQZH9cfqa);X9^1^8VaLCX@63Rjw_ZLA~`LC8}^ zn5r35*H0AaFLRpo)IUZc#B8@oOxf4X*j*j=tE^HaL)e4=nHGn&MA|R@=iDzxquZ^@OAB`*JO>Qw#`194ijcWrZ zo}oP+-o0&~{BFO$A+MK^YN5rI^5;Dg@`e(pJ6|7vR9X(SBYXnr#o&7 zs9XQ;9N)zK>a_Ce=RTO4s#~%rM78(MP2*!}!hwIa&y zll{!|9AWBP**gE)(qlKDAKqV{2H9^>ktE;y_@=Oc(Z18qhmL<&4}qvD-o44KAaL~k zf`3~n$Ej$Ar$vvE1(}EEk$0o-+&rFnMyT+)iR9yC!{e%kR}b8@$L>3FdLR6aLqw`Q zGo844Nw`r2eFft#vYao%;aAsv%$h?)DQn-OR|obzlKJvG*#>#rJ&zey=XnMAJ#t__ z|0#YO{RhP3QQsg!`y3!{eNtJdZo`R{<9;`$PdKdwzrWxd@UXf=YHss)=qo6Dh?S|Y zjOnmZPuj;YqZ0q9BLbK1DWSy45q6y)gIn|*?MM7d29kDh+ke)6In6&HT@7k}G!5o%mhEN}cu65Q!ON&uDbRRH z+xc|Q$up>f?!5t>ZZ-DCL6kvz@8Q=oHkO48@_y--sJC(Qn#m876e}ve*5B6$6&5t2 zfem>T)1`}?iYzU9w#{4oLjLNfwIq%AbM4rZM%CcHqNPqPfU_;*8 z;CCDQw}!rU{3lb7{KuJJVQ|5ij02#2!D2RjrWYgX+PYXWbYC{)A~ZSz|J{kog}Ba! zbape|?nN3hF6|)(USQ@v!Wi$_kgD;wfA!vv|0(Rp^4(ynH=f0G5qY@ZIVN)QXBB#sBO8*1!ka36f>Cy+{q_!_)cTn5 zxySlg71!zLrnSEf>VHENi#MMQ`INM`79R6gUlSGyv}3;$RfbIhxfpu1Y1U&HFKBt) zUVQ4$hF}HtiB;hie_OIl?oiC)HoTXp{Kvnx_g~xl|6}eAT*pte&{}Jl*q|hsDX)&o zbnD@=A2*y_0F-lAiSr~fao8fWTQ6;mR;HJ;UAL!SAhoO^S$en8M|*xldZE!ghCD0A zhS;v$nwLm(=XIBVV^LACc1-8oax}cEhCM!6PXL%)DMSGUe^H4_ECg z40v_#jE=ZF6nd4ZS4^4NdFi2^`D>(0LJ|BY79@nc$NbGc8>gN;pE5R3Iz^+eqLsif z+niD%bFK;S^1^?os}3?QVrUP(?!xcOXbz(%7xHG9C3OA%&$6HBiju8q%upo4^rrTA zfrn$pLBD0Kf7rgcF|*Q7Q1Jc4lHJVc3znXWKB7?QBtL*tc7OIbt!1N^zVY*y_`AOs z(*0V?*2!c}k^zp0P1*~HcD&IfBDeh1f7^F}cGGT^e8E;h>qK-e=kce;=Z1&6F z#UB_!TYRdT*vB%KBWc@A!(DLEaZzh z{%tnIqw=2=quee!`R^+ZZeQOS$CH?AoPSqte=S;U74z*i8s@^dodiT};j_+y(51q6 z6IfY}m0f?smhdk%gU7A=3I6I!ya9Opg)8_j+3k_xI5W5IpHxNO!rrxiSo9bjPjmqn zyi@mgeg4;~dEIK*kmOh(BAglrCyNfT?sc&t2XBBNYVP^oZ-o90Wj21S`z(B0tNAZu zAOcj5zwpI|EXF2sz!0>s!GBhcn2aUae{J!O-NYi`MJmxcVC)^%6Mn(|-fJp4 z!#{vFR$D*t+IeXzhZT<=vwDwJOjkpAHLBUyeXNa0zILT+0NS{*x~>?i?~~B)u4%Pg zZ*9FGb5rYcwtQEnrkClZLhIGFPdOD85j4BRo9Uc<2^Kf@x}~zMz8hO)ml4u_d<}i| z)eon<^OZfkg_-KRCJL(~^u@;2s8FqXK?_wO#H3Fq@^N6I*%4L5_*ZF3O2fs)G?ETx zxCm3%j8o2Gu7Ce|GOdeb-~ zdi(_Ge3<62yvu-pVr0tur9TE=CK?hcU>LVxyx$jBko?`R9UoKDRgYU-2*1YaMEq>T zm@zLLnc405uv-(S4clrU(m&EegCZgp3HYp*@rB~pwIbGi1?2w?7oP(3K{?8X_{y|9 z`bJ1)s6}iu^z@}GF>r+`LmKG(1h?#kEKNLI|9r%J;F$&JpA><<*R0)if5FPSegtD? z7O$jk?yIut@7T@iLj24v`NEQNd-ZKY=T^{Nx92+yINfu5W7_A(x2CLh9BGNN=Cix& zG6b*ApjQ%y@n&6(=m!Dld$$t}nM#eUjrLh*7$S5N+|2D=uh-#Tn5?*2Ihq6;611%! zLz$Q6l6yYPS)eBMkNbD>6EOk%UK zz5;}yE;z8zc6CXsCs`{YTkFHsYs74+yezST=WK`x=+U*}8Z!*XXHoRsmNvA5XYLf@ zJGn8I8I7OOGtXye^eYY0uG*xe(FXtlz%5NF111*%-b;0|V2V!mZ}38my(|2q>vn=ZLv% zG1t|*+As{LMs<;}sWsIZqXM&cU`UBaCW*~_>xH-PzO_YyO45R3^}EQg^n{tS6G z-Y)I(~41%x@f2{>)8C76etze{UB#xNR9@&G_UJNn76p!Tmz3D(aIVPc| z&k6Y$2;UL^aqa>cUk#{Yde8icnZO-lLpH#qF?_ji6CsHCa4{P*(FLXhOJJZ^#fyxg zX*!jd-;dNo&e0!Phj69iigeGiz_I~128(k%+gDur{RgWp79IycU=uR|IeN{!+w+k> zIV`@)I6Q)``qAKoIla){!RKF}rNPZt_P|^x*Oyr?W?HN1JdO{L>$YX718$^oAcgGf z*1>{+)I#^BumF=GQ zbj4ogk1X2$xP2CI2wpMEG2SjSPIW)TTDG1KwJj2B`h2*_=Oi{sw2}bMW z9U3a<2LzqQgPb|LXyci)O~Q>ay6)sWPHzfgX-p*9NP4EKX^wfP?jg}TX7#XNfQN-b zpJ)W{yh>Hg8yk{w`o4N@;T7^pZgr^|F4WiuG4>}b5aCKX)Lg%LsYR5{rLcHMT> z$XnNDPh<{X8L4umY;%^prRZ`62uHSWgrL#?V0WJ>DHA%bwslbZ8; zbsjFMF2`v;6LKe(XN)vfcX7eK!`cPW-KI7V@*Rw$m@NrsK3LQb(;7^zYRwpH4uo1w zFdY@xy_(T4sS2G_h==- z-0xU(V?*Y`v2$Z$iv2u^@oC~4^>KY9Su^Yt`4Mb7x!hINEL%G(`!~TIaVH+0GKe5e%Zeq!r2KPK)e)SE+`07h?`>NfI)o!A2oO&;*{%D*P%B#!z1*RH zhRVv#{cTvo^n_2l3?(taziQbMi88shR^jyZ=CpcPknJD&EzRj-2A!e#D=C863lrY{ zWs0WqHOQ~Z2+d2C^6g#x@s$!nqv59V7S9&KvT3Qe3s=|Nilc8P;4}|hD$flJ`l@5wN|rZ$x7IEgP}bs|jfeXWw2kC>kNRbqQ&R*FN!) zH$x?=T4pqdA-_h7K2ALIvEfK7MgCmRVAojciYrMzb?MAU!4i{9?jnZ=DJ6vW-?LM1 zPs2~<5OTeGj#kZ=qoGt2$-Dq;%Y`yO8V!grTxH49>Zl_YFk z${4Kl>%zm@g}#ej5uAiOjB&n(ebBM1HE%G zZ}3JKPZq(xc6_6hS&3_ZHqKMhgp;&7?-!IEGON5`5I7v4T)&E3OhilgAfPA&8NMHg zQ*f<7y4kc0`MG@bt6$nt@;!BYM7{srbETgdrSERzhyC~kF7)%nhnG@PxM>fxK8i;} zp$T2uI$D_)*dxdd>$v1G;bRX;w%IuDKkrkr-9>$5?yB(Fam7|fRV8CD6@pQU%}Zt~ zMxBnv*`p<9CPtSmlD{rsYVZ@Fj(@bhD0$T|;G7-yFO|)ug>ei17)1M_Hl7H5d%*BP zs5b=u&n>N)aNG(D!u21`fLBoBI&fSAH>r-9(pF8F%IoayV>SZ}DTXpHUEIZ2*2Jqi(Ua}z*crgB3ItnE>(j}RRVOs>?8Ladt6=v{`L2`YwM7f}anEm`MBu*CGzgWJ=r1cr@#2F z-x;b37FYaS#G6w1U^fN#Y3?nSBcbfclLwR0GP(MdVPcAAa!@m^d#3kvRfXj(qa18j zv_5q4x!jb#w7-34<<^S%VQw3Lbk^mb zjM~(%+T$AIZ7_9e#ygx0Alr>Os;ga!45M3NqV;7*Q7ZF%fxlOe)Fb%3!irNul@bU! z6-B8JDUL?EioQi{blHA!-s6@%3L_og0p9zPD&51~R>iftdWvR8qQX+kZhH@74_Kam z^?l*YNtIBm??y8|K8BHk8Mi6*HK`s~1NRcbQdKp)E(M>qDl@w;&}MT$tavQZsnK@u zYf?uZCs?$X-<&6=(o!($@E8{EA-v|~6qK^l6q;$&azR5zf4xIuAZ64aX2#^(q^Xw~_5fnhgh7FZ3%rB%XVaQR}N5ZWPd>=}}N? zSchz(IZ-_#>ym_>XMqAzFP3Z`L!IcSN|I(H$rIZ!86@|G6}st3d}C0T`;nKSr(RYd zCcPxKWF*E=7lTR6OQc(4x2vZw^pNI&49)~xRFOF}sl-rfSrISo$g@hZUp!=1PE(^* zRR}w%FWNyBO(podt>S~6O?u_9vG1w&4jRj54M%Jq#6iWM6e042qpUKEnT1ZWX7_W9 zuI6}t!P;7TC|FyJ<~oK@3i`s(*rFdHvs_g7^v0?ywLhBmh|$~e5mxrmzxQg_RqFek zY#BM8;&v~2NL=`FwOK;rSTvl|GRntFJz`(Wjo3StQqwI>g-YgbB-avW0Yr6`N zRf`>ay)j2J%KOP+J#X#cL40^{>5tfw`kurL57#BRRw94&` zZkP7jR6p;vs8|NIeq9M-nUbLMvqZ@_rHpb(-syvj3NnXBq0a=Wd)?F8zvyYYMA1pJ z0-RZ)SGbq<(2e^?pD7KTfd{v!S3QV)hd!nwiF}EISHSox7irv9Ywq zT6K?Ox$PZlqeXIbl9R@3Mm8A2+VW{$C#{5^<`@_uGE$%}Ryq3i^6k-_ay@sC+MF?q zI&EBhJlowR;Bm(2gOuLecbdFLT2EU%lQzxM@HW=`o}GUeGa25C!=cm$TlLBTh2933 zs7C+#5kRgdm!HZpf>H>@dWmMbmpoP4=9$JyN;0L8K|T_`wE9ye#g0nF83CD3(7{-7 zZ`W=lSIj5uSV??G|CxN=tD1aqukvG~C3>8I_g#|u7 zx3(rfD^KH1;n%jau3a_IRpz6g3=3X%&?ksw6RU%k;cj;Gv|y`vuTvkRFTSwGW?DS+ z(s^khKHiP+J+vxaASplI-bz@Z>uUYSjf*ORFc3$8)SX1v;z__HZYktsc zyt$C-GX?tR(7K4go1>Mhb->mnd9(g>t48G{GWb+WJl)N@CPJStLvX5a0e!mv>~6H0 zQr?&}YTvt7_iMxP`Ioe#yoEeJS_L0pGbMYPADtIjvMepX(5vQu=8o3hgzkAA{T|ES z2*2eoX@+a%t-?C>@~69=V+U?vxz7xQ8$bVM)X2GLzUl`mc-1dk($In2cLKfbPpOyx zS)qD@u6-*fU2+s|{wYY0>++^Y_5PYy;DdkEy8q9OpeviK!#%Jxx-jZ+5!AfI1Cm+mq%f(EXs`MR>L1V^!lYUfzm6gdpXTUmN4PE^AQ;pk;cq{fO-Dm!1 zawANt{p@uskX2&awNGraA(xd4RXg^xocN#T`p{(`lkubaFgB#dg>PpnbF0yBbAshX z1{1a7M*=dKTx8$x%o3NW8SgQsVs>i^82DVw8^(=<=)s`|KNXgC?MmhwxA(RTpDaul zF!m5s^U8|dkmtInAbZ(0XXwMzm@GD|LY@WfW`2K@tz1Su#z4_Y3Uqqmj&8L&K{(a z#8$*S58eC#<1|GyQQcf(0?1tK%n)h5M&*iQxE4Dj?|&at;hS}ECct90#p0Hia>7De`}TB|AIIiD2iy1>tMj&_>!nF}8tv3`K$HBdBIa1k&@W{S|f zoC6zSwI6H0$_R2~SmY{5$6){Hz3nf2ET;38&v5spx*x_zgZ;Idt%}^QdcCzM3%*eF zbLzn?0|WS1BGHJ0eXmhQfL*?K-jVvG{pM9jS<~wAdfb6}!Ws`)$eOTc7L^aysU3!c z*#wih4_hkyOK8#YMV-DFzuwxSqW5pwt7|6F^Ih7N+>~B3d&SR{QvH0-(s-SXN6xrO zA$>Sg#&EVl6l70KkD+i(rAtz6er(C=^2e;Q!)BI`L%$$z2v-xyh$OGJAj z*QVO9w(e#LS_J;+U{B*H@1XKPo34?-$H~tM4iLh7Fm-v6sJmZgOhTyh??l_hsrpT1 zl_MYKF2258j{3fT0|h1C{Cd2Gen+ipbf0&Z6ZDjkXBKFD zV1#;5!AJ+nMkcAm9?1ok)`;mI3nmQ+K#xpK16fvY-)3BCZRvZ;@N>r$^eD%>79_}Z z_|M*KZclxSkhbG_)MeLk=Ph!soU*3f&$k_yruqVfmB5mV9D>gxgT+RQgnry|*4ZN2 z20LF5nukC1_5iNg^^6t74Jeoh^DaNI$&q-_IsWNvV!Diaft0C-+~F4M9{E)F)0Z-% zg^iUAFn6kLfYM0nOzVM&gK=~KRNUXMd}EwQ#mTu-o9?W=yHA+^WLqVsw6 z{T+E6goW_6HA~iJQP;?)JEOEl5^T%J!GKg4>8Kqk<|CAia+JpE6gtJr ztI{52NJ;7yrYz=2++0c@sPvlU83~d;|8Yw48m|83R)VDIA1B%x8*#7u<`i5@DS3+C zw{0z6-g1q-kzib#D)0W{Q_=0v&=QMsVQNr~u$-JGw*}U=F!RYbByinbep@U0r;z)L zIe*Jn3#TpPvQ;NgJzq>u565Fo_;jqvO>UM2CC0j~gO)U?AbMn05Qv$FCs?2^eQC8s z^Pp^7Da`K)MSvlx@6i@KcFM<7MvsyatBV)$o;p>NKMTb1h+S_Gg&mtKveuknF{fd3&P`)wO1;T3+Hmu}?Q9sI6V~1@Yy`1SMspJ2>vol6+wBS;nz6}3^Cnu4&rqM&~(ruIo+!I9S ztb9Ec_LLe8~>vlpMdS1p~rIh?(8>;J!T|Vh2JH{}m z`r73eE_I3Q64R+Q6&f>^<1N@Rr-nMBruZrijXKY5zmv5nG!%7abF4D{TeM8JsBFR2 zRH@RB)M&(thyc?3R@rExf+*;$Pb}jmH^zJV4!&7!xNnI)VjT1%P-i&PGFW~j)$QcE zc?ZUmXGRh{P9oB3Uhb*+aIYb$q=S@qXu`+#{;4Vr{+yKg~%-P@{U8+8sV`g3%zN+MCfMpmxXTSvJTR;uWHQi zpbPNk9dFFF3Ao>+j<&NQ^7N^QZA37BZhK-Q6!&^pMW4`6R_V~TcT*cS0Up%RW#ym? znwEet6@NqdEyhC@-sP#8b2FS!#D)m%O%XCe!Df9wnTF`R$UC`#c`ms(1gF#zcep^bX0j134LMJYvo*RqZ0@;cFS)s_PShOl@HXbKbH+?bv3Q8y=Ng456i$zY{ z+W{)O(%*|J8?t)6BsE&fPuFW?~)Bc(1K;H(t}r4cB*wYhl>NC!p!=Q zbsKERh4tl@h`x>G49C4)FJJ{UHHGV&{{)MK;4O44ymA{%kizb5Zn7GjqXx>? zSk9>RQ7@lh7s_9)^k>5Mn5#!xx0bRad!(@DDx2*rY~xoA<;LxeVd{82OTj1jH&&Mm zCWJ80(puL0y{|w*+irVtk$JC$C(CA<(Y#A69~xV3oBP%1VMO!xHhn@mbZJ8_u#J0vIns3|hS!$l@zxLqv7W{iu?i?Jx=}hqBUx z;YD$gU_5|=!<}Q15Yn!GJE4sMPdRAC-^R0H~Bt_?vFTO`|$@cV?p<1 zCP~3vYAfKY`kU3e$J_3qVbU+{x(=d6F+NSb4}g+h^SCa%Q$vPQlL2 zBz|`wQ#&r! zZIGNU)SSh{wN;ji=Nm5>RbY(_2T#e?4{rIB5V7en2^9^;{wqLMsA${}jE^{BP+k>D zUI`v#_=psd+GMZS4*gs!zI=S(2z0=6ZfVY4s<{C72Z^EQ6jXj&zczj3qwG};bb;yI z6i(bY6-%1Aeib3F6<05BWVU?LmVCXv>J+k$T5atgI^gLl%dH%(BmQjJIB#rmyKu=! zJ}xcTEb*Mi>CPOjjp6@>vJ!| z!*s7xc601vbM@=5uibVQ8*30YiNMC89i&~~>4|d9H$Xf?swc5RQMSINSY!UpeR&$7_Q)aQ@ zB)k+7VKTTis%K&FeT}TaD_snf2v2dd>{YFZW5CFw5NJn{OmI)cFJ&LZ5ywv$U3$ z4?bpe<8fZb%kLxpFR~BnKarL);0_`=#GJ4d$m$$nc2`&1fX2)Ni(t=yUjFiBGl9H@ z)Pg~u*|W?n4TK~zlKcj#K%T|Un@b>vhI>PDg`46CoqHjj(<>l(J0NTOGG;}AKMIiL zj_WeZjg(?6Y5f4k3+dNIOFz0IcXQ0OX;i&m4g=@Q@K@43B6!B?W2x)LsxV5vr&FUw zIPmoFJDZL<*VvBRXKxflm8_jKZdbk0Lq6#|L<^eHvC7n0ncvVRQ^jMHT^$-Ug-wo9 zHiCY1?O%v5iS02LmFR^dlU##3RK36iQLq2r|b^Vxez4vmkYt-w^;s8ssqTb=TQJu*I`x~F%tol0C zmza*>o^-l;l9o@UxeK2nX&65#(FrOG=pcJ|9LprG@y{{MNxprNTQj2FnWvhGN48a> z27$XOho?+P3IkxxSMN@)a#Hs>B^QKd)FbEm$N!IDZ;-A;mtv_hHTKB+#H>caXb_ z((Y!iQxacknc%>qUXa-@K-EY?e$3sMD@+=LEt}-oH(TIz2QDwa30jOyO^($FTou}A zx>3TNx*Tz}n8U*QOt?rS^_W$Nn&HFB>1JqE+MVrWx*f@0x_$C$oBUhr zBD28k<^*mH~8&#m% z^KIgjjgcKL_LH098?BGEC1%C0F`tfE=3IByed8Z3-{xRV;=Pn8GkgPEbmT-=gd@~Z z&~W;$nBsYl===l53A}>#yRNs&jm2~(j4nOsB1nsyK(7kRo%zz{@qxgXbAeV#Y0vx< znPmoqmI2{p-F9y9p!8bEl`l%l$}Gv{8z^ZYzR-J5sd|iIPM-1W8E>|Gs(p<@1t!k+LiOU%3Thyvh3aee{vkGmnvpjcS3?(ATCH`e# znILLx-Igun!O;55|71Hxko4P_a;}6mHTdky`!a13{^mTE+7I>ll*53`i45+^d$Q5x z_;jVUR z%99O)imx9+sa$=za)Tu;BS8vcRh3oscyVWSos_Ga_4re=(P|F%*Mpr*O5*w!Z{9mA z_JL#*>Ui#x8o|%D)vDltIp-gpBgJK-Wv4$2J*bH<`1E?QO=?FaSMD%3cMb1EsMC7dMk3S+bZyzS!N#-zhtAzN55K?M1ZeqEbgFK@cG1o5#BIW+-TF^ z%oZ5O|0m3#i@%Jz^}IfY|47&$Nmtbaa4Q7D-sD5k^Vr7S;7z9o9a3!GDRkxw03mu`QLwl0aZtdrQ7y zG+SMMcnwoMLS1|Xy$LL}8tv4oq4GUrHv2sk6 zP1z7Wl~M2rQ&2-&xB?_^f)%@#2-OGP5u9!X&2s{dkC&LCDCf1xy49bs@ogshHdw5- zGu?Mz?*c0VV^*AT3j8ISjAb^Yz1h*FE$xx{D&`8Uk1G##%cJV7j{hGFrH&w0Yr8US zZ0cbMbY+i)SH;xO*XA2#a@Y`ke2JAw3Z~0s8$)zf!nu5WCr5+74tI+rw4YTQ7n9a{ z#}b)q#<(Cv!+m@&T!`6bVh}Iz6HFR)I?0R;$wLg66@9>yiuUm9Rq5k6_gjK=*q~lb z$0AWoeC64Z>{}R~2hMPuN_<;=LrzW?I63YUViiqCuJx5*2)Lz@p>IUW5EHR8ZsIfk zrur~5=|}Fw+~)c--tAs4v$OE1x5|0r89lR8(`KJZ;N0WiHMXui;Owo+PkR)^%XxK~ z`j#u9|2USjcZ?$=S6s&nG^^9b*6B0C_3Mn5g!2tsyYQ90>h=E_PDKAppd^BBT|9X9 zqNMSk;$~sdtAX#uxMUy0n`bmOSam>sd*;6_&iRiZ8v~mwHU=CPHox20yu0xV@`{(^ z5kwjyemtYrC8wfp3n+NgXr785ZW$+F;2=>uhw=Q+ASQ$8TGs` zQ`@T*`!H^Mat&I#^m$G_|38MGK}q*{O12lMAq@?~wQoUmZSTf2lZbxOZ@1QWx0yPb zPX9=(oHP^G;WIuo8-Ve4x(W2`wDj6#k4#7hP8aLk#^>q!8o34VV7{E!V?!34G_#m` z&vkY$LG2^NDy##BP9j1m2ncP|GBdz7OhRtgWGx1=B1Sf;o%BWju8=Zou zbr!`&Fw5n7sGgY+}U8r5A z?UbUV_7bFOR7p|$?wM9q?Ne*3NR^1Cq?SmpY!?s z&iS76TR-proa4AX_kCaY^}5#AWod#YeaIuuxkg7sQ~VbEvN*p#r(Xg09iiG)Vp==d zFl@6cKsf+-3-%nWLe%tDgF}9SR5M2dX&(w6Cg-I=G1a>0-T{Qu1+rU(*%PAONi**%~{g1-%x)!`>)VHSx_$@e)e^Z@Wif%0m+}G=i+YvbPMwM zg`!BTNi7^{Spa=LM4rcV=r_VptLuWN;8XkaO-TRgC=373Z$hi?rzqhBXVtwD5qtJ6 z1td4d-mD1tRw%ajwa!Fy?j}8kFW0f@7I}20lqB~%;SbJ8dz>mAcmRnJQQXriIZR`pMzCy0PxvJ3{-?y5Soq*tIxb zUvBrc1v%#Dhj@ zI7=LeUTsB8693y4HSnj<8#_XCmfbC|s(W5Q$B)w(juOada8Mr3Lv4Y5SB>!;WG7@p z5e(%|+iLCz^^8rBNw5hj@XsYq(?J%6Pt`=-ZeE0J-oeRp=VPIQ6T-WJp^Fc=)pEQt z03HP0gd_r=p$R)eZ%d;o$2Z7mST!(X#ij`!!jFQEzWysjB~Oi`M85X#!gU2Cw+1Js7Q~aD0fB9|(+6Uht04C5(?qZ+?0zeJ_oJ2Hs*N{EkuK- z0+9jUCs8Rp#o#o-Ps@?>_=P<=|CFV8P~v`}YV8R1!T|yK4N%6j0S_$-D%gD)%FFku zYz}U&QvBBn1C(`jgv|Lopu2GKEuAQ@0}9MPE>u#Z@esI1&gRxnf7(X;B$Hr)4{nqY zh|8rCK3`$_3B>TAEs2|u(_pAA4}-)~HZEI!GT0{(HQ2P7uJR?vBK4)^7sx3R*eluYVP zCjI+m;lL#FB!~!4^kuhw8(Kk=^LKG71o=gDQh>D_3Yghkk^A$*0sZ z`VW}`Ti>OP3ESpii4!?g^iOXKyzh)kEUpTN1z+nw7jXo6Nc!DN0@K(VhB`9%pFQ^= z|BIIRkCOOrl*Blv5_Ee;8N!KCKX@>c?3sIFOYhh_(Mk7DYobP|T($nWTxlcqHzT)U zrlJ#WxHN9WfaKKtiEt0c-X9XI9RU%h`{RHpu3RSQFwDxQJQCfXV7Ej}xk|Lg*k?RE zv2^L6wN=T-%Al(H=Cn}*qn_H+=)F&y`b18jW@>%bvt3X!i>fQMGPAf?myyXQFY)N% zF@+9u-JdW1iCOe3Di}X=O^T*M?*rmZkP>5s5iP z@%yFS@*fN3uJkgGFrH<*Bvt?(kGkE?iig|HFMp;gk04c+wr?%tn-5-#K1RK#^U+X0 z(u{rWilobU;AoMK@#@P@>5ZQO-9AP}H_G81Lo})I*ZVCO-fJmevy53grZ$ticb~TX zBgb0-+9s+$94Azf9vd7P255UkTogT}7At6;+)9oXagYkhmlI~1;RW3t`WLED#sd>B zG0a60yp0ZOynj9)Tpn{r=-W@#6u~4=Cfs82$K&~;6oIv>mV6d|{@)LXmPJK$i*$X?OcK zzN{BOW~9f!<;a-L@3(r2{snGdbdnse0)vjOYWNqxZG3t`U~Sb?PEM|ja7{H)LW&}|rSwm$6-O@INlVhenMDf}mhGK4HF3^G3aq1u}k(%2?i;A$)y z|El5IoquV9o1iB60xz^h3Zly9p8ylBTdKitrsI0FBe4p<>jA}R7bqvc&{iNOf&q`Kn|+0<6hYCcSJ{k|59M}($}H$CdMtvCP8+H`mG``oXW z-A_~c@;si-ocC26IDbII#9?njr}FjES*5_XBVN`ER$0ZFD2q4is&DEht76vMbxs?* zJn?vA;C$rl5hX4AeP+(-`DR(VuoUWqjp_KfJ}PkMJwFrV`<#ALV8Dhk2dVMxaACm! zSOe%I_SW^xSd_xiT57(ZLyqH^dXOsmb<2qQ38IfrWu^5e*4~MUkH_XS%>&CV80(S$a_Gt%E`~D^4R51ydFkST)P`~!H4dwJ9qQ=(k(knOU z6Z~3c-VeO>E?51&xHoBMe>=ldUl-3FK^vE-SNUvec-h;|SaJ2v_EpDF?M`H(tjsL8 zlC6`|K$ZCT7o)Q~LPNDY2@Xhw8-|o4kqI%=qz^zx#mJ6OQN|ep8`$673C2T6%kHX)zqW(dqL3VL#Kk|6qw3-zqLr9;ELu;=V=Rmup@9Z zBzV`l6a|lS0rnH~9ihAe#7kZ@QE)qO4u-BMKfigU%!;p7+yN%rrp?a+*Nm+H|l5EtA}>J!$N$ z2;@eIsS1-YZ^}~?D^VKxFO#2 zHON0@)(qw7>iF?4RR9~V5cabu2mT>VF3@vX1IjrJddcxi;PVphf^aSX;Ol|Yp<=Tf zZs_VqDG1eW2^i{W2dH0IpI$QmbJ72~=>K^yn(|>shfviM? z@1o8!h2wn`V%w$S`d8vByo^p)k17&9Q}sU&uaK;;4Gckr_}cltZ(cVizpiP#mcaIV z{PC;D44M%t-M*i({qd}B^npH+)WZ7}Ioih9yT($s6*re|Rrh9@1v9X(W|A>dnJ7yD zpOLR47@fh%nRG~_k3DUwakO(js=XBadi6+k=G>&x=)*-4OETP4r*}BHv^(3Z@0pXy zl1$WB9Hl=`a0thA!5@bZ_8N)tjM>OYR8QqZm~d;s4@{4)v?D!!r_e_eN%=DTILUC^~}H?GaE?If|9b+;e4IBP4~T8o=WBERuRg5v-1BH z2{@Atw1KcL1Xzyz7mYe7IOPt88Go{X@UEhEga)9Hv57S>5b*~N{*ENf&FKXhd_mX( z=t2BdIf8iDM;#eNIBPo{^YWS)bPInpd9a7 zi|X$H_-h{sfEYaePx#d>5HYaW5gMZpx(0Tg{~HWxu%9JEK^WQ*VxGv^5z+-yr!YjH z*dO2t{{o(h-K5yOkfre1g8z#BAjXPmB>ofo&w<#ly3+U;;0Dc?Sg07V*%(O5V z_)YrHh5zTm|G)jhm3~*W=gJNcYE4?RZU%fi8fE#deBOg^zu)*icIAA=-`*TZdfEi} z9G58*`qT1#L^-wn=%#4dNNwtj*IUDGM3!3cs6TP)~ThDm(v*}Xm?4{x& zVEfcmk)q;^0TR*S@WNt*Yh_f;H|DoGuyl$sa~Po?I2Cq8G-dMCnoI< zZ;}60Z`O#-i@4tl+m#g;_Ug$=q4ylCY#S@tVUreywBu_DUe-|v8TN0GNCLGZ755r9*b{*YSzS-HM-&aAE#?bj=E6N`M%@V)O!*;ZMPhKPpUaK#Dmw7!-; zOy3m+!8@6pjc&*g|{M5kNM`Tl^V z4y5B=H>>T-CBymUf@VLlB|-^d#Qyo#nm0*|d%oo?j{vDUpC#MyjTOJ@%a+8!09QIj zo5-PlYq|FXC(RH{m_{KSgpEwp77< zX$e$Au$Rl#Ron{Fki^V0HM$7U`E7@7X4j}8=9s?+bb)$p&1LIdwUPN6cW`y}pnKoj zCN0?g$xn z`@UJsfCut1e6T}mg#@h7gNv$}5W8rC9E1_EL5o&z=biyVIf91n#gCh_-hvP0DXhRJ z0<*UHSN97y;9p zi(?#@c-Bq9>Lx!hjo3iz8s%5xni0H-P53E-;tlVf6U2f%5Xq*K(bRa3Eg6r-VLE6SH5&B3kqU~1w*uE6e zjYi%Nf88;Z3kUT)X|ifZXmWyPa+6a*XM^>)u_QEXF~s>bgU+f1JWyUv(VtM|-k~8A zG5IBnETSuANn$HQ0h^zdf~x5;9G#r(cyiIl=NoGagW*?J@{K1(p!z8~dKnc`_a1ua z4g^>w%kK_1^I4QEe(v`(HsX|>fw)<19GJ+qt^`?O^n$1h*2SMj+$_IKR#3JK@IIUD zp!wWX8J>Lsj2rKvO|;ZX#0ORGcNpPaG<-L9{f<-2^T^0o{o%q-J?oOL)ju*HS9d#P z{^@Nt{dA0N+)#)P%7B%n=Zn2|MaMePI@nXkI5%m+(r>ahz&^sYrviOzasET%Xtq?= zKCJ)Yj0c65c?(W97E>zeC7Y#npAv z*iZh7VROgfhv~;=vZQp~o`}!>s;BqJS;0ilCfP&kiGN;r=F5=`%YIX>ppopd&T({b z(7XBAJO!nJ!w;z+J@%?Q8e2uxB)^;q>oJt_(i+v*>GaV&UU_M0tgUT;%Hpu7$Mse9 zuPQV&Ha0e7-NIq9xHwnG6~*kI5+zTVq&qquI#Rp)rQ3*8EF4&bEzp*4blDMV#Ezx5{YB*D#$&kz(&hlfashMPfz>s&iCIUC z0y&r;ITceB#$=1!VkrAESDTM42^2)8PWls1A^*Bw>!t&lxxOPLjIqQGFN6HXTL6ZZ z7n48edI3XEX{NzHk;*}b&Gr7KLo;EfF$yxg%838V2SVrom_~+&H0}t6A}VEKmoT{5 z1%aq*z8~rXIE-}#{2fV$uV2vQ11BGk93%8&>;$gOyOuDKKzj%qKv8Bymnp26XM@`- z`oZ1Ta)F5*A&Q`d9x7NNIq3_J!u`4jd9uxV6!X`DcXE>gLX|%!)G%y@T)NB7()XYzq z^K7J zc20qc^Ug2_@1Ls0F$oW%9H+@Cmy|tU$9B3(j~N(hZJg2T)~tWB#IlD5A33LYvgT6q zlg7A!{Ys^U9XfX%&bPat*&LCw4vQG6P^z!W%-2@89}cl3jghrw{2h(-zn+!$-7o$y zSnOWvsW@l-+|=}Yc3xL{REw8$9ct_IKK9+IEk5hqTvy>|kluZOe*D#WQ=t@escO5%=K`=+-b;qo`@X|#g0y9j1Bbisz|lE1&2+Qbz1D_iWF=8 zap#ieOtJ5t*Aky;BZ|rx=r)2Zn?Xt26(-RotEqRr-sd_k!X#a<%I} z_93PWZ#X}fOJODLzTY`(n4^K^RLNG!kFT!>L_$5xob!0K*FV+doX!mK)(xHdmU;BK z2!8-IGl_uWhOu|*)T|@v>VjbzZCJtn#!^khc9q&}>Y!cjL>G&yW;FA%DSf|wnf0El zia~D{Ms80RK1ONl=)F!>xnvncObkAe5j8^0v&nNV3dvd-%3S+sp=E7%X0xm-#qQE8 zwB7d~$+=a3gCW#FkA%(}k1IsL3p zs8NB&vc<-Avo)vvedqPMSx=L?Ss$fQyX_AaAMw!hwhDZZzb#!9LPht1Vqra(qqf|a zz2}uzTV0uLib{IfzF=o3w6$%6;VZ9xA~?5#dsVlzvAaNRV)41`5w_2)hU^lwDC~Hv zK#^HcEmMc9@p2V=ujO~h+thRKm0bFAP!f!cs!u3R@yKgCky3npS^I@EQ~GjzTld7R z>Yy_t+nIJCVe(LHhJ%$&>F}qJ5QoOGo~8cR&y2NYtlqz!X6p3zo&yJy7djlWx&%2g zsi%onx9j&*Rj90Mh>LvTvPjCYmrBX%PvK{EjN$6FUt1u;y+8!-HpGr%+lt>iUsS2H zxvX-s`nNb4@zGd>^wRlc&wWqwe$FgX!YcI`+E}<~5-qfpPh~&<*cPUexu9hGu+TaW zwYLqCeJ{@ib?i#3sj1l_x|ay`3;(T}vAI$f=~?2HtsRkyDs_Q<)saF;BwV0E zGb%s-)&A344h{~7uZT1^9Wo$VoamN1k1rA%DEf9F5VVyZ1ZK{kER+P507}_Qvhx%< z1h!Op@6kS#XLBCAM z)_|Tr39QP4;i?>gy8tlHk64ZNLH&-)AZj402%e0)&_9C=gdWY`Xx3^$LfHDM-tB*W z8DAXv)W*mK866sBN2roaI!&mD5Y`0Ep)K{pzuOKVW@wS%wd@PPT?oTmb8ScHCUSfc z(on?i2Q(52v}D1Q2smX$mZ=6sbVOaYiHX|M$u0;_RU~p&8~Ckn56w>x98bH8IYCOv^nGC zL|Hr6W$rma-&~!So0Bza*Y$rC4f)iiNEWy)249cXZ{d1}g|cqz$E1++JU?{_I|puZ zD>f)(e?d4rn&dhKA2zx;Bij>mVVL9fo4|>`CBfCGhcJoA!dBorOjg~{y+`a zeMd+}m-F4(C5Y^^?KrtFp8y+!00;_fX@ignEjI+uxN*%eXCwOoaN8aCd9tA^=I+hl zZU`KYW_<(-94NsgT+YV*{~JCxaDs4qybg0PW(e|?!Nsbjh>(QB&k(7YqX@l$+KMUI zBHr~0%Bw*4Jq9ZRwH@(Ocre@!eO>nm*4RgGcv)YEOLYA7jB~g*7j|8Vc#~_V-X`se##cAbn1zG7$|uZVdJl zo2i_A_=AM9nRQ~K1y1rK2ie~8RU-o|>bawaJkoN&OQ6UepB9y4d(MFM#md}^QSzgQ zi5(K~!%ZSo8chb;Q6R^}P9obE4d4u#DKj))v){c_FFyX2-wIay{o!rT5gCadOg^#G zZS9R&wz}=>8+lsJE-Fv;Xt{^lNT`E1EHhn#&Bau-!o9kD4HCUnPet2{sg(WF#XL~- z)Z@F3+YK>pmsK$FpjVKng?e_Ac|Oi*#NSd!BH?Yj*d_6^T1uCED_$%}Seqn8MZO+x zQ5Ot3#bM$Nyrvs_)T7Qbw%R$X9WsHPPWDfxDc^-Hw90+UQ+*lr6Z2%jnGw&}t9Tq! zBGtfjk+9>-|~?JMLN{n~o9> zUj97tF#Kp;(QzZYz9&Khp}f&E=}q*F>l?|jnh&2xbjH-LGlag zf}*jNWp2a>TKi<7(xQ59L5#~UoK*iOb``LFi+$qR?jCwK677C4PU<9P+{ngAiF=je z?6XXbZL{*W!=LAjOd#6{d)eC5K|0t>(lxQQJU`19?a))~7CjWDUIR@A_O-~vDC~i) z8+xP%>b?eAGjdW(dPOe%(saW|&q^oSWzpKtODueXPQIxF6g)>&rq8{Tm34e18-)G^ zm5|;}YR6tWvaPCMjne6?s4(*nO~n30-TpcH+FH~FF>R`JpN!3kuJ@HD`kfvlV_w;_ zBh|jR`ql=w*B{D8Z00lO;_SRyyaONRXkJT8)rNfCba^^0A$#g5=$y1dM3*?mDO`0n zrZq=Sbmf)LX1+~li#9t1kLm809WNSPMUB)UMZ+*Q8P`S(l--XzvcIPNgsnarafQN4 zcWEMA=(g&Zak=Yyp-%DiaLOH0My9-3s(j8awOg5%CLeT&^pJ;(+26G30C`L2wD3i_}kAoFjp$ybN7ABQbJh|z zO$eK~7?T=7`g`c|yAb|yoX%i>^1(L?v@P4;zk<7hXRQf-HIN_I=$e3A=*soMTc|ri z9;|MGUf{;}?jtdV=hyc^>SuXtxK}X(QVR|o>Z^qOf7SOcY=GgjZ{%@Ytu*?N1vvN~ za*5~}-gTM)c!ueDLpfUgY+fY+*O%q?L#Q9;z&kkf0++%_+c1ee(p+Gbr${KMB z?2lBPm=NTGiPs-T*A$lv|J=y14x#6s=bjP_xs#?>(@Yj-xJv`ISSbuvFKs2sQ}z}n zXErBFRsoooif7RHPoX6-@$1vc;GLei?63i6O$fqWwyT=!w;h8P-Pw^#X@0cIx;b-) zEknD_i~MIF4ic6N`1*mwhOVy+qn>n9d(9$fJDA`$$0h<4D!2vZoEL(3%ffILG$y}c@#Cye z!9i_}-vy~@)8#alhoyk=jAG2^t(HkaTMV8yMiO0ai$1>rL4M;-*?u5}{L4q*PW9QmX~wN-V0@A_Sh0s8DacQ0Vp+G8h`|cVcCEcb@%167 zly=(;mZ|B;e6n=j-IeTS(eml1HtqiEd$19icZO3Aj2%QpT{}}}Tj3$ObSAl4JUZ_V z*DUJ?3){dHwC1L-5=Z{h`<8Zu+pKAE+BWu$7WMp>WP4NzXU6T2^MTJU7zsp zhci~VZ3T)<(FD!tOFoxiVNG7eu!}5VHo}KWca6s9*QajILv^}r+Q@1Z31AK)OxFVp ztiPQ8+;6Ehqi4GaQY403J+I4_A3vFQWN2gK%rT2=J$**0Fn-3Ss$HD=4~6;EQNPL| z*16U`kpzru^86`~xl_)6+WwU9sJOB;rc+e`3=p)+eZ^CM%@kd3DE6M($A19^7ZCJl zp2jRZ-f2%liB~Qr&R{n+ZI3J4-1-g4!FnW0l*$DThT$&1$3DN|cXu=2{2~MTa5M6A zl*Q335C5SD{n^LQbj+Sw*L#A=o)4O(yF0yFPzgSJWT|}UI_N$3zEV``%K{rY?en7Z zN(K&)_irxe?8+W0Yaj8RUKgCq_q;6rOZzZt?+ha>?|i$(D66)O>Sm1H7km>fm33$4 z%xH1-zD$J-?x{(y@1l4`TQv(w!{FjN;xEaEcD1foByc$^L7#09c^r@O! zG&)TJSuyHcEX24lbG_7jkHGVC^yB9>ysHoJ4 zhso}=Li!KT?4qK_1B~Eu+qhF`DXpcMXslUS5VAJE$ZW6inRw!?ta(c3t(nBm>FOiQ zV=Wd5LEh9^8xd)HiHEgjNA8Svnv4&M=gh2Yl)F{y#(HZNs^7ihXjZ(SAqgfHfSDif zKaB8PehYZpv9BNuyC40`^!vqRnJbrM(%D1BH>3*S;y_zI0Va-HIX`x?jE=Yne@o|$FAIU zxFAQqGHz*^ay5$1$UVk`aSg({XGDLA0%lg@fg|byH8!F<_nqEWhvBXnkcEyNUBZUh z4WHJeL8aKp8;q5NP%xPyn&4Z7r48dsbAs_c=2-B=dS1EZ+ZF$S#Sv*4yX% z8SHYJSW02`E8njroiZ0~QWeE)L@q)1KF%^56xCw}umlsCDg!A+n`v#}C>v>}N#Z7# zk_?e-`GyyoY z9w=_zrBh*jR?L=cg7n=ORWDghM(}DEt#O`+CFh%wuM7p>Fz}G!vL|}GXX23U6^6%M z8TFMHs*Rre11<8}owt-foh^f%1u6O0y;b7nZzw*@-^Xn2H1?lc$dr+}LlINq^_I`s zSWUN1uf3CTPDDx0X)-cZ?{#`n7ZQt3Pv|E5e=z+tjI6<^RHH1>W0?iYCTgksG|Syk z^gKoT?)cmM=S@J_=K^Ksqvs6ItIQ3-_x#d2pIw)RS9w|`>`qO$OPxnq+MU2a_n|Exdgxux(8?vEnU$N-E{2?jpGw~R>s97=EGR}19;h`DGc>XUm zLC-1?XQ-cdQ>?WM%y0bFRJq*tziX>p2X2S#!f+J{mLE4({C|N8AJ6&X^t(n>WjhI+ zR>8!iwZldv#TOhZ32_OgfrVObM$b5MgAdNG@>)fpTpz%oibrxKN4>%ay^gk`^MmX2 z61)Wqav!2ZWi1c? zCeLdK20KYm5i$035BHssE#$H9hIWZ2nBe@5(cl=__|%3TGTq}C^7Y$G zIp?;{O3wQLpY@MyF5z6FPXlU9lx&Rh0Lq6MgIrrmUs=Wd)DmhC$~B0HNS@slD_AJq z#s0DD$D5WEDA$$Z1LqzO zXpc~65kwyzF~ZDmV@AQ+mG#B|8c&o#vtzQFf@EaPk2}O(Wr{x5QnRtkM!3iuzc)}W zT#`LW+RV3KIu0xISq_?%H*b9NYp37on%|y~VHf=a{ST-Y`1iKx(_cor=2v21l?E+? zmZGOCQmRU==95^#6_&xeMV9%}hRUQ$>*Acc^kijXX7h($>zKJ)bHj!Zs_-(ojHiP= zLxUeO5{I*K%d%o4VlypqR5iFYlUL0qLWi&^Mwq`dvabdv1I3(z!RhZTc;Uinv=q$%GExoPxV{i%R0$(1d98BQ?X=`pb?R97 zk=j>LvdppAX$=vc`Dl}NsbiQ%XYT6bCheC9u^;;maJu7&*D~zupY4mUYaNwO?J|En zY-kKqy9HWWxkkm=6<`viD!MXySuoceLp%9gy~HA^D7G-I?2>;Q72!~5R%D^*aIg&j zBt4>=U3Rh0^o^ZWKu}5H5(!!32S#`$wafu=p4O<=VvD$A)E$WED+8;0Ee1(5laxm< znaTK}VZRN8j1kr1e7m(#W$7YIs(Ez7z8>f2;}>%4XDgji^9)`fNLx zJ8c;%-zzz%6E>C-C2p49SC|`ZE&iz*v0HEdk)=poVv4TWk>h1Lm+C$h9SbOPENU-d z2U0uZuWHMF$2`$WH9ua#|F-|d(j}Ql3oI_Og4VAXom*1o;d2KAiqg;OYBTi?>7F-Y z&rw+2Tf{}m>X0A7bLeCkn6r6I_hg z)twsjuwp(N`!iTN@C$x?B@2IJbpGjF=8_vTXu7;yz0yf*)>0g1o^GA)eZJ8lWxJrq z&_V_}O-g=+8dJN;$i`NANhv0(8%U_*Ua~`B&5Y5pW<)vQaS{Dts;8J-*wEbaUCDDd zkO;iY%oF$cL#Mw|oG3lcBm87#?V>MVg5sn3dLh%@P4DBIh)s7BvD2y@)+m>utYDjQ zrAtK*n#wJEze~3}o}Tm|qq>kf_zU5%ff$t{#g=elT&Op=IPK*JnK@HO)O?=rM6!4= zIeu!?cU6;&NV+h5>QHvljdS^WOW&PJq~1t4eR{?*k~TFq$Rt|0V2N{84Sr(jwYv_n08ooE-Z|` zk#BOj7C#y@>Ko)AlfG(OiyD1HLzu>BJ8ANA$*5&NqodvRE#;qYADNHCB-y>zQnZ%s z6G4UbYW;AjNu_mSWXTGxlGii5aE;l{*y?QSHXen>8C=yQ9GX(kpP^sEpwP~uKS6s8y{B00kYf%)5{l!6J#`FNnimv@p+G+zhlk1Y|~pX z(A1!0F;rUU6pB+&HdHZfKH{cq+K8(4ps?*?jJWDWV94WJ#pIaIq7N`{Ja(MXg=M*@ zUTc0h>A2(say7fH-My<$AFWdJQ@<9nPJWcF>uOMgB+F6Y3qE=<3}r~|mIKT3Y*(=W zoj8a3cBD+BVr(95iO4;lS9#}>)u<`TUv9ifA4sa2}V zXMudpNS%ufoKcSqcRN}ZJyU_`H| zu|AjF&uSc|RrTr>y(}2s6!xDS%Oex17Toj|3K_Plr!7Xt2UIHji})3-brB3p4H~ z|ME5;&1!C$0Mh2NStLgN&{4U*RXlHw&gS7D(U9r*_uu}&FUdS`8yah|jp>B`2oYTz ziU~a1!AG3UTa9r(dQ=;}2@-1a8d)?{Rm|dy7Q{3Ac14R(23X|~woTCJgK29_P0T}X z{ACPYE^xOHIewXyAWIPDg+SKsM>?}{chwbG?PEZ&t~B`4wxs>|4{iY)?*^%ZT*HXj zlJG8|P-pgisC?B37fz4Kqj5-)FdqnP36P71_Xr?4T3|TO5AwZWKzc41GDuV%@gK?C z{7v`B7mEc#8z!r&iF05-atJx#_ukhQz^6vQL#xvB%=6@kr5Xwc=+Q58;b=Vpc4~|a z9pl80@{qIJfne9tsr5C(+8OO-+N@l%vDgfG@SxWj7j}7jYrTW3;)riuAA%piZJ$zL zI=^=^tbbOj`Fc@GG*0U{|MpyIYOAm2)5(;s3a4Fdji^RkNqqzaEM@ zMAg_Ib$i6`w4c_o7i@iX!mMdf1w#3RshllNR;C7DZ+~93i`i;$QT$zchnd2m0l&t1 z5*_A>cf?jra#Y8EOWYQTuu~pxw=H@$a4&N^VDxcE@1a;z?o~a_xfX?T>$J!V4Pj3O z$HOpIL#x=wJQ=Dg{p&7)p{n($JVSsAE_PfBusC3~h{Lt@ELW~hg!{~<4pTiE!iyiO zB*+}C;KGY?ffV|ddNz6G{ah+6DQMnF^6q7{rFY&^SBmRZTOWnJwy95^2bgEo5ktmU z`t$s!JOIseW8mKM&D-r=kFoDl=?-lZu4#T1DvlqHHQmW}H^ygN$JM`)y9MBiC321q zIUk2T#@wAtqhG)sU6doAHSN))hrT4uuMwjgW!7eR-CU?>rhRNta>q!!HCl?8juwpm zwrJWnwPiS0Ohxr6^{NwNt#i8MYF)82`C`|=Asgjk`>D6@m$FNFMi%BX1!(QZ{W-}; z$Ab~@{Zn2!PDVMEmw{;S8dopGQEKkhg``1T9)y zRid5Uq^1&F2op^$vN_C143omD>QF!GW*sBuDcjpATCN<8O&<9EuIzuTIW5`cL&w`qdKYQ4#iX_l*h8;0)288+`v z-jeTR8FXd??NmHF=&7{Uo-JJywW+Z0-qymm&{UAB#fxg==2nW_gXq3CruEZ%&A!@(gIQue(d8HKr5yGeTUGlQSaG7QCf*Kg!1=he7_;9GKjw<}O@%J= z;0k(3hmJMd@DBL|emv@wnh@@#&>hFqLb-Tz9+jcH0q;lK9=N*5=yUpKPvYxQYW2_1ueM0P!gSW- z#!Q!TqA8QLq}%B&jrlsxY3DlZvh*acU8;~)i1IvLHiA}mE49fo%laPGyU;I*wJ>=$ z@;pT^H1DPU%+_0dLx^cY zH?dZUp@*<|(y_{QQIa1{G3T-oP3$)KYc$jb6ZK67LmjA3pK1{-N z%)p|OZMio{M!SndIjA0WrO4d9YEfch6NRcA)1;f5Yuj_O(*VCvZ*Z2apZtxf{vT=* z;n;4q8m+uj0ySrwQJ6(gByETjH1tR#e*LtJN>+tcMxn_ugX0xu-dOF&$?mE|a0;Le zT1$zmm&nLOszmi(*Xa=)3L6jhGSCkMEh0n}wlb~*e`M;&n1sUxZQQRnyfRvzoqEn{ zM+BF;!W?gy4M|pM_`Y~B-ruo!90K-aOvFS^Q38g?%m>Xs4?vfjg1otM<+o;?3~E`H zaUOOmH4o)AJU#R+10J4O&O|93A$y5ef#bO`rbNvY>(w^d6GTlPUmKqn-r7cTBbA!{ zu2IO1=o2e29eVKI(60?D6tgYJuXR;0To*Nv%E%>@)LhTaf0noQ(bmlpd+ovrO`4ID zouXNJildKrKvtFss`BWL(0&Ot+i{lc;?(yeo}rzrhMlwwudJgVo^96~(Q7FT^!rBJ z;Q~wVZoO)Fs9|$uC-xol;{G0Kv3AYphiy-TP)O{cw=_2{*V|)@V$yEg^j1G9+jp*& zT4Z0G=VoTBs#2St?4i{5JTWiNU8&bpvG3TaF;qTm#_zGFpRkueyE*Y2qB_r!Dz}T` zh^*^=#SZNNnZ$>KRrAkxIQfj`o%0%o-&kTgdtH;VYvjsezFqST>LZ8cu(~P(Lr6s% zK&*vVN%@vam5pom;evDiW_e64MtXcp`B2C86y@H0?||$6fhl$>J{M3u_UgkZojh&U zy#@I)LCiDPqSWGrs*As&!Lm%y$HJK3eO2eM=yMQSu?@i4VqkR+2;hIS6d+hmAcp2b z_o?IimHzLV34&(A^fOv5>wC5&a0er z(hXtmC)4ikRse%Hp@fAk;M{PdL*Mewq`ufJkyB8pxC$$L{14>|N{xSowsYt9pkW|?t(d{)9uSP`6{S+r$f zTH|}sn5M4@V2gRZ8mpiM(10z*;Aq&{b%MQs23Oly+um4Zo_r5@rh##_yshoX;6a0e z4NwBYw`uF^I3I|}jc6wpv3J5}bZcBa$rxmO^Jnh|UV742=myyZPKJq@ zk6~er(fZejn|z0hIoID;u3}b_L&oem0#q?M%&JQLr1>3+E3~dpq%8MN9qPHUwSVxD z@IrlE%^bZneAR}<1ZfBnGFlaR?4&ZWG6iMTmC-pL##7MClJ;uTD!;x^g1SH#x3=Y{ zoga#X^_04Ff6!W;$YbZsDn8)Zl{>RnAP)pbfH<%qs!h3gQ4dTg03PAf8oR=Pc5Tk1 z3``hcN90{@6dYF5_+RY3XH?T!_cn~s{}9-{-m4;v?t$ zPWCywoW1wCF4F-;9F9?!6dwAT;PsI-v>J{GyS+(hpsq3tnbNHZNP~CR*;U}Zlv-4jb4L^e(rIS^egm& zJv~n}44QXs4ljf$T801MuEkG+-eRfdI=F5wr7?^qCjuU7RBd6kizK`#kDCij4G1ZY zXV_k12t;FDnN_)+(;ZM{moas5y`gh-eTw;&4AFDb-|)>k^7a(N{1@?O zuU|&yKP(vC@gUzEp%64@>W3`jRvBdLpFuzC#TdOlpsN;BG%5H43(p9x$L|l@D;TAC zxlVePq^8@l6blds6y=UKSNH^>jcVzE%|SH~saG_fLLEbMx&#`}Q9_Q(Ci#1X=hBd5 zBt3b~<~c*%0kcV&(U{0Woytqk*ObT_&x!wxIYurCOJ{6{IlR=G0!rl)hQ$f3bT)(>S1N;$gRT#aiJ9?(Wfak@|{ zAd>ESPPZLjZ-5CAeCfpT#jMGZ@UYY4a%UT}p+x?sLJ3e7X!T_sY*H59*D8?BJAyO3 zcs}`>J+(YwrQNYqV_Iu2F01lL)`nN7S4bzKc0JTDp@!__Td;QG2L02eH@ykQ_uIYq z1ziz3eIPt_pOCi@ufiy~B;ld1aBW7@g)`{I_u9UfHjiHAR)h(Mnb!eQQ0yZ$|E}B# z<2|f_mGd}y!gE)=Aa3E}I5eS7!c{!*A+CK1f`ARtsW$kygL7j!`#%mlt*wbGGyeJn;19cMYg|6?Dn;16K=YD`8Bk|%+TUO zFV}3Iwx52lW|Ctu>E8D0;gu`jpWgF0{Prs0Y^U^N7O)?eX0rJkcu%nE7(2b(GAy8LO7mk?ZfebkN|HQ3pjNbF4(=cG&)wQ2l;iy&L(<7h+$RJO>m+vJ-b$ zWYZMU%9h+P`J;ky4+_|QvvX$ohHa1t=+SK zQA=`*B6~%PKdgZXm31l`y`y)IxpJf@Fu$@2FJ(7cl5W=0PwgE(hw>Znj;^VtUTc5u zc5r!UoF|o%-ovJh^E^{f*R?7VnxL4AL$|s+-3-U6Mi}l>xP{xTJ#kJK+zOOt){-YJ z3sZM!0JdYzqKMw~2y70mN;zqgW*}$GyiH%K4~Jf=Fi_FZd=<2Qk@b8W|7hm~Lj7l@ zl=8RTMb~@$fA+JF=;}jQZl-h=nYam;FL!z)GP+Kdaj6LKfW^)1Iozskp*5vO2-R(pb!C(`b(?RFnsyqx$Dd%H%vU~t4>%-(Ts=g48L zLiDwuSMB=Z{{4CJ9t8%6wsulO?WLeD(%gP2|B9wHhpHi4#EJ1oEjMHsQdb5I4!vC~ zIdFQ%)#N*@8jXTp6tpB$w%>5P;G@1szVVQbMya{exg2T*ybRkI?6t6Ojy+-Z2)C0G z4H41cpG-`!gl(@8p|4Ls9@|dHXE?i!jU&fkyI2FVFd<#~++53aaF`y3RjbW-^oFmb9$3^GKDhD<56^Gkgu#$}{V-u26nD*&v@MgV7s1d*!(7 z8EXyUgCbV6-}2rLvqDtRMiu?J+m%g=r7Th9X*J2$r@XFk3pKXn(S8fUjj^ipM-j^4 z8C?bE7(_t6Ec{TA$ap!5e$bxofD3;Urql;%_R*c#ZD18yU+S8@he)|V_i;6cH8OTQHU`AsI zv_4-0F=lA?p?40?uVGKmv8d}U!|tX^o*K}weXSe*k|o$AqII}}A$_k#i9Zr&Xl2U}()?*Ldf(ehYd~PQ*F&}}k?vZtDv^H==;*Nj-GDBVtr@Q7O+>4BHWgZ39BHs z&k*rVNxF+@&R!eA!@SwiV63L~+d;&^SyGKDqP#mOfCKm1_Z zRIB>+lEiY6J-oTIs@{}EYXbYHCL}c<)oR5Inn`r<-dq)(nO0dP{OEz*&vpXOayK|^ zcP=!agIn{G#i$2XWQ4fapbTC^25BS-pO6qxgw&N5(`x+9Hyu7COngqAA#N^~m8XFk z<>yT|Ptuz0ro*PnJY;Y(TQj(o*6oCPHc8ajTaE(tArxh0gW10czq(nGGLTh0)LEu=81UjXwOcScfv;mm? zlZn~u3eWUQJ&ds3~JF>A=2wB~WPp8Hy`0UKwN~15e9jo1W^tjD$`1(X8j9y9Umr$`m`B?Uf5g|;V{VRM{8zTLEQV;v>gh7Zp={dgyzz@|E-c#EAPgWXM#0ODYBL1Qx;ZBc zILApGi<9d_rs54bRQsinEFqIwnJh3<>hVQIdALce`L@X`Q%ba{u@VQ|51}!a^@C zvmlfr&U2G-Sgn=cAoGOoefZnuYSE5g}YH;=N7O|HbP;k`{sV&NgbNj;Bhh% z@a_KoH|HzON(Y8N2Do>&(SsLUCqVP}&s@`;2ipyHN7j$mrr0?M7awr6I3%U9TMd`r z5~81LZJrsNU!;9-B?}2kH11jIX7tqBaR~n$WZ%3v+ngo#Q^lkCG7s5$Gg`I)OV@*; zW{E~loa=4`1=U3qEgyKZuBrWyN8(m-~(K8-R% zOgB^qTwFG?Bj;ZEIhGc93ED&n2R*Co`#8REL)<#hO7QNr$U?CeC5C_&Qzp)}!51{f z_1Z1#E9449w;*xwM$G_wqs11|M0sryB4R)5W6mg|B3q)IZvVDVO9t6+T0Zp{-GHOb zNQ}I#RA?|FXj;p_CuoShW|6(hL&;<&wzx^Wz8w-;yofhMHj?Ya{bz&M?)+=DA* z)tF4c_Ooo-$1TzxC0!;u@je>FhO4tr1vd-0vWd8sZLw z)~xNs%M1#+M%j&iw6X3my-#u%@iVbl(fEGEDLU#=ahO>yE7F7&9q6o8J@G8;xIaQd zfY1;j(O1MVoA3yrm@#nE>HcE=3>P8O#F`<8PCXl0ZD=^UW07pDG{EVU)pwhDCgVpx z_b|f!^hei$S8rD~_xXO@+dG|bRsG9rEY`8a`W@Ijpap@!V?$6o#uBS-8VSL#Qlh&Z?q!sf@S zym*(J>%!~l=g|I0?=!Y-T_@dw4MHk#SxN9}w{zHA*$E~+BgzG;Z(I~@9v}#!Luvai zwVdP{Gf%#D5?f+s)voz(+M9a@x6~IsL3AjbIEw>=sM;U8&{$-FJaRa8GlMq|KmS3sb`YQRUU4dm_3gzbW*e-CVSGx1e{rzS?FDLe; z@j)Af9Q$^$XU?q0rgzY>O`&Rgaw0J<;w#BCtAgQ^7d(B|C_hE?!+t(oO&>^n-O3PT z>mbEAKlaZLP3+FJw1G!?YKhO1QIR<80$@Wt!AhHGj0Hcd{MG7McjDNB_)xzcV>=jr8ttfz6skx_QNpGl}PR{pf?wj^hlJCp!Y_(p7gVxP}Bw|)s)~ZL7S?l%XO%)9( z{k0Oqu~M4@vU=ks7I(n=@|Np`#3Xc7hm^K&k(|E&iB^Sazk;sUuWO6m&O~u;v#{e! zkKnu4qD$(Yf~s*VN*Jth@Y=W>b%ng+P*3)|eAmKaXVLTaAGND}^=*}V2HV<98KrR+ zhi?5)aV&XMRC>EGzDR>S+fsG+lyZ;>tuL2EM*?D>QU zr^G9C9Wv;17)2rpGecI?@QZV!RyF}kUguW(gSFBwf2#)nZE{6?xg7c|k$_$sar1zF z%^$D!f^%Q7TD$7~iC2Oj%Wx}Y%Cb(NMxt8_H^NL96&nky60)Rr_fWe-GDywab^zn^ zl>IHFF1=uCeE5~-TR+n9RLPeCw%Mfi-Ly$qi|Xktiip_{v}T5Ua_VDyZD72AT` z-*pyQI6atkkRv**KLVHpJt;evHff=dhu+5_D9w&s%03RS!-Lq4Ph*7*vCj^n3+{bb zpZUUfep2jU6)A&T$z8;EeBs+TxW0rKM2BWW`or)r-bJ*%#Tt43>x)(5##VwuxFGw* zX7-Tr!pPb}S#}*<&_ij6U>)5b3MKV$WO)K3Ucz-xpPtk^YIU*RDqjCLfnff_%iGRu z+wpn-`WxOz1$nLeFjV{&3vLukb$*Ff-SoXWX3gVl=Wc842# zpqY!COtLW?8eN{gFjJ(x=Fq;jirfJnkyqF@BTF@_Lf#w_j6kkRo_A;QyB35AZse)VUXTGyzsccTkJYVj0pC8H!zj@pQOsGSh8zd<_l-;`xnAvIq~T z-VY#x{CK~vkWd$Svs&nKB0DfV)I%DZ);MGsurlz={{gbTZn%T2YU5Y_@VH}O+b`Nr zO7A!QE)v*z4`uoyYXk)Oo(oF>Y-11j^7>kNr)KLKC^*blAv{)zDROG-yoJG&y(Ted zb;NQUdr~bN6D*S7CAF34I=8Lhp6xq7Wbk)+9oek#P+SR|t81$(;33tULDyjRh^Dy! zKOVLzDEj-zK;i;n$)5*+Hh-t!LmfKRhqVf)z)meosLf%ENseE^T2Q}BhOEpYNBH}g zWTfX3*5mcsGAq=rcr>I*t8g%)E^!=A51<8MnJyK^7F+Kv5qOa;XO#VO*uLx$03d{MqK&EwX}Cf54e;d6tAefBx^Vb?%MI4KRub2xIiVJRiuVFKtFb$7 z^Yhu0qnxJ=7qPc5&ZQw)9zsadu^kZv{zS{32{$b;Ev*-pT4%YA_d+Zgg+&gBUmoyUvJ zg)PPllqKY60_atXp7By&aR*x8OEZjs-#{#f;&MNM>|*_FxbNMdxzl~9Cc&#}a*&~F z03Y_b6Uvn~RNNRLcm1+vPoY*IODW5t_RDCd3rMfrg|U@yL=sef?@~ZBm)7yiF0}w$ zb)_O}1NVL7khTl`@3GA5t6G&GH2g*9j3@ZtNC+n`jHH0gxRu6B%1aMhTyWX*|C*5U zxz9n)>AE1V^V9W}t}|!jPI9ZcOPuFRNIGX>Cc@+5Tpb+sh3^;20NdYFmitUou=tLk zgpOJg>)sr{cFuq7uU26-_uLXQlxK{nGv@)XgNEz!cODu9)fj3%k@C*(-zaeiIf`l^Rp{SME&())$4*N2gC-)!fisiv!+*1)x4^Sk27_WM$-~6j&*2 zkT$;sXngf9$>0_p?}xAoB-qm=%_SZrt7fV5&38|vzDe=KG#}L0JCM??_U^Z~-;0Yd zoMRG0$#M6^?gWp;$;4_7Ip90`Yj_fhcuF8bCAuF|&^ua%{9G%}QN&SBdAq)~&A)s& z;efQ1WBk$Krd>&8H!@sz=pFklCws@6l_4(X3ttTo8chQ&LvmG@_^%f)wyNPCX``cY=vh5r;Gv@o1Z#iSuiJ!2Qk)4~e>zq9|4 zIV;}wZtyw+LHCAL)xjWX&CrA4u(T=oTY4wl*BDgq+Q~v z#M_EiJVPiA{pu57#QcRdK8D)wiW28Rsbo?#F6-PX$dM1cc;eue_ExS1g<++c46J6* zRBztZ4s{OAb3d1+FhZ`OJC90CxJqmDoF+KO{f!^{BArvFmE6o*%nS7amG%+bNn+tr zgGrW}Z)8;<;<*xLek8H&Y)6HN2I-)wip@&AezAA*N%Kr+;godmSX)0zmE{#spl0df zAe;Q7{(!aX8ZU=uozU2?6D0b%qLaOg&3mwc=k0NVZRoBo>!xtiK-%Snh86E;u+P71 z@!&6z4VrI&x7S(+?YAzRGned3Nz?T+B|^1P;a@2Z0cyx@7U5&h>Kt~;4>D*CypqB* z6lbq32$I4%n@m6#@;`sPlqt5(-M)h49Bx4_C(=GgvY}$&xpNTq+t}a2Irv|}v$0SU zu$=HE-VX%U%}3QP*v?3TeBcBcSAPguSu0u!y23 zy2DllYiNKxx|FNSDUpaSh@+qW=rVtHX>P%LkNixBv%I?u__x7_*&7k} zK;tko2P}q7;rxW6f7}~DfM@V$H<|vqWaUYiv@Wp#pVEzVb`s$SWdi?o==t?! z?pEI31zp}fpj$g(<4=z)WaG~XYCg*S*K)mDB8(Ez7D(9_ytWqT#skn}F&0DN#4gQ{ zDysh6r4=k^lduL^ZVHerIQyYh3iXzNpEb?^ZfqL}Et8S(W01ih!sgayO40k4!*MeF?b_<0KXwZAz;1y z{(DL_KtyCu7#T`>Npg zg8_cVDUc1l99tg3`J}--lJ>79!e#{z{4rV(pEe8m<7@x} z`r+CazFkj5Zm(^;4W8GA{<-SLh_lOn;cA5&_WVrC)5wnF(jdaI1a23O)T*r|e&4Aq zE%Y$4g+160(>3m3E(7Y-_iy z>&{_tr%ME$d;1FMy(!M%e^lm$8@3Vin8>EvibeG7Zt{*89KQuo$hq4oGf97zDP7)t z)!b4hMgMF%+6b*4C)2WT%E;Sppp;sw#eQ%nh5p_SWa&)P%CP)DnVyM!8h46pIzTF~ z4e^XMzMvDEdh0NsUbFWt^Eu1*lGS|eE{vV9OqPn=jH#cD4tNxNQn7;8-MU}Q zSkPgwRq||qoK>*?KE1FZSaMUhlC*_yuKRDLCypP)oZIZ07CwASJ=QkEbx{tjv*CQ- zH78x#Qcp;2FZ?4`5T)G)mF`^3%6cnWUYwPco^z{OsRNZ$$s)FuJT9XLWsG1>SRjnG zY{Q79vmjCh`YcHfpD}{iFP<);ed_I3%f}=xsd@8a zVPPRn>T#J#t;O>Ne=*h2>v&l8uRs$$*8X&CSi<6`uZn2QXL&G%&fZtbK6;m{%-aTY;cFZ z+FM{lpM}rxe&|w1k|b4CXVqNZsQ=qiv2ur}i}jK%s(Af5ZTY zFMLM8pK3+G<-Q6_7rY-LCazv%PY(PteDia*W}vgxUwv`tPa9nF*I?=Y=gKP-6lm&r zz;9^U>25)J+O7^Gg#(vVk3Ct}nbzbxHvPBo<6l1+#E16afsQR{16DjDNX0S*=)8(? z)S&XXaMFatQPXamaY<02Svb_Xq>|`=;uLO4G6-$od$XFjbR=rA%b-)*w4_#eU7^|g zP~gv{m!59Z=4FlVP>F->b`~n}E4bREyECM$Ag|zj7e+5vrCOyuRd2AcaFE==b&<+d z6LZ*+a#=rfT>In&{ocgd<4MNm^8<;iurJ-bQVp=KpTv9o;KHoJ#wz~C5f}jO!au?x zcbzMofIotcht}$Y6l_h}m410S#dzfpJo#lm)|z@~wZSzn|DB=jPi~XsoObOSs=*6B z_ihn{woE7(>{J5}ar_F5EGD(pe(QBV&9tzM3AF2MVOI5aFc@OZG6&FlX31HqK6;fW z*zKXRx9b9DdkDzJHssth7C2>s$AP8%%0Y`VN&z-&@BW=2nid3&;s7A813$x3K7_0Q z*)c*i$g^4j6T{-nFD73EKjh>Wz5=H)0!JeR57?SU@t@ZK{qAuQ-dzY!xD_z1Alv)7 zDcovfUYODsK5sL?H*85sOi2O;Rq$U!(}B?fz3bs>AUOKZFN=H|4daLdi`qKl;`a)| zI%&)`gKR^NPxgZ>PS+Z^)C2y=y$bkMJ`k>#!#M3c2kbm{fs9-k$FgHkJaRcFtSHl% z75shN=YISK0kT5v1uVPI;G29%mMw(m4D4C`d;GUiAjAbz|IUM{tLN?k5wQNBVv+ffXK{S>rt$rH6+is7WonIHeR{{ zB>x;3tmMV>ZRKqG{bOSYbWiBF zaY&XDav7G`)kVDoqnn~O^R#w>0tJ&l;Y0)k>i+_Q{Nyiu z{io?JJV{NEQ^{O{FNgH}$3d8y~h%p`8CQvbOvsIg)Aw{hS|x&zmUgk)v)K0=P= z!q%>Yfq)@85$Ms3I*F4F$VdNi7((#x@ZZ!e3co0WWV;VBmfv~9#;5)qngo39j0Tc` z*8j)efTJ_-dstH*t{z;W3nDGA?2tK+Zku`+vCMzU(EG8}zAp`yg! zgVTgrt2sRM>y+z?#e5pI9-S>eOEyG5zB*zvVmqwd_gR$((aLeai2LLKV9W-Ls-d|9 z$L{5cP8|{BR6Vq4*(5GKx%QCoInhr>Cb5y|W4Xc`m~h9wr}0ZJM8h&R+{CIT;Nf5I z3=jrCplEOO+8R{R$~$a}?$J2-!aW-RW40)zGv><*^*m<`T(c&1vwDw|6#o9m8YJSR zd@+_nC;G9bb6Mj4GM%{rFO39z-hCyq$9Fvccwt{A)>X$eV6?|7VC5Z5Wj}9+UFi`6 zP^;Qy`})nhW8#U;?Dt;dR#zKjKNUp?y9b;e(GmFAOEkroU%U9Tm;BMevk{@ty$Wez z@5NMBLFdOAMDE1f6}@$R--^`es1>qRZ!99me_z0gX5<{!h}!X5{#kCI~yMS;`W3@SB6u@LC?jhYn_&yr|80FZ@fAcb*dpo$pjDT4gNSJz3^JRrjCS)!e+o)(o3Rz@U4wC|^vHYzcSv>Q`n$w~7Xb~2ajbCfWa#u+FZ*yqYi5qMvCt33~2+yNA z$F)EV`7+vM^$VYMcIDP&&AY0y#Nr^K@V`$g_}?l2E6e|T4w1_$Fg!p0(ywH1*~)y8 zVNsjC>AG0AyICmd?XAlkY>eUC8&*@p9m9ze3%&S2`u*2)*L~5ZMuv{`#|~tulT}6F zMhMEKq1PuTw%ThQ+?G}!L%Ovi8p5ys?MVZH?Q#}6pTX8SvrjBSfVGyK@G?3Y`{?Mf zB!U1+PoK@6)4W*5EwmwSdU2j8cKx<4HwawR^FAX5VD#rf->d;%(lHdWF+)^YYL(am z8tE~Pu$g)Mve_~Us4OT)G&oN}H%0xAQn>D+zC29TWSqy^*bXh+BXCoF2N>-xl8rZ> zq5j9&{%$K`zgZOg?2W!etl;G6quBpi|7KPbrwemswy zL0y@|AM)Fl_l3!I&AoAiIayikLIoc-G-CYG2&EPmfWS%R-t#$>Q`x{<>*+ljzqqQVQAanA~YBTbvHyW^Vu5;rMJXcPmGDv=;D)aD!RSo5NhheTJgCN}} zfXUY3-&S{h)hby>vvqYM6^=) z`LTx>EP?DXe+lmuKIu=jm7AfH*~V`qY&GHpcJo0$vB$>NCbMB%h{FOo#ah>YWQ$b5 zjKh3Cu5B%Jd2+A*#ZUiFhy?Z$RJ?xq(ZK@XysK^$^LA(Ja!<2fY4W4JTWcfM$ClU$ zfdFJI$e4T9ob7OcCvcWm*9$s`jv>!0u}`ku_y?qW7U8Si7KBIE0UOpAvi_~fZKX7E zLueYslyCXMH>YH_%Zgoolve|?HAEj+)QWST6X_80^m3waWOu@|gZMZYmbS@xzM*gs z>X~&%&50Np=YmSx`sAsAE8jVL9Zm1n708Ko70sWi_uljHSlaUSZS6y7JAr4bW0PVt zQy)Ixj7;a-oo13~=gnzt)9ezwpmyE!+nmp~ykk;HTk|wkR9b>J?x>0#p1!;FAD>U8 zZSwHP`EJ)%`G24$8VQ`vj<6HZxvctvZ_r{V>Ci_|I060AvKt4omz( zWa7zc9eV5wVZKSO8+8g{`ZE%(asGF=!_quM4t=&-77_nge2M@%2%SJ8(_^ict4QrP zSRNj=>|hjZavkBZHgXI7?o0hXxr?mUPr8s3t%t6MFet>K7azMK;Au0+sc;W;3N9jH zL?Sa$i@n}2HRDk;nzk{h>fSiNHL^^wQ9^A$m87XU>-$7konwy$j0Ie>A1?Ezs?FRc zcb(*%mt~P%MQuy@9R9$2InKL)6Aur`z zUVxF!TQ=X=!d7~#(SuEYf)V&7CS3p~2*Cuzw*gslE@A?fRdx9AwLv$ug-dajPi`>e z#-XsBL*Dmjtw>|fNNLp#qd=b306Ey!YIdP#@OLOa+|(gpsFRA!N=hx#`kB;o&4-vl9-l zZ>@vwuFFR|q$9j6T$ZQwD#H&z=Si3#X*2F?-r=wSmc-;EKjR%NxGe+S$W?|%H)-+p za14t+GOEWb9{z|ZXhr;3QFSFmIv$JyGE=Y1RWoZHXdXxCWSua= z{G_C&kPpRN&3o!5ePX%aW4Sihl|0-mNWM6RZh(SCO$5B8uQaF;qU0mQ!#r8OW3q@& zvt}!;qZv(VkS0RZs2bz*yn$rbA`kK<(=`BC!*7Ovc)1b(Fo%P<2n|y`Gl+LKHgD`gLP2Y4QSH#Gnd*r0Z*OnZEMt3}-c?JM@8u#F92Orc9Ao%PVL-!Jb)zD|WwnfaVy?}PO~ z(>Iw`>t&_^?r@Xgu$`<7hAz{Pi-OjPaZWQ_5(FMjQpiFsPkn?tL_HeZeQhwpY&dfLXes#(ti#@_u6;{{fJlP9N& zpJHUL1{msKr8SF^H>{dM_BBk%PQ5>QW7Y!GZVOm*%%`+$+XbDaMXOzNJ-yVACEY}c z*6j>C)}sMoI0)_t(zWcyEj_fcnkNo=_SZtqPt zKm5|tI|U@cP0EsobF{2$EJXx&sGyv;>*R^6i1}6}L=b7_LNg)B=I6bl-i9ls-zMwX2!4_0$J5?99M~5Mn$N!xBB4v*Vs$N?S^WFn*PKT+27ee zPfm$W#tAy8Z-)AbsS?_J@ym7P0AvWS>zUAXKAb)GbftXM%YZYViMzqvQcva|PsrN7 zO3Th%RsB6DiziQ7KpI9YsM&O#IfU^Kk6_c@5seB6_I^|P%UTU88#*eJTS*DL?2&Co zzRQ_Q94w}6xg6UwlvDZ}JYw^8%|b;6@cio1a@OH@?*!aqIiW9vtIeP_1$<7-AY*ZC zI1yfLlv)}WEo%>l*KN+15erI5_Nt~eiEk6MOcGQ+tWDz473Y&OIcW7Se6lnT4NT!` z!0h~I5lUS?RNZ5G72ox--4gOo!KhLu79vQiq(x$G(c~{}BrDQVUA3IzT+tfI$Q*lg z{%B&dQ-XqK&=Pf_`{oSnB(Sq>n?uI3?p%o~b=9BNmkJEJhJtIMEAM>zQQiEUJ!Hsn z3NzEr>1>#|N((G<;u&Ew(YOmAL3g8U$wq#XsJ#G0FqR1PBFS|p^jJZz$ZQ2(;>V0O-{3K6X70rz;!EeT!Hy+^89#h#d zWF~jG76yCII}!0gbwjs8!j~Z7+oiV3G>hTM&LBzcABXHUl}g=KB6K@faErBSO#v;hY0kFtocn^c@`FL*&2>p3w3kB+B%Rp3va{k`YtDW zs?B7p&1Cvh=h46~(55~w6lbRomTjadcaf@A=g5}j?7(-XZ^IuT??}EEl^mVXibPk4 z3i-ksMgvP}#pHlGuB`-<)W49MUF#7FUn#F=ZXvX3zNcje(Xzcs=y#c4eHk__sk|x# zaf>)@M^Sg65ewaHh89LY>en;(azBKOT@MY-+!V+jzHZV13g*G1gyL#9GJEZI;1JxZ zJT+tuw}9*#;{U`CJHebR4hDa%4%uzqy6U_-2O~0-ipem3?i;ociZ#OIBD^X>rDXX( z>8@{~-MhE2=sT+x>U~f5EJto8RI6$Mjace0p?HKlkgoyG9UM5Kx5IVWLT(Dfynf0L zax-O7i(? zfYPzP~vzr;+Hf~v+HfT4j*tIx0Fpgoh_VjB)M|Z$gKY2 z0Tu1#8*>CQPTya(TH(FjEy+}=A|-1_^2_<7rin{Z#$=lsfIJ?mtZhEUx&a#BDl3Bo zTaeKMTC#wv@Z2o?CqMOYm&Ph&e1sHDTpl%kDJuL3seva})xQ>gi*`u?KS*h`(R}!D ztqUbrjl(nLiuT!xJjh$(S8b5tXlLDc7bAn)VcpS&lLh>OaXv@fZx}5tQc?QAtTZ7(_+3hoAHEcNSBbEl zM?#hcL{CBm4fE_M9AlPtQ9RVL{Ba%W1uM;>8y9|LVGMar_4b;*cN^nVyZL>6vYioh#Iy+>%UQZwY|l%I~}V^5zq1*Hm~izy$zz? zIy0!HmV77K&FTZOsdevt^rOrbjpM=v)1S?_bUrmzSwy6Iaea*e#(xZHdcq6 zYr)mH*_KyXqJ3U_^)qo(HLr3^7jy{LgP6o^+?NrDSjh3#7UUzzUGxG6Y}BfUvq1E= zT(wcSjT<@TVFJeEhHp?wBXjWf(K=xmLgB)?0Y6z>(d8z5>mvm}pN<)Y`Wuxztj;^8 z%RSrFOHoQ#^YtNWLjD+}m$l8Wi51zEvYJj~L zwA&@<<%>E`9E#haiW>J)A6z4OsKX;L32ek1vzOpdUW?rry3XdCnUWj^pnU!W6&i8S z2so&Jn^$?SEUZ7ZzYOEST)LTio&7U!O!JO{W^GqEzpZ*k1`wuzKdBQ8TDEj~+Zhh|4Qh6QLQd|Ui2Tl(V zxv!9)d0^J6)+gZ)LIjQJT3pR=oyknAc6%foN$ursj%s;<3x6PShO#7B0#FVeRlB?a z@BX)wG*x5VU;epJf zg{-vJh!eyb*>U4tRMRq3+m9G`Qd*kY0}}^B7dPSfR|wL&NRQNG-L*5rXt4~nLVi&_ zVzxkx8A=-0cwU~P?$4HSg!9*5V8feljFq~J#n?qiz78rSolfN&}v*a5(krD zRs1^Op(Jte%7C1|RJUJjYplwJ8vkH&he}PgaHg-c>Uhb4 zV{iJ}=>FcUf&*^-IXYGOYawU-T*Qq9ZS?zS`n|~^QfZc^b}4t(4it`>&*?2|D2Z7V zZr3r1>#NPQjlFs&$ucfwC=n!&8)_JdgR2Mv>r$8@6Sh}Oq>%*wq=p^DO+ViCbho%S zV#+;ArRVi;*EFW~T9@-})WGtcR9%hdo)up)x@ns1+gA7mlw9gdbWP>oPQ zd=qjXLTe9QFuLa2L~Pc62Zqa|GTAKQt|cSlfz{x$N3TN(dtJ-Rm)+Gmlg}8wxr9}` z{tC}m7rjec^0_enKbyzoXO58a@!PbfJ;rh=1CatbyS5F4TgV?xA0D(FuKnoW@XL{!$Ja~BYBz42J{{*R zD7c{cP}i8IkQDn;>`j$#L9TFZCS0k%Xlcnu&Emx()!eR>sc-ibLDn6)V<>MWOKlHU z$3U@)cQ^+buk`|bA$MzT_qdktMFcK?e91}=QpCvLtcjvn)wX%y0b5aIhI?5 zcX|JVrGi4N?){GRHvqI@>Yv_BkcJEQT4Bn#)ed z=xFr1CznOyYh};d$P-3AM`<|jFK90U3e#dNtRG%lnP7aE+DC<## z3(YdMr0&iwm!tMy6FvO?a`B?)-rL%pnBPn4s!Po)vkRPAv@W69{^k?oZ{L%98?Rz4 z74sbx7+50vRIiTy(8(qSw_kZDo#{C_oY!$mWq-7=`5vVpXVz*#6r7< zD;#KUC&kkV9|RveK&t7tmFs?D97`M3q|+G{59a4j`D+@{EPEC&F_-NcogLHm1^;%@ zW5)iIhK}vQ;04c_D52VlC##s`TYZuOnRJ!tQ;j)qhR9v5~itBYM&}U89B= zzcIf3f|>rlXgTBEvC$=QZ#rwVr7WMu9m=Fu$l^rvHUPAEzK#ZFMA5qtpfH9!tb18} ze%x4S_?jDiL@PgkkV;p2P5}Hx>`ms-?##KIX_FUpoYqUL=MYjJrx-3#3p-r7g1iT6XS{|Lpce zzUa6Y&e~9>UgN6zE6Y#A6{kNCb{mUC=j#W}YeDu#$Hv!UWO}%rMU=XfJ~wkK z*Q&nLLiQ^C=o3|Rv}rnGA`b8-#8|p1ML54oJ2_8A%||jMN$@gyR5T_G#;AxlZrTk6 zn8Y;)#|4GB_@;>MjIQGK#~!bPin}%&4Pp*nmLe)$#tH6qUAG}cDYg(jd6c4|B6s*F zOoM8!+A6)Q;JWv-k4{wQl(*093{sEs$78x)8tmVZoUi*t&EMjR;r9LpBGZhS;&gMW zUjutUtR+)Q2jk+P;%1s_x^|*Jr8oX^`;%)v?dh+jO*4b-r&Hh2X-iSr=ZHC^MX1Kn z36-Ukrv)a((hEEx1|IX#P_~QQT6FdJ63r^&NFYm z&$g};jj-9=Kgf|~MZYn0^FXt9wxp57<7-Do9@e4&eCcy6rpT!zn>o>Y;`Ys_b30Z1 zHP6}R-`IU5UY09; z@O6QiN3P20A}uN0|0?di!kSLswNd8pC<7K0M5Hveti{d# z_dRm$?E|}k$x3p%Lx=T6pJ9j45zxJ^(6)QvU+NrwwI1`7Rk#>S5gkzJU2@d&d$0MF zhMKBIiH;eor9aW*A4Ei(2D+jR!~=yR-$Dx% zB%7ta=)V7o4tZz?^k>*y%?CT@2VAmoNG#55ZqKtuUotMQj+yb6NHjUdCJn?5CTRtq zKDT^5?DVV;n16I~d*9gB_LGr)eRoKtTH)iDYomS&^?Ypo+GX<>;NySs|0ZAKXZ|vvm;BRmRBC`Ec_G@Zs!x~qkOBd zq_4=~c~7iTC*m_8g%}*Cmg*4Pcg83cD zj_rN3?e2dol=yBu9bZT9o9Fl@niv4TJi1BT>&-iBMD zJ{wexFTMTvi_Aq#rV`0WhN>YvO6>|8h%4*@WFu}gG%6gPlsXWQHOxqFea>QR{Sa`p|_4v!9wRh5R#mmFPaeRAYY+cDh(HqAk~z0+u0 zi6`9y5D%dBCo6VI(&O_7wd;n70plRFv?wL_RiN@jncjHumGoAXt+V<#P(c~5c!kwNkzP`_7Rb6k zr)ZC$qN_amgzR*Xo=w3$+rp?Cv%$i(8@b>i!rkHsyGY~n@xJKwr_W*3tN=T!JjV&` zOMlQOv0JfnoSwB$Vr~=tZIfFqU>cyIPQ(*~clH&-nr04{Ae#Pqa&vdlzy(}8iFPe^ z3Azy)wfuXnBd+-s*;*xk4>f*h4>c-xyX~cUCasLB{2FXFuNMD_{qkE;mki_dbk?i* z}F;%uisr@v`kGV~6(F`<1_$wpV_ ziZ{E+42LhWMmhuwPN|{W|K51MWM45D-eUTjy@bC11Fpq&e3q=G;nCzi5TsO1_(#Pb zE@diB_o~h8Ph8WCy5rJ4l~IOz7$@GiZXr39SA7lDJ0MmMDzjX3N-TB0O;@h%H)zQC zx{SLTVsT0CQY@ehZx?YWEq(zjuJ8?m8LXOF&1ECppK$hVTc4gwxzW_Tuj;1x*Zsz!TgL7_;?<7Iz;@36AW^0N}VNCcIx*5vI3MWVIshBxOz%ssX5 zqNJ=nR&`$?sgx&tV-~br^vy@E7|pQ6UV$S;rR-tAxVrn9qHN%?Q2~@x#5dhU^Ac+;I&!) z$szw04RsEDqi+geeOJjLk2)o`$zNDjoNAk+!o2q{kef>`0^R?Aa05`*G@gzWko-zugshRfrNaQ&-PS-~<=7OD*b)NjOecFD6NKI2! z2Xs3kE{q&3&-HX}htyRBCy_-8@3zjcy|iV;5#Ss^U&wE^VJyp_xu#ifaHh3KNV(b7 z@xfx!rO6^St*>6d%L)-=1IRKr(QPCu=~6bX;7C0SHR-$CV3P=$32oo#xJ5qlbmMIATZt zQFQ5qowR26qTVdrq~)Nc)+OpH?UMg;`U8~xVJjga`xAg5g+aO&O7L4x>9XSFtyeRP z7yW8ou_`(@%CsG$y={B6s1`O(r5h3olCPgS<*k%JF`jk5=tfSc-tV-lUpfCLJAtZk z7@}mwPJ_`k1cE4JzHCWP+;XZ|@2R4l3FGE$jQFf{&zwc|%1q&NzxvgO9-){T%4ajQ zyKUUu&1{(uRfFOl`S<7zt=Gk62P!oVxRGx_Y}h$9J+ES`iM3LeHAPJ**8+pdRbsUV zTG=tN5^QuxQMj&R$fHctr6qM)F-SD|bfk$Fc~?&c3U&j{xJz^MeQ zV5|_ka0+S_qOhDYLpPi9lU~es5UOndgd?As+@L*5dsW^yF8UXA;LbIV&gZw@Jl!$+ zeBIggoH_&^Bp$Gxst{wKo3rVU0_&SuyJKLWK%2&|T`C!6kxrc#@+Uev)r+(o8K_*Jjr=7m@x1I(Ycvun^*pOUbm?VQjz)(+-If>Yjl>nn!cba zuP`HR?CR|<-<3F*2-dA_2LhLT)?9?;`%DKx!U@7PL`frl*C1CDr+G-#|`#H zd<^)vP9zLsy(Zh+ z`$_R3Bm~g0^16(pdw$&*!q|IcDI(X2NcBpv00J#|@fIGnL>}4dYQNWvo8gHtBKbmf z9j5+aPlR~nu+!Pbcx4Eg!Tl*LhpohKjtr%8Qu= zBuj{NbQL07UfNf3yq}Vm@|}fmH5LgAC|y{9QRsNRFkUE4WM2|M+L7TN8#QD*k>R(< zJ?Q`fgG(?Uf%mTi1%LkhqlMed*+BhwbKkS=IXHSVXRD=4J@zrsc{GS%^5u>h=Xl8; z5mpDLgv@RS*KbWQHUuYp|LAn19u=Wta+)+m4_CGl;TO2#?6;>%UO}D#mE!q}AEAN~ z^svK_95ZHNXKrs~2#u`8E~sQ@T$6@o!9wJUYJ)Gyknin^T5RtfG8D2>{Q#!jrmZZ^ znwHp<8QD)=ku22_uGAGtMJ3l20kUfl&duT=BtaZhe@Du)G#_^TXi#`BDU;Hj_KsE> z00DPX5irb)U7>QrfD2H&8?pz_BxN1V;7drzruD} zu>Xx~LEJ*5$qRORGD*GoL(>{srs6?G$m1KTTw7YauNb?eznG6XI#v+u7Zr6>WpVRv z6Oz&w;p(za0K1(o_J@_n$Zx3n98m9}`7@3P|mqMhV*yYaOO}i+>MCR=Y9^_O-eeRXlJT zzNi`Bp!q9IPp(}%3zG)*WJV6iGm{{%2SIPyG0e&z)C1;`;wev>gApf!8X-@}PPv~U z!B#i9+El$?SxAs{S#q@;%BVY7)kkCq{_+lM#; zKh4I|hPXHR$MSl0eUEYNXl15eB&bIHY8Z766yn3MO8M()V4itvHseO|gka;3%Um}% zjiCpbr+g*NrJnRJL5iChw;s%zSF*15*dA3?fV#+9o)>X689>zdF@Nq;O$sCD1Univ)6SmsU)F7z6c8=9Za1M9`j=k2?*K_fkI(WWjU zHwbEtg7{K*pFk}t%&GC-ww6=t+!OS<^8;}ynJCu7YI;#qi3{mi9hS>N-#U8ir43HEh+GUAvmuRd`N3&}nA4HqtLfNR8e4?)P)GPNC zUM=){GH~Z}5-3P+BFiiEl-69Ks8_nSjTBlPdi{-9pS5#8sOel=Y^j2jg{5|YM}e2| zU+59YShJjO%$H{IaSYXdW`sIjRAr{#Cf}doeQ4Z_Ef%PevXyh>X7fOE4fh<|`V~}@ z^?SQ~hLI*qOT~OkV^WCnF!Jfp+ zvxZWRXq&tOo990Ew(5QDk~R8;2c(Ky<6gE?^mtl#R!zAQL5KGENQK z_iU?7Y`|B}$ci?qe$>>WDW{(k){i{60zbywDo%bH_?o=h{<;ZueNtOQVkYS9tp=F? zlBXx{!P?rIWi({`u+d%N`ZpOO_NV(?SoxFp%o$p5jgHA5PR_45at-ZRhqkF>sn_KC zOnc>FFM=E&j8@y!OTl+@wOK!E>JK|-D`pu)k<)8X7j<5(MS9X6mXe_qBd9$PZ2~wZ zQLgIbd0(j@H@Gl6mMZc)1Z&0;RYwLGlM-m2y2xI+b1l=`h9x6mqF*d=5^hAWf%ek# z2R)j{UijN4?`1A)_b!RPUmLhtoFiCxD;zQ`-gZ_5oYSNY zOKU#j>^3HeCZDp&zW|Z%k=E$*O~MN^O>m0UG!229T(7q*ODqkU5hJHcOh!^0@CqSf z7KP)W9G5PfEdwPh@9UP)*y`HtZ~QvoxpVJwtgBy2x#7{rn2eF|o7c^&(|gP{F&Ws5 z4?gFzs!Z!ujm0G`;bWTlbP-qgO6jX86R71mXV$Zzt+}8maW7-($fa5vz0}Dvk8j~n zWY3e>a1|6hsm>LlVzcCqf8;8}PalilC^c7>izArqlXr?kOTt|=o3fKnoVNT`-`_J z1GNY#NKUt$1e2160FS~&gvM$1DL|6ccO2@~Nb0l#i)0yzY2>72$n>W;lc>zXv@q)U zVfIq*z{7T(q;tV?V*Jr5e)pD^W+zrWEw;-j+DlPFB%M}g-fIN%Hlqa;n~SBTOgA8B zYmS0ruGl3I^C2|Pj`GfE_-vVmu2Ff)wdXt zxR_uo^N;@<=e3CnE0KhXe;l(sz9p+b*1p%fMy-Idh4U{zhi-VTk?Jq@+ZLAxq|`bc z!>ah=-%VAaC9j=CJxR~!m05XUj7t5K9nw)L@ZGmik{6{sZgR9fSh}TQdi6f11%s&8 zZVEW@j(~}b?2o)6 zqh&+S@&^SSkvbdS+n)coPv7E|oh@som|yRzdVDddl`N6KX~m-S>)lz5)3 z8D5;HPtk#0CH@~>68XSMe=%^CaA=&t^27>k4H91rTqb-hKWw=8Wto>uMQruk`;PH8 zD|U^}bB_e_U}%7~)s_QL#F<70@4RL7Jo-<5*aJ)BWEsAl(@yeFfkq2Dl6QIDt*jLB z9;5YB;LSq-7~xR!%%3n9VY*Gj@k;=T2w28Pm>^@7NVtFOQ9=tLwGA1@U^V)#qkrm%RpcWEYP}qWZ=-@&NikXf$&Q%N1?Zcm8TBUaCv&{SODs349@`tz8 z!g_a1bl5R;APFVdwgk`m^hv2|TB@=34XcQ4yPf2ND+6z92p z%gCBTeQGzYp$)Gj?&AM|GsSFrqi6I`Lfq1|-s41B*u90CvBZV=$v4j6ULhy43reHf z_VB1#PbAv0ToG*@ZzEc5sjWPn^tUags>oE+ZO1VY_kvevB6W|`CowP26iM6YGcjRD z(mHqn7EELUS1^WV4OL^-(gb@65?q@W=IfN|aq8wFsKw;==4mr}*bzvut%S#)_)kD9 z(ra>%=+Fh8ea82=o6nq4Nw4!GjoylTG#i;57Bc#);fqdS5$)|CROU81xrcryb-=OT z0c~EK805GFNgZvowcogYLFMAL(v9s*V{2_tfrPHTP-=xEK|x>7sczXr+fVYMnT>9= z*he%~ex~SM`5}ZbJAR#Vl7A7R0mO|{A*R%`tn4&27_UZxMhf<2$ z4tB#&uws(ewQ4KdSeBSrogCV@+?>biTG2yJz;Yt34lAfCcM3Il_vUqVVS>(iF~s3ev$dRd$Xan3J#q{rLM922ponCo|9M zyWV`}->}+CcFqJ7s}I}mPUL0T)GK^IX-KiRS-FGsOAz^erRyZEsx4#TO-B}XXWiAo zmZp)(SMRX1q7`LnZNBlIS1r~^RlnbKDdcMK+d~yl2NHq1te~6g!FTE3#TtuE>(hipN4?Ls3oB+C0}U6?P`zg@E9ropP0d1I-aquh#rl zo%kc==hdn`5}ED~D12Rkq@(7?swTdv5)tq&As^#Tz-G^E&w~b!WPK zZ5wrVwcYvRSkuLLeLdG&P)W63Td##mqo0 zH;Zo==hsGp%CGczJ;kU*Us1`mwk1#HvE_{|^g<+rWry-0hC6NS80x-A5=l-`UJ~jH zh|va_3f-MNrFMvDT}ZTN-_0e@_ea-&k;j{Wf*RKaS3ER|d~B5l{S5`d{gqAqIt2y8 zBEa#HSl7duSKFN%1A3Ti`A;}I0@DpRQGB=x#sWzeB%S^{cL!!RFNM`5*|D* z<>-)K+YSUaS9;oWP8j5)6CX4M$iTeghe3LHEc>UfXKrLtvf9lCDC9}z%J^x(bn z-%Wv>!j@P{9hl~=FQ1h2~VYMQti_o*}=BRs5K-5P0G z8bqC>Ui6(AvXG8LY`8Fg3j940opIX0G_}zOII;jGO)B?FL^VWofaWXb!S$S|k7C`S zn=AFTH6ew(R?l688rK{1GgyigS0=3M33v$Y)w`&^#l~GiTZ#Q)bQ^6iRYp@Dt`reb z%1#cpyqp-JJL;{fRgGR(A=EC};H598XGz5;4PFHbB5!M>-K6w>I411WiXD`!L2h_8 zfa7!FBgfId=Pt%so>tnyf83&+_F})Ljnm*LloieXi@DFoD2Y?TTJH&gS*BKRS?K!a zw~(|Atu!Oo!s5l2#Ob>h)9c&MliPTOI9&K^XCND}zg&LKR>t5&e|R)hTX9t*P&T_v zI5yD{l6I}6UXqk0f45$Fst6ag_w`jpiIdBs!+zjFCxG-3@7aF~Kowt_3Q`B#T_d#@ zbymcTEcgijsvMnX1$waFu`~uQomOPCbND9+sd6Wu96e7%Bx^K3RoV$m!*Z4R7m;?X zlP4|>RkCxs-O5#Ur13d0*x?}6Az|I|J8k7+LP&pBWB*ckg+st88TsU>m4FrJdala% zUS7z@dQ^eIXLmXZRIfj_o#$Y6NdAOPwhlTBoO+|E*_lCHObQB(S*)UNcqwt;Rx%z8 zUyaE%E^!Aa3}h%C5QbGt1FN8K{tDX^#g=hrXJNQnOyuKGz9QF*x!hkSMtk@i3t6Tj zlRDkNFIPCn&@b-&!@V+PWD!=!fS*I!=Gp~1HB5;vd0*8)i1+QeSmablq~*VHKP`J2 z8Wk=nt8jf#K52CI?&Wfg-;xG;kJ{h?Gey<(N$d!v`XUxl*!EVY-yV&v8uW2 zvS3gn%CfX}8k3%V)*v4|btCf*$fU>7sSM;BTRm5`J&kDIeXJEG6Ev?&v>zax7@=)u zf>}t139i$tl@b$CFEYf9cIq)0D3~XIgSy8HrUn%64z^6k7!2l75C6_AdcrjoUI}~1 zI9TLCmu*Zofvh-xV3~T3O+Q$rUG%FjsJiu5)hPV)sW;8yx7Pyvgj`E6o34NMIPNS< zOi}}KS7sgKqQ8nkYkx*WwKUkT-s;3bP=-rojgrc+I!Mb+LC-38ZSdj(0!Un@Gnf9z9+KJAW<#s_Uz4LiX#E}wj!Ce&n5?^hkw+aQkYC3omI6oxtZpG1yw5P zysCCwgg~Y%Y!681RSP#`(radK1z26~tvjFO?p6S%zKOzo=xW?4D4h+mbm|){)CphC zyk`lrag0`YNZ-;;gB!9nsGmr9CGX%FWY#AB3^ayq#=1L5EdW%r1ZiD{JskU#EfIM_ zY5+T0A{pvd?*r!eCV6^)yl-S&#l~8)VF-ooqNFG>>SuMXejSC(le=U((zxfZY4EHl z^XB}Sp-6*RXhiFg>z601s4PBd&7p@L?2NG&W;kR?KWmqY_JQ2!?bL$Ty1t5EqbuKt zzH$szQ$ufnq<(U_=j=?Ej^5*?V_Z=VmCNEnE=fW@S_{=yU&bPsfK(IW2@tg%gkF<& zeN#p8Sp6v=sK~aaMgbP|KKC?D{y6YvX`%*+9GuC*BdOJ$!K?$mc|CaXkDcdVukFUw z@1}3t+WMaI@pz@vZWCO1YS-5^V?10h^U&Ru>BuD6>{u5`63XhZ=ZVx0Z%cJ_3(KYT z)_ZDu{gnM8v;$pe3`@!4r_g*@=gC$Tx-uyFd~Qh9t$AtTh$Q_nNT#BqIZLZrMFsnoylBLS!7%i+cVuhZ&Opqc`Bv2Xx1JsY>Z zYz%*A7W@orm&XviuJXNm0e~)*zW|`7TelfwalFf(tK~-}$b6ZwORGftc!K3SlirPwJY&uRCm`2e z&oC0`vBm<}%kSUhNx(%A*#(lou0gC%Bx&^tN z*O->a%bBl-e6+5rPq;sPe*-rh6Qazmlh>fqg|!}xD=X`c30 z-dM@#VLjIymS4ly{yvjgt9&W1SHSWQ*J{U^ac{Sf!miK`&-qV`sYja)`%$L$@K%#& zU-QO}&3-~K05B}KBW0V1=;d|qXoN*DNTu7HTR5VTv<8l=Gw(*MEZ>D^J%auF0R(!J zeY5$<#aRG5&6odX3KZ-YvcvauO`GC4-OT#{4~Kn*-0~_r92Crv=yrp47y$=)R-+K# zgWj5+GvLhbm!IGt`d@Hy1%BYhDEu=F@^3^RbH5~4hHCjA5j)mKKzn64%=vNw31o2K&rfdn_( zkq^UGt&$IXT$UV&On(28@jV`1{19H!G1JXMcp|)afhP9IaUyvih4Wh2x^O#GgD}D( zb8z#(mh<3$hwa2Aga4%*LE7HHa2RB5x?*BUb*dgFz;PEKTeG&800I4yVa~as74G#DV zCv-DdaGMtbR3m@!5}4YDeaoH$!1qs|w&H&ZXbgs}Q6Rvj+ul0^+(^=7)W+kL$jku! z-rEk*d&UGiLsPpsfK!T4Pk6nb069pVZRIw>1)Ee z3B}ie*dkalb|rK9=u->Q*BzeLI!`yIIpNppcYFhk!ZH$r+qDX9>t6Z${j4vdh>qJ1 z>--P2|5G0|xtT+O$upAP{QZ7W%Zayz%2Qdx$q{I=78@SUcRVB>3=e4)t>(*Q(5 zE$~kIMLnI)vvER3eJ^-C}HqBH=Ej7+IoB)D&Z=b~XBv7Aa~A7dg)F z-`E&On2pW%Dq*F@9#r5hI_5SRZcEQJ<;~GY@$ITD5K9V`Ry2)l5 z9}}UzKO-xk-1Uvi+1lWCtd(ut#{UVNqMc>Ji{&uCJW}Se{*B(44hjiJ3GpOP@7VPT zd=arn3_e~7M;tQ~SoqCiGiI5D30tH%Zw4!T7XS`0&X^}?7=NU^Gf;mxS*B~A--X`~ znxDrthMqa}J}#bbBx0Vxe(;B&fUrh9!KBL&q0Yg?yKDUO%e}O1uIQ6L-`=L{k2znS z`?kBiu}gN{+3w1V5YRCvQdkRdvhuzE{HZ745I?uKy-MDTnKGOif&%Y&|Gv=4n=u4z z7=8wat6wik{jE{1{HH+9fsYG(*(blf`&Y*N<~SBa8NpZ2PuqU|4R8UW@tk81cchFV zW03_%bmO~t@vxn#t}Y-*{`2TRTf1C{T#6v`>+xd(Cw+F|u(*eO?`_adqwg8Oe8xvo zK1Sjih8P2prB#0aw!Z@ZKW*>h{qq1VRq~_y@eP@)7w-wM&2JNx70YYkJn#Dh-bgDHdn8-J4umEXXZVn|MgJ zomsW|)@p59OP8@Pn8pNtY8H%-X(=Loo8-O1l0{zs=i9Bo3lRVX794L|0>~Pr&=<=J zI!SJBrozzRlc#oAoy5`eD@A^e%KvC~ln=kio9X1vTrGEbJ|HywV1)H1(f{?LQgYSH z-06*$T-PapSX+_#IWaHfV_mLC%bfltg(>9l{mbT&C*4j>SQ&{8H1=w#H(F?cYFIv1GW3YvY_4j=fAGi(d*CzRZ zvHkEtyNz9A=*Ew={g|1JM@auI`68T2N+#d^-Fv;eWdM)X0qm%nd0>G79`YcF3$(Ya@((8Ve#$fxm0;~7_M90I%eGQ=;{M=5*Ev3(0f{PpD6?D_W@Tskvtp_d3KNmyzJ2^p_^6>LU(mPM z!!(AgONJnS`E!3>2H6zrqltunmRf_~xadQPB#lu_usGQ3&j4wU*;7)wnCs6U^zF*G!hs3(i7am|t_IKWmJAHp_v$J20f4H{|>)3es&i+lD$Osu} zK7deNxGk#*ng&SKNlS$}b3iD(Gp_`MTE5Q+{yXCgz@SiC4#gq!h23zo+dpvQwHuV+ z7Jzo>Biv_}Bd56+c;F1R_#uvajG)y7-(-iiu>Qk&_3ulxAbdIlO?VBOliU}MUHD~e z86M^vy6@cpw6!OE-&y|1LXPN32_7Lf6BeTP(5PKRFASIV%ONdkeC~S70gTG-#^Z9# z;=c<1zufSY&ySX6f6FWHB^kdZXX~3Pzub>qh2wtB9Bb>{U|NY_E7jS_ug5cER9uu& z26hYA-3}WU?Diog=ReQ^>L21Xp5t9ySvl5BVKlcwd$C@<&TjWQFF_hP#=Vm1k#qC*F9ww0NJ`_VXiehu4buO6P|B6!77Q z@SoRTqWwVbNxA?#q`}h_d*2T@6CVJvKjHA*E;jJ;e~nB3E4=?dMNWR(2g;GZ^8hkz zcnW`j7Xzr|MWyen7V(AmNVkZVW<2QF!vDP tzc7&pSlLq+koza0K&_}UScP{ig$a;-lWGv0YsM?k6M#4n;{4Aq{{#3f_dfst diff --git a/docs/static/images/pcache-writeiopath.jpg b/docs/static/images/pcache-writeiopath.jpg deleted file mode 100644 index 561b55181174ff5a97953571f02e946ff34b8140..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22616 zcmeFZ2~-o=zCK!rii#K}K|qKTq9UYKhL#~wQ4ta22m(Ud0U08uZ9pP~gh2?1n4`3y za3rW8B0>}hC_^G7fmRzMQ;^US61r6;sZxp6CaJu_bGrNXf1P*lIq#nP)_rTexFi** z+Lb;0rtkZ9XW}l8k-*HqpM$pf<%Qk_ZEN~|)9<>)=>IQA z=fCu|=8G&WZCBc@TD``}*~Rtijc#6Bw{7>{;j?r9frI`5hXR9+ z9S=JZ9uXN86B~CXo_aPR?Ob{WGc)V_<=pSD6P@o?3c`pw&sQRw}Lk5kG|@UNez`-P6@-@1kV{9Did zkA9(ppfh9UOx>A!)BVz!5kK8=qnWdouA6PVd9U7)(;o4b z{jPJK*$OAQEi~P=FFpI`I+ptXsAs=-?2mp80AA=K{qMcBXe0l<7SOA;uh3=bu9X7w zby0&b(lr7I0MdhBhxDY$14M+mYAf*5V|EUEF)c>HVdB4n?@0)AOJTb(vV3wTb-9pk z1lHA*lLOd`5GVCA-|TW{mQ}?4k_>a9!nu9{shAW5}=G7NrzZOvxP zp`C(HBCFv6P|%_U79mz@7l}uhd_rGQ#|J;P(=j%l;2@7ZpBu8XukC(Iyxh4#a@;5O zAerpvelYKwOC$LlV73#fu5Y~g?klP_%>ue5=L;*X+IXH2PH;_40tecWnGidzNuL7~ zhx&v#mgSFZqo!>o)S_VA+>sYFPngUZouL?ee%15&B@fB{5%ZX+x?~HsDdUu^KSs_v zN4JA7VzYTO;42DjI(EJzf*aEU^Y9C4o8i+6PIeG0WvDZjU>GziwM%;g<^3g$+gr;p zr3UZmcJ}kv0y|!uxDOGSaTZgFA+EvQ97n)oOvnKDu;){^!0-kUp{9Pqx>$X>P(;x8 za4kW^`4TOlLlhE>f@sGdK^?3Vk6^hLFb}5D9>R0W;h@RAaP)ArYhV!}NBc)ca5$YT)rulrC7Z6oFKB@U&IXiW zy^Aa1IIwUv%ASYj3cZB#x0$^ff5HSleM26dX)e zb~(FG>W?OrR%YB@OF22o6U40zSo!q5WPEG%yKgQ%_FS1f4_Jjd@qhQ5_670;?@n)J z>+&Pn7x5-=B{bZ^z#wbk;8wma-Iium2Zuv?a%HZf!uAo&QfL7tvZ4_xy2gph|Bjb_ z=Vj#JoJTKaTGB7p_Zg8ni3=Lbt`o-Vhp@{cv;er1ZN?WgQc?%qG-cuo$Re1z2gVJv zvx9m_b1AWei-;5a2#i+-!4%axT(-wL=sQYw!fW;r2Lrc0lM~K)P%0zb@{^mQ6^UdO z-vSPjDQ5D_A%uP7q7s%o7d zY5TN5YA7uP<|abHdnNoR)?7G23!noa5SzTEJLR{az*a8UE?}F`zU!kAibp>ceFcmnZj2TeWC^{i z;9QXhJOC9A5rmlYd#Kq%X_0P}Of7KyIx$33cmrmDq(JzR;`C5Q%+PkeYDW~Z0@_|W zjJCFm-=p5E2-!cp_dy139=xo3D|ov=>d%Q$7-CMs-l{nfMKd*57!lZhvdCsR829F! z7Kk_jH%8xu6Nf&v*22p{vYf%>IZ`dfj|(_y$Q<=zq7@9&@L;t!IcYeF=x6hF??4dD zR>-X39}h={3(HGKim@J}1kmT zvFhndK}J^JuHINJpc^%4);)2m$xfki?^7+&D?vo02UjM?*_2^>nW5S@Uy+~f5h7=& z7tP8QVsLHO%w#Pxz44|1irjt%+X@{dg48aU`%p!>=s{#SGt<@Ha>~q?-bs|Oo74@v zsg9FVn=lP`{s)W_HFS)&6G8qb4E`UqNziuvey?9|DILz_D%~`>A^q27$_Uu--8
    +g^t$}EG${i*t?OxVt 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 - - - - - - - diff --git a/docs/static/images/promo-flash.svg b/docs/static/images/promo-flash.svg deleted file mode 100644 index 79810c30a..000000000 --- a/docs/static/images/promo-flash.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - diff --git a/docs/static/images/promo-operations.svg b/docs/static/images/promo-operations.svg deleted file mode 100644 index 3036294ab..000000000 --- a/docs/static/images/promo-operations.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/docs/static/images/promo-performance.svg b/docs/static/images/promo-performance.svg deleted file mode 100644 index be8a10120..000000000 --- a/docs/static/images/promo-performance.svg +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - -netalloy chequered flag - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png b/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png deleted file mode 100644 index b4b24849cd394bf7e131bb24b7acac9d4decf399..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176624 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
  • +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

    #??SvQ{%8>@~>xDA5R_YTx1cQ+iOPX!j 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§Iq3>PE;$hvd`2WEO)OJ= zf1}q;FIzOiDu8PNw7|i6V8q+Q+G}&m+RNZQLN;agaaBJaYp!y(%jOC5owasRlAbPi zkGnTS_F=0f-iykFO;kgDN1c2Y($aDYOo&ypwaYaI^Cjd-L%n1@9+e*P$}l}dXqewn z)Ale-#=-)F>)rv-QKr6LoVtgzE%i)sC%g~vUEHgm$7DwQfQd}KUR-2Dz$odQ6|i}7Z&RI z{e#OdR?^`WOlzdM)s(1!#}WjC?(#ybAZNo>%0H-kqrr zx$tFQ?5o*$6Q$ko`M!2_<4uHKlynzgt-GJ(U2T;dZd3wV}1Usa2obMjdU^ydIXi&*N%mu52k86P{I-8U;O(du2!i_$gGN$r~*8c)7Gp2YGnV?#&` zxAL;}Z8bCiqpM8F4k%Z7?15O4$noIGtvav5Q*q1Vm(Nf1ZsCC3 zd3n2EJbm)g9}bTP?sa--3vOQ=Yz4F|3+ic3oH2ZI%@}ZL2c4g1rE0q+wMX4kS8*TE z|2;XuXK)7L%IDHu)nAtdMe+-H5uy?luuk3o*d%f51o1VNQySZ%fwJUALPAk%vC3`w`;;#3WiN37d z2*)5HJK7mbqq{lu3Q$r zp=^>~IZVZ*T5G-en+6qDfKMq1ix0+^=k1yI9T&mAt5$qE{mf%YPfOFN|9UT+Z!e)O zAr0T@odf9#ixd}_^qc)Ww(!T@IgNO}QdHyx)ad=2eJPoHBIf}eUv;{1SzGb{d* z=Ke>8|NDd0?Z4`qyvP35o`|RcG}#y^Dz7l-POI)VmOF!eC(1SJj$S!WTvkF=@uc1Q zm>gS1+8?ik+_Q>Ol_JS7|72p*nU~A;?yxE-)N{E5aL^u$ol8&wV&{fRxeVUkspa}9 z>Fv_QF=x5K2yI^Ulr}nN7d|7ia{0;yo2-Sh^Njb)=dml(>M$sudKt?&L6=W*PI+B9Dr#pkY4mT^YxL) zfv76OrdavU9t6zM_>2WZ#_Mu7QR>`&XM=?nwWL!L>6HcSC@cdBxS|M_Z4GHaVNlFy zZXu}r9O!-Qb`;8XgyfHfD>CNT|z|C-wUf$=y%V&Ns$5Z&SxyXLYR@ z0a5ghhjTY1GNJ_vfIRJV>iB>l}$Q}WB_>wJAZAd>5ujnpxp7A=lDeFjWA=CDZ z6UL|vmI!1v3zzToj3mG3#PP3}-;U$SD^4?(LPd!zmjU#_iD}&!Oh%!-ePdtuwMY2O zjKz~aZJ3?*hFuNiiSc%vuSw}p`3xz!4=c|$?#+eu!TgqwoQqJlcvAq=1=Qs=PR{kI zg>RX_&=fz$MiApZpKjzT8l3PYLT7H}mg@Xv($Z9dd z=)j^2Qr-W~?5*dui0}nmDdb_A))YxXr(R?7(5vSckM!x?m#ZK&U9)oIN=%iQ>%oq4 z_D5n0KF!4yNf&tQd!?9hXG?89!865RkrOg$a4F^UMbSg~*ZZ75KX?4&@&3yx1_yk# zpv$KpJMKR^$QHgciL&(5m7BK_s?z?|Gij!eRsyQknC~A3lT1Y15lOi9kzvkuCf1{Y zYJn>f$VZoyBo%dS0*~rll$kR`X}a&ORn9NvN0rL6ky_)qM#k@n2@#mLi`QpVXOd`#DH8iwMVY?4&_9lkVpZzOB}dHcIUC;WoU#2vLD9K;bmww%(>_hBl|@}a36cFiB!eOboIVffO{_~NS@ zk`>-R#h+2g?3i@z&^uCVj*q#Eg1(`=VGVX`G8dMNJ`p8$NN5>_;vW@Nj!LeDdU?vO ze1>>S_U2|qbC1|M8Ip)7b`Fv?Q*ilG$#N#jS@`gZzfZjS>+V4hCG4^U*&*IpvM}a> ztqKacH0e_upC+PI2kJGx*Eyr||QO>P7JDm7W}{Wh+FSh-7Z|E>x)`n*`GD&D4~N z1IDVyyFvfaxs>-soZiR#Qh8Mx5ucVLY^J+U1YezeKV~52^aI{-}w_WuYPDB zPz`Dwws{rKketP*vO8^*Ta3HWh18fbE*rRY5kDGy&nv^~af7vIIV;ILNWu>@TAB0U zPgHNH66|FyCqLxD`|@tFyeO12i>^TINLckyzQ@68vyweFqXth6jqFU=CJBw77K$yQ z3Gb8OCCu-W;+#8GP=_`u8E3ss$j^F#ca}3h2#K*A*8A*ys0%g!uDn6Qwy)9AwpO9h zLi)**C)YfSNEG!er)#I?B7m2@evArLGn&&7?W5B;(T)x$M5|sxa6THsnN$8|;H_AD zFHh?@KKNn^>>J5EIANkqxhB-7_}1~hw{0OeYmXv-ff2-~^Qa3$GFx)1eE4x+n1+OF zewKo%Jf!`!S>suw68K)xvZ2hjk}nZW2M>4YP2NKmTEcv$n{P|W60sM`wM=DrhC9p( z0vhV3^p8$V9xA{*S70gPt0@9ZX`mL@$7A9(@iCx#pipAb_hb8jDbp@o!bmW0gb(S% z#dbt9?}3lu&AiNg;)rG~{W*d1Mcr6^{mGTNQc8`N&$6Iijiw%pn)dQcunH&NX<)P<9f|`En-MjytTH$ltI7i z|Ei5!nV7o~beF(N#n(NRNpT@xP0{mkNgwp~-aWDr1I^c$0FM`fSApBcaAlqf4YsI! ztjX2q*L&J$X#*P%x7%TovOB_ukH4dZGr`*fuIz<9+0LW*xEu-f8a+uwQ9Q(q{d}K| z&x^NK&2A0$8=4lwl(lbDca7N?j=CO;n$0I{9?WIMg_YQILs}j|y(Q2vC!@J;XjCYk z=%T1~#Du&!;S&#^skbeiDp}U-g*km9OtVY(WtufN)uqin$JW1lwxpqma!2=&JXI9C z@4|@#152p_{GA-wBInYQq13_=dxe%zI(0`oY~5rKQf+nYqQ=WBywQgqd+(oVSc8!z z=lk`U+%;46nz{B&gshxTvFf^!Q~L0@cfnPqT?u{;COsZ%?!Qm-a`}#TI1z!D{|4bH z#HAaKyq4YfcsWQltf5uN)a*Ws_et(4iQRD0r+0;Jl!{JSQWK$bstwB3=W@e6Kw-(< zYtT8&@NGD9~O2zp}yEbPkJ* zIyYTsKE2XBNa;{*=PCb<4L)B}x8%1{9;+`Bs_d>pCdsv#3*S*ws*S+QDLjlu&3b2Y z<4Vg0P(DZnr=*=|;=6$*D8rBJ= zgNg0y%WFSOAODNJzs2wsLqeYvJ(YEhJw@BXO5Y4*J`NO4o{48Yg(t_z#aixLU8ds| z1BzrV8?X5;Q`ZI~SUMZf>@o5d(rlu1Lw?NyESOek7h9=(~Qq zI%Qs7wqTce<>Nvp7<@nxh(|z>olj-86Fv2`GD+cLk37lW9gY_|%6z>HGrhO(_hzn-T z(k8yk%3&|CXY((Hj&?rIWJoS5Q*w7Y_Jn&pd_#%CNgE@s-yNHoG1h5xJ7AG)6_Ry2 zd4;h#7Lqr>;dSv+p4V)0v$UJTHwQd5HlQ;-Ct)#|Va^k{QWKCiU6FxtY-p|Q4t@4y z@CMtw!6mPHN0+xH6;{%hKRU5zhQGhVxG+~8?=}BWV6iAb4PWjlQ%(#Q>%wM6|7njs z`eq>B_Gw!}Q^3`Yls>yc=pHR24gis1oyZcAu1=;dKF_&Nkjfn8{e{C&8SFR%VM#BU zYJb9Oh+|_pV!_p@aBx$+1K|F(pT8Wz@~iAHKXkNMr?z`B#`=pWWYXJB4$*93o!fL| zcxq!O#NCo?JS;R}jG`{>dOZtZk<5ee&UX-Q+JN&KPC?--JL1R*}g8#NShXyF*^( z7)2iS5@rv@?-WyvCyYCA`?oF^0czI5eJhQ2VaNuMJl(~S(nvTOeJx4h@XE3}OV!d;6B#YO z>=$8P71ooJ$``KuglxXE_l~e$yTLROHvAbv05EY%s2OD`65#UCO6=03Q7w~C&!Rd5 zVQ97jW2IeK?%TM9T=DtRCQtGdLHL^E%?uK0Vpth#sr|Mwr~lm?q+D^+?T~ZAd&Rv* zCw!06CwiE+Z^^?Xh4{b69J$PxbZJr|)^cqSr{f;OIkCpfBs;;|Wf!y^OC}W+5zX3= z>@2=nLYs5T)oLm2BU!c55=|%JK~IOc26jh84#NU?FA}hpVYZ@&6=h@pe0^xM7ygL3 z;jUNX8&5$!;YW?^s*w=)Jf%wZKM4cD!b!-s2xCS}eOYA9R1v!ZDOJF}E&Frm=RTxb zK=(YsxoC1xwfG6H(Ed4}ozYozb98gjpi{<}BcXHKgbSM4kR~SO4iuaZY7DPT;g(nS3GOIE$uiTK?vt)V+ z=B01ya^fH}*mGr?%A}mYV5Nw!WnBw6Zcw(o}Ip^JwCR=mx%nV9)w zOzxrGcOLjClxCCh;g5>>dEKvDx<2$*ANyd|j3g||4eBAGdlVjYG;;A|R;B8sAE~`m z-~`u^J{*v1R>ob6)h!NEe7ilOccL!eecL!^r(~cSawVf+d%K+3(-W@~JWVN2xZRw| z9}jW7yj(w8Viat>q5$c>TU4F@{;|R6cy4wcf1u@)?hEgO`Qkq=hQl=w-7sZec@C}J zco_B;uJjqdiN{>ULFQSv1MSSlFPedDvkxL=povE1drY2oJS!V-rDJNIadYx#!#+)0&N=;e^dGX4K z1_k9Ph7TJK8F==pm7-nVCrOa)RRGZeKbd%3hu=?Knz!ej#GVb(!lPT-S?b&XJWr8^ zAA;zG#_HR{H2_KuWO5yl6W*Gw%hlsD!SwR!Bc^+9c|qX`qy#$ z2Yv~CdMw(}!$QHj4C2^&iL#0He!QW6-mZl5TV~BHmuejj^Sro)9*Dwi(>_=!c+)<= zAandPDl;4nbq-x9{H*NC% zcD+HJMSL(yB@e2$BziT&StUX-;P#=BC)>l^Z+aXB)n4x5wk)W}z&2<7IlL^$fh<(1 z<5$_2el;m&0r+_N5rD;9=KdJ|DW9Fgca41^mL-@N1(zMt^4hl{8K`9X_)Na4JansY zx@@5rJLfCcCK#v&>m4#{4n2eQlm3E^VW@71VbUHZXd2^d#Kes}W|mD*`6p^5$`u8~ zuJ+{;UJoq+u^yA9;*w2L9^)OiCr6=Xf7%_<|~q)nOpF5NMndl%9E!5?GoD5YyJFBNA*EbG`v6%-m2 zis>9GEr6Bk%EL-DKbRGGqoto5f5=!I+LR$@I4LLVGkuXM z*p25^gVIbqJKT3<0m6AKC~7r2DaLrYyPf!&VX%m@z=JBl~f z)PvwEI33QaVRWgI*^yXc7c<0uKv}Z+zcfoA{B;gLs@!Ei84EAnErBfmH18D?wbY_F z4Ak(NVx|p(6x@}-Y*BEu1WEK5Gu?2j>+veu0;@aEIE&L8Xxo9;NGMe(f@&lq7U7F8Dfo$4v zF6Ax(tz=xNRlo*#>nbE^$;t_BBA0+w1=bznnoJ71klAH4^SojmmecWiU zHndQJ<4N1#s{{f3Smo?7!Yuk@b^L(!wuSkc$KC}vrAL)XCZskzrdqFrbyFD248C7C%zP1@)@tadMW*0IT3$yJ58GK|%p#>0>w;_+#De%63bniY&J4n94vS z`(g$&YWz7zJQvlK|3m%bFN;2Y**=app7=oph&KH~vmYadLmx^uNGC6JS~c-?I`sC{ z*(5t*ZeQSsWDh8>0=HWgTI?P;AQ=Dxeo|}K(g?29OoqSMPW$W2ZY|z?YXzZGufr>^ z!}tyD)lUV7O4mv`U&_qsx=&*B%@NLkmjj*#IelAj)Kz|1E7P^C2ngOzL0@_w9=DDZ zJ*A&^pwcUYw5+a^?dF%`K~V~55Nniwko3TN%+Rcm8|^df5Y%v^vNdx%;Qquqr=rhU zcFZ?Ld*~v*`jtQQpg@zWoB=g$!gXH9 zd5q`tc^v0;nJ+3EiD`eGBm|>Wj&jR|9vgK%ty|8d&B(b;ct3U|$~5Kac;^My)j~GH zc}#2{@j}AzhyHvC4i^BK2ZOOzF-3i-6ESO=&a2)9^I?G&mta5XJ!=>m-{lZ-N~cdS zUk6Q&`Nhe16Jj`QM!Qq!Jf@Dn?9Nn>gq14jQuSJb6MB91^l}mFa!=kqpDN z-@ea+jmL`>xN%OP0Ipd?Yi}t-dyRwNx2t}P$T-pZpoxZYTi}fc75j;%_5}7OjqX+> z%FX4Uv(zYP^Xb?cC5xpxGLl&tk+*)Dt!=DTcPMLe({FzdOt8|=nNl_7r|9Q7XU6>;7AG%UoIw&5;vQQ3ta5o za=IC^z+n$2daM1y4TK&0(Tb>jOzUk??viRm{ zj+;BRJKDOLpPqQnU%{OxHl=1R;^$1Is_G6fu4IN+p~!_v`c|}N&Vr3QAA!IhPlDLw z+8nz-Xr;!AKW(7tW(rCm{)n-BP0HfXGPa}o`9 zG>-8v2Rwh9qyB2@KYz!yL0D_vx}|o;#2G)UtH(5&mZP7vlZez;HrM%#?Ux>V>+Mg4 zWSdQG{(R{Xe8C=Vfc*-B+zX?m0OC4g!YMyqiq2c?-JU~9w;pp))ZH^LF=eP}1X3?M z4!*i^YF+uhKG%NvVkr(au43_4!Ft3jw4pOdYCXV8*lCVPX0>-aJfeqP{B|Ajb8f!; zZkaKaNGFw_tqbX#-i+05^|=A)XFm0T)bW=T?x$xp%# z+ENi+{t{1v|J_K-;)WyjieuI{IscD|?0EQ1ZS~IEBY~USavnOySv!j>J77tgPD@&9 zk$f#v8pHETx=f%A}NFxzXpQyGY)kVa@8C=x*Igoet=F+Am}XJEtX?mhz0~l}84c z99Y=NJ#5Pw5#_O(6zmV7undt=&M~h5sq5R$Hw3h6Rf@aA?%Gw4!>m~eoHn*}Zm`Sy zd0|t}cP&l!E>VZyB%a3~d}a%6q4B%AuJV>}M2->1@UBI26eC}pG+0i<#@ic`BJnO$ z;p78SvJ!V25*U=+xLng%DXKL4<33Q_-qvOKhNlzH*pXlzT$h&InERNtJ?0Zo=2*V9 z-{OB@Hgd7N>3yhq6gQXh{f%&yORJXFEl&I0=F3f_#Dnt6tO&-Wg*s9^p?3y4MQz|8UtjMVt@HvRbaGObb-l-*%j~%gNB3arr$Xx zA*v5mr`7=#Q#y2T;NVuf$)CgLzur!Enpg30AIrC$QcIV8D%t2bc(LCaEP7q{D#6x% z2kjDYiwVrmms{k^vkG%o_d5Xz!{dQieyA@7 zH9DRO(X(}pEj2y^da+sO;C?dp3x0Fr`Gc+X@a0(!+&+xvO0@e}Kb;(BTpgFER|)US zlr&i8pvp-zdE7~wd_5v={W6KLiPgH0WVoSQBXGpLT=eC^{^OEQ52|)Haw#m=8hVzV z@#gm#mH%X84NI>Y#HQu}jmfQ=(d1|Qe3O5yWLeBlZ0eTbau7}u;g+t2jWn=em$co` zlx+C9_n39a1Zbq2yQ0LRW<7CjWqL`Inq?eo?+wOg7#BFep63tQQ9ygDAQF8h1m-76 zzn6c!Ga$4-`Du4v&z$Wgf?Z$B`0gG(gw7@|J}YDVxfVD!BAg|V-Rql>kf5w#>>Pso zjZlhz>qk&a#1jd#wwoz7=k&Bk0WC2&73zSxuXIv1+kr~#{TjA5EFbb6nC;N|Kb-}9 zjP4B4j?;oamb!$;nrCMrO$A5`jx@K8mRtLQf9QD3jDV&T@=JDYD))MCVQ%fboGSnV zdK0IW>{kpwP^yQDb=WEPMpqTP*e{n@v3~;~V*T^p{1^Sny@oZtI#Xt%L;(Z;VjMri z(0G3n38wt9lx|XzL3vhV&}{>pN0(&hZ}9ihH?dS^i6H@}W40ew~E$F7sr0>|Aw z8ldddgd<6nSsHR)Y`5dkxM9&1IrTzF)5eldQL(sK&l;-n9Hb*#MB|vD;QqL`B)H8u zS$==;UW->@O=o3VUNqfwF+dYr+XZ&f75W%q1^R)n#?LFF@PxD+*VB|8$EWqLI2%>9 z(4qPZjc+5wSqJ%K9=pM-$OC*~~AcD62DFr=yO2{2{QJA-(43SGzeNdifLX)AXNheb(Bg zhgjV>>3y0I1pun#!zMm0977T%K4}A9;2zM-4(e#r##~^|V5gk(l_-EzgbzlWQr<6$ z^VM~Vawh626(RdhWK8tecZS~2Aim7c+-ALOWkcljx?qZ(=ZV!h9bL`Dg6~Sw^y}0F zvj=mrsAw6~LP8%o_z8J+5Ki0BPhc|=GY3K5^2+yTFtO3ssp>2^=vpoq|1g3&Ise0* zQ(KWVSy1xhM8y>F!OAM5anA?Hm{+2n2=c6bw0Y6|=&h2(>qwKz2nB-5VmcK;XY37J!~BEdzVN#AO;FS$taF%lQQ>0w)j z)9l9b=tq>SPhE3gO<2`ioXyTOF6-Vez;kVcJCp?g?J+t(E_Y?`@zsGov?lsGQ|HAe zkk%!6saSSNhKjK>o_X_z-}LycPGzS4Kw|tzb!9*IgxpY3*P(jsIJRc`%y$LJJU`!| zmY&yqOeQ>RTEdn-Lo|KAY)U?1!XS1L3zYa&Fk*diZQKF@6(2?Tv9`=?IgL2cVRM~# zxhE3q2A?d0MFWp$3zkDrg;D$Vz2s@rh3;?ZzQ+d+S*u{eQk?Fc3VZUnFE z6#Em)&*3lhz8Il1i7>XALVL+%)V1_Ac6Dp4@4IPn)K0^b@`iC);?OCd7Tz5GD-uJK z#v+&T_NNlCmdp3Y(rmWY+}EE%=%S#vx6^v8GH7A*s|BodX0kI zo_bouVsE*vd6r{K^F|2IGqUX+e*P;=B|s(nI8dXrV#jfX*jHqsr+saOJVVD&lFT`T zdZrU1osCwJhI+bUUtW03`pvd+XaLev(BOXdAzY!62L07N#y>ut)3bO4hI!z^3^1D3 zDpB8jxFGYv3NM_dKNRT@S`i^ZWSO>J;gKr=mlWZaUdu{(hf4T&j>cOr`hoGd%@FKM z>Y`bn;{tUo`E4Jue|Y~jn?B7DpQ<>H^a783<#6EB$%`jtD(X|WI&^mxp#Fy3{Q)LLyv zTkrwSXG2>^)p<{zbBjiwRmg{L{shaCYL>yWnPdY#Spmu4ey-#5as zqZ(aXdffp`x4-^#i;i$BYR`cSBoCS^`%%@>(1x=dtFffj~P|FbnyZ-ZcN6O+DZ zqpD>U2Pz?w?2&TbZuR$UMEgs?xj$m#Hn~=SI7>$bvlnel{AK2siup{jIIzFSRL-uW z$Mo#wc1_)1iBuZYC!zQdK82k7~3f3Sv{m?-Ui+;?Ei8i#tby?iKBMg z8nOWr!_%=%ATSRv04*)ZtV&Z_w{H$;SJ?~ZSy8zaoQ(P#$!E&9b)Ag%aj43a&EpgpV3fT4UW1SD+V`plH9?p zRn%Qm1nN&CgLxwx&V%ZR3lW#Vcjug1{U#ic-B`O(@)c<}-hUUjJEEp|NR5pd)yu22gmOB$JscWzVR>|5CgkB|45x*W zJ+sNRH3qgXu~jWe^AopJ*c_WBNqUzO>0XcA-5GP_Fiab<&mb_44v#c0kjwD%aa059 z>9bZ21h;%H=I=;8gJJ3Qk-Ls%J(Gsn(IJZ*9K^&xY1vzL#lAB5)yxu^ax&&onY9uf z_eNCd+sAlMRXLgMMC_<0P1hd7#(O*_TyE0yueOfncZ#(#5;4iQ79ovvJFh|H0$td& z+_(B^cnIxZtAb<*iOY3&rrn?qQ>jS64CT+oUGe$oPO3T0;5A|t^G3(=c&swwX=)uP z%Kcha);_f_=^#X6zGuQ15#w={>d>Rjk3%N0BYu|MCe|U55AJKlbYX1_vfwt*&6>?{ zo764_Fh=J4WKP*)8BbsQxku&U!5_5P+b|1cOq9vey=`gAD-eEc(^mj~#=yAg;x?Fr zST~q8({{>r1SbNYeK4(RdQ7LmorAAwzby@EKMc^Y8(J`trSz4)eRp1oCmbEx2>Btt zOjz3K8MS(XaUfz+e70X@e6wr%K6WolGh-__K~TC#;3GA_kE0~M9_^@&Ck9#ta%git z0R!0_+aHSSc!>th&Ds0$looiMtE3X=cWZd%+RA+{6&I9(b7P zwnhzKRq3Qj=Zrjx_}@h}DM_JPmEjMmlyQj9LOL8mbyLjc{0e$Ujfd>~^im2JC5{KB&IbuODbCzCEY~cV9hNm9qiv$f75`w1YwLKnES7OQtVu@~TWM96e!R0Sk zzc%@TTV3Jg;-u#)Bq;Xb%#VsPLhEP0Lh5uN$bF~qT?;PmfNxhqcpsAQeod*|ve)WY zjM=z0ac83O;W=45;254hRwj?A{%g(X*TzQ$6`YFLN}CGbgA!rR8X?gd*T7NKcZY9t z%?qdqd?!pS2>?=XoDEid?$rHQ-gsp)?s2jaNDCY^+NigSFJb0Y#X)=M1^ho3i)}69 z`~#?~h^_j4A;Y}uS6>!yZTEiFSyb}@(iknDRm))dh4{r6LZskEzpj{F&Fkc=TYCz| z<%7T>UkNKmf9{8E2d~Gn!Y|#|^c>4<@`L|rJIJr$6Og#L?3nLs#IuDaX7DyD{my%J zBf}7JOuPX~>hBNF>Dg|Kc>)H&3`8|%Z9l&ZUPtrOoEuoNh32W@*T{|0Dn1a-?Rw4mWu+@z{}FDK(cG5k}ay)7Fj2H;qDa#zx26 zi+BIcbD)0R`F~rU^Q2>P-;BlixT}HzO^TCo{(HkcY83v>?8pH&x|x`3t1Rr)uf{?- z?`;A&H+q4?&$!^{5mR{;r?*Gc+-HPhj<}%JXKK=~pc@)|FphwhS7lb*RKKiY1K}e~ z3j;*f<$7a>vO4u*v}mB}!Om!9^t-g37Y_d5u#o|R?qca9!z$Dv+q=B`vmTdg!Qlwd zV;dQ#mY$}mSwrv`y$Lxf(Vo3wNO3S=UT3*huuw7Fn`m=z305{$Phc7Myn<#6B`0ha z1LVx7ZZaP{ZEW3 zcPF!ypYAT31VNN>(Cu;Hfk(-Z3@mzTvUN$dcfe$Pc!;fjqTG?5AhD+}-0+_-_Dy2{|#Mv&dDqX5nxC>Zi`eoS$k2f!EU-UVGKZVg5P8%f5qv%bM}+ra8-L^v>Cko{X_5Dzyyjzs6m4S3Vs6^~VT(pzJl>E1b%q>R!jnBJCA;yD<4qa<+K#do6u`4X5QSH?uLE zyWrS{oEa^=aNWy~^98*U)1|}&O;=^d8eu`70e^2)2jTUIR*BV%k#`9t_Ob9>-J?mQ zJt5?8E-d%ucfdaTqWdaS0Iv+U_D z<6A%*QB_{O6XlLlKCgmYHE7W(?d`0IvsnC))oE{{UinsT@bh6eFpftt|79*JaK?Qo zkYpWcU#0&cb;roRr2zjBlVHlGONByZmd?Fc7BYjQ>Fq-Z-3%=}q}23W@^WwC%Ua-2 zw>y(h407D1@?vL3mNS|A#xX5^D2%w62Ej|+1mtby%?BpzfA7Wyuhu{+3b+H(s` zN`jxq*^dI=xy`3}Z%!;{{xAgrIxxCm9X(cN(IP1$s-uSp#@XjRrv@$37o8WCQxo)7 z1i0$JK>GdyXPq29qn*F0;(1}-Tg3tf54lvR^_uxc(8M4oywbgVaPJ&9HnhMLi~sR? zBCQ1duyJ+7AGap7yo1L>%#2|#L8vVtL1RIjcS1F5o3Ty(ttV{v+T))^9mxOK-DfDZ zcXH}Fr{^u9-*OW$UlwEynP|@Zv6}ythEZh(3y)X5rW}%VwUSWiBMwVP6|v|@31|x+ z!!Ia5HU=jb1|0WEc+V$44Ob$vc9VzAy7w`zp^E%UB*7>saikIUm3A#;A8#LAmP2C# zC+b-PG_H6b^N&9tXZ|mJx5w^ScHvM1vda_}9>WV3sP1C*reb$g1<={h)Nbu(kSzJ^ z7{R%!GlCS;!1Ra5;aJ&!SFz6gRslK$_f~&@I+AFvmP5~|GNpS+sfo`@64H>-b!=1`~I z5d0Bxi1)0#Dk@WQLp9YHc)ml9T!=Bbom^5N(B)hkE#a7u)^m2?XdHYz4-Y}Y=jG=Q zaG|DljihAeJ)~`Jgoj530jfE@mueyP3#b44mpA=4POVDYsumT{^~_9-)$St(@AM<6 zv%9Ka`x7O4%y|--uIPA{82@EV{AF7VQv?j2TvhQKEzq~sHF@Jvo4=ZE&nzgp zLM^A3qYox!b+mJE!4N>3e8cH8Twk2y3!1bH;KOLby40BZAE$7kCx|+q2@!~R*w*{D zvMOaYI4(@%;^}r60}-=WhUG7BYX6%Z|0mChF*r_JlOfw8L@bXy zJq{X(1I7@61aADM@thw1gFdAalWRi`c$xdkpR?9+{%-xEWzyk6>%Jm>>Qt}bL2MxW z(;fcX!=oTJo~DcZQ2PAEtWDM^Su3~(`j@pP5gVD(g}|w-dOL=7|1R&uL+lIvFouDQ zapunHivG(@I9g&4i|fR$MUG@MwsD3$kqtM!$^uP>2pS}=KI+jl+gSUm_IymHSwZK7xF;fl>(lQx&CpVz6U%(&@BW>+%<@_6vAUdc|qn>drVSm56~*JN;=(X~`cPUEs!cIfiu zT$99ptU%4f1l2bpJm^#NYq$umik`7$;+qa=|-U z6Nb2am6K4xg7c36p)%%Ic^8t#RWNXmAt5A*4zq$J+J0;I6m$IjHU3RB4XeRFlh4U@ zV2LZ#DDnEP^@et@W~K2kH*pIw0Shx{uq{7r^K;=z4a%Pt*5M32ZF;U+k@wH7P-OU#z23aG6P5IUVY z?*EoiZ|S*C`~+@V?CG=aCzujE8h7HTD?SE+nnF6CG<-mZsg3najgO!L2T2J%oX-pN zL_jQZ1bQqq-*`~Btm&kRK({pSZG{)4Q)mF`6c!9YPn4bkp%sro4&#V_#CTHv|F(`PO5~Mm3W=MH7NN8Fp&_;Q>h8 zbS-iJZ)-Sl@`^%~L)JDM0vQ`vKW$UPLE6+%SpMedB=m6y8?x4lXplv>v;|`%0Wkq*k#pnv_K5U zv)A&u+9g9t_ zkz}XDHk3FGM4%jJJW=f=#ee^s6fX`^ zyq@gxd7-q5M9^%5LL$q=y;_T>wM=m z21)@JwgROxHuNXR=;SnYpSqTrEibN|R*t@Sm%$BA(3n1}!eoQ&8uhmJ&u=99k16y& z6lzyvI28jw{j>~hNrtswkP$pAwVX0*xpytZBJD>Hvw|knbl@D5w0h?jwxIP0)NW9A z2^01MulxAmavdrUeVV8Fot7u`St0hZPzAlATS_3Yo4?IIFDDgHjVZJt4@;UrW`oMu zM6}1UDDYAdN!HP&W#N&B5U$H}me5R60F=mQ*L7aF*;79~q1!H2Fva9njSvFLu?hp8 zqXWcapg`bWsqIi^SoiBb5ojPuM2i9#!_R{Lyjl>~vGp0nMa<0-O*P)wlZze;gd~g* zi4ruYMr8Z}vvW+AJwiqXx(wP-BjJvrzBkgLr4Wp89!Zb597#X2cPFbFntAL-X|MFU=5osSQZXY?C5z z3y3wQ4)hiWtHzQS9a@|}E*9lcs^;C$B7zMSYm7v%H1+tSzP_=3{QksiPU@A2@wxkw zkFB0XIet2rH(xJzWas9-`R;)gJ7~L|k?=EJ(S2`}4Wm@D_tbJY1k$Ha zt|fdZ{;e)!>j9lTD8;(|ay59fstH(G?EAgYfv}zs9LPguvGgEBxS@=+6 zxYmNRY{9y$s;pMJLQ=Ad>r{T0*k??lg-NE?ajp8zf9NMM58yt*E4ljw`wmQtQ41X~Up>7vvl8&3N!l-a7-t;o>}vlz zo4MlRLN*JpaU@QWo<@CAG5S2G!sn!ndFu3Av)KH#Eh^L&(= zaJr&DVE*5^j-E$NOF(l$NlC@i*NX6VH73KYWil&1JEWe7lZ$+Ej+F_erw$jl%@v$H4q5>ycJ9yH#8< z1=VzO!HM;tL0O52&PXWiMfl%2?OC5Wj`73%f&y2P1+!e|!5Heie~GS_QBf4$M>uYh zY1HKKN@1c9EBS8UkZ_!SKSfG)=K4>sf_JYnU_U@OlOfL`UAI1mb!x*Mvh?N6mmSci zR4BNPb$9^@dS;%9F9Nl=-7ClW=kYK3$CVlQ^-CF`lbu=RFSq;OeJ~6Q<7_CfJJ$-o z(h~hJ*HR3;UwGxgCK2SX#ni(|qV+wSNi~{NkW#u6oDhWRr(COoeCD!<6u?gSluyrm z>s&GtBl?n9-X`VbbMX2n&t%|Dowoai`-d4OCIwD7o06aj*UwR;2fwOTF`M;^R=lm}zH;9u4f6A%I~ zn=|yeuSYZ~M1g(AK>zR7Qyx<(gfd~;c26lKD`96+T zjrH;WZwAV!6q=vwc+kFYT=SYdt2(D4^ssOV+(f(-z{wJtRkP-xV{MUneI!np>WpL4 zqJuZB!qSVLZ+(S<FvTJ))UIdo`QQuJe+gFdK-wRR% zX9XWsJZNxLB7-EU=*@M$;s1wgz)_Oq+qO^COcg9vPLT|}XOW0GaacfGmtf<2U+aDn z(&1NJy{7xft#*EsMm>xc;&dq(ZbvUaBZL*H}joU9X?m6HX*+r33| zl7-HqlXGyWQ1m5H@+gCj%-8eUl(Hw+)R#Z@>hQnw>i>89Wnw5$GWSZRO4QQl8utmY zXibKdY{N?WK@eUZM}3vfKImkzhX#^|7^o&$CO{I{Z^dZk- zO%?dOe9fe|cHP>#nsR`E<5!fH07||htuXI77Y-NCNUZWu4$&!4R__-iV|YW;jO$Ar z{Kg&@=D0igsB(I$;#_@kFKKblplInrz<}O{o1vY9!<@kgF$wSv+;mP4)vC??E-MPU zCr=*(u`qeEAO^4f8xkZb$;fzq@4hPgpJoVw8rYJrsiD5yzbow#Ov8E}2^QPDS+bFs zs%8oISe=}ZoGDyfFqh1$M*SjjG}U?V=8MHsm0|IY?nYS+5(tP>x_!n?bl^~Bo7Lww%qo7iIg@sCmMY6oRgzo))9_*cwznRR?-w`wJ) zv}Ax!h@auU@(}omqu&qz8&Ef_WQbrPl7*d&t6W~kSY zikg6jHnk}t8OWh%sdW_Aox_=`#xdi=ZO)4wg}tx8U^*I?31N2g z(*`y_Vo1Nc@sIh|bsKw$C8@gmV3JKD>xJ@+2ht&OdW>_6*RUZaJ1@(gR~_j=u%sxf zHh}@nSqG5?X_O8p{>pD`L56PHLi~ESwl2ZOB59M0zHNhQ0?mmm0c%f|t5E7Y={5>Z z;~U{iuEBWM4YyKyilebie!Tablq9CR;L(}&)4zavL#*%aP%t8eS^n-A+#e8UaCOJJ zUb0V-WxXrfpIz5cScq-dqmZF9fVNY^G(l-O8-j$dUNG=E7`p!|Y85Sd*OK^kzI~1x z)8ls<;<34Qiz9@upW0|+F91`$sHi4kitir-SlSuZsUEld2R;$*v<4$?w-{06p=6}5 z21QQ2aakAW>@Yul;&}2aD|iZrh?gepxAVIw@GM;+pt;gdUKYfyRK?)UVW$#N$^_>M z&d~3P!3wwFjAr0sec|13{xCLD>OI`gL$)5yA9PPu1#^}ZgRZl1302qU5Cr6ex|FUwpeft&w$ZsT zxvmx+UUyMpYKrbQzD?hSgLf`%=I)y)zuC-RbFQ)l%usX6BK8lz`AqHmS=vY^AfNR^ zg;s5_D@I?>?t*{8d+xD6<#1s#*`~+>wBHzAzyRfk=9cR%dv;Gv`bNZ&z6UsAo3F8* zHV(Wr@qxkskI0(s5=|65x?_^8)%Kap(7LE>Mkk@C{K=?teSoA=CC^c|NDc%)hA}x1 z!gL8Y@{<#ZwqnovYW0@=R|lt~+Uoip1zSEjKdq&>H5)V^pS7)js>j4n05eojEnR` zDijg?YEgY{*WCvGP((-RSg}$2>~(n$DqJfbioEUR$o|w^dchb1_=o`_Dp*+u=dOim zotnWd1IyH1+Kw8_>~vrEqg67;yR?_fl#F13m!awSX$(hkUQYtkZ3GA6%e91+I#ES*$S#2K8J# z+)flm2v61mJUM)C=Wfx%LzIIuD9rA8QX5(cLEX%&bxI8Rt7Qcw;C^O+y`7ix!6R4B zDY?zhLGRwgueh;82^0Sa+gMe&Vlew^N6jK4CyBYxEKHW^%scYFs2SUfGT4z{L)6It)H>X9D@{ytXefO_#=OlrZr6rH1rDL*JQ>`aA4&Q&6N&n;B z(}%f4D~-fibrm?$1!H?r`W~yHt)5T}UMp6cxbb_5$@M?v0fy`7a6TVet3;m*HHdS) zNm<$sK*VQoGws8KUC&Db2X6$c&F^k-+)^CeM1qNrt+{@ z2I9Lu#>~v$SA%!si+?VJAn1_sA$_`4(0(#I?rtQa{s-?@+-#vPP9;0;S>CF=7|h!` zX4{&$L-k4y&I`vI(+;9`$$H7oYP$xn+0sYW&Ln$ts|_XO2Ex%Tf4~eC1KaCH3h!&I z028kR=NqhE@XvS}S4BWGU_1bgfWv_DdyS~2w_x0fkfJZ>YB-3B!}xswBg8_l1f%#~ z_-Q#Mgh2L-y)vx7320?{n2pA(G=}z~C2j2rR#ed^&jLX33GQ3COdMQEBqeG#^bssl zroc!P1_B7G>Z<%IdHq~CxX9MKoN@I?VdC4Dm=E)@t2ewwMXCL^BL)t>O8t*HX9-ae z>*epcn5;+E4!s?N`#El0Vb(=vG7_)GR;kpa`O(+x!hbdZy0QRFxxUXLS1p6epBNo2 z^AV$CB+L^0Rp!I068`)3o&LD-(%w;Xn zC0z&G95q?PSJ+4_$zR#!olW?Sf0=P9BiS=<>Jp;VMRLKx=9T)6n zc|P40bbsDu_zfnWR1)m7|9tvj zmS4m4PGl|0((x!^#|XiT)>+-37A)VaxuXD!wc(sJv!j$NvvAZI!9to6Xsa|hxV})o z$A*H7YSXdvcyCK#Bu&$E{+&|6HqW2qbj0r^NIs zRNOW&{xJ?fKU;nQV#|9BD_9-8lv+6!&q&>R z8jCoTPb<%H7WuO=IutAN%}2Ry$3)YBJW+L`p!i)kwJRWl^bSB`uSsBve4}5@(j|N5 z&j{o~I3Qi!-d{b-l?rF`y7KD1N1x;B`^w0@EH^xHq+SNK#d_6Qi5XWNB)|&uld;~2 zAXKnql$NcNn4V%}gU#QHb7@s?tNXfKI-|TrWY$9bLm3HGeN4gt#10*2LG1D*?QlqZ&n2XkAG&^uG|uPNM5brwv`TcuZ!!%lniTP+^1C;I$w&&Fq{wY8pEGyH1Pg zNRd;RCxxW~(ljVyZZ`Z4MbgKIv9G&H`^7Xs9MdR&3X`_StpjG{MoMK?%bo7`^UWDzqeTd`$oQxiCwew- z!Nk{=-u-9PCrt!XuKN)qhGgt3P?@wX*9nh%$1xc27jNcW@=Z36dw3)>pY&It{$`Fv zrerLqtIS95qzmpahV;~Qi_ZA-r*mm_G(5Uv5^@J_ zMxuh`7JZGIYK?VcUBG2rBj+W4u~8MAH<%_U-EV1N;s-Z-O*dkZ53vv~t|>ymz3Eq6 z9DTiobjd5G(@UzOlXVkij9rIb4BD#lMD&Ekn_Txb4w3~!(u1eG={g12HLq&r)RWR# z917@vaf?&WAylgXG~mh)zsO83JIZgt?8h3300pw{C(1Vd#3!KV<~aQ##drKn+%?_@ zb(3@1Oz(F|12i*}OXr^~*IpEIV3M!<`Z;bnm%~S}^NUy8i)ux65;$LBra~uejZn7f z*6ON0%7br1DHT+(!d0(i!y^l%OOXVPp>HZ;w4ZY&;s2xZ%$B7kR4VuUE1(R_|GksrBqkXG9>J?aa>eT(+u zabijzeO&H1!6n?minP{v-T$&2Bt*k{qbkJB!iO|v2QN1zJp=RmLhkL(*k6k?)}duh zn2$QR9oNwHvRq3Ny!W{H{!b={XqUE^wl7+xAzS3?j@t?oQ{!gx!Kb_KY5w%^+KkW4 zF1K2-<)@?I3KNf6`pz$Ec3?2GXU1sJ0vggg7lz(!;#h;c=jg!C_DX_NOTOIs^2NL@ zo9V{#6mzTTR>gXgZ3MHHE0Kl%lxMu-cXS!@Jq47_LYZYl9LA@i(C}L&{GjZ6xI;R~ zwDhRBn<&BYYUWjf=~|QeK-;SxsJRXkx6TvemMrEf?#)+C6dJ zWKz)jIv#sL*i9$FnF1zW*yvD$R#B*!9=bNTgoM$;hI!3omjhGPe5LT=NK>m^$^Y0B~oi?rG-t%00X-ujqDD`&p!AeqlVg%rYE_GNgM z!hUe@Nmfj`e`vh@YdSe2&K!R*K|hQm+&SDbVJhQASuJ}qcM@N*6iRV7-apHL&hGVJ z<%>el)ZkLOy>KW<{el=+GhIs8CbbuBgiEqMOUHVSr|0qSl6Ym5+O7*A;Dq6q&jo+D zAUq%0qKGXlV@#BfNS^~ZIj;SNNu@e*5@Cqifv0jxdO&ze0FEWk^w@_m7a0*!I=BeO zxq1_*ConJ#PYPG}etce@O%Y2E(X!^z9K^^68k*Jst&0rGh;HhXKXjB<7FaIj4n_XK zf^A=#AJ`yFBi+hrmfrzJH7f%XpC#5&K|JH24obyC5UNC+4>Z7f{?5lvKVU;&TG5G3 zf7sRi=mAM7TMzliL(9hOLmHg*y5KoUH98pgtG#Sl&f+0vJs1tk=6A1fescvs8ob6NWa{^o2HZDn^2-K<;6Bv_VL9u)cgS*cj4f<~+g6GeOKzomA#lEgx!08q zA+BfU;SZU(ps80azt;B1K#;u6+yI2UT=@p7R}K@WbuYa$eBIYz>YBkse;in0y+CaY zNI@2$14WdmhGbJ9GF+_Im>ResZT}9|ZRrOVoow1Qa|I*~cg z1vr&i=j?TiFu9a-F5RNkVztp!k1{D(i!4DValY+XyZX`Vv*o2V`hPK3f=E3)DFU!Q zkx!bkY^rkQ8H9C2F4#*M2ypg}W__TJR^b5PHP=vOD(PSIY4Tkd68f;6rVaV2ZzJB% zh2%DPsJMth>%?R6EwY5Tnqxo?|LUZ6Zo=K679w5>_iksw3Vr<>$arEEgT+@n+|7y{ z>xs%^o?8_sntb3>>>6M&LUSsPYb$iq8TX3@1f`VL9LgG0qy;>D?0QF}d8O1qdE~Np z8+O<$?u>QfkT8~s0O*3BPxpiFQ8!tFDUcPif3EV|`<0j-IV&$2v^*&P$;&qT&i=u78)USMspI(!^-u$7Lu$S$8 zyFy>tbcXN_G-I_cOXJm2rk84xmxg!>KECpV^_F6~y(zgbhj>BndrW}PmQ7;=q1Qkm zlkPuI1-Sl$dG;20X#w(@Kcjklb5mB#qvhOu(Ye=B-Z&hZUo^J0ir@Gz;*={ZyJ%z~ z9oGICxtK65nSr2=G)+jgkY~4%cqahwTbofN_kH0Y(JsL8iEa9F7ZLx*i{m!lE;8|F zo8`xM_)BgAE`JNg#E`6mtX1)Y>a|`1;{^G`>8w@(*eV^NqaIx60LhK}*0m?iCulWg zR&xC+OEyu&_PeAGH$py+x`VlqE1-(!k%i$gS_HF&1V1Phc` zET#~o&WP%5j~dZ;Oupe_70IQunl-&i{y5a6A0)7kR$~YYQh`sIK5yfDxAHXqr~uJ4 z-QwzuU0>uXCA@4Rs-gP(%cb35hKP{%!BL$vhai!#-3}#6vw1gAX_Y#e74Jl5cMniP z(j&Zk#^uteXBElW8}OEw(Ka;rR=?8xhM)f8aWHTy;uTE2TM&MOeuLDl>%3UN)3tc@{PgpnfBPZ(wM$C#}TR~M!?g!<#MRF^HS#pb>dReL8jr}(9n z-HyZ4wEw(o?oD-IdU;n7qL^%3a1l>Hw}3UbLJg#kd}j%AF-BPvhe@k?S#T-ax@hrK zruR1J%tb5Rqa`cA*RM8Rzk6Iw|75+>|0d#On$LVs+0F4uZ0-O*`cq zv!rkDZS=c=8rKo)mD1v%{yN{`hC@X%vL~%$ffTX{4+v(0*L^~Grt3URP7itHxj`!c z_hY=0d(sQiN5GtXdm^t(6iDbEG9|ayeXZZq{>ZEWD|~mJ&>Om_uzMg2Tz4|QB_Ck1 zWc}WL3x0B9>b1gaW$S<`lMRDSc7b zZ&~WHT+5Ql-Njh|Z32?#g(Ib|kKrcrkLNCJqo318xeI^J{0rd|9r5Zg zp3$<{#B4GJgwU?ORHisW;;c~uRH~SJI$>b2>|03sF1JwP+9%>u>$y(DJq<^`F@PKD z@u%zy`7g+n0Fw=_=MWOuNLO#%$xR@pC)Bjh_C!_=3rHbm4lCH!z?pkmR1STp zc;iM)*Z!FW%2C18*<56cH^(#DjzZ3|1x6gcE{?H6&LZ8n*D{>t$w_8*jY9hagJkz|38 z09k|!KRcO|)fZ*M6oX81qbH{gv9O9IWf`Cj6hv*=3qgxcbIZmGVUyqbY1^T(9HN_> zG~)i%^<9OqlDoCqSUfLFh&yc?U}{=R3BknI0OGr`tC`}`R`r4^_(q{Rv+K7&5?QVy zF~2+`qI?2X^B_niQTAfjnKy+0ob{|~k}|fkHPz;eWs26DQ3P&z{6$SHW-cPe!O=>I2<& zfp(+5VD)-{OViQz?};H2SKWb1^=⁣ZxA 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�&pA3BEis%aZf}4Avz+ilUryDDo4C*@b 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�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�t5!!kacELQzb?ekJWDyt;J}%U z5>I-q`zv;Iu5U*GRFGx{7}XCEBn5tIyyoUD1uNV)Ku4g4>L zCSBZd)h`?p_{!{X?pr3PAG-t$8_0;4XkS-FnCMgPEP9%)SLVYGh%x06-m20$Asp2J zHv7z-Kr4Qh%uc0V^X-Qz>#_@NcI6H;lmqCK{^I_v;gW zATs4?(U)vwLPChmT(bEa;zQ+ib%%Hr+xMHlW7klH*vcKdc&~w0%bAUnT|Z2hJla-I z0~nLRKNel?7NbGw2g1G@C9Lh1^u4iwoW6?k_`sjT2osj_zkXKi;P?MxFc7h?^!*=4 zyPCcWzs`Mn@_#t0|7Qz$l=$k*SVdKTw6i!y=b}KGq3^bdt9Qn6l=ZQ@nz?0~d*_n) z080#d&gKg(>He4dx%UO`56eHZ%6*~s#Ig-{zT*;GxZ6aSy$leks*?2ff^1xzL;fc8 zOXAW0L{DsH*>NQQj|~m9QC~5WD&D~RlD6lv@U6GyCCn@q@2!3#HN9?jjld`(X0N`; zWgGI9+7H>8yIWnA!S{F<*>(TpFmqMJ4~g6QN_Pn#UR(1@*xrPE9Dfg~`fEI48HeJF zxoY1X2eBJ%Xpk2)bLv>X;b86TcIQVtFy%JEzE9tA`EBf_6UELw2kC943w_*qs&O2^ zoOQVbMw3T&%!B15xl#d5yhrr95Y!%_Zrg0aRRtDT#Lb2{{}Fvg(W8vqZ7kcXJ3qMR z>d$x2H>T-BF*blNb|D-at3Sh?eOHhz6}*BU>oH^EhzP1N{KCp5%0A?XtWQ&UK=(7r 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�Gax8F%OEoYIeE&au|0K1>RH&RUXnF$eI}z; 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 _`QoYkl_ZEVkXy_%Y)rdmJ~a4F_&VIM0HD9 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mLAzM@s4Gl>nCf+mc>HmYb~0(J9^3vr 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

    UpJxYASXvyolf057zM_(M3I=_W1s6#$ML3GP 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

    #^}fl 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

    9+kmC@l7l>nI4hkXKRBLt_Ud@^~a+I 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

    a&CKv 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�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 s{V$&X*Be^C`tR-c|J(MlR#&K;qH&A3?4?WdHyG diff --git a/docs/static/images/rocksdb-secondary-cache/arch_diagram.png b/docs/static/images/rocksdb-secondary-cache/arch_diagram.png deleted file mode 100644 index 696a376ed8aae7eb04677056294a21044e233239..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100745 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�|5@hWoX&+Mc&+U54BL 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�Ll?QXh{OFDU(an0`uJtn3D|v?-12(j-zZ0* z`zwLlvFp0mhf-NIv(Ve|&tYV0K~~tU#`KZ7)Cx&B`*kVo>{K)yUuWzj^QTXBCxD4Y zG(3L#Rb!jXW4v+E!Metf`$=s|0I=GKA7Wl^cbnJbw#h8V@%MvrCugIU`i9pXRtL;}wpd?g$71iQpi zwlJ=l+SM^z_BnNrNNikq^he|)5%WP}--^nW^+UwoB){N1w|24Baj;(j9c0lsKCNc) zP;_UbFwABi9Wc37{>pBW)J8xPN9f!8#R!h=dteg%JCt}7ryoQ z(RA0+2rndeT-zlIHBneP;$>x)zQaFIkICDv*V&PF=!**Ps>gVR43?R2q=22K14DTv zHW!FoJm^-Bt;A?7PKr5f0Ibk z;;v`Nl&Lete2k06?D-1@9`wz^OP409gZ+5Y-8@t82C6TP6f7J_e>RH&c>UqRnkFvY zRSG5jRlY&hSsZoJ>oC~-iF>&YeW4S6J|hqf8&M>ErV{3n31mX;bKuBsGNax8I!AUn zJN9Y&XyRu1c0**h{+d+FM^w6Tty?=?P|U|qD6xFFJ5uda-o?Sn6{H3_%O zTn}E%%Z@Ck>+>Ob6mhg-`Lsw_sVtWvt{QCe8P15(mgsj^loJinaJXj^ne($Vj-dDY zCU)?UMS)pC0CbjREevxVys4sTpDmimGN!BKAlA2Xmf7VfPI6)Pt*v{XcDqVYNE(HF?PzBb`;^xx~@0-_^S` z5|uuO3DK0-G%MQ)?9}r(KKa@)po3u=A0Ck?FB$O~@3ma*%xN3ZS*$~r^d2f?bJB9p1pmnB>MkC6T9oa?Z3PQyS9|N}Vz6Eo=?6c+ zOD+im69Q3MgYaG*40f{j`OauU> znkHW(ulh;#V;I}GWB3F)7tZgsBdc1uzF%fnRWX|J$)jeqZYyHcM*1+KhTmqq-U16L zbi&}qRXDR`<9!B%-Y^h@j8=E|I;o&87#Q|C&DT}eFEW%);O;5xY$Ob8s!)@^2TOQso&a}sPC-pjmgl)bO)y&?|Bb-pdri^ zB=)&WEE!8W^UWY^h>96Gcn&{%6cf5DGK$BSW(s%6I2JQ|@}~6lrf_j((KNyNi(qG& ziux^;-d|-4xA{st`OcbM^0*P1O*JPFQ{pChx0&&inZ(uR+)PPUQ7p5zKH^m7p?G}h zanf1Kq>7vyZSvbXWri@8?d~06mU#lafLhDR=B^iIfg`UQREJ9Zo}jh(KIX`jZMe;q z<^?CT-YR>hkdlvw$RKpGT^n^GklStk(PeM9Cpk0Nu0ad zzUHF}7NqAzQhdn`rA`fv*C3OYpVs*yuK78Aa2LdiW#z&I3sG2+L4t$u)-2Sl}?t;6f?A6pLpMCALyDNnqE7% z&TiTK@rRw^tu4MCm4W@}D39a1(q1oBp#9rk(KU7;mxYy_bUv&konH#wldc+-JeRm6XqtoB=k@#Rc><;UfTw+no(&zmHL;Gv+OJbq2n7p;V$E~ zhex@~UPEu9Yeo^HE@5U9_N$eBVH4M~cqoUy4|%;6qNYLqF0HxYHg&4ER#j&#R@g+p zNSJ{3JJN8Gk?@$KYWJ`{kaVz3$(u~~{*Ah}Rqfi$MbsEw3EqHzoT z9T+hlPW^_ceCLLAbTSEpW>%%63Y*Dl2e22>ptQz<9;rxlYZ$?f0o5|$DeV_kwIj)f zI$+%^Mm+ZFN6QoX-ygiquWx}7~i-kR6+LS z85-$4!#>?tt9f~KrR)1ZopVWec5d#TK`S?v{#7iWey0Eq~{E zYg2#cd03F=DH@*Sd7p!51VTZc*Qi2(`({lfv0kw$Fw_ zN3|K>jU3%=s`VzrxaCAuBRN(Bzs?vk@&$D^& zEe1srN8}Sg=#h|psWNFBV_Q04nIh_8>ZLZ?5usn?oLpz)=YEfTbSv|W(>r7=TM=Lz0 z0WCIFR$fn_24Bxcz_+K`=`D`!^cj1&clF?+%cR8PO$+-h54_G)s|?GymgI`5!yT2f zDqmt{c*C?_$k)ak>JPHnv=$(x5KGSzqqxhgt01#AQ+2%dV@r2En{`Fk@1*!tZx_%z^(<@!E7Rf#?lV^mx@2`0+d& zs~xKw*dJB#%QMh!V6sg70jlZWr^ET5U<~1rFoJi2q{Q;z4^I{`Id`k zK{fYae-2uu!PJv4r;(*qwmYz)-vCIgz-|TkH!Jjq$tqEQ%^ndDO5-cC#sP-Hy$CL4uy&XLF2K|6q z49+x@(5bQVQf6HvdKOE2-nQc+IJr8+J2sZR!F^7X+hbXFid@>^hDOa>7&ZUkg-u-= z%Zw{_D%^UX_fe7*U!U!qwH$v^Ut8D%9FmQ`WgXbNTPAktW$dH%xTzdGgm}aJ+MZN6 zn{!Y4TkCO3yT*+UYD2$RyLHS~?%YKQi9lt!J@;6KW|knuy{}Db^w0_Vve@ADP!_bT;|Bq+F_6Cs8#MaV`yL zgP-5OMsM1xQ0Fy!tdNvNTfWrTSlM&mJeq(16EdqnV9xL|PXUXdwHkgR%UVQ8Cu)bHVq zGju#>pK>KTP?kCp=|6}wbaSjB656Fm{`hp5EtzT7Lbe_jx5=;O^6mb`KC2W7=lYtE zda*8eK1!*MHe+MmTpzP?&4ye6>mAxHu+yc3sl}KrIx%7T*+Svl=Jk+H#y-_UFHAV> z?P$N`|dv#m?p!M5It!Fnh6t4)=?#wta4cbTT&)RJd(?c{Dd_*u-42K_v` zmf}Iw*yTxGx&xudnRNuEeM5E9i?bwwOJ-Q*h8u2e%IC` z7xO+_)V!BE=er~4zV8Cq*^zt)!|`%cvz?kkY31Zk#!EjC2cx}GY3lG^ud$!HGTe?y zDwFvJ=YmqlKd2IACTZHX`o?jQXF7eGd|`3+4ZJ)<(Y7Jl>{()l+bj*Dh~$Y{^j^ns z1^zfX-Q!H*$|CIC_+~htt-x4Ijd$qUuJ4|ZW-61r;hM0Lhd0^d<)hdqYVfO%mVDb-uOQ132Q>6;of z6Wai%wUJW^Q9l^$x`0W$&&}Rni0Wk%^heBp#P;;Gpa)fOT^AdH_A7)q zp?yZDl3EsT40=i%RQZaFNtovEInqfPO3zrkNZ^kVr|EGo3_|9*K7Ds4%KO~Hm+8DU zsC)oj-KrKX+ViTovHJMjvSz3K_@`arGC^CBR#^&6Yk11VP5$kjWqLYkcwBH&-ppq- zzF$j77s`G%Zl>*Q(1s%s0h1Z5H4eGAoj3oe<7i!;DFo*CuxD*?t4o;)3G-8bA3y38 zKk>OB=nkZyF4#^+?Ws5sB9OrG@nf53ea7!gU*_rkr1w1AGgY!o+4FOg|J!EC&o_LF zsLZ8a8^{G+tCwLb1qql%xyb5O4&A06@5#kisj|5inXWZ=Bpyn0vxg|$pjh8?s`Ot7 z_l%8FNorSBo_~M8uLliTg11!45-uiGWvkY#LM?Vd%Q6Dr2R z-`^`Y`P~YNVlE61ow1n|>fE@S&_=B#n8nndGE7%4cGb^JM_~M&Ob@E`&DiXmR%L$g{of zu4qZ@`n*ap`1YDl=N~QJIvy(0xRj>qzLP9noHQ|>$wat9$7l0p&}skk!`0BfH!iEZ z?2uL;>EalQnh*Bq-mB!XXX|h*#N9Jn-n1R<7e`5EQs1$tj6GLJE$2ttE8t_1=omN3 z_A_Ai?VLmScViK9_a8|r*K!Y*l5S9>y`|U-FF>_LI(^}?5gEM0$hFzkcJ8ge%IqBf zN`fFHK1@Y7Yt7tDcAr3!ykay&yh1%=oEq=tMRUmbIdWyc;LF&KH0!O_SMbHbt$oLP z4xY=L4hdf$fi3lOg9v9PJcja7)-4}SzLzbFDk>R!gFiQI?_e(U7Av&hF&wABQ_@MO6(v zNyE&MKb;A0e>M&lK8mf9rAa@i=wb`aP;K2(MOv^ZYD18N!v!1o@x1Jea&oK9C|`Oe zX?Ce2yZD*03U>#nJ6*;~t^=+9751f!q~j7kChA4AM33?19bx^MfxXuLZES6i3|0wV8Q)J3W{mx{pQGr2DZ)WTF@fAPLq@6hvkmv&=ETF@EyE6^_cW}FSC zn_~C(_0+uE7wljz^qXRD8O`P=+l3*jj zi6TsRCLpEa-Dova$8XhEA`y6tp&Y*0w_|bkRsD&%x-PtRw*E$STI)jn2=qJrV_zmq zZ_!Mj_-3WI%X%u>g?sP5VW~ZP-6w9TE-!3^gXf6&J7#qK_7dgAX6Pr?hMuL}!+baU zzF~LmClx}>{a^FXBtY=T+_NlHqi;?3n-n-6iZ<9X^{gwCk5S+TsWW=_+TB%k=%{et#ld!wV=H(kI07~=LWzTCO8I3$Ux9H~4=J#6yrSl~cm#%ZR9f78 zBWiQW!t1fJglAukz__^XBwAD*d7MC-;5RqIk7^Z>WQzJ(aG&)h96tU)u3(i4wWo61 z!{Dxj6OlRP{$7MLex_T`s-lIgdUe5gIJL@o_lC}_qs?G{DW0pL#@K27V_EX0?S#rc zr&cMhx+0#s;7a5O{qTrC*6)2yLmzk9Sm9<%vGdyl5yT2h+2~ar)K!^9wwLe6f5vhM5~VE;g^W{~)(Ub6FEFXfv34~pIXTi#P>rmGlhQ4EA4b3!U+C8>Ui z`U2#-Nlgn5W?+ylvG>0H)Arb~y#O{ccWY_&8AgIF32o-;F8rLc)$mbnMnD&Qwq?XE}(tcw5W#M{Y& zB+}4jO?9~MCwdRbmjD}2!5&>7K4vudIbMb5I9)h+$Bv<=%g|+rC`hltLqA$FB@2aw zz4c86XYk89&GO*<=*ppe`f2``p76!BeVu&OAL**sGqfNqz50r0Es^M~38OG(Vs1&KwF8HRMLzZ|?28y`MK|$j#hnD!b{KJb4W(Uivl{=|rED>Gioy zg@{a+b*8$*tSj;MSG1YRyylge{cyK^B1%L#-c5iYR2{r%Y2aCwY|_HT`hAXyB$ho3 zxRbSfUAT(h`Gw87Vr>SIKxvWAbEm z>KcCfJp!BKQ83eZ<{AcGtPfgKz_yR8(?Wj{Z3)am|8YuNp^UB0(mre+q95vvzsOX% z_~A!l^>&UbHba;%bMtDvNMZ-f;o11tYXbPM8pR1(9QiFWP0?kwOfTWvOHSpe4@qL~ z27ShFHe)3gKA@mX!OGn9D_b`!M!E&Wl-+~+Q^>Y98q_o0WKa*v79&;2@5*p*d3<_# z-bIYh%3UU7za&G%;h8dk|!gM{aNG)jJ=1KqyP zV&#?JE%z4TvA(v76^EtF6|t*>`!V4+rZQb>Ep<05qK+mUy)#Q&W}J!4_Z{zhw_l1o zoLp7pua7>rCW%2Iu#{iqjQpKqy5qMoUgHJdQg=Jr(k9XnWJO154}JzfxA8Q_mX(RY z%~Za;;4ooj{VNy~ZddwJ#FpT51uVTUk8uWGKT_ibDxSC$O}QZU21LpGQa7Rx>=WmU zAKmN6!Rw#H?Gc~d!Rt?&RR5WA4~)kiNoxUG0D@Dscch@Kj9@tHOSudrTbVhRWnw+m z8{}t{uI17u%QmvjNID6+>Y3v%Jx_USxBMpaD6o#`ULz|Q<5s^oAufK)RX|hxezO4= z7(^&?eqrM_mcGs{@^KnmGrN>DQX7IFKRx|_I|$S0K6hIhW+NAT-`7BfQ%v@^9=hd{ zxl7hKBnG!8JSw3}<~Os`O{dJ3(d5CLpNkC8`_RP6OQZn~hics+iM6Hx=Y}m7n$v@Q z_SKiBnLOsR3{3>^&ig=B5p409=V+-g=6k$Udp`cPT5??OT%4m!%mVC4$}r(#@~EEm zYl5$;<9{r_D-R^dlA(Wje#{&{t1{J|3*2A&MFq^@9Q8`W2CUyQ`dHUN6|0lW+Ui1` z40q}?X2jd0{PI&$N?-0sOYTTj&emqb#gA%!x9Uo37w(d3j6%t6x0GqEtim=PyvtNm)tc{Ka59?EP3bduP+frF_9xw@GhCro#eVgo z3WMTvrfxXa`FjdkygD|d+GC{JQ>ThKAGUGlNrk_;-^Z+|4=$h!QPJT@R^wMnRe7f> zvJ11uIJisQIOdmx;J7?1yX`&~z}ERC{7T#WuD5km3!IHr{r7#hutaL!`wt!`-6F^` zeEHS>5?(6xmjy{|cpC0O!<{F63lAr+b3yPSy%8?5VANXZY+f$TRwnN`#tk0m=T57( zP=0($FqRVt@z7L{62Rf;rP?76J23ME^I>#8&K9qp>TOx0i+J=2;AABPUs^m+go5&E zCI!MQ;LBUNSt6>caDPc{xd}dYI{z=Jkml3_Tbi(nw_iNuW?!>E_w@5(;b%8qf7P5T zH+!G`IW*2|-0`r;zm|8;k2Y%Wpulpgu{A38nX!HVABKUS0rwtrW`tKXujqur_cOjCYDunU;W^etazbEhYrFcollQBlvqEM{E z1qX+EaWV!-Z(DA;{a4T$NAvfNM3qfm_1($*JM`2wkq>|=YmgAKC`A zKQa&6)-{AL9^E2u7~r2RlYXUT?CFdB$k7jM#zcoO)|VR^tDdTib?2~Njoz*f#>ED! zjVuE~_xu{U!yT)c@M0(WfjjyTd2!K(M9n^%h=^591hgNx!+X6qP8(R#+9;*#y?3xz2YqMYuK-&%Nl4cTSVaN2@>Y76*k-XsQ|RjQI( zygPT~@Tfw`y41aRpxmtk^2eWVq>WG|k4{WJ9WB%%PnCuCRm0@!-_4qNyxR?GJGOj= zlosv>IPQKYXtyFP>D+f$FmcmYZ|fWxJ|R`|5!r{9>n3!`)4{-df`0aS4?)uco{vLJ z2nv8rCZk8=(Eu(2T+R$?KKr7qm;BfSZzBS``r6+SE&~%rtg`O(0%nsuAY&u08coM- zf@!#H5VmGT%Y=9L%@?%XEWgSK1yz_G_ulZkJefyw^+|cacy4zc?Qh$%f7rNIsfeUN zShQkhXB%IR;kY&+q4G{{lRi4?#K6^D4yf{sNt=#BDz#DJnbHAQvwfb+I~8;fXNYvX zIg@e}QKG-`j(15Lrtrd%wMU0Fbz}1a)gCg9>Okh*cZB1M@lxR_4!*0Ekl~C2GLyCs zvQufCLC1XTMgE5geLs&KsBTW&-&Id&^K*(@@xCvVj`AK%GcYAI!2Y8*FhrO5ExoIk%5GXiPX88cqLUKV!=hB-K)AC-Ck$5Y!^iJH{ zKww%Pka6N63h+Q8wU6H@s>1E#OgP5Mj_}X3y8r4%0eU;6BCmUC@@@D}nf~)rsPHXS zD;?x9Tx8h!Rxt*L$}PnI(5zS*GuA`w%Q7&(+ZymiJK=kHFV@Kx5naAK$;Y%L z%{Ja><$)kWKR+OPq-P1(mi@M(HYRmCs=~UB9?uj+QksMBE*qwT-Wd#Ku2s{iM-jP_ z8&Ql)+7k+|Hdzaijg(z|CUQ?+`Y(h^Bt&85W`h%MJS~K^?l;&7%{MTPA=sa%)ik*^ zct2ON0@xjJ@TPL}_aFN>+|Z}Tbo-v)-nRx7x8*1bv9VE3d-RjeSneo(JlU*vgU2T7 z!zbA0{T49m7oZpRf-8>W;oS%y{o%;)`}>fqR7 zfEq1`>vR;JOYS8f1%faNbp`s_fP>YK!>92JXgL%VA5Wyn(^#d^`-15~d6o>vivn|z zeq;cx5(&JT-}fVa)gRLT4X7*l0}f!o=PjA?b)C7Jkrecx$NhBr3c{; z@D(^f=pb_EzI@9y(9vfe?~3bYf6@y&qpole__*PQN#e`B+K90He}5Slh5R9E?6}en zz+ahsA=xq`Tac@A27%cKIWf5DSpFpNFP&)Cf&oshM`5l!m&ek-e(p4r4A-n!ZgaE8 z7gv3&u9a3=gX2_Hb55@Y_~z<^LeWeRrpMEgwSlvu!N3_pdci4rP2h3z1KuX+0nG5R z9>!&ZAWU5aPuA!PIHE0Ue7Wa3ky>=C5A4I0Y?;eE9XzJMUZ(eQA&DYn0j6$#4bulL zy&NnKP1R;EsCftIyYHz=Z1EnO%8^h_Nv2ao-0`W_k9v1WRXR&%#-1A-s#3E@5qN0DZy~qF`p!(TRzq-$U zU${ThIsj#-Np>g~2qn1y9^xoQDHi|<+1|X{W7_})J?nEj4a-)h{+v$Cn3_GWdjFFPh+fd(h)4OsyRhq|!1Gq&kT6SaWlj;i-xr;NSoO&=oc*N?Pcq87u$ z5ju@}qB?>h`?4Kc6rnKnt*Oo@d{~ zHuZtP=W@hJqAxC~ehR59!AQZq-a~#*g_sGDILd|(bYW+zhwEILQN>~QA8xPn7lYJ61Lu7uu z^k{jix>`9;6M~1rr5<7NApO>MCh`0wtp~XEB^squZxd~JY`-anZgQwe0U`%@9iB^G z0+Rh+sdZUnNmWz9U2-oC9s~>})uSNL_`hPxtPo;qkQ4vVUQsjt z2_ZrO04+!EdUzWZ5da!aRr3QiZw39zn_n2Vc+b9uo~K*@Y#TY_-&sYuFv3RJ+;Bam zF7zh;^9LHQmGMzcO|%AO)GD9zo%g~$@B^*@VECuY$8KC{tcqxTcvU4fOXl;{T=tBJ zea+^F9G#lfQ^!$4J4T0>8quF%Md#yPjVa=U>YzD-M0OGuK z0q_2m*FcIez zxGX|i09eNbqI+l3`7_VgQm{j@^m0KaE&R~WOusT3%17ydEX@~15_n7?`h+wVR-fpb zq0+KQGR~G2RGdQA2=rOYVWYP5;gCU;zA(~Tu?X<#edmI3E=PNuh_#u0~9Et#xd-Hw}N)sz!yrI z3Whl)eyS_sfFPkrE;ss25Oaucy=4*G%G@=5|KpNc!@52sjT|8b=;?n|49sQw!*}3Q z+?JW-N^`Y5u-Xi`H!lIJebW1Bc2@4uLx7=ow9Ed*cd+zSj(1X1;F_gVZO$pcYLjZ} zbC$maR-2R}&K;2Ge!fVSnINmu?HWz*D;&IW5R(6@B4B~F+OqqccmST+7ByCO1{_|# zMof$_1}1?*G3wb}KBxinpTL7RoRP=YG$1yRU5x=CIsK}FkkC^=#dSL9$JETD3$-{7 ziJnW^U8MfAY^?0@nHn$upI=5}=&gRldE%cj>K~de$!cZEm_WI|mXW5>M5DyU0&ofY z5swEDWrn!KxCl$qXPvx*q5{Y{O-zaZ*f~X3C{dgM1vff!=Cni>Xe0kuE>Q>!? z+Ln~%H4WdKMJVBmw`qI7Gx4Q&Vr6k+4S&muU{y25Eim1yjH0Q##N$a4w#7zr0b9$8 z)i=!5IP2?rmDL)nVjd6g%i9m@Z9BbFcoV#IUCRUpt#`B=#uxqWzLZ1+$x zm|2;`AM_&oHT(bcFM0N!W&CFlf>MV@{I%8EM%ZK853$xGMU(f=-{$=Pn@$qf?mFBA zWS65T*6MG&+ht{YbJV`kB)9+P3*vu~fJhALg-!5kY zOC`H&^xPJ83uf*;o~sThtf7}S7dM4w{qK|o$~C^)?zLsYD|0_QmVZadehcvZuazhJ zHTn)Ko{Je_7|(3Awf+aC=G;byu8K7jxYW(ZX0$eSgxwcH{1KU&IYM|TdUE{LT3Jho zq548#sOu?dza}yo!`q)(+DX6z!Gd~4cxOY;gouINnBCYS2mH^2h$WK#7rO{9e{Z$i zyL!2~&@RyO=g$&x5P|rX^!WI36T$yelT-KU?*Dm6kU{2YoHt7Dfb$) z@Rht4*I*Zqzu*UPhw!LO{|B>9mc%Dm*CRf@fgu-m`1U5Be!vaCR~HF2Imb)UbaKhe-+1HU7A$g`yHU2+vX&rZd$`~UA%H9v{(YGi zs}+L2;k$?NA$D;uJ-nDA?&sg{{`$+EsOM@AaF~BjsY8^v(X_k8RjMs)ZAu&e>TfQe zs0i7WDZUng@Anb7YvN>oC`rx*@_&2oXVdOW*hRzZUK2DG=xSlc|KcAgwF@LC;Bf^a zrRwSrhQ7DTll?uZCZ+gRe1czG4}V+_T;H!G?{;Dv#~*f{FjkES5z|xuUIlUc0?iw_ zQzkqYnBrTOY{B2=5zu$b{Ib1Or7B4L=ht5=%;7Sk#k^g(kfoa)Zg|Q%Rz1YX;W{x_ z;pE!VT9O%~P|80{#uFYuE6luMuh~XoHQu5T-}jGn)!cDQD5W|`KQPw0d#Wr7D-!ox6+a((AaJkIx=Cv7&w&eSsYIcXyrb}p5egx$3i_x7eZtN{2QchR-h|AGb8%{Ug?voMoq4R_-mal zh7<2iD__0-uQjrUn&3hRp6iQqf(C=XDgE-u=<#DyZ)*8f!IZj@4Y2)lW{Q~N^w-~n zvaaF@!$0jb?e1sifAd;T)av#7GpyiypBRIxmvlp_0Nm^S4$jQdkFH$$$Fyp$xYR?P zT%}^lguEeFJfD8~^SVzX46lq8o=iNw${+&|^3NN!;yC^z^4|>6P;KnTq|puOb==m- zg{`z*KV_7f3PM*3QnSa@9g_60>(V>kfA9%`-*J0WP{7|dZvazuhM@7*sa$dJ+^8OD z>jlCrnsz5MmfFvKRs3-8?CG$-aRCj+cYJY^x&=iqQ_Xs}js5E1FME+JT*Bq@m-$Y> zo0I^yO0`O#GZFj8+&m=Ko=2DO#`PS9ucf{U?={z6EJ$hWn0BHb23$Fl&1t{1vk!)6}(h3pF{(l+AYvZc+Q>qUz0bg5h&GN z|FO62t4k^KEQ^ZjZ;uT%#LGmvu%HRP(be9mG5NHd?zibgDe*Uoe=njZ1e(vBfO)+Z zQfss`vp<~V^&7G$p!DT1qW$2v*vY_ED8Qsb_Ub?U(+RfJ+;Ewq%ZIN{-Brz%B*Zx- ztnnhgl98APqR!Oac$iUkQ|`^*K>YREWyFaqsTDjr3Y`3z8)3KpXWT&bsaZ2VCdka_ zx%WLVsS^hqZ^35!52=$7TaOhxYGbck-u6iG&&yRC1YhI+njMrg5?b4U7cUdsXPhU! zcvFVzA0|jC$>lQ!7`P_ljw$6HkZ4Hp1riYF5WhTyC6PA^7(cpyEP; zz@U{W>oi$R#73i5BmyqJIk^Vpmle2D(%667Gk~rIg?71?*nG|5e)xZ5J~wNUF*=70zVtu)Rb%UVCY8x&*D?YsLg>aU!L(+#+C-SPVPHeU&$B2dNp zJ@L@p#*TuqkGsy=e5FaqPO;!gv$IGYFc0dpm%6#n)B|rzkB1) zT#>Xx^{}M8&8A(yiQAja3hJS5r%U|H1wAHp-P*mBpxt;)uI%xTSN(q&JWL{KKiVb1 zC2g#6PlvkDR-K+t}{3x6WF)w`<}&*Tcuhrb*?VvP8Gb%IDrUE2nx zU9KG2ZGd;5^1p-!WciY0!N3k?B=dt{`<;6Cu-k6(_h%=)Fn@_z?FlZE);H1_lm`L} zvw_cjyVFH#adPM7-yi`s*i@(k4B_q>MvNIaiOnG^UkFbhNPdN9)-TXd^JA1(*<8kL zaBv~({L`>&|5V$;3Miksr1lS*EP{4*54-O&f8LMB<1&}jX*llKC2xqo$*FxRol`M5 zQf3XL-d(B%I1(_0At>(U{S|B@nn;zbb#8WuB!vbdH4nit%5PUtN zE;vjDFkcFpyVW#x@pN)lBp$5rz#hoe?)E`qTb04xSMFy;j5I6#k?%pa!4BTG5m9kY2x=rb8e7=f**IyE(mO; zT|9kYh{Nb`t~It1-U@<(t~}%zE!`=3f5c$UAt+_XV)|nDY)c4#sItViRS9YCse};v zB-ZciHYurF?D{hCYTZpB(Qi0GUGcxwc=kRNF#omvC(;?-jB=QBg=BhA>7QpX_!-#o zkn6^t|(OJ{m=KtaYVw$v{_Cuz-@gawL#as5`g58_!Xa6EmkOFa{ zKkr%Hb3q7S=I*#H-V2TY7yUi5$u}m&Cjba7!R>)3X%TZx$se=~DJE-$B&LKDqMyC$ zpedvy|7X<48Y+tb@E3+VA|?jJ>I#?V1YdK4HtWBH{hZ6_aQBESP{!o;fhq`oB+c45 zZvLABWiSWx;_I~&IcO`1k0}S({C-G7d+H=qwFO?9LSlg+XaiVOWC7vLf0@HZJP6@W zpeW$jNCJqp@*8#77XO+jvA@2I(kBCnp3GSnCg**C!@HV!me#g;! z2h$oxIDKlCF}Mux!r)2iEbv}G_I(6E2!JpV^8PT@^R-SY-7$gaWi z#cnaE|LlIe{r%)p&6A(6yz?PuP~FBmkYZBk(HnA83hPzT{inNz8j<5MJ{6E|-2Jx4 zRVrgU!S)6Jg;me2+V4TudDY?!rH#4#qn|eIqWg|kT4%-5)Bet=Ps#A*5iAp}4gtA? zM}6IMclcsjioE>PU+fhWLMR^vzwG?rRpxxuy-V&5(v;({J1TPj5_6~?!l^8IO`l1% za(+}>8TkGy6<04C|MgstdgE>oJ$3Ws*6oAuedwAHj2n)`AA5E>7_dYUlNkRy$w!jd!^7(9l{=Z&t?7>%(g7kr!M+UQ@*9j_%~##Vfjk+CKcXbC&f| zxraBo{C*E|LCpxH5Vh`}!OpWDDcA#`fUPuwbew5u(dX9di6D+O_yYd!X&3FVIz zxqE+Q>K-5;(hx>qeF4W&o(IqVZ8&idqNEF)pUOTjM1oQa@r(|YPQ^Tw_*7to=iifQ zG5<SV#It`zA>ccga^vnAR=ILL1Oy{ua{@A@qxIH*=?21@pzBfPVaVni zDS^NSJG>g5CkOl1Cafa=0zTA80EDA)CNzF)=jy;2tdqQcd!$3=l&#r~{ZZ{JX&d_s z;qDOQpUv};qfZ!c5r6FbsccWG;=j$|t|(Wa_d_5FJK#k_Z4XLL(_k&`XG=FF&Y{>2 zHcOnJ7OnX%ZWL6GFWok>`!`jrvL`juU0rjT51;5QHH^&@NQ&*&c*pa5w>T|l*C zK%juXW-1Jy!L3r^d-HGGqClD#w>BfQ29#Bx)Z1GgyI!NM`?BM=q3_9r+Q9dSxk`_p z;2qqV5s)eG{xo#D-c$1g6#gQzk;}XFW1^j5h6yv{WW81^l-Vx8pXry;KzNcU4Meld zzW!$0T`{?iS93Kfn^W^&Yw>}eL!77<@c2kNIkCZ|ZT&C|+T&8w)87l?VM|v17Sni? z50__od5Wk9Y4lGoMvlfrHCFzfJdvh#F}X3En&-k-#{(@E}c z*BHg5kKd(gGZCHrqlten@XmR0`@Y~a${>5`IU33pn~*%I|M5QDzt8*I73We3f=tN@ zc-s9cGg?e;_Xq=1S?tU(-M@@3<(1l#oLR#C`&S8I@56LTw@$){tDYIB<&ZZ}K7W=4 zz7eL*?wc}@@lu&Munrbt>rxTiODa}p>?`aQj#m}!#SJAn8l_Sbapb^TRAWTGL1 zdV1*Vysc4yE5>6$Olu@=u(t?f-l|71~A$w#nrA+pvlATg2%P7ijl(Ljv zwy_Lmvc%9>#x`cY&)YejPm6wk{r>pAUf++u?DM>z=U%S+y6)$m5s()l2&DKd|Mu!- z9w&7LlIP}brL`d-lASRG9E~+7&<YH|b(-mXxHc@R5p zE20&;#2t%8mK81mw)XU3NAh_9I(NRnFNXJ{&i?Fv$(vy4KdjJqM36i!GA_lf-#Xpq2rXnow zzId}zPUfWW!6n%iiHze;of3Ei?bH7wJ7es%TnBY!qW*uRnnPK`h7Fj~TiWYxZ8Px@j6x5p0BA&wq^^Vm+SB*lQtZ({kUp61U6vy%6P z=)rC(egQ}X?Eqm&oxL4Gy$|mwRb9Bxzr;9Ow8J27T0V$bJlN^DKY%DWIsgE|Y9SS~ zH$Ng;$Q@^NxOS3p7t#sd*!Dqq*erQ>_!-C>UcjPWzuyk1$zuV0&u+4| zH&3!o-+d=eTkZ@O1y9PpO|!l6XO3lco?{H_;Wd`8;I7yK&y)JQ&QSUEo~JXm0coNi?$iP*d2+4)}s%>&BLjnVzAUKy8LEV z+AM!U7mrTXj}d>q+?wXrLq%e{KQlwLofIQqS_eoIyU7sRWZZ>6A+h7m2@48!Q4H~R z+(DOj+uP`kYCgZY@Z3oct`_<`tbhAY!&L}>_|Bz(daK)?RSE9d4kbLJScVt*M~Xvh z>;0P*VLlYh-R>?GOm_I}?gWWlJFmVvruy@L^HXo+ovJ${mmL@E=rL>pA`$hSrKY}pQ~tdR}Iv04?b%$bR;v_=Bq8W!3NU)r`JJM(R-1Ah-mi;fHx52__d2 z6Uzi$aeWk2c2-B))Hta%4|$1ZVH z{`h-;R>^+qrd#?hcdveBGrrwAeE~ooMsMxsYusm}x%rVt&1Y|(w8WgrFUMDe>j3Lc z@DpC7$x;|*7UTgyT$~asd8kBY^Ct4jn4XT3SZ3=88ho)(i!6QseM$sqBPT7$R+`uPri$@*h#8wT*q9%Afu*dV?!*UW z^^_?1wbI4aa>2fq?Dp2rjja&84vgCcCA>%Q_FOA919Pup(F#XLRX&k+$B!Ex8tDQk zcqaBxBz&3Gf;I)&WGmce@?KiY!JUfM1>&t`b!cc-=5Cbgu@?fVv!Kqm}%uUzA;CoRJZa= z7N8sz!1ezR*ZJWx0cD_OJ_W+r)lz(n0ImMMJm)Txk#)X1Q3Ysip`WTG3zjkQr67QI z&l8{-)%Y{p;;g&^8davSFyz3)-gDdMz&T+c)16Ux{eBV<^fiKvdH{1M@$0UpIL$-g z`$%wX@^XP6v`l5fAR4yj0X{;&h;w@U%QO&!o_17k!c2}fb{u*%K+V;vLFxfG35ZlxwHY-4Qjky1ofX`Jmh9i9zbB1 z2FWd!Yz+h@?xdJ`n#XT#z{jADh-VK3JdY+{hCwX?W@55eeb`Sgtya&U*a32(a!$|v zCDi@lp83R&9Q{AQ2G4)8UM?Y3C_pMV8jw_ii1u_lOSUMWf+F9Ma*q!I-BKa@<{DY4Y z`UK*=>gvi%fI4?cbD@9W$_`8R5fD&5#)p;V$u)cOLmam|=xeg(4V2Ah&P8uI40ybj z&rI$vf!T>Fu zy?siIoVh_U(KuZ@?4!i1ovSgoe)Xb;D-*KMR{nW_fXBKVyxYKWnbf&n=2I{Du)EoZ zErmOq0JwuY0Wp*@Z9aml4qR~cty zHm{i5n5pg(&-J4DQ~*sMDD)liyF2r!3L5Nf*KQRBsy&Iajl20o3DTqY((7$M1hg;j z$H~`WdR?z3>!9S(bC+>**SMl3d!t---jHM_VBO8e+q3vi_s%tLRO~X$zV2E#fkslz z1HSBaTX2Ou9e{S5>sPA<83$hWnVTO5G25THtGLV|HtWEcbe#KWoAR;$Xd*v8cd!X0 zSf(heX@Fk5gxq$2_YT146^nC11Zej5mR$|fLQ`Xo$m_>ceh=P8^#!eJ@a=lL;lf9f zuifyq^bF*o+&*_V^ z2DK@H>>>80)i)gL(IBSZehO&0y&7x*kjDVOu?citgB}WxJQX(f%Q&`9_{YuuH^46P$G_Z1cJm7GyFkR`hz0LbOWOf78t7HFq z;v;C_ds;^Wy2O68uCKx#t_{>s`DRud^xWBip;Mq=r#pT(B}}bl)K7qM6Yb;(g}v#a zlR|ox%(i?WKx{KgyuA!<^v^MXi(j2>P!q>u?zV0hKo9_$*GCgHNMs6iLt>rMoohi% zbI+8;Y+-}ckbZQ%#-#A9JSS84ian^8K<|xbX~}Cr+Q`hIrxx56!O^zdsMox#pled6 zL)9K4h0VkmtXxae@r)UU+DFX=IHH}r#OAeoV7;Y+sY;AW$7gdaSwL^6hJ2}A%r)2! zf@)~UVeE5mM)@E6X+jS+zjQe=cmbT5Z(PWnB|CipQy2S6oWnXP=G!gUFE@f*tjH_6 zOOllKHqN89?P1PgHLqAvO#R0Kk=Q61 zFailMH3w)1ya5Y!GAE_Sp>=UeEW!F*fa)^<2EANDR@<9wB87I6$`_X8*0Cm%E>iOQ z_Lk*y`!B8Yzd!iDxMBL22wL!hV;J3?7E)pBBie3P1qt4Fj|~3BUYyaiC?SGjMLq#! zo8Ys3`fz2nIhE8CU5UhEP0ZGgi%Ab~g@zb#pWSA?Y#(YOUy{K+SC$)Py)0q^Maf#2 zw3JW*MWlOdJbJs~!Ph#Khl}2p+!>vDyk}t@4uOP0w`Z4_t$)j-f5R#Nx?{ccRLe!G z7CiySF;nO-ZjdNLz_w>Of75{W{hysVBF+$*HPn4^B47m)ru;Ne@<&zqYXXspR_{Rb zCjNnvTN|+*L)S%3n7}>rh$iBwFgpWR4^cS&H_ghjwokvqJ=2LM@im!O2CeZUA9$h! zT&X)y4o<$Nc{l<-$-@bzXJo(aR^fSqqkudDTc|&-?yO=Qb zLG4+xb-#6~C^ZjOo8lv1UJ*9OsMRB;Q()A@Ie25mCwGs#YgD@JkqR<6dh0DaTt3Gh-dMyW>1@gX z*Z8L#7F~5zc$;ob%1P_3Dn5_#iP|!G3Vtqn%au6m?&)gNg|wimV4`;`R>Y&6oZjrm zkwZ-!f_sJ%Q=?FFj|H%k{(e2LNeN=HuY%$g4+e}*v)puOY-$j=`%Y(88~l#t;l#~Q z7S`xVPT9L1fX|If9ySUhx*Ha2VSHjUny(Cr*lG;?+nM|w|FXn;ueq|xrTHjxYMKfL zbUAGM6!}*`8A;3fjn1r-uGOn+t4qPmG7WeKRk@=3F;a5a`5AFbI47#O#|)F3qrvD( z!Mo<8Ow;ZKXNs z`$r+%O?6=yx~+7^!tJV%Y9%!97O|dXXzrz`?YXugZAG0Zyu`pX>G!KCMBDON$Ea0i zc>EWBMEqX?eyZym3fTsc(zRBFsUe&LFI`s+u@>V~d|VR=Wk+U3g3t<80 zJ!iW!f>x{$!9u+N$q4%6BemYP{@B`Qf8X)vWTiIF|1}(F4Ku@C<%Z_e;p2q)QbVT3 zCgkCsAw=)9n11@$!ju4^wmKAD6Rs3)Ka0-LC+)x`w=GWV1?)H;u=BIq9}?pXU(rIx>!s}|F+Pm*)|)x8xuvsvx`$GgS($odm7ABQIC5g}q7E~VNlSY$@^7!hgMyWn>HqDMg*qJCebkepuYVo;IQ1n09gnC90|djuw z2J4^Vf@}YKas4hv$RXF%TwUIhKXlr_wUQ=fG5379^tjusq%2|pEU5ff3ld)gb6gwo z)Ed^Uc-X7$+Ptfu(!?tzdGf)HJ_EADD>st&NQQjaG^3(LmzQSmi|q4s?FTie^Ca7OB2j@gcMMQGOgN1OnbRwieKz1)kh+G1gMmi$aCy}VQy;oBZRXkCvoVxXD-K7?aDvOY? zJhxkvMtl|`!VyhllFI~&OeskWdVyOhFJu%YE^4PLqqQX0Mz}t(GNqn3WAvXWzii{C zowo02d9$>#!?Aw0*~IYx^_^A!2**Dm!ti|rucl)ON|Gb#x?9sj-syQ%qDh&%9PD;zWKOzq-6z+0gW&`Omt>WIvI3p$zMH7BDCfi+ z$9Y53E?4a8_i3+~4kv~=<*d;7=6w@|#( z^>*9Fqz<1#mvC((m}1;9OyCayp& zqbYv%Rzja(gM_@d)rDWy!s-^IFjszP#?=r1|JyIiMGv0DNMY!yecD>zTnzfj1ir^p zKOmC-e)$WZ|NpydR*3u}Vlf7ztM_Lz$#GqTM_|<@xU&2?5~&^@L54#h-a2fZnhEBO zHP`5NRTPHqr&M{!0AnVJ(k51!Vyx4N#o8FN?Wh?S7MV;eHiKqSxuBdgax$kG0FNq2 zXNK8#NYuI%a{h+5yHSIIRS5};9$^#T@swaF7`@xC49**lG z1y%XxU0z5bvQM8ls~uQXd7gEmd0xVXs#qUQtg2I5pzcPkImsgD<=~?bN9S1`)hzD1 zh#Sp!i+=@@0C#$Tkn3~^caVFbX$4m&O@d91oHdHs@wP(>!m*KXw z9c3&=VzNZxxOI9ezq#fox+SlP{9-b0?~?($AMM4ud*G~ zC>+w)#CFXq`t>|b+9Rj;3Y!MLC9ghgz&oEyC>r{rS?^+)@5xCfj#4c<5s}lzk+V~` zZ+5CIZ2j~W8$VtA{Ux`rA!bQ+@PJ`y`8}`4m4fXxn^nfR&rdy@#;(L=``*Z@J<+;Y zQ&6PZol!oQD#WsRv`b48Ox!Bq>C)QV*(^*58CwPSOwD05Sa)_*qZv}t;tp_=SIyXGPm0Bd(d~#yZv7S>)upp)G2YrtjU(s{g?Ibbm5f=WoK3R zOIx(Nx+gLMe76gXT+}^_j&G{1o)-nvvpyv=@tqw3dH)YfGfXz7&Dw!^;TgU-$u^b2 z@fMrVoX;XxkFU)GHI<%7?w-}kyP6Jh&ttasX0+waoP5*h&E~SkY+4@6g!0Ua-lRKT zP}hu+(b?<9{QZu=zKvJ;5L?UkUFCV#UgH-LNhpxuk=(I;(Ju46?80N0qVBrhoDo-+ z*+^a;WnrJm$SBouPcS=Ux}|fCBV{fMTYLh>%P%6X?zdv)_n&0ngW;XLpWZ5awi)(U z&5Vn&jBj@!Gjt7Jsk-EOpW1$2r4!XTcNB)sMvmjg-ZOmjYjf0e4p)X*_Hb%m4srKa zPfNUN%6P)AwJ*BWP_lo zNoLWsX#%?LCClqZ@6gAY?8Oms{WnDo_DWDmb*O?d8Vi`t-LokEN%aI#BZH$Q&X-?%q&1^CbbE%;fQ7T5m=_v{hB{r>p=;}x%5*ZDr*$9bH`xs3PEG?dQKvC@G+pmQq9 zPqaZGYEBS{l9%>0@DsnE3fmwMCrIVVLtS5rwMm+qyDCPIavvf}x1UbLnoC&`b zdTt+W^i^<`;P3OseCU`11JAMTf1Hx@}vL3Lgp^q(*P@xlK_9Hg;OE{T;- ziesgfeU;Y!ChWw!{@X@P3r&Sy)eO61s6@C5{TmhjVN?Oj4 z|NiCFg$KFt6h}^?v8}iT%c~f=C+vCU7wj+d$+HQ3M11`d_Ms3;18}*}KbGSL=ru)5 znQ>t;txM&2GSn7=ykFiUey~pgu78;$S)q0Mjo$xw8Srg$!I=qZDh^WIoRxlYoN69p zOEJEec^;1%+`Z3oDK~1&k<0zjpP?qn#0i4ua@QpI*q4b7w?WQ5F_+T0=lt>EbLRhf zB3jNb4wSVs=eamBfMLTr9zjltp!s9?Xo+pXV>X*-hHw7+`Tu2MQIfpigD0RkkTrrN zGTasoF7FyV^7#)8gf> zh*8JftU;3)k#bjNoSY+Mn75yQ#M=NI5TIfb_#Iur@pO2xUylP(88s_gIcVE$1L3$= zzIl1-*b3qkL7gCN@QXxh^_*4FAOQ7o_mf0_OLl zuDe3&Ub_kY`ec&Ji59SZf-8tLdSzn5P4q=rTTH>S0^GKPN4u1`$M3w8a8_9*D?z*! zz-&B+^JDU>D7XmY@%Npq;YHw>`qXbC3LgyJ>VNv+|Vp6oqP^o#}AA| z-};LU{Zw_d47ly7VOvUW8;X(|@)87pz{+X{(-N=oVgc)<%*ievs=z<|(?uu0A*9%u z?Ujr-UiTrhu0P{ZVF^-O7;@$+u`&o++|43EArKh;kt#bf#VNDuKv!j~hH$6Dn{T!3 zK1I)kKd3cSk7@blgO8`JrF*kKyx0^_9I>xR5@f>f_qUB3$a|S7rpyebL-2EG*@iK6 zcYOYF8D(sKu{rcBT0^W{2(q$?cyKSNM`c^(XSQu>D`7;-ma{%ZjL(-=%hQ$T>igEQG4> zW<(auB)Uvw)Djy|WW&pW1Hna)K-wNUF1PZA@0T+|@CrNLRc?2@$Ai#?7K4hZ8Zl;g zcp+rX`~tmZss(ra6fs6{Nyy}L>gdKpoA*XBe~Ha-c|=RP$7S*PUzXwQUVhu9%unVU2>Q9yPc}v3G1#^%p|TL=#w<)W%+t+XgEbFf5lzZDek}ap)U04*l8tL7ShJ+ zbBs()jYPCdEO;IH%Ejhkuf6l7)#`V$@uy#kYT6n#ZX(>g_w9EKS8^190~RW(Dbl2J z{V!zs8z8C|+Nr~$?G59lY$k?~ z-?+2E;bUL1xGgJgoMF^oT=Eg1s{s+v;lwTZ49@o(&8D_}cBMi*C^;5Apby-=7xha_&OrL7q ziu3Ie(cuwc}pe3l^xg$DnN$xLD0sq5ZVc zcvB9n&6?}ytxXHenm%`HjGpZ*UG562%1H&j@uCpi*|uT$B8=#WXLVDQ6^^<}X)JOX++%Og53~=@fYGGfBOy@s%J< zdE4;GRlc&jHC}FDqFPSsrf~TVx0kvFN@i4}Wc6;uS^16IUPlfoboyELP}Qmc6ns+FDA!LJ zo7Mp9;`&P@#GSY!HIYj7(m#GLFpVf3$hhw*#vTY@N)eDMHe9Z~RwEyMHt$2v>BKvW z%&vFBJ>3RLan8EkMHI@;JLpgL9XrKpMnZflQe7)zM8mt)1yR!L8H7k!b&A{3s6Y5RH@f1 zljDyAMnqx|xLzY?$~d9ErK)HKsB?EC1}SEbpRwdah8 z4B2(IaD>!>JxxXEc0AzQA{^Fis-I)7V;}wS8f%X*dV<_4+dW8+%&iV(FJWb3>GArO zJW+bSx?US1rGMjZ(Hu+9&>EsNxWmX3kZU;<^YoPvQoP(CY4m+-cJ{H6@K?=o3+@ha zlqq1{Ym;HI@=Y7O0TFJ}e@HQD;32RgY;WdHtW*mIGW!k{XC;Xdv$g=qQH`HQT&-NO znUt(Pb#7>ZFp_a9Hewn4;dbNvnEGCx#Eq`Y7~&P|k0lP?@ppTF5ng;JspbB%*4_~f zyvyGUzO0!;rJb{o&(?Fz=v2|F@XBdUJ-x|<2i2OjT{etDDv0r^;q1))#dGrhC@edv z8zY*OA*}K_MnDpj|I~ZZ_U8Af+=?BK7hmTOwREZitm6`4f4)^-h)U#@t1)N6N+X9Y zW$c(6wI$i{N5gb-t+X%e3z!Z1VuWs|7v2Wb%Q*)m0%49206%?lix6KyFjVUm`t3-h5^P z;vEaWZe~gs-~+}kv||_j(M!6hs&!#OgGkw}Sa|?5 z7h}x5a#b5K?OwH=D|{B~OMq=4+&C|X*-AhI9Or_rn{UK;|8k$rysPUo`DB4|D@p<> zZl7srJsxy>v3RLY>jT~d)$|;~dez8QYsOfAOoQ0|>Q9*jRGXc&y40KoIT^ZCf0oE6 zlAFLI4=RH?D@OgPZh&7{jsj7%Pl+%yEBq-s&daAkDXBP#!;pb1e|vW56z%ztHFX^= ztv_?d|Bn|pJEGSje{Bv-4J3#Eh28+{Hh0pS_Mq7zkeK`BO(NxNMS)TIoPQGXpCSJz z?*Hbx|NQKKZIt}SYyUySe-QB>e)$id{)Y+w!-W5*GvQhJnJEbV8MyvC2%li~G*S9v-=&ozlyj7rUx+ibwFc*%VD2JbxNtc!Y1GDX z=w}Y3s^q|D&l<6W7$u_D#5xFq7P2|CvZ(T|N9}BXeXJf!O*A}6I&x@7E5QG#0Z!U@ zxScB6{^1Z3@v!v6|$u6Ko~neU^0L0?S0T8JcB_oVEAOelo>-P{BA+(nPHfJFpa|m^AZ?#m{X@BAcOINogb1CiY@u)JY0gb2 z2;=>7er_qZ3t%i>Qyh$8I+Odyd&i^eG>y!Q(>B}ONQSugcmxhwR!yYf=~0mbO8sbUU)(*fSb9uT(k#iw>CB9 zX*OKNEMV4nTK%JR2ZU8Y+h(m*uHxXR3ba7o!G-C=k&)=^!Mfs`LDv+F5)=PY zzXR?$tKIzCS5CZqT;L%mONjJm+O<|h-?FQNMgY+&sYfEJSk&C9Ju%;*Z&@e%|@(gK0mF-iP4JZ-(HHSUs+Lzt_JUvMXio z_;GePqAn+s`J@XawjRdOJkvf^ubj!Ydyt`8tH*)i_B-?c8U~*li)mB+o@sL3z(VT^JxpayoiaJC z$5o9$+6&UR2K0PNG+=cU?W>m#mj%a|z&bN~JFRuDNu#dJSDX0OmiRGi@>lKGv+5iE_v{M6|G4S9NYOr#`O3pht62kAM_XgBo3WUodw$ow^ck2K^%9(P3~R`R zY2I1M?rNXgZh6tT7(I}I7~zfDD7e0FAf6?WrJP9<<#c2hw@8HGs}}o$8mufR)4ug38M-}v0%5Z{f|2feM4tDf))u& zQtVr%Ul&d^fsb;b*S^?XO8toQsUYc=rJl5^nhWTYjj1^Ixaj`qjPu|4a$%7bn+^3b z)-E=G7HB`z0O>NPl^3k1=H;NEN}@Hri#~HmR2Q~n`=3NGcBywET-Mb%*&`B^brqOm z2eA6x2x-1a?cxFvH?t1?_&WAgzr^z!Dz=nBRj5iqsRrim4~vu^w&HD`(CNn4BfO`S)?cJc93L9=0A zEDiHsBxajOLAEZx)g}TlxqQ(R6PjN?QTA8i@x_}m=!|@78=9SMQ6d5sbBu&L+ZscL zoZTtfyFQKbgmbPNVAf_8B3Ee#!^zH z5-#{=q}cK7g+5Hp8nOScTMmU&OMj{E^l8zb3>-+apuFz0>(WY;12St#UUr8Vd&p4{ zJVk{2An$1Dj6C~(1Yha`)GV<$XkcOf?g{47fL|B2qw;1CWoDUqV77HS!~fs;sX6@2 z?hVi(i^9u|*WBL2f>Q>d&9s~9@y^nq{J^8>N!89|9#B4_FRQEg5xnmGxf9ghGo+wo zd+qG+-JnEyEmy|jZoz*Adr^oy2W70L;42ST{;fJ=+9{IDhn(LQ)&Ydou9JB~=);}r zb$_m&y3j!94K43H*z6s4Nxd=6TAO(&o+eWO$GZJqz8K}}^D_^wKLMA=|J&vAj+A;7?Z9$KJzCA@6AI#(P3FsFxfEJ$ zfPimEr;v3z>EZBK5p+}qf-u71VwF9Fj&h}u=Ix$(l?q_q21i5#u!B&|aMPS|*AfLue5slTq((zx7Rzs6eB4QL824%lX)HmF> zeZCpPKv7x7R`$<4SZ)uLHOv(>amT$O7mgDg_P6i}*XPvEq?RECfVnQ%PTmGmcnCZw zk9VmMk`q!Si*(r&$*;q(wzt|YI=n|Jvc7z=yJ_@sbLN4IiuZRd^W7Vhr&L}^}^lGSO5 zw*XitM1Mg>vcbm)kllp#WgCT9ZLZtQv5^rp=h>Ygxo=5B%Wb7_lA_uK-s9M6%o{Z? zem0Z>4?taYZS2&VtLi|G9aYkqm&sxti06g%Nux}_jt|)BasOM&DWu;mkc2CT-3ayQ z*^`u!cE0yoSlu-@y+r!U;_naluJY;nAR;coLUWCfZD>J#_t#gPGZ91IYdQ7I1B;KO zK{(hmat?g587P36ty_D;sB}YL03H`w{pxPEz~qU5w)rUJu{frm_bH9&973O-@9XJmQMw(o|2K~(OD>`;bd``4GK(uqdhW4d-lsQ8eq#9hH7wm7y_P? z*cI~Um@%Qx38y7+{d}$)NPy4-+x1ch9py5T`rTLBd##ZMwOhV)G0>{&p*}eX# z<_f!1H;Y_0M(;|XAOBP=-j$vt5zk-v_SUn}FlmYyD&+F1-B8qC=#3Qgu$@I0B`y7^ z!sZ+eA>7qP!!!;qfC@j_KzRgAUJkM%5iDav})?e& zR4?JEy6)=8>Z8=uY8OiHMm6)4VpdD&DmpIm!tztg?z!D-t+k5c7YCNx>f!LkJ24(s zF?9t2Dkxc@Lmtf9792`&`@ur3ta-ouai9WCOzn;rO>1m6)#6$Y=gnu6VawLL7lDxk z0<=_&svI!_)?GN0xgWxWmZi*AK0~KYt;qpm4tB5H^|D2l*Qo*$#9QsY>u^lOUY)(Z zX_8ZXs+@@Ur=GAvEeV3~rYaVcMRRT1EHZE)xq4a%|BzzH@GWexV-7?#;fb~mmxoab zT+4+fOiXAF4r0h6xmDdtNyjfgM9^?r)V4p${ipzPr_&gBt?WgPf4+FQrv?B(@AmR2 zgN_;1c|#~gw@IUm)uf66?18!O)qsc7MsEp(j}pyf9dS^;mM|gXXb+v;>BBdLCDvUV zhut3k4x0Z7S!}_G9OMZvo{>j=M|ZC?i-I|>=La2RenDY#fmliLkCUJ@qxJMn4la1$ zBU=*Tui4=NtoEfu1TSA2G@jzBlntj{Xh?mX}GEw#Xi-GoVZe$`gKA;tlJ(8Pa^7>VfOI^o06R zC4z?d=f@phA*5WO{!9L;-6EB^OKsMfEA)|vc~g8;5kY{N9lH~7cVJJ%&&fLsXu0^Q zmNL=>3tC^bgqpnLf5X|EBTv(Y^TMd<=r2p}3xMlA5qrfS3`{7L z;oj>#f;}y{BTAk^Z6u zd=d)#cJ}4Y70cs%rjQ21gE-&SxT&rFn;6H4eS}k{x(oQya)q!B#hXS_I_@HpitIhl9mK^w?NNFAdn_eAU0ul}PY_>D}67X31N7oNz`zkS3oUC`>g&c7}e9H5AL_ zY*uhfpaV~jKv$?Z2d^HvFCTD=c*kMGg+8!S%&EpZ&%!2QEPgA(tqA{|pIc24`=T+1 zb@PjtcDhc_vCP#>hJ_!^rc})li{&^hp^lTWXD=)pr)AF#^a!y#VKWzNNJ`*(yJ2GN z{3b=aA?!f9dx5Q|BQ*-C+Vqi%V~?!p#y)7)6x3&i3W9ohlzIATZpm_S>cAPys|S!$ zc~_0_3jd0$u2gMJs`FPOf!J>m@miI7I&Gig`Rppgm*atR@?_gB>MbPr`mFGC&o0te zqI%VP3vVwjerT~}refcB1sDmZTD)CDUT)fH$IkSx=pGMz&4FJST)0%l3 z8f7tGqMAjbFAm@GnGRqLS-IrGeh!7Pi8mrbjPfhjj4QL2th4t$>;*x7ufJ#pqk?@)HRMp%zBt#E+jUc58?vH*>lDFX~Q2XK#@FWNj+rKE2t4~i;v!3epVa1l-0Ffhf1 zKj)}$N%zTI2xK;n&!sKOlMiddIe(LI@(8VOS_aLNB1i+u_=!g#;oSbdItHSLlIZPm zJ*IzQ**PYU5x}xJfaRHbc`Fb>!%9-@8U(cc3W6W}#XWM$gM6^wdtXHOm!C?rMrfN4 zVpRQ6-GS$jv#jBO0+-Y5thm@SzoyGmy>@DYeXE5b8K`!-bVC+bOD3iDi7vFrSYLH~ zDC(Aw=g+{(XD)P)(>|P-hkWDFd(&01d>NG0L!ZC1{!oR>v;{5VZ@$^e5gBLQ;G~-N zjXa^x++o{F%}0D@^NM-;VL=#(@*C4kgJ^@8t78mK%2|a0%t6pB_Ld8Sn{}Xrb8AfN zyA{2v`?zOp#I0+9rDsl^Pur+Ec&k+PO1~V&DrZ0NJ%w^HmR3Co7c=fR_C~sY`tnz` z8?xUXce5`p00KM9U(gl#sz z(zFq7(de+V52}xPznITS(eAf9av2~*^r6o~@kM{rE9=WxS>6NuWiXL+cC{-aWWrH+ zv$C`^x5C#x3hj=40SMG-CGhwvL~=|?9drung%)S^U_-JOhv$ybJX@O0s z1kt0b@<#ptN0efrLv`<2m_nopx0-ewxo?w-)Ay1E~={#LB&5$Pn@8zFP#1Dm%^ZB|{B0{Epw zM@8?T3%?OfD9asS0`f2y;D>walZ5DgA7h^Dx^H)pl`bwpcTjKOX1QIB*PJz{DoUaI zAHq1G?lz!M@{o;ctT^R5?aSnRcAj}TWn6n3r~YPHs?ok6p@^4c%ZcBd14}30D9v$_Bc;E4@I8u2|MAMD*YxN_=)ZDf@u(#d6=eX;%t)Z+~M1w*rw2RgGk zh}DBge%|1!2Zg`+>gCXp?@90Gt{FfO{fM+`=CsY-Dx7}C>@G6F?yb4Oqs;4Fl(DGb z3Q=N*p;&Q9{PJU7*{p?@OcuIq;11&aSrKR_8_GOJU^ zr|>m8B}HlSgHwGz1^@U$lRzZfB#q(C6yNUGn>%mmuhn^Va=Um;tOh?8cfIPY{<(4K zpN^KWO{sN8F^vc7F=G4IN5EsUFWaJm>U~B>#8JaPh7M}_P+Me7@zys*Vp|ORdL;5o zE!dA>XM6Oj4}XxInX=`mI36z^U3GgdDrIY2X?*VZ%6g=n^cXwT!6RT_6(Kd}x(vCW zx!%zu?sHpiY$7!cIeX+j_uQCmX zC!TxN+)LEFM(z8()U{!@LMtS5eQPr4(#!{`Ihr=D5J?U3{dJU`hwV`~CZ%IFdLlKs zpn1YB;HVq?+Fxoe#&ttYwy9QzyaJe?0vu2CSaNJC=&2FNy#h?` zSnS3@q2EtU`ARU0?2^UA#jbLG0Rf1o=G6KAm1?<%KGS^s)oWNZH*Xje#McEB4bVFap44aKa%i>H|`TvTC&H% z7LSFbILpl9&<2O*b?TZ*;#z(tsQSk-vl2yt98%)qy=Y3ne)kF)aO?<)PWY#v(3e;W0%gFq6=xi({8x@qA*Bb&wnEge z*{+jU-b`D{`O=J?qkAskUgntr-ew?v*LI4S7v+5*9>?=HB*m}Sf)CRci;{oDuDW9T zcL0fh*F0p-b8GSAND7ElD>a@zZ~8mID}Y;nFe<^D{+L=89A&-g=shb@*qPg;cVbf@7?qAIpW0>xGudqc{F(jATu6~p=9c9pH)z=T{QR5#M zaWuM57&jK? z7SAm6o8RK%G-wDN%JNSR7|Oa4P{y`fohou^@LC0ss?rcmEK+h~@CjR;9i`1kh81hgoH=Tp~{4vhA}6L6GvLU`)}EbIZs0nboEq>xdI_t_1DOg z5Hj{jlyeeY?{rdU>2=BoCctIMqTHfP4^%;AH_xj8y?(TQ)5YJJG&q5!2N>>!CY%Qn z^hL}U?Syv}?WQw*{HoJ#XV?+6z0jn*Qgs~)f{Bhq)niLHz+DmLIav_OQINF?XB>F~ z{i!&Z`*#LC;uA_enFRCS035}g^gNp{9W-CjoN17Vn!6}1b|LLpM9T`11Dr3%-6^bj z7QiKot-1<^D(5t3y!^a+w15BA6)fiI3w|mzU(fr5GtW-82K3bcmJAndr_)`kYD@87 zILU1KdV0%JdXF09)I-Q%^|1Z1%U9%~Qep7P?vv?2!qR49AmZbwH6pPcZS1*ohyOcDol~(LW~I2E$7?s%H#|;CtX`L(_H#g^^OFhS^ND|6V?t2iksMc> zHk@4Xy*L&1(96W}H<&e>8}bX84A{1kOPSjaLoYY1ooVnZx^~8J*E5R43~=@F+qViy zyPfIDK5C;Y$8S6x3Rl%sh>JJZHPLGsI{jPvfbcwVLFC%`gE#VGAUGBKWH@;ZzzFki zOeQ6wy-&1`5MVm>ZRv0HN8ff*C z2hbYyo71yS%s3Z?@z6#|(G>kfbk%Z)*)jsL)+2?wQLk8&a2rEyV6wlM{Y z_VqZ=IyIt_f-&_?qszQN?OoT*sx#0_)qOGK?iuO2Go(>&Unjob_!bd>ub+fUG(hNH zR8k>MIH^;1^9(*8$k-c#Mo9IvY_j+M`R{~eIH`M-j?fxzf*5T=z!x8y4)iNnmK1EL zcRH1+>&cJ>E4tQIFG7X0v_RV*<2=LOdNT5kg?Tpzt@=?wt__1Ch-t}Se) zH2=4p?5(h_I-w!Si!%*_7uG;!*_&t6K1fX{5H!PTtb@ctFB8TOF>59y#BolpgIq=gg9V5`(y)*8SgpHuclzudcrRxItW#^V$o~qtx?X>NTYGX zD)()2mg2XcOr?$52AaSK{<~(z@l_Q&rDF4mPgGqBRvUH&! zPABw)_Bk;hh5?{7$5B6U6{w}~HuI$#yofawU zKBZG`o>W*bD92+uwt9Fl2LnFh%l&Iu1dMTBwx62=+AHC$-uJ)kKLG1+3g!&%9D~c{ z?%Yu-1opdKZK|+U1;-`a<3EJ4Qm%5M^Gur>*Q>Se?Je9eD=K+a^`b-a>JA257wYn_Se{g8IC(B!^<*!bGEfKB2+Y6`+wXZIIX5@NIvNWkH0q_jzf# z?WFcGywS4iN|ro5NgX!lFR$DJGfMpSwaNlqK8zTRz^uJ~-uNmOr>q>6Lxd>rOhab&H`@v$1;K-* z;9F<;iR8MH$Py3%qLcx0+2CtM<37Hs0iQtY9wmph96_D|b&gpU4w+$?Sd_k>xf|EJ zvZ5&iC*npK)pHU}c*W+~Rd-G1cMm4fsFZF*IZwUlIkg>#?Mgsin8LO<`hr=a+I63;2!)4# zx3W02zcq;o->)!3t8y{UWw2ETHuA1!wsLooQHnBT)x5SAvSP==0J(~$O&12J0!(&3 zhf83e#Gs64N5CWpe1oH^9%idAU2t|GUn{*V7GVxFfMJ=B*xdv>2JmbUd|2z-I#bhN z_3;Oi`5OM~TeKy5 zFAcNZ1aZmDsX??D#13_P932}ZuJRjnE-MP3Pq^3$KwT8>bklRyptjeqSZ#DFuU$pi z9kP7h)oM{bew4bH%bDBnIG{B+>!YXMlt%DQ%3ZY)UCHEZwh7Fnmi{5sNyW(m(zrl6 zZ}&4WyX!M4&?EcuzVXh1A4=+m1$DZofoB-*Q?u@{OXx2tt#A4&_ zo)Qbc#ib#$ZljMNC_n*wA913amRfBG`9_;HYbDn&dLB z7$ENU&Z|e(_2&c3V%Jlu#2dYC&>@Kx#nxIfW!Fek2HH1R?U=z+uh@4Z0&21QIw80n zJFRV&a0yoC{30nK!2Cp;6-@|g-2i&UbwDp6eLrN8IITTx4-DSskE`#2gCqvkuZa}& zHD%!<`qYI3hV((&9Ntq*o!C4yu+{*%Pq_m$U@Ly1R%-KXu5wVZt(&NlaewUeTQ;)T)YSZ*iY4UO|Uli6JTZ-R_u!UbbnJGdhlY(Mb6*DfrPt%r4S` z?lXsZ;Cq`K3+XP@TB~#CO1>Z8vI?N90KKo#ww^wZKCiV#pvSw!sF*F(` z22QQKxsZUSuNd$-QY95}abnr-EJuvff2Coh=EY_g1D^qYw!-n!(;hi}M^r$wr4K3u zcNI_{A6m5uJny8tpGeTmEE~1+r4-n$HVB&|@3YaZ#MuR%>1Vve`o37=I?3HKMF&`r zgjF2+Eglc@mUNev2<3I^SeVacgT(XtFWU;sDhdo?Sx5s;Va53N~`X8Y#;6Vx@(CKN|=A0 z_8b=+lNzet+PNH!{b5sngVLG4Vgktw?v3FOPUQ=bE?p<(wYw(nSSjdNP76xy+k4j4 zXL($PIqTH-+T1ES=_srW8dIb`XhEe88zuG~*f%B(rQ>9C7xrccSolf}f%$<3P;sv} zEZXes6rg5uRoQ_3nC(2jj)vsN{f`_PhN8I1Bj{b#Eo?;EYelyl@igV~em^auFYx)X zh$=;eN#V#{Ey9F^v0P-bB}7vM9MwI*{a!D^l%_i5& zoRehK7Rs?N8Ek8`dzFVQKYN%Jb03t?hBzRSCESeRwN`TK(6n~&Ir$%mCc8`4qjHn> zlF`P=`jF8jwEehm5VQ1d&^}`AAOOq0ml@-l+Z9>4yeMm&DFk+Z6B1H_Ff;?opz^)_|z@UU<6P~eL?+^gK3HbB*QQrX|d ze|qU-F#Ur=58Cwb(FA01Nnj5N@3MLQoUWpGdV?)Gd*aZBb&dR`cmrRw^DdaSCJ%1U z@JJ3dYb%MdT3B73@RDLD+Q3?X-IZzMkJ+_cwnjM#MtBL2F){P1oZ??W)2sZB(ks^r zIrFOqQ7a9&!{wEaHN0PNo21Y1LuHjH2SBKKx%B_w%SGQA`YE7b=7ojTh)h;R;G_2( zjvC&~Rk)2hy^|lO6xg#>4@>t8Aq8f0#LwBZnLA_7-ilJUO=B?XZ7GrPPdgm&$qhM>Wp6n z2}UWh_tt-0^xF5kfCHBH9>In6AEmu%@*9g0I~A)*$I(j`em$TF64scq;Nd-A=w_IQ z49*|QOJrezy&xQ#KWz8Bm8GcmqlvN3$zvETdwqLH>(SP=b-xuYxtFaRwVoP-y4P^- zPVG82D-eb{Vu|2~{iFUa<9=y}Mwk5K0ltVQw1Ce=(n*@(bMjnT{e8^XSW#nzwwMi( z;zQ7!d&^6wR*>~I>Q4ymn~Uj7TO&wO(BaoF7OW&wANX(h*Qk%O*hDqpccK)@=&}R5 z7LKG*{SPtSK6N`Drk?o~u=Jfe3%U}vzAV)u54dx@n7%}x%0gBIA&f?EZ(mqYy?Q;P z(W;SE4oH2@rebV2d#mJCNfN<=U1pOydd^YZLlu|DTNCa{x-K7X>N(p%n(QGye2S=5 zrS$-2xujNgkEOQT22;zfsoTT0hcuVv+H;{ojT1ncY%yFf41O}aw6p7g^kiEo_6*Nn zmU=BXZN7*J44m%XHaxklWbXR4h%4{>eDA+U-Np`Nvmal2JLRzZT`&Hcdld2(?#L!y z5?$VtviU5GdY&?tRk0Xx&DD@Av_=S7h)~qVMU+n;N~LD%X_}em35^*8f{vd= zp=!~~%-QE%2mj{EJdS&q$$rc|FgNqdJTn)FDn*;}zF$??6+*t6|GV4LH!QW~AK z!~F2I<=2hmf7F7kKO16jGtT7kTB>uyt*`H~*XWIk+Rqh8TX850(HD7Gy)QUoIh3Vn zS8&i7(xx^(K;#mD%?g6w{UF_$qBYY|MId#X1;CwymHX`YdVF|9iYTE;ZxuFi7n839 z?pi0o`sL(*cvQ%mUY~y-u0)zGJe>Uaf9uQfTbsCSMK%BHwzc=4+~zL_q=J~M{>@mr zih*g*+KL!OZ0Rp&aKI}#?)H7c&t2Kp?55!4gNdkMrVGWxFv%zf?7}b|)nVS#MwU~t zW7G@lez=Jo;o!`JM~hsfRP8lKX*^cQyj$yX$bjD>y@Cz^xHWF51uccMJ zayK(Z2-|s=n0&Rm50x)Xx{|>m>gmDJ1Qh4k26(xkq)#C`f`zlL3El66>imV`GgZN`+JJ%@MRrLR(4;)Mo%83@z5?gXr@*owZRNoSa- z`xd*3FE=WB!J&LA*1f2%^dFNR?VPGUl}22wXHfp|7w2^8gmRSi0$`(^>f^ijD09aZM-7o3`C^yqOp$1IIX;Y&|bCg&<6>f0`EnM zkz-UF=aaK)D`npcxKlIdFgItt>)47q{h_8tf%gXegGL5T9q1f#jsu^32o^;;0x}1! zMqdhnJVAp zZaqdZ#k6xkk+H-4RpM~>w3olP+Z%Psg=krR;-NY6%v=Ts@<@hyPNb<$2qsz%y-WE) zHc2j(!>c=Kp%c+WN6hW|JeSc2=*s~{7q|CIsM{3EgF4gMd#6yB%_uG@B~8^EHtmM} ziw0O;Z~_aakxbge$w}$qkpo;9C|fH;1?JW!2iZF80)Hb{rm$9aoS&#**xCp^-bfIX z{pBkInOw?YBJk4^(=SF$Oz5k>J?+Ti)%+t5wYkI-!5B#%gWh1g+Z1RlN?DfwQ2M3q z)wiaDJxPYG!AN>}{Yk?N6*!%kCXw>Kv$8)R-rxG`%1^&$szvmQz@Z<6TT4a*Ni|>b zldeE;a4$_<&GI5s@L7injd%rE{PM_52(g8fImL2a+-A-wEY z-gA$ZSzLq)SKK!0?^X>!3#5HHLc!F=8?H$_Pcxakf_oV`wtNp?y6yW0ZA5oX#?P6K z^A@A5?M2GW9SPZTeBorl;E&-*xqy?o*x>HKJ z8Dan>lb>y3pJ%Oaee3=6{(kF|()=+02)xcvh0xL3iaxWnj_K#R@yNP!NMdhTmH5+fRmt zQf6B(o>rgGgAda;2FaeHpSnP#v^HNv%&Hrl@pnt>GZ_fOuU5n>qTFQ7J~)y2S}G^) z1>H^)GBMZuonV@3!-MD-d>6>MHh@mzwwmeb7Y5k$WK>$iNAl&R&Nki}@tuV%&+I`{ z1SR!TpM8&rpgh7ZlJN@v0jq;4zB-!;p^iX|itc?$eJ1Mr)yM3ZxvJB(uV81?_;&XI zBVlkHgW7l0b?;IDm`wQ=aKTO)c=xt?gk!xF`~tPoE#yPLX!H|trM8-P6BpKkbOxtY zy^U&VU}l{n_yp3&39rq0StD$o`#vo*X*)!F(5dD`?p);-;7v1taN|thNmbyPGE0s| zwQ_&a-7^i=$B#E7H!e{FH!U;G$5yBeREc3UEn7gggEyUNF~9P-s=wZ)zKF36TMhXs zyS&jL^lk@#a?y$$2AMVk>H(TMbP~XnPa$jzkprXx6J#0re@HoVKlnaw?@jAqz@cb6 z4pVQfSe__;Zl}k`rIN)PuZ(@!>vKk8w9&`zeC+F2S<5riUMW5XkI>dqUW+m}XTq^7 zg8ELpM}GTA)As{qE6tCmVPi9$;7n=Xx0+Ww@KhdU9fpiu4m-vLm}{EOf&h5r^dhMP zT#Pv9IbA;&Ug#^Iw>nBTTH6BJ8xeaw!I(VHt#ZWrj|O0Nx>2zQJSd}Lr@as?%H&DQ zuI5g0#&gNT-pF#TMoBvRE}VHE*M4pm;f13-Sn{4;vD`qR=8CMc#EAU8`qL z(4eJGBeuFMMS#!r-X~@55Jw+H%nubdUI|`+vR!6QF0L^B%=T z2}fI!`rGQl>N9xu4QScV0>Ftvj(wuOiCe7cO(U^X?7_67KoJPI1rA~^s?%sMv6iIK zgMK_FMkvB=qv`N7moU&6GxDKyq#H=nZvhprFBE z*Ev0Oi|_-FR?>c9*k|WEbNM`K-xN#CN|b2nz!o#-@6rj}Fi~$2mnf&@a)Fl8%}OUd zitvfVLdh@BYv5y1f?aDHOiap7<)nR|oFXpi5%rStHLqQd+ECf?DS-Rh$(*A$(9tw$@QAwL1CP)mfkOJTa?Bc} z52Q8l7@k}GK2o}|O*Smh(Y5!K%%XwdiAp23ex7lnQ@aIaEJv-FOD0-+&!4#zq0jev z#T&Tgou`IM@Ee~N?6<$EU^8Fv%=9`-Yuj;gWV3iN!L|;1Xw#_}3=@AH+b|+cDVFae z>^VWoA(exz9l#P?NIb*Si})+Q@t*i6Z`K}*`BQ$i=0ejla;(qT)qCNies#Lt}a)e0P;3Z6H-BD>Z?Xvo+;v^(R4cuJt2-Q@t-isy>N1x*QqKkJ@ZadtzrD zF`G{Xtv#Z@>|Rho?O8aLQ{J!}Y4On+(CTQg8`K(JYzTW0q=I%y7G!B-^^9}{92yK? zbM1$S%lj4qB@3!R7YBP$k_dq_m#|H4r~zBaX(9N}AJ^mHC(9hIl39={s)JQ?noOxE zeqQcjS{Ip`@uK2UpQs&>me@N`=FSSs5~jq_PXD`%FB2QY+^1kZ#^& zEN|PEqc*_Hhq;CNZkNg}y*uFQHD)mQu$OgZiZosoUg14+P?#me8`73URn=8d=V!jv zY3;_i@G7y0@v+8h#f&C=`M=`mAmhZ18$m9?Y<{%_hYC-{+>EphwqOezb{0T$fNQpx ze`SahT-!VxgslSUCG$$#6w53T*ka&aYv9lxE@a4>Y)N)00vS9sbSgP*NR~N{`=PH0HI*l)Jh6d+fn@@l0_LR7Yi$%!K z#Xb=qQG*E{sB0$a?&%3QOfiML+j|WhfMZ~C+o{9mr`TY4)8t}AK)Ea((|>XWzrAxVA@gGCHxBZodrU!K;p{rHjM&tGpv z3ywb=z7DZ-ebabXxp!jo=>5f z*mHu`)AGS_oD5RVQYulNtn|Ab?vO&ZF`k#CSb{U-KvSyhnXNE6GGf7TzSYvZ^oTB1 z^j#J!bn}p9p#ap#3Uw*jd!v*zsQjB1sQqU7B87{lb(jV-?Q-Y(xhqs{e&BObI=%Fr z5z}JYjPH|j?&ZH-1Qn==`L1M`GhTy*E7+XU!Ym(8oXLZys!fgmKXC=J%!+Oj)XQ?b z-MKS9pdV|WU&Lwin1seCVs@MhhN~ZySytJq=Pf8DA?(oK!yB4dYdFt6xZUU>dEeA-#~+a zBUw7iE{jn!muEwIqb#ylucl-0slwrRyG9u~enl2=box=F!6QQ=2;uOFk>}6Iw{#|t zfMVm*I@rCcVzD3z2{^mc%lTTS1VKJlM? zfPXcqtQM%lQe2-(CYt|B-5L~H5s^fk?Lb3acV~Cwb%npqx?^CUMZV=B4C&Is1y~Q z)Y`a8z^9B?s5?zNE?#cgAhmGUeT7@Xb&eXo>B*MXN|$8hZXkdAI*p$hPv+78Z>BZ7 z_l~?OaLkf(n6a`9-9ZgIbDYO}r>ll62v(3fuB`Ql(XoUXOvrx>i}4Cy@!;JZD;iVN zm(luW2n?Ky9|St;G?}944w|C&E&z&h`=>(S+l-{v2E)jP6N6$}5!K=OoYq8MZ^8cc zaeZr#oqC~y>qf$byw)CK%hNY_+Bhgdn`x|<>hphQ0Ul;ocbC2bQG)SePEJnd^I8_v zC>QZhpRX2kG3z^fiaB|V^4LXDDtB=8A6@#`%WmAd!UTjz{>JP$@3S^aq|z^ue|eDF z3G!&427nAjowrzmQc~DL;5=ZEVX9QsS{3ebthe&QBsFE=4m;AI9){n4{8jDWGJw~L zLpXKSC3u1>?U^i`x??w{?M9`4>~*Mn1Oc3{qUb`TQ7yy&D?l`VNcq%2bcR#`^yd_h zW(mX78n>m-VJO$>fJ+UZUcUEC?;X=$N1)6HOZh3|P7x5ut(Gy(p10JHW;>dP_cZL7&+@0SE7ohdLe3ip_x2>?9x#! zAF^bR!@PC=7RhIL(M_F^Wqe6Si_xS*U6>OCn4|NRa_uH}oRcJ&i`PI18GUypfaEfFzbMfP=YKt%rz9@qpzEA^b$5{tb z6{jc^XO>HrtCMt%xkrVmNjF(xE#J+|2}8!b?(a&apK_?S_gBrTiz@|mxsFZx?wpCG z3T{vR946Z8IFVX$XbJ_*nTee1*DxD2xUA(n`|0h+?GpIeW@!>ZxU|5e9MR~Oyymg? zO6{uux_lu{+`JY_-m&((omVef>GnHvPQ#Q6%L~qN_7rC2PHDMGlctX83X7 zB2*Hy#yYY<`nk;a&)1G;(O)Dww*KZLV!6rp@)Qb^-VIjj{biqHPz*bHC`k7+^f97> z-(1TOJ>vKMm_zjuxYrZNNmhWSNX9_ZlS{-$aKb;Kki$#Zae8Oz)FXzZVXo>-akklJ>qA9qhnN zpRYCJu8wr}+Ff~oF+U4>(gw=cn$T7*X=~nJa;RoLV_uN;xf#ItsjAlCEs4?eWtz4& zR<@5lS3+chWt^;C%WYVcSH7KhEJ~a7!3TF&7kbL_Toa-y!_~9h>68Ak@lB@GeP7gC zq#se?DAlIDAXe=kCl7CF_sQ54opk%NGd_*0ub8}R&X}A(l4A}u$X=gm-HE~zFoYY} zl(ws4(hYGQ#dK0Oj(I(bHO(#p68A^WV$TOpxNeV)et7>yXkZBl*eBf+O$H_qI%;LJ z7=tL$HB_aCoji+Vh;TEjDV69eIc)%8D{0@*PPq# z+8+jlugh61GDeL&XnhrvttKm;`}>-HtJpQ<9)>(O{C7Org>q;@zZG z<_)l&%e|O(e;k@H4CgHi7(BsCt$QX}xofFdZqZQ_Xdg+C^vZmMo6bOuYP(Ni*@K5k zHF6i4>LIzeLdmQx?4Sy(rP)l`PpkO!yM3zMxN)}wAY8%~nOy5{Xyn zh3E@Dg=M%&_(eSK0CTo<+B3vPx1K8?zp z5=G z&IQq5lPmkTN_D961jz*u){0od@UAYv4Ph)JPus!)Gd`UyKb!H_jAJDf$^d5EI2ADC z)`IQ)iomQoKm{m8+da$DTUy!z)h0@POnt zoV$QU%KxsNc*~9LZ*^JQlHvlGfA?Yp981!0BYr~RQTaRWvqt=hYuL9fVP_lXt>J^8 zulYP)S#Gab-Fq52cqE3h8yI`}>3N+O1B{Ghl77Y|;|zeBvNxFuo%1~PtPi2T`(%JW^jRpVY)L@UIMKW}18`||>z(P#q+!S`Q;GbI3xMs4}+$G;PX z*q^^;05lp=fd06vW>cYQup?A9Md@3nIPZ6{FXFhpd9l7k>8L>Uz){;>5UrH)g*pB4 z&GgOGTf99xU0FpgGTL^(R)WkPL@jl!KX6{$&Gw!qiLK7^8#=+J4>ckM+~K6slo0RH z7(I;2HqcGLUev|m-SY2+qLq(pffEj1ZN3=xHBLXQ{ah``4uq;LVapSI@cpz!lX~8k zIH}Uei-`M=W;QpLWD6LF9W}h2S!_u&U#EsEmM9t)0G1j+LOe9G+h6fr@9X@zV!N+E z+0^29(ld5(s3s-U1|vR56Se_}Y9W7#cMEGo4*TIojV-+34M3n%0L^btT5l~b3Lw1+ z&ZmY;3&rs}*hj5Ae4roT0%ihSz}xp-h`ipfbISh^;X1Ah0GgZ@Kn|>OuvhLBnHO~I z`%5@s9)G0{+?U9Ova=GTfUik2N(KxRJEVRxXV(2Y%ei0qffJbJEPa^eY`nR-R$NJ0 zaU6K#dH=@rsDyuEF{PdNsX$@X^PbW(fP(2l;d&;!C2}e1(A@Q)(J>-$zr9`@vAcD7 zs@9`;Be4f^tK)1Zzk#*OUVIrvBV2KY{DJydBhs>*N$?5-;MzIVZv`IHSWcT?$LrZ2 zE_DoBtz7;YTV9G7>s(r}+g}j*WCqaI$gbUwYX1^A57QRS>bYBBRyHU8V|PEXr0pqY z=NAS4YyoUqoVi6(G1~`P=h)qG9)MXtdCPs*8b6%Ea~vQBP7dG3S=!r2W!2TxUAGVW zwy~4iMH#iWaD#iTyiZC24q!u?UYsAvU&b%@8;<$Z^v)Jvgr1a8(fxwe)|p2{WB)zp zJgJ{CK<)bjELbx(vL2V0@zYXzA&x!Lz0U}sXhQe^_N7Zb_tgln<`+N;u#W=snKwRw zc#i>Lb&p-#TOJ8_(U%O~+&;zp9Qwv|Bs3JIvY1#ZkYsbYvh|CA)M$*GG_l2|y9RI@ zzPh;kFfj*`g*tX!XgJH-&_QJ@)20T|qqR`umn0k&CT<(|H(n#(B^Q?X-HH#vu+uYj zc!}DU&sIe%?-b;21ik@ru><23^iH)!0>kCzPKkcXzWvsB!wgVJCFffG-TMr(Ij)3_ zbAhs>=m;lG+pxHswKiKAEJN-DF0FG%Im|k;IT9W>ujfAe#_v`=71H|sC9_gQSud0{ zy!4!a*;?dz8+y0v;iCxcEYq~Xkw_hSxZn6C!pQzmI2;Q{gDEyVoheoYph_cu;=xsk}Pcox42UBS|@E&UA zH-LDpT}50*4x5@mVb;fK0?gp)KcAqXMm;*mdje2Ds-ml`T$X$JwPc=qoAl>?krS(a z>*p^luc}sgAdSDDCasHo8((z>{p3Y!=Ygm%yNrY#LK9f-+uQeYT`RxV`e+(@d`L9H zHNSej9zkT@E4^BNncaRyM=d3LyB`cU)b%_4sM=055<0`zejR&LFUKpU^La--5h5es zzwwkbNYsoX5F;#I0T-W>@5d~Lg|$drBTT= z6|F-LumneD<1B1&l>ABdf=otWRybIYoG@nB=gbD(D7LMO>~X<^!Ksbf(~+^=E{ zJY}>Tjvi2e`|HS+aZ;eL9IZJM>SYn!UkR$p0g++D^lJ!FaES>3{QB14!HrY(g+%-X zr3`ivpv5{^tDzy7^^tUvPv6mm^HMjz-GY{(ovwt>Z18-=g_ys_n0Uy2nj7X z00~}UA|Ss}A7fLLzGbdm{jn^qJIL4(jIO-)Er;_~XT#<))2>jQO?8 z^j?YV#~NGI=^h~hB@~x|AfVR}W?oC7H~7|g*~V%aeIpq|Qyd8exA|dpGL=6TF=);1 zFR;Ql`CpFiB2r{5s7mNzp?bujQHQrS4wJ}K)YX_M6_riz$(7VF#j|nHULZWYXn&90 zI|d=mg>d)ahS5}y?g6t_R4L*7q^7mV9IIrT8bjWIEYJXTC~pt$Kh$SDFV4cydq z#O^YfuC3sB>cW_8b1j8_sdSlM9rD-`6ALUX*Vbh@E4Ei}jajgU;2!g;p6~q0p*H5k zuS8bPg|S__`o7~Q!%0({z8fD{x?%$V!o7I*lJA}r4(O)z24}HY`$m;9`7Jfw=;1O! z;|I+(2G->U<}ep0spcp+t~Pc+kiJ9MPE;eVzc?JL{#vMGjJt%0JgrH%%>6!abgdS% zzAZCpiV-8~dB9)Uhd*a~^#mCc8#Q3_S`!)~Ot~BUTm53GZ_}pe2%Y1X3W4^}dEF=A zOiKUUFU`s1=);NcGOcV1ARYXLOKgTR>{rB^C7Tu`eV<}E#o6gR_~v|buO(g6R3ioJ zFG|@%b=`abN-S8H*P8tg>-U|^Cp(Pa&8JjRT+@t(BF!i>GfkcY-}R~n#@W$YrKxX( zL~;=&-tTps-iLX1MO2ed5;^`^J&m@_%Llu|?{v4V$uR@rJ&C6oO>d!j#eqnGaS%X> z65V||L*=!PQ57|Q)7psRf*Jiq=q){E%KfRFk8^MrPh}R3fGq9Dmop2P84875wP>mL zN;8^U_7(fnPMSBZ2i&DZS)Q5{b9<)Rucv)PaQ1|&y&cj1u`mJ)BqS)f{#_B6o>BdB zZtizRDxl&vJR#BjmtFTDeJm(V*LfYe7kQgD3V=VolsD#CmUtUv;t^JiaM{IuAg8G` z%0cB;Bt{JZ3i3b5qh%bwcvvzUv${OUocYuX-!tm-Bik8uOB|BPhN0$ed4J2o-y$B} zR&Qn&I5K%{Dfv-OZ!_EpU*b{sA2>7K`fRmj%8MamlMDQ_AhyqAVkBe$Ya--(TrJr@ z{_CmQq{w$LJyQP2`S<>NAF@jYN@MZ`rDky7#U#$h zRecB4?<3i`+JphUcNxnvU)OcqK9pN8dZUB^t2*G(Vw3&5>Oy!85nyaUD0iK1c_9L5 z12JMpOUc;~_tr5Y-*77A%B*9dtuN^{kmcFVLoed4?ERT8)1&8J!5PIPqNX+dW0WWz zbieLLyxySuM)Cc#XgD901qPtSG!I%VPZMjD5b|f}VtF0VVmK|=4+gwn2$F(2ox7IH zy$IIT1r8(BDDUZ%8La$I3L-(hTU(ibWa6)E74+$%aO!bFw!7mp{zNfExwT?p?|pI4$GT%YG7 z{Z3&s(Q@u78Tsp*xg4#e|5kd;n*`LffxlZtw-7{?1T3hPqW`s~4cp|1vcHWbSgPJi z*?+G{gzQ2*n+z>Jo_6r0>{XFXS=*Qq$#pb_ad{8Otd(vqtm@>BW$o(wE6i|iV55Ck z`yQ5ue?i@jq&PzXv^3iWt=Ylt>Bg*EPXo7mQF|G|CSr$4N$?AB#uV%spM{RE?#jhd zf$}J5&%TF6t#xASyv#=%=!@OQGXjfBB(bZJprQzGg@2blJSN3>o^x9sIjpo?mqFuU@4>vPlZ@{)a23VR4o zx#u*By9ga+gYH*W#Go}$qC@>xWxH8w@Bo!%e(;aGt{!f+z`9{bE$KGdWkmEDtnpoA z78hRJ-s=)L*-MVN#p-)e)?cG`_In()+dY}_s1~>h(tUz~7wmkHVzfR=Z zs$U`)5uRe;V8O00V>XYC5ipq8(l6^19y_PeZF0O?Vf!`equ(LsGrU?_N_UBD2ecax zmc|gXUB4jBJq@MbrIoYDA}+dWNMNl~W{c4=;O+Gmb#Iu!JHW2`i6yj zZAylP#a8m%J^GLQb{eP(E;ZEgMe9dN^(9boPk+PI{2-f~POE+UWwG5(ICII{uhH}( zoXL+K)JPBQh6vPHyr_?=kJ|12l;yXmr$f`?A3KW+Ft^aukS2Rh)&^y5QcB0bkP=s& zZ{ornF2(&j?(5G40>J~e3PIv9tZmVC5n7zXD=R;$t|!s=ckt4_HsCr^hjhh-)QhLP zv?%M@10JA7&sK1AqJc6HhplW>%1=@J)JSv}w8y3S8QH<(PyTl^LukQ@j$MZDxGIq= zk^P>soGhoN%w>IMzq*5VHVO^fM!mmiO5cQI?|Tk73zGh-Y2vMu_SqkPfjvYtI`F3BUQx0sCI5Jmg$%_Ndy|QZp?w{TLdZ_t@ID&1w@R3MX=_l5 zNcWJEhuFiHU#RZ5_#AwyG(1ArI#bI8!vBBY)q zWEUCtYw1;L@MSZ--#$|C(L(g`&|*=-?-mEt$XW3Rvw-g?ZL6yQNbNz2fBgzJ=62Y~ zeGB`98#nMaK8nrGMK7A#tZz@H-ulP;Q@gL|_nhaQR(B6AH-9zL+mlRjh8dsgbzvCT>*~UH+Qu>zF6+O4 z-9^injixUUIV*dxG)Oslov#-LX8x#LEu?% z{j!w(2fxkkAxhKHk3xhYm|P3FR!;$sy6t@|cu@S8$RXv~gs8Wv&EWysK;CC6J3~_O zSom>OddG=Od2-6?!WH<=!XngdC#;?L{*#4V`Kbc8&}2~7<(Iv@tNyPZ&0gm&g(-?@ zDS_!K(Pt>@?^LSj*Gyu%HlpU(7r$SB2gGpR@IhB!BxXp|nd8Nor8*V7Ml3Jlg5kmT z$0nD}Si$Po_A^(1JlYYx@sDKnu2ZAjmi;ro8XLFsrcvQND!<`|^ zh~{`iE!(j=6nCQm1m!~)`7V;ZOJiWPT{KiaC1REXeI+SW@1t77^ql(?IIlKa8)ta^ z2cNhX)(w?y%(ALFLkOrc511Dp9yt6T(Kf;it zma~z5uLIU-Z)r@9ruf3?o{~3c7M0Hp4J0OK!>h%U=g!chQjdb_-A3n9ncZ|a6v7A~ zdK`^SG>c3MA%_d*44Bd80#9ZEy~lnH7}$9cI`QT0giW#1p-Cgol*uFR^tvCPFtOwP z!5veQal%eNvf*-adFEf{XJ8m@?kM?-WbKjvnFUBR8himVlT*N^Iy*BoLL801Q~jaq zBIK)-HqUA?m)|5=!+HKlbYJXD@bTWcp{aP$>UBqX$(Y9yF+Ne19jP+Til84;==rp8 z;XQdItI4}eE%022LtaHsrUhT6Hy`e{P=HpmjjkctFf``cB%Fx48-jO#r_Ze>+*j?h zG{+3<*<9`Y>A-Vjb*ytt%NtqiaE;$0X#Oj)$lT?sbm8cc%mLg?PW)V~wHzSoYTn;1 zk1Y~qUBAobGVt?CpROv;@9nxq`RV8|8fC3Yx&*tMSn9tKX~}|>2&Vk5D-}LJ z?~Xofx$A#5ciys`e*PXFiqvBDk^I4KP*dX;Tov%+Q)f_<6!fmF^4zMF zcUpfOf3D`G-{Nc1FYi3b+@!B6_!9;vyq$e_rTpSXA^ z4z79N_CASj#Tbl3v{)2gN8U-@nOY3$Qzfpn`VK}{2t@zhgWIpL|3FQ*Jgu=P+?aX{ z%3&|RlMdnd8F(kfdk9a9gj1!(jTYbK91>F$v1c8R%-TC$O0?8Z2-W0tJO@Xke7r0#r2?J%CEAXcVP_6w zMx!DYK57HO{yR*dkTI``@==?p;OZ!s#SgZgxSEZZ!Z+bB&W>eub(HiOo z(Km3@Ks4;$xYPGgKf2EYk!ApkJ-e(`6U&iK-HYa(VYJDZs_M~u`j{Y;fVcR6{2;A~ zaH?n;L;uMW8zZ`KuVb+@3-#$wbEQdvYPYuyUO}e2w?q4u-l3x*0E4v@e z-`X|eQ=8Y_r(LXdPMzlkYQ-btCCi0vy|X@^`1$GJJ1SRBRX1OvVbm7a?@mX1%y!*n zQOfXyaOk~2LA0QeE4;oIGg@#fbYu5k%<4Hd9&$iAcFcceiQ?!koN}~9T$SV@GeJI@0;_<&5mV?vVVJRFKSGz z$bcaT6S0~k37wGsoSjJ7$dr+&cvrT^Pe|IX!a$?Uxu~09h#4J9Kd0Z`Obv{>3r196 zm^j52l~JyyL0uNXpm;f}9C2e@w6mt&_noG==*^sZSkd!t`PV{YE3sTU*Uk}TNk!`t zq2MkRD&1PR@^=wABPk84$I<<5f>?a4&| zKj)saB1!@O-`Aq~a4_IE&G@nfm?_tZ6&A$G5VPy%gw*ti9nmtaOTR#Hv6p-`tZ7VB z2?=5HhDG$5H{h0^OGvL8yNq#XDw}RbjdB0+Yv%ODC%wJgfLGZZ_JFQ!AoHgzvnaS4 z0D8%I0#@B~-aSC=&HYE-ZXV0MCVk7VO0(eL9SLJbw6iYSoDVo;_;5N2JM7h`Oq;W6 zN#n=(Q2>&*7r2ug;FgBX?DZ#12o3)5u}cx|vetsZN?EfB>$&_y`BIqUTs{%C-rVo& z13DB0-|&L_tryO~D0GafjC!k98IpZHZf#3{qT-E3SrM9uXALm~DvR&Gwt{h0Fp>Ny z2D{Bw3NnyNF3rT;`GS{Yj2zugR8y+9@7B%sEA2j&vj?X7F}%8@($T%O+oe40g>j(g zw{{qCMB=;ec;0$SB&WXGzP%(i?+q3h_9J{PM28gq{`+-}Ul9nq))Y#n@;UV|WBGf& zElU1ji`;IpcZCE=5Ej0&+d2NlS$EB9)Lx1nZ21#}wORMV*-h7?(vqLvc=A5vE`<3U z?^VlKU7gYMQ`W7e%R=h(SOgp;Kls?hV+5z~%z^%}^j6d8``-e>EWy|DRWrLs19bP} zyC0>?s1M9wPmyq~-gbp)_GLKI>VjHO{>&keeW+k3Ygk}pHHH>WSsDx z6OFSAO@rx;|8RnSn8$e`**1Jsb<jKa~ySOQ!^*<2LuB z6Z=7Vd6FjVL(w9BFnNbDMdWe49HZ=w+hX&tsa1g*M(tap5=n}U1do0F<`Xsi)fK3Q zAsO&Oa0Glt@=~01Bm$1RO&{R6qnpf9I+c8KGI~z>*sWrowV^T&spL8yMpq@E$AnMf zyQn?qRw6H>@nltSFs_#flynKtLphqHzpvFVDy(n z2wOAU(pKljKCk*VezTCk%G;|)nKu4sfGnyxu;5J*ZF;m+ZpNVz0`JY)FSaj8jBP)X z(1bTCr)&4bn6md z1k=wh<&u?BSARsQa-GTUyctSD+M}(kh*dq$O+P?rHC}4=_D{tUwXh>hZ#KK0^!YI+ zR2Q4unOLEMQU}~ur1XNM1%`$QL(VZeCycMh3+dfX!0}B5?b&WVWzEJ2{2enf!>M(8 zO4s29djge#Y=&Vmm7gcDsI@`z3iVw+HN>96FLd^S6y4LSQPE^fo|S6r3{A3 zuYM9y7)0U*@?KtKR@5`-`*?#Xc6#+ay*5uCFM$|iBCi*4=N+>qG#Qa(#zfsq`BO-9 zNQDh5d6*7gN%uTf(A%Vlg2X$Mamznb&PPfG29U7l)ZLe$VY5AQFB*wszj&-MqFceE zFG()*TY5s?@t#Fo}Q;FX%4o4+XJ6Pu%nZSNRde1|2jVPbbXB&{6mt8Tjyb~b0 zt!Qks+|DdSdu5Vt;~?(|Ipr%;GA%rmMZ^S*W1#3pa3{PF%BvUfGpKdj$A1%E{o0VK zGIQf;j_vG;sDUASfuOEf`Su-sm5WGSP!)7Z^foyUp($2*Vaj!osd2u)lXWlQ(5Ln2 z{NTs!K-dSdo9mO*U&QC2jFcT|x+B(|M>0!m=XnY9fzVE0u>&-jBa#xcjf{%q*yXzW zu{&d+A`axSzcAl%UqSfrN9cmUB&|8*oz@GTKoG_uw^@%J3zVf?U;QG8yTo_}`o%z& zWi(%e*fjU;&_~Q3=2T{g7tIC9_xfTFM74x)-VT-&vD* z&bruNsBKhS4J1UMdEec#GXP z9py`!l-suq05rUfIK4T^37zCeo7CT3X*NcIdx3m#w9sU_CT}{BO746FXl_TB^?^*! z>+W1k4qDT7PhDjx@%XQz@en+et5W1Xv6{VO>^4q+E|ymQLf2U)00~Wy!1L`d8UoQt z*_+2yjy~)F4$ThK(bnIZ*+pgG9zO4@r`VTk^-Qzin&Vtj4Ntf7fadYbiT#L4`?>tp z4J(j&^8+javW4_-1ec?#%q249+Q9UfWjI*z!@Xg^t5xPXS7t+zi(K}CWX{>ER6x=V zr>&nzKSu#kLp1ImxXmrD#11Erea&4xe}@$M|-=1c1rFC?Dk^=Ldb!zEhD!BJ@d*J zM05Ck{%1?O7@&i=;+?pg^i_{qpcw^Z(C=}RfkN}kkx-l5U8UR5cK{NbuN}#_y_>y_ zRfjU%3uff08uSk=Skbk(NV0AlKuw7(jk=q!-k!!-yyC1|sxdKoqQf`$GqRc;gwM2B zQA_G&!n3$?)2RWeeRH!RBrF9nw7IeMd#jCw{;}uYY@*-tU}1RvZL|IJ9b; zPJH+33a&KkT}CDcD-v^elM|Ru-&Ve*phzybZXkO4oL&1pEXI&yvJ-H)7d$Pojh|*WB@?z;Km;9z}`qko?>4od2l>0cCm+ z4r8WP-4^^ZX8U*e+9x|zSXzRsdu~WK_WPNLjhU$y@25dEw6-6Gx#2sU+UP4v{_5C{ z?8QM622CTLuaQ;F+7}}3(|+|I7hy0f6T&{onvb|Fhv>%SqVaVOkaYEXsV~@BY_1tJ zrY4zUGK=P~ypDorHNy-qZ8wlhjtRZ`srXkuQ8-WmEx3mwwKO$QpDYgfA+VG~Obnba z;Aj8Vtz3irg()|#7;pwET?N)NqHkNtUpu13jn$)E>%7iXEp8}KOh4u}$>TqergHhqWiHN1>#_KX#qwaX&uu~KhaNtq)X9_sJG7J23NHPPMg z4Y8I<2;pbRpXNOh{Y^}w7kPcNv!%#xe|JzVgF-MNl$E_P3sg=Y_bCDD^f$10*kD&J zW{>QhYOjy_e!saJ&r1yXv)HYruL_pMMVzmo)f;n5Yzh&(dO@2z(;$sv>Y!R{t$K)o z0PS4C2F&ZHca6aq*C(v7P`5(@UF!w=kzMKEK9pnY=WKfOH3EI#noqjUpdAZyk|KYf z$PzDZ4qaTMljxf`?!tdly4bFCzjH~ig( zhGJHW?Tz(OfR6fC9Ly?({~rZul{Hp$yNH%DP5r1jH^A^8ydR*~<_K{6IoK6WKt7dqdmy~b@oR=xp6O#e*$eJG_QB!bC1cOHAQ;~luzFRm5cw%Qc{T;=*UyO zzEaLjF@;7e7g%mqWt3FVAuGcEu(g=2inEbWU`YIt_kSQ8^N^XHoq-{b_VaAxJVj|LWjboOnSh z)wVEA)c#g;_(K8v-(9GPqNwTI&Qt}{}kel4W9#KN4r~leI1XQ2&+75_zk~r;IPWS+vvqjmivSgHi1ZR z(w#|i2YlYB5sLTUyB_Jd7c4V#vg|Bm|ERi;1I-7K1#%A;Ztrj2OXZ4W8o(QBOH)J& zONMhQ@4gly{^a57?#%WFC7^=uNx0jVhYX2lkFm8pbX5BPS|BrdV^gm!g0ma`gvqn} z$L29;e`@vUszv!MK|WL_+=Tz=o^cJ@tFi_RX`r*6K6$rd8dU(C&1x1Dd>s13o%85t zB1`mETMHoz2Nv!{g=mDmEFUEt*rSq^EqHKW86?8J9oYI z|4oAw;0)w|Ekq12JcSL!+7}ZOX9Up(PBO{3;*;Qu#w7ndJo{~StiIz+NqI9}<@B+t3QoI(xTAcYU=)AqUMbw;`3)-?1xUlpLv0ZW1xlSwz5YShhea;b2iRDXzLXaE zV`R)B)pfq?=3E#aSQc>>_lHy!yp=ka=qX6510tO zPZBIH+#mWH4Q)6}JwuT^by{tWp=M>tsq#9XZyC6uL;Fh>?x_zi0;4-reRw#wNPwry z0aDO>2~ZRM&XjbRu7oyl=(zu$9W%NkTQ2Kg70kpFFmDoIUM98d^;5KM|1%;&790tP zOzDTIOp%(PG|0=zO4s`dhKrXOApW;Y#hJL-ik-Y^&faeB8ZV*igHn z^5xuvUW*F?El4yS&mf}oGGW+k_Y}Cz)&6((LjXa|VT-n!e-{q;>}$K3LwAcrA>@N3#pTaS z_|)ti+*sfWLtcLO;Vn#F~V)_s3r;2T5#vzNd8Qs%-_GE6M|T>2UK z?6lW4N-pO89(o>gMFts1mNY2&T%Khewt{0rlzUuZMmxJrL0?Y(c546N0@yJ8G%DC? zF3G>B{$1lOz(POa41!I}r_9S?^=vfYLjU8YtYoue*I{9`H>D3&J@M(o+()A1gOnF7 zXO%Mj1{GM}9yJB{zlvdJ0e0SzJ!4JwIJ)+C^b7f92OA4N=ZoMJElK1P0>ysD-;sSC zEXm2WzSrRR@YgK(OdT1gSkb^MkyqL-V9$g&xj~Pwz?aB{SkI{b9xee}$pcNGNkd%V z(i`)7@?7$H0TgYZe$hPJVE-A;0Namz=6|t;V5yJKVX>}^HLjR(Q=&sTeurPQCcFT-k_P?-LSKIL9LfOQH&SqX@PC zz!Yk+!EUU}1bwtC>OUr(m@q&tR!lB4`s6El{L}C!0eE!PgvibvDHWV`GpQ->1ipQ& zW1V%C_m6KfZ#$u~ztKcl7fYFgq|Aj3m2=d=FfzkZzpxwnV|S3>iqeN1zuY>kJVDcX z%~_|RV}+l}vwi)T8%TAX_!~$A1s`xQr^VGc<#p23C(sHrgIX*U&vkRHoMe7zSA%-n zS7ZOVS!%?#SPH?&_$UAQLkNCxDNxB?-ceAFt^j;R;~elV=%%gi~#ztG-kVG)>R z^*#mIP_{bS>ZjWt23D{xFhHlq)Galc+yDfqisnJ!=G=JT6*wRN?SntSnkXl(Qqrhb4eDxrWYr9xZY*meUe2lUHnBF}6E0gV@Zo%5ZuUHzhQILS`D>@6 z0-y^p)G4#A<}0B28fO5&@whT7>q;rD@yT;)U$G>93Yhc*K$4QiKo)!?02L!NcQGoV zBU*H`!lpu&Tq|!jlvu}9FwZX(|C}8on4y6F7p%UFF2D{hSTaHP08?iR*g1X*56U4b z!1|3oMLsb?`F@n6*r>mI(Evp+>ET`OR*^64u2n}K_hv98ci&w0gGMxwBain7#V=j+ zMmX_vxAjGE8etD;RAvZMA1zGc0BVkm8K2oz8X@cJS>ByO%d zt-6nwHa)70f$Y4kOhfQ*;Io+A1qQ-Kou4N-(mKZ}0H%5?p6pqG$9RN~`Dg%j$QD+Z zq)d(5_$0~LCfWD1?TCXNsfQpgF^s295A8b25t>WCKK$tX?m+dC43^)nnyih_g$77I zi*mM>9NF1{TWys%A}9QpY2i!G6YuZDz!}}$>VGRXjAhqzV>QE1_JC!bV7_B>K~dy~n=8kiw5tcKo|~o&|W>(1_gf>LcN)`|%;lZ5+%-`RCMBlpmut z_xtTE3e88B(e09G#Rq$m9^G1x81dCXT3d~p4}4Ja5g;$zB-KVHoe>Zl<@$<#Y!(uh z!vn(DiCu5WMwWr0nHga8Hjj5|P7>W_*fWalNP1N~@rZPE{PIL6=>6Xv10Qz{5?3F# z%msQ5wYuJpT7vlmnBh19X=!r}0m&~yUA@?y6wOz}8PweOHgj#qbmhv;Qj=7VOe|if z&P^$<-OTrb+9h3pxtqDl+ZMZvGF{V*zIYEa?aVoz%)m+DC4eNAzS}yU@nzRavjN`s z98JnUtX+n5dXwTkcT;{|AD00BuIx$2XTgZ$cSOD2FJR&7Qit0dchD(eY@G~Dw z)$0{LzIrqDGt#2!AYhhfLPGL)$RTG*X!c=VzDDXN=d#z&v)f_9Fu%gN;?PQ9mbE3lV^PvR#?r%~wUIx4&-CK*madoYi`avC+_+!9xglV)04}~u3lbI4_E{E@bXk|J= zY$mn870@I29>B(DGVZb2#(IU1d)C_Gp#NW%-mbTcC;o7FvdIFZ0Qbf`VnXv)SO28X zxlc8}byDihIPCNozmoqRRGZYfqiSc*-%9zbDKFCJ9OpCEwqMd97(CTNB=b8TG5P@u z%J7^uP>D69H{Ec9$if{ybk7O@exNigV(1#Ew~PuHYF5)!r zb1E$}-O}FO1Q};#{YN|6NN^EK-t#;aR4ZR|a_CMh{8z2Rx4bh4&uU{L3hyZD0#SlK zU|?(_E+qfnvpM*cIRXM^0y*jH>I6Oo`!P?mvT6WU{+g)D*{nPhSJ9EX#7-x%ynCpd5nT+o}2sAyV zA##BFayaBD)u%*SmT?9l{XvLVp3VC4auOZ9{QE%pVYv?!#m$+wN5kUpSbI{}E55sp@-uM@eF zUQNx+iXYKl*<=_Fu4Gr-JKRc2HpmeiMjf(`pD2l0Hry%n!Nw=7~5VL zRhPj0Br8lNv?c>^bbxmeA;i_Fh~3AK0@aY>AflpQdV-ZU7sH2!q&ZKaFg=wf3ay8t z$Oi!!XeNd7MjX4ndujzU!kteLkfYptpZB`MU(gZqJi zBXA@l&#ixG8}&t&r;>4X<4Sc4z|5Wlwkl8l2YUtl1S?up9_sprBPEa^&c|sOGRdchp*5Sr9#^gVo$(is_q=OItYF5FFc=t4r_v z5|vF7WWgaNKj^ODKX!Ei%6+agHQQ8A1-ac{J}I%kpnF6RNzs}O)rfP(VXnM4b6r~g ztY))>nH0UDkQ+H|R^^2K+c^7Q*soNGsa1%#_%Ug`U#;h!H9CLu%^*x8uFn^V7v<

    RKmuR$M0hLkxE)dI+f*U$F#6^d4Zcq53YClee9I`BJKc)WMI? z)fw^V%!zwVdGHmFszzE~sok%b-HW{?(J1}VpitTw1ujBd5V*#t2j5rqc7SnW_A35J zEPiH8cgWM#c|V;E5b((;TW)*VW1>@WBDX$NqN^!3KFXN-l$|z-jc`nAkIQG0A>I@|c>?&okbJCs1ihb4k(jscFXN$raMYKq`wvE6v>CzS*z3_Y-U$lb`+mZy3EpM1FlvN!5KvS za4AOVgX=jE=ta@Di+=1Tl>~j4T*F7@zS*nrGKm&M63o1iFH+lU<{gnmH;7DZ?lOUR zlm0lgGoh@^x%DzyF&}oVD;Q-LR`3ot44&)7wPY{ONU`>Ahb7m2iu)7E3uh!F{gFINg936y^$V!U(#Lq?TV%~ImfszEM{olOGxJaWZ@9HD+PjsYODK={W*Z!~c^xcbCcK{0Guims zCRw+#_EUnH;0;5>vaLFxl+>43#YHP;}a`?}H6pVb6eDY+w5wM?ZzkD#H==!?aCK>ZwV@WHg8g3@R93 z;Z8>ln@cy~(@B={AwvA2&%S#Ms--i+SEFSC0}qe$5tvfn`MRq&!S4h;D?;?quub_Eh7ZShRtnOTNLgIamlAcS{Jrl#g zeN_apd3v+`5}$k{4QRKsP#*o5h4X@f=w!XEMS-rlG3sV4oi+VPaIqh`t7_ZsgPvFY z2X!&H&kpE4L&(qa`W9?Y*@jiCC%v+}Kr8M5W4n=)aB*1n-5Esd^$X)|3Af4qiW0?0 z%}dqeCzxUZ%FX^B9Zaiy9P^}sV!!1UgzEPx`;TG6X4zhW;A~w?5f=Er3#k%wQEL`;;g@Dr*=4Vn?2kF zk263{W}49F9-Z^BxDw7QxL}kRg1+_-+u2)2R@X`lE{qHFG)Da>Q*hxKF5%P?`2Ctt zZ6t5@*h<(f*qq(#ZIrI& z`;EqJDG`nI>Xqb=fW4b}0tapkG;FpMb&2p; zq=LO~Vtp(`lZ$Ic;{TC3Ui(d5$r&BhmzJAl zPd6K*OuF1bM@jTnXq=O~>EzV2p0U?@DT+r90$T!~7FfhZQTb&XuvA~rRLJV2Z>ERc zhhTxHgZo5sO<7Zcls>!sc(!kkE5 z?UV3wx+|rM_^Sg{$AgnUMpU>~x&OUs45ZR>OCcaoz7cUINyK=)8i;)qT~%u}gWl0quh36I{a2fROBfHCE5&*wL2jDE3(g|+ejUNVK&ai5G>Nd##7 z8GSe(xW$BCo>Z85X7XD_rg;coD1nAq6WiFm3;^gAl_a-|!d^b}rXemOD`O!;)~ycT zRK0NjCTHiBnXjrd{qnvMLKWlp&Ql;r>;?pHwK0B`Q8xDxNH(r-t{EG;Lm#n^1qe`^e{ubA*6d=a9{A&rb~}Rx%~tCmGu%$_ zrh(f~FoPz6A?CY+6*Lo{K5~5u{35@n8mB)W|5&@gCEP2C1ppntEkFqM>-4!-_1a6d zfCNNg6;r+wqD;KpB>STe83oJbLE`BV{o2`-No+KggYv*E!eE3=N-cd%2 zH1n#3eQe|rWxB?`jEw!~dOuRf#3u%haqk&KoTZV8bCNJ0bmiNFQ-d1cD_AA=K63H6 zbiBhe-kE>JVqU|5}i{j=!gkDYDg$G2AS!j}+OhL{@#!?;?b@$6$+) zcriq|g}>)L$KNe4HbMw^Qr`fU$yiDQgRMh4-H!829LGx*H+#N^OyBkq2($6#Y%*KD z$%M+@BRqBHA4{qcrV_?I-<%&!!AHrkw|tP`xo<}=!Z!t{1pEq#3lz*rgPrBDO0K6n z1{vFE>I=1#*YIU$tsF6@Fdszn!>F!x>K0b_VmwD@J*Z+&m9>r>Cng)-Yn$)+Znqy; z2EjC}`dt?}n_B|`CK<^lX+7~xWQX>~|FQ!1gMDQqsN@F>6&)j;9OK55Vv1?0Hwgr; zqKKvhmo)`MxoO)gF)ObLyoFq01%+m8pPT;B2nJj8rgFaxh3P8VEVl105`O`)_3Y8! z*EiY2RjkufiikKtv~u}1UWX@yb{8_Djm}3sd*-qALevGy&6wFBUH5=Dj-+6^L5k70 zTaT@9{g|}&4-tXowMk1gT7af`4LH|BD3iNWzCq#>A_X{=5GnY@{CIz8>U7hxUJxHP3o(K}FRP zR3v9JbmEI3KPx=Kf@M-5dP^`PZcW19yC;5%BQB=mygup9f(i!G@!8-aX9oE5+1 zBEtq94Y6uI^UfU2g0NL>L9~Ka@`J0ueNADoTRGW5tG9eD1#<93m$D0tBs&?B$3Uzw zv6Q2vP`!wq?FFB0qP*`&b}^h>RidB()W}nJ-6R-B@Gl9K-C|a98sr5924EbxL5qxI zmf_~PCqOzIAx45!tnlfXRLjn)+co%Apiv+pX~7Z4^z9JYNHb(fU)795!4y4C9&HHM zAM!d{|H=m9qc4iHj!c}d?pj5&ip2m$sd1sYYgY^$4lhWC>a}fE03huyCdc>aUn25g zrbrh}Je|5x2LgsP)#h4h77yA47Wvyi$uFvTh}HYYVynLTy0u!pw^>u+hr??zHAU%e^eARIoV?B-`xHgSpL$jjU{sA7YZ@M1LSa<{$yn$VnQ=q6}gK&A0{b zf`A<)EP*yl`mI z9eM5`G?Pj2F3_r%6ITnU7v?Iy2S{DX5@wUX!}3EFeiH#ZDdIFB5sBL5diP=dR>Z`j z^lR=3i`gO-&@<m~tc*Zw$#D29PUE5nd?tn>MesaPPRK4^_1V$M-HWZ+|`I}#`BE0BT zVACLq<1Z(_f3SR1sbipqVGz&=kcQgERv%$0=uFQV`07Z-e6_ngAtT)-kc#*o=zy5$ zquRWD6}^o4fJF9`;(2YYiu%rzJlnHCQk~s9-T8tjrN5fHH{G_{5-HD~obMhM2l`d$ z>C=m|j?lS)zT5ylA82?*a>}~U+OvK)2$nW_p5D~?mq$Jj5*aVy^9Iln-<0QWh}epw zURW4;$Hb$%Em>l?MCYLrUndlYYJJV~NjX!=`^*4A_cH$8Vyk183yK_KiB)A~W0#Y! z-b5Z&Pk);^BSjKs>7Yhm#smuqkzX+pZF0bE$OP0-b|gJ9is&5a(RwHzzLy1>WO`4d zPX22%m)MwDu6(Bvr(vZEZdj@~`O>JH$(AU^SQX@K08_Xt=MMrNz|glt2}sW5kr(L( zwynfKKIItMjd9K-{Yj{r`=%PkE(ivpaAsDEDa~oh$c4&w zZR()aB3%5y1_p5WoI{88dtN6jB;=pGV^u;$$25F@-rq;D41cy@$J1J z(th#UF8#9yWTnINZ4^)P{KkX$ai%0iCo#|X$yU9C54aD*>y|jSX2dd4gV*&1sifcH zZDZ7n{NyvRvrXDko61_(sR%T@701&EG`0?w`06rUVRVYCPQOzPB5u2l_g%q!tqHa=9 z_#wuDYc8Gmrc*#)#V9D%G%~~{RaNnfr~AxGh}cWUg98gjS)p^J^w4HMXW}kxj3M^9&;H|RgIS~{l^d!r zH$lodf55Z)Ss+OW!;icZe{b)bql9%Ueqoz^eoJQnYIMnB>DSrjjQ#iZp=8N}qI;Ft z+-MH>_=ypJq#q9y6imEU47QMZgMod!B$Z?TZVX%~%D+$<<;yg9Y*H0=+tr!T3~3{T zMqsc$n47dp8(~&=ctQIFi@qKKIKzzJS53sbdZkz7(6VLV53`5>ny93 zd_}m+x|dJhjET{1Bj)>AlfiEoiQ#vZL83zDi}U!MjqI;suQPBT1$Q#$@8tvv3SC8) z6k2S-5#_pVQNW`8r5;W2JiCd(JNx9I#%kbpO>H%jz9NvN>LU9@oF62Lii_?y;>Y(G zP3w3s$?c{I_t{o|(yncwpnXJS;5|55aHskUCb5b@xyIl|c_nHmmwI@&k$fe0VYzj{ zWsgV3TV2D7mrPzvtlyQ*6P}@I7nA40a`(nN>2|YNR0XA%1+v=J z!K7iy=V6hcIJ-7eC%ke{5FUc(A(!!@)5Iy9jn$N?8zqt6mJrD0;C%WC0D8~Ly-Cna zdS1L1*OyJxXDAeF<3KcU>S+t!-jQnk()CKO^1FnpL~md>S6O)*@&+-Os#*MwX5N?q z5>KO94bSKqDF2u`P_-FymMF78zX`2s!ni;etDVj!;P-4{-rebOz%b*)rmEK|^ZxpM zmdAnoeXOaVym06po7cVg$sUN)20^kyTj{*}F_D(Up~cJ2?ee%LzdQ$VFKo+_?_mlj zPRX1CZb_9ytPf)_IeJQdeG6O)t;GD;N?$&`No>;J+TB(BMZu0$9w`#C_{*-1V?zCd|M{PNagqHmY;R;QP5{kcyu@{l(; zSi-@Hn`ZSnqjIJEl00r+C2CI$*csHIDt%}D(z$);O^Z6F*X3uzSW04@#qOQDxoiMc zYC`E({>#tfEPNF(#?vH5J1YfhU(I=QE=C!zmtF2%t6(n#T%D2e&2_zwrCk$F?@dgX zjZ?k*SE1QFk2^-mlPqzOS~850n-zBnarbg6qdt3~o97-e6g+k23;lUq zXHDx)r4@{Z0cSWiW97GTD8_OzL-i1=MjlJGwq*Zh-3Ti=u}jAbGFu$A9dnU z_jZ@(`&#>SY)9AhV|PU^{tTe*%Y5_C@3M1X+MIgejIH~WzdK(t2=yM&m@R8n?OZBR zUdGaRNece$feCz4se1Qf5`==oOQ$ZKMHz=(e1?~H?B>nZepo^e1OEp?TWiM zO!I=mx3>V3u@JJW!Q=$&$9>va-@rU``4Fz6FDC$WXV2k%apA4;jm-<~i(JUGP2$aO zm#s!7ziu16+I^5EEkf$-M>^94_^-Ocwkq|H7)#a|Vo+4ooK-3K#<_MHc-BTyR_n9% zaJN#JM71rsM}R#1V`)OxKrAezhePJCB7#E5uB73&9TNMP2GywSvFY3075&RPVG zozWohHnCI4f%`#SL+hmL(i}K!_;mWm*M6m(ZxwS#iYU(Y9NoIfZ&fN#cx{3vXHjix zMzJZ^)G+2Y@-0zHnFfxADSSy;=`~rjr`HgM?efbyj(1CXY&B0*a9(<~uZa6H&urPo zp)5#?rZTM>u$Qmfoi{D2>RehIlMv5S+p!p)Ds>4d*z@wZ`M>Wp!Jcarhx8w zw@J3))$TlAjFZ`s`LQ}vADQQ>s;0XSIUYL6hy#ipN6y*$uOMv&?EHE}-He)C_C3ot z1KPl`IKoF*nAM5B?;_U)~v8YXbBtfhU;!e#5^ zMAlV|pWnCDivRF0lQsTM+QR5Fi%Y9Y~4$%coIa~)AI%pd) zX$Veq8sK70_KMaD^6VGWL1TwsAYE!v@;d=Z=V+SsL9=93g$^)G^?~Pcgn!3de?8Lx zqAO>ncxuI;VG2Nx_5i-&`>^g^IMknJ5O}*9`sgQeMpljs-L^WC*Fc;Pb#`YK_MS_% z=lxODtuVU+23-BK9s+@1GH2%n3~9_ZUX60^-HTtqosJd$dBc*o0uMz~@)G`p2Qdj;X_ZoE6z2*X{R1A$dwg zliYTRAh^h)`t$)Ps(AN$eawMd4j}Z&Y+l_d6wp;Dq*EwD14_(Q!({lN37mcF z=lG(x-P908J=tFTGWGYHaN@^@h$(aD@_)r_zIq*FhApnrcq2%L?GFe$Ohc_j%}D^UCn z3^Ts0go_pwi_vq7u$%IGrNqG03XHr@wk1n-a)?z{daZ< z#6$Z8Kwuw#AaT}y1>38DEb_q;%*lyo0>qZa_u?z*(WFvF*J0z0oWWlKkM=wWTR}xK zN{o?f1`E^9qIn)7^M_S(N7R5VTcqGvHIw$EL7j~TMSe73fSFz5NlfmwIWhmr3GACI zDLtS9c|ahRaAxH#etJ57 z9Uh7?ntP= zs3J$keiCyW%3pvv-`7~E!n%FLR%g4!xb&;GhiqzW5qQtI*T8d2$-gWUKtjlBBG%15gZPTmH`wBPSn;Er0!a( z%|-tyzJQQei$bf^cHyi}Y&LuVAPi!FS%&pv)z;%|{UDslWIIl}IuLl2jGYwBJT=Ez z14xkAMd26r2W_jiEQa}U()H~ntXf(Db44G zGCr)h2nn{2LfoBys6O;iEn1&5;a&aD#vUw`E-5bI!BpMz>2-So#hdYyb45w;)DzF- zvVJnZS4TPEMgBos$wUZUrM^+CGKPXMCoAPVxpHGl zLo&!CKy^e!CBfrM*@L;^n-cdhOs+)f*s}+|fOYKQQUN%wz^ara7p~s~d<6}NE~)C( z2-rhd0z9N3DT#j>beTO%@c5AN9R*e^3IKVo&ANm~^HaQQO)B^PCfQMReWA5B=^Ew+#{ z$+ii*Gd8pNQ$@q_?&lPUzMsCzD+5>_5tG$mUa~^j%k#eB#TW_KF7R+$P7eR}KkOv1!C-N30wX-GH7mbYpKDDV43pGo z`aqdQ?24zQ`_q<_T|9vFD@vWbqwF6w7KaT9tIT!)b9>fwgK6SApqBK|Eo~QU4xDeQ z>ey^oxw0iqs)LKC`#uvK(FY-t=@mL~My|`|5ogjLx>i*f(HYyv?p*{N1eqB5fiz=VVheK&pG9nc@;RpoM$1>`7Z(}zB3g9_}1}sX^Sd@8%tkEX@)yM zJmB%G<1^78l?TLui8aUJWua}f_vRiYKeVz3O{B@|KGq_Onsmbv7E4W>O?%&8!!ssE zK-x)@j0;zj+ua3CIvJq$_Leph_XA420@<#`JtE#GUxe2iI>g;y`W~*CQ4ppKwTR*5 z0|e*Y+oT=wl#vg2_ya#o;+PW}vjWN5!BA=8M#*7{lO5Xw}9jL3Phf5 z^#%1bowH)7(>85XKS5w`-}IuI5jxkghBWCLh7(qt&B@>&kEi z@ZCF5^}3|3;{SbAt-JyTP^Q9lt%x&B?KnGE;!C0O>O3ueK#?LUMG`M6MVt3iz|f=p|_tpQULH~&>>EeH*Mo8PhnBy}zd`H+QG*XI+ ze?nUrn!0!A+S)WUL-XRXcTt{A9ekiJTIirhWN2Ep>%emYN=6@w*t4=I7kNH?uI#E0 z1^l65zc>ZoQRStqs@w99J+#HDtBO4;&=E@oMbQQ>^v}kOUKH>K6A2fUWnIO}QpCAl z60!;YAq4xcC;4ya_fMPoXN&Uxe)@k^lAv`hxfu({APq`^z5jytpi}_F@O(P3lYD4E z9symZ)u_sE7!ACuAE*^rAhKfnC{NJ4F(C$wkiIAebd8C*pcYXQKN`?mUOc<{tbHFU zIDE|Cueo_$ifEV^gSAmQ9maRM%6jA!>maR}P@s5`cfMq=Lx8lRd?_ijRMghC5yPKC zmg)08Ll|thQq7hm!mR`)UXNkNVByH(S`6D=d{#w?oZ^R8#IGtOK03{0L8z4<%P2+& zYxtCOg!_*w>cR4hn`2n+i*8RerRb^AqYX_7`nYdcHqOM^eh&@`7SWJUyLlKW-8%vx+%t#6EwvJqx2^&-R{uU;`z{A`gC=HD@|Vkuz;t{DI2C9Aw{wGn9Q{f z6UHscrOxITSxw8%iHmpSB%JM=UFEL5@WhJ-3$bsMsMt`)lE)^&NGO=4ya@=24!`jk zV{0?AetCoZ*R>mYqK133y)QQJJJ&Yl5RM;}laNTvzdu%tQE~8?3_2gbSP>c&*F``ie_9HtO=xYe~F|t2@aaJyY?mB@Ef?uO|khd3J276Gi*4e!%J^ zG0gOldl?{Rx5%z0n9hACz<*=wxg1BMkDv=Sk-di(nfp$e!jM{4rQAx&EM)FU^^X?= zZx|F_3rdiL74ql-yjM1Ci9Wq^As)L?>vzZZgGT-aVHdu2{mlAYl{Ri@T`AH zwLTUypUyJMofQ8-V#+Y+n?y(X*PTtzM-~lzQ(Id_xSBPkpTn^Nc?pDbsZEYqO-zS(yzb`yE$?z#TEdJ>E7^=L#LI8gmRkMv25O*>rzf zs!V3t5_`nF{!yjpZvGr2<2@w7MoNMV`PBjEBbD07yv-KVhxp2{73Tp7SHTME`jQ}t z%{~66*#dGN(f5UG6;^^uIIH9Z6S?ybt(!&ymx&s(14jO%OD1e>BIaYvs}IK^J55<#HJ3^sAmIawM_Rl+Uzq%a*!N;N0!Mm5ba zYS3xNL3)htr2DlOE>X!(i<1~Vz|V*7rmbloX)rwJNIsyX9(0@;jJxdE1q#1A$@y7C zzp`Uu0J*q|leJC`Wmjs2hE=sQ z+u917f8jyCE&jZKjBzVzFI z{oc$PTdMUjN7!cF6x|A@%(bsRgr4b zKDlz%GbO1fJ5uWBO2|)cTmS08mjpATo3EAOL=?P$=&)&`iwqu)#LG$vHWsv{3+$J@ z>G@9n=cJ5X`t>@$+^#lb!FW(5cqqm6rA{^4b!pL{C7ZA-tuJBMmDAhuO;_gE<$T1e$BeIqS6q_o5wqMuL{%x767*S5$p};`3$@I|zFro$)?X+;7GgDE zl*h_AWiP~R{ic*>=($<(%u@TnF=9`{LRg%mi*}f^<)+SFm24?Co3L-75$9{3$C;I1y_fjI2Mjh70edUa zy~^rc;kq0^19yEAHlm1o$|e~%-Xvtzos^U?{whpQR4~?6HEg-(nB2*uA;Ai`0JdoB z{w=vb>}icQ^J8^_4J@Tp)g#y7=-{&XC!u=@{?s!)W{Ft#C1S!D|#!5~igK zCIjv44w&toCEx;b;~0>}S!8qM-+$e0c7 zs1|>%DhT)LXG)rsPLhue0xLUPtg_~zP=3?UjKI}3j3#()m#eXdzD(-B1{0j_HFqCT zDk+1jPd_b~SMmtW!$NG%JNDSnam>gF%^?LJf!j$QO)ka>i9T9%NcH=JIq-3NU&BDk z>}3b@(;rycUb;a}m4uiT<#A&m+^|os64mjVB6@5a*27`pg~rt6vohN49zijEGhIt#Sd8bCA#(Cr>pM)yw zQh;Di*->`Bw0j&KU0n5#RN*c=MK%Eek@fyufCCXejv^6re(ugI);Lwk-N>WBxBix` zhpp79Q{1zo^{AXdDJ+`&v}DM5beQ|M@A#yzt0U!|t8uy}PeH{ja z*?8)t_IV8Ek97DK@yA+t=c|+5M)<$q?T(*4j=>b**R!syf!`%;PM$xD!T9aPV1oX^ zV3y&npfL=_^B@K@aT$X-7LCCO-i$9dRE0mRy{f0Hjp3sIBv)lVfp`AAdD6@sgOS~Z z{;l#!*YJWjc|1;?)!`XgFZ$bWdyjRJv2aApDedDIe0rzYfj-YV6XbA#bwmDFbH1d- zTwL1}^!SBGa&V|nTPR`Ag%dnNMICo@Z{%vR>FLIJ(e##%4`w&3|F;6dAqi=s5X=KEGENi^b;GMICl`UFMymcXoH{Iy*ZLmh}`n zo2S^66cyDLJCimwTbi4jJM|Vy(N$eZ#Ruqp@VR>Q2fS;0#@Ud%Zc}%DenWHfIG+Ij zg{kq*#bxQ)%iFBmh+eMhMp#em!m!g>0!R^7R?E?ij9eY7R~h1P@XQx*2@8Kt9%{wXhCN`3zP z`RYKWui>Rju?4Ph3;V4qk2*Lyre{0eq0 zVL}SmJ(R0yI7Wq`rKKo8zfq=s_$}x>AY_052Q2-5UpOTvXTJ@3Vq)Urn`@sq_2^dN z&cw)*3%-eJ{$6;RXN^jC>wLGhxUa7-jC23!P!(lz+Py7~-r3bvm!k7St%1Gob!)o|Qs6>pam`n*T;{9bq00RqFy zy+52*)ooE+*G*#3F2ms&nlNEJvz+v9?AB|ULqMx)43bV!D?&@OhUULX4n4;1~5nA>4 zo{o+!*391Sa+c?kk%3wfcdxy6&CGCP1#9b~J5jBKd*4F4W}-9OX0j_+h}qTE<+Yvs z`I(Huf-}VI;c8dhY+OjaHMzQ>EJL?Afz)SMGwVWdyB%cWmO;?h}&h4e!{CQT+F0b&9&(X zFjRqczonI}otreK+ozQ2uaX3*aAh)*4?hRBfBFrtZC!}yhuLuk#cZ+H2wPsNQd z2aLUVP%c9D&o7&S-O^WO|)va{^?N-aG`4aJ|c6iDo0ERCuJa;$uI%vw10cKAlb#l{|rkZT{68%$s* zj*rsHMsd#xHV@*~wA;>< zycAQ%n6xH5UsNcvZMi9xyM~c6XnlGBB49p8vIGe=Lyrb94FA(*s+_%bpmh?^>Wf)aM2)e%!1w zFRJ2YQCDqiJDjl0D|CL#y?f$g9VM@Std_ra(lC<)?+fA8yrxR^u?DL2R3WBVV1Q0$ z_2#|tPYY<#^5Wwi-ZjB*7Kz-Z$qt4bXD~W!_L7Bar4jifufKI2JzP|!z=&KzmJR0L z_4&}y(Y-b!e{R-4tyyXx$fic62t&&_-dTp@T7&sw-JN zl3|b(vluH3SIE-f62F9XTkK8M#nU<%4ZHOGJ+X;iy&d{k4c7M%A!ee_psvb1bz)Y@ z&tr5F2NxAaVg7hv&~UW+`$t_n5$g=bP|aqI=koP);|ePC2}h``Fs#Nxfr0`|vSL*D zziM`e8x_HiHP0~wuV%0vn-9%w~#`UJYPVd1-y3hA=^jYYD(Kc;qE#?guF2u(c z&>9{kxL+_%h+ABaKm76#eg13bB3e+(EYJCv35S4~JmTli?VGHVp6oTg9>6_>%{URZ z@1X{F_6?ll)D!(rKG^Ha3c1;nZ{N%m>SHUGTf~*7YxJ@85u$>*xohZM=Cmz&m25!v zv=={7EN@#N+q5Q$v!%69gx-ozY#m=BEXCn%1N#5@Q_`dS(NXu2S|R51r%%1gTNmi~ zBw?r@3CknH!?pDqIF|>-1vd3Mr%u_|*$^1IITU2 z^B@fSV1rQa_=z7nf|ie;(b@fE%cnj8Mj5*UxhL>q?e+mli#3@F3kUY?8#`P*-fnl| z`0?X)9J?fTiKrNUo8fbMhEs;J=%KWId(SjjXv<&u@TX`*dq30k{Q2G;vynS=+**`J z?WW?Qq6DL?j+JPco6@YcYxYE041A}joqA?j9cUMC*m)&U7lv!IepW+|_f!u(q&{jq z)}L0?mY1BI+}^yTCzn7PUgS*EWG5oaD(LKHazKvO2t?Y|j;vN8X2f>IWAsIIwd3qb zmjD`;TVA2A!5$BUD{f7#zc!^}`&lTV&eTf1xUq@GNfd^wRbOs3Qdq#!=7n09${nJj zqPoY+ex9a9g?2}uG6@0ZJ0q1)hyE8%A*xdSaS_ewOC#I92-MAxn45mHO)qiDQ&_!Ml63DMnDTT3w+k&@OR_9)^Y6 zhJv@Cp+Uwm6FxlC;LsC(Gsb))u)mUAvq`@LmJM3n*}JSS|G5HzjIP``ib3GAQT)xn zb)uBkl9<(;^>1Re=}l6CcSQt4#CVgGc3x#JIMd_EmHba6liDbY z+qX~H%#MW1<5)Gau7d>c09|plH;uv(G%+$H7y8n5^Ml}D}~vuUTtd4wvG24 zsoxwJyPl~S>Ib-!zP z_g0Rwl$b_D=j_kHqcw&-O z!GrP19~trHn6JXDRr%SN-o#uo?|1qQbBLMG4ZY#0r?LiKb>< z)1fI*O@(>`^M&R_UX8BiPlOSjOZo*&t$cL?D8ePREeS>Ty1!v=H9@GTY)RAB&atXG zcInckwurSD_OE7ncj1lJl%6-XdfM7f{pEYA>ESx)JD8yO^q;wB6KW0)D=K>4AXSt| z6}yt3dmY9|JP3nDU_|P8X_T7$t}76;L4C=d2VF&w*87G>yA+DWv2zVpVt-<`HO$P+ z40f3l1OWR<!dQ{AA9Y`oUBc%VlR+fGeuVj0Nyov&-aG_3}WLY&%O#% zb|1+QU5{xnTw3D4ch5c(6EwGPg&hd8$^uk3KN%=SsY}?l>%`M^Q65ag6`%;VP5tBy zH09W%fhVR&QC{mX65TMImtU*wr=km7d2LYrztIViwxkl*hx3!!Hh|~sbyo#-3}uS` z`o`MFt^t(do^sDdPJNWcty8FFu&@6u2xphRLO`6%0$|>iW44*gA}ZCtCFX?y9xmLq z3Zr}-){yAB^E-FqSQK$xzv^7$_F)Z*eJJ9j!b((p;_shHC7jC*io z%&mqv*CQSdjt&l?kP7hf9%Ek$DXTOIZ_cl<9J|k1TN4Np>HRRvtzXV1t<1`q=$n6M z7QcCOXpIzR!|9**C~+IhT$q0)irE$q{nlWc-e?`9Vt#&km%h?y=pi8BB(@gQP@@*~ zJM<0ErR02ckb+UI4d%DC*If+(bV|~=4*s2RpG`bEu}>xqbI=E6B?8;4cLy4vZ|Ao6#KbCzUXn6lK8!-t1x~k`_=@# zpMG7q62s_N2Y>vWWc2fYDIm?~MTLbmv_R`5uML=!#=ja%4Cb$>#m?U0ol`%mL3PU4N9eX|Ay(Mhr9tT~YBm5xzYE z&3(OvdviDhf_Q~@cdc7}csLsTU8^*ue=UgL028eAchy`rO&2iu1^|8#$-YCw!|9^o z$8U7!m4{K&({**M5(x{`7MXmfFf!Yvn^P~&?0IQ;@nW=Bj+ig;`xm40=eLWqF5746 zuDX11*7_Bir^s`*wzd~8T&T&;cfkYnk~z6UF^M(gD?}o2+0W#idVUmAh6~MI%H(5X zw=ZaLI1E#{A8Ju-7`EkTs`T}y(u3+Vk3x7nB1T3=d|32nbc(n}+pzM^o!499x3*^zgKh8! z3_SYs1BOS~Tu!eeNnv4$%=JIMMNrV&H%ty|f2BBcF7maF?%5nmrU$D&uu=?C>Z|ry z9i2V$Hg#+0-htl7w`}!P3k1;abHOy9+M$kP{d1JdGT8mOt)pWbd9jWvlvFXEA?Izr z?OCpyrfVU>r@v3(`6cEh&pcV4HV9 zk%6>8A2;A?g#qOA+Q0TiyFt>^0ydLu9n;w z#0}Pq@3gmt+s3H{;z-7y~y7Wvhh%{#%$AOwGjsg zho;hWBlv!u@!8n8I7<0s-oz6(&GR<(5%?Wip~ni(CLHUOMukHy|KUn8ulb*){1*9@ zG5)%f22AQ=RAAYz`x}MD&YlgYhr9Nz1fmgPxG}2so}g79wfK-+)!fy(g*aEMzw8ne zbG1l$mY!~me)5Dz9vWO2i@_cn1;J0iO2{y{OfHAKeo&Bbk^GWK;7kr~Q=I5|di|KU z>fD#rLhF@bM6Z5&?5|Q81%qQ7PgJ2-8uh|T8hn%!@1X_Jf@yaqb0B$!9i+%y(hTe`S3)acu4D}0YkeilO)@p@6WI5n)kYR@7_H(qFUo1J}^M$v>rb6BcEJY zO3L83Z|olr_+Zi*eY2^rUmv6ZOIYjHX`F<=w_ZhHJ$BOEnt1V|N2`vYoyE%$U?mz# zHyUWyM2IR1nW`ys7DK~m*y4J(<#9W+18XqfB$o15l$)`8ZEbBwX_Z0+uGYP$l3lk5 z2u#aKqf9l|!)@4wZ|bqKtU6-j_ICvZpFegX?MJVpA{}KALCeD`HvGp;Npm-ELeV^1 zw?K(dDIj%+i)*|vGB+~Psyp!P(AL8`9)x?u-Xa6LfaZARBx;mfCB1GkPZ<+*H|LhqD1O*qejQ@X+e>ASomStj|xde z*?HadO&6X%j*1coia8WQ-gANrt9`og(rL&Sb-Dg1zvXkwXD3wIim=}ObbqN*LW)XC z^+eo)6Qt(wZYxrvTVulN&i09|`Bf&H(LYF!hj$K0omlM1lzj*})q#|_zqcg6tZ`J9 z5YV+sbk>y68}Ifk9vbg!`%ix=yhoG^EK|05^eIe8@S25jx<>sfjOMKe=|3TBP+wbD z*LE^a@oDbcu$ z;^M9_nO7q2XM8sZA5o?5(A>$LkyEM{_1+&W&h_!N@b334xLO;!f@c10b`To0!K(>C zvB1NC)S)ID;B8yP8nYso{#i?bG;}1eVFY8w(%990U{<~xksfFM6%Wo6Y%$0S?-YUKVwlzmn%c64t$ zAVhq)uyt6R%cW?q{SB`a9mN`Z)ZIsN7W+m=bgZlvxR;dW;LBUCtLJjy(=}7G4 zu9V4K{|J@ons^}|kI`l&l|Va1Ma5dEOZD51dE8jJ#IMyWxqcJ0RQ=YGZ7xt4^$Gvn z*XlPi0BTzuyBrp_Mk=3By=zhkB|6CG2lC2BmVJgE8PnOc{4glPo{zlGmT&e7o^(-( zx2%|09*P&V^l-~8hWP3!NZq@0C&fV-t(FfTKE!Y5@v!~y&*o!A;lfJ#&{W@j{Hq>* z176!e<+r&qSZlIL!pgTU)Gg$hn0(3rcvcT}Uc6r1`q2*ekH@!)c68IGXIW*k?Ug6A zod#I9lHzHQ-|U>oh2x{l^+p4Z%X31?O1xvIp`Q-;zJAsQn8E+5PAjRO#6SsD^EUwQ zh9)LmSBjnj>|P+>ymR|9xq3u4DLbFsZ?A|7zO1Q4KD9;xRp@Mt&!cj>gX( zYMCslo}YIqsrDUy7Vbu?B1a4kdXEs^+&ZnT9R^v9^h^(Gmd!|$S%Qi75*;u8sfUX) zQjbleyNk$|1mLi0&MF-UD=vP%Pg?q<3NI$@#Lon?+oSP7NexIUf<)EXGZ#z}1%R}9 z?6rIBa_1{uf8%}gD2&fVw)SsagjkyX5nyCw(-fnS-N=avUEN!?Jdr@ZG)OMr(qZIL};KvL=cQ(*Gt0_Mo1TxsaZ4=l#@NyQJ;#(HIlSvZ*&_2`&>Q zDsY8pfK$r-oGa(r?!yuZ4)!*A(=ZpS(2^C3R?*I#p`jdq#V6>l0K74+xdYu7O!Pze za5pbLeJ-|je!ha@O(1Orw(xV;@9C;SbyWZE;?L>u5gcKeoL<6nBo+^J+XpYN$CSEFn zd;rks3-#4*nipruSO`zIf|K^v=xAZpB2WY1LL_&7K91PXVYu>2~d| ze!;yb7m!EVYEgb@Tj=?nyaBjgQOFr}ohQp@8g^*}1G4gX=UV~DsiBMc!-dS{oF@Ig z@iH`M+opK+RM3jSV9F>tZ!>D;>z_(a_&3Q9qd9}_a2u@e({r(25OX3hNN(?x0l%t3 zulcO%(v`vI<^m_6n2^u(VP3=M-Ka%MB3~QxJwZv?>-OGI9%`GFOI(=RGrd&PXy3RzrfzDTc6eF zsvnXctzTt{gzDH>=hm*S=U!VdC&Pa>kjYU_WUXG18|XPAqj$wcx~ONl0AjBKC$QL= ze+1fTo0a7Y%qjqeNfh+snWnnhHIX}LcsfP0=KBM|h0>Hy5J(^%HuCd}wg~dmqXLzs z5seHVKy9g+)b<;IN@8P8SHjSr!P#-d6{lQ>Px=eOEs)y>LHev{Ry2p>M9b66gCuo@7G=8=Kwt!kyn)U&vlwX1 zto(dD;J+0z3yD=5E_r%-(sNz0GA6l~Jm1x4y?PZ2GX255`ti{VH!+wo8{lnL7&L&_ zzKBpxO-`1e`d~C8jxO}f=sSaQy$(Y3=}L%d29iqMZ2AIbn-S3{dI7OAuZ**=cIR2z z^Zkhl!o3D9JB+HRXzuU38|Wh$DXH*NPoz}}Epbc3h|#-n5vaLs7hItIy;$sI)+>*} z9Q_>traItrumRWyl7K#w_nKNPOhxQoOMICdy*8@WHIR9NHE)fNsI079K`KZ)->{vi z!L7Ul3?c7DfCLtB3d0K*w(!%oq*>hhH0*L{6-H?5mMz-8OWU})Kw(@@-x8#yZDV6| zSV@U7p$Mql(840#YfkF~yw`_gQWJV5&qV(PGK^$fME&ts+1U?4i%u7XMWfZ{Rxc{( zJ>YA{S}2)!8+rr`6>xe%@frKpeEZ7{tXTCh47?NDwr<_(29Ach2kLV#7QZ}D2lels zJ)cY_ki+GT$|g^5-Eeev4hQZv4IOsX5RqGXc!7#YYz$`T2=I4z8N`!XMW09-M6dz^cdJ4gvDZz;I}KDSZ{NOs)BFKBIlLUOzHS|cy0t&Pe^gN;_x5hD z@TNrs2lrQa*XLPQT!rwl=i^_=EfS-ptN%P0+}Y7lQ|UXKl9skxox4P=Hv`ad@*~py}e!wb_ud`5{t!R*Gk8PnyefK<5Q2iehKWxjT_|yIjm@P ztl!}yM>0euw`#!g+9Nc@dtYDC!edXLK8=he_PXfiE@N)o$ijn5<@TaAce3~5+v{fw z3k%ak|HPaJ9WICsH#7)Ja0CFC4AFG5+TypnaIbuO%+`#~CUIHGmQsIUf(Dpu+9V>M zhO&x_i%W-}PY%Y|nbeDOzHDKT0|NsX738xj0+=+E!xI8V6p}*f#RFBT+t?XpWy)Ro zh~7N!HS|a~Lv$6)={saJ>2=Hx7j~_btzN8kv}t^P_P?Bdk+G?%`GGY-$4|l0gqo8y zG+<%2MYPIU0Sj8VD{^&&+CmQ^$08|6K1lSR4gtn9IJgIx{AmI=5o|$vD%&wP|9E{` z4s-XB1{tbIsXp3EnnE`}MYdni|pAemfwzieh@rD}`nY}4Y82O1&}nk|`-9YtGql}D_ER&)mu{W>f3?LtitubzVh+vU`PAY{v`#D)Rku`OjKt&;YpUp zz=^)hOx$jiaVF&Z#|Pp+lq;BhRm_UKq9R2BIjg>xnEaHKoxn5@y_PsIXaJcPdiR{em zELzKXyVY(r^|--&&xiZ#p}>KO1s2Lv-Im^>UrJX6-FB=E-C7W^G{eSc z8YBsg#w+Xs6t5ZanTT(gaqY%@i@bFatY%}@A0P1Hb;YrVrKP1e5k_wFh#gIKXMf|- z2>$d$vYxdx%a@hBb7u;iSba8zSTm~5D9g&u*0XJCyUJOc2t)p|Ufr=fkNTq&<|hyA ztPpLSRf6|Lg3&4kX9DiasPl~*!ArBFU@2(#uNpk(?L`|9ZEkK>696CsLvi>BxLmF{ zsUcjGV@*(>u4Yc>*sjH_y$^P@8YRL~bLn7c8ND+fha^P|#zV_+-8+e=NlD_+=9&(E zcnTh;qnFIh{XHYnl(0`Ofk`PIvO14NyjzwpXKA#Vj5&O3s6mWb>93QvaOY`aapR{9 z2+C4mYu{S^-fk6ti_!1RSZ1clz?7Vfr)NXgsI3j@Z^6MfAzd$OxetR$*dTiB@Lq$w zFwm|uL{aj)6`Qgp==lEe@UQpp-v>6bd$YJVr7j#>obcJ$)bd%8aBrCd{4Vc0P^qrG za5$U{N7X~vzWj`>wbzcE%i7`RjRe>b8TzGW%6OQy9GZ-G$n6Ab|fwi~0CmG9_U#=`6&L>Sbs z|B=PdUjXso^W!}ab3R^J$z{rz|IDdVr!E*9*WOvP{_6WXYeGVu2b^0|ba*^PiwVV0 zu>|&C{0qfULBn+a8mZyo;R0AfsF>i*W1z5sPiJ&P$5|zw`s$UmfUHGNcER7R7=c@rTcZGYFckV4@;{$)(6|VtP zqMs_me{|SAiD^-iU4zE(b7P~wsw`-Ptp94TrdayJ?z(}hvrKVfys`o)bdNxE8}l|f zy=vPKB!kW58@buplM73zp0v6FW@m-1*~-tqSyKN@VfvTbPVXO0F8gy}VYG`KvK-5k zNQWd38B_#@I((z>ks3?y{;lf;W&5~9U@5MCyuYykQuf8NTec#e9foR&uCpV}A?weh z0Cj$(XK-&XO?32qUavbKAuk`*t2M!5)<(w0o%S1OYWf*D7z{r@fKC(`iwh-DHUC#V z7rcgIFl#Gv3E0hpwyF7fm_)pV?`X3#^b>liv!_b7OuRffa;}{`TLN{k@PX<+xYVg2 zYyaVArgd*@8wZD;7(@Uo;=&XV5U2xuRI&fEUr9c0K0Bm^;PmwkZ}uCH^wQF!)w2n- z-N!}%So8X*!#X(D{IQ^3j6|8tI1|(MlBxyHi&di#QBmfuRf8?^QXH)- z6sad6Rr=00H)(K|pdX9|xp2%|jhv3T{4Va0?P(Jeu_ctC`Dr=jU!bIYFQbx5bioYy zG%AYWW27?P{2huqqtQ!-UCY|Yd&)%i(~t^zN&igzQTa*V?ZU#FbALN;Z2T+>#ORuk z&6}OI3gOa`w45>+!-ze21tjU+v;N8Xt1$@r0}|nNR`KpSp{0d=`t)grC?BSQAG~E> zUOM-cx~<6Esi57$o&e3OYFp*c#2v}~$Q8>x^7Ntsk0T}3*tZ&1b>(dTxy}U=t#V~9QMkEXL?{t7uM}G(1ZiPIdVQFL}07Zk1 z0wvcK*bl0+guKp%FI+^Z!@NwtrQ}1QG9gJbwBojQ&qe@qglLO1~s;!~d+&!P9 z)mmWQeKRpJ5dg>_YN973pVA~P6?vDF%4znYce|#E?#is<27k0^3g)ZhU&GzS2^f21 zUw?a*9WYWz=%?ESb_wW;#YB0DK(h%_*<)Nv}|IO&1-FoO%C3w=UhzxULVMw4J8H3QQB_S^Kn775N2RpwAxz2U>0c#46Ag z-X$`5IhGYis9DONSQ|yEq|$&kS-%{x2EUs$c_#Mocu+*bHJOCqU_gZGqBckBe#3kL zla>UmZGO-*|EO5fbn&@L$-g?#uW=#x)?c`i(cDt!rP!5!ZX;ja#$%qD7_7ob$NgL1h8Z*7AJ(o;`ckm7|uz(b@4Kfjd#MHEphdO!bv% zSsHb%TW`(0T1orCedHx_bT?QEbd1{LfQ5`*VH_X`Hn}27rH48TxI*5pCBE>1d&8l? zO41^o(jM`e3|6hSh%YjL&?XpvZ^K}cxlNZm3|McxOiQq#55$B5Rff|RzgR~E6Ikn& z?vf`paOUE!inND%$9@s$;BD%aBpIK^`#O}U+R&7s&5dD2&>PX?0Jv5dm zQAqM^Fn9iC41G$^d@!4#7GFS{@f44@DP1&{#C8RzhjZ@~CMnjkOBIEwA9jGV z{Xjx5sLse%^dB3~F=R(dbA&b`Wch6L!559AP}^+A+tNcK-ulo7ODFRx$`70razgI{ zya-t?CR%&1k=n3J%P?s8Pv@C=b0Cw?D8RWgx&;;_m=>8}O}Fe$L~J+>C zwr$)vPp=bJl=q$a)9~E6NUz;LEkJ8uLOLsjziggQ06+@Tq}LJ)ZC%l>SEko&-aPNW zG#u5Qn8*u-uRY=~{s+^nN2JFB|3GhB)?JTV9?#r7FC!;MoQU?5egj449Ap}|j~kV= zVLOO+<*dvY5V|{n;s+Dn-4FSb6AHyqTfXw{iqlu4?-C->tk`c0Q{V5%OiM#O%vNw( zWr0=f@?Q>4PGLZI=s@R|!a=Q62~qOE9ngDEbwH||*7L*-I07Yu^;LftC3`TfHp)pe2r2J7~S)ptJ= zaWTH>tylKcq9F{uUnz%@O1S^PX%%Q#RtJGkYHbYc!2s$O>URy4{0i!mx%)GKtu_to zl_xac&gqMM__&1(Mp?fpBUqC@oW}wmBP}7D+3Sv7hI5Cks{^isoIQ0azZPcM%60wk zn8@`U7IABuW*{?RX9!Ngtx4Wq7cyuNSTGNs1fN6=?5Y{aMy@TAOGR$II#th^@;+r>s3qANQ=TechOudwpR0w1#$%(j^CwD%9jw~8* zT2ri|43I#B!9I3+u|Q(h#wt4cS*o^w`SO<%Il#RM=pVQH3~5=}2tWs7#C~|+E(z;p zftqUw%X)rY5wKC-6D?Nw3dMb@w@BuQ>wt)g*HkcYInd634@%Tndmqp!YQ*3sa_Xz~ zqGZFZK~e;UQ?S0&jgkUb6ly93IKOZ~>j3%0fSFG<<|i$IZnEb?x^uF@RG*n}wkdb_ z5vQyOkOu4O!MwO{-@cPTdd(q!tjBr4H?WCD&mt?b@O5fGpkTksB}-%Dt(*6r3D$Wi z0EBYqE8_y-D@v!{G@?Y`a%?ikD!=mPS6k#7@`8&xTbQEF+}0LPkTS}cyLj#?K z1UOd^)A3Kg_(>kmYQzvi#K$kZUWMU330@x-hv9nS%6Fcfh+#SkxEK-sflET|>bB$&H(vEyWm~TO7r`-z6 z8Lng{s6=Khp_Z`s5g00-9}u$2&>7c0Oyn!9Lt{(ReuLBUh?v$DdzHDH{?$5 zgC$0%)Yl_|5P&)MmU}4-KeXT5z$1p%Eoz=h*o}>il!@&82%w(4W9$Hh(CPR<)#{mN z`G|k3Q-`^7Tli{M|IeSBd5>`+HSnWU!6W7)A%X^BE&a{tE*DKV=&irKqUurlLFd1M{r)Ai&-g$$Wn^>C1m@BA|bLfQ1o{U zc91v#Dnv;;@2Yq2OiWD$0VpON+j8+t^uZnL*01Ng(HRWLm=})+^sR1*eF1P0=&^q1 zr0U9XTozG$wH|YRcBWCz+ix1MMi>}4jsULYiy0J#y$!8sTR=Bq3l!Tw*PTxme`Ym9 zI8>a^>B@yVbdxhbAYRulY&G6t70Bi4t85p!H8Ts`8Rl-R$~nwj*}|Rh)n>|R6_oiX zxcv*Jen(;YZ>O`D-JtcmYXEc(IpI&=?@Z9}fK_ zj)#uw^4JVCG1AhZ!1Aer@Lu}Ao!~H}ITmF*y{2={0-A}u3tL(kft4X&N=EC#y^JwR?mnvq=6zDFvnV{cc7si5MS|RE6i+?v{3`K(fm(~GXLdGvZ zj&6JRk2+!3Yfju|*MYHNs0p`o02uB)`)cZo7nttS!GwJN3R4Vy^5o^FXn7WePK#x_W(r#rkshyxY(M@>DB zMX#^G;s8m>yA9S9W(MK9`uaqWq|qyX3}u#Hkn1F}5m$<+Q4pmLfFN2LtvoxpvgN^{ zA@T*7mqY|73^Ci9ASUJ>t!(~w=zOJ(g|uh`q-zLX4U{>MBwoEb|Mbuwioj3%IL|4e zCV#_Wh&4A#xKf?DYf%l!RS-xDxWik$E217^?^m!ljHKq@Yu14D3%ElPMz(<;_5j_q z042e`J}CaMbMN8oJTx>IjL20`VB@(T{WSmo!5cBHQuj?h=H0kCQ9>x+ji3>ZxLIoOnr3PwK8^ElAu@jmq0iIw3S z8IH1^=qR>r6v$4;9NdROJSbvC`$9u6e+;<uHWwj)myI^YofxthxT^^BiUhrUYbE=(G;KETP|sZ*OG`^_?WvxET?2lTxoj#m z2@k~;ikrU5^%VI^hJqMB?Bw_-*k^S6WTX3N8>>piNV^7@CjXO_c#I|)I@-xOnNjAn z`6kKubNoNE5r}y_>gOI8u(33M>lMoYm^m8cL6Wf6-;$1CJTZ_n3+6FhG>@&0&^`lU zMGQl77YP17=SB%jBXKq`Q2h&LfLiL}hJbbtLzJl6NM2S}F!+SlLt{En^5j&>V}aV; zt=9Jxps}C>1yk2sjrkK9{xJj!64j z5X6%o8u`H%WPtcB)j0O@3gW)_T0(N&?FKBm8zA;;+S2v>*}c$cT`as|h>Yt9LV@6^ zF*XyoaJEwrJWR^hHQ|3&kAtGGeVA zXlJXXPx|J=r7bqusc#0#yO%I;P+a7i%}>{tZw9QvyN`*0G6<1)=yudUeR}r;rPHTU zM!Z?cgH>Sk*ot}+Rk&0h&v?KA#UQ+O<&6I*Ehvx&2ECKEMhdJG0WHneH7cMn!hZ+v zJoPq*kwD5)wF)3HDJURi?xS8eD(2eC{)1UyJ4RA-W6e)?+p&AV2`LZf1Oc#dxqyt8z>+H%h3_8gLd<*;XFTam;v+5c`z;6Nu5nmP z;9vI^|1tR_C#&Pjm)C@YIUq=*E2VZF{@SG;2>JpXhR+L06ySKvSIhc42F}Brh7Bnh zup+lkVoYyV$J*2xxgz_Sl>cs8@AVz<@MH!e|MP% zEw$otPdiyCaT1!=Jh%_iX{G5J)S4Iq_6BwijXGj3=E|WjzHUp($wSR3QuVNni57_j z*dUXWhnsztAF#&}Hp0Ox==O)xQWz;^u$QEQzot?6lklpja9S?px64$e3IPU|5x72mm!D)q=y&;?37t@babrf^Fq=Y zv`){CnpYs{b)+d~^U@M0cuEc_+67=EcVt#2BU#_A^>2{=es=c{RSJo5N^*#*UR>@b z4A{^+Al^}3t7?h4X8Zz37?h2eo*XDuOe{yt=iBX?r2gIAmK9KZ!RbCrVbcNw!SB33 z0!%Z=)bN1zoMUzSXJz7DGhMnWZ3wwd_2jAwNpZ!1I+`x6s`oD{F5X*x#R5DB$m)|B zR|NuD7_@qIsDD7pXNvFy=;xucEh=&{wMS?NPQHYs35mwLXXjX`Odg`rYRrR?v|8>t zxuSCv$bChEo;G{AV&AYidD$cdXNd*3Byc4*y@mE}U1oN$CB=&5%zqKE1zNHpqpAIm zbj7~fB!QeYckwWN`olv;Z>6t0sLXXBzik>V2Q?aQ8;y_lY$q($v`xFr<8R?P?+AAF6rV*l?Z06k{r>sz}_)CBY+$g(X5=tB){sLdb{ zcz(?U500rhajp)_=m)y`G4%4@E?;1&Czz>upNC`$(^Q&5il-_SJwZzR+gQ97@gtuG zXx6U9e!%ZcyER4wC#247BA7x%28^nZLsq_W=jm!xId)HUla|auAS=NF3kMiGZY&;l zmfHC@A*cY`gKY(_Z^RtE&;if&0ZDOircnc#UPHnDWU$hh6ub0SqOGOySe8`S*7~m# zCv6CT2^hD~GjvEe0}ZxE7ts>vy|Fqfr2I|fgAqQNKX!czT?Lud5Z*7Mga5uZWO*w9 z6Sz)1d)Ys3+bx5~DNkkcE8mzV;e)TqM9kINajJd^@d$0IE90!2tsid<^NU4^KwD-n zQmYe_U{1bCTwjkl+JT<_gS1zEe!P0QDpb|@U#S!Y%n>+*%lbX2)`F5{rZ$R#WIj&S z_f|eWVNUUs!dx2k!1wvY6H5aPNP(IV7bT?rLKXI0JKxx1e{RHx*8?5Bj+xajzZ@h%v$p6Zkb+8|o$448Sh0?Hh z{beqs{*`?RKet?K@{Wz0F2JL18Yh+>i_st*Pt6z9hYLq#p-9cn2^)TB&AOVu48|XI znNizset?2E&8rLs@8TU>1cGdAVF_j)0HA(A zOn!$E^AZ8t>3&GQ!m!yJ`=eVGbF%^3lgQP*CAh4YFF!4^fYeJZC~ymWUk`?ct5CsU z7e`*ptV>nZ4w5-Mh9?vjwb-27vcvkvtLK09Zf|epfS;`f7$Q*WlAjdbxB^?Wv9T|d zNay!>zF#9a=V$wD^`}JbJfgKL!QAiCdvJap9chje1Z(nn){vw|hysyCI{N(|wD|=3 zg_bv1KFx^I<%w))kLD*j*|BzQX{UHty#UQ{jbBNfyZergoI3~4^^vSsgOF+$yF?gs z;qYc8^I;?g6NVRMWMn8wAVtJ`oVQ)sc>5o@jpbCP+L zIV>Ac;WO0=ac-|?y(MIHhI?PZ(*xlZxgPqeA?Uk5xRkuTapT6xLaM$2)X+9D>7hbM z%W0x73`CkHbgK#gxE4O}`@(xI&#%H^^U9YutcA65s{y!*ax4z4RXkW;CH)uvY~#vS zGStFOxlti7GxTa9PQ2kyP?!vj-u@&8f5W`|WB^WZZZ0e%Shgv^f>LG?2b!H%E)8~s zO)wS5lHGf*{`Lkc>nmb}8uSo3;%^}Ud&t&obpeco8qhTl6&(}9@Yty)@MWC&*V0UW0pcNgbQdt1urHe_79^qo zJO5mgxFbr0uX4(~8L!i_pIHsS-BnuGt-{(eru#v&c*a?7+G80mmAf>VO})pN%hX6e zBnGPQc^JR@$H?UpdVPDaaaNBfbc( zrMgkRAwd?cv!71dBenli8lY=KLUp-(d@(#FOA$Sx>Y2?~2Px50CH2QTLjQ_5Iv9*o zCV~RIdA@Oe$1aQ_nuqz2#fKf!3RzZL9!mumN4tSWLRM1T($K5a zh-1=*gu4fLg|i@=Af%+tMwp_e-U+OxKrOFKLtwa&!d`fC!Lh=Pu%{h_!Bb#rjkH0| zb1+5sgZeg0nY8Bmt;CitIJ{BUe;m>AV8dDXO4p4LiOC{Eh3%WPk;FCw6}{{faPuH? zC2c*6EL+em=Hrpd1Ovn~*Q<3Lhr_{c_qiUq+$HEvEmCl>0Q4$FV~o!OowoEKaPa|p z#9eSO%D6y~okUeN70N(LWx$!0Wxob<-UbDqClY3$(PIwvfQJAnUiCAR-K=VE5!C_$_ysOjQgoTAEfP|p81%wSk<5K0s zt8CtJ=VT-qX`BC(E+2@_aQ^(`v5JWh$;4oIzMdy36T+@~xif#^tHp1avP|gvK>QGf z#{!ta1Bi~pssWxS69h;JU8us0aRDVkE1xSrWiI3>>~RGFmmWh5*xM>E2j@{27QFUx z=+l$bw6wH(G$;?n!Q2)S@fFxgc-$RzX303)1X(1|?tup$;LWFv&|s&wzDHBqEV8JI zjpk>6c+Q9gJnYn0T0l}~4EWQIyS`kD5s6*5Zc7?mkEQ`ie6MGT8U#o#Q0}#r!iBeQ zzY3YX|NrCe&BJP3+xPKh+k1PrsU0FTm@;cHHEGy}GB*;U5F$f6&Ev|p5mGiGN+Hci zqBI*CWGJa5X+@)E&Esl)&+A!hk^S!X^M1bH-+#Y#9F8Njp7pHfzVGY4uJb&v^Bx;Q z8d|t4_&MCyXAgZ=95!Mf;S;Qx>0rvnQ^FE-_f^z-NxAd zHdZ$QGDOx-LP3YQf!#07%YY>0_|^~~7$bHYpAWov(p~!>G)JmJ0J0O*ysKRw`c#Z5 zDU|9ZVKOpHNh)&?_>@2ylg<^A;NtSJnoSofjw=ktUdf30lbv35G!&0+PXyvITDtYg zFu8iW{Slxf(vsv|Q3hC^@lbkD#h(f6WumAkVeVy6#jJPn2fa1I_r`5{3x4l% z*x8$@)t1mK10V@-ICh?rWMpPLTPm{P!Em<=F;vQXNsEUVL1yk!=%HM`emUt5L zNU@io_oCMJleRt{9xaaP{lVoMbzFaebSXVsPXGPt;X!G`UcoFSkA3LE|wwRCtCF(1Z z|8f9+JZ@i(Hj!(M-sa8Y$8G)nH=m0a$pEJ3D!OnEe*n|*E;KW6f%$q`d)-PXzQrI7 zuWN19Ts#6~22oGd)w9rXfWWYVkg#Rh5hy279cf`WHfERl{o#u|SI1Rd8t~PAV0E^2 z5ln(B#O_oZH~mV|wB^gO(g$kwHN7T6UJVMYt*E&b-v&(9+WTj6jwG}^pF<+_rkL_& zrYhYP=gUbM7bpqK$0=0v5t`;4<~rq4b<+#e{=*wnOf3@|V$*(7i0vEXzRVP#LU1LZ zg*9cvOdn1$afw#ptM!2FsXRCcZ5s?3&NE~r?burd6WP_xi&j0hp0fqz@cm-6@F*KT z=k>iV1dJw5AT`p{TT=E|7FN|)?X}$cA93`sT27Lv(cdo>BnjGEihjF2wjD&?40@C3 zUk@%Af$%H<5d&bm=CvLAp>O&Az_R&rM8u|FbS->GlAxWVo&ko-J>g!H7uxy45nT#n zNr#EFzU7)13_?bscJ?40HgR%pStDH!c76Fo^P(%;KE+x$oxlJRMWmrdrpG7!<)>K< zfGVsH_Y)tq_dysa?2$jsg1GMQ0QiBn&k71Vmv251yC7R>DuZMrwQl^%OUeHV?w^tC zSGIz_uPb=vL^tpZ6mfR|?zz0B{waB4d36>OP`4g~ARX zR;9XIF+>h-mRr86MJfQ@1}EcuYeeD~vZomE%!>~H#^CHBs#2v*P9Z_{Z~k;ME|SmPM$ND%TsMo`h8_liYo!psh)Z3 zd(q2?RWpR&v|_Rkx)FQQ-mUZe#0jDnKM_fI&ou+k`j{hey4xAKR)pLt#KlnZviF55fV z*>B1e*ha{Zu>(+CJ0|%N_BuyEhe|{%p=g&62-TMhW2`^JFo2eR(C`pUh{ zW$AqXT8FC31<^J&4B5di3h2|P-91g8 zYmMscmk;24S^Zw&-^8*^SVLKRte5Hwr$p@#p!ZLDF z^IM!O9vbMj#sBd0Swih9-s1k_>NRW3Afk;%0OTGdI&U)R`-uaRkqL$mz=E=*);$0s z-tR+>jDUjIr0Wz(NC^VGwW%3>gX4p=&a6kqf*z*1{DCp36Dp8_nF_)3_K6{8PmUazz%dLOKi zs}Z5q^OZOydHnr-1}?qDYj&dViRsCl23i^tlVT>1M?=#paBVjU9K`GFYl8ZXW0S1U z*Bl!cVFnW=t5=KAtOXCT$+otsHTiF$bbx2^NjMCX?zrcIfTKME7XvOr5P*+S%v3;A z(la<9z`|+H90Rg&5*SB}+I!%@%Xb{O(7ixGICwL>Ikwv5%AgbO!i98=zNZ@G3-moD z2XQbkqTXWa^!!T6A?3DP8{o2f;i3k>2>0%n!$Cn{w)+b*X*uY}Cy0uY?(k01VH2jL z*r2QX>r}0oD+g`=UEpgq%@DLK;n_MaJMF%W3ZG-72lCB6ORdMdN0B71k0uM+#aXPj zBgxL4H^GkUUl=ebv;_gAYs!RHpDe-{5zhVl3qfMNZW7#L@@uTJRGzCV81P`Uk39Pg zB_f$;?1YsK+RI=kM<5Nud(9Cr!UY8d(Xc4yddnr-9HR14UGT357F2^LMpW&HWeO-{1z#!z0p_! zb+7G&ehj&=T@wZXaj3O2I`o3H2&H?bP`K6Fj23KdEeEoi{)xpMEcv}ci#8DF-*^VM z_Tj8u3!U10mAGv&LU|rN>roNN_{mx3Z4`T!df%tfH>gQ2uN@MhdlUU zkK0pA()NFM2n67FPG$`Q_;qp;r-A&=^ZF4)K6)rqgn%D$nPe*{xap;6@A1Nxh+zd; zJ$7EtYJP!hX=CebkAW)&WeR7Pk=>*PSr#(BStO0%^ZF~d zgOVIyOlFUW&h8TWnBj^g_vOKFqjn;c3Mn57C_c>o*&$(JRBI@UJoJ-#5Mw$?2#{5BlQkt>c~c4Fi@7|XTI z5q#1QkRL#5^}YeRysx1RxZA%QaKQM&b$l^w&=_&YS!^8{@PQo#!`KzeNxe#7471z3 z6F7#DR2wqRA_=kJ${z%zj5J{PpkX)2=xZY31dRRYj)6qo|50-xyuNxBopmYnyGtl_)Jl##~n{#96-e>WD?e8dE_ z&X$#|N1FE7_Xvq@PAuh4pRS`mnZckT`M4`^k30(#UaH7n+tmbH3)*cpM@|3+J3w}f zhs=*_z^okF3mQ;Q@8xG~BLqVv_y3;A)z+~m8icqk%x5=trOF6b{y5%7;xq zuvBO;j}5ElKmKSpSKGB$RTk~Z2_VnI&-Mb_%0Cfv%43TUoz8J>JOx*`L*&N8k8HP% z6`SN%?u)~@13cq=@WH1Au?MK)3d)lZ*Ai^kz%TXm8-zj$uwDnZqwEmIkN@I+`n6Hn zXBQEbnm>HctWSd2<+QOScMDv5BG8=tHWMt*M6wY4&eIX8AK^uavRtwtV{Bk4Ad=PM zI1?pR_3o@9Js2=fV67BLqE_y0{1D$9$L@`bcH`zC=NnBPHv*8FfH%C7b&+~JJ$@lQ z#dY5dpx5pn?5Igr@Dg|62YCAHw=*&(y@12(*t$ZaOn5@P@e-oF%zK7UgM;8f#+K4s zF23TNQg40Cz{PV`muxnnGwkmUwDd9$+%_7EiJw(tZ)T=@UstAv*Z(AS<49}t7A>&K zTL-l9CumR=OAmZzvNn)83^6!RL{(c?N4Yxr95Xu^iB{n>{`sHfZe%iCFp@QVJtmtK zCOyebJsd(ORo;6cOR@Rp6TU_7Sxv+Sp9>dkH_h9PXDW^*!4GORH=irv&)!3v(b|%W z7D7FIc7h;3emyVl!@t>|GF;lz5JhgRDQj52){%ouZzfe|hG&^CTGL#Y}z0qh4; zra-+>=3Ls#_kR`W(*E3dYm+GCh^4$PxS(VE&8ATI-?ePa;6hhw@&6Lbz8Y@`_Z5m| zW5mQ(Kt*%n`y6fI!u-rerc^Lv%3{)F2_n%~e^bONRDKxThmeIZa|Tk@As2jnGPKnX z`_uliNbr`Wz{QbDibka*sDTl@J&A@WVF%x=_46@M03?eDV*c6b8h-%s8{h9c0|kix zmtLVH7(;l2E$Sg&d2dsy22Ls|O~-yA>}bM=v8EY9LP%#xP!&PIM;~0-$CPJC5=qW% zGw}w+AZp72AKw2jhL;a{{mzV5Z7=G8NgAb=>4U^w1bN;MNY}i^h5Sv$ViS|U!4z+t;)gH6U;R?!tNRZEt7B>+ zftCFz%BI5&LNjp7opI|Rm|S*z_&WM1Gq6eJ{?>ca9IIdbT~10x4#*^j#`wVWlQZ1- z3RKeZDwCNuUTsUkZvch#Bqb>cYyMYC(jvsAvKbniMUf+Y@AvFvR$4HF*Ln0GRWL`E zy-DqR@hTcs{_^DrX)j>cb(s>Cfl||H`B<}PboY26$206}3!QW0`eV7kLGE4%u(6bD zgXR%KdVuPlA-m{XFmBLhXhRScCCeUkgP{e747|T{g8uFuWQ6_xy;rIeCq5=NPwx64 z_s0i?_G+?B+ClrIpwvGCTzN^uzcBdl(w_t|R~;Ik|K^N#N{}%Yuot#cIenqbf^(U~&sPp-KN1191e(R>Xk?dYc`$P<_kP3LAB=O@hg@;E0iFhDh^C z{N-==zKBYpefZ{+;v@yw5Iu*V1vb(2^EW`~**zp>Vc|t%nWy$QcW%;Yqe{HTYle0? z2&DUW_3B;ooPbSncT~_mcpgoblZF8Lwwbx|qz6voIVolU?;v6EeS1^%6cWPon)ijS z!)ZbOSD9tjSEUFUE)ryCaHiAYJ@h(C_zoZ&%%unvf`t7X5|c?MBw&C3&6r5I#84Bw zddDevYgnO0*11IEzuSg+Pz4_`tbv}%=lD41z6 zS;AlN?iOl_frX;7lk-Lkiid|dooj#rX8K$}uaBgSr=>+b^wa1~h53$9 z;Jg=LKTJwHmk%(n_MB+HmG3Tv#aLfboPYf`8io*6842Te!}Q=mgB(Vj@xDo@+s~j$ z%uqoTJihn@6d^*Goo@OFP#7eRj&VpJ8H`{u5Hy6}OffyeW~c})banm%>jE5I{wquI_I7Qf=OQb zLYU6|NQ6~!$KIay4y4K5A9CJ!<;_?qYoxfXik6EPZl7KJqb9TO5 zQJwiodggl7ErIBfI0`ZBU8(LRUTCV(h4Yw)Uho?p>C z=Ha@}b!f;fXpUj`h2A&2*;tv_=yjAu#lpqr)1|VzeMj00b>cWq`-z@ko;FO* zkZf7kez3`V!gqS_Y}_`!*jR8*g3y{?;(14Mk+{xQtJ{D0bUJXO3!?k#$4584%D5l2 zYn`hr`N`XXI{GI6nE=@VJk~KI!KyNK=GRjf9u0cmxyP)pqDk)LHn}8UJ0Ji1QP(HC zwRBz@?)$_?XjiZtj_xBA_UZ3lzT9x*#tdRLX$qx3+M*$bQNI>=zdyYD>K~_%AHN?| zaUWh+&1qtV8Oj3>zt%!+yXu>Z=iPF4JK<%$dM$ zO?0R{8JM1u%zpVUuBLENbDi`Z4-wavKklD!dwnRr@}fh1bSC-1DY*8$^qpVn$!Oef zU1KaB{rE#>V(}q1xp7TTv{<6zgx8B6rVO25YRM|6;G3k1l5|Zrp z-171>XHriEa@j_E>Um|UZu7MKTcVW?rnl=2V6 zSM%~Jtqr2$r>(!@*x@d_GOOZl($g~E(V6M_`(SU*q|+GZR2m#pYNH&?fJwbHTb~y^qh&>bwIA?bkNQ;Q}6Yv zChNN*y`=)9lksqv2~$3Z)kXjVEfX|?Fl81$z`h>FOI358A8WK#_k!Jhe=B_b=IiHY z>~?9yiXp`MS`HVf1j7|J#+$Dq@(?i6)o-E2-Q=@Hu+LNu8<5)J=g-PD{G*d2yH$Ni z>z80#z?>8z*Z}FUf~ME8yRry@;I|pzYkbswV;BR_L9|Kw3T~mLnytLVtoM3~YYyMm zkC&3QaG!o9FEE*X#ycjd>Bq*@MHX5=zYrBy{#ij``6h9nk3ZgxUOh>6OpL_RKJle% z#*P}bc#!?2#p|vF%jm4S^j6vBU$fS{S2{7_^SK%OR+ZT%N{lrabwcyUSmk$LC&j$p1;ed*RS9L~(xyHtV_CYt zfx9p~oPx;ptnzSp`JhK({1Y-B$+sPHh^iYwZW`q5^h~>YI~r$7&?n(C{ub7&w!;le(}NC+{o%=n7*;X))yUEESqfed9?*> zZS`v59R8=k7IHYTXXvv{PEI}=)6;VtBAu#yRPI9;INKRZu>1UdXvOlKwxd{?HTjp=0@Y#4Eg2Dp8h6NjfWZ@gmEcB%6#;_tT@~|*Ky7)$_hAlzNVFwn452j~N zFZuqN1BS(iA>S}qKZ>1B|NME=MD%H#glb$Xded5Z!eQ%#C(mS@ha>Ej&$Z8CCmOM# zAs12_(p6v@%GdI*a&7IgrIV?>pBj!p)72|g`sCe6aXXCP(fp~VrbcS+&7_c^ps5Lo ziI7k#*e+p-Z{P!-x*=rS)zWPwH@~vA8&FdA3R1AJa1~?q&JN5G!MNlVT=2eg$7SK7N{laSHVm%T7N z+%w^f{cR5q58KkN^%C5_hL!uS>6f3rNb-uxSi>Z!4iO`kZCnG3bKDo}-7*6MS#UL{ z%i-Sab<*i0%TR$C=!oZD?N_auD%qpBYHHy@hJTu;*c&ftO)f60kmGtQwfJ|3musdc zR$bW-&*a9_SRh))#Yw)Zo%#8ckX_r!tU%pGZ(UnMHg%q9y6L6&msHnjx#gyA))MyV zGxW8OoVxgG50bfT569MQkN$p4GQ95||9xU(^f@0AEL~>Yvka8bHnsT;Yw^qR9$s-T z+qpbk4Bt{UG~3lV?SqkNc|vNUhAqJ}-Di zoIT2UW7by{-2d54RysMg_9SOsG3U#>0@DQbE9=#i=@IoXlT|*0xe56@4Nf(cU0G~y z{X{A2wq702nkLohBh?RBPcJCh3YRw$vKFuW&)%&A|7kODZ9ZsdV*Nk2{Wx3kv zl4n(_!osui)Bc)qKclTvYP3dw2O!6%*3J@niJywzy-AsUWqo%^zSSVs_O)A*6VwjH z@6P(z0Na2S;0l>FVWCd}&z-xw^Uk*BFGiMg9hvLz3rx5hF?3dx(fRO`dFr-17D}i* zsNkl$e7v47dVMD*e^()Uo7B~-bq3mlShIDn>x%31>%&zDiHPaj^ZAeE(7}DCVwe>& zO@5sxkLjHcZ{*FrgBwnIkuB#~x}Y?19wy}ll_`e)GHWiTRm08>r)6v&iz84=)8JR^ zSSK=+g(4c+-PmHW%r_Fdi1cv@2Z@SzqEbTEImJ0<5~x?9d90waNag= zcW=2)h}RIYVBML^ot=Bn7F}7?kXsnHz;+?)+8vgh#=bSFpnw;#WdmG`Lw7*Wy&O^-G+eg4*pMo|^tg1d{ zTp9wdn53k4+K2c|CZ>MfGRog(ByRNaFI=ZG$@c!KdvS$kbrB~Fw2z)@jn8qFMe7t! zW~qAl_F>EOoE6j0uL7AKritCR>g7Izm4mKD@X4Q<;i;iBY<~<>!q!LzlE9mm4j=yT z5dlJ$Y5c$@zt*jpx-2c%^S4}@5~87B+q@rnL=FhBh+elgSvlX@oJWRkP|%Je4_;LATgg35XRgSa?iT7R}Q zb*j1HtCl#=b}oj>#_o=HN(yIk42Q!Z4KRr!z;_1;_-@_fJEN5-H?ev#kI`KXhj``w zxmM_RzD+>nCMv3`YqA4fJ8GCzL*G;bF2n~U=lf}}-FD5t%>1gLMt?5v+S7KfHOTAA zWy}oS9sQrZ`{yn`RPPD2QL!pVqa;?1WjId&hmOBjbO3FJd!N<&TaVQmuoYhMGcLRL zV(hG&EZqXZ^Vm(mWx2P8x{9h|dVYAGnEs@{5Q*4d*il`*`wcS3+o#yHij>XRFQr2+ z!d9C6Q8-RJuIKo9xuth!vmNp+Pg8Ic8@`f_7Qf3~8JS z>0k+>cgFn7YX|Iqw2N+Ib06elBl7Ur5c!_zJ0$qP>nB!FHXqj44QXs_gq@QkXiRPB z_bzGc=-Ah;M2aBTAg2fBXxqo-j6gCZ-0Vjve)yF{tR5Z^297P<)=7iR5(`O9O|4ks z_XbYRNsVfP7iu#Z!PZOkV?$Ot`9Iik`9X;0Z_fy^3&8C;zVIXH!EZmY>5DWUX5Mo1r(^=SV>`bO;GTGkpz~X1cy0 z!P*^D1Ooc>Xq^M6zwSf@H%HQ_(CblG(13-*&&p_IHqIPV1_DC+y6#GdK+c5^SW$uf zZ2>qsz<7wI92!^A8>0_RzniWe7i$z?4j6Bs7mN2r{SPfaK=UCF`DPCq;a zGs8E{&7iPmV_ONcc%+Nu^hVpkFM6%`dj2QHwy5Tu`L1?)DW^)D1F zw~)X3JM1hzTZG0+rhdX=6Rzds$=rnQFHb}10*1agv`Ek2skR25LFX0h8-MxhMJ2O_ zgk$h**L-RsnH;l5Zrd2_CbVt-ut+g3ExuY-_uIC@dJaH{(2#e|ZmPt_#*3zA@GM!4xXR880ud$FP%?4OzR7lAbX6_V)8=SQrPoXP(E&(FUP|+--#S&h9X0kELeM4{Tlz zgr07Sd$L_9ShHBV(ZXLsjYbOaOexhlJ-v84BGU7X=Y+?bm zsWN3sLZkB}TYu5Q-uhJSr+Q7NGCEUca27irUYOFUX7xyQLQu$C_7hywQw)PBz9V)t4%VzENB7rz zoAq1{ofV2ZH++a*h(wqHNAlLi+3ZWB!V|Lka5Gf8G32wNo7)puD$47~!g(RG50y5u zYkDGP-t=*ibaE;?lG?R&uBzE@OIo?oAzp)ZAD}=dZ_X5MgG_F2^kS;lX)l{KX6Vsl zK84hzJoMO7WOwqleMtx;VViPXcqgKJ))a<%>FM=2JMC=0bjSNilzoPkk;@cY3HOLr zI8rZCQNg1@d(``&@LA)GOp}$Ev<237;T0~`*;lEhRz-^CIh$D#V*5%IE%jGA4z5dS zzWR_CA(z&;EfLvK*aS>hx(fu7AV3cMEY)gxt5nQZ+0e5;Xl`Kko!Qw7N_jhO^=8=X zXNEaUY}z79m7MKx_XwPX>vgV`!;yUIf)g5+yx27#eEsPgDJheuOd%6~GwI$w(!z`PHm+3e;m$zGAUS$SlIt6fKI^)XOD0*wSgzr=A*ZyrvQKKOyW0FF%GBd_ zonl5pB0ILw%m|Oz*R85X&;HAA;bwz9ogr92@R)+SHoLZN;_Chh6W*o%Pi2?*`8_#@ zkOu{hE{@~PM}N{?#G0?OUvq_HeTHRYdrCsj;PU)6<1!{UEtzXEzuf*)=9RVU)~Y<5 zJYDj`uLj;rmUvxNoUxH}e{Sp{hdUL`ou=)_y0}drd{q`}ZsZ z3=x7wSFRlX6!ZhDU@fIrVd8Q&2Tz`{ztA6CM->$+p5M1#((4FW+#B1)UBMQQyi_te z=R@iq@7_4|`zm(HZHM!vnpn-M?=br*9I4aM1k7)Mc_;(&q)y&V_gKtupS{-yCe9 z9j5fknqzR7!+lqdvdQq+&S-JEX-bBELd}!DQ{hmu?LKBJ#&pcIE!nb{_8f1k6oD^`*BgD@>iX{q2DVoo%*lKIU_LS!jl; zx2j65$awagV{HdZMOE1k)STD_qmzwe8`>XoM&uxV&h-wGOPFN4i?vvh@HaCB z=MHBED+A-gj7`6{ixC21xQ=pGlab^NS$`FDg53z}|_%&nfP zv-eDCQoeK~uk(~zqjQyWTzb6*FE3wokh?1-D(Gr)xwWNzrG)j%W`1h;b3`X4^u6_1 zgt`i0kX4?^RSsI*dOv+O8SYUUyN{|qEYtLY*A)tIotEh%H!5Nt-A(R#aa*Q(-R%YXz!%~> z@B8>i@v3(d2r%H6+A9?=`727mYJ1BH<BnAciwC}xV#$u;?hi-^OT(Rwu_Dd4w4&&>N6f;eM)%Ni+p^?Wlrpue!>L3g7*qIG z)~L^=xqd1?uw&| zCBGrKv8hqIuXIj7=c4%3hBb=3NpAP5n)4O5zqGJ;4n5PMXBM+a$@Xf`cf}xCYGrk! zj6U8C@uov(QI5%I5p@Sscx_BYTX<$Bju5$`GKw|rI=>Fv?F&BxXzxcHDjhAVV7+I5 zexc5KCaJ<4Q~z>gL0MZ~&fk+Nas@$Wts3O;9Hs7xyo1FAOKjYIHreC!*nK+&CB`(oqylMk{1B5a3Wv zloywpjEXdhV@k+h1{8rnASNJ>gV81s4=j?MKs9T{cf&(J?G#;Q5j z3i@Y-?4>oJ$g~7a9oL5cLmKkZzq?UwPyE*PlYvU#6gawfO9STcUA zSuM1Ip8WLFrzQ`+bW6y968&#DqV5 zn+v1}#}J)(sowQiB*g0{)+&KK@cHWxAGRKbXd{o0gPhQCvFZ!u*}(kuwJ1!-_OA_C zqD-Mc&=sV6eC_nnnEVsw!zXG5f9V0>b>&w0E(l#m^8k)U^w4G+0752oDZnvg!AR9x z%7ilUYYxFQd znBi2xBX~;ndt99Tvs+P7iU10kQf(4AO0|vp)H#SQ#Hp9|6ApKd&m+6f-)8-45uLx3PTJCk<$E$`h5BE_ z9;Ewtr1EmCn%WRS^nAW2hOBiF*?x+?RN{g`aOdg%r#gJV-VwdYv!OVV2{1pfvhu&h zuB_UEhUOq?R}HYoL7*4_dfX#}ynh0&f^=uzgi77;8uI735BH|wEx@i2hwh~IJIx6h zu!C#?^oz3iz?Tk0mGY<3DT3Pe!@JFgIYmX|QTh!K53qc$3x{-T{1YZ{{r?oS zBoK(PKdA0<`^uHjZJ?F>X>M-w%9mH2U3SG3AwBsDxQ#~yLVMu~k{F0Wq&kA?;3=df zz}Q{GTC%RX|8J-~nE@=wvB}tRV$qzN7>!301#JEBWz)}}9KT@87^0p(uKOo8nvnMg zLdyD)!y9l+ZXGewnXr;H%l0oKE3g$r3=lTO-+zYZbu^Sfq-0hr4)~!5J;;<}K`oY~ zEFEbSj~T1e)v4OCCANV@*%8dK46pAe$c=0RjJM3&N?`(p0M3 zMco#snP%{*5Nu3;97=Iq#pkb}pqqOdJsE^O4_soqXO9_5N3Nj()H~yC?y%Lm?vf~+GdM2F%J1UrHKG*Nuxl@sp%By6a@;NAIV9z>k-MXlY zx@5Ts_XEl>`NE0q3+q8%hpBDfiD}95+yRG7l}9{YwvzPbX76Ay6)q9n~^6(_|1`L=>}7kYW&_P-GEmYxU+6qBp9*{1bK;y z=D0M)hWX8x;k1UY3F=-}y=fI{AH@s*Bo1Jrr&Rhs+6F; zOpRA+^J3K!WJwQzm%G(6OrVIDgtN`n*uR6pD1A=Fl$l-921*eCc!XY$9$rHz->|DV zsE$xU9m${@+=^7U#R1*@A_kfC8{0kSj2Q}t)w=JZwWrrX?>nc(q!W|s%e@_%6vC%^ zKfHZ4)uTUUTxr|Gfx-fn`E(Vt*9h!HZT|cnDMZEOhV3BhtxC=BxIHxsd8IVptNT&C zZ%g*FphIs4g->lV;>snBsgrh?_O^r@lPuNx(bRR88&6d+#A_(%>-X{2=C`&Li@thw zTMt|)zWlW(LdtCHD?`n-Rc5Pce1=jj$}k&dehy)BWALr7tEmU2ZBFn`qy#T2d5ME)J#E zx#X|WTc0ODQbF{8I(BLJf;b^7{*}Lzu6a`j6 z4WALuXb|GyJsaY+QE$&nSIb-drE@xbEnWAXq5VTt6nq)DW!clPy3w+m6K`HV}!|Tsb$)_DCSVHpQw(8nmwj+s`|nxZ zH;+!F_c}YPb#(L$V(FGJnGR+yhiW-bYs{JOsh;~xh!w({UH_RC%|~`=&xv0GZ;2p^ zpUCH!Bv_YEw(fYJQgTd&wY>@KS`=JyF0NRfMt-wGX`N8`m2rwZM5zQrHiKHU?A#jJ z(@57aY%H;xNDe{SEZc*?PI2^a^e4m$Lxy1*;a8f^SZzES4&=>hjjCmaCcGmj)szB&2c%Hkr z+}-{gMDzQi?G9=qQ=QGvVua6+u#cBli^p=g;pZ_7n!6(f83K(-vo?Rrq@TaZJ*I|j znyY1A-2;ciRU}13h*%jSKyt8Q>cEI1DkD$F9&#p9*daSW)j>IlmcWaGudt)cpH2hR zVLRy%^vM88-!{W{h(iqHBFy9yI^OS}3RS^!wCFv^94vCn^DAh94ROoiJ1>W7F5-(- zk#8m@I@7K?a61f(kC(yFAAgA&fzB^R+V@p&@VXQliXT|=S~%{ax(o>j&NT(kOnMk5 zg%qL{cq^vI$__Im`U|j43zYepM2rje_s%Pg(^Jn0&<|r>mL*YID1|S z7J751Ot~m~hH??|mOxky5vVC&pA~+S1c637)KHW?g z3d0+r<>6hGgGE;C(6+2qntG?Ri0yqmh=ao<*M$a1So$rQ6>?u{wCxaSAEb^f-AV!; z;g`}eCGX^n*z`<|l$irqFBl0DolL^jV4TmR)uxd;m6C!3U7u2yHyt-O$Si6yarVMq zuArn^cB?zr3F?w-7povgV7bqRcI7yM=-Qd}}IE|9&-zcWk*Pef6h>r;i5UgC|vrh?b8~nP0y)h`2LPP-|Hv)zDbPq6FY+L zPnfV|9`b#hMzN`Tq`%=jyDwzUO$l!Q=<>Uln@+{)_>DS05|U)cwD#05`t?1pEXxXw zoy&4nH)=IqJQ(a{5l7@Oz#P@w4Jk{Qe6iHFamf<{ zO6s^Xc-5^tdz`&bUU}KYIdU6&w5XLP(R5e54tm|0p;|tJ_q%bHSztHkpw&wmw)pug zBiBhn;YhD8k@DVC(;tc7ueH20M>#4|Ltge_hfdkE(OWWICkOk4nE93ev7fgqs*~5t zi=Sh=od2$mwh>Jc(=~r`zP|s3iP0+M_BY3P(M~JdBm8VM{rduQ<~Wx2uuU$4bHz&& zs9c*(rij~@l81dQ-WlW+I$(1Nn^fYhW^$PG`{#ULr;1O6-V=oY?y=Liv$HJ?idF`| z-*62&4-{>BbH_4L$9&sDt}UI30oV!MzoZ)g>UDNGp(1@7Ay{SDyS0+HZx9m@-Pjn| zpitWJCCEy5jWv;fkCbBRq*b`*0W*Z%rMs8TzWv6t@%?C zgYXaLsLz5J@SL+w4e$5j;?)cAU8i2(G#8rhDN|6;mtQo#xJd!dtT#}dK@GP@BW&=s z9+aeYYP)O;rXT_$(!4?&wi(6TOw z8Tng3%kxh-n_coxZM~|^h=+XPZn`+Yt>@&Xn5t^jeKi|b4Z3C_EM-VIhk%I#&=xB} zljN3PXk0RiB|iIn*7E&zw+OrVryY(?=dmc5ii6ICB1sl&wm_Qx|2rl5xz+=V{SaR~ z69lE|_PKz>1S)~fE}4Z$x4`&;%=BZU=;|q^XIy-lV)~1mqu&$co+;r z4u<3;Qz(}^P)a)xYd7qfJh#d!?t0(@@f|7Bg;KfZPa>ugXy35<+ejq*7Y#RHd5o^V zRmON6eLbFZi^N0DMyl$FVx=1H{6>;c-@lGb(PJD*S|0n zr1&K&4CS~HsdL-PZW0sT~?mbE9FsDWfp|o<9{lpo{N2Q&4--Ep|3bD zjbzWZKGPO+9w3UmbuCx5O55C8p3HVRw!!du+Xu-xH_AhdgGits3~GD6@9;RiXcqrW)rVZb?&X+s&x?U-=}>h)wRXDoYXTL#_D%Cs11nYJ>+H=+ zyO?8q7?gH_nK;qdn?dR#V0^>C4Jjn7suTr!TqnZc8?w z?Z*dPy9r=k8(t|kHa5dAr}DT0JU%AJ@N8D(89CH=KF3tr3Uff^^4I_8zv z){1!Ud)4F_@^(CG_^^}))t%K*vD!^hPB-jd#40Z7prTTH-V!;SZ?j%fHGZ31vIJ!k zNGy_SqVx7hj5jE?b;y^VpR#)VHpz)_Unn=zNv??&=vdyJz#i%TyqykRaO-+1tpzax zC7>exAI+G407wyZ@jR5R-!GN^$=xCZ&ze30Nf6hhiMCQsYwwIqlJ?l1AGuP}KDBt; z2T9`qM_H+4vr3a;vpY@tXGDJqsMIqsE(v{io>$jvxoM>`m9sBq%@B%itQBw5cIe^J z$+r0|>5GeHUvKSn%JtXFHxv+Qp6y zxi-&H`ue?uGO8mXVLcdltDZ`>IMvs-FZCAHd_1ny__8ze{>7|WVAlDPh<_JurzNpGQ$wBf8nBdJu)`DdhpZ^ zsNyW5ilbikraj4Tp8W*f7*Q220IJLi*MMHQ^zx}`a&$%C!v#9wwIG>BsvNMOENl(u z60cc@Qd;Kqai0Ea$I+=p-Al8&t;_>PJt&F`_A(NM7UOPVfxcV&m!R-+nV7iwdC{#^ zH@!A0cwJGAtZ?CK$gN@Y04!F=X3$>*O>TAT)+XXUx1{7MpZIQ>9;?C8JKTS#2)M)T z8xpNYie##p_Kn2*dYFd|TZ0X)0PkNY{lp1Ot6*U=mc5~zZPQ4H4+c4pY z7w25-`?y(SlQ=&1QdtqH4!;9=->bU2YH zsdn$GzMj&+Im#NldQhit@J~*jj>Im2Y86)fC%7Czi*qof^W{R---a$r9^tEYZv^Ih30@|upC8E}*NsRSR;ZcDS3(R9jUVephLy74m zt>E|DXq>h}rH{dS8k{hBcAGYP>_kPo3~p!XL~5ifmO>+)uo#4c>8G>BGXma?g{+fl zA6tv8A4n_E;}t`}SW)JWm*!>CY|?S1OIp{O1GQHpJuIJON?Sq4ANH-^z{|b8?K};~kw(L;zmqTs2J%Ds_>FOT4O8=p3^_rXg+r;_4)TCz zAa4OyA?%}j*vXN`sgIdyecjb)G?ZoPLFC@l?RGw=Iw&zw6<`fCBs}U*dQ3 zupEYsRIUF4EtmUHw90UIe`8tcWGDjMZhV!d;E~c{u*l{Wlp>=nJ&+19B8AKIgB7yX zoqvN*${)T;3bwG566ROH1IML1b6Nv%?lfn>0Eq9v=mfahxe{ce$C`9`fJA-sKX!0^W!u3O^_KDu+1T7- zh@O$CPEquqUAlUC8k%e1OpMx@Peb|aLxVY=dFYB`8xvR7Tq#%T%P2(tLxV6d7pBNC z$%9COY5XGWOH_MdlfvI<)~BcCQZ^m#?`uUbAkepI=d;qFqnj{a6(<}LKR->lZWvuo zY^;{HcIMgc=FA*I&cft@a;4)hCtXq&c&Wt-WCdsc6s{fGX9#FpPVmf~6sKsRy7S*! zfzQv-@xrYr;k1q$4wYi#eDUBvo)I(^AJ38tz;Jzh#ji?eOun_$Y}a6@YL=${Kza}T zGP_@-jMLF55&e%10*IU?2@jZVGZ=6#b>-F-rZZ64a&2wxt?+GSoqJFXgy}+oj%y3LK)2jAWNQ|g};Hgs4L2Ye>?oe0vNDUXKEJtf=YbG!W!9-v{ zOd8d9a)uNWmnLCRp|j%PKlTDIg9-xR7YdDKRglZ+GpnE*czrq+b_Uv)BtX{uiY~H& za*+@yTjU`#{@F4RN`|ngKt^VC1X5e8#`s}l^apfoyuyjhhj3&Ve2H8j!8$`F&L}*0 z!?}EZUOWV6=&oS1Ye_GnTO^nz`1z5Cu!{!dO4?VqPc5_^dJK_Hl0(nyg=XheMLC$h z0MB>7?xQp6+6^a~6ZhuRM`x0V;DD{obZ1@mQ2f9jK+SZXvCsfUMi2jB3agmx@ ziL;9q9tg9sehV)%^RS?HLUjd!Ra>4Uol&Pw0&=E=CwWvH6zT;l`QYkv-I-(1A7bX+ zonY(DaM5S^)zKLdU1ScMQ+!qWk6-SSfi%;g?I6kyJw94b3Y}(e=^mes(hezEP~+x zB@X*tnw(c>$65C5CK01$R2IkNX>+#oddH;d;ohwgOR^(BWiOSKNWUB(4G}v7_~5JjibL2OY;E; zx@e0H&Vm>_KG;VwI&PS(TI#!VbTjRKZ(f`*k8B(F=)vf^)!B--U1tMvF>v^leh%H7 z$IGvF`nNdbjF+n3fU7;Ve&)PoO}3(1ZK-WkT-<4^y!BVE;wtZpQ+bN&9r-4vFDweU zTT=kMgrA`d1>ow2!ztyL80JNe>tvqQ%`r)}s7{*g-8vNX46z|NGf;=vjI#5AeY@tG zrYzxeM3w6#>{?dIRO@hUZ1I39ON})2kZ}#$SiY^~@sJfIKC_%(_V${z$yC3z#u-BzOzrW|b#x?rKeyGwdX6revxNE`f{NNW3F?4djlbdDK?nUpZWKd=6 zwWJK@T=!|=U2&;EV`N`B+I_+J7}r=>MwvdaHo0KFB+!V?ma}9#uexQ@a4CoNC;$rq z-qiOd$AI(e{=lOC1;^1%N}pV1s*M$wm=_=A-A#O`O5EhR&g6qFH}jj%?dv{zOr|%n0%aCoU(1wsp#4h_LuyP>K5Bp z`dU3cu{yhucVHbJh*_SQ&I-0^O+l4~1==bjmE!Z%bq}ZXS7Y66)O*VT3I7FHHCrM8>m`v0PSYnf_WnBiYPXsw^QW{;(*zPdGULS%4Sz9C943Io8K>9NVk>je7T3{`9i<(B?gn7d$rEcXD$RgWFPD{m@#DueXT%5(05d z9-kat(Olk>HPkL{+>%n>|M@(C%9}^GTbgDEYp)kvCnlqW?RM6*>uXq2BtnRlXa?)#e zcN#XVxKxr}675u^=FB}8UMVtmW4qj<&JLN~%hQredqN{p!tP<_mT@e(c_vLcT+XcU zsnaXfLKikETb^l+SXf!Z%Icl4N1~Ej zJ3=X{;}E7C4&a@w%nL5P%c?4`hYM!aEi=^@&Ir!sjBN0QwpONln05b0N9@byFbIVI6k$cUi6 zy}vwri466fP>+nHEmKn$vzuRR;>9c?wR}hamA>2=tQ zrqS!z2W^2}R3~-0=N@ip)>Eu;&)4fz4ZN!}I7!zbkMA zaBlqM<6s|6K`kQDB+!)a-JzA_^@V@fQGfMi&gT~)g?beGi0=Mk>pNd&?1$#T-Wx&^kjNET0DAQt>NY({MP5_ zR`-8?8>Z^u;_4-_kHD694uz+ZIi+9#97=H(b32rZ1T7mjQlWZS6 z5FPDWIYLv+VD(2=;e8}xv(~@*n7F@LALb9*joBD^_E3Y!l$V{pKg{oseJebQ!O!P8*!kbjr@8}zmzwYv=R$jBQx}V7T z*4i`5QOHuRB^0HYOH+5vEncW}(GQcSc=bO!)*PU-t#+Kw?sw>O%I%i3Pb)7?39h|l ze+W?TeYdi6jAcoX=Pw*5#Tk>%TaOvC&A;9GN2qWq1VCp1-G zXmH-%hC9TcA)Evbr}0@Ov=%J)T|WAp8-=n-eHQIgH?*hc_V;<1>q)ELcpj^$I{kw^BRL2Q|1$FwPnlfWgNT0qf za(lg`c|POva`(K>DSQ7fDOS#dWP>R@0()!z&|shWc7uq~y5G{x!?@<8Zz@sjzaGH} zg#hQd&tB}&&38lsf;AQY&<`*w>e)G_Xa1@{_M)pst7pmg@MnH~A_)1uat*fy2?;G= zg3-=>$78GPB>@yK8jIN*#0JqU-do_n)1P$#K=4hkyQT$_`5((ZxP4SRfkmI*e;2)9 z4mCz4;b!$l*^vufj_;gCk2Cgu>Xu2L6Pk{Acvx+g(kQ!SWb|$yGDaVNt;c%!((QcH~$%%Weo2^dfYWXf~&dziSRM;&skhJ*&4EdEJQ* zZap<#a4F)eo6&1;a@g(NjuWH{7ho{khfjRhai)|r(*|OmJ5->c8tn8Xc@n>@7ApQ_ z<10oPcLH=5m>_H~9X`g?^T=(}?uLex-kncwp4ZQu5I0>XKmw&(OZW9TLD0@x@QXX( zZeDroRTpfGa+_53N>!O~8tt$B`0`pKmhiaO<4j%Tk;XSsBPr^`$HjoJR+09WYSo3i zSDX1eH~v6k2Z0G!#@d2B20@E_+UI{IGIT1m?fsugE5`0>o3q_jk%qT^I*>SC%SzV!$J#~Q+~5ODQV6xLkMm+^WkC${ojqdG^lsh!1t zXcnLzfV1a|a-8itf#hn`B9hBzL_3R+YIc?AqSD&eH9*<>Fv3D z3U))SEkB)fon4-cGe&3pAop6nz(-zd_!u|Q!K!X;Rhh|shP7ekZ>jjiF_faswLgX8 zboADX+qm6bGXM!!4%&f^JD!!$aWz_ z9ziA@0?!kH;`EWYysk_1tquB9Zq%2H2MiWja<`%{ENBn-;0>SZN0)9p>{dNkB-Cs1 zoxYEpA*-_wmJEK%$cAY$Nw_)4Vj7>L?p*GmnOp zga{E)_TFS35z3bBgd%(I?fkC$96e9Z^ZWi@|NnWtp7AO^=kvKg*L{um^}gQs=auH^ z3is|}>b_P%SHSzp!`F(Nr*FxCTF>-D^3X%r<)9ZxKq^fNwx(HWNlA(5$U(3c%oy69 z?L~K2(l%m@D(KJM{w5h8=_3tPG1nRYa53p>Cy>~&C+11J6DnGAH>917%ipm(9OZRT zavmKa(Cwe!IZ+mQc-r)M{|wiL%UJtzDZ^rs{!G+^v5I=}3Ocpn%S0+P3j(V-Y>{gO^sJQUn-zXq-vVHl=&5d%Lk9VDHJd?L%;=3ET zHU#CxirD0%Da6%H=^iTcTesB5Tzfyl9(uayG0*s{Mo~nH9{b&~KL9CZu|^1JZ!Gq_ zFeDewXt+TQr-5sKnhW2q!4+GtYMp-#9;8JRaZYqb0@aTlZoe_uOB270AGE zDOqV~a|!*7<&vD@QQlkhQJ21DQ;dSjJYtk2HUFe%$ofJA{yN@PwqR`p_RidBpd#TTO!O6C0zT-DRiptb}1BZjNhlhVQ z%isPE0Rbc#I*Eie#NH-40F`8YYmbU5E|*i@wE!m=-qg6bSdB+iB66tv+S|&@zI~|#k~Z$A9h*v)l>4G z;jYs>(*~~6YxUT5Ne-u}^yXI;QT8(LD1$a!wL)Eb3;No>zmM-Ld9yf{3G~!IXJAM$ zA6rAo!rShZ$8+|d!IIzj@}He=qNUzMenD5x^3wVo1sYqiy%c9!IdI^m<$9aX=U(Z$ z7j?;a{n-Wj>F=pIl}~l;pe$}2z)ikcDk(7jeB7TU+IwqX6%bcZ)i^XdN*~&9#gE*) z9}!6sb*DYOc7n`q&BTCV?84gOE4bdImTiq80+=kI+aF79tZ5wumR3R$c+a z3f?zU&LL)^(I0!c&@^I6Z%pTc%V!45(#wP5wTF77*0U;JLhAsaSM~2slRlsdtPk3F z){*gFmDVt#cTlFz5+eu4$dN*Wif@-z$_t2|w2hTDg_ceuv3wJSGg8-xu%u`-?16~R z&7rB1L^4k%M(so^Wxz zh)Kjw1keOcP$f&dkD1KxKiZS zc`Myvh|%K9I1Hl`r7bv+0Gn-d%|8O7q)`2YOy|Mr4>)}S;jxb6#d&4=S)f(`7j*I* zcIf?1A}dAWHw9qf6S?bI0B?V54ej1hBcC_lxLH>T_v*;*qoDqom*-KJ&)@r#rRS)vIMc#({M}gBnqA z=UZFao6h~T zKOni@TyY6r(vO#bmHu}&v!xZZP)WmeZhNu|RvGnf&4%NCo}-`>p`xHmeYU&9UhH$X z`pxGLvOg;E^m)HII~>(4Z&Bk!QlLXaoC^p#hg#urt~H6sNQC7#yDkcyB@0?TYi}kj zE1x_izrH5UYIu+8^_Zakl{N9@sA*i`3f5;yZ6ua=z}DWrk{r8KhLo?M6n7rFN-YLl zGEz6CsYwXQ#DN&q|7t}#(LFG9x`q{Z*va_d%E(96zL>XnJ&7~zke*jwD85hZ#1Y#v zxFDeo$T>y%NJ?OFqTBG6XGTBnei`n$n}Xx1ARSD0)6&yB{pF7EdLlN3CTL<}zWZtb z+k`l2`%g!c$_Sj*KJ#@cjY>I_B?a`=CELio;J|vJ0hTZuG`TP8zC31}`WzYIp+hK$ zA*nKA2gy|onS#P?vK*JlwZ|4+Jf_eGCbGa`p6K7oL14cBX=57y;WF3MrW>zT3EVCH zsp}n5LGTl}+cG>c0fz%P9SeEu^Ab(}+=nz@P3s7BD^>VN`*jcZ)Too*B2HJOHJz_g z_%JbT&toh4fA~qu@rey1jY`>zL(cA~w-fQl~6m_Bu5WELg;k*Su(ztw<<<0R-93KaiQzlYR^S9xTW zJ8@@CMdr;9_AnLKsmF{+-xtVRpfv(t`2r>-hoCxWV@|Bx%BiWp0F zq7!~CVA=wsVnnj#uUD$XQtsCub6kJOtsTy^t#7w0*#dkA2q|b_6^;bRGck3{e&RcgA*gZSCO~7`=_^4+XgXWg2jhxv^`|;Znlz?`-9Cy z6XPbOu5|>pnL+ZsZeSr9|TTu>rtw+9E!0t*}Ud{yk;8g*E)(SM_ z2#SJ;rd@refCJbQ0SJ1?-^7C9n=mT=57l!jCs>REU7Tz0zty`kP|}>fOpdzl4|<0| zaXsw~H2z6kz8B?7Yl8${VAXomSQal2pmphk-r@`e;xW4^<~TL=8!`&s?o{2wN@egr z@B!F4p!YV&fb`s8#uEB!l1Otw_4nc!T*NP2(>9ZNvmzePG%EnA#`IRJ!?A)N64}Fl z_jOkoGM_#rgN?J9c$J){rD>;py!yt~c?$ zj|qu4{;kJ-i*gfRU|n4bvG1azIJG3C7`@zHDU6O{f1C@4I2@)9#<=+*V+?K@$b^A4 zi$6S0U)Ka@TY$Dy@V%v=$e3wdly}XM&6cq3tKFCfSO1%mjTvv_xjl*-57kS8Y226-ky(geqtco|#N6AL5+=QfSz`B00J_U>`R(yTb+<{EZbeBqw<2(Ax`x`tu_#=?|uRAtr5#<6^wh9 z16Bp&<@@t4Uio*pPiJjMiUbF;I{5y2ZNU~ERc+cXcBzy73FGW!1Bms<`pxQ}p3 zNH0(Lav!LK6dwV418V$J#cU4mXXTQlW&XAY(O@z%yYdNRE*jV$H6NKy$q2C8+K7EY z_KA)JfkU*5V361I<3LC~QHuRfnNb=ok*y^cNg6x~F%MJBPS^!t&s{!l=nQk4(10qg zTze(1MpVM^g;(-humPF%Khm!XPoW$;(wZZ*hqa~e$KzOHD15`7x^}Y$4B!wV;*gXZ z{%toAe&gL%+9{Qi4d(rUBv*Pdrqm!ma-P9CNBnt?ycKnf%Lqe4h7CweH&YFmw2qBWD-DWwBaQ8}I zEQDV8Y&<5G$0gYIwiZR0THDxm?)`9Qvpfpn5qixe-C2d*WIC?gb|UAjd-kwqD@Piw z%KOinNWbOjQ$w^ml!Q@BLhSK zoc<-%qAJn3DiZMoQCRh`D^gS|=){_RdFw#30hL?K(~E0U!%j5-*rT*Win#4WTO8Qk zU+-?hQ3Xf3Qc|3{;g-`m_8ToH%mg8xC&=?d`3QQZy#FxBmlhC9IgW1IwgSC~(GZ%U zjfE+~$Bk`@JnLF&;_-TjJ@$V^M&dygAfxr4>0P})!rLmoq*}fL_RO>PY>RfR;O&*% z4+B4Dl|^>2i2|+k`@ORLs1gBr#ySzg-(uHST3W@>ti_JsbDdshmUnDXNqJvf=8cWg zNbF_zj*Mu*Q*xIsBKqlR;G`0<2vR29*9v89y3;zgGZu`5QmMV*KjE=pa{^<*qpOQ> zups;r=_rO{uQpLDv2O?kUH>l5wBnjy@AIB0wJ_C6QV6EGK(a1=E%0}4QU8@81C|-F zCix>ve2{2oPq@0zd}G!H>jrMsp)L8`@zuI!dG~X-sskt9T%7)aa;Zp=OB{NkyM`nU zc3KXCqVI2tDGA#5y2F&NUH{~9ozL#P)bb}a597lRP^AE$^e%)o2!SX-8i&n)S}NID zM+#~{SS5<*g2F2xxWC0>Hm{~(=}_>lGhT5vWfDO{N)qomQmHnlrhq97nIq%}XkHj; zC#lwe{CL#a&=(Rbp{$r6(S8iHb`(TSA_yuISq?%oYCbBg}`$(HE zcKzSm?*DR9`lS;S*k=!GvBmXR5y)fD-R=ypc(VI1@BU=)(rDo7RW&^ooFD=+su&e? zXhH%>VM3u%%%I>Mlco1=e>tgj6&i6J2WE7T#~w_?H?T>C@ikt=^^TJOm_z>-{sPh0 ztY0>7L;2UH+LwZ&Sd`b{oKfV;Bx-ZH;Xk6+Y2vv(#~tA}x1*NY#_HtDdGZ?M*s%3 zgDT;}WUS@-C-b(l)A_?OZ$A{Rx4e)6Wr9keX-;RSy0B~z#(+&o+22JVEE-(BaOm*) zC$~4 z*RP*_X7bqW*ujI=EN&IIPJZUt{l@&Ofh)GbT*K~s$59rKw%O?))Av&Hj0UW0&DGjn z2J^q)&25U_f^k#pZFqkIZx>!aXain8>!J~H!XAU+tjI`ESs!;+ouuvNefav#eNURH zLk}slcEL}QM}?XX)>+5}0ee~V6`Tpn&dSOk!O73Bv3vJ!^rxaPm`K7#7|kcx_;(*X z=&%`aDzwHM0sW2*A5Jihv(Qhpa&`}YgICLLUACVPeTeDXNl7?<5(KsiLeK2RV8m$x z4K?Jbkr7}povEj^6U6-vUAQWyM0T6vd@m6~RQ{vqGDgJP{NCmC6;m3L_N0XrvnI;ui*W%WcblC1gNlnXoU0hHbN`<=Pzc1uvs48hVs_HW38gFIqAL@U0k7i_FWptHP zY$DubGW~l>jHozu#}XQhg-XM_j@##}z+6;PnRHabRaq;!($mDhz*roCpG2RATe3yh zd`_~msU9k#{`!nzXl{!16Ainb5W_r1w1~Izun?b@j(qjA0;#8;#Q%aHFu^+uhXa-n z(-`SdJ#Htc4Fk>IXE~}fLeHS|7+#}mpo2{7t3?Bk6;pbWEGsL^t^;KXcK4I@h z!>{T&367S>Iq0M#$LVtFb^JE6sF7B!NvnLQm>N8tXKU4S#yt+uD_L1(H1%)6G<;ZU zMgRZ?vpwBew|eFLRkh(!S=YOLHMpuFyZzGh5w+)Jl`V_r)kN{#MFj!j;niNOP zPZnRcVDRw}1&4Fh^Iksj8kh#h8w2N5v4XlSPERjb9AnP#fc0e8`=ahCvBU>9Em`SJ z@834M-;}VidYEKK&#L>s)dXj5>rBZTphJsNa;Rq03=A|>Go*T6t3L2iv5gy+JIW(> z=sDK6vEMn?V;8Tcxp|VGzrQLR&)Fbs@Z}!qYBgV8-Juh9e!XYU4Bx$5xsU5E+qfn}w1L^Ul{~%e(CZAnN;+TH?LgIx3+E`_D@H4Z1s*mE_hrbDwR$$9YjtCl z+jfL<)CT6bBLdJS9U9sdO}v4IhpHw)?Qui{BP|FvL(5?Vg%K+JtVN!s}aE#ZqIb&Y7@0r2IiyUS1COu*+*M!3q8yj z91pP_Dc0Hd;M{+ zdNDg`l5i$*=)Do{FZi81^de_e)dY~wHW^1&Eg&H5PIQP7J2cb+Y{qz0sn>JCwlz|( zA0!zuMy{8FWU-oVh7A7m>(v>ae@Uo%gp3PpM?qwM>$pQmWVL9hAn(Ydp7JEE${~j`R`!=E3M1cl@f@2ryw)oSh$$JVsPh z)Wu`ICnO8(C)L2S96r3{v<(xX|Fa39s2I%1r;LOs{qV1B1c|^!3kY?6@^oZOKTpw4 zw-I9$Cq^JkUDri*5=SUL86OcLny7AU`m>Z-1_tRcYlYoPc)MKuILQV#UvYCOT(at> zhB)E)=$8Ve<)?uK4-Cj@pXXK&*}dGSZX9d5qv0o{=GbHX2A~$w3jR_q2qJ*yb5eGp z;qJa;Bd57prM#j-KpUp`IK35!3xSDEg8(6rbDhme7h(jbu4v80>nArZiv`Vw6;okK z?{(gZ<<|xyFfqJaynNo!FuBo#u{iZP1&e9bYgW0S?HD(vebpHpu`h^^ULx5#UG|L} zZy1moq?)gkAn;3qUaVJ}JHOtqF}|QFcf05=)XFX~X9YKGXmF`?MhL==nn4sL{kRPk z26NGabXyT+={5RF*S(Wwd<-0V0@>^Z)n!x?PB_;2>v09%KKq`vp%b1K^mb(}aqBrx z_tD0~iStwxWh&qlN*IWFggD8UH%x%(ssyu-4v84hyEb+ltisXkzVP)KPEKr2G*)^` z1E$hPIv_w!9ek*!AS}0=x3}&mqSa?fBZpL(^JT>~Po?RPvG4ohXC-L6QeOQW z8AF>2eMB=iQzaAXq&|;m#++(47g< zfWW{3(q1Ub;HVxxft~=9rms5o%65)1!@H0@!17*xD8&h*qg0?(rPYTM2k3;gWt zYzt20p&CZC@(Sr~{-7CMx;^YMo>A3S&8E$&N^g}PMWI*mb;vH+tBwgJr6Aal{Nzpu z96P(mgq>@VO++M!7V=5Ao?zpJJ*Kk{E^S9q`0(Znc=2B|gBPF}&z-N!6E$lgDc(cCuJLpt%jw1R&T zm`OpV<0tn_D&S`@2a{3KKkK{?<8_UD^sj2RrgJKFtY%2*+W-B!{#e`-7z>p3=s1n@85$cyfuRAX z!0oT`1IxbR>Gk?UBrz!&JV?q^c|C2{(&ozSH2OyksYLp0_+7!$S?xajUeu$Q@tVd+ zVU53V=s+`5w6_`>)16WG2@|2iG5bd46;t9GlZHnSIRDTy!%yDa=g!wT1&hf2VW)uh zemA?ial6EfZ^LZpH!vn$n@N+Yp@yoGVG75jsYZ8hbXAvJkj)j9gnjh}wCD7r>jojW zVIk$hn1+z4=R^_CW6$8()!@PZW2*4%@$hvw!@^Qfi3}f6&dqNzpcmm-Pt#T#HY2Xq zQ4yxQPrW{dOU3T@M_#F)_`^4(q&%}tTg*E1`Lv*rS{atdCO}*#CG@oa6jLS|P#XxW zkfWvg@1`BQMmv!innH##4~~iF!quqzFckf=DL)Pgwoo8vo@=kydN8~sW)_y6 zJCp(WyRum?K)S?dH^#iO)JM*Hzn7T_^_BFe6}TpVa?{vv5INV@+>=$XMMUl_Q1W2&Cz-59aj za-1^ar3U!hjEsz5^NsjkRys{&m^;Kq{?C@?g1B9yef1-QYlYlycdtd!Jp=h~~! zzx$a{oZm4m>WMH5V(OlH^YP;8-kO~THesU2c9`gchkqR$%$nN9jw*_V*?JL6d>dqK zKRp-^46TCBdFKM3EKl4dOxQAHOoDp%Ao*iBnR^KSKJZRR&brxFRzI{3!NKvpy?XfK z)S`tDLZRR5u&`u!2l!&M)}-A-1x|~1$GZyJaxCTKym3!MLfSyDmZP5NVB8kG+>22} zhwOOZT>J^~z(rY&X!huYtH_JhT%^_J@^9?lO53&69{z}REa5^AqYi&ZknX@}rO1I$ zjq<_Sj@GqC@o*YTIfJ32ZpTbQ-EM!bIggI22>OIVM{f!fQ1?7&e|{9SwCKDvfs z1+T@RBmk=0C#1B2mkDY&Hfuu%_3D{oPSuIKYm#p_o$fpKsJIR8eH#wDvM$81Law+o zyYlmc=6GfEhHpGyzpk6Fiiz9r9dW9lWzA{xdOm!pIal0AC&$HUZpbim?WLBac>9QPC4MHcs5_&rCAX^JH7&Y(&y!p>v)G(kjOWxhK- z!;``4yzk+Tt74_Ho=elF*Y_<;rC6Uo-RmW2c#Zs|@764yKxS1H36Zs9VYX^mX67Ar zH@WpI_;M=CD_KlR+?w5TfYR`7q1yk=n;Rfq_t`BcF|}CdZmwWlDt^3`N?nHB0OeN4{t7E0pBd}fhBzHEzFlk)xOQlHfcQ32PXwqHPfdU|*E!u+yxXV!3J+UmsjJlU@N@>!VvSgx6&4FfAeV_GmKx8HAN zHPHmg#66naCZ8Ua+dg%HU|p$r<3_|zhBrr=H)G1eVK{Z7aik?UMA#+&8|VfQvDa%Cwb7+9S!7T_sA38E3FP0CM(Qb4 zi|mt8hS&FXvbXUcp}b!a#+@H3e{6@S8+Iepb-lW}T17`^pyvz+ zhwq(`jq$a$?`|6`guvAL!MzuApaT8T-dSfDVpNhRms~b+^Zm$KS!HJC@-3C`oKx1; zZW8ijnv&G%-&g#ARQ2xnCAW}#o9Bu?4A1xH=k8A~yDQV*Z@6VKGA&IM7A$G?=j`&< zk&E*>=lk>IV7&SqfIaU%SF#MvRL71C$LcnK4+sX*Si@}+wApYRUUaoUhFVL{n&iO3 z1b{3zTh%W>;nUTa@AZ+E+fgX_*+cv_=@~W!5w|fo$_++_tWC>tOEIp6WniA;9wo=F!MzFklaCvq>i00nd*S`4p%p3tW zc6PeCn`0FZW`yFBk{Td)o;>yyBx9%=7JrRnH=3xYZWeZba}y}+o*cDJ@vo$;v*ur> z<0j^^b{v|>@Hf{WB~9A+*8ly-{+c73$Z`Mr-F94MR(WkwRQ zp8>afi-=Ovhr5E?!kDOYBuWV^1anNj&F~DrpMR7U(9nAXlnGdeF-UphsXz1P)y>)*@k_g5vrw-zL$-~rU9G2@* zAja*$3u0t*JO33N=*sPagcsp{kl$*_kb>|bLB*%59I=-B-45tMV-QXkeuWi0RT0@; zw7Qb3eI{sSVSLgiSTqtL3Y~MWDqg<*VFRGn6N~CVsPI-Maf!9>giuXD3PwEtGzv;@ zLuFB2pw#-48j6=Xs>12e@787Na-fnDSptCT5YhbPoS)ZQ z_I)UV{R)Oz7_$Xq@w9%h36934X)b;+t+#(6K*ZS4}EnHXy%U@c`E=Kp>Pn{vCASD{jB=kw1o7*FPfsgH)1Ug@(!y z9Vp2a{J01g@OC*>y&99$YkV~R)3dk7%jI4-Iz;VT)R5}|M|sbaS^BTxPBUzI#!kT7 z>-(+l4AgXJ=G(l&p++rQ)jvU`hG`Oz4z_U$T26sn5k`^>n}9~J3&uj(HhXK}P&py(ke?f)X}2hNH=Z6V8f%im=^sz_Y~F%goXN2TB@j!QpR zZ7S&(4+D0AG1+l~DSF5R^`lDoNi8H^c9fcS2V@+;orYv-d8_H8)9>ODfU#nS5%ov8 zrw8L*1tlfj{j}Vn#{g9mWk-F4hQ@OueDDh0P8pM4;po(1fRC%GAs!yuO<=ao6Gga6 z?lw$Fd1toSJe>1Fmc@DV+OOvK2lmX;Aq>dvJSlZ^6U!tOiG`L`Y_1ir z=)9i1HTZl}=HL!dkLgw|%N^MWZ*FdW4`?{=i??_4UqT-TK3V5BALSXuQ16YaE#>l( z{YT(c{Q)^|IBbjPn_6}z;vA6i>uP9Bo_o8!lQ}SUD<ntPnFbG1d=CjV4CA+8yf*=!ZJmJaR8k=%`VQO>z0U3154m}Q z%qF|LqyV4IZqrHp^~>PT=9vO}qMi@J1&HoUv)B##pIxazkWfE5jk$L?ygn9=J5XnI z_KiNHt!LAss-9^>-kH7sNb3lTh@|mf$1tLArz%D26dm%~aO2YtvhMx7Lc07p9PzDS zpcIq=0NyZ0E-ZmP<)czSfU~5}0vxoq1tCWkSNw^|e?EU<1%zzP} z4|Oo&!Y}9)IkwEozzcIBzA(1~HN-yDK8^k(^co?R@Vdlm$^lr9*xhB*WP$Dna}@9e zKD}!pId^Lpa-mNAiu#!d;H^vJsJ6-{Rw|s;q(c<5snX#`jqF}j9fIJz8hjEnM%Li8wS|)?q1)_q+!IcK|j!Mw# z;k0igfkkDT>%SFak_+vCx-fpv7o+h@OWP&CJ>73M?;jpDTMlTkTiHKbRD;x0%u*(H zj*PyrxfESbL73~IMdeK}h(Q>WGx;|xnc-#nj0C%7dT*X!>o*>ZdOic>SW!COqLl2>I zff3*Udw%rjQN#il?mIY-@m3IEN+d$suw!7wnydQ#K70s;KB8Oer};rhN9swv{fOHD zz9mg=h^uw;4u8Qb&8NrUBp~bw8L{^!RZ`twu>=QkI#T&j)771B2ROO8mGbiP*v_0e zU_$7qPf@=7X$#arryuFoUKgAN@zJy}*66}Q8Aj)+ly^=OlD6X`ii;CMl(csU1rNXl z;Bze-^?hpFL*H>rfnw9sy4hl*Hh=}S4p3lGqT74^6!lcG{N2YXo zx^_#lmh+cl$WAqjR`wSy5A?KT8fWC&%|BTShSJ25u%0_VlE}rzHj*V!x%ic3G~0Q3 zmW_)`J@@Xkz&P)!A(d=XGboKG7*@YiQf$nfdC*%**E3KX9_O^kklpG$v-IJE9Px%+ z%-;1t{T4SfV+G}&R9Jm#%)sZ*Jf`$L)VgEeGV6t7K76pGqM}kA<@s7)-}2TnrJ<71 z*qyJKT;%4F1)g{Z*Z%(g?BW5_cRkAT>pmOH^15&hBWDMPJX`qFip7Znc6gXYP0+UG zDF)i7QkcN?5g35o$6x$Z7?UH&-kQj4Qq z{N37gTS;eI2;CL=y11KT@B%5hYX&5wHv8Et*l8HaYnN40>RyT*$3%PukjZ3YZFz&) z8DW8d+o7dLT2HDY&rX)^c~ z**=MuGiIKI6J1x13GD zHb1R`nxd~!myq9(GZ~*M&pJ1oSU;KxHUXzSiHqWaIKgLSpn0P&aol*s2OCN;HLH$C zKVrQ&LGL*8)dRxmTxxth&Vzy`2ueU@bj9e16x zi;Uah51l@AEse~+qZ#2-4<9sMTuHKUCbdZS-)zkho_zjZ$J$N=o7*ESFS2nTo7csH za`hiQO98aVfxScyOuV&fO)oTI(c5B0R0?$;?O72AWN1ChD}`cL6y!aD04joR<2CES zx~4}vUaafVcpnWGJGNt7&cPuV%9lr-)w}?EqJ3fg$7B1)o!u^roWU`)&F=uJm~u})3Ba5d_>{{o zF2cGE)Ejui=6|aWZw#BBrk{AckfE%Z01)>t;?dSmJO#_ICk`L!|E4O1tO7^+uDd236yzb1lJ~!TAWEg^8Huks{{(KL0Flih91-Xm7^q12*6HR z%y1YfdYklSPh0SAtx-v1&!DcQWMqWhztP1&Z_$IZHXd&N%5%jP91%-eq)>=Wb}S`Kqr=z#KQ z;SF&z#w0q)Qz$|BUS@**f}iS~Leowqy$aUe%EW#Fs>~7;BOlISGm%+8 zVX0LK**QhC{oK&3#VX(jQ4z&3?vXhzXz(Q>qV4;2YHb5SVIP+BCeHEk@yEl%yGAp0 zamBZB!d!VLyK*MwU9E1n*Vj)NkN8P{D|2;;iHS)N_t}o04qI8f#NY$1d&-4+mAL7b zM=qR?n*?4W-ORGPla3FI8eDFRi@(3WMY!&@C%lGL0hjys&Tm+4EJv2Qx*Rxq@z~gO zhY${qNWcq=6tvw0>f1Q-hI{8%j-Kl+j3{Oj3#DFg>IJuMfp44OyWp}i=R3I^3t`;J z6?z)B3iR7{9@w=iG+j9Rs1mxGa{n*@OLjwb5Rj}s|7jKK`j;Z__apiS6m3L4*$xcv z!x7URwXcG{9>qyg$6_`1A!@gm!(md*t@fcY?i)SPSCqiN>4*kzw>SpApmyGt5n9^~X2*0oFB zuB#R=kMb)G4-b33%nslGiC4D&*Dn*0H=*OViaJv`RSJpLr=2W;zjhfrhda+a&oS@# z0Ev_4W3v5w%$z|t<5dM@BBMMYc%O-N&TfGmtB!y0sB8!?Ytl60F?MF%x$R>K z!Ye~eE&vC^wGEO}^OSYNZG~_vg~EIH?3r7@TaLc9ENn$%ZEzk)MMn=wXv~@{%b&SUrfbqKNmO}C;v%nzv-B3#w&E#sN zfZoEQ2Djt)6J;%V(}UYES#);qDI<#7GnlmylrHXb&Eor;=r0m?#@ah^--#T%ECy1+ zy;B3QKbJ)jXggcJ+Y9a7i!R)CG z%W;?;Xj#TjgNw&t2CtK_``FH;y&&uH(a9S0oj}!L-VTxl#8DbNd*y z;-Ka{SbZsG#BwlE!1!6hqBaxMiZf<3D8~OdpSMzUhRDRD#WZ+97}hp4G8*}EC>01+ z$vgH=1ffVy4aGfS5|}2cN=jor!r55h*WMx9zC&(fa^>!Yf%ce7>iRwwHF>55xi1~g z*wSl-uQrCJ`pnE^xVf-|CuU?^5m}p>9L5%}PH2TCq@^jKdvj~`Awqi%HUILdzQ7#4 z!*vke3~CMjV!s}GKNg`)UCur5|0IK1a?TQ)Q69Kv>|ZzeIPC?4ko6 zu)||b!ad+4t2ZfGdd3G5Q<^pk~L-nu}+iSM}ah2h6@W` zwi1>23EA*p_fnvlqlI91K+j8ZyhFz65bkbkj?dx0wa=`zkV-}ThY%M2A2$1fC3fw; znkYStZ+hVlH1Te$gXKw5P1M;Rrw`XAEX?%RuZ{u~h`Q~f^eA(mx+#~^2P1*|cQFXf z)nbffuqJ`D ziYLQvd|;U-uX(l=Ll13-6*z8>CAIBHgD|jN0F)lEaS-*#7dd%!J6pSZGjO&s2Strm zv8Nhn5Q!Zo+ek&UYz+b&7VAw{XcafgB?_g*a>IF;_F!!8nYE>~fO<3yG085D6EA3u z_m7V|t<#5YszHrJM+tcbY?P$ZJhW|A`~k0cHAfBI)BxzC&navH<^c4@h1YE|dwb>D zh?H|+SnM`YQ&d!nl_L_lfDKDcgaIR70f625GdiPqS-&Yfl-Uu3v4uvCH@$WKo(MzI zx4Gpoe;YGM8QhBoOY#A08+Hm#@Lr({OT7AAN&cKJ+_5rAbUH*GSq= zNm-fCX~Dd_;xGtGAhSPHx_L__#Fg~6aQ>5aeX&mt_Pd)Hz?W5Zb!t#p!uQo0fk9z` z00RB_c_`1Bq@FnJ%2{^H z?+Qk=2_AcZvL4Oq}Pdhj3LHT?6yb&6dfBiMoo<3^z*$2WSfNv9V6_}oh0*gujLPyq@TLF}Y;ba#PQJ*ZF zeOkPpqY!k{59K5>hytEL{0>`UWN@=fU0H22bg{MWlm86A=B5TSVv@bp4q3X;L)1+B z;YzSGGKx8QXQVkE(XinYw~e?%vb1FLZ_1~}8h+MB-{+==xGacg@z2tl{Q81sCPKbf zywtdC5gZ&`6G&Vx3(+Seo_y`68JFad(Bm%;L=l4#Dmc$SM)L`(@w=lWl%PU``F_g2g? zxB9`^ol0^GkULz|rfc{GX(>aqU4MZ3Z&DJh-+?s&^zJ+N18*7vBp1AxDSofd1qO2; z-2wKGP_-`Tnt5;u0Cd!|0{j~`5%2`(+i=1uFOOvYnx$_UYK(-OP4|D;@wtEeRGIu? z=nxdW3q;(svMrcn9KiGVyJ_X+CH_We>$s-M>*jcWL<5IU>2>0+vH;S1yyGPE%W!(; zX?yN{rw(t&WN{iA8ba@1d48x-y2xpf+4qqhDWt+*5tEb#BMlgwm?!8oPElMVx&>P8 zI(x^{7$9!hX4{OtX;RH1*RhO;%5iIFzTHb@8=#*@^9eR3DczrbAF&V@XX0?_zN{U6 zffI-1?%hJatXk*~o+73c7>m8QJ+Rr_&c`=Cqzpt8X|WH+(s8WpX0oFA$K=8Zjgi=1 zn>)2BAD4V#xJpXF$#Lamm$kf@zjoyWgY>s*u2dyGJ^5HmuwQrs&q-V3ZBx7G9>f_D zIFXtQfY!nVyvzK4`mxUWJhc>OzBreW=I}dHKdi$TOdof+GdnI$P%KQt6SSscb@}-P zRW&se#_<?&# zB97uOBA<}~4B14CU9B)GS~`5evOdfSSK))ps_TPYdp6FZJxwv2p>IBLqJ%1oetb}_Ctt0aSGG26RWKmPiCOXmwY2^B`yRmR8X@*$Fm3{y zl!b!_M!$4E-JygWPJK0CJG>7mF`@-}Au+ILZR>@s=j#O7QHEzDnOs^Gkjgjo(Pj0K zF+Z(<&5bV`1$HhecbZP|p7ym$aNj#<){#-pY46|p2ExJm!`E~m0do&cEJ0|cA=S#s zA<&Uhy;lIO;87Qj%t+W?nK0HY4UWJs>d~XE0KmnA#GL(*q3ptVwSSw|w+Q{r%nM}M z&|UT@gy~J(-EW;{M)rA74ZU4HlP#p{n#U5C44iu5@YJigZnR+|I7x0yjmTHfB`|^m zEb`$yJgM}V)?Ppe67~>xAXtc2eFZ^=m{&|qp=avP9l4NVIh77z?#^tmkCSn=qns`D z_G_np!JNl~^??gG<#5PADrhA^%_Hz+uu;Im*w;o_4>oqQm*eTuLBh$B{wWGX+ciw& zwi$BJEE1TSe-{`CXc$_6P7Kj`o7j?^_nn5Z(}&SYSI(D_i-(s}3-I9JOy-osevEPv&SPejh@#M>iQ=L2t-iLo;E-Nqj5H#nmn zMMf^H{&;72;%M6|Ll01v;#~p_-<7%H%bC$$3_`8L-%2y?G*xP*>*eKG_8iUd zsia7qo7)Yl9)x|g3ao4bab_vON~77_+yksk`tPt#6W>?)T-TzvxR^dV$098`OidZn zp3B8$P}>wsxRTqwvYJ*Q;y5SI>$1Z2I(S*Cp*SgtFMqzZr;V-(R;g(2+hL3LsfQMC z27aA`V`_P2gSjIcKlyf!0juQYkv5MclXZ%?fAPDnk2XlJwCD8);$H`3pTE@_Fx-TF z|B6Yxdfa=D!}nqrhx|V8o+qKn{RUu$cYc;GJB}t_;I-^tA8Cr!_3tUrE)Z^m4Um#u z|1DbHtkQcNT3ojyQ~Kc?<~$y-CHR;~6Z3Ik;ve8L{0doTDe4y9F5Rwe^>sVF;D8OE zEk0{lUudpDw{(wkC>yLdoY#>6JC4spBRt=E`a0mD{Db@Uw5PM*tI zW6c6r7AAYBo9#xDPB=K|KE1S&km%YOG3t2fKAp(Mx3sOS;Ct1Lj~OD5I8hI45B{k8 ziB~W)Ep6e2}F@e+TF++_^oiMTUo3dv?PRI?#l?Eyd%QGPSZ}ycOx#e!#wumkj=#B&+ot@--BMLT;yX`D2>7d z^4^Yh6Vtm8>gq+f^Ag3@M)}cPb9n>u>-&}@fGP&mUPu>yOjlC3=hTNfSS!Fl6M%JL z7ON0OWplQ_JV}4?V4+R@)K8{IomJI7?F`|5-*R_Ps+~>E<^7c*gj*jUKW=;p4Aizs z7MIP<=&eL*-OW>k(~pz@u8XWd%ZV|e zB_>8NYcdp@0EbPKiW~!|g>2j)4|dQ-Ij`(^d-_!<`Q?HRe<)7y<XVBr+W?n?(WM!SZYym#p!bfB@sasaJjxV;@ ze~s}U#|IhX-}!w@ZKX8={%&)`C@?L}$~~!7btVE%LOnA|SjQ3Ce!!XV@ZluQNgY+3 z6!naHrX$ZI2x%6<4=_WGOrv2CQE4q&?0yfuGJb6(-`?J<@Fi4nzERa&xw>knRYaqX z7jLYHD&U7>S;&a-ZLIIDpSFouMtE(%O7;$?kQs*7k26(wvM22GP6WIYj_)1>u@HGE z2QxGKXO}~q0WO8KE{2qMa;6-AND2FwIMibU(>pKWjFKJZmR%vqfTP4#?@`b?9eoZ` zAoIp&7fPkooh=58E$*71* z&XPq$GDyx0D~gGrfS_cR93^K)C5cE50}Me45(gv=ab~{0gS+qh)&EufRBhGn0>cyT z?c06&oYQrOkB@wcl8_B4v`HVVu;PUkCAiY9vYc5$d+fqZ(5O-7B|-$<=SPlaQlERv zh0UD#9`li#t@aXH zzWGZR;@G^&X^78sdr{eiWQ$I#5HfGq*Vljl_N}F_WFa5gqNHgv99m&2Tj;Zk5>&@n zCB3ePc9=1GTp12XctvV&=jtw$n*V0R;zWX%(T>ikrf4Ip-uzo0!-3eR6@pESMuOtI z+wWLdm@s=;TUa#kk*KNu!_6aCkOyIs-Ztiap$ETt3fG$?*P}mKo#mIhhc|?pK+HrQF(t;B#aHwb z6S|*XLGEXt{J4{J5~*1mrCugXC9e@qO>MJJ$%Up*JeDR|i4x+Sus3@P%^p<3im_CY zsHu6%47CV+#V=9ShQ1F>Sao=SALqe(4aH32C@L=s2_Wjfk-5bmra<=;dWz36i!PRb z%BBE0th*4iZey&1|784eQhcxg*90rYLtfCVDSO+4D><{;X}j1~upDNz@2)KT_Pwis zE?VX-qmdWih%@A(^5NnpW&9G}A^KV0KF$70g%EYbVQ_6}>gvos&EV6p!&LpKjw3Yr zhMTMBJTxcMuw|MRiW;#=ONOz6jfH+5=0u@74Rg+#57??BoL;{=wtp=8(dqb@hlqqLWcvETR}i zbR-PMvFBr2;yH{1xkN>Q?qB`^Zx_dUNm`v9ZYTV_yDXcs{x?bVPC&=oDctvKDf}_* zvuJDdoFJXMf#ZpBOLFd?g`#(VsgJ(Ce#KYOkbH}FIT;%`t4rs<^FXQ_c^_m@uswf& znMZ(@xf&!CniaE=pVnFSTt0}l=oYO^-NYY16rfez30@ZkcfXM4SG|-X7}}r|GKVL| z;$#h|1H=Pe&NIY}g24=(%TJso+cR?MLQkJw++B7im`mv;BQ{}i@m63dKS(_$X%lX8 zJ-W~kkxg4-tz_`~M~V8v7{9efW-`jaGv%3L&^@5s2Y>>>`hp5WyU-wN)z>1vnUB{a zuL^)%4wAQk0`GJ`(7PeHUV$75IN&}i>%jg3qGSO)1~s6e)PC|i4v&n@>yIkJMq=L6 zO;k^_+Lh;MadVR_3EKL{%d79+zAaUvzZ9j>J^G>pZftxsLc3G!d`PV1Eq-kIx#^r+ zT#vY+Sn>q%AP?}Tsze{aH9{0FK={BCn~}&cJ=fcikT&JzfvDD>Q^6oCNwI;djj9+0 z-lOk-{Q~p#N!FXMk6iqD3Q3#zSRr4l6I3M4xMKH3@`9^>5A&5vyww*q3yo=SIX6i= zq(MiN^@sIKgUzzML3qEw%)3U#GIqdbp;v+4K6WEr_B66h+PD}?V<`ie_%1JrH+2`wF;leiz zN}W_8@E>7dV0Af=D7*<-g@xv+BP!$C)V3`+<_jl;rl%fzct6%lx6qnM&)#od7u1Y? z#4M&0iI-%WbKYmiK+@v9*t%oubN?* z8}0<*$*fcTzu%AFL2Zmk`ynyUo3g4&c{pHbXaZFsID$&?o1h}-=Fty7=H6_aw7BRD z^6pLtxzb^3-6`0;jsAxw(9=%W56_J^iOp&tUz*Jhz~pBt1JwK#v>F@E`JBn91SV>7 z1Nag5gCz}+6FnwVWqWfuqk2xY*g_|GZ5vglx74Aod<6++X=qN+QtLKfk94w2WyXi6 zU!~#~+?66|0j65`A<%qQOiT~LgnD6DmbV^eO&vLxD9(VgWP1T{%>cTlGGsSX%A^DiNUA10aT{)_wmW26HQRJuD2 z8^f>3y0;A5>K%^Sml1Su4~JP;5Eq)^$o8gyHqe?u>m{-V5&Dx9R^RPsUAw()7ts~8 z^YA78_CHCh^pu&7W*S-i@4EVV6v>t%_QU6CXlW&BrfW^W$Yk8JgBFn@_PKD*pzh57 zt6}!I2KAdL%P;J)YF@R{ewiWQLv13jdw|LMS8P^b1{@_9F3L@Dd~`EnoC z)W4qqAL4uZ-!--dq+E^+ACP>-@G-!52rAI_0-+$C_qxq{zLL`h5e6z(xK~Ajv92H-|_x$ zgbz%dd_<;o)J}#_0Oj2R?HYx4+TDMhY!=6JGS}qS0B6uGUwQs&9Ya}n2SCxKR3ZP; zo;}IC3f#rnWN*y3oStnod6@|0E82;^~^3w$S-%kKeFAvL|^yQ-5m56?JDb`3;PimDj+zg zR`-uNHoKQCqQHLu>n>+k0C-N#Y;N;w@T=(X-qy=kYX0Hgy?Zfyya^JX4kJt99ld50 zvN*(Vr&5jltF}dYr{t0*BCWDc{oQrDu}x^X=!LsF-ysE5^{YO89pH4?E>D(IC^pL6~E`$zcPN`uDU*<3mNa!psasG^#Irk+BVJ2 zFe*zx7}V;h$)jF%wKWR>@_m5>X9x#w?LbJ;@r_OUO6?y*&kLAUoij2!*_~x-ORNjVHe3k3D+} z$|i9%!mB$2IN%0 zMNEm8bde`_JS;hB6I|akV2;N$$42W~(F(nMVWHL2 zpT9qFAmT^mzc*9>QO~(Fp9XqEMK;+wrO7u^IK3oSt9^Ecs?@nva2DoyM3-)dCv|z( zQcjej>0ss0Pq%L0#`?x9q+Co&ScDGqY3_?T$ulKfRWAOF6V(A{mPX-C{Q7FPw6Tyq z9_x9@sab+a7wcp7`AP(zcQ3J9_GS3WHtx3PJ!UXud)ed97dgK*K7+Mb$mWBHB{nVBJrnXfgU7_o!FN5H7UwkYsX`SFYD;+be-*ixVc;J_VUC z+FQo5Q&M(E*56R6jItTLe$SaQ2 zTny6i+w$`5Flb0#&XW3^JLsut)JhI@*<1tbzqYnE&>ua2K9>Rtb)|^kMX+}Y8*24E z2?}_!i~I_}BwPUWo~>3pCE=~s;vfWlfSudH>SN{nc(D(uNlUl)t*^;t>y;Dc&zZ|c zeL+aT^vQ-XGS!AV7V3*`zF&C&1y@|EXHa8?G>MuV`<_P6I6iZ=GutN-R1w<$%ZDn4kNfje_N+V` z?4PgjxZXb)cUx%0J^Ff5NQo}|W?O9_i=a(!GYOBvOSe(pMpL}ZwXMC&v(4(zmcv-E zWfye_YpCnR4{N|N;nxc!(#R3Af8_Jg`!2s=)nr_N9)d>pRL>5l=mUol_yb?PhpwV6 z_^P@M%HVt_>sO<&xEW+Q}*F-d7;c8{kvfjQTbmt`Z_)11b z!4|ww!)!ONk>mV%o*g@kI@5gs{{nnxv4-(VFVHSq6izAfa=e%G3pRiriU+$1V+-{i z#`95Q=!u53duX?`+*gW;N$>~5Dg>=?1TT1Ci!6Bdl;j5*e1h)LwOh~<#rG&F8TyQM zjAFopbBI0>t-0}XliKd)`5|ay6Y*x3-4AdAEHhFG9LXe|&V+)Sl*(*$oBx(kpg*fe zcN$+0ZZ&;{-f%|m+;Lee+b~tnj#NRYRQKZyL3JH;rV3nVXX43R{8-1yc_|iQub**m zOrcpOQ`gIT5zvG>1VKfPN~))XE`-7m{*#yc~}cgkaE=Z%tycMV%LNMcGA14_B>O^5eP}Zuxn>B zJ1wG1y{Hpj)JH8^n!EI?Ii!uioSY8tJ`?6|Wm<+W%=Cl~8|jYS2ih}xNBElpEmZ9T z+)igM<-E#TmS_%biF`UES{C7RXUFI2xmc{N6mJ2@7<=c?0bdJ6U`e$94*%so7V`#L z)#@Rj6Gr|pihK4v2Ky{{(glCb6-7Yg&kK3@A`P{~*ayVvp@mgsn*y#A$?|ia(4~wV zEk}SMerB$^e)u`P`GSW$;It}+cDJN~BO?ZzZ);(Z4gN!Pbc_s%59@VXMbCwJ10Pu% zSa$mUiP4w*4w0keZYIFTY}TPi-EvPz@Citp5xEVFgcdgzn#KOFTCn9GWJhq$TOy+p zylG?j7(r=>28V2#r2-tLDz)C@9Mpv_0)cTedPTPCneyK|E;ODCWS0_6@m?o3LKgOk z&NCrdSq1WvEjHPerQ#`rsSUF?zcf&+p@R&;Vds*Vb81}gX--ajp*Adq&Yo!CP&_E3 zZLJNQ>P%L~9!>w4BS~5ejW6skb8Yy4)S4}?NuMM#Y{6ZN>J$Y9?Z!)sNMwt-_NQu@ zHSVHmV`OI`LBMR_xE9>4e%8&#X!krqrAotSz;UMI_pS(S7#D2>9XT$HfK)gy=_a+~ zMs2S)`X5U3xrNME);E4d3Qp|B6W{O{g~G`{1KE*cl<+M`Jlc%Nm$Wgf4f-H29z<(j z(a@+ErWoB-(Q|OPL@nKE{!%!d?rN>BeOhBmrm!c=!z$xig&M;K4^0k&xdD%MFaJJ~ z2R~|-M3qy>Fxj)fh8>`l0LP#_o1%SD(WTSPsqz~gyM;V&Yjx>DpLBaZuU_FD0N}57 z6&Y9US}K+MbyH#5Laz*`_d#qn+Jr^H{pUXv9%v;R+C9(B?K%rs{3QXS*4B@5QRS5n zL1HOoPJG8=Tl^(wlZE+<>Ieh+*mDL%)Rw7t87e0CiUD6DIH-wfwL|i~ z>&X>>A)aS1t6c;(VrboAcBe{6Afep&KKxv4l-a(Jq%2JB!$CH;_s-Wbjl*a3AK=*z zw|5ryL;gM*5j?l+Dnu$~ly_s}O@k1r$0V4fcuFp)tL6Pz51)F+0kqlhIRN(R4A%u~ z0iKwc7}&~aGy->$Nsv&0jRrurO^65cq%!8a3P2-4tm}qjv0}*Pvi0vh87Bd#0CYeQ zuBVA=xYmIzmNxeXg!40? z*8<%4zH4D=vQuoN67c5&HWte>;V=2w_)#0(UPvG?`uHMh0E2qi&So8&3m<@ZpV?*V zGYKv*?a^0UyL3%%)QX!vs62E@0~$OZ&ZH+_1hhct{x_9P{HVpBdi;&2_;j;rUH&*_ zYPrRm0D5eI?6Qi{o)OLm;hQnr}$AuEXgg zDhiD5aAWRm}~yyqUM)RZbJ?z6-_<|$Bt6X@iI3Vba_H<6^UsX z8M`fmU%ar@Na5&kFQXFaoK!ra(!wtQ&Ya?$6y~o#r2%~*sDBVE2c5k09`bKCq0QEB zuRq#k+eK=gBgmDm7VP4J)be?2iFIlFsazpRj=*Dp|o(<$dxO?c~i|&_*~}%eiB( zjkkkFn#T8m7GQwNQl#skWNQUwO{R?B&MTGH2#=wOx20yEEiz98)RAr1!HPH+>Py*| zU8KOLozZSe(^w1Sdr*YXnny``mx99nKl<5UAPArv4ZuN@V?AzzW*a)2U7FeknN&xE z9fBn>VnsU&P)`Ce&I=I&2O7A4EyRFNqWMW>CdF?98(*ae z_#Ni=#l_w1H?Qb`ff7R3`d)EV`SpT%4;4>p+7wcruClpc?lb(--y8BP67bJFI{xX|^wAZ@@^v7glhUhphf z^TE?7SoyW9UJ(9 zY(KO(0IeqaxRFZre>x>X8U1+DGL`D>IJWU;+n3GvvQHm+XmONjs>Hzj3Npf-f7vTm z6QF*5c`6zZycQ(}Mn+IsDnm{2A|N26ARClW4Gry(9y74BXT#_RoIAO%ukYx@Ak0yA zhSR)$oe{*spp)ZR*Vc|(xf;9UU2ChRX3U-Mbr;C};Xr<1;L%C}<%onQF$_=0S~?DE z2uM>!`Ho{sG4{1g&Xv1XKp|sO_IGBnOkJMcSq5cr6efKdFtHq1K-v~?IeoGe ztE#V`QDB8Tck<-bxw$!KB0mpLI|R%0XU}#cQ11F4&zJDTXd;7+b-W(KYH4|`!CtHb zuCE2ei$f32epqftXOj8T-%T;;KxCv4SzY_yLrb$4*=}KBYBhMzk!$uw3N+ z1x_&CULM3?r19nM3kZnAli@CaKPI-d_tK?Hkjq))2Vv{gvcT4}np{qJ!=rspK%gCK zfb&#vaVdo6;>aM;P26MTwH)B0%#;)8-ahZVh={OoZzmz^+FEJ-CIKGsi8Zw28yz=J?)L;E!EaMbJxv4iaE16-t`5((Usa}s zq~%$u6h4DPk9t9ptTGN#jG4ZVaQrlk?$zJBhkBJ;1z^X(m+u2@RSr(hrG1vlw{L4X zIc21!9a$W!lTxCaltP|z<_d3jyPEHx0M;nr zsQ-F0Q7C1nso7S;XvEw>$!$n_GA(8Ly;Fu%7aVsymm`h1`G^b4@~Zzp~B9JjsMGU!Z+Lm z*)d~N3W5(xsQ8cFM#*9R;UxEa?t$$}Q|GHLEMh61LzHWP8-PNQqGoNajUNVN#bfa% z42p;)uQuNfJoY*%j=UHlO2}_2B%L~Ss`I1VhZirg>L7ToSf@YsD$OdJr1_0}@Zdqw zv1pmzqFD@yWY$D$083214cLglJ9icJDi+Bg(~t=nd1^+D3!DEL4 za~rnupV!on>0FjbEg9U#tg8&-d|}%kZDVnEt&jJFg40Sshz8aXmG{y%)WZZVR|Y0- zICsWZ$Kuo=eMM#s)z3qx7{E12khfj%&FhQG;L&gk^%pd>h%x_jw)Y;So@9_8=m6+h z#}>tpEIX0Q?x4?lAo4MQGgyV5(MbGjp*LwQQz^oKXLs4LRVn&j(9>SG!Sr3iRDd5xCO;?Ff1jGhVF&!wr0**C>y!asuNX0MK)Q@A6f;m9x41 zq<|Jcu?2)DNL-x&N*A^4NFd$QSwa%oGrfFLR8(}D31#vhnLQKM^ccN{EA9!~cr55zGyddDgGbTyJh?dbM{nFeYlve;#8YIx&i z3$ieDR0f^z{<^V+9oyATr37|G7!a3HdLnU5~e%JQvg+|~q`M@ox zU!W7zp=Vd|3p<1O8BnJP!2zbyodDQAILb|?TNbSS zgdl#ic~0ArZOX>5UaM+w35$00Kt*p)BK2WO&=@MnoB}-kYZ~@sirM6Z9lF$aFfHkCmu?a$_*zzO)md383VG*7XoU;}7{no;PFc>C&7u#;yLtr>&0Ye2gWqCZPnz zZkT2vMqLG%G=nOp83LDaFmv&<{7z>ulCRN7vYRv+X3AU8=dYGzRBZ!crB z=iTxpKYK+AMM)a-P$Y|)1XCy#kVQgA1G0=z=UxljcS-GUga(3G+-|4+;=Da6dn^nV`L*UNKW6 zHk*;=D)ON)M{0HCV?iu%Gu6I%V&Tk!EA{w1FOpnJBI->V*aZTAP7CZi0u)9t=Yz+2y89w^ zatI>e`0jtU2@udc)Q6T0^2tk8PhP{HmrqMkiK0)Ul91Vf37p5zsHWj`VI<=OYE-k6 zFDO&xfwE~37{pw>lAxSwETjyQT`-^3_?)k-ibF(jA`}RqNd7pw?4RKjlal>6MFN<}SI?q1I7(=TNFg$KxC<35ju{8Onups(){!Nfu zMHm@!PF$xk2d1Up8fX#n&M20C#j-MV7W>x5k)6tHJR3=~xLIpiIk^VvDRg#3zzy^q zPQRWEf88GdU5!o*77hrDua6rbcV{0RneLb$mA}@ce}6d|zcsFz3wk-~keosMSF6VU z{^nAmze#)%@mJaFTlgg{?8i1W=YKRvnc$i%U8l18sGWgE8h_N@|8&vFOswKp6@nT=2Yp_gL*hFY8aI} zyq6mb=y*^!u0117ySNT;vk=bg?R}L05q$&^oIy*(d_*h7W@mJ$-x>G=pdVy9$IDwF z;fj=_o|ciCRQ*e{li%c{eFcV#RnC$NEtU{U4$oMA>3=>!-HMLwq3BV*%B(H1K z#N^M@eo}TSZ->Ai4|%Yumq$)0Ri!SCR^TccwJqqD!gT7vQ%zvhj{QU@2M2?-`uii$ zLLY62!*yl$gd{*Z59I(>ErQRM$-li5h4#;TptF*1`}?w5r*Qqer~x=N?#psKDguLV zoQ?|_Pfl(N0V(&Xgl7Yf)hFvOC~eeYAO}8r{%!WWLw3Spqspd8A^gNV^Jui=PNAt) zSHhb3hyn`}6a7$y+R4+*frl&Q$s%#_V?;H6&yVOeU!I2IiqEq|fwoyT#Y&TEYEJh>PeQ_(sY z8!lW+90+0(Xd=49{-aI9bf)2Kt{rAm_WIUR4U)EQe>E7yeo*ew(f3z{u&a%;S9j*3 z=0c=%D8XB|bkV}Vd&$|jIsB4!_gVabmvx7e*JnJx3A+{8;Jc1ul>>7e$AnS)1$?`@ zo*f*Ue6+7E8jZPv{smMB<}n$T6DxI;H5xQ(!hGtS@&-xxq|v0)o-biL-3SGvWE6uN za5*OYs)K0qU8a(fcWZ?W{wSUZ(xT4Q8nP^7U=dvqtj1LBv!>s}m^iFH?PGHjpDS#b z>(HA0XWRND!)+#!3r@$*LWTv0Dqi{7s9xH9|jD*MHY1~z!$gmR6qfS*t| zQBgTg#fm^LxsnpZCc)eyY2Nb6qxnobqTgKOYeyT^Qo;q4!q+7IKrbQW_NTZmaf2G1Q0OoyONg=$^Hry07$7}vlzd(~2tjWtt@udnTdgCVTls61;X)dK9 z!eG&F0bLRq0w_`IKIdBzTlU6aoisZ zTVx?Ez!TN49V*YfUWh(X0V7~`4@DiRG={Ql>&-u4q<93d}qOv$X^dezO@49 z8?b8gjTQNAWDuTN&%KhRcC;UERaF}|`_u0#YmB>q-~?|-PyOjwOv|#1S5)K&(rz$` z2}`8#N=Q^}F+*yV1m{`eTQ!j3u^>GmF@ihQzdZhb^8cQ$o)00W$f z7T=sfdZG^v`*U1o$*7;=5dNwbD89;h(M5@m*Ygfoy2nB7X3Q^pWb*h@W}*;_z!?J= zjkPnMN=>I}>4nxqOQ{CCsXu}k^D&xHoZ5Ev&m7>X4`azwSa6|KUiIS!iOM7{8w<0I zzE2)KH=+{9-yNj}GT9rfM*3c7Ge#Z0?(yxAOBuC`)J>TJUx@7maL}WCU0C$IV=f#V zN14nVzas{Xp|(oG4VzyH4`CfD4L&*w?Wm0K^Ew|kHoR`Bffo@#Dt{5*t(%u-<5-Fw zX1wX!U2l04*}zup&Fk0iVZ6jGxPV7#>51$$J#8C zZyh`sGNMKy!no+_Af# zeKqhz5X#cUGE@ns=R?NXD4OnXa6(q};)1;15-c1#uz~Lpv)q@U5J%pJgpc4`rAd~8 z&18NDmue&Y8Wu4xrk(j`M&2erigs#ce*Tp4($wzx1xMuPX#v*a=lJ=>{*q=h*#Y`8 zmpC5>rx<+k*3IA6;*?(z3^Bgh+S=CoF$u~ffLsT|(WC`%vI@3jkk@e&w};ZmW*aS% zze58sXJE_=(xo>b?l^6MSoT6wXXuWL+~mVyr6%_{H$q3*#-}#We=pbc0n3SZp@iNg z_ixhQ!AKyt0t>B?ePvC+<5w&_ts-wV-KO~dE6@9{e1uLgm@T+h^O8(p;4$oISLWBc z{usZ>13h7Ve{xGcTR1#NxM<&SP48}aqC2Wy?PvReLtqD_!q9SnNl-UIza?f6IoOdZ zRbXR;ND|F1xpqdeQou(9UOQneFRW>j7(IKe+x^m!<`Pv7AsM`Ctg{A1iq8g zc^E8d3jEi6$dvs;!{4Fqxw_K_#V?nb_YXd%@^mBNj4qVusECKsc#R@B79i?!l{XX& z5W7AF(J#>=4U#WGWm&Xi&ISy|JU~wi(|0J*rorg6qZ&WtpQJdIqW2L0WX|+49p{r8qLQ$h8iJ#|O~>MD*1UO@QTHz=&F3IrdMaAU zo0^set{<0ENRU4|qLEo3R$=@Lxro$k)WYit1hXfB6au8+)}P-+K84c^UV7m=Qm3He zX)Iz1P5|ez7{{x8EDKSC8+>rb6L7@!gW$F>n-2cR z-SCKBuv}ZX)R^Ycr|1cUHzXM+RCcCocX>>|T&F&|N*JxbVn+Q`EcWD{WK zdJl-4M*s`!A+7*j5LF#pY~TtY`%}V`!9zRBW_PV%`qiCAz;CdCgV#8Cc`BPe|C=|R zD=~l>h%V+(QppgIwL?IFdtu#19cJU)T`{obDI5g$pkKITZ^4u!X$(U?d0zq%681j2 zE+;p28M_)|LxESsn*wiAaD-OWMPBa7-c(e;3c^Y-_c0mXyEkL!IiOE&1+cl-!ZGl9DWr`s`` zYiB5{aaa{NfGL-xK!W-a_)r{X_ugHe^}$=^%OW=iIu^;6H`+?-3JTgl!c5bhT~CbI z$=4tN8O1B_WW}g>SYV={@a@ep@%-#A5xWw<4(GHvWMQa(y9w}|z6Btsp|begGs40h zNVz~@W&q8v-a;FzZ)U)B#q+VkikN-1H0U{N0OM{SW7Yw}V0H>V3>p~7ZDE98JI5@g z*goPcm}&!^uJ_X~zgIG^LCw~hke86ZqqSA-euDh9r~9F#xkep22F=b4Z?BNeM}+`pW;P;v?gU}4wY+um@<>E@0wU|#*wfMj8&bc(Itd)cON9i;8R zVo;31-BOM_LOgUKvj;-b2FGh4Q9v27AsUR(Bq)M*<#V21Q~`>_D~`Qoo}!Mw8v)w6 zhu2X*$`_ptZXi5>a7b-e!{zPcDt8+6rthf~h71%)7rB{G!S6A*Ge0XEl2v9K4n*@t zVL_l!xyduTi_fhsWnE=SA+Ms>vZdyYQU6 z3Xai`qI<2DfswGG_Yx=QsDTLrgaQOLaC0Wwa|FZ7D%h!J8Nfz5jPy`+l>^ZAzkKd( zOP>$e7=Bte^jST^tG{C14Sk1l9dOz-Q7MBg9CU@*%kGmb)2Z$*@58;*8oxeiwnXXU z3}uwxtaCkjY1Bd1a&`XAZ5aF$Ka6TW_zxK{$+-u{y^#WD;Xpw(Cd~z7JK^N`&!vH| zk4l3C)Feyx`b|~UBpAl!ipC>IZv63vNkOb2hr#DWBG6=Y%m@Um&0YT~yKXP{g{qpJ zNW1}dB>8T0^02L&JPcsKlCUKtg)D-#53Q^+yw@jVZHDg)<#l#2EWSk>U-~v;MLc03 zqIU<=QL)=jEYY1toN}N!bin)lIw=J&&|@qn5Iv|S{E=TNrD$8jf}CW?nsXak>T{$r zRwH>|wY`d0R`*4bpn`2!T9LC5p{MbeNmlElFR_+vP4RK97$8DOJ_ zAc&Sz*6M>0x8@C@YaA<1oMeG5Pv+z{)XD;)vip>X5#UsJ$xW*(6FE5KuX!q z$1Gz<0d+{#&K__m`5MKS@LmWKm6FQ!tg|T+9RgOCV63$^%#LB{@F6YXtiC?p)9B-k zHnBN3DpWV!C0kk#+_t#Lqn)Mk;K2ixb?{rJN}}l%&ug6J>~IkGJf7>itvgz z5o=buY|{r1Q1;!xO9dKmC>U;GR`p;ap@xV7lP__4Wcm2S^%J8b8@B*cYUf00v~=FT z$DU|dDE4>|vKohB@%6b%`LL&XBx5-RX#|y^5Jyhy5DsAtXo59Lx(_LYp`I%jU99K< z@}AAh7R~>56CqlSQLf{Sm$iW=AdPyIlWFO1B1JnU=4tX}k}Lo~{heOk2kj~FXpVKe zSIOpS{k}bs+6jC4-#B!dWS#I-t~jqd@Ha!0emwjGa}w&2C~jNJPKZ#$^a&jz4%O)} zMegtMeRR_&0q)*yb#+z71{!6oBCiI0^R|qS!>;ZvT%PO%&pmX(@v?uXYSN7V0as?P z-|xKs={t&mQLudKn7GvP51LyBrfD#Eo?s^r4-!vs*-V1WRjKq)cy3S8cv7$tMw5QX zg{mJ{wlYaq73;UzVZ`(Xy##dPyov8Xl_w4r-CrJMzQ|w3igd{R|JQE)c^YI=aJZ{u zeMn)iUO@wYZA>aAYp=B4AsBWbd+Xm8(NFPzTSP02MflA8{QS_u#Amw^JLEe}zA$_U zMat8a-N@9NolNnLaXj((z)k71&t#5l-(Kx`Rx5E~CqAuUAj8$s3a1-mJ5_4yvsBh= zJE&E*=Gf^|mPzW3vM6%N%gKXL9Br>Y>bFFFs(@a zL7J8w?MaOR2TxfNMTH@_9h2%-RJ#q4+u3si?)5mD}GWUybx;HczG?9cP=Jc3*Y

    RAA02FS_} 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 D!2cSl diff --git a/docs/static/images/time-aware-tiered-storage/per_key_placement_compaction.png b/docs/static/images/time-aware-tiered-storage/per_key_placement_compaction.png deleted file mode 100644 index 0b232d1fe7f92c10da685fbdce8fff85bd26a19a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149731 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⁡9PnCKi}HIC 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�whqrev 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 diff --git a/docs/static/images/time-aware-tiered-storage/tiered_storage_design.png b/docs/static/images/time-aware-tiered-storage/tiered_storage_design.png deleted file mode 100644 index 7e5158c18bf89b7abcf7e03d22238602f319edac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38700 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 diff --git a/docs/static/images/time-aware-tiered-storage/tiered_storage_overview.png b/docs/static/images/time-aware-tiered-storage/tiered_storage_overview.png deleted file mode 100644 index 7d115e667d6558dc276a952a19d7b5f373d017ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73052 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 diff --git a/docs/static/images/time-aware-tiered-storage/tiered_storage_problem.png b/docs/static/images/time-aware-tiered-storage/tiered_storage_problem.png deleted file mode 100644 index dbe2ae53257799cebf71d651cdbda0dda5bc4b1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188590 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 z 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

    YS*Ku8aK= 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%0Du}fAW_LUzj!+Dmo|6 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%0DS1i=s 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 - - - - - - - - - - - - - - - diff --git a/docs/static/og_image.png b/docs/static/og_image.png deleted file mode 100644 index 4e2759e61784914e2d4ce92daddc9cfb4a01013f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17639 zcmZ^~19YX$@-G~FqKQ4p#I|kQwzFe%Voz+_wmq?J+xE@8|8ws--}&y{Ywf-Isj6R9 zS9MqO*`abWqTgV!V1R&tzKM$oDF6WhyL^>xpg_KU72*12fPi3x%moGI#03Qj?^ z=2pf)Kw{rA5?z!~7SV?`$E;}UwG#=Ak2{*c^JzN75=dZ1FF+*K7mU%XS%ts}!$P1` zev%n6=k3b+3;qW+8=Bb6nTk{L^Gh(TmXpGq)N${_RF zf8g`tX&{SvTN}tVCz@Cnk~AJ?hdzRRo9`JPgBTxv1NP<_56CXhA|6M}euFS}XUU3# z1qvdZCHA!87+D}w?4ORYia!M5%g*;2AlI!4(Dgll2I3U07orlAOrncyA(6s1HAQ3e z&B_``5~J#6Uz3VT9x^&K3TNvj2J(C{QAd%Bolgu!Tb>?Vo}TVqK&JjYypK-H>iEDk z>UiI~X4sj`MDRUXr%nCf0osEFHamYk@xZ8c z6HgKoqES(Ketu2_;aOBrnVLNH;qZKT^LRSpl3aGnvK+eGF0#*8M=mTcUP~{>DM?PD z+_8O@3jf60@xgsk*3jM8#~YU24}TW8E5>jf0qbR|Ul+*aBm9HJ0N&emdesH@N&$)K zoob0<=7bN-J&a6tHw}DaiO@au2S|L)|AH^;6-^)lxHb*FjQhd-LSE$Yv;I?mi{TD+ z0yUrBn1PcabvPmH)+k~FI;95$`8T9vHblKI9vuW)HyRtT>K3#oKOz+9GyxtJh}LiT zT0e|E|9!o0WFW+PuuJfzJvwrbSbX2vAXd7?ZQ-@RRC}0hzpsG`>Y;Ff#%(bt`w>7P z$p65~hcFj}_>E}n>J`4|eUN>u zeZ>($xB&e>P)0`dBN#&%O&FI-Ov>#_PZViNsY(z^h02G;?xi=%;mT>sfYMGy!_wzc z<5Fv7OGS_DD0x=pXt_z%%{*^@F9Ue5s8gJa^d0u0gSB~IW+`Su=0tOxsiW!SnYo#} zMHKUkIj$=9oPnvExsj>TDUJ%}O6ghWMKm*2v#e?F5hW9M`nqK0F`9$?s2~~NC?(S_ znW~A1%9sn!^@i5*R)jk-cd8Dh4ww#^4skE*ce(fdm&g~FZy^v={*L~*5bF@LK_Wpl zLD+Dsa9J27Sm0RH%oa?o04e4NIvp!5OV2(q(_V&Qre$mUaTFFOI%(Dl`g2DAah!25 zleY1f@tT8)1A$wOTYlKEz$D>J;kgV-^xkO7%Lyx{Fi$l|XB|P4DP;Ysgf=;y#FAnP7w{nJ9xH zk|8@%uOg!&Z5e+m4$5@pk=DHkrw3=9eb1TaitxPf{{7@pKqXpbN@cKi));f@XqG0c zOTYAD1p6%O^sC{|2*;=I)c(!?oSsNQa1q(MWy9-z@*MbA?Z2i?ofz zj1)~|O_(I=EIOSVn+q@65%my76W+DuY&Z5rVzWgsOu=2$Lk<#r~QuDll3Ty}kO z{p;xg%u_&0z^iu-WD|T9J%WVeCmQjFhFN{J-uAxmNq8+QXa z=-oN{LIn^-=K?AFI=0%K73eOKc*ivv> z1`vh^6DiXmW4y8S@vw1=2@)ex(=0>^XvRQob`5rL_IE-?`4}^^n$ex}aMFREofj=K z7k!(yfT$0td$GuZ(}F&?3pZvrz>VQeG&YVL9B+lWk#{1w@ecE)nPc2|?E>RHg=nqRx;Mc#Bb!pgF#;~ZfbP5;#R6u>#X-Q(mZNhfH4ua8z?&$U!_%DQN38_U-_|4uY{^F->K+xn3!={ zHd_W*a9w1&oLOoxU%a!~xbWl7^hkZMxS(EswaL=kJb(Xq)wKL(Nr|upzlZCpg|4{x zPJOx>*0@uqfk|GTQ1>PjVtj`g|Sk@@rRc2T0v(8dB!QRjo@#3IMb;X@YQ6$YEj`r*p} zwjXQdM!HMg=faz26XbWuV4GVT-j+D0!-4hoHpWMl&7loi@9eAWo1DSEl`Ls*8$5d) z4(2n)wOg{2=?lh¨{7(sI-qTHpc6Z7 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 -#include -#include -#include - -#include "env/mock_env.h" -#include "file/file_util.h" -#include "rocksdb/convenience.h" -#include "rocksdb/env.h" -#include "rocksdb/env_encryption.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { -namespace { -using CreateEnvFunc = Env*(); - -// These functions are used to create the various environments under which this -// test can execute. These functions are used to allow the test cases to be -// created without the Env being initialized, thereby eliminating a potential -// static initialization fiasco/race condition when attempting to get a -// custom/configured env prior to main being invoked. - -static Env* GetDefaultEnv() { return Env::Default(); } - -static Env* GetMockEnv() { - static std::unique_ptr mock_env(MockEnv::Create(Env::Default())); - return mock_env.get(); -} -static Env* NewTestEncryptedEnv(Env* base, const std::string& provider_id) { - ConfigOptions config_opts; - config_opts.invoke_prepare_options = false; - - std::shared_ptr provider; - EXPECT_OK(EncryptionProvider::CreateFromString(config_opts, provider_id, - &provider)); - return NewEncryptedEnv(base, provider); -} - -static Env* GetCtrEncryptedEnv() { - static std::unique_ptr ctr_encrypt_env( - NewTestEncryptedEnv(Env::Default(), "CTR://test")); - return ctr_encrypt_env.get(); -} - -static Env* GetMemoryEnv() { - static std::unique_ptr mem_env(NewMemEnv(Env::Default())); - return mem_env.get(); -} - -static Env* GetTestEnv() { - static std::shared_ptr env_guard; - static Env* custom_env = nullptr; - if (custom_env == nullptr) { - const char* uri = getenv("TEST_ENV_URI"); - if (uri != nullptr) { - EXPECT_OK(Env::CreateFromUri(ConfigOptions(), uri, "", &custom_env, - &env_guard)); - } - } - EXPECT_NE(custom_env, nullptr); - return custom_env; -} - -static Env* GetTestFS() { - static std::shared_ptr fs_env_guard; - static Env* fs_env = nullptr; - if (fs_env == nullptr) { - const char* uri = getenv("TEST_FS_URI"); - if (uri != nullptr) { - EXPECT_OK( - Env::CreateFromUri(ConfigOptions(), uri, "", &fs_env, &fs_env_guard)); - } - } - EXPECT_NE(fs_env, nullptr); - return fs_env; -} - -} // namespace -class EnvBasicTestWithParam - : public testing::Test, - public ::testing::WithParamInterface { - public: - Env* env_; - const EnvOptions soptions_; - std::string test_dir_; - - EnvBasicTestWithParam() : env_(GetParam()()) { - test_dir_ = test::PerThreadDBPath(env_, "env_basic_test"); - } - - void SetUp() override { ASSERT_OK(env_->CreateDirIfMissing(test_dir_)); } - - void TearDown() override { ASSERT_OK(DestroyDir(env_, test_dir_)); } -}; - -class EnvMoreTestWithParam : public EnvBasicTestWithParam {}; - -INSTANTIATE_TEST_CASE_P(EnvDefault, EnvBasicTestWithParam, - ::testing::Values(&GetDefaultEnv)); -INSTANTIATE_TEST_CASE_P(EnvDefault, EnvMoreTestWithParam, - ::testing::Values(&GetDefaultEnv)); - -INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam, - ::testing::Values(&GetMockEnv)); - -// next statements run env test against default encryption code. -INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvBasicTestWithParam, - ::testing::Values(&GetCtrEncryptedEnv)); -INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvMoreTestWithParam, - ::testing::Values(&GetCtrEncryptedEnv)); - -INSTANTIATE_TEST_CASE_P(MemEnv, EnvBasicTestWithParam, - ::testing::Values(&GetMemoryEnv)); - -namespace { - -// Returns a vector of 0 or 1 Env*, depending whether an Env is registered for -// TEST_ENV_URI. -// -// The purpose of returning an empty vector (instead of nullptr) is that gtest -// ValuesIn() will skip running tests when given an empty collection. -std::vector GetCustomEnvs() { - std::vector res; - const char* uri = getenv("TEST_ENV_URI"); - if (uri != nullptr) { - res.push_back(&GetTestEnv); - } - uri = getenv("TEST_FS_URI"); - if (uri != nullptr) { - res.push_back(&GetTestFS); - } - return res; -} - -} // anonymous namespace - -INSTANTIATE_TEST_CASE_P(CustomEnv, EnvBasicTestWithParam, - ::testing::ValuesIn(GetCustomEnvs())); - -INSTANTIATE_TEST_CASE_P(CustomEnv, EnvMoreTestWithParam, - ::testing::ValuesIn(GetCustomEnvs())); - -TEST_P(EnvBasicTestWithParam, Basics) { - uint64_t file_size; - std::unique_ptr writable_file; - std::vector children; - - // Check that the directory is empty. - ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/non_existent")); - ASSERT_TRUE(!env_->GetFileSize(test_dir_ + "/non_existent", &file_size).ok()); - ASSERT_OK(env_->GetChildren(test_dir_, &children)); - ASSERT_EQ(0U, children.size()); - - // Create a file. - ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); - - // Check that the file exists. - ASSERT_OK(env_->FileExists(test_dir_ + "/f")); - ASSERT_OK(env_->GetFileSize(test_dir_ + "/f", &file_size)); - ASSERT_EQ(0U, file_size); - ASSERT_OK(env_->GetChildren(test_dir_, &children)); - ASSERT_EQ(1U, children.size()); - ASSERT_EQ("f", children[0]); - ASSERT_OK(env_->DeleteFile(test_dir_ + "/f")); - - // Write to the file. - ASSERT_OK( - env_->NewWritableFile(test_dir_ + "/f1", &writable_file, soptions_)); - ASSERT_OK(writable_file->Append("abc")); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); - ASSERT_OK( - env_->NewWritableFile(test_dir_ + "/f2", &writable_file, soptions_)); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); - - // Check for expected size. - ASSERT_OK(env_->GetFileSize(test_dir_ + "/f1", &file_size)); - ASSERT_EQ(3U, file_size); - - // Check that renaming works. - ASSERT_TRUE( - !env_->RenameFile(test_dir_ + "/non_existent", test_dir_ + "/g").ok()); - ASSERT_OK(env_->RenameFile(test_dir_ + "/f1", test_dir_ + "/g")); - ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/f1")); - ASSERT_OK(env_->FileExists(test_dir_ + "/g")); - ASSERT_OK(env_->GetFileSize(test_dir_ + "/g", &file_size)); - ASSERT_EQ(3U, file_size); - - // Check that renaming overwriting works - ASSERT_OK(env_->RenameFile(test_dir_ + "/f2", test_dir_ + "/g")); - ASSERT_OK(env_->GetFileSize(test_dir_ + "/g", &file_size)); - ASSERT_EQ(0U, file_size); - - // Check that opening non-existent file fails. - std::unique_ptr seq_file; - std::unique_ptr rand_file; - ASSERT_TRUE(!env_->NewSequentialFile(test_dir_ + "/non_existent", &seq_file, - soptions_) - .ok()); - ASSERT_TRUE(!seq_file); - ASSERT_NOK(env_->NewRandomAccessFile(test_dir_ + "/non_existent", &rand_file, - soptions_)); - ASSERT_TRUE(!rand_file); - - // Check that deleting works. - ASSERT_NOK(env_->DeleteFile(test_dir_ + "/non_existent")); - ASSERT_OK(env_->DeleteFile(test_dir_ + "/g")); - ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/g")); - ASSERT_OK(env_->GetChildren(test_dir_, &children)); - ASSERT_EQ(0U, children.size()); - Status s = env_->GetChildren(test_dir_ + "/non_existent", &children); - ASSERT_TRUE(s.IsNotFound()); -} - -TEST_P(EnvBasicTestWithParam, ReadWrite) { - std::unique_ptr writable_file; - std::unique_ptr seq_file; - std::unique_ptr rand_file; - Slice result; - char scratch[100]; - - ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); - ASSERT_OK(writable_file->Append("hello ")); - ASSERT_OK(writable_file->Append("world")); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); - - // Read sequentially. - ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_)); - ASSERT_OK(seq_file->Read(5, &result, scratch)); // Read "hello". - ASSERT_EQ(0, result.compare("hello")); - ASSERT_OK(seq_file->Skip(1)); - ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Read "world". - ASSERT_EQ(0, result.compare("world")); - ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Try reading past EOF. - ASSERT_EQ(0U, result.size()); - ASSERT_OK(seq_file->Skip(100)); // Try to skip past end of file. - ASSERT_OK(seq_file->Read(1000, &result, scratch)); - ASSERT_EQ(0U, result.size()); - - // Random reads. - ASSERT_OK(env_->NewRandomAccessFile(test_dir_ + "/f", &rand_file, soptions_)); - ASSERT_OK(rand_file->Read(6, 5, &result, scratch)); // Read "world". - ASSERT_EQ(0, result.compare("world")); - ASSERT_OK(rand_file->Read(0, 5, &result, scratch)); // Read "hello". - ASSERT_EQ(0, result.compare("hello")); - ASSERT_OK(rand_file->Read(10, 100, &result, scratch)); // Read "d". - ASSERT_EQ(0, result.compare("d")); - - // Too high offset. - ASSERT_TRUE(rand_file->Read(1000, 5, &result, scratch).ok()); -} - -TEST_P(EnvBasicTestWithParam, Misc) { - std::unique_ptr writable_file; - ASSERT_OK(env_->NewWritableFile(test_dir_ + "/b", &writable_file, soptions_)); - - // These are no-ops, but we test they return success. - ASSERT_OK(writable_file->Sync()); - ASSERT_OK(writable_file->Flush()); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); -} - -TEST_P(EnvBasicTestWithParam, LargeWrite) { - const size_t kWriteSize = 300 * 1024; - char* scratch = new char[kWriteSize * 2]; - - std::string write_data; - for (size_t i = 0; i < kWriteSize; ++i) { - write_data.append(1, static_cast(i)); - } - - std::unique_ptr writable_file; - ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); - ASSERT_OK(writable_file->Append("foo")); - ASSERT_OK(writable_file->Append(write_data)); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); - - std::unique_ptr seq_file; - Slice result; - ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_)); - ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo". - ASSERT_EQ(0, result.compare("foo")); - - size_t read = 0; - std::string read_data; - while (read < kWriteSize) { - ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch)); - read_data.append(result.data(), result.size()); - read += result.size(); - } - ASSERT_TRUE(write_data == read_data); - delete[] scratch; -} - -TEST_P(EnvMoreTestWithParam, GetModTime) { - ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/dir1")); - uint64_t mtime1 = 0x0; - ASSERT_OK(env_->GetFileModificationTime(test_dir_ + "/dir1", &mtime1)); -} - -TEST_P(EnvMoreTestWithParam, MakeDir) { - ASSERT_OK(env_->CreateDir(test_dir_ + "/j")); - ASSERT_OK(env_->FileExists(test_dir_ + "/j")); - std::vector children; - ASSERT_OK(env_->GetChildren(test_dir_, &children)); - ASSERT_EQ(1U, children.size()); - // fail because file already exists - ASSERT_TRUE(!env_->CreateDir(test_dir_ + "/j").ok()); - ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/j")); - ASSERT_OK(env_->DeleteDir(test_dir_ + "/j")); - ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/j")); -} - -TEST_P(EnvMoreTestWithParam, GetChildren) { - // empty folder returns empty vector - std::vector children; - std::vector childAttr; - ASSERT_OK(env_->CreateDirIfMissing(test_dir_)); - ASSERT_OK(env_->GetChildren(test_dir_, &children)); - ASSERT_OK(env_->FileExists(test_dir_)); - ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr)); - ASSERT_EQ(0U, children.size()); - ASSERT_EQ(0U, childAttr.size()); - - // folder with contents returns relative path to test dir - ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/niu")); - ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/you")); - ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/guo")); - ASSERT_OK(env_->GetChildren(test_dir_, &children)); - ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr)); - ASSERT_EQ(3U, children.size()); - ASSERT_EQ(3U, childAttr.size()); - for (auto each : children) { - env_->DeleteDir(test_dir_ + "/" + each).PermitUncheckedError(); - } // necessary for default POSIX env - - // non-exist directory returns IOError - ASSERT_OK(env_->DeleteDir(test_dir_)); - ASSERT_NOK(env_->FileExists(test_dir_)); - ASSERT_NOK(env_->GetChildren(test_dir_, &children)); - ASSERT_NOK(env_->GetChildrenFileAttributes(test_dir_, &childAttr)); - - // if dir is a file, returns IOError - ASSERT_OK(env_->CreateDir(test_dir_)); - std::unique_ptr writable_file; - ASSERT_OK( - env_->NewWritableFile(test_dir_ + "/file", &writable_file, soptions_)); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); - ASSERT_NOK(env_->GetChildren(test_dir_ + "/file", &children)); - ASSERT_EQ(0U, children.size()); -} - -TEST_P(EnvMoreTestWithParam, GetChildrenIgnoresDotAndDotDot) { - auto* env = Env::Default(); - ASSERT_OK(env->CreateDirIfMissing(test_dir_)); - - // Create a single file - std::string path = test_dir_; - const EnvOptions soptions; -#ifdef OS_WIN - path.append("\\test_file"); -#else - path.append("/test_file"); -#endif - std::string data("test data"); - std::unique_ptr file; - ASSERT_OK(env->NewWritableFile(path, &file, soptions)); - ASSERT_OK(file->Append("test data")); - - // get the children - std::vector result; - ASSERT_OK(env->GetChildren(test_dir_, &result)); - - // expect only one file named `test_data`, i.e. no `.` or `..` names - ASSERT_EQ(result.size(), 1); - ASSERT_EQ(result.at(0), "test_file"); -} - -} // namespace ROCKSDB_NAMESPACE -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/env/env_test.cc b/env/env_test.cc deleted file mode 100644 index 2f748846b..000000000 --- a/env/env_test.cc +++ /dev/null @@ -1,3546 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifndef OS_WIN -#include -#endif - -#if defined(ROCKSDB_IOURING_PRESENT) -#include -#include -#endif - -#include - -#include -#include -#include -#include - -#ifdef OS_LINUX -#include -#include -#include -#include -#include -#endif - -#ifdef ROCKSDB_FALLOCATE_PRESENT -#include -#endif - -#include "db/db_impl/db_impl.h" -#include "env/emulated_clock.h" -#include "env/env_chroot.h" -#include "env/env_encryption_ctr.h" -#include "env/fs_readonly.h" -#include "env/mock_env.h" -#include "env/unique_id_gen.h" -#include "logging/log_buffer.h" -#include "logging/logging.h" -#include "options/options_helper.h" -#include "port/malloc.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/convenience.h" -#include "rocksdb/env.h" -#include "rocksdb/env_encryption.h" -#include "rocksdb/file_system.h" -#include "rocksdb/system_clock.h" -#include "rocksdb/utilities/object_registry.h" -#include "test_util/mock_time_env.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding.h" -#include "util/crc32c.h" -#include "util/mutexlock.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/counted_fs.h" -#include "utilities/env_timed.h" -#include "utilities/fault_injection_env.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -using port::kPageSize; - -static const int kDelayMicros = 100000; - -struct Deleter { - explicit Deleter(void (*fn)(void*)) : fn_(fn) {} - - void operator()(void* ptr) { - assert(fn_); - assert(ptr); - (*fn_)(ptr); - } - - void (*fn_)(void*); -}; - -extern "C" bool RocksDbIOUringEnable() { return true; } - -std::unique_ptr NewAligned(const size_t size, const char ch) { - char* ptr = nullptr; -#ifdef OS_WIN - if (nullptr == - (ptr = reinterpret_cast(_aligned_malloc(size, kPageSize)))) { - return std::unique_ptr(nullptr, Deleter(_aligned_free)); - } - std::unique_ptr uptr(ptr, Deleter(_aligned_free)); -#else - if (posix_memalign(reinterpret_cast(&ptr), kPageSize, size) != 0) { - return std::unique_ptr(nullptr, Deleter(free)); - } - std::unique_ptr uptr(ptr, Deleter(free)); -#endif - memset(uptr.get(), ch, size); - return uptr; -} - -class EnvPosixTest : public testing::Test { - private: - port::Mutex mu_; - std::string events_; - - public: - Env* env_; - bool direct_io_; - EnvPosixTest() : env_(Env::Default()), direct_io_(false) {} - ~EnvPosixTest() { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({}); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } -}; - -class EnvPosixTestWithParam - : public EnvPosixTest, - public ::testing::WithParamInterface> { - public: - EnvPosixTestWithParam() { - std::pair param_pair = GetParam(); - env_ = param_pair.first; - direct_io_ = param_pair.second; - } - - void WaitThreadPoolsEmpty() { - // Wait until the thread pools are empty. - while (env_->GetThreadPoolQueueLen(Env::Priority::LOW) != 0) { - Env::Default()->SleepForMicroseconds(kDelayMicros); - } - while (env_->GetThreadPoolQueueLen(Env::Priority::HIGH) != 0) { - Env::Default()->SleepForMicroseconds(kDelayMicros); - } - } - - ~EnvPosixTestWithParam() override { WaitThreadPoolsEmpty(); } -}; - -static void SetBool(void* ptr) { - reinterpret_cast*>(ptr)->store(true); -} - -TEST_F(EnvPosixTest, DISABLED_RunImmediately) { - for (int pri = Env::BOTTOM; pri < Env::TOTAL; ++pri) { - std::atomic called(false); - env_->SetBackgroundThreads(1, static_cast(pri)); - env_->Schedule(&SetBool, &called, static_cast(pri)); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_TRUE(called.load()); - } -} - -TEST_F(EnvPosixTest, RunEventually) { - std::atomic called(false); - env_->StartThread(&SetBool, &called); - env_->WaitForJoin(); - ASSERT_TRUE(called.load()); -} - -#ifdef OS_WIN -TEST_F(EnvPosixTest, AreFilesSame) { - { - bool tmp; - if (env_->AreFilesSame("", "", &tmp).IsNotSupported()) { - fprintf(stderr, - "skipping EnvBasicTestWithParam.AreFilesSame due to " - "unsupported Env::AreFilesSame\n"); - return; - } - } - - const EnvOptions soptions; - auto* env = Env::Default(); - std::string same_file_name = test::PerThreadDBPath(env, "same_file"); - std::string same_file_link_name = same_file_name + "_link"; - - std::unique_ptr same_file; - ASSERT_OK(env->NewWritableFile(same_file_name, &same_file, soptions)); - same_file->Append("random_data"); - ASSERT_OK(same_file->Flush()); - same_file.reset(); - - ASSERT_OK(env->LinkFile(same_file_name, same_file_link_name)); - bool result = false; - ASSERT_OK(env->AreFilesSame(same_file_name, same_file_link_name, &result)); - ASSERT_TRUE(result); -} -#endif - -#ifdef OS_LINUX -TEST_F(EnvPosixTest, DISABLED_FilePermission) { - // Only works for Linux environment - if (env_ == Env::Default()) { - EnvOptions soptions; - std::vector fileNames{ - test::PerThreadDBPath(env_, "testfile"), - test::PerThreadDBPath(env_, "testfile1")}; - std::unique_ptr wfile; - ASSERT_OK(env_->NewWritableFile(fileNames[0], &wfile, soptions)); - ASSERT_OK(env_->NewWritableFile(fileNames[1], &wfile, soptions)); - wfile.reset(); - std::unique_ptr rwfile; - ASSERT_OK(env_->NewRandomRWFile(fileNames[1], &rwfile, soptions)); - - struct stat sb; - for (const auto& filename : fileNames) { - if (::stat(filename.c_str(), &sb) == 0) { - ASSERT_EQ(sb.st_mode & 0777, 0644); - } - ASSERT_OK(env_->DeleteFile(filename)); - } - - env_->SetAllowNonOwnerAccess(false); - ASSERT_OK(env_->NewWritableFile(fileNames[0], &wfile, soptions)); - ASSERT_OK(env_->NewWritableFile(fileNames[1], &wfile, soptions)); - wfile.reset(); - ASSERT_OK(env_->NewRandomRWFile(fileNames[1], &rwfile, soptions)); - - for (const auto& filename : fileNames) { - if (::stat(filename.c_str(), &sb) == 0) { - ASSERT_EQ(sb.st_mode & 0777, 0600); - } - ASSERT_OK(env_->DeleteFile(filename)); - } - } -} - -TEST_F(EnvPosixTest, LowerThreadPoolCpuPriority) { - std::atomic from_priority(CpuPriority::kNormal); - std::atomic to_priority(CpuPriority::kNormal); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ThreadPoolImpl::BGThread::BeforeSetCpuPriority", [&](void* pri) { - from_priority.store(*reinterpret_cast(pri)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ThreadPoolImpl::BGThread::AfterSetCpuPriority", [&](void* pri) { - to_priority.store(*reinterpret_cast(pri)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - env_->SetBackgroundThreads(1, Env::BOTTOM); - env_->SetBackgroundThreads(1, Env::HIGH); - - auto RunTask = [&](Env::Priority pool) { - std::atomic called(false); - env_->Schedule(&SetBool, &called, pool); - for (int i = 0; i < kDelayMicros; i++) { - if (called.load()) { - break; - } - Env::Default()->SleepForMicroseconds(1); - } - ASSERT_TRUE(called.load()); - }; - - { - // Same priority, no-op. - env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, - CpuPriority::kNormal) - .PermitUncheckedError(); - RunTask(Env::Priority::BOTTOM); - ASSERT_EQ(from_priority, CpuPriority::kNormal); - ASSERT_EQ(to_priority, CpuPriority::kNormal); - } - - { - // Higher priority, no-op. - env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, CpuPriority::kHigh) - .PermitUncheckedError(); - RunTask(Env::Priority::BOTTOM); - ASSERT_EQ(from_priority, CpuPriority::kNormal); - ASSERT_EQ(to_priority, CpuPriority::kNormal); - } - - { - // Lower priority from kNormal -> kLow. - env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, CpuPriority::kLow) - .PermitUncheckedError(); - RunTask(Env::Priority::BOTTOM); - ASSERT_EQ(from_priority, CpuPriority::kNormal); - ASSERT_EQ(to_priority, CpuPriority::kLow); - } - - { - // Lower priority from kLow -> kIdle. - env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, CpuPriority::kIdle) - .PermitUncheckedError(); - RunTask(Env::Priority::BOTTOM); - ASSERT_EQ(from_priority, CpuPriority::kLow); - ASSERT_EQ(to_priority, CpuPriority::kIdle); - } - - { - // Lower priority from kNormal -> kIdle for another pool. - env_->LowerThreadPoolCPUPriority(Env::Priority::HIGH, CpuPriority::kIdle) - .PermitUncheckedError(); - RunTask(Env::Priority::HIGH); - ASSERT_EQ(from_priority, CpuPriority::kNormal); - ASSERT_EQ(to_priority, CpuPriority::kIdle); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} -#endif - -TEST_F(EnvPosixTest, MemoryMappedFileBuffer) { - const int kFileBytes = 1 << 15; // 32 KB - std::string expected_data; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - { - std::unique_ptr wfile; - const EnvOptions soptions; - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - - Random rnd(301); - expected_data = rnd.RandomString(kFileBytes); - ASSERT_OK(wfile->Append(expected_data)); - } - - std::unique_ptr mmap_buffer; - Status status = env_->NewMemoryMappedFileBuffer(fname, &mmap_buffer); - // it should be supported at least on linux -#if !defined(OS_LINUX) - if (status.IsNotSupported()) { - fprintf(stderr, - "skipping EnvPosixTest.MemoryMappedFileBuffer due to " - "unsupported Env::NewMemoryMappedFileBuffer\n"); - return; - } -#endif // !defined(OS_LINUX) - - ASSERT_OK(status); - ASSERT_NE(nullptr, mmap_buffer.get()); - ASSERT_NE(nullptr, mmap_buffer->GetBase()); - ASSERT_EQ(kFileBytes, mmap_buffer->GetLen()); - std::string actual_data(reinterpret_cast(mmap_buffer->GetBase()), - mmap_buffer->GetLen()); - ASSERT_EQ(expected_data, actual_data); -} - -#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION -TEST_F(EnvPosixTest, LoadRocksDBLibrary) { - std::shared_ptr library; - std::function function; - Status status = env_->LoadLibrary("no-such-library", "", &library); - ASSERT_NOK(status); - ASSERT_EQ(nullptr, library.get()); - status = env_->LoadLibrary("rocksdb", "", &library); - if (status.ok()) { // If we have can find a rocksdb shared library - ASSERT_NE(nullptr, library.get()); - ASSERT_OK(library->LoadFunction("rocksdb_create_default_env", - &function)); // from C definition - ASSERT_NE(nullptr, function); - ASSERT_NOK(library->LoadFunction("no-such-method", &function)); - ASSERT_EQ(nullptr, function); - ASSERT_OK(env_->LoadLibrary(library->Name(), "", &library)); - } else { - ASSERT_EQ(nullptr, library.get()); - } -} -#endif // !ROCKSDB_NO_DYNAMIC_EXTENSION - -#if !defined(OS_WIN) && !defined(ROCKSDB_NO_DYNAMIC_EXTENSION) -TEST_F(EnvPosixTest, LoadRocksDBLibraryWithSearchPath) { - std::shared_ptr library; - std::function function; - ASSERT_NOK(env_->LoadLibrary("no-such-library", "/tmp", &library)); - ASSERT_EQ(nullptr, library.get()); - ASSERT_NOK(env_->LoadLibrary("dl", "/tmp", &library)); - ASSERT_EQ(nullptr, library.get()); - Status status = env_->LoadLibrary("rocksdb", "/tmp:./", &library); - if (status.ok()) { - ASSERT_NE(nullptr, library.get()); - ASSERT_OK(env_->LoadLibrary(library->Name(), "", &library)); - } - char buff[1024]; - std::string cwd = getcwd(buff, sizeof(buff)); - - status = env_->LoadLibrary("rocksdb", "/tmp:" + cwd, &library); - if (status.ok()) { - ASSERT_NE(nullptr, library.get()); - ASSERT_OK(env_->LoadLibrary(library->Name(), "", &library)); - } -} -#endif // !OS_WIN && !ROCKSDB_NO_DYNAMIC_EXTENSION - -TEST_P(EnvPosixTestWithParam, UnSchedule) { - std::atomic called(false); - env_->SetBackgroundThreads(1, Env::LOW); - - /* Block the low priority queue */ - test::SleepingBackgroundTask sleeping_task, sleeping_task1; - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task, - Env::Priority::LOW); - - /* Schedule another task */ - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task1, - Env::Priority::LOW, &sleeping_task1); - - /* Remove it with a different tag */ - ASSERT_EQ(0, env_->UnSchedule(&called, Env::Priority::LOW)); - - /* Remove it from the queue with the right tag */ - ASSERT_EQ(1, env_->UnSchedule(&sleeping_task1, Env::Priority::LOW)); - - // Unblock background thread - sleeping_task.WakeUp(); - - /* Schedule another task */ - env_->Schedule(&SetBool, &called); - for (int i = 0; i < kDelayMicros; i++) { - if (called.load()) { - break; - } - Env::Default()->SleepForMicroseconds(1); - } - ASSERT_TRUE(called.load()); - - ASSERT_TRUE(!sleeping_task.IsSleeping() && !sleeping_task1.IsSleeping()); - WaitThreadPoolsEmpty(); -} - -// This tests assumes that the last scheduled -// task will run last. In fact, in the allotted -// sleeping time nothing may actually run or they may -// run in any order. The purpose of the test is unclear. -#ifndef OS_WIN -TEST_P(EnvPosixTestWithParam, RunMany) { - env_->SetBackgroundThreads(1, Env::LOW); - std::atomic last_id(0); - - struct CB { - std::atomic* last_id_ptr; // Pointer to shared slot - int id; // Order# for the execution of this callback - - CB(std::atomic* p, int i) : last_id_ptr(p), id(i) {} - - static void Run(void* v) { - CB* cb = reinterpret_cast(v); - int cur = cb->last_id_ptr->load(); - ASSERT_EQ(cb->id - 1, cur); - cb->last_id_ptr->store(cb->id); - } - }; - - // Schedule in different order than start time - CB cb1(&last_id, 1); - CB cb2(&last_id, 2); - CB cb3(&last_id, 3); - CB cb4(&last_id, 4); - env_->Schedule(&CB::Run, &cb1); - env_->Schedule(&CB::Run, &cb2); - env_->Schedule(&CB::Run, &cb3); - env_->Schedule(&CB::Run, &cb4); - // thread-pool pops a thread function and then run the function, which may - // cause threadpool is empty but the last function is still running. Add a - // dummy function at the end, to make sure the last callback is finished - // before threadpool is empty. - struct DummyCB { - static void Run(void*) {} - }; - env_->Schedule(&DummyCB::Run, nullptr); - - WaitThreadPoolsEmpty(); - ASSERT_EQ(4, last_id.load(std::memory_order_acquire)); -} -#endif - -struct State { - port::Mutex mu; - int val; - int num_running; -}; - -static void ThreadBody(void* arg) { - State* s = reinterpret_cast(arg); - s->mu.Lock(); - s->val += 1; - s->num_running -= 1; - s->mu.Unlock(); -} - -TEST_P(EnvPosixTestWithParam, StartThread) { - State state; - state.val = 0; - state.num_running = 3; - for (int i = 0; i < 3; i++) { - env_->StartThread(&ThreadBody, &state); - } - while (true) { - state.mu.Lock(); - int num = state.num_running; - state.mu.Unlock(); - if (num == 0) { - break; - } - Env::Default()->SleepForMicroseconds(kDelayMicros); - } - ASSERT_EQ(state.val, 3); - WaitThreadPoolsEmpty(); -} - -TEST_P(EnvPosixTestWithParam, TwoPools) { - // Data structures to signal tasks to run. - port::Mutex mutex; - port::CondVar cv(&mutex); - bool should_start = false; - - class CB { - public: - CB(const std::string& pool_name, int pool_size, port::Mutex* trigger_mu, - port::CondVar* trigger_cv, bool* _should_start) - : mu_(), - num_running_(0), - num_finished_(0), - pool_size_(pool_size), - pool_name_(pool_name), - trigger_mu_(trigger_mu), - trigger_cv_(trigger_cv), - should_start_(_should_start) {} - - static void Run(void* v) { - CB* cb = reinterpret_cast(v); - cb->Run(); - } - - void Run() { - { - MutexLock l(&mu_); - num_running_++; - // make sure we don't have more than pool_size_ jobs running. - ASSERT_LE(num_running_, pool_size_.load()); - } - - { - MutexLock l(trigger_mu_); - while (!(*should_start_)) { - trigger_cv_->Wait(); - } - } - - { - MutexLock l(&mu_); - num_running_--; - num_finished_++; - } - } - - int NumFinished() { - MutexLock l(&mu_); - return num_finished_; - } - - void Reset(int pool_size) { - pool_size_.store(pool_size); - num_finished_ = 0; - } - - private: - port::Mutex mu_; - int num_running_; - int num_finished_; - std::atomic pool_size_; - std::string pool_name_; - port::Mutex* trigger_mu_; - port::CondVar* trigger_cv_; - bool* should_start_; - }; - - const int kLowPoolSize = 2; - const int kHighPoolSize = 4; - const int kJobs = 8; - - CB low_pool_job("low", kLowPoolSize, &mutex, &cv, &should_start); - CB high_pool_job("high", kHighPoolSize, &mutex, &cv, &should_start); - - env_->SetBackgroundThreads(kLowPoolSize); - env_->SetBackgroundThreads(kHighPoolSize, Env::Priority::HIGH); - - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - // schedule same number of jobs in each pool - for (int i = 0; i < kJobs; i++) { - env_->Schedule(&CB::Run, &low_pool_job); - env_->Schedule(&CB::Run, &high_pool_job, Env::Priority::HIGH); - } - // Wait a short while for the jobs to be dispatched. - int sleep_count = 0; - while ((unsigned int)(kJobs - kLowPoolSize) != - env_->GetThreadPoolQueueLen(Env::Priority::LOW) || - (unsigned int)(kJobs - kHighPoolSize) != - env_->GetThreadPoolQueueLen(Env::Priority::HIGH)) { - env_->SleepForMicroseconds(kDelayMicros); - if (++sleep_count > 100) { - break; - } - } - - ASSERT_EQ((unsigned int)(kJobs - kLowPoolSize), - env_->GetThreadPoolQueueLen()); - ASSERT_EQ((unsigned int)(kJobs - kLowPoolSize), - env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - ASSERT_EQ((unsigned int)(kJobs - kHighPoolSize), - env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - // Trigger jobs to run. - { - MutexLock l(&mutex); - should_start = true; - cv.SignalAll(); - } - - // wait for all jobs to finish - while (low_pool_job.NumFinished() < kJobs || - high_pool_job.NumFinished() < kJobs) { - env_->SleepForMicroseconds(kDelayMicros); - } - - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - // Hold jobs to schedule; - should_start = false; - - // call IncBackgroundThreadsIfNeeded to two pools. One increasing and - // the other decreasing - env_->IncBackgroundThreadsIfNeeded(kLowPoolSize - 1, Env::Priority::LOW); - env_->IncBackgroundThreadsIfNeeded(kHighPoolSize + 1, Env::Priority::HIGH); - high_pool_job.Reset(kHighPoolSize + 1); - low_pool_job.Reset(kLowPoolSize); - - // schedule same number of jobs in each pool - for (int i = 0; i < kJobs; i++) { - env_->Schedule(&CB::Run, &low_pool_job); - env_->Schedule(&CB::Run, &high_pool_job, Env::Priority::HIGH); - } - // Wait a short while for the jobs to be dispatched. - sleep_count = 0; - while ((unsigned int)(kJobs - kLowPoolSize) != - env_->GetThreadPoolQueueLen(Env::Priority::LOW) || - (unsigned int)(kJobs - (kHighPoolSize + 1)) != - env_->GetThreadPoolQueueLen(Env::Priority::HIGH)) { - env_->SleepForMicroseconds(kDelayMicros); - if (++sleep_count > 100) { - break; - } - } - ASSERT_EQ((unsigned int)(kJobs - kLowPoolSize), - env_->GetThreadPoolQueueLen()); - ASSERT_EQ((unsigned int)(kJobs - kLowPoolSize), - env_->GetThreadPoolQueueLen(Env::Priority::LOW)); - ASSERT_EQ((unsigned int)(kJobs - (kHighPoolSize + 1)), - env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - // Trigger jobs to run. - { - MutexLock l(&mutex); - should_start = true; - cv.SignalAll(); - } - - // wait for all jobs to finish - while (low_pool_job.NumFinished() < kJobs || - high_pool_job.NumFinished() < kJobs) { - env_->SleepForMicroseconds(kDelayMicros); - } - - env_->SetBackgroundThreads(kHighPoolSize, Env::Priority::HIGH); - WaitThreadPoolsEmpty(); -} - -TEST_P(EnvPosixTestWithParam, DecreaseNumBgThreads) { - constexpr int kWaitMicros = 60000000; // 1min - - std::vector tasks(10); - - // Set number of thread to 1 first. - env_->SetBackgroundThreads(1, Env::Priority::HIGH); - - // Schedule 3 tasks. 0 running; Task 1, 2 waiting. - for (size_t i = 0; i < 3; i++) { - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &tasks[i], - Env::Priority::HIGH); - } - ASSERT_FALSE(tasks[0].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_EQ(2U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(!tasks[1].IsSleeping()); - ASSERT_TRUE(!tasks[2].IsSleeping()); - - // Increase to 2 threads. Task 0, 1 running; 2 waiting - env_->SetBackgroundThreads(2, Env::Priority::HIGH); - ASSERT_FALSE(tasks[1].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_EQ(1U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(tasks[1].IsSleeping()); - ASSERT_TRUE(!tasks[2].IsSleeping()); - - // Shrink back to 1 thread. Still task 0, 1 running, 2 waiting - env_->SetBackgroundThreads(1, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_EQ(1U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(tasks[1].IsSleeping()); - ASSERT_TRUE(!tasks[2].IsSleeping()); - - // The last task finishes. Task 0 running, 2 waiting. - tasks[1].WakeUp(); - ASSERT_FALSE(tasks[1].TimedWaitUntilDone(kWaitMicros)); - ASSERT_EQ(1U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(!tasks[1].IsSleeping()); - ASSERT_TRUE(!tasks[2].IsSleeping()); - - // Increase to 5 threads. Task 0 and 2 running. - env_->SetBackgroundThreads(5, Env::Priority::HIGH); - ASSERT_FALSE(tasks[2].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(!tasks[1].IsSleeping()); - ASSERT_TRUE(tasks[2].IsSleeping()); - - // Change number of threads a couple of times while there is no sufficient - // tasks. - env_->SetBackgroundThreads(7, Env::Priority::HIGH); - tasks[2].WakeUp(); - ASSERT_FALSE(tasks[2].TimedWaitUntilDone(kWaitMicros)); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - env_->SetBackgroundThreads(3, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - env_->SetBackgroundThreads(4, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - env_->SetBackgroundThreads(5, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - env_->SetBackgroundThreads(4, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - Env::Default()->SleepForMicroseconds(kDelayMicros * 50); - - // Enqueue 5 more tasks. Thread pool size now is 4. - // Task 0, 3, 4, 5 running;6, 7 waiting. - for (size_t i = 3; i < 8; i++) { - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &tasks[i], - Env::Priority::HIGH); - } - for (size_t i = 3; i <= 5; i++) { - ASSERT_FALSE(tasks[i].TimedWaitUntilSleeping(kWaitMicros)); - } - ASSERT_EQ(2U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(!tasks[1].IsSleeping()); - ASSERT_TRUE(!tasks[2].IsSleeping()); - ASSERT_TRUE(tasks[3].IsSleeping()); - ASSERT_TRUE(tasks[4].IsSleeping()); - ASSERT_TRUE(tasks[5].IsSleeping()); - ASSERT_TRUE(!tasks[6].IsSleeping()); - ASSERT_TRUE(!tasks[7].IsSleeping()); - - // Wake up task 0, 3 and 4. Task 5, 6, 7 running. - tasks[0].WakeUp(); - tasks[3].WakeUp(); - tasks[4].WakeUp(); - - for (size_t i = 5; i < 8; i++) { - ASSERT_FALSE(tasks[i].TimedWaitUntilSleeping(kWaitMicros)); - } - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - for (size_t i = 5; i < 8; i++) { - ASSERT_TRUE(tasks[i].IsSleeping()); - } - - // Shrink back to 1 thread. Still task 5, 6, 7 running - env_->SetBackgroundThreads(1, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_TRUE(tasks[5].IsSleeping()); - ASSERT_TRUE(tasks[6].IsSleeping()); - ASSERT_TRUE(tasks[7].IsSleeping()); - - // Wake up task 6. Task 5, 7 running - tasks[6].WakeUp(); - ASSERT_FALSE(tasks[6].TimedWaitUntilDone(kWaitMicros)); - ASSERT_TRUE(tasks[5].IsSleeping()); - ASSERT_TRUE(!tasks[6].IsSleeping()); - ASSERT_TRUE(tasks[7].IsSleeping()); - - // Wake up threads 7. Task 5 running - tasks[7].WakeUp(); - ASSERT_FALSE(tasks[7].TimedWaitUntilDone(kWaitMicros)); - ASSERT_TRUE(!tasks[7].IsSleeping()); - - // Enqueue thread 8 and 9. Task 5 running; one of 8, 9 might be running. - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &tasks[8], - Env::Priority::HIGH); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &tasks[9], - Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_GT(env_->GetThreadPoolQueueLen(Env::Priority::HIGH), (unsigned int)0); - ASSERT_TRUE(!tasks[8].IsSleeping() || !tasks[9].IsSleeping()); - - // Increase to 4 threads. Task 5, 8, 9 running. - env_->SetBackgroundThreads(4, Env::Priority::HIGH); - Env::Default()->SleepForMicroseconds(kDelayMicros); - ASSERT_EQ((unsigned int)0, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[8].IsSleeping()); - ASSERT_TRUE(tasks[9].IsSleeping()); - - // Shrink to 1 thread - env_->SetBackgroundThreads(1, Env::Priority::HIGH); - - // Wake up thread 9. - tasks[9].WakeUp(); - ASSERT_FALSE(tasks[9].TimedWaitUntilDone(kWaitMicros)); - ASSERT_TRUE(!tasks[9].IsSleeping()); - ASSERT_TRUE(tasks[8].IsSleeping()); - - // Wake up thread 8 - tasks[8].WakeUp(); - ASSERT_FALSE(tasks[8].TimedWaitUntilDone(kWaitMicros)); - ASSERT_TRUE(!tasks[8].IsSleeping()); - - // Wake up the last thread - tasks[5].WakeUp(); - ASSERT_FALSE(tasks[5].TimedWaitUntilDone(kWaitMicros)); - WaitThreadPoolsEmpty(); -} - -TEST_P(EnvPosixTestWithParam, ReserveThreads) { - // Initialize the background thread to 1 in case other threads exist - // from the last unit test - env_->SetBackgroundThreads(1, Env::Priority::HIGH); - ASSERT_EQ(env_->GetBackgroundThreads(Env::HIGH), 1); - constexpr int kWaitMicros = 10000000; // 10seconds - std::vector tasks(4); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - // Set the sync point to ensure thread 0 can terminate - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ThreadPoolImpl::BGThread::Termination:th0", - "EnvTest::ReserveThreads:0"}}); - // Empty the thread pool to ensure all the threads can start later - env_->SetBackgroundThreads(0, Env::Priority::HIGH); - TEST_SYNC_POINT("EnvTest::ReserveThreads:0"); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - // Set the sync point to ensure threads start and pass the sync point - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ThreadPoolImpl::BGThread::Start:th0", "EnvTest::ReserveThreads:1"}, - {"ThreadPoolImpl::BGThread::Start:th1", "EnvTest::ReserveThreads:2"}, - {"ThreadPoolImpl::BGThread::Start:th2", "EnvTest::ReserveThreads:3"}, - {"ThreadPoolImpl::BGThread::Start:th3", "EnvTest::ReserveThreads:4"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Set number of thread to 3 first. - env_->SetBackgroundThreads(3, Env::Priority::HIGH); - ASSERT_EQ(env_->GetBackgroundThreads(Env::HIGH), 3); - // Add sync points to ensure all 3 threads start - TEST_SYNC_POINT("EnvTest::ReserveThreads:1"); - TEST_SYNC_POINT("EnvTest::ReserveThreads:2"); - TEST_SYNC_POINT("EnvTest::ReserveThreads:3"); - // Reserve 2 threads - ASSERT_EQ(2, env_->ReserveThreads(2, Env::Priority::HIGH)); - - // Schedule 3 tasks. Task 0 running (in this context, doing - // SleepingBackgroundTask); Task 1, 2 waiting; 3 reserved threads. - for (size_t i = 0; i < 3; i++) { - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &tasks[i], - Env::Priority::HIGH); - } - ASSERT_FALSE(tasks[0].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_EQ(2U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[0].IsSleeping()); - ASSERT_TRUE(!tasks[1].IsSleeping()); - ASSERT_TRUE(!tasks[2].IsSleeping()); - - // Release 2 threads. Task 0, 1, 2 running; 0 reserved thread. - ASSERT_EQ(2, env_->ReleaseThreads(2, Env::Priority::HIGH)); - ASSERT_FALSE(tasks[1].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_FALSE(tasks[2].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(tasks[1].IsSleeping()); - ASSERT_TRUE(tasks[2].IsSleeping()); - // No more threads can be reserved - ASSERT_EQ(0, env_->ReserveThreads(3, Env::Priority::HIGH)); - // Expand the number of background threads so that the last thread - // is waiting - env_->SetBackgroundThreads(4, Env::Priority::HIGH); - // Add sync point to ensure the 4th thread starts - TEST_SYNC_POINT("EnvTest::ReserveThreads:4"); - // As the thread pool is expanded, we can reserve one more thread - ASSERT_EQ(1, env_->ReserveThreads(3, Env::Priority::HIGH)); - // No more threads can be reserved - ASSERT_EQ(0, env_->ReserveThreads(3, Env::Priority::HIGH)); - - // Reset the sync points for the next iteration in BGThread or the - // next time Submit() is called - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"ThreadPoolImpl::BGThread::WaitingThreadsInc", - "EnvTest::ReserveThreads:5"}, - {"ThreadPoolImpl::BGThread::Termination", "EnvTest::ReserveThreads:6"}, - {"ThreadPoolImpl::Submit::Enqueue", "EnvTest::ReserveThreads:7"}}); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - tasks[0].WakeUp(); - ASSERT_FALSE(tasks[0].TimedWaitUntilDone(kWaitMicros)); - // Add sync point to ensure the number of waiting threads increases - TEST_SYNC_POINT("EnvTest::ReserveThreads:5"); - // 1 more thread can be reserved - ASSERT_EQ(1, env_->ReserveThreads(3, Env::Priority::HIGH)); - // 2 reserved threads now - - // Currently, two threads are blocked since the number of waiting - // threads is equal to the number of reserved threads (i.e., 2). - // If we reduce the number of background thread to 1, at least one thread - // will be the last excessive thread (here we have no control over the - // number of excessive threads because thread order does not - // necessarily follows the schedule order, but we ensure that the last thread - // shall not run any task by expanding the thread pool after we schedule - // the tasks), and thus they(it) become(s) unblocked, the number of waiting - // threads decreases to 0 or 1, but the number of reserved threads is still 2 - env_->SetBackgroundThreads(1, Env::Priority::HIGH); - - // Task 1,2 running; 2 reserved threads, however, in fact, we only have - // 0 or 1 waiting thread in the thread pool, proved by the - // following test, we CANNOT reserve 2 threads even though we just - // release 2 - TEST_SYNC_POINT("EnvTest::ReserveThreads:6"); - ASSERT_EQ(2, env_->ReleaseThreads(2, Env::Priority::HIGH)); - ASSERT_GT(2, env_->ReserveThreads(2, Env::Priority::HIGH)); - - // Every new task will be put into the queue at this point - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &tasks[3], - Env::Priority::HIGH); - TEST_SYNC_POINT("EnvTest::ReserveThreads:7"); - ASSERT_EQ(1U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - ASSERT_TRUE(!tasks[3].IsSleeping()); - - // Set the number of threads to 3 so that Task 3 can dequeue - env_->SetBackgroundThreads(3, Env::Priority::HIGH); - // Wakup Task 1 - tasks[1].WakeUp(); - ASSERT_FALSE(tasks[1].TimedWaitUntilDone(kWaitMicros)); - // Task 2, 3 running (Task 3 dequeue); 0 or 1 reserved thread - ASSERT_FALSE(tasks[3].TimedWaitUntilSleeping(kWaitMicros)); - ASSERT_TRUE(tasks[3].IsSleeping()); - ASSERT_EQ(0U, env_->GetThreadPoolQueueLen(Env::Priority::HIGH)); - - // At most 1 thread can be released - ASSERT_GT(2, env_->ReleaseThreads(3, Env::Priority::HIGH)); - tasks[2].WakeUp(); - ASSERT_FALSE(tasks[2].TimedWaitUntilDone(kWaitMicros)); - tasks[3].WakeUp(); - ASSERT_FALSE(tasks[3].TimedWaitUntilDone(kWaitMicros)); - WaitThreadPoolsEmpty(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -#if (defined OS_LINUX || defined OS_WIN) -namespace { -bool IsSingleVarint(const std::string& s) { - Slice slice(s); - - uint64_t v; - if (!GetVarint64(&slice, &v)) { - return false; - } - - return slice.size() == 0; -} - -bool IsUniqueIDValid(const std::string& s) { - return !s.empty() && !IsSingleVarint(s); -} - -const size_t MAX_ID_SIZE = 100; -char temp_id[MAX_ID_SIZE]; - -} // namespace - -// Determine whether we can use the FS_IOC_GETVERSION ioctl -// on a file in directory DIR. Create a temporary file therein, -// try to apply the ioctl (save that result), cleanup and -// return the result. Return true if it is supported, and -// false if anything fails. -// Note that this function "knows" that dir has just been created -// and is empty, so we create a simply-named test file: "f". -bool ioctl_support__FS_IOC_GETVERSION(const std::string& dir) { -#ifdef OS_WIN - return true; -#else - const std::string file = dir + "/f"; - int fd; - do { - fd = open(file.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644); - } while (fd < 0 && errno == EINTR); - long int version; - bool ok = (fd >= 0 && ioctl(fd, FS_IOC_GETVERSION, &version) >= 0); - - close(fd); - unlink(file.c_str()); - - return ok; -#endif -} - -// To ensure that Env::GetUniqueId-related tests work correctly, the files -// should be stored in regular storage like "hard disk" or "flash device", -// and not on a tmpfs file system (like /dev/shm and /tmp on some systems). -// Otherwise we cannot get the correct id. -// -// This function serves as the replacement for test::TmpDir(), which may be -// customized to be on a file system that doesn't work with GetUniqueId(). - -class IoctlFriendlyTmpdir { - public: - explicit IoctlFriendlyTmpdir() { - char dir_buf[100]; - - const char* fmt = "%s/rocksdb.XXXXXX"; - const char* tmp = getenv("TEST_IOCTL_FRIENDLY_TMPDIR"); - -#ifdef OS_WIN -#define rmdir _rmdir - if (tmp == nullptr) { - tmp = getenv("TMP"); - } - - snprintf(dir_buf, sizeof dir_buf, fmt, tmp); - auto result = _mktemp(dir_buf); - assert(result != nullptr); - BOOL ret = CreateDirectory(dir_buf, NULL); - assert(ret == TRUE); - dir_ = dir_buf; -#else - std::list candidate_dir_list = {"/var/tmp", "/tmp"}; - - // If $TEST_IOCTL_FRIENDLY_TMPDIR/rocksdb.XXXXXX fits, use - // $TEST_IOCTL_FRIENDLY_TMPDIR; subtract 2 for the "%s", and - // add 1 for the trailing NUL byte. - if (tmp && strlen(tmp) + strlen(fmt) - 2 + 1 <= sizeof dir_buf) { - // use $TEST_IOCTL_FRIENDLY_TMPDIR value - candidate_dir_list.push_front(tmp); - } - - for (const std::string& d : candidate_dir_list) { - snprintf(dir_buf, sizeof dir_buf, fmt, d.c_str()); - if (mkdtemp(dir_buf)) { - if (ioctl_support__FS_IOC_GETVERSION(dir_buf)) { - dir_ = dir_buf; - return; - } else { - // Diagnose ioctl-related failure only if this is the - // directory specified via that envvar. - if (tmp && tmp == d) { - fprintf(stderr, - "TEST_IOCTL_FRIENDLY_TMPDIR-specified directory is " - "not suitable: %s\n", - d.c_str()); - } - rmdir(dir_buf); // ignore failure - } - } else { - // mkdtemp failed: diagnose it, but don't give up. - fprintf(stderr, "mkdtemp(%s/...) failed: %s\n", d.c_str(), - errnoStr(errno).c_str()); - } - } - - // check if it's running test within a docker container, in which case, the - // file system inside `overlayfs` may not support FS_IOC_GETVERSION - // skip the tests - struct stat buffer; - if (stat("/.dockerenv", &buffer) == 0) { - is_supported_ = false; - return; - } - - fprintf(stderr, - "failed to find an ioctl-friendly temporary directory;" - " specify one via the TEST_IOCTL_FRIENDLY_TMPDIR envvar\n"); - std::abort(); -#endif - } - - ~IoctlFriendlyTmpdir() { rmdir(dir_.c_str()); } - - const std::string& name() const { return dir_; } - - bool is_supported() const { return is_supported_; } - - private: - std::string dir_; - - bool is_supported_ = true; -}; - -TEST_F(EnvPosixTest, PositionedAppend) { - std::unique_ptr writable_file; - EnvOptions options; - options.use_direct_writes = true; - options.use_mmap_writes = false; - std::string fname = test::PerThreadDBPath(env_, "positioned_append"); - SetupSyncPointsToMockDirectIO(); - - ASSERT_OK(env_->NewWritableFile(fname, &writable_file, options)); - const size_t kBlockSize = 4096; - const size_t kDataSize = kPageSize; - // Write a page worth of 'a' - auto data_ptr = NewAligned(kDataSize, 'a'); - Slice data_a(data_ptr.get(), kDataSize); - ASSERT_OK(writable_file->PositionedAppend(data_a, 0U)); - // Write a page worth of 'b' right after the first sector - data_ptr = NewAligned(kDataSize, 'b'); - Slice data_b(data_ptr.get(), kDataSize); - ASSERT_OK(writable_file->PositionedAppend(data_b, kBlockSize)); - ASSERT_OK(writable_file->Close()); - // The file now has 1 sector worth of a followed by a page worth of b - - // Verify the above - std::unique_ptr seq_file; - ASSERT_OK(env_->NewSequentialFile(fname, &seq_file, options)); - size_t scratch_len = kPageSize * 2; - std::unique_ptr scratch(new char[scratch_len]); - Slice result; - ASSERT_OK(seq_file->Read(scratch_len, &result, scratch.get())); - ASSERT_EQ(kPageSize + kBlockSize, result.size()); - ASSERT_EQ('a', result[kBlockSize - 1]); - ASSERT_EQ('b', result[kBlockSize]); -} - -// `GetUniqueId()` temporarily returns zero on Windows. `BlockBasedTable` can -// handle a return value of zero but this test case cannot. -#ifndef OS_WIN -TEST_P(EnvPosixTestWithParam, RandomAccessUniqueID) { - // Create file. - if (env_ == Env::Default()) { - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - IoctlFriendlyTmpdir ift; - if (!ift.is_supported()) { - ROCKSDB_GTEST_BYPASS( - "FS_IOC_GETVERSION is not supported by the filesystem"); - return; - } - std::string fname = ift.name() + "/testfile"; - std::unique_ptr wfile; - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - - std::unique_ptr file; - - // Get Unique ID - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); - ASSERT_TRUE(id_size > 0); - std::string unique_id1(temp_id, id_size); - ASSERT_TRUE(IsUniqueIDValid(unique_id1)); - - // Get Unique ID again - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); - ASSERT_TRUE(id_size > 0); - std::string unique_id2(temp_id, id_size); - ASSERT_TRUE(IsUniqueIDValid(unique_id2)); - - // Get Unique ID again after waiting some time. - env_->SleepForMicroseconds(1000000); - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); - ASSERT_TRUE(id_size > 0); - std::string unique_id3(temp_id, id_size); - ASSERT_TRUE(IsUniqueIDValid(unique_id3)); - - // Check IDs are the same. - ASSERT_EQ(unique_id1, unique_id2); - ASSERT_EQ(unique_id2, unique_id3); - - // Delete the file - ASSERT_OK(env_->DeleteFile(fname)); - } -} -#endif // !defined(OS_WIN) - -// only works in linux platforms -#ifdef ROCKSDB_FALLOCATE_PRESENT -TEST_P(EnvPosixTestWithParam, AllocateTest) { - if (env_ == Env::Default()) { - SetupSyncPointsToMockDirectIO(); - std::string fname = test::PerThreadDBPath(env_, "preallocate_testfile"); - // Try fallocate in a file to see whether the target file system supports - // it. - // Skip the test if fallocate is not supported. - std::string fname_test_fallocate = - test::PerThreadDBPath(env_, "preallocate_testfile_2"); - int fd = -1; - do { - fd = open(fname_test_fallocate.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644); - } while (fd < 0 && errno == EINTR); - ASSERT_GT(fd, 0); - - int alloc_status = fallocate(fd, 0, 0, 1); - - int err_number = 0; - if (alloc_status != 0) { - err_number = errno; - fprintf(stderr, "Warning: fallocate() fails, %s\n", - errnoStr(err_number).c_str()); - } - close(fd); - ASSERT_OK(env_->DeleteFile(fname_test_fallocate)); - if (alloc_status != 0 && err_number == EOPNOTSUPP) { - // The filesystem containing the file does not support fallocate - return; - } - - EnvOptions soptions; - soptions.use_mmap_writes = false; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - std::unique_ptr wfile; - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - - // allocate 100 MB - size_t kPreallocateSize = 100 * 1024 * 1024; - size_t kBlockSize = 512; - size_t kDataSize = 1024 * 1024; - auto data_ptr = NewAligned(kDataSize, 'A'); - Slice data(data_ptr.get(), kDataSize); - wfile->SetPreallocationBlockSize(kPreallocateSize); - wfile->PrepareWrite(wfile->GetFileSize(), kDataSize); - ASSERT_OK(wfile->Append(data)); - ASSERT_OK(wfile->Flush()); - - struct stat f_stat; - ASSERT_EQ(stat(fname.c_str(), &f_stat), 0); - ASSERT_EQ((unsigned int)kDataSize, f_stat.st_size); - // verify that blocks are preallocated - // Note here that we don't check the exact number of blocks preallocated -- - // we only require that number of allocated blocks is at least what we - // expect. - // It looks like some FS give us more blocks that we asked for. That's fine. - // It might be worth investigating further. - ASSERT_LE((unsigned int)(kPreallocateSize / kBlockSize), f_stat.st_blocks); - - // close the file, should deallocate the blocks - wfile.reset(); - - stat(fname.c_str(), &f_stat); - ASSERT_EQ((unsigned int)kDataSize, f_stat.st_size); - // verify that preallocated blocks were deallocated on file close - // Because the FS might give us more blocks, we add a full page to the size - // and expect the number of blocks to be less or equal to that. - ASSERT_GE((f_stat.st_size + kPageSize + kBlockSize - 1) / kBlockSize, - (unsigned int)f_stat.st_blocks); - } -} -#endif // ROCKSDB_FALLOCATE_PRESENT - -// Returns true if any of the strings in ss are the prefix of another string. -bool HasPrefix(const std::unordered_set& ss) { - for (const std::string& s : ss) { - if (s.empty()) { - return true; - } - for (size_t i = 1; i < s.size(); ++i) { - if (ss.count(s.substr(0, i)) != 0) { - return true; - } - } - } - return false; -} - -// `GetUniqueId()` temporarily returns zero on Windows. `BlockBasedTable` can -// handle a return value of zero but this test case cannot. -#ifndef OS_WIN -TEST_P(EnvPosixTestWithParam, RandomAccessUniqueIDConcurrent) { - if (env_ == Env::Default()) { - // Check whether a bunch of concurrently existing files have unique IDs. - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - - // Create the files - IoctlFriendlyTmpdir ift; - if (!ift.is_supported()) { - ROCKSDB_GTEST_BYPASS( - "FS_IOC_GETVERSION is not supported by the filesystem"); - return; - } - std::vector fnames; - for (int i = 0; i < 1000; ++i) { - fnames.push_back(ift.name() + "/" + "testfile" + std::to_string(i)); - - // Create file. - std::unique_ptr wfile; - ASSERT_OK(env_->NewWritableFile(fnames[i], &wfile, soptions)); - } - - // Collect and check whether the IDs are unique. - std::unordered_set ids; - for (const std::string& fname : fnames) { - std::unique_ptr file; - std::string unique_id; - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); - ASSERT_TRUE(id_size > 0); - unique_id = std::string(temp_id, id_size); - ASSERT_TRUE(IsUniqueIDValid(unique_id)); - - ASSERT_TRUE(ids.count(unique_id) == 0); - ids.insert(unique_id); - } - - // Delete the files - for (const std::string& fname : fnames) { - ASSERT_OK(env_->DeleteFile(fname)); - } - - ASSERT_TRUE(!HasPrefix(ids)); - } -} - -// TODO: Disable the flaky test, it's a known issue that ext4 may return same -// key after file deletion. The issue is tracked in #7405, #7470. -TEST_P(EnvPosixTestWithParam, DISABLED_RandomAccessUniqueIDDeletes) { - if (env_ == Env::Default()) { - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - - IoctlFriendlyTmpdir ift; - if (!ift.is_supported()) { - ROCKSDB_GTEST_BYPASS( - "FS_IOC_GETVERSION is not supported by the filesystem"); - return; - } - std::string fname = ift.name() + "/" + "testfile"; - - // Check that after file is deleted we don't get same ID again in a new - // file. - std::unordered_set ids; - for (int i = 0; i < 1000; ++i) { - // Create file. - { - std::unique_ptr wfile; - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - } - - // Get Unique ID - std::string unique_id; - { - std::unique_ptr file; - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); - ASSERT_TRUE(id_size > 0); - unique_id = std::string(temp_id, id_size); - } - - ASSERT_TRUE(IsUniqueIDValid(unique_id)); - ASSERT_TRUE(ids.count(unique_id) == 0); - ids.insert(unique_id); - - // Delete the file - ASSERT_OK(env_->DeleteFile(fname)); - } - - ASSERT_TRUE(!HasPrefix(ids)); - } -} -#endif // !defined(OS_WIN) - -TEST_P(EnvPosixTestWithParam, MultiRead) { - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - const size_t kSectorSize = 4096; - const size_t kNumSectors = 8; - - // Create file. - { - std::unique_ptr wfile; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) - if (soptions.use_direct_writes) { - soptions.use_direct_writes = false; - } -#endif - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - for (size_t i = 0; i < kNumSectors; ++i) { - auto data = NewAligned(kSectorSize * 8, static_cast(i + 1)); - Slice slice(data.get(), kSectorSize); - ASSERT_OK(wfile->Append(slice)); - } - ASSERT_OK(wfile->Close()); - } - - // More attempts to simulate more partial result sequences. - for (uint32_t attempt = 0; attempt < 20; attempt++) { - // Random Read - Random rnd(301 + attempt); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UpdateResults::io_uring_result", [&](void* arg) { - if (attempt > 0) { - // No failure in the first attempt. - size_t& bytes_read = *static_cast(arg); - if (rnd.OneIn(4)) { - bytes_read = 0; - } else if (rnd.OneIn(3)) { - bytes_read = static_cast( - rnd.Uniform(static_cast(bytes_read))); - } - } - }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - std::unique_ptr file; - std::vector reqs(3); - std::vector> data; - uint64_t offset = 0; - for (size_t i = 0; i < reqs.size(); ++i) { - reqs[i].offset = offset; - offset += 2 * kSectorSize; - reqs[i].len = kSectorSize; - data.emplace_back(NewAligned(kSectorSize, 0)); - reqs[i].scratch = data.back().get(); - } -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) - if (soptions.use_direct_reads) { - soptions.use_direct_reads = false; - } -#endif - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - ASSERT_OK(file->MultiRead(reqs.data(), reqs.size())); - for (size_t i = 0; i < reqs.size(); ++i) { - auto buf = NewAligned(kSectorSize * 8, static_cast(i * 2 + 1)); - ASSERT_OK(reqs[i].status); - ASSERT_EQ(memcmp(reqs[i].scratch, buf.get(), kSectorSize), 0); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(EnvPosixTest, MultiReadNonAlignedLargeNum) { - // In this test we don't do aligned read, so it doesn't work for - // direct I/O case. - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = false; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - const size_t kTotalSize = 81920; - Random rnd(301); - std::string expected_data = rnd.RandomString(kTotalSize); - - // Create file. - { - std::unique_ptr wfile; - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - ASSERT_OK(wfile->Append(expected_data)); - ASSERT_OK(wfile->Close()); - } - - // More attempts to simulate more partial result sequences. - for (uint32_t attempt = 0; attempt < 25; attempt++) { - // Right now kIoUringDepth is hard coded as 256, so we need very large - // number of keys to cover the case of multiple rounds of submissions. - // Right now the test latency is still acceptable. If it ends up with - // too long, we can modify the io uring depth with SyncPoint here. - const int num_reads = rnd.Uniform(512) + 1; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "UpdateResults::io_uring_result", [&](void* arg) { - if (attempt > 5) { - // Improve partial result rates in second half of the run to - // cover the case of repeated partial results. - int odd = (attempt < 15) ? num_reads / 2 : 4; - // No failure in first several attempts. - size_t& bytes_read = *static_cast(arg); - if (rnd.OneIn(odd)) { - bytes_read = 0; - } else if (rnd.OneIn(odd / 2)) { - bytes_read = static_cast( - rnd.Uniform(static_cast(bytes_read))); - } - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Generate (offset, len) pairs - std::set start_offsets; - for (int i = 0; i < num_reads; i++) { - int rnd_off; - // No repeat offsets. - while (start_offsets.find(rnd_off = rnd.Uniform(81920)) != - start_offsets.end()) { - } - start_offsets.insert(rnd_off); - } - std::vector offsets; - std::vector lens; - // std::set already sorted the offsets. - for (int so : start_offsets) { - offsets.push_back(so); - } - for (size_t i = 0; i + 1 < offsets.size(); i++) { - lens.push_back(static_cast( - rnd.Uniform(static_cast(offsets[i + 1] - offsets[i])) + 1)); - } - lens.push_back(static_cast( - rnd.Uniform(static_cast(kTotalSize - offsets.back())) + 1)); - ASSERT_EQ(num_reads, lens.size()); - - // Create requests - std::vector scratches; - scratches.reserve(num_reads); - std::vector reqs(num_reads); - for (size_t i = 0; i < reqs.size(); ++i) { - reqs[i].offset = offsets[i]; - reqs[i].len = lens[i]; - scratches.emplace_back(reqs[i].len, ' '); - reqs[i].scratch = const_cast(scratches.back().data()); - } - - // Query the data - std::unique_ptr file; - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - ASSERT_OK(file->MultiRead(reqs.data(), reqs.size())); - - // Validate results - for (int i = 0; i < num_reads; ++i) { - ASSERT_OK(reqs[i].status); - ASSERT_EQ( - Slice(expected_data.data() + offsets[i], lens[i]).ToString(true), - reqs[i].result.ToString(true)); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(EnvPosixTest, NonAlignedDirectIOMultiReadBeyondFileSize) { - EnvOptions soptions; - soptions.use_direct_reads = true; - soptions.use_direct_writes = false; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - Random rnd(301); - std::unique_ptr wfile; - size_t alignment = 0; - // Create file. - { - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - auto data_ptr = NewAligned(4095, 'b'); - Slice data_b(data_ptr.get(), 4095); - ASSERT_OK(wfile->PositionedAppend(data_b, 0U)); - ASSERT_OK(wfile->Close()); - } - -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) && !defined(OS_OPENBSD) && !defined(OS_FREEBSD) - if (soptions.use_direct_reads) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "NewRandomAccessFile:O_DIRECT", [&](void* arg) { - int* val = static_cast(arg); - *val &= ~O_DIRECT; - }); - } -#endif - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - const int num_reads = 2; - // Create requests - std::vector scratches; - scratches.reserve(num_reads); - std::vector reqs(num_reads); - - std::unique_ptr file; - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - alignment = file->GetRequiredBufferAlignment(); - ASSERT_EQ(num_reads, reqs.size()); - - std::vector> data; - - std::vector offsets = {0, 2047}; - std::vector lens = {2047, 4096 - 2047}; - - for (size_t i = 0; i < num_reads; i++) { - // Do alignment - reqs[i].offset = static_cast(TruncateToPageBoundary( - alignment, static_cast(/*offset=*/offsets[i]))); - reqs[i].len = - Roundup(static_cast(/*offset=*/offsets[i]) + /*length=*/lens[i], - alignment) - - reqs[i].offset; - - size_t new_capacity = Roundup(reqs[i].len, alignment); - data.emplace_back(NewAligned(new_capacity, 0)); - reqs[i].scratch = data.back().get(); - } - - // Query the data - ASSERT_OK(file->MultiRead(reqs.data(), reqs.size())); - - // Validate results - for (size_t i = 0; i < num_reads; ++i) { - ASSERT_OK(reqs[i].status); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -#if defined(ROCKSDB_IOURING_PRESENT) -void GenerateFilesAndRequest(Env* env, const std::string& fname, - std::vector* ret_reqs, - std::vector* scratches) { - const size_t kTotalSize = 81920; - Random rnd(301); - std::string expected_data = rnd.RandomString(kTotalSize); - - // Create file. - { - std::unique_ptr wfile; - ASSERT_OK(env->NewWritableFile(fname, &wfile, EnvOptions())); - ASSERT_OK(wfile->Append(expected_data)); - ASSERT_OK(wfile->Close()); - } - - // Right now kIoUringDepth is hard coded as 256, so we need very large - // number of keys to cover the case of multiple rounds of submissions. - // Right now the test latency is still acceptable. If it ends up with - // too long, we can modify the io uring depth with SyncPoint here. - const int num_reads = 3; - std::vector offsets = {10000, 20000, 30000}; - std::vector lens = {3000, 200, 100}; - - // Create requests - scratches->reserve(num_reads); - std::vector& reqs = *ret_reqs; - reqs.resize(num_reads); - for (int i = 0; i < num_reads; ++i) { - reqs[i].offset = offsets[i]; - reqs[i].len = lens[i]; - scratches->emplace_back(reqs[i].len, ' '); - reqs[i].scratch = const_cast(scratches->back().data()); - } -} - -TEST_F(EnvPosixTest, MultiReadIOUringError) { - // In this test we don't do aligned read, so we can't do direct I/O. - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = false; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - std::vector scratches; - std::vector reqs; - GenerateFilesAndRequest(env_, fname, &reqs, &scratches); - // Query the data - std::unique_ptr file; - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - - bool io_uring_wait_cqe_called = false; - SyncPoint::GetInstance()->SetCallBack( - "PosixRandomAccessFile::MultiRead:io_uring_wait_cqe:return", - [&](void* arg) { - if (!io_uring_wait_cqe_called) { - io_uring_wait_cqe_called = true; - ssize_t& ret = *(static_cast(arg)); - ret = 1; - } - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = file->MultiRead(reqs.data(), reqs.size()); - if (io_uring_wait_cqe_called) { - ASSERT_NOK(s); - } else { - s.PermitUncheckedError(); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} - -TEST_F(EnvPosixTest, MultiReadIOUringError2) { - // In this test we don't do aligned read, so we can't do direct I/O. - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = false; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - std::vector scratches; - std::vector reqs; - GenerateFilesAndRequest(env_, fname, &reqs, &scratches); - // Query the data - std::unique_ptr file; - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - - bool io_uring_submit_and_wait_called = false; - SyncPoint::GetInstance()->SetCallBack( - "PosixRandomAccessFile::MultiRead:io_uring_submit_and_wait:return1", - [&](void* arg) { - io_uring_submit_and_wait_called = true; - ssize_t* ret = static_cast(arg); - (*ret)--; - }); - SyncPoint::GetInstance()->SetCallBack( - "PosixRandomAccessFile::MultiRead:io_uring_submit_and_wait:return2", - [&](void* arg) { - struct io_uring* iu = static_cast(arg); - struct io_uring_cqe* cqe; - assert(io_uring_wait_cqe(iu, &cqe) == 0); - io_uring_cqe_seen(iu, cqe); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = file->MultiRead(reqs.data(), reqs.size()); - if (io_uring_submit_and_wait_called) { - ASSERT_NOK(s); - } else { - s.PermitUncheckedError(); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); -} -#endif // ROCKSDB_IOURING_PRESENT - -// Only works in linux platforms -#ifdef OS_WIN -TEST_P(EnvPosixTestWithParam, DISABLED_InvalidateCache) { -#else -TEST_P(EnvPosixTestWithParam, InvalidateCache) { -#endif - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - const size_t kSectorSize = 512; - auto data = NewAligned(kSectorSize, 0); - Slice slice(data.get(), kSectorSize); - - // Create file. - { - std::unique_ptr wfile; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) - if (soptions.use_direct_writes) { - soptions.use_direct_writes = false; - } -#endif - ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - ASSERT_OK(wfile->Append(slice)); - ASSERT_OK(wfile->InvalidateCache(0, 0)); - ASSERT_OK(wfile->Close()); - } - - // Random Read - { - std::unique_ptr file; - auto scratch = NewAligned(kSectorSize, 0); - Slice result; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) - if (soptions.use_direct_reads) { - soptions.use_direct_reads = false; - } -#endif - ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); - ASSERT_OK(file->Read(0, kSectorSize, &result, scratch.get())); - ASSERT_EQ(memcmp(scratch.get(), data.get(), kSectorSize), 0); - ASSERT_OK(file->InvalidateCache(0, 11)); - ASSERT_OK(file->InvalidateCache(0, 0)); - } - - // Sequential Read - { - std::unique_ptr file; - auto scratch = NewAligned(kSectorSize, 0); - Slice result; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) - if (soptions.use_direct_reads) { - soptions.use_direct_reads = false; - } -#endif - ASSERT_OK(env_->NewSequentialFile(fname, &file, soptions)); - if (file->use_direct_io()) { - ASSERT_OK(file->PositionedRead(0, kSectorSize, &result, scratch.get())); - } else { - ASSERT_OK(file->Read(kSectorSize, &result, scratch.get())); - } - ASSERT_EQ(memcmp(scratch.get(), data.get(), kSectorSize), 0); - ASSERT_OK(file->InvalidateCache(0, 11)); - ASSERT_OK(file->InvalidateCache(0, 0)); - } - // Delete the file - ASSERT_OK(env_->DeleteFile(fname)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); -} -#endif // OS_LINUX || OS_WIN - -class TestLogger : public Logger { - public: - using Logger::Logv; - void Logv(const char* format, va_list ap) override { - log_count++; - - char new_format[550]; - std::fill_n(new_format, sizeof(new_format), '2'); - { - va_list backup_ap; - va_copy(backup_ap, ap); - int n = vsnprintf(new_format, sizeof(new_format) - 1, format, backup_ap); - // 48 bytes for extra information + bytes allocated - -// When we have n == -1 there is not a terminating zero expected -#ifdef OS_WIN - if (n < 0) { - char_0_count++; - } -#endif - - if (new_format[0] == '[') { - // "[DEBUG] " - ASSERT_TRUE(n <= 56 + (512 - static_cast(sizeof(port::TimeVal)))); - } else { - ASSERT_TRUE(n <= 48 + (512 - static_cast(sizeof(port::TimeVal)))); - } - va_end(backup_ap); - } - - for (size_t i = 0; i < sizeof(new_format); i++) { - if (new_format[i] == 'x') { - char_x_count++; - } else if (new_format[i] == '\0') { - char_0_count++; - } - } - } - int log_count; - int char_x_count; - int char_0_count; -}; - -TEST_P(EnvPosixTestWithParam, LogBufferTest) { - TestLogger test_logger; - test_logger.SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - test_logger.log_count = 0; - test_logger.char_x_count = 0; - test_logger.char_0_count = 0; - LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, &test_logger); - LogBuffer log_buffer_debug(DEBUG_LEVEL, &test_logger); - - char bytes200[200]; - std::fill_n(bytes200, sizeof(bytes200), '1'); - bytes200[sizeof(bytes200) - 1] = '\0'; - char bytes600[600]; - std::fill_n(bytes600, sizeof(bytes600), '1'); - bytes600[sizeof(bytes600) - 1] = '\0'; - char bytes9000[9000]; - std::fill_n(bytes9000, sizeof(bytes9000), '1'); - bytes9000[sizeof(bytes9000) - 1] = '\0'; - - ROCKS_LOG_BUFFER(&log_buffer, "x%sx", bytes200); - ROCKS_LOG_BUFFER(&log_buffer, "x%sx", bytes600); - ROCKS_LOG_BUFFER(&log_buffer, "x%sx%sx%sx", bytes200, bytes200, bytes200); - ROCKS_LOG_BUFFER(&log_buffer, "x%sx%sx", bytes200, bytes600); - ROCKS_LOG_BUFFER(&log_buffer, "x%sx%sx", bytes600, bytes9000); - - ROCKS_LOG_BUFFER(&log_buffer_debug, "x%sx", bytes200); - test_logger.SetInfoLogLevel(DEBUG_LEVEL); - ROCKS_LOG_BUFFER(&log_buffer_debug, "x%sx%sx%sx", bytes600, bytes9000, - bytes200); - - ASSERT_EQ(0, test_logger.log_count); - log_buffer.FlushBufferToLog(); - log_buffer_debug.FlushBufferToLog(); - ASSERT_EQ(6, test_logger.log_count); - ASSERT_EQ(6, test_logger.char_0_count); - ASSERT_EQ(10, test_logger.char_x_count); -} - -class TestLogger2 : public Logger { - public: - explicit TestLogger2(size_t max_log_size) : max_log_size_(max_log_size) {} - using Logger::Logv; - void Logv(const char* format, va_list ap) override { - char new_format[2000]; - std::fill_n(new_format, sizeof(new_format), '2'); - { - va_list backup_ap; - va_copy(backup_ap, ap); - int n = vsnprintf(new_format, sizeof(new_format) - 1, format, backup_ap); - // 48 bytes for extra information + bytes allocated - ASSERT_TRUE(n <= - 48 + static_cast(max_log_size_ - sizeof(port::TimeVal))); - ASSERT_TRUE(n > static_cast(max_log_size_ - sizeof(port::TimeVal))); - va_end(backup_ap); - } - } - size_t max_log_size_; -}; - -TEST_P(EnvPosixTestWithParam, LogBufferMaxSizeTest) { - char bytes9000[9000]; - std::fill_n(bytes9000, sizeof(bytes9000), '1'); - bytes9000[sizeof(bytes9000) - 1] = '\0'; - - for (size_t max_log_size = 256; max_log_size <= 1024; - max_log_size += 1024 - 256) { - TestLogger2 test_logger(max_log_size); - test_logger.SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, &test_logger); - ROCKS_LOG_BUFFER_MAX_SZ(&log_buffer, max_log_size, "%s", bytes9000); - log_buffer.FlushBufferToLog(); - } -} - -TEST_P(EnvPosixTestWithParam, Preallocation) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - const std::string src = test::PerThreadDBPath(env_, "testfile"); - std::unique_ptr srcfile; - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) && !defined(OS_OPENBSD) && !defined(OS_FREEBSD) - if (soptions.use_direct_writes) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "NewWritableFile:O_DIRECT", [&](void* arg) { - int* val = static_cast(arg); - *val &= ~O_DIRECT; - }); - } -#endif - ASSERT_OK(env_->NewWritableFile(src, &srcfile, soptions)); - srcfile->SetPreallocationBlockSize(1024 * 1024); - - // No writes should mean no preallocation - size_t block_size, last_allocated_block; - srcfile->GetPreallocationStatus(&block_size, &last_allocated_block); - ASSERT_EQ(last_allocated_block, 0UL); - - // Small write should preallocate one block - size_t kStrSize = 4096; - auto data = NewAligned(kStrSize, 'A'); - Slice str(data.get(), kStrSize); - srcfile->PrepareWrite(srcfile->GetFileSize(), kStrSize); - ASSERT_OK(srcfile->Append(str)); - srcfile->GetPreallocationStatus(&block_size, &last_allocated_block); - ASSERT_EQ(last_allocated_block, 1UL); - - // Write an entire preallocation block, make sure we increased by two. - { - auto buf_ptr = NewAligned(block_size, ' '); - Slice buf(buf_ptr.get(), block_size); - srcfile->PrepareWrite(srcfile->GetFileSize(), block_size); - ASSERT_OK(srcfile->Append(buf)); - srcfile->GetPreallocationStatus(&block_size, &last_allocated_block); - ASSERT_EQ(last_allocated_block, 2UL); - } - - // Write five more blocks at once, ensure we're where we need to be. - { - auto buf_ptr = NewAligned(block_size * 5, ' '); - Slice buf = Slice(buf_ptr.get(), block_size * 5); - srcfile->PrepareWrite(srcfile->GetFileSize(), buf.size()); - ASSERT_OK(srcfile->Append(buf)); - srcfile->GetPreallocationStatus(&block_size, &last_allocated_block); - ASSERT_EQ(last_allocated_block, 7UL); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); -} - -// Test that the two ways to get children file attributes (in bulk or -// individually) behave consistently. -TEST_P(EnvPosixTestWithParam, ConsistentChildrenAttributes) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - const int kNumChildren = 10; - - std::string data; - std::string test_base_dir = test::PerThreadDBPath(env_, "env_test_chr_attr"); - env_->CreateDir(test_base_dir).PermitUncheckedError(); - for (int i = 0; i < kNumChildren; ++i) { - const std::string path = test_base_dir + "/testfile_" + std::to_string(i); - std::unique_ptr file; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) && !defined(OS_OPENBSD) && !defined(OS_FREEBSD) - if (soptions.use_direct_writes) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "NewWritableFile:O_DIRECT", [&](void* arg) { - int* val = static_cast(arg); - *val &= ~O_DIRECT; - }); - } -#endif - ASSERT_OK(env_->NewWritableFile(path, &file, soptions)); - auto buf_ptr = NewAligned(data.size(), 'T'); - Slice buf(buf_ptr.get(), data.size()); - ASSERT_OK(file->Append(buf)); - data.append(std::string(4096, 'T')); - } - - std::vector file_attrs; - ASSERT_OK(env_->GetChildrenFileAttributes(test_base_dir, &file_attrs)); - for (int i = 0; i < kNumChildren; ++i) { - const std::string name = "testfile_" + std::to_string(i); - const std::string path = test_base_dir + "/" + name; - - auto file_attrs_iter = std::find_if( - file_attrs.begin(), file_attrs.end(), - [&name](const Env::FileAttributes& fm) { return fm.name == name; }); - ASSERT_TRUE(file_attrs_iter != file_attrs.end()); - uint64_t size; - ASSERT_OK(env_->GetFileSize(path, &size)); - ASSERT_EQ(size, 4096 * i); - ASSERT_EQ(size, file_attrs_iter->size_bytes); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); -} - -// Test that all WritableFileWrapper forwards all calls to WritableFile. -TEST_P(EnvPosixTestWithParam, WritableFileWrapper) { - class Base : public WritableFile { - public: - mutable int* step_; - - void inc(int x) const { EXPECT_EQ(x, (*step_)++); } - - explicit Base(int* step) : step_(step) { inc(0); } - - Status Append(const Slice& /*data*/) override { - inc(1); - return Status::OK(); - } - - Status Append( - const Slice& /*data*/, - const DataVerificationInfo& /* verification_info */) override { - inc(1); - return Status::OK(); - } - - Status PositionedAppend(const Slice& /*data*/, - uint64_t /*offset*/) override { - inc(2); - return Status::OK(); - } - - Status PositionedAppend( - const Slice& /*data*/, uint64_t /*offset*/, - const DataVerificationInfo& /* verification_info */) override { - inc(2); - return Status::OK(); - } - - Status Truncate(uint64_t /*size*/) override { - inc(3); - return Status::OK(); - } - - Status Close() override { - inc(4); - return Status::OK(); - } - - Status Flush() override { - inc(5); - return Status::OK(); - } - - Status Sync() override { - inc(6); - return Status::OK(); - } - - Status Fsync() override { - inc(7); - return Status::OK(); - } - - bool IsSyncThreadSafe() const override { - inc(8); - return true; - } - - bool use_direct_io() const override { - inc(9); - return true; - } - - size_t GetRequiredBufferAlignment() const override { - inc(10); - return 0; - } - - void SetIOPriority(Env::IOPriority /*pri*/) override { inc(11); } - - Env::IOPriority GetIOPriority() override { - inc(12); - return Env::IOPriority::IO_LOW; - } - - void SetWriteLifeTimeHint(Env::WriteLifeTimeHint /*hint*/) override { - inc(13); - } - - Env::WriteLifeTimeHint GetWriteLifeTimeHint() override { - inc(14); - return Env::WriteLifeTimeHint::WLTH_NOT_SET; - } - - uint64_t GetFileSize() override { - inc(15); - return 0; - } - - void SetPreallocationBlockSize(size_t /*size*/) override { inc(16); } - - void GetPreallocationStatus(size_t* /*block_size*/, - size_t* /*last_allocated_block*/) override { - inc(17); - } - - size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const override { - inc(18); - return 0; - } - - Status InvalidateCache(size_t /*offset*/, size_t /*length*/) override { - inc(19); - return Status::OK(); - } - - Status RangeSync(uint64_t /*offset*/, uint64_t /*nbytes*/) override { - inc(20); - return Status::OK(); - } - - void PrepareWrite(size_t /*offset*/, size_t /*len*/) override { inc(21); } - - Status Allocate(uint64_t /*offset*/, uint64_t /*len*/) override { - inc(22); - return Status::OK(); - } - - public: - ~Base() override { inc(23); } - }; - - class Wrapper : public WritableFileWrapper { - public: - explicit Wrapper(WritableFile* target) : WritableFileWrapper(target) {} - }; - - int step = 0; - - { - Base b(&step); - Wrapper w(&b); - ASSERT_OK(w.Append(Slice())); - ASSERT_OK(w.PositionedAppend(Slice(), 0)); - ASSERT_OK(w.Truncate(0)); - ASSERT_OK(w.Close()); - ASSERT_OK(w.Flush()); - ASSERT_OK(w.Sync()); - ASSERT_OK(w.Fsync()); - w.IsSyncThreadSafe(); - w.use_direct_io(); - w.GetRequiredBufferAlignment(); - w.SetIOPriority(Env::IOPriority::IO_HIGH); - w.GetIOPriority(); - w.SetWriteLifeTimeHint(Env::WriteLifeTimeHint::WLTH_NOT_SET); - w.GetWriteLifeTimeHint(); - w.GetFileSize(); - w.SetPreallocationBlockSize(0); - w.GetPreallocationStatus(nullptr, nullptr); - w.GetUniqueId(nullptr, 0); - ASSERT_OK(w.InvalidateCache(0, 0)); - ASSERT_OK(w.RangeSync(0, 0)); - w.PrepareWrite(0, 0); - ASSERT_OK(w.Allocate(0, 0)); - } - - EXPECT_EQ(24, step); -} - -TEST_P(EnvPosixTestWithParam, PosixRandomRWFile) { - const std::string path = test::PerThreadDBPath(env_, "random_rw_file"); - - env_->DeleteFile(path).PermitUncheckedError(); - - std::unique_ptr file; - - // Cannot open non-existing file. - ASSERT_NOK(env_->NewRandomRWFile(path, &file, EnvOptions())); - - // Create the file using WritableFile - { - std::unique_ptr wf; - ASSERT_OK(env_->NewWritableFile(path, &wf, EnvOptions())); - } - - ASSERT_OK(env_->NewRandomRWFile(path, &file, EnvOptions())); - - char buf[10000]; - Slice read_res; - - ASSERT_OK(file->Write(0, "ABCD")); - ASSERT_OK(file->Read(0, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ABCD"); - - ASSERT_OK(file->Write(2, "XXXX")); - ASSERT_OK(file->Read(0, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ABXXXX"); - - ASSERT_OK(file->Write(10, "ZZZ")); - ASSERT_OK(file->Read(10, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ZZZ"); - - ASSERT_OK(file->Write(11, "Y")); - ASSERT_OK(file->Read(10, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ZYZ"); - - ASSERT_OK(file->Write(200, "FFFFF")); - ASSERT_OK(file->Read(200, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "FFFFF"); - - ASSERT_OK(file->Write(205, "XXXX")); - ASSERT_OK(file->Read(200, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "FFFFFXXXX"); - - ASSERT_OK(file->Write(5, "QQQQ")); - ASSERT_OK(file->Read(0, 9, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ABXXXQQQQ"); - - ASSERT_OK(file->Read(2, 4, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "XXXQ"); - - // Close file and reopen it - ASSERT_OK(file->Close()); - ASSERT_OK(env_->NewRandomRWFile(path, &file, EnvOptions())); - - ASSERT_OK(file->Read(0, 9, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ABXXXQQQQ"); - - ASSERT_OK(file->Read(10, 3, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ZYZ"); - - ASSERT_OK(file->Read(200, 9, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "FFFFFXXXX"); - - ASSERT_OK(file->Write(4, "TTTTTTTTTTTTTTTT")); - ASSERT_OK(file->Read(0, 10, &read_res, buf)); - ASSERT_EQ(read_res.ToString(), "ABXXTTTTTT"); - - // Clean up - ASSERT_OK(env_->DeleteFile(path)); -} - -class RandomRWFileWithMirrorString { - public: - explicit RandomRWFileWithMirrorString(RandomRWFile* _file) : file_(_file) {} - - void Write(size_t offset, const std::string& data) { - // Write to mirror string - StringWrite(offset, data); - - // Write to file - Status s = file_->Write(offset, data); - ASSERT_OK(s) << s.ToString(); - } - - void Read(size_t offset = 0, size_t n = 1000000) { - Slice str_res(nullptr, 0); - if (offset < file_mirror_.size()) { - size_t str_res_sz = std::min(file_mirror_.size() - offset, n); - str_res = Slice(file_mirror_.data() + offset, str_res_sz); - StopSliceAtNull(&str_res); - } - - Slice file_res; - Status s = file_->Read(offset, n, &file_res, buf_); - ASSERT_OK(s) << s.ToString(); - StopSliceAtNull(&file_res); - - ASSERT_EQ(str_res.ToString(), file_res.ToString()) << offset << " " << n; - } - - void SetFile(RandomRWFile* _file) { file_ = _file; } - - private: - void StringWrite(size_t offset, const std::string& src) { - if (offset + src.size() > file_mirror_.size()) { - file_mirror_.resize(offset + src.size(), '\0'); - } - - char* pos = const_cast(file_mirror_.data() + offset); - memcpy(pos, src.data(), src.size()); - } - - void StopSliceAtNull(Slice* slc) { - for (size_t i = 0; i < slc->size(); i++) { - if ((*slc)[i] == '\0') { - *slc = Slice(slc->data(), i); - break; - } - } - } - - char buf_[10000]; - RandomRWFile* file_; - std::string file_mirror_; -}; - -TEST_P(EnvPosixTestWithParam, PosixRandomRWFileRandomized) { - const std::string path = test::PerThreadDBPath(env_, "random_rw_file_rand"); - env_->DeleteFile(path).PermitUncheckedError(); - - std::unique_ptr file; - -#ifdef OS_LINUX - // Cannot open non-existing file. - ASSERT_NOK(env_->NewRandomRWFile(path, &file, EnvOptions())); -#endif - - // Create the file using WritableFile - { - std::unique_ptr wf; - ASSERT_OK(env_->NewWritableFile(path, &wf, EnvOptions())); - } - - ASSERT_OK(env_->NewRandomRWFile(path, &file, EnvOptions())); - RandomRWFileWithMirrorString file_with_mirror(file.get()); - - Random rnd(301); - std::string buf; - for (int i = 0; i < 10000; i++) { - // Genrate random data - buf = rnd.RandomString(10); - - // Pick random offset for write - size_t write_off = rnd.Next() % 1000; - file_with_mirror.Write(write_off, buf); - - // Pick random offset for read - size_t read_off = rnd.Next() % 1000; - size_t read_sz = rnd.Next() % 20; - file_with_mirror.Read(read_off, read_sz); - - if (i % 500 == 0) { - // Reopen the file every 500 iters - ASSERT_OK(env_->NewRandomRWFile(path, &file, EnvOptions())); - file_with_mirror.SetFile(file.get()); - } - } - - // clean up - ASSERT_OK(env_->DeleteFile(path)); -} - -class TestEnv : public EnvWrapper { - public: - explicit TestEnv() : EnvWrapper(Env::Default()), close_count(0) {} - const char* Name() const override { return "TestEnv"; } - class TestLogger : public Logger { - public: - using Logger::Logv; - explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; } - ~TestLogger() override { - if (!closed_) { - Status s = CloseHelper(); - s.PermitUncheckedError(); - } - } - void Logv(const char* /*format*/, va_list /*ap*/) override {} - - protected: - Status CloseImpl() override { return CloseHelper(); } - - private: - Status CloseHelper() { - env->CloseCountInc(); - return Status::OK(); - } - TestEnv* env; - }; - - void CloseCountInc() { close_count++; } - - int GetCloseCount() { return close_count; } - - Status NewLogger(const std::string& /*fname*/, - std::shared_ptr* result) override { - result->reset(new TestLogger(this)); - return Status::OK(); - } - - private: - int close_count; -}; - -class EnvTest : public testing::Test { - public: - EnvTest() : test_directory_(test::PerThreadDBPath("env_test")) {} - - protected: - const std::string test_directory_; -}; - -TEST_F(EnvTest, Close) { - TestEnv* env = new TestEnv(); - std::shared_ptr logger; - Status s; - - s = env->NewLogger("", &logger); - ASSERT_OK(s); - ASSERT_OK(logger.get()->Close()); - ASSERT_EQ(env->GetCloseCount(), 1); - // Call Close() again. CloseHelper() should not be called again - ASSERT_OK(logger.get()->Close()); - ASSERT_EQ(env->GetCloseCount(), 1); - logger.reset(); - ASSERT_EQ(env->GetCloseCount(), 1); - - s = env->NewLogger("", &logger); - ASSERT_OK(s); - logger.reset(); - ASSERT_EQ(env->GetCloseCount(), 2); - - delete env; -} - -class LogvWithInfoLogLevelLogger : public Logger { - public: - using Logger::Logv; - void Logv(const InfoLogLevel /* log_level */, const char* /* format */, - va_list /* ap */) override {} -}; - -TEST_F(EnvTest, LogvWithInfoLogLevel) { - // Verifies the log functions work on a `Logger` that only overrides the - // `Logv()` overload including `InfoLogLevel`. - const std::string kSampleMessage("sample log message"); - LogvWithInfoLogLevelLogger logger; - ROCKS_LOG_HEADER(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_DEBUG(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_INFO(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_WARN(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_ERROR(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_FATAL(&logger, "%s", kSampleMessage.c_str()); -} - -INSTANTIATE_TEST_CASE_P(DefaultEnvWithoutDirectIO, EnvPosixTestWithParam, - ::testing::Values(std::pair(Env::Default(), - false))); -INSTANTIATE_TEST_CASE_P(DefaultEnvWithDirectIO, EnvPosixTestWithParam, - ::testing::Values(std::pair(Env::Default(), - true))); - -#if !defined(OS_WIN) -static Env* GetChrootEnv() { - static std::unique_ptr chroot_env( - NewChrootEnv(Env::Default(), test::TmpDir(Env::Default()))); - return chroot_env.get(); -} -INSTANTIATE_TEST_CASE_P(ChrootEnvWithoutDirectIO, EnvPosixTestWithParam, - ::testing::Values(std::pair(GetChrootEnv(), - false))); -INSTANTIATE_TEST_CASE_P(ChrootEnvWithDirectIO, EnvPosixTestWithParam, - ::testing::Values(std::pair(GetChrootEnv(), - true))); -#endif // !defined(OS_WIN) - -class EnvFSTestWithParam - : public ::testing::Test, - public ::testing::WithParamInterface> { - public: - EnvFSTestWithParam() { - bool env_non_null = std::get<0>(GetParam()); - bool env_default = std::get<1>(GetParam()); - bool fs_default = std::get<2>(GetParam()); - - env_ = env_non_null ? (env_default ? Env::Default() : nullptr) : nullptr; - fs_ = fs_default - ? FileSystem::Default() - : std::make_shared(FileSystem::Default()); - if (env_non_null && env_default && !fs_default) { - env_ptr_ = NewCompositeEnv(fs_); - } - if (env_non_null && !env_default && fs_default) { - env_ptr_ = - std::unique_ptr(new FaultInjectionTestEnv(Env::Default())); - fs_.reset(); - } - if (env_non_null && !env_default && !fs_default) { - env_ptr_.reset(new FaultInjectionTestEnv(Env::Default())); - composite_env_ptr_.reset(new CompositeEnvWrapper(env_ptr_.get(), fs_)); - env_ = composite_env_ptr_.get(); - } else { - env_ = env_ptr_.get(); - } - - dbname1_ = test::PerThreadDBPath("env_fs_test1"); - dbname2_ = test::PerThreadDBPath("env_fs_test2"); - } - - ~EnvFSTestWithParam() = default; - - Env* env_; - std::unique_ptr env_ptr_; - std::unique_ptr composite_env_ptr_; - std::shared_ptr fs_; - std::string dbname1_; - std::string dbname2_; -}; - -TEST_P(EnvFSTestWithParam, OptionsTest) { - Options opts; - opts.env = env_; - opts.create_if_missing = true; - std::string dbname = dbname1_; - - if (env_) { - if (fs_) { - ASSERT_EQ(fs_.get(), env_->GetFileSystem().get()); - } else { - ASSERT_NE(FileSystem::Default().get(), env_->GetFileSystem().get()); - } - } - for (int i = 0; i < 2; ++i) { - DB* db; - Status s = DB::Open(opts, dbname, &db); - ASSERT_OK(s); - - WriteOptions wo; - ASSERT_OK(db->Put(wo, "a", "a")); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK(db->Put(wo, "b", "b")); - ASSERT_OK(db->Flush(FlushOptions())); - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - std::string val; - ASSERT_OK(db->Get(ReadOptions(), "a", &val)); - ASSERT_EQ("a", val); - ASSERT_OK(db->Get(ReadOptions(), "b", &val)); - ASSERT_EQ("b", val); - - ASSERT_OK(db->Close()); - delete db; - ASSERT_OK(DestroyDB(dbname, opts)); - - dbname = dbname2_; - } -} - -// The parameters are as follows - -// 1. True means Options::env is non-null, false means null -// 2. True means use Env::Default, false means custom -// 3. True means use FileSystem::Default, false means custom -INSTANTIATE_TEST_CASE_P(EnvFSTest, EnvFSTestWithParam, - ::testing::Combine(::testing::Bool(), ::testing::Bool(), - ::testing::Bool())); -// This test ensures that default Env and those allocated by -// NewCompositeEnv() all share the same threadpool -TEST_F(EnvTest, MultipleCompositeEnv) { - std::shared_ptr fs1 = - std::make_shared(FileSystem::Default()); - std::shared_ptr fs2 = - std::make_shared(FileSystem::Default()); - std::unique_ptr env1 = NewCompositeEnv(fs1); - std::unique_ptr env2 = NewCompositeEnv(fs2); - Env::Default()->SetBackgroundThreads(8, Env::HIGH); - Env::Default()->SetBackgroundThreads(16, Env::LOW); - ASSERT_EQ(env1->GetBackgroundThreads(Env::LOW), 16); - ASSERT_EQ(env1->GetBackgroundThreads(Env::HIGH), 8); - ASSERT_EQ(env2->GetBackgroundThreads(Env::LOW), 16); - ASSERT_EQ(env2->GetBackgroundThreads(Env::HIGH), 8); -} - -TEST_F(EnvTest, IsDirectory) { - Status s = Env::Default()->CreateDirIfMissing(test_directory_); - ASSERT_OK(s); - const std::string test_sub_dir = test_directory_ + "sub1"; - const std::string test_file_path = test_directory_ + "file1"; - ASSERT_OK(Env::Default()->CreateDirIfMissing(test_sub_dir)); - bool is_dir = false; - ASSERT_OK(Env::Default()->IsDirectory(test_sub_dir, &is_dir)); - ASSERT_TRUE(is_dir); - { - std::unique_ptr wfile; - s = Env::Default()->GetFileSystem()->NewWritableFile( - test_file_path, FileOptions(), &wfile, /*dbg=*/nullptr); - ASSERT_OK(s); - std::unique_ptr fwriter; - fwriter.reset(new WritableFileWriter(std::move(wfile), test_file_path, - FileOptions(), - SystemClock::Default().get())); - constexpr char buf[] = "test"; - s = fwriter->Append(buf); - ASSERT_OK(s); - } - ASSERT_OK(Env::Default()->IsDirectory(test_file_path, &is_dir)); - ASSERT_FALSE(is_dir); -} - -TEST_F(EnvTest, EnvWriteVerificationTest) { - Status s = Env::Default()->CreateDirIfMissing(test_directory_); - const std::string test_file_path = test_directory_ + "file1"; - ASSERT_OK(s); - std::shared_ptr fault_fs( - new FaultInjectionTestFS(FileSystem::Default())); - fault_fs->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - std::unique_ptr fault_fs_env(NewCompositeEnv(fault_fs)); - std::unique_ptr file; - s = fault_fs_env->NewWritableFile(test_file_path, &file, EnvOptions()); - ASSERT_OK(s); - - DataVerificationInfo v_info; - std::string test_data = "test"; - std::string checksum; - uint32_t v_crc32c = crc32c::Extend(0, test_data.c_str(), test_data.size()); - PutFixed32(&checksum, v_crc32c); - v_info.checksum = Slice(checksum); - s = file->Append(Slice(test_data), v_info); - ASSERT_OK(s); -} - -class CreateEnvTest : public testing::Test { - public: - CreateEnvTest() { - config_options_.ignore_unknown_options = false; - config_options_.ignore_unsupported_options = false; - } - ConfigOptions config_options_; -}; - -TEST_F(CreateEnvTest, LoadCTRProvider) { - config_options_.invoke_prepare_options = false; - std::string CTR = CTREncryptionProvider::kClassName(); - std::shared_ptr provider; - // Test a provider with no cipher - ASSERT_OK( - EncryptionProvider::CreateFromString(config_options_, CTR, &provider)); - ASSERT_NE(provider, nullptr); - ASSERT_EQ(provider->Name(), CTR); - ASSERT_NOK(provider->PrepareOptions(config_options_)); - ASSERT_NOK(provider->ValidateOptions(DBOptions(), ColumnFamilyOptions())); - auto cipher = provider->GetOptions>("Cipher"); - ASSERT_NE(cipher, nullptr); - ASSERT_EQ(cipher->get(), nullptr); - provider.reset(); - - ASSERT_OK(EncryptionProvider::CreateFromString(config_options_, - CTR + "://test", &provider)); - ASSERT_NE(provider, nullptr); - ASSERT_EQ(provider->Name(), CTR); - ASSERT_OK(provider->PrepareOptions(config_options_)); - ASSERT_OK(provider->ValidateOptions(DBOptions(), ColumnFamilyOptions())); - cipher = provider->GetOptions>("Cipher"); - ASSERT_NE(cipher, nullptr); - ASSERT_NE(cipher->get(), nullptr); - ASSERT_STREQ(cipher->get()->Name(), "ROT13"); - provider.reset(); - - ASSERT_OK(EncryptionProvider::CreateFromString(config_options_, "1://test", - &provider)); - ASSERT_NE(provider, nullptr); - ASSERT_EQ(provider->Name(), CTR); - ASSERT_OK(provider->PrepareOptions(config_options_)); - ASSERT_OK(provider->ValidateOptions(DBOptions(), ColumnFamilyOptions())); - cipher = provider->GetOptions>("Cipher"); - ASSERT_NE(cipher, nullptr); - ASSERT_NE(cipher->get(), nullptr); - ASSERT_STREQ(cipher->get()->Name(), "ROT13"); - provider.reset(); - - ASSERT_OK(EncryptionProvider::CreateFromString( - config_options_, "id=" + CTR + "; cipher=ROT13", &provider)); - ASSERT_NE(provider, nullptr); - ASSERT_EQ(provider->Name(), CTR); - cipher = provider->GetOptions>("Cipher"); - ASSERT_NE(cipher, nullptr); - ASSERT_NE(cipher->get(), nullptr); - ASSERT_STREQ(cipher->get()->Name(), "ROT13"); - provider.reset(); -} - -TEST_F(CreateEnvTest, LoadROT13Cipher) { - std::shared_ptr cipher; - // Test a provider with no cipher - ASSERT_OK(BlockCipher::CreateFromString(config_options_, "ROT13", &cipher)); - ASSERT_NE(cipher, nullptr); - ASSERT_STREQ(cipher->Name(), "ROT13"); -} - -TEST_F(CreateEnvTest, CreateDefaultSystemClock) { - std::shared_ptr clock, copy; - ASSERT_OK(SystemClock::CreateFromString(config_options_, - SystemClock::kDefaultName(), &clock)); - ASSERT_NE(clock, nullptr); - ASSERT_EQ(clock, SystemClock::Default()); - std::string opts_str = clock->ToString(config_options_); - std::string mismatch; - ASSERT_OK(SystemClock::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(clock->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -TEST_F(CreateEnvTest, CreateMockSystemClock) { - std::shared_ptr mock, copy; - - config_options_.registry->AddLibrary("test")->AddFactory( - MockSystemClock::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockSystemClock(nullptr)); - return guard->get(); - }); - ASSERT_OK(SystemClock::CreateFromString( - config_options_, EmulatedSystemClock::kClassName(), &mock)); - ASSERT_NE(mock, nullptr); - ASSERT_STREQ(mock->Name(), EmulatedSystemClock::kClassName()); - ASSERT_EQ(mock->Inner(), SystemClock::Default().get()); - std::string opts_str = mock->ToString(config_options_); - std::string mismatch; - ASSERT_OK(SystemClock::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(mock->AreEquivalent(config_options_, copy.get(), &mismatch)); - - std::string id = std::string("id=") + EmulatedSystemClock::kClassName() + - ";target=" + MockSystemClock::kClassName(); - - ASSERT_OK(SystemClock::CreateFromString(config_options_, id, &mock)); - ASSERT_NE(mock, nullptr); - ASSERT_STREQ(mock->Name(), EmulatedSystemClock::kClassName()); - ASSERT_NE(mock->Inner(), nullptr); - ASSERT_STREQ(mock->Inner()->Name(), MockSystemClock::kClassName()); - ASSERT_EQ(mock->Inner()->Inner(), SystemClock::Default().get()); - opts_str = mock->ToString(config_options_); - ASSERT_OK(SystemClock::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(mock->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_OK(SystemClock::CreateFromString( - config_options_, EmulatedSystemClock::kClassName(), &mock)); -} - -TEST_F(CreateEnvTest, CreateReadOnlyFileSystem) { - std::shared_ptr fs, copy; - - ASSERT_OK(FileSystem::CreateFromString( - config_options_, ReadOnlyFileSystem::kClassName(), &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), ReadOnlyFileSystem::kClassName()); - ASSERT_EQ(fs->Inner(), FileSystem::Default().get()); - - std::string opts_str = fs->ToString(config_options_); - std::string mismatch; - - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); - - ASSERT_OK(FileSystem::CreateFromString( - config_options_, - std::string("id=") + ReadOnlyFileSystem::kClassName() + - "; target=" + TimedFileSystem::kClassName(), - &fs)); - ASSERT_NE(fs, nullptr); - opts_str = fs->ToString(config_options_); - ASSERT_STREQ(fs->Name(), ReadOnlyFileSystem::kClassName()); - ASSERT_NE(fs->Inner(), nullptr); - ASSERT_STREQ(fs->Inner()->Name(), TimedFileSystem::kClassName()); - ASSERT_EQ(fs->Inner()->Inner(), FileSystem::Default().get()); - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -TEST_F(CreateEnvTest, CreateTimedFileSystem) { - std::shared_ptr fs, copy; - - ASSERT_OK(FileSystem::CreateFromString(config_options_, - TimedFileSystem::kClassName(), &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), TimedFileSystem::kClassName()); - ASSERT_EQ(fs->Inner(), FileSystem::Default().get()); - - std::string opts_str = fs->ToString(config_options_); - std::string mismatch; - - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); - - ASSERT_OK(FileSystem::CreateFromString( - config_options_, - std::string("id=") + TimedFileSystem::kClassName() + - "; target=" + ReadOnlyFileSystem::kClassName(), - &fs)); - ASSERT_NE(fs, nullptr); - opts_str = fs->ToString(config_options_); - ASSERT_STREQ(fs->Name(), TimedFileSystem::kClassName()); - ASSERT_NE(fs->Inner(), nullptr); - ASSERT_STREQ(fs->Inner()->Name(), ReadOnlyFileSystem::kClassName()); - ASSERT_EQ(fs->Inner()->Inner(), FileSystem::Default().get()); - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -TEST_F(CreateEnvTest, CreateCountedFileSystem) { - std::shared_ptr fs, copy; - - ASSERT_OK(FileSystem::CreateFromString(config_options_, - CountedFileSystem::kClassName(), &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), CountedFileSystem::kClassName()); - ASSERT_EQ(fs->Inner(), FileSystem::Default().get()); - - std::string opts_str = fs->ToString(config_options_); - std::string mismatch; - - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); - - ASSERT_OK(FileSystem::CreateFromString( - config_options_, - std::string("id=") + CountedFileSystem::kClassName() + - "; target=" + ReadOnlyFileSystem::kClassName(), - &fs)); - ASSERT_NE(fs, nullptr); - opts_str = fs->ToString(config_options_); - ASSERT_STREQ(fs->Name(), CountedFileSystem::kClassName()); - ASSERT_NE(fs->Inner(), nullptr); - ASSERT_STREQ(fs->Inner()->Name(), ReadOnlyFileSystem::kClassName()); - ASSERT_EQ(fs->Inner()->Inner(), FileSystem::Default().get()); - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -#ifndef OS_WIN -TEST_F(CreateEnvTest, CreateChrootFileSystem) { - std::shared_ptr fs, copy; - auto tmp_dir = test::TmpDir(Env::Default()); - // The Chroot FileSystem has a required "chroot_dir" option. - ASSERT_NOK(FileSystem::CreateFromString(config_options_, - ChrootFileSystem::kClassName(), &fs)); - - // ChrootFileSystem fails with an invalid directory - ASSERT_NOK(FileSystem::CreateFromString( - config_options_, - std::string("chroot_dir=/No/Such/Directory; id=") + - ChrootFileSystem::kClassName(), - &fs)); - std::string chroot_opts = std::string("chroot_dir=") + tmp_dir + - std::string("; id=") + - ChrootFileSystem::kClassName(); - - // Create a valid ChrootFileSystem with an inner Default - ASSERT_OK(FileSystem::CreateFromString(config_options_, chroot_opts, &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), ChrootFileSystem::kClassName()); - ASSERT_EQ(fs->Inner(), FileSystem::Default().get()); - std::string opts_str = fs->ToString(config_options_); - std::string mismatch; - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); - - // Create a valid ChrootFileSystem with an inner TimedFileSystem - ASSERT_OK(FileSystem::CreateFromString( - config_options_, - chroot_opts + "; target=" + TimedFileSystem::kClassName(), &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), ChrootFileSystem::kClassName()); - ASSERT_NE(fs->Inner(), nullptr); - ASSERT_STREQ(fs->Inner()->Name(), TimedFileSystem::kClassName()); - ASSERT_EQ(fs->Inner()->Inner(), FileSystem::Default().get()); - opts_str = fs->ToString(config_options_); - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); - - // Create a TimedFileSystem with an inner ChrootFileSystem - ASSERT_OK(FileSystem::CreateFromString( - config_options_, - "target={" + chroot_opts + "}; id=" + TimedFileSystem::kClassName(), - &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), TimedFileSystem::kClassName()); - ASSERT_NE(fs->Inner(), nullptr); - ASSERT_STREQ(fs->Inner()->Name(), ChrootFileSystem::kClassName()); - ASSERT_EQ(fs->Inner()->Inner(), FileSystem::Default().get()); - opts_str = fs->ToString(config_options_); - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); -} -#endif // OS_WIN - -TEST_F(CreateEnvTest, CreateEncryptedFileSystem) { - std::shared_ptr fs, copy; - - std::string base_opts = - std::string("provider=1://test; id=") + EncryptedFileSystem::kClassName(); - // The EncryptedFileSystem requires a "provider" option. - ASSERT_NOK(FileSystem::CreateFromString( - config_options_, EncryptedFileSystem::kClassName(), &fs)); - - ASSERT_OK(FileSystem::CreateFromString(config_options_, base_opts, &fs)); - - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), EncryptedFileSystem::kClassName()); - ASSERT_EQ(fs->Inner(), FileSystem::Default().get()); - std::string opts_str = fs->ToString(config_options_); - std::string mismatch; - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_OK(FileSystem::CreateFromString( - config_options_, base_opts + "; target=" + TimedFileSystem::kClassName(), - &fs)); - ASSERT_NE(fs, nullptr); - ASSERT_STREQ(fs->Name(), EncryptedFileSystem::kClassName()); - ASSERT_NE(fs->Inner(), nullptr); - ASSERT_STREQ(fs->Inner()->Name(), TimedFileSystem::kClassName()); - ASSERT_EQ(fs->Inner()->Inner(), FileSystem::Default().get()); - opts_str = fs->ToString(config_options_); - ASSERT_OK(FileSystem::CreateFromString(config_options_, opts_str, ©)); - ASSERT_TRUE(fs->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - - -namespace { - -constexpr size_t kThreads = 8; -constexpr size_t kIdsPerThread = 1000; - -// This is a mini-stress test to check for duplicates in functions like -// GenerateUniqueId() -template > -struct NoDuplicateMiniStressTest { - std::unordered_set ids; - std::mutex mutex; - Env* env; - - NoDuplicateMiniStressTest() { env = Env::Default(); } - - virtual ~NoDuplicateMiniStressTest() {} - - void Run() { - std::array threads; - for (size_t i = 0; i < kThreads; ++i) { - threads[i] = std::thread([&]() { ThreadFn(); }); - } - for (auto& thread : threads) { - thread.join(); - } - // All must be unique - ASSERT_EQ(ids.size(), kThreads * kIdsPerThread); - } - - void ThreadFn() { - std::array my_ids; - // Generate in parallel threads as fast as possible - for (size_t i = 0; i < kIdsPerThread; ++i) { - my_ids[i] = Generate(); - } - // Now collate - std::lock_guard lock(mutex); - for (auto& id : my_ids) { - ids.insert(id); - } - } - - virtual IdType Generate() = 0; -}; - -void VerifyRfcUuids(const std::unordered_set& uuids) { - if (uuids.empty()) { - return; - } -} - -using uint64_pair_t = std::pair; -struct HashUint64Pair { - std::size_t operator()( - std::pair const& u) const noexcept { - // Assume suitable distribution already - return static_cast(u.first ^ u.second); - } -}; - -} // namespace - -TEST_F(EnvTest, GenerateUniqueId) { - struct MyStressTest : public NoDuplicateMiniStressTest { - std::string Generate() override { return env->GenerateUniqueId(); } - }; - - MyStressTest t; - t.Run(); - - // Basically verify RFC-4122 format - for (auto& uuid : t.ids) { - ASSERT_EQ(36U, uuid.size()); - ASSERT_EQ('-', uuid[8]); - ASSERT_EQ('-', uuid[13]); - ASSERT_EQ('-', uuid[18]); - ASSERT_EQ('-', uuid[23]); - } -} - -TEST_F(EnvTest, GenerateDbSessionId) { - struct MyStressTest : public NoDuplicateMiniStressTest { - std::string Generate() override { return DBImpl::GenerateDbSessionId(env); } - }; - - MyStressTest t; - t.Run(); - - // Basically verify session ID - for (auto& id : t.ids) { - ASSERT_EQ(20U, id.size()); - } -} - -constexpr bool kRequirePortGenerateRfcUuid = -#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_WIN) - true; -#else - false; -#endif - -TEST_F(EnvTest, PortGenerateRfcUuid) { - if (!kRequirePortGenerateRfcUuid) { - ROCKSDB_GTEST_SKIP("Not supported/expected on this platform"); - return; - } - struct MyStressTest : public NoDuplicateMiniStressTest { - std::string Generate() override { - std::string u; - assert(port::GenerateRfcUuid(&u)); - return u; - } - }; - - MyStressTest t; - t.Run(); - - // Extra verification on versions and variants - VerifyRfcUuids(t.ids); -} - -// Test the atomic, linear generation of GenerateRawUuid -TEST_F(EnvTest, GenerateRawUniqueId) { - struct MyStressTest - : public NoDuplicateMiniStressTest { - uint64_pair_t Generate() override { - uint64_pair_t p; - GenerateRawUniqueId(&p.first, &p.second); - return p; - } - }; - - MyStressTest t; - t.Run(); -} - -// Test that each entropy source ("track") is at least adequate -TEST_F(EnvTest, GenerateRawUniqueIdTrackPortUuidOnly) { - if (!kRequirePortGenerateRfcUuid) { - ROCKSDB_GTEST_SKIP("Not supported/expected on this platform"); - return; - } - - struct MyStressTest - : public NoDuplicateMiniStressTest { - uint64_pair_t Generate() override { - uint64_pair_t p; - TEST_GenerateRawUniqueId(&p.first, &p.second, false, true, true); - return p; - } - }; - - MyStressTest t; - t.Run(); -} - -TEST_F(EnvTest, GenerateRawUniqueIdTrackEnvDetailsOnly) { - struct MyStressTest - : public NoDuplicateMiniStressTest { - uint64_pair_t Generate() override { - uint64_pair_t p; - TEST_GenerateRawUniqueId(&p.first, &p.second, true, false, true); - return p; - } - }; - - MyStressTest t; - t.Run(); -} - -TEST_F(EnvTest, GenerateRawUniqueIdTrackRandomDeviceOnly) { - struct MyStressTest - : public NoDuplicateMiniStressTest { - uint64_pair_t Generate() override { - uint64_pair_t p; - TEST_GenerateRawUniqueId(&p.first, &p.second, true, true, false); - return p; - } - }; - - MyStressTest t; - t.Run(); -} - -TEST_F(EnvTest, SemiStructuredUniqueIdGenTest) { - // Must be thread safe and usable as a static - static SemiStructuredUniqueIdGen gen; - - struct MyStressTest - : public NoDuplicateMiniStressTest { - uint64_pair_t Generate() override { - uint64_pair_t p; - gen.GenerateNext(&p.first, &p.second); - return p; - } - }; - - MyStressTest t; - t.Run(); -} - -TEST_F(EnvTest, FailureToCreateLockFile) { - auto env = Env::Default(); - auto fs = env->GetFileSystem(); - std::string dir = test::PerThreadDBPath(env, "lockdir"); - std::string file = dir + "/lockfile"; - - // Ensure directory doesn't exist - ASSERT_OK(DestroyDir(env, dir)); - - // Make sure that we can acquire a file lock after the first attempt fails - FileLock* lock = nullptr; - ASSERT_NOK(fs->LockFile(file, IOOptions(), &lock, /*dbg*/ nullptr)); - ASSERT_FALSE(lock); - - ASSERT_OK(fs->CreateDir(dir, IOOptions(), /*dbg*/ nullptr)); - ASSERT_OK(fs->LockFile(file, IOOptions(), &lock, /*dbg*/ nullptr)); - ASSERT_OK(fs->UnlockFile(lock, IOOptions(), /*dbg*/ nullptr)); - - // Clean up - ASSERT_OK(DestroyDir(env, dir)); -} - -TEST_F(CreateEnvTest, CreateDefaultEnv) { - ConfigOptions options; - options.ignore_unsupported_options = false; - - std::shared_ptr guard; - Env* env = nullptr; - ASSERT_OK(Env::CreateFromString(options, "", &env)); - ASSERT_EQ(env, Env::Default()); - - env = nullptr; - ASSERT_OK(Env::CreateFromString(options, Env::kDefaultName(), &env)); - ASSERT_EQ(env, Env::Default()); - - env = nullptr; - ASSERT_OK(Env::CreateFromString(options, "", &env, &guard)); - ASSERT_EQ(env, Env::Default()); - ASSERT_EQ(guard, nullptr); - - env = nullptr; - ASSERT_OK(Env::CreateFromString(options, Env::kDefaultName(), &env, &guard)); - ASSERT_EQ(env, Env::Default()); - ASSERT_EQ(guard, nullptr); - - std::string opt_str = env->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env)); - ASSERT_EQ(env, Env::Default()); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, &guard)); - ASSERT_EQ(env, Env::Default()); - ASSERT_EQ(guard, nullptr); -} - -namespace { -class WrappedEnv : public EnvWrapper { - public: - explicit WrappedEnv(Env* t) : EnvWrapper(t) {} - explicit WrappedEnv(const std::shared_ptr& t) : EnvWrapper(t) {} - static const char* kClassName() { return "WrappedEnv"; } - const char* Name() const override { return kClassName(); } - static void Register(ObjectLibrary& lib, const std::string& /*arg*/) { - lib.AddFactory( - WrappedEnv::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new WrappedEnv(nullptr)); - return guard->get(); - }); - } -}; -} // namespace -TEST_F(CreateEnvTest, CreateMockEnv) { - ConfigOptions options; - options.ignore_unsupported_options = false; - WrappedEnv::Register(*(options.registry->AddLibrary("test")), ""); - std::shared_ptr guard, copy; - std::string opt_str; - - Env* env = nullptr; - ASSERT_NOK(Env::CreateFromString(options, MockEnv::kClassName(), &env)); - ASSERT_OK( - Env::CreateFromString(options, MockEnv::kClassName(), &env, &guard)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - opt_str = env->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(copy, guard); - std::string mismatch; - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); - guard.reset(MockEnv::Create(Env::Default(), SystemClock::Default())); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - std::unique_ptr wrapped_env(new WrappedEnv(Env::Default())); - guard.reset(MockEnv::Create(wrapped_env.get(), SystemClock::Default())); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - opt_str = copy->ToString(options); -} - -TEST_F(CreateEnvTest, CreateWrappedEnv) { - ConfigOptions options; - options.ignore_unsupported_options = false; - WrappedEnv::Register(*(options.registry->AddLibrary("test")), ""); - Env* env = nullptr; - std::shared_ptr guard, copy; - std::string opt_str; - std::string mismatch; - - ASSERT_NOK(Env::CreateFromString(options, WrappedEnv::kClassName(), &env)); - ASSERT_OK( - Env::CreateFromString(options, WrappedEnv::kClassName(), &env, &guard)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - ASSERT_FALSE(guard->AreEquivalent(options, Env::Default(), &mismatch)); - - opt_str = env->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(copy, guard); - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); - - guard.reset(new WrappedEnv(std::make_shared(Env::Default()))); - ASSERT_NE(guard.get(), env); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(copy, guard); - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); - - guard.reset(new WrappedEnv(std::make_shared( - std::make_shared(Env::Default())))); - ASSERT_NE(guard.get(), env); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(copy, guard); - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); -} - -TEST_F(CreateEnvTest, CreateCompositeEnv) { - ConfigOptions options; - options.ignore_unsupported_options = false; - std::shared_ptr guard, copy; - Env* env = nullptr; - std::string mismatch, opt_str; - - WrappedEnv::Register(*(options.registry->AddLibrary("test")), ""); - std::unique_ptr base(NewCompositeEnv(FileSystem::Default())); - std::unique_ptr wrapped(new WrappedEnv(Env::Default())); - std::shared_ptr timed_fs = - std::make_shared(FileSystem::Default()); - std::shared_ptr clock = - std::make_shared(SystemClock::Default()); - - opt_str = base->ToString(options); - ASSERT_NOK(Env::CreateFromString(options, opt_str, &env)); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, &guard)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - ASSERT_EQ(env->GetFileSystem(), FileSystem::Default()); - ASSERT_EQ(env->GetSystemClock(), SystemClock::Default()); - - base = NewCompositeEnv(timed_fs); - opt_str = base->ToString(options); - ASSERT_NOK(Env::CreateFromString(options, opt_str, &env)); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, &guard)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - ASSERT_NE(env->GetFileSystem(), FileSystem::Default()); - ASSERT_EQ(env->GetSystemClock(), SystemClock::Default()); - - env = nullptr; - guard.reset(new CompositeEnvWrapper(wrapped.get(), timed_fs)); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); - - env = nullptr; - guard.reset(new CompositeEnvWrapper(wrapped.get(), clock)); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); - - env = nullptr; - guard.reset(new CompositeEnvWrapper(wrapped.get(), timed_fs, clock)); - opt_str = guard->ToString(options); - ASSERT_OK(Env::CreateFromString(options, opt_str, &env, ©)); - ASSERT_NE(env, nullptr); - ASSERT_NE(env, Env::Default()); - ASSERT_TRUE(guard->AreEquivalent(options, copy.get(), &mismatch)); - - guard.reset(new CompositeEnvWrapper(nullptr, timed_fs, clock)); - ColumnFamilyOptions cf_opts; - DBOptions db_opts; - db_opts.env = guard.get(); - auto comp = db_opts.env->CheckedCast(); - ASSERT_NE(comp, nullptr); - ASSERT_EQ(comp->Inner(), nullptr); - ASSERT_NOK(ValidateOptions(db_opts, cf_opts)); - ASSERT_OK(db_opts.env->PrepareOptions(options)); - ASSERT_NE(comp->Inner(), nullptr); - ASSERT_OK(ValidateOptions(db_opts, cf_opts)); -} - -// Forward declaration -class ReadAsyncFS; - -struct MockIOHandle { - std::function cb; - void* cb_arg; - bool create_io_error; -}; - -// ReadAsyncFS and ReadAsyncRandomAccessFile mocks the FS doing asynchronous -// reads by creating threads that submit read requests and then calling Poll API -// to obtain those results. -class ReadAsyncRandomAccessFile : public FSRandomAccessFileOwnerWrapper { - public: - ReadAsyncRandomAccessFile(ReadAsyncFS& fs, - std::unique_ptr& file) - : FSRandomAccessFileOwnerWrapper(std::move(file)), fs_(fs) {} - - IOStatus ReadAsync(FSReadRequest& req, const IOOptions& opts, - std::function cb, - void* cb_arg, void** io_handle, IOHandleDeleter* del_fn, - IODebugContext* dbg) override; - - private: - ReadAsyncFS& fs_; - std::unique_ptr file_; - int counter = 0; -}; - -class ReadAsyncFS : public FileSystemWrapper { - public: - explicit ReadAsyncFS(const std::shared_ptr& wrapped) - : FileSystemWrapper(wrapped) {} - - static const char* kClassName() { return "ReadAsyncFS"; } - const char* Name() const override { return kClassName(); } - - IOStatus NewRandomAccessFile(const std::string& fname, - const FileOptions& opts, - std::unique_ptr* result, - IODebugContext* dbg) override { - std::unique_ptr file; - IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg); - EXPECT_OK(s); - result->reset(new ReadAsyncRandomAccessFile(*this, file)); - return s; - } - - IOStatus Poll(std::vector& io_handles, - size_t /*min_completions*/) override { - // Wait for the threads completion. - for (auto& t : workers) { - t.join(); - } - - for (size_t i = 0; i < io_handles.size(); i++) { - MockIOHandle* handle = static_cast(io_handles[i]); - if (handle->create_io_error) { - FSReadRequest req; - req.status = IOStatus::IOError(); - handle->cb(req, handle->cb_arg); - } - } - return IOStatus::OK(); - } - - std::vector workers; -}; - -IOStatus ReadAsyncRandomAccessFile::ReadAsync( - FSReadRequest& req, const IOOptions& opts, - std::function cb, void* cb_arg, - void** io_handle, IOHandleDeleter* del_fn, IODebugContext* dbg) { - IOHandleDeleter deletefn = [](void* args) -> void { - delete (static_cast(args)); - args = nullptr; - }; - *del_fn = deletefn; - - // Allocate and populate io_handle. - MockIOHandle* mock_handle = new MockIOHandle(); - bool create_io_error = false; - if (counter % 2) { - create_io_error = true; - } - mock_handle->create_io_error = create_io_error; - mock_handle->cb = cb; - mock_handle->cb_arg = cb_arg; - *io_handle = static_cast(mock_handle); - counter++; - - // Submit read request asynchronously. - std::function submit_request = - [&opts, cb, cb_arg, dbg, create_io_error, this](FSReadRequest _req) { - if (!create_io_error) { - _req.status = target()->Read(_req.offset, _req.len, opts, - &(_req.result), _req.scratch, dbg); - cb(_req, cb_arg); - } - }; - - fs_.workers.emplace_back(submit_request, req); - return IOStatus::OK(); -} - -class TestAsyncRead : public testing::Test { - public: - TestAsyncRead() { env_ = Env::Default(); } - Env* env_; -}; - -// Tests the default implementation of ReadAsync API. -TEST_F(TestAsyncRead, ReadAsync) { - EnvOptions soptions; - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem()); - - std::string fname = test::PerThreadDBPath(env_, "testfile"); - - const size_t kSectorSize = 4096; - const size_t kNumSectors = 8; - - // 1. create & write to a file. - { - std::unique_ptr wfile; - ASSERT_OK( - fs->NewWritableFile(fname, FileOptions(), &wfile, nullptr /*dbg*/)); - - for (size_t i = 0; i < kNumSectors; ++i) { - auto data = NewAligned(kSectorSize * 8, static_cast(i + 1)); - Slice slice(data.get(), kSectorSize); - ASSERT_OK(wfile->Append(slice, IOOptions(), nullptr)); - } - ASSERT_OK(wfile->Close(IOOptions(), nullptr)); - } - // 2. Read file - { - std::unique_ptr file; - ASSERT_OK(fs->NewRandomAccessFile(fname, FileOptions(), &file, nullptr)); - - IOOptions opts; - std::vector io_handles(kNumSectors); - std::vector reqs(kNumSectors); - std::vector> data; - std::vector vals; - IOHandleDeleter del_fn; - uint64_t offset = 0; - - // Initialize read requests - for (size_t i = 0; i < kNumSectors; i++) { - reqs[i].offset = offset; - reqs[i].len = kSectorSize; - data.emplace_back(NewAligned(kSectorSize, 0)); - reqs[i].scratch = data.back().get(); - vals.push_back(i); - offset += kSectorSize; - } - - // callback function passed to async read. - std::function callback = - [&](const FSReadRequest& req, void* cb_arg) { - assert(cb_arg != nullptr); - size_t i = *(reinterpret_cast(cb_arg)); - reqs[i].offset = req.offset; - reqs[i].result = req.result; - reqs[i].status = req.status; - }; - - // Submit asynchronous read requests. - for (size_t i = 0; i < kNumSectors; i++) { - void* cb_arg = static_cast(&(vals[i])); - ASSERT_OK(file->ReadAsync(reqs[i], opts, callback, cb_arg, - &(io_handles[i]), &del_fn, nullptr)); - } - - // Poll for the submitted requests. - fs->Poll(io_handles, kNumSectors); - - // Check the status of read requests. - for (size_t i = 0; i < kNumSectors; i++) { - if (i % 2) { - ASSERT_EQ(reqs[i].status, IOStatus::IOError()); - } else { - auto buf = NewAligned(kSectorSize * 8, static_cast(i + 1)); - Slice expected_data(buf.get(), kSectorSize); - - ASSERT_EQ(reqs[i].offset, i * kSectorSize); - ASSERT_OK(reqs[i].status); - ASSERT_EQ(expected_data.ToString(), reqs[i].result.ToString()); - } - } - - // Delete io_handles. - for (size_t i = 0; i < io_handles.size(); i++) { - del_fn(io_handles[i]); - } - } -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/env/io_posix_test.cc b/env/io_posix_test.cc deleted file mode 100644 index 81ce50587..000000000 --- a/env/io_posix_test.cc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2020-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "test_util/testharness.h" - -#ifdef ROCKSDB_LIB_IO_POSIX -#include "env/io_posix.h" - -namespace ROCKSDB_NAMESPACE { - -#ifdef OS_LINUX -class LogicalBlockSizeCacheTest : public testing::Test {}; - -// Tests the caching behavior. -TEST_F(LogicalBlockSizeCacheTest, Cache) { - int ncall = 0; - auto get_fd_block_size = [&](int fd) { - ncall++; - return fd; - }; - std::map dir_fds{ - {"/", 0}, - {"/db", 1}, - {"/db1", 2}, - {"/db2", 3}, - }; - auto get_dir_block_size = [&](const std::string& dir, size_t* size) { - ncall++; - *size = dir_fds[dir]; - return Status::OK(); - }; - LogicalBlockSizeCache cache(get_fd_block_size, get_dir_block_size); - ASSERT_EQ(0, ncall); - ASSERT_EQ(0, cache.Size()); - - ASSERT_EQ(6, cache.GetLogicalBlockSize("/sst", 6)); - ASSERT_EQ(1, ncall); - ASSERT_EQ(7, cache.GetLogicalBlockSize("/db/sst1", 7)); - ASSERT_EQ(2, ncall); - ASSERT_EQ(8, cache.GetLogicalBlockSize("/db/sst2", 8)); - ASSERT_EQ(3, ncall); - - ASSERT_OK(cache.RefAndCacheLogicalBlockSize({"/", "/db1/", "/db2"})); - ASSERT_EQ(3, cache.Size()); - ASSERT_TRUE(cache.Contains("/")); - ASSERT_TRUE(cache.Contains("/db1")); - ASSERT_TRUE(cache.Contains("/db2")); - ASSERT_EQ(6, ncall); - // Block size for / is cached. - ASSERT_EQ(0, cache.GetLogicalBlockSize("/sst", 6)); - ASSERT_EQ(6, ncall); - // No cached size for /db. - ASSERT_EQ(7, cache.GetLogicalBlockSize("/db/sst1", 7)); - ASSERT_EQ(7, ncall); - ASSERT_EQ(8, cache.GetLogicalBlockSize("/db/sst2", 8)); - ASSERT_EQ(8, ncall); - // Block size for /db1 is cached. - ASSERT_EQ(2, cache.GetLogicalBlockSize("/db1/sst1", 4)); - ASSERT_EQ(8, ncall); - ASSERT_EQ(2, cache.GetLogicalBlockSize("/db1/sst2", 5)); - ASSERT_EQ(8, ncall); - // Block size for /db2 is cached. - ASSERT_EQ(3, cache.GetLogicalBlockSize("/db2/sst1", 6)); - ASSERT_EQ(8, ncall); - ASSERT_EQ(3, cache.GetLogicalBlockSize("/db2/sst2", 7)); - ASSERT_EQ(8, ncall); - - ASSERT_OK(cache.RefAndCacheLogicalBlockSize({"/db"})); - ASSERT_EQ(4, cache.Size()); - ASSERT_TRUE(cache.Contains("/")); - ASSERT_TRUE(cache.Contains("/db1")); - ASSERT_TRUE(cache.Contains("/db2")); - ASSERT_TRUE(cache.Contains("/db")); - - ASSERT_EQ(9, ncall); - // Block size for /db is cached. - ASSERT_EQ(1, cache.GetLogicalBlockSize("/db/sst1", 7)); - ASSERT_EQ(9, ncall); - ASSERT_EQ(1, cache.GetLogicalBlockSize("/db/sst2", 8)); - ASSERT_EQ(9, ncall); -} - -// Tests the reference counting behavior. -TEST_F(LogicalBlockSizeCacheTest, Ref) { - int ncall = 0; - auto get_fd_block_size = [&](int fd) { - ncall++; - return fd; - }; - std::map dir_fds{ - {"/db", 0}, - }; - auto get_dir_block_size = [&](const std::string& dir, size_t* size) { - ncall++; - *size = dir_fds[dir]; - return Status::OK(); - }; - LogicalBlockSizeCache cache(get_fd_block_size, get_dir_block_size); - - ASSERT_EQ(0, ncall); - - ASSERT_EQ(1, cache.GetLogicalBlockSize("/db/sst0", 1)); - ASSERT_EQ(1, ncall); - - ASSERT_OK(cache.RefAndCacheLogicalBlockSize({"/db"})); - ASSERT_EQ(2, ncall); - ASSERT_EQ(1, cache.GetRefCount("/db")); - // Block size for /db is cached. Ref count = 1. - ASSERT_EQ(0, cache.GetLogicalBlockSize("/db/sst1", 1)); - ASSERT_EQ(2, ncall); - - // Ref count = 2, but won't recompute the cached buffer size. - ASSERT_OK(cache.RefAndCacheLogicalBlockSize({"/db"})); - ASSERT_EQ(2, cache.GetRefCount("/db")); - ASSERT_EQ(2, ncall); - - // Ref count = 1. - cache.UnrefAndTryRemoveCachedLogicalBlockSize({"/db"}); - ASSERT_EQ(1, cache.GetRefCount("/db")); - // Block size for /db is still cached. - ASSERT_EQ(0, cache.GetLogicalBlockSize("/db/sst2", 1)); - ASSERT_EQ(2, ncall); - - // Ref count = 0 and cached buffer size for /db is removed. - cache.UnrefAndTryRemoveCachedLogicalBlockSize({"/db"}); - ASSERT_EQ(0, cache.Size()); - ASSERT_EQ(1, cache.GetLogicalBlockSize("/db/sst0", 1)); - ASSERT_EQ(3, ncall); -} -#endif - -} // namespace ROCKSDB_NAMESPACE -#endif - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/env/mock_env_test.cc b/env/mock_env_test.cc deleted file mode 100644 index be174bd73..000000000 --- a/env/mock_env_test.cc +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -// -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -#include "env/mock_env.h" - -#include -#include - -#include "rocksdb/env.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class MockEnvTest : public testing::Test { - public: - MockEnv* env_; - const EnvOptions soptions_; - - MockEnvTest() : env_(MockEnv::Create(Env::Default())) {} - ~MockEnvTest() override { delete env_; } -}; - -TEST_F(MockEnvTest, Corrupt) { - const std::string kGood = "this is a good string, synced to disk"; - const std::string kCorrupted = "this part may be corrupted"; - const std::string kFileName = "/dir/f"; - std::unique_ptr writable_file; - ASSERT_OK(env_->NewWritableFile(kFileName, &writable_file, soptions_)); - ASSERT_OK(writable_file->Append(kGood)); - ASSERT_TRUE(writable_file->GetFileSize() == kGood.size()); - - std::string scratch; - scratch.resize(kGood.size() + kCorrupted.size() + 16); - Slice result; - std::unique_ptr rand_file; - ASSERT_OK(env_->NewRandomAccessFile(kFileName, &rand_file, soptions_)); - ASSERT_OK(rand_file->Read(0, kGood.size(), &result, &(scratch[0]))); - ASSERT_EQ(result.compare(kGood), 0); - - // Sync + corrupt => no change - ASSERT_OK(writable_file->Fsync()); - ASSERT_OK(dynamic_cast(env_)->CorruptBuffer(kFileName)); - result.clear(); - ASSERT_OK(rand_file->Read(0, kGood.size(), &result, &(scratch[0]))); - ASSERT_EQ(result.compare(kGood), 0); - - // Add new data and corrupt it - ASSERT_OK(writable_file->Append(kCorrupted)); - ASSERT_TRUE(writable_file->GetFileSize() == kGood.size() + kCorrupted.size()); - result.clear(); - ASSERT_OK( - rand_file->Read(kGood.size(), kCorrupted.size(), &result, &(scratch[0]))); - ASSERT_EQ(result.compare(kCorrupted), 0); - // Corrupted - ASSERT_OK(dynamic_cast(env_)->CorruptBuffer(kFileName)); - result.clear(); - ASSERT_OK( - rand_file->Read(kGood.size(), kCorrupted.size(), &result, &(scratch[0]))); - ASSERT_NE(result.compare(kCorrupted), 0); -} - -TEST_F(MockEnvTest, FakeSleeping) { - int64_t now = 0; - auto s = env_->GetCurrentTime(&now); - ASSERT_OK(s); - env_->SleepForMicroseconds(3 * 1000 * 1000); - int64_t after_sleep = 0; - s = env_->GetCurrentTime(&after_sleep); - ASSERT_OK(s); - auto delta = after_sleep - now; - // this will be true unless test runs for 2 seconds - ASSERT_TRUE(delta == 3 || delta == 4); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index 39da06a85..000000000 --- a/examples/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -c_simple_example -column_families_example -compact_files_example -compaction_filter_example -multi_processes_example -optimistic_transaction_example -options_file_example -rocksdb_backup_restore_example -simple_example -transaction_example diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 0b93a6d8d..000000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -add_executable(simple_example - simple_example.cc) -target_link_libraries(simple_example - ${ROCKSDB_LIB}) - -add_executable(column_families_example - column_families_example.cc) -target_link_libraries(column_families_example - ${ROCKSDB_LIB}) - -add_executable(compact_files_example - compact_files_example.cc) -target_link_libraries(compact_files_example - ${ROCKSDB_LIB}) - -add_executable(c_simple_example - c_simple_example.c) -target_link_libraries(c_simple_example - ${ROCKSDB_LIB}) - -add_executable(optimistic_transaction_example - optimistic_transaction_example.cc) -target_link_libraries(optimistic_transaction_example - ${ROCKSDB_LIB}) - -add_executable(transaction_example - transaction_example.cc) -target_link_libraries(transaction_example - ${ROCKSDB_LIB}) - -add_executable(compaction_filter_example - compaction_filter_example.cc) -target_link_libraries(compaction_filter_example - ${ROCKSDB_LIB}) - -add_executable(options_file_example - options_file_example.cc) -target_link_libraries(options_file_example - ${ROCKSDB_LIB}) - -add_executable(multi_processes_example - EXCLUDE_FROM_ALL - multi_processes_example.cc) -target_link_libraries(multi_processes_example - ${ROCKSDB_LIB}) diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index b056508a6..000000000 --- a/examples/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -include ../make_config.mk - -ifndef DISABLE_JEMALLOC - ifdef JEMALLOC - PLATFORM_CXXFLAGS += -DROCKSDB_JEMALLOC -DJEMALLOC_NO_DEMANGLE - endif - EXEC_LDFLAGS := $(JEMALLOC_LIB) $(EXEC_LDFLAGS) -lpthread - PLATFORM_CXXFLAGS += $(JEMALLOC_INCLUDE) -endif - -ifneq ($(USE_RTTI), 1) - CXXFLAGS += -fno-rtti -endif - -CFLAGS += -Wstrict-prototypes - -.PHONY: clean librocksdb - -all: simple_example column_families_example compact_files_example c_simple_example optimistic_transaction_example transaction_example compaction_filter_example options_file_example rocksdb_backup_restore_example - -simple_example: librocksdb simple_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -column_families_example: librocksdb column_families_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -compaction_filter_example: librocksdb compaction_filter_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -compact_files_example: librocksdb compact_files_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -.c.o: - $(CC) $(CFLAGS) -c $< -o $@ -I../include - -c_simple_example: librocksdb c_simple_example.o - $(CXX) $@.o -o$@ ../librocksdb.a $(PLATFORM_LDFLAGS) $(EXEC_LDFLAGS) - -optimistic_transaction_example: librocksdb optimistic_transaction_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -transaction_example: librocksdb transaction_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -options_file_example: librocksdb options_file_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -multi_processes_example: librocksdb multi_processes_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -rocksdb_backup_restore_example: librocksdb rocksdb_backup_restore_example.cc - $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++17 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) - -clean: - rm -rf ./simple_example ./column_families_example ./compact_files_example ./compaction_filter_example ./c_simple_example c_simple_example.o ./optimistic_transaction_example ./transaction_example ./options_file_example ./multi_processes_example ./rocksdb_backup_restore_example - -librocksdb: - cd .. && $(MAKE) static_lib diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index f4ba2384b..000000000 --- a/examples/README.md +++ /dev/null @@ -1,2 +0,0 @@ -1. Compile RocksDB first by executing `make static_lib` in parent dir -2. Compile all examples: `cd examples/; make all` diff --git a/examples/c_simple_example.c b/examples/c_simple_example.c deleted file mode 100644 index fe2f917b4..000000000 --- a/examples/c_simple_example.c +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include -#include - -#include "rocksdb/c.h" - -#if defined(OS_WIN) -#include -#else -#include // sysconf() - get CPU count -#endif - -#if defined(OS_WIN) -const char DBPath[] = "C:\\Windows\\TEMP\\rocksdb_c_simple_example"; -const char DBBackupPath[] = - "C:\\Windows\\TEMP\\rocksdb_c_simple_example_backup"; -#else -const char DBPath[] = "/tmp/rocksdb_c_simple_example"; -const char DBBackupPath[] = "/tmp/rocksdb_c_simple_example_backup"; -#endif - -int main(int argc, char **argv) { - rocksdb_t *db; - rocksdb_backup_engine_t *be; - rocksdb_options_t *options = rocksdb_options_create(); - // Optimize RocksDB. This is the easiest way to - // get RocksDB to perform well. -#if defined(OS_WIN) - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - long cpus = system_info.dwNumberOfProcessors; -#else - long cpus = sysconf(_SC_NPROCESSORS_ONLN); -#endif - // Set # of online cores - rocksdb_options_increase_parallelism(options, (int)(cpus)); - rocksdb_options_optimize_level_style_compaction(options, 0); - // create the DB if it's not already present - rocksdb_options_set_create_if_missing(options, 1); - - // open DB - char *err = NULL; - db = rocksdb_open(options, DBPath, &err); - assert(!err); - - // open Backup Engine that we will use for backing up our database - be = rocksdb_backup_engine_open(options, DBBackupPath, &err); - assert(!err); - - // Put key-value - rocksdb_writeoptions_t *writeoptions = rocksdb_writeoptions_create(); - const char key[] = "key"; - const char *value = "value"; - rocksdb_put(db, writeoptions, key, strlen(key), value, strlen(value) + 1, - &err); - assert(!err); - // Get value - rocksdb_readoptions_t *readoptions = rocksdb_readoptions_create(); - size_t len; - char *returned_value = - rocksdb_get(db, readoptions, key, strlen(key), &len, &err); - assert(!err); - assert(strcmp(returned_value, "value") == 0); - free(returned_value); - - // create new backup in a directory specified by DBBackupPath - rocksdb_backup_engine_create_new_backup(be, db, &err); - assert(!err); - - rocksdb_close(db); - - // If something is wrong, you might want to restore data from last backup - rocksdb_restore_options_t *restore_options = rocksdb_restore_options_create(); - rocksdb_backup_engine_restore_db_from_latest_backup(be, DBPath, DBPath, - restore_options, &err); - assert(!err); - rocksdb_restore_options_destroy(restore_options); - - db = rocksdb_open(options, DBPath, &err); - assert(!err); - - // cleanup - rocksdb_writeoptions_destroy(writeoptions); - rocksdb_readoptions_destroy(readoptions); - rocksdb_options_destroy(options); - rocksdb_backup_engine_close(be); - rocksdb_close(db); - - return 0; -} diff --git a/examples/column_families_example.cc b/examples/column_families_example.cc deleted file mode 100644 index 3828d3fb3..000000000 --- a/examples/column_families_example.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -#include -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_column_families_example"; -#else -std::string kDBPath = "/tmp/rocksdb_column_families_example"; -#endif - -using ROCKSDB_NAMESPACE::ColumnFamilyDescriptor; -using ROCKSDB_NAMESPACE::ColumnFamilyHandle; -using ROCKSDB_NAMESPACE::ColumnFamilyOptions; -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::DBOptions; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Slice; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::WriteBatch; -using ROCKSDB_NAMESPACE::WriteOptions; - -int main() { - // open DB - Options options; - options.create_if_missing = true; - DB* db; - Status s = DB::Open(options, kDBPath, &db); - assert(s.ok()); - - // create column family - ColumnFamilyHandle* cf; - s = db->CreateColumnFamily(ColumnFamilyOptions(), "new_cf", &cf); - assert(s.ok()); - - // close DB - s = db->DestroyColumnFamilyHandle(cf); - assert(s.ok()); - delete db; - - // open DB with two column families - std::vector column_families; - // have to open default column family - column_families.push_back(ColumnFamilyDescriptor( - ROCKSDB_NAMESPACE::kDefaultColumnFamilyName, ColumnFamilyOptions())); - // open the new one, too - column_families.push_back( - ColumnFamilyDescriptor("new_cf", ColumnFamilyOptions())); - std::vector handles; - s = DB::Open(DBOptions(), kDBPath, column_families, &handles, &db); - assert(s.ok()); - - // put and get from non-default column family - s = db->Put(WriteOptions(), handles[1], Slice("key"), Slice("value")); - assert(s.ok()); - std::string value; - s = db->Get(ReadOptions(), handles[1], Slice("key"), &value); - assert(s.ok()); - - // atomic write - WriteBatch batch; - batch.Put(handles[0], Slice("key2"), Slice("value2")); - batch.Put(handles[1], Slice("key3"), Slice("value3")); - batch.Delete(handles[0], Slice("key")); - s = db->Write(WriteOptions(), &batch); - assert(s.ok()); - - // drop column family - s = db->DropColumnFamily(handles[1]); - assert(s.ok()); - - // close db - for (auto handle : handles) { - s = db->DestroyColumnFamilyHandle(handle); - assert(s.ok()); - } - delete db; - - return 0; -} diff --git a/examples/compact_files_example.cc b/examples/compact_files_example.cc deleted file mode 100644 index 1ecf8c794..000000000 --- a/examples/compact_files_example.cc +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// An example code demonstrating how to use CompactFiles, EventListener, -// and GetColumnFamilyMetaData APIs to implement custom compaction algorithm. - -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/options.h" - -using ROCKSDB_NAMESPACE::ColumnFamilyMetaData; -using ROCKSDB_NAMESPACE::CompactionOptions; -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::EventListener; -using ROCKSDB_NAMESPACE::FlushJobInfo; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::WriteOptions; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_compact_files_example"; -#else -std::string kDBPath = "/tmp/rocksdb_compact_files_example"; -#endif - -struct CompactionTask; - -// This is an example interface of external-compaction algorithm. -// Compaction algorithm can be implemented outside the core-RocksDB -// code by using the pluggable compaction APIs that RocksDb provides. -class Compactor : public EventListener { - public: - // Picks and returns a compaction task given the specified DB - // and column family. It is the caller's responsibility to - // destroy the returned CompactionTask. Returns "nullptr" - // if it cannot find a proper compaction task. - virtual CompactionTask* PickCompaction(DB* db, - const std::string& cf_name) = 0; - - // Schedule and run the specified compaction task in background. - virtual void ScheduleCompaction(CompactionTask* task) = 0; -}; - -// Example structure that describes a compaction task. -struct CompactionTask { - CompactionTask(DB* _db, Compactor* _compactor, - const std::string& _column_family_name, - const std::vector& _input_file_names, - const int _output_level, - const CompactionOptions& _compact_options, bool _retry_on_fail) - : db(_db), - compactor(_compactor), - column_family_name(_column_family_name), - input_file_names(_input_file_names), - output_level(_output_level), - compact_options(_compact_options), - retry_on_fail(_retry_on_fail) {} - DB* db; - Compactor* compactor; - const std::string& column_family_name; - std::vector input_file_names; - int output_level; - CompactionOptions compact_options; - bool retry_on_fail; -}; - -// A simple compaction algorithm that always compacts everything -// to the highest level whenever possible. -class FullCompactor : public Compactor { - public: - explicit FullCompactor(const Options options) : options_(options) { - compact_options_.compression = options_.compression; - compact_options_.output_file_size_limit = options_.target_file_size_base; - } - - // When flush happens, it determines whether to trigger compaction. If - // triggered_writes_stop is true, it will also set the retry flag of - // compaction-task to true. - void OnFlushCompleted(DB* db, const FlushJobInfo& info) override { - CompactionTask* task = PickCompaction(db, info.cf_name); - if (task != nullptr) { - if (info.triggered_writes_stop) { - task->retry_on_fail = true; - } - // Schedule compaction in a different thread. - ScheduleCompaction(task); - } - } - - // Always pick a compaction which includes all files whenever possible. - CompactionTask* PickCompaction(DB* db, const std::string& cf_name) override { - ColumnFamilyMetaData cf_meta; - db->GetColumnFamilyMetaData(&cf_meta); - - std::vector input_file_names; - for (auto level : cf_meta.levels) { - for (auto file : level.files) { - if (file.being_compacted) { - return nullptr; - } - input_file_names.push_back(file.name); - } - } - return new CompactionTask(db, this, cf_name, input_file_names, - options_.num_levels - 1, compact_options_, false); - } - - // Schedule the specified compaction task in background. - void ScheduleCompaction(CompactionTask* task) override { - options_.env->Schedule(&FullCompactor::CompactFiles, task); - } - - static void CompactFiles(void* arg) { - std::unique_ptr task( - reinterpret_cast(arg)); - assert(task); - assert(task->db); - Status s = task->db->CompactFiles( - task->compact_options, task->input_file_names, task->output_level); - printf("CompactFiles() finished with status %s\n", s.ToString().c_str()); - if (!s.ok() && !s.IsIOError() && task->retry_on_fail) { - // If a compaction task with its retry_on_fail=true failed, - // try to schedule another compaction in case the reason - // is not an IO error. - CompactionTask* new_task = - task->compactor->PickCompaction(task->db, task->column_family_name); - task->compactor->ScheduleCompaction(new_task); - } - } - - private: - Options options_; - CompactionOptions compact_options_; -}; - -int main() { - Options options; - options.create_if_missing = true; - // Disable RocksDB background compaction. - options.compaction_style = ROCKSDB_NAMESPACE::kCompactionStyleNone; - // Small slowdown and stop trigger for experimental purpose. - options.level0_slowdown_writes_trigger = 3; - options.level0_stop_writes_trigger = 5; - options.IncreaseParallelism(5); - options.listeners.emplace_back(new FullCompactor(options)); - - DB* db = nullptr; - ROCKSDB_NAMESPACE::DestroyDB(kDBPath, options); - Status s = DB::Open(options, kDBPath, &db); - assert(s.ok()); - assert(db); - - // if background compaction is not working, write will stall - // because of options.level0_stop_writes_trigger - for (int i = 1000; i < 99999; ++i) { - db->Put(WriteOptions(), std::to_string(i), - std::string(500, 'a' + (i % 26))); - } - - // verify the values are still there - std::string value; - for (int i = 1000; i < 99999; ++i) { - db->Get(ReadOptions(), std::to_string(i), &value); - assert(value == std::string(500, 'a' + (i % 26))); - } - - // close the db. - delete db; - - return 0; -} diff --git a/examples/compaction_filter_example.cc b/examples/compaction_filter_example.cc deleted file mode 100644 index ed1ada823..000000000 --- a/examples/compaction_filter_example.cc +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/compaction_filter.h" -#include "rocksdb/db.h" -#include "rocksdb/merge_operator.h" -#include "rocksdb/options.h" - -class MyMerge : public ROCKSDB_NAMESPACE::MergeOperator { - public: - virtual bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { - merge_out->new_value.clear(); - if (merge_in.existing_value != nullptr) { - merge_out->new_value.assign(merge_in.existing_value->data(), - merge_in.existing_value->size()); - } - for (const ROCKSDB_NAMESPACE::Slice& m : merge_in.operand_list) { - fprintf(stderr, "Merge(%s)\n", m.ToString().c_str()); - // the compaction filter filters out bad values - assert(m.ToString() != "bad"); - merge_out->new_value.assign(m.data(), m.size()); - } - return true; - } - - const char* Name() const override { return "MyMerge"; } -}; - -class MyFilter : public ROCKSDB_NAMESPACE::CompactionFilter { - public: - bool Filter(int level, const ROCKSDB_NAMESPACE::Slice& key, - const ROCKSDB_NAMESPACE::Slice& existing_value, - std::string* new_value, bool* value_changed) const override { - fprintf(stderr, "Filter(%s)\n", key.ToString().c_str()); - ++count_; - assert(*value_changed == false); - return false; - } - - bool FilterMergeOperand( - int level, const ROCKSDB_NAMESPACE::Slice& key, - const ROCKSDB_NAMESPACE::Slice& existing_value) const override { - fprintf(stderr, "FilterMerge(%s)\n", key.ToString().c_str()); - ++merge_count_; - return existing_value == "bad"; - } - - const char* Name() const override { return "MyFilter"; } - - mutable int count_ = 0; - mutable int merge_count_ = 0; -}; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksmergetest"; -std::string kRemoveDirCommand = "rmdir /Q /S "; -#else -std::string kDBPath = "/tmp/rocksmergetest"; -std::string kRemoveDirCommand = "rm -rf "; -#endif - -int main() { - ROCKSDB_NAMESPACE::DB* raw_db; - ROCKSDB_NAMESPACE::Status status; - - MyFilter filter; - - std::string rm_cmd = kRemoveDirCommand + kDBPath; - int ret = system(rm_cmd.c_str()); - if (ret != 0) { - fprintf(stderr, "Error deleting %s, code: %d\n", kDBPath.c_str(), ret); - } - ROCKSDB_NAMESPACE::Options options; - options.create_if_missing = true; - options.merge_operator.reset(new MyMerge); - options.compaction_filter = &filter; - status = ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &raw_db); - assert(status.ok()); - std::unique_ptr db(raw_db); - - ROCKSDB_NAMESPACE::WriteOptions wopts; - db->Merge(wopts, "0", "bad"); // This is filtered out - db->Merge(wopts, "1", "data1"); - db->Merge(wopts, "1", "bad"); - db->Merge(wopts, "1", "data2"); - db->Merge(wopts, "1", "bad"); - db->Merge(wopts, "3", "data3"); - db->CompactRange(ROCKSDB_NAMESPACE::CompactRangeOptions(), nullptr, nullptr); - fprintf(stderr, "filter.count_ = %d\n", filter.count_); - assert(filter.count_ == 0); - fprintf(stderr, "filter.merge_count_ = %d\n", filter.merge_count_); - assert(filter.merge_count_ == 6); -} diff --git a/examples/multi_processes_example.cc b/examples/multi_processes_example.cc deleted file mode 100644 index 93c54d755..000000000 --- a/examples/multi_processes_example.cc +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -// How to use this example -// Open two terminals, in one of them, run `./multi_processes_example 0` to -// start a process running the primary instance. This will create a new DB in -// kDBPath. The process will run for a while inserting keys to the normal -// RocksDB database. -// Next, go to the other terminal and run `./multi_processes_example 1` to -// start a process running the secondary instance. This will create a secondary -// instance following the aforementioned primary instance. This process will -// run for a while, tailing the logs of the primary. After process with primary -// instance exits, this process will keep running until you hit 'CTRL+C'. - -#include -#include -#include -#include -#include -#include -#include -#include - -// TODO: port this example to other systems. It should be straightforward for -// POSIX-compliant systems. -#if defined(OS_LINUX) -#include -#include -#include -#include -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" - -using ROCKSDB_NAMESPACE::ColumnFamilyDescriptor; -using ROCKSDB_NAMESPACE::ColumnFamilyHandle; -using ROCKSDB_NAMESPACE::ColumnFamilyOptions; -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::FlushOptions; -using ROCKSDB_NAMESPACE::Iterator; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Slice; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::WriteOptions; - -const std::string kDBPath = "/tmp/rocksdb_multi_processes_example"; -const std::string kPrimaryStatusFile = - "/tmp/rocksdb_multi_processes_example_primary_status"; -const uint64_t kMaxKey = 600000; -const size_t kMaxValueLength = 256; -const size_t kNumKeysPerFlush = 1000; - -const std::vector& GetColumnFamilyNames() { - static std::vector column_family_names = { - ROCKSDB_NAMESPACE::kDefaultColumnFamilyName, "pikachu"}; - return column_family_names; -} - -inline bool IsLittleEndian() { - uint32_t x = 1; - return *reinterpret_cast(&x) != 0; -} - -static std::atomic& ShouldSecondaryWait() { - static std::atomic should_secondary_wait{1}; - return should_secondary_wait; -} - -static std::string Key(uint64_t k) { - std::string ret; - if (IsLittleEndian()) { - ret.append(reinterpret_cast(&k), sizeof(k)); - } else { - char buf[sizeof(k)]; - buf[0] = k & 0xff; - buf[1] = (k >> 8) & 0xff; - buf[2] = (k >> 16) & 0xff; - buf[3] = (k >> 24) & 0xff; - buf[4] = (k >> 32) & 0xff; - buf[5] = (k >> 40) & 0xff; - buf[6] = (k >> 48) & 0xff; - buf[7] = (k >> 56) & 0xff; - ret.append(buf, sizeof(k)); - } - size_t i = 0, j = ret.size() - 1; - while (i < j) { - char tmp = ret[i]; - ret[i] = ret[j]; - ret[j] = tmp; - ++i; - --j; - } - return ret; -} - -static uint64_t Key(std::string key) { - assert(key.size() == sizeof(uint64_t)); - size_t i = 0, j = key.size() - 1; - while (i < j) { - char tmp = key[i]; - key[i] = key[j]; - key[j] = tmp; - ++i; - --j; - } - uint64_t ret = 0; - if (IsLittleEndian()) { - memcpy(&ret, key.c_str(), sizeof(uint64_t)); - } else { - const char* buf = key.c_str(); - ret |= static_cast(buf[0]); - ret |= (static_cast(buf[1]) << 8); - ret |= (static_cast(buf[2]) << 16); - ret |= (static_cast(buf[3]) << 24); - ret |= (static_cast(buf[4]) << 32); - ret |= (static_cast(buf[5]) << 40); - ret |= (static_cast(buf[6]) << 48); - ret |= (static_cast(buf[7]) << 56); - } - return ret; -} - -static Slice GenerateRandomValue(const size_t max_length, char scratch[]) { - size_t sz = 1 + (std::rand() % max_length); - int rnd = std::rand(); - for (size_t i = 0; i != sz; ++i) { - scratch[i] = static_cast(rnd ^ i); - } - return Slice(scratch, sz); -} - -static bool ShouldCloseDB() { return true; } - -void CreateDB() { - long my_pid = static_cast(getpid()); - Options options; - Status s = ROCKSDB_NAMESPACE::DestroyDB(kDBPath, options); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to destroy DB: %s\n", my_pid, - s.ToString().c_str()); - assert(false); - } - options.create_if_missing = true; - DB* db = nullptr; - s = DB::Open(options, kDBPath, &db); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to open DB: %s\n", my_pid, - s.ToString().c_str()); - assert(false); - } - std::vector handles; - ColumnFamilyOptions cf_opts(options); - for (const auto& cf_name : GetColumnFamilyNames()) { - if (ROCKSDB_NAMESPACE::kDefaultColumnFamilyName != cf_name) { - ColumnFamilyHandle* handle = nullptr; - s = db->CreateColumnFamily(cf_opts, cf_name, &handle); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to create CF %s: %s\n", my_pid, - cf_name.c_str(), s.ToString().c_str()); - assert(false); - } - handles.push_back(handle); - } - } - fprintf(stdout, "[process %ld] Column families created\n", my_pid); - for (auto h : handles) { - delete h; - } - handles.clear(); - delete db; -} - -void RunPrimary() { - long my_pid = static_cast(getpid()); - fprintf(stdout, "[process %ld] Primary instance starts\n", my_pid); - CreateDB(); - std::srand(time(nullptr)); - DB* db = nullptr; - Options options; - options.create_if_missing = false; - std::vector column_families; - for (const auto& cf_name : GetColumnFamilyNames()) { - column_families.push_back(ColumnFamilyDescriptor(cf_name, options)); - } - std::vector handles; - WriteOptions write_opts; - char val_buf[kMaxValueLength] = {0}; - uint64_t curr_key = 0; - while (curr_key < kMaxKey) { - Status s; - if (nullptr == db) { - s = DB::Open(options, kDBPath, column_families, &handles, &db); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to open DB: %s\n", my_pid, - s.ToString().c_str()); - assert(false); - } - } - assert(nullptr != db); - assert(handles.size() == GetColumnFamilyNames().size()); - for (auto h : handles) { - assert(nullptr != h); - for (size_t i = 0; i != kNumKeysPerFlush; ++i) { - Slice key = Key(curr_key + static_cast(i)); - Slice value = GenerateRandomValue(kMaxValueLength, val_buf); - s = db->Put(write_opts, h, key, value); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to insert\n", my_pid); - assert(false); - } - } - s = db->Flush(FlushOptions(), h); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to flush\n", my_pid); - assert(false); - } - } - curr_key += static_cast(kNumKeysPerFlush); - if (ShouldCloseDB()) { - for (auto h : handles) { - delete h; - } - handles.clear(); - delete db; - db = nullptr; - } - } - if (nullptr != db) { - for (auto h : handles) { - delete h; - } - handles.clear(); - delete db; - db = nullptr; - } - fprintf(stdout, "[process %ld] Finished adding keys\n", my_pid); -} - -void secondary_instance_sigint_handler(int signal) { - ShouldSecondaryWait().store(0, std::memory_order_relaxed); - fprintf(stdout, "\n"); - fflush(stdout); -}; - -void RunSecondary() { - ::signal(SIGINT, secondary_instance_sigint_handler); - long my_pid = static_cast(getpid()); - const std::string kSecondaryPath = - "/tmp/rocksdb_multi_processes_example_secondary"; - // Create directory if necessary - if (nullptr == opendir(kSecondaryPath.c_str())) { - int ret = - mkdir(kSecondaryPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - if (ret < 0) { - perror("failed to create directory for secondary instance"); - exit(0); - } - } - DB* db = nullptr; - Options options; - options.create_if_missing = false; - options.max_open_files = -1; - Status s = DB::OpenAsSecondary(options, kDBPath, kSecondaryPath, &db); - if (!s.ok()) { - fprintf(stderr, "[process %ld] Failed to open in secondary mode: %s\n", - my_pid, s.ToString().c_str()); - assert(false); - } else { - fprintf(stdout, "[process %ld] Secondary instance starts\n", my_pid); - } - - ReadOptions ropts; - ropts.verify_checksums = true; - ropts.total_order_seek = true; - - std::vector test_threads; - test_threads.emplace_back([&]() { - while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) { - std::unique_ptr iter(db->NewIterator(ropts)); - iter->SeekToFirst(); - size_t count = 0; - for (; iter->Valid(); iter->Next()) { - ++count; - } - } - fprintf(stdout, "[process %ld] Range_scan thread finished\n", my_pid); - }); - - test_threads.emplace_back([&]() { - std::srand(time(nullptr)); - while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) { - Slice key = Key(std::rand() % kMaxKey); - std::string value; - db->Get(ropts, key, &value); - } - fprintf(stdout, "[process %ld] Point lookup thread finished\n", my_pid); - }); - - uint64_t curr_key = 0; - while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) { - s = db->TryCatchUpWithPrimary(); - if (!s.ok()) { - fprintf(stderr, - "[process %ld] error while trying to catch up with " - "primary %s\n", - my_pid, s.ToString().c_str()); - assert(false); - } - { - std::unique_ptr iter(db->NewIterator(ropts)); - if (!iter) { - fprintf(stderr, "[process %ld] Failed to create iterator\n", my_pid); - assert(false); - } - iter->SeekToLast(); - if (iter->Valid()) { - uint64_t curr_max_key = Key(iter->key().ToString()); - if (curr_max_key != curr_key) { - fprintf(stdout, "[process %ld] Observed key %" PRIu64 "\n", my_pid, - curr_key); - curr_key = curr_max_key; - } - } - } - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - s = db->TryCatchUpWithPrimary(); - if (!s.ok()) { - fprintf(stderr, - "[process %ld] error while trying to catch up with " - "primary %s\n", - my_pid, s.ToString().c_str()); - assert(false); - } - - std::vector column_families; - for (const auto& cf_name : GetColumnFamilyNames()) { - column_families.push_back(ColumnFamilyDescriptor(cf_name, options)); - } - std::vector handles; - DB* verification_db = nullptr; - s = DB::OpenForReadOnly(options, kDBPath, column_families, &handles, - &verification_db); - assert(s.ok()); - Iterator* iter1 = verification_db->NewIterator(ropts); - iter1->SeekToFirst(); - - Iterator* iter = db->NewIterator(ropts); - iter->SeekToFirst(); - for (; iter->Valid() && iter1->Valid(); iter->Next(), iter1->Next()) { - if (iter->key().ToString() != iter1->key().ToString()) { - fprintf(stderr, "%" PRIu64 "!= %" PRIu64 "\n", - Key(iter->key().ToString()), Key(iter1->key().ToString())); - assert(false); - } else if (iter->value().ToString() != iter1->value().ToString()) { - fprintf(stderr, "Value mismatch\n"); - assert(false); - } - } - fprintf(stdout, "[process %ld] Verification succeeded\n", my_pid); - for (auto& thr : test_threads) { - thr.join(); - } - delete iter; - delete iter1; - delete db; - delete verification_db; -} - -int main(int argc, char** argv) { - if (argc < 2) { - fprintf(stderr, "%s <0 for primary, 1 for secondary>\n", argv[0]); - return 0; - } - if (atoi(argv[1]) == 0) { - RunPrimary(); - } else { - RunSecondary(); - } - return 0; -} -#else // OS_LINUX -int main() { - fprintf(stderr, "Not implemented.\n"); - return 0; -} -#endif // !OS_LINUX diff --git a/examples/optimistic_transaction_example.cc b/examples/optimistic_transaction_example.cc deleted file mode 100644 index 079572737..000000000 --- a/examples/optimistic_transaction_example.cc +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" -#include "rocksdb/utilities/optimistic_transaction_db.h" -#include "rocksdb/utilities/transaction.h" - -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::OptimisticTransactionDB; -using ROCKSDB_NAMESPACE::OptimisticTransactionOptions; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Snapshot; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::Transaction; -using ROCKSDB_NAMESPACE::WriteOptions; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_transaction_example"; -#else -std::string kDBPath = "/tmp/rocksdb_transaction_example"; -#endif - -int main() { - // open DB - Options options; - options.create_if_missing = true; - DB* db; - OptimisticTransactionDB* txn_db; - - Status s = OptimisticTransactionDB::Open(options, kDBPath, &txn_db); - assert(s.ok()); - db = txn_db->GetBaseDB(); - - WriteOptions write_options; - ReadOptions read_options; - OptimisticTransactionOptions txn_options; - std::string value; - - //////////////////////////////////////////////////////// - // - // Simple OptimisticTransaction Example ("Read Committed") - // - //////////////////////////////////////////////////////// - - // Start a transaction - Transaction* txn = txn_db->BeginTransaction(write_options); - assert(txn); - - // Read a key in this transaction - s = txn->Get(read_options, "abc", &value); - assert(s.IsNotFound()); - - // Write a key in this transaction - s = txn->Put("abc", "xyz"); - assert(s.ok()); - - // Read a key OUTSIDE this transaction. Does not affect txn. - s = db->Get(read_options, "abc", &value); - assert(s.IsNotFound()); - - // Write a key OUTSIDE of this transaction. - // Does not affect txn since this is an unrelated key. If we wrote key 'abc' - // here, the transaction would fail to commit. - s = db->Put(write_options, "xyz", "zzz"); - assert(s.ok()); - s = db->Put(write_options, "abc", "def"); - assert(s.ok()); - - // Commit transaction - s = txn->Commit(); - assert(s.IsBusy()); - delete txn; - - s = db->Get(read_options, "xyz", &value); - assert(s.ok()); - assert(value == "zzz"); - - s = db->Get(read_options, "abc", &value); - assert(s.ok()); - assert(value == "def"); - - //////////////////////////////////////////////////////// - // - // "Repeatable Read" (Snapshot Isolation) Example - // -- Using a single Snapshot - // - //////////////////////////////////////////////////////// - - // Set a snapshot at start of transaction by setting set_snapshot=true - txn_options.set_snapshot = true; - txn = txn_db->BeginTransaction(write_options, txn_options); - - const Snapshot* snapshot = txn->GetSnapshot(); - - // Write a key OUTSIDE of transaction - s = db->Put(write_options, "abc", "xyz"); - assert(s.ok()); - - // Read a key using the snapshot - read_options.snapshot = snapshot; - s = txn->GetForUpdate(read_options, "abc", &value); - assert(s.ok()); - assert(value == "def"); - - // Attempt to commit transaction - s = txn->Commit(); - - // Transaction could not commit since the write outside of the txn conflicted - // with the read! - assert(s.IsBusy()); - - delete txn; - // Clear snapshot from read options since it is no longer valid - read_options.snapshot = nullptr; - snapshot = nullptr; - - s = db->Get(read_options, "abc", &value); - assert(s.ok()); - assert(value == "xyz"); - - //////////////////////////////////////////////////////// - // - // "Read Committed" (Monotonic Atomic Views) Example - // --Using multiple Snapshots - // - //////////////////////////////////////////////////////// - - // In this example, we set the snapshot multiple times. This is probably - // only necessary if you have very strict isolation requirements to - // implement. - - // Set a snapshot at start of transaction - txn_options.set_snapshot = true; - txn = txn_db->BeginTransaction(write_options, txn_options); - - // Do some reads and writes to key "x" - read_options.snapshot = db->GetSnapshot(); - s = txn->Get(read_options, "x", &value); - assert(s.IsNotFound()); - s = txn->Put("x", "x"); - assert(s.ok()); - - // The transaction hasn't committed, so the write is not visible - // outside of txn. - s = db->Get(read_options, "x", &value); - assert(s.IsNotFound()); - - // Do a write outside of the transaction to key "y" - s = db->Put(write_options, "y", "z"); - assert(s.ok()); - - // Set a new snapshot in the transaction - txn->SetSnapshot(); - read_options.snapshot = db->GetSnapshot(); - - // Do some reads and writes to key "y" - s = txn->GetForUpdate(read_options, "y", &value); - assert(s.ok()); - assert(value == "z"); - txn->Put("y", "y"); - - // Commit. Since the snapshot was advanced, the write done outside of the - // transaction does not prevent this transaction from Committing. - s = txn->Commit(); - assert(s.ok()); - delete txn; - // Clear snapshot from read options since it is no longer valid - read_options.snapshot = nullptr; - - // txn is committed, read the latest values. - s = db->Get(read_options, "x", &value); - assert(s.ok()); - assert(value == "x"); - - s = db->Get(read_options, "y", &value); - assert(s.ok()); - assert(value == "y"); - - // Cleanup - delete txn_db; - DestroyDB(kDBPath, options); - return 0; -} - diff --git a/examples/options_file_example.cc b/examples/options_file_example.cc deleted file mode 100644 index 00632f391..000000000 --- a/examples/options_file_example.cc +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file demonstrates how to use the utility functions defined in -// rocksdb/utilities/options_util.h to open a rocksdb database without -// remembering all the rocksdb options. -#include -#include -#include - -#include "rocksdb/cache.h" -#include "rocksdb/compaction_filter.h" -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" -#include "rocksdb/table.h" -#include "rocksdb/utilities/options_util.h" - -using ROCKSDB_NAMESPACE::BlockBasedTableOptions; -using ROCKSDB_NAMESPACE::ColumnFamilyDescriptor; -using ROCKSDB_NAMESPACE::ColumnFamilyHandle; -using ROCKSDB_NAMESPACE::ColumnFamilyOptions; -using ROCKSDB_NAMESPACE::CompactionFilter; -using ROCKSDB_NAMESPACE::ConfigOptions; -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::DBOptions; -using ROCKSDB_NAMESPACE::NewLRUCache; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::Slice; -using ROCKSDB_NAMESPACE::Status; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_options_file_example"; -#else -std::string kDBPath = "/tmp/rocksdb_options_file_example"; -#endif - -namespace { -// A dummy compaction filter -class DummyCompactionFilter : public CompactionFilter { - public: - virtual ~DummyCompactionFilter() {} - virtual bool Filter(int level, const Slice& key, const Slice& existing_value, - std::string* new_value, bool* value_changed) const { - return false; - } - virtual const char* Name() const { return "DummyCompactionFilter"; } -}; - -} // namespace - -int main() { - DBOptions db_opt; - db_opt.create_if_missing = true; - - std::vector cf_descs; - cf_descs.push_back( - {ROCKSDB_NAMESPACE::kDefaultColumnFamilyName, ColumnFamilyOptions()}); - cf_descs.push_back({"new_cf", ColumnFamilyOptions()}); - - // initialize BlockBasedTableOptions - auto cache = NewLRUCache(1 * 1024 * 1024 * 1024); - BlockBasedTableOptions bbt_opts; - bbt_opts.block_size = 32 * 1024; - bbt_opts.block_cache = cache; - - // initialize column families options - std::unique_ptr compaction_filter; - compaction_filter.reset(new DummyCompactionFilter()); - cf_descs[0].options.table_factory.reset(NewBlockBasedTableFactory(bbt_opts)); - cf_descs[0].options.compaction_filter = compaction_filter.get(); - cf_descs[1].options.table_factory.reset(NewBlockBasedTableFactory(bbt_opts)); - - // destroy and open DB - DB* db; - Status s = ROCKSDB_NAMESPACE::DestroyDB(kDBPath, - Options(db_opt, cf_descs[0].options)); - assert(s.ok()); - s = DB::Open(Options(db_opt, cf_descs[0].options), kDBPath, &db); - assert(s.ok()); - - // Create column family, and rocksdb will persist the options. - ColumnFamilyHandle* cf; - s = db->CreateColumnFamily(ColumnFamilyOptions(), "new_cf", &cf); - assert(s.ok()); - - // close DB - delete cf; - delete db; - - // In the following code, we will reopen the rocksdb instance using - // the options file stored in the db directory. - - // Load the options file. - DBOptions loaded_db_opt; - std::vector loaded_cf_descs; - ConfigOptions config_options; - s = LoadLatestOptions(config_options, kDBPath, &loaded_db_opt, - &loaded_cf_descs); - assert(s.ok()); - assert(loaded_db_opt.create_if_missing == db_opt.create_if_missing); - - // Initialize pointer options for each column family - for (size_t i = 0; i < loaded_cf_descs.size(); ++i) { - auto* loaded_bbt_opt = - loaded_cf_descs[0] - .options.table_factory->GetOptions(); - // Expect the same as BlockBasedTableOptions will be loaded form file. - assert(loaded_bbt_opt->block_size == bbt_opts.block_size); - // However, block_cache needs to be manually initialized as documented - // in rocksdb/utilities/options_util.h. - loaded_bbt_opt->block_cache = cache; - } - // In addition, as pointer options are initialized with default value, - // we need to properly initialized all the pointer options if non-defalut - // values are used before calling DB::Open(). - assert(loaded_cf_descs[0].options.compaction_filter == nullptr); - loaded_cf_descs[0].options.compaction_filter = compaction_filter.get(); - - // reopen the db using the loaded options. - std::vector handles; - s = DB::Open(loaded_db_opt, kDBPath, loaded_cf_descs, &handles, &db); - assert(s.ok()); - - // close DB - for (auto* handle : handles) { - delete handle; - } - delete db; -} diff --git a/examples/rocksdb_backup_restore_example.cc b/examples/rocksdb_backup_restore_example.cc deleted file mode 100644 index c833ed1c2..000000000 --- a/examples/rocksdb_backup_restore_example.cc +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/utilities/backup_engine.h" - -using ROCKSDB_NAMESPACE::BackupEngine; -using ROCKSDB_NAMESPACE::BackupEngineOptions; -using ROCKSDB_NAMESPACE::BackupEngineReadOnly; -using ROCKSDB_NAMESPACE::BackupInfo; -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::Env; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::WriteOptions; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_example"; -#else -std::string kDBPath = "/tmp/rocksdb_example"; -#endif - -int main() { - DB* db; - Options options; - // Optimize RocksDB. This is the easiest way to get RocksDB to perform well - options.IncreaseParallelism(); - options.OptimizeLevelStyleCompaction(); - // create the DB if it's not already present - options.create_if_missing = true; - - // open DB - Status s = DB::Open(options, kDBPath, &db); - assert(s.ok()); - - // Put key-value - db->Put(WriteOptions(), "key1", "value1"); - assert(s.ok()); - - // create backup - BackupEngine* backup_engine; - s = BackupEngine::Open(Env::Default(), - BackupEngineOptions("/tmp/rocksdb_example_backup"), - &backup_engine); - assert(s.ok()); - - backup_engine->CreateNewBackup(db); - assert(s.ok()); - - std::vector backup_info; - backup_engine->GetBackupInfo(&backup_info); - - s = backup_engine->VerifyBackup(1); - assert(s.ok()); - - // Put key-value - db->Put(WriteOptions(), "key2", "value2"); - assert(s.ok()); - - db->Close(); - delete db; - db = nullptr; - - // restore db to backup 1 - BackupEngineReadOnly* backup_engine_ro; - s = BackupEngineReadOnly::Open( - Env::Default(), BackupEngineOptions("/tmp/rocksdb_example_backup"), - &backup_engine_ro); - assert(s.ok()); - - s = backup_engine_ro->RestoreDBFromBackup(1, "/tmp/rocksdb_example", - "/tmp/rocksdb_example"); - assert(s.ok()); - - // open db again - s = DB::Open(options, kDBPath, &db); - assert(s.ok()); - - std::string value; - s = db->Get(ReadOptions(), "key1", &value); - assert(!s.IsNotFound()); - - s = db->Get(ReadOptions(), "key2", &value); - assert(s.IsNotFound()); - - delete backup_engine; - delete backup_engine_ro; - delete db; - - return 0; -} diff --git a/examples/rocksdb_option_file_example.ini b/examples/rocksdb_option_file_example.ini deleted file mode 100644 index 351890e51..000000000 --- a/examples/rocksdb_option_file_example.ini +++ /dev/null @@ -1,142 +0,0 @@ -# This is a RocksDB option file. -# -# A typical RocksDB options file has four sections, which are -# Version section, DBOptions section, at least one CFOptions -# section, and one TableOptions section for each column family. -# The RocksDB options file in general follows the basic INI -# file format with the following extensions / modifications: -# -# * Escaped characters -# We escaped the following characters: -# - \n -- line feed - new line -# - \r -- carriage return -# - \\ -- backslash \ -# - \: -- colon symbol : -# - \# -- hash tag # -# * Comments -# We support # style comments. Comments can appear at the ending -# part of a line. -# * Statements -# A statement is of the form option_name = value. -# Each statement contains a '=', where extra white-spaces -# are supported. However, we don't support multi-lined statement. -# Furthermore, each line can only contain at most one statement. -# * Sections -# Sections are of the form [SecitonTitle "SectionArgument"], -# where section argument is optional. -# * List -# We use colon-separated string to represent a list. -# For instance, n1:n2:n3:n4 is a list containing four values. -# -# Below is an example of a RocksDB options file: -[Version] - rocksdb_version=4.3.0 - options_file_version=1.1 - -[DBOptions] - stats_dump_period_sec=600 - max_manifest_file_size=18446744073709551615 - bytes_per_sync=8388608 - delayed_write_rate=2097152 - WAL_ttl_seconds=0 - WAL_size_limit_MB=0 - max_subcompactions=1 - wal_dir= - wal_bytes_per_sync=0 - db_write_buffer_size=0 - keep_log_file_num=1000 - table_cache_numshardbits=4 - max_file_opening_threads=1 - writable_file_max_buffer_size=1048576 - random_access_max_buffer_size=1048576 - use_fsync=false - max_total_wal_size=0 - max_open_files=-1 - skip_stats_update_on_db_open=false - max_background_compactions=16 - manifest_preallocation_size=4194304 - max_background_flushes=7 - is_fd_close_on_exec=true - max_log_file_size=0 - advise_random_on_open=true - create_missing_column_families=false - paranoid_checks=true - delete_obsolete_files_period_micros=21600000000 - log_file_time_to_roll=0 - compaction_readahead_size=0 - create_if_missing=false - use_adaptive_mutex=false - enable_thread_tracking=false - allow_fallocate=true - error_if_exists=false - recycle_log_file_num=0 - db_log_dir= - skip_log_error_on_recovery=false - new_table_reader_for_compaction_inputs=true - allow_mmap_reads=false - allow_mmap_writes=false - use_direct_reads=false - use_direct_writes=false - - -[CFOptions "default"] - compaction_style=kCompactionStyleLevel - compaction_filter=nullptr - num_levels=6 - table_factory=BlockBasedTable - comparator=leveldb.BytewiseComparator - max_sequential_skip_in_iterations=8 - max_bytes_for_level_base=1073741824 - memtable_prefix_bloom_probes=6 - memtable_prefix_bloom_bits=0 - memtable_prefix_bloom_huge_page_tlb_size=0 - max_successive_merges=0 - arena_block_size=16777216 - min_write_buffer_number_to_merge=1 - target_file_size_multiplier=1 - source_compaction_factor=1 - max_bytes_for_level_multiplier=8 - max_bytes_for_level_multiplier_additional=2:3:5 - compaction_filter_factory=nullptr - max_write_buffer_number=8 - level0_stop_writes_trigger=20 - compression=kSnappyCompression - level0_file_num_compaction_trigger=4 - purge_redundant_kvs_while_flush=true - max_write_buffer_size_to_maintain=0 - memtable_factory=SkipListFactory - max_grandparent_overlap_factor=8 - expanded_compaction_factor=25 - hard_pending_compaction_bytes_limit=137438953472 - inplace_update_num_locks=10000 - level_compaction_dynamic_level_bytes=true - level0_slowdown_writes_trigger=12 - filter_deletes=false - verify_checksums_in_compaction=true - min_partial_merge_operands=2 - paranoid_file_checks=false - target_file_size_base=134217728 - optimize_filters_for_hits=false - merge_operator=PutOperator - compression_per_level=kNoCompression:kNoCompression:kNoCompression:kSnappyCompression:kSnappyCompression:kSnappyCompression - compaction_measure_io_stats=false - prefix_extractor=nullptr - bloom_locality=0 - write_buffer_size=134217728 - disable_auto_compactions=false - inplace_update_support=false - -[TableOptions/BlockBasedTable "default"] - format_version=2 - whole_key_filtering=true - no_block_cache=false - checksum=kCRC32c - filter_policy=rocksdb.BuiltinBloomFilter - block_size_deviation=10 - block_size=8192 - block_restart_interval=16 - cache_index_and_filter_blocks=false - pin_l0_filter_and_index_blocks_in_cache=false - pin_top_level_index_and_filter=false - index_type=kBinarySearch - flush_block_policy_factory=FlushBlockBySizePolicyFactory diff --git a/examples/simple_example.cc b/examples/simple_example.cc deleted file mode 100644 index 2d49c4d14..000000000 --- a/examples/simple_example.cc +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" - -using ROCKSDB_NAMESPACE::DB; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::PinnableSlice; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::WriteBatch; -using ROCKSDB_NAMESPACE::WriteOptions; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_simple_example"; -#else -std::string kDBPath = "/tmp/rocksdb_simple_example"; -#endif - -int main() { - DB* db; - Options options; - // Optimize RocksDB. This is the easiest way to get RocksDB to perform well - options.IncreaseParallelism(); - options.OptimizeLevelStyleCompaction(); - // create the DB if it's not already present - options.create_if_missing = true; - - // open DB - Status s = DB::Open(options, kDBPath, &db); - assert(s.ok()); - - // Put key-value - s = db->Put(WriteOptions(), "key1", "value"); - assert(s.ok()); - std::string value; - // get value - s = db->Get(ReadOptions(), "key1", &value); - assert(s.ok()); - assert(value == "value"); - - // atomically apply a set of updates - { - WriteBatch batch; - batch.Delete("key1"); - batch.Put("key2", value); - s = db->Write(WriteOptions(), &batch); - } - - s = db->Get(ReadOptions(), "key1", &value); - assert(s.IsNotFound()); - - db->Get(ReadOptions(), "key2", &value); - assert(value == "value"); - - { - PinnableSlice pinnable_val; - db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val); - assert(pinnable_val == "value"); - } - - { - std::string string_val; - // If it cannot pin the value, it copies the value to its internal buffer. - // The intenral buffer could be set during construction. - PinnableSlice pinnable_val(&string_val); - db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val); - assert(pinnable_val == "value"); - // If the value is not pinned, the internal buffer must have the value. - assert(pinnable_val.IsPinned() || string_val == "value"); - } - - PinnableSlice pinnable_val; - s = db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &pinnable_val); - assert(s.IsNotFound()); - // Reset PinnableSlice after each use and before each reuse - pinnable_val.Reset(); - db->Get(ReadOptions(), db->DefaultColumnFamily(), "key2", &pinnable_val); - assert(pinnable_val == "value"); - pinnable_val.Reset(); - // The Slice pointed by pinnable_val is not valid after this point - - delete db; - - return 0; -} diff --git a/examples/transaction_example.cc b/examples/transaction_example.cc deleted file mode 100644 index 541b13f79..000000000 --- a/examples/transaction_example.cc +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" -#include "rocksdb/utilities/transaction.h" -#include "rocksdb/utilities/transaction_db.h" - -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::Snapshot; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::Transaction; -using ROCKSDB_NAMESPACE::TransactionDB; -using ROCKSDB_NAMESPACE::TransactionDBOptions; -using ROCKSDB_NAMESPACE::TransactionOptions; -using ROCKSDB_NAMESPACE::WriteOptions; - -#if defined(OS_WIN) -std::string kDBPath = "C:\\Windows\\TEMP\\rocksdb_transaction_example"; -#else -std::string kDBPath = "/tmp/rocksdb_transaction_example"; -#endif - -int main() { - // open DB - Options options; - TransactionDBOptions txn_db_options; - options.create_if_missing = true; - TransactionDB* txn_db; - - Status s = TransactionDB::Open(options, txn_db_options, kDBPath, &txn_db); - assert(s.ok()); - - WriteOptions write_options; - ReadOptions read_options; - TransactionOptions txn_options; - std::string value; - - //////////////////////////////////////////////////////// - // - // Simple Transaction Example ("Read Committed") - // - //////////////////////////////////////////////////////// - - // Start a transaction - Transaction* txn = txn_db->BeginTransaction(write_options); - assert(txn); - - // Read a key in this transaction - s = txn->Get(read_options, "abc", &value); - assert(s.IsNotFound()); - - // Write a key in this transaction - s = txn->Put("abc", "def"); - assert(s.ok()); - - // Read a key OUTSIDE this transaction. Does not affect txn. - s = txn_db->Get(read_options, "abc", &value); - assert(s.IsNotFound()); - - // Write a key OUTSIDE of this transaction. - // Does not affect txn since this is an unrelated key. - s = txn_db->Put(write_options, "xyz", "zzz"); - assert(s.ok()); - - // Write a key OUTSIDE of this transaction. - // Fail because the key conflicts with the key written in txn. - s = txn_db->Put(write_options, "abc", "def"); - assert(s.subcode() == Status::kLockTimeout); - - // Value for key "xyz" has been committed, can be read in txn. - s = txn->Get(read_options, "xyz", &value); - assert(s.ok()); - assert(value == "zzz"); - - // Commit transaction - s = txn->Commit(); - assert(s.ok()); - delete txn; - - // Value is committed, can be read now. - s = txn_db->Get(read_options, "abc", &value); - assert(s.ok()); - assert(value == "def"); - - //////////////////////////////////////////////////////// - // - // "Repeatable Read" (Snapshot Isolation) Example - // -- Using a single Snapshot - // - //////////////////////////////////////////////////////// - - // Set a snapshot at start of transaction by setting set_snapshot=true - txn_options.set_snapshot = true; - txn = txn_db->BeginTransaction(write_options, txn_options); - - const Snapshot* snapshot = txn->GetSnapshot(); - - // Write a key OUTSIDE of transaction - s = txn_db->Put(write_options, "abc", "xyz"); - assert(s.ok()); - - // Read the latest committed value. - s = txn->Get(read_options, "abc", &value); - assert(s.ok()); - assert(value == "xyz"); - - // Read the snapshotted value. - read_options.snapshot = snapshot; - s = txn->Get(read_options, "abc", &value); - assert(s.ok()); - assert(value == "def"); - - // Attempt to read a key using the snapshot. This will fail since - // the previous write outside this txn conflicts with this read. - s = txn->GetForUpdate(read_options, "abc", &value); - assert(s.IsBusy()); - - txn->Rollback(); - - // Snapshot will be released upon deleting the transaction. - delete txn; - // Clear snapshot from read options since it is no longer valid - read_options.snapshot = nullptr; - snapshot = nullptr; - - //////////////////////////////////////////////////////// - // - // "Read Committed" (Monotonic Atomic Views) Example - // --Using multiple Snapshots - // - //////////////////////////////////////////////////////// - - // In this example, we set the snapshot multiple times. This is probably - // only necessary if you have very strict isolation requirements to - // implement. - - // Set a snapshot at start of transaction - txn_options.set_snapshot = true; - txn = txn_db->BeginTransaction(write_options, txn_options); - - // Do some reads and writes to key "x" - read_options.snapshot = txn_db->GetSnapshot(); - s = txn->Get(read_options, "x", &value); - assert(s.IsNotFound()); - s = txn->Put("x", "x"); - assert(s.ok()); - - // Do a write outside of the transaction to key "y" - s = txn_db->Put(write_options, "y", "y1"); - assert(s.ok()); - - // Set a new snapshot in the transaction - txn->SetSnapshot(); - txn->SetSavePoint(); - read_options.snapshot = txn_db->GetSnapshot(); - - // Do some reads and writes to key "y" - // Since the snapshot was advanced, the write done outside of the - // transaction does not conflict. - s = txn->GetForUpdate(read_options, "y", &value); - assert(s.ok()); - assert(value == "y1"); - s = txn->Put("y", "y2"); - assert(s.ok()); - - // Decide we want to revert the last write from this transaction. - txn->RollbackToSavePoint(); - - // Commit. - s = txn->Commit(); - assert(s.ok()); - delete txn; - // Clear snapshot from read options since it is no longer valid - read_options.snapshot = nullptr; - - // db state is at the save point. - s = txn_db->Get(read_options, "x", &value); - assert(s.ok()); - assert(value == "x"); - - s = txn_db->Get(read_options, "y", &value); - assert(s.ok()); - assert(value == "y1"); - - // Cleanup - delete txn_db; - ROCKSDB_NAMESPACE::DestroyDB(kDBPath, options); - return 0; -} - diff --git a/file/delete_scheduler_test.cc b/file/delete_scheduler_test.cc deleted file mode 100644 index 74982dbee..000000000 --- a/file/delete_scheduler_test.cc +++ /dev/null @@ -1,717 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "file/delete_scheduler.h" - -#include -#include -#include -#include - -#include "file/file_util.h" -#include "file/sst_file_manager_impl.h" -#include "rocksdb/env.h" -#include "rocksdb/options.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "util/string_util.h" - - -namespace ROCKSDB_NAMESPACE { - -class DeleteSchedulerTest : public testing::Test { - public: - DeleteSchedulerTest() : env_(Env::Default()) { - const int kNumDataDirs = 3; - dummy_files_dirs_.reserve(kNumDataDirs); - for (size_t i = 0; i < kNumDataDirs; ++i) { - dummy_files_dirs_.emplace_back( - test::PerThreadDBPath(env_, "delete_scheduler_dummy_data_dir") + - std::to_string(i)); - DestroyAndCreateDir(dummy_files_dirs_.back()); - } - stats_ = ROCKSDB_NAMESPACE::CreateDBStatistics(); - } - - ~DeleteSchedulerTest() override { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - for (const auto& dummy_files_dir : dummy_files_dirs_) { - DestroyDir(env_, dummy_files_dir); - } - } - - void DestroyAndCreateDir(const std::string& dir) { - ASSERT_OK(DestroyDir(env_, dir)); - EXPECT_OK(env_->CreateDir(dir)); - } - - int CountNormalFiles(size_t dummy_files_dirs_idx = 0) { - std::vector files_in_dir; - EXPECT_OK(env_->GetChildren(dummy_files_dirs_[dummy_files_dirs_idx], - &files_in_dir)); - - int normal_cnt = 0; - for (auto& f : files_in_dir) { - if (!DeleteScheduler::IsTrashFile(f)) { - normal_cnt++; - } - } - return normal_cnt; - } - - int CountTrashFiles(size_t dummy_files_dirs_idx = 0) { - std::vector files_in_dir; - EXPECT_OK(env_->GetChildren(dummy_files_dirs_[dummy_files_dirs_idx], - &files_in_dir)); - - int trash_cnt = 0; - for (auto& f : files_in_dir) { - if (DeleteScheduler::IsTrashFile(f)) { - trash_cnt++; - } - } - return trash_cnt; - } - - std::string NewDummyFile(const std::string& file_name, uint64_t size = 1024, - size_t dummy_files_dirs_idx = 0) { - std::string file_path = - dummy_files_dirs_[dummy_files_dirs_idx] + "/" + file_name; - std::unique_ptr f; - env_->NewWritableFile(file_path, &f, EnvOptions()); - std::string data(size, 'A'); - EXPECT_OK(f->Append(data)); - EXPECT_OK(f->Close()); - sst_file_mgr_->OnAddFile(file_path); - return file_path; - } - - void NewDeleteScheduler() { - // Tests in this file are for DeleteScheduler component and don't create any - // DBs, so we need to set max_trash_db_ratio to 100% (instead of default - // 25%) - sst_file_mgr_.reset( - new SstFileManagerImpl(env_->GetSystemClock(), env_->GetFileSystem(), - nullptr, rate_bytes_per_sec_, - /* max_trash_db_ratio= */ 1.1, 128 * 1024)); - delete_scheduler_ = sst_file_mgr_->delete_scheduler(); - sst_file_mgr_->SetStatisticsPtr(stats_); - } - - Env* env_; - std::vector dummy_files_dirs_; - int64_t rate_bytes_per_sec_; - DeleteScheduler* delete_scheduler_; - std::unique_ptr sst_file_mgr_; - std::shared_ptr stats_; -}; - -// Test the basic functionality of DeleteScheduler (Rate Limiting). -// 1- Create 100 dummy files -// 2- Delete the 100 dummy files using DeleteScheduler -// --- Hold DeleteScheduler::BackgroundEmptyTrash --- -// 3- Wait for DeleteScheduler to delete all files in trash -// 4- Verify that BackgroundEmptyTrash used to correct penlties for the files -// 5- Make sure that all created files were completely deleted -TEST_F(DeleteSchedulerTest, BasicRateLimiting) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DeleteSchedulerTest::BasicRateLimiting:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - - std::vector penalties; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::BackgroundEmptyTrash:Wait", - [&](void* arg) { penalties.push_back(*(static_cast(arg))); }); - int dir_synced = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile::AfterSyncDir", [&](void* arg) { - dir_synced++; - std::string* dir = reinterpret_cast(arg); - EXPECT_EQ(dummy_files_dirs_[0], *dir); - }); - - int num_files = 100; // 100 files - uint64_t file_size = 1024; // every file is 1 kb - std::vector delete_kbs_per_sec = {512, 200, 100, 50, 25}; - - for (size_t t = 0; t < delete_kbs_per_sec.size(); t++) { - penalties.clear(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndCreateDir(dummy_files_dirs_[0]); - rate_bytes_per_sec_ = delete_kbs_per_sec[t] * 1024; - NewDeleteScheduler(); - - dir_synced = 0; - // Create 100 dummy files, every file is 1 Kb - std::vector generated_files; - for (int i = 0; i < num_files; i++) { - std::string file_name = "file" + std::to_string(i) + ".data"; - generated_files.push_back(NewDummyFile(file_name, file_size)); - } - - // Delete dummy files and measure time spent to empty trash - for (int i = 0; i < num_files; i++) { - ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[i], - dummy_files_dirs_[0])); - } - ASSERT_EQ(CountNormalFiles(), 0); - - uint64_t delete_start_time = env_->NowMicros(); - TEST_SYNC_POINT("DeleteSchedulerTest::BasicRateLimiting:1"); - delete_scheduler_->WaitForEmptyTrash(); - uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time; - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - - uint64_t total_files_size = 0; - uint64_t expected_penlty = 0; - ASSERT_EQ(penalties.size(), num_files); - for (int i = 0; i < num_files; i++) { - total_files_size += file_size; - expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec_); - ASSERT_EQ(expected_penlty, penalties[i]); - } - ASSERT_GT(time_spent_deleting, expected_penlty * 0.9); - - ASSERT_EQ(num_files, dir_synced); - - ASSERT_EQ(CountTrashFiles(), 0); - ASSERT_EQ(num_files, stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DeleteSchedulerTest, MultiDirectoryDeletionsScheduled) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DeleteSchedulerTest::MultiDbPathDeletionsScheduled:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - rate_bytes_per_sec_ = 1 << 20; // 1MB - NewDeleteScheduler(); - - // Generate dummy files in multiple directories - const size_t kNumFiles = dummy_files_dirs_.size(); - const size_t kFileSize = 1 << 10; // 1KB - std::vector generated_files; - for (size_t i = 0; i < kNumFiles; i++) { - generated_files.push_back(NewDummyFile("file", kFileSize, i)); - ASSERT_EQ(1, CountNormalFiles(i)); - } - - // Mark dummy files as trash - for (size_t i = 0; i < kNumFiles; i++) { - ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[i], "")); - ASSERT_EQ(0, CountNormalFiles(i)); - ASSERT_EQ(1, CountTrashFiles(i)); - } - TEST_SYNC_POINT("DeleteSchedulerTest::MultiDbPathDeletionsScheduled:1"); - delete_scheduler_->WaitForEmptyTrash(); - - // Verify dummy files eventually got deleted - for (size_t i = 0; i < kNumFiles; i++) { - ASSERT_EQ(0, CountNormalFiles(i)); - ASSERT_EQ(0, CountTrashFiles(i)); - } - - ASSERT_EQ(kNumFiles, stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// Same as the BasicRateLimiting test but delete files in multiple threads. -// 1- Create 100 dummy files -// 2- Delete the 100 dummy files using DeleteScheduler using 10 threads -// --- Hold DeleteScheduler::BackgroundEmptyTrash --- -// 3- Wait for DeleteScheduler to delete all files in queue -// 4- Verify that BackgroundEmptyTrash used to correct penlties for the files -// 5- Make sure that all created files were completely deleted -TEST_F(DeleteSchedulerTest, RateLimitingMultiThreaded) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DeleteSchedulerTest::RateLimitingMultiThreaded:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - - std::vector penalties; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::BackgroundEmptyTrash:Wait", - [&](void* arg) { penalties.push_back(*(static_cast(arg))); }); - - int thread_cnt = 10; - int num_files = 10; // 10 files per thread - uint64_t file_size = 1024; // every file is 1 kb - - std::vector delete_kbs_per_sec = {512, 200, 100, 50, 25}; - for (size_t t = 0; t < delete_kbs_per_sec.size(); t++) { - penalties.clear(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndCreateDir(dummy_files_dirs_[0]); - rate_bytes_per_sec_ = delete_kbs_per_sec[t] * 1024; - NewDeleteScheduler(); - - // Create 100 dummy files, every file is 1 Kb - std::vector generated_files; - for (int i = 0; i < num_files * thread_cnt; i++) { - std::string file_name = "file" + std::to_string(i) + ".data"; - generated_files.push_back(NewDummyFile(file_name, file_size)); - } - - // Delete dummy files using 10 threads and measure time spent to empty trash - std::atomic thread_num(0); - std::vector threads; - std::function delete_thread = [&]() { - int idx = thread_num.fetch_add(1); - int range_start = idx * num_files; - int range_end = range_start + num_files; - for (int j = range_start; j < range_end; j++) { - ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[j], "")); - } - }; - - for (int i = 0; i < thread_cnt; i++) { - threads.emplace_back(delete_thread); - } - - for (size_t i = 0; i < threads.size(); i++) { - threads[i].join(); - } - - uint64_t delete_start_time = env_->NowMicros(); - TEST_SYNC_POINT("DeleteSchedulerTest::RateLimitingMultiThreaded:1"); - delete_scheduler_->WaitForEmptyTrash(); - uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time; - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - - uint64_t total_files_size = 0; - uint64_t expected_penlty = 0; - ASSERT_EQ(penalties.size(), num_files * thread_cnt); - for (int i = 0; i < num_files * thread_cnt; i++) { - total_files_size += file_size; - expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec_); - ASSERT_EQ(expected_penlty, penalties[i]); - } - ASSERT_GT(time_spent_deleting, expected_penlty * 0.9); - - ASSERT_EQ(CountNormalFiles(), 0); - ASSERT_EQ(CountTrashFiles(), 0); - ASSERT_EQ(num_files * thread_cnt, - stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -// Disable rate limiting by setting rate_bytes_per_sec_ to 0 and make sure -// that when DeleteScheduler delete a file it delete it immediately and don't -// move it to trash -TEST_F(DeleteSchedulerTest, DisableRateLimiting) { - int bg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 0; - NewDeleteScheduler(); - constexpr int num_files = 10; - - for (int i = 0; i < num_files; i++) { - // Every file we delete will be deleted immediately - std::string dummy_file = NewDummyFile("dummy.data"); - ASSERT_OK(delete_scheduler_->DeleteFile(dummy_file, "")); - ASSERT_TRUE(env_->FileExists(dummy_file).IsNotFound()); - ASSERT_EQ(CountNormalFiles(), 0); - ASSERT_EQ(CountTrashFiles(), 0); - } - - ASSERT_EQ(bg_delete_file, 0); - ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(num_files, - stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// Testing that moving files to trash with the same name is not a problem -// 1- Create 10 files with the same name "conflict.data" -// 2- Delete the 10 files using DeleteScheduler -// 3- Make sure that trash directory contain 10 files ("conflict.data" x 10) -// --- Hold DeleteScheduler::BackgroundEmptyTrash --- -// 4- Make sure that files are deleted from trash -TEST_F(DeleteSchedulerTest, ConflictNames) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DeleteSchedulerTest::ConflictNames:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 1024 * 1024; // 1 Mb/sec - NewDeleteScheduler(); - - // Create "conflict.data" and move it to trash 10 times - for (int i = 0; i < 10; i++) { - std::string dummy_file = NewDummyFile("conflict.data"); - ASSERT_OK(delete_scheduler_->DeleteFile(dummy_file, "")); - } - ASSERT_EQ(CountNormalFiles(), 0); - // 10 files ("conflict.data" x 10) in trash - ASSERT_EQ(CountTrashFiles(), 10); - - // Hold BackgroundEmptyTrash - TEST_SYNC_POINT("DeleteSchedulerTest::ConflictNames:1"); - delete_scheduler_->WaitForEmptyTrash(); - ASSERT_EQ(CountTrashFiles(), 0); - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - ASSERT_EQ(10, stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// 1- Create 10 dummy files -// 2- Delete the 10 files using DeleteScheduler (move them to trsah) -// 3- Delete the 10 files directly (using env_->DeleteFile) -// --- Hold DeleteScheduler::BackgroundEmptyTrash --- -// 4- Make sure that DeleteScheduler failed to delete the 10 files and -// reported 10 background errors -TEST_F(DeleteSchedulerTest, BackgroundError) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DeleteSchedulerTest::BackgroundError:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 1024 * 1024; // 1 Mb/sec - NewDeleteScheduler(); - - // Generate 10 dummy files and move them to trash - for (int i = 0; i < 10; i++) { - std::string file_name = "data_" + std::to_string(i) + ".data"; - ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name), "")); - } - ASSERT_EQ(CountNormalFiles(), 0); - ASSERT_EQ(CountTrashFiles(), 10); - - // Delete 10 files from trash, this will cause background errors in - // BackgroundEmptyTrash since we already deleted the files it was - // goind to delete - for (int i = 0; i < 10; i++) { - std::string file_name = "data_" + std::to_string(i) + ".data.trash"; - ASSERT_OK(env_->DeleteFile(dummy_files_dirs_[0] + "/" + file_name)); - } - - // Hold BackgroundEmptyTrash - TEST_SYNC_POINT("DeleteSchedulerTest::BackgroundError:1"); - delete_scheduler_->WaitForEmptyTrash(); - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 10); - for (const auto& it : bg_errors) { - ASSERT_TRUE(it.second.IsPathNotFound()); - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -// 1- Create kTestFileNum dummy files -// 2- Delete kTestFileNum dummy files using DeleteScheduler -// 3- Wait for DeleteScheduler to delete all files in queue -// 4- Make sure all files in trash directory were deleted -// 5- Repeat previous steps 5 times -TEST_F(DeleteSchedulerTest, StartBGEmptyTrashMultipleTimes) { - constexpr int kTestFileNum = 10; - std::atomic_int bg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / sec - NewDeleteScheduler(); - - // If trash file is generated faster than deleting, delete_scheduler will - // delete it directly instead of waiting for background trash empty thread to - // clean it. Set the ratio higher to avoid that. - sst_file_mgr_->SetMaxTrashDBRatio(kTestFileNum + 1); - - // Move files to trash, wait for empty trash, start again - for (int run = 1; run <= 5; run++) { - // Generate kTestFileNum dummy files and move them to trash - for (int i = 0; i < kTestFileNum; i++) { - std::string file_name = "data_" + std::to_string(i) + ".data"; - ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name), "")); - } - ASSERT_EQ(CountNormalFiles(), 0); - delete_scheduler_->WaitForEmptyTrash(); - ASSERT_EQ(bg_delete_file, kTestFileNum * run); - ASSERT_EQ(CountTrashFiles(), 0); - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - ASSERT_EQ(kTestFileNum, stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - } - - ASSERT_EQ(bg_delete_file, 5 * kTestFileNum); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); -} - -TEST_F(DeleteSchedulerTest, DeletePartialFile) { - int bg_delete_file = 0; - int bg_fsync = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void*) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:Fsync", [&](void*) { bg_fsync++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / sec - NewDeleteScheduler(); - - // Should delete in 4 batch - ASSERT_OK( - delete_scheduler_->DeleteFile(NewDummyFile("data_1", 500 * 1024), "")); - ASSERT_OK( - delete_scheduler_->DeleteFile(NewDummyFile("data_2", 100 * 1024), "")); - // Should delete in 2 batch - ASSERT_OK( - delete_scheduler_->DeleteFile(NewDummyFile("data_2", 200 * 1024), "")); - - delete_scheduler_->WaitForEmptyTrash(); - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - ASSERT_EQ(7, bg_delete_file); - ASSERT_EQ(4, bg_fsync); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); -} - -#ifdef OS_LINUX -TEST_F(DeleteSchedulerTest, NoPartialDeleteWithLink) { - int bg_delete_file = 0; - int bg_fsync = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void*) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:Fsync", [&](void*) { bg_fsync++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / sec - NewDeleteScheduler(); - - std::string file1 = NewDummyFile("data_1", 500 * 1024); - std::string file2 = NewDummyFile("data_2", 100 * 1024); - - ASSERT_OK(env_->LinkFile(file1, dummy_files_dirs_[0] + "/data_1b")); - ASSERT_OK(env_->LinkFile(file2, dummy_files_dirs_[0] + "/data_2b")); - - // Should delete in 4 batch if there is no hardlink - ASSERT_OK(delete_scheduler_->DeleteFile(file1, "")); - ASSERT_OK(delete_scheduler_->DeleteFile(file2, "")); - - delete_scheduler_->WaitForEmptyTrash(); - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - ASSERT_EQ(2, bg_delete_file); - ASSERT_EQ(0, bg_fsync); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); -} -#endif - -// 1- Create a DeleteScheduler with very slow rate limit (1 Byte / sec) -// 2- Delete 100 files using DeleteScheduler -// 3- Delete the DeleteScheduler (call the destructor while queue is not empty) -// 4- Make sure that not all files were deleted from trash and that -// DeleteScheduler background thread did not delete all files -TEST_F(DeleteSchedulerTest, DestructorWithNonEmptyQueue) { - int bg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 1; // 1 Byte / sec - NewDeleteScheduler(); - - for (int i = 0; i < 100; i++) { - std::string file_name = "data_" + std::to_string(i) + ".data"; - ASSERT_OK(delete_scheduler_->DeleteFile(NewDummyFile(file_name), "")); - } - - // Deleting 100 files will need >28 hours to delete - // we will delete the DeleteScheduler while delete queue is not empty - sst_file_mgr_.reset(); - - ASSERT_LT(bg_delete_file, 100); - ASSERT_GT(CountTrashFiles(), 0); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DeleteSchedulerTest, DISABLED_DynamicRateLimiting1) { - std::vector penalties; - int bg_delete_file = 0; - int fg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteFile", [&](void* /*arg*/) { fg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::BackgroundEmptyTrash:Wait", - [&](void* arg) { penalties.push_back(*(static_cast(arg))); }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({ - {"DeleteSchedulerTest::DynamicRateLimiting1:1", - "DeleteScheduler::BackgroundEmptyTrash"}, - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - rate_bytes_per_sec_ = 0; // Disable rate limiting initially - NewDeleteScheduler(); - - int num_files = 10; // 10 files - uint64_t file_size = 1024; // every file is 1 kb - - std::vector delete_kbs_per_sec = {512, 200, 0, 100, 50, -2, 25}; - for (size_t t = 0; t < delete_kbs_per_sec.size(); t++) { - penalties.clear(); - bg_delete_file = 0; - fg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - DestroyAndCreateDir(dummy_files_dirs_[0]); - rate_bytes_per_sec_ = delete_kbs_per_sec[t] * 1024; - delete_scheduler_->SetRateBytesPerSecond(rate_bytes_per_sec_); - - // Create 100 dummy files, every file is 1 Kb - std::vector generated_files; - for (int i = 0; i < num_files; i++) { - std::string file_name = "file" + std::to_string(i) + ".data"; - generated_files.push_back(NewDummyFile(file_name, file_size)); - } - - // Delete dummy files and measure time spent to empty trash - for (int i = 0; i < num_files; i++) { - ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[i], "")); - } - ASSERT_EQ(CountNormalFiles(), 0); - - if (rate_bytes_per_sec_ > 0) { - uint64_t delete_start_time = env_->NowMicros(); - TEST_SYNC_POINT("DeleteSchedulerTest::DynamicRateLimiting1:1"); - delete_scheduler_->WaitForEmptyTrash(); - uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time; - - auto bg_errors = delete_scheduler_->GetBackgroundErrors(); - ASSERT_EQ(bg_errors.size(), 0); - - uint64_t total_files_size = 0; - uint64_t expected_penlty = 0; - ASSERT_EQ(penalties.size(), num_files); - for (int i = 0; i < num_files; i++) { - total_files_size += file_size; - expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec_); - ASSERT_EQ(expected_penlty, penalties[i]); - } - ASSERT_GT(time_spent_deleting, expected_penlty * 0.9); - ASSERT_EQ(bg_delete_file, num_files); - ASSERT_EQ(fg_delete_file, 0); - } else { - ASSERT_EQ(penalties.size(), 0); - ASSERT_EQ(bg_delete_file, 0); - ASSERT_EQ(fg_delete_file, num_files); - } - - ASSERT_EQ(CountTrashFiles(), 0); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(DeleteSchedulerTest, ImmediateDeleteOn25PercDBSize) { - int bg_delete_file = 0; - int fg_delete_file = 0; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* /*arg*/) { bg_delete_file++; }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "DeleteScheduler::DeleteFile", [&](void* /*arg*/) { fg_delete_file++; }); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - int num_files = 100; // 100 files - uint64_t file_size = 1024 * 10; // 100 KB as a file size - rate_bytes_per_sec_ = 1; // 1 byte per sec (very slow trash delete) - - NewDeleteScheduler(); - delete_scheduler_->SetMaxTrashDBRatio(0.25); - - std::vector generated_files; - for (int i = 0; i < num_files; i++) { - std::string file_name = "file" + std::to_string(i) + ".data"; - generated_files.push_back(NewDummyFile(file_name, file_size)); - } - - for (std::string& file_name : generated_files) { - ASSERT_OK(delete_scheduler_->DeleteFile(file_name, "")); - } - - // When we end up with 26 files in trash we will start - // deleting new files immediately - ASSERT_EQ(fg_delete_file, 74); - ASSERT_EQ(26, stats_->getAndResetTickerCount(FILES_MARKED_TRASH)); - ASSERT_EQ(74, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(DeleteSchedulerTest, IsTrashCheck) { - // Trash files - ASSERT_TRUE(DeleteScheduler::IsTrashFile("x.trash")); - ASSERT_TRUE(DeleteScheduler::IsTrashFile(".trash")); - ASSERT_TRUE(DeleteScheduler::IsTrashFile("abc.sst.trash")); - ASSERT_TRUE(DeleteScheduler::IsTrashFile("/a/b/c/abc..sst.trash")); - ASSERT_TRUE(DeleteScheduler::IsTrashFile("log.trash")); - ASSERT_TRUE(DeleteScheduler::IsTrashFile("^^^^^.log.trash")); - ASSERT_TRUE(DeleteScheduler::IsTrashFile("abc.t.trash")); - - // Not trash files - ASSERT_FALSE(DeleteScheduler::IsTrashFile("abc.sst")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile("abc.txt")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile("/a/b/c/abc.sst")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile("/a/b/c/abc.sstrash")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile("^^^^^.trashh")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile("abc.ttrash")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile(".ttrash")); - ASSERT_FALSE(DeleteScheduler::IsTrashFile("abc.trashx")); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/file/prefetch_test.cc b/file/prefetch_test.cc deleted file mode 100644 index 488e037ff..000000000 --- a/file/prefetch_test.cc +++ /dev/null @@ -1,2285 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "db/db_test_util.h" -#include "file/file_prefetch_buffer.h" -#include "file/file_util.h" -#include "rocksdb/file_system.h" -#include "test_util/sync_point.h" -#ifdef GFLAGS -#include "tools/io_tracer_parser_tool.h" -#endif -#include "util/random.h" - -namespace { -static bool enable_io_uring = true; -extern "C" bool RocksDbIOUringEnable() { return enable_io_uring; } -} // namespace - -namespace ROCKSDB_NAMESPACE { - -class MockFS; - -class MockRandomAccessFile : public FSRandomAccessFileOwnerWrapper { - public: - MockRandomAccessFile(std::unique_ptr& file, - bool support_prefetch, std::atomic_int& prefetch_count) - : FSRandomAccessFileOwnerWrapper(std::move(file)), - support_prefetch_(support_prefetch), - prefetch_count_(prefetch_count) {} - - IOStatus Prefetch(uint64_t offset, size_t n, const IOOptions& options, - IODebugContext* dbg) override { - if (support_prefetch_) { - prefetch_count_.fetch_add(1); - return target()->Prefetch(offset, n, options, dbg); - } else { - return IOStatus::NotSupported("Prefetch not supported"); - } - } - - private: - const bool support_prefetch_; - std::atomic_int& prefetch_count_; -}; - -class MockFS : public FileSystemWrapper { - public: - explicit MockFS(const std::shared_ptr& wrapped, - bool support_prefetch) - : FileSystemWrapper(wrapped), support_prefetch_(support_prefetch) {} - - static const char* kClassName() { return "MockFS"; } - const char* Name() const override { return kClassName(); } - - IOStatus NewRandomAccessFile(const std::string& fname, - const FileOptions& opts, - std::unique_ptr* result, - IODebugContext* dbg) override { - std::unique_ptr file; - IOStatus s; - s = target()->NewRandomAccessFile(fname, opts, &file, dbg); - result->reset( - new MockRandomAccessFile(file, support_prefetch_, prefetch_count_)); - return s; - } - - void ClearPrefetchCount() { prefetch_count_ = 0; } - - bool IsPrefetchCalled() { return prefetch_count_ > 0; } - - int GetPrefetchCount() { - return prefetch_count_.load(std::memory_order_relaxed); - } - - private: - const bool support_prefetch_; - std::atomic_int prefetch_count_{0}; -}; - -class PrefetchTest - : public DBTestBase, - public ::testing::WithParamInterface> { - public: - PrefetchTest() : DBTestBase("prefetch_test", true) {} - - void SetGenericOptions(Env* env, bool use_direct_io, Options& options) { - options = CurrentOptions(); - options.write_buffer_size = 1024; - options.create_if_missing = true; - options.compression = kNoCompression; - options.env = env; - options.disable_auto_compactions = true; - if (use_direct_io) { - options.use_direct_reads = true; - options.use_direct_io_for_flush_and_compaction = true; - } - } - - void SetBlockBasedTableOptions(BlockBasedTableOptions& table_options) { - table_options.no_block_cache = true; - table_options.cache_index_and_filter_blocks = false; - table_options.metadata_block_size = 1024; - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } -}; - -INSTANTIATE_TEST_CASE_P(PrefetchTest, PrefetchTest, - ::testing::Combine(::testing::Bool(), - ::testing::Bool())); - -std::string BuildKey(int num, std::string postfix = "") { - return "my_key_" + std::to_string(num) + postfix; -} - -// This test verifies the basic functionality of prefetching. -TEST_P(PrefetchTest, Basic) { - // First param is if the mockFS support_prefetch or not - bool support_prefetch = - std::get<0>(GetParam()) && - test::IsPrefetchSupported(env_->GetFileSystem(), dbname_); - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), support_prefetch); - - // Second param is if directIO is enabled or not - bool use_direct_io = std::get<1>(GetParam()); - - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - - const int kNumKeys = 1100; - int buff_prefetch_count = 0; - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - // create first key range - WriteBatch batch; - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), "value for range 1 key")); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - // create second key range - batch.Clear(); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i, "key2"), "value for range 2 key")); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - // delete second key range - batch.Clear(); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Delete(BuildKey(i, "key2"))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - // compact database - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - HistogramData prev_table_open_prefetch_tail_read; - options.statistics->histogramData(TABLE_OPEN_PREFETCH_TAIL_READ_BYTES, - &prev_table_open_prefetch_tail_read); - const uint64_t prev_table_open_prefetch_tail_miss = - options.statistics->getTickerCount(TABLE_OPEN_PREFETCH_TAIL_MISS); - const uint64_t prev_table_open_prefetch_tail_hit = - options.statistics->getTickerCount(TABLE_OPEN_PREFETCH_TAIL_HIT); - - // commenting out the line below causes the example to work correctly - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - HistogramData cur_table_open_prefetch_tail_read; - options.statistics->histogramData(TABLE_OPEN_PREFETCH_TAIL_READ_BYTES, - &cur_table_open_prefetch_tail_read); - const uint64_t cur_table_open_prefetch_tail_miss = - options.statistics->getTickerCount(TABLE_OPEN_PREFETCH_TAIL_MISS); - const uint64_t cur_table_open_prefetch_tail_hit = - options.statistics->getTickerCount(TABLE_OPEN_PREFETCH_TAIL_HIT); - - if (support_prefetch && !use_direct_io) { - // If underline file system supports prefetch, and directIO is not enabled - // make sure prefetch() is called and FilePrefetchBuffer is not used. - ASSERT_TRUE(fs->IsPrefetchCalled()); - fs->ClearPrefetchCount(); - ASSERT_EQ(0, buff_prefetch_count); - } else { - // If underline file system doesn't support prefetch, or directIO is - // enabled, make sure prefetch() is not called and FilePrefetchBuffer is - // used. - ASSERT_FALSE(fs->IsPrefetchCalled()); - ASSERT_GT(buff_prefetch_count, 0); - ASSERT_GT(cur_table_open_prefetch_tail_read.count, - prev_table_open_prefetch_tail_read.count); - ASSERT_GT(cur_table_open_prefetch_tail_hit, - prev_table_open_prefetch_tail_hit); - ASSERT_GE(cur_table_open_prefetch_tail_miss, - prev_table_open_prefetch_tail_miss); - buff_prefetch_count = 0; - } - - // count the keys - { - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - num_keys++; - } - } - - // Make sure prefetch is called only if file system support prefetch. - if (support_prefetch && !use_direct_io) { - ASSERT_TRUE(fs->IsPrefetchCalled()); - fs->ClearPrefetchCount(); - ASSERT_EQ(0, buff_prefetch_count); - } else { - ASSERT_FALSE(fs->IsPrefetchCalled()); - ASSERT_GT(buff_prefetch_count, 0); - buff_prefetch_count = 0; - } - Close(); -} - -// This test verifies BlockBasedTableOptions.max_auto_readahead_size is -// configured dynamically. -TEST_P(PrefetchTest, ConfigureAutoMaxReadaheadSize) { - // First param is if the mockFS support_prefetch or not - bool support_prefetch = - std::get<0>(GetParam()) && - test::IsPrefetchSupported(env_->GetFileSystem(), dbname_); - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), support_prefetch); - - // Second param is if directIO is enabled or not - bool use_direct_io = std::get<1>(GetParam()); - - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - table_options.max_auto_readahead_size = 0; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - int buff_prefetch_count = 0; - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - - // DB open will create table readers unless we reduce the table cache - // capacity. SanitizeOptions will set max_open_files to minimum of 20. Table - // cache is allocated with max_open_files - 10 as capacity. So override - // max_open_files to 10 so table cache capacity will become 0. This will - // prevent file open during DB open and force the file to be opened during - // Iteration. - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = (int*)arg; - *max_open_files = 11; - }); - - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - Random rnd(309); - int key_count = 0; - const int num_keys_per_level = 100; - // Level 0 : Keys in range [0, 99], Level 1:[100, 199], Level 2:[200, 299]. - for (int level = 2; level >= 0; level--) { - key_count = level * num_keys_per_level; - for (int i = 0; i < num_keys_per_level; ++i) { - ASSERT_OK(Put(Key(key_count++), rnd.RandomString(500))); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(level); - } - Close(); - std::vector buff_prefectch_level_count = {0, 0, 0}; - TryReopen(options); - { - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - fs->ClearPrefetchCount(); - buff_prefetch_count = 0; - - for (int level = 2; level >= 0; level--) { - key_count = level * num_keys_per_level; - switch (level) { - case 0: - // max_auto_readahead_size is set 0 so data and index blocks are not - // prefetched. - ASSERT_OK(db_->SetOptions( - {{"block_based_table_factory", "{max_auto_readahead_size=0;}"}})); - break; - case 1: - // max_auto_readahead_size is set less than - // initial_auto_readahead_size. So readahead_size remains equal to - // max_auto_readahead_size. - ASSERT_OK(db_->SetOptions({{"block_based_table_factory", - "{max_auto_readahead_size=4096;}"}})); - break; - case 2: - ASSERT_OK(db_->SetOptions({{"block_based_table_factory", - "{max_auto_readahead_size=65536;}"}})); - break; - default: - assert(false); - } - - for (int i = 0; i < num_keys_per_level; ++i) { - iter->Seek(Key(key_count++)); - iter->Next(); - } - - buff_prefectch_level_count[level] = buff_prefetch_count; - if (support_prefetch && !use_direct_io) { - if (level == 0) { - ASSERT_FALSE(fs->IsPrefetchCalled()); - } else { - ASSERT_TRUE(fs->IsPrefetchCalled()); - } - fs->ClearPrefetchCount(); - } else { - ASSERT_FALSE(fs->IsPrefetchCalled()); - if (level == 0) { - ASSERT_EQ(buff_prefetch_count, 0); - } else { - ASSERT_GT(buff_prefetch_count, 0); - } - buff_prefetch_count = 0; - } - } - } - - if (!support_prefetch) { - ASSERT_GT(buff_prefectch_level_count[1], buff_prefectch_level_count[2]); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -// This test verifies BlockBasedTableOptions.initial_auto_readahead_size is -// configured dynamically. -TEST_P(PrefetchTest, ConfigureInternalAutoReadaheadSize) { - // First param is if the mockFS support_prefetch or not - bool support_prefetch = - std::get<0>(GetParam()) && - test::IsPrefetchSupported(env_->GetFileSystem(), dbname_); - - // Second param is if directIO is enabled or not - bool use_direct_io = std::get<1>(GetParam()); - - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), support_prefetch); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - table_options.initial_auto_readahead_size = 0; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - int buff_prefetch_count = 0; - // DB open will create table readers unless we reduce the table cache - // capacity. SanitizeOptions will set max_open_files to minimum of 20. - // Table cache is allocated with max_open_files - 10 as capacity. So - // override max_open_files to 10 so table cache capacity will become 0. - // This will prevent file open during DB open and force the file to be - // opened during Iteration. - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { - int* max_open_files = (int*)arg; - *max_open_files = 11; - }); - - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - - SyncPoint::GetInstance()->EnableProcessing(); - - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - Random rnd(309); - int key_count = 0; - const int num_keys_per_level = 100; - // Level 0 : Keys in range [0, 99], Level 1:[100, 199], Level 2:[200, 299]. - for (int level = 2; level >= 0; level--) { - key_count = level * num_keys_per_level; - for (int i = 0; i < num_keys_per_level; ++i) { - ASSERT_OK(Put(Key(key_count++), rnd.RandomString(500))); - } - ASSERT_OK(Flush()); - MoveFilesToLevel(level); - } - Close(); - - TryReopen(options); - { - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - fs->ClearPrefetchCount(); - buff_prefetch_count = 0; - std::vector buff_prefetch_level_count = {0, 0, 0}; - - for (int level = 2; level >= 0; level--) { - key_count = level * num_keys_per_level; - switch (level) { - case 0: - // initial_auto_readahead_size is set 0 so data and index blocks are - // not prefetched. - ASSERT_OK(db_->SetOptions({{"block_based_table_factory", - "{initial_auto_readahead_size=0;}"}})); - break; - case 1: - // intial_auto_readahead_size and max_auto_readahead_size are set same - // so readahead_size remains same. - ASSERT_OK(db_->SetOptions({{"block_based_table_factory", - "{initial_auto_readahead_size=4096;max_" - "auto_readahead_size=4096;}"}})); - break; - case 2: - ASSERT_OK( - db_->SetOptions({{"block_based_table_factory", - "{initial_auto_readahead_size=65536;}"}})); - break; - default: - assert(false); - } - - for (int i = 0; i < num_keys_per_level; ++i) { - iter->Seek(Key(key_count++)); - iter->Next(); - } - - buff_prefetch_level_count[level] = buff_prefetch_count; - if (support_prefetch && !use_direct_io) { - if (level == 0) { - ASSERT_FALSE(fs->IsPrefetchCalled()); - } else { - ASSERT_TRUE(fs->IsPrefetchCalled()); - } - fs->ClearPrefetchCount(); - } else { - ASSERT_FALSE(fs->IsPrefetchCalled()); - if (level == 0) { - ASSERT_EQ(buff_prefetch_count, 0); - } else { - ASSERT_GT(buff_prefetch_count, 0); - } - buff_prefetch_count = 0; - } - } - if (!support_prefetch) { - ASSERT_GT(buff_prefetch_level_count[1], buff_prefetch_level_count[2]); - } - } - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -// This test verifies BlockBasedTableOptions.num_file_reads_for_auto_readahead -// is configured dynamically. -TEST_P(PrefetchTest, ConfigureNumFilesReadsForReadaheadSize) { - // First param is if the mockFS support_prefetch or not - bool support_prefetch = - std::get<0>(GetParam()) && - test::IsPrefetchSupported(env_->GetFileSystem(), dbname_); - - const int kNumKeys = 2000; - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), support_prefetch); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - // Second param is if directIO is enabled or not - bool use_direct_io = std::get<1>(GetParam()); - - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - table_options.num_file_reads_for_auto_readahead = 0; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - int buff_prefetch_count = 0; - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - Close(); - TryReopen(options); - - fs->ClearPrefetchCount(); - buff_prefetch_count = 0; - - { - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - /* - * Reseek keys from sequential Data Blocks within same partitioned - * index. It will prefetch the data block at the first seek since - * num_file_reads_for_auto_readahead = 0. Data Block size is nearly 4076 so - * readahead will fetch 8 * 1024 data more initially (2 more data blocks). - */ - iter->Seek(BuildKey(0)); // Prefetch data + index block since - // num_file_reads_for_auto_readahead = 0. - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1000)); // In buffer - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1004)); // In buffer - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1008)); // Prefetch Data - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1011)); // In buffer - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1015)); // In buffer - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); // In buffer - ASSERT_TRUE(iter->Valid()); - // Missed 2 blocks but they are already in buffer so no reset. - iter->Seek(BuildKey(103)); // Already in buffer. - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1033)); // Prefetch Data. - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 4); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 4); - buff_prefetch_count = 0; - } - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -// This test verifies the basic functionality of implicit autoreadahead: -// - Enable implicit autoreadahead and prefetch only if sequential blocks are -// read, -// - If data is already in buffer and few blocks are not requested to read, -// don't reset, -// - If data blocks are sequential during read after enabling implicit -// autoreadahead, reset readahead parameters. -TEST_P(PrefetchTest, PrefetchWhenReseek) { - // First param is if the mockFS support_prefetch or not - bool support_prefetch = - std::get<0>(GetParam()) && - test::IsPrefetchSupported(env_->GetFileSystem(), dbname_); - - const int kNumKeys = 2000; - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), support_prefetch); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - // Second param is if directIO is enabled or not - bool use_direct_io = std::get<1>(GetParam()); - - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - int buff_prefetch_count = 0; - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - fs->ClearPrefetchCount(); - buff_prefetch_count = 0; - - { - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - /* - * Reseek keys from sequential Data Blocks within same partitioned - * index. After 2 sequential reads it will prefetch the data block. - * Data Block size is nearly 4076 so readahead will fetch 8 * 1024 data more - * initially (2 more data blocks). - */ - iter->Seek(BuildKey(0)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1000)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1004)); // Prefetch Data - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1008)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1011)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1015)); // Prefetch Data - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); - ASSERT_TRUE(iter->Valid()); - // Missed 2 blocks but they are already in buffer so no reset. - iter->Seek(BuildKey(103)); // Already in buffer. - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1033)); // Prefetch Data - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 3); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 3); - buff_prefetch_count = 0; - } - } - { - /* - * Reseek keys from non sequential data blocks within same partitioned - * index. buff_prefetch_count will be 0 in that case. - */ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - iter->Seek(BuildKey(0)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1008)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1033)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1048)); - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 0); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 0); - buff_prefetch_count = 0; - } - } - { - /* - * Reesek keys from Single Data Block. - */ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - iter->Seek(BuildKey(0)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(10)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(100)); - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 0); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 0); - buff_prefetch_count = 0; - } - } - { - /* - * Reseek keys from sequential data blocks to set implicit auto readahead - * and prefetch data but after that iterate over different (non sequential) - * data blocks which won't prefetch any data further. So buff_prefetch_count - * will be 1 for the first one. - */ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - iter->Seek(BuildKey(0)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1000)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1004)); // This iteration will prefetch buffer - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1008)); - ASSERT_TRUE(iter->Valid()); - iter->Seek( - BuildKey(996)); // Reseek won't prefetch any data and - // readahead_size will be initiallized to 8*1024. - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(992)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(989)); - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 1); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 1); - buff_prefetch_count = 0; - } - - // Read sequentially to confirm readahead_size is reset to initial value (2 - // more data blocks) - iter->Seek(BuildKey(1011)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1015)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); // Prefetch Data - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1022)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1026)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(103)); // Prefetch Data - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 2); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 2); - buff_prefetch_count = 0; - } - } - { - /* Reseek keys from sequential partitioned index block. Since partitioned - * index fetch are sequential, buff_prefetch_count will be 1. - */ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - iter->Seek(BuildKey(0)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1167)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1334)); // This iteration will prefetch buffer - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1499)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1667)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1847)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1999)); - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 1); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 1); - buff_prefetch_count = 0; - } - } - { - /* - * Reseek over different keys from different blocks. buff_prefetch_count is - * set 0. - */ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - int i = 0; - int j = 1000; - do { - iter->Seek(BuildKey(i)); - if (!iter->Valid()) { - break; - } - i = i + 100; - iter->Seek(BuildKey(j)); - j = j + 100; - } while (i < 1000 && j < kNumKeys && iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 0); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 0); - buff_prefetch_count = 0; - } - } - { - /* Iterates sequentially over all keys. It will prefetch the buffer.*/ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - } - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 13); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 13); - buff_prefetch_count = 0; - } - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -// This test verifies the functionality of implicit autoreadahead when caching -// is enabled: -// - If data is already in buffer and few blocks are not requested to read, -// don't reset, -// - If block was eligible for prefetching/in buffer but found in cache, don't -// prefetch and reset. -TEST_P(PrefetchTest, PrefetchWhenReseekwithCache) { - // First param is if the mockFS support_prefetch or not - bool support_prefetch = - std::get<0>(GetParam()) && - test::IsPrefetchSupported(env_->GetFileSystem(), dbname_); - - const int kNumKeys = 2000; - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), support_prefetch); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - // Second param is if directIO is enabled or not - bool use_direct_io = std::get<1>(GetParam()); - - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); // 8MB - table_options.block_cache = cache; - table_options.no_block_cache = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - int buff_prefetch_count = 0; - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - fs->ClearPrefetchCount(); - buff_prefetch_count = 0; - - { - /* - * Reseek keys from sequential Data Blocks within same partitioned - * index. After 2 sequential reads it will prefetch the data block. - * Data Block size is nearly 4076 so readahead will fetch 8 * 1024 data more - * initially (2 more data blocks). - */ - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - // Warm up the cache - iter->Seek(BuildKey(1011)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1015)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); - ASSERT_TRUE(iter->Valid()); - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 1); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 1); - buff_prefetch_count = 0; - } - } - { - // After caching, blocks will be read from cache (Sequential blocks) - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - iter->Seek(BuildKey(0)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1000)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1004)); // Prefetch data (not in cache). - ASSERT_TRUE(iter->Valid()); - // Missed one sequential block but next is in already in buffer so readahead - // will not be reset. - iter->Seek(BuildKey(1011)); - ASSERT_TRUE(iter->Valid()); - // Prefetch data but blocks are in cache so no prefetch and reset. - iter->Seek(BuildKey(1015)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1022)); - ASSERT_TRUE(iter->Valid()); - // Prefetch data with readahead_size = 4 blocks. - iter->Seek(BuildKey(1026)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(103)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1033)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1037)); - ASSERT_TRUE(iter->Valid()); - - if (support_prefetch && !use_direct_io) { - ASSERT_EQ(fs->GetPrefetchCount(), 3); - fs->ClearPrefetchCount(); - } else { - ASSERT_EQ(buff_prefetch_count, 2); - buff_prefetch_count = 0; - } - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -// This test verifies the functionality of ReadOptions.adaptive_readahead. -TEST_P(PrefetchTest, DBIterLevelReadAhead) { - const int kNumKeys = 1000; - // Set options - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - bool use_direct_io = std::get<0>(GetParam()); - bool is_adaptive_readahead = std::get<1>(GetParam()); - - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - int total_keys = 0; - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - total_keys++; - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - int buff_prefetch_count = 0; - int readahead_carry_over_count = 0; - int num_sst_files = NumTableFilesAtLevel(2); - size_t current_readahead_size = 0; - - // Test - Iterate over the keys sequentially. - { - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - - // The callback checks, since reads are sequential, readahead_size doesn't - // start from 8KB when iterator moves to next file and its called - // num_sst_files-1 times (excluding for first file). - SyncPoint::GetInstance()->SetCallBack( - "BlockPrefetcher::SetReadaheadState", [&](void* arg) { - readahead_carry_over_count++; - size_t readahead_size = *reinterpret_cast(arg); - if (readahead_carry_over_count) { - ASSERT_GT(readahead_size, 8 * 1024); - } - }); - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::TryReadFromCache", [&](void* arg) { - current_readahead_size = *reinterpret_cast(arg); - ASSERT_GT(current_readahead_size, 0); - }); - - SyncPoint::GetInstance()->EnableProcessing(); - - ReadOptions ro; - if (is_adaptive_readahead) { - ro.adaptive_readahead = true; - } - - ASSERT_OK(options.statistics->Reset()); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - num_keys++; - } - ASSERT_EQ(num_keys, total_keys); - - // For index and data blocks. - if (is_adaptive_readahead) { - ASSERT_EQ(readahead_carry_over_count, 2 * (num_sst_files - 1)); - } else { - ASSERT_GT(buff_prefetch_count, 0); - ASSERT_EQ(readahead_carry_over_count, 0); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } - Close(); -} - -// This test verifies the functionality of ReadOptions.adaptive_readahead when -// async_io is enabled. -TEST_P(PrefetchTest, DBIterLevelReadAheadWithAsyncIO) { - const int kNumKeys = 1000; - // Set options - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - bool use_direct_io = std::get<0>(GetParam()); - bool is_adaptive_readahead = std::get<1>(GetParam()); - - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - int total_keys = 0; - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - total_keys++; - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - int buff_async_prefetch_count = 0; - int readahead_carry_over_count = 0; - int num_sst_files = NumTableFilesAtLevel(2); - size_t current_readahead_size = 0; - - // Test - Iterate over the keys sequentially. - { - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::PrefetchAsyncInternal:Start", - [&](void*) { buff_async_prefetch_count++; }); - - // The callback checks, since reads are sequential, readahead_size doesn't - // start from 8KB when iterator moves to next file and its called - // num_sst_files-1 times (excluding for first file). - SyncPoint::GetInstance()->SetCallBack( - "BlockPrefetcher::SetReadaheadState", [&](void* arg) { - readahead_carry_over_count++; - size_t readahead_size = *reinterpret_cast(arg); - if (readahead_carry_over_count) { - ASSERT_GT(readahead_size, 8 * 1024); - } - }); - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::TryReadFromCache", [&](void* arg) { - current_readahead_size = *reinterpret_cast(arg); - ASSERT_GT(current_readahead_size, 0); - }); - - SyncPoint::GetInstance()->EnableProcessing(); - - ReadOptions ro; - if (is_adaptive_readahead) { - ro.adaptive_readahead = true; - } - ro.async_io = true; - - ASSERT_OK(options.statistics->Reset()); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - num_keys++; - } - ASSERT_EQ(num_keys, total_keys); - - // For index and data blocks. - if (is_adaptive_readahead) { - ASSERT_EQ(readahead_carry_over_count, 2 * (num_sst_files - 1)); - } else { - ASSERT_EQ(readahead_carry_over_count, 0); - } - ASSERT_GT(buff_async_prefetch_count, 0); - - // Check stats to make sure async prefetch is done. - { - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - if (ro.async_io) { - ASSERT_GT(async_read_bytes.count, 0); - } else { - ASSERT_EQ(async_read_bytes.count, 0); - } - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } - Close(); -} - -TEST_P(PrefetchTest, DBIterAsyncIONoIOUring) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - - const int kNumKeys = 1000; - // Set options - bool use_direct_io = std::get<0>(GetParam()); - bool is_adaptive_readahead = std::get<1>(GetParam()); - - Options options; - SetGenericOptions(Env::Default(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - enable_io_uring = false; - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - int total_keys = 0; - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - total_keys++; - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - - // Test - Iterate over the keys sequentially. - { - ReadOptions ro; - if (is_adaptive_readahead) { - ro.adaptive_readahead = true; - } - ro.async_io = true; - - ASSERT_OK(options.statistics->Reset()); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - num_keys++; - } - ASSERT_EQ(num_keys, total_keys); - - // Check stats to make sure async prefetch is done. - { - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - ASSERT_EQ(async_read_bytes.count, 0); - ASSERT_EQ(options.statistics->getTickerCount(READ_ASYNC_MICROS), 0); - } - } - - { - ReadOptions ro; - if (is_adaptive_readahead) { - ro.adaptive_readahead = true; - } - ro.async_io = true; - ro.tailing = true; - - ASSERT_OK(options.statistics->Reset()); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - num_keys++; - } - ASSERT_EQ(num_keys, total_keys); - - // Check stats to make sure async prefetch is done. - { - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - ASSERT_EQ(async_read_bytes.count, 0); - ASSERT_EQ(options.statistics->getTickerCount(READ_ASYNC_MICROS), 0); - } - } - Close(); - - enable_io_uring = true; -} - -class PrefetchTest1 : public DBTestBase, - public ::testing::WithParamInterface { - public: - PrefetchTest1() : DBTestBase("prefetch_test1", true) {} - - void SetGenericOptions(Env* env, bool use_direct_io, Options& options) { - options = CurrentOptions(); - options.write_buffer_size = 1024; - options.create_if_missing = true; - options.compression = kNoCompression; - options.env = env; - options.disable_auto_compactions = true; - if (use_direct_io) { - options.use_direct_reads = true; - options.use_direct_io_for_flush_and_compaction = true; - } - } - - void SetBlockBasedTableOptions(BlockBasedTableOptions& table_options) { - table_options.no_block_cache = true; - table_options.cache_index_and_filter_blocks = false; - table_options.metadata_block_size = 1024; - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - } -}; - -INSTANTIATE_TEST_CASE_P(PrefetchTest1, PrefetchTest1, ::testing::Bool()); - -// This test verifies the functionality of ReadOptions.adaptive_readahead when -// reads are not sequential. -TEST_P(PrefetchTest1, NonSequentialReadsWithAdaptiveReadahead) { - const int kNumKeys = 1000; - // Set options - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - Options options; - SetGenericOptions(env.get(), GetParam(), options); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (GetParam() && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - - int buff_prefetch_count = 0; - int set_readahead = 0; - size_t readahead_size = 0; - - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - SyncPoint::GetInstance()->SetCallBack( - "BlockPrefetcher::SetReadaheadState", - [&](void* /*arg*/) { set_readahead++; }); - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::TryReadFromCache", - [&](void* arg) { readahead_size = *reinterpret_cast(arg); }); - - SyncPoint::GetInstance()->EnableProcessing(); - - { - // Iterate until prefetch is done. - ReadOptions ro; - ro.adaptive_readahead = true; - auto iter = std::unique_ptr(db_->NewIterator(ro)); - - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - - while (iter->Valid() && buff_prefetch_count == 0) { - iter->Next(); - } - - ASSERT_EQ(readahead_size, 8 * 1024); - ASSERT_EQ(buff_prefetch_count, 1); - ASSERT_EQ(set_readahead, 0); - buff_prefetch_count = 0; - - // Move to last file and check readahead size fallbacks to 8KB. So next - // readahead size after prefetch should be 8 * 1024; - iter->Seek(BuildKey(4004)); - ASSERT_TRUE(iter->Valid()); - - while (iter->Valid() && buff_prefetch_count == 0) { - iter->Next(); - } - - ASSERT_EQ(readahead_size, 8 * 1024); - ASSERT_EQ(set_readahead, 0); - ASSERT_EQ(buff_prefetch_count, 1); - } - Close(); -} - -// This test verifies the functionality of adaptive_readaheadsize with cache and -// if block is found in cache, decrease the readahead_size if -// - its enabled internally by RocksDB (implicit_auto_readahead_) and, -// - readahead_size is greater than 0 and, -// - the block would have called prefetch API if not found in cache for -// which conditions are: -// - few/no bytes are in buffer and, -// - block is sequential with the previous read and, -// - num_file_reads_ + 1 (including this read) > -// num_file_reads_for_auto_readahead_ -TEST_P(PrefetchTest1, DecreaseReadAheadIfInCache) { - const int kNumKeys = 2000; - // Set options - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - Options options; - SetGenericOptions(env.get(), GetParam(), options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - std::shared_ptr cache = NewLRUCache(4 * 1024 * 1024, 2); // 8MB - table_options.block_cache = cache; - table_options.no_block_cache = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (GetParam() && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - int buff_prefetch_count = 0; - size_t current_readahead_size = 0; - size_t expected_current_readahead_size = 8 * 1024; - size_t decrease_readahead_size = 8 * 1024; - - SyncPoint::GetInstance()->SetCallBack("FilePrefetchBuffer::Prefetch:Start", - [&](void*) { buff_prefetch_count++; }); - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::TryReadFromCache", [&](void* arg) { - current_readahead_size = *reinterpret_cast(arg); - }); - - SyncPoint::GetInstance()->EnableProcessing(); - ReadOptions ro; - ro.adaptive_readahead = true; - { - /* - * Reseek keys from sequential Data Blocks within same partitioned - * index. After 2 sequential reads it will prefetch the data block. - * Data Block size is nearly 4076 so readahead will fetch 8 * 1024 data - * more initially (2 more data blocks). - */ - auto iter = std::unique_ptr(db_->NewIterator(ro)); - // Warm up the cache - iter->Seek(BuildKey(1011)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1015)); - ASSERT_TRUE(iter->Valid()); - iter->Seek(BuildKey(1019)); - ASSERT_TRUE(iter->Valid()); - buff_prefetch_count = 0; - } - - { - ASSERT_OK(options.statistics->Reset()); - // After caching, blocks will be read from cache (Sequential blocks) - auto iter = std::unique_ptr(db_->NewIterator(ro)); - iter->Seek( - BuildKey(0)); // In cache so it will decrease the readahead_size. - ASSERT_TRUE(iter->Valid()); - expected_current_readahead_size = std::max( - decrease_readahead_size, - (expected_current_readahead_size >= decrease_readahead_size - ? (expected_current_readahead_size - decrease_readahead_size) - : 0)); - - iter->Seek(BuildKey(1000)); // Won't prefetch the block. - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(current_readahead_size, expected_current_readahead_size); - - iter->Seek(BuildKey(1004)); // Prefetch the block. - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(current_readahead_size, expected_current_readahead_size); - expected_current_readahead_size *= 2; - - iter->Seek(BuildKey(1011)); - ASSERT_TRUE(iter->Valid()); - - // Eligible to Prefetch data (not in buffer) but block is in cache so no - // prefetch will happen and will result in decrease in readahead_size. - // readahead_size will be 8 * 1024 - iter->Seek(BuildKey(1015)); - ASSERT_TRUE(iter->Valid()); - expected_current_readahead_size = std::max( - decrease_readahead_size, - (expected_current_readahead_size >= decrease_readahead_size - ? (expected_current_readahead_size - decrease_readahead_size) - : 0)); - - // 1016 is the same block as 1015. So no change in readahead_size. - iter->Seek(BuildKey(1016)); - ASSERT_TRUE(iter->Valid()); - - // Prefetch data (not in buffer) but found in cache. So decrease - // readahead_size. Since it will 0 after decrementing so readahead_size will - // be set to initial value. - iter->Seek(BuildKey(1019)); - ASSERT_TRUE(iter->Valid()); - expected_current_readahead_size = std::max( - decrease_readahead_size, - (expected_current_readahead_size >= decrease_readahead_size - ? (expected_current_readahead_size - decrease_readahead_size) - : 0)); - - // Prefetch next sequential data. - iter->Seek(BuildKey(1022)); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(current_readahead_size, expected_current_readahead_size); - ASSERT_EQ(buff_prefetch_count, 2); - - buff_prefetch_count = 0; - } - Close(); -} - -// This test verifies the basic functionality of seek parallelization for -// async_io. -TEST_P(PrefetchTest1, SeekParallelizationTest) { - const int kNumKeys = 2000; - // Set options - std::shared_ptr fs = - std::make_shared(env_->GetFileSystem(), false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - Options options; - SetGenericOptions(env.get(), GetParam(), options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (GetParam() && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - int buff_prefetch_count = 0; - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::PrefetchAsyncInternal:Start", - [&](void*) { buff_prefetch_count++; }); - - SyncPoint::GetInstance()->EnableProcessing(); - ReadOptions ro; - ro.adaptive_readahead = true; - ro.async_io = true; - - { - ASSERT_OK(options.statistics->Reset()); - // Each block contains around 4 keys. - auto iter = std::unique_ptr(db_->NewIterator(ro)); - iter->Seek(BuildKey(0)); // Prefetch data because of seek parallelization. - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - - // New data block. Since num_file_reads in FilePrefetch after this read is - // 2, it won't go for prefetching. - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - - // Prefetch data. - iter->Next(); - ASSERT_TRUE(iter->Valid()); - - ASSERT_EQ(buff_prefetch_count, 2); - - // Check stats to make sure async prefetch is done. - { - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - ASSERT_GT(async_read_bytes.count, 0); - ASSERT_GT(get_perf_context()->number_async_seek, 0); - } - - buff_prefetch_count = 0; - } - Close(); -} - -namespace { -#ifdef GFLAGS -const int kMaxArgCount = 100; -const size_t kArgBufferSize = 100000; - -void RunIOTracerParserTool(std::string trace_file) { - std::vector params = {"./io_tracer_parser", - "-io_trace_file=" + trace_file}; - - char arg_buffer[kArgBufferSize]; - char* argv[kMaxArgCount]; - int argc = 0; - int cursor = 0; - for (const auto& arg : params) { - ASSERT_LE(cursor + arg.size() + 1, kArgBufferSize); - ASSERT_LE(argc + 1, kMaxArgCount); - - snprintf(arg_buffer + cursor, arg.size() + 1, "%s", arg.c_str()); - - argv[argc++] = arg_buffer + cursor; - cursor += static_cast(arg.size()) + 1; - } - ASSERT_EQ(0, ROCKSDB_NAMESPACE::io_tracer_parser(argc, argv)); -} -#endif // GFLAGS -} // namespace - -// Tests the default implementation of ReadAsync API with PosixFileSystem during -// prefetching. -TEST_P(PrefetchTest, ReadAsyncWithPosixFS) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - - const int kNumKeys = 1000; - std::shared_ptr fs = std::make_shared( - FileSystem::Default(), /*support_prefetch=*/false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - bool use_direct_io = std::get<0>(GetParam()); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - int total_keys = 0; - // Write the keys. - { - WriteBatch batch; - Random rnd(309); - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - total_keys++; - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - } - - int buff_prefetch_count = 0; - bool read_async_called = false; - ReadOptions ro; - ro.adaptive_readahead = true; - ro.async_io = true; - - if (std::get<1>(GetParam())) { - ro.readahead_size = 16 * 1024; - } - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::PrefetchAsyncInternal:Start", - [&](void*) { buff_prefetch_count++; }); - - SyncPoint::GetInstance()->SetCallBack( - "UpdateResults::io_uring_result", - [&](void* /*arg*/) { read_async_called = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Read the keys. - { - ASSERT_OK(options.statistics->Reset()); - get_perf_context()->Reset(); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - num_keys++; - } - - if (read_async_called) { - ASSERT_EQ(num_keys, total_keys); - ASSERT_GT(buff_prefetch_count, 0); - // Check stats to make sure async prefetch is done. - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - HistogramData prefetched_bytes_discarded; - options.statistics->histogramData(PREFETCHED_BYTES_DISCARDED, - &prefetched_bytes_discarded); - ASSERT_GT(async_read_bytes.count, 0); - ASSERT_GT(prefetched_bytes_discarded.count, 0); - ASSERT_EQ(get_perf_context()->number_async_seek, 0); - } else { - // Not all platforms support iouring. In that case, ReadAsync in posix - // won't submit async requests. - ASSERT_EQ(num_keys, total_keys); - ASSERT_EQ(buff_prefetch_count, 0); - } - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - Close(); -} - -// This test verifies implementation of seek parallelization with -// PosixFileSystem during prefetching. -TEST_P(PrefetchTest, MultipleSeekWithPosixFS) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - - const int kNumKeys = 1000; - std::shared_ptr fs = std::make_shared( - FileSystem::Default(), /*support_prefetch=*/false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - bool use_direct_io = std::get<0>(GetParam()); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - int total_keys = 0; - // Write the keys. - { - WriteBatch batch; - Random rnd(309); - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - total_keys++; - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - } - - int num_keys_first_batch = 0; - int num_keys_second_batch = 0; - // Calculate number of keys without async_io for correctness validation. - { - auto iter = std::unique_ptr(db_->NewIterator(ReadOptions())); - // First Seek. - iter->Seek(BuildKey(450)); - while (iter->Valid() && num_keys_first_batch < 100) { - ASSERT_OK(iter->status()); - num_keys_first_batch++; - iter->Next(); - } - ASSERT_OK(iter->status()); - - iter->Seek(BuildKey(942)); - while (iter->Valid()) { - ASSERT_OK(iter->status()); - num_keys_second_batch++; - iter->Next(); - } - ASSERT_OK(iter->status()); - } - - int buff_prefetch_count = 0; - bool read_async_called = false; - ReadOptions ro; - ro.adaptive_readahead = true; - ro.async_io = true; - - if (std::get<1>(GetParam())) { - ro.readahead_size = 16 * 1024; - } - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::PrefetchAsyncInternal:Start", - [&](void*) { buff_prefetch_count++; }); - - SyncPoint::GetInstance()->SetCallBack( - "UpdateResults::io_uring_result", - [&](void* /*arg*/) { read_async_called = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Read the keys using seek. - { - ASSERT_OK(options.statistics->Reset()); - get_perf_context()->Reset(); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - // First Seek. - { - iter->Seek(BuildKey(450)); - while (iter->Valid() && num_keys < 100) { - ASSERT_OK(iter->status()); - num_keys++; - iter->Next(); - } - - ASSERT_OK(iter->status()); - ASSERT_EQ(num_keys, num_keys_first_batch); - // Check stats to make sure async prefetch is done. - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - if (read_async_called) { - ASSERT_GT(async_read_bytes.count, 0); - ASSERT_GT(get_perf_context()->number_async_seek, 0); - } else { - // Not all platforms support iouring. In that case, ReadAsync in posix - // won't submit async requests. - ASSERT_EQ(async_read_bytes.count, 0); - ASSERT_EQ(get_perf_context()->number_async_seek, 0); - } - } - - // Second Seek. - { - num_keys = 0; - ASSERT_OK(options.statistics->Reset()); - get_perf_context()->Reset(); - - iter->Seek(BuildKey(942)); - while (iter->Valid()) { - ASSERT_OK(iter->status()); - num_keys++; - iter->Next(); - } - - ASSERT_OK(iter->status()); - ASSERT_EQ(num_keys, num_keys_second_batch); - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - HistogramData prefetched_bytes_discarded; - options.statistics->histogramData(PREFETCHED_BYTES_DISCARDED, - &prefetched_bytes_discarded); - ASSERT_GT(prefetched_bytes_discarded.count, 0); - - if (read_async_called) { - ASSERT_GT(buff_prefetch_count, 0); - - // Check stats to make sure async prefetch is done. - ASSERT_GT(async_read_bytes.count, 0); - ASSERT_GT(get_perf_context()->number_async_seek, 0); - } else { - // Not all platforms support iouring. In that case, ReadAsync in posix - // won't submit async requests. - ASSERT_EQ(async_read_bytes.count, 0); - ASSERT_EQ(get_perf_context()->number_async_seek, 0); - } - } - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - Close(); -} - -// This test verifies implementation of seek parallelization with -// PosixFileSystem during prefetching. -TEST_P(PrefetchTest, SeekParallelizationTestWithPosix) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - const int kNumKeys = 2000; - // Set options - std::shared_ptr fs = std::make_shared( - FileSystem::Default(), /*support_prefetch=*/false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - bool use_direct_io = std::get<0>(GetParam()); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - WriteBatch batch; - Random rnd(309); - for (int i = 0; i < kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - - std::string start_key = BuildKey(0); - std::string end_key = BuildKey(kNumKeys - 1); - Slice least(start_key.data(), start_key.size()); - Slice greatest(end_key.data(), end_key.size()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest)); - - int buff_prefetch_count = 0; - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::PrefetchAsyncInternal:Start", - [&](void*) { buff_prefetch_count++; }); - - bool read_async_called = false; - SyncPoint::GetInstance()->SetCallBack( - "UpdateResults::io_uring_result", - [&](void* /*arg*/) { read_async_called = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - SyncPoint::GetInstance()->EnableProcessing(); - ReadOptions ro; - ro.adaptive_readahead = true; - ro.async_io = true; - - if (std::get<1>(GetParam())) { - ro.readahead_size = 16 * 1024; - } - - { - ASSERT_OK(options.statistics->Reset()); - // Each block contains around 4 keys. - auto iter = std::unique_ptr(db_->NewIterator(ro)); - iter->Seek(BuildKey(0)); // Prefetch data because of seek parallelization. - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - - // New data block. Since num_file_reads in FilePrefetch after this read is - // 2, it won't go for prefetching. - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - - // Prefetch data. - iter->Next(); - - ASSERT_TRUE(iter->Valid()); - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - if (read_async_called) { - ASSERT_GT(async_read_bytes.count, 0); - ASSERT_GT(get_perf_context()->number_async_seek, 0); - if (std::get<1>(GetParam())) { - ASSERT_EQ(buff_prefetch_count, 1); - } else { - ASSERT_EQ(buff_prefetch_count, 2); - } - } else { - // Not all platforms support iouring. In that case, ReadAsync in posix - // won't submit async requests. - ASSERT_EQ(async_read_bytes.count, 0); - ASSERT_EQ(get_perf_context()->number_async_seek, 0); - } - } - Close(); -} - -#ifdef GFLAGS -// This test verifies io_tracing with PosixFileSystem during prefetching. -TEST_P(PrefetchTest, TraceReadAsyncWithCallbackWrapper) { - if (mem_env_ || encrypted_env_) { - ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); - return; - } - - const int kNumKeys = 1000; - std::shared_ptr fs = std::make_shared( - FileSystem::Default(), /*support_prefetch=*/false); - std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); - - bool use_direct_io = std::get<0>(GetParam()); - Options options; - SetGenericOptions(env.get(), use_direct_io, options); - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; - SetBlockBasedTableOptions(table_options); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - Status s = TryReopen(options); - if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) { - // If direct IO is not supported, skip the test - return; - } else { - ASSERT_OK(s); - } - - int total_keys = 0; - // Write the keys. - { - WriteBatch batch; - Random rnd(309); - for (int j = 0; j < 5; j++) { - for (int i = j * kNumKeys; i < (j + 1) * kNumKeys; i++) { - ASSERT_OK(batch.Put(BuildKey(i), rnd.RandomString(1000))); - total_keys++; - } - ASSERT_OK(db_->Write(WriteOptions(), &batch)); - ASSERT_OK(Flush()); - } - MoveFilesToLevel(2); - } - - int buff_prefetch_count = 0; - bool read_async_called = false; - ReadOptions ro; - ro.adaptive_readahead = true; - ro.async_io = true; - - if (std::get<1>(GetParam())) { - ro.readahead_size = 16 * 1024; - } - - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::PrefetchAsyncInternal:Start", - [&](void*) { buff_prefetch_count++; }); - - SyncPoint::GetInstance()->SetCallBack( - "UpdateResults::io_uring_result", - [&](void* /*arg*/) { read_async_called = true; }); - SyncPoint::GetInstance()->EnableProcessing(); - - // Read the keys. - { - // Start io_tracing. - WriteOptions write_opt; - TraceOptions trace_opt; - std::unique_ptr trace_writer; - std::string trace_file_path = dbname_ + "/io_trace_file"; - - ASSERT_OK( - NewFileTraceWriter(env_, EnvOptions(), trace_file_path, &trace_writer)); - ASSERT_OK(db_->StartIOTrace(trace_opt, std::move(trace_writer))); - ASSERT_OK(options.statistics->Reset()); - - auto iter = std::unique_ptr(db_->NewIterator(ro)); - int num_keys = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ASSERT_OK(iter->status()); - num_keys++; - } - - // End the tracing. - ASSERT_OK(db_->EndIOTrace()); - ASSERT_OK(env_->FileExists(trace_file_path)); - - ASSERT_EQ(num_keys, total_keys); - HistogramData async_read_bytes; - options.statistics->histogramData(ASYNC_READ_BYTES, &async_read_bytes); - if (read_async_called) { - ASSERT_GT(buff_prefetch_count, 0); - // Check stats to make sure async prefetch is done. - ASSERT_GT(async_read_bytes.count, 0); - } else { - // Not all platforms support iouring. In that case, ReadAsync in posix - // won't submit async requests. - ASSERT_EQ(async_read_bytes.count, 0); - } - - // Check the file to see if ReadAsync is logged. - RunIOTracerParserTool(trace_file_path); - } - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - - Close(); -} -#endif // GFLAGS - -class FilePrefetchBufferTest : public testing::Test { - public: - void SetUp() override { - SetupSyncPointsToMockDirectIO(); - env_ = Env::Default(); - fs_ = FileSystem::Default(); - test_dir_ = test::PerThreadDBPath("file_prefetch_buffer_test"); - ASSERT_OK(fs_->CreateDir(test_dir_, IOOptions(), nullptr)); - stats_ = CreateDBStatistics(); - } - - void TearDown() override { EXPECT_OK(DestroyDir(env_, test_dir_)); } - - void Write(const std::string& fname, const std::string& content) { - std::unique_ptr f; - ASSERT_OK(fs_->NewWritableFile(Path(fname), FileOptions(), &f, nullptr)); - ASSERT_OK(f->Append(content, IOOptions(), nullptr)); - ASSERT_OK(f->Close(IOOptions(), nullptr)); - } - - void Read(const std::string& fname, const FileOptions& opts, - std::unique_ptr* reader) { - std::string fpath = Path(fname); - std::unique_ptr f; - ASSERT_OK(fs_->NewRandomAccessFile(fpath, opts, &f, nullptr)); - reader->reset(new RandomAccessFileReader( - std::move(f), fpath, env_->GetSystemClock().get(), - /*io_tracer=*/nullptr, stats_.get())); - } - - void AssertResult(const std::string& content, - const std::vector& reqs) { - for (const auto& r : reqs) { - ASSERT_OK(r.status); - ASSERT_EQ(r.len, r.result.size()); - ASSERT_EQ(content.substr(r.offset, r.len), r.result.ToString()); - } - } - - FileSystem* fs() { return fs_.get(); } - Statistics* stats() { return stats_.get(); } - - private: - Env* env_; - std::shared_ptr fs_; - std::string test_dir_; - std::shared_ptr stats_; - - std::string Path(const std::string& fname) { return test_dir_ + "/" + fname; } -}; - -TEST_F(FilePrefetchBufferTest, SeekWithBlockCacheHit) { - std::string fname = "seek-with-block-cache-hit"; - Random rand(0); - std::string content = rand.RandomString(32768); - Write(fname, content); - - FileOptions opts; - std::unique_ptr r; - Read(fname, opts, &r); - - FilePrefetchBuffer fpb(16384, 16384, true, false, false, 0, 0, fs()); - Slice result; - // Simulate a seek of 4096 bytes at offset 0. Due to the readahead settings, - // it will do two reads of 4096+8192 and 8192 - Status s = fpb.PrefetchAsync(IOOptions(), r.get(), 0, 4096, &result); - - // Platforms that don't have IO uring may not support async IO. - if (s.IsNotSupported()) { - return; - } - - ASSERT_TRUE(s.IsTryAgain()); - // Simulate a block cache hit - fpb.UpdateReadPattern(0, 4096, false); - // Now read some data that straddles the two prefetch buffers - offset 8192 to - // 16384 - ASSERT_TRUE(fpb.TryReadFromCacheAsync(IOOptions(), r.get(), 8192, 8192, - &result, &s, Env::IOPriority::IO_LOW)); -} - -TEST_F(FilePrefetchBufferTest, NoSyncWithAsyncIO) { - std::string fname = "seek-with-block-cache-hit"; - Random rand(0); - std::string content = rand.RandomString(32768); - Write(fname, content); - - FileOptions opts; - std::unique_ptr r; - Read(fname, opts, &r); - - FilePrefetchBuffer fpb( - /*readahead_size=*/8192, /*max_readahead_size=*/16384, /*enable=*/true, - /*track_min_offset=*/false, /*implicit_auto_readahead=*/false, - /*num_file_reads=*/0, /*num_file_reads_for_auto_readahead=*/0, fs()); - - int read_async_called = 0; - SyncPoint::GetInstance()->SetCallBack( - "FilePrefetchBuffer::ReadAsync", - [&](void* /*arg*/) { read_async_called++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Slice async_result; - // Simulate a seek of 4000 bytes at offset 3000. Due to the readahead - // settings, it will do two reads of 4000+4096 and 4096 - Status s = fpb.PrefetchAsync(IOOptions(), r.get(), 3000, 4000, &async_result); - - // Platforms that don't have IO uring may not support async IO - if (s.IsNotSupported()) { - return; - } - - ASSERT_TRUE(s.IsTryAgain()); - ASSERT_TRUE(fpb.TryReadFromCacheAsync(IOOptions(), r.get(), /*offset=*/3000, - /*length=*/4000, &async_result, &s, - Env::IOPriority::IO_LOW)); - // No sync call should be made. - HistogramData sst_read_micros; - stats()->histogramData(SST_READ_MICROS, &sst_read_micros); - ASSERT_EQ(sst_read_micros.count, 0); - - // Number of async calls should be. - ASSERT_EQ(read_async_called, 2); - // Length should be 4000. - ASSERT_EQ(async_result.size(), 4000); - // Data correctness. - Slice result(content.c_str() + 3000, 4000); - ASSERT_EQ(result.size(), 4000); - ASSERT_EQ(result, async_result); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/file/random_access_file_reader_test.cc b/file/random_access_file_reader_test.cc deleted file mode 100644 index 22e950c78..000000000 --- a/file/random_access_file_reader_test.cc +++ /dev/null @@ -1,479 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "file/random_access_file_reader.h" - -#include - -#include "file/file_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/file_system.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class RandomAccessFileReaderTest : public testing::Test { - public: - void SetUp() override { - SetupSyncPointsToMockDirectIO(); - env_ = Env::Default(); - fs_ = FileSystem::Default(); - test_dir_ = test::PerThreadDBPath("random_access_file_reader_test"); - ASSERT_OK(fs_->CreateDir(test_dir_, IOOptions(), nullptr)); - } - - void TearDown() override { EXPECT_OK(DestroyDir(env_, test_dir_)); } - - void Write(const std::string& fname, const std::string& content) { - std::unique_ptr f; - ASSERT_OK(fs_->NewWritableFile(Path(fname), FileOptions(), &f, nullptr)); - ASSERT_OK(f->Append(content, IOOptions(), nullptr)); - ASSERT_OK(f->Close(IOOptions(), nullptr)); - } - - void Read(const std::string& fname, const FileOptions& opts, - std::unique_ptr* reader) { - std::string fpath = Path(fname); - std::unique_ptr f; - ASSERT_OK(fs_->NewRandomAccessFile(fpath, opts, &f, nullptr)); - reader->reset(new RandomAccessFileReader(std::move(f), fpath, - env_->GetSystemClock().get())); - } - - void AssertResult(const std::string& content, - const std::vector& reqs) { - for (const auto& r : reqs) { - ASSERT_OK(r.status); - ASSERT_EQ(r.len, r.result.size()); - ASSERT_EQ(content.substr(r.offset, r.len), r.result.ToString()); - } - } - - private: - Env* env_; - std::shared_ptr fs_; - std::string test_dir_; - - std::string Path(const std::string& fname) { return test_dir_ + "/" + fname; } -}; - -// Skip the following tests in lite mode since direct I/O is unsupported. - -TEST_F(RandomAccessFileReaderTest, ReadDirectIO) { - std::string fname = "read-direct-io"; - Random rand(0); - std::string content = rand.RandomString(kDefaultPageSize); - Write(fname, content); - - FileOptions opts; - opts.use_direct_reads = true; - std::unique_ptr r; - Read(fname, opts, &r); - ASSERT_TRUE(r->use_direct_io()); - - const size_t page_size = r->file()->GetRequiredBufferAlignment(); - size_t offset = page_size / 2; - size_t len = page_size / 3; - Slice result; - AlignedBuf buf; - for (Env::IOPriority rate_limiter_priority : {Env::IO_LOW, Env::IO_TOTAL}) { - ASSERT_OK(r->Read(IOOptions(), offset, len, &result, nullptr, &buf, - rate_limiter_priority)); - ASSERT_EQ(result.ToString(), content.substr(offset, len)); - } -} - -TEST_F(RandomAccessFileReaderTest, MultiReadDirectIO) { - std::vector aligned_reqs; - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "RandomAccessFileReader::MultiRead:AlignedReqs", [&](void* reqs) { - // Copy reqs, since it's allocated on stack inside MultiRead, which will - // be deallocated after MultiRead returns. - aligned_reqs = *reinterpret_cast*>(reqs); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // Creates a file with 3 pages. - std::string fname = "multi-read-direct-io"; - Random rand(0); - std::string content = rand.RandomString(3 * kDefaultPageSize); - Write(fname, content); - - FileOptions opts; - opts.use_direct_reads = true; - std::unique_ptr r; - Read(fname, opts, &r); - ASSERT_TRUE(r->use_direct_io()); - - const size_t page_size = r->file()->GetRequiredBufferAlignment(); - - { - // Reads 2 blocks in the 1st page. - // The results should be SharedSlices of the same underlying buffer. - // - // Illustration (each x is a 1/4 page) - // First page: xxxx - // 1st block: x - // 2nd block: xx - FSReadRequest r0; - r0.offset = 0; - r0.len = page_size / 4; - r0.scratch = nullptr; - - FSReadRequest r1; - r1.offset = page_size / 2; - r1.len = page_size / 2; - r1.scratch = nullptr; - - std::vector reqs; - reqs.push_back(std::move(r0)); - reqs.push_back(std::move(r1)); - AlignedBuf aligned_buf; - ASSERT_OK(r->MultiRead(IOOptions(), reqs.data(), reqs.size(), &aligned_buf, - Env::IO_TOTAL /* rate_limiter_priority */)); - - AssertResult(content, reqs); - - // Reads the first page internally. - ASSERT_EQ(aligned_reqs.size(), 1); - const FSReadRequest& aligned_r = aligned_reqs[0]; - ASSERT_OK(aligned_r.status); - ASSERT_EQ(aligned_r.offset, 0); - ASSERT_EQ(aligned_r.len, page_size); - } - - { - // Reads 3 blocks: - // 1st block in the 1st page; - // 2nd block from the middle of the 1st page to the middle of the 2nd page; - // 3rd block in the 2nd page. - // The results should be SharedSlices of the same underlying buffer. - // - // Illustration (each x is a 1/4 page) - // 2 pages: xxxxxxxx - // 1st block: x - // 2nd block: xxxx - // 3rd block: x - FSReadRequest r0; - r0.offset = 0; - r0.len = page_size / 4; - r0.scratch = nullptr; - - FSReadRequest r1; - r1.offset = page_size / 2; - r1.len = page_size; - r1.scratch = nullptr; - - FSReadRequest r2; - r2.offset = 2 * page_size - page_size / 4; - r2.len = page_size / 4; - r2.scratch = nullptr; - - std::vector reqs; - reqs.push_back(std::move(r0)); - reqs.push_back(std::move(r1)); - reqs.push_back(std::move(r2)); - AlignedBuf aligned_buf; - ASSERT_OK(r->MultiRead(IOOptions(), reqs.data(), reqs.size(), &aligned_buf, - Env::IO_TOTAL /* rate_limiter_priority */)); - - AssertResult(content, reqs); - - // Reads the first two pages in one request internally. - ASSERT_EQ(aligned_reqs.size(), 1); - const FSReadRequest& aligned_r = aligned_reqs[0]; - ASSERT_OK(aligned_r.status); - ASSERT_EQ(aligned_r.offset, 0); - ASSERT_EQ(aligned_r.len, 2 * page_size); - } - - { - // Reads 3 blocks: - // 1st block in the middle of the 1st page; - // 2nd block in the middle of the 2nd page; - // 3rd block in the middle of the 3rd page. - // The results should be SharedSlices of the same underlying buffer. - // - // Illustration (each x is a 1/4 page) - // 3 pages: xxxxxxxxxxxx - // 1st block: xx - // 2nd block: xx - // 3rd block: xx - FSReadRequest r0; - r0.offset = page_size / 4; - r0.len = page_size / 2; - r0.scratch = nullptr; - - FSReadRequest r1; - r1.offset = page_size + page_size / 4; - r1.len = page_size / 2; - r1.scratch = nullptr; - - FSReadRequest r2; - r2.offset = 2 * page_size + page_size / 4; - r2.len = page_size / 2; - r2.scratch = nullptr; - - std::vector reqs; - reqs.push_back(std::move(r0)); - reqs.push_back(std::move(r1)); - reqs.push_back(std::move(r2)); - AlignedBuf aligned_buf; - ASSERT_OK(r->MultiRead(IOOptions(), reqs.data(), reqs.size(), &aligned_buf, - Env::IO_TOTAL /* rate_limiter_priority */)); - - AssertResult(content, reqs); - - // Reads the first 3 pages in one request internally. - ASSERT_EQ(aligned_reqs.size(), 1); - const FSReadRequest& aligned_r = aligned_reqs[0]; - ASSERT_OK(aligned_r.status); - ASSERT_EQ(aligned_r.offset, 0); - ASSERT_EQ(aligned_r.len, 3 * page_size); - } - - { - // Reads 2 blocks: - // 1st block in the middle of the 1st page; - // 2nd block in the middle of the 3rd page. - // The results are two different buffers. - // - // Illustration (each x is a 1/4 page) - // 3 pages: xxxxxxxxxxxx - // 1st block: xx - // 2nd block: xx - FSReadRequest r0; - r0.offset = page_size / 4; - r0.len = page_size / 2; - r0.scratch = nullptr; - - FSReadRequest r1; - r1.offset = 2 * page_size + page_size / 4; - r1.len = page_size / 2; - r1.scratch = nullptr; - - std::vector reqs; - reqs.push_back(std::move(r0)); - reqs.push_back(std::move(r1)); - AlignedBuf aligned_buf; - ASSERT_OK(r->MultiRead(IOOptions(), reqs.data(), reqs.size(), &aligned_buf, - Env::IO_TOTAL /* rate_limiter_priority */)); - - AssertResult(content, reqs); - - // Reads the 1st and 3rd pages in two requests internally. - ASSERT_EQ(aligned_reqs.size(), 2); - const FSReadRequest& aligned_r0 = aligned_reqs[0]; - const FSReadRequest& aligned_r1 = aligned_reqs[1]; - ASSERT_OK(aligned_r0.status); - ASSERT_EQ(aligned_r0.offset, 0); - ASSERT_EQ(aligned_r0.len, page_size); - ASSERT_OK(aligned_r1.status); - ASSERT_EQ(aligned_r1.offset, 2 * page_size); - ASSERT_EQ(aligned_r1.len, page_size); - } - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); -} - - -TEST(FSReadRequest, Align) { - FSReadRequest r; - r.offset = 2000; - r.len = 2000; - r.scratch = nullptr; - ASSERT_OK(r.status); - - FSReadRequest aligned_r = Align(r, 1024); - ASSERT_OK(r.status); - ASSERT_OK(aligned_r.status); - ASSERT_EQ(aligned_r.offset, 1024); - ASSERT_EQ(aligned_r.len, 3072); -} - -TEST(FSReadRequest, TryMerge) { - // reverse means merging dest into src. - for (bool reverse : {true, false}) { - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 15; - src.len = 10; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) { - std::swap(dest, src); - } - ASSERT_FALSE(TryMerge(&dest, src)); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 10; - src.len = 10; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) { - std::swap(dest, src); - } - ASSERT_TRUE(TryMerge(&dest, src)); - ASSERT_EQ(dest.offset, 0); - ASSERT_EQ(dest.len, 20); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 5; - src.len = 10; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) { - std::swap(dest, src); - } - ASSERT_TRUE(TryMerge(&dest, src)); - ASSERT_EQ(dest.offset, 0); - ASSERT_EQ(dest.len, 15); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 5; - src.len = 5; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) { - std::swap(dest, src); - } - ASSERT_TRUE(TryMerge(&dest, src)); - ASSERT_EQ(dest.offset, 0); - ASSERT_EQ(dest.len, 10); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 5; - src.len = 1; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) std::swap(dest, src); - ASSERT_TRUE(TryMerge(&dest, src)); - ASSERT_EQ(dest.offset, 0); - ASSERT_EQ(dest.len, 10); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 0; - src.len = 10; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) std::swap(dest, src); - ASSERT_TRUE(TryMerge(&dest, src)); - ASSERT_EQ(dest.offset, 0); - ASSERT_EQ(dest.len, 10); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - - { - // dest: [ ] - // src: [ ] - FSReadRequest dest; - dest.offset = 0; - dest.len = 10; - dest.scratch = nullptr; - ASSERT_OK(dest.status); - - FSReadRequest src; - src.offset = 0; - src.len = 5; - src.scratch = nullptr; - ASSERT_OK(src.status); - - if (reverse) std::swap(dest, src); - ASSERT_TRUE(TryMerge(&dest, src)); - ASSERT_EQ(dest.offset, 0); - ASSERT_EQ(dest.len, 10); - ASSERT_OK(dest.status); - ASSERT_OK(src.status); - } - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/fuzz/.gitignore b/fuzz/.gitignore deleted file mode 100644 index 9dab42105..000000000 --- a/fuzz/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -db_fuzzer -db_map_fuzzer -sst_file_writer_fuzzer - -proto/gen/* diff --git a/fuzz/Makefile b/fuzz/Makefile deleted file mode 100644 index b83040504..000000000 --- a/fuzz/Makefile +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -# This source code is licensed under both the GPLv2 (found in the -# COPYING file in the root directory) and Apache 2.0 License -# (found in the LICENSE.Apache file in the root directory). - -ROOT_DIR = $(abspath $(shell pwd)/../) - -include $(ROOT_DIR)/make_config.mk - -PROTOBUF_CFLAGS = `pkg-config --cflags protobuf` -PROTOBUF_LDFLAGS = `pkg-config --libs protobuf` - -PROTOBUF_MUTATOR_CFLAGS = `pkg-config --cflags libprotobuf-mutator` -PROTOBUF_MUTATOR_LDFLAGS = `pkg-config --libs libprotobuf-mutator` - -ROCKSDB_INCLUDE_DIR = $(ROOT_DIR)/include -ROCKSDB_LIB_DIR = $(ROOT_DIR) - -PROTO_IN = $(ROOT_DIR)/fuzz/proto -PROTO_OUT = $(ROOT_DIR)/fuzz/proto/gen - -ifneq ($(FUZZ_ENV), ossfuzz) -CC = $(CXX) -CCFLAGS += -Wall -fsanitize=address,fuzzer -CFLAGS += $(PLATFORM_CXXFLAGS) $(PROTOBUF_CFLAGS) $(PROTOBUF_MUTATOR_CFLAGS) -I$(PROTO_OUT) -I$(ROCKSDB_INCLUDE_DIR) -I$(ROCKSDB_LIB_DIR) -LDFLAGS += $(PLATFORM_LDFLAGS) $(PROTOBUF_MUTATOR_LDFLAGS) $(PROTOBUF_LDFLAGS) -L$(ROCKSDB_LIB_DIR) -lrocksdb -else -# OSS-Fuzz sets various environment flags that are used for compilation. -# These environment flags depend on which type of sanitizer build is being -# used, however, an ASan build would set the environment flags as follows: -# CFLAGS="-O1 -fno-omit-frame-pointer -gline-tables-only \ - -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address \ - -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link" -# CXXFLAGS="-O1 -fno-omit-frame-pointer -gline-tables-only \ - -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address \ - -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link \ - -stdlib=libc++" -# LIB_FUZZING_ENGINE="-fsanitize=fuzzer" -CC = $(CXX) -CCFLAGS = $(CXXFLAGS) -CFLAGS += $(PROTOBUF_CFLAGS) $(PROTOBUF_MUTATOR_CFLAGS) -I$(PROTO_OUT) -I$(ROCKSDB_INCLUDE_DIR) -I$(ROCKSDB_LIB_DIR) -LDFLAGS += $(PLATFORM_LDFLAGS) $(LIB_FUZZING_ENGINE) $(PROTOBUF_MUTATOR_LDFLAGS) $(PROTOBUF_LDFLAGS) -L$(ROCKSDB_LIB_DIR) -lrocksdb -endif - -.PHONY: gen_proto clean - -# Set PROTOC_BIN when invoking `make` if a custom protoc is required. -PROTOC_BIN ?= protoc - -gen_proto: - mkdir -p $(PROTO_OUT) - $(PROTOC_BIN) \ - --proto_path=$(PROTO_IN) \ - --cpp_out=$(PROTO_OUT) \ - $(PROTO_IN)/*.proto - -clean: - rm -rf db_fuzzer db_map_fuzzer sst_file_writer_fuzzer $(PROTO_OUT) - -db_fuzzer: db_fuzzer.cc - $(CC) $(CCFLAGS) -o db_fuzzer db_fuzzer.cc $(CFLAGS) $(LDFLAGS) - -db_map_fuzzer: gen_proto db_map_fuzzer.cc proto/gen/db_operation.pb.cc - $(CC) $(CCFLAGS) -o db_map_fuzzer db_map_fuzzer.cc proto/gen/db_operation.pb.cc $(CFLAGS) $(LDFLAGS) - -sst_file_writer_fuzzer: gen_proto sst_file_writer_fuzzer.cc proto/gen/db_operation.pb.cc - $(CC) $(CCFLAGS) -o sst_file_writer_fuzzer sst_file_writer_fuzzer.cc proto/gen/db_operation.pb.cc $(CFLAGS) $(LDFLAGS) diff --git a/fuzz/README.md b/fuzz/README.md deleted file mode 100644 index 238b283a2..000000000 --- a/fuzz/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# Fuzzing RocksDB - -## Overview - -This directory contains [fuzz tests](https://en.wikipedia.org/wiki/Fuzzing) for RocksDB. -RocksDB testing infrastructure currently includes unit tests and [stress tests](https://github.com/facebook/rocksdb/wiki/Stress-test), -we hope fuzz testing can catch more bugs. - -## Prerequisite - -We use [LLVM libFuzzer](http://llvm.org/docs/LibFuzzer.html) as the fuzzying engine, -so make sure you have [clang](https://clang.llvm.org/get_started.html) as your compiler. - -Some tests rely on [structure aware fuzzing](https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md). -We use [protobuf](https://developers.google.com/protocol-buffers) to define structured input to the fuzzer, -and use [libprotobuf-mutator](https://github.com/google/libprotobuf-mutator) as the custom libFuzzer mutator. -So make sure you have protobuf and libprotobuf-mutator installed, and make sure `pkg-config` can find them. -On some systems, there are both protobuf2 and protobuf3 in the package management system, -make sure protobuf3 is installed. - -If you do not want to install protobuf library yourself, you can rely on libprotobuf-mutator to download protobuf -for you. For details about installation, please refer to [libprotobuf-mutator README](https://github.com/google/libprotobuf-mutator#readme) - -## Example - -This example shows you how to do structure aware fuzzing to `rocksdb::SstFileWriter`. - -After walking through the steps to create the fuzzer, we'll introduce a bug into `rocksdb::SstFileWriter::Put`, -then show that the fuzzer can catch the bug. - -### Design the test - -We want the fuzzing engine to automatically generate a list of database operations, -then we apply these operations to `SstFileWriter` in sequence, -finally, after the SST file is generated, we use `SstFileReader` to check the file's checksum. - -### Define input - -We define the database operations in protobuf, each operation has a type of operation and a key value pair, -see [proto/db_operation.proto](proto/db_operation.proto) for details. - -### Define tests with the input - -In [sst_file_writer_fuzzer.cc](sst_file_writer_fuzzer.cc), -we define the tests to be run on the generated input: - -``` -DEFINE_PROTO_FUZZER(DBOperations& input) { - // apply the operations to SstFileWriter and use SstFileReader to verify checksum. - // ... -} -``` - -`SstFileWriter` requires the keys of the operations to be unique and be in ascending order, -but the fuzzing engine generates the input randomly, so we need to process the generated input before -passing it to `DEFINE_PROTO_FUZZER`, this is accomplished by registering a post processor: - -``` -protobuf_mutator::libfuzzer::PostProcessorRegistration -``` - -### Compile and link the fuzzer - -In the rocksdb root directory, compile rocksdb library by `make static_lib`. - -Go to the `fuzz` directory, -run `make sst_file_writer_fuzzer` to generate the fuzzer, -it will compile rocksdb static library, generate protobuf, then compile and link `sst_file_writer_fuzzer`. - -### Introduce a bug - -Manually introduce a bug to `SstFileWriter::Put`: - -``` -diff --git a/table/sst_file_writer.cc b/table/sst_file_writer.cc -index ab1ee7c4e..c7da9ffa0 100644 ---- a/table/sst_file_writer.cc -+++ b/table/sst_file_writer.cc -@@ -277,6 +277,11 @@ Status SstFileWriter::Add(const Slice& user_key, const Slice& value) { - } - - Status SstFileWriter::Put(const Slice& user_key, const Slice& value) { -+ if (user_key.starts_with("!")) { -+ if (value.ends_with("!")) { -+ return Status::Corruption("bomb"); -+ } -+ } - return rep_->Add(user_key, value, ValueType::kTypeValue); - } -``` - -The bug is that for `Put`, if `user_key` starts with `!` and `value` ends with `!`, then corrupt. - -### Run fuzz testing to catch the bug - -Run the fuzzer by `time ./sst_file_writer_fuzzer`. - -Here is the output on my machine: - -``` -Corruption: bomb -==59680== ERROR: libFuzzer: deadly signal - #0 0x109487315 in __sanitizer_print_stack_trace+0x35 (libclang_rt.asan_osx_dynamic.dylib:x86_64+0x4d315) - #1 0x108d63f18 in fuzzer::PrintStackTrace() FuzzerUtil.cpp:205 - #2 0x108d47613 in fuzzer::Fuzzer::CrashCallback() FuzzerLoop.cpp:232 - #3 0x7fff6af535fc in _sigtramp+0x1c (libsystem_platform.dylib:x86_64+0x35fc) - #4 0x7ffee720f3ef () - #5 0x7fff6ae29807 in abort+0x77 (libsystem_c.dylib:x86_64+0x7f807) - #6 0x108cf1c4c in TestOneProtoInput(DBOperations&)+0x113c (sst_file_writer_fuzzer:x86_64+0x100302c4c) - #7 0x108cf09be in LLVMFuzzerTestOneInput+0x16e (sst_file_writer_fuzzer:x86_64+0x1003019be) - #8 0x108d48ce0 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:556 - #9 0x108d48425 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470 - #10 0x108d4a626 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698 - #11 0x108d4b325 in fuzzer::Fuzzer::Loop(std::__1::vector >&) FuzzerLoop.cpp:830 - #12 0x108d37fcd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829 - #13 0x108d652b2 in main FuzzerMain.cpp:19 - #14 0x7fff6ad5acc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8) - -NOTE: libFuzzer has rudimentary signal handlers. - Combine libFuzzer with AddressSanitizer or similar for better crash reports. -SUMMARY: libFuzzer: deadly signal -MS: 7 Custom-CustomCrossOver-InsertByte-Custom-ChangeBit-Custom-CustomCrossOver-; base unit: 90863b4d83c3f994bba0a417d0c2ee3b68f9e795 -0x6f,0x70,0x65,0x72,0x61,0x74,0x69,0x6f,0x6e,0x73,0x20,0x7b,0xa,0x20,0x20,0x6b,0x65,0x79,0x3a,0x20,0x22,0x21,0x22,0xa,0x20,0x20,0x76,0x61,0x6c,0x75,0x65,0x3a,0x20,0x22,0x21,0x22,0xa,0x20,0x20,0x74,0x79,0x70,0x65,0x3a,0x20,0x50,0x55,0x54,0xa,0x7d,0xa,0x6f,0x70,0x65,0x72,0x61,0x74,0x69,0x6f,0x6e,0x73,0x20,0x7b,0xa,0x20,0x20,0x6b,0x65,0x79,0x3a,0x20,0x22,0x2b,0x22,0xa,0x20,0x20,0x74,0x79,0x70,0x65,0x3a,0x20,0x50,0x55,0x54,0xa,0x7d,0xa,0x6f,0x70,0x65,0x72,0x61,0x74,0x69,0x6f,0x6e,0x73,0x20,0x7b,0xa,0x20,0x20,0x6b,0x65,0x79,0x3a,0x20,0x22,0x2e,0x22,0xa,0x20,0x20,0x74,0x79,0x70,0x65,0x3a,0x20,0x50,0x55,0x54,0xa,0x7d,0xa,0x6f,0x70,0x65,0x72,0x61,0x74,0x69,0x6f,0x6e,0x73,0x20,0x7b,0xa,0x20,0x20,0x6b,0x65,0x79,0x3a,0x20,0x22,0x5c,0x32,0x35,0x33,0x22,0xa,0x20,0x20,0x74,0x79,0x70,0x65,0x3a,0x20,0x50,0x55,0x54,0xa,0x7d,0xa, -operations {\x0a key: \"!\"\x0a value: \"!\"\x0a type: PUT\x0a}\x0aoperations {\x0a key: \"+\"\x0a type: PUT\x0a}\x0aoperations {\x0a key: \".\"\x0a type: PUT\x0a}\x0aoperations {\x0a key: \"\\253\"\x0a type: PUT\x0a}\x0a -artifact_prefix='./'; Test unit written to ./crash-a1460be302d09b548e61787178d9edaa40aea467 -Base64: b3BlcmF0aW9ucyB7CiAga2V5OiAiISIKICB2YWx1ZTogIiEiCiAgdHlwZTogUFVUCn0Kb3BlcmF0aW9ucyB7CiAga2V5OiAiKyIKICB0eXBlOiBQVVQKfQpvcGVyYXRpb25zIHsKICBrZXk6ICIuIgogIHR5cGU6IFBVVAp9Cm9wZXJhdGlvbnMgewogIGtleTogIlwyNTMiCiAgdHlwZTogUFVUCn0K -./sst_file_writer_fuzzer 5.97s user 4.40s system 64% cpu 16.195 total -``` - -Within 6 seconds, it catches the bug. - -The input that triggers the bug is persisted in `./crash-a1460be302d09b548e61787178d9edaa40aea467`: - -``` -$ cat ./crash-a1460be302d09b548e61787178d9edaa40aea467 -operations { - key: "!" - value: "!" - type: PUT -} -operations { - key: "+" - type: PUT -} -operations { - key: "." - type: PUT -} -operations { - key: "\253" - type: PUT -} -``` - -### Reproduce the crash to debug - -The above crash can be reproduced by `./sst_file_writer_fuzzer ./crash-a1460be302d09b548e61787178d9edaa40aea467`, -so you can debug the crash. - -## Future Work - -According to [OSS-Fuzz](https://github.com/google/oss-fuzz), -`as of June 2020, OSS-Fuzz has found over 20,000 bugs in 300 open source projects.` - -RocksDB can join OSS-Fuzz together with other open source projects such as sqlite. diff --git a/fuzz/db_fuzzer.cc b/fuzz/db_fuzzer.cc deleted file mode 100644 index e6d5bb63c..000000000 --- a/fuzz/db_fuzzer.cc +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "rocksdb/db.h" - -enum OperationType { - kPut, - kGet, - kDelete, - kGetProperty, - kIterator, - kSnapshot, - kOpenClose, - kColumn, - kCompactRange, - kSeekForPrev, - OP_COUNT -}; - -constexpr char db_path[] = "/tmp/testdb"; - -// Fuzzes DB operations by doing interpretations on the data. Both the -// sequence of API calls to be called on the DB as well as the arguments -// to each of these APIs are interpreted by way of the data buffer. -// The operations that the fuzzer supports are given by the OperationType -// enum. The goal is to capture sanitizer bugs, so the code should be -// compiled with a given sanitizer (ASan, UBSan, MSan). -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - ROCKSDB_NAMESPACE::DB* db; - ROCKSDB_NAMESPACE::Options options; - options.create_if_missing = true; - ROCKSDB_NAMESPACE::Status status = - ROCKSDB_NAMESPACE::DB::Open(options, db_path, &db); - if (!status.ok()) { - return 0; - } - FuzzedDataProvider fuzzed_data(data, size); - - // perform a sequence of calls on our db instance - int max_iter = static_cast(data[0]); - for (int i = 0; i < max_iter && i < size; i++) { - OperationType op = static_cast(data[i] % OP_COUNT); - - switch (op) { - case kPut: { - std::string key = fuzzed_data.ConsumeRandomLengthString(); - std::string val = fuzzed_data.ConsumeRandomLengthString(); - db->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, val); - break; - } - case kGet: { - std::string key = fuzzed_data.ConsumeRandomLengthString(); - std::string value; - db->Get(ROCKSDB_NAMESPACE::ReadOptions(), key, &value); - break; - } - case kDelete: { - std::string key = fuzzed_data.ConsumeRandomLengthString(); - db->Delete(ROCKSDB_NAMESPACE::WriteOptions(), key); - break; - } - case kGetProperty: { - std::string prop; - std::string property_name = fuzzed_data.ConsumeRandomLengthString(); - db->GetProperty(property_name, &prop); - break; - } - case kIterator: { - ROCKSDB_NAMESPACE::Iterator* it = - db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions()); - for (it->SeekToFirst(); it->Valid(); it->Next()) { - } - delete it; - break; - } - case kSnapshot: { - ROCKSDB_NAMESPACE::ReadOptions snapshot_options; - snapshot_options.snapshot = db->GetSnapshot(); - ROCKSDB_NAMESPACE::Iterator* it = db->NewIterator(snapshot_options); - db->ReleaseSnapshot(snapshot_options.snapshot); - delete it; - break; - } - case kOpenClose: { - db->Close(); - delete db; - status = ROCKSDB_NAMESPACE::DB::Open(options, db_path, &db); - if (!status.ok()) { - ROCKSDB_NAMESPACE::DestroyDB(db_path, options); - return 0; - } - - break; - } - case kColumn: { - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf; - ROCKSDB_NAMESPACE::Status s; - s = db->CreateColumnFamily(ROCKSDB_NAMESPACE::ColumnFamilyOptions(), - "new_cf", &cf); - s = db->DestroyColumnFamilyHandle(cf); - db->Close(); - delete db; - - // open DB with two column families - std::vector column_families; - // have to open default column family - column_families.push_back(ROCKSDB_NAMESPACE::ColumnFamilyDescriptor( - ROCKSDB_NAMESPACE::kDefaultColumnFamilyName, - ROCKSDB_NAMESPACE::ColumnFamilyOptions())); - // open the new one, too - column_families.push_back(ROCKSDB_NAMESPACE::ColumnFamilyDescriptor( - "new_cf", ROCKSDB_NAMESPACE::ColumnFamilyOptions())); - std::vector handles; - s = ROCKSDB_NAMESPACE::DB::Open(ROCKSDB_NAMESPACE::DBOptions(), db_path, - column_families, &handles, &db); - - if (s.ok()) { - std::string key1 = fuzzed_data.ConsumeRandomLengthString(); - std::string val1 = fuzzed_data.ConsumeRandomLengthString(); - std::string key2 = fuzzed_data.ConsumeRandomLengthString(); - s = db->Put(ROCKSDB_NAMESPACE::WriteOptions(), handles[1], key1, - val1); - std::string value; - s = db->Get(ROCKSDB_NAMESPACE::ReadOptions(), handles[1], key2, - &value); - s = db->DropColumnFamily(handles[1]); - for (auto handle : handles) { - s = db->DestroyColumnFamilyHandle(handle); - } - } else { - status = ROCKSDB_NAMESPACE::DB::Open(options, db_path, &db); - if (!status.ok()) { - // At this point there is no saving to do. So we exit - ROCKSDB_NAMESPACE::DestroyDB(db_path, ROCKSDB_NAMESPACE::Options()); - return 0; - } - } - break; - } - case kCompactRange: { - std::string slice_start = fuzzed_data.ConsumeRandomLengthString(); - std::string slice_end = fuzzed_data.ConsumeRandomLengthString(); - - ROCKSDB_NAMESPACE::Slice begin(slice_start); - ROCKSDB_NAMESPACE::Slice end(slice_end); - ROCKSDB_NAMESPACE::CompactRangeOptions options; - ROCKSDB_NAMESPACE::Status s = db->CompactRange(options, &begin, &end); - break; - } - case kSeekForPrev: { - std::string key = fuzzed_data.ConsumeRandomLengthString(); - auto iter = db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions()); - iter->SeekForPrev(key); - delete iter; - break; - } - case OP_COUNT: - break; - } - } - - // Cleanup DB - db->Close(); - delete db; - ROCKSDB_NAMESPACE::DestroyDB(db_path, options); - return 0; -} diff --git a/fuzz/db_map_fuzzer.cc b/fuzz/db_map_fuzzer.cc deleted file mode 100644 index ed9df8f84..000000000 --- a/fuzz/db_map_fuzzer.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include -#include - -#include "proto/gen/db_operation.pb.h" -#include "rocksdb/db.h" -#include "rocksdb/file_system.h" -#include "src/libfuzzer/libfuzzer_macro.h" -#include "util.h" - -protobuf_mutator::libfuzzer::PostProcessorRegistration reg = { - [](DBOperations* input, unsigned int /* seed */) { - const ROCKSDB_NAMESPACE::Comparator* comparator = - ROCKSDB_NAMESPACE::BytewiseComparator(); - auto ops = input->mutable_operations(); - // Make sure begin <= end for DELETE_RANGE. - for (DBOperation& op : *ops) { - if (op.type() == OpType::DELETE_RANGE) { - auto begin = op.key(); - auto end = op.value(); - if (comparator->Compare(begin, end) > 0) { - std::swap(begin, end); - op.set_key(begin); - op.set_value(end); - } - } - } - }}; - -// Execute randomly generated operations on both a DB and a std::map, -// then reopen the DB and make sure that iterating the DB produces the -// same key-value pairs as iterating through the std::map. -DEFINE_PROTO_FUZZER(DBOperations& input) { - if (input.operations().empty()) { - return; - } - - const std::string kDbPath = "/tmp/db_map_fuzzer_test"; - auto fs = ROCKSDB_NAMESPACE::FileSystem::Default(); - if (fs->FileExists(kDbPath, ROCKSDB_NAMESPACE::IOOptions(), /*dbg=*/nullptr) - .ok()) { - std::cerr << "db path " << kDbPath << " already exists" << std::endl; - abort(); - } - - std::map kv; - ROCKSDB_NAMESPACE::DB* db = nullptr; - ROCKSDB_NAMESPACE::Options options; - options.create_if_missing = true; - CHECK_OK(ROCKSDB_NAMESPACE::DB::Open(options, kDbPath, &db)); - - for (const DBOperation& op : input.operations()) { - switch (op.type()) { - case OpType::PUT: { - CHECK_OK( - db->Put(ROCKSDB_NAMESPACE::WriteOptions(), op.key(), op.value())); - kv[op.key()] = op.value(); - break; - } - case OpType::MERGE: { - break; - } - case OpType::DELETE: { - CHECK_OK(db->Delete(ROCKSDB_NAMESPACE::WriteOptions(), op.key())); - kv.erase(op.key()); - break; - } - case OpType::DELETE_RANGE: { - // [op.key(), op.value()) corresponds to [begin, end). - CHECK_OK(db->DeleteRange(ROCKSDB_NAMESPACE::WriteOptions(), - db->DefaultColumnFamily(), op.key(), - op.value())); - kv.erase(kv.lower_bound(op.key()), kv.lower_bound(op.value())); - break; - } - default: { - std::cerr << "Unsupported operation" << static_cast(op.type()); - return; - } - } - } - CHECK_OK(db->Close()); - delete db; - db = nullptr; - - CHECK_OK(ROCKSDB_NAMESPACE::DB::Open(options, kDbPath, &db)); - auto kv_it = kv.begin(); - ROCKSDB_NAMESPACE::Iterator* it = - db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions()); - for (it->SeekToFirst(); it->Valid(); it->Next(), kv_it++) { - CHECK_TRUE(kv_it != kv.end()); - CHECK_EQ(it->key().ToString(), kv_it->first); - CHECK_EQ(it->value().ToString(), kv_it->second); - } - CHECK_TRUE(kv_it == kv.end()); - delete it; - - CHECK_OK(db->Close()); - delete db; - CHECK_OK(ROCKSDB_NAMESPACE::DestroyDB(kDbPath, options)); -} diff --git a/fuzz/proto/db_operation.proto b/fuzz/proto/db_operation.proto deleted file mode 100644 index 20a55eaa5..000000000 --- a/fuzz/proto/db_operation.proto +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -// Defines database operations. -// Each operation is a key-value pair and an operation type. - -syntax = "proto2"; - -enum OpType { - PUT = 0; - MERGE = 1; - DELETE = 2; - DELETE_RANGE = 3; -} - -message DBOperation { - required string key = 1; - // value is ignored for DELETE. - // [key, value] is the range for DELETE_RANGE. - optional string value = 2; - required OpType type = 3; -} - -message DBOperations { - repeated DBOperation operations = 1; -} diff --git a/fuzz/sst_file_writer_fuzzer.cc b/fuzz/sst_file_writer_fuzzer.cc deleted file mode 100644 index e93b9a3f5..000000000 --- a/fuzz/sst_file_writer_fuzzer.cc +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include -#include - -#include "proto/gen/db_operation.pb.h" -#include "rocksdb/file_system.h" -#include "rocksdb/sst_file_writer.h" -#include "src/libfuzzer/libfuzzer_macro.h" -#include "table/table_builder.h" -#include "table/table_reader.h" -#include "util.h" - -using ROCKSDB_NAMESPACE::BytewiseComparator; -using ROCKSDB_NAMESPACE::Comparator; -using ROCKSDB_NAMESPACE::EnvOptions; -using ROCKSDB_NAMESPACE::ExternalSstFileInfo; -using ROCKSDB_NAMESPACE::FileOptions; -using ROCKSDB_NAMESPACE::FileSystem; -using ROCKSDB_NAMESPACE::ImmutableCFOptions; -using ROCKSDB_NAMESPACE::ImmutableOptions; -using ROCKSDB_NAMESPACE::InternalIterator; -using ROCKSDB_NAMESPACE::IOOptions; -using ROCKSDB_NAMESPACE::kMaxSequenceNumber; -using ROCKSDB_NAMESPACE::Options; -using ROCKSDB_NAMESPACE::ParsedInternalKey; -using ROCKSDB_NAMESPACE::ParseInternalKey; -using ROCKSDB_NAMESPACE::RandomAccessFileReader; -using ROCKSDB_NAMESPACE::ReadOptions; -using ROCKSDB_NAMESPACE::SstFileWriter; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::TableReader; -using ROCKSDB_NAMESPACE::TableReaderCaller; -using ROCKSDB_NAMESPACE::TableReaderOptions; -using ROCKSDB_NAMESPACE::ValueType; - -// Keys in SST file writer operations must be unique and in ascending order. -// For each DBOperation generated by the fuzzer, this function is called on -// it to deduplicate and sort the keys in the DBOperations. -protobuf_mutator::libfuzzer::PostProcessorRegistration reg = { - [](DBOperations* input, unsigned int /* seed */) { - const Comparator* comparator = BytewiseComparator(); - auto ops = input->mutable_operations(); - - // Make sure begin <= end for DELETE_RANGE. - for (DBOperation& op : *ops) { - if (op.type() == OpType::DELETE_RANGE) { - auto begin = op.key(); - auto end = op.value(); - if (comparator->Compare(begin, end) > 0) { - std::swap(begin, end); - op.set_key(begin); - op.set_value(end); - } - } - } - - std::sort(ops->begin(), ops->end(), - [&comparator](const DBOperation& a, const DBOperation& b) { - return comparator->Compare(a.key(), b.key()) < 0; - }); - - auto last = std::unique( - ops->begin(), ops->end(), - [&comparator](const DBOperation& a, const DBOperation& b) { - return comparator->Compare(a.key(), b.key()) == 0; - }); - ops->erase(last, ops->end()); - }}; - -TableReader* NewTableReader(const std::string& sst_file_path, - const Options& options, - const EnvOptions& env_options, - const ImmutableCFOptions& cf_ioptions) { - // This code block is similar to SstFileReader::Open. - - uint64_t file_size = 0; - std::unique_ptr file_reader; - std::unique_ptr table_reader; - const auto& fs = options.env->GetFileSystem(); - FileOptions fopts(env_options); - Status s = options.env->GetFileSize(sst_file_path, &file_size); - if (s.ok()) { - s = RandomAccessFileReader::Create(fs, sst_file_path, fopts, &file_reader, - nullptr); - } - if (s.ok()) { - ImmutableOptions iopts(options, cf_ioptions); - TableReaderOptions t_opt(iopts, /*prefix_extractor=*/nullptr, env_options, - cf_ioptions.internal_comparator); - t_opt.largest_seqno = kMaxSequenceNumber; - s = options.table_factory->NewTableReader(t_opt, std::move(file_reader), - file_size, &table_reader, - /*prefetch=*/false); - } - if (!s.ok()) { - std::cerr << "Failed to create TableReader for " << sst_file_path << ": " - << s.ToString() << std::endl; - abort(); - } - return table_reader.release(); -} - -ValueType ToValueType(OpType op_type) { - switch (op_type) { - case OpType::PUT: - return ValueType::kTypeValue; - case OpType::MERGE: - return ValueType::kTypeMerge; - case OpType::DELETE: - return ValueType::kTypeDeletion; - case OpType::DELETE_RANGE: - return ValueType::kTypeRangeDeletion; - default: - std::cerr << "Unknown operation type " << static_cast(op_type) - << std::endl; - abort(); - } -} - -// Fuzzes DB operations as input, let SstFileWriter generate a SST file -// according to the operations, then let TableReader read and check all the -// key-value pairs from the generated SST file. -DEFINE_PROTO_FUZZER(DBOperations& input) { - if (input.operations().empty()) { - return; - } - - std::string sstfile; - { - auto fs = FileSystem::Default(); - std::string dir; - IOOptions opt; - CHECK_OK(fs->GetTestDirectory(opt, &dir, nullptr)); - sstfile = dir + "/SstFileWriterFuzzer.sst"; - } - - Options options; - EnvOptions env_options(options); - ImmutableCFOptions cf_ioptions(options); - - // Generate sst file. - SstFileWriter writer(env_options, options); - CHECK_OK(writer.Open(sstfile)); - for (const DBOperation& op : input.operations()) { - switch (op.type()) { - case OpType::PUT: { - CHECK_OK(writer.Put(op.key(), op.value())); - break; - } - case OpType::MERGE: { - CHECK_OK(writer.Merge(op.key(), op.value())); - break; - } - case OpType::DELETE: { - CHECK_OK(writer.Delete(op.key())); - break; - } - case OpType::DELETE_RANGE: { - CHECK_OK(writer.DeleteRange(op.key(), op.value())); - break; - } - default: { - std::cerr << "Unsupported operation" << static_cast(op.type()) - << std::endl; - abort(); - } - } - } - ExternalSstFileInfo info; - CHECK_OK(writer.Finish(&info)); - - // Iterate and verify key-value pairs. - std::unique_ptr table_reader( - ::NewTableReader(sstfile, options, env_options, cf_ioptions)); - ReadOptions roptions; - CHECK_OK(table_reader->VerifyChecksum(roptions, - TableReaderCaller::kUncategorized)); - std::unique_ptr it( - table_reader->NewIterator(roptions, /*prefix_extractor=*/nullptr, - /*arena=*/nullptr, /*skip_filters=*/true, - TableReaderCaller::kUncategorized)); - it->SeekToFirst(); - for (const DBOperation& op : input.operations()) { - if (op.type() == OpType::DELETE_RANGE) { - // InternalIterator cannot iterate over DELETE_RANGE entries. - continue; - } - CHECK_TRUE(it->Valid()); - ParsedInternalKey ikey; - CHECK_OK(ParseInternalKey(it->key(), &ikey, /*log_err_key=*/true)); - CHECK_EQ(ikey.user_key.ToString(), op.key()); - CHECK_EQ(ikey.sequence, 0); - CHECK_EQ(ikey.type, ToValueType(op.type())); - if (op.type() != OpType::DELETE) { - CHECK_EQ(op.value(), it->value().ToString()); - } - it->Next(); - } - CHECK_TRUE(!it->Valid()); - - // Delete sst file. - remove(sstfile.c_str()); -} diff --git a/fuzz/util.h b/fuzz/util.h deleted file mode 100644 index 97011823a..000000000 --- a/fuzz/util.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#pragma once - -#define CHECK_OK(expression) \ - do { \ - auto status = (expression); \ - if (!status.ok()) { \ - std::cerr << status.ToString() << std::endl; \ - abort(); \ - } \ - } while (0) - -#define CHECK_EQ(a, b) \ - if (a != b) { \ - std::cerr << "(" << #a << "=" << a << ") != (" << #b << "=" << b << ")" \ - << std::endl; \ - abort(); \ - } - -#define CHECK_TRUE(cond) \ - if (!(cond)) { \ - std::cerr << "\"" << #cond << "\" is false" << std::endl; \ - abort(); \ - } diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt deleted file mode 100644 index 5d62630fd..000000000 --- a/java/CMakeLists.txt +++ /dev/null @@ -1,549 +0,0 @@ -cmake_minimum_required(VERSION 3.4) - -if(${CMAKE_VERSION} VERSION_LESS "3.11.4") - message("Please consider switching to CMake 3.11.4 or newer") -endif() - -set(CMAKE_JAVA_COMPILE_FLAGS -source 7) - -set(JNI_NATIVE_SOURCES - rocksjni/backup_engine_options.cc - rocksjni/backupenginejni.cc - rocksjni/cassandra_compactionfilterjni.cc - rocksjni/cassandra_value_operator.cc - rocksjni/checkpoint.cc - rocksjni/clock_cache.cc - rocksjni/cache.cc - rocksjni/columnfamilyhandle.cc - rocksjni/compaction_filter.cc - rocksjni/compaction_filter_factory.cc - rocksjni/compaction_filter_factory_jnicallback.cc - rocksjni/compaction_job_info.cc - rocksjni/compaction_job_stats.cc - rocksjni/compaction_options.cc - rocksjni/compaction_options_fifo.cc - rocksjni/compaction_options_universal.cc - rocksjni/compact_range_options.cc - rocksjni/comparator.cc - rocksjni/comparatorjnicallback.cc - rocksjni/compression_options.cc - rocksjni/concurrent_task_limiter.cc - rocksjni/config_options.cc - rocksjni/env.cc - rocksjni/env_options.cc - rocksjni/event_listener.cc - rocksjni/event_listener_jnicallback.cc - rocksjni/filter.cc - rocksjni/ingest_external_file_options.cc - rocksjni/iterator.cc - rocksjni/jnicallback.cc - rocksjni/loggerjnicallback.cc - rocksjni/lru_cache.cc - rocksjni/memory_util.cc - rocksjni/memtablejni.cc - rocksjni/merge_operator.cc - rocksjni/native_comparator_wrapper_test.cc - rocksjni/optimistic_transaction_db.cc - rocksjni/optimistic_transaction_options.cc - rocksjni/options.cc - rocksjni/options_util.cc - rocksjni/persistent_cache.cc - rocksjni/ratelimiterjni.cc - rocksjni/remove_emptyvalue_compactionfilterjni.cc - rocksjni/restorejni.cc - rocksjni/rocks_callback_object.cc - rocksjni/rocksdb_exception_test.cc - rocksjni/rocksjni.cc - rocksjni/slice.cc - rocksjni/snapshot.cc - rocksjni/sst_file_manager.cc - rocksjni/sst_file_writerjni.cc - rocksjni/sst_file_readerjni.cc - rocksjni/sst_file_reader_iterator.cc - rocksjni/sst_partitioner.cc - rocksjni/statistics.cc - rocksjni/statisticsjni.cc - rocksjni/table.cc - rocksjni/table_filter.cc - rocksjni/table_filter_jnicallback.cc - rocksjni/testable_event_listener.cc - rocksjni/thread_status.cc - rocksjni/trace_writer.cc - rocksjni/trace_writer_jnicallback.cc - rocksjni/transaction.cc - rocksjni/transaction_db.cc - rocksjni/transaction_db_options.cc - rocksjni/transaction_log.cc - rocksjni/transaction_notifier.cc - rocksjni/transaction_notifier_jnicallback.cc - rocksjni/transaction_options.cc - rocksjni/ttl.cc - rocksjni/wal_filter.cc - rocksjni/wal_filter_jnicallback.cc - rocksjni/write_batch.cc - rocksjni/writebatchhandlerjnicallback.cc - rocksjni/write_batch_test.cc - rocksjni/write_batch_with_index.cc - rocksjni/write_buffer_manager.cc -) - -set(JAVA_MAIN_CLASSES - src/main/java/org/rocksdb/AbstractCompactionFilter.java - src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java - src/main/java/org/rocksdb/AbstractComparator.java - src/main/java/org/rocksdb/AbstractEventListener.java - src/main/java/org/rocksdb/AbstractImmutableNativeReference.java - src/main/java/org/rocksdb/AbstractMutableOptions.java - src/main/java/org/rocksdb/AbstractNativeReference.java - src/main/java/org/rocksdb/AbstractRocksIterator.java - src/main/java/org/rocksdb/AbstractSlice.java - src/main/java/org/rocksdb/AbstractTableFilter.java - src/main/java/org/rocksdb/AbstractTraceWriter.java - src/main/java/org/rocksdb/AbstractTransactionNotifier.java - src/main/java/org/rocksdb/AbstractWalFilter.java - src/main/java/org/rocksdb/AbstractWriteBatch.java - src/main/java/org/rocksdb/AccessHint.java - src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java - src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java - src/main/java/org/rocksdb/BackgroundErrorReason.java - src/main/java/org/rocksdb/BackupEngineOptions.java - src/main/java/org/rocksdb/BackupEngine.java - src/main/java/org/rocksdb/BackupInfo.java - src/main/java/org/rocksdb/BlockBasedTableConfig.java - src/main/java/org/rocksdb/BloomFilter.java - src/main/java/org/rocksdb/BuiltinComparator.java - src/main/java/org/rocksdb/ByteBufferGetStatus.java - src/main/java/org/rocksdb/Cache.java - src/main/java/org/rocksdb/CassandraCompactionFilter.java - src/main/java/org/rocksdb/CassandraValueMergeOperator.java - src/main/java/org/rocksdb/Checkpoint.java - src/main/java/org/rocksdb/ChecksumType.java - src/main/java/org/rocksdb/ClockCache.java - src/main/java/org/rocksdb/ColumnFamilyDescriptor.java - src/main/java/org/rocksdb/ColumnFamilyHandle.java - src/main/java/org/rocksdb/ColumnFamilyMetaData.java - src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java - src/main/java/org/rocksdb/ColumnFamilyOptions.java - src/main/java/org/rocksdb/CompactionJobInfo.java - src/main/java/org/rocksdb/CompactionJobStats.java - src/main/java/org/rocksdb/CompactionOptions.java - src/main/java/org/rocksdb/CompactionOptionsFIFO.java - src/main/java/org/rocksdb/CompactionOptionsUniversal.java - src/main/java/org/rocksdb/CompactionPriority.java - src/main/java/org/rocksdb/CompactionReason.java - src/main/java/org/rocksdb/CompactRangeOptions.java - src/main/java/org/rocksdb/CompactionStopStyle.java - src/main/java/org/rocksdb/CompactionStyle.java - src/main/java/org/rocksdb/ComparatorOptions.java - src/main/java/org/rocksdb/ComparatorType.java - src/main/java/org/rocksdb/CompressionOptions.java - src/main/java/org/rocksdb/CompressionType.java - src/main/java/org/rocksdb/ConfigOptions.java - src/main/java/org/rocksdb/DataBlockIndexType.java - src/main/java/org/rocksdb/DBOptionsInterface.java - src/main/java/org/rocksdb/DBOptions.java - src/main/java/org/rocksdb/DbPath.java - src/main/java/org/rocksdb/DirectSlice.java - src/main/java/org/rocksdb/EncodingType.java - src/main/java/org/rocksdb/Env.java - src/main/java/org/rocksdb/EnvOptions.java - src/main/java/org/rocksdb/EventListener.java - src/main/java/org/rocksdb/Experimental.java - src/main/java/org/rocksdb/ExternalFileIngestionInfo.java - src/main/java/org/rocksdb/Filter.java - src/main/java/org/rocksdb/FileOperationInfo.java - src/main/java/org/rocksdb/FlushJobInfo.java - src/main/java/org/rocksdb/FlushReason.java - src/main/java/org/rocksdb/FlushOptions.java - src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java - src/main/java/org/rocksdb/HashSkipListMemTableConfig.java - src/main/java/org/rocksdb/HistogramData.java - src/main/java/org/rocksdb/HistogramType.java - src/main/java/org/rocksdb/Holder.java - src/main/java/org/rocksdb/IndexShorteningMode.java - src/main/java/org/rocksdb/IndexType.java - src/main/java/org/rocksdb/InfoLogLevel.java - src/main/java/org/rocksdb/IngestExternalFileOptions.java - src/main/java/org/rocksdb/LevelMetaData.java - src/main/java/org/rocksdb/ConcurrentTaskLimiter.java - src/main/java/org/rocksdb/ConcurrentTaskLimiterImpl.java - src/main/java/org/rocksdb/KeyMayExist.java - src/main/java/org/rocksdb/LiveFileMetaData.java - src/main/java/org/rocksdb/LogFile.java - src/main/java/org/rocksdb/Logger.java - src/main/java/org/rocksdb/LRUCache.java - src/main/java/org/rocksdb/MemoryUsageType.java - src/main/java/org/rocksdb/MemoryUtil.java - src/main/java/org/rocksdb/MemTableConfig.java - src/main/java/org/rocksdb/MemTableInfo.java - src/main/java/org/rocksdb/MergeOperator.java - src/main/java/org/rocksdb/MutableColumnFamilyOptions.java - src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java - src/main/java/org/rocksdb/MutableDBOptions.java - src/main/java/org/rocksdb/MutableDBOptionsInterface.java - src/main/java/org/rocksdb/MutableOptionKey.java - src/main/java/org/rocksdb/MutableOptionValue.java - src/main/java/org/rocksdb/NativeComparatorWrapper.java - src/main/java/org/rocksdb/NativeLibraryLoader.java - src/main/java/org/rocksdb/OperationStage.java - src/main/java/org/rocksdb/OperationType.java - src/main/java/org/rocksdb/OptimisticTransactionDB.java - src/main/java/org/rocksdb/OptimisticTransactionOptions.java - src/main/java/org/rocksdb/Options.java - src/main/java/org/rocksdb/OptionString.java - src/main/java/org/rocksdb/OptionsUtil.java - src/main/java/org/rocksdb/PersistentCache.java - src/main/java/org/rocksdb/PlainTableConfig.java - src/main/java/org/rocksdb/PrepopulateBlobCache.java - src/main/java/org/rocksdb/Priority.java - src/main/java/org/rocksdb/Range.java - src/main/java/org/rocksdb/RateLimiter.java - src/main/java/org/rocksdb/RateLimiterMode.java - src/main/java/org/rocksdb/ReadOptions.java - src/main/java/org/rocksdb/ReadTier.java - src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java - src/main/java/org/rocksdb/RestoreOptions.java - src/main/java/org/rocksdb/ReusedSynchronisationType.java - src/main/java/org/rocksdb/RocksCallbackObject.java - src/main/java/org/rocksdb/RocksDBException.java - src/main/java/org/rocksdb/RocksDB.java - src/main/java/org/rocksdb/RocksEnv.java - src/main/java/org/rocksdb/RocksIteratorInterface.java - src/main/java/org/rocksdb/RocksIterator.java - src/main/java/org/rocksdb/RocksMemEnv.java - src/main/java/org/rocksdb/RocksMutableObject.java - src/main/java/org/rocksdb/RocksObject.java - src/main/java/org/rocksdb/SanityLevel.java - src/main/java/org/rocksdb/SizeApproximationFlag.java - src/main/java/org/rocksdb/SkipListMemTableConfig.java - src/main/java/org/rocksdb/Slice.java - src/main/java/org/rocksdb/Snapshot.java - src/main/java/org/rocksdb/SstFileManager.java - src/main/java/org/rocksdb/SstFileMetaData.java - src/main/java/org/rocksdb/SstFileReader.java - src/main/java/org/rocksdb/SstFileReaderIterator.java - src/main/java/org/rocksdb/SstFileWriter.java - src/main/java/org/rocksdb/SstPartitionerFactory.java - src/main/java/org/rocksdb/SstPartitionerFixedPrefixFactory.java - src/main/java/org/rocksdb/StateType.java - src/main/java/org/rocksdb/StatisticsCollectorCallback.java - src/main/java/org/rocksdb/StatisticsCollector.java - src/main/java/org/rocksdb/Statistics.java - src/main/java/org/rocksdb/StatsCollectorInput.java - src/main/java/org/rocksdb/StatsLevel.java - src/main/java/org/rocksdb/Status.java - src/main/java/org/rocksdb/StringAppendOperator.java - src/main/java/org/rocksdb/TableFileCreationBriefInfo.java - src/main/java/org/rocksdb/TableFileCreationInfo.java - src/main/java/org/rocksdb/TableFileCreationReason.java - src/main/java/org/rocksdb/TableFileDeletionInfo.java - src/main/java/org/rocksdb/TableFilter.java - src/main/java/org/rocksdb/TableProperties.java - src/main/java/org/rocksdb/TableFormatConfig.java - src/main/java/org/rocksdb/ThreadType.java - src/main/java/org/rocksdb/ThreadStatus.java - src/main/java/org/rocksdb/TickerType.java - src/main/java/org/rocksdb/TimedEnv.java - src/main/java/org/rocksdb/TraceOptions.java - src/main/java/org/rocksdb/TraceWriter.java - src/main/java/org/rocksdb/TransactionalDB.java - src/main/java/org/rocksdb/TransactionalOptions.java - src/main/java/org/rocksdb/TransactionDB.java - src/main/java/org/rocksdb/TransactionDBOptions.java - src/main/java/org/rocksdb/Transaction.java - src/main/java/org/rocksdb/TransactionLogIterator.java - src/main/java/org/rocksdb/TransactionOptions.java - src/main/java/org/rocksdb/TtlDB.java - src/main/java/org/rocksdb/TxnDBWritePolicy.java - src/main/java/org/rocksdb/VectorMemTableConfig.java - src/main/java/org/rocksdb/WalFileType.java - src/main/java/org/rocksdb/WalFilter.java - src/main/java/org/rocksdb/WalProcessingOption.java - src/main/java/org/rocksdb/WALRecoveryMode.java - src/main/java/org/rocksdb/WBWIRocksIterator.java - src/main/java/org/rocksdb/WriteBatch.java - src/main/java/org/rocksdb/WriteBatchInterface.java - src/main/java/org/rocksdb/WriteBatchWithIndex.java - src/main/java/org/rocksdb/WriteOptions.java - src/main/java/org/rocksdb/WriteBufferManager.java - src/main/java/org/rocksdb/WriteStallCondition.java - src/main/java/org/rocksdb/WriteStallInfo.java - src/main/java/org/rocksdb/util/ByteUtil.java - src/main/java/org/rocksdb/util/BytewiseComparator.java - src/main/java/org/rocksdb/util/Environment.java - src/main/java/org/rocksdb/util/IntComparator.java - src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java - src/main/java/org/rocksdb/util/SizeUnit.java - src/main/java/org/rocksdb/UInt64AddOperator.java -) - -set(JAVA_TEST_CLASSES - src/test/java/org/rocksdb/BackupEngineTest.java - src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java - src/test/java/org/rocksdb/NativeComparatorWrapperTest.java - src/test/java/org/rocksdb/PlatformRandomHelper.java - src/test/java/org/rocksdb/RocksDBExceptionTest.java - src/test/java/org/rocksdb/RocksNativeLibraryResource.java - src/test/java/org/rocksdb/SnapshotTest.java - src/test/java/org/rocksdb/WriteBatchTest.java - src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java - src/test/java/org/rocksdb/util/WriteBatchGetter.java - src/test/java/org/rocksdb/test/TestableEventListener.java -) - -include(FindJava) -include(UseJava) -find_package(JNI) - -include_directories(${JNI_INCLUDE_DIRS}) -include_directories(${PROJECT_SOURCE_DIR}/java) - -set(JAVA_TEST_LIBDIR ${PROJECT_SOURCE_DIR}/java/test-libs) -set(JAVA_TMP_JAR ${JAVA_TEST_LIBDIR}/tmp.jar) -set(JAVA_JUNIT_JAR ${JAVA_TEST_LIBDIR}/junit-4.12.jar) -set(JAVA_HAMCR_JAR ${JAVA_TEST_LIBDIR}/hamcrest-core-1.3.jar) -set(JAVA_MOCKITO_JAR ${JAVA_TEST_LIBDIR}/mockito-all-1.10.19.jar) -set(JAVA_CGLIB_JAR ${JAVA_TEST_LIBDIR}/cglib-2.2.2.jar) -set(JAVA_ASSERTJ_JAR ${JAVA_TEST_LIBDIR}/assertj-core-1.7.1.jar) -set(JAVA_TESTCLASSPATH ${JAVA_JUNIT_JAR} ${JAVA_HAMCR_JAR} ${JAVA_MOCKITO_JAR} ${JAVA_CGLIB_JAR} ${JAVA_ASSERTJ_JAR}) - -set(JNI_OUTPUT_DIR ${PROJECT_SOURCE_DIR}/java/include) -file(MAKE_DIRECTORY ${JNI_OUTPUT_DIR}) - -if(${Java_VERSION_MINOR} VERSION_LESS_EQUAL "7" AND ${Java_VERSION_MAJOR} STREQUAL "1") - message(FATAL_ERROR "Detected Java 7 or older (${Java_VERSION_STRING}), minimum required version in now Java 8") -endif() - -if(${Java_VERSION_MAJOR} VERSION_GREATER_EQUAL "10" AND ${CMAKE_VERSION} VERSION_LESS "3.11.4") - # Java 10 and newer don't have javah, but the alternative GENERATE_NATIVE_HEADERS requires CMake 3.11.4 or newer - message(FATAL_ERROR "Detected Java 10 or newer (${Java_VERSION_STRING}), to build with CMake please upgrade CMake to 3.11.4 or newer") - -elseif(${CMAKE_VERSION} VERSION_LESS "3.11.4") - # Old CMake - message("Using an old CMAKE (${CMAKE_VERSION}) - JNI headers generated in separate step") - add_jar( - rocksdbjni_classes - SOURCES - ${JAVA_MAIN_CLASSES} - ${JAVA_TEST_CLASSES} - INCLUDE_JARS ${JAVA_TESTCLASSPATH} - ) - -else () - # Java 1.8 or newer prepare the JAR... - message("Preparing Jar for JDK ${Java_VERSION_STRING}") - add_jar( - rocksdbjni_classes - SOURCES - ${JAVA_MAIN_CLASSES} - ${JAVA_TEST_CLASSES} - INCLUDE_JARS ${JAVA_TESTCLASSPATH} - GENERATE_NATIVE_HEADERS rocksdbjni_headers DESTINATION ${JNI_OUTPUT_DIR} - ) - -endif() - -if(NOT EXISTS ${PROJECT_SOURCE_DIR}/java/classes) - file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/java/classes) -endif() - -if(NOT EXISTS ${JAVA_TEST_LIBDIR}) - file(MAKE_DIRECTORY mkdir ${JAVA_TEST_LIBDIR}) -endif() - -if (DEFINED CUSTOM_DEPS_URL) - set(DEPS_URL ${CUSTOM_DEPS_URL}/) -else () - # Using a Facebook AWS account for S3 storage. (maven.org has a history - # of failing in Travis builds.) - set(DEPS_URL "https://rocksdb-deps.s3-us-west-2.amazonaws.com/jars") -endif() - -if(NOT EXISTS ${JAVA_JUNIT_JAR}) - message("Downloading ${JAVA_JUNIT_JAR}") - file(DOWNLOAD ${DEPS_URL}/junit-4.12.jar ${JAVA_TMP_JAR} STATUS downloadStatus) - list(GET downloadStatus 0 error_code) - list(GET downloadStatus 1 error_message) - if(NOT error_code EQUAL 0) - message(FATAL_ERROR "Failed downloading ${JAVA_JUNIT_JAR}: ${error_message}") - endif() - file(RENAME ${JAVA_TMP_JAR} ${JAVA_JUNIT_JAR}) -endif() -if(NOT EXISTS ${JAVA_HAMCR_JAR}) - message("Downloading ${JAVA_HAMCR_JAR}") - file(DOWNLOAD ${DEPS_URL}/hamcrest-core-1.3.jar ${JAVA_TMP_JAR} STATUS downloadStatus) - list(GET downloadStatus 0 error_code) - list(GET downloadStatus 1 error_message) - if(NOT error_code EQUAL 0) - message(FATAL_ERROR "Failed downloading ${JAVA_HAMCR_JAR}: ${error_message}") - endif() - file(RENAME ${JAVA_TMP_JAR} ${JAVA_HAMCR_JAR}) -endif() -if(NOT EXISTS ${JAVA_MOCKITO_JAR}) - message("Downloading ${JAVA_MOCKITO_JAR}") - file(DOWNLOAD ${DEPS_URL}/mockito-all-1.10.19.jar ${JAVA_TMP_JAR} STATUS downloadStatus) - list(GET downloadStatus 0 error_code) - list(GET downloadStatus 1 error_message) - if(NOT error_code EQUAL 0) - message(FATAL_ERROR "Failed downloading ${JAVA_MOCKITO_JAR}: ${error_message}") - endif() - file(RENAME ${JAVA_TMP_JAR} ${JAVA_MOCKITO_JAR}) -endif() -if(NOT EXISTS ${JAVA_CGLIB_JAR}) - message("Downloading ${JAVA_CGLIB_JAR}") - file(DOWNLOAD ${DEPS_URL}/cglib-2.2.2.jar ${JAVA_TMP_JAR} STATUS downloadStatus) - list(GET downloadStatus 0 error_code) - list(GET downloadStatus 1 error_message) - if(NOT error_code EQUAL 0) - message(FATAL_ERROR "Failed downloading ${JAVA_CGLIB_JAR}: ${error_message}") - endif() - file(RENAME ${JAVA_TMP_JAR} ${JAVA_CGLIB_JAR}) -endif() -if(NOT EXISTS ${JAVA_ASSERTJ_JAR}) - message("Downloading ${JAVA_ASSERTJ_JAR}") - file(DOWNLOAD ${DEPS_URL}/assertj-core-1.7.1.jar ${JAVA_TMP_JAR} STATUS downloadStatus) - list(GET downloadStatus 0 error_code) - list(GET downloadStatus 1 error_message) - if(NOT error_code EQUAL 0) - message(FATAL_ERROR "Failed downloading ${JAVA_ASSERTJ_JAR}: ${error_message}") - endif() - file(RENAME ${JAVA_TMP_JAR} ${JAVA_ASSERTJ_JAR}) -endif() - -if(${CMAKE_VERSION} VERSION_LESS "3.11.4") - # Old CMake ONLY generate JNI headers, otherwise JNI is handled in add_jar step above - message("Preparing JNI headers for old CMake (${CMAKE_VERSION})") - set(NATIVE_JAVA_CLASSES - org.rocksdb.AbstractCompactionFilter - org.rocksdb.AbstractCompactionFilterFactory - org.rocksdb.AbstractComparator - org.rocksdb.AbstractEventListener - org.rocksdb.AbstractImmutableNativeReference - org.rocksdb.AbstractNativeReference - org.rocksdb.AbstractRocksIterator - org.rocksdb.AbstractSlice - org.rocksdb.AbstractTableFilter - org.rocksdb.AbstractTraceWriter - org.rocksdb.AbstractTransactionNotifier - org.rocksdb.AbstractWalFilter - org.rocksdb.BackupEngineOptions - org.rocksdb.BackupEngine - org.rocksdb.BlockBasedTableConfig - org.rocksdb.BloomFilter - org.rocksdb.CassandraCompactionFilter - org.rocksdb.CassandraValueMergeOperator - org.rocksdb.Checkpoint - org.rocksdb.ClockCache - org.rocksdb.Cache - org.rocksdb.ColumnFamilyHandle - org.rocksdb.ColumnFamilyOptions - org.rocksdb.CompactionJobInfo - org.rocksdb.CompactionJobStats - org.rocksdb.CompactionOptions - org.rocksdb.CompactionOptionsFIFO - org.rocksdb.CompactionOptionsUniversal - org.rocksdb.CompactRangeOptions - org.rocksdb.ComparatorOptions - org.rocksdb.CompressionOptions - org.rocksdb.ConcurrentTaskLimiterImpl - org.rocksdb.ConfigOptions - org.rocksdb.DBOptions - org.rocksdb.DirectSlice - org.rocksdb.Env - org.rocksdb.EnvOptions - org.rocksdb.Filter - org.rocksdb.FlushOptions - org.rocksdb.HashLinkedListMemTableConfig - org.rocksdb.HashSkipListMemTableConfig - org.rocksdb.IngestExternalFileOptions - org.rocksdb.Logger - org.rocksdb.LRUCache - org.rocksdb.MemoryUtil - org.rocksdb.MemTableConfig - org.rocksdb.NativeComparatorWrapper - org.rocksdb.NativeLibraryLoader - org.rocksdb.OptimisticTransactionDB - org.rocksdb.OptimisticTransactionOptions - org.rocksdb.Options - org.rocksdb.OptionsUtil - org.rocksdb.PersistentCache - org.rocksdb.PlainTableConfig - org.rocksdb.RateLimiter - org.rocksdb.ReadOptions - org.rocksdb.RemoveEmptyValueCompactionFilter - org.rocksdb.RestoreOptions - org.rocksdb.RocksCallbackObject - org.rocksdb.RocksDB - org.rocksdb.RocksEnv - org.rocksdb.RocksIterator - org.rocksdb.RocksIteratorInterface - org.rocksdb.RocksMemEnv - org.rocksdb.RocksMutableObject - org.rocksdb.RocksObject - org.rocksdb.SkipListMemTableConfig - org.rocksdb.Slice - org.rocksdb.Snapshot - org.rocksdb.SstFileManager - org.rocksdb.SstFileWriter - org.rocksdb.SstFileReader - org.rocksdb.SstFileReaderIterator - org.rocksdb.SstPartitionerFactory - org.rocksdb.SstPartitionerFixedPrefixFactory - org.rocksdb.Statistics - org.rocksdb.StringAppendOperator - org.rocksdb.TableFormatConfig - org.rocksdb.ThreadStatus - org.rocksdb.TimedEnv - org.rocksdb.Transaction - org.rocksdb.TransactionDB - org.rocksdb.TransactionDBOptions - org.rocksdb.TransactionLogIterator - org.rocksdb.TransactionOptions - org.rocksdb.TtlDB - org.rocksdb.UInt64AddOperator - org.rocksdb.VectorMemTableConfig - org.rocksdb.WBWIRocksIterator - org.rocksdb.WriteBatch - org.rocksdb.WriteBatch.Handler - org.rocksdb.WriteBatchInterface - org.rocksdb.WriteBatchWithIndex - org.rocksdb.WriteOptions - org.rocksdb.NativeComparatorWrapperTest - org.rocksdb.RocksDBExceptionTest - org.rocksdb.SnapshotTest - org.rocksdb.WriteBatchTest - org.rocksdb.WriteBatchTestInternalHelper - org.rocksdb.WriteBufferManager - org.rocksdb.test.TestableEventListener - ) - - create_javah( - TARGET rocksdbjni_headers - CLASSES ${NATIVE_JAVA_CLASSES} - CLASSPATH rocksdbjni_classes ${JAVA_TESTCLASSPATH} - OUTPUT_DIR ${JNI_OUTPUT_DIR} - ) -endif() - -if(NOT MSVC) - set_property(TARGET ${ROCKSDB_STATIC_LIB} PROPERTY POSITION_INDEPENDENT_CODE ON) -endif() - -set(ROCKSDBJNI_STATIC_LIB rocksdbjni${ARTIFACT_SUFFIX}) -add_library(${ROCKSDBJNI_STATIC_LIB} ${JNI_NATIVE_SOURCES}) -add_dependencies(${ROCKSDBJNI_STATIC_LIB} rocksdbjni_headers) -target_link_libraries(${ROCKSDBJNI_STATIC_LIB} ${ROCKSDB_STATIC_LIB} ${ROCKSDB_LIB}) - -if(NOT MINGW) - set(ROCKSDBJNI_SHARED_LIB rocksdbjni-shared${ARTIFACT_SUFFIX}) - add_library(${ROCKSDBJNI_SHARED_LIB} SHARED ${JNI_NATIVE_SOURCES}) - add_dependencies(${ROCKSDBJNI_SHARED_LIB} rocksdbjni_headers) - target_link_libraries(${ROCKSDBJNI_SHARED_LIB} ${ROCKSDB_STATIC_LIB} ${ROCKSDB_LIB}) - - set_target_properties( - ${ROCKSDBJNI_SHARED_LIB} - PROPERTIES - COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_CFG_INTDIR} - COMPILE_PDB_NAME ${ROCKSDBJNI_STATIC_LIB}.pdb - ) -endif() diff --git a/java/GetBenchmarks.md b/java/GetBenchmarks.md deleted file mode 100644 index b66a897e2..000000000 --- a/java/GetBenchmarks.md +++ /dev/null @@ -1,161 +0,0 @@ -# RocksDB Get Performance Benchmarks - -Results associated with [Improve Java API `get()` performance by reducing copies](https://github.com/facebook/rocksdb/pull/10970) - -## Build/Run - -Mac -``` -make clean jclean -DEBUG_LEVEL=0 make -j12 rocksdbjava -(cd java/target; cp rocksdbjni-7.9.0-osx.jar rocksdbjni-7.9.0-SNAPSHOT-osx.jar) -mvn install:install-file -Dfile=./java/target/rocksdbjni-7.9.0-SNAPSHOT-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=7.9.0-SNAPSHOT -Dpackaging=jar -``` - -Linux -``` -make clean jclean -DEBUG_LEVEL=0 make -j12 rocksdbjava -(cd java/target; cp rocksdbjni-7.9.0-linux64.jar rocksdbjni-7.9.0-SNAPSHOT-linux64.jar) -mvn install:install-file -Dfile=./java/target/rocksdbjni-7.9.0-SNAPSHOT-linux64.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=7.9.0-SNAPSHOT -Dpackaging=jar -``` - -Build jmh test package, on either platform -``` -pushd java/jmh -mvn clean package -``` - -A quick test run, just as a sanity check, using a small number of keys, would be -``` -java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000 -p keySize=128 -p valueSize=32768 -p columnFamilyTestType="no_column_family" GetBenchmarks -``` -The long performance run (as big as we can make it on our Ubuntu box without filling the disk) -``` -java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,16384 -p columnFamilyTestType="1_column_family","20_column_families" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet -``` - -## Results (small runs, Mac) - -These are run on a 10-core M1 with 64GB of memory and 2TB of SSD. -They probably reflect the absolute best case for this optimization, hitting in-memory buffers and completely eliminating a buffer copy. - -### Before -Benchmark (columnFamilyTestType) (keyCount) (keySize) (multiGetSize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get no_column_family 1000 128 N/A 32768 thrpt 25 43496.578 ± 5743.090 ops/s -GetBenchmarks.preallocatedByteBufferGet no_column_family 1000 128 N/A 32768 thrpt 25 70765.578 ± 697.548 ops/s -GetBenchmarks.preallocatedGet no_column_family 1000 128 N/A 32768 thrpt 25 69883.554 ± 944.184 ops/s - -### After fixing byte[] (.get and .preallocatedGet) - -Benchmark (columnFamilyTestType) (keyCount) (keySize) (multiGetSize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get no_column_family 1000 128 N/A 32768 thrpt 25 149207.681 ± 2261.671 ops/s -GetBenchmarks.preallocatedByteBufferGet no_column_family 1000 128 N/A 32768 thrpt 25 68920.489 ± 1574.664 ops/s -GetBenchmarks.preallocatedGet no_column_family 1000 128 N/A 32768 thrpt 25 177399.022 ± 2107.375 ops/s - -### After fixing ByteBuffer (.preallocatedByteBufferGet) - -Benchmark (columnFamilyTestType) (keyCount) (keySize) (multiGetSize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get no_column_family 1000 128 N/A 32768 thrpt 25 150389.259 ± 1371.473 ops/s -GetBenchmarks.preallocatedByteBufferGet no_column_family 1000 128 N/A 32768 thrpt 25 179919.468 ± 1670.714 ops/s -GetBenchmarks.preallocatedGet no_column_family 1000 128 N/A 32768 thrpt 25 178261.938 ± 2630.571 ops/s -## Results (Ubuntu, big runs) -These take 3-4 hours -``` -java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000,50000 -p keySize=128 -p valueSize=1024,16384 -p columnFamilyTestType="1_column_family","20_column_families" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet -``` -It's clear that all `get()` variants have noticeably improved performance, though not the spectacular gains of the M1. -### With fixes for all of the `get()` instances - -Benchmark (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get 1_column_family 1000 128 1024 thrpt 25 935648.793 ± 22879.910 ops/s -GetBenchmarks.get 1_column_family 1000 128 16384 thrpt 25 204366.301 ± 1326.570 ops/s -GetBenchmarks.get 1_column_family 50000 128 1024 thrpt 25 693451.990 ± 19822.720 ops/s -GetBenchmarks.get 1_column_family 50000 128 16384 thrpt 25 50473.768 ± 497.335 ops/s -GetBenchmarks.get 20_column_families 1000 128 1024 thrpt 25 550118.874 ± 14289.009 ops/s -GetBenchmarks.get 20_column_families 1000 128 16384 thrpt 25 120545.549 ± 648.280 ops/s -GetBenchmarks.get 20_column_families 50000 128 1024 thrpt 25 235671.353 ± 2231.195 ops/s -GetBenchmarks.get 20_column_families 50000 128 16384 thrpt 25 12463.887 ± 1950.746 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 1000 128 1024 thrpt 25 1196026.040 ± 35435.729 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 1000 128 16384 thrpt 25 403252.655 ± 3287.054 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 50000 128 1024 thrpt 25 829965.448 ± 16945.452 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 50000 128 16384 thrpt 25 63798.042 ± 1292.858 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 1000 128 1024 thrpt 25 724557.253 ± 12710.828 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 1000 128 16384 thrpt 25 176846.615 ± 1121.644 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 50000 128 1024 thrpt 25 263553.764 ± 1304.243 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 50000 128 16384 thrpt 25 14721.693 ± 2574.240 ops/s -GetBenchmarks.preallocatedGet 1_column_family 1000 128 1024 thrpt 25 1093947.765 ± 42846.276 ops/s -GetBenchmarks.preallocatedGet 1_column_family 1000 128 16384 thrpt 25 391629.913 ± 4039.965 ops/s -GetBenchmarks.preallocatedGet 1_column_family 50000 128 1024 thrpt 25 769332.958 ± 24180.749 ops/s -GetBenchmarks.preallocatedGet 1_column_family 50000 128 16384 thrpt 25 61712.038 ± 423.494 ops/s -GetBenchmarks.preallocatedGet 20_column_families 1000 128 1024 thrpt 25 694684.465 ± 5484.205 ops/s -GetBenchmarks.preallocatedGet 20_column_families 1000 128 16384 thrpt 25 172383.593 ± 841.679 ops/s -GetBenchmarks.preallocatedGet 20_column_families 50000 128 1024 thrpt 25 257447.351 ± 1388.667 ops/s -GetBenchmarks.preallocatedGet 20_column_families 50000 128 16384 thrpt 25 13418.522 ± 2418.619 ops/s - -### Baseline (no fixes) - -Benchmark (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get 1_column_family 1000 128 1024 thrpt 25 866745.224 ± 8834.629 ops/s -GetBenchmarks.get 1_column_family 1000 128 16384 thrpt 25 184332.195 ± 2304.217 ops/s -GetBenchmarks.get 1_column_family 50000 128 1024 thrpt 25 666794.288 ± 16150.684 ops/s -GetBenchmarks.get 1_column_family 50000 128 16384 thrpt 25 47221.788 ± 433.165 ops/s -GetBenchmarks.get 20_column_families 1000 128 1024 thrpt 25 551513.636 ± 7763.681 ops/s -GetBenchmarks.get 20_column_families 1000 128 16384 thrpt 25 113117.720 ± 580.738 ops/s -GetBenchmarks.get 20_column_families 50000 128 1024 thrpt 25 238675.555 ± 1758.978 ops/s -GetBenchmarks.get 20_column_families 50000 128 16384 thrpt 25 11639.390 ± 1459.765 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 1000 128 1024 thrpt 25 1153617.917 ± 26350.028 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 1000 128 16384 thrpt 25 401710.334 ± 4324.539 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 50000 128 1024 thrpt 25 809384.073 ± 13833.871 ops/s -GetBenchmarks.preallocatedByteBufferGet 1_column_family 50000 128 16384 thrpt 25 59279.005 ± 443.207 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 1000 128 1024 thrpt 25 715466.403 ± 6591.375 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 1000 128 16384 thrpt 25 175279.163 ± 910.923 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 50000 128 1024 thrpt 25 263295.180 ± 856.456 ops/s -GetBenchmarks.preallocatedByteBufferGet 20_column_families 50000 128 16384 thrpt 25 14001.928 ± 2462.067 ops/s -GetBenchmarks.preallocatedGet 1_column_family 1000 128 1024 thrpt 25 1072866.854 ± 27030.592 ops/s -GetBenchmarks.preallocatedGet 1_column_family 1000 128 16384 thrpt 25 383950.853 ± 4510.654 ops/s -GetBenchmarks.preallocatedGet 1_column_family 50000 128 1024 thrpt 25 764395.469 ± 10097.417 ops/s -GetBenchmarks.preallocatedGet 1_column_family 50000 128 16384 thrpt 25 56851.330 ± 388.029 ops/s -GetBenchmarks.preallocatedGet 20_column_families 1000 128 1024 thrpt 25 668518.593 ± 9764.117 ops/s -GetBenchmarks.preallocatedGet 20_column_families 1000 128 16384 thrpt 25 171309.695 ± 875.895 ops/s -GetBenchmarks.preallocatedGet 20_column_families 50000 128 1024 thrpt 25 256057.801 ± 954.621 ops/s -GetBenchmarks.preallocatedGet 20_column_families 50000 128 16384 thrpt 25 13319.380 ± 2126.654 ops/s - -### Comparison - -It does at least look best when the data is cached. That is to say, smallest number of column families, and least keys. - -GetBenchmarks.get 1_column_family 1000 128 16384 thrpt 25 204366.301 ± 1326.570 ops/s -GetBenchmarks.get 1_column_family 1000 128 16384 thrpt 25 184332.195 ± 2304.217 ops/s - -GetBenchmarks.get 1_column_family 50000 128 16384 thrpt 25 50473.768 ± 497.335 ops/s -GetBenchmarks.get 1_column_family 50000 128 16384 thrpt 25 47221.788 ± 433.165 ops/s - -GetBenchmarks.get 20_column_families 1000 128 16384 thrpt 25 120545.549 ± 648.280 ops/s -GetBenchmarks.get 20_column_families 1000 128 16384 thrpt 25 113117.720 ± 580.738 ops/s - -GetBenchmarks.get 20_column_families 50000 128 16384 thrpt 25 12463.887 ± 1950.746 ops/s -GetBenchmarks.get 20_column_families 50000 128 16384 thrpt 25 11639.390 ± 1459.765 ops/s - -### Baseline -25 minute run, small number of keys -``` -java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -p keyCount=1000 -p keySize=128 -p valueSize=32768 -p columnFamilyTestType="no_column_families" GetBenchmarks.get GetBenchmarks.preallocatedByteBufferGet GetBenchmarks.preallocatedGet -``` - -Benchmark (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get no_column_families 1000 128 32768 thrpt 25 32344.908 ± 296.651 ops/s -GetBenchmarks.preallocatedByteBufferGet no_column_families 1000 128 32768 thrpt 25 45266.968 ± 424.514 ops/s -GetBenchmarks.preallocatedGet no_column_families 1000 128 32768 thrpt 25 43531.088 ± 291.785 ops/s - -### Optimized - -Benchmark (columnFamilyTestType) (keyCount) (keySize) (valueSize) Mode Cnt Score Error Units -GetBenchmarks.get no_column_families 1000 128 32768 thrpt 25 37463.716 ± 235.744 ops/s -GetBenchmarks.preallocatedByteBufferGet no_column_families 1000 128 32768 thrpt 25 48946.105 ± 466.463 ops/s -GetBenchmarks.preallocatedGet no_column_families 1000 128 32768 thrpt 25 47143.624 ± 576.763 ops/s - -## Conclusion - -The performance improvement is real. - diff --git a/java/HISTORY-JAVA.md b/java/HISTORY-JAVA.md deleted file mode 100644 index 731886a61..000000000 --- a/java/HISTORY-JAVA.md +++ /dev/null @@ -1,86 +0,0 @@ -# RocksJava Change Log - -## 3.13 (8/4/2015) -### New Features -* Exposed BackupEngine API. -* Added CappedPrefixExtractor support. To use such extractor, simply call useCappedPrefixExtractor in either Options or ColumnFamilyOptions. -* Added RemoveEmptyValueCompactionFilter. - -## 3.10.0 (3/24/2015) -### New Features -* Added compression per level API. -* MemEnv is now available in RocksJava via RocksMemEnv class. -* lz4 compression is now included in rocksjava static library when running `make rocksdbjavastatic`. - -### Public API Changes -* Overflowing a size_t when setting rocksdb options now throws an IllegalArgumentException, which removes the necessity for a developer to catch these Exceptions explicitly. -* The set and get functions for tableCacheRemoveScanCountLimit are deprecated. - - -## By 01/31/2015 -### New Features -* WriteBatchWithIndex support. -* Iterator support for WriteBatch and WriteBatchWithIndex -* GetUpdatesSince support. -* Snapshots carry now information about the related sequence number. -* TTL DB support. - -## By 11/14/2014 -### New Features -* Full support for Column Family. -* Slice and Comparator support. -* Default merge operator support. -* RateLimiter support. - -## By 06/15/2014 -### New Features -* Added basic Java binding for rocksdb::Env such that multiple RocksDB can share the same thread pool and environment. -* Added RestoreBackupableDB - -## By 05/30/2014 -### Internal Framework Improvement -* Added disOwnNativeHandle to RocksObject, which allows a RocksObject to give-up the ownership of its native handle. This method is useful when sharing and transferring the ownership of RocksDB C++ resources. - -## By 05/15/2014 -### New Features -* Added RocksObject --- the base class of all RocksDB classes which holds some RocksDB resources in the C++ side. -* Use environmental variable JAVA_HOME in Makefile for RocksJava -### Public API changes -* Renamed org.rocksdb.Iterator to org.rocksdb.RocksIterator to avoid potential confliction with Java built-in Iterator. - -## By 04/30/2014 -### New Features -* Added Java binding for MultiGet. -* Added static method RocksDB.loadLibrary(), which loads necessary library files. -* Added Java bindings for 60+ rocksdb::Options. -* Added Java binding for BloomFilter. -* Added Java binding for ReadOptions. -* Added Java binding for memtables. -* Added Java binding for sst formats. -* Added Java binding for RocksDB Iterator which enables sequential scan operation. -* Added Java binding for Statistics -* Added Java binding for BackupableDB. - -### DB Benchmark -* Added filluniquerandom, readseq benchmark. -* 70+ command-line options. -* Enabled BloomFilter configuration. - -## By 04/15/2014 -### New Features -* Added Java binding for WriteOptions. -* Added Java binding for WriteBatch, which enables batch-write. -* Added Java binding for rocksdb::Options. -* Added Java binding for block cache. -* Added Java version DB Benchmark. - -### DB Benchmark -* Added readwhilewriting benchmark. - -### Internal Framework Improvement -* Avoid a potential byte-array-copy between c++ and Java in RocksDB.get. -* Added SizeUnit in org.rocksdb.util to store consts like KB and GB. - -### 03/28/2014 -* RocksJava project started. -* Added Java binding for RocksDB, which supports Open, Close, Get and Put. diff --git a/java/Makefile b/java/Makefile deleted file mode 100644 index 7d2695af8..000000000 --- a/java/Makefile +++ /dev/null @@ -1,453 +0,0 @@ -NATIVE_JAVA_CLASSES = \ - org.rocksdb.AbstractCompactionFilter\ - org.rocksdb.AbstractCompactionFilterFactory\ - org.rocksdb.AbstractComparator\ - org.rocksdb.AbstractEventListener\ - org.rocksdb.AbstractSlice\ - org.rocksdb.AbstractTableFilter\ - org.rocksdb.AbstractTraceWriter\ - org.rocksdb.AbstractTransactionNotifier\ - org.rocksdb.AbstractWalFilter\ - org.rocksdb.BackupEngine\ - org.rocksdb.BackupEngineOptions\ - org.rocksdb.BlockBasedTableConfig\ - org.rocksdb.BloomFilter\ - org.rocksdb.Checkpoint\ - org.rocksdb.ClockCache\ - org.rocksdb.Cache\ - org.rocksdb.CassandraCompactionFilter\ - org.rocksdb.CassandraValueMergeOperator\ - org.rocksdb.ColumnFamilyHandle\ - org.rocksdb.ColumnFamilyOptions\ - org.rocksdb.CompactionJobInfo\ - org.rocksdb.CompactionJobStats\ - org.rocksdb.CompactionOptions\ - org.rocksdb.CompactionOptionsFIFO\ - org.rocksdb.CompactionOptionsUniversal\ - org.rocksdb.CompactRangeOptions\ - org.rocksdb.ComparatorOptions\ - org.rocksdb.CompressionOptions\ - org.rocksdb.ConfigOptions\ - org.rocksdb.DBOptions\ - org.rocksdb.DirectSlice\ - org.rocksdb.Env\ - org.rocksdb.EnvOptions\ - org.rocksdb.FlushOptions\ - org.rocksdb.Filter\ - org.rocksdb.IngestExternalFileOptions\ - org.rocksdb.HashLinkedListMemTableConfig\ - org.rocksdb.HashSkipListMemTableConfig\ - org.rocksdb.ConcurrentTaskLimiter\ - org.rocksdb.ConcurrentTaskLimiterImpl\ - org.rocksdb.KeyMayExist\ - org.rocksdb.Logger\ - org.rocksdb.LRUCache\ - org.rocksdb.MemoryUsageType\ - org.rocksdb.MemoryUtil\ - org.rocksdb.MergeOperator\ - org.rocksdb.NativeComparatorWrapper\ - org.rocksdb.OptimisticTransactionDB\ - org.rocksdb.OptimisticTransactionOptions\ - org.rocksdb.Options\ - org.rocksdb.OptionsUtil\ - org.rocksdb.PersistentCache\ - org.rocksdb.PlainTableConfig\ - org.rocksdb.RateLimiter\ - org.rocksdb.ReadOptions\ - org.rocksdb.RemoveEmptyValueCompactionFilter\ - org.rocksdb.RestoreOptions\ - org.rocksdb.RocksCallbackObject\ - org.rocksdb.RocksDB\ - org.rocksdb.RocksEnv\ - org.rocksdb.RocksIterator\ - org.rocksdb.RocksMemEnv\ - org.rocksdb.SkipListMemTableConfig\ - org.rocksdb.Slice\ - org.rocksdb.SstFileManager\ - org.rocksdb.SstFileWriter\ - org.rocksdb.SstFileReader\ - org.rocksdb.SstFileReaderIterator\ - org.rocksdb.SstPartitionerFactory\ - org.rocksdb.SstPartitionerFixedPrefixFactory\ - org.rocksdb.Statistics\ - org.rocksdb.ThreadStatus\ - org.rocksdb.TimedEnv\ - org.rocksdb.Transaction\ - org.rocksdb.TransactionDB\ - org.rocksdb.TransactionDBOptions\ - org.rocksdb.TransactionOptions\ - org.rocksdb.TransactionLogIterator\ - org.rocksdb.TtlDB\ - org.rocksdb.VectorMemTableConfig\ - org.rocksdb.Snapshot\ - org.rocksdb.StringAppendOperator\ - org.rocksdb.UInt64AddOperator\ - org.rocksdb.WriteBatch\ - org.rocksdb.WriteBatch.Handler\ - org.rocksdb.WriteOptions\ - org.rocksdb.WriteBatchWithIndex\ - org.rocksdb.WriteBufferManager\ - org.rocksdb.WBWIRocksIterator - -NATIVE_JAVA_TEST_CLASSES = \ - org.rocksdb.RocksDBExceptionTest\ - org.rocksdb.test.TestableEventListener\ - org.rocksdb.NativeComparatorWrapperTest.NativeStringComparatorWrapper\ - org.rocksdb.WriteBatchTest\ - org.rocksdb.WriteBatchTestInternalHelper - -ROCKSDB_MAJOR = $(shell grep -E "ROCKSDB_MAJOR.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3) -ROCKSDB_MINOR = $(shell grep -E "ROCKSDB_MINOR.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3) -ROCKSDB_PATCH = $(shell grep -E "ROCKSDB_PATCH.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3) - -NATIVE_INCLUDE = ./include -ARCH := $(shell getconf LONG_BIT) -SHA256_CMD ?= sha256sum - -JAVA_TESTS = \ - org.rocksdb.BackupEngineOptionsTest\ - org.rocksdb.BackupEngineTest\ - org.rocksdb.BlobOptionsTest\ - org.rocksdb.BlockBasedTableConfigTest\ - org.rocksdb.BuiltinComparatorTest\ - org.rocksdb.ByteBufferUnsupportedOperationTest\ - org.rocksdb.BytewiseComparatorRegressionTest\ - org.rocksdb.util.BytewiseComparatorTest\ - org.rocksdb.util.BytewiseComparatorIntTest\ - org.rocksdb.CheckPointTest\ - org.rocksdb.ClockCacheTest\ - org.rocksdb.ColumnFamilyOptionsTest\ - org.rocksdb.ColumnFamilyTest\ - org.rocksdb.CompactionFilterFactoryTest\ - org.rocksdb.CompactionJobInfoTest\ - org.rocksdb.CompactionJobStatsTest\ - org.rocksdb.CompactionOptionsTest\ - org.rocksdb.CompactionOptionsFIFOTest\ - org.rocksdb.CompactionOptionsUniversalTest\ - org.rocksdb.CompactionPriorityTest\ - org.rocksdb.CompactionStopStyleTest\ - org.rocksdb.ComparatorOptionsTest\ - org.rocksdb.CompressionOptionsTest\ - org.rocksdb.CompressionTypesTest\ - org.rocksdb.DBOptionsTest\ - org.rocksdb.DirectSliceTest\ - org.rocksdb.util.EnvironmentTest\ - org.rocksdb.EnvOptionsTest\ - org.rocksdb.EventListenerTest\ - org.rocksdb.IngestExternalFileOptionsTest\ - org.rocksdb.util.IntComparatorTest\ - org.rocksdb.util.JNIComparatorTest\ - org.rocksdb.FilterTest\ - org.rocksdb.FlushTest\ - org.rocksdb.InfoLogLevelTest\ - org.rocksdb.KeyMayExistTest\ - org.rocksdb.ConcurrentTaskLimiterTest\ - org.rocksdb.LoggerTest\ - org.rocksdb.LRUCacheTest\ - org.rocksdb.MemoryUtilTest\ - org.rocksdb.MemTableTest\ - org.rocksdb.MergeTest\ - org.rocksdb.MultiColumnRegressionTest \ - org.rocksdb.MultiGetManyKeysTest\ - org.rocksdb.MultiGetTest\ - org.rocksdb.MixedOptionsTest\ - org.rocksdb.MutableColumnFamilyOptionsTest\ - org.rocksdb.MutableDBOptionsTest\ - org.rocksdb.MutableOptionsGetSetTest \ - org.rocksdb.NativeComparatorWrapperTest\ - org.rocksdb.NativeLibraryLoaderTest\ - org.rocksdb.OptimisticTransactionTest\ - org.rocksdb.OptimisticTransactionDBTest\ - org.rocksdb.OptimisticTransactionOptionsTest\ - org.rocksdb.OptionsUtilTest\ - org.rocksdb.OptionsTest\ - org.rocksdb.PlainTableConfigTest\ - org.rocksdb.RateLimiterTest\ - org.rocksdb.ReadOnlyTest\ - org.rocksdb.ReadOptionsTest\ - org.rocksdb.util.ReverseBytewiseComparatorIntTest\ - org.rocksdb.RocksDBTest\ - org.rocksdb.RocksDBExceptionTest\ - org.rocksdb.DefaultEnvTest\ - org.rocksdb.RocksIteratorTest\ - org.rocksdb.RocksMemEnvTest\ - org.rocksdb.util.SizeUnitTest\ - org.rocksdb.SecondaryDBTest\ - org.rocksdb.SliceTest\ - org.rocksdb.SnapshotTest\ - org.rocksdb.SstFileManagerTest\ - org.rocksdb.SstFileWriterTest\ - org.rocksdb.SstFileReaderTest\ - org.rocksdb.SstPartitionerTest\ - org.rocksdb.TableFilterTest\ - org.rocksdb.TimedEnvTest\ - org.rocksdb.TransactionTest\ - org.rocksdb.TransactionDBTest\ - org.rocksdb.TransactionOptionsTest\ - org.rocksdb.TransactionDBOptionsTest\ - org.rocksdb.TransactionLogIteratorTest\ - org.rocksdb.TtlDBTest\ - org.rocksdb.StatisticsTest\ - org.rocksdb.StatisticsCollectorTest\ - org.rocksdb.VerifyChecksumsTest\ - org.rocksdb.WalFilterTest\ - org.rocksdb.WALRecoveryModeTest\ - org.rocksdb.WriteBatchHandlerTest\ - org.rocksdb.WriteBatchTest\ - org.rocksdb.WriteBatchThreadedTest\ - org.rocksdb.WriteOptionsTest\ - org.rocksdb.WriteBatchWithIndexTest - -MAIN_SRC = src/main/java -TEST_SRC = src/test/java -OUTPUT = target -MAIN_CLASSES = $(OUTPUT)/classes -TEST_CLASSES = $(OUTPUT)/test-classes -JAVADOC = $(OUTPUT)/apidocs - -BENCHMARK_MAIN_SRC = benchmark/src/main/java -BENCHMARK_OUTPUT = benchmark/target -BENCHMARK_MAIN_CLASSES = $(BENCHMARK_OUTPUT)/classes - -SAMPLES_MAIN_SRC = samples/src/main/java -SAMPLES_OUTPUT = samples/target -SAMPLES_MAIN_CLASSES = $(SAMPLES_OUTPUT)/classes - -JAVA_TEST_LIBDIR = test-libs -JAVA_JUNIT_VER = 4.13.1 -JAVA_JUNIT_SHA256 = c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122 -JAVA_JUNIT_JAR = junit-$(JAVA_JUNIT_VER).jar -JAVA_JUNIT_JAR_PATH = $(JAVA_TEST_LIBDIR)/$(JAVA_JUNIT_JAR) -JAVA_HAMCREST_VER = 2.2 -JAVA_HAMCREST_SHA256 = 5e62846a89f05cd78cd9c1a553f340d002458380c320455dd1f8fc5497a8a1c1 -JAVA_HAMCREST_JAR = hamcrest-$(JAVA_HAMCREST_VER).jar -JAVA_HAMCREST_JAR_PATH = $(JAVA_TEST_LIBDIR)/$(JAVA_HAMCREST_JAR) -JAVA_MOCKITO_VER = 1.10.19 -JAVA_MOCKITO_SHA256 = d1a7a7ef14b3db5c0fc3e0a63a81b374b510afe85add9f7984b97911f4c70605 -JAVA_MOCKITO_JAR = mockito-all-$(JAVA_MOCKITO_VER).jar -JAVA_MOCKITO_JAR_PATH = $(JAVA_TEST_LIBDIR)/$(JAVA_MOCKITO_JAR) -JAVA_CGLIB_VER = 3.3.0 -JAVA_CGLIB_SHA256 = 9fe0c26d7464140ccdfe019ac687be1fb906122b508ab54beb810db0f09a9212 -JAVA_CGLIB_JAR = cglib-$(JAVA_CGLIB_VER).jar -JAVA_CGLIB_JAR_PATH = $(JAVA_TEST_LIBDIR)/$(JAVA_CGLIB_JAR) -JAVA_ASSERTJ_VER = 2.9.0 -JAVA_ASSERTJ_SHA256 = 5e88ea3ecbe3c48aa1346fec76c84979fa9c8d22499f11479011691230e8babf -JAVA_ASSERTJ_JAR = assertj-core-$(JAVA_ASSERTJ_VER).jar -JAVA_ASSERTJ_JAR_PATH = $(JAVA_TEST_LIBDIR)/$(JAVA_ASSERTJ_JAR) -JAVA_TESTCLASSPATH = $(JAVA_JUNIT_JAR_PATH):$(JAVA_HAMCREST_JAR_PATH):$(JAVA_MOCKITO_JAR_PATH):$(JAVA_CGLIB_JAR_PATH):$(JAVA_ASSERTJ_JAR_PATH) - -MVN_LOCAL = ~/.m2/repository - -# Set the path of the java commands -ifeq ($(JAVA_CMD),) -ifneq ($(JAVA_HOME),) -JAVA_CMD := $(JAVA_HOME)/bin/java -else -JAVA_CMD := java -endif -endif - -ifeq ($(JAVAC_CMD),) -ifneq ($(JAVA_HOME),) -JAVAC_CMD := $(JAVA_HOME)/bin/javac -else -JAVAC_CMD := javac -endif -endif - -ifeq ($(JAVADOC_CMD),) -ifneq ($(JAVA_HOME),) -JAVADOC_CMD := $(JAVA_HOME)/bin/javadoc -else -JAVADOC_CMD := javadoc -endif -endif - -# Look for the Java version (1.6->6, 1.7->7, 1.8->8, 11.0->11, 13.0->13, 15.0->15 etc..) -JAVAC_VERSION := $(shell $(JAVAC_CMD) -version 2>&1) -JAVAC_MAJOR_VERSION := $(word 2,$(subst ., ,$(JAVAC_VERSION))) -ifeq ($(JAVAC_MAJOR_VERSION),1) -JAVAC_MAJOR_VERSION := $(word 3,$(subst ., ,$(JAVAC_VERSION))) -endif - -# Test whether the version we see meets our minimum -MIN_JAVAC_MAJOR_VERSION := 8 -JAVAC_VERSION_GE_MIN := $(shell [ $(JAVAC_MAJOR_VERSION) -ge $(MIN_JAVAC_MAJOR_VERSION) ] > /dev/null 2>&1 && echo true) - -# Set the default JAVA_ARGS to "" for DEBUG_LEVEL=0 -JAVA_ARGS ?= - -JAVAC_ARGS ?= - -# Read plugin configuration -PLUGIN_PATH = ../plugin -ROCKSDB_PLUGIN_MKS = $(foreach plugin, $(ROCKSDB_PLUGINS), $(PLUGIN_PATH)/$(plugin)/*.mk) -include $(ROCKSDB_PLUGIN_MKS) - -# Add paths to Java sources in plugins -ROCKSDB_PLUGIN_JAVA_ROOTS = $(foreach plugin, $(ROCKSDB_PLUGINS), $(PLUGIN_PATH)/$(plugin)/java) -PLUGIN_SOURCES = $(foreach root, $(ROCKSDB_PLUGIN_JAVA_ROOTS), $(foreach pkg, org/rocksdb/util org/rocksdb, $(root)/$(MAIN_SRC)/$(pkg)/*.java)) -CORE_SOURCES = $(foreach pkg, org/rocksdb/util org/rocksdb, $(MAIN_SRC)/$(pkg)/*.java) -SOURCES = $(wildcard $(CORE_SOURCES) $(PLUGIN_SOURCES)) -PLUGIN_TEST_SOURCES = $(foreach root, $(ROCKSDB_PLUGIN_JAVA_ROOTS), $(foreach pkg, org/rocksdb/test org/rocksdb/util org/rocksdb, $(root)/$(TEST_SRC)/$(pkg)/*.java)) -CORE_TEST_SOURCES = $(foreach pkg, org/rocksdb/test org/rocksdb/util org/rocksdb, $(TEST_SRC)/$(pkg)/*.java) -TEST_SOURCES = $(wildcard $(CORE_TEST_SOURCES) $(PLUGIN_TEST_SOURCES)) - -# Configure the plugin tests and java classes -ROCKSDB_PLUGIN_NATIVE_JAVA_CLASSES = $(foreach plugin, $(ROCKSDB_PLUGINS), $(foreach class, $($(plugin)_NATIVE_JAVA_CLASSES), $(class))) -NATIVE_JAVA_CLASSES = $(NATIVE_JAVA_CLASSES) $(ROCKSDB_PLUGIN_NATIVE_JAVA_CLASSES) -ROCKSDB_PLUGIN_JAVA_TESTS = $(foreach plugin, $(ROCKSDB_PLUGINS), $(foreach testclass, $($(plugin)_JAVA_TESTS), $(testclass))) -ALL_JAVA_TESTS = $(JAVA_TESTS) $(ROCKSDB_PLUGIN_JAVA_TESTS) - -# When debugging add -Xcheck:jni to the java args -ifneq ($(DEBUG_LEVEL),0) - JAVA_ARGS += -ea -Xcheck:jni - JAVAC_ARGS += -Xlint:deprecation -Xlint:unchecked -endif - -# Using a Facebook AWS account for S3 storage. (maven.org has a history -# of failing in Travis builds.) -DEPS_URL?=https://rocksdb-deps.s3-us-west-2.amazonaws.com/jars - -java-version: -ifneq ($(JAVAC_VERSION_GE_MIN),true) - echo 'Java version is $(JAVAC_VERSION), minimum required version is $(MIN_JAVAC_MAJOR_VERSION)' - exit 1 -endif - -clean: clean-not-downloaded clean-downloaded - -clean-not-downloaded: - $(AM_V_at)rm -rf $(NATIVE_INCLUDE) - $(AM_V_at)rm -rf $(OUTPUT) - $(AM_V_at)rm -rf $(BENCHMARK_OUTPUT) - $(AM_V_at)rm -rf $(SAMPLES_OUTPUT) - -clean-downloaded: - $(AM_V_at)rm -rf $(JAVA_TEST_LIBDIR) - - -javadocs: java - $(AM_V_GEN)mkdir -p $(JAVADOC) - $(AM_V_at)$(JAVADOC_CMD) -d $(JAVADOC) -sourcepath $(MAIN_SRC) -subpackages org - -javalib: java java_test javadocs - -java: java-version - $(AM_V_GEN)mkdir -p $(MAIN_CLASSES) - $(AM_V_at) $(JAVAC_CMD) $(JAVAC_ARGS) -h $(NATIVE_INCLUDE) -d $(MAIN_CLASSES) $(SOURCES) - $(AM_V_at)@cp ../HISTORY.md ./HISTORY-CPP.md - $(AM_V_at)@rm -f ./HISTORY-CPP.md - -sample: java - $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES) - $(AM_V_at)$(JAVAC_CMD) $(JAVAC_ARGS) -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/RocksDBSample.java - $(AM_V_at)@rm -rf /tmp/rocksdbjni - $(AM_V_at)@rm -rf /tmp/rocksdbjni_not_found - $(JAVA_CMD) $(JAVA_ARGS) -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) RocksDBSample /tmp/rocksdbjni - $(AM_V_at)@rm -rf /tmp/rocksdbjni - $(AM_V_at)@rm -rf /tmp/rocksdbjni_not_found - -column_family_sample: java - $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES) - $(AM_V_at)$(JAVAC_CMD) $(JAVAC_ARGS) -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/RocksDBColumnFamilySample.java - $(AM_V_at)@rm -rf /tmp/rocksdbjni - $(JAVA_CMD) $(JAVA_ARGS) -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) RocksDBColumnFamilySample /tmp/rocksdbjni - $(AM_V_at)@rm -rf /tmp/rocksdbjni - -transaction_sample: java - $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES) - $(AM_V_at)$(JAVAC_CMD) -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/TransactionSample.java - $(AM_V_at)@rm -rf /tmp/rocksdbjni - $(JAVA_CMD) -ea -Xcheck:jni -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) TransactionSample /tmp/rocksdbjni - $(AM_V_at)@rm -rf /tmp/rocksdbjni - -optimistic_transaction_sample: java - $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES) - $(AM_V_at)$(JAVAC_CMD) -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/OptimisticTransactionSample.java - $(AM_V_at)@rm -rf /tmp/rocksdbjni - $(JAVA_CMD) -ea -Xcheck:jni -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) OptimisticTransactionSample /tmp/rocksdbjni - $(AM_V_at)@rm -rf /tmp/rocksdbjni - -$(JAVA_TEST_LIBDIR): - mkdir -p "$(JAVA_TEST_LIBDIR)" - -$(JAVA_JUNIT_JAR_PATH): $(JAVA_TEST_LIBDIR) -ifneq (,$(wildcard $(MVN_LOCAL)/junit/junit/$(JAVA_JUNIT_VER)/$(JAVA_JUNIT_JAR))) - cp -v $(MVN_LOCAL)/junit/junit/$(JAVA_JUNIT_VER)/$(JAVA_JUNIT_JAR) $(JAVA_TEST_LIBDIR) -else - curl --fail --insecure --output $(JAVA_JUNIT_JAR_PATH) --location $(DEPS_URL)/$(JAVA_JUNIT_JAR) - JAVA_JUNIT_SHA256_ACTUAL=`$(SHA256_CMD) $(JAVA_JUNIT_JAR_PATH) | cut -d ' ' -f 1`; \ - if [ "$(JAVA_JUNIT_SHA256)" != "$$JAVA_JUNIT_SHA256_ACTUAL" ]; then \ - echo $(JAVA_JUNIT_JAR_PATH) checksum mismatch, expected=\"$(JAVA_JUNIT_SHA256)\" actual=\"$$JAVA_JUNIT_SHA256_ACTUAL\"; \ - exit 1; \ - fi -endif - -$(JAVA_HAMCREST_JAR_PATH): $(JAVA_TEST_LIBDIR) -ifneq (,$(wildcard $(MVN_LOCAL)/org/hamcrest/hamcrest/$(JAVA_HAMCREST_VER)/$(JAVA_HAMCREST_JAR))) - cp -v $(MVN_LOCAL)/org/hamcrest/hamcrest/$(JAVA_HAMCREST_VER)/$(JAVA_HAMCREST_JAR) $(JAVA_TEST_LIBDIR) -else - curl --fail --insecure --output $(JAVA_HAMCREST_JAR_PATH) --location $(DEPS_URL)/$(JAVA_HAMCREST_JAR) - JAVA_HAMCREST_SHA256_ACTUAL=`$(SHA256_CMD) $(JAVA_HAMCREST_JAR_PATH) | cut -d ' ' -f 1`; \ - if [ "$(JAVA_HAMCREST_SHA256)" != "$$JAVA_HAMCREST_SHA256_ACTUAL" ]; then \ - echo $(JAVA_HAMCREST_JAR_PATH) checksum mismatch, expected=\"$(JAVA_HAMCREST_SHA256)\" actual=\"$$JAVA_HAMCREST_SHA256_ACTUAL\"; \ - exit 1; \ - fi -endif - -$(JAVA_MOCKITO_JAR_PATH): $(JAVA_TEST_LIBDIR) -ifneq (,$(wildcard $(MVN_LOCAL)/org/mockito/mockito-all/$(JAVA_MOCKITO_VER)/$(JAVA_MOCKITO_JAR))) - cp -v $(MVN_LOCAL)/org/mockito/mockito-all/$(JAVA_MOCKITO_VER)/$(JAVA_MOCKITO_JAR) $(JAVA_TEST_LIBDIR) -else - curl --fail --insecure --output "$(JAVA_MOCKITO_JAR_PATH)" --location $(DEPS_URL)/$(JAVA_MOCKITO_JAR) - JAVA_MOCKITO_SHA256_ACTUAL=`$(SHA256_CMD) $(JAVA_MOCKITO_JAR_PATH) | cut -d ' ' -f 1`; \ - if [ "$(JAVA_MOCKITO_SHA256)" != "$$JAVA_MOCKITO_SHA256_ACTUAL" ]; then \ - echo $(JAVA_MOCKITO_JAR_PATH) checksum mismatch, expected=\"$(JAVA_MOCKITO_SHA256)\" actual=\"$$JAVA_MOCKITO_SHA256_ACTUAL\"; \ - exit 1; \ - fi -endif - -$(JAVA_CGLIB_JAR_PATH): $(JAVA_TEST_LIBDIR) -ifneq (,$(wildcard $(MVN_LOCAL)/cglib/cglib/$(JAVA_CGLIB_VER)/$(JAVA_CGLIB_JAR))) - cp -v $(MVN_LOCAL)/cglib/cglib/$(JAVA_CGLIB_VER)/$(JAVA_CGLIB_JAR) $(JAVA_TEST_LIBDIR) -else - curl --fail --insecure --output "$(JAVA_CGLIB_JAR_PATH)" --location $(DEPS_URL)/$(JAVA_CGLIB_JAR) - JAVA_CGLIB_SHA256_ACTUAL=`$(SHA256_CMD) $(JAVA_CGLIB_JAR_PATH) | cut -d ' ' -f 1`; \ - if [ "$(JAVA_CGLIB_SHA256)" != "$$JAVA_CGLIB_SHA256_ACTUAL" ]; then \ - echo $(JAVA_CGLIB_JAR_PATH) checksum mismatch, expected=\"$(JAVA_CGLIB_SHA256)\" actual=\"$$JAVA_CGLIB_SHA256_ACTUAL\"; \ - exit 1; \ - fi -endif - -$(JAVA_ASSERTJ_JAR_PATH): $(JAVA_TEST_LIBDIR) -ifneq (,$(wildcard $(MVN_LOCAL)/org/assertj/assertj-core/$(JAVA_ASSERTJ_VER)/$(JAVA_ASSERTJ_JAR))) - cp -v $(MVN_LOCAL)/org/assertj/assertj-core/$(JAVA_ASSERTJ_VER)/$(JAVA_ASSERTJ_JAR) $(JAVA_TEST_LIBDIR) -else - curl --fail --insecure --output "$(JAVA_ASSERTJ_JAR_PATH)" --location $(DEPS_URL)/$(JAVA_ASSERTJ_JAR) - JAVA_ASSERTJ_SHA256_ACTUAL=`$(SHA256_CMD) $(JAVA_ASSERTJ_JAR_PATH) | cut -d ' ' -f 1`; \ - if [ "$(JAVA_ASSERTJ_SHA256)" != "$$JAVA_ASSERTJ_SHA256_ACTUAL" ]; then \ - echo $(JAVA_ASSERTJ_JAR_PATH) checksum mismatch, expected=\"$(JAVA_ASSERTJ_SHA256)\" actual=\"$$JAVA_ASSERTJ_SHA256_ACTUAL\"; \ - exit 1; \ - fi -endif - -resolve_test_deps: $(JAVA_JUNIT_JAR_PATH) $(JAVA_HAMCREST_JAR_PATH) $(JAVA_MOCKITO_JAR_PATH) $(JAVA_CGLIB_JAR_PATH) $(JAVA_ASSERTJ_JAR_PATH) - -java_test: java resolve_test_deps - $(AM_V_GEN)mkdir -p $(TEST_CLASSES) - $(AM_V_at) $(JAVAC_CMD) $(JAVAC_ARGS) -cp $(MAIN_CLASSES):$(JAVA_TESTCLASSPATH) -h $(NATIVE_INCLUDE) -d $(TEST_CLASSES)\ - $(TEST_SOURCES) - -test: java java_test - $(MAKE) run_test - -run_test: - $(JAVA_CMD) $(JAVA_ARGS) -Djava.library.path=target -cp "$(MAIN_CLASSES):$(TEST_CLASSES):$(JAVA_TESTCLASSPATH):target/*" org.rocksdb.test.RocksJunitRunner $(ALL_JAVA_TESTS) - -run_plugin_test: - $(JAVA_CMD) $(JAVA_ARGS) -Djava.library.path=target -cp "$(MAIN_CLASSES):$(TEST_CLASSES):$(JAVA_TESTCLASSPATH):target/*" org.rocksdb.test.RocksJunitRunner $(ROCKSDB_PLUGIN_JAVA_TESTS) - -db_bench: java - $(AM_V_GEN)mkdir -p $(BENCHMARK_MAIN_CLASSES) - $(AM_V_at)$(JAVAC_CMD) $(JAVAC_ARGS) -cp $(MAIN_CLASSES) -d $(BENCHMARK_MAIN_CLASSES) $(BENCHMARK_MAIN_SRC)/org/rocksdb/benchmark/*.java diff --git a/java/RELEASE.md b/java/RELEASE.md deleted file mode 100644 index dda19455f..000000000 --- a/java/RELEASE.md +++ /dev/null @@ -1,59 +0,0 @@ -## Cross-building - -RocksDB can be built as a single self contained cross-platform JAR. The cross-platform jar can be used on any 64-bit OSX system, 32-bit Linux system, or 64-bit Linux system. - -Building a cross-platform JAR requires: - - * [Docker](https://www.docker.com/docker-community) - * A Mac OSX machine that can compile RocksDB. - * Java 7 set as JAVA_HOME. - -Once you have these items, run this make command from RocksDB's root source directory: - - make jclean clean rocksdbjavastaticreleasedocker - -This command will build RocksDB natively on OSX, and will then spin up docker containers to build RocksDB for 32-bit and 64-bit Linux with glibc, and 32-bit and 64-bit Linux with musl libc. - -You can find all native binaries and JARs in the java/target directory upon completion: - - librocksdbjni-linux32.so - librocksdbjni-linux64.so - librocksdbjni-linux64-musl.so - librocksdbjni-linux32-musl.so - librocksdbjni-osx.jnilib - rocksdbjni-x.y.z-javadoc.jar - rocksdbjni-x.y.z-linux32.jar - rocksdbjni-x.y.z-linux64.jar - rocksdbjni-x.y.z-linux64-musl.jar - rocksdbjni-x.y.z-linux32-musl.jar - rocksdbjni-x.y.z-osx.jar - rocksdbjni-x.y.z-sources.jar - rocksdbjni-x.y.z.jar - -Where x.y.z is the built version number of RocksDB. - -## Maven publication - -Set ~/.m2/settings.xml to contain: - - - - - sonatype-nexus-staging - your-sonatype-jira-username - your-sonatype-jira-password - - - - -From RocksDB's root directory, first build the Java static JARs: - - make jclean clean rocksdbjavastaticpublish - -This command will [stage the JAR artifacts on the Sonatype staging repository](http://central.sonatype.org/pages/manual-staging-bundle-creation-and-deployment.html). To release the staged artifacts. - -1. Go to [https://oss.sonatype.org/#stagingRepositories](https://oss.sonatype.org/#stagingRepositories) and search for "rocksdb" in the upper right hand search box. -2. Select the rocksdb staging repository, and inspect its contents. -3. If all is well, follow [these steps](https://oss.sonatype.org/#stagingRepositories) to close the repository and release it. - -After the release has occurred, the artifacts will be synced to Maven central within 24-48 hours. diff --git a/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java b/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java deleted file mode 100644 index 070f0fe75..000000000 --- a/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java +++ /dev/null @@ -1,1640 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -/** - * Copyright (C) 2011 the original author or authors. - * See the notice.md file distributed with this work for additional - * information regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.rocksdb.benchmark; - -import java.io.IOException; -import java.lang.Runnable; -import java.lang.Math; -import java.io.File; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.util.Collection; -import java.util.Date; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import org.rocksdb.*; -import org.rocksdb.RocksMemEnv; -import org.rocksdb.util.SizeUnit; - -class Stats { - int id_; - long start_; - long finish_; - double seconds_; - long done_; - long found_; - long lastOpTime_; - long nextReport_; - long bytes_; - StringBuilder message_; - boolean excludeFromMerge_; - - // TODO(yhchiang): use the following arguments: - // (Long)Flag.stats_interval - // (Integer)Flag.stats_per_interval - - Stats(int id) { - id_ = id; - nextReport_ = 100; - done_ = 0; - bytes_ = 0; - seconds_ = 0; - start_ = System.nanoTime(); - lastOpTime_ = start_; - finish_ = start_; - found_ = 0; - message_ = new StringBuilder(""); - excludeFromMerge_ = false; - } - - void merge(final Stats other) { - if (other.excludeFromMerge_) { - return; - } - - done_ += other.done_; - found_ += other.found_; - bytes_ += other.bytes_; - seconds_ += other.seconds_; - if (other.start_ < start_) start_ = other.start_; - if (other.finish_ > finish_) finish_ = other.finish_; - - // Just keep the messages from one thread - if (message_.length() == 0) { - message_ = other.message_; - } - } - - void stop() { - finish_ = System.nanoTime(); - seconds_ = (double) (finish_ - start_) * 1e-9; - } - - void addMessage(String msg) { - if (message_.length() > 0) { - message_.append(" "); - } - message_.append(msg); - } - - void setId(int id) { id_ = id; } - void setExcludeFromMerge() { excludeFromMerge_ = true; } - - void finishedSingleOp(int bytes) { - done_++; - lastOpTime_ = System.nanoTime(); - bytes_ += bytes; - if (done_ >= nextReport_) { - if (nextReport_ < 1000) { - nextReport_ += 100; - } else if (nextReport_ < 5000) { - nextReport_ += 500; - } else if (nextReport_ < 10000) { - nextReport_ += 1000; - } else if (nextReport_ < 50000) { - nextReport_ += 5000; - } else if (nextReport_ < 100000) { - nextReport_ += 10000; - } else if (nextReport_ < 500000) { - nextReport_ += 50000; - } else { - nextReport_ += 100000; - } - System.err.printf("... Task %s finished %d ops%30s\r", id_, done_, ""); - } - } - - void report(String name) { - // Pretend at least one op was done in case we are running a benchmark - // that does not call FinishedSingleOp(). - if (done_ < 1) done_ = 1; - - StringBuilder extra = new StringBuilder(""); - if (bytes_ > 0) { - // Rate is computed on actual elapsed time, not the sum of per-thread - // elapsed times. - double elapsed = (finish_ - start_) * 1e-9; - extra.append(String.format("%6.1f MB/s", (bytes_ / 1048576.0) / elapsed)); - } - extra.append(message_.toString()); - double elapsed = (finish_ - start_); - double throughput = (double) done_ / (elapsed * 1e-9); - - System.out.format("%-12s : %11.3f micros/op %d ops/sec;%s%s\n", - name, (elapsed * 1e-6) / done_, - (long) throughput, (extra.length() == 0 ? "" : " "), extra.toString()); - } -} - -public class DbBenchmark { - enum Order { - SEQUENTIAL, - RANDOM - } - - enum DBState { - FRESH, - EXISTING - } - - static { - RocksDB.loadLibrary(); - } - - abstract class BenchmarkTask implements Callable { - // TODO(yhchiang): use (Integer)Flag.perf_level. - public BenchmarkTask( - int tid, long randSeed, long numEntries, long keyRange) { - tid_ = tid; - rand_ = new Random(randSeed + tid * 1000); - numEntries_ = numEntries; - keyRange_ = keyRange; - stats_ = new Stats(tid); - } - - @Override public Stats call() throws RocksDBException { - stats_.start_ = System.nanoTime(); - runTask(); - stats_.finish_ = System.nanoTime(); - return stats_; - } - - abstract protected void runTask() throws RocksDBException; - - protected int tid_; - protected Random rand_; - protected long numEntries_; - protected long keyRange_; - protected Stats stats_; - - protected void getFixedKey(byte[] key, long sn) { - generateKeyFromLong(key, sn); - } - - protected void getRandomKey(byte[] key, long range) { - generateKeyFromLong(key, Math.abs(rand_.nextLong() % range)); - } - } - - abstract class WriteTask extends BenchmarkTask { - public WriteTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch) { - super(tid, randSeed, numEntries, keyRange); - writeOpt_ = writeOpt; - entriesPerBatch_ = entriesPerBatch; - maxWritesPerSecond_ = -1; - } - - public WriteTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch, long maxWritesPerSecond) { - super(tid, randSeed, numEntries, keyRange); - writeOpt_ = writeOpt; - entriesPerBatch_ = entriesPerBatch; - maxWritesPerSecond_ = maxWritesPerSecond; - } - - @Override public void runTask() throws RocksDBException { - if (numEntries_ != DbBenchmark.this.num_) { - stats_.message_.append(String.format(" (%d ops)", numEntries_)); - } - byte[] key = new byte[keySize_]; - byte[] value = new byte[valueSize_]; - - try { - if (entriesPerBatch_ == 1) { - for (long i = 0; i < numEntries_; ++i) { - getKey(key, i, keyRange_); - DbBenchmark.this.gen_.generate(value); - db_.put(writeOpt_, key, value); - stats_.finishedSingleOp(keySize_ + valueSize_); - writeRateControl(i); - if (isFinished()) { - return; - } - } - } else { - for (long i = 0; i < numEntries_; i += entriesPerBatch_) { - WriteBatch batch = new WriteBatch(); - for (long j = 0; j < entriesPerBatch_; j++) { - getKey(key, i + j, keyRange_); - DbBenchmark.this.gen_.generate(value); - batch.put(key, value); - stats_.finishedSingleOp(keySize_ + valueSize_); - } - db_.write(writeOpt_, batch); - batch.dispose(); - writeRateControl(i); - if (isFinished()) { - return; - } - } - } - } catch (InterruptedException e) { - // thread has been terminated. - } - } - - protected void writeRateControl(long writeCount) - throws InterruptedException { - if (maxWritesPerSecond_ <= 0) return; - long minInterval = - writeCount * TimeUnit.SECONDS.toNanos(1) / maxWritesPerSecond_; - long interval = System.nanoTime() - stats_.start_; - if (minInterval - interval > TimeUnit.MILLISECONDS.toNanos(1)) { - TimeUnit.NANOSECONDS.sleep(minInterval - interval); - } - } - - abstract protected void getKey(byte[] key, long id, long range); - protected WriteOptions writeOpt_; - protected long entriesPerBatch_; - protected long maxWritesPerSecond_; - } - - class WriteSequentialTask extends WriteTask { - public WriteSequentialTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch) { - super(tid, randSeed, numEntries, keyRange, - writeOpt, entriesPerBatch); - } - public WriteSequentialTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch, - long maxWritesPerSecond) { - super(tid, randSeed, numEntries, keyRange, - writeOpt, entriesPerBatch, - maxWritesPerSecond); - } - @Override protected void getKey(byte[] key, long id, long range) { - getFixedKey(key, id); - } - } - - class WriteRandomTask extends WriteTask { - public WriteRandomTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch) { - super(tid, randSeed, numEntries, keyRange, - writeOpt, entriesPerBatch); - } - public WriteRandomTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch, - long maxWritesPerSecond) { - super(tid, randSeed, numEntries, keyRange, - writeOpt, entriesPerBatch, - maxWritesPerSecond); - } - @Override protected void getKey(byte[] key, long id, long range) { - getRandomKey(key, range); - } - } - - class WriteUniqueRandomTask extends WriteTask { - static final int MAX_BUFFER_SIZE = 10000000; - public WriteUniqueRandomTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch) { - super(tid, randSeed, numEntries, keyRange, - writeOpt, entriesPerBatch); - initRandomKeySequence(); - } - public WriteUniqueRandomTask( - int tid, long randSeed, long numEntries, long keyRange, - WriteOptions writeOpt, long entriesPerBatch, - long maxWritesPerSecond) { - super(tid, randSeed, numEntries, keyRange, - writeOpt, entriesPerBatch, - maxWritesPerSecond); - initRandomKeySequence(); - } - @Override protected void getKey(byte[] key, long id, long range) { - generateKeyFromLong(key, nextUniqueRandom()); - } - - protected void initRandomKeySequence() { - bufferSize_ = MAX_BUFFER_SIZE; - if (bufferSize_ > keyRange_) { - bufferSize_ = (int) keyRange_; - } - currentKeyCount_ = bufferSize_; - keyBuffer_ = new long[MAX_BUFFER_SIZE]; - for (int k = 0; k < bufferSize_; ++k) { - keyBuffer_[k] = k; - } - } - - /** - * Semi-randomly return the next unique key. It is guaranteed to be - * fully random if keyRange_ <= MAX_BUFFER_SIZE. - */ - long nextUniqueRandom() { - if (bufferSize_ == 0) { - System.err.println("bufferSize_ == 0."); - return 0; - } - int r = rand_.nextInt(bufferSize_); - // randomly pick one from the keyBuffer - long randKey = keyBuffer_[r]; - if (currentKeyCount_ < keyRange_) { - // if we have not yet inserted all keys, insert next new key to [r]. - keyBuffer_[r] = currentKeyCount_++; - } else { - // move the last element to [r] and decrease the size by 1. - keyBuffer_[r] = keyBuffer_[--bufferSize_]; - } - return randKey; - } - - int bufferSize_; - long currentKeyCount_; - long[] keyBuffer_; - } - - class ReadRandomTask extends BenchmarkTask { - public ReadRandomTask( - int tid, long randSeed, long numEntries, long keyRange) { - super(tid, randSeed, numEntries, keyRange); - } - @Override public void runTask() throws RocksDBException { - byte[] key = new byte[keySize_]; - byte[] value = new byte[valueSize_]; - for (long i = 0; i < numEntries_; i++) { - getRandomKey(key, keyRange_); - int len = db_.get(key, value); - if (len != RocksDB.NOT_FOUND) { - stats_.found_++; - stats_.finishedSingleOp(keySize_ + valueSize_); - } else { - stats_.finishedSingleOp(keySize_); - } - if (isFinished()) { - return; - } - } - } - } - - class ReadSequentialTask extends BenchmarkTask { - public ReadSequentialTask( - int tid, long randSeed, long numEntries, long keyRange) { - super(tid, randSeed, numEntries, keyRange); - } - @Override public void runTask() throws RocksDBException { - RocksIterator iter = db_.newIterator(); - long i; - for (iter.seekToFirst(), i = 0; - iter.isValid() && i < numEntries_; - iter.next(), ++i) { - stats_.found_++; - stats_.finishedSingleOp(iter.key().length + iter.value().length); - if (isFinished()) { - iter.dispose(); - return; - } - } - iter.dispose(); - } - } - - public DbBenchmark(Map flags) throws Exception { - benchmarks_ = (List) flags.get(Flag.benchmarks); - num_ = (Integer) flags.get(Flag.num); - threadNum_ = (Integer) flags.get(Flag.threads); - reads_ = (Integer) (flags.get(Flag.reads) == null ? - flags.get(Flag.num) : flags.get(Flag.reads)); - keySize_ = (Integer) flags.get(Flag.key_size); - valueSize_ = (Integer) flags.get(Flag.value_size); - compressionRatio_ = (Double) flags.get(Flag.compression_ratio); - useExisting_ = (Boolean) flags.get(Flag.use_existing_db); - randSeed_ = (Long) flags.get(Flag.seed); - databaseDir_ = (String) flags.get(Flag.db); - writesPerSeconds_ = (Integer) flags.get(Flag.writes_per_second); - memtable_ = (String) flags.get(Flag.memtablerep); - maxWriteBufferNumber_ = (Integer) flags.get(Flag.max_write_buffer_number); - prefixSize_ = (Integer) flags.get(Flag.prefix_size); - keysPerPrefix_ = (Integer) flags.get(Flag.keys_per_prefix); - hashBucketCount_ = (Long) flags.get(Flag.hash_bucket_count); - usePlainTable_ = (Boolean) flags.get(Flag.use_plain_table); - useMemenv_ = (Boolean) flags.get(Flag.use_mem_env); - flags_ = flags; - finishLock_ = new Object(); - // options.setPrefixSize((Integer)flags_.get(Flag.prefix_size)); - // options.setKeysPerPrefix((Long)flags_.get(Flag.keys_per_prefix)); - compressionType_ = (String) flags.get(Flag.compression_type); - compression_ = CompressionType.NO_COMPRESSION; - try { - if (compressionType_!=null) { - final CompressionType compressionType = - CompressionType.getCompressionType(compressionType_); - if (compressionType != null && - compressionType != CompressionType.NO_COMPRESSION) { - System.loadLibrary(compressionType.getLibraryName()); - } - - } - } catch (UnsatisfiedLinkError e) { - System.err.format("Unable to load %s library:%s%n" + - "No compression is used.%n", - compressionType_, e.toString()); - compressionType_ = "none"; - } - gen_ = new RandomGenerator(randSeed_, compressionRatio_); - } - - private void prepareReadOptions(ReadOptions options) { - options.setVerifyChecksums((Boolean)flags_.get(Flag.verify_checksum)); - options.setTailing((Boolean)flags_.get(Flag.use_tailing_iterator)); - } - - private void prepareWriteOptions(WriteOptions options) { - options.setSync((Boolean)flags_.get(Flag.sync)); - options.setDisableWAL((Boolean)flags_.get(Flag.disable_wal)); - } - - private void prepareOptions(Options options) throws RocksDBException { - if (!useExisting_) { - options.setCreateIfMissing(true); - } else { - options.setCreateIfMissing(false); - } - if (useMemenv_) { - options.setEnv(new RocksMemEnv(Env.getDefault())); - } - switch (memtable_) { - case "skip_list": - options.setMemTableConfig(new SkipListMemTableConfig()); - break; - case "vector": - options.setMemTableConfig(new VectorMemTableConfig()); - break; - case "hash_linkedlist": - options.setMemTableConfig( - new HashLinkedListMemTableConfig() - .setBucketCount(hashBucketCount_)); - options.useFixedLengthPrefixExtractor(prefixSize_); - break; - case "hash_skiplist": - case "prefix_hash": - options.setMemTableConfig( - new HashSkipListMemTableConfig() - .setBucketCount(hashBucketCount_)); - options.useFixedLengthPrefixExtractor(prefixSize_); - break; - default: - System.err.format( - "unable to detect the specified memtable, " + - "use the default memtable factory %s%n", - options.memTableFactoryName()); - break; - } - if (usePlainTable_) { - options.setTableFormatConfig( - new PlainTableConfig().setKeySize(keySize_)); - } else { - BlockBasedTableConfig table_options = new BlockBasedTableConfig(); - table_options.setBlockSize((Long)flags_.get(Flag.block_size)) - .setBlockCacheSize((Long)flags_.get(Flag.cache_size)) - .setCacheNumShardBits( - (Integer)flags_.get(Flag.cache_numshardbits)); - options.setTableFormatConfig(table_options); - } - options.setWriteBufferSize( - (Long)flags_.get(Flag.write_buffer_size)); - options.setMaxWriteBufferNumber( - (Integer)flags_.get(Flag.max_write_buffer_number)); - options.setMaxBackgroundCompactions( - (Integer)flags_.get(Flag.max_background_compactions)); - options.getEnv().setBackgroundThreads( - (Integer)flags_.get(Flag.max_background_compactions)); - options.setMaxBackgroundFlushes( - (Integer)flags_.get(Flag.max_background_flushes)); - options.setMaxBackgroundJobs((Integer) flags_.get(Flag.max_background_jobs)); - options.setMaxOpenFiles( - (Integer)flags_.get(Flag.open_files)); - options.setUseFsync( - (Boolean)flags_.get(Flag.use_fsync)); - options.setWalDir( - (String)flags_.get(Flag.wal_dir)); - options.setDeleteObsoleteFilesPeriodMicros( - (Integer)flags_.get(Flag.delete_obsolete_files_period_micros)); - options.setTableCacheNumshardbits( - (Integer)flags_.get(Flag.table_cache_numshardbits)); - options.setAllowMmapReads( - (Boolean)flags_.get(Flag.mmap_read)); - options.setAllowMmapWrites( - (Boolean)flags_.get(Flag.mmap_write)); - options.setAdviseRandomOnOpen( - (Boolean)flags_.get(Flag.advise_random_on_open)); - options.setUseAdaptiveMutex( - (Boolean)flags_.get(Flag.use_adaptive_mutex)); - options.setBytesPerSync( - (Long)flags_.get(Flag.bytes_per_sync)); - options.setBloomLocality( - (Integer)flags_.get(Flag.bloom_locality)); - options.setMinWriteBufferNumberToMerge( - (Integer)flags_.get(Flag.min_write_buffer_number_to_merge)); - options.setMemtablePrefixBloomSizeRatio((Double) flags_.get(Flag.memtable_bloom_size_ratio)); - options.setMemtableWholeKeyFiltering((Boolean) flags_.get(Flag.memtable_whole_key_filtering)); - options.setNumLevels( - (Integer)flags_.get(Flag.num_levels)); - options.setTargetFileSizeBase( - (Integer)flags_.get(Flag.target_file_size_base)); - options.setTargetFileSizeMultiplier((Integer)flags_.get(Flag.target_file_size_multiplier)); - options.setMaxBytesForLevelBase( - (Integer)flags_.get(Flag.max_bytes_for_level_base)); - options.setMaxBytesForLevelMultiplier((Double) flags_.get(Flag.max_bytes_for_level_multiplier)); - options.setLevelZeroStopWritesTrigger( - (Integer)flags_.get(Flag.level0_stop_writes_trigger)); - options.setLevelZeroSlowdownWritesTrigger( - (Integer)flags_.get(Flag.level0_slowdown_writes_trigger)); - options.setLevelZeroFileNumCompactionTrigger( - (Integer)flags_.get(Flag.level0_file_num_compaction_trigger)); - options.setMaxCompactionBytes( - (Long) flags_.get(Flag.max_compaction_bytes)); - options.setDisableAutoCompactions( - (Boolean)flags_.get(Flag.disable_auto_compactions)); - options.setMaxSuccessiveMerges( - (Integer)flags_.get(Flag.max_successive_merges)); - options.setWalTtlSeconds((Long)flags_.get(Flag.wal_ttl_seconds)); - options.setWalSizeLimitMB((Long)flags_.get(Flag.wal_size_limit_MB)); - if(flags_.get(Flag.java_comparator) != null) { - options.setComparator( - (AbstractComparator)flags_.get(Flag.java_comparator)); - } - - /* TODO(yhchiang): enable the following parameters - options.setCompressionType((String)flags_.get(Flag.compression_type)); - options.setCompressionLevel((Integer)flags_.get(Flag.compression_level)); - options.setMinLevelToCompress((Integer)flags_.get(Flag.min_level_to_compress)); - options.setStatistics((Boolean)flags_.get(Flag.statistics)); - options.setUniversalSizeRatio( - (Integer)flags_.get(Flag.universal_size_ratio)); - options.setUniversalMinMergeWidth( - (Integer)flags_.get(Flag.universal_min_merge_width)); - options.setUniversalMaxMergeWidth( - (Integer)flags_.get(Flag.universal_max_merge_width)); - options.setUniversalMaxSizeAmplificationPercent( - (Integer)flags_.get(Flag.universal_max_size_amplification_percent)); - options.setUniversalCompressionSizePercent( - (Integer)flags_.get(Flag.universal_compression_size_percent)); - // TODO(yhchiang): add RocksDB.openForReadOnly() to enable Flag.readonly - // TODO(yhchiang): enable Flag.merge_operator by switch - options.setAccessHintOnCompactionStart( - (String)flags_.get(Flag.compaction_fadvice)); - // available values of fadvice are "NONE", "NORMAL", "SEQUENTIAL", "WILLNEED" for fadvice - */ - } - - private void run() throws RocksDBException { - if (!useExisting_) { - destroyDb(); - } - Options options = new Options(); - prepareOptions(options); - open(options); - - printHeader(options); - - for (String benchmark : benchmarks_) { - List> tasks = new ArrayList>(); - List> bgTasks = new ArrayList>(); - WriteOptions writeOpt = new WriteOptions(); - prepareWriteOptions(writeOpt); - ReadOptions readOpt = new ReadOptions(); - prepareReadOptions(readOpt); - int currentTaskId = 0; - boolean known = true; - - switch (benchmark) { - case "fillseq": - tasks.add(new WriteSequentialTask( - currentTaskId++, randSeed_, num_, num_, writeOpt, 1)); - break; - case "fillbatch": - tasks.add( - new WriteSequentialTask(currentTaskId++, randSeed_, num_, num_, writeOpt, 1000)); - break; - case "fillrandom": - tasks.add(new WriteRandomTask( - currentTaskId++, randSeed_, num_, num_, writeOpt, 1)); - break; - case "filluniquerandom": - tasks.add(new WriteUniqueRandomTask( - currentTaskId++, randSeed_, num_, num_, writeOpt, 1)); - break; - case "fillsync": - writeOpt.setSync(true); - tasks.add(new WriteRandomTask( - currentTaskId++, randSeed_, num_ / 1000, num_ / 1000, - writeOpt, 1)); - break; - case "readseq": - for (int t = 0; t < threadNum_; ++t) { - tasks.add(new ReadSequentialTask( - currentTaskId++, randSeed_, reads_ / threadNum_, num_)); - } - break; - case "readrandom": - for (int t = 0; t < threadNum_; ++t) { - tasks.add(new ReadRandomTask( - currentTaskId++, randSeed_, reads_ / threadNum_, num_)); - } - break; - case "readwhilewriting": - WriteTask writeTask = new WriteRandomTask( - -1, randSeed_, Long.MAX_VALUE, num_, writeOpt, 1, writesPerSeconds_); - writeTask.stats_.setExcludeFromMerge(); - bgTasks.add(writeTask); - for (int t = 0; t < threadNum_; ++t) { - tasks.add(new ReadRandomTask( - currentTaskId++, randSeed_, reads_ / threadNum_, num_)); - } - break; - case "readhot": - for (int t = 0; t < threadNum_; ++t) { - tasks.add(new ReadRandomTask( - currentTaskId++, randSeed_, reads_ / threadNum_, num_ / 100)); - } - break; - case "delete": - destroyDb(); - open(options); - break; - default: - known = false; - System.err.println("Unknown benchmark: " + benchmark); - break; - } - if (known) { - ExecutorService executor = Executors.newCachedThreadPool(); - ExecutorService bgExecutor = Executors.newCachedThreadPool(); - try { - // measure only the main executor time - List> bgResults = new ArrayList>(); - for (Callable bgTask : bgTasks) { - bgResults.add(bgExecutor.submit(bgTask)); - } - start(); - List> results = executor.invokeAll(tasks); - executor.shutdown(); - boolean finished = executor.awaitTermination(10, TimeUnit.SECONDS); - if (!finished) { - System.out.format( - "Benchmark %s was not finished before timeout.", - benchmark); - executor.shutdownNow(); - } - setFinished(true); - bgExecutor.shutdown(); - finished = bgExecutor.awaitTermination(10, TimeUnit.SECONDS); - if (!finished) { - System.out.format( - "Benchmark %s was not finished before timeout.", - benchmark); - bgExecutor.shutdownNow(); - } - - stop(benchmark, results, currentTaskId); - } catch (InterruptedException e) { - System.err.println(e); - } - } - writeOpt.dispose(); - readOpt.dispose(); - } - options.dispose(); - db_.close(); - } - - private void printHeader(Options options) { - int kKeySize = 16; - System.out.printf("Keys: %d bytes each\n", kKeySize); - System.out.printf("Values: %d bytes each (%d bytes after compression)\n", - valueSize_, - (int) (valueSize_ * compressionRatio_ + 0.5)); - System.out.printf("Entries: %d\n", num_); - System.out.printf("RawSize: %.1f MB (estimated)\n", - ((double)(kKeySize + valueSize_) * num_) / SizeUnit.MB); - System.out.printf("FileSize: %.1f MB (estimated)\n", - (((kKeySize + valueSize_ * compressionRatio_) * num_) / SizeUnit.MB)); - System.out.format("Memtable Factory: %s%n", options.memTableFactoryName()); - System.out.format("Prefix: %d bytes%n", prefixSize_); - System.out.format("Compression: %s%n", compressionType_); - printWarnings(); - System.out.printf("------------------------------------------------\n"); - } - - void printWarnings() { - boolean assertsEnabled = false; - assert assertsEnabled = true; // Intentional side effect!!! - if (assertsEnabled) { - System.out.printf( - "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n"); - } - } - - private void open(Options options) throws RocksDBException { - System.out.println("Using database directory: " + databaseDir_); - db_ = RocksDB.open(options, databaseDir_); - } - - private void start() { - setFinished(false); - startTime_ = System.nanoTime(); - } - - private void stop( - String benchmark, List> results, int concurrentThreads) { - long endTime = System.nanoTime(); - double elapsedSeconds = - 1.0d * (endTime - startTime_) / TimeUnit.SECONDS.toNanos(1); - - Stats stats = new Stats(-1); - int taskFinishedCount = 0; - for (Future result : results) { - if (result.isDone()) { - try { - Stats taskStats = result.get(3, TimeUnit.SECONDS); - if (!result.isCancelled()) { - taskFinishedCount++; - } - stats.merge(taskStats); - } catch (Exception e) { - // then it's not successful, the output will indicate this - } - } - } - String extra = ""; - if (benchmark.indexOf("read") >= 0) { - extra = String.format(" %d / %d found; ", stats.found_, stats.done_); - } else { - extra = String.format(" %d ops done; ", stats.done_); - } - - System.out.printf( - "%-16s : %11.5f micros/op; %6.1f MB/s;%s %d / %d task(s) finished.\n", - benchmark, elapsedSeconds / stats.done_ * 1e6, - (stats.bytes_ / 1048576.0) / elapsedSeconds, extra, - taskFinishedCount, concurrentThreads); - } - - public void generateKeyFromLong(byte[] slice, long n) { - assert(n >= 0); - int startPos = 0; - - if (keysPerPrefix_ > 0) { - long numPrefix = (num_ + keysPerPrefix_ - 1) / keysPerPrefix_; - long prefix = n % numPrefix; - int bytesToFill = Math.min(prefixSize_, 8); - for (int i = 0; i < bytesToFill; ++i) { - slice[i] = (byte) (prefix % 256); - prefix /= 256; - } - for (int i = 8; i < bytesToFill; ++i) { - slice[i] = '0'; - } - startPos = bytesToFill; - } - - for (int i = slice.length - 1; i >= startPos; --i) { - slice[i] = (byte) ('0' + (n % 10)); - n /= 10; - } - } - - private void destroyDb() { - if (db_ != null) { - db_.close(); - } - // TODO(yhchiang): develop our own FileUtil - // FileUtil.deleteDir(databaseDir_); - } - - private void printStats() { - } - - static void printHelp() { - System.out.println("usage:"); - for (Flag flag : Flag.values()) { - System.out.format(" --%s%n\t%s%n", - flag.name(), - flag.desc()); - if (flag.getDefaultValue() != null) { - System.out.format("\tDEFAULT: %s%n", - flag.getDefaultValue().toString()); - } - } - } - - public static void main(String[] args) throws Exception { - Map flags = new EnumMap(Flag.class); - for (Flag flag : Flag.values()) { - if (flag.getDefaultValue() != null) { - flags.put(flag, flag.getDefaultValue()); - } - } - for (String arg : args) { - boolean valid = false; - if (arg.equals("--help") || arg.equals("-h")) { - printHelp(); - System.exit(0); - } - if (arg.startsWith("--")) { - try { - String[] parts = arg.substring(2).split("="); - if (parts.length >= 1) { - Flag key = Flag.valueOf(parts[0]); - if (key != null) { - Object value = null; - if (parts.length >= 2) { - value = key.parseValue(parts[1]); - } - flags.put(key, value); - valid = true; - } - } - } - catch (Exception e) { - } - } - if (!valid) { - System.err.println("Invalid argument " + arg); - System.exit(1); - } - } - new DbBenchmark(flags).run(); - } - - private enum Flag { - benchmarks(Arrays.asList("fillseq", "readrandom", "fillrandom"), - "Comma-separated list of operations to run in the specified order\n" - + "\tActual benchmarks:\n" - + "\t\tfillseq -- write N values in sequential key order in async mode.\n" - + "\t\tfillrandom -- write N values in random key order in async mode.\n" - + "\t\tfillbatch -- write N/1000 batch where each batch has 1000 values\n" - + "\t\t in sequential key order in sync mode.\n" - + "\t\tfillsync -- write N/100 values in random key order in sync mode.\n" - + "\t\tfill100K -- write N/1000 100K values in random order in async mode.\n" - + "\t\treadseq -- read N times sequentially.\n" - + "\t\treadrandom -- read N times in random order.\n" - + "\t\treadhot -- read N times in random order from 1% section of DB.\n" - + "\t\treadwhilewriting -- measure the read performance of multiple readers\n" - + "\t\t with a bg single writer. The write rate of the bg\n" - + "\t\t is capped by --writes_per_second.\n" - + "\tMeta Operations:\n" - + "\t\tdelete -- delete DB") { - @Override public Object parseValue(String value) { - return new ArrayList(Arrays.asList(value.split(","))); - } - }, - compression_ratio(0.5d, - "Arrange to generate values that shrink to this fraction of\n" + - "\ttheir original size after compression.") { - @Override public Object parseValue(String value) { - return Double.parseDouble(value); - } - }, - use_existing_db(false, - "If true, do not destroy the existing database. If you set this\n" + - "\tflag and also specify a benchmark that wants a fresh database,\n" + - "\tthat benchmark will fail.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - num(1000000, - "Number of key/values to place in database.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - threads(1, - "Number of concurrent threads to run.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - reads(null, - "Number of read operations to do. If negative, do --nums reads.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - key_size(16, - "The size of each key in bytes.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - value_size(100, - "The size of each value in bytes.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - write_buffer_size(4L * SizeUnit.MB, - "Number of bytes to buffer in memtable before compacting\n" + - "\t(initialized to default value by 'main'.)") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - max_write_buffer_number(2, - "The number of in-memory memtables. Each memtable is of size\n" + - "\twrite_buffer_size.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - prefix_size(0, "Controls the prefix size for HashSkipList, HashLinkedList,\n" + - "\tand plain table.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - keys_per_prefix(0, "Controls the average number of keys generated\n" + - "\tper prefix, 0 means no special handling of the prefix,\n" + - "\ti.e. use the prefix comes with the generated random number.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - memtablerep("skip_list", - "The memtable format. Available options are\n" + - "\tskip_list,\n" + - "\tvector,\n" + - "\thash_linkedlist,\n" + - "\thash_skiplist (prefix_hash.)") { - @Override public Object parseValue(String value) { - return value; - } - }, - hash_bucket_count(SizeUnit.MB, - "The number of hash buckets used in the hash-bucket-based\n" + - "\tmemtables. Memtables that currently support this argument are\n" + - "\thash_linkedlist and hash_skiplist.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - writes_per_second(10000, - "The write-rate of the background writer used in the\n" + - "\t`readwhilewriting` benchmark. Non-positive number indicates\n" + - "\tusing an unbounded write-rate in `readwhilewriting` benchmark.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - use_plain_table(false, - "Use plain-table sst format.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - cache_size(-1L, - "Number of bytes to use as a cache of uncompressed data.\n" + - "\tNegative means use default settings.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - seed(0L, - "Seed base for random number generators.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - num_levels(7, - "The total number of levels.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - numdistinct(1000L, - "Number of distinct keys to use. Used in RandomWithVerify to\n" + - "\tread/write on fewer keys so that gets are more likely to find the\n" + - "\tkey and puts are more likely to update the same key.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - merge_keys(-1L, - "Number of distinct keys to use for MergeRandom and\n" + - "\tReadRandomMergeRandom.\n" + - "\tIf negative, there will be FLAGS_num keys.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - bloom_locality(0,"Control bloom filter probes locality.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - duration(0,"Time in seconds for the random-ops tests to run.\n" + - "\tWhen 0 then num & reads determine the test duration.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - num_multi_db(0, - "Number of DBs used in the benchmark. 0 means single DB.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - histogram(false,"Print histogram of operation timings.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - min_write_buffer_number_to_merge( - defaultOptions_.minWriteBufferNumberToMerge(), - "The minimum number of write buffers that will be merged together\n" + - "\tbefore writing to storage. This is cheap because it is an\n" + - "\tin-memory merge. If this feature is not enabled, then all these\n" + - "\twrite buffers are flushed to L0 as separate files and this\n" + - "\tincreases read amplification because a get request has to check\n" + - "\tin all of these files. Also, an in-memory merge may result in\n" + - "\twriting less data to storage if there are duplicate records\n" + - "\tin each of these individual write buffers.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - max_background_compactions( - defaultOptions_.maxBackgroundCompactions(), - "The maximum number of concurrent background compactions\n" + - "\tthat can occur in parallel.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - max_background_flushes( - defaultOptions_.maxBackgroundFlushes(), - "The maximum number of concurrent background flushes\n" + - "\tthat can occur in parallel.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - max_background_jobs(defaultOptions_.maxBackgroundJobs(), - "The maximum number of concurrent background jobs\n" - + "\tthat can occur in parallel.") { - @Override - public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - /* TODO(yhchiang): enable the following - compaction_style((int32_t) defaultOptions_.compactionStyle(), - "style of compaction: level-based vs universal.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - },*/ - universal_size_ratio(0, - "Percentage flexibility while comparing file size\n" + - "\t(for universal compaction only).") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - universal_min_merge_width(0,"The minimum number of files in a\n" + - "\tsingle compaction run (for universal compaction only).") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - universal_max_merge_width(0,"The max number of files to compact\n" + - "\tin universal style compaction.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - universal_max_size_amplification_percent(0, - "The max size amplification for universal style compaction.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - universal_compression_size_percent(-1, - "The percentage of the database to compress for universal\n" + - "\tcompaction. -1 means compress everything.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - block_size(defaultBlockBasedTableOptions_.blockSize(), - "Number of bytes in a block.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - compressed_cache_size(-1L, - "Number of bytes to use as a cache of compressed data.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - open_files(defaultOptions_.maxOpenFiles(), - "Maximum number of files to keep open at the same time\n" + - "\t(use default if == 0)") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - bloom_bits(-1,"Bloom filter bits per key. Negative means\n" + - "\tuse default settings.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - memtable_bloom_size_ratio(0.0d, "Ratio of memtable used by the bloom filter.\n" - + "\t0 means no bloom filter.") { - @Override public Object parseValue(String value) { - return Double.parseDouble(value); - } - }, - memtable_whole_key_filtering(false, "Enable whole key bloom filter in memtable.") { - @Override - public Object parseValue(String value) { - return parseBoolean(value); - } - }, - cache_numshardbits(-1,"Number of shards for the block cache\n" + - "\tis 2 ** cache_numshardbits. Negative means use default settings.\n" + - "\tThis is applied only if FLAGS_cache_size is non-negative.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - verify_checksum(false,"Verify checksum for every block read\n" + - "\tfrom storage.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - statistics(false,"Database statistics.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - writes(-1L, "Number of write operations to do. If negative, do\n" + - "\t--num reads.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - sync(false,"Sync all writes to disk.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - use_fsync(false,"If true, issue fsync instead of fdatasync.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - disable_wal(false,"If true, do not write WAL for write.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - wal_dir("", "If not empty, use the given dir for WAL.") { - @Override public Object parseValue(String value) { - return value; - } - }, - target_file_size_base(2 * 1048576,"Target file size at level-1") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - target_file_size_multiplier(1, - "A multiplier to compute target level-N file size (N >= 2)") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - max_bytes_for_level_base(10 * 1048576, - "Max bytes for level-1") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - max_bytes_for_level_multiplier(10.0d, - "A multiplier to compute max bytes for level-N (N >= 2)") { - @Override public Object parseValue(String value) { - return Double.parseDouble(value); - } - }, - level0_stop_writes_trigger(12,"Number of files in level-0\n" + - "\tthat will trigger put stop.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - level0_slowdown_writes_trigger(8,"Number of files in level-0\n" + - "\tthat will slow down writes.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - level0_file_num_compaction_trigger(4,"Number of files in level-0\n" + - "\twhen compactions start.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - readwritepercent(90,"Ratio of reads to reads/writes (expressed\n" + - "\tas percentage) for the ReadRandomWriteRandom workload. The\n" + - "\tdefault value 90 means 90% operations out of all reads and writes\n" + - "\toperations are reads. In other words, 9 gets for every 1 put.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - mergereadpercent(70,"Ratio of merges to merges&reads (expressed\n" + - "\tas percentage) for the ReadRandomMergeRandom workload. The\n" + - "\tdefault value 70 means 70% out of all read and merge operations\n" + - "\tare merges. In other words, 7 merges for every 3 gets.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - deletepercent(2,"Percentage of deletes out of reads/writes/\n" + - "\tdeletes (used in RandomWithVerify only). RandomWithVerify\n" + - "\tcalculates writepercent as (100 - FLAGS_readwritepercent -\n" + - "\tdeletepercent), so deletepercent must be smaller than (100 -\n" + - "\tFLAGS_readwritepercent)") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - delete_obsolete_files_period_micros(0,"Option to delete\n" + - "\tobsolete files periodically. 0 means that obsolete files are\n" + - "\tdeleted after every compaction run.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - compression_type("snappy", - "Algorithm used to compress the database.") { - @Override public Object parseValue(String value) { - return value; - } - }, - compression_level(-1, - "Compression level. For zlib this should be -1 for the\n" + - "\tdefault level, or between 0 and 9.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - min_level_to_compress(-1,"If non-negative, compression starts\n" + - "\tfrom this level. Levels with number < min_level_to_compress are\n" + - "\tnot compressed. Otherwise, apply compression_type to\n" + - "\tall levels.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - table_cache_numshardbits(4,"") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - stats_interval(0L, "Stats are reported every N operations when\n" + - "\tthis is greater than zero. When 0 the interval grows over time.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - stats_per_interval(0,"Reports additional stats per interval when\n" + - "\tthis is greater than 0.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - perf_level(0,"Level of perf collection.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - max_compaction_bytes(0L, "Limit number of bytes in one compaction to be lower than this\n" + - "\threshold. But it's not guaranteed.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - readonly(false,"Run read only benchmarks.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - disable_auto_compactions(false,"Do not auto trigger compactions.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - wal_ttl_seconds(0L,"Set the TTL for the WAL Files in seconds.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - wal_size_limit_MB(0L,"Set the size limit for the WAL Files\n" + - "\tin MB.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - /* TODO(yhchiang): enable the following - direct_reads(rocksdb::EnvOptions().use_direct_reads, - "Allow direct I/O reads.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - direct_writes(rocksdb::EnvOptions().use_direct_reads, - "Allow direct I/O reads.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - */ - mmap_read(false, - "Allow reads to occur via mmap-ing files.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - mmap_write(false, - "Allow writes to occur via mmap-ing files.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - advise_random_on_open(defaultOptions_.adviseRandomOnOpen(), - "Advise random access on table file open.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - compaction_fadvice("NORMAL", - "Access pattern advice when a file is compacted.") { - @Override public Object parseValue(String value) { - return value; - } - }, - use_tailing_iterator(false, - "Use tailing iterator to access a series of keys instead of get.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - use_adaptive_mutex(defaultOptions_.useAdaptiveMutex(), - "Use adaptive mutex.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - bytes_per_sync(defaultOptions_.bytesPerSync(), - "Allows OS to incrementally sync files to disk while they are\n" + - "\tbeing written, in the background. Issue one request for every\n" + - "\tbytes_per_sync written. 0 turns it off.") { - @Override public Object parseValue(String value) { - return Long.parseLong(value); - } - }, - filter_deletes(false," On true, deletes use bloom-filter and drop\n" + - "\tthe delete if key not present.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - max_successive_merges(0,"Maximum number of successive merge\n" + - "\toperations on a key in the memtable.") { - @Override public Object parseValue(String value) { - return Integer.parseInt(value); - } - }, - db(getTempDir("rocksdb-jni"), - "Use the db with the following name.") { - @Override public Object parseValue(String value) { - return value; - } - }, - use_mem_env(false, "Use RocksMemEnv instead of default filesystem based\n" + - "environment.") { - @Override public Object parseValue(String value) { - return parseBoolean(value); - } - }, - java_comparator(null, "Class name of a Java Comparator to use instead\n" + - "\tof the default C++ ByteWiseComparatorImpl. Must be available on\n" + - "\tthe classpath") { - @Override - protected Object parseValue(final String value) { - try { - final ComparatorOptions copt = new ComparatorOptions(); - final Class clsComparator = - (Class)Class.forName(value); - final Constructor cstr = - clsComparator.getConstructor(ComparatorOptions.class); - return cstr.newInstance(copt); - } catch(final ClassNotFoundException cnfe) { - throw new IllegalArgumentException("Java Comparator '" + value + "'" + - " not found on the classpath", cnfe); - } catch(final NoSuchMethodException nsme) { - throw new IllegalArgumentException("Java Comparator '" + value + "'" + - " does not have a public ComparatorOptions constructor", nsme); - } catch(final IllegalAccessException | InstantiationException - | InvocationTargetException ie) { - throw new IllegalArgumentException("Unable to construct Java" + - " Comparator '" + value + "'", ie); - } - } - }; - - private Flag(Object defaultValue, String desc) { - defaultValue_ = defaultValue; - desc_ = desc; - } - - public Object getDefaultValue() { - return defaultValue_; - } - - public String desc() { - return desc_; - } - - public boolean parseBoolean(String value) { - if (value.equals("1")) { - return true; - } else if (value.equals("0")) { - return false; - } - return Boolean.parseBoolean(value); - } - - protected abstract Object parseValue(String value); - - private final Object defaultValue_; - private final String desc_; - } - - private final static String DEFAULT_TEMP_DIR = "/tmp"; - - private static String getTempDir(final String dirName) { - try { - return Files.createTempDirectory(dirName).toAbsolutePath().toString(); - } catch(final IOException ioe) { - System.err.println("Unable to create temp directory, defaulting to: " + - DEFAULT_TEMP_DIR); - return DEFAULT_TEMP_DIR + File.pathSeparator + dirName; - } - } - - private static class RandomGenerator { - private final byte[] data_; - private int dataLength_; - private int position_; - private double compressionRatio_; - Random rand_; - - private RandomGenerator(long seed, double compressionRatio) { - // We use a limited amount of data over and over again and ensure - // that it is larger than the compression window (32KB), and also - byte[] value = new byte[100]; - // large enough to serve all typical value sizes we want to write. - rand_ = new Random(seed); - dataLength_ = value.length * 10000; - data_ = new byte[dataLength_]; - compressionRatio_ = compressionRatio; - int pos = 0; - while (pos < dataLength_) { - compressibleBytes(value); - System.arraycopy(value, 0, data_, pos, - Math.min(value.length, dataLength_ - pos)); - pos += value.length; - } - } - - private void compressibleBytes(byte[] value) { - int baseLength = value.length; - if (compressionRatio_ < 1.0d) { - baseLength = (int) (compressionRatio_ * value.length + 0.5); - } - if (baseLength <= 0) { - baseLength = 1; - } - int pos; - for (pos = 0; pos < baseLength; ++pos) { - value[pos] = (byte) (' ' + rand_.nextInt(95)); // ' ' .. '~' - } - while (pos < value.length) { - System.arraycopy(value, 0, value, pos, - Math.min(baseLength, value.length - pos)); - pos += baseLength; - } - } - - private void generate(byte[] value) { - if (position_ + value.length > data_.length) { - position_ = 0; - assert(value.length <= data_.length); - } - position_ += value.length; - System.arraycopy(data_, position_ - value.length, - value, 0, value.length); - } - } - - boolean isFinished() { - synchronized(finishLock_) { - return isFinished_; - } - } - - void setFinished(boolean flag) { - synchronized(finishLock_) { - isFinished_ = flag; - } - } - - RocksDB db_; - final List benchmarks_; - final int num_; - final int reads_; - final int keySize_; - final int valueSize_; - final int threadNum_; - final int writesPerSeconds_; - final long randSeed_; - final boolean useExisting_; - final String databaseDir_; - double compressionRatio_; - RandomGenerator gen_; - long startTime_; - - // env - boolean useMemenv_; - - // memtable related - final int maxWriteBufferNumber_; - final int prefixSize_; - final int keysPerPrefix_; - final String memtable_; - final long hashBucketCount_; - - // sst format related - boolean usePlainTable_; - - Object finishLock_; - boolean isFinished_; - Map flags_; - // as the scope of a static member equals to the scope of the problem, - // we let its c++ pointer to be disposed in its finalizer. - static Options defaultOptions_ = new Options(); - static BlockBasedTableConfig defaultBlockBasedTableOptions_ = - new BlockBasedTableConfig(); - String compressionType_; - CompressionType compression_; -} diff --git a/java/crossbuild/Vagrantfile b/java/crossbuild/Vagrantfile deleted file mode 100644 index 0ee50de2c..000000000 --- a/java/crossbuild/Vagrantfile +++ /dev/null @@ -1,51 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! -VAGRANTFILE_API_VERSION = "2" - -Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - - config.vm.define "linux32" do |linux32| - linux32.vm.box = "bento/centos-6.10-i386" - linux32.vm.provision :shell, path: "build-linux-centos.sh" - end - - config.vm.define "linux64" do |linux64| - linux64.vm.box = "bento/centos-6.10" - linux64.vm.provision :shell, path: "build-linux-centos.sh" - end - - config.vm.define "linux32-musl" do |musl32| - musl32.vm.box = "alpine/alpine32" - musl32.vm.box_version = "3.6.0" - musl32.vm.provision :shell, path: "build-linux-alpine.sh" - end - - config.vm.define "linux64-musl" do |musl64| - musl64.vm.box = "generic/alpine36" - - ## Should use the alpine/alpine64 box, but this issue needs to be fixed first - https://github.com/hashicorp/vagrant/issues/11218 - # musl64.vm.box = "alpine/alpine64" - # musl64.vm.box_version = "3.6.0" - - musl64.vm.provision :shell, path: "build-linux-alpine.sh" - end - - config.vm.provider "virtualbox" do |v| - v.memory = 2048 - v.cpus = 4 - v.customize ["modifyvm", :id, "--nictype1", "virtio" ] - end - - if Vagrant.has_plugin?("vagrant-cachier") - config.cache.scope = :box - end - if Vagrant.has_plugin?("vagrant-vbguest") - config.vbguest.no_install = true - end - - config.vm.synced_folder "../target", "/rocksdb-build" - config.vm.synced_folder "../..", "/rocksdb", type: "rsync" - config.vm.boot_timeout = 1200 -end diff --git a/java/crossbuild/build-linux-alpine.sh b/java/crossbuild/build-linux-alpine.sh deleted file mode 100755 index 561d34141..000000000 --- a/java/crossbuild/build-linux-alpine.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -set -e - -# update Alpine with latest versions -echo '@edge http://nl.alpinelinux.org/alpine/edge/main' >> /etc/apk/repositories -echo '@community http://nl.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories -apk update -apk upgrade - -# install CA certificates -apk add ca-certificates - -# install build tools -apk add \ - build-base \ - coreutils \ - file \ - git \ - perl \ - automake \ - autoconf \ - cmake - -# install tool dependencies for building RocksDB static library -apk add \ - curl \ - bash \ - wget \ - tar \ - openssl - -# install RocksDB dependencies -apk add \ - snappy snappy-dev \ - zlib zlib-dev \ - bzip2 bzip2-dev \ - lz4 lz4-dev \ - zstd zstd-dev \ - linux-headers \ - jemalloc jemalloc-dev - -# install OpenJDK7 -apk add openjdk7 \ - && apk add java-cacerts \ - && rm /usr/lib/jvm/java-1.7-openjdk/jre/lib/security/cacerts \ - && ln -s /etc/ssl/certs/java/cacerts /usr/lib/jvm/java-1.7-openjdk/jre/lib/security/cacerts - -# cleanup -rm -rf /var/cache/apk/* - -# puts javac in the PATH -export JAVA_HOME=/usr/lib/jvm/java-1.7-openjdk -export PATH=/usr/lib/jvm/java-1.7-openjdk/bin:$PATH - -# gflags from source -cd /tmp &&\ - git clone -b v2.0 --single-branch https://github.com/gflags/gflags.git &&\ - cd gflags &&\ - ./configure --prefix=/usr && make && make install &&\ - rm -rf /tmp/* - - -# build rocksdb -cd /rocksdb -make jclean clean -PORTABLE=1 make -j8 rocksdbjavastatic -cp /rocksdb/java/target/librocksdbjni-* /rocksdb-build -cp /rocksdb/java/target/rocksdbjni-* /rocksdb-build diff --git a/java/crossbuild/build-linux-centos.sh b/java/crossbuild/build-linux-centos.sh deleted file mode 100755 index 176e3456c..000000000 --- a/java/crossbuild/build-linux-centos.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -set -e - -# remove fixed relesever variable present in the hanscode boxes -sudo rm -f /etc/yum/vars/releasever - -# enable EPEL -sudo yum -y install epel-release - -# install all required packages for rocksdb that are available through yum -sudo yum -y install openssl java-1.7.0-openjdk-devel zlib-devel bzip2-devel lz4-devel snappy-devel libzstd-devel jemalloc-devel cmake3 - -# set up cmake3 as cmake binary -sudo alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake 10 --slave /usr/local/bin/ctest ctest /usr/bin/ctest --slave /usr/local/bin/cpack cpack /usr/bin/cpack --slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake -sudo alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake3 20 --slave /usr/local/bin/ctest ctest /usr/bin/ctest3 --slave /usr/local/bin/cpack cpack /usr/bin/cpack3 --slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake3 - -# install gcc/g++ 4.8.2 from tru/devtools-2 -sudo wget -O /etc/yum.repos.d/devtools-2.repo https://people.centos.org/tru/devtools-2/devtools-2.repo -sudo yum -y install devtoolset-2-binutils devtoolset-2-gcc devtoolset-2-gcc-c++ - -# install gflags -wget https://github.com/gflags/gflags/archive/v2.0.tar.gz -O gflags-2.0.tar.gz -tar xvfz gflags-2.0.tar.gz; cd gflags-2.0; scl enable devtoolset-2 ./configure; scl enable devtoolset-2 make; sudo make install -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - -# set java home so we can build rocksdb jars -export JAVA_HOME=/usr/lib/jvm/java-1.7.0 - -export PATH=$JAVA_HOME:/usr/local/bin:$PATH - -# build rocksdb -cd /rocksdb -scl enable devtoolset-2 'make clean-not-downloaded' -scl enable devtoolset-2 'PORTABLE=1 make -j8 rocksdbjavastatic' -cp /rocksdb/java/target/librocksdbjni-* /rocksdb-build -cp /rocksdb/java/target/rocksdbjni-* /rocksdb-build diff --git a/java/crossbuild/build-linux.sh b/java/crossbuild/build-linux.sh deleted file mode 100755 index 74178adb5..000000000 --- a/java/crossbuild/build-linux.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -# install all required packages for rocksdb -sudo apt-get update -sudo apt-get -y install git make gcc g++ libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev default-jdk - -# set java home so we can build rocksdb jars -export JAVA_HOME=$(echo /usr/lib/jvm/java-7-openjdk*) -cd /rocksdb -make jclean clean -make -j 4 rocksdbjavastatic -cp /rocksdb/java/target/librocksdbjni-* /rocksdb-build -cp /rocksdb/java/target/rocksdbjni-* /rocksdb-build -sudo shutdown -h now - diff --git a/java/crossbuild/docker-build-linux-alpine.sh b/java/crossbuild/docker-build-linux-alpine.sh deleted file mode 100755 index e3e852efe..000000000 --- a/java/crossbuild/docker-build-linux-alpine.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -set -e -#set -x - -# just in-case this is run outside Docker -mkdir -p /rocksdb-local-build - -rm -rf /rocksdb-local-build/* -cp -r /rocksdb-host/* /rocksdb-local-build -cd /rocksdb-local-build - -make clean-not-downloaded -PORTABLE=1 make -j2 rocksdbjavastatic - -cp java/target/librocksdbjni-linux*.so java/target/rocksdbjni-*-linux*.jar java/target/rocksdbjni-*-linux*.jar.sha1 /rocksdb-java-target diff --git a/java/crossbuild/docker-build-linux-centos.sh b/java/crossbuild/docker-build-linux-centos.sh deleted file mode 100755 index 16581dec7..000000000 --- a/java/crossbuild/docker-build-linux-centos.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -set -e -#set -x - -# just in-case this is run outside Docker -mkdir -p /rocksdb-local-build - -rm -rf /rocksdb-local-build/* -cp -r /rocksdb-host/* /rocksdb-local-build -cd /rocksdb-local-build - -# Use scl devtoolset if available -if hash scl 2>/dev/null; then - if scl --list | grep -q 'devtoolset-8'; then - # CentOS 6+ - scl enable devtoolset-8 'make clean-not-downloaded' - scl enable devtoolset-8 'PORTABLE=1 make -j2 rocksdbjavastatic' - elif scl --list | grep -q 'devtoolset-7'; then - # CentOS 6+ - scl enable devtoolset-7 'make clean-not-downloaded' - scl enable devtoolset-7 'PORTABLE=1 make -j2 rocksdbjavastatic' - elif scl --list | grep -q 'devtoolset-2'; then - # CentOS 5 or 6 - scl enable devtoolset-2 'make clean-not-downloaded' - scl enable devtoolset-2 'PORTABLE=1 make -j2 rocksdbjavastatic' - else - echo "Could not find devtoolset" - exit 1; - fi -else - make clean-not-downloaded - PORTABLE=1 make -j2 rocksdbjavastatic -fi - -cp java/target/librocksdbjni-linux*.so java/target/rocksdbjni-*-linux*.jar java/target/rocksdbjni-*-linux*.jar.sha1 /rocksdb-java-target - diff --git a/java/jdb_bench.sh b/java/jdb_bench.sh deleted file mode 100755 index 5dfc385e3..000000000 --- a/java/jdb_bench.sh +++ /dev/null @@ -1,13 +0,0 @@ -# shellcheck disable=SC2148 -# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -PLATFORM=64 -if [ `getconf LONG_BIT` != "64" ] -then - PLATFORM=32 -fi - -ROCKS_JAR=`find target -name rocksdbjni*.jar` - -echo "Running benchmark in $PLATFORM-Bit mode." -# shellcheck disable=SC2068 -java -server -d$PLATFORM -XX:NewSize=4m -XX:+AggressiveOpts -Djava.library.path=target -cp "${ROCKS_JAR}:benchmark/target/classes" org.rocksdb.benchmark.DbBenchmark $@ diff --git a/java/jmh/LICENSE-HEADER.txt b/java/jmh/LICENSE-HEADER.txt deleted file mode 100644 index 365ee653b..000000000 --- a/java/jmh/LICENSE-HEADER.txt +++ /dev/null @@ -1,5 +0,0 @@ -Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - This source code is licensed under both the GPLv2 (found in the - COPYING file in the root directory) and Apache 2.0 License - (found in the LICENSE.Apache file in the root directory). - diff --git a/java/jmh/README.md b/java/jmh/README.md deleted file mode 100644 index 1575ab517..000000000 --- a/java/jmh/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# JMH Benchmarks for RocksJava - -These are micro-benchmarks for RocksJava functionality, using [JMH (Java Microbenchmark Harness)](https://openjdk.java.net/projects/code-tools/jmh/). - -## Compiling - -**Note**: This uses a specific build of RocksDB that is set in the `` element of the `dependencies` section of the `pom.xml` file. If you are testing local changes you should build and install a SNAPSHOT version of rocksdbjni, and update the `pom.xml` of rocksdbjni-jmh file to test with this. - -For instance, this is how to install the OSX jar you just built for 6.26.0 - -```bash -$ mvn install:install-file -Dfile=./java/target/rocksdbjni-6.26.0-SNAPSHOT-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=6.26.0-SNAPSHOT -Dpackaging=jar -``` - -```bash -$ mvn package -``` - -## Running -```bash -$ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -``` - -NOTE: you can append `-help` to the command above to see all of the JMH runtime options. diff --git a/java/jmh/pom.xml b/java/jmh/pom.xml deleted file mode 100644 index 3016aefa7..000000000 --- a/java/jmh/pom.xml +++ /dev/null @@ -1,138 +0,0 @@ - - - 4.0.0 - - org.rocksdb - rocksdbjni-jmh - 1.0-SNAPSHOT - - http://rocksdb.org/ - - rocksdbjni-jmh - JMH Benchmarks for RocksDB Java API - - - Facebook, Inc. - https://www.facebook.com - - - - - Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - - GNU General Public License, version 2 - http://www.gnu.org/licenses/gpl-2.0.html - repo - - - - - scm:git:git://github.com/facebook/rocksdb.git - scm:git:git@github.com:facebook/rocksdb.git - http://github.com/facebook/rocksdb/ - - - - 1.7 - 1.7 - UTF-8 - - 1.22 - benchmarks - - - - - org.rocksdb - rocksdbjni - 7.9.0-SNAPSHOT - - - - org.openjdk.jmh - jmh-core - ${jmh.version} - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - provided - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${project.build.source} - ${project.build.target} - ${project.build.sourceEncoding} - - - - - com.mycila - license-maven-plugin - 3.0 - true - -

    LICENSE-HEADER.txt
    - true - true - true - - pom.xml - - ${project.build.sourceEncoding} - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.1 - - - package - - shade - - - ${project.artifactId}-${project.version}-${uberjar.name} - - - org.openjdk.jmh.Main - - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - - - \ No newline at end of file diff --git a/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java b/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java deleted file mode 100644 index 1973b5487..000000000 --- a/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - * This source code is licensed under both the GPLv2 (found in the - * COPYING file in the root directory) and Apache 2.0 License - * (found in the LICENSE.Apache file in the root directory). - */ -package org.rocksdb.jmh; - -import org.openjdk.jmh.annotations.*; -import org.rocksdb.*; -import org.rocksdb.util.BytewiseComparator; -import org.rocksdb.util.FileUtils; -import org.rocksdb.util.ReverseBytewiseComparator; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.rocksdb.util.KVUtils.ba; - -@State(Scope.Benchmark) -public class ComparatorBenchmarks { - - @Param({ - "native_bytewise", - "native_reverse_bytewise", - - "java_bytewise_non-direct_reused-64_adaptive-mutex", - "java_bytewise_non-direct_reused-64_non-adaptive-mutex", - "java_bytewise_non-direct_reused-64_thread-local", - "java_bytewise_direct_reused-64_adaptive-mutex", - "java_bytewise_direct_reused-64_non-adaptive-mutex", - "java_bytewise_direct_reused-64_thread-local", - "java_bytewise_non-direct_no-reuse", - "java_bytewise_direct_no-reuse", - - "java_reverse_bytewise_non-direct_reused-64_adaptive-mutex", - "java_reverse_bytewise_non-direct_reused-64_non-adaptive-mutex", - "java_reverse_bytewise_non-direct_reused-64_thread-local", - "java_reverse_bytewise_direct_reused-64_adaptive-mutex", - "java_reverse_bytewise_direct_reused-64_non-adaptive-mutex", - "java_reverse_bytewise_direct_reused-64_thread-local", - "java_reverse_bytewise_non-direct_no-reuse", - "java_reverse_bytewise_direct_no-reuse" - }) - public String comparatorName; - - Path dbDir; - ComparatorOptions comparatorOptions; - AbstractComparator comparator; - Options options; - RocksDB db; - - @Setup(Level.Trial) - public void setup() throws IOException, RocksDBException { - RocksDB.loadLibrary(); - - dbDir = Files.createTempDirectory("rocksjava-comparator-benchmarks"); - - options = new Options() - .setCreateIfMissing(true); - - if ("native_bytewise".equals(comparatorName)) { - options.setComparator(BuiltinComparator.BYTEWISE_COMPARATOR); - - } else if ("native_reverse_bytewise".equals(comparatorName)) { - options.setComparator(BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR); - - } else if (comparatorName.startsWith("java_")) { - comparatorOptions = new ComparatorOptions(); - - if (comparatorName.indexOf("non-direct") > -1) { - comparatorOptions.setUseDirectBuffer(false); - } else if (comparatorName.indexOf("direct") > -1) { - comparatorOptions.setUseDirectBuffer(true); - } - - if (comparatorName.indexOf("no-reuse") > -1) { - comparatorOptions.setMaxReusedBufferSize(-1); - } else if (comparatorName.indexOf("_reused-") > -1) { - final int idx = comparatorName.indexOf("_reused-"); - String s = comparatorName.substring(idx + 8); - s = s.substring(0, s.indexOf('_')); - comparatorOptions.setMaxReusedBufferSize(Integer.parseInt(s)); - } - - if (comparatorName.indexOf("non-adaptive-mutex") > -1) { - comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.MUTEX); - } else if (comparatorName.indexOf("adaptive-mutex") > -1) { - comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.ADAPTIVE_MUTEX); - } else if (comparatorName.indexOf("thread-local") > -1) { - comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.THREAD_LOCAL); - } - - if (comparatorName.startsWith("java_bytewise")) { - comparator = new BytewiseComparator(comparatorOptions); - } else if (comparatorName.startsWith("java_reverse_bytewise")) { - comparator = new ReverseBytewiseComparator(comparatorOptions); - } - - options.setComparator(comparator); - - } else { - throw new IllegalArgumentException("Unknown comparatorName: " + comparatorName); - } - - db = RocksDB.open(options, dbDir.toAbsolutePath().toString()); - } - - @TearDown(Level.Trial) - public void cleanup() throws IOException { - db.close(); - if (comparator != null) { - comparator.close(); - } - if (comparatorOptions != null) { - comparatorOptions.close(); - } - options.close(); - FileUtils.delete(dbDir); - } - - @State(Scope.Benchmark) - public static class Counter { - private final AtomicInteger count = new AtomicInteger(); - - public int next() { - return count.getAndIncrement(); - } - } - - - @Benchmark - public void put(final Counter counter) throws RocksDBException { - final int i = counter.next(); - db.put(ba("key" + i), ba("value" + i)); - } -} diff --git a/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java b/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java deleted file mode 100644 index 1c4329b3a..000000000 --- a/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - * This source code is licensed under both the GPLv2 (found in the - * COPYING file in the root directory) and Apache 2.0 License - * (found in the LICENSE.Apache file in the root directory). - */ -package org.rocksdb.jmh; - -import static org.rocksdb.util.KVUtils.ba; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.openjdk.jmh.annotations.*; -import org.rocksdb.*; -import org.rocksdb.util.FileUtils; - -@State(Scope.Benchmark) -public class GetBenchmarks { - - @Param({ - "no_column_family", - "1_column_family", - "20_column_families", - "100_column_families" - }) - String columnFamilyTestType; - - @Param({"1000", "100000"}) int keyCount; - - @Param({"12", "64", "128"}) int keySize; - - @Param({"64", "1024", "65536"}) int valueSize; - - Path dbDir; - DBOptions options; - ReadOptions readOptions; - int cfs = 0; // number of column families - private AtomicInteger cfHandlesIdx; - ColumnFamilyHandle[] cfHandles; - RocksDB db; - private final AtomicInteger keyIndex = new AtomicInteger(); - private ByteBuffer keyBuf; - private ByteBuffer valueBuf; - private byte[] keyArr; - private byte[] valueArr; - - @Setup(Level.Trial) - public void setup() throws IOException, RocksDBException { - RocksDB.loadLibrary(); - - dbDir = Files.createTempDirectory("rocksjava-get-benchmarks"); - - options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - readOptions = new ReadOptions(); - - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); - - if ("1_column_family".equals(columnFamilyTestType)) { - cfs = 1; - } else if ("20_column_families".equals(columnFamilyTestType)) { - cfs = 20; - } else if ("100_column_families".equals(columnFamilyTestType)) { - cfs = 100; - } - - if (cfs > 0) { - cfHandlesIdx = new AtomicInteger(1); - for (int i = 1; i <= cfs; i++) { - cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i))); - } - } - - final List cfHandlesList = new ArrayList<>(cfDescriptors.size()); - db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList); - cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]); - - // store initial data for retrieving via get - keyArr = new byte[keySize]; - valueArr = new byte[valueSize]; - Arrays.fill(keyArr, (byte) 0x30); - Arrays.fill(valueArr, (byte) 0x30); - for (int i = 0; i <= cfs; i++) { - for (int j = 0; j < keyCount; j++) { - final byte[] keyPrefix = ba("key" + j); - final byte[] valuePrefix = ba("value" + j); - System.arraycopy(keyPrefix, 0, keyArr, 0, keyPrefix.length); - System.arraycopy(valuePrefix, 0, valueArr, 0, valuePrefix.length); - db.put(cfHandles[i], keyArr, valueArr); - } - } - - try (final FlushOptions flushOptions = new FlushOptions().setWaitForFlush(true)) { - db.flush(flushOptions); - } - - keyBuf = ByteBuffer.allocateDirect(keySize); - valueBuf = ByteBuffer.allocateDirect(valueSize); - Arrays.fill(keyArr, (byte) 0x30); - Arrays.fill(valueArr, (byte) 0x30); - keyBuf.put(keyArr); - keyBuf.flip(); - valueBuf.put(valueArr); - valueBuf.flip(); - } - - @TearDown(Level.Trial) - public void cleanup() throws IOException { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - db.close(); - options.close(); - readOptions.close(); - FileUtils.delete(dbDir); - } - - private ColumnFamilyHandle getColumnFamily() { - if (cfs == 0) { - return cfHandles[0]; - } else if (cfs == 1) { - return cfHandles[1]; - } else { - int idx = cfHandlesIdx.getAndIncrement(); - if (idx > cfs) { - cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok - idx = 0; - } - return cfHandles[idx]; - } - } - - /** - * Takes the next position in the index. - */ - private int next() { - int idx; - int nextIdx; - while (true) { - idx = keyIndex.get(); - nextIdx = idx + 1; - if (nextIdx >= keyCount) { - nextIdx = 0; - } - - if (keyIndex.compareAndSet(idx, nextIdx)) { - break; - } - } - return idx; - } - - // String -> byte[] - private byte[] getKeyArr() { - final int MAX_LEN = 9; // key100000 - final int keyIdx = next(); - final byte[] keyPrefix = ba("key" + keyIdx); - System.arraycopy(keyPrefix, 0, keyArr, 0, keyPrefix.length); - Arrays.fill(keyArr, keyPrefix.length, MAX_LEN, (byte) 0x30); - return keyArr; - } - - // String -> ByteBuffer - private ByteBuffer getKeyBuf() { - final int MAX_LEN = 9; // key100000 - final int keyIdx = next(); - final String keyStr = "key" + keyIdx; - for (int i = 0; i < keyStr.length(); ++i) { - keyBuf.put(i, (byte) keyStr.charAt(i)); - } - for (int i = keyStr.length(); i < MAX_LEN; ++i) { - keyBuf.put(i, (byte) 0x30); - } - // Reset position for future reading - keyBuf.position(0); - return keyBuf; - } - - private byte[] getValueArr() { - return valueArr; - } - - private ByteBuffer getValueBuf() { - return valueBuf; - } - - @Benchmark - public void get() throws RocksDBException { - db.get(getColumnFamily(), getKeyArr()); - } - - @Benchmark - public void preallocatedGet() throws RocksDBException { - db.get(getColumnFamily(), getKeyArr(), getValueArr()); - } - - @Benchmark - public void preallocatedByteBufferGet() throws RocksDBException { - int res = db.get(getColumnFamily(), readOptions, getKeyBuf(), getValueBuf()); - // For testing correctness: - // assert res > 0; - // final byte[] ret = new byte[valueSize]; - // valueBuf.get(ret); - // System.out.println(str(ret)); - // valueBuf.flip(); - } -} \ No newline at end of file diff --git a/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java b/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java deleted file mode 100644 index d37447716..000000000 --- a/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - * This source code is licensed under both the GPLv2 (found in the - * COPYING file in the root directory) and Apache 2.0 License - * (found in the LICENSE.Apache file in the root directory). - */ -package org.rocksdb.jmh; - -import static org.rocksdb.util.KVUtils.ba; -import static org.rocksdb.util.KVUtils.keys; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.OptionsBuilder; -import org.rocksdb.*; -import org.rocksdb.util.FileUtils; - -@State(Scope.Thread) -public class MultiGetBenchmarks { - @Param({ - "no_column_family", - "1_column_family", - "20_column_families", - "100_column_families" - }) - String columnFamilyTestType; - - @Param({"10000", "25000", "100000"}) int keyCount; - - @Param({ - "10", - "100", - "1000", - "10000", - }) - int multiGetSize; - - @Param({"16", "64", "250", "1000", "4000", "16000"}) int valueSize; - @Param({"16"}) int keySize; // big enough - - Path dbDir; - DBOptions options; - int cfs = 0; // number of column families - private AtomicInteger cfHandlesIdx; - ColumnFamilyHandle[] cfHandles; - RocksDB db; - private final AtomicInteger keyIndex = new AtomicInteger(); - - @Setup(Level.Trial) - public void setup() throws IOException, RocksDBException { - RocksDB.loadLibrary(); - - dbDir = Files.createTempDirectory("rocksjava-multiget-benchmarks"); - - options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); - - if ("1_column_family".equals(columnFamilyTestType)) { - cfs = 1; - } else if ("20_column_families".equals(columnFamilyTestType)) { - cfs = 20; - } else if ("100_column_families".equals(columnFamilyTestType)) { - cfs = 100; - } - - if (cfs > 0) { - cfHandlesIdx = new AtomicInteger(1); - for (int i = 1; i <= cfs; i++) { - cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i))); - } - } - - final List cfHandlesList = new ArrayList<>(cfDescriptors.size()); - db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList); - cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]); - - // store initial data for retrieving via get - for (int i = 0; i < cfs; i++) { - for (int j = 0; j < keyCount; j++) { - final byte[] paddedValue = Arrays.copyOf(ba("value" + j), valueSize); - db.put(cfHandles[i], ba("key" + j), paddedValue); - } - } - - try (final FlushOptions flushOptions = new FlushOptions() - .setWaitForFlush(true)) { - db.flush(flushOptions); - } - } - - @TearDown(Level.Trial) - public void cleanup() throws IOException { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - db.close(); - options.close(); - FileUtils.delete(dbDir); - } - - private ColumnFamilyHandle getColumnFamily() { - if (cfs == 0) { - return cfHandles[0]; - } else if (cfs == 1) { - return cfHandles[1]; - } else { - int idx = cfHandlesIdx.getAndIncrement(); - if (idx > cfs) { - cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok - idx = 0; - } - return cfHandles[idx]; - } - } - - /** - * Reserves the next {@inc} positions in the index. - * - * @param inc the number by which to increment the index - * @param limit the limit for the index - * @return the index before {@code inc} is added - */ - private int next(final int inc, final int limit) { - int idx; - int nextIdx; - while (true) { - idx = keyIndex.get(); - nextIdx = idx + inc; - if (nextIdx >= limit) { - nextIdx = inc; - } - - if (keyIndex.compareAndSet(idx, nextIdx)) { - break; - } - } - - if (nextIdx >= limit) { - return -1; - } else { - return idx; - } - } - - ByteBuffer keysBuffer; - ByteBuffer valuesBuffer; - - List valueBuffersList; - List keyBuffersList; - - @Setup - public void allocateSliceBuffers() { - keysBuffer = ByteBuffer.allocateDirect(keyCount * valueSize); - valuesBuffer = ByteBuffer.allocateDirect(keyCount * valueSize); - valueBuffersList = new ArrayList<>(); - keyBuffersList = new ArrayList<>(); - for (int i = 0; i < keyCount; i++) { - valueBuffersList.add(valuesBuffer.slice()); - valuesBuffer.position(i * valueSize); - keyBuffersList.add(keysBuffer.slice()); - keysBuffer.position(i * keySize); - } - } - - @TearDown - public void freeSliceBuffers() { - valueBuffersList.clear(); - } - - @Benchmark - public List multiGet10() throws RocksDBException { - final int fromKeyIdx = next(multiGetSize, keyCount); - if (fromKeyIdx >= 0) { - final List keys = keys(fromKeyIdx, fromKeyIdx + multiGetSize); - final List valueResults = db.multiGetAsList(keys); - for (final byte[] result : valueResults) { - if (result.length != valueSize) - throw new RuntimeException("Test valueSize assumption wrong"); - } - } - return new ArrayList<>(); - } - - public static void main(final String[] args) throws RunnerException { - final org.openjdk.jmh.runner.options.Options opt = - new OptionsBuilder() - .include(MultiGetBenchmarks.class.getSimpleName()) - .forks(1) - .jvmArgs("-ea") - .warmupIterations(1) - .measurementIterations(2) - .forks(2) - .param("columnFamilyTestType=", "1_column_family") - .param("multiGetSize=", "10", "1000") - .param("keyCount=", "1000") - .output("jmh_output") - .build(); - - new Runner(opt).run(); - } -} diff --git a/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java b/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java deleted file mode 100644 index 5aae21cb9..000000000 --- a/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - * This source code is licensed under both the GPLv2 (found in the - * COPYING file in the root directory) and Apache 2.0 License - * (found in the LICENSE.Apache file in the root directory). - */ -package org.rocksdb.jmh; - -import org.openjdk.jmh.annotations.*; -import org.rocksdb.*; -import org.rocksdb.util.FileUtils; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.rocksdb.util.KVUtils.ba; - -@State(Scope.Benchmark) -public class PutBenchmarks { - - @Param({ - "no_column_family", - "1_column_family", - "20_column_families", - "100_column_families" - }) - String columnFamilyTestType; - - Path dbDir; - DBOptions options; - int cfs = 0; // number of column families - private AtomicInteger cfHandlesIdx; - ColumnFamilyHandle[] cfHandles; - RocksDB db; - - @Setup(Level.Trial) - public void setup() throws IOException, RocksDBException { - RocksDB.loadLibrary(); - - dbDir = Files.createTempDirectory("rocksjava-put-benchmarks"); - - options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); - - if ("1_column_family".equals(columnFamilyTestType)) { - cfs = 1; - } else if ("20_column_families".equals(columnFamilyTestType)) { - cfs = 20; - } else if ("100_column_families".equals(columnFamilyTestType)) { - cfs = 100; - } - - if (cfs > 0) { - cfHandlesIdx = new AtomicInteger(1); - for (int i = 1; i <= cfs; i++) { - cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i))); - } - } - - final List cfHandlesList = new ArrayList<>(cfDescriptors.size()); - db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList); - cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]); - } - - @TearDown(Level.Trial) - public void cleanup() throws IOException { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - db.close(); - options.close(); - FileUtils.delete(dbDir); - } - - private ColumnFamilyHandle getColumnFamily() { - if (cfs == 0) { - return cfHandles[0]; - } else if (cfs == 1) { - return cfHandles[1]; - } else { - int idx = cfHandlesIdx.getAndIncrement(); - if (idx > cfs) { - cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok - idx = 0; - } - return cfHandles[idx]; - } - } - - @State(Scope.Benchmark) - public static class Counter { - private final AtomicInteger count = new AtomicInteger(); - - public int next() { - return count.getAndIncrement(); - } - } - - @Benchmark - public void put(final ComparatorBenchmarks.Counter counter) throws RocksDBException { - final int i = counter.next(); - db.put(getColumnFamily(), ba("key" + i), ba("value" + i)); - } -} diff --git a/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java b/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java deleted file mode 100644 index 63744a14f..000000000 --- a/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - * This source code is licensed under both the GPLv2 (found in the - * COPYING file in the root directory) and Apache 2.0 License - * (found in the LICENSE.Apache file in the root directory). - */ -package org.rocksdb.util; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; - -public final class FileUtils { - private static final SimpleFileVisitor DELETE_DIR_VISITOR = new DeleteDirVisitor(); - - /** - * Deletes a path from the filesystem - * - * If the path is a directory its contents - * will be recursively deleted before it itself - * is deleted. - * - * Note that removal of a directory is not an atomic-operation - * and so if an error occurs during removal, some of the directories - * descendants may have already been removed - * - * @param path the path to delete. - * - * @throws IOException if an error occurs whilst removing a file or directory - */ - public static void delete(final Path path) throws IOException { - if (!Files.isDirectory(path)) { - Files.deleteIfExists(path); - } else { - Files.walkFileTree(path, DELETE_DIR_VISITOR); - } - } - - private static class DeleteDirVisitor extends SimpleFileVisitor { - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - Files.deleteIfExists(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { - if (exc != null) { - throw exc; - } - - Files.deleteIfExists(dir); - return FileVisitResult.CONTINUE; - } - } -} diff --git a/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java b/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java deleted file mode 100644 index 5077291c8..000000000 --- a/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2011-present, Facebook, Inc. All rights reserved. - * This source code is licensed under both the GPLv2 (found in the - * COPYING file in the root directory) and Apache 2.0 License - * (found in the LICENSE.Apache file in the root directory). - */ -package org.rocksdb.util; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -public final class KVUtils { - - /** - * Get a byte array from a string. - * - * Assumes UTF-8 encoding - * - * @param string the string - * - * @return the bytes. - */ - public static byte[] ba(final String string) { - return string.getBytes(UTF_8); - } - - /** - * Get a string from a byte array. - * - * Assumes UTF-8 encoding - * - * @param bytes the bytes - * - * @return the string. - */ - public static String str(final byte[] bytes) { - return new String(bytes, UTF_8); - } - - /** - * Get a list of keys where the keys are named key1..key1+N - * in the range of {@code from} to {@code to} i.e. keyFrom..keyTo. - * - * @param from the first key - * @param to the last key - * - * @return the array of keys - */ - public static List keys(final int from, final int to) { - final List keys = new ArrayList<>(to - from); - for (int i = from; i < to; i++) { - keys.add(ba("key" + i)); - } - return keys; - } - - public static List keys( - final List keyBuffers, final int from, final int to) { - final List keys = new ArrayList<>(to - from); - for (int i = from; i < to; i++) { - final ByteBuffer key = keyBuffers.get(i); - key.clear(); - key.put(ba("key" + i)); - key.flip(); - keys.add(key); - } - return keys; - } -} diff --git a/java/pom.xml.template b/java/pom.xml.template deleted file mode 100644 index 8a1981c66..000000000 --- a/java/pom.xml.template +++ /dev/null @@ -1,178 +0,0 @@ - - - 4.0.0 - - org.rocksdb - rocksdbjni - ${ROCKSDB_JAVA_VERSION} - - RocksDB JNI - RocksDB fat jar that contains .so files for linux32 and linux64 (glibc and musl-libc), jnilib files - for Mac OSX, and a .dll for Windows x64. - - https://rocksdb.org - 2012 - - - - Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - - GNU General Public License, version 2 - http://www.gnu.org/licenses/gpl-2.0.html - repo - - - - - scm:git:https://github.com/facebook/rocksdb.git - scm:git:https://github.com/facebook/rocksdb.git - scm:git:https://github.com/facebook/rocksdb.git - - - - Facebook - https://www.facebook.com - - - - - Facebook - help@facebook.com - America/New_York - - architect - - - - - - - rocksdb - Google Groups - rocksdb-subscribe@googlegroups.com - rocksdb-unsubscribe@googlegroups.com - rocksdb@googlegroups.com - https://groups.google.com/forum/#!forum/rocksdb - - - - - 1.8 - 1.8 - UTF-8 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.2 - - ${project.build.source} - ${project.build.target} - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.18.1 - - ${argLine} -ea -Xcheck:jni -Djava.library.path=${project.build.directory} - false - false - - ${project.build.directory}/* - - - - - org.jacoco - jacoco-maven-plugin - 0.7.2.201409121644 - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - org.codehaus.gmaven - groovy-maven-plugin - 2.0 - - - process-classes - - execute - - - - Xenu - - - String fileContents = new File(project.basedir.absolutePath + '/../include/rocksdb/version.h').getText('UTF-8') - matcher = (fileContents =~ /(?s).*ROCKSDB_MAJOR ([0-9]+).*?/) - String major_version = matcher.getAt(0).getAt(1) - matcher = (fileContents =~ /(?s).*ROCKSDB_MINOR ([0-9]+).*?/) - String minor_version = matcher.getAt(0).getAt(1) - matcher = (fileContents =~ /(?s).*ROCKSDB_PATCH ([0-9]+).*?/) - String patch_version = matcher.getAt(0).getAt(1) - String version = String.format('%s.%s.%s', major_version, minor_version, patch_version) - // Set version to be used in pom.properties - project.version = version - // Set version to be set as jar name - project.build.finalName = project.artifactId + "-" + version - - - - - - - - - - - junit - junit - 4.13.1 - test - - - org.hamcrest - hamcrest - 2.2 - test - - - cglib - cglib - 3.3.0 - test - - - org.assertj - assertj-core - 2.9.0 - test - - - org.mockito - mockito-all - 1.10.19 - test - - - diff --git a/java/rocksjni/backup_engine_options.cc b/java/rocksjni/backup_engine_options.cc deleted file mode 100644 index 25bfb6720..000000000 --- a/java/rocksjni/backup_engine_options.cc +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::BackupEnginge and -// ROCKSDB_NAMESPACE::BackupEngineOptions methods from Java side. - -#include -#include -#include - -#include -#include - -#include "include/org_rocksdb_BackupEngineOptions.h" -#include "rocksdb/utilities/backup_engine.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/////////////////////////////////////////////////////////////////////////// -// BackupDBOptions - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: newBackupEngineOptions - * Signature: (Ljava/lang/String;)J - */ -jlong Java_org_rocksdb_BackupEngineOptions_newBackupEngineOptions( - JNIEnv* env, jclass /*jcls*/, jstring jpath) { - const char* cpath = env->GetStringUTFChars(jpath, nullptr); - if (cpath == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - auto* bopt = new ROCKSDB_NAMESPACE::BackupEngineOptions(cpath); - env->ReleaseStringUTFChars(jpath, cpath); - return GET_CPLUSPLUS_POINTER(bopt); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: backupDir - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_BackupEngineOptions_backupDir(JNIEnv* env, - jobject /*jopt*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return env->NewStringUTF(bopt->backup_dir.c_str()); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setBackupEnv - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setBackupEnv( - JNIEnv* /*env*/, jobject /*jopt*/, jlong jhandle, jlong jrocks_env_handle) { - auto* bopt = - reinterpret_cast(jhandle); - auto* rocks_env = - reinterpret_cast(jrocks_env_handle); - bopt->backup_env = rocks_env; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setShareTableFiles - * Signature: (JZ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setShareTableFiles(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jboolean flag) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->share_table_files = flag; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: shareTableFiles - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_BackupEngineOptions_shareTableFiles(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->share_table_files; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setInfoLog - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setInfoLog(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong /*jlogger_handle*/) { - auto* bopt = - reinterpret_cast(jhandle); - auto* sptr_logger = - reinterpret_cast*>( - jhandle); - bopt->info_log = sptr_logger->get(); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setSync - * Signature: (JZ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setSync(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jboolean flag) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->sync = flag; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: sync - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_BackupEngineOptions_sync(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->sync; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setDestroyOldData - * Signature: (JZ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setDestroyOldData(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jboolean flag) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->destroy_old_data = flag; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: destroyOldData - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_BackupEngineOptions_destroyOldData(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->destroy_old_data; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setBackupLogFiles - * Signature: (JZ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setBackupLogFiles(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jboolean flag) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->backup_log_files = flag; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: backupLogFiles - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_BackupEngineOptions_backupLogFiles(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->backup_log_files; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setBackupRateLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setBackupRateLimit( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jbackup_rate_limit) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->backup_rate_limit = jbackup_rate_limit; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: backupRateLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_BackupEngineOptions_backupRateLimit(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->backup_rate_limit; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setBackupRateLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setBackupRateLimiter( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jrate_limiter_handle) { - auto* bopt = - reinterpret_cast(jhandle); - auto* sptr_rate_limiter = - reinterpret_cast*>( - jrate_limiter_handle); - bopt->backup_rate_limiter = *sptr_rate_limiter; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setRestoreRateLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setRestoreRateLimit( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jrestore_rate_limit) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->restore_rate_limit = jrestore_rate_limit; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: restoreRateLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_BackupEngineOptions_restoreRateLimit(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->restore_rate_limit; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setRestoreRateLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setRestoreRateLimiter( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jrate_limiter_handle) { - auto* bopt = - reinterpret_cast(jhandle); - auto* sptr_rate_limiter = - reinterpret_cast*>( - jrate_limiter_handle); - bopt->restore_rate_limiter = *sptr_rate_limiter; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setShareFilesWithChecksum - * Signature: (JZ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setShareFilesWithChecksum( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean flag) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->share_files_with_checksum = flag; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: shareFilesWithChecksum - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_BackupEngineOptions_shareFilesWithChecksum( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return bopt->share_files_with_checksum; -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setMaxBackgroundOperations - * Signature: (JI)V - */ -void Java_org_rocksdb_BackupEngineOptions_setMaxBackgroundOperations( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jint max_background_operations) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->max_background_operations = static_cast(max_background_operations); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: maxBackgroundOperations - * Signature: (J)I - */ -jint Java_org_rocksdb_BackupEngineOptions_maxBackgroundOperations( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return static_cast(bopt->max_background_operations); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: setCallbackTriggerIntervalSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_BackupEngineOptions_setCallbackTriggerIntervalSize( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jcallback_trigger_interval_size) { - auto* bopt = - reinterpret_cast(jhandle); - bopt->callback_trigger_interval_size = - static_cast(jcallback_trigger_interval_size); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: callbackTriggerIntervalSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_BackupEngineOptions_callbackTriggerIntervalSize( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - return static_cast(bopt->callback_trigger_interval_size); -} - -/* - * Class: org_rocksdb_BackupEngineOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_BackupEngineOptions_disposeInternal(JNIEnv* /*env*/, - jobject /*jopt*/, - jlong jhandle) { - auto* bopt = - reinterpret_cast(jhandle); - assert(bopt != nullptr); - delete bopt; -} diff --git a/java/rocksjni/backupenginejni.cc b/java/rocksjni/backupenginejni.cc deleted file mode 100644 index 1ba7ea286..000000000 --- a/java/rocksjni/backupenginejni.cc +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::BackupEngine methods from the Java side. - -#include - -#include - -#include "include/org_rocksdb_BackupEngine.h" -#include "rocksdb/utilities/backup_engine.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_BackupEngine - * Method: open - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_BackupEngine_open(JNIEnv* env, jclass /*jcls*/, - jlong env_handle, - jlong backup_engine_options_handle) { - auto* rocks_env = reinterpret_cast(env_handle); - auto* backup_engine_options = - reinterpret_cast( - backup_engine_options_handle); - ROCKSDB_NAMESPACE::BackupEngine* backup_engine; - auto status = ROCKSDB_NAMESPACE::BackupEngine::Open( - rocks_env, *backup_engine_options, &backup_engine); - - if (status.ok()) { - return GET_CPLUSPLUS_POINTER(backup_engine); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - return 0; - } -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: createNewBackup - * Signature: (JJZ)V - */ -void Java_org_rocksdb_BackupEngine_createNewBackup( - JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jlong db_handle, - jboolean jflush_before_backup) { - auto* db = reinterpret_cast(db_handle); - auto* backup_engine = - reinterpret_cast(jbe_handle); - auto status = backup_engine->CreateNewBackup( - db, static_cast(jflush_before_backup)); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: createNewBackupWithMetadata - * Signature: (JJLjava/lang/String;Z)V - */ -void Java_org_rocksdb_BackupEngine_createNewBackupWithMetadata( - JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jlong db_handle, - jstring japp_metadata, jboolean jflush_before_backup) { - auto* db = reinterpret_cast(db_handle); - auto* backup_engine = - reinterpret_cast(jbe_handle); - - jboolean has_exception = JNI_FALSE; - std::string app_metadata = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, japp_metadata, &has_exception); - if (has_exception == JNI_TRUE) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Could not copy jstring to std::string"); - return; - } - - auto status = backup_engine->CreateNewBackupWithMetadata( - db, app_metadata, static_cast(jflush_before_backup)); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: getBackupInfo - * Signature: (J)Ljava/util/List; - */ -jobject Java_org_rocksdb_BackupEngine_getBackupInfo(JNIEnv* env, - jobject /*jbe*/, - jlong jbe_handle) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - std::vector backup_infos; - backup_engine->GetBackupInfo(&backup_infos); - return ROCKSDB_NAMESPACE::BackupInfoListJni::getBackupInfo(env, backup_infos); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: getCorruptedBackups - * Signature: (J)[I - */ -jintArray Java_org_rocksdb_BackupEngine_getCorruptedBackups(JNIEnv* env, - jobject /*jbe*/, - jlong jbe_handle) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - std::vector backup_ids; - backup_engine->GetCorruptedBackups(&backup_ids); - // store backupids in int array - std::vector int_backup_ids(backup_ids.begin(), backup_ids.end()); - - // Store ints in java array - // Its ok to loose precision here (64->32) - jsize ret_backup_ids_size = static_cast(backup_ids.size()); - jintArray ret_backup_ids = env->NewIntArray(ret_backup_ids_size); - if (ret_backup_ids == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetIntArrayRegion(ret_backup_ids, 0, ret_backup_ids_size, - int_backup_ids.data()); - return ret_backup_ids; -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: garbageCollect - * Signature: (J)V - */ -void Java_org_rocksdb_BackupEngine_garbageCollect(JNIEnv* env, jobject /*jbe*/, - jlong jbe_handle) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - auto status = backup_engine->GarbageCollect(); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: purgeOldBackups - * Signature: (JI)V - */ -void Java_org_rocksdb_BackupEngine_purgeOldBackups(JNIEnv* env, jobject /*jbe*/, - jlong jbe_handle, - jint jnum_backups_to_keep) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - auto status = backup_engine->PurgeOldBackups( - static_cast(jnum_backups_to_keep)); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: deleteBackup - * Signature: (JI)V - */ -void Java_org_rocksdb_BackupEngine_deleteBackup(JNIEnv* env, jobject /*jbe*/, - jlong jbe_handle, - jint jbackup_id) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - auto status = backup_engine->DeleteBackup( - static_cast(jbackup_id)); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: restoreDbFromBackup - * Signature: (JILjava/lang/String;Ljava/lang/String;J)V - */ -void Java_org_rocksdb_BackupEngine_restoreDbFromBackup( - JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jint jbackup_id, - jstring jdb_dir, jstring jwal_dir, jlong jrestore_options_handle) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - const char* db_dir = env->GetStringUTFChars(jdb_dir, nullptr); - if (db_dir == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - const char* wal_dir = env->GetStringUTFChars(jwal_dir, nullptr); - if (wal_dir == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_dir, db_dir); - return; - } - auto* restore_options = reinterpret_cast( - jrestore_options_handle); - auto status = backup_engine->RestoreDBFromBackup( - static_cast(jbackup_id), db_dir, wal_dir, - *restore_options); - - env->ReleaseStringUTFChars(jwal_dir, wal_dir); - env->ReleaseStringUTFChars(jdb_dir, db_dir); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: restoreDbFromLatestBackup - * Signature: (JLjava/lang/String;Ljava/lang/String;J)V - */ -void Java_org_rocksdb_BackupEngine_restoreDbFromLatestBackup( - JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jstring jdb_dir, - jstring jwal_dir, jlong jrestore_options_handle) { - auto* backup_engine = - reinterpret_cast(jbe_handle); - const char* db_dir = env->GetStringUTFChars(jdb_dir, nullptr); - if (db_dir == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - const char* wal_dir = env->GetStringUTFChars(jwal_dir, nullptr); - if (wal_dir == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_dir, db_dir); - return; - } - auto* restore_options = reinterpret_cast( - jrestore_options_handle); - auto status = backup_engine->RestoreDBFromLatestBackup(db_dir, wal_dir, - *restore_options); - - env->ReleaseStringUTFChars(jwal_dir, wal_dir); - env->ReleaseStringUTFChars(jdb_dir, db_dir); - - if (status.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); -} - -/* - * Class: org_rocksdb_BackupEngine - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_BackupEngine_disposeInternal(JNIEnv* /*env*/, - jobject /*jbe*/, - jlong jbe_handle) { - auto* be = reinterpret_cast(jbe_handle); - assert(be != nullptr); - delete be; -} diff --git a/java/rocksjni/cache.cc b/java/rocksjni/cache.cc deleted file mode 100644 index 5ca1d5175..000000000 --- a/java/rocksjni/cache.cc +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Cache. - -#include - -#include "include/org_rocksdb_Cache.h" -#include "rocksdb/advanced_cache.h" - -/* - * Class: org_rocksdb_Cache - * Method: getUsage - * Signature: (J)J - */ -jlong Java_org_rocksdb_Cache_getUsage(JNIEnv*, jclass, jlong jhandle) { - auto* sptr_cache = - reinterpret_cast*>(jhandle); - return static_cast(sptr_cache->get()->GetUsage()); -} - -/* - * Class: org_rocksdb_Cache - * Method: getPinnedUsage - * Signature: (J)J - */ -jlong Java_org_rocksdb_Cache_getPinnedUsage(JNIEnv*, jclass, jlong jhandle) { - auto* sptr_cache = - reinterpret_cast*>(jhandle); - return static_cast(sptr_cache->get()->GetPinnedUsage()); -} diff --git a/java/rocksjni/cassandra_compactionfilterjni.cc b/java/rocksjni/cassandra_compactionfilterjni.cc deleted file mode 100644 index 25817aeca..000000000 --- a/java/rocksjni/cassandra_compactionfilterjni.cc +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "include/org_rocksdb_CassandraCompactionFilter.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "utilities/cassandra/cassandra_compaction_filter.h" - -/* - * Class: org_rocksdb_CassandraCompactionFilter - * Method: createNewCassandraCompactionFilter0 - * Signature: (ZI)J - */ -jlong Java_org_rocksdb_CassandraCompactionFilter_createNewCassandraCompactionFilter0( - JNIEnv* /*env*/, jclass /*jcls*/, jboolean purge_ttl_on_expiration, - jint gc_grace_period_in_seconds) { - auto* compaction_filter = - new ROCKSDB_NAMESPACE::cassandra::CassandraCompactionFilter( - purge_ttl_on_expiration, gc_grace_period_in_seconds); - // set the native handle to our native compaction filter - return GET_CPLUSPLUS_POINTER(compaction_filter); -} diff --git a/java/rocksjni/cassandra_value_operator.cc b/java/rocksjni/cassandra_value_operator.cc deleted file mode 100644 index 6de28c1b1..000000000 --- a/java/rocksjni/cassandra_value_operator.cc +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2017-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include - -#include -#include - -#include "include/org_rocksdb_CassandraValueMergeOperator.h" -#include "rocksdb/db.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/merge_operator.h" -#include "rocksdb/options.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -#include "utilities/cassandra/merge_operator.h" - -/* - * Class: org_rocksdb_CassandraValueMergeOperator - * Method: newSharedCassandraValueMergeOperator - * Signature: (II)J - */ -jlong Java_org_rocksdb_CassandraValueMergeOperator_newSharedCassandraValueMergeOperator( - JNIEnv* /*env*/, jclass /*jclazz*/, jint gcGracePeriodInSeconds, - jint operands_limit) { - auto* op = new std::shared_ptr( - new ROCKSDB_NAMESPACE::cassandra::CassandraValueMergeOperator( - gcGracePeriodInSeconds, operands_limit)); - return GET_CPLUSPLUS_POINTER(op); -} - -/* - * Class: org_rocksdb_CassandraValueMergeOperator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CassandraValueMergeOperator_disposeInternal( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* op = - reinterpret_cast*>( - jhandle); - delete op; -} diff --git a/java/rocksjni/checkpoint.cc b/java/rocksjni/checkpoint.cc deleted file mode 100644 index d7cfd813b..000000000 --- a/java/rocksjni/checkpoint.cc +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::Checkpoint methods from Java side. - -#include "rocksdb/utilities/checkpoint.h" - -#include -#include -#include - -#include - -#include "include/org_rocksdb_Checkpoint.h" -#include "rocksdb/db.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -/* - * Class: org_rocksdb_Checkpoint - * Method: newCheckpoint - * Signature: (J)J - */ -jlong Java_org_rocksdb_Checkpoint_newCheckpoint(JNIEnv* /*env*/, - jclass /*jclazz*/, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::Checkpoint* checkpoint; - ROCKSDB_NAMESPACE::Checkpoint::Create(db, &checkpoint); - return GET_CPLUSPLUS_POINTER(checkpoint); -} - -/* - * Class: org_rocksdb_Checkpoint - * Method: dispose - * Signature: (J)V - */ -void Java_org_rocksdb_Checkpoint_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* checkpoint = reinterpret_cast(jhandle); - assert(checkpoint != nullptr); - delete checkpoint; -} - -/* - * Class: org_rocksdb_Checkpoint - * Method: createCheckpoint - * Signature: (JLjava/lang/String;)V - */ -void Java_org_rocksdb_Checkpoint_createCheckpoint(JNIEnv* env, jobject /*jobj*/, - jlong jcheckpoint_handle, - jstring jcheckpoint_path) { - const char* checkpoint_path = env->GetStringUTFChars(jcheckpoint_path, 0); - if (checkpoint_path == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* checkpoint = - reinterpret_cast(jcheckpoint_handle); - ROCKSDB_NAMESPACE::Status s = checkpoint->CreateCheckpoint(checkpoint_path); - - env->ReleaseStringUTFChars(jcheckpoint_path, checkpoint_path); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} diff --git a/java/rocksjni/clock_cache.cc b/java/rocksjni/clock_cache.cc deleted file mode 100644 index e04991aa9..000000000 --- a/java/rocksjni/clock_cache.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::ClockCache. - -#include "cache/clock_cache.h" - -#include - -#include "include/org_rocksdb_ClockCache.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_ClockCache - * Method: newClockCache - * Signature: (JIZ)J - */ -jlong Java_org_rocksdb_ClockCache_newClockCache( - JNIEnv* /*env*/, jclass /*jcls*/, jlong jcapacity, jint jnum_shard_bits, - jboolean jstrict_capacity_limit) { - auto* sptr_clock_cache = new std::shared_ptr( - ROCKSDB_NAMESPACE::NewClockCache( - static_cast(jcapacity), static_cast(jnum_shard_bits), - static_cast(jstrict_capacity_limit))); - return GET_CPLUSPLUS_POINTER(sptr_clock_cache); -} - -/* - * Class: org_rocksdb_ClockCache - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ClockCache_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_clock_cache = - reinterpret_cast*>(jhandle); - delete sptr_clock_cache; // delete std::shared_ptr -} diff --git a/java/rocksjni/columnfamilyhandle.cc b/java/rocksjni/columnfamilyhandle.cc deleted file mode 100644 index 4140580f0..000000000 --- a/java/rocksjni/columnfamilyhandle.cc +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::ColumnFamilyHandle. - -#include -#include -#include - -#include "include/org_rocksdb_ColumnFamilyHandle.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_ColumnFamilyHandle - * Method: getName - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_ColumnFamilyHandle_getName(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle) { - auto* cfh = reinterpret_cast(jhandle); - std::string cf_name = cfh->GetName(); - return ROCKSDB_NAMESPACE::JniUtil::copyBytes(env, cf_name); -} - -/* - * Class: org_rocksdb_ColumnFamilyHandle - * Method: getID - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyHandle_getID(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* cfh = reinterpret_cast(jhandle); - const int32_t id = cfh->GetID(); - return static_cast(id); -} - -/* - * Class: org_rocksdb_ColumnFamilyHandle - * Method: getDescriptor - * Signature: (J)Lorg/rocksdb/ColumnFamilyDescriptor; - */ -jobject Java_org_rocksdb_ColumnFamilyHandle_getDescriptor(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle) { - auto* cfh = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::ColumnFamilyDescriptor desc; - ROCKSDB_NAMESPACE::Status s = cfh->GetDescriptor(&desc); - if (s.ok()) { - return ROCKSDB_NAMESPACE::ColumnFamilyDescriptorJni::construct(env, &desc); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } -} - -/* - * Class: org_rocksdb_ColumnFamilyHandle - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ColumnFamilyHandle_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* cfh = reinterpret_cast(jhandle); - assert(cfh != nullptr); - delete cfh; -} diff --git a/java/rocksjni/compact_range_options.cc b/java/rocksjni/compact_range_options.cc deleted file mode 100644 index 77fbb8890..000000000 --- a/java/rocksjni/compact_range_options.cc +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactRangeOptions. - -#include - -#include "include/org_rocksdb_CompactRangeOptions.h" -#include "rocksdb/options.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: newCompactRangeOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_CompactRangeOptions_newCompactRangeOptions( - JNIEnv* /*env*/, jclass /*jclazz*/) { - auto* options = new ROCKSDB_NAMESPACE::CompactRangeOptions(); - return GET_CPLUSPLUS_POINTER(options); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: exclusiveManualCompaction - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompactRangeOptions_exclusiveManualCompaction( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->exclusive_manual_compaction); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setExclusiveManualCompaction - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompactRangeOptions_setExclusiveManualCompaction( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jboolean exclusive_manual_compaction) { - auto* options = - reinterpret_cast(jhandle); - options->exclusive_manual_compaction = - static_cast(exclusive_manual_compaction); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: bottommostLevelCompaction - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactRangeOptions_bottommostLevelCompaction( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::BottommostLevelCompactionJni:: - toJavaBottommostLevelCompaction(options->bottommost_level_compaction); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setBottommostLevelCompaction - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactRangeOptions_setBottommostLevelCompaction( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jint bottommost_level_compaction) { - auto* options = - reinterpret_cast(jhandle); - options->bottommost_level_compaction = - ROCKSDB_NAMESPACE::BottommostLevelCompactionJni:: - toCppBottommostLevelCompaction(bottommost_level_compaction); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: changeLevel - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompactRangeOptions_changeLevel(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->change_level); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setChangeLevel - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompactRangeOptions_setChangeLevel( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean change_level) { - auto* options = - reinterpret_cast(jhandle); - options->change_level = static_cast(change_level); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: targetLevel - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactRangeOptions_targetLevel(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->target_level); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setTargetLevel - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactRangeOptions_setTargetLevel(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jint target_level) { - auto* options = - reinterpret_cast(jhandle); - options->target_level = static_cast(target_level); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: targetPathId - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactRangeOptions_targetPathId(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->target_path_id); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setTargetPathId - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactRangeOptions_setTargetPathId(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jint target_path_id) { - auto* options = - reinterpret_cast(jhandle); - options->target_path_id = static_cast(target_path_id); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: allowWriteStall - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompactRangeOptions_allowWriteStall(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->allow_write_stall); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setAllowWriteStall - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompactRangeOptions_setAllowWriteStall( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jboolean allow_write_stall) { - auto* options = - reinterpret_cast(jhandle); - options->allow_write_stall = static_cast(allow_write_stall); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: maxSubcompactions - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactRangeOptions_maxSubcompactions(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->max_subcompactions); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: setMaxSubcompactions - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactRangeOptions_setMaxSubcompactions( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jint max_subcompactions) { - auto* options = - reinterpret_cast(jhandle); - options->max_subcompactions = static_cast(max_subcompactions); -} - -/* - * Class: org_rocksdb_CompactRangeOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompactRangeOptions_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - delete options; -} diff --git a/java/rocksjni/compaction_filter.cc b/java/rocksjni/compaction_filter.cc deleted file mode 100644 index ea04996ac..000000000 --- a/java/rocksjni/compaction_filter.cc +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionFilter. - -#include "rocksdb/compaction_filter.h" - -#include - -#include "include/org_rocksdb_AbstractCompactionFilter.h" - -// - -/* - * Class: org_rocksdb_AbstractCompactionFilter - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_AbstractCompactionFilter_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - auto* cf = reinterpret_cast(handle); - assert(cf != nullptr); - delete cf; -} -// diff --git a/java/rocksjni/compaction_filter_factory.cc b/java/rocksjni/compaction_filter_factory.cc deleted file mode 100644 index 16fbdbbdd..000000000 --- a/java/rocksjni/compaction_filter_factory.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionFilterFactory. - -#include - -#include - -#include "include/org_rocksdb_AbstractCompactionFilterFactory.h" -#include "rocksjni/compaction_filter_factory_jnicallback.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_AbstractCompactionFilterFactory - * Method: createNewCompactionFilterFactory0 - * Signature: ()J - */ -jlong Java_org_rocksdb_AbstractCompactionFilterFactory_createNewCompactionFilterFactory0( - JNIEnv* env, jobject jobj) { - auto* cff = - new ROCKSDB_NAMESPACE::CompactionFilterFactoryJniCallback(env, jobj); - auto* ptr_sptr_cff = new std::shared_ptr< - ROCKSDB_NAMESPACE::CompactionFilterFactoryJniCallback>(cff); - return GET_CPLUSPLUS_POINTER(ptr_sptr_cff); -} - -/* - * Class: org_rocksdb_AbstractCompactionFilterFactory - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_AbstractCompactionFilterFactory_disposeInternal( - JNIEnv*, jobject, jlong jhandle) { - auto* ptr_sptr_cff = reinterpret_cast< - std::shared_ptr*>( - jhandle); - delete ptr_sptr_cff; -} diff --git a/java/rocksjni/compaction_filter_factory_jnicallback.cc b/java/rocksjni/compaction_filter_factory_jnicallback.cc deleted file mode 100644 index 14285526f..000000000 --- a/java/rocksjni/compaction_filter_factory_jnicallback.cc +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionFilterFactory. - -#include "rocksjni/compaction_filter_factory_jnicallback.h" - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -CompactionFilterFactoryJniCallback::CompactionFilterFactoryJniCallback( - JNIEnv* env, jobject jcompaction_filter_factory) - : JniCallback(env, jcompaction_filter_factory) { - // Note: The name of a CompactionFilterFactory will not change during - // it's lifetime, so we cache it in a global var - jmethodID jname_method_id = - AbstractCompactionFilterFactoryJni::getNameMethodId(env); - if (jname_method_id == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - jstring jname = - (jstring)env->CallObjectMethod(m_jcallback_obj, jname_method_id); - if (env->ExceptionCheck()) { - // exception thrown - return; - } - jboolean has_exception = JNI_FALSE; - m_name = - JniUtil::copyString(env, jname, &has_exception); // also releases jname - if (has_exception == JNI_TRUE) { - // exception thrown - return; - } - - m_jcreate_compaction_filter_methodid = - AbstractCompactionFilterFactoryJni::getCreateCompactionFilterMethodId( - env); - if (m_jcreate_compaction_filter_methodid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } -} - -const char* CompactionFilterFactoryJniCallback::Name() const { - return m_name.get(); -} - -std::unique_ptr -CompactionFilterFactoryJniCallback::CreateCompactionFilter( - const CompactionFilter::Context& context) { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - jlong addr_compaction_filter = - env->CallLongMethod(m_jcallback_obj, m_jcreate_compaction_filter_methodid, - static_cast(context.is_full_compaction), - static_cast(context.is_manual_compaction)); - - if (env->ExceptionCheck()) { - // exception thrown from CallLongMethod - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return nullptr; - } - - auto* cff = reinterpret_cast(addr_compaction_filter); - - releaseJniEnv(attached_thread); - - return std::unique_ptr(cff); -} - -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/compaction_filter_factory_jnicallback.h b/java/rocksjni/compaction_filter_factory_jnicallback.h deleted file mode 100644 index 2f26f8dbe..000000000 --- a/java/rocksjni/compaction_filter_factory_jnicallback.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionFilterFactory. - -#ifndef JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_ - -#include - -#include - -#include "rocksdb/compaction_filter.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -class CompactionFilterFactoryJniCallback : public JniCallback, - public CompactionFilterFactory { - public: - CompactionFilterFactoryJniCallback(JNIEnv* env, - jobject jcompaction_filter_factory); - virtual std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context); - virtual const char* Name() const; - - private: - std::unique_ptr m_name; - jmethodID m_jcreate_compaction_filter_methodid; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_ diff --git a/java/rocksjni/compaction_job_info.cc b/java/rocksjni/compaction_job_info.cc deleted file mode 100644 index fb292f59c..000000000 --- a/java/rocksjni/compaction_job_info.cc +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionJobInfo. - -#include - -#include "include/org_rocksdb_CompactionJobInfo.h" -#include "rocksdb/listener.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: newCompactionJobInfo - * Signature: ()J - */ -jlong Java_org_rocksdb_CompactionJobInfo_newCompactionJobInfo(JNIEnv*, jclass) { - auto* compact_job_info = new ROCKSDB_NAMESPACE::CompactionJobInfo(); - return GET_CPLUSPLUS_POINTER(compact_job_info); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompactionJobInfo_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - delete compact_job_info; -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: columnFamilyName - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_CompactionJobInfo_columnFamilyName(JNIEnv* env, - jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::JniUtil::copyBytes(env, compact_job_info->cf_name); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: status - * Signature: (J)Lorg/rocksdb/Status; - */ -jobject Java_org_rocksdb_CompactionJobInfo_status(JNIEnv* env, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::StatusJni::construct(env, compact_job_info->status); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: threadId - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobInfo_threadId(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return static_cast(compact_job_info->thread_id); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: jobId - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionJobInfo_jobId(JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return static_cast(compact_job_info->job_id); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: baseInputLevel - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionJobInfo_baseInputLevel(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return static_cast(compact_job_info->base_input_level); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: outputLevel - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionJobInfo_outputLevel(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return static_cast(compact_job_info->output_level); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: inputFiles - * Signature: (J)[Ljava/lang/String; - */ -jobjectArray Java_org_rocksdb_CompactionJobInfo_inputFiles(JNIEnv* env, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::JniUtil::toJavaStrings( - env, &compact_job_info->input_files); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: outputFiles - * Signature: (J)[Ljava/lang/String; - */ -jobjectArray Java_org_rocksdb_CompactionJobInfo_outputFiles(JNIEnv* env, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::JniUtil::toJavaStrings( - env, &compact_job_info->output_files); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: tableProperties - * Signature: (J)Ljava/util/Map; - */ -jobject Java_org_rocksdb_CompactionJobInfo_tableProperties(JNIEnv* env, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - auto* map = &compact_job_info->table_properties; - - jobject jhash_map = ROCKSDB_NAMESPACE::HashMapJni::construct( - env, static_cast(map->size())); - if (jhash_map == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const std::string, - std::shared_ptr, jobject, - jobject> - fn_map_kv = - [env](const std::pair< - const std::string, - std::shared_ptr>& - kv) { - jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &(kv.first), false); - if (env->ExceptionCheck()) { - // an error occurred - return std::unique_ptr>(nullptr); - } - - jobject jtable_properties = - ROCKSDB_NAMESPACE::TablePropertiesJni::fromCppTableProperties( - env, *(kv.second.get())); - if (env->ExceptionCheck()) { - // an error occurred - env->DeleteLocalRef(jkey); - return std::unique_ptr>(nullptr); - } - - return std::unique_ptr>( - new std::pair(static_cast(jkey), - jtable_properties)); - }; - - if (!ROCKSDB_NAMESPACE::HashMapJni::putAll(env, jhash_map, map->begin(), - map->end(), fn_map_kv)) { - // exception occurred - return nullptr; - } - - return jhash_map; -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: compactionReason - * Signature: (J)B - */ -jbyte Java_org_rocksdb_CompactionJobInfo_compactionReason(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompactionReasonJni::toJavaCompactionReason( - compact_job_info->compaction_reason); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: compression - * Signature: (J)B - */ -jbyte Java_org_rocksdb_CompactionJobInfo_compression(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - compact_job_info->compression); -} - -/* - * Class: org_rocksdb_CompactionJobInfo - * Method: stats - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobInfo_stats(JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_info = - reinterpret_cast(jhandle); - auto* stats = new ROCKSDB_NAMESPACE::CompactionJobStats(); - stats->Add(compact_job_info->stats); - return GET_CPLUSPLUS_POINTER(stats); -} diff --git a/java/rocksjni/compaction_job_stats.cc b/java/rocksjni/compaction_job_stats.cc deleted file mode 100644 index a2599c132..000000000 --- a/java/rocksjni/compaction_job_stats.cc +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionJobStats. - -#include "rocksdb/compaction_job_stats.h" - -#include - -#include "include/org_rocksdb_CompactionJobStats.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: newCompactionJobStats - * Signature: ()J - */ -jlong Java_org_rocksdb_CompactionJobStats_newCompactionJobStats(JNIEnv*, - jclass) { - auto* compact_job_stats = new ROCKSDB_NAMESPACE::CompactionJobStats(); - return GET_CPLUSPLUS_POINTER(compact_job_stats); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompactionJobStats_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - delete compact_job_stats; -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: reset - * Signature: (J)V - */ -void Java_org_rocksdb_CompactionJobStats_reset(JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - compact_job_stats->Reset(); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: add - * Signature: (JJ)V - */ -void Java_org_rocksdb_CompactionJobStats_add(JNIEnv*, jclass, jlong jhandle, - jlong jother_handle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - auto* other_compact_job_stats = - reinterpret_cast(jother_handle); - compact_job_stats->Add(*other_compact_job_stats); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: elapsedMicros - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_elapsedMicros(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->elapsed_micros); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numInputRecords - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numInputRecords(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_input_records); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numInputFiles - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numInputFiles(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_input_files); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numInputFilesAtOutputLevel - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numInputFilesAtOutputLevel( - JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_input_files_at_output_level); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numOutputRecords - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numOutputRecords(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_output_records); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numOutputFiles - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numOutputFiles(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_output_files); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: isManualCompaction - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompactionJobStats_isManualCompaction(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - if (compact_job_stats->is_manual_compaction) { - return JNI_TRUE; - } else { - return JNI_FALSE; - } -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: totalInputBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_totalInputBytes(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->total_input_bytes); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: totalOutputBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_totalOutputBytes(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->total_output_bytes); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numRecordsReplaced - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numRecordsReplaced(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_records_replaced); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: totalInputRawKeyBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_totalInputRawKeyBytes(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->total_input_raw_key_bytes); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: totalInputRawValueBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_totalInputRawValueBytes( - JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->total_input_raw_value_bytes); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numInputDeletionRecords - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numInputDeletionRecords( - JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_input_deletion_records); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numExpiredDeletionRecords - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numExpiredDeletionRecords( - JNIEnv*, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_expired_deletion_records); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numCorruptKeys - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numCorruptKeys(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_corrupt_keys); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: fileWriteNanos - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_fileWriteNanos(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->file_write_nanos); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: fileRangeSyncNanos - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_fileRangeSyncNanos(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->file_range_sync_nanos); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: fileFsyncNanos - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_fileFsyncNanos(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->file_fsync_nanos); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: filePrepareWriteNanos - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_filePrepareWriteNanos(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->file_prepare_write_nanos); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: smallestOutputKeyPrefix - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_CompactionJobStats_smallestOutputKeyPrefix( - JNIEnv* env, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, compact_job_stats->smallest_output_key_prefix); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: largestOutputKeyPrefix - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_CompactionJobStats_largestOutputKeyPrefix( - JNIEnv* env, jclass, jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, compact_job_stats->largest_output_key_prefix); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numSingleDelFallthru - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numSingleDelFallthru(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_single_del_fallthru); -} - -/* - * Class: org_rocksdb_CompactionJobStats - * Method: numSingleDelMismatch - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionJobStats_numSingleDelMismatch(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_job_stats = - reinterpret_cast(jhandle); - return static_cast(compact_job_stats->num_single_del_mismatch); -} diff --git a/java/rocksjni/compaction_options.cc b/java/rocksjni/compaction_options.cc deleted file mode 100644 index bbbde0313..000000000 --- a/java/rocksjni/compaction_options.cc +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionOptions. - -#include - -#include "include/org_rocksdb_CompactionOptions.h" -#include "rocksdb/options.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_CompactionOptions - * Method: newCompactionOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_CompactionOptions_newCompactionOptions(JNIEnv*, jclass) { - auto* compact_opts = new ROCKSDB_NAMESPACE::CompactionOptions(); - return GET_CPLUSPLUS_POINTER(compact_opts); -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompactionOptions_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* compact_opts = - reinterpret_cast(jhandle); - delete compact_opts; -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: compression - * Signature: (J)B - */ -jbyte Java_org_rocksdb_CompactionOptions_compression(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_opts = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - compact_opts->compression); -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: setCompression - * Signature: (JB)V - */ -void Java_org_rocksdb_CompactionOptions_setCompression( - JNIEnv*, jclass, jlong jhandle, jbyte jcompression_type_value) { - auto* compact_opts = - reinterpret_cast(jhandle); - compact_opts->compression = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jcompression_type_value); -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: outputFileSizeLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionOptions_outputFileSizeLimit(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_opts = - reinterpret_cast(jhandle); - return static_cast(compact_opts->output_file_size_limit); -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: setOutputFileSizeLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_CompactionOptions_setOutputFileSizeLimit( - JNIEnv*, jclass, jlong jhandle, jlong joutput_file_size_limit) { - auto* compact_opts = - reinterpret_cast(jhandle); - compact_opts->output_file_size_limit = - static_cast(joutput_file_size_limit); -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: maxSubcompactions - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionOptions_maxSubcompactions(JNIEnv*, jclass, - jlong jhandle) { - auto* compact_opts = - reinterpret_cast(jhandle); - return static_cast(compact_opts->max_subcompactions); -} - -/* - * Class: org_rocksdb_CompactionOptions - * Method: setMaxSubcompactions - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactionOptions_setMaxSubcompactions( - JNIEnv*, jclass, jlong jhandle, jint jmax_subcompactions) { - auto* compact_opts = - reinterpret_cast(jhandle); - compact_opts->max_subcompactions = static_cast(jmax_subcompactions); -} diff --git a/java/rocksjni/compaction_options_fifo.cc b/java/rocksjni/compaction_options_fifo.cc deleted file mode 100644 index f6a47fec5..000000000 --- a/java/rocksjni/compaction_options_fifo.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionOptionsFIFO. - -#include - -#include "include/org_rocksdb_CompactionOptionsFIFO.h" -#include "rocksdb/advanced_options.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_CompactionOptionsFIFO - * Method: newCompactionOptionsFIFO - * Signature: ()J - */ -jlong Java_org_rocksdb_CompactionOptionsFIFO_newCompactionOptionsFIFO(JNIEnv*, - jclass) { - const auto* opt = new ROCKSDB_NAMESPACE::CompactionOptionsFIFO(); - return GET_CPLUSPLUS_POINTER(opt); -} - -/* - * Class: org_rocksdb_CompactionOptionsFIFO - * Method: setMaxTableFilesSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_CompactionOptionsFIFO_setMaxTableFilesSize( - JNIEnv*, jobject, jlong jhandle, jlong jmax_table_files_size) { - auto* opt = - reinterpret_cast(jhandle); - opt->max_table_files_size = static_cast(jmax_table_files_size); -} - -/* - * Class: org_rocksdb_CompactionOptionsFIFO - * Method: maxTableFilesSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompactionOptionsFIFO_maxTableFilesSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->max_table_files_size); -} - -/* - * Class: org_rocksdb_CompactionOptionsFIFO - * Method: setAllowCompaction - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompactionOptionsFIFO_setAllowCompaction( - JNIEnv*, jobject, jlong jhandle, jboolean allow_compaction) { - auto* opt = - reinterpret_cast(jhandle); - opt->allow_compaction = static_cast(allow_compaction); -} - -/* - * Class: org_rocksdb_CompactionOptionsFIFO - * Method: allowCompaction - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompactionOptionsFIFO_allowCompaction(JNIEnv*, - jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->allow_compaction); -} - -/* - * Class: org_rocksdb_CompactionOptionsFIFO - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompactionOptionsFIFO_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} diff --git a/java/rocksjni/compaction_options_universal.cc b/java/rocksjni/compaction_options_universal.cc deleted file mode 100644 index 9fc6f3158..000000000 --- a/java/rocksjni/compaction_options_universal.cc +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionOptionsUniversal. - -#include - -#include "include/org_rocksdb_CompactionOptionsUniversal.h" -#include "rocksdb/advanced_options.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: newCompactionOptionsUniversal - * Signature: ()J - */ -jlong Java_org_rocksdb_CompactionOptionsUniversal_newCompactionOptionsUniversal( - JNIEnv*, jclass) { - const auto* opt = new ROCKSDB_NAMESPACE::CompactionOptionsUniversal(); - return GET_CPLUSPLUS_POINTER(opt); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setSizeRatio - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setSizeRatio( - JNIEnv*, jobject, jlong jhandle, jint jsize_ratio) { - auto* opt = - reinterpret_cast(jhandle); - opt->size_ratio = static_cast(jsize_ratio); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: sizeRatio - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionOptionsUniversal_sizeRatio(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->size_ratio); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setMinMergeWidth - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setMinMergeWidth( - JNIEnv*, jobject, jlong jhandle, jint jmin_merge_width) { - auto* opt = - reinterpret_cast(jhandle); - opt->min_merge_width = static_cast(jmin_merge_width); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: minMergeWidth - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionOptionsUniversal_minMergeWidth(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->min_merge_width); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setMaxMergeWidth - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setMaxMergeWidth( - JNIEnv*, jobject, jlong jhandle, jint jmax_merge_width) { - auto* opt = - reinterpret_cast(jhandle); - opt->max_merge_width = static_cast(jmax_merge_width); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: maxMergeWidth - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionOptionsUniversal_maxMergeWidth(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->max_merge_width); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setMaxSizeAmplificationPercent - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setMaxSizeAmplificationPercent( - JNIEnv*, jobject, jlong jhandle, jint jmax_size_amplification_percent) { - auto* opt = - reinterpret_cast(jhandle); - opt->max_size_amplification_percent = - static_cast(jmax_size_amplification_percent); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: maxSizeAmplificationPercent - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionOptionsUniversal_maxSizeAmplificationPercent( - JNIEnv*, jobject, jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->max_size_amplification_percent); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setCompressionSizePercent - * Signature: (JI)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setCompressionSizePercent( - JNIEnv*, jobject, jlong jhandle, jint jcompression_size_percent) { - auto* opt = - reinterpret_cast(jhandle); - opt->compression_size_percent = - static_cast(jcompression_size_percent); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: compressionSizePercent - * Signature: (J)I - */ -jint Java_org_rocksdb_CompactionOptionsUniversal_compressionSizePercent( - JNIEnv*, jobject, jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->compression_size_percent); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setStopStyle - * Signature: (JB)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setStopStyle( - JNIEnv*, jobject, jlong jhandle, jbyte jstop_style_value) { - auto* opt = - reinterpret_cast(jhandle); - opt->stop_style = - ROCKSDB_NAMESPACE::CompactionStopStyleJni::toCppCompactionStopStyle( - jstop_style_value); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: stopStyle - * Signature: (J)B - */ -jbyte Java_org_rocksdb_CompactionOptionsUniversal_stopStyle(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompactionStopStyleJni::toJavaCompactionStopStyle( - opt->stop_style); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: setAllowTrivialMove - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_setAllowTrivialMove( - JNIEnv*, jobject, jlong jhandle, jboolean jallow_trivial_move) { - auto* opt = - reinterpret_cast(jhandle); - opt->allow_trivial_move = static_cast(jallow_trivial_move); -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: allowTrivialMove - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompactionOptionsUniversal_allowTrivialMove( - JNIEnv*, jobject, jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return opt->allow_trivial_move; -} - -/* - * Class: org_rocksdb_CompactionOptionsUniversal - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompactionOptionsUniversal_disposeInternal( - JNIEnv*, jobject, jlong jhandle) { - delete reinterpret_cast( - jhandle); -} diff --git a/java/rocksjni/comparator.cc b/java/rocksjni/comparator.cc deleted file mode 100644 index 11279c4ce..000000000 --- a/java/rocksjni/comparator.cc +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Comparator. - -#include -#include -#include - -#include -#include - -#include "include/org_rocksdb_AbstractComparator.h" -#include "include/org_rocksdb_NativeComparatorWrapper.h" -#include "rocksjni/comparatorjnicallback.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_AbstractComparator - * Method: createNewComparator - * Signature: (J)J - */ -jlong Java_org_rocksdb_AbstractComparator_createNewComparator( - JNIEnv* env, jobject jcomparator, jlong copt_handle) { - auto* copt = - reinterpret_cast( - copt_handle); - auto* c = - new ROCKSDB_NAMESPACE::ComparatorJniCallback(env, jcomparator, copt); - return GET_CPLUSPLUS_POINTER(c); -} - -/* - * Class: org_rocksdb_AbstractComparator - * Method: usingDirectBuffers - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_AbstractComparator_usingDirectBuffers(JNIEnv*, - jobject, - jlong jhandle) { - auto* c = - reinterpret_cast(jhandle); - return static_cast(c->m_options->direct_buffer); -} - -/* - * Class: org_rocksdb_NativeComparatorWrapper - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_NativeComparatorWrapper_disposeInternal( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jcomparator_handle) { - auto* comparator = - reinterpret_cast(jcomparator_handle); - delete comparator; -} diff --git a/java/rocksjni/comparatorjnicallback.cc b/java/rocksjni/comparatorjnicallback.cc deleted file mode 100644 index d354b40b8..000000000 --- a/java/rocksjni/comparatorjnicallback.cc +++ /dev/null @@ -1,647 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Comparator. - -#include "rocksjni/comparatorjnicallback.h" - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -ComparatorJniCallback::ComparatorJniCallback( - JNIEnv* env, jobject jcomparator, - const ComparatorJniCallbackOptions* options) - : JniCallback(env, jcomparator), - m_options(std::make_unique(*options)) { - // cache the AbstractComparatorJniBridge class as we will reuse it many times - // for each callback - m_abstract_comparator_jni_bridge_clazz = static_cast( - env->NewGlobalRef(AbstractComparatorJniBridge::getJClass(env))); - - // Note: The name of a Comparator will not change during it's lifetime, - // so we cache it in a global var - jmethodID jname_mid = AbstractComparatorJni::getNameMethodId(env); - if (jname_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - jstring js_name = (jstring)env->CallObjectMethod(m_jcallback_obj, jname_mid); - if (env->ExceptionCheck()) { - // exception thrown - return; - } - jboolean has_exception = JNI_FALSE; - m_name = JniUtil::copyString(env, js_name, - &has_exception); // also releases jsName - if (has_exception == JNI_TRUE) { - // exception thrown - return; - } - - // cache the ByteBuffer class as we will reuse it many times for each callback - m_jbytebuffer_clazz = - static_cast(env->NewGlobalRef(ByteBufferJni::getJClass(env))); - - m_jcompare_mid = AbstractComparatorJniBridge::getCompareInternalMethodId( - env, m_abstract_comparator_jni_bridge_clazz); - if (m_jcompare_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - m_jshortest_mid = - AbstractComparatorJniBridge::getFindShortestSeparatorInternalMethodId( - env, m_abstract_comparator_jni_bridge_clazz); - if (m_jshortest_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - m_jshort_mid = - AbstractComparatorJniBridge::getFindShortSuccessorInternalMethodId( - env, m_abstract_comparator_jni_bridge_clazz); - if (m_jshort_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - // do we need reusable buffers? - if (m_options->max_reused_buffer_size > -1) { - if (m_options->reused_synchronisation_type == - ReusedSynchronisationType::THREAD_LOCAL) { - // buffers reused per thread - UnrefHandler unref = [](void* ptr) { - ThreadLocalBuf* tlb = reinterpret_cast(ptr); - jboolean attached_thread = JNI_FALSE; - JNIEnv* _env = JniUtil::getJniEnv(tlb->jvm, &attached_thread); - if (_env != nullptr) { - if (tlb->direct_buffer) { - void* buf = _env->GetDirectBufferAddress(tlb->jbuf); - delete[] static_cast(buf); - } - _env->DeleteGlobalRef(tlb->jbuf); - JniUtil::releaseJniEnv(tlb->jvm, attached_thread); - } - }; - - m_tl_buf_a = new ThreadLocalPtr(unref); - m_tl_buf_b = new ThreadLocalPtr(unref); - - m_jcompare_buf_a = nullptr; - m_jcompare_buf_b = nullptr; - m_jshortest_buf_start = nullptr; - m_jshortest_buf_limit = nullptr; - m_jshort_buf_key = nullptr; - - } else { - // buffers reused and shared across threads - const bool adaptive = m_options->reused_synchronisation_type == - ReusedSynchronisationType::ADAPTIVE_MUTEX; - mtx_compare = std::unique_ptr(new port::Mutex(adaptive)); - mtx_shortest = std::unique_ptr(new port::Mutex(adaptive)); - mtx_short = std::unique_ptr(new port::Mutex(adaptive)); - - m_jcompare_buf_a = env->NewGlobalRef(ByteBufferJni::construct( - env, m_options->direct_buffer, m_options->max_reused_buffer_size, - m_jbytebuffer_clazz)); - if (m_jcompare_buf_a == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - m_jcompare_buf_b = env->NewGlobalRef(ByteBufferJni::construct( - env, m_options->direct_buffer, m_options->max_reused_buffer_size, - m_jbytebuffer_clazz)); - if (m_jcompare_buf_b == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - m_jshortest_buf_start = env->NewGlobalRef(ByteBufferJni::construct( - env, m_options->direct_buffer, m_options->max_reused_buffer_size, - m_jbytebuffer_clazz)); - if (m_jshortest_buf_start == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - m_jshortest_buf_limit = env->NewGlobalRef(ByteBufferJni::construct( - env, m_options->direct_buffer, m_options->max_reused_buffer_size, - m_jbytebuffer_clazz)); - if (m_jshortest_buf_limit == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - m_jshort_buf_key = env->NewGlobalRef(ByteBufferJni::construct( - env, m_options->direct_buffer, m_options->max_reused_buffer_size, - m_jbytebuffer_clazz)); - if (m_jshort_buf_key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - m_tl_buf_a = nullptr; - m_tl_buf_b = nullptr; - } - - } else { - m_jcompare_buf_a = nullptr; - m_jcompare_buf_b = nullptr; - m_jshortest_buf_start = nullptr; - m_jshortest_buf_limit = nullptr; - m_jshort_buf_key = nullptr; - - m_tl_buf_a = nullptr; - m_tl_buf_b = nullptr; - } -} - -ComparatorJniCallback::~ComparatorJniCallback() { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - env->DeleteGlobalRef(m_abstract_comparator_jni_bridge_clazz); - - env->DeleteGlobalRef(m_jbytebuffer_clazz); - - if (m_jcompare_buf_a != nullptr) { - if (m_options->direct_buffer) { - void* buf = env->GetDirectBufferAddress(m_jcompare_buf_a); - delete[] static_cast(buf); - } - env->DeleteGlobalRef(m_jcompare_buf_a); - } - - if (m_jcompare_buf_b != nullptr) { - if (m_options->direct_buffer) { - void* buf = env->GetDirectBufferAddress(m_jcompare_buf_b); - delete[] static_cast(buf); - } - env->DeleteGlobalRef(m_jcompare_buf_b); - } - - if (m_jshortest_buf_start != nullptr) { - if (m_options->direct_buffer) { - void* buf = env->GetDirectBufferAddress(m_jshortest_buf_start); - delete[] static_cast(buf); - } - env->DeleteGlobalRef(m_jshortest_buf_start); - } - - if (m_jshortest_buf_limit != nullptr) { - if (m_options->direct_buffer) { - void* buf = env->GetDirectBufferAddress(m_jshortest_buf_limit); - delete[] static_cast(buf); - } - env->DeleteGlobalRef(m_jshortest_buf_limit); - } - - if (m_jshort_buf_key != nullptr) { - if (m_options->direct_buffer) { - void* buf = env->GetDirectBufferAddress(m_jshort_buf_key); - delete[] static_cast(buf); - } - env->DeleteGlobalRef(m_jshort_buf_key); - } - - if (m_tl_buf_a != nullptr) { - delete m_tl_buf_a; - } - - if (m_tl_buf_b != nullptr) { - delete m_tl_buf_b; - } - - releaseJniEnv(attached_thread); -} - -const char* ComparatorJniCallback::Name() const { return m_name.get(); } - -int ComparatorJniCallback::Compare(const Slice& a, const Slice& b) const { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - const bool reuse_jbuf_a = - static_cast(a.size()) <= m_options->max_reused_buffer_size; - const bool reuse_jbuf_b = - static_cast(b.size()) <= m_options->max_reused_buffer_size; - - MaybeLockForReuse(mtx_compare, reuse_jbuf_a || reuse_jbuf_b); - - jobject jcompare_buf_a = - GetBuffer(env, a, reuse_jbuf_a, m_tl_buf_a, m_jcompare_buf_a); - if (jcompare_buf_a == nullptr) { - // exception occurred - MaybeUnlockForReuse(mtx_compare, reuse_jbuf_a || reuse_jbuf_b); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return 0; - } - - jobject jcompare_buf_b = - GetBuffer(env, b, reuse_jbuf_b, m_tl_buf_b, m_jcompare_buf_b); - if (jcompare_buf_b == nullptr) { - // exception occurred - if (!reuse_jbuf_a) { - DeleteBuffer(env, jcompare_buf_a); - } - MaybeUnlockForReuse(mtx_compare, reuse_jbuf_a || reuse_jbuf_b); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return 0; - } - - jint result = env->CallStaticIntMethod( - m_abstract_comparator_jni_bridge_clazz, m_jcompare_mid, m_jcallback_obj, - jcompare_buf_a, reuse_jbuf_a ? a.size() : -1, jcompare_buf_b, - reuse_jbuf_b ? b.size() : -1); - - if (env->ExceptionCheck()) { - // exception thrown from CallIntMethod - env->ExceptionDescribe(); // print out exception to stderr - result = 0; // we could not get a result from java callback so use 0 - } - - if (!reuse_jbuf_a) { - DeleteBuffer(env, jcompare_buf_a); - } - if (!reuse_jbuf_b) { - DeleteBuffer(env, jcompare_buf_b); - } - - MaybeUnlockForReuse(mtx_compare, reuse_jbuf_a || reuse_jbuf_b); - - releaseJniEnv(attached_thread); - - return result; -} - -void ComparatorJniCallback::FindShortestSeparator(std::string* start, - const Slice& limit) const { - if (start == nullptr) { - return; - } - - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - const bool reuse_jbuf_start = static_cast(start->length()) <= - m_options->max_reused_buffer_size; - const bool reuse_jbuf_limit = - static_cast(limit.size()) <= m_options->max_reused_buffer_size; - - MaybeLockForReuse(mtx_shortest, reuse_jbuf_start || reuse_jbuf_limit); - - Slice sstart(start->data(), start->length()); - jobject j_start_buf = GetBuffer(env, sstart, reuse_jbuf_start, m_tl_buf_a, - m_jshortest_buf_start); - if (j_start_buf == nullptr) { - // exception occurred - MaybeUnlockForReuse(mtx_shortest, reuse_jbuf_start || reuse_jbuf_limit); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - - jobject j_limit_buf = GetBuffer(env, limit, reuse_jbuf_limit, m_tl_buf_b, - m_jshortest_buf_limit); - if (j_limit_buf == nullptr) { - // exception occurred - if (!reuse_jbuf_start) { - DeleteBuffer(env, j_start_buf); - } - MaybeUnlockForReuse(mtx_shortest, reuse_jbuf_start || reuse_jbuf_limit); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - - jint jstart_len = env->CallStaticIntMethod( - m_abstract_comparator_jni_bridge_clazz, m_jshortest_mid, m_jcallback_obj, - j_start_buf, reuse_jbuf_start ? start->length() : -1, j_limit_buf, - reuse_jbuf_limit ? limit.size() : -1); - - if (env->ExceptionCheck()) { - // exception thrown from CallIntMethod - env->ExceptionDescribe(); // print out exception to stderr - - } else if (static_cast(jstart_len) != start->length()) { - // start buffer has changed in Java, so update `start` with the result - bool copy_from_non_direct = false; - if (reuse_jbuf_start) { - // reused a buffer - if (m_options->direct_buffer) { - // reused direct buffer - void* start_buf = env->GetDirectBufferAddress(j_start_buf); - if (start_buf == nullptr) { - if (!reuse_jbuf_start) { - DeleteBuffer(env, j_start_buf); - } - if (!reuse_jbuf_limit) { - DeleteBuffer(env, j_limit_buf); - } - MaybeUnlockForReuse(mtx_shortest, - reuse_jbuf_start || reuse_jbuf_limit); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Unable to get Direct Buffer Address"); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - start->assign(static_cast(start_buf), jstart_len); - - } else { - // reused non-direct buffer - copy_from_non_direct = true; - } - } else { - // there was a new buffer - if (m_options->direct_buffer) { - // it was direct... don't forget to potentially truncate the `start` - // string - start->resize(jstart_len); - } else { - // it was non-direct - copy_from_non_direct = true; - } - } - - if (copy_from_non_direct) { - jbyteArray jarray = - ByteBufferJni::array(env, j_start_buf, m_jbytebuffer_clazz); - if (jarray == nullptr) { - if (!reuse_jbuf_start) { - DeleteBuffer(env, j_start_buf); - } - if (!reuse_jbuf_limit) { - DeleteBuffer(env, j_limit_buf); - } - MaybeUnlockForReuse(mtx_shortest, reuse_jbuf_start || reuse_jbuf_limit); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - jboolean has_exception = JNI_FALSE; - JniUtil::byteString( - env, jarray, - [start, jstart_len](const char* data, const size_t) { - return start->assign(data, static_cast(jstart_len)); - }, - &has_exception); - env->DeleteLocalRef(jarray); - if (has_exception == JNI_TRUE) { - if (!reuse_jbuf_start) { - DeleteBuffer(env, j_start_buf); - } - if (!reuse_jbuf_limit) { - DeleteBuffer(env, j_limit_buf); - } - env->ExceptionDescribe(); // print out exception to stderr - MaybeUnlockForReuse(mtx_shortest, reuse_jbuf_start || reuse_jbuf_limit); - releaseJniEnv(attached_thread); - return; - } - } - } - - if (!reuse_jbuf_start) { - DeleteBuffer(env, j_start_buf); - } - if (!reuse_jbuf_limit) { - DeleteBuffer(env, j_limit_buf); - } - - MaybeUnlockForReuse(mtx_shortest, reuse_jbuf_start || reuse_jbuf_limit); - - releaseJniEnv(attached_thread); -} - -void ComparatorJniCallback::FindShortSuccessor(std::string* key) const { - if (key == nullptr) { - return; - } - - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - const bool reuse_jbuf_key = - static_cast(key->length()) <= m_options->max_reused_buffer_size; - - MaybeLockForReuse(mtx_short, reuse_jbuf_key); - - Slice skey(key->data(), key->length()); - jobject j_key_buf = - GetBuffer(env, skey, reuse_jbuf_key, m_tl_buf_a, m_jshort_buf_key); - if (j_key_buf == nullptr) { - // exception occurred - MaybeUnlockForReuse(mtx_short, reuse_jbuf_key); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - - jint jkey_len = env->CallStaticIntMethod( - m_abstract_comparator_jni_bridge_clazz, m_jshort_mid, m_jcallback_obj, - j_key_buf, reuse_jbuf_key ? key->length() : -1); - - if (env->ExceptionCheck()) { - // exception thrown from CallObjectMethod - if (!reuse_jbuf_key) { - DeleteBuffer(env, j_key_buf); - } - MaybeUnlockForReuse(mtx_short, reuse_jbuf_key); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - - if (static_cast(jkey_len) != key->length()) { - // key buffer has changed in Java, so update `key` with the result - bool copy_from_non_direct = false; - if (reuse_jbuf_key) { - // reused a buffer - if (m_options->direct_buffer) { - // reused direct buffer - void* key_buf = env->GetDirectBufferAddress(j_key_buf); - if (key_buf == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Unable to get Direct Buffer Address"); - if (!reuse_jbuf_key) { - DeleteBuffer(env, j_key_buf); - } - MaybeUnlockForReuse(mtx_short, reuse_jbuf_key); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - key->assign(static_cast(key_buf), jkey_len); - } else { - // reused non-direct buffer - copy_from_non_direct = true; - } - } else { - // there was a new buffer - if (m_options->direct_buffer) { - // it was direct... don't forget to potentially truncate the `key` - // string - key->resize(jkey_len); - } else { - // it was non-direct - copy_from_non_direct = true; - } - } - - if (copy_from_non_direct) { - jbyteArray jarray = - ByteBufferJni::array(env, j_key_buf, m_jbytebuffer_clazz); - if (jarray == nullptr) { - if (!reuse_jbuf_key) { - DeleteBuffer(env, j_key_buf); - } - MaybeUnlockForReuse(mtx_short, reuse_jbuf_key); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - jboolean has_exception = JNI_FALSE; - JniUtil::byteString( - env, jarray, - [key, jkey_len](const char* data, const size_t) { - return key->assign(data, static_cast(jkey_len)); - }, - &has_exception); - env->DeleteLocalRef(jarray); - if (has_exception == JNI_TRUE) { - if (!reuse_jbuf_key) { - DeleteBuffer(env, j_key_buf); - } - MaybeUnlockForReuse(mtx_short, reuse_jbuf_key); - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - } - } - - if (!reuse_jbuf_key) { - DeleteBuffer(env, j_key_buf); - } - - MaybeUnlockForReuse(mtx_short, reuse_jbuf_key); - - releaseJniEnv(attached_thread); -} - -inline void ComparatorJniCallback::MaybeLockForReuse( - const std::unique_ptr& mutex, const bool cond) const { - // no need to lock if using thread_local - if (m_options->reused_synchronisation_type != - ReusedSynchronisationType::THREAD_LOCAL && - cond) { - mutex.get()->Lock(); - } -} - -inline void ComparatorJniCallback::MaybeUnlockForReuse( - const std::unique_ptr& mutex, const bool cond) const { - // no need to unlock if using thread_local - if (m_options->reused_synchronisation_type != - ReusedSynchronisationType::THREAD_LOCAL && - cond) { - mutex.get()->Unlock(); - } -} - -jobject ComparatorJniCallback::GetBuffer(JNIEnv* env, const Slice& src, - bool reuse_buffer, - ThreadLocalPtr* tl_buf, - jobject jreuse_buffer) const { - if (reuse_buffer) { - if (m_options->reused_synchronisation_type == - ReusedSynchronisationType::THREAD_LOCAL) { - // reuse thread-local bufffer - ThreadLocalBuf* tlb = reinterpret_cast(tl_buf->Get()); - if (tlb == nullptr) { - // thread-local buffer has not yet been created, so create it - jobject jtl_buf = env->NewGlobalRef(ByteBufferJni::construct( - env, m_options->direct_buffer, m_options->max_reused_buffer_size, - m_jbytebuffer_clazz)); - if (jtl_buf == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - tlb = new ThreadLocalBuf(m_jvm, m_options->direct_buffer, jtl_buf); - tl_buf->Reset(tlb); - } - return ReuseBuffer(env, src, tlb->jbuf); - } else { - // reuse class member buffer - return ReuseBuffer(env, src, jreuse_buffer); - } - } else { - // new buffer - return NewBuffer(env, src); - } -} - -jobject ComparatorJniCallback::ReuseBuffer(JNIEnv* env, const Slice& src, - jobject jreuse_buffer) const { - // we can reuse the buffer - if (m_options->direct_buffer) { - // copy into direct buffer - void* buf = env->GetDirectBufferAddress(jreuse_buffer); - if (buf == nullptr) { - // either memory region is undefined, given object is not a direct - // java.nio.Buffer, or JNI access to direct buffers is not supported by - // this virtual machine. - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Unable to get Direct Buffer Address"); - return nullptr; - } - memcpy(buf, src.data(), src.size()); - } else { - // copy into non-direct buffer - const jbyteArray jarray = - ByteBufferJni::array(env, jreuse_buffer, m_jbytebuffer_clazz); - if (jarray == nullptr) { - // exception occurred - return nullptr; - } - env->SetByteArrayRegion( - jarray, 0, static_cast(src.size()), - const_cast(reinterpret_cast(src.data()))); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(jarray); - return nullptr; - } - env->DeleteLocalRef(jarray); - } - return jreuse_buffer; -} - -jobject ComparatorJniCallback::NewBuffer(JNIEnv* env, const Slice& src) const { - // we need a new buffer - jobject jbuf = - ByteBufferJni::constructWith(env, m_options->direct_buffer, src.data(), - src.size(), m_jbytebuffer_clazz); - if (jbuf == nullptr) { - // exception occurred - return nullptr; - } - return jbuf; -} - -void ComparatorJniCallback::DeleteBuffer(JNIEnv* env, jobject jbuffer) const { - env->DeleteLocalRef(jbuffer); -} - -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/comparatorjnicallback.h b/java/rocksjni/comparatorjnicallback.h deleted file mode 100644 index 034c0d5d7..000000000 --- a/java/rocksjni/comparatorjnicallback.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Comparator - -#ifndef JAVA_ROCKSJNI_COMPARATORJNICALLBACK_H_ -#define JAVA_ROCKSJNI_COMPARATORJNICALLBACK_H_ - -#include - -#include -#include - -#include "port/port.h" -#include "rocksdb/comparator.h" -#include "rocksdb/slice.h" -#include "rocksjni/jnicallback.h" -#include "util/thread_local.h" - -namespace ROCKSDB_NAMESPACE { - -enum ReusedSynchronisationType { - /** - * Standard mutex. - */ - MUTEX, - - /** - * Use adaptive mutex, which spins in the user space before resorting - * to kernel. This could reduce context switch when the mutex is not - * heavily contended. However, if the mutex is hot, we could end up - * wasting spin time. - */ - ADAPTIVE_MUTEX, - - /** - * There is a reused buffer per-thread. - */ - THREAD_LOCAL -}; - -struct ComparatorJniCallbackOptions { - // Set the synchronisation type used to guard the reused buffers. - // Only used if max_reused_buffer_size > 0. - ReusedSynchronisationType reused_synchronisation_type = ADAPTIVE_MUTEX; - - // Indicates if a direct byte buffer (i.e. outside of the normal - // garbage-collected heap) is used for the callbacks to Java, - // as opposed to a non-direct byte buffer which is a wrapper around - // an on-heap byte[]. - bool direct_buffer = true; - - // Maximum size of a buffer (in bytes) that will be reused. - // Comparators will use 5 of these buffers, - // so the retained memory size will be 5 * max_reused_buffer_size. - // When a buffer is needed for transferring data to a callback, - // if it requires less than max_reused_buffer_size, then an - // existing buffer will be reused, else a new buffer will be - // allocated just for that callback. -1 to disable. - int32_t max_reused_buffer_size = 64; -}; - -/** - * This class acts as a bridge between C++ - * and Java. The methods in this class will be - * called back from the RocksDB storage engine (C++) - * we then callback to the appropriate Java method - * this enables Comparators to be implemented in Java. - * - * The design of this Comparator caches the Java Slice - * objects that are used in the compare and findShortestSeparator - * method callbacks. Instead of creating new objects for each callback - * of those functions, by reuse via setHandle we are a lot - * faster; Unfortunately this means that we have to - * introduce independent locking in regions of each of those methods - * via the mutexs mtx_compare and mtx_findShortestSeparator respectively - */ -class ComparatorJniCallback : public JniCallback, public Comparator { - public: - ComparatorJniCallback(JNIEnv* env, jobject jcomparator, - const ComparatorJniCallbackOptions* options); - ~ComparatorJniCallback(); - virtual const char* Name() const; - virtual int Compare(const Slice& a, const Slice& b) const; - virtual void FindShortestSeparator(std::string* start, - const Slice& limit) const; - virtual void FindShortSuccessor(std::string* key) const; - const std::unique_ptr m_options; - - private: - struct ThreadLocalBuf { - ThreadLocalBuf(JavaVM* _jvm, bool _direct_buffer, jobject _jbuf) - : jvm(_jvm), direct_buffer(_direct_buffer), jbuf(_jbuf) {} - JavaVM* jvm; - bool direct_buffer; - jobject jbuf; - }; - inline void MaybeLockForReuse(const std::unique_ptr& mutex, - const bool cond) const; - inline void MaybeUnlockForReuse(const std::unique_ptr& mutex, - const bool cond) const; - jobject GetBuffer(JNIEnv* env, const Slice& src, bool reuse_buffer, - ThreadLocalPtr* tl_buf, jobject jreuse_buffer) const; - jobject ReuseBuffer(JNIEnv* env, const Slice& src, - jobject jreuse_buffer) const; - jobject NewBuffer(JNIEnv* env, const Slice& src) const; - void DeleteBuffer(JNIEnv* env, jobject jbuffer) const; - // used for synchronisation in compare method - std::unique_ptr mtx_compare; - // used for synchronisation in findShortestSeparator method - std::unique_ptr mtx_shortest; - // used for synchronisation in findShortSuccessor method - std::unique_ptr mtx_short; - std::unique_ptr m_name; - jclass m_abstract_comparator_jni_bridge_clazz; // TODO(AR) could we make this - // static somehow? - jclass m_jbytebuffer_clazz; // TODO(AR) we could cache this globally for the - // entire VM if we switch more APIs to use - // ByteBuffer // TODO(AR) could we make this - // static somehow? - jmethodID m_jcompare_mid; // TODO(AR) could we make this static somehow? - jmethodID m_jshortest_mid; // TODO(AR) could we make this static somehow? - jmethodID m_jshort_mid; // TODO(AR) could we make this static somehow? - jobject m_jcompare_buf_a; - jobject m_jcompare_buf_b; - jobject m_jshortest_buf_start; - jobject m_jshortest_buf_limit; - jobject m_jshort_buf_key; - ThreadLocalPtr* m_tl_buf_a; - ThreadLocalPtr* m_tl_buf_b; -}; -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_COMPARATORJNICALLBACK_H_ diff --git a/java/rocksjni/compression_options.cc b/java/rocksjni/compression_options.cc deleted file mode 100644 index 53f240560..000000000 --- a/java/rocksjni/compression_options.cc +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompressionOptions. - -#include - -#include "include/org_rocksdb_CompressionOptions.h" -#include "rocksdb/advanced_options.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_CompressionOptions - * Method: newCompressionOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_CompressionOptions_newCompressionOptions(JNIEnv*, - jclass) { - const auto* opt = new ROCKSDB_NAMESPACE::CompressionOptions(); - return GET_CPLUSPLUS_POINTER(opt); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setWindowBits - * Signature: (JI)V - */ -void Java_org_rocksdb_CompressionOptions_setWindowBits(JNIEnv*, jobject, - jlong jhandle, - jint jwindow_bits) { - auto* opt = reinterpret_cast(jhandle); - opt->window_bits = static_cast(jwindow_bits); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: windowBits - * Signature: (J)I - */ -jint Java_org_rocksdb_CompressionOptions_windowBits(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->window_bits); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setLevel - * Signature: (JI)V - */ -void Java_org_rocksdb_CompressionOptions_setLevel(JNIEnv*, jobject, - jlong jhandle, jint jlevel) { - auto* opt = reinterpret_cast(jhandle); - opt->level = static_cast(jlevel); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: level - * Signature: (J)I - */ -jint Java_org_rocksdb_CompressionOptions_level(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->level); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setStrategy - * Signature: (JI)V - */ -void Java_org_rocksdb_CompressionOptions_setStrategy(JNIEnv*, jobject, - jlong jhandle, - jint jstrategy) { - auto* opt = reinterpret_cast(jhandle); - opt->strategy = static_cast(jstrategy); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: strategy - * Signature: (J)I - */ -jint Java_org_rocksdb_CompressionOptions_strategy(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->strategy); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setMaxDictBytes - * Signature: (JI)V - */ -void Java_org_rocksdb_CompressionOptions_setMaxDictBytes(JNIEnv*, jobject, - jlong jhandle, - jint jmax_dict_bytes) { - auto* opt = reinterpret_cast(jhandle); - opt->max_dict_bytes = static_cast(jmax_dict_bytes); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: maxDictBytes - * Signature: (J)I - */ -jint Java_org_rocksdb_CompressionOptions_maxDictBytes(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_dict_bytes); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setZstdMaxTrainBytes - * Signature: (JI)V - */ -void Java_org_rocksdb_CompressionOptions_setZstdMaxTrainBytes( - JNIEnv*, jobject, jlong jhandle, jint jzstd_max_train_bytes) { - auto* opt = reinterpret_cast(jhandle); - opt->zstd_max_train_bytes = static_cast(jzstd_max_train_bytes); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: zstdMaxTrainBytes - * Signature: (J)I - */ -jint Java_org_rocksdb_CompressionOptions_zstdMaxTrainBytes(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->zstd_max_train_bytes); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setMaxDictBufferBytes - * Signature: (JJ)V - */ -void Java_org_rocksdb_CompressionOptions_setMaxDictBufferBytes( - JNIEnv*, jobject, jlong jhandle, jlong jmax_dict_buffer_bytes) { - auto* opt = reinterpret_cast(jhandle); - opt->max_dict_buffer_bytes = static_cast(jmax_dict_buffer_bytes); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: maxDictBufferBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_CompressionOptions_maxDictBufferBytes(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_dict_buffer_bytes); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setZstdMaxTrainBytes - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompressionOptions_setUseZstdDictTrainer( - JNIEnv*, jobject, jlong jhandle, jboolean juse_zstd_dict_trainer) { - auto* opt = reinterpret_cast(jhandle); - opt->use_zstd_dict_trainer = juse_zstd_dict_trainer == JNI_TRUE; -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: zstdMaxTrainBytes - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompressionOptions_useZstdDictTrainer(JNIEnv*, - jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->use_zstd_dict_trainer); -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: setEnabled - * Signature: (JZ)V - */ -void Java_org_rocksdb_CompressionOptions_setEnabled(JNIEnv*, jobject, - jlong jhandle, - jboolean jenabled) { - auto* opt = reinterpret_cast(jhandle); - opt->enabled = jenabled == JNI_TRUE; -} - -/* - * Class: org_rocksdb_CompressionOptions - * Method: enabled - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_CompressionOptions_enabled(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->enabled); -} -/* - * Class: org_rocksdb_CompressionOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_CompressionOptions_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} diff --git a/java/rocksjni/concurrent_task_limiter.cc b/java/rocksjni/concurrent_task_limiter.cc deleted file mode 100644 index 0b0b2d271..000000000 --- a/java/rocksjni/concurrent_task_limiter.cc +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/concurrent_task_limiter.h" - -#include - -#include -#include - -#include "include/org_rocksdb_ConcurrentTaskLimiterImpl.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_ConcurrentTaskLimiterImpl - * Method: newConcurrentTaskLimiterImpl0 - * Signature: (Ljava/lang/String;I)J - */ -jlong Java_org_rocksdb_ConcurrentTaskLimiterImpl_newConcurrentTaskLimiterImpl0( - JNIEnv* env, jclass, jstring jname, jint limit) { - jboolean has_exception = JNI_FALSE; - std::string name = - ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jname, &has_exception); - if (JNI_TRUE == has_exception) { - return 0; - } - - auto* ptr = new std::shared_ptr( - ROCKSDB_NAMESPACE::NewConcurrentTaskLimiter(name, limit)); - - return GET_CPLUSPLUS_POINTER(ptr); -} - -/* - * Class: org_rocksdb_ConcurrentTaskLimiterImpl - * Method: name - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ConcurrentTaskLimiterImpl_name(JNIEnv* env, jclass, - jlong handle) { - const auto& limiter = *reinterpret_cast< - std::shared_ptr*>(handle); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &limiter->GetName()); -} - -/* - * Class: org_rocksdb_ConcurrentTaskLimiterImpl - * Method: setMaxOutstandingTask - * Signature: (JI)V - */ -void Java_org_rocksdb_ConcurrentTaskLimiterImpl_setMaxOutstandingTask( - JNIEnv*, jclass, jlong handle, jint max_outstanding_task) { - const auto& limiter = *reinterpret_cast< - std::shared_ptr*>(handle); - limiter->SetMaxOutstandingTask(static_cast(max_outstanding_task)); -} - -/* - * Class: org_rocksdb_ConcurrentTaskLimiterImpl - * Method: resetMaxOutstandingTask - * Signature: (J)V - */ -void Java_org_rocksdb_ConcurrentTaskLimiterImpl_resetMaxOutstandingTask( - JNIEnv*, jclass, jlong handle) { - const auto& limiter = *reinterpret_cast< - std::shared_ptr*>(handle); - limiter->ResetMaxOutstandingTask(); -} - -/* - * Class: org_rocksdb_ConcurrentTaskLimiterImpl - * Method: outstandingTask - * Signature: (J)I - */ -jint Java_org_rocksdb_ConcurrentTaskLimiterImpl_outstandingTask(JNIEnv*, jclass, - jlong handle) { - const auto& limiter = *reinterpret_cast< - std::shared_ptr*>(handle); - return static_cast(limiter->GetOutstandingTask()); -} - -/* - * Class: org_rocksdb_ConcurrentTaskLimiterImpl - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ConcurrentTaskLimiterImpl_disposeInternal(JNIEnv*, - jobject, - jlong jhandle) { - auto* ptr = reinterpret_cast< - std::shared_ptr*>(jhandle); - delete ptr; // delete std::shared_ptr -} diff --git a/java/rocksjni/config_options.cc b/java/rocksjni/config_options.cc deleted file mode 100644 index 55a9cbb66..000000000 --- a/java/rocksjni/config_options.cc +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::ConfigOptions methods -// from Java side. - -#include - -#include "include/org_rocksdb_ConfigOptions.h" -#include "rocksdb/convenience.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_ConfigOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ConfigOptions_disposeInternal(JNIEnv *, jobject, - jlong jhandle) { - auto *co = reinterpret_cast(jhandle); - assert(co != nullptr); - delete co; -} - -/* - * Class: org_rocksdb_ConfigOptions - * Method: newConfigOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_ConfigOptions_newConfigOptions(JNIEnv *, jclass) { - auto *cfg_opt = new ROCKSDB_NAMESPACE::ConfigOptions(); - return GET_CPLUSPLUS_POINTER(cfg_opt); -} - -/* - * Class: org_rocksdb_ConfigOptions - * Method: setEnv - * Signature: (JJ;)V - */ -void Java_org_rocksdb_ConfigOptions_setEnv(JNIEnv *, jclass, jlong handle, - jlong rocksdb_env_handle) { - auto *cfg_opt = reinterpret_cast(handle); - auto *rocksdb_env = - reinterpret_cast(rocksdb_env_handle); - cfg_opt->env = rocksdb_env; -} - -/* - * Class: org_rocksdb_ConfigOptions - * Method: setDelimiter - * Signature: (JLjava/lang/String;)V - */ -void Java_org_rocksdb_ConfigOptions_setDelimiter(JNIEnv *env, jclass, - jlong handle, jstring s) { - auto *cfg_opt = reinterpret_cast(handle); - const char *delim = env->GetStringUTFChars(s, nullptr); - if (delim == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - cfg_opt->delimiter = delim; - env->ReleaseStringUTFChars(s, delim); -} - -/* - * Class: org_rocksdb_ConfigOptions - * Method: setIgnoreUnknownOptions - * Signature: (JZ)V - */ -void Java_org_rocksdb_ConfigOptions_setIgnoreUnknownOptions(JNIEnv *, jclass, - jlong handle, - jboolean b) { - auto *cfg_opt = reinterpret_cast(handle); - cfg_opt->ignore_unknown_options = static_cast(b); -} - -/* - * Class: org_rocksdb_ConfigOptions - * Method: setInputStringsEscaped - * Signature: (JZ)V - */ -void Java_org_rocksdb_ConfigOptions_setInputStringsEscaped(JNIEnv *, jclass, - jlong handle, - jboolean b) { - auto *cfg_opt = reinterpret_cast(handle); - cfg_opt->input_strings_escaped = static_cast(b); -} - -/* - * Class: org_rocksdb_ConfigOptions - * Method: setSanityLevel - * Signature: (JI)V - */ -void Java_org_rocksdb_ConfigOptions_setSanityLevel(JNIEnv *, jclass, - jlong handle, jbyte level) { - auto *cfg_opt = reinterpret_cast(handle); - cfg_opt->sanity_level = - ROCKSDB_NAMESPACE::SanityLevelJni::toCppSanityLevel(level); -} diff --git a/java/rocksjni/cplusplus_to_java_convert.h b/java/rocksjni/cplusplus_to_java_convert.h deleted file mode 100644 index 0eea6fa2c..000000000 --- a/java/rocksjni/cplusplus_to_java_convert.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#pragma once - -/* - * This macro is used for 32 bit OS. In 32 bit OS, the result number is a - negative number if we use reinterpret_cast(pointer). - * For example, jlong ptr = reinterpret_cast(pointer), ptr is a negative - number in 32 bit OS. - * If we check ptr using ptr > 0, it fails. For example, the following code is - not correct. - * if (jblock_cache_handle > 0) { - std::shared_ptr *pCache = - reinterpret_cast *>( - jblock_cache_handle); - options.block_cache = *pCache; - } - * But the result number is positive number if we do - reinterpret_cast(pointer) first and then cast it to jlong. size_t is 4 - bytes long in 32 bit OS and 8 bytes long in 64 bit OS. - static_cast(reinterpret_cast(_pointer)) is also working in 64 - bit OS. - * - * We don't need an opposite cast because it works from jlong to c++ pointer in - both 32 bit and 64 bit OS. - * For example, the following code is working in both 32 bit and 64 bit OS. - jblock_cache_handle is jlong. - * std::shared_ptr *pCache = - reinterpret_cast *>( - jblock_cache_handle); -*/ - -#define GET_CPLUSPLUS_POINTER(_pointer) \ - static_cast(reinterpret_cast(_pointer)) diff --git a/java/rocksjni/env.cc b/java/rocksjni/env.cc deleted file mode 100644 index bb739fe2b..000000000 --- a/java/rocksjni/env.cc +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::Env methods from Java side. - -#include "rocksdb/env.h" - -#include - -#include - -#include "include/org_rocksdb_Env.h" -#include "include/org_rocksdb_RocksEnv.h" -#include "include/org_rocksdb_RocksMemEnv.h" -#include "include/org_rocksdb_TimedEnv.h" -#include "portal.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_Env - * Method: getDefaultEnvInternal - * Signature: ()J - */ -jlong Java_org_rocksdb_Env_getDefaultEnvInternal(JNIEnv*, jclass) { - return GET_CPLUSPLUS_POINTER(ROCKSDB_NAMESPACE::Env::Default()); -} - -/* - * Class: org_rocksdb_RocksEnv - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksEnv_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* e = reinterpret_cast(jhandle); - assert(e != nullptr); - delete e; -} - -/* - * Class: org_rocksdb_Env - * Method: setBackgroundThreads - * Signature: (JIB)V - */ -void Java_org_rocksdb_Env_setBackgroundThreads(JNIEnv*, jobject, jlong jhandle, - jint jnum, - jbyte jpriority_value) { - auto* rocks_env = reinterpret_cast(jhandle); - rocks_env->SetBackgroundThreads( - static_cast(jnum), - ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value)); -} - -/* - * Class: org_rocksdb_Env - * Method: getBackgroundThreads - * Signature: (JB)I - */ -jint Java_org_rocksdb_Env_getBackgroundThreads(JNIEnv*, jobject, jlong jhandle, - jbyte jpriority_value) { - auto* rocks_env = reinterpret_cast(jhandle); - const int num = rocks_env->GetBackgroundThreads( - ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value)); - return static_cast(num); -} - -/* - * Class: org_rocksdb_Env - * Method: getThreadPoolQueueLen - * Signature: (JB)I - */ -jint Java_org_rocksdb_Env_getThreadPoolQueueLen(JNIEnv*, jobject, jlong jhandle, - jbyte jpriority_value) { - auto* rocks_env = reinterpret_cast(jhandle); - const int queue_len = rocks_env->GetThreadPoolQueueLen( - ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value)); - return static_cast(queue_len); -} - -/* - * Class: org_rocksdb_Env - * Method: incBackgroundThreadsIfNeeded - * Signature: (JIB)V - */ -void Java_org_rocksdb_Env_incBackgroundThreadsIfNeeded(JNIEnv*, jobject, - jlong jhandle, jint jnum, - jbyte jpriority_value) { - auto* rocks_env = reinterpret_cast(jhandle); - rocks_env->IncBackgroundThreadsIfNeeded( - static_cast(jnum), - ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value)); -} - -/* - * Class: org_rocksdb_Env - * Method: lowerThreadPoolIOPriority - * Signature: (JB)V - */ -void Java_org_rocksdb_Env_lowerThreadPoolIOPriority(JNIEnv*, jobject, - jlong jhandle, - jbyte jpriority_value) { - auto* rocks_env = reinterpret_cast(jhandle); - rocks_env->LowerThreadPoolIOPriority( - ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value)); -} - -/* - * Class: org_rocksdb_Env - * Method: lowerThreadPoolCPUPriority - * Signature: (JB)V - */ -void Java_org_rocksdb_Env_lowerThreadPoolCPUPriority(JNIEnv*, jobject, - jlong jhandle, - jbyte jpriority_value) { - auto* rocks_env = reinterpret_cast(jhandle); - rocks_env->LowerThreadPoolCPUPriority( - ROCKSDB_NAMESPACE::PriorityJni::toCppPriority(jpriority_value)); -} - -/* - * Class: org_rocksdb_Env - * Method: getThreadList - * Signature: (J)[Lorg/rocksdb/ThreadStatus; - */ -jobjectArray Java_org_rocksdb_Env_getThreadList(JNIEnv* env, jobject, - jlong jhandle) { - auto* rocks_env = reinterpret_cast(jhandle); - std::vector thread_status; - ROCKSDB_NAMESPACE::Status s = rocks_env->GetThreadList(&thread_status); - if (!s.ok()) { - // error, throw exception - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - // object[] - const jsize len = static_cast(thread_status.size()); - jobjectArray jthread_status = env->NewObjectArray( - len, ROCKSDB_NAMESPACE::ThreadStatusJni::getJClass(env), nullptr); - if (jthread_status == nullptr) { - // an exception occurred - return nullptr; - } - for (jsize i = 0; i < len; ++i) { - jobject jts = - ROCKSDB_NAMESPACE::ThreadStatusJni::construct(env, &(thread_status[i])); - env->SetObjectArrayElement(jthread_status, i, jts); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(jthread_status); - return nullptr; - } - } - - return jthread_status; -} - -/* - * Class: org_rocksdb_RocksMemEnv - * Method: createMemEnv - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksMemEnv_createMemEnv(JNIEnv*, jclass, - jlong jbase_env_handle) { - auto* base_env = reinterpret_cast(jbase_env_handle); - return GET_CPLUSPLUS_POINTER(ROCKSDB_NAMESPACE::NewMemEnv(base_env)); -} - -/* - * Class: org_rocksdb_RocksMemEnv - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksMemEnv_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* e = reinterpret_cast(jhandle); - assert(e != nullptr); - delete e; -} - -/* - * Class: org_rocksdb_TimedEnv - * Method: createTimedEnv - * Signature: (J)J - */ -jlong Java_org_rocksdb_TimedEnv_createTimedEnv(JNIEnv*, jclass, - jlong jbase_env_handle) { - auto* base_env = reinterpret_cast(jbase_env_handle); - return GET_CPLUSPLUS_POINTER(ROCKSDB_NAMESPACE::NewTimedEnv(base_env)); -} - -/* - * Class: org_rocksdb_TimedEnv - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_TimedEnv_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* e = reinterpret_cast(jhandle); - assert(e != nullptr); - delete e; -} diff --git a/java/rocksjni/env_options.cc b/java/rocksjni/env_options.cc deleted file mode 100644 index 3237e2775..000000000 --- a/java/rocksjni/env_options.cc +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::EnvOptions methods -// from Java side. - -#include - -#include "include/org_rocksdb_EnvOptions.h" -#include "rocksdb/env.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -#define ENV_OPTIONS_SET_BOOL(_jhandle, _opt) \ - reinterpret_cast(_jhandle)->_opt = \ - static_cast(_opt) - -#define ENV_OPTIONS_SET_SIZE_T(_jhandle, _opt) \ - reinterpret_cast(_jhandle)->_opt = \ - static_cast(_opt) - -#define ENV_OPTIONS_SET_UINT64_T(_jhandle, _opt) \ - reinterpret_cast(_jhandle)->_opt = \ - static_cast(_opt) - -#define ENV_OPTIONS_GET(_jhandle, _opt) \ - reinterpret_cast(_jhandle)->_opt - -/* - * Class: org_rocksdb_EnvOptions - * Method: newEnvOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_EnvOptions_newEnvOptions__(JNIEnv *, jclass) { - auto *env_opt = new ROCKSDB_NAMESPACE::EnvOptions(); - return GET_CPLUSPLUS_POINTER(env_opt); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: newEnvOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_EnvOptions_newEnvOptions__J(JNIEnv *, jclass, - jlong jdboptions_handle) { - auto *db_options = - reinterpret_cast(jdboptions_handle); - auto *env_opt = new ROCKSDB_NAMESPACE::EnvOptions(*db_options); - return GET_CPLUSPLUS_POINTER(env_opt); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_EnvOptions_disposeInternal(JNIEnv *, jobject, - jlong jhandle) { - auto *eo = reinterpret_cast(jhandle); - assert(eo != nullptr); - delete eo; -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setUseMmapReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setUseMmapReads(JNIEnv *, jobject, - jlong jhandle, - jboolean use_mmap_reads) { - ENV_OPTIONS_SET_BOOL(jhandle, use_mmap_reads); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: useMmapReads - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_useMmapReads(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_mmap_reads); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setUseMmapWrites - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setUseMmapWrites(JNIEnv *, jobject, - jlong jhandle, - jboolean use_mmap_writes) { - ENV_OPTIONS_SET_BOOL(jhandle, use_mmap_writes); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: useMmapWrites - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_useMmapWrites(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_mmap_writes); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setUseDirectReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setUseDirectReads(JNIEnv *, jobject, - jlong jhandle, - jboolean use_direct_reads) { - ENV_OPTIONS_SET_BOOL(jhandle, use_direct_reads); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: useDirectReads - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_useDirectReads(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_direct_reads); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setUseDirectWrites - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setUseDirectWrites( - JNIEnv *, jobject, jlong jhandle, jboolean use_direct_writes) { - ENV_OPTIONS_SET_BOOL(jhandle, use_direct_writes); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: useDirectWrites - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_useDirectWrites(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_direct_writes); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setAllowFallocate - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setAllowFallocate(JNIEnv *, jobject, - jlong jhandle, - jboolean allow_fallocate) { - ENV_OPTIONS_SET_BOOL(jhandle, allow_fallocate); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: allowFallocate - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_allowFallocate(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, allow_fallocate); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setSetFdCloexec - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setSetFdCloexec(JNIEnv *, jobject, - jlong jhandle, - jboolean set_fd_cloexec) { - ENV_OPTIONS_SET_BOOL(jhandle, set_fd_cloexec); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setFdCloexec - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_setFdCloexec(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, set_fd_cloexec); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setBytesPerSync - * Signature: (JJ)V - */ -void Java_org_rocksdb_EnvOptions_setBytesPerSync(JNIEnv *, jobject, - jlong jhandle, - jlong bytes_per_sync) { - ENV_OPTIONS_SET_UINT64_T(jhandle, bytes_per_sync); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: bytesPerSync - * Signature: (J)J - */ -jlong Java_org_rocksdb_EnvOptions_bytesPerSync(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, bytes_per_sync); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setFallocateWithKeepSize - * Signature: (JZ)V - */ -void Java_org_rocksdb_EnvOptions_setFallocateWithKeepSize( - JNIEnv *, jobject, jlong jhandle, jboolean fallocate_with_keep_size) { - ENV_OPTIONS_SET_BOOL(jhandle, fallocate_with_keep_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: fallocateWithKeepSize - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_EnvOptions_fallocateWithKeepSize(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, fallocate_with_keep_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setCompactionReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_EnvOptions_setCompactionReadaheadSize( - JNIEnv *, jobject, jlong jhandle, jlong compaction_readahead_size) { - ENV_OPTIONS_SET_SIZE_T(jhandle, compaction_readahead_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: compactionReadaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_EnvOptions_compactionReadaheadSize(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, compaction_readahead_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setRandomAccessMaxBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_EnvOptions_setRandomAccessMaxBufferSize( - JNIEnv *, jobject, jlong jhandle, jlong random_access_max_buffer_size) { - ENV_OPTIONS_SET_SIZE_T(jhandle, random_access_max_buffer_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: randomAccessMaxBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_EnvOptions_randomAccessMaxBufferSize(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, random_access_max_buffer_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setWritableFileMaxBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_EnvOptions_setWritableFileMaxBufferSize( - JNIEnv *, jobject, jlong jhandle, jlong writable_file_max_buffer_size) { - ENV_OPTIONS_SET_SIZE_T(jhandle, writable_file_max_buffer_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: writableFileMaxBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_EnvOptions_writableFileMaxBufferSize(JNIEnv *, jobject, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, writable_file_max_buffer_size); -} - -/* - * Class: org_rocksdb_EnvOptions - * Method: setRateLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_EnvOptions_setRateLimiter(JNIEnv *, jobject, - jlong jhandle, - jlong rl_handle) { - auto *sptr_rate_limiter = - reinterpret_cast *>( - rl_handle); - auto *env_opt = reinterpret_cast(jhandle); - env_opt->rate_limiter = sptr_rate_limiter->get(); -} diff --git a/java/rocksjni/event_listener.cc b/java/rocksjni/event_listener.cc deleted file mode 100644 index 965932c9c..000000000 --- a/java/rocksjni/event_listener.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::EventListener. - -#include - -#include - -#include "include/org_rocksdb_AbstractEventListener.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/event_listener_jnicallback.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_AbstractEventListener - * Method: createNewEventListener - * Signature: (J)J - */ -jlong Java_org_rocksdb_AbstractEventListener_createNewEventListener( - JNIEnv* env, jobject jobj, jlong jenabled_event_callback_values) { - auto enabled_event_callbacks = - ROCKSDB_NAMESPACE::EnabledEventCallbackJni::toCppEnabledEventCallbacks( - jenabled_event_callback_values); - auto* sptr_event_listener = - new std::shared_ptr( - new ROCKSDB_NAMESPACE::EventListenerJniCallback( - env, jobj, enabled_event_callbacks)); - return GET_CPLUSPLUS_POINTER(sptr_event_listener); -} - -/* - * Class: org_rocksdb_AbstractEventListener - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_AbstractEventListener_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - delete reinterpret_cast*>( - jhandle); -} diff --git a/java/rocksjni/event_listener_jnicallback.cc b/java/rocksjni/event_listener_jnicallback.cc deleted file mode 100644 index 342d938b4..000000000 --- a/java/rocksjni/event_listener_jnicallback.cc +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::EventListener. - -#include "rocksjni/event_listener_jnicallback.h" - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -EventListenerJniCallback::EventListenerJniCallback( - JNIEnv* env, jobject jevent_listener, - const std::set& enabled_event_callbacks) - : JniCallback(env, jevent_listener), - m_enabled_event_callbacks(enabled_event_callbacks) { - InitCallbackMethodId( - m_on_flush_completed_proxy_mid, EnabledEventCallback::ON_FLUSH_COMPLETED, - env, AbstractEventListenerJni::getOnFlushCompletedProxyMethodId); - - InitCallbackMethodId(m_on_flush_begin_proxy_mid, - EnabledEventCallback::ON_FLUSH_BEGIN, env, - AbstractEventListenerJni::getOnFlushBeginProxyMethodId); - - InitCallbackMethodId(m_on_table_file_deleted_mid, - EnabledEventCallback::ON_TABLE_FILE_DELETED, env, - AbstractEventListenerJni::getOnTableFileDeletedMethodId); - - InitCallbackMethodId( - m_on_compaction_begin_proxy_mid, - EnabledEventCallback::ON_COMPACTION_BEGIN, env, - AbstractEventListenerJni::getOnCompactionBeginProxyMethodId); - - InitCallbackMethodId( - m_on_compaction_completed_proxy_mid, - EnabledEventCallback::ON_COMPACTION_COMPLETED, env, - AbstractEventListenerJni::getOnCompactionCompletedProxyMethodId); - - InitCallbackMethodId(m_on_table_file_created_mid, - EnabledEventCallback::ON_TABLE_FILE_CREATED, env, - AbstractEventListenerJni::getOnTableFileCreatedMethodId); - - InitCallbackMethodId( - m_on_table_file_creation_started_mid, - EnabledEventCallback::ON_TABLE_FILE_CREATION_STARTED, env, - AbstractEventListenerJni::getOnTableFileCreationStartedMethodId); - - InitCallbackMethodId(m_on_mem_table_sealed_mid, - EnabledEventCallback::ON_MEMTABLE_SEALED, env, - AbstractEventListenerJni::getOnMemTableSealedMethodId); - - InitCallbackMethodId( - m_on_column_family_handle_deletion_started_mid, - EnabledEventCallback::ON_COLUMN_FAMILY_HANDLE_DELETION_STARTED, env, - AbstractEventListenerJni::getOnColumnFamilyHandleDeletionStartedMethodId); - - InitCallbackMethodId( - m_on_external_file_ingested_proxy_mid, - EnabledEventCallback::ON_EXTERNAL_FILE_INGESTED, env, - AbstractEventListenerJni::getOnExternalFileIngestedProxyMethodId); - - InitCallbackMethodId( - m_on_background_error_proxy_mid, - EnabledEventCallback::ON_BACKGROUND_ERROR, env, - AbstractEventListenerJni::getOnBackgroundErrorProxyMethodId); - - InitCallbackMethodId( - m_on_stall_conditions_changed_mid, - EnabledEventCallback::ON_STALL_CONDITIONS_CHANGED, env, - AbstractEventListenerJni::getOnStallConditionsChangedMethodId); - - InitCallbackMethodId(m_on_file_read_finish_mid, - EnabledEventCallback::ON_FILE_READ_FINISH, env, - AbstractEventListenerJni::getOnFileReadFinishMethodId); - - InitCallbackMethodId(m_on_file_write_finish_mid, - EnabledEventCallback::ON_FILE_WRITE_FINISH, env, - AbstractEventListenerJni::getOnFileWriteFinishMethodId); - - InitCallbackMethodId(m_on_file_flush_finish_mid, - EnabledEventCallback::ON_FILE_FLUSH_FINISH, env, - AbstractEventListenerJni::getOnFileFlushFinishMethodId); - - InitCallbackMethodId(m_on_file_sync_finish_mid, - EnabledEventCallback::ON_FILE_SYNC_FINISH, env, - AbstractEventListenerJni::getOnFileSyncFinishMethodId); - - InitCallbackMethodId( - m_on_file_range_sync_finish_mid, - EnabledEventCallback::ON_FILE_RANGE_SYNC_FINISH, env, - AbstractEventListenerJni::getOnFileRangeSyncFinishMethodId); - - InitCallbackMethodId( - m_on_file_truncate_finish_mid, - EnabledEventCallback::ON_FILE_TRUNCATE_FINISH, env, - AbstractEventListenerJni::getOnFileTruncateFinishMethodId); - - InitCallbackMethodId(m_on_file_close_finish_mid, - EnabledEventCallback::ON_FILE_CLOSE_FINISH, env, - AbstractEventListenerJni::getOnFileCloseFinishMethodId); - - InitCallbackMethodId( - m_should_be_notified_on_file_io, - EnabledEventCallback::SHOULD_BE_NOTIFIED_ON_FILE_IO, env, - AbstractEventListenerJni::getShouldBeNotifiedOnFileIOMethodId); - - InitCallbackMethodId( - m_on_error_recovery_begin_proxy_mid, - EnabledEventCallback::ON_ERROR_RECOVERY_BEGIN, env, - AbstractEventListenerJni::getOnErrorRecoveryBeginProxyMethodId); - - InitCallbackMethodId( - m_on_error_recovery_completed_mid, - EnabledEventCallback::ON_ERROR_RECOVERY_COMPLETED, env, - AbstractEventListenerJni::getOnErrorRecoveryCompletedMethodId); -} - -EventListenerJniCallback::~EventListenerJniCallback() {} - -void EventListenerJniCallback::OnFlushCompleted( - DB* db, const FlushJobInfo& flush_job_info) { - if (m_on_flush_completed_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jflush_job_info = SetupCallbackInvocation( - env, attached_thread, flush_job_info, - FlushJobInfoJni::fromCppFlushJobInfo); - - if (jflush_job_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_flush_completed_proxy_mid, - reinterpret_cast(db), jflush_job_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jflush_job_info}); -} - -void EventListenerJniCallback::OnFlushBegin( - DB* db, const FlushJobInfo& flush_job_info) { - if (m_on_flush_begin_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jflush_job_info = SetupCallbackInvocation( - env, attached_thread, flush_job_info, - FlushJobInfoJni::fromCppFlushJobInfo); - - if (jflush_job_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_flush_begin_proxy_mid, - reinterpret_cast(db), jflush_job_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jflush_job_info}); -} - -void EventListenerJniCallback::OnTableFileDeleted( - const TableFileDeletionInfo& info) { - if (m_on_table_file_deleted_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jdeletion_info = SetupCallbackInvocation( - env, attached_thread, info, - TableFileDeletionInfoJni::fromCppTableFileDeletionInfo); - - if (jdeletion_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_table_file_deleted_mid, - jdeletion_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jdeletion_info}); -} - -void EventListenerJniCallback::OnCompactionBegin(DB* db, - const CompactionJobInfo& ci) { - if (m_on_compaction_begin_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jcompaction_job_info = SetupCallbackInvocation( - env, attached_thread, ci, CompactionJobInfoJni::fromCppCompactionJobInfo); - - if (jcompaction_job_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_compaction_begin_proxy_mid, - reinterpret_cast(db), jcompaction_job_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jcompaction_job_info}); -} - -void EventListenerJniCallback::OnCompactionCompleted( - DB* db, const CompactionJobInfo& ci) { - if (m_on_compaction_completed_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jcompaction_job_info = SetupCallbackInvocation( - env, attached_thread, ci, CompactionJobInfoJni::fromCppCompactionJobInfo); - - if (jcompaction_job_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_compaction_completed_proxy_mid, - reinterpret_cast(db), jcompaction_job_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jcompaction_job_info}); -} - -void EventListenerJniCallback::OnTableFileCreated( - const TableFileCreationInfo& info) { - if (m_on_table_file_created_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jfile_creation_info = SetupCallbackInvocation( - env, attached_thread, info, - TableFileCreationInfoJni::fromCppTableFileCreationInfo); - - if (jfile_creation_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_table_file_created_mid, - jfile_creation_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jfile_creation_info}); -} - -void EventListenerJniCallback::OnTableFileCreationStarted( - const TableFileCreationBriefInfo& info) { - if (m_on_table_file_creation_started_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jcreation_brief_info = - SetupCallbackInvocation( - env, attached_thread, info, - TableFileCreationBriefInfoJni::fromCppTableFileCreationBriefInfo); - - if (jcreation_brief_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_table_file_creation_started_mid, - jcreation_brief_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jcreation_brief_info}); -} - -void EventListenerJniCallback::OnMemTableSealed(const MemTableInfo& info) { - if (m_on_mem_table_sealed_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jmem_table_info = SetupCallbackInvocation( - env, attached_thread, info, MemTableInfoJni::fromCppMemTableInfo); - - if (jmem_table_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_mem_table_sealed_mid, - jmem_table_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jmem_table_info}); -} - -void EventListenerJniCallback::OnColumnFamilyHandleDeletionStarted( - ColumnFamilyHandle* handle) { - if (m_on_column_family_handle_deletion_started_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jcf_handle = SetupCallbackInvocation( - env, attached_thread, *handle, - ColumnFamilyHandleJni::fromCppColumnFamilyHandle); - - if (jcf_handle != nullptr) { - env->CallVoidMethod(m_jcallback_obj, - m_on_column_family_handle_deletion_started_mid, - jcf_handle); - } - - CleanupCallbackInvocation(env, attached_thread, {&jcf_handle}); -} - -void EventListenerJniCallback::OnExternalFileIngested( - DB* db, const ExternalFileIngestionInfo& info) { - if (m_on_external_file_ingested_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jingestion_info = SetupCallbackInvocation( - env, attached_thread, info, - ExternalFileIngestionInfoJni::fromCppExternalFileIngestionInfo); - - if (jingestion_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_external_file_ingested_proxy_mid, - reinterpret_cast(db), jingestion_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jingestion_info}); -} - -void EventListenerJniCallback::OnBackgroundError(BackgroundErrorReason reason, - Status* bg_error) { - if (m_on_background_error_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jstatus = SetupCallbackInvocation( - env, attached_thread, *bg_error, StatusJni::construct); - - if (jstatus != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_background_error_proxy_mid, - static_cast(reason), jstatus); - } - - CleanupCallbackInvocation(env, attached_thread, {&jstatus}); -} - -void EventListenerJniCallback::OnStallConditionsChanged( - const WriteStallInfo& info) { - if (m_on_stall_conditions_changed_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jwrite_stall_info = SetupCallbackInvocation( - env, attached_thread, info, WriteStallInfoJni::fromCppWriteStallInfo); - - if (jwrite_stall_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_stall_conditions_changed_mid, - jwrite_stall_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jwrite_stall_info}); -} - -void EventListenerJniCallback::OnFileReadFinish(const FileOperationInfo& info) { - OnFileOperation(m_on_file_read_finish_mid, info); -} - -void EventListenerJniCallback::OnFileWriteFinish( - const FileOperationInfo& info) { - OnFileOperation(m_on_file_write_finish_mid, info); -} - -void EventListenerJniCallback::OnFileFlushFinish( - const FileOperationInfo& info) { - OnFileOperation(m_on_file_flush_finish_mid, info); -} - -void EventListenerJniCallback::OnFileSyncFinish(const FileOperationInfo& info) { - OnFileOperation(m_on_file_sync_finish_mid, info); -} - -void EventListenerJniCallback::OnFileRangeSyncFinish( - const FileOperationInfo& info) { - OnFileOperation(m_on_file_range_sync_finish_mid, info); -} - -void EventListenerJniCallback::OnFileTruncateFinish( - const FileOperationInfo& info) { - OnFileOperation(m_on_file_truncate_finish_mid, info); -} - -void EventListenerJniCallback::OnFileCloseFinish( - const FileOperationInfo& info) { - OnFileOperation(m_on_file_close_finish_mid, info); -} - -bool EventListenerJniCallback::ShouldBeNotifiedOnFileIO() { - if (m_should_be_notified_on_file_io == nullptr) { - return false; - } - - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - jboolean jshould_be_notified = - env->CallBooleanMethod(m_jcallback_obj, m_should_be_notified_on_file_io); - - CleanupCallbackInvocation(env, attached_thread, {}); - - return static_cast(jshould_be_notified); -} - -void EventListenerJniCallback::OnErrorRecoveryBegin( - BackgroundErrorReason reason, Status bg_error, bool* auto_recovery) { - if (m_on_error_recovery_begin_proxy_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jbg_error = SetupCallbackInvocation( - env, attached_thread, bg_error, StatusJni::construct); - - if (jbg_error != nullptr) { - jboolean jauto_recovery = env->CallBooleanMethod( - m_jcallback_obj, m_on_error_recovery_begin_proxy_mid, - static_cast(reason), jbg_error); - *auto_recovery = jauto_recovery == JNI_TRUE; - } - - CleanupCallbackInvocation(env, attached_thread, {&jbg_error}); -} - -void EventListenerJniCallback::OnErrorRecoveryCompleted(Status old_bg_error) { - if (m_on_error_recovery_completed_mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jold_bg_error = SetupCallbackInvocation( - env, attached_thread, old_bg_error, StatusJni::construct); - - if (jold_bg_error != nullptr) { - env->CallVoidMethod(m_jcallback_obj, m_on_error_recovery_completed_mid, - jold_bg_error); - } - - CleanupCallbackInvocation(env, attached_thread, {&jold_bg_error}); -} - -void EventListenerJniCallback::InitCallbackMethodId( - jmethodID& mid, EnabledEventCallback eec, JNIEnv* env, - jmethodID (*get_id)(JNIEnv* env)) { - if (m_enabled_event_callbacks.count(eec) == 1) { - mid = get_id(env); - } else { - mid = nullptr; - } -} - -template -jobject EventListenerJniCallback::SetupCallbackInvocation( - JNIEnv*& env, jboolean& attached_thread, const T& cpp_obj, - jobject (*convert)(JNIEnv* env, const T* cpp_obj)) { - attached_thread = JNI_FALSE; - env = getJniEnv(&attached_thread); - assert(env != nullptr); - - return convert(env, &cpp_obj); -} - -void EventListenerJniCallback::CleanupCallbackInvocation( - JNIEnv* env, jboolean attached_thread, - std::initializer_list refs) { - for (auto* ref : refs) { - if (*ref == nullptr) continue; - env->DeleteLocalRef(*ref); - } - - if (env->ExceptionCheck()) { - // exception thrown from CallVoidMethod - env->ExceptionDescribe(); // print out exception to stderr - } - - releaseJniEnv(attached_thread); -} - -void EventListenerJniCallback::OnFileOperation(const jmethodID& mid, - const FileOperationInfo& info) { - if (mid == nullptr) { - return; - } - - JNIEnv* env; - jboolean attached_thread; - jobject jop_info = SetupCallbackInvocation( - env, attached_thread, info, - FileOperationInfoJni::fromCppFileOperationInfo); - - if (jop_info != nullptr) { - env->CallVoidMethod(m_jcallback_obj, mid, jop_info); - } - - CleanupCallbackInvocation(env, attached_thread, {&jop_info}); -} -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/event_listener_jnicallback.h b/java/rocksjni/event_listener_jnicallback.h deleted file mode 100644 index f4a235a23..000000000 --- a/java/rocksjni/event_listener_jnicallback.h +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::EventListener. - -#ifndef JAVA_ROCKSJNI_EVENT_LISTENER_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_EVENT_LISTENER_JNICALLBACK_H_ - -#include - -#include -#include - -#include "rocksdb/listener.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -enum EnabledEventCallback { - ON_FLUSH_COMPLETED = 0x0, - ON_FLUSH_BEGIN = 0x1, - ON_TABLE_FILE_DELETED = 0x2, - ON_COMPACTION_BEGIN = 0x3, - ON_COMPACTION_COMPLETED = 0x4, - ON_TABLE_FILE_CREATED = 0x5, - ON_TABLE_FILE_CREATION_STARTED = 0x6, - ON_MEMTABLE_SEALED = 0x7, - ON_COLUMN_FAMILY_HANDLE_DELETION_STARTED = 0x8, - ON_EXTERNAL_FILE_INGESTED = 0x9, - ON_BACKGROUND_ERROR = 0xA, - ON_STALL_CONDITIONS_CHANGED = 0xB, - ON_FILE_READ_FINISH = 0xC, - ON_FILE_WRITE_FINISH = 0xD, - ON_FILE_FLUSH_FINISH = 0xE, - ON_FILE_SYNC_FINISH = 0xF, - ON_FILE_RANGE_SYNC_FINISH = 0x10, - ON_FILE_TRUNCATE_FINISH = 0x11, - ON_FILE_CLOSE_FINISH = 0x12, - SHOULD_BE_NOTIFIED_ON_FILE_IO = 0x13, - ON_ERROR_RECOVERY_BEGIN = 0x14, - ON_ERROR_RECOVERY_COMPLETED = 0x15, - - NUM_ENABLED_EVENT_CALLBACK = 0x16, -}; - -class EventListenerJniCallback : public JniCallback, public EventListener { - public: - EventListenerJniCallback( - JNIEnv* env, jobject jevent_listener, - const std::set& enabled_event_callbacks); - virtual ~EventListenerJniCallback(); - virtual void OnFlushCompleted(DB* db, const FlushJobInfo& flush_job_info); - virtual void OnFlushBegin(DB* db, const FlushJobInfo& flush_job_info); - virtual void OnTableFileDeleted(const TableFileDeletionInfo& info); - virtual void OnCompactionBegin(DB* db, const CompactionJobInfo& ci); - virtual void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci); - virtual void OnTableFileCreated(const TableFileCreationInfo& info); - virtual void OnTableFileCreationStarted( - const TableFileCreationBriefInfo& info); - virtual void OnMemTableSealed(const MemTableInfo& info); - virtual void OnColumnFamilyHandleDeletionStarted(ColumnFamilyHandle* handle); - virtual void OnExternalFileIngested(DB* db, - const ExternalFileIngestionInfo& info); - virtual void OnBackgroundError(BackgroundErrorReason reason, - Status* bg_error); - virtual void OnStallConditionsChanged(const WriteStallInfo& info); - virtual void OnFileReadFinish(const FileOperationInfo& info); - virtual void OnFileWriteFinish(const FileOperationInfo& info); - virtual void OnFileFlushFinish(const FileOperationInfo& info); - virtual void OnFileSyncFinish(const FileOperationInfo& info); - virtual void OnFileRangeSyncFinish(const FileOperationInfo& info); - virtual void OnFileTruncateFinish(const FileOperationInfo& info); - virtual void OnFileCloseFinish(const FileOperationInfo& info); - virtual bool ShouldBeNotifiedOnFileIO(); - virtual void OnErrorRecoveryBegin(BackgroundErrorReason reason, - Status bg_error, bool* auto_recovery); - virtual void OnErrorRecoveryCompleted(Status old_bg_error); - - private: - inline void InitCallbackMethodId(jmethodID& mid, EnabledEventCallback eec, - JNIEnv* env, - jmethodID (*get_id)(JNIEnv* env)); - template - inline jobject SetupCallbackInvocation( - JNIEnv*& env, jboolean& attached_thread, const T& cpp_obj, - jobject (*convert)(JNIEnv* env, const T* cpp_obj)); - inline void CleanupCallbackInvocation(JNIEnv* env, jboolean attached_thread, - std::initializer_list refs); - inline void OnFileOperation(const jmethodID& mid, - const FileOperationInfo& info); - - const std::set m_enabled_event_callbacks; - jmethodID m_on_flush_completed_proxy_mid; - jmethodID m_on_flush_begin_proxy_mid; - jmethodID m_on_table_file_deleted_mid; - jmethodID m_on_compaction_begin_proxy_mid; - jmethodID m_on_compaction_completed_proxy_mid; - jmethodID m_on_table_file_created_mid; - jmethodID m_on_table_file_creation_started_mid; - jmethodID m_on_mem_table_sealed_mid; - jmethodID m_on_column_family_handle_deletion_started_mid; - jmethodID m_on_external_file_ingested_proxy_mid; - jmethodID m_on_background_error_proxy_mid; - jmethodID m_on_stall_conditions_changed_mid; - jmethodID m_on_file_read_finish_mid; - jmethodID m_on_file_write_finish_mid; - jmethodID m_on_file_flush_finish_mid; - jmethodID m_on_file_sync_finish_mid; - jmethodID m_on_file_range_sync_finish_mid; - jmethodID m_on_file_truncate_finish_mid; - jmethodID m_on_file_close_finish_mid; - jmethodID m_should_be_notified_on_file_io; - jmethodID m_on_error_recovery_begin_proxy_mid; - jmethodID m_on_error_recovery_completed_mid; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_EVENT_LISTENER_JNICALLBACK_H_ diff --git a/java/rocksjni/filter.cc b/java/rocksjni/filter.cc deleted file mode 100644 index ed22016d2..000000000 --- a/java/rocksjni/filter.cc +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::FilterPolicy. - -#include -#include -#include - -#include - -#include "include/org_rocksdb_BloomFilter.h" -#include "include/org_rocksdb_Filter.h" -#include "rocksdb/filter_policy.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_BloomFilter - * Method: createBloomFilter - * Signature: (DZ)J - */ -jlong Java_org_rocksdb_BloomFilter_createNewBloomFilter(JNIEnv* /*env*/, - jclass /*jcls*/, - jdouble bits_per_key) { - auto* sptr_filter = - new std::shared_ptr( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(bits_per_key)); - return GET_CPLUSPLUS_POINTER(sptr_filter); -} - -/* - * Class: org_rocksdb_Filter - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_Filter_disposeInternal(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* handle = - reinterpret_cast*>( - jhandle); - delete handle; // delete std::shared_ptr -} diff --git a/java/rocksjni/ingest_external_file_options.cc b/java/rocksjni/ingest_external_file_options.cc deleted file mode 100644 index 052cf3325..000000000 --- a/java/rocksjni/ingest_external_file_options.cc +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::FilterPolicy. - -#include - -#include "include/org_rocksdb_IngestExternalFileOptions.h" -#include "rocksdb/options.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: newIngestExternalFileOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__( - JNIEnv*, jclass) { - auto* options = new ROCKSDB_NAMESPACE::IngestExternalFileOptions(); - return GET_CPLUSPLUS_POINTER(options); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: newIngestExternalFileOptions - * Signature: (ZZZZ)J - */ -jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__ZZZZ( - JNIEnv*, jclass, jboolean jmove_files, jboolean jsnapshot_consistency, - jboolean jallow_global_seqno, jboolean jallow_blocking_flush) { - auto* options = new ROCKSDB_NAMESPACE::IngestExternalFileOptions(); - options->move_files = static_cast(jmove_files); - options->snapshot_consistency = static_cast(jsnapshot_consistency); - options->allow_global_seqno = static_cast(jallow_global_seqno); - options->allow_blocking_flush = static_cast(jallow_blocking_flush); - return GET_CPLUSPLUS_POINTER(options); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: moveFiles - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_IngestExternalFileOptions_moveFiles(JNIEnv*, jobject, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->move_files); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: setMoveFiles - * Signature: (JZ)V - */ -void Java_org_rocksdb_IngestExternalFileOptions_setMoveFiles( - JNIEnv*, jobject, jlong jhandle, jboolean jmove_files) { - auto* options = - reinterpret_cast(jhandle); - options->move_files = static_cast(jmove_files); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: snapshotConsistency - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_IngestExternalFileOptions_snapshotConsistency( - JNIEnv*, jobject, jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->snapshot_consistency); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: setSnapshotConsistency - * Signature: (JZ)V - */ -void Java_org_rocksdb_IngestExternalFileOptions_setSnapshotConsistency( - JNIEnv*, jobject, jlong jhandle, jboolean jsnapshot_consistency) { - auto* options = - reinterpret_cast(jhandle); - options->snapshot_consistency = static_cast(jsnapshot_consistency); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: allowGlobalSeqNo - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_IngestExternalFileOptions_allowGlobalSeqNo( - JNIEnv*, jobject, jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->allow_global_seqno); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: setAllowGlobalSeqNo - * Signature: (JZ)V - */ -void Java_org_rocksdb_IngestExternalFileOptions_setAllowGlobalSeqNo( - JNIEnv*, jobject, jlong jhandle, jboolean jallow_global_seqno) { - auto* options = - reinterpret_cast(jhandle); - options->allow_global_seqno = static_cast(jallow_global_seqno); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: allowBlockingFlush - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_IngestExternalFileOptions_allowBlockingFlush( - JNIEnv*, jobject, jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return static_cast(options->allow_blocking_flush); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: setAllowBlockingFlush - * Signature: (JZ)V - */ -void Java_org_rocksdb_IngestExternalFileOptions_setAllowBlockingFlush( - JNIEnv*, jobject, jlong jhandle, jboolean jallow_blocking_flush) { - auto* options = - reinterpret_cast(jhandle); - options->allow_blocking_flush = static_cast(jallow_blocking_flush); -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: ingestBehind - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_IngestExternalFileOptions_ingestBehind( - JNIEnv*, jobject, jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return options->ingest_behind == JNI_TRUE; -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: setIngestBehind - * Signature: (JZ)V - */ -void Java_org_rocksdb_IngestExternalFileOptions_setIngestBehind( - JNIEnv*, jobject, jlong jhandle, jboolean jingest_behind) { - auto* options = - reinterpret_cast(jhandle); - options->ingest_behind = jingest_behind == JNI_TRUE; -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: writeGlobalSeqno - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL -Java_org_rocksdb_IngestExternalFileOptions_writeGlobalSeqno(JNIEnv*, jobject, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - return options->write_global_seqno == JNI_TRUE; -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: setWriteGlobalSeqno - * Signature: (JZ)V - */ -JNIEXPORT void JNICALL -Java_org_rocksdb_IngestExternalFileOptions_setWriteGlobalSeqno( - JNIEnv*, jobject, jlong jhandle, jboolean jwrite_global_seqno) { - auto* options = - reinterpret_cast(jhandle); - options->write_global_seqno = jwrite_global_seqno == JNI_TRUE; -} - -/* - * Class: org_rocksdb_IngestExternalFileOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_IngestExternalFileOptions_disposeInternal(JNIEnv*, - jobject, - jlong jhandle) { - auto* options = - reinterpret_cast(jhandle); - delete options; -} diff --git a/java/rocksjni/iterator.cc b/java/rocksjni/iterator.cc deleted file mode 100644 index 3ddb9778b..000000000 --- a/java/rocksjni/iterator.cc +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::Iterator methods from Java side. - -#include "rocksdb/iterator.h" - -#include -#include -#include - -#include - -#include "include/org_rocksdb_RocksIterator.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_RocksIterator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - assert(it != nullptr); - delete it; -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: isValid0 - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_RocksIterator_isValid0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast(handle)->Valid(); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: seekToFirst0 - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_seekToFirst0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->SeekToFirst(); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: seekToLast0 - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_seekToLast0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->SeekToLast(); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: next0 - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_next0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Next(); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: prev0 - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_prev0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Prev(); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: refresh0 - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_refresh0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Status s = it->Refresh(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: seek0 - * Signature: (J[BI)V - */ -void Java_org_rocksdb_RocksIterator_seek0(JNIEnv* env, jobject /*jobj*/, - jlong handle, jbyteArray jtarget, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->Seek(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_region(seek, env, jtarget, 0, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * In this case, the buffer offset of the key may be non-zero. - * - * Class: org_rocksdb_RocksIterator - * Method: seek0 - * Signature: (J[BII)V - */ -void Java_org_rocksdb_RocksIterator_seekByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->Seek(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_region(seek, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: seekDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_RocksIterator_seekDirect0(JNIEnv* env, jobject /*jobj*/, - jlong handle, jobject jtarget, - jint jtarget_off, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->Seek(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(seek, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: seekForPrevDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_RocksIterator_seekForPrevDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seekPrev = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->SeekForPrev(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(seekPrev, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: seekForPrev0 - * Signature: (J[BI)V - */ -void Java_org_rocksdb_RocksIterator_seekForPrev0(JNIEnv* env, jobject /*jobj*/, - jlong handle, - jbyteArray jtarget, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->SeekForPrev(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_region(seek, env, jtarget, 0, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * In this case, the buffer offset of the key may be non-zero. - * - * Class: org_rocksdb_RocksIterator - * Method: seek0 - * Signature: (J[BII)V - */ -void Java_org_rocksdb_RocksIterator_seekForPrevByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->SeekForPrev(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_region(seek, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: status0 - * Signature: (J)V - */ -void Java_org_rocksdb_RocksIterator_status0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Status s = it->status(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: key0 - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_RocksIterator_key0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice key_slice = it->key(); - - jbyteArray jkey = env->NewByteArray(static_cast(key_slice.size())); - if (jkey == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion( - jkey, 0, static_cast(key_slice.size()), - const_cast(reinterpret_cast(key_slice.data()))); - return jkey; -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: keyDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)I - */ -jint Java_org_rocksdb_RocksIterator_keyDirect0(JNIEnv* env, jobject /*jobj*/, - jlong handle, jobject jtarget, - jint jtarget_off, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice key_slice = it->key(); - return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, key_slice, jtarget, - jtarget_off, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_RocksIterator - * Method: keyByteArray0 - * Signature: (J[BII)I - */ -jint Java_org_rocksdb_RocksIterator_keyByteArray0(JNIEnv* env, jobject /*jobj*/, - jlong handle, jbyteArray jkey, - jint jkey_off, - jint jkey_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice key_slice = it->key(); - jsize copy_size = std::min(static_cast(key_slice.size()), - static_cast(jkey_len)); - env->SetByteArrayRegion( - jkey, jkey_off, copy_size, - const_cast(reinterpret_cast(key_slice.data()))); - - return static_cast(key_slice.size()); -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: value0 - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_RocksIterator_value0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice value_slice = it->value(); - - jbyteArray jkeyValue = - env->NewByteArray(static_cast(value_slice.size())); - if (jkeyValue == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion( - jkeyValue, 0, static_cast(value_slice.size()), - const_cast(reinterpret_cast(value_slice.data()))); - return jkeyValue; -} - -/* - * Class: org_rocksdb_RocksIterator - * Method: valueDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)I - */ -jint Java_org_rocksdb_RocksIterator_valueDirect0(JNIEnv* env, jobject /*jobj*/, - jlong handle, jobject jtarget, - jint jtarget_off, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice value_slice = it->value(); - return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, value_slice, jtarget, - jtarget_off, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_RocksIterator - * Method: valueByteArray0 - * Signature: (J[BII)I - */ -jint Java_org_rocksdb_RocksIterator_valueByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jvalue_target, - jint jvalue_off, jint jvalue_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice value_slice = it->value(); - jsize copy_size = std::min(static_cast(value_slice.size()), - static_cast(jvalue_len)); - env->SetByteArrayRegion( - jvalue_target, jvalue_off, copy_size, - const_cast(reinterpret_cast(value_slice.data()))); - - return static_cast(value_slice.size()); -} diff --git a/java/rocksjni/jnicallback.cc b/java/rocksjni/jnicallback.cc deleted file mode 100644 index f2742cd88..000000000 --- a/java/rocksjni/jnicallback.cc +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// JNI Callbacks from C++ to sub-classes or org.rocksdb.RocksCallbackObject - -#include "rocksjni/jnicallback.h" - -#include - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -JniCallback::JniCallback(JNIEnv* env, jobject jcallback_obj) { - // Note: jcallback_obj may be accessed by multiple threads, - // so we ref the jvm not the env - const jint rs = env->GetJavaVM(&m_jvm); - if (rs != JNI_OK) { - // exception thrown - return; - } - - // Note: we may want to access the Java callback object instance - // across multiple method calls, so we create a global ref - assert(jcallback_obj != nullptr); - m_jcallback_obj = env->NewGlobalRef(jcallback_obj); - if (jcallback_obj == nullptr) { - // exception thrown: OutOfMemoryError - return; - } -} - -JNIEnv* JniCallback::getJniEnv(jboolean* attached) const { - return JniUtil::getJniEnv(m_jvm, attached); -} - -void JniCallback::releaseJniEnv(jboolean& attached) const { - JniUtil::releaseJniEnv(m_jvm, attached); -} - -JniCallback::~JniCallback() { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - if (m_jcallback_obj != nullptr) { - env->DeleteGlobalRef(m_jcallback_obj); - } - - releaseJniEnv(attached_thread); -} -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/jnicallback.h b/java/rocksjni/jnicallback.h deleted file mode 100644 index a03a04128..000000000 --- a/java/rocksjni/jnicallback.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// JNI Callbacks from C++ to sub-classes or org.rocksdb.RocksCallbackObject - -#ifndef JAVA_ROCKSJNI_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_JNICALLBACK_H_ - -#include - -#include "rocksdb/rocksdb_namespace.h" - -namespace ROCKSDB_NAMESPACE { -class JniCallback { - public: - JniCallback(JNIEnv* env, jobject jcallback_obj); - virtual ~JniCallback(); - - const jobject& GetJavaObject() const { return m_jcallback_obj; } - - protected: - JavaVM* m_jvm; - jobject m_jcallback_obj; - JNIEnv* getJniEnv(jboolean* attached) const; - void releaseJniEnv(jboolean& attached) const; -}; -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_JNICALLBACK_H_ diff --git a/java/rocksjni/loggerjnicallback.cc b/java/rocksjni/loggerjnicallback.cc deleted file mode 100644 index aa9f95cd4..000000000 --- a/java/rocksjni/loggerjnicallback.cc +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Logger. - -#include "rocksjni/loggerjnicallback.h" - -#include -#include - -#include "include/org_rocksdb_Logger.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { - -LoggerJniCallback::LoggerJniCallback(JNIEnv* env, jobject jlogger) - : JniCallback(env, jlogger) { - m_jLogMethodId = LoggerJni::getLogMethodId(env); - if (m_jLogMethodId == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - jobject jdebug_level = InfoLogLevelJni::DEBUG_LEVEL(env); - if (jdebug_level == nullptr) { - // exception thrown: NoSuchFieldError, ExceptionInInitializerError - // or OutOfMemoryError - return; - } - m_jdebug_level = env->NewGlobalRef(jdebug_level); - if (m_jdebug_level == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jobject jinfo_level = InfoLogLevelJni::INFO_LEVEL(env); - if (jinfo_level == nullptr) { - // exception thrown: NoSuchFieldError, ExceptionInInitializerError - // or OutOfMemoryError - return; - } - m_jinfo_level = env->NewGlobalRef(jinfo_level); - if (m_jinfo_level == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jobject jwarn_level = InfoLogLevelJni::WARN_LEVEL(env); - if (jwarn_level == nullptr) { - // exception thrown: NoSuchFieldError, ExceptionInInitializerError - // or OutOfMemoryError - return; - } - m_jwarn_level = env->NewGlobalRef(jwarn_level); - if (m_jwarn_level == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jobject jerror_level = InfoLogLevelJni::ERROR_LEVEL(env); - if (jerror_level == nullptr) { - // exception thrown: NoSuchFieldError, ExceptionInInitializerError - // or OutOfMemoryError - return; - } - m_jerror_level = env->NewGlobalRef(jerror_level); - if (m_jerror_level == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jobject jfatal_level = InfoLogLevelJni::FATAL_LEVEL(env); - if (jfatal_level == nullptr) { - // exception thrown: NoSuchFieldError, ExceptionInInitializerError - // or OutOfMemoryError - return; - } - m_jfatal_level = env->NewGlobalRef(jfatal_level); - if (m_jfatal_level == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jobject jheader_level = InfoLogLevelJni::HEADER_LEVEL(env); - if (jheader_level == nullptr) { - // exception thrown: NoSuchFieldError, ExceptionInInitializerError - // or OutOfMemoryError - return; - } - m_jheader_level = env->NewGlobalRef(jheader_level); - if (m_jheader_level == nullptr) { - // exception thrown: OutOfMemoryError - return; - } -} - -void LoggerJniCallback::Logv(const char* /*format*/, va_list /*ap*/) { - // We implement this method because it is virtual but we don't - // use it because we need to know about the log level. -} - -void LoggerJniCallback::Logv(const InfoLogLevel log_level, const char* format, - va_list ap) { - if (GetInfoLogLevel() <= log_level) { - // determine InfoLogLevel java enum instance - jobject jlog_level; - switch (log_level) { - case ROCKSDB_NAMESPACE::InfoLogLevel::DEBUG_LEVEL: - jlog_level = m_jdebug_level; - break; - case ROCKSDB_NAMESPACE::InfoLogLevel::INFO_LEVEL: - jlog_level = m_jinfo_level; - break; - case ROCKSDB_NAMESPACE::InfoLogLevel::WARN_LEVEL: - jlog_level = m_jwarn_level; - break; - case ROCKSDB_NAMESPACE::InfoLogLevel::ERROR_LEVEL: - jlog_level = m_jerror_level; - break; - case ROCKSDB_NAMESPACE::InfoLogLevel::FATAL_LEVEL: - jlog_level = m_jfatal_level; - break; - case ROCKSDB_NAMESPACE::InfoLogLevel::HEADER_LEVEL: - jlog_level = m_jheader_level; - break; - default: - jlog_level = m_jfatal_level; - break; - } - - assert(format != nullptr); - const std::unique_ptr msg = format_str(format, ap); - - // pass msg to java callback handler - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - jstring jmsg = env->NewStringUTF(msg.get()); - if (jmsg == nullptr) { - // unable to construct string - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); // print out exception to stderr - } - releaseJniEnv(attached_thread); - return; - } - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - env->ExceptionDescribe(); // print out exception to stderr - env->DeleteLocalRef(jmsg); - releaseJniEnv(attached_thread); - return; - } - - env->CallVoidMethod(m_jcallback_obj, m_jLogMethodId, jlog_level, jmsg); - if (env->ExceptionCheck()) { - // exception thrown - env->ExceptionDescribe(); // print out exception to stderr - env->DeleteLocalRef(jmsg); - releaseJniEnv(attached_thread); - return; - } - - env->DeleteLocalRef(jmsg); - releaseJniEnv(attached_thread); - } -} - -std::unique_ptr LoggerJniCallback::format_str(const char* format, - va_list ap) const { - va_list ap_copy; - - va_copy(ap_copy, ap); - const size_t required = - vsnprintf(nullptr, 0, format, ap_copy) + 1; // Extra space for '\0' - va_end(ap_copy); - - std::unique_ptr buf(new char[required]); - - va_copy(ap_copy, ap); - vsnprintf(buf.get(), required, format, ap_copy); - va_end(ap_copy); - - return buf; -} -LoggerJniCallback::~LoggerJniCallback() { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - if (m_jdebug_level != nullptr) { - env->DeleteGlobalRef(m_jdebug_level); - } - - if (m_jinfo_level != nullptr) { - env->DeleteGlobalRef(m_jinfo_level); - } - - if (m_jwarn_level != nullptr) { - env->DeleteGlobalRef(m_jwarn_level); - } - - if (m_jerror_level != nullptr) { - env->DeleteGlobalRef(m_jerror_level); - } - - if (m_jfatal_level != nullptr) { - env->DeleteGlobalRef(m_jfatal_level); - } - - if (m_jheader_level != nullptr) { - env->DeleteGlobalRef(m_jheader_level); - } - - releaseJniEnv(attached_thread); -} - -} // namespace ROCKSDB_NAMESPACE - -/* - * Class: org_rocksdb_Logger - * Method: createNewLoggerOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_Logger_createNewLoggerOptions(JNIEnv* env, jobject jobj, - jlong joptions) { - auto* sptr_logger = new std::shared_ptr( - new ROCKSDB_NAMESPACE::LoggerJniCallback(env, jobj)); - - // set log level - auto* options = reinterpret_cast(joptions); - sptr_logger->get()->SetInfoLogLevel(options->info_log_level); - - return GET_CPLUSPLUS_POINTER(sptr_logger); -} - -/* - * Class: org_rocksdb_Logger - * Method: createNewLoggerDbOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_Logger_createNewLoggerDbOptions(JNIEnv* env, - jobject jobj, - jlong jdb_options) { - auto* sptr_logger = new std::shared_ptr( - new ROCKSDB_NAMESPACE::LoggerJniCallback(env, jobj)); - - // set log level - auto* db_options = - reinterpret_cast(jdb_options); - sptr_logger->get()->SetInfoLogLevel(db_options->info_log_level); - - return GET_CPLUSPLUS_POINTER(sptr_logger); -} - -/* - * Class: org_rocksdb_Logger - * Method: setInfoLogLevel - * Signature: (JB)V - */ -void Java_org_rocksdb_Logger_setInfoLogLevel(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle, jbyte jlog_level) { - auto* handle = - reinterpret_cast*>( - jhandle); - handle->get()->SetInfoLogLevel( - static_cast(jlog_level)); -} - -/* - * Class: org_rocksdb_Logger - * Method: infoLogLevel - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Logger_infoLogLevel(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* handle = - reinterpret_cast*>( - jhandle); - return static_cast(handle->get()->GetInfoLogLevel()); -} - -/* - * Class: org_rocksdb_Logger - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_Logger_disposeInternal(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* handle = - reinterpret_cast*>( - jhandle); - delete handle; // delete std::shared_ptr -} diff --git a/java/rocksjni/loggerjnicallback.h b/java/rocksjni/loggerjnicallback.h deleted file mode 100644 index 57774988c..000000000 --- a/java/rocksjni/loggerjnicallback.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Logger - -#ifndef JAVA_ROCKSJNI_LOGGERJNICALLBACK_H_ -#define JAVA_ROCKSJNI_LOGGERJNICALLBACK_H_ - -#include - -#include -#include - -#include "port/port.h" -#include "rocksdb/env.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -class LoggerJniCallback : public JniCallback, public Logger { - public: - LoggerJniCallback(JNIEnv* env, jobject jLogger); - ~LoggerJniCallback(); - - using Logger::GetInfoLogLevel; - using Logger::SetInfoLogLevel; - // Write an entry to the log file with the specified format. - virtual void Logv(const char* format, va_list ap); - // Write an entry to the log file with the specified log level - // and format. Any log with level under the internal log level - // of *this (see @SetInfoLogLevel and @GetInfoLogLevel) will not be - // printed. - virtual void Logv(const InfoLogLevel log_level, const char* format, - va_list ap); - - private: - jmethodID m_jLogMethodId; - jobject m_jdebug_level; - jobject m_jinfo_level; - jobject m_jwarn_level; - jobject m_jerror_level; - jobject m_jfatal_level; - jobject m_jheader_level; - std::unique_ptr format_str(const char* format, va_list ap) const; -}; -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_LOGGERJNICALLBACK_H_ diff --git a/java/rocksjni/lru_cache.cc b/java/rocksjni/lru_cache.cc deleted file mode 100644 index 56dffa2f0..000000000 --- a/java/rocksjni/lru_cache.cc +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::LRUCache. - -#include "cache/lru_cache.h" - -#include - -#include "include/org_rocksdb_LRUCache.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_LRUCache - * Method: newLRUCache - * Signature: (JIZD)J - */ -jlong Java_org_rocksdb_LRUCache_newLRUCache(JNIEnv* /*env*/, jclass /*jcls*/, - jlong jcapacity, - jint jnum_shard_bits, - jboolean jstrict_capacity_limit, - jdouble jhigh_pri_pool_ratio, - jdouble jlow_pri_pool_ratio) { - auto* sptr_lru_cache = new std::shared_ptr( - ROCKSDB_NAMESPACE::NewLRUCache( - static_cast(jcapacity), static_cast(jnum_shard_bits), - static_cast(jstrict_capacity_limit), - static_cast(jhigh_pri_pool_ratio), - nullptr /* memory_allocator */, rocksdb::kDefaultToAdaptiveMutex, - rocksdb::kDefaultCacheMetadataChargePolicy, - static_cast(jlow_pri_pool_ratio))); - return GET_CPLUSPLUS_POINTER(sptr_lru_cache); -} - -/* - * Class: org_rocksdb_LRUCache - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_LRUCache_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_lru_cache = - reinterpret_cast*>(jhandle); - delete sptr_lru_cache; // delete std::shared_ptr -} diff --git a/java/rocksjni/memory_util.cc b/java/rocksjni/memory_util.cc deleted file mode 100644 index c87c4f403..000000000 --- a/java/rocksjni/memory_util.cc +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/utilities/memory_util.h" - -#include - -#include -#include -#include -#include - -#include "include/org_rocksdb_MemoryUtil.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_MemoryUtil - * Method: getApproximateMemoryUsageByType - * Signature: ([J[J)Ljava/util/Map; - */ -jobject Java_org_rocksdb_MemoryUtil_getApproximateMemoryUsageByType( - JNIEnv *env, jclass, jlongArray jdb_handles, jlongArray jcache_handles) { - jboolean has_exception = JNI_FALSE; - std::vector dbs = - ROCKSDB_NAMESPACE::JniUtil::fromJPointers( - env, jdb_handles, &has_exception); - if (has_exception == JNI_TRUE) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - std::unordered_set cache_set; - jsize cache_handle_count = env->GetArrayLength(jcache_handles); - if (cache_handle_count > 0) { - jlong *ptr_jcache_handles = - env->GetLongArrayElements(jcache_handles, nullptr); - if (ptr_jcache_handles == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - for (jsize i = 0; i < cache_handle_count; i++) { - auto *cache_ptr = - reinterpret_cast *>( - ptr_jcache_handles[i]); - cache_set.insert(cache_ptr->get()); - } - env->ReleaseLongArrayElements(jcache_handles, ptr_jcache_handles, - JNI_ABORT); - } - - std::map usage_by_type; - if (ROCKSDB_NAMESPACE::MemoryUtil::GetApproximateMemoryUsageByType( - dbs, cache_set, &usage_by_type) != ROCKSDB_NAMESPACE::Status::OK()) { - // Non-OK status - return nullptr; - } - - jobject jusage_by_type = ROCKSDB_NAMESPACE::HashMapJni::construct( - env, static_cast(usage_by_type.size())); - if (jusage_by_type == nullptr) { - // exception occurred - return nullptr; - } - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const ROCKSDB_NAMESPACE::MemoryUtil::UsageType, const uint64_t, jobject, - jobject> - fn_map_kv = [env]( - const std::pair &pair) { - // Construct key - const jobject jusage_type = ROCKSDB_NAMESPACE::ByteJni::valueOf( - env, ROCKSDB_NAMESPACE::MemoryUsageTypeJni::toJavaMemoryUsageType( - pair.first)); - if (jusage_type == nullptr) { - // an error occurred - return std::unique_ptr>(nullptr); - } - // Construct value - const jobject jusage_value = - ROCKSDB_NAMESPACE::LongJni::valueOf(env, pair.second); - if (jusage_value == nullptr) { - // an error occurred - return std::unique_ptr>(nullptr); - } - // Construct and return pointer to pair of jobjects - return std::unique_ptr>( - new std::pair(jusage_type, jusage_value)); - }; - - if (!ROCKSDB_NAMESPACE::HashMapJni::putAll(env, jusage_by_type, - usage_by_type.begin(), - usage_by_type.end(), fn_map_kv)) { - // exception occcurred - jusage_by_type = nullptr; - } - - return jusage_by_type; -} diff --git a/java/rocksjni/memtablejni.cc b/java/rocksjni/memtablejni.cc deleted file mode 100644 index a4d02f354..000000000 --- a/java/rocksjni/memtablejni.cc +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for MemTables. - -#include "include/org_rocksdb_HashLinkedListMemTableConfig.h" -#include "include/org_rocksdb_HashSkipListMemTableConfig.h" -#include "include/org_rocksdb_SkipListMemTableConfig.h" -#include "include/org_rocksdb_VectorMemTableConfig.h" -#include "rocksdb/memtablerep.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_HashSkipListMemTableConfig - * Method: newMemTableFactoryHandle - * Signature: (JII)J - */ -jlong Java_org_rocksdb_HashSkipListMemTableConfig_newMemTableFactoryHandle( - JNIEnv* env, jobject /*jobj*/, jlong jbucket_count, jint jheight, - jint jbranching_factor) { - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(jbucket_count); - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(ROCKSDB_NAMESPACE::NewHashSkipListRepFactory( - static_cast(jbucket_count), static_cast(jheight), - static_cast(jbranching_factor))); - } - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - return 0; -} - -/* - * Class: org_rocksdb_HashLinkedListMemTableConfig - * Method: newMemTableFactoryHandle - * Signature: (JJIZI)J - */ -jlong Java_org_rocksdb_HashLinkedListMemTableConfig_newMemTableFactoryHandle( - JNIEnv* env, jobject /*jobj*/, jlong jbucket_count, - jlong jhuge_page_tlb_size, jint jbucket_entries_logging_threshold, - jboolean jif_log_bucket_dist_when_flash, jint jthreshold_use_skiplist) { - ROCKSDB_NAMESPACE::Status statusBucketCount = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(jbucket_count); - ROCKSDB_NAMESPACE::Status statusHugePageTlb = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jhuge_page_tlb_size); - if (statusBucketCount.ok() && statusHugePageTlb.ok()) { - return GET_CPLUSPLUS_POINTER(ROCKSDB_NAMESPACE::NewHashLinkListRepFactory( - static_cast(jbucket_count), - static_cast(jhuge_page_tlb_size), - static_cast(jbucket_entries_logging_threshold), - static_cast(jif_log_bucket_dist_when_flash), - static_cast(jthreshold_use_skiplist))); - } - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew( - env, !statusBucketCount.ok() ? statusBucketCount : statusHugePageTlb); - return 0; -} - -/* - * Class: org_rocksdb_VectorMemTableConfig - * Method: newMemTableFactoryHandle - * Signature: (J)J - */ -jlong Java_org_rocksdb_VectorMemTableConfig_newMemTableFactoryHandle( - JNIEnv* env, jobject /*jobj*/, jlong jreserved_size) { - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(jreserved_size); - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(new ROCKSDB_NAMESPACE::VectorRepFactory( - static_cast(jreserved_size))); - } - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - return 0; -} - -/* - * Class: org_rocksdb_SkipListMemTableConfig - * Method: newMemTableFactoryHandle0 - * Signature: (J)J - */ -jlong Java_org_rocksdb_SkipListMemTableConfig_newMemTableFactoryHandle0( - JNIEnv* env, jobject /*jobj*/, jlong jlookahead) { - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(jlookahead); - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(new ROCKSDB_NAMESPACE::SkipListFactory( - static_cast(jlookahead))); - } - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - return 0; -} diff --git a/java/rocksjni/merge_operator.cc b/java/rocksjni/merge_operator.cc deleted file mode 100644 index ce3c5df56..000000000 --- a/java/rocksjni/merge_operator.cc +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// Copyright (c) 2014, Vlad Balan (vlad.gm@gmail.com). All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::MergeOperator. - -#include "rocksdb/merge_operator.h" - -#include -#include -#include - -#include -#include - -#include "include/org_rocksdb_StringAppendOperator.h" -#include "include/org_rocksdb_UInt64AddOperator.h" -#include "rocksdb/db.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/options.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -#include "utilities/merge_operators.h" - -/* - * Class: org_rocksdb_StringAppendOperator - * Method: newSharedStringAppendOperator - * Signature: (C)J - */ -jlong Java_org_rocksdb_StringAppendOperator_newSharedStringAppendOperator__C( - JNIEnv* /*env*/, jclass /*jclazz*/, jchar jdelim) { - auto* sptr_string_append_op = - new std::shared_ptr( - ROCKSDB_NAMESPACE::MergeOperators::CreateStringAppendOperator( - (char)jdelim)); - return GET_CPLUSPLUS_POINTER(sptr_string_append_op); -} - -jlong Java_org_rocksdb_StringAppendOperator_newSharedStringAppendOperator__Ljava_lang_String_2( - JNIEnv* env, jclass /*jclass*/, jstring jdelim) { - jboolean has_exception = JNI_FALSE; - auto delim = - ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jdelim, &has_exception); - if (has_exception == JNI_TRUE) { - return 0; - } - auto* sptr_string_append_op = - new std::shared_ptr( - ROCKSDB_NAMESPACE::MergeOperators::CreateStringAppendOperator(delim)); - return GET_CPLUSPLUS_POINTER(sptr_string_append_op); -} - -/* - * Class: org_rocksdb_StringAppendOperator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_StringAppendOperator_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_string_append_op = - reinterpret_cast*>( - jhandle); - delete sptr_string_append_op; // delete std::shared_ptr -} - -/* - * Class: org_rocksdb_UInt64AddOperator - * Method: newSharedUInt64AddOperator - * Signature: ()J - */ -jlong Java_org_rocksdb_UInt64AddOperator_newSharedUInt64AddOperator( - JNIEnv* /*env*/, jclass /*jclazz*/) { - auto* sptr_uint64_add_op = - new std::shared_ptr( - ROCKSDB_NAMESPACE::MergeOperators::CreateUInt64AddOperator()); - return GET_CPLUSPLUS_POINTER(sptr_uint64_add_op); -} - -/* - * Class: org_rocksdb_UInt64AddOperator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_UInt64AddOperator_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_uint64_add_op = - reinterpret_cast*>( - jhandle); - delete sptr_uint64_add_op; // delete std::shared_ptr -} diff --git a/java/rocksjni/native_comparator_wrapper_test.cc b/java/rocksjni/native_comparator_wrapper_test.cc deleted file mode 100644 index ac33ca22d..000000000 --- a/java/rocksjni/native_comparator_wrapper_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include - -#include "include/org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper.h" -#include "rocksdb/comparator.h" -#include "rocksdb/slice.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -namespace ROCKSDB_NAMESPACE { - -class NativeComparatorWrapperTestStringComparator : public Comparator { - const char* Name() const { - return "NativeComparatorWrapperTestStringComparator"; - } - - int Compare(const Slice& a, const Slice& b) const { - return a.ToString().compare(b.ToString()); - } - - void FindShortestSeparator(std::string* /*start*/, - const Slice& /*limit*/) const { - return; - } - - void FindShortSuccessor(std::string* /*key*/) const { return; } -}; -} // namespace ROCKSDB_NAMESPACE - -/* - * Class: org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper - * Method: newStringComparator - * Signature: ()J - */ -jlong Java_org_rocksdb_NativeComparatorWrapperTest_00024NativeStringComparatorWrapper_newStringComparator( - JNIEnv* /*env*/, jobject /*jobj*/) { - auto* comparator = - new ROCKSDB_NAMESPACE::NativeComparatorWrapperTestStringComparator(); - return GET_CPLUSPLUS_POINTER(comparator); -} diff --git a/java/rocksjni/optimistic_transaction_db.cc b/java/rocksjni/optimistic_transaction_db.cc deleted file mode 100644 index 238224f58..000000000 --- a/java/rocksjni/optimistic_transaction_db.cc +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::TransactionDB. - -#include "rocksdb/utilities/optimistic_transaction_db.h" - -#include - -#include "include/org_rocksdb_OptimisticTransactionDB.h" -#include "rocksdb/options.h" -#include "rocksdb/utilities/transaction.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: open - * Signature: (JLjava/lang/String;)J - */ -jlong Java_org_rocksdb_OptimisticTransactionDB_open__JLjava_lang_String_2( - JNIEnv* env, jclass, jlong joptions_handle, jstring jdb_path) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - auto* options = - reinterpret_cast(joptions_handle); - ROCKSDB_NAMESPACE::OptimisticTransactionDB* otdb = nullptr; - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::OptimisticTransactionDB::Open(*options, db_path, - &otdb); - env->ReleaseStringUTFChars(jdb_path, db_path); - - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(otdb); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; - } -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: open - * Signature: (JLjava/lang/String;[[B[J)[J - */ -jlongArray -Java_org_rocksdb_OptimisticTransactionDB_open__JLjava_lang_String_2_3_3B_3J( - JNIEnv* env, jclass, jlong jdb_options_handle, jstring jdb_path, - jobjectArray jcolumn_names, jlongArray jcolumn_options_handles) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - std::vector column_families; - const jsize len_cols = env->GetArrayLength(jcolumn_names); - if (len_cols > 0) { - jlong* jco = env->GetLongArrayElements(jcolumn_options_handles, nullptr); - if (jco == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - for (int i = 0; i < len_cols; i++) { - const jobject jcn = env->GetObjectArrayElement(jcolumn_names, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - const jbyteArray jcn_ba = reinterpret_cast(jcn); - const jsize jcf_name_len = env->GetArrayLength(jcn_ba); - jbyte* jcf_name = env->GetByteArrayElements(jcn_ba, nullptr); - if (jcf_name == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jcn); - env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - const std::string cf_name(reinterpret_cast(jcf_name), - jcf_name_len); - const ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options = - reinterpret_cast(jco[i]); - column_families.push_back( - ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(cf_name, *cf_options)); - - env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT); - env->DeleteLocalRef(jcn); - } - env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); - } - - auto* db_options = - reinterpret_cast(jdb_options_handle); - std::vector handles; - ROCKSDB_NAMESPACE::OptimisticTransactionDB* otdb = nullptr; - const ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::OptimisticTransactionDB::Open( - *db_options, db_path, column_families, &handles, &otdb); - - env->ReleaseStringUTFChars(jdb_path, db_path); - - // check if open operation was successful - if (s.ok()) { - const jsize resultsLen = 1 + len_cols; // db handle + column family handles - std::unique_ptr results = - std::unique_ptr(new jlong[resultsLen]); - results[0] = reinterpret_cast(otdb); - for (int i = 1; i <= len_cols; i++) { - results[i] = reinterpret_cast(handles[i - 1]); - } - - jlongArray jresults = env->NewLongArray(resultsLen); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return nullptr; - } - return jresults; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_OptimisticTransactionDB_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - assert(optimistic_txn_db != nullptr); - delete optimistic_txn_db; -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: closeDatabase - * Signature: (J)V - */ -void Java_org_rocksdb_OptimisticTransactionDB_closeDatabase(JNIEnv* env, jclass, - jlong jhandle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - assert(optimistic_txn_db != nullptr); - ROCKSDB_NAMESPACE::Status s = optimistic_txn_db->Close(); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: beginTransaction - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction__JJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - ROCKSDB_NAMESPACE::Transaction* txn = - optimistic_txn_db->BeginTransaction(*write_options); - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: beginTransaction - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction__JJJ( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jwrite_options_handle, jlong joptimistic_txn_options_handle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* optimistic_txn_options = - reinterpret_cast( - joptimistic_txn_options_handle); - ROCKSDB_NAMESPACE::Transaction* txn = optimistic_txn_db->BeginTransaction( - *write_options, *optimistic_txn_options); - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: beginTransaction_withOld - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction_1withOld__JJJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, - jlong jold_txn_handle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* old_txn = - reinterpret_cast(jold_txn_handle); - ROCKSDB_NAMESPACE::OptimisticTransactionOptions optimistic_txn_options; - ROCKSDB_NAMESPACE::Transaction* txn = optimistic_txn_db->BeginTransaction( - *write_options, optimistic_txn_options, old_txn); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_optimistic_txn - assert(txn == old_txn); - - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: beginTransaction_withOld - * Signature: (JJJJ)J - */ -jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction_1withOld__JJJJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, - jlong joptimistic_txn_options_handle, jlong jold_txn_handle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* optimistic_txn_options = - reinterpret_cast( - joptimistic_txn_options_handle); - auto* old_txn = - reinterpret_cast(jold_txn_handle); - ROCKSDB_NAMESPACE::Transaction* txn = optimistic_txn_db->BeginTransaction( - *write_options, *optimistic_txn_options, old_txn); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_optimisic_txn - assert(txn == old_txn); - - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_OptimisticTransactionDB - * Method: getBaseDB - * Signature: (J)J - */ -jlong Java_org_rocksdb_OptimisticTransactionDB_getBaseDB(JNIEnv*, jobject, - jlong jhandle) { - auto* optimistic_txn_db = - reinterpret_cast(jhandle); - return GET_CPLUSPLUS_POINTER(optimistic_txn_db->GetBaseDB()); -} diff --git a/java/rocksjni/optimistic_transaction_options.cc b/java/rocksjni/optimistic_transaction_options.cc deleted file mode 100644 index 501c6c4fb..000000000 --- a/java/rocksjni/optimistic_transaction_options.cc +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::OptimisticTransactionOptions. - -#include - -#include "include/org_rocksdb_OptimisticTransactionOptions.h" -#include "rocksdb/comparator.h" -#include "rocksdb/utilities/optimistic_transaction_db.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_OptimisticTransactionOptions - * Method: newOptimisticTransactionOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_OptimisticTransactionOptions_newOptimisticTransactionOptions( - JNIEnv* /*env*/, jclass /*jcls*/) { - ROCKSDB_NAMESPACE::OptimisticTransactionOptions* opts = - new ROCKSDB_NAMESPACE::OptimisticTransactionOptions(); - return GET_CPLUSPLUS_POINTER(opts); -} - -/* - * Class: org_rocksdb_OptimisticTransactionOptions - * Method: isSetSnapshot - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_OptimisticTransactionOptions_isSetSnapshot( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* opts = - reinterpret_cast( - jhandle); - return opts->set_snapshot; -} - -/* - * Class: org_rocksdb_OptimisticTransactionOptions - * Method: setSetSnapshot - * Signature: (JZ)V - */ -void Java_org_rocksdb_OptimisticTransactionOptions_setSetSnapshot( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean jset_snapshot) { - auto* opts = - reinterpret_cast( - jhandle); - opts->set_snapshot = jset_snapshot; -} - -/* - * Class: org_rocksdb_OptimisticTransactionOptions - * Method: setComparator - * Signature: (JJ)V - */ -void Java_org_rocksdb_OptimisticTransactionOptions_setComparator( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jcomparator_handle) { - auto* opts = - reinterpret_cast( - jhandle); - opts->cmp = - reinterpret_cast(jcomparator_handle); -} - -/* - * Class: org_rocksdb_OptimisticTransactionOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_OptimisticTransactionOptions_disposeInternal( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - delete reinterpret_cast( - jhandle); -} diff --git a/java/rocksjni/options.cc b/java/rocksjni/options.cc deleted file mode 100644 index 724d298e7..000000000 --- a/java/rocksjni/options.cc +++ /dev/null @@ -1,8695 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Options. - -#include "rocksdb/options.h" - -#include -#include -#include - -#include -#include - -#include "include/org_rocksdb_ColumnFamilyOptions.h" -#include "include/org_rocksdb_ComparatorOptions.h" -#include "include/org_rocksdb_DBOptions.h" -#include "include/org_rocksdb_FlushOptions.h" -#include "include/org_rocksdb_Options.h" -#include "include/org_rocksdb_ReadOptions.h" -#include "include/org_rocksdb_WriteOptions.h" -#include "rocksdb/comparator.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/merge_operator.h" -#include "rocksdb/rate_limiter.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/sst_partitioner.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table.h" -#include "rocksjni/comparatorjnicallback.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -#include "rocksjni/statisticsjni.h" -#include "rocksjni/table_filter_jnicallback.h" -#include "utilities/merge_operators.h" - -/* - * Class: org_rocksdb_Options - * Method: newOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_Options_newOptions__(JNIEnv*, jclass) { - auto* op = new ROCKSDB_NAMESPACE::Options(); - return GET_CPLUSPLUS_POINTER(op); -} - -/* - * Class: org_rocksdb_Options - * Method: newOptions - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_Options_newOptions__JJ(JNIEnv*, jclass, jlong jdboptions, - jlong jcfoptions) { - auto* dbOpt = - reinterpret_cast(jdboptions); - auto* cfOpt = reinterpret_cast( - jcfoptions); - auto* op = new ROCKSDB_NAMESPACE::Options(*dbOpt, *cfOpt); - return GET_CPLUSPLUS_POINTER(op); -} - -/* - * Class: org_rocksdb_Options - * Method: copyOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_copyOptions(JNIEnv*, jclass, jlong jhandle) { - auto new_opt = new ROCKSDB_NAMESPACE::Options( - *(reinterpret_cast(jhandle))); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_Options - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_Options_disposeInternal(JNIEnv*, jobject, jlong handle) { - auto* op = reinterpret_cast(handle); - assert(op != nullptr); - delete op; -} - -/* - * Class: org_rocksdb_Options - * Method: setIncreaseParallelism - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setIncreaseParallelism(JNIEnv*, jobject, - jlong jhandle, - jint totalThreads) { - reinterpret_cast(jhandle)->IncreaseParallelism( - static_cast(totalThreads)); -} - -/* - * Class: org_rocksdb_Options - * Method: setCreateIfMissing - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setCreateIfMissing(JNIEnv*, jobject, - jlong jhandle, jboolean flag) { - reinterpret_cast(jhandle)->create_if_missing = - flag; -} - -/* - * Class: org_rocksdb_Options - * Method: createIfMissing - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_createIfMissing(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->create_if_missing; -} - -/* - * Class: org_rocksdb_Options - * Method: setCreateMissingColumnFamilies - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setCreateMissingColumnFamilies(JNIEnv*, jobject, - jlong jhandle, - jboolean flag) { - reinterpret_cast(jhandle) - ->create_missing_column_families = flag; -} - -/* - * Class: org_rocksdb_Options - * Method: createMissingColumnFamilies - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_createMissingColumnFamilies(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->create_missing_column_families; -} - -/* - * Class: org_rocksdb_Options - * Method: setComparatorHandle - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setComparatorHandle__JI(JNIEnv*, jobject, - jlong jhandle, - jint builtinComparator) { - switch (builtinComparator) { - case 1: - reinterpret_cast(jhandle)->comparator = - ROCKSDB_NAMESPACE::ReverseBytewiseComparator(); - break; - default: - reinterpret_cast(jhandle)->comparator = - ROCKSDB_NAMESPACE::BytewiseComparator(); - break; - } -} - -/* - * Class: org_rocksdb_Options - * Method: setComparatorHandle - * Signature: (JJB)V - */ -void Java_org_rocksdb_Options_setComparatorHandle__JJB(JNIEnv*, jobject, - jlong jopt_handle, - jlong jcomparator_handle, - jbyte jcomparator_type) { - ROCKSDB_NAMESPACE::Comparator* comparator = nullptr; - switch (jcomparator_type) { - // JAVA_COMPARATOR - case 0x0: - comparator = reinterpret_cast( - jcomparator_handle); - break; - - // JAVA_NATIVE_COMPARATOR_WRAPPER - case 0x1: - comparator = - reinterpret_cast(jcomparator_handle); - break; - } - auto* opt = reinterpret_cast(jopt_handle); - opt->comparator = comparator; -} - -/* - * Class: org_rocksdb_Options - * Method: setMergeOperatorName - * Signature: (JJjava/lang/String)V - */ -void Java_org_rocksdb_Options_setMergeOperatorName(JNIEnv* env, jobject, - jlong jhandle, - jstring jop_name) { - const char* op_name = env->GetStringUTFChars(jop_name, nullptr); - if (op_name == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* options = reinterpret_cast(jhandle); - options->merge_operator = - ROCKSDB_NAMESPACE::MergeOperators::CreateFromStringId(op_name); - - env->ReleaseStringUTFChars(jop_name, op_name); -} - -/* - * Class: org_rocksdb_Options - * Method: setMergeOperator - * Signature: (JJjava/lang/String)V - */ -void Java_org_rocksdb_Options_setMergeOperator(JNIEnv*, jobject, jlong jhandle, - jlong mergeOperatorHandle) { - reinterpret_cast(jhandle)->merge_operator = - *(reinterpret_cast*>( - mergeOperatorHandle)); -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionFilterHandle - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setCompactionFilterHandle( - JNIEnv*, jobject, jlong jopt_handle, jlong jcompactionfilter_handle) { - reinterpret_cast(jopt_handle) - ->compaction_filter = - reinterpret_cast( - jcompactionfilter_handle); -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionFilterFactoryHandle - * Signature: (JJ)V - */ -void JNICALL Java_org_rocksdb_Options_setCompactionFilterFactoryHandle( - JNIEnv*, jobject, jlong jopt_handle, - jlong jcompactionfilterfactory_handle) { - auto* cff_factory = reinterpret_cast< - std::shared_ptr*>( - jcompactionfilterfactory_handle); - reinterpret_cast(jopt_handle) - ->compaction_filter_factory = *cff_factory; -} - -/* - * Class: org_rocksdb_Options - * Method: setWriteBufferSize - * Signature: (JJ)I - */ -void Java_org_rocksdb_Options_setWriteBufferSize(JNIEnv* env, jobject, - jlong jhandle, - jlong jwrite_buffer_size) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jwrite_buffer_size); - if (s.ok()) { - reinterpret_cast(jhandle)->write_buffer_size = - jwrite_buffer_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: setWriteBufferManager - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWriteBufferManager( - JNIEnv*, jobject, jlong joptions_handle, - jlong jwrite_buffer_manager_handle) { - auto* write_buffer_manager = - reinterpret_cast*>( - jwrite_buffer_manager_handle); - reinterpret_cast(joptions_handle) - ->write_buffer_manager = *write_buffer_manager; -} - -/* - * Class: org_rocksdb_Options - * Method: writeBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_writeBufferSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->write_buffer_size; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxWriteBufferNumber - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxWriteBufferNumber( - JNIEnv*, jobject, jlong jhandle, jint jmax_write_buffer_number) { - reinterpret_cast(jhandle) - ->max_write_buffer_number = jmax_write_buffer_number; -} - -/* - * Class: org_rocksdb_Options - * Method: setStatistics - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setStatistics(JNIEnv*, jobject, jlong jhandle, - jlong jstatistics_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* pSptr = - reinterpret_cast*>( - jstatistics_handle); - opt->statistics = *pSptr; -} - -/* - * Class: org_rocksdb_Options - * Method: statistics - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_statistics(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - std::shared_ptr sptr = opt->statistics; - if (sptr == nullptr) { - return 0; - } else { - std::shared_ptr* pSptr = - new std::shared_ptr(sptr); - return GET_CPLUSPLUS_POINTER(pSptr); - } -} - -/* - * Class: org_rocksdb_Options - * Method: maxWriteBufferNumber - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxWriteBufferNumber(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_write_buffer_number; -} - -/* - * Class: org_rocksdb_Options - * Method: errorIfExists - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_errorIfExists(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->error_if_exists; -} - -/* - * Class: org_rocksdb_Options - * Method: setErrorIfExists - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setErrorIfExists(JNIEnv*, jobject, jlong jhandle, - jboolean error_if_exists) { - reinterpret_cast(jhandle)->error_if_exists = - static_cast(error_if_exists); -} - -/* - * Class: org_rocksdb_Options - * Method: paranoidChecks - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_paranoidChecks(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->paranoid_checks; -} - -/* - * Class: org_rocksdb_Options - * Method: setParanoidChecks - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setParanoidChecks(JNIEnv*, jobject, jlong jhandle, - jboolean paranoid_checks) { - reinterpret_cast(jhandle)->paranoid_checks = - static_cast(paranoid_checks); -} - -/* - * Class: org_rocksdb_Options - * Method: setEnv - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setEnv(JNIEnv*, jobject, jlong jhandle, - jlong jenv) { - reinterpret_cast(jhandle)->env = - reinterpret_cast(jenv); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxTotalWalSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxTotalWalSize(JNIEnv*, jobject, - jlong jhandle, - jlong jmax_total_wal_size) { - reinterpret_cast(jhandle)->max_total_wal_size = - static_cast(jmax_total_wal_size); -} - -/* - * Class: org_rocksdb_Options - * Method: maxTotalWalSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxTotalWalSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_total_wal_size; -} - -/* - * Class: org_rocksdb_Options - * Method: maxOpenFiles - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxOpenFiles(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->max_open_files; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxOpenFiles - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxOpenFiles(JNIEnv*, jobject, jlong jhandle, - jint max_open_files) { - reinterpret_cast(jhandle)->max_open_files = - static_cast(max_open_files); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxFileOpeningThreads - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxFileOpeningThreads( - JNIEnv*, jobject, jlong jhandle, jint jmax_file_opening_threads) { - reinterpret_cast(jhandle) - ->max_file_opening_threads = static_cast(jmax_file_opening_threads); -} - -/* - * Class: org_rocksdb_Options - * Method: maxFileOpeningThreads - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxFileOpeningThreads(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_file_opening_threads); -} - -/* - * Class: org_rocksdb_Options - * Method: useFsync - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_useFsync(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->use_fsync; -} - -/* - * Class: org_rocksdb_Options - * Method: setUseFsync - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setUseFsync(JNIEnv*, jobject, jlong jhandle, - jboolean use_fsync) { - reinterpret_cast(jhandle)->use_fsync = - static_cast(use_fsync); -} - -/* - * Class: org_rocksdb_Options - * Method: setDbPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_Options_setDbPaths(JNIEnv* env, jobject, jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - std::vector db_paths; - jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr); - if (ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jboolean has_exception = JNI_FALSE; - const jsize len = env->GetArrayLength(jpaths); - for (jsize i = 0; i < len; i++) { - jobject jpath = - reinterpret_cast(env->GetObjectArrayElement(jpaths, i)); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - std::string path = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, static_cast(jpath), &has_exception); - env->DeleteLocalRef(jpath); - - if (has_exception == JNI_TRUE) { - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - - jlong jtarget_size = ptr_jtarget_size[i]; - - db_paths.push_back( - ROCKSDB_NAMESPACE::DbPath(path, static_cast(jtarget_size))); - } - - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - - auto* opt = reinterpret_cast(jhandle); - opt->db_paths = db_paths; -} - -/* - * Class: org_rocksdb_Options - * Method: dbPathsLen - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_dbPathsLen(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->db_paths.size()); -} - -/* - * Class: org_rocksdb_Options - * Method: dbPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_Options_dbPaths(JNIEnv* env, jobject, jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - jboolean is_copy; - jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, &is_copy); - if (ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* opt = reinterpret_cast(jhandle); - const jsize len = env->GetArrayLength(jpaths); - for (jsize i = 0; i < len; i++) { - ROCKSDB_NAMESPACE::DbPath db_path = opt->db_paths[i]; - - jstring jpath = env->NewStringUTF(db_path.path.c_str()); - if (jpath == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - env->SetObjectArrayElement(jpaths, i, jpath); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jpath); - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - - ptr_jtarget_size[i] = static_cast(db_path.target_size); - } - - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, - is_copy == JNI_TRUE ? 0 : JNI_ABORT); -} - -/* - * Class: org_rocksdb_Options - * Method: dbLogDir - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_Options_dbLogDir(JNIEnv* env, jobject, jlong jhandle) { - return env->NewStringUTF( - reinterpret_cast(jhandle) - ->db_log_dir.c_str()); -} - -/* - * Class: org_rocksdb_Options - * Method: setDbLogDir - * Signature: (JLjava/lang/String)V - */ -void Java_org_rocksdb_Options_setDbLogDir(JNIEnv* env, jobject, jlong jhandle, - jstring jdb_log_dir) { - const char* log_dir = env->GetStringUTFChars(jdb_log_dir, nullptr); - if (log_dir == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - reinterpret_cast(jhandle)->db_log_dir.assign( - log_dir); - env->ReleaseStringUTFChars(jdb_log_dir, log_dir); -} - -/* - * Class: org_rocksdb_Options - * Method: walDir - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_Options_walDir(JNIEnv* env, jobject, jlong jhandle) { - return env->NewStringUTF( - reinterpret_cast(jhandle)->wal_dir.c_str()); -} - -/* - * Class: org_rocksdb_Options - * Method: setWalDir - * Signature: (JLjava/lang/String)V - */ -void Java_org_rocksdb_Options_setWalDir(JNIEnv* env, jobject, jlong jhandle, - jstring jwal_dir) { - const char* wal_dir = env->GetStringUTFChars(jwal_dir, nullptr); - if (wal_dir == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - reinterpret_cast(jhandle)->wal_dir.assign( - wal_dir); - env->ReleaseStringUTFChars(jwal_dir, wal_dir); -} - -/* - * Class: org_rocksdb_Options - * Method: deleteObsoleteFilesPeriodMicros - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_deleteObsoleteFilesPeriodMicros(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->delete_obsolete_files_period_micros; -} - -/* - * Class: org_rocksdb_Options - * Method: setDeleteObsoleteFilesPeriodMicros - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setDeleteObsoleteFilesPeriodMicros(JNIEnv*, - jobject, - jlong jhandle, - jlong micros) { - reinterpret_cast(jhandle) - ->delete_obsolete_files_period_micros = static_cast(micros); -} - -/* - * Class: org_rocksdb_Options - * Method: maxBackgroundCompactions - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxBackgroundCompactions(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_background_compactions; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBackgroundCompactions - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxBackgroundCompactions(JNIEnv*, jobject, - jlong jhandle, - jint max) { - reinterpret_cast(jhandle) - ->max_background_compactions = static_cast(max); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxSubcompactions - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxSubcompactions(JNIEnv*, jobject, - jlong jhandle, jint max) { - reinterpret_cast(jhandle)->max_subcompactions = - static_cast(max); -} - -/* - * Class: org_rocksdb_Options - * Method: maxSubcompactions - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxSubcompactions(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_subcompactions; -} - -/* - * Class: org_rocksdb_Options - * Method: maxBackgroundFlushes - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxBackgroundFlushes(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_background_flushes; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBackgroundFlushes - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxBackgroundFlushes( - JNIEnv*, jobject, jlong jhandle, jint max_background_flushes) { - reinterpret_cast(jhandle) - ->max_background_flushes = static_cast(max_background_flushes); -} - -/* - * Class: org_rocksdb_Options - * Method: maxBackgroundJobs - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxBackgroundJobs(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_background_jobs; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBackgroundJobs - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxBackgroundJobs(JNIEnv*, jobject, - jlong jhandle, - jint max_background_jobs) { - reinterpret_cast(jhandle)->max_background_jobs = - static_cast(max_background_jobs); -} - -/* - * Class: org_rocksdb_Options - * Method: maxLogFileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxLogFileSize(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_log_file_size; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxLogFileSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxLogFileSize(JNIEnv* env, jobject, - jlong jhandle, - jlong max_log_file_size) { - auto s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(max_log_file_size); - if (s.ok()) { - reinterpret_cast(jhandle)->max_log_file_size = - max_log_file_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: logFileTimeToRoll - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_logFileTimeToRoll(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->log_file_time_to_roll; -} - -/* - * Class: org_rocksdb_Options - * Method: setLogFileTimeToRoll - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setLogFileTimeToRoll( - JNIEnv* env, jobject, jlong jhandle, jlong log_file_time_to_roll) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - log_file_time_to_roll); - if (s.ok()) { - reinterpret_cast(jhandle) - ->log_file_time_to_roll = log_file_time_to_roll; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: keepLogFileNum - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_keepLogFileNum(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->keep_log_file_num; -} - -/* - * Class: org_rocksdb_Options - * Method: setKeepLogFileNum - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setKeepLogFileNum(JNIEnv* env, jobject, - jlong jhandle, - jlong keep_log_file_num) { - auto s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(keep_log_file_num); - if (s.ok()) { - reinterpret_cast(jhandle)->keep_log_file_num = - keep_log_file_num; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: recycleLogFileNum - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_recycleLogFileNum(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->recycle_log_file_num; -} - -/* - * Class: org_rocksdb_Options - * Method: setRecycleLogFileNum - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setRecycleLogFileNum(JNIEnv* env, jobject, - jlong jhandle, - jlong recycle_log_file_num) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - recycle_log_file_num); - if (s.ok()) { - reinterpret_cast(jhandle) - ->recycle_log_file_num = recycle_log_file_num; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: maxManifestFileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxManifestFileSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_manifest_file_size; -} - -/* - * Method: memTableFactoryName - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_Options_memTableFactoryName(JNIEnv* env, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::MemTableRepFactory* tf = opt->memtable_factory.get(); - - // Should never be nullptr. - // Default memtable factory is SkipListFactory - assert(tf); - - // temporarly fix for the historical typo - if (strcmp(tf->Name(), "HashLinkListRepFactory") == 0) { - return env->NewStringUTF("HashLinkedListRepFactory"); - } - - return env->NewStringUTF(tf->Name()); -} - -static std::vector -rocksdb_convert_cf_paths_from_java_helper(JNIEnv* env, jobjectArray path_array, - jlongArray size_array, - jboolean* has_exception) { - jboolean copy_str_has_exception; - std::vector paths = ROCKSDB_NAMESPACE::JniUtil::copyStrings( - env, path_array, ©_str_has_exception); - if (JNI_TRUE == copy_str_has_exception) { - // Exception thrown - *has_exception = JNI_TRUE; - return {}; - } - - if (static_cast(env->GetArrayLength(size_array)) != paths.size()) { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew( - env, - ROCKSDB_NAMESPACE::Status::InvalidArgument( - ROCKSDB_NAMESPACE::Slice("There should be a corresponding target " - "size for every path and vice versa."))); - *has_exception = JNI_TRUE; - return {}; - } - - jlong* size_array_ptr = env->GetLongArrayElements(size_array, nullptr); - if (nullptr == size_array_ptr) { - // exception thrown: OutOfMemoryError - *has_exception = JNI_TRUE; - return {}; - } - std::vector cf_paths; - for (size_t i = 0; i < paths.size(); ++i) { - jlong target_size = size_array_ptr[i]; - if (target_size < 0) { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew( - env, - ROCKSDB_NAMESPACE::Status::InvalidArgument(ROCKSDB_NAMESPACE::Slice( - "Path target size has to be positive."))); - *has_exception = JNI_TRUE; - env->ReleaseLongArrayElements(size_array, size_array_ptr, JNI_ABORT); - return {}; - } - cf_paths.push_back(ROCKSDB_NAMESPACE::DbPath( - paths[i], static_cast(target_size))); - } - - env->ReleaseLongArrayElements(size_array, size_array_ptr, JNI_ABORT); - - return cf_paths; -} - -/* - * Class: org_rocksdb_Options - * Method: setCfPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_Options_setCfPaths(JNIEnv* env, jclass, jlong jhandle, - jobjectArray path_array, - jlongArray size_array) { - auto* options = reinterpret_cast(jhandle); - jboolean has_exception = JNI_FALSE; - std::vector cf_paths = - rocksdb_convert_cf_paths_from_java_helper(env, path_array, size_array, - &has_exception); - if (JNI_FALSE == has_exception) { - options->cf_paths = std::move(cf_paths); - } -} - -/* - * Class: org_rocksdb_Options - * Method: cfPathsLen - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_cfPathsLen(JNIEnv*, jclass, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->cf_paths.size()); -} - -template -static void rocksdb_convert_cf_paths_to_java_helper(JNIEnv* env, jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - jboolean is_copy; - jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, &is_copy); - if (ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* opt = reinterpret_cast(jhandle); - const jsize len = env->GetArrayLength(jpaths); - for (jsize i = 0; i < len; i++) { - ROCKSDB_NAMESPACE::DbPath cf_path = opt->cf_paths[i]; - - jstring jpath = env->NewStringUTF(cf_path.path.c_str()); - if (jpath == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - env->SetObjectArrayElement(jpaths, i, jpath); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jpath); - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - - ptr_jtarget_size[i] = static_cast(cf_path.target_size); - - env->DeleteLocalRef(jpath); - } - - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, - is_copy ? 0 : JNI_ABORT); -} - -/* - * Class: org_rocksdb_Options - * Method: cfPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_Options_cfPaths(JNIEnv* env, jclass, jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - rocksdb_convert_cf_paths_to_java_helper( - env, jhandle, jpaths, jtarget_sizes); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxManifestFileSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxManifestFileSize( - JNIEnv*, jobject, jlong jhandle, jlong max_manifest_file_size) { - reinterpret_cast(jhandle) - ->max_manifest_file_size = static_cast(max_manifest_file_size); -} - -/* - * Method: setMemTableFactory - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMemTableFactory(JNIEnv*, jobject, - jlong jhandle, - jlong jfactory_handle) { - reinterpret_cast(jhandle) - ->memtable_factory.reset( - reinterpret_cast( - jfactory_handle)); -} - -/* - * Class: org_rocksdb_Options - * Method: setRateLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setRateLimiter(JNIEnv*, jobject, jlong jhandle, - jlong jrate_limiter_handle) { - std::shared_ptr* pRateLimiter = - reinterpret_cast*>( - jrate_limiter_handle); - reinterpret_cast(jhandle)->rate_limiter = - *pRateLimiter; -} - -/* - * Class: org_rocksdb_Options - * Method: setSstFileManager - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setSstFileManager( - JNIEnv*, jobject, jlong jhandle, jlong jsst_file_manager_handle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jsst_file_manager_handle); - reinterpret_cast(jhandle)->sst_file_manager = - *sptr_sst_file_manager; -} - -/* - * Class: org_rocksdb_Options - * Method: setLogger - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setLogger(JNIEnv*, jobject, jlong jhandle, - jlong jlogger_handle) { - std::shared_ptr* pLogger = - reinterpret_cast*>( - jlogger_handle); - reinterpret_cast(jhandle)->info_log = *pLogger; -} - -/* - * Class: org_rocksdb_Options - * Method: setInfoLogLevel - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setInfoLogLevel(JNIEnv*, jobject, jlong jhandle, - jbyte jlog_level) { - reinterpret_cast(jhandle)->info_log_level = - static_cast(jlog_level); -} - -/* - * Class: org_rocksdb_Options - * Method: infoLogLevel - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_infoLogLevel(JNIEnv*, jobject, jlong jhandle) { - return static_cast( - reinterpret_cast(jhandle)->info_log_level); -} - -/* - * Class: org_rocksdb_Options - * Method: tableCacheNumshardbits - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_tableCacheNumshardbits(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->table_cache_numshardbits; -} - -/* - * Class: org_rocksdb_Options - * Method: setTableCacheNumshardbits - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setTableCacheNumshardbits( - JNIEnv*, jobject, jlong jhandle, jint table_cache_numshardbits) { - reinterpret_cast(jhandle) - ->table_cache_numshardbits = static_cast(table_cache_numshardbits); -} - -/* - * Method: useFixedLengthPrefixExtractor - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_useFixedLengthPrefixExtractor( - JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { - reinterpret_cast(jhandle) - ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewFixedPrefixTransform( - static_cast(jprefix_length))); -} - -/* - * Method: useCappedPrefixExtractor - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_useCappedPrefixExtractor(JNIEnv*, jobject, - jlong jhandle, - jint jprefix_length) { - reinterpret_cast(jhandle) - ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewCappedPrefixTransform( - static_cast(jprefix_length))); -} - -/* - * Class: org_rocksdb_Options - * Method: walTtlSeconds - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_walTtlSeconds(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->WAL_ttl_seconds; -} - -/* - * Class: org_rocksdb_Options - * Method: setWalTtlSeconds - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWalTtlSeconds(JNIEnv*, jobject, jlong jhandle, - jlong WAL_ttl_seconds) { - reinterpret_cast(jhandle)->WAL_ttl_seconds = - static_cast(WAL_ttl_seconds); -} - -/* - * Class: org_rocksdb_Options - * Method: walTtlSeconds - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_walSizeLimitMB(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->WAL_size_limit_MB; -} - -/* - * Class: org_rocksdb_Options - * Method: setWalSizeLimitMB - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWalSizeLimitMB(JNIEnv*, jobject, jlong jhandle, - jlong WAL_size_limit_MB) { - reinterpret_cast(jhandle)->WAL_size_limit_MB = - static_cast(WAL_size_limit_MB); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxWriteBatchGroupSizeBytes - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxWriteBatchGroupSizeBytes( - JNIEnv*, jclass, jlong jhandle, jlong jmax_write_batch_group_size_bytes) { - auto* opt = reinterpret_cast(jhandle); - opt->max_write_batch_group_size_bytes = - static_cast(jmax_write_batch_group_size_bytes); -} - -/* - * Class: org_rocksdb_Options - * Method: maxWriteBatchGroupSizeBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxWriteBatchGroupSizeBytes(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_write_batch_group_size_bytes); -} - -/* - * Class: org_rocksdb_Options - * Method: manifestPreallocationSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_manifestPreallocationSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->manifest_preallocation_size; -} - -/* - * Class: org_rocksdb_Options - * Method: setManifestPreallocationSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setManifestPreallocationSize( - JNIEnv* env, jobject, jlong jhandle, jlong preallocation_size) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - preallocation_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->manifest_preallocation_size = preallocation_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Method: setTableFactory - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setTableFactory(JNIEnv*, jobject, jlong jhandle, - jlong jtable_factory_handle) { - auto* options = reinterpret_cast(jhandle); - auto* table_factory = - reinterpret_cast(jtable_factory_handle); - options->table_factory.reset(table_factory); -} - -/* - * Method: setSstPartitionerFactory - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setSstPartitionerFactory(JNIEnv*, jobject, - jlong jhandle, - jlong factory_handle) { - auto* options = reinterpret_cast(jhandle); - auto factory = reinterpret_cast< - std::shared_ptr*>( - factory_handle); - options->sst_partitioner_factory = *factory; -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionThreadLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setCompactionThreadLimiter( - JNIEnv*, jclass, jlong jhandle, jlong jlimiter_handle) { - auto* options = reinterpret_cast(jhandle); - auto* limiter = reinterpret_cast< - std::shared_ptr*>( - jlimiter_handle); - options->compaction_thread_limiter = *limiter; -} - -/* - * Class: org_rocksdb_Options - * Method: allowMmapReads - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_allowMmapReads(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->allow_mmap_reads; -} - -/* - * Class: org_rocksdb_Options - * Method: setAllowMmapReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAllowMmapReads(JNIEnv*, jobject, jlong jhandle, - jboolean allow_mmap_reads) { - reinterpret_cast(jhandle)->allow_mmap_reads = - static_cast(allow_mmap_reads); -} - -/* - * Class: org_rocksdb_Options - * Method: allowMmapWrites - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_allowMmapWrites(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->allow_mmap_writes; -} - -/* - * Class: org_rocksdb_Options - * Method: setAllowMmapWrites - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAllowMmapWrites(JNIEnv*, jobject, - jlong jhandle, - jboolean allow_mmap_writes) { - reinterpret_cast(jhandle)->allow_mmap_writes = - static_cast(allow_mmap_writes); -} - -/* - * Class: org_rocksdb_Options - * Method: useDirectReads - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_useDirectReads(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->use_direct_reads; -} - -/* - * Class: org_rocksdb_Options - * Method: setUseDirectReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setUseDirectReads(JNIEnv*, jobject, jlong jhandle, - jboolean use_direct_reads) { - reinterpret_cast(jhandle)->use_direct_reads = - static_cast(use_direct_reads); -} - -/* - * Class: org_rocksdb_Options - * Method: useDirectIoForFlushAndCompaction - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_useDirectIoForFlushAndCompaction( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->use_direct_io_for_flush_and_compaction; -} - -/* - * Class: org_rocksdb_Options - * Method: setUseDirectIoForFlushAndCompaction - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setUseDirectIoForFlushAndCompaction( - JNIEnv*, jobject, jlong jhandle, - jboolean use_direct_io_for_flush_and_compaction) { - reinterpret_cast(jhandle) - ->use_direct_io_for_flush_and_compaction = - static_cast(use_direct_io_for_flush_and_compaction); -} - -/* - * Class: org_rocksdb_Options - * Method: setAllowFAllocate - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAllowFAllocate(JNIEnv*, jobject, jlong jhandle, - jboolean jallow_fallocate) { - reinterpret_cast(jhandle)->allow_fallocate = - static_cast(jallow_fallocate); -} - -/* - * Class: org_rocksdb_Options - * Method: allowFAllocate - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_allowFAllocate(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->allow_fallocate); -} - -/* - * Class: org_rocksdb_Options - * Method: isFdCloseOnExec - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_isFdCloseOnExec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->is_fd_close_on_exec; -} - -/* - * Class: org_rocksdb_Options - * Method: setIsFdCloseOnExec - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setIsFdCloseOnExec(JNIEnv*, jobject, - jlong jhandle, - jboolean is_fd_close_on_exec) { - reinterpret_cast(jhandle)->is_fd_close_on_exec = - static_cast(is_fd_close_on_exec); -} - -/* - * Class: org_rocksdb_Options - * Method: statsDumpPeriodSec - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_statsDumpPeriodSec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->stats_dump_period_sec; -} - -/* - * Class: org_rocksdb_Options - * Method: setStatsDumpPeriodSec - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setStatsDumpPeriodSec( - JNIEnv*, jobject, jlong jhandle, jint jstats_dump_period_sec) { - reinterpret_cast(jhandle) - ->stats_dump_period_sec = - static_cast(jstats_dump_period_sec); -} - -/* - * Class: org_rocksdb_Options - * Method: statsPersistPeriodSec - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_statsPersistPeriodSec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->stats_persist_period_sec; -} - -/* - * Class: org_rocksdb_Options - * Method: setStatsPersistPeriodSec - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setStatsPersistPeriodSec( - JNIEnv*, jobject, jlong jhandle, jint jstats_persist_period_sec) { - reinterpret_cast(jhandle) - ->stats_persist_period_sec = - static_cast(jstats_persist_period_sec); -} - -/* - * Class: org_rocksdb_Options - * Method: statsHistoryBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_statsHistoryBufferSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->stats_history_buffer_size; -} - -/* - * Class: org_rocksdb_Options - * Method: setStatsHistoryBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setStatsHistoryBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jstats_history_buffer_size) { - reinterpret_cast(jhandle) - ->stats_history_buffer_size = - static_cast(jstats_history_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: adviseRandomOnOpen - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_adviseRandomOnOpen(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->advise_random_on_open; -} - -/* - * Class: org_rocksdb_Options - * Method: setAdviseRandomOnOpen - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAdviseRandomOnOpen( - JNIEnv*, jobject, jlong jhandle, jboolean advise_random_on_open) { - reinterpret_cast(jhandle) - ->advise_random_on_open = static_cast(advise_random_on_open); -} - -/* - * Class: org_rocksdb_Options - * Method: setDbWriteBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setDbWriteBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jdb_write_buffer_size) { - auto* opt = reinterpret_cast(jhandle); - opt->db_write_buffer_size = static_cast(jdb_write_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: dbWriteBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_dbWriteBufferSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->db_write_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setAccessHintOnCompactionStart - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setAccessHintOnCompactionStart( - JNIEnv*, jobject, jlong jhandle, jbyte jaccess_hint_value) { - auto* opt = reinterpret_cast(jhandle); - opt->access_hint_on_compaction_start = - ROCKSDB_NAMESPACE::AccessHintJni::toCppAccessHint(jaccess_hint_value); -} - -/* - * Class: org_rocksdb_Options - * Method: accessHintOnCompactionStart - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_accessHintOnCompactionStart(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::AccessHintJni::toJavaAccessHint( - opt->access_hint_on_compaction_start); -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setCompactionReadaheadSize( - JNIEnv*, jobject, jlong jhandle, jlong jcompaction_readahead_size) { - auto* opt = reinterpret_cast(jhandle); - opt->compaction_readahead_size = - static_cast(jcompaction_readahead_size); -} - -/* - * Class: org_rocksdb_Options - * Method: compactionReadaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_compactionReadaheadSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->compaction_readahead_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setRandomAccessMaxBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setRandomAccessMaxBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jrandom_access_max_buffer_size) { - auto* opt = reinterpret_cast(jhandle); - opt->random_access_max_buffer_size = - static_cast(jrandom_access_max_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: randomAccessMaxBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_randomAccessMaxBufferSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->random_access_max_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setWritableFileMaxBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWritableFileMaxBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jwritable_file_max_buffer_size) { - auto* opt = reinterpret_cast(jhandle); - opt->writable_file_max_buffer_size = - static_cast(jwritable_file_max_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: writableFileMaxBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_writableFileMaxBufferSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->writable_file_max_buffer_size); -} - -/* - * Class: org_rocksdb_Options - * Method: useAdaptiveMutex - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_useAdaptiveMutex(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->use_adaptive_mutex; -} - -/* - * Class: org_rocksdb_Options - * Method: setUseAdaptiveMutex - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setUseAdaptiveMutex(JNIEnv*, jobject, - jlong jhandle, - jboolean use_adaptive_mutex) { - reinterpret_cast(jhandle)->use_adaptive_mutex = - static_cast(use_adaptive_mutex); -} - -/* - * Class: org_rocksdb_Options - * Method: bytesPerSync - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_bytesPerSync(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->bytes_per_sync; -} - -/* - * Class: org_rocksdb_Options - * Method: setBytesPerSync - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setBytesPerSync(JNIEnv*, jobject, jlong jhandle, - jlong bytes_per_sync) { - reinterpret_cast(jhandle)->bytes_per_sync = - static_cast(bytes_per_sync); -} - -/* - * Class: org_rocksdb_Options - * Method: setWalBytesPerSync - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWalBytesPerSync(JNIEnv*, jobject, - jlong jhandle, - jlong jwal_bytes_per_sync) { - reinterpret_cast(jhandle)->wal_bytes_per_sync = - static_cast(jwal_bytes_per_sync); -} - -/* - * Class: org_rocksdb_Options - * Method: walBytesPerSync - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_walBytesPerSync(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->wal_bytes_per_sync); -} - -/* - * Class: org_rocksdb_Options - * Method: setStrictBytesPerSync - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setStrictBytesPerSync( - JNIEnv*, jobject, jlong jhandle, jboolean jstrict_bytes_per_sync) { - reinterpret_cast(jhandle) - ->strict_bytes_per_sync = jstrict_bytes_per_sync == JNI_TRUE; -} - -/* - * Class: org_rocksdb_Options - * Method: strictBytesPerSync - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_strictBytesPerSync(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->strict_bytes_per_sync); -} - -// Note: the RocksJava API currently only supports EventListeners implemented in -// Java. It could be extended in future to also support adding/removing -// EventListeners implemented in C++. -static void rocksdb_set_event_listeners_helper( - JNIEnv* env, jlongArray jlistener_array, - std::vector>& - listener_sptr_vec) { - jlong* ptr_jlistener_array = - env->GetLongArrayElements(jlistener_array, nullptr); - if (ptr_jlistener_array == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - const jsize array_size = env->GetArrayLength(jlistener_array); - listener_sptr_vec.clear(); - for (jsize i = 0; i < array_size; ++i) { - const auto& listener_sptr = - *reinterpret_cast*>( - ptr_jlistener_array[i]); - listener_sptr_vec.push_back(listener_sptr); - } -} - -/* - * Class: org_rocksdb_Options - * Method: setEventListeners - * Signature: (J[J)V - */ -void Java_org_rocksdb_Options_setEventListeners(JNIEnv* env, jclass, - jlong jhandle, - jlongArray jlistener_array) { - auto* opt = reinterpret_cast(jhandle); - rocksdb_set_event_listeners_helper(env, jlistener_array, opt->listeners); -} - -// Note: the RocksJava API currently only supports EventListeners implemented in -// Java. It could be extended in future to also support adding/removing -// EventListeners implemented in C++. -static jobjectArray rocksdb_get_event_listeners_helper( - JNIEnv* env, - const std::vector>& - listener_sptr_vec) { - jsize sz = static_cast(listener_sptr_vec.size()); - jclass jlistener_clazz = - ROCKSDB_NAMESPACE::AbstractEventListenerJni::getJClass(env); - jobjectArray jlisteners = env->NewObjectArray(sz, jlistener_clazz, nullptr); - if (jlisteners == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - for (jsize i = 0; i < sz; ++i) { - const auto* jni_cb = - static_cast( - listener_sptr_vec[i].get()); - env->SetObjectArrayElement(jlisteners, i, jni_cb->GetJavaObject()); - } - return jlisteners; -} - -/* - * Class: org_rocksdb_Options - * Method: eventListeners - * Signature: (J)[Lorg/rocksdb/AbstractEventListener; - */ -jobjectArray Java_org_rocksdb_Options_eventListeners(JNIEnv* env, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return rocksdb_get_event_listeners_helper(env, opt->listeners); -} - -/* - * Class: org_rocksdb_Options - * Method: setEnableThreadTracking - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setEnableThreadTracking( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_thread_tracking) { - auto* opt = reinterpret_cast(jhandle); - opt->enable_thread_tracking = static_cast(jenable_thread_tracking); -} - -/* - * Class: org_rocksdb_Options - * Method: enableThreadTracking - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_enableThreadTracking(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->enable_thread_tracking); -} - -/* - * Class: org_rocksdb_Options - * Method: setDelayedWriteRate - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setDelayedWriteRate(JNIEnv*, jobject, - jlong jhandle, - jlong jdelayed_write_rate) { - auto* opt = reinterpret_cast(jhandle); - opt->delayed_write_rate = static_cast(jdelayed_write_rate); -} - -/* - * Class: org_rocksdb_Options - * Method: delayedWriteRate - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_delayedWriteRate(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->delayed_write_rate); -} - -/* - * Class: org_rocksdb_Options - * Method: setEnablePipelinedWrite - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setEnablePipelinedWrite( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_pipelined_write) { - auto* opt = reinterpret_cast(jhandle); - opt->enable_pipelined_write = jenable_pipelined_write == JNI_TRUE; -} - -/* - * Class: org_rocksdb_Options - * Method: enablePipelinedWrite - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_enablePipelinedWrite(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->enable_pipelined_write); -} - -/* - * Class: org_rocksdb_Options - * Method: setUnorderedWrite - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setUnorderedWrite(JNIEnv*, jobject, jlong jhandle, - jboolean unordered_write) { - reinterpret_cast(jhandle)->unordered_write = - static_cast(unordered_write); -} - -/* - * Class: org_rocksdb_Options - * Method: unorderedWrite - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_unorderedWrite(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->unordered_write; -} - -/* - * Class: org_rocksdb_Options - * Method: setAllowConcurrentMemtableWrite - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAllowConcurrentMemtableWrite(JNIEnv*, jobject, - jlong jhandle, - jboolean allow) { - reinterpret_cast(jhandle) - ->allow_concurrent_memtable_write = static_cast(allow); -} - -/* - * Class: org_rocksdb_Options - * Method: allowConcurrentMemtableWrite - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_allowConcurrentMemtableWrite(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->allow_concurrent_memtable_write; -} - -/* - * Class: org_rocksdb_Options - * Method: setEnableWriteThreadAdaptiveYield - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setEnableWriteThreadAdaptiveYield( - JNIEnv*, jobject, jlong jhandle, jboolean yield) { - reinterpret_cast(jhandle) - ->enable_write_thread_adaptive_yield = static_cast(yield); -} - -/* - * Class: org_rocksdb_Options - * Method: enableWriteThreadAdaptiveYield - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_enableWriteThreadAdaptiveYield( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->enable_write_thread_adaptive_yield; -} - -/* - * Class: org_rocksdb_Options - * Method: setWriteThreadMaxYieldUsec - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWriteThreadMaxYieldUsec(JNIEnv*, jobject, - jlong jhandle, - jlong max) { - reinterpret_cast(jhandle) - ->write_thread_max_yield_usec = static_cast(max); -} - -/* - * Class: org_rocksdb_Options - * Method: writeThreadMaxYieldUsec - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_writeThreadMaxYieldUsec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->write_thread_max_yield_usec; -} - -/* - * Class: org_rocksdb_Options - * Method: setWriteThreadSlowYieldUsec - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWriteThreadSlowYieldUsec(JNIEnv*, jobject, - jlong jhandle, - jlong slow) { - reinterpret_cast(jhandle) - ->write_thread_slow_yield_usec = static_cast(slow); -} - -/* - * Class: org_rocksdb_Options - * Method: writeThreadSlowYieldUsec - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_writeThreadSlowYieldUsec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->write_thread_slow_yield_usec; -} - -/* - * Class: org_rocksdb_Options - * Method: setSkipStatsUpdateOnDbOpen - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setSkipStatsUpdateOnDbOpen( - JNIEnv*, jobject, jlong jhandle, jboolean jskip_stats_update_on_db_open) { - auto* opt = reinterpret_cast(jhandle); - opt->skip_stats_update_on_db_open = - static_cast(jskip_stats_update_on_db_open); -} - -/* - * Class: org_rocksdb_Options - * Method: skipStatsUpdateOnDbOpen - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_skipStatsUpdateOnDbOpen(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->skip_stats_update_on_db_open); -} - -/* - * Class: org_rocksdb_Options - * Method: setSkipCheckingSstFileSizesOnDbOpen - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setSkipCheckingSstFileSizesOnDbOpen( - JNIEnv*, jclass, jlong jhandle, - jboolean jskip_checking_sst_file_sizes_on_db_open) { - auto* opt = reinterpret_cast(jhandle); - opt->skip_checking_sst_file_sizes_on_db_open = - static_cast(jskip_checking_sst_file_sizes_on_db_open); -} - -/* - * Class: org_rocksdb_Options - * Method: skipCheckingSstFileSizesOnDbOpen - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_skipCheckingSstFileSizesOnDbOpen( - JNIEnv*, jclass, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->skip_checking_sst_file_sizes_on_db_open); -} - -/* - * Class: org_rocksdb_Options - * Method: setWalRecoveryMode - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setWalRecoveryMode( - JNIEnv*, jobject, jlong jhandle, jbyte jwal_recovery_mode_value) { - auto* opt = reinterpret_cast(jhandle); - opt->wal_recovery_mode = - ROCKSDB_NAMESPACE::WALRecoveryModeJni::toCppWALRecoveryMode( - jwal_recovery_mode_value); -} - -/* - * Class: org_rocksdb_Options - * Method: walRecoveryMode - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_walRecoveryMode(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::WALRecoveryModeJni::toJavaWALRecoveryMode( - opt->wal_recovery_mode); -} - -/* - * Class: org_rocksdb_Options - * Method: setAllow2pc - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAllow2pc(JNIEnv*, jobject, jlong jhandle, - jboolean jallow_2pc) { - auto* opt = reinterpret_cast(jhandle); - opt->allow_2pc = static_cast(jallow_2pc); -} - -/* - * Class: org_rocksdb_Options - * Method: allow2pc - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_allow2pc(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->allow_2pc); -} - -/* - * Class: org_rocksdb_Options - * Method: setRowCache - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setRowCache(JNIEnv*, jobject, jlong jhandle, - jlong jrow_cache_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* row_cache = - reinterpret_cast*>( - jrow_cache_handle); - opt->row_cache = *row_cache; -} - -/* - * Class: org_rocksdb_Options - * Method: setWalFilter - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setWalFilter(JNIEnv*, jobject, jlong jhandle, - jlong jwal_filter_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* wal_filter = reinterpret_cast( - jwal_filter_handle); - opt->wal_filter = wal_filter; -} - -/* - * Class: org_rocksdb_Options - * Method: setFailIfOptionsFileError - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setFailIfOptionsFileError( - JNIEnv*, jobject, jlong jhandle, jboolean jfail_if_options_file_error) { - auto* opt = reinterpret_cast(jhandle); - opt->fail_if_options_file_error = - static_cast(jfail_if_options_file_error); -} - -/* - * Class: org_rocksdb_Options - * Method: failIfOptionsFileError - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_failIfOptionsFileError(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->fail_if_options_file_error); -} - -/* - * Class: org_rocksdb_Options - * Method: setDumpMallocStats - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setDumpMallocStats(JNIEnv*, jobject, - jlong jhandle, - jboolean jdump_malloc_stats) { - auto* opt = reinterpret_cast(jhandle); - opt->dump_malloc_stats = static_cast(jdump_malloc_stats); -} - -/* - * Class: org_rocksdb_Options - * Method: dumpMallocStats - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_dumpMallocStats(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->dump_malloc_stats); -} - -/* - * Class: org_rocksdb_Options - * Method: setAvoidFlushDuringRecovery - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAvoidFlushDuringRecovery( - JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_recovery) { - auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_recovery = - static_cast(javoid_flush_during_recovery); -} - -/* - * Class: org_rocksdb_Options - * Method: avoidFlushDuringRecovery - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_avoidFlushDuringRecovery(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->avoid_flush_during_recovery); -} - -/* - * Class: org_rocksdb_Options - * Method: setAvoidUnnecessaryBlockingIO - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAvoidUnnecessaryBlockingIO( - JNIEnv*, jclass, jlong jhandle, jboolean avoid_blocking_io) { - auto* opt = reinterpret_cast(jhandle); - opt->avoid_unnecessary_blocking_io = static_cast(avoid_blocking_io); -} - -/* - * Class: org_rocksdb_Options - * Method: avoidUnnecessaryBlockingIO - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_avoidUnnecessaryBlockingIO(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->avoid_unnecessary_blocking_io); -} - -/* - * Class: org_rocksdb_Options - * Method: setPersistStatsToDisk - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setPersistStatsToDisk( - JNIEnv*, jclass, jlong jhandle, jboolean persist_stats_to_disk) { - auto* opt = reinterpret_cast(jhandle); - opt->persist_stats_to_disk = static_cast(persist_stats_to_disk); -} - -/* - * Class: org_rocksdb_Options - * Method: persistStatsToDisk - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_persistStatsToDisk(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->persist_stats_to_disk); -} - -/* - * Class: org_rocksdb_Options - * Method: setWriteDbidToManifest - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setWriteDbidToManifest( - JNIEnv*, jclass, jlong jhandle, jboolean jwrite_dbid_to_manifest) { - auto* opt = reinterpret_cast(jhandle); - opt->write_dbid_to_manifest = static_cast(jwrite_dbid_to_manifest); -} - -/* - * Class: org_rocksdb_Options - * Method: writeDbidToManifest - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_writeDbidToManifest(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->write_dbid_to_manifest); -} - -/* - * Class: org_rocksdb_Options - * Method: setLogReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setLogReadaheadSize(JNIEnv*, jclass, - jlong jhandle, - jlong jlog_readahead_size) { - auto* opt = reinterpret_cast(jhandle); - opt->log_readahead_size = static_cast(jlog_readahead_size); -} - -/* - * Class: org_rocksdb_Options - * Method: logReasaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_logReadaheadSize(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->log_readahead_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setBestEffortsRecovery - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setBestEffortsRecovery( - JNIEnv*, jclass, jlong jhandle, jboolean jbest_efforts_recovery) { - auto* opt = reinterpret_cast(jhandle); - opt->best_efforts_recovery = static_cast(jbest_efforts_recovery); -} - -/* - * Class: org_rocksdb_Options - * Method: bestEffortsRecovery - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_bestEffortsRecovery(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->best_efforts_recovery); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBgErrorResumeCount - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxBgErrorResumeCount( - JNIEnv*, jclass, jlong jhandle, jint jmax_bgerror_resume_count) { - auto* opt = reinterpret_cast(jhandle); - opt->max_bgerror_resume_count = static_cast(jmax_bgerror_resume_count); -} - -/* - * Class: org_rocksdb_Options - * Method: maxBgerrorResumeCount - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxBgerrorResumeCount(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_bgerror_resume_count); -} - -/* - * Class: org_rocksdb_Options - * Method: setBgerrorResumeRetryInterval - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setBgerrorResumeRetryInterval( - JNIEnv*, jclass, jlong jhandle, jlong jbgerror_resume_retry_interval) { - auto* opt = reinterpret_cast(jhandle); - opt->bgerror_resume_retry_interval = - static_cast(jbgerror_resume_retry_interval); -} - -/* - * Class: org_rocksdb_Options - * Method: bgerrorResumeRetryInterval - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_bgerrorResumeRetryInterval(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->bgerror_resume_retry_interval); -} - -/* - * Class: org_rocksdb_Options - * Method: setAvoidFlushDuringShutdown - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAvoidFlushDuringShutdown( - JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_shutdown) { - auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_shutdown = - static_cast(javoid_flush_during_shutdown); -} - -/* - * Class: org_rocksdb_Options - * Method: avoidFlushDuringShutdown - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_avoidFlushDuringShutdown(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->avoid_flush_during_shutdown); -} - -/* - * Class: org_rocksdb_Options - * Method: setAllowIngestBehind - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAllowIngestBehind( - JNIEnv*, jobject, jlong jhandle, jboolean jallow_ingest_behind) { - auto* opt = reinterpret_cast(jhandle); - opt->allow_ingest_behind = jallow_ingest_behind == JNI_TRUE; -} - -/* - * Class: org_rocksdb_Options - * Method: allowIngestBehind - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_allowIngestBehind(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->allow_ingest_behind); -} - -/* - * Class: org_rocksdb_Options - * Method: setTwoWriteQueues - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setTwoWriteQueues(JNIEnv*, jobject, jlong jhandle, - jboolean jtwo_write_queues) { - auto* opt = reinterpret_cast(jhandle); - opt->two_write_queues = jtwo_write_queues == JNI_TRUE; -} - -/* - * Class: org_rocksdb_Options - * Method: twoWriteQueues - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_twoWriteQueues(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->two_write_queues); -} - -/* - * Class: org_rocksdb_Options - * Method: setManualWalFlush - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setManualWalFlush(JNIEnv*, jobject, jlong jhandle, - jboolean jmanual_wal_flush) { - auto* opt = reinterpret_cast(jhandle); - opt->manual_wal_flush = jmanual_wal_flush == JNI_TRUE; -} - -/* - * Class: org_rocksdb_Options - * Method: manualWalFlush - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_manualWalFlush(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->manual_wal_flush); -} - -/* - * Class: org_rocksdb_Options - * Method: setAtomicFlush - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setAtomicFlush(JNIEnv*, jobject, jlong jhandle, - jboolean jatomic_flush) { - auto* opt = reinterpret_cast(jhandle); - opt->atomic_flush = jatomic_flush == JNI_TRUE; -} - -/* - * Class: org_rocksdb_Options - * Method: atomicFlush - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_atomicFlush(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->atomic_flush); -} - -/* - * Method: tableFactoryName - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_Options_tableFactoryName(JNIEnv* env, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::TableFactory* tf = opt->table_factory.get(); - - // Should never be nullptr. - // Default memtable factory is SkipListFactory - assert(tf); - - return env->NewStringUTF(tf->Name()); -} - -/* - * Class: org_rocksdb_Options - * Method: minWriteBufferNumberToMerge - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_minWriteBufferNumberToMerge(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->min_write_buffer_number_to_merge; -} - -/* - * Class: org_rocksdb_Options - * Method: setMinWriteBufferNumberToMerge - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMinWriteBufferNumberToMerge( - JNIEnv*, jobject, jlong jhandle, jint jmin_write_buffer_number_to_merge) { - reinterpret_cast(jhandle) - ->min_write_buffer_number_to_merge = - static_cast(jmin_write_buffer_number_to_merge); -} -/* - * Class: org_rocksdb_Options - * Method: maxWriteBufferNumberToMaintain - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_maxWriteBufferNumberToMaintain(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_write_buffer_number_to_maintain; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxWriteBufferNumberToMaintain - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxWriteBufferNumberToMaintain( - JNIEnv*, jobject, jlong jhandle, - jint jmax_write_buffer_number_to_maintain) { - reinterpret_cast(jhandle) - ->max_write_buffer_number_to_maintain = - static_cast(jmax_write_buffer_number_to_maintain); -} - -/* - * Class: org_rocksdb_Options - * Method: setCompressionType - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setCompressionType( - JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { - auto* opts = reinterpret_cast(jhandle); - opts->compression = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jcompression_type_value); -} - -/* - * Class: org_rocksdb_Options - * Method: compressionType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_compressionType(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - opts->compression); -} - -/** - * Helper method to convert a Java byte array of compression levels - * to a C++ vector of ROCKSDB_NAMESPACE::CompressionType - * - * @param env A pointer to the Java environment - * @param jcompression_levels A reference to a java byte array - * where each byte indicates a compression level - * - * @return A std::unique_ptr to the vector, or std::unique_ptr(nullptr) if a JNI - * exception occurs - */ -std::unique_ptr> -rocksdb_compression_vector_helper(JNIEnv* env, jbyteArray jcompression_levels) { - jsize len = env->GetArrayLength(jcompression_levels); - jbyte* jcompression_level = - env->GetByteArrayElements(jcompression_levels, nullptr); - if (jcompression_level == nullptr) { - // exception thrown: OutOfMemoryError - return std::unique_ptr>(); - } - - auto* compression_levels = - new std::vector(); - std::unique_ptr> - uptr_compression_levels(compression_levels); - - for (jsize i = 0; i < len; i++) { - jbyte jcl = jcompression_level[i]; - compression_levels->push_back( - static_cast(jcl)); - } - - env->ReleaseByteArrayElements(jcompression_levels, jcompression_level, - JNI_ABORT); - - return uptr_compression_levels; -} - -/** - * Helper method to convert a C++ vector of ROCKSDB_NAMESPACE::CompressionType - * to a Java byte array of compression levels - * - * @param env A pointer to the Java environment - * @param jcompression_levels A reference to a java byte array - * where each byte indicates a compression level - * - * @return A jbytearray or nullptr if an exception occurs - */ -jbyteArray rocksdb_compression_list_helper( - JNIEnv* env, - std::vector compression_levels) { - const size_t len = compression_levels.size(); - jbyte* jbuf = new jbyte[len]; - - for (size_t i = 0; i < len; i++) { - jbuf[i] = compression_levels[i]; - } - - // insert in java array - jbyteArray jcompression_levels = env->NewByteArray(static_cast(len)); - if (jcompression_levels == nullptr) { - // exception thrown: OutOfMemoryError - delete[] jbuf; - return nullptr; - } - env->SetByteArrayRegion(jcompression_levels, 0, static_cast(len), - jbuf); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jcompression_levels); - delete[] jbuf; - return nullptr; - } - - delete[] jbuf; - - return jcompression_levels; -} - -/* - * Class: org_rocksdb_Options - * Method: setCompressionPerLevel - * Signature: (J[B)V - */ -void Java_org_rocksdb_Options_setCompressionPerLevel( - JNIEnv* env, jobject, jlong jhandle, jbyteArray jcompressionLevels) { - auto uptr_compression_levels = - rocksdb_compression_vector_helper(env, jcompressionLevels); - if (!uptr_compression_levels) { - // exception occurred - return; - } - auto* options = reinterpret_cast(jhandle); - options->compression_per_level = *(uptr_compression_levels.get()); -} - -/* - * Class: org_rocksdb_Options - * Method: compressionPerLevel - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_Options_compressionPerLevel(JNIEnv* env, jobject, - jlong jhandle) { - auto* options = reinterpret_cast(jhandle); - return rocksdb_compression_list_helper(env, options->compression_per_level); -} - -/* - * Class: org_rocksdb_Options - * Method: setBottommostCompressionType - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setBottommostCompressionType( - JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { - auto* options = reinterpret_cast(jhandle); - options->bottommost_compression = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jcompression_type_value); -} - -/* - * Class: org_rocksdb_Options - * Method: bottommostCompressionType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_bottommostCompressionType(JNIEnv*, jobject, - jlong jhandle) { - auto* options = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - options->bottommost_compression); -} - -/* - * Class: org_rocksdb_Options - * Method: setBottommostCompressionOptions - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setBottommostCompressionOptions( - JNIEnv*, jobject, jlong jhandle, - jlong jbottommost_compression_options_handle) { - auto* options = reinterpret_cast(jhandle); - auto* bottommost_compression_options = - reinterpret_cast( - jbottommost_compression_options_handle); - options->bottommost_compression_opts = *bottommost_compression_options; -} - -/* - * Class: org_rocksdb_Options - * Method: setCompressionOptions - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setCompressionOptions( - JNIEnv*, jobject, jlong jhandle, jlong jcompression_options_handle) { - auto* options = reinterpret_cast(jhandle); - auto* compression_options = - reinterpret_cast( - jcompression_options_handle); - options->compression_opts = *compression_options; -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionStyle - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setCompactionStyle(JNIEnv*, jobject, - jlong jhandle, - jbyte jcompaction_style) { - auto* options = reinterpret_cast(jhandle); - options->compaction_style = - ROCKSDB_NAMESPACE::CompactionStyleJni::toCppCompactionStyle( - jcompaction_style); -} - -/* - * Class: org_rocksdb_Options - * Method: compactionStyle - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_compactionStyle(JNIEnv*, jobject, - jlong jhandle) { - auto* options = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompactionStyleJni::toJavaCompactionStyle( - options->compaction_style); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxTableFilesSizeFIFO - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxTableFilesSizeFIFO( - JNIEnv*, jobject, jlong jhandle, jlong jmax_table_files_size) { - reinterpret_cast(jhandle) - ->compaction_options_fifo.max_table_files_size = - static_cast(jmax_table_files_size); -} - -/* - * Class: org_rocksdb_Options - * Method: maxTableFilesSizeFIFO - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxTableFilesSizeFIFO(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->compaction_options_fifo.max_table_files_size; -} - -/* - * Class: org_rocksdb_Options - * Method: numLevels - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_numLevels(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->num_levels; -} - -/* - * Class: org_rocksdb_Options - * Method: setNumLevels - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setNumLevels(JNIEnv*, jobject, jlong jhandle, - jint jnum_levels) { - reinterpret_cast(jhandle)->num_levels = - static_cast(jnum_levels); -} - -/* - * Class: org_rocksdb_Options - * Method: levelZeroFileNumCompactionTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_levelZeroFileNumCompactionTrigger(JNIEnv*, - jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevelZeroFileNumCompactionTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setLevelZeroFileNumCompactionTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); -} - -/* - * Class: org_rocksdb_Options - * Method: levelZeroSlowdownWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_levelZeroSlowdownWritesTrigger(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevelSlowdownWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setLevelZeroSlowdownWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); -} - -/* - * Class: org_rocksdb_Options - * Method: levelZeroStopWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_levelZeroStopWritesTrigger(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_stop_writes_trigger; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevelStopWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setLevelZeroStopWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_stop_writes_trigger = - static_cast(jlevel0_stop_writes_trigger); -} - -/* - * Class: org_rocksdb_Options - * Method: targetFileSizeBase - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_targetFileSizeBase(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->target_file_size_base; -} - -/* - * Class: org_rocksdb_Options - * Method: setTargetFileSizeBase - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setTargetFileSizeBase( - JNIEnv*, jobject, jlong jhandle, jlong jtarget_file_size_base) { - reinterpret_cast(jhandle) - ->target_file_size_base = static_cast(jtarget_file_size_base); -} - -/* - * Class: org_rocksdb_Options - * Method: targetFileSizeMultiplier - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_targetFileSizeMultiplier(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->target_file_size_multiplier; -} - -/* - * Class: org_rocksdb_Options - * Method: setTargetFileSizeMultiplier - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setTargetFileSizeMultiplier( - JNIEnv*, jobject, jlong jhandle, jint jtarget_file_size_multiplier) { - reinterpret_cast(jhandle) - ->target_file_size_multiplier = - static_cast(jtarget_file_size_multiplier); -} - -/* - * Class: org_rocksdb_Options - * Method: maxBytesForLevelBase - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxBytesForLevelBase(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_bytes_for_level_base; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBytesForLevelBase - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxBytesForLevelBase( - JNIEnv*, jobject, jlong jhandle, jlong jmax_bytes_for_level_base) { - reinterpret_cast(jhandle) - ->max_bytes_for_level_base = - static_cast(jmax_bytes_for_level_base); -} - -/* - * Class: org_rocksdb_Options - * Method: levelCompactionDynamicLevelBytes - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_levelCompactionDynamicLevelBytes( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level_compaction_dynamic_level_bytes; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevelCompactionDynamicLevelBytes - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setLevelCompactionDynamicLevelBytes( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_dynamic_level_bytes) { - reinterpret_cast(jhandle) - ->level_compaction_dynamic_level_bytes = (jenable_dynamic_level_bytes); -} - -/* - * Class: org_rocksdb_Options - * Method: maxBytesForLevelMultiplier - * Signature: (J)D - */ -jdouble Java_org_rocksdb_Options_maxBytesForLevelMultiplier(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_bytes_for_level_multiplier; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBytesForLevelMultiplier - * Signature: (JD)V - */ -void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplier( - JNIEnv*, jobject, jlong jhandle, jdouble jmax_bytes_for_level_multiplier) { - reinterpret_cast(jhandle) - ->max_bytes_for_level_multiplier = - static_cast(jmax_bytes_for_level_multiplier); -} - -/* - * Class: org_rocksdb_Options - * Method: maxCompactionBytes - * Signature: (J)I - */ -jlong Java_org_rocksdb_Options_maxCompactionBytes(JNIEnv*, jobject, - jlong jhandle) { - return static_cast( - reinterpret_cast(jhandle) - ->max_compaction_bytes); -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxCompactionBytes - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMaxCompactionBytes( - JNIEnv*, jobject, jlong jhandle, jlong jmax_compaction_bytes) { - reinterpret_cast(jhandle)->max_compaction_bytes = - static_cast(jmax_compaction_bytes); -} - -/* - * Class: org_rocksdb_Options - * Method: arenaBlockSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_arenaBlockSize(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->arena_block_size; -} - -/* - * Class: org_rocksdb_Options - * Method: setArenaBlockSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setArenaBlockSize(JNIEnv* env, jobject, - jlong jhandle, - jlong jarena_block_size) { - auto s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(jarena_block_size); - if (s.ok()) { - reinterpret_cast(jhandle)->arena_block_size = - jarena_block_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: disableAutoCompactions - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_disableAutoCompactions(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->disable_auto_compactions; -} - -/* - * Class: org_rocksdb_Options - * Method: setDisableAutoCompactions - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setDisableAutoCompactions( - JNIEnv*, jobject, jlong jhandle, jboolean jdisable_auto_compactions) { - reinterpret_cast(jhandle) - ->disable_auto_compactions = static_cast(jdisable_auto_compactions); -} - -/* - * Class: org_rocksdb_Options - * Method: maxSequentialSkipInIterations - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxSequentialSkipInIterations(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_sequential_skip_in_iterations; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxSequentialSkipInIterations - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxSequentialSkipInIterations( - JNIEnv*, jobject, jlong jhandle, jlong jmax_sequential_skip_in_iterations) { - reinterpret_cast(jhandle) - ->max_sequential_skip_in_iterations = - static_cast(jmax_sequential_skip_in_iterations); -} - -/* - * Class: org_rocksdb_Options - * Method: inplaceUpdateSupport - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_inplaceUpdateSupport(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->inplace_update_support; -} - -/* - * Class: org_rocksdb_Options - * Method: setInplaceUpdateSupport - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setInplaceUpdateSupport( - JNIEnv*, jobject, jlong jhandle, jboolean jinplace_update_support) { - reinterpret_cast(jhandle) - ->inplace_update_support = static_cast(jinplace_update_support); -} - -/* - * Class: org_rocksdb_Options - * Method: inplaceUpdateNumLocks - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_inplaceUpdateNumLocks(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->inplace_update_num_locks; -} - -/* - * Class: org_rocksdb_Options - * Method: setInplaceUpdateNumLocks - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setInplaceUpdateNumLocks( - JNIEnv* env, jobject, jlong jhandle, jlong jinplace_update_num_locks) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jinplace_update_num_locks); - if (s.ok()) { - reinterpret_cast(jhandle) - ->inplace_update_num_locks = jinplace_update_num_locks; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: memtablePrefixBloomSizeRatio - * Signature: (J)I - */ -jdouble Java_org_rocksdb_Options_memtablePrefixBloomSizeRatio(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_prefix_bloom_size_ratio; -} - -/* - * Class: org_rocksdb_Options - * Method: setMemtablePrefixBloomSizeRatio - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setMemtablePrefixBloomSizeRatio( - JNIEnv*, jobject, jlong jhandle, - jdouble jmemtable_prefix_bloom_size_ratio) { - reinterpret_cast(jhandle) - ->memtable_prefix_bloom_size_ratio = - static_cast(jmemtable_prefix_bloom_size_ratio); -} - -/* - * Class: org_rocksdb_Options - * Method: experimentalMempurgeThreshold - * Signature: (J)I - */ -jdouble Java_org_rocksdb_Options_experimentalMempurgeThreshold(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->experimental_mempurge_threshold; -} - -/* - * Class: org_rocksdb_Options - * Method: setExperimentalMempurgeThreshold - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setExperimentalMempurgeThreshold( - JNIEnv*, jobject, jlong jhandle, jdouble jexperimental_mempurge_threshold) { - reinterpret_cast(jhandle) - ->experimental_mempurge_threshold = - static_cast(jexperimental_mempurge_threshold); -} - -/* - * Class: org_rocksdb_Options - * Method: memtableWholeKeyFiltering - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_memtableWholeKeyFiltering(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_whole_key_filtering; -} - -/* - * Class: org_rocksdb_Options - * Method: setMemtableWholeKeyFiltering - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setMemtableWholeKeyFiltering( - JNIEnv*, jobject, jlong jhandle, jboolean jmemtable_whole_key_filtering) { - reinterpret_cast(jhandle) - ->memtable_whole_key_filtering = - static_cast(jmemtable_whole_key_filtering); -} - -/* - * Class: org_rocksdb_Options - * Method: bloomLocality - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_bloomLocality(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->bloom_locality; -} - -/* - * Class: org_rocksdb_Options - * Method: setBloomLocality - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setBloomLocality(JNIEnv*, jobject, jlong jhandle, - jint jbloom_locality) { - reinterpret_cast(jhandle)->bloom_locality = - static_cast(jbloom_locality); -} - -/* - * Class: org_rocksdb_Options - * Method: maxSuccessiveMerges - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_maxSuccessiveMerges(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_successive_merges; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxSuccessiveMerges - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMaxSuccessiveMerges( - JNIEnv* env, jobject, jlong jhandle, jlong jmax_successive_merges) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jmax_successive_merges); - if (s.ok()) { - reinterpret_cast(jhandle) - ->max_successive_merges = jmax_successive_merges; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: optimizeFiltersForHits - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_optimizeFiltersForHits(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->optimize_filters_for_hits; -} - -/* - * Class: org_rocksdb_Options - * Method: setOptimizeFiltersForHits - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setOptimizeFiltersForHits( - JNIEnv*, jobject, jlong jhandle, jboolean joptimize_filters_for_hits) { - reinterpret_cast(jhandle) - ->optimize_filters_for_hits = - static_cast(joptimize_filters_for_hits); -} - -/* - * Class: org_rocksdb_Options - * Method: oldDefaults - * Signature: (JII)V - */ -void Java_org_rocksdb_Options_oldDefaults(JNIEnv*, jclass, jlong jhandle, - jint major_version, - jint minor_version) { - reinterpret_cast(jhandle)->OldDefaults( - major_version, minor_version); -} - -/* - * Class: org_rocksdb_Options - * Method: optimizeForSmallDb - * Signature: (J)V - */ -void Java_org_rocksdb_Options_optimizeForSmallDb__J(JNIEnv*, jobject, - jlong jhandle) { - reinterpret_cast(jhandle)->OptimizeForSmallDb(); -} - -/* - * Class: org_rocksdb_Options - * Method: optimizeForSmallDb - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_optimizeForSmallDb__JJ(JNIEnv*, jclass, - jlong jhandle, - jlong cache_handle) { - auto* cache_sptr_ptr = - reinterpret_cast*>( - cache_handle); - auto* options_ptr = reinterpret_cast(jhandle); - auto* cf_options_ptr = - static_cast(options_ptr); - cf_options_ptr->OptimizeForSmallDb(cache_sptr_ptr); -} - -/* - * Class: org_rocksdb_Options - * Method: optimizeForPointLookup - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_optimizeForPointLookup( - JNIEnv*, jobject, jlong jhandle, jlong block_cache_size_mb) { - reinterpret_cast(jhandle) - ->OptimizeForPointLookup(block_cache_size_mb); -} - -/* - * Class: org_rocksdb_Options - * Method: optimizeLevelStyleCompaction - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_optimizeLevelStyleCompaction( - JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { - reinterpret_cast(jhandle) - ->OptimizeLevelStyleCompaction(memtable_memory_budget); -} - -/* - * Class: org_rocksdb_Options - * Method: optimizeUniversalStyleCompaction - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_optimizeUniversalStyleCompaction( - JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { - reinterpret_cast(jhandle) - ->OptimizeUniversalStyleCompaction(memtable_memory_budget); -} - -/* - * Class: org_rocksdb_Options - * Method: prepareForBulkLoad - * Signature: (J)V - */ -void Java_org_rocksdb_Options_prepareForBulkLoad(JNIEnv*, jobject, - jlong jhandle) { - reinterpret_cast(jhandle)->PrepareForBulkLoad(); -} - -/* - * Class: org_rocksdb_Options - * Method: memtableHugePageSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_memtableHugePageSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_huge_page_size; -} - -/* - * Class: org_rocksdb_Options - * Method: setMemtableHugePageSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMemtableHugePageSize( - JNIEnv* env, jobject, jlong jhandle, jlong jmemtable_huge_page_size) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jmemtable_huge_page_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->memtable_huge_page_size = jmemtable_huge_page_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Options - * Method: softPendingCompactionBytesLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_softPendingCompactionBytesLimit(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->soft_pending_compaction_bytes_limit; -} - -/* - * Class: org_rocksdb_Options - * Method: setSoftPendingCompactionBytesLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setSoftPendingCompactionBytesLimit( - JNIEnv*, jobject, jlong jhandle, - jlong jsoft_pending_compaction_bytes_limit) { - reinterpret_cast(jhandle) - ->soft_pending_compaction_bytes_limit = - static_cast(jsoft_pending_compaction_bytes_limit); -} - -/* - * Class: org_rocksdb_Options - * Method: softHardCompactionBytesLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_hardPendingCompactionBytesLimit(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->hard_pending_compaction_bytes_limit; -} - -/* - * Class: org_rocksdb_Options - * Method: setHardPendingCompactionBytesLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setHardPendingCompactionBytesLimit( - JNIEnv*, jobject, jlong jhandle, - jlong jhard_pending_compaction_bytes_limit) { - reinterpret_cast(jhandle) - ->hard_pending_compaction_bytes_limit = - static_cast(jhard_pending_compaction_bytes_limit); -} - -/* - * Class: org_rocksdb_Options - * Method: level0FileNumCompactionTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_level0FileNumCompactionTrigger(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevel0FileNumCompactionTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setLevel0FileNumCompactionTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); -} - -/* - * Class: org_rocksdb_Options - * Method: level0SlowdownWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_level0SlowdownWritesTrigger(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevel0SlowdownWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setLevel0SlowdownWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); -} - -/* - * Class: org_rocksdb_Options - * Method: level0StopWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_level0StopWritesTrigger(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_stop_writes_trigger; -} - -/* - * Class: org_rocksdb_Options - * Method: setLevel0StopWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setLevel0StopWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_stop_writes_trigger = - static_cast(jlevel0_stop_writes_trigger); -} - -/* - * Class: org_rocksdb_Options - * Method: maxBytesForLevelMultiplierAdditional - * Signature: (J)[I - */ -jintArray Java_org_rocksdb_Options_maxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject, jlong jhandle) { - auto mbflma = reinterpret_cast(jhandle) - ->max_bytes_for_level_multiplier_additional; - - const size_t size = mbflma.size(); - - jint* additionals = new jint[size]; - for (size_t i = 0; i < size; i++) { - additionals[i] = static_cast(mbflma[i]); - } - - jsize jlen = static_cast(size); - jintArray result = env->NewIntArray(jlen); - if (result == nullptr) { - // exception thrown: OutOfMemoryError - delete[] additionals; - return nullptr; - } - - env->SetIntArrayRegion(result, 0, jlen, additionals); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(result); - delete[] additionals; - return nullptr; - } - - delete[] additionals; - - return result; -} - -/* - * Class: org_rocksdb_Options - * Method: setMaxBytesForLevelMultiplierAdditional - * Signature: (J[I)V - */ -void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject, jlong jhandle, - jintArray jmax_bytes_for_level_multiplier_additional) { - jsize len = env->GetArrayLength(jmax_bytes_for_level_multiplier_additional); - jint* additionals = env->GetIntArrayElements( - jmax_bytes_for_level_multiplier_additional, nullptr); - if (additionals == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* opt = reinterpret_cast(jhandle); - opt->max_bytes_for_level_multiplier_additional.clear(); - for (jsize i = 0; i < len; i++) { - opt->max_bytes_for_level_multiplier_additional.push_back( - static_cast(additionals[i])); - } - - env->ReleaseIntArrayElements(jmax_bytes_for_level_multiplier_additional, - additionals, JNI_ABORT); -} - -/* - * Class: org_rocksdb_Options - * Method: paranoidFileChecks - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_paranoidFileChecks(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->paranoid_file_checks; -} - -/* - * Class: org_rocksdb_Options - * Method: setParanoidFileChecks - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setParanoidFileChecks( - JNIEnv*, jobject, jlong jhandle, jboolean jparanoid_file_checks) { - reinterpret_cast(jhandle)->paranoid_file_checks = - static_cast(jparanoid_file_checks); -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionPriority - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setCompactionPriority( - JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_priority_value) { - auto* opts = reinterpret_cast(jhandle); - opts->compaction_pri = - ROCKSDB_NAMESPACE::CompactionPriorityJni::toCppCompactionPriority( - jcompaction_priority_value); -} - -/* - * Class: org_rocksdb_Options - * Method: compactionPriority - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_compactionPriority(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompactionPriorityJni::toJavaCompactionPriority( - opts->compaction_pri); -} - -/* - * Class: org_rocksdb_Options - * Method: setReportBgIoStats - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setReportBgIoStats(JNIEnv*, jobject, - jlong jhandle, - jboolean jreport_bg_io_stats) { - auto* opts = reinterpret_cast(jhandle); - opts->report_bg_io_stats = static_cast(jreport_bg_io_stats); -} - -/* - * Class: org_rocksdb_Options - * Method: reportBgIoStats - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_reportBgIoStats(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->report_bg_io_stats); -} - -/* - * Class: org_rocksdb_Options - * Method: setTtl - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setTtl(JNIEnv*, jobject, jlong jhandle, - jlong jttl) { - auto* opts = reinterpret_cast(jhandle); - opts->ttl = static_cast(jttl); -} - -/* - * Class: org_rocksdb_Options - * Method: ttl - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_ttl(JNIEnv*, jobject, jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->ttl); -} - -/* - * Class: org_rocksdb_Options - * Method: setPeriodicCompactionSeconds - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setPeriodicCompactionSeconds( - JNIEnv*, jobject, jlong jhandle, jlong jperiodicCompactionSeconds) { - auto* opts = reinterpret_cast(jhandle); - opts->periodic_compaction_seconds = - static_cast(jperiodicCompactionSeconds); -} - -/* - * Class: org_rocksdb_Options - * Method: periodicCompactionSeconds - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_periodicCompactionSeconds(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->periodic_compaction_seconds); -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionOptionsUniversal - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setCompactionOptionsUniversal( - JNIEnv*, jobject, jlong jhandle, - jlong jcompaction_options_universal_handle) { - auto* opts = reinterpret_cast(jhandle); - auto* opts_uni = - reinterpret_cast( - jcompaction_options_universal_handle); - opts->compaction_options_universal = *opts_uni; -} - -/* - * Class: org_rocksdb_Options - * Method: setCompactionOptionsFIFO - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setCompactionOptionsFIFO( - JNIEnv*, jobject, jlong jhandle, jlong jcompaction_options_fifo_handle) { - auto* opts = reinterpret_cast(jhandle); - auto* opts_fifo = reinterpret_cast( - jcompaction_options_fifo_handle); - opts->compaction_options_fifo = *opts_fifo; -} - -/* - * Class: org_rocksdb_Options - * Method: setForceConsistencyChecks - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setForceConsistencyChecks( - JNIEnv*, jobject, jlong jhandle, jboolean jforce_consistency_checks) { - auto* opts = reinterpret_cast(jhandle); - opts->force_consistency_checks = static_cast(jforce_consistency_checks); -} - -/* - * Class: org_rocksdb_Options - * Method: forceConsistencyChecks - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_forceConsistencyChecks(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->force_consistency_checks); -} - -/// BLOB options - -/* - * Class: org_rocksdb_Options - * Method: setEnableBlobFiles - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setEnableBlobFiles(JNIEnv*, jobject, - jlong jhandle, - jboolean jenable_blob_files) { - auto* opts = reinterpret_cast(jhandle); - opts->enable_blob_files = static_cast(jenable_blob_files); -} - -/* - * Class: org_rocksdb_Options - * Method: enableBlobFiles - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_enableBlobFiles(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->enable_blob_files); -} - -/* - * Class: org_rocksdb_Options - * Method: setMinBlobSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setMinBlobSize(JNIEnv*, jobject, jlong jhandle, - jlong jmin_blob_size) { - auto* opts = reinterpret_cast(jhandle); - opts->min_blob_size = static_cast(jmin_blob_size); -} - -/* - * Class: org_rocksdb_Options - * Method: minBlobSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_minBlobSize(JNIEnv*, jobject, jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->min_blob_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setBlobFileSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setBlobFileSize(JNIEnv*, jobject, jlong jhandle, - jlong jblob_file_size) { - auto* opts = reinterpret_cast(jhandle); - opts->blob_file_size = static_cast(jblob_file_size); -} - -/* - * Class: org_rocksdb_Options - * Method: blobFileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_blobFileSize(JNIEnv*, jobject, jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->blob_file_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setBlobCompressionType - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setBlobCompressionType( - JNIEnv*, jobject, jlong jhandle, jbyte jblob_compression_type_value) { - auto* opts = reinterpret_cast(jhandle); - opts->blob_compression_type = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jblob_compression_type_value); -} - -/* - * Class: org_rocksdb_Options - * Method: blobCompressionType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_blobCompressionType(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - opts->blob_compression_type); -} - -/* - * Class: org_rocksdb_Options - * Method: setEnableBlobGarbageCollection - * Signature: (JZ)V - */ -void Java_org_rocksdb_Options_setEnableBlobGarbageCollection( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_blob_garbage_collection) { - auto* opts = reinterpret_cast(jhandle); - opts->enable_blob_garbage_collection = - static_cast(jenable_blob_garbage_collection); -} - -/* - * Class: org_rocksdb_Options - * Method: enableBlobGarbageCollection - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Options_enableBlobGarbageCollection(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->enable_blob_garbage_collection); -} - -/* - * Class: org_rocksdb_Options - * Method: setBlobGarbageCollectionAgeCutoff - * Signature: (JD)V - */ -void Java_org_rocksdb_Options_setBlobGarbageCollectionAgeCutoff( - JNIEnv*, jobject, jlong jhandle, - jdouble jblob_garbage_collection_age_cutoff) { - auto* opts = reinterpret_cast(jhandle); - opts->blob_garbage_collection_age_cutoff = - static_cast(jblob_garbage_collection_age_cutoff); -} - -/* - * Class: org_rocksdb_Options - * Method: blobGarbageCollectionAgeCutoff - * Signature: (J)D - */ -jdouble Java_org_rocksdb_Options_blobGarbageCollectionAgeCutoff(JNIEnv*, - jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->blob_garbage_collection_age_cutoff); -} - -/* - * Class: org_rocksdb_Options - * Method: setBlobGarbageCollectionForceThreshold - * Signature: (JD)V - */ -void Java_org_rocksdb_Options_setBlobGarbageCollectionForceThreshold( - JNIEnv*, jobject, jlong jhandle, - jdouble jblob_garbage_collection_force_threshold) { - auto* opts = reinterpret_cast(jhandle); - opts->blob_garbage_collection_force_threshold = - static_cast(jblob_garbage_collection_force_threshold); -} - -/* - * Class: org_rocksdb_Options - * Method: blobGarbageCollectionForceThreshold - * Signature: (J)D - */ -jdouble Java_org_rocksdb_Options_blobGarbageCollectionForceThreshold( - JNIEnv*, jobject, jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->blob_garbage_collection_force_threshold); -} - -/* - * Class: org_rocksdb_Options - * Method: setBlobCompactionReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setBlobCompactionReadaheadSize( - JNIEnv*, jobject, jlong jhandle, jlong jblob_compaction_readahead_size) { - auto* opts = reinterpret_cast(jhandle); - opts->blob_compaction_readahead_size = - static_cast(jblob_compaction_readahead_size); -} - -/* - * Class: org_rocksdb_Options - * Method: blobCompactionReadaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_Options_blobCompactionReadaheadSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->blob_compaction_readahead_size); -} - -/* - * Class: org_rocksdb_Options - * Method: setBlobFileStartingLevel - * Signature: (JI)V - */ -void Java_org_rocksdb_Options_setBlobFileStartingLevel( - JNIEnv*, jobject, jlong jhandle, jint jblob_file_starting_level) { - auto* opts = reinterpret_cast(jhandle); - opts->blob_file_starting_level = jblob_file_starting_level; -} - -/* - * Class: org_rocksdb_Options - * Method: blobFileStartingLevel - * Signature: (J)I - */ -jint Java_org_rocksdb_Options_blobFileStartingLevel(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return static_cast(opts->blob_file_starting_level); -} - -/* - * Class: org_rocksdb_Options - * Method: setPrepopulateBlobCache - * Signature: (JB)V - */ -void Java_org_rocksdb_Options_setPrepopulateBlobCache( - JNIEnv*, jobject, jlong jhandle, jbyte jprepopulate_blob_cache_value) { - auto* opts = reinterpret_cast(jhandle); - opts->prepopulate_blob_cache = - ROCKSDB_NAMESPACE::PrepopulateBlobCacheJni::toCppPrepopulateBlobCache( - jprepopulate_blob_cache_value); -} - -/* - * Class: org_rocksdb_Options - * Method: prepopulateBlobCache - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Options_prepopulateBlobCache(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::PrepopulateBlobCacheJni::toJavaPrepopulateBlobCache( - opts->prepopulate_blob_cache); -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::ColumnFamilyOptions - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: newColumnFamilyOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_newColumnFamilyOptions(JNIEnv*, - jclass) { - auto* op = new ROCKSDB_NAMESPACE::ColumnFamilyOptions(); - return GET_CPLUSPLUS_POINTER(op); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: copyColumnFamilyOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_copyColumnFamilyOptions( - JNIEnv*, jclass, jlong jhandle) { - auto new_opt = new ROCKSDB_NAMESPACE::ColumnFamilyOptions( - *(reinterpret_cast(jhandle))); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: newColumnFamilyOptionsFromOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_newColumnFamilyOptionsFromOptions( - JNIEnv*, jclass, jlong joptions_handle) { - auto new_opt = new ROCKSDB_NAMESPACE::ColumnFamilyOptions( - *reinterpret_cast(joptions_handle)); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: getColumnFamilyOptionsFromProps - * Signature: (JLjava/lang/String;)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_getColumnFamilyOptionsFromProps__JLjava_lang_String_2( - JNIEnv* env, jclass, jlong cfg_handle, jstring jopt_string) { - const char* opt_string = env->GetStringUTFChars(jopt_string, nullptr); - if (opt_string == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - auto* config_options = - reinterpret_cast(cfg_handle); - auto* cf_options = new ROCKSDB_NAMESPACE::ColumnFamilyOptions(); - ROCKSDB_NAMESPACE::Status status = - ROCKSDB_NAMESPACE::GetColumnFamilyOptionsFromString( - *config_options, ROCKSDB_NAMESPACE::ColumnFamilyOptions(), opt_string, - cf_options); - - env->ReleaseStringUTFChars(jopt_string, opt_string); - - // Check if ColumnFamilyOptions creation was possible. - jlong ret_value = 0; - if (status.ok()) { - ret_value = GET_CPLUSPLUS_POINTER(cf_options); - } else { - // if operation failed the ColumnFamilyOptions need to be deleted - // again to prevent a memory leak. - delete cf_options; - } - return ret_value; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: getColumnFamilyOptionsFromProps - * Signature: (Ljava/util/String;)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_getColumnFamilyOptionsFromProps__Ljava_lang_String_2( - JNIEnv* env, jclass, jstring jopt_string) { - const char* opt_string = env->GetStringUTFChars(jopt_string, nullptr); - if (opt_string == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - auto* cf_options = new ROCKSDB_NAMESPACE::ColumnFamilyOptions(); - ROCKSDB_NAMESPACE::ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - ROCKSDB_NAMESPACE::Status status = - ROCKSDB_NAMESPACE::GetColumnFamilyOptionsFromString( - config_options, ROCKSDB_NAMESPACE::ColumnFamilyOptions(), opt_string, - cf_options); - - env->ReleaseStringUTFChars(jopt_string, opt_string); - - // Check if ColumnFamilyOptions creation was possible. - jlong ret_value = 0; - if (status.ok()) { - ret_value = GET_CPLUSPLUS_POINTER(cf_options); - } else { - // if operation failed the ColumnFamilyOptions need to be deleted - // again to prevent a memory leak. - delete cf_options; - } - return ret_value; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_disposeInternal(JNIEnv*, jobject, - jlong handle) { - auto* cfo = reinterpret_cast(handle); - assert(cfo != nullptr); - delete cfo; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: oldDefaults - * Signature: (JII)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_oldDefaults(JNIEnv*, jclass, - jlong jhandle, - jint major_version, - jint minor_version) { - reinterpret_cast(jhandle) - ->OldDefaults(major_version, minor_version); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: optimizeForSmallDb - * Signature: (J)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_optimizeForSmallDb__J(JNIEnv*, - jobject, - jlong jhandle) { - reinterpret_cast(jhandle) - ->OptimizeForSmallDb(); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: optimizeForSmallDb - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_optimizeForSmallDb__JJ( - JNIEnv*, jclass, jlong jhandle, jlong cache_handle) { - auto* cache_sptr_ptr = - reinterpret_cast*>( - cache_handle); - reinterpret_cast(jhandle) - ->OptimizeForSmallDb(cache_sptr_ptr); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: optimizeForPointLookup - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_optimizeForPointLookup( - JNIEnv*, jobject, jlong jhandle, jlong block_cache_size_mb) { - reinterpret_cast(jhandle) - ->OptimizeForPointLookup(block_cache_size_mb); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: optimizeLevelStyleCompaction - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_optimizeLevelStyleCompaction( - JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { - reinterpret_cast(jhandle) - ->OptimizeLevelStyleCompaction(memtable_memory_budget); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: optimizeUniversalStyleCompaction - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_optimizeUniversalStyleCompaction( - JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { - reinterpret_cast(jhandle) - ->OptimizeUniversalStyleCompaction(memtable_memory_budget); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setComparatorHandle - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JI( - JNIEnv*, jobject, jlong jhandle, jint builtinComparator) { - switch (builtinComparator) { - case 1: - reinterpret_cast(jhandle) - ->comparator = ROCKSDB_NAMESPACE::ReverseBytewiseComparator(); - break; - default: - reinterpret_cast(jhandle) - ->comparator = ROCKSDB_NAMESPACE::BytewiseComparator(); - break; - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setComparatorHandle - * Signature: (JJB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JJB( - JNIEnv*, jobject, jlong jopt_handle, jlong jcomparator_handle, - jbyte jcomparator_type) { - ROCKSDB_NAMESPACE::Comparator* comparator = nullptr; - switch (jcomparator_type) { - // JAVA_COMPARATOR - case 0x0: - comparator = reinterpret_cast( - jcomparator_handle); - break; - - // JAVA_NATIVE_COMPARATOR_WRAPPER - case 0x1: - comparator = - reinterpret_cast(jcomparator_handle); - break; - } - auto* opt = - reinterpret_cast(jopt_handle); - opt->comparator = comparator; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMergeOperatorName - * Signature: (JJjava/lang/String)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperatorName( - JNIEnv* env, jobject, jlong jhandle, jstring jop_name) { - auto* options = - reinterpret_cast(jhandle); - const char* op_name = env->GetStringUTFChars(jop_name, nullptr); - if (op_name == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - options->merge_operator = - ROCKSDB_NAMESPACE::MergeOperators::CreateFromStringId(op_name); - env->ReleaseStringUTFChars(jop_name, op_name); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMergeOperator - * Signature: (JJjava/lang/String)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperator( - JNIEnv*, jobject, jlong jhandle, jlong mergeOperatorHandle) { - reinterpret_cast(jhandle) - ->merge_operator = - *(reinterpret_cast*>( - mergeOperatorHandle)); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionFilterHandle - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterHandle( - JNIEnv*, jobject, jlong jopt_handle, jlong jcompactionfilter_handle) { - reinterpret_cast(jopt_handle) - ->compaction_filter = - reinterpret_cast( - jcompactionfilter_handle); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionFilterFactoryHandle - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterFactoryHandle( - JNIEnv*, jobject, jlong jopt_handle, - jlong jcompactionfilterfactory_handle) { - auto* cff_factory = reinterpret_cast< - std::shared_ptr*>( - jcompactionfilterfactory_handle); - reinterpret_cast(jopt_handle) - ->compaction_filter_factory = *cff_factory; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setWriteBufferSize - * Signature: (JJ)I - */ -void Java_org_rocksdb_ColumnFamilyOptions_setWriteBufferSize( - JNIEnv* env, jobject, jlong jhandle, jlong jwrite_buffer_size) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jwrite_buffer_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->write_buffer_size = jwrite_buffer_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: writeBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_writeBufferSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->write_buffer_size; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxWriteBufferNumber - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxWriteBufferNumber( - JNIEnv*, jobject, jlong jhandle, jint jmax_write_buffer_number) { - reinterpret_cast(jhandle) - ->max_write_buffer_number = jmax_write_buffer_number; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxWriteBufferNumber - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_maxWriteBufferNumber(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_write_buffer_number; -} - -/* - * Method: setMemTableFactory - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMemTableFactory( - JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) { - reinterpret_cast(jhandle) - ->memtable_factory.reset( - reinterpret_cast( - jfactory_handle)); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: memTableFactoryName - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_ColumnFamilyOptions_memTableFactoryName( - JNIEnv* env, jobject, jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::MemTableRepFactory* tf = opt->memtable_factory.get(); - - // Should never be nullptr. - // Default memtable factory is SkipListFactory - assert(tf); - - // temporarly fix for the historical typo - if (strcmp(tf->Name(), "HashLinkListRepFactory") == 0) { - return env->NewStringUTF("HashLinkedListRepFactory"); - } - - return env->NewStringUTF(tf->Name()); -} - -/* - * Method: useFixedLengthPrefixExtractor - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_useFixedLengthPrefixExtractor( - JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { - reinterpret_cast(jhandle) - ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewFixedPrefixTransform( - static_cast(jprefix_length))); -} - -/* - * Method: useCappedPrefixExtractor - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_useCappedPrefixExtractor( - JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { - reinterpret_cast(jhandle) - ->prefix_extractor.reset(ROCKSDB_NAMESPACE::NewCappedPrefixTransform( - static_cast(jprefix_length))); -} - -/* - * Method: setTableFactory - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setTableFactory( - JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) { - reinterpret_cast(jhandle) - ->table_factory.reset( - reinterpret_cast(jfactory_handle)); -} - -/* - * Method: setSstPartitionerFactory - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setSstPartitionerFactory( - JNIEnv*, jobject, jlong jhandle, jlong factory_handle) { - auto* options = - reinterpret_cast(jhandle); - auto factory = reinterpret_cast< - std::shared_ptr*>( - factory_handle); - options->sst_partitioner_factory = *factory; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionThreadLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionThreadLimiter( - JNIEnv*, jclass, jlong jhandle, jlong jlimiter_handle) { - auto* options = - reinterpret_cast(jhandle); - auto* limiter = reinterpret_cast< - std::shared_ptr*>( - jlimiter_handle); - options->compaction_thread_limiter = *limiter; -} - -/* - * Method: tableFactoryName - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_ColumnFamilyOptions_tableFactoryName(JNIEnv* env, - jobject, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::TableFactory* tf = opt->table_factory.get(); - - // Should never be nullptr. - // Default memtable factory is SkipListFactory - assert(tf); - - return env->NewStringUTF(tf->Name()); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCfPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCfPaths(JNIEnv* env, jclass, - jlong jhandle, - jobjectArray path_array, - jlongArray size_array) { - auto* options = - reinterpret_cast(jhandle); - jboolean has_exception = JNI_FALSE; - std::vector cf_paths = - rocksdb_convert_cf_paths_from_java_helper(env, path_array, size_array, - &has_exception); - if (JNI_FALSE == has_exception) { - options->cf_paths = std::move(cf_paths); - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: cfPathsLen - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_cfPathsLen(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = - reinterpret_cast(jhandle); - return static_cast(opt->cf_paths.size()); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: cfPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_cfPaths(JNIEnv* env, jclass, - jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - rocksdb_convert_cf_paths_to_java_helper< - ROCKSDB_NAMESPACE::ColumnFamilyOptions>(env, jhandle, jpaths, - jtarget_sizes); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: minWriteBufferNumberToMerge - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_minWriteBufferNumberToMerge( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->min_write_buffer_number_to_merge; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMinWriteBufferNumberToMerge - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMinWriteBufferNumberToMerge( - JNIEnv*, jobject, jlong jhandle, jint jmin_write_buffer_number_to_merge) { - reinterpret_cast(jhandle) - ->min_write_buffer_number_to_merge = - static_cast(jmin_write_buffer_number_to_merge); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxWriteBufferNumberToMaintain - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_maxWriteBufferNumberToMaintain( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_write_buffer_number_to_maintain; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxWriteBufferNumberToMaintain - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxWriteBufferNumberToMaintain( - JNIEnv*, jobject, jlong jhandle, - jint jmax_write_buffer_number_to_maintain) { - reinterpret_cast(jhandle) - ->max_write_buffer_number_to_maintain = - static_cast(jmax_write_buffer_number_to_maintain); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompressionType - * Signature: (JB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompressionType( - JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { - auto* cf_opts = - reinterpret_cast(jhandle); - cf_opts->compression = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jcompression_type_value); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: compressionType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ColumnFamilyOptions_compressionType(JNIEnv*, jobject, - jlong jhandle) { - auto* cf_opts = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - cf_opts->compression); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompressionPerLevel - * Signature: (J[B)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompressionPerLevel( - JNIEnv* env, jobject, jlong jhandle, jbyteArray jcompressionLevels) { - auto* options = - reinterpret_cast(jhandle); - auto uptr_compression_levels = - rocksdb_compression_vector_helper(env, jcompressionLevels); - if (!uptr_compression_levels) { - // exception occurred - return; - } - options->compression_per_level = *(uptr_compression_levels.get()); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: compressionPerLevel - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_ColumnFamilyOptions_compressionPerLevel( - JNIEnv* env, jobject, jlong jhandle) { - auto* cf_options = - reinterpret_cast(jhandle); - return rocksdb_compression_list_helper(env, - cf_options->compression_per_level); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBottommostCompressionType - * Signature: (JB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBottommostCompressionType( - JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { - auto* cf_options = - reinterpret_cast(jhandle); - cf_options->bottommost_compression = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jcompression_type_value); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: bottommostCompressionType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ColumnFamilyOptions_bottommostCompressionType( - JNIEnv*, jobject, jlong jhandle) { - auto* cf_options = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - cf_options->bottommost_compression); -} -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBottommostCompressionOptions - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBottommostCompressionOptions( - JNIEnv*, jobject, jlong jhandle, - jlong jbottommost_compression_options_handle) { - auto* cf_options = - reinterpret_cast(jhandle); - auto* bottommost_compression_options = - reinterpret_cast( - jbottommost_compression_options_handle); - cf_options->bottommost_compression_opts = *bottommost_compression_options; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompressionOptions - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompressionOptions( - JNIEnv*, jobject, jlong jhandle, jlong jcompression_options_handle) { - auto* cf_options = - reinterpret_cast(jhandle); - auto* compression_options = - reinterpret_cast( - jcompression_options_handle); - cf_options->compression_opts = *compression_options; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionStyle - * Signature: (JB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionStyle( - JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_style) { - auto* cf_options = - reinterpret_cast(jhandle); - cf_options->compaction_style = - ROCKSDB_NAMESPACE::CompactionStyleJni::toCppCompactionStyle( - jcompaction_style); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: compactionStyle - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ColumnFamilyOptions_compactionStyle(JNIEnv*, jobject, - jlong jhandle) { - auto* cf_options = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompactionStyleJni::toJavaCompactionStyle( - cf_options->compaction_style); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxTableFilesSizeFIFO - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxTableFilesSizeFIFO( - JNIEnv*, jobject, jlong jhandle, jlong jmax_table_files_size) { - reinterpret_cast(jhandle) - ->compaction_options_fifo.max_table_files_size = - static_cast(jmax_table_files_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxTableFilesSizeFIFO - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_maxTableFilesSizeFIFO( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->compaction_options_fifo.max_table_files_size; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: numLevels - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_numLevels(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->num_levels; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setNumLevels - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setNumLevels(JNIEnv*, jobject, - jlong jhandle, - jint jnum_levels) { - reinterpret_cast(jhandle) - ->num_levels = static_cast(jnum_levels); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: levelZeroFileNumCompactionTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroFileNumCompactionTrigger( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevelZeroFileNumCompactionTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroFileNumCompactionTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: levelZeroSlowdownWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroSlowdownWritesTrigger( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevelSlowdownWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroSlowdownWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: levelZeroStopWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroStopWritesTrigger( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_stop_writes_trigger; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevelStopWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroStopWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_stop_writes_trigger = - static_cast(jlevel0_stop_writes_trigger); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: targetFileSizeBase - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_targetFileSizeBase(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->target_file_size_base; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setTargetFileSizeBase - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setTargetFileSizeBase( - JNIEnv*, jobject, jlong jhandle, jlong jtarget_file_size_base) { - reinterpret_cast(jhandle) - ->target_file_size_base = static_cast(jtarget_file_size_base); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: targetFileSizeMultiplier - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_targetFileSizeMultiplier( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->target_file_size_multiplier; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setTargetFileSizeMultiplier - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setTargetFileSizeMultiplier( - JNIEnv*, jobject, jlong jhandle, jint jtarget_file_size_multiplier) { - reinterpret_cast(jhandle) - ->target_file_size_multiplier = - static_cast(jtarget_file_size_multiplier); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxBytesForLevelBase - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelBase(JNIEnv*, - jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_bytes_for_level_base; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxBytesForLevelBase - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelBase( - JNIEnv*, jobject, jlong jhandle, jlong jmax_bytes_for_level_base) { - reinterpret_cast(jhandle) - ->max_bytes_for_level_base = - static_cast(jmax_bytes_for_level_base); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: levelCompactionDynamicLevelBytes - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_levelCompactionDynamicLevelBytes( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level_compaction_dynamic_level_bytes; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevelCompactionDynamicLevelBytes - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevelCompactionDynamicLevelBytes( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_dynamic_level_bytes) { - reinterpret_cast(jhandle) - ->level_compaction_dynamic_level_bytes = (jenable_dynamic_level_bytes); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxBytesForLevelMultiplier - * Signature: (J)D - */ -jdouble Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplier( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_bytes_for_level_multiplier; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxBytesForLevelMultiplier - * Signature: (JD)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplier( - JNIEnv*, jobject, jlong jhandle, jdouble jmax_bytes_for_level_multiplier) { - reinterpret_cast(jhandle) - ->max_bytes_for_level_multiplier = - static_cast(jmax_bytes_for_level_multiplier); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxCompactionBytes - * Signature: (J)I - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_maxCompactionBytes(JNIEnv*, jobject, - jlong jhandle) { - return static_cast( - reinterpret_cast(jhandle) - ->max_compaction_bytes); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxCompactionBytes - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxCompactionBytes( - JNIEnv*, jobject, jlong jhandle, jlong jmax_compaction_bytes) { - reinterpret_cast(jhandle) - ->max_compaction_bytes = static_cast(jmax_compaction_bytes); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: arenaBlockSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_arenaBlockSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->arena_block_size; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setArenaBlockSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setArenaBlockSize( - JNIEnv* env, jobject, jlong jhandle, jlong jarena_block_size) { - auto s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(jarena_block_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->arena_block_size = jarena_block_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: disableAutoCompactions - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_disableAutoCompactions( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->disable_auto_compactions; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setDisableAutoCompactions - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setDisableAutoCompactions( - JNIEnv*, jobject, jlong jhandle, jboolean jdisable_auto_compactions) { - reinterpret_cast(jhandle) - ->disable_auto_compactions = static_cast(jdisable_auto_compactions); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxSequentialSkipInIterations - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_maxSequentialSkipInIterations( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_sequential_skip_in_iterations; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxSequentialSkipInIterations - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxSequentialSkipInIterations( - JNIEnv*, jobject, jlong jhandle, jlong jmax_sequential_skip_in_iterations) { - reinterpret_cast(jhandle) - ->max_sequential_skip_in_iterations = - static_cast(jmax_sequential_skip_in_iterations); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: inplaceUpdateSupport - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_inplaceUpdateSupport( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->inplace_update_support; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setInplaceUpdateSupport - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setInplaceUpdateSupport( - JNIEnv*, jobject, jlong jhandle, jboolean jinplace_update_support) { - reinterpret_cast(jhandle) - ->inplace_update_support = static_cast(jinplace_update_support); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: inplaceUpdateNumLocks - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_inplaceUpdateNumLocks( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->inplace_update_num_locks; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setInplaceUpdateNumLocks - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setInplaceUpdateNumLocks( - JNIEnv* env, jobject, jlong jhandle, jlong jinplace_update_num_locks) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jinplace_update_num_locks); - if (s.ok()) { - reinterpret_cast(jhandle) - ->inplace_update_num_locks = jinplace_update_num_locks; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: memtablePrefixBloomSizeRatio - * Signature: (J)I - */ -jdouble Java_org_rocksdb_ColumnFamilyOptions_memtablePrefixBloomSizeRatio( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_prefix_bloom_size_ratio; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMemtablePrefixBloomSizeRatio - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMemtablePrefixBloomSizeRatio( - JNIEnv*, jobject, jlong jhandle, - jdouble jmemtable_prefix_bloom_size_ratio) { - reinterpret_cast(jhandle) - ->memtable_prefix_bloom_size_ratio = - static_cast(jmemtable_prefix_bloom_size_ratio); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: experimentalMempurgeThreshold - * Signature: (J)I - */ -jdouble Java_org_rocksdb_ColumnFamilyOptions_experimentalMempurgeThreshold( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->experimental_mempurge_threshold; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setExperimentalMempurgeThreshold - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setExperimentalMempurgeThreshold( - JNIEnv*, jobject, jlong jhandle, jdouble jexperimental_mempurge_threshold) { - reinterpret_cast(jhandle) - ->experimental_mempurge_threshold = - static_cast(jexperimental_mempurge_threshold); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: memtableWholeKeyFiltering - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_memtableWholeKeyFiltering( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_whole_key_filtering; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMemtableWholeKeyFiltering - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMemtableWholeKeyFiltering( - JNIEnv*, jobject, jlong jhandle, jboolean jmemtable_whole_key_filtering) { - reinterpret_cast(jhandle) - ->memtable_whole_key_filtering = - static_cast(jmemtable_whole_key_filtering); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: bloomLocality - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_bloomLocality(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->bloom_locality; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBloomLocality - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBloomLocality( - JNIEnv*, jobject, jlong jhandle, jint jbloom_locality) { - reinterpret_cast(jhandle) - ->bloom_locality = static_cast(jbloom_locality); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxSuccessiveMerges - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_maxSuccessiveMerges(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_successive_merges; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxSuccessiveMerges - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxSuccessiveMerges( - JNIEnv* env, jobject, jlong jhandle, jlong jmax_successive_merges) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jmax_successive_merges); - if (s.ok()) { - reinterpret_cast(jhandle) - ->max_successive_merges = jmax_successive_merges; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: optimizeFiltersForHits - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_optimizeFiltersForHits( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->optimize_filters_for_hits; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setOptimizeFiltersForHits - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setOptimizeFiltersForHits( - JNIEnv*, jobject, jlong jhandle, jboolean joptimize_filters_for_hits) { - reinterpret_cast(jhandle) - ->optimize_filters_for_hits = - static_cast(joptimize_filters_for_hits); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: memtableHugePageSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_memtableHugePageSize(JNIEnv*, - jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_huge_page_size; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMemtableHugePageSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMemtableHugePageSize( - JNIEnv* env, jobject, jlong jhandle, jlong jmemtable_huge_page_size) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - jmemtable_huge_page_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->memtable_huge_page_size = jmemtable_huge_page_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: softPendingCompactionBytesLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_softPendingCompactionBytesLimit( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->soft_pending_compaction_bytes_limit; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setSoftPendingCompactionBytesLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setSoftPendingCompactionBytesLimit( - JNIEnv*, jobject, jlong jhandle, - jlong jsoft_pending_compaction_bytes_limit) { - reinterpret_cast(jhandle) - ->soft_pending_compaction_bytes_limit = - static_cast(jsoft_pending_compaction_bytes_limit); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: softHardCompactionBytesLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_hardPendingCompactionBytesLimit( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->hard_pending_compaction_bytes_limit; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setHardPendingCompactionBytesLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setHardPendingCompactionBytesLimit( - JNIEnv*, jobject, jlong jhandle, - jlong jhard_pending_compaction_bytes_limit) { - reinterpret_cast(jhandle) - ->hard_pending_compaction_bytes_limit = - static_cast(jhard_pending_compaction_bytes_limit); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: level0FileNumCompactionTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_level0FileNumCompactionTrigger( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevel0FileNumCompactionTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevel0FileNumCompactionTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast(jhandle) - ->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: level0SlowdownWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_level0SlowdownWritesTrigger( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevel0SlowdownWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevel0SlowdownWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: level0StopWritesTrigger - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_level0StopWritesTrigger( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->level0_stop_writes_trigger; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setLevel0StopWritesTrigger - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setLevel0StopWritesTrigger( - JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { - reinterpret_cast(jhandle) - ->level0_stop_writes_trigger = - static_cast(jlevel0_stop_writes_trigger); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: maxBytesForLevelMultiplierAdditional - * Signature: (J)[I - */ -jintArray -Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject, jlong jhandle) { - auto mbflma = - reinterpret_cast(jhandle) - ->max_bytes_for_level_multiplier_additional; - - const size_t size = mbflma.size(); - - jint* additionals = new jint[size]; - for (size_t i = 0; i < size; i++) { - additionals[i] = static_cast(mbflma[i]); - } - - jsize jlen = static_cast(size); - jintArray result = env->NewIntArray(jlen); - if (result == nullptr) { - // exception thrown: OutOfMemoryError - delete[] additionals; - return nullptr; - } - env->SetIntArrayRegion(result, 0, jlen, additionals); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(result); - delete[] additionals; - return nullptr; - } - - delete[] additionals; - - return result; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMaxBytesForLevelMultiplierAdditional - * Signature: (J[I)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject, jlong jhandle, - jintArray jmax_bytes_for_level_multiplier_additional) { - jsize len = env->GetArrayLength(jmax_bytes_for_level_multiplier_additional); - jint* additionals = env->GetIntArrayElements( - jmax_bytes_for_level_multiplier_additional, nullptr); - if (additionals == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* cf_opt = - reinterpret_cast(jhandle); - cf_opt->max_bytes_for_level_multiplier_additional.clear(); - for (jsize i = 0; i < len; i++) { - cf_opt->max_bytes_for_level_multiplier_additional.push_back( - static_cast(additionals[i])); - } - - env->ReleaseIntArrayElements(jmax_bytes_for_level_multiplier_additional, - additionals, JNI_ABORT); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: paranoidFileChecks - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_paranoidFileChecks( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->paranoid_file_checks; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setParanoidFileChecks - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setParanoidFileChecks( - JNIEnv*, jobject, jlong jhandle, jboolean jparanoid_file_checks) { - reinterpret_cast(jhandle) - ->paranoid_file_checks = static_cast(jparanoid_file_checks); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionPriority - * Signature: (JB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionPriority( - JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_priority_value) { - auto* cf_opts = - reinterpret_cast(jhandle); - cf_opts->compaction_pri = - ROCKSDB_NAMESPACE::CompactionPriorityJni::toCppCompactionPriority( - jcompaction_priority_value); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: compactionPriority - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ColumnFamilyOptions_compactionPriority(JNIEnv*, jobject, - jlong jhandle) { - auto* cf_opts = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompactionPriorityJni::toJavaCompactionPriority( - cf_opts->compaction_pri); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setReportBgIoStats - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setReportBgIoStats( - JNIEnv*, jobject, jlong jhandle, jboolean jreport_bg_io_stats) { - auto* cf_opts = - reinterpret_cast(jhandle); - cf_opts->report_bg_io_stats = static_cast(jreport_bg_io_stats); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: reportBgIoStats - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_reportBgIoStats(JNIEnv*, jobject, - jlong jhandle) { - auto* cf_opts = - reinterpret_cast(jhandle); - return static_cast(cf_opts->report_bg_io_stats); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setTtl - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setTtl(JNIEnv*, jobject, - jlong jhandle, jlong jttl) { - auto* cf_opts = - reinterpret_cast(jhandle); - cf_opts->ttl = static_cast(jttl); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: ttl - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL -Java_org_rocksdb_ColumnFamilyOptions_ttl(JNIEnv*, jobject, jlong jhandle) { - auto* cf_opts = - reinterpret_cast(jhandle); - return static_cast(cf_opts->ttl); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setPeriodicCompactionSeconds - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setPeriodicCompactionSeconds( - JNIEnv*, jobject, jlong jhandle, jlong jperiodicCompactionSeconds) { - auto* cf_opts = - reinterpret_cast(jhandle); - cf_opts->periodic_compaction_seconds = - static_cast(jperiodicCompactionSeconds); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: periodicCompactionSeconds - * Signature: (J)J - */ -JNIEXPORT jlong JNICALL -Java_org_rocksdb_ColumnFamilyOptions_periodicCompactionSeconds(JNIEnv*, jobject, - jlong jhandle) { - auto* cf_opts = - reinterpret_cast(jhandle); - return static_cast(cf_opts->periodic_compaction_seconds); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionOptionsUniversal - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionOptionsUniversal( - JNIEnv*, jobject, jlong jhandle, - jlong jcompaction_options_universal_handle) { - auto* cf_opts = - reinterpret_cast(jhandle); - auto* opts_uni = - reinterpret_cast( - jcompaction_options_universal_handle); - cf_opts->compaction_options_universal = *opts_uni; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setCompactionOptionsFIFO - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setCompactionOptionsFIFO( - JNIEnv*, jobject, jlong jhandle, jlong jcompaction_options_fifo_handle) { - auto* cf_opts = - reinterpret_cast(jhandle); - auto* opts_fifo = reinterpret_cast( - jcompaction_options_fifo_handle); - cf_opts->compaction_options_fifo = *opts_fifo; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setForceConsistencyChecks - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setForceConsistencyChecks( - JNIEnv*, jobject, jlong jhandle, jboolean jforce_consistency_checks) { - auto* cf_opts = - reinterpret_cast(jhandle); - cf_opts->force_consistency_checks = - static_cast(jforce_consistency_checks); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: forceConsistencyChecks - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_forceConsistencyChecks( - JNIEnv*, jobject, jlong jhandle) { - auto* cf_opts = - reinterpret_cast(jhandle); - return static_cast(cf_opts->force_consistency_checks); -} - -/// BLOB options - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setEnableBlobFiles - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setEnableBlobFiles( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_blob_files) { - auto* opts = - reinterpret_cast(jhandle); - opts->enable_blob_files = static_cast(jenable_blob_files); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: enableBlobFiles - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_enableBlobFiles(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->enable_blob_files); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setMinBlobSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setMinBlobSize(JNIEnv*, jobject, - jlong jhandle, - jlong jmin_blob_size) { - auto* opts = - reinterpret_cast(jhandle); - opts->min_blob_size = static_cast(jmin_blob_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: minBlobSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_minBlobSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->min_blob_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBlobFileSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBlobFileSize( - JNIEnv*, jobject, jlong jhandle, jlong jblob_file_size) { - auto* opts = - reinterpret_cast(jhandle); - opts->blob_file_size = static_cast(jblob_file_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: blobFileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_blobFileSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->blob_file_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBlobCompressionType - * Signature: (JB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBlobCompressionType( - JNIEnv*, jobject, jlong jhandle, jbyte jblob_compression_type_value) { - auto* opts = - reinterpret_cast(jhandle); - opts->blob_compression_type = - ROCKSDB_NAMESPACE::CompressionTypeJni::toCppCompressionType( - jblob_compression_type_value); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: blobCompressionType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ColumnFamilyOptions_blobCompressionType(JNIEnv*, jobject, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::CompressionTypeJni::toJavaCompressionType( - opts->blob_compression_type); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setEnableBlobGarbageCollection - * Signature: (JZ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setEnableBlobGarbageCollection( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_blob_garbage_collection) { - auto* opts = - reinterpret_cast(jhandle); - opts->enable_blob_garbage_collection = - static_cast(jenable_blob_garbage_collection); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: enableBlobGarbageCollection - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ColumnFamilyOptions_enableBlobGarbageCollection( - JNIEnv*, jobject, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->enable_blob_garbage_collection); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBlobGarbageCollectionAgeCutoff - * Signature: (JD)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBlobGarbageCollectionAgeCutoff( - JNIEnv*, jobject, jlong jhandle, - jdouble jblob_garbage_collection_age_cutoff) { - auto* opts = - reinterpret_cast(jhandle); - opts->blob_garbage_collection_age_cutoff = - static_cast(jblob_garbage_collection_age_cutoff); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: blobGarbageCollectionAgeCutoff - * Signature: (J)D - */ -jdouble Java_org_rocksdb_ColumnFamilyOptions_blobGarbageCollectionAgeCutoff( - JNIEnv*, jobject, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->blob_garbage_collection_age_cutoff); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBlobGarbageCollectionForceThreshold - * Signature: (JD)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBlobGarbageCollectionForceThreshold( - JNIEnv*, jobject, jlong jhandle, - jdouble jblob_garbage_collection_force_threshold) { - auto* opts = - reinterpret_cast(jhandle); - opts->blob_garbage_collection_force_threshold = - static_cast(jblob_garbage_collection_force_threshold); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: blobGarbageCollectionForceThreshold - * Signature: (J)D - */ -jdouble -Java_org_rocksdb_ColumnFamilyOptions_blobGarbageCollectionForceThreshold( - JNIEnv*, jobject, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->blob_garbage_collection_force_threshold); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBlobCompactionReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBlobCompactionReadaheadSize( - JNIEnv*, jobject, jlong jhandle, jlong jblob_compaction_readahead_size) { - auto* opts = - reinterpret_cast(jhandle); - opts->blob_compaction_readahead_size = - static_cast(jblob_compaction_readahead_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: blobCompactionReadaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ColumnFamilyOptions_blobCompactionReadaheadSize( - JNIEnv*, jobject, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->blob_compaction_readahead_size); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setBlobFileStartingLevel - * Signature: (JI)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setBlobFileStartingLevel( - JNIEnv*, jobject, jlong jhandle, jint jblob_file_starting_level) { - auto* opts = - reinterpret_cast(jhandle); - opts->blob_file_starting_level = jblob_file_starting_level; -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: blobFileStartingLevel - * Signature: (J)I - */ -jint Java_org_rocksdb_ColumnFamilyOptions_blobFileStartingLevel(JNIEnv*, - jobject, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return static_cast(opts->blob_file_starting_level); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: setPrepopulateBlobCache - * Signature: (JB)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setPrepopulateBlobCache( - JNIEnv*, jobject, jlong jhandle, jbyte jprepopulate_blob_cache_value) { - auto* opts = - reinterpret_cast(jhandle); - opts->prepopulate_blob_cache = - ROCKSDB_NAMESPACE::PrepopulateBlobCacheJni::toCppPrepopulateBlobCache( - jprepopulate_blob_cache_value); -} - -/* - * Class: org_rocksdb_ColumnFamilyOptions - * Method: prepopulateBlobCache - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ColumnFamilyOptions_prepopulateBlobCache(JNIEnv*, - jobject, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::PrepopulateBlobCacheJni::toJavaPrepopulateBlobCache( - opts->prepopulate_blob_cache); -} - -///////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DBOptions - -/* - * Class: org_rocksdb_DBOptions - * Method: newDBOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_DBOptions_newDBOptions(JNIEnv*, jclass) { - auto* dbop = new ROCKSDB_NAMESPACE::DBOptions(); - return GET_CPLUSPLUS_POINTER(dbop); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: copyDBOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_copyDBOptions(JNIEnv*, jclass, jlong jhandle) { - auto new_opt = new ROCKSDB_NAMESPACE::DBOptions( - *(reinterpret_cast(jhandle))); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: newDBOptionsFromOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_newDBOptionsFromOptions( - JNIEnv*, jclass, jlong joptions_handle) { - auto new_opt = new ROCKSDB_NAMESPACE::DBOptions( - *reinterpret_cast(joptions_handle)); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: getDBOptionsFromProps - * Signature: (JLjava/lang/String;)J - */ -jlong Java_org_rocksdb_DBOptions_getDBOptionsFromProps__JLjava_lang_String_2( - JNIEnv* env, jclass, jlong config_handle, jstring jopt_string) { - const char* opt_string = env->GetStringUTFChars(jopt_string, nullptr); - if (opt_string == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - auto* config_options = - reinterpret_cast(config_handle); - auto* db_options = new ROCKSDB_NAMESPACE::DBOptions(); - ROCKSDB_NAMESPACE::Status status = ROCKSDB_NAMESPACE::GetDBOptionsFromString( - *config_options, ROCKSDB_NAMESPACE::DBOptions(), opt_string, db_options); - - env->ReleaseStringUTFChars(jopt_string, opt_string); - - // Check if DBOptions creation was possible. - jlong ret_value = 0; - if (status.ok()) { - ret_value = GET_CPLUSPLUS_POINTER(db_options); - } else { - // if operation failed the DBOptions need to be deleted - // again to prevent a memory leak. - delete db_options; - } - return ret_value; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: getDBOptionsFromProps - * Signature: (Ljava/util/String;)J - */ -jlong Java_org_rocksdb_DBOptions_getDBOptionsFromProps__Ljava_lang_String_2( - JNIEnv* env, jclass, jstring jopt_string) { - const char* opt_string = env->GetStringUTFChars(jopt_string, nullptr); - if (opt_string == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - const ROCKSDB_NAMESPACE::DBOptions base_options; - auto* db_options = new ROCKSDB_NAMESPACE::DBOptions(); - ROCKSDB_NAMESPACE::ConfigOptions config_options(base_options); - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - ROCKSDB_NAMESPACE::Status status = ROCKSDB_NAMESPACE::GetDBOptionsFromString( - config_options, base_options, opt_string, db_options); - - env->ReleaseStringUTFChars(jopt_string, opt_string); - - // Check if DBOptions creation was possible. - jlong ret_value = 0; - if (status.ok()) { - ret_value = GET_CPLUSPLUS_POINTER(db_options); - } else { - // if operation failed the DBOptions need to be deleted - // again to prevent a memory leak. - delete db_options; - } - return ret_value; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_DBOptions_disposeInternal(JNIEnv*, jobject, - jlong handle) { - auto* dbo = reinterpret_cast(handle); - assert(dbo != nullptr); - delete dbo; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: optimizeForSmallDb - * Signature: (J)V - */ -void Java_org_rocksdb_DBOptions_optimizeForSmallDb(JNIEnv*, jobject, - jlong jhandle) { - reinterpret_cast(jhandle) - ->OptimizeForSmallDb(); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setEnv - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setEnv(JNIEnv*, jobject, jlong jhandle, - jlong jenv_handle) { - reinterpret_cast(jhandle)->env = - reinterpret_cast(jenv_handle); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setIncreaseParallelism - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setIncreaseParallelism(JNIEnv*, jobject, - jlong jhandle, - jint totalThreads) { - reinterpret_cast(jhandle)->IncreaseParallelism( - static_cast(totalThreads)); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setCreateIfMissing - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setCreateIfMissing(JNIEnv*, jobject, - jlong jhandle, - jboolean flag) { - reinterpret_cast(jhandle)->create_if_missing = - flag; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: createIfMissing - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_createIfMissing(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->create_if_missing; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setCreateMissingColumnFamilies - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setCreateMissingColumnFamilies(JNIEnv*, jobject, - jlong jhandle, - jboolean flag) { - reinterpret_cast(jhandle) - ->create_missing_column_families = flag; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: createMissingColumnFamilies - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_createMissingColumnFamilies(JNIEnv*, - jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->create_missing_column_families; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setErrorIfExists - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setErrorIfExists(JNIEnv*, jobject, - jlong jhandle, - jboolean error_if_exists) { - reinterpret_cast(jhandle)->error_if_exists = - static_cast(error_if_exists); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: errorIfExists - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_errorIfExists(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->error_if_exists; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setParanoidChecks - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setParanoidChecks(JNIEnv*, jobject, - jlong jhandle, - jboolean paranoid_checks) { - reinterpret_cast(jhandle)->paranoid_checks = - static_cast(paranoid_checks); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: paranoidChecks - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_paranoidChecks(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->paranoid_checks; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setRateLimiter - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setRateLimiter(JNIEnv*, jobject, jlong jhandle, - jlong jrate_limiter_handle) { - std::shared_ptr* pRateLimiter = - reinterpret_cast*>( - jrate_limiter_handle); - reinterpret_cast(jhandle)->rate_limiter = - *pRateLimiter; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setSstFileManager - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setSstFileManager( - JNIEnv*, jobject, jlong jhandle, jlong jsst_file_manager_handle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jsst_file_manager_handle); - reinterpret_cast(jhandle)->sst_file_manager = - *sptr_sst_file_manager; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setLogger - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setLogger(JNIEnv*, jobject, jlong jhandle, - jlong jlogger_handle) { - std::shared_ptr* pLogger = - reinterpret_cast*>( - jlogger_handle); - reinterpret_cast(jhandle)->info_log = *pLogger; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setInfoLogLevel - * Signature: (JB)V - */ -void Java_org_rocksdb_DBOptions_setInfoLogLevel(JNIEnv*, jobject, jlong jhandle, - jbyte jlog_level) { - reinterpret_cast(jhandle)->info_log_level = - static_cast(jlog_level); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: infoLogLevel - * Signature: (J)B - */ -jbyte Java_org_rocksdb_DBOptions_infoLogLevel(JNIEnv*, jobject, jlong jhandle) { - return static_cast( - reinterpret_cast(jhandle)->info_log_level); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxTotalWalSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setMaxTotalWalSize(JNIEnv*, jobject, - jlong jhandle, - jlong jmax_total_wal_size) { - reinterpret_cast(jhandle)->max_total_wal_size = - static_cast(jmax_total_wal_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxTotalWalSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_maxTotalWalSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_total_wal_size; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxOpenFiles - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxOpenFiles(JNIEnv*, jobject, jlong jhandle, - jint max_open_files) { - reinterpret_cast(jhandle)->max_open_files = - static_cast(max_open_files); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxOpenFiles - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxOpenFiles(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_open_files; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxFileOpeningThreads - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxFileOpeningThreads( - JNIEnv*, jobject, jlong jhandle, jint jmax_file_opening_threads) { - reinterpret_cast(jhandle) - ->max_file_opening_threads = static_cast(jmax_file_opening_threads); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxFileOpeningThreads - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxFileOpeningThreads(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_file_opening_threads); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setStatistics - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setStatistics(JNIEnv*, jobject, jlong jhandle, - jlong jstatistics_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* pSptr = - reinterpret_cast*>( - jstatistics_handle); - opt->statistics = *pSptr; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: statistics - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_statistics(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - std::shared_ptr sptr = opt->statistics; - if (sptr == nullptr) { - return 0; - } else { - std::shared_ptr* pSptr = - new std::shared_ptr(sptr); - return GET_CPLUSPLUS_POINTER(pSptr); - } -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setUseFsync - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setUseFsync(JNIEnv*, jobject, jlong jhandle, - jboolean use_fsync) { - reinterpret_cast(jhandle)->use_fsync = - static_cast(use_fsync); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: useFsync - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_useFsync(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->use_fsync; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setDbPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_DBOptions_setDbPaths(JNIEnv* env, jobject, jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - std::vector db_paths; - jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr); - if (ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - jboolean has_exception = JNI_FALSE; - const jsize len = env->GetArrayLength(jpaths); - for (jsize i = 0; i < len; i++) { - jobject jpath = - reinterpret_cast(env->GetObjectArrayElement(jpaths, i)); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - std::string path = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, static_cast(jpath), &has_exception); - env->DeleteLocalRef(jpath); - - if (has_exception == JNI_TRUE) { - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - - jlong jtarget_size = ptr_jtarget_size[i]; - - db_paths.push_back( - ROCKSDB_NAMESPACE::DbPath(path, static_cast(jtarget_size))); - } - - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - - auto* opt = reinterpret_cast(jhandle); - opt->db_paths = db_paths; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: dbPathsLen - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_dbPathsLen(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->db_paths.size()); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: dbPaths - * Signature: (J[Ljava/lang/String;[J)V - */ -void Java_org_rocksdb_DBOptions_dbPaths(JNIEnv* env, jobject, jlong jhandle, - jobjectArray jpaths, - jlongArray jtarget_sizes) { - jboolean is_copy; - jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, &is_copy); - if (ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* opt = reinterpret_cast(jhandle); - const jsize len = env->GetArrayLength(jpaths); - for (jsize i = 0; i < len; i++) { - ROCKSDB_NAMESPACE::DbPath db_path = opt->db_paths[i]; - - jstring jpath = env->NewStringUTF(db_path.path.c_str()); - if (jpath == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - env->SetObjectArrayElement(jpaths, i, jpath); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jpath); - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; - } - - ptr_jtarget_size[i] = static_cast(db_path.target_size); - } - - env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, - is_copy == JNI_TRUE ? 0 : JNI_ABORT); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setDbLogDir - * Signature: (JLjava/lang/String)V - */ -void Java_org_rocksdb_DBOptions_setDbLogDir(JNIEnv* env, jobject, jlong jhandle, - jstring jdb_log_dir) { - const char* log_dir = env->GetStringUTFChars(jdb_log_dir, nullptr); - if (log_dir == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - reinterpret_cast(jhandle)->db_log_dir.assign( - log_dir); - env->ReleaseStringUTFChars(jdb_log_dir, log_dir); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: dbLogDir - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_DBOptions_dbLogDir(JNIEnv* env, jobject, - jlong jhandle) { - return env->NewStringUTF( - reinterpret_cast(jhandle) - ->db_log_dir.c_str()); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWalDir - * Signature: (JLjava/lang/String)V - */ -void Java_org_rocksdb_DBOptions_setWalDir(JNIEnv* env, jobject, jlong jhandle, - jstring jwal_dir) { - const char* wal_dir = env->GetStringUTFChars(jwal_dir, 0); - reinterpret_cast(jhandle)->wal_dir.assign( - wal_dir); - env->ReleaseStringUTFChars(jwal_dir, wal_dir); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: walDir - * Signature: (J)Ljava/lang/String - */ -jstring Java_org_rocksdb_DBOptions_walDir(JNIEnv* env, jobject, jlong jhandle) { - return env->NewStringUTF( - reinterpret_cast(jhandle) - ->wal_dir.c_str()); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setDeleteObsoleteFilesPeriodMicros - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setDeleteObsoleteFilesPeriodMicros( - JNIEnv*, jobject, jlong jhandle, jlong micros) { - reinterpret_cast(jhandle) - ->delete_obsolete_files_period_micros = static_cast(micros); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: deleteObsoleteFilesPeriodMicros - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_deleteObsoleteFilesPeriodMicros( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->delete_obsolete_files_period_micros; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxBackgroundCompactions - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxBackgroundCompactions(JNIEnv*, jobject, - jlong jhandle, - jint max) { - reinterpret_cast(jhandle) - ->max_background_compactions = static_cast(max); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxBackgroundCompactions - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxBackgroundCompactions(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_background_compactions; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxSubcompactions - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxSubcompactions(JNIEnv*, jobject, - jlong jhandle, jint max) { - reinterpret_cast(jhandle)->max_subcompactions = - static_cast(max); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxSubcompactions - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxSubcompactions(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_subcompactions; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxBackgroundFlushes - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxBackgroundFlushes( - JNIEnv*, jobject, jlong jhandle, jint max_background_flushes) { - reinterpret_cast(jhandle) - ->max_background_flushes = static_cast(max_background_flushes); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxBackgroundFlushes - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxBackgroundFlushes(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_background_flushes; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxBackgroundJobs - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxBackgroundJobs(JNIEnv*, jobject, - jlong jhandle, - jint max_background_jobs) { - reinterpret_cast(jhandle) - ->max_background_jobs = static_cast(max_background_jobs); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxBackgroundJobs - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxBackgroundJobs(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_background_jobs; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxLogFileSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setMaxLogFileSize(JNIEnv* env, jobject, - jlong jhandle, - jlong max_log_file_size) { - auto s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(max_log_file_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->max_log_file_size = max_log_file_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxLogFileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_maxLogFileSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_log_file_size; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setLogFileTimeToRoll - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setLogFileTimeToRoll( - JNIEnv* env, jobject, jlong jhandle, jlong log_file_time_to_roll) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - log_file_time_to_roll); - if (s.ok()) { - reinterpret_cast(jhandle) - ->log_file_time_to_roll = log_file_time_to_roll; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_DBOptions - * Method: logFileTimeToRoll - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_logFileTimeToRoll(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->log_file_time_to_roll; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setKeepLogFileNum - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setKeepLogFileNum(JNIEnv* env, jobject, - jlong jhandle, - jlong keep_log_file_num) { - auto s = - ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t(keep_log_file_num); - if (s.ok()) { - reinterpret_cast(jhandle) - ->keep_log_file_num = keep_log_file_num; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_DBOptions - * Method: keepLogFileNum - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_keepLogFileNum(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->keep_log_file_num; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setRecycleLogFileNum - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setRecycleLogFileNum( - JNIEnv* env, jobject, jlong jhandle, jlong recycle_log_file_num) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - recycle_log_file_num); - if (s.ok()) { - reinterpret_cast(jhandle) - ->recycle_log_file_num = recycle_log_file_num; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_DBOptions - * Method: recycleLogFileNum - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_recycleLogFileNum(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->recycle_log_file_num; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxManifestFileSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setMaxManifestFileSize( - JNIEnv*, jobject, jlong jhandle, jlong max_manifest_file_size) { - reinterpret_cast(jhandle) - ->max_manifest_file_size = static_cast(max_manifest_file_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxManifestFileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_maxManifestFileSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_manifest_file_size; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setTableCacheNumshardbits - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setTableCacheNumshardbits( - JNIEnv*, jobject, jlong jhandle, jint table_cache_numshardbits) { - reinterpret_cast(jhandle) - ->table_cache_numshardbits = static_cast(table_cache_numshardbits); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: tableCacheNumshardbits - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_tableCacheNumshardbits(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->table_cache_numshardbits; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWalTtlSeconds - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWalTtlSeconds(JNIEnv*, jobject, - jlong jhandle, - jlong WAL_ttl_seconds) { - reinterpret_cast(jhandle)->WAL_ttl_seconds = - static_cast(WAL_ttl_seconds); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: walTtlSeconds - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_walTtlSeconds(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->WAL_ttl_seconds; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWalSizeLimitMB - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWalSizeLimitMB(JNIEnv*, jobject, - jlong jhandle, - jlong WAL_size_limit_MB) { - reinterpret_cast(jhandle)->WAL_size_limit_MB = - static_cast(WAL_size_limit_MB); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: walTtlSeconds - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_walSizeLimitMB(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->WAL_size_limit_MB; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxWriteBatchGroupSizeBytes - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setMaxWriteBatchGroupSizeBytes( - JNIEnv*, jclass, jlong jhandle, jlong jmax_write_batch_group_size_bytes) { - auto* opt = reinterpret_cast(jhandle); - opt->max_write_batch_group_size_bytes = - static_cast(jmax_write_batch_group_size_bytes); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxWriteBatchGroupSizeBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_maxWriteBatchGroupSizeBytes(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_write_batch_group_size_bytes); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setManifestPreallocationSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setManifestPreallocationSize( - JNIEnv* env, jobject, jlong jhandle, jlong preallocation_size) { - auto s = ROCKSDB_NAMESPACE::JniUtil::check_if_jlong_fits_size_t( - preallocation_size); - if (s.ok()) { - reinterpret_cast(jhandle) - ->manifest_preallocation_size = preallocation_size; - } else { - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_DBOptions - * Method: manifestPreallocationSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_manifestPreallocationSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->manifest_preallocation_size; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: useDirectReads - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_useDirectReads(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->use_direct_reads; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setUseDirectReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setUseDirectReads(JNIEnv*, jobject, - jlong jhandle, - jboolean use_direct_reads) { - reinterpret_cast(jhandle)->use_direct_reads = - static_cast(use_direct_reads); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: useDirectIoForFlushAndCompaction - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_useDirectIoForFlushAndCompaction( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->use_direct_io_for_flush_and_compaction; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setUseDirectReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setUseDirectIoForFlushAndCompaction( - JNIEnv*, jobject, jlong jhandle, - jboolean use_direct_io_for_flush_and_compaction) { - reinterpret_cast(jhandle) - ->use_direct_io_for_flush_and_compaction = - static_cast(use_direct_io_for_flush_and_compaction); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAllowFAllocate - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAllowFAllocate(JNIEnv*, jobject, - jlong jhandle, - jboolean jallow_fallocate) { - reinterpret_cast(jhandle)->allow_fallocate = - static_cast(jallow_fallocate); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: allowFAllocate - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_allowFAllocate(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->allow_fallocate); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAllowMmapReads - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAllowMmapReads(JNIEnv*, jobject, - jlong jhandle, - jboolean allow_mmap_reads) { - reinterpret_cast(jhandle)->allow_mmap_reads = - static_cast(allow_mmap_reads); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: allowMmapReads - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_allowMmapReads(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->allow_mmap_reads; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAllowMmapWrites - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAllowMmapWrites(JNIEnv*, jobject, - jlong jhandle, - jboolean allow_mmap_writes) { - reinterpret_cast(jhandle)->allow_mmap_writes = - static_cast(allow_mmap_writes); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: allowMmapWrites - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_allowMmapWrites(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->allow_mmap_writes; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setIsFdCloseOnExec - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setIsFdCloseOnExec( - JNIEnv*, jobject, jlong jhandle, jboolean is_fd_close_on_exec) { - reinterpret_cast(jhandle) - ->is_fd_close_on_exec = static_cast(is_fd_close_on_exec); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: isFdCloseOnExec - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_isFdCloseOnExec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->is_fd_close_on_exec; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setStatsDumpPeriodSec - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setStatsDumpPeriodSec( - JNIEnv*, jobject, jlong jhandle, jint jstats_dump_period_sec) { - reinterpret_cast(jhandle) - ->stats_dump_period_sec = - static_cast(jstats_dump_period_sec); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: statsDumpPeriodSec - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_statsDumpPeriodSec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->stats_dump_period_sec; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setStatsPersistPeriodSec - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setStatsPersistPeriodSec( - JNIEnv*, jobject, jlong jhandle, jint jstats_persist_period_sec) { - reinterpret_cast(jhandle) - ->stats_persist_period_sec = - static_cast(jstats_persist_period_sec); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: statsPersistPeriodSec - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_statsPersistPeriodSec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->stats_persist_period_sec; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setStatsHistoryBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setStatsHistoryBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jstats_history_buffer_size) { - reinterpret_cast(jhandle) - ->stats_history_buffer_size = - static_cast(jstats_history_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: statsHistoryBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_statsHistoryBufferSize(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->stats_history_buffer_size; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAdviseRandomOnOpen - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAdviseRandomOnOpen( - JNIEnv*, jobject, jlong jhandle, jboolean advise_random_on_open) { - reinterpret_cast(jhandle) - ->advise_random_on_open = static_cast(advise_random_on_open); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: adviseRandomOnOpen - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_adviseRandomOnOpen(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->advise_random_on_open; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setDbWriteBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setDbWriteBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jdb_write_buffer_size) { - auto* opt = reinterpret_cast(jhandle); - opt->db_write_buffer_size = static_cast(jdb_write_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWriteBufferManager - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWriteBufferManager( - JNIEnv*, jobject, jlong jdb_options_handle, - jlong jwrite_buffer_manager_handle) { - auto* write_buffer_manager = - reinterpret_cast*>( - jwrite_buffer_manager_handle); - reinterpret_cast(jdb_options_handle) - ->write_buffer_manager = *write_buffer_manager; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: dbWriteBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_dbWriteBufferSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->db_write_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAccessHintOnCompactionStart - * Signature: (JB)V - */ -void Java_org_rocksdb_DBOptions_setAccessHintOnCompactionStart( - JNIEnv*, jobject, jlong jhandle, jbyte jaccess_hint_value) { - auto* opt = reinterpret_cast(jhandle); - opt->access_hint_on_compaction_start = - ROCKSDB_NAMESPACE::AccessHintJni::toCppAccessHint(jaccess_hint_value); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: accessHintOnCompactionStart - * Signature: (J)B - */ -jbyte Java_org_rocksdb_DBOptions_accessHintOnCompactionStart(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::AccessHintJni::toJavaAccessHint( - opt->access_hint_on_compaction_start); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setCompactionReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setCompactionReadaheadSize( - JNIEnv*, jobject, jlong jhandle, jlong jcompaction_readahead_size) { - auto* opt = reinterpret_cast(jhandle); - opt->compaction_readahead_size = - static_cast(jcompaction_readahead_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: compactionReadaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_compactionReadaheadSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->compaction_readahead_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setRandomAccessMaxBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setRandomAccessMaxBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jrandom_access_max_buffer_size) { - auto* opt = reinterpret_cast(jhandle); - opt->random_access_max_buffer_size = - static_cast(jrandom_access_max_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: randomAccessMaxBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_randomAccessMaxBufferSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->random_access_max_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWritableFileMaxBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWritableFileMaxBufferSize( - JNIEnv*, jobject, jlong jhandle, jlong jwritable_file_max_buffer_size) { - auto* opt = reinterpret_cast(jhandle); - opt->writable_file_max_buffer_size = - static_cast(jwritable_file_max_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: writableFileMaxBufferSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_writableFileMaxBufferSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->writable_file_max_buffer_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setUseAdaptiveMutex - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setUseAdaptiveMutex( - JNIEnv*, jobject, jlong jhandle, jboolean use_adaptive_mutex) { - reinterpret_cast(jhandle)->use_adaptive_mutex = - static_cast(use_adaptive_mutex); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: useAdaptiveMutex - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_useAdaptiveMutex(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->use_adaptive_mutex; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setBytesPerSync - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setBytesPerSync(JNIEnv*, jobject, jlong jhandle, - jlong bytes_per_sync) { - reinterpret_cast(jhandle)->bytes_per_sync = - static_cast(bytes_per_sync); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: bytesPerSync - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_bytesPerSync(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->bytes_per_sync; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWalBytesPerSync - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWalBytesPerSync(JNIEnv*, jobject, - jlong jhandle, - jlong jwal_bytes_per_sync) { - reinterpret_cast(jhandle)->wal_bytes_per_sync = - static_cast(jwal_bytes_per_sync); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: walBytesPerSync - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_walBytesPerSync(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->wal_bytes_per_sync); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setStrictBytesPerSync - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setStrictBytesPerSync( - JNIEnv*, jobject, jlong jhandle, jboolean jstrict_bytes_per_sync) { - reinterpret_cast(jhandle) - ->strict_bytes_per_sync = jstrict_bytes_per_sync == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: strictBytesPerSync - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_strictBytesPerSync(JNIEnv*, jobject, - jlong jhandle) { - return static_cast( - reinterpret_cast(jhandle) - ->strict_bytes_per_sync); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setEventListeners - * Signature: (J[J)V - */ -void Java_org_rocksdb_DBOptions_setEventListeners(JNIEnv* env, jclass, - jlong jhandle, - jlongArray jlistener_array) { - auto* opt = reinterpret_cast(jhandle); - rocksdb_set_event_listeners_helper(env, jlistener_array, opt->listeners); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: eventListeners - * Signature: (J)[Lorg/rocksdb/AbstractEventListener; - */ -jobjectArray Java_org_rocksdb_DBOptions_eventListeners(JNIEnv* env, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return rocksdb_get_event_listeners_helper(env, opt->listeners); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setDelayedWriteRate - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setDelayedWriteRate(JNIEnv*, jobject, - jlong jhandle, - jlong jdelayed_write_rate) { - auto* opt = reinterpret_cast(jhandle); - opt->delayed_write_rate = static_cast(jdelayed_write_rate); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: delayedWriteRate - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_delayedWriteRate(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->delayed_write_rate); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setEnablePipelinedWrite - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setEnablePipelinedWrite( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_pipelined_write) { - auto* opt = reinterpret_cast(jhandle); - opt->enable_pipelined_write = jenable_pipelined_write == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: enablePipelinedWrite - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_enablePipelinedWrite(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->enable_pipelined_write); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setUnorderedWrite - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setUnorderedWrite(JNIEnv*, jobject, - jlong jhandle, - jboolean junordered_write) { - auto* opt = reinterpret_cast(jhandle); - opt->unordered_write = junordered_write == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: unorderedWrite - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_unorderedWrite(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->unordered_write); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setEnableThreadTracking - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setEnableThreadTracking( - JNIEnv*, jobject, jlong jhandle, jboolean jenable_thread_tracking) { - auto* opt = reinterpret_cast(jhandle); - opt->enable_thread_tracking = jenable_thread_tracking == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: enableThreadTracking - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_enableThreadTracking(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->enable_thread_tracking); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAllowConcurrentMemtableWrite - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAllowConcurrentMemtableWrite( - JNIEnv*, jobject, jlong jhandle, jboolean allow) { - reinterpret_cast(jhandle) - ->allow_concurrent_memtable_write = static_cast(allow); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: allowConcurrentMemtableWrite - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_allowConcurrentMemtableWrite( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->allow_concurrent_memtable_write; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setEnableWriteThreadAdaptiveYield - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setEnableWriteThreadAdaptiveYield( - JNIEnv*, jobject, jlong jhandle, jboolean yield) { - reinterpret_cast(jhandle) - ->enable_write_thread_adaptive_yield = static_cast(yield); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: enableWriteThreadAdaptiveYield - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_enableWriteThreadAdaptiveYield( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->enable_write_thread_adaptive_yield; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWriteThreadMaxYieldUsec - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWriteThreadMaxYieldUsec(JNIEnv*, jobject, - jlong jhandle, - jlong max) { - reinterpret_cast(jhandle) - ->write_thread_max_yield_usec = static_cast(max); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: writeThreadMaxYieldUsec - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_writeThreadMaxYieldUsec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->write_thread_max_yield_usec; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWriteThreadSlowYieldUsec - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWriteThreadSlowYieldUsec(JNIEnv*, jobject, - jlong jhandle, - jlong slow) { - reinterpret_cast(jhandle) - ->write_thread_slow_yield_usec = static_cast(slow); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: writeThreadSlowYieldUsec - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_writeThreadSlowYieldUsec(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->write_thread_slow_yield_usec; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setSkipStatsUpdateOnDbOpen - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setSkipStatsUpdateOnDbOpen( - JNIEnv*, jobject, jlong jhandle, jboolean jskip_stats_update_on_db_open) { - auto* opt = reinterpret_cast(jhandle); - opt->skip_stats_update_on_db_open = - static_cast(jskip_stats_update_on_db_open); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: skipStatsUpdateOnDbOpen - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_skipStatsUpdateOnDbOpen(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->skip_stats_update_on_db_open); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setSkipCheckingSstFileSizesOnDbOpen - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setSkipCheckingSstFileSizesOnDbOpen( - JNIEnv*, jclass, jlong jhandle, - jboolean jskip_checking_sst_file_sizes_on_db_open) { - auto* opt = reinterpret_cast(jhandle); - opt->skip_checking_sst_file_sizes_on_db_open = - static_cast(jskip_checking_sst_file_sizes_on_db_open); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: skipCheckingSstFileSizesOnDbOpen - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_skipCheckingSstFileSizesOnDbOpen( - JNIEnv*, jclass, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->skip_checking_sst_file_sizes_on_db_open); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWalRecoveryMode - * Signature: (JB)V - */ -void Java_org_rocksdb_DBOptions_setWalRecoveryMode( - JNIEnv*, jobject, jlong jhandle, jbyte jwal_recovery_mode_value) { - auto* opt = reinterpret_cast(jhandle); - opt->wal_recovery_mode = - ROCKSDB_NAMESPACE::WALRecoveryModeJni::toCppWALRecoveryMode( - jwal_recovery_mode_value); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: walRecoveryMode - * Signature: (J)B - */ -jbyte Java_org_rocksdb_DBOptions_walRecoveryMode(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::WALRecoveryModeJni::toJavaWALRecoveryMode( - opt->wal_recovery_mode); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAllow2pc - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAllow2pc(JNIEnv*, jobject, jlong jhandle, - jboolean jallow_2pc) { - auto* opt = reinterpret_cast(jhandle); - opt->allow_2pc = static_cast(jallow_2pc); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: allow2pc - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_allow2pc(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->allow_2pc); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setRowCache - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setRowCache(JNIEnv*, jobject, jlong jhandle, - jlong jrow_cache_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* row_cache = - reinterpret_cast*>( - jrow_cache_handle); - opt->row_cache = *row_cache; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWalFilter - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setWalFilter(JNIEnv*, jobject, jlong jhandle, - jlong jwal_filter_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* wal_filter = reinterpret_cast( - jwal_filter_handle); - opt->wal_filter = wal_filter; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setFailIfOptionsFileError - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setFailIfOptionsFileError( - JNIEnv*, jobject, jlong jhandle, jboolean jfail_if_options_file_error) { - auto* opt = reinterpret_cast(jhandle); - opt->fail_if_options_file_error = - static_cast(jfail_if_options_file_error); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: failIfOptionsFileError - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_failIfOptionsFileError(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->fail_if_options_file_error); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setDumpMallocStats - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setDumpMallocStats( - JNIEnv*, jobject, jlong jhandle, jboolean jdump_malloc_stats) { - auto* opt = reinterpret_cast(jhandle); - opt->dump_malloc_stats = static_cast(jdump_malloc_stats); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: dumpMallocStats - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_dumpMallocStats(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->dump_malloc_stats); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAvoidFlushDuringRecovery - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAvoidFlushDuringRecovery( - JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_recovery) { - auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_recovery = - static_cast(javoid_flush_during_recovery); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: avoidFlushDuringRecovery - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_avoidFlushDuringRecovery(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->avoid_flush_during_recovery); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAllowIngestBehind - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAllowIngestBehind( - JNIEnv*, jobject, jlong jhandle, jboolean jallow_ingest_behind) { - auto* opt = reinterpret_cast(jhandle); - opt->allow_ingest_behind = jallow_ingest_behind == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: allowIngestBehind - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_allowIngestBehind(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->allow_ingest_behind); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setTwoWriteQueues - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setTwoWriteQueues(JNIEnv*, jobject, - jlong jhandle, - jboolean jtwo_write_queues) { - auto* opt = reinterpret_cast(jhandle); - opt->two_write_queues = jtwo_write_queues == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: twoWriteQueues - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_twoWriteQueues(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->two_write_queues); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setManualWalFlush - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setManualWalFlush(JNIEnv*, jobject, - jlong jhandle, - jboolean jmanual_wal_flush) { - auto* opt = reinterpret_cast(jhandle); - opt->manual_wal_flush = jmanual_wal_flush == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: manualWalFlush - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_manualWalFlush(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->manual_wal_flush); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAtomicFlush - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAtomicFlush(JNIEnv*, jobject, jlong jhandle, - jboolean jatomic_flush) { - auto* opt = reinterpret_cast(jhandle); - opt->atomic_flush = jatomic_flush == JNI_TRUE; -} - -/* - * Class: org_rocksdb_DBOptions - * Method: atomicFlush - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_atomicFlush(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->atomic_flush); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAvoidFlushDuringShutdown - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAvoidFlushDuringShutdown( - JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_shutdown) { - auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_shutdown = - static_cast(javoid_flush_during_shutdown); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: avoidFlushDuringShutdown - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_avoidFlushDuringShutdown(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->avoid_flush_during_shutdown); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setAvoidUnnecessaryBlockingIO - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setAvoidUnnecessaryBlockingIO( - JNIEnv*, jclass, jlong jhandle, jboolean avoid_blocking_io) { - auto* opt = reinterpret_cast(jhandle); - opt->avoid_unnecessary_blocking_io = static_cast(avoid_blocking_io); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: avoidUnnecessaryBlockingIO - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_avoidUnnecessaryBlockingIO(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->avoid_unnecessary_blocking_io); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setPersistStatsToDisk - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setPersistStatsToDisk( - JNIEnv*, jclass, jlong jhandle, jboolean persist_stats_to_disk) { - auto* opt = reinterpret_cast(jhandle); - opt->persist_stats_to_disk = static_cast(persist_stats_to_disk); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: persistStatsToDisk - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_persistStatsToDisk(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->persist_stats_to_disk); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setWriteDbidToManifest - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setWriteDbidToManifest( - JNIEnv*, jclass, jlong jhandle, jboolean jwrite_dbid_to_manifest) { - auto* opt = reinterpret_cast(jhandle); - opt->write_dbid_to_manifest = static_cast(jwrite_dbid_to_manifest); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: writeDbidToManifest - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_writeDbidToManifest(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->write_dbid_to_manifest); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setLogReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setLogReadaheadSize(JNIEnv*, jclass, - jlong jhandle, - jlong jlog_readahead_size) { - auto* opt = reinterpret_cast(jhandle); - opt->log_readahead_size = static_cast(jlog_readahead_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: logReasaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_logReadaheadSize(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->log_readahead_size); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setBestEffortsRecovery - * Signature: (JZ)V - */ -void Java_org_rocksdb_DBOptions_setBestEffortsRecovery( - JNIEnv*, jclass, jlong jhandle, jboolean jbest_efforts_recovery) { - auto* opt = reinterpret_cast(jhandle); - opt->best_efforts_recovery = static_cast(jbest_efforts_recovery); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: bestEffortsRecovery - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_DBOptions_bestEffortsRecovery(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->best_efforts_recovery); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setMaxBgErrorResumeCount - * Signature: (JI)V - */ -void Java_org_rocksdb_DBOptions_setMaxBgErrorResumeCount( - JNIEnv*, jclass, jlong jhandle, jint jmax_bgerror_resume_count) { - auto* opt = reinterpret_cast(jhandle); - opt->max_bgerror_resume_count = static_cast(jmax_bgerror_resume_count); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: maxBgerrorResumeCount - * Signature: (J)I - */ -jint Java_org_rocksdb_DBOptions_maxBgerrorResumeCount(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_bgerror_resume_count); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: setBgerrorResumeRetryInterval - * Signature: (JJ)V - */ -void Java_org_rocksdb_DBOptions_setBgerrorResumeRetryInterval( - JNIEnv*, jclass, jlong jhandle, jlong jbgerror_resume_retry_interval) { - auto* opt = reinterpret_cast(jhandle); - opt->bgerror_resume_retry_interval = - static_cast(jbgerror_resume_retry_interval); -} - -/* - * Class: org_rocksdb_DBOptions - * Method: bgerrorResumeRetryInterval - * Signature: (J)J - */ -jlong Java_org_rocksdb_DBOptions_bgerrorResumeRetryInterval(JNIEnv*, jclass, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->bgerror_resume_retry_interval); -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::WriteOptions - -/* - * Class: org_rocksdb_WriteOptions - * Method: newWriteOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_WriteOptions_newWriteOptions(JNIEnv*, jclass) { - auto* op = new ROCKSDB_NAMESPACE::WriteOptions(); - return GET_CPLUSPLUS_POINTER(op); -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: copyWriteOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_WriteOptions_copyWriteOptions(JNIEnv*, jclass, - jlong jhandle) { - auto new_opt = new ROCKSDB_NAMESPACE::WriteOptions( - *(reinterpret_cast(jhandle))); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: disposeInternal - * Signature: ()V - */ -void Java_org_rocksdb_WriteOptions_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* write_options = - reinterpret_cast(jhandle); - assert(write_options != nullptr); - delete write_options; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: setSync - * Signature: (JZ)V - */ -void Java_org_rocksdb_WriteOptions_setSync(JNIEnv*, jobject, jlong jhandle, - jboolean jflag) { - reinterpret_cast(jhandle)->sync = jflag; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: sync - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteOptions_sync(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->sync; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: setDisableWAL - * Signature: (JZ)V - */ -void Java_org_rocksdb_WriteOptions_setDisableWAL(JNIEnv*, jobject, - jlong jhandle, - jboolean jflag) { - reinterpret_cast(jhandle)->disableWAL = - jflag; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: disableWAL - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteOptions_disableWAL(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->disableWAL; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: setIgnoreMissingColumnFamilies - * Signature: (JZ)V - */ -void Java_org_rocksdb_WriteOptions_setIgnoreMissingColumnFamilies( - JNIEnv*, jobject, jlong jhandle, jboolean jignore_missing_column_families) { - reinterpret_cast(jhandle) - ->ignore_missing_column_families = - static_cast(jignore_missing_column_families); -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: ignoreMissingColumnFamilies - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteOptions_ignoreMissingColumnFamilies( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->ignore_missing_column_families; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: setNoSlowdown - * Signature: (JZ)V - */ -void Java_org_rocksdb_WriteOptions_setNoSlowdown(JNIEnv*, jobject, - jlong jhandle, - jboolean jno_slowdown) { - reinterpret_cast(jhandle)->no_slowdown = - static_cast(jno_slowdown); -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: noSlowdown - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteOptions_noSlowdown(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->no_slowdown; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: setLowPri - * Signature: (JZ)V - */ -void Java_org_rocksdb_WriteOptions_setLowPri(JNIEnv*, jobject, jlong jhandle, - jboolean jlow_pri) { - reinterpret_cast(jhandle)->low_pri = - static_cast(jlow_pri); -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: lowPri - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteOptions_lowPri(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->low_pri; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: memtableInsertHintPerBatch - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteOptions_memtableInsertHintPerBatch( - JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle) - ->memtable_insert_hint_per_batch; -} - -/* - * Class: org_rocksdb_WriteOptions - * Method: setMemtableInsertHintPerBatch - * Signature: (JZ)V - */ -void Java_org_rocksdb_WriteOptions_setMemtableInsertHintPerBatch( - JNIEnv*, jobject, jlong jhandle, jboolean jmemtable_insert_hint_per_batch) { - reinterpret_cast(jhandle) - ->memtable_insert_hint_per_batch = - static_cast(jmemtable_insert_hint_per_batch); -} - -///////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::ReadOptions - -/* - * Class: org_rocksdb_ReadOptions - * Method: newReadOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_ReadOptions_newReadOptions__(JNIEnv*, jclass) { - auto* read_options = new ROCKSDB_NAMESPACE::ReadOptions(); - return GET_CPLUSPLUS_POINTER(read_options); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: newReadOptions - * Signature: (ZZ)J - */ -jlong Java_org_rocksdb_ReadOptions_newReadOptions__ZZ( - JNIEnv*, jclass, jboolean jverify_checksums, jboolean jfill_cache) { - auto* read_options = new ROCKSDB_NAMESPACE::ReadOptions( - static_cast(jverify_checksums), static_cast(jfill_cache)); - return GET_CPLUSPLUS_POINTER(read_options); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: copyReadOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_copyReadOptions(JNIEnv*, jclass, - jlong jhandle) { - auto new_opt = new ROCKSDB_NAMESPACE::ReadOptions( - *(reinterpret_cast(jhandle))); - return GET_CPLUSPLUS_POINTER(new_opt); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ReadOptions_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* read_options = - reinterpret_cast(jhandle); - assert(read_options != nullptr); - delete read_options; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setVerifyChecksums - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setVerifyChecksums( - JNIEnv*, jobject, jlong jhandle, jboolean jverify_checksums) { - reinterpret_cast(jhandle)->verify_checksums = - static_cast(jverify_checksums); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: verifyChecksums - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_verifyChecksums(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->verify_checksums; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setFillCache - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setFillCache(JNIEnv*, jobject, jlong jhandle, - jboolean jfill_cache) { - reinterpret_cast(jhandle)->fill_cache = - static_cast(jfill_cache); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: fillCache - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_fillCache(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle)->fill_cache; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setTailing - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setTailing(JNIEnv*, jobject, jlong jhandle, - jboolean jtailing) { - reinterpret_cast(jhandle)->tailing = - static_cast(jtailing); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: tailing - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_tailing(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->tailing; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: managed - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_managed(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->managed; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setManaged - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setManaged(JNIEnv*, jobject, jlong jhandle, - jboolean jmanaged) { - reinterpret_cast(jhandle)->managed = - static_cast(jmanaged); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: totalOrderSeek - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_totalOrderSeek(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->total_order_seek; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setTotalOrderSeek - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setTotalOrderSeek( - JNIEnv*, jobject, jlong jhandle, jboolean jtotal_order_seek) { - reinterpret_cast(jhandle)->total_order_seek = - static_cast(jtotal_order_seek); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: prefixSameAsStart - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_prefixSameAsStart(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle) - ->prefix_same_as_start; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setPrefixSameAsStart - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setPrefixSameAsStart( - JNIEnv*, jobject, jlong jhandle, jboolean jprefix_same_as_start) { - reinterpret_cast(jhandle) - ->prefix_same_as_start = static_cast(jprefix_same_as_start); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: pinData - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_pinData(JNIEnv*, jobject, jlong jhandle) { - return reinterpret_cast(jhandle)->pin_data; -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setPinData - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setPinData(JNIEnv*, jobject, jlong jhandle, - jboolean jpin_data) { - reinterpret_cast(jhandle)->pin_data = - static_cast(jpin_data); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: backgroundPurgeOnIteratorCleanup - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_backgroundPurgeOnIteratorCleanup( - JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->background_purge_on_iterator_cleanup); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setBackgroundPurgeOnIteratorCleanup - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setBackgroundPurgeOnIteratorCleanup( - JNIEnv*, jobject, jlong jhandle, - jboolean jbackground_purge_on_iterator_cleanup) { - auto* opt = reinterpret_cast(jhandle); - opt->background_purge_on_iterator_cleanup = - static_cast(jbackground_purge_on_iterator_cleanup); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: readaheadSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_readaheadSize(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->readahead_size); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setReadaheadSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setReadaheadSize(JNIEnv*, jobject, - jlong jhandle, - jlong jreadahead_size) { - auto* opt = reinterpret_cast(jhandle); - opt->readahead_size = static_cast(jreadahead_size); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: maxSkippableInternalKeys - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_maxSkippableInternalKeys(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->max_skippable_internal_keys); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setMaxSkippableInternalKeys - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setMaxSkippableInternalKeys( - JNIEnv*, jobject, jlong jhandle, jlong jmax_skippable_internal_keys) { - auto* opt = reinterpret_cast(jhandle); - opt->max_skippable_internal_keys = - static_cast(jmax_skippable_internal_keys); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: ignoreRangeDeletions - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_ignoreRangeDeletions(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->ignore_range_deletions); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setIgnoreRangeDeletions - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setIgnoreRangeDeletions( - JNIEnv*, jobject, jlong jhandle, jboolean jignore_range_deletions) { - auto* opt = reinterpret_cast(jhandle); - opt->ignore_range_deletions = static_cast(jignore_range_deletions); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setSnapshot - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setSnapshot(JNIEnv*, jobject, jlong jhandle, - jlong jsnapshot) { - reinterpret_cast(jhandle)->snapshot = - reinterpret_cast(jsnapshot); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: snapshot - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_snapshot(JNIEnv*, jobject, jlong jhandle) { - auto& snapshot = - reinterpret_cast(jhandle)->snapshot; - return GET_CPLUSPLUS_POINTER(snapshot); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: readTier - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ReadOptions_readTier(JNIEnv*, jobject, jlong jhandle) { - return static_cast( - reinterpret_cast(jhandle)->read_tier); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setReadTier - * Signature: (JB)V - */ -void Java_org_rocksdb_ReadOptions_setReadTier(JNIEnv*, jobject, jlong jhandle, - jbyte jread_tier) { - reinterpret_cast(jhandle)->read_tier = - static_cast(jread_tier); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setIterateUpperBound - * Signature: (JJ)I - */ -void Java_org_rocksdb_ReadOptions_setIterateUpperBound( - JNIEnv*, jobject, jlong jhandle, jlong jupper_bound_slice_handle) { - reinterpret_cast(jhandle) - ->iterate_upper_bound = - reinterpret_cast(jupper_bound_slice_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: iterateUpperBound - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_iterateUpperBound(JNIEnv*, jobject, - jlong jhandle) { - auto& upper_bound_slice_handle = - reinterpret_cast(jhandle) - ->iterate_upper_bound; - return GET_CPLUSPLUS_POINTER(upper_bound_slice_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setIterateLowerBound - * Signature: (JJ)I - */ -void Java_org_rocksdb_ReadOptions_setIterateLowerBound( - JNIEnv*, jobject, jlong jhandle, jlong jlower_bound_slice_handle) { - reinterpret_cast(jhandle) - ->iterate_lower_bound = - reinterpret_cast(jlower_bound_slice_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: iterateLowerBound - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_iterateLowerBound(JNIEnv*, jobject, - jlong jhandle) { - auto& lower_bound_slice_handle = - reinterpret_cast(jhandle) - ->iterate_lower_bound; - return GET_CPLUSPLUS_POINTER(lower_bound_slice_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setTableFilter - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setTableFilter( - JNIEnv*, jobject, jlong jhandle, jlong jjni_table_filter_handle) { - auto* opt = reinterpret_cast(jhandle); - auto* jni_table_filter = - reinterpret_cast( - jjni_table_filter_handle); - opt->table_filter = jni_table_filter->GetTableFilterFunction(); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: autoPrefixMode - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ReadOptions_autoPrefixMode(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->auto_prefix_mode); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setAutoPrefixMode - * Signature: (JZ)V - */ -void Java_org_rocksdb_ReadOptions_setAutoPrefixMode( - JNIEnv*, jobject, jlong jhandle, jboolean jauto_prefix_mode) { - auto* opt = reinterpret_cast(jhandle); - opt->auto_prefix_mode = static_cast(jauto_prefix_mode); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: timestamp - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_timestamp(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - auto& timestamp_slice_handle = opt->timestamp; - return reinterpret_cast(timestamp_slice_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setTimestamp - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setTimestamp(JNIEnv*, jobject, jlong jhandle, - jlong jtimestamp_slice_handle) { - auto* opt = reinterpret_cast(jhandle); - opt->timestamp = - reinterpret_cast(jtimestamp_slice_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: iterStartTs - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_iterStartTs(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - auto& iter_start_ts_handle = opt->iter_start_ts; - return reinterpret_cast(iter_start_ts_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setIterStartTs - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setIterStartTs(JNIEnv*, jobject, - jlong jhandle, - jlong jiter_start_ts_handle) { - auto* opt = reinterpret_cast(jhandle); - opt->iter_start_ts = - reinterpret_cast(jiter_start_ts_handle); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: deadline - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_deadline(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->deadline.count()); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setDeadline - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setDeadline(JNIEnv*, jobject, jlong jhandle, - jlong jdeadline) { - auto* opt = reinterpret_cast(jhandle); - opt->deadline = std::chrono::microseconds(static_cast(jdeadline)); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: ioTimeout - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_ioTimeout(JNIEnv*, jobject, jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->io_timeout.count()); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setIoTimeout - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setIoTimeout(JNIEnv*, jobject, jlong jhandle, - jlong jio_timeout) { - auto* opt = reinterpret_cast(jhandle); - opt->io_timeout = - std::chrono::microseconds(static_cast(jio_timeout)); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: valueSizeSofLimit - * Signature: (J)J - */ -jlong Java_org_rocksdb_ReadOptions_valueSizeSoftLimit(JNIEnv*, jobject, - jlong jhandle) { - auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->value_size_soft_limit); -} - -/* - * Class: org_rocksdb_ReadOptions - * Method: setValueSizeSofLimit - * Signature: (JJ)V - */ -void Java_org_rocksdb_ReadOptions_setValueSizeSoftLimit( - JNIEnv*, jobject, jlong jhandle, jlong jvalue_size_soft_limit) { - auto* opt = reinterpret_cast(jhandle); - opt->value_size_soft_limit = static_cast(jvalue_size_soft_limit); -} - -///////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::ComparatorOptions - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: newComparatorOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_ComparatorOptions_newComparatorOptions(JNIEnv*, jclass) { - auto* comparator_opt = new ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions(); - return GET_CPLUSPLUS_POINTER(comparator_opt); -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: reusedSynchronisationType - * Signature: (J)B - */ -jbyte Java_org_rocksdb_ComparatorOptions_reusedSynchronisationType( - JNIEnv*, jobject, jlong jhandle) { - auto* comparator_opt = - reinterpret_cast( - jhandle); - return ROCKSDB_NAMESPACE::ReusedSynchronisationTypeJni:: - toJavaReusedSynchronisationType( - comparator_opt->reused_synchronisation_type); -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: setReusedSynchronisationType - * Signature: (JB)V - */ -void Java_org_rocksdb_ComparatorOptions_setReusedSynchronisationType( - JNIEnv*, jobject, jlong jhandle, jbyte jreused_synhcronisation_type) { - auto* comparator_opt = - reinterpret_cast( - jhandle); - comparator_opt->reused_synchronisation_type = - ROCKSDB_NAMESPACE::ReusedSynchronisationTypeJni:: - toCppReusedSynchronisationType(jreused_synhcronisation_type); -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: useDirectBuffer - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_ComparatorOptions_useDirectBuffer(JNIEnv*, jobject, - jlong jhandle) { - return static_cast( - reinterpret_cast( - jhandle) - ->direct_buffer); -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: setUseDirectBuffer - * Signature: (JZ)V - */ -void Java_org_rocksdb_ComparatorOptions_setUseDirectBuffer( - JNIEnv*, jobject, jlong jhandle, jboolean jdirect_buffer) { - reinterpret_cast(jhandle) - ->direct_buffer = jdirect_buffer == JNI_TRUE; -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: maxReusedBufferSize - * Signature: (J)I - */ -jint Java_org_rocksdb_ComparatorOptions_maxReusedBufferSize(JNIEnv*, jobject, - jlong jhandle) { - return static_cast( - reinterpret_cast( - jhandle) - ->max_reused_buffer_size); -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: setMaxReusedBufferSize - * Signature: (JI)V - */ -void Java_org_rocksdb_ComparatorOptions_setMaxReusedBufferSize( - JNIEnv*, jobject, jlong jhandle, jint jmax_reused_buffer_size) { - reinterpret_cast(jhandle) - ->max_reused_buffer_size = static_cast(jmax_reused_buffer_size); -} - -/* - * Class: org_rocksdb_ComparatorOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_ComparatorOptions_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* comparator_opt = - reinterpret_cast( - jhandle); - assert(comparator_opt != nullptr); - delete comparator_opt; -} - -///////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::FlushOptions - -/* - * Class: org_rocksdb_FlushOptions - * Method: newFlushOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_FlushOptions_newFlushOptions(JNIEnv*, jclass) { - auto* flush_opt = new ROCKSDB_NAMESPACE::FlushOptions(); - return GET_CPLUSPLUS_POINTER(flush_opt); -} - -/* - * Class: org_rocksdb_FlushOptions - * Method: setWaitForFlush - * Signature: (JZ)V - */ -void Java_org_rocksdb_FlushOptions_setWaitForFlush(JNIEnv*, jobject, - jlong jhandle, - jboolean jwait) { - reinterpret_cast(jhandle)->wait = - static_cast(jwait); -} - -/* - * Class: org_rocksdb_FlushOptions - * Method: waitForFlush - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_FlushOptions_waitForFlush(JNIEnv*, jobject, - jlong jhandle) { - return reinterpret_cast(jhandle)->wait; -} - -/* - * Class: org_rocksdb_FlushOptions - * Method: setAllowWriteStall - * Signature: (JZ)V - */ -void Java_org_rocksdb_FlushOptions_setAllowWriteStall( - JNIEnv*, jobject, jlong jhandle, jboolean jallow_write_stall) { - auto* flush_options = - reinterpret_cast(jhandle); - flush_options->allow_write_stall = jallow_write_stall == JNI_TRUE; -} - -/* - * Class: org_rocksdb_FlushOptions - * Method: allowWriteStall - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_FlushOptions_allowWriteStall(JNIEnv*, jobject, - jlong jhandle) { - auto* flush_options = - reinterpret_cast(jhandle); - return static_cast(flush_options->allow_write_stall); -} - -/* - * Class: org_rocksdb_FlushOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_FlushOptions_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* flush_opt = reinterpret_cast(jhandle); - assert(flush_opt != nullptr); - delete flush_opt; -} diff --git a/java/rocksjni/options_util.cc b/java/rocksjni/options_util.cc deleted file mode 100644 index c3d7fcef6..000000000 --- a/java/rocksjni/options_util.cc +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::OptionsUtil methods from Java side. - -#include "rocksdb/utilities/options_util.h" - -#include - -#include - -#include "include/org_rocksdb_OptionsUtil.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksjni/portal.h" - -void build_column_family_descriptor_list( - JNIEnv* env, jobject jcfds, - std::vector& cf_descs) { - jmethodID add_mid = ROCKSDB_NAMESPACE::ListJni::getListAddMethodId(env); - if (add_mid == nullptr) { - // exception occurred accessing method - return; - } - - // Column family descriptor - for (ROCKSDB_NAMESPACE::ColumnFamilyDescriptor& cfd : cf_descs) { - // Construct a ColumnFamilyDescriptor java object - jobject jcfd = - ROCKSDB_NAMESPACE::ColumnFamilyDescriptorJni::construct(env, &cfd); - if (env->ExceptionCheck()) { - // exception occurred constructing object - if (jcfd != nullptr) { - env->DeleteLocalRef(jcfd); - } - return; - } - - // Add the object to java list. - jboolean rs = env->CallBooleanMethod(jcfds, add_mid, jcfd); - if (env->ExceptionCheck() || rs == JNI_FALSE) { - // exception occurred calling method, or could not add - if (jcfd != nullptr) { - env->DeleteLocalRef(jcfd); - } - return; - } - } -} - -/* - * Class: org_rocksdb_OptionsUtil - * Method: loadLatestOptions - * Signature: (JLjava/lang/String;JLjava/util/List;)V - */ -void Java_org_rocksdb_OptionsUtil_loadLatestOptions( - JNIEnv* env, jclass /*jcls*/, jlong cfg_handle, jstring jdbpath, - jlong jdb_opts_handle, jobject jcfds) { - jboolean has_exception = JNI_FALSE; - auto db_path = - ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jdbpath, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - std::vector cf_descs; - auto* config_options = - reinterpret_cast(cfg_handle); - auto* db_options = - reinterpret_cast(jdb_opts_handle); - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::LoadLatestOptions( - *config_options, db_path, db_options, &cf_descs); - if (!s.ok()) { - // error, raise an exception - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } else { - build_column_family_descriptor_list(env, jcfds, cf_descs); - } -} - -/* - * Class: org_rocksdb_OptionsUtil - * Method: loadOptionsFromFile - * Signature: (JLjava/lang/String;JLjava/util/List;)V - */ -void Java_org_rocksdb_OptionsUtil_loadOptionsFromFile( - JNIEnv* env, jclass /*jcls*/, jlong cfg_handle, jstring jopts_file_name, - jlong jdb_opts_handle, jobject jcfds) { - jboolean has_exception = JNI_FALSE; - auto opts_file_name = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, jopts_file_name, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - std::vector cf_descs; - auto* config_options = - reinterpret_cast(cfg_handle); - auto* db_options = - reinterpret_cast(jdb_opts_handle); - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::LoadOptionsFromFile( - *config_options, opts_file_name, db_options, &cf_descs); - if (!s.ok()) { - // error, raise an exception - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } else { - build_column_family_descriptor_list(env, jcfds, cf_descs); - } -} - -/* - * Class: org_rocksdb_OptionsUtil - * Method: getLatestOptionsFileName - * Signature: (Ljava/lang/String;J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_OptionsUtil_getLatestOptionsFileName( - JNIEnv* env, jclass /*jcls*/, jstring jdbpath, jlong jenv_handle) { - jboolean has_exception = JNI_FALSE; - auto db_path = - ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jdbpath, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return nullptr; - } - std::string options_file_name; - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::GetLatestOptionsFileName( - db_path, reinterpret_cast(jenv_handle), - &options_file_name); - if (!s.ok()) { - // error, raise an exception - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } else { - return env->NewStringUTF(options_file_name.c_str()); - } -} diff --git a/java/rocksjni/persistent_cache.cc b/java/rocksjni/persistent_cache.cc deleted file mode 100644 index 295d91798..000000000 --- a/java/rocksjni/persistent_cache.cc +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::PersistentCache. - -#include "rocksdb/persistent_cache.h" - -#include - -#include - -#include "include/org_rocksdb_PersistentCache.h" -#include "loggerjnicallback.h" -#include "portal.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_PersistentCache - * Method: newPersistentCache - * Signature: (JLjava/lang/String;JJZ)J - */ -jlong Java_org_rocksdb_PersistentCache_newPersistentCache( - JNIEnv* env, jclass, jlong jenv_handle, jstring jpath, jlong jsz, - jlong jlogger_handle, jboolean joptimized_for_nvm) { - auto* rocks_env = reinterpret_cast(jenv_handle); - jboolean has_exception = JNI_FALSE; - std::string path = - ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jpath, &has_exception); - if (has_exception == JNI_TRUE) { - return 0; - } - auto* logger = - reinterpret_cast*>( - jlogger_handle); - auto* cache = - new std::shared_ptr(nullptr); - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::NewPersistentCache( - rocks_env, path, static_cast(jsz), *logger, - static_cast(joptimized_for_nvm), cache); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } - return GET_CPLUSPLUS_POINTER(cache); -} - -/* - * Class: org_rocksdb_PersistentCache - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_PersistentCache_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* cache = - reinterpret_cast*>( - jhandle); - delete cache; // delete std::shared_ptr -} diff --git a/java/rocksjni/portal.h b/java/rocksjni/portal.h deleted file mode 100644 index ee87f8947..000000000 --- a/java/rocksjni/portal.h +++ /dev/null @@ -1,8686 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -// This file is designed for caching those frequently used IDs and provide -// efficient portal (i.e, a set of static functions) to access java code -// from c++. - -#ifndef JAVA_ROCKSJNI_PORTAL_H_ -#define JAVA_ROCKSJNI_PORTAL_H_ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/rate_limiter.h" -#include "rocksdb/status.h" -#include "rocksdb/table.h" -#include "rocksdb/utilities/backup_engine.h" -#include "rocksdb/utilities/memory_util.h" -#include "rocksdb/utilities/transaction_db.h" -#include "rocksdb/utilities/write_batch_with_index.h" -#include "rocksjni/compaction_filter_factory_jnicallback.h" -#include "rocksjni/comparatorjnicallback.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/event_listener_jnicallback.h" -#include "rocksjni/loggerjnicallback.h" -#include "rocksjni/table_filter_jnicallback.h" -#include "rocksjni/trace_writer_jnicallback.h" -#include "rocksjni/transaction_notifier_jnicallback.h" -#include "rocksjni/wal_filter_jnicallback.h" -#include "rocksjni/writebatchhandlerjnicallback.h" - -// Remove macro on windows -#ifdef DELETE -#undef DELETE -#endif - -namespace ROCKSDB_NAMESPACE { - -class JavaClass { - public: - /** - * Gets and initializes a Java Class - * - * @param env A pointer to the Java environment - * @param jclazz_name The fully qualified JNI name of the Java Class - * e.g. "java/lang/String" - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env, const char* jclazz_name) { - jclass jclazz = env->FindClass(jclazz_name); - assert(jclazz != nullptr); - return jclazz; - } -}; - -// Native class template -template -class RocksDBNativeClass : public JavaClass {}; - -// Native class template for sub-classes of RocksMutableObject -template -class NativeRocksMutableObject : public RocksDBNativeClass { - public: - /** - * Gets the Java Method ID for the - * RocksMutableObject#setNativeHandle(long, boolean) method - * - * @param env A pointer to the Java environment - * @return The Java Method ID or nullptr the RocksMutableObject class cannot - * be accessed, or if one of the NoSuchMethodError, - * ExceptionInInitializerError or OutOfMemoryError exceptions is thrown - */ - static jmethodID getSetNativeHandleMethod(JNIEnv* env) { - static jclass jclazz = DERIVED::getJClass(env); - if (jclazz == nullptr) { - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "setNativeHandle", "(JZ)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Sets the C++ object pointer handle in the Java object - * - * @param env A pointer to the Java environment - * @param jobj The Java object on which to set the pointer handle - * @param ptr The C++ object pointer - * @param java_owns_handle JNI_TRUE if ownership of the C++ object is - * managed by the Java object - * - * @return true if a Java exception is pending, false otherwise - */ - static bool setHandle(JNIEnv* env, jobject jobj, PTR ptr, - jboolean java_owns_handle) { - assert(jobj != nullptr); - static jmethodID mid = getSetNativeHandleMethod(env); - if (mid == nullptr) { - return true; // signal exception - } - - env->CallVoidMethod(jobj, mid, GET_CPLUSPLUS_POINTER(ptr), - java_owns_handle); - if (env->ExceptionCheck()) { - return true; // signal exception - } - - return false; - } -}; - -// Java Exception template -template -class JavaException : public JavaClass { - public: - /** - * Create and throw a java exception with the provided message - * - * @param env A pointer to the Java environment - * @param msg The message for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, const std::string& msg) { - jclass jclazz = DERIVED::getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - std::cerr << "JavaException::ThrowNew - Error: unexpected exception!" - << std::endl; - return env->ExceptionCheck(); - } - - const jint rs = env->ThrowNew(jclazz, msg.c_str()); - if (rs != JNI_OK) { - // exception could not be thrown - std::cerr << "JavaException::ThrowNew - Fatal: could not throw exception!" - << std::endl; - return env->ExceptionCheck(); - } - - return true; - } -}; - -// The portal class for java.lang.IllegalArgumentException -class IllegalArgumentExceptionJni - : public JavaException { - public: - /** - * Get the Java Class java.lang.IllegalArgumentException - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaException::getJClass(env, "java/lang/IllegalArgumentException"); - } - - /** - * Create and throw a Java IllegalArgumentException with the provided status - * - * If s.ok() == true, then this function will not throw any exception. - * - * @param env A pointer to the Java environment - * @param s The status for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, const Status& s) { - assert(!s.ok()); - if (s.ok()) { - return false; - } - - // get the IllegalArgumentException class - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - std::cerr << "IllegalArgumentExceptionJni::ThrowNew/class - Error: " - "unexpected exception!" - << std::endl; - return env->ExceptionCheck(); - } - - return JavaException::ThrowNew(env, s.ToString()); - } -}; - -// The portal class for org.rocksdb.Status.Code -class CodeJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.Status.Code - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/Status$Code"); - } - - /** - * Get the Java Method: Status.Code#getValue - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getValueMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "getValue", "()b"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.Status.SubCode -class SubCodeJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.Status.SubCode - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/Status$SubCode"); - } - - /** - * Get the Java Method: Status.SubCode#getValue - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getValueMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "getValue", "()b"); - assert(mid != nullptr); - return mid; - } - - static ROCKSDB_NAMESPACE::Status::SubCode toCppSubCode( - const jbyte jsub_code) { - switch (jsub_code) { - case 0x0: - return ROCKSDB_NAMESPACE::Status::SubCode::kNone; - case 0x1: - return ROCKSDB_NAMESPACE::Status::SubCode::kMutexTimeout; - case 0x2: - return ROCKSDB_NAMESPACE::Status::SubCode::kLockTimeout; - case 0x3: - return ROCKSDB_NAMESPACE::Status::SubCode::kLockLimit; - case 0x4: - return ROCKSDB_NAMESPACE::Status::SubCode::kNoSpace; - case 0x5: - return ROCKSDB_NAMESPACE::Status::SubCode::kDeadlock; - case 0x6: - return ROCKSDB_NAMESPACE::Status::SubCode::kStaleFile; - case 0x7: - return ROCKSDB_NAMESPACE::Status::SubCode::kMemoryLimit; - - case 0x7F: - default: - return ROCKSDB_NAMESPACE::Status::SubCode::kNone; - } - } -}; - -// The portal class for org.rocksdb.Status -class StatusJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.Status - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Status"); - } - - /** - * Get the Java Method: Status#getCode - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getCodeMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "getCode", "()Lorg/rocksdb/Status$Code;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Status#getSubCode - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getSubCodeMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "getSubCode", - "()Lorg/rocksdb/Status$SubCode;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Status#getState - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getStateMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "getState", "()Ljava/lang/String;"); - assert(mid != nullptr); - return mid; - } - - /** - * Create a new Java org.rocksdb.Status object with the same properties as - * the provided C++ ROCKSDB_NAMESPACE::Status object - * - * @param env A pointer to the Java environment - * @param status The ROCKSDB_NAMESPACE::Status object - * - * @return A reference to a Java org.rocksdb.Status object, or nullptr - * if an an exception occurs - */ - static jobject construct(JNIEnv* env, const Status& status) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = - env->GetMethodID(jclazz, "", "(BBLjava/lang/String;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - // convert the Status state for Java - jstring jstate = nullptr; - if (status.getState() != nullptr) { - const char* const state = status.getState(); - jstate = env->NewStringUTF(state); - if (env->ExceptionCheck()) { - if (jstate != nullptr) { - env->DeleteLocalRef(jstate); - } - return nullptr; - } - } - - jobject jstatus = - env->NewObject(jclazz, mid, toJavaStatusCode(status.code()), - toJavaStatusSubCode(status.subcode()), jstate); - if (env->ExceptionCheck()) { - // exception occurred - if (jstate != nullptr) { - env->DeleteLocalRef(jstate); - } - return nullptr; - } - - if (jstate != nullptr) { - env->DeleteLocalRef(jstate); - } - - return jstatus; - } - - static jobject construct(JNIEnv* env, const Status* status) { - return construct(env, *status); - } - - // Returns the equivalent org.rocksdb.Status.Code for the provided - // C++ ROCKSDB_NAMESPACE::Status::Code enum - static jbyte toJavaStatusCode(const ROCKSDB_NAMESPACE::Status::Code& code) { - switch (code) { - case ROCKSDB_NAMESPACE::Status::Code::kOk: - return 0x0; - case ROCKSDB_NAMESPACE::Status::Code::kNotFound: - return 0x1; - case ROCKSDB_NAMESPACE::Status::Code::kCorruption: - return 0x2; - case ROCKSDB_NAMESPACE::Status::Code::kNotSupported: - return 0x3; - case ROCKSDB_NAMESPACE::Status::Code::kInvalidArgument: - return 0x4; - case ROCKSDB_NAMESPACE::Status::Code::kIOError: - return 0x5; - case ROCKSDB_NAMESPACE::Status::Code::kMergeInProgress: - return 0x6; - case ROCKSDB_NAMESPACE::Status::Code::kIncomplete: - return 0x7; - case ROCKSDB_NAMESPACE::Status::Code::kShutdownInProgress: - return 0x8; - case ROCKSDB_NAMESPACE::Status::Code::kTimedOut: - return 0x9; - case ROCKSDB_NAMESPACE::Status::Code::kAborted: - return 0xA; - case ROCKSDB_NAMESPACE::Status::Code::kBusy: - return 0xB; - case ROCKSDB_NAMESPACE::Status::Code::kExpired: - return 0xC; - case ROCKSDB_NAMESPACE::Status::Code::kTryAgain: - return 0xD; - case ROCKSDB_NAMESPACE::Status::Code::kColumnFamilyDropped: - return 0xE; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent org.rocksdb.Status.SubCode for the provided - // C++ ROCKSDB_NAMESPACE::Status::SubCode enum - static jbyte toJavaStatusSubCode( - const ROCKSDB_NAMESPACE::Status::SubCode& subCode) { - switch (subCode) { - case ROCKSDB_NAMESPACE::Status::SubCode::kNone: - return 0x0; - case ROCKSDB_NAMESPACE::Status::SubCode::kMutexTimeout: - return 0x1; - case ROCKSDB_NAMESPACE::Status::SubCode::kLockTimeout: - return 0x2; - case ROCKSDB_NAMESPACE::Status::SubCode::kLockLimit: - return 0x3; - case ROCKSDB_NAMESPACE::Status::SubCode::kNoSpace: - return 0x4; - case ROCKSDB_NAMESPACE::Status::SubCode::kDeadlock: - return 0x5; - case ROCKSDB_NAMESPACE::Status::SubCode::kStaleFile: - return 0x6; - case ROCKSDB_NAMESPACE::Status::SubCode::kMemoryLimit: - return 0x7; - default: - return 0x7F; // undefined - } - } - - static std::unique_ptr toCppStatus( - const jbyte jcode_value, const jbyte jsub_code_value) { - std::unique_ptr status; - switch (jcode_value) { - case 0x0: - // Ok - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK())); - break; - case 0x1: - // NotFound - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::NotFound( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0x2: - // Corruption - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Corruption( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0x3: - // NotSupported - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status( - ROCKSDB_NAMESPACE::Status::NotSupported( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode( - jsub_code_value)))); - break; - case 0x4: - // InvalidArgument - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status( - ROCKSDB_NAMESPACE::Status::InvalidArgument( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode( - jsub_code_value)))); - break; - case 0x5: - // IOError - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::IOError( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0x6: - // MergeInProgress - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status( - ROCKSDB_NAMESPACE::Status::MergeInProgress( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode( - jsub_code_value)))); - break; - case 0x7: - // Incomplete - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Incomplete( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0x8: - // ShutdownInProgress - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status( - ROCKSDB_NAMESPACE::Status::ShutdownInProgress( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode( - jsub_code_value)))); - break; - case 0x9: - // TimedOut - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::TimedOut( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0xA: - // Aborted - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Aborted( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0xB: - // Busy - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Busy( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0xC: - // Expired - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Expired( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0xD: - // TryAgain - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::TryAgain( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value)))); - break; - case 0xE: - // ColumnFamilyDropped - status = std::unique_ptr( - new ROCKSDB_NAMESPACE::Status( - ROCKSDB_NAMESPACE::Status::ColumnFamilyDropped( - ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode( - jsub_code_value)))); - break; - case 0x7F: - default: - return nullptr; - } - return status; - } - - // Returns the equivalent ROCKSDB_NAMESPACE::Status for the Java - // org.rocksdb.Status - static std::unique_ptr toCppStatus( - JNIEnv* env, const jobject jstatus) { - jmethodID mid_code = getCodeMethod(env); - if (mid_code == nullptr) { - // exception occurred - return nullptr; - } - jobject jcode = env->CallObjectMethod(jstatus, mid_code); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - jmethodID mid_code_value = ROCKSDB_NAMESPACE::CodeJni::getValueMethod(env); - if (mid_code_value == nullptr) { - // exception occurred - return nullptr; - } - jbyte jcode_value = env->CallByteMethod(jcode, mid_code_value); - if (env->ExceptionCheck()) { - // exception occurred - if (jcode != nullptr) { - env->DeleteLocalRef(jcode); - } - return nullptr; - } - - jmethodID mid_subCode = getSubCodeMethod(env); - if (mid_subCode == nullptr) { - // exception occurred - return nullptr; - } - jobject jsubCode = env->CallObjectMethod(jstatus, mid_subCode); - if (env->ExceptionCheck()) { - // exception occurred - if (jcode != nullptr) { - env->DeleteLocalRef(jcode); - } - return nullptr; - } - - jbyte jsub_code_value = 0x0; // None - if (jsubCode != nullptr) { - jmethodID mid_subCode_value = - ROCKSDB_NAMESPACE::SubCodeJni::getValueMethod(env); - if (mid_subCode_value == nullptr) { - // exception occurred - return nullptr; - } - jsub_code_value = env->CallByteMethod(jsubCode, mid_subCode_value); - if (env->ExceptionCheck()) { - // exception occurred - if (jcode != nullptr) { - env->DeleteLocalRef(jcode); - } - return nullptr; - } - } - - jmethodID mid_state = getStateMethod(env); - if (mid_state == nullptr) { - // exception occurred - return nullptr; - } - jobject jstate = env->CallObjectMethod(jstatus, mid_state); - if (env->ExceptionCheck()) { - // exception occurred - if (jsubCode != nullptr) { - env->DeleteLocalRef(jsubCode); - } - if (jcode != nullptr) { - env->DeleteLocalRef(jcode); - } - return nullptr; - } - - std::unique_ptr status = - toCppStatus(jcode_value, jsub_code_value); - - // delete all local refs - if (jstate != nullptr) { - env->DeleteLocalRef(jstate); - } - if (jsubCode != nullptr) { - env->DeleteLocalRef(jsubCode); - } - if (jcode != nullptr) { - env->DeleteLocalRef(jcode); - } - - return status; - } -}; - -// The portal class for org.rocksdb.RocksDBException -class RocksDBExceptionJni : public JavaException { - public: - /** - * Get the Java Class org.rocksdb.RocksDBException - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaException::getJClass(env, "org/rocksdb/RocksDBException"); - } - - /** - * Create and throw a Java RocksDBException with the provided message - * - * @param env A pointer to the Java environment - * @param msg The message for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, const std::string& msg) { - return JavaException::ThrowNew(env, msg); - } - - /** - * Create and throw a Java RocksDBException with the provided status - * - * If s->ok() == true, then this function will not throw any exception. - * - * @param env A pointer to the Java environment - * @param s The status for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, std::unique_ptr& s) { - return ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, *(s.get())); - } - - /** - * Create and throw a Java RocksDBException with the provided status - * - * If s.ok() == true, then this function will not throw any exception. - * - * @param env A pointer to the Java environment - * @param s The status for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, const Status& s) { - if (s.ok()) { - return false; - } - - // get the RocksDBException class - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - std::cerr << "RocksDBExceptionJni::ThrowNew/class - Error: unexpected " - "exception!" - << std::endl; - return env->ExceptionCheck(); - } - - // get the constructor of org.rocksdb.RocksDBException - jmethodID mid = - env->GetMethodID(jclazz, "", "(Lorg/rocksdb/Status;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - std::cerr - << "RocksDBExceptionJni::ThrowNew/cstr - Error: unexpected exception!" - << std::endl; - return env->ExceptionCheck(); - } - - // get the Java status object - jobject jstatus = StatusJni::construct(env, s); - if (jstatus == nullptr) { - // exception occcurred - std::cerr << "RocksDBExceptionJni::ThrowNew/StatusJni - Error: " - "unexpected exception!" - << std::endl; - return env->ExceptionCheck(); - } - - // construct the RocksDBException - jthrowable rocksdb_exception = - reinterpret_cast(env->NewObject(jclazz, mid, jstatus)); - if (env->ExceptionCheck()) { - if (jstatus != nullptr) { - env->DeleteLocalRef(jstatus); - } - if (rocksdb_exception != nullptr) { - env->DeleteLocalRef(rocksdb_exception); - } - std::cerr << "RocksDBExceptionJni::ThrowNew/NewObject - Error: " - "unexpected exception!" - << std::endl; - return true; - } - - // throw the RocksDBException - const jint rs = env->Throw(rocksdb_exception); - if (rs != JNI_OK) { - // exception could not be thrown - std::cerr - << "RocksDBExceptionJni::ThrowNew - Fatal: could not throw exception!" - << std::endl; - if (jstatus != nullptr) { - env->DeleteLocalRef(jstatus); - } - if (rocksdb_exception != nullptr) { - env->DeleteLocalRef(rocksdb_exception); - } - return env->ExceptionCheck(); - } - - if (jstatus != nullptr) { - env->DeleteLocalRef(jstatus); - } - if (rocksdb_exception != nullptr) { - env->DeleteLocalRef(rocksdb_exception); - } - - return true; - } - - /** - * Create and throw a Java RocksDBException with the provided message - * and status - * - * If s.ok() == true, then this function will not throw any exception. - * - * @param env A pointer to the Java environment - * @param msg The message for the exception - * @param s The status for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, const std::string& msg, const Status& s) { - assert(!s.ok()); - if (s.ok()) { - return false; - } - - // get the RocksDBException class - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - std::cerr << "RocksDBExceptionJni::ThrowNew/class - Error: unexpected " - "exception!" - << std::endl; - return env->ExceptionCheck(); - } - - // get the constructor of org.rocksdb.RocksDBException - jmethodID mid = env->GetMethodID( - jclazz, "", "(Ljava/lang/String;Lorg/rocksdb/Status;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - std::cerr - << "RocksDBExceptionJni::ThrowNew/cstr - Error: unexpected exception!" - << std::endl; - return env->ExceptionCheck(); - } - - jstring jmsg = env->NewStringUTF(msg.c_str()); - if (jmsg == nullptr) { - // exception thrown: OutOfMemoryError - std::cerr - << "RocksDBExceptionJni::ThrowNew/msg - Error: unexpected exception!" - << std::endl; - return env->ExceptionCheck(); - } - - // get the Java status object - jobject jstatus = StatusJni::construct(env, s); - if (jstatus == nullptr) { - // exception occcurred - std::cerr << "RocksDBExceptionJni::ThrowNew/StatusJni - Error: " - "unexpected exception!" - << std::endl; - if (jmsg != nullptr) { - env->DeleteLocalRef(jmsg); - } - return env->ExceptionCheck(); - } - - // construct the RocksDBException - jthrowable rocksdb_exception = reinterpret_cast( - env->NewObject(jclazz, mid, jmsg, jstatus)); - if (env->ExceptionCheck()) { - if (jstatus != nullptr) { - env->DeleteLocalRef(jstatus); - } - if (jmsg != nullptr) { - env->DeleteLocalRef(jmsg); - } - if (rocksdb_exception != nullptr) { - env->DeleteLocalRef(rocksdb_exception); - } - std::cerr << "RocksDBExceptionJni::ThrowNew/NewObject - Error: " - "unexpected exception!" - << std::endl; - return true; - } - - // throw the RocksDBException - const jint rs = env->Throw(rocksdb_exception); - if (rs != JNI_OK) { - // exception could not be thrown - std::cerr - << "RocksDBExceptionJni::ThrowNew - Fatal: could not throw exception!" - << std::endl; - if (jstatus != nullptr) { - env->DeleteLocalRef(jstatus); - } - if (jmsg != nullptr) { - env->DeleteLocalRef(jmsg); - } - if (rocksdb_exception != nullptr) { - env->DeleteLocalRef(rocksdb_exception); - } - return env->ExceptionCheck(); - } - - if (jstatus != nullptr) { - env->DeleteLocalRef(jstatus); - } - if (jmsg != nullptr) { - env->DeleteLocalRef(jmsg); - } - if (rocksdb_exception != nullptr) { - env->DeleteLocalRef(rocksdb_exception); - } - - return true; - } - - /** - * Get the Java Method: RocksDBException#getStatus - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getStatusMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "getStatus", "()Lorg/rocksdb/Status;"); - assert(mid != nullptr); - return mid; - } - - static std::unique_ptr toCppStatus( - JNIEnv* env, jthrowable jrocksdb_exception) { - if (!env->IsInstanceOf(jrocksdb_exception, getJClass(env))) { - // not an instance of RocksDBException - return nullptr; - } - - // get the java status object - jmethodID mid = getStatusMethod(env); - if (mid == nullptr) { - // exception occurred accessing class or method - return nullptr; - } - - jobject jstatus = env->CallObjectMethod(jrocksdb_exception, mid); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - if (jstatus == nullptr) { - return nullptr; // no status available - } - - return ROCKSDB_NAMESPACE::StatusJni::toCppStatus(env, jstatus); - } -}; - -// The portal class for java.util.List -class ListJni : public JavaClass { - public: - /** - * Get the Java Class java.util.List - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getListClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/List"); - } - - /** - * Get the Java Class java.util.ArrayList - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getArrayListClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/ArrayList"); - } - - /** - * Get the Java Class java.util.Iterator - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getIteratorClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/Iterator"); - } - - /** - * Get the Java Method: List#iterator - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getIteratorMethod(JNIEnv* env) { - jclass jlist_clazz = getListClass(env); - if (jlist_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jlist_clazz, "iterator", "()Ljava/util/Iterator;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Iterator#hasNext - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getHasNextMethod(JNIEnv* env) { - jclass jiterator_clazz = getIteratorClass(env); - if (jiterator_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jiterator_clazz, "hasNext", "()Z"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Iterator#next - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getNextMethod(JNIEnv* env) { - jclass jiterator_clazz = getIteratorClass(env); - if (jiterator_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jiterator_clazz, "next", "()Ljava/lang/Object;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: ArrayList constructor - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getArrayListConstructorMethodId(JNIEnv* env) { - jclass jarray_list_clazz = getArrayListClass(env); - if (jarray_list_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - static jmethodID mid = - env->GetMethodID(jarray_list_clazz, "", "(I)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: List#add - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getListAddMethodId(JNIEnv* env) { - jclass jlist_clazz = getListClass(env); - if (jlist_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jlist_clazz, "add", "(Ljava/lang/Object;)Z"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for java.lang.Byte -class ByteJni : public JavaClass { - public: - /** - * Get the Java Class java.lang.Byte - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/lang/Byte"); - } - - /** - * Get the Java Class byte[] - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getArrayJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "[B"); - } - - /** - * Creates a new 2-dimensional Java Byte Array byte[][] - * - * @param env A pointer to the Java environment - * @param len The size of the first dimension - * - * @return A reference to the Java byte[][] or nullptr if an exception occurs - */ - static jobjectArray new2dByteArray(JNIEnv* env, const jsize len) { - jclass clazz = getArrayJClass(env); - if (clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - return env->NewObjectArray(len, clazz, nullptr); - } - - /** - * Get the Java Method: Byte#byteValue - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getByteValueMethod(JNIEnv* env) { - jclass clazz = getJClass(env); - if (clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(clazz, "byteValue", "()B"); - assert(mid != nullptr); - return mid; - } - - /** - * Calls the Java Method: Byte#valueOf, returning a constructed Byte jobject - * - * @param env A pointer to the Java environment - * - * @return A constructing Byte object or nullptr if the class or method id - * could not be retrieved, or an exception occurred - */ - static jobject valueOf(JNIEnv* env, jbyte jprimitive_byte) { - jclass clazz = getJClass(env); - if (clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetStaticMethodID(clazz, "valueOf", "(B)Ljava/lang/Byte;"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - const jobject jbyte_obj = - env->CallStaticObjectMethod(clazz, mid, jprimitive_byte); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - return jbyte_obj; - } -}; - -// The portal class for java.nio.ByteBuffer -class ByteBufferJni : public JavaClass { - public: - /** - * Get the Java Class java.nio.ByteBuffer - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/nio/ByteBuffer"); - } - - /** - * Get the Java Method: ByteBuffer#allocate - * - * @param env A pointer to the Java environment - * @param jbytebuffer_clazz if you have a reference to a ByteBuffer class, or - * nullptr - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getAllocateMethodId(JNIEnv* env, - jclass jbytebuffer_clazz = nullptr) { - const jclass jclazz = - jbytebuffer_clazz == nullptr ? getJClass(env) : jbytebuffer_clazz; - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetStaticMethodID(jclazz, "allocate", "(I)Ljava/nio/ByteBuffer;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: ByteBuffer#array - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getArrayMethodId(JNIEnv* env, - jclass jbytebuffer_clazz = nullptr) { - const jclass jclazz = - jbytebuffer_clazz == nullptr ? getJClass(env) : jbytebuffer_clazz; - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "array", "()[B"); - assert(mid != nullptr); - return mid; - } - - static jobject construct(JNIEnv* env, const bool direct, - const size_t capacity, - jclass jbytebuffer_clazz = nullptr) { - return constructWith(env, direct, nullptr, capacity, jbytebuffer_clazz); - } - - static jobject constructWith(JNIEnv* env, const bool direct, const char* buf, - const size_t capacity, - jclass jbytebuffer_clazz = nullptr) { - if (direct) { - bool allocated = false; - if (buf == nullptr) { - buf = new char[capacity]; - allocated = true; - } - jobject jbuf = env->NewDirectByteBuffer(const_cast(buf), - static_cast(capacity)); - if (jbuf == nullptr) { - // exception occurred - if (allocated) { - delete[] static_cast(buf); - } - return nullptr; - } - return jbuf; - } else { - const jclass jclazz = - jbytebuffer_clazz == nullptr ? getJClass(env) : jbytebuffer_clazz; - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - const jmethodID jmid_allocate = - getAllocateMethodId(env, jbytebuffer_clazz); - if (jmid_allocate == nullptr) { - // exception occurred accessing class, or NoSuchMethodException or - // OutOfMemoryError - return nullptr; - } - const jobject jbuf = env->CallStaticObjectMethod( - jclazz, jmid_allocate, static_cast(capacity)); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - // set buffer data? - if (buf != nullptr) { - jbyteArray jarray = array(env, jbuf, jbytebuffer_clazz); - if (jarray == nullptr) { - // exception occurred - env->DeleteLocalRef(jbuf); - return nullptr; - } - - jboolean is_copy = JNI_FALSE; - jbyte* ja = reinterpret_cast( - env->GetPrimitiveArrayCritical(jarray, &is_copy)); - if (ja == nullptr) { - // exception occurred - env->DeleteLocalRef(jarray); - env->DeleteLocalRef(jbuf); - return nullptr; - } - - memcpy(ja, const_cast(buf), capacity); - - env->ReleasePrimitiveArrayCritical(jarray, ja, 0); - - env->DeleteLocalRef(jarray); - } - - return jbuf; - } - } - - static jbyteArray array(JNIEnv* env, const jobject& jbyte_buffer, - jclass jbytebuffer_clazz = nullptr) { - const jmethodID mid = getArrayMethodId(env, jbytebuffer_clazz); - if (mid == nullptr) { - // exception occurred accessing class, or NoSuchMethodException or - // OutOfMemoryError - return nullptr; - } - const jobject jarray = env->CallObjectMethod(jbyte_buffer, mid); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - return static_cast(jarray); - } -}; - -// The portal class for java.lang.Integer -class IntegerJni : public JavaClass { - public: - /** - * Get the Java Class java.lang.Integer - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/lang/Integer"); - } - - static jobject valueOf(JNIEnv* env, jint jprimitive_int) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = - env->GetStaticMethodID(jclazz, "valueOf", "(I)Ljava/lang/Integer;"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - const jobject jinteger_obj = - env->CallStaticObjectMethod(jclazz, mid, jprimitive_int); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - return jinteger_obj; - } -}; - -// The portal class for java.lang.Long -class LongJni : public JavaClass { - public: - /** - * Get the Java Class java.lang.Long - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/lang/Long"); - } - - static jobject valueOf(JNIEnv* env, jlong jprimitive_long) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = - env->GetStaticMethodID(jclazz, "valueOf", "(J)Ljava/lang/Long;"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - const jobject jlong_obj = - env->CallStaticObjectMethod(jclazz, mid, jprimitive_long); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - return jlong_obj; - } -}; - -// The portal class for java.lang.StringBuilder -class StringBuilderJni : public JavaClass { - public: - /** - * Get the Java Class java.lang.StringBuilder - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/lang/StringBuilder"); - } - - /** - * Get the Java Method: StringBuilder#append - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getListAddMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID( - jclazz, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); - assert(mid != nullptr); - return mid; - } - - /** - * Appends a C-style string to a StringBuilder - * - * @param env A pointer to the Java environment - * @param jstring_builder Reference to a java.lang.StringBuilder - * @param c_str A C-style string to append to the StringBuilder - * - * @return A reference to the updated StringBuilder, or a nullptr if - * an exception occurs - */ - static jobject append(JNIEnv* env, jobject jstring_builder, - const char* c_str) { - jmethodID mid = getListAddMethodId(env); - if (mid == nullptr) { - // exception occurred accessing class or method - return nullptr; - } - - jstring new_value_str = env->NewStringUTF(c_str); - if (new_value_str == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - jobject jresult_string_builder = - env->CallObjectMethod(jstring_builder, mid, new_value_str); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(new_value_str); - return nullptr; - } - - return jresult_string_builder; - } -}; - -// various utility functions for working with RocksDB and JNI -class JniUtil { - public: - /** - * Detect if jlong overflows size_t - * - * @param jvalue the jlong value - * - * @return - */ - inline static Status check_if_jlong_fits_size_t(const jlong& jvalue) { - Status s = Status::OK(); - if (static_cast(jvalue) > std::numeric_limits::max()) { - s = Status::InvalidArgument(Slice("jlong overflows 32 bit value.")); - } - return s; - } - - /** - * Obtains a reference to the JNIEnv from - * the JVM - * - * If the current thread is not attached to the JavaVM - * then it will be attached so as to retrieve the JNIEnv - * - * If a thread is attached, it must later be manually - * released by calling JavaVM::DetachCurrentThread. - * This can be handled by always matching calls to this - * function with calls to {@link JniUtil::releaseJniEnv(JavaVM*, jboolean)} - * - * @param jvm (IN) A pointer to the JavaVM instance - * @param attached (OUT) A pointer to a boolean which - * will be set to JNI_TRUE if we had to attach the thread - * - * @return A pointer to the JNIEnv or nullptr if a fatal error - * occurs and the JNIEnv cannot be retrieved - */ - static JNIEnv* getJniEnv(JavaVM* jvm, jboolean* attached) { - assert(jvm != nullptr); - - JNIEnv* env; - const jint env_rs = - jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - - if (env_rs == JNI_OK) { - // current thread is already attached, return the JNIEnv - *attached = JNI_FALSE; - return env; - } else if (env_rs == JNI_EDETACHED) { - // current thread is not attached, attempt to attach - const jint rs_attach = - jvm->AttachCurrentThread(reinterpret_cast(&env), NULL); - if (rs_attach == JNI_OK) { - *attached = JNI_TRUE; - return env; - } else { - // error, could not attach the thread - std::cerr << "JniUtil::getJniEnv - Fatal: could not attach current " - "thread to JVM!" - << std::endl; - return nullptr; - } - } else if (env_rs == JNI_EVERSION) { - // error, JDK does not support JNI_VERSION_1_6+ - std::cerr - << "JniUtil::getJniEnv - Fatal: JDK does not support JNI_VERSION_1_6" - << std::endl; - return nullptr; - } else { - std::cerr << "JniUtil::getJniEnv - Fatal: Unknown error: env_rs=" - << env_rs << std::endl; - return nullptr; - } - } - - /** - * Counterpart to {@link JniUtil::getJniEnv(JavaVM*, jboolean*)} - * - * Detachess the current thread from the JVM if it was previously - * attached - * - * @param jvm (IN) A pointer to the JavaVM instance - * @param attached (IN) JNI_TRUE if we previously had to attach the thread - * to the JavaVM to get the JNIEnv - */ - static void releaseJniEnv(JavaVM* jvm, jboolean& attached) { - assert(jvm != nullptr); - if (attached == JNI_TRUE) { - const jint rs_detach = jvm->DetachCurrentThread(); - assert(rs_detach == JNI_OK); - if (rs_detach != JNI_OK) { - std::cerr << "JniUtil::getJniEnv - Warn: Unable to detach current " - "thread from JVM!" - << std::endl; - } - } - } - - /** - * Copies a Java String[] to a C++ std::vector - * - * @param env (IN) A pointer to the java environment - * @param jss (IN) The Java String array to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError or ArrayIndexOutOfBoundsException - * exception occurs - * - * @return A std::vector containing copies of the Java strings - */ - static std::vector copyStrings(JNIEnv* env, jobjectArray jss, - jboolean* has_exception) { - return ROCKSDB_NAMESPACE::JniUtil::copyStrings( - env, jss, env->GetArrayLength(jss), has_exception); - } - - /** - * Copies a Java String[] to a C++ std::vector - * - * @param env (IN) A pointer to the java environment - * @param jss (IN) The Java String array to copy - * @param jss_len (IN) The length of the Java String array to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError or ArrayIndexOutOfBoundsException - * exception occurs - * - * @return A std::vector containing copies of the Java strings - */ - static std::vector copyStrings(JNIEnv* env, jobjectArray jss, - const jsize jss_len, - jboolean* has_exception) { - std::vector strs; - strs.reserve(jss_len); - for (jsize i = 0; i < jss_len; i++) { - jobject js = env->GetObjectArrayElement(jss, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - *has_exception = JNI_TRUE; - return strs; - } - - jstring jstr = static_cast(js); - const char* str = env->GetStringUTFChars(jstr, nullptr); - if (str == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(js); - *has_exception = JNI_TRUE; - return strs; - } - - strs.push_back(std::string(str)); - - env->ReleaseStringUTFChars(jstr, str); - env->DeleteLocalRef(js); - } - - *has_exception = JNI_FALSE; - return strs; - } - - /** - * Copies a jstring to a C-style null-terminated byte string - * and releases the original jstring - * - * The jstring is copied as UTF-8 - * - * If an exception occurs, then JNIEnv::ExceptionCheck() - * will have been called - * - * @param env (IN) A pointer to the java environment - * @param js (IN) The java string to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError exception occurs - * - * @return A pointer to the copied string, or a - * nullptr if has_exception == JNI_TRUE - */ - static std::unique_ptr copyString(JNIEnv* env, jstring js, - jboolean* has_exception) { - const char* utf = env->GetStringUTFChars(js, nullptr); - if (utf == nullptr) { - // exception thrown: OutOfMemoryError - env->ExceptionCheck(); - *has_exception = JNI_TRUE; - return nullptr; - } else if (env->ExceptionCheck()) { - // exception thrown - env->ReleaseStringUTFChars(js, utf); - *has_exception = JNI_TRUE; - return nullptr; - } - - const jsize utf_len = env->GetStringUTFLength(js); - std::unique_ptr str( - new char[utf_len + - 1]); // Note: + 1 is needed for the c_str null terminator - std::strcpy(str.get(), utf); - env->ReleaseStringUTFChars(js, utf); - *has_exception = JNI_FALSE; - return str; - } - - /** - * Copies a jstring to a std::string - * and releases the original jstring - * - * If an exception occurs, then JNIEnv::ExceptionCheck() - * will have been called - * - * @param env (IN) A pointer to the java environment - * @param js (IN) The java string to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError exception occurs - * - * @return A std:string copy of the jstring, or an - * empty std::string if has_exception == JNI_TRUE - */ - static std::string copyStdString(JNIEnv* env, jstring js, - jboolean* has_exception) { - const char* utf = env->GetStringUTFChars(js, nullptr); - if (utf == nullptr) { - // exception thrown: OutOfMemoryError - env->ExceptionCheck(); - *has_exception = JNI_TRUE; - return std::string(); - } else if (env->ExceptionCheck()) { - // exception thrown - env->ReleaseStringUTFChars(js, utf); - *has_exception = JNI_TRUE; - return std::string(); - } - - std::string name(utf); - env->ReleaseStringUTFChars(js, utf); - *has_exception = JNI_FALSE; - return name; - } - - /** - * Copies bytes from a std::string to a jByteArray - * - * @param env A pointer to the java environment - * @param bytes The bytes to copy - * - * @return the Java byte[], or nullptr if an exception occurs - * - * @throws RocksDBException thrown - * if memory size to copy exceeds general java specific array size - * limitation. - */ - static jbyteArray copyBytes(JNIEnv* env, std::string bytes) { - return createJavaByteArrayWithSizeCheck(env, bytes.c_str(), bytes.size()); - } - - /** - * Given a Java byte[][] which is an array of java.lang.Strings - * where each String is a byte[], the passed function `string_fn` - * will be called on each String, the result is the collected by - * calling the passed function `collector_fn` - * - * @param env (IN) A pointer to the java environment - * @param jbyte_strings (IN) A Java array of Strings expressed as bytes - * @param string_fn (IN) A transform function to call for each String - * @param collector_fn (IN) A collector which is called for the result - * of each `string_fn` - * @param has_exception (OUT) will be set to JNI_TRUE - * if an ArrayIndexOutOfBoundsException or OutOfMemoryError - * exception occurs - */ - template - static void byteStrings(JNIEnv* env, jobjectArray jbyte_strings, - std::function string_fn, - std::function collector_fn, - jboolean* has_exception) { - const jsize jlen = env->GetArrayLength(jbyte_strings); - - for (jsize i = 0; i < jlen; i++) { - jobject jbyte_string_obj = env->GetObjectArrayElement(jbyte_strings, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - *has_exception = JNI_TRUE; // signal error - return; - } - - jbyteArray jbyte_string_ary = - reinterpret_cast(jbyte_string_obj); - T result = byteString(env, jbyte_string_ary, string_fn, has_exception); - - env->DeleteLocalRef(jbyte_string_obj); - - if (*has_exception == JNI_TRUE) { - // exception thrown: OutOfMemoryError - return; - } - - collector_fn(i, result); - } - - *has_exception = JNI_FALSE; - } - - /** - * Given a Java String which is expressed as a Java Byte Array byte[], - * the passed function `string_fn` will be called on the String - * and the result returned - * - * @param env (IN) A pointer to the java environment - * @param jbyte_string_ary (IN) A Java String expressed in bytes - * @param string_fn (IN) A transform function to call on the String - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError exception occurs - */ - template - static T byteString(JNIEnv* env, jbyteArray jbyte_string_ary, - std::function string_fn, - jboolean* has_exception) { - const jsize jbyte_string_len = env->GetArrayLength(jbyte_string_ary); - return byteString(env, jbyte_string_ary, jbyte_string_len, string_fn, - has_exception); - } - - /** - * Given a Java String which is expressed as a Java Byte Array byte[], - * the passed function `string_fn` will be called on the String - * and the result returned - * - * @param env (IN) A pointer to the java environment - * @param jbyte_string_ary (IN) A Java String expressed in bytes - * @param jbyte_string_len (IN) The length of the Java String - * expressed in bytes - * @param string_fn (IN) A transform function to call on the String - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError exception occurs - */ - template - static T byteString(JNIEnv* env, jbyteArray jbyte_string_ary, - const jsize jbyte_string_len, - std::function string_fn, - jboolean* has_exception) { - jbyte* jbyte_string = env->GetByteArrayElements(jbyte_string_ary, nullptr); - if (jbyte_string == nullptr) { - // exception thrown: OutOfMemoryError - *has_exception = JNI_TRUE; - return nullptr; // signal error - } - - T result = - string_fn(reinterpret_cast(jbyte_string), jbyte_string_len); - - env->ReleaseByteArrayElements(jbyte_string_ary, jbyte_string, JNI_ABORT); - - *has_exception = JNI_FALSE; - return result; - } - - /** - * Converts a std::vector to a Java byte[][] where each Java String - * is expressed as a Java Byte Array byte[]. - * - * @param env A pointer to the java environment - * @param strings A vector of Strings - * - * @return A Java array of Strings expressed as bytes, - * or nullptr if an exception is thrown - */ - static jobjectArray stringsBytes(JNIEnv* env, - std::vector strings) { - jclass jcls_ba = ByteJni::getArrayJClass(env); - if (jcls_ba == nullptr) { - // exception occurred - return nullptr; - } - - const jsize len = static_cast(strings.size()); - - jobjectArray jbyte_strings = env->NewObjectArray(len, jcls_ba, nullptr); - if (jbyte_strings == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - for (jsize i = 0; i < len; i++) { - std::string* str = &strings[i]; - const jsize str_len = static_cast(str->size()); - - jbyteArray jbyte_string_ary = env->NewByteArray(str_len); - if (jbyte_string_ary == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jbyte_strings); - return nullptr; - } - - env->SetByteArrayRegion( - jbyte_string_ary, 0, str_len, - const_cast(reinterpret_cast(str->c_str()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jbyte_string_ary); - env->DeleteLocalRef(jbyte_strings); - return nullptr; - } - - env->SetObjectArrayElement(jbyte_strings, i, jbyte_string_ary); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - // or ArrayStoreException - env->DeleteLocalRef(jbyte_string_ary); - env->DeleteLocalRef(jbyte_strings); - return nullptr; - } - - env->DeleteLocalRef(jbyte_string_ary); - } - - return jbyte_strings; - } - - /** - * Converts a std::vector to a Java String[]. - * - * @param env A pointer to the java environment - * @param strings A vector of Strings - * - * @return A Java array of Strings, - * or nullptr if an exception is thrown - */ - static jobjectArray toJavaStrings(JNIEnv* env, - const std::vector* strings) { - jclass jcls_str = env->FindClass("java/lang/String"); - if (jcls_str == nullptr) { - // exception occurred - return nullptr; - } - - const jsize len = static_cast(strings->size()); - - jobjectArray jstrings = env->NewObjectArray(len, jcls_str, nullptr); - if (jstrings == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - for (jsize i = 0; i < len; i++) { - const std::string* str = &((*strings)[i]); - jstring js = ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, str); - if (js == nullptr) { - env->DeleteLocalRef(jstrings); - return nullptr; - } - - env->SetObjectArrayElement(jstrings, i, js); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - // or ArrayStoreException - env->DeleteLocalRef(js); - env->DeleteLocalRef(jstrings); - return nullptr; - } - } - - return jstrings; - } - - /** - * Creates a Java UTF String from a C++ std::string - * - * @param env A pointer to the java environment - * @param string the C++ std::string - * @param treat_empty_as_null true if empty strings should be treated as null - * - * @return the Java UTF string, or nullptr if the provided string - * is null (or empty and treat_empty_as_null is set), or if an - * exception occurs allocating the Java String. - */ - static jstring toJavaString(JNIEnv* env, const std::string* string, - const bool treat_empty_as_null = false) { - if (string == nullptr) { - return nullptr; - } - - if (treat_empty_as_null && string->empty()) { - return nullptr; - } - - return env->NewStringUTF(string->c_str()); - } - - /** - * Copies bytes to a new jByteArray with the check of java array size - * limitation. - * - * @param bytes pointer to memory to copy to a new jByteArray - * @param size number of bytes to copy - * - * @return the Java byte[], or nullptr if an exception occurs - * - * @throws RocksDBException thrown - * if memory size to copy exceeds general java array size limitation to - * avoid overflow. - */ - static jbyteArray createJavaByteArrayWithSizeCheck(JNIEnv* env, - const char* bytes, - const size_t size) { - // Limitation for java array size is vm specific - // In general it cannot exceed Integer.MAX_VALUE (2^31 - 1) - // Current HotSpot VM limitation for array size is Integer.MAX_VALUE - 5 - // (2^31 - 1 - 5) It means that the next call to env->NewByteArray can still - // end with OutOfMemoryError("Requested array size exceeds VM limit") coming - // from VM - static const size_t MAX_JARRAY_SIZE = (static_cast(1)) << 31; - if (size > MAX_JARRAY_SIZE) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Requested array size exceeds VM limit"); - return nullptr; - } - - const jsize jlen = static_cast(size); - jbyteArray jbytes = env->NewByteArray(jlen); - if (jbytes == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetByteArrayRegion( - jbytes, 0, jlen, - const_cast(reinterpret_cast(bytes))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jbytes); - return nullptr; - } - - return jbytes; - } - - /** - * Copies bytes from a ROCKSDB_NAMESPACE::Slice to a jByteArray - * - * @param env A pointer to the java environment - * @param bytes The bytes to copy - * - * @return the Java byte[] or nullptr if an exception occurs - * - * @throws RocksDBException thrown - * if memory size to copy exceeds general java specific array size - * limitation. - */ - static jbyteArray copyBytes(JNIEnv* env, const Slice& bytes) { - return createJavaByteArrayWithSizeCheck(env, bytes.data(), bytes.size()); - } - - /* - * Helper for operations on a key and value - * for example WriteBatch->Put - * - * TODO(AR) could be used for RocksDB->Put etc. - */ - static std::unique_ptr kv_op( - std::function - op, - JNIEnv* env, jobject /*jobj*/, jbyteArray jkey, jint jkey_len, - jbyteArray jvalue, jint jvalue_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - jbyte* value = env->GetByteArrayElements(jvalue, nullptr); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - if (key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } - return nullptr; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast(value), - jvalue_len); - - auto status = op(key_slice, value_slice); - - if (value != nullptr) { - env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT); - } - if (key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } - - return std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(status)); - } - - /* - * Helper for operations on a key - * for example WriteBatch->Delete - * - * TODO(AR) could be used for RocksDB->Delete etc. - */ - static std::unique_ptr k_op( - std::function op, - JNIEnv* env, jobject /*jobj*/, jbyteArray jkey, jint jkey_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - auto status = op(key_slice); - - if (key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } - - return std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(status)); - } - - /* - * Helper for operations on a key which is a region of an array - * Used to extract the common code from seek/seekForPrev. - * Possible that it can be generalised from that. - * - * We use GetByteArrayRegion to copy the key region of the whole array into - * a char[] We suspect this is not much slower than GetByteArrayElements, - * which probably copies anyway. - */ - static void k_op_region(std::function op, - JNIEnv* env, jbyteArray jkey, jint jkey_off, - jint jkey_len) { - const std::unique_ptr key(new char[jkey_len]); - if (key == nullptr) { - jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError"); - env->ThrowNew(oom_class, - "Memory allocation failed in RocksDB JNI function"); - return; - } - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, - reinterpret_cast(key.get())); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key.get()), - jkey_len); - op(key_slice); - } - - /* - * Helper for operations on a value - * for example WriteBatchWithIndex->GetFromBatch - */ - static jbyteArray v_op(std::function - op, - JNIEnv* env, jbyteArray jkey, jint jkey_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - std::string value; - ROCKSDB_NAMESPACE::Status s = op(key_slice, &value); - - if (key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } - - if (s.IsNotFound()) { - return nullptr; - } - - if (s.ok()) { - jbyteArray jret_value = - env->NewByteArray(static_cast(value.size())); - if (jret_value == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetByteArrayRegion( - jret_value, 0, static_cast(value.size()), - const_cast(reinterpret_cast(value.c_str()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - if (jret_value != nullptr) { - env->DeleteLocalRef(jret_value); - } - return nullptr; - } - - return jret_value; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - /** - * Creates a vector of C++ pointers from - * a Java array of C++ pointer addresses. - * - * @param env (IN) A pointer to the java environment - * @param pointers (IN) A Java array of C++ pointer addresses - * @param has_exception (OUT) will be set to JNI_TRUE - * if an ArrayIndexOutOfBoundsException or OutOfMemoryError - * exception occurs. - * - * @return A vector of C++ pointers. - */ - template - static std::vector fromJPointers(JNIEnv* env, jlongArray jptrs, - jboolean* has_exception) { - const jsize jptrs_len = env->GetArrayLength(jptrs); - std::vector ptrs; - jlong* jptr = env->GetLongArrayElements(jptrs, nullptr); - if (jptr == nullptr) { - // exception thrown: OutOfMemoryError - *has_exception = JNI_TRUE; - return ptrs; - } - ptrs.reserve(jptrs_len); - for (jsize i = 0; i < jptrs_len; i++) { - ptrs.push_back(reinterpret_cast(jptr[i])); - } - env->ReleaseLongArrayElements(jptrs, jptr, JNI_ABORT); - return ptrs; - } - - /** - * Creates a Java array of C++ pointer addresses - * from a vector of C++ pointers. - * - * @param env (IN) A pointer to the java environment - * @param pointers (IN) A vector of C++ pointers - * @param has_exception (OUT) will be set to JNI_TRUE - * if an ArrayIndexOutOfBoundsException or OutOfMemoryError - * exception occurs - * - * @return Java array of C++ pointer addresses. - */ - template - static jlongArray toJPointers(JNIEnv* env, const std::vector& pointers, - jboolean* has_exception) { - const jsize len = static_cast(pointers.size()); - std::unique_ptr results(new jlong[len]); - std::transform( - pointers.begin(), pointers.end(), results.get(), - [](T* pointer) -> jlong { return GET_CPLUSPLUS_POINTER(pointer); }); - - jlongArray jpointers = env->NewLongArray(len); - if (jpointers == nullptr) { - // exception thrown: OutOfMemoryError - *has_exception = JNI_TRUE; - return nullptr; - } - - env->SetLongArrayRegion(jpointers, 0, len, results.get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - *has_exception = JNI_TRUE; - env->DeleteLocalRef(jpointers); - return nullptr; - } - - *has_exception = JNI_FALSE; - - return jpointers; - } - - /* - * Helper for operations on a key and value - * for example WriteBatch->Put - * - * TODO(AR) could be extended to cover returning ROCKSDB_NAMESPACE::Status - * from `op` and used for RocksDB->Put etc. - */ - static void kv_op_direct( - std::function - op, - JNIEnv* env, jobject jkey, jint jkey_off, jint jkey_len, jobject jval, - jint jval_off, jint jval_len) { - char* key = reinterpret_cast(env->GetDirectBufferAddress(jkey)); - if (key == nullptr || - env->GetDirectBufferCapacity(jkey) < (jkey_off + jkey_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, - "Invalid key argument"); - return; - } - - char* value = reinterpret_cast(env->GetDirectBufferAddress(jval)); - if (value == nullptr || - env->GetDirectBufferCapacity(jval) < (jval_off + jval_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Invalid value argument"); - return; - } - - key += jkey_off; - value += jval_off; - - ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len); - ROCKSDB_NAMESPACE::Slice value_slice(value, jval_len); - - op(key_slice, value_slice); - } - - /* - * Helper for operations on a key and value - * for example WriteBatch->Delete - * - * TODO(AR) could be extended to cover returning ROCKSDB_NAMESPACE::Status - * from `op` and used for RocksDB->Delete etc. - */ - static void k_op_direct(std::function op, - JNIEnv* env, jobject jkey, jint jkey_off, - jint jkey_len) { - char* key = reinterpret_cast(env->GetDirectBufferAddress(jkey)); - if (key == nullptr || - env->GetDirectBufferCapacity(jkey) < (jkey_off + jkey_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, - "Invalid key argument"); - return; - } - - key += jkey_off; - - ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len); - - return op(key_slice); - } - - template - static jint copyToDirect(JNIEnv* env, T& source, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - char* target = - reinterpret_cast(env->GetDirectBufferAddress(jtarget)); - if (target == nullptr || - env->GetDirectBufferCapacity(jtarget) < (jtarget_off + jtarget_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Invalid target argument"); - return 0; - } - - target += jtarget_off; - - const jint cvalue_len = static_cast(source.size()); - const jint length = std::min(jtarget_len, cvalue_len); - - memcpy(target, source.data(), length); - - return cvalue_len; - } -}; - -class MapJni : public JavaClass { - public: - /** - * Get the Java Class java.util.Map - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/Map"); - } - - /** - * Get the Java Method: Map#put - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMapPutMethodId(JNIEnv* env) { - jclass jlist_clazz = getJClass(env); - if (jlist_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID( - jlist_clazz, "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - assert(mid != nullptr); - return mid; - } -}; - -class HashMapJni : public JavaClass { - public: - /** - * Get the Java Class java.util.HashMap - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/HashMap"); - } - - /** - * Create a new Java java.util.HashMap object. - * - * @param env A pointer to the Java environment - * - * @return A reference to a Java java.util.HashMap object, or - * nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, const uint32_t initial_capacity = 16) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", "(I)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jobject jhash_map = - env->NewObject(jclazz, mid, static_cast(initial_capacity)); - if (env->ExceptionCheck()) { - return nullptr; - } - - return jhash_map; - } - - /** - * A function which maps a std::pair to a std::pair - * - * @return Either a pointer to a std::pair, or nullptr - * if an error occurs during the mapping - */ - template - using FnMapKV = - std::function>(const std::pair&)>; - - // template ::value_type, std::pair>::value, - // int32_t>::type = 0> static void putAll(JNIEnv* env, const jobject - // jhash_map, I iterator, const FnMapKV &fn_map_kv) { - /** - * Returns true if it succeeds, false if an error occurs - */ - template - static bool putAll(JNIEnv* env, const jobject jhash_map, - iterator_type iterator, iterator_type end, - const FnMapKV& fn_map_kv) { - const jmethodID jmid_put = - ROCKSDB_NAMESPACE::MapJni::getMapPutMethodId(env); - if (jmid_put == nullptr) { - return false; - } - - for (auto it = iterator; it != end; ++it) { - const std::unique_ptr> result = - fn_map_kv(*it); - if (result == nullptr) { - // an error occurred during fn_map_kv - return false; - } - env->CallObjectMethod(jhash_map, jmid_put, result->first, result->second); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(result->second); - env->DeleteLocalRef(result->first); - return false; - } - - // release local references - env->DeleteLocalRef(result->second); - env->DeleteLocalRef(result->first); - } - - return true; - } - - /** - * Creates a java.util.Map from a std::map - * - * @param env A pointer to the Java environment - * @param map the Cpp map - * - * @return a reference to the Java java.util.Map object, or nullptr if an - * exception occcurred - */ - static jobject fromCppMap(JNIEnv* env, - const std::map* map) { - if (map == nullptr) { - return nullptr; - } - - jobject jhash_map = construct(env, static_cast(map->size())); - if (jhash_map == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const std::string, const std::string, jobject, jobject> - fn_map_kv = - [env](const std::pair& kv) { - jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &(kv.first), false); - if (env->ExceptionCheck()) { - // an error occurred - return std::unique_ptr>(nullptr); - } - - jstring jvalue = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &(kv.second), true); - if (env->ExceptionCheck()) { - // an error occurred - env->DeleteLocalRef(jkey); - return std::unique_ptr>(nullptr); - } - - return std::unique_ptr>( - new std::pair( - static_cast(jkey), - static_cast(jvalue))); - }; - - if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { - // exception occurred - return nullptr; - } - - return jhash_map; - } - - /** - * Creates a java.util.Map from a std::map - * - * @param env A pointer to the Java environment - * @param map the Cpp map - * - * @return a reference to the Java java.util.Map object, or nullptr if an - * exception occcurred - */ - static jobject fromCppMap(JNIEnv* env, - const std::map* map) { - if (map == nullptr) { - return nullptr; - } - - if (map == nullptr) { - return nullptr; - } - - jobject jhash_map = construct(env, static_cast(map->size())); - if (jhash_map == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const std::string, const uint32_t, jobject, jobject> - fn_map_kv = - [env](const std::pair& kv) { - jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &(kv.first), false); - if (env->ExceptionCheck()) { - // an error occurred - return std::unique_ptr>(nullptr); - } - - jobject jvalue = ROCKSDB_NAMESPACE::IntegerJni::valueOf( - env, static_cast(kv.second)); - if (env->ExceptionCheck()) { - // an error occurred - env->DeleteLocalRef(jkey); - return std::unique_ptr>(nullptr); - } - - return std::unique_ptr>( - new std::pair(static_cast(jkey), - jvalue)); - }; - - if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { - // exception occurred - return nullptr; - } - - return jhash_map; - } - - /** - * Creates a java.util.Map from a std::map - * - * @param env A pointer to the Java environment - * @param map the Cpp map - * - * @return a reference to the Java java.util.Map object, or nullptr if an - * exception occcurred - */ - static jobject fromCppMap(JNIEnv* env, - const std::map* map) { - if (map == nullptr) { - return nullptr; - } - - jobject jhash_map = construct(env, static_cast(map->size())); - if (jhash_map == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const std::string, const uint64_t, jobject, jobject> - fn_map_kv = - [env](const std::pair& kv) { - jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &(kv.first), false); - if (env->ExceptionCheck()) { - // an error occurred - return std::unique_ptr>(nullptr); - } - - jobject jvalue = ROCKSDB_NAMESPACE::LongJni::valueOf( - env, static_cast(kv.second)); - if (env->ExceptionCheck()) { - // an error occurred - env->DeleteLocalRef(jkey); - return std::unique_ptr>(nullptr); - } - - return std::unique_ptr>( - new std::pair(static_cast(jkey), - jvalue)); - }; - - if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { - // exception occurred - return nullptr; - } - - return jhash_map; - } - - /** - * Creates a java.util.Map from a std::map - * - * @param env A pointer to the Java environment - * @param map the Cpp map - * - * @return a reference to the Java java.util.Map object, or nullptr if an - * exception occcurred - */ - static jobject fromCppMap(JNIEnv* env, - const std::map* map) { - if (map == nullptr) { - return nullptr; - } - - jobject jhash_map = construct(env, static_cast(map->size())); - if (jhash_map == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV - fn_map_kv = [env](const std::pair& kv) { - jobject jkey = ROCKSDB_NAMESPACE::IntegerJni::valueOf( - env, static_cast(kv.first)); - if (env->ExceptionCheck()) { - // an error occurred - return std::unique_ptr>(nullptr); - } - - jobject jvalue = ROCKSDB_NAMESPACE::LongJni::valueOf( - env, static_cast(kv.second)); - if (env->ExceptionCheck()) { - // an error occurred - env->DeleteLocalRef(jkey); - return std::unique_ptr>(nullptr); - } - - return std::unique_ptr>( - new std::pair(static_cast(jkey), - jvalue)); - }; - - if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { - // exception occurred - return nullptr; - } - - return jhash_map; - } -}; - -// The portal class for org.rocksdb.RocksDB -class RocksDBJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.RocksDB - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksDB"); - } -}; - -// The portal class for org.rocksdb.Options -class OptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.Options - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Options"); - } -}; - -// The portal class for org.rocksdb.DBOptions -class DBOptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.DBOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/DBOptions"); - } -}; - -// The portal class for org.rocksdb.ColumnFamilyOptions -class ColumnFamilyOptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.ColumnFamilyOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/ColumnFamilyOptions"); - } - - /** - * Create a new Java org.rocksdb.ColumnFamilyOptions object with the same - * properties as the provided C++ ROCKSDB_NAMESPACE::ColumnFamilyOptions - * object - * - * @param env A pointer to the Java environment - * @param cfoptions A pointer to ROCKSDB_NAMESPACE::ColumnFamilyOptions object - * - * @return A reference to a Java org.rocksdb.ColumnFamilyOptions object, or - * nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, const ColumnFamilyOptions* cfoptions) { - auto* cfo = new ROCKSDB_NAMESPACE::ColumnFamilyOptions(*cfoptions); - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", "(J)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jobject jcfd = env->NewObject(jclazz, mid, GET_CPLUSPLUS_POINTER(cfo)); - if (env->ExceptionCheck()) { - return nullptr; - } - - return jcfd; - } -}; - -// The portal class for org.rocksdb.WriteOptions -class WriteOptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.WriteOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteOptions"); - } -}; - -// The portal class for org.rocksdb.ReadOptions -class ReadOptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.ReadOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/ReadOptions"); - } -}; - -// The portal class for org.rocksdb.WriteBatch -class WriteBatchJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.WriteBatch - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteBatch"); - } - - /** - * Create a new Java org.rocksdb.WriteBatch object - * - * @param env A pointer to the Java environment - * @param wb A pointer to ROCKSDB_NAMESPACE::WriteBatch object - * - * @return A reference to a Java org.rocksdb.WriteBatch object, or - * nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, const WriteBatch* wb) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", "(J)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jobject jwb = env->NewObject(jclazz, mid, GET_CPLUSPLUS_POINTER(wb)); - if (env->ExceptionCheck()) { - return nullptr; - } - - return jwb; - } -}; - -// The portal class for org.rocksdb.WriteBatch.Handler -class WriteBatchHandlerJni - : public RocksDBNativeClass< - const ROCKSDB_NAMESPACE::WriteBatchHandlerJniCallback*, - WriteBatchHandlerJni> { - public: - /** - * Get the Java Class org.rocksdb.WriteBatch.Handler - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteBatch$Handler"); - } - - /** - * Get the Java Method: WriteBatch.Handler#put - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getPutCfMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "put", "(I[B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#put - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getPutMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "put", "([B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#merge - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMergeCfMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "merge", "(I[B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#merge - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMergeMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "merge", "([B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#delete - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getDeleteCfMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "delete", "(I[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#delete - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getDeleteMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "delete", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#singleDelete - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getSingleDeleteCfMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "singleDelete", "(I[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#singleDelete - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getSingleDeleteMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "singleDelete", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#deleteRange - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getDeleteRangeCfMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "(I[B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#deleteRange - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getDeleteRangeMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "([B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#logData - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getLogDataMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "logData", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#putBlobIndex - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getPutBlobIndexCfMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "putBlobIndex", "(I[B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#markBeginPrepare - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMarkBeginPrepareMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "markBeginPrepare", "()V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#markEndPrepare - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMarkEndPrepareMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "markEndPrepare", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#markNoop - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMarkNoopMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "markNoop", "(Z)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#markRollback - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMarkRollbackMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "markRollback", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#markCommit - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMarkCommitMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "markCommit", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#markCommitWithTimestamp - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getMarkCommitWithTimestampMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "markCommitWithTimestamp", "([B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#shouldContinue - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getContinueMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "shouldContinue", "()Z"); - assert(mid != nullptr); - return mid; - } -}; - -class WriteBatchSavePointJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.WriteBatch.SavePoint - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WriteBatch$SavePoint"); - } - - /** - * Get the Java Method: HistogramData constructor - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getConstructorMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "", "(JJJ)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Create a new Java org.rocksdb.WriteBatch.SavePoint object - * - * @param env A pointer to the Java environment - * @param savePoint A pointer to ROCKSDB_NAMESPACE::WriteBatch::SavePoint - * object - * - * @return A reference to a Java org.rocksdb.WriteBatch.SavePoint object, or - * nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, const SavePoint& save_point) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = getConstructorMethodId(env); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jobject jsave_point = - env->NewObject(jclazz, mid, static_cast(save_point.size), - static_cast(save_point.count), - static_cast(save_point.content_flags)); - if (env->ExceptionCheck()) { - return nullptr; - } - - return jsave_point; - } -}; - -// The portal class for org.rocksdb.WriteBatchWithIndex -class WriteBatchWithIndexJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.WriteBatchWithIndex - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/WriteBatchWithIndex"); - } -}; - -// The portal class for org.rocksdb.HistogramData -class HistogramDataJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.HistogramData - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/HistogramData"); - } - - /** - * Get the Java Method: HistogramData constructor - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getConstructorMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "", "(DDDDDDJJD)V"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.BackupEngineOptions -class BackupEngineOptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.BackupEngineOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/BackupEngineOptions"); - } -}; - -// The portal class for org.rocksdb.BackupEngine -class BackupEngineJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.BackupEngine - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/BackupEngine"); - } -}; - -// The portal class for org.rocksdb.RocksIterator -class IteratorJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.RocksIterator - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksIterator"); - } -}; - -// The portal class for org.rocksdb.Filter -class FilterJni - : public RocksDBNativeClass< - std::shared_ptr*, FilterJni> { - public: - /** - * Get the Java Class org.rocksdb.Filter - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Filter"); - } -}; - -// The portal class for org.rocksdb.ColumnFamilyHandle -class ColumnFamilyHandleJni - : public RocksDBNativeClass { - public: - static jobject fromCppColumnFamilyHandle( - JNIEnv* env, const ROCKSDB_NAMESPACE::ColumnFamilyHandle* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - return env->NewObject(jclazz, ctor, GET_CPLUSPLUS_POINTER(info)); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", "(J)V"); - } - - /** - * Get the Java Class org.rocksdb.ColumnFamilyHandle - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/ColumnFamilyHandle"); - } -}; - -// The portal class for org.rocksdb.FlushOptions -class FlushOptionsJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.FlushOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/FlushOptions"); - } -}; - -// The portal class for org.rocksdb.ComparatorOptions -class ComparatorOptionsJni - : public RocksDBNativeClass< - ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*, - ComparatorOptionsJni> { - public: - /** - * Get the Java Class org.rocksdb.ComparatorOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/ComparatorOptions"); - } -}; - -// The portal class for org.rocksdb.AbstractCompactionFilterFactory -class AbstractCompactionFilterFactoryJni - : public RocksDBNativeClass< - const ROCKSDB_NAMESPACE::CompactionFilterFactoryJniCallback*, - AbstractCompactionFilterFactoryJni> { - public: - /** - * Get the Java Class org.rocksdb.AbstractCompactionFilterFactory - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass( - env, "org/rocksdb/AbstractCompactionFilterFactory"); - } - - /** - * Get the Java Method: AbstractCompactionFilterFactory#name - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getNameMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "name", "()Ljava/lang/String;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractCompactionFilterFactory#createCompactionFilter - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getCreateCompactionFilterMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "createCompactionFilter", "(ZZ)J"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.AbstractTransactionNotifier -class AbstractTransactionNotifierJni - : public RocksDBNativeClass< - const ROCKSDB_NAMESPACE::TransactionNotifierJniCallback*, - AbstractTransactionNotifierJni> { - public: - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass( - env, "org/rocksdb/AbstractTransactionNotifier"); - } - - // Get the java method `snapshotCreated` - // of org.rocksdb.AbstractTransactionNotifier. - static jmethodID getSnapshotCreatedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "snapshotCreated", "(J)V"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.AbstractComparatorJniBridge -class AbstractComparatorJniBridge : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.AbstractComparatorJniBridge - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/AbstractComparatorJniBridge"); - } - - /** - * Get the Java Method: Comparator#compareInternal - * - * @param env A pointer to the Java environment - * @param jclazz the AbstractComparatorJniBridge class - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getCompareInternalMethodId(JNIEnv* env, jclass jclazz) { - static jmethodID mid = - env->GetStaticMethodID(jclazz, "compareInternal", - "(Lorg/rocksdb/AbstractComparator;Ljava/nio/" - "ByteBuffer;ILjava/nio/ByteBuffer;I)I"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Comparator#findShortestSeparatorInternal - * - * @param env A pointer to the Java environment - * @param jclazz the AbstractComparatorJniBridge class - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getFindShortestSeparatorInternalMethodId(JNIEnv* env, - jclass jclazz) { - static jmethodID mid = - env->GetStaticMethodID(jclazz, "findShortestSeparatorInternal", - "(Lorg/rocksdb/AbstractComparator;Ljava/nio/" - "ByteBuffer;ILjava/nio/ByteBuffer;I)I"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Comparator#findShortSuccessorInternal - * - * @param env A pointer to the Java environment - * @param jclazz the AbstractComparatorJniBridge class - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getFindShortSuccessorInternalMethodId(JNIEnv* env, - jclass jclazz) { - static jmethodID mid = env->GetStaticMethodID( - jclazz, "findShortSuccessorInternal", - "(Lorg/rocksdb/AbstractComparator;Ljava/nio/ByteBuffer;I)I"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.AbstractComparator -class AbstractComparatorJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.AbstractComparator - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/AbstractComparator"); - } - - /** - * Get the Java Method: Comparator#name - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getNameMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "name", "()Ljava/lang/String;"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.AbstractSlice -class AbstractSliceJni - : public NativeRocksMutableObject { - public: - /** - * Get the Java Class org.rocksdb.AbstractSlice - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/AbstractSlice"); - } -}; - -// The portal class for org.rocksdb.Slice -class SliceJni - : public NativeRocksMutableObject { - public: - /** - * Get the Java Class org.rocksdb.Slice - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Slice"); - } - - /** - * Constructs a Slice object - * - * @param env A pointer to the Java environment - * - * @return A reference to a Java Slice object, or a nullptr if an - * exception occurs - */ - static jobject construct0(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "", "()V"); - if (mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - jobject jslice = env->NewObject(jclazz, mid); - if (env->ExceptionCheck()) { - return nullptr; - } - - return jslice; - } -}; - -// The portal class for org.rocksdb.DirectSlice -class DirectSliceJni - : public NativeRocksMutableObject { - public: - /** - * Get the Java Class org.rocksdb.DirectSlice - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/DirectSlice"); - } - - /** - * Constructs a DirectSlice object - * - * @param env A pointer to the Java environment - * - * @return A reference to a Java DirectSlice object, or a nullptr if an - * exception occurs - */ - static jobject construct0(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "", "()V"); - if (mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - jobject jdirect_slice = env->NewObject(jclazz, mid); - if (env->ExceptionCheck()) { - return nullptr; - } - - return jdirect_slice; - } -}; - -// The portal class for org.rocksdb.BackupInfo -class BackupInfoJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.BackupInfo - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/BackupInfo"); - } - - /** - * Constructs a BackupInfo object - * - * @param env A pointer to the Java environment - * @param backup_id id of the backup - * @param timestamp timestamp of the backup - * @param size size of the backup - * @param number_files number of files related to the backup - * @param app_metadata application specific metadata - * - * @return A reference to a Java BackupInfo object, or a nullptr if an - * exception occurs - */ - static jobject construct0(JNIEnv* env, uint32_t backup_id, int64_t timestamp, - uint64_t size, uint32_t number_files, - const std::string& app_metadata) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "", "(IJJILjava/lang/String;)V"); - if (mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - jstring japp_metadata = nullptr; - if (app_metadata != nullptr) { - japp_metadata = env->NewStringUTF(app_metadata.c_str()); - if (japp_metadata == nullptr) { - // exception occurred creating java string - return nullptr; - } - } - - jobject jbackup_info = env->NewObject(jclazz, mid, backup_id, timestamp, - size, number_files, japp_metadata); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(japp_metadata); - return nullptr; - } - - return jbackup_info; - } -}; - -class BackupInfoListJni { - public: - /** - * Converts a C++ std::vector object to - * a Java ArrayList object - * - * @param env A pointer to the Java environment - * @param backup_infos A vector of BackupInfo - * - * @return Either a reference to a Java ArrayList object, or a nullptr - * if an exception occurs - */ - static jobject getBackupInfo(JNIEnv* env, - std::vector backup_infos) { - jclass jarray_list_clazz = - ROCKSDB_NAMESPACE::ListJni::getArrayListClass(env); - if (jarray_list_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID cstr_mid = - ROCKSDB_NAMESPACE::ListJni::getArrayListConstructorMethodId(env); - if (cstr_mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - jmethodID add_mid = ROCKSDB_NAMESPACE::ListJni::getListAddMethodId(env); - if (add_mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - // create java list - jobject jbackup_info_handle_list = - env->NewObject(jarray_list_clazz, cstr_mid, backup_infos.size()); - if (env->ExceptionCheck()) { - // exception occurred constructing object - return nullptr; - } - - // insert in java list - auto end = backup_infos.end(); - for (auto it = backup_infos.begin(); it != end; ++it) { - auto backup_info = *it; - - jobject obj = ROCKSDB_NAMESPACE::BackupInfoJni::construct0( - env, backup_info.backup_id, backup_info.timestamp, backup_info.size, - backup_info.number_files, backup_info.app_metadata); - if (env->ExceptionCheck()) { - // exception occurred constructing object - if (obj != nullptr) { - env->DeleteLocalRef(obj); - } - if (jbackup_info_handle_list != nullptr) { - env->DeleteLocalRef(jbackup_info_handle_list); - } - return nullptr; - } - - jboolean rs = - env->CallBooleanMethod(jbackup_info_handle_list, add_mid, obj); - if (env->ExceptionCheck() || rs == JNI_FALSE) { - // exception occurred calling method, or could not add - if (obj != nullptr) { - env->DeleteLocalRef(obj); - } - if (jbackup_info_handle_list != nullptr) { - env->DeleteLocalRef(jbackup_info_handle_list); - } - return nullptr; - } - } - - return jbackup_info_handle_list; - } -}; - -// The portal class for org.rocksdb.WBWIRocksIterator -class WBWIRocksIteratorJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.WBWIRocksIterator - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator"); - } - - /** - * Get the Java Field: WBWIRocksIterator#entry - * - * @param env A pointer to the Java environment - * - * @return The Java Field ID or nullptr if the class or field id could not - * be retrieved - */ - static jfieldID getWriteEntryField(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jfieldID fid = env->GetFieldID( - jclazz, "entry", "Lorg/rocksdb/WBWIRocksIterator$WriteEntry;"); - assert(fid != nullptr); - return fid; - } - - /** - * Gets the value of the WBWIRocksIterator#entry - * - * @param env A pointer to the Java environment - * @param jwbwi_rocks_iterator A reference to a WBWIIterator - * - * @return A reference to a Java WBWIRocksIterator.WriteEntry object, or - * a nullptr if an exception occurs - */ - static jobject getWriteEntry(JNIEnv* env, jobject jwbwi_rocks_iterator) { - assert(jwbwi_rocks_iterator != nullptr); - - jfieldID jwrite_entry_field = getWriteEntryField(env); - if (jwrite_entry_field == nullptr) { - // exception occurred accessing the field - return nullptr; - } - - jobject jwe = env->GetObjectField(jwbwi_rocks_iterator, jwrite_entry_field); - assert(jwe != nullptr); - return jwe; - } -}; - -// The portal class for org.rocksdb.WBWIRocksIterator.WriteType -class WriteTypeJni : public JavaClass { - public: - /** - * Get the PUT enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject PUT(JNIEnv* env) { return getEnum(env, "PUT"); } - - /** - * Get the MERGE enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject MERGE(JNIEnv* env) { return getEnum(env, "MERGE"); } - - /** - * Get the DELETE enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject DELETE(JNIEnv* env) { return getEnum(env, "DELETE"); } - - /** - * Get the LOG enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject LOG(JNIEnv* env) { return getEnum(env, "LOG"); } - - // Returns the equivalent org.rocksdb.WBWIRocksIterator.WriteType for the - // provided C++ ROCKSDB_NAMESPACE::WriteType enum - static jbyte toJavaWriteType(const ROCKSDB_NAMESPACE::WriteType& writeType) { - switch (writeType) { - case ROCKSDB_NAMESPACE::WriteType::kPutRecord: - return 0x0; - case ROCKSDB_NAMESPACE::WriteType::kMergeRecord: - return 0x1; - case ROCKSDB_NAMESPACE::WriteType::kDeleteRecord: - return 0x2; - case ROCKSDB_NAMESPACE::WriteType::kSingleDeleteRecord: - return 0x3; - case ROCKSDB_NAMESPACE::WriteType::kDeleteRangeRecord: - return 0x4; - case ROCKSDB_NAMESPACE::WriteType::kLogDataRecord: - return 0x5; - case ROCKSDB_NAMESPACE::WriteType::kXIDRecord: - return 0x6; - default: - return 0x7F; // undefined - } - } - - private: - /** - * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteType"); - } - - /** - * Get an enum field of org.rocksdb.WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * @param name The name of the enum field - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject getEnum(JNIEnv* env, const char name[]) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jfieldID jfid = env->GetStaticFieldID( - jclazz, name, "Lorg/rocksdb/WBWIRocksIterator$WriteType;"); - if (env->ExceptionCheck()) { - // exception occurred while getting field - return nullptr; - } else if (jfid == nullptr) { - return nullptr; - } - - jobject jwrite_type = env->GetStaticObjectField(jclazz, jfid); - assert(jwrite_type != nullptr); - return jwrite_type; - } -}; - -// The portal class for org.rocksdb.WBWIRocksIterator.WriteEntry -class WriteEntryJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteEntry - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, - "org/rocksdb/WBWIRocksIterator$WriteEntry"); - } -}; - -// The portal class for org.rocksdb.InfoLogLevel -class InfoLogLevelJni : public JavaClass { - public: - /** - * Get the DEBUG_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject DEBUG_LEVEL(JNIEnv* env) { - return getEnum(env, "DEBUG_LEVEL"); - } - - /** - * Get the INFO_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject INFO_LEVEL(JNIEnv* env) { return getEnum(env, "INFO_LEVEL"); } - - /** - * Get the WARN_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject WARN_LEVEL(JNIEnv* env) { return getEnum(env, "WARN_LEVEL"); } - - /** - * Get the ERROR_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject ERROR_LEVEL(JNIEnv* env) { - return getEnum(env, "ERROR_LEVEL"); - } - - /** - * Get the FATAL_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject FATAL_LEVEL(JNIEnv* env) { - return getEnum(env, "FATAL_LEVEL"); - } - - /** - * Get the HEADER_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject HEADER_LEVEL(JNIEnv* env) { - return getEnum(env, "HEADER_LEVEL"); - } - - private: - /** - * Get the Java Class org.rocksdb.InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/InfoLogLevel"); - } - - /** - * Get an enum field of org.rocksdb.InfoLogLevel - * - * @param env A pointer to the Java environment - * @param name The name of the enum field - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject getEnum(JNIEnv* env, const char name[]) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jfieldID jfid = - env->GetStaticFieldID(jclazz, name, "Lorg/rocksdb/InfoLogLevel;"); - if (env->ExceptionCheck()) { - // exception occurred while getting field - return nullptr; - } else if (jfid == nullptr) { - return nullptr; - } - - jobject jinfo_log_level = env->GetStaticObjectField(jclazz, jfid); - assert(jinfo_log_level != nullptr); - return jinfo_log_level; - } -}; - -// The portal class for org.rocksdb.Logger -class LoggerJni - : public RocksDBNativeClass< - std::shared_ptr*, LoggerJni> { - public: - /** - * Get the Java Class org/rocksdb/Logger - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Logger"); - } - - /** - * Get the Java Method: Logger#log - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getLogMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID( - jclazz, "log", "(Lorg/rocksdb/InfoLogLevel;Ljava/lang/String;)V"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.TransactionLogIterator.BatchResult -class BatchResultJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.TransactionLogIterator.BatchResult - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass( - env, "org/rocksdb/TransactionLogIterator$BatchResult"); - } - - /** - * Create a new Java org.rocksdb.TransactionLogIterator.BatchResult object - * with the same properties as the provided C++ ROCKSDB_NAMESPACE::BatchResult - * object - * - * @param env A pointer to the Java environment - * @param batch_result The ROCKSDB_NAMESPACE::BatchResult object - * - * @return A reference to a Java - * org.rocksdb.TransactionLogIterator.BatchResult object, - * or nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, - ROCKSDB_NAMESPACE::BatchResult& batch_result) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", "(JJ)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jobject jbatch_result = env->NewObject(jclazz, mid, batch_result.sequence, - batch_result.writeBatchPtr.get()); - if (jbatch_result == nullptr) { - // exception thrown: InstantiationException or OutOfMemoryError - return nullptr; - } - - batch_result.writeBatchPtr.release(); - return jbatch_result; - } -}; - -// The portal class for org.rocksdb.BottommostLevelCompaction -class BottommostLevelCompactionJni { - public: - // Returns the equivalent org.rocksdb.BottommostLevelCompaction for the - // provided C++ ROCKSDB_NAMESPACE::BottommostLevelCompaction enum - static jint toJavaBottommostLevelCompaction( - const ROCKSDB_NAMESPACE::BottommostLevelCompaction& - bottommost_level_compaction) { - switch (bottommost_level_compaction) { - case ROCKSDB_NAMESPACE::BottommostLevelCompaction::kSkip: - return 0x0; - case ROCKSDB_NAMESPACE::BottommostLevelCompaction:: - kIfHaveCompactionFilter: - return 0x1; - case ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForce: - return 0x2; - case ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForceOptimized: - return 0x3; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::BottommostLevelCompaction - // enum for the provided Java org.rocksdb.BottommostLevelCompaction - static ROCKSDB_NAMESPACE::BottommostLevelCompaction - toCppBottommostLevelCompaction(jint bottommost_level_compaction) { - switch (bottommost_level_compaction) { - case 0x0: - return ROCKSDB_NAMESPACE::BottommostLevelCompaction::kSkip; - case 0x1: - return ROCKSDB_NAMESPACE::BottommostLevelCompaction:: - kIfHaveCompactionFilter; - case 0x2: - return ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForce; - case 0x3: - return ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForceOptimized; - default: - // undefined/default - return ROCKSDB_NAMESPACE::BottommostLevelCompaction:: - kIfHaveCompactionFilter; - } - } -}; - -// The portal class for org.rocksdb.CompactionStopStyle -class CompactionStopStyleJni { - public: - // Returns the equivalent org.rocksdb.CompactionStopStyle for the provided - // C++ ROCKSDB_NAMESPACE::CompactionStopStyle enum - static jbyte toJavaCompactionStopStyle( - const ROCKSDB_NAMESPACE::CompactionStopStyle& compaction_stop_style) { - switch (compaction_stop_style) { - case ROCKSDB_NAMESPACE::CompactionStopStyle:: - kCompactionStopStyleSimilarSize: - return 0x0; - case ROCKSDB_NAMESPACE::CompactionStopStyle:: - kCompactionStopStyleTotalSize: - return 0x1; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionStopStyle enum for - // the provided Java org.rocksdb.CompactionStopStyle - static ROCKSDB_NAMESPACE::CompactionStopStyle toCppCompactionStopStyle( - jbyte jcompaction_stop_style) { - switch (jcompaction_stop_style) { - case 0x0: - return ROCKSDB_NAMESPACE::CompactionStopStyle:: - kCompactionStopStyleSimilarSize; - case 0x1: - return ROCKSDB_NAMESPACE::CompactionStopStyle:: - kCompactionStopStyleTotalSize; - default: - // undefined/default - return ROCKSDB_NAMESPACE::CompactionStopStyle:: - kCompactionStopStyleSimilarSize; - } - } -}; - -// The portal class for org.rocksdb.CompressionType -class CompressionTypeJni { - public: - // Returns the equivalent org.rocksdb.CompressionType for the provided - // C++ ROCKSDB_NAMESPACE::CompressionType enum - static jbyte toJavaCompressionType( - const ROCKSDB_NAMESPACE::CompressionType& compression_type) { - switch (compression_type) { - case ROCKSDB_NAMESPACE::CompressionType::kNoCompression: - return 0x0; - case ROCKSDB_NAMESPACE::CompressionType::kSnappyCompression: - return 0x1; - case ROCKSDB_NAMESPACE::CompressionType::kZlibCompression: - return 0x2; - case ROCKSDB_NAMESPACE::CompressionType::kBZip2Compression: - return 0x3; - case ROCKSDB_NAMESPACE::CompressionType::kLZ4Compression: - return 0x4; - case ROCKSDB_NAMESPACE::CompressionType::kLZ4HCCompression: - return 0x5; - case ROCKSDB_NAMESPACE::CompressionType::kXpressCompression: - return 0x6; - case ROCKSDB_NAMESPACE::CompressionType::kZSTD: - return 0x7; - case ROCKSDB_NAMESPACE::CompressionType::kDisableCompressionOption: - default: - return 0x7F; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompressionType enum for the - // provided Java org.rocksdb.CompressionType - static ROCKSDB_NAMESPACE::CompressionType toCppCompressionType( - jbyte jcompression_type) { - switch (jcompression_type) { - case 0x0: - return ROCKSDB_NAMESPACE::CompressionType::kNoCompression; - case 0x1: - return ROCKSDB_NAMESPACE::CompressionType::kSnappyCompression; - case 0x2: - return ROCKSDB_NAMESPACE::CompressionType::kZlibCompression; - case 0x3: - return ROCKSDB_NAMESPACE::CompressionType::kBZip2Compression; - case 0x4: - return ROCKSDB_NAMESPACE::CompressionType::kLZ4Compression; - case 0x5: - return ROCKSDB_NAMESPACE::CompressionType::kLZ4HCCompression; - case 0x6: - return ROCKSDB_NAMESPACE::CompressionType::kXpressCompression; - case 0x7: - return ROCKSDB_NAMESPACE::CompressionType::kZSTD; - case 0x7F: - default: - return ROCKSDB_NAMESPACE::CompressionType::kDisableCompressionOption; - } - } -}; - -// The portal class for org.rocksdb.CompactionPriority -class CompactionPriorityJni { - public: - // Returns the equivalent org.rocksdb.CompactionPriority for the provided - // C++ ROCKSDB_NAMESPACE::CompactionPri enum - static jbyte toJavaCompactionPriority( - const ROCKSDB_NAMESPACE::CompactionPri& compaction_priority) { - switch (compaction_priority) { - case ROCKSDB_NAMESPACE::CompactionPri::kByCompensatedSize: - return 0x0; - case ROCKSDB_NAMESPACE::CompactionPri::kOldestLargestSeqFirst: - return 0x1; - case ROCKSDB_NAMESPACE::CompactionPri::kOldestSmallestSeqFirst: - return 0x2; - case ROCKSDB_NAMESPACE::CompactionPri::kMinOverlappingRatio: - return 0x3; - case ROCKSDB_NAMESPACE::CompactionPri::kRoundRobin: - return 0x4; - default: - return 0x0; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionPri enum for the - // provided Java org.rocksdb.CompactionPriority - static ROCKSDB_NAMESPACE::CompactionPri toCppCompactionPriority( - jbyte jcompaction_priority) { - switch (jcompaction_priority) { - case 0x0: - return ROCKSDB_NAMESPACE::CompactionPri::kByCompensatedSize; - case 0x1: - return ROCKSDB_NAMESPACE::CompactionPri::kOldestLargestSeqFirst; - case 0x2: - return ROCKSDB_NAMESPACE::CompactionPri::kOldestSmallestSeqFirst; - case 0x3: - return ROCKSDB_NAMESPACE::CompactionPri::kMinOverlappingRatio; - case 0x4: - return ROCKSDB_NAMESPACE::CompactionPri::kRoundRobin; - default: - // undefined/default - return ROCKSDB_NAMESPACE::CompactionPri::kByCompensatedSize; - } - } -}; - -// The portal class for org.rocksdb.AccessHint -class AccessHintJni { - public: - // Returns the equivalent org.rocksdb.AccessHint for the provided - // C++ ROCKSDB_NAMESPACE::DBOptions::AccessHint enum - static jbyte toJavaAccessHint( - const ROCKSDB_NAMESPACE::DBOptions::AccessHint& access_hint) { - switch (access_hint) { - case ROCKSDB_NAMESPACE::DBOptions::AccessHint::NONE: - return 0x0; - case ROCKSDB_NAMESPACE::DBOptions::AccessHint::NORMAL: - return 0x1; - case ROCKSDB_NAMESPACE::DBOptions::AccessHint::SEQUENTIAL: - return 0x2; - case ROCKSDB_NAMESPACE::DBOptions::AccessHint::WILLNEED: - return 0x3; - default: - // undefined/default - return 0x1; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::DBOptions::AccessHint enum - // for the provided Java org.rocksdb.AccessHint - static ROCKSDB_NAMESPACE::DBOptions::AccessHint toCppAccessHint( - jbyte jaccess_hint) { - switch (jaccess_hint) { - case 0x0: - return ROCKSDB_NAMESPACE::DBOptions::AccessHint::NONE; - case 0x1: - return ROCKSDB_NAMESPACE::DBOptions::AccessHint::NORMAL; - case 0x2: - return ROCKSDB_NAMESPACE::DBOptions::AccessHint::SEQUENTIAL; - case 0x3: - return ROCKSDB_NAMESPACE::DBOptions::AccessHint::WILLNEED; - default: - // undefined/default - return ROCKSDB_NAMESPACE::DBOptions::AccessHint::NORMAL; - } - } -}; - -// The portal class for org.rocksdb.WALRecoveryMode -class WALRecoveryModeJni { - public: - // Returns the equivalent org.rocksdb.WALRecoveryMode for the provided - // C++ ROCKSDB_NAMESPACE::WALRecoveryMode enum - static jbyte toJavaWALRecoveryMode( - const ROCKSDB_NAMESPACE::WALRecoveryMode& wal_recovery_mode) { - switch (wal_recovery_mode) { - case ROCKSDB_NAMESPACE::WALRecoveryMode::kTolerateCorruptedTailRecords: - return 0x0; - case ROCKSDB_NAMESPACE::WALRecoveryMode::kAbsoluteConsistency: - return 0x1; - case ROCKSDB_NAMESPACE::WALRecoveryMode::kPointInTimeRecovery: - return 0x2; - case ROCKSDB_NAMESPACE::WALRecoveryMode::kSkipAnyCorruptedRecords: - return 0x3; - default: - // undefined/default - return 0x2; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::WALRecoveryMode enum for the - // provided Java org.rocksdb.WALRecoveryMode - static ROCKSDB_NAMESPACE::WALRecoveryMode toCppWALRecoveryMode( - jbyte jwal_recovery_mode) { - switch (jwal_recovery_mode) { - case 0x0: - return ROCKSDB_NAMESPACE::WALRecoveryMode:: - kTolerateCorruptedTailRecords; - case 0x1: - return ROCKSDB_NAMESPACE::WALRecoveryMode::kAbsoluteConsistency; - case 0x2: - return ROCKSDB_NAMESPACE::WALRecoveryMode::kPointInTimeRecovery; - case 0x3: - return ROCKSDB_NAMESPACE::WALRecoveryMode::kSkipAnyCorruptedRecords; - default: - // undefined/default - return ROCKSDB_NAMESPACE::WALRecoveryMode::kPointInTimeRecovery; - } - } -}; - -// The portal class for org.rocksdb.TickerType -class TickerTypeJni { - public: - // Returns the equivalent org.rocksdb.TickerType for the provided - // C++ ROCKSDB_NAMESPACE::Tickers enum - static jbyte toJavaTickerType(const ROCKSDB_NAMESPACE::Tickers& tickers) { - switch (tickers) { - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_MISS: - return 0x0; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_HIT: - return 0x1; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD: - return 0x2; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD_FAILURES: - return 0x3; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_MISS: - return 0x4; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_HIT: - return 0x5; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_ADD: - return 0x6; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT: - return 0x7; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_MISS: - return 0x9; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_HIT: - return 0xA; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_ADD: - return 0xB; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT: - return 0xC; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_MISS: - return 0xE; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_HIT: - return 0xF; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_ADD: - return 0x10; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT: - return 0x11; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_READ: - return 0x12; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_WRITE: - return 0x13; - case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_USEFUL: - return 0x14; - case ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_HIT: - return 0x15; - case ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_MISS: - return 0x16; - case ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_HIT: - return 0x17; - case ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_MISS: - return 0x18; - case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_HIT: - return 0x19; - case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_MISS: - return 0x1A; - case ROCKSDB_NAMESPACE::Tickers::GET_HIT_L0: - return 0x1B; - case ROCKSDB_NAMESPACE::Tickers::GET_HIT_L1: - return 0x1C; - case ROCKSDB_NAMESPACE::Tickers::GET_HIT_L2_AND_UP: - return 0x1D; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY: - return 0x1E; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_OBSOLETE: - return 0x1F; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_RANGE_DEL: - return 0x20; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_USER: - return 0x21; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE: - return 0x22; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_WRITTEN: - return 0x23; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_READ: - return 0x24; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_UPDATED: - return 0x25; - case ROCKSDB_NAMESPACE::Tickers::BYTES_WRITTEN: - return 0x26; - case ROCKSDB_NAMESPACE::Tickers::BYTES_READ: - return 0x27; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK: - return 0x28; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT: - return 0x29; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV: - return 0x2A; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK_FOUND: - return 0x2B; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT_FOUND: - return 0x2C; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV_FOUND: - return 0x2D; - case ROCKSDB_NAMESPACE::Tickers::ITER_BYTES_READ: - return 0x2E; - case ROCKSDB_NAMESPACE::Tickers::NO_FILE_OPENS: - return 0x30; - case ROCKSDB_NAMESPACE::Tickers::NO_FILE_ERRORS: - return 0x31; - case ROCKSDB_NAMESPACE::Tickers::STALL_MICROS: - return 0x35; - case ROCKSDB_NAMESPACE::Tickers::DB_MUTEX_WAIT_MICROS: - return 0x36; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_CALLS: - return 0x39; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_READ: - return 0x3A; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_BYTES_READ: - return 0x3B; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_MERGE_FAILURES: - return 0x3D; - case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_CHECKED: - return 0x3E; - case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_USEFUL: - return 0x3F; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION: - return 0x40; - case ROCKSDB_NAMESPACE::Tickers::GET_UPDATES_SINCE_CALLS: - return 0x41; - case ROCKSDB_NAMESPACE::Tickers::WAL_FILE_SYNCED: - return 0x46; - case ROCKSDB_NAMESPACE::Tickers::WAL_FILE_BYTES: - return 0x47; - case ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_SELF: - return 0x48; - case ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_OTHER: - return 0x49; - case ROCKSDB_NAMESPACE::Tickers::WRITE_WITH_WAL: - return 0x4B; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES: - return 0x4C; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES: - return 0x4D; - case ROCKSDB_NAMESPACE::Tickers::FLUSH_WRITE_BYTES: - return 0x4E; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES: - return 0x4F; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_ACQUIRES: - return 0x50; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_RELEASES: - return 0x51; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_CLEANUPS: - return 0x52; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_COMPRESSED: - return 0x53; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_DECOMPRESSED: - return 0x54; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_NOT_COMPRESSED: - return 0x55; - case ROCKSDB_NAMESPACE::Tickers::MERGE_OPERATION_TOTAL_TIME: - return 0x56; - case ROCKSDB_NAMESPACE::Tickers::FILTER_OPERATION_TOTAL_TIME: - return 0x57; - case ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_HIT: - return 0x58; - case ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_MISS: - return 0x59; - case ROCKSDB_NAMESPACE::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES: - return 0x5A; - case ROCKSDB_NAMESPACE::Tickers::READ_AMP_TOTAL_READ_BYTES: - return 0x5B; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_RATE_LIMITER_DRAINS: - return 0x5C; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_ITER_SKIP: - return 0x5D; - case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_FOUND: - return 0x5E; - case ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_CREATED: - // -0x01 so we can skip over the already taken 0x5F (TICKER_ENUM_MAX). - return -0x01; - case ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_DELETED: - return 0x60; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE: - return 0x61; - case ROCKSDB_NAMESPACE::Tickers::COMPACTION_CANCELLED: - return 0x62; - case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_POSITIVE: - return 0x63; - case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_TRUE_POSITIVE: - return 0x64; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PUT: - return 0x65; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_WRITE: - return 0x66; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_GET: - return 0x67; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_MULTIGET: - return 0x68; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_SEEK: - return 0x69; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_NEXT: - return 0x6A; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PREV: - return 0x6B; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_WRITTEN: - return 0x6C; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_READ: - return 0x6D; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_WRITTEN: - return 0x6E; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_READ: - return 0x6F; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED: - return 0x70; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED_TTL: - return 0x71; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB: - return 0x72; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB_TTL: - return 0x73; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_WRITTEN: - return 0x74; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_READ: - return 0x75; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_SYNCED: - return 0x76; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_COUNT: - return 0x77; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_SIZE: - return 0x78; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_COUNT: - return 0x79; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_SIZE: - return 0x7A; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_FILES: - return 0x7B; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_NEW_FILES: - return 0x7C; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_FAILURES: - return 0x7D; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED: - return -0x02; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_RELOCATED: - return -0x05; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_FILES_EVICTED: - return -0x06; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_KEYS_EVICTED: - return -0x07; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_BYTES_EVICTED: - return -0x08; - case ROCKSDB_NAMESPACE::Tickers::TXN_PREPARE_MUTEX_OVERHEAD: - return -0x09; - case ROCKSDB_NAMESPACE::Tickers::TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD: - return -0x0A; - case ROCKSDB_NAMESPACE::Tickers::TXN_DUPLICATE_KEY_OVERHEAD: - return -0x0B; - case ROCKSDB_NAMESPACE::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD: - return -0x0C; - case ROCKSDB_NAMESPACE::Tickers::TXN_GET_TRY_AGAIN: - return -0x0D; - case ROCKSDB_NAMESPACE::Tickers::FILES_MARKED_TRASH: - return -0x0E; - case ROCKSDB_NAMESPACE::Tickers::FILES_DELETED_IMMEDIATELY: - return -0X0F; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_MARKED: - return -0x10; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_PERIODIC: - return -0x11; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_TTL: - return -0x12; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_MARKED: - return -0x13; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_PERIODIC: - return -0x14; - case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_TTL: - return -0x15; - case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_ERROR_COUNT: - return -0x16; - case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_IO_ERROR_COUNT: - return -0x17; - case ROCKSDB_NAMESPACE::Tickers:: - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT: - return -0x18; - case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_AUTORESUME_COUNT: - return -0x19; - case ROCKSDB_NAMESPACE::Tickers:: - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT: - return -0x1A; - case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT: - return -0x1B; - case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_PAYLOAD_BYTES_AT_FLUSH: - return -0x1C; - case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_GARBAGE_BYTES_AT_FLUSH: - return -0x1D; - case ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_HITS: - return -0x1E; - case ROCKSDB_NAMESPACE::Tickers::VERIFY_CHECKSUM_READ_BYTES: - return -0x1F; - case ROCKSDB_NAMESPACE::Tickers::BACKUP_READ_BYTES: - return -0x20; - case ROCKSDB_NAMESPACE::Tickers::BACKUP_WRITE_BYTES: - return -0x21; - case ROCKSDB_NAMESPACE::Tickers::REMOTE_COMPACT_READ_BYTES: - return -0x22; - case ROCKSDB_NAMESPACE::Tickers::REMOTE_COMPACT_WRITE_BYTES: - return -0x23; - case ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_BYTES: - return -0x24; - case ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_BYTES: - return -0x25; - case ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_BYTES: - return -0x26; - case ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_COUNT: - return -0x27; - case ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_COUNT: - return -0x28; - case ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_COUNT: - return -0x29; - case ROCKSDB_NAMESPACE::Tickers::LAST_LEVEL_READ_BYTES: - return -0x2A; - case ROCKSDB_NAMESPACE::Tickers::LAST_LEVEL_READ_COUNT: - return -0x2B; - case ROCKSDB_NAMESPACE::Tickers::NON_LAST_LEVEL_READ_BYTES: - return -0x2C; - case ROCKSDB_NAMESPACE::Tickers::NON_LAST_LEVEL_READ_COUNT: - return -0x2D; - case ROCKSDB_NAMESPACE::Tickers::BLOCK_CHECKSUM_COMPUTE_COUNT: - return -0x2E; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_MISS: - return -0x2F; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_HIT: - return -0x30; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_ADD: - return -0x31; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_ADD_FAILURES: - return -0x32; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_READ: - return -0x33; - case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_WRITE: - return -0x34; - case ROCKSDB_NAMESPACE::Tickers::READ_ASYNC_MICROS: - return -0x35; - case ROCKSDB_NAMESPACE::Tickers::ASYNC_READ_ERROR_COUNT: - return -0x36; - case ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_FILTER_HITS: - return -0x37; - case ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_INDEX_HITS: - return -0x38; - case ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_DATA_HITS: - return -0x39; - case ROCKSDB_NAMESPACE::Tickers::TABLE_OPEN_PREFETCH_TAIL_MISS: - return -0x3A; - case ROCKSDB_NAMESPACE::Tickers::TABLE_OPEN_PREFETCH_TAIL_HIT: - return -0x3B; - case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX: - // 0x5F was the max value in the initial copy of tickers to Java. - // Since these values are exposed directly to Java clients, we keep - // the value the same forever. - // - // TODO: This particular case seems confusing and unnecessary to pin the - // value since it's meant to be the number of tickers, not an actual - // ticker value. But we aren't yet in a position to fix it since the - // number of tickers doesn't fit in the Java representation (jbyte). - return 0x5F; - default: - // undefined/default - return 0x0; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::Tickers enum for the - // provided Java org.rocksdb.TickerType - static ROCKSDB_NAMESPACE::Tickers toCppTickers(jbyte jticker_type) { - switch (jticker_type) { - case 0x0: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_MISS; - case 0x1: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_HIT; - case 0x2: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD; - case 0x3: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD_FAILURES; - case 0x4: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_MISS; - case 0x5: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_HIT; - case 0x6: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_ADD; - case 0x7: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT; - case 0x9: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_MISS; - case 0xA: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_HIT; - case 0xB: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_ADD; - case 0xC: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT; - case 0xE: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_MISS; - case 0xF: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_HIT; - case 0x10: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_ADD; - case 0x11: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT; - case 0x12: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_READ; - case 0x13: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_WRITE; - case 0x14: - return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_USEFUL; - case 0x15: - return ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_HIT; - case 0x16: - return ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_MISS; - case 0x17: - return ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_HIT; - case 0x18: - return ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_MISS; - case 0x19: - return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_HIT; - case 0x1A: - return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_MISS; - case 0x1B: - return ROCKSDB_NAMESPACE::Tickers::GET_HIT_L0; - case 0x1C: - return ROCKSDB_NAMESPACE::Tickers::GET_HIT_L1; - case 0x1D: - return ROCKSDB_NAMESPACE::Tickers::GET_HIT_L2_AND_UP; - case 0x1E: - return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY; - case 0x1F: - return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_OBSOLETE; - case 0x20: - return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_RANGE_DEL; - case 0x21: - return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_USER; - case 0x22: - return ROCKSDB_NAMESPACE::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE; - case 0x23: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_WRITTEN; - case 0x24: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_READ; - case 0x25: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_UPDATED; - case 0x26: - return ROCKSDB_NAMESPACE::Tickers::BYTES_WRITTEN; - case 0x27: - return ROCKSDB_NAMESPACE::Tickers::BYTES_READ; - case 0x28: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK; - case 0x29: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT; - case 0x2A: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV; - case 0x2B: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK_FOUND; - case 0x2C: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT_FOUND; - case 0x2D: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV_FOUND; - case 0x2E: - return ROCKSDB_NAMESPACE::Tickers::ITER_BYTES_READ; - case 0x30: - return ROCKSDB_NAMESPACE::Tickers::NO_FILE_OPENS; - case 0x31: - return ROCKSDB_NAMESPACE::Tickers::NO_FILE_ERRORS; - case 0x35: - return ROCKSDB_NAMESPACE::Tickers::STALL_MICROS; - case 0x36: - return ROCKSDB_NAMESPACE::Tickers::DB_MUTEX_WAIT_MICROS; - case 0x39: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_CALLS; - case 0x3A: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_READ; - case 0x3B: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_BYTES_READ; - case 0x3D: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_MERGE_FAILURES; - case 0x3E: - return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_CHECKED; - case 0x3F: - return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_USEFUL; - case 0x40: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION; - case 0x41: - return ROCKSDB_NAMESPACE::Tickers::GET_UPDATES_SINCE_CALLS; - case 0x46: - return ROCKSDB_NAMESPACE::Tickers::WAL_FILE_SYNCED; - case 0x47: - return ROCKSDB_NAMESPACE::Tickers::WAL_FILE_BYTES; - case 0x48: - return ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_SELF; - case 0x49: - return ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_OTHER; - case 0x4B: - return ROCKSDB_NAMESPACE::Tickers::WRITE_WITH_WAL; - case 0x4C: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES; - case 0x4D: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES; - case 0x4E: - return ROCKSDB_NAMESPACE::Tickers::FLUSH_WRITE_BYTES; - case 0x4F: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES; - case 0x50: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_ACQUIRES; - case 0x51: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_RELEASES; - case 0x52: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_CLEANUPS; - case 0x53: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_COMPRESSED; - case 0x54: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_DECOMPRESSED; - case 0x55: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_NOT_COMPRESSED; - case 0x56: - return ROCKSDB_NAMESPACE::Tickers::MERGE_OPERATION_TOTAL_TIME; - case 0x57: - return ROCKSDB_NAMESPACE::Tickers::FILTER_OPERATION_TOTAL_TIME; - case 0x58: - return ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_HIT; - case 0x59: - return ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_MISS; - case 0x5A: - return ROCKSDB_NAMESPACE::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES; - case 0x5B: - return ROCKSDB_NAMESPACE::Tickers::READ_AMP_TOTAL_READ_BYTES; - case 0x5C: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_RATE_LIMITER_DRAINS; - case 0x5D: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_ITER_SKIP; - case 0x5E: - return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_FOUND; - case -0x01: - // -0x01 so we can skip over the already taken 0x5F (TICKER_ENUM_MAX). - return ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_CREATED; - case 0x60: - return ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_DELETED; - case 0x61: - return ROCKSDB_NAMESPACE::Tickers:: - COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE; - case 0x62: - return ROCKSDB_NAMESPACE::Tickers::COMPACTION_CANCELLED; - case 0x63: - return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_POSITIVE; - case 0x64: - return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_TRUE_POSITIVE; - case 0x65: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PUT; - case 0x66: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_WRITE; - case 0x67: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_GET; - case 0x68: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_MULTIGET; - case 0x69: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_SEEK; - case 0x6A: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_NEXT; - case 0x6B: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PREV; - case 0x6C: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_WRITTEN; - case 0x6D: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_READ; - case 0x6E: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_WRITTEN; - case 0x6F: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_READ; - case 0x70: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED; - case 0x71: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED_TTL; - case 0x72: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB; - case 0x73: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB_TTL; - case 0x74: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_WRITTEN; - case 0x75: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_READ; - case 0x76: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_SYNCED; - case 0x77: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_COUNT; - case 0x78: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_SIZE; - case 0x79: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_COUNT; - case 0x7A: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_SIZE; - case 0x7B: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_FILES; - case 0x7C: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_NEW_FILES; - case 0x7D: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_FAILURES; - case -0x02: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED; - case -0x05: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_RELOCATED; - case -0x06: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_FILES_EVICTED; - case -0x07: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_KEYS_EVICTED; - case -0x08: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_BYTES_EVICTED; - case -0x09: - return ROCKSDB_NAMESPACE::Tickers::TXN_PREPARE_MUTEX_OVERHEAD; - case -0x0A: - return ROCKSDB_NAMESPACE::Tickers::TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD; - case -0x0B: - return ROCKSDB_NAMESPACE::Tickers::TXN_DUPLICATE_KEY_OVERHEAD; - case -0x0C: - return ROCKSDB_NAMESPACE::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD; - case -0x0D: - return ROCKSDB_NAMESPACE::Tickers::TXN_GET_TRY_AGAIN; - case -0x0E: - return ROCKSDB_NAMESPACE::Tickers::FILES_MARKED_TRASH; - case -0x0F: - return ROCKSDB_NAMESPACE::Tickers::FILES_DELETED_IMMEDIATELY; - case -0x10: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_MARKED; - case -0x11: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_PERIODIC; - case -0x12: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_TTL; - case -0x13: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_MARKED; - case -0x14: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_PERIODIC; - case -0x15: - return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_TTL; - case -0x16: - return ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_ERROR_COUNT; - case -0x17: - return ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_IO_ERROR_COUNT; - case -0x18: - return ROCKSDB_NAMESPACE::Tickers:: - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT; - case -0x19: - return ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_AUTORESUME_COUNT; - case -0x1A: - return ROCKSDB_NAMESPACE::Tickers:: - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT; - case -0x1B: - return ROCKSDB_NAMESPACE::Tickers:: - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT; - case -0x1C: - return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_PAYLOAD_BYTES_AT_FLUSH; - case -0x1D: - return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_GARBAGE_BYTES_AT_FLUSH; - case -0x1E: - return ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_HITS; - case -0x1F: - return ROCKSDB_NAMESPACE::Tickers::VERIFY_CHECKSUM_READ_BYTES; - case -0x20: - return ROCKSDB_NAMESPACE::Tickers::BACKUP_READ_BYTES; - case -0x21: - return ROCKSDB_NAMESPACE::Tickers::BACKUP_WRITE_BYTES; - case -0x22: - return ROCKSDB_NAMESPACE::Tickers::REMOTE_COMPACT_READ_BYTES; - case -0x23: - return ROCKSDB_NAMESPACE::Tickers::REMOTE_COMPACT_WRITE_BYTES; - case -0x24: - return ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_BYTES; - case -0x25: - return ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_BYTES; - case -0x26: - return ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_BYTES; - case -0x27: - return ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_COUNT; - case -0x28: - return ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_COUNT; - case -0x29: - return ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_COUNT; - case -0x2A: - return ROCKSDB_NAMESPACE::Tickers::LAST_LEVEL_READ_BYTES; - case -0x2B: - return ROCKSDB_NAMESPACE::Tickers::LAST_LEVEL_READ_COUNT; - case -0x2C: - return ROCKSDB_NAMESPACE::Tickers::NON_LAST_LEVEL_READ_BYTES; - case -0x2D: - return ROCKSDB_NAMESPACE::Tickers::NON_LAST_LEVEL_READ_COUNT; - case -0x2E: - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CHECKSUM_COMPUTE_COUNT; - case -0x2F: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_MISS; - case -0x30: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_HIT; - case -0x31: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_ADD; - case -0x32: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_ADD_FAILURES; - case -0x33: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_READ; - case -0x34: - return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_WRITE; - case -0x35: - return ROCKSDB_NAMESPACE::Tickers::READ_ASYNC_MICROS; - case -0x36: - return ROCKSDB_NAMESPACE::Tickers::ASYNC_READ_ERROR_COUNT; - case -0x37: - return ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_FILTER_HITS; - case -0x38: - return ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_INDEX_HITS; - case -0x39: - return ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_DATA_HITS; - case -0x3A: - return ROCKSDB_NAMESPACE::Tickers::TABLE_OPEN_PREFETCH_TAIL_MISS; - case -0x3B: - return ROCKSDB_NAMESPACE::Tickers::TABLE_OPEN_PREFETCH_TAIL_HIT; - case 0x5F: - // 0x5F was the max value in the initial copy of tickers to Java. - // Since these values are exposed directly to Java clients, we keep - // the value the same forever. - // - // TODO: This particular case seems confusing and unnecessary to pin the - // value since it's meant to be the number of tickers, not an actual - // ticker value. But we aren't yet in a position to fix it since the - // number of tickers doesn't fit in the Java representation (jbyte). - return ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX; - - default: - // undefined/default - return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_MISS; - } - } -}; - -// The portal class for org.rocksdb.HistogramType -class HistogramTypeJni { - public: - // Returns the equivalent org.rocksdb.HistogramType for the provided - // C++ ROCKSDB_NAMESPACE::Histograms enum - static jbyte toJavaHistogramsType( - const ROCKSDB_NAMESPACE::Histograms& histograms) { - switch (histograms) { - case ROCKSDB_NAMESPACE::Histograms::DB_GET: - return 0x0; - case ROCKSDB_NAMESPACE::Histograms::DB_WRITE: - return 0x1; - case ROCKSDB_NAMESPACE::Histograms::COMPACTION_TIME: - return 0x2; - case ROCKSDB_NAMESPACE::Histograms::SUBCOMPACTION_SETUP_TIME: - return 0x3; - case ROCKSDB_NAMESPACE::Histograms::TABLE_SYNC_MICROS: - return 0x4; - case ROCKSDB_NAMESPACE::Histograms::COMPACTION_OUTFILE_SYNC_MICROS: - return 0x5; - case ROCKSDB_NAMESPACE::Histograms::WAL_FILE_SYNC_MICROS: - return 0x6; - case ROCKSDB_NAMESPACE::Histograms::MANIFEST_FILE_SYNC_MICROS: - return 0x7; - case ROCKSDB_NAMESPACE::Histograms::TABLE_OPEN_IO_MICROS: - return 0x8; - case ROCKSDB_NAMESPACE::Histograms::DB_MULTIGET: - return 0x9; - case ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_COMPACTION_MICROS: - return 0xA; - case ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_GET_MICROS: - return 0xB; - case ROCKSDB_NAMESPACE::Histograms::WRITE_RAW_BLOCK_MICROS: - return 0xC; - case ROCKSDB_NAMESPACE::Histograms::NUM_FILES_IN_SINGLE_COMPACTION: - return 0x12; - case ROCKSDB_NAMESPACE::Histograms::DB_SEEK: - return 0x13; - case ROCKSDB_NAMESPACE::Histograms::WRITE_STALL: - return 0x14; - case ROCKSDB_NAMESPACE::Histograms::SST_READ_MICROS: - return 0x15; - case ROCKSDB_NAMESPACE::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED: - return 0x16; - case ROCKSDB_NAMESPACE::Histograms::BYTES_PER_READ: - return 0x17; - case ROCKSDB_NAMESPACE::Histograms::BYTES_PER_WRITE: - return 0x18; - case ROCKSDB_NAMESPACE::Histograms::BYTES_PER_MULTIGET: - return 0x19; - case ROCKSDB_NAMESPACE::Histograms::BYTES_COMPRESSED: - return 0x1A; - case ROCKSDB_NAMESPACE::Histograms::BYTES_DECOMPRESSED: - return 0x1B; - case ROCKSDB_NAMESPACE::Histograms::COMPRESSION_TIMES_NANOS: - return 0x1C; - case ROCKSDB_NAMESPACE::Histograms::DECOMPRESSION_TIMES_NANOS: - return 0x1D; - case ROCKSDB_NAMESPACE::Histograms::READ_NUM_MERGE_OPERANDS: - return 0x1E; - // 0x20 to skip 0x1F so TICKER_ENUM_MAX remains unchanged for minor - // version compatibility. - case ROCKSDB_NAMESPACE::Histograms::FLUSH_TIME: - return 0x20; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_KEY_SIZE: - return 0x21; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_VALUE_SIZE: - return 0x22; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_WRITE_MICROS: - return 0x23; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GET_MICROS: - return 0x24; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_MULTIGET_MICROS: - return 0x25; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_SEEK_MICROS: - return 0x26; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_NEXT_MICROS: - return 0x27; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_PREV_MICROS: - return 0x28; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS: - return 0x29; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_READ_MICROS: - return 0x2A; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_SYNC_MICROS: - return 0x2B; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_COMPRESSION_MICROS: - return 0x2D; - case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_DECOMPRESSION_MICROS: - return 0x2E; - case ROCKSDB_NAMESPACE::Histograms:: - NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL: - return 0x2F; - case ROCKSDB_NAMESPACE::Histograms::NUM_SST_READ_PER_LEVEL: - return 0x31; - case ROCKSDB_NAMESPACE::Histograms::ERROR_HANDLER_AUTORESUME_RETRY_COUNT: - return 0x32; - case ROCKSDB_NAMESPACE::Histograms::ASYNC_READ_BYTES: - return 0x33; - case ROCKSDB_NAMESPACE::Histograms::POLL_WAIT_MICROS: - return 0x34; - case ROCKSDB_NAMESPACE::Histograms::PREFETCHED_BYTES_DISCARDED: - return 0x35; - case ROCKSDB_NAMESPACE::Histograms::MULTIGET_IO_BATCH_SIZE: - return 0x36; - case NUM_LEVEL_READ_PER_MULTIGET: - return 0x37; - case ASYNC_PREFETCH_ABORT_MICROS: - return 0x38; - case ROCKSDB_NAMESPACE::Histograms::TABLE_OPEN_PREFETCH_TAIL_READ_BYTES: - return 0x39; - case ROCKSDB_NAMESPACE::Histograms::HISTOGRAM_ENUM_MAX: - // 0x1F for backwards compatibility on current minor version. - return 0x1F; - - default: - // undefined/default - return 0x0; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::Histograms enum for the - // provided Java org.rocksdb.HistogramsType - static ROCKSDB_NAMESPACE::Histograms toCppHistograms(jbyte jhistograms_type) { - switch (jhistograms_type) { - case 0x0: - return ROCKSDB_NAMESPACE::Histograms::DB_GET; - case 0x1: - return ROCKSDB_NAMESPACE::Histograms::DB_WRITE; - case 0x2: - return ROCKSDB_NAMESPACE::Histograms::COMPACTION_TIME; - case 0x3: - return ROCKSDB_NAMESPACE::Histograms::SUBCOMPACTION_SETUP_TIME; - case 0x4: - return ROCKSDB_NAMESPACE::Histograms::TABLE_SYNC_MICROS; - case 0x5: - return ROCKSDB_NAMESPACE::Histograms::COMPACTION_OUTFILE_SYNC_MICROS; - case 0x6: - return ROCKSDB_NAMESPACE::Histograms::WAL_FILE_SYNC_MICROS; - case 0x7: - return ROCKSDB_NAMESPACE::Histograms::MANIFEST_FILE_SYNC_MICROS; - case 0x8: - return ROCKSDB_NAMESPACE::Histograms::TABLE_OPEN_IO_MICROS; - case 0x9: - return ROCKSDB_NAMESPACE::Histograms::DB_MULTIGET; - case 0xA: - return ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_COMPACTION_MICROS; - case 0xB: - return ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_GET_MICROS; - case 0xC: - return ROCKSDB_NAMESPACE::Histograms::WRITE_RAW_BLOCK_MICROS; - case 0x12: - return ROCKSDB_NAMESPACE::Histograms::NUM_FILES_IN_SINGLE_COMPACTION; - case 0x13: - return ROCKSDB_NAMESPACE::Histograms::DB_SEEK; - case 0x14: - return ROCKSDB_NAMESPACE::Histograms::WRITE_STALL; - case 0x15: - return ROCKSDB_NAMESPACE::Histograms::SST_READ_MICROS; - case 0x16: - return ROCKSDB_NAMESPACE::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED; - case 0x17: - return ROCKSDB_NAMESPACE::Histograms::BYTES_PER_READ; - case 0x18: - return ROCKSDB_NAMESPACE::Histograms::BYTES_PER_WRITE; - case 0x19: - return ROCKSDB_NAMESPACE::Histograms::BYTES_PER_MULTIGET; - case 0x1A: - return ROCKSDB_NAMESPACE::Histograms::BYTES_COMPRESSED; - case 0x1B: - return ROCKSDB_NAMESPACE::Histograms::BYTES_DECOMPRESSED; - case 0x1C: - return ROCKSDB_NAMESPACE::Histograms::COMPRESSION_TIMES_NANOS; - case 0x1D: - return ROCKSDB_NAMESPACE::Histograms::DECOMPRESSION_TIMES_NANOS; - case 0x1E: - return ROCKSDB_NAMESPACE::Histograms::READ_NUM_MERGE_OPERANDS; - // 0x20 to skip 0x1F so TICKER_ENUM_MAX remains unchanged for minor - // version compatibility. - case 0x20: - return ROCKSDB_NAMESPACE::Histograms::FLUSH_TIME; - case 0x21: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_KEY_SIZE; - case 0x22: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_VALUE_SIZE; - case 0x23: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_WRITE_MICROS; - case 0x24: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GET_MICROS; - case 0x25: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_MULTIGET_MICROS; - case 0x26: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_SEEK_MICROS; - case 0x27: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_NEXT_MICROS; - case 0x28: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_PREV_MICROS; - case 0x29: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS; - case 0x2A: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_READ_MICROS; - case 0x2B: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_SYNC_MICROS; - case 0x2D: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_COMPRESSION_MICROS; - case 0x2E: - return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_DECOMPRESSION_MICROS; - case 0x2F: - return ROCKSDB_NAMESPACE::Histograms:: - NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL; - case 0x31: - return ROCKSDB_NAMESPACE::Histograms::NUM_SST_READ_PER_LEVEL; - case 0x32: - return ROCKSDB_NAMESPACE::Histograms:: - ERROR_HANDLER_AUTORESUME_RETRY_COUNT; - case 0x33: - return ROCKSDB_NAMESPACE::Histograms::ASYNC_READ_BYTES; - case 0x34: - return ROCKSDB_NAMESPACE::Histograms::POLL_WAIT_MICROS; - case 0x35: - return ROCKSDB_NAMESPACE::Histograms::PREFETCHED_BYTES_DISCARDED; - case 0x36: - return ROCKSDB_NAMESPACE::Histograms::MULTIGET_IO_BATCH_SIZE; - case 0x37: - return ROCKSDB_NAMESPACE::Histograms::NUM_LEVEL_READ_PER_MULTIGET; - case 0x38: - return ROCKSDB_NAMESPACE::Histograms::ASYNC_PREFETCH_ABORT_MICROS; - case 0x39: - return ROCKSDB_NAMESPACE::Histograms:: - TABLE_OPEN_PREFETCH_TAIL_READ_BYTES; - case 0x1F: - // 0x1F for backwards compatibility on current minor version. - return ROCKSDB_NAMESPACE::Histograms::HISTOGRAM_ENUM_MAX; - - default: - // undefined/default - return ROCKSDB_NAMESPACE::Histograms::DB_GET; - } - } -}; - -// The portal class for org.rocksdb.StatsLevel -class StatsLevelJni { - public: - // Returns the equivalent org.rocksdb.StatsLevel for the provided - // C++ ROCKSDB_NAMESPACE::StatsLevel enum - static jbyte toJavaStatsLevel( - const ROCKSDB_NAMESPACE::StatsLevel& stats_level) { - switch (stats_level) { - case ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers: - return 0x0; - case ROCKSDB_NAMESPACE::StatsLevel::kExceptTimeForMutex: - return 0x1; - case ROCKSDB_NAMESPACE::StatsLevel::kAll: - return 0x2; - - default: - // undefined/default - return 0x0; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::StatsLevel enum for the - // provided Java org.rocksdb.StatsLevel - static ROCKSDB_NAMESPACE::StatsLevel toCppStatsLevel(jbyte jstats_level) { - switch (jstats_level) { - case 0x0: - return ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers; - case 0x1: - return ROCKSDB_NAMESPACE::StatsLevel::kExceptTimeForMutex; - case 0x2: - return ROCKSDB_NAMESPACE::StatsLevel::kAll; - - default: - // undefined/default - return ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers; - } - } -}; - -// The portal class for org.rocksdb.RateLimiterMode -class RateLimiterModeJni { - public: - // Returns the equivalent org.rocksdb.RateLimiterMode for the provided - // C++ ROCKSDB_NAMESPACE::RateLimiter::Mode enum - static jbyte toJavaRateLimiterMode( - const ROCKSDB_NAMESPACE::RateLimiter::Mode& rate_limiter_mode) { - switch (rate_limiter_mode) { - case ROCKSDB_NAMESPACE::RateLimiter::Mode::kReadsOnly: - return 0x0; - case ROCKSDB_NAMESPACE::RateLimiter::Mode::kWritesOnly: - return 0x1; - case ROCKSDB_NAMESPACE::RateLimiter::Mode::kAllIo: - return 0x2; - - default: - // undefined/default - return 0x1; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::RateLimiter::Mode enum for - // the provided Java org.rocksdb.RateLimiterMode - static ROCKSDB_NAMESPACE::RateLimiter::Mode toCppRateLimiterMode( - jbyte jrate_limiter_mode) { - switch (jrate_limiter_mode) { - case 0x0: - return ROCKSDB_NAMESPACE::RateLimiter::Mode::kReadsOnly; - case 0x1: - return ROCKSDB_NAMESPACE::RateLimiter::Mode::kWritesOnly; - case 0x2: - return ROCKSDB_NAMESPACE::RateLimiter::Mode::kAllIo; - - default: - // undefined/default - return ROCKSDB_NAMESPACE::RateLimiter::Mode::kWritesOnly; - } - } -}; - -// The portal class for org.rocksdb.MemoryUsageType -class MemoryUsageTypeJni { - public: - // Returns the equivalent org.rocksdb.MemoryUsageType for the provided - // C++ ROCKSDB_NAMESPACE::MemoryUtil::UsageType enum - static jbyte toJavaMemoryUsageType( - const ROCKSDB_NAMESPACE::MemoryUtil::UsageType& usage_type) { - switch (usage_type) { - case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableTotal: - return 0x0; - case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableUnFlushed: - return 0x1; - case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kTableReadersTotal: - return 0x2; - case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kCacheTotal: - return 0x3; - default: - // undefined: use kNumUsageTypes - return 0x4; - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::MemoryUtil::UsageType enum - // for the provided Java org.rocksdb.MemoryUsageType - static ROCKSDB_NAMESPACE::MemoryUtil::UsageType toCppMemoryUsageType( - jbyte usage_type) { - switch (usage_type) { - case 0x0: - return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableTotal; - case 0x1: - return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableUnFlushed; - case 0x2: - return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kTableReadersTotal; - case 0x3: - return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kCacheTotal; - default: - // undefined/default: use kNumUsageTypes - return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kNumUsageTypes; - } - } -}; - -// The portal class for org.rocksdb.Transaction -class TransactionJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.Transaction - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/Transaction"); - } - - /** - * Create a new Java org.rocksdb.Transaction.WaitingTransactions object - * - * @param env A pointer to the Java environment - * @param jtransaction A Java org.rocksdb.Transaction object - * @param column_family_id The id of the column family - * @param key The key - * @param transaction_ids The transaction ids - * - * @return A reference to a Java - * org.rocksdb.Transaction.WaitingTransactions object, - * or nullptr if an an exception occurs - */ - static jobject newWaitingTransactions( - JNIEnv* env, jobject jtransaction, const uint32_t column_family_id, - const std::string& key, - const std::vector& transaction_ids) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID( - jclazz, "newWaitingTransactions", - "(JLjava/lang/String;[J)Lorg/rocksdb/Transaction$WaitingTransactions;"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jstring jkey = env->NewStringUTF(key.c_str()); - if (jkey == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - const size_t len = transaction_ids.size(); - jlongArray jtransaction_ids = env->NewLongArray(static_cast(len)); - if (jtransaction_ids == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jkey); - return nullptr; - } - - jboolean is_copy; - jlong* body = env->GetLongArrayElements(jtransaction_ids, &is_copy); - if (body == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jkey); - env->DeleteLocalRef(jtransaction_ids); - return nullptr; - } - for (size_t i = 0; i < len; ++i) { - body[i] = static_cast(transaction_ids[i]); - } - env->ReleaseLongArrayElements(jtransaction_ids, body, - is_copy == JNI_TRUE ? 0 : JNI_ABORT); - - jobject jwaiting_transactions = env->CallObjectMethod( - jtransaction, mid, static_cast(column_family_id), jkey, - jtransaction_ids); - if (env->ExceptionCheck()) { - // exception thrown: InstantiationException or OutOfMemoryError - env->DeleteLocalRef(jkey); - env->DeleteLocalRef(jtransaction_ids); - return nullptr; - } - - return jwaiting_transactions; - } -}; - -// The portal class for org.rocksdb.TransactionDB -class TransactionDBJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.TransactionDB - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TransactionDB"); - } - - /** - * Create a new Java org.rocksdb.TransactionDB.DeadlockInfo object - * - * @param env A pointer to the Java environment - * @param jtransaction A Java org.rocksdb.Transaction object - * @param column_family_id The id of the column family - * @param key The key - * @param transaction_ids The transaction ids - * - * @return A reference to a Java - * org.rocksdb.Transaction.WaitingTransactions object, - * or nullptr if an an exception occurs - */ - static jobject newDeadlockInfo( - JNIEnv* env, jobject jtransaction_db, - const ROCKSDB_NAMESPACE::TransactionID transaction_id, - const uint32_t column_family_id, const std::string& waiting_key, - const bool exclusive) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID( - jclazz, "newDeadlockInfo", - "(JJLjava/lang/String;Z)Lorg/rocksdb/TransactionDB$DeadlockInfo;"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jstring jwaiting_key = env->NewStringUTF(waiting_key.c_str()); - if (jwaiting_key == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - // resolve the column family id to a ColumnFamilyHandle - jobject jdeadlock_info = env->CallObjectMethod( - jtransaction_db, mid, transaction_id, - static_cast(column_family_id), jwaiting_key, exclusive); - if (env->ExceptionCheck()) { - // exception thrown: InstantiationException or OutOfMemoryError - env->DeleteLocalRef(jwaiting_key); - return nullptr; - } - - return jdeadlock_info; - } -}; - -// The portal class for org.rocksdb.TxnDBWritePolicy -class TxnDBWritePolicyJni { - public: - // Returns the equivalent org.rocksdb.TxnDBWritePolicy for the provided - // C++ ROCKSDB_NAMESPACE::TxnDBWritePolicy enum - static jbyte toJavaTxnDBWritePolicy( - const ROCKSDB_NAMESPACE::TxnDBWritePolicy& txndb_write_policy) { - switch (txndb_write_policy) { - case ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_COMMITTED: - return 0x0; - case ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_PREPARED: - return 0x1; - case ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_UNPREPARED: - return 0x2; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::TxnDBWritePolicy enum for the - // provided Java org.rocksdb.TxnDBWritePolicy - static ROCKSDB_NAMESPACE::TxnDBWritePolicy toCppTxnDBWritePolicy( - jbyte jtxndb_write_policy) { - switch (jtxndb_write_policy) { - case 0x0: - return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_COMMITTED; - case 0x1: - return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_PREPARED; - case 0x2: - return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_UNPREPARED; - default: - // undefined/default - return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_COMMITTED; - } - } -}; - -// The portal class for org.rocksdb.TransactionDB.KeyLockInfo -class KeyLockInfoJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.TransactionDB.KeyLockInfo - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TransactionDB$KeyLockInfo"); - } - - /** - * Create a new Java org.rocksdb.TransactionDB.KeyLockInfo object - * with the same properties as the provided C++ ROCKSDB_NAMESPACE::KeyLockInfo - * object - * - * @param env A pointer to the Java environment - * @param key_lock_info The ROCKSDB_NAMESPACE::KeyLockInfo object - * - * @return A reference to a Java - * org.rocksdb.TransactionDB.KeyLockInfo object, - * or nullptr if an an exception occurs - */ - static jobject construct( - JNIEnv* env, const ROCKSDB_NAMESPACE::KeyLockInfo& key_lock_info) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = - env->GetMethodID(jclazz, "", "(Ljava/lang/String;[JZ)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jstring jkey = env->NewStringUTF(key_lock_info.key.c_str()); - if (jkey == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - const jsize jtransaction_ids_len = - static_cast(key_lock_info.ids.size()); - jlongArray jtransactions_ids = env->NewLongArray(jtransaction_ids_len); - if (jtransactions_ids == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jkey); - return nullptr; - } - - const jobject jkey_lock_info = env->NewObject( - jclazz, mid, jkey, jtransactions_ids, key_lock_info.exclusive); - if (jkey_lock_info == nullptr) { - // exception thrown: InstantiationException or OutOfMemoryError - env->DeleteLocalRef(jtransactions_ids); - env->DeleteLocalRef(jkey); - return nullptr; - } - - return jkey_lock_info; - } -}; - -// The portal class for org.rocksdb.TransactionDB.DeadlockInfo -class DeadlockInfoJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.TransactionDB.DeadlockInfo - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TransactionDB$DeadlockInfo"); - } -}; - -// The portal class for org.rocksdb.TransactionDB.DeadlockPath -class DeadlockPathJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.TransactionDB.DeadlockPath - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TransactionDB$DeadlockPath"); - } - - /** - * Create a new Java org.rocksdb.TransactionDB.DeadlockPath object - * - * @param env A pointer to the Java environment - * - * @return A reference to a Java - * org.rocksdb.TransactionDB.DeadlockPath object, - * or nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, const jobjectArray jdeadlock_infos, - const bool limit_exceeded) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", "([LDeadlockInfo;Z)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - const jobject jdeadlock_path = - env->NewObject(jclazz, mid, jdeadlock_infos, limit_exceeded); - if (jdeadlock_path == nullptr) { - // exception thrown: InstantiationException or OutOfMemoryError - return nullptr; - } - - return jdeadlock_path; - } -}; - -class AbstractTableFilterJni - : public RocksDBNativeClass< - const ROCKSDB_NAMESPACE::TableFilterJniCallback*, - AbstractTableFilterJni> { - public: - /** - * Get the Java Method: TableFilter#filter(TableProperties) - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getFilterMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "filter", "(Lorg/rocksdb/TableProperties;)Z"); - assert(mid != nullptr); - return mid; - } - - private: - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TableFilter"); - } -}; - -class TablePropertiesJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.TableProperties object. - * - * @param env A pointer to the Java environment - * @param table_properties A Cpp table properties object - * - * @return A reference to a Java org.rocksdb.TableProperties object, or - * nullptr if an an exception occurs - */ - static jobject fromCppTableProperties( - JNIEnv* env, const ROCKSDB_NAMESPACE::TableProperties& table_properties) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID( - jclazz, "", - "(JJJJJJJJJJJJJJJJJJJJJJ[BLjava/lang/String;Ljava/lang/String;Ljava/" - "lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;Ljava/util/Map;Ljava/util/Map;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jbyteArray jcolumn_family_name = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, table_properties.column_family_name); - if (jcolumn_family_name == nullptr) { - // exception occurred creating java string - return nullptr; - } - - jstring jfilter_policy_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &table_properties.filter_policy_name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - return nullptr; - } - - jstring jcomparator_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &table_properties.comparator_name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - return nullptr; - } - - jstring jmerge_operator_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &table_properties.merge_operator_name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - env->DeleteLocalRef(jcomparator_name); - return nullptr; - } - - jstring jprefix_extractor_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &table_properties.prefix_extractor_name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - env->DeleteLocalRef(jcomparator_name); - env->DeleteLocalRef(jmerge_operator_name); - return nullptr; - } - - jstring jproperty_collectors_names = - ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &table_properties.property_collectors_names, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - env->DeleteLocalRef(jcomparator_name); - env->DeleteLocalRef(jmerge_operator_name); - env->DeleteLocalRef(jprefix_extractor_name); - return nullptr; - } - - jstring jcompression_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &table_properties.compression_name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - env->DeleteLocalRef(jcomparator_name); - env->DeleteLocalRef(jmerge_operator_name); - env->DeleteLocalRef(jprefix_extractor_name); - env->DeleteLocalRef(jproperty_collectors_names); - return nullptr; - } - - // Map - jobject juser_collected_properties = - ROCKSDB_NAMESPACE::HashMapJni::fromCppMap( - env, &table_properties.user_collected_properties); - if (env->ExceptionCheck()) { - // exception occurred creating java map - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - env->DeleteLocalRef(jcomparator_name); - env->DeleteLocalRef(jmerge_operator_name); - env->DeleteLocalRef(jprefix_extractor_name); - env->DeleteLocalRef(jproperty_collectors_names); - env->DeleteLocalRef(jcompression_name); - return nullptr; - } - - // Map - jobject jreadable_properties = ROCKSDB_NAMESPACE::HashMapJni::fromCppMap( - env, &table_properties.readable_properties); - if (env->ExceptionCheck()) { - // exception occurred creating java map - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfilter_policy_name); - env->DeleteLocalRef(jcomparator_name); - env->DeleteLocalRef(jmerge_operator_name); - env->DeleteLocalRef(jprefix_extractor_name); - env->DeleteLocalRef(jproperty_collectors_names); - env->DeleteLocalRef(jcompression_name); - env->DeleteLocalRef(juser_collected_properties); - return nullptr; - } - - jobject jtable_properties = env->NewObject( - jclazz, mid, static_cast(table_properties.data_size), - static_cast(table_properties.index_size), - static_cast(table_properties.index_partitions), - static_cast(table_properties.top_level_index_size), - static_cast(table_properties.index_key_is_user_key), - static_cast(table_properties.index_value_is_delta_encoded), - static_cast(table_properties.filter_size), - static_cast(table_properties.raw_key_size), - static_cast(table_properties.raw_value_size), - static_cast(table_properties.num_data_blocks), - static_cast(table_properties.num_entries), - static_cast(table_properties.num_deletions), - static_cast(table_properties.num_merge_operands), - static_cast(table_properties.num_range_deletions), - static_cast(table_properties.format_version), - static_cast(table_properties.fixed_key_len), - static_cast(table_properties.column_family_id), - static_cast(table_properties.creation_time), - static_cast(table_properties.oldest_key_time), - static_cast( - table_properties.slow_compression_estimated_data_size), - static_cast( - table_properties.fast_compression_estimated_data_size), - static_cast( - table_properties.external_sst_file_global_seqno_offset), - jcolumn_family_name, jfilter_policy_name, jcomparator_name, - jmerge_operator_name, jprefix_extractor_name, - jproperty_collectors_names, jcompression_name, - juser_collected_properties, jreadable_properties); - - if (env->ExceptionCheck()) { - return nullptr; - } - - return jtable_properties; - } - - private: - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TableProperties"); - } -}; - -class ColumnFamilyDescriptorJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.ColumnFamilyDescriptor - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyDescriptor"); - } - - /** - * Create a new Java org.rocksdb.ColumnFamilyDescriptor object with the same - * properties as the provided C++ ROCKSDB_NAMESPACE::ColumnFamilyDescriptor - * object - * - * @param env A pointer to the Java environment - * @param cfd A pointer to ROCKSDB_NAMESPACE::ColumnFamilyDescriptor object - * - * @return A reference to a Java org.rocksdb.ColumnFamilyDescriptor object, or - * nullptr if an an exception occurs - */ - static jobject construct(JNIEnv* env, ColumnFamilyDescriptor* cfd) { - jbyteArray jcf_name = JniUtil::copyBytes(env, cfd->name); - jobject cfopts = ColumnFamilyOptionsJni::construct(env, &(cfd->options)); - - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", - "([BLorg/rocksdb/ColumnFamilyOptions;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - env->DeleteLocalRef(jcf_name); - return nullptr; - } - - jobject jcfd = env->NewObject(jclazz, mid, jcf_name, cfopts); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jcf_name); - return nullptr; - } - - return jcfd; - } - - /** - * Get the Java Method: ColumnFamilyDescriptor#columnFamilyName - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getColumnFamilyNameMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "columnFamilyName", "()[B"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: ColumnFamilyDescriptor#columnFamilyOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getColumnFamilyOptionsMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID( - jclazz, "columnFamilyOptions", "()Lorg/rocksdb/ColumnFamilyOptions;"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.IndexType -class IndexTypeJni { - public: - // Returns the equivalent org.rocksdb.IndexType for the provided - // C++ ROCKSDB_NAMESPACE::IndexType enum - static jbyte toJavaIndexType( - const ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType& index_type) { - switch (index_type) { - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::kBinarySearch: - return 0x0; - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::kHashSearch: - return 0x1; - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kTwoLevelIndexSearch: - return 0x2; - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kBinarySearchWithFirstKey: - return 0x3; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::IndexType enum for the - // provided Java org.rocksdb.IndexType - static ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType toCppIndexType( - jbyte jindex_type) { - switch (jindex_type) { - case 0x0: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kBinarySearch; - case 0x1: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kHashSearch; - case 0x2: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kTwoLevelIndexSearch; - case 0x3: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kBinarySearchWithFirstKey; - default: - // undefined/default - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType:: - kBinarySearch; - } - } -}; - -// The portal class for org.rocksdb.DataBlockIndexType -class DataBlockIndexTypeJni { - public: - // Returns the equivalent org.rocksdb.DataBlockIndexType for the provided - // C++ ROCKSDB_NAMESPACE::DataBlockIndexType enum - static jbyte toJavaDataBlockIndexType( - const ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType& - index_type) { - switch (index_type) { - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType:: - kDataBlockBinarySearch: - return 0x0; - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType:: - kDataBlockBinaryAndHash: - return 0x1; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::DataBlockIndexType enum for - // the provided Java org.rocksdb.DataBlockIndexType - static ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType - toCppDataBlockIndexType(jbyte jindex_type) { - switch (jindex_type) { - case 0x0: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType:: - kDataBlockBinarySearch; - case 0x1: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType:: - kDataBlockBinaryAndHash; - default: - // undefined/default - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType:: - kDataBlockBinarySearch; - } - } -}; - -// The portal class for org.rocksdb.ChecksumType -class ChecksumTypeJni { - public: - // Returns the equivalent org.rocksdb.ChecksumType for the provided - // C++ ROCKSDB_NAMESPACE::ChecksumType enum - static jbyte toJavaChecksumType( - const ROCKSDB_NAMESPACE::ChecksumType& checksum_type) { - switch (checksum_type) { - case ROCKSDB_NAMESPACE::ChecksumType::kNoChecksum: - return 0x0; - case ROCKSDB_NAMESPACE::ChecksumType::kCRC32c: - return 0x1; - case ROCKSDB_NAMESPACE::ChecksumType::kxxHash: - return 0x2; - case ROCKSDB_NAMESPACE::ChecksumType::kxxHash64: - return 0x3; - case ROCKSDB_NAMESPACE::ChecksumType::kXXH3: - return 0x4; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ChecksumType enum for the - // provided Java org.rocksdb.ChecksumType - static ROCKSDB_NAMESPACE::ChecksumType toCppChecksumType( - jbyte jchecksum_type) { - switch (jchecksum_type) { - case 0x0: - return ROCKSDB_NAMESPACE::ChecksumType::kNoChecksum; - case 0x1: - return ROCKSDB_NAMESPACE::ChecksumType::kCRC32c; - case 0x2: - return ROCKSDB_NAMESPACE::ChecksumType::kxxHash; - case 0x3: - return ROCKSDB_NAMESPACE::ChecksumType::kxxHash64; - case 0x4: - return ROCKSDB_NAMESPACE::ChecksumType::kXXH3; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ChecksumType::kCRC32c; - } - } -}; - -// The portal class for org.rocksdb.IndexShorteningMode -class IndexShorteningModeJni { - public: - // Returns the equivalent org.rocksdb.IndexShorteningMode for the provided - // C++ ROCKSDB_NAMESPACE::IndexShorteningMode enum - static jbyte toJavaIndexShorteningMode( - const ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode& - index_shortening_mode) { - switch (index_shortening_mode) { - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kNoShortening: - return 0x0; - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kShortenSeparators: - return 0x1; - case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kShortenSeparatorsAndSuccessor: - return 0x2; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::IndexShorteningMode enum for - // the provided Java org.rocksdb.IndexShorteningMode - static ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode - toCppIndexShorteningMode(jbyte jindex_shortening_mode) { - switch (jindex_shortening_mode) { - case 0x0: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kNoShortening; - case 0x1: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kShortenSeparators; - case 0x2: - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kShortenSeparatorsAndSuccessor; - default: - // undefined/default - return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode:: - kShortenSeparators; - } - } -}; - -// The portal class for org.rocksdb.Priority -class PriorityJni { - public: - // Returns the equivalent org.rocksdb.Priority for the provided - // C++ ROCKSDB_NAMESPACE::Env::Priority enum - static jbyte toJavaPriority( - const ROCKSDB_NAMESPACE::Env::Priority& priority) { - switch (priority) { - case ROCKSDB_NAMESPACE::Env::Priority::BOTTOM: - return 0x0; - case ROCKSDB_NAMESPACE::Env::Priority::LOW: - return 0x1; - case ROCKSDB_NAMESPACE::Env::Priority::HIGH: - return 0x2; - case ROCKSDB_NAMESPACE::Env::Priority::TOTAL: - return 0x3; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::env::Priority enum for the - // provided Java org.rocksdb.Priority - static ROCKSDB_NAMESPACE::Env::Priority toCppPriority(jbyte jpriority) { - switch (jpriority) { - case 0x0: - return ROCKSDB_NAMESPACE::Env::Priority::BOTTOM; - case 0x1: - return ROCKSDB_NAMESPACE::Env::Priority::LOW; - case 0x2: - return ROCKSDB_NAMESPACE::Env::Priority::HIGH; - case 0x3: - return ROCKSDB_NAMESPACE::Env::Priority::TOTAL; - default: - // undefined/default - return ROCKSDB_NAMESPACE::Env::Priority::LOW; - } - } -}; - -// The portal class for org.rocksdb.ThreadType -class ThreadTypeJni { - public: - // Returns the equivalent org.rocksdb.ThreadType for the provided - // C++ ROCKSDB_NAMESPACE::ThreadStatus::ThreadType enum - static jbyte toJavaThreadType( - const ROCKSDB_NAMESPACE::ThreadStatus::ThreadType& thread_type) { - switch (thread_type) { - case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::HIGH_PRIORITY: - return 0x0; - case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::LOW_PRIORITY: - return 0x1; - case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::USER: - return 0x2; - case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::BOTTOM_PRIORITY: - return 0x3; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::ThreadType enum - // for the provided Java org.rocksdb.ThreadType - static ROCKSDB_NAMESPACE::ThreadStatus::ThreadType toCppThreadType( - jbyte jthread_type) { - switch (jthread_type) { - case 0x0: - return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::HIGH_PRIORITY; - case 0x1: - return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::LOW_PRIORITY; - case 0x2: - return ThreadStatus::ThreadType::USER; - case 0x3: - return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::BOTTOM_PRIORITY; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::LOW_PRIORITY; - } - } -}; - -// The portal class for org.rocksdb.OperationType -class OperationTypeJni { - public: - // Returns the equivalent org.rocksdb.OperationType for the provided - // C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationType enum - static jbyte toJavaOperationType( - const ROCKSDB_NAMESPACE::ThreadStatus::OperationType& operation_type) { - switch (operation_type) { - case ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_UNKNOWN: - return 0x0; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_COMPACTION: - return 0x1; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_FLUSH: - return 0x2; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationType - // enum for the provided Java org.rocksdb.OperationType - static ROCKSDB_NAMESPACE::ThreadStatus::OperationType toCppOperationType( - jbyte joperation_type) { - switch (joperation_type) { - case 0x0: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_UNKNOWN; - case 0x1: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_COMPACTION; - case 0x2: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_FLUSH; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_UNKNOWN; - } - } -}; - -// The portal class for org.rocksdb.OperationStage -class OperationStageJni { - public: - // Returns the equivalent org.rocksdb.OperationStage for the provided - // C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationStage enum - static jbyte toJavaOperationStage( - const ROCKSDB_NAMESPACE::ThreadStatus::OperationStage& operation_stage) { - switch (operation_stage) { - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_UNKNOWN: - return 0x0; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_FLUSH_RUN: - return 0x1; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_FLUSH_WRITE_L0: - return 0x2; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_PREPARE: - return 0x3; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_RUN: - return 0x4; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_PROCESS_KV: - return 0x5; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_INSTALL: - return 0x6; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_SYNC_FILE: - return 0x7; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_PICK_MEMTABLES_TO_FLUSH: - return 0x8; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_MEMTABLE_ROLLBACK: - return 0x9; - case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS: - return 0xA; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationStage - // enum for the provided Java org.rocksdb.OperationStage - static ROCKSDB_NAMESPACE::ThreadStatus::OperationStage toCppOperationStage( - jbyte joperation_stage) { - switch (joperation_stage) { - case 0x0: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_UNKNOWN; - case 0x1: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_FLUSH_RUN; - case 0x2: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_FLUSH_WRITE_L0; - case 0x3: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_PREPARE; - case 0x4: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_RUN; - case 0x5: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_PROCESS_KV; - case 0x6: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_INSTALL; - case 0x7: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_COMPACTION_SYNC_FILE; - case 0x8: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_PICK_MEMTABLES_TO_FLUSH; - case 0x9: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_MEMTABLE_ROLLBACK; - case 0xA: - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage:: - STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_UNKNOWN; - } - } -}; - -// The portal class for org.rocksdb.StateType -class StateTypeJni { - public: - // Returns the equivalent org.rocksdb.StateType for the provided - // C++ ROCKSDB_NAMESPACE::ThreadStatus::StateType enum - static jbyte toJavaStateType( - const ROCKSDB_NAMESPACE::ThreadStatus::StateType& state_type) { - switch (state_type) { - case ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_UNKNOWN: - return 0x0; - case ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_MUTEX_WAIT: - return 0x1; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::StateType enum - // for the provided Java org.rocksdb.StateType - static ROCKSDB_NAMESPACE::ThreadStatus::StateType toCppStateType( - jbyte jstate_type) { - switch (jstate_type) { - case 0x0: - return ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_UNKNOWN; - case 0x1: - return ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_MUTEX_WAIT; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_UNKNOWN; - } - } -}; - -// The portal class for org.rocksdb.ThreadStatus -class ThreadStatusJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.ThreadStatus - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/ThreadStatus"); - } - - /** - * Create a new Java org.rocksdb.ThreadStatus object with the same - * properties as the provided C++ ROCKSDB_NAMESPACE::ThreadStatus object - * - * @param env A pointer to the Java environment - * @param thread_status A pointer to ROCKSDB_NAMESPACE::ThreadStatus object - * - * @return A reference to a Java org.rocksdb.ColumnFamilyOptions object, or - * nullptr if an an exception occurs - */ - static jobject construct( - JNIEnv* env, const ROCKSDB_NAMESPACE::ThreadStatus* thread_status) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID( - jclazz, "", "(JBLjava/lang/String;Ljava/lang/String;BJB[JB)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jstring jdb_name = - JniUtil::toJavaString(env, &(thread_status->db_name), true); - if (env->ExceptionCheck()) { - // an error occurred - return nullptr; - } - - jstring jcf_name = - JniUtil::toJavaString(env, &(thread_status->cf_name), true); - if (env->ExceptionCheck()) { - // an error occurred - env->DeleteLocalRef(jdb_name); - return nullptr; - } - - // long[] - const jsize len = static_cast( - ROCKSDB_NAMESPACE::ThreadStatus::kNumOperationProperties); - jlongArray joperation_properties = env->NewLongArray(len); - if (joperation_properties == nullptr) { - // an exception occurred - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - return nullptr; - } - jboolean is_copy; - jlong* body = env->GetLongArrayElements(joperation_properties, &is_copy); - if (body == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(joperation_properties); - return nullptr; - } - for (size_t i = 0; i < len; ++i) { - body[i] = static_cast(thread_status->op_properties[i]); - } - env->ReleaseLongArrayElements(joperation_properties, body, - is_copy == JNI_TRUE ? 0 : JNI_ABORT); - - jobject jcfd = env->NewObject( - jclazz, mid, static_cast(thread_status->thread_id), - ThreadTypeJni::toJavaThreadType(thread_status->thread_type), jdb_name, - jcf_name, - OperationTypeJni::toJavaOperationType(thread_status->operation_type), - static_cast(thread_status->op_elapsed_micros), - OperationStageJni::toJavaOperationStage(thread_status->operation_stage), - joperation_properties, - StateTypeJni::toJavaStateType(thread_status->state_type)); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(joperation_properties); - return nullptr; - } - - // cleanup - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(joperation_properties); - - return jcfd; - } -}; - -// The portal class for org.rocksdb.CompactionStyle -class CompactionStyleJni { - public: - // Returns the equivalent org.rocksdb.CompactionStyle for the provided - // C++ ROCKSDB_NAMESPACE::CompactionStyle enum - static jbyte toJavaCompactionStyle( - const ROCKSDB_NAMESPACE::CompactionStyle& compaction_style) { - switch (compaction_style) { - case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleLevel: - return 0x0; - case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleUniversal: - return 0x1; - case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleFIFO: - return 0x2; - case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleNone: - return 0x3; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionStyle enum for the - // provided Java org.rocksdb.CompactionStyle - static ROCKSDB_NAMESPACE::CompactionStyle toCppCompactionStyle( - jbyte jcompaction_style) { - switch (jcompaction_style) { - case 0x0: - return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleLevel; - case 0x1: - return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleUniversal; - case 0x2: - return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleFIFO; - case 0x3: - return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleNone; - default: - // undefined/default - return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleLevel; - } - } -}; - -// The portal class for org.rocksdb.CompactionReason -class CompactionReasonJni { - public: - // Returns the equivalent org.rocksdb.CompactionReason for the provided - // C++ ROCKSDB_NAMESPACE::CompactionReason enum - static jbyte toJavaCompactionReason( - const ROCKSDB_NAMESPACE::CompactionReason& compaction_reason) { - switch (compaction_reason) { - case ROCKSDB_NAMESPACE::CompactionReason::kUnknown: - return 0x0; - case ROCKSDB_NAMESPACE::CompactionReason::kLevelL0FilesNum: - return 0x1; - case ROCKSDB_NAMESPACE::CompactionReason::kLevelMaxLevelSize: - return 0x2; - case ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeAmplification: - return 0x3; - case ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeRatio: - return 0x4; - case ROCKSDB_NAMESPACE::CompactionReason::kUniversalSortedRunNum: - return 0x5; - case ROCKSDB_NAMESPACE::CompactionReason::kFIFOMaxSize: - return 0x6; - case ROCKSDB_NAMESPACE::CompactionReason::kFIFOReduceNumFiles: - return 0x7; - case ROCKSDB_NAMESPACE::CompactionReason::kFIFOTtl: - return 0x8; - case ROCKSDB_NAMESPACE::CompactionReason::kManualCompaction: - return 0x9; - case ROCKSDB_NAMESPACE::CompactionReason::kFilesMarkedForCompaction: - return 0x10; - case ROCKSDB_NAMESPACE::CompactionReason::kBottommostFiles: - return 0x0A; - case ROCKSDB_NAMESPACE::CompactionReason::kTtl: - return 0x0B; - case ROCKSDB_NAMESPACE::CompactionReason::kFlush: - return 0x0C; - case ROCKSDB_NAMESPACE::CompactionReason::kExternalSstIngestion: - return 0x0D; - case ROCKSDB_NAMESPACE::CompactionReason::kPeriodicCompaction: - return 0x0E; - case ROCKSDB_NAMESPACE::CompactionReason::kChangeTemperature: - return 0x0F; - case ROCKSDB_NAMESPACE::CompactionReason::kForcedBlobGC: - return 0x11; - case ROCKSDB_NAMESPACE::CompactionReason::kRoundRobinTtl: - return 0x12; - case ROCKSDB_NAMESPACE::CompactionReason::kRefitLevel: - return 0x13; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionReason enum for the - // provided Java org.rocksdb.CompactionReason - static ROCKSDB_NAMESPACE::CompactionReason toCppCompactionReason( - jbyte jcompaction_reason) { - switch (jcompaction_reason) { - case 0x0: - return ROCKSDB_NAMESPACE::CompactionReason::kUnknown; - case 0x1: - return ROCKSDB_NAMESPACE::CompactionReason::kLevelL0FilesNum; - case 0x2: - return ROCKSDB_NAMESPACE::CompactionReason::kLevelMaxLevelSize; - case 0x3: - return ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeAmplification; - case 0x4: - return ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeRatio; - case 0x5: - return ROCKSDB_NAMESPACE::CompactionReason::kUniversalSortedRunNum; - case 0x6: - return ROCKSDB_NAMESPACE::CompactionReason::kFIFOMaxSize; - case 0x7: - return ROCKSDB_NAMESPACE::CompactionReason::kFIFOReduceNumFiles; - case 0x8: - return ROCKSDB_NAMESPACE::CompactionReason::kFIFOTtl; - case 0x9: - return ROCKSDB_NAMESPACE::CompactionReason::kManualCompaction; - case 0x10: - return ROCKSDB_NAMESPACE::CompactionReason::kFilesMarkedForCompaction; - case 0x0A: - return ROCKSDB_NAMESPACE::CompactionReason::kBottommostFiles; - case 0x0B: - return ROCKSDB_NAMESPACE::CompactionReason::kTtl; - case 0x0C: - return ROCKSDB_NAMESPACE::CompactionReason::kFlush; - case 0x0D: - return ROCKSDB_NAMESPACE::CompactionReason::kExternalSstIngestion; - case 0x0E: - return ROCKSDB_NAMESPACE::CompactionReason::kPeriodicCompaction; - case 0x0F: - return ROCKSDB_NAMESPACE::CompactionReason::kChangeTemperature; - case 0x11: - return ROCKSDB_NAMESPACE::CompactionReason::kForcedBlobGC; - case 0x12: - return ROCKSDB_NAMESPACE::CompactionReason::kRoundRobinTtl; - case 0x13: - return ROCKSDB_NAMESPACE::CompactionReason::kRefitLevel; - default: - // undefined/default - return ROCKSDB_NAMESPACE::CompactionReason::kUnknown; - } - } -}; - -// The portal class for org.rocksdb.WalFileType -class WalFileTypeJni { - public: - // Returns the equivalent org.rocksdb.WalFileType for the provided - // C++ ROCKSDB_NAMESPACE::WalFileType enum - static jbyte toJavaWalFileType( - const ROCKSDB_NAMESPACE::WalFileType& wal_file_type) { - switch (wal_file_type) { - case ROCKSDB_NAMESPACE::WalFileType::kArchivedLogFile: - return 0x0; - case ROCKSDB_NAMESPACE::WalFileType::kAliveLogFile: - return 0x1; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::WalFileType enum for the - // provided Java org.rocksdb.WalFileType - static ROCKSDB_NAMESPACE::WalFileType toCppWalFileType(jbyte jwal_file_type) { - switch (jwal_file_type) { - case 0x0: - return ROCKSDB_NAMESPACE::WalFileType::kArchivedLogFile; - case 0x1: - return ROCKSDB_NAMESPACE::WalFileType::kAliveLogFile; - default: - // undefined/default - return ROCKSDB_NAMESPACE::WalFileType::kAliveLogFile; - } - } -}; - -class LogFileJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.LogFile object. - * - * @param env A pointer to the Java environment - * @param log_file A Cpp log file object - * - * @return A reference to a Java org.rocksdb.LogFile object, or - * nullptr if an an exception occurs - */ - static jobject fromCppLogFile(JNIEnv* env, - ROCKSDB_NAMESPACE::LogFile* log_file) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = - env->GetMethodID(jclazz, "", "(Ljava/lang/String;JBJJ)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - std::string path_name = log_file->PathName(); - jstring jpath_name = - ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &path_name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - return nullptr; - } - - jobject jlog_file = env->NewObject( - jclazz, mid, jpath_name, static_cast(log_file->LogNumber()), - ROCKSDB_NAMESPACE::WalFileTypeJni::toJavaWalFileType(log_file->Type()), - static_cast(log_file->StartSequence()), - static_cast(log_file->SizeFileBytes())); - - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jpath_name); - return nullptr; - } - - // cleanup - env->DeleteLocalRef(jpath_name); - - return jlog_file; - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/LogFile"); - } -}; - -class LiveFileMetaDataJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.LiveFileMetaData object. - * - * @param env A pointer to the Java environment - * @param live_file_meta_data A Cpp live file meta data object - * - * @return A reference to a Java org.rocksdb.LiveFileMetaData object, or - * nullptr if an an exception occurs - */ - static jobject fromCppLiveFileMetaData( - JNIEnv* env, ROCKSDB_NAMESPACE::LiveFileMetaData* live_file_meta_data) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID( - jclazz, "", - "([BILjava/lang/String;Ljava/lang/String;JJJ[B[BJZJJ)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jbyteArray jcolumn_family_name = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, live_file_meta_data->column_family_name); - if (jcolumn_family_name == nullptr) { - // exception occurred creating java byte array - return nullptr; - } - - jstring jfile_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &live_file_meta_data->name, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - return nullptr; - } - - jstring jpath = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &live_file_meta_data->db_path, true); - if (env->ExceptionCheck()) { - // exception occurred creating java string - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfile_name); - return nullptr; - } - - jbyteArray jsmallest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, live_file_meta_data->smallestkey); - if (jsmallest_key == nullptr) { - // exception occurred creating java byte array - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - return nullptr; - } - - jbyteArray jlargest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, live_file_meta_data->largestkey); - if (jlargest_key == nullptr) { - // exception occurred creating java byte array - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jsmallest_key); - return nullptr; - } - - jobject jlive_file_meta_data = env->NewObject( - jclazz, mid, jcolumn_family_name, - static_cast(live_file_meta_data->level), jfile_name, jpath, - static_cast(live_file_meta_data->size), - static_cast(live_file_meta_data->smallest_seqno), - static_cast(live_file_meta_data->largest_seqno), jsmallest_key, - jlargest_key, - static_cast(live_file_meta_data->num_reads_sampled), - static_cast(live_file_meta_data->being_compacted), - static_cast(live_file_meta_data->num_entries), - static_cast(live_file_meta_data->num_deletions)); - - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jsmallest_key); - env->DeleteLocalRef(jlargest_key); - return nullptr; - } - - // cleanup - env->DeleteLocalRef(jcolumn_family_name); - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jsmallest_key); - env->DeleteLocalRef(jlargest_key); - - return jlive_file_meta_data; - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/LiveFileMetaData"); - } -}; - -class SstFileMetaDataJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.SstFileMetaData object. - * - * @param env A pointer to the Java environment - * @param sst_file_meta_data A Cpp sst file meta data object - * - * @return A reference to a Java org.rocksdb.SstFileMetaData object, or - * nullptr if an an exception occurs - */ - static jobject fromCppSstFileMetaData( - JNIEnv* env, - const ROCKSDB_NAMESPACE::SstFileMetaData* sst_file_meta_data) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID( - jclazz, "", "(Ljava/lang/String;Ljava/lang/String;JJJ[B[BJZJJ)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jstring jfile_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &sst_file_meta_data->name, true); - if (jfile_name == nullptr) { - // exception occurred creating java byte array - return nullptr; - } - - jstring jpath = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &sst_file_meta_data->db_path, true); - if (jpath == nullptr) { - // exception occurred creating java byte array - env->DeleteLocalRef(jfile_name); - return nullptr; - } - - jbyteArray jsmallest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, sst_file_meta_data->smallestkey); - if (jsmallest_key == nullptr) { - // exception occurred creating java byte array - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - return nullptr; - } - - jbyteArray jlargest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, sst_file_meta_data->largestkey); - if (jlargest_key == nullptr) { - // exception occurred creating java byte array - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jsmallest_key); - return nullptr; - } - - jobject jsst_file_meta_data = env->NewObject( - jclazz, mid, jfile_name, jpath, - static_cast(sst_file_meta_data->size), - static_cast(sst_file_meta_data->smallest_seqno), - static_cast(sst_file_meta_data->largest_seqno), jsmallest_key, - jlargest_key, static_cast(sst_file_meta_data->num_reads_sampled), - static_cast(sst_file_meta_data->being_compacted), - static_cast(sst_file_meta_data->num_entries), - static_cast(sst_file_meta_data->num_deletions)); - - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jsmallest_key); - env->DeleteLocalRef(jlargest_key); - return nullptr; - } - - // cleanup - env->DeleteLocalRef(jfile_name); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(jsmallest_key); - env->DeleteLocalRef(jlargest_key); - - return jsst_file_meta_data; - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/SstFileMetaData"); - } -}; - -class LevelMetaDataJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.LevelMetaData object. - * - * @param env A pointer to the Java environment - * @param level_meta_data A Cpp level meta data object - * - * @return A reference to a Java org.rocksdb.LevelMetaData object, or - * nullptr if an an exception occurs - */ - static jobject fromCppLevelMetaData( - JNIEnv* env, const ROCKSDB_NAMESPACE::LevelMetaData* level_meta_data) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", - "(IJ[Lorg/rocksdb/SstFileMetaData;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - const jsize jlen = static_cast(level_meta_data->files.size()); - jobjectArray jfiles = - env->NewObjectArray(jlen, SstFileMetaDataJni::getJClass(env), nullptr); - if (jfiles == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - jsize i = 0; - for (auto it = level_meta_data->files.begin(); - it != level_meta_data->files.end(); ++it) { - jobject jfile = SstFileMetaDataJni::fromCppSstFileMetaData(env, &(*it)); - if (jfile == nullptr) { - // exception occurred - env->DeleteLocalRef(jfiles); - return nullptr; - } - env->SetObjectArrayElement(jfiles, i++, jfile); - } - - jobject jlevel_meta_data = - env->NewObject(jclazz, mid, static_cast(level_meta_data->level), - static_cast(level_meta_data->size), jfiles); - - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jfiles); - return nullptr; - } - - // cleanup - env->DeleteLocalRef(jfiles); - - return jlevel_meta_data; - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/LevelMetaData"); - } -}; - -class ColumnFamilyMetaDataJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.ColumnFamilyMetaData object. - * - * @param env A pointer to the Java environment - * @param column_famly_meta_data A Cpp live file meta data object - * - * @return A reference to a Java org.rocksdb.ColumnFamilyMetaData object, or - * nullptr if an an exception occurs - */ - static jobject fromCppColumnFamilyMetaData( - JNIEnv* env, - const ROCKSDB_NAMESPACE::ColumnFamilyMetaData* column_famly_meta_data) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = env->GetMethodID(jclazz, "", - "(JJ[B[Lorg/rocksdb/LevelMetaData;)V"); - if (mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return nullptr; - } - - jbyteArray jname = ROCKSDB_NAMESPACE::JniUtil::copyBytes( - env, column_famly_meta_data->name); - if (jname == nullptr) { - // exception occurred creating java byte array - return nullptr; - } - - const jsize jlen = - static_cast(column_famly_meta_data->levels.size()); - jobjectArray jlevels = - env->NewObjectArray(jlen, LevelMetaDataJni::getJClass(env), nullptr); - if (jlevels == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jname); - return nullptr; - } - - jsize i = 0; - for (auto it = column_famly_meta_data->levels.begin(); - it != column_famly_meta_data->levels.end(); ++it) { - jobject jlevel = LevelMetaDataJni::fromCppLevelMetaData(env, &(*it)); - if (jlevel == nullptr) { - // exception occurred - env->DeleteLocalRef(jname); - env->DeleteLocalRef(jlevels); - return nullptr; - } - env->SetObjectArrayElement(jlevels, i++, jlevel); - } - - jobject jcolumn_family_meta_data = env->NewObject( - jclazz, mid, static_cast(column_famly_meta_data->size), - static_cast(column_famly_meta_data->file_count), jname, jlevels); - - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jname); - env->DeleteLocalRef(jlevels); - return nullptr; - } - - // cleanup - env->DeleteLocalRef(jname); - env->DeleteLocalRef(jlevels); - - return jcolumn_family_meta_data; - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyMetaData"); - } -}; - -// The portal class for org.rocksdb.AbstractTraceWriter -class AbstractTraceWriterJni - : public RocksDBNativeClass< - const ROCKSDB_NAMESPACE::TraceWriterJniCallback*, - AbstractTraceWriterJni> { - public: - /** - * Get the Java Class org.rocksdb.AbstractTraceWriter - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/AbstractTraceWriter"); - } - - /** - * Get the Java Method: AbstractTraceWriter#write - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getWriteProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "writeProxy", "(J)S"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractTraceWriter#closeWriter - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getCloseWriterProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "closeWriterProxy", "()S"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractTraceWriter#getFileSize - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getGetFileSizeMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "getFileSize", "()J"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.AbstractWalFilter -class AbstractWalFilterJni - : public RocksDBNativeClass { - public: - /** - * Get the Java Class org.rocksdb.AbstractWalFilter - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/AbstractWalFilter"); - } - - /** - * Get the Java Method: AbstractWalFilter#columnFamilyLogNumberMap - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getColumnFamilyLogNumberMapMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "columnFamilyLogNumberMap", - "(Ljava/util/Map;Ljava/util/Map;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractTraceWriter#logRecordFoundProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getLogRecordFoundProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "logRecordFoundProxy", - "(JLjava/lang/String;JJ)S"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractTraceWriter#name - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retrieved - */ - static jmethodID getNameMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "name", "()Ljava/lang/String;"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.WalProcessingOption -class WalProcessingOptionJni { - public: - // Returns the equivalent org.rocksdb.WalProcessingOption for the provided - // C++ ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption enum - static jbyte toJavaWalProcessingOption( - const ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption& - wal_processing_option) { - switch (wal_processing_option) { - case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption:: - kContinueProcessing: - return 0x0; - case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption:: - kIgnoreCurrentRecord: - return 0x1; - case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::kStopReplay: - return 0x2; - case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::kCorruptedRecord: - return 0x3; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ - // ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption enum for the provided - // Java org.rocksdb.WalProcessingOption - static ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption - toCppWalProcessingOption(jbyte jwal_processing_option) { - switch (jwal_processing_option) { - case 0x0: - return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption:: - kContinueProcessing; - case 0x1: - return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption:: - kIgnoreCurrentRecord; - case 0x2: - return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::kStopReplay; - case 0x3: - return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption:: - kCorruptedRecord; - default: - // undefined/default - return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption:: - kCorruptedRecord; - } - } -}; - -// The portal class for org.rocksdb.ReusedSynchronisationType -class ReusedSynchronisationTypeJni { - public: - // Returns the equivalent org.rocksdb.ReusedSynchronisationType for the - // provided C++ ROCKSDB_NAMESPACE::ReusedSynchronisationType enum - static jbyte toJavaReusedSynchronisationType( - const ROCKSDB_NAMESPACE::ReusedSynchronisationType& - reused_synchronisation_type) { - switch (reused_synchronisation_type) { - case ROCKSDB_NAMESPACE::ReusedSynchronisationType::MUTEX: - return 0x0; - case ROCKSDB_NAMESPACE::ReusedSynchronisationType::ADAPTIVE_MUTEX: - return 0x1; - case ROCKSDB_NAMESPACE::ReusedSynchronisationType::THREAD_LOCAL: - return 0x2; - default: - return 0x7F; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ReusedSynchronisationType - // enum for the provided Java org.rocksdb.ReusedSynchronisationType - static ROCKSDB_NAMESPACE::ReusedSynchronisationType - toCppReusedSynchronisationType(jbyte reused_synchronisation_type) { - switch (reused_synchronisation_type) { - case 0x0: - return ROCKSDB_NAMESPACE::ReusedSynchronisationType::MUTEX; - case 0x1: - return ROCKSDB_NAMESPACE::ReusedSynchronisationType::ADAPTIVE_MUTEX; - case 0x2: - return ROCKSDB_NAMESPACE::ReusedSynchronisationType::THREAD_LOCAL; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ReusedSynchronisationType::ADAPTIVE_MUTEX; - } - } -}; -// The portal class for org.rocksdb.SanityLevel -class SanityLevelJni { - public: - // Returns the equivalent org.rocksdb.SanityLevel for the provided - // C++ ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel enum - static jbyte toJavaSanityLevel( - const ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel& sanity_level) { - switch (sanity_level) { - case ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel::kSanityLevelNone: - return 0x0; - case ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel:: - kSanityLevelLooselyCompatible: - return 0x1; - case ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel:: - kSanityLevelExactMatch: - return -0x01; - default: - return -0x01; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel - // enum for the provided Java org.rocksdb.SanityLevel - static ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel toCppSanityLevel( - jbyte sanity_level) { - switch (sanity_level) { - case 0x0: - return ROCKSDB_NAMESPACE::ConfigOptions::kSanityLevelNone; - case 0x1: - return ROCKSDB_NAMESPACE::ConfigOptions::kSanityLevelLooselyCompatible; - default: - // undefined/default - return ROCKSDB_NAMESPACE::ConfigOptions::kSanityLevelExactMatch; - } - } -}; - -// The portal class for org.rocksdb.PrepopulateBlobCache -class PrepopulateBlobCacheJni { - public: - // Returns the equivalent org.rocksdb.PrepopulateBlobCache for the provided - // C++ ROCKSDB_NAMESPACE::PrepopulateBlobCache enum - static jbyte toJavaPrepopulateBlobCache( - ROCKSDB_NAMESPACE::PrepopulateBlobCache prepopulate_blob_cache) { - switch (prepopulate_blob_cache) { - case ROCKSDB_NAMESPACE::PrepopulateBlobCache::kDisable: - return 0x0; - case ROCKSDB_NAMESPACE::PrepopulateBlobCache::kFlushOnly: - return 0x1; - default: - return 0x7f; // undefined - } - } - - // Returns the equivalent C++ ROCKSDB_NAMESPACE::PrepopulateBlobCache enum for - // the provided Java org.rocksdb.PrepopulateBlobCache - static ROCKSDB_NAMESPACE::PrepopulateBlobCache toCppPrepopulateBlobCache( - jbyte jprepopulate_blob_cache) { - switch (jprepopulate_blob_cache) { - case 0x0: - return ROCKSDB_NAMESPACE::PrepopulateBlobCache::kDisable; - case 0x1: - return ROCKSDB_NAMESPACE::PrepopulateBlobCache::kFlushOnly; - case 0x7F: - default: - // undefined/default - return ROCKSDB_NAMESPACE::PrepopulateBlobCache::kDisable; - } - } -}; - -// The portal class for org.rocksdb.AbstractListener.EnabledEventCallback -class EnabledEventCallbackJni { - public: - // Returns the set of equivalent C++ - // ROCKSDB_NAMESPACE::EnabledEventCallbackJni::EnabledEventCallback enums for - // the provided Java jenabled_event_callback_values - static std::set toCppEnabledEventCallbacks( - jlong jenabled_event_callback_values) { - std::set enabled_event_callbacks; - for (size_t i = 0; i < EnabledEventCallback::NUM_ENABLED_EVENT_CALLBACK; - ++i) { - if (((1ULL << i) & jenabled_event_callback_values) > 0) { - enabled_event_callbacks.emplace(static_cast(i)); - } - } - return enabled_event_callbacks; - } -}; - -// The portal class for org.rocksdb.AbstractEventListener -class AbstractEventListenerJni - : public RocksDBNativeClass< - const ROCKSDB_NAMESPACE::EventListenerJniCallback*, - AbstractEventListenerJni> { - public: - /** - * Get the Java Class org.rocksdb.AbstractEventListener - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/AbstractEventListener"); - } - - /** - * Get the Java Method: AbstractEventListener#onFlushCompletedProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFlushCompletedProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onFlushCompletedProxy", - "(JLorg/rocksdb/FlushJobInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFlushBeginProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFlushBeginProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onFlushBeginProxy", - "(JLorg/rocksdb/FlushJobInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onTableFileDeleted - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnTableFileDeletedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onTableFileDeleted", "(Lorg/rocksdb/TableFileDeletionInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onCompactionBeginProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnCompactionBeginProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = - env->GetMethodID(jclazz, "onCompactionBeginProxy", - "(JLorg/rocksdb/CompactionJobInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onCompactionCompletedProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnCompactionCompletedProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = - env->GetMethodID(jclazz, "onCompactionCompletedProxy", - "(JLorg/rocksdb/CompactionJobInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onTableFileCreated - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnTableFileCreatedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onTableFileCreated", "(Lorg/rocksdb/TableFileCreationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onTableFileCreationStarted - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnTableFileCreationStartedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = - env->GetMethodID(jclazz, "onTableFileCreationStarted", - "(Lorg/rocksdb/TableFileCreationBriefInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onMemTableSealed - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnMemTableSealedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onMemTableSealed", - "(Lorg/rocksdb/MemTableInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: - * AbstractEventListener#onColumnFamilyHandleDeletionStarted - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnColumnFamilyHandleDeletionStartedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = - env->GetMethodID(jclazz, "onColumnFamilyHandleDeletionStarted", - "(Lorg/rocksdb/ColumnFamilyHandle;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onExternalFileIngestedProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnExternalFileIngestedProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = - env->GetMethodID(jclazz, "onExternalFileIngestedProxy", - "(JLorg/rocksdb/ExternalFileIngestionInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onBackgroundError - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnBackgroundErrorProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onBackgroundErrorProxy", - "(BLorg/rocksdb/Status;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onStallConditionsChanged - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnStallConditionsChangedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onStallConditionsChanged", - "(Lorg/rocksdb/WriteStallInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileReadFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileReadFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileReadFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileWriteFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileWriteFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileWriteFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileFlushFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileFlushFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileFlushFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileSyncFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileSyncFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileSyncFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileRangeSyncFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileRangeSyncFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileRangeSyncFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileTruncateFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileTruncateFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileTruncateFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onFileCloseFinish - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnFileCloseFinishMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID( - jclazz, "onFileCloseFinish", "(Lorg/rocksdb/FileOperationInfo;)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#shouldBeNotifiedOnFileIO - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getShouldBeNotifiedOnFileIOMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = - env->GetMethodID(jclazz, "shouldBeNotifiedOnFileIO", "()Z"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onErrorRecoveryBeginProxy - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnErrorRecoveryBeginProxyMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onErrorRecoveryBeginProxy", - "(BLorg/rocksdb/Status;)Z"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: AbstractEventListener#onErrorRecoveryCompleted - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID - */ - static jmethodID getOnErrorRecoveryCompletedMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID mid = env->GetMethodID(jclazz, "onErrorRecoveryCompleted", - "(Lorg/rocksdb/Status;)V"); - assert(mid != nullptr); - return mid; - } -}; - -class FlushJobInfoJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.FlushJobInfo object. - * - * @param env A pointer to the Java environment - * @param flush_job_info A Cpp flush job info object - * - * @return A reference to a Java org.rocksdb.FlushJobInfo object, or - * nullptr if an an exception occurs - */ - static jobject fromCppFlushJobInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::FlushJobInfo* flush_job_info) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jcf_name = JniUtil::toJavaString(env, &flush_job_info->cf_name); - if (env->ExceptionCheck()) { - return nullptr; - } - jstring jfile_path = JniUtil::toJavaString(env, &flush_job_info->file_path); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jfile_path); - return nullptr; - } - jobject jtable_properties = TablePropertiesJni::fromCppTableProperties( - env, flush_job_info->table_properties); - if (jtable_properties == nullptr) { - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(jfile_path); - return nullptr; - } - return env->NewObject( - jclazz, ctor, static_cast(flush_job_info->cf_id), jcf_name, - jfile_path, static_cast(flush_job_info->thread_id), - static_cast(flush_job_info->job_id), - static_cast(flush_job_info->triggered_writes_slowdown), - static_cast(flush_job_info->triggered_writes_stop), - static_cast(flush_job_info->smallest_seqno), - static_cast(flush_job_info->largest_seqno), jtable_properties, - static_cast(flush_job_info->flush_reason)); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/FlushJobInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", - "(JLjava/lang/String;Ljava/lang/String;JIZZJJLorg/" - "rocksdb/TableProperties;B)V"); - } -}; - -class TableFileDeletionInfoJni : public JavaClass { - public: - /** - * Create a new Java org.rocksdb.TableFileDeletionInfo object. - * - * @param env A pointer to the Java environment - * @param file_del_info A Cpp table file deletion info object - * - * @return A reference to a Java org.rocksdb.TableFileDeletionInfo object, or - * nullptr if an an exception occurs - */ - static jobject fromCppTableFileDeletionInfo( - JNIEnv* env, - const ROCKSDB_NAMESPACE::TableFileDeletionInfo* file_del_info) { - jclass jclazz = getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jdb_name = JniUtil::toJavaString(env, &file_del_info->db_name); - if (env->ExceptionCheck()) { - return nullptr; - } - jobject jstatus = StatusJni::construct(env, file_del_info->status); - if (jstatus == nullptr) { - env->DeleteLocalRef(jdb_name); - return nullptr; - } - return env->NewObject(jclazz, ctor, jdb_name, - JniUtil::toJavaString(env, &file_del_info->file_path), - static_cast(file_del_info->job_id), jstatus); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TableFileDeletionInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID( - clazz, "", - "(Ljava/lang/String;Ljava/lang/String;ILorg/rocksdb/Status;)V"); - } -}; - -class CompactionJobInfoJni : public JavaClass { - public: - static jobject fromCppCompactionJobInfo( - JNIEnv* env, - const ROCKSDB_NAMESPACE::CompactionJobInfo* compaction_job_info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - return env->NewObject(jclazz, ctor, - GET_CPLUSPLUS_POINTER(compaction_job_info)); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/CompactionJobInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", "(J)V"); - } -}; - -class TableFileCreationInfoJni : public JavaClass { - public: - static jobject fromCppTableFileCreationInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::TableFileCreationInfo* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jdb_name = JniUtil::toJavaString(env, &info->db_name); - if (env->ExceptionCheck()) { - return nullptr; - } - jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jdb_name); - return nullptr; - } - jstring jfile_path = JniUtil::toJavaString(env, &info->file_path); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - return nullptr; - } - jobject jtable_properties = - TablePropertiesJni::fromCppTableProperties(env, info->table_properties); - if (jtable_properties == nullptr) { - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - return nullptr; - } - jobject jstatus = StatusJni::construct(env, info->status); - if (jstatus == nullptr) { - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(jtable_properties); - return nullptr; - } - return env->NewObject(jclazz, ctor, static_cast(info->file_size), - jtable_properties, jstatus, jdb_name, jcf_name, - jfile_path, static_cast(info->job_id), - static_cast(info->reason)); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TableFileCreationInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID( - clazz, "", - "(JLorg/rocksdb/TableProperties;Lorg/rocksdb/Status;Ljava/lang/" - "String;Ljava/lang/String;Ljava/lang/String;IB)V"); - } -}; - -class TableFileCreationBriefInfoJni : public JavaClass { - public: - static jobject fromCppTableFileCreationBriefInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::TableFileCreationBriefInfo* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jdb_name = JniUtil::toJavaString(env, &info->db_name); - if (env->ExceptionCheck()) { - return nullptr; - } - jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jdb_name); - return nullptr; - } - jstring jfile_path = JniUtil::toJavaString(env, &info->file_path); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jdb_name); - env->DeleteLocalRef(jcf_name); - return nullptr; - } - return env->NewObject(jclazz, ctor, jdb_name, jcf_name, jfile_path, - static_cast(info->job_id), - static_cast(info->reason)); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/TableFileCreationBriefInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID( - clazz, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IB)V"); - } -}; - -class MemTableInfoJni : public JavaClass { - public: - static jobject fromCppMemTableInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::MemTableInfo* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name); - if (env->ExceptionCheck()) { - return nullptr; - } - return env->NewObject(jclazz, ctor, jcf_name, - static_cast(info->first_seqno), - static_cast(info->earliest_seqno), - static_cast(info->num_entries), - static_cast(info->num_deletes)); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/MemTableInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", "(Ljava/lang/String;JJJJ)V"); - } -}; - -class ExternalFileIngestionInfoJni : public JavaClass { - public: - static jobject fromCppExternalFileIngestionInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::ExternalFileIngestionInfo* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name); - if (env->ExceptionCheck()) { - return nullptr; - } - jstring jexternal_file_path = - JniUtil::toJavaString(env, &info->external_file_path); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jcf_name); - return nullptr; - } - jstring jinternal_file_path = - JniUtil::toJavaString(env, &info->internal_file_path); - if (env->ExceptionCheck()) { - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(jexternal_file_path); - return nullptr; - } - jobject jtable_properties = - TablePropertiesJni::fromCppTableProperties(env, info->table_properties); - if (jtable_properties == nullptr) { - env->DeleteLocalRef(jcf_name); - env->DeleteLocalRef(jexternal_file_path); - env->DeleteLocalRef(jinternal_file_path); - return nullptr; - } - return env->NewObject( - jclazz, ctor, jcf_name, jexternal_file_path, jinternal_file_path, - static_cast(info->global_seqno), jtable_properties); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/ExternalFileIngestionInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;JLorg/rocksdb/TableProperties;)V"); - } -}; - -class WriteStallInfoJni : public JavaClass { - public: - static jobject fromCppWriteStallInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::WriteStallInfo* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name); - if (env->ExceptionCheck()) { - return nullptr; - } - return env->NewObject(jclazz, ctor, jcf_name, - static_cast(info->condition.cur), - static_cast(info->condition.prev)); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WriteStallInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", "(Ljava/lang/String;BB)V"); - } -}; - -class FileOperationInfoJni : public JavaClass { - public: - static jobject fromCppFileOperationInfo( - JNIEnv* env, const ROCKSDB_NAMESPACE::FileOperationInfo* info) { - jclass jclazz = getJClass(env); - assert(jclazz != nullptr); - static jmethodID ctor = getConstructorMethodId(env, jclazz); - assert(ctor != nullptr); - jstring jpath = JniUtil::toJavaString(env, &info->path); - if (env->ExceptionCheck()) { - return nullptr; - } - jobject jstatus = StatusJni::construct(env, info->status); - if (jstatus == nullptr) { - env->DeleteLocalRef(jpath); - return nullptr; - } - return env->NewObject( - jclazz, ctor, jpath, static_cast(info->offset), - static_cast(info->length), - static_cast(info->start_ts.time_since_epoch().count()), - static_cast(info->duration.count()), jstatus); - } - - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/FileOperationInfo"); - } - - static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) { - return env->GetMethodID(clazz, "", - "(Ljava/lang/String;JJJJLorg/rocksdb/Status;)V"); - } -}; -} // namespace ROCKSDB_NAMESPACE -#endif // JAVA_ROCKSJNI_PORTAL_H_ diff --git a/java/rocksjni/ratelimiterjni.cc b/java/rocksjni/ratelimiterjni.cc deleted file mode 100644 index 7a17f367e..000000000 --- a/java/rocksjni/ratelimiterjni.cc +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for RateLimiter. - -#include "include/org_rocksdb_RateLimiter.h" -#include "rocksdb/rate_limiter.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_RateLimiter - * Method: newRateLimiterHandle - * Signature: (JJIBZ)J - */ -jlong Java_org_rocksdb_RateLimiter_newRateLimiterHandle( - JNIEnv* /*env*/, jclass /*jclazz*/, jlong jrate_bytes_per_second, - jlong jrefill_period_micros, jint jfairness, jbyte jrate_limiter_mode, - jboolean jauto_tune) { - auto rate_limiter_mode = - ROCKSDB_NAMESPACE::RateLimiterModeJni::toCppRateLimiterMode( - jrate_limiter_mode); - auto* sptr_rate_limiter = new std::shared_ptr( - ROCKSDB_NAMESPACE::NewGenericRateLimiter( - static_cast(jrate_bytes_per_second), - static_cast(jrefill_period_micros), - static_cast(jfairness), rate_limiter_mode, jauto_tune)); - - return GET_CPLUSPLUS_POINTER(sptr_rate_limiter); -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RateLimiter_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* handle = - reinterpret_cast*>( - jhandle); - delete handle; // delete std::shared_ptr -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: setBytesPerSecond - * Signature: (JJ)V - */ -void Java_org_rocksdb_RateLimiter_setBytesPerSecond(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle, - jlong jbytes_per_second) { - reinterpret_cast*>(handle) - ->get() - ->SetBytesPerSecond(jbytes_per_second); -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: getBytesPerSecond - * Signature: (J)J - */ -jlong Java_org_rocksdb_RateLimiter_getBytesPerSecond(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast*>( - handle) - ->get() - ->GetBytesPerSecond(); -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: request - * Signature: (JJ)V - */ -void Java_org_rocksdb_RateLimiter_request(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle, jlong jbytes) { - reinterpret_cast*>(handle) - ->get() - ->Request(jbytes, ROCKSDB_NAMESPACE::Env::IO_TOTAL); -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: getSingleBurstBytes - * Signature: (J)J - */ -jlong Java_org_rocksdb_RateLimiter_getSingleBurstBytes(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast*>( - handle) - ->get() - ->GetSingleBurstBytes(); -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: getTotalBytesThrough - * Signature: (J)J - */ -jlong Java_org_rocksdb_RateLimiter_getTotalBytesThrough(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast*>( - handle) - ->get() - ->GetTotalBytesThrough(); -} - -/* - * Class: org_rocksdb_RateLimiter - * Method: getTotalRequests - * Signature: (J)J - */ -jlong Java_org_rocksdb_RateLimiter_getTotalRequests(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast*>( - handle) - ->get() - ->GetTotalRequests(); -} diff --git a/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc b/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc deleted file mode 100644 index c0b09e151..000000000 --- a/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "include/org_rocksdb_RemoveEmptyValueCompactionFilter.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "utilities/compaction_filters/remove_emptyvalue_compactionfilter.h" - -/* - * Class: org_rocksdb_RemoveEmptyValueCompactionFilter - * Method: createNewRemoveEmptyValueCompactionFilter0 - * Signature: ()J - */ -jlong Java_org_rocksdb_RemoveEmptyValueCompactionFilter_createNewRemoveEmptyValueCompactionFilter0( - JNIEnv* /*env*/, jclass /*jcls*/) { - auto* compaction_filter = - new ROCKSDB_NAMESPACE::RemoveEmptyValueCompactionFilter(); - - // set the native handle to our native compaction filter - return GET_CPLUSPLUS_POINTER(compaction_filter); -} diff --git a/java/rocksjni/restorejni.cc b/java/rocksjni/restorejni.cc deleted file mode 100644 index aadc86128..000000000 --- a/java/rocksjni/restorejni.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::RestoreOptions methods -// from Java side. - -#include -#include -#include - -#include - -#include "include/org_rocksdb_RestoreOptions.h" -#include "rocksdb/utilities/backup_engine.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -/* - * Class: org_rocksdb_RestoreOptions - * Method: newRestoreOptions - * Signature: (Z)J - */ -jlong Java_org_rocksdb_RestoreOptions_newRestoreOptions( - JNIEnv* /*env*/, jclass /*jcls*/, jboolean keep_log_files) { - auto* ropt = new ROCKSDB_NAMESPACE::RestoreOptions(keep_log_files); - return GET_CPLUSPLUS_POINTER(ropt); -} - -/* - * Class: org_rocksdb_RestoreOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RestoreOptions_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* ropt = reinterpret_cast(jhandle); - assert(ropt); - delete ropt; -} diff --git a/java/rocksjni/rocks_callback_object.cc b/java/rocksjni/rocks_callback_object.cc deleted file mode 100644 index 35513e151..000000000 --- a/java/rocksjni/rocks_callback_object.cc +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// JNI Callbacks from C++ to sub-classes or org.rocksdb.RocksCallbackObject - -#include - -#include "include/org_rocksdb_RocksCallbackObject.h" -#include "jnicallback.h" - -/* - * Class: org_rocksdb_RocksCallbackObject - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksCallbackObject_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - // TODO(AR) is deleting from the super class JniCallback OK, or must we delete - // the subclass? Example hierarchies: - // 1) Comparator -> BaseComparatorJniCallback + JniCallback -> - // DirectComparatorJniCallback 2) Comparator -> BaseComparatorJniCallback + - // JniCallback -> ComparatorJniCallback - // I think this is okay, as Comparator and JniCallback both have virtual - // destructors... - delete reinterpret_cast(handle); -} diff --git a/java/rocksjni/rocksdb_exception_test.cc b/java/rocksjni/rocksdb_exception_test.cc deleted file mode 100644 index 67e62f726..000000000 --- a/java/rocksjni/rocksdb_exception_test.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "include/org_rocksdb_RocksDBExceptionTest.h" -#include "rocksdb/slice.h" -#include "rocksdb/status.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_RocksDBExceptionTest - * Method: raiseException - * Signature: ()V - */ -void Java_org_rocksdb_RocksDBExceptionTest_raiseException(JNIEnv* env, - jobject /*jobj*/) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, - std::string("test message")); -} - -/* - * Class: org_rocksdb_RocksDBExceptionTest - * Method: raiseExceptionWithStatusCode - * Signature: ()V - */ -void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCode( - JNIEnv* env, jobject /*jobj*/) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "test message", ROCKSDB_NAMESPACE::Status::NotSupported()); -} - -/* - * Class: org_rocksdb_RocksDBExceptionTest - * Method: raiseExceptionNoMsgWithStatusCode - * Signature: ()V - */ -void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionNoMsgWithStatusCode( - JNIEnv* env, jobject /*jobj*/) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::NotSupported()); -} - -/* - * Class: org_rocksdb_RocksDBExceptionTest - * Method: raiseExceptionWithStatusCodeSubCode - * Signature: ()V - */ -void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCodeSubCode( - JNIEnv* env, jobject /*jobj*/) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "test message", - ROCKSDB_NAMESPACE::Status::TimedOut( - ROCKSDB_NAMESPACE::Status::SubCode::kLockTimeout)); -} - -/* - * Class: org_rocksdb_RocksDBExceptionTest - * Method: raiseExceptionNoMsgWithStatusCodeSubCode - * Signature: ()V - */ -void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionNoMsgWithStatusCodeSubCode( - JNIEnv* env, jobject /*jobj*/) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::TimedOut( - ROCKSDB_NAMESPACE::Status::SubCode::kLockTimeout)); -} - -/* - * Class: org_rocksdb_RocksDBExceptionTest - * Method: raiseExceptionWithStatusCodeState - * Signature: ()V - */ -void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCodeState( - JNIEnv* env, jobject /*jobj*/) { - ROCKSDB_NAMESPACE::Slice state("test state"); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "test message", ROCKSDB_NAMESPACE::Status::NotSupported(state)); -} diff --git a/java/rocksjni/rocksjni.cc b/java/rocksjni/rocksjni.cc deleted file mode 100644 index ced72e841..000000000 --- a/java/rocksjni/rocksjni.cc +++ /dev/null @@ -1,3957 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::DB methods from Java side. - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "include/org_rocksdb_RocksDB.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "rocksdb/types.h" -#include "rocksdb/version.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -#ifdef min -#undef min -#endif - -jlong rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, jstring jdb_path, - std::function - open_fn) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - auto* opt = reinterpret_cast(jopt_handle); - ROCKSDB_NAMESPACE::DB* db = nullptr; - ROCKSDB_NAMESPACE::Status s = open_fn(*opt, db_path, &db); - - env->ReleaseStringUTFChars(jdb_path, db_path); - - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(db); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: open - * Signature: (JLjava/lang/String;)J - */ -jlong Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2(JNIEnv* env, jclass, - jlong jopt_handle, - jstring jdb_path) { - return rocksdb_open_helper(env, jopt_handle, jdb_path, - (ROCKSDB_NAMESPACE::Status(*)( - const ROCKSDB_NAMESPACE::Options&, - const std::string&, ROCKSDB_NAMESPACE::DB**)) & - ROCKSDB_NAMESPACE::DB::Open); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: openROnly - * Signature: (JLjava/lang/String;Z)J - */ -jlong Java_org_rocksdb_RocksDB_openROnly__JLjava_lang_String_2Z( - JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, - jboolean jerror_if_wal_file_exists) { - const bool error_if_wal_file_exists = jerror_if_wal_file_exists == JNI_TRUE; - return rocksdb_open_helper( - env, jopt_handle, jdb_path, - [error_if_wal_file_exists](const ROCKSDB_NAMESPACE::Options& options, - const std::string& db_path, - ROCKSDB_NAMESPACE::DB** db) { - return ROCKSDB_NAMESPACE::DB::OpenForReadOnly(options, db_path, db, - error_if_wal_file_exists); - }); -} - -jlongArray rocksdb_open_helper( - JNIEnv* env, jlong jopt_handle, jstring jdb_path, - jobjectArray jcolumn_names, jlongArray jcolumn_options, - std::function&, - std::vector*, - ROCKSDB_NAMESPACE::DB**)> - open_fn) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - const jsize len_cols = env->GetArrayLength(jcolumn_names); - jlong* jco = env->GetLongArrayElements(jcolumn_options, nullptr); - if (jco == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - std::vector column_families; - jboolean has_exception = JNI_FALSE; - ROCKSDB_NAMESPACE::JniUtil::byteStrings( - env, jcolumn_names, - [](const char* str_data, const size_t str_len) { - return std::string(str_data, str_len); - }, - [&jco, &column_families](size_t idx, std::string cf_name) { - ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options = - reinterpret_cast(jco[idx]); - column_families.push_back( - ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(cf_name, *cf_options)); - }, - &has_exception); - - env->ReleaseLongArrayElements(jcolumn_options, jco, JNI_ABORT); - - if (has_exception == JNI_TRUE) { - // exception occurred - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - auto* opt = reinterpret_cast(jopt_handle); - std::vector cf_handles; - ROCKSDB_NAMESPACE::DB* db = nullptr; - ROCKSDB_NAMESPACE::Status s = - open_fn(*opt, db_path, column_families, &cf_handles, &db); - - // we have now finished with db_path - env->ReleaseStringUTFChars(jdb_path, db_path); - - // check if open operation was successful - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - const jsize resultsLen = 1 + len_cols; // db handle + column family handles - std::unique_ptr results = - std::unique_ptr(new jlong[resultsLen]); - results[0] = GET_CPLUSPLUS_POINTER(db); - for (int i = 1; i <= len_cols; i++) { - results[i] = GET_CPLUSPLUS_POINTER(cf_handles[i - 1]); - } - - jlongArray jresults = env->NewLongArray(resultsLen); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresults); - return nullptr; - } - - return jresults; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: openROnly - * Signature: (JLjava/lang/String;[[B[JZ)[J - */ -jlongArray Java_org_rocksdb_RocksDB_openROnly__JLjava_lang_String_2_3_3B_3JZ( - JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, - jobjectArray jcolumn_names, jlongArray jcolumn_options, - jboolean jerror_if_wal_file_exists) { - const bool error_if_wal_file_exists = jerror_if_wal_file_exists == JNI_TRUE; - return rocksdb_open_helper( - env, jopt_handle, jdb_path, jcolumn_names, jcolumn_options, - [error_if_wal_file_exists]( - const ROCKSDB_NAMESPACE::DBOptions& options, - const std::string& db_path, - const std::vector& - column_families, - std::vector* handles, - ROCKSDB_NAMESPACE::DB** db) { - return ROCKSDB_NAMESPACE::DB::OpenForReadOnly( - options, db_path, column_families, handles, db, - error_if_wal_file_exists); - }); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: open - * Signature: (JLjava/lang/String;[[B[J)[J - */ -jlongArray Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2_3_3B_3J( - JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, - jobjectArray jcolumn_names, jlongArray jcolumn_options) { - return rocksdb_open_helper( - env, jopt_handle, jdb_path, jcolumn_names, jcolumn_options, - (ROCKSDB_NAMESPACE::Status(*)( - const ROCKSDB_NAMESPACE::DBOptions&, const std::string&, - const std::vector&, - std::vector*, - ROCKSDB_NAMESPACE::DB**)) & - ROCKSDB_NAMESPACE::DB::Open); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: openAsSecondary - * Signature: (JLjava/lang/String;Ljava/lang/String;)J - */ -jlong Java_org_rocksdb_RocksDB_openAsSecondary__JLjava_lang_String_2Ljava_lang_String_2( - JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, - jstring jsecondary_db_path) { - const char* secondary_db_path = - env->GetStringUTFChars(jsecondary_db_path, nullptr); - if (secondary_db_path == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - jlong db_handle = rocksdb_open_helper( - env, jopt_handle, jdb_path, - [secondary_db_path](const ROCKSDB_NAMESPACE::Options& options, - const std::string& db_path, - ROCKSDB_NAMESPACE::DB** db) { - return ROCKSDB_NAMESPACE::DB::OpenAsSecondary(options, db_path, - secondary_db_path, db); - }); - - // we have now finished with secondary_db_path - env->ReleaseStringUTFChars(jsecondary_db_path, secondary_db_path); - - return db_handle; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: openAsSecondary - * Signature: (JLjava/lang/String;Ljava/lang/String;[[B[J)[J - */ -jlongArray -Java_org_rocksdb_RocksDB_openAsSecondary__JLjava_lang_String_2Ljava_lang_String_2_3_3B_3J( - JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, - jstring jsecondary_db_path, jobjectArray jcolumn_names, - jlongArray jcolumn_options) { - const char* secondary_db_path = - env->GetStringUTFChars(jsecondary_db_path, nullptr); - if (secondary_db_path == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - jlongArray jhandles = rocksdb_open_helper( - env, jopt_handle, jdb_path, jcolumn_names, jcolumn_options, - [secondary_db_path]( - const ROCKSDB_NAMESPACE::DBOptions& options, - const std::string& db_path, - const std::vector& - column_families, - std::vector* handles, - ROCKSDB_NAMESPACE::DB** db) { - return ROCKSDB_NAMESPACE::DB::OpenAsSecondary( - options, db_path, secondary_db_path, column_families, handles, db); - }); - - // we have now finished with secondary_db_path - env->ReleaseStringUTFChars(jsecondary_db_path, secondary_db_path); - - return jhandles; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_disposeInternal(JNIEnv*, jobject, jlong jhandle) { - auto* db = reinterpret_cast(jhandle); - assert(db != nullptr); - delete db; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: closeDatabase - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_closeDatabase(JNIEnv* env, jclass, - jlong jhandle) { - auto* db = reinterpret_cast(jhandle); - assert(db != nullptr); - ROCKSDB_NAMESPACE::Status s = db->Close(); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: listColumnFamilies - * Signature: (JLjava/lang/String;)[[B - */ -jobjectArray Java_org_rocksdb_RocksDB_listColumnFamilies(JNIEnv* env, jclass, - jlong jopt_handle, - jstring jdb_path) { - std::vector column_family_names; - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - auto* opt = reinterpret_cast(jopt_handle); - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::DB::ListColumnFamilies( - *opt, db_path, &column_family_names); - - env->ReleaseStringUTFChars(jdb_path, db_path); - - jobjectArray jcolumn_family_names = - ROCKSDB_NAMESPACE::JniUtil::stringsBytes(env, column_family_names); - - return jcolumn_family_names; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: createColumnFamily - * Signature: (J[BIJ)J - */ -jlong Java_org_rocksdb_RocksDB_createColumnFamily(JNIEnv* env, jobject, - jlong jhandle, - jbyteArray jcf_name, - jint jcf_name_len, - jlong jcf_options_handle) { - auto* db = reinterpret_cast(jhandle); - jboolean has_exception = JNI_FALSE; - const std::string cf_name = - ROCKSDB_NAMESPACE::JniUtil::byteString( - env, jcf_name, jcf_name_len, - [](const char* str, const size_t len) { - return std::string(str, len); - }, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return 0; - } - auto* cf_options = reinterpret_cast( - jcf_options_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - ROCKSDB_NAMESPACE::Status s = - db->CreateColumnFamily(*cf_options, cf_name, &cf_handle); - if (!s.ok()) { - // error occurred - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; - } - return GET_CPLUSPLUS_POINTER(cf_handle); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: createColumnFamilies - * Signature: (JJ[[B)[J - */ -jlongArray Java_org_rocksdb_RocksDB_createColumnFamilies__JJ_3_3B( - JNIEnv* env, jobject, jlong jhandle, jlong jcf_options_handle, - jobjectArray jcf_names) { - auto* db = reinterpret_cast(jhandle); - auto* cf_options = reinterpret_cast( - jcf_options_handle); - jboolean has_exception = JNI_FALSE; - std::vector cf_names; - ROCKSDB_NAMESPACE::JniUtil::byteStrings( - env, jcf_names, - [](const char* str, const size_t len) { return std::string(str, len); }, - [&cf_names](const size_t, std::string str) { cf_names.push_back(str); }, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return nullptr; - } - - std::vector cf_handles; - ROCKSDB_NAMESPACE::Status s = - db->CreateColumnFamilies(*cf_options, cf_names, &cf_handles); - if (!s.ok()) { - // error occurred - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - jlongArray jcf_handles = ROCKSDB_NAMESPACE::JniUtil::toJPointers< - ROCKSDB_NAMESPACE::ColumnFamilyHandle>(env, cf_handles, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return nullptr; - } - return jcf_handles; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: createColumnFamilies - * Signature: (J[J[[B)[J - */ -jlongArray Java_org_rocksdb_RocksDB_createColumnFamilies__J_3J_3_3B( - JNIEnv* env, jobject, jlong jhandle, jlongArray jcf_options_handles, - jobjectArray jcf_names) { - auto* db = reinterpret_cast(jhandle); - const jsize jlen = env->GetArrayLength(jcf_options_handles); - std::vector cf_descriptors; - cf_descriptors.reserve(jlen); - - jlong* jcf_options_handles_elems = - env->GetLongArrayElements(jcf_options_handles, nullptr); - if (jcf_options_handles_elems == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - // extract the column family descriptors - jboolean has_exception = JNI_FALSE; - for (jsize i = 0; i < jlen; i++) { - auto* cf_options = - reinterpret_cast( - jcf_options_handles_elems[i]); - jbyteArray jcf_name = - static_cast(env->GetObjectArrayElement(jcf_names, i)); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements(jcf_options_handles, - jcf_options_handles_elems, JNI_ABORT); - return nullptr; - } - const std::string cf_name = - ROCKSDB_NAMESPACE::JniUtil::byteString( - env, jcf_name, - [](const char* str, const size_t len) { - return std::string(str, len); - }, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - env->DeleteLocalRef(jcf_name); - env->ReleaseLongArrayElements(jcf_options_handles, - jcf_options_handles_elems, JNI_ABORT); - return nullptr; - } - - cf_descriptors.push_back( - ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(cf_name, *cf_options)); - - env->DeleteLocalRef(jcf_name); - } - - std::vector cf_handles; - ROCKSDB_NAMESPACE::Status s = - db->CreateColumnFamilies(cf_descriptors, &cf_handles); - - env->ReleaseLongArrayElements(jcf_options_handles, jcf_options_handles_elems, - JNI_ABORT); - - if (!s.ok()) { - // error occurred - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - jlongArray jcf_handles = ROCKSDB_NAMESPACE::JniUtil::toJPointers< - ROCKSDB_NAMESPACE::ColumnFamilyHandle>(env, cf_handles, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return nullptr; - } - return jcf_handles; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: dropColumnFamily - * Signature: (JJ)V; - */ -void Java_org_rocksdb_RocksDB_dropColumnFamily(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - ROCKSDB_NAMESPACE::Status s = db_handle->DropColumnFamily(cf_handle); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: dropColumnFamilies - * Signature: (J[J)V - */ -void Java_org_rocksdb_RocksDB_dropColumnFamilies( - JNIEnv* env, jobject, jlong jdb_handle, jlongArray jcolumn_family_handles) { - auto* db_handle = reinterpret_cast(jdb_handle); - - std::vector cf_handles; - if (jcolumn_family_handles != nullptr) { - const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); - - jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); - if (jcfh == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - for (jsize i = 0; i < len_cols; i++) { - auto* cf_handle = - reinterpret_cast(jcfh[i]); - cf_handles.push_back(cf_handle); - } - env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); - } - - ROCKSDB_NAMESPACE::Status s = db_handle->DropColumnFamilies(cf_handles); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::Put - -/** - * @return true if the put succeeded, false if a Java Exception was thrown - */ -bool rocksdb_put_helper(JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::WriteOptions& write_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, - jbyteArray jval, jint jval_off, jint jval_len) { - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - return false; - } - - jbyte* value = new jbyte[jval_len]; - env->GetByteArrayRegion(jval, jval_off, jval_len, value); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] value; - delete[] key; - return false; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast(value), - jval_len); - - ROCKSDB_NAMESPACE::Status s; - if (cf_handle != nullptr) { - s = db->Put(write_options, cf_handle, key_slice, value_slice); - } else { - // backwards compatibility - s = db->Put(write_options, key_slice, value_slice); - } - - // cleanup - delete[] value; - delete[] key; - - if (s.ok()) { - return true; - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return false; - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: put - * Signature: (J[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_put__J_3BII_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - rocksdb_put_helper(env, db, default_write_options, nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: put - * Signature: (J[BII[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_put__J_3BII_3BIIJ(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_put_helper(env, db, default_write_options, cf_handle, jkey, - jkey_off, jkey_len, jval, jval_off, jval_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: put - * Signature: (JJ[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - rocksdb_put_helper(env, db, *write_options, nullptr, jkey, jkey_off, jkey_len, - jval, jval_off, jval_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: put - * Signature: (JJ[BII[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_put_helper(env, db, *write_options, cf_handle, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: putDirect - * Signature: (JJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)V - */ -void Java_org_rocksdb_RocksDB_putDirect( - JNIEnv* env, jobject /*jdb*/, jlong jdb_handle, jlong jwrite_options_handle, - jobject jkey, jint jkey_off, jint jkey_len, jobject jval, jint jval_off, - jint jval_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto put = [&env, &db, &cf_handle, &write_options]( - ROCKSDB_NAMESPACE::Slice& key, - ROCKSDB_NAMESPACE::Slice& value) { - ROCKSDB_NAMESPACE::Status s; - if (cf_handle == nullptr) { - s = db->Put(*write_options, key, value); - } else { - s = db->Put(*write_options, cf_handle, key, value); - } - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - }; - ROCKSDB_NAMESPACE::JniUtil::kv_op_direct(put, env, jkey, jkey_off, jkey_len, - jval, jval_off, jval_len); -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::Delete() - -/** - * @return true if the delete succeeded, false if a Java Exception was thrown - */ -bool rocksdb_delete_helper(JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::WriteOptions& write_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len) { - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - return false; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - ROCKSDB_NAMESPACE::Status s; - if (cf_handle != nullptr) { - s = db->Delete(write_options, cf_handle, key_slice); - } else { - // backwards compatibility - s = db->Delete(write_options, key_slice); - } - - // cleanup - delete[] key; - - if (s.ok()) { - return true; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return false; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (J[BII)V - */ -void Java_org_rocksdb_RocksDB_delete__J_3BII(JNIEnv* env, jobject, - jlong jdb_handle, jbyteArray jkey, - jint jkey_off, jint jkey_len) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - rocksdb_delete_helper(env, db, default_write_options, nullptr, jkey, jkey_off, - jkey_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (J[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_delete__J_3BIIJ(JNIEnv* env, jobject, - jlong jdb_handle, jbyteArray jkey, - jint jkey_off, jint jkey_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_delete_helper(env, db, default_write_options, cf_handle, jkey, - jkey_off, jkey_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (JJ[BII)V - */ -void Java_org_rocksdb_RocksDB_delete__JJ_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jwrite_options, - jbyteArray jkey, jint jkey_off, - jint jkey_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - rocksdb_delete_helper(env, db, *write_options, nullptr, jkey, jkey_off, - jkey_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (JJ[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_delete__JJ_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, - jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_delete_helper(env, db, *write_options, cf_handle, jkey, jkey_off, - jkey_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::SingleDelete() -/** - * @return true if the single delete succeeded, false if a Java Exception - * was thrown - */ -bool rocksdb_single_delete_helper( - JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::WriteOptions& write_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle, jbyteArray jkey, - jint jkey_len) { - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, 0, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - return false; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - ROCKSDB_NAMESPACE::Status s; - if (cf_handle != nullptr) { - s = db->SingleDelete(write_options, cf_handle, key_slice); - } else { - // backwards compatibility - s = db->SingleDelete(write_options, key_slice); - } - - delete[] key; - - if (s.ok()) { - return true; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return false; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_RocksDB_singleDelete__J_3BI(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, - jint jkey_len) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - rocksdb_single_delete_helper(env, db, default_write_options, nullptr, jkey, - jkey_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_RocksDB_singleDelete__J_3BIJ(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, - jint jkey_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_single_delete_helper(env, db, default_write_options, cf_handle, - jkey, jkey_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (JJ[BIJ)V - */ -void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BI(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jwrite_options, - jbyteArray jkey, - jint jkey_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - rocksdb_single_delete_helper(env, db, *write_options, nullptr, jkey, - jkey_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (JJ[BIJ)V - */ -void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, - jbyteArray jkey, jint jkey_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_single_delete_helper(env, db, *write_options, cf_handle, jkey, - jkey_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::DeleteRange() -/** - * @return true if the delete range succeeded, false if a Java Exception - * was thrown - */ -bool rocksdb_delete_range_helper( - JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::WriteOptions& write_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle, jbyteArray jbegin_key, - jint jbegin_key_off, jint jbegin_key_len, jbyteArray jend_key, - jint jend_key_off, jint jend_key_len) { - jbyte* begin_key = new jbyte[jbegin_key_len]; - env->GetByteArrayRegion(jbegin_key, jbegin_key_off, jbegin_key_len, - begin_key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] begin_key; - return false; - } - ROCKSDB_NAMESPACE::Slice begin_key_slice(reinterpret_cast(begin_key), - jbegin_key_len); - - jbyte* end_key = new jbyte[jend_key_len]; - env->GetByteArrayRegion(jend_key, jend_key_off, jend_key_len, end_key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] begin_key; - delete[] end_key; - return false; - } - ROCKSDB_NAMESPACE::Slice end_key_slice(reinterpret_cast(end_key), - jend_key_len); - - ROCKSDB_NAMESPACE::Status s = - db->DeleteRange(write_options, cf_handle, begin_key_slice, end_key_slice); - - // cleanup - delete[] begin_key; - delete[] end_key; - - if (s.ok()) { - return true; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return false; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (J[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_deleteRange__J_3BII_3BII( - JNIEnv* env, jobject, jlong jdb_handle, jbyteArray jbegin_key, - jint jbegin_key_off, jint jbegin_key_len, jbyteArray jend_key, - jint jend_key_off, jint jend_key_len) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - rocksdb_delete_range_helper(env, db, default_write_options, nullptr, - jbegin_key, jbegin_key_off, jbegin_key_len, - jend_key, jend_key_off, jend_key_len); -} - -jint rocksdb_get_helper_direct( - JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::ReadOptions& read_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* column_family_handle, jobject jkey, - jint jkey_off, jint jkey_len, jobject jval, jint jval_off, jint jval_len, - bool* has_exception) { - static const int kNotFound = -1; - static const int kStatusError = -2; - static const int kArgumentError = -3; - - char* key = reinterpret_cast(env->GetDirectBufferAddress(jkey)); - if (key == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid key argument (argument is not a valid direct ByteBuffer)"); - *has_exception = true; - return kArgumentError; - } - if (env->GetDirectBufferCapacity(jkey) < (jkey_off + jkey_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid key argument. Capacity is less than requested region (offset " - "+ length)."); - *has_exception = true; - return kArgumentError; - } - - char* value = reinterpret_cast(env->GetDirectBufferAddress(jval)); - if (value == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid value argument (argument is not a valid direct ByteBuffer)"); - *has_exception = true; - return kArgumentError; - } - - if (env->GetDirectBufferCapacity(jval) < (jval_off + jval_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid value argument. Capacity is less than requested region " - "(offset + length)."); - *has_exception = true; - return kArgumentError; - } - - key += jkey_off; - value += jval_off; - - ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len); - - ROCKSDB_NAMESPACE::PinnableSlice pinnable_value; - ROCKSDB_NAMESPACE::Status s; - if (column_family_handle != nullptr) { - s = db->Get(read_options, column_family_handle, key_slice, &pinnable_value); - } else { - // backwards compatibility - s = db->Get(read_options, db->DefaultColumnFamily(), key_slice, - &pinnable_value); - } - - if (s.IsNotFound()) { - *has_exception = false; - return kNotFound; - } else if (!s.ok()) { - *has_exception = true; - // Here since we are throwing a Java exception from c++ side. - // As a result, c++ does not know calling this function will in fact - // throwing an exception. As a result, the execution flow will - // not stop here, and codes after this throw will still be - // executed. - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - - // Return a dummy const value to avoid compilation error, although - // java side might not have a chance to get the return value :) - return kStatusError; - } - - const jint pinnable_value_len = static_cast(pinnable_value.size()); - const jint length = std::min(jval_len, pinnable_value_len); - - memcpy(value, pinnable_value.data(), length); - pinnable_value.Reset(); - - *has_exception = false; - return pinnable_value_len; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (J[BII[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_deleteRange__J_3BII_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jbyteArray jbegin_key, - jint jbegin_key_off, jint jbegin_key_len, jbyteArray jend_key, - jint jend_key_off, jint jend_key_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_delete_range_helper(env, db, default_write_options, cf_handle, - jbegin_key, jbegin_key_off, jbegin_key_len, - jend_key, jend_key_off, jend_key_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (JJ[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_deleteRange__JJ_3BII_3BII( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, - jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, - jbyteArray jend_key, jint jend_key_off, jint jend_key_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - rocksdb_delete_range_helper(env, db, *write_options, nullptr, jbegin_key, - jbegin_key_off, jbegin_key_len, jend_key, - jend_key_off, jend_key_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (JJ[BII[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_deleteRange__JJ_3BII_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, - jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, - jbyteArray jend_key, jint jend_key_off, jint jend_key_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_delete_range_helper(env, db, *write_options, cf_handle, jbegin_key, - jbegin_key_off, jbegin_key_len, jend_key, - jend_key_off, jend_key_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getDirect - * Signature: (JJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)I - */ -jint Java_org_rocksdb_RocksDB_getDirect(JNIEnv* env, jobject /*jdb*/, - jlong jdb_handle, jlong jropt_handle, - jobject jkey, jint jkey_off, - jint jkey_len, jobject jval, - jint jval_off, jint jval_len, - jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto* ro_opt = - reinterpret_cast(jropt_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - bool has_exception = false; - return rocksdb_get_helper_direct( - env, db_handle, - ro_opt == nullptr ? ROCKSDB_NAMESPACE::ReadOptions() : *ro_opt, cf_handle, - jkey, jkey_off, jkey_len, jval, jval_off, jval_len, &has_exception); -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::Merge - -/** - * @return true if the merge succeeded, false if a Java Exception was thrown - */ -bool rocksdb_merge_helper(JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::WriteOptions& write_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, - jbyteArray jval, jint jval_off, jint jval_len) { - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - return false; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - jbyte* value = new jbyte[jval_len]; - env->GetByteArrayRegion(jval, jval_off, jval_len, value); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] value; - delete[] key; - return false; - } - ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast(value), - jval_len); - - ROCKSDB_NAMESPACE::Status s; - if (cf_handle != nullptr) { - s = db->Merge(write_options, cf_handle, key_slice, value_slice); - } else { - s = db->Merge(write_options, key_slice, value_slice); - } - - // cleanup - delete[] value; - delete[] key; - - if (s.ok()) { - return true; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return false; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (J[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_merge__J_3BII_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - rocksdb_merge_helper(env, db, default_write_options, nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (J[BII[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_merge__J_3BII_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, jint jval_off, jint jval_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - static const ROCKSDB_NAMESPACE::WriteOptions default_write_options = - ROCKSDB_NAMESPACE::WriteOptions(); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_merge_helper(env, db, default_write_options, cf_handle, jkey, - jkey_off, jkey_len, jval, jval_off, jval_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (JJ[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BII( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - rocksdb_merge_helper(env, db, *write_options, nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (JJ[BII[BIIJ)V - */ -void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_merge_helper(env, db, *write_options, cf_handle, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - } -} - -jlong rocksdb_iterator_helper( - ROCKSDB_NAMESPACE::DB* db, ROCKSDB_NAMESPACE::ReadOptions read_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle) { - ROCKSDB_NAMESPACE::Iterator* iterator = nullptr; - if (cf_handle != nullptr) { - iterator = db->NewIterator(read_options, cf_handle); - } else { - iterator = db->NewIterator(read_options); - } - return GET_CPLUSPLUS_POINTER(iterator); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: deleteDirect - * Signature: (JJLjava/nio/ByteBuffer;IIJ)V - */ -void Java_org_rocksdb_RocksDB_deleteDirect(JNIEnv* env, jobject /*jdb*/, - jlong jdb_handle, - jlong jwrite_options, jobject jkey, - jint jkey_offset, jint jkey_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto remove = [&env, &db, &write_options, - &cf_handle](ROCKSDB_NAMESPACE::Slice& key) { - ROCKSDB_NAMESPACE::Status s; - if (cf_handle == nullptr) { - s = db->Delete(*write_options, key); - } else { - s = db->Delete(*write_options, cf_handle, key); - } - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(remove, env, jkey, jkey_offset, - jkey_len); -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::Write -/* - * Class: org_rocksdb_RocksDB - * Method: write0 - * Signature: (JJJ)V - */ -void Java_org_rocksdb_RocksDB_write0(JNIEnv* env, jobject, jlong jdb_handle, - jlong jwrite_options_handle, - jlong jwb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* wb = reinterpret_cast(jwb_handle); - - ROCKSDB_NAMESPACE::Status s = db->Write(*write_options, wb); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: write1 - * Signature: (JJJ)V - */ -void Java_org_rocksdb_RocksDB_write1(JNIEnv* env, jobject, jlong jdb_handle, - jlong jwrite_options_handle, - jlong jwbwi_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* wb = wbwi->GetWriteBatch(); - - ROCKSDB_NAMESPACE::Status s = db->Write(*write_options, wb); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::Get - -jbyteArray rocksdb_get_helper( - JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::ReadOptions& read_opt, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* column_family_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len) { - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - return nullptr; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - ROCKSDB_NAMESPACE::PinnableSlice pinnable_value; - ROCKSDB_NAMESPACE::Status s; - if (column_family_handle != nullptr) { - s = db->Get(read_opt, column_family_handle, key_slice, &pinnable_value); - } else { - s = db->Get(read_opt, db->DefaultColumnFamily(), key_slice, - &pinnable_value); - } - - // cleanup - delete[] key; - - if (s.IsNotFound()) { - return nullptr; - } - - if (s.ok()) { - jbyteArray jret_value = - ROCKSDB_NAMESPACE::JniUtil::copyBytes(env, pinnable_value); - pinnable_value.Reset(); - if (jret_value == nullptr) { - // exception occurred - return nullptr; - } - return jret_value; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BII)[B - */ -jbyteArray Java_org_rocksdb_RocksDB_get__J_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len) { - return rocksdb_get_helper( - env, reinterpret_cast(jdb_handle), - ROCKSDB_NAMESPACE::ReadOptions(), nullptr, jkey, jkey_off, jkey_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BIIJ)[B - */ -jbyteArray Java_org_rocksdb_RocksDB_get__J_3BIIJ(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, - jlong jcf_handle) { - auto db_handle = reinterpret_cast(jdb_handle); - auto cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - return rocksdb_get_helper(env, db_handle, ROCKSDB_NAMESPACE::ReadOptions(), - cf_handle, jkey, jkey_off, jkey_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - return nullptr; - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BII)[B - */ -jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jropt_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len) { - return rocksdb_get_helper( - env, reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), nullptr, - jkey, jkey_off, jkey_len); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BIIJ)[B - */ -jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, jbyteArray jkey, - jint jkey_off, jint jkey_len, jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto& ro_opt = - *reinterpret_cast(jropt_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - return rocksdb_get_helper(env, db_handle, ro_opt, cf_handle, jkey, jkey_off, - jkey_len); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - return nullptr; - } -} - -jint rocksdb_get_helper( - JNIEnv* env, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::ReadOptions& read_options, - ROCKSDB_NAMESPACE::ColumnFamilyHandle* column_family_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, bool* has_exception) { - static const int kNotFound = -1; - static const int kStatusError = -2; - - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - delete[] key; - *has_exception = true; - return kStatusError; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - ROCKSDB_NAMESPACE::PinnableSlice pinnable_value; - ROCKSDB_NAMESPACE::Status s; - if (column_family_handle != nullptr) { - s = db->Get(read_options, column_family_handle, key_slice, &pinnable_value); - } else { - s = db->Get(read_options, db->DefaultColumnFamily(), key_slice, - &pinnable_value); - } - - // cleanup - delete[] key; - - if (s.IsNotFound()) { - *has_exception = false; - return kNotFound; - } else if (!s.ok()) { - *has_exception = true; - // Here since we are throwing a Java exception from c++ side. - // As a result, c++ does not know calling this function will in fact - // throwing an exception. As a result, the execution flow will - // not stop here, and codes after this throw will still be - // executed. - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - - // Return a dummy const value to avoid compilation error, although - // java side might not have a chance to get the return value :) - return kStatusError; - } - - const jint pinnable_value_len = static_cast(pinnable_value.size()); - const jint length = std::min(jval_len, pinnable_value_len); - - env->SetByteArrayRegion(jval, jval_off, length, - const_cast(reinterpret_cast( - pinnable_value.data()))); - pinnable_value.Reset(); - if (env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - *has_exception = true; - return kStatusError; - } - - *has_exception = false; - return pinnable_value_len; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BII[BII)I - */ -jint Java_org_rocksdb_RocksDB_get__J_3BII_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - bool has_exception = false; - return rocksdb_get_helper( - env, reinterpret_cast(jdb_handle), - ROCKSDB_NAMESPACE::ReadOptions(), nullptr, jkey, jkey_off, jkey_len, jval, - jval_off, jval_len, &has_exception); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BII[BIIJ)I - */ -jint Java_org_rocksdb_RocksDB_get__J_3BII_3BIIJ(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, - jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - bool has_exception = false; - return rocksdb_get_helper(env, db_handle, ROCKSDB_NAMESPACE::ReadOptions(), - cf_handle, jkey, jkey_off, jkey_len, jval, - jval_off, jval_len, &has_exception); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - // will never be evaluated - return 0; - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BII[BII)I - */ -jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BII(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jropt_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - bool has_exception = false; - return rocksdb_get_helper( - env, reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), nullptr, - jkey, jkey_off, jkey_len, jval, jval_off, jval_len, &has_exception); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BII[BIIJ)I - */ -jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ( - JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, jbyteArray jkey, - jint jkey_off, jint jkey_len, jbyteArray jval, jint jval_off, jint jval_len, - jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto& ro_opt = - *reinterpret_cast(jropt_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - bool has_exception = false; - return rocksdb_get_helper(env, db_handle, ro_opt, cf_handle, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len, - &has_exception); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Invalid ColumnFamilyHandle.")); - // will never be evaluated - return 0; - } -} - -inline void multi_get_helper_release_keys(std::vector& keys_to_free) { - auto end = keys_to_free.end(); - for (auto it = keys_to_free.begin(); it != end; ++it) { - delete[] * it; - } - keys_to_free.clear(); -} - -/** - * @brief fill a native array of cf handles from java handles - * - * @param env - * @param cf_handles to fill from the java variants - * @param jcolumn_family_handles - * @return true if the copy succeeds - * @return false if a JNI exception is generated - */ -inline bool cf_handles_from_jcf_handles( - JNIEnv* env, - std::vector& cf_handles, - jlongArray jcolumn_family_handles) { - if (jcolumn_family_handles != nullptr) { - const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); - - jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); - if (jcfh == nullptr) { - // exception thrown: OutOfMemoryError - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, - "Insufficient Memory for CF handle array."); - return false; - } - - for (jsize i = 0; i < len_cols; i++) { - auto* cf_handle = - reinterpret_cast(jcfh[i]); - cf_handles.push_back(cf_handle); - } - env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); - } - return true; -} - -/** - * @brief copy keys from JNI into vector of slices for Rocks API - * - * @param keys to instantiate - * @param jkeys - * @param jkey_offs - * @param jkey_lens - * @return true if the copy succeeds - * @return false if a JNI exception is raised - */ -inline bool keys_from_jkeys(JNIEnv* env, - std::vector& keys, - std::vector& keys_to_free, - jobjectArray jkeys, jintArray jkey_offs, - jintArray jkey_lens) { - jint* jkey_off = env->GetIntArrayElements(jkey_offs, nullptr); - if (jkey_off == nullptr) { - // exception thrown: OutOfMemoryError - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, "Insufficient Memory for key offset array."); - return false; - } - - jint* jkey_len = env->GetIntArrayElements(jkey_lens, nullptr); - if (jkey_len == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, "Insufficient Memory for key length array."); - return false; - } - - const jsize len_keys = env->GetArrayLength(jkeys); - for (jsize i = 0; i < len_keys; i++) { - jobject jkey = env->GetObjectArrayElement(jkeys, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - multi_get_helper_release_keys(keys_to_free); - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, - "Insufficient Memory for key object array."); - return false; - } - - jbyteArray jkey_ba = reinterpret_cast(jkey); - - const jint len_key = jkey_len[i]; - jbyte* key = new jbyte[len_key]; - env->GetByteArrayRegion(jkey_ba, jkey_off[i], len_key, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - env->DeleteLocalRef(jkey); - env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - multi_get_helper_release_keys(keys_to_free); - jclass exception_cls = - (env)->FindClass("java/lang/ArrayIndexOutOfBoundsException"); - (env)->ThrowNew(exception_cls, "Invalid byte array region index."); - return false; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), len_key); - keys.push_back(key_slice); - - env->DeleteLocalRef(jkey); - keys_to_free.push_back(key); - } - - // cleanup jkey_off and jken_len - env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - - return true; -} - -inline bool keys_from_bytebuffers(JNIEnv* env, - std::vector& keys, - jobjectArray jkeys, jintArray jkey_offs, - jintArray jkey_lens) { - jint* jkey_off = env->GetIntArrayElements(jkey_offs, nullptr); - if (jkey_off == nullptr) { - // exception thrown: OutOfMemoryError - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, "Insufficient Memory for key offset array."); - return false; - } - - jint* jkey_len = env->GetIntArrayElements(jkey_lens, nullptr); - if (jkey_len == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, "Insufficient Memory for key length array."); - return false; - } - - const jsize len_keys = env->GetArrayLength(jkeys); - for (jsize i = 0; i < len_keys; i++) { - jobject jkey = env->GetObjectArrayElement(jkeys, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - // cleanup jkey_off and jkey_len - env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - - return false; - } - char* key = reinterpret_cast(env->GetDirectBufferAddress(jkey)); - ROCKSDB_NAMESPACE::Slice key_slice(key + jkey_off[i], jkey_len[i]); - keys.push_back(key_slice); - - env->DeleteLocalRef(jkey); - } - - // cleanup jkey_off and jkey_len - env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); - env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); - - return true; -} - -/** - * cf multi get - * - * @return byte[][] of values or nullptr if an - * exception occurs - */ -jobjectArray multi_get_helper(JNIEnv* env, jobject, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::ReadOptions& rOpt, - jobjectArray jkeys, jintArray jkey_offs, - jintArray jkey_lens, - jlongArray jcolumn_family_handles) { - std::vector cf_handles; - if (!cf_handles_from_jcf_handles(env, cf_handles, jcolumn_family_handles)) { - return nullptr; - } - - std::vector keys; - std::vector keys_to_free; - if (!keys_from_jkeys(env, keys, keys_to_free, jkeys, jkey_offs, jkey_lens)) { - return nullptr; - } - - std::vector values; - std::vector s; - if (cf_handles.size() == 0) { - s = db->MultiGet(rOpt, keys, &values); - } else { - s = db->MultiGet(rOpt, cf_handles, keys, &values); - } - - // free up allocated byte arrays - multi_get_helper_release_keys(keys_to_free); - - // prepare the results - jobjectArray jresults = ROCKSDB_NAMESPACE::ByteJni::new2dByteArray( - env, static_cast(s.size())); - if (jresults == nullptr) { - // exception occurred - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, "Insufficient Memory for results."); - return nullptr; - } - - // add to the jresults - for (std::vector::size_type i = 0; i != s.size(); - i++) { - if (s[i].ok()) { - std::string* value = &values[i]; - const jsize jvalue_len = static_cast(value->size()); - jbyteArray jentry_value = env->NewByteArray(jvalue_len); - if (jentry_value == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetByteArrayRegion( - jentry_value, 0, static_cast(jvalue_len), - const_cast(reinterpret_cast(value->c_str()))); - if (env->ExceptionCheck()) { - // exception thrown: - // ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jentry_value); - return nullptr; - } - - env->SetObjectArrayElement(jresults, static_cast(i), jentry_value); - if (env->ExceptionCheck()) { - // exception thrown: - // ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jentry_value); - return nullptr; - } - - env->DeleteLocalRef(jentry_value); - } - } - - return jresults; -} - -/** - * cf multi get - * - * fill supplied native buffers, or raise JNI - * exception on a problem - */ - -/** - * @brief multi_get_helper_direct for fast-path multiget (io_uring) on Linux - * - * @param env - * @param db - * @param rOpt read options - * @param jcolumn_family_handles 0, 1, or n column family handles - * @param jkeys - * @param jkey_offsets - * @param jkey_lengths - * @param jvalues byte buffers to receive values - * @param jvalue_sizes returned actual sizes of data values for keys - * @param jstatuses returned java RocksDB status values for per key - */ -void multi_get_helper_direct(JNIEnv* env, jobject, ROCKSDB_NAMESPACE::DB* db, - const ROCKSDB_NAMESPACE::ReadOptions& rOpt, - jlongArray jcolumn_family_handles, - jobjectArray jkeys, jintArray jkey_offsets, - jintArray jkey_lengths, jobjectArray jvalues, - jintArray jvalue_sizes, jobjectArray jstatuses) { - const jsize num_keys = env->GetArrayLength(jkeys); - - std::vector keys; - if (!keys_from_bytebuffers(env, keys, jkeys, jkey_offsets, jkey_lengths)) { - return; - } - - std::vector values(num_keys); - - std::vector cf_handles; - if (!cf_handles_from_jcf_handles(env, cf_handles, jcolumn_family_handles)) { - return; - } - - std::vector s(num_keys); - if (cf_handles.size() == 0) { - // we can use the more efficient call here - auto cf_handle = db->DefaultColumnFamily(); - db->MultiGet(rOpt, cf_handle, num_keys, keys.data(), values.data(), - s.data()); - } else if (cf_handles.size() == 1) { - // we can use the more efficient call here - auto cf_handle = cf_handles[0]; - db->MultiGet(rOpt, cf_handle, num_keys, keys.data(), values.data(), - s.data()); - } else { - // multiple CFs version - db->MultiGet(rOpt, num_keys, cf_handles.data(), keys.data(), values.data(), - s.data()); - } - - // prepare the results - jobjectArray jresults = ROCKSDB_NAMESPACE::ByteJni::new2dByteArray( - env, static_cast(s.size())); - if (jresults == nullptr) { - // exception occurred - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, "Insufficient Memory for results."); - return; - } - - std::vector value_size; - for (int i = 0; i < num_keys; i++) { - auto jstatus = ROCKSDB_NAMESPACE::StatusJni::construct(env, s[i]); - if (jstatus == nullptr) { - // exception in context - return; - } - env->SetObjectArrayElement(jstatuses, i, jstatus); - - if (s[i].ok()) { - jobject jvalue_bytebuf = env->GetObjectArrayElement(jvalues, i); - if (env->ExceptionCheck()) { - // ArrayIndexOutOfBoundsException is thrown - return; - } - jlong jvalue_capacity = env->GetDirectBufferCapacity(jvalue_bytebuf); - if (jvalue_capacity == -1) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid value(s) argument (argument is not a valid direct " - "ByteBuffer)"); - return; - } - void* jvalue_address = env->GetDirectBufferAddress(jvalue_bytebuf); - if (jvalue_address == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid value(s) argument (argument is not a valid direct " - "ByteBuffer)"); - return; - } - - // record num returned, push back that number, which may be bigger then - // the ByteBuffer supplied. then copy as much as fits in the ByteBuffer. - value_size.push_back(static_cast(values[i].size())); - auto copy_bytes = - std::min(static_cast(values[i].size()), jvalue_capacity); - memcpy(jvalue_address, values[i].data(), copy_bytes); - } else { - // bad status for this - value_size.push_back(0); - } - } - - env->SetIntArrayRegion(jvalue_sizes, 0, num_keys, value_size.data()); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: multiGet - * Signature: (J[[B[I[I)[[B - */ -jobjectArray Java_org_rocksdb_RocksDB_multiGet__J_3_3B_3I_3I( - JNIEnv* env, jobject jdb, jlong jdb_handle, jobjectArray jkeys, - jintArray jkey_offs, jintArray jkey_lens) { - return multi_get_helper( - env, jdb, reinterpret_cast(jdb_handle), - ROCKSDB_NAMESPACE::ReadOptions(), jkeys, jkey_offs, jkey_lens, nullptr); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: multiGet - * Signature: (J[[B[I[I[J)[[B - */ -jobjectArray Java_org_rocksdb_RocksDB_multiGet__J_3_3B_3I_3I_3J( - JNIEnv* env, jobject jdb, jlong jdb_handle, jobjectArray jkeys, - jintArray jkey_offs, jintArray jkey_lens, - jlongArray jcolumn_family_handles) { - return multi_get_helper(env, jdb, - reinterpret_cast(jdb_handle), - ROCKSDB_NAMESPACE::ReadOptions(), jkeys, jkey_offs, - jkey_lens, jcolumn_family_handles); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: multiGet - * Signature: (JJ[[B[I[I)[[B - */ -jobjectArray Java_org_rocksdb_RocksDB_multiGet__JJ_3_3B_3I_3I( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, - jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens) { - return multi_get_helper( - env, jdb, reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), jkeys, - jkey_offs, jkey_lens, nullptr); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: multiGet - * Signature: (JJ[[B[I[I[J)[[B - */ -jobjectArray Java_org_rocksdb_RocksDB_multiGet__JJ_3_3B_3I_3I_3J( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, - jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens, - jlongArray jcolumn_family_handles) { - return multi_get_helper( - env, jdb, reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), jkeys, - jkey_offs, jkey_lens, jcolumn_family_handles); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: multiGet - * Signature: - * (JJ[J[Ljava/nio/ByteBuffer;[I[I[Ljava/nio/ByteBuffer;[I[Lorg/rocksdb/Status;)V - */ -void Java_org_rocksdb_RocksDB_multiGet__JJ_3J_3Ljava_nio_ByteBuffer_2_3I_3I_3Ljava_nio_ByteBuffer_2_3I_3Lorg_rocksdb_Status_2( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, - jlongArray jcolumn_family_handles, jobjectArray jkeys, - jintArray jkey_offsets, jintArray jkey_lengths, jobjectArray jvalues, - jintArray jvalues_sizes, jobjectArray jstatus_objects) { - return multi_get_helper_direct( - env, jdb, reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), - jcolumn_family_handles, jkeys, jkey_offsets, jkey_lengths, jvalues, - jvalues_sizes, jstatus_objects); -} -// private native void -// multiGet(final long dbHandle, final long rOptHandle, -// final long[] columnFamilyHandles, final ByteBuffer[] keysArray, -// final ByteBuffer[] valuesArray); - -////////////////////////////////////////////////////////////////////////////// -// ROCKSDB_NAMESPACE::DB::KeyMayExist -bool key_may_exist_helper(JNIEnv* env, jlong jdb_handle, jlong jcf_handle, - jlong jread_opts_handle, jbyteArray jkey, - jint jkey_offset, jint jkey_len, bool* has_exception, - std::string* value, bool* value_found) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - ROCKSDB_NAMESPACE::ReadOptions read_opts = - jread_opts_handle == 0 - ? ROCKSDB_NAMESPACE::ReadOptions() - : *(reinterpret_cast( - jread_opts_handle)); - - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_offset, jkey_len, key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] key; - *has_exception = true; - return false; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), jkey_len); - - const bool exists = - db->KeyMayExist(read_opts, cf_handle, key_slice, value, value_found); - - // cleanup - delete[] key; - - return exists; -} - -bool key_may_exist_direct_helper(JNIEnv* env, jlong jdb_handle, - jlong jcf_handle, jlong jread_opts_handle, - jobject jkey, jint jkey_offset, jint jkey_len, - bool* has_exception, std::string* value, - bool* value_found) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - ROCKSDB_NAMESPACE::ReadOptions read_opts = - jread_opts_handle == 0 - ? ROCKSDB_NAMESPACE::ReadOptions() - : *(reinterpret_cast( - jread_opts_handle)); - - char* key = reinterpret_cast(env->GetDirectBufferAddress(jkey)); - if (key == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid key argument (argument is not a valid direct ByteBuffer)"); - *has_exception = true; - return false; - } - if (env->GetDirectBufferCapacity(jkey) < (jkey_offset + jkey_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid key argument. Capacity is less than requested region (offset " - "+ length)."); - *has_exception = true; - return false; - } - - ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len); - - const bool exists = - db->KeyMayExist(read_opts, cf_handle, key_slice, value, value_found); - - return exists; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: keyMayExist - * Signature: (JJJ[BII)Z - */ -jboolean Java_org_rocksdb_RocksDB_keyMayExist( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlong jread_opts_handle, jbyteArray jkey, jint jkey_offset, jint jkey_len) { - bool has_exception = false; - std::string value; - bool value_found = false; - - const bool exists = key_may_exist_helper( - env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, - jkey_len, &has_exception, &value, &value_found); - - if (has_exception) { - // java exception already raised - return false; - } - - return static_cast(exists); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: keyMayExistDirect - * Signature: (JJJLjava/nio/ByteBuffer;II)Z - */ -jboolean Java_org_rocksdb_RocksDB_keyMayExistDirect( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlong jread_opts_handle, jobject jkey, jint jkey_offset, jint jkey_len) { - bool has_exception = false; - std::string value; - bool value_found = false; - - const bool exists = key_may_exist_direct_helper( - env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, - jkey_len, &has_exception, &value, &value_found); - if (has_exception) { - // java exception already raised - return false; - } - - return static_cast(exists); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: keyMayExistDirectFoundValue - * Signature: - * (JJJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)[J - */ -jintArray Java_org_rocksdb_RocksDB_keyMayExistDirectFoundValue( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlong jread_opts_handle, jobject jkey, jint jkey_offset, jint jkey_len, - jobject jval, jint jval_offset, jint jval_len) { - char* val_buffer = reinterpret_cast(env->GetDirectBufferAddress(jval)); - if (val_buffer == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid value argument (argument is not a valid direct ByteBuffer)"); - return nullptr; - } - - if (env->GetDirectBufferCapacity(jval) < (jval_offset + jval_len)) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, - "Invalid value argument. Capacity is less than requested region " - "(offset + length)."); - return nullptr; - } - - bool has_exception = false; - std::string cvalue; - bool value_found = false; - - const bool exists = key_may_exist_direct_helper( - env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, - jkey_len, &has_exception, &cvalue, &value_found); - - if (has_exception) { - // java exception already raised - return nullptr; - } - - const jint cvalue_len = static_cast(cvalue.size()); - const jint length = std::min(jval_len, cvalue_len); - memcpy(val_buffer + jval_offset, cvalue.c_str(), length); - - // keep consistent with java KeyMayExistEnum.values() - const int kNotExist = 0; - const int kExistsWithoutValue = 1; - const int kExistsWithValue = 2; - - // TODO fix return value/type - // exists/value_found/neither - // cvalue_len - jintArray jresult = env->NewIntArray(2); - const jint jexists = - exists ? (value_found ? kExistsWithValue : kExistsWithoutValue) - : kNotExist; - - env->SetIntArrayRegion(jresult, 0, 1, &jexists); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult); - return nullptr; - } - env->SetIntArrayRegion(jresult, 1, 1, &cvalue_len); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult); - return nullptr; - } - - return jresult; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: keyMayExistFoundValue - * Signature: (JJJ[BII)[[B - */ -jobjectArray Java_org_rocksdb_RocksDB_keyMayExistFoundValue( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlong jread_opts_handle, jbyteArray jkey, jint jkey_offset, jint jkey_len) { - bool has_exception = false; - std::string value; - bool value_found = false; - - const bool exists = key_may_exist_helper( - env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, - jkey_len, &has_exception, &value, &value_found); - - if (has_exception) { - // java exception already raised - return nullptr; - } - - jbyte result_flags[1]; - if (!exists) { - result_flags[0] = 0; - } else if (!value_found) { - result_flags[0] = 1; - } else { - // found - result_flags[0] = 2; - } - - jobjectArray jresults = ROCKSDB_NAMESPACE::ByteJni::new2dByteArray(env, 2); - if (jresults == nullptr) { - // exception occurred - return nullptr; - } - - // prepare the result flag - jbyteArray jresult_flags = env->NewByteArray(1); - if (jresult_flags == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion(jresult_flags, 0, 1, result_flags); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult_flags); - return nullptr; - } - - env->SetObjectArrayElement(jresults, 0, jresult_flags); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult_flags); - return nullptr; - } - - env->DeleteLocalRef(jresult_flags); - - if (result_flags[0] == 2) { - // set the value - const jsize jvalue_len = static_cast(value.size()); - jbyteArray jresult_value = env->NewByteArray(jvalue_len); - if (jresult_value == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion( - jresult_value, 0, jvalue_len, - const_cast(reinterpret_cast(value.data()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult_value); - return nullptr; - } - env->SetObjectArrayElement(jresults, 1, jresult_value); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult_value); - return nullptr; - } - - env->DeleteLocalRef(jresult_value); - } - - return jresults; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: iterator - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksDB_iterator__J(JNIEnv*, jobject, jlong db_handle) { - auto* db = reinterpret_cast(db_handle); - return rocksdb_iterator_helper(db, ROCKSDB_NAMESPACE::ReadOptions(), nullptr); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: iterator - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_RocksDB_iterator__JJ(JNIEnv*, jobject, jlong db_handle, - jlong jread_options_handle) { - auto* db = reinterpret_cast(db_handle); - auto& read_options = - *reinterpret_cast(jread_options_handle); - return rocksdb_iterator_helper(db, read_options, nullptr); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: iteratorCF - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_RocksDB_iteratorCF__JJ(JNIEnv*, jobject, jlong db_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(db_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - return rocksdb_iterator_helper(db, ROCKSDB_NAMESPACE::ReadOptions(), - cf_handle); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: iteratorCF - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_RocksDB_iteratorCF__JJJ(JNIEnv*, jobject, - jlong db_handle, - jlong jcf_handle, - jlong jread_options_handle) { - auto* db = reinterpret_cast(db_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto& read_options = - *reinterpret_cast(jread_options_handle); - return rocksdb_iterator_helper(db, read_options, cf_handle); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: iterators - * Signature: (J[JJ)[J - */ -jlongArray Java_org_rocksdb_RocksDB_iterators(JNIEnv* env, jobject, - jlong db_handle, - jlongArray jcolumn_family_handles, - jlong jread_options_handle) { - auto* db = reinterpret_cast(db_handle); - auto& read_options = - *reinterpret_cast(jread_options_handle); - - std::vector cf_handles; - if (jcolumn_family_handles != nullptr) { - const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); - jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); - if (jcfh == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - for (jsize i = 0; i < len_cols; i++) { - auto* cf_handle = - reinterpret_cast(jcfh[i]); - cf_handles.push_back(cf_handle); - } - - env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); - } - - std::vector iterators; - ROCKSDB_NAMESPACE::Status s = - db->NewIterators(read_options, cf_handles, &iterators); - if (s.ok()) { - jlongArray jLongArray = - env->NewLongArray(static_cast(iterators.size())); - if (jLongArray == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - for (std::vector::size_type i = 0; - i < iterators.size(); i++) { - env->SetLongArrayRegion( - jLongArray, static_cast(i), 1, - const_cast(reinterpret_cast(&iterators[i]))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jLongArray); - return nullptr; - } - } - - return jLongArray; - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } -} - -/* - * Method: getSnapshot - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksDB_getSnapshot(JNIEnv*, jobject, jlong db_handle) { - auto* db = reinterpret_cast(db_handle); - const ROCKSDB_NAMESPACE::Snapshot* snapshot = db->GetSnapshot(); - return GET_CPLUSPLUS_POINTER(snapshot); -} - -/* - * Method: releaseSnapshot - * Signature: (JJ)V - */ -void Java_org_rocksdb_RocksDB_releaseSnapshot(JNIEnv*, jobject, jlong db_handle, - jlong snapshot_handle) { - auto* db = reinterpret_cast(db_handle); - auto* snapshot = - reinterpret_cast(snapshot_handle); - db->ReleaseSnapshot(snapshot); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getProperty - * Signature: (JJLjava/lang/String;I)Ljava/lang/String; - */ -jstring Java_org_rocksdb_RocksDB_getProperty(JNIEnv* env, jobject, - jlong jdb_handle, jlong jcf_handle, - jstring jproperty, - jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if (property == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - ROCKSDB_NAMESPACE::Slice property_name(property, jproperty_len); - - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - std::string property_value; - bool retCode = db->GetProperty(cf_handle, property_name, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return env->NewStringUTF(property_value.c_str()); - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::NotFound()); - return nullptr; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getMapProperty - * Signature: (JJLjava/lang/String;I)Ljava/util/Map; - */ -jobject Java_org_rocksdb_RocksDB_getMapProperty(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle, - jstring jproperty, - jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if (property == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - ROCKSDB_NAMESPACE::Slice property_name(property, jproperty_len); - - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - std::map property_value; - bool retCode = db->GetMapProperty(cf_handle, property_name, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(env, &property_value); - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::NotFound()); - return nullptr; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getLongProperty - * Signature: (JJLjava/lang/String;I)J - */ -jlong Java_org_rocksdb_RocksDB_getLongProperty(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle, - jstring jproperty, - jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if (property == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - ROCKSDB_NAMESPACE::Slice property_name(property, jproperty_len); - - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - uint64_t property_value; - bool retCode = db->GetIntProperty(cf_handle, property_name, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return property_value; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::NotFound()); - return 0; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: resetStats - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_resetStats(JNIEnv*, jobject, jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - db->ResetStats(); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getAggregatedLongProperty - * Signature: (JLjava/lang/String;I)J - */ -jlong Java_org_rocksdb_RocksDB_getAggregatedLongProperty(JNIEnv* env, jobject, - jlong db_handle, - jstring jproperty, - jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if (property == nullptr) { - return 0; - } - ROCKSDB_NAMESPACE::Slice property_name(property, jproperty_len); - auto* db = reinterpret_cast(db_handle); - uint64_t property_value = 0; - bool retCode = db->GetAggregatedIntProperty(property_name, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return property_value; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::NotFound()); - return 0; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getApproximateSizes - * Signature: (JJ[JB)[J - */ -jlongArray Java_org_rocksdb_RocksDB_getApproximateSizes( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlongArray jrange_slice_handles, jbyte jinclude_flags) { - const jsize jlen = env->GetArrayLength(jrange_slice_handles); - const size_t range_count = jlen / 2; - - jlong* jranges = env->GetLongArrayElements(jrange_slice_handles, nullptr); - if (jranges == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - auto ranges = std::unique_ptr( - new ROCKSDB_NAMESPACE::Range[range_count]); - size_t range_offset = 0; - for (jsize i = 0; i < jlen; ++i) { - auto* start = reinterpret_cast(jranges[i]); - auto* limit = reinterpret_cast(jranges[++i]); - ranges.get()[range_offset++] = ROCKSDB_NAMESPACE::Range(*start, *limit); - } - - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - auto sizes = std::unique_ptr(new uint64_t[range_count]); - - ROCKSDB_NAMESPACE::DB::SizeApproximationFlags include_flags = - ROCKSDB_NAMESPACE::DB::SizeApproximationFlags::NONE; - if (jinclude_flags & 1) { - include_flags = - ROCKSDB_NAMESPACE::DB::SizeApproximationFlags::INCLUDE_MEMTABLES; - } - if (jinclude_flags & 2) { - include_flags = - (include_flags | - ROCKSDB_NAMESPACE::DB::SizeApproximationFlags::INCLUDE_FILES); - } - - db->GetApproximateSizes(cf_handle, ranges.get(), - static_cast(range_count), sizes.get(), - include_flags); - - // release LongArrayElements - env->ReleaseLongArrayElements(jrange_slice_handles, jranges, JNI_ABORT); - - // prepare results - auto results = std::unique_ptr(new jlong[range_count]); - for (size_t i = 0; i < range_count; ++i) { - results.get()[i] = static_cast(sizes.get()[i]); - } - - const jsize jrange_count = jlen / 2; - jlongArray jresults = env->NewLongArray(jrange_count); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetLongArrayRegion(jresults, 0, jrange_count, results.get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresults); - return nullptr; - } - - return jresults; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getApproximateMemTableStats - * Signature: (JJJJ)[J - */ -jlongArray Java_org_rocksdb_RocksDB_getApproximateMemTableStats( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlong jstartHandle, jlong jlimitHandle) { - auto* start = reinterpret_cast(jstartHandle); - auto* limit = reinterpret_cast(jlimitHandle); - const ROCKSDB_NAMESPACE::Range range(*start, *limit); - - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - uint64_t count = 0; - uint64_t sizes = 0; - db->GetApproximateMemTableStats(cf_handle, range, &count, &sizes); - - // prepare results - jlong results[2] = {static_cast(count), static_cast(sizes)}; - - jlongArray jsizes = env->NewLongArray(2); - if (jsizes == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetLongArrayRegion(jsizes, 0, 2, results); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jsizes); - return nullptr; - } - - return jsizes; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: compactRange - * Signature: (J[BI[BIJJ)V - */ -void Java_org_rocksdb_RocksDB_compactRange(JNIEnv* env, jobject, - jlong jdb_handle, jbyteArray jbegin, - jint jbegin_len, jbyteArray jend, - jint jend_len, - jlong jcompact_range_opts_handle, - jlong jcf_handle) { - jboolean has_exception = JNI_FALSE; - - std::string str_begin; - if (jbegin_len > 0) { - str_begin = ROCKSDB_NAMESPACE::JniUtil::byteString( - env, jbegin, jbegin_len, - [](const char* str, const size_t len) { return std::string(str, len); }, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - } - - std::string str_end; - if (jend_len > 0) { - str_end = ROCKSDB_NAMESPACE::JniUtil::byteString( - env, jend, jend_len, - [](const char* str, const size_t len) { return std::string(str, len); }, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - } - - ROCKSDB_NAMESPACE::CompactRangeOptions* compact_range_opts = nullptr; - if (jcompact_range_opts_handle == 0) { - // NOTE: we DO own the pointer! - compact_range_opts = new ROCKSDB_NAMESPACE::CompactRangeOptions(); - } else { - // NOTE: we do NOT own the pointer! - compact_range_opts = - reinterpret_cast( - jcompact_range_opts_handle); - } - - auto* db = reinterpret_cast(jdb_handle); - - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - ROCKSDB_NAMESPACE::Status s; - if (jbegin_len > 0 || jend_len > 0) { - const ROCKSDB_NAMESPACE::Slice begin(str_begin); - const ROCKSDB_NAMESPACE::Slice end(str_end); - s = db->CompactRange(*compact_range_opts, cf_handle, &begin, &end); - } else { - s = db->CompactRange(*compact_range_opts, cf_handle, nullptr, nullptr); - } - - if (jcompact_range_opts_handle == 0) { - delete compact_range_opts; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: setOptions - * Signature: (JJ[Ljava/lang/String;[Ljava/lang/String;)V - */ -void Java_org_rocksdb_RocksDB_setOptions(JNIEnv* env, jobject, jlong jdb_handle, - jlong jcf_handle, jobjectArray jkeys, - jobjectArray jvalues) { - const jsize len = env->GetArrayLength(jkeys); - assert(len == env->GetArrayLength(jvalues)); - - std::unordered_map options_map; - for (jsize i = 0; i < len; i++) { - jobject jobj_key = env->GetObjectArrayElement(jkeys, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return; - } - - jobject jobj_value = env->GetObjectArrayElement(jvalues, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jobj_key); - return; - } - - jboolean has_exception = JNI_FALSE; - std::string s_key = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, reinterpret_cast(jobj_key), &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - env->DeleteLocalRef(jobj_value); - env->DeleteLocalRef(jobj_key); - return; - } - - std::string s_value = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, reinterpret_cast(jobj_value), &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - env->DeleteLocalRef(jobj_value); - env->DeleteLocalRef(jobj_key); - return; - } - - options_map[s_key] = s_value; - - env->DeleteLocalRef(jobj_key); - env->DeleteLocalRef(jobj_value); - } - - auto* db = reinterpret_cast(jdb_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - if (cf_handle == nullptr) { - cf_handle = db->DefaultColumnFamily(); - } - auto s = db->SetOptions(cf_handle, options_map); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: setDBOptions - * Signature: (J[Ljava/lang/String;[Ljava/lang/String;)V - */ -void Java_org_rocksdb_RocksDB_setDBOptions(JNIEnv* env, jobject, - jlong jdb_handle, jobjectArray jkeys, - jobjectArray jvalues) { - const jsize len = env->GetArrayLength(jkeys); - assert(len == env->GetArrayLength(jvalues)); - - std::unordered_map options_map; - for (jsize i = 0; i < len; i++) { - jobject jobj_key = env->GetObjectArrayElement(jkeys, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return; - } - - jobject jobj_value = env->GetObjectArrayElement(jvalues, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jobj_key); - return; - } - - jboolean has_exception = JNI_FALSE; - std::string s_key = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, reinterpret_cast(jobj_key), &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - env->DeleteLocalRef(jobj_value); - env->DeleteLocalRef(jobj_key); - return; - } - - std::string s_value = ROCKSDB_NAMESPACE::JniUtil::copyStdString( - env, reinterpret_cast(jobj_value), &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - env->DeleteLocalRef(jobj_value); - env->DeleteLocalRef(jobj_key); - return; - } - - options_map[s_key] = s_value; - - env->DeleteLocalRef(jobj_key); - env->DeleteLocalRef(jobj_value); - } - - auto* db = reinterpret_cast(jdb_handle); - auto s = db->SetDBOptions(options_map); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getOptions - * Signature: (JJ)Ljava/lang/String; - */ -jstring Java_org_rocksdb_RocksDB_getOptions(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - auto options = db->GetOptions(cf_handle); - std::string options_as_string; - ROCKSDB_NAMESPACE::Status s = - GetStringFromColumnFamilyOptions(&options_as_string, options); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - return env->NewStringUTF(options_as_string.c_str()); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getDBOptions - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_RocksDB_getDBOptions(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - - auto options = db->GetDBOptions(); - std::string options_as_string; - ROCKSDB_NAMESPACE::Status s = - GetStringFromDBOptions(&options_as_string, options); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - return env->NewStringUTF(options_as_string.c_str()); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: compactFiles - * Signature: (JJJ[Ljava/lang/String;IIJ)[Ljava/lang/String; - */ -jobjectArray Java_org_rocksdb_RocksDB_compactFiles( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcompaction_opts_handle, - jlong jcf_handle, jobjectArray jinput_file_names, jint joutput_level, - jint joutput_path_id, jlong jcompaction_job_info_handle) { - jboolean has_exception = JNI_FALSE; - const std::vector input_file_names = - ROCKSDB_NAMESPACE::JniUtil::copyStrings(env, jinput_file_names, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return nullptr; - } - - auto* compaction_opts = - reinterpret_cast( - jcompaction_opts_handle); - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - - ROCKSDB_NAMESPACE::CompactionJobInfo* compaction_job_info = nullptr; - if (jcompaction_job_info_handle != 0) { - compaction_job_info = - reinterpret_cast( - jcompaction_job_info_handle); - } - - std::vector output_file_names; - auto s = db->CompactFiles(*compaction_opts, cf_handle, input_file_names, - static_cast(joutput_level), - static_cast(joutput_path_id), - &output_file_names, compaction_job_info); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - return ROCKSDB_NAMESPACE::JniUtil::toJavaStrings(env, &output_file_names); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: cancelAllBackgroundWork - * Signature: (JZ)V - */ -void Java_org_rocksdb_RocksDB_cancelAllBackgroundWork(JNIEnv*, jobject, - jlong jdb_handle, - jboolean jwait) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::CancelAllBackgroundWork(db, jwait); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: pauseBackgroundWork - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_pauseBackgroundWork(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->PauseBackgroundWork(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: continueBackgroundWork - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_continueBackgroundWork(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->ContinueBackgroundWork(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: enableAutoCompaction - * Signature: (J[J)V - */ -void Java_org_rocksdb_RocksDB_enableAutoCompaction(JNIEnv* env, jobject, - jlong jdb_handle, - jlongArray jcf_handles) { - auto* db = reinterpret_cast(jdb_handle); - jboolean has_exception = JNI_FALSE; - const std::vector cf_handles = - ROCKSDB_NAMESPACE::JniUtil::fromJPointers< - ROCKSDB_NAMESPACE::ColumnFamilyHandle>(env, jcf_handles, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - db->EnableAutoCompaction(cf_handles); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: numberLevels - * Signature: (JJ)I - */ -jint Java_org_rocksdb_RocksDB_numberLevels(JNIEnv*, jobject, jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - return static_cast(db->NumberLevels(cf_handle)); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: maxMemCompactionLevel - * Signature: (JJ)I - */ -jint Java_org_rocksdb_RocksDB_maxMemCompactionLevel(JNIEnv*, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - return static_cast(db->MaxMemCompactionLevel(cf_handle)); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: level0StopWriteTrigger - * Signature: (JJ)I - */ -jint Java_org_rocksdb_RocksDB_level0StopWriteTrigger(JNIEnv*, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - return static_cast(db->Level0StopWriteTrigger(cf_handle)); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getName - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_RocksDB_getName(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - std::string name = db->GetName(); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &name, false); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getEnv - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksDB_getEnv(JNIEnv*, jobject, jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - return GET_CPLUSPLUS_POINTER(db->GetEnv()); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: flush - * Signature: (JJ[J)V - */ -void Java_org_rocksdb_RocksDB_flush(JNIEnv* env, jobject, jlong jdb_handle, - jlong jflush_opts_handle, - jlongArray jcf_handles) { - auto* db = reinterpret_cast(jdb_handle); - auto* flush_opts = - reinterpret_cast(jflush_opts_handle); - std::vector cf_handles; - if (jcf_handles == nullptr) { - cf_handles.push_back(db->DefaultColumnFamily()); - } else { - jboolean has_exception = JNI_FALSE; - cf_handles = ROCKSDB_NAMESPACE::JniUtil::fromJPointers< - ROCKSDB_NAMESPACE::ColumnFamilyHandle>(env, jcf_handles, - &has_exception); - if (has_exception) { - // exception occurred - return; - } - } - auto s = db->Flush(*flush_opts, cf_handles); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: flushWal - * Signature: (JZ)V - */ -void Java_org_rocksdb_RocksDB_flushWal(JNIEnv* env, jobject, jlong jdb_handle, - jboolean jsync) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->FlushWAL(jsync == JNI_TRUE); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: syncWal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_syncWal(JNIEnv* env, jobject, jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->SyncWAL(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getLatestSequenceNumber - * Signature: (J)V - */ -jlong Java_org_rocksdb_RocksDB_getLatestSequenceNumber(JNIEnv*, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - return db->GetLatestSequenceNumber(); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: disableFileDeletions - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_disableFileDeletions(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::Status s = db->DisableFileDeletions(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: enableFileDeletions - * Signature: (JZ)V - */ -void Java_org_rocksdb_RocksDB_enableFileDeletions(JNIEnv* env, jobject, - jlong jdb_handle, - jboolean jforce) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::Status s = db->EnableFileDeletions(jforce); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getLiveFiles - * Signature: (JZ)[Ljava/lang/String; - */ -jobjectArray Java_org_rocksdb_RocksDB_getLiveFiles(JNIEnv* env, jobject, - jlong jdb_handle, - jboolean jflush_memtable) { - auto* db = reinterpret_cast(jdb_handle); - std::vector live_files; - uint64_t manifest_file_size = 0; - auto s = db->GetLiveFiles(live_files, &manifest_file_size, - jflush_memtable == JNI_TRUE); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - // append the manifest_file_size to the vector - // for passing back to java - live_files.push_back(std::to_string(manifest_file_size)); - - return ROCKSDB_NAMESPACE::JniUtil::toJavaStrings(env, &live_files); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getSortedWalFiles - * Signature: (J)[Lorg/rocksdb/LogFile; - */ -jobjectArray Java_org_rocksdb_RocksDB_getSortedWalFiles(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - std::vector> sorted_wal_files; - auto s = db->GetSortedWalFiles(sorted_wal_files); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - // convert to Java type - const jsize jlen = static_cast(sorted_wal_files.size()); - jobjectArray jsorted_wal_files = env->NewObjectArray( - jlen, ROCKSDB_NAMESPACE::LogFileJni::getJClass(env), nullptr); - if (jsorted_wal_files == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - jsize i = 0; - for (auto it = sorted_wal_files.begin(); it != sorted_wal_files.end(); ++it) { - jobject jlog_file = - ROCKSDB_NAMESPACE::LogFileJni::fromCppLogFile(env, it->get()); - if (jlog_file == nullptr) { - // exception occurred - env->DeleteLocalRef(jsorted_wal_files); - return nullptr; - } - - env->SetObjectArrayElement(jsorted_wal_files, i++, jlog_file); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(jlog_file); - env->DeleteLocalRef(jsorted_wal_files); - return nullptr; - } - - env->DeleteLocalRef(jlog_file); - } - - return jsorted_wal_files; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getUpdatesSince - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_RocksDB_getUpdatesSince(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jsequence_number) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::SequenceNumber sequence_number = - static_cast(jsequence_number); - std::unique_ptr iter; - ROCKSDB_NAMESPACE::Status s = db->GetUpdatesSince(sequence_number, &iter); - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(iter.release()); - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: deleteFile - * Signature: (JLjava/lang/String;)V - */ -void Java_org_rocksdb_RocksDB_deleteFile(JNIEnv* env, jobject, jlong jdb_handle, - jstring jname) { - auto* db = reinterpret_cast(jdb_handle); - jboolean has_exception = JNI_FALSE; - std::string name = - ROCKSDB_NAMESPACE::JniUtil::copyStdString(env, jname, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - db->DeleteFile(name); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getLiveFilesMetaData - * Signature: (J)[Lorg/rocksdb/LiveFileMetaData; - */ -jobjectArray Java_org_rocksdb_RocksDB_getLiveFilesMetaData(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - std::vector live_files_meta_data; - db->GetLiveFilesMetaData(&live_files_meta_data); - - // convert to Java type - const jsize jlen = static_cast(live_files_meta_data.size()); - jobjectArray jlive_files_meta_data = env->NewObjectArray( - jlen, ROCKSDB_NAMESPACE::LiveFileMetaDataJni::getJClass(env), nullptr); - if (jlive_files_meta_data == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - jsize i = 0; - for (auto it = live_files_meta_data.begin(); it != live_files_meta_data.end(); - ++it) { - jobject jlive_file_meta_data = - ROCKSDB_NAMESPACE::LiveFileMetaDataJni::fromCppLiveFileMetaData(env, - &(*it)); - if (jlive_file_meta_data == nullptr) { - // exception occurred - env->DeleteLocalRef(jlive_files_meta_data); - return nullptr; - } - - env->SetObjectArrayElement(jlive_files_meta_data, i++, - jlive_file_meta_data); - if (env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(jlive_file_meta_data); - env->DeleteLocalRef(jlive_files_meta_data); - return nullptr; - } - - env->DeleteLocalRef(jlive_file_meta_data); - } - - return jlive_files_meta_data; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getColumnFamilyMetaData - * Signature: (JJ)Lorg/rocksdb/ColumnFamilyMetaData; - */ -jobject Java_org_rocksdb_RocksDB_getColumnFamilyMetaData(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_metadata; - db->GetColumnFamilyMetaData(cf_handle, &cf_metadata); - return ROCKSDB_NAMESPACE::ColumnFamilyMetaDataJni:: - fromCppColumnFamilyMetaData(env, &cf_metadata); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: ingestExternalFile - * Signature: (JJ[Ljava/lang/String;IJ)V - */ -void Java_org_rocksdb_RocksDB_ingestExternalFile( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jobjectArray jfile_path_list, jint jfile_path_list_len, - jlong jingest_external_file_options_handle) { - jboolean has_exception = JNI_FALSE; - std::vector file_path_list = - ROCKSDB_NAMESPACE::JniUtil::copyStrings( - env, jfile_path_list, jfile_path_list_len, &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return; - } - - auto* db = reinterpret_cast(jdb_handle); - auto* column_family = - reinterpret_cast(jcf_handle); - auto* ifo = reinterpret_cast( - jingest_external_file_options_handle); - ROCKSDB_NAMESPACE::Status s = - db->IngestExternalFile(column_family, file_path_list, *ifo); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: verifyChecksum - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_verifyChecksum(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->VerifyChecksum(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getDefaultColumnFamily - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksDB_getDefaultColumnFamily(JNIEnv*, jobject, - jlong jdb_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto* cf_handle = db_handle->DefaultColumnFamily(); - return GET_CPLUSPLUS_POINTER(cf_handle); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getPropertiesOfAllTables - * Signature: (JJ)Ljava/util/Map; - */ -jobject Java_org_rocksdb_RocksDB_getPropertiesOfAllTables(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - ROCKSDB_NAMESPACE::TablePropertiesCollection table_properties_collection; - auto s = - db->GetPropertiesOfAllTables(cf_handle, &table_properties_collection); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } - - // convert to Java type - jobject jhash_map = ROCKSDB_NAMESPACE::HashMapJni::construct( - env, static_cast(table_properties_collection.size())); - if (jhash_map == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const std::string, - const std::shared_ptr, jobject, - jobject> - fn_map_kv = - [env](const std::pair>& - kv) { - jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString( - env, &(kv.first), false); - if (env->ExceptionCheck()) { - // an error occurred - return std::unique_ptr>(nullptr); - } - - jobject jtable_properties = - ROCKSDB_NAMESPACE::TablePropertiesJni::fromCppTableProperties( - env, *(kv.second.get())); - if (jtable_properties == nullptr) { - // an error occurred - env->DeleteLocalRef(jkey); - return std::unique_ptr>(nullptr); - } - - return std::unique_ptr>( - new std::pair( - static_cast(jkey), - static_cast(jtable_properties))); - }; - - if (!ROCKSDB_NAMESPACE::HashMapJni::putAll( - env, jhash_map, table_properties_collection.begin(), - table_properties_collection.end(), fn_map_kv)) { - // exception occurred - return nullptr; - } - - return jhash_map; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: getPropertiesOfTablesInRange - * Signature: (JJ[J)Ljava/util/Map; - */ -jobject Java_org_rocksdb_RocksDB_getPropertiesOfTablesInRange( - JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlongArray jrange_slice_handles) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - const jsize jlen = env->GetArrayLength(jrange_slice_handles); - jlong* jrange_slice_handle = - env->GetLongArrayElements(jrange_slice_handles, nullptr); - if (jrange_slice_handle == nullptr) { - // exception occurred - return nullptr; - } - - const size_t ranges_len = static_cast(jlen / 2); - auto ranges = std::unique_ptr( - new ROCKSDB_NAMESPACE::Range[ranges_len]); - for (jsize i = 0, j = 0; i < jlen; ++i) { - auto* start = - reinterpret_cast(jrange_slice_handle[i]); - auto* limit = - reinterpret_cast(jrange_slice_handle[++i]); - ranges[j++] = ROCKSDB_NAMESPACE::Range(*start, *limit); - } - - ROCKSDB_NAMESPACE::TablePropertiesCollection table_properties_collection; - auto s = db->GetPropertiesOfTablesInRange(cf_handle, ranges.get(), ranges_len, - &table_properties_collection); - if (!s.ok()) { - // error occurred - env->ReleaseLongArrayElements(jrange_slice_handles, jrange_slice_handle, - JNI_ABORT); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - // cleanup - env->ReleaseLongArrayElements(jrange_slice_handles, jrange_slice_handle, - JNI_ABORT); - - return jrange_slice_handles; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: suggestCompactRange - * Signature: (JJ)[J - */ -jlongArray Java_org_rocksdb_RocksDB_suggestCompactRange(JNIEnv* env, jobject, - jlong jdb_handle, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - auto* begin = new ROCKSDB_NAMESPACE::Slice(); - auto* end = new ROCKSDB_NAMESPACE::Slice(); - auto s = db->SuggestCompactRange(cf_handle, begin, end); - if (!s.ok()) { - // error occurred - delete begin; - delete end; - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } - - jlongArray jslice_handles = env->NewLongArray(2); - if (jslice_handles == nullptr) { - // exception thrown: OutOfMemoryError - delete begin; - delete end; - return nullptr; - } - - jlong slice_handles[2]; - slice_handles[0] = GET_CPLUSPLUS_POINTER(begin); - slice_handles[1] = GET_CPLUSPLUS_POINTER(end); - env->SetLongArrayRegion(jslice_handles, 0, 2, slice_handles); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete begin; - delete end; - env->DeleteLocalRef(jslice_handles); - return nullptr; - } - - return jslice_handles; -} - -/* - * Class: org_rocksdb_RocksDB - * Method: promoteL0 - * Signature: (JJI)V - */ -void Java_org_rocksdb_RocksDB_promoteL0(JNIEnv*, jobject, jlong jdb_handle, - jlong jcf_handle, jint jtarget_level) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; - if (jcf_handle == 0) { - cf_handle = db->DefaultColumnFamily(); - } else { - cf_handle = - reinterpret_cast(jcf_handle); - } - db->PromoteL0(cf_handle, static_cast(jtarget_level)); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: startTrace - * Signature: (JJJ)V - */ -void Java_org_rocksdb_RocksDB_startTrace( - JNIEnv* env, jobject, jlong jdb_handle, jlong jmax_trace_file_size, - jlong jtrace_writer_jnicallback_handle) { - auto* db = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::TraceOptions trace_options; - trace_options.max_trace_file_size = - static_cast(jmax_trace_file_size); - // transfer ownership of trace writer from Java to C++ - auto trace_writer = - std::unique_ptr( - reinterpret_cast( - jtrace_writer_jnicallback_handle)); - auto s = db->StartTrace(trace_options, std::move(trace_writer)); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: endTrace - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_endTrace(JNIEnv* env, jobject, jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->EndTrace(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: tryCatchUpWithPrimary - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_tryCatchUpWithPrimary(JNIEnv* env, jobject, - jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto s = db->TryCatchUpWithPrimary(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: destroyDB - * Signature: (Ljava/lang/String;J)V - */ -void Java_org_rocksdb_RocksDB_destroyDB(JNIEnv* env, jclass, jstring jdb_path, - jlong joptions_handle) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - auto* options = - reinterpret_cast(joptions_handle); - if (options == nullptr) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument("Invalid Options.")); - } - - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::DestroyDB(db_path, *options); - env->ReleaseStringUTFChars(jdb_path, db_path); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -bool get_slice_helper(JNIEnv* env, jobjectArray ranges, jsize index, - std::unique_ptr& slice, - std::vector>& ranges_to_free) { - jobject jArray = env->GetObjectArrayElement(ranges, index); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return false; - } - - if (jArray == nullptr) { - return true; - } - - jbyteArray jba = reinterpret_cast(jArray); - jsize len_ba = env->GetArrayLength(jba); - ranges_to_free.push_back(std::unique_ptr(new jbyte[len_ba])); - env->GetByteArrayRegion(jba, 0, len_ba, ranges_to_free.back().get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jArray); - return false; - } - env->DeleteLocalRef(jArray); - slice.reset(new ROCKSDB_NAMESPACE::Slice( - reinterpret_cast(ranges_to_free.back().get()), len_ba)); - return true; -} -/* - * Class: org_rocksdb_RocksDB - * Method: deleteFilesInRanges - * Signature: (JJLjava/util/List;Z)V - */ -void Java_org_rocksdb_RocksDB_deleteFilesInRanges(JNIEnv* env, jobject /*jdb*/, - jlong jdb_handle, - jlong jcf_handle, - jobjectArray ranges, - jboolean include_end) { - jsize length = env->GetArrayLength(ranges); - - std::vector rangesVector; - std::vector> slices; - std::vector> ranges_to_free; - for (jsize i = 0; (i + 1) < length; i += 2) { - slices.push_back(std::unique_ptr()); - if (!get_slice_helper(env, ranges, i, slices.back(), ranges_to_free)) { - // exception thrown - return; - } - - slices.push_back(std::unique_ptr()); - if (!get_slice_helper(env, ranges, i + 1, slices.back(), ranges_to_free)) { - // exception thrown - return; - } - - rangesVector.push_back(ROCKSDB_NAMESPACE::RangePtr( - slices[slices.size() - 2].get(), slices[slices.size() - 1].get())); - } - - auto* db = reinterpret_cast(jdb_handle); - auto* column_family = - reinterpret_cast(jcf_handle); - - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::DeleteFilesInRanges( - db, column_family == nullptr ? db->DefaultColumnFamily() : column_family, - rangesVector.data(), rangesVector.size(), include_end); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: version - * Signature: ()I - */ -jint Java_org_rocksdb_RocksDB_version(JNIEnv*, jclass) { - uint32_t encodedVersion = (ROCKSDB_MAJOR & 0xff) << 16; - encodedVersion |= (ROCKSDB_MINOR & 0xff) << 8; - encodedVersion |= (ROCKSDB_PATCH & 0xff); - return static_cast(encodedVersion); -} diff --git a/java/rocksjni/slice.cc b/java/rocksjni/slice.cc deleted file mode 100644 index 63c6b1b9f..000000000 --- a/java/rocksjni/slice.cc +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Slice. - -#include "rocksdb/slice.h" - -#include -#include -#include - -#include - -#include "include/org_rocksdb_AbstractSlice.h" -#include "include/org_rocksdb_DirectSlice.h" -#include "include/org_rocksdb_Slice.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -// - -/* - * Class: org_rocksdb_Slice - * Method: createNewSlice0 - * Signature: ([BI)J - */ -jlong Java_org_rocksdb_Slice_createNewSlice0(JNIEnv* env, jclass /*jcls*/, - jbyteArray data, jint offset) { - const jsize dataSize = env->GetArrayLength(data); - const int len = dataSize - offset; - - // NOTE: buf will be deleted in the Java_org_rocksdb_Slice_disposeInternalBuf - // method - jbyte* buf = new jbyte[len]; - env->GetByteArrayRegion(data, offset, len, buf); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return 0; - } - - const auto* slice = new ROCKSDB_NAMESPACE::Slice((const char*)buf, len); - return GET_CPLUSPLUS_POINTER(slice); -} - -/* - * Class: org_rocksdb_Slice - * Method: createNewSlice1 - * Signature: ([B)J - */ -jlong Java_org_rocksdb_Slice_createNewSlice1(JNIEnv* env, jclass /*jcls*/, - jbyteArray data) { - jbyte* ptrData = env->GetByteArrayElements(data, nullptr); - if (ptrData == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - const int len = env->GetArrayLength(data) + 1; - - // NOTE: buf will be deleted in the Java_org_rocksdb_Slice_disposeInternalBuf - // method - char* buf = new char[len]; - memcpy(buf, ptrData, len - 1); - buf[len - 1] = '\0'; - - const auto* slice = new ROCKSDB_NAMESPACE::Slice(buf, len - 1); - - env->ReleaseByteArrayElements(data, ptrData, JNI_ABORT); - - return GET_CPLUSPLUS_POINTER(slice); -} - -/* - * Class: org_rocksdb_Slice - * Method: data0 - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_Slice_data0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - const auto* slice = reinterpret_cast(handle); - const jsize len = static_cast(slice->size()); - const jbyteArray data = env->NewByteArray(len); - if (data == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetByteArrayRegion( - data, 0, len, - const_cast(reinterpret_cast(slice->data()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(data); - return nullptr; - } - - return data; -} - -/* - * Class: org_rocksdb_Slice - * Method: clear0 - * Signature: (JZJ)V - */ -void Java_org_rocksdb_Slice_clear0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle, jboolean shouldRelease, - jlong internalBufferOffset) { - auto* slice = reinterpret_cast(handle); - if (shouldRelease == JNI_TRUE) { - const char* buf = slice->data_ - internalBufferOffset; - delete[] buf; - } - slice->clear(); -} - -/* - * Class: org_rocksdb_Slice - * Method: removePrefix0 - * Signature: (JI)V - */ -void Java_org_rocksdb_Slice_removePrefix0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle, jint length) { - auto* slice = reinterpret_cast(handle); - slice->remove_prefix(length); -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: setLength0 - * Signature: (JI)V - */ -void Java_org_rocksdb_DirectSlice_setLength0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle, jint length) { - auto* slice = reinterpret_cast(handle); - slice->size_ = length; -} - -/* - * Class: org_rocksdb_Slice - * Method: disposeInternalBuf - * Signature: (JJ)V - */ -void Java_org_rocksdb_Slice_disposeInternalBuf(JNIEnv* /*env*/, - jobject /*jobj*/, jlong handle, - jlong internalBufferOffset) { - const auto* slice = reinterpret_cast(handle); - const char* buf = slice->data_ - internalBufferOffset; - delete[] buf; -} - -// - -// (data_addr); - const auto* slice = new ROCKSDB_NAMESPACE::Slice(ptrData, length); - return GET_CPLUSPLUS_POINTER(slice); -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: createNewDirectSlice1 - * Signature: (Ljava/nio/ByteBuffer;)J - */ -jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice1(JNIEnv* env, - jclass /*jcls*/, - jobject data) { - void* data_addr = env->GetDirectBufferAddress(data); - if (data_addr == nullptr) { - // error: memory region is undefined, given object is not a direct - // java.nio.Buffer, or JNI access to direct buffers is not supported by JVM - ROCKSDB_NAMESPACE::IllegalArgumentExceptionJni::ThrowNew( - env, ROCKSDB_NAMESPACE::Status::InvalidArgument( - "Could not access DirectBuffer")); - return 0; - } - - const auto* ptrData = reinterpret_cast(data_addr); - const auto* slice = new ROCKSDB_NAMESPACE::Slice(ptrData); - return GET_CPLUSPLUS_POINTER(slice); -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: data0 - * Signature: (J)Ljava/lang/Object; - */ -jobject Java_org_rocksdb_DirectSlice_data0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - const auto* slice = reinterpret_cast(handle); - return env->NewDirectByteBuffer(const_cast(slice->data()), - slice->size()); -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: get0 - * Signature: (JI)B - */ -jbyte Java_org_rocksdb_DirectSlice_get0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle, jint offset) { - const auto* slice = reinterpret_cast(handle); - return (*slice)[offset]; -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: clear0 - * Signature: (JZJ)V - */ -void Java_org_rocksdb_DirectSlice_clear0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle, jboolean shouldRelease, - jlong internalBufferOffset) { - auto* slice = reinterpret_cast(handle); - if (shouldRelease == JNI_TRUE) { - const char* buf = slice->data_ - internalBufferOffset; - delete[] buf; - } - slice->clear(); -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: removePrefix0 - * Signature: (JI)V - */ -void Java_org_rocksdb_DirectSlice_removePrefix0(JNIEnv* /*env*/, - jobject /*jobj*/, jlong handle, - jint length) { - auto* slice = reinterpret_cast(handle); - slice->remove_prefix(length); -} - -/* - * Class: org_rocksdb_DirectSlice - * Method: disposeInternalBuf - * Signature: (JJ)V - */ -void Java_org_rocksdb_DirectSlice_disposeInternalBuf( - JNIEnv* /*env*/, jobject /*jobj*/, jlong handle, - jlong internalBufferOffset) { - const auto* slice = reinterpret_cast(handle); - const char* buf = slice->data_ - internalBufferOffset; - delete[] buf; -} - -// diff --git a/java/rocksjni/snapshot.cc b/java/rocksjni/snapshot.cc deleted file mode 100644 index 2a1265a58..000000000 --- a/java/rocksjni/snapshot.cc +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++. - -#include -#include -#include - -#include "include/org_rocksdb_Snapshot.h" -#include "rocksdb/db.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_Snapshot - * Method: getSequenceNumber - * Signature: (J)J - */ -jlong Java_org_rocksdb_Snapshot_getSequenceNumber(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jsnapshot_handle) { - auto* snapshot = - reinterpret_cast(jsnapshot_handle); - return snapshot->GetSequenceNumber(); -} diff --git a/java/rocksjni/sst_file_manager.cc b/java/rocksjni/sst_file_manager.cc deleted file mode 100644 index c51436819..000000000 --- a/java/rocksjni/sst_file_manager.cc +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::SstFileManager methods -// from Java side. - -#include "rocksdb/sst_file_manager.h" - -#include - -#include - -#include "include/org_rocksdb_SstFileManager.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_SstFileManager - * Method: newSstFileManager - * Signature: (JJJDJ)J - */ -jlong Java_org_rocksdb_SstFileManager_newSstFileManager( - JNIEnv* jnienv, jclass /*jcls*/, jlong jenv_handle, jlong jlogger_handle, - jlong jrate_bytes, jdouble jmax_trash_db_ratio, - jlong jmax_delete_chunk_bytes) { - auto* env = reinterpret_cast(jenv_handle); - ROCKSDB_NAMESPACE::Status s; - ROCKSDB_NAMESPACE::SstFileManager* sst_file_manager = nullptr; - - if (jlogger_handle != 0) { - auto* sptr_logger = - reinterpret_cast*>( - jlogger_handle); - sst_file_manager = ROCKSDB_NAMESPACE::NewSstFileManager( - env, *sptr_logger, "", jrate_bytes, true, &s, jmax_trash_db_ratio, - jmax_delete_chunk_bytes); - } else { - sst_file_manager = ROCKSDB_NAMESPACE::NewSstFileManager( - env, nullptr, "", jrate_bytes, true, &s, jmax_trash_db_ratio, - jmax_delete_chunk_bytes); - } - - if (!s.ok()) { - if (sst_file_manager != nullptr) { - delete sst_file_manager; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(jnienv, s); - } - auto* sptr_sst_file_manager = - new std::shared_ptr(sst_file_manager); - - return GET_CPLUSPLUS_POINTER(sptr_sst_file_manager); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: setMaxAllowedSpaceUsage - * Signature: (JJ)V - */ -void Java_org_rocksdb_SstFileManager_setMaxAllowedSpaceUsage( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jmax_allowed_space) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - sptr_sst_file_manager->get()->SetMaxAllowedSpaceUsage(jmax_allowed_space); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: setCompactionBufferSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_SstFileManager_setCompactionBufferSize( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jcompaction_buffer_size) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - sptr_sst_file_manager->get()->SetCompactionBufferSize( - jcompaction_buffer_size); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: isMaxAllowedSpaceReached - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_SstFileManager_isMaxAllowedSpaceReached( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - return sptr_sst_file_manager->get()->IsMaxAllowedSpaceReached(); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: isMaxAllowedSpaceReachedIncludingCompactions - * Signature: (J)Z - */ -jboolean -Java_org_rocksdb_SstFileManager_isMaxAllowedSpaceReachedIncludingCompactions( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - return sptr_sst_file_manager->get() - ->IsMaxAllowedSpaceReachedIncludingCompactions(); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: getTotalSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_SstFileManager_getTotalSize(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - return sptr_sst_file_manager->get()->GetTotalSize(); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: getTrackedFiles - * Signature: (J)Ljava/util/Map; - */ -jobject Java_org_rocksdb_SstFileManager_getTrackedFiles(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - auto tracked_files = sptr_sst_file_manager->get()->GetTrackedFiles(); - - // TODO(AR) could refactor to share code with - // ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(env, tracked_files); - - const jobject jtracked_files = ROCKSDB_NAMESPACE::HashMapJni::construct( - env, static_cast(tracked_files.size())); - if (jtracked_files == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV - fn_map_kv = - [env](const std::pair& pair) { - const jstring jtracked_file_path = - env->NewStringUTF(pair.first.c_str()); - if (jtracked_file_path == nullptr) { - // an error occurred - return std::unique_ptr>(nullptr); - } - const jobject jtracked_file_size = - ROCKSDB_NAMESPACE::LongJni::valueOf(env, pair.second); - if (jtracked_file_size == nullptr) { - // an error occurred - return std::unique_ptr>(nullptr); - } - return std::unique_ptr>( - new std::pair(jtracked_file_path, - jtracked_file_size)); - }; - - if (!ROCKSDB_NAMESPACE::HashMapJni::putAll(env, jtracked_files, - tracked_files.begin(), - tracked_files.end(), fn_map_kv)) { - // exception occcurred - return nullptr; - } - - return jtracked_files; -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: getDeleteRateBytesPerSecond - * Signature: (J)J - */ -jlong Java_org_rocksdb_SstFileManager_getDeleteRateBytesPerSecond( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - return sptr_sst_file_manager->get()->GetDeleteRateBytesPerSecond(); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: setDeleteRateBytesPerSecond - * Signature: (JJ)V - */ -void Java_org_rocksdb_SstFileManager_setDeleteRateBytesPerSecond( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jlong jdelete_rate) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - sptr_sst_file_manager->get()->SetDeleteRateBytesPerSecond(jdelete_rate); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: getMaxTrashDBRatio - * Signature: (J)D - */ -jdouble Java_org_rocksdb_SstFileManager_getMaxTrashDBRatio(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - return sptr_sst_file_manager->get()->GetMaxTrashDBRatio(); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: setMaxTrashDBRatio - * Signature: (JD)V - */ -void Java_org_rocksdb_SstFileManager_setMaxTrashDBRatio(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jdouble jratio) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - sptr_sst_file_manager->get()->SetMaxTrashDBRatio(jratio); -} - -/* - * Class: org_rocksdb_SstFileManager - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileManager_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* sptr_sst_file_manager = - reinterpret_cast*>( - jhandle); - delete sptr_sst_file_manager; -} diff --git a/java/rocksjni/sst_file_reader_iterator.cc b/java/rocksjni/sst_file_reader_iterator.cc deleted file mode 100644 index 68fa4c37c..000000000 --- a/java/rocksjni/sst_file_reader_iterator.cc +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::Iterator methods from Java side. - -#include -#include -#include - -#include "include/org_rocksdb_SstFileReaderIterator.h" -#include "rocksdb/iterator.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - assert(it != nullptr); - delete it; -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: isValid0 - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_SstFileReaderIterator_isValid0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast(handle)->Valid(); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekToFirst0 - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekToFirst0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->SeekToFirst(); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekToLast0 - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekToLast0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->SeekToLast(); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: next0 - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_next0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Next(); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: prev0 - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_prev0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Prev(); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: seek0 - * Signature: (J[BI)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seek0(JNIEnv* env, jobject /*jobj*/, - jlong handle, - jbyteArray jtarget, - jint jtarget_len) { - jbyte* target = env->GetByteArrayElements(jtarget, nullptr); - if (target == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast(target), - jtarget_len); - - auto* it = reinterpret_cast(handle); - it->Seek(target_slice); - - env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekForPrev0 - * Signature: (J[BI)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekForPrev0(JNIEnv* env, - jobject /*jobj*/, - jlong handle, - jbyteArray jtarget, - jint jtarget_len) { - jbyte* target = env->GetByteArrayElements(jtarget, nullptr); - if (target == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast(target), - jtarget_len); - - auto* it = reinterpret_cast(handle); - it->SeekForPrev(target_slice); - - env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: status0 - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_status0(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Status s = it->status(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: key0 - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_SstFileReaderIterator_key0(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice key_slice = it->key(); - - jbyteArray jkey = env->NewByteArray(static_cast(key_slice.size())); - if (jkey == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion( - jkey, 0, static_cast(key_slice.size()), - const_cast(reinterpret_cast(key_slice.data()))); - return jkey; -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: value0 - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_SstFileReaderIterator_value0(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice value_slice = it->value(); - - jbyteArray jkeyValue = - env->NewByteArray(static_cast(value_slice.size())); - if (jkeyValue == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion( - jkeyValue, 0, static_cast(value_slice.size()), - const_cast(reinterpret_cast(value_slice.data()))); - return jkeyValue; -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: keyDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)I - */ -jint Java_org_rocksdb_SstFileReaderIterator_keyDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice key_slice = it->key(); - return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, key_slice, jtarget, - jtarget_off, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_SstFileReaderIterator - * Method: keyByteArray0 - * Signature: (J[BII)I - */ -jint Java_org_rocksdb_SstFileReaderIterator_keyByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jkey, jint jkey_off, - jint jkey_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice key_slice = it->key(); - auto slice_size = key_slice.size(); - jsize copy_size = std::min(static_cast(slice_size), - static_cast(jkey_len)); - env->SetByteArrayRegion( - jkey, jkey_off, copy_size, - const_cast(reinterpret_cast(key_slice.data()))); - - return static_cast(slice_size); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: valueDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)I - */ -jint Java_org_rocksdb_SstFileReaderIterator_valueDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice value_slice = it->value(); - return ROCKSDB_NAMESPACE::JniUtil::copyToDirect(env, value_slice, jtarget, - jtarget_off, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_SstFileReaderIterator - * Method: valueByteArray0 - * Signature: (J[BII)I - */ -jint Java_org_rocksdb_SstFileReaderIterator_valueByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jvalue_target, - jint jvalue_off, jint jvalue_len) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Slice value_slice = it->value(); - auto slice_size = value_slice.size(); - jsize copy_size = std::min(static_cast(slice_size), - static_cast(jvalue_len)); - env->SetByteArrayRegion( - jvalue_target, jvalue_off, copy_size, - const_cast(reinterpret_cast(value_slice.data()))); - - return static_cast(slice_size); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->Seek(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(seek, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekForPrevDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekForPrevDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seekPrev = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->SeekForPrev(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(seekPrev, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekByteArray0 - * Signature: (J[BII)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jtarget, - jint jtarget_off, jint jtarget_len) { - const std::unique_ptr target(new char[jtarget_len]); - if (target == nullptr) { - jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError"); - env->ThrowNew(oom_class, - "Memory allocation failed in RocksDB JNI function"); - return; - } - env->GetByteArrayRegion(jtarget, jtarget_off, jtarget_len, - reinterpret_cast(target.get())); - - ROCKSDB_NAMESPACE::Slice target_slice(target.get(), jtarget_len); - - auto* it = reinterpret_cast(handle); - it->Seek(target_slice); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_SstFileReaderIterator - * Method: seekForPrevByteArray0 - * Signature: (J[BII)V - */ -void Java_org_rocksdb_SstFileReaderIterator_seekForPrevByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jtarget, - jint jtarget_off, jint jtarget_len) { - const std::unique_ptr target(new char[jtarget_len]); - if (target == nullptr) { - jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError"); - env->ThrowNew(oom_class, - "Memory allocation failed in RocksDB JNI function"); - return; - } - env->GetByteArrayRegion(jtarget, jtarget_off, jtarget_len, - reinterpret_cast(target.get())); - - ROCKSDB_NAMESPACE::Slice target_slice(target.get(), jtarget_len); - - auto* it = reinterpret_cast(handle); - it->SeekForPrev(target_slice); -} - -/* - * Class: org_rocksdb_SstFileReaderIterator - * Method: refresh0 - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReaderIterator_refresh0(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Status s = it->Refresh(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} diff --git a/java/rocksjni/sst_file_readerjni.cc b/java/rocksjni/sst_file_readerjni.cc deleted file mode 100644 index 7ef711842..000000000 --- a/java/rocksjni/sst_file_readerjni.cc +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::SstFileReader methods -// from Java side. - -#include - -#include - -#include "include/org_rocksdb_SstFileReader.h" -#include "rocksdb/comparator.h" -#include "rocksdb/env.h" -#include "rocksdb/options.h" -#include "rocksdb/sst_file_reader.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_SstFileReader - * Method: newSstFileReader - * Signature: (J)J - */ -jlong Java_org_rocksdb_SstFileReader_newSstFileReader(JNIEnv * /*env*/, - jclass /*jcls*/, - jlong joptions) { - auto *options = - reinterpret_cast(joptions); - ROCKSDB_NAMESPACE::SstFileReader *sst_file_reader = - new ROCKSDB_NAMESPACE::SstFileReader(*options); - return GET_CPLUSPLUS_POINTER(sst_file_reader); -} - -/* - * Class: org_rocksdb_SstFileReader - * Method: open - * Signature: (JLjava/lang/String;)V - */ -void Java_org_rocksdb_SstFileReader_open(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, jstring jfile_path) { - const char *file_path = env->GetStringUTFChars(jfile_path, nullptr); - if (file_path == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Open( - file_path); - env->ReleaseStringUTFChars(jfile_path, file_path); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileReader - * Method: newIterator - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_SstFileReader_newIterator(JNIEnv * /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jread_options_handle) { - auto *sst_file_reader = - reinterpret_cast(jhandle); - auto *read_options = - reinterpret_cast(jread_options_handle); - return GET_CPLUSPLUS_POINTER(sst_file_reader->NewIterator(*read_options)); -} - -/* - * Class: org_rocksdb_SstFileReader - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReader_disposeInternal(JNIEnv * /*env*/, - jobject /*jobj*/, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} - -/* - * Class: org_rocksdb_SstFileReader - * Method: verifyChecksum - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileReader_verifyChecksum(JNIEnv *env, - jobject /*jobj*/, - jlong jhandle) { - auto *sst_file_reader = - reinterpret_cast(jhandle); - auto s = sst_file_reader->VerifyChecksum(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileReader - * Method: getTableProperties - * Signature: (J)J - */ -jobject Java_org_rocksdb_SstFileReader_getTableProperties(JNIEnv *env, - jobject /*jobj*/, - jlong jhandle) { - auto *sst_file_reader = - reinterpret_cast(jhandle); - std::shared_ptr tp = - sst_file_reader->GetTableProperties(); - jobject jtable_properties = - ROCKSDB_NAMESPACE::TablePropertiesJni::fromCppTableProperties( - env, *(tp.get())); - return jtable_properties; -} diff --git a/java/rocksjni/sst_file_writerjni.cc b/java/rocksjni/sst_file_writerjni.cc deleted file mode 100644 index 1898c3cfc..000000000 --- a/java/rocksjni/sst_file_writerjni.cc +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::SstFileWriter methods -// from Java side. - -#include - -#include - -#include "include/org_rocksdb_SstFileWriter.h" -#include "rocksdb/comparator.h" -#include "rocksdb/env.h" -#include "rocksdb/options.h" -#include "rocksdb/sst_file_writer.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_SstFileWriter - * Method: newSstFileWriter - * Signature: (JJJB)J - */ -jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJJB( - JNIEnv * /*env*/, jclass /*jcls*/, jlong jenvoptions, jlong joptions, - jlong jcomparator_handle, jbyte jcomparator_type) { - ROCKSDB_NAMESPACE::Comparator *comparator = nullptr; - switch (jcomparator_type) { - // JAVA_COMPARATOR - case 0x0: - comparator = reinterpret_cast( - jcomparator_handle); - break; - - // JAVA_NATIVE_COMPARATOR_WRAPPER - case 0x1: - comparator = - reinterpret_cast(jcomparator_handle); - break; - } - auto *env_options = - reinterpret_cast(jenvoptions); - auto *options = - reinterpret_cast(joptions); - ROCKSDB_NAMESPACE::SstFileWriter *sst_file_writer = - new ROCKSDB_NAMESPACE::SstFileWriter(*env_options, *options, comparator); - return GET_CPLUSPLUS_POINTER(sst_file_writer); -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: newSstFileWriter - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJ(JNIEnv * /*env*/, - jclass /*jcls*/, - jlong jenvoptions, - jlong joptions) { - auto *env_options = - reinterpret_cast(jenvoptions); - auto *options = - reinterpret_cast(joptions); - ROCKSDB_NAMESPACE::SstFileWriter *sst_file_writer = - new ROCKSDB_NAMESPACE::SstFileWriter(*env_options, *options); - return GET_CPLUSPLUS_POINTER(sst_file_writer); -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: open - * Signature: (JLjava/lang/String;)V - */ -void Java_org_rocksdb_SstFileWriter_open(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, jstring jfile_path) { - const char *file_path = env->GetStringUTFChars(jfile_path, nullptr); - if (file_path == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Open( - file_path); - env->ReleaseStringUTFChars(jfile_path, file_path); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: put - * Signature: (JJJ)V - */ -void Java_org_rocksdb_SstFileWriter_put__JJJ(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, jlong jkey_handle, - jlong jvalue_handle) { - auto *key_slice = reinterpret_cast(jkey_handle); - auto *value_slice = - reinterpret_cast(jvalue_handle); - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Put( - *key_slice, *value_slice); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: put - * Signature: (JJJ)V - */ -void Java_org_rocksdb_SstFileWriter_put__J_3B_3B(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, jbyteArray jkey, - jbyteArray jval) { - jbyte *key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - env->GetArrayLength(jkey)); - - jbyte *value = env->GetByteArrayElements(jval, nullptr); - if (value == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - return; - } - ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast(value), - env->GetArrayLength(jval)); - - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Put( - key_slice, value_slice); - - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - env->ReleaseByteArrayElements(jval, value, JNI_ABORT); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: putDirect - * Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_SstFileWriter_putDirect(JNIEnv *env, jobject /*jdb*/, - jlong jdb_handle, jobject jkey, - jint jkey_off, jint jkey_len, - jobject jval, jint jval_off, - jint jval_len) { - auto *writer = - reinterpret_cast(jdb_handle); - auto put = [&env, &writer](ROCKSDB_NAMESPACE::Slice &key, - ROCKSDB_NAMESPACE::Slice &value) { - ROCKSDB_NAMESPACE::Status s = writer->Put(key, value); - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - }; - ROCKSDB_NAMESPACE::JniUtil::kv_op_direct(put, env, jkey, jkey_off, jkey_len, - jval, jval_off, jval_len); -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: fileSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_SstFileWriter_fileSize(JNIEnv * /*env*/, jobject /*jdb*/, - jlong jdb_handle) { - auto *writer = - reinterpret_cast(jdb_handle); - return static_cast(writer->FileSize()); -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: merge - * Signature: (JJJ)V - */ -void Java_org_rocksdb_SstFileWriter_merge__JJJ(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, jlong jkey_handle, - jlong jvalue_handle) { - auto *key_slice = reinterpret_cast(jkey_handle); - auto *value_slice = - reinterpret_cast(jvalue_handle); - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Merge( - *key_slice, *value_slice); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: merge - * Signature: (J[B[B)V - */ -void Java_org_rocksdb_SstFileWriter_merge__J_3B_3B(JNIEnv *env, - jobject /*jobj*/, - jlong jhandle, - jbyteArray jkey, - jbyteArray jval) { - jbyte *key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - env->GetArrayLength(jkey)); - - jbyte *value = env->GetByteArrayElements(jval, nullptr); - if (value == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - return; - } - ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast(value), - env->GetArrayLength(jval)); - - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Merge( - key_slice, value_slice); - - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - env->ReleaseByteArrayElements(jval, value, JNI_ABORT); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: delete - * Signature: (JJJ)V - */ -void Java_org_rocksdb_SstFileWriter_delete__J_3B(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, - jbyteArray jkey) { - jbyte *key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - env->GetArrayLength(jkey)); - - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Delete( - key_slice); - - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: delete - * Signature: (JJJ)V - */ -void Java_org_rocksdb_SstFileWriter_delete__JJ(JNIEnv *env, jobject /*jobj*/, - jlong jhandle, - jlong jkey_handle) { - auto *key_slice = reinterpret_cast(jkey_handle); - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Delete( - *key_slice); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: finish - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileWriter_finish(JNIEnv *env, jobject /*jobj*/, - jlong jhandle) { - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(jhandle)->Finish(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_SstFileWriter - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_SstFileWriter_disposeInternal(JNIEnv * /*env*/, - jobject /*jobj*/, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} diff --git a/java/rocksjni/sst_partitioner.cc b/java/rocksjni/sst_partitioner.cc deleted file mode 100644 index 1cea3b0cb..000000000 --- a/java/rocksjni/sst_partitioner.cc +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling C++ ROCKSDB_NAMESPACE::SstFileManager methods -// from Java side. - -#include "rocksdb/sst_partitioner.h" - -#include - -#include - -#include "include/org_rocksdb_SstPartitionerFixedPrefixFactory.h" -#include "rocksdb/sst_file_manager.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_SstPartitionerFixedPrefixFactory - * Method: newSstPartitionerFixedPrefixFactory0 - * Signature: (J)J - */ -jlong Java_org_rocksdb_SstPartitionerFixedPrefixFactory_newSstPartitionerFixedPrefixFactory0( - JNIEnv*, jclass, jlong prefix_len) { - auto* ptr = new std::shared_ptr( - ROCKSDB_NAMESPACE::NewSstPartitionerFixedPrefixFactory(prefix_len)); - return GET_CPLUSPLUS_POINTER(ptr); -} - -/* - * Class: org_rocksdb_SstPartitionerFixedPrefixFactory - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_SstPartitionerFixedPrefixFactory_disposeInternal( - JNIEnv*, jobject, jlong jhandle) { - auto* ptr = reinterpret_cast< - std::shared_ptr*>(jhandle); - delete ptr; // delete std::shared_ptr -} diff --git a/java/rocksjni/statistics.cc b/java/rocksjni/statistics.cc deleted file mode 100644 index bd405afa1..000000000 --- a/java/rocksjni/statistics.cc +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::Statistics methods from Java side. - -#include "rocksdb/statistics.h" - -#include - -#include -#include - -#include "include/org_rocksdb_Statistics.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -#include "rocksjni/statisticsjni.h" - -/* - * Class: org_rocksdb_Statistics - * Method: newStatistics - * Signature: ()J - */ -jlong Java_org_rocksdb_Statistics_newStatistics__(JNIEnv* env, jclass jcls) { - return Java_org_rocksdb_Statistics_newStatistics___3BJ(env, jcls, nullptr, 0); -} - -/* - * Class: org_rocksdb_Statistics - * Method: newStatistics - * Signature: (J)J - */ -jlong Java_org_rocksdb_Statistics_newStatistics__J( - JNIEnv* env, jclass jcls, jlong jother_statistics_handle) { - return Java_org_rocksdb_Statistics_newStatistics___3BJ( - env, jcls, nullptr, jother_statistics_handle); -} - -/* - * Class: org_rocksdb_Statistics - * Method: newStatistics - * Signature: ([B)J - */ -jlong Java_org_rocksdb_Statistics_newStatistics___3B(JNIEnv* env, jclass jcls, - jbyteArray jhistograms) { - return Java_org_rocksdb_Statistics_newStatistics___3BJ(env, jcls, jhistograms, - 0); -} - -/* - * Class: org_rocksdb_Statistics - * Method: newStatistics - * Signature: ([BJ)J - */ -jlong Java_org_rocksdb_Statistics_newStatistics___3BJ( - JNIEnv* env, jclass, jbyteArray jhistograms, - jlong jother_statistics_handle) { - std::shared_ptr* pSptr_other_statistics = - nullptr; - if (jother_statistics_handle > 0) { - pSptr_other_statistics = - reinterpret_cast*>( - jother_statistics_handle); - } - - std::set histograms; - if (jhistograms != nullptr) { - const jsize len = env->GetArrayLength(jhistograms); - if (len > 0) { - jbyte* jhistogram = env->GetByteArrayElements(jhistograms, nullptr); - if (jhistogram == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - for (jsize i = 0; i < len; i++) { - const ROCKSDB_NAMESPACE::Histograms histogram = - ROCKSDB_NAMESPACE::HistogramTypeJni::toCppHistograms(jhistogram[i]); - histograms.emplace(histogram); - } - - env->ReleaseByteArrayElements(jhistograms, jhistogram, JNI_ABORT); - } - } - - std::shared_ptr sptr_other_statistics = - nullptr; - if (pSptr_other_statistics != nullptr) { - sptr_other_statistics = *pSptr_other_statistics; - } - - auto* pSptr_statistics = - new std::shared_ptr( - new ROCKSDB_NAMESPACE::StatisticsJni(sptr_other_statistics, - histograms)); - - return GET_CPLUSPLUS_POINTER(pSptr_statistics); -} - -/* - * Class: org_rocksdb_Statistics - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_Statistics_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - if (jhandle > 0) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - delete pSptr_statistics; - } -} - -/* - * Class: org_rocksdb_Statistics - * Method: statsLevel - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Statistics_statsLevel(JNIEnv*, jobject, jlong jhandle) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - return ROCKSDB_NAMESPACE::StatsLevelJni::toJavaStatsLevel( - pSptr_statistics->get()->get_stats_level()); -} - -/* - * Class: org_rocksdb_Statistics - * Method: setStatsLevel - * Signature: (JB)V - */ -void Java_org_rocksdb_Statistics_setStatsLevel(JNIEnv*, jobject, jlong jhandle, - jbyte jstats_level) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - auto stats_level = - ROCKSDB_NAMESPACE::StatsLevelJni::toCppStatsLevel(jstats_level); - pSptr_statistics->get()->set_stats_level(stats_level); -} - -/* - * Class: org_rocksdb_Statistics - * Method: getTickerCount - * Signature: (JB)J - */ -jlong Java_org_rocksdb_Statistics_getTickerCount(JNIEnv*, jobject, - jlong jhandle, - jbyte jticker_type) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - auto ticker = ROCKSDB_NAMESPACE::TickerTypeJni::toCppTickers(jticker_type); - uint64_t count = pSptr_statistics->get()->getTickerCount(ticker); - return static_cast(count); -} - -/* - * Class: org_rocksdb_Statistics - * Method: getAndResetTickerCount - * Signature: (JB)J - */ -jlong Java_org_rocksdb_Statistics_getAndResetTickerCount(JNIEnv*, jobject, - jlong jhandle, - jbyte jticker_type) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - auto ticker = ROCKSDB_NAMESPACE::TickerTypeJni::toCppTickers(jticker_type); - return pSptr_statistics->get()->getAndResetTickerCount(ticker); -} - -/* - * Class: org_rocksdb_Statistics - * Method: getHistogramData - * Signature: (JB)Lorg/rocksdb/HistogramData; - */ -jobject Java_org_rocksdb_Statistics_getHistogramData(JNIEnv* env, jobject, - jlong jhandle, - jbyte jhistogram_type) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - - // TODO(AR) perhaps better to construct a Java Object Wrapper that - // uses ptr to C++ `new HistogramData` - ROCKSDB_NAMESPACE::HistogramData data; - - auto histogram = - ROCKSDB_NAMESPACE::HistogramTypeJni::toCppHistograms(jhistogram_type); - pSptr_statistics->get()->histogramData( - static_cast(histogram), &data); - - jclass jclazz = ROCKSDB_NAMESPACE::HistogramDataJni::getJClass(env); - if (jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - jmethodID mid = - ROCKSDB_NAMESPACE::HistogramDataJni::getConstructorMethodId(env); - if (mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - return env->NewObject(jclazz, mid, data.median, data.percentile95, - data.percentile99, data.average, - data.standard_deviation, data.max, data.count, data.sum, - data.min); -} - -/* - * Class: org_rocksdb_Statistics - * Method: getHistogramString - * Signature: (JB)Ljava/lang/String; - */ -jstring Java_org_rocksdb_Statistics_getHistogramString(JNIEnv* env, jobject, - jlong jhandle, - jbyte jhistogram_type) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - auto histogram = - ROCKSDB_NAMESPACE::HistogramTypeJni::toCppHistograms(jhistogram_type); - auto str = pSptr_statistics->get()->getHistogramString(histogram); - return env->NewStringUTF(str.c_str()); -} - -/* - * Class: org_rocksdb_Statistics - * Method: reset - * Signature: (J)V - */ -void Java_org_rocksdb_Statistics_reset(JNIEnv* env, jobject, jlong jhandle) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - ROCKSDB_NAMESPACE::Status s = pSptr_statistics->get()->Reset(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Statistics - * Method: toString - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_Statistics_toString(JNIEnv* env, jobject, - jlong jhandle) { - auto* pSptr_statistics = - reinterpret_cast*>( - jhandle); - assert(pSptr_statistics != nullptr); - auto str = pSptr_statistics->get()->ToString(); - return env->NewStringUTF(str.c_str()); -} diff --git a/java/rocksjni/statisticsjni.cc b/java/rocksjni/statisticsjni.cc deleted file mode 100644 index f46337893..000000000 --- a/java/rocksjni/statisticsjni.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Statistics - -#include "rocksjni/statisticsjni.h" - -namespace ROCKSDB_NAMESPACE { - -StatisticsJni::StatisticsJni(std::shared_ptr stats) - : StatisticsImpl(stats), m_ignore_histograms() {} - -StatisticsJni::StatisticsJni(std::shared_ptr stats, - const std::set ignore_histograms) - : StatisticsImpl(stats), m_ignore_histograms(ignore_histograms) {} - -bool StatisticsJni::HistEnabledForType(uint32_t type) const { - if (type >= HISTOGRAM_ENUM_MAX) { - return false; - } - - if (m_ignore_histograms.count(type) > 0) { - return false; - } - - return true; -} -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/statisticsjni.h b/java/rocksjni/statisticsjni.h deleted file mode 100644 index ce823f9b1..000000000 --- a/java/rocksjni/statisticsjni.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Statistics - -#ifndef JAVA_ROCKSJNI_STATISTICSJNI_H_ -#define JAVA_ROCKSJNI_STATISTICSJNI_H_ - -#include -#include -#include - -#include "monitoring/statistics.h" -#include "rocksdb/statistics.h" - -namespace ROCKSDB_NAMESPACE { - -class StatisticsJni : public StatisticsImpl { - public: - StatisticsJni(std::shared_ptr stats); - StatisticsJni(std::shared_ptr stats, - const std::set ignore_histograms); - virtual bool HistEnabledForType(uint32_t type) const override; - - private: - const std::set m_ignore_histograms; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_STATISTICSJNI_H_ diff --git a/java/rocksjni/table.cc b/java/rocksjni/table.cc deleted file mode 100644 index 7f99900e4..000000000 --- a/java/rocksjni/table.cc +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Options. - -#include "rocksdb/table.h" - -#include - -#include "include/org_rocksdb_BlockBasedTableConfig.h" -#include "include/org_rocksdb_PlainTableConfig.h" -#include "portal.h" -#include "rocksdb/cache.h" -#include "rocksdb/filter_policy.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_PlainTableConfig - * Method: newTableFactoryHandle - * Signature: (IIDIIBZZ)J - */ -jlong Java_org_rocksdb_PlainTableConfig_newTableFactoryHandle( - JNIEnv * /*env*/, jobject /*jobj*/, jint jkey_size, - jint jbloom_bits_per_key, jdouble jhash_table_ratio, jint jindex_sparseness, - jint jhuge_page_tlb_size, jbyte jencoding_type, jboolean jfull_scan_mode, - jboolean jstore_index_in_file) { - ROCKSDB_NAMESPACE::PlainTableOptions options = - ROCKSDB_NAMESPACE::PlainTableOptions(); - options.user_key_len = jkey_size; - options.bloom_bits_per_key = jbloom_bits_per_key; - options.hash_table_ratio = jhash_table_ratio; - options.index_sparseness = jindex_sparseness; - options.huge_page_tlb_size = jhuge_page_tlb_size; - options.encoding_type = - static_cast(jencoding_type); - options.full_scan_mode = jfull_scan_mode; - options.store_index_in_file = jstore_index_in_file; - return GET_CPLUSPLUS_POINTER( - ROCKSDB_NAMESPACE::NewPlainTableFactory(options)); -} - -/* - * Class: org_rocksdb_BlockBasedTableConfig - * Method: newTableFactoryHandle - * Signature: (ZZZZBBDBZJJJJIIIJZZZJZZIIZZBJIJI)J - */ -jlong Java_org_rocksdb_BlockBasedTableConfig_newTableFactoryHandle( - JNIEnv *, jobject, jboolean jcache_index_and_filter_blocks, - jboolean jcache_index_and_filter_blocks_with_high_priority, - jboolean jpin_l0_filter_and_index_blocks_in_cache, - jboolean jpin_top_level_index_and_filter, jbyte jindex_type_value, - jbyte jdata_block_index_type_value, - jdouble jdata_block_hash_table_util_ratio, jbyte jchecksum_type_value, - jboolean jno_block_cache, jlong jblock_cache_handle, - jlong jpersistent_cache_handle, jlong jblock_size, - jint jblock_size_deviation, jint jblock_restart_interval, - jint jindex_block_restart_interval, jlong jmetadata_block_size, - jboolean jpartition_filters, jboolean joptimize_filters_for_memory, - jboolean juse_delta_encoding, jlong jfilter_policy_handle, - jboolean jwhole_key_filtering, jboolean jverify_compression, - jint jread_amp_bytes_per_bit, jint jformat_version, - jboolean jenable_index_compression, jboolean jblock_align, - jbyte jindex_shortening, jlong jblock_cache_size, - jint jblock_cache_num_shard_bits) { - ROCKSDB_NAMESPACE::BlockBasedTableOptions options; - options.cache_index_and_filter_blocks = - static_cast(jcache_index_and_filter_blocks); - options.cache_index_and_filter_blocks_with_high_priority = - static_cast(jcache_index_and_filter_blocks_with_high_priority); - options.pin_l0_filter_and_index_blocks_in_cache = - static_cast(jpin_l0_filter_and_index_blocks_in_cache); - options.pin_top_level_index_and_filter = - static_cast(jpin_top_level_index_and_filter); - options.index_type = - ROCKSDB_NAMESPACE::IndexTypeJni::toCppIndexType(jindex_type_value); - options.data_block_index_type = - ROCKSDB_NAMESPACE::DataBlockIndexTypeJni::toCppDataBlockIndexType( - jdata_block_index_type_value); - options.data_block_hash_table_util_ratio = - static_cast(jdata_block_hash_table_util_ratio); - options.checksum = ROCKSDB_NAMESPACE::ChecksumTypeJni::toCppChecksumType( - jchecksum_type_value); - options.no_block_cache = static_cast(jno_block_cache); - if (options.no_block_cache) { - options.block_cache = nullptr; - } else { - if (jblock_cache_handle > 0) { - std::shared_ptr *pCache = - reinterpret_cast *>( - jblock_cache_handle); - options.block_cache = *pCache; - } else if (jblock_cache_size >= 0) { - if (jblock_cache_num_shard_bits > 0) { - options.block_cache = ROCKSDB_NAMESPACE::NewLRUCache( - static_cast(jblock_cache_size), - static_cast(jblock_cache_num_shard_bits)); - } else { - options.block_cache = ROCKSDB_NAMESPACE::NewLRUCache( - static_cast(jblock_cache_size)); - } - } else { - options.no_block_cache = true; - options.block_cache = nullptr; - } - } - if (jpersistent_cache_handle > 0) { - std::shared_ptr *pCache = - reinterpret_cast *>( - jpersistent_cache_handle); - options.persistent_cache = *pCache; - } - options.block_size = static_cast(jblock_size); - options.block_size_deviation = static_cast(jblock_size_deviation); - options.block_restart_interval = static_cast(jblock_restart_interval); - options.index_block_restart_interval = - static_cast(jindex_block_restart_interval); - options.metadata_block_size = static_cast(jmetadata_block_size); - options.partition_filters = static_cast(jpartition_filters); - options.optimize_filters_for_memory = - static_cast(joptimize_filters_for_memory); - options.use_delta_encoding = static_cast(juse_delta_encoding); - if (jfilter_policy_handle > 0) { - std::shared_ptr *pFilterPolicy = - reinterpret_cast *>( - jfilter_policy_handle); - options.filter_policy = *pFilterPolicy; - } - options.whole_key_filtering = static_cast(jwhole_key_filtering); - options.verify_compression = static_cast(jverify_compression); - options.read_amp_bytes_per_bit = - static_cast(jread_amp_bytes_per_bit); - options.format_version = static_cast(jformat_version); - options.enable_index_compression = - static_cast(jenable_index_compression); - options.block_align = static_cast(jblock_align); - options.index_shortening = - ROCKSDB_NAMESPACE::IndexShorteningModeJni::toCppIndexShorteningMode( - jindex_shortening); - - return GET_CPLUSPLUS_POINTER( - ROCKSDB_NAMESPACE::NewBlockBasedTableFactory(options)); -} diff --git a/java/rocksjni/table_filter.cc b/java/rocksjni/table_filter.cc deleted file mode 100644 index 1400fa1d9..000000000 --- a/java/rocksjni/table_filter.cc +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// org.rocksdb.AbstractTableFilter. - -#include - -#include - -#include "include/org_rocksdb_AbstractTableFilter.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/table_filter_jnicallback.h" - -/* - * Class: org_rocksdb_AbstractTableFilter - * Method: createNewTableFilter - * Signature: ()J - */ -jlong Java_org_rocksdb_AbstractTableFilter_createNewTableFilter( - JNIEnv* env, jobject jtable_filter) { - auto* table_filter_jnicallback = - new ROCKSDB_NAMESPACE::TableFilterJniCallback(env, jtable_filter); - return GET_CPLUSPLUS_POINTER(table_filter_jnicallback); -} diff --git a/java/rocksjni/table_filter_jnicallback.cc b/java/rocksjni/table_filter_jnicallback.cc deleted file mode 100644 index 5350c5cee..000000000 --- a/java/rocksjni/table_filter_jnicallback.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::TableFilter. - -#include "rocksjni/table_filter_jnicallback.h" - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -TableFilterJniCallback::TableFilterJniCallback(JNIEnv* env, - jobject jtable_filter) - : JniCallback(env, jtable_filter) { - m_jfilter_methodid = AbstractTableFilterJni::getFilterMethod(env); - if (m_jfilter_methodid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - // create the function reference - /* - Note the JNI ENV must be obtained/release - on each call to the function itself as - it may be called from multiple threads - */ - m_table_filter_function = - [this](const ROCKSDB_NAMESPACE::TableProperties& table_properties) { - jboolean attached_thread = JNI_FALSE; - JNIEnv* thread_env = getJniEnv(&attached_thread); - assert(thread_env != nullptr); - - // create a Java TableProperties object - jobject jtable_properties = TablePropertiesJni::fromCppTableProperties( - thread_env, table_properties); - if (jtable_properties == nullptr) { - // exception thrown from fromCppTableProperties - thread_env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return false; - } - - jboolean result = thread_env->CallBooleanMethod( - m_jcallback_obj, m_jfilter_methodid, jtable_properties); - if (thread_env->ExceptionCheck()) { - // exception thrown from CallBooleanMethod - thread_env->DeleteLocalRef(jtable_properties); - thread_env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return false; - } - - // ok... cleanup and then return - releaseJniEnv(attached_thread); - return static_cast(result); - }; -} - -std::function -TableFilterJniCallback::GetTableFilterFunction() { - return m_table_filter_function; -} - -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/table_filter_jnicallback.h b/java/rocksjni/table_filter_jnicallback.h deleted file mode 100644 index 0ef404ca2..000000000 --- a/java/rocksjni/table_filter_jnicallback.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::TableFilter. - -#ifndef JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_ - -#include - -#include -#include - -#include "rocksdb/table_properties.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -class TableFilterJniCallback : public JniCallback { - public: - TableFilterJniCallback(JNIEnv* env, jobject jtable_filter); - std::function - GetTableFilterFunction(); - - private: - jmethodID m_jfilter_methodid; - std::function - m_table_filter_function; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_ diff --git a/java/rocksjni/testable_event_listener.cc b/java/rocksjni/testable_event_listener.cc deleted file mode 100644 index 71188bc3c..000000000 --- a/java/rocksjni/testable_event_listener.cc +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -#include -#include -#include -#include - -#include "include/org_rocksdb_test_TestableEventListener.h" -#include "rocksdb/listener.h" -#include "rocksdb/status.h" -#include "rocksdb/table_properties.h" - -using ROCKSDB_NAMESPACE::BackgroundErrorReason; -using ROCKSDB_NAMESPACE::CompactionJobInfo; -using ROCKSDB_NAMESPACE::CompactionJobStats; -using ROCKSDB_NAMESPACE::CompactionReason; -using ROCKSDB_NAMESPACE::CompressionType; -using ROCKSDB_NAMESPACE::ExternalFileIngestionInfo; -using ROCKSDB_NAMESPACE::FileOperationInfo; -using ROCKSDB_NAMESPACE::FileOperationType; -using ROCKSDB_NAMESPACE::FlushJobInfo; -using ROCKSDB_NAMESPACE::FlushReason; -using ROCKSDB_NAMESPACE::MemTableInfo; -using ROCKSDB_NAMESPACE::Status; -using ROCKSDB_NAMESPACE::TableFileCreationBriefInfo; -using ROCKSDB_NAMESPACE::TableFileCreationInfo; -using ROCKSDB_NAMESPACE::TableFileCreationReason; -using ROCKSDB_NAMESPACE::TableFileDeletionInfo; -using ROCKSDB_NAMESPACE::TableProperties; -using ROCKSDB_NAMESPACE::WriteStallCondition; -using ROCKSDB_NAMESPACE::WriteStallInfo; - -static TableProperties newTablePropertiesForTest() { - TableProperties table_properties; - table_properties.data_size = UINT64_MAX; - table_properties.index_size = UINT64_MAX; - table_properties.index_partitions = UINT64_MAX; - table_properties.top_level_index_size = UINT64_MAX; - table_properties.index_key_is_user_key = UINT64_MAX; - table_properties.index_value_is_delta_encoded = UINT64_MAX; - table_properties.filter_size = UINT64_MAX; - table_properties.raw_key_size = UINT64_MAX; - table_properties.raw_value_size = UINT64_MAX; - table_properties.num_data_blocks = UINT64_MAX; - table_properties.num_entries = UINT64_MAX; - table_properties.num_deletions = UINT64_MAX; - table_properties.num_merge_operands = UINT64_MAX; - table_properties.num_range_deletions = UINT64_MAX; - table_properties.format_version = UINT64_MAX; - table_properties.fixed_key_len = UINT64_MAX; - table_properties.column_family_id = UINT64_MAX; - table_properties.creation_time = UINT64_MAX; - table_properties.oldest_key_time = UINT64_MAX; - table_properties.file_creation_time = UINT64_MAX; - table_properties.slow_compression_estimated_data_size = UINT64_MAX; - table_properties.fast_compression_estimated_data_size = UINT64_MAX; - table_properties.external_sst_file_global_seqno_offset = UINT64_MAX; - table_properties.db_id = "dbId"; - table_properties.db_session_id = "sessionId"; - table_properties.column_family_name = "columnFamilyName"; - table_properties.filter_policy_name = "filterPolicyName"; - table_properties.comparator_name = "comparatorName"; - table_properties.merge_operator_name = "mergeOperatorName"; - table_properties.prefix_extractor_name = "prefixExtractorName"; - table_properties.property_collectors_names = "propertyCollectorsNames"; - table_properties.compression_name = "compressionName"; - table_properties.compression_options = "compressionOptions"; - table_properties.user_collected_properties = {{"key", "value"}}; - table_properties.readable_properties = {{"key", "value"}}; - return table_properties; -} - -/* - * Class: org_rocksdb_test_TestableEventListener - * Method: invokeAllCallbacks - * Signature: (J)V - */ -void Java_org_rocksdb_test_TestableEventListener_invokeAllCallbacks( - JNIEnv *, jclass, jlong jhandle) { - const auto &el = - *reinterpret_cast *>( - jhandle); - - TableProperties table_properties = newTablePropertiesForTest(); - - FlushJobInfo flush_job_info; - flush_job_info.cf_id = INT_MAX; - flush_job_info.cf_name = "testColumnFamily"; - flush_job_info.file_path = "/file/path"; - flush_job_info.file_number = UINT64_MAX; - flush_job_info.oldest_blob_file_number = UINT64_MAX; - flush_job_info.thread_id = UINT64_MAX; - flush_job_info.job_id = INT_MAX; - flush_job_info.triggered_writes_slowdown = true; - flush_job_info.triggered_writes_stop = true; - flush_job_info.smallest_seqno = UINT64_MAX; - flush_job_info.largest_seqno = UINT64_MAX; - flush_job_info.table_properties = table_properties; - flush_job_info.flush_reason = FlushReason::kManualFlush; - - el->OnFlushCompleted(nullptr, flush_job_info); - el->OnFlushBegin(nullptr, flush_job_info); - - Status status = Status::Incomplete(Status::SubCode::kNoSpace); - - TableFileDeletionInfo file_deletion_info; - file_deletion_info.db_name = "dbName"; - file_deletion_info.file_path = "/file/path"; - file_deletion_info.job_id = INT_MAX; - file_deletion_info.status = status; - - el->OnTableFileDeleted(file_deletion_info); - - CompactionJobInfo compaction_job_info; - compaction_job_info.cf_id = UINT32_MAX; - compaction_job_info.cf_name = "compactionColumnFamily"; - compaction_job_info.status = status; - compaction_job_info.thread_id = UINT64_MAX; - compaction_job_info.job_id = INT_MAX; - compaction_job_info.base_input_level = INT_MAX; - compaction_job_info.output_level = INT_MAX; - compaction_job_info.input_files = {"inputFile.sst"}; - compaction_job_info.input_file_infos = {}; - compaction_job_info.output_files = {"outputFile.sst"}; - compaction_job_info.output_file_infos = {}; - compaction_job_info.table_properties = { - {"tableProperties", std::shared_ptr( - &table_properties, [](TableProperties *) {})}}; - compaction_job_info.compaction_reason = CompactionReason::kFlush; - compaction_job_info.compression = CompressionType::kSnappyCompression; - - compaction_job_info.stats = CompactionJobStats(); - - el->OnCompactionBegin(nullptr, compaction_job_info); - el->OnCompactionCompleted(nullptr, compaction_job_info); - - TableFileCreationInfo file_creation_info; - file_creation_info.file_size = UINT64_MAX; - file_creation_info.table_properties = table_properties; - file_creation_info.status = status; - file_creation_info.file_checksum = "fileChecksum"; - file_creation_info.file_checksum_func_name = "fileChecksumFuncName"; - file_creation_info.db_name = "dbName"; - file_creation_info.cf_name = "columnFamilyName"; - file_creation_info.file_path = "/file/path"; - file_creation_info.job_id = INT_MAX; - file_creation_info.reason = TableFileCreationReason::kMisc; - - el->OnTableFileCreated(file_creation_info); - - TableFileCreationBriefInfo file_creation_brief_info; - file_creation_brief_info.db_name = "dbName"; - file_creation_brief_info.cf_name = "columnFamilyName"; - file_creation_brief_info.file_path = "/file/path"; - file_creation_brief_info.job_id = INT_MAX; - file_creation_brief_info.reason = TableFileCreationReason::kMisc; - - el->OnTableFileCreationStarted(file_creation_brief_info); - - MemTableInfo mem_table_info; - mem_table_info.cf_name = "columnFamilyName"; - mem_table_info.first_seqno = UINT64_MAX; - mem_table_info.earliest_seqno = UINT64_MAX; - mem_table_info.num_entries = UINT64_MAX; - mem_table_info.num_deletes = UINT64_MAX; - - el->OnMemTableSealed(mem_table_info); - el->OnColumnFamilyHandleDeletionStarted(nullptr); - - ExternalFileIngestionInfo file_ingestion_info; - file_ingestion_info.cf_name = "columnFamilyName"; - file_ingestion_info.external_file_path = "/external/file/path"; - file_ingestion_info.internal_file_path = "/internal/file/path"; - file_ingestion_info.global_seqno = UINT64_MAX; - file_ingestion_info.table_properties = table_properties; - el->OnExternalFileIngested(nullptr, file_ingestion_info); - - el->OnBackgroundError(BackgroundErrorReason::kFlush, &status); - - WriteStallInfo write_stall_info; - write_stall_info.cf_name = "columnFamilyName"; - write_stall_info.condition.cur = WriteStallCondition::kDelayed; - write_stall_info.condition.prev = WriteStallCondition::kStopped; - el->OnStallConditionsChanged(write_stall_info); - - const std::string file_path = "/file/path"; - const auto start_timestamp = - std::make_pair(std::chrono::time_point( - std::chrono::nanoseconds(1600699420000000000ll)), - std::chrono::time_point( - std::chrono::nanoseconds(1600699420000000000ll))); - const auto finish_timestamp = - std::chrono::time_point( - std::chrono::nanoseconds(1600699425000000000ll)); - FileOperationInfo op_info = - FileOperationInfo(FileOperationType::kRead, file_path, start_timestamp, - finish_timestamp, status); - op_info.offset = UINT64_MAX; - op_info.length = SIZE_MAX; - - el->OnFileReadFinish(op_info); - el->OnFileWriteFinish(op_info); - el->OnFileFlushFinish(op_info); - el->OnFileSyncFinish(op_info); - el->OnFileRangeSyncFinish(op_info); - el->OnFileTruncateFinish(op_info); - el->OnFileCloseFinish(op_info); - el->ShouldBeNotifiedOnFileIO(); - - bool auto_recovery; - el->OnErrorRecoveryBegin(BackgroundErrorReason::kFlush, status, - &auto_recovery); - el->OnErrorRecoveryCompleted(status); -} diff --git a/java/rocksjni/thread_status.cc b/java/rocksjni/thread_status.cc deleted file mode 100644 index c600f6cd5..000000000 --- a/java/rocksjni/thread_status.cc +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::ThreadStatus methods from Java side. - -#include "rocksdb/thread_status.h" - -#include - -#include "include/org_rocksdb_ThreadStatus.h" -#include "portal.h" - -/* - * Class: org_rocksdb_ThreadStatus - * Method: getThreadTypeName - * Signature: (B)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ThreadStatus_getThreadTypeName( - JNIEnv* env, jclass, jbyte jthread_type_value) { - auto name = ROCKSDB_NAMESPACE::ThreadStatus::GetThreadTypeName( - ROCKSDB_NAMESPACE::ThreadTypeJni::toCppThreadType(jthread_type_value)); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &name, true); -} - -/* - * Class: org_rocksdb_ThreadStatus - * Method: getOperationName - * Signature: (B)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ThreadStatus_getOperationName( - JNIEnv* env, jclass, jbyte joperation_type_value) { - auto name = ROCKSDB_NAMESPACE::ThreadStatus::GetOperationName( - ROCKSDB_NAMESPACE::OperationTypeJni::toCppOperationType( - joperation_type_value)); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &name, true); -} - -/* - * Class: org_rocksdb_ThreadStatus - * Method: microsToStringNative - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ThreadStatus_microsToStringNative(JNIEnv* env, jclass, - jlong jmicros) { - auto str = ROCKSDB_NAMESPACE::ThreadStatus::MicrosToString( - static_cast(jmicros)); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &str, true); -} - -/* - * Class: org_rocksdb_ThreadStatus - * Method: getOperationStageName - * Signature: (B)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ThreadStatus_getOperationStageName( - JNIEnv* env, jclass, jbyte joperation_stage_value) { - auto name = ROCKSDB_NAMESPACE::ThreadStatus::GetOperationStageName( - ROCKSDB_NAMESPACE::OperationStageJni::toCppOperationStage( - joperation_stage_value)); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &name, true); -} - -/* - * Class: org_rocksdb_ThreadStatus - * Method: getOperationPropertyName - * Signature: (BI)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ThreadStatus_getOperationPropertyName( - JNIEnv* env, jclass, jbyte joperation_type_value, jint jindex) { - auto name = ROCKSDB_NAMESPACE::ThreadStatus::GetOperationPropertyName( - ROCKSDB_NAMESPACE::OperationTypeJni::toCppOperationType( - joperation_type_value), - static_cast(jindex)); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &name, true); -} - -/* - * Class: org_rocksdb_ThreadStatus - * Method: interpretOperationProperties - * Signature: (B[J)Ljava/util/Map; - */ -jobject Java_org_rocksdb_ThreadStatus_interpretOperationProperties( - JNIEnv* env, jclass, jbyte joperation_type_value, - jlongArray joperation_properties) { - // convert joperation_properties - const jsize len = env->GetArrayLength(joperation_properties); - const std::unique_ptr op_properties(new uint64_t[len]); - jlong* jop = env->GetLongArrayElements(joperation_properties, nullptr); - if (jop == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - for (jsize i = 0; i < len; i++) { - op_properties[i] = static_cast(jop[i]); - } - env->ReleaseLongArrayElements(joperation_properties, jop, JNI_ABORT); - - // call the function - auto result = ROCKSDB_NAMESPACE::ThreadStatus::InterpretOperationProperties( - ROCKSDB_NAMESPACE::OperationTypeJni::toCppOperationType( - joperation_type_value), - op_properties.get()); - jobject jresult = ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(env, &result); - if (env->ExceptionCheck()) { - // exception occurred - return nullptr; - } - - return jresult; -} - -/* - * Class: org_rocksdb_ThreadStatus - * Method: getStateName - * Signature: (B)Ljava/lang/String; - */ -jstring Java_org_rocksdb_ThreadStatus_getStateName(JNIEnv* env, jclass, - jbyte jstate_type_value) { - auto name = ROCKSDB_NAMESPACE::ThreadStatus::GetStateName( - ROCKSDB_NAMESPACE::StateTypeJni::toCppStateType(jstate_type_value)); - return ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &name, true); -} diff --git a/java/rocksjni/trace_writer.cc b/java/rocksjni/trace_writer.cc deleted file mode 100644 index d58276399..000000000 --- a/java/rocksjni/trace_writer.cc +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::CompactionFilterFactory. - -#include - -#include "include/org_rocksdb_AbstractTraceWriter.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/trace_writer_jnicallback.h" - -/* - * Class: org_rocksdb_AbstractTraceWriter - * Method: createNewTraceWriter - * Signature: ()J - */ -jlong Java_org_rocksdb_AbstractTraceWriter_createNewTraceWriter(JNIEnv* env, - jobject jobj) { - auto* trace_writer = new ROCKSDB_NAMESPACE::TraceWriterJniCallback(env, jobj); - return GET_CPLUSPLUS_POINTER(trace_writer); -} diff --git a/java/rocksjni/trace_writer_jnicallback.cc b/java/rocksjni/trace_writer_jnicallback.cc deleted file mode 100644 index d1ed32038..000000000 --- a/java/rocksjni/trace_writer_jnicallback.cc +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::TraceWriter. - -#include "rocksjni/trace_writer_jnicallback.h" - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -TraceWriterJniCallback::TraceWriterJniCallback(JNIEnv* env, - jobject jtrace_writer) - : JniCallback(env, jtrace_writer) { - m_jwrite_proxy_methodid = AbstractTraceWriterJni::getWriteProxyMethodId(env); - if (m_jwrite_proxy_methodid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - m_jclose_writer_proxy_methodid = - AbstractTraceWriterJni::getCloseWriterProxyMethodId(env); - if (m_jclose_writer_proxy_methodid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - m_jget_file_size_methodid = - AbstractTraceWriterJni::getGetFileSizeMethodId(env); - if (m_jget_file_size_methodid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } -} - -Status TraceWriterJniCallback::Write(const Slice& data) { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - if (env == nullptr) { - return Status::IOError("Unable to attach JNI Environment"); - } - - jshort jstatus = - env->CallShortMethod(m_jcallback_obj, m_jwrite_proxy_methodid, &data); - - if (env->ExceptionCheck()) { - // exception thrown from CallShortMethod - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return Status::IOError( - "Unable to call AbstractTraceWriter#writeProxy(long)"); - } - - // unpack status code and status sub-code from jstatus - jbyte jcode_value = (jstatus >> 8) & 0xFF; - jbyte jsub_code_value = jstatus & 0xFF; - std::unique_ptr s = - StatusJni::toCppStatus(jcode_value, jsub_code_value); - - releaseJniEnv(attached_thread); - - return Status(*s); -} - -Status TraceWriterJniCallback::Close() { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - if (env == nullptr) { - return Status::IOError("Unable to attach JNI Environment"); - } - - jshort jstatus = - env->CallShortMethod(m_jcallback_obj, m_jclose_writer_proxy_methodid); - - if (env->ExceptionCheck()) { - // exception thrown from CallShortMethod - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return Status::IOError( - "Unable to call AbstractTraceWriter#closeWriterProxy()"); - } - - // unpack status code and status sub-code from jstatus - jbyte code_value = (jstatus >> 8) & 0xFF; - jbyte sub_code_value = jstatus & 0xFF; - std::unique_ptr s = - StatusJni::toCppStatus(code_value, sub_code_value); - - releaseJniEnv(attached_thread); - - return Status(*s); -} - -uint64_t TraceWriterJniCallback::GetFileSize() { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - if (env == nullptr) { - return 0; - } - - jlong jfile_size = - env->CallLongMethod(m_jcallback_obj, m_jget_file_size_methodid); - - if (env->ExceptionCheck()) { - // exception thrown from CallLongMethod - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return 0; - } - - releaseJniEnv(attached_thread); - - return static_cast(jfile_size); -} - -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/trace_writer_jnicallback.h b/java/rocksjni/trace_writer_jnicallback.h deleted file mode 100644 index c82a3a72c..000000000 --- a/java/rocksjni/trace_writer_jnicallback.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::TraceWriter. - -#ifndef JAVA_ROCKSJNI_TRACE_WRITER_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_TRACE_WRITER_JNICALLBACK_H_ - -#include - -#include - -#include "rocksdb/trace_reader_writer.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -class TraceWriterJniCallback : public JniCallback, public TraceWriter { - public: - TraceWriterJniCallback(JNIEnv* env, jobject jtrace_writer); - virtual Status Write(const Slice& data); - virtual Status Close(); - virtual uint64_t GetFileSize(); - - private: - jmethodID m_jwrite_proxy_methodid; - jmethodID m_jclose_writer_proxy_methodid; - jmethodID m_jget_file_size_methodid; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_TRACE_WRITER_JNICALLBACK_H_ diff --git a/java/rocksjni/transaction.cc b/java/rocksjni/transaction.cc deleted file mode 100644 index 1a0a64fc7..000000000 --- a/java/rocksjni/transaction.cc +++ /dev/null @@ -1,1655 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::Transaction. - -#include "rocksdb/utilities/transaction.h" - -#include - -#include - -#include "include/org_rocksdb_Transaction.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4503) // identifier' : decorated name length - // exceeded, name was truncated -#endif - -/* - * Class: org_rocksdb_Transaction - * Method: setSnapshot - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_setSnapshot(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - txn->SetSnapshot(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: setSnapshotOnNextOperation - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_setSnapshotOnNextOperation__J( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - txn->SetSnapshotOnNextOperation(nullptr); -} - -/* - * Class: org_rocksdb_Transaction - * Method: setSnapshotOnNextOperation - * Signature: (JJ)V - */ -void Java_org_rocksdb_Transaction_setSnapshotOnNextOperation__JJ( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jtxn_notifier_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* txn_notifier = reinterpret_cast< - std::shared_ptr*>( - jtxn_notifier_handle); - txn->SetSnapshotOnNextOperation(*txn_notifier); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getSnapshot - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getSnapshot(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - const ROCKSDB_NAMESPACE::Snapshot* snapshot = txn->GetSnapshot(); - return GET_CPLUSPLUS_POINTER(snapshot); -} - -/* - * Class: org_rocksdb_Transaction - * Method: clearSnapshot - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_clearSnapshot(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - txn->ClearSnapshot(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: prepare - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_prepare(JNIEnv* env, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::Status s = txn->Prepare(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Transaction - * Method: commit - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_commit(JNIEnv* env, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::Status s = txn->Commit(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Transaction - * Method: rollback - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_rollback(JNIEnv* env, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::Status s = txn->Rollback(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Transaction - * Method: setSavePoint - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_setSavePoint(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - txn->SetSavePoint(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: rollbackToSavePoint - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_rollbackToSavePoint(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::Status s = txn->RollbackToSavePoint(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -typedef std::function - FnGet; - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -jbyteArray txn_get_helper(JNIEnv* env, const FnGet& fn_get, - const jlong& jread_options_handle, - const jbyteArray& jkey, const jint& jkey_part_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - jkey_part_len); - - auto* read_options = - reinterpret_cast(jread_options_handle); - std::string value; - ROCKSDB_NAMESPACE::Status s = fn_get(*read_options, key_slice, &value); - - // trigger java unref on key. - // by passing JNI_ABORT, it will simply release the reference without - // copying the result back to the java byte array. - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - - if (s.IsNotFound()) { - return nullptr; - } - - if (s.ok()) { - jbyteArray jret_value = env->NewByteArray(static_cast(value.size())); - if (jret_value == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetByteArrayRegion( - jret_value, 0, static_cast(value.size()), - const_cast(reinterpret_cast(value.c_str()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return nullptr; - } - return jret_value; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; -} - -/* - * Class: org_rocksdb_Transaction - * Method: get - * Signature: (JJ[BIJ)[B - */ -jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jbyteArray jkey, jint jkey_part_len, jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnGet fn_get = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Get, txn, std::placeholders::_1, - column_family_handle, std::placeholders::_2, std::placeholders::_3); - return txn_get_helper(env, fn_get, jread_options_handle, jkey, jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: get - * Signature: (JJ[BI)[B - */ -jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jbyteArray jkey, jint jkey_part_len) { - auto* txn = reinterpret_cast(jhandle); - FnGet fn_get = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Get, txn, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3); - return txn_get_helper(env, fn_get, jread_options_handle, jkey, jkey_part_len); -} - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -// used by txn_multi_get_helper below -std::vector txn_column_families_helper( - JNIEnv* env, jlongArray jcolumn_family_handles, bool* has_exception) { - std::vector cf_handles; - if (jcolumn_family_handles != nullptr) { - const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); - if (len_cols > 0) { - jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); - if (jcfh == nullptr) { - // exception thrown: OutOfMemoryError - *has_exception = JNI_TRUE; - return std::vector(); - } - for (int i = 0; i < len_cols; i++) { - auto* cf_handle = - reinterpret_cast(jcfh[i]); - cf_handles.push_back(cf_handle); - } - env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); - } - } - return cf_handles; -} - -typedef std::function( - const ROCKSDB_NAMESPACE::ReadOptions&, - const std::vector&, std::vector*)> - FnMultiGet; - -void free_parts( - JNIEnv* env, - std::vector>& parts_to_free) { - for (auto& value : parts_to_free) { - jobject jk; - jbyteArray jk_ba; - jbyte* jk_val; - std::tie(jk_ba, jk_val, jk) = value; - env->ReleaseByteArrayElements(jk_ba, jk_val, JNI_ABORT); - env->DeleteLocalRef(jk); - } -} - -void free_key_values(std::vector& keys_to_free) { - for (auto& key : keys_to_free) { - delete[] key; - } -} - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -// cf multi get -jobjectArray txn_multi_get_helper(JNIEnv* env, const FnMultiGet& fn_multi_get, - const jlong& jread_options_handle, - const jobjectArray& jkey_parts) { - const jsize len_key_parts = env->GetArrayLength(jkey_parts); - - std::vector key_parts; - std::vector keys_to_free; - for (int i = 0; i < len_key_parts; i++) { - const jobject jk = env->GetObjectArrayElement(jkey_parts, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - free_key_values(keys_to_free); - return nullptr; - } - jbyteArray jk_ba = reinterpret_cast(jk); - const jsize len_key = env->GetArrayLength(jk_ba); - jbyte* jk_val = new jbyte[len_key]; - if (jk_val == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jk); - free_key_values(keys_to_free); - - jclass exception_cls = (env)->FindClass("java/lang/OutOfMemoryError"); - (env)->ThrowNew(exception_cls, - "Insufficient Memory for CF handle array."); - return nullptr; - } - env->GetByteArrayRegion(jk_ba, 0, len_key, jk_val); - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(jk_val), - len_key); - key_parts.push_back(key_slice); - keys_to_free.push_back(jk_val); - env->DeleteLocalRef(jk); - } - - auto* read_options = - reinterpret_cast(jread_options_handle); - std::vector value_parts; - std::vector s = - fn_multi_get(*read_options, key_parts, &value_parts); - - // free up allocated byte arrays - free_key_values(keys_to_free); - - // prepare the results - const jclass jcls_ba = env->FindClass("[B"); - jobjectArray jresults = - env->NewObjectArray(static_cast(s.size()), jcls_ba, nullptr); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - // add to the jresults - for (std::vector::size_type i = 0; i != s.size(); - i++) { - if (s[i].ok()) { - jbyteArray jentry_value = - env->NewByteArray(static_cast(value_parts[i].size())); - if (jentry_value == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetByteArrayRegion( - jentry_value, 0, static_cast(value_parts[i].size()), - const_cast( - reinterpret_cast(value_parts[i].c_str()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jentry_value); - return nullptr; - } - - env->SetObjectArrayElement(jresults, static_cast(i), jentry_value); - env->DeleteLocalRef(jentry_value); - } - } - - return jresults; -} - -/* - * Class: org_rocksdb_Transaction - * Method: multiGet - * Signature: (JJ[[B[J)[[B - */ -jobjectArray Java_org_rocksdb_Transaction_multiGet__JJ_3_3B_3J( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jobjectArray jkey_parts, jlongArray jcolumn_family_handles) { - bool has_exception = false; - const std::vector - column_family_handles = txn_column_families_helper( - env, jcolumn_family_handles, &has_exception); - if (has_exception) { - // exception thrown: OutOfMemoryError - return nullptr; - } - auto* txn = reinterpret_cast(jhandle); - FnMultiGet fn_multi_get = std::bind ( - ROCKSDB_NAMESPACE::Transaction::*)( - const ROCKSDB_NAMESPACE::ReadOptions&, - const std::vector&, - const std::vector&, std::vector*)>( - &ROCKSDB_NAMESPACE::Transaction::MultiGet, txn, std::placeholders::_1, - column_family_handles, std::placeholders::_2, std::placeholders::_3); - return txn_multi_get_helper(env, fn_multi_get, jread_options_handle, - jkey_parts); -} - -/* - * Class: org_rocksdb_Transaction - * Method: multiGet - * Signature: (JJ[[B)[[B - */ -jobjectArray Java_org_rocksdb_Transaction_multiGet__JJ_3_3B( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jobjectArray jkey_parts) { - auto* txn = reinterpret_cast(jhandle); - FnMultiGet fn_multi_get = std::bind ( - ROCKSDB_NAMESPACE::Transaction::*)( - const ROCKSDB_NAMESPACE::ReadOptions&, - const std::vector&, std::vector*)>( - &ROCKSDB_NAMESPACE::Transaction::MultiGet, txn, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3); - return txn_multi_get_helper(env, fn_multi_get, jread_options_handle, - jkey_parts); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getForUpdate - * Signature: (JJ[BIJZZ)[B - */ -jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIJZZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jbyteArray jkey, jint jkey_part_len, jlong jcolumn_family_handle, - jboolean jexclusive, jboolean jdo_validate) { - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - auto* txn = reinterpret_cast(jhandle); - FnGet fn_get_for_update = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::GetForUpdate, txn, - std::placeholders::_1, column_family_handle, std::placeholders::_2, - std::placeholders::_3, jexclusive, jdo_validate); - return txn_get_helper(env, fn_get_for_update, jread_options_handle, jkey, - jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getForUpdate - * Signature: (JJ[BIZZ)[B - */ -jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIZZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jbyteArray jkey, jint jkey_part_len, jboolean jexclusive, - jboolean jdo_validate) { - auto* txn = reinterpret_cast(jhandle); - FnGet fn_get_for_update = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::GetForUpdate, txn, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, - jexclusive, jdo_validate); - return txn_get_helper(env, fn_get_for_update, jread_options_handle, jkey, - jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: multiGetForUpdate - * Signature: (JJ[[B[J)[[B - */ -jobjectArray Java_org_rocksdb_Transaction_multiGetForUpdate__JJ_3_3B_3J( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jobjectArray jkey_parts, jlongArray jcolumn_family_handles) { - bool has_exception = false; - const std::vector - column_family_handles = txn_column_families_helper( - env, jcolumn_family_handles, &has_exception); - if (has_exception) { - // exception thrown: OutOfMemoryError - return nullptr; - } - auto* txn = reinterpret_cast(jhandle); - FnMultiGet fn_multi_get_for_update = std::bind (ROCKSDB_NAMESPACE::Transaction::*)( - const ROCKSDB_NAMESPACE::ReadOptions&, - const std::vector&, - const std::vector&, std::vector*)>( - &ROCKSDB_NAMESPACE::Transaction::MultiGetForUpdate, txn, - std::placeholders::_1, column_family_handles, std::placeholders::_2, - std::placeholders::_3); - return txn_multi_get_helper(env, fn_multi_get_for_update, - jread_options_handle, jkey_parts); -} - -/* - * Class: org_rocksdb_Transaction - * Method: multiGetForUpdate - * Signature: (JJ[[B)[[B - */ -jobjectArray Java_org_rocksdb_Transaction_multiGetForUpdate__JJ_3_3B( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, - jobjectArray jkey_parts) { - auto* txn = reinterpret_cast(jhandle); - FnMultiGet fn_multi_get_for_update = std::bind (ROCKSDB_NAMESPACE::Transaction::*)( - const ROCKSDB_NAMESPACE::ReadOptions&, - const std::vector&, std::vector*)>( - &ROCKSDB_NAMESPACE::Transaction::MultiGetForUpdate, txn, - std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); - return txn_multi_get_helper(env, fn_multi_get_for_update, - jread_options_handle, jkey_parts); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getIterator - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_Transaction_getIterator__JJ(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jread_options_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* read_options = - reinterpret_cast(jread_options_handle); - return GET_CPLUSPLUS_POINTER(txn->GetIterator(*read_options)); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getIterator - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_Transaction_getIterator__JJJ( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jread_options_handle, jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* read_options = - reinterpret_cast(jread_options_handle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - return GET_CPLUSPLUS_POINTER( - txn->GetIterator(*read_options, column_family_handle)); -} - -typedef std::function - FnWriteKV; - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -void txn_write_kv_helper(JNIEnv* env, const FnWriteKV& fn_write_kv, - const jbyteArray& jkey, const jint& jkey_part_len, - const jbyteArray& jval, const jint& jval_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - jbyte* value = env->GetByteArrayElements(jval, nullptr); - if (value == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - return; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - jkey_part_len); - ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast(value), - jval_len); - - ROCKSDB_NAMESPACE::Status s = fn_write_kv(key_slice, value_slice); - - // trigger java unref on key. - // by passing JNI_ABORT, it will simply release the reference without - // copying the result back to the java byte array. - env->ReleaseByteArrayElements(jval, value, JNI_ABORT); - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_Transaction - * Method: put - * Signature: (J[BI[BIJZ)V - */ -void Java_org_rocksdb_Transaction_put__J_3BI_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len, - jlong jcolumn_family_handle, jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKV fn_put = - std::bind(&ROCKSDB_NAMESPACE::Transaction::Put, txn, - column_family_handle, std::placeholders::_1, - std::placeholders::_2, jassume_tracked); - txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: put - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_Transaction_put__J_3BI_3BI(JNIEnv* env, jobject /*jobj*/, - jlong jhandle, jbyteArray jkey, - jint jkey_part_len, - jbyteArray jval, - jint jval_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKV fn_put = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Put, txn, std::placeholders::_1, - std::placeholders::_2); - txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len); -} - -typedef std::function - FnWriteKVParts; - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -void txn_write_kv_parts_helper(JNIEnv* env, - const FnWriteKVParts& fn_write_kv_parts, - const jobjectArray& jkey_parts, - const jint& jkey_parts_len, - const jobjectArray& jvalue_parts, - const jint& jvalue_parts_len) { -#ifndef DEBUG - (void)jvalue_parts_len; -#else - assert(jkey_parts_len == jvalue_parts_len); -#endif - - auto key_parts = std::vector(); - auto value_parts = std::vector(); - auto jparts_to_free = std::vector>(); - - // Since this is fundamentally a gather write at the RocksDB level, - // it seems wrong to refactor it by copying (gathering) keys and data here, - // in order to avoid the local reference limit. - // The user needs to be a aware that there is a limit to the number of parts - // which can be gathered. - if (env->EnsureLocalCapacity(jkey_parts_len + jvalue_parts_len) != 0) { - // no space for all the jobjects we store up - env->ExceptionClear(); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( - env, "Insufficient JNI local references for " + - std::to_string(jkey_parts_len) + " key/value parts"); - return; - } - - // convert java key_parts/value_parts byte[][] to Slice(s) - for (jsize i = 0; i < jkey_parts_len; ++i) { - const jobject jobj_key_part = env->GetObjectArrayElement(jkey_parts, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - free_parts(env, jparts_to_free); - return; - } - const jobject jobj_value_part = env->GetObjectArrayElement(jvalue_parts, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jobj_key_part); - free_parts(env, jparts_to_free); - return; - } - - const jbyteArray jba_key_part = reinterpret_cast(jobj_key_part); - const jsize jkey_part_len = env->GetArrayLength(jba_key_part); - jbyte* jkey_part = env->GetByteArrayElements(jba_key_part, nullptr); - if (jkey_part == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jobj_value_part); - env->DeleteLocalRef(jobj_key_part); - free_parts(env, jparts_to_free); - return; - } - - const jbyteArray jba_value_part = - reinterpret_cast(jobj_value_part); - const jsize jvalue_part_len = env->GetArrayLength(jba_value_part); - jbyte* jvalue_part = env->GetByteArrayElements(jba_value_part, nullptr); - if (jvalue_part == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jobj_value_part); - env->DeleteLocalRef(jobj_key_part); - env->ReleaseByteArrayElements(jba_key_part, jkey_part, JNI_ABORT); - free_parts(env, jparts_to_free); - return; - } - - jparts_to_free.push_back( - std::make_tuple(jba_key_part, jkey_part, jobj_key_part)); - jparts_to_free.push_back( - std::make_tuple(jba_value_part, jvalue_part, jobj_value_part)); - - key_parts.push_back(ROCKSDB_NAMESPACE::Slice( - reinterpret_cast(jkey_part), jkey_part_len)); - value_parts.push_back(ROCKSDB_NAMESPACE::Slice( - reinterpret_cast(jvalue_part), jvalue_part_len)); - } - - // call the write_multi function - ROCKSDB_NAMESPACE::Status s = fn_write_kv_parts( - ROCKSDB_NAMESPACE::SliceParts(key_parts.data(), (int)key_parts.size()), - ROCKSDB_NAMESPACE::SliceParts(value_parts.data(), - (int)value_parts.size())); - - // cleanup temporary memory - free_parts(env, jparts_to_free); - - // return - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_Transaction - * Method: put - * Signature: (J[[BI[[BIJZ)V - */ -void Java_org_rocksdb_Transaction_put__J_3_3BI_3_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len, - jlong jcolumn_family_handle, jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKVParts fn_put_parts = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Put, txn, column_family_handle, - std::placeholders::_1, std::placeholders::_2, jassume_tracked); - txn_write_kv_parts_helper(env, fn_put_parts, jkey_parts, jkey_parts_len, - jvalue_parts, jvalue_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: put - * Signature: (J[[BI[[BI)V - */ -void Java_org_rocksdb_Transaction_put__J_3_3BI_3_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKVParts fn_put_parts = std::bind( - &ROCKSDB_NAMESPACE::Transaction::Put, txn, std::placeholders::_1, - std::placeholders::_2); - txn_write_kv_parts_helper(env, fn_put_parts, jkey_parts, jkey_parts_len, - jvalue_parts, jvalue_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: merge - * Signature: (J[BI[BIJZ)V - */ -void Java_org_rocksdb_Transaction_merge__J_3BI_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len, - jlong jcolumn_family_handle, jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKV fn_merge = - std::bind(&ROCKSDB_NAMESPACE::Transaction::Merge, txn, - column_family_handle, std::placeholders::_1, - std::placeholders::_2, jassume_tracked); - txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: merge - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_Transaction_merge__J_3BI_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKV fn_merge = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Merge, txn, std::placeholders::_1, - std::placeholders::_2); - txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len); -} - -typedef std::function - FnWriteK; - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -void txn_write_k_helper(JNIEnv* env, const FnWriteK& fn_write_k, - const jbyteArray& jkey, const jint& jkey_part_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - jkey_part_len); - - ROCKSDB_NAMESPACE::Status s = fn_write_k(key_slice); - - // trigger java unref on key. - // by passing JNI_ABORT, it will simply release the reference without - // copying the result back to the java byte array. - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_Transaction - * Method: delete - * Signature: (J[BIJZ)V - */ -void Java_org_rocksdb_Transaction_delete__J_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jlong jcolumn_family_handle, jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteK fn_delete = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Delete, txn, column_family_handle, - std::placeholders::_1, jassume_tracked); - txn_write_k_helper(env, fn_delete, jkey, jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: delete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_Transaction_delete__J_3BI(JNIEnv* env, jobject /*jobj*/, - jlong jhandle, jbyteArray jkey, - jint jkey_part_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteK fn_delete = std::bind( - &ROCKSDB_NAMESPACE::Transaction::Delete, txn, std::placeholders::_1); - txn_write_k_helper(env, fn_delete, jkey, jkey_part_len); -} - -typedef std::function - FnWriteKParts; - -// TODO(AR) consider refactoring to share this between here and rocksjni.cc -void txn_write_k_parts_helper(JNIEnv* env, - const FnWriteKParts& fn_write_k_parts, - const jobjectArray& jkey_parts, - const jint& jkey_parts_len) { - std::vector key_parts; - std::vector> jkey_parts_to_free; - - // convert java key_parts byte[][] to Slice(s) - for (jint i = 0; i < jkey_parts_len; ++i) { - const jobject jobj_key_part = env->GetObjectArrayElement(jkey_parts, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - free_parts(env, jkey_parts_to_free); - return; - } - - const jbyteArray jba_key_part = reinterpret_cast(jobj_key_part); - const jsize jkey_part_len = env->GetArrayLength(jba_key_part); - jbyte* jkey_part = env->GetByteArrayElements(jba_key_part, nullptr); - if (jkey_part == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jobj_key_part); - free_parts(env, jkey_parts_to_free); - return; - } - - jkey_parts_to_free.push_back(std::tuple( - jba_key_part, jkey_part, jobj_key_part)); - - key_parts.push_back(ROCKSDB_NAMESPACE::Slice( - reinterpret_cast(jkey_part), jkey_part_len)); - } - - // call the write_multi function - ROCKSDB_NAMESPACE::Status s = fn_write_k_parts( - ROCKSDB_NAMESPACE::SliceParts(key_parts.data(), (int)key_parts.size())); - - // cleanup temporary memory - free_parts(env, jkey_parts_to_free); - - // return - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_Transaction - * Method: delete - * Signature: (J[[BIJZ)V - */ -void Java_org_rocksdb_Transaction_delete__J_3_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jlong jcolumn_family_handle, - jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKParts fn_delete_parts = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::Delete, txn, column_family_handle, - std::placeholders::_1, jassume_tracked); - txn_write_k_parts_helper(env, fn_delete_parts, jkey_parts, jkey_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: delete - * Signature: (J[[BI)V - */ -void Java_org_rocksdb_Transaction_delete__J_3_3BI(JNIEnv* env, jobject /*jobj*/, - jlong jhandle, - jobjectArray jkey_parts, - jint jkey_parts_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKParts fn_delete_parts = std::bind( - &ROCKSDB_NAMESPACE::Transaction::Delete, txn, std::placeholders::_1); - txn_write_k_parts_helper(env, fn_delete_parts, jkey_parts, jkey_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: singleDelete - * Signature: (J[BIJZ)V - */ -void Java_org_rocksdb_Transaction_singleDelete__J_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jlong jcolumn_family_handle, jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteK fn_single_delete = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn, - column_family_handle, std::placeholders::_1, jassume_tracked); - txn_write_k_helper(env, fn_single_delete, jkey, jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: singleDelete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_Transaction_singleDelete__J_3BI(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle, - jbyteArray jkey, - jint jkey_part_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteK fn_single_delete = std::bind( - &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn, - std::placeholders::_1); - txn_write_k_helper(env, fn_single_delete, jkey, jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: singleDelete - * Signature: (J[[BIJZ)V - */ -void Java_org_rocksdb_Transaction_singleDelete__J_3_3BIJZ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jlong jcolumn_family_handle, - jboolean jassume_tracked) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKParts fn_single_delete_parts = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn, - column_family_handle, std::placeholders::_1, jassume_tracked); - txn_write_k_parts_helper(env, fn_single_delete_parts, jkey_parts, - jkey_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: singleDelete - * Signature: (J[[BI)V - */ -void Java_org_rocksdb_Transaction_singleDelete__J_3_3BI(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle, - jobjectArray jkey_parts, - jint jkey_parts_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKParts fn_single_delete_parts = std::bind( - &ROCKSDB_NAMESPACE::Transaction::SingleDelete, txn, - std::placeholders::_1); - txn_write_k_parts_helper(env, fn_single_delete_parts, jkey_parts, - jkey_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: putUntracked - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_Transaction_putUntracked__J_3BI_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len, - jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKV fn_put_untracked = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, - column_family_handle, std::placeholders::_1, std::placeholders::_2); - txn_write_kv_helper(env, fn_put_untracked, jkey, jkey_part_len, jval, - jval_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: putUntracked - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_Transaction_putUntracked__J_3BI_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKV fn_put_untracked = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, - std::placeholders::_1, std::placeholders::_2); - txn_write_kv_helper(env, fn_put_untracked, jkey, jkey_part_len, jval, - jval_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: putUntracked - * Signature: (J[[BI[[BIJ)V - */ -void Java_org_rocksdb_Transaction_putUntracked__J_3_3BI_3_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len, - jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKVParts fn_put_parts_untracked = std::bind( - &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, column_family_handle, - std::placeholders::_1, std::placeholders::_2); - txn_write_kv_parts_helper(env, fn_put_parts_untracked, jkey_parts, - jkey_parts_len, jvalue_parts, jvalue_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: putUntracked - * Signature: (J[[BI[[BI)V - */ -void Java_org_rocksdb_Transaction_putUntracked__J_3_3BI_3_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKVParts fn_put_parts_untracked = std::bind( - &ROCKSDB_NAMESPACE::Transaction::PutUntracked, txn, std::placeholders::_1, - std::placeholders::_2); - txn_write_kv_parts_helper(env, fn_put_parts_untracked, jkey_parts, - jkey_parts_len, jvalue_parts, jvalue_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: mergeUntracked - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_Transaction_mergeUntracked__J_3BI_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len, - jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKV fn_merge_untracked = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::MergeUntracked, txn, - column_family_handle, std::placeholders::_1, std::placeholders::_2); - txn_write_kv_helper(env, fn_merge_untracked, jkey, jkey_part_len, jval, - jval_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: mergeUntracked - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_Transaction_mergeUntracked__J_3BI_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jbyteArray jval, jint jval_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKV fn_merge_untracked = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::MergeUntracked, txn, - std::placeholders::_1, std::placeholders::_2); - txn_write_kv_helper(env, fn_merge_untracked, jkey, jkey_part_len, jval, - jval_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: deleteUntracked - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_Transaction_deleteUntracked__J_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteK fn_delete_untracked = std::bind( - &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn, - column_family_handle, std::placeholders::_1); - txn_write_k_helper(env, fn_delete_untracked, jkey, jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: deleteUntracked - * Signature: (J[BI)V - */ -void Java_org_rocksdb_Transaction_deleteUntracked__J_3BI(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle, - jbyteArray jkey, - jint jkey_part_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteK fn_delete_untracked = std::bind( - &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn, - std::placeholders::_1); - txn_write_k_helper(env, fn_delete_untracked, jkey, jkey_part_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: deleteUntracked - * Signature: (J[[BIJ)V - */ -void Java_org_rocksdb_Transaction_deleteUntracked__J_3_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len, jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - FnWriteKParts fn_delete_untracked_parts = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn, - column_family_handle, std::placeholders::_1); - txn_write_k_parts_helper(env, fn_delete_untracked_parts, jkey_parts, - jkey_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: deleteUntracked - * Signature: (J[[BI)V - */ -void Java_org_rocksdb_Transaction_deleteUntracked__J_3_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, - jint jkey_parts_len) { - auto* txn = reinterpret_cast(jhandle); - FnWriteKParts fn_delete_untracked_parts = - std::bind( - &ROCKSDB_NAMESPACE::Transaction::DeleteUntracked, txn, - std::placeholders::_1); - txn_write_k_parts_helper(env, fn_delete_untracked_parts, jkey_parts, - jkey_parts_len); -} - -/* - * Class: org_rocksdb_Transaction - * Method: putLogData - * Signature: (J[BI)V - */ -void Java_org_rocksdb_Transaction_putLogData(JNIEnv* env, jobject /*jobj*/, - jlong jhandle, jbyteArray jkey, - jint jkey_part_len) { - auto* txn = reinterpret_cast(jhandle); - - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - jkey_part_len); - txn->PutLogData(key_slice); - - // trigger java unref on key. - // by passing JNI_ABORT, it will simply release the reference without - // copying the result back to the java byte array. - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); -} - -/* - * Class: org_rocksdb_Transaction - * Method: disableIndexing - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_disableIndexing(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - txn->DisableIndexing(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: enableIndexing - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_enableIndexing(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - txn->EnableIndexing(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getNumKeys - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getNumKeys(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return txn->GetNumKeys(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getNumPuts - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getNumPuts(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return txn->GetNumPuts(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getNumDeletes - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getNumDeletes(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return txn->GetNumDeletes(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getNumMerges - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getNumMerges(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return txn->GetNumMerges(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getElapsedTime - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getElapsedTime(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return txn->GetElapsedTime(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getWriteBatch - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getWriteBatch(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return GET_CPLUSPLUS_POINTER(txn->GetWriteBatch()); -} - -/* - * Class: org_rocksdb_Transaction - * Method: setLockTimeout - * Signature: (JJ)V - */ -void Java_org_rocksdb_Transaction_setLockTimeout(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jlock_timeout) { - auto* txn = reinterpret_cast(jhandle); - txn->SetLockTimeout(jlock_timeout); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getWriteOptions - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getWriteOptions(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return GET_CPLUSPLUS_POINTER(txn->GetWriteOptions()); -} - -/* - * Class: org_rocksdb_Transaction - * Method: setWriteOptions - * Signature: (JJ)V - */ -void Java_org_rocksdb_Transaction_setWriteOptions(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jwrite_options_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - txn->SetWriteOptions(*write_options); -} - -/* - * Class: org_rocksdb_Transaction - * Method: undo - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_Transaction_undoGetForUpdate__J_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, - jint jkey_part_len, jlong jcolumn_family_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* column_family_handle = - reinterpret_cast( - jcolumn_family_handle); - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - jkey_part_len); - txn->UndoGetForUpdate(column_family_handle, key_slice); - - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); -} - -/* - * Class: org_rocksdb_Transaction - * Method: undoGetForUpdate - * Signature: (J[BI)V - */ -void Java_org_rocksdb_Transaction_undoGetForUpdate__J_3BI(JNIEnv* env, - jobject /*jobj*/, - jlong jhandle, - jbyteArray jkey, - jint jkey_part_len) { - auto* txn = reinterpret_cast(jhandle); - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if (key == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast(key), - jkey_part_len); - txn->UndoGetForUpdate(key_slice); - - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); -} - -/* - * Class: org_rocksdb_Transaction - * Method: rebuildFromWriteBatch - * Signature: (JJ)V - */ -void Java_org_rocksdb_Transaction_rebuildFromWriteBatch( - JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jwrite_batch_handle) { - auto* txn = reinterpret_cast(jhandle); - auto* write_batch = - reinterpret_cast(jwrite_batch_handle); - ROCKSDB_NAMESPACE::Status s = txn->RebuildFromWriteBatch(write_batch); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Transaction - * Method: getCommitTimeWriteBatch - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getCommitTimeWriteBatch(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return GET_CPLUSPLUS_POINTER(txn->GetCommitTimeWriteBatch()); -} - -/* - * Class: org_rocksdb_Transaction - * Method: setLogNumber - * Signature: (JJ)V - */ -void Java_org_rocksdb_Transaction_setLogNumber(JNIEnv* /*env*/, - jobject /*jobj*/, jlong jhandle, - jlong jlog_number) { - auto* txn = reinterpret_cast(jhandle); - txn->SetLogNumber(jlog_number); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getLogNumber - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getLogNumber(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return txn->GetLogNumber(); -} - -/* - * Class: org_rocksdb_Transaction - * Method: setName - * Signature: (JLjava/lang/String;)V - */ -void Java_org_rocksdb_Transaction_setName(JNIEnv* env, jobject /*jobj*/, - jlong jhandle, jstring jname) { - auto* txn = reinterpret_cast(jhandle); - const char* name = env->GetStringUTFChars(jname, nullptr); - if (name == nullptr) { - // exception thrown: OutOfMemoryError - return; - } - - ROCKSDB_NAMESPACE::Status s = txn->SetName(name); - - env->ReleaseStringUTFChars(jname, name); - - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_Transaction - * Method: getName - * Signature: (J)Ljava/lang/String; - */ -jstring Java_org_rocksdb_Transaction_getName(JNIEnv* env, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::TransactionName name = txn->GetName(); - return env->NewStringUTF(name.data()); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getID - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getID(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::TransactionID id = txn->GetID(); - return static_cast(id); -} - -/* - * Class: org_rocksdb_Transaction - * Method: isDeadlockDetect - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_Transaction_isDeadlockDetect(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - return static_cast(txn->IsDeadlockDetect()); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getWaitingTxns - * Signature: (J)Lorg/rocksdb/Transaction/WaitingTransactions; - */ -jobject Java_org_rocksdb_Transaction_getWaitingTxns(JNIEnv* env, - jobject jtransaction_obj, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - uint32_t column_family_id; - std::string key; - std::vector waiting_txns = - txn->GetWaitingTxns(&column_family_id, &key); - jobject jwaiting_txns = - ROCKSDB_NAMESPACE::TransactionJni::newWaitingTransactions( - env, jtransaction_obj, column_family_id, key, waiting_txns); - return jwaiting_txns; -} - -/* - * Class: org_rocksdb_Transaction - * Method: getState - * Signature: (J)B - */ -jbyte Java_org_rocksdb_Transaction_getState(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - ROCKSDB_NAMESPACE::Transaction::TransactionState txn_status = txn->GetState(); - switch (txn_status) { - case ROCKSDB_NAMESPACE::Transaction::TransactionState::STARTED: - return 0x0; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::AWAITING_PREPARE: - return 0x1; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::PREPARED: - return 0x2; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::AWAITING_COMMIT: - return 0x3; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::COMMITTED: - return 0x4; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::AWAITING_ROLLBACK: - return 0x5; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::ROLLEDBACK: - return 0x6; - - case ROCKSDB_NAMESPACE::Transaction::TransactionState::LOCKS_STOLEN: - return 0x7; - } - - assert(false); - return static_cast(-1); -} - -/* - * Class: org_rocksdb_Transaction - * Method: getId - * Signature: (J)J - */ -jlong Java_org_rocksdb_Transaction_getId(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jhandle) { - auto* txn = reinterpret_cast(jhandle); - uint64_t id = txn->GetId(); - return static_cast(id); -} - -/* - * Class: org_rocksdb_Transaction - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_Transaction_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} diff --git a/java/rocksjni/transaction_db.cc b/java/rocksjni/transaction_db.cc deleted file mode 100644 index 0adf85606..000000000 --- a/java/rocksjni/transaction_db.cc +++ /dev/null @@ -1,451 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::TransactionDB. - -#include "rocksdb/utilities/transaction_db.h" - -#include - -#include -#include -#include - -#include "include/org_rocksdb_TransactionDB.h" -#include "rocksdb/options.h" -#include "rocksdb/utilities/transaction.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_TransactionDB - * Method: open - * Signature: (JJLjava/lang/String;)J - */ -jlong Java_org_rocksdb_TransactionDB_open__JJLjava_lang_String_2( - JNIEnv* env, jclass, jlong joptions_handle, jlong jtxn_db_options_handle, - jstring jdb_path) { - auto* options = - reinterpret_cast(joptions_handle); - auto* txn_db_options = - reinterpret_cast( - jtxn_db_options_handle); - ROCKSDB_NAMESPACE::TransactionDB* tdb = nullptr; - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::TransactionDB::Open( - *options, *txn_db_options, db_path, &tdb); - env->ReleaseStringUTFChars(jdb_path, db_path); - - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(tdb); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; - } -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: open - * Signature: (JJLjava/lang/String;[[B[J)[J - */ -jlongArray Java_org_rocksdb_TransactionDB_open__JJLjava_lang_String_2_3_3B_3J( - JNIEnv* env, jclass, jlong jdb_options_handle, jlong jtxn_db_options_handle, - jstring jdb_path, jobjectArray jcolumn_names, - jlongArray jcolumn_options_handles) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - const jsize len_cols = env->GetArrayLength(jcolumn_names); - jlong* jco = env->GetLongArrayElements(jcolumn_options_handles, nullptr); - if (jco == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - std::vector column_families; - for (int i = 0; i < len_cols; i++) { - const jobject jcn = env->GetObjectArrayElement(jcolumn_names, i); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - const jbyteArray jcn_ba = reinterpret_cast(jcn); - jbyte* jcf_name = env->GetByteArrayElements(jcn_ba, nullptr); - if (jcf_name == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jcn); - env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - const int jcf_name_len = env->GetArrayLength(jcn_ba); - const std::string cf_name(reinterpret_cast(jcf_name), jcf_name_len); - const ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options = - reinterpret_cast(jco[i]); - column_families.push_back( - ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(cf_name, *cf_options)); - - env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT); - env->DeleteLocalRef(jcn); - } - env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); - - auto* db_options = - reinterpret_cast(jdb_options_handle); - auto* txn_db_options = - reinterpret_cast( - jtxn_db_options_handle); - std::vector handles; - ROCKSDB_NAMESPACE::TransactionDB* tdb = nullptr; - const ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::TransactionDB::Open( - *db_options, *txn_db_options, db_path, column_families, &handles, &tdb); - - // check if open operation was successful - if (s.ok()) { - const jsize resultsLen = 1 + len_cols; // db handle + column family handles - std::unique_ptr results = - std::unique_ptr(new jlong[resultsLen]); - results[0] = GET_CPLUSPLUS_POINTER(tdb); - for (int i = 1; i <= len_cols; i++) { - results[i] = GET_CPLUSPLUS_POINTER(handles[i - 1]); - } - - jlongArray jresults = env->NewLongArray(resultsLen); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresults); - return nullptr; - } - return jresults; - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; - } -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionDB_disposeInternal(JNIEnv*, jobject, - jlong jhandle) { - auto* txn_db = reinterpret_cast(jhandle); - assert(txn_db != nullptr); - delete txn_db; -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: closeDatabase - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionDB_closeDatabase(JNIEnv* env, jclass, - jlong jhandle) { - auto* txn_db = reinterpret_cast(jhandle); - assert(txn_db != nullptr); - ROCKSDB_NAMESPACE::Status s = txn_db->Close(); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: beginTransaction - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_TransactionDB_beginTransaction__JJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle) { - auto* txn_db = reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - ROCKSDB_NAMESPACE::Transaction* txn = - txn_db->BeginTransaction(*write_options); - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: beginTransaction - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_TransactionDB_beginTransaction__JJJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, - jlong jtxn_options_handle) { - auto* txn_db = reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* txn_options = reinterpret_cast( - jtxn_options_handle); - ROCKSDB_NAMESPACE::Transaction* txn = - txn_db->BeginTransaction(*write_options, *txn_options); - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: beginTransaction_withOld - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_TransactionDB_beginTransaction_1withOld__JJJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, - jlong jold_txn_handle) { - auto* txn_db = reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* old_txn = - reinterpret_cast(jold_txn_handle); - ROCKSDB_NAMESPACE::TransactionOptions txn_options; - ROCKSDB_NAMESPACE::Transaction* txn = - txn_db->BeginTransaction(*write_options, txn_options, old_txn); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_txn - assert(txn == old_txn); - - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: beginTransaction_withOld - * Signature: (JJJJ)J - */ -jlong Java_org_rocksdb_TransactionDB_beginTransaction_1withOld__JJJJ( - JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, - jlong jtxn_options_handle, jlong jold_txn_handle) { - auto* txn_db = reinterpret_cast(jhandle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* txn_options = reinterpret_cast( - jtxn_options_handle); - auto* old_txn = - reinterpret_cast(jold_txn_handle); - ROCKSDB_NAMESPACE::Transaction* txn = - txn_db->BeginTransaction(*write_options, *txn_options, old_txn); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_txn - assert(txn == old_txn); - - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: getTransactionByName - * Signature: (JLjava/lang/String;)J - */ -jlong Java_org_rocksdb_TransactionDB_getTransactionByName(JNIEnv* env, jobject, - jlong jhandle, - jstring jname) { - auto* txn_db = reinterpret_cast(jhandle); - const char* name = env->GetStringUTFChars(jname, nullptr); - if (name == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - ROCKSDB_NAMESPACE::Transaction* txn = txn_db->GetTransactionByName(name); - env->ReleaseStringUTFChars(jname, name); - return GET_CPLUSPLUS_POINTER(txn); -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: getAllPreparedTransactions - * Signature: (J)[J - */ -jlongArray Java_org_rocksdb_TransactionDB_getAllPreparedTransactions( - JNIEnv* env, jobject, jlong jhandle) { - auto* txn_db = reinterpret_cast(jhandle); - std::vector txns; - txn_db->GetAllPreparedTransactions(&txns); - - const size_t size = txns.size(); - assert(size < UINT32_MAX); // does it fit in a jint? - - const jsize len = static_cast(size); - std::vector tmp(len); - for (jsize i = 0; i < len; ++i) { - tmp[i] = GET_CPLUSPLUS_POINTER(txns[i]); - } - - jlongArray jtxns = env->NewLongArray(len); - if (jtxns == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - env->SetLongArrayRegion(jtxns, 0, len, tmp.data()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jtxns); - return nullptr; - } - - return jtxns; -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: getLockStatusData - * Signature: (J)Ljava/util/Map; - */ -jobject Java_org_rocksdb_TransactionDB_getLockStatusData(JNIEnv* env, jobject, - jlong jhandle) { - auto* txn_db = reinterpret_cast(jhandle); - const std::unordered_multimap - lock_status_data = txn_db->GetLockStatusData(); - const jobject jlock_status_data = ROCKSDB_NAMESPACE::HashMapJni::construct( - env, static_cast(lock_status_data.size())); - if (jlock_status_data == nullptr) { - // exception occurred - return nullptr; - } - - const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV< - const int32_t, const ROCKSDB_NAMESPACE::KeyLockInfo, jobject, jobject> - fn_map_kv = - [env](const std::pair& pair) { - const jobject jlong_column_family_id = - ROCKSDB_NAMESPACE::LongJni::valueOf(env, pair.first); - if (jlong_column_family_id == nullptr) { - // an error occurred - return std::unique_ptr>(nullptr); - } - const jobject jkey_lock_info = - ROCKSDB_NAMESPACE::KeyLockInfoJni::construct(env, pair.second); - if (jkey_lock_info == nullptr) { - // an error occurred - return std::unique_ptr>(nullptr); - } - return std::unique_ptr>( - new std::pair(jlong_column_family_id, - jkey_lock_info)); - }; - - if (!ROCKSDB_NAMESPACE::HashMapJni::putAll( - env, jlock_status_data, lock_status_data.begin(), - lock_status_data.end(), fn_map_kv)) { - // exception occcurred - return nullptr; - } - - return jlock_status_data; -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: getDeadlockInfoBuffer - * Signature: (J)[Lorg/rocksdb/TransactionDB/DeadlockPath; - */ -jobjectArray Java_org_rocksdb_TransactionDB_getDeadlockInfoBuffer( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto* txn_db = reinterpret_cast(jhandle); - const std::vector deadlock_info_buffer = - txn_db->GetDeadlockInfoBuffer(); - - const jsize deadlock_info_buffer_len = - static_cast(deadlock_info_buffer.size()); - jobjectArray jdeadlock_info_buffer = env->NewObjectArray( - deadlock_info_buffer_len, - ROCKSDB_NAMESPACE::DeadlockPathJni::getJClass(env), nullptr); - if (jdeadlock_info_buffer == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - jsize jdeadlock_info_buffer_offset = 0; - - auto buf_end = deadlock_info_buffer.end(); - for (auto buf_it = deadlock_info_buffer.begin(); buf_it != buf_end; - ++buf_it) { - const ROCKSDB_NAMESPACE::DeadlockPath deadlock_path = *buf_it; - const std::vector deadlock_infos = - deadlock_path.path; - const jsize deadlock_infos_len = - static_cast(deadlock_info_buffer.size()); - jobjectArray jdeadlock_infos = env->NewObjectArray( - deadlock_infos_len, ROCKSDB_NAMESPACE::DeadlockInfoJni::getJClass(env), - nullptr); - if (jdeadlock_infos == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jdeadlock_info_buffer); - return nullptr; - } - jsize jdeadlock_infos_offset = 0; - - auto infos_end = deadlock_infos.end(); - for (auto infos_it = deadlock_infos.begin(); infos_it != infos_end; - ++infos_it) { - const ROCKSDB_NAMESPACE::DeadlockInfo deadlock_info = *infos_it; - const jobject jdeadlock_info = - ROCKSDB_NAMESPACE::TransactionDBJni::newDeadlockInfo( - env, jobj, deadlock_info.m_txn_id, deadlock_info.m_cf_id, - deadlock_info.m_waiting_key, deadlock_info.m_exclusive); - if (jdeadlock_info == nullptr) { - // exception occcurred - env->DeleteLocalRef(jdeadlock_info_buffer); - return nullptr; - } - env->SetObjectArrayElement(jdeadlock_infos, jdeadlock_infos_offset++, - jdeadlock_info); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException or - // ArrayStoreException - env->DeleteLocalRef(jdeadlock_info); - env->DeleteLocalRef(jdeadlock_info_buffer); - return nullptr; - } - } - - const jobject jdeadlock_path = - ROCKSDB_NAMESPACE::DeadlockPathJni::construct( - env, jdeadlock_infos, deadlock_path.limit_exceeded); - if (jdeadlock_path == nullptr) { - // exception occcurred - env->DeleteLocalRef(jdeadlock_info_buffer); - return nullptr; - } - env->SetObjectArrayElement(jdeadlock_info_buffer, - jdeadlock_info_buffer_offset++, jdeadlock_path); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException or ArrayStoreException - env->DeleteLocalRef(jdeadlock_path); - env->DeleteLocalRef(jdeadlock_info_buffer); - return nullptr; - } - } - - return jdeadlock_info_buffer; -} - -/* - * Class: org_rocksdb_TransactionDB - * Method: setDeadlockInfoBufferSize - * Signature: (JI)V - */ -void Java_org_rocksdb_TransactionDB_setDeadlockInfoBufferSize( - JNIEnv*, jobject, jlong jhandle, jint jdeadlock_info_buffer_size) { - auto* txn_db = reinterpret_cast(jhandle); - txn_db->SetDeadlockInfoBufferSize(jdeadlock_info_buffer_size); -} diff --git a/java/rocksjni/transaction_db_options.cc b/java/rocksjni/transaction_db_options.cc deleted file mode 100644 index 4cf27121e..000000000 --- a/java/rocksjni/transaction_db_options.cc +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::TransactionDBOptions. - -#include - -#include "include/org_rocksdb_TransactionDBOptions.h" -#include "rocksdb/utilities/transaction_db.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: newTransactionDBOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_TransactionDBOptions_newTransactionDBOptions( - JNIEnv* /*env*/, jclass /*jcls*/) { - ROCKSDB_NAMESPACE::TransactionDBOptions* opts = - new ROCKSDB_NAMESPACE::TransactionDBOptions(); - return GET_CPLUSPLUS_POINTER(opts); -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: getMaxNumLocks - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionDBOptions_getMaxNumLocks(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->max_num_locks; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: setMaxNumLocks - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionDBOptions_setMaxNumLocks( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jlong jmax_num_locks) { - auto* opts = - reinterpret_cast(jhandle); - opts->max_num_locks = jmax_num_locks; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: getNumStripes - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionDBOptions_getNumStripes(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->num_stripes; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: setNumStripes - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionDBOptions_setNumStripes(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jnum_stripes) { - auto* opts = - reinterpret_cast(jhandle); - opts->num_stripes = jnum_stripes; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: getTransactionLockTimeout - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionDBOptions_getTransactionLockTimeout( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->transaction_lock_timeout; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: setTransactionLockTimeout - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionDBOptions_setTransactionLockTimeout( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jtransaction_lock_timeout) { - auto* opts = - reinterpret_cast(jhandle); - opts->transaction_lock_timeout = jtransaction_lock_timeout; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: getDefaultLockTimeout - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionDBOptions_getDefaultLockTimeout( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->default_lock_timeout; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: setDefaultLockTimeout - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionDBOptions_setDefaultLockTimeout( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jdefault_lock_timeout) { - auto* opts = - reinterpret_cast(jhandle); - opts->default_lock_timeout = jdefault_lock_timeout; -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: getWritePolicy - * Signature: (J)B - */ -jbyte Java_org_rocksdb_TransactionDBOptions_getWritePolicy(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return ROCKSDB_NAMESPACE::TxnDBWritePolicyJni::toJavaTxnDBWritePolicy( - opts->write_policy); -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: setWritePolicy - * Signature: (JB)V - */ -void Java_org_rocksdb_TransactionDBOptions_setWritePolicy(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jbyte jwrite_policy) { - auto* opts = - reinterpret_cast(jhandle); - opts->write_policy = - ROCKSDB_NAMESPACE::TxnDBWritePolicyJni::toCppTxnDBWritePolicy( - jwrite_policy); -} - -/* - * Class: org_rocksdb_TransactionDBOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionDBOptions_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} diff --git a/java/rocksjni/transaction_log.cc b/java/rocksjni/transaction_log.cc deleted file mode 100644 index 97c3bb301..000000000 --- a/java/rocksjni/transaction_log.cc +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::Iterator methods from Java side. - -#include "rocksdb/transaction_log.h" - -#include -#include -#include - -#include "include/org_rocksdb_TransactionLogIterator.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_TransactionLogIterator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionLogIterator_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - delete reinterpret_cast(handle); -} - -/* - * Class: org_rocksdb_TransactionLogIterator - * Method: isValid - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_TransactionLogIterator_isValid(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast(handle) - ->Valid(); -} - -/* - * Class: org_rocksdb_TransactionLogIterator - * Method: next - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionLogIterator_next(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Next(); -} - -/* - * Class: org_rocksdb_TransactionLogIterator - * Method: status - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionLogIterator_status(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - ROCKSDB_NAMESPACE::Status s = - reinterpret_cast(handle) - ->status(); - if (!s.ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -/* - * Class: org_rocksdb_TransactionLogIterator - * Method: getBatch - * Signature: (J)Lorg/rocksdb/TransactionLogIterator$BatchResult - */ -jobject Java_org_rocksdb_TransactionLogIterator_getBatch(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - ROCKSDB_NAMESPACE::BatchResult batch_result = - reinterpret_cast(handle) - ->GetBatch(); - return ROCKSDB_NAMESPACE::BatchResultJni::construct(env, batch_result); -} diff --git a/java/rocksjni/transaction_notifier.cc b/java/rocksjni/transaction_notifier.cc deleted file mode 100644 index cefeb648a..000000000 --- a/java/rocksjni/transaction_notifier.cc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::TransactionNotifier. - -#include - -#include "include/org_rocksdb_AbstractTransactionNotifier.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/transaction_notifier_jnicallback.h" - -/* - * Class: org_rocksdb_AbstractTransactionNotifier - * Method: createNewTransactionNotifier - * Signature: ()J - */ -jlong Java_org_rocksdb_AbstractTransactionNotifier_createNewTransactionNotifier( - JNIEnv* env, jobject jobj) { - auto* transaction_notifier = - new ROCKSDB_NAMESPACE::TransactionNotifierJniCallback(env, jobj); - auto* sptr_transaction_notifier = - new std::shared_ptr( - transaction_notifier); - return GET_CPLUSPLUS_POINTER(sptr_transaction_notifier); -} - -/* - * Class: org_rocksdb_AbstractTransactionNotifier - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_AbstractTransactionNotifier_disposeInternal( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - // TODO(AR) refactor to use JniCallback::JniCallback - // when https://github.com/facebook/rocksdb/pull/1241/ is merged - std::shared_ptr* handle = - reinterpret_cast< - std::shared_ptr*>( - jhandle); - delete handle; -} diff --git a/java/rocksjni/transaction_notifier_jnicallback.cc b/java/rocksjni/transaction_notifier_jnicallback.cc deleted file mode 100644 index 26761cabd..000000000 --- a/java/rocksjni/transaction_notifier_jnicallback.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::TransactionNotifier. - -#include "rocksjni/transaction_notifier_jnicallback.h" - -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { - -TransactionNotifierJniCallback::TransactionNotifierJniCallback( - JNIEnv* env, jobject jtransaction_notifier) - : JniCallback(env, jtransaction_notifier) { - // we cache the method id for the JNI callback - m_jsnapshot_created_methodID = - AbstractTransactionNotifierJni::getSnapshotCreatedMethodId(env); -} - -void TransactionNotifierJniCallback::SnapshotCreated( - const Snapshot* newSnapshot) { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - assert(env != nullptr); - - env->CallVoidMethod(m_jcallback_obj, m_jsnapshot_created_methodID, - GET_CPLUSPLUS_POINTER(newSnapshot)); - - if (env->ExceptionCheck()) { - // exception thrown from CallVoidMethod - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - - releaseJniEnv(attached_thread); -} -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/transaction_notifier_jnicallback.h b/java/rocksjni/transaction_notifier_jnicallback.h deleted file mode 100644 index 089a5ee4a..000000000 --- a/java/rocksjni/transaction_notifier_jnicallback.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::TransactionNotifier. - -#ifndef JAVA_ROCKSJNI_TRANSACTION_NOTIFIER_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_TRANSACTION_NOTIFIER_JNICALLBACK_H_ - -#include - -#include "rocksdb/utilities/transaction.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -/** - * This class acts as a bridge between C++ - * and Java. The methods in this class will be - * called back from the RocksDB TransactionDB or OptimisticTransactionDB (C++), - * we then callback to the appropriate Java method - * this enables TransactionNotifier to be implemented in Java. - * - * Unlike RocksJava's Comparator JNI Callback, we do not attempt - * to reduce Java object allocations by caching the Snapshot object - * presented to the callback. This could be revisited in future - * if performance is lacking. - */ -class TransactionNotifierJniCallback : public JniCallback, - public TransactionNotifier { - public: - TransactionNotifierJniCallback(JNIEnv* env, jobject jtransaction_notifier); - virtual void SnapshotCreated(const Snapshot* newSnapshot); - - private: - jmethodID m_jsnapshot_created_methodID; -}; -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_TRANSACTION_NOTIFIER_JNICALLBACK_H_ diff --git a/java/rocksjni/transaction_options.cc b/java/rocksjni/transaction_options.cc deleted file mode 100644 index dcf363e14..000000000 --- a/java/rocksjni/transaction_options.cc +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ -// for ROCKSDB_NAMESPACE::TransactionOptions. - -#include - -#include "include/org_rocksdb_TransactionOptions.h" -#include "rocksdb/utilities/transaction_db.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_TransactionOptions - * Method: newTransactionOptions - * Signature: ()J - */ -jlong Java_org_rocksdb_TransactionOptions_newTransactionOptions( - JNIEnv* /*env*/, jclass /*jcls*/) { - auto* opts = new ROCKSDB_NAMESPACE::TransactionOptions(); - return GET_CPLUSPLUS_POINTER(opts); -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: isSetSnapshot - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_TransactionOptions_isSetSnapshot(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->set_snapshot; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: setSetSnapshot - * Signature: (JZ)V - */ -void Java_org_rocksdb_TransactionOptions_setSetSnapshot( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean jset_snapshot) { - auto* opts = - reinterpret_cast(jhandle); - opts->set_snapshot = jset_snapshot; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: isDeadlockDetect - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_TransactionOptions_isDeadlockDetect(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->deadlock_detect; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: setDeadlockDetect - * Signature: (JZ)V - */ -void Java_org_rocksdb_TransactionOptions_setDeadlockDetect( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jboolean jdeadlock_detect) { - auto* opts = - reinterpret_cast(jhandle); - opts->deadlock_detect = jdeadlock_detect; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: getLockTimeout - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionOptions_getLockTimeout(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->lock_timeout; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: setLockTimeout - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionOptions_setLockTimeout(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jlock_timeout) { - auto* opts = - reinterpret_cast(jhandle); - opts->lock_timeout = jlock_timeout; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: getExpiration - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionOptions_getExpiration(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->expiration; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: setExpiration - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionOptions_setExpiration(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle, - jlong jexpiration) { - auto* opts = - reinterpret_cast(jhandle); - opts->expiration = jexpiration; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: getDeadlockDetectDepth - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionOptions_getDeadlockDetectDepth( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->deadlock_detect_depth; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: setDeadlockDetectDepth - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionOptions_setDeadlockDetectDepth( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jdeadlock_detect_depth) { - auto* opts = - reinterpret_cast(jhandle); - opts->deadlock_detect_depth = jdeadlock_detect_depth; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: getMaxWriteBatchSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_TransactionOptions_getMaxWriteBatchSize(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* opts = - reinterpret_cast(jhandle); - return opts->max_write_batch_size; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: setMaxWriteBatchSize - * Signature: (JJ)V - */ -void Java_org_rocksdb_TransactionOptions_setMaxWriteBatchSize( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, - jlong jmax_write_batch_size) { - auto* opts = - reinterpret_cast(jhandle); - opts->max_write_batch_size = jmax_write_batch_size; -} - -/* - * Class: org_rocksdb_TransactionOptions - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_TransactionOptions_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - delete reinterpret_cast(jhandle); -} diff --git a/java/rocksjni/ttl.cc b/java/rocksjni/ttl.cc deleted file mode 100644 index 1fe2083d9..000000000 --- a/java/rocksjni/ttl.cc +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::TtlDB methods. -// from Java side. - -#include -#include -#include - -#include -#include -#include - -#include "include/org_rocksdb_TtlDB.h" -#include "rocksdb/utilities/db_ttl.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_TtlDB - * Method: open - * Signature: (JLjava/lang/String;IZ)J - */ -jlong Java_org_rocksdb_TtlDB_open(JNIEnv* env, jclass, jlong joptions_handle, - jstring jdb_path, jint jttl, - jboolean jread_only) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - auto* opt = reinterpret_cast(joptions_handle); - ROCKSDB_NAMESPACE::DBWithTTL* db = nullptr; - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::DBWithTTL::Open(*opt, db_path, &db, jttl, jread_only); - env->ReleaseStringUTFChars(jdb_path, db_path); - - // as TTLDB extends RocksDB on the java side, we can reuse - // the RocksDB portal here. - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(db); - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; - } -} - -/* - * Class: org_rocksdb_TtlDB - * Method: openCF - * Signature: (JLjava/lang/String;[[B[J[IZ)[J - */ -jlongArray Java_org_rocksdb_TtlDB_openCF(JNIEnv* env, jclass, jlong jopt_handle, - jstring jdb_path, - jobjectArray jcolumn_names, - jlongArray jcolumn_options, - jintArray jttls, jboolean jread_only) { - const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if (db_path == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - - const jsize len_cols = env->GetArrayLength(jcolumn_names); - jlong* jco = env->GetLongArrayElements(jcolumn_options, nullptr); - if (jco == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - std::vector column_families; - jboolean has_exception = JNI_FALSE; - ROCKSDB_NAMESPACE::JniUtil::byteStrings( - env, jcolumn_names, - [](const char* str_data, const size_t str_len) { - return std::string(str_data, str_len); - }, - [&jco, &column_families](size_t idx, std::string cf_name) { - ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options = - reinterpret_cast(jco[idx]); - column_families.push_back( - ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(cf_name, *cf_options)); - }, - &has_exception); - - env->ReleaseLongArrayElements(jcolumn_options, jco, JNI_ABORT); - - if (has_exception == JNI_TRUE) { - // exception occurred - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - - std::vector ttl_values; - jint* jttlv = env->GetIntArrayElements(jttls, nullptr); - if (jttlv == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jdb_path, db_path); - return nullptr; - } - const jsize len_ttls = env->GetArrayLength(jttls); - for (jsize i = 0; i < len_ttls; i++) { - ttl_values.push_back(jttlv[i]); - } - env->ReleaseIntArrayElements(jttls, jttlv, JNI_ABORT); - - auto* opt = reinterpret_cast(jopt_handle); - std::vector handles; - ROCKSDB_NAMESPACE::DBWithTTL* db = nullptr; - ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::DBWithTTL::Open( - *opt, db_path, column_families, &handles, &db, ttl_values, jread_only); - - // we have now finished with db_path - env->ReleaseStringUTFChars(jdb_path, db_path); - - // check if open operation was successful - if (s.ok()) { - const jsize resultsLen = 1 + len_cols; // db handle + column family handles - std::unique_ptr results = - std::unique_ptr(new jlong[resultsLen]); - results[0] = GET_CPLUSPLUS_POINTER(db); - for (int i = 1; i <= len_cols; i++) { - results[i] = GET_CPLUSPLUS_POINTER(handles[i - 1]); - } - - jlongArray jresults = env->NewLongArray(resultsLen); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresults); - return nullptr; - } - - return jresults; - } else { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return NULL; - } -} - -/* - * Class: org_rocksdb_TtlDB - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_TtlDB_disposeInternal(JNIEnv*, jobject, jlong jhandle) { - auto* ttl_db = reinterpret_cast(jhandle); - assert(ttl_db != nullptr); - delete ttl_db; -} - -/* - * Class: org_rocksdb_TtlDB - * Method: closeDatabase - * Signature: (J)V - */ -void Java_org_rocksdb_TtlDB_closeDatabase(JNIEnv* /* env */, jclass, - jlong /* jhandle */) { - // auto* ttl_db = reinterpret_cast(jhandle); - // assert(ttl_db != nullptr); - // ROCKSDB_NAMESPACE::Status s = ttl_db->Close(); - // ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - - // TODO(AR) this is disabled until - // https://github.com/facebook/rocksdb/issues/4818 is resolved! -} - -/* - * Class: org_rocksdb_TtlDB - * Method: createColumnFamilyWithTtl - * Signature: (JLorg/rocksdb/ColumnFamilyDescriptor;[BJI)J; - */ -jlong Java_org_rocksdb_TtlDB_createColumnFamilyWithTtl(JNIEnv* env, jobject, - jlong jdb_handle, - jbyteArray jcolumn_name, - jlong jcolumn_options, - jint jttl) { - jbyte* cfname = env->GetByteArrayElements(jcolumn_name, nullptr); - if (cfname == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - const jsize len = env->GetArrayLength(jcolumn_name); - - auto* cfOptions = reinterpret_cast( - jcolumn_options); - - auto* db_handle = reinterpret_cast(jdb_handle); - ROCKSDB_NAMESPACE::ColumnFamilyHandle* handle; - ROCKSDB_NAMESPACE::Status s = db_handle->CreateColumnFamilyWithTtl( - *cfOptions, std::string(reinterpret_cast(cfname), len), &handle, - jttl); - - env->ReleaseByteArrayElements(jcolumn_name, cfname, JNI_ABORT); - - if (s.ok()) { - return GET_CPLUSPLUS_POINTER(handle); - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); - return 0; -} diff --git a/java/rocksjni/wal_filter.cc b/java/rocksjni/wal_filter.cc deleted file mode 100644 index 24b88afed..000000000 --- a/java/rocksjni/wal_filter.cc +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::WalFilter. - -#include - -#include "include/org_rocksdb_AbstractWalFilter.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/wal_filter_jnicallback.h" - -/* - * Class: org_rocksdb_AbstractWalFilter - * Method: createNewWalFilter - * Signature: ()J - */ -jlong Java_org_rocksdb_AbstractWalFilter_createNewWalFilter(JNIEnv* env, - jobject jobj) { - auto* wal_filter = new ROCKSDB_NAMESPACE::WalFilterJniCallback(env, jobj); - return GET_CPLUSPLUS_POINTER(wal_filter); -} diff --git a/java/rocksjni/wal_filter_jnicallback.cc b/java/rocksjni/wal_filter_jnicallback.cc deleted file mode 100644 index d2e3c9076..000000000 --- a/java/rocksjni/wal_filter_jnicallback.cc +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::WalFilter. - -#include "rocksjni/wal_filter_jnicallback.h" - -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -WalFilterJniCallback::WalFilterJniCallback(JNIEnv* env, jobject jwal_filter) - : JniCallback(env, jwal_filter) { - // Note: The name of a WalFilter will not change during it's lifetime, - // so we cache it in a global var - jmethodID jname_mid = AbstractWalFilterJni::getNameMethodId(env); - if (jname_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - jstring jname = (jstring)env->CallObjectMethod(m_jcallback_obj, jname_mid); - if (env->ExceptionCheck()) { - // exception thrown - return; - } - jboolean has_exception = JNI_FALSE; - m_name = JniUtil::copyString(env, jname, - &has_exception); // also releases jname - if (has_exception == JNI_TRUE) { - // exception thrown - return; - } - - m_column_family_log_number_map_mid = - AbstractWalFilterJni::getColumnFamilyLogNumberMapMethodId(env); - if (m_column_family_log_number_map_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } - - m_log_record_found_proxy_mid = - AbstractWalFilterJni::getLogRecordFoundProxyMethodId(env); - if (m_log_record_found_proxy_mid == nullptr) { - // exception thrown: NoSuchMethodException or OutOfMemoryError - return; - } -} - -void WalFilterJniCallback::ColumnFamilyLogNumberMap( - const std::map& cf_lognumber_map, - const std::map& cf_name_id_map) { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - if (env == nullptr) { - return; - } - - jobject jcf_lognumber_map = - ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(env, &cf_lognumber_map); - if (jcf_lognumber_map == nullptr) { - // exception occurred - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return; - } - - jobject jcf_name_id_map = - ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(env, &cf_name_id_map); - if (jcf_name_id_map == nullptr) { - // exception occurred - env->ExceptionDescribe(); // print out exception to stderr - env->DeleteLocalRef(jcf_lognumber_map); - releaseJniEnv(attached_thread); - return; - } - - env->CallVoidMethod(m_jcallback_obj, m_column_family_log_number_map_mid, - jcf_lognumber_map, jcf_name_id_map); - - env->DeleteLocalRef(jcf_lognumber_map); - env->DeleteLocalRef(jcf_name_id_map); - - if (env->ExceptionCheck()) { - // exception thrown from CallVoidMethod - env->ExceptionDescribe(); // print out exception to stderr - } - - releaseJniEnv(attached_thread); -} - -WalFilter::WalProcessingOption WalFilterJniCallback::LogRecordFound( - unsigned long long log_number, const std::string& log_file_name, - const WriteBatch& batch, WriteBatch* new_batch, bool* batch_changed) { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = getJniEnv(&attached_thread); - if (env == nullptr) { - return WalFilter::WalProcessingOption::kCorruptedRecord; - } - - jstring jlog_file_name = JniUtil::toJavaString(env, &log_file_name); - if (jlog_file_name == nullptr) { - // exception occcurred - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return WalFilter::WalProcessingOption::kCorruptedRecord; - } - - jshort jlog_record_found_result = env->CallShortMethod( - m_jcallback_obj, m_log_record_found_proxy_mid, - static_cast(log_number), jlog_file_name, - GET_CPLUSPLUS_POINTER(&batch), GET_CPLUSPLUS_POINTER(new_batch)); - - env->DeleteLocalRef(jlog_file_name); - - if (env->ExceptionCheck()) { - // exception thrown from CallShortMethod - env->ExceptionDescribe(); // print out exception to stderr - releaseJniEnv(attached_thread); - return WalFilter::WalProcessingOption::kCorruptedRecord; - } - - // unpack WalProcessingOption and batch_changed from jlog_record_found_result - jbyte jwal_processing_option_value = (jlog_record_found_result >> 8) & 0xFF; - jbyte jbatch_changed_value = jlog_record_found_result & 0xFF; - - releaseJniEnv(attached_thread); - - *batch_changed = jbatch_changed_value == JNI_TRUE; - - return WalProcessingOptionJni::toCppWalProcessingOption( - jwal_processing_option_value); -} - -const char* WalFilterJniCallback::Name() const { return m_name.get(); } - -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/wal_filter_jnicallback.h b/java/rocksjni/wal_filter_jnicallback.h deleted file mode 100644 index 5cdc65978..000000000 --- a/java/rocksjni/wal_filter_jnicallback.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::WalFilter. - -#ifndef JAVA_ROCKSJNI_WAL_FILTER_JNICALLBACK_H_ -#define JAVA_ROCKSJNI_WAL_FILTER_JNICALLBACK_H_ - -#include - -#include -#include -#include - -#include "rocksdb/wal_filter.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { - -class WalFilterJniCallback : public JniCallback, public WalFilter { - public: - WalFilterJniCallback(JNIEnv* env, jobject jwal_filter); - virtual void ColumnFamilyLogNumberMap( - const std::map& cf_lognumber_map, - const std::map& cf_name_id_map); - virtual WalFilter::WalProcessingOption LogRecordFound( - unsigned long long log_number, const std::string& log_file_name, - const WriteBatch& batch, WriteBatch* new_batch, bool* batch_changed); - virtual const char* Name() const; - - private: - std::unique_ptr m_name; - jmethodID m_column_family_log_number_map_mid; - jmethodID m_log_record_found_proxy_mid; -}; - -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_WAL_FILTER_JNICALLBACK_H_ diff --git a/java/rocksjni/write_batch.cc b/java/rocksjni/write_batch.cc deleted file mode 100644 index 6704e4a7e..000000000 --- a/java/rocksjni/write_batch.cc +++ /dev/null @@ -1,676 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::WriteBatch methods from Java side. -#include "rocksdb/write_batch.h" - -#include - -#include "db/memtable.h" -#include "db/write_batch_internal.h" -#include "include/org_rocksdb_WriteBatch.h" -#include "include/org_rocksdb_WriteBatch_Handler.h" -#include "logging/logging.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/status.h" -#include "rocksdb/write_buffer_manager.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" -#include "rocksjni/writebatchhandlerjnicallback.h" -#include "table/scoped_arena_iterator.h" - -/* - * Class: org_rocksdb_WriteBatch - * Method: newWriteBatch - * Signature: (I)J - */ -jlong Java_org_rocksdb_WriteBatch_newWriteBatch__I(JNIEnv* /*env*/, - jclass /*jcls*/, - jint jreserved_bytes) { - auto* wb = - new ROCKSDB_NAMESPACE::WriteBatch(static_cast(jreserved_bytes)); - return GET_CPLUSPLUS_POINTER(wb); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: newWriteBatch - * Signature: ([BI)J - */ -jlong Java_org_rocksdb_WriteBatch_newWriteBatch___3BI(JNIEnv* env, - jclass /*jcls*/, - jbyteArray jserialized, - jint jserialized_length) { - jboolean has_exception = JNI_FALSE; - std::string serialized = ROCKSDB_NAMESPACE::JniUtil::byteString( - env, jserialized, jserialized_length, - [](const char* str, const size_t len) { return std::string(str, len); }, - &has_exception); - if (has_exception == JNI_TRUE) { - // exception occurred - return 0; - } - - auto* wb = new ROCKSDB_NAMESPACE::WriteBatch(serialized); - return GET_CPLUSPLUS_POINTER(wb); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: count0 - * Signature: (J)I - */ -jint Java_org_rocksdb_WriteBatch_count0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return static_cast(wb->Count()); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: clear0 - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_clear0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - wb->Clear(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: setSavePoint0 - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_setSavePoint0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - wb->SetSavePoint(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: rollbackToSavePoint0 - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_rollbackToSavePoint0(JNIEnv* env, - jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - auto s = wb->RollbackToSavePoint(); - - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: popSavePoint - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_popSavePoint(JNIEnv* env, jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - auto s = wb->PopSavePoint(); - - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: setMaxBytes - * Signature: (JJ)V - */ -void Java_org_rocksdb_WriteBatch_setMaxBytes(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jwb_handle, - jlong jmax_bytes) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - wb->SetMaxBytes(static_cast(jmax_bytes)); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: put - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_WriteBatch_put__J_3BI_3BI(JNIEnv* env, jobject jobj, - jlong jwb_handle, - jbyteArray jkey, jint jkey_len, - jbyteArray jentry_value, - jint jentry_value_len) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto put = [&wb](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wb->Put(key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: put - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_WriteBatch_put__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jkey, jint jkey_len, - jbyteArray jentry_value, jint jentry_value_len, jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto put = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wb->Put(cf_handle, key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: putDirect - * Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)V - */ -void Java_org_rocksdb_WriteBatch_putDirect(JNIEnv* env, jobject /*jobj*/, - jlong jwb_handle, jobject jkey, - jint jkey_offset, jint jkey_len, - jobject jval, jint jval_offset, - jint jval_len, jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto put = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice& key, - ROCKSDB_NAMESPACE::Slice& value) { - if (cf_handle == nullptr) { - wb->Put(key, value); - } else { - wb->Put(cf_handle, key, value); - } - }; - ROCKSDB_NAMESPACE::JniUtil::kv_op_direct( - put, env, jkey, jkey_offset, jkey_len, jval, jval_offset, jval_len); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: merge - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_WriteBatch_merge__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jkey, jint jkey_len, - jbyteArray jentry_value, jint jentry_value_len) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto merge = [&wb](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wb->Merge(key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: merge - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_WriteBatch_merge__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jkey, jint jkey_len, - jbyteArray jentry_value, jint jentry_value_len, jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto merge = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wb->Merge(cf_handle, key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: delete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WriteBatch_delete__J_3BI(JNIEnv* env, jobject jobj, - jlong jwb_handle, - jbyteArray jkey, jint jkey_len) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto remove = [&wb](ROCKSDB_NAMESPACE::Slice key) { return wb->Delete(key); }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: delete - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_WriteBatch_delete__J_3BIJ(JNIEnv* env, jobject jobj, - jlong jwb_handle, - jbyteArray jkey, jint jkey_len, - jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto remove = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice key) { - return wb->Delete(cf_handle, key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: singleDelete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WriteBatch_singleDelete__J_3BI(JNIEnv* env, jobject jobj, - jlong jwb_handle, - jbyteArray jkey, - jint jkey_len) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto single_delete = [&wb](ROCKSDB_NAMESPACE::Slice key) { - return wb->SingleDelete(key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(single_delete, env, jobj, jkey, - jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: singleDelete - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_WriteBatch_singleDelete__J_3BIJ(JNIEnv* env, jobject jobj, - jlong jwb_handle, - jbyteArray jkey, - jint jkey_len, - jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto single_delete = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice key) { - return wb->SingleDelete(cf_handle, key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(single_delete, env, jobj, jkey, - jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: deleteDirect - * Signature: (JLjava/nio/ByteBuffer;IIJ)V - */ -void Java_org_rocksdb_WriteBatch_deleteDirect(JNIEnv* env, jobject /*jobj*/, - jlong jwb_handle, jobject jkey, - jint jkey_offset, jint jkey_len, - jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto remove = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice& key) { - if (cf_handle == nullptr) { - wb->Delete(key); - } else { - wb->Delete(cf_handle, key); - } - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(remove, env, jkey, jkey_offset, - jkey_len); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: deleteRange - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_WriteBatch_deleteRange__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jbegin_key, - jint jbegin_key_len, jbyteArray jend_key, jint jend_key_len) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto deleteRange = [&wb](ROCKSDB_NAMESPACE::Slice beginKey, - ROCKSDB_NAMESPACE::Slice endKey) { - return wb->DeleteRange(beginKey, endKey); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, - jbegin_key_len, jend_key, jend_key_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: deleteRange - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_WriteBatch_deleteRange__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jbegin_key, - jint jbegin_key_len, jbyteArray jend_key, jint jend_key_len, - jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto deleteRange = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice beginKey, - ROCKSDB_NAMESPACE::Slice endKey) { - return wb->DeleteRange(cf_handle, beginKey, endKey); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, - jbegin_key_len, jend_key, jend_key_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: putLogData - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WriteBatch_putLogData(JNIEnv* env, jobject jobj, - jlong jwb_handle, jbyteArray jblob, - jint jblob_len) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto putLogData = [&wb](ROCKSDB_NAMESPACE::Slice blob) { - return wb->PutLogData(blob); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(putLogData, env, jobj, jblob, jblob_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: iterate - * Signature: (JJ)V - */ -void Java_org_rocksdb_WriteBatch_iterate(JNIEnv* env, jobject /*jobj*/, - jlong jwb_handle, - jlong handlerHandle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - ROCKSDB_NAMESPACE::Status s = wb->Iterate( - reinterpret_cast( - handlerHandle)); - - if (s.ok()) { - return; - } - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: data - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_WriteBatch_data(JNIEnv* env, jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - auto data = wb->Data(); - return ROCKSDB_NAMESPACE::JniUtil::copyBytes(env, data); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: getDataSize - * Signature: (J)J - */ -jlong Java_org_rocksdb_WriteBatch_getDataSize(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - auto data_size = wb->GetDataSize(); - return static_cast(data_size); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasPut - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteBatch_hasPut(JNIEnv* /*env*/, jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasPut(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasDelete - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WriteBatch_hasDelete(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasDelete(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasSingleDelete - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasSingleDelete( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasSingleDelete(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasDeleteRange - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasDeleteRange( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasDeleteRange(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasMerge - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasMerge( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasMerge(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasBeginPrepare - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasBeginPrepare( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasBeginPrepare(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasEndPrepare - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasEndPrepare( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasEndPrepare(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasCommit - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasCommit( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasCommit(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: hasRollback - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasRollback( - JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return wb->HasRollback(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: markWalTerminationPoint - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_markWalTerminationPoint(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - wb->MarkWalTerminationPoint(); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: getWalTerminationPoint - * Signature: (J)Lorg/rocksdb/WriteBatch/SavePoint; - */ -jobject Java_org_rocksdb_WriteBatch_getWalTerminationPoint(JNIEnv* env, - jobject /*jobj*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - auto save_point = wb->GetWalTerminationPoint(); - return ROCKSDB_NAMESPACE::WriteBatchSavePointJni::construct(env, save_point); -} - -/* - * Class: org_rocksdb_WriteBatch - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - auto* wb = reinterpret_cast(handle); - assert(wb != nullptr); - delete wb; -} - -/* - * Class: org_rocksdb_WriteBatch_Handler - * Method: createNewHandler0 - * Signature: ()J - */ -jlong Java_org_rocksdb_WriteBatch_00024Handler_createNewHandler0(JNIEnv* env, - jobject jobj) { - auto* wbjnic = new ROCKSDB_NAMESPACE::WriteBatchHandlerJniCallback(env, jobj); - return GET_CPLUSPLUS_POINTER(wbjnic); -} diff --git a/java/rocksjni/write_batch_test.cc b/java/rocksjni/write_batch_test.cc deleted file mode 100644 index 30b9a7229..000000000 --- a/java/rocksjni/write_batch_test.cc +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::WriteBatch methods testing from Java side. -#include "rocksdb/write_batch.h" - -#include - -#include "db/memtable.h" -#include "db/write_batch_internal.h" -#include "include/org_rocksdb_WriteBatch.h" -#include "include/org_rocksdb_WriteBatchTest.h" -#include "include/org_rocksdb_WriteBatchTestInternalHelper.h" -#include "include/org_rocksdb_WriteBatch_Handler.h" -#include "options/cf_options.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/status.h" -#include "rocksdb/write_buffer_manager.h" -#include "rocksjni/portal.h" -#include "table/scoped_arena_iterator.h" -#include "test_util/testharness.h" -#include "util/string_util.h" - -/* - * Class: org_rocksdb_WriteBatchTest - * Method: getContents - * Signature: (J)[B - */ -jbyteArray Java_org_rocksdb_WriteBatchTest_getContents(JNIEnv* env, - jclass /*jclazz*/, - jlong jwb_handle) { - auto* b = reinterpret_cast(jwb_handle); - assert(b != nullptr); - - // todo: Currently the following code is directly copied from - // db/write_bench_test.cc. It could be implemented in java once - // all the necessary components can be accessed via jni api. - - ROCKSDB_NAMESPACE::InternalKeyComparator cmp( - ROCKSDB_NAMESPACE::BytewiseComparator()); - auto factory = std::make_shared(); - ROCKSDB_NAMESPACE::Options options; - ROCKSDB_NAMESPACE::WriteBufferManager wb(options.db_write_buffer_size); - options.memtable_factory = factory; - ROCKSDB_NAMESPACE::MemTable* mem = new ROCKSDB_NAMESPACE::MemTable( - cmp, ROCKSDB_NAMESPACE::ImmutableOptions(options), - ROCKSDB_NAMESPACE::MutableCFOptions(options), &wb, - ROCKSDB_NAMESPACE::kMaxSequenceNumber, 0 /* column_family_id */); - mem->Ref(); - std::string state; - ROCKSDB_NAMESPACE::ColumnFamilyMemTablesDefault cf_mems_default(mem); - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::WriteBatchInternal::InsertInto(b, &cf_mems_default, - nullptr, nullptr); - unsigned int count = 0; - ROCKSDB_NAMESPACE::Arena arena; - ROCKSDB_NAMESPACE::ScopedArenaIterator iter( - mem->NewIterator(ROCKSDB_NAMESPACE::ReadOptions(), &arena)); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ROCKSDB_NAMESPACE::ParsedInternalKey ikey; - ikey.clear(); - ROCKSDB_NAMESPACE::Status pik_status = ROCKSDB_NAMESPACE::ParseInternalKey( - iter->key(), &ikey, true /* log_err_key */); - pik_status.PermitUncheckedError(); - assert(pik_status.ok()); - switch (ikey.type) { - case ROCKSDB_NAMESPACE::kTypeValue: - state.append("Put("); - state.append(ikey.user_key.ToString()); - state.append(", "); - state.append(iter->value().ToString()); - state.append(")"); - count++; - break; - case ROCKSDB_NAMESPACE::kTypeMerge: - state.append("Merge("); - state.append(ikey.user_key.ToString()); - state.append(", "); - state.append(iter->value().ToString()); - state.append(")"); - count++; - break; - case ROCKSDB_NAMESPACE::kTypeDeletion: - state.append("Delete("); - state.append(ikey.user_key.ToString()); - state.append(")"); - count++; - break; - case ROCKSDB_NAMESPACE::kTypeSingleDeletion: - state.append("SingleDelete("); - state.append(ikey.user_key.ToString()); - state.append(")"); - count++; - break; - case ROCKSDB_NAMESPACE::kTypeRangeDeletion: - state.append("DeleteRange("); - state.append(ikey.user_key.ToString()); - state.append(", "); - state.append(iter->value().ToString()); - state.append(")"); - count++; - break; - case ROCKSDB_NAMESPACE::kTypeLogData: - state.append("LogData("); - state.append(ikey.user_key.ToString()); - state.append(")"); - count++; - break; - default: - assert(false); - state.append("Err:Expected("); - state.append(std::to_string(ikey.type)); - state.append(")"); - count++; - break; - } - state.append("@"); - state.append(std::to_string(ikey.sequence)); - } - if (!s.ok()) { - state.append(s.ToString()); - } else if (ROCKSDB_NAMESPACE::WriteBatchInternal::Count(b) != count) { - state.append("Err:CountMismatch(expected="); - state.append( - std::to_string(ROCKSDB_NAMESPACE::WriteBatchInternal::Count(b))); - state.append(", actual="); - state.append(std::to_string(count)); - state.append(")"); - } - delete mem->Unref(); - - jbyteArray jstate = env->NewByteArray(static_cast(state.size())); - if (jstate == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - - env->SetByteArrayRegion( - jstate, 0, static_cast(state.size()), - const_cast(reinterpret_cast(state.c_str()))); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jstate); - return nullptr; - } - - return jstate; -} - -/* - * Class: org_rocksdb_WriteBatchTestInternalHelper - * Method: setSequence - * Signature: (JJ)V - */ -void Java_org_rocksdb_WriteBatchTestInternalHelper_setSequence( - JNIEnv* /*env*/, jclass /*jclazz*/, jlong jwb_handle, jlong jsn) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - ROCKSDB_NAMESPACE::WriteBatchInternal::SetSequence( - wb, static_cast(jsn)); -} - -/* - * Class: org_rocksdb_WriteBatchTestInternalHelper - * Method: sequence - * Signature: (J)J - */ -jlong Java_org_rocksdb_WriteBatchTestInternalHelper_sequence(JNIEnv* /*env*/, - jclass /*jclazz*/, - jlong jwb_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - - return static_cast( - ROCKSDB_NAMESPACE::WriteBatchInternal::Sequence(wb)); -} - -/* - * Class: org_rocksdb_WriteBatchTestInternalHelper - * Method: append - * Signature: (JJ)V - */ -void Java_org_rocksdb_WriteBatchTestInternalHelper_append(JNIEnv* /*env*/, - jclass /*jclazz*/, - jlong jwb_handle_1, - jlong jwb_handle_2) { - auto* wb1 = reinterpret_cast(jwb_handle_1); - assert(wb1 != nullptr); - auto* wb2 = reinterpret_cast(jwb_handle_2); - assert(wb2 != nullptr); - - ROCKSDB_NAMESPACE::WriteBatchInternal::Append(wb1, wb2); -} diff --git a/java/rocksjni/write_batch_with_index.cc b/java/rocksjni/write_batch_with_index.cc deleted file mode 100644 index a5c3216cb..000000000 --- a/java/rocksjni/write_batch_with_index.cc +++ /dev/null @@ -1,953 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the "bridge" between Java and C++ and enables -// calling c++ ROCKSDB_NAMESPACE::WriteBatchWithIndex methods from Java side. - -#include "rocksdb/utilities/write_batch_with_index.h" - -#include "include/org_rocksdb_WBWIRocksIterator.h" -#include "include/org_rocksdb_WriteBatchWithIndex.h" -#include "rocksdb/comparator.h" -#include "rocksjni/cplusplus_to_java_convert.h" -#include "rocksjni/portal.h" - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: newWriteBatchWithIndex - * Signature: ()J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__( - JNIEnv* /*env*/, jclass /*jcls*/) { - auto* wbwi = new ROCKSDB_NAMESPACE::WriteBatchWithIndex(); - return GET_CPLUSPLUS_POINTER(wbwi); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: newWriteBatchWithIndex - * Signature: (Z)J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__Z( - JNIEnv* /*env*/, jclass /*jcls*/, jboolean joverwrite_key) { - auto* wbwi = new ROCKSDB_NAMESPACE::WriteBatchWithIndex( - ROCKSDB_NAMESPACE::BytewiseComparator(), 0, - static_cast(joverwrite_key)); - return GET_CPLUSPLUS_POINTER(wbwi); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: newWriteBatchWithIndex - * Signature: (JBIZ)J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__JBIZ( - JNIEnv* /*env*/, jclass /*jcls*/, jlong jfallback_index_comparator_handle, - jbyte jcomparator_type, jint jreserved_bytes, jboolean joverwrite_key) { - ROCKSDB_NAMESPACE::Comparator* fallback_comparator = nullptr; - switch (jcomparator_type) { - // JAVA_COMPARATOR - case 0x0: - fallback_comparator = - reinterpret_cast( - jfallback_index_comparator_handle); - break; - - // JAVA_NATIVE_COMPARATOR_WRAPPER - case 0x1: - fallback_comparator = reinterpret_cast( - jfallback_index_comparator_handle); - break; - } - auto* wbwi = new ROCKSDB_NAMESPACE::WriteBatchWithIndex( - fallback_comparator, static_cast(jreserved_bytes), - static_cast(joverwrite_key)); - return GET_CPLUSPLUS_POINTER(wbwi); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: count0 - * Signature: (J)I - */ -jint Java_org_rocksdb_WriteBatchWithIndex_count0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - return static_cast(wbwi->GetWriteBatch()->Count()); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: put - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_put__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len, jbyteArray jentry_value, jint jentry_value_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto put = [&wbwi](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wbwi->Put(key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: put - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_put__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len, jbyteArray jentry_value, jint jentry_value_len, - jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto put = [&wbwi, &cf_handle](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wbwi->Put(cf_handle, key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: putDirect - * Signature: (JLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_putDirect( - JNIEnv* env, jobject /*jobj*/, jlong jwb_handle, jobject jkey, - jint jkey_offset, jint jkey_len, jobject jval, jint jval_offset, - jint jval_len, jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto put = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice& key, - ROCKSDB_NAMESPACE::Slice& value) { - if (cf_handle == nullptr) { - wb->Put(key, value); - } else { - wb->Put(cf_handle, key, value); - } - }; - ROCKSDB_NAMESPACE::JniUtil::kv_op_direct( - put, env, jkey, jkey_offset, jkey_len, jval, jval_offset, jval_len); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: merge - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_merge__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len, jbyteArray jentry_value, jint jentry_value_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto merge = [&wbwi](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wbwi->Merge(key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: merge - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_merge__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len, jbyteArray jentry_value, jint jentry_value_len, - jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto merge = [&wbwi, &cf_handle](ROCKSDB_NAMESPACE::Slice key, - ROCKSDB_NAMESPACE::Slice value) { - return wbwi->Merge(cf_handle, key, value); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, - jentry_value, jentry_value_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: delete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_delete__J_3BI(JNIEnv* env, - jobject jobj, - jlong jwbwi_handle, - jbyteArray jkey, - jint jkey_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto remove = [&wbwi](ROCKSDB_NAMESPACE::Slice key) { - return wbwi->Delete(key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: delete - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_delete__J_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len, jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto remove = [&wbwi, &cf_handle](ROCKSDB_NAMESPACE::Slice key) { - return wbwi->Delete(cf_handle, key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: singleDelete - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_singleDelete__J_3BI( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto single_delete = [&wbwi](ROCKSDB_NAMESPACE::Slice key) { - return wbwi->SingleDelete(key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(single_delete, env, jobj, jkey, - jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: singleDelete - * Signature: (J[BIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_singleDelete__J_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, - jint jkey_len, jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto single_delete = [&wbwi, &cf_handle](ROCKSDB_NAMESPACE::Slice key) { - return wbwi->SingleDelete(cf_handle, key); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(single_delete, env, jobj, jkey, - jkey_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: deleteDirect - * Signature: (JLjava/nio/ByteBuffer;IIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_deleteDirect( - JNIEnv* env, jobject /*jobj*/, jlong jwb_handle, jobject jkey, - jint jkey_offset, jint jkey_len, jlong jcf_handle) { - auto* wb = reinterpret_cast(jwb_handle); - assert(wb != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto remove = [&wb, &cf_handle](ROCKSDB_NAMESPACE::Slice& key) { - if (cf_handle == nullptr) { - wb->Delete(key); - } else { - wb->Delete(cf_handle, key); - } - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(remove, env, jkey, jkey_offset, - jkey_len); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: deleteRange - * Signature: (J[BI[BI)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_deleteRange__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jbegin_key, - jint jbegin_key_len, jbyteArray jend_key, jint jend_key_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto deleteRange = [&wbwi](ROCKSDB_NAMESPACE::Slice beginKey, - ROCKSDB_NAMESPACE::Slice endKey) { - return wbwi->DeleteRange(beginKey, endKey); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, - jbegin_key_len, jend_key, jend_key_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: deleteRange - * Signature: (J[BI[BIJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_deleteRange__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jbegin_key, - jint jbegin_key_len, jbyteArray jend_key, jint jend_key_len, - jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto* cf_handle = - reinterpret_cast(jcf_handle); - assert(cf_handle != nullptr); - auto deleteRange = [&wbwi, &cf_handle](ROCKSDB_NAMESPACE::Slice beginKey, - ROCKSDB_NAMESPACE::Slice endKey) { - return wbwi->DeleteRange(cf_handle, beginKey, endKey); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, - jbegin_key_len, jend_key, jend_key_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: putLogData - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_putLogData(JNIEnv* env, jobject jobj, - jlong jwbwi_handle, - jbyteArray jblob, - jint jblob_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - auto putLogData = [&wbwi](ROCKSDB_NAMESPACE::Slice blob) { - return wbwi->PutLogData(blob); - }; - std::unique_ptr status = - ROCKSDB_NAMESPACE::JniUtil::k_op(putLogData, env, jobj, jblob, jblob_len); - if (status != nullptr && !status->ok()) { - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status); - } -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: clear - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_clear0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - wbwi->Clear(); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: setSavePoint0 - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_setSavePoint0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - wbwi->SetSavePoint(); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: rollbackToSavePoint0 - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_rollbackToSavePoint0( - JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - auto s = wbwi->RollbackToSavePoint(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: popSavePoint - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_popSavePoint(JNIEnv* env, - jobject /*jobj*/, - jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - auto s = wbwi->PopSavePoint(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: setMaxBytes - * Signature: (JJ)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_setMaxBytes(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwbwi_handle, - jlong jmax_bytes) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - wbwi->SetMaxBytes(static_cast(jmax_bytes)); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: getWriteBatch - * Signature: (J)Lorg/rocksdb/WriteBatch; - */ -jobject Java_org_rocksdb_WriteBatchWithIndex_getWriteBatch(JNIEnv* env, - jobject /*jobj*/, - jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - assert(wbwi != nullptr); - - auto* wb = wbwi->GetWriteBatch(); - - // TODO(AR) is the `wb` object owned by us? - return ROCKSDB_NAMESPACE::WriteBatchJni::construct(env, wb); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: iterator0 - * Signature: (J)J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_iterator0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwbwi_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* wbwi_iterator = wbwi->NewIterator(); - return GET_CPLUSPLUS_POINTER(wbwi_iterator); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: iterator1 - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_iterator1(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jwbwi_handle, - jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto* wbwi_iterator = wbwi->NewIterator(cf_handle); - return GET_CPLUSPLUS_POINTER(wbwi_iterator); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: iteratorWithBase - * Signature: (JJJJ)J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_iteratorWithBase( - JNIEnv*, jobject, jlong jwbwi_handle, jlong jcf_handle, - jlong jbase_iterator_handle, jlong jread_opts_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - auto* base_iterator = - reinterpret_cast(jbase_iterator_handle); - ROCKSDB_NAMESPACE::ReadOptions* read_opts = - jread_opts_handle == 0 - ? nullptr - : reinterpret_cast( - jread_opts_handle); - auto* iterator = - wbwi->NewIteratorWithBase(cf_handle, base_iterator, read_opts); - return GET_CPLUSPLUS_POINTER(iterator); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: getFromBatch - * Signature: (JJ[BI)[B - */ -jbyteArray JNICALL Java_org_rocksdb_WriteBatchWithIndex_getFromBatch__JJ_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdbopt_handle, - jbyteArray jkey, jint jkey_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* dbopt = reinterpret_cast(jdbopt_handle); - - auto getter = [&wbwi, &dbopt](const ROCKSDB_NAMESPACE::Slice& key, - std::string* value) { - return wbwi->GetFromBatch(*dbopt, key, value); - }; - - return ROCKSDB_NAMESPACE::JniUtil::v_op(getter, env, jkey, jkey_len); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: getFromBatch - * Signature: (JJ[BIJ)[B - */ -jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatch__JJ_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdbopt_handle, - jbyteArray jkey, jint jkey_len, jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* dbopt = reinterpret_cast(jdbopt_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - - auto getter = [&wbwi, &cf_handle, &dbopt](const ROCKSDB_NAMESPACE::Slice& key, - std::string* value) { - return wbwi->GetFromBatch(cf_handle, *dbopt, key, value); - }; - - return ROCKSDB_NAMESPACE::JniUtil::v_op(getter, env, jkey, jkey_len); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: getFromBatchAndDB - * Signature: (JJJ[BI)[B - */ -jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatchAndDB__JJJ_3BI( - JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdb_handle, - jlong jreadopt_handle, jbyteArray jkey, jint jkey_len) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* db = reinterpret_cast(jdb_handle); - auto* readopt = - reinterpret_cast(jreadopt_handle); - - auto getter = [&wbwi, &db, &readopt](const ROCKSDB_NAMESPACE::Slice& key, - std::string* value) { - return wbwi->GetFromBatchAndDB(db, *readopt, key, value); - }; - - return ROCKSDB_NAMESPACE::JniUtil::v_op(getter, env, jkey, jkey_len); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: getFromBatchAndDB - * Signature: (JJJ[BIJ)[B - */ -jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatchAndDB__JJJ_3BIJ( - JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdb_handle, - jlong jreadopt_handle, jbyteArray jkey, jint jkey_len, jlong jcf_handle) { - auto* wbwi = - reinterpret_cast(jwbwi_handle); - auto* db = reinterpret_cast(jdb_handle); - auto* readopt = - reinterpret_cast(jreadopt_handle); - auto* cf_handle = - reinterpret_cast(jcf_handle); - - auto getter = [&wbwi, &db, &cf_handle, &readopt]( - const ROCKSDB_NAMESPACE::Slice& key, std::string* value) { - return wbwi->GetFromBatchAndDB(db, *readopt, cf_handle, key, value); - }; - - return ROCKSDB_NAMESPACE::JniUtil::v_op(getter, env, jkey, jkey_len); -} - -/* - * Class: org_rocksdb_WriteBatchWithIndex - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatchWithIndex_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - auto* wbwi = - reinterpret_cast(handle); - assert(wbwi != nullptr); - delete wbwi; -} - -/* WBWIRocksIterator below */ - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - assert(it != nullptr); - delete it; -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: isValid0 - * Signature: (J)Z - */ -jboolean Java_org_rocksdb_WBWIRocksIterator_isValid0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - return reinterpret_cast(handle)->Valid(); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekToFirst0 - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekToFirst0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->SeekToFirst(); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekToLast0 - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekToLast0(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->SeekToLast(); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: next0 - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_next0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Next(); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: prev0 - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_prev0(JNIEnv* /*env*/, jobject /*jobj*/, - jlong handle) { - reinterpret_cast(handle)->Prev(); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: seek0 - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seek0(JNIEnv* env, jobject /*jobj*/, - jlong handle, jbyteArray jtarget, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - jbyte* target = new jbyte[jtarget_len]; - env->GetByteArrayRegion(jtarget, 0, jtarget_len, target); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] target; - return; - } - - ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast(target), - jtarget_len); - - it->Seek(target_slice); - - delete[] target; -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->Seek(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(seek, env, jtarget, jtarget_off, - jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekByteArray0 - * Signature: (J[BII)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jtarget, - jint jtarget_off, jint jtarget_len) { - const std::unique_ptr target(new char[jtarget_len]); - if (target == nullptr) { - jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError"); - env->ThrowNew(oom_class, - "Memory allocation failed in RocksDB JNI function"); - return; - } - env->GetByteArrayRegion(jtarget, jtarget_off, jtarget_len, - reinterpret_cast(target.get())); - - ROCKSDB_NAMESPACE::Slice target_slice(target.get(), jtarget_len); - - auto* it = reinterpret_cast(handle); - it->Seek(target_slice); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekForPrev0 - * Signature: (J[BI)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekForPrev0(JNIEnv* env, - jobject /*jobj*/, - jlong handle, - jbyteArray jtarget, - jint jtarget_len) { - auto* it = reinterpret_cast(handle); - jbyte* target = new jbyte[jtarget_len]; - env->GetByteArrayRegion(jtarget, 0, jtarget_len, target); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] target; - return; - } - - ROCKSDB_NAMESPACE::Slice target_slice(reinterpret_cast(target), - jtarget_len); - - it->SeekForPrev(target_slice); - - delete[] target; -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekForPrevDirect0 - * Signature: (JLjava/nio/ByteBuffer;II)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekForPrevDirect0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jobject jtarget, - jint jtarget_off, jint jtarget_len) { - auto* it = reinterpret_cast(handle); - auto seek_for_prev = [&it](ROCKSDB_NAMESPACE::Slice& target_slice) { - it->SeekForPrev(target_slice); - }; - ROCKSDB_NAMESPACE::JniUtil::k_op_direct(seek_for_prev, env, jtarget, - jtarget_off, jtarget_len); -} - -/* - * This method supports fetching into indirect byte buffers; - * the Java wrapper extracts the byte[] and passes it here. - * - * Class: org_rocksdb_WBWIRocksIterator - * Method: seekForPrevByteArray0 - * Signature: (J[BII)V - */ -void Java_org_rocksdb_WBWIRocksIterator_seekForPrevByteArray0( - JNIEnv* env, jobject /*jobj*/, jlong handle, jbyteArray jtarget, - jint jtarget_off, jint jtarget_len) { - const std::unique_ptr target(new char[jtarget_len]); - if (target == nullptr) { - jclass oom_class = env->FindClass("/lang/java/OutOfMemoryError"); - env->ThrowNew(oom_class, - "Memory allocation failed in RocksDB JNI function"); - return; - } - env->GetByteArrayRegion(jtarget, jtarget_off, jtarget_len, - reinterpret_cast(target.get())); - - ROCKSDB_NAMESPACE::Slice target_slice(target.get(), jtarget_len); - - auto* it = reinterpret_cast(handle); - it->SeekForPrev(target_slice); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: status0 - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_status0(JNIEnv* env, jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - ROCKSDB_NAMESPACE::Status s = it->status(); - - if (s.ok()) { - return; - } - - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: entry1 - * Signature: (J)[J - */ -jlongArray Java_org_rocksdb_WBWIRocksIterator_entry1(JNIEnv* env, - jobject /*jobj*/, - jlong handle) { - auto* it = reinterpret_cast(handle); - const ROCKSDB_NAMESPACE::WriteEntry& we = it->Entry(); - - jlong results[3]; - - // set the type of the write entry - results[0] = ROCKSDB_NAMESPACE::WriteTypeJni::toJavaWriteType(we.type); - - // NOTE: key_slice and value_slice will be freed by - // org.rocksdb.DirectSlice#close - - auto* key_slice = new ROCKSDB_NAMESPACE::Slice(we.key.data(), we.key.size()); - results[1] = GET_CPLUSPLUS_POINTER(key_slice); - if (we.type == ROCKSDB_NAMESPACE::kDeleteRecord || - we.type == ROCKSDB_NAMESPACE::kSingleDeleteRecord || - we.type == ROCKSDB_NAMESPACE::kLogDataRecord) { - // set native handle of value slice to null if no value available - results[2] = 0; - } else { - auto* value_slice = - new ROCKSDB_NAMESPACE::Slice(we.value.data(), we.value.size()); - results[2] = GET_CPLUSPLUS_POINTER(value_slice); - } - - jlongArray jresults = env->NewLongArray(3); - if (jresults == nullptr) { - // exception thrown: OutOfMemoryError - if (results[2] != 0) { - auto* value_slice = - reinterpret_cast(results[2]); - delete value_slice; - } - delete key_slice; - return nullptr; - } - - env->SetLongArrayRegion(jresults, 0, 3, results); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresults); - if (results[2] != 0) { - auto* value_slice = - reinterpret_cast(results[2]); - delete value_slice; - } - delete key_slice; - return nullptr; - } - - return jresults; -} - -/* - * Class: org_rocksdb_WBWIRocksIterator - * Method: refresh0 - * Signature: (J)V - */ -void Java_org_rocksdb_WBWIRocksIterator_refresh0(JNIEnv* env) { - ROCKSDB_NAMESPACE::Status s = - ROCKSDB_NAMESPACE::Status::NotSupported("Refresh() is not supported"); - ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s); -} diff --git a/java/rocksjni/write_buffer_manager.cc b/java/rocksjni/write_buffer_manager.cc deleted file mode 100644 index 9ce697e10..000000000 --- a/java/rocksjni/write_buffer_manager.cc +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/write_buffer_manager.h" - -#include - -#include - -#include "include/org_rocksdb_WriteBufferManager.h" -#include "rocksdb/cache.h" -#include "rocksjni/cplusplus_to_java_convert.h" - -/* - * Class: org_rocksdb_WriteBufferManager - * Method: newWriteBufferManager - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_WriteBufferManager_newWriteBufferManager( - JNIEnv* /*env*/, jclass /*jclazz*/, jlong jbuffer_size, jlong jcache_handle, - jboolean allow_stall) { - auto* cache_ptr = - reinterpret_cast*>( - jcache_handle); - auto* write_buffer_manager = - new std::shared_ptr( - std::make_shared( - jbuffer_size, *cache_ptr, allow_stall)); - return GET_CPLUSPLUS_POINTER(write_buffer_manager); -} - -/* - * Class: org_rocksdb_WriteBufferManager - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBufferManager_disposeInternal(JNIEnv* /*env*/, - jobject /*jobj*/, - jlong jhandle) { - auto* write_buffer_manager = - reinterpret_cast*>( - jhandle); - assert(write_buffer_manager != nullptr); - delete write_buffer_manager; -} diff --git a/java/rocksjni/writebatchhandlerjnicallback.cc b/java/rocksjni/writebatchhandlerjnicallback.cc deleted file mode 100644 index 66ceabe9a..000000000 --- a/java/rocksjni/writebatchhandlerjnicallback.cc +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::Comparator. - -#include "rocksjni/writebatchhandlerjnicallback.h" - -#include "rocksjni/portal.h" - -namespace ROCKSDB_NAMESPACE { -WriteBatchHandlerJniCallback::WriteBatchHandlerJniCallback( - JNIEnv* env, jobject jWriteBatchHandler) - : JniCallback(env, jWriteBatchHandler), m_env(env) { - m_jPutCfMethodId = WriteBatchHandlerJni::getPutCfMethodId(env); - if (m_jPutCfMethodId == nullptr) { - // exception thrown - return; - } - - m_jPutMethodId = WriteBatchHandlerJni::getPutMethodId(env); - if (m_jPutMethodId == nullptr) { - // exception thrown - return; - } - - m_jMergeCfMethodId = WriteBatchHandlerJni::getMergeCfMethodId(env); - if (m_jMergeCfMethodId == nullptr) { - // exception thrown - return; - } - - m_jMergeMethodId = WriteBatchHandlerJni::getMergeMethodId(env); - if (m_jMergeMethodId == nullptr) { - // exception thrown - return; - } - - m_jDeleteCfMethodId = WriteBatchHandlerJni::getDeleteCfMethodId(env); - if (m_jDeleteCfMethodId == nullptr) { - // exception thrown - return; - } - - m_jDeleteMethodId = WriteBatchHandlerJni::getDeleteMethodId(env); - if (m_jDeleteMethodId == nullptr) { - // exception thrown - return; - } - - m_jSingleDeleteCfMethodId = - WriteBatchHandlerJni::getSingleDeleteCfMethodId(env); - if (m_jSingleDeleteCfMethodId == nullptr) { - // exception thrown - return; - } - - m_jSingleDeleteMethodId = WriteBatchHandlerJni::getSingleDeleteMethodId(env); - if (m_jSingleDeleteMethodId == nullptr) { - // exception thrown - return; - } - - m_jDeleteRangeCfMethodId = - WriteBatchHandlerJni::getDeleteRangeCfMethodId(env); - if (m_jDeleteRangeCfMethodId == nullptr) { - // exception thrown - return; - } - - m_jDeleteRangeMethodId = WriteBatchHandlerJni::getDeleteRangeMethodId(env); - if (m_jDeleteRangeMethodId == nullptr) { - // exception thrown - return; - } - - m_jLogDataMethodId = WriteBatchHandlerJni::getLogDataMethodId(env); - if (m_jLogDataMethodId == nullptr) { - // exception thrown - return; - } - - m_jPutBlobIndexCfMethodId = - WriteBatchHandlerJni::getPutBlobIndexCfMethodId(env); - if (m_jPutBlobIndexCfMethodId == nullptr) { - // exception thrown - return; - } - - m_jMarkBeginPrepareMethodId = - WriteBatchHandlerJni::getMarkBeginPrepareMethodId(env); - if (m_jMarkBeginPrepareMethodId == nullptr) { - // exception thrown - return; - } - - m_jMarkEndPrepareMethodId = - WriteBatchHandlerJni::getMarkEndPrepareMethodId(env); - if (m_jMarkEndPrepareMethodId == nullptr) { - // exception thrown - return; - } - - m_jMarkNoopMethodId = WriteBatchHandlerJni::getMarkNoopMethodId(env); - if (m_jMarkNoopMethodId == nullptr) { - // exception thrown - return; - } - - m_jMarkRollbackMethodId = WriteBatchHandlerJni::getMarkRollbackMethodId(env); - if (m_jMarkRollbackMethodId == nullptr) { - // exception thrown - return; - } - - m_jMarkCommitMethodId = WriteBatchHandlerJni::getMarkCommitMethodId(env); - if (m_jMarkCommitMethodId == nullptr) { - // exception thrown - return; - } - - m_jMarkCommitWithTimestampMethodId = - WriteBatchHandlerJni::getMarkCommitWithTimestampMethodId(env); - if (m_jMarkCommitWithTimestampMethodId == nullptr) { - // exception thrown - return; - } - - m_jContinueMethodId = WriteBatchHandlerJni::getContinueMethodId(env); - if (m_jContinueMethodId == nullptr) { - // exception thrown - return; - } -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::PutCF( - uint32_t column_family_id, const Slice& key, const Slice& value) { - auto put = [this, column_family_id](jbyteArray j_key, jbyteArray j_value) { - m_env->CallVoidMethod(m_jcallback_obj, m_jPutCfMethodId, - static_cast(column_family_id), j_key, j_value); - }; - auto status = WriteBatchHandlerJniCallback::kv_op(key, value, put); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -void WriteBatchHandlerJniCallback::Put(const Slice& key, const Slice& value) { - auto put = [this](jbyteArray j_key, jbyteArray j_value) { - m_env->CallVoidMethod(m_jcallback_obj, m_jPutMethodId, j_key, j_value); - }; - WriteBatchHandlerJniCallback::kv_op(key, value, put); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MergeCF( - uint32_t column_family_id, const Slice& key, const Slice& value) { - auto merge = [this, column_family_id](jbyteArray j_key, jbyteArray j_value) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMergeCfMethodId, - static_cast(column_family_id), j_key, j_value); - }; - auto status = WriteBatchHandlerJniCallback::kv_op(key, value, merge); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -void WriteBatchHandlerJniCallback::Merge(const Slice& key, const Slice& value) { - auto merge = [this](jbyteArray j_key, jbyteArray j_value) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMergeMethodId, j_key, j_value); - }; - WriteBatchHandlerJniCallback::kv_op(key, value, merge); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::DeleteCF( - uint32_t column_family_id, const Slice& key) { - auto remove = [this, column_family_id](jbyteArray j_key) { - m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteCfMethodId, - static_cast(column_family_id), j_key); - }; - auto status = WriteBatchHandlerJniCallback::k_op(key, remove); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -void WriteBatchHandlerJniCallback::Delete(const Slice& key) { - auto remove = [this](jbyteArray j_key) { - m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteMethodId, j_key); - }; - WriteBatchHandlerJniCallback::k_op(key, remove); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::SingleDeleteCF( - uint32_t column_family_id, const Slice& key) { - auto singleDelete = [this, column_family_id](jbyteArray j_key) { - m_env->CallVoidMethod(m_jcallback_obj, m_jSingleDeleteCfMethodId, - static_cast(column_family_id), j_key); - }; - auto status = WriteBatchHandlerJniCallback::k_op(key, singleDelete); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -void WriteBatchHandlerJniCallback::SingleDelete(const Slice& key) { - auto singleDelete = [this](jbyteArray j_key) { - m_env->CallVoidMethod(m_jcallback_obj, m_jSingleDeleteMethodId, j_key); - }; - WriteBatchHandlerJniCallback::k_op(key, singleDelete); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::DeleteRangeCF( - uint32_t column_family_id, const Slice& beginKey, const Slice& endKey) { - auto deleteRange = [this, column_family_id](jbyteArray j_beginKey, - jbyteArray j_endKey) { - m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteRangeCfMethodId, - static_cast(column_family_id), j_beginKey, - j_endKey); - }; - auto status = - WriteBatchHandlerJniCallback::kv_op(beginKey, endKey, deleteRange); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -void WriteBatchHandlerJniCallback::DeleteRange(const Slice& beginKey, - const Slice& endKey) { - auto deleteRange = [this](jbyteArray j_beginKey, jbyteArray j_endKey) { - m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteRangeMethodId, j_beginKey, - j_endKey); - }; - WriteBatchHandlerJniCallback::kv_op(beginKey, endKey, deleteRange); -} - -void WriteBatchHandlerJniCallback::LogData(const Slice& blob) { - auto logData = [this](jbyteArray j_blob) { - m_env->CallVoidMethod(m_jcallback_obj, m_jLogDataMethodId, j_blob); - }; - WriteBatchHandlerJniCallback::k_op(blob, logData); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::PutBlobIndexCF( - uint32_t column_family_id, const Slice& key, const Slice& value) { - auto putBlobIndex = [this, column_family_id](jbyteArray j_key, - jbyteArray j_value) { - m_env->CallVoidMethod(m_jcallback_obj, m_jPutBlobIndexCfMethodId, - static_cast(column_family_id), j_key, j_value); - }; - auto status = WriteBatchHandlerJniCallback::kv_op(key, value, putBlobIndex); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkBeginPrepare( - bool unprepare) { -#ifndef DEBUG - (void)unprepare; -#else - assert(!unprepare); -#endif - m_env->CallVoidMethod(m_jcallback_obj, m_jMarkBeginPrepareMethodId); - - // check for Exception, in-particular RocksDBException - if (m_env->ExceptionCheck()) { - // exception thrown - jthrowable exception = m_env->ExceptionOccurred(); - std::unique_ptr status = - ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception); - if (status == nullptr) { - // unkown status or exception occurred extracting status - m_env->ExceptionDescribe(); - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) probably need a - // better error code here - - } else { - m_env->ExceptionClear(); // clear the exception, as we have extracted the - // status - return ROCKSDB_NAMESPACE::Status(*status); - } - } - - return ROCKSDB_NAMESPACE::Status::OK(); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkEndPrepare( - const Slice& xid) { - auto markEndPrepare = [this](jbyteArray j_xid) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMarkEndPrepareMethodId, j_xid); - }; - auto status = WriteBatchHandlerJniCallback::k_op(xid, markEndPrepare); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkNoop( - bool empty_batch) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMarkNoopMethodId, - static_cast(empty_batch)); - - // check for Exception, in-particular RocksDBException - if (m_env->ExceptionCheck()) { - // exception thrown - jthrowable exception = m_env->ExceptionOccurred(); - std::unique_ptr status = - ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception); - if (status == nullptr) { - // unkown status or exception occurred extracting status - m_env->ExceptionDescribe(); - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) probably need a - // better error code here - - } else { - m_env->ExceptionClear(); // clear the exception, as we have extracted the - // status - return ROCKSDB_NAMESPACE::Status(*status); - } - } - - return ROCKSDB_NAMESPACE::Status::OK(); -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkRollback( - const Slice& xid) { - auto markRollback = [this](jbyteArray j_xid) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMarkRollbackMethodId, j_xid); - }; - auto status = WriteBatchHandlerJniCallback::k_op(xid, markRollback); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkCommit( - const Slice& xid) { - auto markCommit = [this](jbyteArray j_xid) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMarkCommitMethodId, j_xid); - }; - auto status = WriteBatchHandlerJniCallback::k_op(xid, markCommit); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkCommitWithTimestamp( - const Slice& xid, const Slice& ts) { - auto markCommitWithTimestamp = [this](jbyteArray j_xid, jbyteArray j_ts) { - m_env->CallVoidMethod(m_jcallback_obj, m_jMarkCommitWithTimestampMethodId, - j_xid, j_ts); - }; - auto status = - WriteBatchHandlerJniCallback::kv_op(xid, ts, markCommitWithTimestamp); - if (status == nullptr) { - return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is - // an Exception but we don't know - // the ROCKSDB_NAMESPACE::Status? - } else { - return ROCKSDB_NAMESPACE::Status(*status); - } -} - -bool WriteBatchHandlerJniCallback::Continue() { - jboolean jContinue = - m_env->CallBooleanMethod(m_jcallback_obj, m_jContinueMethodId); - if (m_env->ExceptionCheck()) { - // exception thrown - m_env->ExceptionDescribe(); - } - - return static_cast(jContinue == JNI_TRUE); -} - -std::unique_ptr WriteBatchHandlerJniCallback::kv_op( - const Slice& key, const Slice& value, - std::function kvFn) { - const jbyteArray j_key = JniUtil::copyBytes(m_env, key); - if (j_key == nullptr) { - // exception thrown - if (m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - return nullptr; - } - - const jbyteArray j_value = JniUtil::copyBytes(m_env, value); - if (j_value == nullptr) { - // exception thrown - if (m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - if (j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - return nullptr; - } - - kvFn(j_key, j_value); - - // check for Exception, in-particular RocksDBException - if (m_env->ExceptionCheck()) { - if (j_value != nullptr) { - m_env->DeleteLocalRef(j_value); - } - if (j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - - // exception thrown - jthrowable exception = m_env->ExceptionOccurred(); - std::unique_ptr status = - ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception); - if (status == nullptr) { - // unkown status or exception occurred extracting status - m_env->ExceptionDescribe(); - return nullptr; - - } else { - m_env->ExceptionClear(); // clear the exception, as we have extracted the - // status - return status; - } - } - - if (j_value != nullptr) { - m_env->DeleteLocalRef(j_value); - } - if (j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - - // all OK - return std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK())); -} - -std::unique_ptr WriteBatchHandlerJniCallback::k_op( - const Slice& key, std::function kFn) { - const jbyteArray j_key = JniUtil::copyBytes(m_env, key); - if (j_key == nullptr) { - // exception thrown - if (m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - return nullptr; - } - - kFn(j_key); - - // check for Exception, in-particular RocksDBException - if (m_env->ExceptionCheck()) { - if (j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - - // exception thrown - jthrowable exception = m_env->ExceptionOccurred(); - std::unique_ptr status = - ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception); - if (status == nullptr) { - // unkown status or exception occurred extracting status - m_env->ExceptionDescribe(); - return nullptr; - - } else { - m_env->ExceptionClear(); // clear the exception, as we have extracted the - // status - return status; - } - } - - if (j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - - // all OK - return std::unique_ptr( - new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK())); -} -} // namespace ROCKSDB_NAMESPACE diff --git a/java/rocksjni/writebatchhandlerjnicallback.h b/java/rocksjni/writebatchhandlerjnicallback.h deleted file mode 100644 index 9629797ca..000000000 --- a/java/rocksjni/writebatchhandlerjnicallback.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// This file implements the callback "bridge" between Java and C++ for -// ROCKSDB_NAMESPACE::WriteBatch::Handler. - -#ifndef JAVA_ROCKSJNI_WRITEBATCHHANDLERJNICALLBACK_H_ -#define JAVA_ROCKSJNI_WRITEBATCHHANDLERJNICALLBACK_H_ - -#include - -#include -#include - -#include "rocksdb/write_batch.h" -#include "rocksjni/jnicallback.h" - -namespace ROCKSDB_NAMESPACE { -/** - * This class acts as a bridge between C++ - * and Java. The methods in this class will be - * called back from the RocksDB storage engine (C++) - * which calls the appropriate Java method. - * This enables Write Batch Handlers to be implemented in Java. - */ -class WriteBatchHandlerJniCallback : public JniCallback, - public WriteBatch::Handler { - public: - WriteBatchHandlerJniCallback(JNIEnv* env, jobject jWriteBackHandler); - Status PutCF(uint32_t column_family_id, const Slice& key, const Slice& value); - void Put(const Slice& key, const Slice& value); - Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value); - void Merge(const Slice& key, const Slice& value); - Status DeleteCF(uint32_t column_family_id, const Slice& key); - void Delete(const Slice& key); - Status SingleDeleteCF(uint32_t column_family_id, const Slice& key); - void SingleDelete(const Slice& key); - Status DeleteRangeCF(uint32_t column_family_id, const Slice& beginKey, - const Slice& endKey); - void DeleteRange(const Slice& beginKey, const Slice& endKey); - void LogData(const Slice& blob); - Status PutBlobIndexCF(uint32_t column_family_id, const Slice& key, - const Slice& value); - Status MarkBeginPrepare(bool); - Status MarkEndPrepare(const Slice& xid); - Status MarkNoop(bool empty_batch); - Status MarkRollback(const Slice& xid); - Status MarkCommit(const Slice& xid); - Status MarkCommitWithTimestamp(const Slice& xid, const Slice& commit_ts); - bool Continue(); - - private: - JNIEnv* m_env; - jmethodID m_jPutCfMethodId; - jmethodID m_jPutMethodId; - jmethodID m_jMergeCfMethodId; - jmethodID m_jMergeMethodId; - jmethodID m_jDeleteCfMethodId; - jmethodID m_jDeleteMethodId; - jmethodID m_jSingleDeleteCfMethodId; - jmethodID m_jSingleDeleteMethodId; - jmethodID m_jDeleteRangeCfMethodId; - jmethodID m_jDeleteRangeMethodId; - jmethodID m_jLogDataMethodId; - jmethodID m_jPutBlobIndexCfMethodId; - jmethodID m_jMarkBeginPrepareMethodId; - jmethodID m_jMarkEndPrepareMethodId; - jmethodID m_jMarkNoopMethodId; - jmethodID m_jMarkRollbackMethodId; - jmethodID m_jMarkCommitMethodId; - jmethodID m_jMarkCommitWithTimestampMethodId; - jmethodID m_jContinueMethodId; - /** - * @return A pointer to a ROCKSDB_NAMESPACE::Status or nullptr if an - * unexpected exception occurred - */ - std::unique_ptr kv_op( - const Slice& key, const Slice& value, - std::function kvFn); - /** - * @return A pointer to a ROCKSDB_NAMESPACE::Status or nullptr if an - * unexpected exception occurred - */ - std::unique_ptr k_op( - const Slice& key, std::function kFn); -}; -} // namespace ROCKSDB_NAMESPACE - -#endif // JAVA_ROCKSJNI_WRITEBATCHHANDLERJNICALLBACK_H_ diff --git a/java/samples/src/main/java/OptimisticTransactionSample.java b/java/samples/src/main/java/OptimisticTransactionSample.java deleted file mode 100644 index 7e7a22e94..000000000 --- a/java/samples/src/main/java/OptimisticTransactionSample.java +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -import org.rocksdb.*; - -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * Demonstrates using Transactions on an OptimisticTransactionDB with - * varying isolation guarantees - */ -public class OptimisticTransactionSample { - private static final String dbPath = "/tmp/rocksdb_optimistic_transaction_example"; - - public static final void main(final String args[]) throws RocksDBException { - - try(final Options options = new Options() - .setCreateIfMissing(true); - final OptimisticTransactionDB txnDb = - OptimisticTransactionDB.open(options, dbPath)) { - - try (final WriteOptions writeOptions = new WriteOptions(); - final ReadOptions readOptions = new ReadOptions()) { - - //////////////////////////////////////////////////////// - // - // Simple OptimisticTransaction Example ("Read Committed") - // - //////////////////////////////////////////////////////// - readCommitted(txnDb, writeOptions, readOptions); - - - //////////////////////////////////////////////////////// - // - // "Repeatable Read" (Snapshot Isolation) Example - // -- Using a single Snapshot - // - //////////////////////////////////////////////////////// - repeatableRead(txnDb, writeOptions, readOptions); - - - //////////////////////////////////////////////////////// - // - // "Read Committed" (Monotonic Atomic Views) Example - // --Using multiple Snapshots - // - //////////////////////////////////////////////////////// - readCommitted_monotonicAtomicViews(txnDb, writeOptions, readOptions); - } - } - } - - /** - * Demonstrates "Read Committed" isolation - */ - private static void readCommitted(final OptimisticTransactionDB txnDb, - final WriteOptions writeOptions, final ReadOptions readOptions) - throws RocksDBException { - final byte key1[] = "abc".getBytes(UTF_8); - final byte value1[] = "def".getBytes(UTF_8); - - final byte key2[] = "xyz".getBytes(UTF_8); - final byte value2[] = "zzz".getBytes(UTF_8); - - // Start a transaction - try(final Transaction txn = txnDb.beginTransaction(writeOptions)) { - // Read a key in this transaction - byte[] value = txn.get(readOptions, key1); - assert(value == null); - - // Write a key in this transaction - txn.put(key1, value1); - - // Read a key OUTSIDE this transaction. Does not affect txn. - value = txnDb.get(readOptions, key1); - assert(value == null); - - // Write a key OUTSIDE of this transaction. - // Does not affect txn since this is an unrelated key. - // If we wrote key 'abc' here, the transaction would fail to commit. - txnDb.put(writeOptions, key2, value2); - - // Commit transaction - txn.commit(); - } - } - - /** - * Demonstrates "Repeatable Read" (Snapshot Isolation) isolation - */ - private static void repeatableRead(final OptimisticTransactionDB txnDb, - final WriteOptions writeOptions, final ReadOptions readOptions) - throws RocksDBException { - - final byte key1[] = "ghi".getBytes(UTF_8); - final byte value1[] = "jkl".getBytes(UTF_8); - - // Set a snapshot at start of transaction by setting setSnapshot(true) - try(final OptimisticTransactionOptions txnOptions = - new OptimisticTransactionOptions().setSetSnapshot(true); - final Transaction txn = - txnDb.beginTransaction(writeOptions, txnOptions)) { - - final Snapshot snapshot = txn.getSnapshot(); - - // Write a key OUTSIDE of transaction - txnDb.put(writeOptions, key1, value1); - - // Read a key using the snapshot. - readOptions.setSnapshot(snapshot); - final byte[] value = txn.getForUpdate(readOptions, key1, true); - assert (value == null); - - try { - // Attempt to commit transaction - txn.commit(); - throw new IllegalStateException(); - } catch(final RocksDBException e) { - // Transaction could not commit since the write outside of the txn - // conflicted with the read! - assert(e.getStatus().getCode() == Status.Code.Busy); - } - - txn.rollback(); - } finally { - // Clear snapshot from read options since it is no longer valid - readOptions.setSnapshot(null); - } - } - - /** - * Demonstrates "Read Committed" (Monotonic Atomic Views) isolation - * - * In this example, we set the snapshot multiple times. This is probably - * only necessary if you have very strict isolation requirements to - * implement. - */ - private static void readCommitted_monotonicAtomicViews( - final OptimisticTransactionDB txnDb, final WriteOptions writeOptions, - final ReadOptions readOptions) throws RocksDBException { - - final byte keyX[] = "x".getBytes(UTF_8); - final byte valueX[] = "x".getBytes(UTF_8); - - final byte keyY[] = "y".getBytes(UTF_8); - final byte valueY[] = "y".getBytes(UTF_8); - - try (final OptimisticTransactionOptions txnOptions = - new OptimisticTransactionOptions().setSetSnapshot(true); - final Transaction txn = - txnDb.beginTransaction(writeOptions, txnOptions)) { - - // Do some reads and writes to key "x" - Snapshot snapshot = txnDb.getSnapshot(); - readOptions.setSnapshot(snapshot); - byte[] value = txn.get(readOptions, keyX); - txn.put(valueX, valueX); - - // Do a write outside of the transaction to key "y" - txnDb.put(writeOptions, keyY, valueY); - - // Set a new snapshot in the transaction - txn.setSnapshot(); - snapshot = txnDb.getSnapshot(); - readOptions.setSnapshot(snapshot); - - // Do some reads and writes to key "y" - // Since the snapshot was advanced, the write done outside of the - // transaction does not conflict. - value = txn.getForUpdate(readOptions, keyY, true); - txn.put(keyY, valueY); - - // Commit. Since the snapshot was advanced, the write done outside of the - // transaction does not prevent this transaction from Committing. - txn.commit(); - - } finally { - // Clear snapshot from read options since it is no longer valid - readOptions.setSnapshot(null); - } - } -} diff --git a/java/samples/src/main/java/RocksDBColumnFamilySample.java b/java/samples/src/main/java/RocksDBColumnFamilySample.java deleted file mode 100644 index 72f5731a1..000000000 --- a/java/samples/src/main/java/RocksDBColumnFamilySample.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -import org.rocksdb.*; - -import java.util.ArrayList; -import java.util.List; - -public class RocksDBColumnFamilySample { - static { - RocksDB.loadLibrary(); - } - - public static void main(final String[] args) throws RocksDBException { - if (args.length < 1) { - System.out.println( - "usage: RocksDBColumnFamilySample db_path"); - System.exit(-1); - } - - final String db_path = args[0]; - - System.out.println("RocksDBColumnFamilySample"); - try(final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, db_path)) { - - assert(db != null); - - // create column family - try(final ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily( - new ColumnFamilyDescriptor("new_cf".getBytes(), - new ColumnFamilyOptions()))) { - assert (columnFamilyHandle != null); - } - } - - // open DB with two column families - final List columnFamilyDescriptors = - new ArrayList<>(); - // have to open default column family - columnFamilyDescriptors.add(new ColumnFamilyDescriptor( - RocksDB.DEFAULT_COLUMN_FAMILY, new ColumnFamilyOptions())); - // open the new one, too - columnFamilyDescriptors.add(new ColumnFamilyDescriptor( - "new_cf".getBytes(), new ColumnFamilyOptions())); - final List columnFamilyHandles = new ArrayList<>(); - try(final DBOptions options = new DBOptions(); - final RocksDB db = RocksDB.open(options, db_path, - columnFamilyDescriptors, columnFamilyHandles)) { - assert(db != null); - - try { - // put and get from non-default column family - db.put( - columnFamilyHandles.get(1), new WriteOptions(), "key".getBytes(), "value".getBytes()); - - // atomic write - try (final WriteBatch wb = new WriteBatch()) { - wb.put(columnFamilyHandles.get(0), "key2".getBytes(), - "value2".getBytes()); - wb.put(columnFamilyHandles.get(1), "key3".getBytes(), - "value3".getBytes()); - wb.delete(columnFamilyHandles.get(1), "key".getBytes()); - db.write(new WriteOptions(), wb); - } - - // drop column family - db.dropColumnFamily(columnFamilyHandles.get(1)); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } -} diff --git a/java/samples/src/main/java/RocksDBSample.java b/java/samples/src/main/java/RocksDBSample.java deleted file mode 100644 index 8ab9b2de3..000000000 --- a/java/samples/src/main/java/RocksDBSample.java +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -import java.lang.IllegalArgumentException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.ArrayList; - -import org.rocksdb.*; -import org.rocksdb.util.SizeUnit; - -public class RocksDBSample { - static { - RocksDB.loadLibrary(); - } - - public static void main(final String[] args) { - if (args.length < 1) { - System.out.println("usage: RocksDBSample db_path"); - System.exit(-1); - } - - final String db_path = args[0]; - final String db_path_not_found = db_path + "_not_found"; - - System.out.println("RocksDBSample"); - try (final Options options = new Options(); - final Filter bloomFilter = new BloomFilter(10); - final ReadOptions readOptions = new ReadOptions() - .setFillCache(false); - final Statistics stats = new Statistics(); - final RateLimiter rateLimiter = new RateLimiter(10000000,10000, 10)) { - - try (final RocksDB db = RocksDB.open(options, db_path_not_found)) { - assert (false); - } catch (final RocksDBException e) { - System.out.format("Caught the expected exception -- %s\n", e); - } - - try { - options.setCreateIfMissing(true) - .setStatistics(stats) - .setWriteBufferSize(8 * SizeUnit.KB) - .setMaxWriteBufferNumber(3) - .setMaxBackgroundJobs(10) - .setCompressionType(CompressionType.ZLIB_COMPRESSION) - .setCompactionStyle(CompactionStyle.UNIVERSAL); - } catch (final IllegalArgumentException e) { - assert (false); - } - - assert (options.createIfMissing() == true); - assert (options.writeBufferSize() == 8 * SizeUnit.KB); - assert (options.maxWriteBufferNumber() == 3); - assert (options.maxBackgroundJobs() == 10); - assert (options.compressionType() == CompressionType.ZLIB_COMPRESSION); - assert (options.compactionStyle() == CompactionStyle.UNIVERSAL); - - assert (options.memTableFactoryName().equals("SkipListFactory")); - options.setMemTableConfig( - new HashSkipListMemTableConfig() - .setHeight(4) - .setBranchingFactor(4) - .setBucketCount(2000000)); - assert (options.memTableFactoryName().equals("HashSkipListRepFactory")); - - options.setMemTableConfig( - new HashLinkedListMemTableConfig() - .setBucketCount(100000)); - assert (options.memTableFactoryName().equals("HashLinkedListRepFactory")); - - options.setMemTableConfig( - new VectorMemTableConfig().setReservedSize(10000)); - assert (options.memTableFactoryName().equals("VectorRepFactory")); - - options.setMemTableConfig(new SkipListMemTableConfig()); - assert (options.memTableFactoryName().equals("SkipListFactory")); - - options.setTableFormatConfig(new PlainTableConfig()); - // Plain-Table requires mmap read - options.setAllowMmapReads(true); - assert (options.tableFactoryName().equals("PlainTable")); - - options.setRateLimiter(rateLimiter); - - final BlockBasedTableConfig table_options = new BlockBasedTableConfig(); - Cache cache = new LRUCache(64 * 1024, 6); - table_options.setBlockCache(cache) - .setFilterPolicy(bloomFilter) - .setBlockSizeDeviation(5) - .setBlockRestartInterval(10) - .setCacheIndexAndFilterBlocks(true); - - assert (table_options.blockSizeDeviation() == 5); - assert (table_options.blockRestartInterval() == 10); - assert (table_options.cacheIndexAndFilterBlocks() == true); - - options.setTableFormatConfig(table_options); - assert (options.tableFactoryName().equals("BlockBasedTable")); - - try (final RocksDB db = RocksDB.open(options, db_path)) { - db.put("hello".getBytes(), "world".getBytes()); - - final byte[] value = db.get("hello".getBytes()); - assert ("world".equals(new String(value))); - - final String str = db.getProperty("rocksdb.stats"); - assert (str != null && !str.equals("")); - } catch (final RocksDBException e) { - System.out.format("[ERROR] caught the unexpected exception -- %s\n", e); - assert (false); - } - - try (final RocksDB db = RocksDB.open(options, db_path)) { - db.put("hello".getBytes(), "world".getBytes()); - byte[] value = db.get("hello".getBytes()); - System.out.format("Get('hello') = %s\n", - new String(value)); - - for (int i = 1; i <= 9; ++i) { - for (int j = 1; j <= 9; ++j) { - db.put(String.format("%dx%d", i, j).getBytes(), - String.format("%d", i * j).getBytes()); - } - } - - for (int i = 1; i <= 9; ++i) { - for (int j = 1; j <= 9; ++j) { - System.out.format("%s ", new String(db.get( - String.format("%dx%d", i, j).getBytes()))); - } - System.out.println(""); - } - - // write batch test - try (final WriteOptions writeOpt = new WriteOptions()) { - for (int i = 10; i <= 19; ++i) { - try (final WriteBatch batch = new WriteBatch()) { - for (int j = 10; j <= 19; ++j) { - batch.put(String.format("%dx%d", i, j).getBytes(), - String.format("%d", i * j).getBytes()); - } - db.write(writeOpt, batch); - } - } - } - for (int i = 10; i <= 19; ++i) { - for (int j = 10; j <= 19; ++j) { - assert (new String( - db.get(String.format("%dx%d", i, j).getBytes())).equals( - String.format("%d", i * j))); - System.out.format("%s ", new String(db.get( - String.format("%dx%d", i, j).getBytes()))); - } - System.out.println(""); - } - - value = db.get("1x1".getBytes()); - assert (value != null); - value = db.get("world".getBytes()); - assert (value == null); - value = db.get(readOptions, "world".getBytes()); - assert (value == null); - - final byte[] testKey = "asdf".getBytes(); - final byte[] testValue = - "asdfghjkl;'?> insufficientArray.length); - len = db.get("asdfjkl;".getBytes(), enoughArray); - assert (len == RocksDB.NOT_FOUND); - len = db.get(testKey, enoughArray); - assert (len == testValue.length); - - len = db.get(readOptions, testKey, insufficientArray); - assert (len > insufficientArray.length); - len = db.get(readOptions, "asdfjkl;".getBytes(), enoughArray); - assert (len == RocksDB.NOT_FOUND); - len = db.get(readOptions, testKey, enoughArray); - assert (len == testValue.length); - - db.delete(testKey); - len = db.get(testKey, enoughArray); - assert (len == RocksDB.NOT_FOUND); - - // repeat the test with WriteOptions - try (final WriteOptions writeOpts = new WriteOptions()) { - writeOpts.setSync(true); - writeOpts.setDisableWAL(false); - db.put(writeOpts, testKey, testValue); - len = db.get(testKey, enoughArray); - assert (len == testValue.length); - assert (new String(testValue).equals( - new String(enoughArray, 0, len))); - } - - try { - for (final TickerType statsType : TickerType.values()) { - if (statsType != TickerType.TICKER_ENUM_MAX) { - stats.getTickerCount(statsType); - } - } - System.out.println("getTickerCount() passed."); - } catch (final Exception e) { - System.out.println("Failed in call to getTickerCount()"); - assert (false); //Should never reach here. - } - - try { - for (final HistogramType histogramType : HistogramType.values()) { - if (histogramType != HistogramType.HISTOGRAM_ENUM_MAX) { - HistogramData data = stats.getHistogramData(histogramType); - } - } - System.out.println("getHistogramData() passed."); - } catch (final Exception e) { - System.out.println("Failed in call to getHistogramData()"); - assert (false); //Should never reach here. - } - - try (final RocksIterator iterator = db.newIterator()) { - - boolean seekToFirstPassed = false; - for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { - iterator.status(); - assert (iterator.key() != null); - assert (iterator.value() != null); - seekToFirstPassed = true; - } - if (seekToFirstPassed) { - System.out.println("iterator seekToFirst tests passed."); - } - - boolean seekToLastPassed = false; - for (iterator.seekToLast(); iterator.isValid(); iterator.prev()) { - iterator.status(); - assert (iterator.key() != null); - assert (iterator.value() != null); - seekToLastPassed = true; - } - - if (seekToLastPassed) { - System.out.println("iterator seekToLastPassed tests passed."); - } - - iterator.seekToFirst(); - iterator.seek(iterator.key()); - assert (iterator.key() != null); - assert (iterator.value() != null); - - System.out.println("iterator seek test passed."); - - } - System.out.println("iterator tests passed."); - - final List keys = new ArrayList<>(); - try (final RocksIterator iterator = db.newIterator()) { - for (iterator.seekToLast(); iterator.isValid(); iterator.prev()) { - keys.add(iterator.key()); - } - } - - List values = db.multiGetAsList(keys); - assert (values.size() == keys.size()); - for (final byte[] value1 : values) { - assert (value1 != null); - } - - values = db.multiGetAsList(new ReadOptions(), keys); - assert (values.size() == keys.size()); - for (final byte[] value1 : values) { - assert (value1 != null); - } - } catch (final RocksDBException e) { - System.err.println(e); - } - } - } -} diff --git a/java/samples/src/main/java/TransactionSample.java b/java/samples/src/main/java/TransactionSample.java deleted file mode 100644 index b88a68f12..000000000 --- a/java/samples/src/main/java/TransactionSample.java +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -import org.rocksdb.*; - -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * Demonstrates using Transactions on a TransactionDB with - * varying isolation guarantees - */ -public class TransactionSample { - private static final String dbPath = "/tmp/rocksdb_transaction_example"; - - public static final void main(final String args[]) throws RocksDBException { - - try(final Options options = new Options() - .setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDb = - TransactionDB.open(options, txnDbOptions, dbPath)) { - - try (final WriteOptions writeOptions = new WriteOptions(); - final ReadOptions readOptions = new ReadOptions()) { - - //////////////////////////////////////////////////////// - // - // Simple Transaction Example ("Read Committed") - // - //////////////////////////////////////////////////////// - readCommitted(txnDb, writeOptions, readOptions); - - - //////////////////////////////////////////////////////// - // - // "Repeatable Read" (Snapshot Isolation) Example - // -- Using a single Snapshot - // - //////////////////////////////////////////////////////// - repeatableRead(txnDb, writeOptions, readOptions); - - - //////////////////////////////////////////////////////// - // - // "Read Committed" (Monotonic Atomic Views) Example - // --Using multiple Snapshots - // - //////////////////////////////////////////////////////// - readCommitted_monotonicAtomicViews(txnDb, writeOptions, readOptions); - } - } - } - - /** - * Demonstrates "Read Committed" isolation - */ - private static void readCommitted(final TransactionDB txnDb, - final WriteOptions writeOptions, final ReadOptions readOptions) - throws RocksDBException { - final byte key1[] = "abc".getBytes(UTF_8); - final byte value1[] = "def".getBytes(UTF_8); - - final byte key2[] = "xyz".getBytes(UTF_8); - final byte value2[] = "zzz".getBytes(UTF_8); - - // Start a transaction - try(final Transaction txn = txnDb.beginTransaction(writeOptions)) { - // Read a key in this transaction - byte[] value = txn.get(readOptions, key1); - assert(value == null); - - // Write a key in this transaction - txn.put(key1, value1); - - // Read a key OUTSIDE this transaction. Does not affect txn. - value = txnDb.get(readOptions, key1); - assert(value == null); - - // Write a key OUTSIDE of this transaction. - // Does not affect txn since this is an unrelated key. - // If we wrote key 'abc' here, the transaction would fail to commit. - txnDb.put(writeOptions, key2, value2); - - // Commit transaction - txn.commit(); - } - } - - /** - * Demonstrates "Repeatable Read" (Snapshot Isolation) isolation - */ - private static void repeatableRead(final TransactionDB txnDb, - final WriteOptions writeOptions, final ReadOptions readOptions) - throws RocksDBException { - - final byte key1[] = "ghi".getBytes(UTF_8); - final byte value1[] = "jkl".getBytes(UTF_8); - - // Set a snapshot at start of transaction by setting setSnapshot(true) - try(final TransactionOptions txnOptions = new TransactionOptions() - .setSetSnapshot(true); - final Transaction txn = - txnDb.beginTransaction(writeOptions, txnOptions)) { - - final Snapshot snapshot = txn.getSnapshot(); - - // Write a key OUTSIDE of transaction - txnDb.put(writeOptions, key1, value1); - - // Attempt to read a key using the snapshot. This will fail since - // the previous write outside this txn conflicts with this read. - readOptions.setSnapshot(snapshot); - - try { - final byte[] value = txn.getForUpdate(readOptions, key1, true); - throw new IllegalStateException(); - } catch(final RocksDBException e) { - assert(e.getStatus().getCode() == Status.Code.Busy); - } - - txn.rollback(); - } finally { - // Clear snapshot from read options since it is no longer valid - readOptions.setSnapshot(null); - } - } - - /** - * Demonstrates "Read Committed" (Monotonic Atomic Views) isolation - * - * In this example, we set the snapshot multiple times. This is probably - * only necessary if you have very strict isolation requirements to - * implement. - */ - private static void readCommitted_monotonicAtomicViews( - final TransactionDB txnDb, final WriteOptions writeOptions, - final ReadOptions readOptions) throws RocksDBException { - - final byte keyX[] = "x".getBytes(UTF_8); - final byte valueX[] = "x".getBytes(UTF_8); - - final byte keyY[] = "y".getBytes(UTF_8); - final byte valueY[] = "y".getBytes(UTF_8); - - try (final TransactionOptions txnOptions = new TransactionOptions() - .setSetSnapshot(true); - final Transaction txn = - txnDb.beginTransaction(writeOptions, txnOptions)) { - - // Do some reads and writes to key "x" - Snapshot snapshot = txnDb.getSnapshot(); - readOptions.setSnapshot(snapshot); - byte[] value = txn.get(readOptions, keyX); - txn.put(valueX, valueX); - - // Do a write outside of the transaction to key "y" - txnDb.put(writeOptions, keyY, valueY); - - // Set a new snapshot in the transaction - txn.setSnapshot(); - txn.setSavePoint(); - snapshot = txnDb.getSnapshot(); - readOptions.setSnapshot(snapshot); - - // Do some reads and writes to key "y" - // Since the snapshot was advanced, the write done outside of the - // transaction does not conflict. - value = txn.getForUpdate(readOptions, keyY, true); - txn.put(keyY, valueY); - - // Decide we want to revert the last write from this transaction. - txn.rollbackToSavePoint(); - - // Commit. - txn.commit(); - } finally { - // Clear snapshot from read options since it is no longer valid - readOptions.setSnapshot(null); - } - } -} diff --git a/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java b/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java deleted file mode 100644 index 2f0d4f3ca..000000000 --- a/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * A CompactionFilter allows an application to modify/delete a key-value at - * the time of compaction. - * - * At present we just permit an overriding Java class to wrap a C++ - * implementation - */ -public abstract class AbstractCompactionFilter> - extends RocksObject { - - public static class Context { - private final boolean fullCompaction; - private final boolean manualCompaction; - - public Context(final boolean fullCompaction, final boolean manualCompaction) { - this.fullCompaction = fullCompaction; - this.manualCompaction = manualCompaction; - } - - /** - * Does this compaction run include all data files - * - * @return true if this is a full compaction run - */ - public boolean isFullCompaction() { - return fullCompaction; - } - - /** - * Is this compaction requested by the client, - * or is it occurring as an automatic compaction process - * - * @return true if the compaction was initiated by the client - */ - public boolean isManualCompaction() { - return manualCompaction; - } - } - - protected AbstractCompactionFilter(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Deletes underlying C++ compaction pointer. - * - * Note that this function should be called only after all - * RocksDB instances referencing the compaction filter are closed. - * Otherwise an undefined behavior will occur. - */ - @Override - protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java b/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java deleted file mode 100644 index 380b4461d..000000000 --- a/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Each compaction will create a new {@link AbstractCompactionFilter} - * allowing the application to know about different compactions - * - * @param The concrete type of the compaction filter - */ -public abstract class AbstractCompactionFilterFactory> - extends RocksCallbackObject { - - public AbstractCompactionFilterFactory() { - super(null); - } - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewCompactionFilterFactory0(); - } - - /** - * Called from JNI, see compaction_filter_factory_jnicallback.cc - * - * @param fullCompaction {@link AbstractCompactionFilter.Context#fullCompaction} - * @param manualCompaction {@link AbstractCompactionFilter.Context#manualCompaction} - * - * @return native handle of the CompactionFilter - */ - private long createCompactionFilter(final boolean fullCompaction, - final boolean manualCompaction) { - final T filter = createCompactionFilter( - new AbstractCompactionFilter.Context(fullCompaction, manualCompaction)); - - // CompactionFilterFactory::CreateCompactionFilter returns a std::unique_ptr - // which therefore has ownership of the underlying native object - filter.disOwnNativeHandle(); - - return filter.nativeHandle_; - } - - /** - * Create a new compaction filter - * - * @param context The context describing the need for a new compaction filter - * - * @return A new instance of {@link AbstractCompactionFilter} - */ - public abstract T createCompactionFilter( - final AbstractCompactionFilter.Context context); - - /** - * A name which identifies this compaction filter - * - * The name will be printed to the LOG file on start up for diagnosis - * - * @return name which identifies this compaction filter. - */ - public abstract String name(); - - /** - * We override {@link RocksCallbackObject#disposeInternal()} - * as disposing of a rocksdb::AbstractCompactionFilterFactory requires - * a slightly different approach as it is a std::shared_ptr - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - private native long createNewCompactionFilterFactory0(); - private native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/AbstractComparator.java b/java/src/main/java/org/rocksdb/AbstractComparator.java deleted file mode 100644 index a89e79048..000000000 --- a/java/src/main/java/org/rocksdb/AbstractComparator.java +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * Comparators are used by RocksDB to determine - * the ordering of keys. - * - * Implementations of Comparators in Java should extend this class. - */ -public abstract class AbstractComparator - extends RocksCallbackObject { - - AbstractComparator() { - super(); - } - - protected AbstractComparator(final ComparatorOptions comparatorOptions) { - super(comparatorOptions.nativeHandle_); - } - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewComparator(nativeParameterHandles[0]); - } - - /** - * Get the type of this comparator. - * - * Used for determining the correct C++ cast in native code. - * - * @return The type of the comparator. - */ - ComparatorType getComparatorType() { - return ComparatorType.JAVA_COMPARATOR; - } - - /** - * The name of the comparator. Used to check for comparator - * mismatches (i.e., a DB created with one comparator is - * accessed using a different comparator). - * - * A new name should be used whenever - * the comparator implementation changes in a way that will cause - * the relative ordering of any two keys to change. - * - * Names starting with "rocksdb." are reserved and should not be used. - * - * @return The name of this comparator implementation - */ - public abstract String name(); - - /** - * Three-way key comparison. Implementations should provide a - * total order - * on keys that might be passed to it. - * - * The implementation may modify the {@code ByteBuffer}s passed in, though - * it would be unconventional to modify the "limit" or any of the - * underlying bytes. As a callback, RocksJava will ensure that {@code a} - * is a different instance from {@code b}. - * - * @param a buffer containing the first key in its "remaining" elements - * @param b buffer containing the second key in its "remaining" elements - * - * @return Should return either: - * 1) < 0 if "a" < "b" - * 2) == 0 if "a" == "b" - * 3) > 0 if "a" > "b" - */ - public abstract int compare(final ByteBuffer a, final ByteBuffer b); - - /** - *

    Used to reduce the space requirements - * for internal data structures like index blocks.

    - * - *

    If start < limit, you may modify start which is a - * shorter string in [start, limit).

    - * - * If you modify start, it is expected that you set the byte buffer so that - * a subsequent read of start.remaining() bytes from start.position() - * to start.limit() will obtain the new start value. - * - *

    Simple comparator implementations may return with start unchanged. - * i.e., an implementation of this method that does nothing is correct.

    - * - * @param start the start - * @param limit the limit - */ - public void findShortestSeparator(final ByteBuffer start, - final ByteBuffer limit) { - // no-op - } - - /** - *

    Used to reduce the space requirements - * for internal data structures like index blocks.

    - * - *

    You may change key to a shorter key (key1) where - * key1 ≥ key.

    - * - *

    Simple comparator implementations may return the key unchanged. - * i.e., an implementation of - * this method that does nothing is correct.

    - * - * @param key the key - */ - public void findShortSuccessor(final ByteBuffer key) { - // no-op - } - - public final boolean usingDirectBuffers() { - return usingDirectBuffers(nativeHandle_); - } - - private native boolean usingDirectBuffers(final long nativeHandle); - - private native long createNewComparator(final long comparatorOptionsHandle); -} diff --git a/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java b/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java deleted file mode 100644 index b732d2495..000000000 --- a/java/src/main/java/org/rocksdb/AbstractComparatorJniBridge.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * This class is intentionally private, - * it holds methods which are called - * from C++ to interact with a Comparator - * written in Java. - * - * Placing these bridge methods in this - * class keeps the API of the - * {@link org.rocksdb.AbstractComparator} clean. - */ -class AbstractComparatorJniBridge { - - /** - * Only called from JNI. - * - * Simply a bridge to calling - * {@link AbstractComparator#compare(ByteBuffer, ByteBuffer)}, - * which ensures that the byte buffer lengths are correct - * before and after the call. - * - * @param comparator the comparator object on which to - * call {@link AbstractComparator#compare(ByteBuffer, ByteBuffer)} - * @param a buffer access to first key - * @param aLen the length of the a key, - * may be smaller than the buffer {@code a} - * @param b buffer access to second key - * @param bLen the length of the b key, - * may be smaller than the buffer {@code b} - * - * @return the result of the comparison - */ - private static int compareInternal( - final AbstractComparator comparator, - final ByteBuffer a, final int aLen, - final ByteBuffer b, final int bLen) { - if (aLen != -1) { - a.mark(); - a.limit(aLen); - } - if (bLen != -1) { - b.mark(); - b.limit(bLen); - } - - final int c = comparator.compare(a, b); - - if (aLen != -1) { - a.reset(); - } - if (bLen != -1) { - b.reset(); - } - - return c; - } - - /** - * Only called from JNI. - * - * Simply a bridge to calling - * {@link AbstractComparator#findShortestSeparator(ByteBuffer, ByteBuffer)}, - * which ensures that the byte buffer lengths are correct - * before the call. - * - * @param comparator the comparator object on which to - * call {@link AbstractComparator#findShortestSeparator(ByteBuffer, ByteBuffer)} - * @param start buffer access to the start key - * @param startLen the length of the start key, - * may be smaller than the buffer {@code start} - * @param limit buffer access to the limit key - * @param limitLen the length of the limit key, - * may be smaller than the buffer {@code limit} - * - * @return either {@code startLen} if the start key is unchanged, otherwise - * the new length of the start key - */ - private static int findShortestSeparatorInternal( - final AbstractComparator comparator, - final ByteBuffer start, final int startLen, - final ByteBuffer limit, final int limitLen) { - if (startLen != -1) { - start.limit(startLen); - } - if (limitLen != -1) { - limit.limit(limitLen); - } - comparator.findShortestSeparator(start, limit); - return start.remaining(); - } - - /** - * Only called from JNI. - * - * Simply a bridge to calling - * {@link AbstractComparator#findShortestSeparator(ByteBuffer, ByteBuffer)}, - * which ensures that the byte buffer length is correct - * before the call. - * - * @param comparator the comparator object on which to - * call {@link AbstractComparator#findShortSuccessor(ByteBuffer)} - * @param key buffer access to the key - * @param keyLen the length of the key, - * may be smaller than the buffer {@code key} - * - * @return either keyLen if the key is unchanged, otherwise the new length of the key - */ - private static int findShortSuccessorInternal( - final AbstractComparator comparator, - final ByteBuffer key, final int keyLen) { - if (keyLen != -1) { - key.limit(keyLen); - } - comparator.findShortSuccessor(key); - return key.remaining(); - } -} diff --git a/java/src/main/java/org/rocksdb/AbstractEventListener.java b/java/src/main/java/org/rocksdb/AbstractEventListener.java deleted file mode 100644 index 6698acf88..000000000 --- a/java/src/main/java/org/rocksdb/AbstractEventListener.java +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.rocksdb.AbstractEventListener.EnabledEventCallback.*; - -/** - * Base class for Event Listeners. - */ -public abstract class AbstractEventListener extends RocksCallbackObject implements EventListener { - public enum EnabledEventCallback { - ON_FLUSH_COMPLETED((byte) 0x0), - ON_FLUSH_BEGIN((byte) 0x1), - ON_TABLE_FILE_DELETED((byte) 0x2), - ON_COMPACTION_BEGIN((byte) 0x3), - ON_COMPACTION_COMPLETED((byte) 0x4), - ON_TABLE_FILE_CREATED((byte) 0x5), - ON_TABLE_FILE_CREATION_STARTED((byte) 0x6), - ON_MEMTABLE_SEALED((byte) 0x7), - ON_COLUMN_FAMILY_HANDLE_DELETION_STARTED((byte) 0x8), - ON_EXTERNAL_FILE_INGESTED((byte) 0x9), - ON_BACKGROUND_ERROR((byte) 0xA), - ON_STALL_CONDITIONS_CHANGED((byte) 0xB), - ON_FILE_READ_FINISH((byte) 0xC), - ON_FILE_WRITE_FINISH((byte) 0xD), - ON_FILE_FLUSH_FINISH((byte) 0xE), - ON_FILE_SYNC_FINISH((byte) 0xF), - ON_FILE_RANGE_SYNC_FINISH((byte) 0x10), - ON_FILE_TRUNCATE_FINISH((byte) 0x11), - ON_FILE_CLOSE_FINISH((byte) 0x12), - SHOULD_BE_NOTIFIED_ON_FILE_IO((byte) 0x13), - ON_ERROR_RECOVERY_BEGIN((byte) 0x14), - ON_ERROR_RECOVERY_COMPLETED((byte) 0x15); - - private final byte value; - - EnabledEventCallback(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value - */ - byte getValue() { - return value; - } - - /** - * Get the EnabledEventCallbacks from the internal representation value. - * - * @return the enabled event callback. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static EnabledEventCallback fromValue(final byte value) { - for (final EnabledEventCallback enabledEventCallback : EnabledEventCallback.values()) { - if (enabledEventCallback.value == value) { - return enabledEventCallback; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for EnabledEventCallback: " + value); - } - } - - /** - * Creates an Event Listener that will - * received all callbacks from C++. - * - * If you don't need all callbacks, it is much more efficient to - * just register for the ones you need by calling - * {@link #AbstractEventListener(EnabledEventCallback...)} instead. - */ - protected AbstractEventListener() { - this(ON_FLUSH_COMPLETED, ON_FLUSH_BEGIN, ON_TABLE_FILE_DELETED, ON_COMPACTION_BEGIN, - ON_COMPACTION_COMPLETED, ON_TABLE_FILE_CREATED, ON_TABLE_FILE_CREATION_STARTED, - ON_MEMTABLE_SEALED, ON_COLUMN_FAMILY_HANDLE_DELETION_STARTED, ON_EXTERNAL_FILE_INGESTED, - ON_BACKGROUND_ERROR, ON_STALL_CONDITIONS_CHANGED, ON_FILE_READ_FINISH, ON_FILE_WRITE_FINISH, - ON_FILE_FLUSH_FINISH, ON_FILE_SYNC_FINISH, ON_FILE_RANGE_SYNC_FINISH, - ON_FILE_TRUNCATE_FINISH, ON_FILE_CLOSE_FINISH, SHOULD_BE_NOTIFIED_ON_FILE_IO, - ON_ERROR_RECOVERY_BEGIN, ON_ERROR_RECOVERY_COMPLETED); - } - - /** - * Creates an Event Listener that will - * receive only certain callbacks from C++. - * - * @param enabledEventCallbacks callbacks to enable in Java. - */ - protected AbstractEventListener(final EnabledEventCallback... enabledEventCallbacks) { - super(packToLong(enabledEventCallbacks)); - } - - /** - * Pack EnabledEventCallbacks to a long. - * - * @param enabledEventCallbacks the flags - * - * @return a long - */ - private static long packToLong(final EnabledEventCallback... enabledEventCallbacks) { - long l = 0; - for (int i = 0; i < enabledEventCallbacks.length; i++) { - l |= 1 << enabledEventCallbacks[i].getValue(); - } - return l; - } - - @Override - public void onFlushCompleted(final RocksDB db, final FlushJobInfo flushJobInfo) { - // no-op - } - - /** - * Called from JNI, proxy for - * {@link #onFlushCompleted(RocksDB, FlushJobInfo)}. - * - * @param dbHandle native handle of the database - * @param flushJobInfo the flush job info - */ - private void onFlushCompletedProxy(final long dbHandle, final FlushJobInfo flushJobInfo) { - final RocksDB db = new RocksDB(dbHandle); - db.disOwnNativeHandle(); // we don't own this! - onFlushCompleted(db, flushJobInfo); - } - - @Override - public void onFlushBegin(final RocksDB db, final FlushJobInfo flushJobInfo) { - // no-op - } - - /** - * Called from JNI, proxy for - * {@link #onFlushBegin(RocksDB, FlushJobInfo)}. - * - * @param dbHandle native handle of the database - * @param flushJobInfo the flush job info - */ - private void onFlushBeginProxy(final long dbHandle, final FlushJobInfo flushJobInfo) { - final RocksDB db = new RocksDB(dbHandle); - db.disOwnNativeHandle(); // we don't own this! - onFlushBegin(db, flushJobInfo); - } - - @Override - public void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo) { - // no-op - } - - @Override - public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - // no-op - } - - /** - * Called from JNI, proxy for - * {@link #onCompactionBegin(RocksDB, CompactionJobInfo)}. - * - * @param dbHandle native handle of the database - * @param compactionJobInfo the flush job info - */ - private void onCompactionBeginProxy( - final long dbHandle, final CompactionJobInfo compactionJobInfo) { - final RocksDB db = new RocksDB(dbHandle); - db.disOwnNativeHandle(); // we don't own this! - onCompactionBegin(db, compactionJobInfo); - } - - @Override - public void onCompactionCompleted(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - // no-op - } - - /** - * Called from JNI, proxy for - * {@link #onCompactionCompleted(RocksDB, CompactionJobInfo)}. - * - * @param dbHandle native handle of the database - * @param compactionJobInfo the flush job info - */ - private void onCompactionCompletedProxy( - final long dbHandle, final CompactionJobInfo compactionJobInfo) { - final RocksDB db = new RocksDB(dbHandle); - db.disOwnNativeHandle(); // we don't own this! - onCompactionCompleted(db, compactionJobInfo); - } - - @Override - public void onTableFileCreated(final TableFileCreationInfo tableFileCreationInfo) { - // no-op - } - - @Override - public void onTableFileCreationStarted( - final TableFileCreationBriefInfo tableFileCreationBriefInfo) { - // no-op - } - - @Override - public void onMemTableSealed(final MemTableInfo memTableInfo) { - // no-op - } - - @Override - public void onColumnFamilyHandleDeletionStarted(final ColumnFamilyHandle columnFamilyHandle) { - // no-op - } - - @Override - public void onExternalFileIngested( - final RocksDB db, final ExternalFileIngestionInfo externalFileIngestionInfo) { - // no-op - } - - /** - * Called from JNI, proxy for - * {@link #onExternalFileIngested(RocksDB, ExternalFileIngestionInfo)}. - * - * @param dbHandle native handle of the database - * @param externalFileIngestionInfo the flush job info - */ - private void onExternalFileIngestedProxy( - final long dbHandle, final ExternalFileIngestionInfo externalFileIngestionInfo) { - final RocksDB db = new RocksDB(dbHandle); - db.disOwnNativeHandle(); // we don't own this! - onExternalFileIngested(db, externalFileIngestionInfo); - } - - @Override - public void onBackgroundError( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError) { - // no-op - } - - /** - * Called from JNI, proxy for - * {@link #onBackgroundError(BackgroundErrorReason, Status)}. - * - * @param reasonByte byte value representing error reason - * @param backgroundError status with error code - */ - private void onBackgroundErrorProxy(final byte reasonByte, final Status backgroundError) { - onBackgroundError(BackgroundErrorReason.fromValue(reasonByte), backgroundError); - } - - @Override - public void onStallConditionsChanged(final WriteStallInfo writeStallInfo) { - // no-op - } - - @Override - public void onFileReadFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public void onFileWriteFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public void onFileFlushFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public void onFileSyncFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public void onFileRangeSyncFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public void onFileTruncateFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public void onFileCloseFinish(final FileOperationInfo fileOperationInfo) { - // no-op - } - - @Override - public boolean shouldBeNotifiedOnFileIO() { - return false; - } - - @Override - public boolean onErrorRecoveryBegin( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError) { - return true; - } - - /** - * Called from JNI, proxy for - * {@link #onErrorRecoveryBegin(BackgroundErrorReason, Status)}. - * - * @param reasonByte byte value representing error reason - * @param backgroundError status with error code - */ - private boolean onErrorRecoveryBeginProxy(final byte reasonByte, final Status backgroundError) { - return onErrorRecoveryBegin(BackgroundErrorReason.fromValue(reasonByte), backgroundError); - } - - @Override - public void onErrorRecoveryCompleted(final Status oldBackgroundError) { - // no-op - } - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewEventListener(nativeParameterHandles[0]); - } - - /** - * Deletes underlying C++ native callback object pointer - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - private native long createNewEventListener(final long enabledEventCallbackValues); - private native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java b/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java deleted file mode 100644 index 173d63e90..000000000 --- a/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2016, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Offers functionality for implementations of - * {@link AbstractNativeReference} which have an immutable reference to the - * underlying native C++ object - */ -//@ThreadSafe -public abstract class AbstractImmutableNativeReference - extends AbstractNativeReference { - - /** - * A flag indicating whether the current {@code AbstractNativeReference} is - * responsible to free the underlying C++ object - */ - protected final AtomicBoolean owningHandle_; - - protected AbstractImmutableNativeReference(final boolean owningHandle) { - this.owningHandle_ = new AtomicBoolean(owningHandle); - } - - @Override - public boolean isOwningHandle() { - return owningHandle_.get(); - } - - /** - * Releases this {@code AbstractNativeReference} from the responsibility of - * freeing the underlying native C++ object - *

    - * This will prevent the object from attempting to delete the underlying - * native object in {@code close()}. This must be used when another object - * takes over ownership of the native object or both will attempt to delete - * the underlying object when closed. - *

    - * When {@code disOwnNativeHandle()} is called, {@code close()} will - * subsequently take no action. As a result, incorrect use of this function - * may cause a memory leak. - *

    - */ - protected final void disOwnNativeHandle() { - owningHandle_.set(false); - } - - @Override - public void close() { - if (owningHandle_.compareAndSet(true, false)) { - disposeInternal(); - } - } - - /** - * The helper function of {@link AbstractImmutableNativeReference#close()} - * which all subclasses of {@code AbstractImmutableNativeReference} must - * implement to release their underlying native C++ objects. - */ - protected abstract void disposeInternal(); -} diff --git a/java/src/main/java/org/rocksdb/AbstractMutableOptions.java b/java/src/main/java/org/rocksdb/AbstractMutableOptions.java deleted file mode 100644 index 7189272b8..000000000 --- a/java/src/main/java/org/rocksdb/AbstractMutableOptions.java +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import java.util.*; - -public abstract class AbstractMutableOptions { - - protected static final String KEY_VALUE_PAIR_SEPARATOR = ";"; - protected static final char KEY_VALUE_SEPARATOR = '='; - static final String INT_ARRAY_INT_SEPARATOR = ":"; - - protected final String[] keys; - private final String[] values; - - /** - * User must use builder pattern, or parser. - * - * @param keys the keys - * @param values the values - */ - protected AbstractMutableOptions(final String[] keys, final String[] values) { - this.keys = keys; - this.values = values; - } - - String[] getKeys() { - return keys; - } - - String[] getValues() { - return values; - } - - /** - * Returns a string representation of MutableOptions which - * is suitable for consumption by {@code #parse(String)}. - * - * @return String representation of MutableOptions - */ - @Override - public String toString() { - final StringBuilder buffer = new StringBuilder(); - for(int i = 0; i < keys.length; i++) { - buffer - .append(keys[i]) - .append(KEY_VALUE_SEPARATOR) - .append(values[i]); - - if(i + 1 < keys.length) { - buffer.append(KEY_VALUE_PAIR_SEPARATOR); - } - } - return buffer.toString(); - } - - public static abstract class AbstractMutableOptionsBuilder< - T extends AbstractMutableOptions, - U extends AbstractMutableOptionsBuilder, - K extends MutableOptionKey> { - - private final Map> options = new LinkedHashMap<>(); - private final List unknown = new ArrayList<>(); - - protected abstract U self(); - - /** - * Get all of the possible keys - * - * @return A map of all keys, indexed by name. - */ - protected abstract Map allKeys(); - - /** - * Construct a sub-class instance of {@link AbstractMutableOptions}. - * - * @param keys the keys - * @param values the values - * - * @return an instance of the options. - */ - protected abstract T build(final String[] keys, final String[] values); - - public T build() { - final String[] keys = new String[options.size()]; - final String[] values = new String[options.size()]; - - int i = 0; - for (final Map.Entry> option : options.entrySet()) { - keys[i] = option.getKey().name(); - values[i] = option.getValue().asString(); - i++; - } - - return build(keys, values); - } - - protected U setDouble( - final K key, final double value) { - if (key.getValueType() != MutableOptionKey.ValueType.DOUBLE) { - throw new IllegalArgumentException( - key + " does not accept a double value"); - } - options.put(key, MutableOptionValue.fromDouble(value)); - return self(); - } - - protected double getDouble(final K key) - throws NoSuchElementException, NumberFormatException { - final MutableOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asDouble(); - } - - protected U setLong( - final K key, final long value) { - if(key.getValueType() != MutableOptionKey.ValueType.LONG) { - throw new IllegalArgumentException( - key + " does not accept a long value"); - } - options.put(key, MutableOptionValue.fromLong(value)); - return self(); - } - - protected long getLong(final K key) - throws NoSuchElementException, NumberFormatException { - final MutableOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asLong(); - } - - protected U setInt( - final K key, final int value) { - if(key.getValueType() != MutableOptionKey.ValueType.INT) { - throw new IllegalArgumentException( - key + " does not accept an integer value"); - } - options.put(key, MutableOptionValue.fromInt(value)); - return self(); - } - - protected int getInt(final K key) - throws NoSuchElementException, NumberFormatException { - final MutableOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asInt(); - } - - protected U setBoolean( - final K key, final boolean value) { - if(key.getValueType() != MutableOptionKey.ValueType.BOOLEAN) { - throw new IllegalArgumentException( - key + " does not accept a boolean value"); - } - options.put(key, MutableOptionValue.fromBoolean(value)); - return self(); - } - - protected boolean getBoolean(final K key) - throws NoSuchElementException, NumberFormatException { - final MutableOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asBoolean(); - } - - protected U setIntArray( - final K key, final int[] value) { - if(key.getValueType() != MutableOptionKey.ValueType.INT_ARRAY) { - throw new IllegalArgumentException( - key + " does not accept an int array value"); - } - options.put(key, MutableOptionValue.fromIntArray(value)); - return self(); - } - - protected int[] getIntArray(final K key) - throws NoSuchElementException, NumberFormatException { - final MutableOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asIntArray(); - } - - protected > U setEnum( - final K key, final N value) { - if(key.getValueType() != MutableOptionKey.ValueType.ENUM) { - throw new IllegalArgumentException( - key + " does not accept a Enum value"); - } - options.put(key, MutableOptionValue.fromEnum(value)); - return self(); - } - - @SuppressWarnings("unchecked") - protected > N getEnum(final K key) - throws NoSuchElementException, NumberFormatException { - final MutableOptionValue value = options.get(key); - if (value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - - if (!(value instanceof MutableOptionValue.MutableOptionEnumValue)) { - throw new NoSuchElementException(key.name() + " is not of Enum type"); - } - - return ((MutableOptionValue.MutableOptionEnumValue) value).asObject(); - } - - /** - * Parse a string into a long value, accepting values expressed as a double (such as 9.00) which - * are meant to be a long, not a double - * - * @param value the string containing a value which represents a long - * @return the long value of the parsed string - */ - private long parseAsLong(final String value) { - try { - return Long.parseLong(value); - } catch (NumberFormatException nfe) { - final double doubleValue = Double.parseDouble(value); - if (doubleValue != Math.round(doubleValue)) - throw new IllegalArgumentException("Unable to parse or round " + value + " to long"); - return Math.round(doubleValue); - } - } - - /** - * Parse a string into an int value, accepting values expressed as a double (such as 9.00) which - * are meant to be an int, not a double - * - * @param value the string containing a value which represents an int - * @return the int value of the parsed string - */ - private int parseAsInt(final String value) { - try { - return Integer.parseInt(value); - } catch (NumberFormatException nfe) { - final double doubleValue = Double.parseDouble(value); - if (doubleValue != Math.round(doubleValue)) - throw new IllegalArgumentException("Unable to parse or round " + value + " to int"); - return (int) Math.round(doubleValue); - } - } - - /** - * Constructs a builder for mutable column family options from a hierarchical parsed options - * string representation. The {@link OptionString.Parser} class output has been used to create a - * (name,value)-list; each value may be either a simple string or a (name, value)-list in turn. - * - * @param options a list of parsed option string objects - * @param ignoreUnknown what to do if the key is not one of the keys we expect - * - * @return a builder with the values from the parsed input set - * - * @throws IllegalArgumentException if an option value is of the wrong type, or a key is empty - */ - protected U fromParsed(final List options, final boolean ignoreUnknown) { - Objects.requireNonNull(options); - - for (final OptionString.Entry option : options) { - try { - if (option.key.isEmpty()) { - throw new IllegalArgumentException("options string is invalid: " + option); - } - fromOptionString(option, ignoreUnknown); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException( - "" + option.key + "=" + option.value + " - not a valid value for its type", nfe); - } - } - - return self(); - } - - /** - * Set a value in the builder from the supplied option string - * - * @param option the option key/value to add to this builder - * @param ignoreUnknown if this is not set, throw an exception when a key is not in the known - * set - * @return the same object, after adding options - * @throws IllegalArgumentException if the key is unkown, or a value has the wrong type/form - */ - private U fromOptionString(final OptionString.Entry option, final boolean ignoreUnknown) - throws IllegalArgumentException { - Objects.requireNonNull(option.key); - Objects.requireNonNull(option.value); - - final K key = allKeys().get(option.key); - if (key == null && ignoreUnknown) { - unknown.add(option); - return self(); - } else if (key == null) { - throw new IllegalArgumentException("Key: " + key + " is not a known option key"); - } - - if (!option.value.isList()) { - throw new IllegalArgumentException( - "Option: " + key + " is not a simple value or list, don't know how to parse it"); - } - - // Check that simple values are the single item in the array - if (key.getValueType() != MutableOptionKey.ValueType.INT_ARRAY) { - { - if (option.value.list.size() != 1) { - throw new IllegalArgumentException( - "Simple value does not have exactly 1 item: " + option.value.list); - } - } - } - - final List valueStrs = option.value.list; - final String valueStr = valueStrs.get(0); - - switch (key.getValueType()) { - case DOUBLE: - return setDouble(key, Double.parseDouble(valueStr)); - - case LONG: - return setLong(key, parseAsLong(valueStr)); - - case INT: - return setInt(key, parseAsInt(valueStr)); - - case BOOLEAN: - return setBoolean(key, Boolean.parseBoolean(valueStr)); - - case INT_ARRAY: - final int[] value = new int[valueStrs.size()]; - for (int i = 0; i < valueStrs.size(); i++) { - value[i] = Integer.parseInt(valueStrs.get(i)); - } - return setIntArray(key, value); - - case ENUM: - String optionName = key.name(); - if (optionName.equals("prepopulate_blob_cache")) { - final PrepopulateBlobCache prepopulateBlobCache = - PrepopulateBlobCache.getFromInternal(valueStr); - return setEnum(key, prepopulateBlobCache); - } else if (optionName.equals("compression") - || optionName.equals("blob_compression_type")) { - final CompressionType compressionType = CompressionType.getFromInternal(valueStr); - return setEnum(key, compressionType); - } else { - throw new IllegalArgumentException("Unknown enum type: " + key.name()); - } - - default: - throw new IllegalStateException(key + " has unknown value type: " + key.getValueType()); - } - } - - /** - * - * @return the list of keys encountered which were not known to the type being generated - */ - public List getUnknown() { - return new ArrayList<>(unknown); - } - } -} diff --git a/java/src/main/java/org/rocksdb/AbstractNativeReference.java b/java/src/main/java/org/rocksdb/AbstractNativeReference.java deleted file mode 100644 index 88b2963b6..000000000 --- a/java/src/main/java/org/rocksdb/AbstractNativeReference.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2016, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * AbstractNativeReference is the base-class of all RocksDB classes that have - * a pointer to a native C++ {@code rocksdb} object. - *

    - * AbstractNativeReference has the {@link AbstractNativeReference#close()} - * method, which frees its associated C++ object.

    - *

    - * This function should be called manually, or even better, called implicitly using a - * try-with-resources - * statement, when you are finished with the object. It is no longer - * called automatically during the regular Java GC process via - * {@link AbstractNativeReference#finalize()}.

    - *

    - * Explanatory note - When or if the Garbage Collector calls {@link Object#finalize()} - * depends on the JVM implementation and system conditions, which the programmer - * cannot control. In addition, the GC cannot see through the native reference - * long member variable (which is the C++ pointer value to the native object), - * and cannot know what other resources depend on it. - *

    - */ -public abstract class AbstractNativeReference implements AutoCloseable { - /** - * Returns true if we are responsible for freeing the underlying C++ object - * - * @return true if we are responsible to free the C++ object - */ - protected abstract boolean isOwningHandle(); - - /** - * Frees the underlying C++ object - *

    - * It is strong recommended that the developer calls this after they - * have finished using the object.

    - *

    - * Note, that once an instance of {@link AbstractNativeReference} has been - * closed, calling any of its functions will lead to undefined - * behavior.

    - */ - @Override public abstract void close(); -} diff --git a/java/src/main/java/org/rocksdb/AbstractRocksIterator.java b/java/src/main/java/org/rocksdb/AbstractRocksIterator.java deleted file mode 100644 index 1aade1b89..000000000 --- a/java/src/main/java/org/rocksdb/AbstractRocksIterator.java +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * Base class implementation for Rocks Iterators - * in the Java API - * - *

    Multiple threads can invoke const methods on an RocksIterator without - * external synchronization, but if any of the threads may call a - * non-const method, all threads accessing the same RocksIterator must use - * external synchronization.

    - * - * @param

    The type of the Parent Object from which the Rocks Iterator was - * created. This is used by disposeInternal to avoid double-free - * issues with the underlying C++ object. - * @see org.rocksdb.RocksObject - */ -public abstract class AbstractRocksIterator

    - extends RocksObject implements RocksIteratorInterface { - final P parent_; - - protected AbstractRocksIterator(final P parent, - final long nativeHandle) { - super(nativeHandle); - // parent must point to a valid RocksDB instance. - assert (parent != null); - // RocksIterator must hold a reference to the related parent instance - // to guarantee that while a GC cycle starts RocksIterator instances - // are freed prior to parent instances. - parent_ = parent; - } - - @Override - public boolean isValid() { - assert (isOwningHandle()); - return isValid0(nativeHandle_); - } - - @Override - public void seekToFirst() { - assert (isOwningHandle()); - seekToFirst0(nativeHandle_); - } - - @Override - public void seekToLast() { - assert (isOwningHandle()); - seekToLast0(nativeHandle_); - } - - @Override - public void seek(final byte[] target) { - assert (isOwningHandle()); - seek0(nativeHandle_, target, target.length); - } - - @Override - public void seekForPrev(final byte[] target) { - assert (isOwningHandle()); - seekForPrev0(nativeHandle_, target, target.length); - } - - @Override - public void seek(final ByteBuffer target) { - assert (isOwningHandle()); - if (target.isDirect()) { - seekDirect0(nativeHandle_, target, target.position(), target.remaining()); - } else { - seekByteArray0(nativeHandle_, target.array(), target.arrayOffset() + target.position(), - target.remaining()); - } - target.position(target.limit()); - } - - @Override - public void seekForPrev(final ByteBuffer target) { - assert (isOwningHandle()); - if (target.isDirect()) { - seekForPrevDirect0(nativeHandle_, target, target.position(), target.remaining()); - } else { - seekForPrevByteArray0(nativeHandle_, target.array(), target.arrayOffset() + target.position(), - target.remaining()); - } - target.position(target.limit()); - } - - @Override - public void next() { - assert (isOwningHandle()); - next0(nativeHandle_); - } - - @Override - public void prev() { - assert (isOwningHandle()); - prev0(nativeHandle_); - } - - @Override - public void refresh() throws RocksDBException { - assert (isOwningHandle()); - refresh0(nativeHandle_); - } - - @Override - public void status() throws RocksDBException { - assert (isOwningHandle()); - status0(nativeHandle_); - } - - /** - *

    Deletes underlying C++ iterator pointer.

    - * - *

    Note: the underlying handle can only be safely deleted if the parent - * instance related to a certain RocksIterator is still valid and initialized. - * Therefore {@code disposeInternal()} checks if the parent is initialized - * before freeing the native handle.

    - */ - @Override - protected void disposeInternal() { - if (parent_.isOwningHandle()) { - disposeInternal(nativeHandle_); - } - } - - abstract boolean isValid0(long handle); - abstract void seekToFirst0(long handle); - abstract void seekToLast0(long handle); - abstract void next0(long handle); - abstract void prev0(long handle); - abstract void refresh0(long handle) throws RocksDBException; - abstract void seek0(long handle, byte[] target, int targetLen); - abstract void seekForPrev0(long handle, byte[] target, int targetLen); - abstract void seekDirect0(long handle, ByteBuffer target, int targetOffset, int targetLen); - abstract void seekForPrevDirect0(long handle, ByteBuffer target, int targetOffset, int targetLen); - abstract void seekByteArray0(long handle, byte[] target, int targetOffset, int targetLen); - abstract void seekForPrevByteArray0(long handle, byte[] target, int targetOffset, int targetLen); - - abstract void status0(long handle) throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/AbstractSlice.java b/java/src/main/java/org/rocksdb/AbstractSlice.java deleted file mode 100644 index 5a22e2956..000000000 --- a/java/src/main/java/org/rocksdb/AbstractSlice.java +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Slices are used by RocksDB to provide - * efficient access to keys and values. - * - * This class is package private, implementers - * should extend either of the public abstract classes: - * @see org.rocksdb.Slice - * @see org.rocksdb.DirectSlice - * - * Regards the lifecycle of Java Slices in RocksDB: - * At present when you configure a Comparator from Java, it creates an - * instance of a C++ BaseComparatorJniCallback subclass and - * passes that to RocksDB as the comparator. That subclass of - * BaseComparatorJniCallback creates the Java - * @see org.rocksdb.AbstractSlice subclass Objects. When you dispose - * the Java @see org.rocksdb.AbstractComparator subclass, it disposes the - * C++ BaseComparatorJniCallback subclass, which in turn destroys the - * Java @see org.rocksdb.AbstractSlice subclass Objects. - */ -public abstract class AbstractSlice extends RocksMutableObject { - - protected AbstractSlice() { - super(); - } - - protected AbstractSlice(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Returns the data of the slice. - * - * @return The slice data. Note, the type of access is - * determined by the subclass - * @see org.rocksdb.AbstractSlice#data0(long) - */ - public T data() { - return data0(getNativeHandle()); - } - - /** - * Access to the data is provided by the - * subtype as it needs to handle the - * generic typing. - * - * @param handle The address of the underlying - * native object. - * - * @return Java typed access to the data. - */ - protected abstract T data0(long handle); - - /** - * Drops the specified {@code n} - * number of bytes from the start - * of the backing slice - * - * @param n The number of bytes to drop - */ - public abstract void removePrefix(final int n); - - /** - * Clears the backing slice - */ - public abstract void clear(); - - /** - * Return the length (in bytes) of the data. - * - * @return The length in bytes. - */ - public int size() { - return size0(getNativeHandle()); - } - - /** - * Return true if the length of the - * data is zero. - * - * @return true if there is no data, false otherwise. - */ - public boolean empty() { - return empty0(getNativeHandle()); - } - - /** - * Creates a string representation of the data - * - * @param hex When true, the representation - * will be encoded in hexadecimal. - * - * @return The string representation of the data. - */ - public String toString(final boolean hex) { - return toString0(getNativeHandle(), hex); - } - - @Override - public String toString() { - return toString(false); - } - - /** - * Three-way key comparison - * - * @param other A slice to compare against - * - * @return Should return either: - * 1) < 0 if this < other - * 2) == 0 if this == other - * 3) > 0 if this > other - */ - public int compare(final AbstractSlice other) { - assert (other != null); - if(!isOwningHandle()) { - return other.isOwningHandle() ? -1 : 0; - } else { - if(!other.isOwningHandle()) { - return 1; - } else { - return compare0(getNativeHandle(), other.getNativeHandle()); - } - } - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - /** - * If other is a slice object, then - * we defer to {@link #compare(AbstractSlice) compare} - * to check equality, otherwise we return false. - * - * @param other Object to test for equality - * - * @return true when {@code this.compare(other) == 0}, - * false otherwise. - */ - @Override - public boolean equals(final Object other) { - if (other != null && other instanceof AbstractSlice) { - return compare((AbstractSlice)other) == 0; - } else { - return false; - } - } - - /** - * Determines whether this slice starts with - * another slice - * - * @param prefix Another slice which may of may not - * be a prefix of this slice. - * - * @return true when this slice starts with the - * {@code prefix} slice - */ - public boolean startsWith(final AbstractSlice prefix) { - if (prefix != null) { - return startsWith0(getNativeHandle(), prefix.getNativeHandle()); - } else { - return false; - } - } - - protected native static long createNewSliceFromString(final String str); - private native int size0(long handle); - private native boolean empty0(long handle); - private native String toString0(long handle, boolean hex); - private native int compare0(long handle, long otherHandle); - private native boolean startsWith0(long handle, long otherHandle); - - /** - * Deletes underlying C++ slice pointer. - * Note that this function should be called only after all - * RocksDB instances referencing the slice are closed. - * Otherwise an undefined behavior will occur. - */ - @Override - protected final native void disposeInternal(final long handle); - -} diff --git a/java/src/main/java/org/rocksdb/AbstractTableFilter.java b/java/src/main/java/org/rocksdb/AbstractTableFilter.java deleted file mode 100644 index c696c3e13..000000000 --- a/java/src/main/java/org/rocksdb/AbstractTableFilter.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * Base class for Table Filters. - */ -public abstract class AbstractTableFilter - extends RocksCallbackObject implements TableFilter { - - protected AbstractTableFilter() { - super(); - } - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewTableFilter(); - } - - private native long createNewTableFilter(); -} diff --git a/java/src/main/java/org/rocksdb/AbstractTraceWriter.java b/java/src/main/java/org/rocksdb/AbstractTraceWriter.java deleted file mode 100644 index 806709b1f..000000000 --- a/java/src/main/java/org/rocksdb/AbstractTraceWriter.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Base class for TraceWriters. - */ -public abstract class AbstractTraceWriter - extends RocksCallbackObject implements TraceWriter { - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewTraceWriter(); - } - - /** - * Called from JNI, proxy for {@link TraceWriter#write(Slice)}. - * - * @param sliceHandle the native handle of the slice (which we do not own) - * - * @return short (2 bytes) where the first byte is the - * {@link Status.Code#getValue()} and the second byte is the - * {@link Status.SubCode#getValue()}. - */ - private short writeProxy(final long sliceHandle) { - try { - write(new Slice(sliceHandle)); - return statusToShort(Status.Code.Ok, Status.SubCode.None); - } catch (final RocksDBException e) { - return statusToShort(e.getStatus()); - } - } - - /** - * Called from JNI, proxy for {@link TraceWriter#closeWriter()}. - * - * @return short (2 bytes) where the first byte is the - * {@link Status.Code#getValue()} and the second byte is the - * {@link Status.SubCode#getValue()}. - */ - private short closeWriterProxy() { - try { - closeWriter(); - return statusToShort(Status.Code.Ok, Status.SubCode.None); - } catch (final RocksDBException e) { - return statusToShort(e.getStatus()); - } - } - - private static short statusToShort(/*@Nullable*/ final Status status) { - final Status.Code code = status != null && status.getCode() != null - ? status.getCode() - : Status.Code.IOError; - final Status.SubCode subCode = status != null && status.getSubCode() != null - ? status.getSubCode() - : Status.SubCode.None; - return statusToShort(code, subCode); - } - - private static short statusToShort(final Status.Code code, - final Status.SubCode subCode) { - short result = (short)(code.getValue() << 8); - return (short)(result | subCode.getValue()); - } - - private native long createNewTraceWriter(); -} diff --git a/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java b/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java deleted file mode 100644 index cbb49836d..000000000 --- a/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Provides notification to the caller of SetSnapshotOnNextOperation when - * the actual snapshot gets created - */ -public abstract class AbstractTransactionNotifier - extends RocksCallbackObject { - - protected AbstractTransactionNotifier() { - super(); - } - - /** - * Implement this method to receive notification when a snapshot is - * requested via {@link Transaction#setSnapshotOnNextOperation()}. - * - * @param newSnapshot the snapshot that has been created. - */ - public abstract void snapshotCreated(final Snapshot newSnapshot); - - /** - * This is intentionally private as it is the callback hook - * from JNI - */ - private void snapshotCreated(final long snapshotHandle) { - snapshotCreated(new Snapshot(snapshotHandle)); - } - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewTransactionNotifier(); - } - - private native long createNewTransactionNotifier(); - - /** - * Deletes underlying C++ TransactionNotifier pointer. - * - * Note that this function should be called only after all - * Transactions referencing the comparator are closed. - * Otherwise an undefined behavior will occur. - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/AbstractWalFilter.java b/java/src/main/java/org/rocksdb/AbstractWalFilter.java deleted file mode 100644 index d525045c6..000000000 --- a/java/src/main/java/org/rocksdb/AbstractWalFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Base class for WAL Filters. - */ -public abstract class AbstractWalFilter - extends RocksCallbackObject implements WalFilter { - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewWalFilter(); - } - - /** - * Called from JNI, proxy for - * {@link WalFilter#logRecordFound(long, String, WriteBatch, WriteBatch)}. - * - * @param logNumber the log handle. - * @param logFileName the log file name - * @param batchHandle the native handle of a WriteBatch (which we do not own) - * @param newBatchHandle the native handle of a - * new WriteBatch (which we do not own) - * - * @return short (2 bytes) where the first byte is the - * {@link WalFilter.LogRecordFoundResult#walProcessingOption} - * {@link WalFilter.LogRecordFoundResult#batchChanged}. - */ - private short logRecordFoundProxy(final long logNumber, - final String logFileName, final long batchHandle, - final long newBatchHandle) { - final LogRecordFoundResult logRecordFoundResult = logRecordFound( - logNumber, logFileName, new WriteBatch(batchHandle), - new WriteBatch(newBatchHandle)); - return logRecordFoundResultToShort(logRecordFoundResult); - } - - private static short logRecordFoundResultToShort( - final LogRecordFoundResult logRecordFoundResult) { - short result = (short)(logRecordFoundResult.walProcessingOption.getValue() << 8); - return (short)(result | (logRecordFoundResult.batchChanged ? 1 : 0)); - } - - private native long createNewWalFilter(); -} diff --git a/java/src/main/java/org/rocksdb/AbstractWriteBatch.java b/java/src/main/java/org/rocksdb/AbstractWriteBatch.java deleted file mode 100644 index 9527a2fd9..000000000 --- a/java/src/main/java/org/rocksdb/AbstractWriteBatch.java +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -public abstract class AbstractWriteBatch extends RocksObject - implements WriteBatchInterface { - - protected AbstractWriteBatch(final long nativeHandle) { - super(nativeHandle); - } - - @Override - public int count() { - return count0(nativeHandle_); - } - - @Override - public void put(byte[] key, byte[] value) throws RocksDBException { - put(nativeHandle_, key, key.length, value, value.length); - } - - @Override - public void put(ColumnFamilyHandle columnFamilyHandle, byte[] key, - byte[] value) throws RocksDBException { - put(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_); - } - - @Override - public void merge(byte[] key, byte[] value) throws RocksDBException { - merge(nativeHandle_, key, key.length, value, value.length); - } - - @Override - public void merge(ColumnFamilyHandle columnFamilyHandle, byte[] key, - byte[] value) throws RocksDBException { - merge(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_); - } - - @Override - public void put(final ByteBuffer key, final ByteBuffer value) throws RocksDBException { - assert key.isDirect() && value.isDirect(); - putDirect(nativeHandle_, key, key.position(), key.remaining(), value, value.position(), - value.remaining(), 0); - key.position(key.limit()); - value.position(value.limit()); - } - - @Override - public void put(ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key, - final ByteBuffer value) throws RocksDBException { - assert key.isDirect() && value.isDirect(); - putDirect(nativeHandle_, key, key.position(), key.remaining(), value, value.position(), - value.remaining(), columnFamilyHandle.nativeHandle_); - key.position(key.limit()); - value.position(value.limit()); - } - - @Override - public void delete(byte[] key) throws RocksDBException { - delete(nativeHandle_, key, key.length); - } - - @Override - public void delete(ColumnFamilyHandle columnFamilyHandle, byte[] key) - throws RocksDBException { - delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); - } - - @Override - public void delete(final ByteBuffer key) throws RocksDBException { - deleteDirect(nativeHandle_, key, key.position(), key.remaining(), 0); - key.position(key.limit()); - } - - @Override - public void delete(ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key) - throws RocksDBException { - deleteDirect( - nativeHandle_, key, key.position(), key.remaining(), columnFamilyHandle.nativeHandle_); - key.position(key.limit()); - } - - @Override - public void singleDelete(byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, key, key.length); - } - - @Override - public void singleDelete(ColumnFamilyHandle columnFamilyHandle, byte[] key) - throws RocksDBException { - singleDelete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); - } - - @Override - public void deleteRange(byte[] beginKey, byte[] endKey) - throws RocksDBException { - deleteRange(nativeHandle_, beginKey, beginKey.length, endKey, endKey.length); - } - - @Override - public void deleteRange(ColumnFamilyHandle columnFamilyHandle, - byte[] beginKey, byte[] endKey) throws RocksDBException { - deleteRange(nativeHandle_, beginKey, beginKey.length, endKey, endKey.length, - columnFamilyHandle.nativeHandle_); - } - - @Override - public void putLogData(byte[] blob) throws RocksDBException { - putLogData(nativeHandle_, blob, blob.length); - } - - @Override - public void clear() { - clear0(nativeHandle_); - } - - @Override - public void setSavePoint() { - setSavePoint0(nativeHandle_); - } - - @Override - public void rollbackToSavePoint() throws RocksDBException { - rollbackToSavePoint0(nativeHandle_); - } - - @Override - public void popSavePoint() throws RocksDBException { - popSavePoint(nativeHandle_); - } - - @Override - public void setMaxBytes(final long maxBytes) { - setMaxBytes(nativeHandle_, maxBytes); - } - - @Override - public WriteBatch getWriteBatch() { - return getWriteBatch(nativeHandle_); - } - - abstract int count0(final long handle); - - abstract void put(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen) throws RocksDBException; - - abstract void put(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen, final long cfHandle) - throws RocksDBException; - - abstract void putDirect(final long handle, final ByteBuffer key, final int keyOffset, - final int keyLength, final ByteBuffer value, final int valueOffset, final int valueLength, - final long cfHandle) throws RocksDBException; - - abstract void merge(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen) throws RocksDBException; - - abstract void merge(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen, final long cfHandle) - throws RocksDBException; - - abstract void delete(final long handle, final byte[] key, - final int keyLen) throws RocksDBException; - - abstract void delete(final long handle, final byte[] key, - final int keyLen, final long cfHandle) throws RocksDBException; - - abstract void singleDelete(final long handle, final byte[] key, final int keyLen) - throws RocksDBException; - - abstract void singleDelete(final long handle, final byte[] key, final int keyLen, - final long cfHandle) throws RocksDBException; - - abstract void deleteDirect(final long handle, final ByteBuffer key, final int keyOffset, - final int keyLength, final long cfHandle) throws RocksDBException; - - abstract void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen) throws RocksDBException; - - abstract void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen, final long cfHandle) throws RocksDBException; - - abstract void putLogData(final long handle, final byte[] blob, - final int blobLen) throws RocksDBException; - - abstract void clear0(final long handle); - - abstract void setSavePoint0(final long handle); - - abstract void rollbackToSavePoint0(final long handle); - - abstract void popSavePoint(final long handle) throws RocksDBException; - - abstract void setMaxBytes(final long handle, long maxBytes); - - abstract WriteBatch getWriteBatch(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/AccessHint.java b/java/src/main/java/org/rocksdb/AccessHint.java deleted file mode 100644 index 877c4ab39..000000000 --- a/java/src/main/java/org/rocksdb/AccessHint.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * File access pattern once a compaction has started - */ -public enum AccessHint { - NONE((byte)0x0), - NORMAL((byte)0x1), - SEQUENTIAL((byte)0x2), - WILLNEED((byte)0x3); - - private final byte value; - - AccessHint(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - *

    Get the AccessHint enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of AccessHint. - * - * @return AccessHint instance. - * - * @throws IllegalArgumentException if the access hint for the byteIdentifier - * cannot be found - */ - public static AccessHint getAccessHint(final byte byteIdentifier) { - for (final AccessHint accessHint : AccessHint.values()) { - if (accessHint.getValue() == byteIdentifier) { - return accessHint; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for AccessHint."); - } -} diff --git a/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java b/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java deleted file mode 100644 index 5338bc42d..000000000 --- a/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * Advanced Column Family Options which are not - * mutable (i.e. present in {@link AdvancedMutableColumnFamilyOptionsInterface} - * - * Taken from include/rocksdb/advanced_options.h - */ -public interface AdvancedColumnFamilyOptionsInterface< - T extends AdvancedColumnFamilyOptionsInterface> { - /** - * The minimum number of write buffers that will be merged together - * before writing to storage. If set to 1, then - * all write buffers are flushed to L0 as individual files and this increases - * read amplification because a get request has to check in all of these - * files. Also, an in-memory merge may result in writing lesser - * data to storage if there are duplicate records in each of these - * individual write buffers. Default: 1 - * - * @param minWriteBufferNumberToMerge the minimum number of write buffers - * that will be merged together. - * @return the reference to the current options. - */ - T setMinWriteBufferNumberToMerge( - int minWriteBufferNumberToMerge); - - /** - * The minimum number of write buffers that will be merged together - * before writing to storage. If set to 1, then - * all write buffers are flushed to L0 as individual files and this increases - * read amplification because a get request has to check in all of these - * files. Also, an in-memory merge may result in writing lesser - * data to storage if there are duplicate records in each of these - * individual write buffers. Default: 1 - * - * @return the minimum number of write buffers that will be merged together. - */ - int minWriteBufferNumberToMerge(); - - /** - * The total maximum number of write buffers to maintain in memory including - * copies of buffers that have already been flushed. Unlike - * {@link AdvancedMutableColumnFamilyOptionsInterface#maxWriteBufferNumber()}, - * this parameter does not affect flushing. - * This controls the minimum amount of write history that will be available - * in memory for conflict checking when Transactions are used. - * - * When using an OptimisticTransactionDB: - * If this value is too low, some transactions may fail at commit time due - * to not being able to determine whether there were any write conflicts. - * - * When using a TransactionDB: - * If Transaction::SetSnapshot is used, TransactionDB will read either - * in-memory write buffers or SST files to do write-conflict checking. - * Increasing this value can reduce the number of reads to SST files - * done for conflict detection. - * - * Setting this value to 0 will cause write buffers to be freed immediately - * after they are flushed. - * If this value is set to -1, - * {@link AdvancedMutableColumnFamilyOptionsInterface#maxWriteBufferNumber()} - * will be used. - * - * Default: - * If using a TransactionDB/OptimisticTransactionDB, the default value will - * be set to the value of - * {@link AdvancedMutableColumnFamilyOptionsInterface#maxWriteBufferNumber()} - * if it is not explicitly set by the user. Otherwise, the default is 0. - * - * @param maxWriteBufferNumberToMaintain The maximum number of write - * buffers to maintain - * - * @return the reference to the current options. - */ - T setMaxWriteBufferNumberToMaintain( - int maxWriteBufferNumberToMaintain); - - /** - * The total maximum number of write buffers to maintain in memory including - * copies of buffers that have already been flushed. - * - * @return maxWriteBufferNumberToMaintain The maximum number of write buffers - * to maintain - */ - int maxWriteBufferNumberToMaintain(); - - /** - * Allows thread-safe inplace updates. - * If inplace_callback function is not set, - * Put(key, new_value) will update inplace the existing_value iff - * * key exists in current memtable - * * new sizeof(new_value) ≤ sizeof(existing_value) - * * existing_value for that key is a put i.e. kTypeValue - * If inplace_callback function is set, check doc for inplace_callback. - * Default: false. - * - * @param inplaceUpdateSupport true if thread-safe inplace updates - * are allowed. - * @return the reference to the current options. - */ - T setInplaceUpdateSupport( - boolean inplaceUpdateSupport); - - /** - * Allows thread-safe inplace updates. - * If inplace_callback function is not set, - * Put(key, new_value) will update inplace the existing_value iff - * * key exists in current memtable - * * new sizeof(new_value) ≤ sizeof(existing_value) - * * existing_value for that key is a put i.e. kTypeValue - * If inplace_callback function is set, check doc for inplace_callback. - * Default: false. - * - * @return true if thread-safe inplace updates are allowed. - */ - boolean inplaceUpdateSupport(); - - /** - * Control locality of bloom filter probes to improve cache miss rate. - * This option only applies to memtable prefix bloom and plaintable - * prefix bloom. It essentially limits the max number of cache lines each - * bloom filter check can touch. - * This optimization is turned off when set to 0. The number should never - * be greater than number of probes. This option can boost performance - * for in-memory workload but should use with care since it can cause - * higher false positive rate. - * Default: 0 - * - * @param bloomLocality the level of locality of bloom-filter probes. - * @return the reference to the current options. - */ - T setBloomLocality(int bloomLocality); - - /** - * Control locality of bloom filter probes to improve cache miss rate. - * This option only applies to memtable prefix bloom and plaintable - * prefix bloom. It essentially limits the max number of cache lines each - * bloom filter check can touch. - * This optimization is turned off when set to 0. The number should never - * be greater than number of probes. This option can boost performance - * for in-memory workload but should use with care since it can cause - * higher false positive rate. - * Default: 0 - * - * @return the level of locality of bloom-filter probes. - * @see #setBloomLocality(int) - */ - int bloomLocality(); - - /** - *

    Different levels can have different compression - * policies. There are cases where most lower levels - * would like to use quick compression algorithms while - * the higher levels (which have more data) use - * compression algorithms that have better compression - * but could be slower. This array, if non-empty, should - * have an entry for each level of the database; - * these override the value specified in the previous - * field 'compression'.

    - * - * NOTICE - *

    If {@code level_compaction_dynamic_level_bytes=true}, - * {@code compression_per_level[0]} still determines {@code L0}, - * but other elements of the array are based on base level - * (the level {@code L0} files are merged to), and may not - * match the level users see from info log for metadata. - *

    - *

    If {@code L0} files are merged to {@code level - n}, - * then, for {@code i>0}, {@code compression_per_level[i]} - * determines compaction type for level {@code n+i-1}.

    - * - * Example - *

    For example, if we have 5 levels, and we determine to - * merge {@code L0} data to {@code L4} (which means {@code L1..L3} - * will be empty), then the new files go to {@code L4} uses - * compression type {@code compression_per_level[1]}.

    - * - *

    If now {@code L0} is merged to {@code L2}. Data goes to - * {@code L2} will be compressed according to - * {@code compression_per_level[1]}, {@code L3} using - * {@code compression_per_level[2]}and {@code L4} using - * {@code compression_per_level[3]}. Compaction for each - * level can change when data grows.

    - * - *

    Default: empty

    - * - * @param compressionLevels list of - * {@link org.rocksdb.CompressionType} instances. - * - * @return the reference to the current options. - */ - T setCompressionPerLevel( - List compressionLevels); - - /** - *

    Return the currently set {@link org.rocksdb.CompressionType} - * per instances.

    - * - *

    See: {@link #setCompressionPerLevel(java.util.List)}

    - * - * @return list of {@link org.rocksdb.CompressionType} - * instances. - */ - List compressionPerLevel(); - - /** - * Set the number of levels for this database - * If level-styled compaction is used, then this number determines - * the total number of levels. - * - * @param numLevels the number of levels. - * @return the reference to the current options. - */ - T setNumLevels(int numLevels); - - /** - * If level-styled compaction is used, then this number determines - * the total number of levels. - * - * @return the number of levels. - */ - int numLevels(); - - /** - *

    If {@code true}, RocksDB will pick target size of each level - * dynamically. We will pick a base level b >= 1. L0 will be - * directly merged into level b, instead of always into level 1. - * Level 1 to b-1 need to be empty. We try to pick b and its target - * size so that

    - * - *
      - *
    1. target size is in the range of - * (max_bytes_for_level_base / max_bytes_for_level_multiplier, - * max_bytes_for_level_base]
    2. - *
    3. target size of the last level (level num_levels-1) equals to extra size - * of the level.
    4. - *
    - * - *

    At the same time max_bytes_for_level_multiplier and - * max_bytes_for_level_multiplier_additional are still satisfied.

    - * - *

    With this option on, from an empty DB, we make last level the base - * level, which means merging L0 data into the last level, until it exceeds - * max_bytes_for_level_base. And then we make the second last level to be - * base level, to start to merge L0 data to second last level, with its - * target size to be {@code 1/max_bytes_for_level_multiplier} of the last - * levels extra size. After the data accumulates more so that we need to - * move the base level to the third last one, and so on.

    - * - *

    Example

    - * - *

    For example, assume {@code max_bytes_for_level_multiplier=10}, - * {@code num_levels=6}, and {@code max_bytes_for_level_base=10MB}.

    - * - *

    Target sizes of level 1 to 5 starts with:

    - * {@code [- - - - 10MB]} - *

    with base level is level. Target sizes of level 1 to 4 are not applicable - * because they will not be used. - * Until the size of Level 5 grows to more than 10MB, say 11MB, we make - * base target to level 4 and now the targets looks like:

    - * {@code [- - - 1.1MB 11MB]} - *

    While data are accumulated, size targets are tuned based on actual data - * of level 5. When level 5 has 50MB of data, the target is like:

    - * {@code [- - - 5MB 50MB]} - *

    Until level 5's actual size is more than 100MB, say 101MB. Now if we - * keep level 4 to be the base level, its target size needs to be 10.1MB, - * which doesn't satisfy the target size range. So now we make level 3 - * the target size and the target sizes of the levels look like:

    - * {@code [- - 1.01MB 10.1MB 101MB]} - *

    In the same way, while level 5 further grows, all levels' targets grow, - * like

    - * {@code [- - 5MB 50MB 500MB]} - *

    Until level 5 exceeds 1000MB and becomes 1001MB, we make level 2 the - * base level and make levels' target sizes like this:

    - * {@code [- 1.001MB 10.01MB 100.1MB 1001MB]} - *

    and go on...

    - * - *

    By doing it, we give {@code max_bytes_for_level_multiplier} a priority - * against {@code max_bytes_for_level_base}, for a more predictable LSM tree - * shape. It is useful to limit worse case space amplification.

    - * - *

    {@code max_bytes_for_level_multiplier_additional} is ignored with - * this flag on.

    - * - *

    Turning this feature on or off for an existing DB can cause unexpected - * LSM tree structure so it's not recommended.

    - * - *

    Caution: this option is experimental

    - * - *

    Default: false

    - * - * @param enableLevelCompactionDynamicLevelBytes boolean value indicating - * if {@code LevelCompactionDynamicLevelBytes} shall be enabled. - * @return the reference to the current options. - */ - @Experimental("Turning this feature on or off for an existing DB can cause" + - " unexpected LSM tree structure so it's not recommended") - T setLevelCompactionDynamicLevelBytes( - boolean enableLevelCompactionDynamicLevelBytes); - - /** - *

    Return if {@code LevelCompactionDynamicLevelBytes} is enabled. - *

    - * - *

    For further information see - * {@link #setLevelCompactionDynamicLevelBytes(boolean)}

    - * - * @return boolean value indicating if - * {@code levelCompactionDynamicLevelBytes} is enabled. - */ - @Experimental("Caution: this option is experimental") - boolean levelCompactionDynamicLevelBytes(); - - /** - * Maximum size of each compaction (not guarantee) - * - * @param maxCompactionBytes the compaction size limit - * @return the reference to the current options. - */ - T setMaxCompactionBytes( - long maxCompactionBytes); - - /** - * Control maximum size of each compaction (not guaranteed) - * - * @return compaction size threshold - */ - long maxCompactionBytes(); - - /** - * Set compaction style for DB. - * - * Default: LEVEL. - * - * @param compactionStyle Compaction style. - * @return the reference to the current options. - */ - ColumnFamilyOptionsInterface setCompactionStyle( - CompactionStyle compactionStyle); - - /** - * Compaction style for DB. - * - * @return Compaction style. - */ - CompactionStyle compactionStyle(); - - /** - * If level {@link #compactionStyle()} == {@link CompactionStyle#LEVEL}, - * for each level, which files are prioritized to be picked to compact. - * - * Default: {@link CompactionPriority#ByCompensatedSize} - * - * @param compactionPriority The compaction priority - * - * @return the reference to the current options. - */ - T setCompactionPriority( - CompactionPriority compactionPriority); - - /** - * Get the Compaction priority if level compaction - * is used for all levels - * - * @return The compaction priority - */ - CompactionPriority compactionPriority(); - - /** - * Set the options needed to support Universal Style compactions - * - * @param compactionOptionsUniversal The Universal Style compaction options - * - * @return the reference to the current options. - */ - T setCompactionOptionsUniversal( - CompactionOptionsUniversal compactionOptionsUniversal); - - /** - * The options needed to support Universal Style compactions - * - * @return The Universal Style compaction options - */ - CompactionOptionsUniversal compactionOptionsUniversal(); - - /** - * The options for FIFO compaction style - * - * @param compactionOptionsFIFO The FIFO compaction options - * - * @return the reference to the current options. - */ - T setCompactionOptionsFIFO( - CompactionOptionsFIFO compactionOptionsFIFO); - - /** - * The options for FIFO compaction style - * - * @return The FIFO compaction options - */ - CompactionOptionsFIFO compactionOptionsFIFO(); - - /** - *

    This flag specifies that the implementation should optimize the filters - * mainly for cases where keys are found rather than also optimize for keys - * missed. This would be used in cases where the application knows that - * there are very few misses or the performance in the case of misses is not - * important.

    - * - *

    For now, this flag allows us to not store filters for the last level i.e - * the largest level which contains data of the LSM store. For keys which - * are hits, the filters in this level are not useful because we will search - * for the data anyway.

    - * - *

    NOTE: the filters in other levels are still useful - * even for key hit because they tell us whether to look in that level or go - * to the higher level.

    - * - *

    Default: false

    - * - * @param optimizeFiltersForHits boolean value indicating if this flag is set. - * @return the reference to the current options. - */ - T setOptimizeFiltersForHits( - boolean optimizeFiltersForHits); - - /** - *

    Returns the current state of the {@code optimize_filters_for_hits} - * setting.

    - * - * @return boolean value indicating if the flag - * {@code optimize_filters_for_hits} was set. - */ - boolean optimizeFiltersForHits(); - - /** - * By default, RocksDB runs consistency checks on the LSM every time the LSM - * changes (Flush, Compaction, AddFile). Use this option if you need to - * disable them. - * - * Default: true - * - * @param forceConsistencyChecks false to disable consistency checks - * - * @return the reference to the current options. - */ - T setForceConsistencyChecks( - boolean forceConsistencyChecks); - - /** - * By default, RocksDB runs consistency checks on the LSM every time the LSM - * changes (Flush, Compaction, AddFile). - * - * @return true if consistency checks are enforced - */ - boolean forceConsistencyChecks(); -} diff --git a/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java b/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java deleted file mode 100644 index 162d15d80..000000000 --- a/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java +++ /dev/null @@ -1,830 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Advanced Column Family Options which are mutable - * - * Taken from include/rocksdb/advanced_options.h - * and MutableCFOptions in util/cf_options.h - */ -public interface AdvancedMutableColumnFamilyOptionsInterface< - T extends AdvancedMutableColumnFamilyOptionsInterface> { - /** - * The maximum number of write buffers that are built up in memory. - * The default is 2, so that when 1 write buffer is being flushed to - * storage, new writes can continue to the other write buffer. - * Default: 2 - * - * @param maxWriteBufferNumber maximum number of write buffers. - * @return the instance of the current options. - */ - T setMaxWriteBufferNumber( - int maxWriteBufferNumber); - - /** - * Returns maximum number of write buffers. - * - * @return maximum number of write buffers. - * @see #setMaxWriteBufferNumber(int) - */ - int maxWriteBufferNumber(); - - /** - * Number of locks used for inplace update - * Default: 10000, if inplace_update_support = true, else 0. - * - * @param inplaceUpdateNumLocks the number of locks used for - * inplace updates. - * @return the reference to the current options. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setInplaceUpdateNumLocks( - long inplaceUpdateNumLocks); - - /** - * Number of locks used for inplace update - * Default: 10000, if inplace_update_support = true, else 0. - * - * @return the number of locks used for inplace update. - */ - long inplaceUpdateNumLocks(); - - /** - * if prefix_extractor is set and memtable_prefix_bloom_size_ratio is not 0, - * create prefix bloom for memtable with the size of - * write_buffer_size * memtable_prefix_bloom_size_ratio. - * If it is larger than 0.25, it is santinized to 0.25. - * - * Default: 0 (disabled) - * - * @param memtablePrefixBloomSizeRatio the ratio of memtable used by the - * bloom filter, 0 means no bloom filter - * @return the reference to the current options. - */ - T setMemtablePrefixBloomSizeRatio( - double memtablePrefixBloomSizeRatio); - - /** - * if prefix_extractor is set and memtable_prefix_bloom_size_ratio is not 0, - * create prefix bloom for memtable with the size of - * write_buffer_size * memtable_prefix_bloom_size_ratio. - * If it is larger than 0.25, it is santinized to 0.25. - * - * Default: 0 (disabled) - * - * @return the ratio of memtable used by the bloom filter - */ - double memtablePrefixBloomSizeRatio(); - - /** - * Threshold used in the MemPurge (memtable garbage collection) - * feature. A value of 0.0 corresponds to no MemPurge, - * a value of 1.0 will trigger a MemPurge as often as possible. - * - * Default: 0.0 (disabled) - * - * @param experimentalMempurgeThreshold the threshold used by - * the MemPurge decider. - * @return the reference to the current options. - */ - T setExperimentalMempurgeThreshold(double experimentalMempurgeThreshold); - - /** - * Threshold used in the MemPurge (memtable garbage collection) - * feature. A value of 0.0 corresponds to no MemPurge, - * a value of 1.0 will trigger a MemPurge as often as possible. - * - * Default: 0 (disabled) - * - * @return the threshold used by the MemPurge decider - */ - double experimentalMempurgeThreshold(); - - /** - * Enable whole key bloom filter in memtable. Note this will only take effect - * if memtable_prefix_bloom_size_ratio is not 0. Enabling whole key filtering - * can potentially reduce CPU usage for point-look-ups. - * - * Default: false (disabled) - * - * @param memtableWholeKeyFiltering true if whole key bloom filter is enabled - * in memtable - * @return the reference to the current options. - */ - T setMemtableWholeKeyFiltering(boolean memtableWholeKeyFiltering); - - /** - * Returns whether whole key bloom filter is enabled in memtable - * - * @return true if whole key bloom filter is enabled in memtable - */ - boolean memtableWholeKeyFiltering(); - - /** - * Page size for huge page TLB for bloom in memtable. If ≤ 0, not allocate - * from huge page TLB but from malloc. - * Need to reserve huge pages for it to be allocated. For example: - * sysctl -w vm.nr_hugepages=20 - * See linux doc Documentation/vm/hugetlbpage.txt - * - * @param memtableHugePageSize The page size of the huge - * page tlb - * @return the reference to the current options. - */ - T setMemtableHugePageSize( - long memtableHugePageSize); - - /** - * Page size for huge page TLB for bloom in memtable. If ≤ 0, not allocate - * from huge page TLB but from malloc. - * Need to reserve huge pages for it to be allocated. For example: - * sysctl -w vm.nr_hugepages=20 - * See linux doc Documentation/vm/hugetlbpage.txt - * - * @return The page size of the huge page tlb - */ - long memtableHugePageSize(); - - /** - * The size of one block in arena memory allocation. - * If ≤ 0, a proper value is automatically calculated (usually 1/10 of - * writer_buffer_size). - * - * There are two additional restriction of the specified size: - * (1) size should be in the range of [4096, 2 << 30] and - * (2) be the multiple of the CPU word (which helps with the memory - * alignment). - * - * We'll automatically check and adjust the size number to make sure it - * conforms to the restrictions. - * Default: 0 - * - * @param arenaBlockSize the size of an arena block - * @return the reference to the current options. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setArenaBlockSize(long arenaBlockSize); - - /** - * The size of one block in arena memory allocation. - * If ≤ 0, a proper value is automatically calculated (usually 1/10 of - * writer_buffer_size). - * - * There are two additional restriction of the specified size: - * (1) size should be in the range of [4096, 2 << 30] and - * (2) be the multiple of the CPU word (which helps with the memory - * alignment). - * - * We'll automatically check and adjust the size number to make sure it - * conforms to the restrictions. - * Default: 0 - * - * @return the size of an arena block - */ - long arenaBlockSize(); - - /** - * Soft limit on number of level-0 files. We start slowing down writes at this - * point. A value < 0 means that no writing slow down will be triggered by - * number of files in level-0. - * - * @param level0SlowdownWritesTrigger The soft limit on the number of - * level-0 files - * @return the reference to the current options. - */ - T setLevel0SlowdownWritesTrigger( - int level0SlowdownWritesTrigger); - - /** - * Soft limit on number of level-0 files. We start slowing down writes at this - * point. A value < 0 means that no writing slow down will be triggered by - * number of files in level-0. - * - * @return The soft limit on the number of - * level-0 files - */ - int level0SlowdownWritesTrigger(); - - /** - * Maximum number of level-0 files. We stop writes at this point. - * - * @param level0StopWritesTrigger The maximum number of level-0 files - * @return the reference to the current options. - */ - T setLevel0StopWritesTrigger( - int level0StopWritesTrigger); - - /** - * Maximum number of level-0 files. We stop writes at this point. - * - * @return The maximum number of level-0 files - */ - int level0StopWritesTrigger(); - - /** - * The target file size for compaction. - * This targetFileSizeBase determines a level-1 file size. - * Target file size for level L can be calculated by - * targetFileSizeBase * (targetFileSizeMultiplier ^ (L-1)) - * For example, if targetFileSizeBase is 2MB and - * target_file_size_multiplier is 10, then each file on level-1 will - * be 2MB, and each file on level 2 will be 20MB, - * and each file on level-3 will be 200MB. - * by default targetFileSizeBase is 64MB. - * - * @param targetFileSizeBase the target size of a level-0 file. - * @return the reference to the current options. - * - * @see #setTargetFileSizeMultiplier(int) - */ - T setTargetFileSizeBase( - long targetFileSizeBase); - - /** - * The target file size for compaction. - * This targetFileSizeBase determines a level-1 file size. - * Target file size for level L can be calculated by - * targetFileSizeBase * (targetFileSizeMultiplier ^ (L-1)) - * For example, if targetFileSizeBase is 2MB and - * target_file_size_multiplier is 10, then each file on level-1 will - * be 2MB, and each file on level 2 will be 20MB, - * and each file on level-3 will be 200MB. - * by default targetFileSizeBase is 64MB. - * - * @return the target size of a level-0 file. - * - * @see #targetFileSizeMultiplier() - */ - long targetFileSizeBase(); - - /** - * targetFileSizeMultiplier defines the size ratio between a - * level-L file and level-(L+1) file. - * By default target_file_size_multiplier is 1, meaning - * files in different levels have the same target. - * - * @param multiplier the size ratio between a level-(L+1) file - * and level-L file. - * @return the reference to the current options. - */ - T setTargetFileSizeMultiplier( - int multiplier); - - /** - * targetFileSizeMultiplier defines the size ratio between a - * level-(L+1) file and level-L file. - * By default targetFileSizeMultiplier is 1, meaning - * files in different levels have the same target. - * - * @return the size ratio between a level-(L+1) file and level-L file. - */ - int targetFileSizeMultiplier(); - - /** - * The ratio between the total size of level-(L+1) files and the total - * size of level-L files for all L. - * DEFAULT: 10 - * - * @param multiplier the ratio between the total size of level-(L+1) - * files and the total size of level-L files for all L. - * @return the reference to the current options. - * - * See {@link MutableColumnFamilyOptionsInterface#setMaxBytesForLevelBase(long)} - */ - T setMaxBytesForLevelMultiplier(double multiplier); - - /** - * The ratio between the total size of level-(L+1) files and the total - * size of level-L files for all L. - * DEFAULT: 10 - * - * @return the ratio between the total size of level-(L+1) files and - * the total size of level-L files for all L. - * - * See {@link MutableColumnFamilyOptionsInterface#maxBytesForLevelBase()} - */ - double maxBytesForLevelMultiplier(); - - /** - * Different max-size multipliers for different levels. - * These are multiplied by max_bytes_for_level_multiplier to arrive - * at the max-size of each level. - * - * Default: 1 - * - * @param maxBytesForLevelMultiplierAdditional The max-size multipliers - * for each level - * @return the reference to the current options. - */ - T setMaxBytesForLevelMultiplierAdditional( - int[] maxBytesForLevelMultiplierAdditional); - - /** - * Different max-size multipliers for different levels. - * These are multiplied by max_bytes_for_level_multiplier to arrive - * at the max-size of each level. - * - * Default: 1 - * - * @return The max-size multipliers for each level - */ - int[] maxBytesForLevelMultiplierAdditional(); - - /** - * All writes will be slowed down to at least delayed_write_rate if estimated - * bytes needed to be compaction exceed this threshold. - * - * Default: 64GB - * - * @param softPendingCompactionBytesLimit The soft limit to impose on - * compaction - * @return the reference to the current options. - */ - T setSoftPendingCompactionBytesLimit( - long softPendingCompactionBytesLimit); - - /** - * All writes will be slowed down to at least delayed_write_rate if estimated - * bytes needed to be compaction exceed this threshold. - * - * Default: 64GB - * - * @return The soft limit to impose on compaction - */ - long softPendingCompactionBytesLimit(); - - /** - * All writes are stopped if estimated bytes needed to be compaction exceed - * this threshold. - * - * Default: 256GB - * - * @param hardPendingCompactionBytesLimit The hard limit to impose on - * compaction - * @return the reference to the current options. - */ - T setHardPendingCompactionBytesLimit( - long hardPendingCompactionBytesLimit); - - /** - * All writes are stopped if estimated bytes needed to be compaction exceed - * this threshold. - * - * Default: 256GB - * - * @return The hard limit to impose on compaction - */ - long hardPendingCompactionBytesLimit(); - - /** - * An iteration->Next() sequentially skips over keys with the same - * user-key unless this option is set. This number specifies the number - * of keys (with the same userkey) that will be sequentially - * skipped before a reseek is issued. - * Default: 8 - * - * @param maxSequentialSkipInIterations the number of keys could - * be skipped in a iteration. - * @return the reference to the current options. - */ - T setMaxSequentialSkipInIterations( - long maxSequentialSkipInIterations); - - /** - * An iteration->Next() sequentially skips over keys with the same - * user-key unless this option is set. This number specifies the number - * of keys (with the same userkey) that will be sequentially - * skipped before a reseek is issued. - * Default: 8 - * - * @return the number of keys could be skipped in a iteration. - */ - long maxSequentialSkipInIterations(); - - /** - * Maximum number of successive merge operations on a key in the memtable. - * - * When a merge operation is added to the memtable and the maximum number of - * successive merges is reached, the value of the key will be calculated and - * inserted into the memtable instead of the merge operation. This will - * ensure that there are never more than max_successive_merges merge - * operations in the memtable. - * - * Default: 0 (disabled) - * - * @param maxSuccessiveMerges the maximum number of successive merges. - * @return the reference to the current options. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setMaxSuccessiveMerges( - long maxSuccessiveMerges); - - /** - * Maximum number of successive merge operations on a key in the memtable. - * - * When a merge operation is added to the memtable and the maximum number of - * successive merges is reached, the value of the key will be calculated and - * inserted into the memtable instead of the merge operation. This will - * ensure that there are never more than max_successive_merges merge - * operations in the memtable. - * - * Default: 0 (disabled) - * - * @return the maximum number of successive merges. - */ - long maxSuccessiveMerges(); - - /** - * After writing every SST file, reopen it and read all the keys. - * - * Default: false - * - * @param paranoidFileChecks true to enable paranoid file checks - * @return the reference to the current options. - */ - T setParanoidFileChecks( - boolean paranoidFileChecks); - - /** - * After writing every SST file, reopen it and read all the keys. - * - * Default: false - * - * @return true if paranoid file checks are enabled - */ - boolean paranoidFileChecks(); - - /** - * Measure IO stats in compactions and flushes, if true. - * - * Default: false - * - * @param reportBgIoStats true to enable reporting - * @return the reference to the current options. - */ - T setReportBgIoStats( - boolean reportBgIoStats); - - /** - * Determine whether IO stats in compactions and flushes are being measured - * - * @return true if reporting is enabled - */ - boolean reportBgIoStats(); - - /** - * Non-bottom-level files older than TTL will go through the compaction - * process. This needs {@link MutableDBOptionsInterface#maxOpenFiles()} to be - * set to -1. - * - * Enabled only for level compaction for now. - * - * Default: 0 (disabled) - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param ttl the time-to-live. - * - * @return the reference to the current options. - */ - T setTtl(final long ttl); - - /** - * Get the TTL for Non-bottom-level files that will go through the compaction - * process. - * - * See {@link #setTtl(long)}. - * - * @return the time-to-live. - */ - long ttl(); - - /** - * Files older than this value will be picked up for compaction, and - * re-written to the same level as they were before. - * One main use of the feature is to make sure a file goes through compaction - * filters periodically. Users can also use the feature to clear up SST - * files using old format. - * - * A file's age is computed by looking at file_creation_time or creation_time - * table properties in order, if they have valid non-zero values; if not, the - * age is based on the file's last modified time (given by the underlying - * Env). - * - * Supported in Level and FIFO compaction. - * In FIFO compaction, this option has the same meaning as TTL and whichever - * stricter will be used. - * Pre-req: max_open_file == -1. - * unit: seconds. Ex: 7 days = 7 * 24 * 60 * 60 - * - * Values: - * 0: Turn off Periodic compactions. - * UINT64_MAX - 1 (i.e 0xfffffffffffffffe): Let RocksDB control this feature - * as needed. For now, RocksDB will change this value to 30 days - * (i.e 30 * 24 * 60 * 60) so that every file goes through the compaction - * process at least once every 30 days if not compacted sooner. - * In FIFO compaction, since the option has the same meaning as ttl, - * when this value is left default, and ttl is left to 0, 30 days will be - * used. Otherwise, min(ttl, periodic_compaction_seconds) will be used. - * - * Default: 0xfffffffffffffffe (allow RocksDB to auto-tune) - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param periodicCompactionSeconds the periodic compaction in seconds. - * - * @return the reference to the current options. - */ - T setPeriodicCompactionSeconds(final long periodicCompactionSeconds); - - /** - * Get the periodicCompactionSeconds. - * - * See {@link #setPeriodicCompactionSeconds(long)}. - * - * @return the periodic compaction in seconds. - */ - long periodicCompactionSeconds(); - - // - // BEGIN options for blobs (integrated BlobDB) - // - - /** - * When set, large values (blobs) are written to separate blob files, and only - * pointers to them are stored in SST files. This can reduce write amplification - * for large-value use cases at the cost of introducing a level of indirection - * for reads. See also the options min_blob_size, blob_file_size, - * blob_compression_type, enable_blob_garbage_collection, and - * blob_garbage_collection_age_cutoff below. - * - * Default: false - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param enableBlobFiles true iff blob files should be enabled - * - * @return the reference to the current options. - */ - T setEnableBlobFiles(final boolean enableBlobFiles); - - /** - * When set, large values (blobs) are written to separate blob files, and only - * pointers to them are stored in SST files. This can reduce write amplification - * for large-value use cases at the cost of introducing a level of indirection - * for reads. See also the options min_blob_size, blob_file_size, - * blob_compression_type, enable_blob_garbage_collection, and - * blob_garbage_collection_age_cutoff below. - * - * Default: false - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @return true if blob files are enabled - */ - boolean enableBlobFiles(); - - /** - * Set the size of the smallest value to be stored separately in a blob file. Values - * which have an uncompressed size smaller than this threshold are stored - * alongside the keys in SST files in the usual fashion. A value of zero for - * this option means that all values are stored in blob files. Note that - * enable_blob_files has to be set in order for this option to have any effect. - * - * Default: 0 - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param minBlobSize the size of the smallest value to be stored separately in a blob file - * @return the reference to the current options. - */ - T setMinBlobSize(final long minBlobSize); - - /** - * Get the size of the smallest value to be stored separately in a blob file. Values - * which have an uncompressed size smaller than this threshold are stored - * alongside the keys in SST files in the usual fashion. A value of zero for - * this option means that all values are stored in blob files. Note that - * enable_blob_files has to be set in order for this option to have any effect. - * - * Default: 0 - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @return the current minimum size of value which is stored separately in a blob - */ - long minBlobSize(); - - /** - * Set the size limit for blob files. When writing blob files, a new file is opened - * once this limit is reached. Note that enable_blob_files has to be set in - * order for this option to have any effect. - * - * Default: 256 MB - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param blobFileSize the size limit for blob files - * - * @return the reference to the current options. - */ - T setBlobFileSize(final long blobFileSize); - - /** - * The size limit for blob files. When writing blob files, a new file is opened - * once this limit is reached. - * - * @return the current size limit for blob files - */ - long blobFileSize(); - - /** - * Set the compression algorithm to use for large values stored in blob files. Note - * that enable_blob_files has to be set in order for this option to have any - * effect. - * - * Default: no compression - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param compressionType the compression algorithm to use. - * - * @return the reference to the current options. - */ - T setBlobCompressionType(CompressionType compressionType); - - /** - * Get the compression algorithm in use for large values stored in blob files. - * Note that enable_blob_files has to be set in order for this option to have any - * effect. - * - * @return the current compression algorithm - */ - CompressionType blobCompressionType(); - - /** - * Enable/disable garbage collection of blobs. Blob GC is performed as part of - * compaction. Valid blobs residing in blob files older than a cutoff get - * relocated to new files as they are encountered during compaction, which makes - * it possible to clean up blob files once they contain nothing but - * obsolete/garbage blobs. See also blob_garbage_collection_age_cutoff below. - * - * Default: false - * - * @param enableBlobGarbageCollection the new enabled/disabled state of blob garbage collection - * - * @return the reference to the current options. - */ - T setEnableBlobGarbageCollection(final boolean enableBlobGarbageCollection); - - /** - * Query whether garbage collection of blobs is enabled.Blob GC is performed as part of - * compaction. Valid blobs residing in blob files older than a cutoff get - * relocated to new files as they are encountered during compaction, which makes - * it possible to clean up blob files once they contain nothing but - * obsolete/garbage blobs. See also blob_garbage_collection_age_cutoff below. - * - * Default: false - * - * @return true if blob garbage collection is currently enabled. - */ - boolean enableBlobGarbageCollection(); - - /** - * Set cutoff in terms of blob file age for garbage collection. Blobs in the - * oldest N blob files will be relocated when encountered during compaction, - * where N = garbage_collection_cutoff * number_of_blob_files. Note that - * enable_blob_garbage_collection has to be set in order for this option to have - * any effect. - * - * Default: 0.25 - * - * @param blobGarbageCollectionAgeCutoff the new age cutoff - * - * @return the reference to the current options. - */ - T setBlobGarbageCollectionAgeCutoff(double blobGarbageCollectionAgeCutoff); - /** - * Get cutoff in terms of blob file age for garbage collection. Blobs in the - * oldest N blob files will be relocated when encountered during compaction, - * where N = garbage_collection_cutoff * number_of_blob_files. Note that - * enable_blob_garbage_collection has to be set in order for this option to have - * any effect. - * - * Default: 0.25 - * - * @return the current age cutoff for garbage collection - */ - double blobGarbageCollectionAgeCutoff(); - - /** - * If the ratio of garbage in the oldest blob files exceeds this threshold, - * targeted compactions are scheduled in order to force garbage collecting - * the blob files in question, assuming they are all eligible based on the - * value of {@link #blobGarbageCollectionAgeCutoff} above. This option is - * currently only supported with leveled compactions. - * - * Note that {@link #enableBlobGarbageCollection} has to be set in order for this - * option to have any effect. - * - * Default: 1.0 - * - * Dynamically changeable through the SetOptions() API - * - * @param blobGarbageCollectionForceThreshold new value for the threshold - * @return the reference to the current options - */ - T setBlobGarbageCollectionForceThreshold(double blobGarbageCollectionForceThreshold); - - /** - * Get the current value for the {@link #blobGarbageCollectionForceThreshold} - * @return the current threshold at which garbage collection of blobs is forced - */ - double blobGarbageCollectionForceThreshold(); - - /** - * Set compaction readahead for blob files. - * - * Default: 0 - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param blobCompactionReadaheadSize the compaction readahead for blob files - * - * @return the reference to the current options. - */ - T setBlobCompactionReadaheadSize(final long blobCompactionReadaheadSize); - - /** - * Get compaction readahead for blob files. - * - * @return the current compaction readahead for blob files - */ - long blobCompactionReadaheadSize(); - - /** - * Set a certain LSM tree level to enable blob files. - * - * Default: 0 - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param blobFileStartingLevel the starting level to enable blob files - * - * @return the reference to the current options. - */ - T setBlobFileStartingLevel(final int blobFileStartingLevel); - - /** - * Get the starting LSM tree level to enable blob files. - * - * Default: 0 - * - * @return the current LSM tree level to enable blob files. - */ - int blobFileStartingLevel(); - - /** - * Set a certain prepopulate blob cache option. - * - * Default: 0 - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param prepopulateBlobCache the prepopulate blob cache option - * - * @return the reference to the current options. - */ - T setPrepopulateBlobCache(final PrepopulateBlobCache prepopulateBlobCache); - - /** - * Get the prepopulate blob cache option. - * - * Default: 0 - * - * @return the current prepopulate blob cache option. - */ - PrepopulateBlobCache prepopulateBlobCache(); - - // - // END options for blobs (integrated BlobDB) - // -} diff --git a/java/src/main/java/org/rocksdb/BackgroundErrorReason.java b/java/src/main/java/org/rocksdb/BackgroundErrorReason.java deleted file mode 100644 index eec593d35..000000000 --- a/java/src/main/java/org/rocksdb/BackgroundErrorReason.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum BackgroundErrorReason { - FLUSH((byte) 0x0), - COMPACTION((byte) 0x1), - WRITE_CALLBACK((byte) 0x2), - MEMTABLE((byte) 0x3); - - private final byte value; - - BackgroundErrorReason(final byte value) { - this.value = value; - } - - /** - * Get the internal representation. - * - * @return the internal representation - */ - byte getValue() { - return value; - } - - /** - * Get the BackgroundErrorReason from the internal representation value. - * - * @return the background error reason. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static BackgroundErrorReason fromValue(final byte value) { - for (final BackgroundErrorReason backgroundErrorReason : BackgroundErrorReason.values()) { - if (backgroundErrorReason.value == value) { - return backgroundErrorReason; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for BackgroundErrorReason: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/BackupEngine.java b/java/src/main/java/org/rocksdb/BackupEngine.java deleted file mode 100644 index 515824a91..000000000 --- a/java/src/main/java/org/rocksdb/BackupEngine.java +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import java.util.List; - -/** - * BackupEngine allows you to backup - * and restore the database - * - * Be aware, that `new BackupEngine` takes time proportional to the amount - * of backups. So if you have a slow filesystem to backup - * and you have a lot of backups then restoring can take some time. - * That's why we recommend to limit the number of backups. - * Also we recommend to keep BackupEngine alive and not to recreate it every - * time you need to do a backup. - */ -public class BackupEngine extends RocksObject implements AutoCloseable { - - protected BackupEngine(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Opens a new Backup Engine - * - * @param env The environment that the backup engine should operate within - * @param options Any options for the backup engine - * - * @return A new BackupEngine instance - * @throws RocksDBException thrown if the backup engine could not be opened - */ - public static BackupEngine open(final Env env, final BackupEngineOptions options) - throws RocksDBException { - return new BackupEngine(open(env.nativeHandle_, options.nativeHandle_)); - } - - /** - * Captures the state of the database in the latest backup - * - * Just a convenience for {@link #createNewBackup(RocksDB, boolean)} with - * the flushBeforeBackup parameter set to false - * - * @param db The database to backup - * - * Note - This method is not thread safe - * - * @throws RocksDBException thrown if a new backup could not be created - */ - public void createNewBackup(final RocksDB db) throws RocksDBException { - createNewBackup(db, false); - } - - /** - * Captures the state of the database in the latest backup - * - * @param db The database to backup - * @param flushBeforeBackup When true, the Backup Engine will first issue a - * memtable flush and only then copy the DB files to - * the backup directory. Doing so will prevent log - * files from being copied to the backup directory - * (since flush will delete them). - * When false, the Backup Engine will not issue a - * flush before starting the backup. In that case, - * the backup will also include log files - * corresponding to live memtables. If writes have - * been performed with the write ahead log disabled, - * set flushBeforeBackup to true to prevent those - * writes from being lost. Otherwise, the backup will - * always be consistent with the current state of the - * database regardless of the flushBeforeBackup - * parameter. - * - * Note - This method is not thread safe - * - * @throws RocksDBException thrown if a new backup could not be created - */ - public void createNewBackup( - final RocksDB db, final boolean flushBeforeBackup) - throws RocksDBException { - assert (isOwningHandle()); - createNewBackup(nativeHandle_, db.nativeHandle_, flushBeforeBackup); - } - - /** - * Captures the state of the database in the latest backup along with - * application specific metadata. - * - * @param db The database to backup - * @param metadata Application metadata - * @param flushBeforeBackup When true, the Backup Engine will first issue a - * memtable flush and only then copy the DB files to - * the backup directory. Doing so will prevent log - * files from being copied to the backup directory - * (since flush will delete them). - * When false, the Backup Engine will not issue a - * flush before starting the backup. In that case, - * the backup will also include log files - * corresponding to live memtables. If writes have - * been performed with the write ahead log disabled, - * set flushBeforeBackup to true to prevent those - * writes from being lost. Otherwise, the backup will - * always be consistent with the current state of the - * database regardless of the flushBeforeBackup - * parameter. - * - * Note - This method is not thread safe - * - * @throws RocksDBException thrown if a new backup could not be created - */ - public void createNewBackupWithMetadata(final RocksDB db, final String metadata, - final boolean flushBeforeBackup) throws RocksDBException { - assert (isOwningHandle()); - createNewBackupWithMetadata(nativeHandle_, db.nativeHandle_, metadata, flushBeforeBackup); - } - - /** - * Gets information about the available - * backups - * - * @return A list of information about each available backup - */ - public List getBackupInfo() { - assert (isOwningHandle()); - return getBackupInfo(nativeHandle_); - } - - /** - *

    Returns a list of corrupted backup ids. If there - * is no corrupted backup the method will return an - * empty list.

    - * - * @return array of backup ids as int ids. - */ - public int[] getCorruptedBackups() { - assert(isOwningHandle()); - return getCorruptedBackups(nativeHandle_); - } - - /** - *

    Will delete all the files we don't need anymore. It will - * do the full scan of the files/ directory and delete all the - * files that are not referenced.

    - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void garbageCollect() throws RocksDBException { - assert(isOwningHandle()); - garbageCollect(nativeHandle_); - } - - /** - * Deletes old backups, keeping just the latest numBackupsToKeep - * - * @param numBackupsToKeep The latest n backups to keep - * - * @throws RocksDBException thrown if the old backups could not be deleted - */ - public void purgeOldBackups( - final int numBackupsToKeep) throws RocksDBException { - assert (isOwningHandle()); - purgeOldBackups(nativeHandle_, numBackupsToKeep); - } - - /** - * Deletes a backup - * - * @param backupId The id of the backup to delete - * - * @throws RocksDBException thrown if the backup could not be deleted - */ - public void deleteBackup(final int backupId) throws RocksDBException { - assert (isOwningHandle()); - deleteBackup(nativeHandle_, backupId); - } - - /** - * Restore the database from a backup - * - * IMPORTANT: if options.share_table_files == true and you restore the DB - * from some backup that is not the latest, and you start creating new - * backups from the new DB, they will probably fail! - * - * Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3. - * If you add new data to the DB and try creating a new backup now, the - * database will diverge from backups 4 and 5 and the new backup will fail. - * If you want to create new backup, you will first have to delete backups 4 - * and 5. - * - * @param backupId The id of the backup to restore - * @param dbDir The directory to restore the backup to, i.e. where your - * database is - * @param walDir The location of the log files for your database, - * often the same as dbDir - * @param restoreOptions Options for controlling the restore - * - * @throws RocksDBException thrown if the database could not be restored - */ - public void restoreDbFromBackup( - final int backupId, final String dbDir, final String walDir, - final RestoreOptions restoreOptions) throws RocksDBException { - assert (isOwningHandle()); - restoreDbFromBackup(nativeHandle_, backupId, dbDir, walDir, - restoreOptions.nativeHandle_); - } - - /** - * Restore the database from the latest backup - * - * @param dbDir The directory to restore the backup to, i.e. where your - * database is - * @param walDir The location of the log files for your database, often the - * same as dbDir - * @param restoreOptions Options for controlling the restore - * - * @throws RocksDBException thrown if the database could not be restored - */ - public void restoreDbFromLatestBackup( - final String dbDir, final String walDir, - final RestoreOptions restoreOptions) throws RocksDBException { - assert (isOwningHandle()); - restoreDbFromLatestBackup(nativeHandle_, dbDir, walDir, - restoreOptions.nativeHandle_); - } - - private native static long open(final long env, final long backupEngineOptions) - throws RocksDBException; - - private native void createNewBackup(final long handle, final long dbHandle, - final boolean flushBeforeBackup) throws RocksDBException; - - private native void createNewBackupWithMetadata(final long handle, final long dbHandle, - final String metadata, final boolean flushBeforeBackup) throws RocksDBException; - - private native List getBackupInfo(final long handle); - - private native int[] getCorruptedBackups(final long handle); - - private native void garbageCollect(final long handle) throws RocksDBException; - - private native void purgeOldBackups(final long handle, - final int numBackupsToKeep) throws RocksDBException; - - private native void deleteBackup(final long handle, final int backupId) - throws RocksDBException; - - private native void restoreDbFromBackup(final long handle, final int backupId, - final String dbDir, final String walDir, final long restoreOptionsHandle) - throws RocksDBException; - - private native void restoreDbFromLatestBackup(final long handle, - final String dbDir, final String walDir, final long restoreOptionsHandle) - throws RocksDBException; - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/BackupEngineOptions.java b/java/src/main/java/org/rocksdb/BackupEngineOptions.java deleted file mode 100644 index 6e2dacc02..000000000 --- a/java/src/main/java/org/rocksdb/BackupEngineOptions.java +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.io.File; - -/** - *

    BackupEngineOptions controls the behavior of a - * {@link org.rocksdb.BackupEngine}. - *

    - *

    Note that dispose() must be called before an Options instance - * become out-of-scope to release the allocated memory in c++.

    - * - * @see org.rocksdb.BackupEngine - */ -public class BackupEngineOptions extends RocksObject { - private Env backupEnv = null; - private Logger infoLog = null; - private RateLimiter backupRateLimiter = null; - private RateLimiter restoreRateLimiter = null; - - /** - *

    BackupEngineOptions constructor.

    - * - * @param path Where to keep the backup files. Has to be different than db - * name. Best to set this to {@code db name_ + "/backups"} - * @throws java.lang.IllegalArgumentException if illegal path is used. - */ - public BackupEngineOptions(final String path) { - super(newBackupEngineOptions(ensureWritableFile(path))); - } - - private static String ensureWritableFile(final String path) { - final File backupPath = path == null ? null : new File(path); - if (backupPath == null || !backupPath.isDirectory() || - !backupPath.canWrite()) { - throw new IllegalArgumentException("Illegal path provided."); - } else { - return path; - } - } - - /** - *

    Returns the path to the BackupEngine directory.

    - * - * @return the path to the BackupEngine directory. - */ - public String backupDir() { - assert(isOwningHandle()); - return backupDir(nativeHandle_); - } - - /** - * Backup Env object. It will be used for backup file I/O. If it's - * null, backups will be written out using DBs Env. Otherwise - * backup's I/O will be performed using this object. - * - * Default: null - * - * @param env The environment to use - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setBackupEnv(final Env env) { - assert(isOwningHandle()); - setBackupEnv(nativeHandle_, env.nativeHandle_); - this.backupEnv = env; - return this; - } - - /** - * Backup Env object. It will be used for backup file I/O. If it's - * null, backups will be written out using DBs Env. Otherwise - * backup's I/O will be performed using this object. - * - * Default: null - * - * @return The environment in use - */ - public Env backupEnv() { - return this.backupEnv; - } - - /** - *

    Share table files between backups.

    - * - * @param shareTableFiles If {@code share_table_files == true}, backup will - * assume that table files with same name have the same contents. This - * enables incremental backups and avoids unnecessary data copies. If - * {@code share_table_files == false}, each backup will be on its own and - * will not share any data with other backups. - * - *

    Default: true

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setShareTableFiles(final boolean shareTableFiles) { - assert(isOwningHandle()); - setShareTableFiles(nativeHandle_, shareTableFiles); - return this; - } - - /** - *

    Share table files between backups.

    - * - * @return boolean value indicating if SST files will be shared between - * backups. - */ - public boolean shareTableFiles() { - assert(isOwningHandle()); - return shareTableFiles(nativeHandle_); - } - - /** - * Set the logger to use for Backup info and error messages - * - * @param logger The logger to use for the backup - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setInfoLog(final Logger logger) { - assert(isOwningHandle()); - setInfoLog(nativeHandle_, logger.nativeHandle_); - this.infoLog = logger; - return this; - } - - /** - * Set the logger to use for Backup info and error messages - * - * Default: null - * - * @return The logger in use for the backup - */ - public Logger infoLog() { - return this.infoLog; - } - - /** - *

    Set synchronous backups.

    - * - * @param sync If {@code sync == true}, we can guarantee you'll get consistent - * backup even on a machine crash/reboot. Backup process is slower with sync - * enabled. If {@code sync == false}, we don't guarantee anything on machine - * reboot. However, chances are some of the backups are consistent. - * - *

    Default: true

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setSync(final boolean sync) { - assert(isOwningHandle()); - setSync(nativeHandle_, sync); - return this; - } - - /** - *

    Are synchronous backups activated.

    - * - * @return boolean value if synchronous backups are configured. - */ - public boolean sync() { - assert(isOwningHandle()); - return sync(nativeHandle_); - } - - /** - *

    Set if old data will be destroyed.

    - * - * @param destroyOldData If true, it will delete whatever backups there are - * already. - * - *

    Default: false

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setDestroyOldData(final boolean destroyOldData) { - assert(isOwningHandle()); - setDestroyOldData(nativeHandle_, destroyOldData); - return this; - } - - /** - *

    Returns if old data will be destroyed will performing new backups.

    - * - * @return boolean value indicating if old data will be destroyed. - */ - public boolean destroyOldData() { - assert(isOwningHandle()); - return destroyOldData(nativeHandle_); - } - - /** - *

    Set if log files shall be persisted.

    - * - * @param backupLogFiles If false, we won't backup log files. This option can - * be useful for backing up in-memory databases where log file are - * persisted, but table files are in memory. - * - *

    Default: true

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setBackupLogFiles(final boolean backupLogFiles) { - assert(isOwningHandle()); - setBackupLogFiles(nativeHandle_, backupLogFiles); - return this; - } - - /** - *

    Return information if log files shall be persisted.

    - * - * @return boolean value indicating if log files will be persisted. - */ - public boolean backupLogFiles() { - assert(isOwningHandle()); - return backupLogFiles(nativeHandle_); - } - - /** - *

    Set backup rate limit.

    - * - * @param backupRateLimit Max bytes that can be transferred in a second during - * backup. If 0 or negative, then go as fast as you can. - * - *

    Default: 0

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setBackupRateLimit(long backupRateLimit) { - assert(isOwningHandle()); - backupRateLimit = (backupRateLimit <= 0) ? 0 : backupRateLimit; - setBackupRateLimit(nativeHandle_, backupRateLimit); - return this; - } - - /** - *

    Return backup rate limit which described the max bytes that can be - * transferred in a second during backup.

    - * - * @return numerical value describing the backup transfer limit in bytes per - * second. - */ - public long backupRateLimit() { - assert(isOwningHandle()); - return backupRateLimit(nativeHandle_); - } - - /** - * Backup rate limiter. Used to control transfer speed for backup. If this is - * not null, {@link #backupRateLimit()} is ignored. - * - * Default: null - * - * @param backupRateLimiter The rate limiter to use for the backup - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setBackupRateLimiter(final RateLimiter backupRateLimiter) { - assert(isOwningHandle()); - setBackupRateLimiter(nativeHandle_, backupRateLimiter.nativeHandle_); - this.backupRateLimiter = backupRateLimiter; - return this; - } - - /** - * Backup rate limiter. Used to control transfer speed for backup. If this is - * not null, {@link #backupRateLimit()} is ignored. - * - * Default: null - * - * @return The rate limiter in use for the backup - */ - public RateLimiter backupRateLimiter() { - assert(isOwningHandle()); - return this.backupRateLimiter; - } - - /** - *

    Set restore rate limit.

    - * - * @param restoreRateLimit Max bytes that can be transferred in a second - * during restore. If 0 or negative, then go as fast as you can. - * - *

    Default: 0

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setRestoreRateLimit(long restoreRateLimit) { - assert(isOwningHandle()); - restoreRateLimit = (restoreRateLimit <= 0) ? 0 : restoreRateLimit; - setRestoreRateLimit(nativeHandle_, restoreRateLimit); - return this; - } - - /** - *

    Return restore rate limit which described the max bytes that can be - * transferred in a second during restore.

    - * - * @return numerical value describing the restore transfer limit in bytes per - * second. - */ - public long restoreRateLimit() { - assert(isOwningHandle()); - return restoreRateLimit(nativeHandle_); - } - - /** - * Restore rate limiter. Used to control transfer speed during restore. If - * this is not null, {@link #restoreRateLimit()} is ignored. - * - * Default: null - * - * @param restoreRateLimiter The rate limiter to use during restore - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setRestoreRateLimiter(final RateLimiter restoreRateLimiter) { - assert(isOwningHandle()); - setRestoreRateLimiter(nativeHandle_, restoreRateLimiter.nativeHandle_); - this.restoreRateLimiter = restoreRateLimiter; - return this; - } - - /** - * Restore rate limiter. Used to control transfer speed during restore. If - * this is not null, {@link #restoreRateLimit()} is ignored. - * - * Default: null - * - * @return The rate limiter in use during restore - */ - public RateLimiter restoreRateLimiter() { - assert(isOwningHandle()); - return this.restoreRateLimiter; - } - - /** - *

    Only used if share_table_files is set to true. If true, will consider - * that backups can come from different databases, hence a sst is not uniquely - * identified by its name, but by the triple (file name, crc32, file length) - *

    - * - * @param shareFilesWithChecksum boolean value indicating if SST files are - * stored using the triple (file name, crc32, file length) and not its name. - * - *

    Note: this is an experimental option, and you'll need to set it manually - * turn it on only if you know what you're doing*

    - * - *

    Default: false

    - * - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setShareFilesWithChecksum(final boolean shareFilesWithChecksum) { - assert(isOwningHandle()); - setShareFilesWithChecksum(nativeHandle_, shareFilesWithChecksum); - return this; - } - - /** - *

    Return of share files with checksum is active.

    - * - * @return boolean value indicating if share files with checksum - * is active. - */ - public boolean shareFilesWithChecksum() { - assert(isOwningHandle()); - return shareFilesWithChecksum(nativeHandle_); - } - - /** - * Up to this many background threads will copy files for - * {@link BackupEngine#createNewBackup(RocksDB, boolean)} and - * {@link BackupEngine#restoreDbFromBackup(int, String, String, RestoreOptions)} - * - * Default: 1 - * - * @param maxBackgroundOperations The maximum number of background threads - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setMaxBackgroundOperations(final int maxBackgroundOperations) { - assert(isOwningHandle()); - setMaxBackgroundOperations(nativeHandle_, maxBackgroundOperations); - return this; - } - - /** - * Up to this many background threads will copy files for - * {@link BackupEngine#createNewBackup(RocksDB, boolean)} and - * {@link BackupEngine#restoreDbFromBackup(int, String, String, RestoreOptions)} - * - * Default: 1 - * - * @return The maximum number of background threads - */ - public int maxBackgroundOperations() { - assert(isOwningHandle()); - return maxBackgroundOperations(nativeHandle_); - } - - /** - * During backup user can get callback every time next - * {@link #callbackTriggerIntervalSize()} bytes being copied. - * - * Default: 4194304 - * - * @param callbackTriggerIntervalSize The interval size for the - * callback trigger - * @return instance of current BackupEngineOptions. - */ - public BackupEngineOptions setCallbackTriggerIntervalSize( - final long callbackTriggerIntervalSize) { - assert(isOwningHandle()); - setCallbackTriggerIntervalSize(nativeHandle_, callbackTriggerIntervalSize); - return this; - } - - /** - * During backup user can get callback every time next - * {@link #callbackTriggerIntervalSize()} bytes being copied. - * - * Default: 4194304 - * - * @return The interval size for the callback trigger - */ - public long callbackTriggerIntervalSize() { - assert(isOwningHandle()); - return callbackTriggerIntervalSize(nativeHandle_); - } - - private native static long newBackupEngineOptions(final String path); - private native String backupDir(long handle); - private native void setBackupEnv(final long handle, final long envHandle); - private native void setShareTableFiles(long handle, boolean flag); - private native boolean shareTableFiles(long handle); - private native void setInfoLog(final long handle, final long infoLogHandle); - private native void setSync(long handle, boolean flag); - private native boolean sync(long handle); - private native void setDestroyOldData(long handle, boolean flag); - private native boolean destroyOldData(long handle); - private native void setBackupLogFiles(long handle, boolean flag); - private native boolean backupLogFiles(long handle); - private native void setBackupRateLimit(long handle, long rateLimit); - private native long backupRateLimit(long handle); - private native void setBackupRateLimiter(long handle, long rateLimiterHandle); - private native void setRestoreRateLimit(long handle, long rateLimit); - private native long restoreRateLimit(long handle); - private native void setRestoreRateLimiter(final long handle, - final long rateLimiterHandle); - private native void setShareFilesWithChecksum(long handle, boolean flag); - private native boolean shareFilesWithChecksum(long handle); - private native void setMaxBackgroundOperations(final long handle, - final int maxBackgroundOperations); - private native int maxBackgroundOperations(final long handle); - private native void setCallbackTriggerIntervalSize(final long handle, - long callbackTriggerIntervalSize); - private native long callbackTriggerIntervalSize(final long handle); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/BackupInfo.java b/java/src/main/java/org/rocksdb/BackupInfo.java deleted file mode 100644 index 9244e4eb1..000000000 --- a/java/src/main/java/org/rocksdb/BackupInfo.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * Instances of this class describe a Backup made by - * {@link org.rocksdb.BackupEngine}. - */ -public class BackupInfo { - - /** - * Package private constructor used to create instances - * of BackupInfo by {@link org.rocksdb.BackupEngine} - * - * @param backupId id of backup - * @param timestamp timestamp of backup - * @param size size of backup - * @param numberFiles number of files related to this backup. - */ - BackupInfo(final int backupId, final long timestamp, final long size, final int numberFiles, - final String app_metadata) { - backupId_ = backupId; - timestamp_ = timestamp; - size_ = size; - numberFiles_ = numberFiles; - app_metadata_ = app_metadata; - } - - /** - * - * @return the backup id. - */ - public int backupId() { - return backupId_; - } - - /** - * - * @return the timestamp of the backup. - */ - public long timestamp() { - return timestamp_; - } - - /** - * - * @return the size of the backup - */ - public long size() { - return size_; - } - - /** - * - * @return the number of files of this backup. - */ - public int numberFiles() { - return numberFiles_; - } - - /** - * - * @return the associated application metadata, or null - */ - public String appMetadata() { - return app_metadata_; - } - - private int backupId_; - private long timestamp_; - private long size_; - private int numberFiles_; - private String app_metadata_; -} diff --git a/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java b/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java deleted file mode 100644 index 9300468b0..000000000 --- a/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java +++ /dev/null @@ -1,951 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * The config for plain table sst format. - * - * BlockBasedTable is a RocksDB's default SST file format. - */ -//TODO(AR) should be renamed BlockBasedTableOptions -public class BlockBasedTableConfig extends TableFormatConfig { - - public BlockBasedTableConfig() { - //TODO(AR) flushBlockPolicyFactory - cacheIndexAndFilterBlocks = false; - cacheIndexAndFilterBlocksWithHighPriority = true; - pinL0FilterAndIndexBlocksInCache = false; - pinTopLevelIndexAndFilter = true; - indexType = IndexType.kBinarySearch; - dataBlockIndexType = DataBlockIndexType.kDataBlockBinarySearch; - dataBlockHashTableUtilRatio = 0.75; - checksumType = ChecksumType.kCRC32c; - noBlockCache = false; - blockCache = null; - persistentCache = null; - blockSize = 4 * 1024; - blockSizeDeviation = 10; - blockRestartInterval = 16; - indexBlockRestartInterval = 1; - metadataBlockSize = 4096; - partitionFilters = false; - optimizeFiltersForMemory = false; - useDeltaEncoding = true; - filterPolicy = null; - wholeKeyFiltering = true; - verifyCompression = false; - readAmpBytesPerBit = 0; - formatVersion = 5; - enableIndexCompression = true; - blockAlign = false; - indexShortening = IndexShorteningMode.kShortenSeparators; - - // NOTE: ONLY used if blockCache == null - blockCacheSize = 8 * 1024 * 1024; - blockCacheNumShardBits = 0; - } - - /** - * Indicating if we'd put index/filter blocks to the block cache. - * If not specified, each "table reader" object will pre-load index/filter - * block during table initialization. - * - * @return if index and filter blocks should be put in block cache. - */ - public boolean cacheIndexAndFilterBlocks() { - return cacheIndexAndFilterBlocks; - } - - /** - * Indicating if we'd put index/filter blocks to the block cache. - * If not specified, each "table reader" object will pre-load index/filter - * block during table initialization. - * - * @param cacheIndexAndFilterBlocks and filter blocks should be put in block cache. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setCacheIndexAndFilterBlocks( - final boolean cacheIndexAndFilterBlocks) { - this.cacheIndexAndFilterBlocks = cacheIndexAndFilterBlocks; - return this; - } - - /** - * Indicates if index and filter blocks will be treated as high-priority in the block cache. - * See note below about applicability. If not specified, defaults to true. - * - * @return if index and filter blocks will be treated as high-priority. - */ - public boolean cacheIndexAndFilterBlocksWithHighPriority() { - return cacheIndexAndFilterBlocksWithHighPriority; - } - - /** - * If true, cache index and filter blocks with high priority. If set to true, - * depending on implementation of block cache, index and filter blocks may be - * less likely to be evicted than data blocks. - * - * @param cacheIndexAndFilterBlocksWithHighPriority if index and filter blocks - * will be treated as high-priority. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setCacheIndexAndFilterBlocksWithHighPriority( - final boolean cacheIndexAndFilterBlocksWithHighPriority) { - this.cacheIndexAndFilterBlocksWithHighPriority = cacheIndexAndFilterBlocksWithHighPriority; - return this; - } - - /** - * Indicating if we'd like to pin L0 index/filter blocks to the block cache. - If not specified, defaults to false. - * - * @return if L0 index and filter blocks should be pinned to the block cache. - */ - public boolean pinL0FilterAndIndexBlocksInCache() { - return pinL0FilterAndIndexBlocksInCache; - } - - /** - * Indicating if we'd like to pin L0 index/filter blocks to the block cache. - If not specified, defaults to false. - * - * @param pinL0FilterAndIndexBlocksInCache pin blocks in block cache - * @return the reference to the current config. - */ - public BlockBasedTableConfig setPinL0FilterAndIndexBlocksInCache( - final boolean pinL0FilterAndIndexBlocksInCache) { - this.pinL0FilterAndIndexBlocksInCache = pinL0FilterAndIndexBlocksInCache; - return this; - } - - /** - * Indicates if top-level index and filter blocks should be pinned. - * - * @return if top-level index and filter blocks should be pinned. - */ - public boolean pinTopLevelIndexAndFilter() { - return pinTopLevelIndexAndFilter; - } - - /** - * If cacheIndexAndFilterBlocks is true and the below is true, then - * the top-level index of partitioned filter and index blocks are stored in - * the cache, but a reference is held in the "table reader" object so the - * blocks are pinned and only evicted from cache when the table reader is - * freed. This is not limited to l0 in LSM tree. - * - * @param pinTopLevelIndexAndFilter if top-level index and filter blocks should be pinned. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setPinTopLevelIndexAndFilter(final boolean pinTopLevelIndexAndFilter) { - this.pinTopLevelIndexAndFilter = pinTopLevelIndexAndFilter; - return this; - } - - /** - * Get the index type. - * - * @return the currently set index type - */ - public IndexType indexType() { - return indexType; - } - - /** - * Sets the index type to used with this table. - * - * @param indexType {@link org.rocksdb.IndexType} value - * @return the reference to the current option. - */ - public BlockBasedTableConfig setIndexType( - final IndexType indexType) { - this.indexType = indexType; - return this; - } - - /** - * Get the data block index type. - * - * @return the currently set data block index type - */ - public DataBlockIndexType dataBlockIndexType() { - return dataBlockIndexType; - } - - /** - * Sets the data block index type to used with this table. - * - * @param dataBlockIndexType {@link org.rocksdb.DataBlockIndexType} value - * @return the reference to the current option. - */ - public BlockBasedTableConfig setDataBlockIndexType( - final DataBlockIndexType dataBlockIndexType) { - this.dataBlockIndexType = dataBlockIndexType; - return this; - } - - /** - * Get the #entries/#buckets. It is valid only when {@link #dataBlockIndexType()} is - * {@link DataBlockIndexType#kDataBlockBinaryAndHash}. - * - * @return the #entries/#buckets. - */ - public double dataBlockHashTableUtilRatio() { - return dataBlockHashTableUtilRatio; - } - - /** - * Set the #entries/#buckets. It is valid only when {@link #dataBlockIndexType()} is - * {@link DataBlockIndexType#kDataBlockBinaryAndHash}. - * - * @param dataBlockHashTableUtilRatio #entries/#buckets - * @return the reference to the current option. - */ - public BlockBasedTableConfig setDataBlockHashTableUtilRatio( - final double dataBlockHashTableUtilRatio) { - this.dataBlockHashTableUtilRatio = dataBlockHashTableUtilRatio; - return this; - } - - /** - * Get the checksum type to be used with this table. - * - * @return the currently set checksum type - */ - public ChecksumType checksumType() { - return checksumType; - } - - /** - * Sets - * - * @param checksumType {@link org.rocksdb.ChecksumType} value. - * @return the reference to the current option. - */ - public BlockBasedTableConfig setChecksumType( - final ChecksumType checksumType) { - this.checksumType = checksumType; - return this; - } - - /** - * Determine if the block cache is disabled. - * - * @return if block cache is disabled - */ - public boolean noBlockCache() { - return noBlockCache; - } - - /** - * Disable block cache. If this is set to true, - * then no block cache should be used, and the {@link #setBlockCache(Cache)} - * should point to a {@code null} object. - * - * Default: false - * - * @param noBlockCache if use block cache - * @return the reference to the current config. - */ - public BlockBasedTableConfig setNoBlockCache(final boolean noBlockCache) { - this.noBlockCache = noBlockCache; - return this; - } - - /** - * Use the specified cache for blocks. - * When not null this take precedence even if the user sets a block cache size. - * - * {@link org.rocksdb.Cache} should not be disposed before options instances - * using this cache is disposed. - * - * {@link org.rocksdb.Cache} instance can be re-used in multiple options - * instances. - * - * @param blockCache {@link org.rocksdb.Cache} Cache java instance - * (e.g. LRUCache). - * - * @return the reference to the current config. - */ - public BlockBasedTableConfig setBlockCache(final Cache blockCache) { - this.blockCache = blockCache; - return this; - } - - /** - * Use the specified persistent cache. - * - * If {@code !null} use the specified cache for pages read from device, - * otherwise no page cache is used. - * - * @param persistentCache the persistent cache - * - * @return the reference to the current config. - */ - public BlockBasedTableConfig setPersistentCache( - final PersistentCache persistentCache) { - this.persistentCache = persistentCache; - return this; - } - - /** - * Get the approximate size of user data packed per block. - * - * @return block size in bytes - */ - public long blockSize() { - return blockSize; - } - - /** - * Approximate size of user data packed per block. Note that the - * block size specified here corresponds to uncompressed data. The - * actual size of the unit read from disk may be smaller if - * compression is enabled. This parameter can be changed dynamically. - * Default: 4K - * - * @param blockSize block size in bytes - * @return the reference to the current config. - */ - public BlockBasedTableConfig setBlockSize(final long blockSize) { - this.blockSize = blockSize; - return this; - } - - /** - * @return the hash table ratio. - */ - public int blockSizeDeviation() { - return blockSizeDeviation; - } - - /** - * This is used to close a block before it reaches the configured - * {@link #blockSize()}. If the percentage of free space in the current block - * is less than this specified number and adding a new record to the block - * will exceed the configured block size, then this block will be closed and - * the new record will be written to the next block. - * - * Default is 10. - * - * @param blockSizeDeviation the deviation to block size allowed - * @return the reference to the current config. - */ - public BlockBasedTableConfig setBlockSizeDeviation( - final int blockSizeDeviation) { - this.blockSizeDeviation = blockSizeDeviation; - return this; - } - - /** - * Get the block restart interval. - * - * @return block restart interval - */ - public int blockRestartInterval() { - return blockRestartInterval; - } - - /** - * Set the block restart interval. - * - * @param restartInterval block restart interval. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setBlockRestartInterval( - final int restartInterval) { - blockRestartInterval = restartInterval; - return this; - } - - /** - * Get the index block restart interval. - * - * @return index block restart interval - */ - public int indexBlockRestartInterval() { - return indexBlockRestartInterval; - } - - /** - * Set the index block restart interval - * - * @param restartInterval index block restart interval. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setIndexBlockRestartInterval( - final int restartInterval) { - indexBlockRestartInterval = restartInterval; - return this; - } - - /** - * Get the block size for partitioned metadata. - * - * @return block size for partitioned metadata. - */ - public long metadataBlockSize() { - return metadataBlockSize; - } - - /** - * Set block size for partitioned metadata. - * - * @param metadataBlockSize Partitioned metadata block size. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setMetadataBlockSize( - final long metadataBlockSize) { - this.metadataBlockSize = metadataBlockSize; - return this; - } - - /** - * Indicates if we're using partitioned filters. - * - * @return if we're using partition filters. - */ - public boolean partitionFilters() { - return partitionFilters; - } - - /** - * Use partitioned full filters for each SST file. This option is incompatible - * with block-based filters. - * - * Defaults to false. - * - * @param partitionFilters use partition filters. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setPartitionFilters(final boolean partitionFilters) { - this.partitionFilters = partitionFilters; - return this; - } - - /*** - * Option to generate Bloom filters that minimize memory - * internal fragmentation. - * - * See {@link #setOptimizeFiltersForMemory(boolean)}. - * - * @return true if bloom filters are used to minimize memory internal - * fragmentation - */ - @Experimental("Option to generate Bloom filters that minimize memory internal fragmentation") - public boolean optimizeFiltersForMemory() { - return optimizeFiltersForMemory; - } - - /** - * Option to generate Bloom filters that minimize memory - * internal fragmentation. - * - * When false, malloc_usable_size is not available, or format_version < 5, - * filters are generated without regard to internal fragmentation when - * loaded into memory (historical behavior). When true (and - * malloc_usable_size is available and {@link #formatVersion()} >= 5), - * then Bloom filters are generated to "round up" and "round down" their - * sizes to minimize internal fragmentation when loaded into memory, assuming - * the reading DB has the same memory allocation characteristics as the - * generating DB. This option does not break forward or backward - * compatibility. - * - * While individual filters will vary in bits/key and false positive rate - * when setting is true, the implementation attempts to maintain a weighted - * average FP rate for filters consistent with this option set to false. - * - * With Jemalloc for example, this setting is expected to save about 10% of - * the memory footprint and block cache charge of filters, while increasing - * disk usage of filters by about 1-2% due to encoding efficiency losses - * with variance in bits/key. - * - * NOTE: Because some memory counted by block cache might be unmapped pages - * within internal fragmentation, this option can increase observed RSS - * memory usage. With {@link #cacheIndexAndFilterBlocks()} == true, - * this option makes the block cache better at using space it is allowed. - * - * NOTE: Do not set to true if you do not trust malloc_usable_size. With - * this option, RocksDB might access an allocated memory object beyond its - * original size if malloc_usable_size says it is safe to do so. While this - * can be considered bad practice, it should not produce undefined behavior - * unless malloc_usable_size is buggy or broken. - * - * @param optimizeFiltersForMemory true to enable Bloom filters that minimize - * memory internal fragmentation, or false to disable. - * - * @return the reference to the current config. - */ - @Experimental("Option to generate Bloom filters that minimize memory internal fragmentation") - public BlockBasedTableConfig setOptimizeFiltersForMemory(final boolean optimizeFiltersForMemory) { - this.optimizeFiltersForMemory = optimizeFiltersForMemory; - return this; - } - - /** - * Determine if delta encoding is being used to compress block keys. - * - * @return true if delta encoding is enabled, false otherwise. - */ - public boolean useDeltaEncoding() { - return useDeltaEncoding; - } - - /** - * Use delta encoding to compress keys in blocks. - * - * NOTE: {@link ReadOptions#pinData()} requires this option to be disabled. - * - * Default: true - * - * @param useDeltaEncoding true to enable delta encoding - * - * @return the reference to the current config. - */ - public BlockBasedTableConfig setUseDeltaEncoding( - final boolean useDeltaEncoding) { - this.useDeltaEncoding = useDeltaEncoding; - return this; - } - - /** - * Get the filter policy. - * - * @return the current filter policy. - */ - public Filter filterPolicy() { - return filterPolicy; - } - - /** - * Use the specified filter policy to reduce disk reads. - * - * {@link org.rocksdb.Filter} should not be closed before options instances - * using this filter are closed. - * - * {@link org.rocksdb.Filter} instance can be re-used in multiple options - * instances. - * - * @param filterPolicy {@link org.rocksdb.Filter} Filter Policy java instance. - * @return the reference to the current config. - */ - public BlockBasedTableConfig setFilterPolicy( - final Filter filterPolicy) { - this.filterPolicy = filterPolicy; - return this; - } - - /** - * Set the filter. - * - * @param filter the filter - * @return the reference to the current config. - * - * @deprecated Use {@link #setFilterPolicy(Filter)} - */ - @Deprecated - public BlockBasedTableConfig setFilter( - final Filter filter) { - return setFilterPolicy(filter); - } - - /** - * Determine if whole keys as opposed to prefixes are placed in the filter. - * - * @return if whole key filtering is enabled - */ - public boolean wholeKeyFiltering() { - return wholeKeyFiltering; - } - - /** - * If true, place whole keys in the filter (not just prefixes). - * This must generally be true for gets to be efficient. - * Default: true - * - * @param wholeKeyFiltering if enable whole key filtering - * @return the reference to the current config. - */ - public BlockBasedTableConfig setWholeKeyFiltering( - final boolean wholeKeyFiltering) { - this.wholeKeyFiltering = wholeKeyFiltering; - return this; - } - - /** - * Returns true when compression verification is enabled. - * - * See {@link #setVerifyCompression(boolean)}. - * - * @return true if compression verification is enabled. - */ - public boolean verifyCompression() { - return verifyCompression; - } - - /** - * Verify that decompressing the compressed block gives back the input. This - * is a verification mode that we use to detect bugs in compression - * algorithms. - * - * @param verifyCompression true to enable compression verification. - * - * @return the reference to the current config. - */ - public BlockBasedTableConfig setVerifyCompression( - final boolean verifyCompression) { - this.verifyCompression = verifyCompression; - return this; - } - - /** - * Get the Read amplification bytes per-bit. - * - * See {@link #setReadAmpBytesPerBit(int)}. - * - * @return the bytes per-bit. - */ - public int readAmpBytesPerBit() { - return readAmpBytesPerBit; - } - - /** - * Set the Read amplification bytes per-bit. - * - * If used, For every data block we load into memory, we will create a bitmap - * of size ((block_size / `read_amp_bytes_per_bit`) / 8) bytes. This bitmap - * will be used to figure out the percentage we actually read of the blocks. - * - * When this feature is used Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES and - * Tickers::READ_AMP_TOTAL_READ_BYTES can be used to calculate the - * read amplification using this formula - * (READ_AMP_TOTAL_READ_BYTES / READ_AMP_ESTIMATE_USEFUL_BYTES) - * - * value => memory usage (percentage of loaded blocks memory) - * 1 => 12.50 % - * 2 => 06.25 % - * 4 => 03.12 % - * 8 => 01.56 % - * 16 => 00.78 % - * - * Note: This number must be a power of 2, if not it will be sanitized - * to be the next lowest power of 2, for example a value of 7 will be - * treated as 4, a value of 19 will be treated as 16. - * - * Default: 0 (disabled) - * - * @param readAmpBytesPerBit the bytes per-bit - * - * @return the reference to the current config. - */ - public BlockBasedTableConfig setReadAmpBytesPerBit(final int readAmpBytesPerBit) { - this.readAmpBytesPerBit = readAmpBytesPerBit; - return this; - } - - /** - * Get the format version. - * See {@link #setFormatVersion(int)}. - * - * @return the currently configured format version. - */ - public int formatVersion() { - return formatVersion; - } - - /** - *

    We currently have five versions:

    - * - *
      - *
    • 0 - This version is currently written - * out by all RocksDB's versions by default. Can be read by really old - * RocksDB's. Doesn't support changing checksum (default is CRC32).
    • - *
    • 1 - Can be read by RocksDB's versions since 3.0. - * Supports non-default checksum, like xxHash. It is written by RocksDB when - * BlockBasedTableOptions::checksum is something other than kCRC32c. (version - * 0 is silently upconverted)
    • - *
    • 2 - Can be read by RocksDB's versions since 3.10. - * Changes the way we encode compressed blocks with LZ4, BZip2 and Zlib - * compression. If you don't plan to run RocksDB before version 3.10, - * you should probably use this.
    • - *
    • 3 - Can be read by RocksDB's versions since 5.15. Changes the way we - * encode the keys in index blocks. If you don't plan to run RocksDB before - * version 5.15, you should probably use this. - * This option only affects newly written tables. When reading existing - * tables, the information about version is read from the footer.
    • - *
    • 4 - Can be read by RocksDB's versions since 5.16. Changes the way we - * encode the values in index blocks. If you don't plan to run RocksDB before - * version 5.16 and you are using index_block_restart_interval > 1, you should - * probably use this as it would reduce the index size. - * This option only affects newly written tables. When reading existing - * tables, the information about version is read from the footer.
    • - *
    • 5 - Can be read by RocksDB's versions since 6.6.0. - * Full and partitioned filters use a generally faster and more accurate - * Bloom filter implementation, with a different schema.
    • - *
    - * - * @param formatVersion integer representing the version to be used. - * - * @return the reference to the current option. - */ - public BlockBasedTableConfig setFormatVersion( - final int formatVersion) { - assert (formatVersion >= 0); - this.formatVersion = formatVersion; - return this; - } - - /** - * Determine if index compression is enabled. - * - * See {@link #setEnableIndexCompression(boolean)}. - * - * @return true if index compression is enabled, false otherwise - */ - public boolean enableIndexCompression() { - return enableIndexCompression; - } - - /** - * Store index blocks on disk in compressed format. - * - * Changing this option to false will avoid the overhead of decompression - * if index blocks are evicted and read back. - * - * @param enableIndexCompression true to enable index compression, - * false to disable - * - * @return the reference to the current option. - */ - public BlockBasedTableConfig setEnableIndexCompression( - final boolean enableIndexCompression) { - this.enableIndexCompression = enableIndexCompression; - return this; - } - - /** - * Determines whether data blocks are aligned on the lesser of page size - * and block size. - * - * @return true if data blocks are aligned on the lesser of page size - * and block size. - */ - public boolean blockAlign() { - return blockAlign; - } - - /** - * Set whether data blocks should be aligned on the lesser of page size - * and block size. - * - * @param blockAlign true to align data blocks on the lesser of page size - * and block size. - * - * @return the reference to the current option. - */ - public BlockBasedTableConfig setBlockAlign(final boolean blockAlign) { - this.blockAlign = blockAlign; - return this; - } - - /** - * Get the index shortening mode. - * - * @return the index shortening mode. - */ - public IndexShorteningMode indexShortening() { - return indexShortening; - } - - /** - * Set the index shortening mode. - * - * See {@link IndexShorteningMode}. - * - * @param indexShortening the index shortening mode. - * - * @return the reference to the current option. - */ - public BlockBasedTableConfig setIndexShortening(final IndexShorteningMode indexShortening) { - this.indexShortening = indexShortening; - return this; - } - - /** - * Get the size of the cache in bytes that will be used by RocksDB. - * - * @return block cache size in bytes - */ - @Deprecated - public long blockCacheSize() { - return blockCacheSize; - } - - /** - * Set the size of the cache in bytes that will be used by RocksDB. - * If cacheSize is negative, then cache will not be used. - * DEFAULT: 8M - * - * @param blockCacheSize block cache size in bytes - * @return the reference to the current config. - * - * @deprecated Use {@link #setBlockCache(Cache)}. - */ - @Deprecated - public BlockBasedTableConfig setBlockCacheSize(final long blockCacheSize) { - this.blockCacheSize = blockCacheSize; - return this; - } - - /** - * Returns the number of shard bits used in the block cache. - * The resulting number of shards would be 2 ^ (returned value). - * Any negative number means use default settings. - * - * @return the number of shard bits used in the block cache. - */ - @Deprecated - public int cacheNumShardBits() { - return blockCacheNumShardBits; - } - - /** - * Controls the number of shards for the block cache. - * This is applied only if cacheSize is set to non-negative. - * - * @param blockCacheNumShardBits the number of shard bits. The resulting - * number of shards would be 2 ^ numShardBits. Any negative - * number means use default settings." - * @return the reference to the current option. - * - * @deprecated Use {@link #setBlockCache(Cache)}. - */ - @Deprecated - public BlockBasedTableConfig setCacheNumShardBits( - final int blockCacheNumShardBits) { - this.blockCacheNumShardBits = blockCacheNumShardBits; - return this; - } - - /** - * Influence the behavior when kHashSearch is used. - * if false, stores a precise prefix to block range mapping - * if true, does not store prefix and allows prefix hash collision - * (less memory consumption) - * - * @return if hash collisions should be allowed. - * - * @deprecated This option is now deprecated. No matter what value it - * is set to, it will behave as - * if {@link #hashIndexAllowCollision()} == true. - */ - @Deprecated - public boolean hashIndexAllowCollision() { - return true; - } - - /** - * Influence the behavior when kHashSearch is used. - * if false, stores a precise prefix to block range mapping - * if true, does not store prefix and allows prefix hash collision - * (less memory consumption) - * - * @param hashIndexAllowCollision points out if hash collisions should be allowed. - * - * @return the reference to the current config. - * - * @deprecated This option is now deprecated. No matter what value it - * is set to, it will behave as - * if {@link #hashIndexAllowCollision()} == true. - */ - @Deprecated - public BlockBasedTableConfig setHashIndexAllowCollision( - final boolean hashIndexAllowCollision) { - // no-op - return this; - } - - @Override protected long newTableFactoryHandle() { - final long filterPolicyHandle; - if (filterPolicy != null) { - filterPolicyHandle = filterPolicy.nativeHandle_; - } else { - filterPolicyHandle = 0; - } - - final long blockCacheHandle; - if (blockCache != null) { - blockCacheHandle = blockCache.nativeHandle_; - } else { - blockCacheHandle = 0; - } - - final long persistentCacheHandle; - if (persistentCache != null) { - persistentCacheHandle = persistentCache.nativeHandle_; - } else { - persistentCacheHandle = 0; - } - - return newTableFactoryHandle(cacheIndexAndFilterBlocks, - cacheIndexAndFilterBlocksWithHighPriority, pinL0FilterAndIndexBlocksInCache, - pinTopLevelIndexAndFilter, indexType.getValue(), dataBlockIndexType.getValue(), - dataBlockHashTableUtilRatio, checksumType.getValue(), noBlockCache, blockCacheHandle, - persistentCacheHandle, blockSize, blockSizeDeviation, blockRestartInterval, - indexBlockRestartInterval, metadataBlockSize, partitionFilters, optimizeFiltersForMemory, - useDeltaEncoding, filterPolicyHandle, wholeKeyFiltering, verifyCompression, - readAmpBytesPerBit, formatVersion, enableIndexCompression, blockAlign, - indexShortening.getValue(), blockCacheSize, blockCacheNumShardBits); - } - - private native long newTableFactoryHandle(final boolean cacheIndexAndFilterBlocks, - final boolean cacheIndexAndFilterBlocksWithHighPriority, - final boolean pinL0FilterAndIndexBlocksInCache, final boolean pinTopLevelIndexAndFilter, - final byte indexTypeValue, final byte dataBlockIndexTypeValue, - final double dataBlockHashTableUtilRatio, final byte checksumTypeValue, - final boolean noBlockCache, final long blockCacheHandle, final long persistentCacheHandle, - final long blockSize, final int blockSizeDeviation, final int blockRestartInterval, - final int indexBlockRestartInterval, final long metadataBlockSize, - final boolean partitionFilters, final boolean optimizeFiltersForMemory, - final boolean useDeltaEncoding, final long filterPolicyHandle, - final boolean wholeKeyFiltering, final boolean verifyCompression, - final int readAmpBytesPerBit, final int formatVersion, final boolean enableIndexCompression, - final boolean blockAlign, final byte indexShortening, - - @Deprecated final long blockCacheSize, @Deprecated final int blockCacheNumShardBits); - - //TODO(AR) flushBlockPolicyFactory - private boolean cacheIndexAndFilterBlocks; - private boolean cacheIndexAndFilterBlocksWithHighPriority; - private boolean pinL0FilterAndIndexBlocksInCache; - private boolean pinTopLevelIndexAndFilter; - private IndexType indexType; - private DataBlockIndexType dataBlockIndexType; - private double dataBlockHashTableUtilRatio; - private ChecksumType checksumType; - private boolean noBlockCache; - private Cache blockCache; - private PersistentCache persistentCache; - private long blockSize; - private int blockSizeDeviation; - private int blockRestartInterval; - private int indexBlockRestartInterval; - private long metadataBlockSize; - private boolean partitionFilters; - private boolean optimizeFiltersForMemory; - private boolean useDeltaEncoding; - private Filter filterPolicy; - private boolean wholeKeyFiltering; - private boolean verifyCompression; - private int readAmpBytesPerBit; - private int formatVersion; - private boolean enableIndexCompression; - private boolean blockAlign; - private IndexShorteningMode indexShortening; - - // NOTE: ONLY used if blockCache == null - @Deprecated private long blockCacheSize; - @Deprecated private int blockCacheNumShardBits; -} diff --git a/java/src/main/java/org/rocksdb/BloomFilter.java b/java/src/main/java/org/rocksdb/BloomFilter.java deleted file mode 100644 index 8aff715b7..000000000 --- a/java/src/main/java/org/rocksdb/BloomFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Bloom filter policy that uses a bloom filter with approximately - * the specified number of bits per key. - * - *

    - * Note: if you are using a custom comparator that ignores some parts - * of the keys being compared, you must not use this {@code BloomFilter} - * and must provide your own FilterPolicy that also ignores the - * corresponding parts of the keys. For example, if the comparator - * ignores trailing spaces, it would be incorrect to use a - * FilterPolicy (like {@code BloomFilter}) that does not ignore - * trailing spaces in keys.

    - */ -public class BloomFilter extends Filter { - - private static final double DEFAULT_BITS_PER_KEY = 10.0; - - /** - * BloomFilter constructor - * - *

    - * Callers must delete the result after any database that is using the - * result has been closed.

    - */ - public BloomFilter() { - this(DEFAULT_BITS_PER_KEY); - } - - /** - * BloomFilter constructor - * - *

    - * bits_per_key: bits per key in bloom filter. A good value for bits_per_key - * is 9.9, which yields a filter with ~ 1% false positive rate. - *

    - *

    - * Callers must delete the result after any database that is using the - * result has been closed.

    - * - * @param bitsPerKey number of bits to use - */ - public BloomFilter(final double bitsPerKey) { - super(createNewBloomFilter(bitsPerKey)); - } - - /** - * BloomFilter constructor - * - *

    - * bits_per_key: bits per key in bloom filter. A good value for bits_per_key - * is 10, which yields a filter with ~ 1% false positive rate. - *

    default bits_per_key: 10

    - * - *

    - * Callers must delete the result after any database that is using the - * result has been closed.

    - * - * @param bitsPerKey number of bits to use - * @param IGNORED_useBlockBasedMode obsolete, ignored parameter - */ - public BloomFilter(final double bitsPerKey, final boolean IGNORED_useBlockBasedMode) { - this(bitsPerKey); - } - - private native static long createNewBloomFilter(final double bitsKeyKey); -} diff --git a/java/src/main/java/org/rocksdb/BuiltinComparator.java b/java/src/main/java/org/rocksdb/BuiltinComparator.java deleted file mode 100644 index 2c89bf218..000000000 --- a/java/src/main/java/org/rocksdb/BuiltinComparator.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Builtin RocksDB comparators - * - *
      - *
    1. BYTEWISE_COMPARATOR - Sorts all keys in ascending bytewise - * order.
    2. - *
    3. REVERSE_BYTEWISE_COMPARATOR - Sorts all keys in descending bytewise - * order
    4. - *
    - */ -public enum BuiltinComparator { - BYTEWISE_COMPARATOR, REVERSE_BYTEWISE_COMPARATOR -} diff --git a/java/src/main/java/org/rocksdb/ByteBufferGetStatus.java b/java/src/main/java/org/rocksdb/ByteBufferGetStatus.java deleted file mode 100644 index 8eef95447..000000000 --- a/java/src/main/java/org/rocksdb/ByteBufferGetStatus.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; -import java.util.List; - -/** - * A ByteBuffer containing fetched data, together with a result for the fetch - * and the total size of the object fetched. - * - * Used for the individual results of - * {@link RocksDB#multiGetByteBuffers(List, List)} - * {@link RocksDB#multiGetByteBuffers(List, List, List)} - * {@link RocksDB#multiGetByteBuffers(ReadOptions, List, List)} - * {@link RocksDB#multiGetByteBuffers(ReadOptions, List, List, List)} - */ -public class ByteBufferGetStatus { - public final Status status; - public final int requiredSize; - public final ByteBuffer value; - - /** - * Constructor used for success status, when the value is contained in the buffer - * - * @param status the status of the request to fetch into the buffer - * @param requiredSize the size of the data, which may be bigger than the buffer - * @param value the buffer containing as much of the value as fits - */ - ByteBufferGetStatus(final Status status, final int requiredSize, final ByteBuffer value) { - this.status = status; - this.requiredSize = requiredSize; - this.value = value; - } - - /** - * Constructor used for a failure status, when no value is filled in - * - * @param status the status of the request to fetch into the buffer - */ - ByteBufferGetStatus(final Status status) { - this.status = status; - this.requiredSize = 0; - this.value = null; - } -} diff --git a/java/src/main/java/org/rocksdb/Cache.java b/java/src/main/java/org/rocksdb/Cache.java deleted file mode 100644 index 569a1df06..000000000 --- a/java/src/main/java/org/rocksdb/Cache.java +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - - -public abstract class Cache extends RocksObject { - protected Cache(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Returns the memory size for the entries - * residing in cache. - * - * @return cache usage size. - * - */ - public long getUsage() { - assert (isOwningHandle()); - return getUsage(this.nativeHandle_); - } - - /** - * Returns the memory size for the entries - * being pinned in cache. - * - * @return cache pinned usage size. - * - */ - public long getPinnedUsage() { - assert (isOwningHandle()); - return getPinnedUsage(this.nativeHandle_); - } - - private native static long getUsage(final long handle); - private native static long getPinnedUsage(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java b/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java deleted file mode 100644 index 6c87cc188..000000000 --- a/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2017-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Just a Java wrapper around CassandraCompactionFilter implemented in C++ - */ -public class CassandraCompactionFilter - extends AbstractCompactionFilter { - public CassandraCompactionFilter(boolean purgeTtlOnExpiration, int gcGracePeriodInSeconds) { - super(createNewCassandraCompactionFilter0(purgeTtlOnExpiration, gcGracePeriodInSeconds)); - } - - private native static long createNewCassandraCompactionFilter0( - boolean purgeTtlOnExpiration, int gcGracePeriodInSeconds); -} diff --git a/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java b/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java deleted file mode 100644 index 4b0c71ba5..000000000 --- a/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2017-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * CassandraValueMergeOperator is a merge operator that merges two cassandra wide column - * values. - */ -public class CassandraValueMergeOperator extends MergeOperator { - public CassandraValueMergeOperator(int gcGracePeriodInSeconds) { - super(newSharedCassandraValueMergeOperator(gcGracePeriodInSeconds, 0)); - } - - public CassandraValueMergeOperator(int gcGracePeriodInSeconds, int operandsLimit) { - super(newSharedCassandraValueMergeOperator(gcGracePeriodInSeconds, operandsLimit)); - } - - private native static long newSharedCassandraValueMergeOperator( - int gcGracePeriodInSeconds, int limit); - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/Checkpoint.java b/java/src/main/java/org/rocksdb/Checkpoint.java deleted file mode 100644 index 000969932..000000000 --- a/java/src/main/java/org/rocksdb/Checkpoint.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Provides Checkpoint functionality. Checkpoints - * provide persistent snapshots of RocksDB databases. - */ -public class Checkpoint extends RocksObject { - - /** - * Creates a Checkpoint object to be used for creating open-able - * snapshots. - * - * @param db {@link RocksDB} instance. - * @return a Checkpoint instance. - * - * @throws java.lang.IllegalArgumentException if {@link RocksDB} - * instance is null. - * @throws java.lang.IllegalStateException if {@link RocksDB} - * instance is not initialized. - */ - public static Checkpoint create(final RocksDB db) { - if (db == null) { - throw new IllegalArgumentException( - "RocksDB instance shall not be null."); - } else if (!db.isOwningHandle()) { - throw new IllegalStateException( - "RocksDB instance must be initialized."); - } - Checkpoint checkpoint = new Checkpoint(db); - return checkpoint; - } - - /** - *

    Builds an open-able snapshot of RocksDB on the same disk, which - * accepts an output directory on the same disk, and under the directory - * (1) hard-linked SST files pointing to existing live SST files - * (2) a copied manifest files and other files

    - * - * @param checkpointPath path to the folder where the snapshot is going - * to be stored. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void createCheckpoint(final String checkpointPath) - throws RocksDBException { - createCheckpoint(nativeHandle_, checkpointPath); - } - - private Checkpoint(final RocksDB db) { - super(newCheckpoint(db.nativeHandle_)); - this.db_ = db; - } - - private final RocksDB db_; - - private static native long newCheckpoint(long dbHandle); - @Override protected final native void disposeInternal(final long handle); - - private native void createCheckpoint(long handle, String checkpointPath) - throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/ChecksumType.java b/java/src/main/java/org/rocksdb/ChecksumType.java deleted file mode 100644 index e03fa14ba..000000000 --- a/java/src/main/java/org/rocksdb/ChecksumType.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Checksum types used in conjunction with BlockBasedTable. - */ -public enum ChecksumType { - /** - * Not implemented yet. - */ - kNoChecksum((byte) 0), - /** - * CRC32 Checksum - */ - kCRC32c((byte) 1), - /** - * XX Hash - */ - kxxHash((byte) 2), - /** - * XX Hash 64 - */ - kxxHash64((byte) 3), - - kXXH3((byte) 4); - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - private ChecksumType(final byte value) { - value_ = value; - } - - private final byte value_; -} diff --git a/java/src/main/java/org/rocksdb/ClockCache.java b/java/src/main/java/org/rocksdb/ClockCache.java deleted file mode 100644 index a66dc0e8a..000000000 --- a/java/src/main/java/org/rocksdb/ClockCache.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Similar to {@link LRUCache}, but based on the CLOCK algorithm with - * better concurrent performance in some cases - */ -public class ClockCache extends Cache { - - /** - * Create a new cache with a fixed size capacity. - * - * @param capacity The fixed size capacity of the cache - */ - public ClockCache(final long capacity) { - super(newClockCache(capacity, -1, false)); - } - - /** - * Create a new cache with a fixed size capacity. The cache is sharded - * to 2^numShardBits shards, by hash of the key. The total capacity - * is divided and evenly assigned to each shard. - * numShardBits = -1 means it is automatically determined: every shard - * will be at least 512KB and number of shard bits will not exceed 6. - * - * @param capacity The fixed size capacity of the cache - * @param numShardBits The cache is sharded to 2^numShardBits shards, - * by hash of the key - */ - public ClockCache(final long capacity, final int numShardBits) { - super(newClockCache(capacity, numShardBits, false)); - } - - /** - * Create a new cache with a fixed size capacity. The cache is sharded - * to 2^numShardBits shards, by hash of the key. The total capacity - * is divided and evenly assigned to each shard. If strictCapacityLimit - * is set, insert to the cache will fail when cache is full. - * numShardBits = -1 means it is automatically determined: every shard - * will be at least 512KB and number of shard bits will not exceed 6. - * - * @param capacity The fixed size capacity of the cache - * @param numShardBits The cache is sharded to 2^numShardBits shards, - * by hash of the key - * @param strictCapacityLimit insert to the cache will fail when cache is full - */ - public ClockCache(final long capacity, final int numShardBits, - final boolean strictCapacityLimit) { - super(newClockCache(capacity, numShardBits, strictCapacityLimit)); - } - - private native static long newClockCache(final long capacity, - final int numShardBits, final boolean strictCapacityLimit); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java b/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java deleted file mode 100644 index 125a8dcf8..000000000 --- a/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; - -/** - *

    Describes a column family with a - * name and respective Options.

    - */ -public class ColumnFamilyDescriptor { - - /** - *

    Creates a new Column Family using a name and default - * options,

    - * - * @param columnFamilyName name of column family. - * @since 3.10.0 - */ - public ColumnFamilyDescriptor(final byte[] columnFamilyName) { - this(columnFamilyName, new ColumnFamilyOptions()); - } - - /** - *

    Creates a new Column Family using a name and custom - * options.

    - * - * @param columnFamilyName name of column family. - * @param columnFamilyOptions options to be used with - * column family. - * @since 3.10.0 - */ - public ColumnFamilyDescriptor(final byte[] columnFamilyName, - final ColumnFamilyOptions columnFamilyOptions) { - columnFamilyName_ = columnFamilyName; - columnFamilyOptions_ = columnFamilyOptions; - } - - /** - * Retrieve name of column family. - * - * @return column family name. - * @since 3.10.0 - */ - public byte[] getName() { - return columnFamilyName_; - } - - /** - * Retrieve assigned options instance. - * - * @return Options instance assigned to this instance. - */ - public ColumnFamilyOptions getOptions() { - return columnFamilyOptions_; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - final ColumnFamilyDescriptor that = (ColumnFamilyDescriptor) o; - return Arrays.equals(columnFamilyName_, that.columnFamilyName_) - && columnFamilyOptions_.nativeHandle_ == that.columnFamilyOptions_.nativeHandle_; - } - - @Override - public int hashCode() { - int result = (int) (columnFamilyOptions_.nativeHandle_ ^ (columnFamilyOptions_.nativeHandle_ >>> 32)); - result = 31 * result + Arrays.hashCode(columnFamilyName_); - return result; - } - - private final byte[] columnFamilyName_; - private final ColumnFamilyOptions columnFamilyOptions_; -} diff --git a/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java b/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java deleted file mode 100644 index 1ac0a35bb..000000000 --- a/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.Objects; - -/** - * ColumnFamilyHandle class to hold handles to underlying rocksdb - * ColumnFamily Pointers. - */ -public class ColumnFamilyHandle extends RocksObject { - /** - * Constructs column family Java object, which operates on underlying native object. - * - * @param rocksDB db instance associated with this column family - * @param nativeHandle native handle to underlying native ColumnFamily object - */ - ColumnFamilyHandle(final RocksDB rocksDB, - final long nativeHandle) { - super(nativeHandle); - // rocksDB must point to a valid RocksDB instance; - assert(rocksDB != null); - // ColumnFamilyHandle must hold a reference to the related RocksDB instance - // to guarantee that while a GC cycle starts ColumnFamilyHandle instances - // are freed prior to RocksDB instances. - this.rocksDB_ = rocksDB; - } - - /** - * Constructor called only from JNI. - * - * NOTE: we are producing an additional Java Object here to represent the underlying native C++ - * ColumnFamilyHandle object. The underlying object is not owned by ourselves. The Java API user - * likely already had a ColumnFamilyHandle Java object which owns the underlying C++ object, as - * they will have been presented it when they opened the database or added a Column Family. - * - * - * TODO(AR) - Potentially a better design would be to cache the active Java Column Family Objects - * in RocksDB, and return the same Java Object instead of instantiating a new one here. This could - * also help us to improve the Java API semantics for Java users. See for example - * https://github.com/facebook/rocksdb/issues/2687. - * - * @param nativeHandle native handle to the column family. - */ - ColumnFamilyHandle(final long nativeHandle) { - super(nativeHandle); - rocksDB_ = null; - disOwnNativeHandle(); - } - - /** - * Gets the name of the Column Family. - * - * @return The name of the Column Family. - * - * @throws RocksDBException if an error occurs whilst retrieving the name. - */ - public byte[] getName() throws RocksDBException { - assert(isOwningHandle() || isDefaultColumnFamily()); - return getName(nativeHandle_); - } - - /** - * Gets the ID of the Column Family. - * - * @return the ID of the Column Family. - */ - public int getID() { - assert(isOwningHandle() || isDefaultColumnFamily()); - return getID(nativeHandle_); - } - - /** - * Gets the up-to-date descriptor of the column family - * associated with this handle. Since it fills "*desc" with the up-to-date - * information, this call might internally lock and release DB mutex to - * access the up-to-date CF options. In addition, all the pointer-typed - * options cannot be referenced any longer than the original options exist. - * - * Note that this function is not supported in RocksDBLite. - * - * @return the up-to-date descriptor. - * - * @throws RocksDBException if an error occurs whilst retrieving the - * descriptor. - */ - public ColumnFamilyDescriptor getDescriptor() throws RocksDBException { - assert(isOwningHandle() || isDefaultColumnFamily()); - return getDescriptor(nativeHandle_); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - final ColumnFamilyHandle that = (ColumnFamilyHandle) o; - try { - return rocksDB_.nativeHandle_ == that.rocksDB_.nativeHandle_ && - getID() == that.getID() && - Arrays.equals(getName(), that.getName()); - } catch (RocksDBException e) { - throw new RuntimeException("Cannot compare column family handles", e); - } - } - - @Override - public int hashCode() { - try { - int result = Objects.hash(getID(), rocksDB_.nativeHandle_); - result = 31 * result + Arrays.hashCode(getName()); - return result; - } catch (RocksDBException e) { - throw new RuntimeException("Cannot calculate hash code of column family handle", e); - } - } - - protected boolean isDefaultColumnFamily() { - return nativeHandle_ == rocksDB_.getDefaultColumnFamily().nativeHandle_; - } - - /** - *

    Deletes underlying C++ iterator pointer.

    - * - *

    Note: the underlying handle can only be safely deleted if the RocksDB - * instance related to a certain ColumnFamilyHandle is still valid and - * initialized. Therefore {@code disposeInternal()} checks if the RocksDB is - * initialized before freeing the native handle.

    - */ - @Override - protected void disposeInternal() { - if(rocksDB_.isOwningHandle()) { - disposeInternal(nativeHandle_); - } - } - - private native byte[] getName(final long handle) throws RocksDBException; - private native int getID(final long handle); - private native ColumnFamilyDescriptor getDescriptor(final long handle) throws RocksDBException; - @Override protected final native void disposeInternal(final long handle); - - private final RocksDB rocksDB_; -} diff --git a/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java b/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java deleted file mode 100644 index 191904017..000000000 --- a/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.List; - -/** - * The metadata that describes a column family. - */ -public class ColumnFamilyMetaData { - private final long size; - private final long fileCount; - private final byte[] name; - private final LevelMetaData[] levels; - - /** - * Called from JNI C++ - */ - private ColumnFamilyMetaData( - final long size, - final long fileCount, - final byte[] name, - final LevelMetaData[] levels) { - this.size = size; - this.fileCount = fileCount; - this.name = name; - this.levels = levels; - } - - /** - * The size of this column family in bytes, which is equal to the sum of - * the file size of its {@link #levels()}. - * - * @return the size of this column family - */ - public long size() { - return size; - } - - /** - * The number of files in this column family. - * - * @return the number of files - */ - public long fileCount() { - return fileCount; - } - - /** - * The name of the column family. - * - * @return the name - */ - public byte[] name() { - return name; - } - - /** - * The metadata of all levels in this column family. - * - * @return the levels metadata - */ - public List levels() { - return Arrays.asList(levels); - } -} diff --git a/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java b/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java deleted file mode 100644 index 65dfd328f..000000000 --- a/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java +++ /dev/null @@ -1,1543 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.file.Paths; -import java.util.*; - -/** - * ColumnFamilyOptions to control the behavior of a database. It will be used - * during the creation of a {@link org.rocksdb.RocksDB} (i.e., RocksDB.open()). - *

    - * As a descendant of {@link AbstractNativeReference}, this class is {@link AutoCloseable} - * and will be automatically released if opened in the preamble of a try with resources block. - */ -public class ColumnFamilyOptions extends RocksObject - implements ColumnFamilyOptionsInterface, - MutableColumnFamilyOptionsInterface { - static { - RocksDB.loadLibrary(); - } - - /** - * Construct ColumnFamilyOptions. - *

    - * This constructor will create (by allocating a block of memory) - * an {@code rocksdb::ColumnFamilyOptions} in the c++ side. - */ - public ColumnFamilyOptions() { - super(newColumnFamilyOptions()); - } - - /** - * Copy constructor for ColumnFamilyOptions. - *

    - * NOTE: This does a shallow copy, which means comparator, merge_operator, compaction_filter, - * compaction_filter_factory and other pointers will be cloned! - * - * @param other The ColumnFamilyOptions to copy. - */ - public ColumnFamilyOptions(final ColumnFamilyOptions other) { - super(copyColumnFamilyOptions(other.nativeHandle_)); - this.memTableConfig_ = other.memTableConfig_; - this.tableFormatConfig_ = other.tableFormatConfig_; - this.comparator_ = other.comparator_; - this.compactionFilter_ = other.compactionFilter_; - this.compactionFilterFactory_ = other.compactionFilterFactory_; - this.compactionOptionsUniversal_ = other.compactionOptionsUniversal_; - this.compactionOptionsFIFO_ = other.compactionOptionsFIFO_; - this.bottommostCompressionOptions_ = other.bottommostCompressionOptions_; - this.compressionOptions_ = other.compressionOptions_; - this.compactionThreadLimiter_ = other.compactionThreadLimiter_; - this.sstPartitionerFactory_ = other.sstPartitionerFactory_; - } - - /** - * Constructor from Options - * - * @param options The options. - */ - public ColumnFamilyOptions(final Options options) { - super(newColumnFamilyOptionsFromOptions(options.nativeHandle_)); - } - - /** - *

    Constructor to be used by - * {@link #getColumnFamilyOptionsFromProps(java.util.Properties)}, - * {@link ColumnFamilyDescriptor#getOptions()} - * and also called via JNI.

    - * - * @param handle native handle to ColumnFamilyOptions instance. - */ - ColumnFamilyOptions(final long handle) { - super(handle); - } - - /** - *

    Method to get a options instance by using pre-configured - * property values. If one or many values are undefined in - * the context of RocksDB the method will return a null - * value.

    - * - *

    Note: Property keys can be derived from - * getter methods within the options class. Example: the method - * {@code writeBufferSize()} has a property key: - * {@code write_buffer_size}.

    - * - * @param properties {@link java.util.Properties} instance. - * - * @return {@link org.rocksdb.ColumnFamilyOptions instance} - * or null. - * - * @throws java.lang.IllegalArgumentException if null or empty - * {@link Properties} instance is passed to the method call. - */ - public static ColumnFamilyOptions getColumnFamilyOptionsFromProps( - final Properties properties) { - ColumnFamilyOptions columnFamilyOptions = null; - final long handle = - getColumnFamilyOptionsFromProps(Options.getOptionStringFromProps(properties)); - if (handle != 0) { - columnFamilyOptions = new ColumnFamilyOptions(handle); - } - return columnFamilyOptions; - } - - /** - *

    Method to get a options instance by using pre-configured - * property values. If one or many values are undefined in - * the context of RocksDB the method will return a null - * value.

    - * - *

    Note: Property keys can be derived from - * getter methods within the options class. Example: the method - * {@code writeBufferSize()} has a property key: - * {@code write_buffer_size}.

    - * - * @param cfgOpts ConfigOptions controlling how the properties are parsed. - * @param properties {@link java.util.Properties} instance. - * - * @return {@link org.rocksdb.ColumnFamilyOptions instance} - * or null. - * - * @throws java.lang.IllegalArgumentException if null or empty - * {@link Properties} instance is passed to the method call. - */ - public static ColumnFamilyOptions getColumnFamilyOptionsFromProps( - final ConfigOptions cfgOpts, final Properties properties) { - ColumnFamilyOptions columnFamilyOptions = null; - final long handle = getColumnFamilyOptionsFromProps( - cfgOpts.nativeHandle_, Options.getOptionStringFromProps(properties)); - if (handle != 0){ - columnFamilyOptions = new ColumnFamilyOptions(handle); - } - return columnFamilyOptions; - } - - @Override - public ColumnFamilyOptions oldDefaults(final int majorVersion, final int minorVersion) { - oldDefaults(nativeHandle_, majorVersion, minorVersion); - return this; - } - - @Override - public ColumnFamilyOptions optimizeForSmallDb() { - optimizeForSmallDb(nativeHandle_); - return this; - } - - @Override - public ColumnFamilyOptions optimizeForSmallDb(final Cache cache) { - optimizeForSmallDb(nativeHandle_, cache.getNativeHandle()); - return this; - } - - @Override - public ColumnFamilyOptions optimizeForPointLookup( - final long blockCacheSizeMb) { - optimizeForPointLookup(nativeHandle_, - blockCacheSizeMb); - return this; - } - - @Override - public ColumnFamilyOptions optimizeLevelStyleCompaction() { - optimizeLevelStyleCompaction(nativeHandle_, - DEFAULT_COMPACTION_MEMTABLE_MEMORY_BUDGET); - return this; - } - - @Override - public ColumnFamilyOptions optimizeLevelStyleCompaction( - final long memtableMemoryBudget) { - optimizeLevelStyleCompaction(nativeHandle_, - memtableMemoryBudget); - return this; - } - - @Override - public ColumnFamilyOptions optimizeUniversalStyleCompaction() { - optimizeUniversalStyleCompaction(nativeHandle_, - DEFAULT_COMPACTION_MEMTABLE_MEMORY_BUDGET); - return this; - } - - @Override - public ColumnFamilyOptions optimizeUniversalStyleCompaction( - final long memtableMemoryBudget) { - optimizeUniversalStyleCompaction(nativeHandle_, - memtableMemoryBudget); - return this; - } - - @Override - public ColumnFamilyOptions setComparator( - final BuiltinComparator builtinComparator) { - assert(isOwningHandle()); - setComparatorHandle(nativeHandle_, builtinComparator.ordinal()); - return this; - } - - @Override - public ColumnFamilyOptions setComparator( - final AbstractComparator comparator) { - assert (isOwningHandle()); - setComparatorHandle(nativeHandle_, comparator.nativeHandle_, - comparator.getComparatorType().getValue()); - comparator_ = comparator; - return this; - } - - @Override - public ColumnFamilyOptions setMergeOperatorName(final String name) { - assert (isOwningHandle()); - if (name == null) { - throw new IllegalArgumentException( - "Merge operator name must not be null."); - } - setMergeOperatorName(nativeHandle_, name); - return this; - } - - @Override - public ColumnFamilyOptions setMergeOperator( - final MergeOperator mergeOperator) { - setMergeOperator(nativeHandle_, mergeOperator.nativeHandle_); - return this; - } - - @Override - public ColumnFamilyOptions setCompactionFilter( - final AbstractCompactionFilter> - compactionFilter) { - setCompactionFilterHandle(nativeHandle_, compactionFilter.nativeHandle_); - compactionFilter_ = compactionFilter; - return this; - } - - @Override - public AbstractCompactionFilter> compactionFilter() { - assert (isOwningHandle()); - return compactionFilter_; - } - - @Override - public ColumnFamilyOptions setCompactionFilterFactory(final AbstractCompactionFilterFactory> compactionFilterFactory) { - assert (isOwningHandle()); - setCompactionFilterFactoryHandle(nativeHandle_, compactionFilterFactory.nativeHandle_); - compactionFilterFactory_ = compactionFilterFactory; - return this; - } - - @Override - public AbstractCompactionFilterFactory> compactionFilterFactory() { - assert (isOwningHandle()); - return compactionFilterFactory_; - } - - @Override - public ColumnFamilyOptions setWriteBufferSize(final long writeBufferSize) { - assert(isOwningHandle()); - setWriteBufferSize(nativeHandle_, writeBufferSize); - return this; - } - - @Override - public long writeBufferSize() { - assert(isOwningHandle()); - return writeBufferSize(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxWriteBufferNumber( - final int maxWriteBufferNumber) { - assert(isOwningHandle()); - setMaxWriteBufferNumber(nativeHandle_, maxWriteBufferNumber); - return this; - } - - @Override - public int maxWriteBufferNumber() { - assert(isOwningHandle()); - return maxWriteBufferNumber(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMinWriteBufferNumberToMerge( - final int minWriteBufferNumberToMerge) { - setMinWriteBufferNumberToMerge(nativeHandle_, minWriteBufferNumberToMerge); - return this; - } - - @Override - public int minWriteBufferNumberToMerge() { - return minWriteBufferNumberToMerge(nativeHandle_); - } - - @Override - public ColumnFamilyOptions useFixedLengthPrefixExtractor(final int n) { - assert(isOwningHandle()); - useFixedLengthPrefixExtractor(nativeHandle_, n); - return this; - } - - @Override - public ColumnFamilyOptions useCappedPrefixExtractor(final int n) { - assert(isOwningHandle()); - useCappedPrefixExtractor(nativeHandle_, n); - return this; - } - - @Override - public ColumnFamilyOptions setCompressionType( - final CompressionType compressionType) { - setCompressionType(nativeHandle_, compressionType.getValue()); - return this; - } - - @Override - public CompressionType compressionType() { - return CompressionType.getCompressionType(compressionType(nativeHandle_)); - } - - @Override - public ColumnFamilyOptions setCompressionPerLevel( - final List compressionLevels) { - final byte[] byteCompressionTypes = new byte[ - compressionLevels.size()]; - for (int i = 0; i < compressionLevels.size(); i++) { - byteCompressionTypes[i] = compressionLevels.get(i).getValue(); - } - setCompressionPerLevel(nativeHandle_, byteCompressionTypes); - return this; - } - - @Override - public List compressionPerLevel() { - final byte[] byteCompressionTypes = - compressionPerLevel(nativeHandle_); - final List compressionLevels = new ArrayList<>(); - for (final byte byteCompressionType : byteCompressionTypes) { - compressionLevels.add(CompressionType.getCompressionType( - byteCompressionType)); - } - return compressionLevels; - } - - @Override - public ColumnFamilyOptions setBottommostCompressionType( - final CompressionType bottommostCompressionType) { - setBottommostCompressionType(nativeHandle_, - bottommostCompressionType.getValue()); - return this; - } - - @Override - public CompressionType bottommostCompressionType() { - return CompressionType.getCompressionType( - bottommostCompressionType(nativeHandle_)); - } - - @Override - public ColumnFamilyOptions setBottommostCompressionOptions( - final CompressionOptions bottommostCompressionOptions) { - setBottommostCompressionOptions(nativeHandle_, - bottommostCompressionOptions.nativeHandle_); - this.bottommostCompressionOptions_ = bottommostCompressionOptions; - return this; - } - - @Override - public CompressionOptions bottommostCompressionOptions() { - return this.bottommostCompressionOptions_; - } - - @Override - public ColumnFamilyOptions setCompressionOptions( - final CompressionOptions compressionOptions) { - setCompressionOptions(nativeHandle_, compressionOptions.nativeHandle_); - this.compressionOptions_ = compressionOptions; - return this; - } - - @Override - public CompressionOptions compressionOptions() { - return this.compressionOptions_; - } - - @Override - public ColumnFamilyOptions setNumLevels(final int numLevels) { - setNumLevels(nativeHandle_, numLevels); - return this; - } - - @Override - public int numLevels() { - return numLevels(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevelZeroFileNumCompactionTrigger( - final int numFiles) { - setLevelZeroFileNumCompactionTrigger( - nativeHandle_, numFiles); - return this; - } - - @Override - public int levelZeroFileNumCompactionTrigger() { - return levelZeroFileNumCompactionTrigger(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevelZeroSlowdownWritesTrigger( - final int numFiles) { - setLevelZeroSlowdownWritesTrigger(nativeHandle_, numFiles); - return this; - } - - @Override - public int levelZeroSlowdownWritesTrigger() { - return levelZeroSlowdownWritesTrigger(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevelZeroStopWritesTrigger(final int numFiles) { - setLevelZeroStopWritesTrigger(nativeHandle_, numFiles); - return this; - } - - @Override - public int levelZeroStopWritesTrigger() { - return levelZeroStopWritesTrigger(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setTargetFileSizeBase( - final long targetFileSizeBase) { - setTargetFileSizeBase(nativeHandle_, targetFileSizeBase); - return this; - } - - @Override - public long targetFileSizeBase() { - return targetFileSizeBase(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setTargetFileSizeMultiplier( - final int multiplier) { - setTargetFileSizeMultiplier(nativeHandle_, multiplier); - return this; - } - - @Override - public int targetFileSizeMultiplier() { - return targetFileSizeMultiplier(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxBytesForLevelBase( - final long maxBytesForLevelBase) { - setMaxBytesForLevelBase(nativeHandle_, maxBytesForLevelBase); - return this; - } - - @Override - public long maxBytesForLevelBase() { - return maxBytesForLevelBase(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevelCompactionDynamicLevelBytes( - final boolean enableLevelCompactionDynamicLevelBytes) { - setLevelCompactionDynamicLevelBytes(nativeHandle_, - enableLevelCompactionDynamicLevelBytes); - return this; - } - - @Override - public boolean levelCompactionDynamicLevelBytes() { - return levelCompactionDynamicLevelBytes(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxBytesForLevelMultiplier(final double multiplier) { - setMaxBytesForLevelMultiplier(nativeHandle_, multiplier); - return this; - } - - @Override - public double maxBytesForLevelMultiplier() { - return maxBytesForLevelMultiplier(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxCompactionBytes(final long maxCompactionBytes) { - setMaxCompactionBytes(nativeHandle_, maxCompactionBytes); - return this; - } - - @Override - public long maxCompactionBytes() { - return maxCompactionBytes(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setArenaBlockSize( - final long arenaBlockSize) { - setArenaBlockSize(nativeHandle_, arenaBlockSize); - return this; - } - - @Override - public long arenaBlockSize() { - return arenaBlockSize(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setDisableAutoCompactions( - final boolean disableAutoCompactions) { - setDisableAutoCompactions(nativeHandle_, disableAutoCompactions); - return this; - } - - @Override - public boolean disableAutoCompactions() { - return disableAutoCompactions(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setCompactionStyle( - final CompactionStyle compactionStyle) { - setCompactionStyle(nativeHandle_, compactionStyle.getValue()); - return this; - } - - @Override - public CompactionStyle compactionStyle() { - return CompactionStyle.fromValue(compactionStyle(nativeHandle_)); - } - - @Override - public ColumnFamilyOptions setMaxTableFilesSizeFIFO( - final long maxTableFilesSize) { - assert(maxTableFilesSize > 0); // unsigned native type - assert(isOwningHandle()); - setMaxTableFilesSizeFIFO(nativeHandle_, maxTableFilesSize); - return this; - } - - @Override - public long maxTableFilesSizeFIFO() { - return maxTableFilesSizeFIFO(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxSequentialSkipInIterations( - final long maxSequentialSkipInIterations) { - setMaxSequentialSkipInIterations(nativeHandle_, - maxSequentialSkipInIterations); - return this; - } - - @Override - public long maxSequentialSkipInIterations() { - return maxSequentialSkipInIterations(nativeHandle_); - } - - @Override - public MemTableConfig memTableConfig() { - return this.memTableConfig_; - } - - @Override - public ColumnFamilyOptions setMemTableConfig( - final MemTableConfig memTableConfig) { - setMemTableFactory( - nativeHandle_, memTableConfig.newMemTableFactoryHandle()); - this.memTableConfig_ = memTableConfig; - return this; - } - - @Override - public String memTableFactoryName() { - assert(isOwningHandle()); - return memTableFactoryName(nativeHandle_); - } - - @Override - public TableFormatConfig tableFormatConfig() { - return this.tableFormatConfig_; - } - - @Override - public ColumnFamilyOptions setTableFormatConfig( - final TableFormatConfig tableFormatConfig) { - setTableFactory(nativeHandle_, tableFormatConfig.newTableFactoryHandle()); - this.tableFormatConfig_ = tableFormatConfig; - return this; - } - - @Override - public String tableFactoryName() { - assert(isOwningHandle()); - return tableFactoryName(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setCfPaths(final Collection cfPaths) { - assert (isOwningHandle()); - - final int len = cfPaths.size(); - final String[] paths = new String[len]; - final long[] targetSizes = new long[len]; - - int i = 0; - for (final DbPath dbPath : cfPaths) { - paths[i] = dbPath.path.toString(); - targetSizes[i] = dbPath.targetSize; - i++; - } - setCfPaths(nativeHandle_, paths, targetSizes); - return this; - } - - @Override - public List cfPaths() { - final int len = (int) cfPathsLen(nativeHandle_); - - if (len == 0) { - return Collections.emptyList(); - } - - final String[] paths = new String[len]; - final long[] targetSizes = new long[len]; - - cfPaths(nativeHandle_, paths, targetSizes); - - final List cfPaths = new ArrayList<>(); - for (int i = 0; i < len; i++) { - cfPaths.add(new DbPath(Paths.get(paths[i]), targetSizes[i])); - } - - return cfPaths; - } - - @Override - public ColumnFamilyOptions setInplaceUpdateSupport( - final boolean inplaceUpdateSupport) { - setInplaceUpdateSupport(nativeHandle_, inplaceUpdateSupport); - return this; - } - - @Override - public boolean inplaceUpdateSupport() { - return inplaceUpdateSupport(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setInplaceUpdateNumLocks( - final long inplaceUpdateNumLocks) { - setInplaceUpdateNumLocks(nativeHandle_, inplaceUpdateNumLocks); - return this; - } - - @Override - public long inplaceUpdateNumLocks() { - return inplaceUpdateNumLocks(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMemtablePrefixBloomSizeRatio( - final double memtablePrefixBloomSizeRatio) { - setMemtablePrefixBloomSizeRatio(nativeHandle_, memtablePrefixBloomSizeRatio); - return this; - } - - @Override - public double memtablePrefixBloomSizeRatio() { - return memtablePrefixBloomSizeRatio(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setExperimentalMempurgeThreshold( - final double experimentalMempurgeThreshold) { - setExperimentalMempurgeThreshold(nativeHandle_, experimentalMempurgeThreshold); - return this; - } - - @Override - public double experimentalMempurgeThreshold() { - return experimentalMempurgeThreshold(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMemtableWholeKeyFiltering(final boolean memtableWholeKeyFiltering) { - setMemtableWholeKeyFiltering(nativeHandle_, memtableWholeKeyFiltering); - return this; - } - - @Override - public boolean memtableWholeKeyFiltering() { - return memtableWholeKeyFiltering(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setBloomLocality(final int bloomLocality) { - setBloomLocality(nativeHandle_, bloomLocality); - return this; - } - - @Override - public int bloomLocality() { - return bloomLocality(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxSuccessiveMerges( - final long maxSuccessiveMerges) { - setMaxSuccessiveMerges(nativeHandle_, maxSuccessiveMerges); - return this; - } - - @Override - public long maxSuccessiveMerges() { - return maxSuccessiveMerges(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setOptimizeFiltersForHits( - final boolean optimizeFiltersForHits) { - setOptimizeFiltersForHits(nativeHandle_, optimizeFiltersForHits); - return this; - } - - @Override - public boolean optimizeFiltersForHits() { - return optimizeFiltersForHits(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMemtableHugePageSize(final long memtableHugePageSize) { - setMemtableHugePageSize(nativeHandle_, - memtableHugePageSize); - return this; - } - - @Override - public long memtableHugePageSize() { - return memtableHugePageSize(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setSoftPendingCompactionBytesLimit( - final long softPendingCompactionBytesLimit) { - setSoftPendingCompactionBytesLimit(nativeHandle_, - softPendingCompactionBytesLimit); - return this; - } - - @Override - public long softPendingCompactionBytesLimit() { - return softPendingCompactionBytesLimit(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setHardPendingCompactionBytesLimit( - final long hardPendingCompactionBytesLimit) { - setHardPendingCompactionBytesLimit(nativeHandle_, hardPendingCompactionBytesLimit); - return this; - } - - @Override - public long hardPendingCompactionBytesLimit() { - return hardPendingCompactionBytesLimit(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevel0FileNumCompactionTrigger( - final int level0FileNumCompactionTrigger) { - setLevel0FileNumCompactionTrigger(nativeHandle_, level0FileNumCompactionTrigger); - return this; - } - - @Override - public int level0FileNumCompactionTrigger() { - return level0FileNumCompactionTrigger(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevel0SlowdownWritesTrigger(final int level0SlowdownWritesTrigger) { - setLevel0SlowdownWritesTrigger(nativeHandle_, level0SlowdownWritesTrigger); - return this; - } - - @Override - public int level0SlowdownWritesTrigger() { - return level0SlowdownWritesTrigger(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setLevel0StopWritesTrigger(final int level0StopWritesTrigger) { - setLevel0StopWritesTrigger(nativeHandle_, level0StopWritesTrigger); - return this; - } - - @Override - public int level0StopWritesTrigger() { - return level0StopWritesTrigger(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxBytesForLevelMultiplierAdditional( - final int[] maxBytesForLevelMultiplierAdditional) { - setMaxBytesForLevelMultiplierAdditional(nativeHandle_, maxBytesForLevelMultiplierAdditional); - return this; - } - - @Override - public int[] maxBytesForLevelMultiplierAdditional() { - return maxBytesForLevelMultiplierAdditional(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setParanoidFileChecks(final boolean paranoidFileChecks) { - setParanoidFileChecks(nativeHandle_, paranoidFileChecks); - return this; - } - - @Override - public boolean paranoidFileChecks() { - return paranoidFileChecks(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setMaxWriteBufferNumberToMaintain( - final int maxWriteBufferNumberToMaintain) { - setMaxWriteBufferNumberToMaintain( - nativeHandle_, maxWriteBufferNumberToMaintain); - return this; - } - - @Override - public int maxWriteBufferNumberToMaintain() { - return maxWriteBufferNumberToMaintain(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setCompactionPriority( - final CompactionPriority compactionPriority) { - setCompactionPriority(nativeHandle_, compactionPriority.getValue()); - return this; - } - - @Override - public CompactionPriority compactionPriority() { - return CompactionPriority.getCompactionPriority( - compactionPriority(nativeHandle_)); - } - - @Override - public ColumnFamilyOptions setReportBgIoStats(final boolean reportBgIoStats) { - setReportBgIoStats(nativeHandle_, reportBgIoStats); - return this; - } - - @Override - public boolean reportBgIoStats() { - return reportBgIoStats(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setTtl(final long ttl) { - setTtl(nativeHandle_, ttl); - return this; - } - - @Override - public long ttl() { - return ttl(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setPeriodicCompactionSeconds(final long periodicCompactionSeconds) { - setPeriodicCompactionSeconds(nativeHandle_, periodicCompactionSeconds); - return this; - } - - @Override - public long periodicCompactionSeconds() { - return periodicCompactionSeconds(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setCompactionOptionsUniversal( - final CompactionOptionsUniversal compactionOptionsUniversal) { - setCompactionOptionsUniversal(nativeHandle_, - compactionOptionsUniversal.nativeHandle_); - this.compactionOptionsUniversal_ = compactionOptionsUniversal; - return this; - } - - @Override - public CompactionOptionsUniversal compactionOptionsUniversal() { - return this.compactionOptionsUniversal_; - } - - @Override - public ColumnFamilyOptions setCompactionOptionsFIFO(final CompactionOptionsFIFO compactionOptionsFIFO) { - setCompactionOptionsFIFO(nativeHandle_, - compactionOptionsFIFO.nativeHandle_); - this.compactionOptionsFIFO_ = compactionOptionsFIFO; - return this; - } - - @Override - public CompactionOptionsFIFO compactionOptionsFIFO() { - return this.compactionOptionsFIFO_; - } - - @Override - public ColumnFamilyOptions setForceConsistencyChecks(final boolean forceConsistencyChecks) { - setForceConsistencyChecks(nativeHandle_, forceConsistencyChecks); - return this; - } - - @Override - public boolean forceConsistencyChecks() { - return forceConsistencyChecks(nativeHandle_); - } - - @Override - public ColumnFamilyOptions setSstPartitionerFactory( - final SstPartitionerFactory sstPartitionerFactory) { - setSstPartitionerFactory(nativeHandle_, sstPartitionerFactory.nativeHandle_); - this.sstPartitionerFactory_ = sstPartitionerFactory; - return this; - } - - @Override - public ColumnFamilyOptions setCompactionThreadLimiter( - final ConcurrentTaskLimiter compactionThreadLimiter) { - setCompactionThreadLimiter(nativeHandle_, compactionThreadLimiter.nativeHandle_); - this.compactionThreadLimiter_ = compactionThreadLimiter; - return this; - } - - @Override - public ConcurrentTaskLimiter compactionThreadLimiter() { - assert (isOwningHandle()); - return this.compactionThreadLimiter_; - } - - @Override - public SstPartitionerFactory sstPartitionerFactory() { - return sstPartitionerFactory_; - } - - // - // BEGIN options for blobs (integrated BlobDB) - // - - /** - * When set, large values (blobs) are written to separate blob files, and only - * pointers to them are stored in SST files. This can reduce write amplification - * for large-value use cases at the cost of introducing a level of indirection - * for reads. See also the options min_blob_size, blob_file_size, - * blob_compression_type, enable_blob_garbage_collection, and - * blob_garbage_collection_age_cutoff below. - *

    - * Default: false - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param enableBlobFiles true iff blob files should be enabled - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setEnableBlobFiles(final boolean enableBlobFiles) { - setEnableBlobFiles(nativeHandle_, enableBlobFiles); - return this; - } - - /** - * When set, large values (blobs) are written to separate blob files, and only - * pointers to them are stored in SST files. This can reduce write amplification - * for large-value use cases at the cost of introducing a level of indirection - * for reads. See also the options min_blob_size, blob_file_size, - * blob_compression_type, enable_blob_garbage_collection, and - * blob_garbage_collection_age_cutoff below. - *

    - * Default: false - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @return true iff blob files are currently enabled - */ - public boolean enableBlobFiles() { - return enableBlobFiles(nativeHandle_); - } - - /** - * Set the size of the smallest value to be stored separately in a blob file. Values - * which have an uncompressed size smaller than this threshold are stored - * alongside the keys in SST files in the usual fashion. A value of zero for - * this option means that all values are stored in blob files. Note that - * enable_blob_files has to be set in order for this option to have any effect. - *

    - * Default: 0 - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param minBlobSize the size of the smallest value to be stored separately in a blob file - * @return these options, updated with the supplied minimum blob size value - */ - @Override - public ColumnFamilyOptions setMinBlobSize(final long minBlobSize) { - setMinBlobSize(nativeHandle_, minBlobSize); - return this; - } - - /** - * Get the size of the smallest value to be stored separately in a blob file. Values - * which have an uncompressed size smaller than this threshold are stored - * alongside the keys in SST files in the usual fashion. A value of zero for - * this option means that all values are stored in blob files. Note that - * enable_blob_files has to be set in order for this option to have any effect. - *

    - * Default: 0 - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @return the current minimum blob size - */ - @Override - public long minBlobSize() { - return minBlobSize(nativeHandle_); - } - - /** - * Set the size limit for blob files. When writing blob files, a new file is opened - * once this limit is reached. Note that enable_blob_files has to be set in - * order for this option to have any effect. - *

    - * Default: 256 MB - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param blobFileSize the new size limit for blob files - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setBlobFileSize(final long blobFileSize) { - setBlobFileSize(nativeHandle_, blobFileSize); - return this; - } - - /** - * Get the size limit for blob files. When writing blob files, a new file is opened - * once this limit is reached. Note that enable_blob_files has to be set in - * order for this option to have any effect. - *

    - * Default: 256 MB - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @return the size limit for blob files - */ - @Override - public long blobFileSize() { - return blobFileSize(nativeHandle_); - } - - /** - * Set the compression algorithm to use for large values stored in blob files. Note - * that enable_blob_files has to be set in order for this option to have any - * effect. - *

    - * Default: no compression - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param compressionType the compression algorithm to use - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setBlobCompressionType(final CompressionType compressionType) { - setBlobCompressionType(nativeHandle_, compressionType.getValue()); - return this; - } - - /** - * Get the compression algorithm to use for large values stored in blob files. Note - * that enable_blob_files has to be set in order for this option to have any - * effect. - *

    - * Default: no compression - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @return the compression algorithm currently in use for blobs - */ - @Override - public CompressionType blobCompressionType() { - return CompressionType.values()[blobCompressionType(nativeHandle_)]; - } - - /** - * Enable/disable garbage collection of blobs. Blob GC is performed as part of - * compaction. Valid blobs residing in blob files older than a cutoff get - * relocated to new files as they are encountered during compaction, which makes - * it possible to clean up blob files once they contain nothing but - * obsolete/garbage blobs. See also blob_garbage_collection_age_cutoff below. - *

    - * Default: false - * - * @param enableBlobGarbageCollection true iff blob garbage collection is to be enabled - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setEnableBlobGarbageCollection( - final boolean enableBlobGarbageCollection) { - setEnableBlobGarbageCollection(nativeHandle_, enableBlobGarbageCollection); - return this; - } - - /** - * Get enabled/disables state for garbage collection of blobs. Blob GC is performed as part of - * compaction. Valid blobs residing in blob files older than a cutoff get - * relocated to new files as they are encountered during compaction, which makes - * it possible to clean up blob files once they contain nothing but - * obsolete/garbage blobs. See also blob_garbage_collection_age_cutoff below. - *

    - * Default: false - * - * @return true iff blob garbage collection is currently enabled - */ - @Override - public boolean enableBlobGarbageCollection() { - return enableBlobGarbageCollection(nativeHandle_); - } - - /** - * Set the cutoff in terms of blob file age for garbage collection. Blobs in the - * oldest N blob files will be relocated when encountered during compaction, - * where N = garbage_collection_cutoff * number_of_blob_files. Note that - * enable_blob_garbage_collection has to be set in order for this option to have - * any effect. - *

    - * Default: 0.25 - * - * @param blobGarbageCollectionAgeCutoff the new blob garbage collection age cutoff - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setBlobGarbageCollectionAgeCutoff( - final double blobGarbageCollectionAgeCutoff) { - setBlobGarbageCollectionAgeCutoff(nativeHandle_, blobGarbageCollectionAgeCutoff); - return this; - } - - /** - * Get the cutoff in terms of blob file age for garbage collection. Blobs in the - * oldest N blob files will be relocated when encountered during compaction, - * where N = garbage_collection_cutoff * number_of_blob_files. Note that - * enable_blob_garbage_collection has to be set in order for this option to have - * any effect. - *

    - * Default: 0.25 - * - * @return the current blob garbage collection age cutoff - */ - @Override - public double blobGarbageCollectionAgeCutoff() { - return blobGarbageCollectionAgeCutoff(nativeHandle_); - } - - /** - * If the ratio of garbage in the oldest blob files exceeds this threshold, - * targeted compactions are scheduled in order to force garbage collecting - * the blob files in question, assuming they are all eligible based on the - * value of {@link #blobGarbageCollectionAgeCutoff} above. This option is - * currently only supported with leveled compactions. - *

    - * Note that {@link #enableBlobGarbageCollection} has to be set in order for this - * option to have any effect. - *

    - * Default: 1.0 - *

    - * Dynamically changeable through the SetOptions() API - * - * @param blobGarbageCollectionForceThreshold new value for the threshold - * @return the reference to the current options - */ - @Override - public ColumnFamilyOptions setBlobGarbageCollectionForceThreshold( - final double blobGarbageCollectionForceThreshold) { - setBlobGarbageCollectionForceThreshold(nativeHandle_, blobGarbageCollectionForceThreshold); - return this; - } - - /** - * Get the current value for the {@link #blobGarbageCollectionForceThreshold} - * @return the current threshold at which garbage collection of blobs is forced - */ - @Override - public double blobGarbageCollectionForceThreshold() { - return blobGarbageCollectionForceThreshold(nativeHandle_); - } - - /** - * Set compaction readahead for blob files. - *

    - * Default: 0 - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param blobCompactionReadaheadSize the compaction readahead for blob files - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setBlobCompactionReadaheadSize( - final long blobCompactionReadaheadSize) { - setBlobCompactionReadaheadSize(nativeHandle_, blobCompactionReadaheadSize); - return this; - } - - /** - * Get compaction readahead for blob files. - * - * @return the current compaction readahead for blob files - */ - @Override - public long blobCompactionReadaheadSize() { - return blobCompactionReadaheadSize(nativeHandle_); - } - - /** - * Set a certain LSM tree level to enable blob files. - *

    - * Default: 0 - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param blobFileStartingLevel the starting level to enable blob files - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setBlobFileStartingLevel(final int blobFileStartingLevel) { - setBlobFileStartingLevel(nativeHandle_, blobFileStartingLevel); - return this; - } - - /** - * Get the starting LSM tree level to enable blob files. - *

    - * Default: 0 - * - * @return the current LSM tree level to enable blob files. - */ - @Override - public int blobFileStartingLevel() { - return blobFileStartingLevel(nativeHandle_); - } - - /** - * Set a certain prepopulate blob cache option. - *

    - * Default: 0 - *

    - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. - * - * @param prepopulateBlobCache the prepopulate blob cache option - * - * @return the reference to the current options. - */ - @Override - public ColumnFamilyOptions setPrepopulateBlobCache( - final PrepopulateBlobCache prepopulateBlobCache) { - setPrepopulateBlobCache(nativeHandle_, prepopulateBlobCache.getValue()); - return this; - } - - /** - * Get the prepopulate blob cache option. - *

    - * Default: 0 - * - * @return the current prepopulate blob cache option. - */ - @Override - public PrepopulateBlobCache prepopulateBlobCache() { - return PrepopulateBlobCache.getPrepopulateBlobCache(prepopulateBlobCache(nativeHandle_)); - } - - // - // END options for blobs (integrated BlobDB) - // - - private static native long getColumnFamilyOptionsFromProps( - final long cfgHandle, String optString); - private static native long getColumnFamilyOptionsFromProps(final String optString); - - private static native long newColumnFamilyOptions(); - private static native long copyColumnFamilyOptions(final long handle); - private static native long newColumnFamilyOptionsFromOptions( - final long optionsHandle); - @Override protected final native void disposeInternal(final long handle); - - private static native void oldDefaults( - final long handle, final int majorVersion, final int minorVersion); - private native void optimizeForSmallDb(final long handle); - private static native void optimizeForSmallDb(final long handle, final long cacheHandle); - private native void optimizeForPointLookup(long handle, - long blockCacheSizeMb); - private native void optimizeLevelStyleCompaction(long handle, - long memtableMemoryBudget); - private native void optimizeUniversalStyleCompaction(long handle, - long memtableMemoryBudget); - private native void setComparatorHandle(long handle, int builtinComparator); - private native void setComparatorHandle(long optHandle, - long comparatorHandle, byte comparatorType); - private native void setMergeOperatorName(long handle, String name); - private native void setMergeOperator(long handle, long mergeOperatorHandle); - private native void setCompactionFilterHandle(long handle, - long compactionFilterHandle); - private native void setCompactionFilterFactoryHandle(long handle, - long compactionFilterFactoryHandle); - private native void setWriteBufferSize(long handle, long writeBufferSize) - throws IllegalArgumentException; - private native long writeBufferSize(long handle); - private native void setMaxWriteBufferNumber( - long handle, int maxWriteBufferNumber); - private native int maxWriteBufferNumber(long handle); - private native void setMinWriteBufferNumberToMerge( - long handle, int minWriteBufferNumberToMerge); - private native int minWriteBufferNumberToMerge(long handle); - private native void setCompressionType(long handle, byte compressionType); - private native byte compressionType(long handle); - private native void setCompressionPerLevel(long handle, - byte[] compressionLevels); - private native byte[] compressionPerLevel(long handle); - private native void setBottommostCompressionType(long handle, - byte bottommostCompressionType); - private native byte bottommostCompressionType(long handle); - private native void setBottommostCompressionOptions(final long handle, - final long bottommostCompressionOptionsHandle); - private native void setCompressionOptions(long handle, - long compressionOptionsHandle); - private native void useFixedLengthPrefixExtractor( - long handle, int prefixLength); - private native void useCappedPrefixExtractor( - long handle, int prefixLength); - private native void setNumLevels( - long handle, int numLevels); - private native int numLevels(long handle); - private native void setLevelZeroFileNumCompactionTrigger( - long handle, int numFiles); - private native int levelZeroFileNumCompactionTrigger(long handle); - private native void setLevelZeroSlowdownWritesTrigger( - long handle, int numFiles); - private native int levelZeroSlowdownWritesTrigger(long handle); - private native void setLevelZeroStopWritesTrigger( - long handle, int numFiles); - private native int levelZeroStopWritesTrigger(long handle); - private native void setTargetFileSizeBase( - long handle, long targetFileSizeBase); - private native long targetFileSizeBase(long handle); - private native void setTargetFileSizeMultiplier( - long handle, int multiplier); - private native int targetFileSizeMultiplier(long handle); - private native void setMaxBytesForLevelBase( - long handle, long maxBytesForLevelBase); - private native long maxBytesForLevelBase(long handle); - private native void setLevelCompactionDynamicLevelBytes( - long handle, boolean enableLevelCompactionDynamicLevelBytes); - private native boolean levelCompactionDynamicLevelBytes( - long handle); - private native void setMaxBytesForLevelMultiplier(long handle, double multiplier); - private native double maxBytesForLevelMultiplier(long handle); - private native void setMaxCompactionBytes(long handle, long maxCompactionBytes); - private native long maxCompactionBytes(long handle); - private native void setArenaBlockSize( - long handle, long arenaBlockSize) - throws IllegalArgumentException; - private native long arenaBlockSize(long handle); - private native void setDisableAutoCompactions( - long handle, boolean disableAutoCompactions); - private native boolean disableAutoCompactions(long handle); - private native void setCompactionStyle(long handle, byte compactionStyle); - private native byte compactionStyle(long handle); - private native void setMaxTableFilesSizeFIFO( - long handle, long max_table_files_size); - private native long maxTableFilesSizeFIFO(long handle); - private native void setMaxSequentialSkipInIterations( - long handle, long maxSequentialSkipInIterations); - private native long maxSequentialSkipInIterations(long handle); - private native void setMemTableFactory(long handle, long factoryHandle); - private native String memTableFactoryName(long handle); - private native void setTableFactory(long handle, long factoryHandle); - private native String tableFactoryName(long handle); - private static native void setCfPaths( - final long handle, final String[] paths, final long[] targetSizes); - private static native long cfPathsLen(final long handle); - private static native void cfPaths( - final long handle, final String[] paths, final long[] targetSizes); - private native void setInplaceUpdateSupport( - long handle, boolean inplaceUpdateSupport); - private native boolean inplaceUpdateSupport(long handle); - private native void setInplaceUpdateNumLocks( - long handle, long inplaceUpdateNumLocks) - throws IllegalArgumentException; - private native long inplaceUpdateNumLocks(long handle); - private native void setMemtablePrefixBloomSizeRatio( - long handle, double memtablePrefixBloomSizeRatio); - private native double memtablePrefixBloomSizeRatio(long handle); - private native void setExperimentalMempurgeThreshold( - long handle, double experimentalMempurgeThreshold); - private native double experimentalMempurgeThreshold(long handle); - private native void setMemtableWholeKeyFiltering(long handle, boolean memtableWholeKeyFiltering); - private native boolean memtableWholeKeyFiltering(long handle); - private native void setBloomLocality( - long handle, int bloomLocality); - private native int bloomLocality(long handle); - private native void setMaxSuccessiveMerges( - long handle, long maxSuccessiveMerges) - throws IllegalArgumentException; - private native long maxSuccessiveMerges(long handle); - private native void setOptimizeFiltersForHits(long handle, - boolean optimizeFiltersForHits); - private native boolean optimizeFiltersForHits(long handle); - private native void setMemtableHugePageSize(long handle, - long memtableHugePageSize); - private native long memtableHugePageSize(long handle); - private native void setSoftPendingCompactionBytesLimit(long handle, - long softPendingCompactionBytesLimit); - private native long softPendingCompactionBytesLimit(long handle); - private native void setHardPendingCompactionBytesLimit(long handle, - long hardPendingCompactionBytesLimit); - private native long hardPendingCompactionBytesLimit(long handle); - private native void setLevel0FileNumCompactionTrigger(long handle, - int level0FileNumCompactionTrigger); - private native int level0FileNumCompactionTrigger(long handle); - private native void setLevel0SlowdownWritesTrigger(long handle, - int level0SlowdownWritesTrigger); - private native int level0SlowdownWritesTrigger(long handle); - private native void setLevel0StopWritesTrigger(long handle, - int level0StopWritesTrigger); - private native int level0StopWritesTrigger(long handle); - private native void setMaxBytesForLevelMultiplierAdditional(long handle, - int[] maxBytesForLevelMultiplierAdditional); - private native int[] maxBytesForLevelMultiplierAdditional(long handle); - private native void setParanoidFileChecks(long handle, - boolean paranoidFileChecks); - private native boolean paranoidFileChecks(long handle); - private native void setMaxWriteBufferNumberToMaintain(final long handle, - final int maxWriteBufferNumberToMaintain); - private native int maxWriteBufferNumberToMaintain(final long handle); - private native void setCompactionPriority(final long handle, - final byte compactionPriority); - private native byte compactionPriority(final long handle); - private native void setReportBgIoStats(final long handle, - final boolean reportBgIoStats); - private native boolean reportBgIoStats(final long handle); - private native void setTtl(final long handle, final long ttl); - private native long ttl(final long handle); - private native void setPeriodicCompactionSeconds( - final long handle, final long periodicCompactionSeconds); - private native long periodicCompactionSeconds(final long handle); - private native void setCompactionOptionsUniversal(final long handle, - final long compactionOptionsUniversalHandle); - private native void setCompactionOptionsFIFO(final long handle, - final long compactionOptionsFIFOHandle); - private native void setForceConsistencyChecks(final long handle, - final boolean forceConsistencyChecks); - private native boolean forceConsistencyChecks(final long handle); - private native void setSstPartitionerFactory(long nativeHandle_, long newFactoryHandle); - private static native void setCompactionThreadLimiter( - final long nativeHandle_, final long compactionThreadLimiterHandle); - - private native void setEnableBlobFiles(final long nativeHandle_, final boolean enableBlobFiles); - private native boolean enableBlobFiles(final long nativeHandle_); - private native void setMinBlobSize(final long nativeHandle_, final long minBlobSize); - private native long minBlobSize(final long nativeHandle_); - private native void setBlobFileSize(final long nativeHandle_, final long blobFileSize); - private native long blobFileSize(final long nativeHandle_); - private native void setBlobCompressionType(final long nativeHandle_, final byte compressionType); - private native byte blobCompressionType(final long nativeHandle_); - private native void setEnableBlobGarbageCollection( - final long nativeHandle_, final boolean enableBlobGarbageCollection); - private native boolean enableBlobGarbageCollection(final long nativeHandle_); - private native void setBlobGarbageCollectionAgeCutoff( - final long nativeHandle_, final double blobGarbageCollectionAgeCutoff); - private native double blobGarbageCollectionAgeCutoff(final long nativeHandle_); - private native void setBlobGarbageCollectionForceThreshold( - final long nativeHandle_, final double blobGarbageCollectionForceThreshold); - private native double blobGarbageCollectionForceThreshold(final long nativeHandle_); - private native void setBlobCompactionReadaheadSize( - final long nativeHandle_, final long blobCompactionReadaheadSize); - private native long blobCompactionReadaheadSize(final long nativeHandle_); - private native void setBlobFileStartingLevel( - final long nativeHandle_, final int blobFileStartingLevel); - private native int blobFileStartingLevel(final long nativeHandle_); - private native void setPrepopulateBlobCache( - final long nativeHandle_, final byte prepopulateBlobCache); - private native byte prepopulateBlobCache(final long nativeHandle_); - - // instance variables - // NOTE: If you add new member variables, please update the copy constructor above! - private MemTableConfig memTableConfig_; - private TableFormatConfig tableFormatConfig_; - private AbstractComparator comparator_; - private AbstractCompactionFilter> compactionFilter_; - private AbstractCompactionFilterFactory> - compactionFilterFactory_; - private CompactionOptionsUniversal compactionOptionsUniversal_; - private CompactionOptionsFIFO compactionOptionsFIFO_; - private CompressionOptions bottommostCompressionOptions_; - private CompressionOptions compressionOptions_; - private SstPartitionerFactory sstPartitionerFactory_; - private ConcurrentTaskLimiter compactionThreadLimiter_; -} diff --git a/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java b/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java deleted file mode 100644 index 97357aacf..000000000 --- a/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java +++ /dev/null @@ -1,536 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Collection; -import java.util.List; - -public interface ColumnFamilyOptionsInterface> - extends AdvancedColumnFamilyOptionsInterface { - /** - * The function recovers options to a previous version. Only 4.6 or later - * versions are supported. - * - * @param majorVersion The major version to recover default values of options - * @param minorVersion The minor version to recover default values of options - * @return the instance of the current object. - */ - T oldDefaults(int majorVersion, int minorVersion); - - /** - * Use this if your DB is very small (like under 1GB) and you don't want to - * spend lots of memory for memtables. - * - * @return the instance of the current object. - */ - T optimizeForSmallDb(); - - /** - * Some functions that make it easier to optimize RocksDB - * Use this if your DB is very small (like under 1GB) and you don't want to - * spend lots of memory for memtables. - * - * @param cache An optional cache object is passed in to be used as the block cache - * @return the instance of the current object. - */ - T optimizeForSmallDb(Cache cache); - - /** - * Use this if you don't need to keep the data sorted, i.e. you'll never use - * an iterator, only Put() and Get() API calls - * - * @param blockCacheSizeMb Block cache size in MB - * @return the instance of the current object. - */ - T optimizeForPointLookup(long blockCacheSizeMb); - - /** - *

    Default values for some parameters in ColumnFamilyOptions are not - * optimized for heavy workloads and big datasets, which means you might - * observe write stalls under some conditions. As a starting point for tuning - * RocksDB options, use the following for level style compaction.

    - * - *

    Make sure to also call IncreaseParallelism(), which will provide the - * biggest performance gains.

    - *

    Note: we might use more memory than memtable_memory_budget during high - * write rate period

    - * - * @return the instance of the current object. - */ - T optimizeLevelStyleCompaction(); - - /** - *

    Default values for some parameters in ColumnFamilyOptions are not - * optimized for heavy workloads and big datasets, which means you might - * observe write stalls under some conditions. As a starting point for tuning - * RocksDB options, use the following for level style compaction.

    - * - *

    Make sure to also call IncreaseParallelism(), which will provide the - * biggest performance gains.

    - *

    Note: we might use more memory than memtable_memory_budget during high - * write rate period

    - * - * @param memtableMemoryBudget memory budget in bytes - * @return the instance of the current object. - */ - T optimizeLevelStyleCompaction( - long memtableMemoryBudget); - - /** - *

    Default values for some parameters in ColumnFamilyOptions are not - * optimized for heavy workloads and big datasets, which means you might - * observe write stalls under some conditions. As a starting point for tuning - * RocksDB options, use the following for universal style compaction.

    - * - *

    Universal style compaction is focused on reducing Write Amplification - * Factor for big data sets, but increases Space Amplification.

    - * - *

    Make sure to also call IncreaseParallelism(), which will provide the - * biggest performance gains.

    - * - *

    Note: we might use more memory than memtable_memory_budget during high - * write rate period

    - * - * @return the instance of the current object. - */ - T optimizeUniversalStyleCompaction(); - - /** - *

    Default values for some parameters in ColumnFamilyOptions are not - * optimized for heavy workloads and big datasets, which means you might - * observe write stalls under some conditions. As a starting point for tuning - * RocksDB options, use the following for universal style compaction.

    - * - *

    Universal style compaction is focused on reducing Write Amplification - * Factor for big data sets, but increases Space Amplification.

    - * - *

    Make sure to also call IncreaseParallelism(), which will provide the - * biggest performance gains.

    - * - *

    Note: we might use more memory than memtable_memory_budget during high - * write rate period

    - * - * @param memtableMemoryBudget memory budget in bytes - * @return the instance of the current object. - */ - T optimizeUniversalStyleCompaction( - long memtableMemoryBudget); - - /** - * Set {@link BuiltinComparator} to be used with RocksDB. - * - * Note: Comparator can be set once upon database creation. - * - * Default: BytewiseComparator. - * @param builtinComparator a {@link BuiltinComparator} type. - * @return the instance of the current object. - */ - T setComparator( - BuiltinComparator builtinComparator); - - /** - * Use the specified comparator for key ordering. - * - * Comparator should not be disposed before options instances using this comparator is - * disposed. If dispose() function is not called, then comparator object will be - * GC'd automatically. - * - * Comparator instance can be re-used in multiple options instances. - * - * @param comparator java instance. - * @return the instance of the current object. - */ - T setComparator( - AbstractComparator comparator); - - /** - *

    Set the merge operator to be used for merging two merge operands - * of the same key. The merge function is invoked during - * compaction and at lookup time, if multiple key/value pairs belonging - * to the same key are found in the database.

    - * - * @param name the name of the merge function, as defined by - * the MergeOperators factory (see utilities/MergeOperators.h) - * The merge function is specified by name and must be one of the - * standard merge operators provided by RocksDB. The available - * operators are "put", "uint64add", "stringappend" and "stringappendtest". - * @return the instance of the current object. - */ - T setMergeOperatorName(String name); - - /** - *

    Set the merge operator to be used for merging two different key/value - * pairs that share the same key. The merge function is invoked during - * compaction and at lookup time, if multiple key/value pairs belonging - * to the same key are found in the database.

    - * - * @param mergeOperator {@link MergeOperator} instance. - * @return the instance of the current object. - */ - T setMergeOperator(MergeOperator mergeOperator); - - /** - * A single CompactionFilter instance to call into during compaction. - * Allows an application to modify/delete a key-value during background - * compaction. - * - * If the client requires a new compaction filter to be used for different - * compaction runs, it can specify call - * {@link #setCompactionFilterFactory(AbstractCompactionFilterFactory)} - * instead. - * - * The client should specify only set one of the two. - * {@link #setCompactionFilter(AbstractCompactionFilter)} takes precedence - * over {@link #setCompactionFilterFactory(AbstractCompactionFilterFactory)} - * if the client specifies both. - * - * If multithreaded compaction is being used, the supplied CompactionFilter - * instance may be used from different threads concurrently and so should be thread-safe. - * - * @param compactionFilter {@link AbstractCompactionFilter} instance. - * @return the instance of the current object. - */ - T setCompactionFilter( - final AbstractCompactionFilter> compactionFilter); - - /** - * Accessor for the CompactionFilter instance in use. - * - * @return Reference to the CompactionFilter, or null if one hasn't been set. - */ - AbstractCompactionFilter> compactionFilter(); - - /** - * This is a factory that provides {@link AbstractCompactionFilter} objects - * which allow an application to modify/delete a key-value during background - * compaction. - * - * A new filter will be created on each compaction run. If multithreaded - * compaction is being used, each created CompactionFilter will only be used - * from a single thread and so does not need to be thread-safe. - * - * @param compactionFilterFactory {@link AbstractCompactionFilterFactory} instance. - * @return the instance of the current object. - */ - T setCompactionFilterFactory( - final AbstractCompactionFilterFactory> - compactionFilterFactory); - - /** - * Accessor for the CompactionFilterFactory instance in use. - * - * @return Reference to the CompactionFilterFactory, or null if one hasn't been set. - */ - AbstractCompactionFilterFactory> compactionFilterFactory(); - - /** - * This prefix-extractor uses the first n bytes of a key as its prefix. - * - * In some hash-based memtable representation such as HashLinkedList - * and HashSkipList, prefixes are used to partition the keys into - * several buckets. Prefix extractor is used to specify how to - * extract the prefix given a key. - * - * @param n use the first n bytes of a key as its prefix. - * @return the reference to the current option. - */ - T useFixedLengthPrefixExtractor(int n); - - /** - * Same as fixed length prefix extractor, except that when slice is - * shorter than the fixed length, it will use the full key. - * - * @param n use the first n bytes of a key as its prefix. - * @return the reference to the current option. - */ - T useCappedPrefixExtractor(int n); - - /** - * Number of files to trigger level-0 compaction. A value < 0 means that - * level-0 compaction will not be triggered by number of files at all. - * Default: 4 - * - * @param numFiles the number of files in level-0 to trigger compaction. - * @return the reference to the current option. - */ - T setLevelZeroFileNumCompactionTrigger( - int numFiles); - - /** - * The number of files in level 0 to trigger compaction from level-0 to - * level-1. A value < 0 means that level-0 compaction will not be - * triggered by number of files at all. - * Default: 4 - * - * @return the number of files in level 0 to trigger compaction. - */ - int levelZeroFileNumCompactionTrigger(); - - /** - * Soft limit on number of level-0 files. We start slowing down writes at this - * point. A value < 0 means that no writing slow down will be triggered by - * number of files in level-0. - * - * @param numFiles soft limit on number of level-0 files. - * @return the reference to the current option. - */ - T setLevelZeroSlowdownWritesTrigger( - int numFiles); - - /** - * Soft limit on the number of level-0 files. We start slowing down writes - * at this point. A value < 0 means that no writing slow down will be - * triggered by number of files in level-0. - * - * @return the soft limit on the number of level-0 files. - */ - int levelZeroSlowdownWritesTrigger(); - - /** - * Maximum number of level-0 files. We stop writes at this point. - * - * @param numFiles the hard limit of the number of level-0 files. - * @return the reference to the current option. - */ - T setLevelZeroStopWritesTrigger(int numFiles); - - /** - * Maximum number of level-0 files. We stop writes at this point. - * - * @return the hard limit of the number of level-0 file. - */ - int levelZeroStopWritesTrigger(); - - /** - * The ratio between the total size of level-(L+1) files and the total - * size of level-L files for all L. - * DEFAULT: 10 - * - * @param multiplier the ratio between the total size of level-(L+1) - * files and the total size of level-L files for all L. - * @return the reference to the current option. - */ - T setMaxBytesForLevelMultiplier( - double multiplier); - - /** - * The ratio between the total size of level-(L+1) files and the total - * size of level-L files for all L. - * DEFAULT: 10 - * - * @return the ratio between the total size of level-(L+1) files and - * the total size of level-L files for all L. - */ - double maxBytesForLevelMultiplier(); - - /** - * FIFO compaction option. - * The oldest table file will be deleted - * once the sum of table files reaches this size. - * The default value is 1GB (1 * 1024 * 1024 * 1024). - * - * @param maxTableFilesSize the size limit of the total sum of table files. - * @return the instance of the current object. - */ - T setMaxTableFilesSizeFIFO( - long maxTableFilesSize); - - /** - * FIFO compaction option. - * The oldest table file will be deleted - * once the sum of table files reaches this size. - * The default value is 1GB (1 * 1024 * 1024 * 1024). - * - * @return the size limit of the total sum of table files. - */ - long maxTableFilesSizeFIFO(); - - /** - * Get the config for mem-table. - * - * @return the mem-table config. - */ - MemTableConfig memTableConfig(); - - /** - * Set the config for mem-table. - * - * @param memTableConfig the mem-table config. - * @return the instance of the current object. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setMemTableConfig(MemTableConfig memTableConfig); - - /** - * Returns the name of the current mem table representation. - * Memtable format can be set using setTableFormatConfig. - * - * @return the name of the currently-used memtable factory. - * @see #setTableFormatConfig(org.rocksdb.TableFormatConfig) - */ - String memTableFactoryName(); - - /** - * Get the config for table format. - * - * @return the table format config. - */ - TableFormatConfig tableFormatConfig(); - - /** - * Set the config for table format. - * - * @param config the table format config. - * @return the reference of the current options. - */ - T setTableFormatConfig(TableFormatConfig config); - - /** - * @return the name of the currently used table factory. - */ - String tableFactoryName(); - - /** - * A list of paths where SST files for this column family - * can be put into, with its target size. Similar to db_paths, - * newer data is placed into paths specified earlier in the - * vector while older data gradually moves to paths specified - * later in the vector. - * Note that, if a path is supplied to multiple column - * families, it would have files and total size from all - * the column families combined. User should provision for the - * total size(from all the column families) in such cases. - * - * If left empty, db_paths will be used. - * Default: empty - * - * @param paths collection of paths for SST files. - * @return the reference of the current options. - */ - T setCfPaths(final Collection paths); - - /** - * @return collection of paths for SST files. - */ - List cfPaths(); - - /** - * Compression algorithm that will be used for the bottommost level that - * contain files. If level-compaction is used, this option will only affect - * levels after base level. - * - * Default: {@link CompressionType#DISABLE_COMPRESSION_OPTION} - * - * @param bottommostCompressionType The compression type to use for the - * bottommost level - * - * @return the reference of the current options. - */ - T setBottommostCompressionType( - final CompressionType bottommostCompressionType); - - /** - * Compression algorithm that will be used for the bottommost level that - * contain files. If level-compaction is used, this option will only affect - * levels after base level. - * - * Default: {@link CompressionType#DISABLE_COMPRESSION_OPTION} - * - * @return The compression type used for the bottommost level - */ - CompressionType bottommostCompressionType(); - - /** - * Set the options for compression algorithms used by - * {@link #bottommostCompressionType()} if it is enabled. - * - * To enable it, please see the definition of - * {@link CompressionOptions}. - * - * @param compressionOptions the bottom most compression options. - * - * @return the reference of the current options. - */ - T setBottommostCompressionOptions( - final CompressionOptions compressionOptions); - - /** - * Get the bottom most compression options. - * - * See {@link #setBottommostCompressionOptions(CompressionOptions)}. - * - * @return the bottom most compression options. - */ - CompressionOptions bottommostCompressionOptions(); - - /** - * Set the different options for compression algorithms - * - * @param compressionOptions The compression options - * - * @return the reference of the current options. - */ - T setCompressionOptions( - CompressionOptions compressionOptions); - - /** - * Get the different options for compression algorithms - * - * @return The compression options - */ - CompressionOptions compressionOptions(); - - /** - * If non-nullptr, use the specified factory for a function to determine the - * partitioning of sst files. This helps compaction to split the files - * on interesting boundaries (key prefixes) to make propagation of sst - * files less write amplifying (covering the whole key space). - * - * Default: nullptr - * - * @param factory The factory reference - * @return the reference of the current options. - */ - @Experimental("Caution: this option is experimental") - T setSstPartitionerFactory(SstPartitionerFactory factory); - - /** - * Get SST partitioner factory - * - * @return SST partitioner factory - */ - @Experimental("Caution: this option is experimental") - SstPartitionerFactory sstPartitionerFactory(); - - /** - * Compaction concurrent thread limiter for the column family. - * If non-nullptr, use given concurrent thread limiter to control - * the max outstanding compaction tasks. Limiter can be shared with - * multiple column families across db instances. - * - * @param concurrentTaskLimiter The compaction thread limiter. - * @return the reference of the current options. - */ - T setCompactionThreadLimiter(ConcurrentTaskLimiter concurrentTaskLimiter); - - /** - * Get compaction thread limiter - * - * @return Compaction thread limiter - */ - ConcurrentTaskLimiter compactionThreadLimiter(); - - /** - * Default memtable memory budget used with the following methods: - * - *
      - *
    1. {@link #optimizeLevelStyleCompaction()}
    2. - *
    3. {@link #optimizeUniversalStyleCompaction()}
    4. - *
    - */ - long DEFAULT_COMPACTION_MEMTABLE_MEMORY_BUDGET = 512 * 1024 * 1024; -} diff --git a/java/src/main/java/org/rocksdb/CompactRangeOptions.java b/java/src/main/java/org/rocksdb/CompactRangeOptions.java deleted file mode 100644 index cf5708601..000000000 --- a/java/src/main/java/org/rocksdb/CompactRangeOptions.java +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * CompactRangeOptions is used by CompactRange() call. In the documentation of the methods "the compaction" refers to - * any compaction that is using this CompactRangeOptions. - */ -public class CompactRangeOptions extends RocksObject { - - private final static byte VALUE_kSkip = 0; - private final static byte VALUE_kIfHaveCompactionFilter = 1; - private final static byte VALUE_kForce = 2; - private final static byte VALUE_kForceOptimized = 3; - - // For level based compaction, we can configure if we want to skip/force bottommost level - // compaction. The order of this enum MUST follow the C++ layer. See BottommostLevelCompaction in - // db/options.h - public enum BottommostLevelCompaction { - /** - * Skip bottommost level compaction - */ - kSkip(VALUE_kSkip), - /** - * Only compact bottommost level if there is a compaction filter. This is the default option - */ - kIfHaveCompactionFilter(VALUE_kIfHaveCompactionFilter), - /** - * Always compact bottommost level - */ - kForce(VALUE_kForce), - /** - * Always compact bottommost level but in bottommost level avoid - * double-compacting files created in the same compaction - */ - kForceOptimized(VALUE_kForceOptimized); - - private final byte value; - - BottommostLevelCompaction(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Returns the BottommostLevelCompaction for the given C++ rocks enum value. - * @param bottommostLevelCompaction The value of the BottommostLevelCompaction - * @return BottommostLevelCompaction instance, or null if none matches - */ - public static BottommostLevelCompaction fromRocksId(final int bottommostLevelCompaction) { - switch (bottommostLevelCompaction) { - case VALUE_kSkip: return kSkip; - case VALUE_kIfHaveCompactionFilter: return kIfHaveCompactionFilter; - case VALUE_kForce: return kForce; - case VALUE_kForceOptimized: - return kForceOptimized; - default: return null; - } - } - } - - /** - * Construct CompactRangeOptions. - */ - public CompactRangeOptions() { - super(newCompactRangeOptions()); - } - - /** - * Returns whether the compaction is exclusive or other compactions may run concurrently at the same time. - * - * @return true if exclusive, false if concurrent - */ - public boolean exclusiveManualCompaction() { - return exclusiveManualCompaction(nativeHandle_); - } - - /** - * Sets whether the compaction is exclusive or other compaction are allowed run concurrently at the same time. - * - * @param exclusiveCompaction true if compaction should be exclusive - * @return This CompactRangeOptions - */ - public CompactRangeOptions setExclusiveManualCompaction(final boolean exclusiveCompaction) { - setExclusiveManualCompaction(nativeHandle_, exclusiveCompaction); - return this; - } - - /** - * Returns whether compacted files will be moved to the minimum level capable of holding the data or given level - * (specified non-negative target_level). - * @return true, if compacted files will be moved to the minimum level - */ - public boolean changeLevel() { - return changeLevel(nativeHandle_); - } - - /** - * Whether compacted files will be moved to the minimum level capable of holding the data or given level - * (specified non-negative target_level). - * - * @param changeLevel If true, compacted files will be moved to the minimum level - * @return This CompactRangeOptions - */ - public CompactRangeOptions setChangeLevel(final boolean changeLevel) { - setChangeLevel(nativeHandle_, changeLevel); - return this; - } - - /** - * If change_level is true and target_level have non-negative value, compacted files will be moved to target_level. - * @return The target level for the compacted files - */ - public int targetLevel() { - return targetLevel(nativeHandle_); - } - - - /** - * If change_level is true and target_level have non-negative value, compacted files will be moved to target_level. - * - * @param targetLevel target level for the compacted files - * @return This CompactRangeOptions - */ - public CompactRangeOptions setTargetLevel(final int targetLevel) { - setTargetLevel(nativeHandle_, targetLevel); - return this; - } - - /** - * target_path_id for compaction output. Compaction outputs will be placed in options.db_paths[target_path_id]. - * - * @return target_path_id - */ - public int targetPathId() { - return targetPathId(nativeHandle_); - } - - /** - * Compaction outputs will be placed in options.db_paths[target_path_id]. Behavior is undefined if target_path_id is - * out of range. - * - * @param targetPathId target path id - * @return This CompactRangeOptions - */ - public CompactRangeOptions setTargetPathId(final int targetPathId) { - setTargetPathId(nativeHandle_, targetPathId); - return this; - } - - /** - * Returns the policy for compacting the bottommost level - * @return The BottommostLevelCompaction policy - */ - public BottommostLevelCompaction bottommostLevelCompaction() { - return BottommostLevelCompaction.fromRocksId(bottommostLevelCompaction(nativeHandle_)); - } - - /** - * Sets the policy for compacting the bottommost level - * - * @param bottommostLevelCompaction The policy for compacting the bottommost level - * @return This CompactRangeOptions - */ - public CompactRangeOptions setBottommostLevelCompaction(final BottommostLevelCompaction bottommostLevelCompaction) { - setBottommostLevelCompaction(nativeHandle_, bottommostLevelCompaction.getValue()); - return this; - } - - /** - * If true, compaction will execute immediately even if doing so would cause the DB to - * enter write stall mode. Otherwise, it'll sleep until load is low enough. - * @return true if compaction will execute immediately - */ - public boolean allowWriteStall() { - return allowWriteStall(nativeHandle_); - } - - - /** - * If true, compaction will execute immediately even if doing so would cause the DB to - * enter write stall mode. Otherwise, it'll sleep until load is low enough. - * - * @return This CompactRangeOptions - * @param allowWriteStall true if compaction should execute immediately - */ - public CompactRangeOptions setAllowWriteStall(final boolean allowWriteStall) { - setAllowWriteStall(nativeHandle_, allowWriteStall); - return this; - } - - /** - * If > 0, it will replace the option in the DBOptions for this compaction - * @return number of subcompactions - */ - public int maxSubcompactions() { - return maxSubcompactions(nativeHandle_); - } - - /** - * If > 0, it will replace the option in the DBOptions for this compaction - * - * @param maxSubcompactions number of subcompactions - * @return This CompactRangeOptions - */ - public CompactRangeOptions setMaxSubcompactions(final int maxSubcompactions) { - setMaxSubcompactions(nativeHandle_, maxSubcompactions); - return this; - } - - private native static long newCompactRangeOptions(); - @Override protected final native void disposeInternal(final long handle); - - private native boolean exclusiveManualCompaction(final long handle); - private native void setExclusiveManualCompaction(final long handle, - final boolean exclusive_manual_compaction); - private native boolean changeLevel(final long handle); - private native void setChangeLevel(final long handle, - final boolean changeLevel); - private native int targetLevel(final long handle); - private native void setTargetLevel(final long handle, - final int targetLevel); - private native int targetPathId(final long handle); - private native void setTargetPathId(final long handle, - final int targetPathId); - private native int bottommostLevelCompaction(final long handle); - private native void setBottommostLevelCompaction(final long handle, - final int bottommostLevelCompaction); - private native boolean allowWriteStall(final long handle); - private native void setAllowWriteStall(final long handle, - final boolean allowWriteStall); - private native void setMaxSubcompactions(final long handle, - final int maxSubcompactions); - private native int maxSubcompactions(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CompactionJobInfo.java b/java/src/main/java/org/rocksdb/CompactionJobInfo.java deleted file mode 100644 index 4e3b8d68b..000000000 --- a/java/src/main/java/org/rocksdb/CompactionJobInfo.java +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -public class CompactionJobInfo extends RocksObject { - - public CompactionJobInfo() { - super(newCompactionJobInfo()); - } - - /** - * Private as called from JNI C++ - */ - private CompactionJobInfo(final long nativeHandle) { - super(nativeHandle); - // We do not own the native object! - disOwnNativeHandle(); - } - - /** - * Get the name of the column family where the compaction happened. - * - * @return the name of the column family - */ - public byte[] columnFamilyName() { - return columnFamilyName(nativeHandle_); - } - - /** - * Get the status indicating whether the compaction was successful or not. - * - * @return the status - */ - public Status status() { - return status(nativeHandle_); - } - - /** - * Get the id of the thread that completed this compaction job. - * - * @return the id of the thread - */ - public long threadId() { - return threadId(nativeHandle_); - } - - /** - * Get the job id, which is unique in the same thread. - * - * @return the id of the thread - */ - public int jobId() { - return jobId(nativeHandle_); - } - - /** - * Get the smallest input level of the compaction. - * - * @return the input level - */ - public int baseInputLevel() { - return baseInputLevel(nativeHandle_); - } - - /** - * Get the output level of the compaction. - * - * @return the output level - */ - public int outputLevel() { - return outputLevel(nativeHandle_); - } - - /** - * Get the names of the compaction input files. - * - * @return the names of the input files. - */ - public List inputFiles() { - return Arrays.asList(inputFiles(nativeHandle_)); - } - - /** - * Get the names of the compaction output files. - * - * @return the names of the output files. - */ - public List outputFiles() { - return Arrays.asList(outputFiles(nativeHandle_)); - } - - /** - * Get the table properties for the input and output tables. - * - * The map is keyed by values from {@link #inputFiles()} and - * {@link #outputFiles()}. - * - * @return the table properties - */ - public Map tableProperties() { - return tableProperties(nativeHandle_); - } - - /** - * Get the Reason for running the compaction. - * - * @return the reason. - */ - public CompactionReason compactionReason() { - return CompactionReason.fromValue(compactionReason(nativeHandle_)); - } - - // - /** - * Get the compression algorithm used for output files. - * - * @return the compression algorithm - */ - public CompressionType compression() { - return CompressionType.getCompressionType(compression(nativeHandle_)); - } - - /** - * Get detailed information about this compaction. - * - * @return the detailed information, or null if not available. - */ - public /* @Nullable */ CompactionJobStats stats() { - final long statsHandle = stats(nativeHandle_); - if (statsHandle == 0) { - return null; - } - - return new CompactionJobStats(statsHandle); - } - - - private static native long newCompactionJobInfo(); - @Override protected native void disposeInternal(final long handle); - - private static native byte[] columnFamilyName(final long handle); - private static native Status status(final long handle); - private static native long threadId(final long handle); - private static native int jobId(final long handle); - private static native int baseInputLevel(final long handle); - private static native int outputLevel(final long handle); - private static native String[] inputFiles(final long handle); - private static native String[] outputFiles(final long handle); - private static native Map tableProperties( - final long handle); - private static native byte compactionReason(final long handle); - private static native byte compression(final long handle); - private static native long stats(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CompactionJobStats.java b/java/src/main/java/org/rocksdb/CompactionJobStats.java deleted file mode 100644 index 3d53b5565..000000000 --- a/java/src/main/java/org/rocksdb/CompactionJobStats.java +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class CompactionJobStats extends RocksObject { - - public CompactionJobStats() { - super(newCompactionJobStats()); - } - - /** - * Private as called from JNI C++ - */ - CompactionJobStats(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Reset the stats. - */ - public void reset() { - reset(nativeHandle_); - } - - /** - * Aggregate the CompactionJobStats from another instance with this one. - * - * @param compactionJobStats another instance of stats. - */ - public void add(final CompactionJobStats compactionJobStats) { - add(nativeHandle_, compactionJobStats.nativeHandle_); - } - - /** - * Get the elapsed time in micro of this compaction. - * - * @return the elapsed time in micro of this compaction. - */ - public long elapsedMicros() { - return elapsedMicros(nativeHandle_); - } - - /** - * Get the number of compaction input records. - * - * @return the number of compaction input records. - */ - public long numInputRecords() { - return numInputRecords(nativeHandle_); - } - - /** - * Get the number of compaction input files. - * - * @return the number of compaction input files. - */ - public long numInputFiles() { - return numInputFiles(nativeHandle_); - } - - /** - * Get the number of compaction input files at the output level. - * - * @return the number of compaction input files at the output level. - */ - public long numInputFilesAtOutputLevel() { - return numInputFilesAtOutputLevel(nativeHandle_); - } - - /** - * Get the number of compaction output records. - * - * @return the number of compaction output records. - */ - public long numOutputRecords() { - return numOutputRecords(nativeHandle_); - } - - /** - * Get the number of compaction output files. - * - * @return the number of compaction output files. - */ - public long numOutputFiles() { - return numOutputFiles(nativeHandle_); - } - - /** - * Determine if the compaction is a manual compaction. - * - * @return true if the compaction is a manual compaction, false otherwise. - */ - public boolean isManualCompaction() { - return isManualCompaction(nativeHandle_); - } - - /** - * Get the size of the compaction input in bytes. - * - * @return the size of the compaction input in bytes. - */ - public long totalInputBytes() { - return totalInputBytes(nativeHandle_); - } - - /** - * Get the size of the compaction output in bytes. - * - * @return the size of the compaction output in bytes. - */ - public long totalOutputBytes() { - return totalOutputBytes(nativeHandle_); - } - - /** - * Get the number of records being replaced by newer record associated - * with same key. - * - * This could be a new value or a deletion entry for that key so this field - * sums up all updated and deleted keys. - * - * @return the number of records being replaced by newer record associated - * with same key. - */ - public long numRecordsReplaced() { - return numRecordsReplaced(nativeHandle_); - } - - /** - * Get the sum of the uncompressed input keys in bytes. - * - * @return the sum of the uncompressed input keys in bytes. - */ - public long totalInputRawKeyBytes() { - return totalInputRawKeyBytes(nativeHandle_); - } - - /** - * Get the sum of the uncompressed input values in bytes. - * - * @return the sum of the uncompressed input values in bytes. - */ - public long totalInputRawValueBytes() { - return totalInputRawValueBytes(nativeHandle_); - } - - /** - * Get the number of deletion entries before compaction. - * - * Deletion entries can disappear after compaction because they expired. - * - * @return the number of deletion entries before compaction. - */ - public long numInputDeletionRecords() { - return numInputDeletionRecords(nativeHandle_); - } - - /** - * Get the number of deletion records that were found obsolete and discarded - * because it is not possible to delete any more keys with this entry. - * (i.e. all possible deletions resulting from it have been completed) - * - * @return the number of deletion records that were found obsolete and - * discarded. - */ - public long numExpiredDeletionRecords() { - return numExpiredDeletionRecords(nativeHandle_); - } - - /** - * Get the number of corrupt keys (ParseInternalKey returned false when - * applied to the key) encountered and written out. - * - * @return the number of corrupt keys. - */ - public long numCorruptKeys() { - return numCorruptKeys(nativeHandle_); - } - - /** - * Get the Time spent on file's Append() call. - * - * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. - * - * @return the Time spent on file's Append() call. - */ - public long fileWriteNanos() { - return fileWriteNanos(nativeHandle_); - } - - /** - * Get the Time spent on sync file range. - * - * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. - * - * @return the Time spent on sync file range. - */ - public long fileRangeSyncNanos() { - return fileRangeSyncNanos(nativeHandle_); - } - - /** - * Get the Time spent on file fsync. - * - * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. - * - * @return the Time spent on file fsync. - */ - public long fileFsyncNanos() { - return fileFsyncNanos(nativeHandle_); - } - - /** - * Get the Time spent on preparing file write (falocate, etc) - * - * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. - * - * @return the Time spent on preparing file write (falocate, etc). - */ - public long filePrepareWriteNanos() { - return filePrepareWriteNanos(nativeHandle_); - } - - /** - * Get the smallest output key prefix. - * - * @return the smallest output key prefix. - */ - public byte[] smallestOutputKeyPrefix() { - return smallestOutputKeyPrefix(nativeHandle_); - } - - /** - * Get the largest output key prefix. - * - * @return the smallest output key prefix. - */ - public byte[] largestOutputKeyPrefix() { - return largestOutputKeyPrefix(nativeHandle_); - } - - /** - * Get the number of single-deletes which do not meet a put. - * - * @return number of single-deletes which do not meet a put. - */ - @Experimental("Performance optimization for a very specific workload") - public long numSingleDelFallthru() { - return numSingleDelFallthru(nativeHandle_); - } - - /** - * Get the number of single-deletes which meet something other than a put. - * - * @return the number of single-deletes which meet something other than a put. - */ - @Experimental("Performance optimization for a very specific workload") - public long numSingleDelMismatch() { - return numSingleDelMismatch(nativeHandle_); - } - - private static native long newCompactionJobStats(); - @Override protected native void disposeInternal(final long handle); - - - private static native void reset(final long handle); - private static native void add(final long handle, - final long compactionJobStatsHandle); - private static native long elapsedMicros(final long handle); - private static native long numInputRecords(final long handle); - private static native long numInputFiles(final long handle); - private static native long numInputFilesAtOutputLevel(final long handle); - private static native long numOutputRecords(final long handle); - private static native long numOutputFiles(final long handle); - private static native boolean isManualCompaction(final long handle); - private static native long totalInputBytes(final long handle); - private static native long totalOutputBytes(final long handle); - private static native long numRecordsReplaced(final long handle); - private static native long totalInputRawKeyBytes(final long handle); - private static native long totalInputRawValueBytes(final long handle); - private static native long numInputDeletionRecords(final long handle); - private static native long numExpiredDeletionRecords(final long handle); - private static native long numCorruptKeys(final long handle); - private static native long fileWriteNanos(final long handle); - private static native long fileRangeSyncNanos(final long handle); - private static native long fileFsyncNanos(final long handle); - private static native long filePrepareWriteNanos(final long handle); - private static native byte[] smallestOutputKeyPrefix(final long handle); - private static native byte[] largestOutputKeyPrefix(final long handle); - private static native long numSingleDelFallthru(final long handle); - private static native long numSingleDelMismatch(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CompactionOptions.java b/java/src/main/java/org/rocksdb/CompactionOptions.java deleted file mode 100644 index 2c7e391fb..000000000 --- a/java/src/main/java/org/rocksdb/CompactionOptions.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * CompactionOptions are used in - * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, CompactionJobInfo)} - * calls. - */ -public class CompactionOptions extends RocksObject { - - public CompactionOptions() { - super(newCompactionOptions()); - } - - /** - * Get the compaction output compression type. - * - * See {@link #setCompression(CompressionType)}. - * - * @return the compression type. - */ - public CompressionType compression() { - return CompressionType.getCompressionType( - compression(nativeHandle_)); - } - - /** - * Set the compaction output compression type. - * - * Default: snappy - * - * If set to {@link CompressionType#DISABLE_COMPRESSION_OPTION}, - * RocksDB will choose compression type according to the - * {@link ColumnFamilyOptions#compressionType()}, taking into account - * the output level if {@link ColumnFamilyOptions#compressionPerLevel()} - * is specified. - * - * @param compression the compression type to use for compaction output. - * - * @return the instance of the current Options. - */ - public CompactionOptions setCompression(final CompressionType compression) { - setCompression(nativeHandle_, compression.getValue()); - return this; - } - - /** - * Get the compaction output file size limit. - * - * See {@link #setOutputFileSizeLimit(long)}. - * - * @return the file size limit. - */ - public long outputFileSizeLimit() { - return outputFileSizeLimit(nativeHandle_); - } - - /** - * Compaction will create files of size {@link #outputFileSizeLimit()}. - * - * Default: 2^64-1, which means that compaction will create a single file - * - * @param outputFileSizeLimit the size limit - * - * @return the instance of the current Options. - */ - public CompactionOptions setOutputFileSizeLimit( - final long outputFileSizeLimit) { - setOutputFileSizeLimit(nativeHandle_, outputFileSizeLimit); - return this; - } - - /** - * Get the maximum number of threads that will concurrently perform a - * compaction job. - * - * @return the maximum number of threads. - */ - public int maxSubcompactions() { - return maxSubcompactions(nativeHandle_); - } - - /** - * This value represents the maximum number of threads that will - * concurrently perform a compaction job by breaking it into multiple, - * smaller ones that are run simultaneously. - * - * Default: 0 (i.e. no subcompactions) - * - * If > 0, it will replace the option in - * {@link DBOptions#maxSubcompactions()} for this compaction. - * - * @param maxSubcompactions The maximum number of threads that will - * concurrently perform a compaction job - * - * @return the instance of the current Options. - */ - public CompactionOptions setMaxSubcompactions(final int maxSubcompactions) { - setMaxSubcompactions(nativeHandle_, maxSubcompactions); - return this; - } - - private static native long newCompactionOptions(); - @Override protected final native void disposeInternal(final long handle); - - private static native byte compression(final long handle); - private static native void setCompression(final long handle, - final byte compressionTypeValue); - private static native long outputFileSizeLimit(final long handle); - private static native void setOutputFileSizeLimit(final long handle, - final long outputFileSizeLimit); - private static native int maxSubcompactions(final long handle); - private static native void setMaxSubcompactions(final long handle, - final int maxSubcompactions); -} diff --git a/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java b/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java deleted file mode 100644 index 4c8d6545c..000000000 --- a/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Options for FIFO Compaction - */ -public class CompactionOptionsFIFO extends RocksObject { - - public CompactionOptionsFIFO() { - super(newCompactionOptionsFIFO()); - } - - /** - * Once the total sum of table files reaches this, we will delete the oldest - * table file - * - * Default: 1GB - * - * @param maxTableFilesSize The maximum size of the table files - * - * @return the reference to the current options. - */ - public CompactionOptionsFIFO setMaxTableFilesSize( - final long maxTableFilesSize) { - setMaxTableFilesSize(nativeHandle_, maxTableFilesSize); - return this; - } - - /** - * Once the total sum of table files reaches this, we will delete the oldest - * table file - * - * Default: 1GB - * - * @return max table file size in bytes - */ - public long maxTableFilesSize() { - return maxTableFilesSize(nativeHandle_); - } - - /** - * If true, try to do compaction to compact smaller files into larger ones. - * Minimum files to compact follows options.level0_file_num_compaction_trigger - * and compaction won't trigger if average compact bytes per del file is - * larger than options.write_buffer_size. This is to protect large files - * from being compacted again. - * - * Default: false - * - * @param allowCompaction true to allow intra-L0 compaction - * - * @return the reference to the current options. - */ - public CompactionOptionsFIFO setAllowCompaction( - final boolean allowCompaction) { - setAllowCompaction(nativeHandle_, allowCompaction); - return this; - } - - - /** - * Check if intra-L0 compaction is enabled. - * When enabled, we try to compact smaller files into larger ones. - * - * See {@link #setAllowCompaction(boolean)}. - * - * Default: false - * - * @return true if intra-L0 compaction is enabled, false otherwise. - */ - public boolean allowCompaction() { - return allowCompaction(nativeHandle_); - } - - - private native static long newCompactionOptionsFIFO(); - @Override protected final native void disposeInternal(final long handle); - - private native void setMaxTableFilesSize(final long handle, - final long maxTableFilesSize); - private native long maxTableFilesSize(final long handle); - private native void setAllowCompaction(final long handle, - final boolean allowCompaction); - private native boolean allowCompaction(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java b/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java deleted file mode 100644 index d2dfa4eef..000000000 --- a/java/src/main/java/org/rocksdb/CompactionOptionsUniversal.java +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Options for Universal Compaction - */ -public class CompactionOptionsUniversal extends RocksObject { - - public CompactionOptionsUniversal() { - super(newCompactionOptionsUniversal()); - } - - /** - * Percentage flexibility while comparing file size. If the candidate file(s) - * size is 1% smaller than the next file's size, then include next file into - * this candidate set. - * - * Default: 1 - * - * @param sizeRatio The size ratio to use - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setSizeRatio(final int sizeRatio) { - setSizeRatio(nativeHandle_, sizeRatio); - return this; - } - - /** - * Percentage flexibility while comparing file size. If the candidate file(s) - * size is 1% smaller than the next file's size, then include next file into - * this candidate set. - * - * Default: 1 - * - * @return The size ratio in use - */ - public int sizeRatio() { - return sizeRatio(nativeHandle_); - } - - /** - * The minimum number of files in a single compaction run. - * - * Default: 2 - * - * @param minMergeWidth minimum number of files in a single compaction run - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setMinMergeWidth(final int minMergeWidth) { - setMinMergeWidth(nativeHandle_, minMergeWidth); - return this; - } - - /** - * The minimum number of files in a single compaction run. - * - * Default: 2 - * - * @return minimum number of files in a single compaction run - */ - public int minMergeWidth() { - return minMergeWidth(nativeHandle_); - } - - /** - * The maximum number of files in a single compaction run. - * - * Default: {@link Long#MAX_VALUE} - * - * @param maxMergeWidth maximum number of files in a single compaction run - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setMaxMergeWidth(final int maxMergeWidth) { - setMaxMergeWidth(nativeHandle_, maxMergeWidth); - return this; - } - - /** - * The maximum number of files in a single compaction run. - * - * Default: {@link Long#MAX_VALUE} - * - * @return maximum number of files in a single compaction run - */ - public int maxMergeWidth() { - return maxMergeWidth(nativeHandle_); - } - - /** - * The size amplification is defined as the amount (in percentage) of - * additional storage needed to store a single byte of data in the database. - * For example, a size amplification of 2% means that a database that - * contains 100 bytes of user-data may occupy upto 102 bytes of - * physical storage. By this definition, a fully compacted database has - * a size amplification of 0%. Rocksdb uses the following heuristic - * to calculate size amplification: it assumes that all files excluding - * the earliest file contribute to the size amplification. - * - * Default: 200, which means that a 100 byte database could require upto - * 300 bytes of storage. - * - * @param maxSizeAmplificationPercent the amount of additional storage needed - * (as a percentage) to store a single byte in the database - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setMaxSizeAmplificationPercent( - final int maxSizeAmplificationPercent) { - setMaxSizeAmplificationPercent(nativeHandle_, maxSizeAmplificationPercent); - return this; - } - - /** - * The size amplification is defined as the amount (in percentage) of - * additional storage needed to store a single byte of data in the database. - * For example, a size amplification of 2% means that a database that - * contains 100 bytes of user-data may occupy upto 102 bytes of - * physical storage. By this definition, a fully compacted database has - * a size amplification of 0%. Rocksdb uses the following heuristic - * to calculate size amplification: it assumes that all files excluding - * the earliest file contribute to the size amplification. - * - * Default: 200, which means that a 100 byte database could require upto - * 300 bytes of storage. - * - * @return the amount of additional storage needed (as a percentage) to store - * a single byte in the database - */ - public int maxSizeAmplificationPercent() { - return maxSizeAmplificationPercent(nativeHandle_); - } - - /** - * If this option is set to be -1 (the default value), all the output files - * will follow compression type specified. - * - * If this option is not negative, we will try to make sure compressed - * size is just above this value. In normal cases, at least this percentage - * of data will be compressed. - * - * When we are compacting to a new file, here is the criteria whether - * it needs to be compressed: assuming here are the list of files sorted - * by generation time: - * A1...An B1...Bm C1...Ct - * where A1 is the newest and Ct is the oldest, and we are going to compact - * B1...Bm, we calculate the total size of all the files as total_size, as - * well as the total size of C1...Ct as total_C, the compaction output file - * will be compressed iff - * total_C / total_size < this percentage - * - * Default: -1 - * - * @param compressionSizePercent percentage of size for compression - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setCompressionSizePercent( - final int compressionSizePercent) { - setCompressionSizePercent(nativeHandle_, compressionSizePercent); - return this; - } - - /** - * If this option is set to be -1 (the default value), all the output files - * will follow compression type specified. - * - * If this option is not negative, we will try to make sure compressed - * size is just above this value. In normal cases, at least this percentage - * of data will be compressed. - * - * When we are compacting to a new file, here is the criteria whether - * it needs to be compressed: assuming here are the list of files sorted - * by generation time: - * A1...An B1...Bm C1...Ct - * where A1 is the newest and Ct is the oldest, and we are going to compact - * B1...Bm, we calculate the total size of all the files as total_size, as - * well as the total size of C1...Ct as total_C, the compaction output file - * will be compressed iff - * total_C / total_size < this percentage - * - * Default: -1 - * - * @return percentage of size for compression - */ - public int compressionSizePercent() { - return compressionSizePercent(nativeHandle_); - } - - /** - * The algorithm used to stop picking files into a single compaction run - * - * Default: {@link CompactionStopStyle#CompactionStopStyleTotalSize} - * - * @param compactionStopStyle The compaction algorithm - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setStopStyle( - final CompactionStopStyle compactionStopStyle) { - setStopStyle(nativeHandle_, compactionStopStyle.getValue()); - return this; - } - - /** - * The algorithm used to stop picking files into a single compaction run - * - * Default: {@link CompactionStopStyle#CompactionStopStyleTotalSize} - * - * @return The compaction algorithm - */ - public CompactionStopStyle stopStyle() { - return CompactionStopStyle.getCompactionStopStyle(stopStyle(nativeHandle_)); - } - - /** - * Option to optimize the universal multi level compaction by enabling - * trivial move for non overlapping files. - * - * Default: false - * - * @param allowTrivialMove true if trivial move is allowed - * - * @return the reference to the current options. - */ - public CompactionOptionsUniversal setAllowTrivialMove( - final boolean allowTrivialMove) { - setAllowTrivialMove(nativeHandle_, allowTrivialMove); - return this; - } - - /** - * Option to optimize the universal multi level compaction by enabling - * trivial move for non overlapping files. - * - * Default: false - * - * @return true if trivial move is allowed - */ - public boolean allowTrivialMove() { - return allowTrivialMove(nativeHandle_); - } - - private native static long newCompactionOptionsUniversal(); - @Override protected final native void disposeInternal(final long handle); - - private native void setSizeRatio(final long handle, final int sizeRatio); - private native int sizeRatio(final long handle); - private native void setMinMergeWidth( - final long handle, final int minMergeWidth); - private native int minMergeWidth(final long handle); - private native void setMaxMergeWidth( - final long handle, final int maxMergeWidth); - private native int maxMergeWidth(final long handle); - private native void setMaxSizeAmplificationPercent( - final long handle, final int maxSizeAmplificationPercent); - private native int maxSizeAmplificationPercent(final long handle); - private native void setCompressionSizePercent( - final long handle, final int compressionSizePercent); - private native int compressionSizePercent(final long handle); - private native void setStopStyle( - final long handle, final byte stopStyle); - private native byte stopStyle(final long handle); - private native void setAllowTrivialMove( - final long handle, final boolean allowTrivialMove); - private native boolean allowTrivialMove(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CompactionPriority.java b/java/src/main/java/org/rocksdb/CompactionPriority.java deleted file mode 100644 index eda05942e..000000000 --- a/java/src/main/java/org/rocksdb/CompactionPriority.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Compaction Priorities - */ -public enum CompactionPriority { - - /** - * Slightly Prioritize larger files by size compensated by #deletes - */ - ByCompensatedSize((byte)0x0), - - /** - * First compact files whose data's latest update time is oldest. - * Try this if you only update some hot keys in small ranges. - */ - OldestLargestSeqFirst((byte)0x1), - - /** - * First compact files whose range hasn't been compacted to the next level - * for the longest. If your updates are random across the key space, - * write amplification is slightly better with this option. - */ - OldestSmallestSeqFirst((byte)0x2), - - /** - * First compact files whose ratio between overlapping size in next level - * and its size is the smallest. It in many cases can optimize write - * amplification. - */ - MinOverlappingRatio((byte)0x3), - - /** - * Keeps a cursor(s) of the successor of the file (key range) was/were - * compacted before, and always picks the next files (key range) in that - * level. The file picking process will cycle through all the files in a - * round-robin manner. - */ - RoundRobin((byte)0x4); - - - private final byte value; - - CompactionPriority(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get CompactionPriority by byte value. - * - * @param value byte representation of CompactionPriority. - * - * @return {@link org.rocksdb.CompactionPriority} instance or null. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static CompactionPriority getCompactionPriority(final byte value) { - for (final CompactionPriority compactionPriority : - CompactionPriority.values()) { - if (compactionPriority.getValue() == value){ - return compactionPriority; - } - } - throw new IllegalArgumentException( - "Illegal value provided for CompactionPriority."); - } -} diff --git a/java/src/main/java/org/rocksdb/CompactionReason.java b/java/src/main/java/org/rocksdb/CompactionReason.java deleted file mode 100644 index 46ec33f3f..000000000 --- a/java/src/main/java/org/rocksdb/CompactionReason.java +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum CompactionReason { - kUnknown((byte)0x0), - - /** - * [Level] number of L0 files > level0_file_num_compaction_trigger - */ - kLevelL0FilesNum((byte)0x1), - - /** - * [Level] total size of level > MaxBytesForLevel() - */ - kLevelMaxLevelSize((byte)0x2), - - /** - * [Universal] Compacting for size amplification - */ - kUniversalSizeAmplification((byte)0x3), - - /** - * [Universal] Compacting for size ratio - */ - kUniversalSizeRatio((byte)0x4), - - /** - * [Universal] number of sorted runs > level0_file_num_compaction_trigger - */ - kUniversalSortedRunNum((byte)0x5), - - /** - * [FIFO] total size > max_table_files_size - */ - kFIFOMaxSize((byte)0x6), - - /** - * [FIFO] reduce number of files. - */ - kFIFOReduceNumFiles((byte)0x7), - - /** - * [FIFO] files with creation time < (current_time - interval) - */ - kFIFOTtl((byte)0x8), - - /** - * Manual compaction - */ - kManualCompaction((byte)0x9), - - /** - * DB::SuggestCompactRange() marked files for compaction - */ - kFilesMarkedForCompaction((byte)0x10), - - /** - * [Level] Automatic compaction within bottommost level to cleanup duplicate - * versions of same user key, usually due to a released snapshot. - */ - kBottommostFiles((byte)0x0A), - - /** - * Compaction based on TTL - */ - kTtl((byte)0x0B), - - /** - * According to the comments in flush_job.cc, RocksDB treats flush as - * a level 0 compaction in internal stats. - */ - kFlush((byte)0x0C), - - /** - * Compaction caused by external sst file ingestion - */ - kExternalSstIngestion((byte) 0x0D), - - /** - * Compaction due to SST file being too old - */ - kPeriodicCompaction((byte) 0x0E), - - /** - * Compaction in order to move files to temperature - */ - kChangeTemperature((byte) 0x0F), - - /** - * Compaction scheduled to force garbage collection of blob files - */ - kForcedBlobGC((byte) 0x11), - - /** - * A special TTL compaction for RoundRobin policy, which basically the same as - * kLevelMaxLevelSize, but the goal is to compact TTLed files. - */ - kRoundRobinTtl((byte) 0x12), - - /** - * Compaction by calling DBImpl::ReFitLevel - */ - kRefitLevel((byte) 0x13); - - private final byte value; - - CompactionReason(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value - */ - byte getValue() { - return value; - } - - /** - * Get the CompactionReason from the internal representation value. - * - * @return the compaction reason. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static CompactionReason fromValue(final byte value) { - for (final CompactionReason compactionReason : CompactionReason.values()) { - if(compactionReason.value == value) { - return compactionReason; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for CompactionReason: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/CompactionStopStyle.java b/java/src/main/java/org/rocksdb/CompactionStopStyle.java deleted file mode 100644 index f6e63209c..000000000 --- a/java/src/main/java/org/rocksdb/CompactionStopStyle.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * Algorithm used to make a compaction request stop picking new files - * into a single compaction run - */ -public enum CompactionStopStyle { - - /** - * Pick files of similar size - */ - CompactionStopStyleSimilarSize((byte)0x0), - - /** - * Total size of picked files > next file - */ - CompactionStopStyleTotalSize((byte)0x1); - - - private final byte value; - - CompactionStopStyle(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get CompactionStopStyle by byte value. - * - * @param value byte representation of CompactionStopStyle. - * - * @return {@link org.rocksdb.CompactionStopStyle} instance or null. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static CompactionStopStyle getCompactionStopStyle(final byte value) { - for (final CompactionStopStyle compactionStopStyle : - CompactionStopStyle.values()) { - if (compactionStopStyle.getValue() == value){ - return compactionStopStyle; - } - } - throw new IllegalArgumentException( - "Illegal value provided for CompactionStopStyle."); - } -} diff --git a/java/src/main/java/org/rocksdb/CompactionStyle.java b/java/src/main/java/org/rocksdb/CompactionStyle.java deleted file mode 100644 index b24bbf850..000000000 --- a/java/src/main/java/org/rocksdb/CompactionStyle.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * Enum CompactionStyle - * - * RocksDB supports different styles of compaction. Available - * compaction styles can be chosen using this enumeration. - * - *
      - *
    1. LEVEL - Level based Compaction style
    2. - *
    3. UNIVERSAL - Universal Compaction Style is a - * compaction style, targeting the use cases requiring lower write - * amplification, trading off read amplification and space - * amplification.
    4. - *
    5. FIFO - FIFO compaction style is the simplest - * compaction strategy. It is suited for keeping event log data with - * very low overhead (query log for example). It periodically deletes - * the old data, so it's basically a TTL compaction style.
    6. - *
    7. NONE - Disable background compaction. - * Compaction jobs are submitted - * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, CompactionJobInfo)} ()}.
    8. - *
    - * - * @see - * Universal Compaction - * @see - * FIFO Compaction - */ -public enum CompactionStyle { - LEVEL((byte) 0x0), - UNIVERSAL((byte) 0x1), - FIFO((byte) 0x2), - NONE((byte) 0x3); - - private final byte value; - - CompactionStyle(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value. - */ - //TODO(AR) should be made package-private - public byte getValue() { - return value; - } - - /** - * Get the Compaction style from the internal representation value. - * - * @param value the internal representation value. - * - * @return the Compaction style - * - * @throws IllegalArgumentException if the value does not match a - * CompactionStyle - */ - static CompactionStyle fromValue(final byte value) - throws IllegalArgumentException { - for (final CompactionStyle compactionStyle : CompactionStyle.values()) { - if (compactionStyle.value == value) { - return compactionStyle; - } - } - throw new IllegalArgumentException("Unknown value for CompactionStyle: " - + value); - } -} diff --git a/java/src/main/java/org/rocksdb/ComparatorOptions.java b/java/src/main/java/org/rocksdb/ComparatorOptions.java deleted file mode 100644 index 8c3162858..000000000 --- a/java/src/main/java/org/rocksdb/ComparatorOptions.java +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * This class controls the behaviour - * of Java implementations of - * AbstractComparator - * - * Note that dispose() must be called before a ComparatorOptions - * instance becomes out-of-scope to release the allocated memory in C++. - */ -public class ComparatorOptions extends RocksObject { - public ComparatorOptions() { - super(newComparatorOptions()); - } - - /** - * Get the synchronisation type used to guard the reused buffers. - * Only used if {@link #maxReusedBufferSize()} > 0 - * Default: {@link ReusedSynchronisationType#ADAPTIVE_MUTEX} - * - * @return the synchronisation type - */ - public ReusedSynchronisationType reusedSynchronisationType() { - assert(isOwningHandle()); - return ReusedSynchronisationType.getReusedSynchronisationType( - reusedSynchronisationType(nativeHandle_)); - } - - /** - * Set the synchronisation type used to guard the reused buffers. - * Only used if {@link #maxReusedBufferSize()} > 0 - * Default: {@link ReusedSynchronisationType#ADAPTIVE_MUTEX} - * - * @param reusedSynchronisationType the synchronisation type - * - * @return the reference to the current comparator options. - */ - public ComparatorOptions setReusedSynchronisationType( - final ReusedSynchronisationType reusedSynchronisationType) { - assert (isOwningHandle()); - setReusedSynchronisationType(nativeHandle_, - reusedSynchronisationType.getValue()); - return this; - } - - /** - * Indicates if a direct byte buffer (i.e. outside of the normal - * garbage-collected heap) is used, as opposed to a non-direct byte buffer - * which is a wrapper around an on-heap byte[]. - * - * Default: true - * - * @return true if a direct byte buffer will be used, false otherwise - */ - public boolean useDirectBuffer() { - assert(isOwningHandle()); - return useDirectBuffer(nativeHandle_); - } - - /** - * Controls whether a direct byte buffer (i.e. outside of the normal - * garbage-collected heap) is used, as opposed to a non-direct byte buffer - * which is a wrapper around an on-heap byte[]. - * - * Default: true - * - * @param useDirectBuffer true if a direct byte buffer should be used, - * false otherwise - * @return the reference to the current comparator options. - */ - public ComparatorOptions setUseDirectBuffer(final boolean useDirectBuffer) { - assert(isOwningHandle()); - setUseDirectBuffer(nativeHandle_, useDirectBuffer); - return this; - } - - /** - * Maximum size of a buffer (in bytes) that will be reused. - * Comparators will use 5 of these buffers, - * so the retained memory size will be 5 * max_reused_buffer_size. - * When a buffer is needed for transferring data to a callback, - * if it requires less than {@code maxReuseBufferSize}, then an - * existing buffer will be reused, else a new buffer will be - * allocated just for that callback. - * - * Default: 64 bytes - * - * @return the maximum size of a buffer which is reused, - * or 0 if reuse is disabled - */ - public int maxReusedBufferSize() { - assert(isOwningHandle()); - return maxReusedBufferSize(nativeHandle_); - } - - /** - * Sets the maximum size of a buffer (in bytes) that will be reused. - * Comparators will use 5 of these buffers, - * so the retained memory size will be 5 * max_reused_buffer_size. - * When a buffer is needed for transferring data to a callback, - * if it requires less than {@code maxReuseBufferSize}, then an - * existing buffer will be reused, else a new buffer will be - * allocated just for that callback. - * - * Default: 64 bytes - * - * @param maxReusedBufferSize the maximum size for a buffer to reuse, or 0 to - * disable reuse - * - * @return the maximum size of a buffer which is reused - */ - public ComparatorOptions setMaxReusedBufferSize(final int maxReusedBufferSize) { - assert(isOwningHandle()); - setMaxReusedBufferSize(nativeHandle_, maxReusedBufferSize); - return this; - } - - private native static long newComparatorOptions(); - private native byte reusedSynchronisationType(final long handle); - private native void setReusedSynchronisationType(final long handle, - final byte reusedSynchronisationType); - private native boolean useDirectBuffer(final long handle); - private native void setUseDirectBuffer(final long handle, - final boolean useDirectBuffer); - private native int maxReusedBufferSize(final long handle); - private native void setMaxReusedBufferSize(final long handle, - final int maxReuseBufferSize); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/ComparatorType.java b/java/src/main/java/org/rocksdb/ComparatorType.java deleted file mode 100644 index 199980b6e..000000000 --- a/java/src/main/java/org/rocksdb/ComparatorType.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -enum ComparatorType { - JAVA_COMPARATOR((byte)0x0), - JAVA_NATIVE_COMPARATOR_WRAPPER((byte)0x1); - - private final byte value; - - ComparatorType(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - byte getValue() { - return value; - } - - /** - *

    Get the ComparatorType enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of ComparatorType. - * - * @return ComparatorType instance. - * - * @throws IllegalArgumentException if the comparator type for the byteIdentifier - * cannot be found - */ - static ComparatorType getComparatorType(final byte byteIdentifier) { - for (final ComparatorType comparatorType : ComparatorType.values()) { - if (comparatorType.getValue() == byteIdentifier) { - return comparatorType; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for ComparatorType."); - } -} diff --git a/java/src/main/java/org/rocksdb/CompressionOptions.java b/java/src/main/java/org/rocksdb/CompressionOptions.java deleted file mode 100644 index a9072bbb9..000000000 --- a/java/src/main/java/org/rocksdb/CompressionOptions.java +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Options for Compression - */ -public class CompressionOptions extends RocksObject { - - public CompressionOptions() { - super(newCompressionOptions()); - } - - public CompressionOptions setWindowBits(final int windowBits) { - setWindowBits(nativeHandle_, windowBits); - return this; - } - - public int windowBits() { - return windowBits(nativeHandle_); - } - - public CompressionOptions setLevel(final int level) { - setLevel(nativeHandle_, level); - return this; - } - - public int level() { - return level(nativeHandle_); - } - - public CompressionOptions setStrategy(final int strategy) { - setStrategy(nativeHandle_, strategy); - return this; - } - - public int strategy() { - return strategy(nativeHandle_); - } - - /** - * Maximum size of dictionary used to prime the compression library. Currently - * this dictionary will be constructed by sampling the first output file in a - * subcompaction when the target level is bottommost. This dictionary will be - * loaded into the compression library before compressing/uncompressing each - * data block of subsequent files in the subcompaction. Effectively, this - * improves compression ratios when there are repetitions across data blocks. - * - * A value of 0 indicates the feature is disabled. - * - * Default: 0. - * - * @param maxDictBytes Maximum bytes to use for the dictionary - * - * @return the reference to the current options - */ - public CompressionOptions setMaxDictBytes(final int maxDictBytes) { - setMaxDictBytes(nativeHandle_, maxDictBytes); - return this; - } - - /** - * Maximum size of dictionary used to prime the compression library. - * - * @return The maximum bytes to use for the dictionary - */ - public int maxDictBytes() { - return maxDictBytes(nativeHandle_); - } - - /** - * Maximum size of training data passed to zstd's dictionary trainer. Using - * zstd's dictionary trainer can achieve even better compression ratio - * improvements than using {@link #setMaxDictBytes(int)} alone. - * - * The training data will be used to generate a dictionary - * of {@link #maxDictBytes()}. - * - * Default: 0. - * - * @param zstdMaxTrainBytes Maximum bytes to use for training ZStd. - * - * @return the reference to the current options - */ - public CompressionOptions setZStdMaxTrainBytes(final int zstdMaxTrainBytes) { - setZstdMaxTrainBytes(nativeHandle_, zstdMaxTrainBytes); - return this; - } - - /** - * Maximum size of training data passed to zstd's dictionary trainer. - * - * @return Maximum bytes to use for training ZStd - */ - public int zstdMaxTrainBytes() { - return zstdMaxTrainBytes(nativeHandle_); - } - - /** - * When the compression options are set by the user, it will be set to "true". - * For bottommost_compression_opts, to enable it, user must set enabled=true. - * Otherwise, bottommost compression will use compression_opts as default - * compression options. - * - * For compression_opts, if compression_opts.enabled=false, it is still - * used as compression options for compression process. - * - * Default: false. - * - * @param enabled true to use these compression options - * for the bottommost_compression_opts, false otherwise - * - * @return the reference to the current options - */ - public CompressionOptions setEnabled(final boolean enabled) { - setEnabled(nativeHandle_, enabled); - return this; - } - - /** - * Determine whether these compression options - * are used for the bottommost_compression_opts. - * - * @return true if these compression options are used - * for the bottommost_compression_opts, false otherwise - */ - public boolean enabled() { - return enabled(nativeHandle_); - } - - - private native static long newCompressionOptions(); - @Override protected final native void disposeInternal(final long handle); - - private native void setWindowBits(final long handle, final int windowBits); - private native int windowBits(final long handle); - private native void setLevel(final long handle, final int level); - private native int level(final long handle); - private native void setStrategy(final long handle, final int strategy); - private native int strategy(final long handle); - private native void setMaxDictBytes(final long handle, final int maxDictBytes); - private native int maxDictBytes(final long handle); - private native void setZstdMaxTrainBytes(final long handle, - final int zstdMaxTrainBytes); - private native int zstdMaxTrainBytes(final long handle); - private native void setEnabled(final long handle, final boolean enabled); - private native boolean enabled(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/CompressionType.java b/java/src/main/java/org/rocksdb/CompressionType.java deleted file mode 100644 index d1d73d51a..000000000 --- a/java/src/main/java/org/rocksdb/CompressionType.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Enum CompressionType - * - *

    DB contents are stored in a set of blocks, each of which holds a - * sequence of key,value pairs. Each block may be compressed before - * being stored in a file. The following enum describes which - * compression method (if any) is used to compress a block.

    - */ -public enum CompressionType { - NO_COMPRESSION((byte) 0x0, null, "kNoCompression"), - SNAPPY_COMPRESSION((byte) 0x1, "snappy", "kSnappyCompression"), - ZLIB_COMPRESSION((byte) 0x2, "z", "kZlibCompression"), - BZLIB2_COMPRESSION((byte) 0x3, "bzip2", "kBZip2Compression"), - LZ4_COMPRESSION((byte) 0x4, "lz4", "kLZ4Compression"), - LZ4HC_COMPRESSION((byte) 0x5, "lz4hc", "kLZ4HCCompression"), - XPRESS_COMPRESSION((byte) 0x6, "xpress", "kXpressCompression"), - ZSTD_COMPRESSION((byte) 0x7, "zstd", "kZSTD"), - DISABLE_COMPRESSION_OPTION((byte) 0x7F, null, "kDisableCompressionOption"); - - /** - *

    Get the CompressionType enumeration value by - * passing the library name to this method.

    - * - *

    If library cannot be found the enumeration - * value {@code NO_COMPRESSION} will be returned.

    - * - * @param libraryName compression library name. - * - * @return CompressionType instance. - */ - public static CompressionType getCompressionType(String libraryName) { - if (libraryName != null) { - for (CompressionType compressionType : CompressionType.values()) { - if (compressionType.getLibraryName() != null && - compressionType.getLibraryName().equals(libraryName)) { - return compressionType; - } - } - } - return CompressionType.NO_COMPRESSION; - } - - /** - *

    Get the CompressionType enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of CompressionType. - * - * @return CompressionType instance. - * - * @throws IllegalArgumentException If CompressionType cannot be found for the - * provided byteIdentifier - */ - public static CompressionType getCompressionType(byte byteIdentifier) { - for (final CompressionType compressionType : CompressionType.values()) { - if (compressionType.getValue() == byteIdentifier) { - return compressionType; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for CompressionType."); - } - - /** - *

    Get a CompressionType value based on the string key in the C++ options output. - * This gets used in support of getting options into Java from an options string, - * which is generated at the C++ level. - *

    - * - * @param internalName the internal (C++) name by which the option is known. - * - * @return CompressionType instance (optional) - */ - static CompressionType getFromInternal(final String internalName) { - for (final CompressionType compressionType : CompressionType.values()) { - if (compressionType.internalName_.equals(internalName)) { - return compressionType; - } - } - - throw new IllegalArgumentException( - "Illegal internalName '" + internalName + " ' provided for CompressionType."); - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - /** - *

    Returns the library name of the compression type - * identified by the enumeration value.

    - * - * @return library name - */ - public String getLibraryName() { - return libraryName_; - } - - CompressionType(final byte value, final String libraryName, final String internalName) { - value_ = value; - libraryName_ = libraryName; - internalName_ = internalName; - } - - private final byte value_; - private final String libraryName_; - private final String internalName_; -} diff --git a/java/src/main/java/org/rocksdb/ConcurrentTaskLimiter.java b/java/src/main/java/org/rocksdb/ConcurrentTaskLimiter.java deleted file mode 100644 index b4e34303b..000000000 --- a/java/src/main/java/org/rocksdb/ConcurrentTaskLimiter.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public abstract class ConcurrentTaskLimiter extends RocksObject { - protected ConcurrentTaskLimiter(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Returns a name that identifies this concurrent task limiter. - * - * @return Concurrent task limiter name. - */ - public abstract String name(); - - /** - * Set max concurrent tasks.
    - * limit = 0 means no new task allowed.
    - * limit < 0 means no limitation. - * - * @param maxOutstandinsTask max concurrent tasks. - * @return the reference to the current instance of ConcurrentTaskLimiter. - */ - public abstract ConcurrentTaskLimiter setMaxOutstandingTask(final int maxOutstandinsTask); - - /** - * Reset to unlimited max concurrent task. - * - * @return the reference to the current instance of ConcurrentTaskLimiter. - */ - public abstract ConcurrentTaskLimiter resetMaxOutstandingTask(); - - /** - * Returns current outstanding task count. - * - * @return current outstanding task count. - */ - public abstract int outstandingTask(); -} diff --git a/java/src/main/java/org/rocksdb/ConcurrentTaskLimiterImpl.java b/java/src/main/java/org/rocksdb/ConcurrentTaskLimiterImpl.java deleted file mode 100644 index d28b9060a..000000000 --- a/java/src/main/java/org/rocksdb/ConcurrentTaskLimiterImpl.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class ConcurrentTaskLimiterImpl extends ConcurrentTaskLimiter { - public ConcurrentTaskLimiterImpl(final String name, final int maxOutstandingTask) { - super(newConcurrentTaskLimiterImpl0(name, maxOutstandingTask)); - } - - @Override - public String name() { - assert (isOwningHandle()); - return name(nativeHandle_); - } - - @Override - public ConcurrentTaskLimiter setMaxOutstandingTask(final int maxOutstandingTask) { - assert (isOwningHandle()); - setMaxOutstandingTask(nativeHandle_, maxOutstandingTask); - return this; - } - - @Override - public ConcurrentTaskLimiter resetMaxOutstandingTask() { - assert (isOwningHandle()); - resetMaxOutstandingTask(nativeHandle_); - return this; - } - - @Override - public int outstandingTask() { - assert (isOwningHandle()); - return outstandingTask(nativeHandle_); - } - - private static native long newConcurrentTaskLimiterImpl0( - final String name, final int maxOutstandingTask); - private static native String name(final long handle); - private static native void setMaxOutstandingTask(final long handle, final int limit); - private static native void resetMaxOutstandingTask(final long handle); - private static native int outstandingTask(final long handle); - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/ConfigOptions.java b/java/src/main/java/org/rocksdb/ConfigOptions.java deleted file mode 100644 index 4d93f0c99..000000000 --- a/java/src/main/java/org/rocksdb/ConfigOptions.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class ConfigOptions extends RocksObject { - static { - RocksDB.loadLibrary(); - } - - /** - * Construct with default Options - */ - public ConfigOptions() { - super(newConfigOptions()); - } - - public ConfigOptions setDelimiter(final String delimiter) { - setDelimiter(nativeHandle_, delimiter); - return this; - } - public ConfigOptions setIgnoreUnknownOptions(final boolean ignore) { - setIgnoreUnknownOptions(nativeHandle_, ignore); - return this; - } - - public ConfigOptions setEnv(final Env env) { - setEnv(nativeHandle_, env.nativeHandle_); - return this; - } - - public ConfigOptions setInputStringsEscaped(final boolean escaped) { - setInputStringsEscaped(nativeHandle_, escaped); - return this; - } - - public ConfigOptions setSanityLevel(final SanityLevel level) { - setSanityLevel(nativeHandle_, level.getValue()); - return this; - } - - @Override protected final native void disposeInternal(final long handle); - - private native static long newConfigOptions(); - private native static void setEnv(final long handle, final long envHandle); - private native static void setDelimiter(final long handle, final String delimiter); - private native static void setIgnoreUnknownOptions(final long handle, final boolean ignore); - private native static void setInputStringsEscaped(final long handle, final boolean escaped); - private native static void setSanityLevel(final long handle, final byte level); -} diff --git a/java/src/main/java/org/rocksdb/DBOptions.java b/java/src/main/java/org/rocksdb/DBOptions.java deleted file mode 100644 index 9eb5ca873..000000000 --- a/java/src/main/java/org/rocksdb/DBOptions.java +++ /dev/null @@ -1,1496 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.file.Paths; -import java.util.*; - -/** - * DBOptions to control the behavior of a database. It will be used - * during the creation of a {@link org.rocksdb.RocksDB} (i.e., RocksDB.open()). - * - * As a descendent of {@link AbstractNativeReference}, this class is {@link AutoCloseable} - * and will be automatically released if opened in the preamble of a try with resources block. - */ -public class DBOptions extends RocksObject - implements DBOptionsInterface, - MutableDBOptionsInterface { - static { - RocksDB.loadLibrary(); - } - - /** - * Construct DBOptions. - * - * This constructor will create (by allocating a block of memory) - * an {@code rocksdb::DBOptions} in the c++ side. - */ - public DBOptions() { - super(newDBOptions()); - numShardBits_ = DEFAULT_NUM_SHARD_BITS; - env_ = Env.getDefault(); - } - - /** - * Copy constructor for DBOptions. - * - * NOTE: This does a shallow copy, which means env, rate_limiter, sst_file_manager, - * info_log and other pointers will be cloned! - * - * @param other The DBOptions to copy. - */ - public DBOptions(DBOptions other) { - super(copyDBOptions(other.nativeHandle_)); - this.env_ = other.env_; - this.numShardBits_ = other.numShardBits_; - this.rateLimiter_ = other.rateLimiter_; - this.rowCache_ = other.rowCache_; - this.walFilter_ = other.walFilter_; - this.writeBufferManager_ = other.writeBufferManager_; - } - - /** - * Constructor from Options - * - * @param options The options. - */ - public DBOptions(final Options options) { - super(newDBOptionsFromOptions(options.nativeHandle_)); - } - - /** - *

    Method to get a options instance by using pre-configured - * property values. If one or many values are undefined in - * the context of RocksDB the method will return a null - * value.

    - * - *

    Note: Property keys can be derived from - * getter methods within the options class. Example: the method - * {@code allowMmapReads()} has a property key: - * {@code allow_mmap_reads}.

    - * - * @param cfgOpts The ConfigOptions to control how the string is processed. - * @param properties {@link java.util.Properties} instance. - * - * @return {@link org.rocksdb.DBOptions instance} - * or null. - * - * @throws java.lang.IllegalArgumentException if null or empty - * {@link java.util.Properties} instance is passed to the method call. - */ - public static DBOptions getDBOptionsFromProps( - final ConfigOptions cfgOpts, final Properties properties) { - DBOptions dbOptions = null; - final String optionsString = Options.getOptionStringFromProps(properties); - final long handle = getDBOptionsFromProps(cfgOpts.nativeHandle_, optionsString); - if (handle != 0) { - dbOptions = new DBOptions(handle); - } - return dbOptions; - } - - /** - *

    Method to get a options instance by using pre-configured - * property values. If one or many values are undefined in - * the context of RocksDB the method will return a null - * value.

    - * - *

    Note: Property keys can be derived from - * getter methods within the options class. Example: the method - * {@code allowMmapReads()} has a property key: - * {@code allow_mmap_reads}.

    - * - * @param properties {@link java.util.Properties} instance. - * - * @return {@link org.rocksdb.DBOptions instance} - * or null. - * - * @throws java.lang.IllegalArgumentException if null or empty - * {@link java.util.Properties} instance is passed to the method call. - */ - public static DBOptions getDBOptionsFromProps(final Properties properties) { - DBOptions dbOptions = null; - final String optionsString = Options.getOptionStringFromProps(properties); - final long handle = getDBOptionsFromProps(optionsString); - if (handle != 0) { - dbOptions = new DBOptions(handle); - } - return dbOptions; - } - - @Override - public DBOptions optimizeForSmallDb() { - optimizeForSmallDb(nativeHandle_); - return this; - } - - @Override - public DBOptions setIncreaseParallelism( - final int totalThreads) { - assert(isOwningHandle()); - setIncreaseParallelism(nativeHandle_, totalThreads); - return this; - } - - @Override - public DBOptions setCreateIfMissing(final boolean flag) { - assert(isOwningHandle()); - setCreateIfMissing(nativeHandle_, flag); - return this; - } - - @Override - public boolean createIfMissing() { - assert(isOwningHandle()); - return createIfMissing(nativeHandle_); - } - - @Override - public DBOptions setCreateMissingColumnFamilies( - final boolean flag) { - assert(isOwningHandle()); - setCreateMissingColumnFamilies(nativeHandle_, flag); - return this; - } - - @Override - public boolean createMissingColumnFamilies() { - assert(isOwningHandle()); - return createMissingColumnFamilies(nativeHandle_); - } - - @Override - public DBOptions setErrorIfExists( - final boolean errorIfExists) { - assert(isOwningHandle()); - setErrorIfExists(nativeHandle_, errorIfExists); - return this; - } - - @Override - public boolean errorIfExists() { - assert(isOwningHandle()); - return errorIfExists(nativeHandle_); - } - - @Override - public DBOptions setParanoidChecks( - final boolean paranoidChecks) { - assert(isOwningHandle()); - setParanoidChecks(nativeHandle_, paranoidChecks); - return this; - } - - @Override - public boolean paranoidChecks() { - assert(isOwningHandle()); - return paranoidChecks(nativeHandle_); - } - - @Override - public DBOptions setEnv(final Env env) { - setEnv(nativeHandle_, env.nativeHandle_); - this.env_ = env; - return this; - } - - @Override - public Env getEnv() { - return env_; - } - - @Override - public DBOptions setRateLimiter(final RateLimiter rateLimiter) { - assert(isOwningHandle()); - rateLimiter_ = rateLimiter; - setRateLimiter(nativeHandle_, rateLimiter.nativeHandle_); - return this; - } - - @Override - public DBOptions setSstFileManager(final SstFileManager sstFileManager) { - assert(isOwningHandle()); - setSstFileManager(nativeHandle_, sstFileManager.nativeHandle_); - return this; - } - - @Override - public DBOptions setLogger(final Logger logger) { - assert(isOwningHandle()); - setLogger(nativeHandle_, logger.nativeHandle_); - return this; - } - - @Override - public DBOptions setInfoLogLevel( - final InfoLogLevel infoLogLevel) { - assert(isOwningHandle()); - setInfoLogLevel(nativeHandle_, infoLogLevel.getValue()); - return this; - } - - @Override - public InfoLogLevel infoLogLevel() { - assert(isOwningHandle()); - return InfoLogLevel.getInfoLogLevel( - infoLogLevel(nativeHandle_)); - } - - @Override - public DBOptions setMaxOpenFiles( - final int maxOpenFiles) { - assert(isOwningHandle()); - setMaxOpenFiles(nativeHandle_, maxOpenFiles); - return this; - } - - @Override - public int maxOpenFiles() { - assert(isOwningHandle()); - return maxOpenFiles(nativeHandle_); - } - - @Override - public DBOptions setMaxFileOpeningThreads(final int maxFileOpeningThreads) { - assert(isOwningHandle()); - setMaxFileOpeningThreads(nativeHandle_, maxFileOpeningThreads); - return this; - } - - @Override - public int maxFileOpeningThreads() { - assert(isOwningHandle()); - return maxFileOpeningThreads(nativeHandle_); - } - - @Override - public DBOptions setMaxTotalWalSize( - final long maxTotalWalSize) { - assert(isOwningHandle()); - setMaxTotalWalSize(nativeHandle_, maxTotalWalSize); - return this; - } - - @Override - public long maxTotalWalSize() { - assert(isOwningHandle()); - return maxTotalWalSize(nativeHandle_); - } - - @Override - public DBOptions setStatistics(final Statistics statistics) { - assert(isOwningHandle()); - setStatistics(nativeHandle_, statistics.nativeHandle_); - return this; - } - - @Override - public Statistics statistics() { - assert(isOwningHandle()); - final long statisticsNativeHandle = statistics(nativeHandle_); - if(statisticsNativeHandle == 0) { - return null; - } else { - return new Statistics(statisticsNativeHandle); - } - } - - @Override - public DBOptions setUseFsync( - final boolean useFsync) { - assert(isOwningHandle()); - setUseFsync(nativeHandle_, useFsync); - return this; - } - - @Override - public boolean useFsync() { - assert(isOwningHandle()); - return useFsync(nativeHandle_); - } - - @Override - public DBOptions setDbPaths(final Collection dbPaths) { - assert(isOwningHandle()); - - final int len = dbPaths.size(); - final String[] paths = new String[len]; - final long[] targetSizes = new long[len]; - - int i = 0; - for(final DbPath dbPath : dbPaths) { - paths[i] = dbPath.path.toString(); - targetSizes[i] = dbPath.targetSize; - i++; - } - setDbPaths(nativeHandle_, paths, targetSizes); - return this; - } - - @Override - public List dbPaths() { - final int len = (int)dbPathsLen(nativeHandle_); - if(len == 0) { - return Collections.emptyList(); - } else { - final String[] paths = new String[len]; - final long[] targetSizes = new long[len]; - - dbPaths(nativeHandle_, paths, targetSizes); - - final List dbPaths = new ArrayList<>(); - for(int i = 0; i < len; i++) { - dbPaths.add(new DbPath(Paths.get(paths[i]), targetSizes[i])); - } - return dbPaths; - } - } - - @Override - public DBOptions setDbLogDir( - final String dbLogDir) { - assert(isOwningHandle()); - setDbLogDir(nativeHandle_, dbLogDir); - return this; - } - - @Override - public String dbLogDir() { - assert(isOwningHandle()); - return dbLogDir(nativeHandle_); - } - - @Override - public DBOptions setWalDir( - final String walDir) { - assert(isOwningHandle()); - setWalDir(nativeHandle_, walDir); - return this; - } - - @Override - public String walDir() { - assert(isOwningHandle()); - return walDir(nativeHandle_); - } - - @Override - public DBOptions setDeleteObsoleteFilesPeriodMicros( - final long micros) { - assert(isOwningHandle()); - setDeleteObsoleteFilesPeriodMicros(nativeHandle_, micros); - return this; - } - - @Override - public long deleteObsoleteFilesPeriodMicros() { - assert(isOwningHandle()); - return deleteObsoleteFilesPeriodMicros(nativeHandle_); - } - - @Override - public DBOptions setMaxBackgroundJobs(final int maxBackgroundJobs) { - assert(isOwningHandle()); - setMaxBackgroundJobs(nativeHandle_, maxBackgroundJobs); - return this; - } - - @Override - public int maxBackgroundJobs() { - assert(isOwningHandle()); - return maxBackgroundJobs(nativeHandle_); - } - - @Override - @Deprecated - public DBOptions setMaxBackgroundCompactions( - final int maxBackgroundCompactions) { - assert(isOwningHandle()); - setMaxBackgroundCompactions(nativeHandle_, maxBackgroundCompactions); - return this; - } - - @Override - @Deprecated - public int maxBackgroundCompactions() { - assert(isOwningHandle()); - return maxBackgroundCompactions(nativeHandle_); - } - - @Override - public DBOptions setMaxSubcompactions(final int maxSubcompactions) { - assert(isOwningHandle()); - setMaxSubcompactions(nativeHandle_, maxSubcompactions); - return this; - } - - @Override - public int maxSubcompactions() { - assert(isOwningHandle()); - return maxSubcompactions(nativeHandle_); - } - - @Override - @Deprecated - public DBOptions setMaxBackgroundFlushes( - final int maxBackgroundFlushes) { - assert(isOwningHandle()); - setMaxBackgroundFlushes(nativeHandle_, maxBackgroundFlushes); - return this; - } - - @Override - @Deprecated - public int maxBackgroundFlushes() { - assert(isOwningHandle()); - return maxBackgroundFlushes(nativeHandle_); - } - - @Override - public DBOptions setMaxLogFileSize(final long maxLogFileSize) { - assert(isOwningHandle()); - setMaxLogFileSize(nativeHandle_, maxLogFileSize); - return this; - } - - @Override - public long maxLogFileSize() { - assert(isOwningHandle()); - return maxLogFileSize(nativeHandle_); - } - - @Override - public DBOptions setLogFileTimeToRoll( - final long logFileTimeToRoll) { - assert(isOwningHandle()); - setLogFileTimeToRoll(nativeHandle_, logFileTimeToRoll); - return this; - } - - @Override - public long logFileTimeToRoll() { - assert(isOwningHandle()); - return logFileTimeToRoll(nativeHandle_); - } - - @Override - public DBOptions setKeepLogFileNum( - final long keepLogFileNum) { - assert(isOwningHandle()); - setKeepLogFileNum(nativeHandle_, keepLogFileNum); - return this; - } - - @Override - public long keepLogFileNum() { - assert(isOwningHandle()); - return keepLogFileNum(nativeHandle_); - } - - @Override - public DBOptions setRecycleLogFileNum(final long recycleLogFileNum) { - assert(isOwningHandle()); - setRecycleLogFileNum(nativeHandle_, recycleLogFileNum); - return this; - } - - @Override - public long recycleLogFileNum() { - assert(isOwningHandle()); - return recycleLogFileNum(nativeHandle_); - } - - @Override - public DBOptions setMaxManifestFileSize( - final long maxManifestFileSize) { - assert(isOwningHandle()); - setMaxManifestFileSize(nativeHandle_, maxManifestFileSize); - return this; - } - - @Override - public long maxManifestFileSize() { - assert(isOwningHandle()); - return maxManifestFileSize(nativeHandle_); - } - - @Override - public DBOptions setTableCacheNumshardbits( - final int tableCacheNumshardbits) { - assert(isOwningHandle()); - setTableCacheNumshardbits(nativeHandle_, tableCacheNumshardbits); - return this; - } - - @Override - public int tableCacheNumshardbits() { - assert(isOwningHandle()); - return tableCacheNumshardbits(nativeHandle_); - } - - @Override - public DBOptions setWalTtlSeconds( - final long walTtlSeconds) { - assert(isOwningHandle()); - setWalTtlSeconds(nativeHandle_, walTtlSeconds); - return this; - } - - @Override - public long walTtlSeconds() { - assert(isOwningHandle()); - return walTtlSeconds(nativeHandle_); - } - - @Override - public DBOptions setWalSizeLimitMB( - final long sizeLimitMB) { - assert(isOwningHandle()); - setWalSizeLimitMB(nativeHandle_, sizeLimitMB); - return this; - } - - @Override - public long walSizeLimitMB() { - assert(isOwningHandle()); - return walSizeLimitMB(nativeHandle_); - } - - @Override - public DBOptions setMaxWriteBatchGroupSizeBytes(final long maxWriteBatchGroupSizeBytes) { - setMaxWriteBatchGroupSizeBytes(nativeHandle_, maxWriteBatchGroupSizeBytes); - return this; - } - - @Override - public long maxWriteBatchGroupSizeBytes() { - assert (isOwningHandle()); - return maxWriteBatchGroupSizeBytes(nativeHandle_); - } - - @Override - public DBOptions setManifestPreallocationSize( - final long size) { - assert(isOwningHandle()); - setManifestPreallocationSize(nativeHandle_, size); - return this; - } - - @Override - public long manifestPreallocationSize() { - assert(isOwningHandle()); - return manifestPreallocationSize(nativeHandle_); - } - - @Override - public DBOptions setAllowMmapReads( - final boolean allowMmapReads) { - assert(isOwningHandle()); - setAllowMmapReads(nativeHandle_, allowMmapReads); - return this; - } - - @Override - public boolean allowMmapReads() { - assert(isOwningHandle()); - return allowMmapReads(nativeHandle_); - } - - @Override - public DBOptions setAllowMmapWrites( - final boolean allowMmapWrites) { - assert(isOwningHandle()); - setAllowMmapWrites(nativeHandle_, allowMmapWrites); - return this; - } - - @Override - public boolean allowMmapWrites() { - assert(isOwningHandle()); - return allowMmapWrites(nativeHandle_); - } - - @Override - public DBOptions setUseDirectReads( - final boolean useDirectReads) { - assert(isOwningHandle()); - setUseDirectReads(nativeHandle_, useDirectReads); - return this; - } - - @Override - public boolean useDirectReads() { - assert(isOwningHandle()); - return useDirectReads(nativeHandle_); - } - - @Override - public DBOptions setUseDirectIoForFlushAndCompaction( - final boolean useDirectIoForFlushAndCompaction) { - assert(isOwningHandle()); - setUseDirectIoForFlushAndCompaction(nativeHandle_, - useDirectIoForFlushAndCompaction); - return this; - } - - @Override - public boolean useDirectIoForFlushAndCompaction() { - assert(isOwningHandle()); - return useDirectIoForFlushAndCompaction(nativeHandle_); - } - - @Override - public DBOptions setAllowFAllocate(final boolean allowFAllocate) { - assert(isOwningHandle()); - setAllowFAllocate(nativeHandle_, allowFAllocate); - return this; - } - - @Override - public boolean allowFAllocate() { - assert(isOwningHandle()); - return allowFAllocate(nativeHandle_); - } - - @Override - public DBOptions setIsFdCloseOnExec( - final boolean isFdCloseOnExec) { - assert(isOwningHandle()); - setIsFdCloseOnExec(nativeHandle_, isFdCloseOnExec); - return this; - } - - @Override - public boolean isFdCloseOnExec() { - assert(isOwningHandle()); - return isFdCloseOnExec(nativeHandle_); - } - - @Override - public DBOptions setStatsDumpPeriodSec( - final int statsDumpPeriodSec) { - assert(isOwningHandle()); - setStatsDumpPeriodSec(nativeHandle_, statsDumpPeriodSec); - return this; - } - - @Override - public int statsDumpPeriodSec() { - assert(isOwningHandle()); - return statsDumpPeriodSec(nativeHandle_); - } - - @Override - public DBOptions setStatsPersistPeriodSec( - final int statsPersistPeriodSec) { - assert(isOwningHandle()); - setStatsPersistPeriodSec(nativeHandle_, statsPersistPeriodSec); - return this; - } - - @Override - public int statsPersistPeriodSec() { - assert(isOwningHandle()); - return statsPersistPeriodSec(nativeHandle_); - } - - @Override - public DBOptions setStatsHistoryBufferSize( - final long statsHistoryBufferSize) { - assert(isOwningHandle()); - setStatsHistoryBufferSize(nativeHandle_, statsHistoryBufferSize); - return this; - } - - @Override - public long statsHistoryBufferSize() { - assert(isOwningHandle()); - return statsHistoryBufferSize(nativeHandle_); - } - - @Override - public DBOptions setAdviseRandomOnOpen( - final boolean adviseRandomOnOpen) { - assert(isOwningHandle()); - setAdviseRandomOnOpen(nativeHandle_, adviseRandomOnOpen); - return this; - } - - @Override - public boolean adviseRandomOnOpen() { - return adviseRandomOnOpen(nativeHandle_); - } - - @Override - public DBOptions setDbWriteBufferSize(final long dbWriteBufferSize) { - assert(isOwningHandle()); - setDbWriteBufferSize(nativeHandle_, dbWriteBufferSize); - return this; - } - - @Override - public DBOptions setWriteBufferManager(final WriteBufferManager writeBufferManager) { - assert(isOwningHandle()); - setWriteBufferManager(nativeHandle_, writeBufferManager.nativeHandle_); - this.writeBufferManager_ = writeBufferManager; - return this; - } - - @Override - public WriteBufferManager writeBufferManager() { - assert(isOwningHandle()); - return this.writeBufferManager_; - } - - @Override - public long dbWriteBufferSize() { - assert(isOwningHandle()); - return dbWriteBufferSize(nativeHandle_); - } - - @Override - public DBOptions setAccessHintOnCompactionStart(final AccessHint accessHint) { - assert(isOwningHandle()); - setAccessHintOnCompactionStart(nativeHandle_, accessHint.getValue()); - return this; - } - - @Override - public AccessHint accessHintOnCompactionStart() { - assert(isOwningHandle()); - return AccessHint.getAccessHint(accessHintOnCompactionStart(nativeHandle_)); - } - - @Override - public DBOptions setCompactionReadaheadSize(final long compactionReadaheadSize) { - assert(isOwningHandle()); - setCompactionReadaheadSize(nativeHandle_, compactionReadaheadSize); - return this; - } - - @Override - public long compactionReadaheadSize() { - assert(isOwningHandle()); - return compactionReadaheadSize(nativeHandle_); - } - - @Override - public DBOptions setRandomAccessMaxBufferSize(final long randomAccessMaxBufferSize) { - assert(isOwningHandle()); - setRandomAccessMaxBufferSize(nativeHandle_, randomAccessMaxBufferSize); - return this; - } - - @Override - public long randomAccessMaxBufferSize() { - assert(isOwningHandle()); - return randomAccessMaxBufferSize(nativeHandle_); - } - - @Override - public DBOptions setWritableFileMaxBufferSize(final long writableFileMaxBufferSize) { - assert(isOwningHandle()); - setWritableFileMaxBufferSize(nativeHandle_, writableFileMaxBufferSize); - return this; - } - - @Override - public long writableFileMaxBufferSize() { - assert(isOwningHandle()); - return writableFileMaxBufferSize(nativeHandle_); - } - - @Override - public DBOptions setUseAdaptiveMutex( - final boolean useAdaptiveMutex) { - assert(isOwningHandle()); - setUseAdaptiveMutex(nativeHandle_, useAdaptiveMutex); - return this; - } - - @Override - public boolean useAdaptiveMutex() { - assert(isOwningHandle()); - return useAdaptiveMutex(nativeHandle_); - } - - @Override - public DBOptions setBytesPerSync( - final long bytesPerSync) { - assert(isOwningHandle()); - setBytesPerSync(nativeHandle_, bytesPerSync); - return this; - } - - @Override - public long bytesPerSync() { - return bytesPerSync(nativeHandle_); - } - - @Override - public DBOptions setWalBytesPerSync(final long walBytesPerSync) { - assert(isOwningHandle()); - setWalBytesPerSync(nativeHandle_, walBytesPerSync); - return this; - } - - @Override - public long walBytesPerSync() { - assert(isOwningHandle()); - return walBytesPerSync(nativeHandle_); - } - - @Override - public DBOptions setStrictBytesPerSync(final boolean strictBytesPerSync) { - assert(isOwningHandle()); - setStrictBytesPerSync(nativeHandle_, strictBytesPerSync); - return this; - } - - @Override - public boolean strictBytesPerSync() { - assert(isOwningHandle()); - return strictBytesPerSync(nativeHandle_); - } - - @Override - public DBOptions setListeners(final List listeners) { - assert (isOwningHandle()); - setEventListeners(nativeHandle_, RocksCallbackObject.toNativeHandleList(listeners)); - return this; - } - - @Override - public List listeners() { - assert (isOwningHandle()); - return Arrays.asList(eventListeners(nativeHandle_)); - } - - @Override - public DBOptions setEnableThreadTracking(final boolean enableThreadTracking) { - assert(isOwningHandle()); - setEnableThreadTracking(nativeHandle_, enableThreadTracking); - return this; - } - - @Override - public boolean enableThreadTracking() { - assert(isOwningHandle()); - return enableThreadTracking(nativeHandle_); - } - - @Override - public DBOptions setDelayedWriteRate(final long delayedWriteRate) { - assert(isOwningHandle()); - setDelayedWriteRate(nativeHandle_, delayedWriteRate); - return this; - } - - @Override - public long delayedWriteRate(){ - return delayedWriteRate(nativeHandle_); - } - - @Override - public DBOptions setEnablePipelinedWrite(final boolean enablePipelinedWrite) { - assert(isOwningHandle()); - setEnablePipelinedWrite(nativeHandle_, enablePipelinedWrite); - return this; - } - - @Override - public boolean enablePipelinedWrite() { - assert(isOwningHandle()); - return enablePipelinedWrite(nativeHandle_); - } - - @Override - public DBOptions setUnorderedWrite(final boolean unorderedWrite) { - setUnorderedWrite(nativeHandle_, unorderedWrite); - return this; - } - - @Override - public boolean unorderedWrite() { - return unorderedWrite(nativeHandle_); - } - - - @Override - public DBOptions setAllowConcurrentMemtableWrite( - final boolean allowConcurrentMemtableWrite) { - setAllowConcurrentMemtableWrite(nativeHandle_, - allowConcurrentMemtableWrite); - return this; - } - - @Override - public boolean allowConcurrentMemtableWrite() { - return allowConcurrentMemtableWrite(nativeHandle_); - } - - @Override - public DBOptions setEnableWriteThreadAdaptiveYield( - final boolean enableWriteThreadAdaptiveYield) { - setEnableWriteThreadAdaptiveYield(nativeHandle_, - enableWriteThreadAdaptiveYield); - return this; - } - - @Override - public boolean enableWriteThreadAdaptiveYield() { - return enableWriteThreadAdaptiveYield(nativeHandle_); - } - - @Override - public DBOptions setWriteThreadMaxYieldUsec(final long writeThreadMaxYieldUsec) { - setWriteThreadMaxYieldUsec(nativeHandle_, writeThreadMaxYieldUsec); - return this; - } - - @Override - public long writeThreadMaxYieldUsec() { - return writeThreadMaxYieldUsec(nativeHandle_); - } - - @Override - public DBOptions setWriteThreadSlowYieldUsec(final long writeThreadSlowYieldUsec) { - setWriteThreadSlowYieldUsec(nativeHandle_, writeThreadSlowYieldUsec); - return this; - } - - @Override - public long writeThreadSlowYieldUsec() { - return writeThreadSlowYieldUsec(nativeHandle_); - } - - @Override - public DBOptions setSkipStatsUpdateOnDbOpen(final boolean skipStatsUpdateOnDbOpen) { - assert(isOwningHandle()); - setSkipStatsUpdateOnDbOpen(nativeHandle_, skipStatsUpdateOnDbOpen); - return this; - } - - @Override - public boolean skipStatsUpdateOnDbOpen() { - assert(isOwningHandle()); - return skipStatsUpdateOnDbOpen(nativeHandle_); - } - - @Override - public DBOptions setSkipCheckingSstFileSizesOnDbOpen( - final boolean skipCheckingSstFileSizesOnDbOpen) { - setSkipCheckingSstFileSizesOnDbOpen(nativeHandle_, skipCheckingSstFileSizesOnDbOpen); - return this; - } - - @Override - public boolean skipCheckingSstFileSizesOnDbOpen() { - assert (isOwningHandle()); - return skipCheckingSstFileSizesOnDbOpen(nativeHandle_); - } - - @Override - public DBOptions setWalRecoveryMode(final WALRecoveryMode walRecoveryMode) { - assert(isOwningHandle()); - setWalRecoveryMode(nativeHandle_, walRecoveryMode.getValue()); - return this; - } - - @Override - public WALRecoveryMode walRecoveryMode() { - assert(isOwningHandle()); - return WALRecoveryMode.getWALRecoveryMode(walRecoveryMode(nativeHandle_)); - } - - @Override - public DBOptions setAllow2pc(final boolean allow2pc) { - assert(isOwningHandle()); - setAllow2pc(nativeHandle_, allow2pc); - return this; - } - - @Override - public boolean allow2pc() { - assert(isOwningHandle()); - return allow2pc(nativeHandle_); - } - - @Override - public DBOptions setRowCache(final Cache rowCache) { - assert(isOwningHandle()); - setRowCache(nativeHandle_, rowCache.nativeHandle_); - this.rowCache_ = rowCache; - return this; - } - - @Override - public Cache rowCache() { - assert(isOwningHandle()); - return this.rowCache_; - } - - @Override - public DBOptions setWalFilter(final AbstractWalFilter walFilter) { - assert(isOwningHandle()); - setWalFilter(nativeHandle_, walFilter.nativeHandle_); - this.walFilter_ = walFilter; - return this; - } - - @Override - public WalFilter walFilter() { - assert(isOwningHandle()); - return this.walFilter_; - } - - @Override - public DBOptions setFailIfOptionsFileError(final boolean failIfOptionsFileError) { - assert(isOwningHandle()); - setFailIfOptionsFileError(nativeHandle_, failIfOptionsFileError); - return this; - } - - @Override - public boolean failIfOptionsFileError() { - assert(isOwningHandle()); - return failIfOptionsFileError(nativeHandle_); - } - - @Override - public DBOptions setDumpMallocStats(final boolean dumpMallocStats) { - assert(isOwningHandle()); - setDumpMallocStats(nativeHandle_, dumpMallocStats); - return this; - } - - @Override - public boolean dumpMallocStats() { - assert(isOwningHandle()); - return dumpMallocStats(nativeHandle_); - } - - @Override - public DBOptions setAvoidFlushDuringRecovery(final boolean avoidFlushDuringRecovery) { - assert(isOwningHandle()); - setAvoidFlushDuringRecovery(nativeHandle_, avoidFlushDuringRecovery); - return this; - } - - @Override - public boolean avoidFlushDuringRecovery() { - assert(isOwningHandle()); - return avoidFlushDuringRecovery(nativeHandle_); - } - - @Override - public DBOptions setAvoidFlushDuringShutdown(final boolean avoidFlushDuringShutdown) { - assert(isOwningHandle()); - setAvoidFlushDuringShutdown(nativeHandle_, avoidFlushDuringShutdown); - return this; - } - - @Override - public boolean avoidFlushDuringShutdown() { - assert(isOwningHandle()); - return avoidFlushDuringShutdown(nativeHandle_); - } - - @Override - public DBOptions setAllowIngestBehind(final boolean allowIngestBehind) { - assert(isOwningHandle()); - setAllowIngestBehind(nativeHandle_, allowIngestBehind); - return this; - } - - @Override - public boolean allowIngestBehind() { - assert(isOwningHandle()); - return allowIngestBehind(nativeHandle_); - } - - @Override - public DBOptions setTwoWriteQueues(final boolean twoWriteQueues) { - assert(isOwningHandle()); - setTwoWriteQueues(nativeHandle_, twoWriteQueues); - return this; - } - - @Override - public boolean twoWriteQueues() { - assert(isOwningHandle()); - return twoWriteQueues(nativeHandle_); - } - - @Override - public DBOptions setManualWalFlush(final boolean manualWalFlush) { - assert(isOwningHandle()); - setManualWalFlush(nativeHandle_, manualWalFlush); - return this; - } - - @Override - public boolean manualWalFlush() { - assert(isOwningHandle()); - return manualWalFlush(nativeHandle_); - } - - @Override - public DBOptions setAtomicFlush(final boolean atomicFlush) { - setAtomicFlush(nativeHandle_, atomicFlush); - return this; - } - - @Override - public boolean atomicFlush() { - return atomicFlush(nativeHandle_); - } - - @Override - public DBOptions setAvoidUnnecessaryBlockingIO(final boolean avoidUnnecessaryBlockingIO) { - setAvoidUnnecessaryBlockingIO(nativeHandle_, avoidUnnecessaryBlockingIO); - return this; - } - - @Override - public boolean avoidUnnecessaryBlockingIO() { - assert (isOwningHandle()); - return avoidUnnecessaryBlockingIO(nativeHandle_); - } - - @Override - public DBOptions setPersistStatsToDisk(final boolean persistStatsToDisk) { - setPersistStatsToDisk(nativeHandle_, persistStatsToDisk); - return this; - } - - @Override - public boolean persistStatsToDisk() { - assert (isOwningHandle()); - return persistStatsToDisk(nativeHandle_); - } - - @Override - public DBOptions setWriteDbidToManifest(final boolean writeDbidToManifest) { - setWriteDbidToManifest(nativeHandle_, writeDbidToManifest); - return this; - } - - @Override - public boolean writeDbidToManifest() { - assert (isOwningHandle()); - return writeDbidToManifest(nativeHandle_); - } - - @Override - public DBOptions setLogReadaheadSize(final long logReadaheadSize) { - setLogReadaheadSize(nativeHandle_, logReadaheadSize); - return this; - } - - @Override - public long logReadaheadSize() { - assert (isOwningHandle()); - return logReadaheadSize(nativeHandle_); - } - - @Override - public DBOptions setBestEffortsRecovery(final boolean bestEffortsRecovery) { - setBestEffortsRecovery(nativeHandle_, bestEffortsRecovery); - return this; - } - - @Override - public boolean bestEffortsRecovery() { - assert (isOwningHandle()); - return bestEffortsRecovery(nativeHandle_); - } - - @Override - public DBOptions setMaxBgErrorResumeCount(final int maxBgerrorResumeCount) { - setMaxBgErrorResumeCount(nativeHandle_, maxBgerrorResumeCount); - return this; - } - - @Override - public int maxBgerrorResumeCount() { - assert (isOwningHandle()); - return maxBgerrorResumeCount(nativeHandle_); - } - - @Override - public DBOptions setBgerrorResumeRetryInterval(final long bgerrorResumeRetryInterval) { - setBgerrorResumeRetryInterval(nativeHandle_, bgerrorResumeRetryInterval); - return this; - } - - @Override - public long bgerrorResumeRetryInterval() { - assert (isOwningHandle()); - return bgerrorResumeRetryInterval(nativeHandle_); - } - - static final int DEFAULT_NUM_SHARD_BITS = -1; - - - - - /** - *

    Private constructor to be used by - * {@link #getDBOptionsFromProps(java.util.Properties)}

    - * - * @param nativeHandle native handle to DBOptions instance. - */ - private DBOptions(final long nativeHandle) { - super(nativeHandle); - } - - private static native long getDBOptionsFromProps(long cfgHandle, String optString); - private static native long getDBOptionsFromProps(String optString); - - private static native long newDBOptions(); - private static native long copyDBOptions(final long handle); - private static native long newDBOptionsFromOptions(final long optionsHandle); - @Override protected final native void disposeInternal(final long handle); - - private native void optimizeForSmallDb(final long handle); - private native void setIncreaseParallelism(long handle, int totalThreads); - private native void setCreateIfMissing(long handle, boolean flag); - private native boolean createIfMissing(long handle); - private native void setCreateMissingColumnFamilies( - long handle, boolean flag); - private native boolean createMissingColumnFamilies(long handle); - private native void setEnv(long handle, long envHandle); - private native void setErrorIfExists(long handle, boolean errorIfExists); - private native boolean errorIfExists(long handle); - private native void setParanoidChecks( - long handle, boolean paranoidChecks); - private native boolean paranoidChecks(long handle); - private native void setRateLimiter(long handle, - long rateLimiterHandle); - private native void setSstFileManager(final long handle, - final long sstFileManagerHandle); - private native void setLogger(long handle, - long loggerHandle); - private native void setInfoLogLevel(long handle, byte logLevel); - private native byte infoLogLevel(long handle); - private native void setMaxOpenFiles(long handle, int maxOpenFiles); - private native int maxOpenFiles(long handle); - private native void setMaxFileOpeningThreads(final long handle, - final int maxFileOpeningThreads); - private native int maxFileOpeningThreads(final long handle); - private native void setMaxTotalWalSize(long handle, - long maxTotalWalSize); - private native long maxTotalWalSize(long handle); - private native void setStatistics(final long handle, final long statisticsHandle); - private native long statistics(final long handle); - private native boolean useFsync(long handle); - private native void setUseFsync(long handle, boolean useFsync); - private native void setDbPaths(final long handle, final String[] paths, - final long[] targetSizes); - private native long dbPathsLen(final long handle); - private native void dbPaths(final long handle, final String[] paths, - final long[] targetSizes); - private native void setDbLogDir(long handle, String dbLogDir); - private native String dbLogDir(long handle); - private native void setWalDir(long handle, String walDir); - private native String walDir(long handle); - private native void setDeleteObsoleteFilesPeriodMicros( - long handle, long micros); - private native long deleteObsoleteFilesPeriodMicros(long handle); - private native void setMaxBackgroundCompactions( - long handle, int maxBackgroundCompactions); - private native int maxBackgroundCompactions(long handle); - private native void setMaxSubcompactions(long handle, int maxSubcompactions); - private native int maxSubcompactions(long handle); - private native void setMaxBackgroundFlushes( - long handle, int maxBackgroundFlushes); - private native int maxBackgroundFlushes(long handle); - private native void setMaxBackgroundJobs(long handle, int maxBackgroundJobs); - private native int maxBackgroundJobs(long handle); - private native void setMaxLogFileSize(long handle, long maxLogFileSize) - throws IllegalArgumentException; - private native long maxLogFileSize(long handle); - private native void setLogFileTimeToRoll( - long handle, long logFileTimeToRoll) throws IllegalArgumentException; - private native long logFileTimeToRoll(long handle); - private native void setKeepLogFileNum(long handle, long keepLogFileNum) - throws IllegalArgumentException; - private native long keepLogFileNum(long handle); - private native void setRecycleLogFileNum(long handle, long recycleLogFileNum); - private native long recycleLogFileNum(long handle); - private native void setMaxManifestFileSize( - long handle, long maxManifestFileSize); - private native long maxManifestFileSize(long handle); - private native void setTableCacheNumshardbits( - long handle, int tableCacheNumshardbits); - private native int tableCacheNumshardbits(long handle); - private native void setWalTtlSeconds(long handle, long walTtlSeconds); - private native long walTtlSeconds(long handle); - private native void setWalSizeLimitMB(long handle, long sizeLimitMB); - private native long walSizeLimitMB(long handle); - private static native void setMaxWriteBatchGroupSizeBytes( - final long handle, final long maxWriteBatchGroupSizeBytes); - private static native long maxWriteBatchGroupSizeBytes(final long handle); - private native void setManifestPreallocationSize( - long handle, long size) throws IllegalArgumentException; - private native long manifestPreallocationSize(long handle); - private native void setUseDirectReads(long handle, boolean useDirectReads); - private native boolean useDirectReads(long handle); - private native void setUseDirectIoForFlushAndCompaction( - long handle, boolean useDirectIoForFlushAndCompaction); - private native boolean useDirectIoForFlushAndCompaction(long handle); - private native void setAllowFAllocate(final long handle, - final boolean allowFAllocate); - private native boolean allowFAllocate(final long handle); - private native void setAllowMmapReads( - long handle, boolean allowMmapReads); - private native boolean allowMmapReads(long handle); - private native void setAllowMmapWrites( - long handle, boolean allowMmapWrites); - private native boolean allowMmapWrites(long handle); - private native void setIsFdCloseOnExec( - long handle, boolean isFdCloseOnExec); - private native boolean isFdCloseOnExec(long handle); - private native void setStatsDumpPeriodSec( - long handle, int statsDumpPeriodSec); - private native int statsDumpPeriodSec(long handle); - private native void setStatsPersistPeriodSec( - final long handle, final int statsPersistPeriodSec); - private native int statsPersistPeriodSec( - final long handle); - private native void setStatsHistoryBufferSize( - final long handle, final long statsHistoryBufferSize); - private native long statsHistoryBufferSize( - final long handle); - private native void setAdviseRandomOnOpen( - long handle, boolean adviseRandomOnOpen); - private native boolean adviseRandomOnOpen(long handle); - private native void setDbWriteBufferSize(final long handle, - final long dbWriteBufferSize); - private native void setWriteBufferManager(final long dbOptionsHandle, - final long writeBufferManagerHandle); - private native long dbWriteBufferSize(final long handle); - private native void setAccessHintOnCompactionStart(final long handle, - final byte accessHintOnCompactionStart); - private native byte accessHintOnCompactionStart(final long handle); - private native void setCompactionReadaheadSize(final long handle, - final long compactionReadaheadSize); - private native long compactionReadaheadSize(final long handle); - private native void setRandomAccessMaxBufferSize(final long handle, - final long randomAccessMaxBufferSize); - private native long randomAccessMaxBufferSize(final long handle); - private native void setWritableFileMaxBufferSize(final long handle, - final long writableFileMaxBufferSize); - private native long writableFileMaxBufferSize(final long handle); - private native void setUseAdaptiveMutex( - long handle, boolean useAdaptiveMutex); - private native boolean useAdaptiveMutex(long handle); - private native void setBytesPerSync( - long handle, long bytesPerSync); - private native long bytesPerSync(long handle); - private native void setWalBytesPerSync(long handle, long walBytesPerSync); - private native long walBytesPerSync(long handle); - private native void setStrictBytesPerSync( - final long handle, final boolean strictBytesPerSync); - private native boolean strictBytesPerSync( - final long handle); - private static native void setEventListeners( - final long handle, final long[] eventListenerHandles); - private static native AbstractEventListener[] eventListeners(final long handle); - private native void setEnableThreadTracking(long handle, - boolean enableThreadTracking); - private native boolean enableThreadTracking(long handle); - private native void setDelayedWriteRate(long handle, long delayedWriteRate); - private native long delayedWriteRate(long handle); - private native void setEnablePipelinedWrite(final long handle, - final boolean enablePipelinedWrite); - private native boolean enablePipelinedWrite(final long handle); - private native void setUnorderedWrite(final long handle, - final boolean unorderedWrite); - private native boolean unorderedWrite(final long handle); - private native void setAllowConcurrentMemtableWrite(long handle, - boolean allowConcurrentMemtableWrite); - private native boolean allowConcurrentMemtableWrite(long handle); - private native void setEnableWriteThreadAdaptiveYield(long handle, - boolean enableWriteThreadAdaptiveYield); - private native boolean enableWriteThreadAdaptiveYield(long handle); - private native void setWriteThreadMaxYieldUsec(long handle, - long writeThreadMaxYieldUsec); - private native long writeThreadMaxYieldUsec(long handle); - private native void setWriteThreadSlowYieldUsec(long handle, - long writeThreadSlowYieldUsec); - private native long writeThreadSlowYieldUsec(long handle); - private native void setSkipStatsUpdateOnDbOpen(final long handle, - final boolean skipStatsUpdateOnDbOpen); - private native boolean skipStatsUpdateOnDbOpen(final long handle); - private static native void setSkipCheckingSstFileSizesOnDbOpen( - final long handle, final boolean skipChecking); - private static native boolean skipCheckingSstFileSizesOnDbOpen(final long handle); - private native void setWalRecoveryMode(final long handle, - final byte walRecoveryMode); - private native byte walRecoveryMode(final long handle); - private native void setAllow2pc(final long handle, - final boolean allow2pc); - private native boolean allow2pc(final long handle); - private native void setRowCache(final long handle, - final long rowCacheHandle); - private native void setWalFilter(final long handle, - final long walFilterHandle); - private native void setFailIfOptionsFileError(final long handle, - final boolean failIfOptionsFileError); - private native boolean failIfOptionsFileError(final long handle); - private native void setDumpMallocStats(final long handle, - final boolean dumpMallocStats); - private native boolean dumpMallocStats(final long handle); - private native void setAvoidFlushDuringRecovery(final long handle, - final boolean avoidFlushDuringRecovery); - private native boolean avoidFlushDuringRecovery(final long handle); - private native void setAvoidFlushDuringShutdown(final long handle, - final boolean avoidFlushDuringShutdown); - private native boolean avoidFlushDuringShutdown(final long handle); - private native void setAllowIngestBehind(final long handle, - final boolean allowIngestBehind); - private native boolean allowIngestBehind(final long handle); - private native void setTwoWriteQueues(final long handle, - final boolean twoWriteQueues); - private native boolean twoWriteQueues(final long handle); - private native void setManualWalFlush(final long handle, - final boolean manualWalFlush); - private native boolean manualWalFlush(final long handle); - private native void setAtomicFlush(final long handle, - final boolean atomicFlush); - private native boolean atomicFlush(final long handle); - private static native void setAvoidUnnecessaryBlockingIO( - final long handle, final boolean avoidBlockingIO); - private static native boolean avoidUnnecessaryBlockingIO(final long handle); - private static native void setPersistStatsToDisk( - final long handle, final boolean persistStatsToDisk); - private static native boolean persistStatsToDisk(final long handle); - private static native void setWriteDbidToManifest( - final long handle, final boolean writeDbidToManifest); - private static native boolean writeDbidToManifest(final long handle); - private static native void setLogReadaheadSize(final long handle, final long logReadaheadSize); - private static native long logReadaheadSize(final long handle); - private static native void setBestEffortsRecovery( - final long handle, final boolean bestEffortsRecovery); - private static native boolean bestEffortsRecovery(final long handle); - private static native void setMaxBgErrorResumeCount( - final long handle, final int maxBgerrorRecumeCount); - private static native int maxBgerrorResumeCount(final long handle); - private static native void setBgerrorResumeRetryInterval( - final long handle, final long bgerrorResumeRetryInterval); - private static native long bgerrorResumeRetryInterval(final long handle); - - // instance variables - // NOTE: If you add new member variables, please update the copy constructor above! - private Env env_; - private int numShardBits_; - private RateLimiter rateLimiter_; - private Cache rowCache_; - private WalFilter walFilter_; - private WriteBufferManager writeBufferManager_; -} diff --git a/java/src/main/java/org/rocksdb/DBOptionsInterface.java b/java/src/main/java/org/rocksdb/DBOptionsInterface.java deleted file mode 100644 index ef1b86bff..000000000 --- a/java/src/main/java/org/rocksdb/DBOptionsInterface.java +++ /dev/null @@ -1,1756 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Collection; -import java.util.List; - -public interface DBOptionsInterface> { - /** - * Use this if your DB is very small (like under 1GB) and you don't want to - * spend lots of memory for memtables. - * - * @return the instance of the current object. - */ - T optimizeForSmallDb(); - - /** - * Use the specified object to interact with the environment, - * e.g. to read/write files, schedule background work, etc. - * Default: {@link Env#getDefault()} - * - * @param env {@link Env} instance. - * @return the instance of the current Options. - */ - T setEnv(final Env env); - - /** - * Returns the set RocksEnv instance. - * - * @return {@link RocksEnv} instance set in the options. - */ - Env getEnv(); - - /** - *

    By default, RocksDB uses only one background thread for flush and - * compaction. Calling this function will set it up such that total of - * `total_threads` is used.

    - * - *

    You almost definitely want to call this function if your system is - * bottlenecked by RocksDB.

    - * - * @param totalThreads The total number of threads to be used by RocksDB. - * A good value is the number of cores. - * - * @return the instance of the current Options - */ - T setIncreaseParallelism(int totalThreads); - - /** - * If this value is set to true, then the database will be created - * if it is missing during {@code RocksDB.open()}. - * Default: false - * - * @param flag a flag indicating whether to create a database the - * specified database in {@link RocksDB#open(org.rocksdb.Options, String)} operation - * is missing. - * @return the instance of the current Options - * @see RocksDB#open(org.rocksdb.Options, String) - */ - T setCreateIfMissing(boolean flag); - - /** - * Return true if the create_if_missing flag is set to true. - * If true, the database will be created if it is missing. - * - * @return true if the createIfMissing option is set to true. - * @see #setCreateIfMissing(boolean) - */ - boolean createIfMissing(); - - /** - *

    If true, missing column families will be automatically created

    - * - *

    Default: false

    - * - * @param flag a flag indicating if missing column families shall be - * created automatically. - * @return true if missing column families shall be created automatically - * on open. - */ - T setCreateMissingColumnFamilies(boolean flag); - - /** - * Return true if the create_missing_column_families flag is set - * to true. If true column families be created if missing. - * - * @return true if the createMissingColumnFamilies is set to - * true. - * @see #setCreateMissingColumnFamilies(boolean) - */ - boolean createMissingColumnFamilies(); - - /** - * If true, an error will be thrown during RocksDB.open() if the - * database already exists. - * Default: false - * - * @param errorIfExists if true, an exception will be thrown - * during {@code RocksDB.open()} if the database already exists. - * @return the reference to the current option. - * @see RocksDB#open(org.rocksdb.Options, String) - */ - T setErrorIfExists(boolean errorIfExists); - - /** - * If true, an error will be thrown during RocksDB.open() if the - * database already exists. - * - * @return if true, an error is raised when the specified database - * already exists before open. - */ - boolean errorIfExists(); - - /** - * If true, the implementation will do aggressive checking of the - * data it is processing and will stop early if it detects any - * errors. This may have unforeseen ramifications: for example, a - * corruption of one DB entry may cause a large number of entries to - * become unreadable or for the entire DB to become unopenable. - * If any of the writes to the database fails (Put, Delete, Merge, Write), - * the database will switch to read-only mode and fail all other - * Write operations. - * Default: true - * - * @param paranoidChecks a flag to indicate whether paranoid-check - * is on. - * @return the reference to the current option. - */ - T setParanoidChecks(boolean paranoidChecks); - - /** - * If true, the implementation will do aggressive checking of the - * data it is processing and will stop early if it detects any - * errors. This may have unforeseen ramifications: for example, a - * corruption of one DB entry may cause a large number of entries to - * become unreadable or for the entire DB to become unopenable. - * If any of the writes to the database fails (Put, Delete, Merge, Write), - * the database will switch to read-only mode and fail all other - * Write operations. - * - * @return a boolean indicating whether paranoid-check is on. - */ - boolean paranoidChecks(); - - /** - * Use to control write rate of flush and compaction. Flush has higher - * priority than compaction. Rate limiting is disabled if nullptr. - * Default: nullptr - * - * @param rateLimiter {@link org.rocksdb.RateLimiter} instance. - * @return the instance of the current object. - * - * @since 3.10.0 - */ - T setRateLimiter(RateLimiter rateLimiter); - - /** - * Use to track SST files and control their file deletion rate. - * - * Features: - * - Throttle the deletion rate of the SST files. - * - Keep track the total size of all SST files. - * - Set a maximum allowed space limit for SST files that when reached - * the DB wont do any further flushes or compactions and will set the - * background error. - * - Can be shared between multiple dbs. - * - * Limitations: - * - Only track and throttle deletes of SST files in - * first db_path (db_name if db_paths is empty). - * - * @param sstFileManager The SST File Manager for the db. - * @return the instance of the current object. - */ - T setSstFileManager(SstFileManager sstFileManager); - - /** - *

    Any internal progress/error information generated by - * the db will be written to the Logger if it is non-nullptr, - * or to a file stored in the same directory as the DB - * contents if info_log is nullptr.

    - * - *

    Default: nullptr

    - * - * @param logger {@link Logger} instance. - * @return the instance of the current object. - */ - T setLogger(Logger logger); - - /** - *

    Sets the RocksDB log level. Default level is INFO

    - * - * @param infoLogLevel log level to set. - * @return the instance of the current object. - */ - T setInfoLogLevel(InfoLogLevel infoLogLevel); - - /** - *

    Returns currently set log level.

    - * @return {@link org.rocksdb.InfoLogLevel} instance. - */ - InfoLogLevel infoLogLevel(); - - /** - * If {@link MutableDBOptionsInterface#maxOpenFiles()} is -1, DB will open - * all files on DB::Open(). You can use this option to increase the number - * of threads used to open the files. - * - * Default: 16 - * - * @param maxFileOpeningThreads the maximum number of threads to use to - * open files - * - * @return the reference to the current options. - */ - T setMaxFileOpeningThreads(int maxFileOpeningThreads); - - /** - * If {@link MutableDBOptionsInterface#maxOpenFiles()} is -1, DB will open all - * files on DB::Open(). You can use this option to increase the number of - * threads used to open the files. - * - * Default: 16 - * - * @return the maximum number of threads to use to open files - */ - int maxFileOpeningThreads(); - - /** - *

    Sets the statistics object which collects metrics about database operations. - * Statistics objects should not be shared between DB instances as - * it does not use any locks to prevent concurrent updates.

    - * - * @param statistics The statistics to set - * - * @return the instance of the current object. - * - * @see RocksDB#open(org.rocksdb.Options, String) - */ - T setStatistics(final Statistics statistics); - - /** - *

    Returns statistics object.

    - * - * @return the instance of the statistics object or null if there is no - * statistics object. - * - * @see #setStatistics(Statistics) - */ - Statistics statistics(); - - /** - *

    If true, then every store to stable storage will issue a fsync.

    - *

    If false, then every store to stable storage will issue a fdatasync. - * This parameter should be set to true while storing data to - * filesystem like ext3 that can lose files after a reboot.

    - *

    Default: false

    - * - * @param useFsync a boolean flag to specify whether to use fsync - * @return the instance of the current object. - */ - T setUseFsync(boolean useFsync); - - /** - *

    If true, then every store to stable storage will issue a fsync.

    - *

    If false, then every store to stable storage will issue a fdatasync. - * This parameter should be set to true while storing data to - * filesystem like ext3 that can lose files after a reboot.

    - * - * @return boolean value indicating if fsync is used. - */ - boolean useFsync(); - - /** - * A list of paths where SST files can be put into, with its target size. - * Newer data is placed into paths specified earlier in the vector while - * older data gradually moves to paths specified later in the vector. - * - * For example, you have a flash device with 10GB allocated for the DB, - * as well as a hard drive of 2TB, you should config it to be: - * [{"/flash_path", 10GB}, {"/hard_drive", 2TB}] - * - * The system will try to guarantee data under each path is close to but - * not larger than the target size. But current and future file sizes used - * by determining where to place a file are based on best-effort estimation, - * which means there is a chance that the actual size under the directory - * is slightly more than target size under some workloads. User should give - * some buffer room for those cases. - * - * If none of the paths has sufficient room to place a file, the file will - * be placed to the last path anyway, despite to the target size. - * - * Placing newer data to earlier paths is also best-efforts. User should - * expect user files to be placed in higher levels in some extreme cases. - * - * If left empty, only one path will be used, which is db_name passed when - * opening the DB. - * - * Default: empty - * - * @param dbPaths the paths and target sizes - * - * @return the reference to the current options - */ - T setDbPaths(final Collection dbPaths); - - /** - * A list of paths where SST files can be put into, with its target size. - * Newer data is placed into paths specified earlier in the vector while - * older data gradually moves to paths specified later in the vector. - * - * For example, you have a flash device with 10GB allocated for the DB, - * as well as a hard drive of 2TB, you should config it to be: - * [{"/flash_path", 10GB}, {"/hard_drive", 2TB}] - * - * The system will try to guarantee data under each path is close to but - * not larger than the target size. But current and future file sizes used - * by determining where to place a file are based on best-effort estimation, - * which means there is a chance that the actual size under the directory - * is slightly more than target size under some workloads. User should give - * some buffer room for those cases. - * - * If none of the paths has sufficient room to place a file, the file will - * be placed to the last path anyway, despite to the target size. - * - * Placing newer data to earlier paths is also best-efforts. User should - * expect user files to be placed in higher levels in some extreme cases. - * - * If left empty, only one path will be used, which is db_name passed when - * opening the DB. - * - * Default: {@link java.util.Collections#emptyList()} - * - * @return dbPaths the paths and target sizes - */ - List dbPaths(); - - /** - * This specifies the info LOG dir. - * If it is empty, the log files will be in the same dir as data. - * If it is non empty, the log files will be in the specified dir, - * and the db data dir's absolute path will be used as the log file - * name's prefix. - * - * @param dbLogDir the path to the info log directory - * @return the instance of the current object. - */ - T setDbLogDir(String dbLogDir); - - /** - * Returns the directory of info log. - * - * If it is empty, the log files will be in the same dir as data. - * If it is non empty, the log files will be in the specified dir, - * and the db data dir's absolute path will be used as the log file - * name's prefix. - * - * @return the path to the info log directory - */ - String dbLogDir(); - - /** - * This specifies the absolute dir path for write-ahead logs (WAL). - * If it is empty, the log files will be in the same dir as data, - * dbname is used as the data dir by default - * If it is non empty, the log files will be in kept the specified dir. - * When destroying the db, - * all log files in wal_dir and the dir itself is deleted - * - * @param walDir the path to the write-ahead-log directory. - * @return the instance of the current object. - */ - T setWalDir(String walDir); - - /** - * Returns the path to the write-ahead-logs (WAL) directory. - * - * If it is empty, the log files will be in the same dir as data, - * dbname is used as the data dir by default - * If it is non empty, the log files will be in kept the specified dir. - * When destroying the db, - * all log files in wal_dir and the dir itself is deleted - * - * @return the path to the write-ahead-logs (WAL) directory. - */ - String walDir(); - - /** - * The periodicity when obsolete files get deleted. The default - * value is 6 hours. The files that get out of scope by compaction - * process will still get automatically delete on every compaction, - * regardless of this setting - * - * @param micros the time interval in micros - * @return the instance of the current object. - */ - T setDeleteObsoleteFilesPeriodMicros(long micros); - - /** - * The periodicity when obsolete files get deleted. The default - * value is 6 hours. The files that get out of scope by compaction - * process will still get automatically delete on every compaction, - * regardless of this setting - * - * @return the time interval in micros when obsolete files will be deleted. - */ - long deleteObsoleteFilesPeriodMicros(); - - /** - * This value represents the maximum number of threads that will - * concurrently perform a compaction job by breaking it into multiple, - * smaller ones that are run simultaneously. - * Default: 1 (i.e. no subcompactions) - * - * @param maxSubcompactions The maximum number of threads that will - * concurrently perform a compaction job - * - * @return the instance of the current object. - */ - T setMaxSubcompactions(int maxSubcompactions); - - /** - * This value represents the maximum number of threads that will - * concurrently perform a compaction job by breaking it into multiple, - * smaller ones that are run simultaneously. - * Default: 1 (i.e. no subcompactions) - * - * @return The maximum number of threads that will concurrently perform a - * compaction job - */ - int maxSubcompactions(); - - /** - * NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the - * value of max_background_jobs. For backwards compatibility we will set - * `max_background_jobs = max_background_compactions + max_background_flushes` - * in the case where user sets at least one of `max_background_compactions` or - * `max_background_flushes`. - * - * Specifies the maximum number of concurrent background flush jobs. - * If you're increasing this, also consider increasing number of threads in - * HIGH priority thread pool. For more information, see - * Default: -1 - * - * @param maxBackgroundFlushes number of max concurrent flush jobs - * @return the instance of the current object. - * - * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, Priority) - * @see MutableDBOptionsInterface#maxBackgroundCompactions() - * - * @deprecated Use {@link MutableDBOptionsInterface#setMaxBackgroundJobs(int)} - */ - @Deprecated - T setMaxBackgroundFlushes(int maxBackgroundFlushes); - - /** - * NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the - * value of max_background_jobs. For backwards compatibility we will set - * `max_background_jobs = max_background_compactions + max_background_flushes` - * in the case where user sets at least one of `max_background_compactions` or - * `max_background_flushes`. - * - * Returns the maximum number of concurrent background flush jobs. - * If you're increasing this, also consider increasing number of threads in - * HIGH priority thread pool. For more information, see - * Default: -1 - * - * @return the maximum number of concurrent background flush jobs. - * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, Priority) - */ - @Deprecated - int maxBackgroundFlushes(); - - /** - * Specifies the maximum size of a info log file. If the current log file - * is larger than `max_log_file_size`, a new info log file will - * be created. - * If 0, all logs will be written to one log file. - * - * @param maxLogFileSize the maximum size of a info log file. - * @return the instance of the current object. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setMaxLogFileSize(long maxLogFileSize); - - /** - * Returns the maximum size of a info log file. If the current log file - * is larger than this size, a new info log file will be created. - * If 0, all logs will be written to one log file. - * - * @return the maximum size of the info log file. - */ - long maxLogFileSize(); - - /** - * Specifies the time interval for the info log file to roll (in seconds). - * If specified with non-zero value, log file will be rolled - * if it has been active longer than `log_file_time_to_roll`. - * Default: 0 (disabled) - * - * @param logFileTimeToRoll the time interval in seconds. - * @return the instance of the current object. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setLogFileTimeToRoll(long logFileTimeToRoll); - - /** - * Returns the time interval for the info log file to roll (in seconds). - * If specified with non-zero value, log file will be rolled - * if it has been active longer than `log_file_time_to_roll`. - * Default: 0 (disabled) - * - * @return the time interval in seconds. - */ - long logFileTimeToRoll(); - - /** - * Specifies the maximum number of info log files to be kept. - * Default: 1000 - * - * @param keepLogFileNum the maximum number of info log files to be kept. - * @return the instance of the current object. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setKeepLogFileNum(long keepLogFileNum); - - /** - * Returns the maximum number of info log files to be kept. - * Default: 1000 - * - * @return the maximum number of info log files to be kept. - */ - long keepLogFileNum(); - - /** - * Recycle log files. - * - * If non-zero, we will reuse previously written log files for new - * logs, overwriting the old data. The value indicates how many - * such files we will keep around at any point in time for later - * use. - * - * This is more efficient because the blocks are already - * allocated and fdatasync does not need to update the inode after - * each write. - * - * Default: 0 - * - * @param recycleLogFileNum the number of log files to keep for recycling - * - * @return the reference to the current options - */ - T setRecycleLogFileNum(long recycleLogFileNum); - - /** - * Recycle log files. - * - * If non-zero, we will reuse previously written log files for new - * logs, overwriting the old data. The value indicates how many - * such files we will keep around at any point in time for later - * use. - * - * This is more efficient because the blocks are already - * allocated and fdatasync does not need to update the inode after - * each write. - * - * Default: 0 - * - * @return the number of log files kept for recycling - */ - long recycleLogFileNum(); - - /** - * Manifest file is rolled over on reaching this limit. - * The older manifest file be deleted. - * The default value is 1GB so that the manifest file can grow, but not - * reach the limit of storage capacity. - * - * @param maxManifestFileSize the size limit of a manifest file. - * @return the instance of the current object. - */ - T setMaxManifestFileSize(long maxManifestFileSize); - - /** - * Manifest file is rolled over on reaching this limit. - * The older manifest file be deleted. - * The default value is 1GB so that the manifest file can grow, but not - * reach the limit of storage capacity. - * - * @return the size limit of a manifest file. - */ - long maxManifestFileSize(); - - /** - * Number of shards used for table cache. - * - * @param tableCacheNumshardbits the number of chards - * @return the instance of the current object. - */ - T setTableCacheNumshardbits(int tableCacheNumshardbits); - - /** - * Number of shards used for table cache. - * - * @return the number of shards used for table cache. - */ - int tableCacheNumshardbits(); - - /** - * {@link #walTtlSeconds()} and {@link #walSizeLimitMB()} affect how archived logs - * will be deleted. - *
      - *
    1. If both set to 0, logs will be deleted asap and will not get into - * the archive.
    2. - *
    3. If WAL_ttl_seconds is 0 and WAL_size_limit_MB is not 0, - * WAL files will be checked every 10 min and if total size is greater - * then WAL_size_limit_MB, they will be deleted starting with the - * earliest until size_limit is met. All empty files will be deleted.
    4. - *
    5. If WAL_ttl_seconds is not 0 and WAL_size_limit_MB is 0, then - * WAL files will be checked every WAL_ttl_seconds / 2 and those that - * are older than WAL_ttl_seconds will be deleted.
    6. - *
    7. If both are not 0, WAL files will be checked every 10 min and both - * checks will be performed with ttl being first.
    8. - *
    - * - * @param walTtlSeconds the ttl seconds - * @return the instance of the current object. - * @see #setWalSizeLimitMB(long) - */ - T setWalTtlSeconds(long walTtlSeconds); - - /** - * WalTtlSeconds() and walSizeLimitMB() affect how archived logs - * will be deleted. - *
      - *
    1. If both set to 0, logs will be deleted asap and will not get into - * the archive.
    2. - *
    3. If WAL_ttl_seconds is 0 and WAL_size_limit_MB is not 0, - * WAL files will be checked every 10 min and if total size is greater - * then WAL_size_limit_MB, they will be deleted starting with the - * earliest until size_limit is met. All empty files will be deleted.
    4. - *
    5. If WAL_ttl_seconds is not 0 and WAL_size_limit_MB is 0, then - * WAL files will be checked every WAL_ttl_seconds / 2 and those that - * are older than WAL_ttl_seconds will be deleted.
    6. - *
    7. If both are not 0, WAL files will be checked every 10 min and both - * checks will be performed with ttl being first.
    8. - *
    - * - * @return the wal-ttl seconds - * @see #walSizeLimitMB() - */ - long walTtlSeconds(); - - /** - * WalTtlSeconds() and walSizeLimitMB() affect how archived logs - * will be deleted. - *
      - *
    1. If both set to 0, logs will be deleted asap and will not get into - * the archive.
    2. - *
    3. If WAL_ttl_seconds is 0 and WAL_size_limit_MB is not 0, - * WAL files will be checked every 10 min and if total size is greater - * then WAL_size_limit_MB, they will be deleted starting with the - * earliest until size_limit is met. All empty files will be deleted.
    4. - *
    5. If WAL_ttl_seconds is not 0 and WAL_size_limit_MB is 0, then - * WAL files will be checked every WAL_ttl_secondsi / 2 and those that - * are older than WAL_ttl_seconds will be deleted.
    6. - *
    7. If both are not 0, WAL files will be checked every 10 min and both - * checks will be performed with ttl being first.
    8. - *
    - * - * @param sizeLimitMB size limit in mega-bytes. - * @return the instance of the current object. - * @see #setWalSizeLimitMB(long) - */ - T setWalSizeLimitMB(long sizeLimitMB); - - /** - * {@link #walTtlSeconds()} and {@code #walSizeLimitMB()} affect how archived logs - * will be deleted. - *
      - *
    1. If both set to 0, logs will be deleted asap and will not get into - * the archive.
    2. - *
    3. If WAL_ttl_seconds is 0 and WAL_size_limit_MB is not 0, - * WAL files will be checked every 10 min and if total size is greater - * then WAL_size_limit_MB, they will be deleted starting with the - * earliest until size_limit is met. All empty files will be deleted.
    4. - *
    5. If WAL_ttl_seconds is not 0 and WAL_size_limit_MB is 0, then - * WAL files will be checked every WAL_ttl_seconds i / 2 and those that - * are older than WAL_ttl_seconds will be deleted.
    6. - *
    7. If both are not 0, WAL files will be checked every 10 min and both - * checks will be performed with ttl being first.
    8. - *
    - * @return size limit in mega-bytes. - * @see #walSizeLimitMB() - */ - long walSizeLimitMB(); - - /** - * The maximum limit of number of bytes that are written in a single batch - * of WAL or memtable write. It is followed when the leader write size - * is larger than 1/8 of this limit. - * - * Default: 1 MB - * - * @param maxWriteBatchGroupSizeBytes the maximum limit of number of bytes, see description. - * @return the instance of the current object. - */ - T setMaxWriteBatchGroupSizeBytes(final long maxWriteBatchGroupSizeBytes); - - /** - * The maximum limit of number of bytes that are written in a single batch - * of WAL or memtable write. It is followed when the leader write size - * is larger than 1/8 of this limit. - * - * Default: 1 MB - * - * @return the maximum limit of number of bytes, see description. - */ - long maxWriteBatchGroupSizeBytes(); - - /** - * Number of bytes to preallocate (via fallocate) the manifest - * files. Default is 4mb, which is reasonable to reduce random IO - * as well as prevent overallocation for mounts that preallocate - * large amounts of data (such as xfs's allocsize option). - * - * @param size the size in byte - * @return the instance of the current object. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setManifestPreallocationSize(long size); - - /** - * Number of bytes to preallocate (via fallocate) the manifest - * files. Default is 4mb, which is reasonable to reduce random IO - * as well as prevent overallocation for mounts that preallocate - * large amounts of data (such as xfs's allocsize option). - * - * @return size in bytes. - */ - long manifestPreallocationSize(); - - /** - * Enable the OS to use direct I/O for reading sst tables. - * Default: false - * - * @param useDirectReads if true, then direct read is enabled - * @return the instance of the current object. - */ - T setUseDirectReads(boolean useDirectReads); - - /** - * Enable the OS to use direct I/O for reading sst tables. - * Default: false - * - * @return if true, then direct reads are enabled - */ - boolean useDirectReads(); - - /** - * Enable the OS to use direct reads and writes in flush and - * compaction - * Default: false - * - * @param useDirectIoForFlushAndCompaction if true, then direct - * I/O will be enabled for background flush and compactions - * @return the instance of the current object. - */ - T setUseDirectIoForFlushAndCompaction(boolean useDirectIoForFlushAndCompaction); - - /** - * Enable the OS to use direct reads and writes in flush and - * compaction - * - * @return if true, then direct I/O is enabled for flush and - * compaction - */ - boolean useDirectIoForFlushAndCompaction(); - - /** - * Whether fallocate calls are allowed - * - * @param allowFAllocate false if fallocate() calls are bypassed - * - * @return the reference to the current options. - */ - T setAllowFAllocate(boolean allowFAllocate); - - /** - * Whether fallocate calls are allowed - * - * @return false if fallocate() calls are bypassed - */ - boolean allowFAllocate(); - - /** - * Allow the OS to mmap file for reading sst tables. - * Default: false - * - * @param allowMmapReads true if mmap reads are allowed. - * @return the instance of the current object. - */ - T setAllowMmapReads(boolean allowMmapReads); - - /** - * Allow the OS to mmap file for reading sst tables. - * Default: false - * - * @return true if mmap reads are allowed. - */ - boolean allowMmapReads(); - - /** - * Allow the OS to mmap file for writing. Default: false - * - * @param allowMmapWrites true if mmap writes are allowd. - * @return the instance of the current object. - */ - T setAllowMmapWrites(boolean allowMmapWrites); - - /** - * Allow the OS to mmap file for writing. Default: false - * - * @return true if mmap writes are allowed. - */ - boolean allowMmapWrites(); - - /** - * Disable child process inherit open files. Default: true - * - * @param isFdCloseOnExec true if child process inheriting open - * files is disabled. - * @return the instance of the current object. - */ - T setIsFdCloseOnExec(boolean isFdCloseOnExec); - - /** - * Disable child process inherit open files. Default: true - * - * @return true if child process inheriting open files is disabled. - */ - boolean isFdCloseOnExec(); - - /** - * If set true, will hint the underlying file system that the file - * access pattern is random, when a sst file is opened. - * Default: true - * - * @param adviseRandomOnOpen true if hinting random access is on. - * @return the instance of the current object. - */ - T setAdviseRandomOnOpen(boolean adviseRandomOnOpen); - - /** - * If set true, will hint the underlying file system that the file - * access pattern is random, when a sst file is opened. - * Default: true - * - * @return true if hinting random access is on. - */ - boolean adviseRandomOnOpen(); - - /** - * Amount of data to build up in memtables across all column - * families before writing to disk. - * - * This is distinct from {@link ColumnFamilyOptions#writeBufferSize()}, - * which enforces a limit for a single memtable. - * - * This feature is disabled by default. Specify a non-zero value - * to enable it. - * - * Default: 0 (disabled) - * - * @param dbWriteBufferSize the size of the write buffer - * - * @return the reference to the current options. - */ - T setDbWriteBufferSize(long dbWriteBufferSize); - - /** - * Use passed {@link WriteBufferManager} to control memory usage across - * multiple column families and/or DB instances. - * - * Check - * https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager - * for more details on when to use it - * - * @param writeBufferManager The WriteBufferManager to use - * @return the reference of the current options. - */ - T setWriteBufferManager(final WriteBufferManager writeBufferManager); - - /** - * Reference to {@link WriteBufferManager} used by it.
    - * - * Default: null (Disabled) - * - * @return a reference to WriteBufferManager - */ - WriteBufferManager writeBufferManager(); - - /** - * Amount of data to build up in memtables across all column - * families before writing to disk. - * - * This is distinct from {@link ColumnFamilyOptions#writeBufferSize()}, - * which enforces a limit for a single memtable. - * - * This feature is disabled by default. Specify a non-zero value - * to enable it. - * - * Default: 0 (disabled) - * - * @return the size of the write buffer - */ - long dbWriteBufferSize(); - - /** - * Specify the file access pattern once a compaction is started. - * It will be applied to all input files of a compaction. - * - * Default: {@link AccessHint#NORMAL} - * - * @param accessHint The access hint - * - * @return the reference to the current options. - */ - T setAccessHintOnCompactionStart(final AccessHint accessHint); - - /** - * Specify the file access pattern once a compaction is started. - * It will be applied to all input files of a compaction. - * - * Default: {@link AccessHint#NORMAL} - * - * @return The access hint - */ - AccessHint accessHintOnCompactionStart(); - - /** - * This is a maximum buffer size that is used by WinMmapReadableFile in - * unbuffered disk I/O mode. We need to maintain an aligned buffer for - * reads. We allow the buffer to grow until the specified value and then - * for bigger requests allocate one shot buffers. In unbuffered mode we - * always bypass read-ahead buffer at ReadaheadRandomAccessFile - * When read-ahead is required we then make use of - * {@link MutableDBOptionsInterface#compactionReadaheadSize()} value and - * always try to read ahead. - * With read-ahead we always pre-allocate buffer to the size instead of - * growing it up to a limit. - * - * This option is currently honored only on Windows - * - * Default: 1 Mb - * - * Special value: 0 - means do not maintain per instance buffer. Allocate - * per request buffer and avoid locking. - * - * @param randomAccessMaxBufferSize the maximum size of the random access - * buffer - * - * @return the reference to the current options. - */ - T setRandomAccessMaxBufferSize(long randomAccessMaxBufferSize); - - /** - * This is a maximum buffer size that is used by WinMmapReadableFile in - * unbuffered disk I/O mode. We need to maintain an aligned buffer for - * reads. We allow the buffer to grow until the specified value and then - * for bigger requests allocate one shot buffers. In unbuffered mode we - * always bypass read-ahead buffer at ReadaheadRandomAccessFile - * When read-ahead is required we then make use of - * {@link MutableDBOptionsInterface#compactionReadaheadSize()} value and - * always try to read ahead. With read-ahead we always pre-allocate buffer - * to the size instead of growing it up to a limit. - * - * This option is currently honored only on Windows - * - * Default: 1 Mb - * - * Special value: 0 - means do not maintain per instance buffer. Allocate - * per request buffer and avoid locking. - * - * @return the maximum size of the random access buffer - */ - long randomAccessMaxBufferSize(); - - /** - * Use adaptive mutex, which spins in the user space before resorting - * to kernel. This could reduce context switch when the mutex is not - * heavily contended. However, if the mutex is hot, we could end up - * wasting spin time. - * Default: false - * - * @param useAdaptiveMutex true if adaptive mutex is used. - * @return the instance of the current object. - */ - T setUseAdaptiveMutex(boolean useAdaptiveMutex); - - /** - * Use adaptive mutex, which spins in the user space before resorting - * to kernel. This could reduce context switch when the mutex is not - * heavily contended. However, if the mutex is hot, we could end up - * wasting spin time. - * Default: false - * - * @return true if adaptive mutex is used. - */ - boolean useAdaptiveMutex(); - - /** - * Sets the {@link EventListener}s whose callback functions - * will be called when specific RocksDB event happens. - * - * Note: the RocksJava API currently only supports EventListeners implemented in Java. - * It could be extended in future to also support adding/removing EventListeners implemented in - * C++. - * - * @param listeners the listeners who should be notified on various events. - * - * @return the instance of the current object. - */ - T setListeners(final List listeners); - - /** - * Sets the {@link EventListener}s whose callback functions - * will be called when specific RocksDB event happens. - * - * Note: the RocksJava API currently only supports EventListeners implemented in Java. - * It could be extended in future to also support adding/removing EventListeners implemented in - * C++. - * - * @return the instance of the current object. - */ - List listeners(); - - /** - * If true, then the status of the threads involved in this DB will - * be tracked and available via GetThreadList() API. - * - * Default: false - * - * @param enableThreadTracking true to enable tracking - * - * @return the reference to the current options. - */ - T setEnableThreadTracking(boolean enableThreadTracking); - - /** - * If true, then the status of the threads involved in this DB will - * be tracked and available via GetThreadList() API. - * - * Default: false - * - * @return true if tracking is enabled - */ - boolean enableThreadTracking(); - - /** - * By default, a single write thread queue is maintained. The thread gets - * to the head of the queue becomes write batch group leader and responsible - * for writing to WAL and memtable for the batch group. - * - * If {@link #enablePipelinedWrite()} is true, separate write thread queue is - * maintained for WAL write and memtable write. A write thread first enter WAL - * writer queue and then memtable writer queue. Pending thread on the WAL - * writer queue thus only have to wait for previous writers to finish their - * WAL writing but not the memtable writing. Enabling the feature may improve - * write throughput and reduce latency of the prepare phase of two-phase - * commit. - * - * Default: false - * - * @param enablePipelinedWrite true to enabled pipelined writes - * - * @return the reference to the current options. - */ - T setEnablePipelinedWrite(final boolean enablePipelinedWrite); - - /** - * Returns true if pipelined writes are enabled. - * See {@link #setEnablePipelinedWrite(boolean)}. - * - * @return true if pipelined writes are enabled, false otherwise. - */ - boolean enablePipelinedWrite(); - - /** - * Setting {@link #unorderedWrite()} to true trades higher write throughput with - * relaxing the immutability guarantee of snapshots. This violates the - * repeatability one expects from ::Get from a snapshot, as well as - * ::MultiGet and Iterator's consistent-point-in-time view property. - * If the application cannot tolerate the relaxed guarantees, it can implement - * its own mechanisms to work around that and yet benefit from the higher - * throughput. Using TransactionDB with WRITE_PREPARED write policy and - * {@link #twoWriteQueues()} true is one way to achieve immutable snapshots despite - * unordered_write. - * - * By default, i.e., when it is false, rocksdb does not advance the sequence - * number for new snapshots unless all the writes with lower sequence numbers - * are already finished. This provides the immutability that we except from - * snapshots. Moreover, since Iterator and MultiGet internally depend on - * snapshots, the snapshot immutability results into Iterator and MultiGet - * offering consistent-point-in-time view. If set to true, although - * Read-Your-Own-Write property is still provided, the snapshot immutability - * property is relaxed: the writes issued after the snapshot is obtained (with - * larger sequence numbers) will be still not visible to the reads from that - * snapshot, however, there still might be pending writes (with lower sequence - * number) that will change the state visible to the snapshot after they are - * landed to the memtable. - * - * @param unorderedWrite true to enabled unordered write - * - * @return the reference to the current options. - */ - T setUnorderedWrite(final boolean unorderedWrite); - - /** - * Returns true if unordered write are enabled. - * See {@link #setUnorderedWrite(boolean)}. - * - * @return true if unordered write are enabled, false otherwise. - */ - boolean unorderedWrite(); - - /** - * If true, allow multi-writers to update mem tables in parallel. - * Only some memtable factorys support concurrent writes; currently it - * is implemented only for SkipListFactory. Concurrent memtable writes - * are not compatible with inplace_update_support or filter_deletes. - * It is strongly recommended to set - * {@link #setEnableWriteThreadAdaptiveYield(boolean)} if you are going to use - * this feature. - * Default: true - * - * @param allowConcurrentMemtableWrite true to enable concurrent writes - * for the memtable - * - * @return the reference to the current options. - */ - T setAllowConcurrentMemtableWrite(boolean allowConcurrentMemtableWrite); - - /** - * If true, allow multi-writers to update mem tables in parallel. - * Only some memtable factorys support concurrent writes; currently it - * is implemented only for SkipListFactory. Concurrent memtable writes - * are not compatible with inplace_update_support or filter_deletes. - * It is strongly recommended to set - * {@link #setEnableWriteThreadAdaptiveYield(boolean)} if you are going to use - * this feature. - * Default: true - * - * @return true if concurrent writes are enabled for the memtable - */ - boolean allowConcurrentMemtableWrite(); - - /** - * If true, threads synchronizing with the write batch group leader will - * wait for up to {@link #writeThreadMaxYieldUsec()} before blocking on a - * mutex. This can substantially improve throughput for concurrent workloads, - * regardless of whether {@link #allowConcurrentMemtableWrite()} is enabled. - * Default: true - * - * @param enableWriteThreadAdaptiveYield true to enable adaptive yield for the - * write threads - * - * @return the reference to the current options. - */ - T setEnableWriteThreadAdaptiveYield( - boolean enableWriteThreadAdaptiveYield); - - /** - * If true, threads synchronizing with the write batch group leader will - * wait for up to {@link #writeThreadMaxYieldUsec()} before blocking on a - * mutex. This can substantially improve throughput for concurrent workloads, - * regardless of whether {@link #allowConcurrentMemtableWrite()} is enabled. - * Default: true - * - * @return true if adaptive yield is enabled - * for the writing threads - */ - boolean enableWriteThreadAdaptiveYield(); - - /** - * The maximum number of microseconds that a write operation will use - * a yielding spin loop to coordinate with other write threads before - * blocking on a mutex. (Assuming {@link #writeThreadSlowYieldUsec()} is - * set properly) increasing this value is likely to increase RocksDB - * throughput at the expense of increased CPU usage. - * Default: 100 - * - * @param writeThreadMaxYieldUsec maximum number of microseconds - * - * @return the reference to the current options. - */ - T setWriteThreadMaxYieldUsec(long writeThreadMaxYieldUsec); - - /** - * The maximum number of microseconds that a write operation will use - * a yielding spin loop to coordinate with other write threads before - * blocking on a mutex. (Assuming {@link #writeThreadSlowYieldUsec()} is - * set properly) increasing this value is likely to increase RocksDB - * throughput at the expense of increased CPU usage. - * Default: 100 - * - * @return the maximum number of microseconds - */ - long writeThreadMaxYieldUsec(); - - /** - * The latency in microseconds after which a std::this_thread::yield - * call (sched_yield on Linux) is considered to be a signal that - * other processes or threads would like to use the current core. - * Increasing this makes writer threads more likely to take CPU - * by spinning, which will show up as an increase in the number of - * involuntary context switches. - * Default: 3 - * - * @param writeThreadSlowYieldUsec the latency in microseconds - * - * @return the reference to the current options. - */ - T setWriteThreadSlowYieldUsec(long writeThreadSlowYieldUsec); - - /** - * The latency in microseconds after which a std::this_thread::yield - * call (sched_yield on Linux) is considered to be a signal that - * other processes or threads would like to use the current core. - * Increasing this makes writer threads more likely to take CPU - * by spinning, which will show up as an increase in the number of - * involuntary context switches. - * Default: 3 - * - * @return writeThreadSlowYieldUsec the latency in microseconds - */ - long writeThreadSlowYieldUsec(); - - /** - * If true, then DB::Open() will not update the statistics used to optimize - * compaction decision by loading table properties from many files. - * Turning off this feature will improve DBOpen time especially in - * disk environment. - * - * Default: false - * - * @param skipStatsUpdateOnDbOpen true if updating stats will be skipped - * - * @return the reference to the current options. - */ - T setSkipStatsUpdateOnDbOpen(boolean skipStatsUpdateOnDbOpen); - - /** - * If true, then DB::Open() will not update the statistics used to optimize - * compaction decision by loading table properties from many files. - * Turning off this feature will improve DBOpen time especially in - * disk environment. - * - * Default: false - * - * @return true if updating stats will be skipped - */ - boolean skipStatsUpdateOnDbOpen(); - - /** - * If true, then {@link RocksDB#open(String)} will not fetch and check sizes of all sst files. - * This may significantly speed up startup if there are many sst files, - * especially when using non-default Env with expensive GetFileSize(). - * We'll still check that all required sst files exist. - * If {@code paranoid_checks} is false, this option is ignored, and sst files are - * not checked at all. - * - * Default: false - * - * @param skipCheckingSstFileSizesOnDbOpen if true, then SST file sizes will not be checked - * when calling {@link RocksDB#open(String)}. - * @return the reference to the current options. - */ - T setSkipCheckingSstFileSizesOnDbOpen(final boolean skipCheckingSstFileSizesOnDbOpen); - - /** - * If true, then {@link RocksDB#open(String)} will not fetch and check sizes of all sst files. - * This may significantly speed up startup if there are many sst files, - * especially when using non-default Env with expensive GetFileSize(). - * We'll still check that all required sst files exist. - * If {@code paranoid_checks} is false, this option is ignored, and sst files are - * not checked at all. - * - * Default: false - * - * @return true, if file sizes will not be checked when calling {@link RocksDB#open(String)}. - */ - boolean skipCheckingSstFileSizesOnDbOpen(); - - /** - * Recovery mode to control the consistency while replaying WAL - * - * Default: {@link WALRecoveryMode#PointInTimeRecovery} - * - * @param walRecoveryMode The WAL recover mode - * - * @return the reference to the current options. - */ - T setWalRecoveryMode(WALRecoveryMode walRecoveryMode); - - /** - * Recovery mode to control the consistency while replaying WAL - * - * Default: {@link WALRecoveryMode#PointInTimeRecovery} - * - * @return The WAL recover mode - */ - WALRecoveryMode walRecoveryMode(); - - /** - * if set to false then recovery will fail when a prepared - * transaction is encountered in the WAL - * - * Default: false - * - * @param allow2pc true if two-phase-commit is enabled - * - * @return the reference to the current options. - */ - T setAllow2pc(boolean allow2pc); - - /** - * if set to false then recovery will fail when a prepared - * transaction is encountered in the WAL - * - * Default: false - * - * @return true if two-phase-commit is enabled - */ - boolean allow2pc(); - - /** - * A global cache for table-level rows. - * - * Default: null (disabled) - * - * @param rowCache The global row cache - * - * @return the reference to the current options. - */ - T setRowCache(final Cache rowCache); - - /** - * A global cache for table-level rows. - * - * Default: null (disabled) - * - * @return The global row cache - */ - Cache rowCache(); - - /** - * A filter object supplied to be invoked while processing write-ahead-logs - * (WALs) during recovery. The filter provides a way to inspect log - * records, ignoring a particular record or skipping replay. - * The filter is invoked at startup and is invoked from a single-thread - * currently. - * - * @param walFilter the filter for processing WALs during recovery. - * - * @return the reference to the current options. - */ - T setWalFilter(final AbstractWalFilter walFilter); - - /** - * Get's the filter for processing WALs during recovery. - * See {@link #setWalFilter(AbstractWalFilter)}. - * - * @return the filter used for processing WALs during recovery. - */ - WalFilter walFilter(); - - /** - * If true, then DB::Open / CreateColumnFamily / DropColumnFamily - * / SetOptions will fail if options file is not detected or properly - * persisted. - * - * DEFAULT: false - * - * @param failIfOptionsFileError true if we should fail if there is an error - * in the options file - * - * @return the reference to the current options. - */ - T setFailIfOptionsFileError(boolean failIfOptionsFileError); - - /** - * If true, then DB::Open / CreateColumnFamily / DropColumnFamily - * / SetOptions will fail if options file is not detected or properly - * persisted. - * - * DEFAULT: false - * - * @return true if we should fail if there is an error in the options file - */ - boolean failIfOptionsFileError(); - - /** - * If true, then print malloc stats together with rocksdb.stats - * when printing to LOG. - * - * DEFAULT: false - * - * @param dumpMallocStats true if malloc stats should be printed to LOG - * - * @return the reference to the current options. - */ - T setDumpMallocStats(boolean dumpMallocStats); - - /** - * If true, then print malloc stats together with rocksdb.stats - * when printing to LOG. - * - * DEFAULT: false - * - * @return true if malloc stats should be printed to LOG - */ - boolean dumpMallocStats(); - - /** - * By default RocksDB replay WAL logs and flush them on DB open, which may - * create very small SST files. If this option is enabled, RocksDB will try - * to avoid (but not guarantee not to) flush during recovery. Also, existing - * WAL logs will be kept, so that if crash happened before flush, we still - * have logs to recover from. - * - * DEFAULT: false - * - * @param avoidFlushDuringRecovery true to try to avoid (but not guarantee - * not to) flush during recovery - * - * @return the reference to the current options. - */ - T setAvoidFlushDuringRecovery(boolean avoidFlushDuringRecovery); - - /** - * By default RocksDB replay WAL logs and flush them on DB open, which may - * create very small SST files. If this option is enabled, RocksDB will try - * to avoid (but not guarantee not to) flush during recovery. Also, existing - * WAL logs will be kept, so that if crash happened before flush, we still - * have logs to recover from. - * - * DEFAULT: false - * - * @return true to try to avoid (but not guarantee not to) flush during - * recovery - */ - boolean avoidFlushDuringRecovery(); - - /** - * Set this option to true during creation of database if you want - * to be able to ingest behind (call IngestExternalFile() skipping keys - * that already exist, rather than overwriting matching keys). - * Setting this option to true will affect 2 things: - * 1) Disable some internal optimizations around SST file compression - * 2) Reserve bottom-most level for ingested files only. - * 3) Note that num_levels should be >= 3 if this option is turned on. - * - * DEFAULT: false - * - * @param allowIngestBehind true to allow ingest behind, false to disallow. - * - * @return the reference to the current options. - */ - T setAllowIngestBehind(final boolean allowIngestBehind); - - /** - * Returns true if ingest behind is allowed. - * See {@link #setAllowIngestBehind(boolean)}. - * - * @return true if ingest behind is allowed, false otherwise. - */ - boolean allowIngestBehind(); - - /** - * If enabled it uses two queues for writes, one for the ones with - * disable_memtable and one for the ones that also write to memtable. This - * allows the memtable writes not to lag behind other writes. It can be used - * to optimize MySQL 2PC in which only the commits, which are serial, write to - * memtable. - * - * DEFAULT: false - * - * @param twoWriteQueues true to enable two write queues, false otherwise. - * - * @return the reference to the current options. - */ - T setTwoWriteQueues(final boolean twoWriteQueues); - - /** - * Returns true if two write queues are enabled. - * - * @return true if two write queues are enabled, false otherwise. - */ - boolean twoWriteQueues(); - - /** - * If true WAL is not flushed automatically after each write. Instead it - * relies on manual invocation of FlushWAL to write the WAL buffer to its - * file. - * - * DEFAULT: false - * - * @param manualWalFlush true to set disable automatic WAL flushing, - * false otherwise. - * - * @return the reference to the current options. - */ - T setManualWalFlush(final boolean manualWalFlush); - - /** - * Returns true if automatic WAL flushing is disabled. - * See {@link #setManualWalFlush(boolean)}. - * - * @return true if automatic WAL flushing is disabled, false otherwise. - */ - boolean manualWalFlush(); - - /** - * If true, RocksDB supports flushing multiple column families and committing - * their results atomically to MANIFEST. Note that it is not - * necessary to set atomic_flush to true if WAL is always enabled since WAL - * allows the database to be restored to the last persistent state in WAL. - * This option is useful when there are column families with writes NOT - * protected by WAL. - * For manual flush, application has to specify which column families to - * flush atomically in {@link RocksDB#flush(FlushOptions, List)}. - * For auto-triggered flush, RocksDB atomically flushes ALL column families. - * - * Currently, any WAL-enabled writes after atomic flush may be replayed - * independently if the process crashes later and tries to recover. - * - * @param atomicFlush true to enable atomic flush of multiple column families. - * - * @return the reference to the current options. - */ - T setAtomicFlush(final boolean atomicFlush); - - /** - * Determine if atomic flush of multiple column families is enabled. - * - * See {@link #setAtomicFlush(boolean)}. - * - * @return true if atomic flush is enabled. - */ - boolean atomicFlush(); - - /** - * If true, working thread may avoid doing unnecessary and long-latency - * operation (such as deleting obsolete files directly or deleting memtable) - * and will instead schedule a background job to do it. - * Use it if you're latency-sensitive. - * If set to true, takes precedence over - * {@link ReadOptions#setBackgroundPurgeOnIteratorCleanup(boolean)}. - * - * @param avoidUnnecessaryBlockingIO If true, working thread may avoid doing unnecessary - * operation. - * @return the reference to the current options. - */ - T setAvoidUnnecessaryBlockingIO(final boolean avoidUnnecessaryBlockingIO); - - /** - * If true, working thread may avoid doing unnecessary and long-latency - * operation (such as deleting obsolete files directly or deleting memtable) - * and will instead schedule a background job to do it. - * Use it if you're latency-sensitive. - * If set to true, takes precedence over - * {@link ReadOptions#setBackgroundPurgeOnIteratorCleanup(boolean)}. - * - * @return true, if working thread may avoid doing unnecessary operation. - */ - boolean avoidUnnecessaryBlockingIO(); - - /** - * If true, automatically persist stats to a hidden column family (column - * family name: ___rocksdb_stats_history___) every - * stats_persist_period_sec seconds; otherwise, write to an in-memory - * struct. User can query through `GetStatsHistory` API. - * If user attempts to create a column family with the same name on a DB - * which have previously set persist_stats_to_disk to true, the column family - * creation will fail, but the hidden column family will survive, as well as - * the previously persisted statistics. - * When peristing stats to disk, the stat name will be limited at 100 bytes. - * Default: false - * - * @param persistStatsToDisk true if stats should be persisted to hidden column family. - * @return the instance of the current object. - */ - T setPersistStatsToDisk(final boolean persistStatsToDisk); - - /** - * If true, automatically persist stats to a hidden column family (column - * family name: ___rocksdb_stats_history___) every - * stats_persist_period_sec seconds; otherwise, write to an in-memory - * struct. User can query through `GetStatsHistory` API. - * If user attempts to create a column family with the same name on a DB - * which have previously set persist_stats_to_disk to true, the column family - * creation will fail, but the hidden column family will survive, as well as - * the previously persisted statistics. - * When peristing stats to disk, the stat name will be limited at 100 bytes. - * Default: false - * - * @return true if stats should be persisted to hidden column family. - */ - boolean persistStatsToDisk(); - - /** - * Historically DB ID has always been stored in Identity File in DB folder. - * If this flag is true, the DB ID is written to Manifest file in addition - * to the Identity file. By doing this 2 problems are solved - * 1. We don't checksum the Identity file where as Manifest file is. - * 2. Since the source of truth for DB is Manifest file DB ID will sit with - * the source of truth. Previously the Identity file could be copied - * independent of Manifest and that can result in wrong DB ID. - * We recommend setting this flag to true. - * Default: false - * - * @param writeDbidToManifest if true, then DB ID will be written to Manifest file. - * @return the instance of the current object. - */ - T setWriteDbidToManifest(final boolean writeDbidToManifest); - - /** - * Historically DB ID has always been stored in Identity File in DB folder. - * If this flag is true, the DB ID is written to Manifest file in addition - * to the Identity file. By doing this 2 problems are solved - * 1. We don't checksum the Identity file where as Manifest file is. - * 2. Since the source of truth for DB is Manifest file DB ID will sit with - * the source of truth. Previously the Identity file could be copied - * independent of Manifest and that can result in wrong DB ID. - * We recommend setting this flag to true. - * Default: false - * - * @return true, if DB ID will be written to Manifest file. - */ - boolean writeDbidToManifest(); - - /** - * The number of bytes to prefetch when reading the log. This is mostly useful - * for reading a remotely located log, as it can save the number of - * round-trips. If 0, then the prefetching is disabled. - * - * Default: 0 - * - * @param logReadaheadSize the number of bytes to prefetch when reading the log. - * @return the instance of the current object. - */ - T setLogReadaheadSize(final long logReadaheadSize); - - /** - * The number of bytes to prefetch when reading the log. This is mostly useful - * for reading a remotely located log, as it can save the number of - * round-trips. If 0, then the prefetching is disabled. - * - * Default: 0 - * - * @return the number of bytes to prefetch when reading the log. - */ - long logReadaheadSize(); - - /** - * By default, RocksDB recovery fails if any table file referenced in - * MANIFEST are missing after scanning the MANIFEST. - * Best-efforts recovery is another recovery mode that - * tries to restore the database to the most recent point in time without - * missing file. - * Currently not compatible with atomic flush. Furthermore, WAL files will - * not be used for recovery if best_efforts_recovery is true. - * Default: false - * - * @param bestEffortsRecovery if true, RocksDB will use best-efforts mode when recovering. - * @return the instance of the current object. - */ - T setBestEffortsRecovery(final boolean bestEffortsRecovery); - - /** - * By default, RocksDB recovery fails if any table file referenced in - * MANIFEST are missing after scanning the MANIFEST. - * Best-efforts recovery is another recovery mode that - * tries to restore the database to the most recent point in time without - * missing file. - * Currently not compatible with atomic flush. Furthermore, WAL files will - * not be used for recovery if best_efforts_recovery is true. - * Default: false - * - * @return true, if RocksDB uses best-efforts mode when recovering. - */ - boolean bestEffortsRecovery(); - - /** - * It defines how many times db resume is called by a separate thread when - * background retryable IO Error happens. When background retryable IO - * Error happens, SetBGError is called to deal with the error. If the error - * can be auto-recovered (e.g., retryable IO Error during Flush or WAL write), - * then db resume is called in background to recover from the error. If this - * value is 0 or negative, db resume will not be called. - * - * Default: INT_MAX - * - * @param maxBgerrorResumeCount maximum number of times db resume should be called when IO Error - * happens. - * @return the instance of the current object. - */ - T setMaxBgErrorResumeCount(final int maxBgerrorResumeCount); - - /** - * It defines how many times db resume is called by a separate thread when - * background retryable IO Error happens. When background retryable IO - * Error happens, SetBGError is called to deal with the error. If the error - * can be auto-recovered (e.g., retryable IO Error during Flush or WAL write), - * then db resume is called in background to recover from the error. If this - * value is 0 or negative, db resume will not be called. - * - * Default: INT_MAX - * - * @return maximum number of times db resume should be called when IO Error happens. - */ - int maxBgerrorResumeCount(); - - /** - * If max_bgerror_resume_count is ≥ 2, db resume is called multiple times. - * This option decides how long to wait to retry the next resume if the - * previous resume fails and satisfy redo resume conditions. - * - * Default: 1000000 (microseconds). - * - * @param bgerrorResumeRetryInterval how many microseconds to wait between DB resume attempts. - * @return the instance of the current object. - */ - T setBgerrorResumeRetryInterval(final long bgerrorResumeRetryInterval); - - /** - * If max_bgerror_resume_count is ≥ 2, db resume is called multiple times. - * This option decides how long to wait to retry the next resume if the - * previous resume fails and satisfy redo resume conditions. - * - * Default: 1000000 (microseconds). - * - * @return the instance of the current object. - */ - long bgerrorResumeRetryInterval(); -} diff --git a/java/src/main/java/org/rocksdb/DataBlockIndexType.java b/java/src/main/java/org/rocksdb/DataBlockIndexType.java deleted file mode 100644 index 513e5b429..000000000 --- a/java/src/main/java/org/rocksdb/DataBlockIndexType.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - - -/** - * DataBlockIndexType used in conjunction with BlockBasedTable. - */ -public enum DataBlockIndexType { - /** - * traditional block type - */ - kDataBlockBinarySearch((byte)0x0), - - /** - * additional hash index - */ - kDataBlockBinaryAndHash((byte)0x1); - - private final byte value; - - DataBlockIndexType(final byte value) { - this.value = value; - } - - byte getValue() { - return value; - } -} diff --git a/java/src/main/java/org/rocksdb/DbPath.java b/java/src/main/java/org/rocksdb/DbPath.java deleted file mode 100644 index 3f0b67557..000000000 --- a/java/src/main/java/org/rocksdb/DbPath.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.file.Path; - -/** - * Tuple of database path and target size - */ -public class DbPath { - final Path path; - final long targetSize; - - public DbPath(final Path path, final long targetSize) { - this.path = path; - this.targetSize = targetSize; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - final DbPath dbPath = (DbPath) o; - - if (targetSize != dbPath.targetSize) { - return false; - } - - return path != null ? path.equals(dbPath.path) : dbPath.path == null; - } - - @Override - public int hashCode() { - int result = path != null ? path.hashCode() : 0; - result = 31 * result + (int) (targetSize ^ (targetSize >>> 32)); - return result; - } -} diff --git a/java/src/main/java/org/rocksdb/DirectSlice.java b/java/src/main/java/org/rocksdb/DirectSlice.java deleted file mode 100644 index 02fa3511f..000000000 --- a/java/src/main/java/org/rocksdb/DirectSlice.java +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * Base class for slices which will receive direct - * ByteBuffer based access to the underlying data. - * - * ByteBuffer backed slices typically perform better with - * larger keys and values. When using smaller keys and - * values consider using @see org.rocksdb.Slice - */ -public class DirectSlice extends AbstractSlice { - public final static DirectSlice NONE = new DirectSlice(); - - /** - * Indicates whether we have to free the memory pointed to by the Slice - */ - private final boolean internalBuffer; - private volatile boolean cleared = false; - private volatile long internalBufferOffset = 0; - - /** - * Called from JNI to construct a new Java DirectSlice - * without an underlying C++ object set - * at creation time. - * - * Note: You should be aware that it is intentionally marked as - * package-private. This is so that developers cannot construct their own - * default DirectSlice objects (at present). As developers cannot construct - * their own DirectSlice objects through this, they are not creating - * underlying C++ DirectSlice objects, and so there is nothing to free - * (dispose) from Java. - */ - DirectSlice() { - super(); - this.internalBuffer = false; - } - - /** - * Constructs a slice - * where the data is taken from - * a String. - * - * @param str The string - */ - public DirectSlice(final String str) { - super(createNewSliceFromString(str)); - this.internalBuffer = true; - } - - /** - * Constructs a slice where the data is - * read from the provided - * ByteBuffer up to a certain length - * - * @param data The buffer containing the data - * @param length The length of the data to use for the slice - */ - public DirectSlice(final ByteBuffer data, final int length) { - super(createNewDirectSlice0(ensureDirect(data), length)); - this.internalBuffer = false; - } - - /** - * Constructs a slice where the data is - * read from the provided - * ByteBuffer - * - * @param data The bugger containing the data - */ - public DirectSlice(final ByteBuffer data) { - super(createNewDirectSlice1(ensureDirect(data))); - this.internalBuffer = false; - } - - private static ByteBuffer ensureDirect(final ByteBuffer data) { - if(!data.isDirect()) { - throw new IllegalArgumentException("The ByteBuffer must be direct"); - } - return data; - } - - /** - * Retrieves the byte at a specific offset - * from the underlying data - * - * @param offset The (zero-based) offset of the byte to retrieve - * - * @return the requested byte - */ - public byte get(final int offset) { - return get0(getNativeHandle(), offset); - } - - @Override - public void clear() { - clear0(getNativeHandle(), !cleared && internalBuffer, internalBufferOffset); - cleared = true; - } - - @Override - public void removePrefix(final int n) { - removePrefix0(getNativeHandle(), n); - this.internalBufferOffset += n; - } - - public void setLength(final int n) { - setLength0(getNativeHandle(), n); - } - - @Override - protected void disposeInternal() { - final long nativeHandle = getNativeHandle(); - if(!cleared && internalBuffer) { - disposeInternalBuf(nativeHandle, internalBufferOffset); - } - disposeInternal(nativeHandle); - } - - private native static long createNewDirectSlice0(final ByteBuffer data, - final int length); - private native static long createNewDirectSlice1(final ByteBuffer data); - @Override protected final native ByteBuffer data0(long handle); - private native byte get0(long handle, int offset); - private native void clear0(long handle, boolean internalBuffer, - long internalBufferOffset); - private native void removePrefix0(long handle, int length); - private native void setLength0(long handle, int length); - private native void disposeInternalBuf(final long handle, - long internalBufferOffset); -} diff --git a/java/src/main/java/org/rocksdb/EncodingType.java b/java/src/main/java/org/rocksdb/EncodingType.java deleted file mode 100644 index 5ceeb54c8..000000000 --- a/java/src/main/java/org/rocksdb/EncodingType.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * EncodingType - * - *

    The value will determine how to encode keys - * when writing to a new SST file.

    - * - *

    This value will be stored - * inside the SST file which will be used when reading from - * the file, which makes it possible for users to choose - * different encoding type when reopening a DB. Files with - * different encoding types can co-exist in the same DB and - * can be read.

    - */ -public enum EncodingType { - /** - * Always write full keys without any special encoding. - */ - kPlain((byte) 0), - /** - *

    Find opportunity to write the same prefix once for multiple rows. - * In some cases, when a key follows a previous key with the same prefix, - * instead of writing out the full key, it just writes out the size of the - * shared prefix, as well as other bytes, to save some bytes.

    - * - *

    When using this option, the user is required to use the same prefix - * extractor to make sure the same prefix will be extracted from the same key. - * The Name() value of the prefix extractor will be stored in the file. When - * reopening the file, the name of the options.prefix_extractor given will be - * bitwise compared to the prefix extractors stored in the file. An error - * will be returned if the two don't match.

    - */ - kPrefix((byte) 1); - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - private EncodingType(byte value) { - value_ = value; - } - - private final byte value_; -} diff --git a/java/src/main/java/org/rocksdb/Env.java b/java/src/main/java/org/rocksdb/Env.java deleted file mode 100644 index 07b5319bb..000000000 --- a/java/src/main/java/org/rocksdb/Env.java +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.List; - -/** - * Base class for all Env implementations in RocksDB. - */ -public abstract class Env extends RocksObject { - - static { - RocksDB.loadLibrary(); - } - - private static final Env DEFAULT_ENV = new RocksEnv(getDefaultEnvInternal()); - static { - /** - * The Ownership of the Default Env belongs to C++ - * and so we disown the native handle here so that - * we cannot accidentally free it from Java. - */ - DEFAULT_ENV.disOwnNativeHandle(); - } - - /** - *

    Returns the default environment suitable for the current operating - * system.

    - * - *

    The result of {@code getDefault()} is a singleton whose ownership - * belongs to rocksdb c++. As a result, the returned RocksEnv will not - * have the ownership of its c++ resource, and calling its dispose()/close() - * will be no-op.

    - * - * @return the default {@link org.rocksdb.RocksEnv} instance. - */ - public static Env getDefault() { - return DEFAULT_ENV; - } - - /** - *

    Sets the number of background worker threads of the low priority - * pool for this environment.

    - *

    Default number: 1

    - * - * @param number the number of threads - * - * @return current {@link RocksEnv} instance. - */ - public Env setBackgroundThreads(final int number) { - return setBackgroundThreads(number, Priority.LOW); - } - - /** - *

    Gets the number of background worker threads of the pool - * for this environment.

    - * - * @param priority the priority id of a specified thread pool. - * - * @return the number of threads. - */ - public int getBackgroundThreads(final Priority priority) { - return getBackgroundThreads(nativeHandle_, priority.getValue()); - } - - /** - *

    Sets the number of background worker threads of the specified thread - * pool for this environment.

    - * - * @param number the number of threads - * @param priority the priority id of a specified thread pool. - * - *

    Default number: 1

    - * @return current {@link RocksEnv} instance. - */ - public Env setBackgroundThreads(final int number, final Priority priority) { - setBackgroundThreads(nativeHandle_, number, priority.getValue()); - return this; - } - - /** - *

    Returns the length of the queue associated with the specified - * thread pool.

    - * - * @param priority the priority id of a specified thread pool. - * - * @return the thread pool queue length. - */ - public int getThreadPoolQueueLen(final Priority priority) { - return getThreadPoolQueueLen(nativeHandle_, priority.getValue()); - } - - /** - * Enlarge number of background worker threads of a specific thread pool - * for this environment if it is smaller than specified. 'LOW' is the default - * pool. - * - * @param number the number of threads. - * @param priority the priority id of a specified thread pool. - * - * @return current {@link RocksEnv} instance. - */ - public Env incBackgroundThreadsIfNeeded(final int number, - final Priority priority) { - incBackgroundThreadsIfNeeded(nativeHandle_, number, priority.getValue()); - return this; - } - - /** - * Lower IO priority for threads from the specified pool. - * - * @param priority the priority id of a specified thread pool. - * - * @return current {@link RocksEnv} instance. - */ - public Env lowerThreadPoolIOPriority(final Priority priority) { - lowerThreadPoolIOPriority(nativeHandle_, priority.getValue()); - return this; - } - - /** - * Lower CPU priority for threads from the specified pool. - * - * @param priority the priority id of a specified thread pool. - * - * @return current {@link RocksEnv} instance. - */ - public Env lowerThreadPoolCPUPriority(final Priority priority) { - lowerThreadPoolCPUPriority(nativeHandle_, priority.getValue()); - return this; - } - - /** - * Returns the status of all threads that belong to the current Env. - * - * @return the status of all threads belong to this env. - * - * @throws RocksDBException if the thread list cannot be acquired. - */ - public List getThreadList() throws RocksDBException { - return Arrays.asList(getThreadList(nativeHandle_)); - } - - Env(final long nativeHandle) { - super(nativeHandle); - } - - private static native long getDefaultEnvInternal(); - private native void setBackgroundThreads( - final long handle, final int number, final byte priority); - private native int getBackgroundThreads(final long handle, - final byte priority); - private native int getThreadPoolQueueLen(final long handle, - final byte priority); - private native void incBackgroundThreadsIfNeeded(final long handle, - final int number, final byte priority); - private native void lowerThreadPoolIOPriority(final long handle, - final byte priority); - private native void lowerThreadPoolCPUPriority(final long handle, - final byte priority); - private native ThreadStatus[] getThreadList(final long handle) - throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/EnvOptions.java b/java/src/main/java/org/rocksdb/EnvOptions.java deleted file mode 100644 index 6baddb310..000000000 --- a/java/src/main/java/org/rocksdb/EnvOptions.java +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Options while opening a file to read/write - */ -public class EnvOptions extends RocksObject { - static { - RocksDB.loadLibrary(); - } - - /** - * Construct with default Options - */ - public EnvOptions() { - super(newEnvOptions()); - } - - /** - * Construct from {@link DBOptions}. - * - * @param dbOptions the database options. - */ - public EnvOptions(final DBOptions dbOptions) { - super(newEnvOptions(dbOptions.nativeHandle_)); - } - - /** - * Enable/Disable memory mapped reads. - * - * Default: false - * - * @param useMmapReads true to enable memory mapped reads, false to disable. - * - * @return the reference to these options. - */ - public EnvOptions setUseMmapReads(final boolean useMmapReads) { - setUseMmapReads(nativeHandle_, useMmapReads); - return this; - } - - /** - * Determine if memory mapped reads are in-use. - * - * @return true if memory mapped reads are in-use, false otherwise. - */ - public boolean useMmapReads() { - assert(isOwningHandle()); - return useMmapReads(nativeHandle_); - } - - /** - * Enable/Disable memory mapped Writes. - * - * Default: true - * - * @param useMmapWrites true to enable memory mapped writes, false to disable. - * - * @return the reference to these options. - */ - public EnvOptions setUseMmapWrites(final boolean useMmapWrites) { - setUseMmapWrites(nativeHandle_, useMmapWrites); - return this; - } - - /** - * Determine if memory mapped writes are in-use. - * - * @return true if memory mapped writes are in-use, false otherwise. - */ - public boolean useMmapWrites() { - assert(isOwningHandle()); - return useMmapWrites(nativeHandle_); - } - - /** - * Enable/Disable direct reads, i.e. {@code O_DIRECT}. - * - * Default: false - * - * @param useDirectReads true to enable direct reads, false to disable. - * - * @return the reference to these options. - */ - public EnvOptions setUseDirectReads(final boolean useDirectReads) { - setUseDirectReads(nativeHandle_, useDirectReads); - return this; - } - - /** - * Determine if direct reads are in-use. - * - * @return true if direct reads are in-use, false otherwise. - */ - public boolean useDirectReads() { - assert(isOwningHandle()); - return useDirectReads(nativeHandle_); - } - - /** - * Enable/Disable direct writes, i.e. {@code O_DIRECT}. - * - * Default: false - * - * @param useDirectWrites true to enable direct writes, false to disable. - * - * @return the reference to these options. - */ - public EnvOptions setUseDirectWrites(final boolean useDirectWrites) { - setUseDirectWrites(nativeHandle_, useDirectWrites); - return this; - } - - /** - * Determine if direct writes are in-use. - * - * @return true if direct writes are in-use, false otherwise. - */ - public boolean useDirectWrites() { - assert(isOwningHandle()); - return useDirectWrites(nativeHandle_); - } - - /** - * Enable/Disable fallocate calls. - * - * Default: true - * - * If false, {@code fallocate()} calls are bypassed. - * - * @param allowFallocate true to enable fallocate calls, false to disable. - * - * @return the reference to these options. - */ - public EnvOptions setAllowFallocate(final boolean allowFallocate) { - setAllowFallocate(nativeHandle_, allowFallocate); - return this; - } - - /** - * Determine if fallocate calls are used. - * - * @return true if fallocate calls are used, false otherwise. - */ - public boolean allowFallocate() { - assert(isOwningHandle()); - return allowFallocate(nativeHandle_); - } - - /** - * Enable/Disable the {@code FD_CLOEXEC} bit when opening file descriptors. - * - * Default: true - * - * @param setFdCloexec true to enable the {@code FB_CLOEXEC} bit, - * false to disable. - * - * @return the reference to these options. - */ - public EnvOptions setSetFdCloexec(final boolean setFdCloexec) { - setSetFdCloexec(nativeHandle_, setFdCloexec); - return this; - } - - /** - * Determine i fthe {@code FD_CLOEXEC} bit is set when opening file - * descriptors. - * - * @return true if the {@code FB_CLOEXEC} bit is enabled, false otherwise. - */ - public boolean setFdCloexec() { - assert(isOwningHandle()); - return setFdCloexec(nativeHandle_); - } - - /** - * Allows OS to incrementally sync files to disk while they are being - * written, in the background. Issue one request for every - * {@code bytesPerSync} written. - * - * Default: 0 - * - * @param bytesPerSync 0 to disable, otherwise the number of bytes. - * - * @return the reference to these options. - */ - public EnvOptions setBytesPerSync(final long bytesPerSync) { - setBytesPerSync(nativeHandle_, bytesPerSync); - return this; - } - - /** - * Get the number of incremental bytes per sync written in the background. - * - * @return 0 if disabled, otherwise the number of bytes. - */ - public long bytesPerSync() { - assert(isOwningHandle()); - return bytesPerSync(nativeHandle_); - } - - /** - * If true, we will preallocate the file with {@code FALLOC_FL_KEEP_SIZE} - * flag, which means that file size won't change as part of preallocation. - * If false, preallocation will also change the file size. This option will - * improve the performance in workloads where you sync the data on every - * write. By default, we set it to true for MANIFEST writes and false for - * WAL writes - * - * @param fallocateWithKeepSize true to preallocate, false otherwise. - * - * @return the reference to these options. - */ - public EnvOptions setFallocateWithKeepSize( - final boolean fallocateWithKeepSize) { - setFallocateWithKeepSize(nativeHandle_, fallocateWithKeepSize); - return this; - } - - /** - * Determine if file is preallocated. - * - * @return true if the file is preallocated, false otherwise. - */ - public boolean fallocateWithKeepSize() { - assert(isOwningHandle()); - return fallocateWithKeepSize(nativeHandle_); - } - - /** - * See {@link DBOptions#setCompactionReadaheadSize(long)}. - * - * @param compactionReadaheadSize the compaction read-ahead size. - * - * @return the reference to these options. - */ - public EnvOptions setCompactionReadaheadSize( - final long compactionReadaheadSize) { - setCompactionReadaheadSize(nativeHandle_, compactionReadaheadSize); - return this; - } - - /** - * See {@link DBOptions#compactionReadaheadSize()}. - * - * @return the compaction read-ahead size. - */ - public long compactionReadaheadSize() { - assert(isOwningHandle()); - return compactionReadaheadSize(nativeHandle_); - } - - /** - * See {@link DBOptions#setRandomAccessMaxBufferSize(long)}. - * - * @param randomAccessMaxBufferSize the max buffer size for random access. - * - * @return the reference to these options. - */ - public EnvOptions setRandomAccessMaxBufferSize( - final long randomAccessMaxBufferSize) { - setRandomAccessMaxBufferSize(nativeHandle_, randomAccessMaxBufferSize); - return this; - } - - /** - * See {@link DBOptions#randomAccessMaxBufferSize()}. - * - * @return the max buffer size for random access. - */ - public long randomAccessMaxBufferSize() { - assert(isOwningHandle()); - return randomAccessMaxBufferSize(nativeHandle_); - } - - /** - * See {@link DBOptions#setWritableFileMaxBufferSize(long)}. - * - * @param writableFileMaxBufferSize the max buffer size. - * - * @return the reference to these options. - */ - public EnvOptions setWritableFileMaxBufferSize( - final long writableFileMaxBufferSize) { - setWritableFileMaxBufferSize(nativeHandle_, writableFileMaxBufferSize); - return this; - } - - /** - * See {@link DBOptions#writableFileMaxBufferSize()}. - * - * @return the max buffer size. - */ - public long writableFileMaxBufferSize() { - assert(isOwningHandle()); - return writableFileMaxBufferSize(nativeHandle_); - } - - /** - * Set the write rate limiter for flush and compaction. - * - * @param rateLimiter the rate limiter. - * - * @return the reference to these options. - */ - public EnvOptions setRateLimiter(final RateLimiter rateLimiter) { - this.rateLimiter = rateLimiter; - setRateLimiter(nativeHandle_, rateLimiter.nativeHandle_); - return this; - } - - /** - * Get the write rate limiter for flush and compaction. - * - * @return the rate limiter. - */ - public RateLimiter rateLimiter() { - assert(isOwningHandle()); - return rateLimiter; - } - - private native static long newEnvOptions(); - private native static long newEnvOptions(final long dboptions_handle); - @Override protected final native void disposeInternal(final long handle); - - private native void setUseMmapReads(final long handle, - final boolean useMmapReads); - private native boolean useMmapReads(final long handle); - private native void setUseMmapWrites(final long handle, - final boolean useMmapWrites); - private native boolean useMmapWrites(final long handle); - private native void setUseDirectReads(final long handle, - final boolean useDirectReads); - private native boolean useDirectReads(final long handle); - private native void setUseDirectWrites(final long handle, - final boolean useDirectWrites); - private native boolean useDirectWrites(final long handle); - private native void setAllowFallocate(final long handle, - final boolean allowFallocate); - private native boolean allowFallocate(final long handle); - private native void setSetFdCloexec(final long handle, - final boolean setFdCloexec); - private native boolean setFdCloexec(final long handle); - private native void setBytesPerSync(final long handle, - final long bytesPerSync); - private native long bytesPerSync(final long handle); - private native void setFallocateWithKeepSize( - final long handle, final boolean fallocateWithKeepSize); - private native boolean fallocateWithKeepSize(final long handle); - private native void setCompactionReadaheadSize( - final long handle, final long compactionReadaheadSize); - private native long compactionReadaheadSize(final long handle); - private native void setRandomAccessMaxBufferSize( - final long handle, final long randomAccessMaxBufferSize); - private native long randomAccessMaxBufferSize(final long handle); - private native void setWritableFileMaxBufferSize( - final long handle, final long writableFileMaxBufferSize); - private native long writableFileMaxBufferSize(final long handle); - private native void setRateLimiter(final long handle, - final long rateLimiterHandle); - private RateLimiter rateLimiter; -} diff --git a/java/src/main/java/org/rocksdb/EventListener.java b/java/src/main/java/org/rocksdb/EventListener.java deleted file mode 100644 index a12ab92ba..000000000 --- a/java/src/main/java/org/rocksdb/EventListener.java +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * EventListener class contains a set of callback functions that will - * be called when specific RocksDB event happens such as flush. It can - * be used as a building block for developing custom features such as - * stats-collector or external compaction algorithm. - * - * Note that callback functions should not run for an extended period of - * time before the function returns, otherwise RocksDB may be blocked. - * For example, it is not suggested to do - * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, - * CompactionJobInfo)} (as it may run for a long while) or issue many of - * {@link RocksDB#put(ColumnFamilyHandle, WriteOptions, byte[], byte[])} - * (as Put may be blocked in certain cases) in the same thread in the - * EventListener callback. - * - * However, doing - * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, - * CompactionJobInfo)} and {@link RocksDB#put(ColumnFamilyHandle, WriteOptions, byte[], byte[])} in - * another thread is considered safe. - * - * [Threading] All EventListener callback will be called using the - * actual thread that involves in that specific event. For example, it - * is the RocksDB background flush thread that does the actual flush to - * call {@link #onFlushCompleted(RocksDB, FlushJobInfo)}. - * - * [Locking] All EventListener callbacks are designed to be called without - * the current thread holding any DB mutex. This is to prevent potential - * deadlock and performance issue when using EventListener callback - * in a complex way. - */ -public interface EventListener { - /** - * A callback function to RocksDB which will be called before a - * RocksDB starts to flush memtables. - * - * Note that the this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * @param db the database - * @param flushJobInfo the flush job info, contains data copied from - * respective native structure. - */ - void onFlushBegin(final RocksDB db, final FlushJobInfo flushJobInfo); - - /** - * callback function to RocksDB which will be called whenever a - * registered RocksDB flushes a file. - * - * Note that the this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * @param db the database - * @param flushJobInfo the flush job info, contains data copied from - * respective native structure. - */ - void onFlushCompleted(final RocksDB db, final FlushJobInfo flushJobInfo); - - /** - * A callback function for RocksDB which will be called whenever - * a SST file is deleted. Different from - * {@link #onCompactionCompleted(RocksDB, CompactionJobInfo)} and - * {@link #onFlushCompleted(RocksDB, FlushJobInfo)}, - * this callback is designed for external logging - * service and thus only provide string parameters instead - * of a pointer to DB. Applications that build logic basic based - * on file creations and deletions is suggested to implement - * {@link #onFlushCompleted(RocksDB, FlushJobInfo)} and - * {@link #onCompactionCompleted(RocksDB, CompactionJobInfo)}. - * - * Note that if applications would like to use the passed reference - * outside this function call, they should make copies from the - * returned value. - * - * @param tableFileDeletionInfo the table file deletion info, - * contains data copied from respective native structure. - */ - void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo); - - /** - * A callback function to RocksDB which will be called before a - * RocksDB starts to compact. The default implementation is - * no-op. - * - * Note that the this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * @param db a pointer to the rocksdb instance which just compacted - * a file. - * @param compactionJobInfo a reference to a native CompactionJobInfo struct, - * which is released after this function is returned, and must be copied - * if it is needed outside of this function. - */ - void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo); - - /** - * A callback function for RocksDB which will be called whenever - * a registered RocksDB compacts a file. The default implementation - * is a no-op. - * - * Note that this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * @param db a pointer to the rocksdb instance which just compacted - * a file. - * @param compactionJobInfo a reference to a native CompactionJobInfo struct, - * which is released after this function is returned, and must be copied - * if it is needed outside of this function. - */ - void onCompactionCompleted(final RocksDB db, final CompactionJobInfo compactionJobInfo); - - /** - * A callback function for RocksDB which will be called whenever - * a SST file is created. Different from OnCompactionCompleted and - * OnFlushCompleted, this callback is designed for external logging - * service and thus only provide string parameters instead - * of a pointer to DB. Applications that build logic basic based - * on file creations and deletions is suggested to implement - * OnFlushCompleted and OnCompactionCompleted. - * - * Historically it will only be called if the file is successfully created. - * Now it will also be called on failure case. User can check info.status - * to see if it succeeded or not. - * - * Note that if applications would like to use the passed reference - * outside this function call, they should make copies from these - * returned value. - * - * @param tableFileCreationInfo the table file creation info, - * contains data copied from respective native structure. - */ - void onTableFileCreated(final TableFileCreationInfo tableFileCreationInfo); - - /** - * A callback function for RocksDB which will be called before - * a SST file is being created. It will follow by OnTableFileCreated after - * the creation finishes. - * - * Note that if applications would like to use the passed reference - * outside this function call, they should make copies from these - * returned value. - * - * @param tableFileCreationBriefInfo the table file creation brief info, - * contains data copied from respective native structure. - */ - void onTableFileCreationStarted(final TableFileCreationBriefInfo tableFileCreationBriefInfo); - - /** - * A callback function for RocksDB which will be called before - * a memtable is made immutable. - * - * Note that the this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * Note that if applications would like to use the passed reference - * outside this function call, they should make copies from these - * returned value. - * - * @param memTableInfo the mem table info, contains data - * copied from respective native structure. - */ - void onMemTableSealed(final MemTableInfo memTableInfo); - - /** - * A callback function for RocksDB which will be called before - * a column family handle is deleted. - * - * Note that the this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * @param columnFamilyHandle is a pointer to the column family handle to be - * deleted which will become a dangling pointer after the deletion. - */ - void onColumnFamilyHandleDeletionStarted(final ColumnFamilyHandle columnFamilyHandle); - - /** - * A callback function for RocksDB which will be called after an external - * file is ingested using IngestExternalFile. - * - * Note that the this function will run on the same thread as - * IngestExternalFile(), if this function is blocked, IngestExternalFile() - * will be blocked from finishing. - * - * @param db the database - * @param externalFileIngestionInfo the external file ingestion info, - * contains data copied from respective native structure. - */ - void onExternalFileIngested( - final RocksDB db, final ExternalFileIngestionInfo externalFileIngestionInfo); - - /** - * A callback function for RocksDB which will be called before setting the - * background error status to a non-OK value. The new background error status - * is provided in `bg_error` and can be modified by the callback. E.g., a - * callback can suppress errors by resetting it to Status::OK(), thus - * preventing the database from entering read-only mode. We do not provide any - * guarantee when failed flushes/compactions will be rescheduled if the user - * suppresses an error. - * - * Note that this function can run on the same threads as flush, compaction, - * and user writes. So, it is extremely important not to perform heavy - * computations or blocking calls in this function. - * - * @param backgroundErrorReason background error reason code - * @param backgroundError background error codes - */ - void onBackgroundError( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError); - - /** - * A callback function for RocksDB which will be called whenever a change - * of superversion triggers a change of the stall conditions. - * - * Note that the this function must be implemented in a way such that - * it should not run for an extended period of time before the function - * returns. Otherwise, RocksDB may be blocked. - * - * @param writeStallInfo write stall info, - * contains data copied from respective native structure. - */ - void onStallConditionsChanged(final WriteStallInfo writeStallInfo); - - /** - * A callback function for RocksDB which will be called whenever a file read - * operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileReadFinish(final FileOperationInfo fileOperationInfo); - - /** - * A callback function for RocksDB which will be called whenever a file write - * operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileWriteFinish(final FileOperationInfo fileOperationInfo); - - /** - * A callback function for RocksDB which will be called whenever a file flush - * operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileFlushFinish(final FileOperationInfo fileOperationInfo); - - /** - * A callback function for RocksDB which will be called whenever a file sync - * operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileSyncFinish(final FileOperationInfo fileOperationInfo); - - /** - * A callback function for RocksDB which will be called whenever a file - * rangeSync operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileRangeSyncFinish(final FileOperationInfo fileOperationInfo); - - /** - * A callback function for RocksDB which will be called whenever a file - * truncate operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileTruncateFinish(final FileOperationInfo fileOperationInfo); - - /** - * A callback function for RocksDB which will be called whenever a file close - * operation finishes. - * - * @param fileOperationInfo file operation info, - * contains data copied from respective native structure. - */ - void onFileCloseFinish(final FileOperationInfo fileOperationInfo); - - /** - * If true, the {@link #onFileReadFinish(FileOperationInfo)} - * and {@link #onFileWriteFinish(FileOperationInfo)} will be called. If - * false, then they won't be called. - * - * Default: false - * - * @return whether to callback when file read/write is finished - */ - boolean shouldBeNotifiedOnFileIO(); - - /** - * A callback function for RocksDB which will be called just before - * starting the automatic recovery process for recoverable background - * errors, such as NoSpace(). The callback can suppress the automatic - * recovery by setting returning false. The database will then - * have to be transitioned out of read-only mode by calling - * RocksDB#resume(). - * - * @param backgroundErrorReason background error reason code - * @param backgroundError background error codes - * @return return {@code false} if the automatic recovery should be suppressed - */ - boolean onErrorRecoveryBegin( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError); - - /** - * A callback function for RocksDB which will be called once the database - * is recovered from read-only mode after an error. When this is called, it - * means normal writes to the database can be issued and the user can - * initiate any further recovery actions needed - * - * @param oldBackgroundError old background error codes - */ - void onErrorRecoveryCompleted(final Status oldBackgroundError); -} diff --git a/java/src/main/java/org/rocksdb/Experimental.java b/java/src/main/java/org/rocksdb/Experimental.java deleted file mode 100644 index 64b404d6f..000000000 --- a/java/src/main/java/org/rocksdb/Experimental.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marks a feature as experimental, meaning that it is likely - * to change or even be removed/re-engineered in the future - */ -@Documented -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.TYPE, ElementType.METHOD}) -public @interface Experimental { - String value(); -} diff --git a/java/src/main/java/org/rocksdb/ExternalFileIngestionInfo.java b/java/src/main/java/org/rocksdb/ExternalFileIngestionInfo.java deleted file mode 100644 index 6b14a8024..000000000 --- a/java/src/main/java/org/rocksdb/ExternalFileIngestionInfo.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class ExternalFileIngestionInfo { - private final String columnFamilyName; - private final String externalFilePath; - private final String internalFilePath; - private final long globalSeqno; - private final TableProperties tableProperties; - - /** - * Access is package private as this will only be constructed from - * C++ via JNI and for testing. - */ - ExternalFileIngestionInfo(final String columnFamilyName, final String externalFilePath, - final String internalFilePath, final long globalSeqno, - final TableProperties tableProperties) { - this.columnFamilyName = columnFamilyName; - this.externalFilePath = externalFilePath; - this.internalFilePath = internalFilePath; - this.globalSeqno = globalSeqno; - this.tableProperties = tableProperties; - } - - /** - * Get the name of the column family. - * - * @return the name of the column family. - */ - public String getColumnFamilyName() { - return columnFamilyName; - } - - /** - * Get the path of the file outside the DB. - * - * @return the path of the file outside the DB. - */ - public String getExternalFilePath() { - return externalFilePath; - } - - /** - * Get the path of the file inside the DB. - * - * @return the path of the file inside the DB. - */ - public String getInternalFilePath() { - return internalFilePath; - } - - /** - * Get the global sequence number assigned to keys in this file. - * - * @return the global sequence number. - */ - public long getGlobalSeqno() { - return globalSeqno; - } - - /** - * Get the Table properties of the table being flushed. - * - * @return the table properties. - */ - public TableProperties getTableProperties() { - return tableProperties; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - ExternalFileIngestionInfo that = (ExternalFileIngestionInfo) o; - return globalSeqno == that.globalSeqno - && Objects.equals(columnFamilyName, that.columnFamilyName) - && Objects.equals(externalFilePath, that.externalFilePath) - && Objects.equals(internalFilePath, that.internalFilePath) - && Objects.equals(tableProperties, that.tableProperties); - } - - @Override - public int hashCode() { - return Objects.hash( - columnFamilyName, externalFilePath, internalFilePath, globalSeqno, tableProperties); - } - - @Override - public String toString() { - return "ExternalFileIngestionInfo{" - + "columnFamilyName='" + columnFamilyName + '\'' + ", externalFilePath='" + externalFilePath - + '\'' + ", internalFilePath='" + internalFilePath + '\'' + ", globalSeqno=" + globalSeqno - + ", tableProperties=" + tableProperties + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/FileOperationInfo.java b/java/src/main/java/org/rocksdb/FileOperationInfo.java deleted file mode 100644 index aa5743ed3..000000000 --- a/java/src/main/java/org/rocksdb/FileOperationInfo.java +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -/** - * Java representation of FileOperationInfo struct from include/rocksdb/listener.h - */ -public class FileOperationInfo { - private final String path; - private final long offset; - private final long length; - private final long startTimestamp; - private final long duration; - private final Status status; - - /** - * Access is private as this will only be constructed from - * C++ via JNI. - */ - FileOperationInfo(final String path, final long offset, final long length, - final long startTimestamp, final long duration, final Status status) { - this.path = path; - this.offset = offset; - this.length = length; - this.startTimestamp = startTimestamp; - this.duration = duration; - this.status = status; - } - - /** - * Get the file path. - * - * @return the file path. - */ - public String getPath() { - return path; - } - - /** - * Get the offset. - * - * @return the offset. - */ - public long getOffset() { - return offset; - } - - /** - * Get the length. - * - * @return the length. - */ - public long getLength() { - return length; - } - - /** - * Get the start timestamp (in nanoseconds). - * - * @return the start timestamp. - */ - public long getStartTimestamp() { - return startTimestamp; - } - - /** - * Get the operation duration (in nanoseconds). - * - * @return the operation duration. - */ - public long getDuration() { - return duration; - } - - /** - * Get the status. - * - * @return the status. - */ - public Status getStatus() { - return status; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - FileOperationInfo that = (FileOperationInfo) o; - return offset == that.offset && length == that.length && startTimestamp == that.startTimestamp - && duration == that.duration && Objects.equals(path, that.path) - && Objects.equals(status, that.status); - } - - @Override - public int hashCode() { - return Objects.hash(path, offset, length, startTimestamp, duration, status); - } - - @Override - public String toString() { - return "FileOperationInfo{" - + "path='" + path + '\'' + ", offset=" + offset + ", length=" + length + ", startTimestamp=" - + startTimestamp + ", duration=" + duration + ", status=" + status + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/Filter.java b/java/src/main/java/org/rocksdb/Filter.java deleted file mode 100644 index 7f490cf59..000000000 --- a/java/src/main/java/org/rocksdb/Filter.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Filters are stored in rocksdb and are consulted automatically - * by rocksdb to decide whether or not to read some - * information from disk. In many cases, a filter can cut down the - * number of disk seeks form a handful to a single disk seek per - * DB::Get() call. - */ -//TODO(AR) should be renamed FilterPolicy -public abstract class Filter extends RocksObject { - - protected Filter(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Deletes underlying C++ filter pointer. - * - * Note that this function should be called only after all - * RocksDB instances referencing the filter are closed. - * Otherwise an undefined behavior will occur. - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - @Override - protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/FlushJobInfo.java b/java/src/main/java/org/rocksdb/FlushJobInfo.java deleted file mode 100644 index ca9aa0523..000000000 --- a/java/src/main/java/org/rocksdb/FlushJobInfo.java +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class FlushJobInfo { - private final long columnFamilyId; - private final String columnFamilyName; - private final String filePath; - private final long threadId; - private final int jobId; - private final boolean triggeredWritesSlowdown; - private final boolean triggeredWritesStop; - private final long smallestSeqno; - private final long largestSeqno; - private final TableProperties tableProperties; - private final FlushReason flushReason; - - /** - * Access is package private as this will only be constructed from - * C++ via JNI and for testing. - */ - FlushJobInfo(final long columnFamilyId, final String columnFamilyName, final String filePath, - final long threadId, final int jobId, final boolean triggeredWritesSlowdown, - final boolean triggeredWritesStop, final long smallestSeqno, final long largestSeqno, - final TableProperties tableProperties, final byte flushReasonValue) { - this.columnFamilyId = columnFamilyId; - this.columnFamilyName = columnFamilyName; - this.filePath = filePath; - this.threadId = threadId; - this.jobId = jobId; - this.triggeredWritesSlowdown = triggeredWritesSlowdown; - this.triggeredWritesStop = triggeredWritesStop; - this.smallestSeqno = smallestSeqno; - this.largestSeqno = largestSeqno; - this.tableProperties = tableProperties; - this.flushReason = FlushReason.fromValue(flushReasonValue); - } - - /** - * Get the id of the column family. - * - * @return the id of the column family - */ - public long getColumnFamilyId() { - return columnFamilyId; - } - - /** - * Get the name of the column family. - * - * @return the name of the column family - */ - public String getColumnFamilyName() { - return columnFamilyName; - } - - /** - * Get the path to the newly created file. - * - * @return the path to the newly created file - */ - public String getFilePath() { - return filePath; - } - - /** - * Get the id of the thread that completed this flush job. - * - * @return the id of the thread that completed this flush job - */ - public long getThreadId() { - return threadId; - } - - /** - * Get the job id, which is unique in the same thread. - * - * @return the job id - */ - public int getJobId() { - return jobId; - } - - /** - * Determine if rocksdb is currently slowing-down all writes to prevent - * creating too many Level 0 files as compaction seems not able to - * catch up the write request speed. - * - * This indicates that there are too many files in Level 0. - * - * @return true if rocksdb is currently slowing-down all writes, - * false otherwise - */ - public boolean isTriggeredWritesSlowdown() { - return triggeredWritesSlowdown; - } - - /** - * Determine if rocksdb is currently blocking any writes to prevent - * creating more L0 files. - * - * This indicates that there are too many files in level 0. - * Compactions should try to compact L0 files down to lower levels as soon - * as possible. - * - * @return true if rocksdb is currently blocking any writes, false otherwise - */ - public boolean isTriggeredWritesStop() { - return triggeredWritesStop; - } - - /** - * Get the smallest sequence number in the newly created file. - * - * @return the smallest sequence number - */ - public long getSmallestSeqno() { - return smallestSeqno; - } - - /** - * Get the largest sequence number in the newly created file. - * - * @return the largest sequence number - */ - public long getLargestSeqno() { - return largestSeqno; - } - - /** - * Get the Table properties of the table being flushed. - * - * @return the Table properties of the table being flushed - */ - public TableProperties getTableProperties() { - return tableProperties; - } - - /** - * Get the reason for initiating the flush. - * - * @return the reason for initiating the flush. - */ - public FlushReason getFlushReason() { - return flushReason; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - FlushJobInfo that = (FlushJobInfo) o; - return columnFamilyId == that.columnFamilyId && threadId == that.threadId && jobId == that.jobId - && triggeredWritesSlowdown == that.triggeredWritesSlowdown - && triggeredWritesStop == that.triggeredWritesStop && smallestSeqno == that.smallestSeqno - && largestSeqno == that.largestSeqno - && Objects.equals(columnFamilyName, that.columnFamilyName) - && Objects.equals(filePath, that.filePath) - && Objects.equals(tableProperties, that.tableProperties) && flushReason == that.flushReason; - } - - @Override - public int hashCode() { - return Objects.hash(columnFamilyId, columnFamilyName, filePath, threadId, jobId, - triggeredWritesSlowdown, triggeredWritesStop, smallestSeqno, largestSeqno, tableProperties, - flushReason); - } - - @Override - public String toString() { - return "FlushJobInfo{" - + "columnFamilyId=" + columnFamilyId + ", columnFamilyName='" + columnFamilyName + '\'' - + ", filePath='" + filePath + '\'' + ", threadId=" + threadId + ", jobId=" + jobId - + ", triggeredWritesSlowdown=" + triggeredWritesSlowdown - + ", triggeredWritesStop=" + triggeredWritesStop + ", smallestSeqno=" + smallestSeqno - + ", largestSeqno=" + largestSeqno + ", tableProperties=" + tableProperties - + ", flushReason=" + flushReason + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/FlushOptions.java b/java/src/main/java/org/rocksdb/FlushOptions.java deleted file mode 100644 index 760b515fd..000000000 --- a/java/src/main/java/org/rocksdb/FlushOptions.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * FlushOptions to be passed to flush operations of - * {@link org.rocksdb.RocksDB}. - */ -public class FlushOptions extends RocksObject { - static { - RocksDB.loadLibrary(); - } - - /** - * Construct a new instance of FlushOptions. - */ - public FlushOptions(){ - super(newFlushOptions()); - } - - /** - * Set if the flush operation shall block until it terminates. - * - * @param waitForFlush boolean value indicating if the flush - * operations waits for termination of the flush process. - * - * @return instance of current FlushOptions. - */ - public FlushOptions setWaitForFlush(final boolean waitForFlush) { - assert(isOwningHandle()); - setWaitForFlush(nativeHandle_, waitForFlush); - return this; - } - - /** - * Wait for flush to finished. - * - * @return boolean value indicating if the flush operation - * waits for termination of the flush process. - */ - public boolean waitForFlush() { - assert(isOwningHandle()); - return waitForFlush(nativeHandle_); - } - - /** - * Set to true so that flush would proceeds immediately even it it means - * writes will stall for the duration of the flush. - * - * Set to false so that the operation will wait until it's possible to do - * the flush without causing stall or until required flush is performed by - * someone else (foreground call or background thread). - * - * Default: false - * - * @param allowWriteStall true to allow writes to stall for flush, false - * otherwise. - * - * @return instance of current FlushOptions. - */ - public FlushOptions setAllowWriteStall(final boolean allowWriteStall) { - assert(isOwningHandle()); - setAllowWriteStall(nativeHandle_, allowWriteStall); - return this; - } - - /** - * Returns true if writes are allowed to stall for flushes to complete, false - * otherwise. - * - * @return true if writes are allowed to stall for flushes - */ - public boolean allowWriteStall() { - assert(isOwningHandle()); - return allowWriteStall(nativeHandle_); - } - - private native static long newFlushOptions(); - @Override protected final native void disposeInternal(final long handle); - - private native void setWaitForFlush(final long handle, - final boolean wait); - private native boolean waitForFlush(final long handle); - private native void setAllowWriteStall(final long handle, - final boolean allowWriteStall); - private native boolean allowWriteStall(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/FlushReason.java b/java/src/main/java/org/rocksdb/FlushReason.java deleted file mode 100644 index 9d486cda1..000000000 --- a/java/src/main/java/org/rocksdb/FlushReason.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum FlushReason { - OTHERS((byte) 0x00), - GET_LIVE_FILES((byte) 0x01), - SHUTDOWN((byte) 0x02), - EXTERNAL_FILE_INGESTION((byte) 0x03), - MANUAL_COMPACTION((byte) 0x04), - WRITE_BUFFER_MANAGER((byte) 0x05), - WRITE_BUFFER_FULL((byte) 0x06), - TEST((byte) 0x07), - DELETE_FILES((byte) 0x08), - AUTO_COMPACTION((byte) 0x09), - MANUAL_FLUSH((byte) 0x0a), - ERROR_RECOVERY((byte) 0xb); - - private final byte value; - - FlushReason(final byte value) { - this.value = value; - } - - /** - * Get the internal representation. - * - * @return the internal representation - */ - byte getValue() { - return value; - } - - /** - * Get the FlushReason from the internal representation value. - * - * @return the flush reason. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static FlushReason fromValue(final byte value) { - for (final FlushReason flushReason : FlushReason.values()) { - if (flushReason.value == value) { - return flushReason; - } - } - - throw new IllegalArgumentException("Illegal value provided for FlushReason: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java b/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java deleted file mode 100644 index 05cc2bb90..000000000 --- a/java/src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * The config for hash linked list memtable representation - * Such memtable contains a fix-sized array of buckets, where - * each bucket points to a sorted singly-linked - * list (or null if the bucket is empty). - * - * Note that since this mem-table representation relies on the - * key prefix, it is required to invoke one of the usePrefixExtractor - * functions to specify how to extract key prefix given a key. - * If proper prefix-extractor is not set, then RocksDB will - * use the default memtable representation (SkipList) instead - * and post a warning in the LOG. - */ -public class HashLinkedListMemTableConfig extends MemTableConfig { - public static final long DEFAULT_BUCKET_COUNT = 50000; - public static final long DEFAULT_HUGE_PAGE_TLB_SIZE = 0; - public static final int DEFAULT_BUCKET_ENTRIES_LOG_THRES = 4096; - public static final boolean - DEFAULT_IF_LOG_BUCKET_DIST_WHEN_FLUSH = true; - public static final int DEFAUL_THRESHOLD_USE_SKIPLIST = 256; - - /** - * HashLinkedListMemTableConfig constructor - */ - public HashLinkedListMemTableConfig() { - bucketCount_ = DEFAULT_BUCKET_COUNT; - hugePageTlbSize_ = DEFAULT_HUGE_PAGE_TLB_SIZE; - bucketEntriesLoggingThreshold_ = DEFAULT_BUCKET_ENTRIES_LOG_THRES; - ifLogBucketDistWhenFlush_ = DEFAULT_IF_LOG_BUCKET_DIST_WHEN_FLUSH; - thresholdUseSkiplist_ = DEFAUL_THRESHOLD_USE_SKIPLIST; - } - - /** - * Set the number of buckets in the fixed-size array used - * in the hash linked-list mem-table. - * - * @param count the number of hash buckets. - * @return the reference to the current HashLinkedListMemTableConfig. - */ - public HashLinkedListMemTableConfig setBucketCount( - final long count) { - bucketCount_ = count; - return this; - } - - /** - * Returns the number of buckets that will be used in the memtable - * created based on this config. - * - * @return the number of buckets - */ - public long bucketCount() { - return bucketCount_; - } - - /** - *

    Set the size of huge tlb or allocate the hashtable bytes from - * malloc if {@code size <= 0}.

    - * - *

    The user needs to reserve huge pages for it to be allocated, - * like: {@code sysctl -w vm.nr_hugepages=20}

    - * - *

    See linux documentation/vm/hugetlbpage.txt

    - * - * @param size if set to {@code <= 0} hashtable bytes from malloc - * @return the reference to the current HashLinkedListMemTableConfig. - */ - public HashLinkedListMemTableConfig setHugePageTlbSize( - final long size) { - hugePageTlbSize_ = size; - return this; - } - - /** - * Returns the size value of hugePageTlbSize. - * - * @return the hugePageTlbSize. - */ - public long hugePageTlbSize() { - return hugePageTlbSize_; - } - - /** - * If number of entries in one bucket exceeds that setting, log - * about it. - * - * @param threshold - number of entries in a single bucket before - * logging starts. - * @return the reference to the current HashLinkedListMemTableConfig. - */ - public HashLinkedListMemTableConfig - setBucketEntriesLoggingThreshold(final int threshold) { - bucketEntriesLoggingThreshold_ = threshold; - return this; - } - - /** - * Returns the maximum number of entries in one bucket before - * logging starts. - * - * @return maximum number of entries in one bucket before logging - * starts. - */ - public int bucketEntriesLoggingThreshold() { - return bucketEntriesLoggingThreshold_; - } - - /** - * If true the distrubition of number of entries will be logged. - * - * @param logDistribution - boolean parameter indicating if number - * of entry distribution shall be logged. - * @return the reference to the current HashLinkedListMemTableConfig. - */ - public HashLinkedListMemTableConfig - setIfLogBucketDistWhenFlush(final boolean logDistribution) { - ifLogBucketDistWhenFlush_ = logDistribution; - return this; - } - - /** - * Returns information about logging the distribution of - * number of entries on flush. - * - * @return if distribution of number of entries shall be logged. - */ - public boolean ifLogBucketDistWhenFlush() { - return ifLogBucketDistWhenFlush_; - } - - /** - * Set maximum number of entries in one bucket. Exceeding this val - * leads to a switch from LinkedList to SkipList. - * - * @param threshold maximum number of entries before SkipList is - * used. - * @return the reference to the current HashLinkedListMemTableConfig. - */ - public HashLinkedListMemTableConfig - setThresholdUseSkiplist(final int threshold) { - thresholdUseSkiplist_ = threshold; - return this; - } - - /** - * Returns entries per bucket threshold before LinkedList is - * replaced by SkipList usage for that bucket. - * - * @return entries per bucket threshold before SkipList is used. - */ - public int thresholdUseSkiplist() { - return thresholdUseSkiplist_; - } - - @Override protected long newMemTableFactoryHandle() { - return newMemTableFactoryHandle(bucketCount_, hugePageTlbSize_, - bucketEntriesLoggingThreshold_, ifLogBucketDistWhenFlush_, - thresholdUseSkiplist_); - } - - private native long newMemTableFactoryHandle(long bucketCount, - long hugePageTlbSize, int bucketEntriesLoggingThreshold, - boolean ifLogBucketDistWhenFlush, int thresholdUseSkiplist) - throws IllegalArgumentException; - - private long bucketCount_; - private long hugePageTlbSize_; - private int bucketEntriesLoggingThreshold_; - private boolean ifLogBucketDistWhenFlush_; - private int thresholdUseSkiplist_; -} diff --git a/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java b/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java deleted file mode 100644 index efc78b14e..000000000 --- a/java/src/main/java/org/rocksdb/HashSkipListMemTableConfig.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * The config for hash skip-list mem-table representation. - * Such mem-table representation contains a fix-sized array of - * buckets, where each bucket points to a skiplist (or null if the - * bucket is empty). - * - * Note that since this mem-table representation relies on the - * key prefix, it is required to invoke one of the usePrefixExtractor - * functions to specify how to extract key prefix given a key. - * If proper prefix-extractor is not set, then RocksDB will - * use the default memtable representation (SkipList) instead - * and post a warning in the LOG. - */ -public class HashSkipListMemTableConfig extends MemTableConfig { - public static final int DEFAULT_BUCKET_COUNT = 1000000; - public static final int DEFAULT_BRANCHING_FACTOR = 4; - public static final int DEFAULT_HEIGHT = 4; - - /** - * HashSkipListMemTableConfig constructor - */ - public HashSkipListMemTableConfig() { - bucketCount_ = DEFAULT_BUCKET_COUNT; - branchingFactor_ = DEFAULT_BRANCHING_FACTOR; - height_ = DEFAULT_HEIGHT; - } - - /** - * Set the number of hash buckets used in the hash skiplist memtable. - * Default = 1000000. - * - * @param count the number of hash buckets used in the hash - * skiplist memtable. - * @return the reference to the current HashSkipListMemTableConfig. - */ - public HashSkipListMemTableConfig setBucketCount( - final long count) { - bucketCount_ = count; - return this; - } - - /** - * @return the number of hash buckets - */ - public long bucketCount() { - return bucketCount_; - } - - /** - * Set the height of the skip list. Default = 4. - * - * @param height height to set. - * - * @return the reference to the current HashSkipListMemTableConfig. - */ - public HashSkipListMemTableConfig setHeight(final int height) { - height_ = height; - return this; - } - - /** - * @return the height of the skip list. - */ - public int height() { - return height_; - } - - /** - * Set the branching factor used in the hash skip-list memtable. - * This factor controls the probabilistic size ratio between adjacent - * links in the skip list. - * - * @param bf the probabilistic size ratio between adjacent link - * lists in the skip list. - * @return the reference to the current HashSkipListMemTableConfig. - */ - public HashSkipListMemTableConfig setBranchingFactor( - final int bf) { - branchingFactor_ = bf; - return this; - } - - /** - * @return branching factor, the probabilistic size ratio between - * adjacent links in the skip list. - */ - public int branchingFactor() { - return branchingFactor_; - } - - @Override protected long newMemTableFactoryHandle() { - return newMemTableFactoryHandle( - bucketCount_, height_, branchingFactor_); - } - - private native long newMemTableFactoryHandle( - long bucketCount, int height, int branchingFactor) - throws IllegalArgumentException; - - private long bucketCount_; - private int branchingFactor_; - private int height_; -} diff --git a/java/src/main/java/org/rocksdb/HistogramData.java b/java/src/main/java/org/rocksdb/HistogramData.java deleted file mode 100644 index 81d890883..000000000 --- a/java/src/main/java/org/rocksdb/HistogramData.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class HistogramData { - private final double median_; - private final double percentile95_; - private final double percentile99_; - private final double average_; - private final double standardDeviation_; - private final double max_; - private final long count_; - private final long sum_; - private final double min_; - - public HistogramData(final double median, final double percentile95, - final double percentile99, final double average, - final double standardDeviation) { - this(median, percentile95, percentile99, average, standardDeviation, 0.0, 0, 0, 0.0); - } - - public HistogramData(final double median, final double percentile95, - final double percentile99, final double average, - final double standardDeviation, final double max, final long count, - final long sum, final double min) { - median_ = median; - percentile95_ = percentile95; - percentile99_ = percentile99; - average_ = average; - standardDeviation_ = standardDeviation; - min_ = min; - max_ = max; - count_ = count; - sum_ = sum; - } - - public double getMedian() { - return median_; - } - - public double getPercentile95() { - return percentile95_; - } - - public double getPercentile99() { - return percentile99_; - } - - public double getAverage() { - return average_; - } - - public double getStandardDeviation() { - return standardDeviation_; - } - - public double getMax() { - return max_; - } - - public long getCount() { - return count_; - } - - public long getSum() { - return sum_; - } - - public double getMin() { - return min_; - } -} diff --git a/java/src/main/java/org/rocksdb/HistogramType.java b/java/src/main/java/org/rocksdb/HistogramType.java deleted file mode 100644 index 20c54422c..000000000 --- a/java/src/main/java/org/rocksdb/HistogramType.java +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum HistogramType { - - DB_GET((byte) 0x0), - - DB_WRITE((byte) 0x1), - - COMPACTION_TIME((byte) 0x2), - - SUBCOMPACTION_SETUP_TIME((byte) 0x3), - - TABLE_SYNC_MICROS((byte) 0x4), - - COMPACTION_OUTFILE_SYNC_MICROS((byte) 0x5), - - WAL_FILE_SYNC_MICROS((byte) 0x6), - - MANIFEST_FILE_SYNC_MICROS((byte) 0x7), - - /** - * TIME SPENT IN IO DURING TABLE OPEN. - */ - TABLE_OPEN_IO_MICROS((byte) 0x8), - - DB_MULTIGET((byte) 0x9), - - READ_BLOCK_COMPACTION_MICROS((byte) 0xA), - - READ_BLOCK_GET_MICROS((byte) 0xB), - - WRITE_RAW_BLOCK_MICROS((byte) 0xC), - - NUM_FILES_IN_SINGLE_COMPACTION((byte) 0x12), - - DB_SEEK((byte) 0x13), - - WRITE_STALL((byte) 0x14), - - SST_READ_MICROS((byte) 0x15), - - /** - * The number of subcompactions actually scheduled during a compaction. - */ - NUM_SUBCOMPACTIONS_SCHEDULED((byte) 0x16), - - /** - * Value size distribution in each operation. - */ - BYTES_PER_READ((byte) 0x17), - BYTES_PER_WRITE((byte) 0x18), - BYTES_PER_MULTIGET((byte) 0x19), - - /** - * number of bytes compressed. - */ - BYTES_COMPRESSED((byte) 0x1A), - - /** - * number of bytes decompressed. - * - * number of bytes is when uncompressed; i.e. before/after respectively - */ - BYTES_DECOMPRESSED((byte) 0x1B), - - COMPRESSION_TIMES_NANOS((byte) 0x1C), - - DECOMPRESSION_TIMES_NANOS((byte) 0x1D), - - READ_NUM_MERGE_OPERANDS((byte) 0x1E), - - /** - * Time spent flushing memtable to disk. - */ - FLUSH_TIME((byte) 0x20), - - /** - * Size of keys written to BlobDB. - */ - BLOB_DB_KEY_SIZE((byte) 0x21), - - /** - * Size of values written to BlobDB. - */ - BLOB_DB_VALUE_SIZE((byte) 0x22), - - /** - * BlobDB Put/PutWithTTL/PutUntil/Write latency. - */ - BLOB_DB_WRITE_MICROS((byte) 0x23), - - /** - * BlobDB Get lagency. - */ - BLOB_DB_GET_MICROS((byte) 0x24), - - /** - * BlobDB MultiGet latency. - */ - BLOB_DB_MULTIGET_MICROS((byte) 0x25), - - /** - * BlobDB Seek/SeekToFirst/SeekToLast/SeekForPrev latency. - */ - BLOB_DB_SEEK_MICROS((byte) 0x26), - - /** - * BlobDB Next latency. - */ - BLOB_DB_NEXT_MICROS((byte) 0x27), - - /** - * BlobDB Prev latency. - */ - BLOB_DB_PREV_MICROS((byte) 0x28), - - /** - * Blob file write latency. - */ - BLOB_DB_BLOB_FILE_WRITE_MICROS((byte) 0x29), - - /** - * Blob file read latency. - */ - BLOB_DB_BLOB_FILE_READ_MICROS((byte) 0x2A), - - /** - * Blob file sync latency. - */ - BLOB_DB_BLOB_FILE_SYNC_MICROS((byte) 0x2B), - - /** - * BlobDB compression time. - */ - BLOB_DB_COMPRESSION_MICROS((byte) 0x2D), - - /** - * BlobDB decompression time. - */ - BLOB_DB_DECOMPRESSION_MICROS((byte) 0x2E), - - /** - * Num of Index and Filter blocks read from file system per level in MultiGet - * request - */ - NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL((byte) 0x2F), - - /** - * Num of SST files read from file system per level in MultiGet request. - */ - NUM_SST_READ_PER_LEVEL((byte) 0x31), - - /** - * The number of retry in auto resume - */ - ERROR_HANDLER_AUTORESUME_RETRY_COUNT((byte) 0x32), - - ASYNC_READ_BYTES((byte) 0x33), - - /** - * Number of bytes read for RocksDB's prefetching contents - * (as opposed to file system's prefetch) - * from the end of SST table during block based table open - */ - TABLE_OPEN_PREFETCH_TAIL_READ_BYTES((byte) 0x39), - - // 0x1F for backwards compatibility on current minor version. - HISTOGRAM_ENUM_MAX((byte) 0x1F); - - private final byte value; - - HistogramType(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get Histogram type by byte value. - * - * @param value byte representation of HistogramType. - * - * @return {@link org.rocksdb.HistogramType} instance. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static HistogramType getHistogramType(final byte value) { - for (final HistogramType histogramType : HistogramType.values()) { - if (histogramType.getValue() == value) { - return histogramType; - } - } - throw new IllegalArgumentException( - "Illegal value provided for HistogramType."); - } -} diff --git a/java/src/main/java/org/rocksdb/Holder.java b/java/src/main/java/org/rocksdb/Holder.java deleted file mode 100644 index 716a0bda0..000000000 --- a/java/src/main/java/org/rocksdb/Holder.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2016, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Simple instance reference wrapper. - */ -public class Holder { - private /* @Nullable */ T value; - - /** - * Constructs a new Holder with null instance. - */ - public Holder() { - } - - /** - * Constructs a new Holder. - * - * @param value the instance or null - */ - public Holder(/* @Nullable */ final T value) { - this.value = value; - } - - /** - * Get the instance reference. - * - * @return value the instance reference or null - */ - public /* @Nullable */ T getValue() { - return value; - } - - /** - * Set the instance reference. - * - * @param value the instance reference or null - */ - public void setValue(/* @Nullable */ final T value) { - this.value = value; - } -} diff --git a/java/src/main/java/org/rocksdb/IndexShorteningMode.java b/java/src/main/java/org/rocksdb/IndexShorteningMode.java deleted file mode 100644 index a68346c38..000000000 --- a/java/src/main/java/org/rocksdb/IndexShorteningMode.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * This enum allows trading off increased index size for improved iterator - * seek performance in some situations, particularly when block cache is - * disabled ({@link ReadOptions#fillCache()} == false and direct IO is - * enabled ({@link DBOptions#useDirectReads()} == true). - * The default mode is the best tradeoff for most use cases. - * This option only affects newly written tables. - * - * The index contains a key separating each pair of consecutive blocks. - * Let A be the highest key in one block, B the lowest key in the next block, - * and I the index entry separating these two blocks: - * [ ... A] I [B ...] - * I is allowed to be anywhere in [A, B). - * If an iterator is seeked to a key in (A, I], we'll unnecessarily read the - * first block, then immediately fall through to the second block. - * However, if I=A, this can't happen, and we'll read only the second block. - * In kNoShortening mode, we use I=A. In other modes, we use the shortest - * key in [A, B), which usually significantly reduces index size. - * - * There's a similar story for the last index entry, which is an upper bound - * of the highest key in the file. If it's shortened and therefore - * overestimated, iterator is likely to unnecessarily read the last data block - * from each file on each seek. - */ -public enum IndexShorteningMode { - /** - * Use full keys. - */ - kNoShortening((byte) 0), - /** - * Shorten index keys between blocks, but use full key for the last index - * key, which is the upper bound of the whole file. - */ - kShortenSeparators((byte) 1), - /** - * Shorten both keys between blocks and key after last block. - */ - kShortenSeparatorsAndSuccessor((byte) 2); - - private final byte value; - - IndexShorteningMode(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value. - * - * @return byte representation - */ - byte getValue() { - return value; - } -} diff --git a/java/src/main/java/org/rocksdb/IndexType.java b/java/src/main/java/org/rocksdb/IndexType.java deleted file mode 100644 index 162edad1b..000000000 --- a/java/src/main/java/org/rocksdb/IndexType.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * IndexType used in conjunction with BlockBasedTable. - */ -public enum IndexType { - /** - * A space efficient index block that is optimized for - * binary-search-based index. - */ - kBinarySearch((byte) 0), - /** - * The hash index, if enabled, will do the hash lookup when - * {@code Options.prefix_extractor} is provided. - */ - kHashSearch((byte) 1), - /** - * A two-level index implementation. Both levels are binary search indexes. - */ - kTwoLevelIndexSearch((byte) 2), - /** - * Like {@link #kBinarySearch}, but index also contains first key of each block. - * This allows iterators to defer reading the block until it's actually - * needed. May significantly reduce read amplification of short range scans. - * Without it, iterator seek usually reads one block from each level-0 file - * and from each level, which may be expensive. - * Works best in combination with: - * - IndexShorteningMode::kNoShortening, - * - custom FlushBlockPolicy to cut blocks at some meaningful boundaries, - * e.g. when prefix changes. - * Makes the index significantly bigger (2x or more), especially when keys - * are long. - */ - kBinarySearchWithFirstKey((byte) 3); - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - IndexType(byte value) { - value_ = value; - } - - private final byte value_; -} diff --git a/java/src/main/java/org/rocksdb/InfoLogLevel.java b/java/src/main/java/org/rocksdb/InfoLogLevel.java deleted file mode 100644 index b7c0f0700..000000000 --- a/java/src/main/java/org/rocksdb/InfoLogLevel.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * RocksDB log levels. - */ -public enum InfoLogLevel { - DEBUG_LEVEL((byte)0), - INFO_LEVEL((byte)1), - WARN_LEVEL((byte)2), - ERROR_LEVEL((byte)3), - FATAL_LEVEL((byte)4), - HEADER_LEVEL((byte)5), - NUM_INFO_LOG_LEVELS((byte)6); - - private final byte value_; - - private InfoLogLevel(final byte value) { - value_ = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - /** - * Get InfoLogLevel by byte value. - * - * @param value byte representation of InfoLogLevel. - * - * @return {@link org.rocksdb.InfoLogLevel} instance. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static InfoLogLevel getInfoLogLevel(final byte value) { - for (final InfoLogLevel infoLogLevel : InfoLogLevel.values()) { - if (infoLogLevel.getValue() == value) { - return infoLogLevel; - } - } - throw new IllegalArgumentException( - "Illegal value provided for InfoLogLevel."); - } -} diff --git a/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java b/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java deleted file mode 100644 index a6a308daa..000000000 --- a/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java +++ /dev/null @@ -1,227 +0,0 @@ -package org.rocksdb; -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -import java.util.List; - -/** - * IngestExternalFileOptions is used by - * {@link RocksDB#ingestExternalFile(ColumnFamilyHandle, List, IngestExternalFileOptions)}. - */ -public class IngestExternalFileOptions extends RocksObject { - - public IngestExternalFileOptions() { - super(newIngestExternalFileOptions()); - } - - /** - * @param moveFiles {@link #setMoveFiles(boolean)} - * @param snapshotConsistency {@link #setSnapshotConsistency(boolean)} - * @param allowGlobalSeqNo {@link #setAllowGlobalSeqNo(boolean)} - * @param allowBlockingFlush {@link #setAllowBlockingFlush(boolean)} - */ - public IngestExternalFileOptions(final boolean moveFiles, - final boolean snapshotConsistency, final boolean allowGlobalSeqNo, - final boolean allowBlockingFlush) { - super(newIngestExternalFileOptions(moveFiles, snapshotConsistency, - allowGlobalSeqNo, allowBlockingFlush)); - } - - /** - * Can be set to true to move the files instead of copying them. - * - * @return true if files will be moved - */ - public boolean moveFiles() { - return moveFiles(nativeHandle_); - } - - /** - * Can be set to true to move the files instead of copying them. - * - * @param moveFiles true if files should be moved instead of copied - * - * @return the reference to the current IngestExternalFileOptions. - */ - public IngestExternalFileOptions setMoveFiles(final boolean moveFiles) { - setMoveFiles(nativeHandle_, moveFiles); - return this; - } - - /** - * If set to false, an ingested file keys could appear in existing snapshots - * that where created before the file was ingested. - * - * @return true if snapshot consistency is assured - */ - public boolean snapshotConsistency() { - return snapshotConsistency(nativeHandle_); - } - - /** - * If set to false, an ingested file keys could appear in existing snapshots - * that where created before the file was ingested. - * - * @param snapshotConsistency true if snapshot consistency is required - * - * @return the reference to the current IngestExternalFileOptions. - */ - public IngestExternalFileOptions setSnapshotConsistency( - final boolean snapshotConsistency) { - setSnapshotConsistency(nativeHandle_, snapshotConsistency); - return this; - } - - /** - * If set to false, {@link RocksDB#ingestExternalFile(ColumnFamilyHandle, List, IngestExternalFileOptions)} - * will fail if the file key range overlaps with existing keys or tombstones in the DB. - * - * @return true if global seq numbers are assured - */ - public boolean allowGlobalSeqNo() { - return allowGlobalSeqNo(nativeHandle_); - } - - /** - * If set to false, {@link RocksDB#ingestExternalFile(ColumnFamilyHandle, List, IngestExternalFileOptions)} - * will fail if the file key range overlaps with existing keys or tombstones in the DB. - * - * @param allowGlobalSeqNo true if global seq numbers are required - * - * @return the reference to the current IngestExternalFileOptions. - */ - public IngestExternalFileOptions setAllowGlobalSeqNo( - final boolean allowGlobalSeqNo) { - setAllowGlobalSeqNo(nativeHandle_, allowGlobalSeqNo); - return this; - } - - /** - * If set to false and the file key range overlaps with the memtable key range - * (memtable flush required), IngestExternalFile will fail. - * - * @return true if blocking flushes may occur - */ - public boolean allowBlockingFlush() { - return allowBlockingFlush(nativeHandle_); - } - - /** - * If set to false and the file key range overlaps with the memtable key range - * (memtable flush required), IngestExternalFile will fail. - * - * @param allowBlockingFlush true if blocking flushes are allowed - * - * @return the reference to the current IngestExternalFileOptions. - */ - public IngestExternalFileOptions setAllowBlockingFlush( - final boolean allowBlockingFlush) { - setAllowBlockingFlush(nativeHandle_, allowBlockingFlush); - return this; - } - - /** - * Returns true if duplicate keys in the file being ingested are - * to be skipped rather than overwriting existing data under that key. - * - * @return true if duplicate keys in the file being ingested are to be - * skipped, false otherwise. - */ - public boolean ingestBehind() { - return ingestBehind(nativeHandle_); - } - - /** - * Set to true if you would like duplicate keys in the file being ingested - * to be skipped rather than overwriting existing data under that key. - * - * Usecase: back-fill of some historical data in the database without - * over-writing existing newer version of data. - * - * This option could only be used if the DB has been running - * with DBOptions#allowIngestBehind() == true since the dawn of time. - * - * All files will be ingested at the bottommost level with seqno=0. - * - * Default: false - * - * @param ingestBehind true if you would like duplicate keys in the file being - * ingested to be skipped. - * - * @return the reference to the current IngestExternalFileOptions. - */ - public IngestExternalFileOptions setIngestBehind(final boolean ingestBehind) { - setIngestBehind(nativeHandle_, ingestBehind); - return this; - } - - /** - * Returns true write if the global_seqno is written to a given offset - * in the external SST file for backward compatibility. - * - * See {@link #setWriteGlobalSeqno(boolean)}. - * - * @return true if the global_seqno is written to a given offset, - * false otherwise. - */ - public boolean writeGlobalSeqno() { - return writeGlobalSeqno(nativeHandle_); - } - - /** - * Set to true if you would like to write the global_seqno to a given offset - * in the external SST file for backward compatibility. - * - * Older versions of RocksDB write the global_seqno to a given offset within - * the ingested SST files, and new versions of RocksDB do not. - * - * If you ingest an external SST using new version of RocksDB and would like - * to be able to downgrade to an older version of RocksDB, you should set - * {@link #writeGlobalSeqno()} to true. - * - * If your service is just starting to use the new RocksDB, we recommend that - * you set this option to false, which brings two benefits: - * 1. No extra random write for global_seqno during ingestion. - * 2. Without writing external SST file, it's possible to do checksum. - * - * We have a plan to set this option to false by default in the future. - * - * Default: true - * - * @param writeGlobalSeqno true to write the gloal_seqno to a given offset, - * false otherwise - * - * @return the reference to the current IngestExternalFileOptions. - */ - public IngestExternalFileOptions setWriteGlobalSeqno( - final boolean writeGlobalSeqno) { - setWriteGlobalSeqno(nativeHandle_, writeGlobalSeqno); - return this; - } - - private native static long newIngestExternalFileOptions(); - private native static long newIngestExternalFileOptions( - final boolean moveFiles, final boolean snapshotConsistency, - final boolean allowGlobalSeqNo, final boolean allowBlockingFlush); - @Override protected final native void disposeInternal(final long handle); - - private native boolean moveFiles(final long handle); - private native void setMoveFiles(final long handle, final boolean move_files); - private native boolean snapshotConsistency(final long handle); - private native void setSnapshotConsistency(final long handle, - final boolean snapshotConsistency); - private native boolean allowGlobalSeqNo(final long handle); - private native void setAllowGlobalSeqNo(final long handle, - final boolean allowGloablSeqNo); - private native boolean allowBlockingFlush(final long handle); - private native void setAllowBlockingFlush(final long handle, - final boolean allowBlockingFlush); - private native boolean ingestBehind(final long handle); - private native void setIngestBehind(final long handle, - final boolean ingestBehind); - private native boolean writeGlobalSeqno(final long handle); - private native void setWriteGlobalSeqno(final long handle, - final boolean writeGlobalSeqNo); -} diff --git a/java/src/main/java/org/rocksdb/KeyMayExist.java b/java/src/main/java/org/rocksdb/KeyMayExist.java deleted file mode 100644 index 36185d8c9..000000000 --- a/java/src/main/java/org/rocksdb/KeyMayExist.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class KeyMayExist { - @Override - public boolean equals(final Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - final KeyMayExist that = (KeyMayExist) o; - return (valueLength == that.valueLength && exists == that.exists); - } - - @Override - public int hashCode() { - return Objects.hash(exists, valueLength); - } - - public enum KeyMayExistEnum { kNotExist, kExistsWithoutValue, kExistsWithValue } - ; - - public KeyMayExist(final KeyMayExistEnum exists, final int valueLength) { - this.exists = exists; - this.valueLength = valueLength; - } - - public final KeyMayExistEnum exists; - public final int valueLength; -} diff --git a/java/src/main/java/org/rocksdb/LRUCache.java b/java/src/main/java/org/rocksdb/LRUCache.java deleted file mode 100644 index db90b17c5..000000000 --- a/java/src/main/java/org/rocksdb/LRUCache.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Least Recently Used Cache - */ -public class LRUCache extends Cache { - - /** - * Create a new cache with a fixed size capacity - * - * @param capacity The fixed size capacity of the cache - */ - public LRUCache(final long capacity) { - this(capacity, -1, false, 0.0, 0.0); - } - - /** - * Create a new cache with a fixed size capacity. The cache is sharded - * to 2^numShardBits shards, by hash of the key. The total capacity - * is divided and evenly assigned to each shard. - * numShardBits = -1 means it is automatically determined: every shard - * will be at least 512KB and number of shard bits will not exceed 6. - * - * @param capacity The fixed size capacity of the cache - * @param numShardBits The cache is sharded to 2^numShardBits shards, - * by hash of the key - */ - public LRUCache(final long capacity, final int numShardBits) { - super(newLRUCache(capacity, numShardBits, false, 0.0, 0.0)); - } - - /** - * Create a new cache with a fixed size capacity. The cache is sharded - * to 2^numShardBits shards, by hash of the key. The total capacity - * is divided and evenly assigned to each shard. If strictCapacityLimit - * is set, insert to the cache will fail when cache is full. - * numShardBits = -1 means it is automatically determined: every shard - * will be at least 512KB and number of shard bits will not exceed 6. - * - * @param capacity The fixed size capacity of the cache - * @param numShardBits The cache is sharded to 2^numShardBits shards, - * by hash of the key - * @param strictCapacityLimit insert to the cache will fail when cache is full - */ - public LRUCache(final long capacity, final int numShardBits, - final boolean strictCapacityLimit) { - super(newLRUCache(capacity, numShardBits, strictCapacityLimit, 0.0, 0.0)); - } - - /** - * Create a new cache with a fixed size capacity. The cache is sharded - * to 2^numShardBits shards, by hash of the key. The total capacity - * is divided and evenly assigned to each shard. If strictCapacityLimit - * is set, insert to the cache will fail when cache is full. User can also - * set percentage of the cache reserves for high priority entries via - * highPriPoolRatio. - * numShardBits = -1 means it is automatically determined: every shard - * will be at least 512KB and number of shard bits will not exceed 6. - * - * @param capacity The fixed size capacity of the cache - * @param numShardBits The cache is sharded to 2^numShardBits shards, - * by hash of the key - * @param strictCapacityLimit insert to the cache will fail when cache is full - * @param highPriPoolRatio percentage of the cache reserves for high priority - * entries - */ - public LRUCache(final long capacity, final int numShardBits, final boolean strictCapacityLimit, - final double highPriPoolRatio) { - super(newLRUCache(capacity, numShardBits, strictCapacityLimit, highPriPoolRatio, 0.0)); - } - - /** - * Create a new cache with a fixed size capacity. The cache is sharded - * to 2^numShardBits shards, by hash of the key. The total capacity - * is divided and evenly assigned to each shard. If strictCapacityLimit - * is set, insert to the cache will fail when cache is full. User can also - * set percentage of the cache reserves for high priority entries and low - * priority entries via highPriPoolRatio and lowPriPoolRatio. - * numShardBits = -1 means it is automatically determined: every shard - * will be at least 512KB and number of shard bits will not exceed 6. - * - * @param capacity The fixed size capacity of the cache - * @param numShardBits The cache is sharded to 2^numShardBits shards, - * by hash of the key - * @param strictCapacityLimit insert to the cache will fail when cache is full - * @param highPriPoolRatio percentage of the cache reserves for high priority - * entries - * @param lowPriPoolRatio percentage of the cache reserves for low priority - * entries - */ - public LRUCache(final long capacity, final int numShardBits, final boolean strictCapacityLimit, - final double highPriPoolRatio, final double lowPriPoolRatio) { - super(newLRUCache( - capacity, numShardBits, strictCapacityLimit, highPriPoolRatio, lowPriPoolRatio)); - } - - private native static long newLRUCache(final long capacity, final int numShardBits, - final boolean strictCapacityLimit, final double highPriPoolRatio, - final double lowPriPoolRatio); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/LevelMetaData.java b/java/src/main/java/org/rocksdb/LevelMetaData.java deleted file mode 100644 index c5685098b..000000000 --- a/java/src/main/java/org/rocksdb/LevelMetaData.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.List; - -/** - * The metadata that describes a level. - */ -public class LevelMetaData { - private final int level; - private final long size; - private final SstFileMetaData[] files; - - /** - * Called from JNI C++ - */ - private LevelMetaData(final int level, final long size, - final SstFileMetaData[] files) { - this.level = level; - this.size = size; - this.files = files; - } - - /** - * The level which this meta data describes. - * - * @return the level - */ - public int level() { - return level; - } - - /** - * The size of this level in bytes, which is equal to the sum of - * the file size of its {@link #files()}. - * - * @return the size - */ - public long size() { - return size; - } - - /** - * The metadata of all sst files in this level. - * - * @return the metadata of the files - */ - public List files() { - return Arrays.asList(files); - } -} diff --git a/java/src/main/java/org/rocksdb/LiveFileMetaData.java b/java/src/main/java/org/rocksdb/LiveFileMetaData.java deleted file mode 100644 index 35d883e18..000000000 --- a/java/src/main/java/org/rocksdb/LiveFileMetaData.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The full set of metadata associated with each SST file. - */ -public class LiveFileMetaData extends SstFileMetaData { - private final byte[] columnFamilyName; - private final int level; - - /** - * Called from JNI C++ - */ - private LiveFileMetaData( - final byte[] columnFamilyName, - final int level, - final String fileName, - final String path, - final long size, - final long smallestSeqno, - final long largestSeqno, - final byte[] smallestKey, - final byte[] largestKey, - final long numReadsSampled, - final boolean beingCompacted, - final long numEntries, - final long numDeletions) { - super(fileName, path, size, smallestSeqno, largestSeqno, smallestKey, - largestKey, numReadsSampled, beingCompacted, numEntries, numDeletions); - this.columnFamilyName = columnFamilyName; - this.level = level; - } - - /** - * Get the name of the column family. - * - * @return the name of the column family - */ - public byte[] columnFamilyName() { - return columnFamilyName; - } - - /** - * Get the level at which this file resides. - * - * @return the level at which the file resides. - */ - public int level() { - return level; - } -} diff --git a/java/src/main/java/org/rocksdb/LogFile.java b/java/src/main/java/org/rocksdb/LogFile.java deleted file mode 100644 index ef24a6427..000000000 --- a/java/src/main/java/org/rocksdb/LogFile.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class LogFile { - private final String pathName; - private final long logNumber; - private final WalFileType type; - private final long startSequence; - private final long sizeFileBytes; - - /** - * Called from JNI C++ - */ - private LogFile(final String pathName, final long logNumber, - final byte walFileTypeValue, final long startSequence, - final long sizeFileBytes) { - this.pathName = pathName; - this.logNumber = logNumber; - this.type = WalFileType.fromValue(walFileTypeValue); - this.startSequence = startSequence; - this.sizeFileBytes = sizeFileBytes; - } - - /** - * Returns log file's pathname relative to the main db dir - * Eg. For a live-log-file = /000003.log - * For an archived-log-file = /archive/000003.log - * - * @return log file's pathname - */ - public String pathName() { - return pathName; - } - - /** - * Primary identifier for log file. - * This is directly proportional to creation time of the log file - * - * @return the log number - */ - public long logNumber() { - return logNumber; - } - - /** - * Log file can be either alive or archived. - * - * @return the type of the log file. - */ - public WalFileType type() { - return type; - } - - /** - * Starting sequence number of writebatch written in this log file. - * - * @return the stating sequence number - */ - public long startSequence() { - return startSequence; - } - - /** - * Size of log file on disk in Bytes. - * - * @return size of log file - */ - public long sizeFileBytes() { - return sizeFileBytes; - } -} diff --git a/java/src/main/java/org/rocksdb/Logger.java b/java/src/main/java/org/rocksdb/Logger.java deleted file mode 100644 index 00a5d5674..000000000 --- a/java/src/main/java/org/rocksdb/Logger.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - *

    This class provides a custom logger functionality - * in Java which wraps {@code RocksDB} logging facilities. - *

    - * - *

    Using this class RocksDB can log with common - * Java logging APIs like Log4j or Slf4j without keeping - * database logs in the filesystem.

    - * - * Performance - *

    There are certain performance penalties using a Java - * {@code Logger} implementation within production code. - *

    - * - *

    - * A log level can be set using {@link org.rocksdb.Options} or - * {@link Logger#setInfoLogLevel(InfoLogLevel)}. The set log level - * influences the underlying native code. Each log message is - * checked against the set log level and if the log level is more - * verbose as the set log level, native allocations will be made - * and data structures are allocated. - *

    - * - *

    Every log message which will be emitted by native code will - * trigger expensive native to Java transitions. So the preferred - * setting for production use is either - * {@link org.rocksdb.InfoLogLevel#ERROR_LEVEL} or - * {@link org.rocksdb.InfoLogLevel#FATAL_LEVEL}. - *

    - */ -public abstract class Logger extends RocksCallbackObject { - - private final static long WITH_OPTIONS = 0; - private final static long WITH_DBOPTIONS = 1; - - /** - *

    AbstractLogger constructor.

    - * - *

    Important: the log level set within - * the {@link org.rocksdb.Options} instance will be used as - * maximum log level of RocksDB.

    - * - * @param options {@link org.rocksdb.Options} instance. - */ - public Logger(final Options options) { - super(options.nativeHandle_, WITH_OPTIONS); - - } - - /** - *

    AbstractLogger constructor.

    - * - *

    Important: the log level set within - * the {@link org.rocksdb.DBOptions} instance will be used - * as maximum log level of RocksDB.

    - * - * @param dboptions {@link org.rocksdb.DBOptions} instance. - */ - public Logger(final DBOptions dboptions) { - super(dboptions.nativeHandle_, WITH_DBOPTIONS); - } - - @Override - protected long initializeNative(long... nativeParameterHandles) { - if(nativeParameterHandles[1] == WITH_OPTIONS) { - return createNewLoggerOptions(nativeParameterHandles[0]); - } else if(nativeParameterHandles[1] == WITH_DBOPTIONS) { - return createNewLoggerDbOptions(nativeParameterHandles[0]); - } else { - throw new IllegalArgumentException(); - } - } - - /** - * Set {@link org.rocksdb.InfoLogLevel} to AbstractLogger. - * - * @param infoLogLevel {@link org.rocksdb.InfoLogLevel} instance. - */ - public void setInfoLogLevel(final InfoLogLevel infoLogLevel) { - setInfoLogLevel(nativeHandle_, infoLogLevel.getValue()); - } - - /** - * Return the loggers log level. - * - * @return {@link org.rocksdb.InfoLogLevel} instance. - */ - public InfoLogLevel infoLogLevel() { - return InfoLogLevel.getInfoLogLevel( - infoLogLevel(nativeHandle_)); - } - - protected abstract void log(InfoLogLevel infoLogLevel, - String logMsg); - - protected native long createNewLoggerOptions( - long options); - protected native long createNewLoggerDbOptions( - long dbOptions); - protected native void setInfoLogLevel(long handle, - byte infoLogLevel); - protected native byte infoLogLevel(long handle); - - /** - * We override {@link RocksCallbackObject#disposeInternal()} - * as disposing of a rocksdb::LoggerJniCallback requires - * a slightly different approach as it is a std::shared_ptr - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - private native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/MemTableConfig.java b/java/src/main/java/org/rocksdb/MemTableConfig.java deleted file mode 100644 index 83cee974a..000000000 --- a/java/src/main/java/org/rocksdb/MemTableConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * MemTableConfig is used to config the internal mem-table of a RocksDB. - * It is required for each memtable to have one such sub-class to allow - * Java developers to use it. - * - * To make a RocksDB to use a specific MemTable format, its associated - * MemTableConfig should be properly set and passed into Options - * via Options.setMemTableFactory() and open the db using that Options. - * - * @see Options - */ -public abstract class MemTableConfig { - /** - * This function should only be called by Options.setMemTableConfig(), - * which will create a c++ shared-pointer to the c++ MemTableRepFactory - * that associated with the Java MemTableConfig. - * - * @see Options#setMemTableConfig(MemTableConfig) - * - * @return native handle address to native memory table instance. - */ - abstract protected long newMemTableFactoryHandle(); -} diff --git a/java/src/main/java/org/rocksdb/MemTableInfo.java b/java/src/main/java/org/rocksdb/MemTableInfo.java deleted file mode 100644 index f4fb577c3..000000000 --- a/java/src/main/java/org/rocksdb/MemTableInfo.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class MemTableInfo { - private final String columnFamilyName; - private final long firstSeqno; - private final long earliestSeqno; - private final long numEntries; - private final long numDeletes; - - /** - * Access is package private as this will only be constructed from - * C++ via JNI and for testing. - */ - MemTableInfo(final String columnFamilyName, final long firstSeqno, final long earliestSeqno, - final long numEntries, final long numDeletes) { - this.columnFamilyName = columnFamilyName; - this.firstSeqno = firstSeqno; - this.earliestSeqno = earliestSeqno; - this.numEntries = numEntries; - this.numDeletes = numDeletes; - } - - /** - * Get the name of the column family to which memtable belongs. - * - * @return the name of the column family. - */ - public String getColumnFamilyName() { - return columnFamilyName; - } - - /** - * Get the Sequence number of the first element that was inserted into the - * memtable. - * - * @return the sequence number of the first inserted element. - */ - public long getFirstSeqno() { - return firstSeqno; - } - - /** - * Get the Sequence number that is guaranteed to be smaller than or equal - * to the sequence number of any key that could be inserted into this - * memtable. It can then be assumed that any write with a larger(or equal) - * sequence number will be present in this memtable or a later memtable. - * - * @return the earliest sequence number. - */ - public long getEarliestSeqno() { - return earliestSeqno; - } - - /** - * Get the total number of entries in memtable. - * - * @return the total number of entries. - */ - public long getNumEntries() { - return numEntries; - } - - /** - * Get the total number of deletes in memtable. - * - * @return the total number of deletes. - */ - public long getNumDeletes() { - return numDeletes; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - MemTableInfo that = (MemTableInfo) o; - return firstSeqno == that.firstSeqno && earliestSeqno == that.earliestSeqno - && numEntries == that.numEntries && numDeletes == that.numDeletes - && Objects.equals(columnFamilyName, that.columnFamilyName); - } - - @Override - public int hashCode() { - return Objects.hash(columnFamilyName, firstSeqno, earliestSeqno, numEntries, numDeletes); - } - - @Override - public String toString() { - return "MemTableInfo{" - + "columnFamilyName='" + columnFamilyName + '\'' + ", firstSeqno=" + firstSeqno - + ", earliestSeqno=" + earliestSeqno + ", numEntries=" + numEntries - + ", numDeletes=" + numDeletes + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/MemoryUsageType.java b/java/src/main/java/org/rocksdb/MemoryUsageType.java deleted file mode 100644 index 6010ce7af..000000000 --- a/java/src/main/java/org/rocksdb/MemoryUsageType.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * MemoryUsageType - * - *

    The value will be used as a key to indicate the type of memory usage - * described

    - */ -public enum MemoryUsageType { - /** - * Memory usage of all the mem-tables. - */ - kMemTableTotal((byte) 0), - /** - * Memory usage of those un-flushed mem-tables. - */ - kMemTableUnFlushed((byte) 1), - /** - * Memory usage of all the table readers. - */ - kTableReadersTotal((byte) 2), - /** - * Memory usage by Cache. - */ - kCacheTotal((byte) 3), - /** - * Max usage types - copied to keep 1:1 with native. - */ - kNumUsageTypes((byte) 4); - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - /** - *

    Get the MemoryUsageType enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of MemoryUsageType. - * - * @return MemoryUsageType instance. - * - * @throws IllegalArgumentException if the usage type for the byteIdentifier - * cannot be found - */ - public static MemoryUsageType getMemoryUsageType(final byte byteIdentifier) { - for (final MemoryUsageType memoryUsageType : MemoryUsageType.values()) { - if (memoryUsageType.getValue() == byteIdentifier) { - return memoryUsageType; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for MemoryUsageType."); - } - - MemoryUsageType(byte value) { - value_ = value; - } - - private final byte value_; -} diff --git a/java/src/main/java/org/rocksdb/MemoryUtil.java b/java/src/main/java/org/rocksdb/MemoryUtil.java deleted file mode 100644 index 52b2175e6..000000000 --- a/java/src/main/java/org/rocksdb/MemoryUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.*; - -/** - * JNI passthrough for MemoryUtil. - */ -public class MemoryUtil { - - /** - *

    Returns the approximate memory usage of different types in the input - * list of DBs and Cache set. For instance, in the output map the key - * kMemTableTotal will be associated with the memory - * usage of all the mem-tables from all the input rocksdb instances.

    - * - *

    Note that for memory usage inside Cache class, we will - * only report the usage of the input "cache_set" without - * including those Cache usage inside the input list "dbs" - * of DBs.

    - * - * @param dbs List of dbs to collect memory usage for. - * @param caches Set of caches to collect memory usage for. - * @return Map from {@link MemoryUsageType} to memory usage as a {@link Long}. - */ - public static Map getApproximateMemoryUsageByType(final List dbs, final Set caches) { - int dbCount = (dbs == null) ? 0 : dbs.size(); - int cacheCount = (caches == null) ? 0 : caches.size(); - long[] dbHandles = new long[dbCount]; - long[] cacheHandles = new long[cacheCount]; - if (dbCount > 0) { - ListIterator dbIter = dbs.listIterator(); - while (dbIter.hasNext()) { - dbHandles[dbIter.nextIndex()] = dbIter.next().nativeHandle_; - } - } - if (cacheCount > 0) { - // NOTE: This index handling is super ugly but I couldn't get a clean way to track both the - // index and the iterator simultaneously within a Set. - int i = 0; - for (Cache cache : caches) { - cacheHandles[i] = cache.nativeHandle_; - i++; - } - } - Map byteOutput = getApproximateMemoryUsageByType(dbHandles, cacheHandles); - Map output = new HashMap<>(); - for(Map.Entry longEntry : byteOutput.entrySet()) { - output.put(MemoryUsageType.getMemoryUsageType(longEntry.getKey()), longEntry.getValue()); - } - return output; - } - - private native static Map getApproximateMemoryUsageByType(final long[] dbHandles, - final long[] cacheHandles); -} diff --git a/java/src/main/java/org/rocksdb/MergeOperator.java b/java/src/main/java/org/rocksdb/MergeOperator.java deleted file mode 100644 index c299f6221..000000000 --- a/java/src/main/java/org/rocksdb/MergeOperator.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// Copyright (c) 2014, Vlad Balan (vlad.gm@gmail.com). All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * MergeOperator holds an operator to be applied when compacting - * two merge operands held under the same key in order to obtain a single - * value. - */ -public abstract class MergeOperator extends RocksObject { - protected MergeOperator(final long nativeHandle) { - super(nativeHandle); - } -} diff --git a/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java b/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java deleted file mode 100644 index af28fa8ce..000000000 --- a/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java +++ /dev/null @@ -1,623 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.*; - -public class MutableColumnFamilyOptions - extends AbstractMutableOptions { - - /** - * User must use builder pattern, or parser. - * - * @param keys the keys - * @param values the values - * - * See {@link #builder()} and {@link #parse(String)}. - */ - private MutableColumnFamilyOptions(final String[] keys, - final String[] values) { - super(keys, values); - } - - /** - * Creates a builder which allows you - * to set MutableColumnFamilyOptions in a fluent - * manner - * - * @return A builder for MutableColumnFamilyOptions - */ - public static MutableColumnFamilyOptionsBuilder builder() { - return new MutableColumnFamilyOptionsBuilder(); - } - - /** - * Parses a String representation of MutableColumnFamilyOptions - * - * The format is: key1=value1;key2=value2;key3=value3 etc - * - * For int[] values, each int should be separated by a colon, e.g. - * - * key1=value1;intArrayKey1=1:2:3 - * - * @param str The string representation of the mutable column family options - * @param ignoreUnknown what to do if the key is not one of the keys we expect - * - * @return A builder for the mutable column family options - */ - public static MutableColumnFamilyOptionsBuilder parse( - final String str, final boolean ignoreUnknown) { - Objects.requireNonNull(str); - - final List parsedOptions = OptionString.Parser.parse(str); - return new MutableColumnFamilyOptionsBuilder().fromParsed(parsedOptions, ignoreUnknown); - } - - public static MutableColumnFamilyOptionsBuilder parse(final String str) { - return parse(str, false); - } - - private interface MutableColumnFamilyOptionKey extends MutableOptionKey {} - - public enum MemtableOption implements MutableColumnFamilyOptionKey { - write_buffer_size(ValueType.LONG), - arena_block_size(ValueType.LONG), - memtable_prefix_bloom_size_ratio(ValueType.DOUBLE), - memtable_whole_key_filtering(ValueType.BOOLEAN), - @Deprecated memtable_prefix_bloom_bits(ValueType.INT), - @Deprecated memtable_prefix_bloom_probes(ValueType.INT), - memtable_huge_page_size(ValueType.LONG), - max_successive_merges(ValueType.LONG), - @Deprecated filter_deletes(ValueType.BOOLEAN), - max_write_buffer_number(ValueType.INT), - inplace_update_num_locks(ValueType.LONG), - experimental_mempurge_threshold(ValueType.DOUBLE); - - private final ValueType valueType; - MemtableOption(final ValueType valueType) { - this.valueType = valueType; - } - - @Override - public ValueType getValueType() { - return valueType; - } - } - - public enum CompactionOption implements MutableColumnFamilyOptionKey { - disable_auto_compactions(ValueType.BOOLEAN), - soft_pending_compaction_bytes_limit(ValueType.LONG), - hard_pending_compaction_bytes_limit(ValueType.LONG), - level0_file_num_compaction_trigger(ValueType.INT), - level0_slowdown_writes_trigger(ValueType.INT), - level0_stop_writes_trigger(ValueType.INT), - max_compaction_bytes(ValueType.LONG), - target_file_size_base(ValueType.LONG), - target_file_size_multiplier(ValueType.INT), - max_bytes_for_level_base(ValueType.LONG), - max_bytes_for_level_multiplier(ValueType.INT), - max_bytes_for_level_multiplier_additional(ValueType.INT_ARRAY), - ttl(ValueType.LONG), - periodic_compaction_seconds(ValueType.LONG); - - private final ValueType valueType; - CompactionOption(final ValueType valueType) { - this.valueType = valueType; - } - - @Override - public ValueType getValueType() { - return valueType; - } - } - - public enum BlobOption implements MutableColumnFamilyOptionKey { - enable_blob_files(ValueType.BOOLEAN), - min_blob_size(ValueType.LONG), - blob_file_size(ValueType.LONG), - blob_compression_type(ValueType.ENUM), - enable_blob_garbage_collection(ValueType.BOOLEAN), - blob_garbage_collection_age_cutoff(ValueType.DOUBLE), - blob_garbage_collection_force_threshold(ValueType.DOUBLE), - blob_compaction_readahead_size(ValueType.LONG), - blob_file_starting_level(ValueType.INT), - prepopulate_blob_cache(ValueType.ENUM); - - private final ValueType valueType; - BlobOption(final ValueType valueType) { - this.valueType = valueType; - } - - @Override - public ValueType getValueType() { - return valueType; - } - } - - public enum MiscOption implements MutableColumnFamilyOptionKey { - max_sequential_skip_in_iterations(ValueType.LONG), - paranoid_file_checks(ValueType.BOOLEAN), - report_bg_io_stats(ValueType.BOOLEAN), - compression(ValueType.ENUM); - - private final ValueType valueType; - MiscOption(final ValueType valueType) { - this.valueType = valueType; - } - - @Override - public ValueType getValueType() { - return valueType; - } - } - - public static class MutableColumnFamilyOptionsBuilder - extends AbstractMutableOptionsBuilder - implements MutableColumnFamilyOptionsInterface { - - private final static Map ALL_KEYS_LOOKUP = new HashMap<>(); - static { - for(final MutableColumnFamilyOptionKey key : MemtableOption.values()) { - ALL_KEYS_LOOKUP.put(key.name(), key); - } - - for(final MutableColumnFamilyOptionKey key : CompactionOption.values()) { - ALL_KEYS_LOOKUP.put(key.name(), key); - } - - for(final MutableColumnFamilyOptionKey key : MiscOption.values()) { - ALL_KEYS_LOOKUP.put(key.name(), key); - } - - for (final MutableColumnFamilyOptionKey key : BlobOption.values()) { - ALL_KEYS_LOOKUP.put(key.name(), key); - } - } - - private MutableColumnFamilyOptionsBuilder() { - super(); - } - - @Override - protected MutableColumnFamilyOptionsBuilder self() { - return this; - } - - @Override - protected Map allKeys() { - return ALL_KEYS_LOOKUP; - } - - @Override - protected MutableColumnFamilyOptions build(final String[] keys, - final String[] values) { - return new MutableColumnFamilyOptions(keys, values); - } - - @Override - public MutableColumnFamilyOptionsBuilder setWriteBufferSize( - final long writeBufferSize) { - return setLong(MemtableOption.write_buffer_size, writeBufferSize); - } - - @Override - public long writeBufferSize() { - return getLong(MemtableOption.write_buffer_size); - } - - @Override - public MutableColumnFamilyOptionsBuilder setArenaBlockSize( - final long arenaBlockSize) { - return setLong(MemtableOption.arena_block_size, arenaBlockSize); - } - - @Override - public long arenaBlockSize() { - return getLong(MemtableOption.arena_block_size); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMemtablePrefixBloomSizeRatio( - final double memtablePrefixBloomSizeRatio) { - return setDouble(MemtableOption.memtable_prefix_bloom_size_ratio, - memtablePrefixBloomSizeRatio); - } - - @Override - public double memtablePrefixBloomSizeRatio() { - return getDouble(MemtableOption.memtable_prefix_bloom_size_ratio); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMemtableWholeKeyFiltering( - final boolean memtableWholeKeyFiltering) { - return setBoolean(MemtableOption.memtable_whole_key_filtering, memtableWholeKeyFiltering); - } - - @Override - public boolean memtableWholeKeyFiltering() { - return getBoolean(MemtableOption.memtable_whole_key_filtering); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMemtableHugePageSize( - final long memtableHugePageSize) { - return setLong(MemtableOption.memtable_huge_page_size, - memtableHugePageSize); - } - - @Override - public long memtableHugePageSize() { - return getLong(MemtableOption.memtable_huge_page_size); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxSuccessiveMerges( - final long maxSuccessiveMerges) { - return setLong(MemtableOption.max_successive_merges, maxSuccessiveMerges); - } - - @Override - public long maxSuccessiveMerges() { - return getLong(MemtableOption.max_successive_merges); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxWriteBufferNumber( - final int maxWriteBufferNumber) { - return setInt(MemtableOption.max_write_buffer_number, - maxWriteBufferNumber); - } - - @Override - public int maxWriteBufferNumber() { - return getInt(MemtableOption.max_write_buffer_number); - } - - @Override - public MutableColumnFamilyOptionsBuilder setInplaceUpdateNumLocks( - final long inplaceUpdateNumLocks) { - return setLong(MemtableOption.inplace_update_num_locks, - inplaceUpdateNumLocks); - } - - @Override - public long inplaceUpdateNumLocks() { - return getLong(MemtableOption.inplace_update_num_locks); - } - - @Override - public MutableColumnFamilyOptionsBuilder setExperimentalMempurgeThreshold( - final double experimentalMempurgeThreshold) { - return setDouble( - MemtableOption.experimental_mempurge_threshold, experimentalMempurgeThreshold); - } - - @Override - public double experimentalMempurgeThreshold() { - return getDouble(MemtableOption.experimental_mempurge_threshold); - } - - @Override - public MutableColumnFamilyOptionsBuilder setDisableAutoCompactions( - final boolean disableAutoCompactions) { - return setBoolean(CompactionOption.disable_auto_compactions, - disableAutoCompactions); - } - - @Override - public boolean disableAutoCompactions() { - return getBoolean(CompactionOption.disable_auto_compactions); - } - - @Override - public MutableColumnFamilyOptionsBuilder setSoftPendingCompactionBytesLimit( - final long softPendingCompactionBytesLimit) { - return setLong(CompactionOption.soft_pending_compaction_bytes_limit, - softPendingCompactionBytesLimit); - } - - @Override - public long softPendingCompactionBytesLimit() { - return getLong(CompactionOption.soft_pending_compaction_bytes_limit); - } - - @Override - public MutableColumnFamilyOptionsBuilder setHardPendingCompactionBytesLimit( - final long hardPendingCompactionBytesLimit) { - return setLong(CompactionOption.hard_pending_compaction_bytes_limit, - hardPendingCompactionBytesLimit); - } - - @Override - public long hardPendingCompactionBytesLimit() { - return getLong(CompactionOption.hard_pending_compaction_bytes_limit); - } - - @Override - public MutableColumnFamilyOptionsBuilder setLevel0FileNumCompactionTrigger( - final int level0FileNumCompactionTrigger) { - return setInt(CompactionOption.level0_file_num_compaction_trigger, - level0FileNumCompactionTrigger); - } - - @Override - public int level0FileNumCompactionTrigger() { - return getInt(CompactionOption.level0_file_num_compaction_trigger); - } - - @Override - public MutableColumnFamilyOptionsBuilder setLevel0SlowdownWritesTrigger( - final int level0SlowdownWritesTrigger) { - return setInt(CompactionOption.level0_slowdown_writes_trigger, - level0SlowdownWritesTrigger); - } - - @Override - public int level0SlowdownWritesTrigger() { - return getInt(CompactionOption.level0_slowdown_writes_trigger); - } - - @Override - public MutableColumnFamilyOptionsBuilder setLevel0StopWritesTrigger( - final int level0StopWritesTrigger) { - return setInt(CompactionOption.level0_stop_writes_trigger, - level0StopWritesTrigger); - } - - @Override - public int level0StopWritesTrigger() { - return getInt(CompactionOption.level0_stop_writes_trigger); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxCompactionBytes(final long maxCompactionBytes) { - return setLong(CompactionOption.max_compaction_bytes, maxCompactionBytes); - } - - @Override - public long maxCompactionBytes() { - return getLong(CompactionOption.max_compaction_bytes); - } - - - @Override - public MutableColumnFamilyOptionsBuilder setTargetFileSizeBase( - final long targetFileSizeBase) { - return setLong(CompactionOption.target_file_size_base, - targetFileSizeBase); - } - - @Override - public long targetFileSizeBase() { - return getLong(CompactionOption.target_file_size_base); - } - - @Override - public MutableColumnFamilyOptionsBuilder setTargetFileSizeMultiplier( - final int targetFileSizeMultiplier) { - return setInt(CompactionOption.target_file_size_multiplier, - targetFileSizeMultiplier); - } - - @Override - public int targetFileSizeMultiplier() { - return getInt(CompactionOption.target_file_size_multiplier); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxBytesForLevelBase( - final long maxBytesForLevelBase) { - return setLong(CompactionOption.max_bytes_for_level_base, - maxBytesForLevelBase); - } - - @Override - public long maxBytesForLevelBase() { - return getLong(CompactionOption.max_bytes_for_level_base); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxBytesForLevelMultiplier( - final double maxBytesForLevelMultiplier) { - return setDouble(CompactionOption.max_bytes_for_level_multiplier, maxBytesForLevelMultiplier); - } - - @Override - public double maxBytesForLevelMultiplier() { - return getDouble(CompactionOption.max_bytes_for_level_multiplier); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxBytesForLevelMultiplierAdditional( - final int[] maxBytesForLevelMultiplierAdditional) { - return setIntArray( - CompactionOption.max_bytes_for_level_multiplier_additional, - maxBytesForLevelMultiplierAdditional); - } - - @Override - public int[] maxBytesForLevelMultiplierAdditional() { - return getIntArray( - CompactionOption.max_bytes_for_level_multiplier_additional); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMaxSequentialSkipInIterations( - final long maxSequentialSkipInIterations) { - return setLong(MiscOption.max_sequential_skip_in_iterations, - maxSequentialSkipInIterations); - } - - @Override - public long maxSequentialSkipInIterations() { - return getLong(MiscOption.max_sequential_skip_in_iterations); - } - - @Override - public MutableColumnFamilyOptionsBuilder setParanoidFileChecks( - final boolean paranoidFileChecks) { - return setBoolean(MiscOption.paranoid_file_checks, paranoidFileChecks); - } - - @Override - public boolean paranoidFileChecks() { - return getBoolean(MiscOption.paranoid_file_checks); - } - - @Override - public MutableColumnFamilyOptionsBuilder setCompressionType( - final CompressionType compressionType) { - return setEnum(MiscOption.compression, compressionType); - } - - @Override - public CompressionType compressionType() { - return (CompressionType) getEnum(MiscOption.compression); - } - - @Override - public MutableColumnFamilyOptionsBuilder setReportBgIoStats( - final boolean reportBgIoStats) { - return setBoolean(MiscOption.report_bg_io_stats, reportBgIoStats); - } - - @Override - public boolean reportBgIoStats() { - return getBoolean(MiscOption.report_bg_io_stats); - } - - @Override - public MutableColumnFamilyOptionsBuilder setTtl(final long ttl) { - return setLong(CompactionOption.ttl, ttl); - } - - @Override - public long ttl() { - return getLong(CompactionOption.ttl); - } - - @Override - public MutableColumnFamilyOptionsBuilder setPeriodicCompactionSeconds( - final long periodicCompactionSeconds) { - return setLong(CompactionOption.periodic_compaction_seconds, periodicCompactionSeconds); - } - - @Override - public long periodicCompactionSeconds() { - return getLong(CompactionOption.periodic_compaction_seconds); - } - - @Override - public MutableColumnFamilyOptionsBuilder setEnableBlobFiles(final boolean enableBlobFiles) { - return setBoolean(BlobOption.enable_blob_files, enableBlobFiles); - } - - @Override - public boolean enableBlobFiles() { - return getBoolean(BlobOption.enable_blob_files); - } - - @Override - public MutableColumnFamilyOptionsBuilder setMinBlobSize(final long minBlobSize) { - return setLong(BlobOption.min_blob_size, minBlobSize); - } - - @Override - public long minBlobSize() { - return getLong(BlobOption.min_blob_size); - } - - @Override - public MutableColumnFamilyOptionsBuilder setBlobFileSize(final long blobFileSize) { - return setLong(BlobOption.blob_file_size, blobFileSize); - } - - @Override - public long blobFileSize() { - return getLong(BlobOption.blob_file_size); - } - - @Override - public MutableColumnFamilyOptionsBuilder setBlobCompressionType( - final CompressionType compressionType) { - return setEnum(BlobOption.blob_compression_type, compressionType); - } - - @Override - public CompressionType blobCompressionType() { - return (CompressionType) getEnum(BlobOption.blob_compression_type); - } - - @Override - public MutableColumnFamilyOptionsBuilder setEnableBlobGarbageCollection( - final boolean enableBlobGarbageCollection) { - return setBoolean(BlobOption.enable_blob_garbage_collection, enableBlobGarbageCollection); - } - - @Override - public boolean enableBlobGarbageCollection() { - return getBoolean(BlobOption.enable_blob_garbage_collection); - } - - @Override - public MutableColumnFamilyOptionsBuilder setBlobGarbageCollectionAgeCutoff( - final double blobGarbageCollectionAgeCutoff) { - return setDouble( - BlobOption.blob_garbage_collection_age_cutoff, blobGarbageCollectionAgeCutoff); - } - - @Override - public double blobGarbageCollectionAgeCutoff() { - return getDouble(BlobOption.blob_garbage_collection_age_cutoff); - } - - @Override - public MutableColumnFamilyOptionsBuilder setBlobGarbageCollectionForceThreshold( - final double blobGarbageCollectionForceThreshold) { - return setDouble( - BlobOption.blob_garbage_collection_force_threshold, blobGarbageCollectionForceThreshold); - } - - @Override - public double blobGarbageCollectionForceThreshold() { - return getDouble(BlobOption.blob_garbage_collection_force_threshold); - } - - @Override - public MutableColumnFamilyOptionsBuilder setBlobCompactionReadaheadSize( - final long blobCompactionReadaheadSize) { - return setLong(BlobOption.blob_compaction_readahead_size, blobCompactionReadaheadSize); - } - - @Override - public long blobCompactionReadaheadSize() { - return getLong(BlobOption.blob_compaction_readahead_size); - } - - @Override - public MutableColumnFamilyOptionsBuilder setBlobFileStartingLevel( - final int blobFileStartingLevel) { - return setInt(BlobOption.blob_file_starting_level, blobFileStartingLevel); - } - - @Override - public int blobFileStartingLevel() { - return getInt(BlobOption.blob_file_starting_level); - } - - @Override - public MutableColumnFamilyOptionsBuilder setPrepopulateBlobCache( - final PrepopulateBlobCache prepopulateBlobCache) { - return setEnum(BlobOption.prepopulate_blob_cache, prepopulateBlobCache); - } - - @Override - public PrepopulateBlobCache prepopulateBlobCache() { - return (PrepopulateBlobCache) getEnum(BlobOption.prepopulate_blob_cache); - } - } -} diff --git a/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java b/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java deleted file mode 100644 index 0f5fe7d78..000000000 --- a/java/src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public interface MutableColumnFamilyOptionsInterface< - T extends MutableColumnFamilyOptionsInterface> - extends AdvancedMutableColumnFamilyOptionsInterface { - /** - * Amount of data to build up in memory (backed by an unsorted log - * on disk) before converting to a sorted on-disk file. - * - * Larger values increase performance, especially during bulk loads. - * Up to {@code max_write_buffer_number} write buffers may be held in memory - * at the same time, so you may wish to adjust this parameter - * to control memory usage. - * - * Also, a larger write buffer will result in a longer recovery time - * the next time the database is opened. - * - * Default: 64MB - * @param writeBufferSize the size of write buffer. - * @return the instance of the current object. - * @throws java.lang.IllegalArgumentException thrown on 32-Bit platforms - * while overflowing the underlying platform specific value. - */ - T setWriteBufferSize(long writeBufferSize); - - /** - * Return size of write buffer size. - * - * @return size of write buffer. - * @see #setWriteBufferSize(long) - */ - long writeBufferSize(); - - /** - * Disable automatic compactions. Manual compactions can still - * be issued on this column family - * - * @param disableAutoCompactions true if auto-compactions are disabled. - * @return the reference to the current option. - */ - T setDisableAutoCompactions(boolean disableAutoCompactions); - - /** - * Disable automatic compactions. Manual compactions can still - * be issued on this column family - * - * @return true if auto-compactions are disabled. - */ - boolean disableAutoCompactions(); - - /** - * Number of files to trigger level-0 compaction. A value < 0 means that - * level-0 compaction will not be triggered by number of files at all. - * - * Default: 4 - * - * @param level0FileNumCompactionTrigger The number of files to trigger - * level-0 compaction - * @return the reference to the current option. - */ - T setLevel0FileNumCompactionTrigger(int level0FileNumCompactionTrigger); - - /** - * Number of files to trigger level-0 compaction. A value < 0 means that - * level-0 compaction will not be triggered by number of files at all. - * - * Default: 4 - * - * @return The number of files to trigger - */ - int level0FileNumCompactionTrigger(); - - /** - * We try to limit number of bytes in one compaction to be lower than this - * threshold. But it's not guaranteed. - * Value 0 will be sanitized. - * - * @param maxCompactionBytes max bytes in a compaction - * @return the reference to the current option. - * @see #maxCompactionBytes() - */ - T setMaxCompactionBytes(final long maxCompactionBytes); - - /** - * We try to limit number of bytes in one compaction to be lower than this - * threshold. But it's not guaranteed. - * Value 0 will be sanitized. - * - * @return the maximum number of bytes in for a compaction. - * @see #setMaxCompactionBytes(long) - */ - long maxCompactionBytes(); - - /** - * The upper-bound of the total size of level-1 files in bytes. - * Maximum number of bytes for level L can be calculated as - * (maxBytesForLevelBase) * (maxBytesForLevelMultiplier ^ (L-1)) - * For example, if maxBytesForLevelBase is 20MB, and if - * max_bytes_for_level_multiplier is 10, total data size for level-1 - * will be 200MB, total file size for level-2 will be 2GB, - * and total file size for level-3 will be 20GB. - * by default 'maxBytesForLevelBase' is 256MB. - * - * @param maxBytesForLevelBase maximum bytes for level base. - * - * @return the reference to the current option. - * - * See {@link AdvancedMutableColumnFamilyOptionsInterface#setMaxBytesForLevelMultiplier(double)} - */ - T setMaxBytesForLevelBase( - long maxBytesForLevelBase); - - /** - * The upper-bound of the total size of level-1 files in bytes. - * Maximum number of bytes for level L can be calculated as - * (maxBytesForLevelBase) * (maxBytesForLevelMultiplier ^ (L-1)) - * For example, if maxBytesForLevelBase is 20MB, and if - * max_bytes_for_level_multiplier is 10, total data size for level-1 - * will be 200MB, total file size for level-2 will be 2GB, - * and total file size for level-3 will be 20GB. - * by default 'maxBytesForLevelBase' is 256MB. - * - * @return the upper-bound of the total size of level-1 files - * in bytes. - * - * See {@link AdvancedMutableColumnFamilyOptionsInterface#maxBytesForLevelMultiplier()} - */ - long maxBytesForLevelBase(); - - /** - * Compress blocks using the specified compression algorithm. This - * parameter can be changed dynamically. - * - * Default: SNAPPY_COMPRESSION, which gives lightweight but fast compression. - * - * @param compressionType Compression Type. - * @return the reference to the current option. - */ - T setCompressionType( - CompressionType compressionType); - - /** - * Compress blocks using the specified compression algorithm. This - * parameter can be changed dynamically. - * - * Default: SNAPPY_COMPRESSION, which gives lightweight but fast compression. - * - * @return Compression type. - */ - CompressionType compressionType(); -} diff --git a/java/src/main/java/org/rocksdb/MutableDBOptions.java b/java/src/main/java/org/rocksdb/MutableDBOptions.java deleted file mode 100644 index bfba1dab3..000000000 --- a/java/src/main/java/org/rocksdb/MutableDBOptions.java +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -public class MutableDBOptions extends AbstractMutableOptions { - - /** - * User must use builder pattern, or parser. - * - * @param keys the keys - * @param values the values - * - * See {@link #builder()} and {@link #parse(String)}. - */ - private MutableDBOptions(final String[] keys, final String[] values) { - super(keys, values); - } - - /** - * Creates a builder which allows you - * to set MutableDBOptions in a fluent - * manner - * - * @return A builder for MutableDBOptions - */ - public static MutableDBOptionsBuilder builder() { - return new MutableDBOptionsBuilder(); - } - - /** - * Parses a String representation of MutableDBOptions - * - * The format is: key1=value1;key2=value2;key3=value3 etc - * - * For int[] values, each int should be separated by a comma, e.g. - * - * key1=value1;intArrayKey1=1:2:3 - * - * @param str The string representation of the mutable db options - * @param ignoreUnknown what to do if the key is not one of the keys we expect - * - * @return A builder for the mutable db options - */ - public static MutableDBOptionsBuilder parse(final String str, boolean ignoreUnknown) { - Objects.requireNonNull(str); - - final List parsedOptions = OptionString.Parser.parse(str); - return new MutableDBOptions.MutableDBOptionsBuilder().fromParsed(parsedOptions, ignoreUnknown); - } - - public static MutableDBOptionsBuilder parse(final String str) { - return parse(str, false); - } - - private interface MutableDBOptionKey extends MutableOptionKey {} - - public enum DBOption implements MutableDBOptionKey { - max_background_jobs(ValueType.INT), - max_background_compactions(ValueType.INT), - avoid_flush_during_shutdown(ValueType.BOOLEAN), - writable_file_max_buffer_size(ValueType.LONG), - delayed_write_rate(ValueType.LONG), - max_total_wal_size(ValueType.LONG), - delete_obsolete_files_period_micros(ValueType.LONG), - stats_dump_period_sec(ValueType.INT), - stats_persist_period_sec(ValueType.INT), - stats_history_buffer_size(ValueType.LONG), - max_open_files(ValueType.INT), - bytes_per_sync(ValueType.LONG), - wal_bytes_per_sync(ValueType.LONG), - strict_bytes_per_sync(ValueType.BOOLEAN), - compaction_readahead_size(ValueType.LONG); - - private final ValueType valueType; - DBOption(final ValueType valueType) { - this.valueType = valueType; - } - - @Override - public ValueType getValueType() { - return valueType; - } - } - - public static class MutableDBOptionsBuilder - extends AbstractMutableOptionsBuilder - implements MutableDBOptionsInterface { - - private final static Map ALL_KEYS_LOOKUP = new HashMap<>(); - static { - for(final MutableDBOptionKey key : DBOption.values()) { - ALL_KEYS_LOOKUP.put(key.name(), key); - } - } - - private MutableDBOptionsBuilder() { - super(); - } - - @Override - protected MutableDBOptionsBuilder self() { - return this; - } - - @Override - protected Map allKeys() { - return ALL_KEYS_LOOKUP; - } - - @Override - protected MutableDBOptions build(final String[] keys, - final String[] values) { - return new MutableDBOptions(keys, values); - } - - @Override - public MutableDBOptionsBuilder setMaxBackgroundJobs( - final int maxBackgroundJobs) { - return setInt(DBOption.max_background_jobs, maxBackgroundJobs); - } - - @Override - public int maxBackgroundJobs() { - return getInt(DBOption.max_background_jobs); - } - - @Override - @Deprecated - public MutableDBOptionsBuilder setMaxBackgroundCompactions( - final int maxBackgroundCompactions) { - return setInt(DBOption.max_background_compactions, - maxBackgroundCompactions); - } - - @Override - @Deprecated - public int maxBackgroundCompactions() { - return getInt(DBOption.max_background_compactions); - } - - @Override - public MutableDBOptionsBuilder setAvoidFlushDuringShutdown( - final boolean avoidFlushDuringShutdown) { - return setBoolean(DBOption.avoid_flush_during_shutdown, - avoidFlushDuringShutdown); - } - - @Override - public boolean avoidFlushDuringShutdown() { - return getBoolean(DBOption.avoid_flush_during_shutdown); - } - - @Override - public MutableDBOptionsBuilder setWritableFileMaxBufferSize( - final long writableFileMaxBufferSize) { - return setLong(DBOption.writable_file_max_buffer_size, - writableFileMaxBufferSize); - } - - @Override - public long writableFileMaxBufferSize() { - return getLong(DBOption.writable_file_max_buffer_size); - } - - @Override - public MutableDBOptionsBuilder setDelayedWriteRate( - final long delayedWriteRate) { - return setLong(DBOption.delayed_write_rate, - delayedWriteRate); - } - - @Override - public long delayedWriteRate() { - return getLong(DBOption.delayed_write_rate); - } - - @Override - public MutableDBOptionsBuilder setMaxTotalWalSize( - final long maxTotalWalSize) { - return setLong(DBOption.max_total_wal_size, maxTotalWalSize); - } - - @Override - public long maxTotalWalSize() { - return getLong(DBOption.max_total_wal_size); - } - - @Override - public MutableDBOptionsBuilder setDeleteObsoleteFilesPeriodMicros( - final long micros) { - return setLong(DBOption.delete_obsolete_files_period_micros, micros); - } - - @Override - public long deleteObsoleteFilesPeriodMicros() { - return getLong(DBOption.delete_obsolete_files_period_micros); - } - - @Override - public MutableDBOptionsBuilder setStatsDumpPeriodSec( - final int statsDumpPeriodSec) { - return setInt(DBOption.stats_dump_period_sec, statsDumpPeriodSec); - } - - @Override - public int statsDumpPeriodSec() { - return getInt(DBOption.stats_dump_period_sec); - } - - @Override - public MutableDBOptionsBuilder setStatsPersistPeriodSec( - final int statsPersistPeriodSec) { - return setInt(DBOption.stats_persist_period_sec, statsPersistPeriodSec); - } - - @Override - public int statsPersistPeriodSec() { - return getInt(DBOption.stats_persist_period_sec); - } - - @Override - public MutableDBOptionsBuilder setStatsHistoryBufferSize( - final long statsHistoryBufferSize) { - return setLong(DBOption.stats_history_buffer_size, statsHistoryBufferSize); - } - - @Override - public long statsHistoryBufferSize() { - return getLong(DBOption.stats_history_buffer_size); - } - - @Override - public MutableDBOptionsBuilder setMaxOpenFiles(final int maxOpenFiles) { - return setInt(DBOption.max_open_files, maxOpenFiles); - } - - @Override - public int maxOpenFiles() { - return getInt(DBOption.max_open_files); - } - - @Override - public MutableDBOptionsBuilder setBytesPerSync(final long bytesPerSync) { - return setLong(DBOption.bytes_per_sync, bytesPerSync); - } - - @Override - public long bytesPerSync() { - return getLong(DBOption.bytes_per_sync); - } - - @Override - public MutableDBOptionsBuilder setWalBytesPerSync( - final long walBytesPerSync) { - return setLong(DBOption.wal_bytes_per_sync, walBytesPerSync); - } - - @Override - public long walBytesPerSync() { - return getLong(DBOption.wal_bytes_per_sync); - } - - @Override - public MutableDBOptionsBuilder setStrictBytesPerSync( - final boolean strictBytesPerSync) { - return setBoolean(DBOption.strict_bytes_per_sync, strictBytesPerSync); - } - - @Override - public boolean strictBytesPerSync() { - return getBoolean(DBOption.strict_bytes_per_sync); - } - - @Override - public MutableDBOptionsBuilder setCompactionReadaheadSize( - final long compactionReadaheadSize) { - return setLong(DBOption.compaction_readahead_size, - compactionReadaheadSize); - } - - @Override - public long compactionReadaheadSize() { - return getLong(DBOption.compaction_readahead_size); - } - } -} diff --git a/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java b/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java deleted file mode 100644 index bdf9d7bf6..000000000 --- a/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java +++ /dev/null @@ -1,440 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -public interface MutableDBOptionsInterface> { - /** - * Specifies the maximum number of concurrent background jobs (both flushes - * and compactions combined). - * Default: 2 - * - * @param maxBackgroundJobs number of max concurrent background jobs - * @return the instance of the current object. - */ - T setMaxBackgroundJobs(int maxBackgroundJobs); - - /** - * Returns the maximum number of concurrent background jobs (both flushes - * and compactions combined). - * Default: 2 - * - * @return the maximum number of concurrent background jobs. - */ - int maxBackgroundJobs(); - - /** - * NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the - * value of max_background_jobs. For backwards compatibility we will set - * `max_background_jobs = max_background_compactions + max_background_flushes` - * in the case where user sets at least one of `max_background_compactions` or - * `max_background_flushes` (we replace -1 by 1 in case one option is unset). - * - * Specifies the maximum number of concurrent background compaction jobs, - * submitted to the default LOW priority thread pool. - * If you're increasing this, also consider increasing number of threads in - * LOW priority thread pool. For more information, see - * Default: -1 - * - * @param maxBackgroundCompactions the maximum number of background - * compaction jobs. - * @return the instance of the current object. - * - * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, Priority) - * @see DBOptionsInterface#maxBackgroundFlushes() - * @deprecated Use {@link #setMaxBackgroundJobs(int)} - */ - @Deprecated - T setMaxBackgroundCompactions(int maxBackgroundCompactions); - - /** - * NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the - * value of max_background_jobs. For backwards compatibility we will set - * `max_background_jobs = max_background_compactions + max_background_flushes` - * in the case where user sets at least one of `max_background_compactions` or - * `max_background_flushes` (we replace -1 by 1 in case one option is unset). - * - * Returns the maximum number of concurrent background compaction jobs, - * submitted to the default LOW priority thread pool. - * When increasing this number, we may also want to consider increasing - * number of threads in LOW priority thread pool. - * Default: -1 - * - * @return the maximum number of concurrent background compaction jobs. - * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, Priority) - * - * @deprecated Use {@link #setMaxBackgroundJobs(int)} - */ - @Deprecated - int maxBackgroundCompactions(); - - /** - * By default RocksDB will flush all memtables on DB close if there are - * unpersisted data (i.e. with WAL disabled) The flush can be skip to speedup - * DB close. Unpersisted data WILL BE LOST. - * - * DEFAULT: false - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} - * API. - * - * @param avoidFlushDuringShutdown true if we should avoid flush during - * shutdown - * - * @return the reference to the current options. - */ - T setAvoidFlushDuringShutdown(boolean avoidFlushDuringShutdown); - - /** - * By default RocksDB will flush all memtables on DB close if there are - * unpersisted data (i.e. with WAL disabled) The flush can be skip to speedup - * DB close. Unpersisted data WILL BE LOST. - * - * DEFAULT: false - * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} - * API. - * - * @return true if we should avoid flush during shutdown - */ - boolean avoidFlushDuringShutdown(); - - /** - * This is the maximum buffer size that is used by WritableFileWriter. - * On Windows, we need to maintain an aligned buffer for writes. - * We allow the buffer to grow until it's size hits the limit. - * - * Default: 1024 * 1024 (1 MB) - * - * @param writableFileMaxBufferSize the maximum buffer size - * - * @return the reference to the current options. - */ - T setWritableFileMaxBufferSize(long writableFileMaxBufferSize); - - /** - * This is the maximum buffer size that is used by WritableFileWriter. - * On Windows, we need to maintain an aligned buffer for writes. - * We allow the buffer to grow until it's size hits the limit. - * - * Default: 1024 * 1024 (1 MB) - * - * @return the maximum buffer size - */ - long writableFileMaxBufferSize(); - - /** - * The limited write rate to DB if - * {@link ColumnFamilyOptions#softPendingCompactionBytesLimit()} or - * {@link ColumnFamilyOptions#level0SlowdownWritesTrigger()} is triggered, - * or we are writing to the last mem table allowed and we allow more than 3 - * mem tables. It is calculated using size of user write requests before - * compression. RocksDB may decide to slow down more if the compaction still - * gets behind further. - * If the value is 0, we will infer a value from `rater_limiter` value - * if it is not empty, or 16MB if `rater_limiter` is empty. Note that - * if users change the rate in `rate_limiter` after DB is opened, - * `delayed_write_rate` won't be adjusted. - * - * Unit: bytes per second. - * - * Default: 0 - * - * Dynamically changeable through {@link RocksDB#setDBOptions(MutableDBOptions)}. - * - * @param delayedWriteRate the rate in bytes per second - * - * @return the reference to the current options. - */ - T setDelayedWriteRate(long delayedWriteRate); - - /** - * The limited write rate to DB if - * {@link ColumnFamilyOptions#softPendingCompactionBytesLimit()} or - * {@link ColumnFamilyOptions#level0SlowdownWritesTrigger()} is triggered, - * or we are writing to the last mem table allowed and we allow more than 3 - * mem tables. It is calculated using size of user write requests before - * compression. RocksDB may decide to slow down more if the compaction still - * gets behind further. - * If the value is 0, we will infer a value from `rater_limiter` value - * if it is not empty, or 16MB if `rater_limiter` is empty. Note that - * if users change the rate in `rate_limiter` after DB is opened, - * `delayed_write_rate` won't be adjusted. - * - * Unit: bytes per second. - * - * Default: 0 - * - * Dynamically changeable through {@link RocksDB#setDBOptions(MutableDBOptions)}. - * - * @return the rate in bytes per second - */ - long delayedWriteRate(); - - /** - *

    Set the max total write-ahead log size. Once write-ahead logs exceed this size, we will - * start forcing the flush of column families whose memtables are backed by the oldest live WAL - * file - *

    - *

    The oldest WAL files are the ones that are causing all the space amplification. - *

    - * For example, with 15 column families, each with - * write_buffer_size = 128 MB - * max_write_buffer_number = 6 - * max_total_wal_size will be calculated to be [15 * 128MB * 6] * 4 = - * 45GB - *

    - * The RocksDB wiki has some discussion about how the WAL interacts - * with memtables and flushing of column families, at - * ... - *

    - *

    If set to 0 (default), we will dynamically choose the WAL size limit to - * be [sum of all write_buffer_size * max_write_buffer_number] * 4

    - *

    This option takes effect only when there are more than one column family as - * otherwise the wal size is dictated by the write_buffer_size.

    - *

    Default: 0

    - * - * @param maxTotalWalSize max total wal size. - * @return the instance of the current object. - */ - T setMaxTotalWalSize(long maxTotalWalSize); - - /** - *

    Returns the max total write-ahead log size. Once write-ahead logs exceed this size, - * we will start forcing the flush of column families whose memtables are - * backed by the oldest live WAL file.

    - *

    The oldest WAL files are the ones that are causing all the space amplification. - *

    - * For example, with 15 column families, each with - * write_buffer_size = 128 MB - * max_write_buffer_number = 6 - * max_total_wal_size will be calculated to be [15 * 128MB * 6] * 4 = - * 45GB - *

    - * The RocksDB wiki has some discussion about how the WAL interacts - * with memtables and flushing of column families, at - * ... - *

    - *

    If set to 0 (default), we will dynamically choose the WAL size limit to - * be [sum of all write_buffer_size * max_write_buffer_number] * 4

    - *

    This option takes effect only when there are more than one column family as - * otherwise the wal size is dictated by the write_buffer_size.

    - *

    Default: 0

    - * - * - *

    If set to 0 (default), we will dynamically choose the WAL size limit - * to be [sum of all write_buffer_size * max_write_buffer_number] * 4 - *

    - * - * @return max total wal size - */ - long maxTotalWalSize(); - - /** - * The periodicity when obsolete files get deleted. The default - * value is 6 hours. The files that get out of scope by compaction - * process will still get automatically delete on every compaction, - * regardless of this setting - * - * @param micros the time interval in micros - * @return the instance of the current object. - */ - T setDeleteObsoleteFilesPeriodMicros(long micros); - - /** - * The periodicity when obsolete files get deleted. The default - * value is 6 hours. The files that get out of scope by compaction - * process will still get automatically delete on every compaction, - * regardless of this setting - * - * @return the time interval in micros when obsolete files will be deleted. - */ - long deleteObsoleteFilesPeriodMicros(); - - /** - * if not zero, dump rocksdb.stats to LOG every stats_dump_period_sec - * Default: 600 (10 minutes) - * - * @param statsDumpPeriodSec time interval in seconds. - * @return the instance of the current object. - */ - T setStatsDumpPeriodSec(int statsDumpPeriodSec); - - /** - * If not zero, dump rocksdb.stats to LOG every stats_dump_period_sec - * Default: 600 (10 minutes) - * - * @return time interval in seconds. - */ - int statsDumpPeriodSec(); - - /** - * If not zero, dump rocksdb.stats to RocksDB every - * {@code statsPersistPeriodSec} - * - * Default: 600 - * - * @param statsPersistPeriodSec time interval in seconds. - * @return the instance of the current object. - */ - T setStatsPersistPeriodSec(int statsPersistPeriodSec); - - /** - * If not zero, dump rocksdb.stats to RocksDB every - * {@code statsPersistPeriodSec} - * - * @return time interval in seconds. - */ - int statsPersistPeriodSec(); - - /** - * If not zero, periodically take stats snapshots and store in memory, the - * memory size for stats snapshots is capped at {@code statsHistoryBufferSize} - * - * Default: 1MB - * - * @param statsHistoryBufferSize the size of the buffer. - * @return the instance of the current object. - */ - T setStatsHistoryBufferSize(long statsHistoryBufferSize); - - /** - * If not zero, periodically take stats snapshots and store in memory, the - * memory size for stats snapshots is capped at {@code statsHistoryBufferSize} - * - * @return the size of the buffer. - */ - long statsHistoryBufferSize(); - - /** - * Number of open files that can be used by the DB. You may need to - * increase this if your database has a large working set. Value -1 means - * files opened are always kept open. You can estimate number of files based - * on {@code target_file_size_base} and {@code target_file_size_multiplier} - * for level-based compaction. For universal-style compaction, you can usually - * set it to -1. - * Default: -1 - * - * @param maxOpenFiles the maximum number of open files. - * @return the instance of the current object. - */ - T setMaxOpenFiles(int maxOpenFiles); - - /** - * Number of open files that can be used by the DB. You may need to - * increase this if your database has a large working set. Value -1 means - * files opened are always kept open. You can estimate number of files based - * on {@code target_file_size_base} and {@code target_file_size_multiplier} - * for level-based compaction. For universal-style compaction, you can usually - * set it to -1. - * Default: -1 - * - * @return the maximum number of open files. - */ - int maxOpenFiles(); - - /** - * Allows OS to incrementally sync files to disk while they are being - * written, asynchronously, in the background. - * Issue one request for every bytes_per_sync written. 0 turns it off. - * Default: 0 - * - * @param bytesPerSync size in bytes - * @return the instance of the current object. - */ - T setBytesPerSync(long bytesPerSync); - - /** - * Allows OS to incrementally sync files to disk while they are being - * written, asynchronously, in the background. - * Issue one request for every bytes_per_sync written. 0 turns it off. - * Default: 0 - * - * @return size in bytes - */ - long bytesPerSync(); - - /** - * Same as {@link #setBytesPerSync(long)} , but applies to WAL files - * - * Default: 0, turned off - * - * @param walBytesPerSync size in bytes - * @return the instance of the current object. - */ - T setWalBytesPerSync(long walBytesPerSync); - - /** - * Same as {@link #bytesPerSync()} , but applies to WAL files - * - * Default: 0, turned off - * - * @return size in bytes - */ - long walBytesPerSync(); - - /** - * When true, guarantees WAL files have at most {@link #walBytesPerSync()} - * bytes submitted for writeback at any given time, and SST files have at most - * {@link #bytesPerSync()} bytes pending writeback at any given time. This - * can be used to handle cases where processing speed exceeds I/O speed - * during file generation, which can lead to a huge sync when the file is - * finished, even with {@link #bytesPerSync()} / {@link #walBytesPerSync()} - * properly configured. - * - * - If `sync_file_range` is supported it achieves this by waiting for any - * prior `sync_file_range`s to finish before proceeding. In this way, - * processing (compression, etc.) can proceed uninhibited in the gap - * between `sync_file_range`s, and we block only when I/O falls - * behind. - * - Otherwise the `WritableFile::Sync` method is used. Note this mechanism - * always blocks, thus preventing the interleaving of I/O and processing. - * - * Note: Enabling this option does not provide any additional persistence - * guarantees, as it may use `sync_file_range`, which does not write out - * metadata. - * - * Default: false - * - * @param strictBytesPerSync the bytes per sync - * @return the instance of the current object. - */ - T setStrictBytesPerSync(boolean strictBytesPerSync); - - /** - * Return the strict byte limit per sync. - * - * See {@link #setStrictBytesPerSync(boolean)} - * - * @return the limit in bytes. - */ - boolean strictBytesPerSync(); - - /** - * If non-zero, we perform bigger reads when doing compaction. If you're - * running RocksDB on spinning disks, you should set this to at least 2MB. - * - * That way RocksDB's compaction is doing sequential instead of random reads. - * - * Default: 0 - * - * @param compactionReadaheadSize The compaction read-ahead size - * - * @return the reference to the current options. - */ - T setCompactionReadaheadSize(final long compactionReadaheadSize); - - /** - * If non-zero, we perform bigger reads when doing compaction. If you're - * running RocksDB on spinning disks, you should set this to at least 2MB. - * - * That way RocksDB's compaction is doing sequential instead of random reads. - * - * Default: 0 - * - * @return The compaction read-ahead size - */ - long compactionReadaheadSize(); -} diff --git a/java/src/main/java/org/rocksdb/MutableOptionKey.java b/java/src/main/java/org/rocksdb/MutableOptionKey.java deleted file mode 100644 index ec1b9ff3b..000000000 --- a/java/src/main/java/org/rocksdb/MutableOptionKey.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -public interface MutableOptionKey { - enum ValueType { - DOUBLE, - LONG, - INT, - BOOLEAN, - INT_ARRAY, - ENUM - } - - String name(); - ValueType getValueType(); -} diff --git a/java/src/main/java/org/rocksdb/MutableOptionValue.java b/java/src/main/java/org/rocksdb/MutableOptionValue.java deleted file mode 100644 index 7f69eeb9e..000000000 --- a/java/src/main/java/org/rocksdb/MutableOptionValue.java +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import static org.rocksdb.AbstractMutableOptions.INT_ARRAY_INT_SEPARATOR; - -public abstract class MutableOptionValue { - - abstract double asDouble() throws NumberFormatException; - abstract long asLong() throws NumberFormatException; - abstract int asInt() throws NumberFormatException; - abstract boolean asBoolean() throws IllegalStateException; - abstract int[] asIntArray() throws IllegalStateException; - abstract String asString(); - abstract T asObject(); - - private static abstract class MutableOptionValueObject - extends MutableOptionValue { - protected final T value; - - protected MutableOptionValueObject(final T value) { - this.value = value; - } - - @Override T asObject() { - return value; - } - } - - static MutableOptionValue fromString(final String s) { - return new MutableOptionStringValue(s); - } - - static MutableOptionValue fromDouble(final double d) { - return new MutableOptionDoubleValue(d); - } - - static MutableOptionValue fromLong(final long d) { - return new MutableOptionLongValue(d); - } - - static MutableOptionValue fromInt(final int i) { - return new MutableOptionIntValue(i); - } - - static MutableOptionValue fromBoolean(final boolean b) { - return new MutableOptionBooleanValue(b); - } - - static MutableOptionValue fromIntArray(final int[] ix) { - return new MutableOptionIntArrayValue(ix); - } - - static > MutableOptionValue fromEnum(final N value) { - return new MutableOptionEnumValue<>(value); - } - - static class MutableOptionStringValue - extends MutableOptionValueObject { - MutableOptionStringValue(final String value) { - super(value); - } - - @Override - double asDouble() throws NumberFormatException { - return Double.parseDouble(value); - } - - @Override - long asLong() throws NumberFormatException { - return Long.parseLong(value); - } - - @Override - int asInt() throws NumberFormatException { - return Integer.parseInt(value); - } - - @Override - boolean asBoolean() throws IllegalStateException { - return Boolean.parseBoolean(value); - } - - @Override - int[] asIntArray() throws IllegalStateException { - throw new IllegalStateException("String is not applicable as int[]"); - } - - @Override - String asString() { - return value; - } - } - - static class MutableOptionDoubleValue - extends MutableOptionValue { - private final double value; - MutableOptionDoubleValue(final double value) { - this.value = value; - } - - @Override - double asDouble() { - return value; - } - - @Override - long asLong() throws NumberFormatException { - return Double.valueOf(value).longValue(); - } - - @Override - int asInt() throws NumberFormatException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "double value lies outside the bounds of int"); - } - return Double.valueOf(value).intValue(); - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new IllegalStateException( - "double is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "double value lies outside the bounds of int"); - } - return new int[] { Double.valueOf(value).intValue() }; - } - - @Override - String asString() { - return String.valueOf(value); - } - - @Override - Double asObject() { - return value; - } - } - - static class MutableOptionLongValue - extends MutableOptionValue { - private final long value; - - MutableOptionLongValue(final long value) { - this.value = value; - } - - @Override - double asDouble() { - return Long.valueOf(value).doubleValue(); - } - - @Override - long asLong() throws NumberFormatException { - return value; - } - - @Override - int asInt() throws NumberFormatException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "long value lies outside the bounds of int"); - } - return Long.valueOf(value).intValue(); - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new IllegalStateException( - "long is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "long value lies outside the bounds of int"); - } - return new int[] { Long.valueOf(value).intValue() }; - } - - @Override - String asString() { - return String.valueOf(value); - } - - @Override - Long asObject() { - return value; - } - } - - static class MutableOptionIntValue - extends MutableOptionValue { - private final int value; - - MutableOptionIntValue(final int value) { - this.value = value; - } - - @Override - double asDouble() { - return Integer.valueOf(value).doubleValue(); - } - - @Override - long asLong() throws NumberFormatException { - return value; - } - - @Override - int asInt() throws NumberFormatException { - return value; - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new IllegalStateException("int is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - return new int[] { value }; - } - - @Override - String asString() { - return String.valueOf(value); - } - - @Override - Integer asObject() { - return value; - } - } - - static class MutableOptionBooleanValue - extends MutableOptionValue { - private final boolean value; - - MutableOptionBooleanValue(final boolean value) { - this.value = value; - } - - @Override - double asDouble() { - throw new NumberFormatException("boolean is not applicable as double"); - } - - @Override - long asLong() throws NumberFormatException { - throw new NumberFormatException("boolean is not applicable as Long"); - } - - @Override - int asInt() throws NumberFormatException { - throw new NumberFormatException("boolean is not applicable as int"); - } - - @Override - boolean asBoolean() { - return value; - } - - @Override - int[] asIntArray() throws IllegalStateException { - throw new IllegalStateException("boolean is not applicable as int[]"); - } - - @Override - String asString() { - return String.valueOf(value); - } - - @Override - Boolean asObject() { - return value; - } - } - - static class MutableOptionIntArrayValue - extends MutableOptionValueObject { - MutableOptionIntArrayValue(final int[] value) { - super(value); - } - - @Override - double asDouble() { - throw new NumberFormatException("int[] is not applicable as double"); - } - - @Override - long asLong() throws NumberFormatException { - throw new NumberFormatException("int[] is not applicable as Long"); - } - - @Override - int asInt() throws NumberFormatException { - throw new NumberFormatException("int[] is not applicable as int"); - } - - @Override - boolean asBoolean() { - throw new NumberFormatException("int[] is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - return value; - } - - @Override - String asString() { - final StringBuilder builder = new StringBuilder(); - for(int i = 0; i < value.length; i++) { - builder.append(value[i]); - if(i + 1 < value.length) { - builder.append(INT_ARRAY_INT_SEPARATOR); - } - } - return builder.toString(); - } - } - - static class MutableOptionEnumValue> - extends MutableOptionValueObject { - - MutableOptionEnumValue(final T value) { - super(value); - } - - @Override - double asDouble() throws NumberFormatException { - throw new NumberFormatException("Enum is not applicable as double"); - } - - @Override - long asLong() throws NumberFormatException { - throw new NumberFormatException("Enum is not applicable as long"); - } - - @Override - int asInt() throws NumberFormatException { - throw new NumberFormatException("Enum is not applicable as int"); - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new NumberFormatException("Enum is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - throw new NumberFormatException("Enum is not applicable as int[]"); - } - - @Override - String asString() { - return value.name(); - } - } - -} diff --git a/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java b/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java deleted file mode 100644 index 6acc146f7..000000000 --- a/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * A simple abstraction to allow a Java class to wrap a custom comparator - * implemented in C++. - * - * The native comparator must directly extend rocksdb::Comparator. - */ -public abstract class NativeComparatorWrapper - extends AbstractComparator { - - @Override - final ComparatorType getComparatorType() { - return ComparatorType.JAVA_NATIVE_COMPARATOR_WRAPPER; - } - - @Override - public final String name() { - throw new IllegalStateException("This should not be called. " + - "Implementation is in Native code"); - } - - @Override - public final int compare(final ByteBuffer s1, final ByteBuffer s2) { - throw new IllegalStateException("This should not be called. " + - "Implementation is in Native code"); - } - - @Override - public final void findShortestSeparator(final ByteBuffer start, final ByteBuffer limit) { - throw new IllegalStateException("This should not be called. " + - "Implementation is in Native code"); - } - - @Override - public final void findShortSuccessor(final ByteBuffer key) { - throw new IllegalStateException("This should not be called. " + - "Implementation is in Native code"); - } - - /** - * We override {@link RocksCallbackObject#disposeInternal()} - * as disposing of a native rocksdb::Comparator extension requires - * a slightly different approach as it is not really a RocksCallbackObject - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - private native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/NativeLibraryLoader.java b/java/src/main/java/org/rocksdb/NativeLibraryLoader.java deleted file mode 100644 index b97cf28b9..000000000 --- a/java/src/main/java/org/rocksdb/NativeLibraryLoader.java +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; - -import org.rocksdb.util.Environment; - -/** - * This class is used to load the RocksDB shared library from within the jar. - * The shared library is extracted to a temp folder and loaded from there. - */ -public class NativeLibraryLoader { - //singleton - private static final NativeLibraryLoader instance = new NativeLibraryLoader(); - private static boolean initialized = false; - - private static final String sharedLibraryName = Environment.getSharedLibraryName("rocksdb"); - private static final String jniLibraryName = Environment.getJniLibraryName("rocksdb"); - private static final /* @Nullable */ String fallbackJniLibraryName = - Environment.getFallbackJniLibraryName("rocksdb"); - private static final String jniLibraryFileName = Environment.getJniLibraryFileName("rocksdb"); - private static final /* @Nullable */ String fallbackJniLibraryFileName = - Environment.getFallbackJniLibraryFileName("rocksdb"); - private static final String tempFilePrefix = "librocksdbjni"; - private static final String tempFileSuffix = Environment.getJniLibraryExtension(); - - /** - * Get a reference to the NativeLibraryLoader - * - * @return The NativeLibraryLoader - */ - public static NativeLibraryLoader getInstance() { - return instance; - } - - /** - * Firstly attempts to load the library from java.library.path, - * if that fails then it falls back to extracting - * the library from the classpath - * {@link org.rocksdb.NativeLibraryLoader#loadLibraryFromJar(java.lang.String)} - * - * @param tmpDir A temporary directory to use - * to copy the native library to when loading from the classpath. - * If null, or the empty string, we rely on Java's - * {@link java.io.File#createTempFile(String, String)} - * function to provide a temporary location. - * The temporary file will be registered for deletion - * on exit. - * - * @throws java.io.IOException if a filesystem operation fails. - */ - public synchronized void loadLibrary(final String tmpDir) throws IOException { - try { - // try dynamic library - System.loadLibrary(sharedLibraryName); - return; - } catch (final UnsatisfiedLinkError ule) { - // ignore - try from static library - } - - try { - // try static library - System.loadLibrary(jniLibraryName); - return; - } catch (final UnsatisfiedLinkError ule) { - // ignore - then try static library fallback or from jar - } - - if (fallbackJniLibraryName != null) { - try { - // try static library fallback - System.loadLibrary(fallbackJniLibraryName); - return; - } catch (final UnsatisfiedLinkError ule) { - // ignore - then try from jar - } - } - - // try jar - loadLibraryFromJar(tmpDir); - } - - /** - * Attempts to extract the native RocksDB library - * from the classpath and load it - * - * @param tmpDir A temporary directory to use - * to copy the native library to. If null, - * or the empty string, we rely on Java's - * {@link java.io.File#createTempFile(String, String)} - * function to provide a temporary location. - * The temporary file will be registered for deletion - * on exit. - * - * @throws java.io.IOException if a filesystem operation fails. - */ - void loadLibraryFromJar(final String tmpDir) - throws IOException { - if (!initialized) { - System.load(loadLibraryFromJarToTemp(tmpDir).getAbsolutePath()); - initialized = true; - } - } - - File loadLibraryFromJarToTemp(final String tmpDir) - throws IOException { - InputStream is = null; - try { - // attempt to look up the static library in the jar file - String libraryFileName = jniLibraryFileName; - is = getClass().getClassLoader().getResourceAsStream(libraryFileName); - - if (is == null) { - // is there a fallback we can try - if (fallbackJniLibraryFileName == null) { - throw new RuntimeException(libraryFileName + " was not found inside JAR."); - } - - // attempt to look up the fallback static library in the jar file - libraryFileName = fallbackJniLibraryFileName; - is = getClass().getClassLoader().getResourceAsStream(libraryFileName); - if (is == null) { - throw new RuntimeException(libraryFileName + " was not found inside JAR."); - } - } - - // create a temporary file to copy the library to - final File temp; - if (tmpDir == null || tmpDir.isEmpty()) { - temp = File.createTempFile(tempFilePrefix, tempFileSuffix); - } else { - final File parentDir = new File(tmpDir); - if (!parentDir.exists()) { - throw new RuntimeException( - "Directory: " + parentDir.getAbsolutePath() + " does not exist!"); - } - temp = new File(parentDir, libraryFileName); - if (temp.exists() && !temp.delete()) { - throw new RuntimeException( - "File: " + temp.getAbsolutePath() + " already exists and cannot be removed."); - } - if (!temp.createNewFile()) { - throw new RuntimeException("File: " + temp.getAbsolutePath() + " could not be created."); - } - } - if (!temp.exists()) { - throw new RuntimeException("File " + temp.getAbsolutePath() + " does not exist."); - } else { - temp.deleteOnExit(); - } - - // copy the library from the Jar file to the temp destination - Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); - - // return the temporary library file - return temp; - - } finally { - if (is != null) { - is.close(); - } - } - } - - /** - * Private constructor to disallow instantiation - */ - private NativeLibraryLoader() { - } -} diff --git a/java/src/main/java/org/rocksdb/OperationStage.java b/java/src/main/java/org/rocksdb/OperationStage.java deleted file mode 100644 index 6ac0a15a2..000000000 --- a/java/src/main/java/org/rocksdb/OperationStage.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The operation stage. - */ -public enum OperationStage { - STAGE_UNKNOWN((byte)0x0), - STAGE_FLUSH_RUN((byte)0x1), - STAGE_FLUSH_WRITE_L0((byte)0x2), - STAGE_COMPACTION_PREPARE((byte)0x3), - STAGE_COMPACTION_RUN((byte)0x4), - STAGE_COMPACTION_PROCESS_KV((byte)0x5), - STAGE_COMPACTION_INSTALL((byte)0x6), - STAGE_COMPACTION_SYNC_FILE((byte)0x7), - STAGE_PICK_MEMTABLES_TO_FLUSH((byte)0x8), - STAGE_MEMTABLE_ROLLBACK((byte)0x9), - STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS((byte)0xA); - - private final byte value; - - OperationStage(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value. - */ - byte getValue() { - return value; - } - - /** - * Get the Operation stage from the internal representation value. - * - * @param value the internal representation value. - * - * @return the operation stage - * - * @throws IllegalArgumentException if the value does not match - * an OperationStage - */ - static OperationStage fromValue(final byte value) - throws IllegalArgumentException { - for (final OperationStage threadType : OperationStage.values()) { - if (threadType.value == value) { - return threadType; - } - } - throw new IllegalArgumentException( - "Unknown value for OperationStage: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/OperationType.java b/java/src/main/java/org/rocksdb/OperationType.java deleted file mode 100644 index 7cc9b65cd..000000000 --- a/java/src/main/java/org/rocksdb/OperationType.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The type used to refer to a thread operation. - * - * A thread operation describes high-level action of a thread, - * examples include compaction and flush. - */ -public enum OperationType { - OP_UNKNOWN((byte)0x0), - OP_COMPACTION((byte)0x1), - OP_FLUSH((byte)0x2); - - private final byte value; - - OperationType(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value. - */ - byte getValue() { - return value; - } - - /** - * Get the Operation type from the internal representation value. - * - * @param value the internal representation value. - * - * @return the operation type - * - * @throws IllegalArgumentException if the value does not match - * an OperationType - */ - static OperationType fromValue(final byte value) - throws IllegalArgumentException { - for (final OperationType threadType : OperationType.values()) { - if (threadType.value == value) { - return threadType; - } - } - throw new IllegalArgumentException( - "Unknown value for OperationType: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java b/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java deleted file mode 100644 index 5a2e1f3ed..000000000 --- a/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * Database with Transaction support. - */ -public class OptimisticTransactionDB extends RocksDB - implements TransactionalDB { - - /** - * Private constructor. - * - * @param nativeHandle The native handle of the C++ OptimisticTransactionDB - * object - */ - private OptimisticTransactionDB(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Open an OptimisticTransactionDB similar to - * {@link RocksDB#open(Options, String)}. - * - * @param options {@link org.rocksdb.Options} instance. - * @param path the path to the rocksdb. - * - * @return a {@link OptimisticTransactionDB} instance on success, null if the - * specified {@link OptimisticTransactionDB} can not be opened. - * - * @throws RocksDBException if an error occurs whilst opening the database. - */ - public static OptimisticTransactionDB open(final Options options, - final String path) throws RocksDBException { - final OptimisticTransactionDB otdb = new OptimisticTransactionDB(open( - options.nativeHandle_, path)); - - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - otdb.storeOptionsInstance(options); - - return otdb; - } - - /** - * Open an OptimisticTransactionDB similar to - * {@link RocksDB#open(DBOptions, String, List, List)}. - * - * @param dbOptions {@link org.rocksdb.DBOptions} instance. - * @param path the path to the rocksdb. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * - * @return a {@link OptimisticTransactionDB} instance on success, null if the - * specified {@link OptimisticTransactionDB} can not be opened. - * - * @throws RocksDBException if an error occurs whilst opening the database. - */ - public static OptimisticTransactionDB open(final DBOptions dbOptions, - final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles) - throws RocksDBException { - - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors - .get(i); - cfNames[i] = cfDescriptor.getName(); - cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; - } - - final long[] handles = open(dbOptions.nativeHandle_, path, cfNames, - cfOptionHandles); - final OptimisticTransactionDB otdb = - new OptimisticTransactionDB(handles[0]); - - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - otdb.storeOptionsInstance(dbOptions); - - for (int i = 1; i < handles.length; i++) { - columnFamilyHandles.add(new ColumnFamilyHandle(otdb, handles[i])); - } - - return otdb; - } - - - /** - * This is similar to {@link #close()} except that it - * throws an exception if any error occurs. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - * - * @throws RocksDBException if an error occurs whilst closing. - */ - public void closeE() throws RocksDBException { - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } finally { - disposeInternal(); - } - } - } - - /** - * This is similar to {@link #closeE()} except that it - * silently ignores any errors. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - */ - @Override - public void close() { - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } catch (final RocksDBException e) { - // silently ignore the error report - } finally { - disposeInternal(); - } - } - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions) { - return new Transaction(this, beginTransaction(nativeHandle_, - writeOptions.nativeHandle_)); - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions, - final OptimisticTransactionOptions optimisticTransactionOptions) { - return new Transaction(this, beginTransaction(nativeHandle_, - writeOptions.nativeHandle_, - optimisticTransactionOptions.nativeHandle_)); - } - - // TODO(AR) consider having beingTransaction(... oldTransaction) set a - // reference count inside Transaction, so that we can always call - // Transaction#close but the object is only disposed when there are as many - // closes as beginTransaction. Makes the try-with-resources paradigm easier for - // java developers - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions, - final Transaction oldTransaction) { - final long jtxn_handle = beginTransaction_withOld(nativeHandle_, - writeOptions.nativeHandle_, oldTransaction.nativeHandle_); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_txn - assert(jtxn_handle == oldTransaction.nativeHandle_); - - return oldTransaction; - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions, - final OptimisticTransactionOptions optimisticTransactionOptions, - final Transaction oldTransaction) { - final long jtxn_handle = beginTransaction_withOld(nativeHandle_, - writeOptions.nativeHandle_, optimisticTransactionOptions.nativeHandle_, - oldTransaction.nativeHandle_); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_txn - assert(jtxn_handle == oldTransaction.nativeHandle_); - - return oldTransaction; - } - - /** - * Get the underlying database that was opened. - * - * @return The underlying database that was opened. - */ - public RocksDB getBaseDB() { - final RocksDB db = new RocksDB(getBaseDB(nativeHandle_)); - db.disOwnNativeHandle(); - return db; - } - - @Override protected final native void disposeInternal(final long handle); - - protected static native long open(final long optionsHandle, - final String path) throws RocksDBException; - protected static native long[] open(final long handle, final String path, - final byte[][] columnFamilyNames, final long[] columnFamilyOptions); - private native static void closeDatabase(final long handle) - throws RocksDBException; - private native long beginTransaction(final long handle, - final long writeOptionsHandle); - private native long beginTransaction(final long handle, - final long writeOptionsHandle, - final long optimisticTransactionOptionsHandle); - private native long beginTransaction_withOld(final long handle, - final long writeOptionsHandle, final long oldTransactionHandle); - private native long beginTransaction_withOld(final long handle, - final long writeOptionsHandle, - final long optimisticTransactionOptionsHandle, - final long oldTransactionHandle); - private native long getBaseDB(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java b/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java deleted file mode 100644 index 250edf806..000000000 --- a/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class OptimisticTransactionOptions extends RocksObject - implements TransactionalOptions { - - public OptimisticTransactionOptions() { - super(newOptimisticTransactionOptions()); - } - - @Override - public boolean isSetSnapshot() { - assert(isOwningHandle()); - return isSetSnapshot(nativeHandle_); - } - - @Override - public OptimisticTransactionOptions setSetSnapshot( - final boolean setSnapshot) { - assert(isOwningHandle()); - setSetSnapshot(nativeHandle_, setSnapshot); - return this; - } - - /** - * Should be set if the DB has a non-default comparator. - * See comment in - * {@link WriteBatchWithIndex#WriteBatchWithIndex(AbstractComparator, int, boolean)} - * constructor. - * - * @param comparator The comparator to use for the transaction. - * - * @return this OptimisticTransactionOptions instance - */ - public OptimisticTransactionOptions setComparator( - final AbstractComparator comparator) { - assert(isOwningHandle()); - setComparator(nativeHandle_, comparator.nativeHandle_); - return this; - } - - private native static long newOptimisticTransactionOptions(); - private native boolean isSetSnapshot(final long handle); - private native void setSetSnapshot(final long handle, - final boolean setSnapshot); - private native void setComparator(final long handle, - final long comparatorHandle); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/OptionString.java b/java/src/main/java/org/rocksdb/OptionString.java deleted file mode 100644 index 7f97827cb..000000000 --- a/java/src/main/java/org/rocksdb/OptionString.java +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class OptionString { - private final static char kvPairSeparator = ';'; - private final static char kvSeparator = '='; - private final static char complexValueBegin = '{'; - private final static char complexValueEnd = '}'; - private final static char wrappedValueBegin = '{'; - private final static char wrappedValueEnd = '}'; - private final static char arrayValueSeparator = ':'; - - static class Value { - final List list; - final List complex; - - public Value(final List list, final List complex) { - this.list = list; - this.complex = complex; - } - - public boolean isList() { - return (this.list != null && this.complex == null); - } - - public static Value fromList(final List list) { - return new Value(list, null); - } - - public static Value fromComplex(final List complex) { - return new Value(null, complex); - } - - public String toString() { - final StringBuilder sb = new StringBuilder(); - if (isList()) { - for (final String item : list) { - sb.append(item).append(arrayValueSeparator); - } - // remove the final separator - if (sb.length() > 0) - sb.delete(sb.length() - 1, sb.length()); - } else { - sb.append('['); - for (final Entry entry : complex) { - sb.append(entry.toString()).append(';'); - } - sb.append(']'); - } - return sb.toString(); - } - } - - static class Entry { - public final String key; - public final Value value; - - private Entry(final String key, final Value value) { - this.key = key; - this.value = value; - } - - public String toString() { - return "" + key + "=" + value; - } - } - - static class Parser { - static class Exception extends RuntimeException { - public Exception(final String s) { - super(s); - } - } - - final String str; - final StringBuilder sb; - - private Parser(final String str) { - this.str = str; - this.sb = new StringBuilder(str); - } - - private void exception(final String message) { - final int pos = str.length() - sb.length(); - final int before = Math.min(pos, 64); - final int after = Math.min(64, str.length() - pos); - final String here = - str.substring(pos - before, pos) + "__*HERE*__" + str.substring(pos, pos + after); - - throw new Parser.Exception(message + " at [" + here + "]"); - } - - private void skipWhite() { - while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { - sb.delete(0, 1); - } - } - - private char first() { - if (sb.length() == 0) - exception("Unexpected end of input"); - return sb.charAt(0); - } - - private char next() { - if (sb.length() == 0) - exception("Unexpected end of input"); - final char c = sb.charAt(0); - sb.delete(0, 1); - return c; - } - - private boolean hasNext() { - return (sb.length() > 0); - } - - private boolean is(final char c) { - return (sb.length() > 0 && sb.charAt(0) == c); - } - - private boolean isKeyChar() { - if (!hasNext()) - return false; - final char c = first(); - return (Character.isAlphabetic(c) || Character.isDigit(c) || "_".indexOf(c) != -1); - } - - private boolean isValueChar() { - if (!hasNext()) - return false; - final char c = first(); - return (Character.isAlphabetic(c) || Character.isDigit(c) || "_-+.[]".indexOf(c) != -1); - } - - private String parseKey() { - final StringBuilder sbKey = new StringBuilder(); - sbKey.append(next()); - while (isKeyChar()) { - sbKey.append(next()); - } - - return sbKey.toString(); - } - - private String parseSimpleValue() { - if (is(wrappedValueBegin)) { - next(); - final String result = parseSimpleValue(); - if (!is(wrappedValueEnd)) { - exception("Expected to end a wrapped value with " + wrappedValueEnd); - } - next(); - - return result; - } else { - final StringBuilder sbValue = new StringBuilder(); - while (isValueChar()) sbValue.append(next()); - - return sbValue.toString(); - } - } - - private List parseList() { - final List list = new ArrayList<>(1); - while (true) { - list.add(parseSimpleValue()); - if (!is(arrayValueSeparator)) - break; - - next(); - } - - return list; - } - - private Entry parseOption() { - skipWhite(); - if (!isKeyChar()) { - exception("No valid key character(s) for key in key=value "); - } - final String key = parseKey(); - skipWhite(); - if (is(kvSeparator)) { - next(); - } else { - exception("Expected = separating key and value"); - } - skipWhite(); - final Value value = parseValue(); - return new Entry(key, value); - } - - private Value parseValue() { - skipWhite(); - if (is(complexValueBegin)) { - next(); - skipWhite(); - final Value value = Value.fromComplex(parseComplex()); - skipWhite(); - if (is(complexValueEnd)) { - next(); - skipWhite(); - } else { - exception("Expected } ending complex value"); - } - return value; - } else if (isValueChar()) { - return Value.fromList(parseList()); - } - - exception("No valid value character(s) for value in key=value"); - return null; - } - - private List parseComplex() { - final List entries = new ArrayList<>(); - - skipWhite(); - if (hasNext()) { - entries.add(parseOption()); - skipWhite(); - while (is(kvPairSeparator)) { - next(); - skipWhite(); - if (!isKeyChar()) { - // the separator was a terminator - break; - } - entries.add(parseOption()); - skipWhite(); - } - } - return entries; - } - - public static List parse(final String str) { - Objects.requireNonNull(str); - - final Parser parser = new Parser(str); - final List result = parser.parseComplex(); - if (parser.hasNext()) { - parser.exception("Unexpected end of parsing "); - } - - return result; - } - } -} diff --git a/java/src/main/java/org/rocksdb/Options.java b/java/src/main/java/org/rocksdb/Options.java deleted file mode 100644 index 54f88262b..000000000 --- a/java/src/main/java/org/rocksdb/Options.java +++ /dev/null @@ -1,2578 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.file.Paths; -import java.util.*; - -/** - * Options to control the behavior of a database. It will be used - * during the creation of a {@link org.rocksdb.RocksDB} (i.e., RocksDB.open()). - * - * As a descendent of {@link AbstractNativeReference}, this class is {@link AutoCloseable} - * and will be automatically released if opened in the preamble of a try with resources block. - */ -public class Options extends RocksObject - implements DBOptionsInterface, - MutableDBOptionsInterface, - ColumnFamilyOptionsInterface, - MutableColumnFamilyOptionsInterface { - static { - RocksDB.loadLibrary(); - } - - /** - * Converts the input properties into a Options-style formatted string - * @param properties The set of properties to convert - * @return The Options-style representation of those properties. - */ - public static String getOptionStringFromProps(final Properties properties) { - if (properties == null || properties.size() == 0) { - throw new IllegalArgumentException("Properties value must contain at least one value."); - } - StringBuilder stringBuilder = new StringBuilder(); - for (final String name : properties.stringPropertyNames()) { - stringBuilder.append(name); - stringBuilder.append("="); - stringBuilder.append(properties.getProperty(name)); - stringBuilder.append(";"); - } - return stringBuilder.toString(); - } - - /** - * Construct options for opening a RocksDB. - * - * This constructor will create (by allocating a block of memory) - * an {@code rocksdb::Options} in the c++ side. - */ - public Options() { - super(newOptions()); - env_ = Env.getDefault(); - } - - /** - * Construct options for opening a RocksDB. Reusing database options - * and column family options. - * - * @param dbOptions {@link org.rocksdb.DBOptions} instance - * @param columnFamilyOptions {@link org.rocksdb.ColumnFamilyOptions} - * instance - */ - public Options(final DBOptions dbOptions, - final ColumnFamilyOptions columnFamilyOptions) { - super(newOptions(dbOptions.nativeHandle_, - columnFamilyOptions.nativeHandle_)); - env_ = dbOptions.getEnv() != null ? dbOptions.getEnv() : Env.getDefault(); - } - - /** - * Copy constructor for ColumnFamilyOptions. - * - * NOTE: This does a shallow copy, which means comparator, merge_operator - * and other pointers will be cloned! - * - * @param other The Options to copy. - */ - public Options(Options other) { - super(copyOptions(other.nativeHandle_)); - this.env_ = other.env_; - this.memTableConfig_ = other.memTableConfig_; - this.tableFormatConfig_ = other.tableFormatConfig_; - this.rateLimiter_ = other.rateLimiter_; - this.comparator_ = other.comparator_; - this.compactionFilter_ = other.compactionFilter_; - this.compactionFilterFactory_ = other.compactionFilterFactory_; - this.compactionOptionsUniversal_ = other.compactionOptionsUniversal_; - this.compactionOptionsFIFO_ = other.compactionOptionsFIFO_; - this.compressionOptions_ = other.compressionOptions_; - this.rowCache_ = other.rowCache_; - this.writeBufferManager_ = other.writeBufferManager_; - this.compactionThreadLimiter_ = other.compactionThreadLimiter_; - this.bottommostCompressionOptions_ = other.bottommostCompressionOptions_; - this.walFilter_ = other.walFilter_; - this.sstPartitionerFactory_ = other.sstPartitionerFactory_; - } - - @Override - public Options setIncreaseParallelism(final int totalThreads) { - assert(isOwningHandle()); - setIncreaseParallelism(nativeHandle_, totalThreads); - return this; - } - - @Override - public Options setCreateIfMissing(final boolean flag) { - assert(isOwningHandle()); - setCreateIfMissing(nativeHandle_, flag); - return this; - } - - @Override - public Options setCreateMissingColumnFamilies(final boolean flag) { - assert(isOwningHandle()); - setCreateMissingColumnFamilies(nativeHandle_, flag); - return this; - } - - @Override - public Options setEnv(final Env env) { - assert(isOwningHandle()); - setEnv(nativeHandle_, env.nativeHandle_); - env_ = env; - return this; - } - - @Override - public Env getEnv() { - return env_; - } - - /** - *

    Set appropriate parameters for bulk loading. - * The reason that this is a function that returns "this" instead of a - * constructor is to enable chaining of multiple similar calls in the future. - *

    - * - *

    All data will be in level 0 without any automatic compaction. - * It's recommended to manually call CompactRange(NULL, NULL) before reading - * from the database, because otherwise the read can be very slow.

    - * - * @return the instance of the current Options. - */ - public Options prepareForBulkLoad() { - prepareForBulkLoad(nativeHandle_); - return this; - } - - @Override - public boolean createIfMissing() { - assert(isOwningHandle()); - return createIfMissing(nativeHandle_); - } - - @Override - public boolean createMissingColumnFamilies() { - assert(isOwningHandle()); - return createMissingColumnFamilies(nativeHandle_); - } - - @Override - public Options oldDefaults(final int majorVersion, final int minorVersion) { - oldDefaults(nativeHandle_, majorVersion, minorVersion); - return this; - } - - @Override - public Options optimizeForSmallDb() { - optimizeForSmallDb(nativeHandle_); - return this; - } - - @Override - public Options optimizeForSmallDb(final Cache cache) { - optimizeForSmallDb(nativeHandle_, cache.getNativeHandle()); - return this; - } - - @Override - public Options optimizeForPointLookup( - long blockCacheSizeMb) { - optimizeForPointLookup(nativeHandle_, - blockCacheSizeMb); - return this; - } - - @Override - public Options optimizeLevelStyleCompaction() { - optimizeLevelStyleCompaction(nativeHandle_, - DEFAULT_COMPACTION_MEMTABLE_MEMORY_BUDGET); - return this; - } - - @Override - public Options optimizeLevelStyleCompaction( - long memtableMemoryBudget) { - optimizeLevelStyleCompaction(nativeHandle_, - memtableMemoryBudget); - return this; - } - - @Override - public Options optimizeUniversalStyleCompaction() { - optimizeUniversalStyleCompaction(nativeHandle_, - DEFAULT_COMPACTION_MEMTABLE_MEMORY_BUDGET); - return this; - } - - @Override - public Options optimizeUniversalStyleCompaction( - final long memtableMemoryBudget) { - optimizeUniversalStyleCompaction(nativeHandle_, - memtableMemoryBudget); - return this; - } - - @Override - public Options setComparator(final BuiltinComparator builtinComparator) { - assert(isOwningHandle()); - setComparatorHandle(nativeHandle_, builtinComparator.ordinal()); - return this; - } - - @Override - public Options setComparator( - final AbstractComparator comparator) { - assert(isOwningHandle()); - setComparatorHandle(nativeHandle_, comparator.nativeHandle_, - comparator.getComparatorType().getValue()); - comparator_ = comparator; - return this; - } - - @Override - public Options setMergeOperatorName(final String name) { - assert(isOwningHandle()); - if (name == null) { - throw new IllegalArgumentException( - "Merge operator name must not be null."); - } - setMergeOperatorName(nativeHandle_, name); - return this; - } - - @Override - public Options setMergeOperator(final MergeOperator mergeOperator) { - setMergeOperator(nativeHandle_, mergeOperator.nativeHandle_); - return this; - } - - @Override - public Options setCompactionFilter( - final AbstractCompactionFilter> - compactionFilter) { - setCompactionFilterHandle(nativeHandle_, compactionFilter.nativeHandle_); - compactionFilter_ = compactionFilter; - return this; - } - - @Override - public AbstractCompactionFilter> compactionFilter() { - assert (isOwningHandle()); - return compactionFilter_; - } - - @Override - public Options setCompactionFilterFactory(final AbstractCompactionFilterFactory> compactionFilterFactory) { - assert (isOwningHandle()); - setCompactionFilterFactoryHandle(nativeHandle_, compactionFilterFactory.nativeHandle_); - compactionFilterFactory_ = compactionFilterFactory; - return this; - } - - @Override - public AbstractCompactionFilterFactory> compactionFilterFactory() { - assert (isOwningHandle()); - return compactionFilterFactory_; - } - - @Override - public Options setWriteBufferSize(final long writeBufferSize) { - assert(isOwningHandle()); - setWriteBufferSize(nativeHandle_, writeBufferSize); - return this; - } - - @Override - public long writeBufferSize() { - assert(isOwningHandle()); - return writeBufferSize(nativeHandle_); - } - - @Override - public Options setMaxWriteBufferNumber(final int maxWriteBufferNumber) { - assert(isOwningHandle()); - setMaxWriteBufferNumber(nativeHandle_, maxWriteBufferNumber); - return this; - } - - @Override - public int maxWriteBufferNumber() { - assert(isOwningHandle()); - return maxWriteBufferNumber(nativeHandle_); - } - - @Override - public boolean errorIfExists() { - assert(isOwningHandle()); - return errorIfExists(nativeHandle_); - } - - @Override - public Options setErrorIfExists(final boolean errorIfExists) { - assert(isOwningHandle()); - setErrorIfExists(nativeHandle_, errorIfExists); - return this; - } - - @Override - public boolean paranoidChecks() { - assert(isOwningHandle()); - return paranoidChecks(nativeHandle_); - } - - @Override - public Options setParanoidChecks(final boolean paranoidChecks) { - assert(isOwningHandle()); - setParanoidChecks(nativeHandle_, paranoidChecks); - return this; - } - - @Override - public int maxOpenFiles() { - assert(isOwningHandle()); - return maxOpenFiles(nativeHandle_); - } - - @Override - public Options setMaxFileOpeningThreads(final int maxFileOpeningThreads) { - assert(isOwningHandle()); - setMaxFileOpeningThreads(nativeHandle_, maxFileOpeningThreads); - return this; - } - - @Override - public int maxFileOpeningThreads() { - assert(isOwningHandle()); - return maxFileOpeningThreads(nativeHandle_); - } - - @Override - public Options setMaxTotalWalSize(final long maxTotalWalSize) { - assert(isOwningHandle()); - setMaxTotalWalSize(nativeHandle_, maxTotalWalSize); - return this; - } - - @Override - public long maxTotalWalSize() { - assert(isOwningHandle()); - return maxTotalWalSize(nativeHandle_); - } - - @Override - public Options setMaxOpenFiles(final int maxOpenFiles) { - assert(isOwningHandle()); - setMaxOpenFiles(nativeHandle_, maxOpenFiles); - return this; - } - - @Override - public boolean useFsync() { - assert(isOwningHandle()); - return useFsync(nativeHandle_); - } - - @Override - public Options setUseFsync(final boolean useFsync) { - assert(isOwningHandle()); - setUseFsync(nativeHandle_, useFsync); - return this; - } - - @Override - public Options setDbPaths(final Collection dbPaths) { - assert(isOwningHandle()); - - final int len = dbPaths.size(); - final String paths[] = new String[len]; - final long targetSizes[] = new long[len]; - - int i = 0; - for(final DbPath dbPath : dbPaths) { - paths[i] = dbPath.path.toString(); - targetSizes[i] = dbPath.targetSize; - i++; - } - setDbPaths(nativeHandle_, paths, targetSizes); - return this; - } - - @Override - public List dbPaths() { - final int len = (int)dbPathsLen(nativeHandle_); - if(len == 0) { - return Collections.emptyList(); - } else { - final String paths[] = new String[len]; - final long targetSizes[] = new long[len]; - - dbPaths(nativeHandle_, paths, targetSizes); - - final List dbPaths = new ArrayList<>(); - for(int i = 0; i < len; i++) { - dbPaths.add(new DbPath(Paths.get(paths[i]), targetSizes[i])); - } - return dbPaths; - } - } - - @Override - public String dbLogDir() { - assert(isOwningHandle()); - return dbLogDir(nativeHandle_); - } - - @Override - public Options setDbLogDir(final String dbLogDir) { - assert(isOwningHandle()); - setDbLogDir(nativeHandle_, dbLogDir); - return this; - } - - @Override - public String walDir() { - assert(isOwningHandle()); - return walDir(nativeHandle_); - } - - @Override - public Options setWalDir(final String walDir) { - assert(isOwningHandle()); - setWalDir(nativeHandle_, walDir); - return this; - } - - @Override - public long deleteObsoleteFilesPeriodMicros() { - assert(isOwningHandle()); - return deleteObsoleteFilesPeriodMicros(nativeHandle_); - } - - @Override - public Options setDeleteObsoleteFilesPeriodMicros( - final long micros) { - assert(isOwningHandle()); - setDeleteObsoleteFilesPeriodMicros(nativeHandle_, micros); - return this; - } - - @Override - @Deprecated - public int maxBackgroundCompactions() { - assert(isOwningHandle()); - return maxBackgroundCompactions(nativeHandle_); - } - - @Override - public Options setStatistics(final Statistics statistics) { - assert(isOwningHandle()); - setStatistics(nativeHandle_, statistics.nativeHandle_); - return this; - } - - @Override - public Statistics statistics() { - assert(isOwningHandle()); - final long statisticsNativeHandle = statistics(nativeHandle_); - if(statisticsNativeHandle == 0) { - return null; - } else { - return new Statistics(statisticsNativeHandle); - } - } - - @Override - @Deprecated - public Options setMaxBackgroundCompactions( - final int maxBackgroundCompactions) { - assert(isOwningHandle()); - setMaxBackgroundCompactions(nativeHandle_, maxBackgroundCompactions); - return this; - } - - @Override - public Options setMaxSubcompactions(final int maxSubcompactions) { - assert(isOwningHandle()); - setMaxSubcompactions(nativeHandle_, maxSubcompactions); - return this; - } - - @Override - public int maxSubcompactions() { - assert(isOwningHandle()); - return maxSubcompactions(nativeHandle_); - } - - @Override - @Deprecated - public int maxBackgroundFlushes() { - assert(isOwningHandle()); - return maxBackgroundFlushes(nativeHandle_); - } - - @Override - @Deprecated - public Options setMaxBackgroundFlushes( - final int maxBackgroundFlushes) { - assert(isOwningHandle()); - setMaxBackgroundFlushes(nativeHandle_, maxBackgroundFlushes); - return this; - } - - @Override - public int maxBackgroundJobs() { - assert(isOwningHandle()); - return maxBackgroundJobs(nativeHandle_); - } - - @Override - public Options setMaxBackgroundJobs(final int maxBackgroundJobs) { - assert(isOwningHandle()); - setMaxBackgroundJobs(nativeHandle_, maxBackgroundJobs); - return this; - } - - @Override - public long maxLogFileSize() { - assert(isOwningHandle()); - return maxLogFileSize(nativeHandle_); - } - - @Override - public Options setMaxLogFileSize(final long maxLogFileSize) { - assert(isOwningHandle()); - setMaxLogFileSize(nativeHandle_, maxLogFileSize); - return this; - } - - @Override - public long logFileTimeToRoll() { - assert(isOwningHandle()); - return logFileTimeToRoll(nativeHandle_); - } - - @Override - public Options setLogFileTimeToRoll(final long logFileTimeToRoll) { - assert(isOwningHandle()); - setLogFileTimeToRoll(nativeHandle_, logFileTimeToRoll); - return this; - } - - @Override - public long keepLogFileNum() { - assert(isOwningHandle()); - return keepLogFileNum(nativeHandle_); - } - - @Override - public Options setKeepLogFileNum(final long keepLogFileNum) { - assert(isOwningHandle()); - setKeepLogFileNum(nativeHandle_, keepLogFileNum); - return this; - } - - - @Override - public Options setRecycleLogFileNum(final long recycleLogFileNum) { - assert(isOwningHandle()); - setRecycleLogFileNum(nativeHandle_, recycleLogFileNum); - return this; - } - - @Override - public long recycleLogFileNum() { - assert(isOwningHandle()); - return recycleLogFileNum(nativeHandle_); - } - - @Override - public long maxManifestFileSize() { - assert(isOwningHandle()); - return maxManifestFileSize(nativeHandle_); - } - - @Override - public Options setMaxManifestFileSize( - final long maxManifestFileSize) { - assert(isOwningHandle()); - setMaxManifestFileSize(nativeHandle_, maxManifestFileSize); - return this; - } - - @Override - public Options setMaxTableFilesSizeFIFO( - final long maxTableFilesSize) { - assert(maxTableFilesSize > 0); // unsigned native type - assert(isOwningHandle()); - setMaxTableFilesSizeFIFO(nativeHandle_, maxTableFilesSize); - return this; - } - - @Override - public long maxTableFilesSizeFIFO() { - return maxTableFilesSizeFIFO(nativeHandle_); - } - - @Override - public int tableCacheNumshardbits() { - assert(isOwningHandle()); - return tableCacheNumshardbits(nativeHandle_); - } - - @Override - public Options setTableCacheNumshardbits( - final int tableCacheNumshardbits) { - assert(isOwningHandle()); - setTableCacheNumshardbits(nativeHandle_, tableCacheNumshardbits); - return this; - } - - @Override - public long walTtlSeconds() { - assert(isOwningHandle()); - return walTtlSeconds(nativeHandle_); - } - - @Override - public Options setWalTtlSeconds(final long walTtlSeconds) { - assert(isOwningHandle()); - setWalTtlSeconds(nativeHandle_, walTtlSeconds); - return this; - } - - @Override - public long walSizeLimitMB() { - assert(isOwningHandle()); - return walSizeLimitMB(nativeHandle_); - } - - @Override - public Options setMaxWriteBatchGroupSizeBytes(long maxWriteBatchGroupSizeBytes) { - setMaxWriteBatchGroupSizeBytes(nativeHandle_, maxWriteBatchGroupSizeBytes); - return this; - } - - @Override - public long maxWriteBatchGroupSizeBytes() { - assert (isOwningHandle()); - return maxWriteBatchGroupSizeBytes(nativeHandle_); - } - - @Override - public Options setWalSizeLimitMB(final long sizeLimitMB) { - assert(isOwningHandle()); - setWalSizeLimitMB(nativeHandle_, sizeLimitMB); - return this; - } - - @Override - public long manifestPreallocationSize() { - assert(isOwningHandle()); - return manifestPreallocationSize(nativeHandle_); - } - - @Override - public Options setManifestPreallocationSize(final long size) { - assert(isOwningHandle()); - setManifestPreallocationSize(nativeHandle_, size); - return this; - } - - @Override - public Options setUseDirectReads(final boolean useDirectReads) { - assert(isOwningHandle()); - setUseDirectReads(nativeHandle_, useDirectReads); - return this; - } - - @Override - public boolean useDirectReads() { - assert(isOwningHandle()); - return useDirectReads(nativeHandle_); - } - - @Override - public Options setUseDirectIoForFlushAndCompaction( - final boolean useDirectIoForFlushAndCompaction) { - assert(isOwningHandle()); - setUseDirectIoForFlushAndCompaction(nativeHandle_, useDirectIoForFlushAndCompaction); - return this; - } - - @Override - public boolean useDirectIoForFlushAndCompaction() { - assert(isOwningHandle()); - return useDirectIoForFlushAndCompaction(nativeHandle_); - } - - @Override - public Options setAllowFAllocate(final boolean allowFAllocate) { - assert(isOwningHandle()); - setAllowFAllocate(nativeHandle_, allowFAllocate); - return this; - } - - @Override - public boolean allowFAllocate() { - assert(isOwningHandle()); - return allowFAllocate(nativeHandle_); - } - - @Override - public boolean allowMmapReads() { - assert(isOwningHandle()); - return allowMmapReads(nativeHandle_); - } - - @Override - public Options setAllowMmapReads(final boolean allowMmapReads) { - assert(isOwningHandle()); - setAllowMmapReads(nativeHandle_, allowMmapReads); - return this; - } - - @Override - public boolean allowMmapWrites() { - assert(isOwningHandle()); - return allowMmapWrites(nativeHandle_); - } - - @Override - public Options setAllowMmapWrites(final boolean allowMmapWrites) { - assert(isOwningHandle()); - setAllowMmapWrites(nativeHandle_, allowMmapWrites); - return this; - } - - @Override - public boolean isFdCloseOnExec() { - assert(isOwningHandle()); - return isFdCloseOnExec(nativeHandle_); - } - - @Override - public Options setIsFdCloseOnExec(final boolean isFdCloseOnExec) { - assert(isOwningHandle()); - setIsFdCloseOnExec(nativeHandle_, isFdCloseOnExec); - return this; - } - - @Override - public int statsDumpPeriodSec() { - assert(isOwningHandle()); - return statsDumpPeriodSec(nativeHandle_); - } - - @Override - public Options setStatsDumpPeriodSec(final int statsDumpPeriodSec) { - assert(isOwningHandle()); - setStatsDumpPeriodSec(nativeHandle_, statsDumpPeriodSec); - return this; - } - - @Override - public Options setStatsPersistPeriodSec( - final int statsPersistPeriodSec) { - assert(isOwningHandle()); - setStatsPersistPeriodSec(nativeHandle_, statsPersistPeriodSec); - return this; - } - - @Override - public int statsPersistPeriodSec() { - assert(isOwningHandle()); - return statsPersistPeriodSec(nativeHandle_); - } - - @Override - public Options setStatsHistoryBufferSize( - final long statsHistoryBufferSize) { - assert(isOwningHandle()); - setStatsHistoryBufferSize(nativeHandle_, statsHistoryBufferSize); - return this; - } - - @Override - public long statsHistoryBufferSize() { - assert(isOwningHandle()); - return statsHistoryBufferSize(nativeHandle_); - } - - @Override - public boolean adviseRandomOnOpen() { - return adviseRandomOnOpen(nativeHandle_); - } - - @Override - public Options setAdviseRandomOnOpen(final boolean adviseRandomOnOpen) { - assert(isOwningHandle()); - setAdviseRandomOnOpen(nativeHandle_, adviseRandomOnOpen); - return this; - } - - @Override - public Options setDbWriteBufferSize(final long dbWriteBufferSize) { - assert(isOwningHandle()); - setDbWriteBufferSize(nativeHandle_, dbWriteBufferSize); - return this; - } - - @Override - public Options setWriteBufferManager(final WriteBufferManager writeBufferManager) { - assert(isOwningHandle()); - setWriteBufferManager(nativeHandle_, writeBufferManager.nativeHandle_); - this.writeBufferManager_ = writeBufferManager; - return this; - } - - @Override - public WriteBufferManager writeBufferManager() { - assert(isOwningHandle()); - return this.writeBufferManager_; - } - - @Override - public long dbWriteBufferSize() { - assert(isOwningHandle()); - return dbWriteBufferSize(nativeHandle_); - } - - @Override - public Options setAccessHintOnCompactionStart(final AccessHint accessHint) { - assert(isOwningHandle()); - setAccessHintOnCompactionStart(nativeHandle_, accessHint.getValue()); - return this; - } - - @Override - public AccessHint accessHintOnCompactionStart() { - assert(isOwningHandle()); - return AccessHint.getAccessHint(accessHintOnCompactionStart(nativeHandle_)); - } - - @Override - public Options setCompactionReadaheadSize(final long compactionReadaheadSize) { - assert(isOwningHandle()); - setCompactionReadaheadSize(nativeHandle_, compactionReadaheadSize); - return this; - } - - @Override - public long compactionReadaheadSize() { - assert(isOwningHandle()); - return compactionReadaheadSize(nativeHandle_); - } - - @Override - public Options setRandomAccessMaxBufferSize(final long randomAccessMaxBufferSize) { - assert(isOwningHandle()); - setRandomAccessMaxBufferSize(nativeHandle_, randomAccessMaxBufferSize); - return this; - } - - @Override - public long randomAccessMaxBufferSize() { - assert(isOwningHandle()); - return randomAccessMaxBufferSize(nativeHandle_); - } - - @Override - public Options setWritableFileMaxBufferSize(final long writableFileMaxBufferSize) { - assert(isOwningHandle()); - setWritableFileMaxBufferSize(nativeHandle_, writableFileMaxBufferSize); - return this; - } - - @Override - public long writableFileMaxBufferSize() { - assert(isOwningHandle()); - return writableFileMaxBufferSize(nativeHandle_); - } - - @Override - public boolean useAdaptiveMutex() { - assert(isOwningHandle()); - return useAdaptiveMutex(nativeHandle_); - } - - @Override - public Options setUseAdaptiveMutex(final boolean useAdaptiveMutex) { - assert(isOwningHandle()); - setUseAdaptiveMutex(nativeHandle_, useAdaptiveMutex); - return this; - } - - @Override - public long bytesPerSync() { - return bytesPerSync(nativeHandle_); - } - - @Override - public Options setBytesPerSync(final long bytesPerSync) { - assert(isOwningHandle()); - setBytesPerSync(nativeHandle_, bytesPerSync); - return this; - } - - @Override - public Options setWalBytesPerSync(final long walBytesPerSync) { - assert(isOwningHandle()); - setWalBytesPerSync(nativeHandle_, walBytesPerSync); - return this; - } - - @Override - public long walBytesPerSync() { - assert(isOwningHandle()); - return walBytesPerSync(nativeHandle_); - } - - @Override - public Options setStrictBytesPerSync(final boolean strictBytesPerSync) { - assert(isOwningHandle()); - setStrictBytesPerSync(nativeHandle_, strictBytesPerSync); - return this; - } - - @Override - public boolean strictBytesPerSync() { - assert(isOwningHandle()); - return strictBytesPerSync(nativeHandle_); - } - - @Override - public Options setListeners(final List listeners) { - assert (isOwningHandle()); - setEventListeners(nativeHandle_, RocksCallbackObject.toNativeHandleList(listeners)); - return this; - } - - @Override - public List listeners() { - assert (isOwningHandle()); - return Arrays.asList(eventListeners(nativeHandle_)); - } - - @Override - public Options setEnableThreadTracking(final boolean enableThreadTracking) { - assert(isOwningHandle()); - setEnableThreadTracking(nativeHandle_, enableThreadTracking); - return this; - } - - @Override - public boolean enableThreadTracking() { - assert(isOwningHandle()); - return enableThreadTracking(nativeHandle_); - } - - @Override - public Options setDelayedWriteRate(final long delayedWriteRate) { - assert(isOwningHandle()); - setDelayedWriteRate(nativeHandle_, delayedWriteRate); - return this; - } - - @Override - public long delayedWriteRate(){ - return delayedWriteRate(nativeHandle_); - } - - @Override - public Options setEnablePipelinedWrite(final boolean enablePipelinedWrite) { - setEnablePipelinedWrite(nativeHandle_, enablePipelinedWrite); - return this; - } - - @Override - public boolean enablePipelinedWrite() { - return enablePipelinedWrite(nativeHandle_); - } - - @Override - public Options setUnorderedWrite(final boolean unorderedWrite) { - setUnorderedWrite(nativeHandle_, unorderedWrite); - return this; - } - - @Override - public boolean unorderedWrite() { - return unorderedWrite(nativeHandle_); - } - - @Override - public Options setAllowConcurrentMemtableWrite( - final boolean allowConcurrentMemtableWrite) { - setAllowConcurrentMemtableWrite(nativeHandle_, - allowConcurrentMemtableWrite); - return this; - } - - @Override - public boolean allowConcurrentMemtableWrite() { - return allowConcurrentMemtableWrite(nativeHandle_); - } - - @Override - public Options setEnableWriteThreadAdaptiveYield( - final boolean enableWriteThreadAdaptiveYield) { - setEnableWriteThreadAdaptiveYield(nativeHandle_, - enableWriteThreadAdaptiveYield); - return this; - } - - @Override - public boolean enableWriteThreadAdaptiveYield() { - return enableWriteThreadAdaptiveYield(nativeHandle_); - } - - @Override - public Options setWriteThreadMaxYieldUsec(final long writeThreadMaxYieldUsec) { - setWriteThreadMaxYieldUsec(nativeHandle_, writeThreadMaxYieldUsec); - return this; - } - - @Override - public long writeThreadMaxYieldUsec() { - return writeThreadMaxYieldUsec(nativeHandle_); - } - - @Override - public Options setWriteThreadSlowYieldUsec(final long writeThreadSlowYieldUsec) { - setWriteThreadSlowYieldUsec(nativeHandle_, writeThreadSlowYieldUsec); - return this; - } - - @Override - public long writeThreadSlowYieldUsec() { - return writeThreadSlowYieldUsec(nativeHandle_); - } - - @Override - public Options setSkipStatsUpdateOnDbOpen(final boolean skipStatsUpdateOnDbOpen) { - assert(isOwningHandle()); - setSkipStatsUpdateOnDbOpen(nativeHandle_, skipStatsUpdateOnDbOpen); - return this; - } - - @Override - public boolean skipStatsUpdateOnDbOpen() { - assert(isOwningHandle()); - return skipStatsUpdateOnDbOpen(nativeHandle_); - } - - @Override - public Options setSkipCheckingSstFileSizesOnDbOpen(boolean skipCheckingSstFileSizesOnDbOpen) { - setSkipCheckingSstFileSizesOnDbOpen(nativeHandle_, skipCheckingSstFileSizesOnDbOpen); - return this; - } - - @Override - public boolean skipCheckingSstFileSizesOnDbOpen() { - assert (isOwningHandle()); - return skipCheckingSstFileSizesOnDbOpen(nativeHandle_); - } - - @Override - public Options setWalRecoveryMode(final WALRecoveryMode walRecoveryMode) { - assert(isOwningHandle()); - setWalRecoveryMode(nativeHandle_, walRecoveryMode.getValue()); - return this; - } - - @Override - public WALRecoveryMode walRecoveryMode() { - assert(isOwningHandle()); - return WALRecoveryMode.getWALRecoveryMode(walRecoveryMode(nativeHandle_)); - } - - @Override - public Options setAllow2pc(final boolean allow2pc) { - assert(isOwningHandle()); - setAllow2pc(nativeHandle_, allow2pc); - return this; - } - - @Override - public boolean allow2pc() { - assert(isOwningHandle()); - return allow2pc(nativeHandle_); - } - - @Override - public Options setRowCache(final Cache rowCache) { - assert(isOwningHandle()); - setRowCache(nativeHandle_, rowCache.nativeHandle_); - this.rowCache_ = rowCache; - return this; - } - - @Override - public Cache rowCache() { - assert(isOwningHandle()); - return this.rowCache_; - } - - @Override - public Options setWalFilter(final AbstractWalFilter walFilter) { - assert(isOwningHandle()); - setWalFilter(nativeHandle_, walFilter.nativeHandle_); - this.walFilter_ = walFilter; - return this; - } - - @Override - public WalFilter walFilter() { - assert(isOwningHandle()); - return this.walFilter_; - } - - @Override - public Options setFailIfOptionsFileError(final boolean failIfOptionsFileError) { - assert(isOwningHandle()); - setFailIfOptionsFileError(nativeHandle_, failIfOptionsFileError); - return this; - } - - @Override - public boolean failIfOptionsFileError() { - assert(isOwningHandle()); - return failIfOptionsFileError(nativeHandle_); - } - - @Override - public Options setDumpMallocStats(final boolean dumpMallocStats) { - assert(isOwningHandle()); - setDumpMallocStats(nativeHandle_, dumpMallocStats); - return this; - } - - @Override - public boolean dumpMallocStats() { - assert(isOwningHandle()); - return dumpMallocStats(nativeHandle_); - } - - @Override - public Options setAvoidFlushDuringRecovery(final boolean avoidFlushDuringRecovery) { - assert(isOwningHandle()); - setAvoidFlushDuringRecovery(nativeHandle_, avoidFlushDuringRecovery); - return this; - } - - @Override - public boolean avoidFlushDuringRecovery() { - assert(isOwningHandle()); - return avoidFlushDuringRecovery(nativeHandle_); - } - - @Override - public Options setAvoidFlushDuringShutdown(final boolean avoidFlushDuringShutdown) { - assert(isOwningHandle()); - setAvoidFlushDuringShutdown(nativeHandle_, avoidFlushDuringShutdown); - return this; - } - - @Override - public boolean avoidFlushDuringShutdown() { - assert(isOwningHandle()); - return avoidFlushDuringShutdown(nativeHandle_); - } - - @Override - public Options setAllowIngestBehind(final boolean allowIngestBehind) { - assert(isOwningHandle()); - setAllowIngestBehind(nativeHandle_, allowIngestBehind); - return this; - } - - @Override - public boolean allowIngestBehind() { - assert(isOwningHandle()); - return allowIngestBehind(nativeHandle_); - } - - @Override - public Options setTwoWriteQueues(final boolean twoWriteQueues) { - assert(isOwningHandle()); - setTwoWriteQueues(nativeHandle_, twoWriteQueues); - return this; - } - - @Override - public boolean twoWriteQueues() { - assert(isOwningHandle()); - return twoWriteQueues(nativeHandle_); - } - - @Override - public Options setManualWalFlush(final boolean manualWalFlush) { - assert(isOwningHandle()); - setManualWalFlush(nativeHandle_, manualWalFlush); - return this; - } - - @Override - public boolean manualWalFlush() { - assert(isOwningHandle()); - return manualWalFlush(nativeHandle_); - } - - @Override - public MemTableConfig memTableConfig() { - return this.memTableConfig_; - } - - @Override - public Options setMemTableConfig(final MemTableConfig config) { - memTableConfig_ = config; - setMemTableFactory(nativeHandle_, config.newMemTableFactoryHandle()); - return this; - } - - @Override - public Options setRateLimiter(final RateLimiter rateLimiter) { - assert(isOwningHandle()); - rateLimiter_ = rateLimiter; - setRateLimiter(nativeHandle_, rateLimiter.nativeHandle_); - return this; - } - - @Override - public Options setSstFileManager(final SstFileManager sstFileManager) { - assert(isOwningHandle()); - setSstFileManager(nativeHandle_, sstFileManager.nativeHandle_); - return this; - } - - @Override - public Options setLogger(final Logger logger) { - assert(isOwningHandle()); - setLogger(nativeHandle_, logger.nativeHandle_); - return this; - } - - @Override - public Options setInfoLogLevel(final InfoLogLevel infoLogLevel) { - assert(isOwningHandle()); - setInfoLogLevel(nativeHandle_, infoLogLevel.getValue()); - return this; - } - - @Override - public InfoLogLevel infoLogLevel() { - assert(isOwningHandle()); - return InfoLogLevel.getInfoLogLevel( - infoLogLevel(nativeHandle_)); - } - - @Override - public String memTableFactoryName() { - assert(isOwningHandle()); - return memTableFactoryName(nativeHandle_); - } - - @Override - public TableFormatConfig tableFormatConfig() { - return this.tableFormatConfig_; - } - - @Override - public Options setTableFormatConfig(final TableFormatConfig config) { - tableFormatConfig_ = config; - setTableFactory(nativeHandle_, config.newTableFactoryHandle()); - return this; - } - - @Override - public String tableFactoryName() { - assert(isOwningHandle()); - return tableFactoryName(nativeHandle_); - } - - @Override - public Options setCfPaths(final Collection cfPaths) { - assert (isOwningHandle()); - - final int len = cfPaths.size(); - final String[] paths = new String[len]; - final long[] targetSizes = new long[len]; - - int i = 0; - for (final DbPath dbPath : cfPaths) { - paths[i] = dbPath.path.toString(); - targetSizes[i] = dbPath.targetSize; - i++; - } - setCfPaths(nativeHandle_, paths, targetSizes); - return this; - } - - @Override - public List cfPaths() { - final int len = (int) cfPathsLen(nativeHandle_); - - if (len == 0) { - return Collections.emptyList(); - } - - final String[] paths = new String[len]; - final long[] targetSizes = new long[len]; - - cfPaths(nativeHandle_, paths, targetSizes); - - final List cfPaths = new ArrayList<>(); - for (int i = 0; i < len; i++) { - cfPaths.add(new DbPath(Paths.get(paths[i]), targetSizes[i])); - } - - return cfPaths; - } - - @Override - public Options useFixedLengthPrefixExtractor(final int n) { - assert(isOwningHandle()); - useFixedLengthPrefixExtractor(nativeHandle_, n); - return this; - } - - @Override - public Options useCappedPrefixExtractor(final int n) { - assert(isOwningHandle()); - useCappedPrefixExtractor(nativeHandle_, n); - return this; - } - - @Override - public CompressionType compressionType() { - return CompressionType.getCompressionType(compressionType(nativeHandle_)); - } - - @Override - public Options setCompressionPerLevel( - final List compressionLevels) { - final byte[] byteCompressionTypes = new byte[ - compressionLevels.size()]; - for (int i = 0; i < compressionLevels.size(); i++) { - byteCompressionTypes[i] = compressionLevels.get(i).getValue(); - } - setCompressionPerLevel(nativeHandle_, byteCompressionTypes); - return this; - } - - @Override - public List compressionPerLevel() { - final byte[] byteCompressionTypes = - compressionPerLevel(nativeHandle_); - final List compressionLevels = new ArrayList<>(); - for (final byte byteCompressionType : byteCompressionTypes) { - compressionLevels.add(CompressionType.getCompressionType( - byteCompressionType)); - } - return compressionLevels; - } - - @Override - public Options setCompressionType(CompressionType compressionType) { - setCompressionType(nativeHandle_, compressionType.getValue()); - return this; - } - - - @Override - public Options setBottommostCompressionType( - final CompressionType bottommostCompressionType) { - setBottommostCompressionType(nativeHandle_, - bottommostCompressionType.getValue()); - return this; - } - - @Override - public CompressionType bottommostCompressionType() { - return CompressionType.getCompressionType( - bottommostCompressionType(nativeHandle_)); - } - - @Override - public Options setBottommostCompressionOptions( - final CompressionOptions bottommostCompressionOptions) { - setBottommostCompressionOptions(nativeHandle_, - bottommostCompressionOptions.nativeHandle_); - this.bottommostCompressionOptions_ = bottommostCompressionOptions; - return this; - } - - @Override - public CompressionOptions bottommostCompressionOptions() { - return this.bottommostCompressionOptions_; - } - - @Override - public Options setCompressionOptions( - final CompressionOptions compressionOptions) { - setCompressionOptions(nativeHandle_, compressionOptions.nativeHandle_); - this.compressionOptions_ = compressionOptions; - return this; - } - - @Override - public CompressionOptions compressionOptions() { - return this.compressionOptions_; - } - - @Override - public CompactionStyle compactionStyle() { - return CompactionStyle.fromValue(compactionStyle(nativeHandle_)); - } - - @Override - public Options setCompactionStyle( - final CompactionStyle compactionStyle) { - setCompactionStyle(nativeHandle_, compactionStyle.getValue()); - return this; - } - - @Override - public int numLevels() { - return numLevels(nativeHandle_); - } - - @Override - public Options setNumLevels(int numLevels) { - setNumLevels(nativeHandle_, numLevels); - return this; - } - - @Override - public int levelZeroFileNumCompactionTrigger() { - return levelZeroFileNumCompactionTrigger(nativeHandle_); - } - - @Override - public Options setLevelZeroFileNumCompactionTrigger( - final int numFiles) { - setLevelZeroFileNumCompactionTrigger( - nativeHandle_, numFiles); - return this; - } - - @Override - public int levelZeroSlowdownWritesTrigger() { - return levelZeroSlowdownWritesTrigger(nativeHandle_); - } - - @Override - public Options setLevelZeroSlowdownWritesTrigger( - final int numFiles) { - setLevelZeroSlowdownWritesTrigger(nativeHandle_, numFiles); - return this; - } - - @Override - public int levelZeroStopWritesTrigger() { - return levelZeroStopWritesTrigger(nativeHandle_); - } - - @Override - public Options setLevelZeroStopWritesTrigger( - final int numFiles) { - setLevelZeroStopWritesTrigger(nativeHandle_, numFiles); - return this; - } - - @Override - public long targetFileSizeBase() { - return targetFileSizeBase(nativeHandle_); - } - - @Override - public Options setTargetFileSizeBase(long targetFileSizeBase) { - setTargetFileSizeBase(nativeHandle_, targetFileSizeBase); - return this; - } - - @Override - public int targetFileSizeMultiplier() { - return targetFileSizeMultiplier(nativeHandle_); - } - - @Override - public Options setTargetFileSizeMultiplier(int multiplier) { - setTargetFileSizeMultiplier(nativeHandle_, multiplier); - return this; - } - - @Override - public Options setMaxBytesForLevelBase(final long maxBytesForLevelBase) { - setMaxBytesForLevelBase(nativeHandle_, maxBytesForLevelBase); - return this; - } - - @Override - public long maxBytesForLevelBase() { - return maxBytesForLevelBase(nativeHandle_); - } - - @Override - public Options setLevelCompactionDynamicLevelBytes( - final boolean enableLevelCompactionDynamicLevelBytes) { - setLevelCompactionDynamicLevelBytes(nativeHandle_, - enableLevelCompactionDynamicLevelBytes); - return this; - } - - @Override - public boolean levelCompactionDynamicLevelBytes() { - return levelCompactionDynamicLevelBytes(nativeHandle_); - } - - @Override - public double maxBytesForLevelMultiplier() { - return maxBytesForLevelMultiplier(nativeHandle_); - } - - @Override - public Options setMaxBytesForLevelMultiplier(final double multiplier) { - setMaxBytesForLevelMultiplier(nativeHandle_, multiplier); - return this; - } - - @Override - public long maxCompactionBytes() { - return maxCompactionBytes(nativeHandle_); - } - - @Override - public Options setMaxCompactionBytes(final long maxCompactionBytes) { - setMaxCompactionBytes(nativeHandle_, maxCompactionBytes); - return this; - } - - @Override - public long arenaBlockSize() { - return arenaBlockSize(nativeHandle_); - } - - @Override - public Options setArenaBlockSize(final long arenaBlockSize) { - setArenaBlockSize(nativeHandle_, arenaBlockSize); - return this; - } - - @Override - public boolean disableAutoCompactions() { - return disableAutoCompactions(nativeHandle_); - } - - @Override - public Options setDisableAutoCompactions( - final boolean disableAutoCompactions) { - setDisableAutoCompactions(nativeHandle_, disableAutoCompactions); - return this; - } - - @Override - public long maxSequentialSkipInIterations() { - return maxSequentialSkipInIterations(nativeHandle_); - } - - @Override - public Options setMaxSequentialSkipInIterations( - final long maxSequentialSkipInIterations) { - setMaxSequentialSkipInIterations(nativeHandle_, - maxSequentialSkipInIterations); - return this; - } - - @Override - public boolean inplaceUpdateSupport() { - return inplaceUpdateSupport(nativeHandle_); - } - - @Override - public Options setInplaceUpdateSupport( - final boolean inplaceUpdateSupport) { - setInplaceUpdateSupport(nativeHandle_, inplaceUpdateSupport); - return this; - } - - @Override - public long inplaceUpdateNumLocks() { - return inplaceUpdateNumLocks(nativeHandle_); - } - - @Override - public Options setInplaceUpdateNumLocks( - final long inplaceUpdateNumLocks) { - setInplaceUpdateNumLocks(nativeHandle_, inplaceUpdateNumLocks); - return this; - } - - @Override - public double memtablePrefixBloomSizeRatio() { - return memtablePrefixBloomSizeRatio(nativeHandle_); - } - - @Override - public Options setMemtablePrefixBloomSizeRatio(final double memtablePrefixBloomSizeRatio) { - setMemtablePrefixBloomSizeRatio(nativeHandle_, memtablePrefixBloomSizeRatio); - return this; - } - - @Override - public double experimentalMempurgeThreshold() { - return experimentalMempurgeThreshold(nativeHandle_); - } - - @Override - public Options setExperimentalMempurgeThreshold(final double experimentalMempurgeThreshold) { - setExperimentalMempurgeThreshold(nativeHandle_, experimentalMempurgeThreshold); - return this; - } - - @Override - public boolean memtableWholeKeyFiltering() { - return memtableWholeKeyFiltering(nativeHandle_); - } - - @Override - public Options setMemtableWholeKeyFiltering(final boolean memtableWholeKeyFiltering) { - setMemtableWholeKeyFiltering(nativeHandle_, memtableWholeKeyFiltering); - return this; - } - - @Override - public int bloomLocality() { - return bloomLocality(nativeHandle_); - } - - @Override - public Options setBloomLocality(final int bloomLocality) { - setBloomLocality(nativeHandle_, bloomLocality); - return this; - } - - @Override - public long maxSuccessiveMerges() { - return maxSuccessiveMerges(nativeHandle_); - } - - @Override - public Options setMaxSuccessiveMerges(long maxSuccessiveMerges) { - setMaxSuccessiveMerges(nativeHandle_, maxSuccessiveMerges); - return this; - } - - @Override - public int minWriteBufferNumberToMerge() { - return minWriteBufferNumberToMerge(nativeHandle_); - } - - @Override - public Options setMinWriteBufferNumberToMerge( - final int minWriteBufferNumberToMerge) { - setMinWriteBufferNumberToMerge(nativeHandle_, minWriteBufferNumberToMerge); - return this; - } - - @Override - public Options setOptimizeFiltersForHits( - final boolean optimizeFiltersForHits) { - setOptimizeFiltersForHits(nativeHandle_, optimizeFiltersForHits); - return this; - } - - @Override - public boolean optimizeFiltersForHits() { - return optimizeFiltersForHits(nativeHandle_); - } - - @Override - public Options - setMemtableHugePageSize( - long memtableHugePageSize) { - setMemtableHugePageSize(nativeHandle_, - memtableHugePageSize); - return this; - } - - @Override - public long memtableHugePageSize() { - return memtableHugePageSize(nativeHandle_); - } - - @Override - public Options setSoftPendingCompactionBytesLimit(long softPendingCompactionBytesLimit) { - setSoftPendingCompactionBytesLimit(nativeHandle_, - softPendingCompactionBytesLimit); - return this; - } - - @Override - public long softPendingCompactionBytesLimit() { - return softPendingCompactionBytesLimit(nativeHandle_); - } - - @Override - public Options setHardPendingCompactionBytesLimit(long hardPendingCompactionBytesLimit) { - setHardPendingCompactionBytesLimit(nativeHandle_, hardPendingCompactionBytesLimit); - return this; - } - - @Override - public long hardPendingCompactionBytesLimit() { - return hardPendingCompactionBytesLimit(nativeHandle_); - } - - @Override - public Options setLevel0FileNumCompactionTrigger(int level0FileNumCompactionTrigger) { - setLevel0FileNumCompactionTrigger(nativeHandle_, level0FileNumCompactionTrigger); - return this; - } - - @Override - public int level0FileNumCompactionTrigger() { - return level0FileNumCompactionTrigger(nativeHandle_); - } - - @Override - public Options setLevel0SlowdownWritesTrigger(int level0SlowdownWritesTrigger) { - setLevel0SlowdownWritesTrigger(nativeHandle_, level0SlowdownWritesTrigger); - return this; - } - - @Override - public int level0SlowdownWritesTrigger() { - return level0SlowdownWritesTrigger(nativeHandle_); - } - - @Override - public Options setLevel0StopWritesTrigger(int level0StopWritesTrigger) { - setLevel0StopWritesTrigger(nativeHandle_, level0StopWritesTrigger); - return this; - } - - @Override - public int level0StopWritesTrigger() { - return level0StopWritesTrigger(nativeHandle_); - } - - @Override - public Options setMaxBytesForLevelMultiplierAdditional(int[] maxBytesForLevelMultiplierAdditional) { - setMaxBytesForLevelMultiplierAdditional(nativeHandle_, maxBytesForLevelMultiplierAdditional); - return this; - } - - @Override - public int[] maxBytesForLevelMultiplierAdditional() { - return maxBytesForLevelMultiplierAdditional(nativeHandle_); - } - - @Override - public Options setParanoidFileChecks(boolean paranoidFileChecks) { - setParanoidFileChecks(nativeHandle_, paranoidFileChecks); - return this; - } - - @Override - public boolean paranoidFileChecks() { - return paranoidFileChecks(nativeHandle_); - } - - @Override - public Options setMaxWriteBufferNumberToMaintain( - final int maxWriteBufferNumberToMaintain) { - setMaxWriteBufferNumberToMaintain( - nativeHandle_, maxWriteBufferNumberToMaintain); - return this; - } - - @Override - public int maxWriteBufferNumberToMaintain() { - return maxWriteBufferNumberToMaintain(nativeHandle_); - } - - @Override - public Options setCompactionPriority( - final CompactionPriority compactionPriority) { - setCompactionPriority(nativeHandle_, compactionPriority.getValue()); - return this; - } - - @Override - public CompactionPriority compactionPriority() { - return CompactionPriority.getCompactionPriority( - compactionPriority(nativeHandle_)); - } - - @Override - public Options setReportBgIoStats(final boolean reportBgIoStats) { - setReportBgIoStats(nativeHandle_, reportBgIoStats); - return this; - } - - @Override - public boolean reportBgIoStats() { - return reportBgIoStats(nativeHandle_); - } - - @Override - public Options setTtl(final long ttl) { - setTtl(nativeHandle_, ttl); - return this; - } - - @Override - public long ttl() { - return ttl(nativeHandle_); - } - - @Override - public Options setPeriodicCompactionSeconds(final long periodicCompactionSeconds) { - setPeriodicCompactionSeconds(nativeHandle_, periodicCompactionSeconds); - return this; - } - - @Override - public long periodicCompactionSeconds() { - return periodicCompactionSeconds(nativeHandle_); - } - - @Override - public Options setCompactionOptionsUniversal( - final CompactionOptionsUniversal compactionOptionsUniversal) { - setCompactionOptionsUniversal(nativeHandle_, - compactionOptionsUniversal.nativeHandle_); - this.compactionOptionsUniversal_ = compactionOptionsUniversal; - return this; - } - - @Override - public CompactionOptionsUniversal compactionOptionsUniversal() { - return this.compactionOptionsUniversal_; - } - - @Override - public Options setCompactionOptionsFIFO(final CompactionOptionsFIFO compactionOptionsFIFO) { - setCompactionOptionsFIFO(nativeHandle_, - compactionOptionsFIFO.nativeHandle_); - this.compactionOptionsFIFO_ = compactionOptionsFIFO; - return this; - } - - @Override - public CompactionOptionsFIFO compactionOptionsFIFO() { - return this.compactionOptionsFIFO_; - } - - @Override - public Options setForceConsistencyChecks(final boolean forceConsistencyChecks) { - setForceConsistencyChecks(nativeHandle_, forceConsistencyChecks); - return this; - } - - @Override - public boolean forceConsistencyChecks() { - return forceConsistencyChecks(nativeHandle_); - } - - @Override - public Options setAtomicFlush(final boolean atomicFlush) { - setAtomicFlush(nativeHandle_, atomicFlush); - return this; - } - - @Override - public boolean atomicFlush() { - return atomicFlush(nativeHandle_); - } - - @Override - public Options setAvoidUnnecessaryBlockingIO(boolean avoidUnnecessaryBlockingIO) { - setAvoidUnnecessaryBlockingIO(nativeHandle_, avoidUnnecessaryBlockingIO); - return this; - } - - @Override - public boolean avoidUnnecessaryBlockingIO() { - assert (isOwningHandle()); - return avoidUnnecessaryBlockingIO(nativeHandle_); - } - - @Override - public Options setPersistStatsToDisk(boolean persistStatsToDisk) { - setPersistStatsToDisk(nativeHandle_, persistStatsToDisk); - return this; - } - - @Override - public boolean persistStatsToDisk() { - assert (isOwningHandle()); - return persistStatsToDisk(nativeHandle_); - } - - @Override - public Options setWriteDbidToManifest(boolean writeDbidToManifest) { - setWriteDbidToManifest(nativeHandle_, writeDbidToManifest); - return this; - } - - @Override - public boolean writeDbidToManifest() { - assert (isOwningHandle()); - return writeDbidToManifest(nativeHandle_); - } - - @Override - public Options setLogReadaheadSize(long logReadaheadSize) { - setLogReadaheadSize(nativeHandle_, logReadaheadSize); - return this; - } - - @Override - public long logReadaheadSize() { - assert (isOwningHandle()); - return logReadaheadSize(nativeHandle_); - } - - @Override - public Options setBestEffortsRecovery(boolean bestEffortsRecovery) { - setBestEffortsRecovery(nativeHandle_, bestEffortsRecovery); - return this; - } - - @Override - public boolean bestEffortsRecovery() { - assert (isOwningHandle()); - return bestEffortsRecovery(nativeHandle_); - } - - @Override - public Options setMaxBgErrorResumeCount(int maxBgerrorResumeCount) { - setMaxBgErrorResumeCount(nativeHandle_, maxBgerrorResumeCount); - return this; - } - - @Override - public int maxBgerrorResumeCount() { - assert (isOwningHandle()); - return maxBgerrorResumeCount(nativeHandle_); - } - - @Override - public Options setBgerrorResumeRetryInterval(long bgerrorResumeRetryInterval) { - setBgerrorResumeRetryInterval(nativeHandle_, bgerrorResumeRetryInterval); - return this; - } - - @Override - public long bgerrorResumeRetryInterval() { - assert (isOwningHandle()); - return bgerrorResumeRetryInterval(nativeHandle_); - } - - @Override - public Options setSstPartitionerFactory(SstPartitionerFactory sstPartitionerFactory) { - setSstPartitionerFactory(nativeHandle_, sstPartitionerFactory.nativeHandle_); - this.sstPartitionerFactory_ = sstPartitionerFactory; - return this; - } - - @Override - public SstPartitionerFactory sstPartitionerFactory() { - return sstPartitionerFactory_; - } - - @Override - public Options setCompactionThreadLimiter(final ConcurrentTaskLimiter compactionThreadLimiter) { - setCompactionThreadLimiter(nativeHandle_, compactionThreadLimiter.nativeHandle_); - this.compactionThreadLimiter_ = compactionThreadLimiter; - return this; - } - - @Override - public ConcurrentTaskLimiter compactionThreadLimiter() { - assert (isOwningHandle()); - return this.compactionThreadLimiter_; - } - - // - // BEGIN options for blobs (integrated BlobDB) - // - - @Override - public Options setEnableBlobFiles(final boolean enableBlobFiles) { - setEnableBlobFiles(nativeHandle_, enableBlobFiles); - return this; - } - - @Override - public boolean enableBlobFiles() { - return enableBlobFiles(nativeHandle_); - } - - @Override - public Options setMinBlobSize(final long minBlobSize) { - setMinBlobSize(nativeHandle_, minBlobSize); - return this; - } - - @Override - public long minBlobSize() { - return minBlobSize(nativeHandle_); - } - - @Override - public Options setBlobFileSize(final long blobFileSize) { - setBlobFileSize(nativeHandle_, blobFileSize); - return this; - } - - @Override - public long blobFileSize() { - return blobFileSize(nativeHandle_); - } - - @Override - public Options setBlobCompressionType(CompressionType compressionType) { - setBlobCompressionType(nativeHandle_, compressionType.getValue()); - return this; - } - - @Override - public CompressionType blobCompressionType() { - return CompressionType.values()[blobCompressionType(nativeHandle_)]; - } - - @Override - public Options setEnableBlobGarbageCollection(final boolean enableBlobGarbageCollection) { - setEnableBlobGarbageCollection(nativeHandle_, enableBlobGarbageCollection); - return this; - } - - @Override - public boolean enableBlobGarbageCollection() { - return enableBlobGarbageCollection(nativeHandle_); - } - - @Override - public Options setBlobGarbageCollectionAgeCutoff(final double blobGarbageCollectionAgeCutoff) { - setBlobGarbageCollectionAgeCutoff(nativeHandle_, blobGarbageCollectionAgeCutoff); - return this; - } - - @Override - public double blobGarbageCollectionAgeCutoff() { - return blobGarbageCollectionAgeCutoff(nativeHandle_); - } - - @Override - public Options setBlobGarbageCollectionForceThreshold( - final double blobGarbageCollectionForceThreshold) { - setBlobGarbageCollectionForceThreshold(nativeHandle_, blobGarbageCollectionForceThreshold); - return this; - } - - @Override - public double blobGarbageCollectionForceThreshold() { - return blobGarbageCollectionForceThreshold(nativeHandle_); - } - - @Override - public Options setBlobCompactionReadaheadSize(final long blobCompactionReadaheadSize) { - setBlobCompactionReadaheadSize(nativeHandle_, blobCompactionReadaheadSize); - return this; - } - - @Override - public long blobCompactionReadaheadSize() { - return blobCompactionReadaheadSize(nativeHandle_); - } - - @Override - public Options setBlobFileStartingLevel(final int blobFileStartingLevel) { - setBlobFileStartingLevel(nativeHandle_, blobFileStartingLevel); - return this; - } - - @Override - public int blobFileStartingLevel() { - return blobFileStartingLevel(nativeHandle_); - } - - @Override - public Options setPrepopulateBlobCache(final PrepopulateBlobCache prepopulateBlobCache) { - setPrepopulateBlobCache(nativeHandle_, prepopulateBlobCache.getValue()); - return this; - } - - @Override - public PrepopulateBlobCache prepopulateBlobCache() { - return PrepopulateBlobCache.getPrepopulateBlobCache(prepopulateBlobCache(nativeHandle_)); - } - - // - // END options for blobs (integrated BlobDB) - // - - private native static long newOptions(); - private native static long newOptions(long dbOptHandle, - long cfOptHandle); - private native static long copyOptions(long handle); - @Override protected final native void disposeInternal(final long handle); - private native void setEnv(long optHandle, long envHandle); - private native void prepareForBulkLoad(long handle); - - // DB native handles - private native void setIncreaseParallelism(long handle, int totalThreads); - private native void setCreateIfMissing(long handle, boolean flag); - private native boolean createIfMissing(long handle); - private native void setCreateMissingColumnFamilies( - long handle, boolean flag); - private native boolean createMissingColumnFamilies(long handle); - private native void setErrorIfExists(long handle, boolean errorIfExists); - private native boolean errorIfExists(long handle); - private native void setParanoidChecks( - long handle, boolean paranoidChecks); - private native boolean paranoidChecks(long handle); - private native void setRateLimiter(long handle, - long rateLimiterHandle); - private native void setSstFileManager(final long handle, - final long sstFileManagerHandle); - private native void setLogger(long handle, - long loggerHandle); - private native void setInfoLogLevel(long handle, byte logLevel); - private native byte infoLogLevel(long handle); - private native void setMaxOpenFiles(long handle, int maxOpenFiles); - private native int maxOpenFiles(long handle); - private native void setMaxTotalWalSize(long handle, - long maxTotalWalSize); - private native void setMaxFileOpeningThreads(final long handle, - final int maxFileOpeningThreads); - private native int maxFileOpeningThreads(final long handle); - private native long maxTotalWalSize(long handle); - private native void setStatistics(final long handle, final long statisticsHandle); - private native long statistics(final long handle); - private native boolean useFsync(long handle); - private native void setUseFsync(long handle, boolean useFsync); - private native void setDbPaths(final long handle, final String[] paths, - final long[] targetSizes); - private native long dbPathsLen(final long handle); - private native void dbPaths(final long handle, final String[] paths, - final long[] targetSizes); - private native void setDbLogDir(long handle, String dbLogDir); - private native String dbLogDir(long handle); - private native void setWalDir(long handle, String walDir); - private native String walDir(long handle); - private native void setDeleteObsoleteFilesPeriodMicros( - long handle, long micros); - private native long deleteObsoleteFilesPeriodMicros(long handle); - private native void setMaxBackgroundCompactions( - long handle, int maxBackgroundCompactions); - private native int maxBackgroundCompactions(long handle); - private native void setMaxSubcompactions(long handle, int maxSubcompactions); - private native int maxSubcompactions(long handle); - private native void setMaxBackgroundFlushes( - long handle, int maxBackgroundFlushes); - private native int maxBackgroundFlushes(long handle); - private native void setMaxBackgroundJobs(long handle, int maxMaxBackgroundJobs); - private native int maxBackgroundJobs(long handle); - private native void setMaxLogFileSize(long handle, long maxLogFileSize) - throws IllegalArgumentException; - private native long maxLogFileSize(long handle); - private native void setLogFileTimeToRoll( - long handle, long logFileTimeToRoll) throws IllegalArgumentException; - private native long logFileTimeToRoll(long handle); - private native void setKeepLogFileNum(long handle, long keepLogFileNum) - throws IllegalArgumentException; - private native long keepLogFileNum(long handle); - private native void setRecycleLogFileNum(long handle, long recycleLogFileNum); - private native long recycleLogFileNum(long handle); - private native void setMaxManifestFileSize( - long handle, long maxManifestFileSize); - private native long maxManifestFileSize(long handle); - private native void setMaxTableFilesSizeFIFO( - long handle, long maxTableFilesSize); - private native long maxTableFilesSizeFIFO(long handle); - private native void setTableCacheNumshardbits( - long handle, int tableCacheNumshardbits); - private native int tableCacheNumshardbits(long handle); - private native void setWalTtlSeconds(long handle, long walTtlSeconds); - private native long walTtlSeconds(long handle); - private native void setWalSizeLimitMB(long handle, long sizeLimitMB); - private native long walSizeLimitMB(long handle); - private static native void setMaxWriteBatchGroupSizeBytes( - final long handle, final long maxWriteBatchGroupSizeBytes); - private static native long maxWriteBatchGroupSizeBytes(final long handle); - private native void setManifestPreallocationSize( - long handle, long size) throws IllegalArgumentException; - private native long manifestPreallocationSize(long handle); - private native void setUseDirectReads(long handle, boolean useDirectReads); - private native boolean useDirectReads(long handle); - private native void setUseDirectIoForFlushAndCompaction( - long handle, boolean useDirectIoForFlushAndCompaction); - private native boolean useDirectIoForFlushAndCompaction(long handle); - private native void setAllowFAllocate(final long handle, - final boolean allowFAllocate); - private native boolean allowFAllocate(final long handle); - private native void setAllowMmapReads( - long handle, boolean allowMmapReads); - private native boolean allowMmapReads(long handle); - private native void setAllowMmapWrites( - long handle, boolean allowMmapWrites); - private native boolean allowMmapWrites(long handle); - private native void setIsFdCloseOnExec( - long handle, boolean isFdCloseOnExec); - private native boolean isFdCloseOnExec(long handle); - private native void setStatsDumpPeriodSec( - long handle, int statsDumpPeriodSec); - private native int statsDumpPeriodSec(long handle); - private native void setStatsPersistPeriodSec( - final long handle, final int statsPersistPeriodSec); - private native int statsPersistPeriodSec( - final long handle); - private native void setStatsHistoryBufferSize( - final long handle, final long statsHistoryBufferSize); - private native long statsHistoryBufferSize( - final long handle); - private native void setAdviseRandomOnOpen( - long handle, boolean adviseRandomOnOpen); - private native boolean adviseRandomOnOpen(long handle); - private native void setDbWriteBufferSize(final long handle, - final long dbWriteBufferSize); - private native void setWriteBufferManager(final long handle, - final long writeBufferManagerHandle); - private native long dbWriteBufferSize(final long handle); - private native void setAccessHintOnCompactionStart(final long handle, - final byte accessHintOnCompactionStart); - private native byte accessHintOnCompactionStart(final long handle); - private native void setCompactionReadaheadSize(final long handle, - final long compactionReadaheadSize); - private native long compactionReadaheadSize(final long handle); - private native void setRandomAccessMaxBufferSize(final long handle, - final long randomAccessMaxBufferSize); - private native long randomAccessMaxBufferSize(final long handle); - private native void setWritableFileMaxBufferSize(final long handle, - final long writableFileMaxBufferSize); - private native long writableFileMaxBufferSize(final long handle); - private native void setUseAdaptiveMutex( - long handle, boolean useAdaptiveMutex); - private native boolean useAdaptiveMutex(long handle); - private native void setBytesPerSync( - long handle, long bytesPerSync); - private native long bytesPerSync(long handle); - private native void setWalBytesPerSync(long handle, long walBytesPerSync); - private native long walBytesPerSync(long handle); - private native void setStrictBytesPerSync( - final long handle, final boolean strictBytesPerSync); - private native boolean strictBytesPerSync( - final long handle); - private static native void setEventListeners( - final long handle, final long[] eventListenerHandles); - private static native AbstractEventListener[] eventListeners(final long handle); - private native void setEnableThreadTracking(long handle, - boolean enableThreadTracking); - private native boolean enableThreadTracking(long handle); - private native void setDelayedWriteRate(long handle, long delayedWriteRate); - private native long delayedWriteRate(long handle); - private native void setEnablePipelinedWrite(final long handle, - final boolean pipelinedWrite); - private native boolean enablePipelinedWrite(final long handle); - private native void setUnorderedWrite(final long handle, - final boolean unorderedWrite); - private native boolean unorderedWrite(final long handle); - private native void setAllowConcurrentMemtableWrite(long handle, - boolean allowConcurrentMemtableWrite); - private native boolean allowConcurrentMemtableWrite(long handle); - private native void setEnableWriteThreadAdaptiveYield(long handle, - boolean enableWriteThreadAdaptiveYield); - private native boolean enableWriteThreadAdaptiveYield(long handle); - private native void setWriteThreadMaxYieldUsec(long handle, - long writeThreadMaxYieldUsec); - private native long writeThreadMaxYieldUsec(long handle); - private native void setWriteThreadSlowYieldUsec(long handle, - long writeThreadSlowYieldUsec); - private native long writeThreadSlowYieldUsec(long handle); - private native void setSkipStatsUpdateOnDbOpen(final long handle, - final boolean skipStatsUpdateOnDbOpen); - private native boolean skipStatsUpdateOnDbOpen(final long handle); - private static native void setSkipCheckingSstFileSizesOnDbOpen( - final long handle, final boolean skipChecking); - private static native boolean skipCheckingSstFileSizesOnDbOpen(final long handle); - private native void setWalRecoveryMode(final long handle, - final byte walRecoveryMode); - private native byte walRecoveryMode(final long handle); - private native void setAllow2pc(final long handle, - final boolean allow2pc); - private native boolean allow2pc(final long handle); - private native void setRowCache(final long handle, - final long rowCacheHandle); - private native void setWalFilter(final long handle, - final long walFilterHandle); - private native void setFailIfOptionsFileError(final long handle, - final boolean failIfOptionsFileError); - private native boolean failIfOptionsFileError(final long handle); - private native void setDumpMallocStats(final long handle, - final boolean dumpMallocStats); - private native boolean dumpMallocStats(final long handle); - private native void setAvoidFlushDuringRecovery(final long handle, - final boolean avoidFlushDuringRecovery); - private native boolean avoidFlushDuringRecovery(final long handle); - private native void setAvoidFlushDuringShutdown(final long handle, - final boolean avoidFlushDuringShutdown); - private native boolean avoidFlushDuringShutdown(final long handle); - private native void setAllowIngestBehind(final long handle, - final boolean allowIngestBehind); - private native boolean allowIngestBehind(final long handle); - private native void setTwoWriteQueues(final long handle, - final boolean twoWriteQueues); - private native boolean twoWriteQueues(final long handle); - private native void setManualWalFlush(final long handle, - final boolean manualWalFlush); - private native boolean manualWalFlush(final long handle); - - - // CF native handles - private static native void oldDefaults( - final long handle, final int majorVersion, final int minorVersion); - private native void optimizeForSmallDb(final long handle); - private static native void optimizeForSmallDb(final long handle, final long cacheHandle); - private native void optimizeForPointLookup(long handle, - long blockCacheSizeMb); - private native void optimizeLevelStyleCompaction(long handle, - long memtableMemoryBudget); - private native void optimizeUniversalStyleCompaction(long handle, - long memtableMemoryBudget); - private native void setComparatorHandle(long handle, int builtinComparator); - private native void setComparatorHandle(long optHandle, - long comparatorHandle, byte comparatorType); - private native void setMergeOperatorName( - long handle, String name); - private native void setMergeOperator( - long handle, long mergeOperatorHandle); - private native void setCompactionFilterHandle( - long handle, long compactionFilterHandle); - private native void setCompactionFilterFactoryHandle( - long handle, long compactionFilterFactoryHandle); - private native void setWriteBufferSize(long handle, long writeBufferSize) - throws IllegalArgumentException; - private native long writeBufferSize(long handle); - private native void setMaxWriteBufferNumber( - long handle, int maxWriteBufferNumber); - private native int maxWriteBufferNumber(long handle); - private native void setMinWriteBufferNumberToMerge( - long handle, int minWriteBufferNumberToMerge); - private native int minWriteBufferNumberToMerge(long handle); - private native void setCompressionType(long handle, byte compressionType); - private native byte compressionType(long handle); - private native void setCompressionPerLevel(long handle, - byte[] compressionLevels); - private native byte[] compressionPerLevel(long handle); - private native void setBottommostCompressionType(long handle, - byte bottommostCompressionType); - private native byte bottommostCompressionType(long handle); - private native void setBottommostCompressionOptions(final long handle, - final long bottommostCompressionOptionsHandle); - private native void setCompressionOptions(long handle, - long compressionOptionsHandle); - private native void useFixedLengthPrefixExtractor( - long handle, int prefixLength); - private native void useCappedPrefixExtractor( - long handle, int prefixLength); - private native void setNumLevels( - long handle, int numLevels); - private native int numLevels(long handle); - private native void setLevelZeroFileNumCompactionTrigger( - long handle, int numFiles); - private native int levelZeroFileNumCompactionTrigger(long handle); - private native void setLevelZeroSlowdownWritesTrigger( - long handle, int numFiles); - private native int levelZeroSlowdownWritesTrigger(long handle); - private native void setLevelZeroStopWritesTrigger( - long handle, int numFiles); - private native int levelZeroStopWritesTrigger(long handle); - private native void setTargetFileSizeBase( - long handle, long targetFileSizeBase); - private native long targetFileSizeBase(long handle); - private native void setTargetFileSizeMultiplier( - long handle, int multiplier); - private native int targetFileSizeMultiplier(long handle); - private native void setMaxBytesForLevelBase( - long handle, long maxBytesForLevelBase); - private native long maxBytesForLevelBase(long handle); - private native void setLevelCompactionDynamicLevelBytes( - long handle, boolean enableLevelCompactionDynamicLevelBytes); - private native boolean levelCompactionDynamicLevelBytes( - long handle); - private native void setMaxBytesForLevelMultiplier(long handle, double multiplier); - private native double maxBytesForLevelMultiplier(long handle); - private native void setMaxCompactionBytes(long handle, long maxCompactionBytes); - private native long maxCompactionBytes(long handle); - private native void setArenaBlockSize( - long handle, long arenaBlockSize) throws IllegalArgumentException; - private native long arenaBlockSize(long handle); - private native void setDisableAutoCompactions( - long handle, boolean disableAutoCompactions); - private native boolean disableAutoCompactions(long handle); - private native void setCompactionStyle(long handle, byte compactionStyle); - private native byte compactionStyle(long handle); - private native void setMaxSequentialSkipInIterations( - long handle, long maxSequentialSkipInIterations); - private native long maxSequentialSkipInIterations(long handle); - private native void setMemTableFactory(long handle, long factoryHandle); - private native String memTableFactoryName(long handle); - private native void setTableFactory(long handle, long factoryHandle); - private native String tableFactoryName(long handle); - private static native void setCfPaths( - final long handle, final String[] paths, final long[] targetSizes); - private static native long cfPathsLen(final long handle); - private static native void cfPaths( - final long handle, final String[] paths, final long[] targetSizes); - private native void setInplaceUpdateSupport( - long handle, boolean inplaceUpdateSupport); - private native boolean inplaceUpdateSupport(long handle); - private native void setInplaceUpdateNumLocks( - long handle, long inplaceUpdateNumLocks) - throws IllegalArgumentException; - private native long inplaceUpdateNumLocks(long handle); - private native void setMemtablePrefixBloomSizeRatio( - long handle, double memtablePrefixBloomSizeRatio); - private native double memtablePrefixBloomSizeRatio(long handle); - private native void setExperimentalMempurgeThreshold( - long handle, double experimentalMempurgeThreshold); - private native double experimentalMempurgeThreshold(long handle); - private native void setMemtableWholeKeyFiltering(long handle, boolean memtableWholeKeyFiltering); - private native boolean memtableWholeKeyFiltering(long handle); - private native void setBloomLocality( - long handle, int bloomLocality); - private native int bloomLocality(long handle); - private native void setMaxSuccessiveMerges( - long handle, long maxSuccessiveMerges) - throws IllegalArgumentException; - private native long maxSuccessiveMerges(long handle); - private native void setOptimizeFiltersForHits(long handle, - boolean optimizeFiltersForHits); - private native boolean optimizeFiltersForHits(long handle); - private native void setMemtableHugePageSize(long handle, - long memtableHugePageSize); - private native long memtableHugePageSize(long handle); - private native void setSoftPendingCompactionBytesLimit(long handle, - long softPendingCompactionBytesLimit); - private native long softPendingCompactionBytesLimit(long handle); - private native void setHardPendingCompactionBytesLimit(long handle, - long hardPendingCompactionBytesLimit); - private native long hardPendingCompactionBytesLimit(long handle); - private native void setLevel0FileNumCompactionTrigger(long handle, - int level0FileNumCompactionTrigger); - private native int level0FileNumCompactionTrigger(long handle); - private native void setLevel0SlowdownWritesTrigger(long handle, - int level0SlowdownWritesTrigger); - private native int level0SlowdownWritesTrigger(long handle); - private native void setLevel0StopWritesTrigger(long handle, - int level0StopWritesTrigger); - private native int level0StopWritesTrigger(long handle); - private native void setMaxBytesForLevelMultiplierAdditional(long handle, - int[] maxBytesForLevelMultiplierAdditional); - private native int[] maxBytesForLevelMultiplierAdditional(long handle); - private native void setParanoidFileChecks(long handle, - boolean paranoidFileChecks); - private native boolean paranoidFileChecks(long handle); - private native void setMaxWriteBufferNumberToMaintain(final long handle, - final int maxWriteBufferNumberToMaintain); - private native int maxWriteBufferNumberToMaintain(final long handle); - private native void setCompactionPriority(final long handle, - final byte compactionPriority); - private native byte compactionPriority(final long handle); - private native void setReportBgIoStats(final long handle, - final boolean reportBgIoStats); - private native boolean reportBgIoStats(final long handle); - private native void setTtl(final long handle, final long ttl); - private native long ttl(final long handle); - private native void setPeriodicCompactionSeconds( - final long handle, final long periodicCompactionSeconds); - private native long periodicCompactionSeconds(final long handle); - private native void setCompactionOptionsUniversal(final long handle, - final long compactionOptionsUniversalHandle); - private native void setCompactionOptionsFIFO(final long handle, - final long compactionOptionsFIFOHandle); - private native void setForceConsistencyChecks(final long handle, - final boolean forceConsistencyChecks); - private native boolean forceConsistencyChecks(final long handle); - private native void setAtomicFlush(final long handle, - final boolean atomicFlush); - private native boolean atomicFlush(final long handle); - private native void setSstPartitionerFactory(long nativeHandle_, long newFactoryHandle); - private static native void setCompactionThreadLimiter( - final long nativeHandle_, final long newLimiterHandle); - private static native void setAvoidUnnecessaryBlockingIO( - final long handle, final boolean avoidBlockingIO); - private static native boolean avoidUnnecessaryBlockingIO(final long handle); - private static native void setPersistStatsToDisk( - final long handle, final boolean persistStatsToDisk); - private static native boolean persistStatsToDisk(final long handle); - private static native void setWriteDbidToManifest( - final long handle, final boolean writeDbidToManifest); - private static native boolean writeDbidToManifest(final long handle); - private static native void setLogReadaheadSize(final long handle, final long logReadaheadSize); - private static native long logReadaheadSize(final long handle); - private static native void setBestEffortsRecovery( - final long handle, final boolean bestEffortsRecovery); - private static native boolean bestEffortsRecovery(final long handle); - private static native void setMaxBgErrorResumeCount( - final long handle, final int maxBgerrorRecumeCount); - private static native int maxBgerrorResumeCount(final long handle); - private static native void setBgerrorResumeRetryInterval( - final long handle, final long bgerrorResumeRetryInterval); - private static native long bgerrorResumeRetryInterval(final long handle); - - private native void setEnableBlobFiles(final long nativeHandle_, final boolean enableBlobFiles); - private native boolean enableBlobFiles(final long nativeHandle_); - private native void setMinBlobSize(final long nativeHandle_, final long minBlobSize); - private native long minBlobSize(final long nativeHandle_); - private native void setBlobFileSize(final long nativeHandle_, final long blobFileSize); - private native long blobFileSize(final long nativeHandle_); - private native void setBlobCompressionType(final long nativeHandle_, final byte compressionType); - private native byte blobCompressionType(final long nativeHandle_); - private native void setEnableBlobGarbageCollection( - final long nativeHandle_, final boolean enableBlobGarbageCollection); - private native boolean enableBlobGarbageCollection(final long nativeHandle_); - private native void setBlobGarbageCollectionAgeCutoff( - final long nativeHandle_, final double blobGarbageCollectionAgeCutoff); - private native double blobGarbageCollectionAgeCutoff(final long nativeHandle_); - private native void setBlobGarbageCollectionForceThreshold( - final long nativeHandle_, final double blobGarbageCollectionForceThreshold); - private native double blobGarbageCollectionForceThreshold(final long nativeHandle_); - private native void setBlobCompactionReadaheadSize( - final long nativeHandle_, final long blobCompactionReadaheadSize); - private native long blobCompactionReadaheadSize(final long nativeHandle_); - private native void setBlobFileStartingLevel( - final long nativeHandle_, final int blobFileStartingLevel); - private native int blobFileStartingLevel(final long nativeHandle_); - private native void setPrepopulateBlobCache( - final long nativeHandle_, final byte prepopulateBlobCache); - private native byte prepopulateBlobCache(final long nativeHandle_); - - // instance variables - // NOTE: If you add new member variables, please update the copy constructor above! - private Env env_; - private MemTableConfig memTableConfig_; - private TableFormatConfig tableFormatConfig_; - private RateLimiter rateLimiter_; - private AbstractComparator comparator_; - private AbstractCompactionFilter> compactionFilter_; - private AbstractCompactionFilterFactory> - compactionFilterFactory_; - private CompactionOptionsUniversal compactionOptionsUniversal_; - private CompactionOptionsFIFO compactionOptionsFIFO_; - private CompressionOptions bottommostCompressionOptions_; - private CompressionOptions compressionOptions_; - private Cache rowCache_; - private WalFilter walFilter_; - private WriteBufferManager writeBufferManager_; - private SstPartitionerFactory sstPartitionerFactory_; - private ConcurrentTaskLimiter compactionThreadLimiter_; -} diff --git a/java/src/main/java/org/rocksdb/OptionsUtil.java b/java/src/main/java/org/rocksdb/OptionsUtil.java deleted file mode 100644 index e21121a2b..000000000 --- a/java/src/main/java/org/rocksdb/OptionsUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -public class OptionsUtil { - /** - * A static method to construct the DBOptions and ColumnFamilyDescriptors by - * loading the latest RocksDB options file stored in the specified rocksdb - * database. - * - * Note that the all the pointer options (except table_factory, which will - * be described in more details below) will be initialized with the default - * values. Developers can further initialize them after this function call. - * Below is an example list of pointer options which will be initialized. - * - * - env - * - memtable_factory - * - compaction_filter_factory - * - prefix_extractor - * - comparator - * - merge_operator - * - compaction_filter - * - * For table_factory, this function further supports deserializing - * BlockBasedTableFactory and its BlockBasedTableOptions except the - * pointer options of BlockBasedTableOptions (flush_block_policy_factory, - * and block_cache), which will be initialized with - * default values. Developers can further specify these three options by - * casting the return value of TableFactoroy::GetOptions() to - * BlockBasedTableOptions and making necessary changes. - * - * @param dbPath the path to the RocksDB. - * @param configOptions {@link org.rocksdb.ConfigOptions} instance. - * @param dbOptions {@link org.rocksdb.DBOptions} instance. This will be - * filled and returned. - * @param cfDescs A list of {@link org.rocksdb.ColumnFamilyDescriptor}'s be - * returned. - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static void loadLatestOptions(ConfigOptions configOptions, String dbPath, - DBOptions dbOptions, List cfDescs) throws RocksDBException { - loadLatestOptions(configOptions.nativeHandle_, dbPath, dbOptions.nativeHandle_, cfDescs); - } - - /** - * Similar to LoadLatestOptions, this function constructs the DBOptions - * and ColumnFamilyDescriptors based on the specified RocksDB Options file. - * See LoadLatestOptions above. - * - * @param optionsFileName the RocksDB options file path. - * @param configOptions {@link org.rocksdb.ConfigOptions} instance. - * @param dbOptions {@link org.rocksdb.DBOptions} instance. This will be - * filled and returned. - * @param cfDescs A list of {@link org.rocksdb.ColumnFamilyDescriptor}'s be - * returned. - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static void loadOptionsFromFile(ConfigOptions configOptions, String optionsFileName, - DBOptions dbOptions, List cfDescs) throws RocksDBException { - loadOptionsFromFile( - configOptions.nativeHandle_, optionsFileName, dbOptions.nativeHandle_, cfDescs); - } - - /** - * Returns the latest options file name under the specified RocksDB path. - * - * @param dbPath the path to the RocksDB. - * @param env {@link org.rocksdb.Env} instance. - * @return the latest options file name under the db path. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static String getLatestOptionsFileName(String dbPath, Env env) throws RocksDBException { - return getLatestOptionsFileName(dbPath, env.nativeHandle_); - } - - /** - * Private constructor. - * This class has only static methods and shouldn't be instantiated. - */ - private OptionsUtil() {} - - // native methods - private native static void loadLatestOptions(long cfgHandle, String dbPath, long dbOptionsHandle, - List cfDescs) throws RocksDBException; - private native static void loadOptionsFromFile(long cfgHandle, String optionsFileName, - long dbOptionsHandle, List cfDescs) throws RocksDBException; - private native static String getLatestOptionsFileName(String dbPath, long envHandle) - throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/PersistentCache.java b/java/src/main/java/org/rocksdb/PersistentCache.java deleted file mode 100644 index aed565297..000000000 --- a/java/src/main/java/org/rocksdb/PersistentCache.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Persistent cache for caching IO pages on a persistent medium. The - * cache is specifically designed for persistent read cache. - */ -public class PersistentCache extends RocksObject { - - public PersistentCache(final Env env, final String path, final long size, - final Logger logger, final boolean optimizedForNvm) - throws RocksDBException { - super(newPersistentCache(env.nativeHandle_, path, size, - logger.nativeHandle_, optimizedForNvm)); - } - - private native static long newPersistentCache(final long envHandle, - final String path, final long size, final long loggerHandle, - final boolean optimizedForNvm) throws RocksDBException; - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/PlainTableConfig.java b/java/src/main/java/org/rocksdb/PlainTableConfig.java deleted file mode 100644 index c09998167..000000000 --- a/java/src/main/java/org/rocksdb/PlainTableConfig.java +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * The config for plain table sst format. - * - *

    PlainTable is a RocksDB's SST file format optimized for low query - * latency on pure-memory or really low-latency media.

    - * - *

    It also support prefix hash feature.

    - */ -public class PlainTableConfig extends TableFormatConfig { - public static final int VARIABLE_LENGTH = 0; - public static final int DEFAULT_BLOOM_BITS_PER_KEY = 10; - public static final double DEFAULT_HASH_TABLE_RATIO = 0.75; - public static final int DEFAULT_INDEX_SPARSENESS = 16; - public static final int DEFAULT_HUGE_TLB_SIZE = 0; - public static final EncodingType DEFAULT_ENCODING_TYPE = - EncodingType.kPlain; - public static final boolean DEFAULT_FULL_SCAN_MODE = false; - public static final boolean DEFAULT_STORE_INDEX_IN_FILE - = false; - - public PlainTableConfig() { - keySize_ = VARIABLE_LENGTH; - bloomBitsPerKey_ = DEFAULT_BLOOM_BITS_PER_KEY; - hashTableRatio_ = DEFAULT_HASH_TABLE_RATIO; - indexSparseness_ = DEFAULT_INDEX_SPARSENESS; - hugePageTlbSize_ = DEFAULT_HUGE_TLB_SIZE; - encodingType_ = DEFAULT_ENCODING_TYPE; - fullScanMode_ = DEFAULT_FULL_SCAN_MODE; - storeIndexInFile_ = DEFAULT_STORE_INDEX_IN_FILE; - } - - /** - *

    Set the length of the user key. If it is set to be - * VARIABLE_LENGTH, then it indicates the user keys are - * of variable length.

    - * - *

    Otherwise,all the keys need to have the same length - * in byte.

    - * - *

    DEFAULT: VARIABLE_LENGTH

    - * - * @param keySize the length of the user key. - * @return the reference to the current config. - */ - public PlainTableConfig setKeySize(int keySize) { - keySize_ = keySize; - return this; - } - - /** - * @return the specified size of the user key. If VARIABLE_LENGTH, - * then it indicates variable-length key. - */ - public int keySize() { - return keySize_; - } - - /** - * Set the number of bits per key used by the internal bloom filter - * in the plain table sst format. - * - * @param bitsPerKey the number of bits per key for bloom filer. - * @return the reference to the current config. - */ - public PlainTableConfig setBloomBitsPerKey(int bitsPerKey) { - bloomBitsPerKey_ = bitsPerKey; - return this; - } - - /** - * @return the number of bits per key used for the bloom filter. - */ - public int bloomBitsPerKey() { - return bloomBitsPerKey_; - } - - /** - * hashTableRatio is the desired utilization of the hash table used - * for prefix hashing. The ideal ratio would be the number of - * prefixes / the number of hash buckets. If this value is set to - * zero, then hash table will not be used. - * - * @param ratio the hash table ratio. - * @return the reference to the current config. - */ - public PlainTableConfig setHashTableRatio(double ratio) { - hashTableRatio_ = ratio; - return this; - } - - /** - * @return the hash table ratio. - */ - public double hashTableRatio() { - return hashTableRatio_; - } - - /** - * Index sparseness determines the index interval for keys inside the - * same prefix. This number is equal to the maximum number of linear - * search required after hash and binary search. If it's set to 0, - * then each key will be indexed. - * - * @param sparseness the index sparseness. - * @return the reference to the current config. - */ - public PlainTableConfig setIndexSparseness(int sparseness) { - indexSparseness_ = sparseness; - return this; - } - - /** - * @return the index sparseness. - */ - public long indexSparseness() { - return indexSparseness_; - } - - /** - *

    huge_page_tlb_size: if ≤0, allocate hash indexes and blooms - * from malloc otherwise from huge page TLB.

    - * - *

    The user needs to reserve huge pages for it to be allocated, - * like: {@code sysctl -w vm.nr_hugepages=20}

    - * - *

    See linux doc Documentation/vm/hugetlbpage.txt

    - * - * @param hugePageTlbSize huge page tlb size - * @return the reference to the current config. - */ - public PlainTableConfig setHugePageTlbSize(int hugePageTlbSize) { - this.hugePageTlbSize_ = hugePageTlbSize; - return this; - } - - /** - * Returns the value for huge page tlb size - * - * @return hugePageTlbSize - */ - public int hugePageTlbSize() { - return hugePageTlbSize_; - } - - /** - * Sets the encoding type. - * - *

    This setting determines how to encode - * the keys. See enum {@link EncodingType} for - * the choices.

    - * - *

    The value will determine how to encode keys - * when writing to a new SST file. This value will be stored - * inside the SST file which will be used when reading from - * the file, which makes it possible for users to choose - * different encoding type when reopening a DB. Files with - * different encoding types can co-exist in the same DB and - * can be read.

    - * - * @param encodingType {@link org.rocksdb.EncodingType} value. - * @return the reference to the current config. - */ - public PlainTableConfig setEncodingType(EncodingType encodingType) { - this.encodingType_ = encodingType; - return this; - } - - /** - * Returns the active EncodingType - * - * @return currently set encoding type - */ - public EncodingType encodingType() { - return encodingType_; - } - - /** - * Set full scan mode, if true the whole file will be read - * one record by one without using the index. - * - * @param fullScanMode boolean value indicating if full - * scan mode shall be enabled. - * @return the reference to the current config. - */ - public PlainTableConfig setFullScanMode(boolean fullScanMode) { - this.fullScanMode_ = fullScanMode; - return this; - } - - /** - * Return if full scan mode is active - * @return boolean value indicating if the full scan mode is - * enabled. - */ - public boolean fullScanMode() { - return fullScanMode_; - } - - /** - *

    If set to true: compute plain table index and bloom - * filter during file building and store it in file. - * When reading file, index will be mmaped instead - * of doing recomputation.

    - * - * @param storeIndexInFile value indicating if index shall - * be stored in a file - * @return the reference to the current config. - */ - public PlainTableConfig setStoreIndexInFile(boolean storeIndexInFile) { - this.storeIndexInFile_ = storeIndexInFile; - return this; - } - - /** - * Return a boolean value indicating if index shall be stored - * in a file. - * - * @return currently set value for store index in file. - */ - public boolean storeIndexInFile() { - return storeIndexInFile_; - } - - @Override protected long newTableFactoryHandle() { - return newTableFactoryHandle(keySize_, bloomBitsPerKey_, - hashTableRatio_, indexSparseness_, hugePageTlbSize_, - encodingType_.getValue(), fullScanMode_, - storeIndexInFile_); - } - - private native long newTableFactoryHandle( - int keySize, int bloomBitsPerKey, - double hashTableRatio, int indexSparseness, - int hugePageTlbSize, byte encodingType, - boolean fullScanMode, boolean storeIndexInFile); - - private int keySize_; - private int bloomBitsPerKey_; - private double hashTableRatio_; - private int indexSparseness_; - private int hugePageTlbSize_; - private EncodingType encodingType_; - private boolean fullScanMode_; - private boolean storeIndexInFile_; -} diff --git a/java/src/main/java/org/rocksdb/PrepopulateBlobCache.java b/java/src/main/java/org/rocksdb/PrepopulateBlobCache.java deleted file mode 100644 index f1237aa7c..000000000 --- a/java/src/main/java/org/rocksdb/PrepopulateBlobCache.java +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Enum PrepopulateBlobCache - * - *

    Prepopulate warm/hot blobs which are already in memory into blob - * cache at the time of flush. On a flush, the blob that is in memory - * (in memtables) get flushed to the device. If using Direct IO, - * additional IO is incurred to read this blob back into memory again, - * which is avoided by enabling this option. This further helps if the - * workload exhibits high temporal locality, where most of the reads go - * to recently written data. This also helps in case of the remote file - * system since it involves network traffic and higher latencies.

    - */ -public enum PrepopulateBlobCache { - PREPOPULATE_BLOB_DISABLE((byte) 0x0, "prepopulate_blob_disable", "kDisable"), - PREPOPULATE_BLOB_FLUSH_ONLY((byte) 0x1, "prepopulate_blob_flush_only", "kFlushOnly"); - - /** - *

    Get the PrepopulateBlobCache enumeration value by - * passing the library name to this method.

    - * - *

    If library cannot be found the enumeration - * value {@code PREPOPULATE_BLOB_DISABLE} will be returned.

    - * - * @param libraryName prepopulate blob cache library name. - * - * @return PrepopulateBlobCache instance. - */ - public static PrepopulateBlobCache getPrepopulateBlobCache(String libraryName) { - if (libraryName != null) { - for (PrepopulateBlobCache prepopulateBlobCache : PrepopulateBlobCache.values()) { - if (prepopulateBlobCache.getLibraryName() != null - && prepopulateBlobCache.getLibraryName().equals(libraryName)) { - return prepopulateBlobCache; - } - } - } - return PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE; - } - - /** - *

    Get the PrepopulateBlobCache enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of PrepopulateBlobCache. - * - * @return PrepopulateBlobCache instance. - * - * @throws IllegalArgumentException If PrepopulateBlobCache cannot be found for the - * provided byteIdentifier - */ - public static PrepopulateBlobCache getPrepopulateBlobCache(byte byteIdentifier) { - for (final PrepopulateBlobCache prepopulateBlobCache : PrepopulateBlobCache.values()) { - if (prepopulateBlobCache.getValue() == byteIdentifier) { - return prepopulateBlobCache; - } - } - - throw new IllegalArgumentException("Illegal value provided for PrepopulateBlobCache."); - } - - /** - *

    Get a PrepopulateBlobCache value based on the string key in the C++ options output. - * This gets used in support of getting options into Java from an options string, - * which is generated at the C++ level. - *

    - * - * @param internalName the internal (C++) name by which the option is known. - * - * @return PrepopulateBlobCache instance (optional) - */ - static PrepopulateBlobCache getFromInternal(final String internalName) { - for (final PrepopulateBlobCache prepopulateBlobCache : PrepopulateBlobCache.values()) { - if (prepopulateBlobCache.internalName_.equals(internalName)) { - return prepopulateBlobCache; - } - } - - throw new IllegalArgumentException( - "Illegal internalName '" + internalName + " ' provided for PrepopulateBlobCache."); - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value_; - } - - /** - *

    Returns the library name of the prepopulate blob cache mode - * identified by the enumeration value.

    - * - * @return library name - */ - public String getLibraryName() { - return libraryName_; - } - - PrepopulateBlobCache(final byte value, final String libraryName, final String internalName) { - value_ = value; - libraryName_ = libraryName; - internalName_ = internalName; - } - - private final byte value_; - private final String libraryName_; - private final String internalName_; -} diff --git a/java/src/main/java/org/rocksdb/Priority.java b/java/src/main/java/org/rocksdb/Priority.java deleted file mode 100644 index 34a56edcb..000000000 --- a/java/src/main/java/org/rocksdb/Priority.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The Thread Pool priority. - */ -public enum Priority { - BOTTOM((byte) 0x0), - LOW((byte) 0x1), - HIGH((byte)0x2), - TOTAL((byte)0x3); - - private final byte value; - - Priority(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - byte getValue() { - return value; - } - - /** - * Get Priority by byte value. - * - * @param value byte representation of Priority. - * - * @return {@link org.rocksdb.Priority} instance. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - static Priority getPriority(final byte value) { - for (final Priority priority : Priority.values()) { - if (priority.getValue() == value){ - return priority; - } - } - throw new IllegalArgumentException("Illegal value provided for Priority."); - } -} diff --git a/java/src/main/java/org/rocksdb/Range.java b/java/src/main/java/org/rocksdb/Range.java deleted file mode 100644 index 74c85e5f0..000000000 --- a/java/src/main/java/org/rocksdb/Range.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Range from start to limit. - */ -public class Range { - final Slice start; - final Slice limit; - - public Range(final Slice start, final Slice limit) { - this.start = start; - this.limit = limit; - } -} diff --git a/java/src/main/java/org/rocksdb/RateLimiter.java b/java/src/main/java/org/rocksdb/RateLimiter.java deleted file mode 100644 index c2b8a0fd9..000000000 --- a/java/src/main/java/org/rocksdb/RateLimiter.java +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2015, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * RateLimiter, which is used to control write rate of flush and - * compaction. - * - * @since 3.10.0 - */ -public class RateLimiter extends RocksObject { - public static final long DEFAULT_REFILL_PERIOD_MICROS = 100 * 1000; - public static final int DEFAULT_FAIRNESS = 10; - public static final RateLimiterMode DEFAULT_MODE = - RateLimiterMode.WRITES_ONLY; - public static final boolean DEFAULT_AUTOTUNE = false; - - /** - * RateLimiter constructor - * - * @param rateBytesPerSecond this is the only parameter you want to set - * most of the time. It controls the total write rate of compaction - * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to - * WAL. - */ - public RateLimiter(final long rateBytesPerSecond) { - this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS, - DEFAULT_MODE, DEFAULT_AUTOTUNE); - } - - /** - * RateLimiter constructor - * - * @param rateBytesPerSecond this is the only parameter you want to set - * most of the time. It controls the total write rate of compaction - * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to - * WAL. - * @param refillPeriodMicros this controls how often tokens are refilled. For - * example, - * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to - * 100ms, then 1MB is refilled every 100ms internally. Larger value can - * lead to burstier writes while smaller value introduces more CPU - * overhead. The default of 100,000ms should work for most cases. - */ - public RateLimiter(final long rateBytesPerSecond, - final long refillPeriodMicros) { - this(rateBytesPerSecond, refillPeriodMicros, DEFAULT_FAIRNESS, DEFAULT_MODE, - DEFAULT_AUTOTUNE); - } - - /** - * RateLimiter constructor - * - * @param rateBytesPerSecond this is the only parameter you want to set - * most of the time. It controls the total write rate of compaction - * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to - * WAL. - * @param refillPeriodMicros this controls how often tokens are refilled. For - * example, - * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to - * 100ms, then 1MB is refilled every 100ms internally. Larger value can - * lead to burstier writes while smaller value introduces more CPU - * overhead. The default of 100,000ms should work for most cases. - * @param fairness RateLimiter accepts high-pri requests and low-pri requests. - * A low-pri request is usually blocked in favor of hi-pri request. - * Currently, RocksDB assigns low-pri to request from compaction and - * high-pri to request from flush. Low-pri requests can get blocked if - * flush requests come in continuously. This fairness parameter grants - * low-pri requests permission by fairness chance even though high-pri - * requests exist to avoid starvation. - * You should be good by leaving it at default 10. - */ - public RateLimiter(final long rateBytesPerSecond, - final long refillPeriodMicros, final int fairness) { - this(rateBytesPerSecond, refillPeriodMicros, fairness, DEFAULT_MODE, - DEFAULT_AUTOTUNE); - } - - /** - * RateLimiter constructor - * - * @param rateBytesPerSecond this is the only parameter you want to set - * most of the time. It controls the total write rate of compaction - * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to - * WAL. - * @param refillPeriodMicros this controls how often tokens are refilled. For - * example, - * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to - * 100ms, then 1MB is refilled every 100ms internally. Larger value can - * lead to burstier writes while smaller value introduces more CPU - * overhead. The default of 100,000ms should work for most cases. - * @param fairness RateLimiter accepts high-pri requests and low-pri requests. - * A low-pri request is usually blocked in favor of hi-pri request. - * Currently, RocksDB assigns low-pri to request from compaction and - * high-pri to request from flush. Low-pri requests can get blocked if - * flush requests come in continuously. This fairness parameter grants - * low-pri requests permission by fairness chance even though high-pri - * requests exist to avoid starvation. - * You should be good by leaving it at default 10. - * @param rateLimiterMode indicates which types of operations count against - * the limit. - */ - public RateLimiter(final long rateBytesPerSecond, - final long refillPeriodMicros, final int fairness, - final RateLimiterMode rateLimiterMode) { - this(rateBytesPerSecond, refillPeriodMicros, fairness, rateLimiterMode, - DEFAULT_AUTOTUNE); - } - - /** - * RateLimiter constructor - * - * @param rateBytesPerSecond this is the only parameter you want to set - * most of the time. It controls the total write rate of compaction - * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to - * WAL. - * @param refillPeriodMicros this controls how often tokens are refilled. For - * example, - * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to - * 100ms, then 1MB is refilled every 100ms internally. Larger value can - * lead to burstier writes while smaller value introduces more CPU - * overhead. The default of 100,000ms should work for most cases. - * @param fairness RateLimiter accepts high-pri requests and low-pri requests. - * A low-pri request is usually blocked in favor of hi-pri request. - * Currently, RocksDB assigns low-pri to request from compaction and - * high-pri to request from flush. Low-pri requests can get blocked if - * flush requests come in continuously. This fairness parameter grants - * low-pri requests permission by fairness chance even though high-pri - * requests exist to avoid starvation. - * You should be good by leaving it at default 10. - * @param rateLimiterMode indicates which types of operations count against - * the limit. - * @param autoTune Enables dynamic adjustment of rate limit within the range - * {@code [rate_bytes_per_sec / 20, rate_bytes_per_sec]}, according to - * the recent demand for background I/O. - */ - public RateLimiter(final long rateBytesPerSecond, - final long refillPeriodMicros, final int fairness, - final RateLimiterMode rateLimiterMode, final boolean autoTune) { - super(newRateLimiterHandle(rateBytesPerSecond, - refillPeriodMicros, fairness, rateLimiterMode.getValue(), autoTune)); - } - - /** - *

    This API allows user to dynamically change rate limiter's bytes per second. - * REQUIRED: bytes_per_second > 0

    - * - * @param bytesPerSecond bytes per second. - */ - public void setBytesPerSecond(final long bytesPerSecond) { - assert(isOwningHandle()); - setBytesPerSecond(nativeHandle_, bytesPerSecond); - } - - /** - * Returns the bytes per second. - * - * @return bytes per second. - */ - public long getBytesPerSecond() { - assert(isOwningHandle()); - return getBytesPerSecond(nativeHandle_); - } - - /** - *

    Request for token to write bytes. If this request can not be satisfied, - * the call is blocked. Caller is responsible to make sure - * {@code bytes < GetSingleBurstBytes()}.

    - * - * @param bytes requested bytes. - */ - public void request(final long bytes) { - assert(isOwningHandle()); - request(nativeHandle_, bytes); - } - - /** - *

    Max bytes can be granted in a single burst.

    - * - * @return max bytes can be granted in a single burst. - */ - public long getSingleBurstBytes() { - assert(isOwningHandle()); - return getSingleBurstBytes(nativeHandle_); - } - - /** - *

    Total bytes that go through rate limiter.

    - * - * @return total bytes that go through rate limiter. - */ - public long getTotalBytesThrough() { - assert(isOwningHandle()); - return getTotalBytesThrough(nativeHandle_); - } - - /** - *

    Total # of requests that go through rate limiter.

    - * - * @return total # of requests that go through rate limiter. - */ - public long getTotalRequests() { - assert(isOwningHandle()); - return getTotalRequests(nativeHandle_); - } - - private static native long newRateLimiterHandle(final long rateBytesPerSecond, - final long refillPeriodMicros, final int fairness, - final byte rateLimiterMode, final boolean autoTune); - @Override protected final native void disposeInternal(final long handle); - - private native void setBytesPerSecond(final long handle, - final long bytesPerSecond); - private native long getBytesPerSecond(final long handle); - private native void request(final long handle, final long bytes); - private native long getSingleBurstBytes(final long handle); - private native long getTotalBytesThrough(final long handle); - private native long getTotalRequests(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/RateLimiterMode.java b/java/src/main/java/org/rocksdb/RateLimiterMode.java deleted file mode 100644 index 4b029d816..000000000 --- a/java/src/main/java/org/rocksdb/RateLimiterMode.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Mode for {@link RateLimiter#RateLimiter(long, long, int, RateLimiterMode)}. - */ -public enum RateLimiterMode { - READS_ONLY((byte)0x0), - WRITES_ONLY((byte)0x1), - ALL_IO((byte)0x2); - - private final byte value; - - RateLimiterMode(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - *

    Get the RateLimiterMode enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of RateLimiterMode. - * - * @return AccessHint instance. - * - * @throws IllegalArgumentException if the access hint for the byteIdentifier - * cannot be found - */ - public static RateLimiterMode getRateLimiterMode(final byte byteIdentifier) { - for (final RateLimiterMode rateLimiterMode : RateLimiterMode.values()) { - if (rateLimiterMode.getValue() == byteIdentifier) { - return rateLimiterMode; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for RateLimiterMode."); - } -} diff --git a/java/src/main/java/org/rocksdb/ReadOptions.java b/java/src/main/java/org/rocksdb/ReadOptions.java deleted file mode 100755 index c638b17b7..000000000 --- a/java/src/main/java/org/rocksdb/ReadOptions.java +++ /dev/null @@ -1,823 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The class that controls the get behavior. - * - * Note that dispose() must be called before an Options instance - * become out-of-scope to release the allocated memory in c++. - */ -public class ReadOptions extends RocksObject { - public ReadOptions() { - super(newReadOptions()); - } - - /** - * @param verifyChecksums verification will be performed on every read - * when set to true - * @param fillCache if true, then fill-cache behavior will be performed. - */ - public ReadOptions(final boolean verifyChecksums, final boolean fillCache) { - super(newReadOptions(verifyChecksums, fillCache)); - } - - /** - * Copy constructor. - * - * NOTE: This does a shallow copy, which means snapshot, iterate_upper_bound - * and other pointers will be cloned! - * - * @param other The ReadOptions to copy. - */ - public ReadOptions(ReadOptions other) { - super(copyReadOptions(other.nativeHandle_)); - this.iterateLowerBoundSlice_ = other.iterateLowerBoundSlice_; - this.iterateUpperBoundSlice_ = other.iterateUpperBoundSlice_; - this.timestampSlice_ = other.timestampSlice_; - this.iterStartTs_ = other.iterStartTs_; - } - - /** - * If true, all data read from underlying storage will be - * verified against corresponding checksums. - * Default: true - * - * @return true if checksum verification is on. - */ - public boolean verifyChecksums() { - assert(isOwningHandle()); - return verifyChecksums(nativeHandle_); - } - - /** - * If true, all data read from underlying storage will be - * verified against corresponding checksums. - * Default: true - * - * @param verifyChecksums if true, then checksum verification - * will be performed on every read. - * @return the reference to the current ReadOptions. - */ - public ReadOptions setVerifyChecksums( - final boolean verifyChecksums) { - assert(isOwningHandle()); - setVerifyChecksums(nativeHandle_, verifyChecksums); - return this; - } - - // TODO(yhchiang): this option seems to be block-based table only. - // move this to a better place? - /** - * Fill the cache when loading the block-based sst formated db. - * Callers may wish to set this field to false for bulk scans. - * Default: true - * - * @return true if the fill-cache behavior is on. - */ - public boolean fillCache() { - assert(isOwningHandle()); - return fillCache(nativeHandle_); - } - - /** - * Fill the cache when loading the block-based sst formatted db. - * Callers may wish to set this field to false for bulk scans. - * Default: true - * - * @param fillCache if true, then fill-cache behavior will be - * performed. - * @return the reference to the current ReadOptions. - */ - public ReadOptions setFillCache(final boolean fillCache) { - assert(isOwningHandle()); - setFillCache(nativeHandle_, fillCache); - return this; - } - - /** - * Returns the currently assigned Snapshot instance. - * - * @return the Snapshot assigned to this instance. If no Snapshot - * is assigned null. - */ - public Snapshot snapshot() { - assert(isOwningHandle()); - long snapshotHandle = snapshot(nativeHandle_); - if (snapshotHandle != 0) { - return new Snapshot(snapshotHandle); - } - return null; - } - - /** - *

    If "snapshot" is non-nullptr, read as of the supplied snapshot - * (which must belong to the DB that is being read and which must - * not have been released). If "snapshot" is nullptr, use an implicit - * snapshot of the state at the beginning of this read operation.

    - *

    Default: null

    - * - * @param snapshot {@link Snapshot} instance - * @return the reference to the current ReadOptions. - */ - public ReadOptions setSnapshot(final Snapshot snapshot) { - assert(isOwningHandle()); - if (snapshot != null) { - setSnapshot(nativeHandle_, snapshot.nativeHandle_); - } else { - setSnapshot(nativeHandle_, 0l); - } - return this; - } - - /** - * Returns the current read tier. - * - * @return the read tier in use, by default {@link ReadTier#READ_ALL_TIER} - */ - public ReadTier readTier() { - assert(isOwningHandle()); - return ReadTier.getReadTier(readTier(nativeHandle_)); - } - - /** - * Specify if this read request should process data that ALREADY - * resides on a particular cache. If the required data is not - * found at the specified cache, then {@link RocksDBException} is thrown. - * - * @param readTier {@link ReadTier} instance - * @return the reference to the current ReadOptions. - */ - public ReadOptions setReadTier(final ReadTier readTier) { - assert(isOwningHandle()); - setReadTier(nativeHandle_, readTier.getValue()); - return this; - } - - /** - * Specify to create a tailing iterator -- a special iterator that has a - * view of the complete database (i.e. it can also be used to read newly - * added data) and is optimized for sequential reads. It will return records - * that were inserted into the database after the creation of the iterator. - * Default: false - * @return true if tailing iterator is enabled. - */ - public boolean tailing() { - assert(isOwningHandle()); - return tailing(nativeHandle_); - } - - /** - * Specify to create a tailing iterator -- a special iterator that has a - * view of the complete database (i.e. it can also be used to read newly - * added data) and is optimized for sequential reads. It will return records - * that were inserted into the database after the creation of the iterator. - * Default: false - * - * @param tailing if true, then tailing iterator will be enabled. - * @return the reference to the current ReadOptions. - */ - public ReadOptions setTailing(final boolean tailing) { - assert(isOwningHandle()); - setTailing(nativeHandle_, tailing); - return this; - } - - /** - * Returns whether managed iterators will be used. - * - * @return the setting of whether managed iterators will be used, - * by default false - * - * @deprecated This options is not used anymore. - */ - @Deprecated - public boolean managed() { - assert(isOwningHandle()); - return managed(nativeHandle_); - } - - /** - * Specify to create a managed iterator -- a special iterator that - * uses less resources by having the ability to free its underlying - * resources on request. - * - * @param managed if true, then managed iterators will be enabled. - * @return the reference to the current ReadOptions. - * - * @deprecated This options is not used anymore. - */ - @Deprecated - public ReadOptions setManaged(final boolean managed) { - assert(isOwningHandle()); - setManaged(nativeHandle_, managed); - return this; - } - - /** - * Returns whether a total seek order will be used - * - * @return the setting of whether a total seek order will be used - */ - public boolean totalOrderSeek() { - assert(isOwningHandle()); - return totalOrderSeek(nativeHandle_); - } - - /** - * Enable a total order seek regardless of index format (e.g. hash index) - * used in the table. Some table format (e.g. plain table) may not support - * this option. - * - * @param totalOrderSeek if true, then total order seek will be enabled. - * @return the reference to the current ReadOptions. - */ - public ReadOptions setTotalOrderSeek(final boolean totalOrderSeek) { - assert(isOwningHandle()); - setTotalOrderSeek(nativeHandle_, totalOrderSeek); - return this; - } - - /** - * Returns whether the iterator only iterates over the same prefix as the seek - * - * @return the setting of whether the iterator only iterates over the same - * prefix as the seek, default is false - */ - public boolean prefixSameAsStart() { - assert(isOwningHandle()); - return prefixSameAsStart(nativeHandle_); - } - - /** - * Enforce that the iterator only iterates over the same prefix as the seek. - * This option is effective only for prefix seeks, i.e. prefix_extractor is - * non-null for the column family and {@link #totalOrderSeek()} is false. - * Unlike iterate_upper_bound, {@link #setPrefixSameAsStart(boolean)} only - * works within a prefix but in both directions. - * - * @param prefixSameAsStart if true, then the iterator only iterates over the - * same prefix as the seek - * @return the reference to the current ReadOptions. - */ - public ReadOptions setPrefixSameAsStart(final boolean prefixSameAsStart) { - assert(isOwningHandle()); - setPrefixSameAsStart(nativeHandle_, prefixSameAsStart); - return this; - } - - /** - * Returns whether the blocks loaded by the iterator will be pinned in memory - * - * @return the setting of whether the blocks loaded by the iterator will be - * pinned in memory - */ - public boolean pinData() { - assert(isOwningHandle()); - return pinData(nativeHandle_); - } - - /** - * Keep the blocks loaded by the iterator pinned in memory as long as the - * iterator is not deleted, If used when reading from tables created with - * BlockBasedTableOptions::use_delta_encoding = false, - * Iterator's property "rocksdb.iterator.is-key-pinned" is guaranteed to - * return 1. - * - * @param pinData if true, the blocks loaded by the iterator will be pinned - * @return the reference to the current ReadOptions. - */ - public ReadOptions setPinData(final boolean pinData) { - assert(isOwningHandle()); - setPinData(nativeHandle_, pinData); - return this; - } - - /** - * If true, when PurgeObsoleteFile is called in CleanupIteratorState, we - * schedule a background job in the flush job queue and delete obsolete files - * in background. - * - * Default: false - * - * @return true when PurgeObsoleteFile is called in CleanupIteratorState - */ - public boolean backgroundPurgeOnIteratorCleanup() { - assert(isOwningHandle()); - return backgroundPurgeOnIteratorCleanup(nativeHandle_); - } - - /** - * If true, when PurgeObsoleteFile is called in CleanupIteratorState, we - * schedule a background job in the flush job queue and delete obsolete files - * in background. - * - * Default: false - * - * @param backgroundPurgeOnIteratorCleanup true when PurgeObsoleteFile is - * called in CleanupIteratorState - * @return the reference to the current ReadOptions. - */ - public ReadOptions setBackgroundPurgeOnIteratorCleanup( - final boolean backgroundPurgeOnIteratorCleanup) { - assert(isOwningHandle()); - setBackgroundPurgeOnIteratorCleanup(nativeHandle_, - backgroundPurgeOnIteratorCleanup); - return this; - } - - /** - * If non-zero, NewIterator will create a new table reader which - * performs reads of the given size. Using a large size (> 2MB) can - * improve the performance of forward iteration on spinning disks. - * - * Default: 0 - * - * @return The readahead size is bytes - */ - public long readaheadSize() { - assert(isOwningHandle()); - return readaheadSize(nativeHandle_); - } - - /** - * If non-zero, NewIterator will create a new table reader which - * performs reads of the given size. Using a large size (> 2MB) can - * improve the performance of forward iteration on spinning disks. - * - * Default: 0 - * - * @param readaheadSize The readahead size is bytes - * @return the reference to the current ReadOptions. - */ - public ReadOptions setReadaheadSize(final long readaheadSize) { - assert(isOwningHandle()); - setReadaheadSize(nativeHandle_, readaheadSize); - return this; - } - - /** - * A threshold for the number of keys that can be skipped before failing an - * iterator seek as incomplete. - * - * @return the number of keys that can be skipped - * before failing an iterator seek as incomplete. - */ - public long maxSkippableInternalKeys() { - assert(isOwningHandle()); - return maxSkippableInternalKeys(nativeHandle_); - } - - /** - * A threshold for the number of keys that can be skipped before failing an - * iterator seek as incomplete. The default value of 0 should be used to - * never fail a request as incomplete, even on skipping too many keys. - * - * Default: 0 - * - * @param maxSkippableInternalKeys the number of keys that can be skipped - * before failing an iterator seek as incomplete. - * - * @return the reference to the current ReadOptions. - */ - public ReadOptions setMaxSkippableInternalKeys( - final long maxSkippableInternalKeys) { - assert(isOwningHandle()); - setMaxSkippableInternalKeys(nativeHandle_, maxSkippableInternalKeys); - return this; - } - - /** - * If true, keys deleted using the DeleteRange() API will be visible to - * readers until they are naturally deleted during compaction. This improves - * read performance in DBs with many range deletions. - * - * Default: false - * - * @return true if keys deleted using the DeleteRange() API will be visible - */ - public boolean ignoreRangeDeletions() { - assert(isOwningHandle()); - return ignoreRangeDeletions(nativeHandle_); - } - - /** - * If true, keys deleted using the DeleteRange() API will be visible to - * readers until they are naturally deleted during compaction. This improves - * read performance in DBs with many range deletions. - * - * Default: false - * - * @param ignoreRangeDeletions true if keys deleted using the DeleteRange() - * API should be visible - * @return the reference to the current ReadOptions. - */ - public ReadOptions setIgnoreRangeDeletions(final boolean ignoreRangeDeletions) { - assert(isOwningHandle()); - setIgnoreRangeDeletions(nativeHandle_, ignoreRangeDeletions); - return this; - } - - /** - * Defines the smallest key at which the backward - * iterator can return an entry. Once the bound is passed, - * {@link RocksIterator#isValid()} will be false. - * - * The lower bound is inclusive i.e. the bound value is a valid - * entry. - * - * If prefix_extractor is not null, the Seek target and `iterate_lower_bound` - * need to have the same prefix. This is because ordering is not guaranteed - * outside of prefix domain. - * - * Default: null - * - * @param iterateLowerBound Slice representing the lower bound - * @return the reference to the current ReadOptions. - */ - public ReadOptions setIterateLowerBound(final AbstractSlice iterateLowerBound) { - assert(isOwningHandle()); - setIterateLowerBound( - nativeHandle_, iterateLowerBound == null ? 0 : iterateLowerBound.getNativeHandle()); - // Hold onto a reference so it doesn't get garbage collected out from under us. - iterateLowerBoundSlice_ = iterateLowerBound; - return this; - } - - /** - * Returns the smallest key at which the backward - * iterator can return an entry. - * - * The lower bound is inclusive i.e. the bound value is a valid entry. - * - * @return the smallest key, or null if there is no lower bound defined. - */ - public Slice iterateLowerBound() { - assert(isOwningHandle()); - final long lowerBoundSliceHandle = iterateLowerBound(nativeHandle_); - if (lowerBoundSliceHandle != 0) { - // Disown the new slice - it's owned by the C++ side of the JNI boundary - // from the perspective of this method. - return new Slice(lowerBoundSliceHandle, false); - } - return null; - } - - /** - * Defines the extent up to which the forward iterator - * can returns entries. Once the bound is reached, - * {@link RocksIterator#isValid()} will be false. - * - * The upper bound is exclusive i.e. the bound value is not a valid entry. - * - * If prefix_extractor is not null, the Seek target and iterate_upper_bound - * need to have the same prefix. This is because ordering is not guaranteed - * outside of prefix domain. - * - * Default: null - * - * @param iterateUpperBound Slice representing the upper bound - * @return the reference to the current ReadOptions. - */ - public ReadOptions setIterateUpperBound(final AbstractSlice iterateUpperBound) { - assert(isOwningHandle()); - setIterateUpperBound( - nativeHandle_, iterateUpperBound == null ? 0 : iterateUpperBound.getNativeHandle()); - // Hold onto a reference so it doesn't get garbage collected out from under us. - iterateUpperBoundSlice_ = iterateUpperBound; - return this; - } - - /** - * Returns the largest key at which the forward - * iterator can return an entry. - * - * The upper bound is exclusive i.e. the bound value is not a valid entry. - * - * @return the largest key, or null if there is no upper bound defined. - */ - public Slice iterateUpperBound() { - assert(isOwningHandle()); - final long upperBoundSliceHandle = iterateUpperBound(nativeHandle_); - if (upperBoundSliceHandle != 0) { - // Disown the new slice - it's owned by the C++ side of the JNI boundary - // from the perspective of this method. - return new Slice(upperBoundSliceHandle, false); - } - return null; - } - - /** - * A callback to determine whether relevant keys for this scan exist in a - * given table based on the table's properties. The callback is passed the - * properties of each table during iteration. If the callback returns false, - * the table will not be scanned. This option only affects Iterators and has - * no impact on point lookups. - * - * Default: null (every table will be scanned) - * - * @param tableFilter the table filter for the callback. - * - * @return the reference to the current ReadOptions. - */ - public ReadOptions setTableFilter(final AbstractTableFilter tableFilter) { - assert(isOwningHandle()); - setTableFilter(nativeHandle_, tableFilter.nativeHandle_); - return this; - } - - /** - * When true, by default use total_order_seek = true, and RocksDB can - * selectively enable prefix seek mode if won't generate a different result - * from total_order_seek, based on seek key, and iterator upper bound. - * Default: false - * - * @return true if auto prefix mode is set. - * - */ - public boolean autoPrefixMode() { - assert (isOwningHandle()); - return autoPrefixMode(nativeHandle_); - } - - /** - * When true, by default use total_order_seek = true, and RocksDB can - * selectively enable prefix seek mode if won't generate a different result - * from total_order_seek, based on seek key, and iterator upper bound. - * Default: false - * @param mode auto prefix mode - * @return the reference to the current ReadOptions. - */ - public ReadOptions setAutoPrefixMode(final boolean mode) { - assert (isOwningHandle()); - setAutoPrefixMode(nativeHandle_, mode); - return this; - } - - /** - * Timestamp of operation. Read should return the latest data visible to the - * specified timestamp. All timestamps of the same database must be of the - * same length and format. The user is responsible for providing a customized - * compare function via Comparator to order >key, timestamp> tuples. - * For iterator, iter_start_ts is the lower bound (older) and timestamp - * serves as the upper bound. Versions of the same record that fall in - * the timestamp range will be returned. If iter_start_ts is nullptr, - * only the most recent version visible to timestamp is returned. - * The user-specified timestamp feature is still under active development, - * and the API is subject to change. - * - * Default: null - * @see #iterStartTs() - * @return Reference to timestamp or null if there is no timestamp defined. - */ - public Slice timestamp() { - assert (isOwningHandle()); - final long timestampSliceHandle = timestamp(nativeHandle_); - if (timestampSliceHandle != 0) { - return new Slice(timestampSliceHandle); - } else { - return null; - } - } - - /** - * Timestamp of operation. Read should return the latest data visible to the - * specified timestamp. All timestamps of the same database must be of the - * same length and format. The user is responsible for providing a customized - * compare function via Comparator to order {@code } tuples. - * For iterator, {@code iter_start_ts} is the lower bound (older) and timestamp - * serves as the upper bound. Versions of the same record that fall in - * the timestamp range will be returned. If iter_start_ts is nullptr, - * only the most recent version visible to timestamp is returned. - * The user-specified timestamp feature is still under active development, - * and the API is subject to change. - * - * Default: null - * @see #setIterStartTs(AbstractSlice) - * @param timestamp Slice representing the timestamp - * @return the reference to the current ReadOptions. - */ - public ReadOptions setTimestamp(final AbstractSlice timestamp) { - assert (isOwningHandle()); - setTimestamp(nativeHandle_, timestamp == null ? 0 : timestamp.getNativeHandle()); - timestampSlice_ = timestamp; - return this; - } - - /** - * Timestamp of operation. Read should return the latest data visible to the - * specified timestamp. All timestamps of the same database must be of the - * same length and format. The user is responsible for providing a customized - * compare function via Comparator to order {@code } tuples. - * For iterator, {@code iter_start_ts} is the lower bound (older) and timestamp - * serves as the upper bound. Versions of the same record that fall in - * the timestamp range will be returned. If iter_start_ts is nullptr, - * only the most recent version visible to timestamp is returned. - * The user-specified timestamp feature is still under active development, - * and the API is subject to change. - * - * Default: null - * @return Reference to lower bound timestamp or null if there is no lower bound timestamp - * defined. - */ - public Slice iterStartTs() { - assert (isOwningHandle()); - final long iterStartTsHandle = iterStartTs(nativeHandle_); - if (iterStartTsHandle != 0) { - return new Slice(iterStartTsHandle); - } else { - return null; - } - } - - /** - * Timestamp of operation. Read should return the latest data visible to the - * specified timestamp. All timestamps of the same database must be of the - * same length and format. The user is responsible for providing a customized - * compare function via Comparator to order {@code } tuples. - * For iterator, {@code iter_start_ts} is the lower bound (older) and timestamp - * serves as the upper bound. Versions of the same record that fall in - * the timestamp range will be returned. If iter_start_ts is nullptr, - * only the most recent version visible to timestamp is returned. - * The user-specified timestamp feature is still under active development, - * and the API is subject to change. - * - * Default: null - * - * @param iterStartTs Reference to lower bound timestamp or null if there is no lower bound - * timestamp defined - * @return the reference to the current ReadOptions. - */ - public ReadOptions setIterStartTs(final AbstractSlice iterStartTs) { - assert (isOwningHandle()); - setIterStartTs(nativeHandle_, iterStartTs == null ? 0 : iterStartTs.getNativeHandle()); - iterStartTs_ = iterStartTs; - return this; - } - - /** - * Deadline for completing an API call (Get/MultiGet/Seek/Next for now) - * in microseconds. - * It should be set to microseconds since epoch, i.e, {@code gettimeofday} or - * equivalent plus allowed duration in microseconds. The best way is to use - * {@code env->NowMicros() + some timeout}. - * This is best efforts. The call may exceed the deadline if there is IO - * involved and the file system doesn't support deadlines, or due to - * checking for deadline periodically rather than for every key if - * processing a batch - * - * @return deadline time in microseconds - */ - public long deadline() { - assert (isOwningHandle()); - return deadline(nativeHandle_); - } - - /** - * Deadline for completing an API call (Get/MultiGet/Seek/Next for now) - * in microseconds. - * It should be set to microseconds since epoch, i.e, {@code gettimeofday} or - * equivalent plus allowed duration in microseconds. The best way is to use - * {@code env->NowMicros() + some timeout}. - * This is best efforts. The call may exceed the deadline if there is IO - * involved and the file system doesn't support deadlines, or due to - * checking for deadline periodically rather than for every key if - * processing a batch - * - * @param deadlineTime deadline time in microseconds. - * @return the reference to the current ReadOptions. - */ - public ReadOptions setDeadline(final long deadlineTime) { - assert (isOwningHandle()); - setDeadline(nativeHandle_, deadlineTime); - return this; - } - - /** - * A timeout in microseconds to be passed to the underlying FileSystem for - * reads. As opposed to deadline, this determines the timeout for each - * individual file read request. If a MultiGet/Get/Seek/Next etc call - * results in multiple reads, each read can last up to io_timeout us. - * @return ioTimeout time in microseconds - */ - public long ioTimeout() { - assert (isOwningHandle()); - return ioTimeout(nativeHandle_); - } - - /** - * A timeout in microseconds to be passed to the underlying FileSystem for - * reads. As opposed to deadline, this determines the timeout for each - * individual file read request. If a MultiGet/Get/Seek/Next etc call - * results in multiple reads, each read can last up to io_timeout us. - * - * @param ioTimeout time in microseconds. - * @return the reference to the current ReadOptions. - */ - public ReadOptions setIoTimeout(final long ioTimeout) { - assert (isOwningHandle()); - setIoTimeout(nativeHandle_, ioTimeout); - return this; - } - - /** - * It limits the maximum cumulative value size of the keys in batch while - * reading through MultiGet. Once the cumulative value size exceeds this - * soft limit then all the remaining keys are returned with status Aborted. - * - * Default: {@code std::numeric_limits::max()} - * @return actual valueSizeSofLimit - */ - public long valueSizeSoftLimit() { - assert (isOwningHandle()); - return valueSizeSoftLimit(nativeHandle_); - } - - /** - * It limits the maximum cumulative value size of the keys in batch while - * reading through MultiGet. Once the cumulative value size exceeds this - * soft limit then all the remaining keys are returned with status Aborted. - * - * Default: {@code std::numeric_limits::max()} - * - * @param valueSizeSoftLimit the maximum cumulative value size of the keys - * @return the reference to the current ReadOptions - */ - public ReadOptions setValueSizeSoftLimit(final long valueSizeSoftLimit) { - assert (isOwningHandle()); - setValueSizeSoftLimit(nativeHandle_, valueSizeSoftLimit); - return this; - } - - // instance variables - // NOTE: If you add new member variables, please update the copy constructor above! - // - // Hold a reference to any iterate lower or upper bound that was set on this - // object until we're destroyed or it's overwritten. That way the caller can - // freely leave scope without us losing the Java Slice object, which during - // close() would also reap its associated rocksdb::Slice native object since - // it's possibly (likely) to be an owning handle. - private AbstractSlice iterateLowerBoundSlice_; - private AbstractSlice iterateUpperBoundSlice_; - private AbstractSlice timestampSlice_; - private AbstractSlice iterStartTs_; - - private native static long newReadOptions(); - private native static long newReadOptions(final boolean verifyChecksums, - final boolean fillCache); - private native static long copyReadOptions(long handle); - @Override protected final native void disposeInternal(final long handle); - - private native boolean verifyChecksums(long handle); - private native void setVerifyChecksums(long handle, boolean verifyChecksums); - private native boolean fillCache(long handle); - private native void setFillCache(long handle, boolean fillCache); - private native long snapshot(long handle); - private native void setSnapshot(long handle, long snapshotHandle); - private native byte readTier(long handle); - private native void setReadTier(long handle, byte readTierValue); - private native boolean tailing(long handle); - private native void setTailing(long handle, boolean tailing); - private native boolean managed(long handle); - private native void setManaged(long handle, boolean managed); - private native boolean totalOrderSeek(long handle); - private native void setTotalOrderSeek(long handle, boolean totalOrderSeek); - private native boolean prefixSameAsStart(long handle); - private native void setPrefixSameAsStart(long handle, boolean prefixSameAsStart); - private native boolean pinData(long handle); - private native void setPinData(long handle, boolean pinData); - private native boolean backgroundPurgeOnIteratorCleanup(final long handle); - private native void setBackgroundPurgeOnIteratorCleanup(final long handle, - final boolean backgroundPurgeOnIteratorCleanup); - private native long readaheadSize(final long handle); - private native void setReadaheadSize(final long handle, - final long readaheadSize); - private native long maxSkippableInternalKeys(final long handle); - private native void setMaxSkippableInternalKeys(final long handle, - final long maxSkippableInternalKeys); - private native boolean ignoreRangeDeletions(final long handle); - private native void setIgnoreRangeDeletions(final long handle, - final boolean ignoreRangeDeletions); - private native void setIterateUpperBound(final long handle, - final long upperBoundSliceHandle); - private native long iterateUpperBound(final long handle); - private native void setIterateLowerBound(final long handle, - final long lowerBoundSliceHandle); - private native long iterateLowerBound(final long handle); - private native void setTableFilter(final long handle, final long tableFilterHandle); - private native boolean autoPrefixMode(final long handle); - private native void setAutoPrefixMode(final long handle, final boolean autoPrefixMode); - private native long timestamp(final long handle); - private native void setTimestamp(final long handle, final long timestampSliceHandle); - private native long iterStartTs(final long handle); - private native void setIterStartTs(final long handle, final long iterStartTsHandle); - private native long deadline(final long handle); - private native void setDeadline(final long handle, final long deadlineTime); - private native long ioTimeout(final long handle); - private native void setIoTimeout(final long handle, final long ioTimeout); - private native long valueSizeSoftLimit(final long handle); - private native void setValueSizeSoftLimit(final long handle, final long softLimit); -} diff --git a/java/src/main/java/org/rocksdb/ReadTier.java b/java/src/main/java/org/rocksdb/ReadTier.java deleted file mode 100644 index 78f83f6ad..000000000 --- a/java/src/main/java/org/rocksdb/ReadTier.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * RocksDB {@link ReadOptions} read tiers. - */ -public enum ReadTier { - READ_ALL_TIER((byte)0), - BLOCK_CACHE_TIER((byte)1), - PERSISTED_TIER((byte)2), - MEMTABLE_TIER((byte)3); - - private final byte value; - - ReadTier(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get ReadTier by byte value. - * - * @param value byte representation of ReadTier. - * - * @return {@link org.rocksdb.ReadTier} instance or null. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static ReadTier getReadTier(final byte value) { - for (final ReadTier readTier : ReadTier.values()) { - if (readTier.getValue() == value){ - return readTier; - } - } - throw new IllegalArgumentException("Illegal value provided for ReadTier."); - } -} diff --git a/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java b/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java deleted file mode 100644 index 6ee81d858..000000000 --- a/java/src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Just a Java wrapper around EmptyValueCompactionFilter implemented in C++ - */ -public class RemoveEmptyValueCompactionFilter - extends AbstractCompactionFilter { - public RemoveEmptyValueCompactionFilter() { - super(createNewRemoveEmptyValueCompactionFilter0()); - } - - private native static long createNewRemoveEmptyValueCompactionFilter0(); -} diff --git a/java/src/main/java/org/rocksdb/RestoreOptions.java b/java/src/main/java/org/rocksdb/RestoreOptions.java deleted file mode 100644 index 54dc0e61c..000000000 --- a/java/src/main/java/org/rocksdb/RestoreOptions.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * RestoreOptions to control the behavior of restore. - * - * Note that dispose() must be called before this instance become out-of-scope - * to release the allocated memory in c++. - * - */ -public class RestoreOptions extends RocksObject { - /** - * Constructor - * - * @param keepLogFiles If true, restore won't overwrite the existing log files - * in wal_dir. It will also move all log files from archive directory to - * wal_dir. Use this option in combination with - * BackupEngineOptions::backup_log_files = false for persisting in-memory - * databases. - * Default: false - */ - public RestoreOptions(final boolean keepLogFiles) { - super(newRestoreOptions(keepLogFiles)); - } - - private native static long newRestoreOptions(boolean keepLogFiles); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java b/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java deleted file mode 100644 index 2709a5d59..000000000 --- a/java/src/main/java/org/rocksdb/ReusedSynchronisationType.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * Determines the type of synchronisation primitive used - * in native code. - */ -public enum ReusedSynchronisationType { - /** - * Standard mutex. - */ - MUTEX((byte)0x0), - - /** - * Use adaptive mutex, which spins in the user space before resorting - * to kernel. This could reduce context switch when the mutex is not - * heavily contended. However, if the mutex is hot, we could end up - * wasting spin time. - */ - ADAPTIVE_MUTEX((byte)0x1), - - /** - * There is a reused buffer per-thread. - */ - THREAD_LOCAL((byte)0x2); - - private final byte value; - - ReusedSynchronisationType(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get ReusedSynchronisationType by byte value. - * - * @param value byte representation of ReusedSynchronisationType. - * - * @return {@link org.rocksdb.ReusedSynchronisationType} instance. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static ReusedSynchronisationType getReusedSynchronisationType( - final byte value) { - for (final ReusedSynchronisationType reusedSynchronisationType - : ReusedSynchronisationType.values()) { - if (reusedSynchronisationType.getValue() == value) { - return reusedSynchronisationType; - } - } - throw new IllegalArgumentException( - "Illegal value provided for ReusedSynchronisationType."); - } -} diff --git a/java/src/main/java/org/rocksdb/RocksCallbackObject.java b/java/src/main/java/org/rocksdb/RocksCallbackObject.java deleted file mode 100644 index 8d7a867ee..000000000 --- a/java/src/main/java/org/rocksdb/RocksCallbackObject.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * RocksCallbackObject is similar to {@link RocksObject} but varies - * in its construction as it is designed for Java objects which have functions - * which are called from C++ via JNI. - * - * RocksCallbackObject is the base-class any RocksDB classes that acts as a - * callback from some underlying underlying native C++ {@code rocksdb} object. - * - * The use of {@code RocksObject} should always be preferred over - * {@link RocksCallbackObject} if callbacks are not required. - */ -public abstract class RocksCallbackObject extends - AbstractImmutableNativeReference { - - protected final long nativeHandle_; - - protected RocksCallbackObject(final long... nativeParameterHandles) { - super(true); - this.nativeHandle_ = initializeNative(nativeParameterHandles); - } - - /** - * Given a list of RocksCallbackObjects, it returns a list - * of the native handles of the underlying objects. - * - * @param objectList the rocks callback objects - * - * @return the native handles - */ - static /* @Nullable */ long[] toNativeHandleList( - /* @Nullable */ final List objectList) { - if (objectList == null) { - return null; - } - final int len = objectList.size(); - final long[] handleList = new long[len]; - for (int i = 0; i < len; i++) { - handleList[i] = objectList.get(i).nativeHandle_; - } - return handleList; - } - - /** - * Construct the Native C++ object which will callback - * to our object methods - * - * @param nativeParameterHandles An array of native handles for any parameter - * objects that are needed during construction - * - * @return The native handle of the C++ object which will callback to us - */ - protected abstract long initializeNative( - final long... nativeParameterHandles); - - /** - * Deletes underlying C++ native callback object pointer - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - private native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/RocksDB.java b/java/src/main/java/org/rocksdb/RocksDB.java deleted file mode 100644 index 77484288f..000000000 --- a/java/src/main/java/org/rocksdb/RocksDB.java +++ /dev/null @@ -1,4694 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import org.rocksdb.util.Environment; - -/** - * A RocksDB is a persistent ordered map from keys to values. It is safe for - * concurrent access from multiple threads without any external synchronization. - * All methods of this class could potentially throw RocksDBException, which - * indicates sth wrong at the RocksDB library side and the call failed. - */ -public class RocksDB extends RocksObject { - public static final byte[] DEFAULT_COLUMN_FAMILY = "default".getBytes(UTF_8); - public static final int NOT_FOUND = -1; - - private enum LibraryState { - NOT_LOADED, - LOADING, - LOADED - } - - private static final AtomicReference libraryLoaded = - new AtomicReference<>(LibraryState.NOT_LOADED); - - static { - RocksDB.loadLibrary(); - } - - private final List ownedColumnFamilyHandles = new ArrayList<>(); - - /** - * Loads the necessary library files. - * Calling this method twice will have no effect. - * By default the method extracts the shared library for loading at - * java.io.tmpdir, however, you can override this temporary location by - * setting the environment variable ROCKSDB_SHAREDLIB_DIR. - */ - public static void loadLibrary() { - if (libraryLoaded.get() == LibraryState.LOADED) { - return; - } - - if (libraryLoaded.compareAndSet(LibraryState.NOT_LOADED, - LibraryState.LOADING)) { - final String tmpDir = System.getenv("ROCKSDB_SHAREDLIB_DIR"); - // loading possibly necessary libraries. - for (final CompressionType compressionType : CompressionType.values()) { - try { - if (compressionType.getLibraryName() != null) { - System.loadLibrary(compressionType.getLibraryName()); - } - } catch (final UnsatisfiedLinkError e) { - // since it may be optional, we ignore its loading failure here. - } - } - try { - NativeLibraryLoader.getInstance().loadLibrary(tmpDir); - } catch (final IOException e) { - libraryLoaded.set(LibraryState.NOT_LOADED); - throw new RuntimeException("Unable to load the RocksDB shared library", - e); - } - - final int encodedVersion = version(); - version = Version.fromEncodedVersion(encodedVersion); - - libraryLoaded.set(LibraryState.LOADED); - return; - } - - while (libraryLoaded.get() == LibraryState.LOADING) { - try { - Thread.sleep(10); - } catch(final InterruptedException e) { - //ignore - } - } - } - - /** - * Tries to load the necessary library files from the given list of - * directories. - * - * @param paths a list of strings where each describes a directory - * of a library. - */ - public static void loadLibrary(final List paths) { - if (libraryLoaded.get() == LibraryState.LOADED) { - return; - } - - if (libraryLoaded.compareAndSet(LibraryState.NOT_LOADED, - LibraryState.LOADING)) { - for (final CompressionType compressionType : CompressionType.values()) { - if (compressionType.equals(CompressionType.NO_COMPRESSION)) { - continue; - } - for (final String path : paths) { - try { - System.load(path + "/" + Environment.getSharedLibraryFileName( - compressionType.getLibraryName())); - break; - } catch (final UnsatisfiedLinkError e) { - // since they are optional, we ignore loading fails. - } - } - } - boolean success = false; - UnsatisfiedLinkError err = null; - for (final String path : paths) { - try { - System.load(path + "/" + - Environment.getJniLibraryFileName("rocksdbjni")); - success = true; - break; - } catch (final UnsatisfiedLinkError e) { - err = e; - } - } - if (!success) { - libraryLoaded.set(LibraryState.NOT_LOADED); - throw err; - } - - final int encodedVersion = version(); - version = Version.fromEncodedVersion(encodedVersion); - - libraryLoaded.set(LibraryState.LOADED); - return; - } - - while (libraryLoaded.get() == LibraryState.LOADING) { - try { - Thread.sleep(10); - } catch(final InterruptedException e) { - //ignore - } - } - } - - public static Version rocksdbVersion() { - return version; - } - - /** - * Private constructor. - * - * @param nativeHandle The native handle of the C++ RocksDB object - */ - protected RocksDB(final long nativeHandle) { - super(nativeHandle); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance given - * the path to the database using the default options w/ createIfMissing - * set to true. - * - * @param path the path to the rocksdb. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @see Options#setCreateIfMissing(boolean) - */ - public static RocksDB open(final String path) throws RocksDBException { - final Options options = new Options(); - options.setCreateIfMissing(true); - return open(options, path); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance given - * the path to the database using the specified options and db path and a list - * of column family names. - *

    - * If opened in read write mode every existing column family name must be - * passed within the list to this method.

    - *

    - * If opened in read-only mode only a subset of existing column families must - * be passed to this method.

    - *

    - * Options instance *should* not be disposed before all DBs using this options - * instance have been closed. If user doesn't call options dispose explicitly, - * then this options instance will be GC'd automatically

    - *

    - * ColumnFamily handles are disposed when the RocksDB instance is disposed. - *

    - * - * @param path the path to the rocksdb. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @see DBOptions#setCreateIfMissing(boolean) - */ - public static RocksDB open(final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles) - throws RocksDBException { - final DBOptions options = new DBOptions(); - return open(options, path, columnFamilyDescriptors, columnFamilyHandles); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance given - * the path to the database using the specified options and db path. - * - *

    - * Options instance *should* not be disposed before all DBs using this options - * instance have been closed. If user doesn't call options dispose explicitly, - * then this options instance will be GC'd automatically.

    - *

    - * Options instance can be re-used to open multiple DBs if DB statistics is - * not used. If DB statistics are required, then its recommended to open DB - * with new Options instance as underlying native statistics instance does not - * use any locks to prevent concurrent updates.

    - * - * @param options {@link org.rocksdb.Options} instance. - * @param path the path to the rocksdb. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * - * @see Options#setCreateIfMissing(boolean) - */ - public static RocksDB open(final Options options, final String path) - throws RocksDBException { - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - final RocksDB db = new RocksDB(open(options.nativeHandle_, path)); - db.storeOptionsInstance(options); - return db; - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance given - * the path to the database using the specified options and db path and a list - * of column family names. - *

    - * If opened in read write mode every existing column family name must be - * passed within the list to this method.

    - *

    - * If opened in read-only mode only a subset of existing column families must - * be passed to this method.

    - *

    - * Options instance *should* not be disposed before all DBs using this options - * instance have been closed. If user doesn't call options dispose explicitly, - * then this options instance will be GC'd automatically.

    - *

    - * Options instance can be re-used to open multiple DBs if DB statistics is - * not used. If DB statistics are required, then its recommended to open DB - * with new Options instance as underlying native statistics instance does not - * use any locks to prevent concurrent updates.

    - *

    - * ColumnFamily handles are disposed when the RocksDB instance is disposed. - *

    - * - * @param options {@link org.rocksdb.DBOptions} instance. - * @param path the path to the rocksdb. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * - * @see DBOptions#setCreateIfMissing(boolean) - */ - public static RocksDB open(final DBOptions options, final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles) - throws RocksDBException { - - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors - .get(i); - cfNames[i] = cfDescriptor.getName(); - cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; - } - - final long[] handles = open(options.nativeHandle_, path, cfNames, - cfOptionHandles); - final RocksDB db = new RocksDB(handles[0]); - db.storeOptionsInstance(options); - - for (int i = 1; i < handles.length; i++) { - final ColumnFamilyHandle columnFamilyHandle = new ColumnFamilyHandle(db, handles[i]); - columnFamilyHandles.add(columnFamilyHandle); - } - - db.ownedColumnFamilyHandles.addAll(columnFamilyHandles); - - return db; - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance in - * Read-Only mode given the path to the database using the default - * options. - * - * @param path the path to the RocksDB. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openReadOnly(final String path) - throws RocksDBException { - // This allows to use the rocksjni default Options instead of - // the c++ one. - final Options options = new Options(); - return openReadOnly(options, path); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance in - * Read-Only mode given the path to the database using the specified - * options and db path. - * - * Options instance *should* not be disposed before all DBs using this options - * instance have been closed. If user doesn't call options dispose explicitly, - * then this options instance will be GC'd automatically. - * - * @param options {@link Options} instance. - * @param path the path to the RocksDB. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openReadOnly(final Options options, final String path) - throws RocksDBException { - return openReadOnly(options, path, false); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance in - * Read-Only mode given the path to the database using the specified - * options and db path. - * - * Options instance *should* not be disposed before all DBs using this options - * instance have been closed. If user doesn't call options dispose explicitly, - * then this options instance will be GC'd automatically. - * - * @param options {@link Options} instance. - * @param path the path to the RocksDB. - * @param errorIfWalFileExists true to raise an error when opening the db - * if a Write Ahead Log file exists, false otherwise. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openReadOnly(final Options options, final String path, - final boolean errorIfWalFileExists) throws RocksDBException { - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - final RocksDB db = new RocksDB(openROnly(options.nativeHandle_, path, errorIfWalFileExists)); - db.storeOptionsInstance(options); - return db; - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance in - * Read-Only mode given the path to the database using the default - * options. - * - * @param path the path to the RocksDB. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openReadOnly(final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles) - throws RocksDBException { - // This allows to use the rocksjni default Options instead of - // the c++ one. - final DBOptions options = new DBOptions(); - return openReadOnly(options, path, columnFamilyDescriptors, columnFamilyHandles, false); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance in - * Read-Only mode given the path to the database using the specified - * options and db path. - * - *

    This open method allows to open RocksDB using a subset of available - * column families

    - *

    Options instance *should* not be disposed before all DBs using this - * options instance have been closed. If user doesn't call options dispose - * explicitly,then this options instance will be GC'd automatically.

    - * - * @param options {@link DBOptions} instance. - * @param path the path to the RocksDB. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openReadOnly(final DBOptions options, final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles) throws RocksDBException { - return openReadOnly(options, path, columnFamilyDescriptors, columnFamilyHandles, false); - } - - /** - * The factory constructor of RocksDB that opens a RocksDB instance in - * Read-Only mode given the path to the database using the specified - * options and db path. - * - *

    This open method allows to open RocksDB using a subset of available - * column families

    - *

    Options instance *should* not be disposed before all DBs using this - * options instance have been closed. If user doesn't call options dispose - * explicitly,then this options instance will be GC'd automatically.

    - * - * @param options {@link DBOptions} instance. - * @param path the path to the RocksDB. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * @param errorIfWalFileExists true to raise an error when opening the db - * if a Write Ahead Log file exists, false otherwise. - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openReadOnly(final DBOptions options, final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles, final boolean errorIfWalFileExists) - throws RocksDBException { - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors - .get(i); - cfNames[i] = cfDescriptor.getName(); - cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; - } - - final long[] handles = - openROnly(options.nativeHandle_, path, cfNames, cfOptionHandles, errorIfWalFileExists); - final RocksDB db = new RocksDB(handles[0]); - db.storeOptionsInstance(options); - - for (int i = 1; i < handles.length; i++) { - final ColumnFamilyHandle columnFamilyHandle = new ColumnFamilyHandle(db, handles[i]); - columnFamilyHandles.add(columnFamilyHandle); - } - - db.ownedColumnFamilyHandles.addAll(columnFamilyHandles); - - return db; - } - - /** - * Open DB as secondary instance with only the default column family. - * - * The secondary instance can dynamically tail the MANIFEST of - * a primary that must have already been created. User can call - * {@link #tryCatchUpWithPrimary()} to make the secondary instance catch up - * with primary (WAL tailing is NOT supported now) whenever the user feels - * necessary. Column families created by the primary after the secondary - * instance starts are currently ignored by the secondary instance. - * Column families opened by secondary and dropped by the primary will be - * dropped by secondary as well. However the user of the secondary instance - * can still access the data of such dropped column family as long as they - * do not destroy the corresponding column family handle. - * WAL tailing is not supported at present, but will arrive soon. - * - * @param options the options to open the secondary instance. - * @param path the path to the primary RocksDB instance. - * @param secondaryPath points to a directory where the secondary instance - * stores its info log - * - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openAsSecondary(final Options options, final String path, - final String secondaryPath) throws RocksDBException { - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - final RocksDB db = new RocksDB(openAsSecondary(options.nativeHandle_, path, secondaryPath)); - db.storeOptionsInstance(options); - return db; - } - - /** - * Open DB as secondary instance with column families. - * You can open a subset of column families in secondary mode. - * - * The secondary instance can dynamically tail the MANIFEST of - * a primary that must have already been created. User can call - * {@link #tryCatchUpWithPrimary()} to make the secondary instance catch up - * with primary (WAL tailing is NOT supported now) whenever the user feels - * necessary. Column families created by the primary after the secondary - * instance starts are currently ignored by the secondary instance. - * Column families opened by secondary and dropped by the primary will be - * dropped by secondary as well. However the user of the secondary instance - * can still access the data of such dropped column family as long as they - * do not destroy the corresponding column family handle. - * WAL tailing is not supported at present, but will arrive soon. - * - * @param options the options to open the secondary instance. - * @param path the path to the primary RocksDB instance. - * @param secondaryPath points to a directory where the secondary instance - * stores its info log. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * - * @return a {@link RocksDB} instance on success, null if the specified - * {@link RocksDB} can not be opened. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static RocksDB openAsSecondary(final DBOptions options, final String path, - final String secondaryPath, final List columnFamilyDescriptors, - final List columnFamilyHandles) throws RocksDBException { - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors.get(i); - cfNames[i] = cfDescriptor.getName(); - cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; - } - - final long[] handles = - openAsSecondary(options.nativeHandle_, path, secondaryPath, cfNames, cfOptionHandles); - final RocksDB db = new RocksDB(handles[0]); - db.storeOptionsInstance(options); - - for (int i = 1; i < handles.length; i++) { - final ColumnFamilyHandle columnFamilyHandle = new ColumnFamilyHandle(db, handles[i]); - columnFamilyHandles.add(columnFamilyHandle); - } - - db.ownedColumnFamilyHandles.addAll(columnFamilyHandles); - - return db; - } - - /** - * This is similar to {@link #close()} except that it - * throws an exception if any error occurs. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - * - * @throws RocksDBException if an error occurs whilst closing. - */ - public void closeE() throws RocksDBException { - for (final ColumnFamilyHandle columnFamilyHandle : ownedColumnFamilyHandles) { - columnFamilyHandle.close(); - } - ownedColumnFamilyHandles.clear(); - - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } finally { - disposeInternal(); - } - } - } - - /** - * This is similar to {@link #closeE()} except that it - * silently ignores any errors. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - */ - @Override - public void close() { - for (final ColumnFamilyHandle columnFamilyHandle : ownedColumnFamilyHandles) { - columnFamilyHandle.close(); - } - ownedColumnFamilyHandles.clear(); - - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } catch (final RocksDBException e) { - // silently ignore the error report - } finally { - disposeInternal(); - } - } - } - - /** - * Static method to determine all available column families for a - * rocksdb database identified by path - * - * @param options Options for opening the database - * @param path Absolute path to rocksdb database - * @return List<byte[]> List containing the column family names - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static List listColumnFamilies(final Options options, - final String path) throws RocksDBException { - return Arrays.asList(RocksDB.listColumnFamilies(options.nativeHandle_, - path)); - } - - /** - * Creates a new column family with the name columnFamilyName and - * allocates a ColumnFamilyHandle within an internal structure. - * The ColumnFamilyHandle is automatically disposed with DB disposal. - * - * @param columnFamilyDescriptor column family to be created. - * @return {@link org.rocksdb.ColumnFamilyHandle} instance. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public ColumnFamilyHandle createColumnFamily( - final ColumnFamilyDescriptor columnFamilyDescriptor) - throws RocksDBException { - final ColumnFamilyHandle columnFamilyHandle = new ColumnFamilyHandle(this, - createColumnFamily(nativeHandle_, columnFamilyDescriptor.getName(), - columnFamilyDescriptor.getName().length, - columnFamilyDescriptor.getOptions().nativeHandle_)); - ownedColumnFamilyHandles.add(columnFamilyHandle); - return columnFamilyHandle; - } - - /** - * Bulk create column families with the same column family options. - * - * @param columnFamilyOptions the options for the column families. - * @param columnFamilyNames the names of the column families. - * - * @return the handles to the newly created column families. - * - * @throws RocksDBException if an error occurs whilst creating - * the column families - */ - public List createColumnFamilies( - final ColumnFamilyOptions columnFamilyOptions, - final List columnFamilyNames) throws RocksDBException { - final byte[][] cfNames = columnFamilyNames.toArray( - new byte[0][]); - final long[] cfHandles = createColumnFamilies(nativeHandle_, - columnFamilyOptions.nativeHandle_, cfNames); - final List columnFamilyHandles = - new ArrayList<>(cfHandles.length); - for (int i = 0; i < cfHandles.length; i++) { - final ColumnFamilyHandle columnFamilyHandle = new ColumnFamilyHandle(this, cfHandles[i]); - columnFamilyHandles.add(columnFamilyHandle); - } - ownedColumnFamilyHandles.addAll(columnFamilyHandles); - return columnFamilyHandles; - } - - /** - * Bulk create column families with the same column family options. - * - * @param columnFamilyDescriptors the descriptions of the column families. - * - * @return the handles to the newly created column families. - * - * @throws RocksDBException if an error occurs whilst creating - * the column families - */ - public List createColumnFamilies( - final List columnFamilyDescriptors) - throws RocksDBException { - final long[] cfOptsHandles = new long[columnFamilyDescriptors.size()]; - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor columnFamilyDescriptor - = columnFamilyDescriptors.get(i); - cfOptsHandles[i] = columnFamilyDescriptor.getOptions().nativeHandle_; - cfNames[i] = columnFamilyDescriptor.getName(); - } - final long[] cfHandles = createColumnFamilies(nativeHandle_, - cfOptsHandles, cfNames); - final List columnFamilyHandles = - new ArrayList<>(cfHandles.length); - for (int i = 0; i < cfHandles.length; i++) { - final ColumnFamilyHandle columnFamilyHandle = new ColumnFamilyHandle(this, cfHandles[i]); - columnFamilyHandles.add(columnFamilyHandle); - } - ownedColumnFamilyHandles.addAll(columnFamilyHandles); - return columnFamilyHandles; - } - - /** - * Drops the column family specified by {@code columnFamilyHandle}. This call - * only records a drop record in the manifest and prevents the column - * family from flushing and compacting. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void dropColumnFamily(final ColumnFamilyHandle columnFamilyHandle) - throws RocksDBException { - dropColumnFamily(nativeHandle_, columnFamilyHandle.nativeHandle_); - } - - // Bulk drop column families. This call only records drop records in the - // manifest and prevents the column families from flushing and compacting. - // In case of error, the request may succeed partially. User may call - // ListColumnFamilies to check the result. - public void dropColumnFamilies( - final List columnFamilies) throws RocksDBException { - final long[] cfHandles = new long[columnFamilies.size()]; - for (int i = 0; i < columnFamilies.size(); i++) { - cfHandles[i] = columnFamilies.get(i).nativeHandle_; - } - dropColumnFamilies(nativeHandle_, cfHandles); - } - - /** - * Deletes native column family handle of given {@link ColumnFamilyHandle} Java object - * and removes reference from {@link RocksDB#ownedColumnFamilyHandles}. - * - * @param columnFamilyHandle column family handle object. - */ - public void destroyColumnFamilyHandle(final ColumnFamilyHandle columnFamilyHandle) { - for (int i = 0; i < ownedColumnFamilyHandles.size(); ++i) { - final ColumnFamilyHandle ownedHandle = ownedColumnFamilyHandles.get(i); - if (ownedHandle.equals(columnFamilyHandle)) { - columnFamilyHandle.close(); - ownedColumnFamilyHandles.remove(i); - return; - } - } - } - - /** - * Set the database entry for "key" to "value". - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final byte[] key, final byte[] value) - throws RocksDBException { - put(nativeHandle_, key, 0, key.length, value, 0, value.length); - } - - /** - * Set the database entry for "key" to "value". - * - * @param key The specified key to be inserted - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value associated with the specified key - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if errors happens in underlying native - * library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void put(final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - put(nativeHandle_, key, offset, len, value, vOffset, vLen); - } - - /** - * Set the database entry for "key" to "value" in the specified - * column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * throws IllegalArgumentException if column family is not present - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value) throws RocksDBException { - put(nativeHandle_, key, 0, key.length, value, 0, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Set the database entry for "key" to "value" in the specified - * column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key The specified key to be inserted - * @param offset the offset of the "key" array to be used, must - * be non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value associated with the specified key - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if errors happens in underlying native - * library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - put(nativeHandle_, key, offset, len, value, vOffset, vLen, - columnFamilyHandle.nativeHandle_); - } - - /** - * Set the database entry for "key" to "value". - * - * @param writeOpts {@link org.rocksdb.WriteOptions} instance. - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final WriteOptions writeOpts, final byte[] key, - final byte[] value) throws RocksDBException { - put(nativeHandle_, writeOpts.nativeHandle_, - key, 0, key.length, value, 0, value.length); - } - - /** - * Set the database entry for "key" to "value". - * - * @param writeOpts {@link org.rocksdb.WriteOptions} instance. - * @param key The specified key to be inserted - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value associated with the specified key - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void put(final WriteOptions writeOpts, - final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - put(nativeHandle_, writeOpts.nativeHandle_, - key, offset, len, value, vOffset, vLen); - } - - /** - * Set the database entry for "key" to "value" for the specified - * column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpts {@link org.rocksdb.WriteOptions} instance. - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * throws IllegalArgumentException if column family is not present - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @see IllegalArgumentException - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpts, final byte[] key, - final byte[] value) throws RocksDBException { - put(nativeHandle_, writeOpts.nativeHandle_, key, 0, key.length, value, - 0, value.length, columnFamilyHandle.nativeHandle_); - } - - /** - * Set the database entry for "key" to "value" for the specified - * column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpts {@link org.rocksdb.WriteOptions} instance. - * @param key the specified key to be inserted. Position and limit is used. - * Supports direct buffer only. - * @param value the value associated with the specified key. Position and limit is used. - * Supports direct buffer only. - * - * throws IllegalArgumentException if column family is not present - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @see IllegalArgumentException - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, final WriteOptions writeOpts, - final ByteBuffer key, final ByteBuffer value) throws RocksDBException { - assert key.isDirect() && value.isDirect(); - putDirect(nativeHandle_, writeOpts.nativeHandle_, key, key.position(), key.remaining(), value, - value.position(), value.remaining(), columnFamilyHandle.nativeHandle_); - key.position(key.limit()); - value.position(value.limit()); - } - - /** - * Set the database entry for "key" to "value". - * - * @param writeOpts {@link org.rocksdb.WriteOptions} instance. - * @param key the specified key to be inserted. Position and limit is used. - * Supports direct buffer only. - * @param value the value associated with the specified key. Position and limit is used. - * Supports direct buffer only. - * - * throws IllegalArgumentException if column family is not present - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @see IllegalArgumentException - */ - public void put(final WriteOptions writeOpts, final ByteBuffer key, final ByteBuffer value) - throws RocksDBException { - assert key.isDirect() && value.isDirect(); - putDirect(nativeHandle_, writeOpts.nativeHandle_, key, key.position(), key.remaining(), value, - value.position(), value.remaining(), 0); - key.position(key.limit()); - value.position(value.limit()); - } - - /** - * Set the database entry for "key" to "value" for the specified - * column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpts {@link org.rocksdb.WriteOptions} instance. - * @param key The specified key to be inserted - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value associated with the specified key - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpts, - final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - put(nativeHandle_, writeOpts.nativeHandle_, key, offset, len, value, - vOffset, vLen, columnFamilyHandle.nativeHandle_); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final byte[] key) throws RocksDBException { - delete(nativeHandle_, key, 0, key.length); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param key Key to delete within database - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be - * non-negative and no larger than ("key".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final byte[] key, final int offset, final int len) - throws RocksDBException { - delete(nativeHandle_, key, offset, len); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - delete(nativeHandle_, key, 0, key.length, columnFamilyHandle.nativeHandle_); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key Key to delete within database - * @param offset the offset of the "key" array to be used, - * must be non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final int offset, final int len) - throws RocksDBException { - delete(nativeHandle_, key, offset, len, columnFamilyHandle.nativeHandle_); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final WriteOptions writeOpt, final byte[] key) - throws RocksDBException { - delete(nativeHandle_, writeOpt.nativeHandle_, key, 0, key.length); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be - * non-negative and no larger than ("key".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final WriteOptions writeOpt, final byte[] key, - final int offset, final int len) throws RocksDBException { - delete(nativeHandle_, writeOpt.nativeHandle_, key, offset, len); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] key) - throws RocksDBException { - delete(nativeHandle_, writeOpt.nativeHandle_, key, 0, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be - * non-negative and no larger than ("key".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] key, final int offset, - final int len) throws RocksDBException { - delete(nativeHandle_, writeOpt.nativeHandle_, key, offset, len, - columnFamilyHandle.nativeHandle_); - } - - /** - * Get the value associated with the specified key within column family. - * - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. It is using position and limit. - * Supports direct buffer only. - * @param value the out-value to receive the retrieved value. - * It is using position and limit. Limit is set according to value size. - * Supports direct buffer only. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ReadOptions opt, final ByteBuffer key, final ByteBuffer value) - throws RocksDBException { - assert key.isDirect() && value.isDirect(); - int result = getDirect(nativeHandle_, opt.nativeHandle_, key, key.position(), key.remaining(), - value, value.position(), value.remaining(), 0); - if (result != NOT_FOUND) { - value.limit(Math.min(value.limit(), value.position() + result)); - } - key.position(key.limit()); - return result; - } - - /** - * Get the value associated with the specified key within column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. It is using position and limit. - * Supports direct buffer only. - * @param value the out-value to receive the retrieved value. - * It is using position and limit. Limit is set according to value size. - * Supports direct buffer only. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ColumnFamilyHandle columnFamilyHandle, final ReadOptions opt, - final ByteBuffer key, final ByteBuffer value) throws RocksDBException { - assert key.isDirect() && value.isDirect(); - int result = getDirect(nativeHandle_, opt.nativeHandle_, key, key.position(), key.remaining(), - value, value.position(), value.remaining(), columnFamilyHandle.nativeHandle_); - if (result != NOT_FOUND) { - value.limit(Math.min(value.limit(), value.position() + result)); - } - key.position(key.limit()); - return result; - } - - /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. - * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. - * - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, key, key.length); - } - - /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. - * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. - * - * @param columnFamilyHandle The column family to delete the key from - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. - * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. - * - * Note: consider setting {@link WriteOptions#setSync(boolean)} true. - * - * @param writeOpt Write options for the delete - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final WriteOptions writeOpt, final byte[] key) - throws RocksDBException { - singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length); - } - - /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. - * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. - * - * Note: consider setting {@link WriteOptions#setSync(boolean)} true. - * - * @param columnFamilyHandle The column family to delete the key from - * @param writeOpt Write options for the delete - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); - } - - - /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. - * - * @param beginKey First key to delete within database (inclusive) - * @param endKey Last key to delete within database (exclusive) - * - * @throws RocksDBException thrown if error happens in underlying native - * library. - */ - public void deleteRange(final byte[] beginKey, final byte[] endKey) - throws RocksDBException { - deleteRange(nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, - endKey.length); - } - - /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance - * @param beginKey First key to delete within database (inclusive) - * @param endKey Last key to delete within database (exclusive) - * - * @throws RocksDBException thrown if error happens in underlying native - * library. - */ - public void deleteRange(final ColumnFamilyHandle columnFamilyHandle, - final byte[] beginKey, final byte[] endKey) throws RocksDBException { - deleteRange(nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, - endKey.length, columnFamilyHandle.nativeHandle_); - } - - /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. - * - * @param writeOpt WriteOptions to be used with delete operation - * @param beginKey First key to delete within database (inclusive) - * @param endKey Last key to delete within database (exclusive) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void deleteRange(final WriteOptions writeOpt, final byte[] beginKey, - final byte[] endKey) throws RocksDBException { - deleteRange(nativeHandle_, writeOpt.nativeHandle_, beginKey, 0, - beginKey.length, endKey, 0, endKey.length); - } - - /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance - * @param writeOpt WriteOptions to be used with delete operation - * @param beginKey First key to delete within database (included) - * @param endKey Last key to delete within database (excluded) - * - * @throws RocksDBException thrown if error happens in underlying native - * library. - */ - public void deleteRange(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] beginKey, final byte[] endKey) - throws RocksDBException { - deleteRange(nativeHandle_, writeOpt.nativeHandle_, beginKey, 0, - beginKey.length, endKey, 0, endKey.length, - columnFamilyHandle.nativeHandle_); - } - - - /** - * Add merge operand for key/value pair. - * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for the - * specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final byte[] key, final byte[] value) - throws RocksDBException { - merge(nativeHandle_, key, 0, key.length, value, 0, value.length); - } - - /** - * Add merge operand for key/value pair. - * - * @param key the specified key to be merged. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value to be merged with the current value for the - * specified key. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and must be non-negative and no larger than - * ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void merge(final byte[] key, int offset, int len, final byte[] value, - final int vOffset, final int vLen) throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - merge(nativeHandle_, key, offset, len, value, vOffset, vLen); - } - - /** - * Add merge operand for key/value pair in a ColumnFamily. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value) throws RocksDBException { - merge(nativeHandle_, key, 0, key.length, value, 0, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Add merge operand for key/value pair in a ColumnFamily. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key the specified key to be merged. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value to be merged with the current value for - * the specified key. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * must be non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final int offset, final int len, final byte[] value, - final int vOffset, final int vLen) throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - merge(nativeHandle_, key, offset, len, value, vOffset, vLen, - columnFamilyHandle.nativeHandle_); - } - - /** - * Add merge operand for key/value pair. - * - * @param writeOpts {@link WriteOptions} for this write. - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final WriteOptions writeOpts, final byte[] key, - final byte[] value) throws RocksDBException { - merge(nativeHandle_, writeOpts.nativeHandle_, - key, 0, key.length, value, 0, value.length); - } - - /** - * Add merge operand for key/value pair. - * - * @param writeOpts {@link WriteOptions} for this write. - * @param key the specified key to be merged. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("value".length - offset) - * @param value the value to be merged with the current value for - * the specified key. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void merge(final WriteOptions writeOpts, - final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - merge(nativeHandle_, writeOpts.nativeHandle_, - key, offset, len, value, vOffset, vLen); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database. It is using position and limit. - * Supports direct buffer only. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final WriteOptions writeOpt, final ByteBuffer key) throws RocksDBException { - assert key.isDirect(); - deleteDirect(nativeHandle_, writeOpt.nativeHandle_, key, key.position(), key.remaining(), 0); - key.position(key.limit()); - } - - /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database. It is using position and limit. - * Supports direct buffer only. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, final WriteOptions writeOpt, - final ByteBuffer key) throws RocksDBException { - assert key.isDirect(); - deleteDirect(nativeHandle_, writeOpt.nativeHandle_, key, key.position(), key.remaining(), - columnFamilyHandle.nativeHandle_); - key.position(key.limit()); - } - - /** - * Add merge operand for key/value pair. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param writeOpts {@link WriteOptions} for this write. - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for the - * specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpts, final byte[] key, final byte[] value) - throws RocksDBException { - merge(nativeHandle_, writeOpts.nativeHandle_, - key, 0, key.length, value, 0, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Add merge operand for key/value pair. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param writeOpts {@link WriteOptions} for this write. - * @param key the specified key to be merged. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the value to be merged with the current value for - * the specified key. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IndexOutOfBoundsException if an offset or length is out of bounds - */ - public void merge( - final ColumnFamilyHandle columnFamilyHandle, final WriteOptions writeOpts, - final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - merge(nativeHandle_, writeOpts.nativeHandle_, - key, offset, len, value, vOffset, vLen, - columnFamilyHandle.nativeHandle_); - } - - /** - * Apply the specified updates to the database. - * - * @param writeOpts WriteOptions instance - * @param updates WriteBatch instance - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void write(final WriteOptions writeOpts, final WriteBatch updates) - throws RocksDBException { - write0(nativeHandle_, writeOpts.nativeHandle_, updates.nativeHandle_); - } - - /** - * Apply the specified updates to the database. - * - * @param writeOpts WriteOptions instance - * @param updates WriteBatchWithIndex instance - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void write(final WriteOptions writeOpts, - final WriteBatchWithIndex updates) throws RocksDBException { - write1(nativeHandle_, writeOpts.nativeHandle_, updates.nativeHandle_); - } - - // TODO(AR) we should improve the #get() API, returning -1 (RocksDB.NOT_FOUND) is not very nice - // when we could communicate better status into, also the C++ code show that -2 could be returned - - /** - * Get the value associated with the specified key within column family* - * - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final byte[] key, final byte[] value) throws RocksDBException { - return get(nativeHandle_, key, 0, key.length, value, 0, value.length); - } - - /** - * Get the value associated with the specified key within column family* - * - * @param key the key to retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the out-value to receive the retrieved value. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "value".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and and no larger than ("value".length - offset) - * - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - return get(nativeHandle_, key, offset, len, value, vOffset, vLen); - } - - /** - * Get the value associated with the specified key within column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final byte[] value) throws RocksDBException, IllegalArgumentException { - return get(nativeHandle_, key, 0, key.length, value, 0, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Get the value associated with the specified key within column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key to retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * an no larger than ("key".length - offset) - * @param value the out-value to receive the retrieved value. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final int offset, final int len, final byte[] value, final int vOffset, - final int vLen) throws RocksDBException, IllegalArgumentException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - return get(nativeHandle_, key, offset, len, value, vOffset, vLen, - columnFamilyHandle.nativeHandle_); - } - - /** - * Get the value associated with the specified key. - * - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ReadOptions opt, final byte[] key, - final byte[] value) throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, - key, 0, key.length, value, 0, value.length); - } - - /** - * Get the value associated with the specified key. - * - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param value the out-value to receive the retrieved value. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, must be - * non-negative and no larger than ("value".length - offset) - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ReadOptions opt, final byte[] key, final int offset, - final int len, final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - return get(nativeHandle_, opt.nativeHandle_, - key, offset, len, value, vOffset, vLen); - } - - /** - * Get the value associated with the specified key within column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions opt, final byte[] key, final byte[] value) - throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length, value, - 0, value.length, columnFamilyHandle.nativeHandle_); - } - - /** - * Get the value associated with the specified key within column family. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be - * non-negative and and no larger than ("key".length - offset) - * @param value the out-value to receive the retrieved value. - * @param vOffset the offset of the "value" array to be used, must be - * non-negative and no longer than "key".length - * @param vLen the length of the "value" array to be used, and must be - * non-negative and no larger than ("value".length - offset) - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions opt, final byte[] key, final int offset, final int len, - final byte[] value, final int vOffset, final int vLen) - throws RocksDBException { - checkBounds(offset, len, key.length); - checkBounds(vOffset, vLen, value.length); - return get(nativeHandle_, opt.nativeHandle_, key, offset, len, value, - vOffset, vLen, columnFamilyHandle.nativeHandle_); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param key the key retrieve the value. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final byte[] key) throws RocksDBException { - return get(nativeHandle_, key, 0, key.length); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param key the key retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final byte[] key, final int offset, - final int len) throws RocksDBException { - checkBounds(offset, len, key.length); - return get(nativeHandle_, key, offset, len); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key retrieve the value. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - return get(nativeHandle_, key, 0, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final int offset, final int len) - throws RocksDBException { - checkBounds(offset, len, key.length); - return get(nativeHandle_, key, offset, len, - columnFamilyHandle.nativeHandle_); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param key the key retrieve the value. - * @param opt Read options. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ReadOptions opt, final byte[] key) - throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param key the key retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param opt Read options. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ReadOptions opt, final byte[] key, final int offset, - final int len) throws RocksDBException { - checkBounds(offset, len, key.length); - return get(nativeHandle_, opt.nativeHandle_, key, offset, len); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key retrieve the value. - * @param opt Read options. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions opt, final byte[] key) throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key retrieve the value. - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than ("key".length - offset) - * @param opt Read options. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions opt, final byte[] key, final int offset, final int len) - throws RocksDBException { - checkBounds(offset, len, key.length); - return get(nativeHandle_, opt.nativeHandle_, key, offset, len, - columnFamilyHandle.nativeHandle_); - } - - /** - * Takes a list of keys, and returns a list of values for the given list of - * keys. List will contain null for keys which could not be found. - * - * @param keys List of keys for which values need to be retrieved. - * @return List of values for the given list of keys. List will contain - * null for keys which could not be found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List multiGetAsList(final List keys) - throws RocksDBException { - assert(keys.size() != 0); - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int[] keyOffsets = new int[keysArray.length]; - final int[] keyLengths = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - return Arrays.asList(multiGet(nativeHandle_, keysArray, keyOffsets, - keyLengths)); - } - - /** - * Returns a list of values for the given list of keys. List will contain - * null for keys which could not be found. - *

    - * Note: Every key needs to have a related column family name in - * {@code columnFamilyHandleList}. - *

    - * - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys List of keys for which values need to be retrieved. - * @return List of values for the given list of keys. List will contain - * null for keys which could not be found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IllegalArgumentException thrown if the size of passed keys is not - * equal to the amount of passed column family handles. - */ - public List multiGetAsList( - final List columnFamilyHandleList, - final List keys) throws RocksDBException, - IllegalArgumentException { - assert(keys.size() != 0); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size() != columnFamilyHandleList.size()) { - throw new IllegalArgumentException( - "For each key there must be a ColumnFamilyHandle."); - } - final long[] cfHandles = new long[columnFamilyHandleList.size()]; - for (int i = 0; i < columnFamilyHandleList.size(); i++) { - cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; - } - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int[] keyOffsets = new int[keysArray.length]; - final int[] keyLengths = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - return Arrays.asList(multiGet(nativeHandle_, keysArray, keyOffsets, - keyLengths, cfHandles)); - } - - /** - * Returns a list of values for the given list of keys. List will contain - * null for keys which could not be found. - * - * @param opt Read options. - * @param keys of keys for which values need to be retrieved. - * @return List of values for the given list of keys. List will contain - * null for keys which could not be found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List multiGetAsList(final ReadOptions opt, - final List keys) throws RocksDBException { - assert(keys.size() != 0); - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int[] keyOffsets = new int[keysArray.length]; - final int[] keyLengths = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - return Arrays.asList(multiGet(nativeHandle_, opt.nativeHandle_, - keysArray, keyOffsets, keyLengths)); - } - - /** - * Returns a list of values for the given list of keys. List will contain - * null for keys which could not be found. - *

    - * Note: Every key needs to have a related column family name in - * {@code columnFamilyHandleList}. - *

    - * - * @param opt Read options. - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys of keys for which values need to be retrieved. - * @return List of values for the given list of keys. List will contain - * null for keys which could not be found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IllegalArgumentException thrown if the size of passed keys is not - * equal to the amount of passed column family handles. - */ - public List multiGetAsList(final ReadOptions opt, - final List columnFamilyHandleList, - final List keys) throws RocksDBException { - assert(keys.size() != 0); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size()!=columnFamilyHandleList.size()){ - throw new IllegalArgumentException( - "For each key there must be a ColumnFamilyHandle."); - } - final long[] cfHandles = new long[columnFamilyHandleList.size()]; - for (int i = 0; i < columnFamilyHandleList.size(); i++) { - cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; - } - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int[] keyOffsets = new int[keysArray.length]; - final int[] keyLengths = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - return Arrays.asList(multiGet(nativeHandle_, opt.nativeHandle_, - keysArray, keyOffsets, keyLengths, cfHandles)); - } - - /** - * Fetches a list of values for the given list of keys, all from the default column family. - * - * @param keys list of keys for which values need to be retrieved. - * @param values list of buffers to return retrieved values in - * @return list of number of bytes in DB for each requested key - * this can be more than the size of the corresponding buffer; then the buffer will be filled - * with the appropriate truncation of the database value. - * @throws RocksDBException if error happens in underlying native library. - * @throws IllegalArgumentException thrown if the number of passed keys and passed values - * do not match. - */ - public List multiGetByteBuffers( - final List keys, final List values) throws RocksDBException { - final ReadOptions readOptions = new ReadOptions(); - final List columnFamilyHandleList = new ArrayList<>(1); - columnFamilyHandleList.add(getDefaultColumnFamily()); - return multiGetByteBuffers(readOptions, columnFamilyHandleList, keys, values); - } - - /** - * Fetches a list of values for the given list of keys, all from the default column family. - * - * @param readOptions Read options - * @param keys list of keys for which values need to be retrieved. - * @param values list of buffers to return retrieved values in - * @throws RocksDBException if error happens in underlying native library. - * @throws IllegalArgumentException thrown if the number of passed keys and passed values - * do not match. - * @return the list of values for the given list of keys - */ - public List multiGetByteBuffers(final ReadOptions readOptions, - final List keys, final List values) throws RocksDBException { - final List columnFamilyHandleList = new ArrayList<>(1); - columnFamilyHandleList.add(getDefaultColumnFamily()); - return multiGetByteBuffers(readOptions, columnFamilyHandleList, keys, values); - } - - /** - * Fetches a list of values for the given list of keys. - *

    - * Note: Every key needs to have a related column family name in - * {@code columnFamilyHandleList}. - *

    - * - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys list of keys for which values need to be retrieved. - * @param values list of buffers to return retrieved values in - * @throws RocksDBException if error happens in underlying native library. - * @throws IllegalArgumentException thrown if the number of passed keys, passed values and - * passed column family handles do not match. - * @return the list of values for the given list of keys - */ - public List multiGetByteBuffers( - final List columnFamilyHandleList, final List keys, - final List values) throws RocksDBException { - final ReadOptions readOptions = new ReadOptions(); - return multiGetByteBuffers(readOptions, columnFamilyHandleList, keys, values); - } - - /** - * Fetches a list of values for the given list of keys. - *

    - * Note: Every key needs to have a related column family name in - * {@code columnFamilyHandleList}. - *

    - * - * @param readOptions Read options - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys list of keys for which values need to be retrieved. - * @param values list of buffers to return retrieved values in - * @throws RocksDBException if error happens in underlying native library. - * @throws IllegalArgumentException thrown if the number of passed keys, passed values and - * passed column family handles do not match. - * @return the list of values for the given list of keys - */ - public List multiGetByteBuffers(final ReadOptions readOptions, - final List columnFamilyHandleList, final List keys, - final List values) throws RocksDBException { - assert (keys.size() != 0); - - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size() != columnFamilyHandleList.size() && columnFamilyHandleList.size() > 1) { - throw new IllegalArgumentException( - "Wrong number of ColumnFamilyHandle(s) supplied. Provide 0, 1, or as many as there are key/value(s)"); - } - - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (values.size() != keys.size()) { - throw new IllegalArgumentException("For each key there must be a corresponding value."); - } - - // TODO (AP) support indirect buffers - for (final ByteBuffer key : keys) { - if (!key.isDirect()) { - throw new IllegalArgumentException("All key buffers must be direct byte buffers"); - } - } - - // TODO (AP) support indirect buffers, though probably via a less efficient code path - for (final ByteBuffer value : values) { - if (!value.isDirect()) { - throw new IllegalArgumentException("All value buffers must be direct byte buffers"); - } - } - - final int numCFHandles = columnFamilyHandleList.size(); - final long[] cfHandles = new long[numCFHandles]; - for (int i = 0; i < numCFHandles; i++) { - cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; - } - - final int numValues = keys.size(); - - final ByteBuffer[] keysArray = keys.toArray(new ByteBuffer[0]); - final int[] keyOffsets = new int[numValues]; - final int[] keyLengths = new int[numValues]; - for (int i = 0; i < numValues; i++) { - // TODO (AP) add keysArray[i].arrayOffset() if the buffer is indirect - // TODO (AP) because in that case we have to pass the array directly, - // so that the JNI C++ code will not know to compensate for the array offset - keyOffsets[i] = keysArray[i].position(); - keyLengths[i] = keysArray[i].limit(); - } - final ByteBuffer[] valuesArray = values.toArray(new ByteBuffer[0]); - final int[] valuesSizeArray = new int[numValues]; - final Status[] statusArray = new Status[numValues]; - - multiGet(nativeHandle_, readOptions.nativeHandle_, cfHandles, keysArray, keyOffsets, keyLengths, - valuesArray, valuesSizeArray, statusArray); - - final List results = new ArrayList<>(); - for (int i = 0; i < numValues; i++) { - final Status status = statusArray[i]; - if (status.getCode() == Status.Code.Ok) { - final ByteBuffer value = valuesArray[i]; - value.position(Math.min(valuesSizeArray[i], value.capacity())); - value.flip(); // prepare for read out - results.add(new ByteBufferGetStatus(status, valuesSizeArray[i], value)); - } else { - results.add(new ByteBufferGetStatus(status)); - } - } - - return results; - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(byte[])}. One way to make this lighter weight is to avoid - * doing any IOs. - * - * @param key byte array of a key to search for - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist(final byte[] key, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist(key, 0, key.length, valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(byte[], int, int)}. One way to make this lighter weight is to - * avoid doing any IOs. - * - * @param key byte array of a key to search for - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than "key".length - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist(final byte[] key, - final int offset, final int len, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist((ColumnFamilyHandle)null, key, offset, len, valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(ColumnFamilyHandle,byte[])}. One way to make this lighter - * weight is to avoid doing any IOs. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key byte array of a key to search for - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist( - final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist(columnFamilyHandle, key, 0, key.length, - valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(ColumnFamilyHandle, byte[], int, int)}. One way to make this - * lighter weight is to avoid doing any IOs. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key byte array of a key to search for - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than "key".length - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist( - final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, int offset, int len, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist(columnFamilyHandle, null, key, offset, len, - valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(ReadOptions, byte[])}. One way to make this - * lighter weight is to avoid doing any IOs. - * - * @param readOptions {@link ReadOptions} instance - * @param key byte array of a key to search for - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist( - final ReadOptions readOptions, final byte[] key, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist(readOptions, key, 0, key.length, - valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(ReadOptions, byte[], int, int)}. One way to make this - * lighter weight is to avoid doing any IOs. - * - * @param readOptions {@link ReadOptions} instance - * @param key byte array of a key to search for - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than "key".length - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist( - final ReadOptions readOptions, - final byte[] key, final int offset, final int len, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist(null, readOptions, - key, offset, len, valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(ColumnFamilyHandle, ReadOptions, byte[])}. One way to make this - * lighter weight is to avoid doing any IOs. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param readOptions {@link ReadOptions} instance - * @param key byte array of a key to search for - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist( - final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions readOptions, final byte[] key, - /* @Nullable */ final Holder valueHolder) { - return keyMayExist(columnFamilyHandle, readOptions, - key, 0, key.length, valueHolder); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * If the caller wants to obtain value when the key - * is found in memory, then {@code valueHolder} must be set. - * - * This check is potentially lighter-weight than invoking - * {@link #get(ColumnFamilyHandle, ReadOptions, byte[], int, int)}. - * One way to make this lighter weight is to avoid doing any IOs. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param readOptions {@link ReadOptions} instance - * @param key byte array of a key to search for - * @param offset the offset of the "key" array to be used, must be - * non-negative and no larger than "key".length - * @param len the length of the "key" array to be used, must be non-negative - * and no larger than "key".length - * @param valueHolder non-null to retrieve the value if it is found, or null - * if the value is not needed. If non-null, upon return of the function, - * the {@code value} will be set if it could be retrieved. - * - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist( - final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions readOptions, - final byte[] key, final int offset, final int len, - /* @Nullable */ final Holder valueHolder) { - checkBounds(offset, len, key.length); - if (valueHolder == null) { - return keyMayExist(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - readOptions == null ? 0 : readOptions.nativeHandle_, - key, offset, len); - } else { - final byte[][] result = keyMayExistFoundValue( - nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - readOptions == null ? 0 : readOptions.nativeHandle_, - key, offset, len); - if (result[0][0] == 0x0) { - valueHolder.setValue(null); - return false; - } else if (result[0][0] == 0x1) { - valueHolder.setValue(null); - return true; - } else { - valueHolder.setValue(result[1]); - return true; - } - } - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * @param key bytebuffer containing the value of the key - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist(final ByteBuffer key) { - return keyMayExist(null, (ReadOptions) null, key); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in - * @param key bytebuffer containing the value of the key - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key) { - return keyMayExist(columnFamilyHandle, (ReadOptions) null, key); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * @param readOptions the {@link ReadOptions} to use when reading the key/value - * @param key bytebuffer containing the value of the key - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist(final ReadOptions readOptions, final ByteBuffer key) { - return keyMayExist(null, readOptions, key); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, - * otherwise if it can with best effort retreive the value, it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might - * exist is at the discretion of the implementation; the only guarantee is that {@link - * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. - * - * @param key bytebuffer containing the value of the key - * @param value bytebuffer which will receive a value if the key exists and a value is known - * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided - */ - public KeyMayExist keyMayExist(final ByteBuffer key, final ByteBuffer value) { - return keyMayExist(null, null, key, value); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, - * otherwise if it can with best effort retreive the value, it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might - * exist is at the discretion of the implementation; the only guarantee is that {@link - * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. - * - * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in - * @param key bytebuffer containing the value of the key - * @param value bytebuffer which will receive a value if the key exists and a value is known - * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided - */ - public KeyMayExist keyMayExist( - final ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key, final ByteBuffer value) { - return keyMayExist(columnFamilyHandle, null, key, value); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, - * otherwise if it can with best effort retreive the value, it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might - * exist is at the discretion of the implementation; the only guarantee is that {@link - * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. - * - * @param readOptions the {@link ReadOptions} to use when reading the key/value - * @param key bytebuffer containing the value of the key - * @param value bytebuffer which will receive a value if the key exists and a value is known - * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided - */ - public KeyMayExist keyMayExist( - final ReadOptions readOptions, final ByteBuffer key, final ByteBuffer value) { - return keyMayExist(null, readOptions, key, value); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns false, otherwise it returns true if the key might exist. - * That is to say that this method is probabilistic and may return false - * positives, but never a false negative. - * - * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in - * @param readOptions the {@link ReadOptions} to use when reading the key/value - * @param key bytebuffer containing the value of the key - * @return false if the key definitely does not exist in the database, - * otherwise true. - */ - public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions readOptions, final ByteBuffer key) { - assert key != null : "key ByteBuffer parameter cannot be null"; - assert key.isDirect() : "key parameter must be a direct ByteBuffer"; - return keyMayExistDirect(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - readOptions == null ? 0 : readOptions.nativeHandle_, key, key.position(), key.limit()); - } - - /** - * If the key definitely does not exist in the database, then this method - * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, - * otherwise if it can with best effort retreive the value, it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link - * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might - * exist is at the discretion of the implementation; the only guarantee is that {@link - * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. - * - * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in - * @param readOptions the {@link ReadOptions} to use when reading the key/value - * @param key bytebuffer containing the value of the key - * @param value bytebuffer which will receive a value if the key exists and a value is known - * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided - */ - public KeyMayExist keyMayExist(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions readOptions, final ByteBuffer key, final ByteBuffer value) { - assert key != null : "key ByteBuffer parameter cannot be null"; - assert key.isDirect() : "key parameter must be a direct ByteBuffer"; - assert value - != null - : "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"; - assert value.isDirect() : "value parameter must be a direct ByteBuffer"; - - final int[] result = keyMayExistDirectFoundValue(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - readOptions == null ? 0 : readOptions.nativeHandle_, key, key.position(), key.remaining(), - value, value.position(), value.remaining()); - final int valueLength = result[1]; - value.limit(value.position() + Math.min(valueLength, value.remaining())); - return new KeyMayExist(KeyMayExist.KeyMayExistEnum.values()[result[0]], valueLength); - } - - /** - *

    Return a heap-allocated iterator over the contents of the - * database. The result of newIterator() is initially invalid - * (caller must call one of the Seek methods on the iterator - * before using it).

    - * - *

    Caller should close the iterator when it is no longer needed. - * The returned iterator should be closed before this db is closed. - *

    - * - * @return instance of iterator object. - */ - public RocksIterator newIterator() { - return new RocksIterator(this, iterator(nativeHandle_)); - } - - /** - *

    Return a heap-allocated iterator over the contents of the - * database. The result of newIterator() is initially invalid - * (caller must call one of the Seek methods on the iterator - * before using it).

    - * - *

    Caller should close the iterator when it is no longer needed. - * The returned iterator should be closed before this db is closed. - *

    - * - * @param readOptions {@link ReadOptions} instance. - * @return instance of iterator object. - */ - public RocksIterator newIterator(final ReadOptions readOptions) { - return new RocksIterator(this, iterator(nativeHandle_, - readOptions.nativeHandle_)); - } - - /** - *

    Return a heap-allocated iterator over the contents of a - * ColumnFamily. The result of newIterator() is initially invalid - * (caller must call one of the Seek methods on the iterator - * before using it).

    - * - *

    Caller should close the iterator when it is no longer needed. - * The returned iterator should be closed before this db is closed. - *

    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @return instance of iterator object. - */ - public RocksIterator newIterator( - final ColumnFamilyHandle columnFamilyHandle) { - return new RocksIterator(this, iteratorCF(nativeHandle_, - columnFamilyHandle.nativeHandle_)); - } - - /** - *

    Return a heap-allocated iterator over the contents of a - * ColumnFamily. The result of newIterator() is initially invalid - * (caller must call one of the Seek methods on the iterator - * before using it).

    - * - *

    Caller should close the iterator when it is no longer needed. - * The returned iterator should be closed before this db is closed. - *

    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param readOptions {@link ReadOptions} instance. - * @return instance of iterator object. - */ - public RocksIterator newIterator(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions readOptions) { - return new RocksIterator(this, iteratorCF(nativeHandle_, - columnFamilyHandle.nativeHandle_, readOptions.nativeHandle_)); - } - - /** - * Returns iterators from a consistent database state across multiple - * column families. Iterators are heap allocated and need to be deleted - * before the db is deleted - * - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @return {@link java.util.List} containing {@link org.rocksdb.RocksIterator} - * instances - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List newIterators( - final List columnFamilyHandleList) - throws RocksDBException { - return newIterators(columnFamilyHandleList, new ReadOptions()); - } - - /** - * Returns iterators from a consistent database state across multiple - * column families. Iterators are heap allocated and need to be deleted - * before the db is deleted - * - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param readOptions {@link ReadOptions} instance. - * @return {@link java.util.List} containing {@link org.rocksdb.RocksIterator} - * instances - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List newIterators( - final List columnFamilyHandleList, - final ReadOptions readOptions) throws RocksDBException { - - final long[] columnFamilyHandles = new long[columnFamilyHandleList.size()]; - for (int i = 0; i < columnFamilyHandleList.size(); i++) { - columnFamilyHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; - } - - final long[] iteratorRefs = iterators(nativeHandle_, columnFamilyHandles, - readOptions.nativeHandle_); - - final List iterators = new ArrayList<>( - columnFamilyHandleList.size()); - for (int i=0; iReturn a handle to the current DB state. Iterators created with - * this handle will all observe a stable snapshot of the current DB - * state. The caller must call ReleaseSnapshot(result) when the - * snapshot is no longer needed.

    - * - *

    nullptr will be returned if the DB fails to take a snapshot or does - * not support snapshot.

    - * - * @return Snapshot {@link Snapshot} instance - */ - public Snapshot getSnapshot() { - long snapshotHandle = getSnapshot(nativeHandle_); - if (snapshotHandle != 0) { - return new Snapshot(snapshotHandle); - } - return null; - } - - /** - * Release a previously acquired snapshot. - * - * The caller must not use "snapshot" after this call. - * - * @param snapshot {@link Snapshot} instance - */ - public void releaseSnapshot(final Snapshot snapshot) { - if (snapshot != null) { - releaseSnapshot(nativeHandle_, snapshot.nativeHandle_); - } - } - - /** - * DB implements can export properties about their state - * via this method on a per column family level. - * - *

    If {@code property} is a valid property understood by this DB - * implementation, fills {@code value} with its current value and - * returns true. Otherwise returns false.

    - * - *

    Valid property names include: - *

      - *
    • "rocksdb.num-files-at-level<N>" - return the number of files at - * level <N>, where <N> is an ASCII representation of a level - * number (e.g. "0").
    • - *
    • "rocksdb.stats" - returns a multi-line string that describes statistics - * about the internal operation of the DB.
    • - *
    • "rocksdb.sstables" - returns a multi-line string that describes all - * of the sstables that make up the db contents.
    • - *
    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family. - * @param property to be fetched. See above for examples - * @return property value - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public String getProperty( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, - final String property) throws RocksDBException { - return getProperty(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - property, property.length()); - } - - /** - * DB implementations can export properties about their state - * via this method. If "property" is a valid property understood by this - * DB implementation, fills "*value" with its current value and returns - * true. Otherwise returns false. - * - *

    Valid property names include: - *

      - *
    • "rocksdb.num-files-at-level<N>" - return the number of files at - * level <N>, where <N> is an ASCII representation of a level - * number (e.g. "0").
    • - *
    • "rocksdb.stats" - returns a multi-line string that describes statistics - * about the internal operation of the DB.
    • - *
    • "rocksdb.sstables" - returns a multi-line string that describes all - * of the sstables that make up the db contents.
    • - *
    - * - * @param property to be fetched. See above for examples - * @return property value - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public String getProperty(final String property) throws RocksDBException { - return getProperty(null, property); - } - - - /** - * Gets a property map. - * - * @param property to be fetched. - * - * @return the property map - * - * @throws RocksDBException if an error happens in the underlying native code. - */ - public Map getMapProperty(final String property) - throws RocksDBException { - return getMapProperty(null, property); - } - - /** - * Gets a property map. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family. - * @param property to be fetched. - * - * @return the property map - * - * @throws RocksDBException if an error happens in the underlying native code. - */ - public Map getMapProperty( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, - final String property) throws RocksDBException { - return getMapProperty(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - property, property.length()); - } - - /** - *

    Similar to GetProperty(), but only works for a subset of properties - * whose return value is a numerical value. Return the value as long.

    - * - *

    Note: As the returned property is of type - * {@code uint64_t} on C++ side the returning value can be negative - * because Java supports in Java 7 only signed long values.

    - * - *

    Java 7: To mitigate the problem of the non - * existent unsigned long tpye, values should be encapsulated using - * {@link java.math.BigInteger} to reflect the correct value. The correct - * behavior is guaranteed if {@code 2^64} is added to negative values.

    - * - *

    Java 8: In Java 8 the value should be treated as - * unsigned long using provided methods of type {@link Long}.

    - * - * @param property to be fetched. - * - * @return numerical property value. - * - * @throws RocksDBException if an error happens in the underlying native code. - */ - public long getLongProperty(final String property) throws RocksDBException { - return getLongProperty(null, property); - } - - /** - *

    Similar to GetProperty(), but only works for a subset of properties - * whose return value is a numerical value. Return the value as long.

    - * - *

    Note: As the returned property is of type - * {@code uint64_t} on C++ side the returning value can be negative - * because Java supports in Java 7 only signed long values.

    - * - *

    Java 7: To mitigate the problem of the non - * existent unsigned long tpye, values should be encapsulated using - * {@link java.math.BigInteger} to reflect the correct value. The correct - * behavior is guaranteed if {@code 2^64} is added to negative values.

    - * - *

    Java 8: In Java 8 the value should be treated as - * unsigned long using provided methods of type {@link Long}.

    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family - * @param property to be fetched. - * - * @return numerical property value - * - * @throws RocksDBException if an error happens in the underlying native code. - */ - public long getLongProperty( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, - final String property) throws RocksDBException { - return getLongProperty(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - property, property.length()); - } - - /** - * Reset internal stats for DB and all column families. - * - * Note this doesn't reset {@link Options#statistics()} as it is not - * owned by DB. - * - * @throws RocksDBException if an error occurs whilst reseting the stats - */ - public void resetStats() throws RocksDBException { - resetStats(nativeHandle_); - } - - /** - *

    Return sum of the getLongProperty of all the column families

    - * - *

    Note: As the returned property is of type - * {@code uint64_t} on C++ side the returning value can be negative - * because Java supports in Java 7 only signed long values.

    - * - *

    Java 7: To mitigate the problem of the non - * existent unsigned long tpye, values should be encapsulated using - * {@link java.math.BigInteger} to reflect the correct value. The correct - * behavior is guaranteed if {@code 2^64} is added to negative values.

    - * - *

    Java 8: In Java 8 the value should be treated as - * unsigned long using provided methods of type {@link Long}.

    - * - * @param property to be fetched. - * - * @return numerical property value - * - * @throws RocksDBException if an error happens in the underlying native code. - */ - public long getAggregatedLongProperty(final String property) - throws RocksDBException { - return getAggregatedLongProperty(nativeHandle_, property, - property.length()); - } - - /** - * Get the approximate file system space used by keys in each range. - * - * Note that the returned sizes measure file system space usage, so - * if the user data compresses by a factor of ten, the returned - * sizes will be one-tenth the size of the corresponding user data size. - * - * If {@code sizeApproximationFlags} defines whether the returned size - * should include the recently written data in the mem-tables (if - * the mem-table type supports it), data serialized to disk, or both. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family - * @param ranges the ranges over which to approximate sizes - * @param sizeApproximationFlags flags to determine what to include in the - * approximation. - * - * @return the sizes - */ - public long[] getApproximateSizes( - /*@Nullable*/ final ColumnFamilyHandle columnFamilyHandle, - final List ranges, - final SizeApproximationFlag... sizeApproximationFlags) { - - byte flags = 0x0; - for (final SizeApproximationFlag sizeApproximationFlag - : sizeApproximationFlags) { - flags |= sizeApproximationFlag.getValue(); - } - - return getApproximateSizes(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - toRangeSliceHandles(ranges), flags); - } - - /** - * Get the approximate file system space used by keys in each range for - * the default column family. - * - * Note that the returned sizes measure file system space usage, so - * if the user data compresses by a factor of ten, the returned - * sizes will be one-tenth the size of the corresponding user data size. - * - * If {@code sizeApproximationFlags} defines whether the returned size - * should include the recently written data in the mem-tables (if - * the mem-table type supports it), data serialized to disk, or both. - * - * @param ranges the ranges over which to approximate sizes - * @param sizeApproximationFlags flags to determine what to include in the - * approximation. - * - * @return the sizes. - */ - public long[] getApproximateSizes(final List ranges, - final SizeApproximationFlag... sizeApproximationFlags) { - return getApproximateSizes(null, ranges, sizeApproximationFlags); - } - - public static class CountAndSize { - public final long count; - public final long size; - - public CountAndSize(final long count, final long size) { - this.count = count; - this.size = size; - } - } - - /** - * This method is similar to - * {@link #getApproximateSizes(ColumnFamilyHandle, List, SizeApproximationFlag...)}, - * except that it returns approximate number of records and size in memtables. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family - * @param range the ranges over which to get the memtable stats - * - * @return the count and size for the range - */ - public CountAndSize getApproximateMemTableStats( - /*@Nullable*/ final ColumnFamilyHandle columnFamilyHandle, - final Range range) { - final long[] result = getApproximateMemTableStats(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - range.start.getNativeHandle(), - range.limit.getNativeHandle()); - return new CountAndSize(result[0], result[1]); - } - - /** - * This method is similar to - * {@link #getApproximateSizes(ColumnFamilyHandle, List, SizeApproximationFlag...)}, - * except that it returns approximate number of records and size in memtables. - * - * @param range the ranges over which to get the memtable stats - * - * @return the count and size for the range - */ - public CountAndSize getApproximateMemTableStats( - final Range range) { - return getApproximateMemTableStats(null, range); - } - - /** - *

    Range compaction of database.

    - *

    Note: After the database has been compacted, - * all data will have been pushed down to the last level containing - * any data.

    - * - *

    See also

    - *
      - *
    • {@link #compactRange(byte[], byte[])}
    • - *
    - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void compactRange() throws RocksDBException { - compactRange(null); - } - - /** - *

    Range compaction of column family.

    - *

    Note: After the database has been compacted, - * all data will have been pushed down to the last level containing - * any data.

    - * - *

    See also

    - *
      - *
    • - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} - *
    • - *
    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family. - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void compactRange( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) - throws RocksDBException { - compactRange(nativeHandle_, null, -1, null, -1, 0, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - *

    Range compaction of database.

    - *

    Note: After the database has been compacted, - * all data will have been pushed down to the last level containing - * any data.

    - * - *

    See also

    - *
      - *
    • {@link #compactRange()}
    • - *
    - * - * @param begin start of key range (included in range) - * @param end end of key range (excluded from range) - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void compactRange(final byte[] begin, final byte[] end) - throws RocksDBException { - compactRange(null, begin, end); - } - - /** - *

    Range compaction of column family.

    - *

    Note: After the database has been compacted, - * all data will have been pushed down to the last level containing - * any data.

    - * - *

    See also

    - *
      - *
    • {@link #compactRange(ColumnFamilyHandle)}
    • - *
    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family. - * @param begin start of key range (included in range) - * @param end end of key range (excluded from range) - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void compactRange( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, - final byte[] begin, final byte[] end) throws RocksDBException { - compactRange(nativeHandle_, - begin, begin == null ? -1 : begin.length, - end, end == null ? -1 : end.length, - 0, columnFamilyHandle == null ? 0: columnFamilyHandle.nativeHandle_); - } - - /** - *

    Range compaction of column family.

    - *

    Note: After the database has been compacted, - * all data will have been pushed down to the last level containing - * any data.

    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance. - * @param begin start of key range (included in range) - * @param end end of key range (excluded from range) - * @param compactRangeOptions options for the compaction - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void compactRange( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, - final byte[] begin, final byte[] end, - final CompactRangeOptions compactRangeOptions) throws RocksDBException { - compactRange(nativeHandle_, - begin, begin == null ? -1 : begin.length, - end, end == null ? -1 : end.length, - compactRangeOptions.nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - * Change the options for the column family handle. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family. - * @param mutableColumnFamilyOptions the options. - * - * @throws RocksDBException if an error occurs whilst setting the options - */ - public void setOptions( - /* @Nullable */final ColumnFamilyHandle columnFamilyHandle, - final MutableColumnFamilyOptions mutableColumnFamilyOptions) - throws RocksDBException { - setOptions(nativeHandle_, columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - mutableColumnFamilyOptions.getKeys(), mutableColumnFamilyOptions.getValues()); - } - - /** - * Get the options for the column family handle - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance, or null for the default column family. - * - * @return the options parsed from the options string return by RocksDB - * - * @throws RocksDBException if an error occurs while getting the options string, or parsing the - * resulting options string into options - */ - public MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder getOptions( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) throws RocksDBException { - String optionsString = getOptions( - nativeHandle_, columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - return MutableColumnFamilyOptions.parse(optionsString, true); - } - - /** - * Default column family options - * - * @return the options parsed from the options string return by RocksDB - * - * @throws RocksDBException if an error occurs while getting the options string, or parsing the - * resulting options string into options - */ - public MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder getOptions() - throws RocksDBException { - return getOptions(null); - } - - /** - * Get the database options - * - * @return the DB options parsed from the options string return by RocksDB - * - * @throws RocksDBException if an error occurs while getting the options string, or parsing the - * resulting options string into options - */ - public MutableDBOptions.MutableDBOptionsBuilder getDBOptions() throws RocksDBException { - String optionsString = getDBOptions(nativeHandle_); - return MutableDBOptions.parse(optionsString, true); - } - - /** - * Change the options for the default column family handle. - * - * @param mutableColumnFamilyOptions the options. - * - * @throws RocksDBException if an error occurs whilst setting the options - */ - public void setOptions( - final MutableColumnFamilyOptions mutableColumnFamilyOptions) - throws RocksDBException { - setOptions(null, mutableColumnFamilyOptions); - } - - /** - * Set the options for the column family handle. - * - * @param mutableDBoptions the options. - * - * @throws RocksDBException if an error occurs whilst setting the options - */ - public void setDBOptions(final MutableDBOptions mutableDBoptions) - throws RocksDBException { - setDBOptions(nativeHandle_, - mutableDBoptions.getKeys(), - mutableDBoptions.getValues()); - } - - /** - * Takes a list of files specified by file names and - * compacts them to the specified level. - * - * Note that the behavior is different from - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} - * in that CompactFiles() performs the compaction job using the CURRENT - * thread. - * - * @param compactionOptions compaction options - * @param inputFileNames the name of the files to compact - * @param outputLevel the level to which they should be compacted - * @param outputPathId the id of the output path, or -1 - * @param compactionJobInfo the compaction job info, this parameter - * will be updated with the info from compacting the files, - * can just be null if you don't need it. - * - * @return the list of compacted files - * - * @throws RocksDBException if an error occurs during compaction - */ - public List compactFiles( - final CompactionOptions compactionOptions, - final List inputFileNames, - final int outputLevel, - final int outputPathId, - /* @Nullable */ final CompactionJobInfo compactionJobInfo) - throws RocksDBException { - return compactFiles(compactionOptions, null, inputFileNames, outputLevel, - outputPathId, compactionJobInfo); - } - - /** - * Takes a list of files specified by file names and - * compacts them to the specified level. - * - * Note that the behavior is different from - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} - * in that CompactFiles() performs the compaction job using the CURRENT - * thread. - * - * @param compactionOptions compaction options - * @param columnFamilyHandle columnFamilyHandle, or null for the - * default column family - * @param inputFileNames the name of the files to compact - * @param outputLevel the level to which they should be compacted - * @param outputPathId the id of the output path, or -1 - * @param compactionJobInfo the compaction job info, this parameter - * will be updated with the info from compacting the files, - * can just be null if you don't need it. - * - * @return the list of compacted files - * - * @throws RocksDBException if an error occurs during compaction - */ - public List compactFiles( - final CompactionOptions compactionOptions, - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, - final List inputFileNames, - final int outputLevel, - final int outputPathId, - /* @Nullable */ final CompactionJobInfo compactionJobInfo) - throws RocksDBException { - return Arrays.asList(compactFiles(nativeHandle_, compactionOptions.nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - inputFileNames.toArray(new String[0]), - outputLevel, - outputPathId, - compactionJobInfo == null ? 0 : compactionJobInfo.nativeHandle_)); - } - - /** - * This function will cancel all currently running background processes. - * - * @param wait if true, wait for all background work to be cancelled before - * returning. - * - */ - public void cancelAllBackgroundWork(boolean wait) { - cancelAllBackgroundWork(nativeHandle_, wait); - } - - /** - * This function will wait until all currently running background processes - * finish. After it returns, no background process will be run until - * {@link #continueBackgroundWork()} is called - * - * @throws RocksDBException if an error occurs when pausing background work - */ - public void pauseBackgroundWork() throws RocksDBException { - pauseBackgroundWork(nativeHandle_); - } - - /** - * Resumes background work which was suspended by - * previously calling {@link #pauseBackgroundWork()} - * - * @throws RocksDBException if an error occurs when resuming background work - */ - public void continueBackgroundWork() throws RocksDBException { - continueBackgroundWork(nativeHandle_); - } - - /** - * Enable automatic compactions for the given column - * families if they were previously disabled. - * - * The function will first set the - * {@link ColumnFamilyOptions#disableAutoCompactions()} option for each - * column family to false, after which it will schedule a flush/compaction. - * - * NOTE: Setting disableAutoCompactions to 'false' through - * {@link #setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} - * does NOT schedule a flush/compaction afterwards, and only changes the - * parameter itself within the column family option. - * - * @param columnFamilyHandles the column family handles - * - * @throws RocksDBException if an error occurs whilst enabling auto-compaction - */ - public void enableAutoCompaction( - final List columnFamilyHandles) - throws RocksDBException { - enableAutoCompaction(nativeHandle_, - toNativeHandleList(columnFamilyHandles)); - } - - /** - * Number of levels used for this DB. - * - * @return the number of levels - */ - public int numberLevels() { - return numberLevels(null); - } - - /** - * Number of levels used for a column family in this DB. - * - * @param columnFamilyHandle the column family handle, or null - * for the default column family - * - * @return the number of levels - */ - public int numberLevels(/* @Nullable */final ColumnFamilyHandle columnFamilyHandle) { - return numberLevels(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - * Maximum level to which a new compacted memtable is pushed if it - * does not create overlap. - * - * @return the maximum level - */ - public int maxMemCompactionLevel() { - return maxMemCompactionLevel(null); - } - - /** - * Maximum level to which a new compacted memtable is pushed if it - * does not create overlap. - * - * @param columnFamilyHandle the column family handle - * - * @return the maximum level - */ - public int maxMemCompactionLevel( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) { - return maxMemCompactionLevel(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - * Number of files in level-0 that would stop writes. - * - * @return the number of files - */ - public int level0StopWriteTrigger() { - return level0StopWriteTrigger(null); - } - - /** - * Number of files in level-0 that would stop writes. - * - * @param columnFamilyHandle the column family handle - * - * @return the number of files - */ - public int level0StopWriteTrigger( - /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) { - return level0StopWriteTrigger(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - * Get DB name -- the exact same name that was provided as an argument to - * as path to {@link #open(Options, String)}. - * - * @return the DB name - */ - public String getName() { - return getName(nativeHandle_); - } - - /** - * Get the Env object from the DB - * - * @return the env - */ - public Env getEnv() { - final long envHandle = getEnv(nativeHandle_); - if (envHandle == Env.getDefault().nativeHandle_) { - return Env.getDefault(); - } else { - final Env env = new RocksEnv(envHandle); - env.disOwnNativeHandle(); // we do not own the Env! - return env; - } - } - - /** - *

    Flush all memory table data.

    - * - *

    Note: it must be ensured that the FlushOptions instance - * is not GC'ed before this method finishes. If the wait parameter is - * set to false, flush processing is asynchronous.

    - * - * @param flushOptions {@link org.rocksdb.FlushOptions} instance. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void flush(final FlushOptions flushOptions) - throws RocksDBException { - flush(flushOptions, (List) null); - } - - /** - *

    Flush all memory table data.

    - * - *

    Note: it must be ensured that the FlushOptions instance - * is not GC'ed before this method finishes. If the wait parameter is - * set to false, flush processing is asynchronous.

    - * - * @param flushOptions {@link org.rocksdb.FlushOptions} instance. - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void flush(final FlushOptions flushOptions, - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) - throws RocksDBException { - flush(flushOptions, - columnFamilyHandle == null ? null : Arrays.asList(columnFamilyHandle)); - } - - /** - * Flushes multiple column families. - * - * If atomic flush is not enabled, this is equivalent to calling - * {@link #flush(FlushOptions, ColumnFamilyHandle)} multiple times. - * - * If atomic flush is enabled, this will flush all column families - * specified up to the latest sequence number at the time when flush is - * requested. - * - * @param flushOptions {@link org.rocksdb.FlushOptions} instance. - * @param columnFamilyHandles column family handles. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public void flush(final FlushOptions flushOptions, - /* @Nullable */ final List columnFamilyHandles) - throws RocksDBException { - flush(nativeHandle_, flushOptions.nativeHandle_, - toNativeHandleList(columnFamilyHandles)); - } - - /** - * Flush the WAL memory buffer to the file. If {@code sync} is true, - * it calls {@link #syncWal()} afterwards. - * - * @param sync true to also fsync to disk. - * - * @throws RocksDBException if an error occurs whilst flushing - */ - public void flushWal(final boolean sync) throws RocksDBException { - flushWal(nativeHandle_, sync); - } - - /** - * Sync the WAL. - * - * Note that {@link #write(WriteOptions, WriteBatch)} followed by - * {@link #syncWal()} is not exactly the same as - * {@link #write(WriteOptions, WriteBatch)} with - * {@link WriteOptions#sync()} set to true; In the latter case the changes - * won't be visible until the sync is done. - * - * Currently only works if {@link Options#allowMmapWrites()} is set to false. - * - * @throws RocksDBException if an error occurs whilst syncing - */ - public void syncWal() throws RocksDBException { - syncWal(nativeHandle_); - } - - /** - *

    The sequence number of the most recent transaction.

    - * - * @return sequence number of the most - * recent transaction. - */ - public long getLatestSequenceNumber() { - return getLatestSequenceNumber(nativeHandle_); - } - - /** - *

    Prevent file deletions. Compactions will continue to occur, - * but no obsolete files will be deleted. Calling this multiple - * times have the same effect as calling it once.

    - * - * @throws RocksDBException thrown if operation was not performed - * successfully. - */ - public void disableFileDeletions() throws RocksDBException { - disableFileDeletions(nativeHandle_); - } - - /** - *

    Allow compactions to delete obsolete files. - * If force == true, the call to EnableFileDeletions() - * will guarantee that file deletions are enabled after - * the call, even if DisableFileDeletions() was called - * multiple times before.

    - * - *

    If force == false, EnableFileDeletions will only - * enable file deletion after it's been called at least - * as many times as DisableFileDeletions(), enabling - * the two methods to be called by two threads - * concurrently without synchronization - * -- i.e., file deletions will be enabled only after both - * threads call EnableFileDeletions()

    - * - * @param force boolean value described above. - * - * @throws RocksDBException thrown if operation was not performed - * successfully. - */ - public void enableFileDeletions(final boolean force) - throws RocksDBException { - enableFileDeletions(nativeHandle_, force); - } - - public static class LiveFiles { - /** - * The valid size of the manifest file. The manifest file is an ever growing - * file, but only the portion specified here is valid for this snapshot. - */ - public final long manifestFileSize; - - /** - * The files are relative to the {@link #getName()} and are not - * absolute paths. Despite being relative paths, the file names begin - * with "/". - */ - public final List files; - - LiveFiles(final long manifestFileSize, final List files) { - this.manifestFileSize = manifestFileSize; - this.files = files; - } - } - - /** - * Retrieve the list of all files in the database after flushing the memtable. - * - * See {@link #getLiveFiles(boolean)}. - * - * @return the live files - * - * @throws RocksDBException if an error occurs whilst retrieving the list - * of live files - */ - public LiveFiles getLiveFiles() throws RocksDBException { - return getLiveFiles(true); - } - - /** - * Retrieve the list of all files in the database. - * - * In case you have multiple column families, even if {@code flushMemtable} - * is true, you still need to call {@link #getSortedWalFiles()} - * after {@link #getLiveFiles(boolean)} to compensate for new data that - * arrived to already-flushed column families while other column families - * were flushing. - * - * NOTE: Calling {@link #getLiveFiles(boolean)} followed by - * {@link #getSortedWalFiles()} can generate a lossless backup. - * - * @param flushMemtable set to true to flush before recoding the live - * files. Setting to false is useful when we don't want to wait for flush - * which may have to wait for compaction to complete taking an - * indeterminate time. - * - * @return the live files - * - * @throws RocksDBException if an error occurs whilst retrieving the list - * of live files - */ - public LiveFiles getLiveFiles(final boolean flushMemtable) - throws RocksDBException { - final String[] result = getLiveFiles(nativeHandle_, flushMemtable); - if (result == null) { - return null; - } - final String[] files = Arrays.copyOf(result, result.length - 1); - final long manifestFileSize = Long.parseLong(result[result.length - 1]); - - return new LiveFiles(manifestFileSize, Arrays.asList(files)); - } - - /** - * Retrieve the sorted list of all wal files with earliest file first. - * - * @return the log files - * - * @throws RocksDBException if an error occurs whilst retrieving the list - * of sorted WAL files - */ - public List getSortedWalFiles() throws RocksDBException { - final LogFile[] logFiles = getSortedWalFiles(nativeHandle_); - return Arrays.asList(logFiles); - } - - /** - *

    Returns an iterator that is positioned at a write-batch containing - * seq_number. If the sequence number is non existent, it returns an iterator - * at the first available seq_no after the requested seq_no.

    - * - *

    Must set WAL_ttl_seconds or WAL_size_limit_MB to large values to - * use this api, else the WAL files will get - * cleared aggressively and the iterator might keep getting invalid before - * an update is read.

    - * - * @param sequenceNumber sequence number offset - * - * @return {@link org.rocksdb.TransactionLogIterator} instance. - * - * @throws org.rocksdb.RocksDBException if iterator cannot be retrieved - * from native-side. - */ - public TransactionLogIterator getUpdatesSince(final long sequenceNumber) - throws RocksDBException { - return new TransactionLogIterator( - getUpdatesSince(nativeHandle_, sequenceNumber)); - } - - /** - * Delete the file name from the db directory and update the internal state to - * reflect that. Supports deletion of sst and log files only. 'name' must be - * path relative to the db directory. eg. 000001.sst, /archive/000003.log - * - * @param name the file name - * - * @throws RocksDBException if an error occurs whilst deleting the file - */ - public void deleteFile(final String name) throws RocksDBException { - deleteFile(nativeHandle_, name); - } - - /** - * Gets a list of all table files metadata. - * - * @return table files metadata. - */ - public List getLiveFilesMetaData() { - return Arrays.asList(getLiveFilesMetaData(nativeHandle_)); - } - - /** - * Obtains the meta data of the specified column family of the DB. - * - * @param columnFamilyHandle the column family - * - * @return the column family metadata - */ - public ColumnFamilyMetaData getColumnFamilyMetaData( - /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) { - return getColumnFamilyMetaData(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - * Obtains the meta data of the default column family of the DB. - * - * @return the column family metadata - */ - public ColumnFamilyMetaData getColumnFamilyMetaData() { - return getColumnFamilyMetaData(null); - } - - /** - * ingestExternalFile will load a list of external SST files (1) into the DB - * We will try to find the lowest possible level that the file can fit in, and - * ingest the file into this level (2). A file that have a key range that - * overlap with the memtable key range will require us to Flush the memtable - * first before ingesting the file. - * - * (1) External SST files can be created using {@link SstFileWriter} - * (2) We will try to ingest the files to the lowest possible level - * even if the file compression doesn't match the level compression - * - * @param filePathList The list of files to ingest - * @param ingestExternalFileOptions the options for the ingestion - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void ingestExternalFile(final List filePathList, - final IngestExternalFileOptions ingestExternalFileOptions) - throws RocksDBException { - ingestExternalFile(nativeHandle_, getDefaultColumnFamily().nativeHandle_, - filePathList.toArray(new String[0]), - filePathList.size(), ingestExternalFileOptions.nativeHandle_); - } - - /** - * ingestExternalFile will load a list of external SST files (1) into the DB - * We will try to find the lowest possible level that the file can fit in, and - * ingest the file into this level (2). A file that have a key range that - * overlap with the memtable key range will require us to Flush the memtable - * first before ingesting the file. - * - * (1) External SST files can be created using {@link SstFileWriter} - * (2) We will try to ingest the files to the lowest possible level - * even if the file compression doesn't match the level compression - * - * @param columnFamilyHandle The column family for the ingested files - * @param filePathList The list of files to ingest - * @param ingestExternalFileOptions the options for the ingestion - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void ingestExternalFile(final ColumnFamilyHandle columnFamilyHandle, - final List filePathList, - final IngestExternalFileOptions ingestExternalFileOptions) - throws RocksDBException { - ingestExternalFile(nativeHandle_, columnFamilyHandle.nativeHandle_, - filePathList.toArray(new String[0]), - filePathList.size(), ingestExternalFileOptions.nativeHandle_); - } - - /** - * Verify checksum - * - * @throws RocksDBException if the checksum is not valid - */ - public void verifyChecksum() throws RocksDBException { - verifyChecksum(nativeHandle_); - } - - /** - * Gets the handle for the default column family - * - * @return The handle of the default column family - */ - public ColumnFamilyHandle getDefaultColumnFamily() { - final ColumnFamilyHandle cfHandle = new ColumnFamilyHandle(this, - getDefaultColumnFamily(nativeHandle_)); - cfHandle.disOwnNativeHandle(); - return cfHandle; - } - - /** - * Get the properties of all tables. - * - * @param columnFamilyHandle the column family handle, or null for the default - * column family. - * - * @return the properties - * - * @throws RocksDBException if an error occurs whilst getting the properties - */ - public Map getPropertiesOfAllTables( - /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) - throws RocksDBException { - return getPropertiesOfAllTables(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - } - - /** - * Get the properties of all tables in the default column family. - * - * @return the properties - * - * @throws RocksDBException if an error occurs whilst getting the properties - */ - public Map getPropertiesOfAllTables() - throws RocksDBException { - return getPropertiesOfAllTables(null); - } - - /** - * Get the properties of tables in range. - * - * @param columnFamilyHandle the column family handle, or null for the default - * column family. - * @param ranges the ranges over which to get the table properties - * - * @return the properties - * - * @throws RocksDBException if an error occurs whilst getting the properties - */ - public Map getPropertiesOfTablesInRange( - /* @Nullable */final ColumnFamilyHandle columnFamilyHandle, - final List ranges) throws RocksDBException { - return getPropertiesOfTablesInRange(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - toRangeSliceHandles(ranges)); - } - - /** - * Get the properties of tables in range for the default column family. - * - * @param ranges the ranges over which to get the table properties - * - * @return the properties - * - * @throws RocksDBException if an error occurs whilst getting the properties - */ - public Map getPropertiesOfTablesInRange( - final List ranges) throws RocksDBException { - return getPropertiesOfTablesInRange(null, ranges); - } - - /** - * Suggest the range to compact. - * - * @param columnFamilyHandle the column family handle, or null for the default - * column family. - * - * @return the suggested range. - * - * @throws RocksDBException if an error occurs whilst suggesting the range - */ - public Range suggestCompactRange( - /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) - throws RocksDBException { - final long[] rangeSliceHandles = suggestCompactRange(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); - return new Range(new Slice(rangeSliceHandles[0]), - new Slice(rangeSliceHandles[1])); - } - - /** - * Suggest the range to compact for the default column family. - * - * @return the suggested range. - * - * @throws RocksDBException if an error occurs whilst suggesting the range - */ - public Range suggestCompactRange() - throws RocksDBException { - return suggestCompactRange(null); - } - - /** - * Promote L0. - * - * @param columnFamilyHandle the column family handle, - * or null for the default column family. - * @param targetLevel the target level for L0 - * - * @throws RocksDBException if an error occurs whilst promoting L0 - */ - public void promoteL0( - /* @Nullable */final ColumnFamilyHandle columnFamilyHandle, - final int targetLevel) throws RocksDBException { - promoteL0(nativeHandle_, - columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, - targetLevel); - } - - /** - * Promote L0 for the default column family. - * - * @param targetLevel the target level for L0 - * - * @throws RocksDBException if an error occurs whilst promoting L0 - */ - public void promoteL0(final int targetLevel) - throws RocksDBException { - promoteL0(null, targetLevel); - } - - /** - * Trace DB operations. - * - * Use {@link #endTrace()} to stop tracing. - * - * @param traceOptions the options - * @param traceWriter the trace writer - * - * @throws RocksDBException if an error occurs whilst starting the trace - */ - public void startTrace(final TraceOptions traceOptions, - final AbstractTraceWriter traceWriter) throws RocksDBException { - startTrace(nativeHandle_, traceOptions.getMaxTraceFileSize(), - traceWriter.nativeHandle_); - /** - * NOTE: {@link #startTrace(long, long, long) transfers the ownership - * from Java to C++, so we must disown the native handle here. - */ - traceWriter.disOwnNativeHandle(); - } - - /** - * Stop tracing DB operations. - * - * See {@link #startTrace(TraceOptions, AbstractTraceWriter)} - * - * @throws RocksDBException if an error occurs whilst ending the trace - */ - public void endTrace() throws RocksDBException { - endTrace(nativeHandle_); - } - - /** - * Make the secondary instance catch up with the primary by tailing and - * replaying the MANIFEST and WAL of the primary. - * Column families created by the primary after the secondary instance starts - * will be ignored unless the secondary instance closes and restarts with the - * newly created column families. - * Column families that exist before secondary instance starts and dropped by - * the primary afterwards will be marked as dropped. However, as long as the - * secondary instance does not delete the corresponding column family - * handles, the data of the column family is still accessible to the - * secondary. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void tryCatchUpWithPrimary() throws RocksDBException { - tryCatchUpWithPrimary(nativeHandle_); - } - - /** - * Delete files in multiple ranges at once. - * Delete files in a lot of ranges one at a time can be slow, use this API for - * better performance in that case. - * - * @param columnFamily - The column family for operation (null for default) - * @param includeEnd - Whether ranges should include end - * @param ranges - pairs of ranges (from1, to1, from2, to2, ...) - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void deleteFilesInRanges(final ColumnFamilyHandle columnFamily, - final List ranges, final boolean includeEnd) - throws RocksDBException { - if (ranges.size() == 0) { - return; - } - if ((ranges.size() % 2) != 0) { - throw new IllegalArgumentException("Ranges size needs to be multiple of 2 " - + "(from1, to1, from2, to2, ...), but is " + ranges.size()); - } - - final byte[][] rangesArray = ranges.toArray(new byte[ranges.size()][]); - - deleteFilesInRanges(nativeHandle_, columnFamily == null ? 0 : columnFamily.nativeHandle_, - rangesArray, includeEnd); - } - - /** - * Static method to destroy the contents of the specified database. - * Be very careful using this method. - * - * @param path the path to the Rocksdb database. - * @param options {@link org.rocksdb.Options} instance. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public static void destroyDB(final String path, final Options options) - throws RocksDBException { - destroyDB(path, options.nativeHandle_); - } - - private /* @Nullable */ long[] toNativeHandleList( - /* @Nullable */ final List objectList) { - if (objectList == null) { - return null; - } - final int len = objectList.size(); - final long[] handleList = new long[len]; - for (int i = 0; i < len; i++) { - handleList[i] = objectList.get(i).nativeHandle_; - } - return handleList; - } - - private static long[] toRangeSliceHandles(final List ranges) { - final long rangeSliceHandles[] = new long [ranges.size() * 2]; - for (int i = 0, j = 0; i < ranges.size(); i++) { - final Range range = ranges.get(i); - rangeSliceHandles[j++] = range.start.getNativeHandle(); - rangeSliceHandles[j++] = range.limit.getNativeHandle(); - } - return rangeSliceHandles; - } - - protected void storeOptionsInstance(DBOptionsInterface options) { - options_ = options; - } - - private static void checkBounds(int offset, int len, int size) { - if ((offset | len | (offset + len) | (size - (offset + len))) < 0) { - throw new IndexOutOfBoundsException(String.format("offset(%d), len(%d), size(%d)", offset, len, size)); - } - } - - private static int computeCapacityHint(final int estimatedNumberOfItems) { - // Default load factor for HashMap is 0.75, so N * 1.5 will be at the load - // limit. We add +1 for a buffer. - return (int)Math.ceil(estimatedNumberOfItems * 1.5 + 1.0); - } - - // native methods - private native static long open(final long optionsHandle, - final String path) throws RocksDBException; - - /** - * @param optionsHandle Native handle pointing to an Options object - * @param path The directory path for the database files - * @param columnFamilyNames An array of column family names - * @param columnFamilyOptions An array of native handles pointing to - * ColumnFamilyOptions objects - * - * @return An array of native handles, [0] is the handle of the RocksDB object - * [1..1+n] are handles of the ColumnFamilyReferences - * - * @throws RocksDBException thrown if the database could not be opened - */ - private native static long[] open(final long optionsHandle, - final String path, final byte[][] columnFamilyNames, - final long[] columnFamilyOptions) throws RocksDBException; - - private native static long openROnly(final long optionsHandle, final String path, - final boolean errorIfWalFileExists) throws RocksDBException; - - /** - * @param optionsHandle Native handle pointing to an Options object - * @param path The directory path for the database files - * @param columnFamilyNames An array of column family names - * @param columnFamilyOptions An array of native handles pointing to - * ColumnFamilyOptions objects - * - * @return An array of native handles, [0] is the handle of the RocksDB object - * [1..1+n] are handles of the ColumnFamilyReferences - * - * @throws RocksDBException thrown if the database could not be opened - */ - private native static long[] openROnly(final long optionsHandle, final String path, - final byte[][] columnFamilyNames, final long[] columnFamilyOptions, - final boolean errorIfWalFileExists) throws RocksDBException; - - private native static long openAsSecondary(final long optionsHandle, final String path, - final String secondaryPath) throws RocksDBException; - - private native static long[] openAsSecondary(final long optionsHandle, final String path, - final String secondaryPath, final byte[][] columnFamilyNames, - final long[] columnFamilyOptions) throws RocksDBException; - - @Override protected native void disposeInternal(final long handle); - - private native static void closeDatabase(final long handle) - throws RocksDBException; - private native static byte[][] listColumnFamilies(final long optionsHandle, - final String path) throws RocksDBException; - private native long createColumnFamily(final long handle, - final byte[] columnFamilyName, final int columnFamilyNamelen, - final long columnFamilyOptions) throws RocksDBException; - private native long[] createColumnFamilies(final long handle, - final long columnFamilyOptionsHandle, final byte[][] columnFamilyNames) - throws RocksDBException; - private native long[] createColumnFamilies(final long handle, - final long columnFamilyOptionsHandles[], final byte[][] columnFamilyNames) - throws RocksDBException; - private native void dropColumnFamily( - final long handle, final long cfHandle) throws RocksDBException; - private native void dropColumnFamilies(final long handle, - final long[] cfHandles) throws RocksDBException; - private native void put(final long handle, final byte[] key, - final int keyOffset, final int keyLength, final byte[] value, - final int valueOffset, int valueLength) throws RocksDBException; - private native void put(final long handle, final byte[] key, final int keyOffset, - final int keyLength, final byte[] value, final int valueOffset, - final int valueLength, final long cfHandle) throws RocksDBException; - private native void put(final long handle, final long writeOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final byte[] value, final int valueOffset, final int valueLength) - throws RocksDBException; - private native void put(final long handle, final long writeOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final byte[] value, final int valueOffset, final int valueLength, - final long cfHandle) throws RocksDBException; - private native void delete(final long handle, final byte[] key, - final int keyOffset, final int keyLength) throws RocksDBException; - private native void delete(final long handle, final byte[] key, - final int keyOffset, final int keyLength, final long cfHandle) - throws RocksDBException; - private native void delete(final long handle, final long writeOptHandle, - final byte[] key, final int keyOffset, final int keyLength) - throws RocksDBException; - private native void delete(final long handle, final long writeOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final long cfHandle) throws RocksDBException; - private native void singleDelete( - final long handle, final byte[] key, final int keyLen) - throws RocksDBException; - private native void singleDelete( - final long handle, final byte[] key, final int keyLen, - final long cfHandle) throws RocksDBException; - private native void singleDelete( - final long handle, final long writeOptHandle, final byte[] key, - final int keyLen) throws RocksDBException; - private native void singleDelete( - final long handle, final long writeOptHandle, - final byte[] key, final int keyLen, final long cfHandle) - throws RocksDBException; - private native void deleteRange(final long handle, final byte[] beginKey, - final int beginKeyOffset, final int beginKeyLength, final byte[] endKey, - final int endKeyOffset, final int endKeyLength) throws RocksDBException; - private native void deleteRange(final long handle, final byte[] beginKey, - final int beginKeyOffset, final int beginKeyLength, final byte[] endKey, - final int endKeyOffset, final int endKeyLength, final long cfHandle) - throws RocksDBException; - private native void deleteRange(final long handle, final long writeOptHandle, - final byte[] beginKey, final int beginKeyOffset, final int beginKeyLength, - final byte[] endKey, final int endKeyOffset, final int endKeyLength) - throws RocksDBException; - private native void deleteRange( - final long handle, final long writeOptHandle, final byte[] beginKey, - final int beginKeyOffset, final int beginKeyLength, final byte[] endKey, - final int endKeyOffset, final int endKeyLength, final long cfHandle) - throws RocksDBException; - private native void merge(final long handle, final byte[] key, - final int keyOffset, final int keyLength, final byte[] value, - final int valueOffset, final int valueLength) throws RocksDBException; - private native void merge(final long handle, final byte[] key, - final int keyOffset, final int keyLength, final byte[] value, - final int valueOffset, final int valueLength, final long cfHandle) - throws RocksDBException; - private native void merge(final long handle, final long writeOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final byte[] value, final int valueOffset, final int valueLength) - throws RocksDBException; - private native void merge(final long handle, final long writeOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final byte[] value, final int valueOffset, final int valueLength, - final long cfHandle) throws RocksDBException; - private native void write0(final long handle, final long writeOptHandle, - final long wbHandle) throws RocksDBException; - private native void write1(final long handle, final long writeOptHandle, - final long wbwiHandle) throws RocksDBException; - private native int get(final long handle, final byte[] key, - final int keyOffset, final int keyLength, final byte[] value, - final int valueOffset, final int valueLength) throws RocksDBException; - private native int get(final long handle, final byte[] key, - final int keyOffset, final int keyLength, byte[] value, - final int valueOffset, final int valueLength, final long cfHandle) - throws RocksDBException; - private native int get(final long handle, final long readOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final byte[] value, final int valueOffset, final int valueLength) - throws RocksDBException; - private native int get(final long handle, final long readOptHandle, - final byte[] key, final int keyOffset, final int keyLength, - final byte[] value, final int valueOffset, final int valueLength, - final long cfHandle) throws RocksDBException; - private native byte[] get(final long handle, byte[] key, final int keyOffset, - final int keyLength) throws RocksDBException; - private native byte[] get(final long handle, final byte[] key, - final int keyOffset, final int keyLength, final long cfHandle) - throws RocksDBException; - private native byte[] get(final long handle, final long readOptHandle, - final byte[] key, final int keyOffset, final int keyLength) - throws RocksDBException; - private native byte[] get(final long handle, - final long readOptHandle, final byte[] key, final int keyOffset, - final int keyLength, final long cfHandle) throws RocksDBException; - private native byte[][] multiGet(final long dbHandle, final byte[][] keys, - final int[] keyOffsets, final int[] keyLengths); - private native byte[][] multiGet(final long dbHandle, final byte[][] keys, - final int[] keyOffsets, final int[] keyLengths, - final long[] columnFamilyHandles); - private native byte[][] multiGet(final long dbHandle, final long rOptHandle, - final byte[][] keys, final int[] keyOffsets, final int[] keyLengths); - private native byte[][] multiGet(final long dbHandle, final long rOptHandle, - final byte[][] keys, final int[] keyOffsets, final int[] keyLengths, - final long[] columnFamilyHandles); - - private native void multiGet(final long dbHandle, final long rOptHandle, - final long[] columnFamilyHandles, final ByteBuffer[] keysArray, final int[] keyOffsets, - final int[] keyLengths, final ByteBuffer[] valuesArray, final int[] valuesSizeArray, - final Status[] statusArray); - - private native boolean keyMayExist( - final long handle, final long cfHandle, final long readOptHandle, - final byte[] key, final int keyOffset, final int keyLength); - private native byte[][] keyMayExistFoundValue( - final long handle, final long cfHandle, final long readOptHandle, - final byte[] key, final int keyOffset, final int keyLength); - private native void putDirect(long handle, long writeOptHandle, ByteBuffer key, int keyOffset, - int keyLength, ByteBuffer value, int valueOffset, int valueLength, long cfHandle) - throws RocksDBException; - private native long iterator(final long handle); - private native long iterator(final long handle, final long readOptHandle); - private native long iteratorCF(final long handle, final long cfHandle); - private native long iteratorCF(final long handle, final long cfHandle, - final long readOptHandle); - private native long[] iterators(final long handle, - final long[] columnFamilyHandles, final long readOptHandle) - throws RocksDBException; - private native long getSnapshot(final long nativeHandle); - private native void releaseSnapshot( - final long nativeHandle, final long snapshotHandle); - private native String getProperty(final long nativeHandle, - final long cfHandle, final String property, final int propertyLength) - throws RocksDBException; - private native Map getMapProperty(final long nativeHandle, - final long cfHandle, final String property, final int propertyLength) - throws RocksDBException; - private native int getDirect(long handle, long readOptHandle, ByteBuffer key, int keyOffset, - int keyLength, ByteBuffer value, int valueOffset, int valueLength, long cfHandle) - throws RocksDBException; - private native boolean keyMayExistDirect(final long handle, final long cfHhandle, - final long readOptHandle, final ByteBuffer key, final int keyOffset, final int keyLength); - private native int[] keyMayExistDirectFoundValue(final long handle, final long cfHhandle, - final long readOptHandle, final ByteBuffer key, final int keyOffset, final int keyLength, - final ByteBuffer value, final int valueOffset, final int valueLength); - private native void deleteDirect(long handle, long optHandle, ByteBuffer key, int keyOffset, - int keyLength, long cfHandle) throws RocksDBException; - private native long getLongProperty(final long nativeHandle, - final long cfHandle, final String property, final int propertyLength) - throws RocksDBException; - private native void resetStats(final long nativeHandle) - throws RocksDBException; - private native long getAggregatedLongProperty(final long nativeHandle, - final String property, int propertyLength) throws RocksDBException; - private native long[] getApproximateSizes(final long nativeHandle, - final long columnFamilyHandle, final long[] rangeSliceHandles, - final byte includeFlags); - private native long[] getApproximateMemTableStats(final long nativeHandle, - final long columnFamilyHandle, final long rangeStartSliceHandle, - final long rangeLimitSliceHandle); - private native void compactRange(final long handle, - /* @Nullable */ final byte[] begin, final int beginLen, - /* @Nullable */ final byte[] end, final int endLen, - final long compactRangeOptHandle, final long cfHandle) - throws RocksDBException; - private native void setOptions(final long handle, final long cfHandle, - final String[] keys, final String[] values) throws RocksDBException; - private native String getOptions(final long handle, final long cfHandle); - private native void setDBOptions(final long handle, - final String[] keys, final String[] values) throws RocksDBException; - private native String getDBOptions(final long handle); - private native String[] compactFiles(final long handle, - final long compactionOptionsHandle, - final long columnFamilyHandle, - final String[] inputFileNames, - final int outputLevel, - final int outputPathId, - final long compactionJobInfoHandle) throws RocksDBException; - private native void cancelAllBackgroundWork(final long handle, - final boolean wait); - private native void pauseBackgroundWork(final long handle) - throws RocksDBException; - private native void continueBackgroundWork(final long handle) - throws RocksDBException; - private native void enableAutoCompaction(final long handle, - final long[] columnFamilyHandles) throws RocksDBException; - private native int numberLevels(final long handle, - final long columnFamilyHandle); - private native int maxMemCompactionLevel(final long handle, - final long columnFamilyHandle); - private native int level0StopWriteTrigger(final long handle, - final long columnFamilyHandle); - private native String getName(final long handle); - private native long getEnv(final long handle); - private native void flush(final long handle, final long flushOptHandle, - /* @Nullable */ final long[] cfHandles) throws RocksDBException; - private native void flushWal(final long handle, final boolean sync) - throws RocksDBException; - private native void syncWal(final long handle) throws RocksDBException; - private native long getLatestSequenceNumber(final long handle); - private native void disableFileDeletions(long handle) throws RocksDBException; - private native void enableFileDeletions(long handle, boolean force) - throws RocksDBException; - private native String[] getLiveFiles(final long handle, - final boolean flushMemtable) throws RocksDBException; - private native LogFile[] getSortedWalFiles(final long handle) - throws RocksDBException; - private native long getUpdatesSince(final long handle, - final long sequenceNumber) throws RocksDBException; - private native void deleteFile(final long handle, final String name) - throws RocksDBException; - private native LiveFileMetaData[] getLiveFilesMetaData(final long handle); - private native ColumnFamilyMetaData getColumnFamilyMetaData( - final long handle, final long columnFamilyHandle); - private native void ingestExternalFile(final long handle, - final long columnFamilyHandle, final String[] filePathList, - final int filePathListLen, final long ingestExternalFileOptionsHandle) - throws RocksDBException; - private native void verifyChecksum(final long handle) throws RocksDBException; - private native long getDefaultColumnFamily(final long handle); - private native Map getPropertiesOfAllTables( - final long handle, final long columnFamilyHandle) throws RocksDBException; - private native Map getPropertiesOfTablesInRange( - final long handle, final long columnFamilyHandle, - final long[] rangeSliceHandles); - private native long[] suggestCompactRange(final long handle, - final long columnFamilyHandle) throws RocksDBException; - private native void promoteL0(final long handle, - final long columnFamilyHandle, final int tragetLevel) - throws RocksDBException; - private native void startTrace(final long handle, final long maxTraceFileSize, - final long traceWriterHandle) throws RocksDBException; - private native void endTrace(final long handle) throws RocksDBException; - private native void tryCatchUpWithPrimary(final long handle) throws RocksDBException; - private native void deleteFilesInRanges(long handle, long cfHandle, final byte[][] ranges, - boolean include_end) throws RocksDBException; - - private native static void destroyDB(final String path, - final long optionsHandle) throws RocksDBException; - - private native static int version(); - - protected DBOptionsInterface options_; - private static Version version; - - public static class Version { - private final byte major; - private final byte minor; - private final byte patch; - - public Version(final byte major, final byte minor, final byte patch) { - this.major = major; - this.minor = minor; - this.patch = patch; - } - - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getPatch() { - return patch; - } - - @Override - public String toString() { - return getMajor() + "." + getMinor() + "." + getPatch(); - } - - private static Version fromEncodedVersion(int encodedVersion) { - final byte patch = (byte) (encodedVersion & 0xff); - encodedVersion >>= 8; - final byte minor = (byte) (encodedVersion & 0xff); - encodedVersion >>= 8; - final byte major = (byte) (encodedVersion & 0xff); - - return new Version(major, minor, patch); - } - } -} diff --git a/java/src/main/java/org/rocksdb/RocksDBException.java b/java/src/main/java/org/rocksdb/RocksDBException.java deleted file mode 100644 index 8b035f458..000000000 --- a/java/src/main/java/org/rocksdb/RocksDBException.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * A RocksDBException encapsulates the error of an operation. This exception - * type is used to describe an internal error from the c++ rocksdb library. - */ -public class RocksDBException extends Exception { - - /* @Nullable */ private final Status status; - - /** - * The private construct used by a set of public static factory method. - * - * @param msg the specified error message. - */ - public RocksDBException(final String msg) { - this(msg, null); - } - - public RocksDBException(final String msg, final Status status) { - super(msg); - this.status = status; - } - - public RocksDBException(final Status status) { - super(status.getState() != null ? status.getState() - : status.getCodeString()); - this.status = status; - } - - /** - * Get the status returned from RocksDB - * - * @return The status reported by RocksDB, or null if no status is available - */ - public Status getStatus() { - return status; - } -} diff --git a/java/src/main/java/org/rocksdb/RocksEnv.java b/java/src/main/java/org/rocksdb/RocksEnv.java deleted file mode 100644 index b3681d77d..000000000 --- a/java/src/main/java/org/rocksdb/RocksEnv.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - *

    A RocksEnv is an interface used by the rocksdb implementation to access - * operating system functionality like the filesystem etc.

    - * - *

    All Env implementations are safe for concurrent access from - * multiple threads without any external synchronization.

    - */ -public class RocksEnv extends Env { - - /** - *

    Package-private constructor that uses the specified native handle - * to construct a RocksEnv.

    - * - *

    Note that the ownership of the input handle - * belongs to the caller, and the newly created RocksEnv will not take - * the ownership of the input handle. As a result, calling - * {@code dispose()} of the created RocksEnv will be no-op.

    - */ - RocksEnv(final long handle) { - super(handle); - } - - @Override - protected native final void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/RocksIterator.java b/java/src/main/java/org/rocksdb/RocksIterator.java deleted file mode 100644 index 20e56d2eb..000000000 --- a/java/src/main/java/org/rocksdb/RocksIterator.java +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - *

    An iterator that yields a sequence of key/value pairs from a source. - * Multiple implementations are provided by this library. - * In particular, iterators are provided - * to access the contents of a Table or a DB.

    - * - *

    Multiple threads can invoke const methods on an RocksIterator without - * external synchronization, but if any of the threads may call a - * non-const method, all threads accessing the same RocksIterator must use - * external synchronization.

    - * - * @see org.rocksdb.RocksObject - */ -public class RocksIterator extends AbstractRocksIterator { - protected RocksIterator(final RocksDB rocksDB, final long nativeHandle) { - super(rocksDB, nativeHandle); - } - - /** - *

    Return the key for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: {@link #isValid()}

    - * - * @return key for the current entry. - */ - public byte[] key() { - assert(isOwningHandle()); - return key0(nativeHandle_); - } - - /** - *

    Return the key for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: {@link #isValid()}

    - * - * @param key the out-value to receive the retrieved key. - * It is using position and limit. Limit is set according to key size. - * Supports direct buffer only. - * @return The size of the actual key. If the return key is greater than the - * length of {@code key}, then it indicates that the size of the - * input buffer {@code key} is insufficient and partial result will - * be returned. - */ - public int key(final ByteBuffer key) { - assert isOwningHandle(); - final int result; - if (key.isDirect()) { - result = keyDirect0(nativeHandle_, key, key.position(), key.remaining()); - } else { - assert key.hasArray(); - result = keyByteArray0( - nativeHandle_, key.array(), key.arrayOffset() + key.position(), key.remaining()); - } - key.limit(Math.min(key.position() + result, key.limit())); - return result; - } - - /** - *

    Return the value for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: !AtEnd() && !AtStart()

    - * @return value for the current entry. - */ - public byte[] value() { - assert(isOwningHandle()); - return value0(nativeHandle_); - } - - /** - *

    Return the value for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: {@link #isValid()}

    - * - * @param value the out-value to receive the retrieved value. - * It is using position and limit. Limit is set according to value size. - * Supports direct buffer only. - * @return The size of the actual value. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. - */ - public int value(final ByteBuffer value) { - assert isOwningHandle(); - final int result; - if (value.isDirect()) { - result = valueDirect0(nativeHandle_, value, value.position(), value.remaining()); - } else { - assert value.hasArray(); - result = valueByteArray0( - nativeHandle_, value.array(), value.arrayOffset() + value.position(), value.remaining()); - } - value.limit(Math.min(value.position() + result, value.limit())); - return result; - } - - @Override protected final native void disposeInternal(final long handle); - @Override final native boolean isValid0(long handle); - @Override final native void seekToFirst0(long handle); - @Override final native void seekToLast0(long handle); - @Override final native void next0(long handle); - @Override final native void prev0(long handle); - @Override final native void refresh0(long handle); - @Override final native void seek0(long handle, byte[] target, int targetLen); - @Override final native void seekForPrev0(long handle, byte[] target, int targetLen); - @Override - final native void seekDirect0(long handle, ByteBuffer target, int targetOffset, int targetLen); - @Override - final native void seekByteArray0(long handle, byte[] target, int targetOffset, int targetLen); - @Override - final native void seekForPrevDirect0( - long handle, ByteBuffer target, int targetOffset, int targetLen); - @Override - final native void seekForPrevByteArray0( - long handle, byte[] target, int targetOffset, int targetLen); - @Override final native void status0(long handle) throws RocksDBException; - - private native byte[] key0(long handle); - private native byte[] value0(long handle); - private native int keyDirect0(long handle, ByteBuffer buffer, int bufferOffset, int bufferLen); - private native int keyByteArray0(long handle, byte[] array, int arrayOffset, int arrayLen); - private native int valueDirect0(long handle, ByteBuffer buffer, int bufferOffset, int bufferLen); - private native int valueByteArray0(long handle, byte[] array, int arrayOffset, int arrayLen); -} diff --git a/java/src/main/java/org/rocksdb/RocksIteratorInterface.java b/java/src/main/java/org/rocksdb/RocksIteratorInterface.java deleted file mode 100644 index 819c21c2c..000000000 --- a/java/src/main/java/org/rocksdb/RocksIteratorInterface.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - *

    Defines the interface for an Iterator which provides - * access to data one entry at a time. Multiple implementations - * are provided by this library. In particular, iterators are provided - * to access the contents of a DB and Write Batch.

    - * - *

    Multiple threads can invoke const methods on an RocksIterator without - * external synchronization, but if any of the threads may call a - * non-const method, all threads accessing the same RocksIterator must use - * external synchronization.

    - * - * @see org.rocksdb.RocksObject - */ -public interface RocksIteratorInterface { - - /** - *

    An iterator is either positioned at an entry, or - * not valid. This method returns true if the iterator is valid.

    - * - * @return true if iterator is valid. - */ - boolean isValid(); - - /** - *

    Position at the first entry in the source. The iterator is Valid() - * after this call if the source is not empty.

    - */ - void seekToFirst(); - - /** - *

    Position at the last entry in the source. The iterator is - * valid after this call if the source is not empty.

    - */ - void seekToLast(); - - /** - *

    Position at the first entry in the source whose key is at or - * past target.

    - * - *

    The iterator is valid after this call if the source contains - * a key that comes at or past target.

    - * - * @param target byte array describing a key or a - * key prefix to seek for. - */ - void seek(byte[] target); - - /** - *

    Position at the first entry in the source whose key is that or - * before target.

    - * - *

    The iterator is valid after this call if the source contains - * a key that comes at or before target.

    - * - * @param target byte array describing a key or a - * key prefix to seek for. - */ - void seekForPrev(byte[] target); - - /** - *

    Position at the first entry in the source whose key is that or - * past target.

    - * - *

    The iterator is valid after this call if the source contains - * a key that comes at or past target.

    - * - * @param target byte array describing a key or a - * key prefix to seek for. Supports direct buffer only. - */ - void seek(ByteBuffer target); - - /** - *

    Position at the last key that is less than or equal to the target key.

    - * - *

    The iterator is valid after this call if the source contains - * a key that comes at or past target.

    - * - * @param target byte array describing a key or a - * key prefix to seek for. Supports direct buffer only. - */ - void seekForPrev(ByteBuffer target); - - /** - *

    Moves to the next entry in the source. After this call, Valid() is - * true if the iterator was not positioned at the last entry in the source.

    - * - *

    REQUIRES: {@link #isValid()}

    - */ - void next(); - - /** - *

    Moves to the previous entry in the source. After this call, Valid() is - * true if the iterator was not positioned at the first entry in source.

    - * - *

    REQUIRES: {@link #isValid()}

    - */ - void prev(); - - /** - *

    If an error has occurred, return it. Else return an ok status. - * If non-blocking IO is requested and this operation cannot be - * satisfied without doing some IO, then this returns Status::Incomplete().

    - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - void status() throws RocksDBException; - - /** - *

    If supported, renew the iterator to represent the latest state. The iterator will be - * invalidated after the call. Not supported if {@link ReadOptions#setSnapshot(Snapshot)} was - * specified when creating the iterator.

    - * - * @throws RocksDBException thrown if the operation is not supported or an error happens in the - * underlying native library - */ - void refresh() throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/RocksMemEnv.java b/java/src/main/java/org/rocksdb/RocksMemEnv.java deleted file mode 100644 index 39a6f6e1c..000000000 --- a/java/src/main/java/org/rocksdb/RocksMemEnv.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Memory environment. - */ -//TODO(AR) rename to MemEnv -public class RocksMemEnv extends Env { - - /** - *

    Creates a new environment that stores its data - * in memory and delegates all non-file-storage tasks to - * {@code baseEnv}.

    - * - *

    The caller must delete the result when it is - * no longer needed.

    - * - * @param baseEnv the base environment, - * must remain live while the result is in use. - */ - public RocksMemEnv(final Env baseEnv) { - super(createMemEnv(baseEnv.nativeHandle_)); - } - - private static native long createMemEnv(final long baseEnvHandle); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/RocksMutableObject.java b/java/src/main/java/org/rocksdb/RocksMutableObject.java deleted file mode 100644 index e92289dc0..000000000 --- a/java/src/main/java/org/rocksdb/RocksMutableObject.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2016, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * RocksMutableObject is an implementation of {@link AbstractNativeReference} - * whose reference to the underlying native C++ object can change. - * - *

    The use of {@code RocksMutableObject} should be kept to a minimum, as it - * has synchronization overheads and introduces complexity. Instead it is - * recommended to use {@link RocksObject} where possible.

    - */ -public abstract class RocksMutableObject extends AbstractNativeReference { - - /** - * An mutable reference to the value of the C++ pointer pointing to some - * underlying native RocksDB C++ object. - */ - private long nativeHandle_; - private boolean owningHandle_; - - protected RocksMutableObject() { - } - - protected RocksMutableObject(final long nativeHandle) { - this.nativeHandle_ = nativeHandle; - this.owningHandle_ = true; - } - - /** - * Closes the existing handle, and changes the handle to the new handle - * - * @param newNativeHandle The C++ pointer to the new native object - * @param owningNativeHandle true if we own the new native object - */ - public synchronized void resetNativeHandle(final long newNativeHandle, - final boolean owningNativeHandle) { - close(); - setNativeHandle(newNativeHandle, owningNativeHandle); - } - - /** - * Sets the handle (C++ pointer) of the underlying C++ native object - * - * @param nativeHandle The C++ pointer to the native object - * @param owningNativeHandle true if we own the native object - */ - public synchronized void setNativeHandle(final long nativeHandle, - final boolean owningNativeHandle) { - this.nativeHandle_ = nativeHandle; - this.owningHandle_ = owningNativeHandle; - } - - @Override - protected synchronized boolean isOwningHandle() { - return this.owningHandle_; - } - - /** - * Gets the value of the C++ pointer pointing to the underlying - * native C++ object - * - * @return the pointer value for the native object - */ - protected synchronized long getNativeHandle() { - assert (this.nativeHandle_ != 0); - return this.nativeHandle_; - } - - @Override - public synchronized final void close() { - if (isOwningHandle()) { - disposeInternal(); - this.owningHandle_ = false; - this.nativeHandle_ = 0; - } - } - - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - protected abstract void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/RocksObject.java b/java/src/main/java/org/rocksdb/RocksObject.java deleted file mode 100644 index f07e1018a..000000000 --- a/java/src/main/java/org/rocksdb/RocksObject.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * RocksObject is an implementation of {@link AbstractNativeReference} which - * has an immutable and therefore thread-safe reference to the underlying - * native C++ RocksDB object. - *

    - * RocksObject is the base-class of almost all RocksDB classes that have a - * pointer to some underlying native C++ {@code rocksdb} object.

    - *

    - * The use of {@code RocksObject} should always be preferred over - * {@link RocksMutableObject}.

    - */ -public abstract class RocksObject extends AbstractImmutableNativeReference { - - /** - * An immutable reference to the value of the C++ pointer pointing to some - * underlying native RocksDB C++ object. - */ - protected final long nativeHandle_; - - protected RocksObject(final long nativeHandle) { - super(true); - this.nativeHandle_ = nativeHandle; - } - - /** - * Deletes underlying C++ object pointer. - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - - protected abstract void disposeInternal(final long handle); - - public long getNativeHandle() { - return nativeHandle_; - } -} diff --git a/java/src/main/java/org/rocksdb/SanityLevel.java b/java/src/main/java/org/rocksdb/SanityLevel.java deleted file mode 100644 index 30568c363..000000000 --- a/java/src/main/java/org/rocksdb/SanityLevel.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum SanityLevel { - NONE((byte) 0x0), - LOOSELY_COMPATIBLE((byte) 0x1), - EXACT_MATCH((byte) 0xFF); - - private final byte value; - - SanityLevel(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value. - */ - byte getValue() { - return value; - } - - /** - * Get the SanityLevel from the internal representation value. - * - * @param value the internal representation value. - * - * @return the SanityLevel - * - * @throws IllegalArgumentException if the value does not match a - * SanityLevel - */ - static SanityLevel fromValue(final byte value) throws IllegalArgumentException { - for (final SanityLevel level : SanityLevel.values()) { - if (level.value == value) { - return level; - } - } - throw new IllegalArgumentException("Unknown value for SanityLevel: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/SizeApproximationFlag.java b/java/src/main/java/org/rocksdb/SizeApproximationFlag.java deleted file mode 100644 index fe3c2dd05..000000000 --- a/java/src/main/java/org/rocksdb/SizeApproximationFlag.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import java.util.List; - -/** - * Flags for - * {@link RocksDB#getApproximateSizes(ColumnFamilyHandle, List, SizeApproximationFlag...)} - * that specify whether memtable stats should be included, - * or file stats approximation or both. - */ -public enum SizeApproximationFlag { - NONE((byte)0x0), - INCLUDE_MEMTABLES((byte)0x1), - INCLUDE_FILES((byte)0x2); - - private final byte value; - - SizeApproximationFlag(final byte value) { - this.value = value; - } - - /** - * Get the internal byte representation. - * - * @return the internal representation. - */ - byte getValue() { - return value; - } -} diff --git a/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java b/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java deleted file mode 100644 index e2c1b97d8..000000000 --- a/java/src/main/java/org/rocksdb/SkipListMemTableConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * The config for skip-list memtable representation. - */ -public class SkipListMemTableConfig extends MemTableConfig { - - public static final long DEFAULT_LOOKAHEAD = 0; - - /** - * SkipListMemTableConfig constructor - */ - public SkipListMemTableConfig() { - lookahead_ = DEFAULT_LOOKAHEAD; - } - - /** - * Sets lookahead for SkipList - * - * @param lookahead If non-zero, each iterator's seek operation - * will start the search from the previously visited record - * (doing at most 'lookahead' steps). This is an - * optimization for the access pattern including many - * seeks with consecutive keys. - * @return the current instance of SkipListMemTableConfig - */ - public SkipListMemTableConfig setLookahead(final long lookahead) { - lookahead_ = lookahead; - return this; - } - - /** - * Returns the currently set lookahead value. - * - * @return lookahead value - */ - public long lookahead() { - return lookahead_; - } - - - @Override protected long newMemTableFactoryHandle() { - return newMemTableFactoryHandle0(lookahead_); - } - - private native long newMemTableFactoryHandle0(long lookahead) - throws IllegalArgumentException; - - private long lookahead_; -} diff --git a/java/src/main/java/org/rocksdb/Slice.java b/java/src/main/java/org/rocksdb/Slice.java deleted file mode 100644 index 50d9f7652..000000000 --- a/java/src/main/java/org/rocksdb/Slice.java +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - *

    Base class for slices which will receive - * byte[] based access to the underlying data.

    - * - *

    byte[] backed slices typically perform better with - * small keys and values. When using larger keys and - * values consider using {@link org.rocksdb.DirectSlice}

    - */ -public class Slice extends AbstractSlice { - - /** - * Indicates whether we have to free the memory pointed to by the Slice - */ - private volatile boolean cleared; - private volatile long internalBufferOffset = 0; - - /** - *

    Called from JNI to construct a new Java Slice - * without an underlying C++ object set - * at creation time.

    - * - *

    Note: You should be aware that - * {@see org.rocksdb.RocksObject#disOwnNativeHandle()} is intentionally - * called from the default Slice constructor, and that it is marked as - * private. This is so that developers cannot construct their own default - * Slice objects (at present). As developers cannot construct their own - * Slice objects through this, they are not creating underlying C++ Slice - * objects, and so there is nothing to free (dispose) from Java.

    - */ - @SuppressWarnings("unused") - private Slice() { - super(); - } - - /** - *

    Package-private Slice constructor which is used to construct - * Slice instances from C++ side. As the reference to this - * object is also managed from C++ side the handle will be disowned.

    - * - * @param nativeHandle address of native instance. - */ - Slice(final long nativeHandle) { - this(nativeHandle, false); - } - - /** - *

    Package-private Slice constructor which is used to construct - * Slice instances using a handle.

    - * - * @param nativeHandle address of native instance. - * @param owningNativeHandle true if the Java side owns the memory pointed to - * by this reference, false if ownership belongs to the C++ side - */ - Slice(final long nativeHandle, final boolean owningNativeHandle) { - super(); - setNativeHandle(nativeHandle, owningNativeHandle); - } - - /** - *

    Constructs a slice where the data is taken from - * a String.

    - * - * @param str String value. - */ - public Slice(final String str) { - super(createNewSliceFromString(str)); - } - - /** - *

    Constructs a slice where the data is a copy of - * the byte array from a specific offset.

    - * - * @param data byte array. - * @param offset offset within the byte array. - */ - public Slice(final byte[] data, final int offset) { - super(createNewSlice0(data, offset)); - } - - /** - *

    Constructs a slice where the data is a copy of - * the byte array.

    - * - * @param data byte array. - */ - public Slice(final byte[] data) { - super(createNewSlice1(data)); - } - - @Override - public void clear() { - clear0(getNativeHandle(), !cleared, internalBufferOffset); - cleared = true; - } - - @Override - public void removePrefix(final int n) { - removePrefix0(getNativeHandle(), n); - this.internalBufferOffset += n; - } - - /** - *

    Deletes underlying C++ slice pointer - * and any buffered data.

    - * - *

    - * Note that this function should be called only after all - * RocksDB instances referencing the slice are closed. - * Otherwise an undefined behavior will occur.

    - */ - @Override - protected void disposeInternal() { - final long nativeHandle = getNativeHandle(); - if(!cleared) { - disposeInternalBuf(nativeHandle, internalBufferOffset); - } - super.disposeInternal(nativeHandle); - } - - @Override protected final native byte[] data0(long handle); - private native static long createNewSlice0(final byte[] data, - final int length); - private native static long createNewSlice1(final byte[] data); - private native void clear0(long handle, boolean internalBuffer, - long internalBufferOffset); - private native void removePrefix0(long handle, int length); - private native void disposeInternalBuf(final long handle, - long internalBufferOffset); -} diff --git a/java/src/main/java/org/rocksdb/Snapshot.java b/java/src/main/java/org/rocksdb/Snapshot.java deleted file mode 100644 index 39cdf0c2d..000000000 --- a/java/src/main/java/org/rocksdb/Snapshot.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Snapshot of database - */ -public class Snapshot extends RocksObject { - Snapshot(final long nativeHandle) { - super(nativeHandle); - - // The pointer to the snapshot is always released - // by the database instance. - disOwnNativeHandle(); - } - - /** - * Return the associated sequence number; - * - * @return the associated sequence number of - * this snapshot. - */ - public long getSequenceNumber() { - return getSequenceNumber(nativeHandle_); - } - - @Override - protected final void disposeInternal(final long handle) { - /** - * Nothing to release, we never own the pointer for a - * Snapshot. The pointer - * to the snapshot is released by the database - * instance. - */ - } - - private native long getSequenceNumber(long handle); -} diff --git a/java/src/main/java/org/rocksdb/SstFileManager.java b/java/src/main/java/org/rocksdb/SstFileManager.java deleted file mode 100644 index 8805410aa..000000000 --- a/java/src/main/java/org/rocksdb/SstFileManager.java +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Map; - -/** - * SstFileManager is used to track SST files in the DB and control their - * deletion rate. - * - * All SstFileManager public functions are thread-safe. - * - * SstFileManager is not extensible. - */ -//@ThreadSafe -public final class SstFileManager extends RocksObject { - - public static final long RATE_BYTES_PER_SEC_DEFAULT = 0; - public static final boolean DELETE_EXISTING_TRASH_DEFAULT = true; - public static final double MAX_TRASH_DB_RATION_DEFAULT = 0.25; - public static final long BYTES_MAX_DELETE_CHUNK_DEFAULT = 64 * 1024 * 1024; - - /** - * Create a new SstFileManager that can be shared among multiple RocksDB - * instances to track SST file and control there deletion rate. - * - * @param env the environment. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - public SstFileManager(final Env env) throws RocksDBException { - this(env, null); - } - - /** - * Create a new SstFileManager that can be shared among multiple RocksDB - * instances to track SST file and control there deletion rate. - * - * @param env the environment. - * @param logger if not null, the logger will be used to log errors. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - public SstFileManager(final Env env, /*@Nullable*/ final Logger logger) - throws RocksDBException { - this(env, logger, RATE_BYTES_PER_SEC_DEFAULT); - } - - /** - * Create a new SstFileManager that can be shared among multiple RocksDB - * instances to track SST file and control there deletion rate. - * - * @param env the environment. - * @param logger if not null, the logger will be used to log errors. - * - * == Deletion rate limiting specific arguments == - * @param rateBytesPerSec how many bytes should be deleted per second, If - * this value is set to 1024 (1 Kb / sec) and we deleted a file of size - * 4 Kb in 1 second, we will wait for another 3 seconds before we delete - * other files, Set to 0 to disable deletion rate limiting. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - public SstFileManager(final Env env, /*@Nullable*/ final Logger logger, - final long rateBytesPerSec) throws RocksDBException { - this(env, logger, rateBytesPerSec, MAX_TRASH_DB_RATION_DEFAULT); - } - - /** - * Create a new SstFileManager that can be shared among multiple RocksDB - * instances to track SST file and control there deletion rate. - * - * @param env the environment. - * @param logger if not null, the logger will be used to log errors. - * - * == Deletion rate limiting specific arguments == - * @param rateBytesPerSec how many bytes should be deleted per second, If - * this value is set to 1024 (1 Kb / sec) and we deleted a file of size - * 4 Kb in 1 second, we will wait for another 3 seconds before we delete - * other files, Set to 0 to disable deletion rate limiting. - * @param maxTrashDbRatio if the trash size constitutes for more than this - * fraction of the total DB size we will start deleting new files passed - * to DeleteScheduler immediately. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - public SstFileManager(final Env env, /*@Nullable*/ final Logger logger, - final long rateBytesPerSec, final double maxTrashDbRatio) - throws RocksDBException { - this(env, logger, rateBytesPerSec, maxTrashDbRatio, - BYTES_MAX_DELETE_CHUNK_DEFAULT); - } - - /** - * Create a new SstFileManager that can be shared among multiple RocksDB - * instances to track SST file and control there deletion rate. - * - * @param env the environment. - * @param logger if not null, the logger will be used to log errors. - * - * == Deletion rate limiting specific arguments == - * @param rateBytesPerSec how many bytes should be deleted per second, If - * this value is set to 1024 (1 Kb / sec) and we deleted a file of size - * 4 Kb in 1 second, we will wait for another 3 seconds before we delete - * other files, Set to 0 to disable deletion rate limiting. - * @param maxTrashDbRatio if the trash size constitutes for more than this - * fraction of the total DB size we will start deleting new files passed - * to DeleteScheduler immediately. - * @param bytesMaxDeleteChunk if a single file is larger than delete chunk, - * ftruncate the file by this size each time, rather than dropping the whole - * file. 0 means to always delete the whole file. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - public SstFileManager(final Env env, /*@Nullable*/final Logger logger, - final long rateBytesPerSec, final double maxTrashDbRatio, - final long bytesMaxDeleteChunk) throws RocksDBException { - super(newSstFileManager(env.nativeHandle_, - logger != null ? logger.nativeHandle_ : 0, - rateBytesPerSec, maxTrashDbRatio, bytesMaxDeleteChunk)); - } - - - /** - * Update the maximum allowed space that should be used by RocksDB, if - * the total size of the SST files exceeds {@code maxAllowedSpace}, writes to - * RocksDB will fail. - * - * Setting {@code maxAllowedSpace} to 0 will disable this feature; - * maximum allowed space will be infinite (Default value). - * - * @param maxAllowedSpace the maximum allowed space that should be used by - * RocksDB. - */ - public void setMaxAllowedSpaceUsage(final long maxAllowedSpace) { - setMaxAllowedSpaceUsage(nativeHandle_, maxAllowedSpace); - } - - /** - * Set the amount of buffer room each compaction should be able to leave. - * In other words, at its maximum disk space consumption, the compaction - * should still leave {@code compactionBufferSize} available on the disk so - * that other background functions may continue, such as logging and flushing. - * - * @param compactionBufferSize the amount of buffer room each compaction - * should be able to leave. - */ - public void setCompactionBufferSize(final long compactionBufferSize) { - setCompactionBufferSize(nativeHandle_, compactionBufferSize); - } - - /** - * Determines if the total size of SST files exceeded the maximum allowed - * space usage. - * - * @return true when the maximum allows space usage has been exceeded. - */ - public boolean isMaxAllowedSpaceReached() { - return isMaxAllowedSpaceReached(nativeHandle_); - } - - /** - * Determines if the total size of SST files as well as estimated size - * of ongoing compactions exceeds the maximums allowed space usage. - * - * @return true when the total size of SST files as well as estimated size - * of ongoing compactions exceeds the maximums allowed space usage. - */ - public boolean isMaxAllowedSpaceReachedIncludingCompactions() { - return isMaxAllowedSpaceReachedIncludingCompactions(nativeHandle_); - } - - /** - * Get the total size of all tracked files. - * - * @return the total size of all tracked files. - */ - public long getTotalSize() { - return getTotalSize(nativeHandle_); - } - - /** - * Gets all tracked files and their corresponding sizes. - * - * @return a map containing all tracked files and there corresponding sizes. - */ - public Map getTrackedFiles() { - return getTrackedFiles(nativeHandle_); - } - - /** - * Gets the delete rate limit. - * - * @return the delete rate limit (in bytes per second). - */ - public long getDeleteRateBytesPerSecond() { - return getDeleteRateBytesPerSecond(nativeHandle_); - } - - /** - * Set the delete rate limit. - * - * Zero means disable delete rate limiting and delete files immediately. - * - * @param deleteRate the delete rate limit (in bytes per second). - */ - public void setDeleteRateBytesPerSecond(final long deleteRate) { - setDeleteRateBytesPerSecond(nativeHandle_, deleteRate); - } - - /** - * Get the trash/DB size ratio where new files will be deleted immediately. - * - * @return the trash/DB size ratio. - */ - public double getMaxTrashDBRatio() { - return getMaxTrashDBRatio(nativeHandle_); - } - - /** - * Set the trash/DB size ratio where new files will be deleted immediately. - * - * @param ratio the trash/DB size ratio. - */ - public void setMaxTrashDBRatio(final double ratio) { - setMaxTrashDBRatio(nativeHandle_, ratio); - } - - private native static long newSstFileManager(final long handle, - final long logger_handle, final long rateBytesPerSec, - final double maxTrashDbRatio, final long bytesMaxDeleteChunk) - throws RocksDBException; - private native void setMaxAllowedSpaceUsage(final long handle, - final long maxAllowedSpace); - private native void setCompactionBufferSize(final long handle, - final long compactionBufferSize); - private native boolean isMaxAllowedSpaceReached(final long handle); - private native boolean isMaxAllowedSpaceReachedIncludingCompactions( - final long handle); - private native long getTotalSize(final long handle); - private native Map getTrackedFiles(final long handle); - private native long getDeleteRateBytesPerSecond(final long handle); - private native void setDeleteRateBytesPerSecond(final long handle, - final long deleteRate); - private native double getMaxTrashDBRatio(final long handle); - private native void setMaxTrashDBRatio(final long handle, final double ratio); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/SstFileMetaData.java b/java/src/main/java/org/rocksdb/SstFileMetaData.java deleted file mode 100644 index a04d05cb5..000000000 --- a/java/src/main/java/org/rocksdb/SstFileMetaData.java +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The metadata that describes a SST file. - */ -public class SstFileMetaData { - private final String fileName; - private final String path; - private final long size; - private final long smallestSeqno; - private final long largestSeqno; - private final byte[] smallestKey; - private final byte[] largestKey; - private final long numReadsSampled; - private final boolean beingCompacted; - private final long numEntries; - private final long numDeletions; - - /** - * Called from JNI C++ - * - * @param fileName the file name - * @param path the file path - * @param size the size of the file - * @param smallestSeqno the smallest sequence number - * @param largestSeqno the largest sequence number - * @param smallestKey the smallest key - * @param largestKey the largest key - * @param numReadsSampled the number of reads sampled - * @param beingCompacted true if the file is being compacted, false otherwise - * @param numEntries the number of entries - * @param numDeletions the number of deletions - */ - protected SstFileMetaData( - final String fileName, - final String path, - final long size, - final long smallestSeqno, - final long largestSeqno, - final byte[] smallestKey, - final byte[] largestKey, - final long numReadsSampled, - final boolean beingCompacted, - final long numEntries, - final long numDeletions) { - this.fileName = fileName; - this.path = path; - this.size = size; - this.smallestSeqno = smallestSeqno; - this.largestSeqno = largestSeqno; - this.smallestKey = smallestKey; - this.largestKey = largestKey; - this.numReadsSampled = numReadsSampled; - this.beingCompacted = beingCompacted; - this.numEntries = numEntries; - this.numDeletions = numDeletions; - } - - /** - * Get the name of the file. - * - * @return the name of the file. - */ - public String fileName() { - return fileName; - } - - /** - * Get the full path where the file locates. - * - * @return the full path - */ - public String path() { - return path; - } - - /** - * Get the file size in bytes. - * - * @return file size - */ - public long size() { - return size; - } - - /** - * Get the smallest sequence number in file. - * - * @return the smallest sequence number - */ - public long smallestSeqno() { - return smallestSeqno; - } - - /** - * Get the largest sequence number in file. - * - * @return the largest sequence number - */ - public long largestSeqno() { - return largestSeqno; - } - - /** - * Get the smallest user defined key in the file. - * - * @return the smallest user defined key - */ - public byte[] smallestKey() { - return smallestKey; - } - - /** - * Get the largest user defined key in the file. - * - * @return the largest user defined key - */ - public byte[] largestKey() { - return largestKey; - } - - /** - * Get the number of times the file has been read. - * - * @return the number of times the file has been read - */ - public long numReadsSampled() { - return numReadsSampled; - } - - /** - * Returns true if the file is currently being compacted. - * - * @return true if the file is currently being compacted, false otherwise. - */ - public boolean beingCompacted() { - return beingCompacted; - } - - /** - * Get the number of entries. - * - * @return the number of entries. - */ - public long numEntries() { - return numEntries; - } - - /** - * Get the number of deletions. - * - * @return the number of deletions. - */ - public long numDeletions() { - return numDeletions; - } -} diff --git a/java/src/main/java/org/rocksdb/SstFileReader.java b/java/src/main/java/org/rocksdb/SstFileReader.java deleted file mode 100644 index bb1e94ee0..000000000 --- a/java/src/main/java/org/rocksdb/SstFileReader.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class SstFileReader extends RocksObject { - static { - RocksDB.loadLibrary(); - } - - public SstFileReader(final Options options) { - super(newSstFileReader(options.nativeHandle_)); - } - - /** - * Returns an iterator that will iterate on all keys in the default - * column family including both keys in the DB and uncommitted keys in this - * transaction. - * - * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is read - * from the DB but will NOT change which keys are read from this transaction - * (the keys in this transaction do not yet belong to any snapshot and will be - * fetched regardless). - * - * Caller is responsible for deleting the returned Iterator. - * - * @param readOptions Read options. - * - * @return instance of iterator object. - */ - public SstFileReaderIterator newIterator(final ReadOptions readOptions) { - assert (isOwningHandle()); - long iter = newIterator(nativeHandle_, readOptions.nativeHandle_); - return new SstFileReaderIterator(this, iter); - } - - /** - * Prepare SstFileReader to read a file. - * - * @param filePath the location of file - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void open(final String filePath) throws RocksDBException { - open(nativeHandle_, filePath); - } - - /** - * Verify checksum - * - * @throws RocksDBException if the checksum is not valid - */ - public void verifyChecksum() throws RocksDBException { - verifyChecksum(nativeHandle_); - } - - /** - * Get the properties of the table. - * - * @return the properties - * - * @throws RocksDBException if an error occurs whilst getting the table - * properties - */ - public TableProperties getTableProperties() throws RocksDBException { - return getTableProperties(nativeHandle_); - } - - @Override protected final native void disposeInternal(final long handle); - private native long newIterator(final long handle, final long readOptionsHandle); - - private native void open(final long handle, final String filePath) - throws RocksDBException; - - private native static long newSstFileReader(final long optionsHandle); - private native void verifyChecksum(final long handle) throws RocksDBException; - private native TableProperties getTableProperties(final long handle) - throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/SstFileReaderIterator.java b/java/src/main/java/org/rocksdb/SstFileReaderIterator.java deleted file mode 100644 index a4a08167b..000000000 --- a/java/src/main/java/org/rocksdb/SstFileReaderIterator.java +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - *

    An iterator that yields a sequence of key/value pairs from a source. - * Multiple implementations are provided by this library. - * In particular, iterators are provided - * to access the contents of a Table or a DB.

    - * - *

    Multiple threads can invoke const methods on an RocksIterator without - * external synchronization, but if any of the threads may call a - * non-const method, all threads accessing the same RocksIterator must use - * external synchronization.

    - * - * @see RocksObject - */ -public class SstFileReaderIterator extends AbstractRocksIterator { - protected SstFileReaderIterator(final SstFileReader reader, final long nativeHandle) { - super(reader, nativeHandle); - } - - /** - *

    Return the key for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: {@link #isValid()}

    - * - * @return key for the current entry. - */ - public byte[] key() { - assert (isOwningHandle()); - return key0(nativeHandle_); - } - - /** - *

    Return the key for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: {@link #isValid()}

    - * - * @param key the out-value to receive the retrieved key. - * It is using position and limit. Limit is set according to key size. - * Supports direct buffer only. - * @return The size of the actual key. If the return key is greater than the - * length of {@code key}, then it indicates that the size of the - * input buffer {@code key} is insufficient and partial result will - * be returned. - */ - public int key(final ByteBuffer key) { - assert (isOwningHandle()); - final int result; - if (key.isDirect()) { - result = keyDirect0(nativeHandle_, key, key.position(), key.remaining()); - } else { - result = keyByteArray0( - nativeHandle_, key.array(), key.arrayOffset() + key.position(), key.remaining()); - } - key.limit(Math.min(key.position() + result, key.limit())); - return result; - } - - /** - *

    Return the value for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: !AtEnd() && !AtStart()

    - * @return value for the current entry. - */ - public byte[] value() { - assert (isOwningHandle()); - return value0(nativeHandle_); - } - - /** - *

    Return the value for the current entry. The underlying storage for - * the returned slice is valid only until the next modification of - * the iterator.

    - * - *

    REQUIRES: {@link #isValid()}

    - * - * @param value the out-value to receive the retrieved value. - * It is using position and limit. Limit is set according to value size. - * Supports direct buffer only. - * @return The size of the actual value. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. - */ - public int value(final ByteBuffer value) { - assert (isOwningHandle()); - final int result; - if (value.isDirect()) { - result = valueDirect0(nativeHandle_, value, value.position(), value.remaining()); - } else { - result = valueByteArray0( - nativeHandle_, value.array(), value.arrayOffset() + value.position(), value.remaining()); - } - value.limit(Math.min(value.position() + result, value.limit())); - return result; - } - - @Override protected final native void disposeInternal(final long handle); - @Override final native boolean isValid0(long handle); - @Override final native void seekToFirst0(long handle); - @Override final native void seekToLast0(long handle); - @Override final native void next0(long handle); - @Override final native void prev0(long handle); - @Override final native void refresh0(long handle) throws RocksDBException; - @Override final native void seek0(long handle, byte[] target, int targetLen); - @Override final native void seekForPrev0(long handle, byte[] target, int targetLen); - @Override final native void status0(long handle) throws RocksDBException; - @Override - final native void seekDirect0(long handle, ByteBuffer target, int targetOffset, int targetLen); - @Override - final native void seekForPrevDirect0( - long handle, ByteBuffer target, int targetOffset, int targetLen); - @Override - final native void seekByteArray0( - final long handle, final byte[] target, final int targetOffset, final int targetLen); - @Override - final native void seekForPrevByteArray0( - final long handle, final byte[] target, final int targetOffset, final int targetLen); - - private native byte[] key0(long handle); - private native byte[] value0(long handle); - - private native int keyDirect0(long handle, ByteBuffer buffer, int bufferOffset, int bufferLen); - private native int keyByteArray0(long handle, byte[] buffer, int bufferOffset, int bufferLen); - private native int valueDirect0(long handle, ByteBuffer buffer, int bufferOffset, int bufferLen); - private native int valueByteArray0(long handle, byte[] buffer, int bufferOffset, int bufferLen); -} diff --git a/java/src/main/java/org/rocksdb/SstFileWriter.java b/java/src/main/java/org/rocksdb/SstFileWriter.java deleted file mode 100644 index fe00c1a12..000000000 --- a/java/src/main/java/org/rocksdb/SstFileWriter.java +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * SstFileWriter is used to create sst files that can be added to the - * database later. All keys in files generated by SstFileWriter will have - * sequence number = 0. - */ -public class SstFileWriter extends RocksObject { - static { - RocksDB.loadLibrary(); - } - - /** - * SstFileWriter Constructor. - * - * @param envOptions {@link org.rocksdb.EnvOptions} instance. - * @param options {@link org.rocksdb.Options} instance. - */ - public SstFileWriter(final EnvOptions envOptions, final Options options) { - super(newSstFileWriter( - envOptions.nativeHandle_, options.nativeHandle_)); - } - - /** - * Prepare SstFileWriter to write to a file. - * - * @param filePath the location of file - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void open(final String filePath) throws RocksDBException { - open(nativeHandle_, filePath); - } - - /** - * Add a Put key with value to currently opened file. - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final Slice key, final Slice value) throws RocksDBException { - put(nativeHandle_, key.getNativeHandle(), value.getNativeHandle()); - } - - /** - * Add a Put key with value to currently opened file. - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final DirectSlice key, final DirectSlice value) - throws RocksDBException { - put(nativeHandle_, key.getNativeHandle(), value.getNativeHandle()); - } - - /** - * Add a Put key with value to currently opened file. - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final ByteBuffer key, final ByteBuffer value) throws RocksDBException { - assert key.isDirect() && value.isDirect(); - putDirect(nativeHandle_, key, key.position(), key.remaining(), value, value.position(), - value.remaining()); - key.position(key.limit()); - value.position(value.limit()); - } - - /** - * Add a Put key with value to currently opened file. - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void put(final byte[] key, final byte[] value) throws RocksDBException { - put(nativeHandle_, key, value); - } - - /** - * Add a Merge key with value to currently opened file. - * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final Slice key, final Slice value) - throws RocksDBException { - merge(nativeHandle_, key.getNativeHandle(), value.getNativeHandle()); - } - - /** - * Add a Merge key with value to currently opened file. - * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final byte[] key, final byte[] value) - throws RocksDBException { - merge(nativeHandle_, key, value); - } - - /** - * Add a Merge key with value to currently opened file. - * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void merge(final DirectSlice key, final DirectSlice value) - throws RocksDBException { - merge(nativeHandle_, key.getNativeHandle(), value.getNativeHandle()); - } - - /** - * Add a deletion key to currently opened file. - * - * @param key the specified key to be deleted. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final Slice key) throws RocksDBException { - delete(nativeHandle_, key.getNativeHandle()); - } - - /** - * Add a deletion key to currently opened file. - * - * @param key the specified key to be deleted. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final DirectSlice key) throws RocksDBException { - delete(nativeHandle_, key.getNativeHandle()); - } - - /** - * Add a deletion key to currently opened file. - * - * @param key the specified key to be deleted. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void delete(final byte[] key) throws RocksDBException { - delete(nativeHandle_, key); - } - - /** - * Finish the process and close the sst file. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void finish() throws RocksDBException { - finish(nativeHandle_); - } - - /** - * Return the current file size. - * - * @return the current file size. - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public long fileSize() throws RocksDBException { - return fileSize(nativeHandle_); - } - - private native static long newSstFileWriter( - final long envOptionsHandle, final long optionsHandle, - final long userComparatorHandle, final byte comparatorType); - - private native static long newSstFileWriter(final long envOptionsHandle, - final long optionsHandle); - - private native void open(final long handle, final String filePath) - throws RocksDBException; - - private native void put(final long handle, final long keyHandle, - final long valueHandle) throws RocksDBException; - - private native void put(final long handle, final byte[] key, - final byte[] value) throws RocksDBException; - - private native void putDirect(long handle, ByteBuffer key, int keyOffset, int keyLength, - ByteBuffer value, int valueOffset, int valueLength) throws RocksDBException; - - private native long fileSize(long handle) throws RocksDBException; - - private native void merge(final long handle, final long keyHandle, - final long valueHandle) throws RocksDBException; - - private native void merge(final long handle, final byte[] key, - final byte[] value) throws RocksDBException; - - private native void delete(final long handle, final long keyHandle) - throws RocksDBException; - - private native void delete(final long handle, final byte[] key) - throws RocksDBException; - - private native void finish(final long handle) throws RocksDBException; - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/SstPartitionerFactory.java b/java/src/main/java/org/rocksdb/SstPartitionerFactory.java deleted file mode 100644 index ea6f13565..000000000 --- a/java/src/main/java/org/rocksdb/SstPartitionerFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Handle to factory for SstPartitioner. It is used in {@link ColumnFamilyOptions} - */ -public abstract class SstPartitionerFactory extends RocksObject { - protected SstPartitionerFactory(final long nativeHandle) { - super(nativeHandle); - } -} diff --git a/java/src/main/java/org/rocksdb/SstPartitionerFixedPrefixFactory.java b/java/src/main/java/org/rocksdb/SstPartitionerFixedPrefixFactory.java deleted file mode 100644 index d513c5f15..000000000 --- a/java/src/main/java/org/rocksdb/SstPartitionerFixedPrefixFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Fixed prefix factory. It partitions SST files using fixed prefix of the key. - */ -public class SstPartitionerFixedPrefixFactory extends SstPartitionerFactory { - public SstPartitionerFixedPrefixFactory(long prefixLength) { - super(newSstPartitionerFixedPrefixFactory0(prefixLength)); - } - - private native static long newSstPartitionerFixedPrefixFactory0(long prefixLength); - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/StateType.java b/java/src/main/java/org/rocksdb/StateType.java deleted file mode 100644 index 803456bb2..000000000 --- a/java/src/main/java/org/rocksdb/StateType.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The type used to refer to a thread state. - * - * A state describes lower-level action of a thread - * such as reading / writing a file or waiting for a mutex. - */ -public enum StateType { - STATE_UNKNOWN((byte)0x0), - STATE_MUTEX_WAIT((byte)0x1); - - private final byte value; - - StateType(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value. - */ - byte getValue() { - return value; - } - - /** - * Get the State type from the internal representation value. - * - * @param value the internal representation value. - * - * @return the state type - * - * @throws IllegalArgumentException if the value does not match - * a StateType - */ - static StateType fromValue(final byte value) - throws IllegalArgumentException { - for (final StateType threadType : StateType.values()) { - if (threadType.value == value) { - return threadType; - } - } - throw new IllegalArgumentException( - "Unknown value for StateType: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/Statistics.java b/java/src/main/java/org/rocksdb/Statistics.java deleted file mode 100644 index 0938a6d58..000000000 --- a/java/src/main/java/org/rocksdb/Statistics.java +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.EnumSet; - -/** - * Statistics to analyze the performance of a db. Pointer for statistics object - * is managed by Options class. - */ -public class Statistics extends RocksObject { - - public Statistics() { - super(newStatistics()); - } - - public Statistics(final Statistics otherStatistics) { - super(newStatistics(otherStatistics.nativeHandle_)); - } - - public Statistics(final EnumSet ignoreHistograms) { - super(newStatistics(toArrayValues(ignoreHistograms))); - } - - public Statistics(final EnumSet ignoreHistograms, final Statistics otherStatistics) { - super(newStatistics(toArrayValues(ignoreHistograms), otherStatistics.nativeHandle_)); - } - - /** - * Intentionally package-private. - * - * Used from {@link DBOptions#statistics()} - * - * @param existingStatisticsHandle The C++ pointer to an existing statistics object - */ - Statistics(final long existingStatisticsHandle) { - super(existingStatisticsHandle); - } - - private static byte[] toArrayValues(final EnumSet histogramTypes) { - final byte[] values = new byte[histogramTypes.size()]; - int i = 0; - for(final HistogramType histogramType : histogramTypes) { - values[i++] = histogramType.getValue(); - } - return values; - } - - /** - * Gets the current stats level. - * - * @return The stats level. - */ - public StatsLevel statsLevel() { - return StatsLevel.getStatsLevel(statsLevel(nativeHandle_)); - } - - /** - * Sets the stats level. - * - * @param statsLevel The stats level to set. - */ - public void setStatsLevel(final StatsLevel statsLevel) { - setStatsLevel(nativeHandle_, statsLevel.getValue()); - } - - /** - * Get the count for a ticker. - * - * @param tickerType The ticker to get the count for - * - * @return The count for the ticker - */ - public long getTickerCount(final TickerType tickerType) { - assert(isOwningHandle()); - return getTickerCount(nativeHandle_, tickerType.getValue()); - } - - /** - * Get the count for a ticker and reset the tickers count. - * - * @param tickerType The ticker to get the count for - * - * @return The count for the ticker - */ - public long getAndResetTickerCount(final TickerType tickerType) { - assert(isOwningHandle()); - return getAndResetTickerCount(nativeHandle_, tickerType.getValue()); - } - - /** - * Gets the histogram data for a particular histogram. - * - * @param histogramType The histogram to retrieve the data for - * - * @return The histogram data - */ - public HistogramData getHistogramData(final HistogramType histogramType) { - assert(isOwningHandle()); - return getHistogramData(nativeHandle_, histogramType.getValue()); - } - - /** - * Gets a string representation of a particular histogram. - * - * @param histogramType The histogram to retrieve the data for - * - * @return A string representation of the histogram data - */ - public String getHistogramString(final HistogramType histogramType) { - assert(isOwningHandle()); - return getHistogramString(nativeHandle_, histogramType.getValue()); - } - - /** - * Resets all ticker and histogram stats. - * - * @throws RocksDBException if an error occurs when resetting the statistics. - */ - public void reset() throws RocksDBException { - assert(isOwningHandle()); - reset(nativeHandle_); - } - - /** - * String representation of the statistic object. - */ - @Override - public String toString() { - assert(isOwningHandle()); - return toString(nativeHandle_); - } - - private native static long newStatistics(); - private native static long newStatistics(final long otherStatisticsHandle); - private native static long newStatistics(final byte[] ignoreHistograms); - private native static long newStatistics(final byte[] ignoreHistograms, final long otherStatisticsHandle); - - @Override protected final native void disposeInternal(final long handle); - - private native byte statsLevel(final long handle); - private native void setStatsLevel(final long handle, final byte statsLevel); - private native long getTickerCount(final long handle, final byte tickerType); - private native long getAndResetTickerCount(final long handle, final byte tickerType); - private native HistogramData getHistogramData(final long handle, final byte histogramType); - private native String getHistogramString(final long handle, final byte histogramType); - private native void reset(final long nativeHandle) throws RocksDBException; - private native String toString(final long nativeHandle); -} diff --git a/java/src/main/java/org/rocksdb/StatisticsCollector.java b/java/src/main/java/org/rocksdb/StatisticsCollector.java deleted file mode 100644 index fb3f57150..000000000 --- a/java/src/main/java/org/rocksdb/StatisticsCollector.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; - -/** - *

    Helper class to collect DB statistics periodically at a period specified in - * constructor. Callback function (provided in constructor) is called with - * every statistics collection.

    - * - *

    Caller should call start() to start statistics collection. Shutdown() should - * be called to stop stats collection and should be called before statistics ( - * provided in constructor) reference has been disposed.

    - */ -public class StatisticsCollector { - private final List _statsCollectorInputList; - private final ExecutorService _executorService; - private final int _statsCollectionInterval; - private volatile boolean _isRunning = true; - - /** - * Constructor for statistics collector. - * - * @param statsCollectorInputList List of statistics collector input. - * @param statsCollectionIntervalInMilliSeconds Statistics collection time - * period (specified in milliseconds). - */ - public StatisticsCollector( - final List statsCollectorInputList, - final int statsCollectionIntervalInMilliSeconds) { - _statsCollectorInputList = statsCollectorInputList; - _statsCollectionInterval = statsCollectionIntervalInMilliSeconds; - - _executorService = Executors.newSingleThreadExecutor(); - } - - public void start() { - _executorService.submit(collectStatistics()); - } - - /** - * Shuts down statistics collector. - * - * @param shutdownTimeout Time in milli-seconds to wait for shutdown before - * killing the collection process. - * @throws java.lang.InterruptedException thrown if Threads are interrupted. - */ - public void shutDown(final int shutdownTimeout) throws InterruptedException { - _isRunning = false; - - _executorService.shutdownNow(); - // Wait for collectStatistics runnable to finish so that disposal of - // statistics does not cause any exceptions to be thrown. - _executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS); - } - - private Runnable collectStatistics() { - return new Runnable() { - - @Override - public void run() { - while (_isRunning) { - try { - if(Thread.currentThread().isInterrupted()) { - break; - } - for(final StatsCollectorInput statsCollectorInput : - _statsCollectorInputList) { - Statistics statistics = statsCollectorInput.getStatistics(); - StatisticsCollectorCallback statsCallback = - statsCollectorInput.getCallback(); - - // Collect ticker data - for(final TickerType ticker : TickerType.values()) { - if(ticker != TickerType.TICKER_ENUM_MAX) { - final long tickerValue = statistics.getTickerCount(ticker); - statsCallback.tickerCallback(ticker, tickerValue); - } - } - - // Collect histogram data - for(final HistogramType histogramType : HistogramType.values()) { - if(histogramType != HistogramType.HISTOGRAM_ENUM_MAX) { - final HistogramData histogramData = - statistics.getHistogramData(histogramType); - statsCallback.histogramCallback(histogramType, histogramData); - } - } - } - - Thread.sleep(_statsCollectionInterval); - } - catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - catch (final Exception e) { - throw new RuntimeException("Error while calculating statistics", e); - } - } - } - }; - } -} diff --git a/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java b/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java deleted file mode 100644 index f3785b15f..000000000 --- a/java/src/main/java/org/rocksdb/StatisticsCollectorCallback.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Callback interface provided to StatisticsCollector. - * - * Thread safety: - * StatisticsCollector doesn't make any guarantees about thread safety. - * If the same reference of StatisticsCollectorCallback is passed to multiple - * StatisticsCollector references, then its the responsibility of the - * user to make StatisticsCollectorCallback's implementation thread-safe. - * - */ -public interface StatisticsCollectorCallback { - /** - * Callback function to get ticker values. - * @param tickerType Ticker type. - * @param tickerCount Value of ticker type. - */ - void tickerCallback(TickerType tickerType, long tickerCount); - - /** - * Callback function to get histogram values. - * @param histType Histogram type. - * @param histData Histogram data. - */ - void histogramCallback(HistogramType histType, HistogramData histData); -} diff --git a/java/src/main/java/org/rocksdb/StatsCollectorInput.java b/java/src/main/java/org/rocksdb/StatsCollectorInput.java deleted file mode 100644 index 5bf43ade5..000000000 --- a/java/src/main/java/org/rocksdb/StatsCollectorInput.java +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Contains all information necessary to collect statistics from one instance - * of DB statistics. - */ -public class StatsCollectorInput { - private final Statistics _statistics; - private final StatisticsCollectorCallback _statsCallback; - - /** - * Constructor for StatsCollectorInput. - * - * @param statistics Reference of DB statistics. - * @param statsCallback Reference of statistics callback interface. - */ - public StatsCollectorInput(final Statistics statistics, - final StatisticsCollectorCallback statsCallback) { - _statistics = statistics; - _statsCallback = statsCallback; - } - - public Statistics getStatistics() { - return _statistics; - } - - public StatisticsCollectorCallback getCallback() { - return _statsCallback; - } -} diff --git a/java/src/main/java/org/rocksdb/StatsLevel.java b/java/src/main/java/org/rocksdb/StatsLevel.java deleted file mode 100644 index 58504b84a..000000000 --- a/java/src/main/java/org/rocksdb/StatsLevel.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The level of Statistics to report. - */ -public enum StatsLevel { - /** - * Collect all stats except time inside mutex lock AND time spent on - * compression. - */ - EXCEPT_DETAILED_TIMERS((byte) 0x0), - - /** - * Collect all stats except the counters requiring to get time inside the - * mutex lock. - */ - EXCEPT_TIME_FOR_MUTEX((byte) 0x1), - - /** - * Collect all stats, including measuring duration of mutex operations. - * - * If getting time is expensive on the platform to run, it can - * reduce scalability to more threads, especially for writes. - */ - ALL((byte) 0x2); - - private final byte value; - - StatsLevel(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get StatsLevel by byte value. - * - * @param value byte representation of StatsLevel. - * - * @return {@link org.rocksdb.StatsLevel} instance. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static StatsLevel getStatsLevel(final byte value) { - for (final StatsLevel statsLevel : StatsLevel.values()) { - if (statsLevel.getValue() == value){ - return statsLevel; - } - } - throw new IllegalArgumentException( - "Illegal value provided for StatsLevel."); - } -} diff --git a/java/src/main/java/org/rocksdb/Status.java b/java/src/main/java/org/rocksdb/Status.java deleted file mode 100644 index 033ed3ea1..000000000 --- a/java/src/main/java/org/rocksdb/Status.java +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -/** - * Represents the status returned by a function call in RocksDB. - * - * Currently only used with {@link RocksDBException} when the - * status is not {@link Code#Ok} - */ -public class Status { - private final Code code; - /* @Nullable */ private final SubCode subCode; - /* @Nullable */ private final String state; - - public Status(final Code code, final SubCode subCode, final String state) { - this.code = code; - this.subCode = subCode; - this.state = state; - } - - /** - * Intentionally private as this will be called from JNI - */ - private Status(final byte code, final byte subCode, final String state) { - this.code = Code.getCode(code); - this.subCode = SubCode.getSubCode(subCode); - this.state = state; - } - - public Code getCode() { - return code; - } - - public SubCode getSubCode() { - return subCode; - } - - public String getState() { - return state; - } - - public String getCodeString() { - final StringBuilder builder = new StringBuilder() - .append(code.name()); - if(subCode != null && subCode != SubCode.None) { - builder.append("(") - .append(subCode.name()) - .append(")"); - } - return builder.toString(); - } - - // should stay in sync with /include/rocksdb/status.h:Code and /java/rocksjni/portal.h:toJavaStatusCode - public enum Code { - Ok( (byte)0x0), - NotFound( (byte)0x1), - Corruption( (byte)0x2), - NotSupported( (byte)0x3), - InvalidArgument( (byte)0x4), - IOError( (byte)0x5), - MergeInProgress( (byte)0x6), - Incomplete( (byte)0x7), - ShutdownInProgress( (byte)0x8), - TimedOut( (byte)0x9), - Aborted( (byte)0xA), - Busy( (byte)0xB), - Expired( (byte)0xC), - TryAgain( (byte)0xD), - Undefined( (byte)0x7F); - - private final byte value; - - Code(final byte value) { - this.value = value; - } - - public static Code getCode(final byte value) { - for (final Code code : Code.values()) { - if (code.value == value){ - return code; - } - } - throw new IllegalArgumentException( - "Illegal value provided for Code (" + value + ")."); - } - - /** - * Returns the byte value of the enumerations value. - * - * @return byte representation - */ - public byte getValue() { - return value; - } - } - - // should stay in sync with /include/rocksdb/status.h:SubCode and /java/rocksjni/portal.h:toJavaStatusSubCode - public enum SubCode { - None( (byte)0x0), - MutexTimeout( (byte)0x1), - LockTimeout( (byte)0x2), - LockLimit( (byte)0x3), - NoSpace( (byte)0x4), - Deadlock( (byte)0x5), - StaleFile( (byte)0x6), - MemoryLimit( (byte)0x7), - Undefined( (byte)0x7F); - - private final byte value; - - SubCode(final byte value) { - this.value = value; - } - - public static SubCode getSubCode(final byte value) { - for (final SubCode subCode : SubCode.values()) { - if (subCode.value == value){ - return subCode; - } - } - throw new IllegalArgumentException( - "Illegal value provided for SubCode (" + value + ")."); - } - - /** - * Returns the byte value of the enumerations value. - * - * @return byte representation - */ - public byte getValue() { - return value; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Status status = (Status) o; - return code == status.code && subCode == status.subCode && Objects.equals(state, status.state); - } - - @Override - public int hashCode() { - return Objects.hash(code, subCode, state); - } -} diff --git a/java/src/main/java/org/rocksdb/StringAppendOperator.java b/java/src/main/java/org/rocksdb/StringAppendOperator.java deleted file mode 100644 index ddbccff46..000000000 --- a/java/src/main/java/org/rocksdb/StringAppendOperator.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// Copyright (c) 2014, Vlad Balan (vlad.gm@gmail.com). All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * StringAppendOperator is a merge operator that concatenates - * two strings. - */ -public class StringAppendOperator extends MergeOperator { - public StringAppendOperator() { - this(','); - } - - public StringAppendOperator(char delim) { - super(newSharedStringAppendOperator(delim)); - } - - public StringAppendOperator(String delim) { - super(newSharedStringAppendOperator(delim)); - } - - private native static long newSharedStringAppendOperator(final char delim); - private native static long newSharedStringAppendOperator(final String delim); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/TableFileCreationBriefInfo.java b/java/src/main/java/org/rocksdb/TableFileCreationBriefInfo.java deleted file mode 100644 index 5a383ade4..000000000 --- a/java/src/main/java/org/rocksdb/TableFileCreationBriefInfo.java +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class TableFileCreationBriefInfo { - private final String dbName; - private final String columnFamilyName; - private final String filePath; - private final int jobId; - private final TableFileCreationReason reason; - - /** - * Access is private as this will only be constructed from - * C++ via JNI, either directly of via - * {@link TableFileCreationInfo#TableFileCreationInfo(long, TableProperties, Status, String, - * String, String, int, byte)}. - * - * @param dbName the database name - * @param columnFamilyName the column family name - * @param filePath the path to the table file - * @param jobId the job identifier - * @param tableFileCreationReasonValue the reason for creation of the table file - */ - protected TableFileCreationBriefInfo(final String dbName, final String columnFamilyName, - final String filePath, final int jobId, final byte tableFileCreationReasonValue) { - this.dbName = dbName; - this.columnFamilyName = columnFamilyName; - this.filePath = filePath; - this.jobId = jobId; - this.reason = TableFileCreationReason.fromValue(tableFileCreationReasonValue); - } - - /** - * Get the name of the database where the file was created. - * - * @return the name of the database. - */ - public String getDbName() { - return dbName; - } - - /** - * Get the name of the column family where the file was created. - * - * @return the name of the column family. - */ - public String getColumnFamilyName() { - return columnFamilyName; - } - - /** - * Get the path to the created file. - * - * @return the path. - */ - public String getFilePath() { - return filePath; - } - - /** - * Get the id of the job (which could be flush or compaction) that - * created the file. - * - * @return the id of the job. - */ - public int getJobId() { - return jobId; - } - - /** - * Get the reason for creating the table. - * - * @return the reason for creating the table. - */ - public TableFileCreationReason getReason() { - return reason; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TableFileCreationBriefInfo that = (TableFileCreationBriefInfo) o; - return jobId == that.jobId && Objects.equals(dbName, that.dbName) - && Objects.equals(columnFamilyName, that.columnFamilyName) - && Objects.equals(filePath, that.filePath) && reason == that.reason; - } - - @Override - public int hashCode() { - return Objects.hash(dbName, columnFamilyName, filePath, jobId, reason); - } - - @Override - public String toString() { - return "TableFileCreationBriefInfo{" - + "dbName='" + dbName + '\'' + ", columnFamilyName='" + columnFamilyName + '\'' - + ", filePath='" + filePath + '\'' + ", jobId=" + jobId + ", reason=" + reason + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/TableFileCreationInfo.java b/java/src/main/java/org/rocksdb/TableFileCreationInfo.java deleted file mode 100644 index 7742f32f1..000000000 --- a/java/src/main/java/org/rocksdb/TableFileCreationInfo.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class TableFileCreationInfo extends TableFileCreationBriefInfo { - private final long fileSize; - private final TableProperties tableProperties; - private final Status status; - - /** - * Access is protected as this will only be constructed from - * C++ via JNI. - * - * @param fileSize the size of the table file - * @param tableProperties the properties of the table file - * @param status the status of the creation operation - * @param dbName the database name - * @param columnFamilyName the column family name - * @param filePath the path to the table file - * @param jobId the job identifier - * @param tableFileCreationReasonValue the reason for creation of the table file - */ - protected TableFileCreationInfo(final long fileSize, final TableProperties tableProperties, - final Status status, final String dbName, final String columnFamilyName, - final String filePath, final int jobId, final byte tableFileCreationReasonValue) { - super(dbName, columnFamilyName, filePath, jobId, tableFileCreationReasonValue); - this.fileSize = fileSize; - this.tableProperties = tableProperties; - this.status = status; - } - - /** - * Get the size of the file. - * - * @return the size. - */ - public long getFileSize() { - return fileSize; - } - - /** - * Get the detailed properties of the created file. - * - * @return the properties. - */ - public TableProperties getTableProperties() { - return tableProperties; - } - - /** - * Get the status indicating whether the creation was successful or not. - * - * @return the status. - */ - public Status getStatus() { - return status; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TableFileCreationInfo that = (TableFileCreationInfo) o; - return fileSize == that.fileSize && Objects.equals(tableProperties, that.tableProperties) - && Objects.equals(status, that.status); - } - - @Override - public int hashCode() { - return Objects.hash(fileSize, tableProperties, status); - } - - @Override - public String toString() { - return "TableFileCreationInfo{" - + "fileSize=" + fileSize + ", tableProperties=" + tableProperties + ", status=" + status - + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/TableFileCreationReason.java b/java/src/main/java/org/rocksdb/TableFileCreationReason.java deleted file mode 100644 index d3984663d..000000000 --- a/java/src/main/java/org/rocksdb/TableFileCreationReason.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum TableFileCreationReason { - FLUSH((byte) 0x00), - COMPACTION((byte) 0x01), - RECOVERY((byte) 0x02), - MISC((byte) 0x03); - - private final byte value; - - TableFileCreationReason(final byte value) { - this.value = value; - } - - /** - * Get the internal representation. - * - * @return the internal representation - */ - byte getValue() { - return value; - } - - /** - * Get the TableFileCreationReason from the internal representation value. - * - * @return the table file creation reason. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static TableFileCreationReason fromValue(final byte value) { - for (final TableFileCreationReason tableFileCreationReason : TableFileCreationReason.values()) { - if (tableFileCreationReason.value == value) { - return tableFileCreationReason; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for TableFileCreationReason: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/TableFileDeletionInfo.java b/java/src/main/java/org/rocksdb/TableFileDeletionInfo.java deleted file mode 100644 index 8aad03ae8..000000000 --- a/java/src/main/java/org/rocksdb/TableFileDeletionInfo.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class TableFileDeletionInfo { - private final String dbName; - private final String filePath; - private final int jobId; - private final Status status; - - /** - * Access is package private as this will only be constructed from - * C++ via JNI and for testing. - */ - TableFileDeletionInfo( - final String dbName, final String filePath, final int jobId, final Status status) { - this.dbName = dbName; - this.filePath = filePath; - this.jobId = jobId; - this.status = status; - } - - /** - * Get the name of the database where the file was deleted. - * - * @return the name of the database. - */ - public String getDbName() { - return dbName; - } - - /** - * Get the path to the deleted file. - * - * @return the path. - */ - public String getFilePath() { - return filePath; - } - - /** - * Get the id of the job which deleted the file. - * - * @return the id of the job. - */ - public int getJobId() { - return jobId; - } - - /** - * Get the status indicating whether the deletion was successful or not. - * - * @return the status - */ - public Status getStatus() { - return status; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TableFileDeletionInfo that = (TableFileDeletionInfo) o; - return jobId == that.jobId && Objects.equals(dbName, that.dbName) - && Objects.equals(filePath, that.filePath) && Objects.equals(status, that.status); - } - - @Override - public int hashCode() { - return Objects.hash(dbName, filePath, jobId, status); - } - - @Override - public String toString() { - return "TableFileDeletionInfo{" - + "dbName='" + dbName + '\'' + ", filePath='" + filePath + '\'' + ", jobId=" + jobId - + ", status=" + status + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/TableFilter.java b/java/src/main/java/org/rocksdb/TableFilter.java deleted file mode 100644 index a39a329fb..000000000 --- a/java/src/main/java/org/rocksdb/TableFilter.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * Filter for iterating a table. - */ -public interface TableFilter { - - /** - * A callback to determine whether relevant keys for this scan exist in a - * given table based on the table's properties. The callback is passed the - * properties of each table during iteration. If the callback returns false, - * the table will not be scanned. This option only affects Iterators and has - * no impact on point lookups. - * - * @param tableProperties the table properties. - * - * @return true if the table should be scanned, false otherwise. - */ - boolean filter(final TableProperties tableProperties); -} diff --git a/java/src/main/java/org/rocksdb/TableFormatConfig.java b/java/src/main/java/org/rocksdb/TableFormatConfig.java deleted file mode 100644 index dbe524c42..000000000 --- a/java/src/main/java/org/rocksdb/TableFormatConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * TableFormatConfig is used to config the internal Table format of a RocksDB. - * To make a RocksDB to use a specific Table format, its associated - * TableFormatConfig should be properly set and passed into Options via - * Options.setTableFormatConfig() and open the db using that Options. - */ -public abstract class TableFormatConfig { - /** - *

    This function should only be called by Options.setTableFormatConfig(), - * which will create a c++ shared-pointer to the c++ TableFactory - * that associated with the Java TableFormatConfig.

    - * - * @return native handle address to native table instance. - */ - abstract protected long newTableFactoryHandle(); -} diff --git a/java/src/main/java/org/rocksdb/TableProperties.java b/java/src/main/java/org/rocksdb/TableProperties.java deleted file mode 100644 index 096341a4c..000000000 --- a/java/src/main/java/org/rocksdb/TableProperties.java +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import java.util.Arrays; -import java.util.Map; -import java.util.Objects; - -/** - * TableProperties contains read-only properties of its associated - * table. - */ -public class TableProperties { - private final long dataSize; - private final long indexSize; - private final long indexPartitions; - private final long topLevelIndexSize; - private final long indexKeyIsUserKey; - private final long indexValueIsDeltaEncoded; - private final long filterSize; - private final long rawKeySize; - private final long rawValueSize; - private final long numDataBlocks; - private final long numEntries; - private final long numDeletions; - private final long numMergeOperands; - private final long numRangeDeletions; - private final long formatVersion; - private final long fixedKeyLen; - private final long columnFamilyId; - private final long creationTime; - private final long oldestKeyTime; - private final long slowCompressionEstimatedDataSize; - private final long fastCompressionEstimatedDataSize; - private final long externalSstFileGlobalSeqnoOffset; - private final byte[] columnFamilyName; - private final String filterPolicyName; - private final String comparatorName; - private final String mergeOperatorName; - private final String prefixExtractorName; - private final String propertyCollectorsNames; - private final String compressionName; - private final Map userCollectedProperties; - private final Map readableProperties; - - /** - * Access is package private as this will only be constructed from - * C++ via JNI and for testing. - */ - TableProperties(final long dataSize, final long indexSize, final long indexPartitions, - final long topLevelIndexSize, final long indexKeyIsUserKey, - final long indexValueIsDeltaEncoded, final long filterSize, final long rawKeySize, - final long rawValueSize, final long numDataBlocks, final long numEntries, - final long numDeletions, final long numMergeOperands, final long numRangeDeletions, - final long formatVersion, final long fixedKeyLen, final long columnFamilyId, - final long creationTime, final long oldestKeyTime, - final long slowCompressionEstimatedDataSize, final long fastCompressionEstimatedDataSize, - final long externalSstFileGlobalSeqnoOffset, final byte[] columnFamilyName, - final String filterPolicyName, final String comparatorName, final String mergeOperatorName, - final String prefixExtractorName, final String propertyCollectorsNames, - final String compressionName, final Map userCollectedProperties, - final Map readableProperties) { - this.dataSize = dataSize; - this.indexSize = indexSize; - this.indexPartitions = indexPartitions; - this.topLevelIndexSize = topLevelIndexSize; - this.indexKeyIsUserKey = indexKeyIsUserKey; - this.indexValueIsDeltaEncoded = indexValueIsDeltaEncoded; - this.filterSize = filterSize; - this.rawKeySize = rawKeySize; - this.rawValueSize = rawValueSize; - this.numDataBlocks = numDataBlocks; - this.numEntries = numEntries; - this.numDeletions = numDeletions; - this.numMergeOperands = numMergeOperands; - this.numRangeDeletions = numRangeDeletions; - this.formatVersion = formatVersion; - this.fixedKeyLen = fixedKeyLen; - this.columnFamilyId = columnFamilyId; - this.creationTime = creationTime; - this.oldestKeyTime = oldestKeyTime; - this.slowCompressionEstimatedDataSize = slowCompressionEstimatedDataSize; - this.fastCompressionEstimatedDataSize = fastCompressionEstimatedDataSize; - this.externalSstFileGlobalSeqnoOffset = externalSstFileGlobalSeqnoOffset; - this.columnFamilyName = columnFamilyName; - this.filterPolicyName = filterPolicyName; - this.comparatorName = comparatorName; - this.mergeOperatorName = mergeOperatorName; - this.prefixExtractorName = prefixExtractorName; - this.propertyCollectorsNames = propertyCollectorsNames; - this.compressionName = compressionName; - this.userCollectedProperties = userCollectedProperties; - this.readableProperties = readableProperties; - } - - /** - * Get the total size of all data blocks. - * - * @return the total size of all data blocks. - */ - public long getDataSize() { - return dataSize; - } - - /** - * Get the size of index block. - * - * @return the size of index block. - */ - public long getIndexSize() { - return indexSize; - } - - /** - * Get the total number of index partitions - * if {@link IndexType#kTwoLevelIndexSearch} is used. - * - * @return the total number of index partitions. - */ - public long getIndexPartitions() { - return indexPartitions; - } - - /** - * Size of the top-level index - * if {@link IndexType#kTwoLevelIndexSearch} is used. - * - * @return the size of the top-level index. - */ - public long getTopLevelIndexSize() { - return topLevelIndexSize; - } - - /** - * Whether the index key is user key. - * Otherwise it includes 8 byte of sequence - * number added by internal key format. - * - * @return the index key - */ - public long getIndexKeyIsUserKey() { - return indexKeyIsUserKey; - } - - /** - * Whether delta encoding is used to encode the index values. - * - * @return whether delta encoding is used to encode the index values. - */ - public long getIndexValueIsDeltaEncoded() { - return indexValueIsDeltaEncoded; - } - - /** - * Get the size of filter block. - * - * @return the size of filter block. - */ - public long getFilterSize() { - return filterSize; - } - - /** - * Get the total raw key size. - * - * @return the total raw key size. - */ - public long getRawKeySize() { - return rawKeySize; - } - - /** - * Get the total raw value size. - * - * @return the total raw value size. - */ - public long getRawValueSize() { - return rawValueSize; - } - - /** - * Get the number of blocks in this table. - * - * @return the number of blocks in this table. - */ - public long getNumDataBlocks() { - return numDataBlocks; - } - - /** - * Get the number of entries in this table. - * - * @return the number of entries in this table. - */ - public long getNumEntries() { - return numEntries; - } - - /** - * Get the number of deletions in the table. - * - * @return the number of deletions in the table. - */ - public long getNumDeletions() { - return numDeletions; - } - - /** - * Get the number of merge operands in the table. - * - * @return the number of merge operands in the table. - */ - public long getNumMergeOperands() { - return numMergeOperands; - } - - /** - * Get the number of range deletions in this table. - * - * @return the number of range deletions in this table. - */ - public long getNumRangeDeletions() { - return numRangeDeletions; - } - - /** - * Get the format version, reserved for backward compatibility. - * - * @return the format version. - */ - public long getFormatVersion() { - return formatVersion; - } - - /** - * Get the length of the keys. - * - * @return 0 when the key is variable length, otherwise number of - * bytes for each key. - */ - public long getFixedKeyLen() { - return fixedKeyLen; - } - - /** - * Get the ID of column family for this SST file, - * corresponding to the column family identified by - * {@link #getColumnFamilyName()}. - * - * @return the id of the column family. - */ - public long getColumnFamilyId() { - return columnFamilyId; - } - - /** - * The time when the SST file was created. - * Since SST files are immutable, this is equivalent - * to last modified time. - * - * @return the created time. - */ - public long getCreationTime() { - return creationTime; - } - - /** - * Get the timestamp of the earliest key. - * - * @return 0 means unknown, otherwise the timestamp. - */ - public long getOldestKeyTime() { - return oldestKeyTime; - } - - /** - * Get the estimated size of data blocks compressed with a relatively slower - * compression algorithm. - * - * @return 0 means unknown, otherwise the timestamp. - */ - public long getSlowCompressionEstimatedDataSize() { - return slowCompressionEstimatedDataSize; - } - - /** - * Get the estimated size of data blocks compressed with a relatively faster - * compression algorithm. - * - * @return 0 means unknown, otherwise the timestamp. - */ - public long getFastCompressionEstimatedDataSize() { - return fastCompressionEstimatedDataSize; - } - - /** - * Get the name of the column family with which this - * SST file is associated. - * - * @return the name of the column family, or null if the - * column family is unknown. - */ - /*@Nullable*/ public byte[] getColumnFamilyName() { - return columnFamilyName; - } - - /** - * Get the name of the filter policy used in this table. - * - * @return the name of the filter policy, or null if - * no filter policy is used. - */ - /*@Nullable*/ public String getFilterPolicyName() { - return filterPolicyName; - } - - /** - * Get the name of the comparator used in this table. - * - * @return the name of the comparator. - */ - public String getComparatorName() { - return comparatorName; - } - - /** - * Get the name of the merge operator used in this table. - * - * @return the name of the merge operator, or null if no merge operator - * is used. - */ - /*@Nullable*/ public String getMergeOperatorName() { - return mergeOperatorName; - } - - /** - * Get the name of the prefix extractor used in this table. - * - * @return the name of the prefix extractor, or null if no prefix - * extractor is used. - */ - /*@Nullable*/ public String getPrefixExtractorName() { - return prefixExtractorName; - } - - /** - * Get the names of the property collectors factories used in this table. - * - * @return the names of the property collector factories separated - * by commas, e.g. {collector_name[1]},{collector_name[2]},... - */ - public String getPropertyCollectorsNames() { - return propertyCollectorsNames; - } - - /** - * Get the name of the compression algorithm used to compress the SST files. - * - * @return the name of the compression algorithm. - */ - public String getCompressionName() { - return compressionName; - } - - /** - * Get the user collected properties. - * - * @return the user collected properties. - */ - public Map getUserCollectedProperties() { - return userCollectedProperties; - } - - /** - * Get the readable properties. - * - * @return the readable properties. - */ - public Map getReadableProperties() { - return readableProperties; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TableProperties that = (TableProperties) o; - return dataSize == that.dataSize && indexSize == that.indexSize - && indexPartitions == that.indexPartitions && topLevelIndexSize == that.topLevelIndexSize - && indexKeyIsUserKey == that.indexKeyIsUserKey - && indexValueIsDeltaEncoded == that.indexValueIsDeltaEncoded - && filterSize == that.filterSize && rawKeySize == that.rawKeySize - && rawValueSize == that.rawValueSize && numDataBlocks == that.numDataBlocks - && numEntries == that.numEntries && numDeletions == that.numDeletions - && numMergeOperands == that.numMergeOperands && numRangeDeletions == that.numRangeDeletions - && formatVersion == that.formatVersion && fixedKeyLen == that.fixedKeyLen - && columnFamilyId == that.columnFamilyId && creationTime == that.creationTime - && oldestKeyTime == that.oldestKeyTime - && slowCompressionEstimatedDataSize == that.slowCompressionEstimatedDataSize - && fastCompressionEstimatedDataSize == that.fastCompressionEstimatedDataSize - && externalSstFileGlobalSeqnoOffset == that.externalSstFileGlobalSeqnoOffset - && Arrays.equals(columnFamilyName, that.columnFamilyName) - && Objects.equals(filterPolicyName, that.filterPolicyName) - && Objects.equals(comparatorName, that.comparatorName) - && Objects.equals(mergeOperatorName, that.mergeOperatorName) - && Objects.equals(prefixExtractorName, that.prefixExtractorName) - && Objects.equals(propertyCollectorsNames, that.propertyCollectorsNames) - && Objects.equals(compressionName, that.compressionName) - && Objects.equals(userCollectedProperties, that.userCollectedProperties) - && Objects.equals(readableProperties, that.readableProperties); - } - - @Override - public int hashCode() { - int result = Objects.hash(dataSize, indexSize, indexPartitions, topLevelIndexSize, - indexKeyIsUserKey, indexValueIsDeltaEncoded, filterSize, rawKeySize, rawValueSize, - numDataBlocks, numEntries, numDeletions, numMergeOperands, numRangeDeletions, formatVersion, - fixedKeyLen, columnFamilyId, creationTime, oldestKeyTime, slowCompressionEstimatedDataSize, - fastCompressionEstimatedDataSize, externalSstFileGlobalSeqnoOffset, filterPolicyName, - comparatorName, mergeOperatorName, prefixExtractorName, propertyCollectorsNames, - compressionName, userCollectedProperties, readableProperties); - result = 31 * result + Arrays.hashCode(columnFamilyName); - return result; - } -} diff --git a/java/src/main/java/org/rocksdb/ThreadStatus.java b/java/src/main/java/org/rocksdb/ThreadStatus.java deleted file mode 100644 index 062df5889..000000000 --- a/java/src/main/java/org/rocksdb/ThreadStatus.java +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Map; - -public class ThreadStatus { - private final long threadId; - private final ThreadType threadType; - private final String dbName; - private final String cfName; - private final OperationType operationType; - private final long operationElapsedTime; // microseconds - private final OperationStage operationStage; - private final long operationProperties[]; - private final StateType stateType; - - /** - * Invoked from C++ via JNI - */ - private ThreadStatus(final long threadId, - final byte threadTypeValue, - final String dbName, - final String cfName, - final byte operationTypeValue, - final long operationElapsedTime, - final byte operationStageValue, - final long[] operationProperties, - final byte stateTypeValue) { - this.threadId = threadId; - this.threadType = ThreadType.fromValue(threadTypeValue); - this.dbName = dbName; - this.cfName = cfName; - this.operationType = OperationType.fromValue(operationTypeValue); - this.operationElapsedTime = operationElapsedTime; - this.operationStage = OperationStage.fromValue(operationStageValue); - this.operationProperties = operationProperties; - this.stateType = StateType.fromValue(stateTypeValue); - } - - /** - * Get the unique ID of the thread. - * - * @return the thread id - */ - public long getThreadId() { - return threadId; - } - - /** - * Get the type of the thread. - * - * @return the type of the thread. - */ - public ThreadType getThreadType() { - return threadType; - } - - /** - * The name of the DB instance that the thread is currently - * involved with. - * - * @return the name of the db, or null if the thread is not involved - * in any DB operation. - */ - /* @Nullable */ public String getDbName() { - return dbName; - } - - /** - * The name of the Column Family that the thread is currently - * involved with. - * - * @return the name of the db, or null if the thread is not involved - * in any column Family operation. - */ - /* @Nullable */ public String getCfName() { - return cfName; - } - - /** - * Get the operation (high-level action) that the current thread is involved - * with. - * - * @return the operation - */ - public OperationType getOperationType() { - return operationType; - } - - /** - * Get the elapsed time of the current thread operation in microseconds. - * - * @return the elapsed time - */ - public long getOperationElapsedTime() { - return operationElapsedTime; - } - - /** - * Get the current stage where the thread is involved in the current - * operation. - * - * @return the current stage of the current operation - */ - public OperationStage getOperationStage() { - return operationStage; - } - - /** - * Get the list of properties that describe some details about the current - * operation. - * - * Each field in might have different meanings for different operations. - * - * @return the properties - */ - public long[] getOperationProperties() { - return operationProperties; - } - - /** - * Get the state (lower-level action) that the current thread is involved - * with. - * - * @return the state - */ - public StateType getStateType() { - return stateType; - } - - /** - * Get the name of the thread type. - * - * @param threadType the thread type - * - * @return the name of the thread type. - */ - public static String getThreadTypeName(final ThreadType threadType) { - return getThreadTypeName(threadType.getValue()); - } - - /** - * Get the name of an operation given its type. - * - * @param operationType the type of operation. - * - * @return the name of the operation. - */ - public static String getOperationName(final OperationType operationType) { - return getOperationName(operationType.getValue()); - } - - public static String microsToString(final long operationElapsedTime) { - return microsToStringNative(operationElapsedTime); - } - - /** - * Obtain a human-readable string describing the specified operation stage. - * - * @param operationStage the stage of the operation. - * - * @return the description of the operation stage. - */ - public static String getOperationStageName( - final OperationStage operationStage) { - return getOperationStageName(operationStage.getValue()); - } - - /** - * Obtain the name of the "i"th operation property of the - * specified operation. - * - * @param operationType the operation type. - * @param i the index of the operation property. - * - * @return the name of the operation property - */ - public static String getOperationPropertyName( - final OperationType operationType, final int i) { - return getOperationPropertyName(operationType.getValue(), i); - } - - /** - * Translate the "i"th property of the specified operation given - * a property value. - * - * @param operationType the operation type. - * @param operationProperties the operation properties. - * - * @return the property values. - */ - public static Map interpretOperationProperties( - final OperationType operationType, final long[] operationProperties) { - return interpretOperationProperties(operationType.getValue(), - operationProperties); - } - - /** - * Obtain the name of a state given its type. - * - * @param stateType the state type. - * - * @return the name of the state. - */ - public static String getStateName(final StateType stateType) { - return getStateName(stateType.getValue()); - } - - private static native String getThreadTypeName(final byte threadTypeValue); - private static native String getOperationName(final byte operationTypeValue); - private static native String microsToStringNative( - final long operationElapsedTime); - private static native String getOperationStageName( - final byte operationStageTypeValue); - private static native String getOperationPropertyName( - final byte operationTypeValue, final int i); - private static native MapinterpretOperationProperties( - final byte operationTypeValue, final long[] operationProperties); - private static native String getStateName(final byte stateTypeValue); -} diff --git a/java/src/main/java/org/rocksdb/ThreadType.java b/java/src/main/java/org/rocksdb/ThreadType.java deleted file mode 100644 index cc329f442..000000000 --- a/java/src/main/java/org/rocksdb/ThreadType.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The type of a thread. - */ -public enum ThreadType { - /** - * RocksDB BG thread in high-pri thread pool. - */ - HIGH_PRIORITY((byte)0x0), - - /** - * RocksDB BG thread in low-pri thread pool. - */ - LOW_PRIORITY((byte)0x1), - - /** - * User thread (Non-RocksDB BG thread). - */ - USER((byte)0x2), - - /** - * RocksDB BG thread in bottom-pri thread pool - */ - BOTTOM_PRIORITY((byte)0x3); - - private final byte value; - - ThreadType(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value. - */ - byte getValue() { - return value; - } - - /** - * Get the Thread type from the internal representation value. - * - * @param value the internal representation value. - * - * @return the thread type - * - * @throws IllegalArgumentException if the value does not match a ThreadType - */ - static ThreadType fromValue(final byte value) - throws IllegalArgumentException { - for (final ThreadType threadType : ThreadType.values()) { - if (threadType.value == value) { - return threadType; - } - } - throw new IllegalArgumentException("Unknown value for ThreadType: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/TickerType.java b/java/src/main/java/org/rocksdb/TickerType.java deleted file mode 100644 index 98e3043c6..000000000 --- a/java/src/main/java/org/rocksdb/TickerType.java +++ /dev/null @@ -1,792 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The logical mapping of tickers defined in rocksdb::Tickers. - * - * Java byte value mappings don't align 1:1 to the c++ values. c++ rocksdb::Tickers enumeration type - * is uint32_t and java org.rocksdb.TickerType is byte, this causes mapping issues when - * rocksdb::Tickers value is greater then 127 (0x7F) for jbyte jni interface as range greater is not - * available. Without breaking interface in minor versions, value mappings for - * org.rocksdb.TickerType leverage full byte range [-128 (-0x80), (0x7F)]. Newer tickers added - * should descend into negative values until TICKER_ENUM_MAX reaches -128 (-0x80). - */ -public enum TickerType { - - /** - * total block cache misses - * - * REQUIRES: BLOCK_CACHE_MISS == BLOCK_CACHE_INDEX_MISS + - * BLOCK_CACHE_FILTER_MISS + - * BLOCK_CACHE_DATA_MISS; - */ - BLOCK_CACHE_MISS((byte) 0x0), - - /** - * total block cache hit - * - * REQUIRES: BLOCK_CACHE_HIT == BLOCK_CACHE_INDEX_HIT + - * BLOCK_CACHE_FILTER_HIT + - * BLOCK_CACHE_DATA_HIT; - */ - BLOCK_CACHE_HIT((byte) 0x1), - - BLOCK_CACHE_ADD((byte) 0x2), - - /** - * # of failures when adding blocks to block cache. - */ - BLOCK_CACHE_ADD_FAILURES((byte) 0x3), - - /** - * # of times cache miss when accessing index block from block cache. - */ - BLOCK_CACHE_INDEX_MISS((byte) 0x4), - - /** - * # of times cache hit when accessing index block from block cache. - */ - BLOCK_CACHE_INDEX_HIT((byte) 0x5), - - /** - * # of index blocks added to block cache. - */ - BLOCK_CACHE_INDEX_ADD((byte) 0x6), - - /** - * # of bytes of index blocks inserted into cache - */ - BLOCK_CACHE_INDEX_BYTES_INSERT((byte) 0x7), - - /** - * # of times cache miss when accessing filter block from block cache. - */ - BLOCK_CACHE_FILTER_MISS((byte) 0x9), - - /** - * # of times cache hit when accessing filter block from block cache. - */ - BLOCK_CACHE_FILTER_HIT((byte) 0xA), - - /** - * # of filter blocks added to block cache. - */ - BLOCK_CACHE_FILTER_ADD((byte) 0xB), - - /** - * # of bytes of bloom filter blocks inserted into cache - */ - BLOCK_CACHE_FILTER_BYTES_INSERT((byte) 0xC), - - /** - * # of times cache miss when accessing data block from block cache. - */ - BLOCK_CACHE_DATA_MISS((byte) 0xE), - - /** - * # of times cache hit when accessing data block from block cache. - */ - BLOCK_CACHE_DATA_HIT((byte) 0xF), - - /** - * # of data blocks added to block cache. - */ - BLOCK_CACHE_DATA_ADD((byte) 0x10), - - /** - * # of bytes of data blocks inserted into cache - */ - BLOCK_CACHE_DATA_BYTES_INSERT((byte) 0x11), - - /** - * # of bytes read from cache. - */ - BLOCK_CACHE_BYTES_READ((byte) 0x12), - - /** - * # of bytes written into cache. - */ - BLOCK_CACHE_BYTES_WRITE((byte) 0x13), - - /** - * # of times bloom filter has avoided file reads. - */ - BLOOM_FILTER_USEFUL((byte) 0x14), - - /** - * # persistent cache hit - */ - PERSISTENT_CACHE_HIT((byte) 0x15), - - /** - * # persistent cache miss - */ - PERSISTENT_CACHE_MISS((byte) 0x16), - - /** - * # total simulation block cache hits - */ - SIM_BLOCK_CACHE_HIT((byte) 0x17), - - /** - * # total simulation block cache misses - */ - SIM_BLOCK_CACHE_MISS((byte) 0x18), - - /** - * # of memtable hits. - */ - MEMTABLE_HIT((byte) 0x19), - - /** - * # of memtable misses. - */ - MEMTABLE_MISS((byte) 0x1A), - - /** - * # of Get() queries served by L0 - */ - GET_HIT_L0((byte) 0x1B), - - /** - * # of Get() queries served by L1 - */ - GET_HIT_L1((byte) 0x1C), - - /** - * # of Get() queries served by L2 and up - */ - GET_HIT_L2_AND_UP((byte) 0x1D), - - /** - * COMPACTION_KEY_DROP_* count the reasons for key drop during compaction - * There are 4 reasons currently. - */ - - /** - * key was written with a newer value. - */ - COMPACTION_KEY_DROP_NEWER_ENTRY((byte) 0x1E), - - /** - * Also includes keys dropped for range del. - * The key is obsolete. - */ - COMPACTION_KEY_DROP_OBSOLETE((byte) 0x1F), - - /** - * key was covered by a range tombstone. - */ - COMPACTION_KEY_DROP_RANGE_DEL((byte) 0x20), - - /** - * User compaction function has dropped the key. - */ - COMPACTION_KEY_DROP_USER((byte) 0x21), - - /** - * all keys in range were deleted. - */ - COMPACTION_RANGE_DEL_DROP_OBSOLETE((byte) 0x22), - - /** - * Number of keys written to the database via the Put and Write call's. - */ - NUMBER_KEYS_WRITTEN((byte) 0x23), - - /** - * Number of Keys read. - */ - NUMBER_KEYS_READ((byte) 0x24), - - /** - * Number keys updated, if inplace update is enabled - */ - NUMBER_KEYS_UPDATED((byte) 0x25), - - /** - * The number of uncompressed bytes issued by DB::Put(), DB::Delete(),\ - * DB::Merge(), and DB::Write(). - */ - BYTES_WRITTEN((byte) 0x26), - - /** - * The number of uncompressed bytes read from DB::Get(). It could be - * either from memtables, cache, or table files. - * - * For the number of logical bytes read from DB::MultiGet(), - * please use {@link #NUMBER_MULTIGET_BYTES_READ}. - */ - BYTES_READ((byte) 0x27), - - /** - * The number of calls to seek. - */ - NUMBER_DB_SEEK((byte) 0x28), - - /** - * The number of calls to next. - */ - NUMBER_DB_NEXT((byte) 0x29), - - /** - * The number of calls to prev. - */ - NUMBER_DB_PREV((byte) 0x2A), - - /** - * The number of calls to seek that returned data. - */ - NUMBER_DB_SEEK_FOUND((byte) 0x2B), - - /** - * The number of calls to next that returned data. - */ - NUMBER_DB_NEXT_FOUND((byte) 0x2C), - - /** - * The number of calls to prev that returned data. - */ - NUMBER_DB_PREV_FOUND((byte) 0x2D), - - /** - * The number of uncompressed bytes read from an iterator. - * Includes size of key and value. - */ - ITER_BYTES_READ((byte) 0x2E), - - NO_FILE_OPENS((byte) 0x30), - - NO_FILE_ERRORS((byte) 0x31), - - /** - * Writer has to wait for compaction or flush to finish. - */ - STALL_MICROS((byte) 0x35), - - /** - * The wait time for db mutex. - * - * Disabled by default. To enable it set stats level to {@link StatsLevel#ALL} - */ - DB_MUTEX_WAIT_MICROS((byte) 0x36), - - /** - * Number of MultiGet calls. - */ - NUMBER_MULTIGET_CALLS((byte) 0x39), - - /** - * Number of MultiGet keys read. - */ - NUMBER_MULTIGET_KEYS_READ((byte) 0x3A), - - /** - * Number of MultiGet bytes read. - */ - NUMBER_MULTIGET_BYTES_READ((byte) 0x3B), - - NUMBER_MERGE_FAILURES((byte) 0x3D), - - /** - * Number of times bloom was checked before creating iterator on a - * file, and the number of times the check was useful in avoiding - * iterator creation (and thus likely IOPs). - */ - BLOOM_FILTER_PREFIX_CHECKED((byte) 0x3E), - BLOOM_FILTER_PREFIX_USEFUL((byte) 0x3F), - - /** - * Number of times we had to reseek inside an iteration to skip - * over large number of keys with same userkey. - */ - NUMBER_OF_RESEEKS_IN_ITERATION((byte) 0x40), - - /** - * Record the number of calls to {@link RocksDB#getUpdatesSince(long)}. Useful to keep track of - * transaction log iterator refreshes. - */ - GET_UPDATES_SINCE_CALLS((byte) 0x41), - - /** - * Number of times WAL sync is done. - */ - WAL_FILE_SYNCED((byte) 0x46), - - /** - * Number of bytes written to WAL. - */ - WAL_FILE_BYTES((byte) 0x47), - - /** - * Writes can be processed by requesting thread or by the thread at the - * head of the writers queue. - */ - WRITE_DONE_BY_SELF((byte) 0x48), - - /** - * Equivalent to writes done for others. - */ - WRITE_DONE_BY_OTHER((byte) 0x49), - - /** - * Number of Write calls that request WAL. - */ - WRITE_WITH_WAL((byte) 0x4B), - - /** - * Bytes read during compaction. - */ - COMPACT_READ_BYTES((byte) 0x4C), - - /** - * Bytes written during compaction. - */ - COMPACT_WRITE_BYTES((byte) 0x4D), - - /** - * Bytes written during flush. - */ - FLUSH_WRITE_BYTES((byte) 0x4E), - - /** - * Number of table's properties loaded directly from file, without creating - * table reader object. - */ - NUMBER_DIRECT_LOAD_TABLE_PROPERTIES((byte) 0x4F), - NUMBER_SUPERVERSION_ACQUIRES((byte) 0x50), - NUMBER_SUPERVERSION_RELEASES((byte) 0x51), - NUMBER_SUPERVERSION_CLEANUPS((byte) 0x52), - - /** - * # of compressions/decompressions executed - */ - NUMBER_BLOCK_COMPRESSED((byte) 0x53), - NUMBER_BLOCK_DECOMPRESSED((byte) 0x54), - - NUMBER_BLOCK_NOT_COMPRESSED((byte) 0x55), - MERGE_OPERATION_TOTAL_TIME((byte) 0x56), - FILTER_OPERATION_TOTAL_TIME((byte) 0x57), - - /** - * Row cache. - */ - ROW_CACHE_HIT((byte) 0x58), - ROW_CACHE_MISS((byte) 0x59), - - /** - * Read amplification statistics. - * - * Read amplification can be calculated using this formula - * (READ_AMP_TOTAL_READ_BYTES / READ_AMP_ESTIMATE_USEFUL_BYTES) - * - * REQUIRES: ReadOptions::read_amp_bytes_per_bit to be enabled - */ - - /** - * Estimate of total bytes actually used. - */ - READ_AMP_ESTIMATE_USEFUL_BYTES((byte) 0x5A), - - /** - * Total size of loaded data blocks. - */ - READ_AMP_TOTAL_READ_BYTES((byte) 0x5B), - - /** - * Number of refill intervals where rate limiter's bytes are fully consumed. - */ - NUMBER_RATE_LIMITER_DRAINS((byte) 0x5C), - - /** - * Number of internal skipped during iteration - */ - NUMBER_ITER_SKIP((byte) 0x5D), - - /** - * Number of MultiGet keys found (vs number requested) - */ - NUMBER_MULTIGET_KEYS_FOUND((byte) 0x5E), - - // -0x01 to fixate the new value that incorrectly changed TICKER_ENUM_MAX - /** - * Number of iterators created. - */ - NO_ITERATOR_CREATED((byte) -0x01), - - /** - * Number of iterators deleted. - */ - NO_ITERATOR_DELETED((byte) 0x60), - - /** - * Deletions obsoleted before bottom level due to file gap optimization. - */ - COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE((byte) 0x61), - - /** - * If a compaction was cancelled in sfm to prevent ENOSPC - */ - COMPACTION_CANCELLED((byte) 0x62), - - /** - * # of times bloom FullFilter has not avoided the reads. - */ - BLOOM_FILTER_FULL_POSITIVE((byte) 0x63), - - /** - * # of times bloom FullFilter has not avoided the reads and data actually - * exist. - */ - BLOOM_FILTER_FULL_TRUE_POSITIVE((byte) 0x64), - - /** - * BlobDB specific stats - * # of Put/PutTTL/PutUntil to BlobDB. - */ - BLOB_DB_NUM_PUT((byte) 0x65), - - /** - * # of Write to BlobDB. - */ - BLOB_DB_NUM_WRITE((byte) 0x66), - - /** - * # of Get to BlobDB. - */ - BLOB_DB_NUM_GET((byte) 0x67), - - /** - * # of MultiGet to BlobDB. - */ - BLOB_DB_NUM_MULTIGET((byte) 0x68), - - /** - * # of Seek/SeekToFirst/SeekToLast/SeekForPrev to BlobDB iterator. - */ - BLOB_DB_NUM_SEEK((byte) 0x69), - - /** - * # of Next to BlobDB iterator. - */ - BLOB_DB_NUM_NEXT((byte) 0x6A), - - /** - * # of Prev to BlobDB iterator. - */ - BLOB_DB_NUM_PREV((byte) 0x6B), - - /** - * # of keys written to BlobDB. - */ - BLOB_DB_NUM_KEYS_WRITTEN((byte) 0x6C), - - /** - * # of keys read from BlobDB. - */ - BLOB_DB_NUM_KEYS_READ((byte) 0x6D), - - /** - * # of bytes (key + value) written to BlobDB. - */ - BLOB_DB_BYTES_WRITTEN((byte) 0x6E), - - /** - * # of bytes (keys + value) read from BlobDB. - */ - BLOB_DB_BYTES_READ((byte) 0x6F), - - /** - * # of keys written by BlobDB as non-TTL inlined value. - */ - BLOB_DB_WRITE_INLINED((byte) 0x70), - - /** - * # of keys written by BlobDB as TTL inlined value. - */ - BLOB_DB_WRITE_INLINED_TTL((byte) 0x71), - - /** - * # of keys written by BlobDB as non-TTL blob value. - */ - BLOB_DB_WRITE_BLOB((byte) 0x72), - - /** - * # of keys written by BlobDB as TTL blob value. - */ - BLOB_DB_WRITE_BLOB_TTL((byte) 0x73), - - /** - * # of bytes written to blob file. - */ - BLOB_DB_BLOB_FILE_BYTES_WRITTEN((byte) 0x74), - - /** - * # of bytes read from blob file. - */ - BLOB_DB_BLOB_FILE_BYTES_READ((byte) 0x75), - - /** - * # of times a blob files being synced. - */ - BLOB_DB_BLOB_FILE_SYNCED((byte) 0x76), - - /** - * # of blob index evicted from base DB by BlobDB compaction filter because - * of expiration. - */ - BLOB_DB_BLOB_INDEX_EXPIRED_COUNT((byte) 0x77), - - /** - * Size of blob index evicted from base DB by BlobDB compaction filter - * because of expiration. - */ - BLOB_DB_BLOB_INDEX_EXPIRED_SIZE((byte) 0x78), - - /** - * # of blob index evicted from base DB by BlobDB compaction filter because - * of corresponding file deleted. - */ - BLOB_DB_BLOB_INDEX_EVICTED_COUNT((byte) 0x79), - - /** - * Size of blob index evicted from base DB by BlobDB compaction filter - * because of corresponding file deleted. - */ - BLOB_DB_BLOB_INDEX_EVICTED_SIZE((byte) 0x7A), - - /** - * # of blob files being garbage collected. - */ - BLOB_DB_GC_NUM_FILES((byte) 0x7B), - - /** - * # of blob files generated by garbage collection. - */ - BLOB_DB_GC_NUM_NEW_FILES((byte) 0x7C), - - /** - * # of BlobDB garbage collection failures. - */ - BLOB_DB_GC_FAILURES((byte) 0x7D), - - /** - * # of keys relocated to new blob file by garbage collection. - */ - BLOB_DB_GC_NUM_KEYS_RELOCATED((byte) -0x02), - - /** - * # of bytes relocated to new blob file by garbage collection. - */ - BLOB_DB_GC_BYTES_RELOCATED((byte) -0x05), - - /** - * # of blob files evicted because of BlobDB is full. - */ - BLOB_DB_FIFO_NUM_FILES_EVICTED((byte) -0x06), - - /** - * # of keys in the blob files evicted because of BlobDB is full. - */ - BLOB_DB_FIFO_NUM_KEYS_EVICTED((byte) -0x07), - - /** - * # of bytes in the blob files evicted because of BlobDB is full. - */ - BLOB_DB_FIFO_BYTES_EVICTED((byte) -0x08), - - /** - * These counters indicate a performance issue in WritePrepared transactions. - * We should not seem them ticking them much. - * # of times prepare_mutex_ is acquired in the fast path. - */ - TXN_PREPARE_MUTEX_OVERHEAD((byte) -0x09), - - /** - * # of times old_commit_map_mutex_ is acquired in the fast path. - */ - TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD((byte) -0x0A), - - /** - * # of times we checked a batch for duplicate keys. - */ - TXN_DUPLICATE_KEY_OVERHEAD((byte) -0x0B), - - /** - * # of times snapshot_mutex_ is acquired in the fast path. - */ - TXN_SNAPSHOT_MUTEX_OVERHEAD((byte) -0x0C), - - /** - * # of times ::Get returned TryAgain due to expired snapshot seq - */ - TXN_GET_TRY_AGAIN((byte) -0x0D), - - /** - * # of files marked as trash by delete scheduler - */ - FILES_MARKED_TRASH((byte) -0x0E), - - /** - * # of files deleted immediately by delete scheduler - */ - FILES_DELETED_IMMEDIATELY((byte) -0x0f), - - /** - * Compaction read and write statistics broken down by CompactionReason - */ - COMPACT_READ_BYTES_MARKED((byte) -0x10), - COMPACT_READ_BYTES_PERIODIC((byte) -0x11), - COMPACT_READ_BYTES_TTL((byte) -0x12), - COMPACT_WRITE_BYTES_MARKED((byte) -0x13), - COMPACT_WRITE_BYTES_PERIODIC((byte) -0x14), - COMPACT_WRITE_BYTES_TTL((byte) -0x15), - - /** - * DB error handler statistics - */ - ERROR_HANDLER_BG_ERROR_COUNT((byte) -0x16), - ERROR_HANDLER_BG_IO_ERROR_COUNT((byte) -0x17), - ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT((byte) -0x18), - ERROR_HANDLER_AUTORESUME_COUNT((byte) -0x19), - ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT((byte) -0x1A), - ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT((byte) -0x1B), - - /** - * Bytes of raw data (payload) found on memtable at flush time. - * Contains the sum of garbage payload (bytes that are discarded - * at flush time) and useful payload (bytes of data that will - * eventually be written to SSTable). - */ - MEMTABLE_PAYLOAD_BYTES_AT_FLUSH((byte) -0x1C), - /** - * Outdated bytes of data present on memtable at flush time. - */ - MEMTABLE_GARBAGE_BYTES_AT_FLUSH((byte) -0x1D), - - /** - * Number of secondary cache hits - */ - SECONDARY_CACHE_HITS((byte) -0x1E), - - /** - * Bytes read by `VerifyChecksum()` and `VerifyFileChecksums()` APIs. - */ - VERIFY_CHECKSUM_READ_BYTES((byte) -0x1F), - - /** - * Bytes read/written while creating backups - */ - BACKUP_READ_BYTES((byte) -0x20), - BACKUP_WRITE_BYTES((byte) -0x21), - - /** - * Remote compaction read/write statistics - */ - REMOTE_COMPACT_READ_BYTES((byte) -0x22), - REMOTE_COMPACT_WRITE_BYTES((byte) -0x23), - - /** - * Tiered storage related statistics - */ - HOT_FILE_READ_BYTES((byte) -0x24), - WARM_FILE_READ_BYTES((byte) -0x25), - COLD_FILE_READ_BYTES((byte) -0x26), - HOT_FILE_READ_COUNT((byte) -0x27), - WARM_FILE_READ_COUNT((byte) -0x28), - COLD_FILE_READ_COUNT((byte) -0x29), - - /** - * (non-)last level read statistics - */ - LAST_LEVEL_READ_BYTES((byte) -0x2A), - LAST_LEVEL_READ_COUNT((byte) -0x2B), - NON_LAST_LEVEL_READ_BYTES((byte) -0x2C), - NON_LAST_LEVEL_READ_COUNT((byte) -0x2D), - - BLOCK_CHECKSUM_COMPUTE_COUNT((byte) -0x2E), - - /** - * # of times cache miss when accessing blob from blob cache. - */ - BLOB_DB_CACHE_MISS((byte) -0x2F), - - /** - * # of times cache hit when accessing blob from blob cache. - */ - BLOB_DB_CACHE_HIT((byte) -0x30), - - /** - * # of data blocks added to blob cache. - */ - BLOB_DB_CACHE_ADD((byte) -0x31), - - /** - * # # of failures when adding blobs to blob cache. - */ - BLOB_DB_CACHE_ADD_FAILURES((byte) -0x32), - - /** - * # of bytes read from blob cache. - */ - BLOB_DB_CACHE_BYTES_READ((byte) -0x33), - - /** - * # of bytes written into blob cache. - */ - BLOB_DB_CACHE_BYTES_WRITE((byte) -0x34), - - /** - * Number of lookup into the prefetched tail (see - * `TABLE_OPEN_PREFETCH_TAIL_READ_BYTES`) - * that can't find its data for table open - */ - TABLE_OPEN_PREFETCH_TAIL_MISS((byte) -0x3A), - - /** - * Number of lookup into the prefetched tail (see - * `TABLE_OPEN_PREFETCH_TAIL_READ_BYTES`) - * that finds its data for table open - */ - TABLE_OPEN_PREFETCH_TAIL_HIT((byte) -0x3B), - - TICKER_ENUM_MAX((byte) 0x5F); - - private final byte value; - - TickerType(final byte value) { - this.value = value; - } - - /** - * Returns the byte value of the enumerations value - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - * Get Ticker type by byte value. - * - * @param value byte representation of TickerType. - * - * @return {@link org.rocksdb.TickerType} instance. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static TickerType getTickerType(final byte value) { - for (final TickerType tickerType : TickerType.values()) { - if (tickerType.getValue() == value) { - return tickerType; - } - } - throw new IllegalArgumentException( - "Illegal value provided for TickerType."); - } -} diff --git a/java/src/main/java/org/rocksdb/TimedEnv.java b/java/src/main/java/org/rocksdb/TimedEnv.java deleted file mode 100644 index dc8b5d6ef..000000000 --- a/java/src/main/java/org/rocksdb/TimedEnv.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Timed environment. - */ -public class TimedEnv extends Env { - - /** - *

    Creates a new environment that measures function call times for - * filesystem operations, reporting results to variables in PerfContext.

    - * - * - *

    The caller must delete the result when it is - * no longer needed.

    - * - * @param baseEnv the base environment, - * must remain live while the result is in use. - */ - public TimedEnv(final Env baseEnv) { - super(createTimedEnv(baseEnv.nativeHandle_)); - } - - private static native long createTimedEnv(final long baseEnvHandle); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/TraceOptions.java b/java/src/main/java/org/rocksdb/TraceOptions.java deleted file mode 100644 index cf5f7bbe1..000000000 --- a/java/src/main/java/org/rocksdb/TraceOptions.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * TraceOptions is used for - * {@link RocksDB#startTrace(TraceOptions, AbstractTraceWriter)}. - */ -public class TraceOptions { - private final long maxTraceFileSize; - - public TraceOptions() { - this.maxTraceFileSize = 64L * 1024L * 1024L * 1024L; // 64 GB - } - - public TraceOptions(final long maxTraceFileSize) { - this.maxTraceFileSize = maxTraceFileSize; - } - - /** - * To avoid the trace file size grows larger than the storage space, - * user can set the max trace file size in Bytes. Default is 64 GB. - * - * @return the max trace size - */ - public long getMaxTraceFileSize() { - return maxTraceFileSize; - } -} diff --git a/java/src/main/java/org/rocksdb/TraceWriter.java b/java/src/main/java/org/rocksdb/TraceWriter.java deleted file mode 100644 index cb0234e9b..000000000 --- a/java/src/main/java/org/rocksdb/TraceWriter.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * TraceWriter allows exporting RocksDB traces to any system, - * one operation at a time. - */ -public interface TraceWriter { - - /** - * Write the data. - * - * @param data the data - * - * @throws RocksDBException if an error occurs whilst writing. - */ - void write(final Slice data) throws RocksDBException; - - /** - * Close the writer. - * - * @throws RocksDBException if an error occurs whilst closing the writer. - */ - void closeWriter() throws RocksDBException; - - /** - * Get the size of the file that this writer is writing to. - * - * @return the file size - */ - long getFileSize(); -} diff --git a/java/src/main/java/org/rocksdb/Transaction.java b/java/src/main/java/org/rocksdb/Transaction.java deleted file mode 100644 index b2cc8a932..000000000 --- a/java/src/main/java/org/rocksdb/Transaction.java +++ /dev/null @@ -1,2170 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Provides BEGIN/COMMIT/ROLLBACK transactions. - * - * To use transactions, you must first create either an - * {@link OptimisticTransactionDB} or a {@link TransactionDB} - * - * To create a transaction, use - * {@link OptimisticTransactionDB#beginTransaction(org.rocksdb.WriteOptions)} or - * {@link TransactionDB#beginTransaction(org.rocksdb.WriteOptions)} - * - * It is up to the caller to synchronize access to this object. - * - * See samples/src/main/java/OptimisticTransactionSample.java and - * samples/src/main/java/TransactionSample.java for some simple - * examples. - */ -public class Transaction extends RocksObject { - - private final RocksDB parent; - - /** - * Intentionally package private - * as this is called from - * {@link OptimisticTransactionDB#beginTransaction(org.rocksdb.WriteOptions)} - * or {@link TransactionDB#beginTransaction(org.rocksdb.WriteOptions)} - * - * @param parent This must be either {@link TransactionDB} or - * {@link OptimisticTransactionDB} - * @param transactionHandle The native handle to the underlying C++ - * transaction object - */ - Transaction(final RocksDB parent, final long transactionHandle) { - super(transactionHandle); - this.parent = parent; - } - - /** - * If a transaction has a snapshot set, the transaction will ensure that - * any keys successfully written (or fetched via {@link #getForUpdate}) have - * not been modified outside of this transaction since the time the snapshot - * was set. - * - * If a snapshot has not been set, the transaction guarantees that keys have - * not been modified since the time each key was first written (or fetched via - * {@link #getForUpdate}). - * - * Using {@link #setSnapshot()} will provide stricter isolation guarantees - * at the expense of potentially more transaction failures due to conflicts - * with other writes. - * - * Calling {@link #setSnapshot()} has no effect on keys written before this - * function has been called. - * - * {@link #setSnapshot()} may be called multiple times if you would like to - * change the snapshot used for different operations in this transaction. - * - * Calling {@link #setSnapshot()} will not affect the version of Data returned - * by get(...) methods. See {@link #get} for more details. - */ - public void setSnapshot() { - assert(isOwningHandle()); - setSnapshot(nativeHandle_); - } - - /** - * Similar to {@link #setSnapshot()}, but will not change the current snapshot - * until put/merge/delete/getForUpdate/multiGetForUpdate is called. - * By calling this function, the transaction will essentially call - * {@link #setSnapshot()} for you right before performing the next - * write/getForUpdate. - * - * Calling {@link #setSnapshotOnNextOperation()} will not affect what - * snapshot is returned by {@link #getSnapshot} until the next - * write/getForUpdate is executed. - * - * When the snapshot is created the notifier's snapshotCreated method will - * be called so that the caller can get access to the snapshot. - * - * This is an optimization to reduce the likelihood of conflicts that - * could occur in between the time {@link #setSnapshot()} is called and the - * first write/getForUpdate operation. i.e. this prevents the following - * race-condition: - * - * txn1->setSnapshot(); - * txn2->put("A", ...); - * txn2->commit(); - * txn1->getForUpdate(opts, "A", ...); * FAIL! - */ - public void setSnapshotOnNextOperation() { - assert(isOwningHandle()); - setSnapshotOnNextOperation(nativeHandle_); - } - - /** - * Similar to {@link #setSnapshot()}, but will not change the current snapshot - * until put/merge/delete/getForUpdate/multiGetForUpdate is called. - * By calling this function, the transaction will essentially call - * {@link #setSnapshot()} for you right before performing the next - * write/getForUpdate. - * - * Calling {@link #setSnapshotOnNextOperation()} will not affect what - * snapshot is returned by {@link #getSnapshot} until the next - * write/getForUpdate is executed. - * - * When the snapshot is created the - * {@link AbstractTransactionNotifier#snapshotCreated(Snapshot)} method will - * be called so that the caller can get access to the snapshot. - * - * This is an optimization to reduce the likelihood of conflicts that - * could occur in between the time {@link #setSnapshot()} is called and the - * first write/getForUpdate operation. i.e. this prevents the following - * race-condition: - * - * txn1->setSnapshot(); - * txn2->put("A", ...); - * txn2->commit(); - * txn1->getForUpdate(opts, "A", ...); * FAIL! - * - * @param transactionNotifier A handler for receiving snapshot notifications - * for the transaction - * - */ - public void setSnapshotOnNextOperation( - final AbstractTransactionNotifier transactionNotifier) { - assert(isOwningHandle()); - setSnapshotOnNextOperation(nativeHandle_, transactionNotifier.nativeHandle_); - } - - /** - * Returns the Snapshot created by the last call to {@link #setSnapshot()}. - * - * REQUIRED: The returned Snapshot is only valid up until the next time - * {@link #setSnapshot()}/{@link #setSnapshotOnNextOperation()} is called, - * {@link #clearSnapshot()} is called, or the Transaction is deleted. - * - * @return The snapshot or null if there is no snapshot - */ - public Snapshot getSnapshot() { - assert(isOwningHandle()); - final long snapshotNativeHandle = getSnapshot(nativeHandle_); - if(snapshotNativeHandle == 0) { - return null; - } else { - final Snapshot snapshot = new Snapshot(snapshotNativeHandle); - return snapshot; - } - } - - /** - * Clears the current snapshot (i.e. no snapshot will be 'set') - * - * This removes any snapshot that currently exists or is set to be created - * on the next update operation ({@link #setSnapshotOnNextOperation()}). - * - * Calling {@link #clearSnapshot()} has no effect on keys written before this - * function has been called. - * - * If a reference to a snapshot was retrieved via {@link #getSnapshot()}, it - * will no longer be valid and should be discarded after a call to - * {@link #clearSnapshot()}. - */ - public void clearSnapshot() { - assert(isOwningHandle()); - clearSnapshot(nativeHandle_); - } - - /** - * Prepare the current transaction for 2PC - */ - public void prepare() throws RocksDBException { - //TODO(AR) consider a Java'ish version of this function, which returns an AutoCloseable (commit) - assert(isOwningHandle()); - prepare(nativeHandle_); - } - - /** - * Write all batched keys to the db atomically. - * - * Returns OK on success. - * - * May return any error status that could be returned by DB:Write(). - * - * If this transaction was created by an {@link OptimisticTransactionDB} - * Status::Busy() may be returned if the transaction could not guarantee - * that there are no write conflicts. Status::TryAgain() may be returned - * if the memtable history size is not large enough - * (See max_write_buffer_number_to_maintain). - * - * If this transaction was created by a {@link TransactionDB}, - * Status::Expired() may be returned if this transaction has lived for - * longer than {@link TransactionOptions#getExpiration()}. - * - * @throws RocksDBException if an error occurs when committing the transaction - */ - public void commit() throws RocksDBException { - assert(isOwningHandle()); - commit(nativeHandle_); - } - - /** - * Discard all batched writes in this transaction. - * - * @throws RocksDBException if an error occurs when rolling back the transaction - */ - public void rollback() throws RocksDBException { - assert(isOwningHandle()); - rollback(nativeHandle_); - } - - /** - * Records the state of the transaction for future calls to - * {@link #rollbackToSavePoint()}. - * - * May be called multiple times to set multiple save points. - * - * @throws RocksDBException if an error occurs whilst setting a save point - */ - public void setSavePoint() throws RocksDBException { - assert(isOwningHandle()); - setSavePoint(nativeHandle_); - } - - /** - * Undo all operations in this transaction (put, merge, delete, putLogData) - * since the most recent call to {@link #setSavePoint()} and removes the most - * recent {@link #setSavePoint()}. - * - * If there is no previous call to {@link #setSavePoint()}, - * returns Status::NotFound() - * - * @throws RocksDBException if an error occurs when rolling back to a save point - */ - public void rollbackToSavePoint() throws RocksDBException { - assert(isOwningHandle()); - rollbackToSavePoint(nativeHandle_); - } - - /** - * This function is similar to - * {@link RocksDB#get(ColumnFamilyHandle, ReadOptions, byte[])} except it will - * also read pending changes in this transaction. - * Currently, this function will return Status::MergeInProgress if the most - * recent write to the queried key in this batch is a Merge. - * - * If {@link ReadOptions#snapshot()} is not set, the current version of the - * key will be read. Calling {@link #setSnapshot()} does not affect the - * version of the data returned. - * - * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect - * what is read from the DB but will NOT change which keys are read from this - * transaction (the keys in this transaction do not yet belong to any snapshot - * and will be fetched regardless). - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance - * @param readOptions Read options. - * @param key the key to retrieve the value for. - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying native - * library. - */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions readOptions, final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - return get(nativeHandle_, readOptions.nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * This function is similar to - * {@link RocksDB#get(ReadOptions, byte[])} except it will - * also read pending changes in this transaction. - * Currently, this function will return Status::MergeInProgress if the most - * recent write to the queried key in this batch is a Merge. - * - * If {@link ReadOptions#snapshot()} is not set, the current version of the - * key will be read. Calling {@link #setSnapshot()} does not affect the - * version of the data returned. - * - * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect - * what is read from the DB but will NOT change which keys are read from this - * transaction (the keys in this transaction do not yet belong to any snapshot - * and will be fetched regardless). - * - * @param readOptions Read options. - * @param key the key to retrieve the value for. - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying native - * library. - */ - public byte[] get(final ReadOptions readOptions, final byte[] key) - throws RocksDBException { - assert(isOwningHandle()); - return get(nativeHandle_, readOptions.nativeHandle_, key, key.length); - } - - /** - * This function is similar to - * {@link RocksDB#multiGetAsList} except it will - * also read pending changes in this transaction. - * Currently, this function will return Status::MergeInProgress if the most - * recent write to the queried key in this batch is a Merge. - * - * If {@link ReadOptions#snapshot()} is not set, the current version of the - * key will be read. Calling {@link #setSnapshot()} does not affect the - * version of the data returned. - * - * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect - * what is read from the DB but will NOT change which keys are read from this - * transaction (the keys in this transaction do not yet belong to any snapshot - * and will be fetched regardless). - * - * @param readOptions Read options. - * @param columnFamilyHandles {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys of keys for which values need to be retrieved. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IllegalArgumentException thrown if the size of passed keys is not - * equal to the amount of passed column family handles. - */ - @Deprecated - public byte[][] multiGet(final ReadOptions readOptions, - final List columnFamilyHandles, final byte[][] keys) - throws RocksDBException { - assert(isOwningHandle()); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.length != columnFamilyHandles.size()) { - throw new IllegalArgumentException( - "For each key there must be a ColumnFamilyHandle."); - } - if(keys.length == 0) { - return new byte[0][0]; - } - final long[] cfHandles = new long[columnFamilyHandles.size()]; - for (int i = 0; i < columnFamilyHandles.size(); i++) { - cfHandles[i] = columnFamilyHandles.get(i).nativeHandle_; - } - - return multiGet(nativeHandle_, readOptions.nativeHandle_, - keys, cfHandles); - } - - /** - * This function is similar to - * {@link RocksDB#multiGetAsList(ReadOptions, List, List)} except it will - * also read pending changes in this transaction. - * Currently, this function will return Status::MergeInProgress if the most - * recent write to the queried key in this batch is a Merge. - * - * If {@link ReadOptions#snapshot()} is not set, the current version of the - * key will be read. Calling {@link #setSnapshot()} does not affect the - * version of the data returned. - * - * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect - * what is read from the DB but will NOT change which keys are read from this - * transaction (the keys in this transaction do not yet belong to any snapshot - * and will be fetched regardless). - * - * @param readOptions Read options. - * @param columnFamilyHandles {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys of keys for which values need to be retrieved. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IllegalArgumentException thrown if the size of passed keys is not - * equal to the amount of passed column family handles. - */ - - public List multiGetAsList(final ReadOptions readOptions, - final List columnFamilyHandles, final List keys) - throws RocksDBException { - assert (isOwningHandle()); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size() != columnFamilyHandles.size()) { - throw new IllegalArgumentException("For each key there must be a ColumnFamilyHandle."); - } - if (keys.size() == 0) { - return new ArrayList<>(0); - } - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final long[] cfHandles = new long[columnFamilyHandles.size()]; - for (int i = 0; i < columnFamilyHandles.size(); i++) { - cfHandles[i] = columnFamilyHandles.get(i).nativeHandle_; - } - - return Arrays.asList(multiGet(nativeHandle_, readOptions.nativeHandle_, keysArray, cfHandles)); - } - - /** - * This function is similar to - * {@link RocksDB#multiGetAsList} except it will - * also read pending changes in this transaction. - * Currently, this function will return Status::MergeInProgress if the most - * recent write to the queried key in this batch is a Merge. - * - * If {@link ReadOptions#snapshot()} is not set, the current version of the - * key will be read. Calling {@link #setSnapshot()} does not affect the - * version of the data returned. - * - * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect - * what is read from the DB but will NOT change which keys are read from this - * transaction (the keys in this transaction do not yet belong to any snapshot - * and will be fetched regardless). - * - * @param readOptions Read options.= - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys of keys for which values need to be retrieved. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Deprecated - public byte[][] multiGet(final ReadOptions readOptions, final byte[][] keys) - throws RocksDBException { - assert(isOwningHandle()); - if(keys.length == 0) { - return new byte[0][0]; - } - - return multiGet(nativeHandle_, readOptions.nativeHandle_, - keys); - } - - /** - * This function is similar to - * {@link RocksDB#multiGetAsList} except it will - * also read pending changes in this transaction. - * Currently, this function will return Status::MergeInProgress if the most - * recent write to the queried key in this batch is a Merge. - * - * If {@link ReadOptions#snapshot()} is not set, the current version of the - * key will be read. Calling {@link #setSnapshot()} does not affect the - * version of the data returned. - * - * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect - * what is read from the DB but will NOT change which keys are read from this - * transaction (the keys in this transaction do not yet belong to any snapshot - * and will be fetched regardless). - * - * @param readOptions Read options.= - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys of keys for which values need to be retrieved. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List multiGetAsList(final ReadOptions readOptions, final List keys) - throws RocksDBException { - if (keys.size() == 0) { - return new ArrayList<>(0); - } - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - - return Arrays.asList(multiGet(nativeHandle_, readOptions.nativeHandle_, keysArray)); - } - - /** - * Read this key and ensure that this transaction will only - * be able to be committed if this key is not written outside this - * transaction after it has first been read (or after the snapshot if a - * snapshot is set in this transaction). The transaction behavior is the - * same regardless of whether the key exists or not. - * - * Note: Currently, this function will return Status::MergeInProgress - * if the most recent write to the queried key in this batch is a Merge. - * - * The values returned by this function are similar to - * {@link RocksDB#get(ColumnFamilyHandle, ReadOptions, byte[])}. - * If value==nullptr, then this function will not read any data, but will - * still ensure that this key cannot be written to by outside of this - * transaction. - * - * If this transaction was created by an {@link OptimisticTransactionDB}, - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)} - * could cause {@link #commit()} to fail. Otherwise, it could return any error - * that could be returned by - * {@link RocksDB#get(ColumnFamilyHandle, ReadOptions, byte[])}. - * - * If this transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * {@link Status.Code#MergeInProgress} if merge operations cannot be - * resolved. - * - * @param readOptions Read options. - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key to retrieve the value for. - * @param exclusive true if the transaction should have exclusive access to - * the key, otherwise false for shared access. - * @param doValidate true if it should validate the snapshot before doing the read - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] getForUpdate(final ReadOptions readOptions, - final ColumnFamilyHandle columnFamilyHandle, final byte[] key, final boolean exclusive, - final boolean doValidate) throws RocksDBException { - assert (isOwningHandle()); - return getForUpdate(nativeHandle_, readOptions.nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_, exclusive, doValidate); - } - - /** - * Same as - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean, boolean)} - * with doValidate=true. - * - * @param readOptions Read options. - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key to retrieve the value for. - * @param exclusive true if the transaction should have exclusive access to - * the key, otherwise false for shared access. - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] getForUpdate(final ReadOptions readOptions, - final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final boolean exclusive) throws RocksDBException { - assert(isOwningHandle()); - return getForUpdate(nativeHandle_, readOptions.nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_, exclusive, true /*doValidate*/); - } - - /** - * Read this key and ensure that this transaction will only - * be able to be committed if this key is not written outside this - * transaction after it has first been read (or after the snapshot if a - * snapshot is set in this transaction). The transaction behavior is the - * same regardless of whether the key exists or not. - * - * Note: Currently, this function will return Status::MergeInProgress - * if the most recent write to the queried key in this batch is a Merge. - * - * The values returned by this function are similar to - * {@link RocksDB#get(ReadOptions, byte[])}. - * If value==nullptr, then this function will not read any data, but will - * still ensure that this key cannot be written to by outside of this - * transaction. - * - * If this transaction was created on an {@link OptimisticTransactionDB}, - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)} - * could cause {@link #commit()} to fail. Otherwise, it could return any error - * that could be returned by - * {@link RocksDB#get(ReadOptions, byte[])}. - * - * If this transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * {@link Status.Code#MergeInProgress} if merge operations cannot be - * resolved. - * - * @param readOptions Read options. - * @param key the key to retrieve the value for. - * @param exclusive true if the transaction should have exclusive access to - * the key, otherwise false for shared access. - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] getForUpdate(final ReadOptions readOptions, final byte[] key, - final boolean exclusive) throws RocksDBException { - assert(isOwningHandle()); - return getForUpdate( - nativeHandle_, readOptions.nativeHandle_, key, key.length, exclusive, true /*doValidate*/); - } - - /** - * A multi-key version of - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}. - * - * - * @param readOptions Read options. - * @param columnFamilyHandles {@link org.rocksdb.ColumnFamilyHandle} - * instances - * @param keys the keys to retrieve the values for. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Deprecated - public byte[][] multiGetForUpdate(final ReadOptions readOptions, - final List columnFamilyHandles, final byte[][] keys) - throws RocksDBException { - assert(isOwningHandle()); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.length != columnFamilyHandles.size()){ - throw new IllegalArgumentException( - "For each key there must be a ColumnFamilyHandle."); - } - if(keys.length == 0) { - return new byte[0][0]; - } - final long[] cfHandles = new long[columnFamilyHandles.size()]; - for (int i = 0; i < columnFamilyHandles.size(); i++) { - cfHandles[i] = columnFamilyHandles.get(i).nativeHandle_; - } - return multiGetForUpdate(nativeHandle_, readOptions.nativeHandle_, - keys, cfHandles); - } - - /** - * A multi-key version of - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}. - * - * - * @param readOptions Read options. - * @param columnFamilyHandles {@link org.rocksdb.ColumnFamilyHandle} - * instances - * @param keys the keys to retrieve the values for. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List multiGetForUpdateAsList(final ReadOptions readOptions, - final List columnFamilyHandles, final List keys) - throws RocksDBException { - assert (isOwningHandle()); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size() != columnFamilyHandles.size()) { - throw new IllegalArgumentException("For each key there must be a ColumnFamilyHandle."); - } - if (keys.size() == 0) { - return new ArrayList<>(); - } - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - - final long[] cfHandles = new long[columnFamilyHandles.size()]; - for (int i = 0; i < columnFamilyHandles.size(); i++) { - cfHandles[i] = columnFamilyHandles.get(i).nativeHandle_; - } - return Arrays.asList( - multiGetForUpdate(nativeHandle_, readOptions.nativeHandle_, keysArray, cfHandles)); - } - - /** - * A multi-key version of {@link #getForUpdate(ReadOptions, byte[], boolean)}. - * - * - * @param readOptions Read options. - * @param keys the keys to retrieve the values for. - * - * @return Array of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Deprecated - public byte[][] multiGetForUpdate(final ReadOptions readOptions, final byte[][] keys) - throws RocksDBException { - assert(isOwningHandle()); - if(keys.length == 0) { - return new byte[0][0]; - } - - return multiGetForUpdate(nativeHandle_, - readOptions.nativeHandle_, keys); - } - - /** - * A multi-key version of {@link #getForUpdate(ReadOptions, byte[], boolean)}. - * - * - * @param readOptions Read options. - * @param keys the keys to retrieve the values for. - * - * @return List of values, one for each key - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public List multiGetForUpdateAsList( - final ReadOptions readOptions, final List keys) throws RocksDBException { - assert (isOwningHandle()); - if (keys.size() == 0) { - return new ArrayList<>(0); - } - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - - return Arrays.asList(multiGetForUpdate(nativeHandle_, readOptions.nativeHandle_, keysArray)); - } - - /** - * Returns an iterator that will iterate on all keys in the default - * column family including both keys in the DB and uncommitted keys in this - * transaction. - * - * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is read - * from the DB but will NOT change which keys are read from this transaction - * (the keys in this transaction do not yet belong to any snapshot and will be - * fetched regardless). - * - * Caller is responsible for deleting the returned Iterator. - * - * The returned iterator is only valid until {@link #commit()}, - * {@link #rollback()}, or {@link #rollbackToSavePoint()} is called. - * - * @param readOptions Read options. - * - * @return instance of iterator object. - */ - public RocksIterator getIterator(final ReadOptions readOptions) { - assert(isOwningHandle()); - return new RocksIterator(parent, getIterator(nativeHandle_, - readOptions.nativeHandle_)); - } - - /** - * Returns an iterator that will iterate on all keys in the column family - * specified by {@code columnFamilyHandle} including both keys in the DB - * and uncommitted keys in this transaction. - * - * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is read - * from the DB but will NOT change which keys are read from this transaction - * (the keys in this transaction do not yet belong to any snapshot and will be - * fetched regardless). - * - * Caller is responsible for calling {@link RocksIterator#close()} on - * the returned Iterator. - * - * The returned iterator is only valid until {@link #commit()}, - * {@link #rollback()}, or {@link #rollbackToSavePoint()} is called. - * - * @param readOptions Read options. - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * - * @return instance of iterator object. - */ - public RocksIterator getIterator(final ReadOptions readOptions, - final ColumnFamilyHandle columnFamilyHandle) { - assert(isOwningHandle()); - return new RocksIterator(parent, getIterator(nativeHandle_, - readOptions.nativeHandle_, columnFamilyHandle.nativeHandle_)); - } - - /** - * Similar to {@link RocksDB#put(ColumnFamilyHandle, byte[], byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to put the key/value into - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the the key was previous tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final byte[] value, final boolean assumeTracked) throws RocksDBException { - assert (isOwningHandle()); - put(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_, assumeTracked); - } - - /** - * Similar to {@link #put(ColumnFamilyHandle, byte[], byte[], boolean)} - * but with {@code assumeTracked = false}. - * - * Will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to put the key/value into - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final byte[] value) throws RocksDBException { - assert(isOwningHandle()); - put(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_, false); - } - - /** - * Similar to {@link RocksDB#put(byte[], byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void put(final byte[] key, final byte[] value) - throws RocksDBException { - assert(isOwningHandle()); - put(nativeHandle_, key, key.length, value, value.length); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #put(ColumnFamilyHandle, byte[], byte[])} but allows - * you to specify the key and value in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to put the key/value into - * @param keyParts the specified key to be inserted. - * @param valueParts the value associated with the specified key. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the the key was previous tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts, final byte[][] valueParts, - final boolean assumeTracked) throws RocksDBException { - assert (isOwningHandle()); - put(nativeHandle_, keyParts, keyParts.length, valueParts, valueParts.length, - columnFamilyHandle.nativeHandle_, assumeTracked); - } - - /** - * Similar to {@link #put(ColumnFamilyHandle, byte[][], byte[][], boolean)} - * but with with {@code assumeTracked = false}. - * - * Allows you to specify the key and value in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to put the key/value into - * @param keyParts the specified key to be inserted. - * @param valueParts the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void put(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts, final byte[][] valueParts) - throws RocksDBException { - assert(isOwningHandle()); - put(nativeHandle_, keyParts, keyParts.length, valueParts, valueParts.length, - columnFamilyHandle.nativeHandle_, false); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #put(byte[], byte[])} but allows - * you to specify the key and value in several parts that will be - * concatenated together - * - * @param keyParts the specified key to be inserted. - * @param valueParts the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void put(final byte[][] keyParts, final byte[][] valueParts) - throws RocksDBException { - assert(isOwningHandle()); - put(nativeHandle_, keyParts, keyParts.length, valueParts, - valueParts.length); - } - - /** - * Similar to {@link RocksDB#merge(ColumnFamilyHandle, byte[], byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to merge the key/value into - * @param key the specified key to be merged. - * @param value the value associated with the specified key. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the the key was previous tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value, final boolean assumeTracked) - throws RocksDBException { - assert (isOwningHandle()); - merge(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_, assumeTracked); - } - - /** - * Similar to {@link #merge(ColumnFamilyHandle, byte[], byte[], boolean)} - * but with {@code assumeTracked = false}. - * - * Will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to merge the key/value into - * @param key the specified key to be merged. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value) throws RocksDBException { - assert(isOwningHandle()); - merge(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_, false); - } - - /** - * Similar to {@link RocksDB#merge(byte[], byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param key the specified key to be merged. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void merge(final byte[] key, final byte[] value) - throws RocksDBException { - assert(isOwningHandle()); - merge(nativeHandle_, key, key.length, value, value.length); - } - - /** - * Similar to {@link RocksDB#delete(ColumnFamilyHandle, byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param key the specified key to be deleted. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the the key was previous tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final boolean assumeTracked) throws RocksDBException { - assert (isOwningHandle()); - delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_, - assumeTracked); - } - - /** - * Similar to {@link #delete(ColumnFamilyHandle, byte[], boolean)} - * but with {@code assumeTracked = false}. - * - * Will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param key the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_, - /*assumeTracked*/ false); - } - - /** - * Similar to {@link RocksDB#delete(byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param key the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void delete(final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - delete(nativeHandle_, key, key.length); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #delete(ColumnFamilyHandle, byte[])} but allows - * you to specify the key in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param keyParts the specified key to be deleted. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the the key was previous tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts, final boolean assumeTracked) - throws RocksDBException { - assert (isOwningHandle()); - delete(nativeHandle_, keyParts, keyParts.length, - columnFamilyHandle.nativeHandle_, assumeTracked); - } - - /** - * Similar to{@link #delete(ColumnFamilyHandle, byte[][], boolean)} - * but with {@code assumeTracked = false}. - * - * Allows you to specify the key in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param keyParts the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts) throws RocksDBException { - assert(isOwningHandle()); - delete(nativeHandle_, keyParts, keyParts.length, - columnFamilyHandle.nativeHandle_, false); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #delete(byte[])} but allows - * you to specify key the in several parts that will be - * concatenated together. - * - * @param keyParts the specified key to be deleted - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void delete(final byte[][] keyParts) throws RocksDBException { - assert(isOwningHandle()); - delete(nativeHandle_, keyParts, keyParts.length); - } - - /** - * Similar to {@link RocksDB#singleDelete(ColumnFamilyHandle, byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param key the specified key to be deleted. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the key was previously tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final boolean assumeTracked) throws RocksDBException { - assert (isOwningHandle()); - singleDelete(nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_, assumeTracked); - } - - /** - * Similar to {@link #singleDelete(ColumnFamilyHandle, byte[], boolean)} - * but with {@code assumeTracked = false}. - * - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param key the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - singleDelete(nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_, false); - } - - /** - * Similar to {@link RocksDB#singleDelete(byte[])}, but - * will also perform conflict checking on the keys be written. - * - * If this Transaction was created on an {@link OptimisticTransactionDB}, - * these functions should always succeed. - * - * If this Transaction was created on a {@link TransactionDB}, an - * {@link RocksDBException} may be thrown with an accompanying {@link Status} - * when: - * {@link Status.Code#Busy} if there is a write conflict, - * {@link Status.Code#TimedOut} if a lock could not be acquired, - * {@link Status.Code#TryAgain} if the memtable history size is not large - * enough. See - * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} - * - * @param key the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - singleDelete(nativeHandle_, key, key.length); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #singleDelete(ColumnFamilyHandle, byte[])} but allows - * you to specify the key in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param keyParts the specified key to be deleted. - * @param assumeTracked true when it is expected that the key is already - * tracked. More specifically, it means the key was previously tracked - * in the same savepoint, with the same exclusive flag, and at a lower - * sequence number. If valid then it skips ValidateSnapshot, - * throws an error otherwise. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts, final boolean assumeTracked) - throws RocksDBException { - assert (isOwningHandle()); - singleDelete(nativeHandle_, keyParts, keyParts.length, - columnFamilyHandle.nativeHandle_, assumeTracked); - } - - /** - * Similar to{@link #singleDelete(ColumnFamilyHandle, byte[][], boolean)} - * but with {@code assumeTracked = false}. - * - * Allows you to specify the key in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param keyParts the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts) throws RocksDBException { - assert(isOwningHandle()); - singleDelete(nativeHandle_, keyParts, keyParts.length, - columnFamilyHandle.nativeHandle_, false); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #singleDelete(byte[])} but allows - * you to specify the key in several parts that will be - * concatenated together. - * - * @param keyParts the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final byte[][] keyParts) throws RocksDBException { - assert(isOwningHandle()); - singleDelete(nativeHandle_, keyParts, keyParts.length); - } - - /** - * Similar to {@link RocksDB#put(ColumnFamilyHandle, byte[], byte[])}, - * but operates on the transactions write batch. This write will only happen - * if this transaction gets committed successfully. - * - * Unlike {@link #put(ColumnFamilyHandle, byte[], byte[])} no conflict - * checking will be performed for this key. - * - * If this Transaction was created on a {@link TransactionDB}, this function - * will still acquire locks necessary to make sure this write doesn't cause - * conflicts in other transactions; This may cause a {@link RocksDBException} - * with associated {@link Status.Code#Busy}. - * - * @param columnFamilyHandle The column family to put the key/value into - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void putUntracked(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value) throws RocksDBException { - assert(isOwningHandle()); - putUntracked(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Similar to {@link RocksDB#put(byte[], byte[])}, - * but operates on the transactions write batch. This write will only happen - * if this transaction gets committed successfully. - * - * Unlike {@link #put(byte[], byte[])} no conflict - * checking will be performed for this key. - * - * If this Transaction was created on a {@link TransactionDB}, this function - * will still acquire locks necessary to make sure this write doesn't cause - * conflicts in other transactions; This may cause a {@link RocksDBException} - * with associated {@link Status.Code#Busy}. - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void putUntracked(final byte[] key, final byte[] value) - throws RocksDBException { - assert(isOwningHandle()); - putUntracked(nativeHandle_, key, key.length, value, value.length); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #putUntracked(ColumnFamilyHandle, byte[], byte[])} but - * allows you to specify the key and value in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to put the key/value into - * @param keyParts the specified key to be inserted. - * @param valueParts the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void putUntracked(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts, final byte[][] valueParts) - throws RocksDBException { - assert(isOwningHandle()); - putUntracked(nativeHandle_, keyParts, keyParts.length, valueParts, - valueParts.length, columnFamilyHandle.nativeHandle_); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #putUntracked(byte[], byte[])} but - * allows you to specify the key and value in several parts that will be - * concatenated together. - * - * @param keyParts the specified key to be inserted. - * @param valueParts the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void putUntracked(final byte[][] keyParts, final byte[][] valueParts) - throws RocksDBException { - assert(isOwningHandle()); - putUntracked(nativeHandle_, keyParts, keyParts.length, valueParts, - valueParts.length); - } - - /** - * Similar to {@link RocksDB#merge(ColumnFamilyHandle, byte[], byte[])}, - * but operates on the transactions write batch. This write will only happen - * if this transaction gets committed successfully. - * - * Unlike {@link #merge(ColumnFamilyHandle, byte[], byte[])} no conflict - * checking will be performed for this key. - * - * If this Transaction was created on a {@link TransactionDB}, this function - * will still acquire locks necessary to make sure this write doesn't cause - * conflicts in other transactions; This may cause a {@link RocksDBException} - * with associated {@link Status.Code#Busy}. - * - * @param columnFamilyHandle The column family to merge the key/value into - * @param key the specified key to be merged. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void mergeUntracked(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value) throws RocksDBException { - mergeUntracked(nativeHandle_, key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Similar to {@link RocksDB#merge(byte[], byte[])}, - * but operates on the transactions write batch. This write will only happen - * if this transaction gets committed successfully. - * - * Unlike {@link #merge(byte[], byte[])} no conflict - * checking will be performed for this key. - * - * If this Transaction was created on a {@link TransactionDB}, this function - * will still acquire locks necessary to make sure this write doesn't cause - * conflicts in other transactions; This may cause a {@link RocksDBException} - * with associated {@link Status.Code#Busy}. - * - * @param key the specified key to be merged. - * @param value the value associated with the specified key. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void mergeUntracked(final byte[] key, final byte[] value) - throws RocksDBException { - assert(isOwningHandle()); - mergeUntracked(nativeHandle_, key, key.length, value, value.length); - } - - /** - * Similar to {@link RocksDB#delete(ColumnFamilyHandle, byte[])}, - * but operates on the transactions write batch. This write will only happen - * if this transaction gets committed successfully. - * - * Unlike {@link #delete(ColumnFamilyHandle, byte[])} no conflict - * checking will be performed for this key. - * - * If this Transaction was created on a {@link TransactionDB}, this function - * will still acquire locks necessary to make sure this write doesn't cause - * conflicts in other transactions; This may cause a {@link RocksDBException} - * with associated {@link Status.Code#Busy}. - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param key the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void deleteUntracked(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - deleteUntracked(nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Similar to {@link RocksDB#delete(byte[])}, - * but operates on the transactions write batch. This write will only happen - * if this transaction gets committed successfully. - * - * Unlike {@link #delete(byte[])} no conflict - * checking will be performed for this key. - * - * If this Transaction was created on a {@link TransactionDB}, this function - * will still acquire locks necessary to make sure this write doesn't cause - * conflicts in other transactions; This may cause a {@link RocksDBException} - * with associated {@link Status.Code#Busy}. - * - * @param key the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void deleteUntracked(final byte[] key) throws RocksDBException { - assert(isOwningHandle()); - deleteUntracked(nativeHandle_, key, key.length); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #deleteUntracked(ColumnFamilyHandle, byte[])} but allows - * you to specify the key in several parts that will be - * concatenated together. - * - * @param columnFamilyHandle The column family to delete the key/value from - * @param keyParts the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void deleteUntracked(final ColumnFamilyHandle columnFamilyHandle, - final byte[][] keyParts) throws RocksDBException { - assert(isOwningHandle()); - deleteUntracked(nativeHandle_, keyParts, keyParts.length, - columnFamilyHandle.nativeHandle_); - } - - //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future - /** - * Similar to {@link #deleteUntracked(byte[])} but allows - * you to specify the key in several parts that will be - * concatenated together. - * - * @param keyParts the specified key to be deleted. - * - * @throws RocksDBException when one of the TransactionalDB conditions - * described above occurs, or in the case of an unexpected error - */ - public void deleteUntracked(final byte[][] keyParts) throws RocksDBException { - assert(isOwningHandle()); - deleteUntracked(nativeHandle_, keyParts, keyParts.length); - } - - /** - * Similar to {@link WriteBatch#putLogData(byte[])} - * - * @param blob binary object to be inserted - */ - public void putLogData(final byte[] blob) { - assert(isOwningHandle()); - putLogData(nativeHandle_, blob, blob.length); - } - - /** - * By default, all put/merge/delete operations will be indexed in the - * transaction so that get/getForUpdate/getIterator can search for these - * keys. - * - * If the caller does not want to fetch the keys about to be written, - * they may want to avoid indexing as a performance optimization. - * Calling {@link #disableIndexing()} will turn off indexing for all future - * put/merge/delete operations until {@link #enableIndexing()} is called. - * - * If a key is put/merge/deleted after {@link #disableIndexing()} is called - * and then is fetched via get/getForUpdate/getIterator, the result of the - * fetch is undefined. - */ - public void disableIndexing() { - assert(isOwningHandle()); - disableIndexing(nativeHandle_); - } - - /** - * Re-enables indexing after a previous call to {@link #disableIndexing()} - */ - public void enableIndexing() { - assert(isOwningHandle()); - enableIndexing(nativeHandle_); - } - - /** - * Returns the number of distinct Keys being tracked by this transaction. - * If this transaction was created by a {@link TransactionDB}, this is the - * number of keys that are currently locked by this transaction. - * If this transaction was created by an {@link OptimisticTransactionDB}, - * this is the number of keys that need to be checked for conflicts at commit - * time. - * - * @return the number of distinct Keys being tracked by this transaction - */ - public long getNumKeys() { - assert(isOwningHandle()); - return getNumKeys(nativeHandle_); - } - - /** - * Returns the number of puts that have been applied to this - * transaction so far. - * - * @return the number of puts that have been applied to this transaction - */ - public long getNumPuts() { - assert(isOwningHandle()); - return getNumPuts(nativeHandle_); - } - - /** - * Returns the number of deletes that have been applied to this - * transaction so far. - * - * @return the number of deletes that have been applied to this transaction - */ - public long getNumDeletes() { - assert(isOwningHandle()); - return getNumDeletes(nativeHandle_); - } - - /** - * Returns the number of merges that have been applied to this - * transaction so far. - * - * @return the number of merges that have been applied to this transaction - */ - public long getNumMerges() { - assert(isOwningHandle()); - return getNumMerges(nativeHandle_); - } - - /** - * Returns the elapsed time in milliseconds since this Transaction began. - * - * @return the elapsed time in milliseconds since this transaction began. - */ - public long getElapsedTime() { - assert(isOwningHandle()); - return getElapsedTime(nativeHandle_); - } - - /** - * Fetch the underlying write batch that contains all pending changes to be - * committed. - * - * Note: You should not write or delete anything from the batch directly and - * should only use the functions in the {@link Transaction} class to - * write to this transaction. - * - * @return The write batch - */ - public WriteBatchWithIndex getWriteBatch() { - assert(isOwningHandle()); - final WriteBatchWithIndex writeBatchWithIndex = - new WriteBatchWithIndex(getWriteBatch(nativeHandle_)); - return writeBatchWithIndex; - } - - /** - * Change the value of {@link TransactionOptions#getLockTimeout()} - * (in milliseconds) for this transaction. - * - * Has no effect on OptimisticTransactions. - * - * @param lockTimeout the timeout (in milliseconds) for locks used by this - * transaction. - */ - public void setLockTimeout(final long lockTimeout) { - assert(isOwningHandle()); - setLockTimeout(nativeHandle_, lockTimeout); - } - - /** - * Return the WriteOptions that will be used during {@link #commit()}. - * - * @return the WriteOptions that will be used - */ - public WriteOptions getWriteOptions() { - assert(isOwningHandle()); - final WriteOptions writeOptions = - new WriteOptions(getWriteOptions(nativeHandle_)); - return writeOptions; - } - - /** - * Reset the WriteOptions that will be used during {@link #commit()}. - * - * @param writeOptions The new WriteOptions - */ - public void setWriteOptions(final WriteOptions writeOptions) { - assert(isOwningHandle()); - setWriteOptions(nativeHandle_, writeOptions.nativeHandle_); - } - - /** - * If this key was previously fetched in this transaction using - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}/ - * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, calling - * {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} will tell - * the transaction that it no longer needs to do any conflict checking - * for this key. - * - * If a key has been fetched N times via - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}/ - * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, then - * {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} will only have an - * effect if it is also called N times. If this key has been written to in - * this transaction, {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} - * will have no effect. - * - * If {@link #setSavePoint()} has been called after the - * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}, - * {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} will not have any - * effect. - * - * If this Transaction was created by an {@link OptimisticTransactionDB}, - * calling {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} can affect - * whether this key is conflict checked at commit time. - * If this Transaction was created by a {@link TransactionDB}, - * calling {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} may release - * any held locks for this key. - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key to retrieve the value for. - */ - public void undoGetForUpdate(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) { - assert(isOwningHandle()); - undoGetForUpdate(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); - } - - /** - * If this key was previously fetched in this transaction using - * {@link #getForUpdate(ReadOptions, byte[], boolean)}/ - * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, calling - * {@link #undoGetForUpdate(byte[])} will tell - * the transaction that it no longer needs to do any conflict checking - * for this key. - * - * If a key has been fetched N times via - * {@link #getForUpdate(ReadOptions, byte[], boolean)}/ - * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, then - * {@link #undoGetForUpdate(byte[])} will only have an - * effect if it is also called N times. If this key has been written to in - * this transaction, {@link #undoGetForUpdate(byte[])} - * will have no effect. - * - * If {@link #setSavePoint()} has been called after the - * {@link #getForUpdate(ReadOptions, byte[], boolean)}, - * {@link #undoGetForUpdate(byte[])} will not have any - * effect. - * - * If this Transaction was created by an {@link OptimisticTransactionDB}, - * calling {@link #undoGetForUpdate(byte[])} can affect - * whether this key is conflict checked at commit time. - * If this Transaction was created by a {@link TransactionDB}, - * calling {@link #undoGetForUpdate(byte[])} may release - * any held locks for this key. - * - * @param key the key to retrieve the value for. - */ - public void undoGetForUpdate(final byte[] key) { - assert(isOwningHandle()); - undoGetForUpdate(nativeHandle_, key, key.length); - } - - /** - * Adds the keys from the WriteBatch to the transaction - * - * @param writeBatch The write batch to read from - * - * @throws RocksDBException if an error occurs whilst rebuilding from the - * write batch. - */ - public void rebuildFromWriteBatch(final WriteBatch writeBatch) - throws RocksDBException { - assert(isOwningHandle()); - rebuildFromWriteBatch(nativeHandle_, writeBatch.nativeHandle_); - } - - /** - * Get the Commit time Write Batch. - * - * @return the commit time write batch. - */ - public WriteBatch getCommitTimeWriteBatch() { - assert(isOwningHandle()); - final WriteBatch writeBatch = - new WriteBatch(getCommitTimeWriteBatch(nativeHandle_)); - return writeBatch; - } - - /** - * Set the log number. - * - * @param logNumber the log number - */ - public void setLogNumber(final long logNumber) { - assert(isOwningHandle()); - setLogNumber(nativeHandle_, logNumber); - } - - /** - * Get the log number. - * - * @return the log number - */ - public long getLogNumber() { - assert(isOwningHandle()); - return getLogNumber(nativeHandle_); - } - - /** - * Set the name of the transaction. - * - * @param transactionName the name of the transaction - * - * @throws RocksDBException if an error occurs when setting the transaction - * name. - */ - public void setName(final String transactionName) throws RocksDBException { - assert(isOwningHandle()); - setName(nativeHandle_, transactionName); - } - - /** - * Get the name of the transaction. - * - * @return the name of the transaction - */ - public String getName() { - assert(isOwningHandle()); - return getName(nativeHandle_); - } - - /** - * Get the ID of the transaction. - * - * @return the ID of the transaction. - */ - public long getID() { - assert(isOwningHandle()); - return getID(nativeHandle_); - } - - /** - * Determine if a deadlock has been detected. - * - * @return true if a deadlock has been detected. - */ - public boolean isDeadlockDetect() { - assert(isOwningHandle()); - return isDeadlockDetect(nativeHandle_); - } - - /** - * Get the list of waiting transactions. - * - * @return The list of waiting transactions. - */ - public WaitingTransactions getWaitingTxns() { - assert(isOwningHandle()); - return getWaitingTxns(nativeHandle_); - } - - /** - * Get the execution status of the transaction. - * - * NOTE: The execution status of an Optimistic Transaction - * never changes. This is only useful for non-optimistic transactions! - * - * @return The execution status of the transaction - */ - public TransactionState getState() { - assert(isOwningHandle()); - return TransactionState.getTransactionState( - getState(nativeHandle_)); - } - - /** - * The globally unique id with which the transaction is identified. This id - * might or might not be set depending on the implementation. Similarly the - * implementation decides the point in lifetime of a transaction at which it - * assigns the id. Although currently it is the case, the id is not guaranteed - * to remain the same across restarts. - * - * @return the transaction id. - */ - @Experimental("NOTE: Experimental feature") - public long getId() { - assert(isOwningHandle()); - return getId(nativeHandle_); - } - - public enum TransactionState { - STARTED((byte)0), - AWAITING_PREPARE((byte)1), - PREPARED((byte)2), - AWAITING_COMMIT((byte)3), - COMMITTED((byte)4), - AWAITING_ROLLBACK((byte)5), - ROLLEDBACK((byte)6), - LOCKS_STOLEN((byte)7); - - /* - * Keep old misspelled variable as alias - * Tip from https://stackoverflow.com/a/37092410/454544 - */ - public static final TransactionState COMMITED = COMMITTED; - - private final byte value; - - TransactionState(final byte value) { - this.value = value; - } - - /** - * Get TransactionState by byte value. - * - * @param value byte representation of TransactionState. - * - * @return {@link org.rocksdb.Transaction.TransactionState} instance or null. - * @throws java.lang.IllegalArgumentException if an invalid - * value is provided. - */ - public static TransactionState getTransactionState(final byte value) { - for (final TransactionState transactionState : TransactionState.values()) { - if (transactionState.value == value){ - return transactionState; - } - } - throw new IllegalArgumentException( - "Illegal value provided for TransactionState."); - } - } - - /** - * Called from C++ native method {@link #getWaitingTxns(long)} - * to construct a WaitingTransactions object. - * - * @param columnFamilyId The id of the {@link ColumnFamilyHandle} - * @param key The key - * @param transactionIds The transaction ids - * - * @return The waiting transactions - */ - private WaitingTransactions newWaitingTransactions( - final long columnFamilyId, final String key, - final long[] transactionIds) { - return new WaitingTransactions(columnFamilyId, key, transactionIds); - } - - public static class WaitingTransactions { - private final long columnFamilyId; - private final String key; - private final long[] transactionIds; - - private WaitingTransactions(final long columnFamilyId, final String key, - final long[] transactionIds) { - this.columnFamilyId = columnFamilyId; - this.key = key; - this.transactionIds = transactionIds; - } - - /** - * Get the Column Family ID. - * - * @return The column family ID - */ - public long getColumnFamilyId() { - return columnFamilyId; - } - - /** - * Get the key on which the transactions are waiting. - * - * @return The key - */ - public String getKey() { - return key; - } - - /** - * Get the IDs of the waiting transactions. - * - * @return The IDs of the waiting transactions - */ - public long[] getTransactionIds() { - return transactionIds; - } - } - - private native void setSnapshot(final long handle); - private native void setSnapshotOnNextOperation(final long handle); - private native void setSnapshotOnNextOperation(final long handle, - final long transactionNotifierHandle); - private native long getSnapshot(final long handle); - private native void clearSnapshot(final long handle); - private native void prepare(final long handle) throws RocksDBException; - private native void commit(final long handle) throws RocksDBException; - private native void rollback(final long handle) throws RocksDBException; - private native void setSavePoint(final long handle) throws RocksDBException; - private native void rollbackToSavePoint(final long handle) - throws RocksDBException; - private native byte[] get(final long handle, final long readOptionsHandle, - final byte key[], final int keyLength, final long columnFamilyHandle) - throws RocksDBException; - private native byte[] get(final long handle, final long readOptionsHandle, - final byte key[], final int keyLen) throws RocksDBException; - private native byte[][] multiGet(final long handle, - final long readOptionsHandle, final byte[][] keys, - final long[] columnFamilyHandles) throws RocksDBException; - private native byte[][] multiGet(final long handle, - final long readOptionsHandle, final byte[][] keys) - throws RocksDBException; - private native byte[] getForUpdate(final long handle, final long readOptionsHandle, - final byte key[], final int keyLength, final long columnFamilyHandle, final boolean exclusive, - final boolean doValidate) throws RocksDBException; - private native byte[] getForUpdate(final long handle, final long readOptionsHandle, - final byte key[], final int keyLen, final boolean exclusive, final boolean doValidate) - throws RocksDBException; - private native byte[][] multiGetForUpdate(final long handle, - final long readOptionsHandle, final byte[][] keys, - final long[] columnFamilyHandles) throws RocksDBException; - private native byte[][] multiGetForUpdate(final long handle, - final long readOptionsHandle, final byte[][] keys) - throws RocksDBException; - private native long getIterator(final long handle, - final long readOptionsHandle); - private native long getIterator(final long handle, - final long readOptionsHandle, final long columnFamilyHandle); - private native void put(final long handle, final byte[] key, final int keyLength, - final byte[] value, final int valueLength, final long columnFamilyHandle, - final boolean assumeTracked) throws RocksDBException; - private native void put(final long handle, final byte[] key, - final int keyLength, final byte[] value, final int valueLength) - throws RocksDBException; - private native void put(final long handle, final byte[][] keys, final int keysLength, - final byte[][] values, final int valuesLength, final long columnFamilyHandle, - final boolean assumeTracked) throws RocksDBException; - private native void put(final long handle, final byte[][] keys, - final int keysLength, final byte[][] values, final int valuesLength) - throws RocksDBException; - private native void merge(final long handle, final byte[] key, final int keyLength, - final byte[] value, final int valueLength, final long columnFamilyHandle, - final boolean assumeTracked) throws RocksDBException; - private native void merge(final long handle, final byte[] key, - final int keyLength, final byte[] value, final int valueLength) - throws RocksDBException; - private native void delete(final long handle, final byte[] key, final int keyLength, - final long columnFamilyHandle, final boolean assumeTracked) throws RocksDBException; - private native void delete(final long handle, final byte[] key, - final int keyLength) throws RocksDBException; - private native void delete(final long handle, final byte[][] keys, final int keysLength, - final long columnFamilyHandle, final boolean assumeTracked) throws RocksDBException; - private native void delete(final long handle, final byte[][] keys, - final int keysLength) throws RocksDBException; - private native void singleDelete(final long handle, final byte[] key, final int keyLength, - final long columnFamilyHandle, final boolean assumeTracked) throws RocksDBException; - private native void singleDelete(final long handle, final byte[] key, - final int keyLength) throws RocksDBException; - private native void singleDelete(final long handle, final byte[][] keys, final int keysLength, - final long columnFamilyHandle, final boolean assumeTracked) throws RocksDBException; - private native void singleDelete(final long handle, final byte[][] keys, - final int keysLength) throws RocksDBException; - private native void putUntracked(final long handle, final byte[] key, - final int keyLength, final byte[] value, final int valueLength, - final long columnFamilyHandle) throws RocksDBException; - private native void putUntracked(final long handle, final byte[] key, - final int keyLength, final byte[] value, final int valueLength) - throws RocksDBException; - private native void putUntracked(final long handle, final byte[][] keys, - final int keysLength, final byte[][] values, final int valuesLength, - final long columnFamilyHandle) throws RocksDBException; - private native void putUntracked(final long handle, final byte[][] keys, - final int keysLength, final byte[][] values, final int valuesLength) - throws RocksDBException; - private native void mergeUntracked(final long handle, final byte[] key, - final int keyLength, final byte[] value, final int valueLength, - final long columnFamilyHandle) throws RocksDBException; - private native void mergeUntracked(final long handle, final byte[] key, - final int keyLength, final byte[] value, final int valueLength) - throws RocksDBException; - private native void deleteUntracked(final long handle, final byte[] key, - final int keyLength, final long columnFamilyHandle) - throws RocksDBException; - private native void deleteUntracked(final long handle, final byte[] key, - final int keyLength) throws RocksDBException; - private native void deleteUntracked(final long handle, final byte[][] keys, - final int keysLength, final long columnFamilyHandle) - throws RocksDBException; - private native void deleteUntracked(final long handle, final byte[][] keys, - final int keysLength) throws RocksDBException; - private native void putLogData(final long handle, final byte[] blob, - final int blobLength); - private native void disableIndexing(final long handle); - private native void enableIndexing(final long handle); - private native long getNumKeys(final long handle); - private native long getNumPuts(final long handle); - private native long getNumDeletes(final long handle); - private native long getNumMerges(final long handle); - private native long getElapsedTime(final long handle); - private native long getWriteBatch(final long handle); - private native void setLockTimeout(final long handle, final long lockTimeout); - private native long getWriteOptions(final long handle); - private native void setWriteOptions(final long handle, - final long writeOptionsHandle); - private native void undoGetForUpdate(final long handle, final byte[] key, - final int keyLength, final long columnFamilyHandle); - private native void undoGetForUpdate(final long handle, final byte[] key, - final int keyLength); - private native void rebuildFromWriteBatch(final long handle, - final long writeBatchHandle) throws RocksDBException; - private native long getCommitTimeWriteBatch(final long handle); - private native void setLogNumber(final long handle, final long logNumber); - private native long getLogNumber(final long handle); - private native void setName(final long handle, final String name) - throws RocksDBException; - private native String getName(final long handle); - private native long getID(final long handle); - private native boolean isDeadlockDetect(final long handle); - private native WaitingTransactions getWaitingTxns(final long handle); - private native byte getState(final long handle); - private native long getId(final long handle); - - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/TransactionDB.java b/java/src/main/java/org/rocksdb/TransactionDB.java deleted file mode 100644 index 86f25fe15..000000000 --- a/java/src/main/java/org/rocksdb/TransactionDB.java +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Database with Transaction support - */ -public class TransactionDB extends RocksDB - implements TransactionalDB { - - private TransactionDBOptions transactionDbOptions_; - - /** - * Private constructor. - * - * @param nativeHandle The native handle of the C++ TransactionDB object - */ - private TransactionDB(final long nativeHandle) { - super(nativeHandle); - } - - /** - * Open a TransactionDB, similar to {@link RocksDB#open(Options, String)}. - * - * @param options {@link org.rocksdb.Options} instance. - * @param transactionDbOptions {@link org.rocksdb.TransactionDBOptions} - * instance. - * @param path the path to the rocksdb. - * - * @return a {@link TransactionDB} instance on success, null if the specified - * {@link TransactionDB} can not be opened. - * - * @throws RocksDBException if an error occurs whilst opening the database. - */ - public static TransactionDB open(final Options options, - final TransactionDBOptions transactionDbOptions, final String path) - throws RocksDBException { - final TransactionDB tdb = new TransactionDB(open(options.nativeHandle_, - transactionDbOptions.nativeHandle_, path)); - - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - tdb.storeOptionsInstance(options); - tdb.storeTransactionDbOptions(transactionDbOptions); - - return tdb; - } - - /** - * Open a TransactionDB, similar to - * {@link RocksDB#open(DBOptions, String, List, List)}. - * - * @param dbOptions {@link org.rocksdb.DBOptions} instance. - * @param transactionDbOptions {@link org.rocksdb.TransactionDBOptions} - * instance. - * @param path the path to the rocksdb. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * - * @return a {@link TransactionDB} instance on success, null if the specified - * {@link TransactionDB} can not be opened. - * - * @throws RocksDBException if an error occurs whilst opening the database. - */ - public static TransactionDB open(final DBOptions dbOptions, - final TransactionDBOptions transactionDbOptions, - final String path, - final List columnFamilyDescriptors, - final List columnFamilyHandles) - throws RocksDBException { - - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors - .get(i); - cfNames[i] = cfDescriptor.getName(); - cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; - } - - final long[] handles = open(dbOptions.nativeHandle_, - transactionDbOptions.nativeHandle_, path, cfNames, cfOptionHandles); - final TransactionDB tdb = new TransactionDB(handles[0]); - - // when non-default Options is used, keeping an Options reference - // in RocksDB can prevent Java to GC during the life-time of - // the currently-created RocksDB. - tdb.storeOptionsInstance(dbOptions); - tdb.storeTransactionDbOptions(transactionDbOptions); - - for (int i = 1; i < handles.length; i++) { - columnFamilyHandles.add(new ColumnFamilyHandle(tdb, handles[i])); - } - - return tdb; - } - - /** - * This is similar to {@link #close()} except that it - * throws an exception if any error occurs. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - * - * @throws RocksDBException if an error occurs whilst closing. - */ - public void closeE() throws RocksDBException { - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } finally { - disposeInternal(); - } - } - } - - /** - * This is similar to {@link #closeE()} except that it - * silently ignores any errors. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - */ - @Override - public void close() { - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } catch (final RocksDBException e) { - // silently ignore the error report - } finally { - disposeInternal(); - } - } - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions) { - return new Transaction(this, beginTransaction(nativeHandle_, - writeOptions.nativeHandle_)); - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions, - final TransactionOptions transactionOptions) { - return new Transaction(this, beginTransaction(nativeHandle_, - writeOptions.nativeHandle_, transactionOptions.nativeHandle_)); - } - - // TODO(AR) consider having beingTransaction(... oldTransaction) set a - // reference count inside Transaction, so that we can always call - // Transaction#close but the object is only disposed when there are as many - // closes as beginTransaction. Makes the try-with-resources paradigm easier for - // java developers - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions, - final Transaction oldTransaction) { - final long jtxnHandle = beginTransaction_withOld(nativeHandle_, - writeOptions.nativeHandle_, oldTransaction.nativeHandle_); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_txn - assert(jtxnHandle == oldTransaction.nativeHandle_); - - return oldTransaction; - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions, - final TransactionOptions transactionOptions, - final Transaction oldTransaction) { - final long jtxn_handle = beginTransaction_withOld(nativeHandle_, - writeOptions.nativeHandle_, transactionOptions.nativeHandle_, - oldTransaction.nativeHandle_); - - // RocksJava relies on the assumption that - // we do not allocate a new Transaction object - // when providing an old_txn - assert(jtxn_handle == oldTransaction.nativeHandle_); - - return oldTransaction; - } - - public Transaction getTransactionByName(final String transactionName) { - final long jtxnHandle = getTransactionByName(nativeHandle_, transactionName); - if(jtxnHandle == 0) { - return null; - } - - final Transaction txn = new Transaction(this, jtxnHandle); - - // this instance doesn't own the underlying C++ object - txn.disOwnNativeHandle(); - - return txn; - } - - public List getAllPreparedTransactions() { - final long[] jtxnHandles = getAllPreparedTransactions(nativeHandle_); - - final List txns = new ArrayList<>(); - for(final long jtxnHandle : jtxnHandles) { - final Transaction txn = new Transaction(this, jtxnHandle); - - // this instance doesn't own the underlying C++ object - txn.disOwnNativeHandle(); - - txns.add(txn); - } - return txns; - } - - public static class KeyLockInfo { - private final String key; - private final long[] transactionIDs; - private final boolean exclusive; - - public KeyLockInfo(final String key, final long transactionIDs[], - final boolean exclusive) { - this.key = key; - this.transactionIDs = transactionIDs; - this.exclusive = exclusive; - } - - /** - * Get the key. - * - * @return the key - */ - public String getKey() { - return key; - } - - /** - * Get the Transaction IDs. - * - * @return the Transaction IDs. - */ - public long[] getTransactionIDs() { - return transactionIDs; - } - - /** - * Get the Lock status. - * - * @return true if the lock is exclusive, false if the lock is shared. - */ - public boolean isExclusive() { - return exclusive; - } - } - - /** - * Returns map of all locks held. - * - * @return a map of all the locks held. - */ - public Map getLockStatusData() { - return getLockStatusData(nativeHandle_); - } - - /** - * Called from C++ native method {@link #getDeadlockInfoBuffer(long)} - * to construct a DeadlockInfo object. - * - * @param transactionID The transaction id - * @param columnFamilyId The id of the {@link ColumnFamilyHandle} - * @param waitingKey the key that we are waiting on - * @param exclusive true if the lock is exclusive, false if the lock is shared - * - * @return The waiting transactions - */ - private DeadlockInfo newDeadlockInfo( - final long transactionID, final long columnFamilyId, - final String waitingKey, final boolean exclusive) { - return new DeadlockInfo(transactionID, columnFamilyId, - waitingKey, exclusive); - } - - public static class DeadlockInfo { - private final long transactionID; - private final long columnFamilyId; - private final String waitingKey; - private final boolean exclusive; - - private DeadlockInfo(final long transactionID, final long columnFamilyId, - final String waitingKey, final boolean exclusive) { - this.transactionID = transactionID; - this.columnFamilyId = columnFamilyId; - this.waitingKey = waitingKey; - this.exclusive = exclusive; - } - - /** - * Get the Transaction ID. - * - * @return the transaction ID - */ - public long getTransactionID() { - return transactionID; - } - - /** - * Get the Column Family ID. - * - * @return The column family ID - */ - public long getColumnFamilyId() { - return columnFamilyId; - } - - /** - * Get the key that we are waiting on. - * - * @return the key that we are waiting on - */ - public String getWaitingKey() { - return waitingKey; - } - - /** - * Get the Lock status. - * - * @return true if the lock is exclusive, false if the lock is shared. - */ - public boolean isExclusive() { - return exclusive; - } - } - - public static class DeadlockPath { - final DeadlockInfo[] path; - final boolean limitExceeded; - - public DeadlockPath(final DeadlockInfo[] path, final boolean limitExceeded) { - this.path = path; - this.limitExceeded = limitExceeded; - } - - public boolean isEmpty() { - return path.length == 0 && !limitExceeded; - } - } - - public DeadlockPath[] getDeadlockInfoBuffer() { - return getDeadlockInfoBuffer(nativeHandle_); - } - - public void setDeadlockInfoBufferSize(final int targetSize) { - setDeadlockInfoBufferSize(nativeHandle_, targetSize); - } - - private void storeTransactionDbOptions( - final TransactionDBOptions transactionDbOptions) { - this.transactionDbOptions_ = transactionDbOptions; - } - - @Override protected final native void disposeInternal(final long handle); - - private static native long open(final long optionsHandle, - final long transactionDbOptionsHandle, final String path) - throws RocksDBException; - private static native long[] open(final long dbOptionsHandle, - final long transactionDbOptionsHandle, final String path, - final byte[][] columnFamilyNames, final long[] columnFamilyOptions); - private native static void closeDatabase(final long handle) - throws RocksDBException; - private native long beginTransaction(final long handle, - final long writeOptionsHandle); - private native long beginTransaction(final long handle, - final long writeOptionsHandle, final long transactionOptionsHandle); - private native long beginTransaction_withOld(final long handle, - final long writeOptionsHandle, final long oldTransactionHandle); - private native long beginTransaction_withOld(final long handle, - final long writeOptionsHandle, final long transactionOptionsHandle, - final long oldTransactionHandle); - private native long getTransactionByName(final long handle, - final String name); - private native long[] getAllPreparedTransactions(final long handle); - private native Map getLockStatusData( - final long handle); - private native DeadlockPath[] getDeadlockInfoBuffer(final long handle); - private native void setDeadlockInfoBufferSize(final long handle, - final int targetSize); -} diff --git a/java/src/main/java/org/rocksdb/TransactionDBOptions.java b/java/src/main/java/org/rocksdb/TransactionDBOptions.java deleted file mode 100644 index 7f4296a7c..000000000 --- a/java/src/main/java/org/rocksdb/TransactionDBOptions.java +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class TransactionDBOptions extends RocksObject { - - public TransactionDBOptions() { - super(newTransactionDBOptions()); - } - - /** - * Specifies the maximum number of keys that can be locked at the same time - * per column family. - * - * If the number of locked keys is greater than {@link #getMaxNumLocks()}, - * transaction writes (or GetForUpdate) will return an error. - * - * @return The maximum number of keys that can be locked - */ - public long getMaxNumLocks() { - assert(isOwningHandle()); - return getMaxNumLocks(nativeHandle_); - } - - /** - * Specifies the maximum number of keys that can be locked at the same time - * per column family. - * - * If the number of locked keys is greater than {@link #getMaxNumLocks()}, - * transaction writes (or GetForUpdate) will return an error. - * - * @param maxNumLocks The maximum number of keys that can be locked; - * If this value is not positive, no limit will be enforced. - * - * @return this TransactionDBOptions instance - */ - public TransactionDBOptions setMaxNumLocks(final long maxNumLocks) { - assert(isOwningHandle()); - setMaxNumLocks(nativeHandle_, maxNumLocks); - return this; - } - - /** - * The number of sub-tables per lock table (per column family) - * - * @return The number of sub-tables - */ - public long getNumStripes() { - assert(isOwningHandle()); - return getNumStripes(nativeHandle_); - } - - /** - * Increasing this value will increase the concurrency by dividing the lock - * table (per column family) into more sub-tables, each with their own - * separate mutex. - * - * Default: 16 - * - * @param numStripes The number of sub-tables - * - * @return this TransactionDBOptions instance - */ - public TransactionDBOptions setNumStripes(final long numStripes) { - assert(isOwningHandle()); - setNumStripes(nativeHandle_, numStripes); - return this; - } - - /** - * The default wait timeout in milliseconds when - * a transaction attempts to lock a key if not specified by - * {@link TransactionOptions#setLockTimeout(long)} - * - * If 0, no waiting is done if a lock cannot instantly be acquired. - * If negative, there is no timeout. - * - * @return the default wait timeout in milliseconds - */ - public long getTransactionLockTimeout() { - assert(isOwningHandle()); - return getTransactionLockTimeout(nativeHandle_); - } - - /** - * If positive, specifies the default wait timeout in milliseconds when - * a transaction attempts to lock a key if not specified by - * {@link TransactionOptions#setLockTimeout(long)} - * - * If 0, no waiting is done if a lock cannot instantly be acquired. - * If negative, there is no timeout. Not using a timeout is not recommended - * as it can lead to deadlocks. Currently, there is no deadlock-detection to - * recover from a deadlock. - * - * Default: 1000 - * - * @param transactionLockTimeout the default wait timeout in milliseconds - * - * @return this TransactionDBOptions instance - */ - public TransactionDBOptions setTransactionLockTimeout( - final long transactionLockTimeout) { - assert(isOwningHandle()); - setTransactionLockTimeout(nativeHandle_, transactionLockTimeout); - return this; - } - - /** - * The wait timeout in milliseconds when writing a key - * OUTSIDE of a transaction (ie by calling {@link RocksDB#put}, - * {@link RocksDB#merge}, {@link RocksDB#delete} or {@link RocksDB#write} - * directly). - * - * If 0, no waiting is done if a lock cannot instantly be acquired. - * If negative, there is no timeout and will block indefinitely when acquiring - * a lock. - * - * @return the timeout in milliseconds when writing a key OUTSIDE of a - * transaction - */ - public long getDefaultLockTimeout() { - assert(isOwningHandle()); - return getDefaultLockTimeout(nativeHandle_); - } - - /** - * If positive, specifies the wait timeout in milliseconds when writing a key - * OUTSIDE of a transaction (ie by calling {@link RocksDB#put}, - * {@link RocksDB#merge}, {@link RocksDB#delete} or {@link RocksDB#write} - * directly). - * - * If 0, no waiting is done if a lock cannot instantly be acquired. - * If negative, there is no timeout and will block indefinitely when acquiring - * a lock. - * - * Not using a timeout can lead to deadlocks. Currently, there - * is no deadlock-detection to recover from a deadlock. While DB writes - * cannot deadlock with other DB writes, they can deadlock with a transaction. - * A negative timeout should only be used if all transactions have a small - * expiration set. - * - * Default: 1000 - * - * @param defaultLockTimeout the timeout in milliseconds when writing a key - * OUTSIDE of a transaction - * @return this TransactionDBOptions instance - */ - public TransactionDBOptions setDefaultLockTimeout( - final long defaultLockTimeout) { - assert(isOwningHandle()); - setDefaultLockTimeout(nativeHandle_, defaultLockTimeout); - return this; - } - -// /** -// * If set, the {@link TransactionDB} will use this implementation of a mutex -// * and condition variable for all transaction locking instead of the default -// * mutex/condvar implementation. -// * -// * @param transactionDbMutexFactory the mutex factory for the transactions -// * -// * @return this TransactionDBOptions instance -// */ -// public TransactionDBOptions setCustomMutexFactory( -// final TransactionDBMutexFactory transactionDbMutexFactory) { -// -// } - - /** - * The policy for when to write the data into the DB. The default policy is to - * write only the committed data {@link TxnDBWritePolicy#WRITE_COMMITTED}. - * The data could be written before the commit phase. The DB then needs to - * provide the mechanisms to tell apart committed from uncommitted data. - * - * @return The write policy. - */ - public TxnDBWritePolicy getWritePolicy() { - assert(isOwningHandle()); - return TxnDBWritePolicy.getTxnDBWritePolicy(getWritePolicy(nativeHandle_)); - } - - /** - * The policy for when to write the data into the DB. The default policy is to - * write only the committed data {@link TxnDBWritePolicy#WRITE_COMMITTED}. - * The data could be written before the commit phase. The DB then needs to - * provide the mechanisms to tell apart committed from uncommitted data. - * - * @param writePolicy The write policy. - * - * @return this TransactionDBOptions instance - */ - public TransactionDBOptions setWritePolicy( - final TxnDBWritePolicy writePolicy) { - assert(isOwningHandle()); - setWritePolicy(nativeHandle_, writePolicy.getValue()); - return this; - } - - private native static long newTransactionDBOptions(); - private native long getMaxNumLocks(final long handle); - private native void setMaxNumLocks(final long handle, - final long maxNumLocks); - private native long getNumStripes(final long handle); - private native void setNumStripes(final long handle, final long numStripes); - private native long getTransactionLockTimeout(final long handle); - private native void setTransactionLockTimeout(final long handle, - final long transactionLockTimeout); - private native long getDefaultLockTimeout(final long handle); - private native void setDefaultLockTimeout(final long handle, - final long transactionLockTimeout); - private native byte getWritePolicy(final long handle); - private native void setWritePolicy(final long handle, final byte writePolicy); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/TransactionLogIterator.java b/java/src/main/java/org/rocksdb/TransactionLogIterator.java deleted file mode 100644 index 5d9ec58d7..000000000 --- a/java/src/main/java/org/rocksdb/TransactionLogIterator.java +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - *

    A TransactionLogIterator is used to iterate over the transactions in a db. - * One run of the iterator is continuous, i.e. the iterator will stop at the - * beginning of any gap in sequences.

    - */ -public class TransactionLogIterator extends RocksObject { - - /** - *

    An iterator is either positioned at a WriteBatch - * or not valid. This method returns true if the iterator - * is valid. Can read data from a valid iterator.

    - * - * @return true if iterator position is valid. - */ - public boolean isValid() { - return isValid(nativeHandle_); - } - - /** - *

    Moves the iterator to the next WriteBatch. - * REQUIRES: Valid() to be true.

    - */ - public void next() { - next(nativeHandle_); - } - - /** - *

    Throws RocksDBException if something went wrong.

    - * - * @throws org.rocksdb.RocksDBException if something went - * wrong in the underlying C++ code. - */ - public void status() throws RocksDBException { - status(nativeHandle_); - } - - /** - *

    If iterator position is valid, return the current - * write_batch and the sequence number of the earliest - * transaction contained in the batch.

    - * - *

    ONLY use if Valid() is true and status() is OK.

    - * - * @return {@link org.rocksdb.TransactionLogIterator.BatchResult} - * instance. - */ - public BatchResult getBatch() { - assert(isValid()); - return getBatch(nativeHandle_); - } - - /** - *

    TransactionLogIterator constructor.

    - * - * @param nativeHandle address to native address. - */ - TransactionLogIterator(final long nativeHandle) { - super(nativeHandle); - } - - /** - *

    BatchResult represents a data structure returned - * by a TransactionLogIterator containing a sequence - * number and a {@link WriteBatch} instance.

    - */ - public static final class BatchResult { - /** - *

    Constructor of BatchResult class.

    - * - * @param sequenceNumber related to this BatchResult instance. - * @param nativeHandle to {@link org.rocksdb.WriteBatch} - * native instance. - */ - public BatchResult(final long sequenceNumber, - final long nativeHandle) { - sequenceNumber_ = sequenceNumber; - writeBatch_ = new WriteBatch(nativeHandle, true); - } - - /** - *

    Return sequence number related to this BatchResult.

    - * - * @return Sequence number. - */ - public long sequenceNumber() { - return sequenceNumber_; - } - - /** - *

    Return contained {@link org.rocksdb.WriteBatch} - * instance

    - * - * @return {@link org.rocksdb.WriteBatch} instance. - */ - public WriteBatch writeBatch() { - return writeBatch_; - } - - private final long sequenceNumber_; - private final WriteBatch writeBatch_; - } - - @Override protected final native void disposeInternal(final long handle); - private native boolean isValid(long handle); - private native void next(long handle); - private native void status(long handle) - throws RocksDBException; - private native BatchResult getBatch(long handle); -} diff --git a/java/src/main/java/org/rocksdb/TransactionOptions.java b/java/src/main/java/org/rocksdb/TransactionOptions.java deleted file mode 100644 index 195fc85e4..000000000 --- a/java/src/main/java/org/rocksdb/TransactionOptions.java +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class TransactionOptions extends RocksObject - implements TransactionalOptions { - - public TransactionOptions() { - super(newTransactionOptions()); - } - - @Override - public boolean isSetSnapshot() { - assert(isOwningHandle()); - return isSetSnapshot(nativeHandle_); - } - - @Override - public TransactionOptions setSetSnapshot(final boolean setSnapshot) { - assert(isOwningHandle()); - setSetSnapshot(nativeHandle_, setSnapshot); - return this; - } - - /** - * True means that before acquiring locks, this transaction will - * check if doing so will cause a deadlock. If so, it will return with - * {@link Status.Code#Busy}. The user should retry their transaction. - * - * @return true if a deadlock is detected. - */ - public boolean isDeadlockDetect() { - assert(isOwningHandle()); - return isDeadlockDetect(nativeHandle_); - } - - /** - * Setting to true means that before acquiring locks, this transaction will - * check if doing so will cause a deadlock. If so, it will return with - * {@link Status.Code#Busy}. The user should retry their transaction. - * - * @param deadlockDetect true if we should detect deadlocks. - * - * @return this TransactionOptions instance - */ - public TransactionOptions setDeadlockDetect(final boolean deadlockDetect) { - assert(isOwningHandle()); - setDeadlockDetect(nativeHandle_, deadlockDetect); - return this; - } - - /** - * The wait timeout in milliseconds when a transaction attempts to lock a key. - * - * If 0, no waiting is done if a lock cannot instantly be acquired. - * If negative, {@link TransactionDBOptions#getTransactionLockTimeout(long)} - * will be used - * - * @return the lock timeout in milliseconds - */ - public long getLockTimeout() { - assert(isOwningHandle()); - return getLockTimeout(nativeHandle_); - } - - /** - * If positive, specifies the wait timeout in milliseconds when - * a transaction attempts to lock a key. - * - * If 0, no waiting is done if a lock cannot instantly be acquired. - * If negative, {@link TransactionDBOptions#getTransactionLockTimeout(long)} - * will be used - * - * Default: -1 - * - * @param lockTimeout the lock timeout in milliseconds - * - * @return this TransactionOptions instance - */ - public TransactionOptions setLockTimeout(final long lockTimeout) { - assert(isOwningHandle()); - setLockTimeout(nativeHandle_, lockTimeout); - return this; - } - - /** - * Expiration duration in milliseconds. - * - * If non-negative, transactions that last longer than this many milliseconds - * will fail to commit. If not set, a forgotten transaction that is never - * committed, rolled back, or deleted will never relinquish any locks it - * holds. This could prevent keys from being written by other writers. - * - * @return expiration the expiration duration in milliseconds - */ - public long getExpiration() { - assert(isOwningHandle()); - return getExpiration(nativeHandle_); - } - - /** - * Expiration duration in milliseconds. - * - * If non-negative, transactions that last longer than this many milliseconds - * will fail to commit. If not set, a forgotten transaction that is never - * committed, rolled back, or deleted will never relinquish any locks it - * holds. This could prevent keys from being written by other writers. - * - * Default: -1 - * - * @param expiration the expiration duration in milliseconds - * - * @return this TransactionOptions instance - */ - public TransactionOptions setExpiration(final long expiration) { - assert(isOwningHandle()); - setExpiration(nativeHandle_, expiration); - return this; - } - - /** - * Gets the number of traversals to make during deadlock detection. - * - * @return the number of traversals to make during - * deadlock detection - */ - public long getDeadlockDetectDepth() { - return getDeadlockDetectDepth(nativeHandle_); - } - - /** - * Sets the number of traversals to make during deadlock detection. - * - * Default: 50 - * - * @param deadlockDetectDepth the number of traversals to make during - * deadlock detection - * - * @return this TransactionOptions instance - */ - public TransactionOptions setDeadlockDetectDepth( - final long deadlockDetectDepth) { - setDeadlockDetectDepth(nativeHandle_, deadlockDetectDepth); - return this; - } - - /** - * Get the maximum number of bytes that may be used for the write batch. - * - * @return the maximum number of bytes, 0 means no limit. - */ - public long getMaxWriteBatchSize() { - return getMaxWriteBatchSize(nativeHandle_); - } - - /** - * Set the maximum number of bytes that may be used for the write batch. - * - * @param maxWriteBatchSize the maximum number of bytes, 0 means no limit. - * - * @return this TransactionOptions instance - */ - public TransactionOptions setMaxWriteBatchSize(final long maxWriteBatchSize) { - setMaxWriteBatchSize(nativeHandle_, maxWriteBatchSize); - return this; - } - - private native static long newTransactionOptions(); - private native boolean isSetSnapshot(final long handle); - private native void setSetSnapshot(final long handle, - final boolean setSnapshot); - private native boolean isDeadlockDetect(final long handle); - private native void setDeadlockDetect(final long handle, - final boolean deadlockDetect); - private native long getLockTimeout(final long handle); - private native void setLockTimeout(final long handle, final long lockTimeout); - private native long getExpiration(final long handle); - private native void setExpiration(final long handle, final long expiration); - private native long getDeadlockDetectDepth(final long handle); - private native void setDeadlockDetectDepth(final long handle, - final long deadlockDetectDepth); - private native long getMaxWriteBatchSize(final long handle); - private native void setMaxWriteBatchSize(final long handle, - final long maxWriteBatchSize); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/TransactionalDB.java b/java/src/main/java/org/rocksdb/TransactionalDB.java deleted file mode 100644 index 740181989..000000000 --- a/java/src/main/java/org/rocksdb/TransactionalDB.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -interface TransactionalDB> extends AutoCloseable { - /** - * Starts a new Transaction. - * - * Caller is responsible for calling {@link #close()} on the returned - * transaction when it is no longer needed. - * - * @param writeOptions Any write options for the transaction - * @return a new transaction - */ - Transaction beginTransaction(final WriteOptions writeOptions); - - /** - * Starts a new Transaction. - * - * Caller is responsible for calling {@link #close()} on the returned - * transaction when it is no longer needed. - * - * @param writeOptions Any write options for the transaction - * @param transactionOptions Any options for the transaction - * @return a new transaction - */ - Transaction beginTransaction(final WriteOptions writeOptions, - final T transactionOptions); - - /** - * Starts a new Transaction. - * - * Caller is responsible for calling {@link #close()} on the returned - * transaction when it is no longer needed. - * - * @param writeOptions Any write options for the transaction - * @param oldTransaction this Transaction will be reused instead of allocating - * a new one. This is an optimization to avoid extra allocations - * when repeatedly creating transactions. - * @return The oldTransaction which has been reinitialized as a new - * transaction - */ - Transaction beginTransaction(final WriteOptions writeOptions, - final Transaction oldTransaction); - - /** - * Starts a new Transaction. - * - * Caller is responsible for calling {@link #close()} on the returned - * transaction when it is no longer needed. - * - * @param writeOptions Any write options for the transaction - * @param transactionOptions Any options for the transaction - * @param oldTransaction this Transaction will be reused instead of allocating - * a new one. This is an optimization to avoid extra allocations - * when repeatedly creating transactions. - * @return The oldTransaction which has been reinitialized as a new - * transaction - */ - Transaction beginTransaction(final WriteOptions writeOptions, - final T transactionOptions, final Transaction oldTransaction); -} diff --git a/java/src/main/java/org/rocksdb/TransactionalOptions.java b/java/src/main/java/org/rocksdb/TransactionalOptions.java deleted file mode 100644 index d55ee900c..000000000 --- a/java/src/main/java/org/rocksdb/TransactionalOptions.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - - -interface TransactionalOptions> - extends AutoCloseable { - - /** - * True indicates snapshots will be set, just like if - * {@link Transaction#setSnapshot()} had been called - * - * @return whether a snapshot will be set - */ - boolean isSetSnapshot(); - - /** - * Setting the setSnapshot to true is the same as calling - * {@link Transaction#setSnapshot()}. - * - * Default: false - * - * @param setSnapshot Whether to set a snapshot - * - * @return this TransactionalOptions instance - */ - T setSetSnapshot(final boolean setSnapshot); -} diff --git a/java/src/main/java/org/rocksdb/TtlDB.java b/java/src/main/java/org/rocksdb/TtlDB.java deleted file mode 100644 index a7adaf4b2..000000000 --- a/java/src/main/java/org/rocksdb/TtlDB.java +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.List; - -/** - * Database with TTL support. - * - *

    Use case

    - *

    This API should be used to open the db when key-values inserted are - * meant to be removed from the db in a non-strict 'ttl' amount of time - * Therefore, this guarantees that key-values inserted will remain in the - * db for >= ttl amount of time and the db will make efforts to remove the - * key-values as soon as possible after ttl seconds of their insertion. - *

    - * - *

    Behaviour

    - *

    TTL is accepted in seconds - * (int32_t)Timestamp(creation) is suffixed to values in Put internally - * Expired TTL values deleted in compaction only:(Timestamp+ttl<time_now) - * Get/Iterator may return expired entries(compaction not run on them yet) - * Different TTL may be used during different Opens - *

    - * - *

    Example

    - *
      - *
    • Open1 at t=0 with ttl=4 and insert k1,k2, close at t=2
    • - *
    • Open2 at t=3 with ttl=5. Now k1,k2 should be deleted at t>=5
    • - *
    - * - *

    - * read_only=true opens in the usual read-only mode. Compactions will not be - * triggered(neither manual nor automatic), so no expired entries removed - *

    - * - *

    Constraints

    - *

    Not specifying/passing or non-positive TTL behaves - * like TTL = infinity

    - * - *

    !!!WARNING!!!

    - *

    Calling DB::Open directly to re-open a db created by this API will get - * corrupt values(timestamp suffixed) and no ttl effect will be there - * during the second Open, so use this API consistently to open the db - * Be careful when passing ttl with a small positive value because the - * whole database may be deleted in a small amount of time.

    - */ -public class TtlDB extends RocksDB { - - /** - *

    Opens a TtlDB.

    - * - *

    Database is opened in read-write mode without default TTL.

    - * - * @param options {@link org.rocksdb.Options} instance. - * @param db_path path to database. - * - * @return TtlDB instance. - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public static TtlDB open(final Options options, final String db_path) - throws RocksDBException { - return open(options, db_path, 0, false); - } - - /** - *

    Opens a TtlDB.

    - * - * @param options {@link org.rocksdb.Options} instance. - * @param db_path path to database. - * @param ttl time to live for new entries. - * @param readOnly boolean value indicating if database if db is - * opened read-only. - * - * @return TtlDB instance. - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - public static TtlDB open(final Options options, final String db_path, - final int ttl, final boolean readOnly) throws RocksDBException { - return new TtlDB(open(options.nativeHandle_, db_path, ttl, readOnly)); - } - - /** - *

    Opens a TtlDB.

    - * - * @param options {@link org.rocksdb.Options} instance. - * @param db_path path to database. - * @param columnFamilyDescriptors list of column family descriptors - * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances - * on open. - * @param ttlValues time to live values per column family handle - * @param readOnly boolean value indicating if database if db is - * opened read-only. - * - * @return TtlDB instance. - * - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - * @throws java.lang.IllegalArgumentException when there is not a ttl value - * per given column family handle. - */ - public static TtlDB open(final DBOptions options, final String db_path, - final List columnFamilyDescriptors, - final List columnFamilyHandles, - final List ttlValues, final boolean readOnly) - throws RocksDBException { - if (columnFamilyDescriptors.size() != ttlValues.size()) { - throw new IllegalArgumentException("There must be a ttl value per column" - + " family handle."); - } - - final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; - final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; - for (int i = 0; i < columnFamilyDescriptors.size(); i++) { - final ColumnFamilyDescriptor cfDescriptor = - columnFamilyDescriptors.get(i); - cfNames[i] = cfDescriptor.getName(); - cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; - } - - final int ttlVals[] = new int[ttlValues.size()]; - for(int i = 0; i < ttlValues.size(); i++) { - ttlVals[i] = ttlValues.get(i); - } - final long[] handles = openCF(options.nativeHandle_, db_path, - cfNames, cfOptionHandles, ttlVals, readOnly); - - final TtlDB ttlDB = new TtlDB(handles[0]); - for (int i = 1; i < handles.length; i++) { - columnFamilyHandles.add(new ColumnFamilyHandle(ttlDB, handles[i])); - } - return ttlDB; - } - - /** - *

    Close the TtlDB instance and release resource.

    - * - * This is similar to {@link #close()} except that it - * throws an exception if any error occurs. - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - * - * @throws RocksDBException if an error occurs whilst closing. - */ - public void closeE() throws RocksDBException { - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } finally { - disposeInternal(); - } - } - } - - /** - *

    Close the TtlDB instance and release resource.

    - * - * - * This will not fsync the WAL files. - * If syncing is required, the caller must first call {@link #syncWal()} - * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch - * with {@link WriteOptions#setSync(boolean)} set to true. - * - * See also {@link #close()}. - */ - @Override - public void close() { - if (owningHandle_.compareAndSet(true, false)) { - try { - closeDatabase(nativeHandle_); - } catch (final RocksDBException e) { - // silently ignore the error report - } finally { - disposeInternal(); - } - } - } - - /** - *

    Creates a new ttl based column family with a name defined - * in given ColumnFamilyDescriptor and allocates a - * ColumnFamilyHandle within an internal structure.

    - * - *

    The ColumnFamilyHandle is automatically disposed with DB - * disposal.

    - * - * @param columnFamilyDescriptor column family to be created. - * @param ttl TTL to set for this column family. - * - * @return {@link org.rocksdb.ColumnFamilyHandle} instance. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public ColumnFamilyHandle createColumnFamilyWithTtl( - final ColumnFamilyDescriptor columnFamilyDescriptor, - final int ttl) throws RocksDBException { - return new ColumnFamilyHandle(this, - createColumnFamilyWithTtl(nativeHandle_, - columnFamilyDescriptor.getName(), - columnFamilyDescriptor.getOptions().nativeHandle_, ttl)); - } - - /** - *

    A protected constructor that will be used in the static - * factory method - * {@link #open(Options, String, int, boolean)} - * and - * {@link #open(DBOptions, String, java.util.List, java.util.List, - * java.util.List, boolean)}. - *

    - * - * @param nativeHandle The native handle of the C++ TtlDB object - */ - protected TtlDB(final long nativeHandle) { - super(nativeHandle); - } - - @Override protected native void disposeInternal(final long handle); - - private native static long open(final long optionsHandle, - final String db_path, final int ttl, final boolean readOnly) - throws RocksDBException; - private native static long[] openCF(final long optionsHandle, - final String db_path, final byte[][] columnFamilyNames, - final long[] columnFamilyOptions, final int[] ttlValues, - final boolean readOnly) throws RocksDBException; - private native long createColumnFamilyWithTtl(final long handle, - final byte[] columnFamilyName, final long columnFamilyOptions, int ttl) - throws RocksDBException; - private native static void closeDatabase(final long handle) - throws RocksDBException; -} diff --git a/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java b/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java deleted file mode 100644 index 837ce6157..000000000 --- a/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -/** - * The transaction db write policy. - */ -public enum TxnDBWritePolicy { - /** - * Write only the committed data. - */ - WRITE_COMMITTED((byte)0x00), - - /** - * Write data after the prepare phase of 2pc. - */ - WRITE_PREPARED((byte)0x1), - - /** - * Write data before the prepare phase of 2pc. - */ - WRITE_UNPREPARED((byte)0x2); - - private byte value; - - TxnDBWritePolicy(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - *

    Get the TxnDBWritePolicy enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of TxnDBWritePolicy. - * - * @return TxnDBWritePolicy instance. - * - * @throws IllegalArgumentException If TxnDBWritePolicy cannot be found for - * the provided byteIdentifier - */ - public static TxnDBWritePolicy getTxnDBWritePolicy(final byte byteIdentifier) { - for (final TxnDBWritePolicy txnDBWritePolicy : TxnDBWritePolicy.values()) { - if (txnDBWritePolicy.getValue() == byteIdentifier) { - return txnDBWritePolicy; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for TxnDBWritePolicy."); - } -} diff --git a/java/src/main/java/org/rocksdb/UInt64AddOperator.java b/java/src/main/java/org/rocksdb/UInt64AddOperator.java deleted file mode 100644 index cce9b298d..000000000 --- a/java/src/main/java/org/rocksdb/UInt64AddOperator.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Uint64AddOperator is a merge operator that accumlates a long - * integer value. - */ -public class UInt64AddOperator extends MergeOperator { - public UInt64AddOperator() { - super(newSharedUInt64AddOperator()); - } - - private native static long newSharedUInt64AddOperator(); - @Override protected final native void disposeInternal(final long handle); -} diff --git a/java/src/main/java/org/rocksdb/VectorMemTableConfig.java b/java/src/main/java/org/rocksdb/VectorMemTableConfig.java deleted file mode 100644 index fb1e7a948..000000000 --- a/java/src/main/java/org/rocksdb/VectorMemTableConfig.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -/** - * The config for vector memtable representation. - */ -public class VectorMemTableConfig extends MemTableConfig { - public static final int DEFAULT_RESERVED_SIZE = 0; - - /** - * VectorMemTableConfig constructor - */ - public VectorMemTableConfig() { - reservedSize_ = DEFAULT_RESERVED_SIZE; - } - - /** - * Set the initial size of the vector that will be used - * by the memtable created based on this config. - * - * @param size the initial size of the vector. - * @return the reference to the current config. - */ - public VectorMemTableConfig setReservedSize(final int size) { - reservedSize_ = size; - return this; - } - - /** - * Returns the initial size of the vector used by the memtable - * created based on this config. - * - * @return the initial size of the vector. - */ - public int reservedSize() { - return reservedSize_; - } - - @Override protected long newMemTableFactoryHandle() { - return newMemTableFactoryHandle(reservedSize_); - } - - private native long newMemTableFactoryHandle(long reservedSize) - throws IllegalArgumentException; - private int reservedSize_; -} diff --git a/java/src/main/java/org/rocksdb/WALRecoveryMode.java b/java/src/main/java/org/rocksdb/WALRecoveryMode.java deleted file mode 100644 index d8b9eeced..000000000 --- a/java/src/main/java/org/rocksdb/WALRecoveryMode.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * The WAL Recover Mode - */ -public enum WALRecoveryMode { - - /** - * Original levelDB recovery - * - * We tolerate incomplete record in trailing data on all logs - * Use case : This is legacy behavior (default) - */ - TolerateCorruptedTailRecords((byte)0x00), - - /** - * Recover from clean shutdown - * - * We don't expect to find any corruption in the WAL - * Use case : This is ideal for unit tests and rare applications that - * can require high consistency guarantee - */ - AbsoluteConsistency((byte)0x01), - - /** - * Recover to point-in-time consistency - * We stop the WAL playback on discovering WAL inconsistency - * Use case : Ideal for systems that have disk controller cache like - * hard disk, SSD without super capacitor that store related data - */ - PointInTimeRecovery((byte)0x02), - - /** - * Recovery after a disaster - * We ignore any corruption in the WAL and try to salvage as much data as - * possible - * Use case : Ideal for last ditch effort to recover data or systems that - * operate with low grade unrelated data - */ - SkipAnyCorruptedRecords((byte)0x03); - - private byte value; - - WALRecoveryMode(final byte value) { - this.value = value; - } - - /** - *

    Returns the byte value of the enumerations value.

    - * - * @return byte representation - */ - public byte getValue() { - return value; - } - - /** - *

    Get the WALRecoveryMode enumeration value by - * passing the byte identifier to this method.

    - * - * @param byteIdentifier of WALRecoveryMode. - * - * @return WALRecoveryMode instance. - * - * @throws IllegalArgumentException If WALRecoveryMode cannot be found for the - * provided byteIdentifier - */ - public static WALRecoveryMode getWALRecoveryMode(final byte byteIdentifier) { - for (final WALRecoveryMode walRecoveryMode : WALRecoveryMode.values()) { - if (walRecoveryMode.getValue() == byteIdentifier) { - return walRecoveryMode; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for WALRecoveryMode."); - } -} diff --git a/java/src/main/java/org/rocksdb/WBWIRocksIterator.java b/java/src/main/java/org/rocksdb/WBWIRocksIterator.java deleted file mode 100644 index ce146eb3f..000000000 --- a/java/src/main/java/org/rocksdb/WBWIRocksIterator.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -public class WBWIRocksIterator - extends AbstractRocksIterator { - private final WriteEntry entry = new WriteEntry(); - - protected WBWIRocksIterator(final WriteBatchWithIndex wbwi, - final long nativeHandle) { - super(wbwi, nativeHandle); - } - - /** - * Get the current entry - * - * The WriteEntry is only valid - * until the iterator is repositioned. - * If you want to keep the WriteEntry across iterator - * movements, you must make a copy of its data! - * - * Note - This method is not thread-safe with respect to the WriteEntry - * as it performs a non-atomic update across the fields of the WriteEntry - * - * @return The WriteEntry of the current entry - */ - public WriteEntry entry() { - assert(isOwningHandle()); - final long[] ptrs = entry1(nativeHandle_); - - entry.type = WriteType.fromId((byte)ptrs[0]); - entry.key.resetNativeHandle(ptrs[1], ptrs[1] != 0); - entry.value.resetNativeHandle(ptrs[2], ptrs[2] != 0); - - return entry; - } - - @Override protected final native void disposeInternal(final long handle); - @Override final native boolean isValid0(long handle); - @Override final native void seekToFirst0(long handle); - @Override final native void seekToLast0(long handle); - @Override final native void next0(long handle); - @Override final native void prev0(long handle); - @Override final native void refresh0(final long handle) throws RocksDBException; - @Override final native void seek0(long handle, byte[] target, int targetLen); - @Override final native void seekForPrev0(long handle, byte[] target, int targetLen); - @Override final native void status0(long handle) throws RocksDBException; - @Override - final native void seekDirect0( - final long handle, final ByteBuffer target, final int targetOffset, final int targetLen); - @Override - final native void seekForPrevDirect0( - final long handle, final ByteBuffer target, final int targetOffset, final int targetLen); - @Override - final native void seekByteArray0( - final long handle, final byte[] target, final int targetOffset, final int targetLen); - @Override - final native void seekForPrevByteArray0( - final long handle, final byte[] target, final int targetOffset, final int targetLen); - - private native long[] entry1(final long handle); - - /** - * Enumeration of the Write operation - * that created the record in the Write Batch - */ - public enum WriteType { - PUT((byte)0x0), - MERGE((byte)0x1), - DELETE((byte)0x2), - SINGLE_DELETE((byte)0x3), - DELETE_RANGE((byte)0x4), - LOG((byte)0x5), - XID((byte)0x6); - - final byte id; - WriteType(final byte id) { - this.id = id; - } - - public static WriteType fromId(final byte id) { - for(final WriteType wt : WriteType.values()) { - if(id == wt.id) { - return wt; - } - } - throw new IllegalArgumentException("No WriteType with id=" + id); - } - } - - @Override - public void close() { - entry.close(); - super.close(); - } - - /** - * Represents an entry returned by - * {@link org.rocksdb.WBWIRocksIterator#entry()} - * - * It is worth noting that a WriteEntry with - * the type {@link org.rocksdb.WBWIRocksIterator.WriteType#DELETE} - * or {@link org.rocksdb.WBWIRocksIterator.WriteType#LOG} - * will not have a value. - */ - public static class WriteEntry implements AutoCloseable { - WriteType type = null; - final DirectSlice key; - final DirectSlice value; - - /** - * Intentionally private as this - * should only be instantiated in - * this manner by the outer WBWIRocksIterator - * class; The class members are then modified - * by calling {@link org.rocksdb.WBWIRocksIterator#entry()} - */ - private WriteEntry() { - key = new DirectSlice(); - value = new DirectSlice(); - } - - public WriteEntry(final WriteType type, final DirectSlice key, - final DirectSlice value) { - this.type = type; - this.key = key; - this.value = value; - } - - /** - * Returns the type of the Write Entry - * - * @return the WriteType of the WriteEntry - */ - public WriteType getType() { - return type; - } - - /** - * Returns the key of the Write Entry - * - * @return The slice containing the key - * of the WriteEntry - */ - public DirectSlice getKey() { - return key; - } - - /** - * Returns the value of the Write Entry - * - * @return The slice containing the value of - * the WriteEntry or null if the WriteEntry has - * no value - */ - public DirectSlice getValue() { - if(!value.isOwningHandle()) { - return null; //TODO(AR) migrate to JDK8 java.util.Optional#empty() - } else { - return value; - } - } - - /** - * Generates a hash code for the Write Entry. NOTE: The hash code is based - * on the string representation of the key, so it may not work correctly - * with exotic custom comparators. - * - * @return The hash code for the Write Entry - */ - @Override - public int hashCode() { - return (key == null) ? 0 : key.hashCode(); - } - - @Override - public boolean equals(final Object other) { - if(other == null) { - return false; - } else if (this == other) { - return true; - } else if(other instanceof WriteEntry) { - final WriteEntry otherWriteEntry = (WriteEntry)other; - return type.equals(otherWriteEntry.type) - && key.equals(otherWriteEntry.key) - && value.equals(otherWriteEntry.value); - } else { - return false; - } - } - - @Override - public void close() { - value.close(); - key.close(); - } - } -} diff --git a/java/src/main/java/org/rocksdb/WalFileType.java b/java/src/main/java/org/rocksdb/WalFileType.java deleted file mode 100644 index fed27ed11..000000000 --- a/java/src/main/java/org/rocksdb/WalFileType.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum WalFileType { - /** - * Indicates that WAL file is in archive directory. WAL files are moved from - * the main db directory to archive directory once they are not live and stay - * there until cleaned up. Files are cleaned depending on archive size - * (Options::WAL_size_limit_MB) and time since last cleaning - * (Options::WAL_ttl_seconds). - */ - kArchivedLogFile((byte)0x0), - - /** - * Indicates that WAL file is live and resides in the main db directory - */ - kAliveLogFile((byte)0x1); - - private final byte value; - - WalFileType(final byte value) { - this.value = value; - } - - /** - * Get the internal representation value. - * - * @return the internal representation value - */ - byte getValue() { - return value; - } - - /** - * Get the WalFileType from the internal representation value. - * - * @return the wal file type. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static WalFileType fromValue(final byte value) { - for (final WalFileType walFileType : WalFileType.values()) { - if(walFileType.value == value) { - return walFileType; - } - } - - throw new IllegalArgumentException( - "Illegal value provided for WalFileType: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/WalFilter.java b/java/src/main/java/org/rocksdb/WalFilter.java deleted file mode 100644 index 37e36213a..000000000 --- a/java/src/main/java/org/rocksdb/WalFilter.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Map; - -/** - * WALFilter allows an application to inspect write-ahead-log (WAL) - * records or modify their processing on recovery. - */ -public interface WalFilter { - - /** - * Provide ColumnFamily->LogNumber map to filter - * so that filter can determine whether a log number applies to a given - * column family (i.e. that log hasn't been flushed to SST already for the - * column family). - * - * We also pass in name>id map as only name is known during - * recovery (as handles are opened post-recovery). - * while write batch callbacks happen in terms of column family id. - * - * @param cfLognumber column_family_id to lognumber map - * @param cfNameId column_family_name to column_family_id map - */ - void columnFamilyLogNumberMap(final Map cfLognumber, - final Map cfNameId); - - /** - * LogRecord is invoked for each log record encountered for all the logs - * during replay on logs on recovery. This method can be used to: - * * inspect the record (using the batch parameter) - * * ignoring current record - * (by returning WalProcessingOption::kIgnoreCurrentRecord) - * * reporting corrupted record - * (by returning WalProcessingOption::kCorruptedRecord) - * * stop log replay - * (by returning kStop replay) - please note that this implies - * discarding the logs from current record onwards. - * - * @param logNumber log number of the current log. - * Filter might use this to determine if the log - * record is applicable to a certain column family. - * @param logFileName log file name - only for informational purposes - * @param batch batch encountered in the log during recovery - * @param newBatch new batch to populate if filter wants to change - * the batch (for example to filter some records out, or alter some - * records). Please note that the new batch MUST NOT contain - * more records than original, else recovery would be failed. - * - * @return Processing option for the current record. - */ - LogRecordFoundResult logRecordFound(final long logNumber, - final String logFileName, final WriteBatch batch, - final WriteBatch newBatch); - - class LogRecordFoundResult { - public static LogRecordFoundResult CONTINUE_UNCHANGED = - new LogRecordFoundResult(WalProcessingOption.CONTINUE_PROCESSING, false); - - final WalProcessingOption walProcessingOption; - final boolean batchChanged; - - /** - * @param walProcessingOption the processing option - * @param batchChanged Whether batch was changed by the filter. - * It must be set to true if newBatch was populated, - * else newBatch has no effect. - */ - public LogRecordFoundResult(final WalProcessingOption walProcessingOption, - final boolean batchChanged) { - this.walProcessingOption = walProcessingOption; - this.batchChanged = batchChanged; - } - } - - /** - * Returns a name that identifies this WAL filter. - * The name will be printed to LOG file on start up for diagnosis. - * - * @return the name - */ - String name(); -} diff --git a/java/src/main/java/org/rocksdb/WalProcessingOption.java b/java/src/main/java/org/rocksdb/WalProcessingOption.java deleted file mode 100644 index 889602edc..000000000 --- a/java/src/main/java/org/rocksdb/WalProcessingOption.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum WalProcessingOption { - /** - * Continue processing as usual. - */ - CONTINUE_PROCESSING((byte)0x0), - - /** - * Ignore the current record but continue processing of log(s). - */ - IGNORE_CURRENT_RECORD((byte)0x1), - - /** - * Stop replay of logs and discard logs. - * Logs won't be replayed on subsequent recovery. - */ - STOP_REPLAY((byte)0x2), - - /** - * Corrupted record detected by filter. - */ - CORRUPTED_RECORD((byte)0x3); - - private final byte value; - - WalProcessingOption(final byte value) { - this.value = value; - } - - /** - * Get the internal representation. - * - * @return the internal representation. - */ - byte getValue() { - return value; - } - - public static WalProcessingOption fromValue(final byte value) { - for (final WalProcessingOption walProcessingOption : WalProcessingOption.values()) { - if (walProcessingOption.value == value) { - return walProcessingOption; - } - } - throw new IllegalArgumentException( - "Illegal value provided for WalProcessingOption: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/WriteBatch.java b/java/src/main/java/org/rocksdb/WriteBatch.java deleted file mode 100644 index 9b46108d0..000000000 --- a/java/src/main/java/org/rocksdb/WriteBatch.java +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * WriteBatch holds a collection of updates to apply atomically to a DB. - * - * The updates are applied in the order in which they are added - * to the WriteBatch. For example, the value of "key" will be "v3" - * after the following batch is written: - * - * batch.put("key", "v1"); - * batch.remove("key"); - * batch.put("key", "v2"); - * batch.put("key", "v3"); - * - * Multiple threads can invoke const methods on a WriteBatch without - * external synchronization, but if any of the threads may call a - * non-const method, all threads accessing the same WriteBatch must use - * external synchronization. - */ -public class WriteBatch extends AbstractWriteBatch { - /** - * Constructs a WriteBatch instance. - */ - public WriteBatch() { - this(0); - } - - /** - * Constructs a WriteBatch instance with a given size. - * - * @param reserved_bytes reserved size for WriteBatch - */ - public WriteBatch(final int reserved_bytes) { - super(newWriteBatch(reserved_bytes)); - } - - /** - * Constructs a WriteBatch instance from a serialized representation - * as returned by {@link #data()}. - * - * @param serialized the serialized representation. - */ - public WriteBatch(final byte[] serialized) { - super(newWriteBatch(serialized, serialized.length)); - } - - /** - * Support for iterating over the contents of a batch. - * - * @param handler A handler that is called back for each - * update present in the batch - * - * @throws RocksDBException If we cannot iterate over the batch - */ - public void iterate(final Handler handler) throws RocksDBException { - iterate(nativeHandle_, handler.nativeHandle_); - } - - /** - * Retrieve the serialized version of this batch. - * - * @return the serialized representation of this write batch. - * - * @throws RocksDBException if an error occurs whilst retrieving - * the serialized batch data. - */ - public byte[] data() throws RocksDBException { - return data(nativeHandle_); - } - - /** - * Retrieve data size of the batch. - * - * @return the serialized data size of the batch. - */ - public long getDataSize() { - return getDataSize(nativeHandle_); - } - - /** - * Returns true if Put will be called during Iterate. - * - * @return true if Put will be called during Iterate. - */ - public boolean hasPut() { - return hasPut(nativeHandle_); - } - - /** - * Returns true if Delete will be called during Iterate. - * - * @return true if Delete will be called during Iterate. - */ - public boolean hasDelete() { - return hasDelete(nativeHandle_); - } - - /** - * Returns true if SingleDelete will be called during Iterate. - * - * @return true if SingleDelete will be called during Iterate. - */ - public boolean hasSingleDelete() { - return hasSingleDelete(nativeHandle_); - } - - /** - * Returns true if DeleteRange will be called during Iterate. - * - * @return true if DeleteRange will be called during Iterate. - */ - public boolean hasDeleteRange() { - return hasDeleteRange(nativeHandle_); - } - - /** - * Returns true if Merge will be called during Iterate. - * - * @return true if Merge will be called during Iterate. - */ - public boolean hasMerge() { - return hasMerge(nativeHandle_); - } - - /** - * Returns true if MarkBeginPrepare will be called during Iterate. - * - * @return true if MarkBeginPrepare will be called during Iterate. - */ - public boolean hasBeginPrepare() { - return hasBeginPrepare(nativeHandle_); - } - - /** - * Returns true if MarkEndPrepare will be called during Iterate. - * - * @return true if MarkEndPrepare will be called during Iterate. - */ - public boolean hasEndPrepare() { - return hasEndPrepare(nativeHandle_); - } - - /** - * Returns true if MarkCommit will be called during Iterate. - * - * @return true if MarkCommit will be called during Iterate. - */ - public boolean hasCommit() { - return hasCommit(nativeHandle_); - } - - /** - * Returns true if MarkRollback will be called during Iterate. - * - * @return true if MarkRollback will be called during Iterate. - */ - public boolean hasRollback() { - return hasRollback(nativeHandle_); - } - - @Override - public WriteBatch getWriteBatch() { - return this; - } - - /** - * Marks this point in the WriteBatch as the last record to - * be inserted into the WAL, provided the WAL is enabled. - */ - public void markWalTerminationPoint() { - markWalTerminationPoint(nativeHandle_); - } - - /** - * Gets the WAL termination point. - * - * See {@link #markWalTerminationPoint()} - * - * @return the WAL termination point - */ - public SavePoint getWalTerminationPoint() { - return getWalTerminationPoint(nativeHandle_); - } - - @Override - WriteBatch getWriteBatch(final long handle) { - return this; - } - - /** - *

    Private WriteBatch constructor which is used to construct - * WriteBatch instances from C++ side. As the reference to this - * object is also managed from C++ side the handle will be disowned.

    - * - * @param nativeHandle address of native instance. - */ - WriteBatch(final long nativeHandle) { - this(nativeHandle, false); - } - - /** - *

    Private WriteBatch constructor which is used to construct - * WriteBatch instances.

    - * - * @param nativeHandle address of native instance. - * @param owningNativeHandle whether to own this reference from the C++ side or not - */ - WriteBatch(final long nativeHandle, final boolean owningNativeHandle) { - super(nativeHandle); - if(!owningNativeHandle) - disOwnNativeHandle(); - } - - @Override protected final native void disposeInternal(final long handle); - @Override final native int count0(final long handle); - @Override final native void put(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen); - @Override final native void put(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen, - final long cfHandle); - @Override - final native void putDirect(final long handle, final ByteBuffer key, final int keyOffset, - final int keyLength, final ByteBuffer value, final int valueOffset, final int valueLength, - final long cfHandle); - @Override final native void merge(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen); - @Override final native void merge(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen, - final long cfHandle); - @Override final native void delete(final long handle, final byte[] key, - final int keyLen) throws RocksDBException; - @Override final native void delete(final long handle, final byte[] key, - final int keyLen, final long cfHandle) throws RocksDBException; - @Override final native void singleDelete(final long handle, final byte[] key, - final int keyLen) throws RocksDBException; - @Override final native void singleDelete(final long handle, final byte[] key, - final int keyLen, final long cfHandle) throws RocksDBException; - @Override - final native void deleteDirect(final long handle, final ByteBuffer key, final int keyOffset, - final int keyLength, final long cfHandle) throws RocksDBException; - @Override - final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen); - @Override - final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen, final long cfHandle); - @Override final native void putLogData(final long handle, - final byte[] blob, final int blobLen) throws RocksDBException; - @Override final native void clear0(final long handle); - @Override final native void setSavePoint0(final long handle); - @Override final native void rollbackToSavePoint0(final long handle); - @Override final native void popSavePoint(final long handle) throws RocksDBException; - @Override final native void setMaxBytes(final long nativeHandle, - final long maxBytes); - - private native static long newWriteBatch(final int reserved_bytes); - private native static long newWriteBatch(final byte[] serialized, - final int serializedLength); - private native void iterate(final long handle, final long handlerHandle) - throws RocksDBException; - private native byte[] data(final long nativeHandle) throws RocksDBException; - private native long getDataSize(final long nativeHandle); - private native boolean hasPut(final long nativeHandle); - private native boolean hasDelete(final long nativeHandle); - private native boolean hasSingleDelete(final long nativeHandle); - private native boolean hasDeleteRange(final long nativeHandle); - private native boolean hasMerge(final long nativeHandle); - private native boolean hasBeginPrepare(final long nativeHandle); - private native boolean hasEndPrepare(final long nativeHandle); - private native boolean hasCommit(final long nativeHandle); - private native boolean hasRollback(final long nativeHandle); - private native void markWalTerminationPoint(final long nativeHandle); - private native SavePoint getWalTerminationPoint(final long nativeHandle); - - /** - * Handler callback for iterating over the contents of a batch. - */ - public static abstract class Handler - extends RocksCallbackObject { - public Handler() { - super(null); - } - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return createNewHandler0(); - } - - public abstract void put(final int columnFamilyId, final byte[] key, - final byte[] value) throws RocksDBException; - public abstract void put(final byte[] key, final byte[] value); - public abstract void merge(final int columnFamilyId, final byte[] key, - final byte[] value) throws RocksDBException; - public abstract void merge(final byte[] key, final byte[] value); - public abstract void delete(final int columnFamilyId, final byte[] key) - throws RocksDBException; - public abstract void delete(final byte[] key); - public abstract void singleDelete(final int columnFamilyId, - final byte[] key) throws RocksDBException; - public abstract void singleDelete(final byte[] key); - public abstract void deleteRange(final int columnFamilyId, - final byte[] beginKey, final byte[] endKey) throws RocksDBException; - public abstract void deleteRange(final byte[] beginKey, - final byte[] endKey); - public abstract void logData(final byte[] blob); - public abstract void putBlobIndex(final int columnFamilyId, - final byte[] key, final byte[] value) throws RocksDBException; - public abstract void markBeginPrepare() throws RocksDBException; - public abstract void markEndPrepare(final byte[] xid) - throws RocksDBException; - public abstract void markNoop(final boolean emptyBatch) - throws RocksDBException; - public abstract void markRollback(final byte[] xid) - throws RocksDBException; - public abstract void markCommit(final byte[] xid) - throws RocksDBException; - public abstract void markCommitWithTimestamp(final byte[] xid, final byte[] ts) - throws RocksDBException; - - /** - * shouldContinue is called by the underlying iterator - * {@link WriteBatch#iterate(Handler)}. If it returns false, - * iteration is halted. Otherwise, it continues - * iterating. The default implementation always - * returns true. - * - * @return boolean value indicating if the - * iteration is halted. - */ - public boolean shouldContinue() { - return true; - } - - private native long createNewHandler0(); - } - - /** - * A structure for describing the save point in the Write Batch. - */ - public static class SavePoint { - private long size; - private long count; - private long contentFlags; - - public SavePoint(final long size, final long count, - final long contentFlags) { - this.size = size; - this.count = count; - this.contentFlags = contentFlags; - } - - public void clear() { - this.size = 0; - this.count = 0; - this.contentFlags = 0; - } - - /** - * Get the size of the serialized representation. - * - * @return the size of the serialized representation. - */ - public long getSize() { - return size; - } - - /** - * Get the number of elements. - * - * @return the number of elements. - */ - public long getCount() { - return count; - } - - /** - * Get the content flags. - * - * @return the content flags. - */ - public long getContentFlags() { - return contentFlags; - } - - public boolean isCleared() { - return (size | count | contentFlags) == 0; - } - } -} diff --git a/java/src/main/java/org/rocksdb/WriteBatchInterface.java b/java/src/main/java/org/rocksdb/WriteBatchInterface.java deleted file mode 100644 index 92caa22b3..000000000 --- a/java/src/main/java/org/rocksdb/WriteBatchInterface.java +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - *

    Defines the interface for a Write Batch which - * holds a collection of updates to apply atomically to a DB.

    - */ -public interface WriteBatchInterface { - - /** - * Returns the number of updates in the batch. - * - * @return number of items in WriteBatch - */ - int count(); - - /** - *

    Store the mapping "key->value" in the database.

    - * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void put(byte[] key, byte[] value) throws RocksDBException; - - /** - *

    Store the mapping "key->value" within given column - * family.

    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void put(ColumnFamilyHandle columnFamilyHandle, byte[] key, byte[] value) - throws RocksDBException; - - /** - *

    Store the mapping "key->value" within given column - * family.

    - * - * @param key the specified key to be inserted. It is using position and limit. - * Supports direct buffer only. - * @param value the value associated with the specified key. It is using position and limit. - * Supports direct buffer only. - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void put(final ByteBuffer key, final ByteBuffer value) throws RocksDBException; - - /** - *

    Store the mapping "key->value" within given column - * family.

    - * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the specified key to be inserted. It is using position and limit. - * Supports direct buffer only. - * @param value the value associated with the specified key. It is using position and limit. - * Supports direct buffer only. - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void put(ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key, final ByteBuffer value) - throws RocksDBException; - - /** - *

    Merge "value" with the existing value of "key" in the database. - * "key->merge(existing, value)"

    - * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void merge(byte[] key, byte[] value) throws RocksDBException; - - /** - *

    Merge "value" with the existing value of "key" in given column family. - * "key->merge(existing, value)"

    - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void merge(ColumnFamilyHandle columnFamilyHandle, byte[] key, byte[] value) - throws RocksDBException; - - /** - *

    If the database contains a mapping for "key", erase it. Else do nothing.

    - * - * @param key Key to delete within database - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void delete(byte[] key) throws RocksDBException; - - /** - *

    If column family contains a mapping for "key", erase it. Else do nothing.

    - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key Key to delete within database - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void delete(ColumnFamilyHandle columnFamilyHandle, byte[] key) throws RocksDBException; - - /** - *

    If column family contains a mapping for "key", erase it. Else do nothing.

    - * - * @param key Key to delete within database. It is using position and limit. - * Supports direct buffer only. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void delete(final ByteBuffer key) throws RocksDBException; - - /** - *

    If column family contains a mapping for "key", erase it. Else do nothing.

    - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key Key to delete within database. It is using position and limit. - * Supports direct buffer only. - * - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void delete(ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key) - throws RocksDBException; - - /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. - * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. - * - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Experimental("Performance optimization for a very specific workload") - void singleDelete(final byte[] key) throws RocksDBException; - - /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. - * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. - * - * @param columnFamilyHandle The column family to delete the key from - * @param key Key to delete within database - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - @Experimental("Performance optimization for a very specific workload") - void singleDelete(final ColumnFamilyHandle columnFamilyHandle, final byte[] key) - throws RocksDBException; - - /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. - * - * @param beginKey - * First key to delete within database (included) - * @param endKey - * Last key to delete within database (excluded) - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void deleteRange(byte[] beginKey, byte[] endKey) throws RocksDBException; - - /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. - * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param beginKey - * First key to delete within database (included) - * @param endKey - * Last key to delete within database (excluded) - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void deleteRange(ColumnFamilyHandle columnFamilyHandle, byte[] beginKey, byte[] endKey) - throws RocksDBException; - - /** - * Append a blob of arbitrary size to the records in this batch. The blob will - * be stored in the transaction log but not in any other file. In particular, - * it will not be persisted to the SST files. When iterating over this - * WriteBatch, WriteBatch::Handler::LogData will be called with the contents - * of the blob as it is encountered. Blobs, puts, deletes, and merges will be - * encountered in the same order in thich they were inserted. The blob will - * NOT consume sequence number(s) and will NOT increase the count of the batch - * - * Example application: add timestamps to the transaction log for use in - * replication. - * - * @param blob binary object to be inserted - * @throws RocksDBException thrown if error happens in underlying native library. - */ - void putLogData(byte[] blob) throws RocksDBException; - - /** - * Clear all updates buffered in this batch - */ - void clear(); - - /** - * Records the state of the batch for future calls to RollbackToSavePoint(). - * May be called multiple times to set multiple save points. - */ - void setSavePoint(); - - /** - * Remove all entries in this batch (Put, Merge, Delete, PutLogData) since - * the most recent call to SetSavePoint() and removes the most recent save - * point. - * - * @throws RocksDBException if there is no previous call to SetSavePoint() - */ - void rollbackToSavePoint() throws RocksDBException; - - /** - * Pop the most recent save point. - * - * That is to say that it removes the last save point, - * which was set by {@link #setSavePoint()}. - * - * @throws RocksDBException If there is no previous call to - * {@link #setSavePoint()}, an exception with - * {@link Status.Code#NotFound} will be thrown. - */ - void popSavePoint() throws RocksDBException; - - /** - * Set the maximum size of the write batch. - * - * @param maxBytes the maximum size in bytes. - */ - void setMaxBytes(long maxBytes); - - /** - * Get the underlying Write Batch. - * - * @return the underlying WriteBatch. - */ - WriteBatch getWriteBatch(); -} diff --git a/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java b/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java deleted file mode 100644 index d85b8e3f7..000000000 --- a/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.ByteBuffer; - -/** - * Similar to {@link org.rocksdb.WriteBatch} but with a binary searchable - * index built for all the keys inserted. - * - * Calling put, merge, remove or putLogData calls the same function - * as with {@link org.rocksdb.WriteBatch} whilst also building an index. - * - * A user can call {@link org.rocksdb.WriteBatchWithIndex#newIterator()} to - * create an iterator over the write batch or - * {@link org.rocksdb.WriteBatchWithIndex#newIteratorWithBase(org.rocksdb.RocksIterator)} - * to get an iterator for the database with Read-Your-Own-Writes like capability - */ -public class WriteBatchWithIndex extends AbstractWriteBatch { - /** - * Creates a WriteBatchWithIndex where no bytes - * are reserved up-front, bytewise comparison is - * used for fallback key comparisons, - * and duplicate keys operations are retained - */ - public WriteBatchWithIndex() { - super(newWriteBatchWithIndex()); - } - - - /** - * Creates a WriteBatchWithIndex where no bytes - * are reserved up-front, bytewise comparison is - * used for fallback key comparisons, and duplicate key - * assignment is determined by the constructor argument - * - * @param overwriteKey if true, overwrite the key in the index when - * inserting a duplicate key, in this way an iterator will never - * show two entries with the same key. - */ - public WriteBatchWithIndex(final boolean overwriteKey) { - super(newWriteBatchWithIndex(overwriteKey)); - } - - /** - * Creates a WriteBatchWithIndex - * - * @param fallbackIndexComparator We fallback to this comparator - * to compare keys within a column family if we cannot determine - * the column family and so look up it's comparator. - * - * @param reservedBytes reserved bytes in underlying WriteBatch - * - * @param overwriteKey if true, overwrite the key in the index when - * inserting a duplicate key, in this way an iterator will never - * show two entries with the same key. - */ - public WriteBatchWithIndex( - final AbstractComparator - fallbackIndexComparator, final int reservedBytes, - final boolean overwriteKey) { - super(newWriteBatchWithIndex(fallbackIndexComparator.nativeHandle_, - fallbackIndexComparator.getComparatorType().getValue(), reservedBytes, - overwriteKey)); - } - - /** - *

    Private WriteBatchWithIndex constructor which is used to construct - * WriteBatchWithIndex instances from C++ side. As the reference to this - * object is also managed from C++ side the handle will be disowned.

    - * - * @param nativeHandle address of native instance. - */ - WriteBatchWithIndex(final long nativeHandle) { - super(nativeHandle); - disOwnNativeHandle(); - } - - /** - * Create an iterator of a column family. User can call - * {@link org.rocksdb.RocksIteratorInterface#seek(byte[])} to - * search to the next entry of or after a key. Keys will be iterated in the - * order given by index_comparator. For multiple updates on the same key, - * each update will be returned as a separate entry, in the order of update - * time. - * - * @param columnFamilyHandle The column family to iterate over - * @return An iterator for the Write Batch contents, restricted to the column - * family - */ - public WBWIRocksIterator newIterator( - final ColumnFamilyHandle columnFamilyHandle) { - return new WBWIRocksIterator(this, iterator1(nativeHandle_, - columnFamilyHandle.nativeHandle_)); - } - - /** - * Create an iterator of the default column family. User can call - * {@link org.rocksdb.RocksIteratorInterface#seek(byte[])} to - * search to the next entry of or after a key. Keys will be iterated in the - * order given by index_comparator. For multiple updates on the same key, - * each update will be returned as a separate entry, in the order of update - * time. - * - * @return An iterator for the Write Batch contents - */ - public WBWIRocksIterator newIterator() { - return new WBWIRocksIterator(this, iterator0(nativeHandle_)); - } - - /** - * Provides Read-Your-Own-Writes like functionality by - * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} - * as a delta and baseIterator as a base - * - * Updating write batch with the current key of the iterator is not safe. - * We strongly recommend users not to do it. It will invalidate the current - * key() and value() of the iterator. This invalidation happens even before - * the write batch update finishes. The state may recover after Next() is - * called. - * - * @param columnFamilyHandle The column family to iterate over - * @param baseIterator The base iterator, - * e.g. {@link org.rocksdb.RocksDB#newIterator()} - * @return An iterator which shows a view comprised of both the database - * point-in-time from baseIterator and modifications made in this write batch. - */ - public RocksIterator newIteratorWithBase( - final ColumnFamilyHandle columnFamilyHandle, - final RocksIterator baseIterator) { - return newIteratorWithBase(columnFamilyHandle, baseIterator, null); - } - - /** - * Provides Read-Your-Own-Writes like functionality by - * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} - * as a delta and baseIterator as a base - * - * Updating write batch with the current key of the iterator is not safe. - * We strongly recommend users not to do it. It will invalidate the current - * key() and value() of the iterator. This invalidation happens even before - * the write batch update finishes. The state may recover after Next() is - * called. - * - * @param columnFamilyHandle The column family to iterate over - * @param baseIterator The base iterator, - * e.g. {@link org.rocksdb.RocksDB#newIterator()} - * @param readOptions the read options, or null - * @return An iterator which shows a view comprised of both the database - * point-in-time from baseIterator and modifications made in this write batch. - */ - public RocksIterator newIteratorWithBase(final ColumnFamilyHandle columnFamilyHandle, - final RocksIterator baseIterator, /* @Nullable */ final ReadOptions readOptions) { - final RocksIterator iterator = new RocksIterator(baseIterator.parent_, - iteratorWithBase(nativeHandle_, columnFamilyHandle.nativeHandle_, - baseIterator.nativeHandle_, readOptions == null ? 0 : readOptions.nativeHandle_)); - - // when the iterator is deleted it will also delete the baseIterator - baseIterator.disOwnNativeHandle(); - - return iterator; - } - - /** - * Provides Read-Your-Own-Writes like functionality by - * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} - * as a delta and baseIterator as a base. Operates on the default column - * family. - * - * @param baseIterator The base iterator, - * e.g. {@link org.rocksdb.RocksDB#newIterator()} - * @return An iterator which shows a view comprised of both the database - * point-in-timefrom baseIterator and modifications made in this write batch. - */ - public RocksIterator newIteratorWithBase(final RocksIterator baseIterator) { - return newIteratorWithBase(baseIterator.parent_.getDefaultColumnFamily(), baseIterator, null); - } - - /** - * Provides Read-Your-Own-Writes like functionality by - * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} - * as a delta and baseIterator as a base. Operates on the default column - * family. - * - * @param baseIterator The base iterator, - * e.g. {@link org.rocksdb.RocksDB#newIterator()} - * @param readOptions the read options, or null - * @return An iterator which shows a view comprised of both the database - * point-in-timefrom baseIterator and modifications made in this write batch. - */ - public RocksIterator newIteratorWithBase(final RocksIterator baseIterator, - /* @Nullable */ final ReadOptions readOptions) { - return newIteratorWithBase( - baseIterator.parent_.getDefaultColumnFamily(), baseIterator, readOptions); - } - - /** - * Similar to {@link RocksDB#get(ColumnFamilyHandle, byte[])} but will only - * read the key from this batch. - * - * @param columnFamilyHandle The column family to retrieve the value from - * @param options The database options to use - * @param key The key to read the value for - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException if the batch does not have enough data to resolve - * Merge operations, MergeInProgress status may be returned. - */ - public byte[] getFromBatch(final ColumnFamilyHandle columnFamilyHandle, - final DBOptions options, final byte[] key) throws RocksDBException { - return getFromBatch(nativeHandle_, options.nativeHandle_, - key, key.length, columnFamilyHandle.nativeHandle_); - } - - /** - * Similar to {@link RocksDB#get(byte[])} but will only - * read the key from this batch. - * - * @param options The database options to use - * @param key The key to read the value for - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException if the batch does not have enough data to resolve - * Merge operations, MergeInProgress status may be returned. - */ - public byte[] getFromBatch(final DBOptions options, final byte[] key) - throws RocksDBException { - return getFromBatch(nativeHandle_, options.nativeHandle_, key, key.length); - } - - /** - * Similar to {@link RocksDB#get(ColumnFamilyHandle, byte[])} but will also - * read writes from this batch. - * - * This function will query both this batch and the DB and then merge - * the results using the DB's merge operator (if the batch contains any - * merge requests). - * - * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is - * read from the DB but will NOT change which keys are read from the batch - * (the keys in this batch do not yet belong to any snapshot and will be - * fetched regardless). - * - * @param db The Rocks database - * @param columnFamilyHandle The column family to retrieve the value from - * @param options The read options to use - * @param key The key to read the value for - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException if the value for the key cannot be read - */ - public byte[] getFromBatchAndDB(final RocksDB db, final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions options, final byte[] key) throws RocksDBException { - return getFromBatchAndDB(nativeHandle_, db.nativeHandle_, - options.nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); - } - - /** - * Similar to {@link RocksDB#get(byte[])} but will also - * read writes from this batch. - * - * This function will query both this batch and the DB and then merge - * the results using the DB's merge operator (if the batch contains any - * merge requests). - * - * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is - * read from the DB but will NOT change which keys are read from the batch - * (the keys in this batch do not yet belong to any snapshot and will be - * fetched regardless). - * - * @param db The Rocks database - * @param options The read options to use - * @param key The key to read the value for - * - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. - * - * @throws RocksDBException if the value for the key cannot be read - */ - public byte[] getFromBatchAndDB(final RocksDB db, final ReadOptions options, - final byte[] key) throws RocksDBException { - return getFromBatchAndDB(nativeHandle_, db.nativeHandle_, - options.nativeHandle_, key, key.length); - } - - @Override protected final native void disposeInternal(final long handle); - @Override final native int count0(final long handle); - @Override final native void put(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen); - @Override final native void put(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen, - final long cfHandle); - @Override - final native void putDirect(final long handle, final ByteBuffer key, final int keyOffset, - final int keyLength, final ByteBuffer value, final int valueOffset, final int valueLength, - final long cfHandle); - @Override final native void merge(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen); - @Override final native void merge(final long handle, final byte[] key, - final int keyLen, final byte[] value, final int valueLen, - final long cfHandle); - @Override final native void delete(final long handle, final byte[] key, - final int keyLen) throws RocksDBException; - @Override final native void delete(final long handle, final byte[] key, - final int keyLen, final long cfHandle) throws RocksDBException; - @Override final native void singleDelete(final long handle, final byte[] key, - final int keyLen) throws RocksDBException; - @Override final native void singleDelete(final long handle, final byte[] key, - final int keyLen, final long cfHandle) throws RocksDBException; - @Override - final native void deleteDirect(final long handle, final ByteBuffer key, final int keyOffset, - final int keyLength, final long cfHandle) throws RocksDBException; - // DO NOT USE - `WriteBatchWithIndex::deleteRange` is not yet supported - @Override - final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen); - // DO NOT USE - `WriteBatchWithIndex::deleteRange` is not yet supported - @Override - final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen, final long cfHandle); - @Override final native void putLogData(final long handle, final byte[] blob, - final int blobLen) throws RocksDBException; - @Override final native void clear0(final long handle); - @Override final native void setSavePoint0(final long handle); - @Override final native void rollbackToSavePoint0(final long handle); - @Override final native void popSavePoint(final long handle) throws RocksDBException; - @Override final native void setMaxBytes(final long nativeHandle, - final long maxBytes); - @Override final native WriteBatch getWriteBatch(final long handle); - - private native static long newWriteBatchWithIndex(); - private native static long newWriteBatchWithIndex(final boolean overwriteKey); - private native static long newWriteBatchWithIndex( - final long fallbackIndexComparatorHandle, - final byte comparatorType, final int reservedBytes, - final boolean overwriteKey); - private native long iterator0(final long handle); - private native long iterator1(final long handle, final long cfHandle); - private native long iteratorWithBase(final long handle, final long cfHandle, - final long baseIteratorHandle, final long readOptionsHandle); - private native byte[] getFromBatch(final long handle, final long optHandle, - final byte[] key, final int keyLen); - private native byte[] getFromBatch(final long handle, final long optHandle, - final byte[] key, final int keyLen, final long cfHandle); - private native byte[] getFromBatchAndDB(final long handle, - final long dbHandle, final long readOptHandle, final byte[] key, - final int keyLen); - private native byte[] getFromBatchAndDB(final long handle, - final long dbHandle, final long readOptHandle, final byte[] key, - final int keyLen, final long cfHandle); -} diff --git a/java/src/main/java/org/rocksdb/WriteBufferManager.java b/java/src/main/java/org/rocksdb/WriteBufferManager.java deleted file mode 100644 index 8ec963958..000000000 --- a/java/src/main/java/org/rocksdb/WriteBufferManager.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Java wrapper over native write_buffer_manager class - */ -public class WriteBufferManager extends RocksObject { - static { - RocksDB.loadLibrary(); - } - - /** - * Construct a new instance of WriteBufferManager. - * - * Check - * https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager - * for more details on when to use it - * - * @param bufferSizeBytes buffer size(in bytes) to use for native write_buffer_manager - * @param cache cache whose memory should be bounded by this write buffer manager - * @param allowStall if set true, it will enable stalling of writes when memory_usage() exceeds - * buffer_size. - * It will wait for flush to complete and memory usage to drop down. - */ - public WriteBufferManager( - final long bufferSizeBytes, final Cache cache, final boolean allowStall) { - super(newWriteBufferManager(bufferSizeBytes, cache.nativeHandle_, allowStall)); - this.allowStall_ = allowStall; - } - - public WriteBufferManager(final long bufferSizeBytes, final Cache cache){ - this(bufferSizeBytes, cache, false); - } - - public boolean allowStall() { - return allowStall_; - } - - private native static long newWriteBufferManager( - final long bufferSizeBytes, final long cacheHandle, final boolean allowStall); - - @Override - protected native void disposeInternal(final long handle); - - private boolean allowStall_; -} diff --git a/java/src/main/java/org/rocksdb/WriteOptions.java b/java/src/main/java/org/rocksdb/WriteOptions.java deleted file mode 100644 index 5a3ffa6c5..000000000 --- a/java/src/main/java/org/rocksdb/WriteOptions.java +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Options that control write operations. - * - * Note that developers should call WriteOptions.dispose() to release the - * c++ side memory before a WriteOptions instance runs out of scope. - */ -public class WriteOptions extends RocksObject { - /** - * Construct WriteOptions instance. - */ - public WriteOptions() { - super(newWriteOptions()); - - } - - // TODO(AR) consider ownership - WriteOptions(final long nativeHandle) { - super(nativeHandle); - disOwnNativeHandle(); - } - - /** - * Copy constructor for WriteOptions. - * - * NOTE: This does a shallow copy, which means comparator, merge_operator, compaction_filter, - * compaction_filter_factory and other pointers will be cloned! - * - * @param other The ColumnFamilyOptions to copy. - */ - public WriteOptions(WriteOptions other) { - super(copyWriteOptions(other.nativeHandle_)); - } - - - /** - * If true, the write will be flushed from the operating system - * buffer cache (by calling WritableFile::Sync()) before the write - * is considered complete. If this flag is true, writes will be - * slower. - * - * If this flag is false, and the machine crashes, some recent - * writes may be lost. Note that if it is just the process that - * crashes (i.e., the machine does not reboot), no writes will be - * lost even if sync==false. - * - * In other words, a DB write with sync==false has similar - * crash semantics as the "write()" system call. A DB write - * with sync==true has similar crash semantics to a "write()" - * system call followed by "fdatasync()". - * - * Default: false - * - * @param flag a boolean flag to indicate whether a write - * should be synchronized. - * @return the instance of the current WriteOptions. - */ - public WriteOptions setSync(final boolean flag) { - setSync(nativeHandle_, flag); - return this; - } - - /** - * If true, the write will be flushed from the operating system - * buffer cache (by calling WritableFile::Sync()) before the write - * is considered complete. If this flag is true, writes will be - * slower. - * - * If this flag is false, and the machine crashes, some recent - * writes may be lost. Note that if it is just the process that - * crashes (i.e., the machine does not reboot), no writes will be - * lost even if sync==false. - * - * In other words, a DB write with sync==false has similar - * crash semantics as the "write()" system call. A DB write - * with sync==true has similar crash semantics to a "write()" - * system call followed by "fdatasync()". - * - * @return boolean value indicating if sync is active. - */ - public boolean sync() { - return sync(nativeHandle_); - } - - /** - * If true, writes will not first go to the write ahead log, - * and the write may got lost after a crash. The backup engine - * relies on write-ahead logs to back up the memtable, so if - * you disable write-ahead logs, you must create backups with - * flush_before_backup=true to avoid losing unflushed memtable data. - * - * @param flag a boolean flag to specify whether to disable - * write-ahead-log on writes. - * @return the instance of the current WriteOptions. - */ - public WriteOptions setDisableWAL(final boolean flag) { - setDisableWAL(nativeHandle_, flag); - return this; - } - - /** - * If true, writes will not first go to the write ahead log, - * and the write may got lost after a crash. The backup engine - * relies on write-ahead logs to back up the memtable, so if - * you disable write-ahead logs, you must create backups with - * flush_before_backup=true to avoid losing unflushed memtable data. - * - * @return boolean value indicating if WAL is disabled. - */ - public boolean disableWAL() { - return disableWAL(nativeHandle_); - } - - /** - * If true and if user is trying to write to column families that don't exist - * (they were dropped), ignore the write (don't return an error). If there - * are multiple writes in a WriteBatch, other writes will succeed. - * - * Default: false - * - * @param ignoreMissingColumnFamilies true to ignore writes to column families - * which don't exist - * @return the instance of the current WriteOptions. - */ - public WriteOptions setIgnoreMissingColumnFamilies( - final boolean ignoreMissingColumnFamilies) { - setIgnoreMissingColumnFamilies(nativeHandle_, ignoreMissingColumnFamilies); - return this; - } - - /** - * If true and if user is trying to write to column families that don't exist - * (they were dropped), ignore the write (don't return an error). If there - * are multiple writes in a WriteBatch, other writes will succeed. - * - * Default: false - * - * @return true if writes to column families which don't exist are ignored - */ - public boolean ignoreMissingColumnFamilies() { - return ignoreMissingColumnFamilies(nativeHandle_); - } - - /** - * If true and we need to wait or sleep for the write request, fails - * immediately with {@link Status.Code#Incomplete}. - * - * @param noSlowdown true to fail write requests if we need to wait or sleep - * @return the instance of the current WriteOptions. - */ - public WriteOptions setNoSlowdown(final boolean noSlowdown) { - setNoSlowdown(nativeHandle_, noSlowdown); - return this; - } - - /** - * If true and we need to wait or sleep for the write request, fails - * immediately with {@link Status.Code#Incomplete}. - * - * @return true when write requests are failed if we need to wait or sleep - */ - public boolean noSlowdown() { - return noSlowdown(nativeHandle_); - } - - /** - * If true, this write request is of lower priority if compaction is - * behind. In the case that, {@link #noSlowdown()} == true, the request - * will be cancelled immediately with {@link Status.Code#Incomplete} returned. - * Otherwise, it will be slowed down. The slowdown value is determined by - * RocksDB to guarantee it introduces minimum impacts to high priority writes. - * - * Default: false - * - * @param lowPri true if the write request should be of lower priority than - * compactions which are behind. - * - * @return the instance of the current WriteOptions. - */ - public WriteOptions setLowPri(final boolean lowPri) { - setLowPri(nativeHandle_, lowPri); - return this; - } - - /** - * Returns true if this write request is of lower priority if compaction is - * behind. - * - * See {@link #setLowPri(boolean)}. - * - * @return true if this write request is of lower priority, false otherwise. - */ - public boolean lowPri() { - return lowPri(nativeHandle_); - } - - /** - * If true, this writebatch will maintain the last insert positions of each - * memtable as hints in concurrent write. It can improve write performance - * in concurrent writes if keys in one writebatch are sequential. In - * non-concurrent writes (when {@code concurrent_memtable_writes} is false) this - * option will be ignored. - * - * Default: false - * - * @return true if writebatch will maintain the last insert positions of each memtable as hints in - * concurrent write. - */ - public boolean memtableInsertHintPerBatch() { - return memtableInsertHintPerBatch(nativeHandle_); - } - - /** - * If true, this writebatch will maintain the last insert positions of each - * memtable as hints in concurrent write. It can improve write performance - * in concurrent writes if keys in one writebatch are sequential. In - * non-concurrent writes (when {@code concurrent_memtable_writes} is false) this - * option will be ignored. - * - * Default: false - * - * @param memtableInsertHintPerBatch true if writebatch should maintain the last insert positions - * of each memtable as hints in concurrent write. - * @return the instance of the current WriteOptions. - */ - public WriteOptions setMemtableInsertHintPerBatch(final boolean memtableInsertHintPerBatch) { - setMemtableInsertHintPerBatch(nativeHandle_, memtableInsertHintPerBatch); - return this; - } - - private native static long newWriteOptions(); - private native static long copyWriteOptions(long handle); - @Override protected final native void disposeInternal(final long handle); - - private native void setSync(long handle, boolean flag); - private native boolean sync(long handle); - private native void setDisableWAL(long handle, boolean flag); - private native boolean disableWAL(long handle); - private native void setIgnoreMissingColumnFamilies(final long handle, - final boolean ignoreMissingColumnFamilies); - private native boolean ignoreMissingColumnFamilies(final long handle); - private native void setNoSlowdown(final long handle, - final boolean noSlowdown); - private native boolean noSlowdown(final long handle); - private native void setLowPri(final long handle, final boolean lowPri); - private native boolean lowPri(final long handle); - private native boolean memtableInsertHintPerBatch(final long handle); - private native void setMemtableInsertHintPerBatch( - final long handle, final boolean memtableInsertHintPerBatch); -} diff --git a/java/src/main/java/org/rocksdb/WriteStallCondition.java b/java/src/main/java/org/rocksdb/WriteStallCondition.java deleted file mode 100644 index 98d9e2ce4..000000000 --- a/java/src/main/java/org/rocksdb/WriteStallCondition.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public enum WriteStallCondition { - DELAYED((byte) 0x0), - STOPPED((byte) 0x1), - NORMAL((byte) 0x2); - - private final byte value; - - WriteStallCondition(final byte value) { - this.value = value; - } - - /** - * Get the internal representation. - * - * @return the internal representation - */ - byte getValue() { - return value; - } - - /** - * Get the WriteStallCondition from the internal representation value. - * - * @return the flush reason. - * - * @throws IllegalArgumentException if the value is unknown. - */ - static WriteStallCondition fromValue(final byte value) { - for (final WriteStallCondition writeStallCondition : WriteStallCondition.values()) { - if (writeStallCondition.value == value) { - return writeStallCondition; - } - } - - throw new IllegalArgumentException("Illegal value provided for WriteStallCondition: " + value); - } -} diff --git a/java/src/main/java/org/rocksdb/WriteStallInfo.java b/java/src/main/java/org/rocksdb/WriteStallInfo.java deleted file mode 100644 index 4aef0eda9..000000000 --- a/java/src/main/java/org/rocksdb/WriteStallInfo.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Objects; - -public class WriteStallInfo { - private final String columnFamilyName; - private final WriteStallCondition currentCondition; - private final WriteStallCondition previousCondition; - - /** - * Access is package private as this will only be constructed from - * C++ via JNI and for testing. - */ - WriteStallInfo(final String columnFamilyName, final byte currentConditionValue, - final byte previousConditionValue) { - this.columnFamilyName = columnFamilyName; - this.currentCondition = WriteStallCondition.fromValue(currentConditionValue); - this.previousCondition = WriteStallCondition.fromValue(previousConditionValue); - } - - /** - * Get the name of the column family. - * - * @return the name of the column family. - */ - public String getColumnFamilyName() { - return columnFamilyName; - } - - /** - * Get the current state of the write controller. - * - * @return the current state. - */ - public WriteStallCondition getCurrentCondition() { - return currentCondition; - } - - /** - * Get the previous state of the write controller. - * - * @return the previous state. - */ - public WriteStallCondition getPreviousCondition() { - return previousCondition; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - WriteStallInfo that = (WriteStallInfo) o; - return Objects.equals(columnFamilyName, that.columnFamilyName) - && currentCondition == that.currentCondition && previousCondition == that.previousCondition; - } - - @Override - public int hashCode() { - return Objects.hash(columnFamilyName, currentCondition, previousCondition); - } - - @Override - public String toString() { - return "WriteStallInfo{" - + "columnFamilyName='" + columnFamilyName + '\'' + ", currentCondition=" + currentCondition - + ", previousCondition=" + previousCondition + '}'; - } -} diff --git a/java/src/main/java/org/rocksdb/util/ByteUtil.java b/java/src/main/java/org/rocksdb/util/ByteUtil.java deleted file mode 100644 index 5d64d5dcf..000000000 --- a/java/src/main/java/org/rocksdb/util/ByteUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import java.nio.ByteBuffer; - -import static java.nio.charset.StandardCharsets.UTF_8; - -public class ByteUtil { - - /** - * Convert a String to a UTF-8 byte array. - * - * @param str the string - * - * @return the byte array. - */ - public static byte[] bytes(final String str) { - return str.getBytes(UTF_8); - } - - /** - * Compares the first {@code count} bytes of two areas of memory. Returns - * zero if they are the same, a value less than zero if {@code x} is - * lexically less than {@code y}, or a value greater than zero if {@code x} - * is lexically greater than {@code y}. Note that lexical order is determined - * as if comparing unsigned char arrays. - * - * Similar to memcmp.c. - * - * @param x the first value to compare with - * @param y the second value to compare against - * @param count the number of bytes to compare - * - * @return the result of the comparison - */ - public static int memcmp(final ByteBuffer x, final ByteBuffer y, - final int count) { - for (int idx = 0; idx < count; idx++) { - final int aa = x.get(idx) & 0xff; - final int bb = y.get(idx) & 0xff; - if (aa != bb) { - return aa - bb; - } - } - return 0; - } -} diff --git a/java/src/main/java/org/rocksdb/util/BytewiseComparator.java b/java/src/main/java/org/rocksdb/util/BytewiseComparator.java deleted file mode 100644 index 9561b0a31..000000000 --- a/java/src/main/java/org/rocksdb/util/BytewiseComparator.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.rocksdb.*; - -import java.nio.ByteBuffer; - -import static org.rocksdb.util.ByteUtil.memcmp; - -/** - * This is a Java Native implementation of the C++ - * equivalent BytewiseComparatorImpl using {@link Slice} - * - * The performance of Comparators implemented in Java is always - * less than their C++ counterparts due to the bridging overhead, - * as such you likely don't want to use this apart from benchmarking - * and you most likely instead wanted - * {@link org.rocksdb.BuiltinComparator#BYTEWISE_COMPARATOR} - */ -public final class BytewiseComparator extends AbstractComparator { - - public BytewiseComparator(final ComparatorOptions copt) { - super(copt); - } - - @Override - public String name() { - return "rocksdb.java.BytewiseComparator"; - } - - @Override - public int compare(final ByteBuffer a, final ByteBuffer b) { - return _compare(a, b); - } - - static int _compare(final ByteBuffer a, final ByteBuffer b) { - assert(a != null && b != null); - final int minLen = a.remaining() < b.remaining() ? - a.remaining() : b.remaining(); - int r = memcmp(a, b, minLen); - if (r == 0) { - if (a.remaining() < b.remaining()) { - r = -1; - } else if (a.remaining() > b.remaining()) { - r = +1; - } - } - return r; - } - - @Override - public void findShortestSeparator(final ByteBuffer start, - final ByteBuffer limit) { - // Find length of common prefix - final int minLength = Math.min(start.remaining(), limit.remaining()); - int diffIndex = 0; - while (diffIndex < minLength && - start.get(diffIndex) == limit.get(diffIndex)) { - diffIndex++; - } - - if (diffIndex >= minLength) { - // Do not shorten if one string is a prefix of the other - } else { - final int startByte = start.get(diffIndex) & 0xff; - final int limitByte = limit.get(diffIndex) & 0xff; - if (startByte >= limitByte) { - // Cannot shorten since limit is smaller than start or start is - // already the shortest possible. - return; - } - assert(startByte < limitByte); - - if (diffIndex < limit.remaining() - 1 || startByte + 1 < limitByte) { - start.put(diffIndex, (byte)((start.get(diffIndex) & 0xff) + 1)); - start.limit(diffIndex + 1); - } else { - // v - // A A 1 A A A - // A A 2 - // - // Incrementing the current byte will make start bigger than limit, we - // will skip this byte, and find the first non 0xFF byte in start and - // increment it. - diffIndex++; - - while (diffIndex < start.remaining()) { - // Keep moving until we find the first non 0xFF byte to - // increment it - if ((start.get(diffIndex) & 0xff) < - 0xff) { - start.put(diffIndex, (byte)((start.get(diffIndex) & 0xff) + 1)); - start.limit(diffIndex + 1); - break; - } - diffIndex++; - } - } - assert(compare(start.duplicate(), limit.duplicate()) < 0); - } - } - - @Override - public void findShortSuccessor(final ByteBuffer key) { - // Find first character that can be incremented - final int n = key.remaining(); - for (int i = 0; i < n; i++) { - final int byt = key.get(i) & 0xff; - if (byt != 0xff) { - key.put(i, (byte)(byt + 1)); - key.limit(i+1); - return; - } - } - // *key is a run of 0xffs. Leave it alone. - } -} diff --git a/java/src/main/java/org/rocksdb/util/Environment.java b/java/src/main/java/org/rocksdb/util/Environment.java deleted file mode 100644 index 9ad51c7c7..000000000 --- a/java/src/main/java/org/rocksdb/util/Environment.java +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb.util; - -import java.io.File; -import java.io.IOException; - -public class Environment { - private static String OS = System.getProperty("os.name").toLowerCase(); - private static String ARCH = System.getProperty("os.arch").toLowerCase(); - private static String MUSL_ENVIRONMENT = System.getenv("ROCKSDB_MUSL_LIBC"); - - /** - * Will be lazily initialised by {@link #isMuslLibc()} instead of the previous static - * initialisation. The lazy initialisation prevents Windows from reporting suspicious behaviour of - * the JVM attempting IO on Unix paths. - */ - private static Boolean MUSL_LIBC = null; - - public static boolean isAarch64() { - return ARCH.contains("aarch64"); - } - - public static boolean isPowerPC() { - return ARCH.contains("ppc"); - } - - public static boolean isS390x() { - return ARCH.contains("s390x"); - } - - public static boolean isWindows() { - return (OS.contains("win")); - } - - public static boolean isFreeBSD() { - return (OS.contains("freebsd")); - } - - public static boolean isMac() { - return (OS.contains("mac")); - } - - public static boolean isAix() { - return OS.contains("aix"); - } - - public static boolean isUnix() { - return OS.contains("nix") || - OS.contains("nux"); - } - - /** - * Determine if the environment has a musl libc. - * - * @return true if the environment has a musl libc, false otherwise. - */ - public static boolean isMuslLibc() { - if (MUSL_LIBC == null) { - MUSL_LIBC = initIsMuslLibc(); - } - return MUSL_LIBC; - } - - /** - * Determine if the environment has a musl libc. - * - * The initialisation counterpart of {@link #isMuslLibc()}. - * - * Intentionally package-private for testing. - * - * @return true if the environment has a musl libc, false otherwise. - */ - static boolean initIsMuslLibc() { - // consider explicit user setting from environment first - if ("true".equalsIgnoreCase(MUSL_ENVIRONMENT)) { - return true; - } - if ("false".equalsIgnoreCase(MUSL_ENVIRONMENT)) { - return false; - } - - // check if ldd indicates a muslc lib - try { - final Process p = - new ProcessBuilder("/usr/bin/env", "sh", "-c", "ldd /usr/bin/env | grep -q musl").start(); - if (p.waitFor() == 0) { - return true; - } - } catch (final IOException | InterruptedException e) { - // do nothing, and move on to the next check - } - - final File lib = new File("/lib"); - if (lib.exists() && lib.isDirectory() && lib.canRead()) { - // attempt the most likely musl libc name first - final String possibleMuslcLibName; - if (isPowerPC()) { - possibleMuslcLibName = "libc.musl-ppc64le.so.1"; - } else if (isAarch64()) { - possibleMuslcLibName = "libc.musl-aarch64.so.1"; - } else if (isS390x()) { - possibleMuslcLibName = "libc.musl-s390x.so.1"; - } else { - possibleMuslcLibName = "libc.musl-x86_64.so.1"; - } - final File possibleMuslcLib = new File(lib, possibleMuslcLibName); - if (possibleMuslcLib.exists() && possibleMuslcLib.canRead()) { - return true; - } - - // fallback to scanning for a musl libc - final File[] libFiles = lib.listFiles(); - if (libFiles == null) { - return false; - } - for (final File f : libFiles) { - if (f.getName().startsWith("libc.musl")) { - return true; - } - } - } - - return false; - } - - public static boolean isSolaris() { - return OS.contains("sunos"); - } - - public static boolean isOpenBSD() { - return (OS.contains("openbsd")); - } - - public static boolean is64Bit() { - if (ARCH.indexOf("sparcv9") >= 0) { - return true; - } - return (ARCH.indexOf("64") > 0); - } - - public static String getSharedLibraryName(final String name) { - return name + "jni"; - } - - public static String getSharedLibraryFileName(final String name) { - return appendLibOsSuffix("lib" + getSharedLibraryName(name), true); - } - - /** - * Get the name of the libc implementation - * - * @return the name of the implementation, - * or null if the default for that platform (e.g. glibc on Linux). - */ - public static /* @Nullable */ String getLibcName() { - if (isMuslLibc()) { - return "musl"; - } else { - return null; - } - } - - private static String getLibcPostfix() { - final String libcName = getLibcName(); - if (libcName == null) { - return ""; - } - return "-" + libcName; - } - - public static String getJniLibraryName(final String name) { - if (isUnix()) { - final String arch = is64Bit() ? "64" : "32"; - if (isPowerPC() || isAarch64()) { - return String.format("%sjni-linux-%s%s", name, ARCH, getLibcPostfix()); - } else if (isS390x()) { - return String.format("%sjni-linux-%s", name, ARCH); - } else { - return String.format("%sjni-linux%s%s", name, arch, getLibcPostfix()); - } - } else if (isMac()) { - if (is64Bit()) { - final String arch; - if (isAarch64()) { - arch = "arm64"; - } else { - arch = "x86_64"; - } - return String.format("%sjni-osx-%s", name, arch); - } else { - return String.format("%sjni-osx", name); - } - } else if (isFreeBSD()) { - return String.format("%sjni-freebsd%s", name, is64Bit() ? "64" : "32"); - } else if (isAix() && is64Bit()) { - return String.format("%sjni-aix64", name); - } else if (isSolaris()) { - final String arch = is64Bit() ? "64" : "32"; - return String.format("%sjni-solaris%s", name, arch); - } else if (isWindows() && is64Bit()) { - return String.format("%sjni-win64", name); - } else if (isOpenBSD()) { - return String.format("%sjni-openbsd%s", name, is64Bit() ? "64" : "32"); - } - - throw new UnsupportedOperationException(String.format("Cannot determine JNI library name for ARCH='%s' OS='%s' name='%s'", ARCH, OS, name)); - } - - public static /*@Nullable*/ String getFallbackJniLibraryName(final String name) { - if (isMac() && is64Bit()) { - return String.format("%sjni-osx", name); - } - return null; - } - - public static String getJniLibraryFileName(final String name) { - return appendLibOsSuffix("lib" + getJniLibraryName(name), false); - } - - public static /*@Nullable*/ String getFallbackJniLibraryFileName(final String name) { - final String fallbackJniLibraryName = getFallbackJniLibraryName(name); - if (fallbackJniLibraryName == null) { - return null; - } - return appendLibOsSuffix("lib" + fallbackJniLibraryName, false); - } - - private static String appendLibOsSuffix(final String libraryFileName, final boolean shared) { - if (isUnix() || isAix() || isSolaris() || isFreeBSD() || isOpenBSD()) { - return libraryFileName + ".so"; - } else if (isMac()) { - return libraryFileName + (shared ? ".dylib" : ".jnilib"); - } else if (isWindows()) { - return libraryFileName + ".dll"; - } - throw new UnsupportedOperationException(); - } - - public static String getJniLibraryExtension() { - if (isWindows()) { - return ".dll"; - } - return (isMac()) ? ".jnilib" : ".so"; - } -} diff --git a/java/src/main/java/org/rocksdb/util/IntComparator.java b/java/src/main/java/org/rocksdb/util/IntComparator.java deleted file mode 100644 index cc096cd14..000000000 --- a/java/src/main/java/org/rocksdb/util/IntComparator.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.rocksdb.AbstractComparator; -import org.rocksdb.ComparatorOptions; - -import java.nio.ByteBuffer; - -/** - * This is a Java implementation of a Comparator for Java int - * keys. - * - * This comparator assumes keys are (at least) four bytes, so - * the caller must guarantee that in accessing other APIs in - * combination with this comparator. - * - * The performance of Comparators implemented in Java is always - * less than their C++ counterparts due to the bridging overhead, - * as such you likely don't want to use this apart from benchmarking - * or testing. - */ -public final class IntComparator extends AbstractComparator { - - public IntComparator(final ComparatorOptions copt) { - super(copt); - } - - @Override - public String name() { - return "rocksdb.java.IntComparator"; - } - - @Override - public int compare(final ByteBuffer a, final ByteBuffer b) { - return compareIntKeys(a, b); - } - - /** - * Compares integer keys - * so that they are in ascending order - * - * @param a 4-bytes representing an integer key - * @param b 4-bytes representing an integer key - * - * @return negative if a < b, 0 if a == b, positive otherwise - */ - private final int compareIntKeys(final ByteBuffer a, final ByteBuffer b) { - final int iA = a.getInt(); - final int iB = b.getInt(); - - // protect against int key calculation overflow - final long diff = (long)iA - iB; - final int result; - if (diff < Integer.MIN_VALUE) { - result = Integer.MIN_VALUE; - } else if(diff > Integer.MAX_VALUE) { - result = Integer.MAX_VALUE; - } else { - result = (int)diff; - } - return result; - } -} diff --git a/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java b/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java deleted file mode 100644 index 4c06f80aa..000000000 --- a/java/src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.rocksdb.AbstractComparator; -import org.rocksdb.BuiltinComparator; -import org.rocksdb.ComparatorOptions; -import org.rocksdb.Slice; - -import java.nio.ByteBuffer; - -/** - * This is a Java Native implementation of the C++ - * equivalent ReverseBytewiseComparatorImpl using {@link Slice} - * - * The performance of Comparators implemented in Java is always - * less than their C++ counterparts due to the bridging overhead, - * as such you likely don't want to use this apart from benchmarking - * and you most likely instead wanted - * {@link BuiltinComparator#REVERSE_BYTEWISE_COMPARATOR} - */ -public final class ReverseBytewiseComparator extends AbstractComparator { - - public ReverseBytewiseComparator(final ComparatorOptions copt) { - super(copt); - } - - @Override - public String name() { - return "rocksdb.java.ReverseBytewiseComparator"; - } - - @Override - public int compare(final ByteBuffer a, final ByteBuffer b) { - return -BytewiseComparator._compare(a, b); - } - - @Override - public void findShortestSeparator(final ByteBuffer start, - final ByteBuffer limit) { - // Find length of common prefix - final int minLength = Math.min(start.remaining(), limit.remaining()); - int diffIndex = 0; - while (diffIndex < minLength && - start.get(diffIndex) == limit.get(diffIndex)) { - diffIndex++; - } - - assert(diffIndex <= minLength); - if (diffIndex == minLength) { - // Do not shorten if one string is a prefix of the other - // - // We could handle cases like: - // V - // A A 2 X Y - // A A 2 - // in a similar way as BytewiseComparator::FindShortestSeparator(). - // We keep it simple by not implementing it. We can come back to it - // later when needed. - } else { - final int startByte = start.get(diffIndex) & 0xff; - final int limitByte = limit.get(diffIndex) & 0xff; - if (startByte > limitByte && diffIndex < start.remaining() - 1) { - // Case like - // V - // A A 3 A A - // A A 1 B B - // - // or - // v - // A A 2 A A - // A A 1 B B - // In this case "AA2" will be good. -//#ifndef NDEBUG -// std::string old_start = *start; -//#endif - start.limit(diffIndex + 1); -//#ifndef NDEBUG -// assert(old_start >= *start); -//#endif - assert(BytewiseComparator._compare(start.duplicate(), limit.duplicate()) > 0); - } - } - } -} diff --git a/java/src/main/java/org/rocksdb/util/SizeUnit.java b/java/src/main/java/org/rocksdb/util/SizeUnit.java deleted file mode 100644 index 0f717e8d4..000000000 --- a/java/src/main/java/org/rocksdb/util/SizeUnit.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -public class SizeUnit { - public static final long KB = 1024L; - public static final long MB = KB * KB; - public static final long GB = KB * MB; - public static final long TB = KB * GB; - public static final long PB = KB * TB; - - private SizeUnit() {} -} diff --git a/java/src/test/java/org/rocksdb/AbstractTransactionTest.java b/java/src/test/java/org/rocksdb/AbstractTransactionTest.java deleted file mode 100644 index 46685f9fd..000000000 --- a/java/src/test/java/org/rocksdb/AbstractTransactionTest.java +++ /dev/null @@ -1,965 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * Base class of {@link TransactionTest} and {@link OptimisticTransactionTest} - */ -public abstract class AbstractTransactionTest { - - protected final static byte[] TXN_TEST_COLUMN_FAMILY = "txn_test_cf" - .getBytes(); - - protected static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - public abstract DBContainer startDb() - throws RocksDBException; - - @Test - public void setSnapshot() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.setSnapshot(); - } - } - - @Test - public void setSnapshotOnNextOperation() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.setSnapshotOnNextOperation(); - txn.put("key1".getBytes(), "value1".getBytes()); - } - } - - @Test - public void setSnapshotOnNextOperation_transactionNotifier() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - - try(final TestTransactionNotifier notifier = new TestTransactionNotifier()) { - txn.setSnapshotOnNextOperation(notifier); - txn.put("key1".getBytes(), "value1".getBytes()); - - txn.setSnapshotOnNextOperation(notifier); - txn.put("key2".getBytes(), "value2".getBytes()); - - assertThat(notifier.getCreatedSnapshots().size()).isEqualTo(2); - } - } - } - - @Test - public void getSnapshot() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.setSnapshot(); - final Snapshot snapshot = txn.getSnapshot(); - assertThat(snapshot.isOwningHandle()).isFalse(); - } - } - - @Test - public void getSnapshot_null() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - final Snapshot snapshot = txn.getSnapshot(); - assertThat(snapshot).isNull(); - } - } - - @Test - public void clearSnapshot() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.setSnapshot(); - txn.clearSnapshot(); - } - } - - @Test - public void clearSnapshot_none() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.clearSnapshot(); - } - } - - @Test - public void commit() throws RocksDBException { - final byte k1[] = "rollback-key1".getBytes(UTF_8); - final byte v1[] = "rollback-value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb()) { - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.commit(); - } - - try(final ReadOptions readOptions = new ReadOptions(); - final Transaction txn2 = dbContainer.beginTransaction()) { - assertThat(txn2.get(readOptions, k1)).isEqualTo(v1); - } - } - } - - @Test - public void rollback() throws RocksDBException { - final byte k1[] = "rollback-key1".getBytes(UTF_8); - final byte v1[] = "rollback-value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb()) { - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.rollback(); - } - - try(final ReadOptions readOptions = new ReadOptions(); - final Transaction txn2 = dbContainer.beginTransaction()) { - assertThat(txn2.get(readOptions, k1)).isNull(); - } - } - } - - @Test - public void savePoint() throws RocksDBException { - final byte k1[] = "savePoint-key1".getBytes(UTF_8); - final byte v1[] = "savePoint-value1".getBytes(UTF_8); - final byte k2[] = "savePoint-key2".getBytes(UTF_8); - final byte v2[] = "savePoint-value2".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - - txn.setSavePoint(); - - txn.put(k2, v2); - - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - assertThat(txn.get(readOptions, k2)).isEqualTo(v2); - - txn.rollbackToSavePoint(); - - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - assertThat(txn.get(readOptions, k2)).isNull(); - - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - assertThat(txn2.get(readOptions, k1)).isEqualTo(v1); - assertThat(txn2.get(readOptions, k2)).isNull(); - } - } - } - - @Test - public void getPut_cf() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - assertThat(txn.get(testCf, readOptions, k1)).isNull(); - txn.put(testCf, k1, v1); - assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); - } - } - - @Test - public void getPut() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isNull(); - txn.put(k1, v1); - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - } - } - - @Test - public void multiGetPut_cf() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(new byte[][] { null, null }); - - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); - } - } - - @Test - public void multiGetPutAsList_cf() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - assertThat(txn.multiGetAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(null, null); - - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(values); - } - } - - @Test - public void multiGetPut() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - - assertThat(txn.multiGet(readOptions, keys)).isEqualTo(new byte[][] { null, null }); - - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); - } - } - - @Test - public void multiGetPutAsList() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.multiGetAsList(readOptions, Arrays.asList(keys))).containsExactly(null, null); - - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, Arrays.asList(keys))).containsExactly(values); - } - } - - @Test - public void getForUpdate_cf() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isNull(); - txn.put(testCf, k1, v1); - assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); - } - } - - @Test - public void getForUpdate() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getForUpdate(readOptions, k1, true)).isNull(); - txn.put(k1, v1); - assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1); - } - } - - @Test - public void multiGetForUpdate_cf() throws RocksDBException { - final byte keys[][] = new byte[][] { - "key1".getBytes(UTF_8), - "key2".getBytes(UTF_8)}; - final byte values[][] = new byte[][] { - "value1".getBytes(UTF_8), - "value2".getBytes(UTF_8)}; - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - assertThat(txn.multiGetForUpdate(readOptions, cfList, keys)) - .isEqualTo(new byte[][] { null, null }); - - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGetForUpdate(readOptions, cfList, keys)) - .isEqualTo(values); - } - } - - @Test - public void multiGetForUpdate() throws RocksDBException { - final byte keys[][] = new byte[][]{ - "key1".getBytes(UTF_8), - "key2".getBytes(UTF_8)}; - final byte values[][] = new byte[][]{ - "value1".getBytes(UTF_8), - "value2".getBytes(UTF_8)}; - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.multiGetForUpdate(readOptions, keys)).isEqualTo(new byte[][]{null, null}); - - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGetForUpdate(readOptions, keys)).isEqualTo(values); - } - } - - @Test - public void getIterator() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - txn.put(k1, v1); - - try(final RocksIterator iterator = txn.getIterator(readOptions)) { - iterator.seek(k1); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo(k1); - assertThat(iterator.value()).isEqualTo(v1); - } - } - } - - @Test - public void getIterator_cf() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - txn.put(testCf, k1, v1); - - try(final RocksIterator iterator = txn.getIterator(readOptions, testCf)) { - iterator.seek(k1); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo(k1); - assertThat(iterator.value()).isEqualTo(v1); - } - } - } - - @Test - public void merge_cf() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.merge(testCf, k1, v1); - } - } - - @Test - public void merge() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.merge(k1, v1); - } - } - - - @Test - public void delete_cf() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.put(testCf, k1, v1); - assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); - - txn.delete(testCf, k1); - assertThat(txn.get(testCf, readOptions, k1)).isNull(); - } - } - - @Test - public void delete() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - - txn.delete(k1); - assertThat(txn.get(readOptions, k1)).isNull(); - } - } - - @Test - public void delete_parts_cf() throws RocksDBException { - final byte keyParts[][] = new byte[][] { - "ke".getBytes(UTF_8), - "y1".getBytes(UTF_8)}; - final byte valueParts[][] = new byte[][] { - "val".getBytes(UTF_8), - "ue1".getBytes(UTF_8)}; - final byte[] key = concat(keyParts); - final byte[] value = concat(valueParts); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.put(testCf, keyParts, valueParts); - assertThat(txn.get(testCf, readOptions, key)).isEqualTo(value); - - txn.delete(testCf, keyParts); - - assertThat(txn.get(testCf, readOptions, key)) - .isNull(); - } - } - - @Test - public void delete_parts() throws RocksDBException { - final byte keyParts[][] = new byte[][] { - "ke".getBytes(UTF_8), - "y1".getBytes(UTF_8)}; - final byte valueParts[][] = new byte[][] { - "val".getBytes(UTF_8), - "ue1".getBytes(UTF_8)}; - final byte[] key = concat(keyParts); - final byte[] value = concat(valueParts); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - - txn.put(keyParts, valueParts); - - assertThat(txn.get(readOptions, key)).isEqualTo(value); - - txn.delete(keyParts); - - assertThat(txn.get(readOptions, key)).isNull(); - } - } - - @Test - public void getPutUntracked_cf() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - assertThat(txn.get(testCf, readOptions, k1)).isNull(); - txn.putUntracked(testCf, k1, v1); - assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); - } - } - - @Test - public void getPutUntracked() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isNull(); - txn.putUntracked(k1, v1); - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - } - } - - @Deprecated - @Test - public void multiGetPutUntracked_cf() throws RocksDBException { - final byte keys[][] = new byte[][] { - "key1".getBytes(UTF_8), - "key2".getBytes(UTF_8)}; - final byte values[][] = new byte[][] { - "value1".getBytes(UTF_8), - "value2".getBytes(UTF_8)}; - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - - final List cfList = Arrays.asList(testCf, testCf); - - assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(new byte[][] { null, null }); - txn.putUntracked(testCf, keys[0], values[0]); - txn.putUntracked(testCf, keys[1], values[1]); - assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); - } - } - - @Test - public void multiGetPutUntrackedAsList_cf() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - - final List cfList = Arrays.asList(testCf, testCf); - - assertThat(txn.multiGetAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(null, null); - txn.putUntracked(testCf, keys[0], values[0]); - txn.putUntracked(testCf, keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(values); - } - } - - @Deprecated - @Test - public void multiGetPutUntracked() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - - assertThat(txn.multiGet(readOptions, keys)).isEqualTo(new byte[][] { null, null }); - txn.putUntracked(keys[0], values[0]); - txn.putUntracked(keys[1], values[1]); - assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); - } - } - - @Test - public void multiGetPutAsListUntracked() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.multiGetAsList(readOptions, Arrays.asList(keys))).containsExactly(null, null); - txn.putUntracked(keys[0], values[0]); - txn.putUntracked(keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, Arrays.asList(keys))).containsExactly(values); - } - } - - @Test - public void mergeUntracked_cf() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.mergeUntracked(testCf, k1, v1); - } - } - - @Test - public void mergeUntracked() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.mergeUntracked(k1, v1); - } - } - - @Test - public void deleteUntracked_cf() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.put(testCf, k1, v1); - assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); - - txn.deleteUntracked(testCf, k1); - assertThat(txn.get(testCf, readOptions, k1)).isNull(); - } - } - - @Test - public void deleteUntracked() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - - txn.deleteUntracked(k1); - assertThat(txn.get(readOptions, k1)).isNull(); - } - } - - @Test - public void deleteUntracked_parts_cf() throws RocksDBException { - final byte keyParts[][] = new byte[][] { - "ke".getBytes(UTF_8), - "y1".getBytes(UTF_8)}; - final byte valueParts[][] = new byte[][] { - "val".getBytes(UTF_8), - "ue1".getBytes(UTF_8)}; - final byte[] key = concat(keyParts); - final byte[] value = concat(valueParts); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.put(testCf, keyParts, valueParts); - assertThat(txn.get(testCf, readOptions, key)).isEqualTo(value); - - txn.deleteUntracked(testCf, keyParts); - assertThat(txn.get(testCf, readOptions, key)).isNull(); - } - } - - @Test - public void deleteUntracked_parts() throws RocksDBException { - final byte keyParts[][] = new byte[][] { - "ke".getBytes(UTF_8), - "y1".getBytes(UTF_8)}; - final byte valueParts[][] = new byte[][] { - "val".getBytes(UTF_8), - "ue1".getBytes(UTF_8)}; - final byte[] key = concat(keyParts); - final byte[] value = concat(valueParts); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.put(keyParts, valueParts); - assertThat(txn.get(readOptions, key)).isEqualTo(value); - - txn.deleteUntracked(keyParts); - assertThat(txn.get(readOptions, key)).isNull(); - } - } - - @Test - public void putLogData() throws RocksDBException { - final byte[] blob = "blobby".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.putLogData(blob); - } - } - - @Test - public void enabledDisableIndexing() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.disableIndexing(); - txn.enableIndexing(); - txn.disableIndexing(); - txn.enableIndexing(); - } - } - - @Test - public void numKeys() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - final byte k2[] = "key2".getBytes(UTF_8); - final byte v2[] = "value2".getBytes(UTF_8); - final byte k3[] = "key3".getBytes(UTF_8); - final byte v3[] = "value3".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - txn.put(k1, v1); - txn.put(testCf, k2, v2); - txn.merge(k3, v3); - txn.delete(testCf, k2); - - assertThat(txn.getNumKeys()).isEqualTo(3); - assertThat(txn.getNumPuts()).isEqualTo(2); - assertThat(txn.getNumMerges()).isEqualTo(1); - assertThat(txn.getNumDeletes()).isEqualTo(1); - } - } - - @Test - public void elapsedTime() throws RocksDBException, InterruptedException { - final long preStartTxnTime = System.currentTimeMillis(); - try (final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - Thread.sleep(2); - - final long txnElapsedTime = txn.getElapsedTime(); - assertThat(txnElapsedTime).isLessThan(System.currentTimeMillis() - preStartTxnTime); - assertThat(txnElapsedTime).isGreaterThan(0); - } - } - - @Test - public void getWriteBatch() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - - txn.put(k1, v1); - - final WriteBatchWithIndex writeBatch = txn.getWriteBatch(); - assertThat(writeBatch).isNotNull(); - assertThat(writeBatch.isOwningHandle()).isFalse(); - assertThat(writeBatch.count()).isEqualTo(1); - } - } - - @Test - public void setLockTimeout() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - txn.setLockTimeout(1000); - } - } - - @Test - public void writeOptions() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final WriteOptions writeOptions = new WriteOptions() - .setDisableWAL(true) - .setSync(true); - final Transaction txn = dbContainer.beginTransaction(writeOptions)) { - - txn.put(k1, v1); - - WriteOptions txnWriteOptions = txn.getWriteOptions(); - assertThat(txnWriteOptions).isNotNull(); - assertThat(txnWriteOptions.isOwningHandle()).isFalse(); - assertThat(txnWriteOptions).isNotSameAs(writeOptions); - assertThat(txnWriteOptions.disableWAL()).isTrue(); - assertThat(txnWriteOptions.sync()).isTrue(); - - txn.setWriteOptions(txnWriteOptions.setSync(false)); - txnWriteOptions = txn.getWriteOptions(); - assertThat(txnWriteOptions).isNotNull(); - assertThat(txnWriteOptions.isOwningHandle()).isFalse(); - assertThat(txnWriteOptions).isNotSameAs(writeOptions); - assertThat(txnWriteOptions.disableWAL()).isTrue(); - assertThat(txnWriteOptions.sync()).isFalse(); - } - } - - @Test - public void undoGetForUpdate_cf() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isNull(); - txn.put(testCf, k1, v1); - assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); - txn.undoGetForUpdate(testCf, k1); - } - } - - @Test - public void undoGetForUpdate() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getForUpdate(readOptions, k1, true)).isNull(); - txn.put(k1, v1); - assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1); - txn.undoGetForUpdate(k1); - } - } - - @Test - public void rebuildFromWriteBatch() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - final byte k2[] = "key2".getBytes(UTF_8); - final byte v2[] = "value2".getBytes(UTF_8); - final byte k3[] = "key3".getBytes(UTF_8); - final byte v3[] = "value3".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions(); - final Transaction txn = dbContainer.beginTransaction()) { - - txn.put(k1, v1); - - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - assertThat(txn.getNumKeys()).isEqualTo(1); - - try(final WriteBatch writeBatch = new WriteBatch()) { - writeBatch.put(k2, v2); - writeBatch.put(k3, v3); - txn.rebuildFromWriteBatch(writeBatch); - - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - assertThat(txn.get(readOptions, k2)).isEqualTo(v2); - assertThat(txn.get(readOptions, k3)).isEqualTo(v3); - assertThat(txn.getNumKeys()).isEqualTo(3); - } - } - } - - @Test - public void getCommitTimeWriteBatch() throws RocksDBException { - final byte k1[] = "key1".getBytes(UTF_8); - final byte v1[] = "value1".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - - txn.put(k1, v1); - final WriteBatch writeBatch = txn.getCommitTimeWriteBatch(); - - assertThat(writeBatch).isNotNull(); - assertThat(writeBatch.isOwningHandle()).isFalse(); - assertThat(writeBatch.count()).isEqualTo(0); - } - } - - @Test - public void logNumber() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getLogNumber()).isEqualTo(0); - final long logNumber = rand.nextLong(); - txn.setLogNumber(logNumber); - assertThat(txn.getLogNumber()).isEqualTo(logNumber); - } - } - - private static byte[] concat(final byte[][] bufs) { - int resultLength = 0; - for(final byte[] buf : bufs) { - resultLength += buf.length; - } - - final byte[] result = new byte[resultLength]; - int resultOffset = 0; - for(final byte[] buf : bufs) { - final int srcLength = buf.length; - System.arraycopy(buf, 0, result, resultOffset, srcLength); - resultOffset += srcLength; - } - - return result; - } - - private static class TestTransactionNotifier - extends AbstractTransactionNotifier { - private final List createdSnapshots = new ArrayList<>(); - - @Override - public void snapshotCreated(final Snapshot newSnapshot) { - createdSnapshots.add(newSnapshot); - } - - public List getCreatedSnapshots() { - return createdSnapshots; - } - } - - protected static abstract class DBContainer - implements AutoCloseable { - protected final WriteOptions writeOptions; - protected final List columnFamilyHandles; - protected final ColumnFamilyOptions columnFamilyOptions; - protected final DBOptions options; - - public DBContainer(final WriteOptions writeOptions, - final List columnFamilyHandles, - final ColumnFamilyOptions columnFamilyOptions, - final DBOptions options) { - this.writeOptions = writeOptions; - this.columnFamilyHandles = columnFamilyHandles; - this.columnFamilyOptions = columnFamilyOptions; - this.options = options; - } - - public abstract Transaction beginTransaction(); - - public abstract Transaction beginTransaction( - final WriteOptions writeOptions); - - public ColumnFamilyHandle getTestColumnFamily() { - return columnFamilyHandles.get(1); - } - - @Override - public abstract void close(); - } -} diff --git a/java/src/test/java/org/rocksdb/BackupEngineOptionsTest.java b/java/src/test/java/org/rocksdb/BackupEngineOptionsTest.java deleted file mode 100644 index 794bf04fb..000000000 --- a/java/src/test/java/org/rocksdb/BackupEngineOptionsTest.java +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Random; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -public class BackupEngineOptionsTest { - private final static String ARBITRARY_PATH = - System.getProperty("java.io.tmpdir"); - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public ExpectedException exception = ExpectedException.none(); - - public static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void backupDir() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - assertThat(backupEngineOptions.backupDir()).isEqualTo(ARBITRARY_PATH); - } - } - - @Test - public void env() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - assertThat(backupEngineOptions.backupEnv()).isNull(); - - try(final Env env = new RocksMemEnv(Env.getDefault())) { - backupEngineOptions.setBackupEnv(env); - assertThat(backupEngineOptions.backupEnv()).isEqualTo(env); - } - } - } - - @Test - public void shareTableFiles() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final boolean value = rand.nextBoolean(); - backupEngineOptions.setShareTableFiles(value); - assertThat(backupEngineOptions.shareTableFiles()).isEqualTo(value); - } - } - - @Test - public void infoLog() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - assertThat(backupEngineOptions.infoLog()).isNull(); - - try(final Options options = new Options(); - final Logger logger = new Logger(options){ - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - - } - }) { - backupEngineOptions.setInfoLog(logger); - assertThat(backupEngineOptions.infoLog()).isEqualTo(logger); - } - } - } - - @Test - public void sync() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final boolean value = rand.nextBoolean(); - backupEngineOptions.setSync(value); - assertThat(backupEngineOptions.sync()).isEqualTo(value); - } - } - - @Test - public void destroyOldData() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH);) { - final boolean value = rand.nextBoolean(); - backupEngineOptions.setDestroyOldData(value); - assertThat(backupEngineOptions.destroyOldData()).isEqualTo(value); - } - } - - @Test - public void backupLogFiles() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final boolean value = rand.nextBoolean(); - backupEngineOptions.setBackupLogFiles(value); - assertThat(backupEngineOptions.backupLogFiles()).isEqualTo(value); - } - } - - @Test - public void backupRateLimit() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final long value = Math.abs(rand.nextLong()); - backupEngineOptions.setBackupRateLimit(value); - assertThat(backupEngineOptions.backupRateLimit()).isEqualTo(value); - // negative will be mapped to 0 - backupEngineOptions.setBackupRateLimit(-1); - assertThat(backupEngineOptions.backupRateLimit()).isEqualTo(0); - } - } - - @Test - public void backupRateLimiter() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - assertThat(backupEngineOptions.backupEnv()).isNull(); - - try(final RateLimiter backupRateLimiter = - new RateLimiter(999)) { - backupEngineOptions.setBackupRateLimiter(backupRateLimiter); - assertThat(backupEngineOptions.backupRateLimiter()).isEqualTo(backupRateLimiter); - } - } - } - - @Test - public void restoreRateLimit() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final long value = Math.abs(rand.nextLong()); - backupEngineOptions.setRestoreRateLimit(value); - assertThat(backupEngineOptions.restoreRateLimit()).isEqualTo(value); - // negative will be mapped to 0 - backupEngineOptions.setRestoreRateLimit(-1); - assertThat(backupEngineOptions.restoreRateLimit()).isEqualTo(0); - } - } - - @Test - public void restoreRateLimiter() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - assertThat(backupEngineOptions.backupEnv()).isNull(); - - try(final RateLimiter restoreRateLimiter = - new RateLimiter(911)) { - backupEngineOptions.setRestoreRateLimiter(restoreRateLimiter); - assertThat(backupEngineOptions.restoreRateLimiter()).isEqualTo(restoreRateLimiter); - } - } - } - - @Test - public void shareFilesWithChecksum() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - boolean value = rand.nextBoolean(); - backupEngineOptions.setShareFilesWithChecksum(value); - assertThat(backupEngineOptions.shareFilesWithChecksum()).isEqualTo(value); - } - } - - @Test - public void maxBackgroundOperations() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final int value = rand.nextInt(); - backupEngineOptions.setMaxBackgroundOperations(value); - assertThat(backupEngineOptions.maxBackgroundOperations()).isEqualTo(value); - } - } - - @Test - public void callbackTriggerIntervalSize() { - try (final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH)) { - final long value = rand.nextLong(); - backupEngineOptions.setCallbackTriggerIntervalSize(value); - assertThat(backupEngineOptions.callbackTriggerIntervalSize()).isEqualTo(value); - } - } - - @Test - public void failBackupDirIsNull() { - exception.expect(IllegalArgumentException.class); - try (final BackupEngineOptions opts = new BackupEngineOptions(null)) { - //no-op - } - } - - @Test - public void failBackupDirIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.backupDir(); - } - } - - @Test - public void failSetShareTableFilesIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setShareTableFiles(true); - } - } - - @Test - public void failShareTableFilesIfDisposed() { - try (BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.shareTableFiles(); - } - } - - @Test - public void failSetSyncIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setSync(true); - } - } - - @Test - public void failSyncIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.sync(); - } - } - - @Test - public void failSetDestroyOldDataIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setDestroyOldData(true); - } - } - - @Test - public void failDestroyOldDataIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.destroyOldData(); - } - } - - @Test - public void failSetBackupLogFilesIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setBackupLogFiles(true); - } - } - - @Test - public void failBackupLogFilesIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.backupLogFiles(); - } - } - - @Test - public void failSetBackupRateLimitIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setBackupRateLimit(1); - } - } - - @Test - public void failBackupRateLimitIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.backupRateLimit(); - } - } - - @Test - public void failSetRestoreRateLimitIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setRestoreRateLimit(1); - } - } - - @Test - public void failRestoreRateLimitIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.restoreRateLimit(); - } - } - - @Test - public void failSetShareFilesWithChecksumIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.setShareFilesWithChecksum(true); - } - } - - @Test - public void failShareFilesWithChecksumIfDisposed() { - try (final BackupEngineOptions options = setupUninitializedBackupEngineOptions(exception)) { - options.shareFilesWithChecksum(); - } - } - - private BackupEngineOptions setupUninitializedBackupEngineOptions(ExpectedException exception) { - final BackupEngineOptions backupEngineOptions = new BackupEngineOptions(ARBITRARY_PATH); - backupEngineOptions.close(); - exception.expect(AssertionError.class); - return backupEngineOptions; - } -} diff --git a/java/src/test/java/org/rocksdb/BackupEngineTest.java b/java/src/test/java/org/rocksdb/BackupEngineTest.java deleted file mode 100644 index 67145f846..000000000 --- a/java/src/test/java/org/rocksdb/BackupEngineTest.java +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; - -import static org.assertj.core.api.Assertions.assertThat; - -public class BackupEngineTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Rule - public TemporaryFolder backupFolder = new TemporaryFolder(); - - @Test - public void backupDb() throws RocksDBException { - // Open empty database. - try(final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - // Fill database with some test values - prepareDatabase(db); - - // Create two backups - try (final BackupEngineOptions bopt = - new BackupEngineOptions(backupFolder.getRoot().getAbsolutePath()); - final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { - be.createNewBackup(db, false); - be.createNewBackup(db, true); - verifyNumberOfValidBackups(be, 2); - } - } - } - - @Test - public void deleteBackup() throws RocksDBException { - // Open empty database. - try(final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // Fill database with some test values - prepareDatabase(db); - // Create two backups - try (final BackupEngineOptions bopt = - new BackupEngineOptions(backupFolder.getRoot().getAbsolutePath()); - final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { - be.createNewBackup(db, false); - be.createNewBackup(db, true); - final List backupInfo = - verifyNumberOfValidBackups(be, 2); - // Delete the first backup - be.deleteBackup(backupInfo.get(0).backupId()); - final List newBackupInfo = - verifyNumberOfValidBackups(be, 1); - - // The second backup must remain. - assertThat(newBackupInfo.get(0).backupId()). - isEqualTo(backupInfo.get(1).backupId()); - } - } - } - - @Test - public void purgeOldBackups() throws RocksDBException { - // Open empty database. - try(final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // Fill database with some test values - prepareDatabase(db); - // Create four backups - try (final BackupEngineOptions bopt = - new BackupEngineOptions(backupFolder.getRoot().getAbsolutePath()); - final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { - be.createNewBackup(db, false); - be.createNewBackup(db, true); - be.createNewBackup(db, true); - be.createNewBackup(db, true); - final List backupInfo = - verifyNumberOfValidBackups(be, 4); - // Delete everything except the latest backup - be.purgeOldBackups(1); - final List newBackupInfo = - verifyNumberOfValidBackups(be, 1); - // The latest backup must remain. - assertThat(newBackupInfo.get(0).backupId()). - isEqualTo(backupInfo.get(3).backupId()); - } - } - } - - @Test - public void restoreLatestBackup() throws RocksDBException { - try(final Options opt = new Options().setCreateIfMissing(true)) { - // Open empty database. - RocksDB db = null; - try { - db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath()); - // Fill database with some test values - prepareDatabase(db); - - try (final BackupEngineOptions bopt = - new BackupEngineOptions(backupFolder.getRoot().getAbsolutePath()); - final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { - be.createNewBackup(db, true); - verifyNumberOfValidBackups(be, 1); - db.put("key1".getBytes(), "valueV2".getBytes()); - db.put("key2".getBytes(), "valueV2".getBytes()); - be.createNewBackup(db, true); - verifyNumberOfValidBackups(be, 2); - db.put("key1".getBytes(), "valueV3".getBytes()); - db.put("key2".getBytes(), "valueV3".getBytes()); - assertThat(new String(db.get("key1".getBytes()))).endsWith("V3"); - assertThat(new String(db.get("key2".getBytes()))).endsWith("V3"); - - db.close(); - db = null; - - verifyNumberOfValidBackups(be, 2); - // restore db from latest backup - try(final RestoreOptions ropts = new RestoreOptions(false)) { - be.restoreDbFromLatestBackup(dbFolder.getRoot().getAbsolutePath(), - dbFolder.getRoot().getAbsolutePath(), ropts); - } - - // Open database again. - db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath()); - - // Values must have suffix V2 because of restoring latest backup. - assertThat(new String(db.get("key1".getBytes()))).endsWith("V2"); - assertThat(new String(db.get("key2".getBytes()))).endsWith("V2"); - } - } finally { - if(db != null) { - db.close(); - } - } - } - } - - @Test - public void restoreFromBackup() - throws RocksDBException { - try(final Options opt = new Options().setCreateIfMissing(true)) { - RocksDB db = null; - try { - // Open empty database. - db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath()); - // Fill database with some test values - prepareDatabase(db); - try (final BackupEngineOptions bopt = - new BackupEngineOptions(backupFolder.getRoot().getAbsolutePath()); - final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { - be.createNewBackup(db, true); - verifyNumberOfValidBackups(be, 1); - db.put("key1".getBytes(), "valueV2".getBytes()); - db.put("key2".getBytes(), "valueV2".getBytes()); - be.createNewBackup(db, true); - verifyNumberOfValidBackups(be, 2); - db.put("key1".getBytes(), "valueV3".getBytes()); - db.put("key2".getBytes(), "valueV3".getBytes()); - assertThat(new String(db.get("key1".getBytes()))).endsWith("V3"); - assertThat(new String(db.get("key2".getBytes()))).endsWith("V3"); - - //close the database - db.close(); - db = null; - - //restore the backup - final List backupInfo = verifyNumberOfValidBackups(be, 2); - // restore db from first backup - be.restoreDbFromBackup(backupInfo.get(0).backupId(), - dbFolder.getRoot().getAbsolutePath(), - dbFolder.getRoot().getAbsolutePath(), - new RestoreOptions(false)); - // Open database again. - db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath()); - // Values must have suffix V2 because of restoring latest backup. - assertThat(new String(db.get("key1".getBytes()))).endsWith("V1"); - assertThat(new String(db.get("key2".getBytes()))).endsWith("V1"); - } - } finally { - if(db != null) { - db.close(); - } - } - } - } - - @Test - public void backupDbWithMetadata() throws RocksDBException { - // Open empty database. - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - // Fill database with some test values - prepareDatabase(db); - - // Create two backups - try (final BackupEngineOptions bopt = - new BackupEngineOptions(backupFolder.getRoot().getAbsolutePath()); - final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { - final String metadata = String.valueOf(ThreadLocalRandom.current().nextInt()); - be.createNewBackupWithMetadata(db, metadata, true); - final List backupInfoList = verifyNumberOfValidBackups(be, 1); - assertThat(backupInfoList.get(0).appMetadata()).isEqualTo(metadata); - } - } - } - - /** - * Verify backups. - * - * @param be {@link BackupEngine} instance. - * @param expectedNumberOfBackups numerical value - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - private List verifyNumberOfValidBackups(final BackupEngine be, - final int expectedNumberOfBackups) throws RocksDBException { - // Verify that backups exist - assertThat(be.getCorruptedBackups().length). - isEqualTo(0); - be.garbageCollect(); - final List backupInfo = be.getBackupInfo(); - assertThat(backupInfo.size()). - isEqualTo(expectedNumberOfBackups); - return backupInfo; - } - - /** - * Fill database with some test values. - * - * @param db {@link RocksDB} instance. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. - */ - private void prepareDatabase(final RocksDB db) - throws RocksDBException { - db.put("key1".getBytes(), "valueV1".getBytes()); - db.put("key2".getBytes(), "valueV1".getBytes()); - } -} diff --git a/java/src/test/java/org/rocksdb/BlobOptionsTest.java b/java/src/test/java/org/rocksdb/BlobOptionsTest.java deleted file mode 100644 index fe3d9b246..000000000 --- a/java/src/test/java/org/rocksdb/BlobOptionsTest.java +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.File; -import java.io.FilenameFilter; -import java.util.*; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class BlobOptionsTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - final int minBlobSize = 65536; - final int largeBlobSize = 65536 * 2; - - /** - * Count the files in the temporary folder which end with a particular suffix - * Used to query the state of a test database to check if it is as the test expects - * - * @param endsWith the suffix to match - * @return the number of files with a matching suffix - */ - @SuppressWarnings("CallToStringConcatCanBeReplacedByOperator") - private int countDBFiles(final String endsWith) { - return Objects - .requireNonNull(dbFolder.getRoot().list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(endsWith); - } - })) - .length; - } - - @SuppressWarnings("SameParameterValue") - private byte[] small_key(String suffix) { - return ("small_key_" + suffix).getBytes(UTF_8); - } - - @SuppressWarnings("SameParameterValue") - private byte[] small_value(String suffix) { - return ("small_value_" + suffix).getBytes(UTF_8); - } - - private byte[] large_key(String suffix) { - return ("large_key_" + suffix).getBytes(UTF_8); - } - - private byte[] large_value(String repeat) { - final byte[] large_value = ("" + repeat + "_" + largeBlobSize + "b").getBytes(UTF_8); - final byte[] large_buffer = new byte[largeBlobSize]; - for (int pos = 0; pos < largeBlobSize; pos += large_value.length) { - int numBytes = Math.min(large_value.length, large_buffer.length - pos); - System.arraycopy(large_value, 0, large_buffer, pos, numBytes); - } - return large_buffer; - } - - @Test - public void blobOptions() { - try (final Options options = new Options()) { - assertThat(options.enableBlobFiles()).isEqualTo(false); - assertThat(options.minBlobSize()).isEqualTo(0); - assertThat(options.blobCompressionType()).isEqualTo(CompressionType.NO_COMPRESSION); - assertThat(options.enableBlobGarbageCollection()).isEqualTo(false); - assertThat(options.blobFileSize()).isEqualTo(268435456L); - assertThat(options.blobGarbageCollectionAgeCutoff()).isEqualTo(0.25); - assertThat(options.blobGarbageCollectionForceThreshold()).isEqualTo(1.0); - assertThat(options.blobCompactionReadaheadSize()).isEqualTo(0); - assertThat(options.prepopulateBlobCache()) - .isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE); - - assertThat(options.setEnableBlobFiles(true)).isEqualTo(options); - assertThat(options.setMinBlobSize(132768L)).isEqualTo(options); - assertThat(options.setBlobCompressionType(CompressionType.BZLIB2_COMPRESSION)) - .isEqualTo(options); - assertThat(options.setEnableBlobGarbageCollection(true)).isEqualTo(options); - assertThat(options.setBlobFileSize(132768L)).isEqualTo(options); - assertThat(options.setBlobGarbageCollectionAgeCutoff(0.89)).isEqualTo(options); - assertThat(options.setBlobGarbageCollectionForceThreshold(0.80)).isEqualTo(options); - assertThat(options.setBlobCompactionReadaheadSize(262144L)).isEqualTo(options); - assertThat(options.setBlobFileStartingLevel(0)).isEqualTo(options); - assertThat(options.setPrepopulateBlobCache(PrepopulateBlobCache.PREPOPULATE_BLOB_FLUSH_ONLY)) - .isEqualTo(options); - - assertThat(options.enableBlobFiles()).isEqualTo(true); - assertThat(options.minBlobSize()).isEqualTo(132768L); - assertThat(options.blobCompressionType()).isEqualTo(CompressionType.BZLIB2_COMPRESSION); - assertThat(options.enableBlobGarbageCollection()).isEqualTo(true); - assertThat(options.blobFileSize()).isEqualTo(132768L); - assertThat(options.blobGarbageCollectionAgeCutoff()).isEqualTo(0.89); - assertThat(options.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(options.blobCompactionReadaheadSize()).isEqualTo(262144L); - assertThat(options.blobFileStartingLevel()).isEqualTo(0); - assertThat(options.prepopulateBlobCache()) - .isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_FLUSH_ONLY); - } - } - - @Test - public void blobColumnFamilyOptions() { - try (final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions()) { - assertThat(columnFamilyOptions.enableBlobFiles()).isEqualTo(false); - assertThat(columnFamilyOptions.minBlobSize()).isEqualTo(0); - assertThat(columnFamilyOptions.blobCompressionType()) - .isEqualTo(CompressionType.NO_COMPRESSION); - assertThat(columnFamilyOptions.enableBlobGarbageCollection()).isEqualTo(false); - assertThat(columnFamilyOptions.blobFileSize()).isEqualTo(268435456L); - assertThat(columnFamilyOptions.blobGarbageCollectionAgeCutoff()).isEqualTo(0.25); - assertThat(columnFamilyOptions.blobGarbageCollectionForceThreshold()).isEqualTo(1.0); - assertThat(columnFamilyOptions.blobCompactionReadaheadSize()).isEqualTo(0); - - assertThat(columnFamilyOptions.setEnableBlobFiles(true)).isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setMinBlobSize(132768L)).isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setBlobCompressionType(CompressionType.BZLIB2_COMPRESSION)) - .isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setEnableBlobGarbageCollection(true)) - .isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setBlobFileSize(132768L)).isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setBlobGarbageCollectionAgeCutoff(0.89)) - .isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setBlobGarbageCollectionForceThreshold(0.80)) - .isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setBlobCompactionReadaheadSize(262144L)) - .isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setBlobFileStartingLevel(0)).isEqualTo(columnFamilyOptions); - assertThat(columnFamilyOptions.setPrepopulateBlobCache( - PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE)) - .isEqualTo(columnFamilyOptions); - - assertThat(columnFamilyOptions.enableBlobFiles()).isEqualTo(true); - assertThat(columnFamilyOptions.minBlobSize()).isEqualTo(132768L); - assertThat(columnFamilyOptions.blobCompressionType()) - .isEqualTo(CompressionType.BZLIB2_COMPRESSION); - assertThat(columnFamilyOptions.enableBlobGarbageCollection()).isEqualTo(true); - assertThat(columnFamilyOptions.blobFileSize()).isEqualTo(132768L); - assertThat(columnFamilyOptions.blobGarbageCollectionAgeCutoff()).isEqualTo(0.89); - assertThat(columnFamilyOptions.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(columnFamilyOptions.blobCompactionReadaheadSize()).isEqualTo(262144L); - assertThat(columnFamilyOptions.blobFileStartingLevel()).isEqualTo(0); - assertThat(columnFamilyOptions.prepopulateBlobCache()) - .isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE); - } - } - - @Test - public void blobMutableColumnFamilyOptionsBuilder() { - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder = - MutableColumnFamilyOptions.builder(); - builder.setEnableBlobFiles(true) - .setMinBlobSize(1024) - .setBlobFileSize(132768) - .setBlobCompressionType(CompressionType.BZLIB2_COMPRESSION) - .setEnableBlobGarbageCollection(true) - .setBlobGarbageCollectionAgeCutoff(0.89) - .setBlobGarbageCollectionForceThreshold(0.80) - .setBlobCompactionReadaheadSize(262144) - .setBlobFileStartingLevel(1) - .setPrepopulateBlobCache(PrepopulateBlobCache.PREPOPULATE_BLOB_FLUSH_ONLY); - - assertThat(builder.enableBlobFiles()).isEqualTo(true); - assertThat(builder.minBlobSize()).isEqualTo(1024); - assertThat(builder.blobFileSize()).isEqualTo(132768); - assertThat(builder.blobCompressionType()).isEqualTo(CompressionType.BZLIB2_COMPRESSION); - assertThat(builder.enableBlobGarbageCollection()).isEqualTo(true); - assertThat(builder.blobGarbageCollectionAgeCutoff()).isEqualTo(0.89); - assertThat(builder.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(builder.blobCompactionReadaheadSize()).isEqualTo(262144); - assertThat(builder.blobFileStartingLevel()).isEqualTo(1); - assertThat(builder.prepopulateBlobCache()) - .isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_FLUSH_ONLY); - - builder.setEnableBlobFiles(false) - .setMinBlobSize(4096) - .setBlobFileSize(2048) - .setBlobCompressionType(CompressionType.LZ4_COMPRESSION) - .setEnableBlobGarbageCollection(false) - .setBlobGarbageCollectionAgeCutoff(0.91) - .setBlobGarbageCollectionForceThreshold(0.96) - .setBlobCompactionReadaheadSize(1024) - .setBlobFileStartingLevel(0) - .setPrepopulateBlobCache(PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE); - - assertThat(builder.enableBlobFiles()).isEqualTo(false); - assertThat(builder.minBlobSize()).isEqualTo(4096); - assertThat(builder.blobFileSize()).isEqualTo(2048); - assertThat(builder.blobCompressionType()).isEqualTo(CompressionType.LZ4_COMPRESSION); - assertThat(builder.enableBlobGarbageCollection()).isEqualTo(false); - assertThat(builder.blobGarbageCollectionAgeCutoff()).isEqualTo(0.91); - assertThat(builder.blobGarbageCollectionForceThreshold()).isEqualTo(0.96); - assertThat(builder.blobCompactionReadaheadSize()).isEqualTo(1024); - assertThat(builder.blobFileStartingLevel()).isEqualTo(0); - assertThat(builder.prepopulateBlobCache()) - .isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE); - - final MutableColumnFamilyOptions options = builder.build(); - assertThat(options.getKeys()) - .isEqualTo(new String[] {"enable_blob_files", "min_blob_size", "blob_file_size", - "blob_compression_type", "enable_blob_garbage_collection", - "blob_garbage_collection_age_cutoff", "blob_garbage_collection_force_threshold", - "blob_compaction_readahead_size", "blob_file_starting_level", - "prepopulate_blob_cache"}); - assertThat(options.getValues()) - .isEqualTo(new String[] {"false", "4096", "2048", "LZ4_COMPRESSION", "false", "0.91", - "0.96", "1024", "0", "PREPOPULATE_BLOB_DISABLE"}); - } - - /** - * Configure the default column family with BLOBs. - * Confirm that BLOBs are generated when appropriately-sized writes are flushed. - * - * @throws RocksDBException if a db access throws an exception - */ - @Test - public void testBlobWriteAboveThreshold() throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setMinBlobSize(minBlobSize) - .setEnableBlobFiles(true); - - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - db.put(small_key("default"), small_value("default")); - db.flush(new FlushOptions().setWaitForFlush(true)); - - // check there are no blobs in the database - assertThat(countDBFiles(".sst")).isEqualTo(1); - assertThat(countDBFiles(".blob")).isEqualTo(0); - - db.put(large_key("default"), large_value("default")); - db.flush(new FlushOptions().setWaitForFlush(true)); - - // wrote and flushed a value larger than the blobbing threshold - // check there is a single blob in the database - assertThat(countDBFiles(".sst")).isEqualTo(2); - assertThat(countDBFiles(".blob")).isEqualTo(1); - - assertThat(db.get(small_key("default"))).isEqualTo(small_value("default")); - assertThat(db.get(large_key("default"))).isEqualTo(large_value("default")); - - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder fetchOptions = - db.getOptions(null); - assertThat(fetchOptions.minBlobSize()).isEqualTo(minBlobSize); - assertThat(fetchOptions.enableBlobFiles()).isEqualTo(true); - assertThat(fetchOptions.writeBufferSize()).isEqualTo(64 << 20); - } - } - - /** - * Configure 2 column families respectively with and without BLOBs. - * Confirm that BLOB files are generated (once the DB is flushed) only for the appropriate column - * family. - * - * @throws RocksDBException if a db access throws an exception - */ - @Test - public void testBlobWriteAboveThresholdCF() throws RocksDBException { - final ColumnFamilyOptions columnFamilyOptions0 = new ColumnFamilyOptions(); - final ColumnFamilyDescriptor columnFamilyDescriptor0 = - new ColumnFamilyDescriptor("default".getBytes(UTF_8), columnFamilyOptions0); - List columnFamilyDescriptors = - Collections.singletonList(columnFamilyDescriptor0); - List columnFamilyHandles = new ArrayList<>(); - - try (final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(dbOptions, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - db.put(columnFamilyHandles.get(0), small_key("default"), small_value("default")); - db.flush(new FlushOptions().setWaitForFlush(true)); - - assertThat(countDBFiles(".blob")).isEqualTo(0); - - try (final ColumnFamilyOptions columnFamilyOptions1 = - new ColumnFamilyOptions().setMinBlobSize(minBlobSize).setEnableBlobFiles(true); - - final ColumnFamilyOptions columnFamilyOptions2 = - new ColumnFamilyOptions().setMinBlobSize(minBlobSize).setEnableBlobFiles(false)) { - final ColumnFamilyDescriptor columnFamilyDescriptor1 = - new ColumnFamilyDescriptor("column_family_1".getBytes(UTF_8), columnFamilyOptions1); - final ColumnFamilyDescriptor columnFamilyDescriptor2 = - new ColumnFamilyDescriptor("column_family_2".getBytes(UTF_8), columnFamilyOptions2); - - // Create the first column family with blob options - db.createColumnFamily(columnFamilyDescriptor1); - - // Create the second column family with not-blob options - db.createColumnFamily(columnFamilyDescriptor2); - } - } - - // Now re-open after auto-close - at this point the CF options we use are recognized. - try (final ColumnFamilyOptions columnFamilyOptions1 = - new ColumnFamilyOptions().setMinBlobSize(minBlobSize).setEnableBlobFiles(true); - - final ColumnFamilyOptions columnFamilyOptions2 = - new ColumnFamilyOptions().setMinBlobSize(minBlobSize).setEnableBlobFiles(false)) { - assertThat(columnFamilyOptions1.enableBlobFiles()).isEqualTo(true); - assertThat(columnFamilyOptions1.minBlobSize()).isEqualTo(minBlobSize); - assertThat(columnFamilyOptions2.enableBlobFiles()).isEqualTo(false); - assertThat(columnFamilyOptions1.minBlobSize()).isEqualTo(minBlobSize); - - final ColumnFamilyDescriptor columnFamilyDescriptor1 = - new ColumnFamilyDescriptor("column_family_1".getBytes(UTF_8), columnFamilyOptions1); - final ColumnFamilyDescriptor columnFamilyDescriptor2 = - new ColumnFamilyDescriptor("column_family_2".getBytes(UTF_8), columnFamilyOptions2); - columnFamilyDescriptors = new ArrayList<>(); - columnFamilyDescriptors.add(columnFamilyDescriptor0); - columnFamilyDescriptors.add(columnFamilyDescriptor1); - columnFamilyDescriptors.add(columnFamilyDescriptor2); - columnFamilyHandles = new ArrayList<>(); - - assertThat(columnFamilyDescriptor1.getOptions().enableBlobFiles()).isEqualTo(true); - assertThat(columnFamilyDescriptor2.getOptions().enableBlobFiles()).isEqualTo(false); - - try (final DBOptions dbOptions = new DBOptions(); - final RocksDB db = RocksDB.open(dbOptions, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder1 = - db.getOptions(columnFamilyHandles.get(1)); - assertThat(builder1.enableBlobFiles()).isEqualTo(true); - assertThat(builder1.minBlobSize()).isEqualTo(minBlobSize); - - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder2 = - db.getOptions(columnFamilyHandles.get(2)); - assertThat(builder2.enableBlobFiles()).isEqualTo(false); - assertThat(builder2.minBlobSize()).isEqualTo(minBlobSize); - - db.put(columnFamilyHandles.get(1), large_key("column_family_1_k2"), - large_value("column_family_1_k2")); - db.flush(new FlushOptions().setWaitForFlush(true), columnFamilyHandles.get(1)); - assertThat(countDBFiles(".blob")).isEqualTo(1); - - db.put(columnFamilyHandles.get(2), large_key("column_family_2_k2"), - large_value("column_family_2_k2")); - db.flush(new FlushOptions().setWaitForFlush(true), columnFamilyHandles.get(2)); - assertThat(countDBFiles(".blob")).isEqualTo(1); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java b/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java deleted file mode 100644 index 005c8bc6d..000000000 --- a/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Stream; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class BlockBasedTableConfigTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void cacheIndexAndFilterBlocks() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setCacheIndexAndFilterBlocks(true); - assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocks()). - isTrue(); - } - - @Test - public void cacheIndexAndFilterBlocksWithHighPriority() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocksWithHighPriority()). - isTrue(); - blockBasedTableConfig.setCacheIndexAndFilterBlocksWithHighPriority(false); - assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocksWithHighPriority()).isFalse(); - } - - @Test - public void pinL0FilterAndIndexBlocksInCache() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setPinL0FilterAndIndexBlocksInCache(true); - assertThat(blockBasedTableConfig.pinL0FilterAndIndexBlocksInCache()). - isTrue(); - } - - @Test - public void pinTopLevelIndexAndFilter() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setPinTopLevelIndexAndFilter(false); - assertThat(blockBasedTableConfig.pinTopLevelIndexAndFilter()). - isFalse(); - } - - @Test - public void indexType() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - assertThat(IndexType.values().length).isEqualTo(4); - blockBasedTableConfig.setIndexType(IndexType.kHashSearch); - assertThat(blockBasedTableConfig.indexType()).isEqualTo(IndexType.kHashSearch); - assertThat(IndexType.valueOf("kBinarySearch")).isNotNull(); - blockBasedTableConfig.setIndexType(IndexType.valueOf("kBinarySearch")); - assertThat(blockBasedTableConfig.indexType()).isEqualTo(IndexType.kBinarySearch); - } - - @Test - public void dataBlockIndexType() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash); - assertThat(blockBasedTableConfig.dataBlockIndexType()) - .isEqualTo(DataBlockIndexType.kDataBlockBinaryAndHash); - blockBasedTableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch); - assertThat(blockBasedTableConfig.dataBlockIndexType()) - .isEqualTo(DataBlockIndexType.kDataBlockBinarySearch); - } - - @Test - public void checksumType() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - assertThat(ChecksumType.values().length).isEqualTo(5); - assertThat(ChecksumType.valueOf("kxxHash")). - isEqualTo(ChecksumType.kxxHash); - blockBasedTableConfig.setChecksumType(ChecksumType.kNoChecksum); - assertThat(blockBasedTableConfig.checksumType()).isEqualTo(ChecksumType.kNoChecksum); - blockBasedTableConfig.setChecksumType(ChecksumType.kxxHash); - assertThat(blockBasedTableConfig.checksumType()).isEqualTo(ChecksumType.kxxHash); - blockBasedTableConfig.setChecksumType(ChecksumType.kxxHash64); - assertThat(blockBasedTableConfig.checksumType()).isEqualTo(ChecksumType.kxxHash64); - blockBasedTableConfig.setChecksumType(ChecksumType.kXXH3); - assertThat(blockBasedTableConfig.checksumType()).isEqualTo(ChecksumType.kXXH3); - } - - @Test - public void jniPortal() throws Exception { - // Verifies that the JNI layer is correctly translating options. - // Since introspecting the options requires creating a database, the checks - // cover multiple options at the same time. - - final BlockBasedTableConfig tableConfig = new BlockBasedTableConfig(); - - tableConfig.setIndexType(IndexType.kBinarySearch); - tableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch); - tableConfig.setChecksumType(ChecksumType.kNoChecksum); - try (final Options options = new Options().setTableFormatConfig(tableConfig)) { - String opts = getOptionAsString(options); - assertThat(opts).contains("index_type=kBinarySearch"); - assertThat(opts).contains("data_block_index_type=kDataBlockBinarySearch"); - assertThat(opts).contains("checksum=kNoChecksum"); - } - - tableConfig.setIndexType(IndexType.kHashSearch); - tableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash); - tableConfig.setChecksumType(ChecksumType.kCRC32c); - try (final Options options = new Options().setTableFormatConfig(tableConfig)) { - options.useCappedPrefixExtractor(1); // Needed to use kHashSearch - String opts = getOptionAsString(options); - assertThat(opts).contains("index_type=kHashSearch"); - assertThat(opts).contains("data_block_index_type=kDataBlockBinaryAndHash"); - assertThat(opts).contains("checksum=kCRC32c"); - } - - tableConfig.setIndexType(IndexType.kTwoLevelIndexSearch); - tableConfig.setChecksumType(ChecksumType.kxxHash); - try (final Options options = new Options().setTableFormatConfig(tableConfig)) { - String opts = getOptionAsString(options); - assertThat(opts).contains("index_type=kTwoLevelIndexSearch"); - assertThat(opts).contains("checksum=kxxHash"); - } - - tableConfig.setIndexType(IndexType.kBinarySearchWithFirstKey); - tableConfig.setChecksumType(ChecksumType.kxxHash64); - try (final Options options = new Options().setTableFormatConfig(tableConfig)) { - String opts = getOptionAsString(options); - assertThat(opts).contains("index_type=kBinarySearchWithFirstKey"); - assertThat(opts).contains("checksum=kxxHash64"); - } - - tableConfig.setChecksumType(ChecksumType.kXXH3); - try (final Options options = new Options().setTableFormatConfig(tableConfig)) { - String opts = getOptionAsString(options); - assertThat(opts).contains("checksum=kXXH3"); - } - } - - private String getOptionAsString(Options options) throws Exception { - options.setCreateIfMissing(true); - String dbPath = dbFolder.getRoot().getAbsolutePath(); - String result; - try (final RocksDB db = RocksDB.open(options, dbPath); - final Stream pathStream = Files.walk(Paths.get(dbPath))) { - Path optionsPath = - pathStream - .filter(p -> p.getFileName().toString().startsWith("OPTIONS")) - .findAny() - .orElseThrow(() -> new AssertionError("Missing options file")); - byte[] optionsData = Files.readAllBytes(optionsPath); - result = new String(optionsData, StandardCharsets.UTF_8); - } - RocksDB.destroyDB(dbPath, options); - return result; - } - - @Test - public void noBlockCache() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setNoBlockCache(true); - assertThat(blockBasedTableConfig.noBlockCache()).isTrue(); - } - - @Test - public void blockCache() { - try ( - final Cache cache = new LRUCache(17 * 1024 * 1024); - final Options options = new Options().setTableFormatConfig( - new BlockBasedTableConfig().setBlockCache(cache))) { - assertThat(options.tableFactoryName()).isEqualTo("BlockBasedTable"); - } - } - - @Test - public void blockCacheIntegration() throws RocksDBException { - try (final Cache cache = new LRUCache(8 * 1024 * 1024); - final Statistics statistics = new Statistics()) { - for (int shard = 0; shard < 8; shard++) { - try (final Options options = - new Options() - .setCreateIfMissing(true) - .setStatistics(statistics) - .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache)); - final RocksDB db = - RocksDB.open(options, dbFolder.getRoot().getAbsolutePath() + "/" + shard)) { - final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - db.put(key, value); - db.flush(new FlushOptions()); - db.get(key); - - assertThat(statistics.getTickerCount(TickerType.BLOCK_CACHE_ADD)).isEqualTo(shard + 1); - } - } - } - } - - @Test - public void persistentCache() throws RocksDBException { - try (final DBOptions dbOptions = new DBOptions(). - setInfoLogLevel(InfoLogLevel.INFO_LEVEL). - setCreateIfMissing(true); - final Logger logger = new Logger(dbOptions) { - @Override - protected void log(final InfoLogLevel infoLogLevel, final String logMsg) { - System.out.println(infoLogLevel.name() + ": " + logMsg); - } - }) { - try (final PersistentCache persistentCache = - new PersistentCache(Env.getDefault(), dbFolder.getRoot().getPath(), 1024 * 1024 * 100, logger, false); - final Options options = new Options().setTableFormatConfig( - new BlockBasedTableConfig().setPersistentCache(persistentCache))) { - assertThat(options.tableFactoryName()).isEqualTo("BlockBasedTable"); - } - } - } - - @Test - public void blockSize() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockSize(10); - assertThat(blockBasedTableConfig.blockSize()).isEqualTo(10); - } - - @Test - public void blockSizeDeviation() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockSizeDeviation(12); - assertThat(blockBasedTableConfig.blockSizeDeviation()). - isEqualTo(12); - } - - @Test - public void blockRestartInterval() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockRestartInterval(15); - assertThat(blockBasedTableConfig.blockRestartInterval()). - isEqualTo(15); - } - - @Test - public void indexBlockRestartInterval() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setIndexBlockRestartInterval(15); - assertThat(blockBasedTableConfig.indexBlockRestartInterval()). - isEqualTo(15); - } - - @Test - public void metadataBlockSize() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setMetadataBlockSize(1024); - assertThat(blockBasedTableConfig.metadataBlockSize()). - isEqualTo(1024); - } - - @Test - public void partitionFilters() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setPartitionFilters(true); - assertThat(blockBasedTableConfig.partitionFilters()). - isTrue(); - } - - @Test - public void optimizeFiltersForMemory() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setOptimizeFiltersForMemory(true); - assertThat(blockBasedTableConfig.optimizeFiltersForMemory()).isTrue(); - } - - @Test - public void useDeltaEncoding() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setUseDeltaEncoding(false); - assertThat(blockBasedTableConfig.useDeltaEncoding()). - isFalse(); - } - - @Test - public void blockBasedTableWithFilterPolicy() { - try(final Options options = new Options() - .setTableFormatConfig(new BlockBasedTableConfig() - .setFilterPolicy(new BloomFilter(10)))) { - assertThat(options.tableFactoryName()). - isEqualTo("BlockBasedTable"); - } - } - - @Test - public void blockBasedTableWithoutFilterPolicy() { - try(final Options options = new Options().setTableFormatConfig( - new BlockBasedTableConfig().setFilterPolicy(null))) { - assertThat(options.tableFactoryName()). - isEqualTo("BlockBasedTable"); - } - } - - @Test - public void wholeKeyFiltering() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setWholeKeyFiltering(false); - assertThat(blockBasedTableConfig.wholeKeyFiltering()). - isFalse(); - } - - @Test - public void verifyCompression() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - assertThat(blockBasedTableConfig.verifyCompression()).isFalse(); - blockBasedTableConfig.setVerifyCompression(true); - assertThat(blockBasedTableConfig.verifyCompression()). - isTrue(); - } - - @Test - public void readAmpBytesPerBit() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setReadAmpBytesPerBit(2); - assertThat(blockBasedTableConfig.readAmpBytesPerBit()). - isEqualTo(2); - } - - @Test - public void formatVersion() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - for (int version = 0; version <= 5; version++) { - blockBasedTableConfig.setFormatVersion(version); - assertThat(blockBasedTableConfig.formatVersion()).isEqualTo(version); - } - } - - @Test(expected = AssertionError.class) - public void formatVersionFailNegative() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setFormatVersion(-1); - } - - @Test(expected = RocksDBException.class) - public void invalidFormatVersion() throws RocksDBException { - final BlockBasedTableConfig blockBasedTableConfig = - new BlockBasedTableConfig().setFormatVersion(99999); - - try (final Options options = new Options().setTableFormatConfig(blockBasedTableConfig); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - fail("Opening the database with an invalid format_version should have raised an exception"); - } - } - - @Test - public void enableIndexCompression() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setEnableIndexCompression(false); - assertThat(blockBasedTableConfig.enableIndexCompression()). - isFalse(); - } - - @Test - public void blockAlign() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockAlign(true); - assertThat(blockBasedTableConfig.blockAlign()). - isTrue(); - } - - @Test - public void indexShortening() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setIndexShortening(IndexShorteningMode.kShortenSeparatorsAndSuccessor); - assertThat(blockBasedTableConfig.indexShortening()) - .isEqualTo(IndexShorteningMode.kShortenSeparatorsAndSuccessor); - } - - @Deprecated - @Test - public void hashIndexAllowCollision() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setHashIndexAllowCollision(false); - assertThat(blockBasedTableConfig.hashIndexAllowCollision()). - isTrue(); // NOTE: setHashIndexAllowCollision should do nothing! - } - - @Deprecated - @Test - public void blockCacheSize() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockCacheSize(8 * 1024); - assertThat(blockBasedTableConfig.blockCacheSize()). - isEqualTo(8 * 1024); - } - - @Deprecated - @Test - public void blockCacheNumShardBits() { - final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setCacheNumShardBits(5); - assertThat(blockBasedTableConfig.cacheNumShardBits()). - isEqualTo(5); - } - -} diff --git a/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java b/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java deleted file mode 100644 index e238ae07b..000000000 --- a/java/src/test/java/org/rocksdb/BuiltinComparatorTest.java +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class BuiltinComparatorTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void builtinForwardComparator() - throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(BuiltinComparator.BYTEWISE_COMPARATOR); - final RocksDB rocksDb = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()) - ) { - rocksDb.put("abc1".getBytes(), "abc1".getBytes()); - rocksDb.put("abc2".getBytes(), "abc2".getBytes()); - rocksDb.put("abc3".getBytes(), "abc3".getBytes()); - - try(final RocksIterator rocksIterator = rocksDb.newIterator()) { - // Iterate over keys using a iterator - rocksIterator.seekToFirst(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc1".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc1".getBytes()); - rocksIterator.next(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc2".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc2".getBytes()); - rocksIterator.next(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc3".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc3".getBytes()); - rocksIterator.next(); - assertThat(rocksIterator.isValid()).isFalse(); - // Get last one - rocksIterator.seekToLast(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc3".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc3".getBytes()); - // Seek for abc - rocksIterator.seek("abc".getBytes()); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc1".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc1".getBytes()); - } - } - } - - @Test - public void builtinReverseComparator() - throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR); - final RocksDB rocksDb = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()) - ) { - - rocksDb.put("abc1".getBytes(), "abc1".getBytes()); - rocksDb.put("abc2".getBytes(), "abc2".getBytes()); - rocksDb.put("abc3".getBytes(), "abc3".getBytes()); - - try (final RocksIterator rocksIterator = rocksDb.newIterator()) { - // Iterate over keys using a iterator - rocksIterator.seekToFirst(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc3".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc3".getBytes()); - rocksIterator.next(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc2".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc2".getBytes()); - rocksIterator.next(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc1".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc1".getBytes()); - rocksIterator.next(); - assertThat(rocksIterator.isValid()).isFalse(); - // Get last one - rocksIterator.seekToLast(); - assertThat(rocksIterator.isValid()).isTrue(); - assertThat(rocksIterator.key()).isEqualTo( - "abc1".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc1".getBytes()); - // Will be invalid because abc is after abc1 - rocksIterator.seek("abc".getBytes()); - assertThat(rocksIterator.isValid()).isFalse(); - // Will be abc3 because the next one after abc999 - // is abc3 - rocksIterator.seek("abc999".getBytes()); - assertThat(rocksIterator.key()).isEqualTo( - "abc3".getBytes()); - assertThat(rocksIterator.value()).isEqualTo( - "abc3".getBytes()); - } - } - } - - @Test - public void builtinComparatorEnum(){ - assertThat(BuiltinComparator.BYTEWISE_COMPARATOR.ordinal()) - .isEqualTo(0); - assertThat( - BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR.ordinal()) - .isEqualTo(1); - assertThat(BuiltinComparator.values().length).isEqualTo(2); - assertThat(BuiltinComparator.valueOf("BYTEWISE_COMPARATOR")). - isEqualTo(BuiltinComparator.BYTEWISE_COMPARATOR); - } -} diff --git a/java/src/test/java/org/rocksdb/ByteBufferUnsupportedOperationTest.java b/java/src/test/java/org/rocksdb/ByteBufferUnsupportedOperationTest.java deleted file mode 100644 index f596f573f..000000000 --- a/java/src/test/java/org/rocksdb/ByteBufferUnsupportedOperationTest.java +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.ReverseBytewiseComparator; - -public class ByteBufferUnsupportedOperationTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - public static class Handler { - private final RocksDB database; - private final Map columnFamilies; - - public Handler(final String path, final Options options) throws RocksDBException { - RocksDB.destroyDB(path, options); - this.database = RocksDB.open(options, path); - this.columnFamilies = new ConcurrentHashMap<>(); - } - - public void addTable(final UUID streamID) throws RocksDBException { - final ColumnFamilyOptions tableOptions = new ColumnFamilyOptions(); - tableOptions.optimizeUniversalStyleCompaction(); - try (final ComparatorOptions comparatorOptions = new ComparatorOptions()) { - // comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.ADAPTIVE_MUTEX); - tableOptions.setComparator(new ReverseBytewiseComparator(comparatorOptions)); - final ColumnFamilyDescriptor tableDescriptor = new ColumnFamilyDescriptor( - streamID.toString().getBytes(StandardCharsets.UTF_8), tableOptions); - final ColumnFamilyHandle tableHandle = database.createColumnFamily(tableDescriptor); - columnFamilies.put(streamID, tableHandle); - } - } - - public void updateAll(final List keyValuePairs, final UUID streamID) - throws RocksDBException { - final ColumnFamilyHandle currTable = columnFamilies.get(streamID); - try (final WriteBatch batchedWrite = new WriteBatch(); - final WriteOptions writeOptions = new WriteOptions()) { - for (final byte[][] pair : keyValuePairs) { - final byte[] keyBytes = pair[0]; - final byte[] valueBytes = pair[1]; - batchedWrite.put(currTable, keyBytes, valueBytes); - } - database.write(writeOptions, batchedWrite); - } - } - public boolean containsValue(final byte[] encodedValue, final UUID streamID) { - try (final RocksIterator iter = database.newIterator(columnFamilies.get(streamID))) { - iter.seekToFirst(); - while (iter.isValid()) { - final byte[] val = iter.value(); - if (Arrays.equals(val, encodedValue)) { - return true; - } - iter.next(); - } - } - return false; - } - - public void close() { - for (final ColumnFamilyHandle handle : columnFamilies.values()) { - handle.close(); - } - database.close(); - } - } - - private void inner(final int numRepeats) throws RocksDBException { - final Options opts = new Options(); - opts.setCreateIfMissing(true); - final Handler handler = new Handler("testDB", opts); - final UUID stream1 = UUID.randomUUID(); - - final List entries = new ArrayList<>(); - for (int i = 0; i < numRepeats; i++) { - final byte[] value = value(i); - final byte[] key = key(i); - entries.add(new byte[][] {key, value}); - } - handler.addTable(stream1); - handler.updateAll(entries, stream1); - - for (int i = 0; i < numRepeats; i++) { - final byte[] val = value(i); - final boolean hasValue = handler.containsValue(val, stream1); - if (!hasValue) { - throw new IllegalStateException("not has value " + i); - } - } - - handler.close(); - } - - private static byte[] key(final int i) { - return ("key" + i).getBytes(StandardCharsets.UTF_8); - } - - private static byte[] value(final int i) { - return ("value" + i).getBytes(StandardCharsets.UTF_8); - } - - @Test - public void unsupportedOperation() throws RocksDBException { - final int numRepeats = 1000; - final int repeatTest = 10; - - // the error is not always reproducible... let's try to increase the odds by repeating the main - // test body - for (int i = 0; i < repeatTest; i++) { - try { - inner(numRepeats); - } catch (final RuntimeException runtimeException) { - System.out.println("Exception on repeat " + i); - throw runtimeException; - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/BytewiseComparatorRegressionTest.java b/java/src/test/java/org/rocksdb/BytewiseComparatorRegressionTest.java deleted file mode 100644 index fe950362b..000000000 --- a/java/src/test/java/org/rocksdb/BytewiseComparatorRegressionTest.java +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.junit.Assert.assertArrayEquals; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.BytewiseComparator; - -/** - * This test confirms that the following issues were in fact resolved - * by a change made between 6.2.2 and 6.22.1, - * to wit {@link ...} - * which as part of its effect, changed the Java bytewise comparators. - * - * {@link ...} - * {@link ...} - */ -public class BytewiseComparatorRegressionTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Rule public TemporaryFolder temporarySSTFolder = new TemporaryFolder(); - - private final static byte[][] testData = {{10, -11, 13}, {10, 11, 12}, {10, 11, 14}}; - private final static byte[][] orderedData = {{10, 11, 12}, {10, 11, 14}, {10, -11, 13}}; - - /** - * {@link ...} - */ - @Test - public void testJavaComparator() throws RocksDBException { - final BytewiseComparator comparator = new BytewiseComparator(new ComparatorOptions()); - performTest(new Options().setCreateIfMissing(true).setComparator(comparator)); - } - - @Test - public void testDefaultComparator() throws RocksDBException { - performTest(new Options().setCreateIfMissing(true)); - } - - /** - * {@link ...} - */ - @Test - public void testCppComparator() throws RocksDBException { - performTest(new Options().setCreateIfMissing(true).setComparator( - BuiltinComparator.BYTEWISE_COMPARATOR)); - } - - private void performTest(final Options options) throws RocksDBException { - try (final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - for (final byte[] item : testData) { - db.put(item, item); - } - try (final RocksIterator iterator = db.newIterator()) { - iterator.seekToFirst(); - final ArrayList result = new ArrayList<>(); - while (iterator.isValid()) { - result.add(iterator.key()); - iterator.next(); - } - assertArrayEquals(orderedData, result.toArray()); - } - } - } - - private byte[] hexToByte(final String hexString) { - final byte[] bytes = new byte[hexString.length() / 2]; - if (bytes.length * 2 < hexString.length()) { - throw new RuntimeException("Hex string has odd length: " + hexString); - } - - for (int i = 0; i < bytes.length; i++) { - final int firstDigit = toDigit(hexString.charAt(i + i)); - final int secondDigit = toDigit(hexString.charAt(i + i + 1)); - bytes[i] = (byte) ((firstDigit << 4) + secondDigit); - } - - return bytes; - } - - private int toDigit(final char hexChar) { - final int digit = Character.digit(hexChar, 16); - if (digit == -1) { - throw new IllegalArgumentException("Invalid Hexadecimal Character: " + hexChar); - } - return digit; - } - - /** - * {@link ...} - * - * @throws RocksDBException if something goes wrong, or if the regression occurs - * @throws IOException if we can't make the temporary file - */ - @Test - public void testSST() throws RocksDBException, IOException { - final File tempSSTFile = temporarySSTFolder.newFile("test_file_with_weird_keys.sst"); - - final EnvOptions envOpts = new EnvOptions(); - final Options opts = new Options(); - opts.setComparator(new BytewiseComparator(new ComparatorOptions())); - final SstFileWriter writer = new SstFileWriter(envOpts, opts); - writer.open(tempSSTFile.getAbsolutePath()); - final byte[] gKey = - hexToByte("000000293030303030303030303030303030303030303032303736343730696E666F33"); - final byte[] wKey = - hexToByte("0000008d3030303030303030303030303030303030303030303437363433696e666f34"); - writer.put(new Slice(gKey), new Slice("dummyV1")); - writer.put(new Slice(wKey), new Slice("dummyV2")); - writer.finish(); - } -} diff --git a/java/src/test/java/org/rocksdb/CheckPointTest.java b/java/src/test/java/org/rocksdb/CheckPointTest.java deleted file mode 100644 index c2cc6fc62..000000000 --- a/java/src/test/java/org/rocksdb/CheckPointTest.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CheckPointTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Rule - public TemporaryFolder checkpointFolder = new TemporaryFolder(); - - @Test - public void checkPoint() throws RocksDBException { - try (final Options options = new Options(). - setCreateIfMissing(true)) { - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "value".getBytes()); - try (final Checkpoint checkpoint = Checkpoint.create(db)) { - checkpoint.createCheckpoint(checkpointFolder. - getRoot().getAbsolutePath() + "/snapshot1"); - db.put("key2".getBytes(), "value2".getBytes()); - checkpoint.createCheckpoint(checkpointFolder. - getRoot().getAbsolutePath() + "/snapshot2"); - } - } - - try (final RocksDB db = RocksDB.open(options, - checkpointFolder.getRoot().getAbsolutePath() + - "/snapshot1")) { - assertThat(new String(db.get("key".getBytes()))). - isEqualTo("value"); - assertThat(db.get("key2".getBytes())).isNull(); - } - - try (final RocksDB db = RocksDB.open(options, - checkpointFolder.getRoot().getAbsolutePath() + - "/snapshot2")) { - assertThat(new String(db.get("key".getBytes()))). - isEqualTo("value"); - assertThat(new String(db.get("key2".getBytes()))). - isEqualTo("value2"); - } - } - } - - @Test(expected = IllegalArgumentException.class) - public void failIfDbIsNull() { - try (final Checkpoint checkpoint = Checkpoint.create(null)) { - - } - } - - @Test(expected = IllegalStateException.class) - public void failIfDbNotInitialized() throws RocksDBException { - try (final RocksDB db = RocksDB.open( - dbFolder.getRoot().getAbsolutePath())) { - db.close(); - Checkpoint.create(db); - } - } - - @Test(expected = RocksDBException.class) - public void failWithIllegalPath() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final Checkpoint checkpoint = Checkpoint.create(db)) { - checkpoint.createCheckpoint("/Z:///:\\C:\\TZ/-"); - } - } -} diff --git a/java/src/test/java/org/rocksdb/ClockCacheTest.java b/java/src/test/java/org/rocksdb/ClockCacheTest.java deleted file mode 100644 index d1241ac75..000000000 --- a/java/src/test/java/org/rocksdb/ClockCacheTest.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -public class ClockCacheTest { - - static { - RocksDB.loadLibrary(); - } - - @Test - public void newClockCache() { - final long capacity = 1000; - final int numShardBits = 16; - final boolean strictCapacityLimit = true; - try(final Cache clockCache = new ClockCache(capacity, - numShardBits, strictCapacityLimit)) { - //no op - } - } -} diff --git a/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java b/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java deleted file mode 100644 index 7d7581048..000000000 --- a/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java +++ /dev/null @@ -1,714 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import org.junit.ClassRule; -import org.junit.Test; -import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory; - -public class ColumnFamilyOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - public static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void copyConstructor() { - ColumnFamilyOptions origOpts = new ColumnFamilyOptions(); - origOpts.setNumLevels(rand.nextInt(8)); - origOpts.setTargetFileSizeMultiplier(rand.nextInt(100)); - origOpts.setLevel0StopWritesTrigger(rand.nextInt(50)); - ColumnFamilyOptions copyOpts = new ColumnFamilyOptions(origOpts); - assertThat(origOpts.numLevels()).isEqualTo(copyOpts.numLevels()); - assertThat(origOpts.targetFileSizeMultiplier()).isEqualTo(copyOpts.targetFileSizeMultiplier()); - assertThat(origOpts.level0StopWritesTrigger()).isEqualTo(copyOpts.level0StopWritesTrigger()); - } - - @Test - public void getColumnFamilyOptionsFromProps() { - Properties properties = new Properties(); - properties.put("write_buffer_size", "112"); - properties.put("max_write_buffer_number", "13"); - - try (final ColumnFamilyOptions opt = ColumnFamilyOptions. - getColumnFamilyOptionsFromProps(properties)) { - // setup sample properties - assertThat(opt).isNotNull(); - assertThat(String.valueOf(opt.writeBufferSize())). - isEqualTo(properties.get("write_buffer_size")); - assertThat(String.valueOf(opt.maxWriteBufferNumber())). - isEqualTo(properties.get("max_write_buffer_number")); - } - } - - @Test - public void getColumnFamilyOptionsFromPropsWithIgnoreIllegalValue() { - // setup sample properties - final Properties properties = new Properties(); - properties.put("tomato", "1024"); - properties.put("burger", "2"); - properties.put("write_buffer_size", "112"); - properties.put("max_write_buffer_number", "13"); - - try (final ConfigOptions cfgOpts = new ConfigOptions().setIgnoreUnknownOptions(true); - final ColumnFamilyOptions opt = - ColumnFamilyOptions.getColumnFamilyOptionsFromProps(cfgOpts, properties)) { - // setup sample properties - assertThat(opt).isNotNull(); - assertThat(String.valueOf(opt.writeBufferSize())) - .isEqualTo(properties.get("write_buffer_size")); - assertThat(String.valueOf(opt.maxWriteBufferNumber())) - .isEqualTo(properties.get("max_write_buffer_number")); - } - } - - @Test - public void failColumnFamilyOptionsFromPropsWithIllegalValue() { - // setup sample properties - final Properties properties = new Properties(); - properties.put("tomato", "1024"); - properties.put("burger", "2"); - - try (final ColumnFamilyOptions opt = - ColumnFamilyOptions.getColumnFamilyOptionsFromProps(properties)) { - assertThat(opt).isNull(); - } - } - - @Test(expected = IllegalArgumentException.class) - public void failColumnFamilyOptionsFromPropsWithNullValue() { - try (final ColumnFamilyOptions opt = - ColumnFamilyOptions.getColumnFamilyOptionsFromProps(null)) { - } - } - - @Test(expected = IllegalArgumentException.class) - public void failColumnFamilyOptionsFromPropsWithEmptyProps() { - try (final ColumnFamilyOptions opt = - ColumnFamilyOptions.getColumnFamilyOptionsFromProps( - new Properties())) { - } - } - - @Test - public void writeBufferSize() throws RocksDBException { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setWriteBufferSize(longValue); - assertThat(opt.writeBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void maxWriteBufferNumber() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxWriteBufferNumber(intValue); - assertThat(opt.maxWriteBufferNumber()).isEqualTo(intValue); - } - } - - @Test - public void minWriteBufferNumberToMerge() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setMinWriteBufferNumberToMerge(intValue); - assertThat(opt.minWriteBufferNumberToMerge()).isEqualTo(intValue); - } - } - - @Test - public void numLevels() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setNumLevels(intValue); - assertThat(opt.numLevels()).isEqualTo(intValue); - } - } - - @Test - public void levelZeroFileNumCompactionTrigger() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setLevelZeroFileNumCompactionTrigger(intValue); - assertThat(opt.levelZeroFileNumCompactionTrigger()).isEqualTo(intValue); - } - } - - @Test - public void levelZeroSlowdownWritesTrigger() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setLevelZeroSlowdownWritesTrigger(intValue); - assertThat(opt.levelZeroSlowdownWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void levelZeroStopWritesTrigger() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setLevelZeroStopWritesTrigger(intValue); - assertThat(opt.levelZeroStopWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void targetFileSizeBase() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setTargetFileSizeBase(longValue); - assertThat(opt.targetFileSizeBase()).isEqualTo(longValue); - } - } - - @Test - public void targetFileSizeMultiplier() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setTargetFileSizeMultiplier(intValue); - assertThat(opt.targetFileSizeMultiplier()).isEqualTo(intValue); - } - } - - @Test - public void maxBytesForLevelBase() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxBytesForLevelBase(longValue); - assertThat(opt.maxBytesForLevelBase()).isEqualTo(longValue); - } - } - - @Test - public void levelCompactionDynamicLevelBytes() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setLevelCompactionDynamicLevelBytes(boolValue); - assertThat(opt.levelCompactionDynamicLevelBytes()) - .isEqualTo(boolValue); - } - } - - @Test - public void maxBytesForLevelMultiplier() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final double doubleValue = rand.nextDouble(); - opt.setMaxBytesForLevelMultiplier(doubleValue); - assertThat(opt.maxBytesForLevelMultiplier()).isEqualTo(doubleValue); - } - } - - @Test - public void maxBytesForLevelMultiplierAdditional() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue1 = rand.nextInt(); - final int intValue2 = rand.nextInt(); - final int[] ints = new int[]{intValue1, intValue2}; - opt.setMaxBytesForLevelMultiplierAdditional(ints); - assertThat(opt.maxBytesForLevelMultiplierAdditional()).isEqualTo(ints); - } - } - - @Test - public void maxCompactionBytes() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxCompactionBytes(longValue); - assertThat(opt.maxCompactionBytes()).isEqualTo(longValue); - } - } - - @Test - public void softPendingCompactionBytesLimit() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setSoftPendingCompactionBytesLimit(longValue); - assertThat(opt.softPendingCompactionBytesLimit()).isEqualTo(longValue); - } - } - - @Test - public void hardPendingCompactionBytesLimit() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setHardPendingCompactionBytesLimit(longValue); - assertThat(opt.hardPendingCompactionBytesLimit()).isEqualTo(longValue); - } - } - - @Test - public void level0FileNumCompactionTrigger() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setLevel0FileNumCompactionTrigger(intValue); - assertThat(opt.level0FileNumCompactionTrigger()).isEqualTo(intValue); - } - } - - @Test - public void level0SlowdownWritesTrigger() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setLevel0SlowdownWritesTrigger(intValue); - assertThat(opt.level0SlowdownWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void level0StopWritesTrigger() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setLevel0StopWritesTrigger(intValue); - assertThat(opt.level0StopWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void arenaBlockSize() throws RocksDBException { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setArenaBlockSize(longValue); - assertThat(opt.arenaBlockSize()).isEqualTo(longValue); - } - } - - @Test - public void disableAutoCompactions() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setDisableAutoCompactions(boolValue); - assertThat(opt.disableAutoCompactions()).isEqualTo(boolValue); - } - } - - @Test - public void maxSequentialSkipInIterations() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxSequentialSkipInIterations(longValue); - assertThat(opt.maxSequentialSkipInIterations()).isEqualTo(longValue); - } - } - - @Test - public void inplaceUpdateSupport() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setInplaceUpdateSupport(boolValue); - assertThat(opt.inplaceUpdateSupport()).isEqualTo(boolValue); - } - } - - @Test - public void inplaceUpdateNumLocks() throws RocksDBException { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setInplaceUpdateNumLocks(longValue); - assertThat(opt.inplaceUpdateNumLocks()).isEqualTo(longValue); - } - } - - @Test - public void memtablePrefixBloomSizeRatio() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final double doubleValue = rand.nextDouble(); - opt.setMemtablePrefixBloomSizeRatio(doubleValue); - assertThat(opt.memtablePrefixBloomSizeRatio()).isEqualTo(doubleValue); - } - } - - @Test - public void experimentalMempurgeThreshold() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final double doubleValue = rand.nextDouble(); - opt.setExperimentalMempurgeThreshold(doubleValue); - assertThat(opt.experimentalMempurgeThreshold()).isEqualTo(doubleValue); - } - } - - @Test - public void memtableWholeKeyFiltering() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean booleanValue = rand.nextBoolean(); - opt.setMemtableWholeKeyFiltering(booleanValue); - assertThat(opt.memtableWholeKeyFiltering()).isEqualTo(booleanValue); - } - } - - @Test - public void memtableHugePageSize() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setMemtableHugePageSize(longValue); - assertThat(opt.memtableHugePageSize()).isEqualTo(longValue); - } - } - - @Test - public void bloomLocality() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final int intValue = rand.nextInt(); - opt.setBloomLocality(intValue); - assertThat(opt.bloomLocality()).isEqualTo(intValue); - } - } - - @Test - public void maxSuccessiveMerges() throws RocksDBException { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxSuccessiveMerges(longValue); - assertThat(opt.maxSuccessiveMerges()).isEqualTo(longValue); - } - } - - @Test - public void optimizeFiltersForHits() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean aBoolean = rand.nextBoolean(); - opt.setOptimizeFiltersForHits(aBoolean); - assertThat(opt.optimizeFiltersForHits()).isEqualTo(aBoolean); - } - } - - @Test - public void memTable() throws RocksDBException { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - opt.setMemTableConfig(new HashLinkedListMemTableConfig()); - assertThat(opt.memTableFactoryName()). - isEqualTo("HashLinkedListRepFactory"); - } - } - - @Test - public void comparator() throws RocksDBException { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - opt.setComparator(BuiltinComparator.BYTEWISE_COMPARATOR); - } - } - - @Test - public void linkageOfPrepMethods() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - options.optimizeUniversalStyleCompaction(); - options.optimizeUniversalStyleCompaction(4000); - options.optimizeLevelStyleCompaction(); - options.optimizeLevelStyleCompaction(3000); - options.optimizeForPointLookup(10); - options.optimizeForSmallDb(); - } - } - - @Test - public void shouldSetTestPrefixExtractor() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - options.useFixedLengthPrefixExtractor(100); - options.useFixedLengthPrefixExtractor(10); - } - } - - @Test - public void shouldSetTestCappedPrefixExtractor() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - options.useCappedPrefixExtractor(100); - options.useCappedPrefixExtractor(10); - } - } - - @Test - public void compressionTypes() { - try (final ColumnFamilyOptions columnFamilyOptions - = new ColumnFamilyOptions()) { - for (final CompressionType compressionType : - CompressionType.values()) { - columnFamilyOptions.setCompressionType(compressionType); - assertThat(columnFamilyOptions.compressionType()). - isEqualTo(compressionType); - assertThat(CompressionType.valueOf("NO_COMPRESSION")). - isEqualTo(CompressionType.NO_COMPRESSION); - } - } - } - - @Test - public void compressionPerLevel() { - try (final ColumnFamilyOptions columnFamilyOptions - = new ColumnFamilyOptions()) { - assertThat(columnFamilyOptions.compressionPerLevel()).isEmpty(); - List compressionTypeList = new ArrayList<>(); - for (int i = 0; i < columnFamilyOptions.numLevels(); i++) { - compressionTypeList.add(CompressionType.NO_COMPRESSION); - } - columnFamilyOptions.setCompressionPerLevel(compressionTypeList); - compressionTypeList = columnFamilyOptions.compressionPerLevel(); - for (CompressionType compressionType : compressionTypeList) { - assertThat(compressionType).isEqualTo( - CompressionType.NO_COMPRESSION); - } - } - } - - @Test - public void differentCompressionsPerLevel() { - try (final ColumnFamilyOptions columnFamilyOptions - = new ColumnFamilyOptions()) { - columnFamilyOptions.setNumLevels(3); - - assertThat(columnFamilyOptions.compressionPerLevel()).isEmpty(); - List compressionTypeList = new ArrayList<>(); - - compressionTypeList.add(CompressionType.BZLIB2_COMPRESSION); - compressionTypeList.add(CompressionType.SNAPPY_COMPRESSION); - compressionTypeList.add(CompressionType.LZ4_COMPRESSION); - - columnFamilyOptions.setCompressionPerLevel(compressionTypeList); - compressionTypeList = columnFamilyOptions.compressionPerLevel(); - - assertThat(compressionTypeList.size()).isEqualTo(3); - assertThat(compressionTypeList). - containsExactly( - CompressionType.BZLIB2_COMPRESSION, - CompressionType.SNAPPY_COMPRESSION, - CompressionType.LZ4_COMPRESSION); - - } - } - - @Test - public void bottommostCompressionType() { - try (final ColumnFamilyOptions columnFamilyOptions - = new ColumnFamilyOptions()) { - assertThat(columnFamilyOptions.bottommostCompressionType()) - .isEqualTo(CompressionType.DISABLE_COMPRESSION_OPTION); - - for (final CompressionType compressionType : CompressionType.values()) { - columnFamilyOptions.setBottommostCompressionType(compressionType); - assertThat(columnFamilyOptions.bottommostCompressionType()) - .isEqualTo(compressionType); - } - } - } - - @Test - public void bottommostCompressionOptions() { - try (final ColumnFamilyOptions columnFamilyOptions = - new ColumnFamilyOptions(); - final CompressionOptions bottommostCompressionOptions = - new CompressionOptions() - .setMaxDictBytes(123)) { - - columnFamilyOptions.setBottommostCompressionOptions( - bottommostCompressionOptions); - assertThat(columnFamilyOptions.bottommostCompressionOptions()) - .isEqualTo(bottommostCompressionOptions); - assertThat(columnFamilyOptions.bottommostCompressionOptions() - .maxDictBytes()).isEqualTo(123); - } - } - - @Test - public void compressionOptions() { - try (final ColumnFamilyOptions columnFamilyOptions - = new ColumnFamilyOptions(); - final CompressionOptions compressionOptions = new CompressionOptions() - .setMaxDictBytes(123)) { - - columnFamilyOptions.setCompressionOptions(compressionOptions); - assertThat(columnFamilyOptions.compressionOptions()) - .isEqualTo(compressionOptions); - assertThat(columnFamilyOptions.compressionOptions().maxDictBytes()) - .isEqualTo(123); - } - } - - @Test - public void compactionStyles() { - try (final ColumnFamilyOptions columnFamilyOptions - = new ColumnFamilyOptions()) { - for (final CompactionStyle compactionStyle : - CompactionStyle.values()) { - columnFamilyOptions.setCompactionStyle(compactionStyle); - assertThat(columnFamilyOptions.compactionStyle()). - isEqualTo(compactionStyle); - assertThat(CompactionStyle.valueOf("FIFO")). - isEqualTo(CompactionStyle.FIFO); - } - } - } - - @Test - public void maxTableFilesSizeFIFO() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - long longValue = rand.nextLong(); - // Size has to be positive - longValue = (longValue < 0) ? -longValue : longValue; - longValue = (longValue == 0) ? longValue + 1 : longValue; - opt.setMaxTableFilesSizeFIFO(longValue); - assertThat(opt.maxTableFilesSizeFIFO()). - isEqualTo(longValue); - } - } - - @Test - public void maxWriteBufferNumberToMaintain() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - int intValue = rand.nextInt(); - // Size has to be positive - intValue = (intValue < 0) ? -intValue : intValue; - intValue = (intValue == 0) ? intValue + 1 : intValue; - opt.setMaxWriteBufferNumberToMaintain(intValue); - assertThat(opt.maxWriteBufferNumberToMaintain()). - isEqualTo(intValue); - } - } - - @Test - public void compactionPriorities() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - for (final CompactionPriority compactionPriority : - CompactionPriority.values()) { - opt.setCompactionPriority(compactionPriority); - assertThat(opt.compactionPriority()). - isEqualTo(compactionPriority); - } - } - } - - @Test - public void reportBgIoStats() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean booleanValue = true; - opt.setReportBgIoStats(booleanValue); - assertThat(opt.reportBgIoStats()). - isEqualTo(booleanValue); - } - } - - @Test - public void ttl() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - options.setTtl(1000 * 60); - assertThat(options.ttl()). - isEqualTo(1000 * 60); - } - } - - @Test - public void periodicCompactionSeconds() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - options.setPeriodicCompactionSeconds(1000 * 60); - assertThat(options.periodicCompactionSeconds()).isEqualTo(1000 * 60); - } - } - - @Test - public void compactionOptionsUniversal() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions(); - final CompactionOptionsUniversal optUni = new CompactionOptionsUniversal() - .setCompressionSizePercent(7)) { - opt.setCompactionOptionsUniversal(optUni); - assertThat(opt.compactionOptionsUniversal()). - isEqualTo(optUni); - assertThat(opt.compactionOptionsUniversal().compressionSizePercent()) - .isEqualTo(7); - } - } - - @Test - public void compactionOptionsFIFO() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions(); - final CompactionOptionsFIFO optFifo = new CompactionOptionsFIFO() - .setMaxTableFilesSize(2000)) { - opt.setCompactionOptionsFIFO(optFifo); - assertThat(opt.compactionOptionsFIFO()). - isEqualTo(optFifo); - assertThat(opt.compactionOptionsFIFO().maxTableFilesSize()) - .isEqualTo(2000); - } - } - - @Test - public void forceConsistencyChecks() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - final boolean booleanValue = true; - opt.setForceConsistencyChecks(booleanValue); - assertThat(opt.forceConsistencyChecks()). - isEqualTo(booleanValue); - } - } - - @Test - public void compactionFilter() { - try(final ColumnFamilyOptions options = new ColumnFamilyOptions(); - final RemoveEmptyValueCompactionFilter cf = new RemoveEmptyValueCompactionFilter()) { - options.setCompactionFilter(cf); - assertThat(options.compactionFilter()).isEqualTo(cf); - } - } - - @Test - public void compactionFilterFactory() { - try(final ColumnFamilyOptions options = new ColumnFamilyOptions(); - final RemoveEmptyValueCompactionFilterFactory cff = new RemoveEmptyValueCompactionFilterFactory()) { - options.setCompactionFilterFactory(cff); - assertThat(options.compactionFilterFactory()).isEqualTo(cff); - } - } - - @Test - public void compactionThreadLimiter() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions(); - final ConcurrentTaskLimiter compactionThreadLimiter = - new ConcurrentTaskLimiterImpl("name", 3)) { - options.setCompactionThreadLimiter(compactionThreadLimiter); - assertThat(options.compactionThreadLimiter()).isEqualTo(compactionThreadLimiter); - } - } - - @Test - public void oldDefaults() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - options.oldDefaults(4, 6); - assertEquals(4 << 20, options.writeBufferSize()); - assertThat(options.compactionPriority()).isEqualTo(CompactionPriority.ByCompensatedSize); - assertThat(options.targetFileSizeBase()).isEqualTo(2 * 1048576); - assertThat(options.maxBytesForLevelBase()).isEqualTo(10 * 1048576); - assertThat(options.softPendingCompactionBytesLimit()).isEqualTo(0); - assertThat(options.hardPendingCompactionBytesLimit()).isEqualTo(0); - assertThat(options.level0StopWritesTrigger()).isEqualTo(24); - } - } - - @Test - public void optimizeForSmallDbWithCache() { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions(); - final Cache cache = new LRUCache(1024)) { - assertThat(options.optimizeForSmallDb(cache)).isEqualTo(options); - } - } - - @Test - public void cfPaths() throws IOException { - try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { - final List paths = Arrays.asList( - new DbPath(Paths.get("test1"), 2 << 25), new DbPath(Paths.get("/test2/path"), 2 << 25)); - assertThat(options.cfPaths()).isEqualTo(Collections.emptyList()); - assertThat(options.setCfPaths(paths)).isEqualTo(options); - assertThat(options.cfPaths()).isEqualTo(paths); - } - } -} diff --git a/java/src/test/java/org/rocksdb/ColumnFamilyTest.java b/java/src/test/java/org/rocksdb/ColumnFamilyTest.java deleted file mode 100644 index e98327d93..000000000 --- a/java/src/test/java/org/rocksdb/ColumnFamilyTest.java +++ /dev/null @@ -1,582 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.*; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class ColumnFamilyTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void columnFamilyDescriptorName() throws RocksDBException { - final byte[] cfName = "some_name".getBytes(UTF_8); - - try(final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions()) { - final ColumnFamilyDescriptor cfDescriptor = - new ColumnFamilyDescriptor(cfName, cfOptions); - assertThat(cfDescriptor.getName()).isEqualTo(cfName); - } - } - - @Test - public void columnFamilyDescriptorOptions() throws RocksDBException { - final byte[] cfName = "some_name".getBytes(UTF_8); - - try(final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions() - .setCompressionType(CompressionType.BZLIB2_COMPRESSION)) { - final ColumnFamilyDescriptor cfDescriptor = - new ColumnFamilyDescriptor(cfName, cfOptions); - - assertThat(cfDescriptor.getOptions().compressionType()) - .isEqualTo(CompressionType.BZLIB2_COMPRESSION); - } - } - - @Test - public void listColumnFamilies() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - // Test listColumnFamilies - final List columnFamilyNames = RocksDB.listColumnFamilies(options, - dbFolder.getRoot().getAbsolutePath()); - assertThat(columnFamilyNames).isNotNull(); - assertThat(columnFamilyNames.size()).isGreaterThan(0); - assertThat(columnFamilyNames.size()).isEqualTo(1); - assertThat(new String(columnFamilyNames.get(0))).isEqualTo("default"); - } - } - - @Test - public void defaultColumnFamily() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - final ColumnFamilyHandle cfh = db.getDefaultColumnFamily(); - try { - assertThat(cfh).isNotNull(); - - assertThat(cfh.getName()).isEqualTo("default".getBytes(UTF_8)); - assertThat(cfh.getID()).isEqualTo(0); - assertThat(cfh.getDescriptor().getName()).isEqualTo("default".getBytes(UTF_8)); - - final byte[] key = "key".getBytes(); - final byte[] value = "value".getBytes(); - - db.put(cfh, key, value); - - final byte[] actualValue = db.get(cfh, key); - - assertThat(cfh).isNotNull(); - assertThat(actualValue).isEqualTo(value); - } finally { - cfh.close(); - } - } - } - - @Test - public void createColumnFamily() throws RocksDBException { - final byte[] cfName = "new_cf".getBytes(UTF_8); - final ColumnFamilyDescriptor cfDescriptor = new ColumnFamilyDescriptor(cfName, - new ColumnFamilyOptions()); - - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - final ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily(cfDescriptor); - - try { - assertThat(columnFamilyHandle.getName()).isEqualTo(cfName); - assertThat(columnFamilyHandle.getID()).isEqualTo(1); - - final ColumnFamilyDescriptor latestDescriptor = columnFamilyHandle.getDescriptor(); - assertThat(latestDescriptor.getName()).isEqualTo(cfName); - - final List columnFamilyNames = RocksDB.listColumnFamilies( - options, dbFolder.getRoot().getAbsolutePath()); - assertThat(columnFamilyNames).isNotNull(); - assertThat(columnFamilyNames.size()).isGreaterThan(0); - assertThat(columnFamilyNames.size()).isEqualTo(2); - assertThat(new String(columnFamilyNames.get(0))).isEqualTo("default"); - assertThat(new String(columnFamilyNames.get(1))).isEqualTo("new_cf"); - } finally { - columnFamilyHandle.close(); - } - } - } - - @Test - public void openWithColumnFamilies() throws RocksDBException { - final List cfNames = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes()) - ); - - final List columnFamilyHandleList = - new ArrayList<>(); - - // Test open database with column family names - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfNames, - columnFamilyHandleList)) { - assertThat(columnFamilyHandleList.size()).isEqualTo(2); - db.put("dfkey1".getBytes(), "dfvalue".getBytes()); - db.put(columnFamilyHandleList.get(0), "dfkey2".getBytes(), "dfvalue".getBytes()); - db.put(columnFamilyHandleList.get(1), "newcfkey1".getBytes(), "newcfvalue".getBytes()); - - String retVal = new String(db.get(columnFamilyHandleList.get(1), "newcfkey1".getBytes())); - assertThat(retVal).isEqualTo("newcfvalue"); - assertThat((db.get(columnFamilyHandleList.get(1), "dfkey1".getBytes()))).isNull(); - db.delete(columnFamilyHandleList.get(1), "newcfkey1".getBytes()); - assertThat((db.get(columnFamilyHandleList.get(1), "newcfkey1".getBytes()))).isNull(); - db.delete(columnFamilyHandleList.get(0), new WriteOptions(), "dfkey2".getBytes()); - assertThat(db.get(columnFamilyHandleList.get(0), new ReadOptions(), "dfkey2".getBytes())) - .isNull(); - } - } - - @Test - public void getWithOutValueAndCf() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); - final List columnFamilyHandleList = new ArrayList<>(); - - // Test open database with column family names - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - db.put( - columnFamilyHandleList.get(0), new WriteOptions(), "key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - final byte[] outValue = new byte[5]; - // not found value - int getResult = db.get("keyNotFound".getBytes(), outValue); - assertThat(getResult).isEqualTo(RocksDB.NOT_FOUND); - // found value which fits in outValue - getResult = db.get(columnFamilyHandleList.get(0), "key1".getBytes(), outValue); - assertThat(getResult).isNotEqualTo(RocksDB.NOT_FOUND); - assertThat(outValue).isEqualTo("value".getBytes()); - // found value which fits partially - getResult = - db.get(columnFamilyHandleList.get(0), new ReadOptions(), "key2".getBytes(), outValue); - assertThat(getResult).isNotEqualTo(RocksDB.NOT_FOUND); - assertThat(outValue).isEqualTo("12345".getBytes()); - } - } - - @Test - public void createWriteDropColumnFamily() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - ColumnFamilyHandle tmpColumnFamilyHandle; - tmpColumnFamilyHandle = db.createColumnFamily( - new ColumnFamilyDescriptor("tmpCF".getBytes(), new ColumnFamilyOptions())); - db.put(tmpColumnFamilyHandle, "key".getBytes(), "value".getBytes()); - db.dropColumnFamily(tmpColumnFamilyHandle); - assertThat(tmpColumnFamilyHandle.isOwningHandle()).isTrue(); - } - } - - @Test - public void createWriteDropColumnFamilies() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - ColumnFamilyHandle tmpColumnFamilyHandle = null; - ColumnFamilyHandle tmpColumnFamilyHandle2 = null; - tmpColumnFamilyHandle = db.createColumnFamily( - new ColumnFamilyDescriptor("tmpCF".getBytes(), new ColumnFamilyOptions())); - tmpColumnFamilyHandle2 = db.createColumnFamily( - new ColumnFamilyDescriptor("tmpCF2".getBytes(), new ColumnFamilyOptions())); - db.put(tmpColumnFamilyHandle, "key".getBytes(), "value".getBytes()); - db.put(tmpColumnFamilyHandle2, "key".getBytes(), "value".getBytes()); - db.dropColumnFamilies(Arrays.asList(tmpColumnFamilyHandle, tmpColumnFamilyHandle2)); - assertThat(tmpColumnFamilyHandle.isOwningHandle()).isTrue(); - assertThat(tmpColumnFamilyHandle2.isOwningHandle()).isTrue(); - } - } - - @Test - public void writeBatch() throws RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final ColumnFamilyOptions defaultCfOptions = new ColumnFamilyOptions() - .setMergeOperator(stringAppendOperator)) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, - defaultCfOptions), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList); - final WriteBatch writeBatch = new WriteBatch(); - final WriteOptions writeOpt = new WriteOptions()) { - writeBatch.put("key".getBytes(), "value".getBytes()); - writeBatch.put(db.getDefaultColumnFamily(), "mergeKey".getBytes(), "merge".getBytes()); - writeBatch.merge(db.getDefaultColumnFamily(), "mergeKey".getBytes(), "merge".getBytes()); - writeBatch.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(), "value".getBytes()); - writeBatch.put(columnFamilyHandleList.get(1), "newcfkey2".getBytes(), "value2".getBytes()); - writeBatch.delete("xyz".getBytes()); - writeBatch.delete(columnFamilyHandleList.get(1), "xyz".getBytes()); - db.write(writeOpt, writeBatch); - - assertThat(db.get(columnFamilyHandleList.get(1), "xyz".getBytes()) == null); - assertThat(new String(db.get(columnFamilyHandleList.get(1), "newcfkey".getBytes()))) - .isEqualTo("value"); - assertThat(new String(db.get(columnFamilyHandleList.get(1), "newcfkey2".getBytes()))) - .isEqualTo("value2"); - assertThat(new String(db.get("key".getBytes()))).isEqualTo("value"); - // check if key is merged - assertThat(new String(db.get(db.getDefaultColumnFamily(), "mergeKey".getBytes()))) - .isEqualTo("merge,merge"); - } - } - } - - @Test - public void iteratorOnColumnFamily() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - db.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(), "value".getBytes()); - db.put(columnFamilyHandleList.get(1), "newcfkey2".getBytes(), "value2".getBytes()); - try (final RocksIterator rocksIterator = db.newIterator(columnFamilyHandleList.get(1))) { - rocksIterator.seekToFirst(); - Map refMap = new HashMap<>(); - refMap.put("newcfkey", "value"); - refMap.put("newcfkey2", "value2"); - int i = 0; - while (rocksIterator.isValid()) { - i++; - assertThat(refMap.get(new String(rocksIterator.key()))) - .isEqualTo(new String(rocksIterator.value())); - rocksIterator.next(); - } - assertThat(i).isEqualTo(2); - } - } - } - - @Test - public void multiGet() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - db.put(columnFamilyHandleList.get(0), "key".getBytes(), "value".getBytes()); - db.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(), "value".getBytes()); - - final List keys = - Arrays.asList(new byte[][] {"key".getBytes(), "newcfkey".getBytes()}); - - List retValues = db.multiGetAsList(columnFamilyHandleList, keys); - assertThat(retValues.size()).isEqualTo(2); - assertThat(new String(retValues.get(0))).isEqualTo("value"); - assertThat(new String(retValues.get(1))).isEqualTo("value"); - retValues = db.multiGetAsList(new ReadOptions(), columnFamilyHandleList, keys); - assertThat(retValues.size()).isEqualTo(2); - assertThat(new String(retValues.get(0))).isEqualTo("value"); - assertThat(new String(retValues.get(1))).isEqualTo("value"); - } - } - - @Test - public void multiGetAsList() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - db.put(columnFamilyHandleList.get(0), "key".getBytes(), "value".getBytes()); - db.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(), "value".getBytes()); - - final List keys = - Arrays.asList(new byte[][] {"key".getBytes(), "newcfkey".getBytes()}); - List retValues = db.multiGetAsList(columnFamilyHandleList, keys); - assertThat(retValues.size()).isEqualTo(2); - assertThat(new String(retValues.get(0))).isEqualTo("value"); - assertThat(new String(retValues.get(1))).isEqualTo("value"); - retValues = db.multiGetAsList(new ReadOptions(), columnFamilyHandleList, keys); - assertThat(retValues.size()).isEqualTo(2); - assertThat(new String(retValues.get(0))).isEqualTo("value"); - assertThat(new String(retValues.get(1))).isEqualTo("value"); - } - } - - @Test - public void properties() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - assertThat(db.getProperty("rocksdb.estimate-num-keys")).isNotNull(); - assertThat(db.getLongProperty(columnFamilyHandleList.get(0), "rocksdb.estimate-num-keys")) - .isGreaterThanOrEqualTo(0); - assertThat(db.getProperty("rocksdb.stats")).isNotNull(); - assertThat(db.getProperty(columnFamilyHandleList.get(0), "rocksdb.sstables")).isNotNull(); - assertThat(db.getProperty(columnFamilyHandleList.get(1), "rocksdb.estimate-num-keys")) - .isNotNull(); - assertThat(db.getProperty(columnFamilyHandleList.get(1), "rocksdb.stats")).isNotNull(); - assertThat(db.getProperty(columnFamilyHandleList.get(1), "rocksdb.sstables")).isNotNull(); - assertThat(db.getAggregatedLongProperty("rocksdb.estimate-num-keys")).isNotNull(); - assertThat(db.getAggregatedLongProperty("rocksdb.estimate-num-keys")) - .isGreaterThanOrEqualTo(0); - } - } - - - @Test - public void iterators() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - List iterators = null; - try { - iterators = db.newIterators(columnFamilyHandleList); - assertThat(iterators.size()).isEqualTo(2); - RocksIterator iter = iterators.get(0); - iter.seekToFirst(); - final Map defRefMap = new HashMap<>(); - defRefMap.put("dfkey1", "dfvalue"); - defRefMap.put("key", "value"); - while (iter.isValid()) { - assertThat(defRefMap.get(new String(iter.key()))). - isEqualTo(new String(iter.value())); - iter.next(); - } - // iterate over new_cf key/value pairs - final Map cfRefMap = new HashMap<>(); - cfRefMap.put("newcfkey", "value"); - cfRefMap.put("newcfkey2", "value2"); - iter = iterators.get(1); - iter.seekToFirst(); - while (iter.isValid()) { - assertThat(cfRefMap.get(new String(iter.key()))). - isEqualTo(new String(iter.value())); - iter.next(); - } - } finally { - if (iterators != null) { - for (final RocksIterator rocksIterator : iterators) { - rocksIterator.close(); - } - } - } - } - } - - @Test(expected = RocksDBException.class) - public void failPutDisposedCF() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - db.dropColumnFamily(columnFamilyHandleList.get(1)); - db.put(columnFamilyHandleList.get(1), "key".getBytes(), "value".getBytes()); - } - } - - @Test(expected = RocksDBException.class) - public void failRemoveDisposedCF() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - db.dropColumnFamily(columnFamilyHandleList.get(1)); - db.delete(columnFamilyHandleList.get(1), "key".getBytes()); - } - } - - @Test(expected = RocksDBException.class) - public void failGetDisposedCF() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - db.dropColumnFamily(columnFamilyHandleList.get(1)); - db.get(columnFamilyHandleList.get(1), "key".getBytes()); - } - } - - @Test(expected = RocksDBException.class) - public void failMultiGetWithoutCorrectNumberOfCF() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - final List keys = new ArrayList<>(); - keys.add("key".getBytes()); - keys.add("newcfkey".getBytes()); - final List cfCustomList = new ArrayList<>(); - db.multiGetAsList(cfCustomList, keys); - } - } - - @Test - public void testByteCreateFolumnFamily() throws RocksDBException { - - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()) - ) { - final byte[] b0 = new byte[]{(byte) 0x00}; - final byte[] b1 = new byte[]{(byte) 0x01}; - final byte[] b2 = new byte[]{(byte) 0x02}; - db.createColumnFamily(new ColumnFamilyDescriptor(b0)); - db.createColumnFamily(new ColumnFamilyDescriptor(b1)); - final List families = - RocksDB.listColumnFamilies(options, dbFolder.getRoot().getAbsolutePath()); - assertThat(families).contains("default".getBytes(), b0, b1); - db.createColumnFamily(new ColumnFamilyDescriptor(b2)); - } - } - - @Test - public void testCFNamesWithZeroBytes() throws RocksDBException { - ColumnFamilyHandle cf1 = null, cf2 = null; - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()); - ) { - final byte[] b0 = new byte[] {0, 0}; - final byte[] b1 = new byte[] {0, 1}; - cf1 = db.createColumnFamily(new ColumnFamilyDescriptor(b0)); - cf2 = db.createColumnFamily(new ColumnFamilyDescriptor(b1)); - final List families = - RocksDB.listColumnFamilies(options, dbFolder.getRoot().getAbsolutePath()); - assertThat(families).contains("default".getBytes(), b0, b1); - } - } - - @Test - public void testCFNameSimplifiedChinese() throws RocksDBException { - ColumnFamilyHandle columnFamilyHandle = null; - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()); - ) { - final String simplifiedChinese = "\u7b80\u4f53\u5b57"; - columnFamilyHandle = - db.createColumnFamily(new ColumnFamilyDescriptor(simplifiedChinese.getBytes())); - - final List families = - RocksDB.listColumnFamilies(options, dbFolder.getRoot().getAbsolutePath()); - assertThat(families).contains("default".getBytes(), simplifiedChinese.getBytes()); - } - } - - @Test - public void testDestroyColumnFamilyHandle() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath());) { - final byte[] name1 = "cf1".getBytes(); - final byte[] name2 = "cf2".getBytes(); - final ColumnFamilyDescriptor desc1 = new ColumnFamilyDescriptor(name1); - final ColumnFamilyDescriptor desc2 = new ColumnFamilyDescriptor(name2); - final ColumnFamilyHandle cf1 = db.createColumnFamily(desc1); - final ColumnFamilyHandle cf2 = db.createColumnFamily(desc2); - assertTrue(cf1.isOwningHandle()); - assertTrue(cf2.isOwningHandle()); - assertFalse(cf1.isDefaultColumnFamily()); - db.destroyColumnFamilyHandle(cf1); - // At this point cf1 should not be used! - assertFalse(cf1.isOwningHandle()); - assertTrue(cf2.isOwningHandle()); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java b/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java deleted file mode 100644 index 57bf22b57..000000000 --- a/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; -import org.rocksdb.CompactRangeOptions.BottommostLevelCompaction; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactRangeOptionsTest { - - static { - RocksDB.loadLibrary(); - } - - @Test - public void exclusiveManualCompaction() { - CompactRangeOptions opt = new CompactRangeOptions(); - boolean value = false; - opt.setExclusiveManualCompaction(value); - assertThat(opt.exclusiveManualCompaction()).isEqualTo(value); - value = true; - opt.setExclusiveManualCompaction(value); - assertThat(opt.exclusiveManualCompaction()).isEqualTo(value); - } - - @Test - public void bottommostLevelCompaction() { - CompactRangeOptions opt = new CompactRangeOptions(); - BottommostLevelCompaction value = BottommostLevelCompaction.kSkip; - opt.setBottommostLevelCompaction(value); - assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); - value = BottommostLevelCompaction.kForce; - opt.setBottommostLevelCompaction(value); - assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); - value = BottommostLevelCompaction.kIfHaveCompactionFilter; - opt.setBottommostLevelCompaction(value); - assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); - value = BottommostLevelCompaction.kForceOptimized; - opt.setBottommostLevelCompaction(value); - assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); - } - - @Test - public void changeLevel() { - CompactRangeOptions opt = new CompactRangeOptions(); - boolean value = false; - opt.setChangeLevel(value); - assertThat(opt.changeLevel()).isEqualTo(value); - value = true; - opt.setChangeLevel(value); - assertThat(opt.changeLevel()).isEqualTo(value); - } - - @Test - public void targetLevel() { - CompactRangeOptions opt = new CompactRangeOptions(); - int value = 2; - opt.setTargetLevel(value); - assertThat(opt.targetLevel()).isEqualTo(value); - value = 3; - opt.setTargetLevel(value); - assertThat(opt.targetLevel()).isEqualTo(value); - } - - @Test - public void targetPathId() { - CompactRangeOptions opt = new CompactRangeOptions(); - int value = 2; - opt.setTargetPathId(value); - assertThat(opt.targetPathId()).isEqualTo(value); - value = 3; - opt.setTargetPathId(value); - assertThat(opt.targetPathId()).isEqualTo(value); - } - - @Test - public void allowWriteStall() { - CompactRangeOptions opt = new CompactRangeOptions(); - boolean value = false; - opt.setAllowWriteStall(value); - assertThat(opt.allowWriteStall()).isEqualTo(value); - value = true; - opt.setAllowWriteStall(value); - assertThat(opt.allowWriteStall()).isEqualTo(value); - } - - @Test - public void maxSubcompactions() { - CompactRangeOptions opt = new CompactRangeOptions(); - int value = 2; - opt.setMaxSubcompactions(value); - assertThat(opt.maxSubcompactions()).isEqualTo(value); - value = 3; - opt.setMaxSubcompactions(value); - assertThat(opt.maxSubcompactions()).isEqualTo(value); - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java b/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java deleted file mode 100644 index 35a14eb54..000000000 --- a/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionFilterFactoryTest { - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void columnFamilyOptions_setCompactionFilterFactory() - throws RocksDBException { - try(final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RemoveEmptyValueCompactionFilterFactory compactionFilterFactory - = new RemoveEmptyValueCompactionFilterFactory(); - final ColumnFamilyOptions new_cf_opts - = new ColumnFamilyOptions() - .setCompactionFilterFactory(compactionFilterFactory)) { - - final List cfNames = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)); - - final List cfHandles = new ArrayList<>(); - - try (final RocksDB rocksDb = - RocksDB.open(options, dbFolder.getRoot().getAbsolutePath(), cfNames, cfHandles)) { - final byte[] key1 = "key1".getBytes(); - final byte[] key2 = "key2".getBytes(); - - final byte[] value1 = "value1".getBytes(); - final byte[] value2 = new byte[0]; - - rocksDb.put(cfHandles.get(1), key1, value1); - rocksDb.put(cfHandles.get(1), key2, value2); - - rocksDb.compactRange(cfHandles.get(1)); - - assertThat(rocksDb.get(cfHandles.get(1), key1)).isEqualTo(value1); - final boolean exists = rocksDb.keyMayExist(cfHandles.get(1), key2, null); - assertThat(exists).isFalse(); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java b/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java deleted file mode 100644 index c71b0da16..000000000 --- a/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionJobInfoTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void columnFamilyName() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.columnFamilyName()) - .isEmpty(); - } - } - - @Test - public void status() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.status().getCode()) - .isEqualTo(Status.Code.Ok); - } - } - - @Test - public void threadId() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.threadId()) - .isEqualTo(0); - } - } - - @Test - public void jobId() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.jobId()) - .isEqualTo(0); - } - } - - @Test - public void baseInputLevel() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.baseInputLevel()) - .isEqualTo(0); - } - } - - @Test - public void outputLevel() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.outputLevel()) - .isEqualTo(0); - } - } - - @Test - public void inputFiles() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.inputFiles()) - .isEmpty(); - } - } - - @Test - public void outputFiles() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.outputFiles()) - .isEmpty(); - } - } - - @Test - public void tableProperties() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.tableProperties()) - .isEmpty(); - } - } - - @Test - public void compactionReason() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.compactionReason()) - .isEqualTo(CompactionReason.kUnknown); - } - } - - @Test - public void compression() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.compression()) - .isEqualTo(CompressionType.NO_COMPRESSION); - } - } - - @Test - public void stats() { - try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { - assertThat(compactionJobInfo.stats()) - .isNotNull(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java b/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java deleted file mode 100644 index 5c1eb2aab..000000000 --- a/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionJobStatsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void reset() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - compactionJobStats.reset(); - assertThat(compactionJobStats.elapsedMicros()).isEqualTo(0); - } - } - - @Test - public void add() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats(); - final CompactionJobStats otherCompactionJobStats = new CompactionJobStats()) { - compactionJobStats.add(otherCompactionJobStats); - } - } - - @Test - public void elapsedMicros() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.elapsedMicros()).isEqualTo(0); - } - } - - @Test - public void numInputRecords() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numInputRecords()).isEqualTo(0); - } - } - - @Test - public void numInputFiles() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numInputFiles()).isEqualTo(0); - } - } - - @Test - public void numInputFilesAtOutputLevel() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numInputFilesAtOutputLevel()).isEqualTo(0); - } - } - - @Test - public void numOutputRecords() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numOutputRecords()).isEqualTo(0); - } - } - - @Test - public void numOutputFiles() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numOutputFiles()).isEqualTo(0); - } - } - - @Test - public void isManualCompaction() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.isManualCompaction()).isFalse(); - } - } - - @Test - public void totalInputBytes() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.totalInputBytes()).isEqualTo(0); - } - } - - @Test - public void totalOutputBytes() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.totalOutputBytes()).isEqualTo(0); - } - } - - - @Test - public void numRecordsReplaced() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numRecordsReplaced()).isEqualTo(0); - } - } - - @Test - public void totalInputRawKeyBytes() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.totalInputRawKeyBytes()).isEqualTo(0); - } - } - - @Test - public void totalInputRawValueBytes() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.totalInputRawValueBytes()).isEqualTo(0); - } - } - - @Test - public void numInputDeletionRecords() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numInputDeletionRecords()).isEqualTo(0); - } - } - - @Test - public void numExpiredDeletionRecords() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numExpiredDeletionRecords()).isEqualTo(0); - } - } - - @Test - public void numCorruptKeys() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numCorruptKeys()).isEqualTo(0); - } - } - - @Test - public void fileWriteNanos() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.fileWriteNanos()).isEqualTo(0); - } - } - - @Test - public void fileRangeSyncNanos() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.fileRangeSyncNanos()).isEqualTo(0); - } - } - - @Test - public void fileFsyncNanos() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.fileFsyncNanos()).isEqualTo(0); - } - } - - @Test - public void filePrepareWriteNanos() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.filePrepareWriteNanos()).isEqualTo(0); - } - } - - @Test - public void smallestOutputKeyPrefix() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.smallestOutputKeyPrefix()).isEmpty(); - } - } - - @Test - public void largestOutputKeyPrefix() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.largestOutputKeyPrefix()).isEmpty(); - } - } - - @Test - public void numSingleDelFallthru() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numSingleDelFallthru()).isEqualTo(0); - } - } - - @Test - public void numSingleDelMismatch() { - try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { - assertThat(compactionJobStats.numSingleDelMismatch()).isEqualTo(0); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java b/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java deleted file mode 100644 index 841615e67..000000000 --- a/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionOptionsFIFOTest { - - static { - RocksDB.loadLibrary(); - } - - @Test - public void maxTableFilesSize() { - final long size = 500 * 1024 * 1026; - try (final CompactionOptionsFIFO opt = new CompactionOptionsFIFO()) { - opt.setMaxTableFilesSize(size); - assertThat(opt.maxTableFilesSize()).isEqualTo(size); - } - } - - @Test - public void allowCompaction() { - final boolean allowCompaction = true; - try (final CompactionOptionsFIFO opt = new CompactionOptionsFIFO()) { - opt.setAllowCompaction(allowCompaction); - assertThat(opt.allowCompaction()).isEqualTo(allowCompaction); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionOptionsTest.java b/java/src/test/java/org/rocksdb/CompactionOptionsTest.java deleted file mode 100644 index 9b7d79694..000000000 --- a/java/src/test/java/org/rocksdb/CompactionOptionsTest.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void compression() { - try (final CompactionOptions compactionOptions = new CompactionOptions()) { - assertThat(compactionOptions.compression()) - .isEqualTo(CompressionType.SNAPPY_COMPRESSION); - compactionOptions.setCompression(CompressionType.NO_COMPRESSION); - assertThat(compactionOptions.compression()) - .isEqualTo(CompressionType.NO_COMPRESSION); - } - } - - @Test - public void outputFileSizeLimit() { - final long mb250 = 1024 * 1024 * 250; - try (final CompactionOptions compactionOptions = new CompactionOptions()) { - assertThat(compactionOptions.outputFileSizeLimit()) - .isEqualTo(-1); - compactionOptions.setOutputFileSizeLimit(mb250); - assertThat(compactionOptions.outputFileSizeLimit()) - .isEqualTo(mb250); - } - } - - @Test - public void maxSubcompactions() { - try (final CompactionOptions compactionOptions = new CompactionOptions()) { - assertThat(compactionOptions.maxSubcompactions()) - .isEqualTo(0); - compactionOptions.setMaxSubcompactions(9); - assertThat(compactionOptions.maxSubcompactions()) - .isEqualTo(9); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java b/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java deleted file mode 100644 index 5e2d195b6..000000000 --- a/java/src/test/java/org/rocksdb/CompactionOptionsUniversalTest.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionOptionsUniversalTest { - - static { - RocksDB.loadLibrary(); - } - - @Test - public void sizeRatio() { - final int sizeRatio = 4; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setSizeRatio(sizeRatio); - assertThat(opt.sizeRatio()).isEqualTo(sizeRatio); - } - } - - @Test - public void minMergeWidth() { - final int minMergeWidth = 3; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setMinMergeWidth(minMergeWidth); - assertThat(opt.minMergeWidth()).isEqualTo(minMergeWidth); - } - } - - @Test - public void maxMergeWidth() { - final int maxMergeWidth = Integer.MAX_VALUE - 1234; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setMaxMergeWidth(maxMergeWidth); - assertThat(opt.maxMergeWidth()).isEqualTo(maxMergeWidth); - } - } - - @Test - public void maxSizeAmplificationPercent() { - final int maxSizeAmplificationPercent = 150; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setMaxSizeAmplificationPercent(maxSizeAmplificationPercent); - assertThat(opt.maxSizeAmplificationPercent()).isEqualTo(maxSizeAmplificationPercent); - } - } - - @Test - public void compressionSizePercent() { - final int compressionSizePercent = 500; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setCompressionSizePercent(compressionSizePercent); - assertThat(opt.compressionSizePercent()).isEqualTo(compressionSizePercent); - } - } - - @Test - public void stopStyle() { - final CompactionStopStyle stopStyle = CompactionStopStyle.CompactionStopStyleSimilarSize; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setStopStyle(stopStyle); - assertThat(opt.stopStyle()).isEqualTo(stopStyle); - } - } - - @Test - public void allowTrivialMove() { - final boolean allowTrivialMove = true; - try(final CompactionOptionsUniversal opt = new CompactionOptionsUniversal()) { - opt.setAllowTrivialMove(allowTrivialMove); - assertThat(opt.allowTrivialMove()).isEqualTo(allowTrivialMove); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionPriorityTest.java b/java/src/test/java/org/rocksdb/CompactionPriorityTest.java deleted file mode 100644 index b078e132f..000000000 --- a/java/src/test/java/org/rocksdb/CompactionPriorityTest.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionPriorityTest { - - @Test(expected = IllegalArgumentException.class) - public void failIfIllegalByteValueProvided() { - CompactionPriority.getCompactionPriority((byte) -1); - } - - @Test - public void getCompactionPriority() { - assertThat(CompactionPriority.getCompactionPriority( - CompactionPriority.OldestLargestSeqFirst.getValue())) - .isEqualTo(CompactionPriority.OldestLargestSeqFirst); - } - - @Test - public void valueOf() { - assertThat(CompactionPriority.valueOf("OldestSmallestSeqFirst")). - isEqualTo(CompactionPriority.OldestSmallestSeqFirst); - } -} diff --git a/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java b/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java deleted file mode 100644 index 4c8a20950..000000000 --- a/java/src/test/java/org/rocksdb/CompactionStopStyleTest.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompactionStopStyleTest { - - @Test(expected = IllegalArgumentException.class) - public void failIfIllegalByteValueProvided() { - CompactionStopStyle.getCompactionStopStyle((byte) -1); - } - - @Test - public void getCompactionStopStyle() { - assertThat(CompactionStopStyle.getCompactionStopStyle( - CompactionStopStyle.CompactionStopStyleTotalSize.getValue())) - .isEqualTo(CompactionStopStyle.CompactionStopStyleTotalSize); - } - - @Test - public void valueOf() { - assertThat(CompactionStopStyle.valueOf("CompactionStopStyleSimilarSize")). - isEqualTo(CompactionStopStyle.CompactionStopStyleSimilarSize); - } -} diff --git a/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java b/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java deleted file mode 100644 index 3e90b9f10..000000000 --- a/java/src/test/java/org/rocksdb/ComparatorOptionsTest.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ComparatorOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void reusedSynchronisationType() { - try(final ComparatorOptions copt = new ComparatorOptions()) { - - copt.setReusedSynchronisationType(ReusedSynchronisationType.MUTEX); - assertThat(copt.reusedSynchronisationType()) - .isEqualTo(ReusedSynchronisationType.MUTEX); - - copt.setReusedSynchronisationType(ReusedSynchronisationType.ADAPTIVE_MUTEX); - assertThat(copt.reusedSynchronisationType()) - .isEqualTo(ReusedSynchronisationType.ADAPTIVE_MUTEX); - - copt.setReusedSynchronisationType(ReusedSynchronisationType.THREAD_LOCAL); - assertThat(copt.reusedSynchronisationType()) - .isEqualTo(ReusedSynchronisationType.THREAD_LOCAL); - } - } - - @Test - public void useDirectBuffer() { - try(final ComparatorOptions copt = new ComparatorOptions()) { - copt.setUseDirectBuffer(true); - assertThat(copt.useDirectBuffer()).isTrue(); - - copt.setUseDirectBuffer(false); - assertThat(copt.useDirectBuffer()).isFalse(); - } - } - - @Test - public void maxReusedBufferSize() { - try(final ComparatorOptions copt = new ComparatorOptions()) { - copt.setMaxReusedBufferSize(12345); - assertThat(copt.maxReusedBufferSize()).isEqualTo(12345); - - copt.setMaxReusedBufferSize(-1); - assertThat(copt.maxReusedBufferSize()).isEqualTo(-1); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompressionOptionsTest.java b/java/src/test/java/org/rocksdb/CompressionOptionsTest.java deleted file mode 100644 index 116552c32..000000000 --- a/java/src/test/java/org/rocksdb/CompressionOptionsTest.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CompressionOptionsTest { - - static { - RocksDB.loadLibrary(); - } - - @Test - public void windowBits() { - final int windowBits = 7; - try(final CompressionOptions opt = new CompressionOptions()) { - opt.setWindowBits(windowBits); - assertThat(opt.windowBits()).isEqualTo(windowBits); - } - } - - @Test - public void level() { - final int level = 6; - try(final CompressionOptions opt = new CompressionOptions()) { - opt.setLevel(level); - assertThat(opt.level()).isEqualTo(level); - } - } - - @Test - public void strategy() { - final int strategy = 2; - try(final CompressionOptions opt = new CompressionOptions()) { - opt.setStrategy(strategy); - assertThat(opt.strategy()).isEqualTo(strategy); - } - } - - @Test - public void maxDictBytes() { - final int maxDictBytes = 999; - try(final CompressionOptions opt = new CompressionOptions()) { - opt.setMaxDictBytes(maxDictBytes); - assertThat(opt.maxDictBytes()).isEqualTo(maxDictBytes); - } - } - - @Test - public void zstdMaxTrainBytes() { - final int zstdMaxTrainBytes = 999; - try(final CompressionOptions opt = new CompressionOptions()) { - opt.setZStdMaxTrainBytes(zstdMaxTrainBytes); - assertThat(opt.zstdMaxTrainBytes()).isEqualTo(zstdMaxTrainBytes); - } - } - - @Test - public void enabled() { - try(final CompressionOptions opt = new CompressionOptions()) { - assertThat(opt.enabled()).isFalse(); - opt.setEnabled(true); - assertThat(opt.enabled()).isTrue(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/CompressionTypesTest.java b/java/src/test/java/org/rocksdb/CompressionTypesTest.java deleted file mode 100644 index e26cc0aca..000000000 --- a/java/src/test/java/org/rocksdb/CompressionTypesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - - -public class CompressionTypesTest { - @Test - public void getCompressionType() { - for (final CompressionType compressionType : CompressionType.values()) { - String libraryName = compressionType.getLibraryName(); - compressionType.equals(CompressionType.getCompressionType( - libraryName)); - } - } -} diff --git a/java/src/test/java/org/rocksdb/ConcurrentTaskLimiterTest.java b/java/src/test/java/org/rocksdb/ConcurrentTaskLimiterTest.java deleted file mode 100644 index 165f4f24c..000000000 --- a/java/src/test/java/org/rocksdb/ConcurrentTaskLimiterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.junit.Assert.assertEquals; - -import org.junit.After; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; - -public class ConcurrentTaskLimiterTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - private static final String NAME = "name"; - - private ConcurrentTaskLimiter concurrentTaskLimiter; - - @Before - public void beforeTest() { - concurrentTaskLimiter = new ConcurrentTaskLimiterImpl(NAME, 3); - } - - @Test - public void name() { - assertEquals(NAME, concurrentTaskLimiter.name()); - } - - @Test - public void outstandingTask() { - assertEquals(0, concurrentTaskLimiter.outstandingTask()); - } - - @Test - public void setMaxOutstandingTask() { - assertEquals(concurrentTaskLimiter, concurrentTaskLimiter.setMaxOutstandingTask(4)); - assertEquals(0, concurrentTaskLimiter.outstandingTask()); - } - - @Test - public void resetMaxOutstandingTask() { - assertEquals(concurrentTaskLimiter, concurrentTaskLimiter.resetMaxOutstandingTask()); - assertEquals(0, concurrentTaskLimiter.outstandingTask()); - } - - @After - public void afterTest() { - concurrentTaskLimiter.close(); - } -} diff --git a/java/src/test/java/org/rocksdb/DBOptionsTest.java b/java/src/test/java/org/rocksdb/DBOptionsTest.java deleted file mode 100644 index d55ceebcf..000000000 --- a/java/src/test/java/org/rocksdb/DBOptionsTest.java +++ /dev/null @@ -1,904 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.nio.file.Paths; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.ClassRule; -import org.junit.Test; - -public class DBOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - public static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void copyConstructor() { - DBOptions origOpts = new DBOptions(); - origOpts.setCreateIfMissing(rand.nextBoolean()); - origOpts.setAllow2pc(rand.nextBoolean()); - origOpts.setMaxBackgroundJobs(rand.nextInt(10)); - DBOptions copyOpts = new DBOptions(origOpts); - assertThat(origOpts.createIfMissing()).isEqualTo(copyOpts.createIfMissing()); - assertThat(origOpts.allow2pc()).isEqualTo(copyOpts.allow2pc()); - } - - @Test - public void getDBOptionsFromProps() { - // setup sample properties - final Properties properties = new Properties(); - properties.put("allow_mmap_reads", "true"); - properties.put("bytes_per_sync", "13"); - try(final DBOptions opt = DBOptions.getDBOptionsFromProps(properties)) { - assertThat(opt).isNotNull(); - assertThat(String.valueOf(opt.allowMmapReads())). - isEqualTo(properties.get("allow_mmap_reads")); - assertThat(String.valueOf(opt.bytesPerSync())). - isEqualTo(properties.get("bytes_per_sync")); - } - } - - @Test - public void failDBOptionsFromPropsWithIllegalValue() { - // setup sample properties - final Properties properties = new Properties(); - properties.put("tomato", "1024"); - properties.put("burger", "2"); - try(final DBOptions opt = DBOptions.getDBOptionsFromProps(properties)) { - assertThat(opt).isNull(); - } - } - - @Test(expected = IllegalArgumentException.class) - public void failDBOptionsFromPropsWithNullValue() { - try(final DBOptions opt = DBOptions.getDBOptionsFromProps(null)) { - //no-op - } - } - - @Test(expected = IllegalArgumentException.class) - public void failDBOptionsFromPropsWithEmptyProps() { - try(final DBOptions opt = DBOptions.getDBOptionsFromProps( - new Properties())) { - //no-op - } - } - - @Test - public void linkageOfPrepMethods() { - try (final DBOptions opt = new DBOptions()) { - opt.optimizeForSmallDb(); - } - } - - @Test - public void env() { - try (final DBOptions opt = new DBOptions(); - final Env env = Env.getDefault()) { - opt.setEnv(env); - assertThat(opt.getEnv()).isSameAs(env); - } - } - - @Test - public void setIncreaseParallelism() { - try(final DBOptions opt = new DBOptions()) { - final int threads = Runtime.getRuntime().availableProcessors() * 2; - opt.setIncreaseParallelism(threads); - } - } - - @Test - public void createIfMissing() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setCreateIfMissing(boolValue); - assertThat(opt.createIfMissing()).isEqualTo(boolValue); - } - } - - @Test - public void createMissingColumnFamilies() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setCreateMissingColumnFamilies(boolValue); - assertThat(opt.createMissingColumnFamilies()).isEqualTo(boolValue); - } - } - - @Test - public void errorIfExists() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setErrorIfExists(boolValue); - assertThat(opt.errorIfExists()).isEqualTo(boolValue); - } - } - - @Test - public void paranoidChecks() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setParanoidChecks(boolValue); - assertThat(opt.paranoidChecks()).isEqualTo(boolValue); - } - } - - @Test - public void maxTotalWalSize() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxTotalWalSize(longValue); - assertThat(opt.maxTotalWalSize()).isEqualTo(longValue); - } - } - - @Test - public void maxOpenFiles() { - try(final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxOpenFiles(intValue); - assertThat(opt.maxOpenFiles()).isEqualTo(intValue); - } - } - - @Test - public void maxFileOpeningThreads() { - try(final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxFileOpeningThreads(intValue); - assertThat(opt.maxFileOpeningThreads()).isEqualTo(intValue); - } - } - - @Test - public void useFsync() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseFsync(boolValue); - assertThat(opt.useFsync()).isEqualTo(boolValue); - } - } - - @Test - public void dbPaths() { - final List dbPaths = new ArrayList<>(); - dbPaths.add(new DbPath(Paths.get("/a"), 10)); - dbPaths.add(new DbPath(Paths.get("/b"), 100)); - dbPaths.add(new DbPath(Paths.get("/c"), 1000)); - - try(final DBOptions opt = new DBOptions()) { - assertThat(opt.dbPaths()).isEqualTo(Collections.emptyList()); - - opt.setDbPaths(dbPaths); - - assertThat(opt.dbPaths()).isEqualTo(dbPaths); - } - } - - @Test - public void dbLogDir() { - try(final DBOptions opt = new DBOptions()) { - final String str = "path/to/DbLogDir"; - opt.setDbLogDir(str); - assertThat(opt.dbLogDir()).isEqualTo(str); - } - } - - @Test - public void walDir() { - try(final DBOptions opt = new DBOptions()) { - final String str = "path/to/WalDir"; - opt.setWalDir(str); - assertThat(opt.walDir()).isEqualTo(str); - } - } - - @Test - public void deleteObsoleteFilesPeriodMicros() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setDeleteObsoleteFilesPeriodMicros(longValue); - assertThat(opt.deleteObsoleteFilesPeriodMicros()).isEqualTo(longValue); - } - } - - @SuppressWarnings("deprecated") - @Test - public void maxBackgroundCompactions() { - try(final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxBackgroundCompactions(intValue); - assertThat(opt.maxBackgroundCompactions()).isEqualTo(intValue); - } - } - - @Test - public void maxSubcompactions() { - try (final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxSubcompactions(intValue); - assertThat(opt.maxSubcompactions()). - isEqualTo(intValue); - } - } - - @SuppressWarnings("deprecated") - @Test - public void maxBackgroundFlushes() { - try(final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxBackgroundFlushes(intValue); - assertThat(opt.maxBackgroundFlushes()).isEqualTo(intValue); - } - } - - @Test - public void maxBackgroundJobs() { - try (final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setMaxBackgroundJobs(intValue); - assertThat(opt.maxBackgroundJobs()).isEqualTo(intValue); - } - } - - @Test - public void maxLogFileSize() throws RocksDBException { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxLogFileSize(longValue); - assertThat(opt.maxLogFileSize()).isEqualTo(longValue); - } - } - - @Test - public void logFileTimeToRoll() throws RocksDBException { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setLogFileTimeToRoll(longValue); - assertThat(opt.logFileTimeToRoll()).isEqualTo(longValue); - } - } - - @Test - public void keepLogFileNum() throws RocksDBException { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setKeepLogFileNum(longValue); - assertThat(opt.keepLogFileNum()).isEqualTo(longValue); - } - } - - @Test - public void recycleLogFileNum() throws RocksDBException { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setRecycleLogFileNum(longValue); - assertThat(opt.recycleLogFileNum()).isEqualTo(longValue); - } - } - - @Test - public void maxManifestFileSize() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxManifestFileSize(longValue); - assertThat(opt.maxManifestFileSize()).isEqualTo(longValue); - } - } - - @Test - public void tableCacheNumshardbits() { - try(final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setTableCacheNumshardbits(intValue); - assertThat(opt.tableCacheNumshardbits()).isEqualTo(intValue); - } - } - - @Test - public void walSizeLimitMB() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setWalSizeLimitMB(longValue); - assertThat(opt.walSizeLimitMB()).isEqualTo(longValue); - } - } - - @Test - public void walTtlSeconds() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setWalTtlSeconds(longValue); - assertThat(opt.walTtlSeconds()).isEqualTo(longValue); - } - } - - @Test - public void manifestPreallocationSize() throws RocksDBException { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setManifestPreallocationSize(longValue); - assertThat(opt.manifestPreallocationSize()).isEqualTo(longValue); - } - } - - @Test - public void useDirectReads() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseDirectReads(boolValue); - assertThat(opt.useDirectReads()).isEqualTo(boolValue); - } - } - - @Test - public void useDirectIoForFlushAndCompaction() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseDirectIoForFlushAndCompaction(boolValue); - assertThat(opt.useDirectIoForFlushAndCompaction()).isEqualTo(boolValue); - } - } - - @Test - public void allowFAllocate() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowFAllocate(boolValue); - assertThat(opt.allowFAllocate()).isEqualTo(boolValue); - } - } - - @Test - public void allowMmapReads() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowMmapReads(boolValue); - assertThat(opt.allowMmapReads()).isEqualTo(boolValue); - } - } - - @Test - public void allowMmapWrites() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowMmapWrites(boolValue); - assertThat(opt.allowMmapWrites()).isEqualTo(boolValue); - } - } - - @Test - public void isFdCloseOnExec() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setIsFdCloseOnExec(boolValue); - assertThat(opt.isFdCloseOnExec()).isEqualTo(boolValue); - } - } - - @Test - public void statsDumpPeriodSec() { - try(final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setStatsDumpPeriodSec(intValue); - assertThat(opt.statsDumpPeriodSec()).isEqualTo(intValue); - } - } - - @Test - public void statsPersistPeriodSec() { - try (final DBOptions opt = new DBOptions()) { - final int intValue = rand.nextInt(); - opt.setStatsPersistPeriodSec(intValue); - assertThat(opt.statsPersistPeriodSec()).isEqualTo(intValue); - } - } - - @Test - public void statsHistoryBufferSize() { - try (final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setStatsHistoryBufferSize(longValue); - assertThat(opt.statsHistoryBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void adviseRandomOnOpen() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAdviseRandomOnOpen(boolValue); - assertThat(opt.adviseRandomOnOpen()).isEqualTo(boolValue); - } - } - - @Test - public void dbWriteBufferSize() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setDbWriteBufferSize(longValue); - assertThat(opt.dbWriteBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void setWriteBufferManager() throws RocksDBException { - try (final DBOptions opt = new DBOptions(); - final Cache cache = new LRUCache(1 * 1024 * 1024); - final WriteBufferManager writeBufferManager = new WriteBufferManager(2000l, cache)) { - opt.setWriteBufferManager(writeBufferManager); - assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); - } - } - - @Test - public void setWriteBufferManagerWithZeroBufferSize() throws RocksDBException { - try (final DBOptions opt = new DBOptions(); - final Cache cache = new LRUCache(1 * 1024 * 1024); - final WriteBufferManager writeBufferManager = new WriteBufferManager(0l, cache)) { - opt.setWriteBufferManager(writeBufferManager); - assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); - } - } - - @Test - public void accessHintOnCompactionStart() { - try(final DBOptions opt = new DBOptions()) { - final AccessHint accessHint = AccessHint.SEQUENTIAL; - opt.setAccessHintOnCompactionStart(accessHint); - assertThat(opt.accessHintOnCompactionStart()).isEqualTo(accessHint); - } - } - - @Test - public void compactionReadaheadSize() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setCompactionReadaheadSize(longValue); - assertThat(opt.compactionReadaheadSize()).isEqualTo(longValue); - } - } - - @Test - public void randomAccessMaxBufferSize() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setRandomAccessMaxBufferSize(longValue); - assertThat(opt.randomAccessMaxBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void writableFileMaxBufferSize() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setWritableFileMaxBufferSize(longValue); - assertThat(opt.writableFileMaxBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void useAdaptiveMutex() { - try(final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseAdaptiveMutex(boolValue); - assertThat(opt.useAdaptiveMutex()).isEqualTo(boolValue); - } - } - - @Test - public void bytesPerSync() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setBytesPerSync(longValue); - assertThat(opt.bytesPerSync()).isEqualTo(longValue); - } - } - - @Test - public void walBytesPerSync() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setWalBytesPerSync(longValue); - assertThat(opt.walBytesPerSync()).isEqualTo(longValue); - } - } - - @Test - public void strictBytesPerSync() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.strictBytesPerSync()).isFalse(); - opt.setStrictBytesPerSync(true); - assertThat(opt.strictBytesPerSync()).isTrue(); - } - } - - @Test - public void enableThreadTracking() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setEnableThreadTracking(boolValue); - assertThat(opt.enableThreadTracking()).isEqualTo(boolValue); - } - } - - @Test - public void delayedWriteRate() { - try(final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setDelayedWriteRate(longValue); - assertThat(opt.delayedWriteRate()).isEqualTo(longValue); - } - } - - @Test - public void enablePipelinedWrite() { - try(final DBOptions opt = new DBOptions()) { - assertThat(opt.enablePipelinedWrite()).isFalse(); - opt.setEnablePipelinedWrite(true); - assertThat(opt.enablePipelinedWrite()).isTrue(); - } - } - - @Test - public void unordredWrite() { - try(final DBOptions opt = new DBOptions()) { - assertThat(opt.unorderedWrite()).isFalse(); - opt.setUnorderedWrite(true); - assertThat(opt.unorderedWrite()).isTrue(); - } - } - - @Test - public void allowConcurrentMemtableWrite() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowConcurrentMemtableWrite(boolValue); - assertThat(opt.allowConcurrentMemtableWrite()).isEqualTo(boolValue); - } - } - - @Test - public void enableWriteThreadAdaptiveYield() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setEnableWriteThreadAdaptiveYield(boolValue); - assertThat(opt.enableWriteThreadAdaptiveYield()).isEqualTo(boolValue); - } - } - - @Test - public void writeThreadMaxYieldUsec() { - try (final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setWriteThreadMaxYieldUsec(longValue); - assertThat(opt.writeThreadMaxYieldUsec()).isEqualTo(longValue); - } - } - - @Test - public void writeThreadSlowYieldUsec() { - try (final DBOptions opt = new DBOptions()) { - final long longValue = rand.nextLong(); - opt.setWriteThreadSlowYieldUsec(longValue); - assertThat(opt.writeThreadSlowYieldUsec()).isEqualTo(longValue); - } - } - - @Test - public void skipStatsUpdateOnDbOpen() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setSkipStatsUpdateOnDbOpen(boolValue); - assertThat(opt.skipStatsUpdateOnDbOpen()).isEqualTo(boolValue); - } - } - - @Test - public void walRecoveryMode() { - try (final DBOptions opt = new DBOptions()) { - for (final WALRecoveryMode walRecoveryMode : WALRecoveryMode.values()) { - opt.setWalRecoveryMode(walRecoveryMode); - assertThat(opt.walRecoveryMode()).isEqualTo(walRecoveryMode); - } - } - } - - @Test - public void allow2pc() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllow2pc(boolValue); - assertThat(opt.allow2pc()).isEqualTo(boolValue); - } - } - - @Test - public void rowCache() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.rowCache()).isNull(); - - try(final Cache lruCache = new LRUCache(1000)) { - opt.setRowCache(lruCache); - assertThat(opt.rowCache()).isEqualTo(lruCache); - } - - try(final Cache clockCache = new ClockCache(1000)) { - opt.setRowCache(clockCache); - assertThat(opt.rowCache()).isEqualTo(clockCache); - } - } - } - - @Test - public void walFilter() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.walFilter()).isNull(); - - try (final AbstractWalFilter walFilter = new AbstractWalFilter() { - @Override - public void columnFamilyLogNumberMap( - final Map cfLognumber, - final Map cfNameId) { - // no-op - } - - @Override - public LogRecordFoundResult logRecordFound(final long logNumber, - final String logFileName, final WriteBatch batch, - final WriteBatch newBatch) { - return new LogRecordFoundResult( - WalProcessingOption.CONTINUE_PROCESSING, false); - } - - @Override - public String name() { - return "test-wal-filter"; - } - }) { - opt.setWalFilter(walFilter); - assertThat(opt.walFilter()).isEqualTo(walFilter); - } - } - } - - @Test - public void failIfOptionsFileError() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setFailIfOptionsFileError(boolValue); - assertThat(opt.failIfOptionsFileError()).isEqualTo(boolValue); - } - } - - @Test - public void dumpMallocStats() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setDumpMallocStats(boolValue); - assertThat(opt.dumpMallocStats()).isEqualTo(boolValue); - } - } - - @Test - public void avoidFlushDuringRecovery() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAvoidFlushDuringRecovery(boolValue); - assertThat(opt.avoidFlushDuringRecovery()).isEqualTo(boolValue); - } - } - - @Test - public void avoidFlushDuringShutdown() { - try (final DBOptions opt = new DBOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAvoidFlushDuringShutdown(boolValue); - assertThat(opt.avoidFlushDuringShutdown()).isEqualTo(boolValue); - } - } - - @Test - public void allowIngestBehind() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.allowIngestBehind()).isFalse(); - opt.setAllowIngestBehind(true); - assertThat(opt.allowIngestBehind()).isTrue(); - } - } - - @Test - public void twoWriteQueues() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.twoWriteQueues()).isFalse(); - opt.setTwoWriteQueues(true); - assertThat(opt.twoWriteQueues()).isTrue(); - } - } - - @Test - public void manualWalFlush() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.manualWalFlush()).isFalse(); - opt.setManualWalFlush(true); - assertThat(opt.manualWalFlush()).isTrue(); - } - } - - @Test - public void atomicFlush() { - try (final DBOptions opt = new DBOptions()) { - assertThat(opt.atomicFlush()).isFalse(); - opt.setAtomicFlush(true); - assertThat(opt.atomicFlush()).isTrue(); - } - } - - @Test - public void rateLimiter() { - try(final DBOptions options = new DBOptions(); - final DBOptions anotherOptions = new DBOptions(); - final RateLimiter rateLimiter = new RateLimiter(1000, 100 * 1000, 1)) { - options.setRateLimiter(rateLimiter); - // Test with parameter initialization - anotherOptions.setRateLimiter( - new RateLimiter(1000)); - } - } - - @Test - public void sstFileManager() throws RocksDBException { - try (final DBOptions options = new DBOptions(); - final SstFileManager sstFileManager = - new SstFileManager(Env.getDefault())) { - options.setSstFileManager(sstFileManager); - } - } - - @Test - public void statistics() { - try(final DBOptions options = new DBOptions()) { - final Statistics statistics = options.statistics(); - assertThat(statistics).isNull(); - } - - try(final Statistics statistics = new Statistics(); - final DBOptions options = new DBOptions().setStatistics(statistics); - final Statistics stats = options.statistics()) { - assertThat(stats).isNotNull(); - } - } - - @Test - public void avoidUnnecessaryBlockingIO() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.avoidUnnecessaryBlockingIO()).isEqualTo(false); - assertThat(options.setAvoidUnnecessaryBlockingIO(true)).isEqualTo(options); - assertThat(options.avoidUnnecessaryBlockingIO()).isEqualTo(true); - } - } - - @Test - public void persistStatsToDisk() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.persistStatsToDisk()).isEqualTo(false); - assertThat(options.setPersistStatsToDisk(true)).isEqualTo(options); - assertThat(options.persistStatsToDisk()).isEqualTo(true); - } - } - - @Test - public void writeDbidToManifest() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.writeDbidToManifest()).isEqualTo(false); - assertThat(options.setWriteDbidToManifest(true)).isEqualTo(options); - assertThat(options.writeDbidToManifest()).isEqualTo(true); - } - } - - @Test - public void logReadaheadSize() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.logReadaheadSize()).isEqualTo(0); - final int size = 1024 * 1024 * 100; - assertThat(options.setLogReadaheadSize(size)).isEqualTo(options); - assertThat(options.logReadaheadSize()).isEqualTo(size); - } - } - - @Test - public void bestEffortsRecovery() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.bestEffortsRecovery()).isEqualTo(false); - assertThat(options.setBestEffortsRecovery(true)).isEqualTo(options); - assertThat(options.bestEffortsRecovery()).isEqualTo(true); - } - } - - @Test - public void maxBgerrorResumeCount() { - try (final DBOptions options = new DBOptions()) { - final int INT_MAX = 2147483647; - assertThat(options.maxBgerrorResumeCount()).isEqualTo(INT_MAX); - assertThat(options.setMaxBgErrorResumeCount(-1)).isEqualTo(options); - assertThat(options.maxBgerrorResumeCount()).isEqualTo(-1); - } - } - - @Test - public void bgerrorResumeRetryInterval() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.bgerrorResumeRetryInterval()).isEqualTo(1000000); - final long newRetryInterval = 24 * 3600 * 1000000L; - assertThat(options.setBgerrorResumeRetryInterval(newRetryInterval)).isEqualTo(options); - assertThat(options.bgerrorResumeRetryInterval()).isEqualTo(newRetryInterval); - } - } - - @Test - public void maxWriteBatchGroupSizeBytes() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.maxWriteBatchGroupSizeBytes()).isEqualTo(1024 * 1024); - final long size = 1024 * 1024 * 1024 * 10L; - assertThat(options.setMaxWriteBatchGroupSizeBytes(size)).isEqualTo(options); - assertThat(options.maxWriteBatchGroupSizeBytes()).isEqualTo(size); - } - } - - @Test - public void skipCheckingSstFileSizesOnDbOpen() { - try (final DBOptions options = new DBOptions()) { - assertThat(options.skipCheckingSstFileSizesOnDbOpen()).isEqualTo(false); - assertThat(options.setSkipCheckingSstFileSizesOnDbOpen(true)).isEqualTo(options); - assertThat(options.skipCheckingSstFileSizesOnDbOpen()).isEqualTo(true); - } - } - - @Test - public void eventListeners() { - final AtomicBoolean wasCalled1 = new AtomicBoolean(); - final AtomicBoolean wasCalled2 = new AtomicBoolean(); - try (final DBOptions options = new DBOptions(); - final AbstractEventListener el1 = - new AbstractEventListener() { - @Override - public void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo) { - wasCalled1.set(true); - } - }; - final AbstractEventListener el2 = - new AbstractEventListener() { - @Override - public void onMemTableSealed(final MemTableInfo memTableInfo) { - wasCalled2.set(true); - } - }) { - assertThat(options.setListeners(Arrays.asList(el1, el2))).isEqualTo(options); - List listeners = options.listeners(); - assertEquals(el1, listeners.get(0)); - assertEquals(el2, listeners.get(1)); - options.setListeners(Collections.emptyList()); - listeners.get(0).onTableFileDeleted(null); - assertTrue(wasCalled1.get()); - listeners.get(1).onMemTableSealed(null); - assertTrue(wasCalled2.get()); - List listeners2 = options.listeners(); - assertNotNull(listeners2); - assertEquals(0, listeners2.size()); - } - } -} diff --git a/java/src/test/java/org/rocksdb/DefaultEnvTest.java b/java/src/test/java/org/rocksdb/DefaultEnvTest.java deleted file mode 100644 index 3fb563ecb..000000000 --- a/java/src/test/java/org/rocksdb/DefaultEnvTest.java +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.Collection; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultEnvTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void backgroundThreads() { - try (final Env defaultEnv = RocksEnv.getDefault()) { - defaultEnv.setBackgroundThreads(5, Priority.BOTTOM); - assertThat(defaultEnv.getBackgroundThreads(Priority.BOTTOM)).isEqualTo(5); - - defaultEnv.setBackgroundThreads(5); - assertThat(defaultEnv.getBackgroundThreads(Priority.LOW)).isEqualTo(5); - - defaultEnv.setBackgroundThreads(5, Priority.LOW); - assertThat(defaultEnv.getBackgroundThreads(Priority.LOW)).isEqualTo(5); - - defaultEnv.setBackgroundThreads(5, Priority.HIGH); - assertThat(defaultEnv.getBackgroundThreads(Priority.HIGH)).isEqualTo(5); - } - } - - @Test - public void threadPoolQueueLen() { - try (final Env defaultEnv = RocksEnv.getDefault()) { - assertThat(defaultEnv.getThreadPoolQueueLen(Priority.BOTTOM)).isEqualTo(0); - assertThat(defaultEnv.getThreadPoolQueueLen(Priority.LOW)).isEqualTo(0); - assertThat(defaultEnv.getThreadPoolQueueLen(Priority.HIGH)).isEqualTo(0); - } - } - - @Test - public void incBackgroundThreadsIfNeeded() { - try (final Env defaultEnv = RocksEnv.getDefault()) { - defaultEnv.incBackgroundThreadsIfNeeded(20, Priority.BOTTOM); - assertThat(defaultEnv.getBackgroundThreads(Priority.BOTTOM)).isGreaterThanOrEqualTo(20); - - defaultEnv.incBackgroundThreadsIfNeeded(20, Priority.LOW); - assertThat(defaultEnv.getBackgroundThreads(Priority.LOW)).isGreaterThanOrEqualTo(20); - - defaultEnv.incBackgroundThreadsIfNeeded(20, Priority.HIGH); - assertThat(defaultEnv.getBackgroundThreads(Priority.HIGH)).isGreaterThanOrEqualTo(20); - } - } - - @Test - public void lowerThreadPoolIOPriority() { - try (final Env defaultEnv = RocksEnv.getDefault()) { - defaultEnv.lowerThreadPoolIOPriority(Priority.BOTTOM); - - defaultEnv.lowerThreadPoolIOPriority(Priority.LOW); - - defaultEnv.lowerThreadPoolIOPriority(Priority.HIGH); - } - } - - @Test - public void lowerThreadPoolCPUPriority() { - try (final Env defaultEnv = RocksEnv.getDefault()) { - defaultEnv.lowerThreadPoolCPUPriority(Priority.BOTTOM); - - defaultEnv.lowerThreadPoolCPUPriority(Priority.LOW); - - defaultEnv.lowerThreadPoolCPUPriority(Priority.HIGH); - } - } - - @Test - public void threadList() throws RocksDBException { - try (final Env defaultEnv = RocksEnv.getDefault()) { - final Collection threadList = defaultEnv.getThreadList(); - assertThat(threadList.size()).isGreaterThan(0); - } - } - - @Test - public void threadList_integration() throws RocksDBException { - try (final Env env = RocksEnv.getDefault(); - final Options opt = new Options() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true) - .setEnv(env)) { - // open database - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - final List threadList = env.getThreadList(); - assertThat(threadList.size()).isGreaterThan(0); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/DirectSliceTest.java b/java/src/test/java/org/rocksdb/DirectSliceTest.java deleted file mode 100644 index 67385345c..000000000 --- a/java/src/test/java/org/rocksdb/DirectSliceTest.java +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import java.nio.ByteBuffer; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DirectSliceTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void directSlice() { - try(final DirectSlice directSlice = new DirectSlice("abc"); - final DirectSlice otherSlice = new DirectSlice("abc")) { - assertThat(directSlice.toString()).isEqualTo("abc"); - // clear first slice - directSlice.clear(); - assertThat(directSlice.toString()).isEmpty(); - // get first char in otherslice - assertThat(otherSlice.get(0)).isEqualTo("a".getBytes()[0]); - // remove prefix - otherSlice.removePrefix(1); - assertThat(otherSlice.toString()).isEqualTo("bc"); - } - } - - @Test - public void directSliceWithByteBuffer() { - final byte[] data = "Some text".getBytes(); - final ByteBuffer buffer = ByteBuffer.allocateDirect(data.length + 1); - buffer.put(data); - buffer.put(data.length, (byte)0); - - try(final DirectSlice directSlice = new DirectSlice(buffer)) { - assertThat(directSlice.toString()).isEqualTo("Some text"); - } - } - - @Test - public void directSliceWithByteBufferAndLength() { - final byte[] data = "Some text".getBytes(); - final ByteBuffer buffer = ByteBuffer.allocateDirect(data.length); - buffer.put(data); - try(final DirectSlice directSlice = new DirectSlice(buffer, 4)) { - assertThat(directSlice.toString()).isEqualTo("Some"); - } - } - - @Test(expected = IllegalArgumentException.class) - public void directSliceInitWithoutDirectAllocation() { - final byte[] data = "Some text".getBytes(); - final ByteBuffer buffer = ByteBuffer.wrap(data); - try(final DirectSlice directSlice = new DirectSlice(buffer)) { - //no-op - } - } - - @Test(expected = IllegalArgumentException.class) - public void directSlicePrefixInitWithoutDirectAllocation() { - final byte[] data = "Some text".getBytes(); - final ByteBuffer buffer = ByteBuffer.wrap(data); - try(final DirectSlice directSlice = new DirectSlice(buffer, 4)) { - //no-op - } - } - - @Test - public void directSliceClear() { - try(final DirectSlice directSlice = new DirectSlice("abc")) { - assertThat(directSlice.toString()).isEqualTo("abc"); - directSlice.clear(); - assertThat(directSlice.toString()).isEmpty(); - directSlice.clear(); // make sure we don't double-free - } - } - - @Test - public void directSliceRemovePrefix() { - try(final DirectSlice directSlice = new DirectSlice("abc")) { - assertThat(directSlice.toString()).isEqualTo("abc"); - directSlice.removePrefix(1); - assertThat(directSlice.toString()).isEqualTo("bc"); - } - } -} diff --git a/java/src/test/java/org/rocksdb/EnvOptionsTest.java b/java/src/test/java/org/rocksdb/EnvOptionsTest.java deleted file mode 100644 index 0f3d8e234..000000000 --- a/java/src/test/java/org/rocksdb/EnvOptionsTest.java +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; - -public class EnvOptionsTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = new RocksNativeLibraryResource(); - - public static final Random rand = PlatformRandomHelper.getPlatformSpecificRandomFactory(); - - @Test - public void dbOptionsConstructor() { - final long compactionReadaheadSize = 4 * 1024 * 1024; - try (final DBOptions dbOptions = new DBOptions() - .setCompactionReadaheadSize(compactionReadaheadSize)) { - try (final EnvOptions envOptions = new EnvOptions(dbOptions)) { - assertThat(envOptions.compactionReadaheadSize()) - .isEqualTo(compactionReadaheadSize); - } - } - } - - @Test - public void useMmapReads() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setUseMmapReads(boolValue); - assertThat(envOptions.useMmapReads()).isEqualTo(boolValue); - } - } - - @Test - public void useMmapWrites() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setUseMmapWrites(boolValue); - assertThat(envOptions.useMmapWrites()).isEqualTo(boolValue); - } - } - - @Test - public void useDirectReads() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setUseDirectReads(boolValue); - assertThat(envOptions.useDirectReads()).isEqualTo(boolValue); - } - } - - @Test - public void useDirectWrites() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setUseDirectWrites(boolValue); - assertThat(envOptions.useDirectWrites()).isEqualTo(boolValue); - } - } - - @Test - public void allowFallocate() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setAllowFallocate(boolValue); - assertThat(envOptions.allowFallocate()).isEqualTo(boolValue); - } - } - - @Test - public void setFdCloexecs() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setSetFdCloexec(boolValue); - assertThat(envOptions.setFdCloexec()).isEqualTo(boolValue); - } - } - - @Test - public void bytesPerSync() { - try (final EnvOptions envOptions = new EnvOptions()) { - final long longValue = rand.nextLong(); - envOptions.setBytesPerSync(longValue); - assertThat(envOptions.bytesPerSync()).isEqualTo(longValue); - } - } - - @Test - public void fallocateWithKeepSize() { - try (final EnvOptions envOptions = new EnvOptions()) { - final boolean boolValue = rand.nextBoolean(); - envOptions.setFallocateWithKeepSize(boolValue); - assertThat(envOptions.fallocateWithKeepSize()).isEqualTo(boolValue); - } - } - - @Test - public void compactionReadaheadSize() { - try (final EnvOptions envOptions = new EnvOptions()) { - final int intValue = rand.nextInt(2147483647); - envOptions.setCompactionReadaheadSize(intValue); - assertThat(envOptions.compactionReadaheadSize()).isEqualTo(intValue); - } - } - - @Test - public void randomAccessMaxBufferSize() { - try (final EnvOptions envOptions = new EnvOptions()) { - final int intValue = rand.nextInt(2147483647); - envOptions.setRandomAccessMaxBufferSize(intValue); - assertThat(envOptions.randomAccessMaxBufferSize()).isEqualTo(intValue); - } - } - - @Test - public void writableFileMaxBufferSize() { - try (final EnvOptions envOptions = new EnvOptions()) { - final int intValue = rand.nextInt(2147483647); - envOptions.setWritableFileMaxBufferSize(intValue); - assertThat(envOptions.writableFileMaxBufferSize()).isEqualTo(intValue); - } - } - - @Test - public void rateLimiter() { - try (final EnvOptions envOptions = new EnvOptions(); - final RateLimiter rateLimiter1 = new RateLimiter(1000, 100 * 1000, 1)) { - envOptions.setRateLimiter(rateLimiter1); - assertThat(envOptions.rateLimiter()).isEqualTo(rateLimiter1); - - try(final RateLimiter rateLimiter2 = new RateLimiter(1000)) { - envOptions.setRateLimiter(rateLimiter2); - assertThat(envOptions.rateLimiter()).isEqualTo(rateLimiter2); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/EventListenerTest.java b/java/src/test/java/org/rocksdb/EventListenerTest.java deleted file mode 100644 index 93ea19c2f..000000000 --- a/java/src/test/java/org/rocksdb/EventListenerTest.java +++ /dev/null @@ -1,725 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import org.assertj.core.api.AbstractObjectAssert; -import org.assertj.core.api.ObjectAssert; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.AbstractEventListener.EnabledEventCallback; -import org.rocksdb.test.TestableEventListener; - -public class EventListenerTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - public static final Random rand = PlatformRandomHelper.getPlatformSpecificRandomFactory(); - - void flushDb(final AbstractEventListener el, final AtomicBoolean wasCbCalled) - throws RocksDBException { - try (final Options opt = - new Options().setCreateIfMissing(true).setListeners(Collections.singletonList(el)); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - final byte[] value = new byte[24]; - rand.nextBytes(value); - db.put("testKey".getBytes(), value); - db.flush(new FlushOptions()); - assertThat(wasCbCalled.get()).isTrue(); - } - } - - @Test - public void onFlushCompleted() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onFlushCompletedListener = new AbstractEventListener() { - @Override - public void onFlushCompleted(final RocksDB rocksDb, final FlushJobInfo flushJobInfo) { - assertThat(flushJobInfo.getColumnFamilyName()).isNotNull(); - assertThat(flushJobInfo.getFlushReason()).isEqualTo(FlushReason.MANUAL_FLUSH); - wasCbCalled.set(true); - } - }; - flushDb(onFlushCompletedListener, wasCbCalled); - } - - @Test - public void onFlushBegin() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onFlushBeginListener = new AbstractEventListener() { - @Override - public void onFlushBegin(final RocksDB rocksDb, final FlushJobInfo flushJobInfo) { - assertThat(flushJobInfo.getColumnFamilyName()).isNotNull(); - assertThat(flushJobInfo.getFlushReason()).isEqualTo(FlushReason.MANUAL_FLUSH); - wasCbCalled.set(true); - } - }; - flushDb(onFlushBeginListener, wasCbCalled); - } - - void deleteTableFile(final AbstractEventListener el, final AtomicBoolean wasCbCalled) - throws RocksDBException { - try (final Options opt = - new Options().setCreateIfMissing(true).setListeners(Collections.singletonList(el)); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - final byte[] value = new byte[24]; - rand.nextBytes(value); - db.put("testKey".getBytes(), value); - final RocksDB.LiveFiles liveFiles = db.getLiveFiles(); - assertThat(liveFiles).isNotNull(); - assertThat(liveFiles.files).isNotNull(); - assertThat(liveFiles.files.isEmpty()).isFalse(); - db.deleteFile(liveFiles.files.get(0)); - assertThat(wasCbCalled.get()).isTrue(); - } - } - - @Test - public void onTableFileDeleted() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onTableFileDeletedListener = new AbstractEventListener() { - @Override - public void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo) { - assertThat(tableFileDeletionInfo.getDbName()).isNotNull(); - wasCbCalled.set(true); - } - }; - deleteTableFile(onTableFileDeletedListener, wasCbCalled); - } - - void compactRange(final AbstractEventListener el, final AtomicBoolean wasCbCalled) - throws RocksDBException { - try (final Options opt = - new Options().setCreateIfMissing(true).setListeners(Collections.singletonList(el)); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - final byte[] value = new byte[24]; - rand.nextBytes(value); - db.put("testKey".getBytes(), value); - db.compactRange(); - assertThat(wasCbCalled.get()).isTrue(); - } - } - - @Test - public void onCompactionBegin() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onCompactionBeginListener = new AbstractEventListener() { - @Override - public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - assertThat(compactionJobInfo.compactionReason()) - .isEqualTo(CompactionReason.kManualCompaction); - wasCbCalled.set(true); - } - }; - compactRange(onCompactionBeginListener, wasCbCalled); - } - - @Test - public void onCompactionCompleted() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onCompactionCompletedListener = new AbstractEventListener() { - @Override - public void onCompactionCompleted( - final RocksDB db, final CompactionJobInfo compactionJobInfo) { - assertThat(compactionJobInfo.compactionReason()) - .isEqualTo(CompactionReason.kManualCompaction); - wasCbCalled.set(true); - } - }; - compactRange(onCompactionCompletedListener, wasCbCalled); - } - - @Test - public void onTableFileCreated() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onTableFileCreatedListener = new AbstractEventListener() { - @Override - public void onTableFileCreated(final TableFileCreationInfo tableFileCreationInfo) { - assertThat(tableFileCreationInfo.getReason()).isEqualTo(TableFileCreationReason.FLUSH); - wasCbCalled.set(true); - } - }; - flushDb(onTableFileCreatedListener, wasCbCalled); - } - - @Test - public void onTableFileCreationStarted() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onTableFileCreationStartedListener = new AbstractEventListener() { - @Override - public void onTableFileCreationStarted( - final TableFileCreationBriefInfo tableFileCreationBriefInfo) { - assertThat(tableFileCreationBriefInfo.getReason()).isEqualTo(TableFileCreationReason.FLUSH); - wasCbCalled.set(true); - } - }; - flushDb(onTableFileCreationStartedListener, wasCbCalled); - } - - void deleteColumnFamilyHandle(final AbstractEventListener el, final AtomicBoolean wasCbCalled) - throws RocksDBException { - try (final Options opt = - new Options().setCreateIfMissing(true).setListeners(Collections.singletonList(el)); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - final byte[] value = new byte[24]; - rand.nextBytes(value); - db.put("testKey".getBytes(), value); - ColumnFamilyHandle columnFamilyHandle = db.getDefaultColumnFamily(); - columnFamilyHandle.close(); - assertThat(wasCbCalled.get()).isTrue(); - } - } - - @Test - public void onColumnFamilyHandleDeletionStarted() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onColumnFamilyHandleDeletionStartedListener = - new AbstractEventListener() { - @Override - public void onColumnFamilyHandleDeletionStarted( - final ColumnFamilyHandle columnFamilyHandle) { - assertThat(columnFamilyHandle).isNotNull(); - wasCbCalled.set(true); - } - }; - deleteColumnFamilyHandle(onColumnFamilyHandleDeletionStartedListener, wasCbCalled); - } - - void ingestExternalFile(final AbstractEventListener el, final AtomicBoolean wasCbCalled) - throws RocksDBException { - try (final Options opt = - new Options().setCreateIfMissing(true).setListeners(Collections.singletonList(el)); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - final String uuid = UUID.randomUUID().toString(); - final SstFileWriter sstFileWriter = new SstFileWriter(new EnvOptions(), opt); - final Path externalFilePath = Paths.get(db.getName(), uuid); - sstFileWriter.open(externalFilePath.toString()); - sstFileWriter.put("testKey".getBytes(), uuid.getBytes()); - sstFileWriter.finish(); - db.ingestExternalFile( - Collections.singletonList(externalFilePath.toString()), new IngestExternalFileOptions()); - assertThat(wasCbCalled.get()).isTrue(); - } - } - - @Test - public void onExternalFileIngested() throws RocksDBException { - final AtomicBoolean wasCbCalled = new AtomicBoolean(); - final AbstractEventListener onExternalFileIngestedListener = new AbstractEventListener() { - @Override - public void onExternalFileIngested( - final RocksDB db, final ExternalFileIngestionInfo externalFileIngestionInfo) { - assertThat(db).isNotNull(); - wasCbCalled.set(true); - } - }; - ingestExternalFile(onExternalFileIngestedListener, wasCbCalled); - } - - @Test - public void testAllCallbacksInvocation() { - final long TEST_LONG_VAL = -1; - // Expected test data objects - final Map userCollectedPropertiesTestData = - Collections.singletonMap("key", "value"); - final Map readablePropertiesTestData = Collections.singletonMap("key", "value"); - final TableProperties tablePropertiesTestData = new TableProperties(TEST_LONG_VAL, - TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, - TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, - TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, - TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, "columnFamilyName".getBytes(), - "filterPolicyName", "comparatorName", "mergeOperatorName", "prefixExtractorName", - "propertyCollectorsNames", "compressionName", userCollectedPropertiesTestData, - readablePropertiesTestData); - final FlushJobInfo flushJobInfoTestData = new FlushJobInfo(Integer.MAX_VALUE, - "testColumnFamily", "/file/path", TEST_LONG_VAL, Integer.MAX_VALUE, true, true, - TEST_LONG_VAL, TEST_LONG_VAL, tablePropertiesTestData, (byte) 0x0a); - final Status statusTestData = new Status(Status.Code.Incomplete, Status.SubCode.NoSpace, null); - final TableFileDeletionInfo tableFileDeletionInfoTestData = - new TableFileDeletionInfo("dbName", "/file/path", Integer.MAX_VALUE, statusTestData); - final TableFileCreationInfo tableFileCreationInfoTestData = - new TableFileCreationInfo(TEST_LONG_VAL, tablePropertiesTestData, statusTestData, "dbName", - "columnFamilyName", "/file/path", Integer.MAX_VALUE, (byte) 0x03); - final TableFileCreationBriefInfo tableFileCreationBriefInfoTestData = - new TableFileCreationBriefInfo( - "dbName", "columnFamilyName", "/file/path", Integer.MAX_VALUE, (byte) 0x03); - final MemTableInfo memTableInfoTestData = new MemTableInfo( - "columnFamilyName", TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL, TEST_LONG_VAL); - final FileOperationInfo fileOperationInfoTestData = new FileOperationInfo("/file/path", - TEST_LONG_VAL, TEST_LONG_VAL, 1_600_699_420_000_000_000L, 5_000_000_000L, statusTestData); - final WriteStallInfo writeStallInfoTestData = - new WriteStallInfo("columnFamilyName", (byte) 0x0, (byte) 0x1); - final ExternalFileIngestionInfo externalFileIngestionInfoTestData = - new ExternalFileIngestionInfo("columnFamilyName", "/external/file/path", - "/internal/file/path", TEST_LONG_VAL, tablePropertiesTestData); - - final CapturingTestableEventListener listener = new CapturingTestableEventListener() { - @Override - public void onFlushCompleted(final RocksDB db, final FlushJobInfo flushJobInfo) { - super.onFlushCompleted(db, flushJobInfo); - assertThat(flushJobInfo).isEqualTo(flushJobInfoTestData); - } - - @Override - public void onFlushBegin(final RocksDB db, final FlushJobInfo flushJobInfo) { - super.onFlushBegin(db, flushJobInfo); - assertThat(flushJobInfo).isEqualTo(flushJobInfoTestData); - } - - @Override - public void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo) { - super.onTableFileDeleted(tableFileDeletionInfo); - assertThat(tableFileDeletionInfo).isEqualTo(tableFileDeletionInfoTestData); - } - - @Override - public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - super.onCompactionBegin(db, compactionJobInfo); - assertThat(new String(compactionJobInfo.columnFamilyName(), StandardCharsets.UTF_8)) - .isEqualTo("compactionColumnFamily"); - assertThat(compactionJobInfo.status()).isEqualTo(statusTestData); - assertThat(compactionJobInfo.threadId()).isEqualTo(TEST_LONG_VAL); - assertThat(compactionJobInfo.jobId()).isEqualTo(Integer.MAX_VALUE); - assertThat(compactionJobInfo.baseInputLevel()).isEqualTo(Integer.MAX_VALUE); - assertThat(compactionJobInfo.outputLevel()).isEqualTo(Integer.MAX_VALUE); - assertThat(compactionJobInfo.inputFiles()) - .isEqualTo(Collections.singletonList("inputFile.sst")); - assertThat(compactionJobInfo.outputFiles()) - .isEqualTo(Collections.singletonList("outputFile.sst")); - assertThat(compactionJobInfo.tableProperties()) - .isEqualTo(Collections.singletonMap("tableProperties", tablePropertiesTestData)); - assertThat(compactionJobInfo.compactionReason()).isEqualTo(CompactionReason.kFlush); - assertThat(compactionJobInfo.compression()).isEqualTo(CompressionType.SNAPPY_COMPRESSION); - } - - @Override - public void onCompactionCompleted( - final RocksDB db, final CompactionJobInfo compactionJobInfo) { - super.onCompactionCompleted(db, compactionJobInfo); - assertThat(new String(compactionJobInfo.columnFamilyName())) - .isEqualTo("compactionColumnFamily"); - assertThat(compactionJobInfo.status()).isEqualTo(statusTestData); - assertThat(compactionJobInfo.threadId()).isEqualTo(TEST_LONG_VAL); - assertThat(compactionJobInfo.jobId()).isEqualTo(Integer.MAX_VALUE); - assertThat(compactionJobInfo.baseInputLevel()).isEqualTo(Integer.MAX_VALUE); - assertThat(compactionJobInfo.outputLevel()).isEqualTo(Integer.MAX_VALUE); - assertThat(compactionJobInfo.inputFiles()) - .isEqualTo(Collections.singletonList("inputFile.sst")); - assertThat(compactionJobInfo.outputFiles()) - .isEqualTo(Collections.singletonList("outputFile.sst")); - assertThat(compactionJobInfo.tableProperties()) - .isEqualTo(Collections.singletonMap("tableProperties", tablePropertiesTestData)); - assertThat(compactionJobInfo.compactionReason()).isEqualTo(CompactionReason.kFlush); - assertThat(compactionJobInfo.compression()).isEqualTo(CompressionType.SNAPPY_COMPRESSION); - } - - @Override - public void onTableFileCreated(final TableFileCreationInfo tableFileCreationInfo) { - super.onTableFileCreated(tableFileCreationInfo); - assertThat(tableFileCreationInfo).isEqualTo(tableFileCreationInfoTestData); - } - - @Override - public void onTableFileCreationStarted( - final TableFileCreationBriefInfo tableFileCreationBriefInfo) { - super.onTableFileCreationStarted(tableFileCreationBriefInfo); - assertThat(tableFileCreationBriefInfo).isEqualTo(tableFileCreationBriefInfoTestData); - } - - @Override - public void onMemTableSealed(final MemTableInfo memTableInfo) { - super.onMemTableSealed(memTableInfo); - assertThat(memTableInfo).isEqualTo(memTableInfoTestData); - } - - @Override - public void onColumnFamilyHandleDeletionStarted(final ColumnFamilyHandle columnFamilyHandle) { - super.onColumnFamilyHandleDeletionStarted(columnFamilyHandle); - } - - @Override - public void onExternalFileIngested( - final RocksDB db, final ExternalFileIngestionInfo externalFileIngestionInfo) { - super.onExternalFileIngested(db, externalFileIngestionInfo); - assertThat(externalFileIngestionInfo).isEqualTo(externalFileIngestionInfoTestData); - } - - @Override - public void onBackgroundError( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError) { - super.onBackgroundError(backgroundErrorReason, backgroundError); - } - - @Override - public void onStallConditionsChanged(final WriteStallInfo writeStallInfo) { - super.onStallConditionsChanged(writeStallInfo); - assertThat(writeStallInfo).isEqualTo(writeStallInfoTestData); - } - - @Override - public void onFileReadFinish(final FileOperationInfo fileOperationInfo) { - super.onFileReadFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public void onFileWriteFinish(final FileOperationInfo fileOperationInfo) { - super.onFileWriteFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public void onFileFlushFinish(final FileOperationInfo fileOperationInfo) { - super.onFileFlushFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public void onFileSyncFinish(final FileOperationInfo fileOperationInfo) { - super.onFileSyncFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public void onFileRangeSyncFinish(final FileOperationInfo fileOperationInfo) { - super.onFileRangeSyncFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public void onFileTruncateFinish(final FileOperationInfo fileOperationInfo) { - super.onFileTruncateFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public void onFileCloseFinish(final FileOperationInfo fileOperationInfo) { - super.onFileCloseFinish(fileOperationInfo); - assertThat(fileOperationInfo).isEqualTo(fileOperationInfoTestData); - } - - @Override - public boolean shouldBeNotifiedOnFileIO() { - super.shouldBeNotifiedOnFileIO(); - return false; - } - - @Override - public boolean onErrorRecoveryBegin( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError) { - super.onErrorRecoveryBegin(backgroundErrorReason, backgroundError); - assertThat(backgroundErrorReason).isEqualTo(BackgroundErrorReason.FLUSH); - assertThat(backgroundError).isEqualTo(statusTestData); - return true; - } - - @Override - public void onErrorRecoveryCompleted(final Status oldBackgroundError) { - super.onErrorRecoveryCompleted(oldBackgroundError); - assertThat(oldBackgroundError).isEqualTo(statusTestData); - } - }; - - // test action - listener.invokeAllCallbacks(); - - // assert - assertAllEventsCalled(listener); - - assertNoCallbackErrors(listener); - } - - @Test - public void testEnabledCallbacks() { - final EnabledEventCallback[] enabledEvents = { - EnabledEventCallback.ON_MEMTABLE_SEALED, EnabledEventCallback.ON_ERROR_RECOVERY_COMPLETED}; - - final CapturingTestableEventListener listener = - new CapturingTestableEventListener(enabledEvents); - - // test action - listener.invokeAllCallbacks(); - - // assert - assertEventsCalled(listener, enabledEvents); - } - - private static void assertAllEventsCalled( - final CapturingTestableEventListener capturingTestableEventListener) { - assertEventsCalled(capturingTestableEventListener, EnumSet.allOf(EnabledEventCallback.class)); - } - - private static void assertEventsCalled( - final CapturingTestableEventListener capturingTestableEventListener, - final EnabledEventCallback[] expected) { - assertEventsCalled(capturingTestableEventListener, EnumSet.copyOf(Arrays.asList(expected))); - } - - private static void assertNoCallbackErrors( - final CapturingTestableEventListener capturingTestableEventListener) { - for (AssertionError error : capturingTestableEventListener.capturedAssertionErrors) { - throw new Error("An assertion failed in callback", error); - } - } - - private static void assertEventsCalled( - final CapturingTestableEventListener capturingTestableEventListener, - final EnumSet expected) { - final ListenerEvents capturedEvents = capturingTestableEventListener.capturedListenerEvents; - - assertThat(capturedEvents.flushCompleted) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FLUSH_COMPLETED)); - assertThat(capturedEvents.flushBegin) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FLUSH_BEGIN)); - assertThat(capturedEvents.tableFileDeleted) - .isEqualTo(expected.contains(EnabledEventCallback.ON_TABLE_FILE_DELETED)); - assertThat(capturedEvents.compactionBegin) - .isEqualTo(expected.contains(EnabledEventCallback.ON_COMPACTION_BEGIN)); - assertThat(capturedEvents.compactionCompleted) - .isEqualTo(expected.contains(EnabledEventCallback.ON_COMPACTION_COMPLETED)); - assertThat(capturedEvents.tableFileCreated) - .isEqualTo(expected.contains(EnabledEventCallback.ON_TABLE_FILE_CREATED)); - assertThat(capturedEvents.tableFileCreationStarted) - .isEqualTo(expected.contains(EnabledEventCallback.ON_TABLE_FILE_CREATION_STARTED)); - assertThat(capturedEvents.memTableSealed) - .isEqualTo(expected.contains(EnabledEventCallback.ON_MEMTABLE_SEALED)); - assertThat(capturedEvents.columnFamilyHandleDeletionStarted) - .isEqualTo( - expected.contains(EnabledEventCallback.ON_COLUMN_FAMILY_HANDLE_DELETION_STARTED)); - assertThat(capturedEvents.externalFileIngested) - .isEqualTo(expected.contains(EnabledEventCallback.ON_EXTERNAL_FILE_INGESTED)); - assertThat(capturedEvents.backgroundError) - .isEqualTo(expected.contains(EnabledEventCallback.ON_BACKGROUND_ERROR)); - assertThat(capturedEvents.stallConditionsChanged) - .isEqualTo(expected.contains(EnabledEventCallback.ON_STALL_CONDITIONS_CHANGED)); - assertThat(capturedEvents.fileReadFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_READ_FINISH)); - assertThat(capturedEvents.fileWriteFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_WRITE_FINISH)); - assertThat(capturedEvents.fileFlushFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_FLUSH_FINISH)); - assertThat(capturedEvents.fileSyncFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_SYNC_FINISH)); - assertThat(capturedEvents.fileRangeSyncFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_RANGE_SYNC_FINISH)); - assertThat(capturedEvents.fileTruncateFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_TRUNCATE_FINISH)); - assertThat(capturedEvents.fileCloseFinish) - .isEqualTo(expected.contains(EnabledEventCallback.ON_FILE_CLOSE_FINISH)); - assertThat(capturedEvents.shouldBeNotifiedOnFileIO) - .isEqualTo(expected.contains(EnabledEventCallback.SHOULD_BE_NOTIFIED_ON_FILE_IO)); - assertThat(capturedEvents.errorRecoveryBegin) - .isEqualTo(expected.contains(EnabledEventCallback.ON_ERROR_RECOVERY_BEGIN)); - assertThat(capturedEvents.errorRecoveryCompleted) - .isEqualTo(expected.contains(EnabledEventCallback.ON_ERROR_RECOVERY_COMPLETED)); - assertThat(capturedEvents.errorRecoveryCompleted) - .isEqualTo(expected.contains(EnabledEventCallback.ON_ERROR_RECOVERY_COMPLETED)); - } - - /** - * Members are volatile as they may be written - * and read by different threads. - */ - private static class ListenerEvents { - volatile boolean flushCompleted; - volatile boolean flushBegin; - volatile boolean tableFileDeleted; - volatile boolean compactionBegin; - volatile boolean compactionCompleted; - volatile boolean tableFileCreated; - volatile boolean tableFileCreationStarted; - volatile boolean memTableSealed; - volatile boolean columnFamilyHandleDeletionStarted; - volatile boolean externalFileIngested; - volatile boolean backgroundError; - volatile boolean stallConditionsChanged; - volatile boolean fileReadFinish; - volatile boolean fileWriteFinish; - volatile boolean fileFlushFinish; - volatile boolean fileSyncFinish; - volatile boolean fileRangeSyncFinish; - volatile boolean fileTruncateFinish; - volatile boolean fileCloseFinish; - volatile boolean shouldBeNotifiedOnFileIO; - volatile boolean errorRecoveryBegin; - volatile boolean errorRecoveryCompleted; - } - - private static class CapturingObjectAssert extends ObjectAssert { - private final List assertionErrors; - public CapturingObjectAssert(T t, List assertionErrors) { - super(t); - this.assertionErrors = assertionErrors; - } - - @Override - public ObjectAssert isEqualTo(Object other) { - try { - return super.isEqualTo(other); - } catch (AssertionError error) { - assertionErrors.add(error); - throw error; - } - } - - @Override - public ObjectAssert isNotNull() { - try { - return super.isNotNull(); - } catch (AssertionError error) { - assertionErrors.add(error); - throw error; - } - } - } - - private static class CapturingTestableEventListener extends TestableEventListener { - final ListenerEvents capturedListenerEvents = new ListenerEvents(); - - final List capturedAssertionErrors = new ArrayList<>(); - - protected AbstractObjectAssert assertThat(T actual) { - return new CapturingObjectAssert(actual, capturedAssertionErrors); - } - - public CapturingTestableEventListener() {} - - public CapturingTestableEventListener(final EnabledEventCallback... enabledEventCallbacks) { - super(enabledEventCallbacks); - } - - @Override - public void onFlushCompleted(final RocksDB db, final FlushJobInfo flushJobInfo) { - capturedListenerEvents.flushCompleted = true; - } - - @Override - public void onFlushBegin(final RocksDB db, final FlushJobInfo flushJobInfo) { - capturedListenerEvents.flushBegin = true; - } - - @Override - public void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo) { - capturedListenerEvents.tableFileDeleted = true; - } - - @Override - public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - capturedListenerEvents.compactionBegin = true; - } - - @Override - public void onCompactionCompleted(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - capturedListenerEvents.compactionCompleted = true; - } - - @Override - public void onTableFileCreated(final TableFileCreationInfo tableFileCreationInfo) { - capturedListenerEvents.tableFileCreated = true; - } - - @Override - public void onTableFileCreationStarted( - final TableFileCreationBriefInfo tableFileCreationBriefInfo) { - capturedListenerEvents.tableFileCreationStarted = true; - } - - @Override - public void onMemTableSealed(final MemTableInfo memTableInfo) { - capturedListenerEvents.memTableSealed = true; - } - - @Override - public void onColumnFamilyHandleDeletionStarted(final ColumnFamilyHandle columnFamilyHandle) { - capturedListenerEvents.columnFamilyHandleDeletionStarted = true; - } - - @Override - public void onExternalFileIngested( - final RocksDB db, final ExternalFileIngestionInfo externalFileIngestionInfo) { - capturedListenerEvents.externalFileIngested = true; - } - - @Override - public void onBackgroundError( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError) { - capturedListenerEvents.backgroundError = true; - } - - @Override - public void onStallConditionsChanged(final WriteStallInfo writeStallInfo) { - capturedListenerEvents.stallConditionsChanged = true; - } - - @Override - public void onFileReadFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileReadFinish = true; - } - - @Override - public void onFileWriteFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileWriteFinish = true; - } - - @Override - public void onFileFlushFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileFlushFinish = true; - } - - @Override - public void onFileSyncFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileSyncFinish = true; - } - - @Override - public void onFileRangeSyncFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileRangeSyncFinish = true; - } - - @Override - public void onFileTruncateFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileTruncateFinish = true; - } - - @Override - public void onFileCloseFinish(final FileOperationInfo fileOperationInfo) { - capturedListenerEvents.fileCloseFinish = true; - } - - @Override - public boolean shouldBeNotifiedOnFileIO() { - capturedListenerEvents.shouldBeNotifiedOnFileIO = true; - return false; - } - - @Override - public boolean onErrorRecoveryBegin( - final BackgroundErrorReason backgroundErrorReason, final Status backgroundError) { - capturedListenerEvents.errorRecoveryBegin = true; - return true; - } - - @Override - public void onErrorRecoveryCompleted(final Status oldBackgroundError) { - capturedListenerEvents.errorRecoveryCompleted = true; - } - } -} diff --git a/java/src/test/java/org/rocksdb/FilterTest.java b/java/src/test/java/org/rocksdb/FilterTest.java deleted file mode 100644 index dc5c19fbc..000000000 --- a/java/src/test/java/org/rocksdb/FilterTest.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -public class FilterTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void filter() { - // new Bloom filter - final BlockBasedTableConfig blockConfig = new BlockBasedTableConfig(); - try(final Options options = new Options()) { - - try(final Filter bloomFilter = new BloomFilter()) { - blockConfig.setFilterPolicy(bloomFilter); - options.setTableFormatConfig(blockConfig); - } - - try(final Filter bloomFilter = new BloomFilter(10)) { - blockConfig.setFilterPolicy(bloomFilter); - options.setTableFormatConfig(blockConfig); - } - - try(final Filter bloomFilter = new BloomFilter(10, false)) { - blockConfig.setFilterPolicy(bloomFilter); - options.setTableFormatConfig(blockConfig); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/FlushOptionsTest.java b/java/src/test/java/org/rocksdb/FlushOptionsTest.java deleted file mode 100644 index f90ae911d..000000000 --- a/java/src/test/java/org/rocksdb/FlushOptionsTest.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class FlushOptionsTest { - - @Test - public void waitForFlush() { - try (final FlushOptions flushOptions = new FlushOptions()) { - assertThat(flushOptions.waitForFlush()).isTrue(); - flushOptions.setWaitForFlush(false); - assertThat(flushOptions.waitForFlush()).isFalse(); - } - } - - @Test - public void allowWriteStall() { - try (final FlushOptions flushOptions = new FlushOptions()) { - assertThat(flushOptions.allowWriteStall()).isFalse(); - flushOptions.setAllowWriteStall(true); - assertThat(flushOptions.allowWriteStall()).isTrue(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/FlushTest.java b/java/src/test/java/org/rocksdb/FlushTest.java deleted file mode 100644 index 1a354f4ce..000000000 --- a/java/src/test/java/org/rocksdb/FlushTest.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class FlushTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void flush() throws RocksDBException { - try(final Options options = new Options() - .setCreateIfMissing(true) - .setMaxWriteBufferNumber(10) - .setMinWriteBufferNumberToMerge(10); - final WriteOptions wOpt = new WriteOptions() - .setDisableWAL(true); - final FlushOptions flushOptions = new FlushOptions() - .setWaitForFlush(true)) { - assertThat(flushOptions.waitForFlush()).isTrue(); - - try(final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - db.put(wOpt, "key1".getBytes(), "value1".getBytes()); - db.put(wOpt, "key2".getBytes(), "value2".getBytes()); - db.put(wOpt, "key3".getBytes(), "value3".getBytes()); - db.put(wOpt, "key4".getBytes(), "value4".getBytes()); - assertThat(db.getProperty("rocksdb.num-entries-active-mem-table")) - .isEqualTo("4"); - db.flush(flushOptions); - assertThat(db.getProperty("rocksdb.num-entries-active-mem-table")) - .isEqualTo("0"); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/InfoLogLevelTest.java b/java/src/test/java/org/rocksdb/InfoLogLevelTest.java deleted file mode 100644 index 12ee537d9..000000000 --- a/java/src/test/java/org/rocksdb/InfoLogLevelTest.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.Environment; - -import java.io.IOException; - -import static java.nio.file.Files.readAllBytes; -import static java.nio.file.Paths.get; -import static org.assertj.core.api.Assertions.assertThat; - -public class InfoLogLevelTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void testInfoLogLevel() throws RocksDBException, - IOException { - try (final RocksDB db = - RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "value".getBytes()); - db.flush(new FlushOptions().setWaitForFlush(true)); - assertThat(getLogContentsWithoutHeader()).isNotEmpty(); - } - } - - @Test - public void testFatalLogLevel() throws RocksDBException, - IOException { - try (final Options options = new Options(). - setCreateIfMissing(true). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(options.infoLogLevel()). - isEqualTo(InfoLogLevel.FATAL_LEVEL); - db.put("key".getBytes(), "value".getBytes()); - // As InfoLogLevel is set to FATAL_LEVEL, here we expect the log - // content to be empty. - assertThat(getLogContentsWithoutHeader()).isEmpty(); - } - } - - @Test - public void testFatalLogLevelWithDBOptions() - throws RocksDBException, IOException { - try (final DBOptions dbOptions = new DBOptions(). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL); - final Options options = new Options(dbOptions, - new ColumnFamilyOptions()). - setCreateIfMissing(true); - final RocksDB db = - RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - assertThat(dbOptions.infoLogLevel()). - isEqualTo(InfoLogLevel.FATAL_LEVEL); - assertThat(options.infoLogLevel()). - isEqualTo(InfoLogLevel.FATAL_LEVEL); - db.put("key".getBytes(), "value".getBytes()); - assertThat(getLogContentsWithoutHeader()).isEmpty(); - } - } - - @Test(expected = IllegalArgumentException.class) - public void failIfIllegalByteValueProvided() { - InfoLogLevel.getInfoLogLevel((byte) -1); - } - - @Test - public void valueOf() { - assertThat(InfoLogLevel.valueOf("DEBUG_LEVEL")). - isEqualTo(InfoLogLevel.DEBUG_LEVEL); - } - - /** - * Read LOG file contents into String. - * - * @return LOG file contents as String. - * @throws IOException if file is not found. - */ - private String getLogContentsWithoutHeader() throws IOException { - final String separator = Environment.isWindows() ? - "\n" : System.getProperty("line.separator"); - final String[] lines = new String(readAllBytes(get( - dbFolder.getRoot().getAbsolutePath() + "/LOG"))).split(separator); - - int first_non_header = lines.length; - // Identify the last line of the header - for (int i = lines.length - 1; i >= 0; --i) { - if (lines[i].indexOf("DB pointer") >= 0) { - first_non_header = i + 1; - break; - } - } - StringBuilder builder = new StringBuilder(); - for (int i = first_non_header; i < lines.length; ++i) { - builder.append(lines[i]).append(separator); - } - return builder.toString(); - } -} diff --git a/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java b/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java deleted file mode 100644 index 230694615..000000000 --- a/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; - -public class IngestExternalFileOptionsTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE - = new RocksNativeLibraryResource(); - - public static final Random rand = - PlatformRandomHelper.getPlatformSpecificRandomFactory(); - - @Test - public void createExternalSstFileInfoWithoutParameters() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - assertThat(options).isNotNull(); - } - } - - @Test - public void createExternalSstFileInfoWithParameters() { - final boolean moveFiles = rand.nextBoolean(); - final boolean snapshotConsistency = rand.nextBoolean(); - final boolean allowGlobalSeqNo = rand.nextBoolean(); - final boolean allowBlockingFlush = rand.nextBoolean(); - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions(moveFiles, snapshotConsistency, - allowGlobalSeqNo, allowBlockingFlush)) { - assertThat(options).isNotNull(); - assertThat(options.moveFiles()).isEqualTo(moveFiles); - assertThat(options.snapshotConsistency()).isEqualTo(snapshotConsistency); - assertThat(options.allowGlobalSeqNo()).isEqualTo(allowGlobalSeqNo); - assertThat(options.allowBlockingFlush()).isEqualTo(allowBlockingFlush); - } - } - - @Test - public void moveFiles() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - final boolean moveFiles = rand.nextBoolean(); - options.setMoveFiles(moveFiles); - assertThat(options.moveFiles()).isEqualTo(moveFiles); - } - } - - @Test - public void snapshotConsistency() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - final boolean snapshotConsistency = rand.nextBoolean(); - options.setSnapshotConsistency(snapshotConsistency); - assertThat(options.snapshotConsistency()).isEqualTo(snapshotConsistency); - } - } - - @Test - public void allowGlobalSeqNo() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - final boolean allowGlobalSeqNo = rand.nextBoolean(); - options.setAllowGlobalSeqNo(allowGlobalSeqNo); - assertThat(options.allowGlobalSeqNo()).isEqualTo(allowGlobalSeqNo); - } - } - - @Test - public void allowBlockingFlush() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - final boolean allowBlockingFlush = rand.nextBoolean(); - options.setAllowBlockingFlush(allowBlockingFlush); - assertThat(options.allowBlockingFlush()).isEqualTo(allowBlockingFlush); - } - } - - @Test - public void ingestBehind() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - assertThat(options.ingestBehind()).isFalse(); - options.setIngestBehind(true); - assertThat(options.ingestBehind()).isTrue(); - } - } - - @Test - public void writeGlobalSeqno() { - try (final IngestExternalFileOptions options = - new IngestExternalFileOptions()) { - assertThat(options.writeGlobalSeqno()).isFalse(); - options.setWriteGlobalSeqno(true); - assertThat(options.writeGlobalSeqno()).isTrue(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/KeyMayExistTest.java b/java/src/test/java/org/rocksdb/KeyMayExistTest.java deleted file mode 100644 index 3f3bec6ba..000000000 --- a/java/src/test/java/org/rocksdb/KeyMayExistTest.java +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.*; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -public class KeyMayExistTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Rule public ExpectedException exceptionRule = ExpectedException.none(); - - List cfDescriptors; - List columnFamilyHandleList = new ArrayList<>(); - RocksDB db; - - // Slice key - int offset; - int len; - - byte[] sliceKey; - byte[] sliceValue; - - @Before - public void before() throws RocksDBException { - cfDescriptors = Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - - db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfDescriptors, columnFamilyHandleList); - - // Build the slice key - final StringBuilder builder = new StringBuilder("prefix"); - offset = builder.toString().length(); - builder.append("slice key 0"); - len = builder.toString().length() - offset; - builder.append("suffix"); - sliceKey = builder.toString().getBytes(UTF_8); - sliceValue = "slice value 0".getBytes(UTF_8); - } - - @After - public void after() { - for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { - columnFamilyHandle.close(); - } - db.close(); - } - - @Test - public void keyMayExist() throws RocksDBException { - assertThat(columnFamilyHandleList.size()).isEqualTo(2); - - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Test without column family - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist("key".getBytes(UTF_8), holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value"); - - exists = db.keyMayExist("key".getBytes(UTF_8), null); - assertThat(exists).isTrue(); - } - - @Test - public void keyMayExistReadOptions() throws RocksDBException { - // Test without column family but with readOptions - try (final ReadOptions readOptions = new ReadOptions()) { - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Slice key - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value"); - - exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), null); - assertThat(exists).isTrue(); - - exists = db.keyMayExist(readOptions, sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = db.keyMayExist(readOptions, sliceKey, offset, len, null); - assertThat(exists).isTrue(); - } - } - - @Test - public void keyMayExistColumnFamily() throws RocksDBException { - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Slice key - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - // Test slice key with column family - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len, null); - assertThat(exists).isTrue(); - } - - @Test - public void keyMayExistColumnFamilyReadOptions() throws RocksDBException { - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Slice key - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - // Test slice key with column family and read options - final Holder holder = new Holder<>(); - try (final ReadOptions readOptions = new ReadOptions()) { - boolean exists = - db.keyMayExist(columnFamilyHandleList.get(0), readOptions, "key".getBytes(UTF_8), holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value"); - - exists = - db.keyMayExist(columnFamilyHandleList.get(0), readOptions, "key".getBytes(UTF_8), null); - assertThat(exists).isTrue(); - - // Test slice key with column family and read options - exists = - db.keyMayExist(columnFamilyHandleList.get(0), readOptions, sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = - db.keyMayExist(columnFamilyHandleList.get(0), readOptions, sliceKey, offset, len, null); - assertThat(exists).isTrue(); - } - } - - @Test - public void keyMayExistSliceKey() throws RocksDBException { - assertThat(columnFamilyHandleList.size()).isEqualTo(2); - - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Slice key - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist(sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = db.keyMayExist(sliceKey, offset, len, null); - assertThat(exists).isTrue(); - - exists = db.keyMayExist("slice key".getBytes(UTF_8), null); - assertThat(exists).isFalse(); - - exists = db.keyMayExist("slice key 0".getBytes(UTF_8), null); - assertThat(exists).isTrue(); - - // Test with column family - exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(UTF_8), holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value"); - - exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(UTF_8), null); - assertThat(exists).isTrue(); - - // KeyMayExist in CF1 must return null value - exists = db.keyMayExist(columnFamilyHandleList.get(1), "key".getBytes(UTF_8), holder); - assertThat(exists).isFalse(); - assertThat(holder.getValue()).isNull(); - exists = db.keyMayExist(columnFamilyHandleList.get(1), "key".getBytes(UTF_8), null); - assertThat(exists).isFalse(); - - // slice key - exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, holder); - assertThat(exists).isFalse(); - assertThat(holder.getValue()).isNull(); - exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, null); - assertThat(exists).isFalse(); - } - - @Test - public void keyMayExistCF1() throws RocksDBException { - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Slice key - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - // KeyMayExist in CF1 must return null value - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist(columnFamilyHandleList.get(1), "key".getBytes(UTF_8), holder); - assertThat(exists).isFalse(); - assertThat(holder.getValue()).isNull(); - exists = db.keyMayExist(columnFamilyHandleList.get(1), "key".getBytes(UTF_8), null); - assertThat(exists).isFalse(); - } - - @Test - public void keyMayExistCF1Slice() throws RocksDBException { - // Standard key - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - - // Slice key - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - // slice key - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, holder); - assertThat(exists).isFalse(); - assertThat(holder.getValue()).isNull(); - exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, null); - assertThat(exists).isFalse(); - } - - @Test - public void keyMayExistBB() throws RocksDBException { - // Standard key - db.put("keyBB".getBytes(UTF_8), "valueBB".getBytes(UTF_8)); - - final byte[] key = "keyBB".getBytes(UTF_8); - final byte[] value = "valueBB".getBytes(UTF_8); - - final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - assertThat(db.keyMayExist(keyBuffer)).isEqualTo(true); - - final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); - valueBuffer.position(12); - KeyMayExist keyMayExist = db.keyMayExist(keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(12); - assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); - byte[] valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(value); - - valueBuffer.limit(value.length + 24); - valueBuffer.position(25); - keyMayExist = db.keyMayExist(keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(25); - assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); - valueGet = new byte[value.length - 1]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); - - exceptionRule.expect(BufferUnderflowException.class); - valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - } - - @Test - public void keyMayExistBBReadOptions() throws RocksDBException { - // Standard key - db.put("keyBB".getBytes(UTF_8), "valueBB".getBytes(UTF_8)); - - final byte[] key = "keyBB".getBytes(UTF_8); - final byte[] value = "valueBB".getBytes(UTF_8); - - final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - try (final ReadOptions readOptions = new ReadOptions()) { - assertThat(db.keyMayExist(readOptions, keyBuffer)).isEqualTo(true); - - final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); - valueBuffer.position(12); - KeyMayExist keyMayExist = db.keyMayExist(readOptions, keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(12); - assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); - byte[] valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(value); - - valueBuffer.limit(value.length + 24); - valueBuffer.position(25); - keyMayExist = db.keyMayExist(readOptions, keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(25); - assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); - valueGet = new byte[value.length - 1]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); - - exceptionRule.expect(BufferUnderflowException.class); - valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - } - } - - @Test - public void keyMayExistBBNullValue() throws RocksDBException { - // Standard key - db.put("keyBB".getBytes(UTF_8), "valueBB".getBytes(UTF_8)); - - final byte[] key = "keyBB".getBytes(UTF_8); - - final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - exceptionRule.expect(AssertionError.class); - exceptionRule.expectMessage( - "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"); - final KeyMayExist keyMayExist = db.keyMayExist(keyBuffer, null); - } - - @Test - public void keyMayExistBBCF() throws RocksDBException { - // Standard key - db.put(columnFamilyHandleList.get(0), "keyBBCF0".getBytes(UTF_8), "valueBBCF0".getBytes(UTF_8)); - db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); - - // 0 is the default CF - byte[] key = "keyBBCF0".getBytes(UTF_8); - ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - assertThat(db.keyMayExist(keyBuffer)).isEqualTo(true); - assertThat(db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer)).isEqualTo(false); - assertThat(db.keyMayExist(columnFamilyHandleList.get(0), keyBuffer)).isEqualTo(true); - - // 1 is just a CF - key = "keyBBCF1".getBytes(UTF_8); - keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - assertThat(db.keyMayExist(keyBuffer)).isEqualTo(false); - assertThat(db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer)).isEqualTo(true); - assertThat(db.keyMayExist(columnFamilyHandleList.get(0), keyBuffer)).isEqualTo(false); - - exceptionRule.expect(AssertionError.class); - exceptionRule.expectMessage( - "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"); - final KeyMayExist keyMayExist = db.keyMayExist(columnFamilyHandleList.get(0), keyBuffer, null); - } - - @Test - public void keyMayExistBBCFReadOptions() throws RocksDBException { - // Standard key - db.put(columnFamilyHandleList.get(0), "keyBBCF0".getBytes(UTF_8), "valueBBCF0".getBytes(UTF_8)); - db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); - - // 0 is the default CF - byte[] key = "keyBBCF0".getBytes(UTF_8); - ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - try (final ReadOptions readOptions = new ReadOptions()) { - assertThat(db.keyMayExist(keyBuffer)).isEqualTo(true); - assertThat(db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer)) - .isEqualTo(false); - assertThat(db.keyMayExist(columnFamilyHandleList.get(0), readOptions, keyBuffer)) - .isEqualTo(true); - - // 1 is just a CF - key = "keyBBCF1".getBytes(UTF_8); - keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - assertThat(db.keyMayExist(readOptions, keyBuffer)).isEqualTo(false); - assertThat(db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer)) - .isEqualTo(true); - assertThat(db.keyMayExist(columnFamilyHandleList.get(0), readOptions, keyBuffer)) - .isEqualTo(false); - - exceptionRule.expect(AssertionError.class); - exceptionRule.expectMessage( - "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"); - final KeyMayExist keyMayExist = - db.keyMayExist(columnFamilyHandleList.get(0), readOptions, keyBuffer, null); - } - } - - @Test - public void keyMayExistBBCFOffset() throws RocksDBException { - db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); - - final byte[] key = "keyBBCF1".getBytes(UTF_8); - final byte[] value = "valueBBCF1".getBytes(UTF_8); - - final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - assertThat(db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer)).isEqualTo(true); - - final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); - valueBuffer.position(12); - KeyMayExist keyMayExist = db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(12); - assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); - byte[] valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(value); - - valueBuffer.limit(value.length + 24); - valueBuffer.position(25); - keyMayExist = db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(25); - assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); - valueGet = new byte[value.length - 1]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); - - exceptionRule.expect(BufferUnderflowException.class); - valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - } - - @Test - public void keyMayExistBBCFOffsetReadOptions() throws RocksDBException { - db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); - - final byte[] key = "keyBBCF1".getBytes(UTF_8); - final byte[] value = "valueBBCF1".getBytes(UTF_8); - - final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); - keyBuffer.put(key, 0, key.length); - keyBuffer.flip(); - - try (final ReadOptions readOptions = new ReadOptions()) { - assertThat(db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer)) - .isEqualTo(true); - - final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); - valueBuffer.position(12); - KeyMayExist keyMayExist = - db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(12); - assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); - byte[] valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(value); - - valueBuffer.limit(value.length + 24); - valueBuffer.position(25); - keyMayExist = - db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer, valueBuffer); - assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); - assertThat(keyMayExist.valueLength).isEqualTo(value.length); - assertThat(valueBuffer.position()).isEqualTo(25); - assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); - valueGet = new byte[value.length - 1]; - valueBuffer.get(valueGet); - assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); - - exceptionRule.expect(BufferUnderflowException.class); - valueGet = new byte[value.length]; - valueBuffer.get(valueGet); - } - } - - @Test - public void keyMayExistNonUnicodeString() throws RocksDBException { - final byte[] key = "key".getBytes(UTF_8); - final byte[] value = {(byte) 0x80}; // invalid unicode code-point - db.put(key, value); - - final byte[] buf = new byte[10]; - final int read = db.get(key, buf); - assertThat(read).isEqualTo(1); - assertThat(buf).startsWith(value); - - final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist("key".getBytes(UTF_8), holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(value); - - exists = db.keyMayExist("key".getBytes(UTF_8), null); - assertThat(exists).isTrue(); - } -} diff --git a/java/src/test/java/org/rocksdb/LRUCacheTest.java b/java/src/test/java/org/rocksdb/LRUCacheTest.java deleted file mode 100644 index 4d194e712..000000000 --- a/java/src/test/java/org/rocksdb/LRUCacheTest.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.ClassRule; -import org.junit.Test; - -public class LRUCacheTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void newLRUCache() { - final long capacity = 80000000; - final int numShardBits = 16; - final boolean strictCapacityLimit = true; - final double highPriPoolRatio = 0.5; - final double lowPriPoolRatio = 0.5; - try (final Cache lruCache = new LRUCache( - capacity, numShardBits, strictCapacityLimit, highPriPoolRatio, lowPriPoolRatio)) { - //no op - assertThat(lruCache.getUsage()).isGreaterThanOrEqualTo(0); - assertThat(lruCache.getPinnedUsage()).isGreaterThanOrEqualTo(0); - } - } -} diff --git a/java/src/test/java/org/rocksdb/LoggerTest.java b/java/src/test/java/org/rocksdb/LoggerTest.java deleted file mode 100644 index 5bc299f11..000000000 --- a/java/src/test/java/org/rocksdb/LoggerTest.java +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; - -public class LoggerTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void customLogger() throws RocksDBException { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final Options options = new Options(). - setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL). - setCreateIfMissing(true); - final Logger logger = new Logger(options) { - // Create new logger with max log level passed by options - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - // Set custom logger to options - options.setLogger(logger); - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - // there should be more than zero received log messages in - // debug level. - assertThat(logMessageCounter.get()).isGreaterThan(0); - } - } - } - - @Test - public void warnLogger() throws RocksDBException { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final Options options = new Options(). - setInfoLogLevel(InfoLogLevel.WARN_LEVEL). - setCreateIfMissing(true); - - final Logger logger = new Logger(options) { - // Create new logger with max log level passed by options - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - - // Set custom logger to options - options.setLogger(logger); - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - // there should be zero messages - // using warn level as log level. - assertThat(logMessageCounter.get()).isEqualTo(0); - } - } - } - - - @Test - public void fatalLogger() throws RocksDBException { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final Options options = new Options(). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL). - setCreateIfMissing(true); - - final Logger logger = new Logger(options) { - // Create new logger with max log level passed by options - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - - // Set custom logger to options - options.setLogger(logger); - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - // there should be zero messages - // using fatal level as log level. - assertThat(logMessageCounter.get()).isEqualTo(0); - } - } - } - - @Test - public void dbOptionsLogger() throws RocksDBException { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final DBOptions options = new DBOptions(). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL). - setCreateIfMissing(true); - final Logger logger = new Logger(options) { - // Create new logger with max log level passed by options - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - // Set custom logger to options - options.setLogger(logger); - - final List cfDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY)); - final List cfHandles = new ArrayList<>(); - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, cfHandles)) { - try { - // there should be zero messages - // using fatal level as log level. - assertThat(logMessageCounter.get()).isEqualTo(0); - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : cfHandles) { - columnFamilyHandle.close(); - } - } - } - } - } - - @Test - public void setWarnLogLevel() { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final Options options = new Options(). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL). - setCreateIfMissing(true); - final Logger logger = new Logger(options) { - // Create new logger with max log level passed by options - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - assertThat(logger.infoLogLevel()). - isEqualTo(InfoLogLevel.FATAL_LEVEL); - logger.setInfoLogLevel(InfoLogLevel.WARN_LEVEL); - assertThat(logger.infoLogLevel()). - isEqualTo(InfoLogLevel.WARN_LEVEL); - } - } - - @Test - public void setInfoLogLevel() { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final Options options = new Options(). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL). - setCreateIfMissing(true); - final Logger logger = new Logger(options) { - // Create new logger with max log level passed by options - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - assertThat(logger.infoLogLevel()). - isEqualTo(InfoLogLevel.FATAL_LEVEL); - logger.setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL); - assertThat(logger.infoLogLevel()). - isEqualTo(InfoLogLevel.DEBUG_LEVEL); - } - } - - @Test - public void changeLogLevelAtRuntime() throws RocksDBException { - final AtomicInteger logMessageCounter = new AtomicInteger(); - try (final Options options = new Options(). - setInfoLogLevel(InfoLogLevel.FATAL_LEVEL). - setCreateIfMissing(true); - - // Create new logger with max log level passed by options - final Logger logger = new Logger(options) { - @Override - protected void log(InfoLogLevel infoLogLevel, String logMsg) { - assertThat(logMsg).isNotNull(); - assertThat(logMsg.length()).isGreaterThan(0); - logMessageCounter.incrementAndGet(); - } - } - ) { - // Set custom logger to options - options.setLogger(logger); - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - // there should be zero messages - // using fatal level as log level. - assertThat(logMessageCounter.get()).isEqualTo(0); - - // change log level to debug level - logger.setInfoLogLevel(InfoLogLevel.DEBUG_LEVEL); - - db.put("key".getBytes(), "value".getBytes()); - db.flush(new FlushOptions().setWaitForFlush(true)); - - // messages shall be received due to previous actions. - assertThat(logMessageCounter.get()).isNotEqualTo(0); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/MemTableTest.java b/java/src/test/java/org/rocksdb/MemTableTest.java deleted file mode 100644 index 73ac589a9..000000000 --- a/java/src/test/java/org/rocksdb/MemTableTest.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MemTableTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void hashSkipListMemTable() throws RocksDBException { - try(final Options options = new Options()) { - // Test HashSkipListMemTableConfig - HashSkipListMemTableConfig memTableConfig = - new HashSkipListMemTableConfig(); - assertThat(memTableConfig.bucketCount()). - isEqualTo(1000000); - memTableConfig.setBucketCount(2000000); - assertThat(memTableConfig.bucketCount()). - isEqualTo(2000000); - assertThat(memTableConfig.height()). - isEqualTo(4); - memTableConfig.setHeight(5); - assertThat(memTableConfig.height()). - isEqualTo(5); - assertThat(memTableConfig.branchingFactor()). - isEqualTo(4); - memTableConfig.setBranchingFactor(6); - assertThat(memTableConfig.branchingFactor()). - isEqualTo(6); - options.setMemTableConfig(memTableConfig); - } - } - - @Test - public void skipListMemTable() throws RocksDBException { - try(final Options options = new Options()) { - SkipListMemTableConfig skipMemTableConfig = - new SkipListMemTableConfig(); - assertThat(skipMemTableConfig.lookahead()). - isEqualTo(0); - skipMemTableConfig.setLookahead(20); - assertThat(skipMemTableConfig.lookahead()). - isEqualTo(20); - options.setMemTableConfig(skipMemTableConfig); - } - } - - @Test - public void hashLinkedListMemTable() throws RocksDBException { - try(final Options options = new Options()) { - HashLinkedListMemTableConfig hashLinkedListMemTableConfig = - new HashLinkedListMemTableConfig(); - assertThat(hashLinkedListMemTableConfig.bucketCount()). - isEqualTo(50000); - hashLinkedListMemTableConfig.setBucketCount(100000); - assertThat(hashLinkedListMemTableConfig.bucketCount()). - isEqualTo(100000); - assertThat(hashLinkedListMemTableConfig.hugePageTlbSize()). - isEqualTo(0); - hashLinkedListMemTableConfig.setHugePageTlbSize(1); - assertThat(hashLinkedListMemTableConfig.hugePageTlbSize()). - isEqualTo(1); - assertThat(hashLinkedListMemTableConfig. - bucketEntriesLoggingThreshold()). - isEqualTo(4096); - hashLinkedListMemTableConfig. - setBucketEntriesLoggingThreshold(200); - assertThat(hashLinkedListMemTableConfig. - bucketEntriesLoggingThreshold()). - isEqualTo(200); - assertThat(hashLinkedListMemTableConfig. - ifLogBucketDistWhenFlush()).isTrue(); - hashLinkedListMemTableConfig. - setIfLogBucketDistWhenFlush(false); - assertThat(hashLinkedListMemTableConfig. - ifLogBucketDistWhenFlush()).isFalse(); - assertThat(hashLinkedListMemTableConfig. - thresholdUseSkiplist()). - isEqualTo(256); - hashLinkedListMemTableConfig.setThresholdUseSkiplist(29); - assertThat(hashLinkedListMemTableConfig. - thresholdUseSkiplist()). - isEqualTo(29); - options.setMemTableConfig(hashLinkedListMemTableConfig); - } - } - - @Test - public void vectorMemTable() throws RocksDBException { - try(final Options options = new Options()) { - VectorMemTableConfig vectorMemTableConfig = - new VectorMemTableConfig(); - assertThat(vectorMemTableConfig.reservedSize()). - isEqualTo(0); - vectorMemTableConfig.setReservedSize(123); - assertThat(vectorMemTableConfig.reservedSize()). - isEqualTo(123); - options.setMemTableConfig(vectorMemTableConfig); - } - } -} diff --git a/java/src/test/java/org/rocksdb/MemoryUtilTest.java b/java/src/test/java/org/rocksdb/MemoryUtilTest.java deleted file mode 100644 index 1bea02379..000000000 --- a/java/src/test/java/org/rocksdb/MemoryUtilTest.java +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.nio.charset.StandardCharsets; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MemoryUtilTest { - - private static final String MEMTABLE_SIZE = "rocksdb.size-all-mem-tables"; - private static final String UNFLUSHED_MEMTABLE_SIZE = "rocksdb.cur-size-all-mem-tables"; - private static final String TABLE_READERS = "rocksdb.estimate-table-readers-mem"; - - private final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - private final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder1 = new TemporaryFolder(); - @Rule public TemporaryFolder dbFolder2 = new TemporaryFolder(); - - /** - * Test MemoryUtil.getApproximateMemoryUsageByType before and after a put + get - */ - @Test - public void getApproximateMemoryUsageByType() throws RocksDBException { - try (final Cache cache = new LRUCache(8 * 1024 * 1024); - final Options options = - new Options() - .setCreateIfMissing(true) - .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache)); - final FlushOptions flushOptions = - new FlushOptions().setWaitForFlush(true); - final RocksDB db = - RocksDB.open(options, dbFolder1.getRoot().getAbsolutePath())) { - - List dbs = new ArrayList<>(1); - dbs.add(db); - Set caches = new HashSet<>(1); - caches.add(cache); - Map usage = MemoryUtil.getApproximateMemoryUsageByType(dbs, caches); - - assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo( - db.getAggregatedLongProperty(MEMTABLE_SIZE)); - assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo( - db.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE)); - assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo( - db.getAggregatedLongProperty(TABLE_READERS)); - // TODO(peterd): disable block cache entry stats and check for 0 - assertThat(usage.get(MemoryUsageType.kCacheTotal)).isLessThan(1024); - - db.put(key, value); - db.flush(flushOptions); - db.get(key); - - usage = MemoryUtil.getApproximateMemoryUsageByType(dbs, caches); - assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isGreaterThan(0); - assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo( - db.getAggregatedLongProperty(MEMTABLE_SIZE)); - assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isGreaterThan(0); - assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo( - db.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE)); - assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isGreaterThan(0); - assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo( - db.getAggregatedLongProperty(TABLE_READERS)); - assertThat(usage.get(MemoryUsageType.kCacheTotal)).isGreaterThan(0); - - } - } - - /** - * Test MemoryUtil.getApproximateMemoryUsageByType with null inputs - */ - @Test - public void getApproximateMemoryUsageByTypeNulls() throws RocksDBException { - Map usage = MemoryUtil.getApproximateMemoryUsageByType(null, null); - - assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo(null); - assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo(null); - assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo(null); - assertThat(usage.get(MemoryUsageType.kCacheTotal)).isEqualTo(null); - } - - /** - * Test MemoryUtil.getApproximateMemoryUsageByType with two DBs and two caches - */ - @Test - public void getApproximateMemoryUsageByTypeMultiple() throws RocksDBException { - try (final Cache cache1 = new LRUCache(1 * 1024 * 1024); - final Options options1 = - new Options() - .setCreateIfMissing(true) - .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache1)); - final RocksDB db1 = - RocksDB.open(options1, dbFolder1.getRoot().getAbsolutePath()); - final Cache cache2 = new LRUCache(1 * 1024 * 1024); - final Options options2 = - new Options() - .setCreateIfMissing(true) - .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache2)); - final RocksDB db2 = - RocksDB.open(options2, dbFolder2.getRoot().getAbsolutePath()); - final FlushOptions flushOptions = - new FlushOptions().setWaitForFlush(true); - - ) { - List dbs = new ArrayList<>(1); - dbs.add(db1); - dbs.add(db2); - Set caches = new HashSet<>(1); - caches.add(cache1); - caches.add(cache2); - - for (RocksDB db: dbs) { - db.put(key, value); - db.flush(flushOptions); - db.get(key); - } - - Map usage = MemoryUtil.getApproximateMemoryUsageByType(dbs, caches); - assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo( - db1.getAggregatedLongProperty(MEMTABLE_SIZE) + db2.getAggregatedLongProperty(MEMTABLE_SIZE)); - assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo( - db1.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE) + db2.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE)); - assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo( - db1.getAggregatedLongProperty(TABLE_READERS) + db2.getAggregatedLongProperty(TABLE_READERS)); - assertThat(usage.get(MemoryUsageType.kCacheTotal)).isGreaterThan(0); - - } - } - -} diff --git a/java/src/test/java/org/rocksdb/MergeTest.java b/java/src/test/java/org/rocksdb/MergeTest.java deleted file mode 100644 index a840eb104..000000000 --- a/java/src/test/java/org/rocksdb/MergeTest.java +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class MergeTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void stringOption() - throws InterruptedException, RocksDBException { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperatorName("stringappend"); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // writing aa under key - db.put("key".getBytes(), "aa".getBytes()); - // merge bb under key - db.merge("key".getBytes(), "bb".getBytes()); - - final byte[] value = db.get("key".getBytes()); - final String strValue = new String(value); - assertThat(strValue).isEqualTo("aa,bb"); - } - } - - private byte[] longToByteArray(long l) { - ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN); - buf.putLong(l); - return buf.array(); - } - - private long longFromByteArray(byte[] a) { - ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN); - buf.put(a); - buf.flip(); - return buf.getLong(); - } - - @Test - public void uint64AddOption() - throws InterruptedException, RocksDBException { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperatorName("uint64add"); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // writing (long)100 under key - db.put("key".getBytes(), longToByteArray(100)); - // merge (long)1 under key - db.merge("key".getBytes(), longToByteArray(1)); - - final byte[] value = db.get("key".getBytes()); - final long longValue = longFromByteArray(value); - assertThat(longValue).isEqualTo(101); - } - } - - @Test - public void cFStringOption() - throws InterruptedException, RocksDBException { - - try (final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions() - .setMergeOperatorName("stringappend"); - final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions() - .setMergeOperatorName("stringappend") - ) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1), - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2) - ); - - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - try { - // writing aa under key - db.put(columnFamilyHandleList.get(1), - "cfkey".getBytes(), "aa".getBytes()); - // merge bb under key - db.merge(columnFamilyHandleList.get(1), - "cfkey".getBytes(), "bb".getBytes()); - - byte[] value = db.get(columnFamilyHandleList.get(1), - "cfkey".getBytes()); - String strValue = new String(value); - assertThat(strValue).isEqualTo("aa,bb"); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandleList) { - handle.close(); - } - } - } - } - } - - @Test - public void cFUInt64AddOption() - throws InterruptedException, RocksDBException { - - try (final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions() - .setMergeOperatorName("uint64add"); - final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions() - .setMergeOperatorName("uint64add") - ) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1), - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2) - ); - - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList)) { - try { - // writing (long)100 under key - db.put(columnFamilyHandleList.get(1), - "cfkey".getBytes(), longToByteArray(100)); - // merge (long)157 under key - db.merge(columnFamilyHandleList.get(1), "cfkey".getBytes(), longToByteArray(157)); - - byte[] value = db.get(columnFamilyHandleList.get(1), - "cfkey".getBytes()); - long longValue = longFromByteArray(value); - assertThat(longValue).isEqualTo(257); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandleList) { - handle.close(); - } - } - } - } - } - - @Test - public void operatorOption() - throws InterruptedException, RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // Writing aa under key - db.put("key".getBytes(), "aa".getBytes()); - - // Writing bb under key - db.merge("key".getBytes(), "bb".getBytes()); - - final byte[] value = db.get("key".getBytes()); - final String strValue = new String(value); - - assertThat(strValue).isEqualTo("aa,bb"); - } - } - - @Test - public void uint64AddOperatorOption() - throws InterruptedException, RocksDBException { - try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator(); - final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperator(uint64AddOperator); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // Writing (long)100 under key - db.put("key".getBytes(), longToByteArray(100)); - - // Writing (long)1 under key - db.merge("key".getBytes(), longToByteArray(1)); - - final byte[] value = db.get("key".getBytes()); - final long longValue = longFromByteArray(value); - - assertThat(longValue).isEqualTo(101); - } - } - - @Test - public void cFOperatorOption() - throws InterruptedException, RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions() - .setMergeOperator(stringAppendOperator); - final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions() - .setMergeOperator(stringAppendOperator) - ) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1), - new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2) - ); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList) - ) { - try { - // writing aa under key - db.put(columnFamilyHandleList.get(1), - "cfkey".getBytes(), "aa".getBytes()); - // merge bb under key - db.merge(columnFamilyHandleList.get(1), - "cfkey".getBytes(), "bb".getBytes()); - byte[] value = db.get(columnFamilyHandleList.get(1), - "cfkey".getBytes()); - String strValue = new String(value); - - // Test also with createColumnFamily - try (final ColumnFamilyOptions cfHandleOpts = - new ColumnFamilyOptions() - .setMergeOperator(stringAppendOperator); - final ColumnFamilyHandle cfHandle = - db.createColumnFamily( - new ColumnFamilyDescriptor("new_cf2".getBytes(), - cfHandleOpts)) - ) { - // writing xx under cfkey2 - db.put(cfHandle, "cfkey2".getBytes(), "xx".getBytes()); - // merge yy under cfkey2 - db.merge(cfHandle, new WriteOptions(), "cfkey2".getBytes(), - "yy".getBytes()); - value = db.get(cfHandle, "cfkey2".getBytes()); - String strValueTmpCf = new String(value); - - assertThat(strValue).isEqualTo("aa,bb"); - assertThat(strValueTmpCf).isEqualTo("xx,yy"); - } - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : - columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } - } - } - - @Test - public void cFUInt64AddOperatorOption() - throws InterruptedException, RocksDBException { - try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator(); - final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions() - .setMergeOperator(uint64AddOperator); - final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions() - .setMergeOperator(uint64AddOperator) - ) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1), - new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2) - ); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - columnFamilyHandleList) - ) { - try { - // writing (long)100 under key - db.put(columnFamilyHandleList.get(1), - "cfkey".getBytes(), longToByteArray(100)); - // merge (long)1 under key - db.merge(columnFamilyHandleList.get(1), - "cfkey".getBytes(), longToByteArray(1)); - byte[] value = db.get(columnFamilyHandleList.get(1), - "cfkey".getBytes()); - long longValue = longFromByteArray(value); - - // Test also with createColumnFamily - try (final ColumnFamilyOptions cfHandleOpts = - new ColumnFamilyOptions() - .setMergeOperator(uint64AddOperator); - final ColumnFamilyHandle cfHandle = - db.createColumnFamily( - new ColumnFamilyDescriptor("new_cf2".getBytes(), - cfHandleOpts)) - ) { - // writing (long)200 under cfkey2 - db.put(cfHandle, "cfkey2".getBytes(), longToByteArray(200)); - // merge (long)50 under cfkey2 - db.merge(cfHandle, new WriteOptions(), "cfkey2".getBytes(), - longToByteArray(50)); - value = db.get(cfHandle, "cfkey2".getBytes()); - long longValueTmpCf = longFromByteArray(value); - - assertThat(longValue).isEqualTo(101); - assertThat(longValueTmpCf).isEqualTo(250); - } - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : - columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } - } - } - - @Test - public void operatorGcBehaviour() - throws RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator()) { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - // test reuse - try (final Options opt = new Options() - .setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - // test param init - try (final StringAppendOperator stringAppendOperator2 = new StringAppendOperator(); - final Options opt = new Options() - .setMergeOperator(stringAppendOperator2); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - // test replace one with another merge operator instance - try (final Options opt = new Options() - .setMergeOperator(stringAppendOperator); - final StringAppendOperator newStringAppendOperator = new StringAppendOperator()) { - opt.setMergeOperator(newStringAppendOperator); - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - } - } - } - - @Test - public void uint64AddOperatorGcBehaviour() - throws RocksDBException { - try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator()) { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperator(uint64AddOperator); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - // test reuse - try (final Options opt = new Options() - .setMergeOperator(uint64AddOperator); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - // test param init - try (final UInt64AddOperator uint64AddOperator2 = new UInt64AddOperator(); - final Options opt = new Options() - .setMergeOperator(uint64AddOperator2); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - // test replace one with another merge operator instance - try (final Options opt = new Options() - .setMergeOperator(uint64AddOperator); - final UInt64AddOperator newUInt64AddOperator = new UInt64AddOperator()) { - opt.setMergeOperator(newUInt64AddOperator); - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - } - } - } - - @Test - public void emptyStringAsStringAppendDelimiter() throws RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(""); - final Options opt = - new Options().setCreateIfMissing(true).setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "aa".getBytes()); - db.merge("key".getBytes(), "bb".getBytes()); - final byte[] value = db.get("key".getBytes()); - assertThat(new String(value)).isEqualTo("aabb"); - } - } - - @Test - public void multiCharStringAsStringAppendDelimiter() throws RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator("<>"); - final Options opt = - new Options().setCreateIfMissing(true).setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "aa".getBytes()); - db.merge("key".getBytes(), "bb".getBytes()); - final byte[] value = db.get("key".getBytes()); - assertThat(new String(value)).isEqualTo("aa<>bb"); - } - } - - @Test - public void emptyStringInSetMergeOperatorByName() { - try (final Options opt = new Options() - .setMergeOperatorName(""); - final ColumnFamilyOptions cOpt = new ColumnFamilyOptions() - .setMergeOperatorName("")) { - //no-op - } - } - - @Test(expected = IllegalArgumentException.class) - public void nullStringInSetMergeOperatorByNameOptions() { - try (final Options opt = new Options()) { - opt.setMergeOperatorName(null); - } - } - - @Test(expected = IllegalArgumentException.class) - public void - nullStringInSetMergeOperatorByNameColumnFamilyOptions() { - try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) { - opt.setMergeOperatorName(null); - } - } -} diff --git a/java/src/test/java/org/rocksdb/MixedOptionsTest.java b/java/src/test/java/org/rocksdb/MixedOptionsTest.java deleted file mode 100644 index 4e17d04ef..000000000 --- a/java/src/test/java/org/rocksdb/MixedOptionsTest.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MixedOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void mixedOptionsTest(){ - // Set a table factory and check the names - try(final Filter bloomFilter = new BloomFilter(); - final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions() - .setTableFormatConfig( - new BlockBasedTableConfig().setFilterPolicy(bloomFilter)) - ) { - assertThat(cfOptions.tableFactoryName()).isEqualTo( - "BlockBasedTable"); - cfOptions.setTableFormatConfig(new PlainTableConfig()); - assertThat(cfOptions.tableFactoryName()).isEqualTo("PlainTable"); - // Initialize a dbOptions object from cf options and - // db options - try (final DBOptions dbOptions = new DBOptions(); - final Options options = new Options(dbOptions, cfOptions)) { - assertThat(options.tableFactoryName()).isEqualTo("PlainTable"); - // Free instances - } - } - - // Test Optimize for statements - try(final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions()) { - cfOptions.optimizeUniversalStyleCompaction(); - cfOptions.optimizeLevelStyleCompaction(); - cfOptions.optimizeForPointLookup(1024); - try(final Options options = new Options()) { - options.optimizeLevelStyleCompaction(); - options.optimizeLevelStyleCompaction(400); - options.optimizeUniversalStyleCompaction(); - options.optimizeUniversalStyleCompaction(400); - options.optimizeForPointLookup(1024); - options.prepareForBulkLoad(); - } - } - } - - @Test - public void mixedOptionsEnvTest() { - try (final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(); - final DBOptions dbOptions = new DBOptions()) { - assertThat(dbOptions.getEnv()).isNotNull(); - assertThat(dbOptions.getEnv()).isSameAs(Env.getDefault()); - final Env memEnv = new RocksMemEnv(Env.getDefault()); - - try (final Options options = new Options(dbOptions, cfOptions)) { - assertThat(options.getEnv()).isSameAs(Env.getDefault()); - } - - dbOptions.setEnv(memEnv); - memEnv.setBackgroundThreads(4, Priority.LOW); - Env.getDefault().setBackgroundThreads(2, Priority.HIGH); - assertThat(dbOptions.getEnv().getBackgroundThreads(Priority.LOW)).isEqualTo(4); - assertThat(dbOptions.getEnv().getBackgroundThreads(Priority.HIGH)).isEqualTo(2); - assertThat(Env.getDefault().getBackgroundThreads(Priority.LOW)).isEqualTo(4); - assertThat(Env.getDefault().getBackgroundThreads(Priority.HIGH)).isEqualTo(2); - - try (final Options options = new Options(dbOptions, cfOptions)) { - assertThat(options.getEnv().getBackgroundThreads(Priority.LOW)).isEqualTo(4); - assertThat(options.getEnv().getBackgroundThreads(Priority.HIGH)).isEqualTo(2); - - assertThat(options.getEnv()).isNotSameAs(Env.getDefault()); - assertThat(options.getEnv()).isSameAs(memEnv); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/MultiColumnRegressionTest.java b/java/src/test/java/org/rocksdb/MultiColumnRegressionTest.java deleted file mode 100644 index cdfd9d3a9..000000000 --- a/java/src/test/java/org/rocksdb/MultiColumnRegressionTest.java +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -/** - * Test for changes made by - * transactional multiGet problem - * the tests here were previously broken by the nonsense removed by that change. - */ -@RunWith(Parameterized.class) -public class MultiColumnRegressionTest { - @Parameterized.Parameters - public static List data() { - return Arrays.asList(new Params(3, 100), new Params(3, 1000000)); - } - - public static class Params { - final int numColumns; - final int keySize; - - public Params(final int numColumns, final int keySize) { - this.numColumns = numColumns; - this.keySize = keySize; - } - } - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - private final Params params; - - public MultiColumnRegressionTest(final Params params) { - this.params = params; - } - - @Test - public void transactionDB() throws RocksDBException { - final List columnFamilyDescriptors = new ArrayList<>(); - for (int i = 0; i < params.numColumns; i++) { - StringBuilder sb = new StringBuilder(); - sb.append("cf" + i); - for (int j = 0; j < params.keySize; j++) sb.append("_cf"); - columnFamilyDescriptors.add(new ColumnFamilyDescriptor(sb.toString().getBytes())); - } - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - final List columnFamilyHandles = - db.createColumnFamilies(columnFamilyDescriptors); - } - - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); - final List columnFamilyHandles = new ArrayList<>(); - try (final TransactionDB tdb = TransactionDB.open(new DBOptions().setCreateIfMissing(true), - new TransactionDBOptions(), dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - final WriteOptions writeOptions = new WriteOptions(); - try (Transaction transaction = tdb.beginTransaction(writeOptions)) { - for (int i = 0; i < params.numColumns; i++) { - transaction.put( - columnFamilyHandles.get(i), ("key" + i).getBytes(), ("value" + (i - 7)).getBytes()); - } - transaction.put("key".getBytes(), "value".getBytes()); - transaction.commit(); - } - for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - } - - final List columnFamilyHandles2 = new ArrayList<>(); - try (final TransactionDB tdb = TransactionDB.open(new DBOptions().setCreateIfMissing(true), - new TransactionDBOptions(), dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles2)) { - try (Transaction transaction = tdb.beginTransaction(new WriteOptions())) { - final ReadOptions readOptions = new ReadOptions(); - for (int i = 0; i < params.numColumns; i++) { - final byte[] value = - transaction.get(columnFamilyHandles2.get(i), readOptions, ("key" + i).getBytes()); - assertThat(value).isEqualTo(("value" + (i - 7)).getBytes()); - } - transaction.commit(); - } - for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles2) { - columnFamilyHandle.close(); - } - } - } - - @Test - public void optimisticDB() throws RocksDBException { - final List columnFamilyDescriptors = new ArrayList<>(); - for (int i = 0; i < params.numColumns; i++) { - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); - } - - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); - final List columnFamilyHandles = new ArrayList<>(); - try (final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( - new DBOptions().setCreateIfMissing(true), dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - try (Transaction transaction = otdb.beginTransaction(new WriteOptions())) { - for (int i = 0; i < params.numColumns; i++) { - transaction.put( - columnFamilyHandles.get(i), ("key" + i).getBytes(), ("value" + (i - 7)).getBytes()); - } - transaction.put("key".getBytes(), "value".getBytes()); - transaction.commit(); - } - for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - } - - final List columnFamilyHandles2 = new ArrayList<>(); - try (final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( - new DBOptions().setCreateIfMissing(true), dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles2)) { - try (Transaction transaction = otdb.beginTransaction(new WriteOptions())) { - final ReadOptions readOptions = new ReadOptions(); - for (int i = 0; i < params.numColumns; i++) { - final byte[] value = - transaction.get(columnFamilyHandles2.get(i), readOptions, ("key" + i).getBytes()); - assertThat(value).isEqualTo(("value" + (i - 7)).getBytes()); - } - transaction.commit(); - } - for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles2) { - columnFamilyHandle.close(); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/MultiGetManyKeysTest.java b/java/src/test/java/org/rocksdb/MultiGetManyKeysTest.java deleted file mode 100644 index 90a13e1da..000000000 --- a/java/src/test/java/org/rocksdb/MultiGetManyKeysTest.java +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.*; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -@RunWith(Parameterized.class) -public class MultiGetManyKeysTest { - @Parameterized.Parameters - public static List data() { - return Arrays.asList(2, 3, 250, 60000, 70000, 150000, 750000); - } - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - private final int numKeys; - - public MultiGetManyKeysTest(final Integer numKeys) { - this.numKeys = numKeys; - } - - /** - * Test for multiGet problem - */ - @Test - public void multiGetAsListLarge() throws RocksDBException { - final List keys = generateRandomKeys(numKeys); - final Map keyValues = generateRandomKeyValues(keys, 10); - putKeysAndValues(keyValues); - - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - final List values = db.multiGetAsList(keys); - assertKeysAndValues(keys, keyValues, values); - } - } - - /** - * Test for transactional multiGet - * problem - */ - @Test - public void multiGetAsListLargeTransactional() throws RocksDBException { - final List keys = generateRandomKeys(numKeys); - final Map keyValues = generateRandomKeyValues(keys, 10); - putKeysAndValues(keyValues); - - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = - TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath())) { - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final List values = transaction.multiGetAsList(new ReadOptions(), keys); - assertKeysAndValues(keys, keyValues, values); - } - } - } - - /** - * Test for transactional multiGet - * problem - */ - @Test - public void multiGetForUpdateAsListLargeTransactional() throws RocksDBException { - final List keys = generateRandomKeys(numKeys); - final Map keyValues = generateRandomKeyValues(keys, 10); - putKeysAndValues(keyValues); - - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = - TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath())) { - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final List values = transaction.multiGetForUpdateAsList(new ReadOptions(), keys); - assertKeysAndValues(keys, keyValues, values); - } - } - } - - /** - * Test for transactional multiGet - * problem - */ - @Test - public void multiGetAsListLargeTransactionalCF() throws RocksDBException { - final List keys = generateRandomKeys(numKeys); - final Map keyValues = generateRandomKeyValues(keys, 10); - final ColumnFamilyDescriptor columnFamilyDescriptor = - new ColumnFamilyDescriptor("cfTest".getBytes()); - putKeysAndValues(columnFamilyDescriptor, keyValues); - - final List columnFamilyDescriptors = new ArrayList<>(); - columnFamilyDescriptors.add(columnFamilyDescriptor); - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); - final List columnFamilyHandles = new ArrayList<>(); - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = TransactionDB.open(new DBOptions(options), txnDbOptions, - dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, columnFamilyHandles)) { - final List columnFamilyHandlesForMultiGet = new ArrayList<>(numKeys); - for (int i = 0; i < numKeys; i++) - columnFamilyHandlesForMultiGet.add(columnFamilyHandles.get(0)); - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final List values = - transaction.multiGetAsList(new ReadOptions(), columnFamilyHandlesForMultiGet, keys); - assertKeysAndValues(keys, keyValues, values); - } - for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - } - } - - /** - * Test for transactional multiGet - * problem - */ - @Test - public void multiGetForUpdateAsListLargeTransactionalCF() throws RocksDBException { - final List keys = generateRandomKeys(numKeys); - final Map keyValues = generateRandomKeyValues(keys, 10); - final ColumnFamilyDescriptor columnFamilyDescriptor = - new ColumnFamilyDescriptor("cfTest".getBytes()); - putKeysAndValues(columnFamilyDescriptor, keyValues); - - final List columnFamilyDescriptors = new ArrayList<>(); - columnFamilyDescriptors.add(columnFamilyDescriptor); - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); - final List columnFamilyHandles = new ArrayList<>(); - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = TransactionDB.open(new DBOptions(options), txnDbOptions, - dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, columnFamilyHandles)) { - final List columnFamilyHandlesForMultiGet = new ArrayList<>(numKeys); - for (int i = 0; i < numKeys; i++) - columnFamilyHandlesForMultiGet.add(columnFamilyHandles.get(0)); - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final List values = transaction.multiGetForUpdateAsList( - new ReadOptions(), columnFamilyHandlesForMultiGet, keys); - assertKeysAndValues(keys, keyValues, values); - } - for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - } - } - - private List generateRandomKeys(final int numKeys) { - final Random rand = new Random(); - final List keys = new ArrayList<>(); - for (int i = 0; i < numKeys; i++) { - final byte[] key = new byte[4]; - rand.nextBytes(key); - keys.add(key); - } - return keys; - } - - private Map generateRandomKeyValues(final List keys, final int percent) { - final Random rand = new Random(); - final Map keyValues = new HashMap<>(); - for (int i = 0; i < numKeys; i++) { - if (rand.nextInt(100) < percent) { - final byte[] value = new byte[1024]; - rand.nextBytes(value); - keyValues.put(new Key(keys.get(i)), value); - } - } - return keyValues; - } - - private void putKeysAndValues(Map keyValues) throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - for (Map.Entry keyValue : keyValues.entrySet()) { - db.put(keyValue.getKey().get(), keyValue.getValue()); - } - } - } - - private void putKeysAndValues(ColumnFamilyDescriptor columnFamilyDescriptor, - Map keyValues) throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyHandle columnFamilyHandle = - db.createColumnFamily(columnFamilyDescriptor)) { - for (Map.Entry keyValue : keyValues.entrySet()) { - db.put(columnFamilyHandle, keyValue.getKey().get(), keyValue.getValue()); - } - } - } - - private void assertKeysAndValues( - final List keys, final Map keyValues, final List values) { - assertThat(values.size()).isEqualTo(keys.size()); - for (int i = 0; i < numKeys; i++) { - final Key key = new Key(keys.get(i)); - final byte[] value = values.get(i); - if (keyValues.containsKey(key)) { - assertThat(value).isEqualTo(keyValues.get(key)); - } else { - assertThat(value).isNull(); - } - } - } - - static private class Key { - private final byte[] bytes; - public Key(byte[] bytes) { - this.bytes = bytes; - } - - public byte[] get() { - return this.bytes; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - Key key = (Key) o; - return Arrays.equals(bytes, key.bytes); - } - - @Override - public int hashCode() { - return Arrays.hashCode(bytes); - } - } -} diff --git a/java/src/test/java/org/rocksdb/MultiGetTest.java b/java/src/test/java/org/rocksdb/MultiGetTest.java deleted file mode 100644 index c391d81f6..000000000 --- a/java/src/test/java/org/rocksdb/MultiGetTest.java +++ /dev/null @@ -1,530 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.TestUtil; - -public class MultiGetTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void putNThenMultiGet() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put("key3".getBytes(), "value3ForKey3".getBytes()); - final List keys = - Arrays.asList("key1".getBytes(), "key2".getBytes(), "key3".getBytes()); - final List values = db.multiGetAsList(keys); - assertThat(values.size()).isEqualTo(keys.size()); - assertThat(values.get(0)).isEqualTo("value1ForKey1".getBytes()); - assertThat(values.get(1)).isEqualTo("value2ForKey2".getBytes()); - assertThat(values.get(2)).isEqualTo("value3ForKey3".getBytes()); - } - } - - @Test - public void putNThenMultiGetDirect() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put("key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - { - final List results = db.multiGetByteBuffers(keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - - { - final List results = - db.multiGetByteBuffers(new ReadOptions(), keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - } - } - - @Test - public void putNThenMultiGetDirectSliced() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put("key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - keys.add( - ByteBuffer.allocateDirect(12).put("prefix1".getBytes()).slice().put("key1".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - { - final List results = db.multiGetByteBuffers(keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value3ForKey3".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value1ForKey1".getBytes()); - } - } - } - - @Test - public void putNThenMultiGetDirectBadValuesArray() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put("key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - - { - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - values.remove(0); - - try { - db.multiGetByteBuffers(keys, values); - fail("Expected exception when not enough value ByteBuffers supplied"); - } catch (final IllegalArgumentException e) { - assertThat(e.getMessage()).contains("For each key there must be a corresponding value"); - } - } - - { - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - values.add(ByteBuffer.allocateDirect(24)); - - try { - db.multiGetByteBuffers(keys, values); - fail("Expected exception when too many value ByteBuffers supplied"); - } catch (final IllegalArgumentException e) { - assertThat(e.getMessage()).contains("For each key there must be a corresponding value"); - } - } - } - } - - @Test - public void putNThenMultiGetDirectShortValueBuffers() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put("key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - - { - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(4)); - } - - final List statii = db.multiGetByteBuffers(keys, values); - assertThat(statii.size()).isEqualTo(values.size()); - for (final ByteBufferGetStatus status : statii) { - assertThat(status.status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(status.requiredSize).isEqualTo("value3ForKey3".getBytes().length); - final ByteBuffer expected = - ByteBuffer.allocateDirect(24).put(Arrays.copyOf("valueX".getBytes(), 4)); - expected.flip(); - assertThat(status.value).isEqualTo(expected); - } - } - } - } - - @Test - public void putNThenMultiGetDirectNondefaultCF() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - final List cfDescriptors = new ArrayList<>(0); - cfDescriptors.add(new ColumnFamilyDescriptor("cf0".getBytes())); - cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes())); - cfDescriptors.add(new ColumnFamilyDescriptor("cf2".getBytes())); - - final List cf = db.createColumnFamilies(cfDescriptors); - - db.put(cf.get(0), "key1".getBytes(), "value1ForKey1".getBytes()); - db.put(cf.get(0), "key2".getBytes(), "value2ForKey2".getBytes()); - db.put(cf.get(0), "key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - { - final List results = db.multiGetByteBuffers(keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.NotFound); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.NotFound); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.NotFound); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(cf.get(0)); - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(cf.get(0)); - columnFamilyHandles.add(cf.get(0)); - columnFamilyHandles.add(cf.get(0)); - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - } - } - - @Test - public void putNThenMultiGetDirectCFParams() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put("key3".getBytes(), "value3ForKey3".getBytes()); - - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(db.getDefaultColumnFamily()); - columnFamilyHandles.add(db.getDefaultColumnFamily()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - try { - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - fail("Expected exception when 2 column families supplied"); - } catch (final IllegalArgumentException e) { - assertThat(e.getMessage()).contains("Wrong number of ColumnFamilyHandle(s) supplied"); - } - - columnFamilyHandles.clear(); - columnFamilyHandles.add(db.getDefaultColumnFamily()); - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)).isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)).isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)).isEqualTo("value3ForKey3".getBytes()); - } - } - - @Test - public void putNThenMultiGetDirectMixedCF() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor("cf0".getBytes())); - cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes())); - cfDescriptors.add(new ColumnFamilyDescriptor("cf2".getBytes())); - cfDescriptors.add(new ColumnFamilyDescriptor("cf3".getBytes())); - - final List cf = db.createColumnFamilies(cfDescriptors); - - db.put(cf.get(1), "key1".getBytes(), "value1ForKey1".getBytes()); - db.put("key2".getBytes(), "value2ForKey2".getBytes()); - db.put(cf.get(3), "key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(db.getDefaultColumnFamily()); - - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.NotFound); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.NotFound); - - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value2ForKey2".getBytes()); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(cf.get(1)); - - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.NotFound); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.NotFound); - - assertThat(results.get(0).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(cf.get(1)); - columnFamilyHandles.add(db.getDefaultColumnFamily()); - columnFamilyHandles.add(cf.get(3)); - - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize).isEqualTo("value2ForKey2".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("value2ForKey2".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(db.getDefaultColumnFamily()); - columnFamilyHandles.add(cf.get(1)); - columnFamilyHandles.add(cf.get(3)); - - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.NotFound); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.NotFound); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - } - } - - @Test - public void putNThenMultiGetDirectTruncateCF() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor("cf0".getBytes())); - - final List cf = db.createColumnFamilies(cfDescriptors); - - db.put(cf.get(0), "key1".getBytes(), "value1ForKey1".getBytes()); - db.put(cf.get(0), "key2".getBytes(), "value2ForKey2WithLotsOfTrailingGarbage".getBytes()); - db.put(cf.get(0), "key3".getBytes(), "value3ForKey3".getBytes()); - - final List keys = new ArrayList<>(); - keys.add(ByteBuffer.allocateDirect(12).put("key1".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key2".getBytes())); - keys.add(ByteBuffer.allocateDirect(12).put("key3".getBytes())); - // Java8 and lower flip() returns Buffer not ByteBuffer, so can't chain above /\/\ - for (final ByteBuffer key : keys) { - key.flip(); - } - final List values = new ArrayList<>(); - for (int i = 0; i < keys.size(); i++) { - values.add(ByteBuffer.allocateDirect(24)); - } - - { - final List columnFamilyHandles = new ArrayList<>(); - columnFamilyHandles.add(cf.get(0)); - final List results = - db.multiGetByteBuffers(columnFamilyHandles, keys, values); - - assertThat(results.get(0).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(1).status.getCode()).isEqualTo(Status.Code.Ok); - assertThat(results.get(2).status.getCode()).isEqualTo(Status.Code.Ok); - - assertThat(results.get(0).requiredSize).isEqualTo("value1ForKey1".getBytes().length); - assertThat(results.get(1).requiredSize) - .isEqualTo("value2ForKey2WithLotsOfTrailingGarbage".getBytes().length); - assertThat(results.get(2).requiredSize).isEqualTo("value3ForKey3".getBytes().length); - - assertThat(TestUtil.bufferBytes(results.get(0).value)) - .isEqualTo("value1ForKey1".getBytes()); - assertThat(TestUtil.bufferBytes(results.get(1).value)) - .isEqualTo("valu e2Fo rKey 2Wit hLot sOfT".replace(" ", "").getBytes()); - assertThat(TestUtil.bufferBytes(results.get(2).value)) - .isEqualTo("value3ForKey3".getBytes()); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java b/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java deleted file mode 100644 index b2b2599a7..000000000 --- a/java/src/test/java/org/rocksdb/MutableColumnFamilyOptionsTest.java +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.Test; -import org.rocksdb.MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder; - -import java.util.NoSuchElementException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MutableColumnFamilyOptionsTest { - - @Test - public void builder() { - final MutableColumnFamilyOptionsBuilder builder = - MutableColumnFamilyOptions.builder(); - builder - .setWriteBufferSize(10) - .setInplaceUpdateNumLocks(5) - .setDisableAutoCompactions(true) - .setParanoidFileChecks(true); - - assertThat(builder.writeBufferSize()).isEqualTo(10); - assertThat(builder.inplaceUpdateNumLocks()).isEqualTo(5); - assertThat(builder.disableAutoCompactions()).isEqualTo(true); - assertThat(builder.paranoidFileChecks()).isEqualTo(true); - } - - @Test(expected = NoSuchElementException.class) - public void builder_getWhenNotSet() { - final MutableColumnFamilyOptionsBuilder builder = - MutableColumnFamilyOptions.builder(); - - builder.writeBufferSize(); - } - - @Test - public void builder_build() { - final MutableColumnFamilyOptions options = MutableColumnFamilyOptions - .builder() - .setWriteBufferSize(10) - .setParanoidFileChecks(true) - .build(); - - assertThat(options.getKeys().length).isEqualTo(2); - assertThat(options.getValues().length).isEqualTo(2); - assertThat(options.getKeys()[0]) - .isEqualTo( - MutableColumnFamilyOptions.MemtableOption.write_buffer_size.name()); - assertThat(options.getValues()[0]).isEqualTo("10"); - assertThat(options.getKeys()[1]) - .isEqualTo( - MutableColumnFamilyOptions.MiscOption.paranoid_file_checks.name()); - assertThat(options.getValues()[1]).isEqualTo("true"); - } - - @Test - public void mutableColumnFamilyOptions_toString() { - final String str = MutableColumnFamilyOptions.builder() - .setWriteBufferSize(10) - .setInplaceUpdateNumLocks(5) - .setDisableAutoCompactions(true) - .setParanoidFileChecks(true) - .setMaxBytesForLevelMultiplierAdditional(new int[] {2, 3, 5, 7, 11, 13}) - .build() - .toString(); - - assertThat(str).isEqualTo("write_buffer_size=10;inplace_update_num_locks=5;" - + "disable_auto_compactions=true;paranoid_file_checks=true;max_bytes_for_level_multiplier_additional=2:3:5:7:11:13"); - } - - @Test - public void mutableColumnFamilyOptions_parse() { - final String str = "write_buffer_size=10;inplace_update_num_locks=5;" - + "disable_auto_compactions=true;paranoid_file_checks=true;max_bytes_for_level_multiplier_additional=2:{3}:{5}:{7}:{11}:{13}"; - - final MutableColumnFamilyOptionsBuilder builder = - MutableColumnFamilyOptions.parse(str); - - assertThat(builder.writeBufferSize()).isEqualTo(10); - assertThat(builder.inplaceUpdateNumLocks()).isEqualTo(5); - assertThat(builder.disableAutoCompactions()).isEqualTo(true); - assertThat(builder.paranoidFileChecks()).isEqualTo(true); - assertThat(builder.maxBytesForLevelMultiplierAdditional()) - .isEqualTo(new int[] {2, 3, 5, 7, 11, 13}); - } - - /** - * Extended parsing test to deal with all the options which C++ may return. - * We have canned a set of options returned by {RocksDB#getOptions} - */ - @Test - public void mutableColumnFamilyOptions_parse_getOptions_output() { - final String optionsString = - "bottommost_compression=kDisableCompressionOption; sample_for_compression=0; " - + "blob_garbage_collection_age_cutoff=0.250000; blob_garbage_collection_force_threshold=0.800000;" - + "arena_block_size=1048576; enable_blob_garbage_collection=false; level0_stop_writes_trigger=36; min_blob_size=65536;" - + "blob_compaction_readahead_size=262144; blob_file_starting_level=5; prepopulate_blob_cache=kDisable;" - + "compaction_options_universal={allow_trivial_move=false;stop_style=kCompactionStopStyleTotalSize;min_merge_width=2;" - + "compression_size_percent=-1;max_size_amplification_percent=200;max_merge_width=4294967295;size_ratio=1;}; " - + "target_file_size_base=67108864; max_bytes_for_level_base=268435456; memtable_whole_key_filtering=false; " - + "soft_pending_compaction_bytes_limit=68719476736; blob_compression_type=kNoCompression; max_write_buffer_number=2; " - + "ttl=2592000; compaction_options_fifo={allow_compaction=false;age_for_warm=0;max_table_files_size=1073741824;}; " - + "check_flush_compaction_key_order=true; max_successive_merges=0; inplace_update_num_locks=10000; " - + "bottommost_compression_opts={enabled=false;parallel_threads=1;zstd_max_train_bytes=0;max_dict_bytes=0;" - + "strategy=0;max_dict_buffer_bytes=0;level=32767;window_bits=-14;}; " - + "target_file_size_multiplier=1; max_bytes_for_level_multiplier_additional=5:{7}:{9}:{11}:{13}:{15}:{17}; " - + "enable_blob_files=true; level0_slowdown_writes_trigger=20; compression=kLZ4HCCompression; level0_file_num_compaction_trigger=4; " - + "blob_file_size=268435456; prefix_extractor=nullptr; max_bytes_for_level_multiplier=10.000000; write_buffer_size=67108864; " - + "disable_auto_compactions=false; max_compaction_bytes=1677721600; memtable_huge_page_size=0; " - + "compression_opts={enabled=false;parallel_threads=1;zstd_max_train_bytes=0;max_dict_bytes=0;strategy=0;max_dict_buffer_bytes=0;" - + "level=32767;window_bits=-14;}; " - + "hard_pending_compaction_bytes_limit=274877906944; periodic_compaction_seconds=0; paranoid_file_checks=true; " - + "memtable_prefix_bloom_size_ratio=7.500000; max_sequential_skip_in_iterations=8; report_bg_io_stats=true; " - + "compaction_pri=kMinOverlappingRatio; compaction_style=kCompactionStyleLevel; memtable_factory=SkipListFactory; " - + "comparator=leveldb.BytewiseComparator; bloom_locality=0; compaction_filter_factory=nullptr; " - + "min_write_buffer_number_to_merge=1; max_write_buffer_number_to_maintain=0; compaction_filter=nullptr; merge_operator=nullptr; " - + "num_levels=7; optimize_filters_for_hits=false; force_consistency_checks=true; table_factory=BlockBasedTable; " - + "max_write_buffer_size_to_maintain=0; memtable_insert_with_hint_prefix_extractor=nullptr; level_compaction_dynamic_level_bytes=false; " - + "inplace_update_support=false; experimental_mempurge_threshold=0.003"; - - MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder cf = - MutableColumnFamilyOptions.parse(optionsString, true); - - // Check the values from the parsed string which are column family options - assertThat(cf.blobGarbageCollectionAgeCutoff()).isEqualTo(0.25); - assertThat(cf.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(cf.arenaBlockSize()).isEqualTo(1048576); - assertThat(cf.enableBlobGarbageCollection()).isEqualTo(false); - assertThat(cf.level0StopWritesTrigger()).isEqualTo(36); - assertThat(cf.minBlobSize()).isEqualTo(65536); - assertThat(cf.blobCompactionReadaheadSize()).isEqualTo(262144); - assertThat(cf.blobFileStartingLevel()).isEqualTo(5); - assertThat(cf.prepopulateBlobCache()).isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE); - assertThat(cf.targetFileSizeBase()).isEqualTo(67108864); - assertThat(cf.maxBytesForLevelBase()).isEqualTo(268435456); - assertThat(cf.softPendingCompactionBytesLimit()).isEqualTo(68719476736L); - assertThat(cf.blobCompressionType()).isEqualTo(CompressionType.NO_COMPRESSION); - assertThat(cf.maxWriteBufferNumber()).isEqualTo(2); - assertThat(cf.ttl()).isEqualTo(2592000); - assertThat(cf.maxSuccessiveMerges()).isEqualTo(0); - assertThat(cf.inplaceUpdateNumLocks()).isEqualTo(10000); - assertThat(cf.targetFileSizeMultiplier()).isEqualTo(1); - assertThat(cf.maxBytesForLevelMultiplierAdditional()) - .isEqualTo(new int[] {5, 7, 9, 11, 13, 15, 17}); - assertThat(cf.enableBlobFiles()).isEqualTo(true); - assertThat(cf.level0SlowdownWritesTrigger()).isEqualTo(20); - assertThat(cf.compressionType()).isEqualTo(CompressionType.LZ4HC_COMPRESSION); - assertThat(cf.level0FileNumCompactionTrigger()).isEqualTo(4); - assertThat(cf.blobFileSize()).isEqualTo(268435456); - assertThat(cf.maxBytesForLevelMultiplier()).isEqualTo(10.0); - assertThat(cf.writeBufferSize()).isEqualTo(67108864); - assertThat(cf.disableAutoCompactions()).isEqualTo(false); - assertThat(cf.maxCompactionBytes()).isEqualTo(1677721600); - assertThat(cf.memtableHugePageSize()).isEqualTo(0); - assertThat(cf.hardPendingCompactionBytesLimit()).isEqualTo(274877906944L); - assertThat(cf.periodicCompactionSeconds()).isEqualTo(0); - assertThat(cf.paranoidFileChecks()).isEqualTo(true); - assertThat(cf.memtablePrefixBloomSizeRatio()).isEqualTo(7.5); - assertThat(cf.experimentalMempurgeThreshold()).isEqualTo(0.003); - assertThat(cf.maxSequentialSkipInIterations()).isEqualTo(8); - assertThat(cf.reportBgIoStats()).isEqualTo(true); - } -} diff --git a/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java b/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java deleted file mode 100644 index 063a8de38..000000000 --- a/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.Test; -import org.rocksdb.MutableDBOptions.MutableDBOptionsBuilder; - -import java.util.NoSuchElementException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MutableDBOptionsTest { - - @Test - public void builder() { - final MutableDBOptionsBuilder builder = - MutableDBOptions.builder(); - builder - .setBytesPerSync(1024 * 1024 * 7) - .setMaxBackgroundJobs(5) - .setAvoidFlushDuringShutdown(false); - - assertThat(builder.bytesPerSync()).isEqualTo(1024 * 1024 * 7); - assertThat(builder.maxBackgroundJobs()).isEqualTo(5); - assertThat(builder.avoidFlushDuringShutdown()).isEqualTo(false); - } - - @Test(expected = NoSuchElementException.class) - public void builder_getWhenNotSet() { - final MutableDBOptionsBuilder builder = - MutableDBOptions.builder(); - - builder.bytesPerSync(); - } - - @Test - public void builder_build() { - final MutableDBOptions options = MutableDBOptions - .builder() - .setBytesPerSync(1024 * 1024 * 7) - .setMaxBackgroundJobs(5) - .build(); - - assertThat(options.getKeys().length).isEqualTo(2); - assertThat(options.getValues().length).isEqualTo(2); - assertThat(options.getKeys()[0]) - .isEqualTo( - MutableDBOptions.DBOption.bytes_per_sync.name()); - assertThat(options.getValues()[0]).isEqualTo("7340032"); - assertThat(options.getKeys()[1]) - .isEqualTo( - MutableDBOptions.DBOption.max_background_jobs.name()); - assertThat(options.getValues()[1]).isEqualTo("5"); - } - - @Test - public void mutableDBOptions_toString() { - final String str = MutableDBOptions - .builder() - .setMaxOpenFiles(99) - .setDelayedWriteRate(789) - .setAvoidFlushDuringShutdown(true) - .setStrictBytesPerSync(true) - .build() - .toString(); - - assertThat(str).isEqualTo("max_open_files=99;delayed_write_rate=789;" - + "avoid_flush_during_shutdown=true;strict_bytes_per_sync=true"); - } - - @Test - public void mutableDBOptions_parse() { - final String str = "max_open_files=99;delayed_write_rate=789;" - + "avoid_flush_during_shutdown=true"; - - final MutableDBOptionsBuilder builder = - MutableDBOptions.parse(str); - - assertThat(builder.maxOpenFiles()).isEqualTo(99); - assertThat(builder.delayedWriteRate()).isEqualTo(789); - assertThat(builder.avoidFlushDuringShutdown()).isEqualTo(true); - } -} diff --git a/java/src/test/java/org/rocksdb/MutableOptionsGetSetTest.java b/java/src/test/java/org/rocksdb/MutableOptionsGetSetTest.java deleted file mode 100644 index 6db940619..000000000 --- a/java/src/test/java/org/rocksdb/MutableOptionsGetSetTest.java +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class MutableOptionsGetSetTest { - final int minBlobSize = 65536; - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - /** - * Validate the round-trip of blob options into and out of the C++ core of RocksDB - * From CF options on CF Creation to {RocksDB#getOptions} - * Uses 2x column families with different values for their options. - * NOTE that some constraints are applied to the options in the C++ core, - * e.g. on {ColumnFamilyOptions#setMemtablePrefixBloomSizeRatio} - * - * @throws RocksDBException if the database throws an exception - */ - @Test - public void testGetMutableBlobOptionsAfterCreate() throws RocksDBException { - final ColumnFamilyOptions columnFamilyOptions0 = new ColumnFamilyOptions(); - final ColumnFamilyDescriptor columnFamilyDescriptor0 = - new ColumnFamilyDescriptor("default".getBytes(UTF_8), columnFamilyOptions0); - final List columnFamilyDescriptors = - Collections.singletonList(columnFamilyDescriptor0); - final List columnFamilyHandles = new ArrayList<>(); - - try (final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(dbOptions, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - try (final ColumnFamilyOptions columnFamilyOptions1 = - new ColumnFamilyOptions() - .setMinBlobSize(minBlobSize) - .setEnableBlobFiles(true) - .setBlobGarbageCollectionAgeCutoff(0.25) - .setBlobGarbageCollectionForceThreshold(0.80) - .setBlobCompactionReadaheadSize(262144) - .setBlobFileStartingLevel(2) - .setArenaBlockSize(42) - .setMemtablePrefixBloomSizeRatio(0.17) - .setExperimentalMempurgeThreshold(0.005) - .setMemtableWholeKeyFiltering(false) - .setMemtableHugePageSize(3) - .setMaxSuccessiveMerges(4) - .setMaxWriteBufferNumber(12) - .setInplaceUpdateNumLocks(16) - .setDisableAutoCompactions(false) - .setSoftPendingCompactionBytesLimit(112) - .setHardPendingCompactionBytesLimit(280) - .setLevel0FileNumCompactionTrigger(200) - .setLevel0SlowdownWritesTrigger(312) - .setLevel0StopWritesTrigger(584) - .setMaxCompactionBytes(12) - .setTargetFileSizeBase(99) - .setTargetFileSizeMultiplier(112) - .setMaxSequentialSkipInIterations(50) - .setReportBgIoStats(true); - - final ColumnFamilyOptions columnFamilyOptions2 = - new ColumnFamilyOptions() - .setMinBlobSize(minBlobSize) - .setEnableBlobFiles(false) - .setArenaBlockSize(42) - .setMemtablePrefixBloomSizeRatio(0.236) - .setExperimentalMempurgeThreshold(0.247) - .setMemtableWholeKeyFiltering(true) - .setMemtableHugePageSize(8) - .setMaxSuccessiveMerges(12) - .setMaxWriteBufferNumber(22) - .setInplaceUpdateNumLocks(160) - .setDisableAutoCompactions(true) - .setSoftPendingCompactionBytesLimit(1124) - .setHardPendingCompactionBytesLimit(2800) - .setLevel0FileNumCompactionTrigger(2000) - .setLevel0SlowdownWritesTrigger(5840) - .setLevel0StopWritesTrigger(31200) - .setMaxCompactionBytes(112) - .setTargetFileSizeBase(999) - .setTargetFileSizeMultiplier(1120) - .setMaxSequentialSkipInIterations(24) - .setReportBgIoStats(true)) { - final ColumnFamilyDescriptor columnFamilyDescriptor1 = - new ColumnFamilyDescriptor("column_family_1".getBytes(UTF_8), columnFamilyOptions1); - final ColumnFamilyDescriptor columnFamilyDescriptor2 = - new ColumnFamilyDescriptor("column_family_2".getBytes(UTF_8), columnFamilyOptions2); - - // Create the column family with blob options - final ColumnFamilyHandle columnFamilyHandle1 = - db.createColumnFamily(columnFamilyDescriptor1); - final ColumnFamilyHandle columnFamilyHandle2 = - db.createColumnFamily(columnFamilyDescriptor2); - - // Check the getOptions() brings back the creation options for CF1 - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder1 = - db.getOptions(columnFamilyHandle1); - assertThat(builder1.enableBlobFiles()).isEqualTo(true); - assertThat(builder1.blobGarbageCollectionAgeCutoff()).isEqualTo(0.25); - assertThat(builder1.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(builder1.blobCompactionReadaheadSize()).isEqualTo(262144); - assertThat(builder1.blobFileStartingLevel()).isEqualTo(2); - assertThat(builder1.minBlobSize()).isEqualTo(minBlobSize); - assertThat(builder1.arenaBlockSize()).isEqualTo(42); - assertThat(builder1.memtablePrefixBloomSizeRatio()).isEqualTo(0.17); - assertThat(builder1.experimentalMempurgeThreshold()).isEqualTo(0.005); - assertThat(builder1.memtableWholeKeyFiltering()).isEqualTo(false); - assertThat(builder1.memtableHugePageSize()).isEqualTo(3); - assertThat(builder1.maxSuccessiveMerges()).isEqualTo(4); - assertThat(builder1.maxWriteBufferNumber()).isEqualTo(12); - assertThat(builder1.inplaceUpdateNumLocks()).isEqualTo(16); - assertThat(builder1.disableAutoCompactions()).isEqualTo(false); - assertThat(builder1.softPendingCompactionBytesLimit()).isEqualTo(112); - assertThat(builder1.hardPendingCompactionBytesLimit()).isEqualTo(280); - assertThat(builder1.level0FileNumCompactionTrigger()).isEqualTo(200); - assertThat(builder1.level0SlowdownWritesTrigger()).isEqualTo(312); - assertThat(builder1.level0StopWritesTrigger()).isEqualTo(584); - assertThat(builder1.maxCompactionBytes()).isEqualTo(12); - assertThat(builder1.targetFileSizeBase()).isEqualTo(99); - assertThat(builder1.targetFileSizeMultiplier()).isEqualTo(112); - assertThat(builder1.maxSequentialSkipInIterations()).isEqualTo(50); - assertThat(builder1.reportBgIoStats()).isEqualTo(true); - - // Check the getOptions() brings back the creation options for CF2 - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder2 = - db.getOptions(columnFamilyHandle2); - assertThat(builder2.enableBlobFiles()).isEqualTo(false); - assertThat(builder2.minBlobSize()).isEqualTo(minBlobSize); - assertThat(builder2.arenaBlockSize()).isEqualTo(42); - assertThat(builder2.memtablePrefixBloomSizeRatio()).isEqualTo(0.236); - assertThat(builder2.experimentalMempurgeThreshold()).isEqualTo(0.247); - assertThat(builder2.memtableWholeKeyFiltering()).isEqualTo(true); - assertThat(builder2.memtableHugePageSize()).isEqualTo(8); - assertThat(builder2.maxSuccessiveMerges()).isEqualTo(12); - assertThat(builder2.maxWriteBufferNumber()).isEqualTo(22); - assertThat(builder2.inplaceUpdateNumLocks()).isEqualTo(160); - assertThat(builder2.disableAutoCompactions()).isEqualTo(true); - assertThat(builder2.softPendingCompactionBytesLimit()).isEqualTo(1124); - assertThat(builder2.hardPendingCompactionBytesLimit()).isEqualTo(2800); - assertThat(builder2.level0FileNumCompactionTrigger()).isEqualTo(2000); - assertThat(builder2.level0SlowdownWritesTrigger()).isEqualTo(5840); - assertThat(builder2.level0StopWritesTrigger()).isEqualTo(31200); - assertThat(builder2.maxCompactionBytes()).isEqualTo(112); - assertThat(builder2.targetFileSizeBase()).isEqualTo(999); - assertThat(builder2.targetFileSizeMultiplier()).isEqualTo(1120); - assertThat(builder2.maxSequentialSkipInIterations()).isEqualTo(24); - assertThat(builder2.reportBgIoStats()).isEqualTo(true); - } - } - } - - /** - * Validate the round-trip of blob options into and out of the C++ core of RocksDB - * From {RocksDB#setOptions} to {RocksDB#getOptions} - * Uses 2x column families with different values for their options. - * NOTE that some constraints are applied to the options in the C++ core, - * e.g. on {ColumnFamilyOptions#setMemtablePrefixBloomSizeRatio} - * - * @throws RocksDBException if a database access has an error - */ - @Test - public void testGetMutableBlobOptionsAfterSetCF() throws RocksDBException { - final ColumnFamilyOptions columnFamilyOptions0 = new ColumnFamilyOptions(); - final ColumnFamilyDescriptor columnFamilyDescriptor0 = - new ColumnFamilyDescriptor("default".getBytes(UTF_8), columnFamilyOptions0); - final List columnFamilyDescriptors = - Collections.singletonList(columnFamilyDescriptor0); - final List columnFamilyHandles = new ArrayList<>(); - - try (final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(dbOptions, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - try (final ColumnFamilyOptions columnFamilyOptions1 = new ColumnFamilyOptions(); - - final ColumnFamilyOptions columnFamilyOptions2 = new ColumnFamilyOptions()) { - final ColumnFamilyDescriptor columnFamilyDescriptor1 = - new ColumnFamilyDescriptor("column_family_1".getBytes(UTF_8), columnFamilyOptions1); - final ColumnFamilyDescriptor columnFamilyDescriptor2 = - new ColumnFamilyDescriptor("column_family_2".getBytes(UTF_8), columnFamilyOptions2); - - // Create the column family with blob options - final ColumnFamilyHandle columnFamilyHandle1 = - db.createColumnFamily(columnFamilyDescriptor1); - final ColumnFamilyHandle columnFamilyHandle2 = - db.createColumnFamily(columnFamilyDescriptor2); - db.flush(new FlushOptions().setWaitForFlush(true)); - - final MutableColumnFamilyOptions - .MutableColumnFamilyOptionsBuilder mutableColumnFamilyOptions1 = - MutableColumnFamilyOptions.builder() - .setMinBlobSize(minBlobSize) - .setEnableBlobFiles(true) - .setBlobGarbageCollectionAgeCutoff(0.25) - .setBlobGarbageCollectionForceThreshold(0.80) - .setBlobCompactionReadaheadSize(262144) - .setBlobFileStartingLevel(3) - .setArenaBlockSize(42) - .setMemtablePrefixBloomSizeRatio(0.17) - .setExperimentalMempurgeThreshold(0.005) - .setMemtableWholeKeyFiltering(false) - .setMemtableHugePageSize(3) - .setMaxSuccessiveMerges(4) - .setMaxWriteBufferNumber(12) - .setInplaceUpdateNumLocks(16) - .setDisableAutoCompactions(false) - .setSoftPendingCompactionBytesLimit(112) - .setHardPendingCompactionBytesLimit(280) - .setLevel0FileNumCompactionTrigger(200) - .setLevel0SlowdownWritesTrigger(312) - .setLevel0StopWritesTrigger(584) - .setMaxCompactionBytes(12) - .setTargetFileSizeBase(99) - .setTargetFileSizeMultiplier(112); - db.setOptions(columnFamilyHandle1, mutableColumnFamilyOptions1.build()); - - // Check the getOptions() brings back the creation options for CF1 - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder1 = - db.getOptions(columnFamilyHandle1); - assertThat(builder1.enableBlobFiles()).isEqualTo(true); - assertThat(builder1.blobGarbageCollectionAgeCutoff()).isEqualTo(0.25); - assertThat(builder1.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(builder1.blobCompactionReadaheadSize()).isEqualTo(262144); - assertThat(builder1.blobFileStartingLevel()).isEqualTo(3); - assertThat(builder1.minBlobSize()).isEqualTo(minBlobSize); - assertThat(builder1.arenaBlockSize()).isEqualTo(42); - assertThat(builder1.memtablePrefixBloomSizeRatio()).isEqualTo(0.17); - assertThat(builder1.experimentalMempurgeThreshold()).isEqualTo(0.005); - assertThat(builder1.memtableWholeKeyFiltering()).isEqualTo(false); - assertThat(builder1.memtableHugePageSize()).isEqualTo(3); - assertThat(builder1.maxSuccessiveMerges()).isEqualTo(4); - assertThat(builder1.maxWriteBufferNumber()).isEqualTo(12); - assertThat(builder1.inplaceUpdateNumLocks()).isEqualTo(16); - assertThat(builder1.disableAutoCompactions()).isEqualTo(false); - assertThat(builder1.softPendingCompactionBytesLimit()).isEqualTo(112); - assertThat(builder1.hardPendingCompactionBytesLimit()).isEqualTo(280); - assertThat(builder1.level0FileNumCompactionTrigger()).isEqualTo(200); - assertThat(builder1.level0SlowdownWritesTrigger()).isEqualTo(312); - assertThat(builder1.level0StopWritesTrigger()).isEqualTo(584); - assertThat(builder1.maxCompactionBytes()).isEqualTo(12); - assertThat(builder1.targetFileSizeBase()).isEqualTo(99); - assertThat(builder1.targetFileSizeMultiplier()).isEqualTo(112); - - final MutableColumnFamilyOptions - .MutableColumnFamilyOptionsBuilder mutableColumnFamilyOptions2 = - MutableColumnFamilyOptions.builder() - .setMinBlobSize(minBlobSize) - .setEnableBlobFiles(false) - .setArenaBlockSize(42) - .setMemtablePrefixBloomSizeRatio(0.236) - .setExperimentalMempurgeThreshold(0.247) - .setMemtableWholeKeyFiltering(true) - .setMemtableHugePageSize(8) - .setMaxSuccessiveMerges(12) - .setMaxWriteBufferNumber(22) - .setInplaceUpdateNumLocks(160) - .setDisableAutoCompactions(true) - .setSoftPendingCompactionBytesLimit(1124) - .setHardPendingCompactionBytesLimit(2800) - .setLevel0FileNumCompactionTrigger(2000) - .setLevel0SlowdownWritesTrigger(5840) - .setLevel0StopWritesTrigger(31200) - .setMaxCompactionBytes(112) - .setTargetFileSizeBase(999) - .setTargetFileSizeMultiplier(1120); - db.setOptions(columnFamilyHandle2, mutableColumnFamilyOptions2.build()); - - // Check the getOptions() brings back the creation options for CF2 - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder2 = - db.getOptions(columnFamilyHandle2); - assertThat(builder2.enableBlobFiles()).isEqualTo(false); - assertThat(builder2.minBlobSize()).isEqualTo(minBlobSize); - assertThat(builder2.arenaBlockSize()).isEqualTo(42); - assertThat(builder2.memtablePrefixBloomSizeRatio()).isEqualTo(0.236); - assertThat(builder2.experimentalMempurgeThreshold()).isEqualTo(0.247); - assertThat(builder2.memtableWholeKeyFiltering()).isEqualTo(true); - assertThat(builder2.memtableHugePageSize()).isEqualTo(8); - assertThat(builder2.maxSuccessiveMerges()).isEqualTo(12); - assertThat(builder2.maxWriteBufferNumber()).isEqualTo(22); - assertThat(builder2.inplaceUpdateNumLocks()).isEqualTo(160); - assertThat(builder2.disableAutoCompactions()).isEqualTo(true); - assertThat(builder2.softPendingCompactionBytesLimit()).isEqualTo(1124); - assertThat(builder2.hardPendingCompactionBytesLimit()).isEqualTo(2800); - assertThat(builder2.level0FileNumCompactionTrigger()).isEqualTo(2000); - assertThat(builder2.level0SlowdownWritesTrigger()).isEqualTo(5840); - assertThat(builder2.level0StopWritesTrigger()).isEqualTo(31200); - assertThat(builder2.maxCompactionBytes()).isEqualTo(112); - assertThat(builder2.targetFileSizeBase()).isEqualTo(999); - assertThat(builder2.targetFileSizeMultiplier()).isEqualTo(1120); - } - } - } - - /** - * Validate the round-trip of blob options into and out of the C++ core of RocksDB - * From {RocksDB#setOptions} to {RocksDB#getOptions} - * Uses 2x column families with different values for their options. - * NOTE that some constraints are applied to the options in the C++ core, - * e.g. on {ColumnFamilyOptions#setMemtablePrefixBloomSizeRatio} - * - * @throws RocksDBException if a database access has an error - */ - @Test - public void testGetMutableBlobOptionsAfterSet() throws RocksDBException { - final ColumnFamilyOptions columnFamilyOptions0 = new ColumnFamilyOptions(); - final ColumnFamilyDescriptor columnFamilyDescriptor0 = - new ColumnFamilyDescriptor("default".getBytes(UTF_8), columnFamilyOptions0); - final List columnFamilyDescriptors = - Collections.singletonList(columnFamilyDescriptor0); - final List columnFamilyHandles = new ArrayList<>(); - - try (final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(dbOptions, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - final MutableColumnFamilyOptions - .MutableColumnFamilyOptionsBuilder mutableColumnFamilyOptions = - MutableColumnFamilyOptions.builder() - .setMinBlobSize(minBlobSize) - .setEnableBlobFiles(true) - .setBlobGarbageCollectionAgeCutoff(0.25) - .setBlobGarbageCollectionForceThreshold(0.80) - .setBlobCompactionReadaheadSize(131072) - .setBlobFileStartingLevel(4) - .setArenaBlockSize(42) - .setMemtablePrefixBloomSizeRatio(0.17) - .setExperimentalMempurgeThreshold(0.005) - .setMemtableWholeKeyFiltering(false) - .setMemtableHugePageSize(3) - .setMaxSuccessiveMerges(4) - .setMaxWriteBufferNumber(12) - .setInplaceUpdateNumLocks(16) - .setDisableAutoCompactions(false) - .setSoftPendingCompactionBytesLimit(112) - .setHardPendingCompactionBytesLimit(280) - .setLevel0FileNumCompactionTrigger(200) - .setLevel0SlowdownWritesTrigger(312) - .setLevel0StopWritesTrigger(584) - .setMaxCompactionBytes(12) - .setTargetFileSizeBase(99) - .setTargetFileSizeMultiplier(112); - db.setOptions(mutableColumnFamilyOptions.build()); - - // Check the getOptions() brings back the creation options for CF1 - final MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder builder1 = db.getOptions(); - assertThat(builder1.enableBlobFiles()).isEqualTo(true); - assertThat(builder1.blobGarbageCollectionAgeCutoff()).isEqualTo(0.25); - assertThat(builder1.blobGarbageCollectionForceThreshold()).isEqualTo(0.80); - assertThat(builder1.blobCompactionReadaheadSize()).isEqualTo(131072); - assertThat(builder1.blobFileStartingLevel()).isEqualTo(4); - assertThat(builder1.minBlobSize()).isEqualTo(minBlobSize); - assertThat(builder1.arenaBlockSize()).isEqualTo(42); - assertThat(builder1.memtablePrefixBloomSizeRatio()).isEqualTo(0.17); - assertThat(builder1.experimentalMempurgeThreshold()).isEqualTo(0.005); - assertThat(builder1.memtableWholeKeyFiltering()).isEqualTo(false); - assertThat(builder1.memtableHugePageSize()).isEqualTo(3); - assertThat(builder1.maxSuccessiveMerges()).isEqualTo(4); - assertThat(builder1.maxWriteBufferNumber()).isEqualTo(12); - assertThat(builder1.inplaceUpdateNumLocks()).isEqualTo(16); - assertThat(builder1.disableAutoCompactions()).isEqualTo(false); - assertThat(builder1.softPendingCompactionBytesLimit()).isEqualTo(112); - assertThat(builder1.hardPendingCompactionBytesLimit()).isEqualTo(280); - assertThat(builder1.level0FileNumCompactionTrigger()).isEqualTo(200); - assertThat(builder1.level0SlowdownWritesTrigger()).isEqualTo(312); - assertThat(builder1.level0StopWritesTrigger()).isEqualTo(584); - assertThat(builder1.maxCompactionBytes()).isEqualTo(12); - assertThat(builder1.targetFileSizeBase()).isEqualTo(99); - assertThat(builder1.targetFileSizeMultiplier()).isEqualTo(112); - } - } - - @Test - public void testGetMutableDBOptionsAfterSet() throws RocksDBException { - final ColumnFamilyOptions columnFamilyOptions0 = new ColumnFamilyOptions(); - final ColumnFamilyDescriptor columnFamilyDescriptor0 = - new ColumnFamilyDescriptor("default".getBytes(UTF_8), columnFamilyOptions0); - final List columnFamilyDescriptors = - Collections.singletonList(columnFamilyDescriptor0); - final List columnFamilyHandles = new ArrayList<>(); - - try (final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(dbOptions, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - final MutableDBOptions.MutableDBOptionsBuilder mutableDBOptions = - MutableDBOptions.builder() - .setMaxBackgroundJobs(16) - .setAvoidFlushDuringShutdown(true) - .setWritableFileMaxBufferSize(2097152) - .setDelayedWriteRate(67108864) - .setMaxTotalWalSize(16777216) - .setDeleteObsoleteFilesPeriodMicros(86400000000L) - .setStatsDumpPeriodSec(1200) - .setStatsPersistPeriodSec(7200) - .setStatsHistoryBufferSize(6291456) - .setMaxOpenFiles(8) - .setBytesPerSync(4194304) - .setWalBytesPerSync(1048576) - .setStrictBytesPerSync(true) - .setCompactionReadaheadSize(1024); - - db.setDBOptions(mutableDBOptions.build()); - - final MutableDBOptions.MutableDBOptionsBuilder getBuilder = db.getDBOptions(); - assertThat(getBuilder.maxBackgroundJobs()).isEqualTo(16); // 4 - assertThat(getBuilder.avoidFlushDuringShutdown()).isEqualTo(true); // false - assertThat(getBuilder.writableFileMaxBufferSize()).isEqualTo(2097152); // 1048576 - assertThat(getBuilder.delayedWriteRate()).isEqualTo(67108864); // 16777216 - assertThat(getBuilder.maxTotalWalSize()).isEqualTo(16777216); - assertThat(getBuilder.deleteObsoleteFilesPeriodMicros()) - .isEqualTo(86400000000L); // 21600000000 - assertThat(getBuilder.statsDumpPeriodSec()).isEqualTo(1200); // 600 - assertThat(getBuilder.statsPersistPeriodSec()).isEqualTo(7200); // 600 - assertThat(getBuilder.statsHistoryBufferSize()).isEqualTo(6291456); // 1048576 - assertThat(getBuilder.maxOpenFiles()).isEqualTo(8); //-1 - assertThat(getBuilder.bytesPerSync()).isEqualTo(4194304); // 1048576 - assertThat(getBuilder.walBytesPerSync()).isEqualTo(1048576); // 0 - assertThat(getBuilder.strictBytesPerSync()).isEqualTo(true); // false - assertThat(getBuilder.compactionReadaheadSize()).isEqualTo(1024); // 0 - } - } -} diff --git a/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java b/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java deleted file mode 100644 index 970e58c0c..000000000 --- a/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.*; -import java.util.Comparator; - -import static org.junit.Assert.assertEquals; - -public class NativeComparatorWrapperTest { - static { - RocksDB.loadLibrary(); - } - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - private static final Random random = new Random(); - - @Test - public void rountrip() throws RocksDBException { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - final int ITERATIONS = 1_000; - - final String[] storedKeys = new String[ITERATIONS]; - try (final NativeStringComparatorWrapper comparator = new NativeStringComparatorWrapper(); - final Options opt = new Options() - .setCreateIfMissing(true) - .setComparator(comparator)) { - - // store random integer keys - try (final RocksDB db = RocksDB.open(opt, dbPath)) { - for (int i = 0; i < ITERATIONS; i++) { - final String strKey = randomString(); - final byte key[] = strKey.getBytes(); - // does key already exist (avoid duplicates) - if (i > 0 && db.get(key) != null) { - i--; // generate a different key - } else { - db.put(key, "value".getBytes()); - storedKeys[i] = strKey; - } - } - } - - // sort the stored keys into ascending alpha-numeric order - Arrays.sort(storedKeys, new Comparator() { - @Override - public int compare(final String o1, final String o2) { - return o1.compareTo(o2); - } - }); - - // re-open db and read from start to end - // string keys should be in ascending - // order - try (final RocksDB db = RocksDB.open(opt, dbPath); - final RocksIterator it = db.newIterator()) { - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - final String strKey = new String(it.key()); - assertEquals(storedKeys[count++], strKey); - } - } - } - } - - private String randomString() { - final char[] chars = new char[12]; - for(int i = 0; i < 12; i++) { - final int letterCode = random.nextInt(24); - final char letter = (char) (((int) 'a') + letterCode); - chars[i] = letter; - } - return String.copyValueOf(chars); - } - - public static class NativeStringComparatorWrapper - extends NativeComparatorWrapper { - - @Override - protected long initializeNative(final long... nativeParameterHandles) { - return newStringComparator(); - } - - private native long newStringComparator(); - } -} diff --git a/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java b/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java deleted file mode 100644 index ab60081a0..000000000 --- a/java/src/test/java/org/rocksdb/NativeLibraryLoaderTest.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.Environment; - -import java.io.File; -import java.io.IOException; -import java.nio.file.*; - -import static org.assertj.core.api.Assertions.assertThat; - -public class NativeLibraryLoaderTest { - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Test - public void tempFolder() throws IOException { - NativeLibraryLoader.getInstance().loadLibraryFromJarToTemp( - temporaryFolder.getRoot().getAbsolutePath()); - final Path path = Paths.get(temporaryFolder.getRoot().getAbsolutePath(), - Environment.getJniLibraryFileName("rocksdb")); - assertThat(Files.exists(path)).isTrue(); - assertThat(Files.isReadable(path)).isTrue(); - } - - @Test - public void overridesExistingLibrary() throws IOException { - File first = NativeLibraryLoader.getInstance().loadLibraryFromJarToTemp( - temporaryFolder.getRoot().getAbsolutePath()); - NativeLibraryLoader.getInstance().loadLibraryFromJarToTemp( - temporaryFolder.getRoot().getAbsolutePath()); - assertThat(first.exists()).isTrue(); - } -} diff --git a/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java b/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java deleted file mode 100644 index 519b70b1d..000000000 --- a/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class OptimisticTransactionDBTest { - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void open() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final OptimisticTransactionDB otdb = OptimisticTransactionDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(otdb).isNotNull(); - } - } - - @Test - public void open_columnFamilies() throws RocksDBException { - try(final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions myCfOpts = new ColumnFamilyOptions()) { - - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("myCf".getBytes(), myCfOpts)); - - final List columnFamilyHandles = new ArrayList<>(); - - try (final OptimisticTransactionDB otdb = OptimisticTransactionDB.open(dbOptions, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - try { - assertThat(otdb).isNotNull(); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void beginTransaction() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( - options, dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions()) { - - try(final Transaction txn = otdb.beginTransaction(writeOptions)) { - assertThat(txn).isNotNull(); - } - } - } - - @Test - public void beginTransaction_transactionOptions() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( - options, dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions(); - final OptimisticTransactionOptions optimisticTxnOptions = - new OptimisticTransactionOptions()) { - - try(final Transaction txn = otdb.beginTransaction(writeOptions, - optimisticTxnOptions)) { - assertThat(txn).isNotNull(); - } - } - } - - @Test - public void beginTransaction_withOld() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( - options, dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions()) { - - try(final Transaction txn = otdb.beginTransaction(writeOptions)) { - final Transaction txnReused = otdb.beginTransaction(writeOptions, txn); - assertThat(txnReused).isSameAs(txn); - } - } - } - - @Test - public void beginTransaction_withOld_transactionOptions() - throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( - options, dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions(); - final OptimisticTransactionOptions optimisticTxnOptions = - new OptimisticTransactionOptions()) { - - try(final Transaction txn = otdb.beginTransaction(writeOptions)) { - final Transaction txnReused = otdb.beginTransaction(writeOptions, - optimisticTxnOptions, txn); - assertThat(txnReused).isSameAs(txn); - } - } - } - - @Test - public void baseDB() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final OptimisticTransactionDB otdb = OptimisticTransactionDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(otdb).isNotNull(); - final RocksDB db = otdb.getBaseDB(); - assertThat(db).isNotNull(); - assertThat(db.isOwningHandle()).isFalse(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java b/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java deleted file mode 100644 index ef656b958..000000000 --- a/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; -import org.rocksdb.util.BytewiseComparator; - -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; - -public class OptimisticTransactionOptionsTest { - - private static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void setSnapshot() { - try (final OptimisticTransactionOptions opt = new OptimisticTransactionOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setSetSnapshot(boolValue); - assertThat(opt.isSetSnapshot()).isEqualTo(boolValue); - } - } - - @Test - public void comparator() { - try (final OptimisticTransactionOptions opt = new OptimisticTransactionOptions(); - final ComparatorOptions copt = new ComparatorOptions() - .setUseDirectBuffer(true); - final AbstractComparator comparator = new BytewiseComparator(copt)) { - opt.setComparator(comparator); - } - } -} diff --git a/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java b/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java deleted file mode 100644 index d2f92e1ff..000000000 --- a/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; - -public class OptimisticTransactionTest extends AbstractTransactionTest { - @Test - public void prepare_commit() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.commit(); - } - - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v12); - txn.prepare(); - - failBecauseExceptionWasNotThrown(RocksDBException.class); - } catch (final RocksDBException e) { - assertThat(e.getMessage()) - .contains("Two phase commit not supported for optimistic transactions"); - } - } - } - - @Test - public void getForUpdate_cf_conflict() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, k1, v1); - assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(testCf, k1, v12); - assertThat(txn2.get(testCf, readOptions, k1)).isEqualTo(v12); - txn2.commit(); - - try { - txn3.commit(); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void getForUpdate_conflict() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(k1, v12); - assertThat(txn2.get(readOptions, k1)).isEqualTo(v12); - txn2.commit(); - - try { - txn3.commit(); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Deprecated - @Test - public void multiGetForUpdate_cf_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdate(readOptions, cfList, keys)) - .isEqualTo(values); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(testCf, keys[0], otherValue); - assertThat(txn2.get(testCf, readOptions, keys[0])) - .isEqualTo(otherValue); - txn2.commit(); - - try { - txn3.commit(); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void multiGetAsListForUpdate_cf_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(values); - txn.commit(); - } - - try (final Transaction txn2 = dbContainer.beginTransaction()) { - try (final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdateAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(values); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(testCf, keys[0], otherValue); - assertThat(txn2.get(testCf, readOptions, keys[0])).isEqualTo(otherValue); - txn2.commit(); - - try { - txn3.commit(); // should cause an exception! - } catch (final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" - + "transactions"); - } - } - - @Deprecated - @Test - public void multiGetForUpdate_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdate(readOptions, keys)) - .isEqualTo(values); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(keys[0], otherValue); - assertThat(txn2.get(readOptions, keys[0])) - .isEqualTo(otherValue); - txn2.commit(); - - try { - txn3.commit(); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void multiGetasListForUpdate_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, Arrays.asList(keys))).containsExactly(values); - txn.commit(); - } - - try (final Transaction txn2 = dbContainer.beginTransaction()) { - try (final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdateAsList(readOptions, Arrays.asList(keys))) - .containsExactly(values); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(keys[0], otherValue); - assertThat(txn2.get(readOptions, keys[0])).isEqualTo(otherValue); - txn2.commit(); - - try { - txn3.commit(); // should cause an exception! - } catch (final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" - + "transactions"); - } - } - - @Test - public void undoGetForUpdate_cf_conflict() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, k1, v1); - assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); - - // undo the getForUpdate - txn3.undoGetForUpdate(testCf, k1); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(testCf, k1, v12); - assertThat(txn2.get(testCf, readOptions, k1)).isEqualTo(v12); - txn2.commit(); - - // should not cause an exception - // because we undid the getForUpdate above! - txn3.commit(); - } - } - } - } - - @Test - public void undoGetForUpdate_conflict() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1); - - // undo the getForUpdate - txn3.undoGetForUpdate(k1); - - // NOTE: txn2 updates k1, during txn3 - txn2.put(k1, v12); - assertThat(txn2.get(readOptions, k1)).isEqualTo(v12); - txn2.commit(); - - // should not cause an exception - // because we undid the getForUpdate above! - txn3.commit(); - } - } - } - } - - @Test - public void name() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getName()).isEmpty(); - final String name = "my-transaction-" + rand.nextLong(); - - try { - txn.setName(name); - fail("Optimistic transactions cannot be named."); - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isEqualTo(Status.Code.InvalidArgument); - } - } - } - - @Override - public OptimisticTransactionDBContainer startDb() - throws RocksDBException { - final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - - final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY, - columnFamilyOptions)); - final List columnFamilyHandles = new ArrayList<>(); - - final OptimisticTransactionDB optimisticTxnDb; - try { - optimisticTxnDb = OptimisticTransactionDB.open( - options, dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles); - } catch(final RocksDBException e) { - columnFamilyOptions.close(); - options.close(); - throw e; - } - - final WriteOptions writeOptions = new WriteOptions(); - final OptimisticTransactionOptions optimisticTxnOptions = - new OptimisticTransactionOptions(); - - return new OptimisticTransactionDBContainer(optimisticTxnOptions, - writeOptions, columnFamilyHandles, optimisticTxnDb, columnFamilyOptions, - options); - } - - private static class OptimisticTransactionDBContainer - extends DBContainer { - - private final OptimisticTransactionOptions optimisticTxnOptions; - private final OptimisticTransactionDB optimisticTxnDb; - - public OptimisticTransactionDBContainer( - final OptimisticTransactionOptions optimisticTxnOptions, - final WriteOptions writeOptions, - final List columnFamilyHandles, - final OptimisticTransactionDB optimisticTxnDb, - final ColumnFamilyOptions columnFamilyOptions, - final DBOptions options) { - super(writeOptions, columnFamilyHandles, columnFamilyOptions, - options); - this.optimisticTxnOptions = optimisticTxnOptions; - this.optimisticTxnDb = optimisticTxnDb; - } - - @Override - public Transaction beginTransaction() { - return optimisticTxnDb.beginTransaction(writeOptions, - optimisticTxnOptions); - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions) { - return optimisticTxnDb.beginTransaction(writeOptions, - optimisticTxnOptions); - } - - @Override - public void close() { - optimisticTxnOptions.close(); - writeOptions.close(); - for(final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - optimisticTxnDb.close(); - options.close(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/OptionsTest.java b/java/src/test/java/org/rocksdb/OptionsTest.java deleted file mode 100644 index 129f1c39a..000000000 --- a/java/src/test/java/org/rocksdb/OptionsTest.java +++ /dev/null @@ -1,1492 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.ClassRule; -import org.junit.Test; -import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory; - -public class OptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - public static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void copyConstructor() { - Options origOpts = new Options(); - origOpts.setNumLevels(rand.nextInt(8)); - origOpts.setTargetFileSizeMultiplier(rand.nextInt(100)); - origOpts.setLevel0StopWritesTrigger(rand.nextInt(50)); - Options copyOpts = new Options(origOpts); - assertThat(origOpts.numLevels()).isEqualTo(copyOpts.numLevels()); - assertThat(origOpts.targetFileSizeMultiplier()).isEqualTo(copyOpts.targetFileSizeMultiplier()); - assertThat(origOpts.level0StopWritesTrigger()).isEqualTo(copyOpts.level0StopWritesTrigger()); - } - - @Test - public void setIncreaseParallelism() { - try (final Options opt = new Options()) { - final int threads = Runtime.getRuntime().availableProcessors() * 2; - opt.setIncreaseParallelism(threads); - } - } - - @Test - public void writeBufferSize() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWriteBufferSize(longValue); - assertThat(opt.writeBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void maxWriteBufferNumber() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxWriteBufferNumber(intValue); - assertThat(opt.maxWriteBufferNumber()).isEqualTo(intValue); - } - } - - @Test - public void minWriteBufferNumberToMerge() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMinWriteBufferNumberToMerge(intValue); - assertThat(opt.minWriteBufferNumberToMerge()).isEqualTo(intValue); - } - } - - @Test - public void numLevels() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setNumLevels(intValue); - assertThat(opt.numLevels()).isEqualTo(intValue); - } - } - - @Test - public void levelZeroFileNumCompactionTrigger() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setLevelZeroFileNumCompactionTrigger(intValue); - assertThat(opt.levelZeroFileNumCompactionTrigger()).isEqualTo(intValue); - } - } - - @Test - public void levelZeroSlowdownWritesTrigger() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setLevelZeroSlowdownWritesTrigger(intValue); - assertThat(opt.levelZeroSlowdownWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void levelZeroStopWritesTrigger() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setLevelZeroStopWritesTrigger(intValue); - assertThat(opt.levelZeroStopWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void targetFileSizeBase() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setTargetFileSizeBase(longValue); - assertThat(opt.targetFileSizeBase()).isEqualTo(longValue); - } - } - - @Test - public void targetFileSizeMultiplier() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setTargetFileSizeMultiplier(intValue); - assertThat(opt.targetFileSizeMultiplier()).isEqualTo(intValue); - } - } - - @Test - public void maxBytesForLevelBase() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxBytesForLevelBase(longValue); - assertThat(opt.maxBytesForLevelBase()).isEqualTo(longValue); - } - } - - @Test - public void levelCompactionDynamicLevelBytes() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setLevelCompactionDynamicLevelBytes(boolValue); - assertThat(opt.levelCompactionDynamicLevelBytes()) - .isEqualTo(boolValue); - } - } - - @Test - public void maxBytesForLevelMultiplier() { - try (final Options opt = new Options()) { - final double doubleValue = rand.nextDouble(); - opt.setMaxBytesForLevelMultiplier(doubleValue); - assertThat(opt.maxBytesForLevelMultiplier()).isEqualTo(doubleValue); - } - } - - @Test - public void maxBytesForLevelMultiplierAdditional() { - try (final Options opt = new Options()) { - final int intValue1 = rand.nextInt(); - final int intValue2 = rand.nextInt(); - final int[] ints = new int[]{intValue1, intValue2}; - opt.setMaxBytesForLevelMultiplierAdditional(ints); - assertThat(opt.maxBytesForLevelMultiplierAdditional()).isEqualTo(ints); - } - } - - @Test - public void maxCompactionBytes() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxCompactionBytes(longValue); - assertThat(opt.maxCompactionBytes()).isEqualTo(longValue); - } - } - - @Test - public void softPendingCompactionBytesLimit() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setSoftPendingCompactionBytesLimit(longValue); - assertThat(opt.softPendingCompactionBytesLimit()).isEqualTo(longValue); - } - } - - @Test - public void hardPendingCompactionBytesLimit() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setHardPendingCompactionBytesLimit(longValue); - assertThat(opt.hardPendingCompactionBytesLimit()).isEqualTo(longValue); - } - } - - @Test - public void level0FileNumCompactionTrigger() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setLevel0FileNumCompactionTrigger(intValue); - assertThat(opt.level0FileNumCompactionTrigger()).isEqualTo(intValue); - } - } - - @Test - public void level0SlowdownWritesTrigger() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setLevel0SlowdownWritesTrigger(intValue); - assertThat(opt.level0SlowdownWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void level0StopWritesTrigger() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setLevel0StopWritesTrigger(intValue); - assertThat(opt.level0StopWritesTrigger()).isEqualTo(intValue); - } - } - - @Test - public void arenaBlockSize() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setArenaBlockSize(longValue); - assertThat(opt.arenaBlockSize()).isEqualTo(longValue); - } - } - - @Test - public void disableAutoCompactions() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setDisableAutoCompactions(boolValue); - assertThat(opt.disableAutoCompactions()).isEqualTo(boolValue); - } - } - - @Test - public void maxSequentialSkipInIterations() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxSequentialSkipInIterations(longValue); - assertThat(opt.maxSequentialSkipInIterations()).isEqualTo(longValue); - } - } - - @Test - public void inplaceUpdateSupport() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setInplaceUpdateSupport(boolValue); - assertThat(opt.inplaceUpdateSupport()).isEqualTo(boolValue); - } - } - - @Test - public void inplaceUpdateNumLocks() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setInplaceUpdateNumLocks(longValue); - assertThat(opt.inplaceUpdateNumLocks()).isEqualTo(longValue); - } - } - - @Test - public void memtablePrefixBloomSizeRatio() { - try (final Options opt = new Options()) { - final double doubleValue = rand.nextDouble(); - opt.setMemtablePrefixBloomSizeRatio(doubleValue); - assertThat(opt.memtablePrefixBloomSizeRatio()).isEqualTo(doubleValue); - } - } - - @Test - public void experimentalMempurgeThreshold() { - try (final Options opt = new Options()) { - final double doubleValue = rand.nextDouble(); - opt.setExperimentalMempurgeThreshold(doubleValue); - assertThat(opt.experimentalMempurgeThreshold()).isEqualTo(doubleValue); - } - } - - @Test - public void memtableWholeKeyFiltering() { - try (final Options opt = new Options()) { - final boolean booleanValue = rand.nextBoolean(); - opt.setMemtableWholeKeyFiltering(booleanValue); - assertThat(opt.memtableWholeKeyFiltering()).isEqualTo(booleanValue); - } - } - - @Test - public void memtableHugePageSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMemtableHugePageSize(longValue); - assertThat(opt.memtableHugePageSize()).isEqualTo(longValue); - } - } - - @Test - public void bloomLocality() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setBloomLocality(intValue); - assertThat(opt.bloomLocality()).isEqualTo(intValue); - } - } - - @Test - public void maxSuccessiveMerges() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxSuccessiveMerges(longValue); - assertThat(opt.maxSuccessiveMerges()).isEqualTo(longValue); - } - } - - @Test - public void optimizeFiltersForHits() { - try (final Options opt = new Options()) { - final boolean aBoolean = rand.nextBoolean(); - opt.setOptimizeFiltersForHits(aBoolean); - assertThat(opt.optimizeFiltersForHits()).isEqualTo(aBoolean); - } - } - - @Test - public void createIfMissing() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setCreateIfMissing(boolValue); - assertThat(opt.createIfMissing()). - isEqualTo(boolValue); - } - } - - @Test - public void createMissingColumnFamilies() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setCreateMissingColumnFamilies(boolValue); - assertThat(opt.createMissingColumnFamilies()). - isEqualTo(boolValue); - } - } - - @Test - public void errorIfExists() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setErrorIfExists(boolValue); - assertThat(opt.errorIfExists()).isEqualTo(boolValue); - } - } - - @Test - public void paranoidChecks() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setParanoidChecks(boolValue); - assertThat(opt.paranoidChecks()). - isEqualTo(boolValue); - } - } - - @Test - public void maxTotalWalSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxTotalWalSize(longValue); - assertThat(opt.maxTotalWalSize()). - isEqualTo(longValue); - } - } - - @Test - public void maxOpenFiles() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxOpenFiles(intValue); - assertThat(opt.maxOpenFiles()).isEqualTo(intValue); - } - } - - @Test - public void maxFileOpeningThreads() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxFileOpeningThreads(intValue); - assertThat(opt.maxFileOpeningThreads()).isEqualTo(intValue); - } - } - - @Test - public void useFsync() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseFsync(boolValue); - assertThat(opt.useFsync()).isEqualTo(boolValue); - } - } - - @Test - public void dbPaths() { - final List dbPaths = new ArrayList<>(); - dbPaths.add(new DbPath(Paths.get("/a"), 10)); - dbPaths.add(new DbPath(Paths.get("/b"), 100)); - dbPaths.add(new DbPath(Paths.get("/c"), 1000)); - - try (final Options opt = new Options()) { - assertThat(opt.dbPaths()).isEqualTo(Collections.emptyList()); - - opt.setDbPaths(dbPaths); - - assertThat(opt.dbPaths()).isEqualTo(dbPaths); - } - } - - @Test - public void dbLogDir() { - try (final Options opt = new Options()) { - final String str = "path/to/DbLogDir"; - opt.setDbLogDir(str); - assertThat(opt.dbLogDir()).isEqualTo(str); - } - } - - @Test - public void walDir() { - try (final Options opt = new Options()) { - final String str = "path/to/WalDir"; - opt.setWalDir(str); - assertThat(opt.walDir()).isEqualTo(str); - } - } - - @Test - public void deleteObsoleteFilesPeriodMicros() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setDeleteObsoleteFilesPeriodMicros(longValue); - assertThat(opt.deleteObsoleteFilesPeriodMicros()). - isEqualTo(longValue); - } - } - - @SuppressWarnings("deprecated") - @Test - public void maxBackgroundCompactions() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxBackgroundCompactions(intValue); - assertThat(opt.maxBackgroundCompactions()). - isEqualTo(intValue); - } - } - - @Test - public void maxSubcompactions() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxSubcompactions(intValue); - assertThat(opt.maxSubcompactions()). - isEqualTo(intValue); - } - } - - @SuppressWarnings("deprecated") - @Test - public void maxBackgroundFlushes() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxBackgroundFlushes(intValue); - assertThat(opt.maxBackgroundFlushes()). - isEqualTo(intValue); - } - } - - @Test - public void maxBackgroundJobs() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setMaxBackgroundJobs(intValue); - assertThat(opt.maxBackgroundJobs()).isEqualTo(intValue); - } - } - - @Test - public void maxLogFileSize() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxLogFileSize(longValue); - assertThat(opt.maxLogFileSize()).isEqualTo(longValue); - } - } - - @Test - public void logFileTimeToRoll() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setLogFileTimeToRoll(longValue); - assertThat(opt.logFileTimeToRoll()). - isEqualTo(longValue); - } - } - - @Test - public void keepLogFileNum() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setKeepLogFileNum(longValue); - assertThat(opt.keepLogFileNum()).isEqualTo(longValue); - } - } - - @Test - public void recycleLogFileNum() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setRecycleLogFileNum(longValue); - assertThat(opt.recycleLogFileNum()).isEqualTo(longValue); - } - } - - @Test - public void maxManifestFileSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setMaxManifestFileSize(longValue); - assertThat(opt.maxManifestFileSize()). - isEqualTo(longValue); - } - } - - @Test - public void tableCacheNumshardbits() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setTableCacheNumshardbits(intValue); - assertThat(opt.tableCacheNumshardbits()). - isEqualTo(intValue); - } - } - - @Test - public void walSizeLimitMB() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWalSizeLimitMB(longValue); - assertThat(opt.walSizeLimitMB()).isEqualTo(longValue); - } - } - - @Test - public void walTtlSeconds() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWalTtlSeconds(longValue); - assertThat(opt.walTtlSeconds()).isEqualTo(longValue); - } - } - - @Test - public void manifestPreallocationSize() throws RocksDBException { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setManifestPreallocationSize(longValue); - assertThat(opt.manifestPreallocationSize()). - isEqualTo(longValue); - } - } - - @Test - public void useDirectReads() { - try(final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseDirectReads(boolValue); - assertThat(opt.useDirectReads()).isEqualTo(boolValue); - } - } - - @Test - public void useDirectIoForFlushAndCompaction() { - try(final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseDirectIoForFlushAndCompaction(boolValue); - assertThat(opt.useDirectIoForFlushAndCompaction()).isEqualTo(boolValue); - } - } - - @Test - public void allowFAllocate() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowFAllocate(boolValue); - assertThat(opt.allowFAllocate()).isEqualTo(boolValue); - } - } - - @Test - public void allowMmapReads() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowMmapReads(boolValue); - assertThat(opt.allowMmapReads()).isEqualTo(boolValue); - } - } - - @Test - public void allowMmapWrites() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowMmapWrites(boolValue); - assertThat(opt.allowMmapWrites()).isEqualTo(boolValue); - } - } - - @Test - public void isFdCloseOnExec() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setIsFdCloseOnExec(boolValue); - assertThat(opt.isFdCloseOnExec()).isEqualTo(boolValue); - } - } - - @Test - public void statsDumpPeriodSec() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setStatsDumpPeriodSec(intValue); - assertThat(opt.statsDumpPeriodSec()).isEqualTo(intValue); - } - } - - @Test - public void statsPersistPeriodSec() { - try (final Options opt = new Options()) { - final int intValue = rand.nextInt(); - opt.setStatsPersistPeriodSec(intValue); - assertThat(opt.statsPersistPeriodSec()).isEqualTo(intValue); - } - } - - @Test - public void statsHistoryBufferSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setStatsHistoryBufferSize(longValue); - assertThat(opt.statsHistoryBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void adviseRandomOnOpen() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAdviseRandomOnOpen(boolValue); - assertThat(opt.adviseRandomOnOpen()).isEqualTo(boolValue); - } - } - - @Test - public void dbWriteBufferSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setDbWriteBufferSize(longValue); - assertThat(opt.dbWriteBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void setWriteBufferManager() throws RocksDBException { - try (final Options opt = new Options(); - final Cache cache = new LRUCache(1 * 1024 * 1024); - final WriteBufferManager writeBufferManager = new WriteBufferManager(2000l, cache)) { - opt.setWriteBufferManager(writeBufferManager); - assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); - } - } - - @Test - public void setWriteBufferManagerWithZeroBufferSize() throws RocksDBException { - try (final Options opt = new Options(); - final Cache cache = new LRUCache(1 * 1024 * 1024); - final WriteBufferManager writeBufferManager = new WriteBufferManager(0l, cache)) { - opt.setWriteBufferManager(writeBufferManager); - assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); - } - } - - @Test - public void setWriteBufferManagerWithAllowStall() throws RocksDBException { - try (final Options opt = new Options(); final Cache cache = new LRUCache(1 * 1024 * 1024); - final WriteBufferManager writeBufferManager = new WriteBufferManager(2000l, cache, true)) { - opt.setWriteBufferManager(writeBufferManager); - assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); - assertThat(opt.writeBufferManager().allowStall()).isEqualTo(true); - } - } - - @Test - public void accessHintOnCompactionStart() { - try (final Options opt = new Options()) { - final AccessHint accessHint = AccessHint.SEQUENTIAL; - opt.setAccessHintOnCompactionStart(accessHint); - assertThat(opt.accessHintOnCompactionStart()).isEqualTo(accessHint); - } - } - - @Test - public void compactionReadaheadSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setCompactionReadaheadSize(longValue); - assertThat(opt.compactionReadaheadSize()).isEqualTo(longValue); - } - } - - @Test - public void randomAccessMaxBufferSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setRandomAccessMaxBufferSize(longValue); - assertThat(opt.randomAccessMaxBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void writableFileMaxBufferSize() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWritableFileMaxBufferSize(longValue); - assertThat(opt.writableFileMaxBufferSize()).isEqualTo(longValue); - } - } - - @Test - public void useAdaptiveMutex() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setUseAdaptiveMutex(boolValue); - assertThat(opt.useAdaptiveMutex()).isEqualTo(boolValue); - } - } - - @Test - public void bytesPerSync() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setBytesPerSync(longValue); - assertThat(opt.bytesPerSync()).isEqualTo(longValue); - } - } - - @Test - public void walBytesPerSync() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWalBytesPerSync(longValue); - assertThat(opt.walBytesPerSync()).isEqualTo(longValue); - } - } - - @Test - public void strictBytesPerSync() { - try (final Options opt = new Options()) { - assertThat(opt.strictBytesPerSync()).isFalse(); - opt.setStrictBytesPerSync(true); - assertThat(opt.strictBytesPerSync()).isTrue(); - } - } - - @Test - public void enableThreadTracking() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setEnableThreadTracking(boolValue); - assertThat(opt.enableThreadTracking()).isEqualTo(boolValue); - } - } - - @Test - public void delayedWriteRate() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setDelayedWriteRate(longValue); - assertThat(opt.delayedWriteRate()).isEqualTo(longValue); - } - } - - @Test - public void enablePipelinedWrite() { - try(final Options opt = new Options()) { - assertThat(opt.enablePipelinedWrite()).isFalse(); - opt.setEnablePipelinedWrite(true); - assertThat(opt.enablePipelinedWrite()).isTrue(); - } - } - - @Test - public void unordredWrite() { - try(final Options opt = new Options()) { - assertThat(opt.unorderedWrite()).isFalse(); - opt.setUnorderedWrite(true); - assertThat(opt.unorderedWrite()).isTrue(); - } - } - - @Test - public void allowConcurrentMemtableWrite() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllowConcurrentMemtableWrite(boolValue); - assertThat(opt.allowConcurrentMemtableWrite()).isEqualTo(boolValue); - } - } - - @Test - public void enableWriteThreadAdaptiveYield() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setEnableWriteThreadAdaptiveYield(boolValue); - assertThat(opt.enableWriteThreadAdaptiveYield()).isEqualTo(boolValue); - } - } - - @Test - public void writeThreadMaxYieldUsec() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWriteThreadMaxYieldUsec(longValue); - assertThat(opt.writeThreadMaxYieldUsec()).isEqualTo(longValue); - } - } - - @Test - public void writeThreadSlowYieldUsec() { - try (final Options opt = new Options()) { - final long longValue = rand.nextLong(); - opt.setWriteThreadSlowYieldUsec(longValue); - assertThat(opt.writeThreadSlowYieldUsec()).isEqualTo(longValue); - } - } - - @Test - public void skipStatsUpdateOnDbOpen() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setSkipStatsUpdateOnDbOpen(boolValue); - assertThat(opt.skipStatsUpdateOnDbOpen()).isEqualTo(boolValue); - } - } - - @Test - public void walRecoveryMode() { - try (final Options opt = new Options()) { - for (final WALRecoveryMode walRecoveryMode : WALRecoveryMode.values()) { - opt.setWalRecoveryMode(walRecoveryMode); - assertThat(opt.walRecoveryMode()).isEqualTo(walRecoveryMode); - } - } - } - - @Test - public void allow2pc() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAllow2pc(boolValue); - assertThat(opt.allow2pc()).isEqualTo(boolValue); - } - } - - @Test - public void rowCache() { - try (final Options opt = new Options()) { - assertThat(opt.rowCache()).isNull(); - - try(final Cache lruCache = new LRUCache(1000)) { - opt.setRowCache(lruCache); - assertThat(opt.rowCache()).isEqualTo(lruCache); - } - - try(final Cache clockCache = new ClockCache(1000)) { - opt.setRowCache(clockCache); - assertThat(opt.rowCache()).isEqualTo(clockCache); - } - } - } - - @Test - public void walFilter() { - try (final Options opt = new Options()) { - assertThat(opt.walFilter()).isNull(); - - try (final AbstractWalFilter walFilter = new AbstractWalFilter() { - @Override - public void columnFamilyLogNumberMap( - final Map cfLognumber, - final Map cfNameId) { - // no-op - } - - @Override - public LogRecordFoundResult logRecordFound(final long logNumber, - final String logFileName, final WriteBatch batch, - final WriteBatch newBatch) { - return new LogRecordFoundResult( - WalProcessingOption.CONTINUE_PROCESSING, false); - } - - @Override - public String name() { - return "test-wal-filter"; - } - }) { - opt.setWalFilter(walFilter); - assertThat(opt.walFilter()).isEqualTo(walFilter); - } - } - } - - @Test - public void failIfOptionsFileError() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setFailIfOptionsFileError(boolValue); - assertThat(opt.failIfOptionsFileError()).isEqualTo(boolValue); - } - } - - @Test - public void dumpMallocStats() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setDumpMallocStats(boolValue); - assertThat(opt.dumpMallocStats()).isEqualTo(boolValue); - } - } - - @Test - public void avoidFlushDuringRecovery() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAvoidFlushDuringRecovery(boolValue); - assertThat(opt.avoidFlushDuringRecovery()).isEqualTo(boolValue); - } - } - - @Test - public void avoidFlushDuringShutdown() { - try (final Options opt = new Options()) { - final boolean boolValue = rand.nextBoolean(); - opt.setAvoidFlushDuringShutdown(boolValue); - assertThat(opt.avoidFlushDuringShutdown()).isEqualTo(boolValue); - } - } - - - @Test - public void allowIngestBehind() { - try (final Options opt = new Options()) { - assertThat(opt.allowIngestBehind()).isFalse(); - opt.setAllowIngestBehind(true); - assertThat(opt.allowIngestBehind()).isTrue(); - } - } - - @Test - public void twoWriteQueues() { - try (final Options opt = new Options()) { - assertThat(opt.twoWriteQueues()).isFalse(); - opt.setTwoWriteQueues(true); - assertThat(opt.twoWriteQueues()).isTrue(); - } - } - - @Test - public void manualWalFlush() { - try (final Options opt = new Options()) { - assertThat(opt.manualWalFlush()).isFalse(); - opt.setManualWalFlush(true); - assertThat(opt.manualWalFlush()).isTrue(); - } - } - - @Test - public void atomicFlush() { - try (final Options opt = new Options()) { - assertThat(opt.atomicFlush()).isFalse(); - opt.setAtomicFlush(true); - assertThat(opt.atomicFlush()).isTrue(); - } - } - - @Test - public void env() { - try (final Options options = new Options(); - final Env env = Env.getDefault()) { - options.setEnv(env); - assertThat(options.getEnv()).isSameAs(env); - } - } - - @Test - public void linkageOfPrepMethods() { - try (final Options options = new Options()) { - options.optimizeUniversalStyleCompaction(); - options.optimizeUniversalStyleCompaction(4000); - options.optimizeLevelStyleCompaction(); - options.optimizeLevelStyleCompaction(3000); - options.optimizeForPointLookup(10); - options.optimizeForSmallDb(); - options.prepareForBulkLoad(); - } - } - - @Test - public void compressionTypes() { - try (final Options options = new Options()) { - for (final CompressionType compressionType : - CompressionType.values()) { - options.setCompressionType(compressionType); - assertThat(options.compressionType()). - isEqualTo(compressionType); - assertThat(CompressionType.valueOf("NO_COMPRESSION")). - isEqualTo(CompressionType.NO_COMPRESSION); - } - } - } - - @Test - public void prepopulateBlobCache() { - try (final Options options = new Options()) { - for (final PrepopulateBlobCache prepopulateBlobCache : PrepopulateBlobCache.values()) { - options.setPrepopulateBlobCache(prepopulateBlobCache); - assertThat(options.prepopulateBlobCache()).isEqualTo(prepopulateBlobCache); - assertThat(PrepopulateBlobCache.valueOf("PREPOPULATE_BLOB_DISABLE")) - .isEqualTo(PrepopulateBlobCache.PREPOPULATE_BLOB_DISABLE); - } - } - } - - @Test - public void compressionPerLevel() { - try (final Options options = new Options()) { - assertThat(options.compressionPerLevel()).isEmpty(); - List compressionTypeList = - new ArrayList<>(); - for (int i = 0; i < options.numLevels(); i++) { - compressionTypeList.add(CompressionType.NO_COMPRESSION); - } - options.setCompressionPerLevel(compressionTypeList); - compressionTypeList = options.compressionPerLevel(); - for (final CompressionType compressionType : compressionTypeList) { - assertThat(compressionType).isEqualTo( - CompressionType.NO_COMPRESSION); - } - } - } - - @Test - public void differentCompressionsPerLevel() { - try (final Options options = new Options()) { - options.setNumLevels(3); - - assertThat(options.compressionPerLevel()).isEmpty(); - List compressionTypeList = new ArrayList<>(); - - compressionTypeList.add(CompressionType.BZLIB2_COMPRESSION); - compressionTypeList.add(CompressionType.SNAPPY_COMPRESSION); - compressionTypeList.add(CompressionType.LZ4_COMPRESSION); - - options.setCompressionPerLevel(compressionTypeList); - compressionTypeList = options.compressionPerLevel(); - - assertThat(compressionTypeList.size()).isEqualTo(3); - assertThat(compressionTypeList). - containsExactly( - CompressionType.BZLIB2_COMPRESSION, - CompressionType.SNAPPY_COMPRESSION, - CompressionType.LZ4_COMPRESSION); - - } - } - - @Test - public void bottommostCompressionType() { - try (final Options options = new Options()) { - assertThat(options.bottommostCompressionType()) - .isEqualTo(CompressionType.DISABLE_COMPRESSION_OPTION); - - for (final CompressionType compressionType : CompressionType.values()) { - options.setBottommostCompressionType(compressionType); - assertThat(options.bottommostCompressionType()) - .isEqualTo(compressionType); - } - } - } - - @Test - public void bottommostCompressionOptions() { - try (final Options options = new Options(); - final CompressionOptions bottommostCompressionOptions = new CompressionOptions() - .setMaxDictBytes(123)) { - - options.setBottommostCompressionOptions(bottommostCompressionOptions); - assertThat(options.bottommostCompressionOptions()) - .isEqualTo(bottommostCompressionOptions); - assertThat(options.bottommostCompressionOptions().maxDictBytes()) - .isEqualTo(123); - } - } - - @Test - public void compressionOptions() { - try (final Options options = new Options(); - final CompressionOptions compressionOptions = new CompressionOptions() - .setMaxDictBytes(123)) { - - options.setCompressionOptions(compressionOptions); - assertThat(options.compressionOptions()) - .isEqualTo(compressionOptions); - assertThat(options.compressionOptions().maxDictBytes()) - .isEqualTo(123); - } - } - - @Test - public void compactionStyles() { - try (final Options options = new Options()) { - for (final CompactionStyle compactionStyle : - CompactionStyle.values()) { - options.setCompactionStyle(compactionStyle); - assertThat(options.compactionStyle()). - isEqualTo(compactionStyle); - assertThat(CompactionStyle.valueOf("FIFO")). - isEqualTo(CompactionStyle.FIFO); - } - } - } - - @Test - public void maxTableFilesSizeFIFO() { - try (final Options opt = new Options()) { - long longValue = rand.nextLong(); - // Size has to be positive - longValue = (longValue < 0) ? -longValue : longValue; - longValue = (longValue == 0) ? longValue + 1 : longValue; - opt.setMaxTableFilesSizeFIFO(longValue); - assertThat(opt.maxTableFilesSizeFIFO()). - isEqualTo(longValue); - } - } - - @Test - public void rateLimiter() { - try (final Options options = new Options(); - final Options anotherOptions = new Options(); - final RateLimiter rateLimiter = - new RateLimiter(1000, 100 * 1000, 1)) { - options.setRateLimiter(rateLimiter); - // Test with parameter initialization - anotherOptions.setRateLimiter( - new RateLimiter(1000)); - } - } - - @Test - public void sstFileManager() throws RocksDBException { - try (final Options options = new Options(); - final SstFileManager sstFileManager = - new SstFileManager(Env.getDefault())) { - options.setSstFileManager(sstFileManager); - } - } - - @Test - public void shouldSetTestPrefixExtractor() { - try (final Options options = new Options()) { - options.useFixedLengthPrefixExtractor(100); - options.useFixedLengthPrefixExtractor(10); - } - } - - @Test - public void shouldSetTestCappedPrefixExtractor() { - try (final Options options = new Options()) { - options.useCappedPrefixExtractor(100); - options.useCappedPrefixExtractor(10); - } - } - - @Test - public void shouldTestMemTableFactoryName() - throws RocksDBException { - try (final Options options = new Options()) { - options.setMemTableConfig(new VectorMemTableConfig()); - assertThat(options.memTableFactoryName()). - isEqualTo("VectorRepFactory"); - options.setMemTableConfig( - new HashLinkedListMemTableConfig()); - assertThat(options.memTableFactoryName()). - isEqualTo("HashLinkedListRepFactory"); - } - } - - @Test - public void statistics() { - try(final Options options = new Options()) { - final Statistics statistics = options.statistics(); - assertThat(statistics).isNull(); - } - - try(final Statistics statistics = new Statistics(); - final Options options = new Options().setStatistics(statistics); - final Statistics stats = options.statistics()) { - assertThat(stats).isNotNull(); - } - } - - @Test - public void maxWriteBufferNumberToMaintain() { - try (final Options options = new Options()) { - int intValue = rand.nextInt(); - // Size has to be positive - intValue = (intValue < 0) ? -intValue : intValue; - intValue = (intValue == 0) ? intValue + 1 : intValue; - options.setMaxWriteBufferNumberToMaintain(intValue); - assertThat(options.maxWriteBufferNumberToMaintain()). - isEqualTo(intValue); - } - } - - @Test - public void compactionPriorities() { - try (final Options options = new Options()) { - for (final CompactionPriority compactionPriority : - CompactionPriority.values()) { - options.setCompactionPriority(compactionPriority); - assertThat(options.compactionPriority()). - isEqualTo(compactionPriority); - } - } - } - - @Test - public void reportBgIoStats() { - try (final Options options = new Options()) { - final boolean booleanValue = true; - options.setReportBgIoStats(booleanValue); - assertThat(options.reportBgIoStats()). - isEqualTo(booleanValue); - } - } - - @Test - public void ttl() { - try (final Options options = new Options()) { - options.setTtl(1000 * 60); - assertThat(options.ttl()). - isEqualTo(1000 * 60); - } - } - - @Test - public void periodicCompactionSeconds() { - try (final Options options = new Options()) { - options.setPeriodicCompactionSeconds(1000 * 60); - assertThat(options.periodicCompactionSeconds()).isEqualTo(1000 * 60); - } - } - - @Test - public void compactionOptionsUniversal() { - try (final Options options = new Options(); - final CompactionOptionsUniversal optUni = new CompactionOptionsUniversal() - .setCompressionSizePercent(7)) { - options.setCompactionOptionsUniversal(optUni); - assertThat(options.compactionOptionsUniversal()). - isEqualTo(optUni); - assertThat(options.compactionOptionsUniversal().compressionSizePercent()) - .isEqualTo(7); - } - } - - @Test - public void compactionOptionsFIFO() { - try (final Options options = new Options(); - final CompactionOptionsFIFO optFifo = new CompactionOptionsFIFO() - .setMaxTableFilesSize(2000)) { - options.setCompactionOptionsFIFO(optFifo); - assertThat(options.compactionOptionsFIFO()). - isEqualTo(optFifo); - assertThat(options.compactionOptionsFIFO().maxTableFilesSize()) - .isEqualTo(2000); - } - } - - @Test - public void forceConsistencyChecks() { - try (final Options options = new Options()) { - final boolean booleanValue = true; - options.setForceConsistencyChecks(booleanValue); - assertThat(options.forceConsistencyChecks()). - isEqualTo(booleanValue); - } - } - - @Test - public void compactionFilter() { - try(final Options options = new Options(); - final RemoveEmptyValueCompactionFilter cf = new RemoveEmptyValueCompactionFilter()) { - options.setCompactionFilter(cf); - assertThat(options.compactionFilter()).isEqualTo(cf); - } - } - - @Test - public void compactionFilterFactory() { - try(final Options options = new Options(); - final RemoveEmptyValueCompactionFilterFactory cff = new RemoveEmptyValueCompactionFilterFactory()) { - options.setCompactionFilterFactory(cff); - assertThat(options.compactionFilterFactory()).isEqualTo(cff); - } - } - - @Test - public void compactionThreadLimiter() { - try (final Options options = new Options(); - final ConcurrentTaskLimiter compactionThreadLimiter = - new ConcurrentTaskLimiterImpl("name", 3)) { - options.setCompactionThreadLimiter(compactionThreadLimiter); - assertThat(options.compactionThreadLimiter()).isEqualTo(compactionThreadLimiter); - } - } - - @Test - public void oldDefaults() { - try (final Options options = new Options()) { - options.oldDefaults(4, 6); - assertThat(options.writeBufferSize()).isEqualTo(4 << 20); - assertThat(options.compactionPriority()).isEqualTo(CompactionPriority.ByCompensatedSize); - assertThat(options.targetFileSizeBase()).isEqualTo(2 * 1048576); - assertThat(options.maxBytesForLevelBase()).isEqualTo(10 * 1048576); - assertThat(options.softPendingCompactionBytesLimit()).isEqualTo(0); - assertThat(options.hardPendingCompactionBytesLimit()).isEqualTo(0); - assertThat(options.level0StopWritesTrigger()).isEqualTo(24); - } - } - - @Test - public void optimizeForSmallDbWithCache() { - try (final Options options = new Options(); final Cache cache = new LRUCache(1024)) { - assertThat(options.optimizeForSmallDb(cache)).isEqualTo(options); - } - } - - @Test - public void cfPaths() { - try (final Options options = new Options()) { - final List paths = Arrays.asList( - new DbPath(Paths.get("test1"), 2 << 25), new DbPath(Paths.get("/test2/path"), 2 << 25)); - assertThat(options.cfPaths()).isEqualTo(Collections.emptyList()); - assertThat(options.setCfPaths(paths)).isEqualTo(options); - assertThat(options.cfPaths()).isEqualTo(paths); - } - } - - @Test - public void avoidUnnecessaryBlockingIO() { - try (final Options options = new Options()) { - assertThat(options.avoidUnnecessaryBlockingIO()).isEqualTo(false); - assertThat(options.setAvoidUnnecessaryBlockingIO(true)).isEqualTo(options); - assertThat(options.avoidUnnecessaryBlockingIO()).isEqualTo(true); - } - } - - @Test - public void persistStatsToDisk() { - try (final Options options = new Options()) { - assertThat(options.persistStatsToDisk()).isEqualTo(false); - assertThat(options.setPersistStatsToDisk(true)).isEqualTo(options); - assertThat(options.persistStatsToDisk()).isEqualTo(true); - } - } - - @Test - public void writeDbidToManifest() { - try (final Options options = new Options()) { - assertThat(options.writeDbidToManifest()).isEqualTo(false); - assertThat(options.setWriteDbidToManifest(true)).isEqualTo(options); - assertThat(options.writeDbidToManifest()).isEqualTo(true); - } - } - - @Test - public void logReadaheadSize() { - try (final Options options = new Options()) { - assertThat(options.logReadaheadSize()).isEqualTo(0); - final int size = 1024 * 1024 * 100; - assertThat(options.setLogReadaheadSize(size)).isEqualTo(options); - assertThat(options.logReadaheadSize()).isEqualTo(size); - } - } - - @Test - public void bestEffortsRecovery() { - try (final Options options = new Options()) { - assertThat(options.bestEffortsRecovery()).isEqualTo(false); - assertThat(options.setBestEffortsRecovery(true)).isEqualTo(options); - assertThat(options.bestEffortsRecovery()).isEqualTo(true); - } - } - - @Test - public void maxBgerrorResumeCount() { - try (final Options options = new Options()) { - final int INT_MAX = 2147483647; - assertThat(options.maxBgerrorResumeCount()).isEqualTo(INT_MAX); - assertThat(options.setMaxBgErrorResumeCount(-1)).isEqualTo(options); - assertThat(options.maxBgerrorResumeCount()).isEqualTo(-1); - } - } - - @Test - public void bgerrorResumeRetryInterval() { - try (final Options options = new Options()) { - assertThat(options.bgerrorResumeRetryInterval()).isEqualTo(1000000); - final long newRetryInterval = 24 * 3600 * 1000000L; - assertThat(options.setBgerrorResumeRetryInterval(newRetryInterval)).isEqualTo(options); - assertThat(options.bgerrorResumeRetryInterval()).isEqualTo(newRetryInterval); - } - } - - @Test - public void maxWriteBatchGroupSizeBytes() { - try (final Options options = new Options()) { - assertThat(options.maxWriteBatchGroupSizeBytes()).isEqualTo(1024 * 1024); - final long size = 1024 * 1024 * 1024 * 10L; - assertThat(options.setMaxWriteBatchGroupSizeBytes(size)).isEqualTo(options); - assertThat(options.maxWriteBatchGroupSizeBytes()).isEqualTo(size); - } - } - - @Test - public void skipCheckingSstFileSizesOnDbOpen() { - try (final Options options = new Options()) { - assertThat(options.skipCheckingSstFileSizesOnDbOpen()).isEqualTo(false); - assertThat(options.setSkipCheckingSstFileSizesOnDbOpen(true)).isEqualTo(options); - assertThat(options.skipCheckingSstFileSizesOnDbOpen()).isEqualTo(true); - } - } - - @Test - public void eventListeners() { - final AtomicBoolean wasCalled1 = new AtomicBoolean(); - final AtomicBoolean wasCalled2 = new AtomicBoolean(); - try (final Options options = new Options(); - final AbstractEventListener el1 = - new AbstractEventListener() { - @Override - public void onTableFileDeleted(final TableFileDeletionInfo tableFileDeletionInfo) { - wasCalled1.set(true); - } - }; - final AbstractEventListener el2 = - new AbstractEventListener() { - @Override - public void onMemTableSealed(final MemTableInfo memTableInfo) { - wasCalled2.set(true); - } - }) { - assertThat(options.setListeners(Arrays.asList(el1, el2))).isEqualTo(options); - List listeners = options.listeners(); - assertEquals(el1, listeners.get(0)); - assertEquals(el2, listeners.get(1)); - options.setListeners(Collections.emptyList()); - listeners.get(0).onTableFileDeleted(null); - assertTrue(wasCalled1.get()); - listeners.get(1).onMemTableSealed(null); - assertTrue(wasCalled2.get()); - List listeners2 = options.listeners(); - assertNotNull(listeners2); - assertEquals(0, listeners2.size()); - } - } -} diff --git a/java/src/test/java/org/rocksdb/OptionsUtilTest.java b/java/src/test/java/org/rocksdb/OptionsUtilTest.java deleted file mode 100644 index 02bfc0025..000000000 --- a/java/src/test/java/org/rocksdb/OptionsUtilTest.java +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; - -public class OptionsUtilTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - enum TestAPI { LOAD_LATEST_OPTIONS, LOAD_OPTIONS_FROM_FILE } - - @Test - public void loadLatestOptions() throws RocksDBException { - verifyOptions(TestAPI.LOAD_LATEST_OPTIONS); - } - - @Test - public void loadOptionsFromFile() throws RocksDBException { - verifyOptions(TestAPI.LOAD_OPTIONS_FROM_FILE); - } - - @Test - public void getLatestOptionsFileName() throws RocksDBException { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db).isNotNull(); - } - - String fName = OptionsUtil.getLatestOptionsFileName(dbPath, Env.getDefault()); - assertThat(fName).isNotNull(); - assert(fName.startsWith("OPTIONS-") == true); - // System.out.println("latest options fileName: " + fName); - } - - private void verifyOptions(TestAPI apiType) throws RocksDBException { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - final Options options = new Options() - .setCreateIfMissing(true) - .setParanoidChecks(false) - .setMaxOpenFiles(478) - .setDelayedWriteRate(1234567L); - final ColumnFamilyOptions baseDefaultCFOpts = new ColumnFamilyOptions(); - final byte[] secondCFName = "new_cf".getBytes(); - final ColumnFamilyOptions baseSecondCFOpts = - new ColumnFamilyOptions() - .setWriteBufferSize(70 * 1024) - .setMaxWriteBufferNumber(7) - .setMaxBytesForLevelBase(53 * 1024 * 1024) - .setLevel0FileNumCompactionTrigger(3) - .setLevel0SlowdownWritesTrigger(51) - .setBottommostCompressionType(CompressionType.ZSTD_COMPRESSION); - - // Create a database with a new column family - try (final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db).isNotNull(); - - // create column family - try (final ColumnFamilyHandle columnFamilyHandle = - db.createColumnFamily(new ColumnFamilyDescriptor(secondCFName, baseSecondCFOpts))) { - assert(columnFamilyHandle != null); - } - } - - // Read the options back and verify - DBOptions dbOptions = new DBOptions(); - ConfigOptions configOptions = - new ConfigOptions().setIgnoreUnknownOptions(false).setInputStringsEscaped(true).setEnv( - Env.getDefault()); - final List cfDescs = new ArrayList<>(); - String path = dbPath; - if (apiType == TestAPI.LOAD_LATEST_OPTIONS) { - OptionsUtil.loadLatestOptions(configOptions, path, dbOptions, cfDescs); - } else if (apiType == TestAPI.LOAD_OPTIONS_FROM_FILE) { - path = dbPath + "/" + OptionsUtil.getLatestOptionsFileName(dbPath, Env.getDefault()); - OptionsUtil.loadOptionsFromFile(configOptions, path, dbOptions, cfDescs); - } - - assertThat(dbOptions.createIfMissing()).isEqualTo(options.createIfMissing()); - assertThat(dbOptions.paranoidChecks()).isEqualTo(options.paranoidChecks()); - assertThat(dbOptions.maxOpenFiles()).isEqualTo(options.maxOpenFiles()); - assertThat(dbOptions.delayedWriteRate()).isEqualTo(options.delayedWriteRate()); - - assertThat(cfDescs.size()).isEqualTo(2); - assertThat(cfDescs.get(0)).isNotNull(); - assertThat(cfDescs.get(1)).isNotNull(); - assertThat(cfDescs.get(0).getName()).isEqualTo(RocksDB.DEFAULT_COLUMN_FAMILY); - assertThat(cfDescs.get(1).getName()).isEqualTo(secondCFName); - - ColumnFamilyOptions defaultCFOpts = cfDescs.get(0).getOptions(); - assertThat(defaultCFOpts.writeBufferSize()).isEqualTo(baseDefaultCFOpts.writeBufferSize()); - assertThat(defaultCFOpts.maxWriteBufferNumber()) - .isEqualTo(baseDefaultCFOpts.maxWriteBufferNumber()); - assertThat(defaultCFOpts.maxBytesForLevelBase()) - .isEqualTo(baseDefaultCFOpts.maxBytesForLevelBase()); - assertThat(defaultCFOpts.level0FileNumCompactionTrigger()) - .isEqualTo(baseDefaultCFOpts.level0FileNumCompactionTrigger()); - assertThat(defaultCFOpts.level0SlowdownWritesTrigger()) - .isEqualTo(baseDefaultCFOpts.level0SlowdownWritesTrigger()); - assertThat(defaultCFOpts.bottommostCompressionType()) - .isEqualTo(baseDefaultCFOpts.bottommostCompressionType()); - - ColumnFamilyOptions secondCFOpts = cfDescs.get(1).getOptions(); - assertThat(secondCFOpts.writeBufferSize()).isEqualTo(baseSecondCFOpts.writeBufferSize()); - assertThat(secondCFOpts.maxWriteBufferNumber()) - .isEqualTo(baseSecondCFOpts.maxWriteBufferNumber()); - assertThat(secondCFOpts.maxBytesForLevelBase()) - .isEqualTo(baseSecondCFOpts.maxBytesForLevelBase()); - assertThat(secondCFOpts.level0FileNumCompactionTrigger()) - .isEqualTo(baseSecondCFOpts.level0FileNumCompactionTrigger()); - assertThat(secondCFOpts.level0SlowdownWritesTrigger()) - .isEqualTo(baseSecondCFOpts.level0SlowdownWritesTrigger()); - assertThat(secondCFOpts.bottommostCompressionType()) - .isEqualTo(baseSecondCFOpts.bottommostCompressionType()); - } -} diff --git a/java/src/test/java/org/rocksdb/PlainTableConfigTest.java b/java/src/test/java/org/rocksdb/PlainTableConfigTest.java deleted file mode 100644 index c813dbbb4..000000000 --- a/java/src/test/java/org/rocksdb/PlainTableConfigTest.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PlainTableConfigTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void keySize() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setKeySize(5); - assertThat(plainTableConfig.keySize()). - isEqualTo(5); - } - - @Test - public void bloomBitsPerKey() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setBloomBitsPerKey(11); - assertThat(plainTableConfig.bloomBitsPerKey()). - isEqualTo(11); - } - - @Test - public void hashTableRatio() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setHashTableRatio(0.95); - assertThat(plainTableConfig.hashTableRatio()). - isEqualTo(0.95); - } - - @Test - public void indexSparseness() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setIndexSparseness(18); - assertThat(plainTableConfig.indexSparseness()). - isEqualTo(18); - } - - @Test - public void hugePageTlbSize() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setHugePageTlbSize(1); - assertThat(plainTableConfig.hugePageTlbSize()). - isEqualTo(1); - } - - @Test - public void encodingType() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setEncodingType(EncodingType.kPrefix); - assertThat(plainTableConfig.encodingType()).isEqualTo( - EncodingType.kPrefix); - } - - @Test - public void fullScanMode() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setFullScanMode(true); - assertThat(plainTableConfig.fullScanMode()).isTrue(); } - - @Test - public void storeIndexInFile() { - PlainTableConfig plainTableConfig = new PlainTableConfig(); - plainTableConfig.setStoreIndexInFile(true); - assertThat(plainTableConfig.storeIndexInFile()). - isTrue(); - } - - @Test - public void plainTableConfig() { - try(final Options opt = new Options()) { - final PlainTableConfig plainTableConfig = new PlainTableConfig(); - opt.setTableFormatConfig(plainTableConfig); - assertThat(opt.tableFactoryName()).isEqualTo("PlainTable"); - } - } -} diff --git a/java/src/test/java/org/rocksdb/PlatformRandomHelper.java b/java/src/test/java/org/rocksdb/PlatformRandomHelper.java deleted file mode 100644 index 80ea4d197..000000000 --- a/java/src/test/java/org/rocksdb/PlatformRandomHelper.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Random; - -/** - * Helper class to get the appropriate Random class instance dependent - * on the current platform architecture (32bit vs 64bit) - */ -public class PlatformRandomHelper { - /** - * Determine if OS is 32-Bit/64-Bit - * - * @return boolean value indicating if operating system is 64 Bit. - */ - public static boolean isOs64Bit(){ - final boolean is64Bit; - if (System.getProperty("os.name").contains("Windows")) { - is64Bit = (System.getenv("ProgramFiles(x86)") != null); - } else { - is64Bit = (System.getProperty("os.arch").contains("64")); - } - return is64Bit; - } - - /** - * Factory to get a platform specific Random instance - * - * @return {@link java.util.Random} instance. - */ - public static Random getPlatformSpecificRandomFactory(){ - if (isOs64Bit()) { - return new Random(); - } - return new Random32Bit(); - } - - /** - * Random32Bit is a class which overrides {@code nextLong} to - * provide random numbers which fit in size_t. This workaround - * is necessary because there is no unsigned_int < Java 8 - */ - private static class Random32Bit extends Random { - @Override - public long nextLong(){ - return this.nextInt(Integer.MAX_VALUE); - } - } - - /** - * Utility class constructor - */ - private PlatformRandomHelper() { } -} diff --git a/java/src/test/java/org/rocksdb/PutMultiplePartsTest.java b/java/src/test/java/org/rocksdb/PutMultiplePartsTest.java deleted file mode 100644 index 471ef0728..000000000 --- a/java/src/test/java/org/rocksdb/PutMultiplePartsTest.java +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -@RunWith(Parameterized.class) -public class PutMultiplePartsTest { - @Parameterized.Parameters - public static List data() { - return Arrays.asList(2, 3, 250, 20000); - } - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - private final int numParts; - - public PutMultiplePartsTest(final Integer numParts) { - this.numParts = numParts; - } - - @Test - public void putUntracked() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = - TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath())) { - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final byte[][] keys = generateItems("key", ":", numParts); - final byte[][] values = generateItems("value", "", numParts); - transaction.putUntracked(keys, values); - transaction.commit(); - } - txnDB.syncWal(); - } - - validateResults(); - } - - @Test - public void put() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = - TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath())) { - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final byte[][] keys = generateItems("key", ":", numParts); - final byte[][] values = generateItems("value", "", numParts); - transaction.put(keys, values); - transaction.commit(); - } - txnDB.syncWal(); - } - - validateResults(); - } - - @Test - public void putUntrackedCF() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = - TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyHandle columnFamilyHandle = - txnDB.createColumnFamily(new ColumnFamilyDescriptor("cfTest".getBytes()))) { - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final byte[][] keys = generateItems("key", ":", numParts); - final byte[][] values = generateItems("value", "", numParts); - transaction.putUntracked(columnFamilyHandle, keys, values); - transaction.commit(); - } - txnDB.syncWal(); - } - - validateResultsCF(); - } - @Test - public void putCF() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB txnDB = - TransactionDB.open(options, txnDbOptions, dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyHandle columnFamilyHandle = - txnDB.createColumnFamily(new ColumnFamilyDescriptor("cfTest".getBytes()))) { - try (final Transaction transaction = txnDB.beginTransaction(new WriteOptions())) { - final byte[][] keys = generateItems("key", ":", numParts); - final byte[][] values = generateItems("value", "", numParts); - transaction.put(columnFamilyHandle, keys, values); - transaction.commit(); - } - txnDB.syncWal(); - } - - validateResultsCF(); - } - - private void validateResults() throws RocksDBException { - try (final RocksDB db = RocksDB.open(new Options(), dbFolder.getRoot().getAbsolutePath())) { - final List keys = generateItemsAsList("key", ":", numParts); - final byte[][] values = generateItems("value", "", numParts); - - StringBuilder singleKey = new StringBuilder(); - for (int i = 0; i < numParts; i++) { - singleKey.append(new String(keys.get(i), StandardCharsets.UTF_8)); - } - final byte[] result = db.get(singleKey.toString().getBytes()); - StringBuilder singleValue = new StringBuilder(); - for (int i = 0; i < numParts; i++) { - singleValue.append(new String(values[i], StandardCharsets.UTF_8)); - } - assertThat(result).isEqualTo(singleValue.toString().getBytes()); - } - } - - private void validateResultsCF() throws RocksDBException { - final List columnFamilyDescriptors = new ArrayList<>(); - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("cfTest".getBytes())); - columnFamilyDescriptors.add(new ColumnFamilyDescriptor("default".getBytes())); - final List columnFamilyHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(new DBOptions(), dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - final List keys = generateItemsAsList("key", ":", numParts); - final byte[][] values = generateItems("value", "", numParts); - - StringBuilder singleKey = new StringBuilder(); - for (int i = 0; i < numParts; i++) { - singleKey.append(new String(keys.get(i), StandardCharsets.UTF_8)); - } - final byte[] result = db.get(columnFamilyHandles.get(0), singleKey.toString().getBytes()); - StringBuilder singleValue = new StringBuilder(); - for (int i = 0; i < numParts; i++) { - singleValue.append(new String(values[i], StandardCharsets.UTF_8)); - } - assertThat(result).isEqualTo(singleValue.toString().getBytes()); - } - } - - private byte[][] generateItems(final String prefix, final String suffix, final int numItems) { - return generateItemsAsList(prefix, suffix, numItems).toArray(new byte[0][0]); - } - - private List generateItemsAsList( - final String prefix, final String suffix, final int numItems) { - final List items = new ArrayList<>(); - for (int i = 0; i < numItems; i++) { - items.add((prefix + i + suffix).getBytes()); - } - return items; - } -} diff --git a/java/src/test/java/org/rocksdb/RateLimiterTest.java b/java/src/test/java/org/rocksdb/RateLimiterTest.java deleted file mode 100644 index e7d6e6c49..000000000 --- a/java/src/test/java/org/rocksdb/RateLimiterTest.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.rocksdb.RateLimiter.*; - -public class RateLimiterTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void bytesPerSecond() { - try(final RateLimiter rateLimiter = - new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, - DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { - assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0); - rateLimiter.setBytesPerSecond(2000); - assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0); - } - } - - @Test - public void getSingleBurstBytes() { - try(final RateLimiter rateLimiter = - new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, - DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { - assertThat(rateLimiter.getSingleBurstBytes()).isEqualTo(100); - } - } - - @Test - public void getTotalBytesThrough() { - try(final RateLimiter rateLimiter = - new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, - DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { - assertThat(rateLimiter.getTotalBytesThrough()).isEqualTo(0); - } - } - - @Test - public void getTotalRequests() { - try(final RateLimiter rateLimiter = - new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, - DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { - assertThat(rateLimiter.getTotalRequests()).isEqualTo(0); - } - } - - @Test - public void autoTune() { - try(final RateLimiter rateLimiter = - new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, - DEFAULT_FAIRNESS, DEFAULT_MODE, true)) { - assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0); - } - } -} diff --git a/java/src/test/java/org/rocksdb/ReadOnlyTest.java b/java/src/test/java/org/rocksdb/ReadOnlyTest.java deleted file mode 100644 index 5b40a5df1..000000000 --- a/java/src/test/java/org/rocksdb/ReadOnlyTest.java +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ReadOnlyTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void readOnlyOpen() throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "value".getBytes()); - } - try (final RocksDB db = RocksDB.openReadOnly(dbFolder.getRoot().getAbsolutePath())) { - assertThat("value").isEqualTo(new String(db.get("key".getBytes()))); - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)); - final List columnFamilyHandleList = new ArrayList<>(); - try (final RocksDB db = RocksDB.open( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, columnFamilyHandleList)) { - columnFamilyHandleList.add( - db.createColumnFamily(new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpts))); - columnFamilyHandleList.add( - db.createColumnFamily(new ColumnFamilyDescriptor("new_cf2".getBytes(), cfOpts))); - db.put(columnFamilyHandleList.get(2), "key2".getBytes(), "value2".getBytes()); - } - - columnFamilyHandleList.clear(); - try (final RocksDB db = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, columnFamilyHandleList)) { - assertThat(db.get("key2".getBytes())).isNull(); - assertThat(db.get(columnFamilyHandleList.get(0), "key2".getBytes())).isNull(); - } - - cfDescriptors.clear(); - cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)); - cfDescriptors.add(new ColumnFamilyDescriptor("new_cf2".getBytes(), cfOpts)); - columnFamilyHandleList.clear(); - try (final RocksDB db = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, columnFamilyHandleList)) { - assertThat(new String(db.get(columnFamilyHandleList.get(1), "key2".getBytes()))) - .isEqualTo("value2"); - } - } - } - - @Test(expected = RocksDBException.class) - public void failToWriteInReadOnly() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - try (final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - // no-op - } - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)); - - final List readOnlyColumnFamilyHandleList = new ArrayList<>(); - try (final RocksDB rDb = RocksDB.openReadOnly(dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, readOnlyColumnFamilyHandleList)) { - // test that put fails in readonly mode - rDb.put("key".getBytes(), "value".getBytes()); - } - } - } - - @Test(expected = RocksDBException.class) - public void failToCFWriteInReadOnly() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts) - ); - final List readOnlyColumnFamilyHandleList = - new ArrayList<>(); - try (final RocksDB rDb = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - readOnlyColumnFamilyHandleList)) { - rDb.put(readOnlyColumnFamilyHandleList.get(0), "key".getBytes(), "value".getBytes()); - } - } - } - - @Test(expected = RocksDBException.class) - public void failToRemoveInReadOnly() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts) - ); - - final List readOnlyColumnFamilyHandleList = - new ArrayList<>(); - - try (final RocksDB rDb = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - readOnlyColumnFamilyHandleList)) { - rDb.delete("key".getBytes()); - } - } - } - - @Test(expected = RocksDBException.class) - public void failToCFRemoveInReadOnly() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts) - ); - - final List readOnlyColumnFamilyHandleList = - new ArrayList<>(); - try (final RocksDB rDb = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - readOnlyColumnFamilyHandleList)) { - rDb.delete(readOnlyColumnFamilyHandleList.get(0), - "key".getBytes()); - } - } - } - - @Test(expected = RocksDBException.class) - public void failToWriteBatchReadOnly() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts) - ); - - final List readOnlyColumnFamilyHandleList = - new ArrayList<>(); - try (final RocksDB rDb = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - readOnlyColumnFamilyHandleList); - final WriteBatch wb = new WriteBatch(); - final WriteOptions wOpts = new WriteOptions()) { - wb.put("key".getBytes(), "value".getBytes()); - rDb.write(wOpts, wb); - } - } - } - - @Test(expected = RocksDBException.class) - public void failToCFWriteBatchReadOnly() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - //no-op - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts) - ); - - final List readOnlyColumnFamilyHandleList = - new ArrayList<>(); - try (final RocksDB rDb = RocksDB.openReadOnly( - dbFolder.getRoot().getAbsolutePath(), cfDescriptors, - readOnlyColumnFamilyHandleList); - final WriteBatch wb = new WriteBatch(); - final WriteOptions wOpts = new WriteOptions()) { - wb.put(readOnlyColumnFamilyHandleList.get(0), "key".getBytes(), - "value".getBytes()); - rDb.write(wOpts, wb); - } - } - } - - @Test(expected = RocksDBException.class) - public void errorIfWalFileExists() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - // no-op - } - - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)); - - final List readOnlyColumnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions(); - final RocksDB rDb = RocksDB.openReadOnly(options, dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, readOnlyColumnFamilyHandleList, true);) { - // no-op... should have raised an error as errorIfWalFileExists=true - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/ReadOptionsTest.java b/java/src/test/java/org/rocksdb/ReadOptionsTest.java deleted file mode 100644 index 156dd3730..000000000 --- a/java/src/test/java/org/rocksdb/ReadOptionsTest.java +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.Random; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ReadOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public ExpectedException exception = ExpectedException.none(); - - @Test - public void altConstructor() { - try (final ReadOptions opt = new ReadOptions(true, true)) { - assertThat(opt.verifyChecksums()).isTrue(); - assertThat(opt.fillCache()).isTrue(); - } - } - - @Test - public void copyConstructor() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setVerifyChecksums(false); - opt.setFillCache(false); - opt.setIterateUpperBound(buildRandomSlice()); - opt.setIterateLowerBound(buildRandomSlice()); - opt.setTimestamp(buildRandomSlice()); - opt.setIterStartTs(buildRandomSlice()); - try (final ReadOptions other = new ReadOptions(opt)) { - assertThat(opt.verifyChecksums()).isEqualTo(other.verifyChecksums()); - assertThat(opt.fillCache()).isEqualTo(other.fillCache()); - assertThat(Arrays.equals(opt.iterateUpperBound().data(), other.iterateUpperBound().data())).isTrue(); - assertThat(Arrays.equals(opt.iterateLowerBound().data(), other.iterateLowerBound().data())).isTrue(); - assertThat(Arrays.equals(opt.timestamp().data(), other.timestamp().data())).isTrue(); - assertThat(Arrays.equals(opt.iterStartTs().data(), other.iterStartTs().data())).isTrue(); - } - } - } - - @Test - public void verifyChecksum() { - try (final ReadOptions opt = new ReadOptions()) { - final Random rand = new Random(); - final boolean boolValue = rand.nextBoolean(); - opt.setVerifyChecksums(boolValue); - assertThat(opt.verifyChecksums()).isEqualTo(boolValue); - } - } - - @Test - public void fillCache() { - try (final ReadOptions opt = new ReadOptions()) { - final Random rand = new Random(); - final boolean boolValue = rand.nextBoolean(); - opt.setFillCache(boolValue); - assertThat(opt.fillCache()).isEqualTo(boolValue); - } - } - - @Test - public void tailing() { - try (final ReadOptions opt = new ReadOptions()) { - final Random rand = new Random(); - final boolean boolValue = rand.nextBoolean(); - opt.setTailing(boolValue); - assertThat(opt.tailing()).isEqualTo(boolValue); - } - } - - @Test - public void snapshot() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setSnapshot(null); - assertThat(opt.snapshot()).isNull(); - } - } - - @Test - public void readTier() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setReadTier(ReadTier.BLOCK_CACHE_TIER); - assertThat(opt.readTier()).isEqualTo(ReadTier.BLOCK_CACHE_TIER); - } - } - - @SuppressWarnings("deprecated") - @Test - public void managed() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setManaged(true); - assertThat(opt.managed()).isTrue(); - } - } - - @Test - public void totalOrderSeek() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setTotalOrderSeek(true); - assertThat(opt.totalOrderSeek()).isTrue(); - } - } - - @Test - public void prefixSameAsStart() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setPrefixSameAsStart(true); - assertThat(opt.prefixSameAsStart()).isTrue(); - } - } - - @Test - public void pinData() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setPinData(true); - assertThat(opt.pinData()).isTrue(); - } - } - - @Test - public void backgroundPurgeOnIteratorCleanup() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setBackgroundPurgeOnIteratorCleanup(true); - assertThat(opt.backgroundPurgeOnIteratorCleanup()).isTrue(); - } - } - - @Test - public void readaheadSize() { - try (final ReadOptions opt = new ReadOptions()) { - final Random rand = new Random(); - final int intValue = rand.nextInt(2147483647); - opt.setReadaheadSize(intValue); - assertThat(opt.readaheadSize()).isEqualTo(intValue); - } - } - - @Test - public void ignoreRangeDeletions() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setIgnoreRangeDeletions(true); - assertThat(opt.ignoreRangeDeletions()).isTrue(); - } - } - - @Test - public void iterateUpperBound() { - try (final ReadOptions opt = new ReadOptions()) { - Slice upperBound = buildRandomSlice(); - opt.setIterateUpperBound(upperBound); - assertThat(Arrays.equals(upperBound.data(), opt.iterateUpperBound().data())).isTrue(); - opt.setIterateUpperBound(null); - assertThat(opt.iterateUpperBound()).isNull(); - } - } - - @Test - public void iterateUpperBoundNull() { - try (final ReadOptions opt = new ReadOptions()) { - assertThat(opt.iterateUpperBound()).isNull(); - } - } - - @Test - public void iterateLowerBound() { - try (final ReadOptions opt = new ReadOptions()) { - Slice lowerBound = buildRandomSlice(); - opt.setIterateLowerBound(lowerBound); - assertThat(Arrays.equals(lowerBound.data(), opt.iterateLowerBound().data())).isTrue(); - opt.setIterateLowerBound(null); - assertThat(opt.iterateLowerBound()).isNull(); - } - } - - @Test - public void iterateLowerBoundNull() { - try (final ReadOptions opt = new ReadOptions()) { - assertThat(opt.iterateLowerBound()).isNull(); - } - } - - @Test - public void tableFilter() { - try (final ReadOptions opt = new ReadOptions(); - final AbstractTableFilter allTablesFilter = new AllTablesFilter()) { - opt.setTableFilter(allTablesFilter); - } - } - - @Test - public void autoPrefixMode() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setAutoPrefixMode(true); - assertThat(opt.autoPrefixMode()).isTrue(); - } - } - - @Test - public void timestamp() { - try (final ReadOptions opt = new ReadOptions()) { - Slice timestamp = buildRandomSlice(); - opt.setTimestamp(timestamp); - assertThat(Arrays.equals(timestamp.data(), opt.timestamp().data())).isTrue(); - opt.setTimestamp(null); - assertThat(opt.timestamp()).isNull(); - } - } - - @Test - public void iterStartTs() { - try (final ReadOptions opt = new ReadOptions()) { - Slice itertStartTsSlice = buildRandomSlice(); - opt.setIterStartTs(itertStartTsSlice); - assertThat(Arrays.equals(itertStartTsSlice.data(), opt.iterStartTs().data())).isTrue(); - opt.setIterStartTs(null); - assertThat(opt.iterStartTs()).isNull(); - } - } - - @Test - public void deadline() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setDeadline(1999l); - assertThat(opt.deadline()).isEqualTo(1999l); - } - } - - @Test - public void ioTimeout() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setIoTimeout(34555l); - assertThat(opt.ioTimeout()).isEqualTo(34555l); - } - } - - @Test - public void valueSizeSoftLimit() { - try (final ReadOptions opt = new ReadOptions()) { - opt.setValueSizeSoftLimit(12134324l); - assertThat(opt.valueSizeSoftLimit()).isEqualTo(12134324l); - } - } - - @Test - public void failSetVerifyChecksumUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.setVerifyChecksums(true); - } - } - - @Test - public void failVerifyChecksumUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.verifyChecksums(); - } - } - - @Test - public void failSetFillCacheUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.setFillCache(true); - } - } - - @Test - public void failFillCacheUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.fillCache(); - } - } - - @Test - public void failSetTailingUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.setTailing(true); - } - } - - @Test - public void failTailingUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.tailing(); - } - } - - @Test - public void failSetSnapshotUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.setSnapshot(null); - } - } - - @Test - public void failSnapshotUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.snapshot(); - } - } - - @Test - public void failSetIterateUpperBoundUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.setIterateUpperBound(null); - } - } - - @Test - public void failIterateUpperBoundUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.iterateUpperBound(); - } - } - - @Test - public void failSetIterateLowerBoundUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.setIterateLowerBound(null); - } - } - - @Test - public void failIterateLowerBoundUninitialized() { - try (final ReadOptions readOptions = - setupUninitializedReadOptions(exception)) { - readOptions.iterateLowerBound(); - } - } - - private ReadOptions setupUninitializedReadOptions( - ExpectedException exception) { - final ReadOptions readOptions = new ReadOptions(); - readOptions.close(); - exception.expect(AssertionError.class); - return readOptions; - } - - private Slice buildRandomSlice() { - final Random rand = new Random(); - byte[] sliceBytes = new byte[rand.nextInt(100) + 1]; - rand.nextBytes(sliceBytes); - return new Slice(sliceBytes); - } - - private static class AllTablesFilter extends AbstractTableFilter { - @Override - public boolean filter(final TableProperties tableProperties) { - return true; - } - } -} diff --git a/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java b/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java deleted file mode 100644 index d3bd4ece7..000000000 --- a/java/src/test/java/org/rocksdb/RocksDBExceptionTest.java +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import org.rocksdb.Status.Code; -import org.rocksdb.Status.SubCode; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -public class RocksDBExceptionTest { - - @Test - public void exception() { - try { - raiseException(); - } catch(final RocksDBException e) { - assertThat(e.getStatus()).isNull(); - assertThat(e.getMessage()).isEqualTo("test message"); - return; - } - fail(); - } - - @Test - public void exceptionWithStatusCode() { - try { - raiseExceptionWithStatusCode(); - } catch(final RocksDBException e) { - assertThat(e.getStatus()).isNotNull(); - assertThat(e.getStatus().getCode()).isEqualTo(Code.NotSupported); - assertThat(e.getStatus().getSubCode()).isEqualTo(SubCode.None); - assertThat(e.getStatus().getState()).isNull(); - assertThat(e.getMessage()).isEqualTo("test message"); - return; - } - fail(); - } - - @Test - public void exceptionNoMsgWithStatusCode() { - try { - raiseExceptionNoMsgWithStatusCode(); - } catch(final RocksDBException e) { - assertThat(e.getStatus()).isNotNull(); - assertThat(e.getStatus().getCode()).isEqualTo(Code.NotSupported); - assertThat(e.getStatus().getSubCode()).isEqualTo(SubCode.None); - assertThat(e.getStatus().getState()).isNull(); - assertThat(e.getMessage()).isEqualTo(Code.NotSupported.name()); - return; - } - fail(); - } - - @Test - public void exceptionWithStatusCodeSubCode() { - try { - raiseExceptionWithStatusCodeSubCode(); - } catch(final RocksDBException e) { - assertThat(e.getStatus()).isNotNull(); - assertThat(e.getStatus().getCode()).isEqualTo(Code.TimedOut); - assertThat(e.getStatus().getSubCode()) - .isEqualTo(Status.SubCode.LockTimeout); - assertThat(e.getStatus().getState()).isNull(); - assertThat(e.getMessage()).isEqualTo("test message"); - return; - } - fail(); - } - - @Test - public void exceptionNoMsgWithStatusCodeSubCode() { - try { - raiseExceptionNoMsgWithStatusCodeSubCode(); - } catch(final RocksDBException e) { - assertThat(e.getStatus()).isNotNull(); - assertThat(e.getStatus().getCode()).isEqualTo(Code.TimedOut); - assertThat(e.getStatus().getSubCode()).isEqualTo(SubCode.LockTimeout); - assertThat(e.getStatus().getState()).isNull(); - assertThat(e.getMessage()).isEqualTo(Code.TimedOut.name() + - "(" + SubCode.LockTimeout.name() + ")"); - return; - } - fail(); - } - - @Test - public void exceptionWithStatusCodeState() { - try { - raiseExceptionWithStatusCodeState(); - } catch(final RocksDBException e) { - assertThat(e.getStatus()).isNotNull(); - assertThat(e.getStatus().getCode()).isEqualTo(Code.NotSupported); - assertThat(e.getStatus().getSubCode()).isEqualTo(SubCode.None); - assertThat(e.getStatus().getState()).isNotNull(); - assertThat(e.getMessage()).isEqualTo("test message"); - return; - } - fail(); - } - - private native void raiseException() throws RocksDBException; - private native void raiseExceptionWithStatusCode() throws RocksDBException; - private native void raiseExceptionNoMsgWithStatusCode() throws RocksDBException; - private native void raiseExceptionWithStatusCodeSubCode() - throws RocksDBException; - private native void raiseExceptionNoMsgWithStatusCodeSubCode() - throws RocksDBException; - private native void raiseExceptionWithStatusCodeState() - throws RocksDBException; -} diff --git a/java/src/test/java/org/rocksdb/RocksDBTest.java b/java/src/test/java/org/rocksdb/RocksDBTest.java deleted file mode 100644 index 488dbafe8..000000000 --- a/java/src/test/java/org/rocksdb/RocksDBTest.java +++ /dev/null @@ -1,1695 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.*; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -import java.nio.ByteBuffer; -import java.util.*; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -public class RocksDBTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - public static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void open() throws RocksDBException { - try (final RocksDB db = - RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - } - } - - @Test - public void open_opt() throws RocksDBException { - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(db).isNotNull(); - } - } - - @Test - public void openWhenOpen() throws RocksDBException { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - - try (final RocksDB db1 = RocksDB.open(dbPath)) { - try (final RocksDB db2 = RocksDB.open(dbPath)) { - fail("Should have thrown an exception when opening the same db twice"); - } catch (final RocksDBException e) { - assertThat(e.getStatus().getCode()).isEqualTo(Status.Code.IOError); - assertThat(e.getStatus().getSubCode()).isEqualTo(Status.SubCode.None); - assertThat(e.getStatus().getState()).contains("lock "); - } - } - } - - @Test - public void createColumnFamily() throws RocksDBException { - final byte[] col1Name = "col1".getBytes(UTF_8); - - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() - ) { - try (final ColumnFamilyHandle col1 = - db.createColumnFamily(new ColumnFamilyDescriptor(col1Name, cfOpts))) { - assertThat(col1).isNotNull(); - assertThat(col1.getName()).isEqualTo(col1Name); - } - } - - final List cfHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(), - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor(col1Name)), - cfHandles)) { - try { - assertThat(cfHandles.size()).isEqualTo(2); - assertThat(cfHandles.get(1)).isNotNull(); - assertThat(cfHandles.get(1).getName()).isEqualTo(col1Name); - } finally { - for (final ColumnFamilyHandle cfHandle : - cfHandles) { - cfHandle.close(); - } - } - } - } - - - @Test - public void createColumnFamilies() throws RocksDBException { - final byte[] col1Name = "col1".getBytes(UTF_8); - final byte[] col2Name = "col2".getBytes(UTF_8); - - List cfHandles; - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() - ) { - cfHandles = - db.createColumnFamilies(cfOpts, Arrays.asList(col1Name, col2Name)); - try { - assertThat(cfHandles).isNotNull(); - assertThat(cfHandles.size()).isEqualTo(2); - assertThat(cfHandles.get(0).getName()).isEqualTo(col1Name); - assertThat(cfHandles.get(1).getName()).isEqualTo(col2Name); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - - cfHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(), - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor(col1Name), - new ColumnFamilyDescriptor(col2Name)), - cfHandles)) { - try { - assertThat(cfHandles.size()).isEqualTo(3); - assertThat(cfHandles.get(1)).isNotNull(); - assertThat(cfHandles.get(1).getName()).isEqualTo(col1Name); - assertThat(cfHandles.get(2)).isNotNull(); - assertThat(cfHandles.get(2).getName()).isEqualTo(col2Name); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - - @Test - public void createColumnFamiliesfromDescriptors() throws RocksDBException { - final byte[] col1Name = "col1".getBytes(UTF_8); - final byte[] col2Name = "col2".getBytes(UTF_8); - - List cfHandles; - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() - ) { - cfHandles = - db.createColumnFamilies(Arrays.asList( - new ColumnFamilyDescriptor(col1Name, cfOpts), - new ColumnFamilyDescriptor(col2Name, cfOpts))); - try { - assertThat(cfHandles).isNotNull(); - assertThat(cfHandles.size()).isEqualTo(2); - assertThat(cfHandles.get(0).getName()).isEqualTo(col1Name); - assertThat(cfHandles.get(1).getName()).isEqualTo(col2Name); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - - cfHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(), - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor(col1Name), - new ColumnFamilyDescriptor(col2Name)), - cfHandles)) { - try { - assertThat(cfHandles.size()).isEqualTo(3); - assertThat(cfHandles.get(1)).isNotNull(); - assertThat(cfHandles.get(1).getName()).isEqualTo(col1Name); - assertThat(cfHandles.get(2)).isNotNull(); - assertThat(cfHandles.get(2).getName()).isEqualTo(col2Name); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - - @Test - public void put() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteOptions opt = new WriteOptions(); final ReadOptions optr = new ReadOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put(opt, "key2".getBytes(), "12345678".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo( - "value".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo( - "12345678".getBytes()); - - ByteBuffer key = ByteBuffer.allocateDirect(12); - ByteBuffer value = ByteBuffer.allocateDirect(12); - key.position(4); - key.put("key3".getBytes()); - key.position(4).limit(8); - value.position(4); - value.put("val3".getBytes()); - value.position(4).limit(8); - - db.put(opt, key, value); - - assertThat(key.position()).isEqualTo(8); - assertThat(key.limit()).isEqualTo(8); - - assertThat(value.position()).isEqualTo(8); - assertThat(value.limit()).isEqualTo(8); - - key.position(4); - - ByteBuffer result = ByteBuffer.allocateDirect(12); - assertThat(db.get(optr, key, result)).isEqualTo(4); - assertThat(result.position()).isEqualTo(0); - assertThat(result.limit()).isEqualTo(4); - assertThat(key.position()).isEqualTo(8); - assertThat(key.limit()).isEqualTo(8); - - byte[] tmp = new byte[4]; - result.get(tmp); - assertThat(tmp).isEqualTo("val3".getBytes()); - - key.position(4); - - result.clear().position(9); - assertThat(db.get(optr, key, result)).isEqualTo(4); - assertThat(result.position()).isEqualTo(9); - assertThat(result.limit()).isEqualTo(12); - assertThat(key.position()).isEqualTo(8); - assertThat(key.limit()).isEqualTo(8); - byte[] tmp2 = new byte[3]; - result.get(tmp2); - assertThat(tmp2).isEqualTo("val".getBytes()); - - // put - Segment key3 = sliceSegment("key3"); - Segment key4 = sliceSegment("key4"); - Segment value0 = sliceSegment("value 0"); - Segment value1 = sliceSegment("value 1"); - db.put(key3.data, key3.offset, key3.len, value0.data, value0.offset, value0.len); - db.put(opt, key4.data, key4.offset, key4.len, value1.data, value1.offset, value1.len); - - // compare - Assert.assertTrue(value0.isSamePayload(db.get(key3.data, key3.offset, key3.len))); - Assert.assertTrue(value1.isSamePayload(db.get(key4.data, key4.offset, key4.len))); - } - } - - private static Segment sliceSegment(String key) { - ByteBuffer rawKey = ByteBuffer.allocate(key.length() + 4); - rawKey.put((byte)0); - rawKey.put((byte)0); - rawKey.put(key.getBytes()); - - return new Segment(rawKey.array(), 2, key.length()); - } - - private static class Segment { - final byte[] data; - final int offset; - final int len; - - public boolean isSamePayload(byte[] value) { - if (value == null) { - return false; - } - if (value.length != len) { - return false; - } - - for (int i = 0; i < value.length; i++) { - if (data[i + offset] != value[i]) { - return false; - } - } - - return true; - } - - public Segment(byte[] value, int offset, int len) { - this.data = value; - this.offset = offset; - this.len = len; - } - } - - @Test - public void write() throws RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options options = new Options() - .setMergeOperator(stringAppendOperator) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions opts = new WriteOptions()) { - - try (final WriteBatch wb1 = new WriteBatch()) { - wb1.put("key1".getBytes(), "aa".getBytes()); - wb1.merge("key1".getBytes(), "bb".getBytes()); - - try (final WriteBatch wb2 = new WriteBatch()) { - wb2.put("key2".getBytes(), "xx".getBytes()); - wb2.merge("key2".getBytes(), "yy".getBytes()); - db.write(opts, wb1); - db.write(opts, wb2); - } - } - - assertThat(db.get("key1".getBytes())).isEqualTo( - "aa,bb".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo( - "xx,yy".getBytes()); - } - } - - @Test - public void getWithOutValue() throws RocksDBException { - try (final RocksDB db = - RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - byte[] outValue = new byte[5]; - // not found value - int getResult = db.get("keyNotFound".getBytes(), outValue); - assertThat(getResult).isEqualTo(RocksDB.NOT_FOUND); - // found value which fits in outValue - getResult = db.get("key1".getBytes(), outValue); - assertThat(getResult).isNotEqualTo(RocksDB.NOT_FOUND); - assertThat(outValue).isEqualTo("value".getBytes()); - // found value which fits partially - getResult = db.get("key2".getBytes(), outValue); - assertThat(getResult).isNotEqualTo(RocksDB.NOT_FOUND); - assertThat(outValue).isEqualTo("12345".getBytes()); - } - } - - @Test - public void getWithOutValueReadOptions() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final ReadOptions rOpt = new ReadOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - byte[] outValue = new byte[5]; - // not found value - int getResult = db.get(rOpt, "keyNotFound".getBytes(), - outValue); - assertThat(getResult).isEqualTo(RocksDB.NOT_FOUND); - // found value which fits in outValue - getResult = db.get(rOpt, "key1".getBytes(), outValue); - assertThat(getResult).isNotEqualTo(RocksDB.NOT_FOUND); - assertThat(outValue).isEqualTo("value".getBytes()); - // found value which fits partially - getResult = db.get(rOpt, "key2".getBytes(), outValue); - assertThat(getResult).isNotEqualTo(RocksDB.NOT_FOUND); - assertThat(outValue).isEqualTo("12345".getBytes()); - } - } - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void getOutOfArrayMaxSizeValue() throws RocksDBException { - final int numberOfValueSplits = 10; - final int splitSize = Integer.MAX_VALUE / numberOfValueSplits; - - Runtime runtime = Runtime.getRuntime(); - long neededMemory = ((long)(splitSize)) * (((long)numberOfValueSplits) + 3); - boolean isEnoughMemory = runtime.maxMemory() - runtime.totalMemory() > neededMemory; - Assume.assumeTrue(isEnoughMemory); - - final byte[] valueSplit = new byte[splitSize]; - final byte[] key = "key".getBytes(); - - thrown.expect(RocksDBException.class); - thrown.expectMessage("Requested array size exceeds VM limit"); - - // merge (numberOfValueSplits + 1) valueSplit's to get value size exceeding Integer.MAX_VALUE - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - db.put(key, valueSplit); - for (int i = 0; i < numberOfValueSplits; i++) { - db.merge(key, valueSplit); - } - db.get(key); - } - } - - @Test - public void multiGetAsList() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final ReadOptions rOpt = new ReadOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - List lookupKeys = new ArrayList<>(); - lookupKeys.add("key1".getBytes()); - lookupKeys.add("key2".getBytes()); - List results = db.multiGetAsList(lookupKeys); - assertThat(results).isNotNull(); - assertThat(results).hasSize(lookupKeys.size()); - assertThat(results). - containsExactly("value".getBytes(), "12345678".getBytes()); - // test same method with ReadOptions - results = db.multiGetAsList(rOpt, lookupKeys); - assertThat(results).isNotNull(); - assertThat(results). - contains("value".getBytes(), "12345678".getBytes()); - - // remove existing key - lookupKeys.remove(1); - // add non existing key - lookupKeys.add("key3".getBytes()); - results = db.multiGetAsList(lookupKeys); - assertThat(results).isNotNull(); - assertThat(results). - containsExactly("value".getBytes(), null); - // test same call with readOptions - results = db.multiGetAsList(rOpt, lookupKeys); - assertThat(results).isNotNull(); - assertThat(results).contains("value".getBytes()); - } - } - - @Test - public void merge() throws RocksDBException { - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options opt = new Options() - .setCreateIfMissing(true) - .setMergeOperator(stringAppendOperator); - final WriteOptions wOpt = new WriteOptions(); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath()) - ) { - db.put("key1".getBytes(), "value".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo( - "value".getBytes()); - // merge key1 with another value portion - db.merge("key1".getBytes(), "value2".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo( - "value,value2".getBytes()); - // merge key1 with another value portion - db.merge(wOpt, "key1".getBytes(), "value3".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo( - "value,value2,value3".getBytes()); - // merge on non existent key shall insert the value - db.merge(wOpt, "key2".getBytes(), "xxxx".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo( - "xxxx".getBytes()); - - Segment key3 = sliceSegment("key3"); - Segment key4 = sliceSegment("key4"); - Segment value0 = sliceSegment("value 0"); - Segment value1 = sliceSegment("value 1"); - - db.merge(key3.data, key3.offset, key3.len, value0.data, value0.offset, value0.len); - db.merge(wOpt, key4.data, key4.offset, key4.len, value1.data, value1.offset, value1.len); - - // compare - Assert.assertTrue(value0.isSamePayload(db.get(key3.data, key3.offset, key3.len))); - Assert.assertTrue(value1.isSamePayload(db.get(key4.data, key4.offset, key4.len))); - } - } - - @Test - public void delete() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteOptions wOpt = new WriteOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - db.put("key3".getBytes(), "33".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo( - "value".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo( - "12345678".getBytes()); - assertThat(db.get("key3".getBytes())).isEqualTo("33".getBytes()); - db.delete("key1".getBytes()); - db.delete(wOpt, "key2".getBytes()); - ByteBuffer key = ByteBuffer.allocateDirect(16); - key.put("key3".getBytes()).flip(); - db.delete(wOpt, key); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - - assertThat(db.get("key1".getBytes())).isNull(); - assertThat(db.get("key2".getBytes())).isNull(); - - Segment key3 = sliceSegment("key3"); - Segment key4 = sliceSegment("key4"); - db.put("key3".getBytes(), "key3 value".getBytes()); - db.put("key4".getBytes(), "key4 value".getBytes()); - - db.delete(key3.data, key3.offset, key3.len); - db.delete(wOpt, key4.data, key4.offset, key4.len); - - assertThat(db.get("key3".getBytes())).isNull(); - assertThat(db.get("key4".getBytes())).isNull(); - } - } - - @Test - public void singleDelete() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteOptions wOpt = new WriteOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo( - "value".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo( - "12345678".getBytes()); - db.singleDelete("key1".getBytes()); - db.singleDelete(wOpt, "key2".getBytes()); - assertThat(db.get("key1".getBytes())).isNull(); - assertThat(db.get("key2".getBytes())).isNull(); - } - } - - @Test - public void singleDelete_nonExisting() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteOptions wOpt = new WriteOptions()) { - db.singleDelete("key1".getBytes()); - db.singleDelete(wOpt, "key2".getBytes()); - assertThat(db.get("key1".getBytes())).isNull(); - assertThat(db.get("key2".getBytes())).isNull(); - } - } - - @Test - public void deleteRange() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - db.put("key3".getBytes(), "abcdefg".getBytes()); - db.put("key4".getBytes(), "xyz".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo("12345678".getBytes()); - assertThat(db.get("key3".getBytes())).isEqualTo("abcdefg".getBytes()); - assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - db.deleteRange("key2".getBytes(), "key4".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); - assertThat(db.get("key2".getBytes())).isNull(); - assertThat(db.get("key3".getBytes())).isNull(); - assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - } - } - - @Test - public void getIntProperty() throws RocksDBException { - try ( - final Options options = new Options() - .setCreateIfMissing(true) - .setMaxWriteBufferNumber(10) - .setMinWriteBufferNumberToMerge(10); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions wOpt = new WriteOptions().setDisableWAL(true) - ) { - db.put(wOpt, "key1".getBytes(), "value1".getBytes()); - db.put(wOpt, "key2".getBytes(), "value2".getBytes()); - db.put(wOpt, "key3".getBytes(), "value3".getBytes()); - db.put(wOpt, "key4".getBytes(), "value4".getBytes()); - assertThat(db.getLongProperty("rocksdb.num-entries-active-mem-table")) - .isGreaterThan(0); - assertThat(db.getLongProperty("rocksdb.cur-size-active-mem-table")) - .isGreaterThan(0); - } - } - - @Test - public void fullCompactRange() throws RocksDBException { - try (final Options opt = new Options(). - setCreateIfMissing(true). - setDisableAutoCompactions(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(4). - setWriteBufferSize(100 << 10). - setLevelZeroFileNumCompactionTrigger(3). - setTargetFileSizeBase(200 << 10). - setTargetFileSizeMultiplier(1). - setMaxBytesForLevelBase(500 << 10). - setMaxBytesForLevelMultiplier(1). - setDisableAutoCompactions(false); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // fill database with key/value pairs - byte[] b = new byte[10000]; - for (int i = 0; i < 200; i++) { - rand.nextBytes(b); - db.put((String.valueOf(i)).getBytes(), b); - } - db.compactRange(); - } - } - - @Test - public void fullCompactRangeColumnFamily() - throws RocksDBException { - try ( - final DBOptions opt = new DBOptions(). - setCreateIfMissing(true). - setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions(). - setDisableAutoCompactions(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(4). - setWriteBufferSize(100 << 10). - setLevelZeroFileNumCompactionTrigger(3). - setTargetFileSizeBase(200 << 10). - setTargetFileSizeMultiplier(1). - setMaxBytesForLevelBase(500 << 10). - setMaxBytesForLevelMultiplier(1). - setDisableAutoCompactions(false) - ) { - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)); - - // open database - final List columnFamilyHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, - columnFamilyHandles)) { - try { - // fill database with key/value pairs - byte[] b = new byte[10000]; - for (int i = 0; i < 200; i++) { - rand.nextBytes(b); - db.put(columnFamilyHandles.get(1), - String.valueOf(i).getBytes(), b); - } - db.compactRange(columnFamilyHandles.get(1)); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void compactRangeWithKeys() - throws RocksDBException { - try (final Options opt = new Options(). - setCreateIfMissing(true). - setDisableAutoCompactions(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(4). - setWriteBufferSize(100 << 10). - setLevelZeroFileNumCompactionTrigger(3). - setTargetFileSizeBase(200 << 10). - setTargetFileSizeMultiplier(1). - setMaxBytesForLevelBase(500 << 10). - setMaxBytesForLevelMultiplier(1). - setDisableAutoCompactions(false); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // fill database with key/value pairs - byte[] b = new byte[10000]; - for (int i = 0; i < 200; i++) { - rand.nextBytes(b); - db.put((String.valueOf(i)).getBytes(), b); - } - db.compactRange("0".getBytes(), "201".getBytes()); - } - } - - @Test - public void compactRangeWithKeysReduce() - throws RocksDBException { - try ( - final Options opt = new Options(). - setCreateIfMissing(true). - setDisableAutoCompactions(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(4). - setWriteBufferSize(100 << 10). - setLevelZeroFileNumCompactionTrigger(3). - setTargetFileSizeBase(200 << 10). - setTargetFileSizeMultiplier(1). - setMaxBytesForLevelBase(500 << 10). - setMaxBytesForLevelMultiplier(1). - setDisableAutoCompactions(false); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - // fill database with key/value pairs - byte[] b = new byte[10000]; - for (int i = 0; i < 200; i++) { - rand.nextBytes(b); - db.put((String.valueOf(i)).getBytes(), b); - } - db.flush(new FlushOptions().setWaitForFlush(true)); - try (final CompactRangeOptions compactRangeOpts = new CompactRangeOptions() - .setChangeLevel(true) - .setTargetLevel(-1) - .setTargetPathId(0)) { - db.compactRange(null, "0".getBytes(), "201".getBytes(), - compactRangeOpts); - } - } - } - - @Test - public void compactRangeWithKeysColumnFamily() - throws RocksDBException { - try (final DBOptions opt = new DBOptions(). - setCreateIfMissing(true). - setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions(). - setDisableAutoCompactions(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(4). - setWriteBufferSize(100 << 10). - setLevelZeroFileNumCompactionTrigger(3). - setTargetFileSizeBase(200 << 10). - setTargetFileSizeMultiplier(1). - setMaxBytesForLevelBase(500 << 10). - setMaxBytesForLevelMultiplier(1). - setDisableAutoCompactions(false) - ) { - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts) - ); - - // open database - final List columnFamilyHandles = - new ArrayList<>(); - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, - columnFamilyHandles)) { - try { - // fill database with key/value pairs - byte[] b = new byte[10000]; - for (int i = 0; i < 200; i++) { - rand.nextBytes(b); - db.put(columnFamilyHandles.get(1), - String.valueOf(i).getBytes(), b); - } - db.compactRange(columnFamilyHandles.get(1), - "0".getBytes(), "201".getBytes()); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void compactRangeWithKeysReduceColumnFamily() - throws RocksDBException { - try (final DBOptions opt = new DBOptions(). - setCreateIfMissing(true). - setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions(). - setDisableAutoCompactions(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(4). - setWriteBufferSize(100 << 10). - setLevelZeroFileNumCompactionTrigger(3). - setTargetFileSizeBase(200 << 10). - setTargetFileSizeMultiplier(1). - setMaxBytesForLevelBase(500 << 10). - setMaxBytesForLevelMultiplier(1). - setDisableAutoCompactions(false) - ) { - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts) - ); - - final List columnFamilyHandles = new ArrayList<>(); - // open database - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, - columnFamilyHandles)) { - try (final CompactRangeOptions compactRangeOpts = new CompactRangeOptions() - .setChangeLevel(true) - .setTargetLevel(-1) - .setTargetPathId(0)) { - // fill database with key/value pairs - byte[] b = new byte[10000]; - for (int i = 0; i < 200; i++) { - rand.nextBytes(b); - db.put(columnFamilyHandles.get(1), - String.valueOf(i).getBytes(), b); - } - db.compactRange(columnFamilyHandles.get(1), "0".getBytes(), - "201".getBytes(), compactRangeOpts); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void compactRangeToLevel() - throws RocksDBException, InterruptedException { - final int NUM_KEYS_PER_L0_FILE = 100; - final int KEY_SIZE = 20; - final int VALUE_SIZE = 300; - final int L0_FILE_SIZE = - NUM_KEYS_PER_L0_FILE * (KEY_SIZE + VALUE_SIZE); - final int NUM_L0_FILES = 10; - final int TEST_SCALE = 5; - final int KEY_INTERVAL = 100; - try (final Options opt = new Options(). - setCreateIfMissing(true). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(5). - // a slightly bigger write buffer than L0 file - // so that we can ensure manual flush always - // go before background flush happens. - setWriteBufferSize(L0_FILE_SIZE * 2). - // Disable auto L0 -> L1 compaction - setLevelZeroFileNumCompactionTrigger(20). - setTargetFileSizeBase(L0_FILE_SIZE * 100). - setTargetFileSizeMultiplier(1). - // To disable auto compaction - setMaxBytesForLevelBase(NUM_L0_FILES * L0_FILE_SIZE * 100). - setMaxBytesForLevelMultiplier(2). - setDisableAutoCompactions(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath()) - ) { - // fill database with key/value pairs - byte[] value = new byte[VALUE_SIZE]; - int int_key = 0; - for (int round = 0; round < 5; ++round) { - int initial_key = int_key; - for (int f = 1; f <= NUM_L0_FILES; ++f) { - for (int i = 0; i < NUM_KEYS_PER_L0_FILE; ++i) { - int_key += KEY_INTERVAL; - rand.nextBytes(value); - - db.put(String.format("%020d", int_key).getBytes(), - value); - } - db.flush(new FlushOptions().setWaitForFlush(true)); - // Make sure we do create one more L0 files. - assertThat( - db.getProperty("rocksdb.num-files-at-level0")). - isEqualTo("" + f); - } - - // Compact all L0 files we just created - db.compactRange( - String.format("%020d", initial_key).getBytes(), - String.format("%020d", int_key - 1).getBytes()); - // Making sure there isn't any L0 files. - assertThat( - db.getProperty("rocksdb.num-files-at-level0")). - isEqualTo("0"); - // Making sure there are some L1 files. - // Here we only use != 0 instead of a specific number - // as we don't want the test make any assumption on - // how compaction works. - assertThat( - db.getProperty("rocksdb.num-files-at-level1")). - isNotEqualTo("0"); - // Because we only compacted those keys we issued - // in this round, there shouldn't be any L1 -> L2 - // compaction. So we expect zero L2 files here. - assertThat( - db.getProperty("rocksdb.num-files-at-level2")). - isEqualTo("0"); - } - } - } - - @Test - public void deleteFilesInRange() throws RocksDBException, InterruptedException { - final int KEY_SIZE = 20; - final int VALUE_SIZE = 1000; - final int FILE_SIZE = 64000; - final int NUM_FILES = 10; - - final int KEY_INTERVAL = 10000; - /* - * Intention of these options is to end up reliably with 10 files - * we will be deleting using deleteFilesInRange. - * It is writing roughly number of keys that will fit in 10 files (target size) - * It is writing interleaved so that files from memory on L0 will overlap - * Then compaction cleans everything and we should end up with 10 files - */ - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setCompressionType(CompressionType.NO_COMPRESSION) - .setTargetFileSizeBase(FILE_SIZE) - .setWriteBufferSize(FILE_SIZE / 2) - .setDisableAutoCompactions(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - int records = FILE_SIZE / (KEY_SIZE + VALUE_SIZE); - - // fill database with key/value pairs - byte[] value = new byte[VALUE_SIZE]; - int key_init = 0; - for (int o = 0; o < NUM_FILES; ++o) { - int int_key = key_init++; - for (int i = 0; i < records; ++i) { - int_key += KEY_INTERVAL; - rand.nextBytes(value); - - db.put(String.format("%020d", int_key).getBytes(), value); - } - } - db.flush(new FlushOptions().setWaitForFlush(true)); - db.compactRange(); - // Make sure we do create one more L0 files. - assertThat(db.getProperty("rocksdb.num-files-at-level0")).isEqualTo("0"); - - // Should be 10, but we are OK with asserting +- 2 - int files = Integer.parseInt(db.getProperty("rocksdb.num-files-at-level1")); - assertThat(files).isBetween(8, 12); - - // Delete lower 60% (roughly). Result should be 5, but we are OK with asserting +- 2 - // Important is that we know something was deleted (JNI call did something) - // Exact assertions are done in C++ unit tests - db.deleteFilesInRanges(null, - Arrays.asList(null, String.format("%020d", records * KEY_INTERVAL * 6 / 10).getBytes()), - false); - files = Integer.parseInt(db.getProperty("rocksdb.num-files-at-level1")); - assertThat(files).isBetween(3, 7); - } - } - - @Test - public void compactRangeToLevelColumnFamily() - throws RocksDBException { - final int NUM_KEYS_PER_L0_FILE = 100; - final int KEY_SIZE = 20; - final int VALUE_SIZE = 300; - final int L0_FILE_SIZE = - NUM_KEYS_PER_L0_FILE * (KEY_SIZE + VALUE_SIZE); - final int NUM_L0_FILES = 10; - final int TEST_SCALE = 5; - final int KEY_INTERVAL = 100; - - try (final DBOptions opt = new DBOptions(). - setCreateIfMissing(true). - setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions(). - setCompactionStyle(CompactionStyle.LEVEL). - setNumLevels(5). - // a slightly bigger write buffer than L0 file - // so that we can ensure manual flush always - // go before background flush happens. - setWriteBufferSize(L0_FILE_SIZE * 2). - // Disable auto L0 -> L1 compaction - setLevelZeroFileNumCompactionTrigger(20). - setTargetFileSizeBase(L0_FILE_SIZE * 100). - setTargetFileSizeMultiplier(1). - // To disable auto compaction - setMaxBytesForLevelBase(NUM_L0_FILES * L0_FILE_SIZE * 100). - setMaxBytesForLevelMultiplier(2). - setDisableAutoCompactions(true) - ) { - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts) - ); - - final List columnFamilyHandles = new ArrayList<>(); - // open database - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, - columnFamilyHandles)) { - try { - // fill database with key/value pairs - byte[] value = new byte[VALUE_SIZE]; - int int_key = 0; - for (int round = 0; round < 5; ++round) { - int initial_key = int_key; - for (int f = 1; f <= NUM_L0_FILES; ++f) { - for (int i = 0; i < NUM_KEYS_PER_L0_FILE; ++i) { - int_key += KEY_INTERVAL; - rand.nextBytes(value); - - db.put(columnFamilyHandles.get(1), - String.format("%020d", int_key).getBytes(), - value); - } - db.flush(new FlushOptions().setWaitForFlush(true), - columnFamilyHandles.get(1)); - // Make sure we do create one more L0 files. - assertThat( - db.getProperty(columnFamilyHandles.get(1), - "rocksdb.num-files-at-level0")). - isEqualTo("" + f); - } - - // Compact all L0 files we just created - db.compactRange( - columnFamilyHandles.get(1), - String.format("%020d", initial_key).getBytes(), - String.format("%020d", int_key - 1).getBytes()); - // Making sure there isn't any L0 files. - assertThat( - db.getProperty(columnFamilyHandles.get(1), - "rocksdb.num-files-at-level0")). - isEqualTo("0"); - // Making sure there are some L1 files. - // Here we only use != 0 instead of a specific number - // as we don't want the test make any assumption on - // how compaction works. - assertThat( - db.getProperty(columnFamilyHandles.get(1), - "rocksdb.num-files-at-level1")). - isNotEqualTo("0"); - // Because we only compacted those keys we issued - // in this round, there shouldn't be any L1 -> L2 - // compaction. So we expect zero L2 files here. - assertThat( - db.getProperty(columnFamilyHandles.get(1), - "rocksdb.num-files-at-level2")). - isEqualTo("0"); - } - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void continueBackgroundWorkAfterCancelAllBackgroundWork() throws RocksDBException { - final int KEY_SIZE = 20; - final int VALUE_SIZE = 300; - try (final DBOptions opt = new DBOptions(). - setCreateIfMissing(true). - setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions() - ) { - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts) - ); - - final List columnFamilyHandles = new ArrayList<>(); - // open the database - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, - columnFamilyHandles)) { - try { - db.cancelAllBackgroundWork(true); - try { - db.put(new byte[KEY_SIZE], new byte[VALUE_SIZE]); - db.flush(new FlushOptions().setWaitForFlush(true)); - fail("Expected RocksDBException to be thrown if we attempt to trigger a flush after" + - " all background work is cancelled."); - } catch (RocksDBException ignored) { } - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void cancelAllBackgroundWorkTwice() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()) - ) { - // Cancel all background work synchronously - db.cancelAllBackgroundWork(true); - // Cancel all background work asynchronously - db.cancelAllBackgroundWork(false); - } - } - - @Test - public void pauseContinueBackgroundWork() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()) - ) { - db.pauseBackgroundWork(); - db.continueBackgroundWork(); - db.pauseBackgroundWork(); - db.continueBackgroundWork(); - } - } - - @Test - public void enableDisableFileDeletions() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()) - ) { - db.disableFileDeletions(); - db.enableFileDeletions(false); - db.disableFileDeletions(); - db.enableFileDeletions(true); - } - } - - @Test - public void setOptions() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions() - .setWriteBufferSize(4096)) { - - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)); - - // open database - final List columnFamilyHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, columnFamilyHandles)) { - try { - final MutableColumnFamilyOptions mutableOptions = - MutableColumnFamilyOptions.builder() - .setWriteBufferSize(2048) - .build(); - - db.setOptions(columnFamilyHandles.get(1), mutableOptions); - - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void destroyDB() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.put("key1".getBytes(), "value".getBytes()); - } - assertThat(dbFolder.getRoot().exists() && dbFolder.getRoot().listFiles().length != 0) - .isTrue(); - RocksDB.destroyDB(dbPath, options); - assertThat(dbFolder.getRoot().exists() && dbFolder.getRoot().listFiles().length != 0) - .isFalse(); - } - } - - @Test(expected = RocksDBException.class) - public void destroyDBFailIfOpen() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - // Fails as the db is open and locked. - RocksDB.destroyDB(dbPath, options); - } - } - } - - @Test - public void getApproximateSizes() throws RocksDBException { - final byte key1[] = "key1".getBytes(UTF_8); - final byte key2[] = "key2".getBytes(UTF_8); - final byte key3[] = "key3".getBytes(UTF_8); - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.put(key1, key1); - db.put(key2, key2); - db.put(key3, key3); - - final long[] sizes = db.getApproximateSizes( - Arrays.asList( - new Range(new Slice(key1), new Slice(key1)), - new Range(new Slice(key2), new Slice(key3)) - ), - SizeApproximationFlag.INCLUDE_FILES, - SizeApproximationFlag.INCLUDE_MEMTABLES); - - assertThat(sizes.length).isEqualTo(2); - assertThat(sizes[0]).isEqualTo(0); - assertThat(sizes[1]).isGreaterThanOrEqualTo(1); - } - } - } - - @Test - public void getApproximateMemTableStats() throws RocksDBException { - final byte key1[] = "key1".getBytes(UTF_8); - final byte key2[] = "key2".getBytes(UTF_8); - final byte key3[] = "key3".getBytes(UTF_8); - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.put(key1, key1); - db.put(key2, key2); - db.put(key3, key3); - - final RocksDB.CountAndSize stats = - db.getApproximateMemTableStats( - new Range(new Slice(key1), new Slice(key3))); - - assertThat(stats).isNotNull(); - assertThat(stats.count).isGreaterThan(1); - assertThat(stats.size).isGreaterThan(1); - } - } - } - - @Test - public void getApproximateMemTableStatsSingleKey() throws RocksDBException { - final byte key1[] = "key1".getBytes(UTF_8); - final byte key2[] = "key2".getBytes(UTF_8); - final byte key3[] = "key3".getBytes(UTF_8); - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.put(key1, key1); - - final RocksDB.CountAndSize stats = - db.getApproximateMemTableStats(new Range(new Slice(key1), new Slice(key3))); - - assertThat(stats).isNotNull(); - assertThat(stats.count).isEqualTo(1); - assertThat(stats.size).isGreaterThan(1); - } - } - } - - @Ignore("TODO(AR) re-enable when ready!") - @Test - public void compactFiles() throws RocksDBException { - final int kTestKeySize = 16; - final int kTestValueSize = 984; - final int kEntrySize = kTestKeySize + kTestValueSize; - final int kEntriesPerBuffer = 100; - final int writeBufferSize = kEntrySize * kEntriesPerBuffer; - final byte[] cfName = "pikachu".getBytes(UTF_8); - - try (final Options options = new Options() - .setCreateIfMissing(true) - .setWriteBufferSize(writeBufferSize) - .setCompactionStyle(CompactionStyle.LEVEL) - .setTargetFileSizeBase(writeBufferSize) - .setMaxBytesForLevelBase(writeBufferSize * 2) - .setLevel0StopWritesTrigger(2) - .setMaxBytesForLevelMultiplier(2) - .setCompressionType(CompressionType.NO_COMPRESSION) - .setMaxSubcompactions(4)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath); - final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(options)) { - db.createColumnFamily(new ColumnFamilyDescriptor(cfName, - cfOptions)).close(); - } - - try (final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(options)) { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions), - new ColumnFamilyDescriptor(cfName, cfOptions) - ); - final List cfHandles = new ArrayList<>(); - try (final DBOptions dbOptions = new DBOptions(options); - final RocksDB db = RocksDB.open(dbOptions, dbPath, cfDescriptors, - cfHandles); - ) { - try (final FlushOptions flushOptions = new FlushOptions() - .setWaitForFlush(true) - .setAllowWriteStall(true); - final CompactionOptions compactionOptions = new CompactionOptions()) { - final Random rnd = new Random(301); - for (int key = 64 * kEntriesPerBuffer; key >= 0; --key) { - final byte[] value = new byte[kTestValueSize]; - rnd.nextBytes(value); - db.put(cfHandles.get(1), Integer.toString(key).getBytes(UTF_8), - value); - } - db.flush(flushOptions, cfHandles); - - final RocksDB.LiveFiles liveFiles = db.getLiveFiles(); - final List compactedFiles = - db.compactFiles(compactionOptions, cfHandles.get(1), - liveFiles.files, 1, -1, null); - assertThat(compactedFiles).isNotEmpty(); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } - } - - @Test - public void enableAutoCompaction() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true)) { - final List cfDescs = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) - ); - final List cfHandles = new ArrayList<>(); - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { - try { - db.enableAutoCompaction(cfHandles); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } - - @Test - public void numberLevels() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db.numberLevels()).isEqualTo(7); - } - } - } - - @Test - public void maxMemCompactionLevel() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db.maxMemCompactionLevel()).isEqualTo(0); - } - } - } - - @Test - public void level0StopWriteTrigger() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db.level0StopWriteTrigger()).isEqualTo(36); - } - } - } - - @Test - public void getName() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db.getName()).isEqualTo(dbPath); - } - } - } - - @Test - public void getEnv() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - assertThat(db.getEnv()).isEqualTo(Env.getDefault()); - } - } - } - - @Test - public void flush() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath); - final FlushOptions flushOptions = new FlushOptions()) { - db.flush(flushOptions); - } - } - } - - @Test - public void flushWal() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.flushWal(true); - } - } - } - - @Test - public void syncWal() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.syncWal(); - } - } - } - - @Test - public void getLiveFiles() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - final RocksDB.LiveFiles livefiles = db.getLiveFiles(true); - assertThat(livefiles).isNotNull(); - assertThat(livefiles.manifestFileSize).isEqualTo(66); - assertThat(livefiles.files.size()).isEqualTo(3); - assertThat(livefiles.files.get(0)).isEqualTo("/CURRENT"); - assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000005"); - assertThat(livefiles.files.get(2)).isEqualTo("/OPTIONS-000007"); - } - } - } - - @Test - public void getSortedWalFiles() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - final List logFiles = db.getSortedWalFiles(); - assertThat(logFiles).isNotNull(); - assertThat(logFiles.size()).isEqualTo(1); - assertThat(logFiles.get(0).type()) - .isEqualTo(WalFileType.kAliveLogFile); - } - } - } - - @Test - public void deleteFile() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.deleteFile("unknown"); - } - } - } - - @Test - public void getLiveFilesMetaData() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - final List liveFilesMetaData - = db.getLiveFilesMetaData(); - assertThat(liveFilesMetaData).isEmpty(); - } - } - } - - @Test - public void getColumnFamilyMetaData() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true)) { - final List cfDescs = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) - ); - final List cfHandles = new ArrayList<>(); - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { - db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - try { - final ColumnFamilyMetaData cfMetadata = - db.getColumnFamilyMetaData(cfHandles.get(0)); - assertThat(cfMetadata).isNotNull(); - assertThat(cfMetadata.name()).isEqualTo(RocksDB.DEFAULT_COLUMN_FAMILY); - assertThat(cfMetadata.levels().size()).isEqualTo(7); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } - - @Test - public void verifyChecksum() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.verifyChecksum(); - } - } - } - - @Test - public void getPropertiesOfAllTables() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true)) { - final List cfDescs = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) - ); - final List cfHandles = new ArrayList<>(); - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { - db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - try { - final Map properties = - db.getPropertiesOfAllTables(cfHandles.get(0)); - assertThat(properties).isNotNull(); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } - - @Test - public void getPropertiesOfTablesInRange() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true)) { - final List cfDescs = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) - ); - final List cfHandles = new ArrayList<>(); - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { - db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - db.put(cfHandles.get(0), "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); - db.put(cfHandles.get(0), "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); - try { - final Range range = new Range( - new Slice("key1".getBytes(UTF_8)), - new Slice("key3".getBytes(UTF_8))); - final Map properties = - db.getPropertiesOfTablesInRange( - cfHandles.get(0), Arrays.asList(range)); - assertThat(properties).isNotNull(); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } - - @Test - public void suggestCompactRange() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true)) { - final List cfDescs = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) - ); - final List cfHandles = new ArrayList<>(); - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { - db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - db.put(cfHandles.get(0), "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); - db.put(cfHandles.get(0), "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); - try { - final Range range = db.suggestCompactRange(cfHandles.get(0)); - assertThat(range).isNotNull(); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } - - @Test - public void promoteL0() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - db.promoteL0(2); - } - } - } - - @Test - public void startTrace() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true)) { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - try (final RocksDB db = RocksDB.open(options, dbPath)) { - final TraceOptions traceOptions = new TraceOptions(); - - try (final InMemoryTraceWriter traceWriter = new InMemoryTraceWriter()) { - db.startTrace(traceOptions, traceWriter); - - db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - - db.endTrace(); - - final List writes = traceWriter.getWrites(); - assertThat(writes.size()).isGreaterThan(0); - } - } - } - } - - @Test - public void setDBOptions() throws RocksDBException { - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions() - .setWriteBufferSize(4096)) { - - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)); - - // open database - final List columnFamilyHandles = new ArrayList<>(); - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, columnFamilyHandles)) { - try { - final MutableDBOptions mutableOptions = - MutableDBOptions.builder() - .setBytesPerSync(1024 * 1027 * 7) - .setAvoidFlushDuringShutdown(false) - .build(); - - db.setDBOptions(mutableOptions); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void rocksdbVersion() { - final RocksDB.Version version = RocksDB.rocksdbVersion(); - assertThat(version).isNotNull(); - assertThat(version.getMajor()).isGreaterThan(1); - } - - private static class InMemoryTraceWriter extends AbstractTraceWriter { - private final List writes = new ArrayList<>(); - private volatile boolean closed = false; - - @Override - public void write(final Slice slice) { - if (closed) { - return; - } - final byte[] data = slice.data(); - final byte[] dataCopy = new byte[data.length]; - System.arraycopy(data, 0, dataCopy, 0, data.length); - writes.add(dataCopy); - } - - @Override - public void closeWriter() { - closed = true; - } - - @Override - public long getFileSize() { - long size = 0; - for (int i = 0; i < writes.size(); i++) { - size += writes.get(i).length; - } - return size; - } - - public List getWrites() { - return writes; - } - } -} diff --git a/java/src/test/java/org/rocksdb/RocksIteratorTest.java b/java/src/test/java/org/rocksdb/RocksIteratorTest.java deleted file mode 100644 index 2a13550b7..000000000 --- a/java/src/test/java/org/rocksdb/RocksIteratorTest.java +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class RocksIteratorTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - private void validateByteBufferResult( - final int fetched, final ByteBuffer byteBuffer, final String expected) { - assertThat(fetched).isEqualTo(expected.length()); - assertThat(byteBuffer.position()).isEqualTo(0); - assertThat(byteBuffer.limit()).isEqualTo(Math.min(byteBuffer.remaining(), expected.length())); - final int bufferSpace = byteBuffer.remaining(); - final byte[] contents = new byte[bufferSpace]; - byteBuffer.get(contents, 0, bufferSpace); - assertThat(contents).isEqualTo( - expected.substring(0, bufferSpace).getBytes(StandardCharsets.UTF_8)); - } - - private void validateKey( - final RocksIterator iterator, final ByteBuffer byteBuffer, final String key) { - validateByteBufferResult(iterator.key(byteBuffer), byteBuffer, key); - } - - private void validateValue( - final RocksIterator iterator, final ByteBuffer byteBuffer, final String value) { - validateByteBufferResult(iterator.value(byteBuffer), byteBuffer, value); - } - - @Test - public void rocksIterator() throws RocksDBException { - try (final Options options = - new Options().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1".getBytes()); - db.put("key2".getBytes(), "value2".getBytes()); - - try (final RocksIterator iterator = db.newIterator()) { - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - - validateKey(iterator, ByteBuffer.allocateDirect(2), "key1"); - validateKey(iterator, ByteBuffer.allocateDirect(2), "key0"); - validateKey(iterator, ByteBuffer.allocateDirect(4), "key1"); - validateKey(iterator, ByteBuffer.allocateDirect(5), "key1"); - validateValue(iterator, ByteBuffer.allocateDirect(2), "value2"); - validateValue(iterator, ByteBuffer.allocateDirect(2), "vasicu"); - validateValue(iterator, ByteBuffer.allocateDirect(8), "value1"); - - validateKey(iterator, ByteBuffer.allocate(2), "key1"); - validateKey(iterator, ByteBuffer.allocate(2), "key0"); - validateKey(iterator, ByteBuffer.allocate(4), "key1"); - validateKey(iterator, ByteBuffer.allocate(5), "key1"); - validateValue(iterator, ByteBuffer.allocate(2), "value1"); - validateValue(iterator, ByteBuffer.allocate(8), "value1"); - - iterator.next(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - iterator.next(); - assertThat(iterator.isValid()).isFalse(); - iterator.seekToLast(); - iterator.prev(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - iterator.seekToFirst(); - iterator.seekToLast(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - iterator.status(); - - { - final ByteBuffer key = ByteBuffer.allocate(12); - key.put("key1".getBytes()).flip(); - iterator.seek(key); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - - validateValue(iterator, ByteBuffer.allocateDirect(12), "value1"); - validateValue(iterator, ByteBuffer.allocateDirect(4), "valu56"); - } - - { - final ByteBuffer key = ByteBuffer.allocate(12); - key.put("key2".getBytes()).flip(); - iterator.seekForPrev(key); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - } - - { - final ByteBuffer key = ByteBuffer.allocate(12); - key.put("key1".getBytes()).flip(); - iterator.seek(key); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - } - - { - // Check offsets of slice byte buffers - final ByteBuffer key0 = ByteBuffer.allocate(24); - key0.put("key2key2".getBytes()); - final ByteBuffer key = key0.slice(); - key.put("key1".getBytes()).flip(); - iterator.seek(key); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - } - - { - // Check offsets of slice byte buffers - final ByteBuffer key0 = ByteBuffer.allocateDirect(24); - key0.put("key2key2".getBytes()); - final ByteBuffer key = key0.slice(); - key.put("key1".getBytes()).flip(); - iterator.seek(key); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - } - - { - final ByteBuffer key = ByteBuffer.allocate(12); - key.put("key2".getBytes()).flip(); - iterator.seekForPrev(key); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - assertThat(key.position()).isEqualTo(4); - assertThat(key.limit()).isEqualTo(4); - } - } - } - } - - @Test - public void rocksIteratorSeekAndInsert() throws RocksDBException { - try (final Options options = - new Options().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1".getBytes()); - db.put("key2".getBytes(), "value2".getBytes()); - - try (final RocksIterator iterator = db.newIterator()) { - iterator.seek("key0".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - - iterator.seek("key1".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - - iterator.seek("key1.5".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - - iterator.seek("key2".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - - iterator.seek("key3".getBytes()); - assertThat(iterator.isValid()).isFalse(); - } - - try (final RocksIterator iterator = db.newIterator()) { - iterator.seekForPrev("key0".getBytes()); - assertThat(iterator.isValid()).isFalse(); - - iterator.seekForPrev("key1".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - - iterator.seekForPrev("key1.5".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - - iterator.seekForPrev("key2".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - - iterator.seekForPrev("key3".getBytes()); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - } - - try (final RocksIterator iterator = db.newIterator()) { - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - - byte[] lastKey; - do { - lastKey = iterator.key(); - iterator.next(); - } while (iterator.isValid()); - - db.put("key3".getBytes(), "value3".getBytes()); - assertThat(iterator.isValid()).isFalse(); - iterator.refresh(); - iterator.seek(lastKey); - assertThat(iterator.isValid()).isTrue(); - - iterator.next(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key3".getBytes()); - } - } - } - - @Test - public void rocksIteratorReleaseAfterCfClose() throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - this.dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "value".getBytes()); - - // Test case: release iterator after default CF close - try (final RocksIterator iterator = db.newIterator()) { - // In fact, calling close() on default CF has no effect - db.getDefaultColumnFamily().close(); - - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key".getBytes()); - assertThat(iterator.value()).isEqualTo("value".getBytes()); - } - - // Test case: release iterator after custom CF close - final ColumnFamilyDescriptor cfd1 = new ColumnFamilyDescriptor("cf1".getBytes()); - final ColumnFamilyHandle cfHandle1 = db.createColumnFamily(cfd1); - db.put(cfHandle1, "key1".getBytes(), "value1".getBytes()); - - try (final RocksIterator iterator = db.newIterator(cfHandle1)) { - cfHandle1.close(); - - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - } - - // Test case: release iterator after custom CF drop & close - final ColumnFamilyDescriptor cfd2 = new ColumnFamilyDescriptor("cf2".getBytes()); - final ColumnFamilyHandle cfHandle2 = db.createColumnFamily(cfd2); - db.put(cfHandle2, "key2".getBytes(), "value2".getBytes()); - - try (final RocksIterator iterator = db.newIterator(cfHandle2)) { - db.dropColumnFamily(cfHandle2); - cfHandle2.close(); - - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/RocksMemEnvTest.java b/java/src/test/java/org/rocksdb/RocksMemEnvTest.java deleted file mode 100644 index cce0c61e0..000000000 --- a/java/src/test/java/org/rocksdb/RocksMemEnvTest.java +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RocksMemEnvTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void memEnvFillAndReopen() throws RocksDBException { - - final byte[][] keys = { - "aaa".getBytes(), - "bbb".getBytes(), - "ccc".getBytes() - }; - - final byte[][] values = { - "foo".getBytes(), - "bar".getBytes(), - "baz".getBytes() - }; - - try (final Env env = new RocksMemEnv(Env.getDefault()); - final Options options = new Options() - .setCreateIfMissing(true) - .setEnv(env); - final FlushOptions flushOptions = new FlushOptions() - .setWaitForFlush(true); - ) { - try (final RocksDB db = RocksDB.open(options, "/dir/db")) { - // write key/value pairs using MemEnv - for (int i = 0; i < keys.length; i++) { - db.put(keys[i], values[i]); - } - - // read key/value pairs using MemEnv - for (int i = 0; i < keys.length; i++) { - assertThat(db.get(keys[i])).isEqualTo(values[i]); - } - - // Check iterator access - try (final RocksIterator iterator = db.newIterator()) { - iterator.seekToFirst(); - for (int i = 0; i < keys.length; i++) { - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo(keys[i]); - assertThat(iterator.value()).isEqualTo(values[i]); - iterator.next(); - } - // reached end of database - assertThat(iterator.isValid()).isFalse(); - } - - // flush - db.flush(flushOptions); - - // read key/value pairs after flush using MemEnv - for (int i = 0; i < keys.length; i++) { - assertThat(db.get(keys[i])).isEqualTo(values[i]); - } - } - - options.setCreateIfMissing(false); - - // After reopen the values shall still be in the mem env. - // as long as the env is not freed. - try (final RocksDB db = RocksDB.open(options, "/dir/db")) { - // read key/value pairs using MemEnv - for (int i = 0; i < keys.length; i++) { - assertThat(db.get(keys[i])).isEqualTo(values[i]); - } - } - } - } - - @Test - public void multipleDatabaseInstances() throws RocksDBException { - // db - keys - final byte[][] keys = { - "aaa".getBytes(), - "bbb".getBytes(), - "ccc".getBytes() - }; - // otherDb - keys - final byte[][] otherKeys = { - "111".getBytes(), - "222".getBytes(), - "333".getBytes() - }; - // values - final byte[][] values = { - "foo".getBytes(), - "bar".getBytes(), - "baz".getBytes() - }; - - try (final Env env = new RocksMemEnv(Env.getDefault()); - final Options options = new Options().setCreateIfMissing(true).setEnv(env); - final RocksDB db = RocksDB.open(options, "/dir/db"); - final RocksDB otherDb = RocksDB.open(options, "/dir/otherDb")) { - // write key/value pairs using MemEnv - // to db and to otherDb. - for (int i = 0; i < keys.length; i++) { - db.put(keys[i], values[i]); - otherDb.put(otherKeys[i], values[i]); - } - - // verify key/value pairs after flush using MemEnv - for (int i = 0; i < keys.length; i++) { - // verify db - assertThat(db.get(otherKeys[i])).isNull(); - assertThat(db.get(keys[i])).isEqualTo(values[i]); - - // verify otherDb - assertThat(otherDb.get(keys[i])).isNull(); - assertThat(otherDb.get(otherKeys[i])).isEqualTo(values[i]); - } - } - } - - @Test(expected = RocksDBException.class) - public void createIfMissingFalse() throws RocksDBException { - try (final Env env = new RocksMemEnv(Env.getDefault()); - final Options options = new Options().setCreateIfMissing(false).setEnv(env); - final RocksDB db = RocksDB.open(options, "/db/dir")) { - // shall throw an exception because db dir does not - // exist. - } - } -} diff --git a/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java b/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java deleted file mode 100644 index 6116f2f92..000000000 --- a/java/src/test/java/org/rocksdb/RocksNativeLibraryResource.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.rules.ExternalResource; - -/** - * Resource to load the RocksDB JNI library. - */ -public class RocksNativeLibraryResource extends ExternalResource { - @Override - protected void before() { - RocksDB.loadLibrary(); - } -} diff --git a/java/src/test/java/org/rocksdb/SecondaryDBTest.java b/java/src/test/java/org/rocksdb/SecondaryDBTest.java deleted file mode 100644 index 557d4a47d..000000000 --- a/java/src/test/java/org/rocksdb/SecondaryDBTest.java +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class SecondaryDBTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Rule public TemporaryFolder secondaryDbFolder = new TemporaryFolder(); - - @Test - public void openAsSecondary() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1".getBytes()); - db.put("key2".getBytes(), "value2".getBytes()); - db.put("key3".getBytes(), "value3".getBytes()); - - // open secondary - try (final Options secondaryOptions = new Options(); - final RocksDB secondaryDb = - RocksDB.openAsSecondary(secondaryOptions, dbFolder.getRoot().getAbsolutePath(), - secondaryDbFolder.getRoot().getAbsolutePath())) { - assertThat(secondaryDb.get("key1".getBytes())).isEqualTo("value1".getBytes()); - assertThat(secondaryDb.get("key2".getBytes())).isEqualTo("value2".getBytes()); - assertThat(secondaryDb.get("key3".getBytes())).isEqualTo("value3".getBytes()); - - // write to primary - db.put("key4".getBytes(), "value4".getBytes()); - db.put("key5".getBytes(), "value5".getBytes()); - db.put("key6".getBytes(), "value6".getBytes()); - - // tell secondary to catch up - secondaryDb.tryCatchUpWithPrimary(); - - db.put("key7".getBytes(), "value7".getBytes()); - - // check secondary - assertThat(secondaryDb.get("key4".getBytes())).isEqualTo("value4".getBytes()); - assertThat(secondaryDb.get("key5".getBytes())).isEqualTo("value5".getBytes()); - assertThat(secondaryDb.get("key6".getBytes())).isEqualTo("value6".getBytes()); - - assertThat(secondaryDb.get("key7".getBytes())).isNull(); - } - } - } - - @Test - public void openAsSecondaryColumnFamilies() throws RocksDBException { - try (final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions()) { - final List cfDescriptors = new ArrayList<>(); - cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpts)); - cfDescriptors.add(new ColumnFamilyDescriptor("cf1".getBytes(), cfOpts)); - - final List cfHandles = new ArrayList<>(); - - try (final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfDescriptors, cfHandles)) { - try { - final ColumnFamilyHandle cf1 = cfHandles.get(1); - - db.put(cf1, "key1".getBytes(), "value1".getBytes()); - db.put(cf1, "key2".getBytes(), "value2".getBytes()); - db.put(cf1, "key3".getBytes(), "value3".getBytes()); - - final List secondaryCfHandles = new ArrayList<>(); - - // open secondary - try (final DBOptions secondaryOptions = new DBOptions(); - final RocksDB secondaryDb = - RocksDB.openAsSecondary(secondaryOptions, dbFolder.getRoot().getAbsolutePath(), - secondaryDbFolder.getRoot().getAbsolutePath(), cfDescriptors, - secondaryCfHandles)) { - try { - final ColumnFamilyHandle secondaryCf1 = secondaryCfHandles.get(1); - - assertThat(secondaryDb.get(secondaryCf1, "key1".getBytes())) - .isEqualTo("value1".getBytes()); - assertThat(secondaryDb.get(secondaryCf1, "key2".getBytes())) - .isEqualTo("value2".getBytes()); - assertThat(secondaryDb.get(secondaryCf1, "key3".getBytes())) - .isEqualTo("value3".getBytes()); - - // write to primary - db.put(cf1, "key4".getBytes(), "value4".getBytes()); - db.put(cf1, "key5".getBytes(), "value5".getBytes()); - db.put(cf1, "key6".getBytes(), "value6".getBytes()); - - // tell secondary to catch up - secondaryDb.tryCatchUpWithPrimary(); - - db.put(cf1, "key7".getBytes(), "value7".getBytes()); - - // check secondary - assertThat(secondaryDb.get(secondaryCf1, "key4".getBytes())) - .isEqualTo("value4".getBytes()); - assertThat(secondaryDb.get(secondaryCf1, "key5".getBytes())) - .isEqualTo("value5".getBytes()); - assertThat(secondaryDb.get(secondaryCf1, "key6".getBytes())) - .isEqualTo("value6".getBytes()); - - assertThat(secondaryDb.get(secondaryCf1, "key7".getBytes())).isNull(); - - } finally { - for (final ColumnFamilyHandle secondaryCfHandle : secondaryCfHandles) { - secondaryCfHandle.close(); - } - } - } - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - } - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/SliceTest.java b/java/src/test/java/org/rocksdb/SliceTest.java deleted file mode 100644 index c65b01903..000000000 --- a/java/src/test/java/org/rocksdb/SliceTest.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SliceTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void slice() { - try (final Slice slice = new Slice("testSlice")) { - assertThat(slice.empty()).isFalse(); - assertThat(slice.size()).isEqualTo(9); - assertThat(slice.data()).isEqualTo("testSlice".getBytes()); - } - - try (final Slice otherSlice = new Slice("otherSlice".getBytes())) { - assertThat(otherSlice.data()).isEqualTo("otherSlice".getBytes()); - } - - try (final Slice thirdSlice = new Slice("otherSlice".getBytes(), 5)) { - assertThat(thirdSlice.data()).isEqualTo("Slice".getBytes()); - } - } - - @Test - public void sliceClear() { - try (final Slice slice = new Slice("abc")) { - assertThat(slice.toString()).isEqualTo("abc"); - slice.clear(); - assertThat(slice.toString()).isEmpty(); - slice.clear(); // make sure we don't double-free - } - } - - @Test - public void sliceRemovePrefix() { - try (final Slice slice = new Slice("abc")) { - assertThat(slice.toString()).isEqualTo("abc"); - slice.removePrefix(1); - assertThat(slice.toString()).isEqualTo("bc"); - } - } - - @Test - public void sliceEquals() { - try (final Slice slice = new Slice("abc"); - final Slice slice2 = new Slice("abc")) { - assertThat(slice.equals(slice2)).isTrue(); - assertThat(slice.hashCode() == slice2.hashCode()).isTrue(); - } - } - - @Test - public void sliceStartWith() { - try (final Slice slice = new Slice("matchpoint"); - final Slice match = new Slice("mat"); - final Slice noMatch = new Slice("nomatch")) { - assertThat(slice.startsWith(match)).isTrue(); - assertThat(slice.startsWith(noMatch)).isFalse(); - } - } - - @Test - public void sliceToString() { - try (final Slice slice = new Slice("stringTest")) { - assertThat(slice.toString()).isEqualTo("stringTest"); - assertThat(slice.toString(true)).isNotEqualTo(""); - } - } -} diff --git a/java/src/test/java/org/rocksdb/SnapshotTest.java b/java/src/test/java/org/rocksdb/SnapshotTest.java deleted file mode 100644 index 11f0d560a..000000000 --- a/java/src/test/java/org/rocksdb/SnapshotTest.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SnapshotTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void snapshots() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "value".getBytes()); - // Get new Snapshot of database - try (final Snapshot snapshot = db.getSnapshot()) { - assertThat(snapshot.getSequenceNumber()).isGreaterThan(0); - assertThat(snapshot.getSequenceNumber()).isEqualTo(1); - try (final ReadOptions readOptions = new ReadOptions()) { - // set snapshot in ReadOptions - readOptions.setSnapshot(snapshot); - - // retrieve key value pair - assertThat(new String(db.get("key".getBytes()))). - isEqualTo("value"); - // retrieve key value pair created before - // the snapshot was made - assertThat(new String(db.get(readOptions, - "key".getBytes()))).isEqualTo("value"); - // add new key/value pair - db.put("newkey".getBytes(), "newvalue".getBytes()); - // using no snapshot the latest db entries - // will be taken into account - assertThat(new String(db.get("newkey".getBytes()))). - isEqualTo("newvalue"); - // snapshopot was created before newkey - assertThat(db.get(readOptions, "newkey".getBytes())). - isNull(); - // Retrieve snapshot from read options - try (final Snapshot sameSnapshot = readOptions.snapshot()) { - readOptions.setSnapshot(sameSnapshot); - // results must be the same with new Snapshot - // instance using the same native pointer - assertThat(new String(db.get(readOptions, - "key".getBytes()))).isEqualTo("value"); - // update key value pair to newvalue - db.put("key".getBytes(), "newvalue".getBytes()); - // read with previously created snapshot will - // read previous version of key value pair - assertThat(new String(db.get(readOptions, - "key".getBytes()))).isEqualTo("value"); - // read for newkey using the snapshot must be - // null - assertThat(db.get(readOptions, "newkey".getBytes())). - isNull(); - // setting null to snapshot in ReadOptions leads - // to no Snapshot being used. - readOptions.setSnapshot(null); - assertThat(new String(db.get(readOptions, - "newkey".getBytes()))).isEqualTo("newvalue"); - // release Snapshot - db.releaseSnapshot(snapshot); - } - } - } - } - } - - @Test - public void iteratorWithSnapshot() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - db.put("key".getBytes(), "value".getBytes()); - - // Get new Snapshot of database - // set snapshot in ReadOptions - try (final Snapshot snapshot = db.getSnapshot(); - final ReadOptions readOptions = - new ReadOptions().setSnapshot(snapshot)) { - db.put("key2".getBytes(), "value2".getBytes()); - - // iterate over current state of db - try (final RocksIterator iterator = db.newIterator()) { - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key".getBytes()); - iterator.next(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - iterator.next(); - assertThat(iterator.isValid()).isFalse(); - } - - // iterate using a snapshot - try (final RocksIterator snapshotIterator = - db.newIterator(readOptions)) { - snapshotIterator.seekToFirst(); - assertThat(snapshotIterator.isValid()).isTrue(); - assertThat(snapshotIterator.key()).isEqualTo("key".getBytes()); - snapshotIterator.next(); - assertThat(snapshotIterator.isValid()).isFalse(); - } - - // release Snapshot - db.releaseSnapshot(snapshot); - } - } - } - - @Test - public void iteratorWithSnapshotOnColumnFamily() throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - db.put("key".getBytes(), "value".getBytes()); - - // Get new Snapshot of database - // set snapshot in ReadOptions - try (final Snapshot snapshot = db.getSnapshot(); - final ReadOptions readOptions = new ReadOptions() - .setSnapshot(snapshot)) { - db.put("key2".getBytes(), "value2".getBytes()); - - // iterate over current state of column family - try (final RocksIterator iterator = db.newIterator( - db.getDefaultColumnFamily())) { - iterator.seekToFirst(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key".getBytes()); - iterator.next(); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - iterator.next(); - assertThat(iterator.isValid()).isFalse(); - } - - // iterate using a snapshot on default column family - try (final RocksIterator snapshotIterator = db.newIterator( - db.getDefaultColumnFamily(), readOptions)) { - snapshotIterator.seekToFirst(); - assertThat(snapshotIterator.isValid()).isTrue(); - assertThat(snapshotIterator.key()).isEqualTo("key".getBytes()); - snapshotIterator.next(); - assertThat(snapshotIterator.isValid()).isFalse(); - - // release Snapshot - db.releaseSnapshot(snapshot); - } - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/SstFileManagerTest.java b/java/src/test/java/org/rocksdb/SstFileManagerTest.java deleted file mode 100644 index 2e136e820..000000000 --- a/java/src/test/java/org/rocksdb/SstFileManagerTest.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.*; - -public class SstFileManagerTest { - - @Test - public void maxAllowedSpaceUsage() throws RocksDBException { - try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { - sstFileManager.setMaxAllowedSpaceUsage(1024 * 1024 * 64); - assertThat(sstFileManager.isMaxAllowedSpaceReached()).isFalse(); - assertThat(sstFileManager.isMaxAllowedSpaceReachedIncludingCompactions()).isFalse(); - } - } - - @Test - public void compactionBufferSize() throws RocksDBException { - try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { - sstFileManager.setCompactionBufferSize(1024 * 1024 * 10); - assertThat(sstFileManager.isMaxAllowedSpaceReachedIncludingCompactions()).isFalse(); - } - } - - @Test - public void totalSize() throws RocksDBException { - try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { - assertThat(sstFileManager.getTotalSize()).isEqualTo(0); - } - } - - @Test - public void trackedFiles() throws RocksDBException { - try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { - assertThat(sstFileManager.getTrackedFiles()).isEqualTo(Collections.emptyMap()); - } - } - - @Test - public void deleteRateBytesPerSecond() throws RocksDBException { - try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { - assertThat(sstFileManager.getDeleteRateBytesPerSecond()).isEqualTo(SstFileManager.RATE_BYTES_PER_SEC_DEFAULT); - final long ratePerSecond = 1024 * 1024 * 52; - sstFileManager.setDeleteRateBytesPerSecond(ratePerSecond); - assertThat(sstFileManager.getDeleteRateBytesPerSecond()).isEqualTo(ratePerSecond); - } - } - - @Test - public void maxTrashDBRatio() throws RocksDBException { - try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { - assertThat(sstFileManager.getMaxTrashDBRatio()).isEqualTo(SstFileManager.MAX_TRASH_DB_RATION_DEFAULT); - final double trashRatio = 0.2; - sstFileManager.setMaxTrashDBRatio(trashRatio); - assertThat(sstFileManager.getMaxTrashDBRatio()).isEqualTo(trashRatio); - } - } -} diff --git a/java/src/test/java/org/rocksdb/SstFileReaderTest.java b/java/src/test/java/org/rocksdb/SstFileReaderTest.java deleted file mode 100644 index e29df99f2..000000000 --- a/java/src/test/java/org/rocksdb/SstFileReaderTest.java +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.rocksdb.util.ByteBufferAllocator; - -@RunWith(Parameterized.class) -public class SstFileReaderTest { - private static final String SST_FILE_NAME = "test.sst"; - - static class KeyValueWithOp { - KeyValueWithOp(final String key, final String value, final OpType opType) { - this.key = key; - this.value = value; - this.opType = opType; - } - - String getKey() { - return key; - } - - String getValue() { - return value; - } - - OpType getOpType() { - return opType; - } - - private final String key; - private final String value; - private final OpType opType; - } - - @Rule public TemporaryFolder parentFolder = new TemporaryFolder(); - - @Parameterized.Parameters(name = "{0}") - public static Iterable parameters() { - return Arrays.asList(new Object[][] { - {"direct", ByteBufferAllocator.DIRECT}, {"indirect", ByteBufferAllocator.HEAP}}); - } - - @Parameterized.Parameter(0) public String name; - - @Parameterized.Parameter(1) public ByteBufferAllocator byteBufferAllocator; - - enum OpType { PUT, PUT_BYTES, MERGE, MERGE_BYTES, DELETE, DELETE_BYTES } - - private File newSstFile(final List keyValues) - throws IOException, RocksDBException { - final EnvOptions envOptions = new EnvOptions(); - final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options options = new Options().setMergeOperator(stringAppendOperator); - final SstFileWriter sstFileWriter; - sstFileWriter = new SstFileWriter(envOptions, options); - - final File sstFile = parentFolder.newFile(SST_FILE_NAME); - try { - sstFileWriter.open(sstFile.getAbsolutePath()); - for (final KeyValueWithOp keyValue : keyValues) { - final Slice keySlice = new Slice(keyValue.getKey()); - final Slice valueSlice = new Slice(keyValue.getValue()); - final byte[] keyBytes = keyValue.getKey().getBytes(); - final byte[] valueBytes = keyValue.getValue().getBytes(); - switch (keyValue.getOpType()) { - case PUT: - sstFileWriter.put(keySlice, valueSlice); - break; - case PUT_BYTES: - sstFileWriter.put(keyBytes, valueBytes); - break; - case MERGE: - sstFileWriter.merge(keySlice, valueSlice); - break; - case MERGE_BYTES: - sstFileWriter.merge(keyBytes, valueBytes); - break; - case DELETE: - sstFileWriter.delete(keySlice); - break; - case DELETE_BYTES: - sstFileWriter.delete(keyBytes); - break; - default: - fail("Unsupported op type"); - } - keySlice.close(); - valueSlice.close(); - } - sstFileWriter.finish(); - } finally { - assertThat(sstFileWriter).isNotNull(); - sstFileWriter.close(); - options.close(); - envOptions.close(); - } - return sstFile; - } - - @Test - public void readSstFile() throws RocksDBException, IOException { - final List keyValues = new ArrayList<>(); - keyValues.add(new KeyValueWithOp("key1", "value1", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key2", "value2", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key3", "value3", OpType.PUT)); - - final File sstFile = newSstFile(keyValues); - try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options options = - new Options().setCreateIfMissing(true).setMergeOperator(stringAppendOperator); - final SstFileReader reader = new SstFileReader(options)) { - // Open the sst file and iterator - reader.open(sstFile.getAbsolutePath()); - final ReadOptions readOptions = new ReadOptions(); - final SstFileReaderIterator iterator = reader.newIterator(readOptions); - - // Use the iterator to read sst file - iterator.seekToFirst(); - - // Verify Checksum - reader.verifyChecksum(); - - // Verify Table Properties - assertEquals(reader.getTableProperties().getNumEntries(), 3); - - // Check key and value - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - - final ByteBuffer byteBuffer = byteBufferAllocator.allocate(128); - byteBuffer.put("key1".getBytes()).flip(); - iterator.seek(byteBuffer); - assertThat(byteBuffer.position()).isEqualTo(4); - assertThat(byteBuffer.limit()).isEqualTo(4); - - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - - { - byteBuffer.clear(); - assertThat(iterator.key(byteBuffer)).isEqualTo("key1".getBytes().length); - final byte[] dst = new byte["key1".getBytes().length]; - byteBuffer.get(dst); - assertThat(new String(dst)).isEqualTo("key1"); - } - - { - byteBuffer.clear(); - byteBuffer.put("PREFIX".getBytes()); - final ByteBuffer slice = byteBuffer.slice(); - assertThat(iterator.key(byteBuffer)).isEqualTo("key1".getBytes().length); - final byte[] dst = new byte["key1".getBytes().length]; - slice.get(dst); - assertThat(new String(dst)).isEqualTo("key1"); - } - - { - byteBuffer.clear(); - assertThat(iterator.value(byteBuffer)).isEqualTo("value1".getBytes().length); - final byte[] dst = new byte["value1".getBytes().length]; - byteBuffer.get(dst); - assertThat(new String(dst)).isEqualTo("value1"); - } - - byteBuffer.clear(); - byteBuffer.put("key1point5".getBytes()).flip(); - iterator.seek(byteBuffer); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - - byteBuffer.clear(); - byteBuffer.put("key1point5".getBytes()).flip(); - iterator.seekForPrev(byteBuffer); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - - byteBuffer.clear(); - byteBuffer.put("key2point5".getBytes()).flip(); - iterator.seek(byteBuffer); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key3".getBytes()); - assertThat(iterator.value()).isEqualTo("value3".getBytes()); - - byteBuffer.clear(); - byteBuffer.put("key2point5".getBytes()).flip(); - iterator.seekForPrev(byteBuffer); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key2".getBytes()); - assertThat(iterator.value()).isEqualTo("value2".getBytes()); - - byteBuffer.clear(); - byteBuffer.put("PREFIX".getBytes()); - final ByteBuffer slice = byteBuffer.slice(); - slice.put("key1point5".getBytes()).flip(); - iterator.seekForPrev(slice); - assertThat(iterator.isValid()).isTrue(); - assertThat(iterator.key()).isEqualTo("key1".getBytes()); - assertThat(iterator.value()).isEqualTo("value1".getBytes()); - } - } -} diff --git a/java/src/test/java/org/rocksdb/SstFileWriterTest.java b/java/src/test/java/org/rocksdb/SstFileWriterTest.java deleted file mode 100644 index 87165bfe1..000000000 --- a/java/src/test/java/org/rocksdb/SstFileWriterTest.java +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.BytewiseComparator; - -public class SstFileWriterTest { - private static final String SST_FILE_NAME = "test.sst"; - private static final String DB_DIRECTORY_NAME = "test_db"; - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE - = new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder parentFolder = new TemporaryFolder(); - - enum OpType { PUT, PUT_BYTES, PUT_DIRECT, MERGE, MERGE_BYTES, DELETE, DELETE_BYTES } - - static class KeyValueWithOp { - KeyValueWithOp(String key, String value, OpType opType) { - this.key = key; - this.value = value; - this.opType = opType; - } - - String getKey() { - return key; - } - - String getValue() { - return value; - } - - OpType getOpType() { - return opType; - } - - private final String key; - private final String value; - private final OpType opType; - }; - - private File newSstFile(final List keyValues, - boolean useJavaBytewiseComparator) throws IOException, RocksDBException { - final EnvOptions envOptions = new EnvOptions(); - final StringAppendOperator stringAppendOperator = new StringAppendOperator(); - final Options options = new Options().setMergeOperator(stringAppendOperator); - SstFileWriter sstFileWriter = null; - ComparatorOptions comparatorOptions = null; - BytewiseComparator comparator = null; - if (useJavaBytewiseComparator) { - comparatorOptions = new ComparatorOptions().setUseDirectBuffer(false); - comparator = new BytewiseComparator(comparatorOptions); - options.setComparator(comparator); - sstFileWriter = new SstFileWriter(envOptions, options); - } else { - sstFileWriter = new SstFileWriter(envOptions, options); - } - - final File sstFile = parentFolder.newFile(SST_FILE_NAME); - try { - sstFileWriter.open(sstFile.getAbsolutePath()); - assertThat(sstFileWriter.fileSize()).isEqualTo(0); - for (KeyValueWithOp keyValue : keyValues) { - Slice keySlice = new Slice(keyValue.getKey()); - Slice valueSlice = new Slice(keyValue.getValue()); - byte[] keyBytes = keyValue.getKey().getBytes(); - byte[] valueBytes = keyValue.getValue().getBytes(); - ByteBuffer keyDirect = ByteBuffer.allocateDirect(keyBytes.length); - keyDirect.put(keyBytes); - keyDirect.flip(); - ByteBuffer valueDirect = ByteBuffer.allocateDirect(valueBytes.length); - valueDirect.put(valueBytes); - valueDirect.flip(); - switch (keyValue.getOpType()) { - case PUT: - sstFileWriter.put(keySlice, valueSlice); - break; - case PUT_BYTES: - sstFileWriter.put(keyBytes, valueBytes); - break; - case PUT_DIRECT: - sstFileWriter.put(keyDirect, valueDirect); - assertThat(keyDirect.position()).isEqualTo(keyBytes.length); - assertThat(keyDirect.limit()).isEqualTo(keyBytes.length); - assertThat(valueDirect.position()).isEqualTo(valueBytes.length); - assertThat(valueDirect.limit()).isEqualTo(valueBytes.length); - break; - case MERGE: - sstFileWriter.merge(keySlice, valueSlice); - break; - case MERGE_BYTES: - sstFileWriter.merge(keyBytes, valueBytes); - break; - case DELETE: - sstFileWriter.delete(keySlice); - break; - case DELETE_BYTES: - sstFileWriter.delete(keyBytes); - break; - default: - fail("Unsupported op type"); - } - keySlice.close(); - valueSlice.close(); - } - sstFileWriter.finish(); - assertThat(sstFileWriter.fileSize()).isGreaterThan(100); - } finally { - assertThat(sstFileWriter).isNotNull(); - sstFileWriter.close(); - options.close(); - envOptions.close(); - if (comparatorOptions != null) { - comparatorOptions.close(); - } - if (comparator != null) { - comparator.close(); - } - } - return sstFile; - } - - @Test - public void generateSstFileWithJavaComparator() - throws RocksDBException, IOException { - final List keyValues = new ArrayList<>(); - keyValues.add(new KeyValueWithOp("key1", "value1", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key2", "value2", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key3", "value3", OpType.MERGE)); - keyValues.add(new KeyValueWithOp("key4", "value4", OpType.MERGE)); - keyValues.add(new KeyValueWithOp("key5", "", OpType.DELETE)); - - newSstFile(keyValues, true); - } - - @Test - public void generateSstFileWithNativeComparator() - throws RocksDBException, IOException { - final List keyValues = new ArrayList<>(); - keyValues.add(new KeyValueWithOp("key1", "value1", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key2", "value2", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key3", "value3", OpType.MERGE)); - keyValues.add(new KeyValueWithOp("key4", "value4", OpType.MERGE)); - keyValues.add(new KeyValueWithOp("key5", "", OpType.DELETE)); - - newSstFile(keyValues, false); - } - - @Test - public void ingestSstFile() throws RocksDBException, IOException { - final List keyValues = new ArrayList<>(); - keyValues.add(new KeyValueWithOp("key1", "value1", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key2", "value2", OpType.PUT_DIRECT)); - keyValues.add(new KeyValueWithOp("key3", "value3", OpType.PUT_BYTES)); - keyValues.add(new KeyValueWithOp("key4", "value4", OpType.MERGE)); - keyValues.add(new KeyValueWithOp("key5", "value5", OpType.MERGE_BYTES)); - keyValues.add(new KeyValueWithOp("key6", "", OpType.DELETE)); - keyValues.add(new KeyValueWithOp("key7", "", OpType.DELETE)); - - - final File sstFile = newSstFile(keyValues, false); - final File dbFolder = parentFolder.newFolder(DB_DIRECTORY_NAME); - try(final StringAppendOperator stringAppendOperator = - new StringAppendOperator(); - final Options options = new Options() - .setCreateIfMissing(true) - .setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(options, dbFolder.getAbsolutePath()); - final IngestExternalFileOptions ingestExternalFileOptions = - new IngestExternalFileOptions()) { - db.ingestExternalFile(Arrays.asList(sstFile.getAbsolutePath()), - ingestExternalFileOptions); - - assertThat(db.get("key1".getBytes())).isEqualTo("value1".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo("value2".getBytes()); - assertThat(db.get("key3".getBytes())).isEqualTo("value3".getBytes()); - assertThat(db.get("key4".getBytes())).isEqualTo("value4".getBytes()); - assertThat(db.get("key5".getBytes())).isEqualTo("value5".getBytes()); - assertThat(db.get("key6".getBytes())).isEqualTo(null); - assertThat(db.get("key7".getBytes())).isEqualTo(null); - } - } - - @Test - public void ingestSstFile_cf() throws RocksDBException, IOException { - final List keyValues = new ArrayList<>(); - keyValues.add(new KeyValueWithOp("key1", "value1", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key2", "value2", OpType.PUT)); - keyValues.add(new KeyValueWithOp("key3", "value3", OpType.MERGE)); - keyValues.add(new KeyValueWithOp("key4", "", OpType.DELETE)); - - final File sstFile = newSstFile(keyValues, false); - final File dbFolder = parentFolder.newFolder(DB_DIRECTORY_NAME); - try(final StringAppendOperator stringAppendOperator = - new StringAppendOperator(); - final Options options = new Options() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true) - .setMergeOperator(stringAppendOperator); - final RocksDB db = RocksDB.open(options, dbFolder.getAbsolutePath()); - final IngestExternalFileOptions ingestExternalFileOptions = - new IngestExternalFileOptions()) { - - try(final ColumnFamilyOptions cf_opts = new ColumnFamilyOptions() - .setMergeOperator(stringAppendOperator); - final ColumnFamilyHandle cf_handle = db.createColumnFamily( - new ColumnFamilyDescriptor("new_cf".getBytes(), cf_opts))) { - - db.ingestExternalFile(cf_handle, - Arrays.asList(sstFile.getAbsolutePath()), - ingestExternalFileOptions); - - assertThat(db.get(cf_handle, - "key1".getBytes())).isEqualTo("value1".getBytes()); - assertThat(db.get(cf_handle, - "key2".getBytes())).isEqualTo("value2".getBytes()); - assertThat(db.get(cf_handle, - "key3".getBytes())).isEqualTo("value3".getBytes()); - assertThat(db.get(cf_handle, - "key4".getBytes())).isEqualTo(null); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/SstPartitionerTest.java b/java/src/test/java/org/rocksdb/SstPartitionerTest.java deleted file mode 100644 index 74816db93..000000000 --- a/java/src/test/java/org/rocksdb/SstPartitionerTest.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class SstPartitionerTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void sstFixedPrefix() throws RocksDBException { - try (SstPartitionerFixedPrefixFactory factory = new SstPartitionerFixedPrefixFactory(4); - final Options opt = - new Options().setCreateIfMissing(true).setSstPartitionerFactory(factory); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - // writing (long)100 under key - db.put("aaaa1".getBytes(), "A".getBytes()); - db.put("bbbb1".getBytes(), "B".getBytes()); - db.flush(new FlushOptions()); - - db.put("aaaa0".getBytes(), "A2".getBytes()); - db.put("aaaa2".getBytes(), "A2".getBytes()); - db.flush(new FlushOptions()); - - db.compactRange(); - - List metadata = db.getLiveFilesMetaData(); - assertThat(metadata.size()).isEqualTo(2); - } - } - - @Test - public void sstFixedPrefixFamily() throws RocksDBException { - final byte[] cfName = "new_cf".getBytes(UTF_8); - final ColumnFamilyDescriptor cfDescriptor = new ColumnFamilyDescriptor(cfName, - new ColumnFamilyOptions().setSstPartitionerFactory( - new SstPartitionerFixedPrefixFactory(4))); - - try (final Options opt = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { - final ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily(cfDescriptor); - - // writing (long)100 under key - db.put(columnFamilyHandle, "aaaa1".getBytes(), "A".getBytes()); - db.put(columnFamilyHandle, "bbbb1".getBytes(), "B".getBytes()); - db.flush(new FlushOptions(), columnFamilyHandle); - - db.put(columnFamilyHandle, "aaaa0".getBytes(), "A2".getBytes()); - db.put(columnFamilyHandle, "aaaa2".getBytes(), "A2".getBytes()); - db.flush(new FlushOptions(), columnFamilyHandle); - - db.compactRange(columnFamilyHandle); - - List metadata = db.getLiveFilesMetaData(); - assertThat(metadata.size()).isEqualTo(2); - } - } -} diff --git a/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java b/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java deleted file mode 100644 index 36721c80d..000000000 --- a/java/src/test/java/org/rocksdb/StatisticsCollectorTest.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Collections; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StatisticsCollectorTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void statisticsCollector() - throws InterruptedException, RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - try(final Statistics stats = opt.statistics()) { - - final StatsCallbackMock callback = new StatsCallbackMock(); - final StatsCollectorInput statsInput = - new StatsCollectorInput(stats, callback); - - final StatisticsCollector statsCollector = new StatisticsCollector( - Collections.singletonList(statsInput), 100); - statsCollector.start(); - - Thread.sleep(1000); - - assertThat(callback.tickerCallbackCount).isGreaterThan(0); - assertThat(callback.histCallbackCount).isGreaterThan(0); - - statsCollector.shutDown(1000); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/StatisticsTest.java b/java/src/test/java/org/rocksdb/StatisticsTest.java deleted file mode 100644 index de92102ec..000000000 --- a/java/src/test/java/org/rocksdb/StatisticsTest.java +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.nio.charset.StandardCharsets; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StatisticsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void statsLevel() throws RocksDBException { - final Statistics statistics = new Statistics(); - statistics.setStatsLevel(StatsLevel.ALL); - assertThat(statistics.statsLevel()).isEqualTo(StatsLevel.ALL); - } - - @Test - public void getTickerCount() throws RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - db.put(key, value); - for(int i = 0; i < 10; i++) { - db.get(key); - } - - assertThat(statistics.getTickerCount(TickerType.BYTES_READ)).isGreaterThan(0); - } - } - - @Test - public void getAndResetTickerCount() throws RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - db.put(key, value); - for(int i = 0; i < 10; i++) { - db.get(key); - } - - final long read = statistics.getAndResetTickerCount(TickerType.BYTES_READ); - assertThat(read).isGreaterThan(0); - - final long readAfterReset = statistics.getTickerCount(TickerType.BYTES_READ); - assertThat(readAfterReset).isLessThan(read); - } - } - - @Test - public void getHistogramData() throws RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - db.put(key, value); - for(int i = 0; i < 10; i++) { - db.get(key); - } - - final HistogramData histogramData = statistics.getHistogramData(HistogramType.BYTES_PER_READ); - assertThat(histogramData).isNotNull(); - assertThat(histogramData.getAverage()).isGreaterThan(0); - assertThat(histogramData.getMedian()).isGreaterThan(0); - assertThat(histogramData.getPercentile95()).isGreaterThan(0); - assertThat(histogramData.getPercentile99()).isGreaterThan(0); - assertThat(histogramData.getStandardDeviation()).isEqualTo(0.00); - assertThat(histogramData.getMax()).isGreaterThan(0); - assertThat(histogramData.getCount()).isGreaterThan(0); - assertThat(histogramData.getSum()).isGreaterThan(0); - assertThat(histogramData.getMin()).isGreaterThan(0); - } - } - - @Test - public void getHistogramString() throws RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - for(int i = 0; i < 10; i++) { - db.put(key, value); - } - - assertThat(statistics.getHistogramString(HistogramType.BYTES_PER_WRITE)).isNotNull(); - } - } - - @Test - public void reset() throws RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); - final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); - - db.put(key, value); - for(int i = 0; i < 10; i++) { - db.get(key); - } - - final long read = statistics.getTickerCount(TickerType.BYTES_READ); - assertThat(read).isGreaterThan(0); - - statistics.reset(); - - final long readAfterReset = statistics.getTickerCount(TickerType.BYTES_READ); - assertThat(readAfterReset).isLessThan(read); - } - } - - @Test - public void ToString() throws RocksDBException { - try (final Statistics statistics = new Statistics(); - final Options opt = new Options() - .setStatistics(statistics) - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(statistics.toString()).isNotNull(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/StatsCallbackMock.java b/java/src/test/java/org/rocksdb/StatsCallbackMock.java deleted file mode 100644 index af8db0caa..000000000 --- a/java/src/test/java/org/rocksdb/StatsCallbackMock.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -public class StatsCallbackMock implements StatisticsCollectorCallback { - public int tickerCallbackCount = 0; - public int histCallbackCount = 0; - - public void tickerCallback(TickerType tickerType, long tickerCount) { - tickerCallbackCount++; - } - - public void histogramCallback(HistogramType histType, - HistogramData histData) { - histCallbackCount++; - } -} diff --git a/java/src/test/java/org/rocksdb/TableFilterTest.java b/java/src/test/java/org/rocksdb/TableFilterTest.java deleted file mode 100644 index 2bd3b1798..000000000 --- a/java/src/test/java/org/rocksdb/TableFilterTest.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -public class TableFilterTest { - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void readOptions() throws RocksDBException { - try (final DBOptions opt = new DBOptions(). - setCreateIfMissing(true). - setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions() - ) { - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts) - ); - - final List columnFamilyHandles = new ArrayList<>(); - - // open database - try (final RocksDB db = RocksDB.open(opt, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, - columnFamilyHandles)) { - - try (final CfNameCollectionTableFilter cfNameCollectingTableFilter = - new CfNameCollectionTableFilter(); - final FlushOptions flushOptions = - new FlushOptions().setWaitForFlush(true); - final ReadOptions readOptions = - new ReadOptions().setTableFilter(cfNameCollectingTableFilter)) { - - db.put(columnFamilyHandles.get(0), - "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - db.put(columnFamilyHandles.get(0), - "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); - db.put(columnFamilyHandles.get(0), - "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); - db.put(columnFamilyHandles.get(1), - "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - db.put(columnFamilyHandles.get(1), - "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); - db.put(columnFamilyHandles.get(1), - "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); - - db.flush(flushOptions, columnFamilyHandles); - - try (final RocksIterator iterator = - db.newIterator(columnFamilyHandles.get(0), readOptions)) { - iterator.seekToFirst(); - while (iterator.isValid()) { - iterator.key(); - iterator.value(); - iterator.next(); - } - } - - try (final RocksIterator iterator = - db.newIterator(columnFamilyHandles.get(1), readOptions)) { - iterator.seekToFirst(); - while (iterator.isValid()) { - iterator.key(); - iterator.value(); - iterator.next(); - } - } - - assertThat(cfNameCollectingTableFilter.cfNames.size()).isEqualTo(2); - assertThat(cfNameCollectingTableFilter.cfNames.get(0)) - .isEqualTo(RocksDB.DEFAULT_COLUMN_FAMILY); - assertThat(cfNameCollectingTableFilter.cfNames.get(1)) - .isEqualTo("new_cf".getBytes(UTF_8)); - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - } - } - } - } - - private static class CfNameCollectionTableFilter extends AbstractTableFilter { - private final List cfNames = new ArrayList<>(); - - @Override - public boolean filter(final TableProperties tableProperties) { - cfNames.add(tableProperties.getColumnFamilyName()); - return true; - } - } -} diff --git a/java/src/test/java/org/rocksdb/TimedEnvTest.java b/java/src/test/java/org/rocksdb/TimedEnvTest.java deleted file mode 100644 index c958f96b2..000000000 --- a/java/src/test/java/org/rocksdb/TimedEnvTest.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static java.nio.charset.StandardCharsets.UTF_8; - -public class TimedEnvTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void construct() throws RocksDBException { - try (final Env env = new TimedEnv(Env.getDefault())) { - // no-op - } - } - - @Test - public void construct_integration() throws RocksDBException { - try (final Env env = new TimedEnv(Env.getDefault()); - final Options options = new Options() - .setCreateIfMissing(true) - .setEnv(env); - ) { - try (final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getPath())) { - db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java b/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java deleted file mode 100644 index 7eaa6b16c..000000000 --- a/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TransactionDBOptionsTest { - - private static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void maxNumLocks() { - try (final TransactionDBOptions opt = new TransactionDBOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxNumLocks(longValue); - assertThat(opt.getMaxNumLocks()).isEqualTo(longValue); - } - } - - @Test - public void maxNumStripes() { - try (final TransactionDBOptions opt = new TransactionDBOptions()) { - final long longValue = rand.nextLong(); - opt.setNumStripes(longValue); - assertThat(opt.getNumStripes()).isEqualTo(longValue); - } - } - - @Test - public void transactionLockTimeout() { - try (final TransactionDBOptions opt = new TransactionDBOptions()) { - final long longValue = rand.nextLong(); - opt.setTransactionLockTimeout(longValue); - assertThat(opt.getTransactionLockTimeout()).isEqualTo(longValue); - } - } - - @Test - public void defaultLockTimeout() { - try (final TransactionDBOptions opt = new TransactionDBOptions()) { - final long longValue = rand.nextLong(); - opt.setDefaultLockTimeout(longValue); - assertThat(opt.getDefaultLockTimeout()).isEqualTo(longValue); - } - } - - @Test - public void writePolicy() { - try (final TransactionDBOptions opt = new TransactionDBOptions()) { - final TxnDBWritePolicy writePolicy = TxnDBWritePolicy.WRITE_UNPREPARED; // non-default - opt.setWritePolicy(writePolicy); - assertThat(opt.getWritePolicy()).isEqualTo(writePolicy); - } - } - -} diff --git a/java/src/test/java/org/rocksdb/TransactionDBTest.java b/java/src/test/java/org/rocksdb/TransactionDBTest.java deleted file mode 100644 index b0ea813ff..000000000 --- a/java/src/test/java/org/rocksdb/TransactionDBTest.java +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; - -public class TransactionDBTest { - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void open() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(tdb).isNotNull(); - } - } - - @Test - public void open_columnFamilies() throws RocksDBException { - try(final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final ColumnFamilyOptions myCfOpts = new ColumnFamilyOptions()) { - - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("myCf".getBytes(), myCfOpts)); - - final List columnFamilyHandles = new ArrayList<>(); - - try (final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(dbOptions, txnDbOptions, - dbFolder.getRoot().getAbsolutePath(), - columnFamilyDescriptors, columnFamilyHandles)) { - try { - assertThat(tdb).isNotNull(); - } finally { - for (final ColumnFamilyHandle handle : columnFamilyHandles) { - handle.close(); - } - } - } - } - } - - @Test - public void beginTransaction() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions()) { - - try(final Transaction txn = tdb.beginTransaction(writeOptions)) { - assertThat(txn).isNotNull(); - } - } - } - - @Test - public void beginTransaction_transactionOptions() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions(); - final TransactionOptions txnOptions = new TransactionOptions()) { - - try(final Transaction txn = tdb.beginTransaction(writeOptions, - txnOptions)) { - assertThat(txn).isNotNull(); - } - } - } - - @Test - public void beginTransaction_withOld() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions()) { - - try(final Transaction txn = tdb.beginTransaction(writeOptions)) { - final Transaction txnReused = tdb.beginTransaction(writeOptions, txn); - assertThat(txnReused).isSameAs(txn); - } - } - } - - @Test - public void beginTransaction_withOld_transactionOptions() - throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions(); - final TransactionOptions txnOptions = new TransactionOptions()) { - - try(final Transaction txn = tdb.beginTransaction(writeOptions)) { - final Transaction txnReused = tdb.beginTransaction(writeOptions, - txnOptions, txn); - assertThat(txnReused).isSameAs(txn); - } - } - } - - @Test - public void lockStatusData() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath()); - final WriteOptions writeOptions = new WriteOptions(); - final ReadOptions readOptions = new ReadOptions()) { - - try (final Transaction txn = tdb.beginTransaction(writeOptions)) { - - final byte key[] = "key".getBytes(UTF_8); - final byte value[] = "value".getBytes(UTF_8); - - txn.put(key, value); - assertThat(txn.getForUpdate(readOptions, key, true)).isEqualTo(value); - - final Map lockStatus = - tdb.getLockStatusData(); - - assertThat(lockStatus.size()).isEqualTo(1); - final Set> entrySet = lockStatus.entrySet(); - final Map.Entry entry = entrySet.iterator().next(); - final long columnFamilyId = entry.getKey(); - assertThat(columnFamilyId).isEqualTo(0); - final TransactionDB.KeyLockInfo keyLockInfo = entry.getValue(); - assertThat(keyLockInfo.getKey()).isEqualTo(new String(key, UTF_8)); - assertThat(keyLockInfo.getTransactionIDs().length).isEqualTo(1); - assertThat(keyLockInfo.getTransactionIDs()[0]).isEqualTo(txn.getId()); - assertThat(keyLockInfo.isExclusive()).isTrue(); - } - } - } - - @Test - public void deadlockInfoBuffer() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath())) { - - // TODO(AR) can we cause a deadlock so that we can test the output here? - assertThat(tdb.getDeadlockInfoBuffer()).isEmpty(); - } - } - - @Test - public void setDeadlockInfoBufferSize() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath())) { - tdb.setDeadlockInfoBufferSize(123); - } - } -} diff --git a/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java b/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java deleted file mode 100644 index 3c4dff7bb..000000000 --- a/java/src/test/java/org/rocksdb/TransactionLogIteratorTest.java +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TransactionLogIteratorTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void transactionLogIterator() throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath()); - final TransactionLogIterator transactionLogIterator = - db.getUpdatesSince(0)) { - //no-op - } - } - - @Test - public void getBatch() throws RocksDBException { - final int numberOfPuts = 5; - try (final Options options = new Options() - .setCreateIfMissing(true) - .setWalTtlSeconds(1000) - .setWalSizeLimitMB(10); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - for (int i = 0; i < numberOfPuts; i++) { - db.put(String.valueOf(i).getBytes(), - String.valueOf(i).getBytes()); - } - db.flush(new FlushOptions().setWaitForFlush(true)); - - // the latest sequence number is 5 because 5 puts - // were written beforehand - assertThat(db.getLatestSequenceNumber()). - isEqualTo(numberOfPuts); - - // insert 5 writes into a cf - try (final ColumnFamilyHandle cfHandle = db.createColumnFamily( - new ColumnFamilyDescriptor("new_cf".getBytes()))) { - for (int i = 0; i < numberOfPuts; i++) { - db.put(cfHandle, String.valueOf(i).getBytes(), - String.valueOf(i).getBytes()); - } - // the latest sequence number is 10 because - // (5 + 5) puts were written beforehand - assertThat(db.getLatestSequenceNumber()). - isEqualTo(numberOfPuts + numberOfPuts); - - // Get updates since the beginning - try (final TransactionLogIterator transactionLogIterator = - db.getUpdatesSince(0)) { - assertThat(transactionLogIterator.isValid()).isTrue(); - transactionLogIterator.status(); - - // The first sequence number is 1 - final TransactionLogIterator.BatchResult batchResult = - transactionLogIterator.getBatch(); - assertThat(batchResult.sequenceNumber()).isEqualTo(1); - } - } - } - } - - @Test - public void transactionLogIteratorStallAtLastRecord() - throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setWalTtlSeconds(1000) - .setWalSizeLimitMB(10); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - db.put("key1".getBytes(), "value1".getBytes()); - // Get updates since the beginning - try (final TransactionLogIterator transactionLogIterator = - db.getUpdatesSince(0)) { - transactionLogIterator.status(); - assertThat(transactionLogIterator.isValid()).isTrue(); - transactionLogIterator.next(); - assertThat(transactionLogIterator.isValid()).isFalse(); - transactionLogIterator.status(); - db.put("key2".getBytes(), "value2".getBytes()); - transactionLogIterator.next(); - transactionLogIterator.status(); - assertThat(transactionLogIterator.isValid()).isTrue(); - } - } - } - - @Test - public void transactionLogIteratorCheckAfterRestart() - throws RocksDBException { - final int numberOfKeys = 2; - try (final Options options = new Options() - .setCreateIfMissing(true) - .setWalTtlSeconds(1000) - .setWalSizeLimitMB(10)) { - - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - db.put("key1".getBytes(), "value1".getBytes()); - db.put("key2".getBytes(), "value2".getBytes()); - db.flush(new FlushOptions().setWaitForFlush(true)); - - } - - // reopen - try (final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - assertThat(db.getLatestSequenceNumber()).isEqualTo(numberOfKeys); - - try (final TransactionLogIterator transactionLogIterator = - db.getUpdatesSince(0)) { - for (int i = 0; i < numberOfKeys; i++) { - transactionLogIterator.status(); - assertThat(transactionLogIterator.isValid()).isTrue(); - transactionLogIterator.next(); - } - } - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/TransactionOptionsTest.java b/java/src/test/java/org/rocksdb/TransactionOptionsTest.java deleted file mode 100644 index add0439e0..000000000 --- a/java/src/test/java/org/rocksdb/TransactionOptionsTest.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TransactionOptionsTest { - - private static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void snapshot() { - try (final TransactionOptions opt = new TransactionOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setSetSnapshot(boolValue); - assertThat(opt.isSetSnapshot()).isEqualTo(boolValue); - } - } - - @Test - public void deadlockDetect() { - try (final TransactionOptions opt = new TransactionOptions()) { - final boolean boolValue = rand.nextBoolean(); - opt.setDeadlockDetect(boolValue); - assertThat(opt.isDeadlockDetect()).isEqualTo(boolValue); - } - } - - @Test - public void lockTimeout() { - try (final TransactionOptions opt = new TransactionOptions()) { - final long longValue = rand.nextLong(); - opt.setLockTimeout(longValue); - assertThat(opt.getLockTimeout()).isEqualTo(longValue); - } - } - - @Test - public void expiration() { - try (final TransactionOptions opt = new TransactionOptions()) { - final long longValue = rand.nextLong(); - opt.setExpiration(longValue); - assertThat(opt.getExpiration()).isEqualTo(longValue); - } - } - - @Test - public void deadlockDetectDepth() { - try (final TransactionOptions opt = new TransactionOptions()) { - final long longValue = rand.nextLong(); - opt.setDeadlockDetectDepth(longValue); - assertThat(opt.getDeadlockDetectDepth()).isEqualTo(longValue); - } - } - - @Test - public void maxWriteBatchSize() { - try (final TransactionOptions opt = new TransactionOptions()) { - final long longValue = rand.nextLong(); - opt.setMaxWriteBatchSize(longValue); - assertThat(opt.getMaxWriteBatchSize()).isEqualTo(longValue); - } - } -} diff --git a/java/src/test/java/org/rocksdb/TransactionTest.java b/java/src/test/java/org/rocksdb/TransactionTest.java deleted file mode 100644 index 8a3067de9..000000000 --- a/java/src/test/java/org/rocksdb/TransactionTest.java +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -public class TransactionTest extends AbstractTransactionTest { - - @Test - public void getForUpdate_cf_conflict() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, k1, v1); - assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); - - // NOTE: txn2 updates k1, during txn3 - try { - txn2.put(testCf, k1, v12); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void prepare_commit() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.commit(); - } - - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.setName("txnPrepare1"); - txn.put(k1, v12); - txn.prepare(); - txn.commit(); - } - - try (final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isEqualTo(v12); - } - } - } - - @Test - public void prepare_rollback() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.commit(); - } - - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.setName("txnPrepare1"); - txn.put(k1, v12); - txn.prepare(); - txn.rollback(); - } - - try (final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - } - } - } - - @Test - public void prepare_read_prepared_commit() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.commit(); - } - - Transaction txnPrepare; - txnPrepare = dbContainer.beginTransaction(); - txnPrepare.setName("txnPrepare1"); - txnPrepare.put(k1, v12); - txnPrepare.prepare(); - - try (final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - } - - txnPrepare.commit(); - - try (final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isEqualTo(v12); - } - } - } - - @Test - public void prepare_read_prepared_rollback() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - txn.commit(); - } - - Transaction txnPrepare; - txnPrepare = dbContainer.beginTransaction(); - txnPrepare.setName("txnPrepare1"); - txnPrepare.put(k1, v12); - txnPrepare.prepare(); - - try (final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - } - - txnPrepare.rollback(); - - try (final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.get(readOptions, k1)).isEqualTo(v1); - } - } - } - - @Test - public void getForUpdate_conflict() throws RocksDBException { - final byte[] k1 = "key1".getBytes(UTF_8); - final byte[] v1 = "value1".getBytes(UTF_8); - final byte[] v12 = "value12".getBytes(UTF_8); - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(k1, v1); - assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1); - - // NOTE: txn2 updates k1, during txn3 - try { - txn2.put(k1, v12); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void multiGetForUpdate_cf_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdate(readOptions, cfList, keys)) - .isEqualTo(values); - - // NOTE: txn2 updates k1, during txn3 - try { - txn2.put(testCf, keys[0], otherValue); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void multiGetAsListForUpdate_cf_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); - final List cfList = Arrays.asList(testCf, testCf); - - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(testCf, keys[0], values[0]); - txn.put(testCf, keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(values); - txn.commit(); - } - - try (final Transaction txn2 = dbContainer.beginTransaction()) { - try (final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdateAsList(readOptions, cfList, Arrays.asList(keys))) - .containsExactly(values); - - // NOTE: txn2 updates k1, during txn3 - try { - txn2.put(testCf, keys[0], otherValue); // should cause an exception! - } catch (final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" - + "transactions"); - } - } - - @Test - public void multiGetForUpdate_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try(final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try(final Transaction txn = dbContainer.beginTransaction()) { - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); - txn.commit(); - } - - try(final Transaction txn2 = dbContainer.beginTransaction()) { - try(final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdate(readOptions, keys)) - .isEqualTo(values); - - // NOTE: txn2 updates k1, during txn3 - try { - txn2.put(keys[0], otherValue); // should cause an exception! - } catch (final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" - + "transactions"); - } - } - - @Test - public void multiGetAsListForUpdate_conflict() throws RocksDBException { - final byte[][] keys = new byte[][] {"key1".getBytes(UTF_8), "key2".getBytes(UTF_8)}; - final byte[][] values = new byte[][] {"value1".getBytes(UTF_8), "value2".getBytes(UTF_8)}; - final byte[] otherValue = "otherValue".getBytes(UTF_8); - - try (final DBContainer dbContainer = startDb(); - final ReadOptions readOptions = new ReadOptions()) { - try (final Transaction txn = dbContainer.beginTransaction()) { - txn.put(keys[0], values[0]); - txn.put(keys[1], values[1]); - assertThat(txn.multiGetAsList(readOptions, Arrays.asList(keys))).containsExactly(values); - txn.commit(); - } - - try (final Transaction txn2 = dbContainer.beginTransaction()) { - try (final Transaction txn3 = dbContainer.beginTransaction()) { - assertThat(txn3.multiGetForUpdateAsList(readOptions, Arrays.asList(keys))) - .containsExactly(values); - - // NOTE: txn2 updates k1, during txn3 - try { - txn2.put(keys[0], otherValue); // should cause an exception! - } catch(final RocksDBException e) { - assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); - return; - } - } - } - - fail("Expected an exception for put after getForUpdate from conflicting" + - "transactions"); - } - } - - @Test - public void name() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getName()).isEmpty(); - final String name = "my-transaction-" + rand.nextLong(); - txn.setName(name); - assertThat(txn.getName()).isEqualTo(name); - } - } - - @Test - public void ID() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getID()).isGreaterThan(0); - } - } - - @Test - public void deadlockDetect() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.isDeadlockDetect()).isFalse(); - } - } - - @Test - public void waitingTxns() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getWaitingTxns().getTransactionIds().length).isEqualTo(0); - } - } - - @Test - public void state() throws RocksDBException { - try(final DBContainer dbContainer = startDb()) { - - try(final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getState()) - .isSameAs(Transaction.TransactionState.STARTED); - txn.commit(); - assertThat(txn.getState()) - .isSameAs(Transaction.TransactionState.COMMITTED); - } - - try(final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getState()) - .isSameAs(Transaction.TransactionState.STARTED); - txn.rollback(); - assertThat(txn.getState()) - .isSameAs(Transaction.TransactionState.STARTED); - } - } - } - - @Test - public void Id() throws RocksDBException { - try(final DBContainer dbContainer = startDb(); - final Transaction txn = dbContainer.beginTransaction()) { - assertThat(txn.getId()).isNotNull(); - } - } - - @Override - public TransactionDBContainer startDb() throws RocksDBException { - final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); - final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); - final List columnFamilyDescriptors = - Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY, - columnFamilyOptions)); - final List columnFamilyHandles = new ArrayList<>(); - - final TransactionDB txnDb; - try { - txnDb = TransactionDB.open(options, txnDbOptions, - dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, - columnFamilyHandles); - } catch(final RocksDBException e) { - columnFamilyOptions.close(); - txnDbOptions.close(); - options.close(); - throw e; - } - - final WriteOptions writeOptions = new WriteOptions(); - final TransactionOptions txnOptions = new TransactionOptions(); - - return new TransactionDBContainer(txnOptions, writeOptions, - columnFamilyHandles, txnDb, txnDbOptions, columnFamilyOptions, options); - } - - private static class TransactionDBContainer - extends DBContainer { - private final TransactionOptions txnOptions; - private final TransactionDB txnDb; - private final TransactionDBOptions txnDbOptions; - - public TransactionDBContainer( - final TransactionOptions txnOptions, final WriteOptions writeOptions, - final List columnFamilyHandles, - final TransactionDB txnDb, final TransactionDBOptions txnDbOptions, - final ColumnFamilyOptions columnFamilyOptions, - final DBOptions options) { - super(writeOptions, columnFamilyHandles, columnFamilyOptions, - options); - this.txnOptions = txnOptions; - this.txnDb = txnDb; - this.txnDbOptions = txnDbOptions; - } - - @Override - public Transaction beginTransaction() { - return txnDb.beginTransaction(writeOptions, txnOptions); - } - - @Override - public Transaction beginTransaction(final WriteOptions writeOptions) { - return txnDb.beginTransaction(writeOptions, txnOptions); - } - - @Override - public void close() { - txnOptions.close(); - writeOptions.close(); - for(final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { - columnFamilyHandle.close(); - } - txnDb.close(); - txnDbOptions.close(); - options.close(); - } - } - -} diff --git a/java/src/test/java/org/rocksdb/TtlDBTest.java b/java/src/test/java/org/rocksdb/TtlDBTest.java deleted file mode 100644 index ffa15e768..000000000 --- a/java/src/test/java/org/rocksdb/TtlDBTest.java +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TtlDBTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void ttlDBOpen() throws RocksDBException, InterruptedException { - try (final Options options = new Options().setCreateIfMissing(true).setMaxCompactionBytes(0); - final TtlDB ttlDB = TtlDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - ttlDB.put("key".getBytes(), "value".getBytes()); - assertThat(ttlDB.get("key".getBytes())). - isEqualTo("value".getBytes()); - assertThat(ttlDB.get("key".getBytes())).isNotNull(); - } - } - - @Test - public void ttlDBOpenWithTtl() throws RocksDBException, InterruptedException { - try (final Options options = new Options().setCreateIfMissing(true).setMaxCompactionBytes(0); - final TtlDB ttlDB = TtlDB.open(options, dbFolder.getRoot().getAbsolutePath(), 1, false);) { - ttlDB.put("key".getBytes(), "value".getBytes()); - assertThat(ttlDB.get("key".getBytes())). - isEqualTo("value".getBytes()); - TimeUnit.SECONDS.sleep(2); - ttlDB.compactRange(); - assertThat(ttlDB.get("key".getBytes())).isNull(); - } - } - - @Test - public void ttlDbOpenWithColumnFamilies() throws RocksDBException, - InterruptedException { - final List cfNames = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes()) - ); - final List ttlValues = Arrays.asList(0, 1); - - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions dbOptions = new DBOptions() - .setCreateMissingColumnFamilies(true) - .setCreateIfMissing(true); - final TtlDB ttlDB = TtlDB.open(dbOptions, - dbFolder.getRoot().getAbsolutePath(), cfNames, - columnFamilyHandleList, ttlValues, false)) { - try { - ttlDB.put("key".getBytes(), "value".getBytes()); - assertThat(ttlDB.get("key".getBytes())). - isEqualTo("value".getBytes()); - ttlDB.put(columnFamilyHandleList.get(1), "key".getBytes(), - "value".getBytes()); - assertThat(ttlDB.get(columnFamilyHandleList.get(1), - "key".getBytes())).isEqualTo("value".getBytes()); - TimeUnit.SECONDS.sleep(2); - - ttlDB.compactRange(); - ttlDB.compactRange(columnFamilyHandleList.get(1)); - - assertThat(ttlDB.get("key".getBytes())).isNotNull(); - assertThat(ttlDB.get(columnFamilyHandleList.get(1), - "key".getBytes())).isNull(); - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : - columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } - } - - @Test - public void createTtlColumnFamily() throws RocksDBException, - InterruptedException { - try (final Options options = new Options().setCreateIfMissing(true); - final TtlDB ttlDB = TtlDB.open(options, - dbFolder.getRoot().getAbsolutePath()); - final ColumnFamilyHandle columnFamilyHandle = - ttlDB.createColumnFamilyWithTtl( - new ColumnFamilyDescriptor("new_cf".getBytes()), 1)) { - ttlDB.put(columnFamilyHandle, "key".getBytes(), - "value".getBytes()); - assertThat(ttlDB.get(columnFamilyHandle, "key".getBytes())). - isEqualTo("value".getBytes()); - TimeUnit.SECONDS.sleep(2); - ttlDB.compactRange(columnFamilyHandle); - assertThat(ttlDB.get(columnFamilyHandle, "key".getBytes())).isNull(); - } - } -} diff --git a/java/src/test/java/org/rocksdb/Types.java b/java/src/test/java/org/rocksdb/Types.java deleted file mode 100644 index c3c1de833..000000000 --- a/java/src/test/java/org/rocksdb/Types.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -/** - * Simple type conversion methods - * for use in tests - */ -public class Types { - - /** - * Convert first 4 bytes of a byte array to an int - * - * @param data The byte array - * - * @return An integer - */ - public static int byteToInt(final byte data[]) { - return (data[0] & 0xff) | - ((data[1] & 0xff) << 8) | - ((data[2] & 0xff) << 16) | - ((data[3] & 0xff) << 24); - } - - /** - * Convert an int to 4 bytes - * - * @param v The int - * - * @return A byte array containing 4 bytes - */ - public static byte[] intToByte(final int v) { - return new byte[] { - (byte)((v >>> 0) & 0xff), - (byte)((v >>> 8) & 0xff), - (byte)((v >>> 16) & 0xff), - (byte)((v >>> 24) & 0xff) - }; - } -} diff --git a/java/src/test/java/org/rocksdb/VerifyChecksumsTest.java b/java/src/test/java/org/rocksdb/VerifyChecksumsTest.java deleted file mode 100644 index ddc2a456f..000000000 --- a/java/src/test/java/org/rocksdb/VerifyChecksumsTest.java +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class VerifyChecksumsTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); - - /** - * Class to factor out the specific DB operations within the test - */ - abstract static class Operations { - final int kv_count; - final List elements = new ArrayList<>(); - final List sortedElements = new ArrayList<>(); - - Operations(final int kv_count) { - this.kv_count = kv_count; - for (int i = 0; i < kv_count; i++) elements.add(MessageFormat.format("{0,number,#}", i)); - sortedElements.addAll(elements); - Collections.sort(sortedElements); - } - - void fill(final RocksDB db) throws RocksDBException { - for (int i = 0; i < kv_count; i++) { - final String key = MessageFormat.format("key{0}", elements.get(i)); - final String value = MessageFormat.format("value{0}", elements.get(i)); - // noinspection ObjectAllocationInLoop - db.put(key.getBytes(), value.getBytes()); - } - db.flush(new FlushOptions()); - } - - @SuppressWarnings("ObjectAllocationInLoop") - void get(final RocksDB db, final boolean verifyFlag) throws RocksDBException { - try (final ReadOptions readOptions = new ReadOptions()) { - readOptions.setReadaheadSize(32 * 1024); - readOptions.setFillCache(false); - readOptions.setVerifyChecksums(verifyFlag); - - for (int i = 0; i < kv_count / 10; i++) { - @SuppressWarnings("UnsecureRandomNumberGeneration") - final int index = Double.valueOf(Math.random() * kv_count).intValue(); - final String key = MessageFormat.format("key{0}", sortedElements.get(index)); - final String expectedValue = MessageFormat.format("value{0}", sortedElements.get(index)); - - final byte[] value = db.get(readOptions, key.getBytes()); - assertThat(value).isEqualTo(expectedValue.getBytes()); - } - } - } - - @SuppressWarnings("ObjectAllocationInLoop") - void multiGet(final RocksDB db, final boolean verifyFlag) throws RocksDBException { - try (final ReadOptions readOptions = new ReadOptions()) { - readOptions.setReadaheadSize(32 * 1024); - readOptions.setFillCache(false); - readOptions.setVerifyChecksums(verifyFlag); - - final List keys = new ArrayList<>(); - final List expectedValues = new ArrayList<>(); - - for (int i = 0; i < kv_count / 10; i++) { - @SuppressWarnings("UnsecureRandomNumberGeneration") - final int index = Double.valueOf(Math.random() * kv_count).intValue(); - keys.add(MessageFormat.format("key{0}", sortedElements.get(index)).getBytes()); - - expectedValues.add(MessageFormat.format("value{0}", sortedElements.get(index))); - } - - final List values = db.multiGetAsList(readOptions, keys); - for (int i = 0; i < keys.size(); i++) { - assertThat(values.get(i)).isEqualTo(expectedValues.get(i).getBytes()); - } - } - } - - void iterate(final RocksDB db, final boolean verifyFlag) throws RocksDBException { - final ReadOptions readOptions = new ReadOptions(); - readOptions.setReadaheadSize(32 * 1024); - readOptions.setFillCache(false); - readOptions.setVerifyChecksums(verifyFlag); - int i = 0; - try (final RocksIterator rocksIterator = db.newIterator(readOptions)) { - rocksIterator.seekToFirst(); - rocksIterator.status(); - while (rocksIterator.isValid()) { - final byte[] key = rocksIterator.key(); - final byte[] value = rocksIterator.value(); - // noinspection ObjectAllocationInLoop - assertThat(key).isEqualTo( - (MessageFormat.format("key{0}", sortedElements.get(i))).getBytes()); - // noinspection ObjectAllocationInLoop - assertThat(value).isEqualTo( - (MessageFormat.format("value{0}", sortedElements.get(i))).getBytes()); - rocksIterator.next(); - rocksIterator.status(); - i++; - } - } - assertThat(i).isEqualTo(kv_count); - } - - abstract void performOperations(final RocksDB db, final boolean verifyFlag) - throws RocksDBException; - } - - private static final int KV_COUNT = 10000; - - /** - * Run some operations and count the TickerType.BLOCK_CHECKSUM_COMPUTE_COUNT before and after - * It should GO UP when the read options have checksum verification turned on. - * It shoulld REMAIN UNCHANGED when the read options have checksum verification turned off. - * As the read options refer only to the read operations, there are still a few checksums - * performed outside this (blocks are getting loaded for lots of reasons, not aways directly due - * to reads) but this test provides a good enough proxy for whether the flag is being noticed. - * - * @param operations the DB reading operations to perform which affect the checksum stats - * - * @throws RocksDBException - */ - private void verifyChecksums(final Operations operations) throws RocksDBException { - final String dbPath = dbFolder.getRoot().getAbsolutePath(); - - // noinspection SingleStatementInBlock - try (final Statistics statistics = new Statistics(); - final Options options = new Options().setCreateIfMissing(true).setStatistics(statistics)) { - try (final RocksDB db = RocksDB.open(options, dbPath)) { - // 0 - System.out.println(MessageFormat.format( - "newly open {0}", statistics.getTickerCount(TickerType.BLOCK_CHECKSUM_COMPUTE_COUNT))); - operations.fill(db); - // - System.out.println(MessageFormat.format( - "flushed {0}", statistics.getTickerCount(TickerType.BLOCK_CHECKSUM_COMPUTE_COUNT))); - } - - // 2 - System.out.println(MessageFormat.format("closed-after-write {0}", - statistics.getTickerCount(TickerType.BLOCK_CHECKSUM_COMPUTE_COUNT))); - - for (final boolean verifyFlag : new boolean[] {false, true, false, true}) { - try (final RocksDB db = RocksDB.open(options, dbPath)) { - final long beforeOperationsCount = - statistics.getTickerCount(TickerType.BLOCK_CHECKSUM_COMPUTE_COUNT); - System.out.println(MessageFormat.format("re-opened {0}", beforeOperationsCount)); - operations.performOperations(db, verifyFlag); - final long afterOperationsCount = - statistics.getTickerCount(TickerType.BLOCK_CHECKSUM_COMPUTE_COUNT); - if (verifyFlag) { - // We don't need to be exact - we are checking that the checksums happen - // exactly how many depends on block size etc etc, so may not be entirely stable - System.out.println(MessageFormat.format("verify=true {0}", afterOperationsCount)); - assertThat(afterOperationsCount).isGreaterThan(beforeOperationsCount + 20); - } else { - System.out.println(MessageFormat.format("verify=false {0}", afterOperationsCount)); - assertThat(afterOperationsCount).isEqualTo(beforeOperationsCount); - } - } - } - } - } - - @Test - public void verifyChecksumsInIteration() throws RocksDBException { - // noinspection AnonymousInnerClassMayBeStatic - verifyChecksums(new Operations(KV_COUNT) { - @Override - void performOperations(final RocksDB db, final boolean verifyFlag) throws RocksDBException { - iterate(db, verifyFlag); - } - }); - } - - @Test - public void verifyChecksumsGet() throws RocksDBException { - // noinspection AnonymousInnerClassMayBeStatic - verifyChecksums(new Operations(KV_COUNT) { - @Override - void performOperations(final RocksDB db, final boolean verifyFlag) throws RocksDBException { - get(db, verifyFlag); - } - }); - } - - @Test - public void verifyChecksumsMultiGet() throws RocksDBException { - // noinspection AnonymousInnerClassMayBeStatic - verifyChecksums(new Operations(KV_COUNT) { - @Override - void performOperations(final RocksDB db, final boolean verifyFlag) throws RocksDBException { - multiGet(db, verifyFlag); - } - }); - } -} diff --git a/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java b/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java deleted file mode 100644 index 2a0133f6b..000000000 --- a/java/src/test/java/org/rocksdb/WALRecoveryModeTest.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class WALRecoveryModeTest { - - @Test - public void getWALRecoveryMode() { - for (final WALRecoveryMode walRecoveryMode : WALRecoveryMode.values()) { - assertThat(WALRecoveryMode.getWALRecoveryMode(walRecoveryMode.getValue())) - .isEqualTo(walRecoveryMode); - } - } -} diff --git a/java/src/test/java/org/rocksdb/WalFilterTest.java b/java/src/test/java/org/rocksdb/WalFilterTest.java deleted file mode 100644 index adeb959d1..000000000 --- a/java/src/test/java/org/rocksdb/WalFilterTest.java +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.rocksdb.util.ByteUtil.bytes; -import static org.rocksdb.util.TestUtil.*; - -public class WalFilterTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void walFilter() throws RocksDBException { - // Create 3 batches with two keys each - final byte[][][] batchKeys = { - new byte[][] { - bytes("key1"), - bytes("key2") - }, - new byte[][] { - bytes("key3"), - bytes("key4") - }, - new byte[][] { - bytes("key5"), - bytes("key6") - } - - }; - - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor(bytes("pikachu")) - ); - final List cfHandles = new ArrayList<>(); - - // Test with all WAL processing options - for (final WalProcessingOption option : WalProcessingOption.values()) { - try (final Options options = optionsForLogIterTest(); - final DBOptions dbOptions = new DBOptions(options) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(dbOptions, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, cfHandles)) { - try (final WriteOptions writeOptions = new WriteOptions()) { - // Write given keys in given batches - for (int i = 0; i < batchKeys.length; i++) { - final WriteBatch batch = new WriteBatch(); - for (int j = 0; j < batchKeys[i].length; j++) { - batch.put(cfHandles.get(0), batchKeys[i][j], dummyString(1024)); - } - db.write(writeOptions, batch); - } - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - } - } - - // Create a test filter that would apply wal_processing_option at the first - // record - final int applyOptionForRecordIndex = 1; - try (final TestableWalFilter walFilter = - new TestableWalFilter(option, applyOptionForRecordIndex)) { - - try (final Options options = optionsForLogIterTest(); - final DBOptions dbOptions = new DBOptions(options) - .setWalFilter(walFilter)) { - - try (final RocksDB db = RocksDB.open(dbOptions, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, cfHandles)) { - - try { - assertThat(walFilter.logNumbers).isNotEmpty(); - assertThat(walFilter.logFileNames).isNotEmpty(); - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - } - } catch (final RocksDBException e) { - if (option != WalProcessingOption.CORRUPTED_RECORD) { - // exception is expected when CORRUPTED_RECORD! - throw e; - } - } - } - } - } - } - - - private static class TestableWalFilter extends AbstractWalFilter { - private final WalProcessingOption walProcessingOption; - private final int applyOptionForRecordIndex; - Map cfLognumber; - Map cfNameId; - final List logNumbers = new ArrayList<>(); - final List logFileNames = new ArrayList<>(); - private int currentRecordIndex = 0; - - public TestableWalFilter(final WalProcessingOption walProcessingOption, - final int applyOptionForRecordIndex) { - super(); - this.walProcessingOption = walProcessingOption; - this.applyOptionForRecordIndex = applyOptionForRecordIndex; - } - - @Override - public void columnFamilyLogNumberMap(final Map cfLognumber, - final Map cfNameId) { - this.cfLognumber = cfLognumber; - this.cfNameId = cfNameId; - } - - @Override - public LogRecordFoundResult logRecordFound( - final long logNumber, final String logFileName, final WriteBatch batch, - final WriteBatch newBatch) { - - logNumbers.add(logNumber); - logFileNames.add(logFileName); - - final WalProcessingOption optionToReturn; - if (currentRecordIndex == applyOptionForRecordIndex) { - optionToReturn = walProcessingOption; - } - else { - optionToReturn = WalProcessingOption.CONTINUE_PROCESSING; - } - - currentRecordIndex++; - - return new LogRecordFoundResult(optionToReturn, false); - } - - @Override - public String name() { - return "testable-wal-filter"; - } - } -} diff --git a/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java b/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java deleted file mode 100644 index 2826b128f..000000000 --- a/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import java.util.Arrays; -import java.util.List; - -import org.junit.ClassRule; -import org.junit.Test; -import org.rocksdb.util.CapturingWriteBatchHandler; -import org.rocksdb.util.CapturingWriteBatchHandler.Event; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.*; - - -public class WriteBatchHandlerTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Test - public void writeBatchHandler() throws RocksDBException { - // setup test data - final List testEvents = Arrays.asList( - new Event(DELETE, "k0".getBytes(), null), - new Event(PUT, "k1".getBytes(), "v1".getBytes()), - new Event(PUT, "k2".getBytes(), "v2".getBytes()), - new Event(PUT, "k3".getBytes(), "v3".getBytes()), - new Event(LOG, null, "log1".getBytes()), - new Event(MERGE, "k2".getBytes(), "v22".getBytes()), - new Event(DELETE, "k3".getBytes(), null) - ); - - // load test data to the write batch - try (final WriteBatch batch = new WriteBatch()) { - for (final Event testEvent : testEvents) { - switch (testEvent.action) { - - case PUT: - batch.put(testEvent.key, testEvent.value); - break; - - case MERGE: - batch.merge(testEvent.key, testEvent.value); - break; - - case DELETE: - batch.delete(testEvent.key); - break; - - case LOG: - batch.putLogData(testEvent.value); - break; - } - } - - // attempt to read test data back from the WriteBatch by iterating - // with a handler - try (final CapturingWriteBatchHandler handler = - new CapturingWriteBatchHandler()) { - batch.iterate(handler); - - // compare the results to the test data - final List actualEvents = - handler.getEvents(); - assertThat(testEvents.size()).isSameAs(actualEvents.size()); - - assertThat(testEvents).isEqualTo(actualEvents); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/WriteBatchTest.java b/java/src/test/java/org/rocksdb/WriteBatchTest.java deleted file mode 100644 index cc3ad26eb..000000000 --- a/java/src/test/java/org/rocksdb/WriteBatchTest.java +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.DELETE; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.DELETE_RANGE; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.LOG; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.MERGE; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.PUT; -import static org.rocksdb.util.CapturingWriteBatchHandler.Action.SINGLE_DELETE; - -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.CapturingWriteBatchHandler; -import org.rocksdb.util.CapturingWriteBatchHandler.Event; -import org.rocksdb.util.WriteBatchGetter; - -/** - * This class mimics the db/write_batch_test.cc - * in the c++ rocksdb library. - */ -public class WriteBatchTest { - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void emptyWriteBatch() { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.count()).isEqualTo(0); - } - } - - @Test - public void multipleBatchOperations() - throws RocksDBException { - - final byte[] foo = "foo".getBytes(UTF_8); - final byte[] bar = "bar".getBytes(UTF_8); - final byte[] box = "box".getBytes(UTF_8); - final byte[] baz = "baz".getBytes(UTF_8); - final byte[] boo = "boo".getBytes(UTF_8); - final byte[] hoo = "hoo".getBytes(UTF_8); - final byte[] hello = "hello".getBytes(UTF_8); - - try (final WriteBatch batch = new WriteBatch()) { - batch.put(foo, bar); - batch.delete(box); - batch.put(baz, boo); - batch.merge(baz, hoo); - batch.singleDelete(foo); - batch.deleteRange(baz, foo); - batch.putLogData(hello); - - try(final CapturingWriteBatchHandler handler = - new CapturingWriteBatchHandler()) { - batch.iterate(handler); - - assertThat(handler.getEvents().size()).isEqualTo(7); - - assertThat(handler.getEvents().get(0)).isEqualTo(new Event(PUT, foo, bar)); - assertThat(handler.getEvents().get(1)).isEqualTo(new Event(DELETE, box, null)); - assertThat(handler.getEvents().get(2)).isEqualTo(new Event(PUT, baz, boo)); - assertThat(handler.getEvents().get(3)).isEqualTo(new Event(MERGE, baz, hoo)); - assertThat(handler.getEvents().get(4)).isEqualTo(new Event(SINGLE_DELETE, foo, null)); - assertThat(handler.getEvents().get(5)).isEqualTo(new Event(DELETE_RANGE, baz, foo)); - assertThat(handler.getEvents().get(6)).isEqualTo(new Event(LOG, null, hello)); - } - } - } - - @Test - public void multipleBatchOperationsDirect() - throws UnsupportedEncodingException, RocksDBException { - try (WriteBatch batch = new WriteBatch()) { - ByteBuffer key = ByteBuffer.allocateDirect(16); - ByteBuffer value = ByteBuffer.allocateDirect(16); - key.put("foo".getBytes("US-ASCII")).flip(); - value.put("bar".getBytes("US-ASCII")).flip(); - batch.put(key, value); - assertThat(key.position()).isEqualTo(3); - assertThat(key.limit()).isEqualTo(3); - assertThat(value.position()).isEqualTo(3); - assertThat(value.limit()).isEqualTo(3); - - key.clear(); - key.put("box".getBytes("US-ASCII")).flip(); - batch.delete(key); - assertThat(key.position()).isEqualTo(3); - assertThat(key.limit()).isEqualTo(3); - - batch.put("baz".getBytes("US-ASCII"), "boo".getBytes("US-ASCII")); - - WriteBatchTestInternalHelper.setSequence(batch, 100); - assertThat(WriteBatchTestInternalHelper.sequence(batch)).isNotNull().isEqualTo(100); - assertThat(batch.count()).isEqualTo(3); - assertThat(new String(getContents(batch), "US-ASCII")) - .isEqualTo("Put(baz, boo)@102" - + "Delete(box)@101" - + "Put(foo, bar)@100"); - } - } - - @Test - public void testAppendOperation() - throws RocksDBException { - try (final WriteBatch b1 = new WriteBatch(); - final WriteBatch b2 = new WriteBatch()) { - WriteBatchTestInternalHelper.setSequence(b1, 200); - WriteBatchTestInternalHelper.setSequence(b2, 300); - WriteBatchTestInternalHelper.append(b1, b2); - assertThat(getContents(b1).length).isEqualTo(0); - assertThat(b1.count()).isEqualTo(0); - b2.put("a".getBytes(UTF_8), "va".getBytes(UTF_8)); - WriteBatchTestInternalHelper.append(b1, b2); - assertThat("Put(a, va)@200".equals(new String(getContents(b1), - UTF_8))); - assertThat(b1.count()).isEqualTo(1); - b2.clear(); - b2.put("b".getBytes(UTF_8), "vb".getBytes(UTF_8)); - WriteBatchTestInternalHelper.append(b1, b2); - assertThat(("Put(a, va)@200" + - "Put(b, vb)@201") - .equals(new String(getContents(b1), UTF_8))); - assertThat(b1.count()).isEqualTo(2); - b2.delete("foo".getBytes(UTF_8)); - WriteBatchTestInternalHelper.append(b1, b2); - assertThat(("Put(a, va)@200" + - "Put(b, vb)@202" + - "Put(b, vb)@201" + - "Delete(foo)@203") - .equals(new String(getContents(b1), UTF_8))); - assertThat(b1.count()).isEqualTo(4); - } - } - - @Test - public void blobOperation() - throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); - batch.put("k2".getBytes(UTF_8), "v2".getBytes(UTF_8)); - batch.put("k3".getBytes(UTF_8), "v3".getBytes(UTF_8)); - batch.putLogData("blob1".getBytes(UTF_8)); - batch.delete("k2".getBytes(UTF_8)); - batch.putLogData("blob2".getBytes(UTF_8)); - batch.merge("foo".getBytes(UTF_8), "bar".getBytes(UTF_8)); - assertThat(batch.count()).isEqualTo(5); - assertThat(("Merge(foo, bar)@4" + - "Put(k1, v1)@0" + - "Delete(k2)@3" + - "Put(k2, v2)@1" + - "Put(k3, v3)@2") - .equals(new String(getContents(batch), UTF_8))); - } - } - - @Test - public void savePoints() - throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); - batch.put("k2".getBytes(UTF_8), "v2".getBytes(UTF_8)); - batch.put("k3".getBytes(UTF_8), "v3".getBytes(UTF_8)); - - assertThat(getFromWriteBatch(batch, "k1")).isEqualTo("v1"); - assertThat(getFromWriteBatch(batch, "k2")).isEqualTo("v2"); - assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3"); - - batch.setSavePoint(); - - batch.delete("k2".getBytes(UTF_8)); - batch.put("k3".getBytes(UTF_8), "v3-2".getBytes(UTF_8)); - - assertThat(getFromWriteBatch(batch, "k2")).isNull(); - assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3-2"); - - - batch.setSavePoint(); - - batch.put("k3".getBytes(UTF_8), "v3-3".getBytes(UTF_8)); - batch.put("k4".getBytes(UTF_8), "v4".getBytes(UTF_8)); - - assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3-3"); - assertThat(getFromWriteBatch(batch, "k4")).isEqualTo("v4"); - - - batch.rollbackToSavePoint(); - - assertThat(getFromWriteBatch(batch, "k2")).isNull(); - assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3-2"); - assertThat(getFromWriteBatch(batch, "k4")).isNull(); - - - batch.rollbackToSavePoint(); - - assertThat(getFromWriteBatch(batch, "k1")).isEqualTo("v1"); - assertThat(getFromWriteBatch(batch, "k2")).isEqualTo("v2"); - assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3"); - assertThat(getFromWriteBatch(batch, "k4")).isNull(); - } - } - - @Test - public void deleteRange() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteBatch batch = new WriteBatch(); - final WriteOptions wOpt = new WriteOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - db.put("key3".getBytes(), "abcdefg".getBytes()); - db.put("key4".getBytes(), "xyz".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo("12345678".getBytes()); - assertThat(db.get("key3".getBytes())).isEqualTo("abcdefg".getBytes()); - assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - - batch.deleteRange("key2".getBytes(), "key4".getBytes()); - db.write(wOpt, batch); - - assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); - assertThat(db.get("key2".getBytes())).isNull(); - assertThat(db.get("key3".getBytes())).isNull(); - assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - } - } - - @Test - public void restorePoints() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - - batch.put("k1".getBytes(), "v1".getBytes()); - batch.put("k2".getBytes(), "v2".getBytes()); - - batch.setSavePoint(); - - batch.put("k1".getBytes(), "123456789".getBytes()); - batch.delete("k2".getBytes()); - - batch.rollbackToSavePoint(); - - try(final CapturingWriteBatchHandler handler = new CapturingWriteBatchHandler()) { - batch.iterate(handler); - - assertThat(handler.getEvents().size()).isEqualTo(2); - assertThat(handler.getEvents().get(0)).isEqualTo(new Event(PUT, "k1".getBytes(), "v1".getBytes())); - assertThat(handler.getEvents().get(1)).isEqualTo(new Event(PUT, "k2".getBytes(), "v2".getBytes())); - } - } - } - - @Test(expected = RocksDBException.class) - public void restorePoints_withoutSavePoints() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.rollbackToSavePoint(); - } - } - - @Test(expected = RocksDBException.class) - public void restorePoints_withoutSavePoints_nested() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - - batch.setSavePoint(); - batch.rollbackToSavePoint(); - - // without previous corresponding setSavePoint - batch.rollbackToSavePoint(); - } - } - - @Test - public void popSavePoint() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - - batch.put("k1".getBytes(), "v1".getBytes()); - batch.put("k2".getBytes(), "v2".getBytes()); - - batch.setSavePoint(); - - batch.put("k1".getBytes(), "123456789".getBytes()); - batch.delete("k2".getBytes()); - - batch.setSavePoint(); - - batch.popSavePoint(); - - batch.rollbackToSavePoint(); - - try(final CapturingWriteBatchHandler handler = new CapturingWriteBatchHandler()) { - batch.iterate(handler); - - assertThat(handler.getEvents().size()).isEqualTo(2); - assertThat(handler.getEvents().get(0)).isEqualTo(new Event(PUT, "k1".getBytes(), "v1".getBytes())); - assertThat(handler.getEvents().get(1)).isEqualTo(new Event(PUT, "k2".getBytes(), "v2".getBytes())); - } - } - } - - @Test(expected = RocksDBException.class) - public void popSavePoint_withoutSavePoints() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.popSavePoint(); - } - } - - @Test(expected = RocksDBException.class) - public void popSavePoint_withoutSavePoints_nested() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - - batch.setSavePoint(); - batch.popSavePoint(); - - // without previous corresponding setSavePoint - batch.popSavePoint(); - } - } - - @Test - public void maxBytes() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.setMaxBytes(19); - - batch.put("k1".getBytes(), "v1".getBytes()); - } - } - - @Test(expected = RocksDBException.class) - public void maxBytes_over() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.setMaxBytes(1); - - batch.put("k1".getBytes(), "v1".getBytes()); - } - } - - @Test - public void data() throws RocksDBException { - try (final WriteBatch batch1 = new WriteBatch()) { - batch1.delete("k0".getBytes()); - batch1.put("k1".getBytes(), "v1".getBytes()); - batch1.put("k2".getBytes(), "v2".getBytes()); - batch1.put("k3".getBytes(), "v3".getBytes()); - batch1.putLogData("log1".getBytes()); - batch1.merge("k2".getBytes(), "v22".getBytes()); - batch1.delete("k3".getBytes()); - - final byte[] serialized = batch1.data(); - - try(final WriteBatch batch2 = new WriteBatch(serialized)) { - assertThat(batch2.count()).isEqualTo(batch1.count()); - - try(final CapturingWriteBatchHandler handler1 = new CapturingWriteBatchHandler()) { - batch1.iterate(handler1); - - try (final CapturingWriteBatchHandler handler2 = new CapturingWriteBatchHandler()) { - batch2.iterate(handler2); - - assertThat(handler1.getEvents().equals(handler2.getEvents())).isTrue(); - } - } - } - } - } - - @Test - public void dataSize() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - batch.put("k1".getBytes(), "v1".getBytes()); - - assertThat(batch.getDataSize()).isEqualTo(19); - } - } - - @Test - public void hasPut() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasPut()).isFalse(); - - batch.put("k1".getBytes(), "v1".getBytes()); - - assertThat(batch.hasPut()).isTrue(); - } - } - - @Test - public void hasDelete() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasDelete()).isFalse(); - - batch.delete("k1".getBytes()); - - assertThat(batch.hasDelete()).isTrue(); - } - } - - @Test - public void hasSingleDelete() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasSingleDelete()).isFalse(); - - batch.singleDelete("k1".getBytes()); - - assertThat(batch.hasSingleDelete()).isTrue(); - } - } - - @Test - public void hasDeleteRange() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasDeleteRange()).isFalse(); - - batch.deleteRange("k1".getBytes(), "k2".getBytes()); - - assertThat(batch.hasDeleteRange()).isTrue(); - } - } - - @Test - public void hasBeginPrepareRange() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasBeginPrepare()).isFalse(); - } - } - - @Test - public void hasEndPrepareRange() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasEndPrepare()).isFalse(); - } - } - - @Test - public void hasCommit() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasCommit()).isFalse(); - } - } - - @Test - public void hasRollback() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.hasRollback()).isFalse(); - } - } - - @Test - public void walTerminationPoint() throws RocksDBException { - try (final WriteBatch batch = new WriteBatch()) { - WriteBatch.SavePoint walTerminationPoint = batch.getWalTerminationPoint(); - assertThat(walTerminationPoint.isCleared()).isTrue(); - - batch.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); - - batch.markWalTerminationPoint(); - - walTerminationPoint = batch.getWalTerminationPoint(); - assertThat(walTerminationPoint.getSize()).isEqualTo(19); - assertThat(walTerminationPoint.getCount()).isEqualTo(1); - assertThat(walTerminationPoint.getContentFlags()).isEqualTo(2); - } - } - - @Test - public void getWriteBatch() { - try (final WriteBatch batch = new WriteBatch()) { - assertThat(batch.getWriteBatch()).isEqualTo(batch); - } - } - - static byte[] getContents(final WriteBatch wb) { - return getContents(wb.nativeHandle_); - } - - static String getFromWriteBatch(final WriteBatch wb, final String key) - throws RocksDBException { - final WriteBatchGetter getter = - new WriteBatchGetter(key.getBytes(UTF_8)); - wb.iterate(getter); - if(getter.getValue() != null) { - return new String(getter.getValue(), UTF_8); - } else { - return null; - } - } - - private static native byte[] getContents(final long writeBatchHandle); -} - -/** - * Package-private class which provides java api to access - * c++ WriteBatchInternal. - */ -class WriteBatchTestInternalHelper { - static void setSequence(final WriteBatch wb, final long sn) { - setSequence(wb.nativeHandle_, sn); - } - - static long sequence(final WriteBatch wb) { - return sequence(wb.nativeHandle_); - } - - static void append(final WriteBatch wb1, final WriteBatch wb2) { - append(wb1.nativeHandle_, wb2.nativeHandle_); - } - - private static native void setSequence(final long writeBatchHandle, - final long sn); - - private static native long sequence(final long writeBatchHandle); - - private static native void append(final long writeBatchHandle1, - final long writeBatchHandle2); -} diff --git a/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java b/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java deleted file mode 100644 index c5090dbce..000000000 --- a/java/src/test/java/org/rocksdb/WriteBatchThreadedTest.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -import java.nio.ByteBuffer; -import java.util.*; -import java.util.concurrent.*; - -@RunWith(Parameterized.class) -public class WriteBatchThreadedTest { - - @Parameters(name = "WriteBatchThreadedTest(threadCount={0})") - public static Iterable data() { - return Arrays.asList(new Integer[]{1, 10, 50, 100}); - } - - @Parameter - public int threadCount; - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - RocksDB db; - - @Before - public void setUp() throws Exception { - RocksDB.loadLibrary(); - final Options options = new Options() - .setCreateIfMissing(true) - .setIncreaseParallelism(32); - db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath()); - assert (db != null); - } - - @After - public void tearDown() throws Exception { - if (db != null) { - db.close(); - } - } - - @Test - public void threadedWrites() throws InterruptedException, ExecutionException { - final List> callables = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - final int offset = i * 100; - callables.add(new Callable() { - @Override - public Void call() throws RocksDBException { - try (final WriteBatch wb = new WriteBatch(); - final WriteOptions w_opt = new WriteOptions()) { - for (int i = offset; i < offset + 100; i++) { - wb.put(ByteBuffer.allocate(4).putInt(i).array(), "parallel rocks test".getBytes()); - } - db.write(w_opt, wb); - } - return null; - } - }); - } - - //submit the callables - final ExecutorService executorService = - Executors.newFixedThreadPool(threadCount); - try { - final ExecutorCompletionService completionService = - new ExecutorCompletionService<>(executorService); - final Set> futures = new HashSet<>(); - for (final Callable callable : callables) { - futures.add(completionService.submit(callable)); - } - - while (futures.size() > 0) { - final Future future = completionService.take(); - futures.remove(future); - - try { - future.get(); - } catch (final ExecutionException e) { - for (final Future f : futures) { - f.cancel(true); - } - - throw e; - } - } - } finally { - executorService.shutdown(); - executorService.awaitTermination(10, TimeUnit.SECONDS); - } - } -} diff --git a/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java b/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java deleted file mode 100644 index b0a0cdc0e..000000000 --- a/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java +++ /dev/null @@ -1,1068 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -package org.rocksdb; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.util.ByteBufferAllocator; - -public class WriteBatchWithIndexTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - @Test - public void readYourOwnWrites() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] k1 = "key1".getBytes(); - final byte[] v1 = "value1".getBytes(); - final byte[] k2 = "key2".getBytes(); - final byte[] v2 = "value2".getBytes(); - - db.put(k1, v1); - db.put(k2, v2); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final RocksIterator base = db.newIterator(); - final RocksIterator it = wbwi.newIteratorWithBase(base)) { - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k1); - assertThat(it.value()).isEqualTo(v1); - - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k2); - assertThat(it.value()).isEqualTo(v2); - - //put data to the write batch and make sure we can read it. - final byte[] k3 = "key3".getBytes(); - final byte[] v3 = "value3".getBytes(); - wbwi.put(k3, v3); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k3); - assertThat(it.value()).isEqualTo(v3); - - //update k2 in the write batch and check the value - final byte[] v2Other = "otherValue2".getBytes(); - wbwi.put(k2, v2Other); - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k2); - assertThat(it.value()).isEqualTo(v2Other); - - //delete k1 and make sure we can read back the write - wbwi.delete(k1); - it.seek(k1); - assertThat(it.key()).isNotEqualTo(k1); - - //reinsert k1 and make sure we see the new value - final byte[] v1Other = "otherValue1".getBytes(); - wbwi.put(k1, v1Other); - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k1); - assertThat(it.value()).isEqualTo(v1Other); - - //single remove k3 and make sure we can read back the write - wbwi.singleDelete(k3); - it.seek(k3); - assertThat(it.isValid()).isEqualTo(false); - - //reinsert k3 and make sure we see the new value - final byte[] v3Other = "otherValue3".getBytes(); - wbwi.put(k3, v3Other); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k3); - assertThat(it.value()).isEqualTo(v3Other); - } - } - } - - @Test - public void readYourOwnWritesCf() throws RocksDBException { - final List cfNames = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - - final List columnFamilyHandleList = new ArrayList<>(); - - // Test open database with column family names - try (final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfNames, columnFamilyHandleList)) { - final ColumnFamilyHandle newCf = columnFamilyHandleList.get(1); - - try { - final byte[] k1 = "key1".getBytes(); - final byte[] v1 = "value1".getBytes(); - final byte[] k2 = "key2".getBytes(); - final byte[] v2 = "value2".getBytes(); - - db.put(newCf, k1, v1); - db.put(newCf, k2, v2); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator base = db.newIterator(newCf, readOptions); - final RocksIterator it = wbwi.newIteratorWithBase(newCf, base, readOptions)) { - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k1); - assertThat(it.value()).isEqualTo(v1); - - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k2); - assertThat(it.value()).isEqualTo(v2); - - // put data to the write batch and make sure we can read it. - final byte[] k3 = "key3".getBytes(); - final byte[] v3 = "value3".getBytes(); - wbwi.put(newCf, k3, v3); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k3); - assertThat(it.value()).isEqualTo(v3); - - // update k2 in the write batch and check the value - final byte[] v2Other = "otherValue2".getBytes(); - wbwi.put(newCf, k2, v2Other); - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k2); - assertThat(it.value()).isEqualTo(v2Other); - - // delete k1 and make sure we can read back the write - wbwi.delete(newCf, k1); - it.seek(k1); - assertThat(it.key()).isNotEqualTo(k1); - - // reinsert k1 and make sure we see the new value - final byte[] v1Other = "otherValue1".getBytes(); - wbwi.put(newCf, k1, v1Other); - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k1); - assertThat(it.value()).isEqualTo(v1Other); - - // single remove k3 and make sure we can read back the write - wbwi.singleDelete(newCf, k3); - it.seek(k3); - assertThat(it.isValid()).isEqualTo(false); - - // reinsert k3 and make sure we see the new value - final byte[] v3Other = "otherValue3".getBytes(); - wbwi.put(newCf, k3, v3Other); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(k3); - assertThat(it.value()).isEqualTo(v3Other); - } - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } - } - - @Test - public void readYourOwnWritesCfIterDirectBB() throws RocksDBException { - readYourOwnWritesCfIterDirect(ByteBufferAllocator.DIRECT); - } - - @Test - public void readYourOwnWritesCfIterIndirectBB() throws RocksDBException { - readYourOwnWritesCfIterDirect(ByteBufferAllocator.HEAP); - } - - public void readYourOwnWritesCfIterDirect(final ByteBufferAllocator byteBufferAllocator) - throws RocksDBException { - final List cfNames = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - - final List columnFamilyHandleList = new ArrayList<>(); - - // Test open database with column family names - try (final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfNames, columnFamilyHandleList)) { - final ColumnFamilyHandle newCf = columnFamilyHandleList.get(1); - - try { - final byte[] kv1 = "key1".getBytes(); - final byte[] vv1 = "value1".getBytes(); - final ByteBuffer k1 = byteBufferAllocator.allocate(12); - k1.put(kv1); - final byte[] kv2 = "key2".getBytes(); - final byte[] vv2 = "value2".getBytes(); - final ByteBuffer k2 = byteBufferAllocator.allocate(12); - k2.put(kv2); - - db.put(newCf, kv1, vv1); - db.put(newCf, kv2, vv2); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator base = db.newIterator(newCf, readOptions); - final RocksIterator it = wbwi.newIteratorWithBase(newCf, base, readOptions)) { - k1.flip(); - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv1); - assertThat(it.value()).isEqualTo(vv1); - - k2.flip(); - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv2); - assertThat(it.value()).isEqualTo(vv2); - - final byte[] kv1point5 = "key1point5".getBytes(); - final ByteBuffer k1point5 = byteBufferAllocator.allocate(12); - k1point5.put(kv1point5); - - k1point5.flip(); - it.seek(k1point5); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv2); - assertThat(it.value()).isEqualTo(vv2); - - k1point5.flip(); - it.seekForPrev(k1point5); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv1); - assertThat(it.value()).isEqualTo(vv1); - - // put data to the write batch and make sure we can read it. - final byte[] kv3 = "key3".getBytes(); - final ByteBuffer k3 = byteBufferAllocator.allocate(12); - k3.put(kv3); - final byte[] vv3 = "value3".getBytes(); - wbwi.put(newCf, kv3, vv3); - k3.flip(); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv3); - assertThat(it.value()).isEqualTo(vv3); - - // update k2 in the write batch and check the value - final byte[] v2Other = "otherValue2".getBytes(); - wbwi.put(newCf, kv2, v2Other); - k2.flip(); - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv2); - assertThat(it.value()).isEqualTo(v2Other); - - // delete k1 and make sure we can read back the write - wbwi.delete(newCf, kv1); - k1.flip(); - it.seek(k1); - assertThat(it.key()).isNotEqualTo(kv1); - - // reinsert k1 and make sure we see the new value - final byte[] v1Other = "otherValue1".getBytes(); - wbwi.put(newCf, kv1, v1Other); - k1.flip(); - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv1); - assertThat(it.value()).isEqualTo(v1Other); - - // single remove k3 and make sure we can read back the write - wbwi.singleDelete(newCf, kv3); - k3.flip(); - it.seek(k3); - assertThat(it.isValid()).isEqualTo(false); - - // reinsert k3 and make sure we see the new value - final byte[] v3Other = "otherValue3".getBytes(); - wbwi.put(newCf, kv3, v3Other); - k3.flip(); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv3); - assertThat(it.value()).isEqualTo(v3Other); - } - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } - } - - @Test - public void readYourOwnWritesCfIterIndirect() throws RocksDBException { - final List cfNames = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - - final List columnFamilyHandleList = new ArrayList<>(); - - // Test open database with column family names - try (final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfNames, columnFamilyHandleList)) { - final ColumnFamilyHandle newCf = columnFamilyHandleList.get(1); - - try { - final byte[] kv1 = "key1".getBytes(); - final byte[] vv1 = "value1".getBytes(); - final ByteBuffer k1 = ByteBuffer.allocate(12); - k1.put(kv1).flip(); - final byte[] kv2 = "key2".getBytes(); - final byte[] vv2 = "value2".getBytes(); - final ByteBuffer k2 = ByteBuffer.allocate(12); - k2.put(kv2).flip(); - - db.put(newCf, kv1, vv1); - db.put(newCf, kv2, vv2); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator base = db.newIterator(newCf, readOptions); - final RocksIterator it = wbwi.newIteratorWithBase(newCf, base, readOptions)) { - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv1); - assertThat(it.value()).isEqualTo(vv1); - - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv2); - assertThat(it.value()).isEqualTo(vv2); - - // put data to the write batch and make sure we can read it. - final byte[] kv3 = "key3".getBytes(); - final ByteBuffer k3 = ByteBuffer.allocate(12); - k3.put(kv3); - final byte[] vv3 = "value3".getBytes(); - wbwi.put(newCf, kv3, vv3); - k3.flip(); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv3); - assertThat(it.value()).isEqualTo(vv3); - - // update k2 in the write batch and check the value - final byte[] v2Other = "otherValue2".getBytes(); - wbwi.put(newCf, kv2, v2Other); - k2.flip(); - it.seek(k2); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv2); - assertThat(it.value()).isEqualTo(v2Other); - - // delete k1 and make sure we can read back the write - wbwi.delete(newCf, kv1); - k1.flip(); - it.seek(k1); - assertThat(it.key()).isNotEqualTo(kv1); - - // reinsert k1 and make sure we see the new value - final byte[] v1Other = "otherValue1".getBytes(); - wbwi.put(newCf, kv1, v1Other); - k1.flip(); - it.seek(k1); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv1); - assertThat(it.value()).isEqualTo(v1Other); - - // single remove k3 and make sure we can read back the write - wbwi.singleDelete(newCf, kv3); - k3.flip(); - it.seek(k3); - assertThat(it.isValid()).isEqualTo(false); - - // reinsert k3 and make sure we see the new value - final byte[] v3Other = "otherValue3".getBytes(); - wbwi.put(newCf, kv3, v3Other); - k3.flip(); - it.seek(k3); - assertThat(it.isValid()).isTrue(); - assertThat(it.key()).isEqualTo(kv3); - assertThat(it.value()).isEqualTo(v3Other); - } - } finally { - for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } - } - - @Test - public void writeBatchWithIndex() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - final byte[] k1 = "key1".getBytes(); - final byte[] v1 = "value1".getBytes(); - final byte[] k2 = "key2".getBytes(); - final byte[] v2 = "value2".getBytes(); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(); - final WriteOptions wOpt = new WriteOptions()) { - wbwi.put(k1, v1); - wbwi.put(k2, v2); - - db.write(wOpt, wbwi); - } - - assertThat(db.get(k1)).isEqualTo(v1); - assertThat(db.get(k2)).isEqualTo(v2); - } - } - - @Test - public void write_writeBatchWithIndexDirect() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - final ByteBuffer k1 = ByteBuffer.allocateDirect(16); - final ByteBuffer v1 = ByteBuffer.allocateDirect(16); - final ByteBuffer k2 = ByteBuffer.allocateDirect(16); - final ByteBuffer v2 = ByteBuffer.allocateDirect(16); - k1.put("key1".getBytes()).flip(); - v1.put("value1".getBytes()).flip(); - k2.put("key2".getBytes()).flip(); - v2.put("value2".getBytes()).flip(); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - wbwi.put(k1, v1); - assertThat(k1.position()).isEqualTo(4); - assertThat(k1.limit()).isEqualTo(4); - assertThat(v1.position()).isEqualTo(6); - assertThat(v1.limit()).isEqualTo(6); - - wbwi.put(k2, v2); - - db.write(new WriteOptions(), wbwi); - } - - assertThat(db.get("key1".getBytes())).isEqualTo("value1".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo("value2".getBytes()); - } - } - - @Test - public void iterator() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true)) { - - final String k1 = "key1"; - final String v1 = "value1"; - final String k2 = "key2"; - final String v2 = "value2"; - final String k3 = "key3"; - final String v3 = "value3"; - final String k4 = "key4"; - final String k5 = "key5"; - final String v8 = "value8"; - final byte[] k1b = k1.getBytes(UTF_8); - final byte[] v1b = v1.getBytes(UTF_8); - final byte[] k2b = k2.getBytes(UTF_8); - final byte[] v2b = v2.getBytes(UTF_8); - final byte[] k3b = k3.getBytes(UTF_8); - final byte[] v3b = v3.getBytes(UTF_8); - final byte[] k4b = k4.getBytes(UTF_8); - final byte[] k5b = k5.getBytes(UTF_8); - final byte[] v8b = v8.getBytes(UTF_8); - - final String k1point5 = "key1point5"; - final String k2point5 = "key2point5"; - - // add put records - wbwi.put(k1b, v1b); - wbwi.put(k2b, v2b); - wbwi.put(k3b, v3b); - - // add a deletion record - wbwi.delete(k4b); - - // add a single deletion record - wbwi.singleDelete(k5b); - - // add a log record - wbwi.putLogData(v8b); - - final WBWIRocksIterator.WriteEntry[] expected = { - new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, - new DirectSlice(k1), new DirectSlice(v1)), - new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, - new DirectSlice(k2), new DirectSlice(v2)), - new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, - new DirectSlice(k3), new DirectSlice(v3)), - new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.DELETE, - new DirectSlice(k4), DirectSlice.NONE), - new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.SINGLE_DELETE, - new DirectSlice(k5), DirectSlice.NONE), - }; - - try (final WBWIRocksIterator it = wbwi.newIterator()) { - //direct access - seek to key offsets - final int[] testOffsets = {2, 0, 3, 4, 1}; - for (final int testOffset : testOffsets) { - final byte[] key = toArray(expected[testOffset].getKey().data()); - - it.seek(key); - assertThat(it.isValid()).isTrue(); - - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[testOffset]); - } - - for (final int testOffset : testOffsets) { - final byte[] key = toArray(expected[testOffset].getKey().data()); - - // Direct buffer seek - final ByteBuffer db = expected[testOffset].getKey().data(); - it.seek(db); - assertThat(db.position()).isEqualTo(key.length); - assertThat(it.isValid()).isTrue(); - - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[testOffset]); - } - - for (final int testOffset : testOffsets) { - final byte[] key = toArray(expected[testOffset].getKey().data()); - - // Direct buffer seek - final ByteBuffer db = expected[testOffset].getKey().data(); - it.seekForPrev(db); - assertThat(db.position()).isEqualTo(key.length); - assertThat(it.isValid()).isTrue(); - - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[testOffset]); - } - - for (final int testOffset : testOffsets) { - final byte[] key = toArray(expected[testOffset].getKey().data()); - - // Indirect buffer seek - final ByteBuffer db = ByteBuffer.allocate(key.length); - System.arraycopy(key, 0, db.array(), 0, key.length); - it.seek(db); - assertThat(db.position()).isEqualTo(key.length); - assertThat(it.isValid()).isTrue(); - - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[testOffset]); - } - - for (final int testOffset : testOffsets) { - final byte[] key = toArray(expected[testOffset].getKey().data()); - - // Indirect buffer seek for prev - final ByteBuffer db = ByteBuffer.allocate(key.length); - System.arraycopy(key, 0, db.array(), 0, key.length); - it.seekForPrev(db); - assertThat(db.position()).isEqualTo(key.length); - assertThat(it.isValid()).isTrue(); - - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[testOffset]); - } - - { - it.seekForPrev(k2point5.getBytes()); - assertThat(it.isValid()).isTrue(); - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[1]); - } - - { - it.seekForPrev(k1point5.getBytes()); - assertThat(it.isValid()).isTrue(); - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[0]); - } - - { - final ByteBuffer db = ByteBuffer.allocate(k2point5.length()); - db.put(k2point5.getBytes()); - db.flip(); - it.seekForPrev(db); - assertThat(it.isValid()).isTrue(); - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[1]); - } - - { - final ByteBuffer db = ByteBuffer.allocate(k1point5.length()); - db.put(k1point5.getBytes()); - db.flip(); - it.seekForPrev(db); - assertThat(it.isValid()).isTrue(); - final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry).isEqualTo(expected[0]); - } - - //forward iterative access - int i = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - assertThat(it.entry()).isEqualTo(expected[i++]); - } - - //reverse iterative access - i = expected.length - 1; - for (it.seekToLast(); it.isValid(); it.prev()) { - assertThat(it.entry()).isEqualTo(expected[i--]); - } - } - } - } - - @Test - public void zeroByteTests() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true)) { - final byte[] zeroByteValue = new byte[]{0, 0}; - //add zero byte value - wbwi.put(zeroByteValue, zeroByteValue); - - final ByteBuffer buffer = ByteBuffer.allocateDirect(zeroByteValue.length); - buffer.put(zeroByteValue); - - final WBWIRocksIterator.WriteEntry expected = - new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, - new DirectSlice(buffer, zeroByteValue.length), - new DirectSlice(buffer, zeroByteValue.length)); - - try (final WBWIRocksIterator it = wbwi.newIterator()) { - it.seekToFirst(); - final WBWIRocksIterator.WriteEntry actual = it.entry(); - assertThat(actual.equals(expected)).isTrue(); - assertThat(it.entry().hashCode() == expected.hashCode()).isTrue(); - } - } - } - - @Test - public void savePoints() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final ReadOptions readOptions = new ReadOptions()) { - wbwi.put("k1".getBytes(), "v1".getBytes()); - wbwi.put("k2".getBytes(), "v2".getBytes()); - wbwi.put("k3".getBytes(), "v3".getBytes()); - - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k1")) - .isEqualTo("v1"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k2")) - .isEqualTo("v2"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k3")) - .isEqualTo("v3"); - - - wbwi.setSavePoint(); - - wbwi.delete("k2".getBytes()); - wbwi.put("k3".getBytes(), "v3-2".getBytes()); - - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k2")) - .isNull(); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k3")) - .isEqualTo("v3-2"); - - - wbwi.setSavePoint(); - - wbwi.put("k3".getBytes(), "v3-3".getBytes()); - wbwi.put("k4".getBytes(), "v4".getBytes()); - - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k3")) - .isEqualTo("v3-3"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k4")) - .isEqualTo("v4"); - - - wbwi.rollbackToSavePoint(); - - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k2")) - .isNull(); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k3")) - .isEqualTo("v3-2"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k4")) - .isNull(); - - - wbwi.rollbackToSavePoint(); - - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k1")) - .isEqualTo("v1"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k2")) - .isEqualTo("v2"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k3")) - .isEqualTo("v3"); - assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k4")) - .isNull(); - } - } - } - - @Test - public void restorePoints() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - - wbwi.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); - wbwi.put("k2".getBytes(UTF_8), "v2".getBytes(UTF_8)); - - wbwi.setSavePoint(); - - wbwi.put("k1".getBytes(UTF_8), "123456789".getBytes(UTF_8)); - wbwi.delete("k2".getBytes(UTF_8)); - - wbwi.rollbackToSavePoint(); - - try(final DBOptions options = new DBOptions()) { - assertThat(wbwi.getFromBatch(options,"k1".getBytes(UTF_8))).isEqualTo("v1".getBytes()); - assertThat(wbwi.getFromBatch(options,"k2".getBytes(UTF_8))).isEqualTo("v2".getBytes()); - } - } - } - - @Test(expected = RocksDBException.class) - public void restorePoints_withoutSavePoints() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - wbwi.rollbackToSavePoint(); - } - } - - @Test(expected = RocksDBException.class) - public void restorePoints_withoutSavePoints_nested() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - - wbwi.setSavePoint(); - wbwi.rollbackToSavePoint(); - - // without previous corresponding setSavePoint - wbwi.rollbackToSavePoint(); - } - } - - @Test - public void popSavePoint() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - - wbwi.put("k1".getBytes(), "v1".getBytes()); - wbwi.put("k2".getBytes(), "v2".getBytes()); - - wbwi.setSavePoint(); - - wbwi.put("k1".getBytes(), "123456789".getBytes()); - wbwi.delete("k2".getBytes()); - - wbwi.setSavePoint(); - - wbwi.popSavePoint(); - - wbwi.rollbackToSavePoint(); - - try(final DBOptions options = new DBOptions()) { - assertThat(wbwi.getFromBatch(options,"k1".getBytes(UTF_8))).isEqualTo("v1".getBytes()); - assertThat(wbwi.getFromBatch(options,"k2".getBytes(UTF_8))).isEqualTo("v2".getBytes()); - } - } - } - - @Test(expected = RocksDBException.class) - public void popSavePoint_withoutSavePoints() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - wbwi.popSavePoint(); - } - } - - @Test(expected = RocksDBException.class) - public void popSavePoint_withoutSavePoints_nested() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - - wbwi.setSavePoint(); - wbwi.popSavePoint(); - - // without previous corresponding setSavePoint - wbwi.popSavePoint(); - } - } - - @Test - public void maxBytes() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - wbwi.setMaxBytes(19); - - wbwi.put("k1".getBytes(), "v1".getBytes()); - } - } - - @Test(expected = RocksDBException.class) - public void maxBytes_over() throws RocksDBException { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - wbwi.setMaxBytes(1); - - wbwi.put("k1".getBytes(), "v1".getBytes()); - } - } - - @Test - public void getWriteBatch() { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { - - final WriteBatch wb = wbwi.getWriteBatch(); - assertThat(wb).isNotNull(); - assertThat(wb.isOwningHandle()).isFalse(); - } - } - - private static String getFromWriteBatchWithIndex(final RocksDB db, - final ReadOptions readOptions, final WriteBatchWithIndex wbwi, - final String skey) { - final byte[] key = skey.getBytes(); - try (final RocksIterator baseIterator = db.newIterator(readOptions); - final RocksIterator iterator = wbwi.newIteratorWithBase(baseIterator)) { - iterator.seek(key); - - // Arrays.equals(key, iterator.key()) ensures an exact match in Rocks, - // instead of a nearest match - return iterator.isValid() && - Arrays.equals(key, iterator.key()) ? - new String(iterator.value()) : null; - } - } - - @Test - public void getFromBatch() throws RocksDBException { - final byte[] k1 = "k1".getBytes(); - final byte[] k2 = "k2".getBytes(); - final byte[] k3 = "k3".getBytes(); - final byte[] k4 = "k4".getBytes(); - - final byte[] v1 = "v1".getBytes(); - final byte[] v2 = "v2".getBytes(); - final byte[] v3 = "v3".getBytes(); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final DBOptions dbOptions = new DBOptions()) { - wbwi.put(k1, v1); - wbwi.put(k2, v2); - wbwi.put(k3, v3); - - assertThat(wbwi.getFromBatch(dbOptions, k1)).isEqualTo(v1); - assertThat(wbwi.getFromBatch(dbOptions, k2)).isEqualTo(v2); - assertThat(wbwi.getFromBatch(dbOptions, k3)).isEqualTo(v3); - assertThat(wbwi.getFromBatch(dbOptions, k4)).isNull(); - - wbwi.delete(k2); - - assertThat(wbwi.getFromBatch(dbOptions, k2)).isNull(); - } - } - - @Test - public void getFromBatchAndDB() throws RocksDBException { - final byte[] k1 = "k1".getBytes(); - final byte[] k2 = "k2".getBytes(); - final byte[] k3 = "k3".getBytes(); - final byte[] k4 = "k4".getBytes(); - - final byte[] v1 = "v1".getBytes(); - final byte[] v2 = "v2".getBytes(); - final byte[] v3 = "v3".getBytes(); - final byte[] v4 = "v4".getBytes(); - - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - - db.put(k1, v1); - db.put(k2, v2); - db.put(k4, v4); - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final DBOptions dbOptions = new DBOptions(); - final ReadOptions readOptions = new ReadOptions()) { - - assertThat(wbwi.getFromBatch(dbOptions, k1)).isNull(); - assertThat(wbwi.getFromBatch(dbOptions, k2)).isNull(); - assertThat(wbwi.getFromBatch(dbOptions, k4)).isNull(); - - wbwi.put(k3, v3); - - assertThat(wbwi.getFromBatch(dbOptions, k3)).isEqualTo(v3); - - assertThat(wbwi.getFromBatchAndDB(db, readOptions, k1)).isEqualTo(v1); - assertThat(wbwi.getFromBatchAndDB(db, readOptions, k2)).isEqualTo(v2); - assertThat(wbwi.getFromBatchAndDB(db, readOptions, k3)).isEqualTo(v3); - assertThat(wbwi.getFromBatchAndDB(db, readOptions, k4)).isEqualTo(v4); - - wbwi.delete(k4); - - assertThat(wbwi.getFromBatchAndDB(db, readOptions, k4)).isNull(); - } - } - } - private byte[] toArray(final ByteBuffer buf) { - final byte[] ary = new byte[buf.remaining()]; - buf.get(ary); - return ary; - } - - @Test - public void deleteRange() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteBatch batch = new WriteBatch(); - final WriteOptions wOpt = new WriteOptions()) { - db.put("key1".getBytes(), "value".getBytes()); - db.put("key2".getBytes(), "12345678".getBytes()); - db.put("key3".getBytes(), "abcdefg".getBytes()); - db.put("key4".getBytes(), "xyz".getBytes()); - assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); - assertThat(db.get("key2".getBytes())).isEqualTo("12345678".getBytes()); - assertThat(db.get("key3".getBytes())).isEqualTo("abcdefg".getBytes()); - assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - - batch.deleteRange("key2".getBytes(), "key4".getBytes()); - db.write(wOpt, batch); - - assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); - assertThat(db.get("key2".getBytes())).isNull(); - assertThat(db.get("key3".getBytes())).isNull(); - assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - } - } - - @Test - public void iteratorWithBaseOverwriteTrue() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final RocksIterator baseIter = db.newIterator(); - final RocksIterator wbwiIter = wbwi.newIteratorWithBase(baseIter)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final RocksIterator baseIter = db.newIterator(); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator wbwiIter = wbwi.newIteratorWithBase(baseIter, readOptions)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - } - - final List cfNames = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfNames, columnFamilyHandleList)) { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final RocksIterator baseIter = db.newIterator(); - final RocksIterator wbwiIter = - wbwi.newIteratorWithBase(columnFamilyHandleList.get(1), baseIter)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); - final RocksIterator baseIter = db.newIterator(); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator wbwiIter = - wbwi.newIteratorWithBase(columnFamilyHandleList.get(1), baseIter, readOptions)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - } - } - - @Test - public void iteratorWithBaseOverwriteFalse() throws RocksDBException { - try (final Options options = new Options().setCreateIfMissing(true); - final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(false); - final RocksIterator baseIter = db.newIterator(); - final RocksIterator wbwiIter = wbwi.newIteratorWithBase(baseIter)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(false); - final RocksIterator baseIter = db.newIterator(); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator wbwiIter = wbwi.newIteratorWithBase(baseIter, readOptions)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - } - - final List cfNames = - Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes())); - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = - new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open( - options, dbFolder.getRoot().getAbsolutePath(), cfNames, columnFamilyHandleList)) { - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(false); - final RocksIterator baseIter = db.newIterator(); - final RocksIterator wbwiIter = - wbwi.newIteratorWithBase(columnFamilyHandleList.get(1), baseIter)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(false); - final RocksIterator baseIter = db.newIterator(); - final ReadOptions readOptions = new ReadOptions(); - final RocksIterator wbwiIter = - wbwi.newIteratorWithBase(columnFamilyHandleList.get(1), baseIter, readOptions)) { - assertThat(wbwiIter).isNotNull(); - assertThat(wbwiIter.nativeHandle_).isGreaterThan(0); - wbwiIter.status(); - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/WriteOptionsTest.java b/java/src/test/java/org/rocksdb/WriteOptionsTest.java deleted file mode 100644 index 735677cb7..000000000 --- a/java/src/test/java/org/rocksdb/WriteOptionsTest.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Random; -import org.junit.ClassRule; -import org.junit.Test; - -public class WriteOptionsTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - public static final Random rand = PlatformRandomHelper. - getPlatformSpecificRandomFactory(); - - @Test - public void writeOptions() { - try (final WriteOptions writeOptions = new WriteOptions()) { - - writeOptions.setSync(true); - assertThat(writeOptions.sync()).isTrue(); - writeOptions.setSync(false); - assertThat(writeOptions.sync()).isFalse(); - - writeOptions.setDisableWAL(true); - assertThat(writeOptions.disableWAL()).isTrue(); - writeOptions.setDisableWAL(false); - assertThat(writeOptions.disableWAL()).isFalse(); - - - writeOptions.setIgnoreMissingColumnFamilies(true); - assertThat(writeOptions.ignoreMissingColumnFamilies()).isTrue(); - writeOptions.setIgnoreMissingColumnFamilies(false); - assertThat(writeOptions.ignoreMissingColumnFamilies()).isFalse(); - - writeOptions.setNoSlowdown(true); - assertThat(writeOptions.noSlowdown()).isTrue(); - writeOptions.setNoSlowdown(false); - assertThat(writeOptions.noSlowdown()).isFalse(); - - writeOptions.setLowPri(true); - assertThat(writeOptions.lowPri()).isTrue(); - writeOptions.setLowPri(false); - assertThat(writeOptions.lowPri()).isFalse(); - - writeOptions.setMemtableInsertHintPerBatch(true); - assertThat(writeOptions.memtableInsertHintPerBatch()).isTrue(); - writeOptions.setMemtableInsertHintPerBatch(false); - assertThat(writeOptions.memtableInsertHintPerBatch()).isFalse(); - } - } - - @Test - public void copyConstructor() { - WriteOptions origOpts = new WriteOptions(); - origOpts.setDisableWAL(rand.nextBoolean()); - origOpts.setIgnoreMissingColumnFamilies(rand.nextBoolean()); - origOpts.setSync(rand.nextBoolean()); - origOpts.setMemtableInsertHintPerBatch(true); - WriteOptions copyOpts = new WriteOptions(origOpts); - assertThat(origOpts.disableWAL()).isEqualTo(copyOpts.disableWAL()); - assertThat(origOpts.ignoreMissingColumnFamilies()).isEqualTo( - copyOpts.ignoreMissingColumnFamilies()); - assertThat(origOpts.sync()).isEqualTo(copyOpts.sync()); - assertThat(origOpts.memtableInsertHintPerBatch()) - .isEqualTo(copyOpts.memtableInsertHintPerBatch()); - } -} diff --git a/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java b/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java deleted file mode 100644 index c4e4f25a0..000000000 --- a/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb.test; - -import org.rocksdb.AbstractCompactionFilter; -import org.rocksdb.AbstractCompactionFilterFactory; -import org.rocksdb.RemoveEmptyValueCompactionFilter; - -/** - * Simple CompactionFilterFactory class used in tests. Generates RemoveEmptyValueCompactionFilters. - */ -public class RemoveEmptyValueCompactionFilterFactory extends AbstractCompactionFilterFactory { - @Override - public RemoveEmptyValueCompactionFilter createCompactionFilter(final AbstractCompactionFilter.Context context) { - return new RemoveEmptyValueCompactionFilter(); - } - - @Override - public String name() { - return "RemoveEmptyValueCompactionFilterFactory"; - } -} diff --git a/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java b/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java deleted file mode 100644 index 42d3148ef..000000000 --- a/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb.test; - -import org.junit.internal.JUnitSystem; -import org.junit.internal.RealSystem; -import org.junit.internal.TextListener; -import org.junit.runner.Description; -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; -import org.junit.runner.notification.Failure; -import org.rocksdb.RocksDB; - -import java.io.PrintStream; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; - -import static org.rocksdb.test.RocksJunitRunner.RocksJunitListener.Status.*; - -/** - * Custom Junit Runner to print also Test classes - * and executed methods to command prompt. - */ -public class RocksJunitRunner { - - /** - * Listener which overrides default functionality - * to print class and method to system out. - */ - static class RocksJunitListener extends TextListener { - - private final static NumberFormat secsFormat = - new DecimalFormat("###,###.###"); - - private final PrintStream writer; - - private String currentClassName = null; - private String currentMethodName = null; - private Status currentStatus = null; - private long currentTestsStartTime; - private int currentTestsCount = 0; - private int currentTestsIgnoredCount = 0; - private int currentTestsFailureCount = 0; - private int currentTestsErrorCount = 0; - - enum Status { - IGNORED, - FAILURE, - ERROR, - OK - } - - /** - * RocksJunitListener constructor - * - * @param system JUnitSystem - */ - public RocksJunitListener(final JUnitSystem system) { - this(system.out()); - } - - public RocksJunitListener(final PrintStream writer) { - super(writer); - this.writer = writer; - } - - @Override - public void testRunStarted(final Description description) { - writer.format("Starting RocksJava Tests...%n"); - - } - - @Override - public void testStarted(final Description description) { - if(currentClassName == null - || !currentClassName.equals(description.getClassName())) { - if(currentClassName != null) { - printTestsSummary(); - } else { - currentTestsStartTime = System.currentTimeMillis(); - } - writer.format("%nRunning: %s%n", description.getClassName()); - currentClassName = description.getClassName(); - } - currentMethodName = description.getMethodName(); - currentStatus = OK; - currentTestsCount++; - } - - private void printTestsSummary() { - // print summary of last test set - writer.format("Tests run: %d, Failures: %d, Errors: %d, Ignored: %d, Time elapsed: %s sec%n", - currentTestsCount, - currentTestsFailureCount, - currentTestsErrorCount, - currentTestsIgnoredCount, - formatSecs(System.currentTimeMillis() - currentTestsStartTime)); - - // reset counters - currentTestsCount = 0; - currentTestsFailureCount = 0; - currentTestsErrorCount = 0; - currentTestsIgnoredCount = 0; - currentTestsStartTime = System.currentTimeMillis(); - } - - private static String formatSecs(final double milliseconds) { - final double seconds = milliseconds / 1000; - return secsFormat.format(seconds); - } - - @Override - public void testFailure(final Failure failure) { - if (failure.getException() != null - && failure.getException() instanceof AssertionError) { - currentStatus = FAILURE; - currentTestsFailureCount++; - } else { - currentStatus = ERROR; - currentTestsErrorCount++; - } - } - - @Override - public void testIgnored(final Description description) { - currentStatus = IGNORED; - currentTestsIgnoredCount++; - } - - @Override - public void testFinished(final Description description) { - if(currentStatus == OK) { - writer.format("\t%s OK%n",currentMethodName); - } else { - writer.format(" [%s] %s%n", currentStatus.name(), currentMethodName); - } - } - - @Override - public void testRunFinished(final Result result) { - printTestsSummary(); - super.testRunFinished(result); - } - } - - /** - * Main method to execute tests - * - * @param args Test classes as String names - */ - public static void main(final String[] args){ - final JUnitCore runner = new JUnitCore(); - final JUnitSystem system = new RealSystem(); - runner.addListener(new RocksJunitListener(system)); - try { - final List> classes = new ArrayList<>(); - for (final String arg : args) { - classes.add(Class.forName(arg)); - } - final Class[] clazzes = classes.toArray(new Class[classes.size()]); - final Result result = runner.run(clazzes); - if(!result.wasSuccessful()) { - System.exit(-1); - } - } catch (final ClassNotFoundException e) { - e.printStackTrace(); - System.exit(-2); - } - } -} diff --git a/java/src/test/java/org/rocksdb/test/TestableEventListener.java b/java/src/test/java/org/rocksdb/test/TestableEventListener.java deleted file mode 100644 index 865ad5cf7..000000000 --- a/java/src/test/java/org/rocksdb/test/TestableEventListener.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb.test; - -import org.rocksdb.AbstractEventListener; - -public class TestableEventListener extends AbstractEventListener { - public TestableEventListener() { - super(); - } - - public TestableEventListener(final EnabledEventCallback... enabledEventCallbacks) { - super(enabledEventCallbacks); - } - - public void invokeAllCallbacks() { - invokeAllCallbacks(nativeHandle_); - } - - private static native void invokeAllCallbacks(final long handle); -} diff --git a/java/src/test/java/org/rocksdb/util/ByteBufferAllocator.java b/java/src/test/java/org/rocksdb/util/ByteBufferAllocator.java deleted file mode 100644 index 8d7956cf2..000000000 --- a/java/src/test/java/org/rocksdb/util/ByteBufferAllocator.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import java.nio.ByteBuffer; - -public interface ByteBufferAllocator { - ByteBuffer allocate(int capacity); - - ByteBufferAllocator DIRECT = new DirectByteBufferAllocator(); - ByteBufferAllocator HEAP = new HeapByteBufferAllocator(); -} diff --git a/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java b/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java deleted file mode 100644 index fb7239c92..000000000 --- a/java/src/test/java/org/rocksdb/util/BytewiseComparatorIntTest.java +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import org.rocksdb.*; - -import java.nio.ByteBuffer; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Similar to {@link IntComparatorTest}, but uses {@link BytewiseComparator} - * which ensures the correct ordering of positive integers. - */ -@RunWith(Parameterized.class) -public class BytewiseComparatorIntTest { - - // test with 500 random positive integer keys - private static final int TOTAL_KEYS = 500; - private static final byte[][] keys = new byte[TOTAL_KEYS][4]; - - @BeforeClass - public static void prepareKeys() { - final ByteBuffer buf = ByteBuffer.allocate(4); - final Random random = new Random(); - for (int i = 0; i < TOTAL_KEYS; i++) { - final int ri = random.nextInt() & Integer.MAX_VALUE; // the & ensures positive integer - buf.putInt(ri); - buf.flip(); - final byte[] key = buf.array(); - - // does key already exist (avoid duplicates) - if (keyExists(key, i)) { - i--; // loop round and generate a different key - } else { - System.arraycopy(key, 0, keys[i], 0, 4); - } - } - } - - private static boolean keyExists(final byte[] key, final int limit) { - for (int j = 0; j < limit; j++) { - if (Arrays.equals(key, keys[j])) { - return true; - } - } - return false; - } - - @Parameters(name = "{0}") - public static Iterable parameters() { - return Arrays.asList(new Object[][] { - { "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX }, - { "direct_reused64_mutex", true, 64, ReusedSynchronisationType.MUTEX }, - { "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, - { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, - { "non-direct_reused64_thread-local", false, 64, ReusedSynchronisationType.THREAD_LOCAL }, - { "direct_reused64_thread-local", true, 64, ReusedSynchronisationType.THREAD_LOCAL }, - { "non-direct_noreuse", false, -1, null }, - { "direct_noreuse", true, -1, null } - }); - } - - @Parameter(0) - public String name; - - @Parameter(1) - public boolean useDirectBuffer; - - @Parameter(2) - public int maxReusedBufferSize; - - @Parameter(3) - public ReusedSynchronisationType reusedSynchronisationType; - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - - @Test - public void javaComparatorDefaultCf() throws RocksDBException { - try (final ComparatorOptions options = new ComparatorOptions() - .setUseDirectBuffer(useDirectBuffer) - .setMaxReusedBufferSize(maxReusedBufferSize) - // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used - .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); - final BytewiseComparator comparator = new BytewiseComparator(options)) { - - // test the round-tripability of keys written and read with the Comparator - testRoundtrip(FileSystems.getDefault().getPath( - dbFolder.getRoot().getAbsolutePath()), comparator); - } - } - - @Test - public void javaComparatorNamedCf() throws RocksDBException { - try (final ComparatorOptions options = new ComparatorOptions() - .setUseDirectBuffer(useDirectBuffer) - .setMaxReusedBufferSize(maxReusedBufferSize) - // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used - .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); - final BytewiseComparator comparator = new BytewiseComparator(options)) { - - // test the round-tripability of keys written and read with the Comparator - testRoundtripCf(FileSystems.getDefault().getPath( - dbFolder.getRoot().getAbsolutePath()), comparator); - } - } - - /** - * Test which stores random keys into the database - * using an {@link IntComparator} - * it then checks that these keys are read back in - * ascending order - * - * @param db_path A path where we can store database - * files temporarily - * - * @param comparator the comparator - * - * @throws RocksDBException if a database error happens. - */ - private void testRoundtrip(final Path db_path, - final AbstractComparator comparator) throws RocksDBException { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setComparator(comparator)) { - - // store TOTAL_KEYS into the db - try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { - for (int i = 0; i < TOTAL_KEYS; i++) { - db.put(keys[i], "value".getBytes(UTF_8)); - } - } - - // re-open db and read from start to end - // integer keys should be in ascending - // order as defined by IntComparator - final ByteBuffer key = ByteBuffer.allocate(4); - try (final RocksDB db = RocksDB.open(opt, db_path.toString()); - final RocksIterator it = db.newIterator()) { - it.seekToFirst(); - int lastKey = Integer.MIN_VALUE; - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - key.put(it.key()); - key.flip(); - final int thisKey = key.getInt(); - key.clear(); - assertThat(thisKey).isGreaterThan(lastKey); - lastKey = thisKey; - count++; - } - assertThat(count).isEqualTo(TOTAL_KEYS); - } - } - } - - /** - * Test which stores random keys into a column family - * in the database - * using an {@link IntComparator} - * it then checks that these keys are read back in - * ascending order - * - * @param db_path A path where we can store database - * files temporarily - * - * @param comparator the comparator - * - * @throws RocksDBException if a database error happens. - */ - private void testRoundtripCf(final Path db_path, - final AbstractComparator comparator) throws RocksDBException { - - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), - new ColumnFamilyOptions() - .setComparator(comparator)) - ); - - final List cfHandles = new ArrayList<>(); - - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true)) { - - try (final RocksDB db = RocksDB.open(opt, db_path.toString(), - cfDescriptors, cfHandles)) { - try { - assertThat(cfDescriptors.size()).isEqualTo(2); - assertThat(cfHandles.size()).isEqualTo(2); - - for (int i = 0; i < TOTAL_KEYS; i++) { - db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8)); - } - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - } - } - - // re-open db and read from start to end - // integer keys should be in ascending - // order as defined by SimpleIntComparator - final ByteBuffer key = ByteBuffer.allocate(4); - try (final RocksDB db = RocksDB.open(opt, db_path.toString(), - cfDescriptors, cfHandles); - final RocksIterator it = db.newIterator(cfHandles.get(1))) { - try { - assertThat(cfDescriptors.size()).isEqualTo(2); - assertThat(cfHandles.size()).isEqualTo(2); - - it.seekToFirst(); - int lastKey = Integer.MIN_VALUE; - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - key.put(it.key()); - key.flip(); - final int thisKey = key.getInt(); - key.clear(); - assertThat(thisKey).isGreaterThan(lastKey); - lastKey = thisKey; - count++; - } - - assertThat(count).isEqualTo(TOTAL_KEYS); - - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) { - cfDescriptor.getOptions().close(); - } - } - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java b/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java deleted file mode 100644 index 69f2c282b..000000000 --- a/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java +++ /dev/null @@ -1,531 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.rocksdb.*; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.*; -import java.util.*; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.*; -import static org.rocksdb.util.ByteUtil.bytes; - -/** - * This is a direct port of various C++ - * tests from db/comparator_db_test.cc - * and some code to adapt it to RocksJava - */ -public class BytewiseComparatorTest { - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - private List source_strings = Arrays.asList("b", "d", "f", "h", "j", "l"); - private List interleaving_strings = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"); - - /** - * Open the database using the C++ BytewiseComparatorImpl - * and test the results against our Java BytewiseComparator - */ - @Test - public void java_vs_cpp_bytewiseComparator() - throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { - final Path dbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - try(final RocksDB db = openDatabase(dbDir, - BuiltinComparator.BYTEWISE_COMPARATOR)) { - - final Random rnd = new Random(rand_seed); - try(final ComparatorOptions copt2 = new ComparatorOptions() - .setUseDirectBuffer(false); - final AbstractComparator comparator2 = new BytewiseComparator(copt2)) { - final java.util.Comparator jComparator = toJavaComparator(comparator2); - doRandomIterationTest( - db, - jComparator, - rnd, - 8, 100, 3 - ); - } - } - } - } - - /** - * Open the database using the Java BytewiseComparator - * and test the results against another Java BytewiseComparator - */ - @Test - public void java_vs_java_bytewiseComparator() - throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { - final Path dbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - try(final ComparatorOptions copt = new ComparatorOptions() - .setUseDirectBuffer(false); - final AbstractComparator comparator = new BytewiseComparator(copt); - final RocksDB db = openDatabase(dbDir, comparator)) { - - final Random rnd = new Random(rand_seed); - try(final ComparatorOptions copt2 = new ComparatorOptions() - .setUseDirectBuffer(false); - final AbstractComparator comparator2 = new BytewiseComparator(copt2)) { - final java.util.Comparator jComparator = toJavaComparator(comparator2); - doRandomIterationTest( - db, - jComparator, - rnd, - 8, 100, 3 - ); - } - } - } - } - - /** - * Open the database using the C++ BytewiseComparatorImpl - * and test the results against our Java DirectBytewiseComparator - */ - @Test - public void java_vs_cpp_directBytewiseComparator() - throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { - final Path dbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - try(final RocksDB db = openDatabase(dbDir, - BuiltinComparator.BYTEWISE_COMPARATOR)) { - - final Random rnd = new Random(rand_seed); - try(final ComparatorOptions copt2 = new ComparatorOptions() - .setUseDirectBuffer(true); - final AbstractComparator comparator2 = new BytewiseComparator(copt2)) { - final java.util.Comparator jComparator = toJavaComparator(comparator2); - doRandomIterationTest( - db, - jComparator, - rnd, - 8, 100, 3 - ); - } - } - } - } - - /** - * Open the database using the Java DirectBytewiseComparator - * and test the results against another Java DirectBytewiseComparator - */ - @Test - public void java_vs_java_directBytewiseComparator() - throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { - final Path dbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - try (final ComparatorOptions copt = new ComparatorOptions() - .setUseDirectBuffer(true); - final AbstractComparator comparator = new BytewiseComparator(copt); - final RocksDB db = openDatabase(dbDir, comparator)) { - - final Random rnd = new Random(rand_seed); - try(final ComparatorOptions copt2 = new ComparatorOptions() - .setUseDirectBuffer(true); - final AbstractComparator comparator2 = new BytewiseComparator(copt2)) { - final java.util.Comparator jComparator = toJavaComparator(comparator2); - doRandomIterationTest( - db, - jComparator, - rnd, - 8, 100, 3 - ); - } - } - } - } - - /** - * Open the database using the C++ ReverseBytewiseComparatorImpl - * and test the results against our Java ReverseBytewiseComparator - */ - @Test - public void java_vs_cpp_reverseBytewiseComparator() - throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { - final Path dbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - try(final RocksDB db = openDatabase(dbDir, - BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR)) { - - final Random rnd = new Random(rand_seed); - try(final ComparatorOptions copt2 = new ComparatorOptions() - .setUseDirectBuffer(false); - final AbstractComparator comparator2 = new ReverseBytewiseComparator(copt2)) { - final java.util.Comparator jComparator = toJavaComparator(comparator2); - doRandomIterationTest( - db, - jComparator, - rnd, - 8, 100, 3 - ); - } - } - } - } - - /** - * Open the database using the Java ReverseBytewiseComparator - * and test the results against another Java ReverseBytewiseComparator - */ - @Test - public void java_vs_java_reverseBytewiseComparator() - throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { - final Path dbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - try (final ComparatorOptions copt = new ComparatorOptions() - .setUseDirectBuffer(false); - final AbstractComparator comparator = new ReverseBytewiseComparator(copt); - final RocksDB db = openDatabase(dbDir, comparator)) { - - final Random rnd = new Random(rand_seed); - try(final ComparatorOptions copt2 = new ComparatorOptions() - .setUseDirectBuffer(false); - final AbstractComparator comparator2 = new ReverseBytewiseComparator(copt2)) { - final java.util.Comparator jComparator = toJavaComparator(comparator2); - doRandomIterationTest( - db, - jComparator, - rnd, - 8, 100, 3 - ); - } - } - } - } - - private void doRandomIterationTest( - final RocksDB db, final java.util.Comparator javaComparator, - final Random rnd, - final int num_writes, final int num_iter_ops, - final int num_trigger_flush) throws RocksDBException { - - final TreeMap map = new TreeMap<>(javaComparator); - - try (final FlushOptions flushOptions = new FlushOptions(); - final WriteOptions writeOptions = new WriteOptions()) { - for (int i = 0; i < num_writes; i++) { - if (num_trigger_flush > 0 && i != 0 && i % num_trigger_flush == 0) { - db.flush(flushOptions); - } - - final int type = rnd.nextInt(2); - final int index = rnd.nextInt(source_strings.size()); - final String key = source_strings.get(index); - switch (type) { - case 0: - // put - map.put(key, key); - db.put(writeOptions, bytes(key), bytes(key)); - break; - case 1: - // delete - if (map.containsKey(key)) { - map.remove(key); - } - db.delete(writeOptions, bytes(key)); - break; - - default: - fail("Should not be able to generate random outside range 1..2"); - } - } - } - - try (final ReadOptions readOptions = new ReadOptions(); - final RocksIterator iter = db.newIterator(readOptions)) { - final KVIter result_iter = new KVIter<>(map); - - boolean is_valid = false; - for (int i = 0; i < num_iter_ops; i++) { - // Random walk and make sure iter and result_iter returns the - // same key and value - final int type = rnd.nextInt(8); - iter.status(); - switch (type) { - case 0: - // Seek to First - iter.seekToFirst(); - result_iter.seekToFirst(); - break; - case 1: - // Seek to last - iter.seekToLast(); - result_iter.seekToLast(); - break; - case 2: { - // Seek to random (existing or non-existing) key - final int key_idx = rnd.nextInt(interleaving_strings.size()); - final String key = interleaving_strings.get(key_idx); - iter.seek(bytes(key)); - result_iter.seek(bytes(key)); - break; - } - case 3: { - // SeekForPrev to random (existing or non-existing) key - final int key_idx = rnd.nextInt(interleaving_strings.size()); - final String key = interleaving_strings.get(key_idx); - iter.seekForPrev(bytes(key)); - result_iter.seekForPrev(bytes(key)); - break; - } - case 4: - // Next - if (is_valid) { - iter.next(); - result_iter.next(); - } else { - continue; - } - break; - case 5: - // Prev - if (is_valid) { - iter.prev(); - result_iter.prev(); - } else { - continue; - } - break; - case 6: - // Refresh - iter.refresh(); - result_iter.refresh(); - iter.seekToFirst(); - result_iter.seekToFirst(); - break; - default: { - assert (type == 7); - final int key_idx = rnd.nextInt(source_strings.size()); - final String key = source_strings.get(key_idx); - final byte[] result = db.get(readOptions, bytes(key)); - if (!map.containsKey(key)) { - assertNull(result); - } else { - assertArrayEquals(bytes(map.get(key)), result); - } - break; - } - } - - assertEquals(result_iter.isValid(), iter.isValid()); - - is_valid = iter.isValid(); - - if (is_valid) { - assertArrayEquals(bytes(result_iter.key()), iter.key()); - - //note that calling value on a non-valid iterator from the Java API - //results in a SIGSEGV - assertArrayEquals(bytes(result_iter.value()), iter.value()); - } - } - } - } - - /** - * Open the database using a C++ Comparator - */ - private RocksDB openDatabase( - final Path dbDir, final BuiltinComparator cppComparator) - throws IOException, RocksDBException { - final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(cppComparator); - return RocksDB.open(options, dbDir.toAbsolutePath().toString()); - } - - /** - * Open the database using a Java Comparator - */ - private RocksDB openDatabase( - final Path dbDir, - final AbstractComparator javaComparator) - throws IOException, RocksDBException { - final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(javaComparator); - return RocksDB.open(options, dbDir.toAbsolutePath().toString()); - } - - private java.util.Comparator toJavaComparator( - final AbstractComparator rocksComparator) { - return new java.util.Comparator() { - @Override - public int compare(final String s1, final String s2) { - final ByteBuffer bufS1; - final ByteBuffer bufS2; - if (rocksComparator.usingDirectBuffers()) { - bufS1 = ByteBuffer.allocateDirect(s1.length()); - bufS2 = ByteBuffer.allocateDirect(s2.length()); - } else { - bufS1 = ByteBuffer.allocate(s1.length()); - bufS2 = ByteBuffer.allocate(s2.length()); - } - bufS1.put(bytes(s1)); - bufS1.flip(); - bufS2.put(bytes(s2)); - bufS2.flip(); - return rocksComparator.compare(bufS1, bufS2); - } - }; - } - - private static class KVIter implements RocksIteratorInterface { - - private final List> entries; - private final java.util.Comparator comparator; - private int offset = -1; - - private int lastPrefixMatchIdx = -1; - private int lastPrefixMatch = 0; - - public KVIter(final TreeMap map) { - this.entries = new ArrayList<>(); - entries.addAll(map.entrySet()); - this.comparator = map.comparator(); - } - - - @Override - public boolean isValid() { - return offset > -1 && offset < entries.size(); - } - - @Override - public void seekToFirst() { - offset = 0; - } - - @Override - public void seekToLast() { - offset = entries.size() - 1; - } - - @SuppressWarnings("unchecked") - @Override - public void seek(final byte[] target) { - for(offset = 0; offset < entries.size(); offset++) { - if(comparator.compare(entries.get(offset).getKey(), - (K)new String(target, UTF_8)) >= 0) { - return; - } - } - } - - @SuppressWarnings("unchecked") - @Override - public void seekForPrev(final byte[] target) { - for(offset = entries.size()-1; offset >= 0; offset--) { - if(comparator.compare(entries.get(offset).getKey(), - (K)new String(target, UTF_8)) <= 0) { - return; - } - } - } - - /** - * Is `a` a prefix of `b` - * - * @return The length of the matching prefix, or 0 if it is not a prefix - */ - private int isPrefix(final byte[] a, final byte[] b) { - if(b.length >= a.length) { - for(int i = 0; i < a.length; i++) { - if(a[i] != b[i]) { - return i; - } - } - return a.length; - } else { - return 0; - } - } - - @Override - public void next() { - if(offset < entries.size()) { - offset++; - } - } - - @Override - public void prev() { - if(offset >= 0) { - offset--; - } - } - - @Override - public void refresh() throws RocksDBException { - offset = -1; - } - - @Override - public void status() throws RocksDBException { - if(offset < 0 || offset >= entries.size()) { - throw new RocksDBException("Index out of bounds. Size is: " + - entries.size() + ", offset is: " + offset); - } - } - - @SuppressWarnings("unchecked") - public K key() { - if(!isValid()) { - if(entries.isEmpty()) { - return (K)""; - } else if(offset == -1){ - return entries.get(0).getKey(); - } else if(offset == entries.size()) { - return entries.get(offset - 1).getKey(); - } else { - return (K)""; - } - } else { - return entries.get(offset).getKey(); - } - } - - @SuppressWarnings("unchecked") - public V value() { - if(!isValid()) { - return (V)""; - } else { - return entries.get(offset).getValue(); - } - } - - @Override - public void seek(ByteBuffer target) { - throw new IllegalAccessError("Not implemented"); - } - - @Override - public void seekForPrev(ByteBuffer target) { - throw new IllegalAccessError("Not implemented"); - } - } -} diff --git a/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java b/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java deleted file mode 100644 index 8ea104332..000000000 --- a/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb.util; - -import org.rocksdb.RocksDBException; -import org.rocksdb.WriteBatch; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -/** - * A simple WriteBatch Handler which adds a record - * of each event that it receives to a list - */ -public class CapturingWriteBatchHandler extends WriteBatch.Handler { - - private final List events = new ArrayList<>(); - - /** - * Returns a copy of the current events list - * - * @return a list of the events which have happened upto now - */ - public List getEvents() { - return new ArrayList<>(events); - } - - @Override - public void put(final int columnFamilyId, final byte[] key, - final byte[] value) { - events.add(new Event(Action.PUT, columnFamilyId, key, value)); - } - - @Override - public void put(final byte[] key, final byte[] value) { - events.add(new Event(Action.PUT, key, value)); - } - - @Override - public void merge(final int columnFamilyId, final byte[] key, - final byte[] value) { - events.add(new Event(Action.MERGE, columnFamilyId, key, value)); - } - - @Override - public void merge(final byte[] key, final byte[] value) { - events.add(new Event(Action.MERGE, key, value)); - } - - @Override - public void delete(final int columnFamilyId, final byte[] key) { - events.add(new Event(Action.DELETE, columnFamilyId, key, (byte[])null)); - } - - @Override - public void delete(final byte[] key) { - events.add(new Event(Action.DELETE, key, (byte[])null)); - } - - @Override - public void singleDelete(final int columnFamilyId, final byte[] key) { - events.add(new Event(Action.SINGLE_DELETE, - columnFamilyId, key, (byte[])null)); - } - - @Override - public void singleDelete(final byte[] key) { - events.add(new Event(Action.SINGLE_DELETE, key, (byte[])null)); - } - - @Override - public void deleteRange(final int columnFamilyId, final byte[] beginKey, - final byte[] endKey) { - events.add(new Event(Action.DELETE_RANGE, columnFamilyId, beginKey, - endKey)); - } - - @Override - public void deleteRange(final byte[] beginKey, final byte[] endKey) { - events.add(new Event(Action.DELETE_RANGE, beginKey, endKey)); - } - - @Override - public void logData(final byte[] blob) { - events.add(new Event(Action.LOG, (byte[])null, blob)); - } - - @Override - public void putBlobIndex(final int columnFamilyId, final byte[] key, - final byte[] value) { - events.add(new Event(Action.PUT_BLOB_INDEX, key, value)); - } - - @Override - public void markBeginPrepare() throws RocksDBException { - events.add(new Event(Action.MARK_BEGIN_PREPARE, (byte[])null, - (byte[])null)); - } - - @Override - public void markEndPrepare(final byte[] xid) throws RocksDBException { - events.add(new Event(Action.MARK_END_PREPARE, (byte[])null, - (byte[])null)); - } - - @Override - public void markNoop(final boolean emptyBatch) throws RocksDBException { - events.add(new Event(Action.MARK_NOOP, (byte[])null, (byte[])null)); - } - - @Override - public void markRollback(final byte[] xid) throws RocksDBException { - events.add(new Event(Action.MARK_ROLLBACK, (byte[])null, (byte[])null)); - } - - @Override - public void markCommit(final byte[] xid) throws RocksDBException { - events.add(new Event(Action.MARK_COMMIT, (byte[])null, (byte[])null)); - } - - @Override - public void markCommitWithTimestamp(final byte[] xid, final byte[] ts) throws RocksDBException { - events.add(new Event(Action.MARK_COMMIT_WITH_TIMESTAMP, (byte[]) null, (byte[]) null)); - } - - public static class Event { - public final Action action; - public final int columnFamilyId; - public final byte[] key; - public final byte[] value; - - public Event(final Action action, final byte[] key, final byte[] value) { - this(action, 0, key, value); - } - - public Event(final Action action, final int columnFamilyId, final byte[] key, - final byte[] value) { - this.action = action; - this.columnFamilyId = columnFamilyId; - this.key = key; - this.value = value; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final Event event = (Event) o; - return columnFamilyId == event.columnFamilyId && - action == event.action && - ((key == null && event.key == null) - || Arrays.equals(key, event.key)) && - ((value == null && event.value == null) - || Arrays.equals(value, event.value)); - } - - @Override - public int hashCode() { - int result = Objects.hash(action, columnFamilyId); - result = 31 * result + Arrays.hashCode(key); - result = 31 * result + Arrays.hashCode(value); - return result; - } - } - - /** - * Enumeration of Write Batch - * event actions - */ - public enum Action { - PUT, - MERGE, - DELETE, - SINGLE_DELETE, - DELETE_RANGE, - LOG, - PUT_BLOB_INDEX, - MARK_BEGIN_PREPARE, - MARK_END_PREPARE, - MARK_NOOP, - MARK_COMMIT, - MARK_ROLLBACK, - MARK_COMMIT_WITH_TIMESTAMP - } -} diff --git a/java/src/test/java/org/rocksdb/util/DirectByteBufferAllocator.java b/java/src/test/java/org/rocksdb/util/DirectByteBufferAllocator.java deleted file mode 100644 index d26fb578b..000000000 --- a/java/src/test/java/org/rocksdb/util/DirectByteBufferAllocator.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import java.nio.ByteBuffer; - -public final class DirectByteBufferAllocator implements ByteBufferAllocator { - DirectByteBufferAllocator(){}; - - @Override - public ByteBuffer allocate(final int capacity) { - return ByteBuffer.allocateDirect(capacity); - } -} diff --git a/java/src/test/java/org/rocksdb/util/EnvironmentTest.java b/java/src/test/java/org/rocksdb/util/EnvironmentTest.java deleted file mode 100644 index ae340e06d..000000000 --- a/java/src/test/java/org/rocksdb/util/EnvironmentTest.java +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) 2014, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb.util; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.is; - -import java.lang.reflect.Field; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -public class EnvironmentTest { - private final static String ARCH_FIELD_NAME = "ARCH"; - private final static String OS_FIELD_NAME = "OS"; - - private final static String MUSL_ENVIRONMENT_FIELD_NAME = "MUSL_ENVIRONMENT"; - private final static String MUSL_LIBC_FIELD_NAME = "MUSL_LIBC"; - - private static String INITIAL_OS; - private static String INITIAL_ARCH; - private static String INITIAL_MUSL_ENVIRONMENT; - private static Boolean INITIAL_MUSL_LIBC; - - @BeforeClass - public static void saveState() { - INITIAL_ARCH = getEnvironmentClassField(ARCH_FIELD_NAME); - INITIAL_OS = getEnvironmentClassField(OS_FIELD_NAME); - INITIAL_MUSL_LIBC = getEnvironmentClassField(MUSL_LIBC_FIELD_NAME); - INITIAL_MUSL_ENVIRONMENT = getEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME); - } - - @Test - public void mac32() { - setEnvironmentClassFields("mac", "32"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".jnilib"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-osx.jnilib"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.dylib"); - } - - @Test - public void mac64_x86_64() { - setEnvironmentClassFields("mac", "x86_64"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".jnilib"); - assertThat(Environment.getJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-osx-x86_64.jnilib"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-osx.jnilib"); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.dylib"); - } - - @Test - public void macAarch64() { - setEnvironmentClassFields("mac", "aarch64"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()).isEqualTo(".jnilib"); - assertThat(Environment.getJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-osx-arm64.jnilib"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-osx.jnilib"); - assertThat(Environment.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.dylib"); - } - - @Test - public void nix32() { - // Linux - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - setEnvironmentClassFields("Linux", "32"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-linux32.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - // Linux musl-libc (Alpine) - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, true); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-linux32-musl.so"); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - // UNIX - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - setEnvironmentClassFields("Unix", "32"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-linux32.so"); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - } - - @Test(expected = UnsupportedOperationException.class) - public void aix32() { - // AIX - setEnvironmentClassFields("aix", "32"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")).isEqualTo("blah"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - } - - @Test - public void nix64() { - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - setEnvironmentClassFields("Linux", "x64"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-linux64.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - // Linux musl-libc (Alpine) - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, true); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-linux64-musl.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - // UNIX - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - setEnvironmentClassFields("Unix", "x64"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-linux64.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - // AIX - setEnvironmentClassFields("aix", "x64"); - assertThat(Environment.isWindows()).isFalse(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".so"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-aix64.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.so"); - } - - @Test - public void detectWindows(){ - setEnvironmentClassFields("win", "x64"); - assertThat(Environment.isWindows()).isTrue(); - } - - @Test - public void win64() { - setEnvironmentClassFields("win", "x64"); - assertThat(Environment.isWindows()).isTrue(); - assertThat(Environment.getJniLibraryExtension()). - isEqualTo(".dll"); - assertThat(Environment.getJniLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni-win64.dll"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")). - isEqualTo("librocksdbjni.dll"); - } - - @Test - public void ppc64le() { - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - setEnvironmentClassFields("Linux", "ppc64le"); - assertThat(Environment.isUnix()).isTrue(); - assertThat(Environment.isPowerPC()).isTrue(); - assertThat(Environment.is64Bit()).isTrue(); - assertThat(Environment.getJniLibraryExtension()).isEqualTo(".so"); - assertThat(Environment.getSharedLibraryName("rocksdb")).isEqualTo("rocksdbjni"); - assertThat(Environment.getJniLibraryName("rocksdb")).isEqualTo("rocksdbjni-linux-ppc64le"); - assertThat(Environment.getJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-linux-ppc64le.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.so"); - // Linux musl-libc (Alpine) - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, true); - setEnvironmentClassFields("Linux", "ppc64le"); - assertThat(Environment.isUnix()).isTrue(); - assertThat(Environment.isPowerPC()).isTrue(); - assertThat(Environment.is64Bit()).isTrue(); - assertThat(Environment.getJniLibraryExtension()).isEqualTo(".so"); - assertThat(Environment.getSharedLibraryName("rocksdb")).isEqualTo("rocksdbjni"); - assertThat(Environment.getJniLibraryName("rocksdb")).isEqualTo("rocksdbjni-linux-ppc64le-musl"); - assertThat(Environment.getJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-linux-ppc64le-musl.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.so"); - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - } - - @Test - public void linuxArch64() { - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - setEnvironmentClassFields("Linux", "aarch64"); - assertThat(Environment.isUnix()).isTrue(); - assertThat(Environment.isAarch64()).isTrue(); - assertThat(Environment.is64Bit()).isTrue(); - assertThat(Environment.getJniLibraryExtension()).isEqualTo(".so"); - assertThat(Environment.getSharedLibraryName("rocksdb")).isEqualTo("rocksdbjni"); - assertThat(Environment.getJniLibraryName("rocksdb")).isEqualTo("rocksdbjni-linux-aarch64"); - assertThat(Environment.getJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-linux-aarch64.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.so"); - // Linux musl-libc (Alpine) - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, true); - setEnvironmentClassFields("Linux", "aarch64"); - assertThat(Environment.isUnix()).isTrue(); - assertThat(Environment.isAarch64()).isTrue(); - assertThat(Environment.is64Bit()).isTrue(); - assertThat(Environment.getJniLibraryExtension()).isEqualTo(".so"); - assertThat(Environment.getSharedLibraryName("rocksdb")).isEqualTo("rocksdbjni"); - assertThat(Environment.getJniLibraryName("rocksdb")).isEqualTo("rocksdbjni-linux-aarch64-musl"); - assertThat(Environment.getJniLibraryFileName("rocksdb")) - .isEqualTo("librocksdbjni-linux-aarch64-musl.so"); - assertThat(Environment.getFallbackJniLibraryFileName("rocksdb")).isNull(); - assertThat(Environment.getSharedLibraryFileName("rocksdb")).isEqualTo("librocksdbjni.so"); - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, false); - } - - @Test - public void resolveIsMuslLibc() { - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, null); - setEnvironmentClassFields("win", "anyarch"); - assertThat(Environment.isUnix()).isFalse(); - - // with user input, will resolve to true if set as true. Even on OSs that appear absurd for - // musl. Users choice - assertThat(Environment.initIsMuslLibc()).isFalse(); - setEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME, "true"); - assertThat(Environment.initIsMuslLibc()).isTrue(); - setEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME, "false"); - assertThat(Environment.initIsMuslLibc()).isFalse(); - } - - private void setEnvironmentClassFields(String osName, - String osArch) { - setEnvironmentClassField(OS_FIELD_NAME, osName); - setEnvironmentClassField(ARCH_FIELD_NAME, osArch); - } - - @AfterClass - public static void restoreState() { - setEnvironmentClassField(OS_FIELD_NAME, INITIAL_OS); - setEnvironmentClassField(ARCH_FIELD_NAME, INITIAL_ARCH); - setEnvironmentClassField(MUSL_ENVIRONMENT_FIELD_NAME, INITIAL_MUSL_ENVIRONMENT); - setEnvironmentClassField(MUSL_LIBC_FIELD_NAME, INITIAL_MUSL_LIBC); - } - - @SuppressWarnings("unchecked") - private static T getEnvironmentClassField(String fieldName) { - final Field field; - try { - field = Environment.class.getDeclaredField(fieldName); - field.setAccessible(true); - /* Fails in JDK 13; and not needed unless fields are final - final Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - */ - return (T)field.get(null); - } catch (final NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - private static void setEnvironmentClassField(String fieldName, Object value) { - final Field field; - try { - field = Environment.class.getDeclaredField(fieldName); - field.setAccessible(true); - /* Fails in JDK 13; and not needed unless fields are final - final Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - */ - field.set(null, value); - } catch (final NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } -} diff --git a/java/src/test/java/org/rocksdb/util/HeapByteBufferAllocator.java b/java/src/test/java/org/rocksdb/util/HeapByteBufferAllocator.java deleted file mode 100644 index ad6b8f6f4..000000000 --- a/java/src/test/java/org/rocksdb/util/HeapByteBufferAllocator.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import java.nio.ByteBuffer; - -public final class HeapByteBufferAllocator implements ByteBufferAllocator { - HeapByteBufferAllocator(){}; - - @Override - public ByteBuffer allocate(final int capacity) { - return ByteBuffer.allocate(capacity); - } -} diff --git a/java/src/test/java/org/rocksdb/util/IntComparatorTest.java b/java/src/test/java/org/rocksdb/util/IntComparatorTest.java deleted file mode 100644 index dd3288513..000000000 --- a/java/src/test/java/org/rocksdb/util/IntComparatorTest.java +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import org.rocksdb.*; - -import java.nio.ByteBuffer; -import java.nio.file.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for IntComparator, but more generally - * also for rocksdb::ComparatorJniCallback implementation. - */ -@RunWith(Parameterized.class) -public class IntComparatorTest { - - // test with 500 random integer keys - private static final int TOTAL_KEYS = 500; - private static final byte[][] keys = new byte[TOTAL_KEYS][4]; - - @BeforeClass - public static void prepareKeys() { - final ByteBuffer buf = ByteBuffer.allocate(4); - final Random random = new Random(); - for (int i = 0; i < TOTAL_KEYS; i++) { - final int ri = random.nextInt(); - buf.putInt(ri); - buf.flip(); - final byte[] key = buf.array(); - - // does key already exist (avoid duplicates) - if (keyExists(key, i)) { - i--; // loop round and generate a different key - } else { - System.arraycopy(key, 0, keys[i], 0, 4); - } - } - } - - private static boolean keyExists(final byte[] key, final int limit) { - for (int j = 0; j < limit; j++) { - if (Arrays.equals(key, keys[j])) { - return true; - } - } - return false; - } - - @Parameters(name = "{0}") - public static Iterable parameters() { - return Arrays.asList(new Object[][] { - { "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX }, - { "direct_reused64_mutex", true, 64, ReusedSynchronisationType.MUTEX }, - { "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, - { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, - { "non-direct_reused64_thread-local", false, 64, ReusedSynchronisationType.THREAD_LOCAL }, - { "direct_reused64_thread-local", true, 64, ReusedSynchronisationType.THREAD_LOCAL }, - { "non-direct_noreuse", false, -1, null }, - { "direct_noreuse", true, -1, null } - }); - } - - @Parameter(0) - public String name; - - @Parameter(1) - public boolean useDirectBuffer; - - @Parameter(2) - public int maxReusedBufferSize; - - @Parameter(3) - public ReusedSynchronisationType reusedSynchronisationType; - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - - @Test - public void javaComparatorDefaultCf() throws RocksDBException { - try (final ComparatorOptions options = new ComparatorOptions() - .setUseDirectBuffer(useDirectBuffer) - .setMaxReusedBufferSize(maxReusedBufferSize) - // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used - .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); - final IntComparator comparator = new IntComparator(options)) { - - // test the round-tripability of keys written and read with the Comparator - testRoundtrip(FileSystems.getDefault().getPath( - dbFolder.getRoot().getAbsolutePath()), comparator); - } - } - - @Test - public void javaComparatorNamedCf() throws RocksDBException { - try (final ComparatorOptions options = new ComparatorOptions() - .setUseDirectBuffer(useDirectBuffer) - .setMaxReusedBufferSize(maxReusedBufferSize) - // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used - .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); - final IntComparator comparator = new IntComparator(options)) { - - // test the round-tripability of keys written and read with the Comparator - testRoundtripCf(FileSystems.getDefault().getPath( - dbFolder.getRoot().getAbsolutePath()), comparator); - } - } - - /** - * Test which stores random keys into the database - * using an {@link IntComparator} - * it then checks that these keys are read back in - * ascending order - * - * @param db_path A path where we can store database - * files temporarily - * - * @param comparator the comparator - * - * @throws RocksDBException if a database error happens. - */ - private void testRoundtrip(final Path db_path, - final AbstractComparator comparator) throws RocksDBException { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setComparator(comparator)) { - - // store TOTAL_KEYS into the db - try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { - for (int i = 0; i < TOTAL_KEYS; i++) { - db.put(keys[i], "value".getBytes(UTF_8)); - } - } - - // re-open db and read from start to end - // integer keys should be in ascending - // order as defined by IntComparator - final ByteBuffer key = ByteBuffer.allocate(4); - try (final RocksDB db = RocksDB.open(opt, db_path.toString()); - final RocksIterator it = db.newIterator()) { - it.seekToFirst(); - int lastKey = Integer.MIN_VALUE; - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - key.put(it.key()); - key.flip(); - final int thisKey = key.getInt(); - key.clear(); - assertThat(thisKey).isGreaterThan(lastKey); - lastKey = thisKey; - count++; - } - assertThat(count).isEqualTo(TOTAL_KEYS); - } - } - } - - /** - * Test which stores random keys into a column family - * in the database - * using an {@link IntComparator} - * it then checks that these keys are read back in - * ascending order - * - * @param db_path A path where we can store database - * files temporarily - * - * @param comparator the comparator - * - * @throws RocksDBException if a database error happens. - */ - private void testRoundtripCf(final Path db_path, - final AbstractComparator comparator) throws RocksDBException { - - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), - new ColumnFamilyOptions() - .setComparator(comparator)) - ); - - final List cfHandles = new ArrayList<>(); - - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true)) { - - try (final RocksDB db = RocksDB.open(opt, db_path.toString(), - cfDescriptors, cfHandles)) { - try { - assertThat(cfDescriptors.size()).isEqualTo(2); - assertThat(cfHandles.size()).isEqualTo(2); - - for (int i = 0; i < TOTAL_KEYS; i++) { - db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8)); - } - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - } - } - - // re-open db and read from start to end - // integer keys should be in ascending - // order as defined by SimpleIntComparator - final ByteBuffer key = ByteBuffer.allocate(4); - try (final RocksDB db = RocksDB.open(opt, db_path.toString(), - cfDescriptors, cfHandles); - final RocksIterator it = db.newIterator(cfHandles.get(1))) { - try { - assertThat(cfDescriptors.size()).isEqualTo(2); - assertThat(cfHandles.size()).isEqualTo(2); - - it.seekToFirst(); - int lastKey = Integer.MIN_VALUE; - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - key.put(it.key()); - key.flip(); - final int thisKey = key.getInt(); - key.clear(); - assertThat(thisKey).isGreaterThan(lastKey); - lastKey = thisKey; - count++; - } - - assertThat(count).isEqualTo(TOTAL_KEYS); - - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) { - cfDescriptor.getOptions().close(); - } - } - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java b/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java deleted file mode 100644 index a962b8d78..000000000 --- a/java/src/test/java/org/rocksdb/util/JNIComparatorTest.java +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. -// -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import org.rocksdb.*; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.*; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(Parameterized.class) -public class JNIComparatorTest { - - @Parameters(name = "{0}") - public static Iterable parameters() { - return Arrays.asList(new Object[][] { - { "bytewise_non-direct", BuiltinComparator.BYTEWISE_COMPARATOR, false }, - { "bytewise_direct", BuiltinComparator.BYTEWISE_COMPARATOR, true }, - { "reverse-bytewise_non-direct", BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR, false }, - { "reverse-bytewise_direct", BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR, true }, - }); - } - - @Parameter(0) - public String name; - - @Parameter(1) - public BuiltinComparator builtinComparator; - - @Parameter(2) - public boolean useDirectBuffer; - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - private static final int MIN = Short.MIN_VALUE - 1; - private static final int MAX = Short.MAX_VALUE + 1; - - @Test - public void java_comparator_equals_cpp_comparator() throws RocksDBException, IOException { - final int[] javaKeys; - try (final ComparatorOptions comparatorOptions = new ComparatorOptions(); - final AbstractComparator comparator = builtinComparator == BuiltinComparator.BYTEWISE_COMPARATOR - ? new BytewiseComparator(comparatorOptions) - : new ReverseBytewiseComparator(comparatorOptions)) { - final Path javaDbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - storeWithJavaComparator(javaDbDir, comparator); - javaKeys = readAllWithJavaComparator(javaDbDir, comparator); - } - - final Path cppDbDir = - FileSystems.getDefault().getPath(dbFolder.newFolder().getAbsolutePath()); - storeWithCppComparator(cppDbDir, builtinComparator); - final int[] cppKeys = - readAllWithCppComparator(cppDbDir, builtinComparator); - - assertThat(javaKeys).isEqualTo(cppKeys); - } - - private void storeWithJavaComparator(final Path dir, - final AbstractComparator comparator) throws RocksDBException { - final ByteBuffer buf = ByteBuffer.allocate(4); - try (final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(comparator); - final RocksDB db = - RocksDB.open(options, dir.toAbsolutePath().toString())) { - for (int i = MIN; i < MAX; i++) { - buf.putInt(i); - buf.flip(); - - db.put(buf.array(), buf.array()); - - buf.clear(); - } - } - } - - private void storeWithCppComparator(final Path dir, - final BuiltinComparator builtinComparator) throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(builtinComparator); - final RocksDB db = - RocksDB.open(options, dir.toAbsolutePath().toString())) { - - final ByteBuffer buf = ByteBuffer.allocate(4); - for (int i = MIN; i < MAX; i++) { - buf.putInt(i); - buf.flip(); - - db.put(buf.array(), buf.array()); - - buf.clear(); - } - } - } - - private int[] readAllWithJavaComparator(final Path dir, - final AbstractComparator comparator) throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(comparator); - final RocksDB db = - RocksDB.open(options, dir.toAbsolutePath().toString())) { - - try (final RocksIterator it = db.newIterator()) { - it.seekToFirst(); - - final ByteBuffer buf = ByteBuffer.allocate(4); - final int[] keys = new int[MAX - MIN]; - int idx = 0; - while (it.isValid()) { - buf.put(it.key()); - buf.flip(); - - final int thisKey = buf.getInt(); - keys[idx++] = thisKey; - - buf.clear(); - - it.next(); - } - - return keys; - } - } - } - - private int[] readAllWithCppComparator(final Path dir, - final BuiltinComparator comparator) throws RocksDBException { - try (final Options options = new Options() - .setCreateIfMissing(true) - .setComparator(comparator); - final RocksDB db = - RocksDB.open(options, dir.toAbsolutePath().toString())) { - - try (final RocksIterator it = db.newIterator()) { - it.seekToFirst(); - - final ByteBuffer buf = ByteBuffer.allocate(4); - final int[] keys = new int[MAX - MIN]; - int idx = 0; - while (it.isValid()) { - buf.put(it.key()); - buf.flip(); - - final int thisKey = buf.getInt(); - keys[idx++] = thisKey; - - buf.clear(); - - it.next(); - } - - return keys; - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java b/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java deleted file mode 100644 index ca08d9de1..000000000 --- a/java/src/test/java/org/rocksdb/util/ReverseBytewiseComparatorIntTest.java +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; -import org.rocksdb.*; - -import java.nio.ByteBuffer; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Similar to {@link IntComparatorTest}, but uses - * {@link ReverseBytewiseComparator} which ensures the correct reverse - * ordering of positive integers. - */ -@RunWith(Parameterized.class) -public class ReverseBytewiseComparatorIntTest { - - // test with 500 random positive integer keys - private static final int TOTAL_KEYS = 500; - private static final byte[][] keys = new byte[TOTAL_KEYS][4]; - - @BeforeClass - public static void prepareKeys() { - final ByteBuffer buf = ByteBuffer.allocate(4); - final Random random = new Random(); - for (int i = 0; i < TOTAL_KEYS; i++) { - final int ri = random.nextInt() & Integer.MAX_VALUE; // the & ensures positive integer - buf.putInt(ri); - buf.flip(); - final byte[] key = buf.array(); - - // does key already exist (avoid duplicates) - if (keyExists(key, i)) { - i--; // loop round and generate a different key - } else { - System.arraycopy(key, 0, keys[i], 0, 4); - } - } - } - - private static boolean keyExists(final byte[] key, final int limit) { - for (int j = 0; j < limit; j++) { - if (Arrays.equals(key, keys[j])) { - return true; - } - } - return false; - } - - @Parameters(name = "{0}") - public static Iterable parameters() { - return Arrays.asList(new Object[][] { - { "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX }, - { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.MUTEX }, - { "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, - { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX }, - { "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.THREAD_LOCAL }, - { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.THREAD_LOCAL }, - { "non-direct_noreuse", false, -1, null }, - { "direct_noreuse", true, -1, null } - }); - } - - @Parameter(0) - public String name; - - @Parameter(1) - public boolean useDirectBuffer; - - @Parameter(2) - public int maxReusedBufferSize; - - @Parameter(3) - public ReusedSynchronisationType reusedSynchronisationType; - - @ClassRule - public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = - new RocksNativeLibraryResource(); - - @Rule - public TemporaryFolder dbFolder = new TemporaryFolder(); - - - @Test - public void javaComparatorDefaultCf() throws RocksDBException { - try (final ComparatorOptions options = new ComparatorOptions() - .setUseDirectBuffer(useDirectBuffer) - .setMaxReusedBufferSize(maxReusedBufferSize) - // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used - .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); - final ReverseBytewiseComparator comparator = - new ReverseBytewiseComparator(options)) { - - // test the round-tripability of keys written and read with the Comparator - testRoundtrip(FileSystems.getDefault().getPath( - dbFolder.getRoot().getAbsolutePath()), comparator); - } - } - - @Test - public void javaComparatorNamedCf() throws RocksDBException { - try (final ComparatorOptions options = new ComparatorOptions() - .setUseDirectBuffer(useDirectBuffer) - .setMaxReusedBufferSize(maxReusedBufferSize) - // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used - .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType); - final ReverseBytewiseComparator comparator - = new ReverseBytewiseComparator(options)) { - - // test the round-tripability of keys written and read with the Comparator - testRoundtripCf(FileSystems.getDefault().getPath( - dbFolder.getRoot().getAbsolutePath()), comparator); - } - } - - /** - * Test which stores random keys into the database - * using an {@link IntComparator} - * it then checks that these keys are read back in - * ascending order - * - * @param db_path A path where we can store database - * files temporarily - * - * @param comparator the comparator - * - * @throws RocksDBException if a database error happens. - */ - private void testRoundtrip(final Path db_path, - final AbstractComparator comparator) throws RocksDBException { - try (final Options opt = new Options() - .setCreateIfMissing(true) - .setComparator(comparator)) { - - // store TOTAL_KEYS into the db - try (final RocksDB db = RocksDB.open(opt, db_path.toString())) { - for (int i = 0; i < TOTAL_KEYS; i++) { - db.put(keys[i], "value".getBytes(UTF_8)); - } - } - - // re-open db and read from start to end - // integer keys should be in descending - // order - final ByteBuffer key = ByteBuffer.allocate(4); - try (final RocksDB db = RocksDB.open(opt, db_path.toString()); - final RocksIterator it = db.newIterator()) { - it.seekToFirst(); - int lastKey = Integer.MAX_VALUE; - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - key.put(it.key()); - key.flip(); - final int thisKey = key.getInt(); - key.clear(); - assertThat(thisKey).isLessThan(lastKey); - lastKey = thisKey; - count++; - } - assertThat(count).isEqualTo(TOTAL_KEYS); - } - } - } - - /** - * Test which stores random keys into a column family - * in the database - * using an {@link IntComparator} - * it then checks that these keys are read back in - * ascending order - * - * @param db_path A path where we can store database - * files temporarily - * - * @param comparator the comparator - * - * @throws RocksDBException if a database error happens. - */ - private void testRoundtripCf(final Path db_path, - final AbstractComparator comparator) throws RocksDBException { - - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes(), - new ColumnFamilyOptions() - .setComparator(comparator)) - ); - - final List cfHandles = new ArrayList<>(); - - try (final DBOptions opt = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true)) { - - try (final RocksDB db = RocksDB.open(opt, db_path.toString(), - cfDescriptors, cfHandles)) { - try { - assertThat(cfDescriptors.size()).isEqualTo(2); - assertThat(cfHandles.size()).isEqualTo(2); - - for (int i = 0; i < TOTAL_KEYS; i++) { - db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8)); - } - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - } - } - - // re-open db and read from start to end - // integer keys should be in descending - // order - final ByteBuffer key = ByteBuffer.allocate(4); - try (final RocksDB db = RocksDB.open(opt, db_path.toString(), - cfDescriptors, cfHandles); - final RocksIterator it = db.newIterator(cfHandles.get(1))) { - try { - assertThat(cfDescriptors.size()).isEqualTo(2); - assertThat(cfHandles.size()).isEqualTo(2); - - it.seekToFirst(); - int lastKey = Integer.MAX_VALUE; - int count = 0; - for (it.seekToFirst(); it.isValid(); it.next()) { - key.put(it.key()); - key.flip(); - final int thisKey = key.getInt(); - key.clear(); - assertThat(thisKey).isLessThan(lastKey); - lastKey = thisKey; - count++; - } - - assertThat(count).isEqualTo(TOTAL_KEYS); - - } finally { - for (final ColumnFamilyHandle cfHandle : cfHandles) { - cfHandle.close(); - } - cfHandles.clear(); - for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) { - cfDescriptor.getOptions().close(); - } - } - } - } - } -} diff --git a/java/src/test/java/org/rocksdb/util/SizeUnitTest.java b/java/src/test/java/org/rocksdb/util/SizeUnitTest.java deleted file mode 100644 index 990aa5f47..000000000 --- a/java/src/test/java/org/rocksdb/util/SizeUnitTest.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -package org.rocksdb.util; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SizeUnitTest { - - public static final long COMPUTATION_UNIT = 1024L; - - @Test - public void sizeUnit() { - assertThat(SizeUnit.KB).isEqualTo(COMPUTATION_UNIT); - assertThat(SizeUnit.MB).isEqualTo( - SizeUnit.KB * COMPUTATION_UNIT); - assertThat(SizeUnit.GB).isEqualTo( - SizeUnit.MB * COMPUTATION_UNIT); - assertThat(SizeUnit.TB).isEqualTo( - SizeUnit.GB * COMPUTATION_UNIT); - assertThat(SizeUnit.PB).isEqualTo( - SizeUnit.TB * COMPUTATION_UNIT); - } -} diff --git a/java/src/test/java/org/rocksdb/util/TestUtil.java b/java/src/test/java/org/rocksdb/util/TestUtil.java deleted file mode 100644 index e4f490c8e..000000000 --- a/java/src/test/java/org/rocksdb/util/TestUtil.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -package org.rocksdb.util; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.nio.ByteBuffer; -import java.util.Random; -import org.rocksdb.CompactionPriority; -import org.rocksdb.Options; -import org.rocksdb.WALRecoveryMode; - -/** - * General test utilities. - */ -public class TestUtil { - - /** - * Get the options for log iteration tests. - * - * @return the options - */ - public static Options optionsForLogIterTest() { - return defaultOptions() - .setCreateIfMissing(true) - .setWalTtlSeconds(1000); - } - - /** - * Get the default options. - * - * @return the options - */ - public static Options defaultOptions() { - return new Options() - .setWriteBufferSize(4090 * 4096) - .setTargetFileSizeBase(2 * 1024 * 1024) - .setMaxBytesForLevelBase(10 * 1024 * 1024) - .setMaxOpenFiles(5000) - .setWalRecoveryMode(WALRecoveryMode.TolerateCorruptedTailRecords) - .setCompactionPriority(CompactionPriority.ByCompensatedSize); - } - - private static final Random random = new Random(); - - /** - * Generate a random string of bytes. - * - * @param len the length of the string to generate. - * - * @return the random string of bytes - */ - public static byte[] dummyString(final int len) { - final byte[] str = new byte[len]; - random.nextBytes(str); - return str; - } - - /** - * Copy a {@link ByteBuffer} into an array for shorthand ease of test coding - * @param byteBuffer the buffer to copy - * @return a {@link byte[]} containing the same bytes as the input - */ - public static byte[] bufferBytes(final ByteBuffer byteBuffer) { - final byte[] result = new byte[byteBuffer.limit()]; - byteBuffer.get(result); - return result; - } -} diff --git a/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java b/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java deleted file mode 100644 index 2efa16473..000000000 --- a/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -package org.rocksdb.util; - -import org.rocksdb.RocksDBException; -import org.rocksdb.WriteBatch; - -import java.util.Arrays; - -public class WriteBatchGetter extends WriteBatch.Handler { - - private int columnFamilyId = -1; - private final byte[] key; - private byte[] value; - - public WriteBatchGetter(final byte[] key) { - this.key = key; - } - - public byte[] getValue() { - return value; - } - - @Override - public void put(final int columnFamilyId, final byte[] key, - final byte[] value) { - if(Arrays.equals(this.key, key)) { - this.columnFamilyId = columnFamilyId; - this.value = value; - } - } - - @Override - public void put(final byte[] key, final byte[] value) { - if(Arrays.equals(this.key, key)) { - this.value = value; - } - } - - @Override - public void merge(final int columnFamilyId, final byte[] key, - final byte[] value) { - if(Arrays.equals(this.key, key)) { - this.columnFamilyId = columnFamilyId; - this.value = value; - } - } - - @Override - public void merge(final byte[] key, final byte[] value) { - if(Arrays.equals(this.key, key)) { - this.value = value; - } - } - - @Override - public void delete(final int columnFamilyId, final byte[] key) { - if(Arrays.equals(this.key, key)) { - this.columnFamilyId = columnFamilyId; - this.value = null; - } - } - - @Override - public void delete(final byte[] key) { - if(Arrays.equals(this.key, key)) { - this.value = null; - } - } - - @Override - public void singleDelete(final int columnFamilyId, final byte[] key) { - if(Arrays.equals(this.key, key)) { - this.columnFamilyId = columnFamilyId; - this.value = null; - } - } - - @Override - public void singleDelete(final byte[] key) { - if(Arrays.equals(this.key, key)) { - this.value = null; - } - } - - @Override - public void deleteRange(final int columnFamilyId, final byte[] beginKey, - final byte[] endKey) { - throw new UnsupportedOperationException(); - } - - @Override - public void deleteRange(final byte[] beginKey, final byte[] endKey) { - throw new UnsupportedOperationException(); - } - - @Override - public void logData(final byte[] blob) { - throw new UnsupportedOperationException(); - } - - @Override - public void putBlobIndex(final int columnFamilyId, final byte[] key, - final byte[] value) { - if(Arrays.equals(this.key, key)) { - this.columnFamilyId = columnFamilyId; - this.value = value; - } - } - - @Override - public void markBeginPrepare() throws RocksDBException { - throw new UnsupportedOperationException(); - } - - @Override - public void markEndPrepare(final byte[] xid) throws RocksDBException { - throw new UnsupportedOperationException(); - } - - @Override - public void markNoop(final boolean emptyBatch) throws RocksDBException { - throw new UnsupportedOperationException(); - } - - @Override - public void markRollback(final byte[] xid) throws RocksDBException { - throw new UnsupportedOperationException(); - } - - @Override - public void markCommit(final byte[] xid) throws RocksDBException { - throw new UnsupportedOperationException(); - } - - @Override - public void markCommitWithTimestamp(final byte[] xid, final byte[] ts) throws RocksDBException { - throw new UnsupportedOperationException(); - } -} diff --git a/java/understanding_options.md b/java/understanding_options.md deleted file mode 100644 index 0393aff4d..000000000 --- a/java/understanding_options.md +++ /dev/null @@ -1,79 +0,0 @@ -# How RocksDB Options and their Java Wrappers Work - -Options in RocksDB come in many different flavours. This is an attempt at a taxonomy and explanation. - -## RocksDB Options - -Initially, I believe, RocksDB had only database options. I don't know if any of these were mutable. Column families came later. Read on to understand the terminology. - -So to begin, one sets up a collection of options and starts/creates a database with these options. That's a useful way to think about it, because from a Java point-of-view (and I didn't realise this initially and got very confused), despite making native calls to C++, the `API`s are just manipulating a native C++ configuration object. This object is just a record of configuration, and it must later be passed to the database (at create or open time) in order to apply the options. - -### Database versus Column Family - -The concept of the *column family* or `CF` is widespread within RocksDB. I think of it as a data namespace, but conveniently transactions can operate across these namespaces. The concept of a default column family exists, and when operations do not refer to a particular `CF`, it refers to the default. - -We raise this w.r.t. options because many options, perhaps most that users encounter, are *column family options*. That is to say they apply individually to a particular column family, or to the default column family. Crucially also, many/most/all of these same options are exposed as *database options* and then apply as the default for column families which do not have the option set explicitly. Obviously some database options are naturally database-wide; they apply to the operation of the database and don't make any sense applied to a column family. - -### Mutability - -There are 2 kinds of options - -- Mutable options -- Immutable options. We name these in contrast to the mutable ones, but they are usually referred to unqualified. - -Mutable options are those which can be changed on a running `RocksDB` instance. Immutable options can only be configured prior to the start of a database. Of course, we can configure the immutable options at this time too; The entirety of options is a strict superset of the mutable options. - -Mutable options (whether column-family specific or database-wide) are manipulated at runtime with builders, so we have `MutableDBOptions.MutableDBOptionsBuilder` and `MutableColumnFamilyOptions.MutableColumnFamilyOptionsBuilder` which share tooling classes/hierarchy and maintain and manipulate the relevant options as a `(key,value)` map. - -Mutable options are then passed using `setOptions()` and `setDBOptions()` methods on the live RocksDB, and then take effect immediately (depending on the semantics of the option) on the database. - -### Advanced - -There are 2 classes of options - -- Advanced options -- Non-advanced options - -It's not clear to me what the conceptual distinction is between advanced and not. However, the Java code takes care to reflect it from the underlying C++. - -This leads to 2 separate type hierarchies within column family options, one for each `class` of options. The `kind`s are represented by where the options appear in their hierarchy. - -```java -interface ColumnFamilyOptionsInterface> - extends AdvancedColumnFamilyOptionsInterface -interface MutableColumnFamilyOptionsInterface> - extends AdvancedMutableColumnFamilyOptionsInterface -``` - -And then there is ultimately a single concrete implementation class for CF options: - -```java -class ColumnFamilyOptions extends RocksObject - implements ColumnFamilyOptionsInterface, - MutableColumnFamilyOptionsInterface -``` - -as there is a single concrete implementation class for DB options: - -```java -class DBOptions extends RocksObject - implements DBOptionsInterface, - MutableDBOptionsInterface -``` - -Interestingly `DBOptionsInterface` doesn't extend `MutableDBOptionsInterface`, if only in order to disrupt our belief in consistent basic laws of the Universe. - -## Startup/Creation Options - -```java -class Options extends RocksObject - implements DBOptionsInterface, - MutableDBOptionsInterface, - ColumnFamilyOptionsInterface, - MutableColumnFamilyOptionsInterface -``` - -### Example - Blob Options - -The `enable_blob_files` and `min_blob_size` options are per-column-family, and are mutable. The options also appear in the unqualified database options. So by initial configuration, we can set up a RocksDB database where for every `(key,value)` with a value of size at least `min_blob_size`, the value is written (indirected) to a blob file. Blobs may share a blob file, subject to the configuration values set. Later, using the `MutableColumnFamilyOptionsInterface` of the `ColumnFamilyOptions`, we can choose to turn this off (`enable_blob_files=false`) , or alter the `min_blob_size` for the default column family, or any other column family. It seems to me that we cannot, though, mutate the column family options for all column families using the -`setOptions()` mechanism, either for all existing column families or for all future column families; but maybe we can do the latter on a re-`open()/create()' diff --git a/logging/auto_roll_logger_test.cc b/logging/auto_roll_logger_test.cc deleted file mode 100644 index 3d0ec1763..000000000 --- a/logging/auto_roll_logger_test.cc +++ /dev/null @@ -1,731 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// - - -#include "logging/auto_roll_logger.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "db/db_test_util.h" -#include "env/emulated_clock.h" -#include "logging/env_logger.h" -#include "logging/logging.h" -#include "port/port.h" -#include "rocksdb/db.h" -#include "rocksdb/file_system.h" -#include "rocksdb/system_clock.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -// In this test we only want to Log some simple log message with -// no format. LogMessage() provides such a simple interface and -// avoids the [format-security] warning which occurs when you -// call ROCKS_LOG_INFO(logger, log_message) directly. -namespace { -void LogMessage(Logger* logger, const char* message) { - ROCKS_LOG_INFO(logger, "%s", message); -} - -void LogMessage(const InfoLogLevel log_level, Logger* logger, - const char* message) { - Log(log_level, logger, "%s", message); -} -} // namespace - -class AutoRollLoggerTest : public testing::Test { - public: - static void InitTestDb() { - // TODO replace the `system` calls with Env/FileSystem APIs. -#ifdef OS_WIN - // Replace all slashes in the path so windows CompSpec does not - // become confused - std::string testDbDir(kTestDbDir); - std::replace_if( - testDbDir.begin(), testDbDir.end(), [](char ch) { return ch == '/'; }, - '\\'); - std::string deleteDbDirCmd = - "if exist " + testDbDir + " rd /s /q " + testDbDir; - ASSERT_TRUE(system(deleteDbDirCmd.c_str()) == 0); - - std::string testDir(kTestDir); - std::replace_if( - testDir.begin(), testDir.end(), [](char ch) { return ch == '/'; }, - '\\'); - std::string deleteCmd = "if exist " + testDir + " rd /s /q " + testDir; -#else - std::string deleteCmd = "rm -rf " + kTestDir + " " + kTestDbDir; -#endif - ASSERT_TRUE(system(deleteCmd.c_str()) == 0); - ASSERT_OK(Env::Default()->CreateDir(kTestDir)); - ASSERT_OK(Env::Default()->CreateDir(kTestDbDir)); - } - - void RollLogFileBySizeTest(AutoRollLogger* logger, size_t log_max_size, - const std::string& log_message); - void RollLogFileByTimeTest(const std::shared_ptr& fs, - const std::shared_ptr& sc, - AutoRollLogger* logger, size_t time, - const std::string& log_message); - // return list of files under kTestDir that contains "LOG" - std::vector GetLogFiles() { - std::vector ret; - std::vector files; - Status s = default_env->GetChildren(kTestDir, &files); - // Should call ASSERT_OK() here but it doesn't compile. It's not - // worth the time figuring out why. - EXPECT_TRUE(s.ok()); - for (const auto& f : files) { - if (f.find("LOG") != std::string::npos) { - ret.push_back(f); - } - } - return ret; - } - - // Delete all log files under kTestDir - void CleanupLogFiles() { - for (const std::string& f : GetLogFiles()) { - ASSERT_OK(default_env->DeleteFile(kTestDir + "/" + f)); - } - } - - void RollNTimesBySize(Logger* auto_roll_logger, size_t file_num, - size_t max_log_file_size) { - // Roll the log 4 times, and it will trim to 3 files. - std::string dummy_large_string; - dummy_large_string.assign(max_log_file_size, '='); - auto_roll_logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - for (size_t i = 0; i < file_num + 1; i++) { - // Log enough bytes to trigger at least one roll. - LogMessage(auto_roll_logger, dummy_large_string.c_str()); - LogMessage(auto_roll_logger, ""); - } - } - - static const std::string kSampleMessage; - static const std::string kTestDir; - static const std::string kTestDbDir; - static const std::string kLogFile; - static Env* default_env; -}; - -const std::string AutoRollLoggerTest::kSampleMessage( - "this is the message to be written to the log file!!"); -const std::string AutoRollLoggerTest::kTestDir( - test::PerThreadDBPath("db_log_test")); -const std::string AutoRollLoggerTest::kTestDbDir( - test::PerThreadDBPath("db_log_test_db")); -const std::string AutoRollLoggerTest::kLogFile( - test::PerThreadDBPath("db_log_test") + "/LOG"); -Env* AutoRollLoggerTest::default_env = Env::Default(); - -void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger, - size_t log_max_size, - const std::string& log_message) { - logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - ASSERT_EQ(InfoLogLevel::INFO_LEVEL, logger->GetInfoLogLevel()); - ASSERT_EQ(InfoLogLevel::INFO_LEVEL, - logger->TEST_inner_logger()->GetInfoLogLevel()); - // measure the size of each message, which is supposed - // to be equal or greater than log_message.size() - LogMessage(logger, log_message.c_str()); - size_t message_size = logger->GetLogFileSize(); - size_t current_log_size = message_size; - - // Test the cases when the log file will not be rolled. - while (current_log_size + message_size < log_max_size) { - LogMessage(logger, log_message.c_str()); - current_log_size += message_size; - ASSERT_EQ(current_log_size, logger->GetLogFileSize()); - } - - // Now the log file will be rolled - LogMessage(logger, log_message.c_str()); - // Since rotation is checked before actual logging, we need to - // trigger the rotation by logging another message. - LogMessage(logger, log_message.c_str()); - - ASSERT_TRUE(message_size == logger->GetLogFileSize()); -} - -void AutoRollLoggerTest::RollLogFileByTimeTest( - const std::shared_ptr& fs, - const std::shared_ptr& sc, AutoRollLogger* logger, size_t time, - const std::string& log_message) { - uint64_t expected_ctime; - uint64_t actual_ctime; - - uint64_t total_log_size; - EXPECT_OK(fs->GetFileSize(kLogFile, IOOptions(), &total_log_size, nullptr)); - expected_ctime = logger->TEST_ctime(); - logger->SetCallNowMicrosEveryNRecords(0); - - // -- Write to the log for several times, which is supposed - // to be finished before time. - for (int i = 0; i < 10; ++i) { - sc->SleepForMicroseconds(50000); - LogMessage(logger, log_message.c_str()); - EXPECT_OK(logger->GetStatus()); - // Make sure we always write to the same log file (by - // checking the create time); - - actual_ctime = logger->TEST_ctime(); - - // Also make sure the log size is increasing. - EXPECT_EQ(expected_ctime, actual_ctime); - EXPECT_GT(logger->GetLogFileSize(), total_log_size); - total_log_size = logger->GetLogFileSize(); - } - - // -- Make the log file expire - sc->SleepForMicroseconds(static_cast(time * 1000000)); - LogMessage(logger, log_message.c_str()); - - // At this time, the new log file should be created. - actual_ctime = logger->TEST_ctime(); - EXPECT_LT(expected_ctime, actual_ctime); - EXPECT_LT(logger->GetLogFileSize(), total_log_size); -} - -TEST_F(AutoRollLoggerTest, RollLogFileBySize) { - InitTestDb(); - size_t log_max_size = 1024 * 5; - size_t keep_log_file_num = 10; - - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), kTestDir, - "", log_max_size, 0, keep_log_file_num); - - RollLogFileBySizeTest(&logger, log_max_size, - kSampleMessage + ":RollLogFileBySize"); -} - -TEST_F(AutoRollLoggerTest, RollLogFileByTime) { - auto nsc = - std::make_shared(SystemClock::Default(), true); - - size_t time = 2; - size_t log_size = 1024 * 5; - size_t keep_log_file_num = 10; - - InitTestDb(); - // -- Test the existence of file during the server restart. - ASSERT_EQ(Status::NotFound(), default_env->FileExists(kLogFile)); - AutoRollLogger logger(default_env->GetFileSystem(), nsc, kTestDir, "", - log_size, time, keep_log_file_num); - ASSERT_OK(default_env->FileExists(kLogFile)); - - RollLogFileByTimeTest(default_env->GetFileSystem(), nsc, &logger, time, - kSampleMessage + ":RollLogFileByTime"); -} - -TEST_F(AutoRollLoggerTest, SetInfoLogLevel) { - InitTestDb(); - Options options; - options.info_log_level = InfoLogLevel::FATAL_LEVEL; - options.max_log_file_size = 1024; - std::shared_ptr logger; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - auto* auto_roll_logger = dynamic_cast(logger.get()); - ASSERT_NE(nullptr, auto_roll_logger); - ASSERT_EQ(InfoLogLevel::FATAL_LEVEL, auto_roll_logger->GetInfoLogLevel()); - ASSERT_EQ(InfoLogLevel::FATAL_LEVEL, - auto_roll_logger->TEST_inner_logger()->GetInfoLogLevel()); - auto_roll_logger->SetInfoLogLevel(InfoLogLevel::DEBUG_LEVEL); - ASSERT_EQ(InfoLogLevel::DEBUG_LEVEL, auto_roll_logger->GetInfoLogLevel()); - ASSERT_EQ(InfoLogLevel::DEBUG_LEVEL, logger->GetInfoLogLevel()); - ASSERT_EQ(InfoLogLevel::DEBUG_LEVEL, - auto_roll_logger->TEST_inner_logger()->GetInfoLogLevel()); -} - -TEST_F(AutoRollLoggerTest, OpenLogFilesMultipleTimesWithOptionLog_max_size) { - // If only 'log_max_size' options is specified, then every time - // when rocksdb is restarted, a new empty log file will be created. - InitTestDb(); - // WORKAROUND: - // avoid complier's complaint of "comparison between signed - // and unsigned integer expressions" because literal 0 is - // treated as "singed". - size_t kZero = 0; - size_t log_size = 1024; - size_t keep_log_file_num = 10; - - AutoRollLogger* logger = - new AutoRollLogger(FileSystem::Default(), SystemClock::Default(), - kTestDir, "", log_size, 0, keep_log_file_num); - - LogMessage(logger, kSampleMessage.c_str()); - ASSERT_GT(logger->GetLogFileSize(), kZero); - delete logger; - - // reopens the log file and an empty log file will be created. - logger = new AutoRollLogger(FileSystem::Default(), SystemClock::Default(), - kTestDir, "", log_size, 0, 10); - ASSERT_EQ(logger->GetLogFileSize(), kZero); - delete logger; -} - -TEST_F(AutoRollLoggerTest, CompositeRollByTimeAndSizeLogger) { - size_t time = 2, log_max_size = 1024 * 5; - size_t keep_log_file_num = 10; - - InitTestDb(); - - auto nsc = - std::make_shared(SystemClock::Default(), true); - AutoRollLogger logger(FileSystem::Default(), nsc, kTestDir, "", log_max_size, - time, keep_log_file_num); - - // Test the ability to roll by size - RollLogFileBySizeTest(&logger, log_max_size, - kSampleMessage + ":CompositeRollByTimeAndSizeLogger"); - - // Test the ability to roll by Time - RollLogFileByTimeTest(FileSystem::Default(), nsc, &logger, time, - kSampleMessage + ":CompositeRollByTimeAndSizeLogger"); -} - -#ifndef OS_WIN -// TODO: does not build for Windows because of EnvLogger use below. Need to -// port -TEST_F(AutoRollLoggerTest, CreateLoggerFromOptions) { - DBOptions options; - auto nsc = - std::make_shared(SystemClock::Default(), true); - std::unique_ptr nse(new CompositeEnvWrapper(Env::Default(), nsc)); - - std::shared_ptr logger; - - // Normal logger - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - ASSERT_TRUE(dynamic_cast(logger.get())); - - // Only roll by size - InitTestDb(); - options.max_log_file_size = 1024; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - AutoRollLogger* auto_roll_logger = - dynamic_cast(logger.get()); - ASSERT_TRUE(auto_roll_logger); - RollLogFileBySizeTest(auto_roll_logger, options.max_log_file_size, - kSampleMessage + ":CreateLoggerFromOptions - size"); - - // Only roll by Time - options.env = nse.get(); - InitTestDb(); - options.max_log_file_size = 0; - options.log_file_time_to_roll = 2; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - auto_roll_logger = dynamic_cast(logger.get()); - RollLogFileByTimeTest(options.env->GetFileSystem(), nsc, auto_roll_logger, - options.log_file_time_to_roll, - kSampleMessage + ":CreateLoggerFromOptions - time"); - - // roll by both Time and size - InitTestDb(); - options.max_log_file_size = 1024 * 5; - options.log_file_time_to_roll = 2; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - auto_roll_logger = dynamic_cast(logger.get()); - RollLogFileBySizeTest(auto_roll_logger, options.max_log_file_size, - kSampleMessage + ":CreateLoggerFromOptions - both"); - RollLogFileByTimeTest(options.env->GetFileSystem(), nsc, auto_roll_logger, - options.log_file_time_to_roll, - kSampleMessage + ":CreateLoggerFromOptions - both"); - - // Set keep_log_file_num - { - const size_t kFileNum = 3; - InitTestDb(); - options.max_log_file_size = 512; - options.log_file_time_to_roll = 2; - options.keep_log_file_num = kFileNum; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - auto_roll_logger = dynamic_cast(logger.get()); - - // Roll the log 4 times, and it will trim to 3 files. - std::string dummy_large_string; - dummy_large_string.assign(options.max_log_file_size, '='); - auto_roll_logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - for (size_t i = 0; i < kFileNum + 1; i++) { - // Log enough bytes to trigger at least one roll. - LogMessage(auto_roll_logger, dummy_large_string.c_str()); - LogMessage(auto_roll_logger, ""); - } - - std::vector files = GetLogFiles(); - ASSERT_EQ(kFileNum, files.size()); - - CleanupLogFiles(); - } - - // Set keep_log_file_num and dbname is different from - // db_log_dir. - { - const size_t kFileNum = 3; - InitTestDb(); - options.max_log_file_size = 512; - options.log_file_time_to_roll = 2; - options.keep_log_file_num = kFileNum; - options.db_log_dir = kTestDir; - ASSERT_OK(CreateLoggerFromOptions(kTestDbDir, options, &logger)); - auto_roll_logger = dynamic_cast(logger.get()); - - // Roll the log 4 times, and it will trim to 3 files. - std::string dummy_large_string; - dummy_large_string.assign(options.max_log_file_size, '='); - auto_roll_logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - for (size_t i = 0; i < kFileNum + 1; i++) { - // Log enough bytes to trigger at least one roll. - LogMessage(auto_roll_logger, dummy_large_string.c_str()); - LogMessage(auto_roll_logger, ""); - } - - std::vector files = GetLogFiles(); - ASSERT_EQ(kFileNum, files.size()); - for (const auto& f : files) { - ASSERT_TRUE(f.find("db_log_test_db") != std::string::npos); - } - - // Cleaning up those files. - CleanupLogFiles(); - } -} - -TEST_F(AutoRollLoggerTest, AutoDeleting) { - for (int attempt = 0; attempt < 2; attempt++) { - // In the first attemp, db_log_dir is not set, while in the - // second it is set. - std::string dbname = (attempt == 0) ? kTestDir : "/test/dummy/dir"; - std::string db_log_dir = (attempt == 0) ? "" : kTestDir; - - InitTestDb(); - const size_t kMaxFileSize = 512; - { - size_t log_num = 8; - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), - dbname, db_log_dir, kMaxFileSize, 0, log_num); - RollNTimesBySize(&logger, log_num, kMaxFileSize); - - ASSERT_EQ(log_num, GetLogFiles().size()); - } - // Shrink number of files - { - size_t log_num = 5; - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), - dbname, db_log_dir, kMaxFileSize, 0, log_num); - ASSERT_EQ(log_num, GetLogFiles().size()); - - RollNTimesBySize(&logger, 3, kMaxFileSize); - ASSERT_EQ(log_num, GetLogFiles().size()); - } - - // Increase number of files again. - { - size_t log_num = 7; - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), - dbname, db_log_dir, kMaxFileSize, 0, log_num); - ASSERT_EQ(6, GetLogFiles().size()); - - RollNTimesBySize(&logger, 3, kMaxFileSize); - ASSERT_EQ(log_num, GetLogFiles().size()); - } - - CleanupLogFiles(); - } -} - -TEST_F(AutoRollLoggerTest, LogFlushWhileRolling) { - DBOptions options; - std::shared_ptr logger; - - InitTestDb(); - options.max_log_file_size = 1024 * 5; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - AutoRollLogger* auto_roll_logger = - dynamic_cast(logger.get()); - ASSERT_TRUE(auto_roll_logger); - ROCKSDB_NAMESPACE::port::Thread flush_thread; - - // Notes: - // (1) Need to pin the old logger before beginning the roll, as rolling grabs - // the mutex, which would prevent us from accessing the old logger. This - // also marks flush_thread with AutoRollLogger::Flush:PinnedLogger. - // (2) New logger will be cut in AutoRollLogger::RollLogFile only when flush - // is completed and reference to pinned logger is released. - // (3) EnvLogger::Flush() happens in both threads but its SyncPoints only - // are enabled in flush_thread (the one pinning the old logger). - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependencyAndMarkers( - {{"AutoRollLogger::Flush:PinnedLogger", - "AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"}}, - {{"AutoRollLogger::Flush:PinnedLogger", "EnvLogger::Flush:Begin1"}, - {"AutoRollLogger::Flush:PinnedLogger", "EnvLogger::Flush:Begin2"}}); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - flush_thread = port::Thread([&]() { auto_roll_logger->Flush(); }); - TEST_SYNC_POINT( - "AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"); - RollLogFileBySizeTest(auto_roll_logger, options.max_log_file_size, - kSampleMessage + ":LogFlushWhileRolling"); - flush_thread.join(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -#endif // OS_WIN - -TEST_F(AutoRollLoggerTest, InfoLogLevel) { - InitTestDb(); - - size_t log_size = 8192; - size_t log_lines = 0; - // an extra-scope to force the AutoRollLogger to flush the log file when it - // becomes out of scope. - { - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), - kTestDir, "", log_size, 0, 10); - for (int log_level = InfoLogLevel::HEADER_LEVEL; - log_level >= InfoLogLevel::DEBUG_LEVEL; log_level--) { - logger.SetInfoLogLevel((InfoLogLevel)log_level); - for (int log_type = InfoLogLevel::DEBUG_LEVEL; - log_type <= InfoLogLevel::HEADER_LEVEL; log_type++) { - // log messages with log level smaller than log_level will not be - // logged. - LogMessage((InfoLogLevel)log_type, &logger, kSampleMessage.c_str()); - } - log_lines += InfoLogLevel::HEADER_LEVEL - log_level + 1; - } - for (int log_level = InfoLogLevel::HEADER_LEVEL; - log_level >= InfoLogLevel::DEBUG_LEVEL; log_level--) { - logger.SetInfoLogLevel((InfoLogLevel)log_level); - - // again, messages with level smaller than log_level will not be logged. - ROCKS_LOG_HEADER(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_DEBUG(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_INFO(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_WARN(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_ERROR(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_FATAL(&logger, "%s", kSampleMessage.c_str()); - log_lines += InfoLogLevel::HEADER_LEVEL - log_level + 1; - } - } - std::ifstream inFile(AutoRollLoggerTest::kLogFile.c_str()); - size_t lines = std::count(std::istreambuf_iterator(inFile), - std::istreambuf_iterator(), '\n'); - ASSERT_EQ(log_lines, lines); - inFile.close(); -} - -TEST_F(AutoRollLoggerTest, Close) { - InitTestDb(); - - size_t log_size = 8192; - size_t log_lines = 0; - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), kTestDir, - "", log_size, 0, 10); - for (int log_level = InfoLogLevel::HEADER_LEVEL; - log_level >= InfoLogLevel::DEBUG_LEVEL; log_level--) { - logger.SetInfoLogLevel((InfoLogLevel)log_level); - for (int log_type = InfoLogLevel::DEBUG_LEVEL; - log_type <= InfoLogLevel::HEADER_LEVEL; log_type++) { - // log messages with log level smaller than log_level will not be - // logged. - LogMessage((InfoLogLevel)log_type, &logger, kSampleMessage.c_str()); - } - log_lines += InfoLogLevel::HEADER_LEVEL - log_level + 1; - } - for (int log_level = InfoLogLevel::HEADER_LEVEL; - log_level >= InfoLogLevel::DEBUG_LEVEL; log_level--) { - logger.SetInfoLogLevel((InfoLogLevel)log_level); - - // again, messages with level smaller than log_level will not be logged. - ROCKS_LOG_HEADER(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_DEBUG(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_INFO(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_WARN(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_ERROR(&logger, "%s", kSampleMessage.c_str()); - ROCKS_LOG_FATAL(&logger, "%s", kSampleMessage.c_str()); - log_lines += InfoLogLevel::HEADER_LEVEL - log_level + 1; - } - ASSERT_EQ(logger.Close(), Status::OK()); - - std::ifstream inFile(AutoRollLoggerTest::kLogFile.c_str()); - size_t lines = std::count(std::istreambuf_iterator(inFile), - std::istreambuf_iterator(), '\n'); - ASSERT_EQ(log_lines, lines); - inFile.close(); -} - -// Test the logger Header function for roll over logs -// We expect the new logs creates as roll over to carry the headers specified -static std::vector GetOldFileNames(const std::string& path) { - std::vector ret; - - const std::string dirname = path.substr(/*start=*/0, path.find_last_of("/")); - const std::string fname = path.substr(path.find_last_of("/") + 1); - - std::vector children; - EXPECT_OK(Env::Default()->GetChildren(dirname, &children)); - - // We know that the old log files are named [path] - // Return all entities that match the pattern - for (auto& child : children) { - if (fname != child && child.find(fname) == 0) { - ret.push_back(dirname + "/" + child); - } - } - - return ret; -} - -TEST_F(AutoRollLoggerTest, LogHeaderTest) { - static const size_t MAX_HEADERS = 10; - static const size_t LOG_MAX_SIZE = 1024 * 5; - static const std::string HEADER_STR = "Log header line"; - - // test_num == 0 -> standard call to Header() - // test_num == 1 -> call to Log() with InfoLogLevel::HEADER_LEVEL - for (int test_num = 0; test_num < 2; test_num++) { - InitTestDb(); - - AutoRollLogger logger(FileSystem::Default(), SystemClock::Default(), - kTestDir, /*db_log_dir=*/"", LOG_MAX_SIZE, - /*log_file_time_to_roll=*/0, - /*keep_log_file_num=*/10); - - if (test_num == 0) { - // Log some headers explicitly using Header() - for (size_t i = 0; i < MAX_HEADERS; i++) { - Header(&logger, "%s %" ROCKSDB_PRIszt, HEADER_STR.c_str(), i); - } - } else if (test_num == 1) { - // HEADER_LEVEL should make this behave like calling Header() - for (size_t i = 0; i < MAX_HEADERS; i++) { - ROCKS_LOG_HEADER(&logger, "%s %" ROCKSDB_PRIszt, HEADER_STR.c_str(), i); - } - } - - const std::string newfname = logger.TEST_log_fname(); - - // Log enough data to cause a roll over - int i = 0; - for (size_t iter = 0; iter < 2; iter++) { - while (logger.GetLogFileSize() < LOG_MAX_SIZE) { - Info(&logger, (kSampleMessage + ":LogHeaderTest line %d").c_str(), i); - ++i; - } - - Info(&logger, "Rollover"); - } - - // Flush the log for the latest file - LogFlush(&logger); - - const auto oldfiles = GetOldFileNames(newfname); - - ASSERT_EQ(oldfiles.size(), (size_t)2); - - for (auto& oldfname : oldfiles) { - // verify that the files rolled over - ASSERT_NE(oldfname, newfname); - // verify that the old log contains all the header logs - ASSERT_EQ(test::GetLinesCount(oldfname, HEADER_STR), MAX_HEADERS); - } - } -} - -TEST_F(AutoRollLoggerTest, LogFileExistence) { - ROCKSDB_NAMESPACE::DB* db; - ROCKSDB_NAMESPACE::Options options; -#ifdef OS_WIN - // Replace all slashes in the path so windows CompSpec does not - // become confused - std::string testDir(kTestDir); - std::replace_if( - testDir.begin(), testDir.end(), [](char ch) { return ch == '/'; }, '\\'); - std::string deleteCmd = "if exist " + testDir + " rd /s /q " + testDir; -#else - std::string deleteCmd = "rm -rf " + kTestDir; -#endif - ASSERT_EQ(system(deleteCmd.c_str()), 0); - options.max_log_file_size = 100 * 1024 * 1024; - options.create_if_missing = true; - ASSERT_OK(ROCKSDB_NAMESPACE::DB::Open(options, kTestDir, &db)); - ASSERT_OK(default_env->FileExists(kLogFile)); - delete db; -} - -TEST_F(AutoRollLoggerTest, FileCreateFailure) { - Options options; - options.max_log_file_size = 100 * 1024 * 1024; - options.db_log_dir = "/a/dir/does/not/exist/at/all"; - - std::shared_ptr logger; - ASSERT_NOK(CreateLoggerFromOptions("", options, &logger)); - ASSERT_TRUE(!logger); -} - -TEST_F(AutoRollLoggerTest, RenameOnlyWhenExists) { - InitTestDb(); - SpecialEnv env(Env::Default()); - Options options; - options.env = &env; - - // Originally no LOG exists. Should not see a rename. - { - std::shared_ptr logger; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - ASSERT_EQ(0, env.rename_count_); - } - - // Now a LOG exists. Create a new one should see a rename. - { - std::shared_ptr logger; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - ASSERT_EQ(1, env.rename_count_); - } -} - -TEST_F(AutoRollLoggerTest, RenameError) { - InitTestDb(); - SpecialEnv env(Env::Default()); - env.rename_error_ = true; - Options options; - options.env = &env; - - // Originally no LOG exists. Should not be impacted by rename error. - { - std::shared_ptr logger; - ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger)); - ASSERT_TRUE(logger != nullptr); - } - - // Now a LOG exists. Rename error should cause failure. - { - std::shared_ptr logger; - ASSERT_NOK(CreateLoggerFromOptions(kTestDir, options, &logger)); - ASSERT_TRUE(logger == nullptr); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/logging/env_logger_test.cc b/logging/env_logger_test.cc deleted file mode 100644 index 467ab064f..000000000 --- a/logging/env_logger_test.cc +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// - -#include "logging/env_logger.h" - -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -// In this test we only want to Log some simple log message with -// no format. -void LogMessage(std::shared_ptr logger, const std::string& message) { - Log(logger, "%s", message.c_str()); -} - -// Helper method to write the message num_times in the given logger. -void WriteLogs(std::shared_ptr logger, const std::string& message, - int num_times) { - for (int ii = 0; ii < num_times; ++ii) { - LogMessage(logger, message); - } -} - -} // namespace - -class EnvLoggerTest : public testing::Test { - public: - Env* env_; - - EnvLoggerTest() : env_(Env::Default()) {} - - ~EnvLoggerTest() = default; - - std::shared_ptr CreateLogger() { - std::shared_ptr result; - assert(NewEnvLogger(kLogFile, env_, &result).ok()); - assert(result); - result->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL); - return result; - } - - void DeleteLogFile() { ASSERT_OK(env_->DeleteFile(kLogFile)); } - - static const std::string kSampleMessage; - static const std::string kTestDir; - static const std::string kLogFile; -}; - -const std::string EnvLoggerTest::kSampleMessage = - "this is the message to be written to the log file!!"; -const std::string EnvLoggerTest::kLogFile = test::PerThreadDBPath("log_file"); - -TEST_F(EnvLoggerTest, EmptyLogFile) { - auto logger = CreateLogger(); - ASSERT_EQ(logger->Close(), Status::OK()); - - // Check the size of the log file. - uint64_t file_size; - ASSERT_EQ(env_->GetFileSize(kLogFile, &file_size), Status::OK()); - ASSERT_EQ(file_size, 0); - DeleteLogFile(); -} - -TEST_F(EnvLoggerTest, LogMultipleLines) { - auto logger = CreateLogger(); - - // Write multiple lines. - const int kNumIter = 10; - WriteLogs(logger, kSampleMessage, kNumIter); - - // Flush the logs. - logger->Flush(); - ASSERT_EQ(logger->Close(), Status::OK()); - - // Validate whether the log file has 'kNumIter' number of lines. - ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter); - DeleteLogFile(); -} - -TEST_F(EnvLoggerTest, Overwrite) { - { - auto logger = CreateLogger(); - - // Write multiple lines. - const int kNumIter = 10; - WriteLogs(logger, kSampleMessage, kNumIter); - - ASSERT_EQ(logger->Close(), Status::OK()); - - // Validate whether the log file has 'kNumIter' number of lines. - ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter); - } - - // Now reopen the file again. - { - auto logger = CreateLogger(); - - // File should be empty. - uint64_t file_size; - ASSERT_EQ(env_->GetFileSize(kLogFile, &file_size), Status::OK()); - ASSERT_EQ(file_size, 0); - ASSERT_EQ(logger->GetLogFileSize(), 0); - ASSERT_EQ(logger->Close(), Status::OK()); - } - DeleteLogFile(); -} - -TEST_F(EnvLoggerTest, Close) { - auto logger = CreateLogger(); - - // Write multiple lines. - const int kNumIter = 10; - WriteLogs(logger, kSampleMessage, kNumIter); - - ASSERT_EQ(logger->Close(), Status::OK()); - - // Validate whether the log file has 'kNumIter' number of lines. - ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), kNumIter); - DeleteLogFile(); -} - -TEST_F(EnvLoggerTest, ConcurrentLogging) { - auto logger = CreateLogger(); - - const int kNumIter = 20; - std::function cb = [&]() { - WriteLogs(logger, kSampleMessage, kNumIter); - logger->Flush(); - }; - - // Write to the logs from multiple threads. - std::vector threads; - const int kNumThreads = 5; - // Create threads. - for (int ii = 0; ii < kNumThreads; ++ii) { - threads.push_back(port::Thread(cb)); - } - - // Wait for them to complete. - for (auto& th : threads) { - th.join(); - } - - ASSERT_EQ(logger->Close(), Status::OK()); - - // Verfiy the log file. - ASSERT_EQ(test::GetLinesCount(kLogFile, kSampleMessage), - kNumIter * kNumThreads); - DeleteLogFile(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/logging/event_logger_test.cc b/logging/event_logger_test.cc deleted file mode 100644 index 582f56ceb..000000000 --- a/logging/event_logger_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "logging/event_logger.h" - -#include - -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class EventLoggerTest : public testing::Test {}; - -class StringLogger : public Logger { - public: - using Logger::Logv; - void Logv(const char* format, va_list ap) override { - vsnprintf(buffer_, sizeof(buffer_), format, ap); - } - char* buffer() { return buffer_; } - - private: - char buffer_[1000]; -}; - -TEST_F(EventLoggerTest, SimpleTest) { - StringLogger logger; - EventLogger event_logger(&logger); - event_logger.Log() << "id" << 5 << "event" - << "just_testing"; - std::string output(logger.buffer()); - ASSERT_TRUE(output.find("\"event\": \"just_testing\"") != std::string::npos); - ASSERT_TRUE(output.find("\"id\": 5") != std::string::npos); - ASSERT_TRUE(output.find("\"time_micros\"") != std::string::npos); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/memory/arena_test.cc b/memory/arena_test.cc deleted file mode 100644 index 21bf7ed62..000000000 --- a/memory/arena_test.cc +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "memory/arena.h" - -#ifndef OS_WIN -#include -#endif -#include "port/port.h" -#include "test_util/testharness.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -const size_t kHugePageSize = 2 * 1024 * 1024; -} // namespace -class ArenaTest : public testing::Test {}; - -TEST_F(ArenaTest, Empty) { Arena arena0; } - -namespace { -bool CheckMemoryAllocated(size_t allocated, size_t expected) { - // The value returned by Arena::MemoryAllocatedBytes() may be greater than - // the requested memory. We choose a somewhat arbitrary upper bound of - // max_expected = expected * 1.1 to detect critical overallocation. - size_t max_expected = expected + expected / 10; - return allocated >= expected && allocated <= max_expected; -} - -void MemoryAllocatedBytesTest(size_t huge_page_size) { - const int N = 17; - size_t req_sz; // requested size - size_t bsz = 32 * 1024; // block size - size_t expected_memory_allocated; - - Arena arena(bsz, nullptr, huge_page_size); - - // requested size > quarter of a block: - // allocate requested size separately - req_sz = 12 * 1024; - for (int i = 0; i < N; i++) { - arena.Allocate(req_sz); - } - expected_memory_allocated = req_sz * N + Arena::kInlineSize; - ASSERT_PRED2(CheckMemoryAllocated, arena.MemoryAllocatedBytes(), - expected_memory_allocated); - - arena.Allocate(Arena::kInlineSize - 1); - - // requested size < quarter of a block: - // allocate a block with the default size, then try to use unused part - // of the block. So one new block will be allocated for the first - // Allocate(99) call. All the remaining calls won't lead to new allocation. - req_sz = 99; - for (int i = 0; i < N; i++) { - arena.Allocate(req_sz); - } - if (huge_page_size) { - ASSERT_TRUE( - CheckMemoryAllocated(arena.MemoryAllocatedBytes(), - expected_memory_allocated + bsz) || - CheckMemoryAllocated(arena.MemoryAllocatedBytes(), - expected_memory_allocated + huge_page_size)); - } else { - expected_memory_allocated += bsz; - ASSERT_PRED2(CheckMemoryAllocated, arena.MemoryAllocatedBytes(), - expected_memory_allocated); - } - - // requested size > size of a block: - // allocate requested size separately - expected_memory_allocated = arena.MemoryAllocatedBytes(); - req_sz = 8 * 1024 * 1024; - for (int i = 0; i < N; i++) { - arena.Allocate(req_sz); - } - expected_memory_allocated += req_sz * N; - ASSERT_PRED2(CheckMemoryAllocated, arena.MemoryAllocatedBytes(), - expected_memory_allocated); -} - -// Make sure we didn't count the allocate but not used memory space in -// Arena::ApproximateMemoryUsage() -static void ApproximateMemoryUsageTest(size_t huge_page_size) { - const size_t kBlockSize = 4096; - const size_t kEntrySize = kBlockSize / 8; - const size_t kZero = 0; - Arena arena(kBlockSize, nullptr, huge_page_size); - ASSERT_EQ(kZero, arena.ApproximateMemoryUsage()); - - // allocate inline bytes - const size_t kAlignUnit = alignof(max_align_t); - EXPECT_TRUE(arena.IsInInlineBlock()); - arena.AllocateAligned(kAlignUnit); - EXPECT_TRUE(arena.IsInInlineBlock()); - arena.AllocateAligned(Arena::kInlineSize / 2 - (2 * kAlignUnit)); - EXPECT_TRUE(arena.IsInInlineBlock()); - arena.AllocateAligned(Arena::kInlineSize / 2); - EXPECT_TRUE(arena.IsInInlineBlock()); - ASSERT_EQ(arena.ApproximateMemoryUsage(), Arena::kInlineSize - kAlignUnit); - ASSERT_PRED2(CheckMemoryAllocated, arena.MemoryAllocatedBytes(), - Arena::kInlineSize); - - auto num_blocks = kBlockSize / kEntrySize; - - // first allocation - arena.AllocateAligned(kEntrySize); - EXPECT_FALSE(arena.IsInInlineBlock()); - auto mem_usage = arena.MemoryAllocatedBytes(); - if (huge_page_size) { - ASSERT_TRUE( - CheckMemoryAllocated(mem_usage, kBlockSize + Arena::kInlineSize) || - CheckMemoryAllocated(mem_usage, huge_page_size + Arena::kInlineSize)); - } else { - ASSERT_PRED2(CheckMemoryAllocated, mem_usage, - kBlockSize + Arena::kInlineSize); - } - auto usage = arena.ApproximateMemoryUsage(); - ASSERT_LT(usage, mem_usage); - for (size_t i = 1; i < num_blocks; ++i) { - arena.AllocateAligned(kEntrySize); - ASSERT_EQ(mem_usage, arena.MemoryAllocatedBytes()); - ASSERT_EQ(arena.ApproximateMemoryUsage(), usage + kEntrySize); - EXPECT_FALSE(arena.IsInInlineBlock()); - usage = arena.ApproximateMemoryUsage(); - } - if (huge_page_size) { - ASSERT_TRUE(usage > mem_usage || - usage + huge_page_size - kBlockSize == mem_usage); - } else { - ASSERT_GT(usage, mem_usage); - } -} - -static void SimpleTest(size_t huge_page_size) { - std::vector> allocated; - Arena arena(Arena::kMinBlockSize, nullptr, huge_page_size); - const int N = 100000; - size_t bytes = 0; - Random rnd(301); - for (int i = 0; i < N; i++) { - size_t s; - if (i % (N / 10) == 0) { - s = i; - } else { - s = rnd.OneIn(4000) - ? rnd.Uniform(6000) - : (rnd.OneIn(10) ? rnd.Uniform(100) : rnd.Uniform(20)); - } - if (s == 0) { - // Our arena disallows size 0 allocations. - s = 1; - } - char* r; - if (rnd.OneIn(10)) { - r = arena.AllocateAligned(s); - } else { - r = arena.Allocate(s); - } - - for (unsigned int b = 0; b < s; b++) { - // Fill the "i"th allocation with a known bit pattern - r[b] = i % 256; - } - bytes += s; - allocated.push_back(std::make_pair(s, r)); - ASSERT_GE(arena.ApproximateMemoryUsage(), bytes); - if (i > N / 10) { - ASSERT_LE(arena.ApproximateMemoryUsage(), bytes * 1.10); - } - } - for (unsigned int i = 0; i < allocated.size(); i++) { - size_t num_bytes = allocated[i].first; - const char* p = allocated[i].second; - for (unsigned int b = 0; b < num_bytes; b++) { - // Check the "i"th allocation for the known bit pattern - ASSERT_EQ(int(p[b]) & 0xff, (int)(i % 256)); - } - } -} -} // namespace - -TEST_F(ArenaTest, MemoryAllocatedBytes) { - MemoryAllocatedBytesTest(0); - MemoryAllocatedBytesTest(kHugePageSize); -} - -TEST_F(ArenaTest, ApproximateMemoryUsage) { - ApproximateMemoryUsageTest(0); - ApproximateMemoryUsageTest(kHugePageSize); -} - -TEST_F(ArenaTest, Simple) { - SimpleTest(0); - SimpleTest(kHugePageSize); -} - -// Number of minor page faults since last call -size_t PopMinorPageFaultCount() { -#ifdef RUSAGE_SELF - static long prev = 0; - struct rusage usage; - EXPECT_EQ(getrusage(RUSAGE_SELF, &usage), 0); - size_t rv = usage.ru_minflt - prev; - prev = usage.ru_minflt; - return rv; -#else - // Conservative - return SIZE_MAX; -#endif // RUSAGE_SELF -} - -TEST(MmapTest, AllocateLazyZeroed) { - // Doesn't have to be page aligned - constexpr size_t len = 1234567; - MemMapping m = MemMapping::AllocateLazyZeroed(len); - auto arr = static_cast(m.Get()); - - // Should generally work - ASSERT_NE(arr, nullptr); - - // Start counting page faults - PopMinorPageFaultCount(); - - // Access half of the allocation - size_t i = 0; - for (; i < len / 2; ++i) { - ASSERT_EQ(arr[i], 0); - arr[i] = static_cast(i & 255); - } - - // Appropriate page faults (maybe more) - size_t faults = PopMinorPageFaultCount(); - ASSERT_GE(faults, len / 2 / port::kPageSize); - - // Access rest of the allocation - for (; i < len; ++i) { - ASSERT_EQ(arr[i], 0); - arr[i] = static_cast(i & 255); - } - - // Appropriate page faults (maybe more) - faults = PopMinorPageFaultCount(); - ASSERT_GE(faults, len / 2 / port::kPageSize); - - // Verify data - for (i = 0; i < len; ++i) { - ASSERT_EQ(arr[i], static_cast(i & 255)); - } -} - -TEST_F(ArenaTest, UnmappedAllocation) { - // Verify that it's possible to get unmapped pages in large allocations, - // for memory efficiency and to ensure we don't accidentally waste time & - // space initializing the memory. - constexpr size_t kBlockSize = 2U << 20; - Arena arena(kBlockSize); - - // The allocator might give us back recycled memory for a while, but - // shouldn't last forever. - for (int i = 0;; ++i) { - char* p = arena.Allocate(kBlockSize); - - // Start counting page faults - PopMinorPageFaultCount(); - - // Overwrite the whole allocation - for (size_t j = 0; j < kBlockSize; ++j) { - p[j] = static_cast(j & 255); - } - - size_t faults = PopMinorPageFaultCount(); - if (faults >= kBlockSize * 3 / 4 / port::kPageSize) { - // Most of the access generated page faults => GOOD - break; - } - // Should have succeeded after enough tries - ASSERT_LT(i, 1000); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/memory/memory_allocator_test.cc b/memory/memory_allocator_test.cc deleted file mode 100644 index 6616e1c3b..000000000 --- a/memory/memory_allocator_test.cc +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// Copyright (c) 2019 Intel Corporation -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include - -#include "memory/jemalloc_nodump_allocator.h" -#include "memory/memkind_kmem_allocator.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/options.h" -#include "table/block_based/block_based_table_factory.h" -#include "test_util/testharness.h" -#include "utilities/memory_allocators.h" - -namespace ROCKSDB_NAMESPACE { - -// TODO: the tests do not work in LITE mode due to relying on -// `CreateFromString()` to create non-default memory allocators. - -class MemoryAllocatorTest - : public testing::Test, - public ::testing::WithParamInterface> { - public: - MemoryAllocatorTest() { - std::tie(id_, supported_) = GetParam(); - Status s = - MemoryAllocator::CreateFromString(ConfigOptions(), id_, &allocator_); - EXPECT_EQ(supported_, s.ok()); - } - bool IsSupported() { return supported_; } - - std::shared_ptr allocator_; - std::string id_; - - private: - bool supported_; -}; - -TEST_P(MemoryAllocatorTest, Allocate) { - if (!IsSupported()) { - return; - } - void* p = allocator_->Allocate(1024); - ASSERT_NE(p, nullptr); - size_t size = allocator_->UsableSize(p, 1024); - ASSERT_GE(size, 1024); - allocator_->Deallocate(p); -} - -TEST_P(MemoryAllocatorTest, CreateAllocator) { - ConfigOptions config_options; - config_options.ignore_unknown_options = false; - config_options.ignore_unsupported_options = false; - std::shared_ptr orig, copy; - Status s = MemoryAllocator::CreateFromString(config_options, id_, &orig); - if (!IsSupported()) { - ASSERT_TRUE(s.IsNotSupported()); - } else { - ASSERT_OK(s); - ASSERT_NE(orig, nullptr); - std::string str = orig->ToString(config_options); - ASSERT_OK(MemoryAllocator::CreateFromString(config_options, str, ©)); - ASSERT_EQ(orig, copy); - } -} - -TEST_P(MemoryAllocatorTest, DatabaseBlockCache) { - if (!IsSupported()) { - // Check if a memory node is available for allocation - } - - // Create database with block cache using the MemoryAllocator - Options options; - std::string dbname = test::PerThreadDBPath("allocator_test"); - ASSERT_OK(DestroyDB(dbname, options)); - - options.create_if_missing = true; - BlockBasedTableOptions table_options; - auto cache = NewLRUCache(1024 * 1024, 6, false, 0.0, allocator_); - table_options.block_cache = cache; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - DB* db = nullptr; - Status s = DB::Open(options, dbname, &db); - ASSERT_OK(s); - ASSERT_NE(db, nullptr); - ASSERT_LE(cache->GetUsage(), 104); // Cache will contain stats - - // Write 2kB (200 values, each 10 bytes) - int num_keys = 200; - WriteOptions wo; - std::string val = "0123456789"; - for (int i = 0; i < num_keys; i++) { - std::string key = std::to_string(i); - s = db->Put(wo, Slice(key), Slice(val)); - ASSERT_OK(s); - } - ASSERT_OK(db->Flush(FlushOptions())); // Flush all data from memtable so that - // reads are from block cache - - // Read and check block cache usage - ReadOptions ro; - std::string result; - for (int i = 0; i < num_keys; i++) { - std::string key = std::to_string(i); - s = db->Get(ro, key, &result); - ASSERT_OK(s); - ASSERT_EQ(result, val); - } - ASSERT_GT(cache->GetUsage(), 2000); - - // Close database - s = db->Close(); - ASSERT_OK(s); - delete db; - ASSERT_OK(DestroyDB(dbname, options)); -} - -class CreateMemoryAllocatorTest : public testing::Test { - public: - CreateMemoryAllocatorTest() { - config_options_.ignore_unknown_options = false; - config_options_.ignore_unsupported_options = false; - } - ConfigOptions config_options_; -}; - -TEST_F(CreateMemoryAllocatorTest, JemallocOptionsTest) { - std::shared_ptr allocator; - std::string id = std::string("id=") + JemallocNodumpAllocator::kClassName(); - Status s = MemoryAllocator::CreateFromString(config_options_, id, &allocator); - if (!JemallocNodumpAllocator::IsSupported()) { - ASSERT_NOK(s); - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - return; - } - ASSERT_OK(s); - ASSERT_NE(allocator, nullptr); - JemallocAllocatorOptions jopts; - auto opts = allocator->GetOptions(); - ASSERT_NE(opts, nullptr); - ASSERT_EQ(opts->limit_tcache_size, jopts.limit_tcache_size); - ASSERT_EQ(opts->tcache_size_lower_bound, jopts.tcache_size_lower_bound); - ASSERT_EQ(opts->tcache_size_upper_bound, jopts.tcache_size_upper_bound); - - ASSERT_NOK(MemoryAllocator::CreateFromString( - config_options_, - id + "; limit_tcache_size=true; tcache_size_lower_bound=4096; " - "tcache_size_upper_bound=1024", - &allocator)); - ASSERT_OK(MemoryAllocator::CreateFromString( - config_options_, - id + "; limit_tcache_size=false; tcache_size_lower_bound=4096; " - "tcache_size_upper_bound=1024", - &allocator)); - opts = allocator->GetOptions(); - ASSERT_NE(opts, nullptr); - ASSERT_EQ(opts->limit_tcache_size, false); - ASSERT_EQ(opts->tcache_size_lower_bound, 4096U); - ASSERT_EQ(opts->tcache_size_upper_bound, 1024U); - ASSERT_OK(MemoryAllocator::CreateFromString( - config_options_, - id + "; limit_tcache_size=true; tcache_size_upper_bound=4096; " - "tcache_size_lower_bound=1024", - &allocator)); - opts = allocator->GetOptions(); - ASSERT_NE(opts, nullptr); - ASSERT_EQ(opts->limit_tcache_size, true); - ASSERT_EQ(opts->tcache_size_lower_bound, 1024U); - ASSERT_EQ(opts->tcache_size_upper_bound, 4096U); -} - -TEST_F(CreateMemoryAllocatorTest, NewJemallocNodumpAllocator) { - JemallocAllocatorOptions jopts; - std::shared_ptr allocator; - - jopts.limit_tcache_size = true; - jopts.tcache_size_lower_bound = 2 * 1024; - jopts.tcache_size_upper_bound = 1024; - - ASSERT_NOK(NewJemallocNodumpAllocator(jopts, nullptr)); - Status s = NewJemallocNodumpAllocator(jopts, &allocator); - std::string msg; - if (!JemallocNodumpAllocator::IsSupported(&msg)) { - ASSERT_NOK(s); - ROCKSDB_GTEST_BYPASS("JEMALLOC not supported"); - return; - } - ASSERT_NOK(s); // Invalid options - ASSERT_EQ(allocator, nullptr); - - jopts.tcache_size_upper_bound = 4 * 1024; - ASSERT_OK(NewJemallocNodumpAllocator(jopts, &allocator)); - ASSERT_NE(allocator, nullptr); - auto opts = allocator->GetOptions(); - ASSERT_EQ(opts->tcache_size_upper_bound, jopts.tcache_size_upper_bound); - ASSERT_EQ(opts->tcache_size_lower_bound, jopts.tcache_size_lower_bound); - ASSERT_EQ(opts->limit_tcache_size, jopts.limit_tcache_size); - - jopts.limit_tcache_size = false; - ASSERT_OK(NewJemallocNodumpAllocator(jopts, &allocator)); - ASSERT_NE(allocator, nullptr); - opts = allocator->GetOptions(); - ASSERT_EQ(opts->tcache_size_upper_bound, jopts.tcache_size_upper_bound); - ASSERT_EQ(opts->tcache_size_lower_bound, jopts.tcache_size_lower_bound); - ASSERT_EQ(opts->limit_tcache_size, jopts.limit_tcache_size); -} - -INSTANTIATE_TEST_CASE_P(DefaultMemoryAllocator, MemoryAllocatorTest, - ::testing::Values(std::make_tuple( - DefaultMemoryAllocator::kClassName(), true))); -#ifdef MEMKIND -INSTANTIATE_TEST_CASE_P( - MemkindkMemAllocator, MemoryAllocatorTest, - ::testing::Values(std::make_tuple(MemkindKmemAllocator::kClassName(), - MemkindKmemAllocator::IsSupported()))); -#endif // MEMKIND - -#ifdef ROCKSDB_JEMALLOC -INSTANTIATE_TEST_CASE_P( - JemallocNodumpAllocator, MemoryAllocatorTest, - ::testing::Values(std::make_tuple(JemallocNodumpAllocator::kClassName(), - JemallocNodumpAllocator::IsSupported()))); -#endif // ROCKSDB_JEMALLOC - - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/memtable/inlineskiplist_test.cc b/memtable/inlineskiplist_test.cc deleted file mode 100644 index f85644064..000000000 --- a/memtable/inlineskiplist_test.cc +++ /dev/null @@ -1,664 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "memtable/inlineskiplist.h" - -#include -#include - -#include "memory/concurrent_arena.h" -#include "rocksdb/env.h" -#include "test_util/testharness.h" -#include "util/hash.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -// Our test skip list stores 8-byte unsigned integers -using Key = uint64_t; - -static const char* Encode(const uint64_t* key) { - return reinterpret_cast(key); -} - -static Key Decode(const char* key) { - Key rv; - memcpy(&rv, key, sizeof(Key)); - return rv; -} - -struct TestComparator { - using DecodedType = Key; - - static DecodedType decode_key(const char* b) { return Decode(b); } - - int operator()(const char* a, const char* b) const { - if (Decode(a) < Decode(b)) { - return -1; - } else if (Decode(a) > Decode(b)) { - return +1; - } else { - return 0; - } - } - - int operator()(const char* a, const DecodedType b) const { - if (Decode(a) < b) { - return -1; - } else if (Decode(a) > b) { - return +1; - } else { - return 0; - } - } -}; - -using TestInlineSkipList = InlineSkipList; - -class InlineSkipTest : public testing::Test { - public: - void Insert(TestInlineSkipList* list, Key key) { - char* buf = list->AllocateKey(sizeof(Key)); - memcpy(buf, &key, sizeof(Key)); - list->Insert(buf); - keys_.insert(key); - } - - bool InsertWithHint(TestInlineSkipList* list, Key key, void** hint) { - char* buf = list->AllocateKey(sizeof(Key)); - memcpy(buf, &key, sizeof(Key)); - bool res = list->InsertWithHint(buf, hint); - keys_.insert(key); - return res; - } - - void Validate(TestInlineSkipList* list) { - // Check keys exist. - for (Key key : keys_) { - ASSERT_TRUE(list->Contains(Encode(&key))); - } - // Iterate over the list, make sure keys appears in order and no extra - // keys exist. - TestInlineSkipList::Iterator iter(list); - ASSERT_FALSE(iter.Valid()); - Key zero = 0; - iter.Seek(Encode(&zero)); - for (Key key : keys_) { - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(key, Decode(iter.key())); - iter.Next(); - } - ASSERT_FALSE(iter.Valid()); - // Validate the list is well-formed. - list->TEST_Validate(); - } - - private: - std::set keys_; -}; - -TEST_F(InlineSkipTest, Empty) { - Arena arena; - TestComparator cmp; - InlineSkipList list(cmp, &arena); - Key key = 10; - ASSERT_TRUE(!list.Contains(Encode(&key))); - - InlineSkipList::Iterator iter(&list); - ASSERT_TRUE(!iter.Valid()); - iter.SeekToFirst(); - ASSERT_TRUE(!iter.Valid()); - key = 100; - iter.Seek(Encode(&key)); - ASSERT_TRUE(!iter.Valid()); - iter.SeekForPrev(Encode(&key)); - ASSERT_TRUE(!iter.Valid()); - iter.SeekToLast(); - ASSERT_TRUE(!iter.Valid()); -} - -TEST_F(InlineSkipTest, InsertAndLookup) { - const int N = 2000; - const int R = 5000; - Random rnd(1000); - std::set keys; - ConcurrentArena arena; - TestComparator cmp; - InlineSkipList list(cmp, &arena); - for (int i = 0; i < N; i++) { - Key key = rnd.Next() % R; - if (keys.insert(key).second) { - char* buf = list.AllocateKey(sizeof(Key)); - memcpy(buf, &key, sizeof(Key)); - list.Insert(buf); - } - } - - for (Key i = 0; i < R; i++) { - if (list.Contains(Encode(&i))) { - ASSERT_EQ(keys.count(i), 1U); - } else { - ASSERT_EQ(keys.count(i), 0U); - } - } - - // Simple iterator tests - { - InlineSkipList::Iterator iter(&list); - ASSERT_TRUE(!iter.Valid()); - - uint64_t zero = 0; - iter.Seek(Encode(&zero)); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.begin()), Decode(iter.key())); - - uint64_t max_key = R - 1; - iter.SeekForPrev(Encode(&max_key)); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.rbegin()), Decode(iter.key())); - - iter.SeekToFirst(); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.begin()), Decode(iter.key())); - - iter.SeekToLast(); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.rbegin()), Decode(iter.key())); - } - - // Forward iteration test - for (Key i = 0; i < R; i++) { - InlineSkipList::Iterator iter(&list); - iter.Seek(Encode(&i)); - - // Compare against model iterator - std::set::iterator model_iter = keys.lower_bound(i); - for (int j = 0; j < 3; j++) { - if (model_iter == keys.end()) { - ASSERT_TRUE(!iter.Valid()); - break; - } else { - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*model_iter, Decode(iter.key())); - ++model_iter; - iter.Next(); - } - } - } - - // Backward iteration test - for (Key i = 0; i < R; i++) { - InlineSkipList::Iterator iter(&list); - iter.SeekForPrev(Encode(&i)); - - // Compare against model iterator - std::set::iterator model_iter = keys.upper_bound(i); - for (int j = 0; j < 3; j++) { - if (model_iter == keys.begin()) { - ASSERT_TRUE(!iter.Valid()); - break; - } else { - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*--model_iter, Decode(iter.key())); - iter.Prev(); - } - } - } -} - -TEST_F(InlineSkipTest, InsertWithHint_Sequential) { - const int N = 100000; - Arena arena; - TestComparator cmp; - TestInlineSkipList list(cmp, &arena); - void* hint = nullptr; - for (int i = 0; i < N; i++) { - Key key = i; - InsertWithHint(&list, key, &hint); - } - Validate(&list); -} - -TEST_F(InlineSkipTest, InsertWithHint_MultipleHints) { - const int N = 100000; - const int S = 100; - Random rnd(534); - Arena arena; - TestComparator cmp; - TestInlineSkipList list(cmp, &arena); - void* hints[S]; - Key last_key[S]; - for (int i = 0; i < S; i++) { - hints[i] = nullptr; - last_key[i] = 0; - } - for (int i = 0; i < N; i++) { - Key s = rnd.Uniform(S); - Key key = (s << 32) + (++last_key[s]); - InsertWithHint(&list, key, &hints[s]); - } - Validate(&list); -} - -TEST_F(InlineSkipTest, InsertWithHint_MultipleHintsRandom) { - const int N = 100000; - const int S = 100; - Random rnd(534); - Arena arena; - TestComparator cmp; - TestInlineSkipList list(cmp, &arena); - void* hints[S]; - for (int i = 0; i < S; i++) { - hints[i] = nullptr; - } - for (int i = 0; i < N; i++) { - Key s = rnd.Uniform(S); - Key key = (s << 32) + rnd.Next(); - InsertWithHint(&list, key, &hints[s]); - } - Validate(&list); -} - -TEST_F(InlineSkipTest, InsertWithHint_CompatibleWithInsertWithoutHint) { - const int N = 100000; - const int S1 = 100; - const int S2 = 100; - Random rnd(534); - Arena arena; - TestComparator cmp; - TestInlineSkipList list(cmp, &arena); - std::unordered_set used; - Key with_hint[S1]; - Key without_hint[S2]; - void* hints[S1]; - for (int i = 0; i < S1; i++) { - hints[i] = nullptr; - while (true) { - Key s = rnd.Next(); - if (used.insert(s).second) { - with_hint[i] = s; - break; - } - } - } - for (int i = 0; i < S2; i++) { - while (true) { - Key s = rnd.Next(); - if (used.insert(s).second) { - without_hint[i] = s; - break; - } - } - } - for (int i = 0; i < N; i++) { - Key s = rnd.Uniform(S1 + S2); - if (s < S1) { - Key key = (with_hint[s] << 32) + rnd.Next(); - InsertWithHint(&list, key, &hints[s]); - } else { - Key key = (without_hint[s - S1] << 32) + rnd.Next(); - Insert(&list, key); - } - } - Validate(&list); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -// We want to make sure that with a single writer and multiple -// concurrent readers (with no synchronization other than when a -// reader's iterator is created), the reader always observes all the -// data that was present in the skip list when the iterator was -// constructor. Because insertions are happening concurrently, we may -// also observe new values that were inserted since the iterator was -// constructed, but we should never miss any values that were present -// at iterator construction time. -// -// We generate multi-part keys: -// -// where: -// key is in range [0..K-1] -// gen is a generation number for key -// hash is hash(key,gen) -// -// The insertion code picks a random key, sets gen to be 1 + the last -// generation number inserted for that key, and sets hash to Hash(key,gen). -// -// At the beginning of a read, we snapshot the last inserted -// generation number for each key. We then iterate, including random -// calls to Next() and Seek(). For every key we encounter, we -// check that it is either expected given the initial snapshot or has -// been concurrently added since the iterator started. -class ConcurrentTest { - public: - static const uint32_t K = 8; - - private: - static uint64_t key(Key key) { return (key >> 40); } - static uint64_t gen(Key key) { return (key >> 8) & 0xffffffffu; } - static uint64_t hash(Key key) { return key & 0xff; } - - static uint64_t HashNumbers(uint64_t k, uint64_t g) { - uint64_t data[2] = {k, g}; - return Hash(reinterpret_cast(data), sizeof(data), 0); - } - - static Key MakeKey(uint64_t k, uint64_t g) { - assert(sizeof(Key) == sizeof(uint64_t)); - assert(k <= K); // We sometimes pass K to seek to the end of the skiplist - assert(g <= 0xffffffffu); - return ((k << 40) | (g << 8) | (HashNumbers(k, g) & 0xff)); - } - - static bool IsValidKey(Key k) { - return hash(k) == (HashNumbers(key(k), gen(k)) & 0xff); - } - - static Key RandomTarget(Random* rnd) { - switch (rnd->Next() % 10) { - case 0: - // Seek to beginning - return MakeKey(0, 0); - case 1: - // Seek to end - return MakeKey(K, 0); - default: - // Seek to middle - return MakeKey(rnd->Next() % K, 0); - } - } - - // Per-key generation - struct State { - std::atomic generation[K]; - void Set(int k, int v) { - generation[k].store(v, std::memory_order_release); - } - int Get(int k) { return generation[k].load(std::memory_order_acquire); } - - State() { - for (unsigned int k = 0; k < K; k++) { - Set(k, 0); - } - } - }; - - // Current state of the test - State current_; - - ConcurrentArena arena_; - - // InlineSkipList is not protected by mu_. We just use a single writer - // thread to modify it. - InlineSkipList list_; - - public: - ConcurrentTest() : list_(TestComparator(), &arena_) {} - - // REQUIRES: No concurrent calls to WriteStep or ConcurrentWriteStep - void WriteStep(Random* rnd) { - const uint32_t k = rnd->Next() % K; - const int g = current_.Get(k) + 1; - const Key new_key = MakeKey(k, g); - char* buf = list_.AllocateKey(sizeof(Key)); - memcpy(buf, &new_key, sizeof(Key)); - list_.Insert(buf); - current_.Set(k, g); - } - - // REQUIRES: No concurrent calls for the same k - void ConcurrentWriteStep(uint32_t k, bool use_hint = false) { - const int g = current_.Get(k) + 1; - const Key new_key = MakeKey(k, g); - char* buf = list_.AllocateKey(sizeof(Key)); - memcpy(buf, &new_key, sizeof(Key)); - if (use_hint) { - void* hint = nullptr; - list_.InsertWithHintConcurrently(buf, &hint); - delete[] reinterpret_cast(hint); - } else { - list_.InsertConcurrently(buf); - } - ASSERT_EQ(g, current_.Get(k) + 1); - current_.Set(k, g); - } - - void ReadStep(Random* rnd) { - // Remember the initial committed state of the skiplist. - State initial_state; - for (unsigned int k = 0; k < K; k++) { - initial_state.Set(k, current_.Get(k)); - } - - Key pos = RandomTarget(rnd); - InlineSkipList::Iterator iter(&list_); - iter.Seek(Encode(&pos)); - while (true) { - Key current; - if (!iter.Valid()) { - current = MakeKey(K, 0); - } else { - current = Decode(iter.key()); - ASSERT_TRUE(IsValidKey(current)) << current; - } - ASSERT_LE(pos, current) << "should not go backwards"; - - // Verify that everything in [pos,current) was not present in - // initial_state. - while (pos < current) { - ASSERT_LT(key(pos), K) << pos; - - // Note that generation 0 is never inserted, so it is ok if - // <*,0,*> is missing. - ASSERT_TRUE((gen(pos) == 0U) || - (gen(pos) > static_cast(initial_state.Get( - static_cast(key(pos)))))) - << "key: " << key(pos) << "; gen: " << gen(pos) - << "; initgen: " << initial_state.Get(static_cast(key(pos))); - - // Advance to next key in the valid key space - if (key(pos) < key(current)) { - pos = MakeKey(key(pos) + 1, 0); - } else { - pos = MakeKey(key(pos), gen(pos) + 1); - } - } - - if (!iter.Valid()) { - break; - } - - if (rnd->Next() % 2) { - iter.Next(); - pos = MakeKey(key(pos), gen(pos) + 1); - } else { - Key new_target = RandomTarget(rnd); - if (new_target > pos) { - pos = new_target; - iter.Seek(Encode(&new_target)); - } - } - } - } -}; -const uint32_t ConcurrentTest::K; - -// Simple test that does single-threaded testing of the ConcurrentTest -// scaffolding. -TEST_F(InlineSkipTest, ConcurrentReadWithoutThreads) { - ConcurrentTest test; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 10000; i++) { - test.ReadStep(&rnd); - test.WriteStep(&rnd); - } -} - -TEST_F(InlineSkipTest, ConcurrentInsertWithoutThreads) { - ConcurrentTest test; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 10000; i++) { - test.ReadStep(&rnd); - uint32_t base = rnd.Next(); - for (int j = 0; j < 4; ++j) { - test.ConcurrentWriteStep((base + j) % ConcurrentTest::K); - } - } -} - -class TestState { - public: - ConcurrentTest t_; - bool use_hint_; - int seed_; - std::atomic quit_flag_; - std::atomic next_writer_; - - enum ReaderState { STARTING, RUNNING, DONE }; - - explicit TestState(int s) - : seed_(s), - quit_flag_(false), - state_(STARTING), - pending_writers_(0), - state_cv_(&mu_) {} - - void Wait(ReaderState s) { - mu_.Lock(); - while (state_ != s) { - state_cv_.Wait(); - } - mu_.Unlock(); - } - - void Change(ReaderState s) { - mu_.Lock(); - state_ = s; - state_cv_.Signal(); - mu_.Unlock(); - } - - void AdjustPendingWriters(int delta) { - mu_.Lock(); - pending_writers_ += delta; - if (pending_writers_ == 0) { - state_cv_.Signal(); - } - mu_.Unlock(); - } - - void WaitForPendingWriters() { - mu_.Lock(); - while (pending_writers_ != 0) { - state_cv_.Wait(); - } - mu_.Unlock(); - } - - private: - port::Mutex mu_; - ReaderState state_; - int pending_writers_; - port::CondVar state_cv_; -}; - -static void ConcurrentReader(void* arg) { - TestState* state = reinterpret_cast(arg); - Random rnd(state->seed_); - int64_t reads = 0; - state->Change(TestState::RUNNING); - while (!state->quit_flag_.load(std::memory_order_acquire)) { - state->t_.ReadStep(&rnd); - ++reads; - } - state->Change(TestState::DONE); -} - -static void ConcurrentWriter(void* arg) { - TestState* state = reinterpret_cast(arg); - uint32_t k = state->next_writer_++ % ConcurrentTest::K; - state->t_.ConcurrentWriteStep(k, state->use_hint_); - state->AdjustPendingWriters(-1); -} - -static void RunConcurrentRead(int run) { - const int seed = test::RandomSeed() + (run * 100); - Random rnd(seed); - const int N = 1000; - const int kSize = 1000; - for (int i = 0; i < N; i++) { - if ((i % 100) == 0) { - fprintf(stderr, "Run %d of %d\n", i, N); - } - TestState state(seed + 1); - Env::Default()->SetBackgroundThreads(1); - Env::Default()->Schedule(ConcurrentReader, &state); - state.Wait(TestState::RUNNING); - for (int k = 0; k < kSize; ++k) { - state.t_.WriteStep(&rnd); - } - state.quit_flag_.store(true, std::memory_order_release); - state.Wait(TestState::DONE); - } -} - -static void RunConcurrentInsert(int run, bool use_hint = false, - int write_parallelism = 4) { - Env::Default()->SetBackgroundThreads(1 + write_parallelism, - Env::Priority::LOW); - const int seed = test::RandomSeed() + (run * 100); - Random rnd(seed); - const int N = 1000; - const int kSize = 1000; - for (int i = 0; i < N; i++) { - if ((i % 100) == 0) { - fprintf(stderr, "Run %d of %d\n", i, N); - } - TestState state(seed + 1); - state.use_hint_ = use_hint; - Env::Default()->Schedule(ConcurrentReader, &state); - state.Wait(TestState::RUNNING); - for (int k = 0; k < kSize; k += write_parallelism) { - state.next_writer_ = rnd.Next(); - state.AdjustPendingWriters(write_parallelism); - for (int p = 0; p < write_parallelism; ++p) { - Env::Default()->Schedule(ConcurrentWriter, &state); - } - state.WaitForPendingWriters(); - } - state.quit_flag_.store(true, std::memory_order_release); - state.Wait(TestState::DONE); - } -} - -TEST_F(InlineSkipTest, ConcurrentRead1) { RunConcurrentRead(1); } -TEST_F(InlineSkipTest, ConcurrentRead2) { RunConcurrentRead(2); } -TEST_F(InlineSkipTest, ConcurrentRead3) { RunConcurrentRead(3); } -TEST_F(InlineSkipTest, ConcurrentRead4) { RunConcurrentRead(4); } -TEST_F(InlineSkipTest, ConcurrentRead5) { RunConcurrentRead(5); } -TEST_F(InlineSkipTest, ConcurrentInsert1) { RunConcurrentInsert(1); } -TEST_F(InlineSkipTest, ConcurrentInsert2) { RunConcurrentInsert(2); } -TEST_F(InlineSkipTest, ConcurrentInsert3) { RunConcurrentInsert(3); } -TEST_F(InlineSkipTest, ConcurrentInsertWithHint1) { - RunConcurrentInsert(1, true); -} -TEST_F(InlineSkipTest, ConcurrentInsertWithHint2) { - RunConcurrentInsert(2, true); -} -TEST_F(InlineSkipTest, ConcurrentInsertWithHint3) { - RunConcurrentInsert(3, true); -} - -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/memtable/skiplist_test.cc b/memtable/skiplist_test.cc deleted file mode 100644 index a07088511..000000000 --- a/memtable/skiplist_test.cc +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "memtable/skiplist.h" - -#include - -#include "memory/arena.h" -#include "rocksdb/env.h" -#include "test_util/testharness.h" -#include "util/hash.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -using Key = uint64_t; - -struct TestComparator { - int operator()(const Key& a, const Key& b) const { - if (a < b) { - return -1; - } else if (a > b) { - return +1; - } else { - return 0; - } - } -}; - -class SkipTest : public testing::Test {}; - -TEST_F(SkipTest, Empty) { - Arena arena; - TestComparator cmp; - SkipList list(cmp, &arena); - ASSERT_TRUE(!list.Contains(10)); - - SkipList::Iterator iter(&list); - ASSERT_TRUE(!iter.Valid()); - iter.SeekToFirst(); - ASSERT_TRUE(!iter.Valid()); - iter.Seek(100); - ASSERT_TRUE(!iter.Valid()); - iter.SeekForPrev(100); - ASSERT_TRUE(!iter.Valid()); - iter.SeekToLast(); - ASSERT_TRUE(!iter.Valid()); -} - -TEST_F(SkipTest, InsertAndLookup) { - const int N = 2000; - const int R = 5000; - Random rnd(1000); - std::set keys; - Arena arena; - TestComparator cmp; - SkipList list(cmp, &arena); - for (int i = 0; i < N; i++) { - Key key = rnd.Next() % R; - if (keys.insert(key).second) { - list.Insert(key); - } - } - - for (int i = 0; i < R; i++) { - if (list.Contains(i)) { - ASSERT_EQ(keys.count(i), 1U); - } else { - ASSERT_EQ(keys.count(i), 0U); - } - } - - // Simple iterator tests - { - SkipList::Iterator iter(&list); - ASSERT_TRUE(!iter.Valid()); - - iter.Seek(0); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.begin()), iter.key()); - - iter.SeekForPrev(R - 1); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.rbegin()), iter.key()); - - iter.SeekToFirst(); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.begin()), iter.key()); - - iter.SeekToLast(); - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*(keys.rbegin()), iter.key()); - } - - // Forward iteration test - for (int i = 0; i < R; i++) { - SkipList::Iterator iter(&list); - iter.Seek(i); - - // Compare against model iterator - std::set::iterator model_iter = keys.lower_bound(i); - for (int j = 0; j < 3; j++) { - if (model_iter == keys.end()) { - ASSERT_TRUE(!iter.Valid()); - break; - } else { - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*model_iter, iter.key()); - ++model_iter; - iter.Next(); - } - } - } - - // Backward iteration test - for (int i = 0; i < R; i++) { - SkipList::Iterator iter(&list); - iter.SeekForPrev(i); - - // Compare against model iterator - std::set::iterator model_iter = keys.upper_bound(i); - for (int j = 0; j < 3; j++) { - if (model_iter == keys.begin()) { - ASSERT_TRUE(!iter.Valid()); - break; - } else { - ASSERT_TRUE(iter.Valid()); - ASSERT_EQ(*--model_iter, iter.key()); - iter.Prev(); - } - } - } -} - -// We want to make sure that with a single writer and multiple -// concurrent readers (with no synchronization other than when a -// reader's iterator is created), the reader always observes all the -// data that was present in the skip list when the iterator was -// constructor. Because insertions are happening concurrently, we may -// also observe new values that were inserted since the iterator was -// constructed, but we should never miss any values that were present -// at iterator construction time. -// -// We generate multi-part keys: -// -// where: -// key is in range [0..K-1] -// gen is a generation number for key -// hash is hash(key,gen) -// -// The insertion code picks a random key, sets gen to be 1 + the last -// generation number inserted for that key, and sets hash to Hash(key,gen). -// -// At the beginning of a read, we snapshot the last inserted -// generation number for each key. We then iterate, including random -// calls to Next() and Seek(). For every key we encounter, we -// check that it is either expected given the initial snapshot or has -// been concurrently added since the iterator started. -class ConcurrentTest { - private: - static const uint32_t K = 4; - - static uint64_t key(Key key) { return (key >> 40); } - static uint64_t gen(Key key) { return (key >> 8) & 0xffffffffu; } - static uint64_t hash(Key key) { return key & 0xff; } - - static uint64_t HashNumbers(uint64_t k, uint64_t g) { - uint64_t data[2] = {k, g}; - return Hash(reinterpret_cast(data), sizeof(data), 0); - } - - static Key MakeKey(uint64_t k, uint64_t g) { - assert(sizeof(Key) == sizeof(uint64_t)); - assert(k <= K); // We sometimes pass K to seek to the end of the skiplist - assert(g <= 0xffffffffu); - return ((k << 40) | (g << 8) | (HashNumbers(k, g) & 0xff)); - } - - static bool IsValidKey(Key k) { - return hash(k) == (HashNumbers(key(k), gen(k)) & 0xff); - } - - static Key RandomTarget(Random* rnd) { - switch (rnd->Next() % 10) { - case 0: - // Seek to beginning - return MakeKey(0, 0); - case 1: - // Seek to end - return MakeKey(K, 0); - default: - // Seek to middle - return MakeKey(rnd->Next() % K, 0); - } - } - - // Per-key generation - struct State { - std::atomic generation[K]; - void Set(int k, int v) { - generation[k].store(v, std::memory_order_release); - } - int Get(int k) { return generation[k].load(std::memory_order_acquire); } - - State() { - for (unsigned int k = 0; k < K; k++) { - Set(k, 0); - } - } - }; - - // Current state of the test - State current_; - - Arena arena_; - - // SkipList is not protected by mu_. We just use a single writer - // thread to modify it. - SkipList list_; - - public: - ConcurrentTest() : list_(TestComparator(), &arena_) {} - - // REQUIRES: External synchronization - void WriteStep(Random* rnd) { - const uint32_t k = rnd->Next() % K; - const int g = current_.Get(k) + 1; - const Key new_key = MakeKey(k, g); - list_.Insert(new_key); - current_.Set(k, g); - } - - void ReadStep(Random* rnd) { - // Remember the initial committed state of the skiplist. - State initial_state; - for (unsigned int k = 0; k < K; k++) { - initial_state.Set(k, current_.Get(k)); - } - - Key pos = RandomTarget(rnd); - SkipList::Iterator iter(&list_); - iter.Seek(pos); - while (true) { - Key current; - if (!iter.Valid()) { - current = MakeKey(K, 0); - } else { - current = iter.key(); - ASSERT_TRUE(IsValidKey(current)) << current; - } - ASSERT_LE(pos, current) << "should not go backwards"; - - // Verify that everything in [pos,current) was not present in - // initial_state. - while (pos < current) { - ASSERT_LT(key(pos), K) << pos; - - // Note that generation 0 is never inserted, so it is ok if - // <*,0,*> is missing. - ASSERT_TRUE((gen(pos) == 0U) || - (gen(pos) > static_cast(initial_state.Get( - static_cast(key(pos)))))) - << "key: " << key(pos) << "; gen: " << gen(pos) - << "; initgen: " << initial_state.Get(static_cast(key(pos))); - - // Advance to next key in the valid key space - if (key(pos) < key(current)) { - pos = MakeKey(key(pos) + 1, 0); - } else { - pos = MakeKey(key(pos), gen(pos) + 1); - } - } - - if (!iter.Valid()) { - break; - } - - if (rnd->Next() % 2) { - iter.Next(); - pos = MakeKey(key(pos), gen(pos) + 1); - } else { - Key new_target = RandomTarget(rnd); - if (new_target > pos) { - pos = new_target; - iter.Seek(new_target); - } - } - } - } -}; -const uint32_t ConcurrentTest::K; - -// Simple test that does single-threaded testing of the ConcurrentTest -// scaffolding. -TEST_F(SkipTest, ConcurrentWithoutThreads) { - ConcurrentTest test; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 10000; i++) { - test.ReadStep(&rnd); - test.WriteStep(&rnd); - } -} - -class TestState { - public: - ConcurrentTest t_; - int seed_; - std::atomic quit_flag_; - - enum ReaderState { STARTING, RUNNING, DONE }; - - explicit TestState(int s) - : seed_(s), quit_flag_(false), state_(STARTING), state_cv_(&mu_) {} - - void Wait(ReaderState s) { - mu_.Lock(); - while (state_ != s) { - state_cv_.Wait(); - } - mu_.Unlock(); - } - - void Change(ReaderState s) { - mu_.Lock(); - state_ = s; - state_cv_.Signal(); - mu_.Unlock(); - } - - private: - port::Mutex mu_; - ReaderState state_; - port::CondVar state_cv_; -}; - -static void ConcurrentReader(void* arg) { - TestState* state = reinterpret_cast(arg); - Random rnd(state->seed_); - int64_t reads = 0; - state->Change(TestState::RUNNING); - while (!state->quit_flag_.load(std::memory_order_acquire)) { - state->t_.ReadStep(&rnd); - ++reads; - } - state->Change(TestState::DONE); -} - -static void RunConcurrent(int run) { - const int seed = test::RandomSeed() + (run * 100); - Random rnd(seed); - const int N = 1000; - const int kSize = 1000; - for (int i = 0; i < N; i++) { - if ((i % 100) == 0) { - fprintf(stderr, "Run %d of %d\n", i, N); - } - TestState state(seed + 1); - Env::Default()->SetBackgroundThreads(1); - Env::Default()->Schedule(ConcurrentReader, &state); - state.Wait(TestState::RUNNING); - for (int k = 0; k < kSize; k++) { - state.t_.WriteStep(&rnd); - } - state.quit_flag_.store(true, std::memory_order_release); - state.Wait(TestState::DONE); - } -} - -TEST_F(SkipTest, Concurrent1) { RunConcurrent(1); } -TEST_F(SkipTest, Concurrent2) { RunConcurrent(2); } -TEST_F(SkipTest, Concurrent3) { RunConcurrent(3); } -TEST_F(SkipTest, Concurrent4) { RunConcurrent(4); } -TEST_F(SkipTest, Concurrent5) { RunConcurrent(5); } - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/memtable/write_buffer_manager_test.cc b/memtable/write_buffer_manager_test.cc deleted file mode 100644 index c992d2eab..000000000 --- a/memtable/write_buffer_manager_test.cc +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/write_buffer_manager.h" - -#include "rocksdb/advanced_cache.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { -class WriteBufferManagerTest : public testing::Test {}; - -const size_t kSizeDummyEntry = 256 * 1024; - -TEST_F(WriteBufferManagerTest, ShouldFlush) { - // A write buffer manager of size 10MB - std::unique_ptr wbf( - new WriteBufferManager(10 * 1024 * 1024)); - - wbf->ReserveMem(8 * 1024 * 1024); - ASSERT_FALSE(wbf->ShouldFlush()); - // 90% of the hard limit will hit the condition - wbf->ReserveMem(1 * 1024 * 1024); - ASSERT_TRUE(wbf->ShouldFlush()); - // Scheduling for freeing will release the condition - wbf->ScheduleFreeMem(1 * 1024 * 1024); - ASSERT_FALSE(wbf->ShouldFlush()); - - wbf->ReserveMem(2 * 1024 * 1024); - ASSERT_TRUE(wbf->ShouldFlush()); - - wbf->ScheduleFreeMem(4 * 1024 * 1024); - // 11MB total, 6MB mutable. hard limit still hit - ASSERT_TRUE(wbf->ShouldFlush()); - - wbf->ScheduleFreeMem(2 * 1024 * 1024); - // 11MB total, 4MB mutable. hard limit stills but won't flush because more - // than half data is already being flushed. - ASSERT_FALSE(wbf->ShouldFlush()); - - wbf->ReserveMem(4 * 1024 * 1024); - // 15 MB total, 8MB mutable. - ASSERT_TRUE(wbf->ShouldFlush()); - - wbf->FreeMem(7 * 1024 * 1024); - // 8MB total, 8MB mutable. - ASSERT_FALSE(wbf->ShouldFlush()); - - // change size: 8M limit, 7M mutable limit - wbf->SetBufferSize(8 * 1024 * 1024); - // 8MB total, 8MB mutable. - ASSERT_TRUE(wbf->ShouldFlush()); - - wbf->ScheduleFreeMem(2 * 1024 * 1024); - // 8MB total, 6MB mutable. - ASSERT_TRUE(wbf->ShouldFlush()); - - wbf->FreeMem(2 * 1024 * 1024); - // 6MB total, 6MB mutable. - ASSERT_FALSE(wbf->ShouldFlush()); - - wbf->ReserveMem(1 * 1024 * 1024); - // 7MB total, 7MB mutable. - ASSERT_FALSE(wbf->ShouldFlush()); - - wbf->ReserveMem(1 * 1024 * 1024); - // 8MB total, 8MB mutable. - ASSERT_TRUE(wbf->ShouldFlush()); - - wbf->ScheduleFreeMem(1 * 1024 * 1024); - wbf->FreeMem(1 * 1024 * 1024); - // 7MB total, 7MB mutable. - ASSERT_FALSE(wbf->ShouldFlush()); -} - -class ChargeWriteBufferTest : public testing::Test {}; - -TEST_F(ChargeWriteBufferTest, Basic) { - constexpr std::size_t kMetaDataChargeOverhead = 10000; - - LRUCacheOptions co; - // 1GB cache - co.capacity = 1024 * 1024 * 1024; - co.num_shard_bits = 4; - co.metadata_charge_policy = kDontChargeCacheMetadata; - std::shared_ptr cache = NewLRUCache(co); - // A write buffer manager of size 50MB - std::unique_ptr wbf( - new WriteBufferManager(50 * 1024 * 1024, cache)); - - // Allocate 333KB will allocate 512KB, memory_used_ = 333KB - wbf->ReserveMem(333 * 1024); - // 2 dummy entries are added for size 333 KB - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 2 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 2 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 2 * 256 * 1024 + kMetaDataChargeOverhead); - - // Allocate another 512KB, memory_used_ = 845KB - wbf->ReserveMem(512 * 1024); - // 2 more dummy entries are added for size 512 KB - // since ceil((memory_used_ - dummy_entries_in_cache_usage) % kSizeDummyEntry) - // = 2 - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 4 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 4 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 4 * 256 * 1024 + kMetaDataChargeOverhead); - - // Allocate another 10MB, memory_used_ = 11085KB - wbf->ReserveMem(10 * 1024 * 1024); - // 40 more entries are added for size 10 * 1024 * 1024 KB - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 44 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 44 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 44 * 256 * 1024 + kMetaDataChargeOverhead); - - // Free 1MB, memory_used_ = 10061KB - // It will not cause any change in cache cost - // since memory_used_ > dummy_entries_in_cache_usage * (3/4) - wbf->FreeMem(1 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 44 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 44 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 44 * 256 * 1024 + kMetaDataChargeOverhead); - ASSERT_FALSE(wbf->ShouldFlush()); - - // Allocate another 41MB, memory_used_ = 52045KB - wbf->ReserveMem(41 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 204 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 204 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), - 204 * 256 * 1024 + kMetaDataChargeOverhead); - ASSERT_TRUE(wbf->ShouldFlush()); - - ASSERT_TRUE(wbf->ShouldFlush()); - - // Schedule free 20MB, memory_used_ = 52045KB - // It will not cause any change in memory_used and cache cost - wbf->ScheduleFreeMem(20 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 204 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 204 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), - 204 * 256 * 1024 + kMetaDataChargeOverhead); - // Still need flush as the hard limit hits - ASSERT_TRUE(wbf->ShouldFlush()); - - // Free 20MB, memory_used_ = 31565KB - // It will releae 80 dummy entries from cache since - // since memory_used_ < dummy_entries_in_cache_usage * (3/4) - // and floor((dummy_entries_in_cache_usage - memory_used_) % kSizeDummyEntry) - // = 80 - wbf->FreeMem(20 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 124 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 124 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), - 124 * 256 * 1024 + kMetaDataChargeOverhead); - - ASSERT_FALSE(wbf->ShouldFlush()); - - // Free 16KB, memory_used_ = 31549KB - // It will not release any dummy entry since memory_used_ >= - // dummy_entries_in_cache_usage * (3/4) - wbf->FreeMem(16 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 124 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 124 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), - 124 * 256 * 1024 + kMetaDataChargeOverhead); - - // Free 20MB, memory_used_ = 11069KB - // It will releae 80 dummy entries from cache - // since memory_used_ < dummy_entries_in_cache_usage * (3/4) - // and floor((dummy_entries_in_cache_usage - memory_used_) % kSizeDummyEntry) - // = 80 - wbf->FreeMem(20 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 44 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 44 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 44 * 256 * 1024 + kMetaDataChargeOverhead); - - // Free 1MB, memory_used_ = 10045KB - // It will not cause any change in cache cost - // since memory_used_ > dummy_entries_in_cache_usage * (3/4) - wbf->FreeMem(1 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 44 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 44 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 44 * 256 * 1024 + kMetaDataChargeOverhead); - - // Reserve 512KB, memory_used_ = 10557KB - // It will not casue any change in cache cost - // since memory_used_ > dummy_entries_in_cache_usage * (3/4) - // which reflects the benefit of saving dummy entry insertion on memory - // reservation after delay decrease - wbf->ReserveMem(512 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 44 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 44 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 44 * 256 * 1024 + kMetaDataChargeOverhead); - - // Destroy write buffer manger should free everything - wbf.reset(); - ASSERT_EQ(cache->GetPinnedUsage(), 0); -} - -TEST_F(ChargeWriteBufferTest, BasicWithNoBufferSizeLimit) { - constexpr std::size_t kMetaDataChargeOverhead = 10000; - // 1GB cache - std::shared_ptr cache = NewLRUCache(1024 * 1024 * 1024, 4); - // A write buffer manager of size 256MB - std::unique_ptr wbf(new WriteBufferManager(0, cache)); - - // Allocate 10MB, memory_used_ = 10240KB - // It will allocate 40 dummy entries - wbf->ReserveMem(10 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 40 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 40 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 40 * 256 * 1024 + kMetaDataChargeOverhead); - - ASSERT_FALSE(wbf->ShouldFlush()); - - // Free 9MB, memory_used_ = 1024KB - // It will free 36 dummy entries - wbf->FreeMem(9 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 4 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 4 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 4 * 256 * 1024 + kMetaDataChargeOverhead); - - // Free 160KB gradually, memory_used_ = 864KB - // It will not cause any change - // since memory_used_ > dummy_entries_in_cache_usage * 3/4 - for (int i = 0; i < 40; i++) { - wbf->FreeMem(4 * 1024); - } - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 4 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 4 * 256 * 1024); - ASSERT_LT(cache->GetPinnedUsage(), 4 * 256 * 1024 + kMetaDataChargeOverhead); -} - -TEST_F(ChargeWriteBufferTest, BasicWithCacheFull) { - constexpr std::size_t kMetaDataChargeOverhead = 20000; - - // 12MB cache size with strict capacity - LRUCacheOptions lo; - lo.capacity = 12 * 1024 * 1024; - lo.num_shard_bits = 0; - lo.strict_capacity_limit = true; - std::shared_ptr cache = NewLRUCache(lo); - std::unique_ptr wbf(new WriteBufferManager(0, cache)); - - // Allocate 10MB, memory_used_ = 10240KB - wbf->ReserveMem(10 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 40 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 40 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 40 * kSizeDummyEntry + kMetaDataChargeOverhead); - - // Allocate 10MB, memory_used_ = 20480KB - // Some dummy entry insertion will fail due to full cache - wbf->ReserveMem(10 * 1024 * 1024); - ASSERT_GE(cache->GetPinnedUsage(), 40 * kSizeDummyEntry); - ASSERT_LE(cache->GetPinnedUsage(), 12 * 1024 * 1024); - ASSERT_LT(wbf->dummy_entries_in_cache_usage(), 80 * kSizeDummyEntry); - - // Free 15MB after encoutering cache full, memory_used_ = 5120KB - wbf->FreeMem(15 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 20 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 20 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 20 * kSizeDummyEntry + kMetaDataChargeOverhead); - - // Reserve 15MB, creating cache full again, memory_used_ = 20480KB - wbf->ReserveMem(15 * 1024 * 1024); - ASSERT_LE(cache->GetPinnedUsage(), 12 * 1024 * 1024); - ASSERT_LT(wbf->dummy_entries_in_cache_usage(), 80 * kSizeDummyEntry); - - // Increase capacity so next insert will fully succeed - cache->SetCapacity(40 * 1024 * 1024); - - // Allocate 10MB, memory_used_ = 30720KB - wbf->ReserveMem(10 * 1024 * 1024); - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 120 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 120 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 120 * kSizeDummyEntry + kMetaDataChargeOverhead); - - // Gradually release 20 MB - // It ended up sequentially releasing 32, 24, 18 dummy entries when - // memory_used_ decreases to 22528KB, 16384KB, 11776KB. - // In total, it releases 74 dummy entries - for (int i = 0; i < 40; i++) { - wbf->FreeMem(512 * 1024); - } - - ASSERT_EQ(wbf->dummy_entries_in_cache_usage(), 46 * kSizeDummyEntry); - ASSERT_GE(cache->GetPinnedUsage(), 46 * kSizeDummyEntry); - ASSERT_LT(cache->GetPinnedUsage(), - 46 * kSizeDummyEntry + kMetaDataChargeOverhead); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/microbench/CMakeLists.txt b/microbench/CMakeLists.txt deleted file mode 100644 index 483e97973..000000000 --- a/microbench/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -find_package(benchmark REQUIRED) -find_package(Threads REQUIRED) - -file(GLOB_RECURSE ALL_BENCH_CPP *.cc) -foreach(ONE_BENCH_CPP ${ALL_BENCH_CPP}) - get_filename_component(TARGET_NAME ${ONE_BENCH_CPP} NAME_WE) - add_executable(${TARGET_NAME} ${ONE_BENCH_CPP}) - target_link_libraries(${TARGET_NAME} ${ROCKSDB_LIB} benchmark::benchmark - ${CMAKE_THREAD_LIBS_INIT}) - # run benchmark like a test, if added, the benchmark tests could be run by `ctest -R Bench_` - # add_test(Bench_${TARGET_NAME} ${TARGET_NAME}) - list(APPEND ALL_BENCH_TARGETS ${TARGET_NAME}) -endforeach() -add_custom_target(microbench DEPENDS ${ALL_BENCH_TARGETS}) -add_custom_target(run_microbench - COMMAND for t in ${ALL_BENCH_TARGETS}\; do \.\/$$t \|\| exit 1\; done - DEPENDS ${ALL_BENCH_TARGETS}) diff --git a/microbench/README.md b/microbench/README.md deleted file mode 100644 index 290ca58d7..000000000 --- a/microbench/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# RocksDB Micro-Benchmark - -## Overview - -RocksDB micro-benchmark is a set of tests for benchmarking a single component or simple DB operations. The test artificially generates input data and executes the same operation with it to collect and report performance metrics. As it's focusing on testing a single, well-defined operation, the result is more precise and reproducible, which also has its limitation of not representing a real production use case. The test author needs to carefully design the microbench to represent its true purpose. - -The tests are based on [Google Benchmark](https://github.com/google/benchmark) library, which provides a standard framework for writing benchmarks. - -## How to Run -### Prerequisite -Install the [Google Benchmark](https://github.com/google/benchmark) version `1.6.0` or above. - -*Note: Google Benchmark `1.6.x` is incompatible with previous versions like `1.5.x`, please make sure you're using the newer version.* - -### Build and Run -With `Makefile`: -```bash -$ DEBUG_LEVEL=0 make run_microbench -``` -Or with cmake: -```bash -$ mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release -DWITH_BENCHMARK -$ make run_microbench -``` - -*Note: Please run the benchmark code in release build.* -### Run Single Test -Example: -```bash -$ make db_basic_bench -$ ./db_basic_bench --benchmark_filter= -``` - -## Best Practices -#### * Use the Same Test Directory Setting as Unittest -Most of the Micro-benchmark tests use the same test directory setup as unittest, so it could be overridden by: -```bash -$ TEST_TMPDIR=/mydata/tmp/ ./db_basic_bench --benchmark_filter= -``` -Please also follow that when designing new tests. - -#### * Avoid Using Debug API -Even though micro-benchmark is a test, avoid using internal Debug API like TEST_WaitForRun() which is designed for unittest. As benchmark tests are designed for release build, don't use any of that. - -#### * Pay Attention to Local Optimization -As a micro-benchmark is focusing on a single component or area, make sure it is a key part for impacting the overall application performance. - -The compiler might be able to optimize the code that not the same way as the whole application, and if the test data input is simple and small, it may be able to all cached in CPU memory, which is leading to a wrong metric. Take these into consideration when designing the tests. - -#### * Names of user-defined counters/metrics has to be `[A-Za-z0-9_]` -It's a restriction of the metrics collecting and reporting system RocksDB is using internally. It will also help integrate with more systems. - -#### * Minimize the Metrics Variation -Try reducing the test result variation, one way to check that is running the test multiple times and check the CV (Coefficient of Variation) reported by gbenchmark. -```bash -$ ./db_basic_bench --benchmark_filter= --benchmark_repetitions=10 -... -_cv 3.2% -``` -RocksDB has background compaction jobs which may cause the test result to vary a lot. If the micro-benchmark is not purposely testing the operation while compaction is in progress, it should wait for the compaction to finish (`db_impl->WaitForCompact()`) or disable auto-compaction. diff --git a/microbench/db_basic_bench.cc b/microbench/db_basic_bench.cc deleted file mode 100644 index 6c70ad21d..000000000 --- a/microbench/db_basic_bench.cc +++ /dev/null @@ -1,1575 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#ifndef OS_WIN -#include -#endif // ! OS_WIN - -#include "benchmark/benchmark.h" -#include "db/db_impl/db_impl.h" -#include "rocksdb/db.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/options.h" -#include "table/block_based/block.h" -#include "table/block_based/block_builder.h" -#include "util/random.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -class KeyGenerator { - public: - // Generate next key - // buff: the caller needs to make sure there's enough space for generated key - // offset: to control the group of the key, 0 means normal key, 1 means - // non-existing key, 2 is reserved prefix_only: only return a prefix - Slice Next(char* buff, int8_t offset = 0, bool prefix_only = false) { - assert(max_key_ < std::numeric_limits::max() / - MULTIPLIER); // TODO: add large key support - - uint32_t k; - if (is_sequential_) { - assert(next_sequential_key_ < max_key_); - k = (next_sequential_key_ % max_key_) * MULTIPLIER + offset; - if (next_sequential_key_ + 1 == max_key_) { - next_sequential_key_ = 0; - } else { - next_sequential_key_++; - } - } else { - k = (rnd_->Next() % max_key_) * MULTIPLIER + offset; - } - // TODO: make sure the buff is large enough - memset(buff, 0, key_size_); - if (prefix_num_ > 0) { - uint32_t prefix = (k % prefix_num_) * MULTIPLIER + offset; - Encode(buff, prefix); - if (prefix_only) { - return {buff, prefix_size_}; - } - } - Encode(buff + prefix_size_, k); - return {buff, key_size_}; - } - - // use internal buffer for generated key, make sure there's only one caller in - // single thread - Slice Next() { return Next(buff_); } - - // user internal buffer for generated prefix - Slice NextPrefix() { - assert(prefix_num_ > 0); - return Next(buff_, 0, true); - } - - // helper function to get non exist key - Slice NextNonExist() { return Next(buff_, 1); } - - Slice MaxKey(char* buff) const { - memset(buff, 0xff, key_size_); - return {buff, key_size_}; - } - - Slice MinKey(char* buff) const { - memset(buff, 0, key_size_); - return {buff, key_size_}; - } - - // max_key: the max key that it could generate - // prefix_num: the max prefix number - // key_size: in bytes - explicit KeyGenerator(Random* rnd, uint64_t max_key = 100 * 1024 * 1024, - size_t prefix_num = 0, size_t key_size = 10) { - prefix_num_ = prefix_num; - key_size_ = key_size; - max_key_ = max_key; - rnd_ = rnd; - if (prefix_num > 0) { - prefix_size_ = 4; // TODO: support different prefix_size - } - } - - // generate sequential keys - explicit KeyGenerator(uint64_t max_key = 100 * 1024 * 1024, - size_t key_size = 10) { - key_size_ = key_size; - max_key_ = max_key; - rnd_ = nullptr; - is_sequential_ = true; - } - - private: - Random* rnd_; - size_t prefix_num_ = 0; - size_t prefix_size_ = 0; - size_t key_size_; - uint64_t max_key_; - bool is_sequential_ = false; - uint32_t next_sequential_key_ = 0; - char buff_[256] = {0}; - const int MULTIPLIER = 3; - - void static Encode(char* buf, uint32_t value) { - if (port::kLittleEndian) { - buf[0] = static_cast((value >> 24) & 0xff); - buf[1] = static_cast((value >> 16) & 0xff); - buf[2] = static_cast((value >> 8) & 0xff); - buf[3] = static_cast(value & 0xff); - } else { - memcpy(buf, &value, sizeof(value)); - } - } -}; - -static void SetupDB(benchmark::State& state, Options& options, - std::unique_ptr* db, - const std::string& test_name = "") { - options.create_if_missing = true; - auto env = Env::Default(); - std::string db_path; - Status s = env->GetTestDirectory(&db_path); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - std::string db_name = - db_path + kFilePathSeparator + test_name + std::to_string(getpid()); - DestroyDB(db_name, options); - - DB* db_ptr = nullptr; - s = DB::Open(options, db_name, &db_ptr); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - db->reset(db_ptr); -} - -static void TeardownDB(benchmark::State& state, const std::unique_ptr& db, - const Options& options, KeyGenerator& kg) { - char min_buff[256], max_buff[256]; - const Range r(kg.MinKey(min_buff), kg.MaxKey(max_buff)); - uint64_t size; - Status s = db->GetApproximateSizes(&r, 1, &size); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - state.counters["db_size"] = static_cast(size); - - std::string db_name = db->GetName(); - s = db->Close(); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - DestroyDB(db_name, options); -} - -static void DBOpen(benchmark::State& state) { - // create DB - std::unique_ptr db; - Options options; - SetupDB(state, options, &db, "DBOpen"); - - std::string db_name = db->GetName(); - db->Close(); - - options.create_if_missing = false; - - auto rnd = Random(123); - - for (auto _ : state) { - { - DB* db_ptr = nullptr; - Status s = DB::Open(options, db_name, &db_ptr); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - db.reset(db_ptr); - } - state.PauseTiming(); - auto wo = WriteOptions(); - Status s; - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 100; j++) { - s = db->Put(wo, rnd.RandomString(10), rnd.RandomString(100)); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - s = db->Flush(FlushOptions()); - } - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - s = db->Close(); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - state.ResumeTiming(); - } - DestroyDB(db_name, options); -} - -BENCHMARK(DBOpen)->Iterations(200); // specify iteration number as the db size - // is impacted by iteration number - -static void DBClose(benchmark::State& state) { - // create DB - std::unique_ptr db; - Options options; - SetupDB(state, options, &db, "DBClose"); - - std::string db_name = db->GetName(); - db->Close(); - - options.create_if_missing = false; - - auto rnd = Random(12345); - - for (auto _ : state) { - state.PauseTiming(); - { - DB* db_ptr = nullptr; - Status s = DB::Open(options, db_name, &db_ptr); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - db.reset(db_ptr); - } - auto wo = WriteOptions(); - Status s; - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 100; j++) { - s = db->Put(wo, rnd.RandomString(10), rnd.RandomString(100)); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - s = db->Flush(FlushOptions()); - } - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - state.ResumeTiming(); - s = db->Close(); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - DestroyDB(db_name, options); -} - -BENCHMARK(DBClose)->Iterations(200); // specify iteration number as the db size - // is impacted by iteration number - -static void DBPut(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - bool enable_statistics = state.range(3); - bool enable_wal = state.range(4); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db = nullptr; - Options options; - if (enable_statistics) { - options.statistics = CreateDBStatistics(); - } - options.compaction_style = compaction_style; - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "DBPut"); - } - - auto wo = WriteOptions(); - wo.disableWAL = !enable_wal; - - for (auto _ : state) { - state.PauseTiming(); - Slice key = kg.Next(); - std::string val = rnd.RandomString(static_cast(per_key_size)); - state.ResumeTiming(); - Status s = db->Put(wo, key, val); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - if (state.thread_index() == 0) { - auto db_full = static_cast_with_check(db.get()); - Status s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - if (enable_statistics) { - HistogramData histogram_data; - options.statistics->histogramData(DB_WRITE, &histogram_data); - state.counters["put_mean"] = histogram_data.average * std::milli::den; - state.counters["put_p95"] = histogram_data.percentile95 * std::milli::den; - state.counters["put_p99"] = histogram_data.percentile99 * std::milli::den; - } - - TeardownDB(state, db, options, kg); - } -} - -static void DBPutArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO}) { - for (int64_t max_data : {100l << 30}) { - for (int64_t per_key_size : {256, 1024}) { - for (bool enable_statistics : {false, true}) { - for (bool wal : {false, true}) { - b->Args( - {comp_style, max_data, per_key_size, enable_statistics, wal}); - } - } - } - } - } - b->ArgNames( - {"comp_style", "max_data", "per_key_size", "enable_statistics", "wal"}); -} - -static const uint64_t DBPutNum = 409600l; -BENCHMARK(DBPut)->Threads(1)->Iterations(DBPutNum)->Apply(DBPutArguments); -BENCHMARK(DBPut)->Threads(8)->Iterations(DBPutNum / 8)->Apply(DBPutArguments); - -static void ManualCompaction(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - bool enable_statistics = state.range(3); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db; - Options options; - if (enable_statistics) { - options.statistics = CreateDBStatistics(); - } - options.compaction_style = compaction_style; - // No auto compaction - options.disable_auto_compactions = true; - options.level0_file_num_compaction_trigger = (1 << 30); - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.soft_pending_compaction_bytes_limit = 0; - options.hard_pending_compaction_bytes_limit = 0; - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "ManualCompaction"); - } - - auto wo = WriteOptions(); - wo.disableWAL = true; - uint64_t flush_mod = key_num / 4; // at least generate 4 files for compaction - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - if (i + 1 % flush_mod == 0) { - s = db->Flush(FlushOptions()); - } - } - FlushOptions fo; - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - std::vector files_meta; - db->GetLiveFilesMetaData(&files_meta); - std::vector files_before_compact; - files_before_compact.reserve(files_meta.size()); - for (const LiveFileMetaData& file : files_meta) { - files_before_compact.emplace_back(file.name); - } - - SetPerfLevel(kEnableTime); - get_perf_context()->EnablePerLevelPerfContext(); - get_perf_context()->Reset(); - CompactionOptions co; - for (auto _ : state) { - s = db->CompactFiles(co, files_before_compact, 1); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - if (state.thread_index() == 0) { - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - if (enable_statistics) { - HistogramData histogram_data; - options.statistics->histogramData(COMPACTION_TIME, &histogram_data); - state.counters["comp_time"] = histogram_data.average; - options.statistics->histogramData(COMPACTION_CPU_TIME, &histogram_data); - state.counters["comp_cpu_time"] = histogram_data.average; - options.statistics->histogramData(COMPACTION_OUTFILE_SYNC_MICROS, - &histogram_data); - state.counters["comp_outfile_sync"] = histogram_data.average; - - state.counters["comp_read"] = static_cast( - options.statistics->getTickerCount(COMPACT_READ_BYTES)); - state.counters["comp_write"] = static_cast( - options.statistics->getTickerCount(COMPACT_WRITE_BYTES)); - - state.counters["user_key_comparison_count"] = - static_cast(get_perf_context()->user_key_comparison_count); - state.counters["block_read_count"] = - static_cast(get_perf_context()->block_read_count); - state.counters["block_read_time"] = - static_cast(get_perf_context()->block_read_time); - state.counters["block_checksum_time"] = - static_cast(get_perf_context()->block_checksum_time); - state.counters["new_table_block_iter_nanos"] = - static_cast(get_perf_context()->new_table_block_iter_nanos); - state.counters["new_table_iterator_nanos"] = - static_cast(get_perf_context()->new_table_iterator_nanos); - state.counters["find_table_nanos"] = - static_cast(get_perf_context()->find_table_nanos); - } - - TeardownDB(state, db, options, kg); - } -} - -static void ManualCompactionArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal}) { - for (int64_t max_data : {32l << 20, 128l << 20}) { - for (int64_t per_key_size : {256, 1024}) { - for (bool enable_statistics : {false, true}) { - b->Args({comp_style, max_data, per_key_size, enable_statistics}); - } - } - } - } - b->ArgNames({"comp_style", "max_data", "per_key_size", "enable_statistics"}); -} - -BENCHMARK(ManualCompaction)->Iterations(1)->Apply(ManualCompactionArguments); - -static void ManualFlush(benchmark::State& state) { - uint64_t key_num = state.range(0); - uint64_t per_key_size = state.range(1); - bool enable_statistics = true; - - // setup DB - static std::unique_ptr db; - Options options; - if (enable_statistics) { - options.statistics = CreateDBStatistics(); - } - options.disable_auto_compactions = true; - options.level0_file_num_compaction_trigger = (1 << 30); - options.level0_slowdown_writes_trigger = (1 << 30); - options.level0_stop_writes_trigger = (1 << 30); - options.soft_pending_compaction_bytes_limit = 0; - options.hard_pending_compaction_bytes_limit = 0; - options.write_buffer_size = 2l << 30; // 2G to avoid auto flush - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "ManualFlush"); - } - - auto wo = WriteOptions(); - for (auto _ : state) { - state.PauseTiming(); - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - } - FlushOptions fo; - state.ResumeTiming(); - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - if (state.thread_index() == 0) { - auto db_full = static_cast_with_check(db.get()); - Status s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - if (enable_statistics) { - HistogramData histogram_data; - options.statistics->histogramData(FLUSH_TIME, &histogram_data); - state.counters["flush_time"] = histogram_data.average; - state.counters["flush_write_bytes"] = static_cast( - options.statistics->getTickerCount(FLUSH_WRITE_BYTES)); - } - - TeardownDB(state, db, options, kg); - } -} - -static void ManualFlushArguments(benchmark::internal::Benchmark* b) { - for (int64_t key_num : {1l << 10, 8l << 10, 64l << 10}) { - for (int64_t per_key_size : {256, 1024}) { - b->Args({key_num, per_key_size}); - } - } - b->ArgNames({"key_num", "per_key_size"}); -} - -BENCHMARK(ManualFlush)->Iterations(1)->Apply(ManualFlushArguments); - -static void DBGet(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - bool enable_statistics = state.range(3); - bool negative_query = state.range(4); - bool enable_filter = state.range(5); - bool mmap = state.range(6); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db; - Options options; - if (enable_statistics) { - options.statistics = CreateDBStatistics(); - } - if (mmap) { - options.allow_mmap_reads = true; - options.compression = kNoCompression; - } - options.compaction_style = compaction_style; - - BlockBasedTableOptions table_options; - if (enable_filter) { - table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); - } - if (mmap) { - table_options.no_block_cache = true; - table_options.block_restart_interval = 1; - } - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "DBGet"); - - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - FlushOptions fo; - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - } - - auto ro = ReadOptions(); - if (mmap) { - ro.verify_checksums = false; - } - size_t not_found = 0; - if (negative_query) { - for (auto _ : state) { - std::string val; - Status s = db->Get(ro, kg.NextNonExist(), &val); - if (s.IsNotFound()) { - not_found++; - } - } - } else { - for (auto _ : state) { - std::string val; - Status s = db->Get(ro, kg.Next(), &val); - if (s.IsNotFound()) { - not_found++; - } - } - } - - state.counters["neg_qu_pct"] = benchmark::Counter( - static_cast(not_found * 100), benchmark::Counter::kAvgIterations); - - if (state.thread_index() == 0) { - if (enable_statistics) { - HistogramData histogram_data; - options.statistics->histogramData(DB_GET, &histogram_data); - state.counters["get_mean"] = histogram_data.average * std::milli::den; - state.counters["get_p95"] = histogram_data.percentile95 * std::milli::den; - state.counters["get_p99"] = histogram_data.percentile99 * std::milli::den; - } - - TeardownDB(state, db, options, kg); - } -} - -static void DBGetArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO}) { - for (int64_t max_data : {128l << 20, 512l << 20}) { - for (int64_t per_key_size : {256, 1024}) { - for (bool enable_statistics : {false, true}) { - for (bool negative_query : {false, true}) { - for (bool enable_filter : {false, true}) { - for (bool mmap : {false, true}) { - b->Args({comp_style, max_data, per_key_size, enable_statistics, - negative_query, enable_filter, mmap}); - } - } - } - } - } - } - } - b->ArgNames({"comp_style", "max_data", "per_key_size", "enable_statistics", - "negative_query", "enable_filter", "mmap"}); -} - -static constexpr uint64_t kDBGetNum = 1l << 20; -BENCHMARK(DBGet)->Threads(1)->Iterations(kDBGetNum)->Apply(DBGetArguments); -BENCHMARK(DBGet)->Threads(8)->Iterations(kDBGetNum / 8)->Apply(DBGetArguments); - -static void SimpleGetWithPerfContext(benchmark::State& state) { - // setup DB - static std::unique_ptr db; - std::string db_name; - Options options; - options.create_if_missing = true; - options.arena_block_size = 8 << 20; - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, 1024); - - if (state.thread_index() == 0) { - auto env = Env::Default(); - std::string db_path; - Status s = env->GetTestDirectory(&db_path); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - db_name = db_path + "/simple_get_" + std::to_string(getpid()); - DestroyDB(db_name, options); - - { - DB* db_ptr = nullptr; - s = DB::Open(options, db_name, &db_ptr); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - db.reset(db_ptr); - } - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < 1024; i++) { - s = db->Put(wo, kg.Next(), rnd.RandomString(1024)); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - FlushOptions fo; - s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - auto ro = ReadOptions(); - size_t not_found = 0; - uint64_t user_key_comparison_count = 0; - uint64_t block_read_time = 0; - uint64_t block_checksum_time = 0; - uint64_t get_snapshot_time = 0; - uint64_t get_post_process_time = 0; - uint64_t get_from_output_files_time = 0; - uint64_t new_table_block_iter_nanos = 0; - uint64_t block_seek_nanos = 0; - uint64_t get_cpu_nanos = 0; - uint64_t get_from_table_nanos = 0; - SetPerfLevel(kEnableTime); - get_perf_context()->EnablePerLevelPerfContext(); - for (auto _ : state) { - std::string val; - get_perf_context()->Reset(); - Status s = db->Get(ro, kg.NextNonExist(), &val); - if (s.IsNotFound()) { - not_found++; - } - user_key_comparison_count += get_perf_context()->user_key_comparison_count; - block_read_time += get_perf_context()->block_read_time; - block_checksum_time += get_perf_context()->block_checksum_time; - get_snapshot_time += get_perf_context()->get_snapshot_time; - get_post_process_time += get_perf_context()->get_post_process_time; - get_from_output_files_time += - get_perf_context()->get_from_output_files_time; - new_table_block_iter_nanos += - get_perf_context()->new_table_block_iter_nanos; - block_seek_nanos += get_perf_context()->block_seek_nanos; - get_cpu_nanos += get_perf_context()->get_cpu_nanos; - get_from_table_nanos += - (*(get_perf_context()->level_to_perf_context))[0].get_from_table_nanos; - } - - state.counters["neg_qu_pct"] = benchmark::Counter( - static_cast(not_found * 100), benchmark::Counter::kAvgIterations); - state.counters["user_key_comparison_count"] = - benchmark::Counter(static_cast(user_key_comparison_count), - benchmark::Counter::kAvgIterations); - state.counters["block_read_time"] = benchmark::Counter( - static_cast(block_read_time), benchmark::Counter::kAvgIterations); - state.counters["block_checksum_time"] = - benchmark::Counter(static_cast(block_checksum_time), - benchmark::Counter::kAvgIterations); - state.counters["get_snapshot_time"] = - benchmark::Counter(static_cast(get_snapshot_time), - benchmark::Counter::kAvgIterations); - state.counters["get_post_process_time"] = - benchmark::Counter(static_cast(get_post_process_time), - benchmark::Counter::kAvgIterations); - state.counters["get_from_output_files_time"] = - benchmark::Counter(static_cast(get_from_output_files_time), - benchmark::Counter::kAvgIterations); - state.counters["new_table_block_iter_nanos"] = - benchmark::Counter(static_cast(new_table_block_iter_nanos), - benchmark::Counter::kAvgIterations); - state.counters["block_seek_nanos"] = - benchmark::Counter(static_cast(block_seek_nanos), - benchmark::Counter::kAvgIterations); - state.counters["get_cpu_nanos"] = benchmark::Counter( - static_cast(get_cpu_nanos), benchmark::Counter::kAvgIterations); - state.counters["get_from_table_nanos"] = - benchmark::Counter(static_cast(get_from_table_nanos), - benchmark::Counter::kAvgIterations); - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, kg); - } -} - -BENCHMARK(SimpleGetWithPerfContext)->Iterations(1000000); - -static void DBGetMergeOperandsInMemtable(benchmark::State& state) { - const uint64_t kDataLen = 16 << 20; // 16MB - const uint64_t kValueLen = 64; - const uint64_t kNumEntries = kDataLen / kValueLen; - const uint64_t kNumEntriesPerKey = state.range(0); - const uint64_t kNumKeys = kNumEntries / kNumEntriesPerKey; - - // setup DB - static std::unique_ptr db; - - Options options; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - // Make memtable large enough that automatic flush will not be triggered. - options.write_buffer_size = 2 * kDataLen; - - KeyGenerator sequential_key_gen(kNumKeys); - auto rnd = Random(301 + state.thread_index()); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "DBGetMergeOperandsInMemtable"); - - // load db - auto write_opts = WriteOptions(); - write_opts.disableWAL = true; - for (uint64_t i = 0; i < kNumEntries; i++) { - Status s = db->Merge(write_opts, sequential_key_gen.Next(), - rnd.RandomString(static_cast(kValueLen))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - } - - KeyGenerator random_key_gen(kNumKeys); - std::vector value_operands; - value_operands.resize(kNumEntriesPerKey); - GetMergeOperandsOptions get_merge_ops_opts; - get_merge_ops_opts.expected_max_number_of_operands = - static_cast(kNumEntriesPerKey); - for (auto _ : state) { - int num_value_operands = 0; - Status s = db->GetMergeOperands( - ReadOptions(), db->DefaultColumnFamily(), random_key_gen.Next(), - value_operands.data(), &get_merge_ops_opts, &num_value_operands); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - if (num_value_operands != static_cast(kNumEntriesPerKey)) { - state.SkipWithError("Unexpected number of merge operands found for key"); - } - for (auto& value_operand : value_operands) { - value_operand.Reset(); - } - } - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, random_key_gen); - } -} - -static void DBGetMergeOperandsInSstFile(benchmark::State& state) { - const uint64_t kDataLen = 16 << 20; // 16MB - const uint64_t kValueLen = 64; - const uint64_t kNumEntries = kDataLen / kValueLen; - const uint64_t kNumEntriesPerKey = state.range(0); - const uint64_t kNumKeys = kNumEntries / kNumEntriesPerKey; - const bool kMmap = state.range(1); - - // setup DB - static std::unique_ptr db; - - BlockBasedTableOptions table_options; - if (kMmap) { - table_options.no_block_cache = true; - } else { - // Make block cache large enough that eviction will not be triggered. - table_options.block_cache = NewLRUCache(2 * kDataLen); - } - - Options options; - if (kMmap) { - options.allow_mmap_reads = true; - } - options.compression = kNoCompression; - options.merge_operator = MergeOperators::CreateStringAppendOperator(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - // Make memtable large enough that automatic flush will not be triggered. - options.write_buffer_size = 2 * kDataLen; - - KeyGenerator sequential_key_gen(kNumKeys); - auto rnd = Random(301 + state.thread_index()); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "DBGetMergeOperandsInBlockCache"); - - // load db - // - // Take a snapshot after each cycle of merges to ensure flush cannot - // merge any entries. - std::vector snapshots; - snapshots.resize(kNumEntriesPerKey); - auto write_opts = WriteOptions(); - write_opts.disableWAL = true; - for (uint64_t i = 0; i < kNumEntriesPerKey; i++) { - for (uint64_t j = 0; j < kNumKeys; j++) { - Status s = db->Merge(write_opts, sequential_key_gen.Next(), - rnd.RandomString(static_cast(kValueLen))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - snapshots[i] = db->GetSnapshot(); - } - - // Flush to an L0 file; read back to prime the cache/mapped memory. - db->Flush(FlushOptions()); - for (uint64_t i = 0; i < kNumKeys; ++i) { - std::string value; - Status s = db->Get(ReadOptions(), sequential_key_gen.Next(), &value); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - if (state.thread_index() == 0) { - for (uint64_t i = 0; i < kNumEntriesPerKey; ++i) { - db->ReleaseSnapshot(snapshots[i]); - } - } - } - - KeyGenerator random_key_gen(kNumKeys); - std::vector value_operands; - value_operands.resize(kNumEntriesPerKey); - GetMergeOperandsOptions get_merge_ops_opts; - get_merge_ops_opts.expected_max_number_of_operands = - static_cast(kNumEntriesPerKey); - for (auto _ : state) { - int num_value_operands = 0; - ReadOptions read_opts; - read_opts.verify_checksums = false; - Status s = db->GetMergeOperands( - read_opts, db->DefaultColumnFamily(), random_key_gen.Next(), - value_operands.data(), &get_merge_ops_opts, &num_value_operands); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - if (num_value_operands != static_cast(kNumEntriesPerKey)) { - state.SkipWithError("Unexpected number of merge operands found for key"); - } - for (auto& value_operand : value_operands) { - value_operand.Reset(); - } - } - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, random_key_gen); - } -} - -static void DBGetMergeOperandsInMemtableArguments( - benchmark::internal::Benchmark* b) { - for (int entries_per_key : {1, 32, 1024}) { - b->Args({entries_per_key}); - } - b->ArgNames({"entries_per_key"}); -} - -static void DBGetMergeOperandsInSstFileArguments( - benchmark::internal::Benchmark* b) { - for (int entries_per_key : {1, 32, 1024}) { - for (bool mmap : {false, true}) { - b->Args({entries_per_key, mmap}); - } - } - b->ArgNames({"entries_per_key", "mmap"}); -} - -BENCHMARK(DBGetMergeOperandsInMemtable) - ->Threads(1) - ->Apply(DBGetMergeOperandsInMemtableArguments); -BENCHMARK(DBGetMergeOperandsInMemtable) - ->Threads(8) - ->Apply(DBGetMergeOperandsInMemtableArguments); -BENCHMARK(DBGetMergeOperandsInSstFile) - ->Threads(1) - ->Apply(DBGetMergeOperandsInSstFileArguments); -BENCHMARK(DBGetMergeOperandsInSstFile) - ->Threads(8) - ->Apply(DBGetMergeOperandsInSstFileArguments); - -std::string GenerateKey(int primary_key, int secondary_key, int padding_size, - Random* rnd) { - char buf[50]; - char* p = &buf[0]; - snprintf(buf, sizeof(buf), "%6d%4d", primary_key, secondary_key); - std::string k(p); - if (padding_size) { - k += rnd->RandomString(padding_size); - } - - return k; -} - -void GenerateRandomKVs(std::vector* keys, - std::vector* values, const int from, - const int len, const int step = 1, - const int padding_size = 0, - const int keys_share_prefix = 1) { - Random rnd(302); - - // generate different prefix - for (int i = from; i < from + len; i += step) { - // generating keys that share the prefix - for (int j = 0; j < keys_share_prefix; ++j) { - keys->emplace_back(GenerateKey(i, j, padding_size, &rnd)); - // 100 bytes values - values->emplace_back(rnd.RandomString(100)); - } - } -} - -// TODO: move it to different files, as it's testing an internal API -static void DataBlockSeek(benchmark::State& state) { - Random rnd(301); - Options options = Options(); - - BlockBuilder builder(16, true, false, - BlockBasedTableOptions::kDataBlockBinarySearch); - - int num_records = 500; - std::vector keys; - std::vector values; - - GenerateRandomKVs(&keys, &values, 0, num_records); - - for (int i = 0; i < num_records; i++) { - std::string ukey(keys[i] + "1"); - InternalKey ikey(ukey, 0, kTypeValue); - builder.Add(ikey.Encode().ToString(), values[i]); - } - - Slice rawblock = builder.Finish(); - - BlockContents contents; - contents.data = rawblock; - Block reader(std::move(contents)); - - SetPerfLevel(kEnableTime); - uint64_t total = 0; - for (auto _ : state) { - DataBlockIter* iter = reader.NewDataIterator(options.comparator, - kDisableGlobalSequenceNumber); - uint32_t index = rnd.Uniform(static_cast(num_records)); - std::string ukey(keys[index] + "1"); - InternalKey ikey(ukey, 0, kTypeValue); - get_perf_context()->Reset(); - bool may_exist = iter->SeekForGet(ikey.Encode().ToString()); - if (!may_exist) { - state.SkipWithError("key not found"); - } - total += get_perf_context()->block_seek_nanos; - delete iter; - } - state.counters["seek_ns"] = benchmark::Counter( - static_cast(total), benchmark::Counter::kAvgIterations); -} - -BENCHMARK(DataBlockSeek)->Iterations(1000000); - -static void IteratorSeek(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - bool enable_statistics = state.range(3); - bool negative_query = state.range(4); - bool enable_filter = state.range(5); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db; - Options options; - if (enable_statistics) { - options.statistics = CreateDBStatistics(); - } - options.compaction_style = compaction_style; - - if (enable_filter) { - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - } - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "IteratorSeek"); - - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - FlushOptions fo; - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - } - - for (auto _ : state) { - std::unique_ptr iter{nullptr}; - state.PauseTiming(); - if (!iter) { - iter.reset(db->NewIterator(ReadOptions())); - } - Slice key = negative_query ? kg.NextNonExist() : kg.Next(); - if (!iter->status().ok()) { - state.SkipWithError(iter->status().ToString().c_str()); - return; - } - state.ResumeTiming(); - iter->Seek(key); - } - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, kg); - } -} - -static void IteratorSeekArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO}) { - for (int64_t max_data : {128l << 20, 512l << 20}) { - for (int64_t per_key_size : {256, 1024}) { - for (bool enable_statistics : {false, true}) { - for (bool negative_query : {false, true}) { - for (bool enable_filter : {false, true}) { - b->Args({comp_style, max_data, per_key_size, enable_statistics, - negative_query, enable_filter}); - } - } - } - } - } - } - b->ArgNames({"comp_style", "max_data", "per_key_size", "enable_statistics", - "negative_query", "enable_filter"}); -} - -static constexpr uint64_t kDBSeekNum = 10l << 10; -BENCHMARK(IteratorSeek) - ->Threads(1) - ->Iterations(kDBSeekNum) - ->Apply(IteratorSeekArguments); -BENCHMARK(IteratorSeek) - ->Threads(8) - ->Iterations(kDBSeekNum / 8) - ->Apply(IteratorSeekArguments); - -static void IteratorNext(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db; - Options options; - options.compaction_style = compaction_style; - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "IteratorNext"); - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - FlushOptions fo; - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - } - - for (auto _ : state) { - std::unique_ptr iter{nullptr}; - state.PauseTiming(); - if (!iter) { - iter.reset(db->NewIterator(ReadOptions())); - } - while (!iter->Valid()) { - iter->Seek(kg.Next()); - if (!iter->status().ok()) { - state.SkipWithError(iter->status().ToString().c_str()); - } - } - state.ResumeTiming(); - iter->Next(); - } - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, kg); - } -} - -static void IteratorNextArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO}) { - for (int64_t max_data : {128l << 20, 512l << 20}) { - for (int64_t per_key_size : {256, 1024}) { - b->Args({comp_style, max_data, per_key_size}); - } - } - } - b->ArgNames({"comp_style", "max_data", "per_key_size"}); -} -static constexpr uint64_t kIteratorNextNum = 10l << 10; -BENCHMARK(IteratorNext) - ->Iterations(kIteratorNextNum) - ->Apply(IteratorNextArguments); - -static void IteratorNextWithPerfContext(benchmark::State& state) { - // setup DB - static std::unique_ptr db; - Options options; - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, 1024); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "IteratorNextWithPerfContext"); - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < 1024; i++) { - Status s = db->Put(wo, kg.Next(), rnd.RandomString(1024)); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - auto db_full = static_cast_with_check(db.get()); - Status s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - FlushOptions fo; - s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - uint64_t user_key_comparison_count = 0; - uint64_t internal_key_skipped_count = 0; - uint64_t find_next_user_entry_time = 0; - uint64_t iter_next_cpu_nanos = 0; - - SetPerfLevel(kEnableTime); - get_perf_context()->EnablePerLevelPerfContext(); - - for (auto _ : state) { - std::unique_ptr iter{nullptr}; - state.PauseTiming(); - if (!iter) { - iter.reset(db->NewIterator(ReadOptions())); - } - while (!iter->Valid()) { - iter->Seek(kg.Next()); - if (!iter->status().ok()) { - state.SkipWithError(iter->status().ToString().c_str()); - } - } - get_perf_context()->Reset(); - state.ResumeTiming(); - - iter->Next(); - user_key_comparison_count += get_perf_context()->user_key_comparison_count; - internal_key_skipped_count += - get_perf_context()->internal_key_skipped_count; - find_next_user_entry_time += get_perf_context()->find_next_user_entry_time; - iter_next_cpu_nanos += get_perf_context()->iter_next_cpu_nanos; - } - - state.counters["user_key_comparison_count"] = - benchmark::Counter(static_cast(user_key_comparison_count), - benchmark::Counter::kAvgIterations); - state.counters["internal_key_skipped_count"] = - benchmark::Counter(static_cast(internal_key_skipped_count), - benchmark::Counter::kAvgIterations); - state.counters["find_next_user_entry_time"] = - benchmark::Counter(static_cast(find_next_user_entry_time), - benchmark::Counter::kAvgIterations); - state.counters["iter_next_cpu_nanos"] = - benchmark::Counter(static_cast(iter_next_cpu_nanos), - benchmark::Counter::kAvgIterations); - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, kg); - } -} - -BENCHMARK(IteratorNextWithPerfContext)->Iterations(100000); - -static void IteratorPrev(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db; - std::string db_name; - Options options; - options.compaction_style = compaction_style; - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "IteratorPrev"); - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - FlushOptions fo; - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - } - - for (auto _ : state) { - std::unique_ptr iter{nullptr}; - state.PauseTiming(); - if (!iter) { - iter.reset(db->NewIterator(ReadOptions())); - } - while (!iter->Valid()) { - iter->Seek(kg.Next()); - if (!iter->status().ok()) { - state.SkipWithError(iter->status().ToString().c_str()); - } - } - state.ResumeTiming(); - iter->Prev(); - } - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, kg); - } -} - -static void IteratorPrevArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO}) { - for (int64_t max_data : {128l << 20, 512l << 20}) { - for (int64_t per_key_size : {256, 1024}) { - b->Args({comp_style, max_data, per_key_size}); - } - } - } - b->ArgNames({"comp_style", "max_data", "per_key_size"}); -} - -static constexpr uint64_t kIteratorPrevNum = 10l << 10; -BENCHMARK(IteratorPrev) - ->Iterations(kIteratorPrevNum) - ->Apply(IteratorPrevArguments); - -static void PrefixSeek(benchmark::State& state) { - auto compaction_style = static_cast(state.range(0)); - uint64_t max_data = state.range(1); - uint64_t per_key_size = state.range(2); - bool enable_statistics = state.range(3); - bool enable_filter = state.range(4); - uint64_t key_num = max_data / per_key_size; - - // setup DB - static std::unique_ptr db; - Options options; - if (enable_statistics) { - options.statistics = CreateDBStatistics(); - } - options.compaction_style = compaction_style; - options.prefix_extractor.reset(NewFixedPrefixTransform(4)); - - if (enable_filter) { - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - } - - auto rnd = Random(301 + state.thread_index()); - KeyGenerator kg(&rnd, key_num, key_num / 100); - - if (state.thread_index() == 0) { - SetupDB(state, options, &db, "PrefixSeek"); - - // load db - auto wo = WriteOptions(); - wo.disableWAL = true; - for (uint64_t i = 0; i < key_num; i++) { - Status s = db->Put(wo, kg.Next(), - rnd.RandomString(static_cast(per_key_size))); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - FlushOptions fo; - Status s = db->Flush(fo); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - - auto db_full = static_cast_with_check(db.get()); - s = db_full->WaitForCompact(true); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - } - - for (auto _ : state) { - std::unique_ptr iter{nullptr}; - state.PauseTiming(); - if (!iter) { - iter.reset(db->NewIterator(ReadOptions())); - } - state.ResumeTiming(); - iter->Seek(kg.NextPrefix()); - if (!iter->status().ok()) { - state.SkipWithError(iter->status().ToString().c_str()); - return; - } - } - - if (state.thread_index() == 0) { - TeardownDB(state, db, options, kg); - } -} - -static void PrefixSeekArguments(benchmark::internal::Benchmark* b) { - for (int comp_style : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO}) { - for (int64_t max_data : {128l << 20, 512l << 20}) { - for (int64_t per_key_size : {256, 1024}) { - for (bool enable_statistics : {false, true}) { - for (bool enable_filter : {false, true}) { - b->Args({comp_style, max_data, per_key_size, enable_statistics, - enable_filter}); - } - } - } - } - } - b->ArgNames({"comp_style", "max_data", "per_key_size", "enable_statistics", - "enable_filter"}); -} - -static constexpr uint64_t kPrefixSeekNum = 10l << 10; -BENCHMARK(PrefixSeek)->Iterations(kPrefixSeekNum)->Apply(PrefixSeekArguments); -BENCHMARK(PrefixSeek) - ->Threads(8) - ->Iterations(kPrefixSeekNum / 8) - ->Apply(PrefixSeekArguments); - -// TODO: move it to different files, as it's testing an internal API -static void RandomAccessFileReaderRead(benchmark::State& state) { - bool enable_statistics = state.range(0); - constexpr int kFileNum = 10; - auto env = Env::Default(); - auto fs = env->GetFileSystem(); - std::string db_path; - Status s = env->GetTestDirectory(&db_path); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - return; - } - - // Setup multiple `RandomAccessFileReader`s with different parameters to be - // used for test - Random rand(301); - std::string fname_base = - db_path + kFilePathSeparator + "random-access-file-reader-read"; - std::vector> readers; - auto statistics_share = CreateDBStatistics(); - Statistics* statistics = enable_statistics ? statistics_share.get() : nullptr; - for (int i = 0; i < kFileNum; i++) { - std::string fname = fname_base + std::to_string(i); - std::string content = rand.RandomString(kDefaultPageSize); - std::unique_ptr tgt_file; - env->NewWritableFile(fname, &tgt_file, EnvOptions()); - tgt_file->Append(content); - tgt_file->Close(); - - std::unique_ptr f; - fs->NewRandomAccessFile(fname, FileOptions(), &f, nullptr); - int rand_num = rand.Next() % 3; - auto temperature = rand_num == 0 ? Temperature::kUnknown - : rand_num == 1 ? Temperature::kWarm - : Temperature::kCold; - readers.emplace_back(new RandomAccessFileReader( - std::move(f), fname, env->GetSystemClock().get(), nullptr, statistics, - 0, nullptr, nullptr, {}, temperature, rand_num == 1)); - } - - IOOptions io_options; - std::unique_ptr scratch(new char[2048]); - Slice result; - uint64_t idx = 0; - for (auto _ : state) { - s = readers[idx++ % kFileNum]->Read(io_options, 0, kDefaultPageSize / 3, - &result, scratch.get(), nullptr, - Env::IO_TOTAL); - if (!s.ok()) { - state.SkipWithError(s.ToString().c_str()); - } - } - - // clean up - for (int i = 0; i < kFileNum; i++) { - std::string fname = fname_base + std::to_string(i); - env->DeleteFile(fname); // ignore return, okay to fail cleanup - } -} - -BENCHMARK(RandomAccessFileReaderRead) - ->Iterations(1000000) - ->Arg(0) - ->Arg(1) - ->ArgName("enable_statistics"); - -} // namespace ROCKSDB_NAMESPACE - -BENCHMARK_MAIN(); diff --git a/microbench/ribbon_bench.cc b/microbench/ribbon_bench.cc deleted file mode 100644 index d0fb2ec9a..000000000 --- a/microbench/ribbon_bench.cc +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -// this is a simple micro-benchmark for compare ribbon filter vs. other filter -// for more comprehensive, please check the dedicate util/filter_bench. -#include "benchmark/benchmark.h" -#include "table/block_based/filter_policy_internal.h" -#include "table/block_based/mock_block_based_table.h" - -namespace ROCKSDB_NAMESPACE { - -struct KeyMaker { - explicit KeyMaker(size_t avg_size) - : smallest_size_(avg_size), - buf_size_(avg_size + 11), // pad to vary key size and alignment - buf_(new char[buf_size_]) { - memset(buf_.get(), 0, buf_size_); - assert(smallest_size_ > 8); - } - size_t smallest_size_; - size_t buf_size_; - std::unique_ptr buf_; - - // Returns a unique(-ish) key based on the given parameter values. Each - // call returns a Slice from the same buffer so previously returned - // Slices should be considered invalidated. - Slice Get(uint32_t filter_num, uint32_t val_num) const { - size_t start = val_num % 4; - size_t len = smallest_size_; - // To get range [avg_size - 2, avg_size + 2] - // use range [smallest_size, smallest_size + 4] - len += FastRange32((val_num >> 5) * 1234567891, 5); - char *data = buf_.get() + start; - // Populate key data such that all data makes it into a key of at - // least 8 bytes. We also don't want all the within-filter key - // variance confined to a contiguous 32 bits, because then a 32 bit - // hash function can "cheat" the false positive rate by - // approximating a perfect hash. - EncodeFixed32(data, val_num); - EncodeFixed32(data + 4, filter_num + val_num); - // ensure clearing leftovers from different alignment - EncodeFixed32(data + 8, 0); - return {data, len}; - } -}; - -// benchmark arguments: -// 0. filter impl (like filter_bench -impl) -// 1. filter config bits_per_key -// 2. average data key length -// 3. data entry number -static void CustomArguments(benchmark::internal::Benchmark *b) { - const auto kImplCount = - static_cast(BloomLikeFilterPolicy::GetAllFixedImpls().size()); - for (int filter_impl = 0; filter_impl < kImplCount; ++filter_impl) { - for (int bits_per_key : {10, 20}) { - for (int key_len_avg : {10, 100}) { - for (int64_t entry_num : {1 << 10, 1 << 20}) { - b->Args({filter_impl, bits_per_key, key_len_avg, entry_num}); - } - } - } - } - b->ArgNames({"filter_impl", "bits_per_key", "key_len_avg", "entry_num"}); -} - -static void FilterBuild(benchmark::State &state) { - // setup data - auto filter = BloomLikeFilterPolicy::Create( - BloomLikeFilterPolicy::GetAllFixedImpls().at(state.range(0)), - static_cast(state.range(1))); - auto tester = std::make_unique(filter); - KeyMaker km(state.range(2)); - std::unique_ptr owner; - const int64_t kEntryNum = state.range(3); - auto rnd = Random32(12345); - uint32_t filter_num = rnd.Next(); - // run the test - for (auto _ : state) { - std::unique_ptr builder(tester->GetBuilder()); - for (uint32_t i = 0; i < kEntryNum; i++) { - builder->AddKey(km.Get(filter_num, i)); - } - auto ret = builder->Finish(&owner); - state.counters["size"] = static_cast(ret.size()); - } -} -BENCHMARK(FilterBuild)->Apply(CustomArguments); - -static void FilterQueryPositive(benchmark::State &state) { - // setup data - auto filter = BloomLikeFilterPolicy::Create( - BloomLikeFilterPolicy::GetAllFixedImpls().at(state.range(0)), - static_cast(state.range(1))); - auto tester = std::make_unique(filter); - KeyMaker km(state.range(2)); - std::unique_ptr owner; - const int64_t kEntryNum = state.range(3); - auto rnd = Random32(12345); - uint32_t filter_num = rnd.Next(); - std::unique_ptr builder(tester->GetBuilder()); - for (uint32_t i = 0; i < kEntryNum; i++) { - builder->AddKey(km.Get(filter_num, i)); - } - auto data = builder->Finish(&owner); - std::unique_ptr reader{filter->GetFilterBitsReader(data)}; - - // run test - uint32_t i = 0; - for (auto _ : state) { - i++; - i = i % kEntryNum; - reader->MayMatch(km.Get(filter_num, i)); - } -} -BENCHMARK(FilterQueryPositive)->Apply(CustomArguments); - -static void FilterQueryNegative(benchmark::State &state) { - // setup data - auto filter = BloomLikeFilterPolicy::Create( - BloomLikeFilterPolicy::GetAllFixedImpls().at(state.range(0)), - static_cast(state.range(1))); - auto tester = std::make_unique(filter); - KeyMaker km(state.range(2)); - std::unique_ptr owner; - const int64_t kEntryNum = state.range(3); - auto rnd = Random32(12345); - uint32_t filter_num = rnd.Next(); - std::unique_ptr builder(tester->GetBuilder()); - for (uint32_t i = 0; i < kEntryNum; i++) { - builder->AddKey(km.Get(filter_num, i)); - } - auto data = builder->Finish(&owner); - std::unique_ptr reader{filter->GetFilterBitsReader(data)}; - - // run test - uint32_t i = 0; - double fp_cnt = 0; - for (auto _ : state) { - i++; - auto result = reader->MayMatch(km.Get(filter_num + 1, i)); - if (result) { - fp_cnt++; - } - } - state.counters["fp_pct"] = - benchmark::Counter(fp_cnt * 100, benchmark::Counter::kAvgIterations); -} -BENCHMARK(FilterQueryNegative)->Apply(CustomArguments); - -} // namespace ROCKSDB_NAMESPACE - -BENCHMARK_MAIN(); diff --git a/monitoring/histogram_test.cc b/monitoring/histogram_test.cc deleted file mode 100644 index 19e9f15d0..000000000 --- a/monitoring/histogram_test.cc +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include "monitoring/histogram.h" - -#include - -#include "monitoring/histogram_windowing.h" -#include "rocksdb/system_clock.h" -#include "test_util/mock_time_env.h" -#include "test_util/testharness.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class HistogramTest : public testing::Test {}; - -namespace { -const double kIota = 0.1; -const HistogramBucketMapper bucketMapper; -std::shared_ptr clock = - std::make_shared(SystemClock::Default()); -} // namespace - -void PopulateHistogram(Histogram& histogram, uint64_t low, uint64_t high, - uint64_t loop = 1) { - Random rnd(test::RandomSeed()); - for (; loop > 0; loop--) { - for (uint64_t i = low; i <= high; i++) { - histogram.Add(i); - // sleep a random microseconds [0-10) - clock->SleepForMicroseconds(rnd.Uniform(10)); - } - } - // make sure each data population at least take some time - clock->SleepForMicroseconds(1); -} - -void BasicOperation(Histogram& histogram) { - PopulateHistogram(histogram, 1, 110, 10); // fill up to bucket [70, 110) - - HistogramData data; - histogram.Data(&data); - - ASSERT_LE(fabs(histogram.Percentile(100.0) - 110.0), kIota); - ASSERT_LE(fabs(data.percentile99 - 108.9), kIota); // 99 * 110 / 100 - ASSERT_LE(fabs(data.percentile95 - 104.5), kIota); // 95 * 110 / 100 - ASSERT_LE(fabs(data.median - 55.0), kIota); // 50 * 110 / 100 - ASSERT_EQ(data.average, 55.5); // (1 + 110) / 2 -} - -void MergeHistogram(Histogram& histogram, Histogram& other) { - PopulateHistogram(histogram, 1, 100); - PopulateHistogram(other, 101, 250); - histogram.Merge(other); - - HistogramData data; - histogram.Data(&data); - - ASSERT_LE(fabs(histogram.Percentile(100.0) - 250.0), kIota); - ASSERT_LE(fabs(data.percentile99 - 247.5), kIota); // 99 * 250 / 100 - ASSERT_LE(fabs(data.percentile95 - 237.5), kIota); // 95 * 250 / 100 - ASSERT_LE(fabs(data.median - 125.0), kIota); // 50 * 250 / 100 - ASSERT_EQ(data.average, 125.5); // (1 + 250) / 2 -} - -void EmptyHistogram(Histogram& histogram) { - ASSERT_EQ(histogram.min(), bucketMapper.LastValue()); - ASSERT_EQ(histogram.max(), 0); - ASSERT_EQ(histogram.num(), 0); - ASSERT_EQ(histogram.Median(), 0.0); - ASSERT_EQ(histogram.Percentile(85.0), 0.0); - ASSERT_EQ(histogram.Average(), 0.0); - ASSERT_EQ(histogram.StandardDeviation(), 0.0); -} - -void ClearHistogram(Histogram& histogram) { - for (uint64_t i = 1; i <= 100; i++) { - histogram.Add(i); - } - histogram.Clear(); - ASSERT_TRUE(histogram.Empty()); - ASSERT_EQ(histogram.Median(), 0); - ASSERT_EQ(histogram.Percentile(85.0), 0); - ASSERT_EQ(histogram.Average(), 0); -} - -TEST_F(HistogramTest, BasicOperation) { - HistogramImpl histogram; - BasicOperation(histogram); - - HistogramWindowingImpl histogramWindowing; - BasicOperation(histogramWindowing); -} - -TEST_F(HistogramTest, BoundaryValue) { - HistogramImpl histogram; - // - both should be in [0, 1] bucket because we place values on bucket - // boundaries in the lower bucket. - // - all points are in [0, 1] bucket, so p50 will be 0.5 - // - the test cannot be written with a single point since histogram won't - // report percentiles lower than the min or greater than the max. - histogram.Add(0); - histogram.Add(1); - - ASSERT_LE(fabs(histogram.Percentile(50.0) - 0.5), kIota); -} - -TEST_F(HistogramTest, MergeHistogram) { - HistogramImpl histogram; - HistogramImpl other; - MergeHistogram(histogram, other); - - HistogramWindowingImpl histogramWindowing; - HistogramWindowingImpl otherWindowing; - MergeHistogram(histogramWindowing, otherWindowing); -} - -TEST_F(HistogramTest, EmptyHistogram) { - HistogramImpl histogram; - EmptyHistogram(histogram); - - HistogramWindowingImpl histogramWindowing; - EmptyHistogram(histogramWindowing); -} - -TEST_F(HistogramTest, ClearHistogram) { - HistogramImpl histogram; - ClearHistogram(histogram); - - HistogramWindowingImpl histogramWindowing; - ClearHistogram(histogramWindowing); -} - -TEST_F(HistogramTest, HistogramWindowingExpire) { - uint64_t num_windows = 3; - int micros_per_window = 1000000; - uint64_t min_num_per_window = 0; - - HistogramWindowingImpl histogramWindowing(num_windows, micros_per_window, - min_num_per_window); - histogramWindowing.TEST_UpdateClock(clock); - PopulateHistogram(histogramWindowing, 1, 1, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 100); - ASSERT_EQ(histogramWindowing.min(), 1); - ASSERT_EQ(histogramWindowing.max(), 1); - ASSERT_EQ(histogramWindowing.Average(), 1.0); - ASSERT_EQ(histogramWindowing.StandardDeviation(), 0.0); - - PopulateHistogram(histogramWindowing, 2, 2, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 200); - ASSERT_EQ(histogramWindowing.min(), 1); - ASSERT_EQ(histogramWindowing.max(), 2); - ASSERT_EQ(histogramWindowing.Average(), 1.5); - ASSERT_GT(histogramWindowing.StandardDeviation(), 0.0); - - PopulateHistogram(histogramWindowing, 3, 3, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 300); - ASSERT_EQ(histogramWindowing.min(), 1); - ASSERT_EQ(histogramWindowing.max(), 3); - ASSERT_EQ(histogramWindowing.Average(), 2.0); - ASSERT_GT(histogramWindowing.StandardDeviation(), 0.0); - - // dropping oldest window with value 1, remaining 2 ~ 4 - PopulateHistogram(histogramWindowing, 4, 4, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 300); - ASSERT_EQ(histogramWindowing.min(), 2); - ASSERT_EQ(histogramWindowing.max(), 4); - ASSERT_EQ(histogramWindowing.Average(), 3.0); - ASSERT_GT(histogramWindowing.StandardDeviation(), 0.0); - - // dropping oldest window with value 2, remaining 3 ~ 5 - PopulateHistogram(histogramWindowing, 5, 5, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 300); - ASSERT_EQ(histogramWindowing.min(), 3); - ASSERT_EQ(histogramWindowing.max(), 5); - ASSERT_EQ(histogramWindowing.Average(), 4.0); - ASSERT_GT(histogramWindowing.StandardDeviation(), 0.0); -} - -TEST_F(HistogramTest, HistogramWindowingMerge) { - uint64_t num_windows = 3; - int micros_per_window = 1000000; - uint64_t min_num_per_window = 0; - - HistogramWindowingImpl histogramWindowing(num_windows, micros_per_window, - min_num_per_window); - HistogramWindowingImpl otherWindowing(num_windows, micros_per_window, - min_num_per_window); - histogramWindowing.TEST_UpdateClock(clock); - otherWindowing.TEST_UpdateClock(clock); - - PopulateHistogram(histogramWindowing, 1, 1, 100); - PopulateHistogram(otherWindowing, 1, 1, 100); - clock->SleepForMicroseconds(micros_per_window); - - PopulateHistogram(histogramWindowing, 2, 2, 100); - PopulateHistogram(otherWindowing, 2, 2, 100); - clock->SleepForMicroseconds(micros_per_window); - - PopulateHistogram(histogramWindowing, 3, 3, 100); - PopulateHistogram(otherWindowing, 3, 3, 100); - clock->SleepForMicroseconds(micros_per_window); - - histogramWindowing.Merge(otherWindowing); - ASSERT_EQ(histogramWindowing.num(), 600); - ASSERT_EQ(histogramWindowing.min(), 1); - ASSERT_EQ(histogramWindowing.max(), 3); - ASSERT_EQ(histogramWindowing.Average(), 2.0); - - // dropping oldest window with value 1, remaining 2 ~ 4 - PopulateHistogram(histogramWindowing, 4, 4, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 500); - ASSERT_EQ(histogramWindowing.min(), 2); - ASSERT_EQ(histogramWindowing.max(), 4); - - // dropping oldest window with value 2, remaining 3 ~ 5 - PopulateHistogram(histogramWindowing, 5, 5, 100); - clock->SleepForMicroseconds(micros_per_window); - ASSERT_EQ(histogramWindowing.num(), 400); - ASSERT_EQ(histogramWindowing.min(), 3); - ASSERT_EQ(histogramWindowing.max(), 5); -} - -TEST_F(HistogramTest, LargeStandardDeviation) { - HistogramImpl histogram; - PopulateHistogram(histogram, 1, 1000000); - ASSERT_LT(fabs(histogram.StandardDeviation() - 288675), 1); -} - -TEST_F(HistogramTest, LostUpdateStandardDeviation) { - HistogramImpl histogram; - PopulateHistogram(histogram, 100, 100, 100); - // Simulate a possible lost update (since they are not atomic) - histogram.TEST_GetStats().sum_squares_ -= 10000; - // Ideally zero, but should never be negative or NaN - ASSERT_GE(histogram.StandardDeviation(), 0.0); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/monitoring/iostats_context_test.cc b/monitoring/iostats_context_test.cc deleted file mode 100644 index 5fce33406..000000000 --- a/monitoring/iostats_context_test.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/iostats_context.h" - -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -TEST(IOStatsContextTest, ToString) { - get_iostats_context()->Reset(); - get_iostats_context()->bytes_read = 12345; - - std::string zero_included = get_iostats_context()->ToString(); - ASSERT_NE(std::string::npos, zero_included.find("= 0")); - ASSERT_NE(std::string::npos, zero_included.find("= 12345")); - - std::string zero_excluded = get_iostats_context()->ToString(true); - ASSERT_EQ(std::string::npos, zero_excluded.find("= 0")); - ASSERT_NE(std::string::npos, zero_excluded.find("= 12345")); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/monitoring/statistics_test.cc b/monitoring/statistics_test.cc deleted file mode 100644 index 98aae0c82..000000000 --- a/monitoring/statistics_test.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// - -#include "rocksdb/statistics.h" - -#include "port/stack_trace.h" -#include "rocksdb/convenience.h" -#include "rocksdb/utilities/options_type.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -class StatisticsTest : public testing::Test {}; - -// Sanity check to make sure that contents and order of TickersNameMap -// match Tickers enum -TEST_F(StatisticsTest, SanityTickers) { - EXPECT_EQ(static_cast(Tickers::TICKER_ENUM_MAX), - TickersNameMap.size()); - - for (uint32_t t = 0; t < Tickers::TICKER_ENUM_MAX; t++) { - auto pair = TickersNameMap[static_cast(t)]; - ASSERT_EQ(pair.first, t) << "Miss match at " << pair.second; - } -} - -// Sanity check to make sure that contents and order of HistogramsNameMap -// match Tickers enum -TEST_F(StatisticsTest, SanityHistograms) { - EXPECT_EQ(static_cast(Histograms::HISTOGRAM_ENUM_MAX), - HistogramsNameMap.size()); - - for (uint32_t h = 0; h < Histograms::HISTOGRAM_ENUM_MAX; h++) { - auto pair = HistogramsNameMap[static_cast(h)]; - ASSERT_EQ(pair.first, h) << "Miss match at " << pair.second; - } -} - -TEST_F(StatisticsTest, NoNameStats) { - static std::unordered_map no_name_opt_info = { - {"inner", - OptionTypeInfo::AsCustomSharedPtr( - 0, OptionVerificationType::kByName, - OptionTypeFlags::kAllowNull | OptionTypeFlags::kCompareNever)}, - }; - - class DefaultNameStatistics : public Statistics { - public: - DefaultNameStatistics(const std::shared_ptr& stats = nullptr) - : inner(stats) { - RegisterOptions("", &inner, &no_name_opt_info); - } - - uint64_t getTickerCount(uint32_t /*tickerType*/) const override { - return 0; - } - void histogramData(uint32_t /*type*/, - HistogramData* const /*data*/) const override {} - void recordTick(uint32_t /*tickerType*/, uint64_t /*count*/) override {} - void setTickerCount(uint32_t /*tickerType*/, uint64_t /*count*/) override {} - uint64_t getAndResetTickerCount(uint32_t /*tickerType*/) override { - return 0; - } - std::shared_ptr inner; - }; - ConfigOptions options; - options.ignore_unsupported_options = false; - auto stats = std::make_shared(); - ASSERT_STREQ(stats->Name(), ""); - ASSERT_EQ("", stats->ToString( - options)); // A stats with no name with have no options... - ASSERT_OK(stats->ConfigureFromString(options, "inner=")); - ASSERT_EQ("", stats->ToString( - options)); // A stats with no name with have no options... - ASSERT_NE(stats->inner, nullptr); - ASSERT_NE("", stats->inner->ToString(options)); // ... even if it does... -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/monitoring/stats_history_test.cc b/monitoring/stats_history_test.cc deleted file mode 100644 index cfed7bad7..000000000 --- a/monitoring/stats_history_test.cc +++ /dev/null @@ -1,662 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -#include "rocksdb/stats_history.h" - -#include -#include -#include - -#include "db/column_family.h" -#include "db/db_impl/db_impl.h" -#include "db/db_test_util.h" -#include "db/periodic_task_scheduler.h" -#include "monitoring/persistent_stats_history.h" -#include "options/options_helper.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/rate_limiter.h" -#include "test_util/mock_time_env.h" -#include "test_util/sync_point.h" -#include "test_util/testutil.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -class StatsHistoryTest : public DBTestBase { - public: - StatsHistoryTest() : DBTestBase("stats_history_test", /*env_do_fsync=*/true) { - mock_clock_ = std::make_shared(env_->GetSystemClock()); - mock_env_.reset(new CompositeEnvWrapper(env_, mock_clock_)); - } - - protected: - std::shared_ptr mock_clock_; - std::unique_ptr mock_env_; - - void SetUp() override { - mock_clock_->InstallTimedWaitFixCallback(); - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::StartPeriodicTaskScheduler:Init", [&](void* arg) { - auto periodic_task_scheduler_ptr = - reinterpret_cast(arg); - periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock_.get()); - }); - } -}; - -TEST_F(StatsHistoryTest, RunStatsDumpPeriodSec) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_dump_period_sec = kPeriodSec; - options.env = mock_env_.get(); - int counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::DumpStats:1", - [&](void* /*arg*/) { counter++; }); - Reopen(options); - ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_dump_period_sec); - - // Wait for the first stats persist to finish, as the initial delay could be - // different. - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - ASSERT_GE(counter, 1); - - // Test cancel job through SetOptions - ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "0"}})); - int old_val = counter; - for (int i = 1; i < 20; ++i) { - mock_clock_->MockSleepForSeconds(kPeriodSec); - } - ASSERT_EQ(counter, old_val); - Close(); -} - -// Test persistent stats background thread scheduling and cancelling -TEST_F(StatsHistoryTest, StatsPersistScheduling) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = kPeriodSec; - options.env = mock_env_.get(); - int counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::PersistStats:Entry", - [&](void* /*arg*/) { counter++; }); - Reopen(options); - ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_persist_period_sec); - - // Wait for the first stats persist to finish, as the initial delay could be - // different. - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - ASSERT_GE(counter, 1); - - // Test cancel job through SetOptions - ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}})); - int old_val = counter; - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec * 2); }); - ASSERT_EQ(counter, old_val); - - Close(); -} - -// Test enabling persistent stats for the first time -TEST_F(StatsHistoryTest, PersistentStatsFreshInstall) { - constexpr unsigned int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = 0; - options.env = mock_env_.get(); - int counter = 0; - SyncPoint::GetInstance()->SetCallBack("DBImpl::PersistStats:Entry", - [&](void* /*arg*/) { counter++; }); - Reopen(options); - ASSERT_OK(dbfull()->SetDBOptions( - {{"stats_persist_period_sec", std::to_string(kPeriodSec)}})); - ASSERT_EQ(kPeriodSec, dbfull()->GetDBOptions().stats_persist_period_sec); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - ASSERT_GE(counter, 1); - Close(); -} - -// TODO(Zhongyi): Move persistent stats related tests to a separate file -TEST_F(StatsHistoryTest, GetStatsHistoryInMemory) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = kPeriodSec; - options.statistics = CreateDBStatistics(); - options.env = mock_env_.get(); - CreateColumnFamilies({"pikachu"}, options); - ASSERT_OK(Put("foo", "bar")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - // make sure the first stats persist to finish - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - // Wait for stats persist to finish - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - - std::unique_ptr stats_iter; - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - // disabled stats snapshots - ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}})); - size_t stats_count = 0; - for (; stats_iter->Valid(); stats_iter->Next()) { - auto stats_map = stats_iter->GetStatsMap(); - ASSERT_EQ(stats_iter->GetStatsTime(), mock_clock_->NowSeconds()); - stats_count += stats_map.size(); - } - ASSERT_GT(stats_count, 0); - // Wait a bit and verify no more stats are found - for (int i = 0; i < 10; ++i) { - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(1); }); - } - ASSERT_OK(db_->GetStatsHistory(0, mock_clock_->NowSeconds(), &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - size_t stats_count_new = 0; - for (; stats_iter->Valid(); stats_iter->Next()) { - stats_count_new += stats_iter->GetStatsMap().size(); - } - ASSERT_EQ(stats_count_new, stats_count); - Close(); -} - -TEST_F(StatsHistoryTest, InMemoryStatsHistoryPurging) { - constexpr int kPeriodSec = 1; - Options options; - options.create_if_missing = true; - options.statistics = CreateDBStatistics(); - options.stats_persist_period_sec = kPeriodSec; - options.env = mock_env_.get(); - - CreateColumnFamilies({"pikachu"}, options); - ASSERT_OK(Put("foo", "bar")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - // some random operation to populate statistics - ASSERT_OK(Delete("foo")); - ASSERT_OK(Put("sol", "sol")); - ASSERT_OK(Put("epic", "epic")); - ASSERT_OK(Put("ltd", "ltd")); - ASSERT_EQ("sol", Get("sol")); - ASSERT_EQ("epic", Get("epic")); - ASSERT_EQ("ltd", Get("ltd")); - Iterator* iterator = db_->NewIterator(ReadOptions()); - for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { - ASSERT_TRUE(iterator->key() == iterator->value()); - } - delete iterator; - ASSERT_OK(Flush()); - ASSERT_OK(Delete("sol")); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - // second round of ops - ASSERT_OK(Put("saigon", "saigon")); - ASSERT_OK(Put("noodle talk", "noodle talk")); - ASSERT_OK(Put("ping bistro", "ping bistro")); - iterator = db_->NewIterator(ReadOptions()); - for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { - ASSERT_TRUE(iterator->key() == iterator->value()); - } - delete iterator; - ASSERT_OK(Flush()); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - - const int kIterations = 10; - for (int i = 0; i < kIterations; ++i) { - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - } - - std::unique_ptr stats_iter; - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - size_t stats_count = 0; - int slice_count = 0; - for (; stats_iter->Valid(); stats_iter->Next()) { - slice_count++; - auto stats_map = stats_iter->GetStatsMap(); - stats_count += stats_map.size(); - } - size_t stats_history_size = dbfull()->TEST_EstimateInMemoryStatsHistorySize(); - ASSERT_GE(slice_count, kIterations - 1); - ASSERT_GE(stats_history_size, 15000); - // capping memory cost at 15000 bytes since one slice is around 10000~15000 - ASSERT_OK(dbfull()->SetDBOptions({{"stats_history_buffer_size", "15000"}})); - ASSERT_EQ(15000, dbfull()->GetDBOptions().stats_history_buffer_size); - - // Wait for stats persist to finish - for (int i = 0; i < kIterations; ++i) { - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - } - - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - size_t stats_count_reopen = 0; - slice_count = 0; - for (; stats_iter->Valid(); stats_iter->Next()) { - slice_count++; - auto stats_map = stats_iter->GetStatsMap(); - stats_count_reopen += stats_map.size(); - } - size_t stats_history_size_reopen = - dbfull()->TEST_EstimateInMemoryStatsHistorySize(); - // only one slice can fit under the new stats_history_buffer_size - ASSERT_LT(slice_count, 2); - ASSERT_TRUE(stats_history_size_reopen < 15000 && - stats_history_size_reopen > 0); - ASSERT_TRUE(stats_count_reopen < stats_count && stats_count_reopen > 0); - Close(); - // TODO: may also want to verify stats timestamp to make sure we are purging - // the correct stats snapshot -} - -int countkeys(Iterator* iter) { - int count = 0; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - count++; - } - return count; -} - -TEST_F(StatsHistoryTest, GetStatsHistoryFromDisk) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = kPeriodSec; - options.statistics = CreateDBStatistics(); - options.persist_stats_to_disk = true; - options.env = mock_env_.get(); - CreateColumnFamilies({"pikachu"}, options); - ASSERT_OK(Put("foo", "bar")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ(Get("foo"), "bar"); - - // Wait for the first stats persist to finish, as the initial delay could be - // different. - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - // Wait for stats persist to finish - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - - auto iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - int key_count1 = countkeys(iter); - delete iter; - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - int key_count2 = countkeys(iter); - delete iter; - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - int key_count3 = countkeys(iter); - delete iter; - ASSERT_GE(key_count2, key_count1); - ASSERT_GE(key_count3, key_count2); - ASSERT_EQ(key_count3 - key_count2, key_count2 - key_count1); - std::unique_ptr stats_iter; - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - size_t stats_count = 0; - int slice_count = 0; - int non_zero_count = 0; - for (int i = 2; stats_iter->Valid(); stats_iter->Next(), i++) { - slice_count++; - auto stats_map = stats_iter->GetStatsMap(); - ASSERT_EQ(stats_iter->GetStatsTime(), kPeriodSec * i - 1); - for (auto& stat : stats_map) { - if (stat.second != 0) { - non_zero_count++; - } - } - stats_count += stats_map.size(); - } - ASSERT_EQ(slice_count, 3); - // 2 extra keys for format version - ASSERT_EQ(stats_count, key_count3 - 2); - // verify reopen will not cause data loss - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - size_t stats_count_reopen = 0; - int slice_count_reopen = 0; - int non_zero_count_recover = 0; - for (; stats_iter->Valid(); stats_iter->Next()) { - slice_count_reopen++; - auto stats_map = stats_iter->GetStatsMap(); - for (auto& stat : stats_map) { - if (stat.second != 0) { - non_zero_count_recover++; - } - } - stats_count_reopen += stats_map.size(); - } - - ASSERT_EQ(non_zero_count, non_zero_count_recover); - ASSERT_EQ(slice_count, slice_count_reopen); - ASSERT_EQ(stats_count, stats_count_reopen); - Close(); -} - -// Test persisted stats matches the value found in options.statistics and -// the stats value retains after DB reopen -TEST_F(StatsHistoryTest, PersitentStatsVerifyValue) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = kPeriodSec; - options.statistics = CreateDBStatistics(); - options.persist_stats_to_disk = true; - std::map stats_map_before; - ASSERT_TRUE(options.statistics->getTickerMap(&stats_map_before)); - options.env = mock_env_.get(); - CreateColumnFamilies({"pikachu"}, options); - ASSERT_OK(Put("foo", "bar")); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_EQ(Get("foo"), "bar"); - - // Wait for the first stats persist to finish, as the initial delay could be - // different. - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - // Wait for stats persist to finish - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - auto iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - countkeys(iter); - delete iter; - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - countkeys(iter); - delete iter; - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - countkeys(iter); - delete iter; - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - - std::map stats_map_after; - ASSERT_TRUE(options.statistics->getTickerMap(&stats_map_after)); - std::unique_ptr stats_iter; - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - std::string sample = "rocksdb.num.iterator.deleted"; - uint64_t recovered_value = 0; - for (int i = 2; stats_iter->Valid(); stats_iter->Next(), ++i) { - auto stats_map = stats_iter->GetStatsMap(); - ASSERT_EQ(stats_iter->GetStatsTime(), kPeriodSec * i - 1); - for (const auto& stat : stats_map) { - if (sample.compare(stat.first) == 0) { - recovered_value += stat.second; - } - } - } - ASSERT_EQ(recovered_value, stats_map_after[sample]); - - // test stats value retains after recovery - ReopenWithColumnFamilies({"default", "pikachu"}, options); - ASSERT_OK( - db_->GetStatsHistory(0, mock_clock_->NowSeconds() + 1, &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - uint64_t new_recovered_value = 0; - for (int i = 2; stats_iter->Valid(); stats_iter->Next(), i++) { - auto stats_map = stats_iter->GetStatsMap(); - ASSERT_EQ(stats_iter->GetStatsTime(), kPeriodSec * i - 1); - for (const auto& stat : stats_map) { - if (sample.compare(stat.first) == 0) { - new_recovered_value += stat.second; - } - } - } - ASSERT_EQ(recovered_value, new_recovered_value); - - // TODO(Zhongyi): also add test to read raw values from disk and verify - // correctness - Close(); -} - -// TODO(Zhongyi): add test for different format versions - -TEST_F(StatsHistoryTest, PersistentStatsCreateColumnFamilies) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.stats_persist_period_sec = kPeriodSec; - options.statistics = CreateDBStatistics(); - options.persist_stats_to_disk = true; - options.env = mock_env_.get(); - ASSERT_OK(TryReopen(options)); - CreateColumnFamilies({"one", "two", "three"}, options); - ASSERT_OK(Put(1, "foo", "bar")); - ReopenWithColumnFamilies({"default", "one", "two", "three"}, options); - ASSERT_EQ(Get(2, "foo"), "bar"); - CreateColumnFamilies({"four"}, options); - ReopenWithColumnFamilies({"default", "one", "two", "three", "four"}, options); - ASSERT_EQ(Get(2, "foo"), "bar"); - - // make sure the first stats persist to finish - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - auto iter = - db_->NewIterator(ReadOptions(), dbfull()->PersistentStatsColumnFamily()); - int key_count = countkeys(iter); - delete iter; - ASSERT_GE(key_count, 0); - uint64_t num_write_wal = 0; - std::string sample = "rocksdb.write.wal"; - std::unique_ptr stats_iter; - ASSERT_OK(db_->GetStatsHistory(0, mock_clock_->NowSeconds(), &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - for (; stats_iter->Valid(); stats_iter->Next()) { - auto stats_map = stats_iter->GetStatsMap(); - for (const auto& stat : stats_map) { - if (sample.compare(stat.first) == 0) { - num_write_wal += stat.second; - } - } - } - stats_iter.reset(); - ASSERT_EQ(num_write_wal, 1); - - options.persist_stats_to_disk = false; - ReopenWithColumnFamilies({"default", "one", "two", "three", "four"}, options); - int cf_count = 0; - for (auto cfd : *dbfull()->versions_->GetColumnFamilySet()) { - (void)cfd; - cf_count++; - } - // persistent stats cf will be implicitly opened even if - // persist_stats_to_disk is false - ASSERT_EQ(cf_count, 6); - ASSERT_EQ(Get(2, "foo"), "bar"); - - // attempt to create column family using same name, should fail - ColumnFamilyOptions cf_opts(options); - ColumnFamilyHandle* handle; - ASSERT_NOK(db_->CreateColumnFamily(cf_opts, kPersistentStatsColumnFamilyName, - &handle)); - - options.persist_stats_to_disk = true; - ReopenWithColumnFamilies({"default", "one", "two", "three", "four"}, options); - ASSERT_NOK(db_->CreateColumnFamily(cf_opts, kPersistentStatsColumnFamilyName, - &handle)); - // verify stats is not affected by prior failed CF creation - ASSERT_OK(db_->GetStatsHistory(0, mock_clock_->NowSeconds(), &stats_iter)); - ASSERT_TRUE(stats_iter != nullptr); - num_write_wal = 0; - for (; stats_iter->Valid(); stats_iter->Next()) { - auto stats_map = stats_iter->GetStatsMap(); - for (const auto& stat : stats_map) { - if (sample.compare(stat.first) == 0) { - num_write_wal += stat.second; - } - } - } - ASSERT_EQ(num_write_wal, 1); - - Close(); - Destroy(options); -} - -TEST_F(StatsHistoryTest, PersistentStatsReadOnly) { - ASSERT_OK(Put("bar", "v2")); - Close(); - - auto options = CurrentOptions(); - options.stats_persist_period_sec = 5; - options.persist_stats_to_disk = true; - assert(options.env == env_); - ASSERT_OK(ReadOnlyReopen(options)); - ASSERT_EQ("v2", Get("bar")); - Close(); - - // Reopen and flush memtable. - ASSERT_OK(TryReopen(options)); - ASSERT_OK(Flush()); - Close(); - // Now check keys in read only mode. - ASSERT_OK(ReadOnlyReopen(options)); -} - -TEST_F(StatsHistoryTest, ForceManualFlushStatsCF) { - constexpr int kPeriodSec = 5; - Options options; - options.create_if_missing = true; - options.write_buffer_size = 1024 * 1024 * 10; // 10 Mb - options.stats_persist_period_sec = kPeriodSec; - options.statistics = CreateDBStatistics(); - options.persist_stats_to_disk = true; - options.env = mock_env_.get(); - CreateColumnFamilies({"pikachu"}, options); - ReopenWithColumnFamilies({"default", "pikachu"}, options); - - // Wait for the first stats persist to finish, as the initial delay could be - // different. - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec - 1); }); - - ColumnFamilyData* cfd_default = - static_cast(dbfull()->DefaultColumnFamily()) - ->cfd(); - ColumnFamilyData* cfd_stats = static_cast( - dbfull()->PersistentStatsColumnFamily()) - ->cfd(); - ColumnFamilyData* cfd_test = - static_cast(handles_[1])->cfd(); - - ASSERT_OK(Put("foo", "v0")); - ASSERT_OK(Put("bar", "v0")); - ASSERT_EQ("v0", Get("bar")); - ASSERT_EQ("v0", Get("foo")); - ASSERT_OK(Put(1, "Eevee", "v0")); - ASSERT_EQ("v0", Get(1, "Eevee")); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - // writing to all three cf, flush default cf - // LogNumbers: default: 16, stats: 10, pikachu: 5 - // Since in recovery process, cfd_stats column is created after WAL is - // created, synced and MANIFEST is persisted, its log number which depends on - // logfile_number_ will be different. Since "pikachu" is never flushed, thus - // its log_number should be the smallest of the three. - ASSERT_OK(Flush()); - ASSERT_LT(cfd_test->GetLogNumber(), cfd_stats->GetLogNumber()); - ASSERT_LT(cfd_test->GetLogNumber(), cfd_default->GetLogNumber()); - - ASSERT_OK(Put("foo1", "v1")); - ASSERT_OK(Put("bar1", "v1")); - ASSERT_EQ("v1", Get("bar1")); - ASSERT_EQ("v1", Get("foo1")); - ASSERT_OK(Put(1, "Vaporeon", "v1")); - ASSERT_EQ("v1", Get(1, "Vaporeon")); - // writing to default and test cf, flush test cf - // LogNumbers: default: 14, stats: 16, pikachu: 16 - ASSERT_OK(Flush(1)); - ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber()); - ASSERT_GT(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber()); - - ASSERT_OK(Put("foo2", "v2")); - ASSERT_OK(Put("bar2", "v2")); - ASSERT_EQ("v2", Get("bar2")); - ASSERT_EQ("v2", Get("foo2")); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - // writing to default and stats cf, flushing default cf - // LogNumbers: default: 19, stats: 19, pikachu: 19 - ASSERT_OK(Flush()); - ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber()); - ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber()); - - ASSERT_OK(Put("foo3", "v3")); - ASSERT_OK(Put("bar3", "v3")); - ASSERT_EQ("v3", Get("bar3")); - ASSERT_EQ("v3", Get("foo3")); - ASSERT_OK(Put(1, "Jolteon", "v3")); - ASSERT_EQ("v3", Get(1, "Jolteon")); - - dbfull()->TEST_WaitForPeriodicTaskRun( - [&] { mock_clock_->MockSleepForSeconds(kPeriodSec); }); - // writing to all three cf, flushing test cf - // LogNumbers: default: 19, stats: 19, pikachu: 22 - ASSERT_OK(Flush(1)); - ASSERT_LT(cfd_stats->GetLogNumber(), cfd_test->GetLogNumber()); - ASSERT_EQ(cfd_stats->GetLogNumber(), cfd_default->GetLogNumber()); - Close(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/options/configurable_test.cc b/options/configurable_test.cc deleted file mode 100644 index a03d8f0a5..000000000 --- a/options/configurable_test.cc +++ /dev/null @@ -1,861 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "options/configurable_test.h" - -#include -#include -#include -#include - -#include "options/configurable_helper.h" -#include "options/options_helper.h" -#include "options/options_parser.h" -#include "rocksdb/configurable.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -#ifndef GFLAGS -bool FLAGS_enable_print = false; -#else -#include "util/gflags_compat.h" -using GFLAGS_NAMESPACE::ParseCommandLineFlags; -DEFINE_bool(enable_print, false, "Print options generated to console."); -#endif // GFLAGS - -namespace ROCKSDB_NAMESPACE { -namespace test { -class StringLogger : public Logger { - public: - using Logger::Logv; - void Logv(const char* format, va_list ap) override { - char buffer[1000]; - vsnprintf(buffer, sizeof(buffer), format, ap); - string_.append(buffer); - } - const std::string& str() const { return string_; } - void clear() { string_.clear(); } - - private: - std::string string_; -}; -static std::unordered_map struct_option_info = { - {"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0, - OptionVerificationType::kNormal, - OptionTypeFlags::kMutable)}, -}; - -static std::unordered_map imm_struct_option_info = - { - {"struct", OptionTypeInfo::Struct("struct", &simple_option_info, 0, - OptionVerificationType::kNormal, - OptionTypeFlags::kNone)}, -}; - -class SimpleConfigurable : public TestConfigurable { - public: - static SimpleConfigurable* Create( - const std::string& name = "simple", - int mode = TestConfigMode::kDefaultMode, - const std::unordered_map* map = - &simple_option_info) { - return new SimpleConfigurable(name, mode, map); - } - - SimpleConfigurable(const std::string& name, int mode, - const std::unordered_map* - map = &simple_option_info) - : TestConfigurable(name, mode, map) { - if ((mode & TestConfigMode::kUniqueMode) != 0) { - unique_.reset(SimpleConfigurable::Create("Unique" + name_)); - RegisterOptions(name_ + "Unique", &unique_, &unique_option_info); - } - if ((mode & TestConfigMode::kSharedMode) != 0) { - shared_.reset(SimpleConfigurable::Create("Shared" + name_)); - RegisterOptions(name_ + "Shared", &shared_, &shared_option_info); - } - if ((mode & TestConfigMode::kRawPtrMode) != 0) { - pointer_ = SimpleConfigurable::Create("Pointer" + name_); - RegisterOptions(name_ + "Pointer", &pointer_, &pointer_option_info); - } - } - -}; // End class SimpleConfigurable - -using ConfigTestFactoryFunc = std::function; - -class ConfigurableTest : public testing::Test { - public: - ConfigurableTest() { config_options_.invoke_prepare_options = false; } - - ConfigOptions config_options_; -}; - -TEST_F(ConfigurableTest, GetOptionsPtrTest) { - std::string opt_str; - std::unique_ptr configurable(SimpleConfigurable::Create()); - ASSERT_NE(configurable->GetOptions("simple"), nullptr); - ASSERT_EQ(configurable->GetOptions("bad-opt"), nullptr); -} - -TEST_F(ConfigurableTest, ConfigureFromMapTest) { - std::unique_ptr configurable(SimpleConfigurable::Create()); - auto* opts = configurable->GetOptions("simple"); - ASSERT_OK(configurable->ConfigureFromMap(config_options_, {})); - ASSERT_NE(opts, nullptr); - std::unordered_map options_map = { - {"int", "1"}, {"bool", "true"}, {"string", "string"}}; - ASSERT_OK(configurable->ConfigureFromMap(config_options_, options_map)); - ASSERT_EQ(opts->i, 1); - ASSERT_EQ(opts->b, true); - ASSERT_EQ(opts->s, "string"); -} - -TEST_F(ConfigurableTest, ConfigureFromStringTest) { - std::unique_ptr configurable(SimpleConfigurable::Create()); - auto* opts = configurable->GetOptions("simple"); - ASSERT_OK(configurable->ConfigureFromString(config_options_, "")); - ASSERT_NE(opts, nullptr); - ASSERT_OK(configurable->ConfigureFromString(config_options_, - "int=1;bool=true;string=s")); - ASSERT_EQ(opts->i, 1); - ASSERT_EQ(opts->b, true); - ASSERT_EQ(opts->s, "s"); -} - -TEST_F(ConfigurableTest, ConfigureIgnoreTest) { - std::unique_ptr configurable(SimpleConfigurable::Create()); - std::unordered_map options_map = {{"unused", "u"}}; - ConfigOptions ignore = config_options_; - ignore.ignore_unknown_options = true; - ASSERT_NOK(configurable->ConfigureFromMap(config_options_, options_map)); - ASSERT_OK(configurable->ConfigureFromMap(ignore, options_map)); - ASSERT_NOK(configurable->ConfigureFromString(config_options_, "unused=u")); - ASSERT_OK(configurable->ConfigureFromString(ignore, "unused=u")); -} - -TEST_F(ConfigurableTest, ConfigureNestedOptionsTest) { - std::unique_ptr base, copy; - std::string opt_str; - std::string mismatch; - - base.reset(SimpleConfigurable::Create("simple", TestConfigMode::kAllOptMode)); - copy.reset(SimpleConfigurable::Create("simple", TestConfigMode::kAllOptMode)); - ASSERT_OK(base->ConfigureFromString(config_options_, - "shared={int=10; string=10};" - "unique={int=20; string=20};" - "pointer={int=30; string=30};")); - ASSERT_OK(base->GetOptionString(config_options_, &opt_str)); - ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -TEST_F(ConfigurableTest, GetOptionsTest) { - std::unique_ptr simple; - - simple.reset( - SimpleConfigurable::Create("simple", TestConfigMode::kAllOptMode)); - int i = 11; - for (auto opt : {"", "shared.", "unique.", "pointer."}) { - std::string value; - std::string expected = std::to_string(i); - std::string opt_name = opt; - ASSERT_OK( - simple->ConfigureOption(config_options_, opt_name + "int", expected)); - ASSERT_OK(simple->GetOption(config_options_, opt_name + "int", &value)); - ASSERT_EQ(expected, value); - ASSERT_OK(simple->ConfigureOption(config_options_, opt_name + "string", - expected)); - ASSERT_OK(simple->GetOption(config_options_, opt_name + "string", &value)); - ASSERT_EQ(expected, value); - - ASSERT_NOK( - simple->ConfigureOption(config_options_, opt_name + "bad", expected)); - ASSERT_NOK(simple->GetOption(config_options_, "bad option", &value)); - ASSERT_TRUE(value.empty()); - i += 11; - } -} - -TEST_F(ConfigurableTest, ConfigureBadOptionsTest) { - std::unique_ptr configurable(SimpleConfigurable::Create()); - auto* opts = configurable->GetOptions("simple"); - ASSERT_NE(opts, nullptr); - ASSERT_OK(configurable->ConfigureOption(config_options_, "int", "42")); - ASSERT_EQ(opts->i, 42); - ASSERT_NOK(configurable->ConfigureOption(config_options_, "int", "fred")); - ASSERT_NOK(configurable->ConfigureOption(config_options_, "bool", "fred")); - ASSERT_NOK( - configurable->ConfigureFromString(config_options_, "int=33;unused=u")); - ASSERT_EQ(opts->i, 42); -} - -TEST_F(ConfigurableTest, InvalidOptionTest) { - std::unique_ptr configurable(SimpleConfigurable::Create()); - std::unordered_map options_map = { - {"bad-option", "bad"}}; - ASSERT_NOK(configurable->ConfigureFromMap(config_options_, options_map)); - ASSERT_NOK( - configurable->ConfigureFromString(config_options_, "bad-option=bad")); - ASSERT_NOK( - configurable->ConfigureOption(config_options_, "bad-option", "bad")); -} - -static std::unordered_map validated_option_info = { - {"validated", - {0, OptionType::kBoolean, OptionVerificationType::kNormal, - OptionTypeFlags::kNone}}, -}; -static std::unordered_map prepared_option_info = { - {"prepared", - {0, OptionType::kInt, OptionVerificationType::kNormal, - OptionTypeFlags::kMutable}}, -}; -static std::unordered_map - dont_prepare_option_info = { - {"unique", - {0, OptionType::kConfigurable, OptionVerificationType::kNormal, - (OptionTypeFlags::kUnique | OptionTypeFlags::kDontPrepare)}}, - -}; - -class ValidatedConfigurable : public SimpleConfigurable { - public: - ValidatedConfigurable(const std::string& name, unsigned char mode, - bool dont_prepare = false) - : SimpleConfigurable(name, TestConfigMode::kDefaultMode), - validated(false), - prepared(0) { - RegisterOptions("Validated", &validated, &validated_option_info); - RegisterOptions("Prepared", &prepared, &prepared_option_info); - if ((mode & TestConfigMode::kUniqueMode) != 0) { - unique_.reset(new ValidatedConfigurable( - "Unique" + name_, TestConfigMode::kDefaultMode, false)); - if (dont_prepare) { - RegisterOptions(name_ + "Unique", &unique_, &dont_prepare_option_info); - } else { - RegisterOptions(name_ + "Unique", &unique_, &unique_option_info); - } - } - } - - Status PrepareOptions(const ConfigOptions& config_options) override { - if (++prepared <= 0) { - return Status::InvalidArgument("Cannot prepare option"); - } else { - return SimpleConfigurable::PrepareOptions(config_options); - } - } - - Status ValidateOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { - if (!validated) { - return Status::InvalidArgument("Not Validated"); - } else { - return SimpleConfigurable::ValidateOptions(db_opts, cf_opts); - } - } - - private: - bool validated; - int prepared; -}; - -TEST_F(ConfigurableTest, ValidateOptionsTest) { - std::unique_ptr configurable( - new ValidatedConfigurable("validated", TestConfigMode::kDefaultMode)); - ColumnFamilyOptions cf_opts; - DBOptions db_opts; - ASSERT_OK( - configurable->ConfigureOption(config_options_, "validated", "false")); - ASSERT_NOK(configurable->ValidateOptions(db_opts, cf_opts)); - ASSERT_OK( - configurable->ConfigureOption(config_options_, "validated", "true")); - ASSERT_OK(configurable->ValidateOptions(db_opts, cf_opts)); -} - -TEST_F(ConfigurableTest, PrepareOptionsTest) { - std::unique_ptr c( - new ValidatedConfigurable("Simple", TestConfigMode::kUniqueMode, false)); - auto cp = c->GetOptions("Prepared"); - auto u = c->GetOptions>("SimpleUnique"); - auto up = u->get()->GetOptions("Prepared"); - config_options_.invoke_prepare_options = false; - - ASSERT_NE(cp, nullptr); - ASSERT_NE(up, nullptr); - ASSERT_EQ(*cp, 0); - ASSERT_EQ(*up, 0); - ASSERT_OK(c->ConfigureFromMap(config_options_, {})); - ASSERT_EQ(*cp, 0); - ASSERT_EQ(*up, 0); - config_options_.invoke_prepare_options = true; - ASSERT_OK(c->ConfigureFromMap(config_options_, {})); - ASSERT_EQ(*cp, 1); - ASSERT_EQ(*up, 1); - ASSERT_OK(c->ConfigureFromString(config_options_, "prepared=0")); - ASSERT_EQ(*up, 2); - ASSERT_EQ(*cp, 1); - - ASSERT_NOK(c->ConfigureFromString(config_options_, "prepared=-2")); - - c.reset( - new ValidatedConfigurable("Simple", TestConfigMode::kUniqueMode, true)); - cp = c->GetOptions("Prepared"); - u = c->GetOptions>("SimpleUnique"); - up = u->get()->GetOptions("Prepared"); - - ASSERT_OK(c->ConfigureFromString(config_options_, "prepared=0")); - ASSERT_EQ(*cp, 1); - ASSERT_EQ(*up, 0); -} - -TEST_F(ConfigurableTest, CopyObjectTest) { - class CopyConfigurable : public Configurable { - public: - CopyConfigurable() : prepared_(0), validated_(0) {} - Status PrepareOptions(const ConfigOptions& options) override { - prepared_++; - return Configurable::PrepareOptions(options); - } - Status ValidateOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { - validated_++; - return Configurable::ValidateOptions(db_opts, cf_opts); - } - int prepared_; - mutable int validated_; - }; - - CopyConfigurable c1; - ConfigOptions config_options; - Options options; - - ASSERT_OK(c1.PrepareOptions(config_options)); - ASSERT_OK(c1.ValidateOptions(options, options)); - ASSERT_EQ(c1.prepared_, 1); - ASSERT_EQ(c1.validated_, 1); - CopyConfigurable c2 = c1; - ASSERT_OK(c1.PrepareOptions(config_options)); - ASSERT_OK(c1.ValidateOptions(options, options)); - ASSERT_EQ(c2.prepared_, 1); - ASSERT_EQ(c2.validated_, 1); - ASSERT_EQ(c1.prepared_, 2); - ASSERT_EQ(c1.validated_, 2); -} - -TEST_F(ConfigurableTest, MutableOptionsTest) { - static std::unordered_map imm_option_info = { - {"imm", OptionTypeInfo::Struct("imm", &simple_option_info, 0, - OptionVerificationType::kNormal, - OptionTypeFlags::kNone)}, - }; - - class MutableConfigurable : public SimpleConfigurable { - public: - MutableConfigurable() - : SimpleConfigurable("mutable", TestConfigMode::kDefaultMode | - TestConfigMode::kUniqueMode | - TestConfigMode::kSharedMode) { - RegisterOptions("struct", &options_, &struct_option_info); - RegisterOptions("imm", &options_, &imm_option_info); - } - }; - MutableConfigurable mc; - ConfigOptions options = config_options_; - - ASSERT_OK(mc.ConfigureOption(options, "bool", "true")); - ASSERT_OK(mc.ConfigureOption(options, "int", "42")); - auto* opts = mc.GetOptions("mutable"); - ASSERT_NE(opts, nullptr); - ASSERT_EQ(opts->i, 42); - ASSERT_EQ(opts->b, true); - ASSERT_OK(mc.ConfigureOption(options, "struct", "{bool=false;}")); - ASSERT_OK(mc.ConfigureOption(options, "imm", "{int=55;}")); - - options.mutable_options_only = true; - - // Now only mutable options should be settable. - ASSERT_NOK(mc.ConfigureOption(options, "bool", "true")); - ASSERT_OK(mc.ConfigureOption(options, "int", "24")); - ASSERT_EQ(opts->i, 24); - ASSERT_EQ(opts->b, false); - ASSERT_NOK(mc.ConfigureFromString(options, "bool=false;int=33;")); - ASSERT_EQ(opts->i, 24); - ASSERT_EQ(opts->b, false); - - // Setting options through an immutable struct fails - ASSERT_NOK(mc.ConfigureOption(options, "imm", "{int=55;}")); - ASSERT_NOK(mc.ConfigureOption(options, "imm.int", "55")); - ASSERT_EQ(opts->i, 24); - ASSERT_EQ(opts->b, false); - - // Setting options through an mutable struct succeeds - ASSERT_OK(mc.ConfigureOption(options, "struct", "{int=44;}")); - ASSERT_EQ(opts->i, 44); - ASSERT_OK(mc.ConfigureOption(options, "struct.int", "55")); - ASSERT_EQ(opts->i, 55); - - // Setting nested immutable configurable options fail - ASSERT_NOK(mc.ConfigureOption(options, "shared", "{bool=true;}")); - ASSERT_NOK(mc.ConfigureOption(options, "shared.bool", "true")); - - // Setting nested mutable configurable options succeeds - ASSERT_OK(mc.ConfigureOption(options, "unique", "{bool=true}")); - ASSERT_OK(mc.ConfigureOption(options, "unique.bool", "true")); -} - -TEST_F(ConfigurableTest, DeprecatedOptionsTest) { - static std::unordered_map - deprecated_option_info = { - {"deprecated", - {offsetof(struct TestOptions, b), OptionType::kBoolean, - OptionVerificationType::kDeprecated, OptionTypeFlags::kNone}}}; - std::unique_ptr orig; - orig.reset(SimpleConfigurable::Create("simple", TestConfigMode::kDefaultMode, - &deprecated_option_info)); - auto* opts = orig->GetOptions("simple"); - ASSERT_NE(opts, nullptr); - opts->d = true; - ASSERT_OK(orig->ConfigureOption(config_options_, "deprecated", "false")); - ASSERT_TRUE(opts->d); - ASSERT_OK(orig->ConfigureFromString(config_options_, "deprecated=false")); - ASSERT_TRUE(opts->d); -} - -TEST_F(ConfigurableTest, AliasOptionsTest) { - static std::unordered_map alias_option_info = { - {"bool", - {offsetof(struct TestOptions, b), OptionType::kBoolean, - OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, - {"alias", - {offsetof(struct TestOptions, b), OptionType::kBoolean, - OptionVerificationType::kAlias, OptionTypeFlags::kNone, 0}}}; - std::unique_ptr orig; - orig.reset(SimpleConfigurable::Create("simple", TestConfigMode::kDefaultMode, - &alias_option_info)); - auto* opts = orig->GetOptions("simple"); - ASSERT_NE(opts, nullptr); - ASSERT_OK(orig->ConfigureOption(config_options_, "bool", "false")); - ASSERT_FALSE(opts->b); - ASSERT_OK(orig->ConfigureOption(config_options_, "alias", "true")); - ASSERT_TRUE(opts->b); - std::string opts_str; - ASSERT_OK(orig->GetOptionString(config_options_, &opts_str)); - ASSERT_EQ(opts_str.find("alias"), std::string::npos); - - ASSERT_OK(orig->ConfigureOption(config_options_, "bool", "false")); - ASSERT_FALSE(opts->b); - ASSERT_OK(orig->GetOption(config_options_, "alias", &opts_str)); - ASSERT_EQ(opts_str, "false"); -} - -TEST_F(ConfigurableTest, NestedUniqueConfigTest) { - std::unique_ptr simple; - simple.reset( - SimpleConfigurable::Create("Outer", TestConfigMode::kAllOptMode)); - const auto outer = simple->GetOptions("Outer"); - const auto unique = - simple->GetOptions>("OuterUnique"); - ASSERT_NE(outer, nullptr); - ASSERT_NE(unique, nullptr); - ASSERT_OK( - simple->ConfigureFromString(config_options_, "int=24;string=outer")); - ASSERT_OK(simple->ConfigureFromString(config_options_, - "unique={int=42;string=nested}")); - const auto inner = unique->get()->GetOptions("UniqueOuter"); - ASSERT_NE(inner, nullptr); - ASSERT_EQ(outer->i, 24); - ASSERT_EQ(outer->s, "outer"); - ASSERT_EQ(inner->i, 42); - ASSERT_EQ(inner->s, "nested"); -} - -TEST_F(ConfigurableTest, NestedSharedConfigTest) { - std::unique_ptr simple; - simple.reset(SimpleConfigurable::Create( - "Outer", TestConfigMode::kDefaultMode | TestConfigMode::kSharedMode)); - ASSERT_OK( - simple->ConfigureFromString(config_options_, "int=24;string=outer")); - ASSERT_OK(simple->ConfigureFromString(config_options_, - "shared={int=42;string=nested}")); - const auto outer = simple->GetOptions("Outer"); - const auto shared = - simple->GetOptions>("OuterShared"); - ASSERT_NE(outer, nullptr); - ASSERT_NE(shared, nullptr); - const auto inner = shared->get()->GetOptions("SharedOuter"); - ASSERT_NE(inner, nullptr); - ASSERT_EQ(outer->i, 24); - ASSERT_EQ(outer->s, "outer"); - ASSERT_EQ(inner->i, 42); - ASSERT_EQ(inner->s, "nested"); -} - -TEST_F(ConfigurableTest, NestedRawConfigTest) { - std::unique_ptr simple; - simple.reset(SimpleConfigurable::Create( - "Outer", TestConfigMode::kDefaultMode | TestConfigMode::kRawPtrMode)); - ASSERT_OK( - simple->ConfigureFromString(config_options_, "int=24;string=outer")); - ASSERT_OK(simple->ConfigureFromString(config_options_, - "pointer={int=42;string=nested}")); - const auto outer = simple->GetOptions("Outer"); - const auto pointer = simple->GetOptions("OuterPointer"); - ASSERT_NE(outer, nullptr); - ASSERT_NE(pointer, nullptr); - const auto inner = (*pointer)->GetOptions("PointerOuter"); - ASSERT_NE(inner, nullptr); - ASSERT_EQ(outer->i, 24); - ASSERT_EQ(outer->s, "outer"); - ASSERT_EQ(inner->i, 42); - ASSERT_EQ(inner->s, "nested"); -} - -TEST_F(ConfigurableTest, MatchesTest) { - std::string mismatch; - std::unique_ptr base, copy; - base.reset(SimpleConfigurable::Create( - "simple", TestConfigMode::kDefaultMode | TestConfigMode::kNestedMode)); - copy.reset(SimpleConfigurable::Create( - "simple", TestConfigMode::kDefaultMode | TestConfigMode::kNestedMode)); - ASSERT_OK(base->ConfigureFromString( - config_options_, - "int=11;string=outer;unique={int=22;string=u};shared={int=33;string=s}")); - ASSERT_OK(copy->ConfigureFromString( - config_options_, - "int=11;string=outer;unique={int=22;string=u};shared={int=33;string=s}")); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_OK(base->ConfigureOption(config_options_, "shared", "int=44")); - ASSERT_FALSE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_EQ(mismatch, "shared.int"); - std::string c1value, c2value; - ASSERT_OK(base->GetOption(config_options_, mismatch, &c1value)); - ASSERT_OK(copy->GetOption(config_options_, mismatch, &c2value)); - ASSERT_NE(c1value, c2value); -} - -static Configurable* SimpleStructFactory() { - return SimpleConfigurable::Create( - "simple-struct", TestConfigMode::kDefaultMode, &struct_option_info); -} - -TEST_F(ConfigurableTest, ConfigureStructTest) { - std::unique_ptr base(SimpleStructFactory()); - std::unique_ptr copy(SimpleStructFactory()); - std::string opt_str, value; - std::string mismatch; - std::unordered_set names; - - ASSERT_OK( - base->ConfigureFromString(config_options_, "struct={int=10; string=10}")); - ASSERT_OK(base->GetOptionString(config_options_, &opt_str)); - ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_OK(base->GetOptionNames(config_options_, &names)); - ASSERT_EQ(names.size(), 1); - ASSERT_EQ(*(names.begin()), "struct"); - ASSERT_OK( - base->ConfigureFromString(config_options_, "struct={int=20; string=20}")); - ASSERT_OK(base->GetOption(config_options_, "struct", &value)); - ASSERT_OK(copy->ConfigureOption(config_options_, "struct", value)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - - ASSERT_NOK(base->ConfigureFromString(config_options_, - "struct={int=10; string=10; bad=11}")); - ASSERT_OK(base->ConfigureOption(config_options_, "struct.int", "42")); - ASSERT_NOK(base->ConfigureOption(config_options_, "struct.bad", "42")); - ASSERT_NOK(base->GetOption(config_options_, "struct.bad", &value)); - ASSERT_OK(base->GetOption(config_options_, "struct.int", &value)); - ASSERT_EQ(value, "42"); -} - -TEST_F(ConfigurableTest, ConfigurableEnumTest) { - std::unique_ptr base, copy; - base.reset(SimpleConfigurable::Create("e", TestConfigMode::kEnumMode)); - copy.reset(SimpleConfigurable::Create("e", TestConfigMode::kEnumMode)); - - std::string opts_str; - std::string mismatch; - - ASSERT_OK(base->ConfigureFromString(config_options_, "enum=B")); - ASSERT_FALSE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_OK(base->GetOptionString(config_options_, &opts_str)); - ASSERT_OK(copy->ConfigureFromString(config_options_, opts_str)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_NOK(base->ConfigureOption(config_options_, "enum", "bad")); - ASSERT_NOK(base->ConfigureOption(config_options_, "unknown", "bad")); -} - -static std::unordered_map noserialize_option_info = - { - {"int", - {offsetof(struct TestOptions, i), OptionType::kInt, - OptionVerificationType::kNormal, OptionTypeFlags::kDontSerialize}}, -}; - -TEST_F(ConfigurableTest, TestNoSerialize) { - std::unique_ptr base; - base.reset(SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, - &noserialize_option_info)); - std::string opts_str, value; - ASSERT_OK(base->ConfigureFromString(config_options_, "int=10")); - ASSERT_OK(base->GetOptionString(config_options_, &opts_str)); - ASSERT_EQ(opts_str, ""); - ASSERT_NOK(base->GetOption(config_options_, "int", &value)); -} - -TEST_F(ConfigurableTest, TestNoCompare) { - std::unordered_map nocomp_option_info = { - {"int", - {offsetof(struct TestOptions, i), OptionType::kInt, - OptionVerificationType::kNormal, OptionTypeFlags::kCompareNever}}, - }; - std::unordered_map normal_option_info = { - {"int", - {offsetof(struct TestOptions, i), OptionType::kInt, - OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, - }; - - std::unique_ptr base, copy; - base.reset(SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, - &nocomp_option_info)); - copy.reset(SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, - &normal_option_info)); - ASSERT_OK(base->ConfigureFromString(config_options_, "int=10")); - ASSERT_OK(copy->ConfigureFromString(config_options_, "int=20")); - std::string bvalue, cvalue, mismatch; - ASSERT_OK(base->GetOption(config_options_, "int", &bvalue)); - ASSERT_OK(copy->GetOption(config_options_, "int", &cvalue)); - ASSERT_EQ(bvalue, "10"); - ASSERT_EQ(cvalue, "20"); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_FALSE(copy->AreEquivalent(config_options_, base.get(), &mismatch)); -} - -TEST_F(ConfigurableTest, NullOptionMapTest) { - std::unique_ptr base; - std::unordered_set names; - std::string str; - - base.reset( - SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, nullptr)); - ASSERT_NOK(base->ConfigureFromString(config_options_, "int=10")); - ASSERT_NOK(base->ConfigureFromString(config_options_, "int=20")); - ASSERT_NOK(base->ConfigureOption(config_options_, "int", "20")); - ASSERT_NOK(base->GetOption(config_options_, "int", &str)); - ASSERT_NE(base->GetOptions("c"), nullptr); - ASSERT_OK(base->GetOptionNames(config_options_, &names)); - ASSERT_EQ(names.size(), 0UL); - ASSERT_OK(base->PrepareOptions(config_options_)); - ASSERT_OK(base->ValidateOptions(DBOptions(), ColumnFamilyOptions())); - std::unique_ptr copy; - copy.reset( - SimpleConfigurable::Create("c", TestConfigMode::kDefaultMode, nullptr)); - ASSERT_OK(base->GetOptionString(config_options_, &str)); - ASSERT_OK(copy->ConfigureFromString(config_options_, str)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &str)); -} - -static std::unordered_map TestFactories = { - {"Simple", []() { return SimpleConfigurable::Create("simple"); }}, - {"Struct", []() { return SimpleStructFactory(); }}, - {"Unique", - []() { - return SimpleConfigurable::Create( - "simple", TestConfigMode::kSimpleMode | TestConfigMode::kUniqueMode); - }}, - {"Shared", - []() { - return SimpleConfigurable::Create( - "simple", TestConfigMode::kSimpleMode | TestConfigMode::kSharedMode); - }}, - {"Nested", - []() { - return SimpleConfigurable::Create( - "simple", TestConfigMode::kSimpleMode | TestConfigMode::kNestedMode); - }}, - {"Mutable", - []() { - return SimpleConfigurable::Create("simple", - TestConfigMode::kMutableMode | - TestConfigMode::kSimpleMode | - TestConfigMode::kNestedMode); - }}, - {"ThreeDeep", - []() { - Configurable* simple = SimpleConfigurable::Create( - "Simple", - TestConfigMode::kUniqueMode | TestConfigMode::kDefaultMode); - auto* unique = - simple->GetOptions>("SimpleUnique"); - unique->reset(SimpleConfigurable::Create( - "Child", - TestConfigMode::kUniqueMode | TestConfigMode::kDefaultMode)); - unique = unique->get()->GetOptions>( - "ChildUnique"); - unique->reset( - SimpleConfigurable::Create("Child", TestConfigMode::kDefaultMode)); - return simple; - }}, - {"DBOptions", - []() { - auto config = DBOptionsAsConfigurable(DBOptions()); - return config.release(); - }}, - {"CFOptions", - []() { - auto config = CFOptionsAsConfigurable(ColumnFamilyOptions()); - return config.release(); - }}, - {"BlockBased", []() { return NewBlockBasedTableFactory(); }}, -}; - -class ConfigurableParamTest : public ConfigurableTest, - virtual public ::testing::WithParamInterface< - std::pair> { - public: - ConfigurableParamTest() { - type_ = GetParam().first; - configuration_ = GetParam().second; - assert(TestFactories.find(type_) != TestFactories.end()); - object_.reset(CreateConfigurable()); - } - - Configurable* CreateConfigurable() { - const auto& iter = TestFactories.find(type_); - return (iter->second)(); - } - - void TestConfigureOptions(const ConfigOptions& opts); - std::string type_; - std::string configuration_; - std::unique_ptr object_; -}; - -void ConfigurableParamTest::TestConfigureOptions( - const ConfigOptions& config_options) { - std::unique_ptr base, copy; - std::unordered_set names; - std::string opt_str, mismatch; - - base.reset(CreateConfigurable()); - copy.reset(CreateConfigurable()); - - ASSERT_OK(base->ConfigureFromString(config_options, configuration_)); - ASSERT_OK(base->GetOptionString(config_options, &opt_str)); - ASSERT_OK(copy->ConfigureFromString(config_options, opt_str)); - ASSERT_OK(copy->GetOptionString(config_options, &opt_str)); - ASSERT_TRUE(base->AreEquivalent(config_options, copy.get(), &mismatch)); - - copy.reset(CreateConfigurable()); - ASSERT_OK(base->GetOptionNames(config_options, &names)); - std::unordered_map unused; - bool found_one = false; - for (auto name : names) { - std::string value; - Status s = base->GetOption(config_options, name, &value); - if (s.ok()) { - s = copy->ConfigureOption(config_options, name, value); - if (s.ok() || s.IsNotSupported()) { - found_one = true; - } else { - unused[name] = value; - } - } else { - ASSERT_TRUE(s.IsNotSupported()); - } - } - ASSERT_TRUE(found_one || names.empty()); - while (found_one && !unused.empty()) { - found_one = false; - for (auto iter = unused.begin(); iter != unused.end();) { - if (copy->ConfigureOption(config_options, iter->first, iter->second) - .ok()) { - found_one = true; - iter = unused.erase(iter); - } else { - ++iter; - } - } - } - ASSERT_EQ(0, unused.size()); - ASSERT_TRUE(base->AreEquivalent(config_options, copy.get(), &mismatch)); -} - -TEST_P(ConfigurableParamTest, GetDefaultOptionsTest) { - TestConfigureOptions(config_options_); -} - -TEST_P(ConfigurableParamTest, ConfigureFromPropsTest) { - std::string opt_str, mismatch; - std::unordered_set names; - std::unique_ptr copy(CreateConfigurable()); - - ASSERT_OK(object_->ConfigureFromString(config_options_, configuration_)); - config_options_.delimiter = "\n"; - ASSERT_OK(object_->GetOptionString(config_options_, &opt_str)); - std::istringstream iss(opt_str); - std::unordered_map copy_map; - std::string line; - for (int line_num = 0; std::getline(iss, line); line_num++) { - std::string name; - std::string value; - ASSERT_OK( - RocksDBOptionsParser::ParseStatement(&name, &value, line, line_num)); - copy_map[name] = value; - } - ASSERT_OK(copy->ConfigureFromMap(config_options_, copy_map)); - ASSERT_TRUE(object_->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -INSTANTIATE_TEST_CASE_P( - ParamTest, ConfigurableParamTest, - testing::Values( - std::pair("Simple", - "int=42;bool=true;string=s"), - std::pair( - "Mutable", "int=42;unique={int=33;string=unique}"), - std::pair( - "Struct", "struct={int=33;bool=true;string=s;}"), - std::pair("Shared", - "int=33;bool=true;string=outer;" - "shared={int=42;string=shared}"), - std::pair("Unique", - "int=33;bool=true;string=outer;" - "unique={int=42;string=unique}"), - std::pair("Nested", - "int=11;bool=true;string=outer;" - "pointer={int=22;string=pointer};" - "unique={int=33;string=unique};" - "shared={int=44;string=shared}"), - std::pair("ThreeDeep", - "int=11;bool=true;string=outer;" - "unique={int=22;string=inner;" - "unique={int=33;string=unique}};"), - std::pair("DBOptions", - "max_background_jobs=100;" - "max_open_files=200;"), - std::pair("CFOptions", - "table_factory=BlockBasedTable;" - "disable_auto_compactions=true;"), - std::pair("BlockBased", - "block_size=1024;" - "no_block_cache=true;"))); - -} // namespace test -} // namespace ROCKSDB_NAMESPACE -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); -#ifdef GFLAGS - ParseCommandLineFlags(&argc, &argv, true); -#endif // GFLAGS - return RUN_ALL_TESTS(); -} diff --git a/options/customizable_test.cc b/options/customizable_test.cc deleted file mode 100644 index d18335410..000000000 --- a/options/customizable_test.cc +++ /dev/null @@ -1,2116 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/customizable.h" - -#include -#include -#include -#include -#include - -#include "db/db_test_util.h" -#include "memory/jemalloc_nodump_allocator.h" -#include "memory/memkind_kmem_allocator.h" -#include "options/options_helper.h" -#include "options/options_parser.h" -#include "port/stack_trace.h" -#include "rocksdb/convenience.h" -#include "rocksdb/env_encryption.h" -#include "rocksdb/file_checksum.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/flush_block_policy.h" -#include "rocksdb/memory_allocator.h" -#include "rocksdb/secondary_cache.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/sst_partitioner.h" -#include "rocksdb/statistics.h" -#include "rocksdb/utilities/customizable_util.h" -#include "rocksdb/utilities/object_registry.h" -#include "rocksdb/utilities/options_type.h" -#include "table/block_based/filter_policy_internal.h" -#include "table/block_based/flush_block_policy.h" -#include "table/mock_table.h" -#include "test_util/mock_time_env.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/file_checksum_helper.h" -#include "util/string_util.h" -#include "utilities/compaction_filters/remove_emptyvalue_compactionfilter.h" -#include "utilities/memory_allocators.h" -#include "utilities/merge_operators/bytesxor.h" -#include "utilities/merge_operators/sortlist.h" -#include "utilities/merge_operators/string_append/stringappend.h" -#include "utilities/merge_operators/string_append/stringappend2.h" - -#ifndef GFLAGS -bool FLAGS_enable_print = false; -#else -#include "util/gflags_compat.h" -using GFLAGS_NAMESPACE::ParseCommandLineFlags; -DEFINE_bool(enable_print, false, "Print options generated to console."); -#endif // GFLAGS - -namespace ROCKSDB_NAMESPACE { -namespace { -class StringLogger : public Logger { - public: - using Logger::Logv; - void Logv(const char* format, va_list ap) override { - char buffer[1000]; - vsnprintf(buffer, sizeof(buffer), format, ap); - string_.append(buffer); - } - const std::string& str() const { return string_; } - void clear() { string_.clear(); } - - private: - std::string string_; -}; - -class TestCustomizable : public Customizable { - public: - TestCustomizable(const std::string& name) : name_(name) {} - // Method to allow CheckedCast to work for this class - static const char* kClassName() { - return "TestCustomizable"; - } - - const char* Name() const override { return name_.c_str(); } - static const char* Type() { return "test.custom"; } - static Status CreateFromString(const ConfigOptions& opts, - const std::string& value, - std::unique_ptr* result); - static Status CreateFromString(const ConfigOptions& opts, - const std::string& value, - std::shared_ptr* result); - static Status CreateFromString(const ConfigOptions& opts, - const std::string& value, - TestCustomizable** result); - bool IsInstanceOf(const std::string& name) const override { - if (name == kClassName()) { - return true; - } else { - return Customizable::IsInstanceOf(name); - } - } - - protected: - const std::string name_; -}; - -struct AOptions { - static const char* kName() { return "A"; } - int i = 0; - bool b = false; -}; - -static std::unordered_map a_option_info = { - {"int", - {offsetof(struct AOptions, i), OptionType::kInt, - OptionVerificationType::kNormal, OptionTypeFlags::kMutable}}, - {"bool", - {offsetof(struct AOptions, b), OptionType::kBoolean, - OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, -}; - -class ACustomizable : public TestCustomizable { - public: - explicit ACustomizable(const std::string& id) - : TestCustomizable("A"), id_(id) { - RegisterOptions(&opts_, &a_option_info); - } - std::string GetId() const override { return id_; } - static const char* kClassName() { return "A"; } - - private: - AOptions opts_; - const std::string id_; -}; - -struct BOptions { - std::string s; - bool b = false; -}; - -static std::unordered_map b_option_info = { - {"string", - {offsetof(struct BOptions, s), OptionType::kString, - OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, - {"bool", - {offsetof(struct BOptions, b), OptionType::kBoolean, - OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, -}; - -class BCustomizable : public TestCustomizable { - private: - public: - explicit BCustomizable(const std::string& name) : TestCustomizable(name) { - RegisterOptions(name, &opts_, &b_option_info); - } - static const char* kClassName() { return "B"; } - - private: - BOptions opts_; -}; - -static int A_count = 0; -static int RegisterCustomTestObjects(ObjectLibrary& library, - const std::string& /*arg*/) { - library.AddFactory( - ObjectLibrary::PatternEntry("A", true).AddSeparator("_"), - [](const std::string& name, std::unique_ptr* guard, - std::string* /* msg */) { - guard->reset(new ACustomizable(name)); - A_count++; - return guard->get(); - }); - library.AddFactory( - "B", [](const std::string& name, std::unique_ptr* guard, - std::string* /* msg */) { - guard->reset(new BCustomizable(name)); - return guard->get(); - }); - - library.AddFactory( - "S", [](const std::string& name, - std::unique_ptr* /* guard */, - std::string* /* msg */) { return new BCustomizable(name); }); - size_t num_types; - return static_cast(library.GetFactoryCount(&num_types)); -} - -struct SimpleOptions { - static const char* kName() { return "simple"; } - bool b = true; - std::unique_ptr cu; - std::shared_ptr cs; - TestCustomizable* cp = nullptr; -}; - -static std::unordered_map simple_option_info = { - {"bool", - {offsetof(struct SimpleOptions, b), OptionType::kBoolean, - OptionVerificationType::kNormal, OptionTypeFlags::kNone}}, - {"unique", - OptionTypeInfo::AsCustomUniquePtr( - offsetof(struct SimpleOptions, cu), OptionVerificationType::kNormal, - OptionTypeFlags::kAllowNull)}, - {"shared", - OptionTypeInfo::AsCustomSharedPtr( - offsetof(struct SimpleOptions, cs), OptionVerificationType::kNormal, - OptionTypeFlags::kAllowNull)}, - {"pointer", - OptionTypeInfo::AsCustomRawPtr( - offsetof(struct SimpleOptions, cp), OptionVerificationType::kNormal, - OptionTypeFlags::kAllowNull)}, -}; - -class SimpleConfigurable : public Configurable { - private: - SimpleOptions simple_; - - public: - SimpleConfigurable() { RegisterOptions(&simple_, &simple_option_info); } - - explicit SimpleConfigurable( - const std::unordered_map* map) { - RegisterOptions(&simple_, map); - } -}; - -static void GetMapFromProperties( - const std::string& props, - std::unordered_map* map) { - std::istringstream iss(props); - std::unordered_map copy_map; - std::string line; - map->clear(); - for (int line_num = 0; std::getline(iss, line); line_num++) { - std::string name; - std::string value; - ASSERT_OK( - RocksDBOptionsParser::ParseStatement(&name, &value, line, line_num)); - (*map)[name] = value; - } -} -} // namespace - -Status TestCustomizable::CreateFromString( - const ConfigOptions& config_options, const std::string& value, - std::shared_ptr* result) { - return LoadSharedObject(config_options, value, result); -} - -Status TestCustomizable::CreateFromString( - const ConfigOptions& config_options, const std::string& value, - std::unique_ptr* result) { - return LoadUniqueObject(config_options, value, result); -} - -Status TestCustomizable::CreateFromString(const ConfigOptions& config_options, - const std::string& value, - TestCustomizable** result) { - return LoadStaticObject(config_options, value, result); -} - -class CustomizableTest : public testing::Test { - public: - CustomizableTest() { - config_options_.invoke_prepare_options = false; - config_options_.registry->AddLibrary("CustomizableTest", - RegisterCustomTestObjects, ""); - } - - ConfigOptions config_options_; -}; - -// Tests that a Customizable can be created by: -// - a simple name -// - a XXX.id option -// - a property with a name -TEST_F(CustomizableTest, CreateByNameTest) { - ObjectLibrary::Default()->AddFactory( - ObjectLibrary::PatternEntry("TEST", false).AddSeparator("_"), - [](const std::string& name, std::unique_ptr* guard, - std::string* /* msg */) { - guard->reset(new TestCustomizable(name)); - return guard->get(); - }); - std::unique_ptr configurable(new SimpleConfigurable()); - SimpleOptions* simple = configurable->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_OK( - configurable->ConfigureFromString(config_options_, "unique={id=TEST_1}")); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), "TEST_1"); - ASSERT_OK( - configurable->ConfigureFromString(config_options_, "unique.id=TEST_2")); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), "TEST_2"); - ASSERT_OK( - configurable->ConfigureFromString(config_options_, "unique=TEST_3")); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), "TEST_3"); -} - -TEST_F(CustomizableTest, ToStringTest) { - std::unique_ptr custom(new TestCustomizable("test")); - ASSERT_EQ(custom->ToString(config_options_), "test"); -} - -TEST_F(CustomizableTest, SimpleConfigureTest) { - std::unordered_map opt_map = { - {"unique", "id=A;int=1;bool=true"}, - {"shared", "id=B;string=s"}, - }; - std::unique_ptr configurable(new SimpleConfigurable()); - ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); - SimpleOptions* simple = configurable->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), "A"); - std::string opt_str; - std::string mismatch; - ASSERT_OK(configurable->GetOptionString(config_options_, &opt_str)); - std::unique_ptr copy(new SimpleConfigurable()); - ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); - ASSERT_TRUE( - configurable->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -TEST_F(CustomizableTest, ConfigureFromPropsTest) { - std::unordered_map opt_map = { - {"unique.id", "A"}, {"unique.A.int", "1"}, {"unique.A.bool", "true"}, - {"shared.id", "B"}, {"shared.B.string", "s"}, - }; - std::unique_ptr configurable(new SimpleConfigurable()); - ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); - SimpleOptions* simple = configurable->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), "A"); - std::string opt_str; - std::string mismatch; - config_options_.delimiter = "\n"; - std::unordered_map props; - ASSERT_OK(configurable->GetOptionString(config_options_, &opt_str)); - GetMapFromProperties(opt_str, &props); - std::unique_ptr copy(new SimpleConfigurable()); - ASSERT_OK(copy->ConfigureFromMap(config_options_, props)); - ASSERT_TRUE( - configurable->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -TEST_F(CustomizableTest, ConfigureFromShortTest) { - std::unordered_map opt_map = { - {"unique.id", "A"}, {"unique.A.int", "1"}, {"unique.A.bool", "true"}, - {"shared.id", "B"}, {"shared.B.string", "s"}, - }; - std::unique_ptr configurable(new SimpleConfigurable()); - ASSERT_OK(configurable->ConfigureFromMap(config_options_, opt_map)); - SimpleOptions* simple = configurable->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), "A"); -} - -TEST_F(CustomizableTest, AreEquivalentOptionsTest) { - std::unordered_map opt_map = { - {"unique", "id=A;int=1;bool=true"}, - {"shared", "id=A;int=1;bool=true"}, - }; - std::string mismatch; - ConfigOptions config_options = config_options_; - std::unique_ptr c1(new SimpleConfigurable()); - std::unique_ptr c2(new SimpleConfigurable()); - ASSERT_OK(c1->ConfigureFromMap(config_options, opt_map)); - ASSERT_OK(c2->ConfigureFromMap(config_options, opt_map)); - ASSERT_TRUE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); - SimpleOptions* simple = c1->GetOptions(); - ASSERT_TRUE( - simple->cu->AreEquivalent(config_options, simple->cs.get(), &mismatch)); - ASSERT_OK(simple->cu->ConfigureOption(config_options, "int", "2")); - ASSERT_FALSE( - simple->cu->AreEquivalent(config_options, simple->cs.get(), &mismatch)); - ASSERT_FALSE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); - ConfigOptions loosely = config_options; - loosely.sanity_level = ConfigOptions::kSanityLevelLooselyCompatible; - ASSERT_TRUE(c1->AreEquivalent(loosely, c2.get(), &mismatch)); - ASSERT_TRUE(simple->cu->AreEquivalent(loosely, simple->cs.get(), &mismatch)); - - ASSERT_OK(c1->ConfigureOption(config_options, "shared", "id=B;string=3")); - ASSERT_TRUE(c1->AreEquivalent(loosely, c2.get(), &mismatch)); - ASSERT_FALSE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); - ASSERT_FALSE(simple->cs->AreEquivalent(loosely, simple->cu.get(), &mismatch)); - simple->cs.reset(); - ASSERT_TRUE(c1->AreEquivalent(loosely, c2.get(), &mismatch)); - ASSERT_FALSE(c1->AreEquivalent(config_options, c2.get(), &mismatch)); -} - -// Tests that we can initialize a customizable from its options -TEST_F(CustomizableTest, ConfigureStandaloneCustomTest) { - std::unique_ptr base, copy; - const auto& registry = config_options_.registry; - ASSERT_OK(registry->NewUniqueObject("A", &base)); - ASSERT_OK(registry->NewUniqueObject("A", ©)); - ASSERT_OK(base->ConfigureFromString(config_options_, "int=33;bool=true")); - std::string opt_str; - std::string mismatch; - ASSERT_OK(base->GetOptionString(config_options_, &opt_str)); - ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); -} - -// Tests that we fail appropriately if the pattern is not registered -TEST_F(CustomizableTest, BadNameTest) { - config_options_.ignore_unsupported_options = false; - std::unique_ptr c1(new SimpleConfigurable()); - ASSERT_NOK( - c1->ConfigureFromString(config_options_, "unique.shared.id=bad name")); - config_options_.ignore_unsupported_options = true; - ASSERT_OK( - c1->ConfigureFromString(config_options_, "unique.shared.id=bad name")); -} - -// Tests that we fail appropriately if a bad option is passed to the underlying -// configurable -TEST_F(CustomizableTest, BadOptionTest) { - std::unique_ptr c1(new SimpleConfigurable()); - ConfigOptions ignore = config_options_; - ignore.ignore_unknown_options = true; - - ASSERT_NOK(c1->ConfigureFromString(config_options_, "A.int=11")); - ASSERT_NOK(c1->ConfigureFromString(config_options_, "shared={id=B;int=1}")); - ASSERT_OK(c1->ConfigureFromString(ignore, "shared={id=A;string=s}")); - ASSERT_NOK(c1->ConfigureFromString(config_options_, "B.int=11")); - ASSERT_OK(c1->ConfigureFromString(ignore, "B.int=11")); - ASSERT_NOK(c1->ConfigureFromString(config_options_, "A.string=s")); - ASSERT_OK(c1->ConfigureFromString(ignore, "A.string=s")); - // Test as detached - ASSERT_NOK( - c1->ConfigureFromString(config_options_, "shared.id=A;A.string=b}")); - ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=A;A.string=s}")); -} - -TEST_F(CustomizableTest, FailingFactoryTest) { - std::shared_ptr registry = ObjectRegistry::NewInstance(); - std::unique_ptr c1(new SimpleConfigurable()); - ConfigOptions ignore = config_options_; - - Status s; - ignore.registry->AddLibrary("failing")->AddFactory( - "failing", - [](const std::string& /*uri*/, - std::unique_ptr* /*guard */, std::string* errmsg) { - *errmsg = "Bad Factory"; - return nullptr; - }); - - // If we are ignoring unknown and unsupported options, will see - // different errors for failing versus missing - ignore.ignore_unknown_options = false; - ignore.ignore_unsupported_options = false; - s = c1->ConfigureFromString(ignore, "shared.id=failing"); - ASSERT_TRUE(s.IsInvalidArgument()); - s = c1->ConfigureFromString(ignore, "unique.id=failing"); - ASSERT_TRUE(s.IsInvalidArgument()); - s = c1->ConfigureFromString(ignore, "shared.id=missing"); - ASSERT_TRUE(s.IsNotSupported()); - s = c1->ConfigureFromString(ignore, "unique.id=missing"); - ASSERT_TRUE(s.IsNotSupported()); - - // If we are ignoring unsupported options, will see - // errors for failing but not missing - ignore.ignore_unknown_options = false; - ignore.ignore_unsupported_options = true; - s = c1->ConfigureFromString(ignore, "shared.id=failing"); - ASSERT_TRUE(s.IsInvalidArgument()); - s = c1->ConfigureFromString(ignore, "unique.id=failing"); - ASSERT_TRUE(s.IsInvalidArgument()); - - ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=missing")); - ASSERT_OK(c1->ConfigureFromString(ignore, "unique.id=missing")); - - // If we are ignoring unknown options, will see no errors - // for failing or missing - ignore.ignore_unknown_options = true; - ignore.ignore_unsupported_options = false; - ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=failing")); - ASSERT_OK(c1->ConfigureFromString(ignore, "unique.id=failing")); - ASSERT_OK(c1->ConfigureFromString(ignore, "shared.id=missing")); - ASSERT_OK(c1->ConfigureFromString(ignore, "unique.id=missing")); -} - -// Tests that different IDs lead to different objects -TEST_F(CustomizableTest, UniqueIdTest) { - std::unique_ptr base(new SimpleConfigurable()); - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=A_1;int=1;bool=true}")); - SimpleOptions* simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(simple->cu->GetId(), std::string("A_1")); - std::string opt_str; - std::string mismatch; - ASSERT_OK(base->GetOptionString(config_options_, &opt_str)); - std::unique_ptr copy(new SimpleConfigurable()); - ASSERT_OK(copy->ConfigureFromString(config_options_, opt_str)); - ASSERT_TRUE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=A_2;int=1;bool=true}")); - ASSERT_FALSE(base->AreEquivalent(config_options_, copy.get(), &mismatch)); - ASSERT_EQ(simple->cu->GetId(), std::string("A_2")); -} - -TEST_F(CustomizableTest, IsInstanceOfTest) { - std::shared_ptr tc = std::make_shared("A_1"); - - ASSERT_EQ(tc->GetId(), std::string("A_1")); - ASSERT_TRUE(tc->IsInstanceOf("A")); - ASSERT_TRUE(tc->IsInstanceOf("TestCustomizable")); - ASSERT_FALSE(tc->IsInstanceOf("B")); - ASSERT_FALSE(tc->IsInstanceOf("A_1")); - ASSERT_EQ(tc->CheckedCast(), tc.get()); - ASSERT_EQ(tc->CheckedCast(), tc.get()); - ASSERT_EQ(tc->CheckedCast(), nullptr); - - tc.reset(new BCustomizable("B")); - ASSERT_TRUE(tc->IsInstanceOf("B")); - ASSERT_TRUE(tc->IsInstanceOf("TestCustomizable")); - ASSERT_FALSE(tc->IsInstanceOf("A")); - ASSERT_EQ(tc->CheckedCast(), tc.get()); - ASSERT_EQ(tc->CheckedCast(), tc.get()); - ASSERT_EQ(tc->CheckedCast(), nullptr); -} - -TEST_F(CustomizableTest, PrepareOptionsTest) { - static std::unordered_map p_option_info = { - {"can_prepare", - {0, OptionType::kBoolean, OptionVerificationType::kNormal, - OptionTypeFlags::kNone}}, - }; - - class PrepareCustomizable : public TestCustomizable { - public: - bool can_prepare_ = true; - - PrepareCustomizable() : TestCustomizable("P") { - RegisterOptions("Prepare", &can_prepare_, &p_option_info); - } - - Status PrepareOptions(const ConfigOptions& opts) override { - if (!can_prepare_) { - return Status::InvalidArgument("Cannot Prepare"); - } else { - return TestCustomizable::PrepareOptions(opts); - } - } - }; - - ObjectLibrary::Default()->AddFactory( - "P", - [](const std::string& /*name*/, std::unique_ptr* guard, - std::string* /* msg */) { - guard->reset(new PrepareCustomizable()); - return guard->get(); - }); - - std::unique_ptr base(new SimpleConfigurable()); - ConfigOptions prepared(config_options_); - prepared.invoke_prepare_options = true; - - ASSERT_OK(base->ConfigureFromString( - prepared, "unique=A_1; shared={id=B;string=s}; pointer.id=S")); - SimpleOptions* simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_NE(simple->cs, nullptr); - ASSERT_NE(simple->cp, nullptr); - delete simple->cp; - base.reset(new SimpleConfigurable()); - ASSERT_OK(base->ConfigureFromString( - config_options_, "unique=A_1; shared={id=B;string=s}; pointer.id=S")); - - simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_NE(simple->cs, nullptr); - ASSERT_NE(simple->cp, nullptr); - - ASSERT_OK(base->PrepareOptions(config_options_)); - delete simple->cp; - base.reset(new SimpleConfigurable()); - simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - - ASSERT_NOK( - base->ConfigureFromString(prepared, "unique={id=P; can_prepare=false}")); - ASSERT_EQ(simple->cu, nullptr); - - ASSERT_OK( - base->ConfigureFromString(prepared, "unique={id=P; can_prepare=true}")); - ASSERT_NE(simple->cu, nullptr); - - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=P; can_prepare=true}")); - ASSERT_NE(simple->cu, nullptr); - ASSERT_OK(simple->cu->PrepareOptions(prepared)); - - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=P; can_prepare=false}")); - ASSERT_NE(simple->cu, nullptr); - ASSERT_NOK(simple->cu->PrepareOptions(prepared)); -} - -namespace { -static std::unordered_map inner_option_info = { - {"inner", - OptionTypeInfo::AsCustomSharedPtr( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kStringNameOnly)} -}; - -struct InnerOptions { - static const char* kName() { return "InnerOptions"; } - std::shared_ptr inner; -}; - -class InnerCustomizable : public Customizable { - public: - explicit InnerCustomizable(const std::shared_ptr& w) { - iopts_.inner = w; - RegisterOptions(&iopts_, &inner_option_info); - } - static const char* kClassName() { return "Inner"; } - const char* Name() const override { return kClassName(); } - - bool IsInstanceOf(const std::string& name) const override { - if (name == kClassName()) { - return true; - } else { - return Customizable::IsInstanceOf(name); - } - } - - protected: - const Customizable* Inner() const override { return iopts_.inner.get(); } - - private: - InnerOptions iopts_; -}; - -struct WrappedOptions1 { - static const char* kName() { return "WrappedOptions1"; } - int i = 42; -}; - -class WrappedCustomizable1 : public InnerCustomizable { - public: - explicit WrappedCustomizable1(const std::shared_ptr& w) - : InnerCustomizable(w) { - RegisterOptions(&wopts_, nullptr); - } - const char* Name() const override { return kClassName(); } - static const char* kClassName() { return "Wrapped1"; } - - private: - WrappedOptions1 wopts_; -}; - -struct WrappedOptions2 { - static const char* kName() { return "WrappedOptions2"; } - std::string s = "42"; -}; -class WrappedCustomizable2 : public InnerCustomizable { - public: - explicit WrappedCustomizable2(const std::shared_ptr& w) - : InnerCustomizable(w) {} - const void* GetOptionsPtr(const std::string& name) const override { - if (name == WrappedOptions2::kName()) { - return &wopts_; - } else { - return InnerCustomizable::GetOptionsPtr(name); - } - } - - const char* Name() const override { return kClassName(); } - static const char* kClassName() { return "Wrapped2"; } - - private: - WrappedOptions2 wopts_; -}; -} // namespace - -TEST_F(CustomizableTest, WrappedInnerTest) { - std::shared_ptr ac = - std::make_shared("A"); - - ASSERT_TRUE(ac->IsInstanceOf("A")); - ASSERT_TRUE(ac->IsInstanceOf("TestCustomizable")); - ASSERT_EQ(ac->CheckedCast(), ac.get()); - ASSERT_EQ(ac->CheckedCast(), nullptr); - ASSERT_EQ(ac->CheckedCast(), nullptr); - ASSERT_EQ(ac->CheckedCast(), nullptr); - std::shared_ptr wc1 = - std::make_shared(ac); - - ASSERT_TRUE(wc1->IsInstanceOf(WrappedCustomizable1::kClassName())); - ASSERT_EQ(wc1->CheckedCast(), wc1.get()); - ASSERT_EQ(wc1->CheckedCast(), nullptr); - ASSERT_EQ(wc1->CheckedCast(), wc1.get()); - ASSERT_EQ(wc1->CheckedCast(), ac.get()); - - std::shared_ptr wc2 = - std::make_shared(wc1); - ASSERT_TRUE(wc2->IsInstanceOf(WrappedCustomizable2::kClassName())); - ASSERT_EQ(wc2->CheckedCast(), wc2.get()); - ASSERT_EQ(wc2->CheckedCast(), wc1.get()); - ASSERT_EQ(wc2->CheckedCast(), wc2.get()); - ASSERT_EQ(wc2->CheckedCast(), ac.get()); -} - -TEST_F(CustomizableTest, CustomizableInnerTest) { - std::shared_ptr c = - std::make_shared(std::make_shared("a")); - std::shared_ptr wc1 = std::make_shared(c); - std::shared_ptr wc2 = std::make_shared(c); - auto inner = c->GetOptions(); - ASSERT_NE(inner, nullptr); - - auto aopts = c->GetOptions(); - ASSERT_NE(aopts, nullptr); - ASSERT_EQ(aopts, wc1->GetOptions()); - ASSERT_EQ(aopts, wc2->GetOptions()); - auto w1opts = wc1->GetOptions(); - ASSERT_NE(w1opts, nullptr); - ASSERT_EQ(c->GetOptions(), nullptr); - ASSERT_EQ(wc2->GetOptions(), nullptr); - - auto w2opts = wc2->GetOptions(); - ASSERT_NE(w2opts, nullptr); - ASSERT_EQ(c->GetOptions(), nullptr); - ASSERT_EQ(wc1->GetOptions(), nullptr); -} - -TEST_F(CustomizableTest, CopyObjectTest) { - class CopyCustomizable : public Customizable { - public: - CopyCustomizable() : prepared_(0), validated_(0) {} - const char* Name() const override { return "CopyCustomizable"; } - - Status PrepareOptions(const ConfigOptions& options) override { - prepared_++; - return Customizable::PrepareOptions(options); - } - Status ValidateOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { - validated_++; - return Customizable::ValidateOptions(db_opts, cf_opts); - } - int prepared_; - mutable int validated_; - }; - - CopyCustomizable c1; - ConfigOptions config_options; - Options options; - - ASSERT_OK(c1.PrepareOptions(config_options)); - ASSERT_OK(c1.ValidateOptions(options, options)); - ASSERT_EQ(c1.prepared_, 1); - ASSERT_EQ(c1.validated_, 1); - CopyCustomizable c2 = c1; - ASSERT_OK(c1.PrepareOptions(config_options)); - ASSERT_OK(c1.ValidateOptions(options, options)); - ASSERT_EQ(c2.prepared_, 1); - ASSERT_EQ(c2.validated_, 1); - ASSERT_EQ(c1.prepared_, 2); - ASSERT_EQ(c1.validated_, 2); -} - -TEST_F(CustomizableTest, TestStringDepth) { - ConfigOptions shallow = config_options_; - std::unique_ptr c( - new InnerCustomizable(std::make_shared("a"))); - std::string opt_str; - shallow.depth = ConfigOptions::Depth::kDepthShallow; - ASSERT_OK(c->GetOptionString(shallow, &opt_str)); - ASSERT_EQ(opt_str, "inner=a;"); - shallow.depth = ConfigOptions::Depth::kDepthDetailed; - ASSERT_OK(c->GetOptionString(shallow, &opt_str)); - ASSERT_NE(opt_str, "inner=a;"); -} - -// Tests that we only get a new customizable when it changes -TEST_F(CustomizableTest, NewUniqueCustomizableTest) { - std::unique_ptr base(new SimpleConfigurable()); - A_count = 0; - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=A_1;int=1;bool=true}")); - SimpleOptions* simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_NE(simple->cu, nullptr); - ASSERT_EQ(A_count, 1); // Created one A - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=A_1;int=1;bool=false}")); - ASSERT_EQ(A_count, 2); // Create another A_1 - ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=}")); - ASSERT_EQ(simple->cu, nullptr); - ASSERT_EQ(A_count, 2); - ASSERT_OK(base->ConfigureFromString(config_options_, - "unique={id=A_2;int=1;bool=false}")); - ASSERT_EQ(A_count, 3); // Created another A - ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=")); - ASSERT_EQ(simple->cu, nullptr); - ASSERT_OK(base->ConfigureFromString(config_options_, "unique=nullptr")); - ASSERT_EQ(simple->cu, nullptr); - ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=nullptr")); - ASSERT_EQ(simple->cu, nullptr); - ASSERT_EQ(A_count, 3); -} - -TEST_F(CustomizableTest, NewEmptyUniqueTest) { - std::unique_ptr base(new SimpleConfigurable()); - SimpleOptions* simple = base->GetOptions(); - ASSERT_EQ(simple->cu, nullptr); - simple->cu.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=}")); - ASSERT_EQ(simple->cu, nullptr); - simple->cu.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "unique={id=nullptr}")); - ASSERT_EQ(simple->cu, nullptr); - simple->cu.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=")); - ASSERT_EQ(simple->cu, nullptr); - simple->cu.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "unique=nullptr")); - ASSERT_EQ(simple->cu, nullptr); - simple->cu.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "unique.id=nullptr")); - ASSERT_EQ(simple->cu, nullptr); -} - -TEST_F(CustomizableTest, NewEmptySharedTest) { - std::unique_ptr base(new SimpleConfigurable()); - - SimpleOptions* simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_EQ(simple->cs, nullptr); - simple->cs.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "shared={id=}")); - ASSERT_NE(simple, nullptr); - ASSERT_EQ(simple->cs, nullptr); - simple->cs.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "shared={id=nullptr}")); - ASSERT_EQ(simple->cs, nullptr); - simple->cs.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "shared.id=")); - ASSERT_EQ(simple->cs, nullptr); - simple->cs.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "shared.id=nullptr")); - ASSERT_EQ(simple->cs, nullptr); - simple->cs.reset(new BCustomizable("B")); - - ASSERT_OK(base->ConfigureFromString(config_options_, "shared=nullptr")); - ASSERT_EQ(simple->cs, nullptr); -} - -TEST_F(CustomizableTest, NewEmptyStaticTest) { - std::unique_ptr base(new SimpleConfigurable()); - ASSERT_OK(base->ConfigureFromString(config_options_, "pointer={id=}")); - SimpleOptions* simple = base->GetOptions(); - ASSERT_NE(simple, nullptr); - ASSERT_EQ(simple->cp, nullptr); - ASSERT_OK(base->ConfigureFromString(config_options_, "pointer={id=nullptr}")); - ASSERT_EQ(simple->cp, nullptr); - - ASSERT_OK(base->ConfigureFromString(config_options_, "pointer=")); - ASSERT_EQ(simple->cp, nullptr); - ASSERT_OK(base->ConfigureFromString(config_options_, "pointer=nullptr")); - ASSERT_EQ(simple->cp, nullptr); - - ASSERT_OK(base->ConfigureFromString(config_options_, "pointer.id=")); - ASSERT_EQ(simple->cp, nullptr); - ASSERT_OK(base->ConfigureFromString(config_options_, "pointer.id=nullptr")); - ASSERT_EQ(simple->cp, nullptr); -} - -namespace { -static std::unordered_map vector_option_info = { - {"vector", - OptionTypeInfo::Vector>( - 0, OptionVerificationType::kNormal, - - OptionTypeFlags::kNone, - - OptionTypeInfo::AsCustomSharedPtr( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone))}, -}; -class VectorConfigurable : public SimpleConfigurable { - public: - VectorConfigurable() { RegisterOptions("vector", &cv, &vector_option_info); } - std::vector> cv; -}; -} // namespace - -TEST_F(CustomizableTest, VectorConfigTest) { - VectorConfigurable orig, copy; - std::shared_ptr c1, c2; - ASSERT_OK(TestCustomizable::CreateFromString(config_options_, "A", &c1)); - ASSERT_OK(TestCustomizable::CreateFromString(config_options_, "B", &c2)); - orig.cv.push_back(c1); - orig.cv.push_back(c2); - ASSERT_OK(orig.ConfigureFromString(config_options_, "unique=A2")); - std::string opt_str, mismatch; - ASSERT_OK(orig.GetOptionString(config_options_, &opt_str)); - ASSERT_OK(copy.ConfigureFromString(config_options_, opt_str)); - ASSERT_TRUE(orig.AreEquivalent(config_options_, ©, &mismatch)); -} - -TEST_F(CustomizableTest, NoNameTest) { - // If Customizables are created without names, they are not - // part of the serialization (since they cannot be recreated) - VectorConfigurable orig, copy; - auto sopts = orig.GetOptions(); - auto copts = copy.GetOptions(); - sopts->cu.reset(new ACustomizable("")); - orig.cv.push_back(std::make_shared("")); - orig.cv.push_back(std::make_shared("A_1")); - std::string opt_str, mismatch; - ASSERT_OK(orig.GetOptionString(config_options_, &opt_str)); - ASSERT_OK(copy.ConfigureFromString(config_options_, opt_str)); - ASSERT_EQ(copy.cv.size(), 1U); - ASSERT_EQ(copy.cv[0]->GetId(), "A_1"); - ASSERT_EQ(copts->cu, nullptr); -} - - -TEST_F(CustomizableTest, IgnoreUnknownObjects) { - ConfigOptions ignore = config_options_; - std::shared_ptr shared; - std::unique_ptr unique; - TestCustomizable* pointer = nullptr; - ignore.ignore_unsupported_options = false; - ASSERT_NOK(LoadSharedObject(ignore, "Unknown", &shared)); - ASSERT_NOK(LoadUniqueObject(ignore, "Unknown", &unique)); - ASSERT_NOK(LoadStaticObject(ignore, "Unknown", &pointer)); - ASSERT_EQ(shared.get(), nullptr); - ASSERT_EQ(unique.get(), nullptr); - ASSERT_EQ(pointer, nullptr); - ignore.ignore_unsupported_options = true; - ASSERT_OK(LoadSharedObject(ignore, "Unknown", &shared)); - ASSERT_OK(LoadUniqueObject(ignore, "Unknown", &unique)); - ASSERT_OK(LoadStaticObject(ignore, "Unknown", &pointer)); - ASSERT_EQ(shared.get(), nullptr); - ASSERT_EQ(unique.get(), nullptr); - ASSERT_EQ(pointer, nullptr); - ASSERT_OK(LoadSharedObject(ignore, "id=Unknown", &shared)); - ASSERT_OK(LoadUniqueObject(ignore, "id=Unknown", &unique)); - ASSERT_OK(LoadStaticObject(ignore, "id=Unknown", &pointer)); - ASSERT_EQ(shared.get(), nullptr); - ASSERT_EQ(unique.get(), nullptr); - ASSERT_EQ(pointer, nullptr); - ASSERT_OK(LoadSharedObject(ignore, "id=Unknown;option=bad", - &shared)); - ASSERT_OK(LoadUniqueObject(ignore, "id=Unknown;option=bad", - &unique)); - ASSERT_OK(LoadStaticObject(ignore, "id=Unknown;option=bad", - &pointer)); - ASSERT_EQ(shared.get(), nullptr); - ASSERT_EQ(unique.get(), nullptr); - ASSERT_EQ(pointer, nullptr); -} - -TEST_F(CustomizableTest, URLFactoryTest) { - std::unique_ptr unique; - config_options_.registry->AddLibrary("URL")->AddFactory( - ObjectLibrary::PatternEntry("Z", false).AddSeparator(""), - [](const std::string& name, std::unique_ptr* guard, - std::string* /* msg */) { - guard->reset(new TestCustomizable(name)); - return guard->get(); - }); - - ConfigOptions ignore = config_options_; - ignore.ignore_unsupported_options = false; - ignore.ignore_unsupported_options = false; - ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z=1;x=y", &unique)); - ASSERT_NE(unique, nullptr); - ASSERT_EQ(unique->GetId(), "Z=1;x=y"); - ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z;x=y", &unique)); - ASSERT_NE(unique, nullptr); - ASSERT_EQ(unique->GetId(), "Z;x=y"); - unique.reset(); - ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z=1?x=y", &unique)); - ASSERT_NE(unique, nullptr); - ASSERT_EQ(unique->GetId(), "Z=1?x=y"); -} - -TEST_F(CustomizableTest, MutableOptionsTest) { - static std::unordered_map mutable_option_info = { - {"mutable", - OptionTypeInfo::AsCustomSharedPtr( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kMutable)}}; - static std::unordered_map immutable_option_info = - {{"immutable", - OptionTypeInfo::AsCustomSharedPtr( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kAllowNull)}}; - - class MutableCustomizable : public Customizable { - private: - std::shared_ptr mutable_; - std::shared_ptr immutable_; - - public: - MutableCustomizable() { - RegisterOptions("mutable", &mutable_, &mutable_option_info); - RegisterOptions("immutable", &immutable_, &immutable_option_info); - } - const char* Name() const override { return "MutableCustomizable"; } - }; - MutableCustomizable mc, mc2; - std::string mismatch; - std::string opt_str; - - ConfigOptions options = config_options_; - ASSERT_OK(mc.ConfigureOption(options, "mutable", "{id=B;}")); - options.mutable_options_only = true; - ASSERT_OK(mc.GetOptionString(options, &opt_str)); - ASSERT_OK(mc2.ConfigureFromString(options, opt_str)); - ASSERT_TRUE(mc.AreEquivalent(options, &mc2, &mismatch)); - - options.mutable_options_only = false; - ASSERT_OK(mc.ConfigureOption(options, "immutable", "{id=A; int=10}")); - auto* mm = mc.GetOptions>("mutable"); - auto* im = mc.GetOptions>("immutable"); - ASSERT_NE(mm, nullptr); - ASSERT_NE(mm->get(), nullptr); - ASSERT_NE(im, nullptr); - ASSERT_NE(im->get(), nullptr); - - // Now only deal with mutable options - options.mutable_options_only = true; - - // Setting nested immutable customizable options fails - ASSERT_NOK(mc.ConfigureOption(options, "immutable", "{id=B;}")); - ASSERT_NOK(mc.ConfigureOption(options, "immutable.id", "B")); - ASSERT_NOK(mc.ConfigureOption(options, "immutable.bool", "true")); - ASSERT_NOK(mc.ConfigureOption(options, "immutable", "bool=true")); - ASSERT_NOK(mc.ConfigureOption(options, "immutable", "{int=11;bool=true}")); - auto* im_a = im->get()->GetOptions("A"); - ASSERT_NE(im_a, nullptr); - ASSERT_EQ(im_a->i, 10); - ASSERT_EQ(im_a->b, false); - - // Setting nested mutable customizable options succeeds but the object did not - // change - ASSERT_OK(mc.ConfigureOption(options, "immutable.int", "11")); - ASSERT_EQ(im_a->i, 11); - ASSERT_EQ(im_a, im->get()->GetOptions("A")); - - // The mutable configurable itself can be changed - ASSERT_OK(mc.ConfigureOption(options, "mutable.id", "A")); - ASSERT_OK(mc.ConfigureOption(options, "mutable", "A")); - ASSERT_OK(mc.ConfigureOption(options, "mutable", "{id=A}")); - ASSERT_OK(mc.ConfigureOption(options, "mutable", "{bool=true}")); - - // The Nested options in the mutable object can be changed - ASSERT_OK(mc.ConfigureOption(options, "mutable", "{bool=true}")); - auto* mm_a = mm->get()->GetOptions("A"); - ASSERT_EQ(mm_a->b, true); - ASSERT_OK(mc.ConfigureOption(options, "mutable", "{int=22;bool=false}")); - mm_a = mm->get()->GetOptions("A"); - ASSERT_EQ(mm_a->i, 22); - ASSERT_EQ(mm_a->b, false); - - // Only the mutable options should get serialized - options.mutable_options_only = false; - ASSERT_OK(mc.GetOptionString(options, &opt_str)); - ASSERT_OK(mc.ConfigureOption(options, "immutable", "{id=B;}")); - options.mutable_options_only = true; - - ASSERT_OK(mc.GetOptionString(options, &opt_str)); - ASSERT_OK(mc2.ConfigureFromString(options, opt_str)); - ASSERT_TRUE(mc.AreEquivalent(options, &mc2, &mismatch)); - options.mutable_options_only = false; - ASSERT_FALSE(mc.AreEquivalent(options, &mc2, &mismatch)); - ASSERT_EQ(mismatch, "immutable"); -} - -TEST_F(CustomizableTest, CustomManagedObjects) { - std::shared_ptr object1, object2; - ASSERT_OK(LoadManagedObject( - config_options_, "id=A_1;int=1;bool=true", &object1)); - ASSERT_NE(object1, nullptr); - ASSERT_OK( - LoadManagedObject(config_options_, "A_1", &object2)); - ASSERT_EQ(object1, object2); - auto* opts = object2->GetOptions("A"); - ASSERT_NE(opts, nullptr); - ASSERT_EQ(opts->i, 1); - ASSERT_EQ(opts->b, true); - ASSERT_OK( - LoadManagedObject(config_options_, "A_2", &object2)); - ASSERT_NE(object1, object2); - object1.reset(); - ASSERT_OK(LoadManagedObject( - config_options_, "id=A_1;int=2;bool=false", &object1)); - opts = object1->GetOptions("A"); - ASSERT_NE(opts, nullptr); - ASSERT_EQ(opts->i, 2); - ASSERT_EQ(opts->b, false); -} - -TEST_F(CustomizableTest, CreateManagedObjects) { - class ManagedCustomizable : public Customizable { - public: - static const char* Type() { return "ManagedCustomizable"; } - static const char* kClassName() { return "Managed"; } - const char* Name() const override { return kClassName(); } - std::string GetId() const override { return id_; } - ManagedCustomizable() { id_ = GenerateIndividualId(); } - static Status CreateFromString( - const ConfigOptions& opts, const std::string& value, - std::shared_ptr* result) { - return LoadManagedObject(opts, value, result); - } - - private: - std::string id_; - }; - - config_options_.registry->AddLibrary("Managed") - ->AddFactory( - ObjectLibrary::PatternEntry::AsIndividualId( - ManagedCustomizable::kClassName()), - [](const std::string& /*name*/, - std::unique_ptr* guard, - std::string* /* msg */) { - guard->reset(new ManagedCustomizable()); - return guard->get(); - }); - - std::shared_ptr mc1, mc2, mc3, obj; - // Create a "deadbeef" customizable - std::string deadbeef = - std::string(ManagedCustomizable::kClassName()) + "@0xdeadbeef#0001"; - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, deadbeef, &mc1)); - // Create an object with the base/class name - ASSERT_OK(ManagedCustomizable::CreateFromString( - config_options_, ManagedCustomizable::kClassName(), &mc2)); - // Creating another with the base name returns a different object - ASSERT_OK(ManagedCustomizable::CreateFromString( - config_options_, ManagedCustomizable::kClassName(), &mc3)); - // At this point, there should be 4 managed objects (deadbeef, mc1, 2, and 3) - std::vector> objects; - ASSERT_OK(config_options_.registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 4U); - objects.clear(); - // Three separate object, none of them equal - ASSERT_NE(mc1, mc2); - ASSERT_NE(mc1, mc3); - ASSERT_NE(mc2, mc3); - - // Creating another object with "deadbeef" object - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, deadbeef, &obj)); - ASSERT_EQ(mc1, obj); - // Create another with the IDs of the instances - ASSERT_OK(ManagedCustomizable::CreateFromString(config_options_, mc1->GetId(), - &obj)); - ASSERT_EQ(mc1, obj); - ASSERT_OK(ManagedCustomizable::CreateFromString(config_options_, mc2->GetId(), - &obj)); - ASSERT_EQ(mc2, obj); - ASSERT_OK(ManagedCustomizable::CreateFromString(config_options_, mc3->GetId(), - &obj)); - ASSERT_EQ(mc3, obj); - - // Now get rid of deadbeef. 2 Objects left (m2+m3) - mc1.reset(); - ASSERT_EQ( - config_options_.registry->GetManagedObject(deadbeef), - nullptr); - ASSERT_OK(config_options_.registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 2U); - objects.clear(); - - // Associate deadbeef with #2 - ASSERT_OK(config_options_.registry->SetManagedObject(deadbeef, mc2)); - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, deadbeef, &obj)); - ASSERT_EQ(mc2, obj); - obj.reset(); - - // Get the ID of mc2 and then reset it. 1 Object left - std::string mc2id = mc2->GetId(); - mc2.reset(); - ASSERT_EQ( - config_options_.registry->GetManagedObject(mc2id), - nullptr); - ASSERT_OK(config_options_.registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 1U); - objects.clear(); - - // Create another object with the old mc2id. - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, mc2id, &mc2)); - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, mc2id, &obj)); - ASSERT_EQ(mc2, obj); - - // For good measure, create another deadbeef object - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, deadbeef, &mc1)); - ASSERT_OK( - ManagedCustomizable::CreateFromString(config_options_, deadbeef, &obj)); - ASSERT_EQ(mc1, obj); -} - - -namespace { -class TestSecondaryCache : public SecondaryCache { - public: - static const char* kClassName() { return "Test"; } - const char* Name() const override { return kClassName(); } - Status Insert(const Slice& /*key*/, Cache::ObjectPtr /*value*/, - const Cache::CacheItemHelper* /*helper*/) override { - return Status::NotSupported(); - } - std::unique_ptr Lookup( - const Slice& /*key*/, const Cache::CacheItemHelper* /*helper*/, - Cache::CreateContext* /*create_context*/, bool /*wait*/, - bool /*advise_erase*/, bool& kept_in_sec_cache) override { - kept_in_sec_cache = true; - return nullptr; - } - - bool SupportForceErase() const override { return false; } - - void Erase(const Slice& /*key*/) override {} - - // Wait for a collection of handles to become ready - void WaitAll(std::vector /*handles*/) override {} - - std::string GetPrintableOptions() const override { return ""; } -}; - -class TestStatistics : public StatisticsImpl { - public: - TestStatistics() : StatisticsImpl(nullptr) {} - const char* Name() const override { return kClassName(); } - static const char* kClassName() { return "Test"; } -}; - -class TestFlushBlockPolicyFactory : public FlushBlockPolicyFactory { - public: - TestFlushBlockPolicyFactory() {} - - static const char* kClassName() { return "TestFlushBlockPolicyFactory"; } - const char* Name() const override { return kClassName(); } - - FlushBlockPolicy* NewFlushBlockPolicy( - const BlockBasedTableOptions& /*table_options*/, - const BlockBuilder& /*data_block_builder*/) const override { - return nullptr; - } -}; - -class MockSliceTransform : public SliceTransform { - public: - const char* Name() const override { return kClassName(); } - static const char* kClassName() { return "Mock"; } - - Slice Transform(const Slice& /*key*/) const override { return Slice(); } - - bool InDomain(const Slice& /*key*/) const override { return false; } - - bool InRange(const Slice& /*key*/) const override { return false; } -}; - -class MockMemoryAllocator : public BaseMemoryAllocator { - public: - static const char* kClassName() { return "MockMemoryAllocator"; } - const char* Name() const override { return kClassName(); } -}; - -class MockEncryptionProvider : public EncryptionProvider { - public: - explicit MockEncryptionProvider(const std::string& id) : id_(id) {} - static const char* kClassName() { return "Mock"; } - const char* Name() const override { return kClassName(); } - size_t GetPrefixLength() const override { return 0; } - Status CreateNewPrefix(const std::string& /*fname*/, char* /*prefix*/, - size_t /*prefixLength*/) const override { - return Status::NotSupported(); - } - - Status AddCipher(const std::string& /*descriptor*/, const char* /*cipher*/, - size_t /*len*/, bool /*for_write*/) override { - return Status::NotSupported(); - } - - Status CreateCipherStream( - const std::string& /*fname*/, const EnvOptions& /*options*/, - Slice& /*prefix*/, - std::unique_ptr* /*result*/) override { - return Status::NotSupported(); - } - Status ValidateOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { - if (EndsWith(id_, "://test")) { - return EncryptionProvider::ValidateOptions(db_opts, cf_opts); - } else { - return Status::InvalidArgument("MockProvider not initialized"); - } - } - - private: - std::string id_; -}; - -class MockCipher : public BlockCipher { - public: - const char* Name() const override { return "Mock"; } - size_t BlockSize() override { return 0; } - Status Encrypt(char* /*data*/) override { return Status::NotSupported(); } - Status Decrypt(char* data) override { return Encrypt(data); } -}; - -class DummyFileSystem : public FileSystemWrapper { - public: - explicit DummyFileSystem(const std::shared_ptr& t) - : FileSystemWrapper(t) {} - static const char* kClassName() { return "DummyFileSystem"; } - const char* Name() const override { return kClassName(); } -}; - - - -class MockTablePropertiesCollectorFactory - : public TablePropertiesCollectorFactory { - private: - public: - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - return nullptr; - } - static const char* kClassName() { return "Mock"; } - const char* Name() const override { return kClassName(); } -}; - -class MockSstPartitionerFactory : public SstPartitionerFactory { - public: - static const char* kClassName() { return "Mock"; } - const char* Name() const override { return kClassName(); } - std::unique_ptr CreatePartitioner( - const SstPartitioner::Context& /* context */) const override { - return nullptr; - } -}; - -class MockFileChecksumGenFactory : public FileChecksumGenFactory { - public: - static const char* kClassName() { return "Mock"; } - const char* Name() const override { return kClassName(); } - std::unique_ptr CreateFileChecksumGenerator( - const FileChecksumGenContext& /*context*/) override { - return nullptr; - } -}; - -class MockFilterPolicy : public FilterPolicy { - public: - static const char* kClassName() { return "MockFilterPolicy"; } - const char* Name() const override { return kClassName(); } - const char* CompatibilityName() const override { return Name(); } - FilterBitsBuilder* GetBuilderWithContext( - const FilterBuildingContext&) const override { - return nullptr; - } - FilterBitsReader* GetFilterBitsReader( - const Slice& /*contents*/) const override { - return nullptr; - } -}; - -static int RegisterLocalObjects(ObjectLibrary& library, - const std::string& /*arg*/) { - size_t num_types; - library.AddFactory( - mock::MockTableFactory::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new mock::MockTableFactory()); - return guard->get(); - }); - library.AddFactory( - OnFileDeletionListener::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new OnFileDeletionListener()); - return guard->get(); - }); - library.AddFactory( - FlushCounterListener::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new FlushCounterListener()); - return guard->get(); - }); - // Load any locally defined objects here - library.AddFactory( - MockSliceTransform::kClassName(), - [](const std::string& /*uri*/, - std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockSliceTransform()); - return guard->get(); - }); - library.AddFactory( - TestStatistics::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new TestStatistics()); - return guard->get(); - }); - - library.AddFactory( - ObjectLibrary::PatternEntry(MockEncryptionProvider::kClassName(), true) - .AddSuffix("://test"), - [](const std::string& uri, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockEncryptionProvider(uri)); - return guard->get(); - }); - library.AddFactory( - "Mock", - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockCipher()); - return guard->get(); - }); - library.AddFactory( - MockMemoryAllocator::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockMemoryAllocator()); - return guard->get(); - }); - library.AddFactory( - TestFlushBlockPolicyFactory::kClassName(), - [](const std::string& /*uri*/, - std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new TestFlushBlockPolicyFactory()); - return guard->get(); - }); - - library.AddFactory( - TestSecondaryCache::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new TestSecondaryCache()); - return guard->get(); - }); - - library.AddFactory( - DummyFileSystem::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new DummyFileSystem(nullptr)); - return guard->get(); - }); - - library.AddFactory( - MockSstPartitionerFactory::kClassName(), - [](const std::string& /*uri*/, - std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockSstPartitionerFactory()); - return guard->get(); - }); - - library.AddFactory( - MockFileChecksumGenFactory::kClassName(), - [](const std::string& /*uri*/, - std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockFileChecksumGenFactory()); - return guard->get(); - }); - - library.AddFactory( - MockTablePropertiesCollectorFactory::kClassName(), - [](const std::string& /*uri*/, - std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockTablePropertiesCollectorFactory()); - return guard->get(); - }); - - library.AddFactory( - MockFilterPolicy::kClassName(), - [](const std::string& /*uri*/, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MockFilterPolicy()); - return guard->get(); - }); - - return static_cast(library.GetFactoryCount(&num_types)); -} -} // namespace - -class LoadCustomizableTest : public testing::Test { - public: - LoadCustomizableTest() { - config_options_.ignore_unsupported_options = false; - config_options_.invoke_prepare_options = false; - } - bool RegisterTests(const std::string& arg) { - config_options_.registry->AddLibrary("custom-tests", - test::RegisterTestObjects, arg); - config_options_.registry->AddLibrary("local-tests", RegisterLocalObjects, - arg); - return true; - } - - template - Status TestCreateStatic(const std::string& name, U** result, - bool delete_result = false) { - Status s = T::CreateFromString(config_options_, name, result); - if (s.ok()) { - EXPECT_NE(*result, nullptr); - EXPECT_TRUE(*result != nullptr && (*result)->IsInstanceOf(name)); - } - if (delete_result) { - delete *result; - *result = nullptr; - } - return s; - } - - template - std::shared_ptr ExpectCreateShared(const std::string& name, - std::shared_ptr* object) { - EXPECT_OK(T::CreateFromString(config_options_, name, object)); - EXPECT_NE(object->get(), nullptr); - EXPECT_TRUE(object->get()->IsInstanceOf(name)); - return *object; - } - - template - std::shared_ptr ExpectCreateShared(const std::string& name) { - std::shared_ptr result; - return ExpectCreateShared(name, &result); - } - - template - Status TestExpectedBuiltins( - const std::string& mock, const std::unordered_set& expected, - std::shared_ptr* object, std::vector* failed, - const std::function(const std::string&)>& alt = - nullptr) { - std::unordered_set factories = expected; - Status s = T::CreateFromString(config_options_, mock, object); - EXPECT_NOK(s); - std::vector builtins; - ObjectLibrary::Default()->GetFactoryNames(T::Type(), &builtins); - factories.insert(builtins.begin(), builtins.end()); - Status result; - int created = 0; - for (const auto& name : factories) { - created++; - s = T::CreateFromString(config_options_, name, object); - if (!s.ok() && alt != nullptr) { - for (const auto& alt_name : alt(name)) { - s = T::CreateFromString(config_options_, alt_name, object); - if (s.ok()) { - break; - } - } - } - if (!s.ok()) { - result = s; - failed->push_back(name); - } else { - EXPECT_NE(object->get(), nullptr); - EXPECT_TRUE(object->get()->IsInstanceOf(name)); - } - } - std::vector plugins; - ObjectRegistry::Default()->GetFactoryNames(T::Type(), &plugins); - if (plugins.size() > builtins.size()) { - for (const auto& name : plugins) { - if (factories.find(name) == factories.end()) { - created++; - s = T::CreateFromString(config_options_, name, object); - if (!s.ok() && alt != nullptr) { - for (const auto& alt_name : alt(name)) { - s = T::CreateFromString(config_options_, alt_name, object); - if (s.ok()) { - break; - } - } - } - if (!s.ok()) { - failed->push_back(name); - if (result.ok()) { - result = s; - } - printf("%s: Failed creating plugin[%s]: %s\n", T::Type(), - name.c_str(), s.ToString().c_str()); - } else if (object->get() == nullptr || - !object->get()->IsInstanceOf(name)) { - failed->push_back(name); - printf("%s: Invalid plugin[%s]\n", T::Type(), name.c_str()); - } - } - } - } - printf("%s: Created %d (expected+builtins+plugins %d+%d+%d) %d Failed\n", - T::Type(), created, (int)expected.size(), - (int)(factories.size() - expected.size()), - (int)(plugins.size() - builtins.size()), (int)failed->size()); - return result; - } - - template - Status TestSharedBuiltins(const std::string& mock, - const std::string& expected, - std::vector* failed = nullptr) { - std::unordered_set values; - if (!expected.empty()) { - values.insert(expected); - } - std::shared_ptr object; - if (failed != nullptr) { - return TestExpectedBuiltins(mock, values, &object, failed); - } else { - std::vector failures; - Status s = TestExpectedBuiltins(mock, values, &object, &failures); - EXPECT_EQ(0U, failures.size()); - return s; - } - } - - template - Status TestStaticBuiltins(const std::string& mock, U** object, - const std::unordered_set& expected, - std::vector* failed, - bool delete_objects = false) { - std::unordered_set factories = expected; - Status s = TestCreateStatic(mock, object, delete_objects); - EXPECT_NOK(s); - std::vector builtins; - ObjectLibrary::Default()->GetFactoryNames(T::Type(), &builtins); - factories.insert(builtins.begin(), builtins.end()); - int created = 0; - Status result; - for (const auto& name : factories) { - created++; - s = TestCreateStatic(name, object, delete_objects); - if (!s.ok()) { - result = s; - failed->push_back(name); - } - } - std::vector plugins; - ObjectRegistry::Default()->GetFactoryNames(T::Type(), &plugins); - if (plugins.size() > builtins.size()) { - for (const auto& name : plugins) { - if (factories.find(name) == factories.end()) { - created++; - s = T::CreateFromString(config_options_, name, object); - if (!s.ok() || *object == nullptr || - !((*object)->IsInstanceOf(name))) { - failed->push_back(name); - if (result.ok() && !s.ok()) { - result = s; - } - printf("%s: Failed creating plugin[%s]: %s\n", T::Type(), - name.c_str(), s.ToString().c_str()); - } - if (delete_objects) { - delete *object; - *object = nullptr; - } - } - } - } - printf("%s: Created %d (expected+builtins+plugins %d+%d+%d) %d Failed\n", - T::Type(), created, (int)expected.size(), - (int)(factories.size() - expected.size()), - (int)(plugins.size() - builtins.size()), (int)failed->size()); - return result; - } - - protected: - DBOptions db_opts_; - ColumnFamilyOptions cf_opts_; - ConfigOptions config_options_; -}; - -TEST_F(LoadCustomizableTest, LoadTableFactoryTest) { - ASSERT_OK( - TestSharedBuiltins(mock::MockTableFactory::kClassName(), - TableFactory::kBlockBasedTableName())); - std::string opts_str = "table_factory="; - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options_, cf_opts_, - opts_str + TableFactory::kBlockBasedTableName(), &cf_opts_)); - ASSERT_NE(cf_opts_.table_factory.get(), nullptr); - ASSERT_STREQ(cf_opts_.table_factory->Name(), - TableFactory::kBlockBasedTableName()); - if (RegisterTests("Test")) { - ExpectCreateShared(mock::MockTableFactory::kClassName()); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options_, cf_opts_, - opts_str + mock::MockTableFactory::kClassName(), &cf_opts_)); - ASSERT_NE(cf_opts_.table_factory.get(), nullptr); - ASSERT_STREQ(cf_opts_.table_factory->Name(), - mock::MockTableFactory::kClassName()); - } -} - -TEST_F(LoadCustomizableTest, LoadFileSystemTest) { - ASSERT_OK(TestSharedBuiltins(DummyFileSystem::kClassName(), - FileSystem::kDefaultName())); - if (RegisterTests("Test")) { - auto fs = ExpectCreateShared(DummyFileSystem::kClassName()); - ASSERT_FALSE(fs->IsInstanceOf(FileSystem::kDefaultName())); - } -} - -TEST_F(LoadCustomizableTest, LoadSecondaryCacheTest) { - ASSERT_OK( - TestSharedBuiltins(TestSecondaryCache::kClassName(), "")); - if (RegisterTests("Test")) { - ExpectCreateShared(TestSecondaryCache::kClassName()); - } -} - -TEST_F(LoadCustomizableTest, LoadSstPartitionerFactoryTest) { - ASSERT_OK(TestSharedBuiltins( - "Mock", SstPartitionerFixedPrefixFactory::kClassName())); - if (RegisterTests("Test")) { - ExpectCreateShared("Mock"); - } -} - -TEST_F(LoadCustomizableTest, LoadChecksumGenFactoryTest) { - ASSERT_OK(TestSharedBuiltins("Mock", "")); - if (RegisterTests("Test")) { - ExpectCreateShared("Mock"); - } -} - -TEST_F(LoadCustomizableTest, LoadTablePropertiesCollectorFactoryTest) { - ASSERT_OK(TestSharedBuiltins( - MockTablePropertiesCollectorFactory::kClassName(), "")); - if (RegisterTests("Test")) { - ExpectCreateShared( - MockTablePropertiesCollectorFactory::kClassName()); - } -} - -TEST_F(LoadCustomizableTest, LoadComparatorTest) { - const Comparator* bytewise = BytewiseComparator(); - const Comparator* reverse = ReverseBytewiseComparator(); - const Comparator* result = nullptr; - std::unordered_set expected = {bytewise->Name(), - reverse->Name()}; - std::vector failures; - ASSERT_OK(TestStaticBuiltins( - test::SimpleSuffixReverseComparator::kClassName(), &result, expected, - &failures)); - if (RegisterTests("Test")) { - ASSERT_OK(TestCreateStatic( - test::SimpleSuffixReverseComparator::kClassName(), &result)); - } -} - -TEST_F(LoadCustomizableTest, LoadSliceTransformFactoryTest) { - std::shared_ptr result; - std::vector failures; - std::unordered_set expected = {"rocksdb.Noop", "fixed", - "rocksdb.FixedPrefix", "capped", - "rocksdb.CappedPrefix"}; - ASSERT_OK(TestExpectedBuiltins( - "Mock", expected, &result, &failures, [](const std::string& name) { - std::vector names = {name + ":22", name + ".22"}; - return names; - })); - ASSERT_OK(SliceTransform::CreateFromString( - config_options_, "rocksdb.FixedPrefix.22", &result)); - ASSERT_NE(result.get(), nullptr); - ASSERT_TRUE(result->IsInstanceOf("fixed")); - ASSERT_OK(SliceTransform::CreateFromString( - config_options_, "rocksdb.CappedPrefix.22", &result)); - ASSERT_NE(result.get(), nullptr); - ASSERT_TRUE(result->IsInstanceOf("capped")); - if (RegisterTests("Test")) { - ExpectCreateShared("Mock", &result); - } -} - -TEST_F(LoadCustomizableTest, LoadStatisticsTest) { - ASSERT_OK(TestSharedBuiltins(TestStatistics::kClassName(), - "BasicStatistics")); - // Empty will create a default BasicStatistics - ASSERT_OK( - Statistics::CreateFromString(config_options_, "", &db_opts_.statistics)); - ASSERT_NE(db_opts_.statistics, nullptr); - ASSERT_STREQ(db_opts_.statistics->Name(), "BasicStatistics"); - - ASSERT_NOK(GetDBOptionsFromString(config_options_, db_opts_, - "statistics=Test", &db_opts_)); - ASSERT_OK(GetDBOptionsFromString(config_options_, db_opts_, - "statistics=BasicStatistics", &db_opts_)); - ASSERT_NE(db_opts_.statistics, nullptr); - ASSERT_STREQ(db_opts_.statistics->Name(), "BasicStatistics"); - - if (RegisterTests("test")) { - auto stats = ExpectCreateShared(TestStatistics::kClassName()); - - ASSERT_OK(GetDBOptionsFromString(config_options_, db_opts_, - "statistics=Test", &db_opts_)); - ASSERT_NE(db_opts_.statistics, nullptr); - ASSERT_STREQ(db_opts_.statistics->Name(), TestStatistics::kClassName()); - - ASSERT_OK(GetDBOptionsFromString( - config_options_, db_opts_, "statistics={id=Test;inner=BasicStatistics}", - &db_opts_)); - ASSERT_NE(db_opts_.statistics, nullptr); - ASSERT_STREQ(db_opts_.statistics->Name(), TestStatistics::kClassName()); - auto* inner = db_opts_.statistics->GetOptions>( - "StatisticsOptions"); - ASSERT_NE(inner, nullptr); - ASSERT_NE(inner->get(), nullptr); - ASSERT_STREQ(inner->get()->Name(), "BasicStatistics"); - - ASSERT_OK(Statistics::CreateFromString( - config_options_, "id=BasicStatistics;inner=Test", &stats)); - ASSERT_NE(stats, nullptr); - ASSERT_STREQ(stats->Name(), "BasicStatistics"); - inner = stats->GetOptions>("StatisticsOptions"); - ASSERT_NE(inner, nullptr); - ASSERT_NE(inner->get(), nullptr); - ASSERT_STREQ(inner->get()->Name(), TestStatistics::kClassName()); - } -} - -TEST_F(LoadCustomizableTest, LoadMemTableRepFactoryTest) { - std::unordered_set expected = { - SkipListFactory::kClassName(), - SkipListFactory::kNickName(), - }; - - std::vector failures; - std::shared_ptr factory; - Status s = TestExpectedBuiltins( - "SpecialSkipListFactory", expected, &factory, &failures); - // There is a "cuckoo" factory registered that we expect to fail. Ignore the - // error if this is the one - if (s.ok() || failures.size() > 1 || failures[0] != "cuckoo") { - ASSERT_OK(s); - } - if (RegisterTests("Test")) { - ExpectCreateShared("SpecialSkipListFactory"); - } -} - -TEST_F(LoadCustomizableTest, LoadMergeOperatorTest) { - std::shared_ptr result; - std::vector failed; - std::unordered_set expected = { - "put", "put_v1", "PutOperator", "uint64add", "UInt64AddOperator", - "max", "MaxOperator", - }; - expected.insert({ - StringAppendOperator::kClassName(), - StringAppendOperator::kNickName(), - StringAppendTESTOperator::kClassName(), - StringAppendTESTOperator::kNickName(), - SortList::kClassName(), - SortList::kNickName(), - BytesXOROperator::kClassName(), - BytesXOROperator::kNickName(), - }); - - ASSERT_OK(TestExpectedBuiltins("Changling", expected, &result, - &failed)); - if (RegisterTests("Test")) { - ExpectCreateShared("Changling"); - } -} - -TEST_F(LoadCustomizableTest, LoadCompactionFilterFactoryTest) { - ASSERT_OK(TestSharedBuiltins("Changling", "")); - if (RegisterTests("Test")) { - ExpectCreateShared("Changling"); - } -} - -TEST_F(LoadCustomizableTest, LoadCompactionFilterTest) { - const CompactionFilter* result = nullptr; - std::vector failures; - ASSERT_OK(TestStaticBuiltins("Changling", &result, {}, - &failures, true)); - if (RegisterTests("Test")) { - ASSERT_OK(TestCreateStatic("Changling", &result, true)); - } -} - -TEST_F(LoadCustomizableTest, LoadEventListenerTest) { - ASSERT_OK(TestSharedBuiltins( - OnFileDeletionListener::kClassName(), "")); - if (RegisterTests("Test")) { - ExpectCreateShared(OnFileDeletionListener::kClassName()); - ExpectCreateShared(FlushCounterListener::kClassName()); - } -} - -TEST_F(LoadCustomizableTest, LoadEncryptionProviderTest) { - std::vector failures; - std::shared_ptr result; - ASSERT_OK( - TestExpectedBuiltins("Mock", {}, &result, &failures)); - if (!failures.empty()) { - ASSERT_EQ(failures[0], "1://test"); - ASSERT_EQ(failures.size(), 1U); - } - - result = ExpectCreateShared("CTR"); - ASSERT_NOK(result->ValidateOptions(db_opts_, cf_opts_)); - ASSERT_OK(EncryptionProvider::CreateFromString(config_options_, "CTR://test", - &result)); - ASSERT_NE(result, nullptr); - ASSERT_STREQ(result->Name(), "CTR"); - ASSERT_OK(result->ValidateOptions(db_opts_, cf_opts_)); - - if (RegisterTests("Test")) { - ExpectCreateShared("Mock"); - ASSERT_OK(EncryptionProvider::CreateFromString(config_options_, - "Mock://test", &result)); - ASSERT_NE(result, nullptr); - ASSERT_STREQ(result->Name(), "Mock"); - ASSERT_OK(result->ValidateOptions(db_opts_, cf_opts_)); - } -} - -TEST_F(LoadCustomizableTest, LoadEncryptionCipherTest) { - ASSERT_OK(TestSharedBuiltins("Mock", "ROT13")); - if (RegisterTests("Test")) { - ExpectCreateShared("Mock"); - } -} - -TEST_F(LoadCustomizableTest, LoadSystemClockTest) { - ASSERT_OK(TestSharedBuiltins(MockSystemClock::kClassName(), - SystemClock::kDefaultName())); - if (RegisterTests("Test")) { - auto result = - ExpectCreateShared(MockSystemClock::kClassName()); - ASSERT_FALSE(result->IsInstanceOf(SystemClock::kDefaultName())); - } -} - -TEST_F(LoadCustomizableTest, LoadMemoryAllocatorTest) { - std::vector failures; - Status s = TestSharedBuiltins( - MockMemoryAllocator::kClassName(), DefaultMemoryAllocator::kClassName(), - &failures); - if (failures.empty()) { - ASSERT_OK(s); - } else { - ASSERT_NOK(s); - for (const auto& failure : failures) { - if (failure == JemallocNodumpAllocator::kClassName()) { - ASSERT_FALSE(JemallocNodumpAllocator::IsSupported()); - } else if (failure == MemkindKmemAllocator::kClassName()) { - ASSERT_FALSE(MemkindKmemAllocator::IsSupported()); - } else { - printf("BYPASSED: %s -- %s\n", failure.c_str(), s.ToString().c_str()); - } - } - } - if (RegisterTests("Test")) { - ExpectCreateShared(MockMemoryAllocator::kClassName()); - } -} - -TEST_F(LoadCustomizableTest, LoadFilterPolicyTest) { - const std::string kAutoBloom = BloomFilterPolicy::kClassName(); - const std::string kAutoRibbon = RibbonFilterPolicy::kClassName(); - - std::shared_ptr result; - std::vector failures; - std::unordered_set expected = { - ReadOnlyBuiltinFilterPolicy::kClassName(), - }; - - expected.insert({ - kAutoBloom, - BloomFilterPolicy::kNickName(), - kAutoRibbon, - RibbonFilterPolicy::kNickName(), - }); - ASSERT_OK(TestExpectedBuiltins( - "Mock", expected, &result, &failures, [](const std::string& name) { - std::vector names = {name + ":1.234"}; - return names; - })); - ASSERT_OK(FilterPolicy::CreateFromString( - config_options_, kAutoBloom + ":1.234:false", &result)); - ASSERT_NE(result.get(), nullptr); - ASSERT_TRUE(result->IsInstanceOf(kAutoBloom)); - ASSERT_OK(FilterPolicy::CreateFromString( - config_options_, kAutoBloom + ":1.234:false", &result)); - ASSERT_NE(result.get(), nullptr); - ASSERT_TRUE(result->IsInstanceOf(kAutoBloom)); - ASSERT_OK(FilterPolicy::CreateFromString(config_options_, - kAutoRibbon + ":1.234:-1", &result)); - ASSERT_NE(result.get(), nullptr); - ASSERT_TRUE(result->IsInstanceOf(kAutoRibbon)); - ASSERT_OK(FilterPolicy::CreateFromString(config_options_, - kAutoRibbon + ":1.234:56", &result)); - ASSERT_NE(result.get(), nullptr); - ASSERT_TRUE(result->IsInstanceOf(kAutoRibbon)); - - if (RegisterTests("Test")) { - ExpectCreateShared(MockFilterPolicy::kClassName(), &result); - } - - std::shared_ptr table; - - std::string table_opts = "id=BlockBasedTable; filter_policy="; - ASSERT_OK(TableFactory::CreateFromString(config_options_, - table_opts + "nullptr", &table)); - ASSERT_NE(table.get(), nullptr); - auto bbto = table->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_EQ(bbto->filter_policy.get(), nullptr); - ASSERT_OK(TableFactory::CreateFromString( - config_options_, table_opts + ReadOnlyBuiltinFilterPolicy::kClassName(), - &table)); - bbto = table->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_NE(bbto->filter_policy.get(), nullptr); - ASSERT_STREQ(bbto->filter_policy->Name(), - ReadOnlyBuiltinFilterPolicy::kClassName()); - ASSERT_OK(TableFactory::CreateFromString( - config_options_, table_opts + MockFilterPolicy::kClassName(), &table)); - bbto = table->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_NE(bbto->filter_policy.get(), nullptr); - ASSERT_TRUE( - bbto->filter_policy->IsInstanceOf(MockFilterPolicy::kClassName())); -} - -TEST_F(LoadCustomizableTest, LoadFlushBlockPolicyFactoryTest) { - std::shared_ptr result; - std::shared_ptr table; - std::vector failed; - std::unordered_set expected = { - FlushBlockBySizePolicyFactory::kClassName(), - FlushBlockEveryKeyPolicyFactory::kClassName(), - }; - - ASSERT_OK(TestExpectedBuiltins( - TestFlushBlockPolicyFactory::kClassName(), expected, &result, &failed)); - - // An empty policy name creates a BySize policy - ASSERT_OK( - FlushBlockPolicyFactory::CreateFromString(config_options_, "", &result)); - ASSERT_NE(result, nullptr); - ASSERT_STREQ(result->Name(), FlushBlockBySizePolicyFactory::kClassName()); - - std::string table_opts = "id=BlockBasedTable; flush_block_policy_factory="; - ASSERT_OK(TableFactory::CreateFromString( - config_options_, - table_opts + FlushBlockEveryKeyPolicyFactory::kClassName(), &table)); - auto bbto = table->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_NE(bbto->flush_block_policy_factory.get(), nullptr); - ASSERT_STREQ(bbto->flush_block_policy_factory->Name(), - FlushBlockEveryKeyPolicyFactory::kClassName()); - if (RegisterTests("Test")) { - ExpectCreateShared( - TestFlushBlockPolicyFactory::kClassName()); - ASSERT_OK(TableFactory::CreateFromString( - config_options_, table_opts + TestFlushBlockPolicyFactory::kClassName(), - &table)); - bbto = table->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_NE(bbto->flush_block_policy_factory.get(), nullptr); - ASSERT_STREQ(bbto->flush_block_policy_factory->Name(), - TestFlushBlockPolicyFactory::kClassName()); - } -} - -} // namespace ROCKSDB_NAMESPACE -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); -#ifdef GFLAGS - ParseCommandLineFlags(&argc, &argv, true); -#endif // GFLAGS - return RUN_ALL_TESTS(); -} diff --git a/options/options_settable_test.cc b/options/options_settable_test.cc deleted file mode 100644 index 020debf01..000000000 --- a/options/options_settable_test.cc +++ /dev/null @@ -1,627 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include "options/cf_options.h" -#include "options/db_options.h" -#include "options/options_helper.h" -#include "rocksdb/convenience.h" -#include "test_util/testharness.h" - -#ifndef GFLAGS -bool FLAGS_enable_print = false; -#else -#include "util/gflags_compat.h" -using GFLAGS_NAMESPACE::ParseCommandLineFlags; -DEFINE_bool(enable_print, false, "Print options generated to console."); -#endif // GFLAGS - -namespace ROCKSDB_NAMESPACE { - -// Verify options are settable from options strings. -// We take the approach that depends on compiler behavior that copy constructor -// won't touch implicit padding bytes, so that the test is fragile. -// As a result, we only run the tests to verify new fields in options are -// settable through string on limited platforms as it depends on behavior of -// compilers. -#if defined OS_LINUX || defined OS_WIN -#ifndef __clang__ -#ifndef ROCKSDB_UBSAN_RUN - -class OptionsSettableTest : public testing::Test { - public: - OptionsSettableTest() {} -}; - -const char kSpecialChar = 'z'; -using OffsetGap = std::vector>; - -void FillWithSpecialChar(char* start_ptr, size_t total_size, - const OffsetGap& excluded, - char special_char = kSpecialChar) { - size_t offset = 0; - // The excluded vector contains pairs of bytes, (first, second). - // The first bytes are all set to the special char (represented as 'c' below). - // The second bytes are simply skipped (padding bytes). - // ccccc[skipped]cccccccc[skiped]cccccccc[skipped] - for (auto& pair : excluded) { - std::memset(start_ptr + offset, special_char, pair.first - offset); - offset = pair.first + pair.second; - } - // The rest of the structure is filled with the special characters. - // ccccc[skipped]cccccccc[skiped]cccccccc[skipped]cccccccccccccccc - std::memset(start_ptr + offset, special_char, total_size - offset); -} - -int NumUnsetBytes(char* start_ptr, size_t total_size, - const OffsetGap& excluded) { - int total_unset_bytes_base = 0; - size_t offset = 0; - for (auto& pair : excluded) { - // The first part of the structure contains memory spaces that can be - // set (pair.first), and memory spaces that cannot be set (pair.second). - // Therefore total_unset_bytes_base only agregates bytes set to kSpecialChar - // in the pair.first bytes, but skips the pair.second bytes (padding bytes). - for (char* ptr = start_ptr + offset; ptr < start_ptr + pair.first; ptr++) { - if (*ptr == kSpecialChar) { - total_unset_bytes_base++; - } - } - offset = pair.first + pair.second; - } - // Then total_unset_bytes_base aggregates the bytes - // set to kSpecialChar in the rest of the structure - for (char* ptr = start_ptr + offset; ptr < start_ptr + total_size; ptr++) { - if (*ptr == kSpecialChar) { - total_unset_bytes_base++; - } - } - return total_unset_bytes_base; -} - -// Return true iff two structs are the same except excluded fields. -bool CompareBytes(char* start_ptr1, char* start_ptr2, size_t total_size, - const OffsetGap& excluded) { - size_t offset = 0; - for (auto& pair : excluded) { - for (; offset < pair.first; offset++) { - if (*(start_ptr1 + offset) != *(start_ptr2 + offset)) { - return false; - } - } - offset = pair.first + pair.second; - } - for (; offset < total_size; offset++) { - if (*(start_ptr1 + offset) != *(start_ptr2 + offset)) { - return false; - } - } - return true; -} - -// If the test fails, likely a new option is added to BlockBasedTableOptions -// but it cannot be set through GetBlockBasedTableOptionsFromString(), or the -// test is not updated accordingly. -// After adding an option, we need to make sure it is settable by -// GetBlockBasedTableOptionsFromString() and add the option to the input string -// passed to the GetBlockBasedTableOptionsFromString() in this test. -// If it is a complicated type, you also need to add the field to -// kBbtoExcluded, and maybe add customized verification for it. -TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) { - // Items in the form of . Need to be in ascending order - // and not overlapping. Need to update if new option to be excluded is added - // (e.g, pointer-type) - const OffsetGap kBbtoExcluded = { - {offsetof(struct BlockBasedTableOptions, flush_block_policy_factory), - sizeof(std::shared_ptr)}, - {offsetof(struct BlockBasedTableOptions, block_cache), - sizeof(std::shared_ptr)}, - {offsetof(struct BlockBasedTableOptions, persistent_cache), - sizeof(std::shared_ptr)}, - {offsetof(struct BlockBasedTableOptions, cache_usage_options), - sizeof(CacheUsageOptions)}, - {offsetof(struct BlockBasedTableOptions, filter_policy), - sizeof(std::shared_ptr)}, - }; - - // In this test, we catch a new option of BlockBasedTableOptions that is not - // settable through GetBlockBasedTableOptionsFromString(). - // We count padding bytes of the option struct, and assert it to be the same - // as unset bytes of an option struct initialized by - // GetBlockBasedTableOptionsFromString(). - - char* bbto_ptr = new char[sizeof(BlockBasedTableOptions)]; - - // Count padding bytes by setting all bytes in the memory to a special char, - // copy a well constructed struct to this memory and see how many special - // bytes left. - BlockBasedTableOptions* bbto = new (bbto_ptr) BlockBasedTableOptions(); - FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded); - // It based on the behavior of compiler that padding bytes are not changed - // when copying the struct. It's prone to failure when compiler behavior - // changes. We verify there is unset bytes to detect the case. - *bbto = BlockBasedTableOptions(); - int unset_bytes_base = - NumUnsetBytes(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded); - ASSERT_GT(unset_bytes_base, 0); - bbto->~BlockBasedTableOptions(); - - // Construct the base option passed into - // GetBlockBasedTableOptionsFromString(). - bbto = new (bbto_ptr) BlockBasedTableOptions(); - FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded); - // This option is not setable: - bbto->use_delta_encoding = true; - - char* new_bbto_ptr = new char[sizeof(BlockBasedTableOptions)]; - BlockBasedTableOptions* new_bbto = - new (new_bbto_ptr) BlockBasedTableOptions(); - FillWithSpecialChar(new_bbto_ptr, sizeof(BlockBasedTableOptions), - kBbtoExcluded); - - // Need to update the option string if a new option is added. - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - config_options.invoke_prepare_options = false; - config_options.ignore_unsupported_options = false; - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, *bbto, - "cache_index_and_filter_blocks=1;" - "cache_index_and_filter_blocks_with_high_priority=true;" - "metadata_cache_options={top_level_index_pinning=kFallback;" - "partition_pinning=kAll;" - "unpartitioned_pinning=kFlushedAndSimilar;};" - "pin_l0_filter_and_index_blocks_in_cache=1;" - "pin_top_level_index_and_filter=1;" - "index_type=kHashSearch;" - "data_block_index_type=kDataBlockBinaryAndHash;" - "index_shortening=kNoShortening;" - "data_block_hash_table_util_ratio=0.75;" - "checksum=kxxHash;no_block_cache=1;" - "block_cache=1M;block_cache_compressed=1k;block_size=1024;" - "block_size_deviation=8;block_restart_interval=4; " - "metadata_block_size=1024;" - "partition_filters=false;" - "optimize_filters_for_memory=true;" - "index_block_restart_interval=4;" - "filter_policy=bloomfilter:4:true;whole_key_filtering=1;detect_filter_" - "construct_corruption=false;" - "format_version=1;" - "verify_compression=true;read_amp_bytes_per_bit=0;" - "enable_index_compression=false;" - "block_align=true;" - "max_auto_readahead_size=0;" - "prepopulate_block_cache=kDisable;" - "initial_auto_readahead_size=0;" - "num_file_reads_for_auto_readahead=0", - new_bbto)); - - ASSERT_EQ(unset_bytes_base, - NumUnsetBytes(new_bbto_ptr, sizeof(BlockBasedTableOptions), - kBbtoExcluded)); - - ASSERT_TRUE(new_bbto->block_cache.get() != nullptr); - ASSERT_TRUE(new_bbto->filter_policy.get() != nullptr); - - bbto->~BlockBasedTableOptions(); - new_bbto->~BlockBasedTableOptions(); - - delete[] bbto_ptr; - delete[] new_bbto_ptr; -} - -// If the test fails, likely a new option is added to DBOptions -// but it cannot be set through GetDBOptionsFromString(), or the test is not -// updated accordingly. -// After adding an option, we need to make sure it is settable by -// GetDBOptionsFromString() and add the option to the input string passed to -// DBOptionsFromString()in this test. -// If it is a complicated type, you also need to add the field to -// kDBOptionsExcluded, and maybe add customized verification for it. -TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) { - const OffsetGap kDBOptionsExcluded = { - {offsetof(struct DBOptions, env), sizeof(Env*)}, - {offsetof(struct DBOptions, rate_limiter), - sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, sst_file_manager), - sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, info_log), sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, statistics), - sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, db_paths), sizeof(std::vector)}, - {offsetof(struct DBOptions, db_log_dir), sizeof(std::string)}, - {offsetof(struct DBOptions, wal_dir), sizeof(std::string)}, - {offsetof(struct DBOptions, write_buffer_manager), - sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, listeners), - sizeof(std::vector>)}, - {offsetof(struct DBOptions, row_cache), sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, wal_filter), sizeof(const WalFilter*)}, - {offsetof(struct DBOptions, file_checksum_gen_factory), - sizeof(std::shared_ptr)}, - {offsetof(struct DBOptions, db_host_id), sizeof(std::string)}, - {offsetof(struct DBOptions, checksum_handoff_file_types), - sizeof(FileTypeSet)}, - {offsetof(struct DBOptions, compaction_service), - sizeof(std::shared_ptr)}, - }; - - char* options_ptr = new char[sizeof(DBOptions)]; - - // Count padding bytes by setting all bytes in the memory to a special char, - // copy a well constructed struct to this memory and see how many special - // bytes left. - DBOptions* options = new (options_ptr) DBOptions(); - FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsExcluded); - // It based on the behavior of compiler that padding bytes are not changed - // when copying the struct. It's prone to failure when compiler behavior - // changes. We verify there is unset bytes to detect the case. - *options = DBOptions(); - int unset_bytes_base = - NumUnsetBytes(options_ptr, sizeof(DBOptions), kDBOptionsExcluded); - ASSERT_GT(unset_bytes_base, 0); - options->~DBOptions(); - - options = new (options_ptr) DBOptions(); - FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsExcluded); - - char* new_options_ptr = new char[sizeof(DBOptions)]; - DBOptions* new_options = new (new_options_ptr) DBOptions(); - FillWithSpecialChar(new_options_ptr, sizeof(DBOptions), kDBOptionsExcluded); - - // Need to update the option string if a new option is added. - ConfigOptions config_options(*options); - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - ASSERT_OK( - GetDBOptionsFromString(config_options, *options, - "wal_bytes_per_sync=4295048118;" - "delete_obsolete_files_period_micros=4294967758;" - "WAL_ttl_seconds=4295008036;" - "WAL_size_limit_MB=4295036161;" - "max_write_batch_group_size_bytes=1048576;" - "wal_dir=path/to/wal_dir;" - "db_write_buffer_size=2587;" - "max_subcompactions=64330;" - "table_cache_numshardbits=28;" - "max_open_files=72;" - "max_file_opening_threads=35;" - "max_background_jobs=8;" - "max_background_compactions=33;" - "use_fsync=true;" - "use_adaptive_mutex=false;" - "max_total_wal_size=4295005604;" - "compaction_readahead_size=0;" - "keep_log_file_num=4890;" - "skip_stats_update_on_db_open=false;" - "skip_checking_sst_file_sizes_on_db_open=false;" - "max_manifest_file_size=4295009941;" - "db_log_dir=path/to/db_log_dir;" - "writable_file_max_buffer_size=1048576;" - "paranoid_checks=true;" - "flush_verify_memtable_count=true;" - "track_and_verify_wals_in_manifest=true;" - "verify_sst_unique_id_in_manifest=true;" - "is_fd_close_on_exec=false;" - "bytes_per_sync=4295013613;" - "strict_bytes_per_sync=true;" - "enable_thread_tracking=false;" - "recycle_log_file_num=0;" - "create_missing_column_families=true;" - "log_file_time_to_roll=3097;" - "max_background_flushes=35;" - "create_if_missing=false;" - "error_if_exists=true;" - "delayed_write_rate=4294976214;" - "manifest_preallocation_size=1222;" - "allow_mmap_writes=false;" - "stats_dump_period_sec=70127;" - "stats_persist_period_sec=54321;" - "persist_stats_to_disk=true;" - "stats_history_buffer_size=14159;" - "allow_fallocate=true;" - "allow_mmap_reads=false;" - "use_direct_reads=false;" - "use_direct_io_for_flush_and_compaction=false;" - "max_log_file_size=4607;" - "random_access_max_buffer_size=1048576;" - "advise_random_on_open=true;" - "fail_if_options_file_error=false;" - "enable_pipelined_write=false;" - "unordered_write=false;" - "allow_concurrent_memtable_write=true;" - "wal_recovery_mode=kPointInTimeRecovery;" - "enable_write_thread_adaptive_yield=true;" - "write_thread_slow_yield_usec=5;" - "write_thread_max_yield_usec=1000;" - "access_hint_on_compaction_start=NONE;" - "info_log_level=DEBUG_LEVEL;" - "dump_malloc_stats=false;" - "allow_2pc=false;" - "avoid_flush_during_recovery=false;" - "avoid_flush_during_shutdown=false;" - "allow_ingest_behind=false;" - "concurrent_prepare=false;" - "two_write_queues=false;" - "manual_wal_flush=false;" - "wal_compression=kZSTD;" - "seq_per_batch=false;" - "atomic_flush=false;" - "avoid_unnecessary_blocking_io=false;" - "log_readahead_size=0;" - "write_dbid_to_manifest=false;" - "best_efforts_recovery=false;" - "max_bgerror_resume_count=2;" - "bgerror_resume_retry_interval=1000000;" - "db_host_id=hostname;" - "lowest_used_cache_tier=kNonVolatileBlockTier;" - "allow_data_in_errors=false;" - "enforce_single_del_contracts=false;", - new_options)); - - ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions), - kDBOptionsExcluded)); - - options->~DBOptions(); - new_options->~DBOptions(); - - delete[] options_ptr; - delete[] new_options_ptr; -} - -// If the test fails, likely a new option is added to ColumnFamilyOptions -// but it cannot be set through GetColumnFamilyOptionsFromString(), or the -// test is not updated accordingly. -// After adding an option, we need to make sure it is settable by -// GetColumnFamilyOptionsFromString() and add the option to the input -// string passed to GetColumnFamilyOptionsFromString() in this test. -// If it is a complicated type, you also need to add the field to -// kColumnFamilyOptionsExcluded, and maybe add customized verification -// for it. -TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) { - // options in the excluded set need to appear in the same order as in - // ColumnFamilyOptions. - const OffsetGap kColumnFamilyOptionsExcluded = { - {offsetof(struct ColumnFamilyOptions, inplace_callback), - sizeof(UpdateStatus(*)(char*, uint32_t*, Slice, std::string*))}, - {offsetof(struct ColumnFamilyOptions, - memtable_insert_with_hint_prefix_extractor), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, compression_per_level), - sizeof(std::vector)}, - {offsetof(struct ColumnFamilyOptions, - max_bytes_for_level_multiplier_additional), - sizeof(std::vector)}, - {offsetof(struct ColumnFamilyOptions, memtable_factory), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, - table_properties_collector_factories), - sizeof(ColumnFamilyOptions::TablePropertiesCollectorFactories)}, - {offsetof(struct ColumnFamilyOptions, preclude_last_level_data_seconds), - sizeof(uint64_t)}, - {offsetof(struct ColumnFamilyOptions, preserve_internal_time_seconds), - sizeof(uint64_t)}, - {offsetof(struct ColumnFamilyOptions, blob_cache), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, comparator), sizeof(Comparator*)}, - {offsetof(struct ColumnFamilyOptions, merge_operator), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, compaction_filter), - sizeof(const CompactionFilter*)}, - {offsetof(struct ColumnFamilyOptions, compaction_filter_factory), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, prefix_extractor), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, snap_refresh_nanos), - sizeof(uint64_t)}, - {offsetof(struct ColumnFamilyOptions, table_factory), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, cf_paths), - sizeof(std::vector)}, - {offsetof(struct ColumnFamilyOptions, compaction_thread_limiter), - sizeof(std::shared_ptr)}, - {offsetof(struct ColumnFamilyOptions, sst_partitioner_factory), - sizeof(std::shared_ptr)}, - }; - - char* options_ptr = new char[sizeof(ColumnFamilyOptions)]; - - // Count padding bytes by setting all bytes in the memory to a special char, - // copy a well constructed struct to this memory and see how many special - // bytes left. - FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions), - kColumnFamilyOptionsExcluded); - - // Invoke a user-defined constructor in the hope that it does not overwrite - // padding bytes. Note that previously we relied on the implicitly-defined - // copy-assignment operator (i.e., `*options = ColumnFamilyOptions();`) here, - // which did in fact modify padding bytes. - ColumnFamilyOptions* options = new (options_ptr) ColumnFamilyOptions(); - - int unset_bytes_base = NumUnsetBytes(options_ptr, sizeof(ColumnFamilyOptions), - kColumnFamilyOptionsExcluded); - ASSERT_GT(unset_bytes_base, 0); - options->~ColumnFamilyOptions(); - - options = new (options_ptr) ColumnFamilyOptions(); - FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions), - kColumnFamilyOptionsExcluded); - - // Following options are not settable through - // GetColumnFamilyOptionsFromString(): - options->compaction_options_universal = CompactionOptionsUniversal(); - options->num_levels = 42; // Initialize options for MutableCF - options->compaction_filter = nullptr; - options->sst_partitioner_factory = nullptr; - - char* new_options_ptr = new char[sizeof(ColumnFamilyOptions)]; - ColumnFamilyOptions* new_options = - new (new_options_ptr) ColumnFamilyOptions(); - FillWithSpecialChar(new_options_ptr, sizeof(ColumnFamilyOptions), - kColumnFamilyOptionsExcluded); - - // Need to update the option string if a new option is added. - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, *options, - "compaction_filter_factory=mpudlojcujCompactionFilterFactory;" - "table_factory=PlainTable;" - "prefix_extractor=rocksdb.CappedPrefix.13;" - "comparator=leveldb.BytewiseComparator;" - "compression_per_level=kBZip2Compression:kBZip2Compression:" - "kBZip2Compression:kNoCompression:kZlibCompression:kBZip2Compression:" - "kSnappyCompression;" - "max_bytes_for_level_base=986;" - "bloom_locality=8016;" - "target_file_size_base=4294976376;" - "memtable_huge_page_size=2557;" - "max_successive_merges=5497;" - "max_sequential_skip_in_iterations=4294971408;" - "arena_block_size=1893;" - "target_file_size_multiplier=35;" - "min_write_buffer_number_to_merge=9;" - "max_write_buffer_number=84;" - "write_buffer_size=1653;" - "max_compaction_bytes=64;" - "ignore_max_compaction_bytes_for_input=true;" - "max_bytes_for_level_multiplier=60;" - "memtable_factory=SkipListFactory;" - "compression=kNoCompression;" - "compression_opts=5:6:7:8:9:10:true:11:false;" - "bottommost_compression_opts=4:5:6:7:8:9:true:10:true;" - "bottommost_compression=kDisableCompressionOption;" - "level0_stop_writes_trigger=33;" - "num_levels=99;" - "level0_slowdown_writes_trigger=22;" - "level0_file_num_compaction_trigger=14;" - "compaction_filter=urxcqstuwnCompactionFilter;" - "soft_pending_compaction_bytes_limit=0;" - "max_write_buffer_number_to_maintain=84;" - "max_write_buffer_size_to_maintain=2147483648;" - "merge_operator=aabcxehazrMergeOperator;" - "memtable_prefix_bloom_size_ratio=0.4642;" - "memtable_whole_key_filtering=true;" - "memtable_insert_with_hint_prefix_extractor=rocksdb.CappedPrefix.13;" - "check_flush_compaction_key_order=false;" - "paranoid_file_checks=true;" - "force_consistency_checks=true;" - "inplace_update_num_locks=7429;" - "experimental_mempurge_threshold=0.0001;" - "optimize_filters_for_hits=false;" - "level_compaction_dynamic_level_bytes=false;" - "level_compaction_dynamic_file_size=true;" - "inplace_update_support=false;" - "compaction_style=kCompactionStyleFIFO;" - "compaction_pri=kMinOverlappingRatio;" - "hard_pending_compaction_bytes_limit=0;" - "disable_auto_compactions=false;" - "report_bg_io_stats=true;" - "ttl=60;" - "periodic_compaction_seconds=3600;" - "sample_for_compression=0;" - "enable_blob_files=true;" - "min_blob_size=256;" - "blob_file_size=1000000;" - "blob_compression_type=kBZip2Compression;" - "enable_blob_garbage_collection=true;" - "blob_garbage_collection_age_cutoff=0.5;" - "blob_garbage_collection_force_threshold=0.75;" - "blob_compaction_readahead_size=262144;" - "blob_file_starting_level=1;" - "prepopulate_blob_cache=kDisable;" - "bottommost_temperature=kWarm;" - "last_level_temperature=kWarm;" - "preclude_last_level_data_seconds=86400;" - "preserve_internal_time_seconds=86400;" - "compaction_options_fifo={max_table_files_size=3;allow_" - "compaction=false;age_for_warm=1;};" - "blob_cache=1M;" - "memtable_protection_bytes_per_key=2;", - new_options)); - - ASSERT_NE(new_options->blob_cache.get(), nullptr); - - ASSERT_EQ(unset_bytes_base, - NumUnsetBytes(new_options_ptr, sizeof(ColumnFamilyOptions), - kColumnFamilyOptionsExcluded)); - - ColumnFamilyOptions rnd_filled_options = *new_options; - - options->~ColumnFamilyOptions(); - new_options->~ColumnFamilyOptions(); - - delete[] options_ptr; - delete[] new_options_ptr; - - // Test copying to mutabable and immutable options and copy back the mutable - // part. - const OffsetGap kMutableCFOptionsExcluded = { - {offsetof(struct MutableCFOptions, prefix_extractor), - sizeof(std::shared_ptr)}, - {offsetof(struct MutableCFOptions, - max_bytes_for_level_multiplier_additional), - sizeof(std::vector)}, - {offsetof(struct MutableCFOptions, compression_per_level), - sizeof(std::vector)}, - {offsetof(struct MutableCFOptions, max_file_size), - sizeof(std::vector)}, - }; - - // For all memory used for options, pre-fill every char. Otherwise, the - // padding bytes might be different so that byte-wise comparison doesn't - // general equal results even if objects are equal. - const char kMySpecialChar = 'x'; - char* mcfo1_ptr = new char[sizeof(MutableCFOptions)]; - FillWithSpecialChar(mcfo1_ptr, sizeof(MutableCFOptions), - kMutableCFOptionsExcluded, kMySpecialChar); - char* mcfo2_ptr = new char[sizeof(MutableCFOptions)]; - FillWithSpecialChar(mcfo2_ptr, sizeof(MutableCFOptions), - kMutableCFOptionsExcluded, kMySpecialChar); - - // A clean column family options is constructed after filling the same special - // char as the initial one. So that the padding bytes are the same. - char* cfo_clean_ptr = new char[sizeof(ColumnFamilyOptions)]; - FillWithSpecialChar(cfo_clean_ptr, sizeof(ColumnFamilyOptions), - kColumnFamilyOptionsExcluded); - rnd_filled_options.num_levels = 66; - ColumnFamilyOptions* cfo_clean = new (cfo_clean_ptr) ColumnFamilyOptions(); - - MutableCFOptions* mcfo1 = - new (mcfo1_ptr) MutableCFOptions(rnd_filled_options); - ColumnFamilyOptions cfo_back = BuildColumnFamilyOptions(*cfo_clean, *mcfo1); - MutableCFOptions* mcfo2 = new (mcfo2_ptr) MutableCFOptions(cfo_back); - - ASSERT_TRUE(CompareBytes(mcfo1_ptr, mcfo2_ptr, sizeof(MutableCFOptions), - kMutableCFOptionsExcluded)); - - cfo_clean->~ColumnFamilyOptions(); - mcfo1->~MutableCFOptions(); - mcfo2->~MutableCFOptions(); - delete[] mcfo1_ptr; - delete[] mcfo2_ptr; - delete[] cfo_clean_ptr; -} -#endif // !ROCKSDB_UBSAN_RUN -#endif // !__clang__ -#endif // OS_LINUX || OS_WIN - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); -#ifdef GFLAGS - ParseCommandLineFlags(&argc, &argv, true); -#endif // GFLAGS - return RUN_ALL_TESTS(); -} diff --git a/options/options_test.cc b/options/options_test.cc deleted file mode 100644 index 481259a9e..000000000 --- a/options/options_test.cc +++ /dev/null @@ -1,4976 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include -#include -#include -#include - -#include "cache/lru_cache.h" -#include "cache/sharded_cache.h" -#include "options/options_helper.h" -#include "options/options_parser.h" -#include "port/port.h" -#include "rocksdb/cache.h" -#include "rocksdb/convenience.h" -#include "rocksdb/file_checksum.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/utilities/leveldb_options.h" -#include "rocksdb/utilities/object_registry.h" -#include "rocksdb/utilities/options_type.h" -#include "table/block_based/filter_policy_internal.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" -#include "util/stderr_logger.h" -#include "util/string_util.h" -#include "utilities/merge_operators/bytesxor.h" -#include "utilities/merge_operators/sortlist.h" -#include "utilities/merge_operators/string_append/stringappend.h" -#include "utilities/merge_operators/string_append/stringappend2.h" - -#ifndef GFLAGS -bool FLAGS_enable_print = false; -#else -#include "util/gflags_compat.h" -using GFLAGS_NAMESPACE::ParseCommandLineFlags; -DEFINE_bool(enable_print, false, "Print options generated to console."); -#endif // GFLAGS - -namespace ROCKSDB_NAMESPACE { - -class OptionsTest : public testing::Test {}; - -class UnregisteredTableFactory : public TableFactory { - public: - UnregisteredTableFactory() {} - const char* Name() const override { return "Unregistered"; } - using TableFactory::NewTableReader; - Status NewTableReader(const ReadOptions&, const TableReaderOptions&, - std::unique_ptr&&, uint64_t, - std::unique_ptr*, bool) const override { - return Status::NotSupported(); - } - TableBuilder* NewTableBuilder(const TableBuilderOptions&, - WritableFileWriter*) const override { - return nullptr; - } -}; - -TEST_F(OptionsTest, GetOptionsFromMapTest) { - std::unordered_map cf_options_map = { - {"write_buffer_size", "1"}, - {"max_write_buffer_number", "2"}, - {"min_write_buffer_number_to_merge", "3"}, - {"max_write_buffer_number_to_maintain", "99"}, - {"max_write_buffer_size_to_maintain", "-99999"}, - {"compression", "kSnappyCompression"}, - {"compression_per_level", - "kNoCompression:" - "kSnappyCompression:" - "kZlibCompression:" - "kBZip2Compression:" - "kLZ4Compression:" - "kLZ4HCCompression:" - "kXpressCompression:" - "kZSTD:" - "kZSTDNotFinalCompression"}, - {"bottommost_compression", "kLZ4Compression"}, - {"bottommost_compression_opts", "5:6:7:8:10:true"}, - {"compression_opts", "4:5:6:7:8:2:true:100:false"}, - {"num_levels", "8"}, - {"level0_file_num_compaction_trigger", "8"}, - {"level0_slowdown_writes_trigger", "9"}, - {"level0_stop_writes_trigger", "10"}, - {"target_file_size_base", "12"}, - {"target_file_size_multiplier", "13"}, - {"max_bytes_for_level_base", "14"}, - {"level_compaction_dynamic_level_bytes", "true"}, - {"max_bytes_for_level_multiplier", "15.0"}, - {"max_bytes_for_level_multiplier_additional", "16:17:18"}, - {"max_compaction_bytes", "21"}, - {"hard_pending_compaction_bytes_limit", "211"}, - {"arena_block_size", "22"}, - {"disable_auto_compactions", "true"}, - {"compaction_style", "kCompactionStyleLevel"}, - {"compaction_pri", "kOldestSmallestSeqFirst"}, - {"verify_checksums_in_compaction", "false"}, - {"compaction_options_fifo", "23"}, - {"max_sequential_skip_in_iterations", "24"}, - {"inplace_update_support", "true"}, - {"report_bg_io_stats", "true"}, - {"compaction_measure_io_stats", "false"}, - {"purge_redundant_kvs_while_flush", "false"}, - {"inplace_update_num_locks", "25"}, - {"memtable_prefix_bloom_size_ratio", "0.26"}, - {"memtable_whole_key_filtering", "true"}, - {"memtable_huge_page_size", "28"}, - {"bloom_locality", "29"}, - {"max_successive_merges", "30"}, - {"min_partial_merge_operands", "31"}, - {"prefix_extractor", "fixed:31"}, - {"experimental_mempurge_threshold", "0.003"}, - {"optimize_filters_for_hits", "true"}, - {"enable_blob_files", "true"}, - {"min_blob_size", "1K"}, - {"blob_file_size", "1G"}, - {"blob_compression_type", "kZSTD"}, - {"enable_blob_garbage_collection", "true"}, - {"blob_garbage_collection_age_cutoff", "0.5"}, - {"blob_garbage_collection_force_threshold", "0.75"}, - {"blob_compaction_readahead_size", "256K"}, - {"blob_file_starting_level", "1"}, - {"prepopulate_blob_cache", "kDisable"}, - {"last_level_temperature", "kWarm"}, - }; - - std::unordered_map db_options_map = { - {"create_if_missing", "false"}, - {"create_missing_column_families", "true"}, - {"error_if_exists", "false"}, - {"paranoid_checks", "true"}, - {"track_and_verify_wals_in_manifest", "true"}, - {"verify_sst_unique_id_in_manifest", "true"}, - {"max_open_files", "32"}, - {"max_total_wal_size", "33"}, - {"use_fsync", "true"}, - {"db_log_dir", "/db_log_dir"}, - {"wal_dir", "/wal_dir"}, - {"delete_obsolete_files_period_micros", "34"}, - {"max_background_compactions", "35"}, - {"max_background_flushes", "36"}, - {"max_log_file_size", "37"}, - {"log_file_time_to_roll", "38"}, - {"keep_log_file_num", "39"}, - {"recycle_log_file_num", "5"}, - {"max_manifest_file_size", "40"}, - {"table_cache_numshardbits", "41"}, - {"WAL_ttl_seconds", "43"}, - {"WAL_size_limit_MB", "44"}, - {"manifest_preallocation_size", "45"}, - {"allow_mmap_reads", "true"}, - {"allow_mmap_writes", "false"}, - {"use_direct_reads", "false"}, - {"use_direct_io_for_flush_and_compaction", "false"}, - {"is_fd_close_on_exec", "true"}, - {"skip_log_error_on_recovery", "false"}, - {"stats_dump_period_sec", "46"}, - {"stats_persist_period_sec", "57"}, - {"persist_stats_to_disk", "false"}, - {"stats_history_buffer_size", "69"}, - {"advise_random_on_open", "true"}, - {"use_adaptive_mutex", "false"}, - {"compaction_readahead_size", "100"}, - {"random_access_max_buffer_size", "3145728"}, - {"writable_file_max_buffer_size", "314159"}, - {"bytes_per_sync", "47"}, - {"wal_bytes_per_sync", "48"}, - {"strict_bytes_per_sync", "true"}, - {"preserve_deletes", "false"}, - }; - - ColumnFamilyOptions base_cf_opt; - ColumnFamilyOptions new_cf_opt; - ConfigOptions exact, loose; - exact.input_strings_escaped = false; - exact.ignore_unknown_options = false; - exact.sanity_level = ConfigOptions::kSanityLevelExactMatch; - loose.sanity_level = ConfigOptions::kSanityLevelLooselyCompatible; - - loose.input_strings_escaped = false; - loose.ignore_unknown_options = true; - ASSERT_OK(GetColumnFamilyOptionsFromMap(exact, base_cf_opt, cf_options_map, - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 1U); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 2); - ASSERT_EQ(new_cf_opt.min_write_buffer_number_to_merge, 3); - ASSERT_EQ(new_cf_opt.max_write_buffer_number_to_maintain, 99); - ASSERT_EQ(new_cf_opt.max_write_buffer_size_to_maintain, -99999); - ASSERT_EQ(new_cf_opt.compression, kSnappyCompression); - ASSERT_EQ(new_cf_opt.compression_per_level.size(), 9U); - ASSERT_EQ(new_cf_opt.compression_per_level[0], kNoCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[1], kSnappyCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[2], kZlibCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[3], kBZip2Compression); - ASSERT_EQ(new_cf_opt.compression_per_level[4], kLZ4Compression); - ASSERT_EQ(new_cf_opt.compression_per_level[5], kLZ4HCCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[6], kXpressCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[7], kZSTD); - ASSERT_EQ(new_cf_opt.compression_per_level[8], kZSTDNotFinalCompression); - ASSERT_EQ(new_cf_opt.compression_opts.window_bits, 4); - ASSERT_EQ(new_cf_opt.compression_opts.level, 5); - ASSERT_EQ(new_cf_opt.compression_opts.strategy, 6); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_bytes, 7u); - ASSERT_EQ(new_cf_opt.compression_opts.zstd_max_train_bytes, 8u); - ASSERT_EQ(new_cf_opt.compression_opts.parallel_threads, 2u); - ASSERT_EQ(new_cf_opt.compression_opts.enabled, true); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_buffer_bytes, 100u); - ASSERT_EQ(new_cf_opt.compression_opts.use_zstd_dict_trainer, false); - ASSERT_EQ(new_cf_opt.bottommost_compression, kLZ4Compression); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.window_bits, 5); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.level, 6); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.strategy, 7); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_bytes, 8u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, 10u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.parallel_threads, - CompressionOptions().parallel_threads); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, true); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, - CompressionOptions().use_zstd_dict_trainer); - ASSERT_EQ(new_cf_opt.num_levels, 8); - ASSERT_EQ(new_cf_opt.level0_file_num_compaction_trigger, 8); - ASSERT_EQ(new_cf_opt.level0_slowdown_writes_trigger, 9); - ASSERT_EQ(new_cf_opt.level0_stop_writes_trigger, 10); - ASSERT_EQ(new_cf_opt.target_file_size_base, static_cast(12)); - ASSERT_EQ(new_cf_opt.target_file_size_multiplier, 13); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_base, 14U); - ASSERT_EQ(new_cf_opt.level_compaction_dynamic_level_bytes, true); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier, 15.0); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional.size(), 3U); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional[0], 16); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional[1], 17); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional[2], 18); - ASSERT_EQ(new_cf_opt.max_compaction_bytes, 21); - ASSERT_EQ(new_cf_opt.hard_pending_compaction_bytes_limit, 211); - ASSERT_EQ(new_cf_opt.arena_block_size, 22U); - ASSERT_EQ(new_cf_opt.disable_auto_compactions, true); - ASSERT_EQ(new_cf_opt.compaction_style, kCompactionStyleLevel); - ASSERT_EQ(new_cf_opt.compaction_pri, kOldestSmallestSeqFirst); - ASSERT_EQ(new_cf_opt.compaction_options_fifo.max_table_files_size, - static_cast(23)); - ASSERT_EQ(new_cf_opt.max_sequential_skip_in_iterations, - static_cast(24)); - ASSERT_EQ(new_cf_opt.inplace_update_support, true); - ASSERT_EQ(new_cf_opt.inplace_update_num_locks, 25U); - ASSERT_EQ(new_cf_opt.memtable_prefix_bloom_size_ratio, 0.26); - ASSERT_EQ(new_cf_opt.memtable_whole_key_filtering, true); - ASSERT_EQ(new_cf_opt.memtable_huge_page_size, 28U); - ASSERT_EQ(new_cf_opt.bloom_locality, 29U); - ASSERT_EQ(new_cf_opt.max_successive_merges, 30U); - ASSERT_TRUE(new_cf_opt.prefix_extractor != nullptr); - ASSERT_EQ(new_cf_opt.optimize_filters_for_hits, true); - ASSERT_EQ(new_cf_opt.prefix_extractor->AsString(), "rocksdb.FixedPrefix.31"); - ASSERT_EQ(new_cf_opt.experimental_mempurge_threshold, 0.003); - ASSERT_EQ(new_cf_opt.enable_blob_files, true); - ASSERT_EQ(new_cf_opt.min_blob_size, 1ULL << 10); - ASSERT_EQ(new_cf_opt.blob_file_size, 1ULL << 30); - ASSERT_EQ(new_cf_opt.blob_compression_type, kZSTD); - ASSERT_EQ(new_cf_opt.enable_blob_garbage_collection, true); - ASSERT_EQ(new_cf_opt.blob_garbage_collection_age_cutoff, 0.5); - ASSERT_EQ(new_cf_opt.blob_garbage_collection_force_threshold, 0.75); - ASSERT_EQ(new_cf_opt.blob_compaction_readahead_size, 262144); - ASSERT_EQ(new_cf_opt.blob_file_starting_level, 1); - ASSERT_EQ(new_cf_opt.prepopulate_blob_cache, PrepopulateBlobCache::kDisable); - ASSERT_EQ(new_cf_opt.last_level_temperature, Temperature::kWarm); - ASSERT_EQ(new_cf_opt.bottommost_temperature, Temperature::kWarm); - - cf_options_map["write_buffer_size"] = "hello"; - ASSERT_NOK(GetColumnFamilyOptionsFromMap(exact, base_cf_opt, cf_options_map, - &new_cf_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - cf_options_map["write_buffer_size"] = "1"; - ASSERT_OK(GetColumnFamilyOptionsFromMap(exact, base_cf_opt, cf_options_map, - &new_cf_opt)); - - cf_options_map["unknown_option"] = "1"; - ASSERT_NOK(GetColumnFamilyOptionsFromMap(exact, base_cf_opt, cf_options_map, - &new_cf_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // ignore_unknown_options=true;input_strings_escaped=false - ASSERT_OK(GetColumnFamilyOptionsFromMap(loose, base_cf_opt, cf_options_map, - &new_cf_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyCFOptions(loose, base_cf_opt, new_cf_opt)); - ASSERT_NOK( - RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - DBOptions base_db_opt; - DBOptions new_db_opt; - ASSERT_OK( - GetDBOptionsFromMap(exact, base_db_opt, db_options_map, &new_db_opt)); - ASSERT_EQ(new_db_opt.create_if_missing, false); - ASSERT_EQ(new_db_opt.create_missing_column_families, true); - ASSERT_EQ(new_db_opt.error_if_exists, false); - ASSERT_EQ(new_db_opt.paranoid_checks, true); - ASSERT_EQ(new_db_opt.track_and_verify_wals_in_manifest, true); - ASSERT_EQ(new_db_opt.verify_sst_unique_id_in_manifest, true); - ASSERT_EQ(new_db_opt.max_open_files, 32); - ASSERT_EQ(new_db_opt.max_total_wal_size, static_cast(33)); - ASSERT_EQ(new_db_opt.use_fsync, true); - ASSERT_EQ(new_db_opt.db_log_dir, "/db_log_dir"); - ASSERT_EQ(new_db_opt.wal_dir, "/wal_dir"); - ASSERT_EQ(new_db_opt.delete_obsolete_files_period_micros, - static_cast(34)); - ASSERT_EQ(new_db_opt.max_background_compactions, 35); - ASSERT_EQ(new_db_opt.max_background_flushes, 36); - ASSERT_EQ(new_db_opt.max_log_file_size, 37U); - ASSERT_EQ(new_db_opt.log_file_time_to_roll, 38U); - ASSERT_EQ(new_db_opt.keep_log_file_num, 39U); - ASSERT_EQ(new_db_opt.recycle_log_file_num, 5U); - ASSERT_EQ(new_db_opt.max_manifest_file_size, static_cast(40)); - ASSERT_EQ(new_db_opt.table_cache_numshardbits, 41); - ASSERT_EQ(new_db_opt.WAL_ttl_seconds, static_cast(43)); - ASSERT_EQ(new_db_opt.WAL_size_limit_MB, static_cast(44)); - ASSERT_EQ(new_db_opt.manifest_preallocation_size, 45U); - ASSERT_EQ(new_db_opt.allow_mmap_reads, true); - ASSERT_EQ(new_db_opt.allow_mmap_writes, false); - ASSERT_EQ(new_db_opt.use_direct_reads, false); - ASSERT_EQ(new_db_opt.use_direct_io_for_flush_and_compaction, false); - ASSERT_EQ(new_db_opt.is_fd_close_on_exec, true); - ASSERT_EQ(new_db_opt.stats_dump_period_sec, 46U); - ASSERT_EQ(new_db_opt.stats_persist_period_sec, 57U); - ASSERT_EQ(new_db_opt.persist_stats_to_disk, false); - ASSERT_EQ(new_db_opt.stats_history_buffer_size, 69U); - ASSERT_EQ(new_db_opt.advise_random_on_open, true); - ASSERT_EQ(new_db_opt.use_adaptive_mutex, false); - ASSERT_EQ(new_db_opt.compaction_readahead_size, 100); - ASSERT_EQ(new_db_opt.random_access_max_buffer_size, 3145728); - ASSERT_EQ(new_db_opt.writable_file_max_buffer_size, 314159); - ASSERT_EQ(new_db_opt.bytes_per_sync, static_cast(47)); - ASSERT_EQ(new_db_opt.wal_bytes_per_sync, static_cast(48)); - ASSERT_EQ(new_db_opt.strict_bytes_per_sync, true); - - db_options_map["max_open_files"] = "hello"; - Status s = - GetDBOptionsFromMap(exact, base_db_opt, db_options_map, &new_db_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(loose, base_db_opt, new_db_opt)); - - // unknow options should fail parsing without ignore_unknown_options = true - db_options_map["unknown_db_option"] = "1"; - s = GetDBOptionsFromMap(exact, base_db_opt, db_options_map, &new_db_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); - - ASSERT_OK( - GetDBOptionsFromMap(loose, base_db_opt, db_options_map, &new_db_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(loose, base_db_opt, new_db_opt)); - ASSERT_NOK( - RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); -} - -TEST_F(OptionsTest, GetColumnFamilyOptionsFromStringTest) { - ColumnFamilyOptions base_cf_opt; - ColumnFamilyOptions new_cf_opt; - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - - base_cf_opt.table_factory.reset(); - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, "", - &new_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=5", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 5U); - ASSERT_TRUE(new_cf_opt.table_factory == nullptr); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=6;", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 6U); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, " write_buffer_size = 7 ", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 7U); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, " write_buffer_size = 8 ; ", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 8U); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=9;max_write_buffer_number=10", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 9U); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 10); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=11; max_write_buffer_number = 12 ;", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 11U); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 12); - // Wrong name "max_write_buffer_number_" - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=13;max_write_buffer_number_=14;", &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - // Comparator from object registry - std::string kCompName = "reverse_comp"; - ObjectLibrary::Default()->AddFactory( - kCompName, - [](const std::string& /*name*/, - std::unique_ptr* /*guard*/, - std::string* /* errmsg */) { return ReverseBytewiseComparator(); }); - - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "comparator=" + kCompName + ";", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.comparator, ReverseBytewiseComparator()); - - // MergeOperator from object registry - std::unique_ptr bxo(new BytesXOROperator()); - std::string kMoName = bxo->Name(); - - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "merge_operator=" + kMoName + ";", - &new_cf_opt)); - ASSERT_EQ(kMoName, std::string(new_cf_opt.merge_operator->Name())); - - // Wrong key/value pair - Status s = GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=13;max_write_buffer_number;", &new_cf_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - // Error Parsing value - s = GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=13;max_write_buffer_number=;", &new_cf_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - // Missing option name - s = GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=13; =100;", &new_cf_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - const uint64_t kilo = 1024UL; - const uint64_t mega = 1024 * kilo; - const uint64_t giga = 1024 * mega; - const uint64_t tera = 1024 * giga; - - // Units (k) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "max_write_buffer_number=15K", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 15 * kilo); - // Units (m) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "max_write_buffer_number=16m;inplace_update_num_locks=17M", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 16 * mega); - ASSERT_EQ(new_cf_opt.inplace_update_num_locks, 17u * mega); - // Units (g) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=18g;prefix_extractor=capped:8;" - "arena_block_size=19G", - &new_cf_opt)); - - ASSERT_EQ(new_cf_opt.write_buffer_size, 18 * giga); - ASSERT_EQ(new_cf_opt.arena_block_size, 19 * giga); - ASSERT_TRUE(new_cf_opt.prefix_extractor.get() != nullptr); - ASSERT_EQ(new_cf_opt.prefix_extractor->AsString(), "rocksdb.CappedPrefix.8"); - - // Units (t) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=20t;arena_block_size=21T", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 20 * tera); - ASSERT_EQ(new_cf_opt.arena_block_size, 21 * tera); - - // Nested block based table options - // Empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={};arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - // Non-empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_cache=1M;block_size=4;};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - // Last one - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_cache=1M;block_size=4;}", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - // Mismatch curly braces - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={{{block_size=4;};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - // Unexpected chars after closing curly brace - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_size=4;}};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_size=4;}xdfa;" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_size=4;}xdfa", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - // Invalid block based table option - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={xx_block_size=4;}", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "optimize_filters_for_hits=true", - &new_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "optimize_filters_for_hits=false", - &new_cf_opt)); - - ASSERT_NOK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "optimize_filters_for_hits=junk", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opt, - new_cf_opt)); - - // Nested plain table options - // Empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "plain_table_factory={};arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - ASSERT_EQ(std::string(new_cf_opt.table_factory->Name()), "PlainTable"); - // Non-empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "plain_table_factory={user_key_len=66;bloom_bits_per_key=20;};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - ASSERT_EQ(std::string(new_cf_opt.table_factory->Name()), "PlainTable"); - - // memtable factory - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "memtable=skip_list:10;arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.memtable_factory != nullptr); - ASSERT_EQ(std::string(new_cf_opt.memtable_factory->Name()), "SkipListFactory"); - ASSERT_TRUE(new_cf_opt.memtable_factory->IsInstanceOf("SkipListFactory")); - - // blob cache - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "blob_cache={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;high_pri_pool_ratio=0.5;};", - &new_cf_opt)); - ASSERT_NE(new_cf_opt.blob_cache, nullptr); - ASSERT_EQ(new_cf_opt.blob_cache->GetCapacity(), 1024UL * 1024UL); - ASSERT_EQ(static_cast(new_cf_opt.blob_cache.get()) - ->GetNumShardBits(), - 4); - ASSERT_EQ(new_cf_opt.blob_cache->HasStrictCapacityLimit(), true); - ASSERT_EQ(static_cast(new_cf_opt.blob_cache.get()) - ->GetHighPriPoolRatio(), - 0.5); -} - -TEST_F(OptionsTest, CompressionOptionsFromString) { - ColumnFamilyOptions base_cf_opt; - ColumnFamilyOptions new_cf_opt; - ConfigOptions config_options; - std::string opts_str; - config_options.ignore_unknown_options = false; - CompressionOptions dflt; - // Test with some optional values removed.... - ASSERT_OK( - GetColumnFamilyOptionsFromString(config_options, ColumnFamilyOptions(), - "compression_opts=3:4:5; " - "bottommost_compression_opts=4:5:6:7", - &base_cf_opt)); - ASSERT_EQ(base_cf_opt.compression_opts.window_bits, 3); - ASSERT_EQ(base_cf_opt.compression_opts.level, 4); - ASSERT_EQ(base_cf_opt.compression_opts.strategy, 5); - ASSERT_EQ(base_cf_opt.compression_opts.max_dict_bytes, dflt.max_dict_bytes); - ASSERT_EQ(base_cf_opt.compression_opts.zstd_max_train_bytes, - dflt.zstd_max_train_bytes); - ASSERT_EQ(base_cf_opt.compression_opts.parallel_threads, - dflt.parallel_threads); - ASSERT_EQ(base_cf_opt.compression_opts.enabled, dflt.enabled); - ASSERT_EQ(base_cf_opt.compression_opts.use_zstd_dict_trainer, - dflt.use_zstd_dict_trainer); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.window_bits, 4); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.level, 5); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.strategy, 6); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.max_dict_bytes, 7u); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, - dflt.zstd_max_train_bytes); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.parallel_threads, - dflt.parallel_threads); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.enabled, dflt.enabled); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, - dflt.use_zstd_dict_trainer); - - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), - "compression_opts=4:5:6:7:8:9:true:10:false; " - "bottommost_compression_opts=5:6:7:8:9:false", - &base_cf_opt)); - ASSERT_EQ(base_cf_opt.compression_opts.window_bits, 4); - ASSERT_EQ(base_cf_opt.compression_opts.level, 5); - ASSERT_EQ(base_cf_opt.compression_opts.strategy, 6); - ASSERT_EQ(base_cf_opt.compression_opts.max_dict_bytes, 7u); - ASSERT_EQ(base_cf_opt.compression_opts.zstd_max_train_bytes, 8u); - ASSERT_EQ(base_cf_opt.compression_opts.parallel_threads, 9u); - ASSERT_EQ(base_cf_opt.compression_opts.enabled, true); - ASSERT_EQ(base_cf_opt.compression_opts.max_dict_buffer_bytes, 10u); - ASSERT_EQ(base_cf_opt.compression_opts.use_zstd_dict_trainer, false); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.window_bits, 5); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.level, 6); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.strategy, 7); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.max_dict_bytes, 8u); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, 9u); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.parallel_threads, - dflt.parallel_threads); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.enabled, false); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, - dflt.use_zstd_dict_trainer); - - ASSERT_OK( - GetStringFromColumnFamilyOptions(config_options, base_cf_opt, &opts_str)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), opts_str, &new_cf_opt)); - ASSERT_EQ(new_cf_opt.compression_opts.window_bits, 4); - ASSERT_EQ(new_cf_opt.compression_opts.level, 5); - ASSERT_EQ(new_cf_opt.compression_opts.strategy, 6); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_bytes, 7u); - ASSERT_EQ(new_cf_opt.compression_opts.zstd_max_train_bytes, 8u); - ASSERT_EQ(new_cf_opt.compression_opts.parallel_threads, 9u); - ASSERT_EQ(new_cf_opt.compression_opts.enabled, true); - ASSERT_EQ(base_cf_opt.compression_opts.max_dict_buffer_bytes, 10u); - ASSERT_EQ(base_cf_opt.compression_opts.use_zstd_dict_trainer, false); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.window_bits, 5); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.level, 6); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.strategy, 7); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_bytes, 8u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, 9u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.parallel_threads, - dflt.parallel_threads); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, false); - ASSERT_EQ(base_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, - dflt.use_zstd_dict_trainer); - - // Test as struct values - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), - "compression_opts={window_bits=5; level=6; strategy=7; max_dict_bytes=8;" - "zstd_max_train_bytes=9;parallel_threads=10;enabled=true;use_zstd_dict_" - "trainer=false}; " - "bottommost_compression_opts={window_bits=4; level=5; strategy=6;" - " max_dict_bytes=7;zstd_max_train_bytes=8;parallel_threads=9;" - "enabled=false;use_zstd_dict_trainer=true}; ", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.compression_opts.window_bits, 5); - ASSERT_EQ(new_cf_opt.compression_opts.level, 6); - ASSERT_EQ(new_cf_opt.compression_opts.strategy, 7); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_bytes, 8u); - ASSERT_EQ(new_cf_opt.compression_opts.zstd_max_train_bytes, 9u); - ASSERT_EQ(new_cf_opt.compression_opts.parallel_threads, 10u); - ASSERT_EQ(new_cf_opt.compression_opts.enabled, true); - ASSERT_EQ(new_cf_opt.compression_opts.use_zstd_dict_trainer, false); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.window_bits, 4); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.level, 5); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.strategy, 6); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_bytes, 7u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, 8u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.parallel_threads, 9u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, false); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, true); - - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "compression_opts={window_bits=4; strategy=5;};" - "bottommost_compression_opts={level=6; strategy=7;}", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.compression_opts.window_bits, 4); - ASSERT_EQ(new_cf_opt.compression_opts.strategy, 5); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.level, 6); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.strategy, 7); - - ASSERT_EQ(new_cf_opt.compression_opts.level, - base_cf_opt.compression_opts.level); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_bytes, - base_cf_opt.compression_opts.max_dict_bytes); - ASSERT_EQ(new_cf_opt.compression_opts.zstd_max_train_bytes, - base_cf_opt.compression_opts.zstd_max_train_bytes); - ASSERT_EQ(new_cf_opt.compression_opts.parallel_threads, - base_cf_opt.compression_opts.parallel_threads); - ASSERT_EQ(new_cf_opt.compression_opts.enabled, - base_cf_opt.compression_opts.enabled); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.window_bits, - base_cf_opt.bottommost_compression_opts.window_bits); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_bytes, - base_cf_opt.bottommost_compression_opts.max_dict_bytes); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, - base_cf_opt.bottommost_compression_opts.zstd_max_train_bytes); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.parallel_threads, - base_cf_opt.bottommost_compression_opts.parallel_threads); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, - base_cf_opt.bottommost_compression_opts.enabled); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, - base_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer); - - // Test a few individual struct values - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "compression_opts.enabled=false; " - "bottommost_compression_opts.enabled=true; ", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.compression_opts.enabled, false); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, true); - - // Now test some illegal values - ConfigOptions ignore; - ignore.ignore_unknown_options = true; - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), - "compression_opts=5:6:7:8:9:x:false", &base_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - ignore, ColumnFamilyOptions(), "compression_opts=5:6:7:8:9:x:false", - &base_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), - "compression_opts=1:2:3:4:5:6:true:8", &base_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - ignore, ColumnFamilyOptions(), "compression_opts=1:2:3:4:5:6:true:8", - &base_cf_opt)); - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), - "compression_opts=1:2:3:4:5:6:true:8:9", &base_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - ignore, ColumnFamilyOptions(), "compression_opts=1:2:3:4:5:6:true:8:9", - &base_cf_opt)); - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), "compression_opts={unknown=bad;}", - &base_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString(ignore, ColumnFamilyOptions(), - "compression_opts={unknown=bad;}", - &base_cf_opt)); - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, ColumnFamilyOptions(), "compression_opts.unknown=bad", - &base_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString(ignore, ColumnFamilyOptions(), - "compression_opts.unknown=bad", - &base_cf_opt)); -} - -TEST_F(OptionsTest, OldInterfaceTest) { - ColumnFamilyOptions base_cf_opt; - ColumnFamilyOptions new_cf_opt; - ConfigOptions exact; - ConfigOptions cf_config_options; - cf_config_options.input_strings_escaped = false; - cf_config_options.ignore_unknown_options = false; - ASSERT_OK(GetColumnFamilyOptionsFromString( - cf_config_options, base_cf_opt, - "write_buffer_size=18;prefix_extractor=capped:8;" - "arena_block_size=19", - &new_cf_opt)); - - ASSERT_EQ(new_cf_opt.write_buffer_size, 18); - ASSERT_EQ(new_cf_opt.arena_block_size, 19); - ASSERT_TRUE(new_cf_opt.prefix_extractor.get() != nullptr); - - // And with a bad option - ASSERT_NOK(GetColumnFamilyOptionsFromString( - cf_config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={xx_block_size=4;}", - &new_cf_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - std::unordered_map cf_options_map = { - {"write_buffer_size", "1"}, - {"max_write_buffer_number", "2"}, - {"min_write_buffer_number_to_merge", "3"}, - }; - ASSERT_OK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - cf_options_map["unknown_option"] = "1"; - ASSERT_NOK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - cf_config_options.input_strings_escaped = true; - cf_config_options.ignore_unknown_options = true; - ASSERT_OK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - - DBOptions base_db_opt; - DBOptions new_db_opt; - std::unordered_map db_options_map = { - {"create_if_missing", "false"}, - {"create_missing_column_families", "true"}, - {"error_if_exists", "false"}, - {"paranoid_checks", "true"}, - {"track_and_verify_wals_in_manifest", "true"}, - {"verify_sst_unique_id_in_manifest", "true"}, - {"max_open_files", "32"}, - }; - - ConfigOptions db_config_options(base_db_opt); - db_config_options.input_strings_escaped = false; - db_config_options.ignore_unknown_options = false; - ASSERT_OK(GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt)); - ASSERT_EQ(new_db_opt.create_if_missing, false); - ASSERT_EQ(new_db_opt.create_missing_column_families, true); - ASSERT_EQ(new_db_opt.error_if_exists, false); - ASSERT_EQ(new_db_opt.paranoid_checks, true); - ASSERT_EQ(new_db_opt.track_and_verify_wals_in_manifest, true); - ASSERT_EQ(new_db_opt.verify_sst_unique_id_in_manifest, true); - ASSERT_EQ(new_db_opt.max_open_files, 32); - db_options_map["unknown_option"] = "1"; - Status s = GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); - db_config_options.input_strings_escaped = true; - db_config_options.ignore_unknown_options = true; - ASSERT_OK(GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt)); - db_config_options.input_strings_escaped = false; - db_config_options.ignore_unknown_options = false; - ASSERT_OK(GetDBOptionsFromString( - db_config_options, base_db_opt, - "create_if_missing=false;error_if_exists=false;max_open_files=42;", - &new_db_opt)); - ASSERT_EQ(new_db_opt.create_if_missing, false); - ASSERT_EQ(new_db_opt.error_if_exists, false); - ASSERT_EQ(new_db_opt.max_open_files, 42); - s = GetDBOptionsFromString( - db_config_options, base_db_opt, - "create_if_missing=false;error_if_exists=false;max_open_files=42;" - "unknown_option=1;", - &new_db_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); -} - - -TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) { - BlockBasedTableOptions table_opt; - BlockBasedTableOptions new_opt; - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - config_options.ignore_unsupported_options = false; - - // make sure default values are overwritten by something else - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;index_type=kHashSearch;" - "checksum=kxxHash;" - "block_cache=1M;block_cache_compressed=1k;block_size=1024;" - "block_size_deviation=8;block_restart_interval=4;" - "format_version=5;whole_key_filtering=1;" - "filter_policy=bloomfilter:4.567:false;detect_filter_construct_" - "corruption=true;" - // A bug caused read_amp_bytes_per_bit to be a large integer in OPTIONS - // file generated by 6.10 to 6.14. Though bug is fixed in these releases, - // we need to handle the case of loading OPTIONS file generated before the - // fix. - "read_amp_bytes_per_bit=17179869185;", - &new_opt)); - ASSERT_TRUE(new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(new_opt.index_type, BlockBasedTableOptions::kHashSearch); - ASSERT_EQ(new_opt.checksum, ChecksumType::kxxHash); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); - ASSERT_EQ(new_opt.block_size, 1024UL); - ASSERT_EQ(new_opt.block_size_deviation, 8); - ASSERT_EQ(new_opt.block_restart_interval, 4); - ASSERT_EQ(new_opt.format_version, 5U); - ASSERT_EQ(new_opt.whole_key_filtering, true); - ASSERT_EQ(new_opt.detect_filter_construct_corruption, true); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - auto bfp = new_opt.filter_policy->CheckedCast(); - ASSERT_NE(bfp, nullptr); - EXPECT_EQ(bfp->GetMillibitsPerKey(), 4567); - EXPECT_EQ(bfp->GetWholeBitsPerKey(), 5); - // Verify that only the lower 32bits are stored in - // new_opt.read_amp_bytes_per_bit. - EXPECT_EQ(1U, new_opt.read_amp_bytes_per_bit); - - // unknown option - Status s = GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;index_type=kBinarySearch;" - "bad_option=1", - &new_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_EQ(static_cast(table_opt.cache_index_and_filter_blocks), - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.index_type, new_opt.index_type); - - // unrecognized index type - s = GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;index_type=kBinarySearchXX", &new_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_EQ(table_opt.cache_index_and_filter_blocks, - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.index_type, new_opt.index_type); - - // unrecognized checksum type - ASSERT_NOK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;checksum=kxxHashXX", &new_opt)); - ASSERT_EQ(table_opt.cache_index_and_filter_blocks, - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.index_type, new_opt.index_type); - - // unrecognized filter policy name - s = GetBlockBasedTableOptionsFromString(config_options, table_opt, - "filter_policy=bloomfilterxx:4:true", - &new_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - // missing bits per key - s = GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=bloomfilter", &new_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - // Used to be rejected, now accepted - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=bloomfilter:4", &new_opt)); - bfp = dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(bfp->GetMillibitsPerKey(), 4000); - EXPECT_EQ(bfp->GetWholeBitsPerKey(), 4); - - // use_block_based_builder=true now ignored in public API (same as false) - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=bloomfilter:4:true", &new_opt)); - bfp = dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(bfp->GetMillibitsPerKey(), 4000); - EXPECT_EQ(bfp->GetWholeBitsPerKey(), 4); - - // Test configuring using other internal names - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "filter_policy=rocksdb.internal.LegacyBloomFilter:3", &new_opt)); - auto builtin = - dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(builtin->GetId(), "rocksdb.internal.LegacyBloomFilter:3"); - - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "filter_policy=rocksdb.internal.FastLocalBloomFilter:1.234", &new_opt)); - builtin = - dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(builtin->GetId(), "rocksdb.internal.FastLocalBloomFilter:1.234"); - - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "filter_policy=rocksdb.internal.Standard128RibbonFilter:1.234", - &new_opt)); - builtin = - dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(builtin->GetId(), "rocksdb.internal.Standard128RibbonFilter:1.234"); - - // Ribbon filter policy (no Bloom hybrid) - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=ribbonfilter:5.678:-1;", - &new_opt)); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - auto rfp = - dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(rfp->GetMillibitsPerKey(), 5678); - EXPECT_EQ(rfp->GetBloomBeforeLevel(), -1); - - // Ribbon filter policy (default Bloom hybrid) - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=ribbonfilter:6.789;", - &new_opt)); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - rfp = dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(rfp->GetMillibitsPerKey(), 6789); - EXPECT_EQ(rfp->GetBloomBeforeLevel(), 0); - - // Ribbon filter policy (custom Bloom hybrid) - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=ribbonfilter:6.789:5;", - &new_opt)); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - rfp = dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(rfp->GetMillibitsPerKey(), 6789); - EXPECT_EQ(rfp->GetBloomBeforeLevel(), 5); - - // Check block cache options are overwritten when specified - // in new format as a struct. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;high_pri_pool_ratio=0.5;};" - "block_cache_compressed={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;high_pri_pool_ratio=0.5;}", - &new_opt)); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - 4); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true); - ASSERT_EQ(std::dynamic_pointer_cast( - new_opt.block_cache)->GetHighPriPoolRatio(), 0.5); - - // Set only block cache capacity. Check other values are - // reset to default values. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={capacity=2M};" - "block_cache_compressed={capacity=2M}", - &new_opt)); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 2*1024UL*1024UL); - // Default values - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - GetDefaultCacheShardBits(new_opt.block_cache->GetCapacity())); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetHighPriPoolRatio(), - 0.5); - - // Set couple of block cache options. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={num_shard_bits=5;high_pri_pool_ratio=0.5;};" - "block_cache_compressed={num_shard_bits=5;" - "high_pri_pool_ratio=0.0;}", - &new_opt)); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 0); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - 5); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false); - ASSERT_EQ(std::dynamic_pointer_cast( - new_opt.block_cache)->GetHighPriPoolRatio(), 0.5); - - // Set couple of block cache options. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;};" - "block_cache_compressed={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;}", - &new_opt)); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - 4); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetHighPriPoolRatio(), - 0.5); - - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=rocksdb.BloomFilter:1.234", - &new_opt)); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - ASSERT_TRUE( - new_opt.filter_policy->IsInstanceOf(BloomFilterPolicy::kClassName())); - ASSERT_TRUE( - new_opt.filter_policy->IsInstanceOf(BloomFilterPolicy::kNickName())); - - // Ribbon filter policy alternative name - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=rocksdb.RibbonFilter:6.789:5;", - &new_opt)); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - ASSERT_TRUE( - new_opt.filter_policy->IsInstanceOf(RibbonFilterPolicy::kClassName())); - ASSERT_TRUE( - new_opt.filter_policy->IsInstanceOf(RibbonFilterPolicy::kNickName())); -} - - -TEST_F(OptionsTest, GetPlainTableOptionsFromString) { - PlainTableOptions table_opt; - PlainTableOptions new_opt; - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - // make sure default values are overwritten by something else - ASSERT_OK(GetPlainTableOptionsFromString( - config_options, table_opt, - "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;" - "index_sparseness=8;huge_page_tlb_size=4;encoding_type=kPrefix;" - "full_scan_mode=true;store_index_in_file=true", - &new_opt)); - ASSERT_EQ(new_opt.user_key_len, 66u); - ASSERT_EQ(new_opt.bloom_bits_per_key, 20); - ASSERT_EQ(new_opt.hash_table_ratio, 0.5); - ASSERT_EQ(new_opt.index_sparseness, 8); - ASSERT_EQ(new_opt.huge_page_tlb_size, 4); - ASSERT_EQ(new_opt.encoding_type, EncodingType::kPrefix); - ASSERT_TRUE(new_opt.full_scan_mode); - ASSERT_TRUE(new_opt.store_index_in_file); - - // unknown option - Status s = GetPlainTableOptionsFromString( - config_options, table_opt, - "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;" - "bad_option=1", - &new_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - // unrecognized EncodingType - s = GetPlainTableOptionsFromString( - config_options, table_opt, - "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;" - "encoding_type=kPrefixXX", - &new_opt); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); -} - -TEST_F(OptionsTest, GetMemTableRepFactoryFromString) { - std::unique_ptr new_mem_factory = nullptr; - - ASSERT_OK(GetMemTableRepFactoryFromString("skip_list", &new_mem_factory)); - ASSERT_OK(GetMemTableRepFactoryFromString("skip_list:16", &new_mem_factory)); - ASSERT_STREQ(new_mem_factory->Name(), "SkipListFactory"); - ASSERT_NOK(GetMemTableRepFactoryFromString("skip_list:16:invalid_opt", - &new_mem_factory)); - - ASSERT_OK(GetMemTableRepFactoryFromString("prefix_hash", &new_mem_factory)); - ASSERT_OK(GetMemTableRepFactoryFromString("prefix_hash:1000", - &new_mem_factory)); - ASSERT_STREQ(new_mem_factory->Name(), "HashSkipListRepFactory"); - ASSERT_NOK(GetMemTableRepFactoryFromString("prefix_hash:1000:invalid_opt", - &new_mem_factory)); - - ASSERT_OK(GetMemTableRepFactoryFromString("hash_linkedlist", - &new_mem_factory)); - ASSERT_OK(GetMemTableRepFactoryFromString("hash_linkedlist:1000", - &new_mem_factory)); - ASSERT_EQ(std::string(new_mem_factory->Name()), "HashLinkListRepFactory"); - ASSERT_NOK(GetMemTableRepFactoryFromString("hash_linkedlist:1000:invalid_opt", - &new_mem_factory)); - - ASSERT_OK(GetMemTableRepFactoryFromString("vector", &new_mem_factory)); - ASSERT_OK(GetMemTableRepFactoryFromString("vector:1024", &new_mem_factory)); - ASSERT_EQ(std::string(new_mem_factory->Name()), "VectorRepFactory"); - ASSERT_NOK(GetMemTableRepFactoryFromString("vector:1024:invalid_opt", - &new_mem_factory)); - - ASSERT_NOK(GetMemTableRepFactoryFromString("cuckoo", &new_mem_factory)); - // CuckooHash memtable is already removed. - ASSERT_NOK(GetMemTableRepFactoryFromString("cuckoo:1024", &new_mem_factory)); - - ASSERT_NOK(GetMemTableRepFactoryFromString("bad_factory", &new_mem_factory)); -} - -TEST_F(OptionsTest, MemTableRepFactoryCreateFromString) { - std::unique_ptr new_mem_factory = nullptr; - ConfigOptions config_options; - config_options.ignore_unsupported_options = false; - config_options.ignore_unknown_options = false; - - ASSERT_OK(MemTableRepFactory::CreateFromString(config_options, "skip_list", - &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString(config_options, "skip_list:16", - &new_mem_factory)); - ASSERT_STREQ(new_mem_factory->Name(), "SkipListFactory"); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("skip_list")); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("SkipListFactory")); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "skip_list:16:invalid_opt", &new_mem_factory)); - - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "invalid_opt=10", &new_mem_factory)); - - // Test a reset - ASSERT_OK(MemTableRepFactory::CreateFromString(config_options, "", - &new_mem_factory)); - ASSERT_EQ(new_mem_factory, nullptr); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "invalid_opt=10", &new_mem_factory)); - - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, "id=skip_list; lookahead=32", &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString(config_options, "prefix_hash", - &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, "prefix_hash:1000", &new_mem_factory)); - ASSERT_STREQ(new_mem_factory->Name(), "HashSkipListRepFactory"); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("prefix_hash")); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("HashSkipListRepFactory")); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "prefix_hash:1000:invalid_opt", &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, - "id=prefix_hash; bucket_count=32; skiplist_height=64; " - "branching_factor=16", - &new_mem_factory)); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, - "id=prefix_hash; bucket_count=32; skiplist_height=64; " - "branching_factor=16; invalid=unknown", - &new_mem_factory)); - - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, "hash_linkedlist", &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, "hash_linkedlist:1000", &new_mem_factory)); - ASSERT_STREQ(new_mem_factory->Name(), "HashLinkListRepFactory"); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("hash_linkedlist")); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("HashLinkListRepFactory")); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "hash_linkedlist:1000:invalid_opt", &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, - "id=hash_linkedlist; bucket_count=32; threshold=64; huge_page_size=16; " - "logging_threshold=12; log_when_flash=true", - &new_mem_factory)); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, - "id=hash_linkedlist; bucket_count=32; threshold=64; huge_page_size=16; " - "logging_threshold=12; log_when_flash=true; invalid=unknown", - &new_mem_factory)); - - ASSERT_OK(MemTableRepFactory::CreateFromString(config_options, "vector", - &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString(config_options, "vector:1024", - &new_mem_factory)); - ASSERT_STREQ(new_mem_factory->Name(), "VectorRepFactory"); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("vector")); - ASSERT_TRUE(new_mem_factory->IsInstanceOf("VectorRepFactory")); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "vector:1024:invalid_opt", &new_mem_factory)); - ASSERT_OK(MemTableRepFactory::CreateFromString( - config_options, "id=vector; count=42", &new_mem_factory)); - ASSERT_NOK(MemTableRepFactory::CreateFromString( - config_options, "id=vector; invalid=unknown", &new_mem_factory)); - ASSERT_NOK(MemTableRepFactory::CreateFromString(config_options, "cuckoo", - &new_mem_factory)); - // CuckooHash memtable is already removed. - ASSERT_NOK(MemTableRepFactory::CreateFromString(config_options, "cuckoo:1024", - &new_mem_factory)); - - ASSERT_NOK(MemTableRepFactory::CreateFromString(config_options, "bad_factory", - &new_mem_factory)); -} - -class CustomEnv : public EnvWrapper { - public: - explicit CustomEnv(Env* _target) : EnvWrapper(_target) {} - static const char* kClassName() { return "CustomEnv"; } - const char* Name() const override { return kClassName(); } -}; - -TEST_F(OptionsTest, GetOptionsFromStringTest) { - Options base_options, new_options; - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - - base_options.write_buffer_size = 20; - base_options.min_write_buffer_number_to_merge = 15; - BlockBasedTableOptions block_based_table_options; - block_based_table_options.cache_index_and_filter_blocks = true; - base_options.table_factory.reset( - NewBlockBasedTableFactory(block_based_table_options)); - - // Register an Env with object registry. - ObjectLibrary::Default()->AddFactory( - CustomEnv::kClassName(), - [](const std::string& /*name*/, std::unique_ptr* /*env_guard*/, - std::string* /* errmsg */) { - static CustomEnv env(Env::Default()); - return &env; - }); - - ASSERT_OK(GetOptionsFromString( - config_options, base_options, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_cache=1M;block_size=4;};" - "compression_opts=4:5:6;create_if_missing=true;max_open_files=1;" - "bottommost_compression_opts=5:6:7;create_if_missing=true;max_open_files=" - "1;" - "rate_limiter_bytes_per_sec=1024;env=CustomEnv", - &new_options)); - - ASSERT_EQ(new_options.compression_opts.window_bits, 4); - ASSERT_EQ(new_options.compression_opts.level, 5); - ASSERT_EQ(new_options.compression_opts.strategy, 6); - ASSERT_EQ(new_options.compression_opts.max_dict_bytes, 0u); - ASSERT_EQ(new_options.compression_opts.zstd_max_train_bytes, 0u); - ASSERT_EQ(new_options.compression_opts.parallel_threads, 1u); - ASSERT_EQ(new_options.compression_opts.enabled, false); - ASSERT_EQ(new_options.compression_opts.use_zstd_dict_trainer, true); - ASSERT_EQ(new_options.bottommost_compression, kDisableCompressionOption); - ASSERT_EQ(new_options.bottommost_compression_opts.window_bits, 5); - ASSERT_EQ(new_options.bottommost_compression_opts.level, 6); - ASSERT_EQ(new_options.bottommost_compression_opts.strategy, 7); - ASSERT_EQ(new_options.bottommost_compression_opts.max_dict_bytes, 0u); - ASSERT_EQ(new_options.bottommost_compression_opts.zstd_max_train_bytes, 0u); - ASSERT_EQ(new_options.bottommost_compression_opts.parallel_threads, 1u); - ASSERT_EQ(new_options.bottommost_compression_opts.enabled, false); - ASSERT_EQ(new_options.bottommost_compression_opts.use_zstd_dict_trainer, - true); - ASSERT_EQ(new_options.write_buffer_size, 10U); - ASSERT_EQ(new_options.max_write_buffer_number, 16); - const auto new_bbto = - new_options.table_factory->GetOptions(); - ASSERT_NE(new_bbto, nullptr); - ASSERT_EQ(new_bbto->block_cache->GetCapacity(), 1U << 20); - ASSERT_EQ(new_bbto->block_size, 4U); - // don't overwrite block based table options - ASSERT_TRUE(new_bbto->cache_index_and_filter_blocks); - - ASSERT_EQ(new_options.create_if_missing, true); - ASSERT_EQ(new_options.max_open_files, 1); - ASSERT_TRUE(new_options.rate_limiter.get() != nullptr); - Env* newEnv = new_options.env; - ASSERT_OK(Env::CreateFromString({}, CustomEnv::kClassName(), &newEnv)); - ASSERT_EQ(newEnv, new_options.env); - - config_options.ignore_unknown_options = false; - // Test a bad value for a DBOption returns a failure - base_options.dump_malloc_stats = false; - base_options.write_buffer_size = 1024; - Options bad_options = new_options; - Status s = GetOptionsFromString(config_options, base_options, - "create_if_missing=XX;dump_malloc_stats=true", - &bad_options); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_EQ(bad_options.dump_malloc_stats, false); - - bad_options = new_options; - s = GetOptionsFromString(config_options, base_options, - "write_buffer_size=XX;dump_malloc_stats=true", - &bad_options); - ASSERT_NOK(s); - ASSERT_TRUE(s.IsInvalidArgument()); - - ASSERT_EQ(bad_options.dump_malloc_stats, false); - - // Test a bad value for a TableFactory Option returns a failure - bad_options = new_options; - s = GetOptionsFromString(config_options, base_options, - "write_buffer_size=16;dump_malloc_stats=true" - "block_based_table_factory={block_size=XX;};", - &bad_options); - ASSERT_TRUE(s.IsInvalidArgument()); - ASSERT_EQ(bad_options.dump_malloc_stats, false); - ASSERT_EQ(bad_options.write_buffer_size, 1024); - - config_options.ignore_unknown_options = true; - ASSERT_OK(GetOptionsFromString(config_options, base_options, - "create_if_missing=XX;dump_malloc_stats=true;" - "write_buffer_size=XX;" - "block_based_table_factory={block_size=XX;};", - &bad_options)); - ASSERT_EQ(bad_options.create_if_missing, base_options.create_if_missing); - ASSERT_EQ(bad_options.dump_malloc_stats, true); - ASSERT_EQ(bad_options.write_buffer_size, base_options.write_buffer_size); - - // Test the old interface - ASSERT_OK(GetOptionsFromString( - base_options, - "write_buffer_size=22;max_write_buffer_number=33;max_open_files=44;", - &new_options)); - ASSERT_EQ(new_options.write_buffer_size, 22U); - ASSERT_EQ(new_options.max_write_buffer_number, 33); - ASSERT_EQ(new_options.max_open_files, 44); -} - -TEST_F(OptionsTest, DBOptionsSerialization) { - Options base_options, new_options; - Random rnd(301); - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - - // Phase 1: Make big change in base_options - test::RandomInitDBOptions(&base_options, &rnd); - - // Phase 2: obtain a string from base_option - std::string base_options_file_content; - ASSERT_OK(GetStringFromDBOptions(config_options, base_options, - &base_options_file_content)); - - // Phase 3: Set new_options from the derived string and expect - // new_options == base_options - ASSERT_OK(GetDBOptionsFromString(config_options, DBOptions(), - base_options_file_content, &new_options)); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_options, base_options, - new_options)); -} - -TEST_F(OptionsTest, OptionsComposeDecompose) { - // build an Options from DBOptions + CFOptions, then decompose it to verify - // we get same constituent options. - DBOptions base_db_opts; - ColumnFamilyOptions base_cf_opts; - ConfigOptions - config_options; // Use default for ignore(false) and check (exact) - config_options.input_strings_escaped = false; - - Random rnd(301); - test::RandomInitDBOptions(&base_db_opts, &rnd); - test::RandomInitCFOptions(&base_cf_opts, base_db_opts, &rnd); - - Options base_opts(base_db_opts, base_cf_opts); - DBOptions new_db_opts(base_opts); - ColumnFamilyOptions new_cf_opts(base_opts); - - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_options, base_db_opts, - new_db_opts)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_cf_opts, - new_cf_opts)); - delete new_cf_opts.compaction_filter; -} - -TEST_F(OptionsTest, DBOptionsComposeImmutable) { - // Build a DBOptions from an Immutable/Mutable one and verify that - // we get same constituent options. - ConfigOptions config_options; - Random rnd(301); - DBOptions base_opts, new_opts; - test::RandomInitDBOptions(&base_opts, &rnd); - MutableDBOptions m_opts(base_opts); - ImmutableDBOptions i_opts(base_opts); - new_opts = BuildDBOptions(i_opts, m_opts); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_options, base_opts, - new_opts)); -} - -TEST_F(OptionsTest, GetMutableDBOptions) { - Random rnd(228); - DBOptions base_opts; - std::string opts_str; - std::unordered_map opts_map; - ConfigOptions config_options; - - test::RandomInitDBOptions(&base_opts, &rnd); - ImmutableDBOptions i_opts(base_opts); - MutableDBOptions m_opts(base_opts); - MutableDBOptions new_opts; - ASSERT_OK(GetStringFromMutableDBOptions(config_options, m_opts, &opts_str)); - ASSERT_OK(StringToMap(opts_str, &opts_map)); - ASSERT_OK(GetMutableDBOptionsFromStrings(m_opts, opts_map, &new_opts)); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions( - config_options, base_opts, BuildDBOptions(i_opts, new_opts))); -} - -TEST_F(OptionsTest, CFOptionsComposeImmutable) { - // Build a DBOptions from an Immutable/Mutable one and verify that - // we get same constituent options. - ConfigOptions config_options; - Random rnd(301); - ColumnFamilyOptions base_opts, new_opts; - DBOptions dummy; // Needed to create ImmutableCFOptions - test::RandomInitCFOptions(&base_opts, dummy, &rnd); - MutableCFOptions m_opts(base_opts); - ImmutableCFOptions i_opts(base_opts); - UpdateColumnFamilyOptions(i_opts, &new_opts); - UpdateColumnFamilyOptions(m_opts, &new_opts); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base_opts, - new_opts)); - delete new_opts.compaction_filter; -} - -TEST_F(OptionsTest, GetMutableCFOptions) { - Random rnd(228); - ColumnFamilyOptions base, copy; - std::string opts_str; - std::unordered_map opts_map; - ConfigOptions config_options; - DBOptions dummy; // Needed to create ImmutableCFOptions - - test::RandomInitCFOptions(&base, dummy, &rnd); - ColumnFamilyOptions result; - MutableCFOptions m_opts(base), new_opts; - - ASSERT_OK(GetStringFromMutableCFOptions(config_options, m_opts, &opts_str)); - ASSERT_OK(StringToMap(opts_str, &opts_map)); - ASSERT_OK(GetMutableOptionsFromStrings(m_opts, opts_map, nullptr, &new_opts)); - UpdateColumnFamilyOptions(ImmutableCFOptions(base), ©); - UpdateColumnFamilyOptions(new_opts, ©); - - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, base, copy)); - delete copy.compaction_filter; -} - -TEST_F(OptionsTest, ColumnFamilyOptionsSerialization) { - Options options; - ColumnFamilyOptions base_opt, new_opt; - Random rnd(302); - ConfigOptions config_options; - config_options.input_strings_escaped = false; - - // Phase 1: randomly assign base_opt - // custom type options - test::RandomInitCFOptions(&base_opt, options, &rnd); - - // Phase 2: obtain a string from base_opt - std::string base_options_file_content; - ASSERT_OK(GetStringFromColumnFamilyOptions(config_options, base_opt, - &base_options_file_content)); - - // Phase 3: Set new_opt from the derived string and expect - // new_opt == base_opt - ASSERT_OK( - GetColumnFamilyOptionsFromString(config_options, ColumnFamilyOptions(), - base_options_file_content, &new_opt)); - ASSERT_OK( - RocksDBOptionsParser::VerifyCFOptions(config_options, base_opt, new_opt)); - if (base_opt.compaction_filter) { - delete base_opt.compaction_filter; - } -} - -TEST_F(OptionsTest, CheckBlockBasedTableOptions) { - ColumnFamilyOptions cf_opts; - DBOptions db_opts; - ConfigOptions config_opts; - - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_opts, cf_opts, "prefix_extractor=capped:8", &cf_opts)); - ASSERT_OK(TableFactory::CreateFromString(config_opts, "BlockBasedTable", - &cf_opts.table_factory)); - ASSERT_NE(cf_opts.table_factory.get(), nullptr); - ASSERT_TRUE(cf_opts.table_factory->IsInstanceOf( - TableFactory::kBlockBasedTableName())); - auto bbto = cf_opts.table_factory->GetOptions(); - ASSERT_OK(cf_opts.table_factory->ConfigureFromString( - config_opts, - "block_cache={capacity=1M;num_shard_bits=4;};" - "block_size_deviation=101;" - "block_restart_interval=0;" - "index_block_restart_interval=5;" - "partition_filters=true;" - "index_type=kHashSearch;" - "no_block_cache=1;")); - ASSERT_NE(bbto, nullptr); - ASSERT_EQ(bbto->block_cache.get(), nullptr); - ASSERT_EQ(bbto->block_size_deviation, 0); - ASSERT_EQ(bbto->block_restart_interval, 1); - ASSERT_EQ(bbto->index_block_restart_interval, 1); - ASSERT_FALSE(bbto->partition_filters); - ASSERT_OK(TableFactory::CreateFromString(config_opts, "BlockBasedTable", - &cf_opts.table_factory)); - bbto = cf_opts.table_factory->GetOptions(); - - ASSERT_OK(cf_opts.table_factory->ConfigureFromString(config_opts, - "no_block_cache=0;")); - ASSERT_NE(bbto->block_cache.get(), nullptr); - ASSERT_OK(cf_opts.table_factory->ValidateOptions(db_opts, cf_opts)); -} - -TEST_F(OptionsTest, MutableTableOptions) { - ConfigOptions config_options; - std::shared_ptr bbtf; - bbtf.reset(NewBlockBasedTableFactory()); - auto bbto = bbtf->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_OK(bbtf->ConfigureOption(config_options, "block_align", "true")); - ASSERT_OK(bbtf->ConfigureOption(config_options, "block_size", "1024")); - ASSERT_EQ(bbto->block_align, true); - ASSERT_EQ(bbto->block_size, 1024); - ASSERT_OK(bbtf->PrepareOptions(config_options)); - config_options.mutable_options_only = true; - ASSERT_OK(bbtf->ConfigureOption(config_options, "block_size", "1024")); - ASSERT_EQ(bbto->block_align, true); - ASSERT_NOK(bbtf->ConfigureOption(config_options, "block_align", "false")); - ASSERT_OK(bbtf->ConfigureOption(config_options, "block_size", "2048")); - ASSERT_EQ(bbto->block_align, true); - ASSERT_EQ(bbto->block_size, 2048); - - ColumnFamilyOptions cf_opts; - cf_opts.table_factory = bbtf; - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, cf_opts, "block_based_table_factory.block_align=false", - &cf_opts)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, cf_opts, "block_based_table_factory.block_size=8192", - &cf_opts)); - ASSERT_EQ(bbto->block_align, true); - ASSERT_EQ(bbto->block_size, 8192); -} - -TEST_F(OptionsTest, MutableCFOptions) { - ConfigOptions config_options; - ColumnFamilyOptions cf_opts; - - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, cf_opts, - "paranoid_file_checks=true; block_based_table_factory.block_align=false; " - "block_based_table_factory.block_size=8192;", - &cf_opts)); - ASSERT_TRUE(cf_opts.paranoid_file_checks); - ASSERT_NE(cf_opts.table_factory.get(), nullptr); - const auto bbto = cf_opts.table_factory->GetOptions(); - ASSERT_NE(bbto, nullptr); - ASSERT_EQ(bbto->block_size, 8192); - ASSERT_EQ(bbto->block_align, false); - std::unordered_map unused_opts; - ASSERT_OK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, {{"paranoid_file_checks", "false"}}, &cf_opts)); - ASSERT_EQ(cf_opts.paranoid_file_checks, false); - - ASSERT_OK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, - {{"block_based_table_factory.block_size", "16384"}}, &cf_opts)); - ASSERT_EQ(bbto, cf_opts.table_factory->GetOptions()); - ASSERT_EQ(bbto->block_size, 16384); - - config_options.mutable_options_only = true; - // Force consistency checks is not mutable - ASSERT_NOK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, {{"force_consistency_checks", "true"}}, - &cf_opts)); - - // Attempt to change the table. It is not mutable, so this should fail and - // leave the original intact - ASSERT_NOK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, {{"table_factory", "PlainTable"}}, &cf_opts)); - ASSERT_NOK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, {{"table_factory.id", "PlainTable"}}, &cf_opts)); - ASSERT_NE(cf_opts.table_factory.get(), nullptr); - ASSERT_EQ(bbto, cf_opts.table_factory->GetOptions()); - - // Change the block size. Should update the value in the current table - ASSERT_OK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, - {{"block_based_table_factory.block_size", "8192"}}, &cf_opts)); - ASSERT_EQ(bbto, cf_opts.table_factory->GetOptions()); - ASSERT_EQ(bbto->block_size, 8192); - - // Attempt to turn off block cache fails, as this option is not mutable - ASSERT_NOK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, - {{"block_based_table_factory.no_block_cache", "true"}}, &cf_opts)); - ASSERT_EQ(bbto, cf_opts.table_factory->GetOptions()); - - // Attempt to change the block size via a config string/map. Should update - // the current value - ASSERT_OK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, - {{"block_based_table_factory", "{block_size=32768}"}}, &cf_opts)); - ASSERT_EQ(bbto, cf_opts.table_factory->GetOptions()); - ASSERT_EQ(bbto->block_size, 32768); - - // Attempt to change the block size and no cache through the map. Should - // fail, leaving the old values intact - ASSERT_NOK(GetColumnFamilyOptionsFromMap( - config_options, cf_opts, - {{"block_based_table_factory", - "{block_size=16384; no_block_cache=true}"}}, - &cf_opts)); - ASSERT_EQ(bbto, cf_opts.table_factory->GetOptions()); - ASSERT_EQ(bbto->block_size, 32768); -} - - -Status StringToMap( - const std::string& opts_str, - std::unordered_map* opts_map); - -TEST_F(OptionsTest, StringToMapTest) { - std::unordered_map opts_map; - // Regular options - ASSERT_OK(StringToMap("k1=v1;k2=v2;k3=v3", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "v2"); - ASSERT_EQ(opts_map["k3"], "v3"); - // Value with '=' - opts_map.clear(); - ASSERT_OK(StringToMap("k1==v1;k2=v2=;", &opts_map)); - ASSERT_EQ(opts_map["k1"], "=v1"); - ASSERT_EQ(opts_map["k2"], "v2="); - // Overwrriten option - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k1=v2;k3=v3", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v2"); - ASSERT_EQ(opts_map["k3"], "v3"); - // Empty value - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2=;k3=v3;k4=", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_TRUE(opts_map.find("k2") != opts_map.end()); - ASSERT_EQ(opts_map["k2"], ""); - ASSERT_EQ(opts_map["k3"], "v3"); - ASSERT_TRUE(opts_map.find("k4") != opts_map.end()); - ASSERT_EQ(opts_map["k4"], ""); - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2=;k3=v3;k4= ", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_TRUE(opts_map.find("k2") != opts_map.end()); - ASSERT_EQ(opts_map["k2"], ""); - ASSERT_EQ(opts_map["k3"], "v3"); - ASSERT_TRUE(opts_map.find("k4") != opts_map.end()); - ASSERT_EQ(opts_map["k4"], ""); - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2=;k3=", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_TRUE(opts_map.find("k2") != opts_map.end()); - ASSERT_EQ(opts_map["k2"], ""); - ASSERT_TRUE(opts_map.find("k3") != opts_map.end()); - ASSERT_EQ(opts_map["k3"], ""); - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2=;k3=;", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_TRUE(opts_map.find("k2") != opts_map.end()); - ASSERT_EQ(opts_map["k2"], ""); - ASSERT_TRUE(opts_map.find("k3") != opts_map.end()); - ASSERT_EQ(opts_map["k3"], ""); - // Regular nested options - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2={nk1=nv1;nk2=nv2};k3=v3", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "nk1=nv1;nk2=nv2"); - ASSERT_EQ(opts_map["k3"], "v3"); - // Multi-level nested options - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2={nk1=nv1;nk2={nnk1=nnk2}};" - "k3={nk1={nnk1={nnnk1=nnnv1;nnnk2;nnnv2}}};k4=v4", - &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "nk1=nv1;nk2={nnk1=nnk2}"); - ASSERT_EQ(opts_map["k3"], "nk1={nnk1={nnnk1=nnnv1;nnnk2;nnnv2}}"); - ASSERT_EQ(opts_map["k4"], "v4"); - // Garbage inside curly braces - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2={dfad=};k3={=};k4=v4", - &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "dfad="); - ASSERT_EQ(opts_map["k3"], "="); - ASSERT_EQ(opts_map["k4"], "v4"); - // Empty nested options - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2={};", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], ""); - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2={{{{}}}{}{}};", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "{{{}}}{}{}"); - // With random spaces - opts_map.clear(); - ASSERT_OK(StringToMap(" k1 = v1 ; k2= {nk1=nv1; nk2={nnk1=nnk2}} ; " - "k3={ { } }; k4= v4 ", - &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "nk1=nv1; nk2={nnk1=nnk2}"); - ASSERT_EQ(opts_map["k3"], "{ }"); - ASSERT_EQ(opts_map["k4"], "v4"); - - // Empty key - ASSERT_NOK(StringToMap("k1=v1;k2=v2;=", &opts_map)); - ASSERT_NOK(StringToMap("=v1;k2=v2", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2v2;", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2=v2;fadfa", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2=v2;;", &opts_map)); - // Mismatch curly braces - ASSERT_NOK(StringToMap("k1=v1;k2={;k3=v3", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{};k3=v3", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={}};k3=v3", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{}{}}};k3=v3", &opts_map)); - // However this is valid! - opts_map.clear(); - ASSERT_OK(StringToMap("k1=v1;k2=};k3=v3", &opts_map)); - ASSERT_EQ(opts_map["k1"], "v1"); - ASSERT_EQ(opts_map["k2"], "}"); - ASSERT_EQ(opts_map["k3"], "v3"); - - // Invalid chars after closing curly brace - ASSERT_NOK(StringToMap("k1=v1;k2={{}}{};k3=v3", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{}}cfda;k3=v3", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{}} cfda;k3=v3", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{}} cfda", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{}}{}", &opts_map)); - ASSERT_NOK(StringToMap("k1=v1;k2={{dfdl}adfa}{}", &opts_map)); -} - -TEST_F(OptionsTest, StringToMapRandomTest) { - std::unordered_map opts_map; - // Make sure segfault is not hit by semi-random strings - - std::vector bases = { - "a={aa={};tt={xxx={}}};c=defff", - "a={aa={};tt={xxx={}}};c=defff;d={{}yxx{}3{xx}}", - "abc={{}{}{}{{{}}}{{}{}{}{}{}{}{}"}; - - for (std::string base : bases) { - for (int rand_seed = 301; rand_seed < 401; rand_seed++) { - Random rnd(rand_seed); - for (int attempt = 0; attempt < 10; attempt++) { - std::string str = base; - // Replace random position to space - size_t pos = static_cast( - rnd.Uniform(static_cast(base.size()))); - str[pos] = ' '; - Status s = StringToMap(str, &opts_map); - ASSERT_TRUE(s.ok() || s.IsInvalidArgument()); - opts_map.clear(); - } - } - } - - // Random Construct a string - std::vector chars = {'{', '}', ' ', '=', ';', 'c'}; - for (int rand_seed = 301; rand_seed < 1301; rand_seed++) { - Random rnd(rand_seed); - int len = rnd.Uniform(30); - std::string str = ""; - for (int attempt = 0; attempt < len; attempt++) { - // Add a random character - size_t pos = static_cast( - rnd.Uniform(static_cast(chars.size()))); - str.append(1, chars[pos]); - } - Status s = StringToMap(str, &opts_map); - ASSERT_TRUE(s.ok() || s.IsInvalidArgument()); - s = StringToMap("name=" + str, &opts_map); - ASSERT_TRUE(s.ok() || s.IsInvalidArgument()); - opts_map.clear(); - } -} - -TEST_F(OptionsTest, GetStringFromCompressionType) { - std::string res; - - ASSERT_OK(GetStringFromCompressionType(&res, kNoCompression)); - ASSERT_EQ(res, "kNoCompression"); - - ASSERT_OK(GetStringFromCompressionType(&res, kSnappyCompression)); - ASSERT_EQ(res, "kSnappyCompression"); - - ASSERT_OK(GetStringFromCompressionType(&res, kDisableCompressionOption)); - ASSERT_EQ(res, "kDisableCompressionOption"); - - ASSERT_OK(GetStringFromCompressionType(&res, kLZ4Compression)); - ASSERT_EQ(res, "kLZ4Compression"); - - ASSERT_OK(GetStringFromCompressionType(&res, kZlibCompression)); - ASSERT_EQ(res, "kZlibCompression"); - - ASSERT_NOK( - GetStringFromCompressionType(&res, static_cast(-10))); -} - -TEST_F(OptionsTest, OnlyMutableDBOptions) { - std::string opt_str; - Random rnd(302); - ConfigOptions cfg_opts; - DBOptions db_opts; - DBOptions mdb_opts; - std::unordered_set m_names; - std::unordered_set a_names; - - test::RandomInitDBOptions(&db_opts, &rnd); - auto db_config = DBOptionsAsConfigurable(db_opts); - - // Get all of the DB Option names (mutable or not) - ASSERT_OK(db_config->GetOptionNames(cfg_opts, &a_names)); - - // Get only the mutable options from db_opts and set those in mdb_opts - cfg_opts.mutable_options_only = true; - - // Get only the Mutable DB Option names - ASSERT_OK(db_config->GetOptionNames(cfg_opts, &m_names)); - ASSERT_OK(GetStringFromDBOptions(cfg_opts, db_opts, &opt_str)); - ASSERT_OK(GetDBOptionsFromString(cfg_opts, mdb_opts, opt_str, &mdb_opts)); - std::string mismatch; - // Comparing only the mutable options, the two are equivalent - auto mdb_config = DBOptionsAsConfigurable(mdb_opts); - ASSERT_TRUE(mdb_config->AreEquivalent(cfg_opts, db_config.get(), &mismatch)); - ASSERT_TRUE(db_config->AreEquivalent(cfg_opts, mdb_config.get(), &mismatch)); - - ASSERT_GT(a_names.size(), m_names.size()); - for (const auto& n : m_names) { - std::string m, d; - ASSERT_OK(mdb_config->GetOption(cfg_opts, n, &m)); - ASSERT_OK(db_config->GetOption(cfg_opts, n, &d)); - ASSERT_EQ(m, d); - } - - cfg_opts.mutable_options_only = false; - // Comparing all of the options, the two are not equivalent - ASSERT_FALSE(mdb_config->AreEquivalent(cfg_opts, db_config.get(), &mismatch)); - ASSERT_FALSE(db_config->AreEquivalent(cfg_opts, mdb_config.get(), &mismatch)); - - // Make sure there are only mutable options being configured - ASSERT_OK(GetDBOptionsFromString(cfg_opts, DBOptions(), opt_str, &db_opts)); -} - -TEST_F(OptionsTest, OnlyMutableCFOptions) { - std::string opt_str; - Random rnd(302); - ConfigOptions cfg_opts; - DBOptions db_opts; - ColumnFamilyOptions mcf_opts; - ColumnFamilyOptions cf_opts; - std::unordered_set m_names; - std::unordered_set a_names; - - test::RandomInitCFOptions(&cf_opts, db_opts, &rnd); - cf_opts.comparator = ReverseBytewiseComparator(); - auto cf_config = CFOptionsAsConfigurable(cf_opts); - - // Get all of the CF Option names (mutable or not) - ASSERT_OK(cf_config->GetOptionNames(cfg_opts, &a_names)); - - // Get only the mutable options from cf_opts and set those in mcf_opts - cfg_opts.mutable_options_only = true; - // Get only the Mutable CF Option names - ASSERT_OK(cf_config->GetOptionNames(cfg_opts, &m_names)); - ASSERT_OK(GetStringFromColumnFamilyOptions(cfg_opts, cf_opts, &opt_str)); - ASSERT_OK( - GetColumnFamilyOptionsFromString(cfg_opts, mcf_opts, opt_str, &mcf_opts)); - std::string mismatch; - - auto mcf_config = CFOptionsAsConfigurable(mcf_opts); - // Comparing only the mutable options, the two are equivalent - ASSERT_TRUE(mcf_config->AreEquivalent(cfg_opts, cf_config.get(), &mismatch)); - ASSERT_TRUE(cf_config->AreEquivalent(cfg_opts, mcf_config.get(), &mismatch)); - - ASSERT_GT(a_names.size(), m_names.size()); - for (const auto& n : m_names) { - std::string m, d; - ASSERT_OK(mcf_config->GetOption(cfg_opts, n, &m)); - ASSERT_OK(cf_config->GetOption(cfg_opts, n, &d)); - ASSERT_EQ(m, d); - } - - cfg_opts.mutable_options_only = false; - // Comparing all of the options, the two are not equivalent - ASSERT_FALSE(mcf_config->AreEquivalent(cfg_opts, cf_config.get(), &mismatch)); - ASSERT_FALSE(cf_config->AreEquivalent(cfg_opts, mcf_config.get(), &mismatch)); - delete cf_opts.compaction_filter; - - // Make sure the options string contains only mutable options - ASSERT_OK(GetColumnFamilyOptionsFromString(cfg_opts, ColumnFamilyOptions(), - opt_str, &cf_opts)); - delete cf_opts.compaction_filter; -} - -TEST_F(OptionsTest, SstPartitionerTest) { - ConfigOptions cfg_opts; - ColumnFamilyOptions cf_opts, new_opt; - std::string opts_str, mismatch; - - ASSERT_OK(SstPartitionerFactory::CreateFromString( - cfg_opts, SstPartitionerFixedPrefixFactory::kClassName(), - &cf_opts.sst_partitioner_factory)); - ASSERT_NE(cf_opts.sst_partitioner_factory, nullptr); - ASSERT_STREQ(cf_opts.sst_partitioner_factory->Name(), - SstPartitionerFixedPrefixFactory::kClassName()); - ASSERT_NOK(GetColumnFamilyOptionsFromString( - cfg_opts, ColumnFamilyOptions(), - std::string("sst_partitioner_factory={id=") + - SstPartitionerFixedPrefixFactory::kClassName() + "; unknown=10;}", - &cf_opts)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - cfg_opts, ColumnFamilyOptions(), - std::string("sst_partitioner_factory={id=") + - SstPartitionerFixedPrefixFactory::kClassName() + "; length=10;}", - &cf_opts)); - ASSERT_NE(cf_opts.sst_partitioner_factory, nullptr); - ASSERT_STREQ(cf_opts.sst_partitioner_factory->Name(), - SstPartitionerFixedPrefixFactory::kClassName()); - ASSERT_OK(GetStringFromColumnFamilyOptions(cfg_opts, cf_opts, &opts_str)); - ASSERT_OK( - GetColumnFamilyOptionsFromString(cfg_opts, cf_opts, opts_str, &new_opt)); - ASSERT_NE(new_opt.sst_partitioner_factory, nullptr); - ASSERT_STREQ(new_opt.sst_partitioner_factory->Name(), - SstPartitionerFixedPrefixFactory::kClassName()); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(cfg_opts, cf_opts, new_opt)); - ASSERT_TRUE(cf_opts.sst_partitioner_factory->AreEquivalent( - cfg_opts, new_opt.sst_partitioner_factory.get(), &mismatch)); -} - -TEST_F(OptionsTest, FileChecksumGenFactoryTest) { - ConfigOptions cfg_opts; - DBOptions db_opts, new_opt; - std::string opts_str, mismatch; - auto factory = GetFileChecksumGenCrc32cFactory(); - - cfg_opts.ignore_unsupported_options = false; - - ASSERT_OK(GetStringFromDBOptions(cfg_opts, db_opts, &opts_str)); - ASSERT_OK(GetDBOptionsFromString(cfg_opts, db_opts, opts_str, &new_opt)); - - ASSERT_NE(factory, nullptr); - ASSERT_OK(FileChecksumGenFactory::CreateFromString( - cfg_opts, factory->Name(), &db_opts.file_checksum_gen_factory)); - ASSERT_NE(db_opts.file_checksum_gen_factory, nullptr); - ASSERT_STREQ(db_opts.file_checksum_gen_factory->Name(), factory->Name()); - ASSERT_NOK(GetDBOptionsFromString( - cfg_opts, DBOptions(), "file_checksum_gen_factory=unknown", &db_opts)); - ASSERT_OK(GetDBOptionsFromString( - cfg_opts, DBOptions(), - std::string("file_checksum_gen_factory=") + factory->Name(), &db_opts)); - ASSERT_NE(db_opts.file_checksum_gen_factory, nullptr); - ASSERT_STREQ(db_opts.file_checksum_gen_factory->Name(), factory->Name()); - - ASSERT_OK(GetStringFromDBOptions(cfg_opts, db_opts, &opts_str)); - ASSERT_OK(GetDBOptionsFromString(cfg_opts, db_opts, opts_str, &new_opt)); - ASSERT_NE(new_opt.file_checksum_gen_factory, nullptr); - ASSERT_STREQ(new_opt.file_checksum_gen_factory->Name(), factory->Name()); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(cfg_opts, db_opts, new_opt)); - ASSERT_TRUE(factory->AreEquivalent( - cfg_opts, new_opt.file_checksum_gen_factory.get(), &mismatch)); - ASSERT_TRUE(db_opts.file_checksum_gen_factory->AreEquivalent( - cfg_opts, new_opt.file_checksum_gen_factory.get(), &mismatch)); -} - -class TestTablePropertiesCollectorFactory - : public TablePropertiesCollectorFactory { - private: - std::string id_; - - public: - explicit TestTablePropertiesCollectorFactory(const std::string& id) - : id_(id) {} - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - return nullptr; - } - static const char* kClassName() { return "TestCollector"; } - const char* Name() const override { return kClassName(); } - std::string GetId() const override { - return std::string(kClassName()) + ":" + id_; - } -}; - -TEST_F(OptionsTest, OptionTablePropertiesTest) { - ConfigOptions cfg_opts; - ColumnFamilyOptions orig, copy; - orig.table_properties_collector_factories.push_back( - std::make_shared("1")); - orig.table_properties_collector_factories.push_back( - std::make_shared("2")); - - // Push two TablePropertiesCollectorFactories then create a new - // ColumnFamilyOptions based on those settings. The copy should - // have no properties but still match the original - std::string opts_str; - ASSERT_OK(GetStringFromColumnFamilyOptions(cfg_opts, orig, &opts_str)); - ASSERT_OK(GetColumnFamilyOptionsFromString(cfg_opts, orig, opts_str, ©)); - ASSERT_EQ(copy.table_properties_collector_factories.size(), 0); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(cfg_opts, orig, copy)); - - // Now register a TablePropertiesCollectorFactory - // Repeat the experiment. The copy should have the same - // properties as the original - cfg_opts.registry->AddLibrary("collector") - ->AddFactory( - ObjectLibrary::PatternEntry( - TestTablePropertiesCollectorFactory::kClassName(), false) - .AddSeparator(":"), - [](const std::string& name, - std::unique_ptr* guard, - std::string* /* errmsg */) { - std::string id = name.substr( - strlen(TestTablePropertiesCollectorFactory::kClassName()) + 1); - guard->reset(new TestTablePropertiesCollectorFactory(id)); - return guard->get(); - }); - - ASSERT_OK(GetColumnFamilyOptionsFromString(cfg_opts, orig, opts_str, ©)); - ASSERT_EQ(copy.table_properties_collector_factories.size(), 2); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(cfg_opts, orig, copy)); -} - -TEST_F(OptionsTest, ConvertOptionsTest) { - LevelDBOptions leveldb_opt; - Options converted_opt = ConvertOptions(leveldb_opt); - - ASSERT_EQ(converted_opt.create_if_missing, leveldb_opt.create_if_missing); - ASSERT_EQ(converted_opt.error_if_exists, leveldb_opt.error_if_exists); - ASSERT_EQ(converted_opt.paranoid_checks, leveldb_opt.paranoid_checks); - ASSERT_EQ(converted_opt.env, leveldb_opt.env); - ASSERT_EQ(converted_opt.info_log.get(), leveldb_opt.info_log); - ASSERT_EQ(converted_opt.write_buffer_size, leveldb_opt.write_buffer_size); - ASSERT_EQ(converted_opt.max_open_files, leveldb_opt.max_open_files); - ASSERT_EQ(converted_opt.compression, leveldb_opt.compression); - - std::shared_ptr table_factory = converted_opt.table_factory; - const auto table_opt = table_factory->GetOptions(); - ASSERT_NE(table_opt, nullptr); - - ASSERT_EQ(table_opt->block_cache->GetCapacity(), 8UL << 20); - ASSERT_EQ(table_opt->block_size, leveldb_opt.block_size); - ASSERT_EQ(table_opt->block_restart_interval, - leveldb_opt.block_restart_interval); - ASSERT_EQ(table_opt->filter_policy.get(), leveldb_opt.filter_policy); -} -class TestEventListener : public EventListener { - private: - std::string id_; - - public: - explicit TestEventListener(const std::string& id) : id_("Test" + id) {} - const char* Name() const override { return id_.c_str(); } -}; - -static std::unordered_map - test_listener_option_info = { - {"s", - {0, OptionType::kString, OptionVerificationType::kNormal, - OptionTypeFlags::kNone}}, - -}; - -class TestConfigEventListener : public TestEventListener { - private: - std::string s_; - - public: - explicit TestConfigEventListener(const std::string& id) - : TestEventListener("Config" + id) { - s_ = id; - RegisterOptions("Test", &s_, &test_listener_option_info); - } -}; - -static int RegisterTestEventListener(ObjectLibrary& library, - const std::string& arg) { - library.AddFactory( - "Test" + arg, - [](const std::string& name, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new TestEventListener(name.substr(4))); - return guard->get(); - }); - library.AddFactory( - "TestConfig" + arg, - [](const std::string& name, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new TestConfigEventListener(name.substr(10))); - return guard->get(); - }); - return 1; -} -TEST_F(OptionsTest, OptionsListenerTest) { - DBOptions orig, copy; - orig.listeners.push_back(std::make_shared("1")); - orig.listeners.push_back(std::make_shared("2")); - orig.listeners.push_back(std::make_shared("")); - orig.listeners.push_back(std::make_shared("1")); - orig.listeners.push_back(std::make_shared("2")); - orig.listeners.push_back(std::make_shared("")); - ConfigOptions config_opts(orig); - config_opts.registry->AddLibrary("listener", RegisterTestEventListener, "1"); - std::string opts_str; - ASSERT_OK(GetStringFromDBOptions(config_opts, orig, &opts_str)); - ASSERT_OK(GetDBOptionsFromString(config_opts, orig, opts_str, ©)); - ASSERT_OK(GetStringFromDBOptions(config_opts, copy, &opts_str)); - ASSERT_EQ( - copy.listeners.size(), - 2); // The Test{Config}1 Listeners could be loaded but not the others - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_opts, orig, copy)); -} - -const static std::string kCustomEnvName = "Custom"; -const static std::string kCustomEnvProp = "env=" + kCustomEnvName; - -static int RegisterCustomEnv(ObjectLibrary& library, const std::string& arg) { - library.AddFactory( - arg, [](const std::string& /*name*/, std::unique_ptr* /*env_guard*/, - std::string* /* errmsg */) { - static CustomEnv env(Env::Default()); - return &env; - }); - return 1; -} - -// This test suite tests the old APIs into the Configure options methods. -// Once those APIs are officially deprecated, this test suite can be deleted. -class OptionsOldApiTest : public testing::Test {}; - -TEST_F(OptionsOldApiTest, GetOptionsFromMapTest) { - std::unordered_map cf_options_map = { - {"write_buffer_size", "1"}, - {"max_write_buffer_number", "2"}, - {"min_write_buffer_number_to_merge", "3"}, - {"max_write_buffer_number_to_maintain", "99"}, - {"max_write_buffer_size_to_maintain", "-99999"}, - {"compression", "kSnappyCompression"}, - {"compression_per_level", - "kNoCompression:" - "kSnappyCompression:" - "kZlibCompression:" - "kBZip2Compression:" - "kLZ4Compression:" - "kLZ4HCCompression:" - "kXpressCompression:" - "kZSTD:" - "kZSTDNotFinalCompression"}, - {"bottommost_compression", "kLZ4Compression"}, - {"bottommost_compression_opts", "5:6:7:8:9:true"}, - {"compression_opts", "4:5:6:7:8:9:true:10:false"}, - {"num_levels", "8"}, - {"level0_file_num_compaction_trigger", "8"}, - {"level0_slowdown_writes_trigger", "9"}, - {"level0_stop_writes_trigger", "10"}, - {"target_file_size_base", "12"}, - {"target_file_size_multiplier", "13"}, - {"max_bytes_for_level_base", "14"}, - {"level_compaction_dynamic_level_bytes", "true"}, - {"level_compaction_dynamic_file_size", "true"}, - {"max_bytes_for_level_multiplier", "15.0"}, - {"max_bytes_for_level_multiplier_additional", "16:17:18"}, - {"max_compaction_bytes", "21"}, - {"soft_rate_limit", "1.1"}, - {"hard_rate_limit", "2.1"}, - {"rate_limit_delay_max_milliseconds", "100"}, - {"hard_pending_compaction_bytes_limit", "211"}, - {"arena_block_size", "22"}, - {"disable_auto_compactions", "true"}, - {"compaction_style", "kCompactionStyleLevel"}, - {"compaction_pri", "kOldestSmallestSeqFirst"}, - {"verify_checksums_in_compaction", "false"}, - {"compaction_options_fifo", "23"}, - {"max_sequential_skip_in_iterations", "24"}, - {"inplace_update_support", "true"}, - {"report_bg_io_stats", "true"}, - {"compaction_measure_io_stats", "false"}, - {"purge_redundant_kvs_while_flush", "false"}, - {"inplace_update_num_locks", "25"}, - {"memtable_prefix_bloom_size_ratio", "0.26"}, - {"memtable_whole_key_filtering", "true"}, - {"memtable_huge_page_size", "28"}, - {"bloom_locality", "29"}, - {"max_successive_merges", "30"}, - {"min_partial_merge_operands", "31"}, - {"prefix_extractor", "fixed:31"}, - {"experimental_mempurge_threshold", "0.003"}, - {"optimize_filters_for_hits", "true"}, - {"enable_blob_files", "true"}, - {"min_blob_size", "1K"}, - {"blob_file_size", "1G"}, - {"blob_compression_type", "kZSTD"}, - {"enable_blob_garbage_collection", "true"}, - {"blob_garbage_collection_age_cutoff", "0.5"}, - {"blob_garbage_collection_force_threshold", "0.75"}, - {"blob_compaction_readahead_size", "256K"}, - {"blob_file_starting_level", "1"}, - {"prepopulate_blob_cache", "kDisable"}, - {"last_level_temperature", "kWarm"}, - }; - - std::unordered_map db_options_map = { - {"create_if_missing", "false"}, - {"create_missing_column_families", "true"}, - {"error_if_exists", "false"}, - {"paranoid_checks", "true"}, - {"track_and_verify_wals_in_manifest", "true"}, - {"verify_sst_unique_id_in_manifest", "true"}, - {"max_open_files", "32"}, - {"max_total_wal_size", "33"}, - {"use_fsync", "true"}, - {"db_log_dir", "/db_log_dir"}, - {"wal_dir", "/wal_dir"}, - {"delete_obsolete_files_period_micros", "34"}, - {"max_background_compactions", "35"}, - {"max_background_flushes", "36"}, - {"max_log_file_size", "37"}, - {"log_file_time_to_roll", "38"}, - {"keep_log_file_num", "39"}, - {"recycle_log_file_num", "5"}, - {"max_manifest_file_size", "40"}, - {"table_cache_numshardbits", "41"}, - {"WAL_ttl_seconds", "43"}, - {"WAL_size_limit_MB", "44"}, - {"manifest_preallocation_size", "45"}, - {"allow_mmap_reads", "true"}, - {"allow_mmap_writes", "false"}, - {"use_direct_reads", "false"}, - {"use_direct_io_for_flush_and_compaction", "false"}, - {"is_fd_close_on_exec", "true"}, - {"skip_log_error_on_recovery", "false"}, - {"stats_dump_period_sec", "46"}, - {"stats_persist_period_sec", "57"}, - {"persist_stats_to_disk", "false"}, - {"stats_history_buffer_size", "69"}, - {"advise_random_on_open", "true"}, - {"use_adaptive_mutex", "false"}, - {"compaction_readahead_size", "100"}, - {"random_access_max_buffer_size", "3145728"}, - {"writable_file_max_buffer_size", "314159"}, - {"bytes_per_sync", "47"}, - {"wal_bytes_per_sync", "48"}, - {"strict_bytes_per_sync", "true"}, - {"preserve_deletes", "false"}, - }; - - ColumnFamilyOptions base_cf_opt; - ColumnFamilyOptions new_cf_opt; - ConfigOptions cf_config_options; - cf_config_options.ignore_unknown_options = false; - cf_config_options.input_strings_escaped = false; - ASSERT_OK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 1U); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 2); - ASSERT_EQ(new_cf_opt.min_write_buffer_number_to_merge, 3); - ASSERT_EQ(new_cf_opt.max_write_buffer_number_to_maintain, 99); - ASSERT_EQ(new_cf_opt.max_write_buffer_size_to_maintain, -99999); - ASSERT_EQ(new_cf_opt.compression, kSnappyCompression); - ASSERT_EQ(new_cf_opt.compression_per_level.size(), 9U); - ASSERT_EQ(new_cf_opt.compression_per_level[0], kNoCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[1], kSnappyCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[2], kZlibCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[3], kBZip2Compression); - ASSERT_EQ(new_cf_opt.compression_per_level[4], kLZ4Compression); - ASSERT_EQ(new_cf_opt.compression_per_level[5], kLZ4HCCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[6], kXpressCompression); - ASSERT_EQ(new_cf_opt.compression_per_level[7], kZSTD); - ASSERT_EQ(new_cf_opt.compression_per_level[8], kZSTDNotFinalCompression); - ASSERT_EQ(new_cf_opt.compression_opts.window_bits, 4); - ASSERT_EQ(new_cf_opt.compression_opts.level, 5); - ASSERT_EQ(new_cf_opt.compression_opts.strategy, 6); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_bytes, 7u); - ASSERT_EQ(new_cf_opt.compression_opts.zstd_max_train_bytes, 8u); - ASSERT_EQ(new_cf_opt.compression_opts.parallel_threads, 9u); - ASSERT_EQ(new_cf_opt.compression_opts.enabled, true); - ASSERT_EQ(new_cf_opt.compression_opts.max_dict_buffer_bytes, 10u); - ASSERT_EQ(new_cf_opt.compression_opts.use_zstd_dict_trainer, false); - ASSERT_EQ(new_cf_opt.bottommost_compression, kLZ4Compression); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.window_bits, 5); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.level, 6); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.strategy, 7); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_bytes, 8u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, 9u); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.parallel_threads, - CompressionOptions().parallel_threads); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, true); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_buffer_bytes, - CompressionOptions().max_dict_buffer_bytes); - ASSERT_EQ(new_cf_opt.bottommost_compression_opts.use_zstd_dict_trainer, - CompressionOptions().use_zstd_dict_trainer); - ASSERT_EQ(new_cf_opt.num_levels, 8); - ASSERT_EQ(new_cf_opt.level0_file_num_compaction_trigger, 8); - ASSERT_EQ(new_cf_opt.level0_slowdown_writes_trigger, 9); - ASSERT_EQ(new_cf_opt.level0_stop_writes_trigger, 10); - ASSERT_EQ(new_cf_opt.target_file_size_base, static_cast(12)); - ASSERT_EQ(new_cf_opt.target_file_size_multiplier, 13); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_base, 14U); - ASSERT_EQ(new_cf_opt.level_compaction_dynamic_level_bytes, true); - ASSERT_EQ(new_cf_opt.level_compaction_dynamic_file_size, true); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier, 15.0); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional.size(), 3U); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional[0], 16); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional[1], 17); - ASSERT_EQ(new_cf_opt.max_bytes_for_level_multiplier_additional[2], 18); - ASSERT_EQ(new_cf_opt.max_compaction_bytes, 21); - ASSERT_EQ(new_cf_opt.hard_pending_compaction_bytes_limit, 211); - ASSERT_EQ(new_cf_opt.arena_block_size, 22U); - ASSERT_EQ(new_cf_opt.disable_auto_compactions, true); - ASSERT_EQ(new_cf_opt.compaction_style, kCompactionStyleLevel); - ASSERT_EQ(new_cf_opt.compaction_pri, kOldestSmallestSeqFirst); - ASSERT_EQ(new_cf_opt.compaction_options_fifo.max_table_files_size, - static_cast(23)); - ASSERT_EQ(new_cf_opt.max_sequential_skip_in_iterations, - static_cast(24)); - ASSERT_EQ(new_cf_opt.inplace_update_support, true); - ASSERT_EQ(new_cf_opt.inplace_update_num_locks, 25U); - ASSERT_EQ(new_cf_opt.memtable_prefix_bloom_size_ratio, 0.26); - ASSERT_EQ(new_cf_opt.memtable_whole_key_filtering, true); - ASSERT_EQ(new_cf_opt.memtable_huge_page_size, 28U); - ASSERT_EQ(new_cf_opt.bloom_locality, 29U); - ASSERT_EQ(new_cf_opt.max_successive_merges, 30U); - ASSERT_TRUE(new_cf_opt.prefix_extractor != nullptr); - ASSERT_EQ(new_cf_opt.optimize_filters_for_hits, true); - ASSERT_EQ(new_cf_opt.prefix_extractor->AsString(), "rocksdb.FixedPrefix.31"); - ASSERT_EQ(new_cf_opt.experimental_mempurge_threshold, 0.003); - ASSERT_EQ(new_cf_opt.enable_blob_files, true); - ASSERT_EQ(new_cf_opt.min_blob_size, 1ULL << 10); - ASSERT_EQ(new_cf_opt.blob_file_size, 1ULL << 30); - ASSERT_EQ(new_cf_opt.blob_compression_type, kZSTD); - ASSERT_EQ(new_cf_opt.enable_blob_garbage_collection, true); - ASSERT_EQ(new_cf_opt.blob_garbage_collection_age_cutoff, 0.5); - ASSERT_EQ(new_cf_opt.blob_garbage_collection_force_threshold, 0.75); - ASSERT_EQ(new_cf_opt.blob_compaction_readahead_size, 262144); - ASSERT_EQ(new_cf_opt.blob_file_starting_level, 1); - ASSERT_EQ(new_cf_opt.prepopulate_blob_cache, PrepopulateBlobCache::kDisable); - ASSERT_EQ(new_cf_opt.last_level_temperature, Temperature::kWarm); - ASSERT_EQ(new_cf_opt.bottommost_temperature, Temperature::kWarm); - - cf_options_map["write_buffer_size"] = "hello"; - ASSERT_NOK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - ConfigOptions exact, loose; - exact.sanity_level = ConfigOptions::kSanityLevelExactMatch; - loose.sanity_level = ConfigOptions::kSanityLevelLooselyCompatible; - - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - cf_options_map["write_buffer_size"] = "1"; - ASSERT_OK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - - cf_options_map["unknown_option"] = "1"; - ASSERT_NOK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - cf_config_options.input_strings_escaped = false; - cf_config_options.ignore_unknown_options = true; - ASSERT_OK(GetColumnFamilyOptionsFromMap(cf_config_options, base_cf_opt, - cf_options_map, &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - loose, base_cf_opt, new_cf_opt, nullptr /* new_opt_map */)); - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - exact /* default for VerifyCFOptions */, base_cf_opt, new_cf_opt, nullptr)); - - DBOptions base_db_opt; - DBOptions new_db_opt; - ConfigOptions db_config_options(base_db_opt); - db_config_options.input_strings_escaped = false; - db_config_options.ignore_unknown_options = false; - ASSERT_OK(GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt)); - ASSERT_EQ(new_db_opt.create_if_missing, false); - ASSERT_EQ(new_db_opt.create_missing_column_families, true); - ASSERT_EQ(new_db_opt.error_if_exists, false); - ASSERT_EQ(new_db_opt.paranoid_checks, true); - ASSERT_EQ(new_db_opt.track_and_verify_wals_in_manifest, true); - ASSERT_EQ(new_db_opt.max_open_files, 32); - ASSERT_EQ(new_db_opt.max_total_wal_size, static_cast(33)); - ASSERT_EQ(new_db_opt.use_fsync, true); - ASSERT_EQ(new_db_opt.db_log_dir, "/db_log_dir"); - ASSERT_EQ(new_db_opt.wal_dir, "/wal_dir"); - ASSERT_EQ(new_db_opt.delete_obsolete_files_period_micros, - static_cast(34)); - ASSERT_EQ(new_db_opt.max_background_compactions, 35); - ASSERT_EQ(new_db_opt.max_background_flushes, 36); - ASSERT_EQ(new_db_opt.max_log_file_size, 37U); - ASSERT_EQ(new_db_opt.log_file_time_to_roll, 38U); - ASSERT_EQ(new_db_opt.keep_log_file_num, 39U); - ASSERT_EQ(new_db_opt.recycle_log_file_num, 5U); - ASSERT_EQ(new_db_opt.max_manifest_file_size, static_cast(40)); - ASSERT_EQ(new_db_opt.table_cache_numshardbits, 41); - ASSERT_EQ(new_db_opt.WAL_ttl_seconds, static_cast(43)); - ASSERT_EQ(new_db_opt.WAL_size_limit_MB, static_cast(44)); - ASSERT_EQ(new_db_opt.manifest_preallocation_size, 45U); - ASSERT_EQ(new_db_opt.allow_mmap_reads, true); - ASSERT_EQ(new_db_opt.allow_mmap_writes, false); - ASSERT_EQ(new_db_opt.use_direct_reads, false); - ASSERT_EQ(new_db_opt.use_direct_io_for_flush_and_compaction, false); - ASSERT_EQ(new_db_opt.is_fd_close_on_exec, true); - ASSERT_EQ(new_db_opt.stats_dump_period_sec, 46U); - ASSERT_EQ(new_db_opt.stats_persist_period_sec, 57U); - ASSERT_EQ(new_db_opt.persist_stats_to_disk, false); - ASSERT_EQ(new_db_opt.stats_history_buffer_size, 69U); - ASSERT_EQ(new_db_opt.advise_random_on_open, true); - ASSERT_EQ(new_db_opt.use_adaptive_mutex, false); - ASSERT_EQ(new_db_opt.compaction_readahead_size, 100); - ASSERT_EQ(new_db_opt.random_access_max_buffer_size, 3145728); - ASSERT_EQ(new_db_opt.writable_file_max_buffer_size, 314159); - ASSERT_EQ(new_db_opt.bytes_per_sync, static_cast(47)); - ASSERT_EQ(new_db_opt.wal_bytes_per_sync, static_cast(48)); - ASSERT_EQ(new_db_opt.strict_bytes_per_sync, true); - - db_options_map["max_open_files"] = "hello"; - ASSERT_NOK(GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(loose, base_db_opt, new_db_opt)); - - // unknow options should fail parsing without ignore_unknown_options = true - db_options_map["unknown_db_option"] = "1"; - ASSERT_NOK(GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); - - db_config_options.input_strings_escaped = false; - db_config_options.ignore_unknown_options = true; - ASSERT_OK(GetDBOptionsFromMap(db_config_options, base_db_opt, db_options_map, - &new_db_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(loose, base_db_opt, new_db_opt)); - ASSERT_NOK(RocksDBOptionsParser::VerifyDBOptions(exact, base_db_opt, new_db_opt)); -} - -TEST_F(OptionsOldApiTest, GetColumnFamilyOptionsFromStringTest) { - ColumnFamilyOptions base_cf_opt; - ColumnFamilyOptions new_cf_opt; - base_cf_opt.table_factory.reset(); - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, "", - &new_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=5", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 5U); - ASSERT_TRUE(new_cf_opt.table_factory == nullptr); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=6;", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 6U); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, " write_buffer_size = 7 ", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 7U); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, " write_buffer_size = 8 ; ", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 8U); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=9;max_write_buffer_number=10", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 9U); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 10); - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=11; max_write_buffer_number = 12 ;", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 11U); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 12); - // Wrong name "max_write_buffer_number_" - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=13;max_write_buffer_number_=14;", &new_cf_opt)); - ConfigOptions exact; - exact.sanity_level = ConfigOptions::kSanityLevelExactMatch; - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // Comparator from object registry - std::string kCompName = "reverse_comp"; - ObjectLibrary::Default()->AddFactory( - kCompName, - [](const std::string& /*name*/, - std::unique_ptr* /*guard*/, - std::string* /* errmsg */) { return ReverseBytewiseComparator(); }); - - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "comparator=" + kCompName + ";", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.comparator, ReverseBytewiseComparator()); - - // MergeOperator from object registry - std::unique_ptr bxo(new BytesXOROperator()); - std::string kMoName = bxo->Name(); - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "merge_operator=" + kMoName + ";", - &new_cf_opt)); - ASSERT_EQ(kMoName, std::string(new_cf_opt.merge_operator->Name())); - - // Wrong key/value pair - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=13;max_write_buffer_number;", &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // Error Paring value - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=13;max_write_buffer_number=;", &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // Missing option name - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=13; =100;", &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - const uint64_t kilo = 1024UL; - const uint64_t mega = 1024 * kilo; - const uint64_t giga = 1024 * mega; - const uint64_t tera = 1024 * giga; - - // Units (k) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "max_write_buffer_number=15K", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 15 * kilo); - // Units (m) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "max_write_buffer_number=16m;inplace_update_num_locks=17M", &new_cf_opt)); - ASSERT_EQ(new_cf_opt.max_write_buffer_number, 16 * mega); - ASSERT_EQ(new_cf_opt.inplace_update_num_locks, 17u * mega); - // Units (g) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=18g;prefix_extractor=capped:8;" - "arena_block_size=19G", - &new_cf_opt)); - - ASSERT_EQ(new_cf_opt.write_buffer_size, 18 * giga); - ASSERT_EQ(new_cf_opt.arena_block_size, 19 * giga); - ASSERT_TRUE(new_cf_opt.prefix_extractor.get() != nullptr); - ASSERT_EQ(new_cf_opt.prefix_extractor->AsString(), "rocksdb.CappedPrefix.8"); - - // Units (t) - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, "write_buffer_size=20t;arena_block_size=21T", - &new_cf_opt)); - ASSERT_EQ(new_cf_opt.write_buffer_size, 20 * tera); - ASSERT_EQ(new_cf_opt.arena_block_size, 21 * tera); - - // Nested block based table options - // Empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={};arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - // Non-empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_cache=1M;block_size=4;};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - // Last one - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_cache=1M;block_size=4;}", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - // Mismatch curly braces - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={{{block_size=4;};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // Unexpected chars after closing curly brace - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_size=4;}};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_size=4;}xdfa;" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_size=4;}xdfa", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // Invalid block based table option - ASSERT_NOK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={xx_block_size=4;}", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "optimize_filters_for_hits=true", - &new_cf_opt)); - ASSERT_OK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "optimize_filters_for_hits=false", - &new_cf_opt)); - - ASSERT_NOK(GetColumnFamilyOptionsFromString(config_options, base_cf_opt, - "optimize_filters_for_hits=junk", - &new_cf_opt)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(exact, base_cf_opt, new_cf_opt)); - - // Nested plain table options - // Empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "plain_table_factory={};arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - ASSERT_EQ(std::string(new_cf_opt.table_factory->Name()), "PlainTable"); - // Non-empty - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "plain_table_factory={user_key_len=66;bloom_bits_per_key=20;};" - "arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.table_factory != nullptr); - ASSERT_EQ(std::string(new_cf_opt.table_factory->Name()), "PlainTable"); - - // memtable factory - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "write_buffer_size=10;max_write_buffer_number=16;" - "memtable=skip_list:10;arena_block_size=1024", - &new_cf_opt)); - ASSERT_TRUE(new_cf_opt.memtable_factory != nullptr); - ASSERT_TRUE(new_cf_opt.memtable_factory->IsInstanceOf("SkipListFactory")); - - // blob cache - ASSERT_OK(GetColumnFamilyOptionsFromString( - config_options, base_cf_opt, - "blob_cache={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;high_pri_pool_ratio=0.5;};", - &new_cf_opt)); - ASSERT_NE(new_cf_opt.blob_cache, nullptr); - ASSERT_EQ(new_cf_opt.blob_cache->GetCapacity(), 1024UL * 1024UL); - ASSERT_EQ(static_cast(new_cf_opt.blob_cache.get()) - ->GetNumShardBits(), - 4); - ASSERT_EQ(new_cf_opt.blob_cache->HasStrictCapacityLimit(), true); - ASSERT_EQ(static_cast(new_cf_opt.blob_cache.get()) - ->GetHighPriPoolRatio(), - 0.5); -} - -TEST_F(OptionsTest, SliceTransformCreateFromString) { - std::shared_ptr transform = nullptr; - ConfigOptions config_options; - config_options.ignore_unsupported_options = false; - config_options.ignore_unknown_options = false; - - ASSERT_OK( - SliceTransform::CreateFromString(config_options, "fixed:31", &transform)); - ASSERT_NE(transform, nullptr); - ASSERT_FALSE(transform->IsInstanceOf("capped")); - ASSERT_TRUE(transform->IsInstanceOf("fixed")); - ASSERT_TRUE(transform->IsInstanceOf("rocksdb.FixedPrefix")); - ASSERT_EQ(transform->GetId(), "rocksdb.FixedPrefix.31"); - ASSERT_OK(SliceTransform::CreateFromString( - config_options, "rocksdb.FixedPrefix.42", &transform)); - ASSERT_NE(transform, nullptr); - ASSERT_EQ(transform->GetId(), "rocksdb.FixedPrefix.42"); - - ASSERT_OK(SliceTransform::CreateFromString(config_options, "capped:16", - &transform)); - ASSERT_NE(transform, nullptr); - ASSERT_FALSE(transform->IsInstanceOf("fixed")); - ASSERT_TRUE(transform->IsInstanceOf("capped")); - ASSERT_TRUE(transform->IsInstanceOf("rocksdb.CappedPrefix")); - ASSERT_EQ(transform->GetId(), "rocksdb.CappedPrefix.16"); - ASSERT_OK(SliceTransform::CreateFromString( - config_options, "rocksdb.CappedPrefix.42", &transform)); - ASSERT_NE(transform, nullptr); - ASSERT_EQ(transform->GetId(), "rocksdb.CappedPrefix.42"); - - ASSERT_OK(SliceTransform::CreateFromString(config_options, "rocksdb.Noop", - &transform)); - ASSERT_NE(transform, nullptr); - - ASSERT_NOK(SliceTransform::CreateFromString(config_options, - "fixed:21:invalid", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString(config_options, - "capped:21:invalid", &transform)); - ASSERT_NOK( - SliceTransform::CreateFromString(config_options, "fixed", &transform)); - ASSERT_NOK( - SliceTransform::CreateFromString(config_options, "capped", &transform)); - ASSERT_NOK( - SliceTransform::CreateFromString(config_options, "fixed:", &transform)); - ASSERT_NOK( - SliceTransform::CreateFromString(config_options, "capped:", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString( - config_options, "rocksdb.FixedPrefix:42", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString( - config_options, "rocksdb.CappedPrefix:42", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString( - config_options, "rocksdb.FixedPrefix", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString( - config_options, "rocksdb.CappedPrefix", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString( - config_options, "rocksdb.FixedPrefix.", &transform)); - ASSERT_NOK(SliceTransform::CreateFromString( - config_options, "rocksdb.CappedPrefix.", &transform)); - ASSERT_NOK( - SliceTransform::CreateFromString(config_options, "invalid", &transform)); - - ASSERT_OK(SliceTransform::CreateFromString( - config_options, "rocksdb.CappedPrefix.11", &transform)); - ASSERT_NE(transform, nullptr); - ASSERT_EQ(transform->GetId(), "rocksdb.CappedPrefix.11"); - ASSERT_TRUE(transform->IsInstanceOf("capped")); - ASSERT_TRUE(transform->IsInstanceOf("capped:11")); - ASSERT_TRUE(transform->IsInstanceOf("rocksdb.CappedPrefix")); - ASSERT_TRUE(transform->IsInstanceOf("rocksdb.CappedPrefix.11")); - ASSERT_FALSE(transform->IsInstanceOf("fixed")); - ASSERT_FALSE(transform->IsInstanceOf("fixed:11")); - ASSERT_FALSE(transform->IsInstanceOf("rocksdb.FixedPrefix")); - ASSERT_FALSE(transform->IsInstanceOf("rocksdb.FixedPrefix.11")); - - ASSERT_OK(SliceTransform::CreateFromString( - config_options, "rocksdb.FixedPrefix.11", &transform)); - ASSERT_TRUE(transform->IsInstanceOf("fixed")); - ASSERT_TRUE(transform->IsInstanceOf("fixed:11")); - ASSERT_TRUE(transform->IsInstanceOf("rocksdb.FixedPrefix")); - ASSERT_TRUE(transform->IsInstanceOf("rocksdb.FixedPrefix.11")); - ASSERT_FALSE(transform->IsInstanceOf("capped")); - ASSERT_FALSE(transform->IsInstanceOf("capped:11")); - ASSERT_FALSE(transform->IsInstanceOf("rocksdb.CappedPrefix")); - ASSERT_FALSE(transform->IsInstanceOf("rocksdb.CappedPrefix.11")); -} - -TEST_F(OptionsOldApiTest, GetBlockBasedTableOptionsFromString) { - BlockBasedTableOptions table_opt; - BlockBasedTableOptions new_opt; - ConfigOptions config_options; - config_options.input_strings_escaped = false; - config_options.ignore_unknown_options = false; - config_options.invoke_prepare_options = false; - config_options.ignore_unsupported_options = false; - - // make sure default values are overwritten by something else - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;index_type=kHashSearch;" - "checksum=kxxHash;no_block_cache=1;" - "block_cache=1M;block_cache_compressed=1k;block_size=1024;" - "block_size_deviation=8;block_restart_interval=4;" - "format_version=5;whole_key_filtering=1;" - "filter_policy=bloomfilter:4.567:false;", - &new_opt)); - ASSERT_TRUE(new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(new_opt.index_type, BlockBasedTableOptions::kHashSearch); - ASSERT_EQ(new_opt.checksum, ChecksumType::kxxHash); - ASSERT_TRUE(new_opt.no_block_cache); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); - ASSERT_EQ(new_opt.block_size, 1024UL); - ASSERT_EQ(new_opt.block_size_deviation, 8); - ASSERT_EQ(new_opt.block_restart_interval, 4); - ASSERT_EQ(new_opt.format_version, 5U); - ASSERT_EQ(new_opt.whole_key_filtering, true); - ASSERT_TRUE(new_opt.filter_policy != nullptr); - const BloomFilterPolicy* bfp = - dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(bfp->GetMillibitsPerKey(), 4567); - EXPECT_EQ(bfp->GetWholeBitsPerKey(), 5); - - // unknown option - ASSERT_NOK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;index_type=kBinarySearch;" - "bad_option=1", - &new_opt)); - ASSERT_EQ(static_cast(table_opt.cache_index_and_filter_blocks), - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.index_type, new_opt.index_type); - - // unrecognized index type - ASSERT_NOK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;index_type=kBinarySearchXX", &new_opt)); - ASSERT_EQ(table_opt.cache_index_and_filter_blocks, - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.index_type, new_opt.index_type); - - // unrecognized checksum type - ASSERT_NOK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "cache_index_and_filter_blocks=1;checksum=kxxHashXX", &new_opt)); - ASSERT_EQ(table_opt.cache_index_and_filter_blocks, - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.index_type, new_opt.index_type); - - // unrecognized filter policy name - ASSERT_NOK( - GetBlockBasedTableOptionsFromString(config_options, table_opt, - "cache_index_and_filter_blocks=1;" - "filter_policy=bloomfilterxx:4:true", - &new_opt)); - ASSERT_EQ(table_opt.cache_index_and_filter_blocks, - new_opt.cache_index_and_filter_blocks); - ASSERT_EQ(table_opt.filter_policy, new_opt.filter_policy); - - // Used to be rejected, now accepted - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, "filter_policy=bloomfilter:4", &new_opt)); - bfp = dynamic_cast(new_opt.filter_policy.get()); - EXPECT_EQ(bfp->GetMillibitsPerKey(), 4000); - EXPECT_EQ(bfp->GetWholeBitsPerKey(), 4); - - // Check block cache options are overwritten when specified - // in new format as a struct. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;high_pri_pool_ratio=0.5;};" - "block_cache_compressed={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;high_pri_pool_ratio=0.5;}", - &new_opt)); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - 4); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true); - ASSERT_EQ(std::dynamic_pointer_cast( - new_opt.block_cache)->GetHighPriPoolRatio(), 0.5); - - // Set only block cache capacity. Check other values are - // reset to default values. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={capacity=2M};" - "block_cache_compressed={capacity=2M}", - &new_opt)); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 2*1024UL*1024UL); - // Default values - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - GetDefaultCacheShardBits(new_opt.block_cache->GetCapacity())); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetHighPriPoolRatio(), - 0.5); - - // Set couple of block cache options. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={num_shard_bits=5;high_pri_pool_ratio=0.5;};" - "block_cache_compressed={num_shard_bits=5;" - "high_pri_pool_ratio=0.0;}", - &new_opt)); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 0); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - 5); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false); - ASSERT_EQ(std::dynamic_pointer_cast( - new_opt.block_cache)->GetHighPriPoolRatio(), 0.5); - - // Set couple of block cache options. - ASSERT_OK(GetBlockBasedTableOptionsFromString( - config_options, table_opt, - "block_cache={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;};" - "block_cache_compressed={capacity=1M;num_shard_bits=4;" - "strict_capacity_limit=true;}", - &new_opt)); - ASSERT_TRUE(new_opt.block_cache != nullptr); - ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetNumShardBits(), - 4); - ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true); - ASSERT_EQ(std::dynamic_pointer_cast(new_opt.block_cache) - ->GetHighPriPoolRatio(), - 0.5); -} - -TEST_F(OptionsOldApiTest, GetPlainTableOptionsFromString) { - PlainTableOptions table_opt; - PlainTableOptions new_opt; - // make sure default values are overwritten by something else - ConfigOptions config_options_from_string; - config_options_from_string.input_strings_escaped = false; - config_options_from_string.ignore_unknown_options = false; - config_options_from_string.invoke_prepare_options = false; - ASSERT_OK(GetPlainTableOptionsFromString( - config_options_from_string, table_opt, - "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;" - "index_sparseness=8;huge_page_tlb_size=4;encoding_type=kPrefix;" - "full_scan_mode=true;store_index_in_file=true", - &new_opt)); - ASSERT_EQ(new_opt.user_key_len, 66u); - ASSERT_EQ(new_opt.bloom_bits_per_key, 20); - ASSERT_EQ(new_opt.hash_table_ratio, 0.5); - ASSERT_EQ(new_opt.index_sparseness, 8); - ASSERT_EQ(new_opt.huge_page_tlb_size, 4); - ASSERT_EQ(new_opt.encoding_type, EncodingType::kPrefix); - ASSERT_TRUE(new_opt.full_scan_mode); - ASSERT_TRUE(new_opt.store_index_in_file); - - std::unordered_map opt_map; - ASSERT_OK(StringToMap( - "user_key_len=55;bloom_bits_per_key=10;huge_page_tlb_size=8;", &opt_map)); - ConfigOptions config_options_from_map; - config_options_from_map.input_strings_escaped = false; - config_options_from_map.ignore_unknown_options = false; - ASSERT_OK(GetPlainTableOptionsFromMap(config_options_from_map, table_opt, - opt_map, &new_opt)); - ASSERT_EQ(new_opt.user_key_len, 55u); - ASSERT_EQ(new_opt.bloom_bits_per_key, 10); - ASSERT_EQ(new_opt.huge_page_tlb_size, 8); - - // unknown option - ASSERT_NOK(GetPlainTableOptionsFromString( - config_options_from_string, table_opt, - "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;" - "bad_option=1", - &new_opt)); - - // unrecognized EncodingType - ASSERT_NOK(GetPlainTableOptionsFromString( - config_options_from_string, table_opt, - "user_key_len=66;bloom_bits_per_key=20;hash_table_ratio=0.5;" - "encoding_type=kPrefixXX", - &new_opt)); -} - -TEST_F(OptionsOldApiTest, GetOptionsFromStringTest) { - Options base_options, new_options; - base_options.write_buffer_size = 20; - base_options.min_write_buffer_number_to_merge = 15; - BlockBasedTableOptions block_based_table_options; - block_based_table_options.cache_index_and_filter_blocks = true; - base_options.table_factory.reset( - NewBlockBasedTableFactory(block_based_table_options)); - - // Register an Env with object registry. - ObjectLibrary::Default()->AddFactory( - "CustomEnvDefault", - [](const std::string& /*name*/, std::unique_ptr* /*env_guard*/, - std::string* /* errmsg */) { - static CustomEnv env(Env::Default()); - return &env; - }); - - ASSERT_OK(GetOptionsFromString( - base_options, - "write_buffer_size=10;max_write_buffer_number=16;" - "block_based_table_factory={block_cache=1M;block_size=4;};" - "compression_opts=4:5:6;create_if_missing=true;max_open_files=1;" - "bottommost_compression_opts=5:6:7;create_if_missing=true;max_open_files=" - "1;" - "rate_limiter_bytes_per_sec=1024;env=CustomEnvDefault", - &new_options)); - - ASSERT_EQ(new_options.compression_opts.window_bits, 4); - ASSERT_EQ(new_options.compression_opts.level, 5); - ASSERT_EQ(new_options.compression_opts.strategy, 6); - ASSERT_EQ(new_options.compression_opts.max_dict_bytes, 0u); - ASSERT_EQ(new_options.compression_opts.zstd_max_train_bytes, 0u); - ASSERT_EQ(new_options.compression_opts.parallel_threads, 1u); - ASSERT_EQ(new_options.compression_opts.enabled, false); - ASSERT_EQ(new_options.compression_opts.use_zstd_dict_trainer, true); - ASSERT_EQ(new_options.bottommost_compression, kDisableCompressionOption); - ASSERT_EQ(new_options.bottommost_compression_opts.window_bits, 5); - ASSERT_EQ(new_options.bottommost_compression_opts.level, 6); - ASSERT_EQ(new_options.bottommost_compression_opts.strategy, 7); - ASSERT_EQ(new_options.bottommost_compression_opts.max_dict_bytes, 0u); - ASSERT_EQ(new_options.bottommost_compression_opts.zstd_max_train_bytes, 0u); - ASSERT_EQ(new_options.bottommost_compression_opts.parallel_threads, 1u); - ASSERT_EQ(new_options.bottommost_compression_opts.enabled, false); - ASSERT_EQ(new_options.bottommost_compression_opts.use_zstd_dict_trainer, - true); - ASSERT_EQ(new_options.write_buffer_size, 10U); - ASSERT_EQ(new_options.max_write_buffer_number, 16); - - auto new_block_based_table_options = - new_options.table_factory->GetOptions(); - ASSERT_NE(new_block_based_table_options, nullptr); - ASSERT_EQ(new_block_based_table_options->block_cache->GetCapacity(), - 1U << 20); - ASSERT_EQ(new_block_based_table_options->block_size, 4U); - // don't overwrite block based table options - ASSERT_TRUE(new_block_based_table_options->cache_index_and_filter_blocks); - - ASSERT_EQ(new_options.create_if_missing, true); - ASSERT_EQ(new_options.max_open_files, 1); - ASSERT_TRUE(new_options.rate_limiter.get() != nullptr); - Env* newEnv = new_options.env; - ASSERT_OK(Env::CreateFromString({}, "CustomEnvDefault", &newEnv)); - ASSERT_EQ(newEnv, new_options.env); -} - -TEST_F(OptionsOldApiTest, DBOptionsSerialization) { - Options base_options, new_options; - Random rnd(301); - - // Phase 1: Make big change in base_options - test::RandomInitDBOptions(&base_options, &rnd); - - // Phase 2: obtain a string from base_option - std::string base_options_file_content; - ASSERT_OK(GetStringFromDBOptions(&base_options_file_content, base_options)); - - // Phase 3: Set new_options from the derived string and expect - // new_options == base_options - const DBOptions base_db_options; - ConfigOptions db_config_options(base_db_options); - db_config_options.input_strings_escaped = false; - db_config_options.ignore_unknown_options = false; - ASSERT_OK(GetDBOptionsFromString(db_config_options, base_db_options, - base_options_file_content, &new_options)); - ConfigOptions verify_db_config_options; - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(verify_db_config_options, - base_options, new_options)); -} - -TEST_F(OptionsOldApiTest, ColumnFamilyOptionsSerialization) { - Options options; - ColumnFamilyOptions base_opt, new_opt; - Random rnd(302); - // Phase 1: randomly assign base_opt - // custom type options - test::RandomInitCFOptions(&base_opt, options, &rnd); - - // Phase 2: obtain a string from base_opt - std::string base_options_file_content; - ASSERT_OK( - GetStringFromColumnFamilyOptions(&base_options_file_content, base_opt)); - - // Phase 3: Set new_opt from the derived string and expect - // new_opt == base_opt - ConfigOptions cf_config_options; - cf_config_options.input_strings_escaped = false; - cf_config_options.ignore_unknown_options = false; - ASSERT_OK( - GetColumnFamilyOptionsFromString(cf_config_options, ColumnFamilyOptions(), - base_options_file_content, &new_opt)); - ConfigOptions verify_cf_config_options; - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(verify_cf_config_options, - base_opt, new_opt)); - if (base_opt.compaction_filter) { - delete base_opt.compaction_filter; - } -} - -class OptionsParserTest : public testing::Test { - public: - OptionsParserTest() { fs_.reset(new test::StringFS(FileSystem::Default())); } - - protected: - std::shared_ptr fs_; -}; - -TEST_F(OptionsParserTest, Comment) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[ DBOptions ]\n" - " # note that we don't support space around \"=\"\n" - " max_open_files=12345;\n" - " max_background_flushes=301 # comment after a statement is fine\n" - " # max_background_flushes=1000 # this line would be ignored\n" - " # max_background_compactions=2000 # so does this one\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - "[CFOptions \"default\"] # column family must be specified\n" - " # in the correct order\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_OK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); - - ConfigOptions exact; - exact.input_strings_escaped = false; - exact.sanity_level = ConfigOptions::kSanityLevelExactMatch; - ASSERT_OK( - RocksDBOptionsParser::VerifyDBOptions(exact, *parser.db_opt(), db_opt)); - ASSERT_EQ(parser.NumColumnFamilies(), 1U); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - exact, *parser.GetCFOptions("default"), cf_opt)); -} - -TEST_F(OptionsParserTest, ExtraSpace) { - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[ Version ]\n" - " rocksdb_version = 3.14.0 \n" - " options_file_version=1 # some comment\n" - "[DBOptions ] # some comment\n" - "max_open_files=12345 \n" - " max_background_flushes = 301 \n" - " max_total_wal_size = 1024 # keep_log_file_num=1000\n" - " [CFOptions \"default\" ]\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_OK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); -} - -TEST_F(OptionsParserTest, MissingDBOptions) { - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[CFOptions \"default\"]\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_NOK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); - ; -} - -TEST_F(OptionsParserTest, DoubleDBOptions) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[DBOptions]\n" - " max_open_files=12345\n" - " max_background_flushes=301\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - "[DBOptions]\n" - "[CFOptions \"default\"]\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_NOK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); -} - -TEST_F(OptionsParserTest, NoDefaultCFOptions) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[DBOptions]\n" - " max_open_files=12345\n" - " max_background_flushes=301\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - "[CFOptions \"something_else\"]\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_NOK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); -} - -TEST_F(OptionsParserTest, DefaultCFOptionsMustBeTheFirst) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[DBOptions]\n" - " max_open_files=12345\n" - " max_background_flushes=301\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - "[CFOptions \"something_else\"]\n" - " # if a section is blank, we will use the default\n" - "[CFOptions \"default\"]\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_NOK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); -} - -TEST_F(OptionsParserTest, DuplicateCFOptions) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[DBOptions]\n" - " max_open_files=12345\n" - " max_background_flushes=301\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - "[CFOptions \"default\"]\n" - "[CFOptions \"something_else\"]\n" - "[CFOptions \"something_else\"]\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_NOK( - parser.Parse(kTestFileName, fs_.get(), false, 4096 /* readahead_size */)); -} - -TEST_F(OptionsParserTest, IgnoreUnknownOptions) { - for (int case_id = 0; case_id < 5; case_id++) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string version_string; - bool should_ignore = true; - if (case_id == 0) { - // same version - should_ignore = false; - version_string = std::to_string(ROCKSDB_MAJOR) + "." + - std::to_string(ROCKSDB_MINOR) + ".0"; - } else if (case_id == 1) { - // higher minor version - should_ignore = true; - version_string = std::to_string(ROCKSDB_MAJOR) + "." + - std::to_string(ROCKSDB_MINOR + 1) + ".0"; - } else if (case_id == 2) { - // higher major version. - should_ignore = true; - version_string = std::to_string(ROCKSDB_MAJOR + 1) + ".0.0"; - } else if (case_id == 3) { - // lower minor version -#if ROCKSDB_MINOR == 0 - continue; -#else - version_string = std::to_string(ROCKSDB_MAJOR) + "." + - std::to_string(ROCKSDB_MINOR - 1) + ".0"; - should_ignore = false; -#endif - } else { - // lower major version - should_ignore = false; - version_string = std::to_string(ROCKSDB_MAJOR - 1) + "." + - std::to_string(ROCKSDB_MINOR) + ".0"; - } - - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=" + - version_string + - "\n" - " options_file_version=1\n" - "[DBOptions]\n" - " max_open_files=12345\n" - " max_background_flushes=301\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - " unknown_db_option1=321\n" - " unknown_db_option2=false\n" - "[CFOptions \"default\"]\n" - " unknown_cf_option1=hello\n" - "[CFOptions \"something_else\"]\n" - " unknown_cf_option2=world\n" - " # if a section is blank, we will use the default\n"; - - const std::string kTestFileName = "test-rocksdb-options.ini"; - auto s = fs_->FileExists(kTestFileName, IOOptions(), nullptr); - ASSERT_TRUE(s.ok() || s.IsNotFound()); - if (s.ok()) { - ASSERT_OK(fs_->DeleteFile(kTestFileName, IOOptions(), nullptr)); - } - ASSERT_OK(fs_->WriteToNewFile(kTestFileName, options_file_content)); - RocksDBOptionsParser parser; - ASSERT_NOK(parser.Parse(kTestFileName, fs_.get(), false, - 4096 /* readahead_size */)); - if (should_ignore) { - ASSERT_OK(parser.Parse(kTestFileName, fs_.get(), - true /* ignore_unknown_options */, - 4096 /* readahead_size */)); - } else { - ASSERT_NOK(parser.Parse(kTestFileName, fs_.get(), - true /* ignore_unknown_options */, - 4096 /* readahead_size */)); - } - } -} - -TEST_F(OptionsParserTest, ParseVersion) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; - - std::string file_template = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.13.1\n" - " options_file_version=%s\n" - "[DBOptions]\n" - "[CFOptions \"default\"]\n"; - const int kLength = 1000; - char buffer[kLength]; - RocksDBOptionsParser parser; - - const std::vector invalid_versions = { - "a.b.c", "3.2.2b", "3.-12", "3. 1", // only digits and dots are allowed - "1.2.3.4", - "1.2.3" // can only contains at most one dot. - "0", // options_file_version must be at least one - "3..2", - ".", ".1.2", // must have at least one digit before each dot - "1.2.", "1.", "2.34."}; // must have at least one digit after each dot - for (auto iv : invalid_versions) { - snprintf(buffer, kLength - 1, file_template.c_str(), iv.c_str()); - - parser.Reset(); - ASSERT_OK(fs_->WriteToNewFile(iv, buffer)); - ASSERT_NOK(parser.Parse(iv, fs_.get(), false, 0 /* readahead_size */)); - } - - const std::vector valid_versions = { - "1.232", "100", "3.12", "1", "12.3 ", " 1.25 "}; - for (auto vv : valid_versions) { - snprintf(buffer, kLength - 1, file_template.c_str(), vv.c_str()); - parser.Reset(); - ASSERT_OK(fs_->WriteToNewFile(vv, buffer)); - ASSERT_OK(parser.Parse(vv, fs_.get(), false, 0 /* readahead_size */)); - } -} - -void VerifyCFPointerTypedOptions( - ColumnFamilyOptions* base_cf_opt, const ColumnFamilyOptions* new_cf_opt, - const std::unordered_map* new_cf_opt_map) { - std::string name_buffer; - ConfigOptions config_options; - config_options.input_strings_escaped = false; - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_options, *base_cf_opt, - *new_cf_opt, new_cf_opt_map)); - - // change the name of merge operator back-and-forth - { - auto* merge_operator = base_cf_opt->merge_operator - ->CheckedCast(); - if (merge_operator != nullptr) { - name_buffer = merge_operator->Name(); - // change the name and expect non-ok status - merge_operator->SetName("some-other-name"); - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - // change the name back and expect ok status - merge_operator->SetName(name_buffer); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - } - } - - // change the name of the compaction filter factory back-and-forth - { - auto* compaction_filter_factory = - base_cf_opt->compaction_filter_factory - ->CheckedCast(); - if (compaction_filter_factory != nullptr) { - name_buffer = compaction_filter_factory->Name(); - // change the name and expect non-ok status - compaction_filter_factory->SetName("some-other-name"); - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - // change the name back and expect ok status - compaction_filter_factory->SetName(name_buffer); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - } - } - - // test by setting compaction_filter to nullptr - { - auto* tmp_compaction_filter = base_cf_opt->compaction_filter; - if (tmp_compaction_filter != nullptr) { - base_cf_opt->compaction_filter = nullptr; - // set compaction_filter to nullptr and expect non-ok status - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - // set the value back and expect ok status - base_cf_opt->compaction_filter = tmp_compaction_filter; - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - } - } - - // test by setting table_factory to nullptr - { - auto tmp_table_factory = base_cf_opt->table_factory; - if (tmp_table_factory != nullptr) { - base_cf_opt->table_factory.reset(); - // set table_factory to nullptr and expect non-ok status - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - // set the value back and expect ok status - base_cf_opt->table_factory = tmp_table_factory; - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - } - } - - // test by setting memtable_factory to nullptr - { - auto tmp_memtable_factory = base_cf_opt->memtable_factory; - if (tmp_memtable_factory != nullptr) { - base_cf_opt->memtable_factory.reset(); - // set memtable_factory to nullptr and expect non-ok status - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - // set the value back and expect ok status - base_cf_opt->memtable_factory = tmp_memtable_factory; - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - config_options, *base_cf_opt, *new_cf_opt, new_cf_opt_map)); - } - } -} - -TEST_F(OptionsParserTest, Readahead) { - DBOptions base_db_opt; - std::vector base_cf_opts; - base_cf_opts.emplace_back(); - base_cf_opts.emplace_back(); - - std::string one_mb_string = std::string(1024 * 1024, 'x'); - std::vector cf_names = {"default", one_mb_string}; - const std::string kOptionsFileName = "test-persisted-options.ini"; - - ASSERT_OK(PersistRocksDBOptions(base_db_opt, cf_names, base_cf_opts, - kOptionsFileName, fs_.get())); - - uint64_t file_size = 0; - ASSERT_OK( - fs_->GetFileSize(kOptionsFileName, IOOptions(), &file_size, nullptr)); - assert(file_size > 0); - - RocksDBOptionsParser parser; - - fs_->num_seq_file_read_ = 0; - size_t readahead_size = 128 * 1024; - - ASSERT_OK(parser.Parse(kOptionsFileName, fs_.get(), false, readahead_size)); - ASSERT_EQ(fs_->num_seq_file_read_.load(), - (file_size - 1) / readahead_size + 1); - - fs_->num_seq_file_read_.store(0); - readahead_size = 1024 * 1024; - ASSERT_OK(parser.Parse(kOptionsFileName, fs_.get(), false, readahead_size)); - ASSERT_EQ(fs_->num_seq_file_read_.load(), - (file_size - 1) / readahead_size + 1); - - // Tiny readahead. 8 KB is read each time. - fs_->num_seq_file_read_.store(0); - ASSERT_OK( - parser.Parse(kOptionsFileName, fs_.get(), false, 1 /* readahead_size */)); - ASSERT_GE(fs_->num_seq_file_read_.load(), file_size / (8 * 1024)); - ASSERT_LT(fs_->num_seq_file_read_.load(), file_size / (8 * 1024) * 2); - - // Disable readahead means 512KB readahead. - fs_->num_seq_file_read_.store(0); - ASSERT_OK( - parser.Parse(kOptionsFileName, fs_.get(), false, 0 /* readahead_size */)); - ASSERT_GE(fs_->num_seq_file_read_.load(), (file_size - 1) / (512 * 1024) + 1); -} - -TEST_F(OptionsParserTest, DumpAndParse) { - DBOptions base_db_opt; - std::vector base_cf_opts; - std::vector cf_names = {"default", "cf1", "cf2", "cf3", - "c:f:4:4:4" - "p\\i\\k\\a\\chu\\\\\\", - "###rocksdb#1-testcf#2###"}; - const int num_cf = static_cast(cf_names.size()); - Random rnd(302); - test::RandomInitDBOptions(&base_db_opt, &rnd); - base_db_opt.db_log_dir += "/#odd #but #could #happen #path #/\\\\#OMG"; - - BlockBasedTableOptions special_bbto; - special_bbto.cache_index_and_filter_blocks = true; - special_bbto.block_size = 999999; - - for (int c = 0; c < num_cf; ++c) { - ColumnFamilyOptions cf_opt; - Random cf_rnd(0xFB + c); - test::RandomInitCFOptions(&cf_opt, base_db_opt, &cf_rnd); - if (c < 4) { - cf_opt.prefix_extractor.reset(test::RandomSliceTransform(&rnd, c)); - } - if (c < 3) { - cf_opt.table_factory.reset(test::RandomTableFactory(&rnd, c)); - } else if (c == 4) { - cf_opt.table_factory.reset(NewBlockBasedTableFactory(special_bbto)); - } else if (c == 5) { - // A table factory that doesn't support deserialization should be - // supported. - cf_opt.table_factory.reset(new UnregisteredTableFactory()); - } - base_cf_opts.emplace_back(cf_opt); - } - - const std::string kOptionsFileName = "test-persisted-options.ini"; - // Use default for escaped(true), unknown(false) and check (exact) - ConfigOptions config_options; - ASSERT_OK(PersistRocksDBOptions(base_db_opt, cf_names, base_cf_opts, - kOptionsFileName, fs_.get())); - - RocksDBOptionsParser parser; - ASSERT_OK(parser.Parse(config_options, kOptionsFileName, fs_.get())); - - // Make sure block-based table factory options was deserialized correctly - std::shared_ptr ttf = (*parser.cf_opts())[4].table_factory; - ASSERT_EQ(TableFactory::kBlockBasedTableName(), std::string(ttf->Name())); - const auto parsed_bbto = ttf->GetOptions(); - ASSERT_NE(parsed_bbto, nullptr); - ASSERT_EQ(special_bbto.block_size, parsed_bbto->block_size); - ASSERT_EQ(special_bbto.cache_index_and_filter_blocks, - parsed_bbto->cache_index_and_filter_blocks); - - ASSERT_OK(RocksDBOptionsParser::VerifyRocksDBOptionsFromFile( - config_options, base_db_opt, cf_names, base_cf_opts, kOptionsFileName, - fs_.get())); - - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions( - config_options, *parser.db_opt(), base_db_opt)); - for (int c = 0; c < num_cf; ++c) { - const auto* cf_opt = parser.GetCFOptions(cf_names[c]); - ASSERT_NE(cf_opt, nullptr); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - config_options, base_cf_opts[c], *cf_opt, - &(parser.cf_opt_maps()->at(c)))); - } - - // Further verify pointer-typed options - for (int c = 0; c < num_cf; ++c) { - const auto* cf_opt = parser.GetCFOptions(cf_names[c]); - ASSERT_NE(cf_opt, nullptr); - VerifyCFPointerTypedOptions(&base_cf_opts[c], cf_opt, - &(parser.cf_opt_maps()->at(c))); - } - - ASSERT_EQ(parser.GetCFOptions("does not exist"), nullptr); - - base_db_opt.max_open_files++; - ASSERT_NOK(RocksDBOptionsParser::VerifyRocksDBOptionsFromFile( - config_options, base_db_opt, cf_names, base_cf_opts, kOptionsFileName, - fs_.get())); - - for (int c = 0; c < num_cf; ++c) { - if (base_cf_opts[c].compaction_filter) { - delete base_cf_opts[c].compaction_filter; - } - } -} - -TEST_F(OptionsParserTest, DifferentDefault) { - const std::string kOptionsFileName = "test-persisted-options.ini"; - - ColumnFamilyOptions cf_level_opts; - ASSERT_EQ(CompactionPri::kMinOverlappingRatio, cf_level_opts.compaction_pri); - cf_level_opts.OptimizeLevelStyleCompaction(); - - ColumnFamilyOptions cf_univ_opts; - cf_univ_opts.OptimizeUniversalStyleCompaction(); - - ASSERT_OK(PersistRocksDBOptions(DBOptions(), {"default", "universal"}, - {cf_level_opts, cf_univ_opts}, - kOptionsFileName, fs_.get())); - - RocksDBOptionsParser parser; - ASSERT_OK(parser.Parse(kOptionsFileName, fs_.get(), false, - 4096 /* readahead_size */)); - - { - Options old_default_opts; - old_default_opts.OldDefaults(); - ASSERT_EQ(10 * 1048576, old_default_opts.max_bytes_for_level_base); - ASSERT_EQ(5000, old_default_opts.max_open_files); - ASSERT_EQ(2 * 1024U * 1024U, old_default_opts.delayed_write_rate); - ASSERT_EQ(WALRecoveryMode::kTolerateCorruptedTailRecords, - old_default_opts.wal_recovery_mode); - } - { - Options old_default_opts; - old_default_opts.OldDefaults(4, 6); - ASSERT_EQ(10 * 1048576, old_default_opts.max_bytes_for_level_base); - ASSERT_EQ(5000, old_default_opts.max_open_files); - } - { - Options old_default_opts; - old_default_opts.OldDefaults(4, 7); - ASSERT_NE(10 * 1048576, old_default_opts.max_bytes_for_level_base); - ASSERT_NE(4, old_default_opts.table_cache_numshardbits); - ASSERT_EQ(5000, old_default_opts.max_open_files); - ASSERT_EQ(2 * 1024U * 1024U, old_default_opts.delayed_write_rate); - } - { - ColumnFamilyOptions old_default_cf_opts; - old_default_cf_opts.OldDefaults(); - ASSERT_EQ(2 * 1048576, old_default_cf_opts.target_file_size_base); - ASSERT_EQ(4 << 20, old_default_cf_opts.write_buffer_size); - ASSERT_EQ(2 * 1048576, old_default_cf_opts.target_file_size_base); - ASSERT_EQ(0, old_default_cf_opts.soft_pending_compaction_bytes_limit); - ASSERT_EQ(0, old_default_cf_opts.hard_pending_compaction_bytes_limit); - ASSERT_EQ(CompactionPri::kByCompensatedSize, - old_default_cf_opts.compaction_pri); - } - { - ColumnFamilyOptions old_default_cf_opts; - old_default_cf_opts.OldDefaults(4, 6); - ASSERT_EQ(2 * 1048576, old_default_cf_opts.target_file_size_base); - ASSERT_EQ(CompactionPri::kByCompensatedSize, - old_default_cf_opts.compaction_pri); - } - { - ColumnFamilyOptions old_default_cf_opts; - old_default_cf_opts.OldDefaults(4, 7); - ASSERT_NE(2 * 1048576, old_default_cf_opts.target_file_size_base); - ASSERT_EQ(CompactionPri::kByCompensatedSize, - old_default_cf_opts.compaction_pri); - } - { - Options old_default_opts; - old_default_opts.OldDefaults(5, 1); - ASSERT_EQ(2 * 1024U * 1024U, old_default_opts.delayed_write_rate); - } - { - Options old_default_opts; - old_default_opts.OldDefaults(5, 2); - ASSERT_EQ(16 * 1024U * 1024U, old_default_opts.delayed_write_rate); - ASSERT_TRUE(old_default_opts.compaction_pri == - CompactionPri::kByCompensatedSize); - } - { - Options old_default_opts; - old_default_opts.OldDefaults(5, 18); - ASSERT_TRUE(old_default_opts.compaction_pri == - CompactionPri::kByCompensatedSize); - } - - Options small_opts; - small_opts.OptimizeForSmallDb(); - ASSERT_EQ(2 << 20, small_opts.write_buffer_size); - ASSERT_EQ(5000, small_opts.max_open_files); -} - -class OptionsSanityCheckTest : public OptionsParserTest, - public ::testing::WithParamInterface { - protected: - ConfigOptions config_options_; - - public: - OptionsSanityCheckTest() { - config_options_.ignore_unknown_options = false; - config_options_.ignore_unsupported_options = GetParam(); - config_options_.input_strings_escaped = true; - } - - protected: - Status SanityCheckOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts, - ConfigOptions::SanityLevel level) { - config_options_.sanity_level = level; - return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile( - config_options_, db_opts, {"default"}, {cf_opts}, kOptionsFileName, - fs_.get()); - } - - Status SanityCheckCFOptions(const ColumnFamilyOptions& cf_opts, - ConfigOptions::SanityLevel level) { - return SanityCheckOptions(DBOptions(), cf_opts, level); - } - - void SanityCheckCFOptions(const ColumnFamilyOptions& opts, bool exact) { - ASSERT_OK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - if (exact) { - ASSERT_OK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } else { - ASSERT_NOK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } - } - - Status SanityCheckDBOptions(const DBOptions& db_opts, - ConfigOptions::SanityLevel level) { - return SanityCheckOptions(db_opts, ColumnFamilyOptions(), level); - } - - void SanityCheckDBOptions(const DBOptions& opts, bool exact) { - ASSERT_OK(SanityCheckDBOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckDBOptions(opts, ConfigOptions::kSanityLevelNone)); - if (exact) { - ASSERT_OK( - SanityCheckDBOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } else { - ASSERT_NOK( - SanityCheckDBOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } - } - - Status PersistOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) { - Status s = fs_->DeleteFile(kOptionsFileName, IOOptions(), nullptr); - if (!s.ok()) { - return s; - } - return PersistRocksDBOptions(db_opts, {"default"}, {cf_opts}, - kOptionsFileName, fs_.get()); - } - - Status PersistCFOptions(const ColumnFamilyOptions& cf_opts) { - return PersistOptions(DBOptions(), cf_opts); - } - - Status PersistDBOptions(const DBOptions& db_opts) { - return PersistOptions(db_opts, ColumnFamilyOptions()); - } - - const std::string kOptionsFileName = "OPTIONS"; -}; - -TEST_P(OptionsSanityCheckTest, CFOptionsSanityCheck) { - ColumnFamilyOptions opts; - Random rnd(301); - - // default ColumnFamilyOptions - { - ASSERT_OK(PersistCFOptions(opts)); - ASSERT_OK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } - - // prefix_extractor - { - // Okay to change prefix_extractor form nullptr to non-nullptr - ASSERT_EQ(opts.prefix_extractor.get(), nullptr); - opts.prefix_extractor.reset(NewCappedPrefixTransform(10)); - ASSERT_OK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - ASSERT_OK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - - // use same prefix extractor but with different parameter - opts.prefix_extractor.reset(NewCappedPrefixTransform(15)); - // expect pass only in - // ConfigOptions::kSanityLevelLooselyCompatible - ASSERT_NOK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - ASSERT_OK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // repeat the test with FixedPrefixTransform - opts.prefix_extractor.reset(NewFixedPrefixTransform(10)); - ASSERT_NOK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - ASSERT_OK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // persist the change of prefix_extractor - ASSERT_OK(PersistCFOptions(opts)); - ASSERT_OK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - - // use same prefix extractor but with different parameter - opts.prefix_extractor.reset(NewFixedPrefixTransform(15)); - // expect pass only in - // ConfigOptions::kSanityLevelLooselyCompatible - SanityCheckCFOptions(opts, false); - - // Change prefix extractor from non-nullptr to nullptr - opts.prefix_extractor.reset(); - // expect pass as it's safe to change prefix_extractor - // from non-null to null - ASSERT_OK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - } - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - - // table_factory - { - for (int tb = 0; tb <= 2; ++tb) { - // change the table factory - opts.table_factory.reset(test::RandomTableFactory(&rnd, tb)); - ASSERT_NOK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - ASSERT_OK( - SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } - } - - // merge_operator - { - // Test when going from nullptr -> merge operator - opts.merge_operator.reset(test::RandomMergeOperator(&rnd)); - ASSERT_OK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - SanityCheckCFOptions(opts, config_options_.ignore_unsupported_options); - - for (int test = 0; test < 5; ++test) { - // change the merge operator - opts.merge_operator.reset(test::RandomMergeOperator(&rnd)); - ASSERT_NOK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - SanityCheckCFOptions(opts, config_options_.ignore_unsupported_options); - } - - // Test when going from merge operator -> nullptr - opts.merge_operator = nullptr; - ASSERT_NOK(SanityCheckCFOptions( - opts, ConfigOptions::kSanityLevelLooselyCompatible)); - ASSERT_OK(SanityCheckCFOptions(opts, ConfigOptions::kSanityLevelNone)); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - SanityCheckCFOptions(opts, true); - } - - // compaction_filter - { - for (int test = 0; test < 5; ++test) { - // change the compaction filter - opts.compaction_filter = test::RandomCompactionFilter(&rnd); - SanityCheckCFOptions(opts, false); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - SanityCheckCFOptions(opts, config_options_.ignore_unsupported_options); - delete opts.compaction_filter; - opts.compaction_filter = nullptr; - } - } - - // compaction_filter_factory - { - for (int test = 0; test < 5; ++test) { - // change the compaction filter factory - opts.compaction_filter_factory.reset( - test::RandomCompactionFilterFactory(&rnd)); - SanityCheckCFOptions(opts, false); - - // persist the change - ASSERT_OK(PersistCFOptions(opts)); - SanityCheckCFOptions(opts, config_options_.ignore_unsupported_options); - } - } -} - -TEST_P(OptionsSanityCheckTest, DBOptionsSanityCheck) { - DBOptions opts; - Random rnd(301); - - // default DBOptions - { - ASSERT_OK(PersistDBOptions(opts)); - ASSERT_OK( - SanityCheckDBOptions(opts, ConfigOptions::kSanityLevelExactMatch)); - } - - // File checksum generator - { - class MockFileChecksumGenFactory : public FileChecksumGenFactory { - public: - static const char* kClassName() { return "Mock"; } - const char* Name() const override { return kClassName(); } - std::unique_ptr CreateFileChecksumGenerator( - const FileChecksumGenContext& /*context*/) override { - return nullptr; - } - }; - - // Okay to change file_checksum_gen_factory form nullptr to non-nullptr - ASSERT_EQ(opts.file_checksum_gen_factory.get(), nullptr); - opts.file_checksum_gen_factory.reset(new MockFileChecksumGenFactory()); - - // persist the change - ASSERT_OK(PersistDBOptions(opts)); - SanityCheckDBOptions(opts, config_options_.ignore_unsupported_options); - - // Change file_checksum_gen_factory from non-nullptr to nullptr - opts.file_checksum_gen_factory.reset(); - // expect pass as it's safe to change file_checksum_gen_factory - // from non-null to null - SanityCheckDBOptions(opts, false); - } - // persist the change - ASSERT_OK(PersistDBOptions(opts)); - ASSERT_OK(SanityCheckDBOptions(opts, ConfigOptions::kSanityLevelExactMatch)); -} - -namespace { -bool IsEscapedString(const std::string& str) { - for (size_t i = 0; i < str.size(); ++i) { - if (str[i] == '\\') { - // since we already handle those two consecutive '\'s in - // the next if-then branch, any '\' appear at the end - // of an escaped string in such case is not valid. - if (i == str.size() - 1) { - return false; - } - if (str[i + 1] == '\\') { - // if there're two consecutive '\'s, skip the second one. - i++; - continue; - } - switch (str[i + 1]) { - case ':': - case '\\': - case '#': - continue; - default: - // if true, '\' together with str[i + 1] is not a valid escape. - if (UnescapeChar(str[i + 1]) == str[i + 1]) { - return false; - } - } - } else if (isSpecialChar(str[i]) && (i == 0 || str[i - 1] != '\\')) { - return false; - } - } - return true; -} -} // namespace - -TEST_F(OptionsParserTest, IntegerParsing) { - ASSERT_EQ(ParseUint64("18446744073709551615"), 18446744073709551615U); - ASSERT_EQ(ParseUint32("4294967295"), 4294967295U); - ASSERT_EQ(ParseSizeT("18446744073709551615"), 18446744073709551615U); - ASSERT_EQ(ParseInt64("9223372036854775807"), 9223372036854775807); - ASSERT_EQ(ParseInt64("-9223372036854775808"), - std::numeric_limits::min()); - ASSERT_EQ(ParseInt32("2147483647"), 2147483647); - ASSERT_EQ(ParseInt32("-2147483648"), std::numeric_limits::min()); - ASSERT_EQ(ParseInt("-32767"), -32767); - ASSERT_EQ(ParseDouble("-1.234567"), -1.234567); -} - -TEST_F(OptionsParserTest, EscapeOptionString) { - ASSERT_EQ(UnescapeOptionString( - "This is a test string with \\# \\: and \\\\ escape chars."), - "This is a test string with # : and \\ escape chars."); - - ASSERT_EQ( - EscapeOptionString("This is a test string with # : and \\ escape chars."), - "This is a test string with \\# \\: and \\\\ escape chars."); - - std::string readible_chars = - "A String like this \"1234567890-=_)(*&^%$#@!ertyuiop[]{POIU" - "YTREWQasdfghjkl;':LKJHGFDSAzxcvbnm,.?>" - " -void TestOptInfo(const ConfigOptions& config_options, OptionType opt_type, - T* base, T* comp) { - std::string result; - OptionTypeInfo opt_info(0, opt_type); - ASSERT_FALSE(opt_info.AreEqual(config_options, "base", base, comp, &result)); - ASSERT_EQ(result, "base"); - ASSERT_NE(*base, *comp); - TestAndCompareOption(config_options, opt_info, "base", base, comp); - ASSERT_EQ(*base, *comp); -} - -class OptionTypeInfoTest : public testing::Test {}; - -TEST_F(OptionTypeInfoTest, BasicTypes) { - ConfigOptions config_options; - { - bool a = true, b = false; - TestOptInfo(config_options, OptionType::kBoolean, &a, &b); - } - { - int a = 100, b = 200; - TestOptInfo(config_options, OptionType::kInt, &a, &b); - } - { - int32_t a = 100, b = 200; - TestOptInfo(config_options, OptionType::kInt32T, &a, &b); - } - { - int64_t a = 100, b = 200; - TestOptInfo(config_options, OptionType::kInt64T, &a, &b); - } - { - unsigned int a = 100, b = 200; - TestOptInfo(config_options, OptionType::kUInt, &a, &b); - } - { - uint32_t a = 100, b = 200; - TestOptInfo(config_options, OptionType::kUInt32T, &a, &b); - } - { - uint64_t a = 100, b = 200; - TestOptInfo(config_options, OptionType::kUInt64T, &a, &b); - } - { - size_t a = 100, b = 200; - TestOptInfo(config_options, OptionType::kSizeT, &a, &b); - } - { - std::string a = "100", b = "200"; - TestOptInfo(config_options, OptionType::kString, &a, &b); - } - { - double a = 1.0, b = 2.0; - TestOptInfo(config_options, OptionType::kDouble, &a, &b); - } -} - -TEST_F(OptionTypeInfoTest, TestInvalidArgs) { - ConfigOptions config_options; - bool b; - int i; - int32_t i32; - int64_t i64; - unsigned int u; - int32_t u32; - int64_t u64; - size_t sz; - double d; - - ASSERT_NOK(OptionTypeInfo(0, OptionType::kBoolean) - .Parse(config_options, "b", "x", &b)); - ASSERT_NOK( - OptionTypeInfo(0, OptionType::kInt).Parse(config_options, "b", "x", &i)); - ASSERT_NOK(OptionTypeInfo(0, OptionType::kInt32T) - .Parse(config_options, "b", "x", &i32)); - ASSERT_NOK(OptionTypeInfo(0, OptionType::kInt64T) - .Parse(config_options, "b", "x", &i64)); - ASSERT_NOK( - OptionTypeInfo(0, OptionType::kUInt).Parse(config_options, "b", "x", &u)); - ASSERT_NOK(OptionTypeInfo(0, OptionType::kUInt32T) - .Parse(config_options, "b", "x", &u32)); - ASSERT_NOK(OptionTypeInfo(0, OptionType::kUInt64T) - .Parse(config_options, "b", "x", &u64)); - ASSERT_NOK(OptionTypeInfo(0, OptionType::kSizeT) - .Parse(config_options, "b", "x", &sz)); - ASSERT_NOK(OptionTypeInfo(0, OptionType::kDouble) - .Parse(config_options, "b", "x", &d)); - - // Don't know how to convert Unknowns to anything else - ASSERT_NOK(OptionTypeInfo(0, OptionType::kUnknown) - .Parse(config_options, "b", "x", &d)); - - // Verify that if the parse function throws an exception, it is also trapped - OptionTypeInfo func_info(0, OptionType::kUnknown, - OptionVerificationType::kNormal, - OptionTypeFlags::kNone, - [](const ConfigOptions&, const std::string&, - const std::string& value, void* addr) { - auto ptr = static_cast(addr); - *ptr = ParseInt(value); - return Status::OK(); - }); - ASSERT_OK(func_info.Parse(config_options, "b", "1", &i)); - ASSERT_NOK(func_info.Parse(config_options, "b", "x", &i)); -} - -TEST_F(OptionTypeInfoTest, TestParseFunc) { - OptionTypeInfo opt_info(0, OptionType::kUnknown, - OptionVerificationType::kNormal, - OptionTypeFlags::kNone); - opt_info.SetParseFunc([](const ConfigOptions& /*opts*/, - const std::string& name, const std::string& value, - void* addr) { - auto ptr = static_cast(addr); - if (name == "Oops") { - return Status::InvalidArgument(value); - } else { - *ptr = value + " " + name; - return Status::OK(); - } - }); - ConfigOptions config_options; - std::string base; - ASSERT_OK(opt_info.Parse(config_options, "World", "Hello", &base)); - ASSERT_EQ(base, "Hello World"); - ASSERT_NOK(opt_info.Parse(config_options, "Oops", "Hello", &base)); -} - -TEST_F(OptionTypeInfoTest, TestSerializeFunc) { - OptionTypeInfo opt_info(0, OptionType::kString, - OptionVerificationType::kNormal, - OptionTypeFlags::kNone); - opt_info.SetSerializeFunc([](const ConfigOptions& /*opts*/, - const std::string& name, const void* /*addr*/, - std::string* value) { - if (name == "Oops") { - return Status::InvalidArgument(name); - } else { - *value = name; - return Status::OK(); - } - }); - ConfigOptions config_options; - std::string base; - std::string value; - ASSERT_OK(opt_info.Serialize(config_options, "Hello", &base, &value)); - ASSERT_EQ(value, "Hello"); - ASSERT_NOK(opt_info.Serialize(config_options, "Oops", &base, &value)); -} - -TEST_F(OptionTypeInfoTest, TestEqualsFunc) { - OptionTypeInfo opt_info(0, OptionType::kInt, OptionVerificationType::kNormal, - OptionTypeFlags::kNone); - opt_info.SetEqualsFunc([](const ConfigOptions& /*opts*/, - const std::string& name, const void* addr1, - const void* addr2, std::string* mismatch) { - auto i1 = *(static_cast(addr1)); - auto i2 = *(static_cast(addr2)); - if (name == "LT") { - return i1 < i2; - } else if (name == "GT") { - return i1 > i2; - } else if (name == "EQ") { - return i1 == i2; - } else { - *mismatch = name + "???"; - return false; - } - }); - - ConfigOptions config_options; - int int1 = 100; - int int2 = 200; - std::string mismatch; - ASSERT_TRUE(opt_info.AreEqual(config_options, "LT", &int1, &int2, &mismatch)); - ASSERT_EQ(mismatch, ""); - ASSERT_FALSE( - opt_info.AreEqual(config_options, "GT", &int1, &int2, &mismatch)); - ASSERT_EQ(mismatch, "GT"); - ASSERT_FALSE( - opt_info.AreEqual(config_options, "NO", &int1, &int2, &mismatch)); - ASSERT_EQ(mismatch, "NO???"); -} - -TEST_F(OptionTypeInfoTest, TestPrepareFunc) { - OptionTypeInfo opt_info(0, OptionType::kInt, OptionVerificationType::kNormal, - OptionTypeFlags::kNone); - opt_info.SetPrepareFunc( - [](const ConfigOptions& /*opts*/, const std::string& name, void* addr) { - auto i1 = static_cast(addr); - if (name == "x2") { - *i1 *= 2; - } else if (name == "/2") { - *i1 /= 2; - } else { - return Status::InvalidArgument("Bad Argument", name); - } - return Status::OK(); - }); - ConfigOptions config_options; - int int1 = 100; - ASSERT_OK(opt_info.Prepare(config_options, "x2", &int1)); - ASSERT_EQ(int1, 200); - ASSERT_OK(opt_info.Prepare(config_options, "/2", &int1)); - ASSERT_EQ(int1, 100); - ASSERT_NOK(opt_info.Prepare(config_options, "??", &int1)); - ASSERT_EQ(int1, 100); -} -TEST_F(OptionTypeInfoTest, TestValidateFunc) { - OptionTypeInfo opt_info(0, OptionType::kSizeT, - OptionVerificationType::kNormal, - OptionTypeFlags::kNone); - opt_info.SetValidateFunc([](const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts, - const std::string& name, const void* addr) { - const auto sz = static_cast(addr); - bool is_valid = false; - if (name == "keep_log_file_num") { - is_valid = (*sz == db_opts.keep_log_file_num); - } else if (name == "write_buffer_size") { - is_valid = (*sz == cf_opts.write_buffer_size); - } - if (is_valid) { - return Status::OK(); - } else { - return Status::InvalidArgument("Mismatched value", name); - } - }); - ConfigOptions config_options; - DBOptions db_options; - ColumnFamilyOptions cf_options; - - ASSERT_OK(opt_info.Validate(db_options, cf_options, "keep_log_file_num", - &db_options.keep_log_file_num)); - ASSERT_OK(opt_info.Validate(db_options, cf_options, "write_buffer_size", - &cf_options.write_buffer_size)); - ASSERT_NOK(opt_info.Validate(db_options, cf_options, "keep_log_file_num", - &cf_options.write_buffer_size)); - ASSERT_NOK(opt_info.Validate(db_options, cf_options, "write_buffer_size", - &db_options.keep_log_file_num)); -} - -TEST_F(OptionTypeInfoTest, TestOptionFlags) { - OptionTypeInfo opt_none(0, OptionType::kString, - OptionVerificationType::kNormal, - OptionTypeFlags::kDontSerialize); - OptionTypeInfo opt_never(0, OptionType::kString, - OptionVerificationType::kNormal, - OptionTypeFlags::kCompareNever); - OptionTypeInfo opt_alias(0, OptionType::kString, - OptionVerificationType::kAlias, - OptionTypeFlags::kNone); - OptionTypeInfo opt_deprecated(0, OptionType::kString, - OptionVerificationType::kDeprecated, - OptionTypeFlags::kNone); - ConfigOptions config_options; - std::string opts_str; - std::string base = "base"; - std::string comp = "comp"; - - // If marked string none, the serialization returns not supported - ASSERT_NOK(opt_none.Serialize(config_options, "None", &base, &opts_str)); - // If marked never compare, they match even when they do not - ASSERT_TRUE(opt_never.AreEqual(config_options, "Never", &base, &comp, &base)); - ASSERT_FALSE(opt_none.AreEqual(config_options, "Never", &base, &comp, &base)); - - // An alias can change the value via parse, but does nothing on serialize on - // match - std::string result; - ASSERT_OK(opt_alias.Parse(config_options, "Alias", "Alias", &base)); - ASSERT_OK(opt_alias.Serialize(config_options, "Alias", &base, &result)); - ASSERT_TRUE( - opt_alias.AreEqual(config_options, "Alias", &base, &comp, &result)); - ASSERT_EQ(base, "Alias"); - ASSERT_NE(base, comp); - - // Deprecated options do nothing on any of the commands - ASSERT_OK(opt_deprecated.Parse(config_options, "Alias", "Deprecated", &base)); - ASSERT_OK(opt_deprecated.Serialize(config_options, "Alias", &base, &result)); - ASSERT_TRUE( - opt_deprecated.AreEqual(config_options, "Alias", &base, &comp, &result)); - ASSERT_EQ(base, "Alias"); - ASSERT_NE(base, comp); -} - -TEST_F(OptionTypeInfoTest, TestCustomEnum) { - enum TestEnum { kA, kB, kC }; - std::unordered_map enum_map = { - {"A", TestEnum::kA}, - {"B", TestEnum::kB}, - {"C", TestEnum::kC}, - }; - OptionTypeInfo opt_info = OptionTypeInfo::Enum(0, &enum_map); - TestEnum e1, e2; - ConfigOptions config_options; - std::string result, mismatch; - - e2 = TestEnum::kA; - - ASSERT_OK(opt_info.Parse(config_options, "", "B", &e1)); - ASSERT_OK(opt_info.Serialize(config_options, "", &e1, &result)); - ASSERT_EQ(e1, TestEnum::kB); - ASSERT_EQ(result, "B"); - - ASSERT_FALSE(opt_info.AreEqual(config_options, "Enum", &e1, &e2, &mismatch)); - ASSERT_EQ(mismatch, "Enum"); - - TestParseAndCompareOption(config_options, opt_info, "", "C", &e1, &e2); - ASSERT_EQ(e2, TestEnum::kC); - - ASSERT_NOK(opt_info.Parse(config_options, "", "D", &e1)); - ASSERT_EQ(e1, TestEnum::kC); -} - -TEST_F(OptionTypeInfoTest, TestBuiltinEnum) { - ConfigOptions config_options; - for (auto iter : OptionsHelper::compaction_style_string_map) { - CompactionStyle e1, e2; - TestParseAndCompareOption(config_options, - OptionTypeInfo(0, OptionType::kCompactionStyle), - "CompactionStyle", iter.first, &e1, &e2); - ASSERT_EQ(e1, iter.second); - } - for (auto iter : OptionsHelper::compaction_pri_string_map) { - CompactionPri e1, e2; - TestParseAndCompareOption(config_options, - OptionTypeInfo(0, OptionType::kCompactionPri), - "CompactionPri", iter.first, &e1, &e2); - ASSERT_EQ(e1, iter.second); - } - for (auto iter : OptionsHelper::compression_type_string_map) { - CompressionType e1, e2; - TestParseAndCompareOption(config_options, - OptionTypeInfo(0, OptionType::kCompressionType), - "CompressionType", iter.first, &e1, &e2); - ASSERT_EQ(e1, iter.second); - } - for (auto iter : OptionsHelper::compaction_stop_style_string_map) { - CompactionStopStyle e1, e2; - TestParseAndCompareOption( - config_options, OptionTypeInfo(0, OptionType::kCompactionStopStyle), - "CompactionStopStyle", iter.first, &e1, &e2); - ASSERT_EQ(e1, iter.second); - } - for (auto iter : OptionsHelper::checksum_type_string_map) { - ChecksumType e1, e2; - TestParseAndCompareOption(config_options, - OptionTypeInfo(0, OptionType::kChecksumType), - "CheckSumType", iter.first, &e1, &e2); - ASSERT_EQ(e1, iter.second); - } - for (auto iter : OptionsHelper::encoding_type_string_map) { - EncodingType e1, e2; - TestParseAndCompareOption(config_options, - OptionTypeInfo(0, OptionType::kEncodingType), - "EncodingType", iter.first, &e1, &e2); - ASSERT_EQ(e1, iter.second); - } -} - -TEST_F(OptionTypeInfoTest, TestStruct) { - struct Basic { - int i = 42; - std::string s = "Hello"; - }; - - struct Extended { - int j = 11; - Basic b; - }; - - std::unordered_map basic_type_map = { - {"i", {offsetof(struct Basic, i), OptionType::kInt}}, - {"s", {offsetof(struct Basic, s), OptionType::kString}}, - }; - OptionTypeInfo basic_info = OptionTypeInfo::Struct( - "b", &basic_type_map, 0, OptionVerificationType::kNormal, - OptionTypeFlags::kMutable); - - std::unordered_map extended_type_map = { - {"j", {offsetof(struct Extended, j), OptionType::kInt}}, - {"b", OptionTypeInfo::Struct( - "b", &basic_type_map, offsetof(struct Extended, b), - OptionVerificationType::kNormal, OptionTypeFlags::kNone)}, - {"m", OptionTypeInfo::Struct( - "m", &basic_type_map, offsetof(struct Extended, b), - OptionVerificationType::kNormal, OptionTypeFlags::kMutable)}, - }; - OptionTypeInfo extended_info = OptionTypeInfo::Struct( - "e", &extended_type_map, 0, OptionVerificationType::kNormal, - OptionTypeFlags::kMutable); - Extended e1, e2; - ConfigOptions config_options; - std::string mismatch; - TestParseAndCompareOption(config_options, basic_info, "b", "{i=33;s=33}", - &e1.b, &e2.b); - ASSERT_EQ(e1.b.i, 33); - ASSERT_EQ(e1.b.s, "33"); - - TestParseAndCompareOption(config_options, basic_info, "b.i", "44", &e1.b, - &e2.b); - ASSERT_EQ(e1.b.i, 44); - - TestParseAndCompareOption(config_options, basic_info, "i", "55", &e1.b, - &e2.b); - ASSERT_EQ(e1.b.i, 55); - - e1.b.i = 0; - - ASSERT_FALSE( - basic_info.AreEqual(config_options, "b", &e1.b, &e2.b, &mismatch)); - ASSERT_EQ(mismatch, "b.i"); - mismatch.clear(); - ASSERT_FALSE( - basic_info.AreEqual(config_options, "b.i", &e1.b, &e2.b, &mismatch)); - ASSERT_EQ(mismatch, "b.i"); - mismatch.clear(); - ASSERT_FALSE( - basic_info.AreEqual(config_options, "i", &e1.b, &e2.b, &mismatch)); - ASSERT_EQ(mismatch, "b.i"); - mismatch.clear(); - - e1 = e2; - ASSERT_NOK(basic_info.Parse(config_options, "b", "{i=33;s=33;j=44}", &e1.b)); - ASSERT_NOK(basic_info.Parse(config_options, "b.j", "44", &e1.b)); - ASSERT_NOK(basic_info.Parse(config_options, "j", "44", &e1.b)); - - TestParseAndCompareOption(config_options, extended_info, "e", - "b={i=55;s=55}; j=22;", &e1, &e2); - ASSERT_EQ(e1.b.i, 55); - ASSERT_EQ(e1.j, 22); - ASSERT_EQ(e1.b.s, "55"); - TestParseAndCompareOption(config_options, extended_info, "e.b", - "{i=66;s=66;}", &e1, &e2); - ASSERT_EQ(e1.b.i, 66); - ASSERT_EQ(e1.j, 22); - ASSERT_EQ(e1.b.s, "66"); - TestParseAndCompareOption(config_options, extended_info, "e.b.i", "77", &e1, - &e2); - ASSERT_EQ(e1.b.i, 77); - ASSERT_EQ(e1.j, 22); - ASSERT_EQ(e1.b.s, "66"); -} - -TEST_F(OptionTypeInfoTest, TestArrayType) { - OptionTypeInfo array_info = OptionTypeInfo::Array( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone, - {0, OptionType::kString}); - std::array array1, array2; - std::string mismatch; - - ConfigOptions config_options; - TestParseAndCompareOption(config_options, array_info, "v", "a:b:c:d", &array1, - &array2); - - ASSERT_EQ(array1.size(), 4); - ASSERT_EQ(array1[0], "a"); - ASSERT_EQ(array1[1], "b"); - ASSERT_EQ(array1[2], "c"); - ASSERT_EQ(array1[3], "d"); - array1[3] = "e"; - ASSERT_FALSE( - array_info.AreEqual(config_options, "v", &array1, &array2, &mismatch)); - ASSERT_EQ(mismatch, "v"); - - // Test vectors with inner brackets - TestParseAndCompareOption(config_options, array_info, "v", "a:{b}:c:d", - &array1, &array2); - ASSERT_EQ(array1.size(), 4); - ASSERT_EQ(array1[0], "a"); - ASSERT_EQ(array1[1], "b"); - ASSERT_EQ(array1[2], "c"); - ASSERT_EQ(array1[3], "d"); - - std::array array3, array4; - OptionTypeInfo bar_info = OptionTypeInfo::Array( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone, - {0, OptionType::kString}, '|'); - TestParseAndCompareOption(config_options, bar_info, "v", "x|y|z", &array3, - &array4); - - // Test arrays with inner array - TestParseAndCompareOption(config_options, bar_info, "v", - "a|{b1|b2}|{c1|c2|{d1|d2}}", &array3, &array4, - false); - ASSERT_EQ(array3.size(), 3); - ASSERT_EQ(array3[0], "a"); - ASSERT_EQ(array3[1], "b1|b2"); - ASSERT_EQ(array3[2], "c1|c2|{d1|d2}"); - - TestParseAndCompareOption(config_options, bar_info, "v", - "{a1|a2}|{b1|{c1|c2}}|d1", &array3, &array4, true); - ASSERT_EQ(array3.size(), 3); - ASSERT_EQ(array3[0], "a1|a2"); - ASSERT_EQ(array3[1], "b1|{c1|c2}"); - ASSERT_EQ(array3[2], "d1"); - - // Test invalid input: less element than requested - auto s = bar_info.Parse(config_options, "opt_name1", "a1|a2", &array3); - ASSERT_TRUE(s.IsInvalidArgument()); - - // Test invalid input: more element than requested - s = bar_info.Parse(config_options, "opt_name2", "a1|b|c1|d3", &array3); - ASSERT_TRUE(s.IsInvalidArgument()); -} - -TEST_F(OptionTypeInfoTest, TestVectorType) { - OptionTypeInfo vec_info = OptionTypeInfo::Vector( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone, - {0, OptionType::kString}); - std::vector vec1, vec2; - std::string mismatch; - - ConfigOptions config_options; - TestParseAndCompareOption(config_options, vec_info, "v", "a:b:c:d", &vec1, - &vec2); - ASSERT_EQ(vec1.size(), 4); - ASSERT_EQ(vec1[0], "a"); - ASSERT_EQ(vec1[1], "b"); - ASSERT_EQ(vec1[2], "c"); - ASSERT_EQ(vec1[3], "d"); - vec1[3] = "e"; - ASSERT_FALSE(vec_info.AreEqual(config_options, "v", &vec1, &vec2, &mismatch)); - ASSERT_EQ(mismatch, "v"); - - // Test vectors with inner brackets - TestParseAndCompareOption(config_options, vec_info, "v", "a:{b}:c:d", &vec1, - &vec2); - ASSERT_EQ(vec1.size(), 4); - ASSERT_EQ(vec1[0], "a"); - ASSERT_EQ(vec1[1], "b"); - ASSERT_EQ(vec1[2], "c"); - ASSERT_EQ(vec1[3], "d"); - - OptionTypeInfo bar_info = OptionTypeInfo::Vector( - 0, OptionVerificationType::kNormal, OptionTypeFlags::kNone, - {0, OptionType::kString}, '|'); - TestParseAndCompareOption(config_options, vec_info, "v", "x|y|z", &vec1, - &vec2); - // Test vectors with inner vector - TestParseAndCompareOption(config_options, bar_info, "v", - "a|{b1|b2}|{c1|c2|{d1|d2}}", &vec1, &vec2, false); - ASSERT_EQ(vec1.size(), 3); - ASSERT_EQ(vec1[0], "a"); - ASSERT_EQ(vec1[1], "b1|b2"); - ASSERT_EQ(vec1[2], "c1|c2|{d1|d2}"); - - TestParseAndCompareOption(config_options, bar_info, "v", - "{a1|a2}|{b1|{c1|c2}}|d1", &vec1, &vec2, true); - ASSERT_EQ(vec1.size(), 3); - ASSERT_EQ(vec1[0], "a1|a2"); - ASSERT_EQ(vec1[1], "b1|{c1|c2}"); - ASSERT_EQ(vec1[2], "d1"); - - TestParseAndCompareOption(config_options, bar_info, "v", "{a1}", &vec1, &vec2, - false); - ASSERT_EQ(vec1.size(), 1); - ASSERT_EQ(vec1[0], "a1"); - - TestParseAndCompareOption(config_options, bar_info, "v", "{a1|a2}|{b1|b2}", - &vec1, &vec2, true); - ASSERT_EQ(vec1.size(), 2); - ASSERT_EQ(vec1[0], "a1|a2"); - ASSERT_EQ(vec1[1], "b1|b2"); -} - -TEST_F(OptionTypeInfoTest, TestStaticType) { - struct SimpleOptions { - size_t size = 0; - bool verify = true; - }; - - static std::unordered_map type_map = { - {"size", {offsetof(struct SimpleOptions, size), OptionType::kSizeT}}, - {"verify", - {offsetof(struct SimpleOptions, verify), OptionType::kBoolean}}, - }; - - ConfigOptions config_options; - SimpleOptions opts, copy; - opts.size = 12345; - opts.verify = false; - std::string str, mismatch; - - ASSERT_OK( - OptionTypeInfo::SerializeType(config_options, type_map, &opts, &str)); - ASSERT_FALSE(OptionTypeInfo::TypesAreEqual(config_options, type_map, &opts, - ©, &mismatch)); - ASSERT_OK(OptionTypeInfo::ParseType(config_options, str, type_map, ©)); - ASSERT_TRUE(OptionTypeInfo::TypesAreEqual(config_options, type_map, &opts, - ©, &mismatch)); -} - -class ConfigOptionsTest : public testing::Test {}; - -TEST_F(ConfigOptionsTest, EnvFromConfigOptions) { - ConfigOptions config_options; - DBOptions db_opts; - Options opts; - Env* mem_env = NewMemEnv(Env::Default()); - config_options.registry->AddLibrary("custom-env", RegisterCustomEnv, - kCustomEnvName); - - config_options.env = mem_env; - // First test that we can get the env as expected - ASSERT_OK(GetDBOptionsFromString(config_options, DBOptions(), kCustomEnvProp, - &db_opts)); - ASSERT_OK( - GetOptionsFromString(config_options, Options(), kCustomEnvProp, &opts)); - ASSERT_NE(config_options.env, db_opts.env); - ASSERT_EQ(opts.env, db_opts.env); - Env* custom_env = db_opts.env; - - // Now try a "bad" env" and check that nothing changed - config_options.ignore_unsupported_options = true; - ASSERT_OK( - GetDBOptionsFromString(config_options, db_opts, "env=unknown", &db_opts)); - ASSERT_OK(GetOptionsFromString(config_options, opts, "env=unknown", &opts)); - ASSERT_EQ(config_options.env, mem_env); - ASSERT_EQ(db_opts.env, custom_env); - ASSERT_EQ(opts.env, db_opts.env); - - // Now try a "bad" env" ignoring unknown objects - config_options.ignore_unsupported_options = false; - ASSERT_NOK( - GetDBOptionsFromString(config_options, db_opts, "env=unknown", &db_opts)); - ASSERT_EQ(config_options.env, mem_env); - ASSERT_EQ(db_opts.env, custom_env); - ASSERT_EQ(opts.env, db_opts.env); - - delete mem_env; -} -TEST_F(ConfigOptionsTest, MergeOperatorFromString) { - ConfigOptions config_options; - std::shared_ptr merge_op; - - ASSERT_OK(MergeOperator::CreateFromString(config_options, "put", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("put")); - ASSERT_STREQ(merge_op->Name(), "PutOperator"); - - ASSERT_OK( - MergeOperator::CreateFromString(config_options, "put_v1", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("PutOperator")); - - ASSERT_OK( - MergeOperator::CreateFromString(config_options, "uint64add", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("uint64add")); - ASSERT_STREQ(merge_op->Name(), "UInt64AddOperator"); - - ASSERT_OK(MergeOperator::CreateFromString(config_options, "max", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("max")); - ASSERT_STREQ(merge_op->Name(), "MaxOperator"); - - ASSERT_OK( - MergeOperator::CreateFromString(config_options, "bytesxor", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("bytesxor")); - ASSERT_STREQ(merge_op->Name(), BytesXOROperator::kClassName()); - - ASSERT_OK( - MergeOperator::CreateFromString(config_options, "sortlist", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("sortlist")); - ASSERT_STREQ(merge_op->Name(), SortList::kClassName()); - - ASSERT_OK(MergeOperator::CreateFromString(config_options, "stringappend", - &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("stringappend")); - ASSERT_STREQ(merge_op->Name(), StringAppendOperator::kClassName()); - auto delimiter = merge_op->GetOptions("Delimiter"); - ASSERT_NE(delimiter, nullptr); - ASSERT_EQ(*delimiter, ","); - - ASSERT_OK(MergeOperator::CreateFromString(config_options, "stringappendtest", - &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("stringappendtest")); - ASSERT_STREQ(merge_op->Name(), StringAppendTESTOperator::kClassName()); - delimiter = merge_op->GetOptions("Delimiter"); - ASSERT_NE(delimiter, nullptr); - ASSERT_EQ(*delimiter, ","); - - ASSERT_OK(MergeOperator::CreateFromString( - config_options, "id=stringappend; delimiter=||", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("stringappend")); - ASSERT_STREQ(merge_op->Name(), StringAppendOperator::kClassName()); - delimiter = merge_op->GetOptions("Delimiter"); - ASSERT_NE(delimiter, nullptr); - ASSERT_EQ(*delimiter, "||"); - - ASSERT_OK(MergeOperator::CreateFromString( - config_options, "id=stringappendtest; delimiter=&&", &merge_op)); - ASSERT_NE(merge_op, nullptr); - ASSERT_TRUE(merge_op->IsInstanceOf("stringappendtest")); - ASSERT_STREQ(merge_op->Name(), StringAppendTESTOperator::kClassName()); - delimiter = merge_op->GetOptions("Delimiter"); - ASSERT_NE(delimiter, nullptr); - ASSERT_EQ(*delimiter, "&&"); - - std::shared_ptr copy; - std::string mismatch; - std::string opts_str = merge_op->ToString(config_options); - - ASSERT_OK(MergeOperator::CreateFromString(config_options, opts_str, ©)); - ASSERT_TRUE(merge_op->AreEquivalent(config_options, copy.get(), &mismatch)); - ASSERT_NE(copy, nullptr); - delimiter = copy->GetOptions("Delimiter"); - ASSERT_NE(delimiter, nullptr); - ASSERT_EQ(*delimiter, "&&"); -} - -TEST_F(ConfigOptionsTest, ConfiguringOptionsDoesNotRevertRateLimiterBandwidth) { - // Regression test for bug where rate limiter's dynamically set bandwidth - // could be silently reverted when configuring an options structure with an - // existing `rate_limiter`. - Options base_options; - base_options.rate_limiter.reset( - NewGenericRateLimiter(1 << 20 /* rate_bytes_per_sec */)); - Options copy_options(base_options); - - base_options.rate_limiter->SetBytesPerSecond(2 << 20); - ASSERT_EQ(2 << 20, base_options.rate_limiter->GetBytesPerSecond()); - - ASSERT_OK(GetOptionsFromString(base_options, "", ©_options)); - ASSERT_EQ(2 << 20, base_options.rate_limiter->GetBytesPerSecond()); -} - -INSTANTIATE_TEST_CASE_P(OptionsSanityCheckTest, OptionsSanityCheckTest, - ::testing::Bool()); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); -#ifdef GFLAGS - ParseCommandLineFlags(&argc, &argv, true); -#endif // GFLAGS - return RUN_ALL_TESTS(); -} diff --git a/table/block_fetcher_test.cc b/table/block_fetcher_test.cc deleted file mode 100644 index 6d983f9b7..000000000 --- a/table/block_fetcher_test.cc +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "table/block_fetcher.h" - -#include "db/table_properties_collector.h" -#include "file/file_util.h" -#include "options/options_helper.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/db.h" -#include "rocksdb/file_system.h" -#include "table/block_based/binary_search_index_reader.h" -#include "table/block_based/block_based_table_builder.h" -#include "table/block_based/block_based_table_factory.h" -#include "table/block_based/block_based_table_reader.h" -#include "table/format.h" -#include "test_util/testharness.h" -#include "utilities/memory_allocators.h" - -namespace ROCKSDB_NAMESPACE { -namespace { -struct MemcpyStats { - int num_stack_buf_memcpy; - int num_heap_buf_memcpy; - int num_compressed_buf_memcpy; -}; - -struct BufAllocationStats { - int num_heap_buf_allocations; - int num_compressed_buf_allocations; -}; - -struct TestStats { - MemcpyStats memcpy_stats; - BufAllocationStats buf_allocation_stats; -}; - -class BlockFetcherTest : public testing::Test { - public: - enum class Mode { - kBufferedRead = 0, - kBufferedMmap, - kDirectRead, - kNumModes, - }; - // use NumModes as array size to avoid "size of array '...' has non-integral - // type" errors. - const static int NumModes = static_cast(Mode::kNumModes); - - protected: - void SetUp() override { - SetupSyncPointsToMockDirectIO(); - test_dir_ = test::PerThreadDBPath("block_fetcher_test"); - env_ = Env::Default(); - fs_ = FileSystem::Default(); - ASSERT_OK(fs_->CreateDir(test_dir_, IOOptions(), nullptr)); - } - - void TearDown() override { EXPECT_OK(DestroyDir(env_, test_dir_)); } - - void AssertSameBlock(const std::string& block1, const std::string& block2) { - ASSERT_EQ(block1, block2); - } - - // Creates a table with kv pairs (i, i) where i ranges from 0 to 9, inclusive. - void CreateTable(const std::string& table_name, - const CompressionType& compression_type) { - std::unique_ptr writer; - NewFileWriter(table_name, &writer); - - // Create table builder. - ImmutableOptions ioptions(options_); - InternalKeyComparator comparator(options_.comparator); - ColumnFamilyOptions cf_options(options_); - MutableCFOptions moptions(cf_options); - IntTblPropCollectorFactories factories; - std::unique_ptr table_builder(table_factory_.NewTableBuilder( - TableBuilderOptions(ioptions, moptions, comparator, &factories, - compression_type, CompressionOptions(), - 0 /* column_family_id */, kDefaultColumnFamilyName, - -1 /* level */), - writer.get())); - - // Build table. - for (int i = 0; i < 9; i++) { - std::string key = ToInternalKey(std::to_string(i)); - // Append "00000000" to string value to enhance compression ratio - std::string value = "00000000" + std::to_string(i); - table_builder->Add(key, value); - } - ASSERT_OK(table_builder->Finish()); - } - - void FetchIndexBlock(const std::string& table_name, - CountedMemoryAllocator* heap_buf_allocator, - CountedMemoryAllocator* compressed_buf_allocator, - MemcpyStats* memcpy_stats, BlockContents* index_block, - std::string* result) { - FileOptions fopt(options_); - std::unique_ptr file; - NewFileReader(table_name, fopt, &file); - - // Get handle of the index block. - Footer footer; - ReadFooter(file.get(), &footer); - const BlockHandle& index_handle = footer.index_handle(); - - CompressionType compression_type; - FetchBlock(file.get(), index_handle, BlockType::kIndex, - false /* compressed */, false /* do_uncompress */, - heap_buf_allocator, compressed_buf_allocator, index_block, - memcpy_stats, &compression_type); - ASSERT_EQ(compression_type, CompressionType::kNoCompression); - result->assign(index_block->data.ToString()); - } - - // Fetches the first data block in both direct IO and non-direct IO mode. - // - // compressed: whether the data blocks are compressed; - // do_uncompress: whether the data blocks should be uncompressed on fetching. - // compression_type: the expected compression type. - // - // Expects: - // Block contents are the same. - // Bufferr allocation and memory copy statistics are expected. - void TestFetchDataBlock( - const std::string& table_name_prefix, bool compressed, bool do_uncompress, - std::array expected_stats_by_mode) { - for (CompressionType compression_type : GetSupportedCompressions()) { - bool do_compress = compression_type != kNoCompression; - if (compressed != do_compress) continue; - std::string compression_type_str = - CompressionTypeToString(compression_type); - - std::string table_name = table_name_prefix + compression_type_str; - CreateTable(table_name, compression_type); - - CompressionType expected_compression_type_after_fetch = - (compressed && !do_uncompress) ? compression_type : kNoCompression; - - BlockContents blocks[NumModes]; - std::string block_datas[NumModes]; - MemcpyStats memcpy_stats[NumModes]; - CountedMemoryAllocator heap_buf_allocators[NumModes]; - CountedMemoryAllocator compressed_buf_allocators[NumModes]; - for (int i = 0; i < NumModes; ++i) { - SetMode(static_cast(i)); - FetchFirstDataBlock(table_name, compressed, do_uncompress, - expected_compression_type_after_fetch, - &heap_buf_allocators[i], - &compressed_buf_allocators[i], &blocks[i], - &block_datas[i], &memcpy_stats[i]); - } - - for (int i = 0; i < NumModes - 1; ++i) { - AssertSameBlock(block_datas[i], block_datas[i + 1]); - } - - // Check memcpy and buffer allocation statistics. - for (int i = 0; i < NumModes; ++i) { - const TestStats& expected_stats = expected_stats_by_mode[i]; - - ASSERT_EQ(memcpy_stats[i].num_stack_buf_memcpy, - expected_stats.memcpy_stats.num_stack_buf_memcpy); - ASSERT_EQ(memcpy_stats[i].num_heap_buf_memcpy, - expected_stats.memcpy_stats.num_heap_buf_memcpy); - ASSERT_EQ(memcpy_stats[i].num_compressed_buf_memcpy, - expected_stats.memcpy_stats.num_compressed_buf_memcpy); - - if (kXpressCompression == compression_type) { - // XPRESS allocates memory internally, thus does not support for - // custom allocator verification - continue; - } else { - ASSERT_EQ( - heap_buf_allocators[i].GetNumAllocations(), - expected_stats.buf_allocation_stats.num_heap_buf_allocations); - ASSERT_EQ(compressed_buf_allocators[i].GetNumAllocations(), - expected_stats.buf_allocation_stats - .num_compressed_buf_allocations); - - // The allocated buffers are not deallocated until - // the block content is deleted. - ASSERT_EQ(heap_buf_allocators[i].GetNumDeallocations(), 0); - ASSERT_EQ(compressed_buf_allocators[i].GetNumDeallocations(), 0); - blocks[i].allocation.reset(); - ASSERT_EQ( - heap_buf_allocators[i].GetNumDeallocations(), - expected_stats.buf_allocation_stats.num_heap_buf_allocations); - ASSERT_EQ(compressed_buf_allocators[i].GetNumDeallocations(), - expected_stats.buf_allocation_stats - .num_compressed_buf_allocations); - } - } - } - } - - void SetMode(Mode mode) { - switch (mode) { - case Mode::kBufferedRead: - options_.use_direct_reads = false; - options_.allow_mmap_reads = false; - break; - case Mode::kBufferedMmap: - options_.use_direct_reads = false; - options_.allow_mmap_reads = true; - break; - case Mode::kDirectRead: - options_.use_direct_reads = true; - options_.allow_mmap_reads = false; - break; - case Mode::kNumModes: - assert(false); - } - } - - private: - std::string test_dir_; - Env* env_; - std::shared_ptr fs_; - BlockBasedTableFactory table_factory_; - Options options_; - - std::string Path(const std::string& fname) { return test_dir_ + "/" + fname; } - - void WriteToFile(const std::string& content, const std::string& filename) { - std::unique_ptr f; - ASSERT_OK(fs_->NewWritableFile(Path(filename), FileOptions(), &f, nullptr)); - ASSERT_OK(f->Append(content, IOOptions(), nullptr)); - ASSERT_OK(f->Close(IOOptions(), nullptr)); - } - - void NewFileWriter(const std::string& filename, - std::unique_ptr* writer) { - std::string path = Path(filename); - FileOptions file_options; - ASSERT_OK(WritableFileWriter::Create(env_->GetFileSystem(), path, - file_options, writer, nullptr)); - } - - void NewFileReader(const std::string& filename, const FileOptions& opt, - std::unique_ptr* reader) { - std::string path = Path(filename); - std::unique_ptr f; - ASSERT_OK(fs_->NewRandomAccessFile(path, opt, &f, nullptr)); - reader->reset(new RandomAccessFileReader(std::move(f), path, - env_->GetSystemClock().get())); - } - - void NewTableReader(const ImmutableOptions& ioptions, - const FileOptions& foptions, - const InternalKeyComparator& comparator, - const std::string& table_name, - std::unique_ptr* table) { - std::unique_ptr file; - NewFileReader(table_name, foptions, &file); - - uint64_t file_size = 0; - ASSERT_OK(env_->GetFileSize(Path(table_name), &file_size)); - - std::unique_ptr table_reader; - ReadOptions ro; - const auto* table_options = - table_factory_.GetOptions(); - ASSERT_NE(table_options, nullptr); - ASSERT_OK(BlockBasedTable::Open(ro, ioptions, EnvOptions(), *table_options, - comparator, std::move(file), file_size, - &table_reader)); - - table->reset(reinterpret_cast(table_reader.release())); - } - - std::string ToInternalKey(const std::string& key) { - InternalKey internal_key(key, 0, ValueType::kTypeValue); - return internal_key.Encode().ToString(); - } - - void ReadFooter(RandomAccessFileReader* file, Footer* footer) { - uint64_t file_size = 0; - ASSERT_OK(env_->GetFileSize(file->file_name(), &file_size)); - IOOptions opts; - ASSERT_OK(ReadFooterFromFile(opts, file, *fs_, - nullptr /* prefetch_buffer */, file_size, - footer, kBlockBasedTableMagicNumber)); - } - - // NOTE: compression_type returns the compression type of the fetched block - // contents, so if the block is fetched and uncompressed, then it's - // kNoCompression. - void FetchBlock(RandomAccessFileReader* file, const BlockHandle& block, - BlockType block_type, bool compressed, bool do_uncompress, - MemoryAllocator* heap_buf_allocator, - MemoryAllocator* compressed_buf_allocator, - BlockContents* contents, MemcpyStats* stats, - CompressionType* compresstion_type) { - ImmutableOptions ioptions(options_); - ReadOptions roptions; - PersistentCacheOptions persistent_cache_options; - Footer footer; - ReadFooter(file, &footer); - std::unique_ptr fetcher(new BlockFetcher( - file, nullptr /* prefetch_buffer */, footer, roptions, block, contents, - ioptions, do_uncompress, compressed, block_type, - UncompressionDict::GetEmptyDict(), persistent_cache_options, - heap_buf_allocator, compressed_buf_allocator)); - - ASSERT_OK(fetcher->ReadBlockContents()); - - stats->num_stack_buf_memcpy = fetcher->TEST_GetNumStackBufMemcpy(); - stats->num_heap_buf_memcpy = fetcher->TEST_GetNumHeapBufMemcpy(); - stats->num_compressed_buf_memcpy = - fetcher->TEST_GetNumCompressedBufMemcpy(); - - *compresstion_type = fetcher->get_compression_type(); - } - - // NOTE: expected_compression_type is the expected compression - // type of the fetched block content, if the block is uncompressed, - // then the expected compression type is kNoCompression. - void FetchFirstDataBlock(const std::string& table_name, bool compressed, - bool do_uncompress, - CompressionType expected_compression_type, - MemoryAllocator* heap_buf_allocator, - MemoryAllocator* compressed_buf_allocator, - BlockContents* block, std::string* result, - MemcpyStats* memcpy_stats) { - ImmutableOptions ioptions(options_); - InternalKeyComparator comparator(options_.comparator); - FileOptions foptions(options_); - - // Get block handle for the first data block. - std::unique_ptr table; - NewTableReader(ioptions, foptions, comparator, table_name, &table); - - std::unique_ptr index_reader; - ReadOptions ro; - ASSERT_OK(BinarySearchIndexReader::Create( - table.get(), ro, nullptr /* prefetch_buffer */, false /* use_cache */, - false /* prefetch */, false /* pin */, nullptr /* lookup_context */, - &index_reader)); - - std::unique_ptr> iter( - index_reader->NewIterator( - ReadOptions(), false /* disable_prefix_seek */, nullptr /* iter */, - nullptr /* get_context */, nullptr /* lookup_context */)); - ASSERT_OK(iter->status()); - iter->SeekToFirst(); - BlockHandle first_block_handle = iter->value().handle; - - // Fetch first data block. - std::unique_ptr file; - NewFileReader(table_name, foptions, &file); - CompressionType compression_type; - FetchBlock(file.get(), first_block_handle, BlockType::kData, compressed, - do_uncompress, heap_buf_allocator, compressed_buf_allocator, - block, memcpy_stats, &compression_type); - ASSERT_EQ(compression_type, expected_compression_type); - result->assign(block->data.ToString()); - } -}; - -// Skip the following tests in lite mode since direct I/O is unsupported. - -// Fetch index block under both direct IO and non-direct IO. -// Expects: -// the index block contents are the same for both read modes. -TEST_F(BlockFetcherTest, FetchIndexBlock) { - for (CompressionType compression : GetSupportedCompressions()) { - std::string table_name = - "FetchIndexBlock" + CompressionTypeToString(compression); - CreateTable(table_name, compression); - - CountedMemoryAllocator allocator; - MemcpyStats memcpy_stats; - BlockContents indexes[NumModes]; - std::string index_datas[NumModes]; - for (int i = 0; i < NumModes; ++i) { - SetMode(static_cast(i)); - FetchIndexBlock(table_name, &allocator, &allocator, &memcpy_stats, - &indexes[i], &index_datas[i]); - } - for (int i = 0; i < NumModes - 1; ++i) { - AssertSameBlock(index_datas[i], index_datas[i + 1]); - } - } -} - -// Data blocks are not compressed, -// fetch data block under direct IO, mmap IO,and non-direct IO. -// Expects: -// 1. in non-direct IO mode, allocate a heap buffer and memcpy the block -// into the buffer; -// 2. in direct IO mode, allocate a heap buffer and memcpy from the -// direct IO buffer to the heap buffer. -TEST_F(BlockFetcherTest, FetchUncompressedDataBlock) { - TestStats expected_non_mmap_stats = { - { - 0 /* num_stack_buf_memcpy */, - 1 /* num_heap_buf_memcpy */, - 0 /* num_compressed_buf_memcpy */, - }, - { - 1 /* num_heap_buf_allocations */, - 0 /* num_compressed_buf_allocations */, - }}; - TestStats expected_mmap_stats = {{ - 0 /* num_stack_buf_memcpy */, - 0 /* num_heap_buf_memcpy */, - 0 /* num_compressed_buf_memcpy */, - }, - { - 0 /* num_heap_buf_allocations */, - 0 /* num_compressed_buf_allocations */, - }}; - std::array expected_stats_by_mode{{ - expected_non_mmap_stats /* kBufferedRead */, - expected_mmap_stats /* kBufferedMmap */, - expected_non_mmap_stats /* kDirectRead */, - }}; - TestFetchDataBlock("FetchUncompressedDataBlock", false, false, - expected_stats_by_mode); -} - -// Data blocks are compressed, -// fetch data block under both direct IO and non-direct IO, -// but do not uncompress. -// Expects: -// 1. in non-direct IO mode, allocate a compressed buffer and memcpy the block -// into the buffer; -// 2. in direct IO mode, allocate a compressed buffer and memcpy from the -// direct IO buffer to the compressed buffer. -TEST_F(BlockFetcherTest, FetchCompressedDataBlock) { - TestStats expected_non_mmap_stats = { - { - 0 /* num_stack_buf_memcpy */, - 0 /* num_heap_buf_memcpy */, - 1 /* num_compressed_buf_memcpy */, - }, - { - 0 /* num_heap_buf_allocations */, - 1 /* num_compressed_buf_allocations */, - }}; - TestStats expected_mmap_stats = {{ - 0 /* num_stack_buf_memcpy */, - 0 /* num_heap_buf_memcpy */, - 0 /* num_compressed_buf_memcpy */, - }, - { - 0 /* num_heap_buf_allocations */, - 0 /* num_compressed_buf_allocations */, - }}; - std::array expected_stats_by_mode{{ - expected_non_mmap_stats /* kBufferedRead */, - expected_mmap_stats /* kBufferedMmap */, - expected_non_mmap_stats /* kDirectRead */, - }}; - TestFetchDataBlock("FetchCompressedDataBlock", true, false, - expected_stats_by_mode); -} - -// Data blocks are compressed, -// fetch and uncompress data block under both direct IO and non-direct IO. -// Expects: -// 1. in non-direct IO mode, since the block is small, so it's first memcpyed -// to the stack buffer, then a heap buffer is allocated and the block is -// uncompressed into the heap. -// 2. in direct IO mode mode, allocate a heap buffer, then directly uncompress -// and memcpy from the direct IO buffer to the heap buffer. -TEST_F(BlockFetcherTest, FetchAndUncompressCompressedDataBlock) { - TestStats expected_buffered_read_stats = { - { - 1 /* num_stack_buf_memcpy */, - 1 /* num_heap_buf_memcpy */, - 0 /* num_compressed_buf_memcpy */, - }, - { - 1 /* num_heap_buf_allocations */, - 0 /* num_compressed_buf_allocations */, - }}; - TestStats expected_mmap_stats = {{ - 0 /* num_stack_buf_memcpy */, - 1 /* num_heap_buf_memcpy */, - 0 /* num_compressed_buf_memcpy */, - }, - { - 1 /* num_heap_buf_allocations */, - 0 /* num_compressed_buf_allocations */, - }}; - TestStats expected_direct_read_stats = { - { - 0 /* num_stack_buf_memcpy */, - 1 /* num_heap_buf_memcpy */, - 0 /* num_compressed_buf_memcpy */, - }, - { - 1 /* num_heap_buf_allocations */, - 0 /* num_compressed_buf_allocations */, - }}; - std::array expected_stats_by_mode{{ - expected_buffered_read_stats, - expected_mmap_stats, - expected_direct_read_stats, - }}; - TestFetchDataBlock("FetchAndUncompressCompressedDataBlock", true, true, - expected_stats_by_mode); -} - - -} // namespace -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/table/cleanable_test.cc b/table/cleanable_test.cc deleted file mode 100644 index b58eb7dc6..000000000 --- a/table/cleanable_test.cc +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/cleanable.h" - -#include - -#include - -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/iostats_context.h" -#include "rocksdb/perf_context.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -class CleanableTest : public testing::Test {}; - -// Use this to keep track of the cleanups that were actually performed -void Multiplier(void* arg1, void* arg2) { - int* res = reinterpret_cast(arg1); - int* num = reinterpret_cast(arg2); - *res *= *num; -} - -// the first Cleanup is on stack and the rest on heap, so test with both cases -TEST_F(CleanableTest, Register) { - int n2 = 2, n3 = 3; - int res = 1; - { Cleanable c1; } - // ~Cleanable - ASSERT_EQ(1, res); - - res = 1; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - } - // ~Cleanable - ASSERT_EQ(2, res); - - res = 1; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.RegisterCleanup(Multiplier, &res, &n3); // res = 2 * 3; - } - // ~Cleanable - ASSERT_EQ(6, res); - - // Test the Reset does cleanup - res = 1; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.RegisterCleanup(Multiplier, &res, &n3); // res = 2 * 3; - c1.Reset(); - ASSERT_EQ(6, res); - } - // ~Cleanable - ASSERT_EQ(6, res); - - // Test Clenable is usable after Reset - res = 1; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.Reset(); - ASSERT_EQ(2, res); - c1.RegisterCleanup(Multiplier, &res, &n3); // res = 2 * 3; - } - // ~Cleanable - ASSERT_EQ(6, res); -} - -// the first Cleanup is on stack and the rest on heap, -// so test all the combinations of them -TEST_F(CleanableTest, Delegation) { - int n2 = 2, n3 = 3, n5 = 5, n7 = 7; - int res = 1; - { - Cleanable c2; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.DelegateCleanupsTo(&c2); - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(2, res); - - res = 1; - { - Cleanable c2; - { - Cleanable c1; - c1.DelegateCleanupsTo(&c2); - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(1, res); - - res = 1; - { - Cleanable c2; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.RegisterCleanup(Multiplier, &res, &n3); // res = 2 * 3; - c1.DelegateCleanupsTo(&c2); - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(6, res); - - res = 1; - { - Cleanable c2; - c2.RegisterCleanup(Multiplier, &res, &n5); // res = 5; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.RegisterCleanup(Multiplier, &res, &n3); // res = 2 * 3; - c1.DelegateCleanupsTo(&c2); // res = 2 * 3 * 5; - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(30, res); - - res = 1; - { - Cleanable c2; - c2.RegisterCleanup(Multiplier, &res, &n5); // res = 5; - c2.RegisterCleanup(Multiplier, &res, &n7); // res = 5 * 7; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.RegisterCleanup(Multiplier, &res, &n3); // res = 2 * 3; - c1.DelegateCleanupsTo(&c2); // res = 2 * 3 * 5 * 7; - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(210, res); - - res = 1; - { - Cleanable c2; - c2.RegisterCleanup(Multiplier, &res, &n5); // res = 5; - c2.RegisterCleanup(Multiplier, &res, &n7); // res = 5 * 7; - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - c1.DelegateCleanupsTo(&c2); // res = 2 * 5 * 7; - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(70, res); - - res = 1; - { - Cleanable c2; - c2.RegisterCleanup(Multiplier, &res, &n5); // res = 5; - c2.RegisterCleanup(Multiplier, &res, &n7); // res = 5 * 7; - { - Cleanable c1; - c1.DelegateCleanupsTo(&c2); // res = 5 * 7; - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(35, res); - - res = 1; - { - Cleanable c2; - c2.RegisterCleanup(Multiplier, &res, &n5); // res = 5; - { - Cleanable c1; - c1.DelegateCleanupsTo(&c2); // res = 5; - } - // ~Cleanable - ASSERT_EQ(1, res); - } - // ~Cleanable - ASSERT_EQ(5, res); -} - -static void ReleaseStringHeap(void* s, void*) { - delete reinterpret_cast(s); -} - -class PinnableSlice4Test : public PinnableSlice { - public: - void TestStringIsRegistered(std::string* s) { - ASSERT_TRUE(cleanup_.function == ReleaseStringHeap); - ASSERT_EQ(cleanup_.arg1, s); - ASSERT_EQ(cleanup_.arg2, nullptr); - ASSERT_EQ(cleanup_.next, nullptr); - } -}; - -// Putting the PinnableSlice tests here due to similarity to Cleanable tests -TEST_F(CleanableTest, PinnableSlice) { - int n2 = 2; - int res = 1; - const std::string const_str = "123"; - - { - res = 1; - PinnableSlice4Test value; - Slice slice(const_str); - value.PinSlice(slice, Multiplier, &res, &n2); - std::string str; - str.assign(value.data(), value.size()); - ASSERT_EQ(const_str, str); - } - // ~Cleanable - ASSERT_EQ(2, res); - - { - res = 1; - PinnableSlice4Test value; - Slice slice(const_str); - { - Cleanable c1; - c1.RegisterCleanup(Multiplier, &res, &n2); // res = 2; - value.PinSlice(slice, &c1); - } - // ~Cleanable - ASSERT_EQ(1, res); // cleanups must have be delegated to value - std::string str; - str.assign(value.data(), value.size()); - ASSERT_EQ(const_str, str); - } - // ~Cleanable - ASSERT_EQ(2, res); - - { - PinnableSlice4Test value; - Slice slice(const_str); - value.PinSelf(slice); - std::string str; - str.assign(value.data(), value.size()); - ASSERT_EQ(const_str, str); - } - - { - PinnableSlice4Test value; - std::string* self_str_ptr = value.GetSelf(); - self_str_ptr->assign(const_str); - value.PinSelf(); - std::string str; - str.assign(value.data(), value.size()); - ASSERT_EQ(const_str, str); - } -} - -static void Decrement(void* intptr, void*) { --*static_cast(intptr); } - -// Allow unit testing moved-from data -template -void MarkInitializedForClangAnalyze(T& t) { - // No net effect, but confuse analyzer. (Published advice doesn't work.) - char* p = reinterpret_cast(&t); - std::swap(*p, *p); -} - -TEST_F(CleanableTest, SharedWrapCleanables) { - int val = 5; - Cleanable c1, c2; - c1.RegisterCleanup(&Decrement, &val, nullptr); - c1.RegisterCleanup(&Decrement, &val, nullptr); - ASSERT_TRUE(c1.HasCleanups()); - ASSERT_FALSE(c2.HasCleanups()); - - SharedCleanablePtr scp1; - ASSERT_EQ(scp1.get(), nullptr); - - // No-ops - scp1.RegisterCopyWith(&c2); - scp1.MoveAsCleanupTo(&c2); - - ASSERT_FALSE(c2.HasCleanups()); - c2.RegisterCleanup(&Decrement, &val, nullptr); - c2.RegisterCleanup(&Decrement, &val, nullptr); - c2.RegisterCleanup(&Decrement, &val, nullptr); - - scp1.Allocate(); - ASSERT_NE(scp1.get(), nullptr); - ASSERT_FALSE(scp1->HasCleanups()); - - // Copy ctor (alias scp2 = scp1) - SharedCleanablePtr scp2{scp1}; - ASSERT_EQ(scp1.get(), scp2.get()); - - c1.DelegateCleanupsTo(&*scp1); - ASSERT_TRUE(scp1->HasCleanups()); - ASSERT_TRUE(scp2->HasCleanups()); - ASSERT_FALSE(c1.HasCleanups()); - - SharedCleanablePtr scp3; - ASSERT_EQ(scp3.get(), nullptr); - - // Copy operator (alias scp3 = scp2 = scp1) - scp3 = scp2; - - // Make scp2 point elsewhere - scp2.Allocate(); - c2.DelegateCleanupsTo(&*scp2); - - ASSERT_EQ(val, 5); - // Move operator, invoke old c2 cleanups - scp2 = std::move(scp1); - ASSERT_EQ(val, 2); - MarkInitializedForClangAnalyze(scp1); - ASSERT_EQ(scp1.get(), nullptr); - - // Move ctor - { - SharedCleanablePtr scp4{std::move(scp3)}; - MarkInitializedForClangAnalyze(scp3); - ASSERT_EQ(scp3.get(), nullptr); - ASSERT_EQ(scp4.get(), scp2.get()); - - scp2.Reset(); - ASSERT_EQ(val, 2); - // invoke old c1 cleanups - } - ASSERT_EQ(val, 0); -} - -TEST_F(CleanableTest, CleanableWrapShared) { - int val = 5; - SharedCleanablePtr scp1, scp2; - scp1.Allocate(); - scp1->RegisterCleanup(&Decrement, &val, nullptr); - scp1->RegisterCleanup(&Decrement, &val, nullptr); - - scp2.Allocate(); - scp2->RegisterCleanup(&Decrement, &val, nullptr); - scp2->RegisterCleanup(&Decrement, &val, nullptr); - scp2->RegisterCleanup(&Decrement, &val, nullptr); - - { - Cleanable c1; - { - Cleanable c2, c3; - scp1.RegisterCopyWith(&c1); - scp1.MoveAsCleanupTo(&c2); - ASSERT_TRUE(c1.HasCleanups()); - ASSERT_TRUE(c2.HasCleanups()); - ASSERT_EQ(scp1.get(), nullptr); - scp2.MoveAsCleanupTo(&c3); - ASSERT_TRUE(c3.HasCleanups()); - ASSERT_EQ(scp2.get(), nullptr); - c2.Reset(); - ASSERT_FALSE(c2.HasCleanups()); - ASSERT_EQ(val, 5); - // invoke cleanups from scp2 - } - ASSERT_EQ(val, 2); - // invoke cleanups from scp1 - } - ASSERT_EQ(val, 0); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/table/merger_test.cc b/table/merger_test.cc deleted file mode 100644 index 71dc798e5..000000000 --- a/table/merger_test.cc +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include - -#include "table/merging_iterator.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" -#include "util/vector_iterator.h" - -namespace ROCKSDB_NAMESPACE { - -class MergerTest : public testing::Test { - public: - MergerTest() - : icomp_(BytewiseComparator()), - rnd_(3), - merging_iterator_(nullptr), - single_iterator_(nullptr) {} - ~MergerTest() override = default; - std::vector GenerateStrings(size_t len, int string_len) { - std::vector ret; - - for (size_t i = 0; i < len; ++i) { - InternalKey ik(rnd_.HumanReadableString(string_len), 0, - ValueType::kTypeValue); - ret.push_back(ik.Encode().ToString(false)); - } - return ret; - } - - void AssertEquivalence() { - auto a = merging_iterator_.get(); - auto b = single_iterator_.get(); - if (!a->Valid()) { - ASSERT_TRUE(!b->Valid()); - } else { - ASSERT_TRUE(b->Valid()); - ASSERT_EQ(b->key().ToString(), a->key().ToString()); - ASSERT_EQ(b->value().ToString(), a->value().ToString()); - } - } - - void SeekToRandom() { - InternalKey ik(rnd_.HumanReadableString(5), 0, ValueType::kTypeValue); - Seek(ik.Encode().ToString(false)); - } - - void Seek(std::string target) { - merging_iterator_->Seek(target); - single_iterator_->Seek(target); - } - - void SeekToFirst() { - merging_iterator_->SeekToFirst(); - single_iterator_->SeekToFirst(); - } - - void SeekToLast() { - merging_iterator_->SeekToLast(); - single_iterator_->SeekToLast(); - } - - void Next(int times) { - for (int i = 0; i < times && merging_iterator_->Valid(); ++i) { - AssertEquivalence(); - merging_iterator_->Next(); - single_iterator_->Next(); - } - AssertEquivalence(); - } - - void Prev(int times) { - for (int i = 0; i < times && merging_iterator_->Valid(); ++i) { - AssertEquivalence(); - merging_iterator_->Prev(); - single_iterator_->Prev(); - } - AssertEquivalence(); - } - - void NextAndPrev(int times) { - for (int i = 0; i < times && merging_iterator_->Valid(); ++i) { - AssertEquivalence(); - if (rnd_.OneIn(2)) { - merging_iterator_->Prev(); - single_iterator_->Prev(); - } else { - merging_iterator_->Next(); - single_iterator_->Next(); - } - } - AssertEquivalence(); - } - - void Generate(size_t num_iterators, size_t strings_per_iterator, - int letters_per_string) { - std::vector small_iterators; - for (size_t i = 0; i < num_iterators; ++i) { - auto strings = GenerateStrings(strings_per_iterator, letters_per_string); - small_iterators.push_back(new VectorIterator(strings, strings, &icomp_)); - all_keys_.insert(all_keys_.end(), strings.begin(), strings.end()); - } - - merging_iterator_.reset( - NewMergingIterator(&icomp_, &small_iterators[0], - static_cast(small_iterators.size()))); - single_iterator_.reset(new VectorIterator(all_keys_, all_keys_, &icomp_)); - } - - InternalKeyComparator icomp_; - Random rnd_; - std::unique_ptr merging_iterator_; - std::unique_ptr single_iterator_; - std::vector all_keys_; -}; - -TEST_F(MergerTest, SeekToRandomNextTest) { - Generate(1000, 50, 50); - for (int i = 0; i < 10; ++i) { - SeekToRandom(); - AssertEquivalence(); - Next(50000); - } -} - -TEST_F(MergerTest, SeekToRandomNextSmallStringsTest) { - Generate(1000, 50, 2); - for (int i = 0; i < 10; ++i) { - SeekToRandom(); - AssertEquivalence(); - Next(50000); - } -} - -TEST_F(MergerTest, SeekToRandomPrevTest) { - Generate(1000, 50, 50); - for (int i = 0; i < 10; ++i) { - SeekToRandom(); - AssertEquivalence(); - Prev(50000); - } -} - -TEST_F(MergerTest, SeekToRandomRandomTest) { - Generate(200, 50, 50); - for (int i = 0; i < 3; ++i) { - SeekToRandom(); - AssertEquivalence(); - NextAndPrev(5000); - } -} - -TEST_F(MergerTest, SeekToFirstTest) { - Generate(1000, 50, 50); - for (int i = 0; i < 10; ++i) { - SeekToFirst(); - AssertEquivalence(); - Next(50000); - } -} - -TEST_F(MergerTest, SeekToLastTest) { - Generate(1000, 50, 50); - for (int i = 0; i < 10; ++i) { - SeekToLast(); - AssertEquivalence(); - Prev(50000); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/table/sst_file_reader_test.cc b/table/sst_file_reader_test.cc deleted file mode 100644 index ba81d7815..000000000 --- a/table/sst_file_reader_test.cc +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "rocksdb/sst_file_reader.h" - -#include - -#include "port/stack_trace.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/sst_file_writer.h" -#include "table/sst_file_writer_collectors.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -std::string EncodeAsString(uint64_t v) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08" PRIu64, v); - return std::string(buf); -} - -std::string EncodeAsUint64(uint64_t v) { - std::string dst; - PutFixed64(&dst, v); - return dst; -} - -class SstFileReaderTest : public testing::Test { - public: - SstFileReaderTest() { - options_.merge_operator = MergeOperators::CreateUInt64AddOperator(); - sst_name_ = test::PerThreadDBPath("sst_file"); - - Env* base_env = Env::Default(); - EXPECT_OK( - test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_)); - EXPECT_NE(nullptr, base_env); - env_ = base_env; - options_.env = env_; - } - - ~SstFileReaderTest() { - Status s = env_->DeleteFile(sst_name_); - EXPECT_OK(s); - } - - void CreateFile(const std::string& file_name, - const std::vector& keys) { - SstFileWriter writer(soptions_, options_); - ASSERT_OK(writer.Open(file_name)); - for (size_t i = 0; i + 2 < keys.size(); i += 3) { - ASSERT_OK(writer.Put(keys[i], keys[i])); - ASSERT_OK(writer.Merge(keys[i + 1], EncodeAsUint64(i + 1))); - ASSERT_OK(writer.Delete(keys[i + 2])); - } - ASSERT_OK(writer.Finish()); - } - - void CheckFile(const std::string& file_name, - const std::vector& keys, - bool check_global_seqno = false) { - ReadOptions ropts; - SstFileReader reader(options_); - ASSERT_OK(reader.Open(file_name)); - ASSERT_OK(reader.VerifyChecksum()); - std::unique_ptr iter(reader.NewIterator(ropts)); - iter->SeekToFirst(); - for (size_t i = 0; i + 2 < keys.size(); i += 3) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(keys[i]), 0); - ASSERT_EQ(iter->value().compare(keys[i]), 0); - iter->Next(); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key().compare(keys[i + 1]), 0); - ASSERT_EQ(iter->value().compare(EncodeAsUint64(i + 1)), 0); - iter->Next(); - } - ASSERT_FALSE(iter->Valid()); - if (check_global_seqno) { - auto properties = reader.GetTableProperties(); - ASSERT_TRUE(properties); - std::string hostname; - ASSERT_OK(env_->GetHostNameString(&hostname)); - ASSERT_EQ(properties->db_host_id, hostname); - auto& user_properties = properties->user_collected_properties; - ASSERT_TRUE( - user_properties.count(ExternalSstFilePropertyNames::kGlobalSeqno)); - } - } - - void CreateFileAndCheck(const std::vector& keys) { - CreateFile(sst_name_, keys); - CheckFile(sst_name_, keys); - } - - protected: - Options options_; - EnvOptions soptions_; - std::string sst_name_; - std::shared_ptr env_guard_; - Env* env_; -}; - -const uint64_t kNumKeys = 100; - -TEST_F(SstFileReaderTest, Basic) { - std::vector keys; - for (uint64_t i = 0; i < kNumKeys; i++) { - keys.emplace_back(EncodeAsString(i)); - } - CreateFileAndCheck(keys); -} - -TEST_F(SstFileReaderTest, Uint64Comparator) { - options_.comparator = test::Uint64Comparator(); - std::vector keys; - for (uint64_t i = 0; i < kNumKeys; i++) { - keys.emplace_back(EncodeAsUint64(i)); - } - CreateFileAndCheck(keys); -} - -TEST_F(SstFileReaderTest, ReadOptionsOutOfScope) { - // Repro a bug where the SstFileReader depended on its configured ReadOptions - // outliving it. - options_.comparator = test::Uint64Comparator(); - std::vector keys; - for (uint64_t i = 0; i < kNumKeys; i++) { - keys.emplace_back(EncodeAsUint64(i)); - } - CreateFile(sst_name_, keys); - - SstFileReader reader(options_); - ASSERT_OK(reader.Open(sst_name_)); - std::unique_ptr iter; - { - // Make sure ReadOptions go out of scope ASAP so we know the iterator - // operations do not depend on it. - ReadOptions ropts; - iter.reset(reader.NewIterator(ropts)); - } - iter->SeekToFirst(); - while (iter->Valid()) { - iter->Next(); - } -} - -TEST_F(SstFileReaderTest, ReadFileWithGlobalSeqno) { - std::vector keys; - for (uint64_t i = 0; i < kNumKeys; i++) { - keys.emplace_back(EncodeAsString(i)); - } - // Generate a SST file. - CreateFile(sst_name_, keys); - - // Ingest the file into a db, to assign it a global sequence number. - Options options; - options.create_if_missing = true; - std::string db_name = test::PerThreadDBPath("test_db"); - DB* db; - ASSERT_OK(DB::Open(options, db_name, &db)); - // Bump sequence number. - ASSERT_OK(db->Put(WriteOptions(), keys[0], "foo")); - ASSERT_OK(db->Flush(FlushOptions())); - // Ingest the file. - IngestExternalFileOptions ingest_options; - ingest_options.write_global_seqno = true; - ASSERT_OK(db->IngestExternalFile({sst_name_}, ingest_options)); - std::vector live_files; - uint64_t manifest_file_size = 0; - ASSERT_OK(db->GetLiveFiles(live_files, &manifest_file_size)); - // Get the ingested file. - std::string ingested_file; - for (auto& live_file : live_files) { - if (live_file.substr(live_file.size() - 4, std::string::npos) == ".sst") { - if (ingested_file.empty() || ingested_file < live_file) { - ingested_file = live_file; - } - } - } - ASSERT_FALSE(ingested_file.empty()); - delete db; - - // Verify the file can be open and read by SstFileReader. - CheckFile(db_name + ingested_file, keys, true /* check_global_seqno */); - - // Cleanup. - ASSERT_OK(DestroyDB(db_name, options)); -} - -TEST_F(SstFileReaderTest, TimestampSizeMismatch) { - SstFileWriter writer(soptions_, options_); - - ASSERT_OK(writer.Open(sst_name_)); - - // Comparator is not timestamp-aware; calls to APIs taking timestamps should - // fail. - ASSERT_NOK(writer.Put("key", EncodeAsUint64(100), "value")); - ASSERT_NOK(writer.Delete("another_key", EncodeAsUint64(200))); -} - -class SstFileReaderTimestampTest : public testing::Test { - public: - SstFileReaderTimestampTest() { - Env* env = Env::Default(); - EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env, &env_guard_)); - EXPECT_NE(nullptr, env); - - options_.env = env; - - options_.comparator = test::BytewiseComparatorWithU64TsWrapper(); - - sst_name_ = test::PerThreadDBPath("sst_file_ts"); - } - - ~SstFileReaderTimestampTest() { - EXPECT_OK(options_.env->DeleteFile(sst_name_)); - } - - struct KeyValueDesc { - KeyValueDesc(std::string k, std::string ts, std::string v) - : key(std::move(k)), timestamp(std::move(ts)), value(std::move(v)) {} - - std::string key; - std::string timestamp; - std::string value; - }; - - struct InputKeyValueDesc : public KeyValueDesc { - InputKeyValueDesc(std::string k, std::string ts, std::string v, bool is_del, - bool use_contig_buf) - : KeyValueDesc(std::move(k), std::move(ts), std::move(v)), - is_delete(is_del), - use_contiguous_buffer(use_contig_buf) {} - - bool is_delete = false; - bool use_contiguous_buffer = false; - }; - - struct OutputKeyValueDesc : public KeyValueDesc { - OutputKeyValueDesc(std::string k, std::string ts, std::string v) - : KeyValueDesc(std::move(k), std::string(ts), std::string(v)) {} - }; - - void CreateFile(const std::vector& descs) { - SstFileWriter writer(soptions_, options_); - - ASSERT_OK(writer.Open(sst_name_)); - - for (const auto& desc : descs) { - if (desc.is_delete) { - if (desc.use_contiguous_buffer) { - std::string key_with_ts(desc.key + desc.timestamp); - ASSERT_OK(writer.Delete(Slice(key_with_ts.data(), desc.key.size()), - Slice(key_with_ts.data() + desc.key.size(), - desc.timestamp.size()))); - } else { - ASSERT_OK(writer.Delete(desc.key, desc.timestamp)); - } - } else { - if (desc.use_contiguous_buffer) { - std::string key_with_ts(desc.key + desc.timestamp); - ASSERT_OK(writer.Put(Slice(key_with_ts.data(), desc.key.size()), - Slice(key_with_ts.data() + desc.key.size(), - desc.timestamp.size()), - desc.value)); - } else { - ASSERT_OK(writer.Put(desc.key, desc.timestamp, desc.value)); - } - } - } - - ASSERT_OK(writer.Finish()); - } - - void CheckFile(const std::string& timestamp, - const std::vector& descs) { - SstFileReader reader(options_); - - ASSERT_OK(reader.Open(sst_name_)); - ASSERT_OK(reader.VerifyChecksum()); - - Slice ts_slice(timestamp); - - ReadOptions read_options; - read_options.timestamp = &ts_slice; - - std::unique_ptr iter(reader.NewIterator(read_options)); - iter->SeekToFirst(); - - for (const auto& desc : descs) { - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->key(), desc.key); - ASSERT_EQ(iter->timestamp(), desc.timestamp); - ASSERT_EQ(iter->value(), desc.value); - - iter->Next(); - } - - ASSERT_FALSE(iter->Valid()); - } - - protected: - std::shared_ptr env_guard_; - Options options_; - EnvOptions soptions_; - std::string sst_name_; -}; - -TEST_F(SstFileReaderTimestampTest, Basic) { - std::vector input_descs; - - for (uint64_t k = 0; k < kNumKeys; k += 4) { - // A Put with key k, timestamp k that gets overwritten by a subsequent Put - // with timestamp (k + 1). Note that the comparator uses descending order - // for the timestamp part, so we add the later Put first. - input_descs.emplace_back( - /* key */ EncodeAsString(k), /* timestamp */ EncodeAsUint64(k + 1), - /* value */ EncodeAsString(k * 2), /* is_delete */ false, - /* use_contiguous_buffer */ false); - input_descs.emplace_back( - /* key */ EncodeAsString(k), /* timestamp */ EncodeAsUint64(k), - /* value */ EncodeAsString(k * 3), /* is_delete */ false, - /* use_contiguous_buffer */ true); - - // A Put with key (k + 2), timestamp (k + 2) that gets cancelled out by a - // Delete with timestamp (k + 3). Note that the comparator uses descending - // order for the timestamp part, so we add the Delete first. - input_descs.emplace_back(/* key */ EncodeAsString(k + 2), - /* timestamp */ EncodeAsUint64(k + 3), - /* value */ std::string(), /* is_delete */ true, - /* use_contiguous_buffer */ (k % 8) == 0); - input_descs.emplace_back( - /* key */ EncodeAsString(k + 2), /* timestamp */ EncodeAsUint64(k + 2), - /* value */ EncodeAsString(k * 5), /* is_delete */ false, - /* use_contiguous_buffer */ (k % 8) != 0); - } - - CreateFile(input_descs); - - // Note: below, we check the results as of each timestamp in the range, - // updating the expected result as needed. - std::vector output_descs; - - for (uint64_t ts = 0; ts < kNumKeys; ++ts) { - const uint64_t k = ts - (ts % 4); - - switch (ts % 4) { - case 0: // Initial Put for key k - output_descs.emplace_back(/* key */ EncodeAsString(k), - /* timestamp */ EncodeAsUint64(ts), - /* value */ EncodeAsString(k * 3)); - break; - - case 1: // Second Put for key k - assert(output_descs.back().key == EncodeAsString(k)); - assert(output_descs.back().timestamp == EncodeAsUint64(ts - 1)); - assert(output_descs.back().value == EncodeAsString(k * 3)); - output_descs.back().timestamp = EncodeAsUint64(ts); - output_descs.back().value = EncodeAsString(k * 2); - break; - - case 2: // Put for key (k + 2) - output_descs.emplace_back(/* key */ EncodeAsString(k + 2), - /* timestamp */ EncodeAsUint64(ts), - /* value */ EncodeAsString(k * 5)); - break; - - case 3: // Delete for key (k + 2) - assert(output_descs.back().key == EncodeAsString(k + 2)); - assert(output_descs.back().timestamp == EncodeAsUint64(ts - 1)); - assert(output_descs.back().value == EncodeAsString(k * 5)); - output_descs.pop_back(); - break; - } - - CheckFile(EncodeAsUint64(ts), output_descs); - } -} - -TEST_F(SstFileReaderTimestampTest, TimestampsOutOfOrder) { - SstFileWriter writer(soptions_, options_); - - ASSERT_OK(writer.Open(sst_name_)); - - // Note: KVs that have the same user key disregarding timestamps should be in - // descending order of timestamps. - ASSERT_OK(writer.Put("key", EncodeAsUint64(1), "value1")); - ASSERT_NOK(writer.Put("key", EncodeAsUint64(2), "value2")); -} - -TEST_F(SstFileReaderTimestampTest, TimestampSizeMismatch) { - SstFileWriter writer(soptions_, options_); - - ASSERT_OK(writer.Open(sst_name_)); - - // Comparator expects 64-bit timestamps; timestamps with other sizes as well - // as calls to the timestamp-less APIs should be rejected. - ASSERT_NOK(writer.Put("key", "not_an_actual_64_bit_timestamp", "value")); - ASSERT_NOK(writer.Delete("another_key", "timestamp_of_unexpected_size")); - - ASSERT_NOK(writer.Put("key_without_timestamp", "value")); - ASSERT_NOK(writer.Merge("another_key_missing_a_timestamp", "merge_operand")); - ASSERT_NOK(writer.Delete("yet_another_key_still_no_timestamp")); - ASSERT_NOK(writer.DeleteRange("begin_key_timestamp_absent", - "end_key_with_a_complete_lack_of_timestamps")); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/table/table_test.cc b/table/table_test.cc deleted file mode 100644 index df9e508f5..000000000 --- a/table/table_test.cc +++ /dev/null @@ -1,5625 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/table.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "cache/lru_cache.h" -#include "db/db_test_util.h" -#include "db/dbformat.h" -#include "db/memtable.h" -#include "db/write_batch_internal.h" -#include "memtable/stl_wrappers.h" -#include "monitoring/statistics.h" -#include "options/options_helper.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/cache.h" -#include "rocksdb/compression_type.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/file_checksum.h" -#include "rocksdb/file_system.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/iterator.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/options.h" -#include "rocksdb/perf_context.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table_properties.h" -#include "rocksdb/trace_record.h" -#include "rocksdb/unique_id.h" -#include "rocksdb/write_buffer_manager.h" -#include "table/block_based/block.h" -#include "table/block_based/block_based_table_builder.h" -#include "table/block_based/block_based_table_factory.h" -#include "table/block_based/block_based_table_reader.h" -#include "table/block_based/block_builder.h" -#include "table/block_based/filter_policy_internal.h" -#include "table/block_based/flush_block_policy.h" -#include "table/block_fetcher.h" -#include "table/format.h" -#include "table/get_context.h" -#include "table/internal_iterator.h" -#include "table/meta_blocks.h" -#include "table/plain/plain_table_factory.h" -#include "table/scoped_arena_iterator.h" -#include "table/sst_file_writer_collectors.h" -#include "table/unique_id_impl.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/coding_lean.h" -#include "util/compression.h" -#include "util/file_checksum_helper.h" -#include "util/random.h" -#include "util/string_util.h" -#include "utilities/memory_allocators.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -extern const uint64_t kLegacyBlockBasedTableMagicNumber; -extern const uint64_t kLegacyPlainTableMagicNumber; -extern const uint64_t kBlockBasedTableMagicNumber; -extern const uint64_t kPlainTableMagicNumber; - -namespace { - -const std::string kDummyValue(10000, 'o'); - -// DummyPropertiesCollector used to test BlockBasedTableProperties -class DummyPropertiesCollector : public TablePropertiesCollector { - public: - const char* Name() const override { return "DummyPropertiesCollector"; } - - Status Finish(UserCollectedProperties* /*properties*/) override { - return Status::OK(); - } - - Status Add(const Slice& /*user_key*/, const Slice& /*value*/) override { - return Status::OK(); - } - - UserCollectedProperties GetReadableProperties() const override { - return UserCollectedProperties{}; - } -}; - -class DummyPropertiesCollectorFactory1 - : public TablePropertiesCollectorFactory { - public: - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - return new DummyPropertiesCollector(); - } - const char* Name() const override { - return "DummyPropertiesCollectorFactory1"; - } -}; - -class DummyPropertiesCollectorFactory2 - : public TablePropertiesCollectorFactory { - public: - TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context /*context*/) override { - return new DummyPropertiesCollector(); - } - const char* Name() const override { - return "DummyPropertiesCollectorFactory2"; - } -}; - -// Return reverse of "key". -// Used to test non-lexicographic comparators. -std::string Reverse(const Slice& key) { - auto rev = key.ToString(); - std::reverse(rev.begin(), rev.end()); - return rev; -} - -class ReverseKeyComparator : public Comparator { - public: - const char* Name() const override { - return "rocksdb.ReverseBytewiseComparator"; - } - - int Compare(const Slice& a, const Slice& b) const override { - return BytewiseComparator()->Compare(Reverse(a), Reverse(b)); - } - - void FindShortestSeparator(std::string* start, - const Slice& limit) const override { - std::string s = Reverse(*start); - std::string l = Reverse(limit); - BytewiseComparator()->FindShortestSeparator(&s, l); - *start = Reverse(s); - } - - void FindShortSuccessor(std::string* key) const override { - std::string s = Reverse(*key); - BytewiseComparator()->FindShortSuccessor(&s); - *key = Reverse(s); - } -}; - -ReverseKeyComparator reverse_key_comparator; - -void Increment(const Comparator* cmp, std::string* key) { - if (cmp == BytewiseComparator()) { - key->push_back('\0'); - } else { - assert(cmp == &reverse_key_comparator); - std::string rev = Reverse(*key); - rev.push_back('\0'); - *key = Reverse(rev); - } -} - -const auto kUnknownColumnFamily = - TablePropertiesCollectorFactory::Context::kUnknownColumnFamily; - -} // namespace - -// Helper class for tests to unify the interface between -// BlockBuilder/TableBuilder and Block/Table. -class Constructor { - public: - explicit Constructor(const Comparator* cmp) - : data_(stl_wrappers::LessOfComparator(cmp)) {} - virtual ~Constructor() {} - - void Add(const std::string& key, const Slice& value) { - data_[key] = value.ToString(); - } - - // Finish constructing the data structure with all the keys that have - // been added so far. Returns the keys in sorted order in "*keys" - // and stores the key/value pairs in "*kvmap" - void Finish(const Options& options, const ImmutableOptions& ioptions, - const MutableCFOptions& moptions, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& internal_comparator, - std::vector* keys, stl_wrappers::KVMap* kvmap) { - last_internal_comparator_ = &internal_comparator; - *kvmap = data_; - keys->clear(); - for (const auto& kv : data_) { - keys->push_back(kv.first); - } - data_.clear(); - Status s = FinishImpl(options, ioptions, moptions, table_options, - internal_comparator, *kvmap); - ASSERT_TRUE(s.ok()) << s.ToString(); - } - - // Construct the data structure from the data in "data" - virtual Status FinishImpl(const Options& options, - const ImmutableOptions& ioptions, - const MutableCFOptions& moptions, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& internal_comparator, - const stl_wrappers::KVMap& data) = 0; - - virtual InternalIterator* NewIterator( - const SliceTransform* prefix_extractor = nullptr) const = 0; - - virtual const stl_wrappers::KVMap& data() { return data_; } - - virtual bool IsArenaMode() const { return false; } - - virtual DB* db() const { return nullptr; } // Overridden in DBConstructor - - virtual bool AnywayDeleteIterator() const { return false; } - - protected: - const InternalKeyComparator* last_internal_comparator_; - - private: - stl_wrappers::KVMap data_; -}; - -// A helper class that converts internal format keys into user keys -class KeyConvertingIterator : public InternalIterator { - public: - explicit KeyConvertingIterator(InternalIterator* iter, - bool arena_mode = false) - : iter_(iter), arena_mode_(arena_mode) {} - ~KeyConvertingIterator() override { - if (arena_mode_) { - iter_->~InternalIterator(); - } else { - delete iter_; - } - } - bool Valid() const override { return iter_->Valid() && status_.ok(); } - void Seek(const Slice& target) override { - ParsedInternalKey ikey(target, kMaxSequenceNumber, kTypeValue); - std::string encoded; - AppendInternalKey(&encoded, ikey); - iter_->Seek(encoded); - } - void SeekForPrev(const Slice& target) override { - ParsedInternalKey ikey(target, kMaxSequenceNumber, kTypeValue); - std::string encoded; - AppendInternalKey(&encoded, ikey); - iter_->SeekForPrev(encoded); - } - void SeekToFirst() override { iter_->SeekToFirst(); } - void SeekToLast() override { iter_->SeekToLast(); } - void Next() override { iter_->Next(); } - void Prev() override { iter_->Prev(); } - IterBoundCheck UpperBoundCheckResult() override { - return iter_->UpperBoundCheckResult(); - } - - Slice key() const override { - assert(Valid()); - ParsedInternalKey parsed_key; - Status pik_status = - ParseInternalKey(iter_->key(), &parsed_key, true /* log_err_key */); - if (!pik_status.ok()) { - status_ = pik_status; - return Slice(status_.getState()); - } - return parsed_key.user_key; - } - - Slice value() const override { return iter_->value(); } - Status status() const override { - return status_.ok() ? iter_->status() : status_; - } - - private: - mutable Status status_; - InternalIterator* iter_; - bool arena_mode_; - - // No copying allowed - KeyConvertingIterator(const KeyConvertingIterator&); - void operator=(const KeyConvertingIterator&); -}; - -// `BlockConstructor` APIs always accept/return user keys. -class BlockConstructor : public Constructor { - public: - explicit BlockConstructor(const Comparator* cmp) - : Constructor(cmp), comparator_(cmp), block_(nullptr) {} - ~BlockConstructor() override { delete block_; } - Status FinishImpl(const Options& /*options*/, - const ImmutableOptions& /*ioptions*/, - const MutableCFOptions& /*moptions*/, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& /*internal_comparator*/, - const stl_wrappers::KVMap& kv_map) override { - delete block_; - block_ = nullptr; - BlockBuilder builder(table_options.block_restart_interval); - - for (const auto& kv : kv_map) { - // `DataBlockIter` assumes it reads only internal keys. `BlockConstructor` - // clients provide user keys, so we need to convert to internal key format - // before writing the data block. - ParsedInternalKey ikey(kv.first, kMaxSequenceNumber, kTypeValue); - std::string encoded; - AppendInternalKey(&encoded, ikey); - builder.Add(encoded, kv.second); - } - // Open the block - data_ = builder.Finish().ToString(); - BlockContents contents; - contents.data = data_; - block_ = new Block(std::move(contents)); - return Status::OK(); - } - InternalIterator* NewIterator( - const SliceTransform* /*prefix_extractor*/) const override { - // `DataBlockIter` returns the internal keys it reads. - // `KeyConvertingIterator` converts them to user keys before they are - // exposed to the `BlockConstructor` clients. - return new KeyConvertingIterator( - block_->NewDataIterator(comparator_, kDisableGlobalSequenceNumber)); - } - - private: - const Comparator* comparator_; - std::string data_; - Block* block_; - - BlockConstructor(); -}; - -class TableConstructor : public Constructor { - public: - explicit TableConstructor(const Comparator* cmp, - bool convert_to_internal_key = false, - int level = -1, SequenceNumber largest_seqno = 0) - : Constructor(cmp), - largest_seqno_(largest_seqno), - convert_to_internal_key_(convert_to_internal_key), - level_(level) { - env_ = ROCKSDB_NAMESPACE::Env::Default(); - } - ~TableConstructor() override { Reset(); } - - Status FinishImpl(const Options& options, const ImmutableOptions& ioptions, - const MutableCFOptions& moptions, - const BlockBasedTableOptions& /*table_options*/, - const InternalKeyComparator& internal_comparator, - const stl_wrappers::KVMap& kv_map) override { - Reset(); - soptions.use_mmap_reads = ioptions.allow_mmap_reads; - std::unique_ptr sink(new test::StringSink()); - file_writer_.reset(new WritableFileWriter( - std::move(sink), "" /* don't care */, FileOptions())); - std::unique_ptr builder; - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - - if (largest_seqno_ != 0) { - // Pretend that it's an external file written by SstFileWriter. - int_tbl_prop_collector_factories.emplace_back( - new SstFileWriterPropertiesCollectorFactory(2 /* version */, - 0 /* global_seqno*/)); - } - - std::string column_family_name; - builder.reset(ioptions.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, internal_comparator, - &int_tbl_prop_collector_factories, - options.compression, options.compression_opts, - kUnknownColumnFamily, column_family_name, level_), - file_writer_.get())); - - for (const auto& kv : kv_map) { - if (convert_to_internal_key_) { - ParsedInternalKey ikey(kv.first, kMaxSequenceNumber, kTypeValue); - std::string encoded; - AppendInternalKey(&encoded, ikey); - builder->Add(encoded, kv.second); - } else { - builder->Add(kv.first, kv.second); - } - EXPECT_OK(builder->status()); - } - Status s = builder->Finish(); - EXPECT_OK(file_writer_->Flush()); - EXPECT_TRUE(s.ok()) << s.ToString(); - - EXPECT_EQ(TEST_GetSink()->contents().size(), builder->FileSize()); - - // Open the table - file_num_ = cur_file_num_++; - - return Reopen(ioptions, moptions); - } - - InternalIterator* NewIterator( - const SliceTransform* prefix_extractor) const override { - InternalIterator* iter = table_reader_->NewIterator( - read_options_, prefix_extractor, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized); - if (convert_to_internal_key_) { - return new KeyConvertingIterator(iter); - } else { - return iter; - } - } - - uint64_t ApproximateOffsetOf(const Slice& key) const { - if (convert_to_internal_key_) { - InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); - const Slice skey = ikey.Encode(); - return table_reader_->ApproximateOffsetOf( - skey, TableReaderCaller::kUncategorized); - } - return table_reader_->ApproximateOffsetOf( - key, TableReaderCaller::kUncategorized); - } - - virtual Status Reopen(const ImmutableOptions& ioptions, - const MutableCFOptions& moptions) { - std::unique_ptr source(new test::StringSource( - TEST_GetSink()->contents(), file_num_, ioptions.allow_mmap_reads)); - - file_reader_.reset(new RandomAccessFileReader(std::move(source), "test")); - return ioptions.table_factory->NewTableReader( - TableReaderOptions(ioptions, moptions.prefix_extractor, soptions, - *last_internal_comparator_, /*skip_filters*/ false, - /*immortal*/ false, false, level_, - &block_cache_tracer_, moptions.write_buffer_size, "", - file_num_, kNullUniqueId64x2, largest_seqno_), - std::move(file_reader_), TEST_GetSink()->contents().size(), - &table_reader_); - } - - virtual TableReader* GetTableReader() { return table_reader_.get(); } - - bool AnywayDeleteIterator() const override { - return convert_to_internal_key_; - } - - void ResetTableReader() { table_reader_.reset(); } - - bool ConvertToInternalKey() { return convert_to_internal_key_; } - - test::StringSink* TEST_GetSink() { - return static_cast(file_writer_->writable_file()); - } - - BlockCacheTracer block_cache_tracer_; - - private: - void Reset() { - file_num_ = 0; - table_reader_.reset(); - file_writer_.reset(); - file_reader_.reset(); - } - - const ReadOptions read_options_; - uint64_t file_num_; - std::unique_ptr file_writer_; - std::unique_ptr file_reader_; - std::unique_ptr table_reader_; - SequenceNumber largest_seqno_; - bool convert_to_internal_key_; - int level_; - - TableConstructor(); - - static uint64_t cur_file_num_; - EnvOptions soptions; - Env* env_; -}; -uint64_t TableConstructor::cur_file_num_ = 1; - -class MemTableConstructor : public Constructor { - public: - explicit MemTableConstructor(const Comparator* cmp, WriteBufferManager* wb) - : Constructor(cmp), - internal_comparator_(cmp), - write_buffer_manager_(wb), - table_factory_(new SkipListFactory) { - options_.memtable_factory = table_factory_; - ImmutableOptions ioptions(options_); - memtable_ = - new MemTable(internal_comparator_, ioptions, MutableCFOptions(options_), - wb, kMaxSequenceNumber, 0 /* column_family_id */); - memtable_->Ref(); - } - ~MemTableConstructor() override { delete memtable_->Unref(); } - Status FinishImpl(const Options&, const ImmutableOptions& ioptions, - const MutableCFOptions& /*moptions*/, - const BlockBasedTableOptions& /*table_options*/, - const InternalKeyComparator& /*internal_comparator*/, - const stl_wrappers::KVMap& kv_map) override { - delete memtable_->Unref(); - ImmutableOptions mem_ioptions(ioptions); - memtable_ = new MemTable(internal_comparator_, mem_ioptions, - MutableCFOptions(options_), write_buffer_manager_, - kMaxSequenceNumber, 0 /* column_family_id */); - memtable_->Ref(); - int seq = 1; - for (const auto& kv : kv_map) { - Status s = memtable_->Add(seq, kTypeValue, kv.first, kv.second, - nullptr /* kv_prot_info */); - if (!s.ok()) { - return s; - } - seq++; - } - return Status::OK(); - } - InternalIterator* NewIterator( - const SliceTransform* /*prefix_extractor*/) const override { - return new KeyConvertingIterator( - memtable_->NewIterator(ReadOptions(), &arena_), true); - } - - bool AnywayDeleteIterator() const override { return true; } - - bool IsArenaMode() const override { return true; } - - private: - mutable Arena arena_; - InternalKeyComparator internal_comparator_; - Options options_; - WriteBufferManager* write_buffer_manager_; - MemTable* memtable_; - std::shared_ptr table_factory_; -}; - -class InternalIteratorFromIterator : public InternalIterator { - public: - explicit InternalIteratorFromIterator(Iterator* it) : it_(it) {} - bool Valid() const override { return it_->Valid(); } - void Seek(const Slice& target) override { it_->Seek(target); } - void SeekForPrev(const Slice& target) override { it_->SeekForPrev(target); } - void SeekToFirst() override { it_->SeekToFirst(); } - void SeekToLast() override { it_->SeekToLast(); } - void Next() override { it_->Next(); } - void Prev() override { it_->Prev(); } - Slice key() const override { return it_->key(); } - Slice value() const override { return it_->value(); } - Status status() const override { return it_->status(); } - - private: - std::unique_ptr it_; -}; - -class DBConstructor : public Constructor { - public: - explicit DBConstructor(const Comparator* cmp) - : Constructor(cmp), comparator_(cmp) { - db_ = nullptr; - NewDB(); - } - ~DBConstructor() override { delete db_; } - Status FinishImpl(const Options& /*options*/, - const ImmutableOptions& /*ioptions*/, - const MutableCFOptions& /*moptions*/, - const BlockBasedTableOptions& /*table_options*/, - const InternalKeyComparator& /*internal_comparator*/, - const stl_wrappers::KVMap& kv_map) override { - delete db_; - db_ = nullptr; - NewDB(); - for (const auto& kv : kv_map) { - WriteBatch batch; - EXPECT_OK(batch.Put(kv.first, kv.second)); - EXPECT_TRUE(db_->Write(WriteOptions(), &batch).ok()); - } - return Status::OK(); - } - - InternalIterator* NewIterator( - const SliceTransform* /*prefix_extractor*/) const override { - return new InternalIteratorFromIterator(db_->NewIterator(ReadOptions())); - } - - DB* db() const override { return db_; } - - private: - void NewDB() { - std::string name = test::PerThreadDBPath("table_testdb"); - - Options options; - options.comparator = comparator_; - Status status = DestroyDB(name, options); - ASSERT_TRUE(status.ok()) << status.ToString(); - - options.create_if_missing = true; - options.error_if_exists = true; - options.write_buffer_size = 10000; // Something small to force merging - status = DB::Open(options, name, &db_); - ASSERT_TRUE(status.ok()) << status.ToString(); - } - - const Comparator* comparator_; - DB* db_; -}; - -enum TestType { - BLOCK_BASED_TABLE_TEST, - PLAIN_TABLE_SEMI_FIXED_PREFIX, - PLAIN_TABLE_FULL_STR_PREFIX, - PLAIN_TABLE_TOTAL_ORDER, - BLOCK_TEST, - MEMTABLE_TEST, - DB_TEST -}; - -struct TestArgs { - TestType type; - bool reverse_compare; - int restart_interval; - CompressionType compression; - uint32_t compression_parallel_threads; - uint32_t format_version; - bool use_mmap; -}; - -std::ostream& operator<<(std::ostream& os, const TestArgs& args) { - os << "type: " << args.type << " reverse_compare: " << args.reverse_compare - << " restart_interval: " << args.restart_interval - << " compression: " << args.compression - << " compression_parallel_threads: " << args.compression_parallel_threads - << " format_version: " << args.format_version - << " use_mmap: " << args.use_mmap; - - return os; -} - -static std::vector GenerateArgList() { - std::vector test_args; - std::vector test_types = {BLOCK_BASED_TABLE_TEST, - PLAIN_TABLE_SEMI_FIXED_PREFIX, - PLAIN_TABLE_FULL_STR_PREFIX, - PLAIN_TABLE_TOTAL_ORDER, - BLOCK_TEST, - MEMTABLE_TEST, - DB_TEST}; - std::vector reverse_compare_types = {false, true}; - std::vector restart_intervals = {16, 1, 1024}; - std::vector compression_parallel_threads = {1, 4}; - - // Only add compression if it is supported - std::vector> compression_types; - compression_types.emplace_back(kNoCompression, false); - if (Snappy_Supported()) { - compression_types.emplace_back(kSnappyCompression, false); - } - if (Zlib_Supported()) { - compression_types.emplace_back(kZlibCompression, false); - compression_types.emplace_back(kZlibCompression, true); - } - if (BZip2_Supported()) { - compression_types.emplace_back(kBZip2Compression, false); - compression_types.emplace_back(kBZip2Compression, true); - } - if (LZ4_Supported()) { - compression_types.emplace_back(kLZ4Compression, false); - compression_types.emplace_back(kLZ4Compression, true); - compression_types.emplace_back(kLZ4HCCompression, false); - compression_types.emplace_back(kLZ4HCCompression, true); - } - if (XPRESS_Supported()) { - compression_types.emplace_back(kXpressCompression, false); - compression_types.emplace_back(kXpressCompression, true); - } - if (ZSTD_Supported()) { - compression_types.emplace_back(kZSTD, false); - compression_types.emplace_back(kZSTD, true); - } - - for (auto test_type : test_types) { - for (auto reverse_compare : reverse_compare_types) { - if (test_type == PLAIN_TABLE_SEMI_FIXED_PREFIX || - test_type == PLAIN_TABLE_FULL_STR_PREFIX || - test_type == PLAIN_TABLE_TOTAL_ORDER) { - // Plain table doesn't use restart index or compression. - TestArgs one_arg; - one_arg.type = test_type; - one_arg.reverse_compare = reverse_compare; - one_arg.restart_interval = restart_intervals[0]; - one_arg.compression = compression_types[0].first; - one_arg.compression_parallel_threads = 1; - one_arg.format_version = 0; - one_arg.use_mmap = true; - test_args.push_back(one_arg); - one_arg.use_mmap = false; - test_args.push_back(one_arg); - continue; - } - - for (auto restart_interval : restart_intervals) { - for (auto compression_type : compression_types) { - for (auto num_threads : compression_parallel_threads) { - TestArgs one_arg; - one_arg.type = test_type; - one_arg.reverse_compare = reverse_compare; - one_arg.restart_interval = restart_interval; - one_arg.compression = compression_type.first; - one_arg.compression_parallel_threads = num_threads; - one_arg.format_version = compression_type.second ? 2 : 1; - one_arg.use_mmap = false; - test_args.push_back(one_arg); - } - } - } - } - } - return test_args; -} - -// In order to make all tests run for plain table format, including -// those operating on empty keys, create a new prefix transformer which -// return fixed prefix if the slice is not shorter than the prefix length, -// and the full slice if it is shorter. -class FixedOrLessPrefixTransform : public SliceTransform { - private: - const size_t prefix_len_; - - public: - explicit FixedOrLessPrefixTransform(size_t prefix_len) - : prefix_len_(prefix_len) {} - - const char* Name() const override { return "rocksdb.FixedPrefix"; } - - Slice Transform(const Slice& src) const override { - assert(InDomain(src)); - if (src.size() < prefix_len_) { - return src; - } - return Slice(src.data(), prefix_len_); - } - - bool InDomain(const Slice& /*src*/) const override { return true; } - - bool InRange(const Slice& dst) const override { - return (dst.size() <= prefix_len_); - } - bool FullLengthEnabled(size_t* /*len*/) const override { return false; } -}; - -class HarnessTest : public testing::Test { - public: - explicit HarnessTest(const TestArgs& args) - : args_(args), - ioptions_(options_), - moptions_(options_), - write_buffer_(options_.db_write_buffer_size), - support_prev_(true), - only_support_prefix_seek_(false) { - options_.compression = args_.compression; - options_.compression_opts.parallel_threads = - args_.compression_parallel_threads; - // Use shorter block size for tests to exercise block boundary - // conditions more. - if (args_.reverse_compare) { - options_.comparator = &reverse_key_comparator; - } - - internal_comparator_.reset( - new test::PlainInternalKeyComparator(options_.comparator)); - - options_.allow_mmap_reads = args_.use_mmap; - switch (args_.type) { - case BLOCK_BASED_TABLE_TEST: - table_options_.flush_block_policy_factory.reset( - new FlushBlockBySizePolicyFactory()); - table_options_.block_size = 256; - table_options_.block_restart_interval = args_.restart_interval; - table_options_.index_block_restart_interval = args_.restart_interval; - table_options_.format_version = args_.format_version; - options_.table_factory.reset( - new BlockBasedTableFactory(table_options_)); - constructor_.reset(new TableConstructor( - options_.comparator, true /* convert_to_internal_key_ */)); - internal_comparator_.reset( - new InternalKeyComparator(options_.comparator)); - break; - - case PLAIN_TABLE_SEMI_FIXED_PREFIX: - support_prev_ = false; - only_support_prefix_seek_ = true; - options_.prefix_extractor.reset(new FixedOrLessPrefixTransform(2)); - options_.table_factory.reset(NewPlainTableFactory()); - constructor_.reset(new TableConstructor( - options_.comparator, true /* convert_to_internal_key_ */)); - internal_comparator_.reset( - new InternalKeyComparator(options_.comparator)); - break; - case PLAIN_TABLE_FULL_STR_PREFIX: - support_prev_ = false; - only_support_prefix_seek_ = true; - options_.prefix_extractor.reset(NewNoopTransform()); - options_.table_factory.reset(NewPlainTableFactory()); - constructor_.reset(new TableConstructor( - options_.comparator, true /* convert_to_internal_key_ */)); - internal_comparator_.reset( - new InternalKeyComparator(options_.comparator)); - break; - case PLAIN_TABLE_TOTAL_ORDER: - support_prev_ = false; - only_support_prefix_seek_ = false; - options_.prefix_extractor = nullptr; - - { - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = kPlainTableVariableLength; - plain_table_options.bloom_bits_per_key = 0; - plain_table_options.hash_table_ratio = 0; - - options_.table_factory.reset( - NewPlainTableFactory(plain_table_options)); - } - constructor_.reset(new TableConstructor( - options_.comparator, true /* convert_to_internal_key_ */)); - internal_comparator_.reset( - new InternalKeyComparator(options_.comparator)); - break; - case BLOCK_TEST: - table_options_.block_size = 256; - options_.table_factory.reset( - new BlockBasedTableFactory(table_options_)); - constructor_.reset(new BlockConstructor(options_.comparator)); - break; - case MEMTABLE_TEST: - table_options_.block_size = 256; - options_.table_factory.reset( - new BlockBasedTableFactory(table_options_)); - constructor_.reset( - new MemTableConstructor(options_.comparator, &write_buffer_)); - break; - case DB_TEST: - table_options_.block_size = 256; - options_.table_factory.reset( - new BlockBasedTableFactory(table_options_)); - constructor_.reset(new DBConstructor(options_.comparator)); - break; - } - ioptions_ = ImmutableOptions(options_); - moptions_ = MutableCFOptions(options_); - } - - void Add(const std::string& key, const std::string& value) { - constructor_->Add(key, value); - } - - void Test(Random* rnd) { - std::vector keys; - stl_wrappers::KVMap data; - constructor_->Finish(options_, ioptions_, moptions_, table_options_, - *internal_comparator_, &keys, &data); - - TestForwardScan(keys, data); - if (support_prev_) { - TestBackwardScan(keys, data); - } - TestRandomAccess(rnd, keys, data); - } - - void TestForwardScan(const std::vector& /*keys*/, - const stl_wrappers::KVMap& data) { - InternalIterator* iter = constructor_->NewIterator(); - ASSERT_TRUE(!iter->Valid()); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - for (stl_wrappers::KVMap::const_iterator model_iter = data.begin(); - model_iter != data.end(); ++model_iter) { - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - iter->Next(); - ASSERT_OK(iter->status()); - } - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) { - iter->~InternalIterator(); - } else { - delete iter; - } - } - - void TestBackwardScan(const std::vector& /*keys*/, - const stl_wrappers::KVMap& data) { - InternalIterator* iter = constructor_->NewIterator(); - ASSERT_TRUE(!iter->Valid()); - iter->SeekToLast(); - ASSERT_OK(iter->status()); - for (stl_wrappers::KVMap::const_reverse_iterator model_iter = data.rbegin(); - model_iter != data.rend(); ++model_iter) { - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - iter->Prev(); - ASSERT_OK(iter->status()); - } - ASSERT_TRUE(!iter->Valid()); - ASSERT_OK(iter->status()); - if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) { - iter->~InternalIterator(); - } else { - delete iter; - } - } - - void TestRandomAccess(Random* rnd, const std::vector& keys, - const stl_wrappers::KVMap& data) { - static const bool kVerbose = false; - InternalIterator* iter = constructor_->NewIterator(); - ASSERT_TRUE(!iter->Valid()); - stl_wrappers::KVMap::const_iterator model_iter = data.begin(); - if (kVerbose) fprintf(stderr, "---\n"); - for (int i = 0; i < 200; i++) { - const int toss = rnd->Uniform(support_prev_ ? 5 : 3); - switch (toss) { - case 0: { - if (iter->Valid()) { - if (kVerbose) fprintf(stderr, "Next\n"); - iter->Next(); - ASSERT_OK(iter->status()); - ++model_iter; - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - } - break; - } - - case 1: { - if (kVerbose) fprintf(stderr, "SeekToFirst\n"); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - model_iter = data.begin(); - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - break; - } - - case 2: { - std::string key = PickRandomKey(rnd, keys); - model_iter = data.lower_bound(key); - if (kVerbose) - fprintf(stderr, "Seek '%s'\n", EscapeString(key).c_str()); - iter->Seek(Slice(key)); - ASSERT_OK(iter->status()); - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - break; - } - - case 3: { - if (iter->Valid()) { - if (kVerbose) fprintf(stderr, "Prev\n"); - iter->Prev(); - ASSERT_OK(iter->status()); - if (model_iter == data.begin()) { - model_iter = data.end(); // Wrap around to invalid value - } else { - --model_iter; - } - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - } - break; - } - - case 4: { - if (kVerbose) fprintf(stderr, "SeekToLast\n"); - iter->SeekToLast(); - ASSERT_OK(iter->status()); - if (keys.empty()) { - model_iter = data.end(); - } else { - std::string last = data.rbegin()->first; - model_iter = data.lower_bound(last); - } - ASSERT_EQ(ToString(data, model_iter), ToString(iter)); - break; - } - } - } - if (constructor_->IsArenaMode() && !constructor_->AnywayDeleteIterator()) { - iter->~InternalIterator(); - } else { - delete iter; - } - } - - std::string ToString(const stl_wrappers::KVMap& data, - const stl_wrappers::KVMap::const_iterator& it) { - if (it == data.end()) { - return "END"; - } else { - return "'" + it->first + "->" + it->second + "'"; - } - } - - std::string ToString(const stl_wrappers::KVMap& data, - const stl_wrappers::KVMap::const_reverse_iterator& it) { - if (it == data.rend()) { - return "END"; - } else { - return "'" + it->first + "->" + it->second + "'"; - } - } - - std::string ToString(const InternalIterator* it) { - if (!it->Valid()) { - return "END"; - } else { - return "'" + it->key().ToString() + "->" + it->value().ToString() + "'"; - } - } - - std::string PickRandomKey(Random* rnd, const std::vector& keys) { - if (keys.empty()) { - return "foo"; - } else { - const int index = rnd->Uniform(static_cast(keys.size())); - std::string result = keys[index]; - switch (rnd->Uniform(support_prev_ ? 3 : 1)) { - case 0: - // Return an existing key - break; - case 1: { - // Attempt to return something smaller than an existing key - if (result.size() > 0 && result[result.size() - 1] > '\0' && - (!only_support_prefix_seek_ || - options_.prefix_extractor->Transform(result).size() < - result.size())) { - result[result.size() - 1]--; - } - break; - } - case 2: { - // Return something larger than an existing key - Increment(options_.comparator, &result); - break; - } - } - return result; - } - } - - // Returns nullptr if not running against a DB - DB* db() const { return constructor_->db(); } - - private: - TestArgs args_; - Options options_; - ImmutableOptions ioptions_; - MutableCFOptions moptions_; - BlockBasedTableOptions table_options_; - std::unique_ptr constructor_; - WriteBufferManager write_buffer_; - bool support_prev_; - bool only_support_prefix_seek_; - std::shared_ptr internal_comparator_; -}; - -class ParameterizedHarnessTest : public HarnessTest, - public testing::WithParamInterface { - public: - ParameterizedHarnessTest() : HarnessTest(GetParam()) {} -}; - -INSTANTIATE_TEST_CASE_P(TableTest, ParameterizedHarnessTest, - ::testing::ValuesIn(GenerateArgList())); - -class DBHarnessTest : public HarnessTest { - public: - DBHarnessTest() - : HarnessTest(TestArgs{DB_TEST, /* reverse_compare */ false, - /* restart_interval */ 16, kNoCompression, - /* compression_parallel_threads */ 1, - /* format_version */ 0, /* use_mmap */ false}) {} -}; - -static bool Between(uint64_t val, uint64_t low, uint64_t high) { - bool result = (val >= low) && (val <= high); - if (!result) { - fprintf(stderr, "Value %llu is not in range [%llu, %llu]\n", - (unsigned long long)(val), (unsigned long long)(low), - (unsigned long long)(high)); - } - return result; -} - -// Tests against all kinds of tables -class TableTest : public testing::Test { - public: - const InternalKeyComparator& GetPlainInternalComparator( - const Comparator* comp) { - if (!plain_internal_comparator) { - plain_internal_comparator.reset( - new test::PlainInternalKeyComparator(comp)); - } - return *plain_internal_comparator; - } - void IndexTest(BlockBasedTableOptions table_options); - - private: - std::unique_ptr plain_internal_comparator; -}; - -class GeneralTableTest : public TableTest {}; -class BlockBasedTableTestBase : public TableTest {}; -class BlockBasedTableTest - : public BlockBasedTableTestBase, - virtual public ::testing::WithParamInterface { - public: - BlockBasedTableTest() : format_(GetParam()) { - env_ = ROCKSDB_NAMESPACE::Env::Default(); - } - - BlockBasedTableOptions GetBlockBasedTableOptions() { - BlockBasedTableOptions options; - options.format_version = format_; - return options; - } - - void SetupTracingTest(TableConstructor* c) { - test_path_ = test::PerThreadDBPath("block_based_table_tracing_test"); - EXPECT_OK(env_->CreateDir(test_path_)); - trace_file_path_ = test_path_ + "/block_cache_trace_file"; - - BlockCacheTraceWriterOptions trace_writer_opt; - BlockCacheTraceOptions trace_opt; - std::unique_ptr trace_writer; - EXPECT_OK(NewFileTraceWriter(env_, EnvOptions(), trace_file_path_, - &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - // Always return Status::OK(). - assert(c->block_cache_tracer_ - .StartTrace(trace_opt, std::move(block_cache_trace_writer)) - .ok()); - - { - std::string user_key = "k01"; - InternalKey internal_key(user_key, 0, kTypeValue); - std::string encoded_key = internal_key.Encode().ToString(); - c->Add(encoded_key, kDummyValue); - } - { - std::string user_key = "k02"; - InternalKey internal_key(user_key, 0, kTypeValue); - std::string encoded_key = internal_key.Encode().ToString(); - c->Add(encoded_key, kDummyValue); - } - } - - void VerifyBlockAccessTrace( - TableConstructor* c, - const std::vector& expected_records) { - c->block_cache_tracer_.EndTrace(); - - { - std::unique_ptr trace_reader; - Status s = NewFileTraceReader(env_, EnvOptions(), trace_file_path_, - &trace_reader); - EXPECT_OK(s); - BlockCacheTraceReader reader(std::move(trace_reader)); - BlockCacheTraceHeader header; - EXPECT_OK(reader.ReadHeader(&header)); - uint32_t index = 0; - while (s.ok()) { - BlockCacheTraceRecord access; - s = reader.ReadAccess(&access); - if (!s.ok()) { - break; - } - ASSERT_LT(index, expected_records.size()); - EXPECT_NE("", access.block_key); - EXPECT_EQ(access.block_type, expected_records[index].block_type); - EXPECT_GT(access.block_size, 0); - EXPECT_EQ(access.caller, expected_records[index].caller); - EXPECT_EQ(access.no_insert, expected_records[index].no_insert); - EXPECT_EQ(access.is_cache_hit, expected_records[index].is_cache_hit); - // Get - if (access.caller == TableReaderCaller::kUserGet) { - EXPECT_EQ(access.referenced_key, - expected_records[index].referenced_key); - EXPECT_EQ(access.get_id, expected_records[index].get_id); - EXPECT_EQ(access.get_from_user_specified_snapshot, - expected_records[index].get_from_user_specified_snapshot); - if (access.block_type == TraceType::kBlockTraceDataBlock) { - EXPECT_GT(access.referenced_data_size, 0); - EXPECT_GT(access.num_keys_in_block, 0); - EXPECT_EQ(access.referenced_key_exist_in_block, - expected_records[index].referenced_key_exist_in_block); - } - } else { - EXPECT_EQ(access.referenced_key, ""); - EXPECT_EQ(access.get_id, 0); - EXPECT_FALSE(access.get_from_user_specified_snapshot); - EXPECT_EQ(access.referenced_data_size, 0); - EXPECT_EQ(access.num_keys_in_block, 0); - EXPECT_FALSE(access.referenced_key_exist_in_block); - } - index++; - } - EXPECT_EQ(index, expected_records.size()); - } - EXPECT_OK(env_->DeleteFile(trace_file_path_)); - EXPECT_OK(env_->DeleteDir(test_path_)); - } - - protected: - uint64_t IndexUncompressedHelper(bool indexCompress); - - private: - uint32_t format_; - Env* env_; - std::string trace_file_path_; - std::string test_path_; -}; -class PlainTableTest : public TableTest {}; -class TablePropertyTest : public testing::Test {}; -class BBTTailPrefetchTest : public TableTest {}; - -// The helper class to test the file checksum -class FileChecksumTestHelper { - public: - FileChecksumTestHelper(bool convert_to_internal_key = false) - : convert_to_internal_key_(convert_to_internal_key) {} - ~FileChecksumTestHelper() {} - - void CreateWritableFile() { - sink_ = new test::StringSink(); - std::unique_ptr holder(sink_); - file_writer_.reset(new WritableFileWriter( - std::move(holder), "" /* don't care */, FileOptions())); - } - - void SetFileChecksumGenerator(FileChecksumGenerator* checksum_generator) { - if (file_writer_ != nullptr) { - file_writer_->TEST_SetFileChecksumGenerator(checksum_generator); - } else { - delete checksum_generator; - } - } - - WritableFileWriter* GetFileWriter() { return file_writer_.get(); } - - Status ResetTableBuilder(std::unique_ptr&& builder) { - assert(builder != nullptr); - table_builder_ = std::move(builder); - return Status::OK(); - } - - void AddKVtoKVMap(int num_entries) { - Random rnd(test::RandomSeed()); - for (int i = 0; i < num_entries; i++) { - std::string v = rnd.RandomString(100); - kv_map_[test::RandomKey(&rnd, 20)] = v; - } - } - - Status WriteKVAndFlushTable() { - for (const auto& kv : kv_map_) { - if (convert_to_internal_key_) { - ParsedInternalKey ikey(kv.first, kMaxSequenceNumber, kTypeValue); - std::string encoded; - AppendInternalKey(&encoded, ikey); - table_builder_->Add(encoded, kv.second); - } else { - table_builder_->Add(kv.first, kv.second); - } - EXPECT_TRUE(table_builder_->status().ok()); - } - Status s = table_builder_->Finish(); - EXPECT_OK(file_writer_->Flush()); - EXPECT_OK(s); - - EXPECT_EQ(sink_->contents().size(), table_builder_->FileSize()); - return s; - } - - std::string GetFileChecksum() { - EXPECT_OK(file_writer_->Close()); - return table_builder_->GetFileChecksum(); - } - - const char* GetFileChecksumFuncName() { - return table_builder_->GetFileChecksumFuncName(); - } - - Status CalculateFileChecksum(FileChecksumGenerator* file_checksum_generator, - std::string* checksum) { - assert(file_checksum_generator != nullptr); - cur_file_num_ = checksum_file_num_++; - test::StringSink* ss_rw = - static_cast(file_writer_->writable_file()); - std::unique_ptr source( - new test::StringSource(ss_rw->contents())); - file_reader_.reset(new RandomAccessFileReader(std::move(source), "test")); - - std::unique_ptr scratch(new char[2048]); - Slice result; - uint64_t offset = 0; - Status s; - s = file_reader_->Read(IOOptions(), offset, 2048, &result, scratch.get(), - nullptr, Env::IO_TOTAL /* rate_limiter_priority */); - if (!s.ok()) { - return s; - } - while (result.size() != 0) { - file_checksum_generator->Update(scratch.get(), result.size()); - offset += static_cast(result.size()); - s = file_reader_->Read(IOOptions(), offset, 2048, &result, scratch.get(), - nullptr, - Env::IO_TOTAL /* rate_limiter_priority */); - if (!s.ok()) { - return s; - } - } - EXPECT_EQ(offset, static_cast(table_builder_->FileSize())); - file_checksum_generator->Finalize(); - *checksum = file_checksum_generator->GetChecksum(); - return Status::OK(); - } - - private: - bool convert_to_internal_key_; - uint64_t cur_file_num_; - std::unique_ptr file_writer_; - std::unique_ptr file_reader_; - std::unique_ptr table_builder_; - stl_wrappers::KVMap kv_map_; - test::StringSink* sink_ = nullptr; - - static uint64_t checksum_file_num_; -}; - -uint64_t FileChecksumTestHelper::checksum_file_num_ = 1; - -INSTANTIATE_TEST_CASE_P(FormatVersions, BlockBasedTableTest, - testing::ValuesIn(test::kFooterFormatVersionsToTest)); - -// This test serves as the living tutorial for the prefix scan of user collected -// properties. -TEST_F(TablePropertyTest, PrefixScanTest) { - UserCollectedProperties props{ - {"num.111.1", "1"}, {"num.111.2", "2"}, {"num.111.3", "3"}, - {"num.333.1", "1"}, {"num.333.2", "2"}, {"num.333.3", "3"}, - {"num.555.1", "1"}, {"num.555.2", "2"}, {"num.555.3", "3"}, - }; - - // prefixes that exist - for (const std::string prefix : {"num.111", "num.333", "num.555"}) { - int num = 0; - for (auto pos = props.lower_bound(prefix); - pos != props.end() && - pos->first.compare(0, prefix.size(), prefix) == 0; - ++pos) { - ++num; - auto key = prefix + "." + std::to_string(num); - ASSERT_EQ(key, pos->first); - ASSERT_EQ(std::to_string(num), pos->second); - } - ASSERT_EQ(3, num); - } - - // prefixes that don't exist - for (const std::string prefix : - {"num.000", "num.222", "num.444", "num.666"}) { - auto pos = props.lower_bound(prefix); - ASSERT_TRUE(pos == props.end() || - pos->first.compare(0, prefix.size(), prefix) != 0); - } -} - -namespace { -struct TestIds { - UniqueId64x3 internal_id; - UniqueId64x3 external_id; -}; - -inline bool operator==(const TestIds& lhs, const TestIds& rhs) { - return lhs.internal_id == rhs.internal_id && - lhs.external_id == rhs.external_id; -} - -std::ostream& operator<<(std::ostream& os, const TestIds& ids) { - return os << std::hex << "{{{ 0x" << ids.internal_id[0] << "U, 0x" - << ids.internal_id[1] << "U, 0x" << ids.internal_id[2] - << "U }}, {{ 0x" << ids.external_id[0] << "U, 0x" - << ids.external_id[1] << "U, 0x" << ids.external_id[2] << "U }}}"; -} - -TestIds GetUniqueId(TableProperties* tp, std::unordered_set* seen, - const std::string& db_id, const std::string& db_session_id, - uint64_t file_number) { - // First test session id logic - if (db_session_id.size() == 20) { - uint64_t upper; - uint64_t lower; - EXPECT_OK(DecodeSessionId(db_session_id, &upper, &lower)); - EXPECT_EQ(EncodeSessionId(upper, lower), db_session_id); - } - - // Get external using public API - tp->db_id = db_id; - tp->db_session_id = db_session_id; - tp->orig_file_number = file_number; - TestIds t; - { - std::string euid; - EXPECT_OK(GetExtendedUniqueIdFromTableProperties(*tp, &euid)); - EXPECT_EQ(euid.size(), 24U); - t.external_id[0] = DecodeFixed64(&euid[0]); - t.external_id[1] = DecodeFixed64(&euid[8]); - t.external_id[2] = DecodeFixed64(&euid[16]); - - std::string uid; - EXPECT_OK(GetUniqueIdFromTableProperties(*tp, &uid)); - EXPECT_EQ(uid.size(), 16U); - EXPECT_EQ(uid, euid.substr(0, 16)); - EXPECT_EQ(t.external_id[0], DecodeFixed64(&uid[0])); - EXPECT_EQ(t.external_id[1], DecodeFixed64(&uid[8])); - } - // All these should be effectively random - EXPECT_TRUE(seen->insert(t.external_id[0]).second); - EXPECT_TRUE(seen->insert(t.external_id[1]).second); - EXPECT_TRUE(seen->insert(t.external_id[2]).second); - - // Get internal with internal API - EXPECT_OK(GetSstInternalUniqueId(db_id, db_session_id, file_number, - &t.internal_id)); - EXPECT_NE(t.internal_id, kNullUniqueId64x3); - - // Verify relationship - UniqueId64x3 tmp = t.internal_id; - InternalUniqueIdToExternal(&tmp); - EXPECT_EQ(tmp, t.external_id); - ExternalUniqueIdToInternal(&tmp); - EXPECT_EQ(tmp, t.internal_id); - - // And 128-bit internal version - UniqueId64x2 tmp2{}; - EXPECT_OK(GetSstInternalUniqueId(db_id, db_session_id, file_number, &tmp2)); - EXPECT_NE(tmp2, kNullUniqueId64x2); - - EXPECT_EQ(tmp2[0], t.internal_id[0]); - EXPECT_EQ(tmp2[1], t.internal_id[1]); - InternalUniqueIdToExternal(&tmp2); - EXPECT_EQ(tmp2[0], t.external_id[0]); - EXPECT_EQ(tmp2[1], t.external_id[1]); - ExternalUniqueIdToInternal(&tmp2); - EXPECT_EQ(tmp2[0], t.internal_id[0]); - EXPECT_EQ(tmp2[1], t.internal_id[1]); - - return t; -} -} // namespace - -TEST_F(TablePropertyTest, UniqueIdsSchemaAndQuality) { - // To ensure the computation only depends on the expected entries, we set - // the rest randomly - TableProperties tp; - TEST_SetRandomTableProperties(&tp); - - // DB id is normally RFC-4122 - const std::string db_id1 = "7265b6eb-4e42-4aec-86a4-0dc5e73a228d"; - // Allow other forms of DB id - const std::string db_id2 = "1728000184588763620"; - const std::string db_id3 = "x"; - - // DB session id is normally 20 chars in base-36, but 13 to 24 chars - // is ok, roughly 64 to 128 bits. - const std::string ses_id1 = "ABCDEFGHIJ0123456789"; - // Same trailing 13 digits - const std::string ses_id2 = "HIJ0123456789"; - const std::string ses_id3 = "0123ABCDEFGHIJ0123456789"; - // Different trailing 12 digits - const std::string ses_id4 = "ABCDEFGH888888888888"; - // And change length - const std::string ses_id5 = "ABCDEFGHIJ012"; - const std::string ses_id6 = "ABCDEFGHIJ0123456789ABCD"; - - using T = TestIds; - std::unordered_set seen; - // Establish a stable schema for the unique IDs. These values must not - // change for existing table files. - // (Note: parens needed for macro parsing, extra braces needed for some - // compilers.) - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id1, 1), - T({{{0x61d7dcf415d9cf19U, 0x160d77aae90757fdU, 0x907f41dfd90724ffU}}, - {{0xf0bd230365df7464U, 0xca089303f3648eb4U, 0x4b44f7e7324b2817U}}})); - // Only change internal_id[1] with file number - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id1, 2), - T({{{0x61d7dcf415d9cf19U, 0x160d77aae90757feU, 0x907f41dfd90724ffU}}, - {{0xf13fdf7adcfebb6dU, 0x97cd2226cc033ea2U, 0x198c438182091f0eU}}})); - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id1, 123456789), - T({{{0x61d7dcf415d9cf19U, 0x160d77aaee5c9ae9U, 0x907f41dfd90724ffU}}, - {{0x81fbcebe1ac6c4f0U, 0x6b14a64cfdc0f1c4U, 0x7d8fb6eaf18edbb3U}}})); - // Change internal_id[1] and internal_id[2] with db_id - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id2, ses_id1, 1), - T({{{0x61d7dcf415d9cf19U, 0xf89c471f572f0d25U, 0x1f0f2a5eb0e6257eU}}, - {{0x7f1d01d453616991U, 0x32ddf2afec804ab2U, 0xd10a1ee2f0c7d9c1U}}})); - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id3, ses_id1, 1), - T({{{0x61d7dcf415d9cf19U, 0xfed297a8154a57d0U, 0x8b931b9cdebd9e8U}}, - {{0x62b2f43183f6894bU, 0x897ff2b460eefad1U, 0xf4ec189fb2d15e04U}}})); - // Keeping same last 13 digits of ses_id keeps same internal_id[0] - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id2, 1), - T({{{0x61d7dcf415d9cf19U, 0x5f6cc4fa2d528c8U, 0x7b70845d5bfb5446U}}, - {{0x96d1c83ffcc94266U, 0x82663eac0ec6e14aU, 0x94a88b49678b77f6U}}})); - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id3, 1), - T({{{0x61d7dcf415d9cf19U, 0xfc7232879db37ea2U, 0xc0378d74ea4c89cdU}}, - {{0xdf2ef57e98776905U, 0xda5b31c987da833bU, 0x79c1b4bd0a9e760dU}}})); - // Changing last 12 digits of ses_id only changes internal_id[0] - // (vs. db_id1, ses_id1, 1) - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id4, 1), - T({{{0x4f07cc0d003a83a8U, 0x160d77aae90757fdU, 0x907f41dfd90724ffU}}, - {{0xbcf85336a9f71f04U, 0x4f2949e2f3adb60dU, 0x9ca0def976abfa10U}}})); - // ses_id can change everything. - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id5, 1), - T({{{0x94b8768e43f87ce6U, 0xc2559653ac4e7c93U, 0xde6dff6bbb1223U}}, - {{0x5a9537af681817fbU, 0x1afcd1fecaead5eaU, 0x767077ad9ebe0008U}}})); - EXPECT_EQ( - GetUniqueId(&tp, &seen, db_id1, ses_id6, 1), - T({{{0x43cfb0ffa3b710edU, 0x263c580426406a1bU, 0xfacc91379a80d29dU}}, - {{0xfa90547d84cb1cdbU, 0x2afe99c641992d4aU, 0x205b7f7b60e51cc2U}}})); - - // Now verify more thoroughly that any small change in inputs completely - // changes external unique id. - // (Relying on 'seen' checks etc. in GetUniqueId) - std::string db_id = "00000000-0000-0000-0000-000000000000"; - std::string ses_id = "000000000000000000000000"; - uint64_t file_num = 1; - // change db_id - for (size_t i = 0; i < db_id.size(); ++i) { - if (db_id[i] == '-') { - continue; - } - for (char alt : std::string("123456789abcdef")) { - db_id[i] = alt; - GetUniqueId(&tp, &seen, db_id, ses_id, file_num); - } - db_id[i] = '0'; - } - // change ses_id - for (size_t i = 0; i < ses_id.size(); ++i) { - for (char alt : std::string("123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")) { - ses_id[i] = alt; - GetUniqueId(&tp, &seen, db_id, ses_id, file_num); - } - ses_id[i] = '0'; - } - // change file_num - for (int i = 1; i < 64; ++i) { - GetUniqueId(&tp, &seen, db_id, ses_id, file_num << i); - } - - // Verify that "all zeros" in first 128 bits is equivalent for internal and - // external IDs. This way, as long as we avoid "all zeros" in internal IDs, - // we avoid it in external IDs. - { - UniqueId64x3 id1{{0, 0, Random::GetTLSInstance()->Next64()}}; - UniqueId64x3 id2 = id1; - InternalUniqueIdToExternal(&id1); - EXPECT_EQ(id1, id2); - ExternalUniqueIdToInternal(&id2); - EXPECT_EQ(id1, id2); - } -} - -namespace { -void SetGoodTableProperties(TableProperties* tp) { - // To ensure the computation only depends on the expected entries, we set - // the rest randomly - TEST_SetRandomTableProperties(tp); - tp->db_id = "7265b6eb-4e42-4aec-86a4-0dc5e73a228d"; - tp->db_session_id = "ABCDEFGHIJ0123456789"; - tp->orig_file_number = 1; -} -} // namespace - -TEST_F(TablePropertyTest, UniqueIdHumanStrings) { - TableProperties tp; - SetGoodTableProperties(&tp); - - std::string tmp; - EXPECT_OK(GetExtendedUniqueIdFromTableProperties(tp, &tmp)); - EXPECT_EQ(tmp, - (std::string{{'\x64', '\x74', '\xdf', '\x65', '\x03', '\x23', - '\xbd', '\xf0', '\xb4', '\x8e', '\x64', '\xf3', - '\x03', '\x93', '\x08', '\xca', '\x17', '\x28', - '\x4b', '\x32', '\xe7', '\xf7', '\x44', '\x4b'}})); - EXPECT_EQ(UniqueIdToHumanString(tmp), - "6474DF650323BDF0-B48E64F3039308CA-17284B32E7F7444B"); - - EXPECT_OK(GetUniqueIdFromTableProperties(tp, &tmp)); - EXPECT_EQ(UniqueIdToHumanString(tmp), "6474DF650323BDF0-B48E64F3039308CA"); - - // including zero padding - tmp = std::string(24U, '\0'); - tmp[15] = '\x12'; - tmp[23] = '\xAB'; - EXPECT_EQ(UniqueIdToHumanString(tmp), - "0000000000000000-0000000000000012-00000000000000AB"); - - // And shortened - tmp = std::string(20U, '\0'); - tmp[5] = '\x12'; - tmp[10] = '\xAB'; - tmp[17] = '\xEF'; - EXPECT_EQ(UniqueIdToHumanString(tmp), - "0000000000120000-0000AB0000000000-00EF0000"); - - tmp.resize(16); - EXPECT_EQ(UniqueIdToHumanString(tmp), "0000000000120000-0000AB0000000000"); - - tmp.resize(11); - EXPECT_EQ(UniqueIdToHumanString(tmp), "0000000000120000-0000AB"); - - tmp.resize(6); - EXPECT_EQ(UniqueIdToHumanString(tmp), "000000000012"); - - // Also internal IDs to human string - UniqueId64x3 euid = {12345, 678, 9}; - EXPECT_EQ(InternalUniqueIdToHumanString(&euid), "{12345,678,9}"); - - UniqueId64x2 uid = {1234, 567890}; - EXPECT_EQ(InternalUniqueIdToHumanString(&uid), "{1234,567890}"); -} - -TEST_F(TablePropertyTest, UniqueIdsFailure) { - TableProperties tp; - std::string tmp; - - // Missing DB id - SetGoodTableProperties(&tp); - tp.db_id = ""; - EXPECT_TRUE(GetUniqueIdFromTableProperties(tp, &tmp).IsNotSupported()); - EXPECT_TRUE( - GetExtendedUniqueIdFromTableProperties(tp, &tmp).IsNotSupported()); - - // Missing session id - SetGoodTableProperties(&tp); - tp.db_session_id = ""; - EXPECT_TRUE(GetUniqueIdFromTableProperties(tp, &tmp).IsNotSupported()); - EXPECT_TRUE( - GetExtendedUniqueIdFromTableProperties(tp, &tmp).IsNotSupported()); - - // Missing file number - SetGoodTableProperties(&tp); - tp.orig_file_number = 0; - EXPECT_TRUE(GetUniqueIdFromTableProperties(tp, &tmp).IsNotSupported()); - EXPECT_TRUE( - GetExtendedUniqueIdFromTableProperties(tp, &tmp).IsNotSupported()); -} - -// This test include all the basic checks except those for index size and block -// size, which will be conducted in separated unit tests. -TEST_P(BlockBasedTableTest, BasicBlockBasedTableProperties) { - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - - c.Add("a1", "val1"); - c.Add("b2", "val2"); - c.Add("c3", "val3"); - c.Add("d4", "val4"); - c.Add("e5", "val5"); - c.Add("f6", "val6"); - c.Add("g7", "val7"); - c.Add("h8", "val8"); - c.Add("j9", "val9"); - uint64_t diff_internal_user_bytes = 9 * 8; // 8 is seq size, 9 k-v totally - - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - options.compression = kNoCompression; - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_restart_interval = 1; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_NOT_COMPRESSED), 0); - - auto& props = *c.GetTableReader()->GetTableProperties(); - ASSERT_EQ(kvmap.size(), props.num_entries); - - auto raw_key_size = kvmap.size() * 2ul; - auto raw_value_size = kvmap.size() * 4ul; - - ASSERT_EQ(raw_key_size + diff_internal_user_bytes, props.raw_key_size); - ASSERT_EQ(raw_value_size, props.raw_value_size); - ASSERT_EQ(1ul, props.num_data_blocks); - ASSERT_EQ("", props.filter_policy_name); // no filter policy is used - - // Verify data size. - BlockBuilder block_builder(1); - for (const auto& item : kvmap) { - block_builder.Add(item.first, item.second); - } - Slice content = block_builder.Finish(); - ASSERT_EQ(content.size() + BlockBasedTable::kBlockTrailerSize + - diff_internal_user_bytes, - props.data_size); - c.ResetTableReader(); -} - -#ifdef SNAPPY -uint64_t BlockBasedTableTest::IndexUncompressedHelper(bool compressed) { - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - constexpr size_t kNumKeys = 10000; - - for (size_t k = 0; k < kNumKeys; ++k) { - c.Add("key" + std::to_string(k), "val" + std::to_string(k)); - } - - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - options.compression = kSnappyCompression; - options.statistics = CreateDBStatistics(); - options.statistics->set_stats_level(StatsLevel::kAll); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_restart_interval = 1; - table_options.enable_index_compression = compressed; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - c.ResetTableReader(); - return options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED); -} -TEST_P(BlockBasedTableTest, IndexUncompressed) { - uint64_t tbl1_compressed_cnt = IndexUncompressedHelper(true); - uint64_t tbl2_compressed_cnt = IndexUncompressedHelper(false); - // tbl1_compressed_cnt should include 1 index block - EXPECT_EQ(tbl2_compressed_cnt + 1, tbl1_compressed_cnt); -} -#endif // SNAPPY - -TEST_P(BlockBasedTableTest, BlockBasedTableProperties2) { - TableConstructor c(&reverse_key_comparator); - std::vector keys; - stl_wrappers::KVMap kvmap; - - { - Options options; - options.compression = CompressionType::kNoCompression; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - - auto& props = *c.GetTableReader()->GetTableProperties(); - - // Default comparator - ASSERT_EQ("leveldb.BytewiseComparator", props.comparator_name); - // No merge operator - ASSERT_EQ("nullptr", props.merge_operator_name); - // No prefix extractor - ASSERT_EQ("nullptr", props.prefix_extractor_name); - // No property collectors - ASSERT_EQ("[]", props.property_collectors_names); - // No filter policy is used - ASSERT_EQ("", props.filter_policy_name); - // Compression type == that set: - ASSERT_EQ("NoCompression", props.compression_name); - c.ResetTableReader(); - } - - { - Options options; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.comparator = &reverse_key_comparator; - options.merge_operator = MergeOperators::CreateUInt64AddOperator(); - options.prefix_extractor.reset(NewNoopTransform()); - options.table_properties_collector_factories.emplace_back( - new DummyPropertiesCollectorFactory1()); - options.table_properties_collector_factories.emplace_back( - new DummyPropertiesCollectorFactory2()); - - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - - auto& props = *c.GetTableReader()->GetTableProperties(); - - ASSERT_EQ("rocksdb.ReverseBytewiseComparator", props.comparator_name); - ASSERT_EQ("UInt64AddOperator", props.merge_operator_name); - ASSERT_EQ("rocksdb.Noop", props.prefix_extractor_name); - ASSERT_EQ( - "[DummyPropertiesCollectorFactory1,DummyPropertiesCollectorFactory2]", - props.property_collectors_names); - ASSERT_EQ("", props.filter_policy_name); // no filter policy is used - c.ResetTableReader(); - } -} - -TEST_P(BlockBasedTableTest, RangeDelBlock) { - TableConstructor c(BytewiseComparator()); - std::vector keys = {"1pika", "2chu"}; - std::vector vals = {"p", "c"}; - - std::vector expected_tombstones = { - {"1pika", "2chu", 0}, - {"2chu", "c", 1}, - {"2chu", "c", 0}, - {"c", "p", 0}, - }; - - for (int i = 0; i < 2; i++) { - RangeTombstone t(keys[i], vals[i], i); - std::pair p = t.Serialize(); - c.Add(p.first.Encode().ToString(), p.second); - } - - std::vector sorted_keys; - stl_wrappers::KVMap kvmap; - Options options; - options.compression = kNoCompression; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_restart_interval = 1; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - std::unique_ptr internal_cmp( - new InternalKeyComparator(options.comparator)); - c.Finish(options, ioptions, moptions, table_options, *internal_cmp, - &sorted_keys, &kvmap); - - for (int j = 0; j < 2; ++j) { - std::unique_ptr iter( - c.GetTableReader()->NewRangeTombstoneIterator(ReadOptions())); - if (j > 0) { - // For second iteration, delete the table reader object and verify the - // iterator can still access its metablock's range tombstones. - c.ResetTableReader(); - } - ASSERT_FALSE(iter->Valid()); - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - for (size_t i = 0; i < expected_tombstones.size(); i++) { - ASSERT_TRUE(iter->Valid()); - ParsedInternalKey parsed_key; - ASSERT_OK( - ParseInternalKey(iter->key(), &parsed_key, true /* log_err_key */)); - RangeTombstone t(parsed_key, iter->value()); - const auto& expected_t = expected_tombstones[i]; - ASSERT_EQ(t.start_key_, expected_t.start_key_); - ASSERT_EQ(t.end_key_, expected_t.end_key_); - ASSERT_EQ(t.seq_, expected_t.seq_); - iter->Next(); - } - ASSERT_TRUE(!iter->Valid()); - } -} - -TEST_P(BlockBasedTableTest, FilterPolicyNameProperties) { - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("a1", "val1"); - std::vector keys; - stl_wrappers::KVMap kvmap; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - Options options; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - auto& props = *c.GetTableReader()->GetTableProperties(); - ASSERT_EQ(table_options.filter_policy->Name(), props.filter_policy_name); - c.ResetTableReader(); -} - -// -// BlockBasedTableTest::PrefetchTest -// -void AssertKeysInCache(BlockBasedTable* table_reader, - const std::vector& keys_in_cache, - const std::vector& keys_not_in_cache, - bool convert = false) { - if (convert) { - for (auto key : keys_in_cache) { - InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); - ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); - } - for (auto key : keys_not_in_cache) { - InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); - ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); - } - } else { - for (auto key : keys_in_cache) { - ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), key)); - } - for (auto key : keys_not_in_cache) { - ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), key)); - } - } -} - -void PrefetchRange(TableConstructor* c, Options* opt, - BlockBasedTableOptions* table_options, const char* key_begin, - const char* key_end, - const std::vector& keys_in_cache, - const std::vector& keys_not_in_cache, - const Status expected_status = Status::OK()) { - // reset the cache and reopen the table - table_options->block_cache = NewLRUCache(16 * 1024 * 1024, 4); - opt->table_factory.reset(NewBlockBasedTableFactory(*table_options)); - const ImmutableOptions ioptions2(*opt); - const MutableCFOptions moptions(*opt); - ASSERT_OK(c->Reopen(ioptions2, moptions)); - - // prefetch - auto* table_reader = dynamic_cast(c->GetTableReader()); - Status s; - std::unique_ptr begin, end; - std::unique_ptr i_begin, i_end; - if (key_begin != nullptr) { - if (c->ConvertToInternalKey()) { - i_begin.reset(new InternalKey(key_begin, kMaxSequenceNumber, kTypeValue)); - begin.reset(new Slice(i_begin->Encode())); - } else { - begin.reset(new Slice(key_begin)); - } - } - if (key_end != nullptr) { - if (c->ConvertToInternalKey()) { - i_end.reset(new InternalKey(key_end, kMaxSequenceNumber, kTypeValue)); - end.reset(new Slice(i_end->Encode())); - } else { - end.reset(new Slice(key_end)); - } - } - s = table_reader->Prefetch(begin.get(), end.get()); - - ASSERT_TRUE(s.code() == expected_status.code()); - - // assert our expectation in cache warmup - AssertKeysInCache(table_reader, keys_in_cache, keys_not_in_cache, - c->ConvertToInternalKey()); - c->ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, PrefetchTest) { - // The purpose of this test is to test the prefetching operation built into - // BlockBasedTable. - Options opt; - std::unique_ptr ikc; - ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); - opt.compression = kNoCompression; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_size = 1024; - // big enough so we don't ever lose cached values. - table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); - opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("k01", "hello"); - c.Add("k02", "hello2"); - c.Add("k03", std::string(10000, 'x')); - c.Add("k04", std::string(200000, 'x')); - c.Add("k05", std::string(300000, 'x')); - c.Add("k06", "hello3"); - c.Add("k07", std::string(100000, 'x')); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(opt); - const MutableCFOptions moptions(opt); - c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); - c.ResetTableReader(); - - // We get the following data spread : - // - // Data block Index - // ======================== - // [ k01 k02 k03 ] k03 - // [ k04 ] k04 - // [ k05 ] k05 - // [ k06 k07 ] k07 - - // Simple - PrefetchRange(&c, &opt, &table_options, - /*key_range=*/"k01", "k05", - /*keys_in_cache=*/{"k01", "k02", "k03", "k04", "k05"}, - /*keys_not_in_cache=*/{"k06", "k07"}); - PrefetchRange(&c, &opt, &table_options, "k01", "k01", {"k01", "k02", "k03"}, - {"k04", "k05", "k06", "k07"}); - // odd - PrefetchRange(&c, &opt, &table_options, "a", "z", - {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); - PrefetchRange(&c, &opt, &table_options, "k00", "k00", {"k01", "k02", "k03"}, - {"k04", "k05", "k06", "k07"}); - // Edge cases - PrefetchRange(&c, &opt, &table_options, "k00", "k06", - {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); - PrefetchRange(&c, &opt, &table_options, "k00", "zzz", - {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); - // null keys - PrefetchRange(&c, &opt, &table_options, nullptr, nullptr, - {"k01", "k02", "k03", "k04", "k05", "k06", "k07"}, {}); - PrefetchRange(&c, &opt, &table_options, "k04", nullptr, - {"k04", "k05", "k06", "k07"}, {"k01", "k02", "k03"}); - PrefetchRange(&c, &opt, &table_options, nullptr, "k05", - {"k01", "k02", "k03", "k04", "k05"}, {"k06", "k07"}); - // invalid - PrefetchRange(&c, &opt, &table_options, "k06", "k00", {}, {}, - Status::InvalidArgument(Slice("k06 "), Slice("k07"))); - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, TotalOrderSeekOnHashIndex) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - for (int i = 0; i <= 4; ++i) { - Options options; - // Make each key/value an individual block - table_options.block_size = 64; - switch (i) { - case 0: - // Binary search index - table_options.index_type = BlockBasedTableOptions::kBinarySearch; - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - break; - case 1: - // Hash search index - table_options.index_type = BlockBasedTableOptions::kHashSearch; - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewFixedPrefixTransform(4)); - break; - case 2: - // Hash search index with filter policy - table_options.index_type = BlockBasedTableOptions::kHashSearch; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewFixedPrefixTransform(4)); - break; - case 3: - // Two-level index - table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - break; - case 4: - // Binary search with first key - table_options.index_type = - BlockBasedTableOptions::kBinarySearchWithFirstKey; - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - break; - } - - TableConstructor c(BytewiseComparator(), - true /* convert_to_internal_key_ */); - c.Add("aaaa1", std::string('a', 56)); - c.Add("bbaa1", std::string('a', 56)); - c.Add("cccc1", std::string('a', 56)); - c.Add("bbbb1", std::string('a', 56)); - c.Add("baaa1", std::string('a', 56)); - c.Add("abbb1", std::string('a', 56)); - c.Add("cccc2", std::string('a', 56)); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - auto props = c.GetTableReader()->GetTableProperties(); - ASSERT_EQ(7u, props->num_data_blocks); - auto* reader = c.GetTableReader(); - ReadOptions ro; - ro.total_order_seek = true; - std::unique_ptr iter(reader->NewIterator( - ro, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - iter->Seek(InternalKey("b", 0, kTypeValue).Encode()); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("baaa1", ExtractUserKey(iter->key()).ToString()); - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bbaa1", ExtractUserKey(iter->key()).ToString()); - - iter->Seek(InternalKey("bb", 0, kTypeValue).Encode()); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bbaa1", ExtractUserKey(iter->key()).ToString()); - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bbbb1", ExtractUserKey(iter->key()).ToString()); - - iter->Seek(InternalKey("bbb", 0, kTypeValue).Encode()); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bbbb1", ExtractUserKey(iter->key()).ToString()); - iter->Next(); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("cccc1", ExtractUserKey(iter->key()).ToString()); - } -} - -TEST_P(BlockBasedTableTest, NoopTransformSeek) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - - Options options; - options.comparator = BytewiseComparator(); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewNoopTransform()); - - TableConstructor c(options.comparator); - // To tickle the PrefixMayMatch bug it is important that the - // user-key is a single byte so that the index key exactly matches - // the user-key. - InternalKey key("a", 1, kTypeValue); - c.Add(key.Encode().ToString(), "b"); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - const InternalKeyComparator internal_comparator(options.comparator); - c.Finish(options, ioptions, moptions, table_options, internal_comparator, - &keys, &kvmap); - - auto* reader = c.GetTableReader(); - for (int i = 0; i < 2; ++i) { - ReadOptions ro; - ro.total_order_seek = (i == 0); - std::unique_ptr iter(reader->NewIterator( - ro, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - iter->Seek(key.Encode()); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("a", ExtractUserKey(iter->key()).ToString()); - } -} - -TEST_P(BlockBasedTableTest, SkipPrefixBloomFilter) { - // if DB is opened with a prefix extractor of a different name, - // prefix bloom is skipped when read the file - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.filter_policy.reset(NewBloomFilterPolicy(2)); - table_options.whole_key_filtering = false; - - Options options; - options.comparator = BytewiseComparator(); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - - TableConstructor c(options.comparator); - InternalKey key("abcdefghijk", 1, kTypeValue); - c.Add(key.Encode().ToString(), "test"); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - const InternalKeyComparator internal_comparator(options.comparator); - c.Finish(options, ioptions, moptions, table_options, internal_comparator, - &keys, &kvmap); - // TODO(Zhongyi): update test to use MutableCFOptions - options.prefix_extractor.reset(NewFixedPrefixTransform(9)); - const ImmutableOptions new_ioptions(options); - const MutableCFOptions new_moptions(options); - ASSERT_OK(c.Reopen(new_ioptions, new_moptions)); - auto reader = c.GetTableReader(); - ReadOptions read_options; - std::unique_ptr db_iter(reader->NewIterator( - read_options, new_moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - // Test point lookup - // only one kv - for (auto& kv : kvmap) { - db_iter->Seek(kv.first); - ASSERT_TRUE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - ASSERT_EQ(db_iter->key(), kv.first); - ASSERT_EQ(db_iter->value(), kv.second); - } -} - -TEST_P(BlockBasedTableTest, BadChecksumType) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - - Options options; - options.comparator = BytewiseComparator(); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - - TableConstructor c(options.comparator); - InternalKey key("abc", 1, kTypeValue); - c.Add(key.Encode().ToString(), "test"); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - const InternalKeyComparator internal_comparator(options.comparator); - c.Finish(options, ioptions, moptions, table_options, internal_comparator, - &keys, &kvmap); - - // Corrupt checksum type (123 is invalid) - auto& sink = *c.TEST_GetSink(); - size_t len = sink.contents_.size(); - ASSERT_EQ(sink.contents_[len - Footer::kNewVersionsEncodedLength], - table_options.checksum); - sink.contents_[len - Footer::kNewVersionsEncodedLength] = char{123}; - - // (Re-)Open table file with bad checksum type - const ImmutableOptions new_ioptions(options); - const MutableCFOptions new_moptions(options); - Status s = c.Reopen(new_ioptions, new_moptions); - ASSERT_NOK(s); - // "test" is file name - ASSERT_EQ(s.ToString(), - "Corruption: Corrupt or unsupported checksum type: 123 in test"); -} - -class BuiltinChecksumTest : public testing::Test, - public testing::WithParamInterface {}; - -INSTANTIATE_TEST_CASE_P(SupportedChecksums, BuiltinChecksumTest, - testing::ValuesIn(GetSupportedChecksums())); - -namespace { -std::string ChecksumAsString(const std::string& data, - ChecksumType checksum_type) { - uint32_t v = ComputeBuiltinChecksum(checksum_type, data.data(), data.size()); - - // Verify consistency with other function - if (data.size() >= 1) { - EXPECT_EQ(v, ComputeBuiltinChecksumWithLastByte( - checksum_type, data.data(), data.size() - 1, data.back())); - } - // Little endian as in file - std::array raw_bytes; - EncodeFixed32(raw_bytes.data(), v); - return Slice(raw_bytes.data(), raw_bytes.size()).ToString(/*hex*/ true); -} - -std::string ChecksumAsString(std::string* data, char new_last_byte, - ChecksumType checksum_type) { - data->back() = new_last_byte; - return ChecksumAsString(*data, checksum_type); -} -} // namespace - -// Make sure that checksum values don't change in later versions, even if -// consistent within current version. -TEST_P(BuiltinChecksumTest, ChecksumSchemas) { - // Trailing 'x' chars will be replaced by compression type. Specifically, - // the first byte of a block trailer is compression type, which is part of - // the checksum input. This test does not deal with storing or parsing - // checksums from the trailer (next 4 bytes of trailer). - std::string b0 = "x"; - std::string b1 = "This is a short block!x"; - std::string b2; - for (int i = 0; i < 100; ++i) { - b2.append("This is a long block!"); - } - b2.append("x"); - - std::string empty; - - char ct1 = kNoCompression; - char ct2 = kSnappyCompression; - char ct3 = kZSTD; - - ChecksumType t = GetParam(); - switch (t) { - case kNoChecksum: - EXPECT_EQ(ChecksumAsString(empty, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b0, ct1, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b0, ct2, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b0, ct3, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b1, ct1, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b1, ct2, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b1, ct3, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b2, ct1, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b2, ct2, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b2, ct3, t), "00000000"); - break; - case kCRC32c: - EXPECT_EQ(ChecksumAsString(empty, t), "D8EA82A2"); - EXPECT_EQ(ChecksumAsString(&b0, ct1, t), "D28F2549"); - EXPECT_EQ(ChecksumAsString(&b0, ct2, t), "052B2843"); - EXPECT_EQ(ChecksumAsString(&b0, ct3, t), "46F8F711"); - EXPECT_EQ(ChecksumAsString(&b1, ct1, t), "583F0355"); - EXPECT_EQ(ChecksumAsString(&b1, ct2, t), "2F9B0A57"); - EXPECT_EQ(ChecksumAsString(&b1, ct3, t), "ECE7DA1D"); - EXPECT_EQ(ChecksumAsString(&b2, ct1, t), "943EF0AB"); - EXPECT_EQ(ChecksumAsString(&b2, ct2, t), "43A2EDB1"); - EXPECT_EQ(ChecksumAsString(&b2, ct3, t), "00E53D63"); - break; - case kxxHash: - EXPECT_EQ(ChecksumAsString(empty, t), "055DCC02"); - EXPECT_EQ(ChecksumAsString(&b0, ct1, t), "3EB065CF"); - EXPECT_EQ(ChecksumAsString(&b0, ct2, t), "31F79238"); - EXPECT_EQ(ChecksumAsString(&b0, ct3, t), "320D2E00"); - EXPECT_EQ(ChecksumAsString(&b1, ct1, t), "4A2E5FB0"); - EXPECT_EQ(ChecksumAsString(&b1, ct2, t), "0BD9F652"); - EXPECT_EQ(ChecksumAsString(&b1, ct3, t), "B4107E50"); - EXPECT_EQ(ChecksumAsString(&b2, ct1, t), "20F4D4BA"); - EXPECT_EQ(ChecksumAsString(&b2, ct2, t), "8F1A1F99"); - EXPECT_EQ(ChecksumAsString(&b2, ct3, t), "A191A338"); - break; - case kxxHash64: - EXPECT_EQ(ChecksumAsString(empty, t), "99E9D851"); - EXPECT_EQ(ChecksumAsString(&b0, ct1, t), "682705DB"); - EXPECT_EQ(ChecksumAsString(&b0, ct2, t), "30E7211B"); - EXPECT_EQ(ChecksumAsString(&b0, ct3, t), "B7BB58E8"); - EXPECT_EQ(ChecksumAsString(&b1, ct1, t), "B74655EF"); - EXPECT_EQ(ChecksumAsString(&b1, ct2, t), "B6C8BBBE"); - EXPECT_EQ(ChecksumAsString(&b1, ct3, t), "AED9E3B4"); - EXPECT_EQ(ChecksumAsString(&b2, ct1, t), "0D4999FE"); - EXPECT_EQ(ChecksumAsString(&b2, ct2, t), "F5932423"); - EXPECT_EQ(ChecksumAsString(&b2, ct3, t), "6B31BAB1"); - break; - case kXXH3: - EXPECT_EQ(ChecksumAsString(empty, t), "00000000"); - EXPECT_EQ(ChecksumAsString(&b0, ct1, t), "C294D338"); - EXPECT_EQ(ChecksumAsString(&b0, ct2, t), "1B174353"); - EXPECT_EQ(ChecksumAsString(&b0, ct3, t), "2D0E20C8"); - EXPECT_EQ(ChecksumAsString(&b1, ct1, t), "B37FB5E6"); - EXPECT_EQ(ChecksumAsString(&b1, ct2, t), "6AFC258D"); - EXPECT_EQ(ChecksumAsString(&b1, ct3, t), "5CE54616"); - EXPECT_EQ(ChecksumAsString(&b2, ct1, t), "FA2D482E"); - EXPECT_EQ(ChecksumAsString(&b2, ct2, t), "23AED845"); - EXPECT_EQ(ChecksumAsString(&b2, ct3, t), "15B7BBDE"); - break; - default: - // Force this test to be updated on new ChecksumTypes - assert(false); - break; - } -} - -TEST_P(BuiltinChecksumTest, ChecksumZeroInputs) { - // Verify that no reasonably sized "all zeros" inputs produce "all zeros" - // output. Otherwise, "wiped" data could appear to be well-formed. - // Assuming essentially random assignment of output values, the likelihood - // of encountering checksum == 0 for an input not specifically crafted is - // 1 in 4 billion. - if (GetParam() == kNoChecksum) { - return; - } - // "Thorough" case is too slow for continouous testing - bool thorough = getenv("ROCKSDB_THOROUGH_CHECKSUM_TEST") != nullptr; - // Verified through 10M - size_t kMaxZerosLen = thorough ? 10000000 : 20000; - std::string zeros(kMaxZerosLen, '\0'); - - for (size_t len = 0; len < kMaxZerosLen; ++len) { - if (thorough && (len & 0xffffU) == 0) { - fprintf(stderr, "t=%u len=%u\n", (unsigned)GetParam(), (unsigned)len); - } - uint32_t v = ComputeBuiltinChecksum(GetParam(), zeros.data(), len); - if (v == 0U) { - // One exception case: - if (GetParam() == kXXH3 && len == 0) { - // This is not a big deal because assuming the block length is known - // from the block handle, which comes from a checksum-verified block, - // there is nothing to corrupt in a zero-length block. And when there - // is a block trailer with compression byte (as in block-based table), - // zero length checksummed data never arises. - continue; - } - // Only compute this on failure - SCOPED_TRACE("len=" + std::to_string(len)); - ASSERT_NE(v, 0U); - } - } -} - -void AddInternalKey(TableConstructor* c, const std::string& prefix, - std::string value = "v", int /*suffix_len*/ = 800) { - static Random rnd(1023); - InternalKey k(prefix + rnd.RandomString(800), 0, kTypeValue); - c->Add(k.Encode().ToString(), value); -} - -void TableTest::IndexTest(BlockBasedTableOptions table_options) { - TableConstructor c(BytewiseComparator()); - - // keys with prefix length 3, make sure the key/value is big enough to fill - // one block - AddInternalKey(&c, "0015"); - AddInternalKey(&c, "0035"); - - AddInternalKey(&c, "0054"); - AddInternalKey(&c, "0055"); - - AddInternalKey(&c, "0056"); - AddInternalKey(&c, "0057"); - - AddInternalKey(&c, "0058"); - AddInternalKey(&c, "0075"); - - AddInternalKey(&c, "0076"); - AddInternalKey(&c, "0095"); - - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - options.prefix_extractor.reset(NewFixedPrefixTransform(3)); - table_options.block_size = 1700; - table_options.block_cache = NewLRUCache(1024, 4); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, - &kvmap); - auto reader = c.GetTableReader(); - - auto props = reader->GetTableProperties(); - ASSERT_EQ(5u, props->num_data_blocks); - - // TODO(Zhongyi): update test to use MutableCFOptions - ReadOptions read_options; - std::unique_ptr index_iter(reader->NewIterator( - read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - // -- Find keys do not exist, but have common prefix. - std::vector prefixes = {"001", "003", "005", "007", "009"}; - std::vector lower_bound = { - keys[0], keys[1], keys[2], keys[7], keys[9], - }; - - // find the lower bound of the prefix - for (size_t i = 0; i < prefixes.size(); ++i) { - index_iter->Seek(InternalKey(prefixes[i], 0, kTypeValue).Encode()); - ASSERT_OK(index_iter->status()); - ASSERT_TRUE(index_iter->Valid()); - - // seek the first element in the block - ASSERT_EQ(lower_bound[i], index_iter->key().ToString()); - ASSERT_EQ("v", index_iter->value().ToString()); - } - - // find the upper bound of prefixes - std::vector upper_bound = { - keys[1], - keys[2], - keys[7], - keys[9], - }; - - // find existing keys - for (const auto& item : kvmap) { - auto ukey = ExtractUserKey(item.first).ToString(); - index_iter->Seek(ukey); - - // ASSERT_OK(regular_iter->status()); - ASSERT_OK(index_iter->status()); - - // ASSERT_TRUE(regular_iter->Valid()); - ASSERT_TRUE(index_iter->Valid()); - - ASSERT_EQ(item.first, index_iter->key().ToString()); - ASSERT_EQ(item.second, index_iter->value().ToString()); - } - - for (size_t i = 0; i < prefixes.size(); ++i) { - // the key is greater than any existing keys. - auto key = prefixes[i] + "9"; - index_iter->Seek(InternalKey(key, 0, kTypeValue).Encode()); - - ASSERT_TRUE(index_iter->status().ok() || index_iter->status().IsNotFound()); - ASSERT_TRUE(!index_iter->status().IsNotFound() || !index_iter->Valid()); - if (i == prefixes.size() - 1) { - // last key - ASSERT_TRUE(!index_iter->Valid()); - } else { - ASSERT_TRUE(index_iter->Valid()); - // seek the first element in the block - ASSERT_EQ(upper_bound[i], index_iter->key().ToString()); - ASSERT_EQ("v", index_iter->value().ToString()); - } - } - - // find keys with prefix that don't match any of the existing prefixes. - std::vector non_exist_prefixes = {"002", "004", "006", "008"}; - for (const auto& prefix : non_exist_prefixes) { - index_iter->Seek(InternalKey(prefix, 0, kTypeValue).Encode()); - // regular_iter->Seek(prefix); - - ASSERT_OK(index_iter->status()); - // Seek to non-existing prefixes should yield either invalid, or a - // key with prefix greater than the target. - if (index_iter->Valid()) { - Slice ukey = ExtractUserKey(index_iter->key()); - Slice ukey_prefix = options.prefix_extractor->Transform(ukey); - ASSERT_TRUE(BytewiseComparator()->Compare(prefix, ukey_prefix) < 0); - } - } - for (const auto& prefix : non_exist_prefixes) { - index_iter->SeekForPrev(InternalKey(prefix, 0, kTypeValue).Encode()); - // regular_iter->Seek(prefix); - - ASSERT_OK(index_iter->status()); - // Seek to non-existing prefixes should yield either invalid, or a - // key with prefix greater than the target. - if (index_iter->Valid()) { - Slice ukey = ExtractUserKey(index_iter->key()); - Slice ukey_prefix = options.prefix_extractor->Transform(ukey); - ASSERT_TRUE(BytewiseComparator()->Compare(prefix, ukey_prefix) > 0); - } - } - - { - // Test reseek case. It should impact partitioned index more. - ReadOptions ro; - ro.total_order_seek = true; - std::unique_ptr index_iter2(reader->NewIterator( - ro, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - // Things to cover in partitioned index: - // 1. Both of Seek() and SeekToLast() has optimization to prevent - // rereek leaf index block if it remains to the same one, and - // they reuse the same variable. - // 2. When Next() or Prev() is called, the block moves, so the - // optimization should kick in only with the current one. - index_iter2->Seek(InternalKey("0055", 0, kTypeValue).Encode()); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0055", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->SeekToLast(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->Seek(InternalKey("0055", 0, kTypeValue).Encode()); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0055", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->SeekToLast(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - index_iter2->Prev(); - ASSERT_TRUE(index_iter2->Valid()); - index_iter2->Prev(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->Seek(InternalKey("0095", 0, kTypeValue).Encode()); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - index_iter2->Prev(); - ASSERT_TRUE(index_iter2->Valid()); - index_iter2->Prev(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->SeekToLast(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->Seek(InternalKey("0095", 0, kTypeValue).Encode()); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->Prev(); - ASSERT_TRUE(index_iter2->Valid()); - index_iter2->Prev(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->Seek(InternalKey("0075", 0, kTypeValue).Encode()); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0075", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->Next(); - ASSERT_TRUE(index_iter2->Valid()); - index_iter2->Next(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - - index_iter2->SeekToLast(); - ASSERT_TRUE(index_iter2->Valid()); - ASSERT_EQ("0095", index_iter2->key().ToString().substr(0, 4)); - } - - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, BinaryIndexTest) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = BlockBasedTableOptions::kBinarySearch; - IndexTest(table_options); -} - -TEST_P(BlockBasedTableTest, HashIndexTest) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = BlockBasedTableOptions::kHashSearch; - IndexTest(table_options); -} - -TEST_P(BlockBasedTableTest, PartitionIndexTest) { - const int max_index_keys = 5; - const int est_max_index_key_value_size = 32; - const int est_max_index_size = max_index_keys * est_max_index_key_value_size; - for (int i = 1; i <= est_max_index_size + 1; i++) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; - table_options.metadata_block_size = i; - IndexTest(table_options); - } -} - -TEST_P(BlockBasedTableTest, IndexSeekOptimizationIncomplete) { - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - Options options; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - - TableConstructor c(BytewiseComparator()); - AddInternalKey(&c, "pika"); - - std::vector keys; - stl_wrappers::KVMap kvmap; - c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, - &kvmap); - ASSERT_EQ(1, keys.size()); - - auto reader = c.GetTableReader(); - ReadOptions ropt; - ropt.read_tier = ReadTier::kBlockCacheTier; - std::unique_ptr iter(reader->NewIterator( - ropt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - auto ikey = [](Slice user_key) { - return InternalKey(user_key, 0, kTypeValue).Encode().ToString(); - }; - - iter->Seek(ikey("pika")); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsIncomplete()); - - // This used to crash at some point. - iter->Seek(ikey("pika")); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->status().IsIncomplete()); -} - -TEST_P(BlockBasedTableTest, BinaryIndexWithFirstKey1) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = BlockBasedTableOptions::kBinarySearchWithFirstKey; - IndexTest(table_options); -} - -class CustomFlushBlockPolicy : public FlushBlockPolicyFactory, - public FlushBlockPolicy { - public: - explicit CustomFlushBlockPolicy(std::vector keys_per_block) - : keys_per_block_(keys_per_block) {} - - const char* Name() const override { return "CustomFlushBlockPolicy"; } - - FlushBlockPolicy* NewFlushBlockPolicy(const BlockBasedTableOptions&, - const BlockBuilder&) const override { - return new CustomFlushBlockPolicy(keys_per_block_); - } - - bool Update(const Slice&, const Slice&) override { - if (keys_in_current_block_ >= keys_per_block_.at(current_block_idx_)) { - ++current_block_idx_; - keys_in_current_block_ = 1; - return true; - } - - ++keys_in_current_block_; - return false; - } - - std::vector keys_per_block_; - - int current_block_idx_ = 0; - int keys_in_current_block_ = 0; -}; - -TEST_P(BlockBasedTableTest, BinaryIndexWithFirstKey2) { - for (int use_first_key = 0; use_first_key < 2; ++use_first_key) { - SCOPED_TRACE("use_first_key = " + std::to_string(use_first_key)); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = - use_first_key ? BlockBasedTableOptions::kBinarySearchWithFirstKey - : BlockBasedTableOptions::kBinarySearch; - table_options.block_cache = NewLRUCache(10000); // fits all blocks - table_options.index_shortening = - BlockBasedTableOptions::IndexShorteningMode::kNoShortening; - table_options.flush_block_policy_factory = - std::make_shared(std::vector{2, 1, 3, 2}); - Options options; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - options.statistics = CreateDBStatistics(); - Statistics* stats = options.statistics.get(); - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - - TableConstructor c(BytewiseComparator()); - - // Block 0. - AddInternalKey(&c, "aaaa", "v0"); - AddInternalKey(&c, "aaac", "v1"); - - // Block 1. - AddInternalKey(&c, "aaca", "v2"); - - // Block 2. - AddInternalKey(&c, "caaa", "v3"); - AddInternalKey(&c, "caac", "v4"); - AddInternalKey(&c, "caae", "v5"); - - // Block 3. - AddInternalKey(&c, "ccaa", "v6"); - AddInternalKey(&c, "ccac", "v7"); - - // Write the file. - std::vector keys; - stl_wrappers::KVMap kvmap; - c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, - &kvmap); - ASSERT_EQ(8, keys.size()); - - auto reader = c.GetTableReader(); - auto props = reader->GetTableProperties(); - ASSERT_EQ(4u, props->num_data_blocks); - ReadOptions read_options; - std::unique_ptr iter(reader->NewIterator( - read_options, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized, - /*compaction_readahead_size=*/0, /*allow_unprepared_value=*/true)); - - // Shouldn't have read data blocks before iterator is seeked. - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - auto ikey = [](Slice user_key) { - return InternalKey(user_key, 0, kTypeValue).Encode().ToString(); - }; - - // Seek to a key between blocks. If index contains first key, we shouldn't - // read any data blocks until value is requested. - iter->Seek(ikey("aaba")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[2], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 0 : 1, - stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v2", iter->value().ToString()); - EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Seek to the middle of a block. The block should be read right away. - iter->Seek(ikey("caab")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[4], iter->key().ToString()); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v4", iter->value().ToString()); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Seek to just before the same block and don't access value. - // The iterator should keep pinning the block contents. - iter->Seek(ikey("baaa")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[3], iter->key().ToString()); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Seek to the same block again to check that the block is still pinned. - iter->Seek(ikey("caae")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[5], iter->key().ToString()); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v5", iter->value().ToString()); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Step forward and fall through to the next block. Don't access value. - iter->Next(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[6], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 2 : 3, - stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Step forward again. Block should be read. - iter->Next(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[7], iter->key().ToString()); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v7", iter->value().ToString()); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Step forward and reach the end. - iter->Next(); - EXPECT_FALSE(iter->Valid()); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Seek to a single-key block and step forward without accessing value. - iter->Seek(ikey("aaca")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[2], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 0 : 1, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - iter->Next(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[3], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 1 : 2, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v3", iter->value().ToString()); - EXPECT_EQ(2, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - // Seek between blocks and step back without accessing value. - iter->Seek(ikey("aaca")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[2], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 2 : 3, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(3, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - iter->Prev(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[1], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 2 : 3, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - // All blocks are in cache now, there'll be no more misses ever. - EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v1", iter->value().ToString()); - - // Next into the next block again. - iter->Next(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[2], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 2 : 4, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Seek to first and step back without accessing value. - iter->SeekToFirst(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[0], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 2 : 5, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - iter->Prev(); - EXPECT_FALSE(iter->Valid()); - EXPECT_EQ(use_first_key ? 2 : 5, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - // Do some SeekForPrev() and SeekToLast() just to cover all methods. - iter->SeekForPrev(ikey("caad")); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[4], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 3 : 6, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v4", iter->value().ToString()); - EXPECT_EQ(use_first_key ? 3 : 6, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - iter->SeekToLast(); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(keys[7], iter->key().ToString()); - EXPECT_EQ(use_first_key ? 4 : 7, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("v7", iter->value().ToString()); - EXPECT_EQ(use_first_key ? 4 : 7, - stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - - EXPECT_EQ(4, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - c.ResetTableReader(); - } -} - -TEST_P(BlockBasedTableTest, BinaryIndexWithFirstKeyGlobalSeqno) { - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = BlockBasedTableOptions::kBinarySearchWithFirstKey; - table_options.block_cache = NewLRUCache(10000); - Options options; - options.statistics = CreateDBStatistics(); - Statistics* stats = options.statistics.get(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - - TableConstructor c(BytewiseComparator(), /* convert_to_internal_key */ false, - /* level */ -1, /* largest_seqno */ 42); - - c.Add(InternalKey("b", 0, kTypeValue).Encode().ToString(), "x"); - c.Add(InternalKey("c", 0, kTypeValue).Encode().ToString(), "y"); - - std::vector keys; - stl_wrappers::KVMap kvmap; - c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, - &kvmap); - ASSERT_EQ(2, keys.size()); - - auto reader = c.GetTableReader(); - auto props = reader->GetTableProperties(); - ASSERT_EQ(1u, props->num_data_blocks); - ReadOptions read_options; - std::unique_ptr iter(reader->NewIterator( - read_options, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized, - /*compaction_readahead_size=*/0, /*allow_unprepared_value=*/true)); - - iter->Seek(InternalKey("a", 0, kTypeValue).Encode().ToString()); - ASSERT_TRUE(iter->Valid()); - EXPECT_EQ(InternalKey("b", 42, kTypeValue).Encode().ToString(), - iter->key().ToString()); - EXPECT_NE(keys[0], iter->key().ToString()); - // Key should have been served from index, without reading data blocks. - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - - ASSERT_TRUE(iter->PrepareValue()); - EXPECT_EQ("x", iter->value().ToString()); - EXPECT_EQ(1, stats->getTickerCount(BLOCK_CACHE_DATA_MISS)); - EXPECT_EQ(0, stats->getTickerCount(BLOCK_CACHE_DATA_HIT)); - EXPECT_EQ(InternalKey("b", 42, kTypeValue).Encode().ToString(), - iter->key().ToString()); - - c.ResetTableReader(); -} - -// It's very hard to figure out the index block size of a block accurately. -// To make sure we get the index size, we just make sure as key number -// grows, the filter block size also grows. -TEST_P(BlockBasedTableTest, IndexSizeStat) { - uint64_t last_index_size = 0; - - // we need to use random keys since the pure human readable texts - // may be well compressed, resulting insignifcant change of index - // block size. - Random rnd(test::RandomSeed()); - std::vector keys; - - for (int i = 0; i < 100; ++i) { - keys.push_back(rnd.RandomString(10000)); - } - - // Each time we load one more key to the table. the table index block - // size is expected to be larger than last time's. - for (size_t i = 1; i < keys.size(); ++i) { - TableConstructor c(BytewiseComparator(), - true /* convert_to_internal_key_ */); - for (size_t j = 0; j < i; ++j) { - c.Add(keys[j], "val"); - } - - std::vector ks; - stl_wrappers::KVMap kvmap; - Options options; - options.compression = kNoCompression; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_restart_interval = 1; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &ks, &kvmap); - auto index_size = c.GetTableReader()->GetTableProperties()->index_size; - ASSERT_GT(index_size, last_index_size); - last_index_size = index_size; - c.ResetTableReader(); - } -} - -TEST_P(BlockBasedTableTest, NumBlockStat) { - Random rnd(test::RandomSeed()); - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - Options options; - options.compression = kNoCompression; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_restart_interval = 1; - table_options.block_size = 1000; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - for (int i = 0; i < 10; ++i) { - // the key/val are slightly smaller than block size, so that each block - // holds roughly one key/value pair. - c.Add(rnd.RandomString(900), "val"); - } - - std::vector ks; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &ks, &kvmap); - ASSERT_EQ(kvmap.size(), - c.GetTableReader()->GetTableProperties()->num_data_blocks); - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, TracingGetTest) { - TableConstructor c(BytewiseComparator()); - Options options; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.create_if_missing = true; - table_options.block_cache = NewLRUCache(1024 * 1024, 0); - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - SetupTracingTest(&c); - std::vector keys; - stl_wrappers::KVMap kvmap; - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - std::string user_key = "k01"; - InternalKey internal_key(user_key, 0, kTypeValue); - std::string encoded_key = internal_key.Encode().ToString(); - for (uint32_t i = 1; i <= 2; i++) { - PinnableSlice value; - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, nullptr, - nullptr, nullptr, true, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, /*tracing_get_id=*/i); - get_perf_context()->Reset(); - ASSERT_OK(c.GetTableReader()->Get(ReadOptions(), encoded_key, &get_context, - moptions.prefix_extractor.get())); - ASSERT_EQ(get_context.State(), GetContext::kFound); - ASSERT_EQ(value.ToString(), kDummyValue); - } - - // Verify traces. - std::vector expected_records; - // The first two records should be prefetching index and filter blocks. - BlockCacheTraceRecord record; - record.block_type = TraceType::kBlockTraceIndexBlock; - record.caller = TableReaderCaller::kPrefetch; - record.is_cache_hit = false; - record.no_insert = false; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceFilterBlock; - expected_records.push_back(record); - // Then we should have three records for one index, one filter, and one data - // block access. - record.get_id = 1; - record.block_type = TraceType::kBlockTraceFilterBlock; - record.caller = TableReaderCaller::kUserGet; - record.get_from_user_specified_snapshot = false; - record.referenced_key = encoded_key; - record.referenced_key_exist_in_block = true; - record.is_cache_hit = true; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceIndexBlock; - expected_records.push_back(record); - record.is_cache_hit = false; - record.block_type = TraceType::kBlockTraceDataBlock; - expected_records.push_back(record); - // The second get should all observe cache hits. - record.is_cache_hit = true; - record.get_id = 2; - record.block_type = TraceType::kBlockTraceFilterBlock; - record.caller = TableReaderCaller::kUserGet; - record.get_from_user_specified_snapshot = false; - record.referenced_key = encoded_key; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceIndexBlock; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceDataBlock; - expected_records.push_back(record); - VerifyBlockAccessTrace(&c, expected_records); - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, TracingApproximateOffsetOfTest) { - TableConstructor c(BytewiseComparator()); - Options options; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.create_if_missing = true; - table_options.block_cache = NewLRUCache(1024 * 1024, 0); - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - SetupTracingTest(&c); - std::vector keys; - stl_wrappers::KVMap kvmap; - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - for (uint32_t i = 1; i <= 2; i++) { - std::string user_key = "k01"; - InternalKey internal_key(user_key, 0, kTypeValue); - std::string encoded_key = internal_key.Encode().ToString(); - c.GetTableReader()->ApproximateOffsetOf( - encoded_key, TableReaderCaller::kUserApproximateSize); - } - // Verify traces. - std::vector expected_records; - // The first two records should be prefetching index and filter blocks. - BlockCacheTraceRecord record; - record.block_type = TraceType::kBlockTraceIndexBlock; - record.caller = TableReaderCaller::kPrefetch; - record.is_cache_hit = false; - record.no_insert = false; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceFilterBlock; - expected_records.push_back(record); - // Then we should have two records for only index blocks. - record.block_type = TraceType::kBlockTraceIndexBlock; - record.caller = TableReaderCaller::kUserApproximateSize; - record.is_cache_hit = true; - expected_records.push_back(record); - expected_records.push_back(record); - VerifyBlockAccessTrace(&c, expected_records); - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, TracingIterator) { - TableConstructor c(BytewiseComparator()); - Options options; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - options.create_if_missing = true; - table_options.block_cache = NewLRUCache(1024 * 1024, 0); - table_options.cache_index_and_filter_blocks = true; - table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - SetupTracingTest(&c); - std::vector keys; - stl_wrappers::KVMap kvmap; - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - - for (uint32_t i = 1; i <= 2; i++) { - ReadOptions read_options; - std::unique_ptr iter(c.GetTableReader()->NewIterator( - read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUserIterator)); - iter->SeekToFirst(); - while (iter->Valid()) { - iter->key(); - iter->value(); - iter->Next(); - } - ASSERT_OK(iter->status()); - iter.reset(); - } - - // Verify traces. - std::vector expected_records; - // The first two records should be prefetching index and filter blocks. - BlockCacheTraceRecord record; - record.block_type = TraceType::kBlockTraceIndexBlock; - record.caller = TableReaderCaller::kPrefetch; - record.is_cache_hit = false; - record.no_insert = false; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceFilterBlock; - expected_records.push_back(record); - // Then we should have three records for index and two data block access. - record.block_type = TraceType::kBlockTraceIndexBlock; - record.caller = TableReaderCaller::kUserIterator; - record.is_cache_hit = true; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceDataBlock; - record.is_cache_hit = false; - expected_records.push_back(record); - expected_records.push_back(record); - // When we iterate this file for the second time, we should observe all cache - // hits. - record.block_type = TraceType::kBlockTraceIndexBlock; - record.is_cache_hit = true; - expected_records.push_back(record); - record.block_type = TraceType::kBlockTraceDataBlock; - expected_records.push_back(record); - expected_records.push_back(record); - VerifyBlockAccessTrace(&c, expected_records); - c.ResetTableReader(); -} - -// A simple tool that takes the snapshot of block cache statistics. -class BlockCachePropertiesSnapshot { - public: - explicit BlockCachePropertiesSnapshot(Statistics* statistics) { - block_cache_miss = statistics->getTickerCount(BLOCK_CACHE_MISS); - block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_HIT); - index_block_cache_miss = statistics->getTickerCount(BLOCK_CACHE_INDEX_MISS); - index_block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_INDEX_HIT); - data_block_cache_miss = statistics->getTickerCount(BLOCK_CACHE_DATA_MISS); - data_block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_DATA_HIT); - filter_block_cache_miss = - statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS); - filter_block_cache_hit = statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT); - block_cache_bytes_read = statistics->getTickerCount(BLOCK_CACHE_BYTES_READ); - block_cache_bytes_write = - statistics->getTickerCount(BLOCK_CACHE_BYTES_WRITE); - } - - void AssertIndexBlockStat(int64_t expected_index_block_cache_miss, - int64_t expected_index_block_cache_hit) { - ASSERT_EQ(expected_index_block_cache_miss, index_block_cache_miss); - ASSERT_EQ(expected_index_block_cache_hit, index_block_cache_hit); - } - - void AssertFilterBlockStat(int64_t expected_filter_block_cache_miss, - int64_t expected_filter_block_cache_hit) { - ASSERT_EQ(expected_filter_block_cache_miss, filter_block_cache_miss); - ASSERT_EQ(expected_filter_block_cache_hit, filter_block_cache_hit); - } - - // Check if the fetched props matches the expected ones. - // TODO(kailiu) Use this only when you disabled filter policy! - void AssertEqual(int64_t expected_index_block_cache_miss, - int64_t expected_index_block_cache_hit, - int64_t expected_data_block_cache_miss, - int64_t expected_data_block_cache_hit) const { - ASSERT_EQ(expected_index_block_cache_miss, index_block_cache_miss); - ASSERT_EQ(expected_index_block_cache_hit, index_block_cache_hit); - ASSERT_EQ(expected_data_block_cache_miss, data_block_cache_miss); - ASSERT_EQ(expected_data_block_cache_hit, data_block_cache_hit); - ASSERT_EQ(expected_index_block_cache_miss + expected_data_block_cache_miss, - block_cache_miss); - ASSERT_EQ(expected_index_block_cache_hit + expected_data_block_cache_hit, - block_cache_hit); - } - - int64_t GetCacheBytesRead() { return block_cache_bytes_read; } - - int64_t GetCacheBytesWrite() { return block_cache_bytes_write; } - - private: - int64_t block_cache_miss = 0; - int64_t block_cache_hit = 0; - int64_t index_block_cache_miss = 0; - int64_t index_block_cache_hit = 0; - int64_t data_block_cache_miss = 0; - int64_t data_block_cache_hit = 0; - int64_t filter_block_cache_miss = 0; - int64_t filter_block_cache_hit = 0; - int64_t block_cache_bytes_read = 0; - int64_t block_cache_bytes_write = 0; -}; - -// Make sure, by default, index/filter blocks were pre-loaded (meaning we won't -// use block cache to store them). -TEST_P(BlockBasedTableTest, BlockCacheDisabledTest) { - Options options; - options.create_if_missing = true; - options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_cache = NewLRUCache(1024, 4); - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - std::vector keys; - stl_wrappers::KVMap kvmap; - - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("key", "value"); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - - // preloading filter/index blocks is enabled. - auto reader = dynamic_cast(c.GetTableReader()); - ASSERT_FALSE(reader->TEST_FilterBlockInCache()); - ASSERT_FALSE(reader->TEST_IndexBlockInCache()); - - { - // nothing happens in the beginning - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertIndexBlockStat(0, 0); - props.AssertFilterBlockStat(0, 0); - } - - { - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, Slice(), nullptr, nullptr, - nullptr, nullptr, true, nullptr, nullptr); - // a hack that just to trigger BlockBasedTable::GetFilter. - ASSERT_OK(reader->Get(ReadOptions(), "non-exist-key", &get_context, - moptions.prefix_extractor.get())); - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertIndexBlockStat(0, 0); - props.AssertFilterBlockStat(0, 0); - } -} - -// Due to the difficulities of the intersaction between statistics, this test -// only tests the case when "index block is put to block cache" -TEST_P(BlockBasedTableTest, FilterBlockInBlockCache) { - // -- Table construction - Options options; - options.create_if_missing = true; - options.statistics = CreateDBStatistics(); - - // Enable the cache for index/filter blocks - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - LRUCacheOptions co; - co.capacity = 2048; - co.num_shard_bits = 2; - co.metadata_charge_policy = kDontChargeCacheMetadata; - table_options.block_cache = NewLRUCache(co); - table_options.cache_index_and_filter_blocks = true; - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - std::vector keys; - stl_wrappers::KVMap kvmap; - - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("key", "value"); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - // preloading filter/index blocks is prohibited. - auto* reader = dynamic_cast(c.GetTableReader()); - ASSERT_FALSE(reader->TEST_FilterBlockInCache()); - ASSERT_TRUE(reader->TEST_IndexBlockInCache()); - - // -- PART 1: Open with regular block cache. - // Since block_cache is disabled, no cache activities will be involved. - std::unique_ptr iter; - - int64_t last_cache_bytes_read = 0; - // At first, no block will be accessed. - { - BlockCachePropertiesSnapshot props(options.statistics.get()); - // index will be added to block cache. - props.AssertEqual(1, // index block miss - 0, 0, 0); - ASSERT_EQ(props.GetCacheBytesRead(), 0); - ASSERT_EQ(props.GetCacheBytesWrite(), - static_cast(table_options.block_cache->GetUsage())); - last_cache_bytes_read = props.GetCacheBytesRead(); - } - - // Only index block will be accessed - { - iter.reset(c.NewIterator(moptions.prefix_extractor.get())); - BlockCachePropertiesSnapshot props(options.statistics.get()); - // NOTE: to help better highlight the "detla" of each ticker, I use - // + to indicate the increment of changed - // value; other numbers remain the same. - props.AssertEqual(1, 0 + 1, // index block hit - 0, 0); - // Cache hit, bytes read from cache should increase - ASSERT_GT(props.GetCacheBytesRead(), last_cache_bytes_read); - ASSERT_EQ(props.GetCacheBytesWrite(), - static_cast(table_options.block_cache->GetUsage())); - last_cache_bytes_read = props.GetCacheBytesRead(); - } - - // Only data block will be accessed - { - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertEqual(1, 1, 0 + 1, // data block miss - 0); - // Cache miss, Bytes read from cache should not change - ASSERT_EQ(props.GetCacheBytesRead(), last_cache_bytes_read); - ASSERT_EQ(props.GetCacheBytesWrite(), - static_cast(table_options.block_cache->GetUsage())); - last_cache_bytes_read = props.GetCacheBytesRead(); - } - - // Data block will be in cache - { - iter.reset(c.NewIterator(moptions.prefix_extractor.get())); - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertEqual(1, 1 + 1, /* index block hit */ - 1, 0 + 1 /* data block hit */); - // Cache hit, bytes read from cache should increase - ASSERT_GT(props.GetCacheBytesRead(), last_cache_bytes_read); - ASSERT_EQ(props.GetCacheBytesWrite(), - static_cast(table_options.block_cache->GetUsage())); - } - // release the iterator so that the block cache can reset correctly. - iter.reset(); - - c.ResetTableReader(); - - // -- PART 2: Open with very small block cache - // In this test, no block will ever get hit since the block cache is - // too small to fit even one entry. - table_options.block_cache = NewLRUCache(1, 4); - options.statistics = CreateDBStatistics(); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - const ImmutableOptions ioptions2(options); - const MutableCFOptions moptions2(options); - ASSERT_OK(c.Reopen(ioptions2, moptions2)); - { - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertEqual(1, // index block miss - 0, 0, 0); - // Cache miss, Bytes read from cache should not change - ASSERT_EQ(props.GetCacheBytesRead(), 0); - } - - { - // Both index and data block get accessed. - // It first cache index block then data block. But since the cache size - // is only 1, index block will be purged after data block is inserted. - iter.reset(c.NewIterator(moptions2.prefix_extractor.get())); - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertEqual(1 + 1, // index block miss - 0, 0, // data block miss - 0); - // Cache hit, bytes read from cache should increase - ASSERT_EQ(props.GetCacheBytesRead(), 0); - } - - { - // SeekToFirst() accesses data block. With similar reason, we expect data - // block's cache miss. - iter->SeekToFirst(); - ASSERT_OK(iter->status()); - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertEqual(2, 0, 0 + 1, // data block miss - 0); - // Cache miss, Bytes read from cache should not change - ASSERT_EQ(props.GetCacheBytesRead(), 0); - } - iter.reset(); - c.ResetTableReader(); - - // -- PART 3: Open table with bloom filter enabled but not in SST file - table_options.block_cache = NewLRUCache(4096, 4); - table_options.cache_index_and_filter_blocks = false; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - TableConstructor c3(BytewiseComparator()); - std::string user_key = "k01"; - InternalKey internal_key(user_key, 0, kTypeValue); - c3.Add(internal_key.Encode().ToString(), "hello"); - ImmutableOptions ioptions3(options); - MutableCFOptions moptions3(options); - // Generate table without filter policy - c3.Finish(options, ioptions3, moptions3, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - c3.ResetTableReader(); - - // Open table with filter policy - table_options.filter_policy.reset(NewBloomFilterPolicy(1)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - options.statistics = CreateDBStatistics(); - ImmutableOptions ioptions4(options); - MutableCFOptions moptions4(options); - ASSERT_OK(c3.Reopen(ioptions4, moptions4)); - reader = dynamic_cast(c3.GetTableReader()); - ASSERT_FALSE(reader->TEST_FilterBlockInCache()); - PinnableSlice value; - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, nullptr, - nullptr, nullptr, true, nullptr, nullptr); - ASSERT_OK(reader->Get(ReadOptions(), internal_key.Encode(), &get_context, - moptions4.prefix_extractor.get())); - ASSERT_STREQ(value.data(), "hello"); - BlockCachePropertiesSnapshot props(options.statistics.get()); - props.AssertFilterBlockStat(0, 0); - c3.ResetTableReader(); -} - -void ValidateBlockSizeDeviation(int value, int expected) { - BlockBasedTableOptions table_options; - table_options.block_size_deviation = value; - BlockBasedTableFactory* factory = new BlockBasedTableFactory(table_options); - - const BlockBasedTableOptions* normalized_table_options = - factory->GetOptions(); - ASSERT_EQ(normalized_table_options->block_size_deviation, expected); - - delete factory; -} - -void ValidateBlockRestartInterval(int value, int expected) { - BlockBasedTableOptions table_options; - table_options.block_restart_interval = value; - BlockBasedTableFactory* factory = new BlockBasedTableFactory(table_options); - - const BlockBasedTableOptions* normalized_table_options = - factory->GetOptions(); - ASSERT_EQ(normalized_table_options->block_restart_interval, expected); - - delete factory; -} - -TEST_P(BlockBasedTableTest, InvalidOptions) { - // invalid values for block_size_deviation (<0 or >100) are silently set to 0 - ValidateBlockSizeDeviation(-10, 0); - ValidateBlockSizeDeviation(-1, 0); - ValidateBlockSizeDeviation(0, 0); - ValidateBlockSizeDeviation(1, 1); - ValidateBlockSizeDeviation(99, 99); - ValidateBlockSizeDeviation(100, 100); - ValidateBlockSizeDeviation(101, 0); - ValidateBlockSizeDeviation(1000, 0); - - // invalid values for block_restart_interval (<1) are silently set to 1 - ValidateBlockRestartInterval(-10, 1); - ValidateBlockRestartInterval(-1, 1); - ValidateBlockRestartInterval(0, 1); - ValidateBlockRestartInterval(1, 1); - ValidateBlockRestartInterval(2, 2); - ValidateBlockRestartInterval(1000, 1000); -} - -TEST_P(BlockBasedTableTest, BlockReadCountTest) { - // bloom_filter_type = 1 -- full filter using use_block_based_builder=false - // bloom_filter_type = 2 -- full filter using use_block_based_builder=true - // because of API change to hide block-based filter - for (int bloom_filter_type = 1; bloom_filter_type <= 2; ++bloom_filter_type) { - for (int index_and_filter_in_cache = 0; index_and_filter_in_cache < 2; - ++index_and_filter_in_cache) { - Options options; - options.create_if_missing = true; - - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_cache = NewLRUCache(1, 0); - table_options.cache_index_and_filter_blocks = index_and_filter_in_cache; - table_options.filter_policy.reset( - NewBloomFilterPolicy(10, bloom_filter_type == 2)); - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - std::vector keys; - stl_wrappers::KVMap kvmap; - - TableConstructor c(BytewiseComparator()); - std::string user_key = "k04"; - InternalKey internal_key(user_key, 0, kTypeValue); - std::string encoded_key = internal_key.Encode().ToString(); - c.Add(encoded_key, "hello"); - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - // Generate table with filter policy - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - auto reader = c.GetTableReader(); - PinnableSlice value; - { - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, nullptr, - nullptr, nullptr, true, nullptr, nullptr); - get_perf_context()->Reset(); - ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context, - moptions.prefix_extractor.get())); - if (index_and_filter_in_cache) { - // data, index and filter block - ASSERT_EQ(get_perf_context()->block_read_count, 3); - ASSERT_EQ(get_perf_context()->index_block_read_count, 1); - ASSERT_EQ(get_perf_context()->filter_block_read_count, 1); - } else { - // just the data block - ASSERT_EQ(get_perf_context()->block_read_count, 1); - } - ASSERT_EQ(get_context.State(), GetContext::kFound); - ASSERT_STREQ(value.data(), "hello"); - } - - // Get non-existing key - user_key = "does-not-exist"; - internal_key = InternalKey(user_key, 0, kTypeValue); - encoded_key = internal_key.Encode().ToString(); - - value.Reset(); - { - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, nullptr, - nullptr, nullptr, true, nullptr, nullptr); - get_perf_context()->Reset(); - ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context, - moptions.prefix_extractor.get())); - ASSERT_EQ(get_context.State(), GetContext::kNotFound); - } - - if (index_and_filter_in_cache) { - if (bloom_filter_type == 0) { - // with block-based, we read index and then the filter - ASSERT_EQ(get_perf_context()->block_read_count, 2); - ASSERT_EQ(get_perf_context()->index_block_read_count, 1); - ASSERT_EQ(get_perf_context()->filter_block_read_count, 1); - } else { - // with full-filter, we read filter first and then we stop - ASSERT_EQ(get_perf_context()->block_read_count, 1); - ASSERT_EQ(get_perf_context()->filter_block_read_count, 1); - } - } else { - // filter is already in memory and it figures out that the key doesn't - // exist - ASSERT_EQ(get_perf_context()->block_read_count, 0); - } - } - } -} - -TEST_P(BlockBasedTableTest, BlockCacheLeak) { - // Check that when we reopen a table we don't lose access to blocks already - // in the cache. This test checks whether the Table actually makes use of the - // unique ID from the file. - - Options opt; - std::unique_ptr ikc; - ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); - opt.compression = kNoCompression; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.block_size = 1024; - // big enough so we don't ever lose cached values. - table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); - opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("k01", "hello"); - c.Add("k02", "hello2"); - c.Add("k03", std::string(10000, 'x')); - c.Add("k04", std::string(200000, 'x')); - c.Add("k05", std::string(300000, 'x')); - c.Add("k06", "hello3"); - c.Add("k07", std::string(100000, 'x')); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(opt); - const MutableCFOptions moptions(opt); - c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); - - std::unique_ptr iter( - c.NewIterator(moptions.prefix_extractor.get())); - iter->SeekToFirst(); - while (iter->Valid()) { - iter->key(); - iter->value(); - iter->Next(); - } - ASSERT_OK(iter->status()); - iter.reset(); - - const ImmutableOptions ioptions1(opt); - const MutableCFOptions moptions1(opt); - ASSERT_OK(c.Reopen(ioptions1, moptions1)); - auto table_reader = dynamic_cast(c.GetTableReader()); - for (const std::string& key : keys) { - InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); - ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); - } - c.ResetTableReader(); - - // rerun with different block cache - table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); - opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); - const ImmutableOptions ioptions2(opt); - const MutableCFOptions moptions2(opt); - ASSERT_OK(c.Reopen(ioptions2, moptions2)); - table_reader = dynamic_cast(c.GetTableReader()); - for (const std::string& key : keys) { - InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); - ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); - } - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, MemoryAllocator) { - auto default_memory_allocator = std::make_shared(); - auto custom_memory_allocator = - std::make_shared(default_memory_allocator); - { - Options opt; - std::unique_ptr ikc; - ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); - opt.compression = kNoCompression; - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - LRUCacheOptions lruOptions; - lruOptions.memory_allocator = custom_memory_allocator; - lruOptions.capacity = 16 * 1024 * 1024; - lruOptions.num_shard_bits = 4; - table_options.block_cache = NewLRUCache(std::move(lruOptions)); - opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - TableConstructor c(BytewiseComparator(), - true /* convert_to_internal_key_ */); - c.Add("k01", "hello"); - c.Add("k02", "hello2"); - c.Add("k03", std::string(10000, 'x')); - c.Add("k04", std::string(200000, 'x')); - c.Add("k05", std::string(300000, 'x')); - c.Add("k06", "hello3"); - c.Add("k07", std::string(100000, 'x')); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(opt); - const MutableCFOptions moptions(opt); - c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); - - std::unique_ptr iter( - c.NewIterator(moptions.prefix_extractor.get())); - iter->SeekToFirst(); - while (iter->Valid()) { - iter->key(); - iter->value(); - iter->Next(); - } - ASSERT_OK(iter->status()); - } - - // out of scope, block cache should have been deleted, all allocations - // deallocated - EXPECT_EQ(custom_memory_allocator->GetNumAllocations(), - custom_memory_allocator->GetNumDeallocations()); - // make sure that allocations actually happened through the cache allocator - EXPECT_GT(custom_memory_allocator->GetNumAllocations(), 0); -} - -// Test the file checksum of block based table -TEST_P(BlockBasedTableTest, NoFileChecksum) { - Options options; - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - int level = 0; - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - - FileChecksumTestHelper f(true); - f.CreateWritableFile(); - std::unique_ptr builder; - builder.reset(ioptions.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, *comparator, - &int_tbl_prop_collector_factories, - options.compression, options.compression_opts, - kUnknownColumnFamily, column_family_name, level), - f.GetFileWriter())); - ASSERT_OK(f.ResetTableBuilder(std::move(builder))); - f.AddKVtoKVMap(1000); - ASSERT_OK(f.WriteKVAndFlushTable()); - ASSERT_STREQ(f.GetFileChecksumFuncName(), kUnknownFileChecksumFuncName); - ASSERT_STREQ(f.GetFileChecksum().c_str(), kUnknownFileChecksum); -} - -TEST_P(BlockBasedTableTest, Crc32cFileChecksum) { - FileChecksumGenCrc32cFactory* file_checksum_gen_factory = - new FileChecksumGenCrc32cFactory(); - Options options; - options.file_checksum_gen_factory.reset(file_checksum_gen_factory); - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - int level = 0; - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - - FileChecksumGenContext gen_context; - gen_context.file_name = "db/tmp"; - std::unique_ptr checksum_crc32c_gen1 = - options.file_checksum_gen_factory->CreateFileChecksumGenerator( - gen_context); - FileChecksumTestHelper f(true); - f.CreateWritableFile(); - f.SetFileChecksumGenerator(checksum_crc32c_gen1.release()); - std::unique_ptr builder; - builder.reset(ioptions.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, *comparator, - &int_tbl_prop_collector_factories, - options.compression, options.compression_opts, - kUnknownColumnFamily, column_family_name, level), - f.GetFileWriter())); - ASSERT_OK(f.ResetTableBuilder(std::move(builder))); - f.AddKVtoKVMap(1000); - ASSERT_OK(f.WriteKVAndFlushTable()); - ASSERT_STREQ(f.GetFileChecksumFuncName(), "FileChecksumCrc32c"); - - std::unique_ptr checksum_crc32c_gen2 = - options.file_checksum_gen_factory->CreateFileChecksumGenerator( - gen_context); - std::string checksum; - ASSERT_OK(f.CalculateFileChecksum(checksum_crc32c_gen2.get(), &checksum)); - ASSERT_STREQ(f.GetFileChecksum().c_str(), checksum.c_str()); - - // Unit test the generator itself for schema stability - std::unique_ptr checksum_crc32c_gen3 = - options.file_checksum_gen_factory->CreateFileChecksumGenerator( - gen_context); - const char data[] = "here is some data"; - checksum_crc32c_gen3->Update(data, sizeof(data)); - checksum_crc32c_gen3->Finalize(); - checksum = checksum_crc32c_gen3->GetChecksum(); - ASSERT_STREQ(checksum.c_str(), "\345\245\277\110"); -} - -TEST_F(PlainTableTest, BasicPlainTableProperties) { - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 8; - plain_table_options.bloom_bits_per_key = 8; - plain_table_options.hash_table_ratio = 0; - - PlainTableFactory factory(plain_table_options); - std::unique_ptr sink(new test::StringSink()); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(sink), "" /* don't care */, FileOptions())); - Options options; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - int unknown_level = -1; - std::unique_ptr builder(factory.NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), kUnknownColumnFamily, - column_family_name, unknown_level), - file_writer.get())); - - for (char c = 'a'; c <= 'z'; ++c) { - std::string key(8, c); - key.append("\1 "); // PlainTable expects internal key structure - std::string value(28, c + 42); - builder->Add(key, value); - } - ASSERT_OK(builder->Finish()); - ASSERT_OK(file_writer->Flush()); - - test::StringSink* ss = - static_cast(file_writer->writable_file()); - std::unique_ptr source( - new test::StringSource(ss->contents(), 72242, true)); - std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(source), "test")); - - std::unique_ptr props; - auto s = ReadTableProperties(file_reader.get(), ss->contents().size(), - kPlainTableMagicNumber, ioptions, &props); - ASSERT_OK(s); - - ASSERT_EQ(0ul, props->index_size); - ASSERT_EQ(0ul, props->filter_size); - ASSERT_EQ(16ul * 26, props->raw_key_size); - ASSERT_EQ(28ul * 26, props->raw_value_size); - ASSERT_EQ(26ul, props->num_entries); - ASSERT_EQ(1ul, props->num_data_blocks); -} - -TEST_F(PlainTableTest, NoFileChecksum) { - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 20; - plain_table_options.bloom_bits_per_key = 8; - plain_table_options.hash_table_ratio = 0; - PlainTableFactory factory(plain_table_options); - - Options options; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - int unknown_level = -1; - FileChecksumTestHelper f(true); - f.CreateWritableFile(); - - std::unique_ptr builder(factory.NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), kUnknownColumnFamily, - column_family_name, unknown_level), - f.GetFileWriter())); - ASSERT_OK(f.ResetTableBuilder(std::move(builder))); - f.AddKVtoKVMap(1000); - ASSERT_OK(f.WriteKVAndFlushTable()); - ASSERT_STREQ(f.GetFileChecksumFuncName(), kUnknownFileChecksumFuncName); - EXPECT_EQ(f.GetFileChecksum(), kUnknownFileChecksum); -} - -TEST_F(PlainTableTest, Crc32cFileChecksum) { - PlainTableOptions plain_table_options; - plain_table_options.user_key_len = 20; - plain_table_options.bloom_bits_per_key = 8; - plain_table_options.hash_table_ratio = 0; - PlainTableFactory factory(plain_table_options); - - FileChecksumGenCrc32cFactory* file_checksum_gen_factory = - new FileChecksumGenCrc32cFactory(); - Options options; - options.file_checksum_gen_factory.reset(file_checksum_gen_factory); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - int unknown_level = -1; - - FileChecksumGenContext gen_context; - gen_context.file_name = "db/tmp"; - std::unique_ptr checksum_crc32c_gen1 = - options.file_checksum_gen_factory->CreateFileChecksumGenerator( - gen_context); - FileChecksumTestHelper f(true); - f.CreateWritableFile(); - f.SetFileChecksumGenerator(checksum_crc32c_gen1.release()); - - std::unique_ptr builder(factory.NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), kUnknownColumnFamily, - column_family_name, unknown_level), - f.GetFileWriter())); - ASSERT_OK(f.ResetTableBuilder(std::move(builder))); - f.AddKVtoKVMap(1000); - ASSERT_OK(f.WriteKVAndFlushTable()); - ASSERT_STREQ(f.GetFileChecksumFuncName(), "FileChecksumCrc32c"); - - std::unique_ptr checksum_crc32c_gen2 = - options.file_checksum_gen_factory->CreateFileChecksumGenerator( - gen_context); - std::string checksum; - ASSERT_OK(f.CalculateFileChecksum(checksum_crc32c_gen2.get(), &checksum)); - EXPECT_STREQ(f.GetFileChecksum().c_str(), checksum.c_str()); -} - - -TEST_F(GeneralTableTest, ApproximateOffsetOfPlain) { - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("k01", "hello"); - c.Add("k02", "hello2"); - c.Add("k03", std::string(10000, 'x')); - c.Add("k04", std::string(200000, 'x')); - c.Add("k05", std::string(300000, 'x')); - c.Add("k06", "hello3"); - c.Add("k07", std::string(100000, 'x')); - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - options.db_host_id = ""; - test::PlainInternalKeyComparator internal_comparator(options.comparator); - options.compression = kNoCompression; - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, internal_comparator, - &keys, &kvmap); - - ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01a"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k02"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 10000, 11000)); - // k04 and k05 will be in two consecutive blocks, the index is - // an arbitrary slice between k04 and k05, either before or after k04a - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04a"), 10000, 211000)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k05"), 210000, 211000)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k06"), 510000, 511000)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k07"), 510000, 511000)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 610000, 612000)); - c.ResetTableReader(); -} - -static void DoCompressionTest(CompressionType comp) { - Random rnd(301); - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - std::string tmp; - c.Add("k01", "hello"); - c.Add("k02", test::CompressibleString(&rnd, 0.25, 10000, &tmp)); - c.Add("k03", "hello3"); - c.Add("k04", test::CompressibleString(&rnd, 0.25, 10000, &tmp)); - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - test::PlainInternalKeyComparator ikc(options.comparator); - options.compression = comp; - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, ikc, &keys, &kvmap); - - ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k02"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 2000, 3525)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 2000, 3525)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 4000, 7075)); - c.ResetTableReader(); -} - -TEST_F(GeneralTableTest, ApproximateOffsetOfCompressed) { - std::vector compression_state; - if (!Snappy_Supported()) { - fprintf(stderr, "skipping snappy compression tests\n"); - } else { - compression_state.push_back(kSnappyCompression); - } - - if (!Zlib_Supported()) { - fprintf(stderr, "skipping zlib compression tests\n"); - } else { - compression_state.push_back(kZlibCompression); - } - - // TODO(kailiu) DoCompressionTest() doesn't work with BZip2. - /* - if (!BZip2_Supported()) { - fprintf(stderr, "skipping bzip2 compression tests\n"); - } else { - compression_state.push_back(kBZip2Compression); - } - */ - - if (!LZ4_Supported()) { - fprintf(stderr, "skipping lz4 and lz4hc compression tests\n"); - } else { - compression_state.push_back(kLZ4Compression); - compression_state.push_back(kLZ4HCCompression); - } - - if (!XPRESS_Supported()) { - fprintf(stderr, "skipping xpress and xpress compression tests\n"); - } else { - compression_state.push_back(kXpressCompression); - } - - for (auto state : compression_state) { - DoCompressionTest(state); - } -} - -TEST_F(GeneralTableTest, ApproximateKeyAnchors) { - Random rnd(301); - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - std::string tmp; - for (int i = 1000; i < 9000; i++) { - c.Add(std::to_string(i), rnd.RandomString(2000)); - } - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - InternalKeyComparator ikc(options.comparator); - options.compression = kNoCompression; - BlockBasedTableOptions table_options; - table_options.block_size = 4096; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, ikc, &keys, &kvmap); - - std::vector anchors; - ASSERT_OK(c.GetTableReader()->ApproximateKeyAnchors(ReadOptions(), anchors)); - // The target is 128 anchors. But in reality it can be slightly more or fewer. - ASSERT_GT(anchors.size(), 120); - ASSERT_LT(anchors.size(), 140); - - // We have around 8000 keys. With 128 anchors, in average 62.5 keys per - // anchor. Here we take a rough range and estimate the distance between - // anchors is between 50 and 100. - // Total data size is about 18,000,000, so each anchor range is about - // 140,625. We also take a rough range. - int prev_num = 1000; - // Non-last anchor - for (size_t i = 0; i + 1 < anchors.size(); i++) { - auto& anchor = anchors[i]; - ASSERT_GT(anchor.range_size, 100000); - ASSERT_LT(anchor.range_size, 200000); - - // Key might be shortened, so fill 0 in the end if it is the case. - std::string key_cpy = anchor.user_key; - key_cpy.append(4 - key_cpy.size(), '0'); - int num = std::stoi(key_cpy); - ASSERT_GT(num - prev_num, 50); - ASSERT_LT(num - prev_num, 100); - prev_num = num; - } - - ASSERT_EQ("8999", anchors.back().user_key); - ASSERT_LT(anchors.back().range_size, 200000); - - c.ResetTableReader(); -} - -#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) -TEST_P(ParameterizedHarnessTest, RandomizedHarnessTest) { - Random rnd(test::RandomSeed() + 5); - for (int num_entries = 0; num_entries < 2000; - num_entries += (num_entries < 50 ? 1 : 200)) { - for (int e = 0; e < num_entries; e++) { - Add(test::RandomKey(&rnd, rnd.Skewed(4)), - rnd.RandomString(rnd.Skewed(5))); - } - Test(&rnd); - } -} - -TEST_F(DBHarnessTest, RandomizedLongDB) { - Random rnd(test::RandomSeed()); - int num_entries = 100000; - for (int e = 0; e < num_entries; e++) { - std::string v; - Add(test::RandomKey(&rnd, rnd.Skewed(4)), rnd.RandomString(rnd.Skewed(5))); - } - Test(&rnd); - - // We must have created enough data to force merging - int files = 0; - for (int level = 0; level < db()->NumberLevels(); level++) { - std::string value; - char name[100]; - snprintf(name, sizeof(name), "rocksdb.num-files-at-level%d", level); - ASSERT_TRUE(db()->GetProperty(name, &value)); - files += atoi(value.c_str()); - } - ASSERT_GT(files, 0); -} -#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN) - -class MemTableTest : public testing::Test { - public: - MemTableTest() { - InternalKeyComparator cmp(BytewiseComparator()); - auto table_factory = std::make_shared(); - options_.memtable_factory = table_factory; - ImmutableOptions ioptions(options_); - wb_ = new WriteBufferManager(options_.db_write_buffer_size); - memtable_ = new MemTable(cmp, ioptions, MutableCFOptions(options_), wb_, - kMaxSequenceNumber, 0 /* column_family_id */); - memtable_->Ref(); - } - - ~MemTableTest() { - delete memtable_->Unref(); - delete wb_; - } - - MemTable* GetMemTable() { return memtable_; } - - private: - MemTable* memtable_; - Options options_; - WriteBufferManager* wb_; -}; - -TEST_F(MemTableTest, Simple) { - WriteBatch batch; - WriteBatchInternal::SetSequence(&batch, 100); - ASSERT_OK(batch.Put(std::string("k1"), std::string("v1"))); - ASSERT_OK(batch.Put(std::string("k2"), std::string("v2"))); - ASSERT_OK(batch.Put(std::string("k3"), std::string("v3"))); - ASSERT_OK(batch.Put(std::string("largekey"), std::string("vlarge"))); - ASSERT_OK(batch.DeleteRange(std::string("chi"), std::string("xigua"))); - ASSERT_OK(batch.DeleteRange(std::string("begin"), std::string("end"))); - ColumnFamilyMemTablesDefault cf_mems_default(GetMemTable()); - ASSERT_TRUE( - WriteBatchInternal::InsertInto(&batch, &cf_mems_default, nullptr, nullptr) - .ok()); - - for (int i = 0; i < 2; ++i) { - Arena arena; - ScopedArenaIterator arena_iter_guard; - std::unique_ptr iter_guard; - InternalIterator* iter; - if (i == 0) { - iter = GetMemTable()->NewIterator(ReadOptions(), &arena); - arena_iter_guard.set(iter); - } else { - iter = GetMemTable()->NewRangeTombstoneIterator( - ReadOptions(), kMaxSequenceNumber /* read_seq */, - false /* immutable_memtable */); - iter_guard.reset(iter); - } - if (iter == nullptr) { - continue; - } - iter->SeekToFirst(); - while (iter->Valid()) { - fprintf(stderr, "key: '%s' -> '%s'\n", iter->key().ToString().c_str(), - iter->value().ToString().c_str()); - iter->Next(); - } - } -} - -// Test the empty key -TEST_P(ParameterizedHarnessTest, SimpleEmptyKey) { - Random rnd(test::RandomSeed() + 1); - Add("", "v"); - Test(&rnd); -} - -TEST_P(ParameterizedHarnessTest, SimpleSingle) { - Random rnd(test::RandomSeed() + 2); - Add("abc", "v"); - Test(&rnd); -} - -TEST_P(ParameterizedHarnessTest, SimpleMulti) { - Random rnd(test::RandomSeed() + 3); - Add("abc", "v"); - Add("abcd", "v"); - Add("ac", "v2"); - Test(&rnd); -} - -TEST_P(ParameterizedHarnessTest, SimpleSpecialKey) { - Random rnd(test::RandomSeed() + 4); - Add("\xff\xff", "v3"); - Test(&rnd); -} - -TEST(TableTest, FooterTests) { - Random* r = Random::GetTLSInstance(); - uint64_t data_size = (uint64_t{1} << r->Uniform(40)) + r->Uniform(100); - uint64_t index_size = r->Uniform(1000000000); - uint64_t metaindex_size = r->Uniform(1000000); - // 5 == block trailer size - BlockHandle index(data_size + 5, index_size); - BlockHandle meta_index(data_size + index_size + 2 * 5, metaindex_size); - uint64_t footer_offset = data_size + metaindex_size + index_size + 3 * 5; - { - // legacy block based - FooterBuilder footer; - footer.Build(kBlockBasedTableMagicNumber, /* format_version */ 0, - footer_offset, kCRC32c, meta_index, index); - Footer decoded_footer; - ASSERT_OK(decoded_footer.DecodeFrom(footer.GetSlice(), footer_offset)); - ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber); - ASSERT_EQ(decoded_footer.checksum_type(), kCRC32c); - ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); - ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); - ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); - ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); - ASSERT_EQ(decoded_footer.format_version(), 0U); - ASSERT_EQ(decoded_footer.GetBlockTrailerSize(), 5U); - // Ensure serialized with legacy magic - ASSERT_EQ( - DecodeFixed64(footer.GetSlice().data() + footer.GetSlice().size() - 8), - kLegacyBlockBasedTableMagicNumber); - } - // block based, various checksums, various versions - for (auto t : GetSupportedChecksums()) { - for (uint32_t fv = 1; IsSupportedFormatVersion(fv); ++fv) { - FooterBuilder footer; - footer.Build(kBlockBasedTableMagicNumber, fv, footer_offset, t, - meta_index, index); - Footer decoded_footer; - ASSERT_OK(decoded_footer.DecodeFrom(footer.GetSlice(), footer_offset)); - ASSERT_EQ(decoded_footer.table_magic_number(), - kBlockBasedTableMagicNumber); - ASSERT_EQ(decoded_footer.checksum_type(), t); - ASSERT_EQ(decoded_footer.metaindex_handle().offset(), - meta_index.offset()); - ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); - ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); - ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); - ASSERT_EQ(decoded_footer.format_version(), fv); - ASSERT_EQ(decoded_footer.GetBlockTrailerSize(), 5U); - } - } - - { - // legacy plain table - FooterBuilder footer; - footer.Build(kPlainTableMagicNumber, /* format_version */ 0, footer_offset, - kNoChecksum, meta_index); - Footer decoded_footer; - ASSERT_OK(decoded_footer.DecodeFrom(footer.GetSlice(), footer_offset)); - ASSERT_EQ(decoded_footer.table_magic_number(), kPlainTableMagicNumber); - ASSERT_EQ(decoded_footer.checksum_type(), kCRC32c); - ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); - ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); - ASSERT_EQ(decoded_footer.index_handle().offset(), 0U); - ASSERT_EQ(decoded_footer.index_handle().size(), 0U); - ASSERT_EQ(decoded_footer.format_version(), 0U); - ASSERT_EQ(decoded_footer.GetBlockTrailerSize(), 0U); - // Ensure serialized with legacy magic - ASSERT_EQ( - DecodeFixed64(footer.GetSlice().data() + footer.GetSlice().size() - 8), - kLegacyPlainTableMagicNumber); - } - { - // xxhash plain table (not currently used) - FooterBuilder footer; - footer.Build(kPlainTableMagicNumber, /* format_version */ 1, footer_offset, - kxxHash, meta_index); - Footer decoded_footer; - ASSERT_OK(decoded_footer.DecodeFrom(footer.GetSlice(), footer_offset)); - ASSERT_EQ(decoded_footer.table_magic_number(), kPlainTableMagicNumber); - ASSERT_EQ(decoded_footer.checksum_type(), kxxHash); - ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); - ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); - ASSERT_EQ(decoded_footer.index_handle().offset(), 0U); - ASSERT_EQ(decoded_footer.index_handle().size(), 0U); - ASSERT_EQ(decoded_footer.format_version(), 1U); - ASSERT_EQ(decoded_footer.GetBlockTrailerSize(), 0U); - } -} - -class IndexBlockRestartIntervalTest - : public TableTest, - public ::testing::WithParamInterface> { - public: - static std::vector> GetRestartValues() { - return {{-1, false}, {0, false}, {1, false}, {8, false}, - {16, false}, {32, false}, {-1, true}, {0, true}, - {1, true}, {8, true}, {16, true}, {32, true}}; - } -}; - -INSTANTIATE_TEST_CASE_P( - IndexBlockRestartIntervalTest, IndexBlockRestartIntervalTest, - ::testing::ValuesIn(IndexBlockRestartIntervalTest::GetRestartValues())); - -TEST_P(IndexBlockRestartIntervalTest, IndexBlockRestartInterval) { - const int kKeysInTable = 10000; - const int kKeySize = 100; - const int kValSize = 500; - - const int index_block_restart_interval = std::get<0>(GetParam()); - const bool value_delta_encoding = std::get<1>(GetParam()); - - Options options; - BlockBasedTableOptions table_options; - table_options.block_size = 64; // small block size to get big index block - table_options.index_block_restart_interval = index_block_restart_interval; - if (value_delta_encoding) { - table_options.format_version = 4; - } else { - table_options.format_version = 3; - } - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - - TableConstructor c(BytewiseComparator()); - static Random rnd(301); - for (int i = 0; i < kKeysInTable; i++) { - InternalKey k(rnd.RandomString(kKeySize), 0, kTypeValue); - c.Add(k.Encode().ToString(), rnd.RandomString(kValSize)); - } - - std::vector keys; - stl_wrappers::KVMap kvmap; - std::unique_ptr comparator( - new InternalKeyComparator(BytewiseComparator())); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, - &kvmap); - auto reader = c.GetTableReader(); - - ReadOptions read_options; - std::unique_ptr db_iter(reader->NewIterator( - read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - // Test point lookup - for (auto& kv : kvmap) { - db_iter->Seek(kv.first); - - ASSERT_TRUE(db_iter->Valid()); - ASSERT_OK(db_iter->status()); - ASSERT_EQ(db_iter->key(), kv.first); - ASSERT_EQ(db_iter->value(), kv.second); - } - - // Test iterating - auto kv_iter = kvmap.begin(); - for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { - ASSERT_EQ(db_iter->key(), kv_iter->first); - ASSERT_EQ(db_iter->value(), kv_iter->second); - kv_iter++; - } - ASSERT_EQ(kv_iter, kvmap.end()); - c.ResetTableReader(); -} - -class PrefixTest : public testing::Test { - public: - PrefixTest() : testing::Test() {} - ~PrefixTest() override {} -}; - -namespace { -// A simple PrefixExtractor that only works for test PrefixAndWholeKeyTest -class TestPrefixExtractor : public ROCKSDB_NAMESPACE::SliceTransform { - public: - ~TestPrefixExtractor() override{}; - const char* Name() const override { return "TestPrefixExtractor"; } - - ROCKSDB_NAMESPACE::Slice Transform( - const ROCKSDB_NAMESPACE::Slice& src) const override { - assert(IsValid(src)); - return ROCKSDB_NAMESPACE::Slice(src.data(), 3); - } - - bool InDomain(const ROCKSDB_NAMESPACE::Slice& src) const override { - return IsValid(src); - } - - bool InRange(const ROCKSDB_NAMESPACE::Slice& /*dst*/) const override { - return true; - } - - bool IsValid(const ROCKSDB_NAMESPACE::Slice& src) const { - if (src.size() != 4) { - return false; - } - if (src[0] != '[') { - return false; - } - if (src[1] < '0' || src[1] > '9') { - return false; - } - if (src[2] != ']') { - return false; - } - if (src[3] < '0' || src[3] > '9') { - return false; - } - return true; - } -}; -} // namespace - -TEST_F(PrefixTest, PrefixAndWholeKeyTest) { - ROCKSDB_NAMESPACE::Options options; - options.compaction_style = ROCKSDB_NAMESPACE::kCompactionStyleUniversal; - options.num_levels = 20; - options.create_if_missing = true; - options.optimize_filters_for_hits = false; - options.target_file_size_base = 268435456; - options.prefix_extractor = std::make_shared(); - ROCKSDB_NAMESPACE::BlockBasedTableOptions bbto; - bbto.filter_policy.reset(ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10)); - bbto.block_size = 262144; - bbto.whole_key_filtering = true; - - const std::string kDBPath = test::PerThreadDBPath("table_prefix_test"); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - ASSERT_OK(DestroyDB(kDBPath, options)); - ROCKSDB_NAMESPACE::DB* db; - ASSERT_OK(ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &db)); - - // Create a bunch of keys with 10 filters. - for (int i = 0; i < 10; i++) { - std::string prefix = "[" + std::to_string(i) + "]"; - for (int j = 0; j < 10; j++) { - std::string key = prefix + std::to_string(j); - ASSERT_OK(db->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, "1")); - } - } - - // Trigger compaction. - ASSERT_OK(db->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - delete db; - // In the second round, turn whole_key_filtering off and expect - // rocksdb still works. -} - -/* - * Disable TableWithGlobalSeqno since RocksDB does not store global_seqno in - * the SST file any more. Instead, RocksDB deduces global_seqno from the - * MANIFEST while reading from an SST. Therefore, it's not possible to test the - * functionality of global_seqno in a single, isolated unit test without the - * involvement of Version, VersionSet, etc. - */ -TEST_P(BlockBasedTableTest, DISABLED_TableWithGlobalSeqno) { - BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); - test::StringSink* sink = new test::StringSink(); - std::unique_ptr holder(sink); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(holder), "" /* don't care */, FileOptions())); - Options options; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - int_tbl_prop_collector_factories.emplace_back( - new SstFileWriterPropertiesCollectorFactory(2 /* version */, - 0 /* global_seqno*/)); - std::string column_family_name; - std::unique_ptr builder(options.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), kUnknownColumnFamily, - column_family_name, -1), - file_writer.get())); - - for (char c = 'a'; c <= 'z'; ++c) { - std::string key(8, c); - std::string value = key; - InternalKey ik(key, 0, kTypeValue); - - builder->Add(ik.Encode(), value); - } - ASSERT_OK(builder->Finish()); - ASSERT_OK(file_writer->Flush()); - - test::RandomRWStringSink ss_rw(sink); - uint32_t version; - uint64_t global_seqno; - uint64_t global_seqno_offset; - - // Helper function to get version, global_seqno, global_seqno_offset - std::function GetVersionAndGlobalSeqno = [&]() { - std::unique_ptr source( - new test::StringSource(ss_rw.contents(), 73342, true)); - std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(source), "")); - - std::unique_ptr props; - ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(), - kBlockBasedTableMagicNumber, ioptions, - &props)); - - UserCollectedProperties user_props = props->user_collected_properties; - version = DecodeFixed32( - user_props[ExternalSstFilePropertyNames::kVersion].c_str()); - global_seqno = DecodeFixed64( - user_props[ExternalSstFilePropertyNames::kGlobalSeqno].c_str()); - global_seqno_offset = props->external_sst_file_global_seqno_offset; - }; - - // Helper function to update the value of the global seqno in the file - std::function SetGlobalSeqno = [&](uint64_t val) { - std::string new_global_seqno; - PutFixed64(&new_global_seqno, val); - - ASSERT_OK(ss_rw.Write(global_seqno_offset, new_global_seqno, IOOptions(), - nullptr)); - }; - - // Helper function to get the contents of the table InternalIterator - std::unique_ptr table_reader; - const ReadOptions read_options; - std::function GetTableInternalIter = [&]() { - std::unique_ptr source( - new test::StringSource(ss_rw.contents(), 73342, true)); - std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(source), "")); - - options.table_factory->NewTableReader( - TableReaderOptions(ioptions, moptions.prefix_extractor, EnvOptions(), - ikc), - std::move(file_reader), ss_rw.contents().size(), &table_reader); - - return table_reader->NewIterator( - read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized); - }; - - GetVersionAndGlobalSeqno(); - ASSERT_EQ(2u, version); - ASSERT_EQ(0u, global_seqno); - - InternalIterator* iter = GetTableInternalIter(); - char current_c = 'a'; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ParsedInternalKey pik; - ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); - - ASSERT_EQ(pik.type, ValueType::kTypeValue); - ASSERT_EQ(pik.sequence, 0); - ASSERT_EQ(pik.user_key, iter->value()); - ASSERT_EQ(pik.user_key.ToString(), std::string(8, current_c)); - current_c++; - } - ASSERT_EQ(current_c, 'z' + 1); - delete iter; - - // Update global sequence number to 10 - SetGlobalSeqno(10); - GetVersionAndGlobalSeqno(); - ASSERT_EQ(2u, version); - ASSERT_EQ(10u, global_seqno); - - iter = GetTableInternalIter(); - current_c = 'a'; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ParsedInternalKey pik; - ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); - - ASSERT_EQ(pik.type, ValueType::kTypeValue); - ASSERT_EQ(pik.sequence, 10); - ASSERT_EQ(pik.user_key, iter->value()); - ASSERT_EQ(pik.user_key.ToString(), std::string(8, current_c)); - current_c++; - } - ASSERT_EQ(current_c, 'z' + 1); - - // Verify Seek - for (char c = 'a'; c <= 'z'; c++) { - std::string k = std::string(8, c); - InternalKey ik(k, 10, kValueTypeForSeek); - iter->Seek(ik.Encode()); - ASSERT_TRUE(iter->Valid()); - - ParsedInternalKey pik; - ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); - - ASSERT_EQ(pik.type, ValueType::kTypeValue); - ASSERT_EQ(pik.sequence, 10); - ASSERT_EQ(pik.user_key.ToString(), k); - ASSERT_EQ(iter->value().ToString(), k); - } - delete iter; - - // Update global sequence number to 3 - SetGlobalSeqno(3); - GetVersionAndGlobalSeqno(); - ASSERT_EQ(2u, version); - ASSERT_EQ(3u, global_seqno); - - iter = GetTableInternalIter(); - current_c = 'a'; - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - ParsedInternalKey pik; - ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); - - ASSERT_EQ(pik.type, ValueType::kTypeValue); - ASSERT_EQ(pik.sequence, 3); - ASSERT_EQ(pik.user_key, iter->value()); - ASSERT_EQ(pik.user_key.ToString(), std::string(8, current_c)); - current_c++; - } - ASSERT_EQ(current_c, 'z' + 1); - - // Verify Seek - for (char c = 'a'; c <= 'z'; c++) { - std::string k = std::string(8, c); - // seqno=4 is less than 3 so we still should get our key - InternalKey ik(k, 4, kValueTypeForSeek); - iter->Seek(ik.Encode()); - ASSERT_TRUE(iter->Valid()); - - ParsedInternalKey pik; - ASSERT_OK(ParseInternalKey(iter->key(), &pik, true /* log_err_key */)); - - ASSERT_EQ(pik.type, ValueType::kTypeValue); - ASSERT_EQ(pik.sequence, 3); - ASSERT_EQ(pik.user_key.ToString(), k); - ASSERT_EQ(iter->value().ToString(), k); - } - - delete iter; -} - -TEST_P(BlockBasedTableTest, BlockAlignTest) { - BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); - bbto.block_align = true; - test::StringSink* sink = new test::StringSink(); - std::unique_ptr holder(sink); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(holder), "" /* don't care */, FileOptions())); - Options options; - options.compression = kNoCompression; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - std::unique_ptr builder(options.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), kUnknownColumnFamily, - column_family_name, -1), - file_writer.get())); - - for (int i = 1; i <= 10000; ++i) { - std::ostringstream ostr; - ostr << std::setfill('0') << std::setw(5) << i; - std::string key = ostr.str(); - std::string value = "val"; - InternalKey ik(key, 0, kTypeValue); - - builder->Add(ik.Encode(), value); - } - ASSERT_OK(builder->Finish()); - ASSERT_OK(file_writer->Flush()); - - std::unique_ptr source( - new test::StringSource(sink->contents(), 73342, false)); - std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(source), "test")); - // Helper function to get version, global_seqno, global_seqno_offset - std::function VerifyBlockAlignment = [&]() { - std::unique_ptr props; - ASSERT_OK(ReadTableProperties(file_reader.get(), sink->contents().size(), - kBlockBasedTableMagicNumber, ioptions, - &props)); - - uint64_t data_block_size = props->data_size / props->num_data_blocks; - ASSERT_EQ(data_block_size, 4096); - ASSERT_EQ(props->data_size, data_block_size * props->num_data_blocks); - }; - - VerifyBlockAlignment(); - - // The below block of code verifies that we can read back the keys. Set - // block_align to false when creating the reader to ensure we can flip between - // the two modes without any issues - std::unique_ptr table_reader; - bbto.block_align = false; - Options options2; - options2.table_factory.reset(NewBlockBasedTableFactory(bbto)); - ImmutableOptions ioptions2(options2); - const MutableCFOptions moptions2(options2); - - ASSERT_OK(ioptions.table_factory->NewTableReader( - TableReaderOptions(ioptions2, moptions2.prefix_extractor, EnvOptions(), - GetPlainInternalComparator(options2.comparator)), - std::move(file_reader), sink->contents().size(), &table_reader)); - - ReadOptions read_options; - std::unique_ptr db_iter(table_reader->NewIterator( - read_options, moptions2.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - - int expected_key = 1; - for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { - std::ostringstream ostr; - ostr << std::setfill('0') << std::setw(5) << expected_key++; - std::string key = ostr.str(); - std::string value = "val"; - - ASSERT_OK(db_iter->status()); - ASSERT_EQ(ExtractUserKey(db_iter->key()).ToString(), key); - ASSERT_EQ(db_iter->value().ToString(), value); - } - expected_key--; - ASSERT_EQ(expected_key, 10000); - table_reader.reset(); -} - -TEST_P(BlockBasedTableTest, PropertiesBlockRestartPointTest) { - BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); - bbto.block_align = true; - test::StringSink* sink = new test::StringSink(); - std::unique_ptr holder(sink); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(holder), "" /* don't care */, FileOptions())); - - Options options; - options.compression = kNoCompression; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::string column_family_name; - - std::unique_ptr builder(options.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kNoCompression, - CompressionOptions(), kUnknownColumnFamily, - column_family_name, -1), - file_writer.get())); - - for (int i = 1; i <= 10000; ++i) { - std::ostringstream ostr; - ostr << std::setfill('0') << std::setw(5) << i; - std::string key = ostr.str(); - std::string value = "val"; - InternalKey ik(key, 0, kTypeValue); - - builder->Add(ik.Encode(), value); - } - ASSERT_OK(builder->Finish()); - ASSERT_OK(file_writer->Flush()); - - std::unique_ptr source( - new test::StringSource(sink->contents(), 73342, true)); - std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(source), "test")); - - { - RandomAccessFileReader* file = file_reader.get(); - uint64_t file_size = sink->contents().size(); - - Footer footer; - IOOptions opts; - ASSERT_OK(ReadFooterFromFile(opts, file, *FileSystem::Default(), - nullptr /* prefetch_buffer */, file_size, - &footer, kBlockBasedTableMagicNumber)); - - auto BlockFetchHelper = [&](const BlockHandle& handle, BlockType block_type, - BlockContents* contents) { - ReadOptions read_options; - read_options.verify_checksums = false; - PersistentCacheOptions cache_options; - - BlockFetcher block_fetcher( - file, nullptr /* prefetch_buffer */, footer, read_options, handle, - contents, ioptions, false /* decompress */, - false /*maybe_compressed*/, block_type, - UncompressionDict::GetEmptyDict(), cache_options); - - ASSERT_OK(block_fetcher.ReadBlockContents()); - }; - - // -- Read metaindex block - auto metaindex_handle = footer.metaindex_handle(); - BlockContents metaindex_contents; - - BlockFetchHelper(metaindex_handle, BlockType::kMetaIndex, - &metaindex_contents); - Block metaindex_block(std::move(metaindex_contents)); - - std::unique_ptr meta_iter(metaindex_block.NewDataIterator( - BytewiseComparator(), kDisableGlobalSequenceNumber)); - - // -- Read properties block - BlockHandle properties_handle; - ASSERT_OK(FindOptionalMetaBlock(meta_iter.get(), kPropertiesBlockName, - &properties_handle)); - ASSERT_FALSE(properties_handle.IsNull()); - BlockContents properties_contents; - BlockFetchHelper(properties_handle, BlockType::kProperties, - &properties_contents); - Block properties_block(std::move(properties_contents)); - - ASSERT_EQ(properties_block.NumRestarts(), 1u); - } -} - -TEST_P(BlockBasedTableTest, PropertiesMetaBlockLast) { - // The properties meta-block should come at the end since we always need to - // read it when opening a file, unlike index/filter/other meta-blocks, which - // are sometimes read depending on the user's configuration. This ordering - // allows us to do a small readahead on the end of the file to read properties - // and meta-index blocks with one I/O. - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("a1", "val1"); - c.Add("b2", "val2"); - c.Add("c3", "val3"); - c.Add("d4", "val4"); - c.Add("e5", "val5"); - c.Add("f6", "val6"); - c.Add("g7", "val7"); - c.Add("h8", "val8"); - c.Add("j9", "val9"); - - // write an SST file - Options options; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.filter_policy.reset(NewBloomFilterPolicy( - 8 /* bits_per_key */, false /* use_block_based_filter */)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - std::vector keys; - stl_wrappers::KVMap kvmap; - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - - // get file reader - test::StringSink* table_sink = c.TEST_GetSink(); - std::unique_ptr source(new test::StringSource( - table_sink->contents(), 0 /* unique_id */, false /* allow_mmap_reads */)); - - std::unique_ptr table_reader( - new RandomAccessFileReader(std::move(source), "test")); - size_t table_size = table_sink->contents().size(); - - // read footer - Footer footer; - IOOptions opts; - ASSERT_OK(ReadFooterFromFile(opts, table_reader.get(), *FileSystem::Default(), - nullptr /* prefetch_buffer */, table_size, - &footer, kBlockBasedTableMagicNumber)); - - // read metaindex - auto metaindex_handle = footer.metaindex_handle(); - BlockContents metaindex_contents; - PersistentCacheOptions pcache_opts; - BlockFetcher block_fetcher( - table_reader.get(), nullptr /* prefetch_buffer */, footer, ReadOptions(), - metaindex_handle, &metaindex_contents, ioptions, false /* decompress */, - false /*maybe_compressed*/, BlockType::kMetaIndex, - UncompressionDict::GetEmptyDict(), pcache_opts, - nullptr /*memory_allocator*/); - ASSERT_OK(block_fetcher.ReadBlockContents()); - Block metaindex_block(std::move(metaindex_contents)); - - // verify properties block comes last - std::unique_ptr metaindex_iter{ - metaindex_block.NewMetaIterator()}; - uint64_t max_offset = 0; - std::string key_at_max_offset; - for (metaindex_iter->SeekToFirst(); metaindex_iter->Valid(); - metaindex_iter->Next()) { - BlockHandle handle; - Slice value = metaindex_iter->value(); - ASSERT_OK(handle.DecodeFrom(&value)); - if (handle.offset() > max_offset) { - max_offset = handle.offset(); - key_at_max_offset = metaindex_iter->key().ToString(); - } - } - ASSERT_EQ(kPropertiesBlockName, key_at_max_offset); - // index handle is stored in footer rather than metaindex block, so need - // separate logic to verify it comes before properties block. - ASSERT_GT(max_offset, footer.index_handle().offset()); - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, SeekMetaBlocks) { - TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); - c.Add("foo_a1", "val1"); - c.Add("foo_b2", "val2"); - c.Add("foo_c3", "val3"); - c.Add("foo_d4", "val4"); - c.Add("foo_e5", "val5"); - c.Add("foo_f6", "val6"); - c.Add("foo_g7", "val7"); - c.Add("foo_h8", "val8"); - c.Add("foo_j9", "val9"); - - // write an SST file - Options options; - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.index_type = BlockBasedTableOptions::kHashSearch; - table_options.filter_policy.reset(NewBloomFilterPolicy( - 8 /* bits_per_key */, false /* use_block_based_filter */)); - options.prefix_extractor.reset(NewFixedPrefixTransform(4)); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - std::vector keys; - stl_wrappers::KVMap kvmap; - c.Finish(options, ioptions, moptions, table_options, - GetPlainInternalComparator(options.comparator), &keys, &kvmap); - - // get file reader - test::StringSink* table_sink = c.TEST_GetSink(); - std::unique_ptr source(new test::StringSource( - table_sink->contents(), 0 /* unique_id */, false /* allow_mmap_reads */)); - - std::unique_ptr table_reader( - new RandomAccessFileReader(std::move(source), "test")); - size_t table_size = table_sink->contents().size(); - - // read footer - Footer footer; - IOOptions opts; - ASSERT_OK(ReadFooterFromFile(opts, table_reader.get(), *FileSystem::Default(), - nullptr /* prefetch_buffer */, table_size, - &footer, kBlockBasedTableMagicNumber)); - - // read metaindex - auto metaindex_handle = footer.metaindex_handle(); - BlockContents metaindex_contents; - PersistentCacheOptions pcache_opts; - BlockFetcher block_fetcher( - table_reader.get(), nullptr /* prefetch_buffer */, footer, ReadOptions(), - metaindex_handle, &metaindex_contents, ioptions, false /* decompress */, - false /*maybe_compressed*/, BlockType::kMetaIndex, - UncompressionDict::GetEmptyDict(), pcache_opts, - nullptr /*memory_allocator*/); - ASSERT_OK(block_fetcher.ReadBlockContents()); - Block metaindex_block(std::move(metaindex_contents)); - - // verify properties block comes last - std::unique_ptr metaindex_iter( - metaindex_block.NewMetaIterator()); - bool has_hash_prefixes = false; - bool has_hash_metadata = false; - for (metaindex_iter->SeekToFirst(); metaindex_iter->Valid(); - metaindex_iter->Next()) { - if (metaindex_iter->key().ToString() == kHashIndexPrefixesBlock) { - has_hash_prefixes = true; - } else if (metaindex_iter->key().ToString() == - kHashIndexPrefixesMetadataBlock) { - has_hash_metadata = true; - } - } - if (has_hash_metadata) { - metaindex_iter->Seek(kHashIndexPrefixesMetadataBlock); - ASSERT_TRUE(metaindex_iter->Valid()); - ASSERT_EQ(kHashIndexPrefixesMetadataBlock, - metaindex_iter->key().ToString()); - } - if (has_hash_prefixes) { - metaindex_iter->Seek(kHashIndexPrefixesBlock); - ASSERT_TRUE(metaindex_iter->Valid()); - ASSERT_EQ(kHashIndexPrefixesBlock, metaindex_iter->key().ToString()); - } - c.ResetTableReader(); -} - -TEST_P(BlockBasedTableTest, BadOptions) { - ROCKSDB_NAMESPACE::Options options; - options.compression = kNoCompression; - BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); - bbto.block_size = 4000; - bbto.block_align = true; - - const std::string kDBPath = - test::PerThreadDBPath("block_based_table_bad_options_test"); - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - ASSERT_OK(DestroyDB(kDBPath, options)); - ROCKSDB_NAMESPACE::DB* db; - ASSERT_NOK(ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &db)); - - bbto.block_size = 4096; - options.compression = kSnappyCompression; - options.table_factory.reset(NewBlockBasedTableFactory(bbto)); - ASSERT_NOK(ROCKSDB_NAMESPACE::DB::Open(options, kDBPath, &db)); -} - -TEST_F(BBTTailPrefetchTest, TestTailPrefetchStats) { - TailPrefetchStats tpstats; - ASSERT_EQ(0, tpstats.GetSuggestedPrefetchSize()); - tpstats.RecordEffectiveSize(size_t{1000}); - tpstats.RecordEffectiveSize(size_t{1005}); - tpstats.RecordEffectiveSize(size_t{1002}); - ASSERT_EQ(1005, tpstats.GetSuggestedPrefetchSize()); - - // One single super large value shouldn't influence much - tpstats.RecordEffectiveSize(size_t{1002000}); - tpstats.RecordEffectiveSize(size_t{999}); - ASSERT_LE(1005, tpstats.GetSuggestedPrefetchSize()); - ASSERT_GT(1200, tpstats.GetSuggestedPrefetchSize()); - - // Only history of 32 is kept - for (int i = 0; i < 32; i++) { - tpstats.RecordEffectiveSize(size_t{100}); - } - ASSERT_EQ(100, tpstats.GetSuggestedPrefetchSize()); - - // 16 large values and 16 small values. The result should be closer - // to the small value as the algorithm. - for (int i = 0; i < 16; i++) { - tpstats.RecordEffectiveSize(size_t{1000}); - } - tpstats.RecordEffectiveSize(size_t{10}); - tpstats.RecordEffectiveSize(size_t{20}); - for (int i = 0; i < 6; i++) { - tpstats.RecordEffectiveSize(size_t{100}); - } - ASSERT_LE(80, tpstats.GetSuggestedPrefetchSize()); - ASSERT_GT(200, tpstats.GetSuggestedPrefetchSize()); -} - -TEST_F(BBTTailPrefetchTest, FilePrefetchBufferMinOffset) { - TailPrefetchStats tpstats; - FilePrefetchBuffer buffer(0 /* readahead_size */, 0 /* max_readahead_size */, - false /* enable */, true /* track_min_offset */); - IOOptions opts; - buffer.TryReadFromCache(opts, nullptr /* reader */, 500 /* offset */, - 10 /* n */, nullptr /* result */, - nullptr /* status */, - Env::IO_TOTAL /* rate_limiter_priority */); - buffer.TryReadFromCache(opts, nullptr /* reader */, 480 /* offset */, - 10 /* n */, nullptr /* result */, - nullptr /* status */, - Env::IO_TOTAL /* rate_limiter_priority */); - buffer.TryReadFromCache(opts, nullptr /* reader */, 490 /* offset */, - 10 /* n */, nullptr /* result */, - nullptr /* status */, - Env::IO_TOTAL /* rate_limiter_priority */); - ASSERT_EQ(480, buffer.min_offset_read()); -} - -TEST_P(BlockBasedTableTest, DataBlockHashIndex) { - const int kNumKeys = 500; - const int kKeySize = 8; - const int kValSize = 40; - - BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); - table_options.data_block_index_type = - BlockBasedTableOptions::kDataBlockBinaryAndHash; - - Options options; - options.comparator = BytewiseComparator(); - - options.table_factory.reset(new BlockBasedTableFactory(table_options)); - - TableConstructor c(options.comparator); - - static Random rnd(1048); - for (int i = 0; i < kNumKeys; i++) { - // padding one "0" to mark existent keys. - std::string random_key(rnd.RandomString(kKeySize - 1) + "1"); - InternalKey k(random_key, 0, kTypeValue); - c.Add(k.Encode().ToString(), rnd.RandomString(kValSize)); - } - - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - const InternalKeyComparator internal_comparator(options.comparator); - c.Finish(options, ioptions, moptions, table_options, internal_comparator, - &keys, &kvmap); - - auto reader = c.GetTableReader(); - - std::unique_ptr seek_iter; - ReadOptions read_options; - seek_iter.reset(reader->NewIterator( - read_options, moptions.prefix_extractor.get(), /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized)); - for (int i = 0; i < 2; ++i) { - ReadOptions ro; - // for every kv, we seek using two method: Get() and Seek() - // Get() will use the SuffixIndexHash in Block. For non-existent key it - // will invalidate the iterator - // Seek() will use the default BinarySeek() in Block. So for non-existent - // key it will land at the closest key that is large than target. - - // Search for existent keys - for (auto& kv : kvmap) { - if (i == 0) { - // Search using Seek() - seek_iter->Seek(kv.first); - ASSERT_OK(seek_iter->status()); - ASSERT_TRUE(seek_iter->Valid()); - ASSERT_EQ(seek_iter->key(), kv.first); - ASSERT_EQ(seek_iter->value(), kv.second); - } else { - // Search using Get() - PinnableSlice value; - std::string user_key = ExtractUserKey(kv.first).ToString(); - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, nullptr, - nullptr, nullptr, true, nullptr, nullptr); - ASSERT_OK(reader->Get(ro, kv.first, &get_context, - moptions.prefix_extractor.get())); - ASSERT_EQ(get_context.State(), GetContext::kFound); - ASSERT_EQ(value, Slice(kv.second)); - value.Reset(); - } - } - - // Search for non-existent keys - for (auto& kv : kvmap) { - std::string user_key = ExtractUserKey(kv.first).ToString(); - user_key.back() = '0'; // make it non-existent key - InternalKey internal_key(user_key, 0, kTypeValue); - std::string encoded_key = internal_key.Encode().ToString(); - if (i == 0) { // Search using Seek() - seek_iter->Seek(encoded_key); - ASSERT_OK(seek_iter->status()); - if (seek_iter->Valid()) { - ASSERT_TRUE(BytewiseComparator()->Compare( - user_key, ExtractUserKey(seek_iter->key())) < 0); - } - } else { // Search using Get() - PinnableSlice value; - GetContext get_context(options.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, nullptr, - nullptr, nullptr, true, nullptr, nullptr); - ASSERT_OK(reader->Get(ro, encoded_key, &get_context, - moptions.prefix_extractor.get())); - ASSERT_EQ(get_context.State(), GetContext::kNotFound); - value.Reset(); - } - } - } -} - -// BlockBasedTableIterator should invalidate itself and return -// OutOfBound()=true immediately after Seek(), to allow LevelIterator -// filter out corresponding level. -TEST_P(BlockBasedTableTest, OutOfBoundOnSeek) { - TableConstructor c(BytewiseComparator(), true /*convert_to_internal_key*/); - c.Add("foo", "v1"); - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - BlockBasedTableOptions table_opt(GetBlockBasedTableOptions()); - options.table_factory.reset(NewBlockBasedTableFactory(table_opt)); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_opt, - GetPlainInternalComparator(BytewiseComparator()), &keys, &kvmap); - auto* reader = c.GetTableReader(); - ReadOptions read_opt; - std::string upper_bound = "bar"; - Slice upper_bound_slice(upper_bound); - read_opt.iterate_upper_bound = &upper_bound_slice; - std::unique_ptr iter; - iter.reset(new KeyConvertingIterator(reader->NewIterator( - read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized))); - iter->SeekToFirst(); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); - iter.reset(new KeyConvertingIterator(reader->NewIterator( - read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized))); - iter->Seek("foo"); - ASSERT_FALSE(iter->Valid()); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); -} - -// BlockBasedTableIterator should invalidate itself and return -// OutOfBound()=true after Next(), if it finds current index key is no smaller -// than upper bound, unless it is pointing to the last data block. -TEST_P(BlockBasedTableTest, OutOfBoundOnNext) { - TableConstructor c(BytewiseComparator(), true /*convert_to_internal_key*/); - c.Add("bar", "v"); - c.Add("foo", "v"); - std::vector keys; - stl_wrappers::KVMap kvmap; - Options options; - BlockBasedTableOptions table_opt(GetBlockBasedTableOptions()); - table_opt.flush_block_policy_factory = - std::make_shared(); - options.table_factory.reset(NewBlockBasedTableFactory(table_opt)); - const ImmutableOptions ioptions(options); - const MutableCFOptions moptions(options); - c.Finish(options, ioptions, moptions, table_opt, - GetPlainInternalComparator(BytewiseComparator()), &keys, &kvmap); - auto* reader = c.GetTableReader(); - ReadOptions read_opt; - std::string ub1 = "bar_after"; - Slice ub_slice1(ub1); - read_opt.iterate_upper_bound = &ub_slice1; - std::unique_ptr iter; - iter.reset(new KeyConvertingIterator(reader->NewIterator( - read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized))); - iter->Seek("bar"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("bar", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_TRUE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); - std::string ub2 = "foo_after"; - Slice ub_slice2(ub2); - read_opt.iterate_upper_bound = &ub_slice2; - iter.reset(new KeyConvertingIterator(reader->NewIterator( - read_opt, /*prefix_extractor=*/nullptr, /*arena=*/nullptr, - /*skip_filters=*/false, TableReaderCaller::kUncategorized))); - iter->Seek("foo"); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ("foo", iter->key()); - iter->Next(); - ASSERT_FALSE(iter->Valid()); - ASSERT_FALSE(iter->UpperBoundCheckResult() == IterBoundCheck::kOutOfBound); -} - -class ChargeCompressionDictionaryBuildingBufferTest - : public BlockBasedTableTestBase {}; -TEST_F(ChargeCompressionDictionaryBuildingBufferTest, Basic) { - constexpr std::size_t kSizeDummyEntry = 256 * 1024; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - constexpr std::size_t kCacheCapacity = 8 * 1024 * 1024; - constexpr std::size_t kMaxDictBytes = 1024; - constexpr std::size_t kMaxDictBufferBytes = 1024; - - for (CacheEntryRoleOptions::Decision - charge_compression_dictionary_building_buffer : - {CacheEntryRoleOptions::Decision::kEnabled, - CacheEntryRoleOptions::Decision::kDisabled}) { - BlockBasedTableOptions table_options; - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; // 2^0 shard - lo.strict_capacity_limit = true; - std::shared_ptr cache(NewLRUCache(lo)); - table_options.block_cache = cache; - table_options.flush_block_policy_factory = - std::make_shared(); - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kCompressionDictionaryBuildingBuffer, - {/*.charged = */ charge_compression_dictionary_building_buffer}}); - Options options; - options.compression = kSnappyCompression; - options.compression_opts.max_dict_bytes = kMaxDictBytes; - options.compression_opts.max_dict_buffer_bytes = kMaxDictBufferBytes; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - test::StringSink* sink = new test::StringSink(); - std::unique_ptr holder(sink); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(holder), "test_file_name", FileOptions())); - - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - - std::unique_ptr builder( - options.table_factory->NewTableBuilder( - TableBuilderOptions( - ioptions, moptions, ikc, &int_tbl_prop_collector_factories, - kSnappyCompression, options.compression_opts, - kUnknownColumnFamily, "test_cf", -1 /* level */), - file_writer.get())); - - std::string key1 = "key1"; - std::string value1 = "val1"; - InternalKey ik1(key1, 0 /* sequnce number */, kTypeValue); - // Adding the first key won't trigger a flush by FlushBlockEveryKeyPolicy - // therefore won't trigger any data block's buffering - builder->Add(ik1.Encode(), value1); - ASSERT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - - std::string key2 = "key2"; - std::string value2 = "val2"; - InternalKey ik2(key2, 1 /* sequnce number */, kTypeValue); - // Adding the second key will trigger a flush of the last data block (the - // one containing key1 and value1) by FlushBlockEveryKeyPolicy and hence - // trigger buffering of that data block. - builder->Add(ik2.Encode(), value2); - // Cache charging will increase for last buffered data block (the one - // containing key1 and value1) since the buffer limit is not exceeded after - // that buffering and the cache will not be full after this reservation - if (charge_compression_dictionary_building_buffer == - CacheEntryRoleOptions::Decision::kEnabled) { - EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry); - EXPECT_LT(cache->GetPinnedUsage(), - 1 * kSizeDummyEntry + kMetaDataChargeOverhead); - } else { - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - } - - ASSERT_OK(builder->Finish()); - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - } -} - -TEST_F(ChargeCompressionDictionaryBuildingBufferTest, - BasicWithBufferLimitExceed) { - constexpr std::size_t kSizeDummyEntry = 256 * 1024; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - constexpr std::size_t kCacheCapacity = 8 * 1024 * 1024; - constexpr std::size_t kMaxDictBytes = 1024; - constexpr std::size_t kMaxDictBufferBytes = 2 * kSizeDummyEntry; - - // `CacheEntryRoleOptions::charged` is enabled by default for - // CacheEntryRole::kCompressionDictionaryBuildingBuffer - BlockBasedTableOptions table_options; - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; // 2^0 shard - lo.strict_capacity_limit = true; - std::shared_ptr cache(NewLRUCache(lo)); - table_options.block_cache = cache; - table_options.flush_block_policy_factory = - std::make_shared(); - - Options options; - options.compression = kSnappyCompression; - options.compression_opts.max_dict_bytes = kMaxDictBytes; - options.compression_opts.max_dict_buffer_bytes = kMaxDictBufferBytes; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - test::StringSink* sink = new test::StringSink(); - std::unique_ptr holder(sink); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(holder), "test_file_name", FileOptions())); - - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - - std::unique_ptr builder(options.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kSnappyCompression, - options.compression_opts, kUnknownColumnFamily, - "test_cf", -1 /* level */), - file_writer.get())); - - std::string key1 = "key1"; - std::string value1(kSizeDummyEntry, '0'); - InternalKey ik1(key1, 0 /* sequnce number */, kTypeValue); - // Adding the first key won't trigger a flush by FlushBlockEveryKeyPolicy - // therefore won't trigger any data block's buffering - builder->Add(ik1.Encode(), value1); - ASSERT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - - std::string key2 = "key2"; - std::string value2(kSizeDummyEntry, '0'); - InternalKey ik2(key2, 1 /* sequnce number */, kTypeValue); - // Adding the second key will trigger a flush of the last data block (the one - // containing key1 and value1) by FlushBlockEveryKeyPolicy and hence trigger - // buffering of the last data block. - builder->Add(ik2.Encode(), value2); - // Cache charging will increase for last buffered data block (the one - // containing key1 and value1) since the buffer limit is not exceeded after - // the buffering and the cache will not be full after this reservation - EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry); - EXPECT_LT(cache->GetPinnedUsage(), - 2 * kSizeDummyEntry + kMetaDataChargeOverhead); - - std::string key3 = "key3"; - std::string value3 = "val3"; - InternalKey ik3(key3, 2 /* sequnce number */, kTypeValue); - // Adding the third key will trigger a flush of the last data block (the one - // containing key2 and value2) by FlushBlockEveryKeyPolicy and hence trigger - // buffering of the last data block. - builder->Add(ik3.Encode(), value3); - // Cache charging will decrease since the buffer limit is now exceeded - // after the last buffering and EnterUnbuffered() is triggered - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - - ASSERT_OK(builder->Finish()); - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); -} - -TEST_F(ChargeCompressionDictionaryBuildingBufferTest, BasicWithCacheFull) { - constexpr std::size_t kSizeDummyEntry = 256 * 1024; - constexpr std::size_t kMetaDataChargeOverhead = 10000; - // A small kCacheCapacity is chosen so that increase cache charging for - // buffering two data blocks, each containing key1/value1, key2/a big - // value2, will cause cache full - constexpr std::size_t kCacheCapacity = - 1 * kSizeDummyEntry + kSizeDummyEntry / 2; - constexpr std::size_t kMaxDictBytes = 1024; - // A big kMaxDictBufferBytes is chosen so that adding a big key value pair - // (key2, value2) won't exceed the buffer limit - constexpr std::size_t kMaxDictBufferBytes = 1024 * 1024 * 1024; - - // `CacheEntryRoleOptions::charged` is enabled by default for - // CacheEntryRole::kCompressionDictionaryBuildingBuffer - BlockBasedTableOptions table_options; - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; // 2^0 shard - lo.strict_capacity_limit = true; - std::shared_ptr cache(NewLRUCache(lo)); - table_options.block_cache = cache; - table_options.flush_block_policy_factory = - std::make_shared(); - - Options options; - options.compression = kSnappyCompression; - options.compression_opts.max_dict_bytes = kMaxDictBytes; - options.compression_opts.max_dict_buffer_bytes = kMaxDictBufferBytes; - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - test::StringSink* sink = new test::StringSink(); - std::unique_ptr holder(sink); - std::unique_ptr file_writer(new WritableFileWriter( - std::move(holder), "test_file_name", FileOptions())); - - ImmutableOptions ioptions(options); - MutableCFOptions moptions(options); - InternalKeyComparator ikc(options.comparator); - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - - std::unique_ptr builder(options.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, moptions, ikc, - &int_tbl_prop_collector_factories, kSnappyCompression, - options.compression_opts, kUnknownColumnFamily, - "test_cf", -1 /* level */), - file_writer.get())); - - std::string key1 = "key1"; - std::string value1 = "val1"; - InternalKey ik1(key1, 0 /* sequnce number */, kTypeValue); - // Adding the first key won't trigger a flush by FlushBlockEveryKeyPolicy - // therefore won't trigger any data block's buffering - builder->Add(ik1.Encode(), value1); - ASSERT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - - std::string key2 = "key2"; - std::string value2(kSizeDummyEntry, '0'); - InternalKey ik2(key2, 1 /* sequnce number */, kTypeValue); - // Adding the second key will trigger a flush of the last data block (the one - // containing key1 and value1) by FlushBlockEveryKeyPolicy and hence trigger - // buffering of the last data block. - builder->Add(ik2.Encode(), value2); - // Cache charging will increase for the last buffered data block (the one - // containing key1 and value1) since the buffer limit is not exceeded after - // the buffering and the cache will not be full after this reservation - EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry); - EXPECT_LT(cache->GetPinnedUsage(), - 1 * kSizeDummyEntry + kMetaDataChargeOverhead); - - std::string key3 = "key3"; - std::string value3 = "value3"; - InternalKey ik3(key3, 2 /* sequnce number */, kTypeValue); - // Adding the third key will trigger a flush of the last data block (the one - // containing key2 and value2) by FlushBlockEveryKeyPolicy and hence trigger - // buffering of the last data block. - builder->Add(ik3.Encode(), value3); - // Cache charging will decrease since the cache is now full after - // increasing reservation for the last buffered block and EnterUnbuffered() is - // triggered - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); - - ASSERT_OK(builder->Finish()); - EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry); -} - -class CacheUsageOptionsOverridesTest : public DBTestBase { - public: - CacheUsageOptionsOverridesTest() - : DBTestBase("cache_usage_options_overrides_test", - /*env_do_fsync=*/false) {} -}; - -TEST_F(CacheUsageOptionsOverridesTest, SanitizeAndValidateOptions) { - // To test `cache_usage_options.options_overrides` is sanitized - // where `cache_usage_options.options` is used when there is no entry in - // `cache_usage_options.options_overrides` - Options options; - options.create_if_missing = true; - BlockBasedTableOptions table_options = BlockBasedTableOptions(); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Destroy(options); - Status s = TryReopen(options); - EXPECT_TRUE(s.ok()); - const auto* sanitized_table_options = - options.table_factory->GetOptions(); - const auto sanitized_options_overrides = - sanitized_table_options->cache_usage_options.options_overrides; - EXPECT_EQ(sanitized_options_overrides.size(), kNumCacheEntryRoles); - for (auto options_overrides_iter = sanitized_options_overrides.cbegin(); - options_overrides_iter != sanitized_options_overrides.cend(); - ++options_overrides_iter) { - CacheEntryRoleOptions role_options = options_overrides_iter->second; - CacheEntryRoleOptions default_options = - sanitized_table_options->cache_usage_options.options; - EXPECT_TRUE(role_options == default_options); - } - Destroy(options); - - // To test option validation on unsupported CacheEntryRole - table_options = BlockBasedTableOptions(); - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kDataBlock, - {/*.charged = */ CacheEntryRoleOptions::Decision::kDisabled}}); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Destroy(options); - s = TryReopen(options); - EXPECT_TRUE(s.IsNotSupported()); - EXPECT_TRUE( - s.ToString().find("Enable/Disable CacheEntryRoleOptions::charged") != - std::string::npos); - EXPECT_TRUE( - s.ToString().find(kCacheEntryRoleToCamelString[static_cast( - CacheEntryRole::kDataBlock)]) != std::string::npos); - Destroy(options); - - // To test option validation on existence of block cache - table_options = BlockBasedTableOptions(); - table_options.no_block_cache = true; - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kFilterConstruction, - {/*.charged = */ CacheEntryRoleOptions::Decision::kEnabled}}); - options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - Destroy(options); - s = TryReopen(options); - EXPECT_TRUE(s.IsInvalidArgument()); - EXPECT_TRUE(s.ToString().find("Enable CacheEntryRoleOptions::charged") != - std::string::npos); - EXPECT_TRUE( - s.ToString().find(kCacheEntryRoleToCamelString[static_cast( - CacheEntryRole::kFilterConstruction)]) != std::string::npos); - EXPECT_TRUE(s.ToString().find("block cache is disabled") != - std::string::npos); - Destroy(options); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/test_util/testutil_test.cc b/test_util/testutil_test.cc deleted file mode 100644 index 41f26e389..000000000 --- a/test_util/testutil_test.cc +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "test_util/testutil.h" - -#include "file/file_util.h" -#include "port/port.h" -#include "port/stack_trace.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -void CreateFile(Env* env, const std::string& path) { - std::unique_ptr f; - ASSERT_OK(env->NewWritableFile(path, &f, EnvOptions())); - f->Close(); -} - -TEST(TestUtil, DestroyDirRecursively) { - auto env = Env::Default(); - // test_util/file - // /dir - // /dir/file - std::string test_dir = test::PerThreadDBPath("test_util"); - ASSERT_OK(env->CreateDir(test_dir)); - CreateFile(env, test_dir + "/file"); - ASSERT_OK(env->CreateDir(test_dir + "/dir")); - CreateFile(env, test_dir + "/dir/file"); - - ASSERT_OK(DestroyDir(env, test_dir)); - auto s = env->FileExists(test_dir); - ASSERT_TRUE(s.IsNotFound()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tools/db_bench_tool_test.cc b/tools/db_bench_tool_test.cc deleted file mode 100644 index a30c65065..000000000 --- a/tools/db_bench_tool_test.cc +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/db_bench_tool.h" - -#include "db/db_impl/db_impl.h" -#include "options/options_parser.h" -#include "rocksdb/utilities/options_util.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/random.h" - -#ifdef GFLAGS -#include "util/gflags_compat.h" - -namespace ROCKSDB_NAMESPACE { -namespace { -static const int kMaxArgCount = 100; -static const size_t kArgBufferSize = 100000; -} // namespace - -class DBBenchTest : public testing::Test { - public: - DBBenchTest() : rnd_(0xFB) { - test_path_ = test::PerThreadDBPath("db_bench_test"); - Env::Default()->CreateDir(test_path_); - db_path_ = test_path_ + "/db"; - wal_path_ = test_path_ + "/wal"; - } - - ~DBBenchTest() { - // DestroyDB(db_path_, Options()); - } - - void ResetArgs() { - argc_ = 0; - cursor_ = 0; - memset(arg_buffer_, 0, kArgBufferSize); - } - - void AppendArgs(const std::vector& args) { - for (const auto& arg : args) { - ASSERT_LE(cursor_ + arg.size() + 1, kArgBufferSize); - ASSERT_LE(argc_ + 1, kMaxArgCount); - snprintf(arg_buffer_ + cursor_, arg.size() + 1, "%s", arg.c_str()); - - argv_[argc_++] = arg_buffer_ + cursor_; - cursor_ += arg.size() + 1; - } - } - - // Gets the default options for this test/db_bench. - // Note that db_bench may change some of the default option values and that - // the database might as well. The options changed by db_bench are - // specified here; the ones by the DB are set via SanitizeOptions - Options GetDefaultOptions(CompactionStyle style = kCompactionStyleLevel, - int levels = 7) const { - Options opt; - - opt.create_if_missing = true; - opt.max_open_files = 256; - opt.max_background_compactions = 10; - opt.dump_malloc_stats = true; // db_bench uses a different default - opt.compaction_style = style; - opt.num_levels = levels; - opt.compression = kNoCompression; - opt.arena_block_size = 8388608; - - return SanitizeOptions(db_path_, opt); - } - - void RunDbBench(const std::string& options_file_name) { - AppendArgs({"./db_bench", "--benchmarks=fillseq", "--use_existing_db=0", - "--num=1000", "--compression_type=none", - std::string(std::string("--db=") + db_path_).c_str(), - std::string(std::string("--wal_dir=") + wal_path_).c_str(), - std::string(std::string("--options_file=") + options_file_name) - .c_str()}); - ASSERT_EQ(0, db_bench_tool(argc(), argv())); - } - - void VerifyOptions(const Options& opt) { - DBOptions loaded_db_opts; - ConfigOptions config_opts; - config_opts.ignore_unknown_options = false; - config_opts.input_strings_escaped = true; - config_opts.env = Env::Default(); - std::vector cf_descs; - ASSERT_OK( - LoadLatestOptions(config_opts, db_path_, &loaded_db_opts, &cf_descs)); - - ConfigOptions exact; - exact.input_strings_escaped = false; - exact.sanity_level = ConfigOptions::kSanityLevelExactMatch; - ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(exact, DBOptions(opt), - loaded_db_opts)); - ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions( - exact, ColumnFamilyOptions(opt), cf_descs[0].options)); - - // check with the default rocksdb options and expect failure - ASSERT_NOK(RocksDBOptionsParser::VerifyDBOptions(exact, DBOptions(), - loaded_db_opts)); - ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions( - exact, ColumnFamilyOptions(), cf_descs[0].options)); - } - - char** argv() { return argv_; } - - int argc() { return argc_; } - - std::string db_path_; - std::string test_path_; - std::string wal_path_; - - char arg_buffer_[kArgBufferSize]; - char* argv_[kMaxArgCount]; - int argc_ = 0; - int cursor_ = 0; - Random rnd_; -}; - -namespace {} // namespace - -TEST_F(DBBenchTest, OptionsFile) { - const std::string kOptionsFileName = test_path_ + "/OPTIONS_test"; - Options opt = GetDefaultOptions(); - ASSERT_OK(PersistRocksDBOptions(DBOptions(opt), {"default"}, - {ColumnFamilyOptions(opt)}, kOptionsFileName, - opt.env->GetFileSystem().get())); - - // override the following options as db_bench will not take these - // options from the options file - opt.wal_dir = wal_path_; - - RunDbBench(kOptionsFileName); - opt.delayed_write_rate = 16 * 1024 * 1024; // Set by SanitizeOptions - - VerifyOptions(opt); -} - -TEST_F(DBBenchTest, OptionsFileUniversal) { - const std::string kOptionsFileName = test_path_ + "/OPTIONS_test"; - - Options opt = GetDefaultOptions(kCompactionStyleUniversal, 1); - - ASSERT_OK(PersistRocksDBOptions(DBOptions(opt), {"default"}, - {ColumnFamilyOptions(opt)}, kOptionsFileName, - opt.env->GetFileSystem().get())); - - // override the following options as db_bench will not take these - // options from the options file - opt.wal_dir = wal_path_; - RunDbBench(kOptionsFileName); - - VerifyOptions(opt); -} - -TEST_F(DBBenchTest, OptionsFileMultiLevelUniversal) { - const std::string kOptionsFileName = test_path_ + "/OPTIONS_test"; - - Options opt = GetDefaultOptions(kCompactionStyleUniversal, 12); - - ASSERT_OK(PersistRocksDBOptions(DBOptions(opt), {"default"}, - {ColumnFamilyOptions(opt)}, kOptionsFileName, - opt.env->GetFileSystem().get())); - - // override the following options as db_bench will not take these - // options from the options file - opt.wal_dir = wal_path_; - - RunDbBench(kOptionsFileName); - VerifyOptions(opt); -} - -const std::string options_file_content = R"OPTIONS_FILE( -[Version] - rocksdb_version=4.3.1 - options_file_version=1.1 - -[DBOptions] - wal_bytes_per_sync=1048576 - delete_obsolete_files_period_micros=0 - WAL_ttl_seconds=0 - WAL_size_limit_MB=0 - db_write_buffer_size=0 - max_subcompactions=1 - table_cache_numshardbits=4 - max_open_files=-1 - max_file_opening_threads=10 - max_background_compactions=5 - use_fsync=false - use_adaptive_mutex=false - max_total_wal_size=18446744073709551615 - compaction_readahead_size=0 - keep_log_file_num=10 - skip_stats_update_on_db_open=false - max_manifest_file_size=18446744073709551615 - db_log_dir= - writable_file_max_buffer_size=1048576 - paranoid_checks=true - is_fd_close_on_exec=true - bytes_per_sync=1048576 - enable_thread_tracking=true - recycle_log_file_num=0 - create_missing_column_families=false - log_file_time_to_roll=0 - max_background_flushes=1 - create_if_missing=true - error_if_exists=false - delayed_write_rate=1048576 - manifest_preallocation_size=4194304 - allow_mmap_reads=false - allow_mmap_writes=false - use_direct_reads=false - use_direct_io_for_flush_and_compaction=false - stats_dump_period_sec=600 - allow_fallocate=true - max_log_file_size=83886080 - random_access_max_buffer_size=1048576 - advise_random_on_open=true - dump_malloc_stats=true - -[CFOptions "default"] - compaction_filter_factory=nullptr - table_factory=BlockBasedTable - prefix_extractor=nullptr - comparator=leveldb.BytewiseComparator - compression_per_level= - max_bytes_for_level_base=104857600 - bloom_locality=0 - target_file_size_base=10485760 - memtable_huge_page_size=0 - max_successive_merges=1000 - max_sequential_skip_in_iterations=8 - arena_block_size=52428800 - target_file_size_multiplier=1 - source_compaction_factor=1 - min_write_buffer_number_to_merge=1 - max_write_buffer_number=2 - write_buffer_size=419430400 - max_grandparent_overlap_factor=10 - max_bytes_for_level_multiplier=10 - memtable_factory=SkipListFactory - compression=kNoCompression - min_partial_merge_operands=2 - level0_stop_writes_trigger=100 - num_levels=1 - level0_slowdown_writes_trigger=50 - level0_file_num_compaction_trigger=10 - expanded_compaction_factor=25 - max_write_buffer_number_to_maintain=0 - max_write_buffer_size_to_maintain=0 - verify_checksums_in_compaction=true - merge_operator=nullptr - memtable_prefix_bloom_bits=0 - memtable_whole_key_filtering=true - paranoid_file_checks=false - inplace_update_num_locks=10000 - optimize_filters_for_hits=false - level_compaction_dynamic_level_bytes=false - inplace_update_support=false - compaction_style=kCompactionStyleUniversal - memtable_prefix_bloom_probes=6 - filter_deletes=false - hard_pending_compaction_bytes_limit=0 - disable_auto_compactions=false - compaction_measure_io_stats=false - enable_blob_files=true - min_blob_size=16 - blob_file_size=10485760 - blob_compression_type=kNoCompression - enable_blob_garbage_collection=true - blob_garbage_collection_age_cutoff=0.5 - blob_garbage_collection_force_threshold=0.75 - blob_compaction_readahead_size=262144 - blob_file_starting_level=0 - prepopulate_blob_cache=kDisable; - -[TableOptions/BlockBasedTable "default"] - format_version=0 - skip_table_builder_flush=false - cache_index_and_filter_blocks=false - flush_block_policy_factory=FlushBlockBySizePolicyFactory - index_type=kBinarySearch - whole_key_filtering=true - checksum=kCRC32c - no_block_cache=false - block_size=32768 - block_size_deviation=10 - block_restart_interval=16 - filter_policy=rocksdb.BuiltinBloomFilter -)OPTIONS_FILE"; - -TEST_F(DBBenchTest, OptionsFileFromFile) { - const std::string kOptionsFileName = test_path_ + "/OPTIONS_flash"; - std::unique_ptr writable; - ASSERT_OK(Env::Default()->NewWritableFile(kOptionsFileName, &writable, - EnvOptions())); - ASSERT_OK(writable->Append(options_file_content)); - ASSERT_OK(writable->Close()); - - DBOptions db_opt; - ConfigOptions config_opt; - config_opt.ignore_unknown_options = false; - config_opt.input_strings_escaped = true; - config_opt.env = Env::Default(); - std::vector cf_descs; - ASSERT_OK( - LoadOptionsFromFile(config_opt, kOptionsFileName, &db_opt, &cf_descs)); - Options opt(db_opt, cf_descs[0].options); - opt.create_if_missing = true; - - // override the following options as db_bench will not take these - // options from the options file - opt.wal_dir = wal_path_; - - RunDbBench(kOptionsFileName); - - VerifyOptions(SanitizeOptions(db_path_, opt)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); - return RUN_ALL_TESTS(); -} - -#else - -int main(int argc, char** argv) { - printf("Skip db_bench_tool_test as the required library GFLAG is missing."); -} -#endif // #ifdef GFLAGS diff --git a/tools/db_sanity_test.cc b/tools/db_sanity_test.cc deleted file mode 100644 index f40be5ae2..000000000 --- a/tools/db_sanity_test.cc +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include -#include -#include - -#include "port/port.h" -#include "rocksdb/comparator.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/options.h" -#include "rocksdb/slice.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/status.h" -#include "rocksdb/table.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class SanityTest { - public: - explicit SanityTest(const std::string& path) - : env_(Env::Default()), path_(path) { - env_->CreateDirIfMissing(path); - } - virtual ~SanityTest() {} - - virtual std::string Name() const = 0; - virtual Options GetOptions() const = 0; - - Status Create() { - Options options = GetOptions(); - options.create_if_missing = true; - std::string dbname = path_ + Name(); - Status s = DestroyDB(dbname, options); - if (!s.ok()) { - return s; - } - DB* db = nullptr; - s = DB::Open(options, dbname, &db); - std::unique_ptr db_guard(db); - if (!s.ok()) { - return s; - } - for (int i = 0; i < 1000000; ++i) { - std::string k = "key" + std::to_string(i); - std::string v = "value" + std::to_string(i); - s = db->Put(WriteOptions(), Slice(k), Slice(v)); - if (!s.ok()) { - return s; - } - } - return db->Flush(FlushOptions()); - } - Status Verify() { - DB* db = nullptr; - std::string dbname = path_ + Name(); - Status s = DB::Open(GetOptions(), dbname, &db); - std::unique_ptr db_guard(db); - if (!s.ok()) { - return s; - } - for (int i = 0; i < 1000000; ++i) { - std::string k = "key" + std::to_string(i); - std::string v = "value" + std::to_string(i); - std::string result; - s = db->Get(ReadOptions(), Slice(k), &result); - if (!s.ok()) { - return s; - } - if (result != v) { - return Status::Corruption("Unexpected value for key " + k); - } - } - return Status::OK(); - } - - private: - Env* env_; - std::string const path_; -}; - -class SanityTestBasic : public SanityTest { - public: - explicit SanityTestBasic(const std::string& path) : SanityTest(path) {} - virtual Options GetOptions() const override { - Options options; - options.create_if_missing = true; - return options; - } - virtual std::string Name() const override { return "Basic"; } -}; - -class SanityTestSpecialComparator : public SanityTest { - public: - explicit SanityTestSpecialComparator(const std::string& path) - : SanityTest(path) { - options_.comparator = new NewComparator(); - } - ~SanityTestSpecialComparator() { delete options_.comparator; } - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "SpecialComparator"; } - - private: - class NewComparator : public Comparator { - public: - virtual const char* Name() const override { - return "rocksdb.NewComparator"; - } - virtual int Compare(const Slice& a, const Slice& b) const override { - return BytewiseComparator()->Compare(a, b); - } - virtual void FindShortestSeparator(std::string* s, - const Slice& l) const override { - BytewiseComparator()->FindShortestSeparator(s, l); - } - virtual void FindShortSuccessor(std::string* key) const override { - BytewiseComparator()->FindShortSuccessor(key); - } - }; - Options options_; -}; - -class SanityTestZlibCompression : public SanityTest { - public: - explicit SanityTestZlibCompression(const std::string& path) - : SanityTest(path) { - options_.compression = kZlibCompression; - } - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "ZlibCompression"; } - - private: - Options options_; -}; - -class SanityTestZlibCompressionVersion2 : public SanityTest { - public: - explicit SanityTestZlibCompressionVersion2(const std::string& path) - : SanityTest(path) { - options_.compression = kZlibCompression; - BlockBasedTableOptions table_options; -#if ROCKSDB_MAJOR > 3 || (ROCKSDB_MAJOR == 3 && ROCKSDB_MINOR >= 10) - table_options.format_version = 2; -#endif - options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); - } - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { - return "ZlibCompressionVersion2"; - } - - private: - Options options_; -}; - -class SanityTestLZ4Compression : public SanityTest { - public: - explicit SanityTestLZ4Compression(const std::string& path) - : SanityTest(path) { - options_.compression = kLZ4Compression; - } - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "LZ4Compression"; } - - private: - Options options_; -}; - -class SanityTestLZ4HCCompression : public SanityTest { - public: - explicit SanityTestLZ4HCCompression(const std::string& path) - : SanityTest(path) { - options_.compression = kLZ4HCCompression; - } - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "LZ4HCCompression"; } - - private: - Options options_; -}; - -class SanityTestZSTDCompression : public SanityTest { - public: - explicit SanityTestZSTDCompression(const std::string& path) - : SanityTest(path) { - options_.compression = kZSTD; - } - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "ZSTDCompression"; } - - private: - Options options_; -}; - -class SanityTestPlainTableFactory : public SanityTest { - public: - explicit SanityTestPlainTableFactory(const std::string& path) - : SanityTest(path) { - options_.table_factory.reset(NewPlainTableFactory()); - options_.prefix_extractor.reset(NewFixedPrefixTransform(2)); - options_.allow_mmap_reads = true; - } - ~SanityTestPlainTableFactory() {} - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "PlainTable"; } - - private: - Options options_; -}; - -class SanityTestBloomFilter : public SanityTest { - public: - explicit SanityTestBloomFilter(const std::string& path) : SanityTest(path) { - BlockBasedTableOptions table_options; - table_options.filter_policy.reset(NewBloomFilterPolicy(10)); - options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); - } - ~SanityTestBloomFilter() {} - virtual Options GetOptions() const override { return options_; } - virtual std::string Name() const override { return "BloomFilter"; } - - private: - Options options_; -}; - -namespace { -bool RunSanityTests(const std::string& command, const std::string& path) { - bool result = true; -// Suppress false positive clang static anaylzer warnings. -#ifndef __clang_analyzer__ - std::vector sanity_tests = { - new SanityTestBasic(path), - new SanityTestSpecialComparator(path), - new SanityTestZlibCompression(path), - new SanityTestZlibCompressionVersion2(path), - new SanityTestLZ4Compression(path), - new SanityTestLZ4HCCompression(path), - new SanityTestZSTDCompression(path), - new SanityTestPlainTableFactory(path), - new SanityTestBloomFilter(path)}; - - if (command == "create") { - fprintf(stderr, "Creating...\n"); - } else { - fprintf(stderr, "Verifying...\n"); - } - for (auto sanity_test : sanity_tests) { - Status s; - fprintf(stderr, "%s -- ", sanity_test->Name().c_str()); - if (command == "create") { - s = sanity_test->Create(); - } else { - assert(command == "verify"); - s = sanity_test->Verify(); - } - fprintf(stderr, "%s\n", s.ToString().c_str()); - if (!s.ok()) { - fprintf(stderr, "FAIL\n"); - result = false; - } - - delete sanity_test; - } -#endif // __clang_analyzer__ - return result; -} -} // namespace - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - std::string path, command; - bool ok = (argc == 3); - if (ok) { - path = std::string(argv[1]); - command = std::string(argv[2]); - ok = (command == "create" || command == "verify"); - } - if (!ok) { - fprintf(stderr, "Usage: %s [create|verify] \n", argv[0]); - exit(1); - } - if (path.back() != '/') { - path += "/"; - } - - bool sanity_ok = ROCKSDB_NAMESPACE::RunSanityTests(command, path); - - return sanity_ok ? 0 : 1; -} diff --git a/tools/io_tracer_parser_test.cc b/tools/io_tracer_parser_test.cc deleted file mode 100644 index 8e1fb72df..000000000 --- a/tools/io_tracer_parser_test.cc +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// - -#ifndef GFLAGS -#include -int main() { - fprintf(stderr, "Please install gflags to run io_tracer_parser_test\n"); - return 0; -} -#else - -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/status.h" -#include "rocksdb/trace_reader_writer.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "tools/io_tracer_parser_tool.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -const int kMaxArgCount = 100; -const size_t kArgBufferSize = 100000; -} // namespace - -class IOTracerParserTest : public testing::Test { - public: - IOTracerParserTest() { - test_path_ = test::PerThreadDBPath("io_tracer_parser_test"); - env_ = ROCKSDB_NAMESPACE::Env::Default(); - EXPECT_OK(env_->CreateDirIfMissing(test_path_)); - trace_file_path_ = test_path_ + "/io_trace_file"; - dbname_ = test_path_ + "/db"; - Options options; - options.create_if_missing = true; - EXPECT_OK(DB::Open(options, dbname_, &db_)); - } - - ~IOTracerParserTest() { - if (env_->FileExists(trace_file_path_).ok()) { - EXPECT_OK(env_->DeleteFile(trace_file_path_)); - } - if (db_ != nullptr) { - Options options; - options.env = env_; - delete db_; - db_ = nullptr; - EXPECT_OK(DestroyDB(dbname_, options)); - } - EXPECT_OK(env_->DeleteDir(test_path_)); - } - - void GenerateIOTrace() { - WriteOptions write_opt; - TraceOptions trace_opt; - std::unique_ptr trace_writer; - - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - - ASSERT_OK(db_->StartIOTrace(trace_opt, std::move(trace_writer))); - - for (int i = 0; i < 10; i++) { - ASSERT_OK(db_->Put(write_opt, "key_" + std::to_string(i), - "value_" + std::to_string(i))); - ASSERT_OK(db_->Flush(FlushOptions())); - } - - ASSERT_OK(db_->EndIOTrace()); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - - void RunIOTracerParserTool() { - std::vector params = {"./io_tracer_parser", - "-io_trace_file=" + trace_file_path_}; - - char arg_buffer[kArgBufferSize]; - char* argv[kMaxArgCount]; - int argc = 0; - int cursor = 0; - for (const auto& arg : params) { - ASSERT_LE(cursor + arg.size() + 1, kArgBufferSize); - ASSERT_LE(argc + 1, kMaxArgCount); - - snprintf(arg_buffer + cursor, arg.size() + 1, "%s", arg.c_str()); - - argv[argc++] = arg_buffer + cursor; - cursor += static_cast(arg.size()) + 1; - } - ASSERT_EQ(0, ROCKSDB_NAMESPACE::io_tracer_parser(argc, argv)); - } - - DB* db_; - Env* env_; - EnvOptions env_options_; - std::string trace_file_path_; - std::string output_file_; - std::string test_path_; - std::string dbname_; -}; - -TEST_F(IOTracerParserTest, InvalidArguments) { - { - std::vector params = {"./io_tracer_parser"}; - char arg_buffer[kArgBufferSize]; - char* argv[kMaxArgCount]; - int argc = 0; - int cursor = 0; - for (const auto& arg : params) { - ASSERT_LE(cursor + arg.size() + 1, kArgBufferSize); - ASSERT_LE(argc + 1, kMaxArgCount); - - snprintf(arg_buffer + cursor, arg.size() + 1, "%s", arg.c_str()); - - argv[argc++] = arg_buffer + cursor; - cursor += static_cast(arg.size()) + 1; - } - ASSERT_EQ(1, ROCKSDB_NAMESPACE::io_tracer_parser(argc, argv)); - } -} - -TEST_F(IOTracerParserTest, DumpAndParseIOTraceRecords) { - GenerateIOTrace(); - RunIOTracerParserTool(); -} - -TEST_F(IOTracerParserTest, NoRecordingAfterEndIOTrace) { - uint64_t file_size = 0; - // Generate IO trace records and parse them. - { - GenerateIOTrace(); - RunIOTracerParserTool(); - ASSERT_OK(env_->GetFileSize(trace_file_path_, &file_size)); - } - // Once DB::EndIOTrace is invoked in GenerateIOTrace(), no new records should - // be appended. - { - WriteOptions write_opt; - for (int i = 10; i < 20; i++) { - ASSERT_OK(db_->Put(write_opt, "key_" + std::to_string(i), - "value_" + std::to_string(i))); - ASSERT_OK(db_->Flush(FlushOptions())); - } - } - - uint64_t new_file_size = 0; - ASSERT_OK(env_->GetFileSize(trace_file_path_, &new_file_size)); - ASSERT_EQ(file_size, new_file_size); -} - -TEST_F(IOTracerParserTest, NoRecordingBeforeStartIOTrace) { - { - WriteOptions write_opt; - for (int i = 10; i < 20; i++) { - ASSERT_OK(db_->Put(write_opt, "key_" + std::to_string(i), - "value_" + std::to_string(i))); - ASSERT_OK(db_->Flush(FlushOptions())); - } - // IO trace file doesn't exist - ASSERT_NOK(env_->FileExists(trace_file_path_)); - } - // Generate IO trace records and parse them. - { - GenerateIOTrace(); - RunIOTracerParserTool(); - } -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} -#endif // GFLAGS diff --git a/tools/ldb_cmd_test.cc b/tools/ldb_cmd_test.cc deleted file mode 100644 index c5b4115d1..000000000 --- a/tools/ldb_cmd_test.cc +++ /dev/null @@ -1,1216 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include "rocksdb/utilities/ldb_cmd.h" - -#include - -#include "db/db_test_util.h" -#include "db/version_edit.h" -#include "db/version_set.h" -#include "env/composite_env_wrapper.h" -#include "file/filename.h" -#include "port/stack_trace.h" -#include "rocksdb/advanced_options.h" -#include "rocksdb/convenience.h" -#include "rocksdb/db.h" -#include "rocksdb/file_checksum.h" -#include "rocksdb/file_system.h" -#include "rocksdb/utilities/options_util.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/file_checksum_helper.h" -#include "util/random.h" - -using std::map; -using std::string; -using std::vector; - -namespace ROCKSDB_NAMESPACE { - -class LdbCmdTest : public testing::Test { - public: - LdbCmdTest() : testing::Test() {} - - Env* TryLoadCustomOrDefaultEnv() { - Env* env = Env::Default(); - EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env, &env_guard_)); - return env; - } - - private: - std::shared_ptr env_guard_; -}; - -TEST_F(LdbCmdTest, HelpAndVersion) { - Options o; - o.env = TryLoadCustomOrDefaultEnv(); - LDBOptions lo; - static const char* help[] = {"./ldb", "--help"}; - ASSERT_EQ(0, LDBCommandRunner::RunCommand(2, help, o, lo, nullptr)); - static const char* version[] = {"./ldb", "--version"}; - ASSERT_EQ(0, LDBCommandRunner::RunCommand(2, version, o, lo, nullptr)); - static const char* bad[] = {"./ldb", "--not_an_option"}; - ASSERT_NE(0, LDBCommandRunner::RunCommand(2, bad, o, lo, nullptr)); -} - -TEST_F(LdbCmdTest, HexToString) { - // map input to expected outputs. - // odd number of "hex" half bytes doesn't make sense - map> inputMap = { - {"0x07", {7}}, {"0x5050", {80, 80}}, {"0xFF", {-1}}, - {"0x1234", {18, 52}}, {"0xaaAbAC", {-86, -85, -84}}, {"0x1203", {18, 3}}, - }; - - for (const auto& inPair : inputMap) { - auto actual = ROCKSDB_NAMESPACE::LDBCommand::HexToString(inPair.first); - auto expected = inPair.second; - for (unsigned int i = 0; i < actual.length(); i++) { - EXPECT_EQ(expected[i], static_cast((signed char)actual[i])); - } - auto reverse = ROCKSDB_NAMESPACE::LDBCommand::StringToHex(actual); - EXPECT_STRCASEEQ(inPair.first.c_str(), reverse.c_str()); - } -} - -TEST_F(LdbCmdTest, HexToStringBadInputs) { - const vector badInputs = { - "0xZZ", "123", "0xx5", "0x111G", "0x123", "Ox12", "0xT", "0x1Q1", - }; - for (const auto& badInput : badInputs) { - try { - ROCKSDB_NAMESPACE::LDBCommand::HexToString(badInput); - std::cerr << "Should fail on bad hex value: " << badInput << "\n"; - FAIL(); - } catch (...) { - } - } -} - -TEST_F(LdbCmdTest, MemEnv) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - for (int i = 0; i < 100; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - ASSERT_OK(db->Put(wopts, buf, buf)); - } - FlushOptions fopts; - fopts.wait = true; - ASSERT_OK(db->Flush(fopts)); - - delete db; - - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "dump_live_files"; - char* argv[] = {arg1, arg2, arg3}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr)); -} - -class FileChecksumTestHelper { - private: - Options options_; - DB* db_; - std::string dbname_; - - Status VerifyChecksum(LiveFileMetaData& file_meta) { - std::string cur_checksum; - std::string checksum_func_name; - - Status s; - EnvOptions soptions; - std::unique_ptr file_reader; - std::string file_path = dbname_ + "/" + file_meta.name; - s = options_.env->NewSequentialFile(file_path, &file_reader, soptions); - if (!s.ok()) { - return s; - } - std::unique_ptr scratch(new char[2048]); - Slice result; - FileChecksumGenFactory* file_checksum_gen_factory = - options_.file_checksum_gen_factory.get(); - if (file_checksum_gen_factory == nullptr) { - cur_checksum = kUnknownFileChecksum; - checksum_func_name = kUnknownFileChecksumFuncName; - } else { - FileChecksumGenContext gen_context; - gen_context.file_name = file_meta.name; - std::unique_ptr file_checksum_gen = - file_checksum_gen_factory->CreateFileChecksumGenerator(gen_context); - checksum_func_name = file_checksum_gen->Name(); - s = file_reader->Read(2048, &result, scratch.get()); - if (!s.ok()) { - return s; - } - while (result.size() != 0) { - file_checksum_gen->Update(scratch.get(), result.size()); - s = file_reader->Read(2048, &result, scratch.get()); - if (!s.ok()) { - return s; - } - } - file_checksum_gen->Finalize(); - cur_checksum = file_checksum_gen->GetChecksum(); - } - - std::string stored_checksum = file_meta.file_checksum; - std::string stored_checksum_func_name = file_meta.file_checksum_func_name; - if ((cur_checksum != stored_checksum) || - (checksum_func_name != stored_checksum_func_name)) { - return Status::Corruption( - "Checksum does not match! The file: " + file_meta.name + - ", checksum name: " + stored_checksum_func_name + " and checksum " + - stored_checksum + ". However, expected checksum name: " + - checksum_func_name + " and checksum " + cur_checksum); - } - return Status::OK(); - } - - public: - FileChecksumTestHelper(Options& options, DB* db, std::string db_name) - : options_(options), db_(db), dbname_(db_name) {} - ~FileChecksumTestHelper() {} - - // Verify the checksum information in Manifest. - Status VerifyChecksumInManifest( - const std::vector& live_files) { - // Step 1: verify if the dbname_ is correct - if (dbname_.back() != '/') { - dbname_.append("/"); - } - - // Step 2, get the the checksum information by recovering the VersionSet - // from Manifest. - std::unique_ptr checksum_list(NewFileChecksumList()); - EnvOptions sopt; - std::shared_ptr tc(NewLRUCache(options_.max_open_files - 10, - options_.table_cache_numshardbits)); - options_.db_paths.emplace_back(dbname_, 0); - options_.num_levels = 64; - WriteController wc(options_.delayed_write_rate); - WriteBufferManager wb(options_.db_write_buffer_size); - ImmutableDBOptions immutable_db_options(options_); - VersionSet versions(dbname_, &immutable_db_options, sopt, tc.get(), &wb, - &wc, nullptr, nullptr, "", ""); - std::vector cf_name_list; - Status s; - s = versions.ListColumnFamilies(&cf_name_list, dbname_, - immutable_db_options.fs.get()); - if (s.ok()) { - std::vector cf_list; - for (const auto& name : cf_name_list) { - fprintf(stdout, "cf_name: %s", name.c_str()); - cf_list.emplace_back(name, ColumnFamilyOptions(options_)); - } - s = versions.Recover(cf_list, true); - } - if (s.ok()) { - s = versions.GetLiveFilesChecksumInfo(checksum_list.get()); - } - if (!s.ok()) { - return s; - } - - // Step 3 verify the checksum - if (live_files.size() != checksum_list->size()) { - return Status::Corruption("The number of files does not match!"); - } - for (size_t i = 0; i < live_files.size(); i++) { - std::string stored_checksum = ""; - std::string stored_func_name = ""; - s = checksum_list->SearchOneFileChecksum( - live_files[i].file_number, &stored_checksum, &stored_func_name); - if (s.IsNotFound()) { - return s; - } - if (live_files[i].file_checksum != stored_checksum || - live_files[i].file_checksum_func_name != stored_func_name) { - return Status::Corruption( - "Checksum does not match! The file: " + - std::to_string(live_files[i].file_number) + - ". In Manifest, checksum name: " + stored_func_name + - " and checksum " + stored_checksum + - ". However, expected checksum name: " + - live_files[i].file_checksum_func_name + " and checksum " + - live_files[i].file_checksum); - } - } - return Status::OK(); - } - - // Verify the checksum of each file by recalculting the checksum and - // comparing it with the one being generated when a SST file is created. - Status VerifyEachFileChecksum() { - assert(db_ != nullptr); - EXPECT_OK(db_->DisableFileDeletions()); - std::vector live_files; - db_->GetLiveFilesMetaData(&live_files); - Status cs; - for (auto a_file : live_files) { - cs = VerifyChecksum(a_file); - if (!cs.ok()) { - break; - } - } - EXPECT_OK(db_->EnableFileDeletions()); - return cs; - } -}; - -TEST_F(LdbCmdTest, DumpFileChecksumNoChecksum) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - FlushOptions fopts; - fopts.wait = true; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 200; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 100; i < 300; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 200; i < 400; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 300; i < 400; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - ASSERT_OK(db->Close()); - delete db; - - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "file_checksum_dump"; - char arg4[] = "--hex"; - char* argv[] = {arg1, arg2, arg3, arg4}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Verify each sst file checksum value and checksum name - FileChecksumTestHelper fct_helper(opts, db, dbname); - ASSERT_OK(fct_helper.VerifyEachFileChecksum()); - - // Manually trigger compaction - char b_buf[16]; - snprintf(b_buf, sizeof(b_buf), "%08d", 0); - char e_buf[16]; - snprintf(e_buf, sizeof(e_buf), "%08d", 399); - Slice begin(b_buf); - Slice end(e_buf); - CompactRangeOptions options; - ASSERT_OK(db->CompactRange(options, &begin, &end)); - // Verify each sst file checksum after compaction - FileChecksumTestHelper fct_helper_ac(opts, db, dbname); - ASSERT_OK(fct_helper_ac.VerifyEachFileChecksum()); - - ASSERT_OK(db->Close()); - delete db; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Verify the checksum information in memory is the same as that in Manifest; - std::vector live_files; - db->GetLiveFilesMetaData(&live_files); - delete db; - ASSERT_OK(fct_helper_ac.VerifyChecksumInManifest(live_files)); -} - -TEST_F(LdbCmdTest, BlobDBDumpFileChecksumNoChecksum) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - opts.enable_blob_files = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - FlushOptions fopts; - fopts.wait = true; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 200; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 100; i < 300; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 200; i < 400; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 300; i < 400; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - ASSERT_OK(db->Close()); - delete db; - - char arg1[] = "./ldb"; - std::string arg2_str = "--db=" + dbname; - char arg3[] = "file_checksum_dump"; - char arg4[] = "--hex"; - char* argv[] = {arg1, const_cast(arg2_str.c_str()), arg3, arg4}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Verify each sst and blob file checksum value and checksum name - FileChecksumTestHelper fct_helper(opts, db, dbname); - ASSERT_OK(fct_helper.VerifyEachFileChecksum()); - - // Manually trigger compaction - std::ostringstream oss_b_buf; - oss_b_buf << std::setfill('0') << std::setw(8) << std::fixed << 0; - std::ostringstream oss_e_buf; - oss_e_buf << std::setfill('0') << std::setw(8) << std::fixed << 399; - std::string b_buf = oss_b_buf.str(); - std::string e_buf = oss_e_buf.str(); - Slice begin(b_buf); - Slice end(e_buf); - - CompactRangeOptions options; - ASSERT_OK(db->CompactRange(options, &begin, &end)); - // Verify each sst file checksum after compaction - FileChecksumTestHelper fct_helper_ac(opts, db, dbname); - ASSERT_OK(fct_helper_ac.VerifyEachFileChecksum()); - - ASSERT_OK(db->Close()); - delete db; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); -} - -TEST_F(LdbCmdTest, DumpFileChecksumCRC32) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - opts.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - FlushOptions fopts; - fopts.wait = true; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 100; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 50; i < 150; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 100; i < 200; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 150; i < 250; i++) { - char buf[16]; - snprintf(buf, sizeof(buf), "%08d", i); - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, buf, v)); - } - ASSERT_OK(db->Flush(fopts)); - ASSERT_OK(db->Close()); - delete db; - - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "file_checksum_dump"; - char arg4[] = "--hex"; - char* argv[] = {arg1, arg2, arg3, arg4}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Verify each sst file checksum value and checksum name - FileChecksumTestHelper fct_helper(opts, db, dbname); - ASSERT_OK(fct_helper.VerifyEachFileChecksum()); - - // Manually trigger compaction - char b_buf[16]; - snprintf(b_buf, sizeof(b_buf), "%08d", 0); - char e_buf[16]; - snprintf(e_buf, sizeof(e_buf), "%08d", 249); - Slice begin(b_buf); - Slice end(e_buf); - CompactRangeOptions options; - ASSERT_OK(db->CompactRange(options, &begin, &end)); - // Verify each sst file checksum after compaction - FileChecksumTestHelper fct_helper_ac(opts, db, dbname); - ASSERT_OK(fct_helper_ac.VerifyEachFileChecksum()); - - ASSERT_OK(db->Close()); - delete db; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Verify the checksum information in memory is the same as that in Manifest; - std::vector live_files; - db->GetLiveFilesMetaData(&live_files); - ASSERT_OK(fct_helper_ac.VerifyChecksumInManifest(live_files)); - - ASSERT_OK(db->Close()); - delete db; -} - -TEST_F(LdbCmdTest, BlobDBDumpFileChecksumCRC32) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - opts.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory(); - opts.enable_blob_files = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - FlushOptions fopts; - fopts.wait = true; - Random rnd(test::RandomSeed()); - for (int i = 0; i < 100; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 50; i < 150; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 100; i < 200; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - for (int i = 150; i < 250; i++) { - std::ostringstream oss; - oss << std::setfill('0') << std::setw(8) << std::fixed << i; - std::string v = rnd.RandomString(100); - ASSERT_OK(db->Put(wopts, oss.str(), v)); - } - ASSERT_OK(db->Flush(fopts)); - ASSERT_OK(db->Close()); - delete db; - - char arg1[] = "./ldb"; - std::string arg2_str = "--db=" + dbname; - char arg3[] = "file_checksum_dump"; - char arg4[] = "--hex"; - char* argv[] = {arg1, const_cast(arg2_str.c_str()), arg3, arg4}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Verify each sst and blob file checksum value and checksum name - FileChecksumTestHelper fct_helper(opts, db, dbname); - ASSERT_OK(fct_helper.VerifyEachFileChecksum()); - - // Manually trigger compaction - std::ostringstream oss_b_buf; - oss_b_buf << std::setfill('0') << std::setw(8) << std::fixed << 0; - std::ostringstream oss_e_buf; - oss_e_buf << std::setfill('0') << std::setw(8) << std::fixed << 249; - std::string b_buf = oss_b_buf.str(); - std::string e_buf = oss_e_buf.str(); - Slice begin(b_buf); - Slice end(e_buf); - - CompactRangeOptions options; - ASSERT_OK(db->CompactRange(options, &begin, &end)); - // Verify each sst file checksum after compaction - FileChecksumTestHelper fct_helper_ac(opts, db, dbname); - ASSERT_OK(fct_helper_ac.VerifyEachFileChecksum()); - - ASSERT_OK(db->Close()); - delete db; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); -} - -TEST_F(LdbCmdTest, OptionParsing) { - // test parsing flags - Options opts; - opts.env = TryLoadCustomOrDefaultEnv(); - { - std::vector args; - args.push_back("scan"); - args.push_back("--ttl"); - args.push_back("--timestamp"); - LDBCommand* command = ROCKSDB_NAMESPACE::LDBCommand::InitFromCmdLineArgs( - args, opts, LDBOptions(), nullptr); - const std::vector flags = command->TEST_GetFlags(); - EXPECT_EQ(flags.size(), 2); - EXPECT_EQ(flags[0], "ttl"); - EXPECT_EQ(flags[1], "timestamp"); - delete command; - } - // test parsing options which contains equal sign in the option value - { - std::vector args; - args.push_back("scan"); - args.push_back("--db=/dev/shm/ldbtest/"); - args.push_back( - "--from='abcd/efg/hijk/lmn/" - "opq:__rst.uvw.xyz?a=3+4+bcd+efghi&jk=lm_no&pq=rst-0&uv=wx-8&yz=a&bcd_" - "ef=gh.ijk'"); - LDBCommand* command = ROCKSDB_NAMESPACE::LDBCommand::InitFromCmdLineArgs( - args, opts, LDBOptions(), nullptr); - const std::map option_map = - command->TEST_GetOptionMap(); - EXPECT_EQ(option_map.at("db"), "/dev/shm/ldbtest/"); - EXPECT_EQ(option_map.at("from"), - "'abcd/efg/hijk/lmn/" - "opq:__rst.uvw.xyz?a=3+4+bcd+efghi&jk=lm_no&pq=rst-0&uv=wx-8&yz=" - "a&bcd_ef=gh.ijk'"); - delete command; - } -} - -TEST_F(LdbCmdTest, ListFileTombstone) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - ASSERT_OK(db->Put(wopts, "foo", "1")); - ASSERT_OK(db->Put(wopts, "bar", "2")); - - FlushOptions fopts; - fopts.wait = true; - ASSERT_OK(db->Flush(fopts)); - - ASSERT_OK(db->DeleteRange(wopts, db->DefaultColumnFamily(), "foo", "foo2")); - ASSERT_OK(db->DeleteRange(wopts, db->DefaultColumnFamily(), "bar", "foo2")); - ASSERT_OK(db->Flush(fopts)); - - delete db; - - { - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "list_file_range_deletes"; - char* argv[] = {arg1, arg2, arg3}; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ListFileRangeDeletesCommand::DoCommand:BeforePrint", [&](void* arg) { - std::string* out_str = reinterpret_cast(arg); - - // Count number of tombstones printed - int num_tb = 0; - const std::string kFingerprintStr = "start: "; - auto offset = out_str->find(kFingerprintStr); - while (offset != std::string::npos) { - num_tb++; - offset = - out_str->find(kFingerprintStr, offset + kFingerprintStr.size()); - } - EXPECT_EQ(2, num_tb); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } - - // Test the case of limiting tombstones - { - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "list_file_range_deletes"; - char arg4[] = "--max_keys=1"; - char* argv[] = {arg1, arg2, arg3, arg4}; - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "ListFileRangeDeletesCommand::DoCommand:BeforePrint", [&](void* arg) { - std::string* out_str = reinterpret_cast(arg); - - // Count number of tombstones printed - int num_tb = 0; - const std::string kFingerprintStr = "start: "; - auto offset = out_str->find(kFingerprintStr); - while (offset != std::string::npos) { - num_tb++; - offset = - out_str->find(kFingerprintStr, offset + kFingerprintStr.size()); - } - EXPECT_EQ(1, num_tb); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(LdbCmdTest, DisableConsistencyChecks) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - - { - DB* db = nullptr; - ASSERT_OK(DB::Open(opts, dbname, &db)); - - WriteOptions wopts; - FlushOptions fopts; - fopts.wait = true; - - ASSERT_OK(db->Put(wopts, "foo1", "1")); - ASSERT_OK(db->Put(wopts, "bar1", "2")); - ASSERT_OK(db->Flush(fopts)); - - ASSERT_OK(db->Put(wopts, "foo2", "3")); - ASSERT_OK(db->Put(wopts, "bar2", "4")); - ASSERT_OK(db->Flush(fopts)); - - delete db; - } - - { - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "checkconsistency"; - char* argv[] = {arg1, arg2, arg3}; - - SyncPoint::GetInstance()->SetCallBack( - "Version::PrepareAppend:forced_check", [&](void* arg) { - bool* forced = reinterpret_cast(arg); - ASSERT_TRUE(*forced); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr)); - - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - } - { - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "scan"; - char* argv[] = {arg1, arg2, arg3}; - - SyncPoint::GetInstance()->SetCallBack( - "Version::PrepareAppend:forced_check", [&](void* arg) { - bool* forced = reinterpret_cast(arg); - ASSERT_TRUE(*forced); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(3, argv, opts, LDBOptions(), nullptr)); - - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - } - { - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "scan"; - char arg4[] = "--disable_consistency_checks"; - char* argv[] = {arg1, arg2, arg3, arg4}; - - SyncPoint::GetInstance()->SetCallBack( - "ColumnFamilyData::ColumnFamilyData", [&](void* arg) { - ColumnFamilyOptions* cfo = - reinterpret_cast(arg); - ASSERT_FALSE(cfo->force_consistency_checks); - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - } -} - -TEST_F(LdbCmdTest, TestBadDbPath) { - Env* base_env = TryLoadCustomOrDefaultEnv(); - std::unique_ptr env(NewMemEnv(base_env)); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s/.no_such_dir", dbname.c_str()); - char arg3[1024]; - snprintf(arg3, sizeof(arg3), "create_column_family"); - char arg4[] = "bad cf"; - char* argv[] = {arg1, arg2, arg3, arg4}; - - ASSERT_EQ(1, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - snprintf(arg3, sizeof(arg3), "drop_column_family"); - ASSERT_EQ(1, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); -} -namespace { -class WrappedEnv : public EnvWrapper { - public: - explicit WrappedEnv(Env* t) : EnvWrapper(t) {} - static const char* kClassName() { return "WrappedEnv"; } - const char* Name() const override { return kClassName(); } -}; -} // namespace -TEST_F(LdbCmdTest, LoadCFOptionsAndOverride) { - // Env* base_env = TryLoadCustomOrDefaultEnv(); - // std::unique_ptr env(NewMemEnv(base_env)); - std::unique_ptr env(new WrappedEnv(Env::Default())); - Options opts; - opts.env = env.get(); - opts.create_if_missing = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DestroyDB(dbname, opts)); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - ColumnFamilyHandle* cf_handle; - ColumnFamilyOptions cf_opts; - cf_opts.num_levels = 20; - ASSERT_OK(db->CreateColumnFamily(cf_opts, "cf1", &cf_handle)); - - delete cf_handle; - delete db; - - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "put"; - char arg4[] = "key1"; - char arg5[] = "value1"; - char arg6[] = "--try_load_options"; - char arg7[] = "--column_family=cf1"; - char arg8[] = "--write_buffer_size=268435456"; - char* argv[] = {arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(8, argv, opts, LDBOptions(), nullptr)); - - ConfigOptions config_opts; - Options options; - std::vector column_families; - config_opts.env = env.get(); - ASSERT_OK(LoadLatestOptions(config_opts, dbname, &options, &column_families)); - ASSERT_EQ(column_families.size(), 2); - ASSERT_EQ(options.num_levels, opts.num_levels); - ASSERT_EQ(column_families[1].options.num_levels, cf_opts.num_levels); - ASSERT_EQ(column_families[1].options.write_buffer_size, 268435456); -} - -TEST_F(LdbCmdTest, UnsafeRemoveSstFile) { - Options opts; - opts.level0_file_num_compaction_trigger = 10; - opts.create_if_missing = true; - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(Env::Default(), "ldb_cmd_test"); - ASSERT_OK(DestroyDB(dbname, opts)); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - // Create three SST files - for (size_t i = 0; i < 3; ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), std::to_string(i))); - ASSERT_OK(db->Flush(FlushOptions())); - } - - // Determine which is the "middle" one - std::vector sst_files; - db->GetLiveFilesMetaData(&sst_files); - - std::vector numbers; - for (auto& f : sst_files) { - numbers.push_back(f.file_number); - } - ASSERT_EQ(numbers.size(), 3); - std::sort(numbers.begin(), numbers.end()); - uint64_t to_remove = numbers[1]; - - // Close for unsafe_remove_sst_file - delete db; - db = nullptr; - - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "unsafe_remove_sst_file"; - char arg4[20]; - snprintf(arg4, sizeof(arg4), "%" PRIu64, to_remove); - char* argv[] = {arg1, arg2, arg3, arg4}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - // Re-open, and verify with Get that middle file is gone - ASSERT_OK(DB::Open(opts, dbname, &db)); - - std::string val; - ASSERT_OK(db->Get(ReadOptions(), "0", &val)); - ASSERT_EQ(val, "0"); - - ASSERT_OK(db->Get(ReadOptions(), "2", &val)); - ASSERT_EQ(val, "2"); - - ASSERT_TRUE(db->Get(ReadOptions(), "1", &val).IsNotFound()); - - // Now with extra CF, two more files - ColumnFamilyHandle* cf_handle; - ColumnFamilyOptions cf_opts; - ASSERT_OK(db->CreateColumnFamily(cf_opts, "cf1", &cf_handle)); - for (size_t i = 3; i < 5; ++i) { - ASSERT_OK(db->Put(WriteOptions(), cf_handle, std::to_string(i), - std::to_string(i))); - ASSERT_OK(db->Flush(FlushOptions(), cf_handle)); - } - - // Determine which is the "last" one - sst_files.clear(); - db->GetLiveFilesMetaData(&sst_files); - - numbers.clear(); - for (auto& f : sst_files) { - numbers.push_back(f.file_number); - } - ASSERT_EQ(numbers.size(), 4); - std::sort(numbers.begin(), numbers.end()); - to_remove = numbers.back(); - - // Close for unsafe_remove_sst_file - delete cf_handle; - delete db; - db = nullptr; - - snprintf(arg4, sizeof(arg4), "%" PRIu64, to_remove); - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - std::vector cfds = {{kDefaultColumnFamilyName, opts}, - {"cf1", cf_opts}}; - std::vector handles; - ASSERT_OK(DB::Open(opts, dbname, cfds, &handles, &db)); - - ASSERT_OK(db->Get(ReadOptions(), handles[1], "3", &val)); - ASSERT_EQ(val, "3"); - - ASSERT_TRUE(db->Get(ReadOptions(), handles[1], "4", &val).IsNotFound()); - - ASSERT_OK(db->Get(ReadOptions(), handles[0], "0", &val)); - ASSERT_EQ(val, "0"); - - // Determine which is the "first" one (most likely to be opened in recovery) - sst_files.clear(); - db->GetLiveFilesMetaData(&sst_files); - - numbers.clear(); - for (auto& f : sst_files) { - numbers.push_back(f.file_number); - } - ASSERT_EQ(numbers.size(), 3); - std::sort(numbers.begin(), numbers.end()); - to_remove = numbers.front(); - - // This time physically delete the file before unsafe_remove - { - std::string f = dbname + "/" + MakeTableFileName(to_remove); - ASSERT_OK(Env::Default()->DeleteFile(f)); - } - - // Close for unsafe_remove_sst_file - for (auto& h : handles) { - delete h; - } - delete db; - db = nullptr; - - snprintf(arg4, sizeof(arg4), "%" PRIu64, to_remove); - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - ASSERT_OK(DB::Open(opts, dbname, cfds, &handles, &db)); - - ASSERT_OK(db->Get(ReadOptions(), handles[1], "3", &val)); - ASSERT_EQ(val, "3"); - - ASSERT_TRUE(db->Get(ReadOptions(), handles[0], "0", &val).IsNotFound()); - - for (auto& h : handles) { - delete h; - } - delete db; -} - -TEST_F(LdbCmdTest, FileTemperatureUpdateManifest) { - auto test_fs = std::make_shared(FileSystem::Default()); - std::unique_ptr env(new CompositeEnvWrapper(Env::Default(), test_fs)); - Options opts; - opts.bottommost_temperature = Temperature::kWarm; - opts.level0_file_num_compaction_trigger = 10; - opts.create_if_missing = true; - opts.env = env.get(); - - DB* db = nullptr; - std::string dbname = test::PerThreadDBPath(env.get(), "ldb_cmd_test"); - ASSERT_OK(DestroyDB(dbname, opts)); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - std::array kTestTemps = { - Temperature::kCold, Temperature::kWarm, Temperature::kHot, - Temperature::kWarm, Temperature::kCold}; - std::map number_to_temp; - for (size_t i = 0; i < kTestTemps.size(); ++i) { - ASSERT_OK(db->Put(WriteOptions(), std::to_string(i), std::to_string(i))); - ASSERT_OK(db->Flush(FlushOptions())); - - std::map current_temps; - test_fs->CopyCurrentSstFileTemperatures(¤t_temps); - for (auto e : current_temps) { - if (e.second == Temperature::kUnknown) { - test_fs->OverrideSstFileTemperature(e.first, kTestTemps[i]); - number_to_temp[e.first] = kTestTemps[i]; - } - } - } - - // Close & reopen - delete db; - db = nullptr; - test_fs->PopRequestedSstFileTemperatures(); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - for (size_t i = 0; i < kTestTemps.size(); ++i) { - std::string val; - ASSERT_OK(db->Get(ReadOptions(), std::to_string(i), &val)); - ASSERT_EQ(val, std::to_string(i)); - } - - // Still all unknown - std::vector> requests; - test_fs->PopRequestedSstFileTemperatures(&requests); - ASSERT_EQ(requests.size(), kTestTemps.size()); - for (auto& r : requests) { - ASSERT_EQ(r.second, Temperature::kUnknown); - } - - // Close for update_manifest - delete db; - db = nullptr; - - char arg1[] = "./ldb"; - char arg2[1024]; - snprintf(arg2, sizeof(arg2), "--db=%s", dbname.c_str()); - char arg3[] = "update_manifest"; - char arg4[] = "--update_temperatures"; - char* argv[] = {arg1, arg2, arg3, arg4}; - - ASSERT_EQ(0, - LDBCommandRunner::RunCommand(4, argv, opts, LDBOptions(), nullptr)); - - // Re-open, get, and verify manifest temps (based on request) - test_fs->PopRequestedSstFileTemperatures(); - ASSERT_OK(DB::Open(opts, dbname, &db)); - - for (size_t i = 0; i < kTestTemps.size(); ++i) { - std::string val; - ASSERT_OK(db->Get(ReadOptions(), std::to_string(i), &val)); - ASSERT_EQ(val, std::to_string(i)); - } - - requests.clear(); - test_fs->PopRequestedSstFileTemperatures(&requests); - ASSERT_EQ(requests.size(), kTestTemps.size()); - for (auto& r : requests) { - ASSERT_EQ(r.second, number_to_temp[r.first]); - } - delete db; -} - -TEST_F(LdbCmdTest, RenameDbAndLoadOptions) { - Env* env = TryLoadCustomOrDefaultEnv(); - Options opts; - opts.env = env; - opts.create_if_missing = false; - - std::string old_dbname = test::PerThreadDBPath(env, "ldb_cmd_test"); - std::string new_dbname = old_dbname + "_2"; - ASSERT_OK(DestroyDB(old_dbname, opts)); - ASSERT_OK(DestroyDB(new_dbname, opts)); - - char old_arg[1024]; - snprintf(old_arg, sizeof(old_arg), "--db=%s", old_dbname.c_str()); - char new_arg[1024]; - snprintf(new_arg, sizeof(old_arg), "--db=%s", new_dbname.c_str()); - const char* argv1[] = {"./ldb", - old_arg, - "put", - "key1", - "value1", - "--try_load_options", - "--create_if_missing"}; - - const char* argv2[] = {"./ldb", old_arg, "get", "key1", "--try_load_options"}; - const char* argv3[] = {"./ldb", new_arg, "put", - "key2", "value2", "--try_load_options"}; - - const char* argv4[] = {"./ldb", new_arg, "get", "key1", "--try_load_options"}; - const char* argv5[] = {"./ldb", new_arg, "get", "key2", "--try_load_options"}; - - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(7, argv1, opts, LDBOptions(), nullptr)); - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(5, argv2, opts, LDBOptions(), nullptr)); - ConfigOptions config_opts; - Options options; - std::vector column_families; - config_opts.env = env; - ASSERT_OK( - LoadLatestOptions(config_opts, old_dbname, &options, &column_families)); - ASSERT_EQ(options.wal_dir, ""); - - ASSERT_OK(env->RenameFile(old_dbname, new_dbname)); - ASSERT_NE( - 0, LDBCommandRunner::RunCommand(6, argv1, opts, LDBOptions(), nullptr)); - ASSERT_NE( - 0, LDBCommandRunner::RunCommand(5, argv2, opts, LDBOptions(), nullptr)); - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(6, argv3, opts, LDBOptions(), nullptr)); - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(5, argv4, opts, LDBOptions(), nullptr)); - ASSERT_EQ( - 0, LDBCommandRunner::RunCommand(5, argv5, opts, LDBOptions(), nullptr)); - ASSERT_OK(DestroyDB(new_dbname, opts)); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tools/reduce_levels_test.cc b/tools/reduce_levels_test.cc deleted file mode 100644 index 97f8030b7..000000000 --- a/tools/reduce_levels_test.cc +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// - - -#include "db/db_impl/db_impl.h" -#include "db/version_set.h" -#include "rocksdb/db.h" -#include "rocksdb/utilities/ldb_cmd.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "tools/ldb_cmd_impl.h" -#include "util/cast_util.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class ReduceLevelTest : public testing::Test { - public: - ReduceLevelTest() { - dbname_ = test::PerThreadDBPath("db_reduce_levels_test"); - EXPECT_OK(DestroyDB(dbname_, Options())); - db_ = nullptr; - } - - Status OpenDB(bool create_if_missing, int levels); - - Status Put(const std::string& k, const std::string& v) { - return db_->Put(WriteOptions(), k, v); - } - - std::string Get(const std::string& k) { - ReadOptions options; - std::string result; - Status s = db_->Get(options, k, &result); - if (s.IsNotFound()) { - result = "NOT_FOUND"; - } else if (!s.ok()) { - result = s.ToString(); - } - return result; - } - - Status Flush() { - if (db_ == nullptr) { - return Status::InvalidArgument("DB not opened."); - } - DBImpl* db_impl = static_cast_with_check(db_); - return db_impl->TEST_FlushMemTable(); - } - - void MoveL0FileToLevel(int level) { - DBImpl* db_impl = static_cast_with_check(db_); - for (int i = 0; i < level; ++i) { - ASSERT_OK(db_impl->TEST_CompactRange(i, nullptr, nullptr)); - } - } - - void CloseDB() { - if (db_ != nullptr) { - delete db_; - db_ = nullptr; - } - } - - bool ReduceLevels(int target_level); - - int FilesOnLevel(int level) { - std::string property; - EXPECT_TRUE(db_->GetProperty( - "rocksdb.num-files-at-level" + std::to_string(level), &property)); - return atoi(property.c_str()); - } - - private: - std::string dbname_; - DB* db_; -}; - -Status ReduceLevelTest::OpenDB(bool create_if_missing, int num_levels) { - ROCKSDB_NAMESPACE::Options opt; - opt.num_levels = num_levels; - opt.create_if_missing = create_if_missing; - ROCKSDB_NAMESPACE::Status st = - ROCKSDB_NAMESPACE::DB::Open(opt, dbname_, &db_); - if (!st.ok()) { - fprintf(stderr, "Can't open the db:%s\n", st.ToString().c_str()); - } - return st; -} - -bool ReduceLevelTest::ReduceLevels(int target_level) { - std::vector args = - ROCKSDB_NAMESPACE::ReduceDBLevelsCommand::PrepareArgs( - dbname_, target_level, false); - LDBCommand* level_reducer = LDBCommand::InitFromCmdLineArgs( - args, Options(), LDBOptions(), nullptr, LDBCommand::SelectCommand); - level_reducer->Run(); - bool is_succeed = level_reducer->GetExecuteState().IsSucceed(); - delete level_reducer; - return is_succeed; -} - -TEST_F(ReduceLevelTest, Last_Level) { - ASSERT_OK(OpenDB(true, 4)); - ASSERT_OK(Put("aaaa", "11111")); - ASSERT_OK(Flush()); - MoveL0FileToLevel(3); - ASSERT_EQ(FilesOnLevel(3), 1); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(3)); - ASSERT_OK(OpenDB(true, 3)); - ASSERT_EQ(FilesOnLevel(2), 1); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(2)); - ASSERT_OK(OpenDB(true, 2)); - ASSERT_EQ(FilesOnLevel(1), 1); - CloseDB(); -} - -TEST_F(ReduceLevelTest, Top_Level) { - ASSERT_OK(OpenDB(true, 5)); - ASSERT_OK(Put("aaaa", "11111")); - ASSERT_OK(Flush()); - ASSERT_EQ(FilesOnLevel(0), 1); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(4)); - ASSERT_OK(OpenDB(true, 4)); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(3)); - ASSERT_OK(OpenDB(true, 3)); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(2)); - ASSERT_OK(OpenDB(true, 2)); - CloseDB(); -} - -TEST_F(ReduceLevelTest, All_Levels) { - ASSERT_OK(OpenDB(true, 5)); - ASSERT_OK(Put("a", "a11111")); - ASSERT_OK(Flush()); - MoveL0FileToLevel(4); - ASSERT_EQ(FilesOnLevel(4), 1); - CloseDB(); - - ASSERT_OK(OpenDB(true, 5)); - ASSERT_OK(Put("b", "b11111")); - ASSERT_OK(Flush()); - MoveL0FileToLevel(3); - ASSERT_EQ(FilesOnLevel(3), 1); - ASSERT_EQ(FilesOnLevel(4), 1); - CloseDB(); - - ASSERT_OK(OpenDB(true, 5)); - ASSERT_OK(Put("c", "c11111")); - ASSERT_OK(Flush()); - MoveL0FileToLevel(2); - ASSERT_EQ(FilesOnLevel(2), 1); - ASSERT_EQ(FilesOnLevel(3), 1); - ASSERT_EQ(FilesOnLevel(4), 1); - CloseDB(); - - ASSERT_OK(OpenDB(true, 5)); - ASSERT_OK(Put("d", "d11111")); - ASSERT_OK(Flush()); - MoveL0FileToLevel(1); - ASSERT_EQ(FilesOnLevel(1), 1); - ASSERT_EQ(FilesOnLevel(2), 1); - ASSERT_EQ(FilesOnLevel(3), 1); - ASSERT_EQ(FilesOnLevel(4), 1); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(4)); - ASSERT_OK(OpenDB(true, 4)); - ASSERT_EQ("a11111", Get("a")); - ASSERT_EQ("b11111", Get("b")); - ASSERT_EQ("c11111", Get("c")); - ASSERT_EQ("d11111", Get("d")); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(3)); - ASSERT_OK(OpenDB(true, 3)); - ASSERT_EQ("a11111", Get("a")); - ASSERT_EQ("b11111", Get("b")); - ASSERT_EQ("c11111", Get("c")); - ASSERT_EQ("d11111", Get("d")); - CloseDB(); - - ASSERT_TRUE(ReduceLevels(2)); - ASSERT_OK(OpenDB(true, 2)); - ASSERT_EQ("a11111", Get("a")); - ASSERT_EQ("b11111", Get("b")); - ASSERT_EQ("c11111", Get("c")); - ASSERT_EQ("d11111", Get("d")); - CloseDB(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/tools/sst_dump_test.cc b/tools/sst_dump_test.cc deleted file mode 100644 index 29d11d4da..000000000 --- a/tools/sst_dump_test.cc +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2012 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include - -#include "file/random_access_file_reader.h" -#include "port/stack_trace.h" -#include "rocksdb/convenience.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/sst_dump_tool.h" -#include "table/block_based/block_based_table_factory.h" -#include "table/table_builder.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -const uint32_t kOptLength = 1024; - -namespace { -static std::string MakeKey(int i) { - char buf[100]; - snprintf(buf, sizeof(buf), "k_%04d", i); - InternalKey key(std::string(buf), 0, ValueType::kTypeValue); - return key.Encode().ToString(); -} - -static std::string MakeKeyWithTimeStamp(int i, uint64_t ts) { - char buf[100]; - snprintf(buf, sizeof(buf), "k_%04d", i); - return test::KeyStr(ts, std::string(buf), /*seq=*/0, kTypeValue); -} - -static std::string MakeValue(int i) { - char buf[100]; - snprintf(buf, sizeof(buf), "v_%04d", i); - InternalKey key(std::string(buf), 0, ValueType::kTypeValue); - return key.Encode().ToString(); -} - -void cleanup(const Options& opts, const std::string& file_name) { - Env* env = opts.env; - ASSERT_OK(env->DeleteFile(file_name)); - std::string outfile_name = file_name.substr(0, file_name.length() - 4); - outfile_name.append("_dump.txt"); - env->DeleteFile(outfile_name).PermitUncheckedError(); -} -} // namespace - -// Test for sst dump tool "raw" mode -class SSTDumpToolTest : public testing::Test { - std::string test_dir_; - Env* env_; - std::shared_ptr env_guard_; - - public: - SSTDumpToolTest() : env_(Env::Default()) { - EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env_, &env_guard_)); - test_dir_ = test::PerThreadDBPath(env_, "sst_dump_test_db"); - Status s = env_->CreateDirIfMissing(test_dir_); - EXPECT_OK(s); - } - - ~SSTDumpToolTest() override { - if (getenv("KEEP_DB")) { - fprintf(stdout, "Data is still at %s\n", test_dir_.c_str()); - } else { - EXPECT_OK(env_->DeleteDir(test_dir_)); - } - } - - Env* env() { return env_; } - - std::string MakeFilePath(const std::string& file_name) const { - std::string path(test_dir_); - path.append("/").append(file_name); - return path; - } - - template - void PopulateCommandArgs(const std::string& file_path, const char* command, - char* (&usage)[N]) const { - for (int i = 0; i < static_cast(N); ++i) { - usage[i] = new char[kOptLength]; - } - snprintf(usage[0], kOptLength, "./sst_dump"); - snprintf(usage[1], kOptLength, "%s", command); - snprintf(usage[2], kOptLength, "--file=%s", file_path.c_str()); - } - - void createSST(const Options& opts, const std::string& file_name) { - Env* test_env = opts.env; - FileOptions file_options(opts); - ReadOptions read_options; - const ImmutableOptions imoptions(opts); - const MutableCFOptions moptions(opts); - ROCKSDB_NAMESPACE::InternalKeyComparator ikc(opts.comparator); - std::unique_ptr tb; - - IntTblPropCollectorFactories int_tbl_prop_collector_factories; - std::unique_ptr file_writer; - ASSERT_OK(WritableFileWriter::Create(test_env->GetFileSystem(), file_name, - file_options, &file_writer, nullptr)); - - std::string column_family_name; - int unknown_level = -1; - tb.reset(opts.table_factory->NewTableBuilder( - TableBuilderOptions( - imoptions, moptions, ikc, &int_tbl_prop_collector_factories, - CompressionType::kNoCompression, CompressionOptions(), - TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, - column_family_name, unknown_level), - file_writer.get())); - - // Populate slightly more than 1K keys - uint32_t num_keys = kNumKey; - const char* comparator_name = ikc.user_comparator()->Name(); - if (strcmp(comparator_name, ReverseBytewiseComparator()->Name()) == 0) { - for (int32_t i = num_keys; i >= 0; i--) { - tb->Add(MakeKey(i), MakeValue(i)); - } - } else if (strcmp(comparator_name, - test::BytewiseComparatorWithU64TsWrapper()->Name()) == - 0) { - for (uint32_t i = 0; i < num_keys; i++) { - tb->Add(MakeKeyWithTimeStamp(i, 100 + i), MakeValue(i)); - } - } else { - for (uint32_t i = 0; i < num_keys; i++) { - tb->Add(MakeKey(i), MakeValue(i)); - } - } - ASSERT_OK(tb->Finish()); - ASSERT_OK(file_writer->Close()); - } - - protected: - constexpr static int kNumKey = 1024; -}; - -constexpr int SSTDumpToolTest::kNumKey; - -TEST_F(SSTDumpToolTest, HelpAndVersion) { - Options opts; - opts.env = env(); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - - static const char* help[] = {"./sst_dump", "--help"}; - ASSERT_TRUE(!tool.Run(2, help, opts)); - static const char* version[] = {"./sst_dump", "--version"}; - ASSERT_TRUE(!tool.Run(2, version, opts)); - static const char* bad[] = {"./sst_dump", "--not_an_option"}; - ASSERT_TRUE(tool.Run(2, bad, opts)); -} - -TEST_F(SSTDumpToolTest, EmptyFilter) { - Options opts; - opts.env = env(); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=raw", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, SstDumpReverseBytewiseComparator) { - Options opts; - opts.env = env(); - opts.comparator = ReverseBytewiseComparator(); - BlockBasedTableOptions table_opts; - table_opts.filter_policy.reset( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false)); - opts.table_factory.reset(new BlockBasedTableFactory(table_opts)); - std::string file_path = - MakeFilePath("rocksdb_sst_reverse_bytewise_comparator.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=raw", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, SstDumpComparatorWithU64Ts) { - Options opts; - opts.env = env(); - opts.comparator = test::BytewiseComparatorWithU64TsWrapper(); - BlockBasedTableOptions table_opts; - table_opts.filter_policy.reset( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false)); - opts.table_factory.reset(new BlockBasedTableFactory(table_opts)); - std::string file_path = - MakeFilePath("rocksdb_sst_comparator_with_u64_ts.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=raw", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, FilterBlock) { - Options opts; - opts.env = env(); - BlockBasedTableOptions table_opts; - table_opts.filter_policy.reset( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, true)); - opts.table_factory.reset(new BlockBasedTableFactory(table_opts)); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=raw", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, FullFilterBlock) { - Options opts; - opts.env = env(); - BlockBasedTableOptions table_opts; - table_opts.filter_policy.reset( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false)); - opts.table_factory.reset(new BlockBasedTableFactory(table_opts)); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=raw", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, GetProperties) { - Options opts; - opts.env = env(); - BlockBasedTableOptions table_opts; - table_opts.filter_policy.reset( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false)); - opts.table_factory.reset(new BlockBasedTableFactory(table_opts)); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--show_properties", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, CompressedSizes) { - Options opts; - opts.env = env(); - BlockBasedTableOptions table_opts; - table_opts.filter_policy.reset( - ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false)); - opts.table_factory.reset(new BlockBasedTableFactory(table_opts)); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=recompress", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, MemEnv) { - std::unique_ptr mem_env(NewMemEnv(env())); - Options opts; - opts.env = mem_env.get(); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=verify_checksum", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, ReadaheadSize) { - Options opts; - opts.env = env(); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[4]; - PopulateCommandArgs(file_path, "--command=verify", usage); - snprintf(usage[3], kOptLength, "--readahead_size=4000000"); - - int num_reads = 0; - SyncPoint::GetInstance()->SetCallBack("RandomAccessFileReader::Read", - [&](void*) { num_reads++; }); - SyncPoint::GetInstance()->EnableProcessing(); - - SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(4, usage, opts)); - - // The file is approximately 10MB. Readahead is 4MB. - // We usually need 3 reads + one metadata read. - // One extra read is needed before opening the file for metadata. - ASSERT_EQ(5, num_reads); - - SyncPoint::GetInstance()->ClearAllCallBacks(); - SyncPoint::GetInstance()->DisableProcessing(); - - cleanup(opts, file_path); - for (int i = 0; i < 4; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, NoSstFile) { - Options opts; - opts.env = env(); - std::string file_path = MakeFilePath("no_such_file.sst"); - char* usage[3]; - PopulateCommandArgs(file_path, "", usage); - ROCKSDB_NAMESPACE::SSTDumpTool tool; - for (const auto& command : - {"--command=check", "--command=dump", "--command=raw", - "--command=verify", "--command=recompress", "--command=verify_checksum", - "--show_properties"}) { - snprintf(usage[1], kOptLength, "%s", command); - ASSERT_TRUE(tool.Run(3, usage, opts)); - } - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, ValidSSTPath) { - Options opts; - opts.env = env(); - char* usage[3]; - PopulateCommandArgs("", "", usage); - SSTDumpTool tool; - std::string file_not_exists = MakeFilePath("file_not_exists.sst"); - std::string sst_file = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, sst_file); - std::string text_file = MakeFilePath("text_file"); - ASSERT_OK(WriteStringToFile(opts.env, "Hello World!", text_file)); - std::string fake_sst = MakeFilePath("fake_sst.sst"); - ASSERT_OK(WriteStringToFile(opts.env, "Not an SST file!", fake_sst)); - - for (const auto& command_arg : {"--command=verify", "--command=identify"}) { - snprintf(usage[1], kOptLength, "%s", command_arg); - - snprintf(usage[2], kOptLength, "--file=%s", file_not_exists.c_str()); - ASSERT_TRUE(tool.Run(3, usage, opts)); - - snprintf(usage[2], kOptLength, "--file=%s", sst_file.c_str()); - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - snprintf(usage[2], kOptLength, "--file=%s", text_file.c_str()); - ASSERT_TRUE(tool.Run(3, usage, opts)); - - snprintf(usage[2], kOptLength, "--file=%s", fake_sst.c_str()); - ASSERT_TRUE(tool.Run(3, usage, opts)); - } - ASSERT_OK(opts.env->DeleteFile(sst_file)); - ASSERT_OK(opts.env->DeleteFile(text_file)); - ASSERT_OK(opts.env->DeleteFile(fake_sst)); - - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -TEST_F(SSTDumpToolTest, RawOutput) { - Options opts; - opts.env = env(); - std::string file_path = MakeFilePath("rocksdb_sst_test.sst"); - createSST(opts, file_path); - - char* usage[3]; - PopulateCommandArgs(file_path, "--command=raw", usage); - - ROCKSDB_NAMESPACE::SSTDumpTool tool; - ASSERT_TRUE(!tool.Run(3, usage, opts)); - - const std::string raw_path = MakeFilePath("rocksdb_sst_test_dump.txt"); - std::ifstream raw_file(raw_path); - - std::string tp; - bool is_data_block = false; - int key_count = 0; - while (getline(raw_file, tp)) { - if (tp.find("Data Block #") != std::string::npos) { - is_data_block = true; - } - - if (is_data_block && tp.find("HEX") != std::string::npos) { - key_count++; - } - } - - ASSERT_EQ(kNumKey, key_count); - - raw_file.close(); - - cleanup(opts, file_path); - for (int i = 0; i < 3; i++) { - delete[] usage[i]; - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - RegisterCustomObjects(argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/tools/trace_analyzer_test.cc b/tools/trace_analyzer_test.cc deleted file mode 100644 index 81dc4f2cc..000000000 --- a/tools/trace_analyzer_test.cc +++ /dev/null @@ -1,880 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2012 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifndef GFLAGS -#include -int main() { - fprintf(stderr, "Please install gflags to run trace_analyzer test\n"); - return 0; -} -#else - -#include -#include -#include -#include -#include - -#include "db/db_test_util.h" -#include "file/line_file_reader.h" -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/status.h" -#include "rocksdb/trace_reader_writer.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "tools/trace_analyzer_tool.h" -#include "trace_replay/trace_replay.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -static const int kMaxArgCount = 100; -static const size_t kArgBufferSize = 100000; -} // namespace - -// Note that, the QPS part verification of the analyzing result is not robost -// enough and causes the failure in some rare cases. Disable them temporally and -// wait for future refactor. - -// The helper functions for the test -class TraceAnalyzerTest : public testing::Test { - public: - TraceAnalyzerTest() : rnd_(0xFB) { - // test_path_ = test::TmpDir() + "trace_analyzer_test"; - test_path_ = test::PerThreadDBPath("trace_analyzer_test"); - env_ = ROCKSDB_NAMESPACE::Env::Default(); - env_->CreateDir(test_path_).PermitUncheckedError(); - dbname_ = test_path_ + "/db"; - } - - ~TraceAnalyzerTest() override {} - - void GenerateTrace(std::string trace_path) { - Options options; - options.create_if_missing = true; - options.merge_operator = MergeOperators::CreatePutOperator(); - Slice upper_bound("a"); - Slice lower_bound("abce"); - ReadOptions ro; - ro.iterate_upper_bound = &upper_bound; - ro.iterate_lower_bound = &lower_bound; - WriteOptions wo; - TraceOptions trace_opt; - DB* db_ = nullptr; - std::string value; - std::unique_ptr trace_writer; - Iterator* single_iter = nullptr; - - ASSERT_OK( - NewFileTraceWriter(env_, env_options_, trace_path, &trace_writer)); - ASSERT_OK(DB::Open(options, dbname_, &db_)); - ASSERT_OK(db_->StartTrace(trace_opt, std::move(trace_writer))); - - WriteBatch batch; - ASSERT_OK(batch.Put("a", "aaaaaaaaa")); - ASSERT_OK(batch.Merge("b", "aaaaaaaaaaaaaaaaaaaa")); - ASSERT_OK(batch.Delete("c")); - ASSERT_OK(batch.SingleDelete("d")); - ASSERT_OK(batch.DeleteRange("e", "f")); - ASSERT_OK(db_->Write(wo, &batch)); - std::vector keys; - keys.push_back("a"); - keys.push_back("b"); - keys.push_back("df"); - keys.push_back("gege"); - keys.push_back("hjhjhj"); - std::vector values; - std::vector ss = db_->MultiGet(ro, keys, &values); - ASSERT_GE(ss.size(), 0); - ASSERT_OK(ss[0]); - ASSERT_NOK(ss[2]); - std::vector cfs(2, db_->DefaultColumnFamily()); - std::vector values2(keys.size()); - db_->MultiGet(ro, 2, cfs.data(), keys.data(), values2.data(), ss.data(), - false); - ASSERT_OK(ss[0]); - db_->MultiGet(ro, db_->DefaultColumnFamily(), 2, keys.data() + 3, - values2.data(), ss.data(), false); - ASSERT_OK(db_->Get(ro, "a", &value)); - - single_iter = db_->NewIterator(ro); - single_iter->Seek("a"); - ASSERT_OK(single_iter->status()); - single_iter->SeekForPrev("b"); - ASSERT_OK(single_iter->status()); - delete single_iter; - std::this_thread::sleep_for(std::chrono::seconds(1)); - - db_->Get(ro, "g", &value).PermitUncheckedError(); - - ASSERT_OK(db_->EndTrace()); - - ASSERT_OK(env_->FileExists(trace_path)); - - std::unique_ptr whole_f; - std::string whole_path = test_path_ + "/0.txt"; - ASSERT_OK(env_->NewWritableFile(whole_path, &whole_f, env_options_)); - std::string whole_str = "0x61\n0x62\n0x63\n0x64\n0x65\n0x66\n"; - ASSERT_OK(whole_f->Append(whole_str)); - delete db_; - ASSERT_OK(DestroyDB(dbname_, options)); - } - - void RunTraceAnalyzer(const std::vector& args) { - char arg_buffer[kArgBufferSize]; - char* argv[kMaxArgCount]; - int argc = 0; - int cursor = 0; - - for (const auto& arg : args) { - ASSERT_LE(cursor + arg.size() + 1, kArgBufferSize); - ASSERT_LE(argc + 1, kMaxArgCount); - snprintf(arg_buffer + cursor, arg.size() + 1, "%s", arg.c_str()); - - argv[argc++] = arg_buffer + cursor; - cursor += static_cast(arg.size()) + 1; - } - - ASSERT_EQ(0, ROCKSDB_NAMESPACE::trace_analyzer_tool(argc, argv)); - } - - void CheckFileContent(const std::vector& cnt, - std::string file_path, bool full_content) { - const auto& fs = env_->GetFileSystem(); - FileOptions fopts(env_options_); - - ASSERT_OK(fs->FileExists(file_path, fopts.io_options, nullptr)); - std::unique_ptr file; - ASSERT_OK(fs->NewSequentialFile(file_path, fopts, &file, nullptr)); - - LineFileReader lf_reader(std::move(file), file_path, - 4096 /* filereadahead_size */); - - std::vector result; - std::string line; - while ( - lf_reader.ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)) { - result.push_back(line); - } - - ASSERT_OK(lf_reader.GetStatus()); - - size_t min_size = std::min(cnt.size(), result.size()); - for (size_t i = 0; i < min_size; i++) { - if (full_content) { - ASSERT_EQ(result[i], cnt[i]); - } else { - ASSERT_EQ(result[i][0], cnt[i][0]); - } - } - - return; - } - - void AnalyzeTrace(std::vector& paras_diff, - std::string output_path, std::string trace_path) { - std::vector paras = {"./trace_analyzer", - "-convert_to_human_readable_trace", - "-output_key_stats", - "-output_access_count_stats", - "-output_prefix=test", - "-output_prefix_cut=1", - "-output_time_series", - "-output_value_distribution", - "-output_qps_stats", - "-no_key", - "-no_print"}; - for (auto& para : paras_diff) { - paras.push_back(para); - } - Status s = env_->FileExists(trace_path); - if (!s.ok()) { - GenerateTrace(trace_path); - } - ASSERT_OK(env_->CreateDir(output_path)); - RunTraceAnalyzer(paras); - } - - ROCKSDB_NAMESPACE::Env* env_; - EnvOptions env_options_; - std::string test_path_; - std::string dbname_; - Random rnd_; -}; - -TEST_F(TraceAnalyzerTest, Get) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/get"; - std::string file_path; - std::vector paras = { - "-analyze_get=true", "-analyze_put=false", - "-analyze_delete=false", "-analyze_single_delete=false", - "-analyze_range_delete=false", "-analyze_iterator=false", - "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 10 0 1 1.000000", "0 10 1 1 1.000000"}; - file_path = output_path + "/test-get-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 2"}; - file_path = output_path + "/test-get-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30", - "1 1 1 1.000000 1.000000 0x61"}; - file_path = output_path + "/test-get-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"0 1533000630 0", "0 1533000630 1"}; - file_path = output_path + "/test-get-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"0 1"}; - file_path = output_path + "/test-get-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-get-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps - std::vector all_qps = {"1 0 0 0 0 0 0 0 0 1"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of get - std::vector get_qps = {"1"}; - file_path = output_path + "/test-get-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 1", - "The prefix: 0x61 Access count: 1"}; - file_path = output_path + "/test-get-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ -} - -// Test analyzing of Put -TEST_F(TraceAnalyzerTest, Put) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/put"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=true", - "-analyze_delete=false", "-analyze_single_delete=false", - "-analyze_range_delete=false", "-analyze_iterator=false", - "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 9 0 1 1.000000"}; - file_path = output_path + "/test-put-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 1"}; - file_path = output_path + "/test-put-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30"}; - file_path = output_path + "/test-put-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"1 1533056278 0"}; - file_path = output_path + "/test-put-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"0 1"}; - file_path = output_path + "/test-put-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-put-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - // Check the overall qps - std::vector all_qps = {"0 1 0 0 0 0 0 0 0 0 1"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - /* - // Check the qps of Put - std::vector get_qps = {"1"}; - file_path = output_path + "/test-put-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 1", - "The prefix: 0x61 Access count: 1"}; - file_path = output_path + "/test-put-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - - // Check the value size distribution - std::vector value_dist = { - "Number_of_value_size_between 0 and 16 is: 1"}; - file_path = output_path + "/test-put-0-accessed_value_size_distribution.txt"; - CheckFileContent(value_dist, file_path, true); - */ -} - -// Test analyzing of delete -TEST_F(TraceAnalyzerTest, Delete) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/delete"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=false", - "-analyze_delete=true", "-analyze_single_delete=false", - "-analyze_range_delete=false", "-analyze_iterator=false", - "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 10 0 1 1.000000"}; - file_path = output_path + "/test-delete-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 1"}; - file_path = - output_path + "/test-delete-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30"}; - file_path = output_path + "/test-delete-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"2 1533000630 0"}; - file_path = output_path + "/test-delete-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"2 1"}; - file_path = output_path + "/test-delete-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-delete-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps - std::vector all_qps = {"0 0 1 0 0 0 0 0 0 1"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of Delete - std::vector get_qps = {"1"}; - file_path = output_path + "/test-delete-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 1", - "The prefix: 0x63 Access count: 1"}; - file_path = output_path + "/test-delete-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ -} - -// Test analyzing of Merge -TEST_F(TraceAnalyzerTest, Merge) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/merge"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=false", - "-analyze_delete=false", "-analyze_merge=true", - "-analyze_single_delete=false", "-analyze_range_delete=false", - "-analyze_iterator=false", "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 20 0 1 1.000000"}; - file_path = output_path + "/test-merge-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 1"}; - file_path = output_path + "/test-merge-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30"}; - file_path = output_path + "/test-merge-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"5 1533000630 0"}; - file_path = output_path + "/test-merge-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"1 1"}; - file_path = output_path + "/test-merge-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-merge-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps - std::vector all_qps = {"0 0 0 0 0 1 0 0 0 1"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of Merge - std::vector get_qps = {"1"}; - file_path = output_path + "/test-merge-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 1", - "The prefix: 0x62 Access count: 1"}; - file_path = output_path + "/test-merge-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ - - // Check the value size distribution - std::vector value_dist = { - "Number_of_value_size_between 0 and 24 is: 1"}; - file_path = - output_path + "/test-merge-0-accessed_value_size_distribution.txt"; - CheckFileContent(value_dist, file_path, true); -} - -// Test analyzing of SingleDelete -TEST_F(TraceAnalyzerTest, SingleDelete) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/single_delete"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=false", - "-analyze_delete=false", "-analyze_merge=false", - "-analyze_single_delete=true", "-analyze_range_delete=false", - "-analyze_iterator=false", "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 10 0 1 1.000000"}; - file_path = output_path + "/test-single_delete-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 1"}; - file_path = - output_path + "/test-single_delete-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30"}; - file_path = output_path + "/test-single_delete-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"3 1533000630 0"}; - file_path = output_path + "/test-single_delete-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"3 1"}; - file_path = output_path + "/test-single_delete-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-single_delete-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps - std::vector all_qps = {"0 0 0 1 0 0 0 0 0 1"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of SingleDelete - std::vector get_qps = {"1"}; - file_path = output_path + "/test-single_delete-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 1", - "The prefix: 0x64 Access count: 1"}; - file_path = - output_path + "/test-single_delete-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ -} - -// Test analyzing of delete -TEST_F(TraceAnalyzerTest, DeleteRange) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/range_delete"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=false", - "-analyze_delete=false", "-analyze_merge=false", - "-analyze_single_delete=false", "-analyze_range_delete=true", - "-analyze_iterator=false", "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 10 0 1 1.000000", "0 10 1 1 1.000000"}; - file_path = output_path + "/test-range_delete-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 2"}; - file_path = - output_path + "/test-range_delete-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30", - "1 1 1 1.000000 1.000000 0x65"}; - file_path = output_path + "/test-range_delete-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"4 1533000630 0", "4 1533060100 1"}; - file_path = output_path + "/test-range_delete-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"4 1", "5 1"}; - file_path = output_path + "/test-range_delete-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-range_delete-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps - std::vector all_qps = {"0 0 0 0 2 0 0 0 0 2"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of DeleteRange - std::vector get_qps = {"2"}; - file_path = output_path + "/test-range_delete-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 2", - "The prefix: 0x65 Access count: 1", - "The prefix: 0x66 Access count: 1"}; - file_path = - output_path + "/test-range_delete-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ -} - -// Test analyzing of Iterator -TEST_F(TraceAnalyzerTest, Iterator) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/iterator"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=false", - "-analyze_delete=false", "-analyze_merge=false", - "-analyze_single_delete=false", "-analyze_range_delete=false", - "-analyze_iterator=true", "-analyze_multiget=false"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // Check the output of Seek - // check the key_stats file - std::vector k_stats = {"0 10 0 1 1.000000"}; - file_path = output_path + "/test-iterator_Seek-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 1"}; - file_path = - output_path + "/test-iterator_Seek-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = {"0 0 0 0.000000 0.000000 0x30"}; - file_path = output_path + "/test-iterator_Seek-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"6 1 0"}; - file_path = output_path + "/test-iterator_Seek-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"0 1"}; - file_path = output_path + "/test-iterator_Seek-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-iterator_Seek-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps - std::vector all_qps = {"0 0 0 0 0 0 1 1 0 2"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of Iterator_Seek - std::vector get_qps = {"1"}; - file_path = output_path + "/test-iterator_Seek-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = {"At time: 0 with QPS: 1", - "The prefix: 0x61 Access count: 1"}; - file_path = - output_path + "/test-iterator_Seek-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ - - // Check the output of SeekForPrev - // check the key_stats file - k_stats = {"0 10 0 1 1.000000"}; - file_path = - output_path + "/test-iterator_SeekForPrev-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - k_dist = {"access_count: 1 num: 1"}; - file_path = - output_path + - "/test-iterator_SeekForPrev-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the prefix - k_prefix = {"0 0 0 0.000000 0.000000 0x30"}; - file_path = - output_path + "/test-iterator_SeekForPrev-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - k_series = {"7 0 0"}; - file_path = output_path + "/test-iterator_SeekForPrev-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - k_whole_access = {"1 1"}; - file_path = output_path + "/test-iterator_SeekForPrev-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", "3 0x64", "4 0x65", "5 0x66"}; - file_path = - output_path + "/test-iterator_SeekForPrev-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the qps of Iterator_SeekForPrev - get_qps = {"1"}; - file_path = output_path + "/test-iterator_SeekForPrev-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - top_qps = {"At time: 0 with QPS: 1", "The prefix: 0x62 Access count: 1"}; - file_path = output_path + - "/test-iterator_SeekForPrev-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ -} - -// Test analyzing of multiget -TEST_F(TraceAnalyzerTest, MultiGet) { - std::string trace_path = test_path_ + "/trace"; - std::string output_path = test_path_ + "/multiget"; - std::string file_path; - std::vector paras = { - "-analyze_get=false", "-analyze_put=false", - "-analyze_delete=false", "-analyze_merge=false", - "-analyze_single_delete=false", "-analyze_range_delete=true", - "-analyze_iterator=false", "-analyze_multiget=true"}; - paras.push_back("-output_dir=" + output_path); - paras.push_back("-trace_path=" + trace_path); - paras.push_back("-key_space_dir=" + test_path_); - AnalyzeTrace(paras, output_path, trace_path); - - // check the key_stats file - std::vector k_stats = {"0 10 0 2 1.000000", "0 10 1 2 1.000000", - "0 10 2 1 1.000000", "0 10 3 2 1.000000", - "0 10 4 2 1.000000"}; - file_path = output_path + "/test-multiget-0-accessed_key_stats.txt"; - CheckFileContent(k_stats, file_path, true); - - // Check the access count distribution - std::vector k_dist = {"access_count: 1 num: 1", - "access_count: 2 num: 4"}; - file_path = - output_path + "/test-multiget-0-accessed_key_count_distribution.txt"; - CheckFileContent(k_dist, file_path, true); - - // Check the trace sequence - std::vector k_sequence = {"1", "5", "2", "3", "4", "8", - "8", "8", "8", "8", "8", "8", - "8", "8", "0", "6", "7", "0"}; - file_path = output_path + "/test-human_readable_trace.txt"; - CheckFileContent(k_sequence, file_path, false); - - // Check the prefix - std::vector k_prefix = { - "0 0 0 0.000000 0.000000 0x30", "1 2 1 2.000000 1.000000 0x61", - "2 2 1 2.000000 1.000000 0x62", "3 1 1 1.000000 1.000000 0x64", - "4 2 1 2.000000 1.000000 0x67"}; - file_path = output_path + "/test-multiget-0-accessed_key_prefix_cut.txt"; - CheckFileContent(k_prefix, file_path, true); - - // Check the time series - std::vector k_series = {"8 0 0", "8 0 1", "8 0 2", - "8 0 3", "8 0 4", "8 0 0", - "8 0 1", "8 0 3", "8 0 4"}; - file_path = output_path + "/test-multiget-0-time_series.txt"; - CheckFileContent(k_series, file_path, false); - - // Check the accessed key in whole key space - std::vector k_whole_access = {"0 2", "1 2"}; - file_path = output_path + "/test-multiget-0-whole_key_stats.txt"; - CheckFileContent(k_whole_access, file_path, true); - - // Check the whole key prefix cut - std::vector k_whole_prefix = {"0 0x61", "1 0x62", "2 0x63", - "3 0x64", "4 0x65", "5 0x66"}; - file_path = output_path + "/test-multiget-0-whole_key_prefix_cut.txt"; - CheckFileContent(k_whole_prefix, file_path, true); - - /* - // Check the overall qps. We have 3 MultiGet queries and it requested 9 keys - // in total - std::vector all_qps = {"0 0 0 0 2 0 0 0 9 11"}; - file_path = output_path + "/test-qps_stats.txt"; - CheckFileContent(all_qps, file_path, true); - - // Check the qps of DeleteRange - std::vector get_qps = {"9"}; - file_path = output_path + "/test-multiget-0-qps_stats.txt"; - CheckFileContent(get_qps, file_path, true); - - // Check the top k qps prefix cut - std::vector top_qps = { - "At time: 0 with QPS: 9", "The prefix: 0x61 Access count: 2", - "The prefix: 0x62 Access count: 2", "The prefix: 0x64 Access count: 1", - "The prefix: 0x67 Access count: 2", "The prefix: 0x68 Access count: 2"}; - file_path = - output_path + "/test-multiget-0-accessed_top_k_qps_prefix_cut.txt"; - CheckFileContent(top_qps, file_path, true); - */ -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} -#endif // GFLAG diff --git a/trace_replay/block_cache_tracer_test.cc b/trace_replay/block_cache_tracer_test.cc deleted file mode 100644 index f9d0773bf..000000000 --- a/trace_replay/block_cache_tracer_test.cc +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "trace_replay/block_cache_tracer.h" - -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/status.h" -#include "rocksdb/trace_reader_writer.h" -#include "rocksdb/trace_record.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -const uint64_t kBlockSize = 1024; -const std::string kBlockKeyPrefix = "test-block-"; -const uint32_t kCFId = 0; -const uint32_t kLevel = 1; -const uint64_t kSSTFDNumber = 100; -const std::string kRefKeyPrefix = "test-get-"; -const uint64_t kNumKeysInBlock = 1024; -const uint64_t kReferencedDataSize = 10; -} // namespace - -class BlockCacheTracerTest : public testing::Test { - public: - BlockCacheTracerTest() { - test_path_ = test::PerThreadDBPath("block_cache_tracer_test"); - env_ = ROCKSDB_NAMESPACE::Env::Default(); - clock_ = env_->GetSystemClock().get(); - EXPECT_OK(env_->CreateDir(test_path_)); - trace_file_path_ = test_path_ + "/block_cache_trace"; - } - - ~BlockCacheTracerTest() override { - EXPECT_OK(env_->DeleteFile(trace_file_path_)); - EXPECT_OK(env_->DeleteDir(test_path_)); - } - - TableReaderCaller GetCaller(uint32_t key_id) { - uint32_t n = key_id % 5; - switch (n) { - case 0: - return TableReaderCaller::kPrefetch; - case 1: - return TableReaderCaller::kCompaction; - case 2: - return TableReaderCaller::kUserGet; - case 3: - return TableReaderCaller::kUserMultiGet; - case 4: - return TableReaderCaller::kUserIterator; - } - assert(false); - return TableReaderCaller::kMaxBlockCacheLookupCaller; - } - - void WriteBlockAccess(BlockCacheTraceWriter* writer, uint32_t from_key_id, - TraceType block_type, uint32_t nblocks) { - assert(writer); - for (uint32_t i = 0; i < nblocks; i++) { - uint32_t key_id = from_key_id + i; - BlockCacheTraceRecord record; - record.block_type = block_type; - record.block_size = kBlockSize + key_id; - record.block_key = (kBlockKeyPrefix + std::to_string(key_id)); - record.access_timestamp = clock_->NowMicros(); - record.cf_id = kCFId; - record.cf_name = kDefaultColumnFamilyName; - record.caller = GetCaller(key_id); - record.level = kLevel; - record.sst_fd_number = kSSTFDNumber + key_id; - record.is_cache_hit = false; - record.no_insert = false; - // Provide get_id for all callers. The writer should only write get_id - // when the caller is either GET or MGET. - record.get_id = key_id + 1; - record.get_from_user_specified_snapshot = true; - // Provide these fields for all block types. - // The writer should only write these fields for data blocks and the - // caller is either GET or MGET. - record.referenced_key = (kRefKeyPrefix + std::to_string(key_id)); - record.referenced_key_exist_in_block = true; - record.num_keys_in_block = kNumKeysInBlock; - record.referenced_data_size = kReferencedDataSize + key_id; - ASSERT_OK(writer->WriteBlockAccess( - record, record.block_key, record.cf_name, record.referenced_key)); - } - } - - BlockCacheTraceRecord GenerateAccessRecord() { - uint32_t key_id = 0; - BlockCacheTraceRecord record; - record.block_type = TraceType::kBlockTraceDataBlock; - record.block_size = kBlockSize; - record.block_key = kBlockKeyPrefix + std::to_string(key_id); - record.access_timestamp = clock_->NowMicros(); - record.cf_id = kCFId; - record.cf_name = kDefaultColumnFamilyName; - record.caller = GetCaller(key_id); - record.level = kLevel; - record.sst_fd_number = kSSTFDNumber + key_id; - record.is_cache_hit = false; - record.no_insert = false; - record.referenced_key = kRefKeyPrefix + std::to_string(key_id); - record.referenced_key_exist_in_block = true; - record.num_keys_in_block = kNumKeysInBlock; - return record; - } - - void VerifyAccess(BlockCacheTraceReader* reader, uint32_t from_key_id, - TraceType block_type, uint32_t nblocks) { - assert(reader); - for (uint32_t i = 0; i < nblocks; i++) { - uint32_t key_id = from_key_id + i; - BlockCacheTraceRecord record; - ASSERT_OK(reader->ReadAccess(&record)); - ASSERT_EQ(block_type, record.block_type); - ASSERT_EQ(kBlockSize + key_id, record.block_size); - ASSERT_EQ(kBlockKeyPrefix + std::to_string(key_id), record.block_key); - ASSERT_EQ(kCFId, record.cf_id); - ASSERT_EQ(kDefaultColumnFamilyName, record.cf_name); - ASSERT_EQ(GetCaller(key_id), record.caller); - ASSERT_EQ(kLevel, record.level); - ASSERT_EQ(kSSTFDNumber + key_id, record.sst_fd_number); - ASSERT_FALSE(record.is_cache_hit); - ASSERT_FALSE(record.no_insert); - if (record.caller == TableReaderCaller::kUserGet || - record.caller == TableReaderCaller::kUserMultiGet) { - ASSERT_EQ(key_id + 1, record.get_id); - ASSERT_TRUE(record.get_from_user_specified_snapshot); - ASSERT_EQ(kRefKeyPrefix + std::to_string(key_id), - record.referenced_key); - } else { - ASSERT_EQ(BlockCacheTraceHelper::kReservedGetId, record.get_id); - ASSERT_FALSE(record.get_from_user_specified_snapshot); - ASSERT_EQ("", record.referenced_key); - } - if (block_type == TraceType::kBlockTraceDataBlock && - (record.caller == TableReaderCaller::kUserGet || - record.caller == TableReaderCaller::kUserMultiGet)) { - ASSERT_TRUE(record.referenced_key_exist_in_block); - ASSERT_EQ(kNumKeysInBlock, record.num_keys_in_block); - ASSERT_EQ(kReferencedDataSize + key_id, record.referenced_data_size); - continue; - } - ASSERT_FALSE(record.referenced_key_exist_in_block); - ASSERT_EQ(0, record.num_keys_in_block); - ASSERT_EQ(0, record.referenced_data_size); - } - } - - Env* env_; - SystemClock* clock_; - EnvOptions env_options_; - std::string trace_file_path_; - std::string test_path_; -}; - -TEST_F(BlockCacheTracerTest, AtomicWriteBeforeStartTrace) { - BlockCacheTraceRecord record = GenerateAccessRecord(); - { - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - BlockCacheTracer writer; - // The record should be written to the trace_file since StartTrace is not - // called. - ASSERT_OK(writer.WriteBlockAccess(record, record.block_key, record.cf_name, - record.referenced_key)); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file contains nothing. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - BlockCacheTraceReader reader(std::move(trace_reader)); - BlockCacheTraceHeader header; - ASSERT_NOK(reader.ReadHeader(&header)); - } -} - -TEST_F(BlockCacheTracerTest, AtomicWrite) { - BlockCacheTraceRecord record = GenerateAccessRecord(); - { - BlockCacheTraceWriterOptions trace_writer_opt; - BlockCacheTraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - BlockCacheTracer writer; - ASSERT_OK( - writer.StartTrace(trace_opt, std::move(block_cache_trace_writer))); - ASSERT_OK(writer.WriteBlockAccess(record, record.block_key, record.cf_name, - record.referenced_key)); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file contains one record. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - BlockCacheTraceReader reader(std::move(trace_reader)); - BlockCacheTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - VerifyAccess(&reader, 0, TraceType::kBlockTraceDataBlock, 1); - ASSERT_NOK(reader.ReadAccess(&record)); - } -} - -TEST_F(BlockCacheTracerTest, ConsecutiveStartTrace) { - BlockCacheTraceWriterOptions trace_writer_opt; - BlockCacheTraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK( - NewFileTraceWriter(env_, env_options_, trace_file_path_, &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - BlockCacheTracer writer; - ASSERT_OK(writer.StartTrace(trace_opt, std::move(block_cache_trace_writer))); - ASSERT_NOK(writer.StartTrace(trace_opt, std::move(block_cache_trace_writer))); - ASSERT_OK(env_->FileExists(trace_file_path_)); -} - -TEST_F(BlockCacheTracerTest, AtomicNoWriteAfterEndTrace) { - BlockCacheTraceRecord record = GenerateAccessRecord(); - { - BlockCacheTraceWriterOptions trace_writer_opt; - BlockCacheTraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - BlockCacheTracer writer; - ASSERT_OK( - writer.StartTrace(trace_opt, std::move(block_cache_trace_writer))); - ASSERT_OK(writer.WriteBlockAccess(record, record.block_key, record.cf_name, - record.referenced_key)); - writer.EndTrace(); - // Write the record again. This time the record should not be written since - // EndTrace is called. - ASSERT_OK(writer.WriteBlockAccess(record, record.block_key, record.cf_name, - record.referenced_key)); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file contains one record. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - BlockCacheTraceReader reader(std::move(trace_reader)); - BlockCacheTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - VerifyAccess(&reader, 0, TraceType::kBlockTraceDataBlock, 1); - ASSERT_NOK(reader.ReadAccess(&record)); - } -} - -TEST_F(BlockCacheTracerTest, NextGetId) { - BlockCacheTracer writer; - { - BlockCacheTraceWriterOptions trace_writer_opt; - BlockCacheTraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - // next get id should always return 0 before we call StartTrace. - ASSERT_EQ(0, writer.NextGetId()); - ASSERT_EQ(0, writer.NextGetId()); - ASSERT_OK( - writer.StartTrace(trace_opt, std::move(block_cache_trace_writer))); - ASSERT_EQ(1, writer.NextGetId()); - ASSERT_EQ(2, writer.NextGetId()); - writer.EndTrace(); - // next get id should return 0. - ASSERT_EQ(0, writer.NextGetId()); - } - - // Start trace again and next get id should return 1. - { - BlockCacheTraceWriterOptions trace_writer_opt; - BlockCacheTraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - ASSERT_OK( - writer.StartTrace(trace_opt, std::move(block_cache_trace_writer))); - ASSERT_EQ(1, writer.NextGetId()); - } -} - -TEST_F(BlockCacheTracerTest, MixedBlocks) { - { - // Generate a trace file containing a mix of blocks. - BlockCacheTraceWriterOptions trace_writer_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - std::unique_ptr block_cache_trace_writer = - NewBlockCacheTraceWriter(env_->GetSystemClock().get(), trace_writer_opt, - std::move(trace_writer)); - ASSERT_NE(block_cache_trace_writer, nullptr); - ASSERT_OK(block_cache_trace_writer->WriteHeader()); - // Write blocks of different types. - WriteBlockAccess(block_cache_trace_writer.get(), 0, - TraceType::kBlockTraceUncompressionDictBlock, 10); - WriteBlockAccess(block_cache_trace_writer.get(), 10, - TraceType::kBlockTraceDataBlock, 10); - WriteBlockAccess(block_cache_trace_writer.get(), 20, - TraceType::kBlockTraceFilterBlock, 10); - WriteBlockAccess(block_cache_trace_writer.get(), 30, - TraceType::kBlockTraceIndexBlock, 10); - WriteBlockAccess(block_cache_trace_writer.get(), 40, - TraceType::kBlockTraceRangeDeletionBlock, 10); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - - { - // Verify trace file is generated correctly. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - BlockCacheTraceReader reader(std::move(trace_reader)); - BlockCacheTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - // Read blocks. - VerifyAccess(&reader, 0, TraceType::kBlockTraceUncompressionDictBlock, 10); - VerifyAccess(&reader, 10, TraceType::kBlockTraceDataBlock, 10); - VerifyAccess(&reader, 20, TraceType::kBlockTraceFilterBlock, 10); - VerifyAccess(&reader, 30, TraceType::kBlockTraceIndexBlock, 10); - VerifyAccess(&reader, 40, TraceType::kBlockTraceRangeDeletionBlock, 10); - // Read one more record should report an error. - BlockCacheTraceRecord record; - ASSERT_NOK(reader.ReadAccess(&record)); - } -} - -TEST_F(BlockCacheTracerTest, HumanReadableTrace) { - BlockCacheTraceRecord record = GenerateAccessRecord(); - record.get_id = 1; - record.referenced_key = ""; - record.caller = TableReaderCaller::kUserGet; - record.get_from_user_specified_snapshot = true; - record.referenced_data_size = kReferencedDataSize; - PutFixed32(&record.referenced_key, 111); - PutLengthPrefixedSlice(&record.referenced_key, "get_key"); - PutFixed64(&record.referenced_key, 2 << 8); - PutLengthPrefixedSlice(&record.block_key, "block_key"); - PutVarint64(&record.block_key, 333); - { - // Generate a human readable trace file. - BlockCacheHumanReadableTraceWriter writer; - ASSERT_OK(writer.NewWritableFile(trace_file_path_, env_)); - ASSERT_OK(writer.WriteHumanReadableTraceRecord(record, 1, 1)); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - BlockCacheHumanReadableTraceReader reader(trace_file_path_); - BlockCacheTraceHeader header; - BlockCacheTraceRecord read_record; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_OK(reader.ReadAccess(&read_record)); - ASSERT_EQ(TraceType::kBlockTraceDataBlock, read_record.block_type); - ASSERT_EQ(kBlockSize, read_record.block_size); - ASSERT_EQ(kCFId, read_record.cf_id); - ASSERT_EQ(kDefaultColumnFamilyName, read_record.cf_name); - ASSERT_EQ(TableReaderCaller::kUserGet, read_record.caller); - ASSERT_EQ(kLevel, read_record.level); - ASSERT_EQ(kSSTFDNumber, read_record.sst_fd_number); - ASSERT_FALSE(read_record.is_cache_hit); - ASSERT_FALSE(read_record.no_insert); - ASSERT_EQ(1, read_record.get_id); - ASSERT_TRUE(read_record.get_from_user_specified_snapshot); - ASSERT_TRUE(read_record.referenced_key_exist_in_block); - ASSERT_EQ(kNumKeysInBlock, read_record.num_keys_in_block); - ASSERT_EQ(kReferencedDataSize, read_record.referenced_data_size); - ASSERT_EQ(record.block_key.size(), read_record.block_key.size()); - ASSERT_EQ(record.referenced_key.size(), record.referenced_key.size()); - ASSERT_EQ(112, BlockCacheTraceHelper::GetTableId(read_record)); - ASSERT_EQ(3, BlockCacheTraceHelper::GetSequenceNumber(read_record)); - ASSERT_EQ(333, BlockCacheTraceHelper::GetBlockOffsetInFile(read_record)); - // Read again should fail. - ASSERT_NOK(reader.ReadAccess(&read_record)); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/trace_replay/io_tracer_test.cc b/trace_replay/io_tracer_test.cc deleted file mode 100644 index be3af4fb3..000000000 --- a/trace_replay/io_tracer_test.cc +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "trace_replay/io_tracer.h" - -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/status.h" -#include "rocksdb/trace_reader_writer.h" -#include "rocksdb/trace_record.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -namespace { -const std::string kDummyFile = "/dummy/file"; - -} // namespace - -class IOTracerTest : public testing::Test { - public: - IOTracerTest() { - test_path_ = test::PerThreadDBPath("io_tracer_test"); - env_ = ROCKSDB_NAMESPACE::Env::Default(); - clock_ = env_->GetSystemClock().get(); - EXPECT_OK(env_->CreateDir(test_path_)); - trace_file_path_ = test_path_ + "/io_trace"; - } - - ~IOTracerTest() override { - EXPECT_OK(env_->DeleteFile(trace_file_path_)); - EXPECT_OK(env_->DeleteDir(test_path_)); - } - - std::string GetFileOperation(uint64_t id) { - id = id % 4; - switch (id) { - case 0: - return "CreateDir"; - case 1: - return "GetChildren"; - case 2: - return "FileSize"; - case 3: - return "DeleteDir"; - default: - assert(false); - } - return ""; - } - - void WriteIOOp(IOTraceWriter* writer, uint64_t nrecords) { - assert(writer); - for (uint64_t i = 0; i < nrecords; i++) { - IOTraceRecord record; - record.io_op_data = 0; - record.trace_type = TraceType::kIOTracer; - record.io_op_data |= (1 << IOTraceOp::kIOLen); - record.io_op_data |= (1 << IOTraceOp::kIOOffset); - record.file_operation = GetFileOperation(i); - record.io_status = IOStatus::OK().ToString(); - record.file_name = kDummyFile + std::to_string(i); - record.len = i; - record.offset = i + 20; - EXPECT_OK(writer->WriteIOOp(record, nullptr)); - } - } - - void VerifyIOOp(IOTraceReader* reader, uint32_t nrecords) { - assert(reader); - for (uint32_t i = 0; i < nrecords; i++) { - IOTraceRecord record; - ASSERT_OK(reader->ReadIOOp(&record)); - ASSERT_EQ(record.file_operation, GetFileOperation(i)); - ASSERT_EQ(record.io_status, IOStatus::OK().ToString()); - ASSERT_EQ(record.len, i); - ASSERT_EQ(record.offset, i + 20); - } - } - - Env* env_; - SystemClock* clock_; - EnvOptions env_options_; - std::string trace_file_path_; - std::string test_path_; -}; - -TEST_F(IOTracerTest, MultipleRecordsWithDifferentIOOpOptions) { - std::string file_name = kDummyFile + std::to_string(5); - { - TraceOptions trace_opt; - std::unique_ptr trace_writer; - - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - IOTracer writer; - ASSERT_OK(writer.StartIOTrace(clock_, trace_opt, std::move(trace_writer))); - - // Write general record. - IOTraceRecord record0(0, TraceType::kIOTracer, 0 /*io_op_data*/, - GetFileOperation(0), 155 /*latency*/, - IOStatus::OK().ToString(), file_name); - writer.WriteIOOp(record0, nullptr); - - // Write record with FileSize. - uint64_t io_op_data = 0; - io_op_data |= (1 << IOTraceOp::kIOFileSize); - IOTraceRecord record1(0, TraceType::kIOTracer, io_op_data, - GetFileOperation(1), 10 /*latency*/, - IOStatus::OK().ToString(), file_name, - 256 /*file_size*/); - writer.WriteIOOp(record1, nullptr); - - // Write record with Length. - io_op_data = 0; - io_op_data |= (1 << IOTraceOp::kIOLen); - IOTraceRecord record2(0, TraceType::kIOTracer, io_op_data, - GetFileOperation(2), 10 /*latency*/, - IOStatus::OK().ToString(), file_name, 100 /*length*/, - 200 /*offset*/); - writer.WriteIOOp(record2, nullptr); - - // Write record with Length and offset. - io_op_data = 0; - io_op_data |= (1 << IOTraceOp::kIOLen); - io_op_data |= (1 << IOTraceOp::kIOOffset); - IOTraceRecord record3(0, TraceType::kIOTracer, io_op_data, - GetFileOperation(3), 10 /*latency*/, - IOStatus::OK().ToString(), file_name, 120 /*length*/, - 17 /*offset*/); - writer.WriteIOOp(record3, nullptr); - - // Write record with offset. - io_op_data = 0; - io_op_data |= (1 << IOTraceOp::kIOOffset); - IOTraceRecord record4(0, TraceType::kIOTracer, io_op_data, - GetFileOperation(4), 10 /*latency*/, - IOStatus::OK().ToString(), file_name, 13 /*length*/, - 50 /*offset*/); - writer.WriteIOOp(record4, nullptr); - - // Write record with IODebugContext. - io_op_data = 0; - IODebugContext dbg; - dbg.SetRequestId("request_id_1"); - IOTraceRecord record5(0, TraceType::kIOTracer, io_op_data, - GetFileOperation(5), 10 /*latency*/, - IOStatus::OK().ToString(), file_name); - writer.WriteIOOp(record5, &dbg); - - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file is generated correctly. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - IOTraceReader reader(std::move(trace_reader)); - IOTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - - // Read general record. - IOTraceRecord record0; - ASSERT_OK(reader.ReadIOOp(&record0)); - ASSERT_EQ(record0.file_operation, GetFileOperation(0)); - ASSERT_EQ(record0.latency, 155); - ASSERT_EQ(record0.file_name, file_name); - - // Read record with FileSize. - IOTraceRecord record1; - ASSERT_OK(reader.ReadIOOp(&record1)); - ASSERT_EQ(record1.file_size, 256); - ASSERT_EQ(record1.len, 0); - ASSERT_EQ(record1.offset, 0); - - // Read record with Length. - IOTraceRecord record2; - ASSERT_OK(reader.ReadIOOp(&record2)); - ASSERT_EQ(record2.len, 100); - ASSERT_EQ(record2.file_size, 0); - ASSERT_EQ(record2.offset, 0); - - // Read record with Length and offset. - IOTraceRecord record3; - ASSERT_OK(reader.ReadIOOp(&record3)); - ASSERT_EQ(record3.len, 120); - ASSERT_EQ(record3.file_size, 0); - ASSERT_EQ(record3.offset, 17); - - // Read record with offset. - IOTraceRecord record4; - ASSERT_OK(reader.ReadIOOp(&record4)); - ASSERT_EQ(record4.len, 0); - ASSERT_EQ(record4.file_size, 0); - ASSERT_EQ(record4.offset, 50); - - IOTraceRecord record5; - ASSERT_OK(reader.ReadIOOp(&record5)); - ASSERT_EQ(record5.len, 0); - ASSERT_EQ(record5.file_size, 0); - ASSERT_EQ(record5.offset, 0); - ASSERT_EQ(record5.request_id, "request_id_1"); - // Read one more record and it should report error. - IOTraceRecord record6; - ASSERT_NOK(reader.ReadIOOp(&record6)); - } -} - -TEST_F(IOTracerTest, AtomicWrite) { - std::string file_name = kDummyFile + std::to_string(0); - { - IOTraceRecord record(0, TraceType::kIOTracer, 0 /*io_op_data*/, - GetFileOperation(0), 10 /*latency*/, - IOStatus::OK().ToString(), file_name); - TraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - IOTracer writer; - ASSERT_OK(writer.StartIOTrace(clock_, trace_opt, std::move(trace_writer))); - writer.WriteIOOp(record, nullptr); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file contains one record. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - IOTraceReader reader(std::move(trace_reader)); - IOTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - // Read record and verify data. - IOTraceRecord access_record; - ASSERT_OK(reader.ReadIOOp(&access_record)); - ASSERT_EQ(access_record.file_operation, GetFileOperation(0)); - ASSERT_EQ(access_record.io_status, IOStatus::OK().ToString()); - ASSERT_EQ(access_record.file_name, file_name); - ASSERT_NOK(reader.ReadIOOp(&access_record)); - } -} - -TEST_F(IOTracerTest, AtomicWriteBeforeStartTrace) { - std::string file_name = kDummyFile + std::to_string(0); - { - IOTraceRecord record(0, TraceType::kIOTracer, 0 /*io_op_data*/, - GetFileOperation(0), 0, IOStatus::OK().ToString(), - file_name); - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - IOTracer writer; - // The record should not be written to the trace_file since StartIOTrace is - // not called. - writer.WriteIOOp(record, nullptr); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file contains nothing. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - IOTraceReader reader(std::move(trace_reader)); - IOTraceHeader header; - ASSERT_NOK(reader.ReadHeader(&header)); - } -} - -TEST_F(IOTracerTest, AtomicNoWriteAfterEndTrace) { - std::string file_name = kDummyFile + std::to_string(0); - { - uint64_t io_op_data = 0; - io_op_data |= (1 << IOTraceOp::kIOFileSize); - IOTraceRecord record( - 0, TraceType::kIOTracer, io_op_data, GetFileOperation(2), 0 /*latency*/, - IOStatus::OK().ToString(), file_name, 10 /*file_size*/); - TraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - IOTracer writer; - ASSERT_OK(writer.StartIOTrace(clock_, trace_opt, std::move(trace_writer))); - writer.WriteIOOp(record, nullptr); - writer.EndIOTrace(); - // Write the record again. This time the record should not be written since - // EndIOTrace is called. - writer.WriteIOOp(record, nullptr); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - { - // Verify trace file contains one record. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - IOTraceReader reader(std::move(trace_reader)); - IOTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - - IOTraceRecord access_record; - ASSERT_OK(reader.ReadIOOp(&access_record)); - ASSERT_EQ(access_record.file_operation, GetFileOperation(2)); - ASSERT_EQ(access_record.io_status, IOStatus::OK().ToString()); - ASSERT_EQ(access_record.file_size, 10); - // No more record. - ASSERT_NOK(reader.ReadIOOp(&access_record)); - } -} - -TEST_F(IOTracerTest, AtomicMultipleWrites) { - { - TraceOptions trace_opt; - std::unique_ptr trace_writer; - ASSERT_OK(NewFileTraceWriter(env_, env_options_, trace_file_path_, - &trace_writer)); - IOTraceWriter writer(clock_, trace_opt, std::move(trace_writer)); - ASSERT_OK(writer.WriteHeader()); - // Write 10 records - WriteIOOp(&writer, 10); - ASSERT_OK(env_->FileExists(trace_file_path_)); - } - - { - // Verify trace file is generated correctly. - std::unique_ptr trace_reader; - ASSERT_OK(NewFileTraceReader(env_, env_options_, trace_file_path_, - &trace_reader)); - IOTraceReader reader(std::move(trace_reader)); - IOTraceHeader header; - ASSERT_OK(reader.ReadHeader(&header)); - ASSERT_EQ(kMajorVersion, static_cast(header.rocksdb_major_version)); - ASSERT_EQ(kMinorVersion, static_cast(header.rocksdb_minor_version)); - // Read 10 records. - VerifyIOOp(&reader, 10); - // Read one more and record and it should report error. - IOTraceRecord record; - ASSERT_NOK(reader.ReadIOOp(&record)); - } -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/autovector_test.cc b/util/autovector_test.cc deleted file mode 100644 index b75a0fa2a..000000000 --- a/util/autovector_test.cc +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "util/autovector.h" - -#include -#include -#include -#include - -#include "rocksdb/env.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/string_util.h" - -using std::cout; -using std::endl; - -namespace ROCKSDB_NAMESPACE { - -class AutoVectorTest : public testing::Test {}; -const unsigned long kSize = 8; - -namespace { -template -void AssertAutoVectorOnlyInStack(autovector* vec, bool result) { - ASSERT_EQ(vec->only_in_stack(), result); -} -} // namespace - -TEST_F(AutoVectorTest, PushBackAndPopBack) { - autovector vec; - ASSERT_TRUE(vec.empty()); - ASSERT_EQ(0ul, vec.size()); - - for (size_t i = 0; i < 1000 * kSize; ++i) { - vec.push_back(i); - ASSERT_TRUE(!vec.empty()); - if (i < kSize) { - AssertAutoVectorOnlyInStack(&vec, true); - } else { - AssertAutoVectorOnlyInStack(&vec, false); - } - ASSERT_EQ(i + 1, vec.size()); - ASSERT_EQ(i, vec[i]); - ASSERT_EQ(i, vec.at(i)); - } - - size_t size = vec.size(); - while (size != 0) { - vec.pop_back(); - // will always be in heap - AssertAutoVectorOnlyInStack(&vec, false); - ASSERT_EQ(--size, vec.size()); - } - - ASSERT_TRUE(vec.empty()); -} - -TEST_F(AutoVectorTest, EmplaceBack) { - using ValType = std::pair; - autovector vec; - - for (size_t i = 0; i < 1000 * kSize; ++i) { - vec.emplace_back(i, std::to_string(i + 123)); - ASSERT_TRUE(!vec.empty()); - if (i < kSize) { - AssertAutoVectorOnlyInStack(&vec, true); - } else { - AssertAutoVectorOnlyInStack(&vec, false); - } - - ASSERT_EQ(i + 1, vec.size()); - ASSERT_EQ(i, vec[i].first); - ASSERT_EQ(std::to_string(i + 123), vec[i].second); - } - - vec.clear(); - ASSERT_TRUE(vec.empty()); - AssertAutoVectorOnlyInStack(&vec, false); -} - -TEST_F(AutoVectorTest, Resize) { - autovector vec; - - vec.resize(kSize); - AssertAutoVectorOnlyInStack(&vec, true); - for (size_t i = 0; i < kSize; ++i) { - vec[i] = i; - } - - vec.resize(kSize * 2); - AssertAutoVectorOnlyInStack(&vec, false); - for (size_t i = 0; i < kSize; ++i) { - ASSERT_EQ(vec[i], i); - } - for (size_t i = 0; i < kSize; ++i) { - vec[i + kSize] = i; - } - - vec.resize(1); - ASSERT_EQ(1U, vec.size()); -} - -namespace { -void AssertEqual(const autovector& a, - const autovector& b) { - ASSERT_EQ(a.size(), b.size()); - ASSERT_EQ(a.empty(), b.empty()); - ASSERT_EQ(a.only_in_stack(), b.only_in_stack()); - for (size_t i = 0; i < a.size(); ++i) { - ASSERT_EQ(a[i], b[i]); - } -} -} // namespace - -TEST_F(AutoVectorTest, CopyAndAssignment) { - // Test both heap-allocated and stack-allocated cases. - for (auto size : {kSize / 2, kSize * 1000}) { - autovector vec; - for (size_t i = 0; i < size; ++i) { - vec.push_back(i); - } - - { - autovector other; - other = vec; - AssertEqual(other, vec); - } - - { - autovector other(vec); - AssertEqual(other, vec); - } - } -} - -TEST_F(AutoVectorTest, Iterators) { - autovector vec; - for (size_t i = 0; i < kSize * 1000; ++i) { - vec.push_back(std::to_string(i)); - } - - // basic operator test - ASSERT_EQ(vec.front(), *vec.begin()); - ASSERT_EQ(vec.back(), *(vec.end() - 1)); - ASSERT_TRUE(vec.begin() < vec.end()); - - // non-const iterator - size_t index = 0; - for (const auto& item : vec) { - ASSERT_EQ(vec[index++], item); - } - - index = vec.size() - 1; - for (auto pos = vec.rbegin(); pos != vec.rend(); ++pos) { - ASSERT_EQ(vec[index--], *pos); - } - - // const iterator - const auto& cvec = vec; - index = 0; - for (const auto& item : cvec) { - ASSERT_EQ(cvec[index++], item); - } - - index = vec.size() - 1; - for (auto pos = cvec.rbegin(); pos != cvec.rend(); ++pos) { - ASSERT_EQ(cvec[index--], *pos); - } - - // forward and backward - auto pos = vec.begin(); - while (pos != vec.end()) { - auto old_val = *pos; - auto old = pos++; - // HACK: make sure -> works - ASSERT_TRUE(!old->empty()); - ASSERT_EQ(old_val, *old); - ASSERT_TRUE(pos == vec.end() || old_val != *pos); - } - - pos = vec.begin(); - for (size_t i = 0; i < vec.size(); i += 2) { - // Cannot use ASSERT_EQ since that macro depends on iostream serialization - ASSERT_TRUE(pos + 2 - 2 == pos); - pos += 2; - ASSERT_TRUE(pos >= vec.begin()); - ASSERT_TRUE(pos <= vec.end()); - - size_t diff = static_cast(pos - vec.begin()); - ASSERT_EQ(i + 2, diff); - } -} - -namespace { -std::vector GetTestKeys(size_t size) { - std::vector keys; - keys.resize(size); - - int index = 0; - for (auto& key : keys) { - key = "item-" + std::to_string(index++); - } - return keys; -} -} // namespace - -template -void BenchmarkVectorCreationAndInsertion( - std::string name, size_t ops, size_t item_size, - const std::vector& items) { - auto env = Env::Default(); - - int index = 0; - auto start_time = env->NowNanos(); - auto ops_remaining = ops; - while (ops_remaining--) { - TVector v; - for (size_t i = 0; i < item_size; ++i) { - v.push_back(items[index++]); - } - } - auto elapsed = env->NowNanos() - start_time; - cout << "created " << ops << " " << name << " instances:\n\t" - << "each was inserted with " << item_size << " elements\n\t" - << "total time elapsed: " << elapsed << " (ns)" << endl; -} - -template -size_t BenchmarkSequenceAccess(std::string name, size_t ops, size_t elem_size) { - TVector v; - for (const auto& item : GetTestKeys(elem_size)) { - v.push_back(item); - } - auto env = Env::Default(); - - auto ops_remaining = ops; - auto start_time = env->NowNanos(); - size_t total = 0; - while (ops_remaining--) { - auto end = v.end(); - for (auto pos = v.begin(); pos != end; ++pos) { - total += pos->size(); - } - } - auto elapsed = env->NowNanos() - start_time; - cout << "performed " << ops << " sequence access against " << name << "\n\t" - << "size: " << elem_size << "\n\t" - << "total time elapsed: " << elapsed << " (ns)" << endl; - // HACK avoid compiler's optimization to ignore total - return total; -} - -// This test case only reports the performance between std::vector -// and autovector. We chose string for comparison because in most -// of our use cases we used std::vector. -TEST_F(AutoVectorTest, PerfBench) { - // We run same operations for kOps times in order to get a more fair result. - size_t kOps = 100000; - - // Creation and insertion test - // Test the case when there is: - // * no element inserted: internal array of std::vector may not really get - // initialize. - // * one element inserted: internal array of std::vector must have - // initialized. - // * kSize elements inserted. This shows the most time we'll spend if we - // keep everything in stack. - // * 2 * kSize elements inserted. The internal vector of - // autovector must have been initialized. - cout << "=====================================================" << endl; - cout << "Creation and Insertion Test (value type: std::string)" << endl; - cout << "=====================================================" << endl; - - // pre-generated unique keys - auto string_keys = GetTestKeys(kOps * 2 * kSize); - for (auto insertions : {0ul, 1ul, kSize / 2, kSize, 2 * kSize}) { - BenchmarkVectorCreationAndInsertion>( - "std::vector", kOps, insertions, string_keys); - BenchmarkVectorCreationAndInsertion>( - "autovector", kOps, insertions, string_keys); - cout << "-----------------------------------" << endl; - } - - cout << "=====================================================" << endl; - cout << "Creation and Insertion Test (value type: uint64_t)" << endl; - cout << "=====================================================" << endl; - - // pre-generated unique keys - std::vector int_keys(kOps * 2 * kSize); - for (size_t i = 0; i < kOps * 2 * kSize; ++i) { - int_keys[i] = i; - } - for (auto insertions : {0ul, 1ul, kSize / 2, kSize, 2 * kSize}) { - BenchmarkVectorCreationAndInsertion>( - "std::vector", kOps, insertions, int_keys); - BenchmarkVectorCreationAndInsertion>( - "autovector", kOps, insertions, int_keys); - cout << "-----------------------------------" << endl; - } - - // Sequence Access Test - cout << "=====================================================" << endl; - cout << "Sequence Access Test" << endl; - cout << "=====================================================" << endl; - for (auto elem_size : {kSize / 2, kSize, 2 * kSize}) { - BenchmarkSequenceAccess>("std::vector", kOps, - elem_size); - BenchmarkSequenceAccess>("autovector", kOps, - elem_size); - cout << "-----------------------------------" << endl; - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/bloom_test.cc b/util/bloom_test.cc deleted file mode 100644 index 06dd1de06..000000000 --- a/util/bloom_test.cc +++ /dev/null @@ -1,1175 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2012 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#ifndef GFLAGS -#include -int main() { - fprintf(stderr, "Please install gflags to run this test... Skipping...\n"); - return 0; -} -#else - -#include -#include -#include - -#include "cache/cache_entry_roles.h" -#include "cache/cache_reservation_manager.h" -#include "memory/arena.h" -#include "port/jemalloc_helper.h" -#include "rocksdb/filter_policy.h" -#include "table/block_based/filter_policy_internal.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/gflags_compat.h" -#include "util/hash.h" - -using GFLAGS_NAMESPACE::ParseCommandLineFlags; - -// The test is not fully designed for bits_per_key other than 10, but with -// this parameter you can easily explore the behavior of other bits_per_key. -// See also filter_bench. -DEFINE_int32(bits_per_key, 10, ""); - -namespace ROCKSDB_NAMESPACE { - -namespace { -const std::string kLegacyBloom = test::LegacyBloomFilterPolicy::kClassName(); -const std::string kFastLocalBloom = - test::FastLocalBloomFilterPolicy::kClassName(); -const std::string kStandard128Ribbon = - test::Standard128RibbonFilterPolicy::kClassName(); -} // namespace - -static const int kVerbose = 1; - -static Slice Key(int i, char* buffer) { - std::string s; - PutFixed32(&s, static_cast(i)); - memcpy(buffer, s.c_str(), sizeof(i)); - return Slice(buffer, sizeof(i)); -} - -static int NextLength(int length) { - if (length < 10) { - length += 1; - } else if (length < 100) { - length += 10; - } else if (length < 1000) { - length += 100; - } else { - length += 1000; - } - return length; -} - -class FullBloomTest : public testing::TestWithParam { - protected: - BlockBasedTableOptions table_options_; - - private: - std::shared_ptr& policy_; - std::unique_ptr bits_builder_; - std::unique_ptr bits_reader_; - std::unique_ptr buf_; - size_t filter_size_; - - public: - FullBloomTest() : policy_(table_options_.filter_policy), filter_size_(0) { - ResetPolicy(); - } - - BuiltinFilterBitsBuilder* GetBuiltinFilterBitsBuilder() { - // Throws on bad cast - return dynamic_cast(bits_builder_.get()); - } - - const BloomLikeFilterPolicy* GetBloomLikeFilterPolicy() { - // Throws on bad cast - return &dynamic_cast(*policy_); - } - - void Reset() { - bits_builder_.reset(BloomFilterPolicy::GetBuilderFromContext( - FilterBuildingContext(table_options_))); - bits_reader_.reset(nullptr); - buf_.reset(nullptr); - filter_size_ = 0; - } - - void ResetPolicy(double bits_per_key) { - policy_ = BloomLikeFilterPolicy::Create(GetParam(), bits_per_key); - Reset(); - } - - void ResetPolicy() { ResetPolicy(FLAGS_bits_per_key); } - - void Add(const Slice& s) { bits_builder_->AddKey(s); } - - void OpenRaw(const Slice& s) { - bits_reader_.reset(policy_->GetFilterBitsReader(s)); - } - - void Build() { - Slice filter = bits_builder_->Finish(&buf_); - bits_reader_.reset(policy_->GetFilterBitsReader(filter)); - filter_size_ = filter.size(); - } - - size_t FilterSize() const { return filter_size_; } - - Slice FilterData() { return Slice(buf_.get(), filter_size_); } - - int GetNumProbesFromFilterData() { - assert(filter_size_ >= 5); - int8_t raw_num_probes = static_cast(buf_.get()[filter_size_ - 5]); - if (raw_num_probes == -1) { // New bloom filter marker - return static_cast(buf_.get()[filter_size_ - 3]); - } else { - return raw_num_probes; - } - } - - int GetRibbonSeedFromFilterData() { - assert(filter_size_ >= 5); - // Check for ribbon marker - assert(-2 == static_cast(buf_.get()[filter_size_ - 5])); - return static_cast(buf_.get()[filter_size_ - 4]); - } - - bool Matches(const Slice& s) { - if (bits_reader_ == nullptr) { - Build(); - } - return bits_reader_->MayMatch(s); - } - - // Provides a kind of fingerprint on the Bloom filter's - // behavior, for reasonbly high FP rates. - uint64_t PackedMatches() { - char buffer[sizeof(int)]; - uint64_t result = 0; - for (int i = 0; i < 64; i++) { - if (Matches(Key(i + 12345, buffer))) { - result |= uint64_t{1} << i; - } - } - return result; - } - - // Provides a kind of fingerprint on the Bloom filter's - // behavior, for lower FP rates. - std::string FirstFPs(int count) { - char buffer[sizeof(int)]; - std::string rv; - int fp_count = 0; - for (int i = 0; i < 1000000; i++) { - // Pack four match booleans into each hexadecimal digit - if (Matches(Key(i + 1000000, buffer))) { - ++fp_count; - rv += std::to_string(i); - if (fp_count == count) { - break; - } - rv += ','; - } - } - return rv; - } - - double FalsePositiveRate() { - char buffer[sizeof(int)]; - int result = 0; - for (int i = 0; i < 10000; i++) { - if (Matches(Key(i + 1000000000, buffer))) { - result++; - } - } - return result / 10000.0; - } -}; - -TEST_P(FullBloomTest, FilterSize) { - // In addition to checking the consistency of space computation, we are - // checking that denoted and computed doubles are interpreted as expected - // as bits_per_key values. - bool some_computed_less_than_denoted = false; - // Note: to avoid unproductive configurations, bits_per_key < 0.5 is rounded - // down to 0 (no filter), and 0.5 <= bits_per_key < 1.0 is rounded up to 1 - // bit per key (1000 millibits). Also, enforced maximum is 100 bits per key - // (100000 millibits). - for (auto bpk : std::vector >{{-HUGE_VAL, 0}, - {-INFINITY, 0}, - {0.0, 0}, - {0.499, 0}, - {0.5, 1000}, - {1.234, 1234}, - {3.456, 3456}, - {9.5, 9500}, - {10.0, 10000}, - {10.499, 10499}, - {21.345, 21345}, - {99.999, 99999}, - {1234.0, 100000}, - {HUGE_VAL, 100000}, - {INFINITY, 100000}, - {NAN, 100000}}) { - ResetPolicy(bpk.first); - auto bfp = GetBloomLikeFilterPolicy(); - EXPECT_EQ(bpk.second, bfp->GetMillibitsPerKey()); - EXPECT_EQ((bpk.second + 500) / 1000, bfp->GetWholeBitsPerKey()); - - double computed = bpk.first; - // This transforms e.g. 9.5 -> 9.499999999999998, which we still - // round to 10 for whole bits per key. - computed += 0.5; - computed /= 1234567.0; - computed *= 1234567.0; - computed -= 0.5; - some_computed_less_than_denoted |= (computed < bpk.first); - ResetPolicy(computed); - bfp = GetBloomLikeFilterPolicy(); - EXPECT_EQ(bpk.second, bfp->GetMillibitsPerKey()); - EXPECT_EQ((bpk.second + 500) / 1000, bfp->GetWholeBitsPerKey()); - - auto bits_builder = GetBuiltinFilterBitsBuilder(); - if (bpk.second == 0) { - ASSERT_EQ(bits_builder, nullptr); - continue; - } - - size_t n = 1; - size_t space = 0; - for (; n < 1000000; n += 1 + n / 1000) { - // Ensure consistency between CalculateSpace and ApproximateNumEntries - space = bits_builder->CalculateSpace(n); - size_t n2 = bits_builder->ApproximateNumEntries(space); - EXPECT_GE(n2, n); - size_t space2 = bits_builder->CalculateSpace(n2); - if (n > 12000 && GetParam() == kStandard128Ribbon) { - // TODO(peterd): better approximation? - EXPECT_GE(space2, space); - EXPECT_LE(space2 * 0.998, space * 1.0); - } else { - EXPECT_EQ(space2, space); - } - } - // Until size_t overflow - for (; n < (n + n / 3); n += n / 3) { - // Ensure space computation is not overflowing; capped is OK - size_t space2 = bits_builder->CalculateSpace(n); - EXPECT_GE(space2, space); - space = space2; - } - } - // Check that the compiler hasn't optimized our computation into nothing - EXPECT_TRUE(some_computed_less_than_denoted); - ResetPolicy(); -} - -TEST_P(FullBloomTest, FullEmptyFilter) { - // Empty filter is not match, at this level - ASSERT_TRUE(!Matches("hello")); - ASSERT_TRUE(!Matches("world")); -} - -TEST_P(FullBloomTest, FullSmall) { - Add("hello"); - Add("world"); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - ASSERT_TRUE(!Matches("x")); - ASSERT_TRUE(!Matches("foo")); -} - -TEST_P(FullBloomTest, FullVaryingLengths) { - char buffer[sizeof(int)]; - - // Count number of filters that significantly exceed the false positive rate - int mediocre_filters = 0; - int good_filters = 0; - - for (int length = 1; length <= 10000; length = NextLength(length)) { - Reset(); - for (int i = 0; i < length; i++) { - Add(Key(i, buffer)); - } - Build(); - - EXPECT_LE(FilterSize(), (size_t)((length * FLAGS_bits_per_key / 8) + - CACHE_LINE_SIZE * 2 + 5)); - - // All added keys must match - for (int i = 0; i < length; i++) { - ASSERT_TRUE(Matches(Key(i, buffer))) - << "Length " << length << "; key " << i; - } - - // Check false positive rate - double rate = FalsePositiveRate(); - if (kVerbose >= 1) { - fprintf(stderr, "False positives: %5.2f%% @ length = %6d ; bytes = %6d\n", - rate * 100.0, length, static_cast(FilterSize())); - } - if (FLAGS_bits_per_key == 10) { - EXPECT_LE(rate, 0.02); // Must not be over 2% - if (rate > 0.0125) { - mediocre_filters++; // Allowed, but not too often - } else { - good_filters++; - } - } - } - if (kVerbose >= 1) { - fprintf(stderr, "Filters: %d good, %d mediocre\n", good_filters, - mediocre_filters); - } - EXPECT_LE(mediocre_filters, good_filters / 5); -} - -TEST_P(FullBloomTest, OptimizeForMemory) { - char buffer[sizeof(int)]; - for (bool offm : {true, false}) { - table_options_.optimize_filters_for_memory = offm; - ResetPolicy(); - Random32 rnd(12345); - uint64_t total_size = 0; - uint64_t total_mem = 0; - int64_t total_keys = 0; - double total_fp_rate = 0; - constexpr int nfilters = 100; - for (int i = 0; i < nfilters; ++i) { - int nkeys = static_cast(rnd.Uniformish(10000)) + 100; - Reset(); - for (int j = 0; j < nkeys; ++j) { - Add(Key(j, buffer)); - } - Build(); - size_t size = FilterData().size(); - total_size += size; - // optimize_filters_for_memory currently depends on malloc_usable_size - // but we run the rest of the test to ensure no bad behavior without it. -#ifdef ROCKSDB_MALLOC_USABLE_SIZE - size = malloc_usable_size(const_cast(FilterData().data())); -#endif // ROCKSDB_MALLOC_USABLE_SIZE - total_mem += size; - total_keys += nkeys; - total_fp_rate += FalsePositiveRate(); - } - if (FLAGS_bits_per_key == 10) { - EXPECT_LE(total_fp_rate / double{nfilters}, 0.011); - EXPECT_GE(total_fp_rate / double{nfilters}, - CACHE_LINE_SIZE >= 256 ? 0.007 : 0.008); - } - - int64_t ex_min_total_size = int64_t{FLAGS_bits_per_key} * total_keys / 8; - if (GetParam() == kStandard128Ribbon) { - // ~ 30% savings vs. Bloom filter - ex_min_total_size = 7 * ex_min_total_size / 10; - } - EXPECT_GE(static_cast(total_size), ex_min_total_size); - - int64_t blocked_bloom_overhead = nfilters * (CACHE_LINE_SIZE + 5); - if (GetParam() == kLegacyBloom) { - // this config can add extra cache line to make odd number - blocked_bloom_overhead += nfilters * CACHE_LINE_SIZE; - } - - EXPECT_GE(total_mem, total_size); - - // optimize_filters_for_memory not implemented with legacy Bloom - if (offm && GetParam() != kLegacyBloom) { - // This value can include a small extra penalty for kExtraPadding - fprintf(stderr, "Internal fragmentation (optimized): %g%%\n", - (total_mem - total_size) * 100.0 / total_size); - // Less than 1% internal fragmentation - EXPECT_LE(total_mem, total_size * 101 / 100); - // Up to 2% storage penalty - EXPECT_LE(static_cast(total_size), - ex_min_total_size * 102 / 100 + blocked_bloom_overhead); - } else { - fprintf(stderr, "Internal fragmentation (not optimized): %g%%\n", - (total_mem - total_size) * 100.0 / total_size); - // TODO: add control checks for more allocators? -#ifdef ROCKSDB_JEMALLOC - fprintf(stderr, "Jemalloc detected? %d\n", HasJemalloc()); - if (HasJemalloc()) { -#ifdef ROCKSDB_MALLOC_USABLE_SIZE - // More than 5% internal fragmentation - EXPECT_GE(total_mem, total_size * 105 / 100); -#endif // ROCKSDB_MALLOC_USABLE_SIZE - } -#endif // ROCKSDB_JEMALLOC - // No storage penalty, just usual overhead - EXPECT_LE(static_cast(total_size), - ex_min_total_size + blocked_bloom_overhead); - } - } -} - -class ChargeFilterConstructionTest : public testing::Test {}; -TEST_F(ChargeFilterConstructionTest, RibbonFilterFallBackOnLargeBanding) { - constexpr std::size_t kCacheCapacity = - 8 * CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize(); - constexpr std::size_t num_entries_for_cache_full = kCacheCapacity / 8; - - for (CacheEntryRoleOptions::Decision charge_filter_construction_mem : - {CacheEntryRoleOptions::Decision::kEnabled, - CacheEntryRoleOptions::Decision::kDisabled}) { - bool will_fall_back = charge_filter_construction_mem == - CacheEntryRoleOptions::Decision::kEnabled; - - BlockBasedTableOptions table_options; - table_options.cache_usage_options.options_overrides.insert( - {CacheEntryRole::kFilterConstruction, - {/*.charged = */ charge_filter_construction_mem}}); - LRUCacheOptions lo; - lo.capacity = kCacheCapacity; - lo.num_shard_bits = 0; // 2^0 shard - lo.strict_capacity_limit = true; - std::shared_ptr cache(NewLRUCache(lo)); - table_options.block_cache = cache; - table_options.filter_policy = - BloomLikeFilterPolicy::Create(kStandard128Ribbon, FLAGS_bits_per_key); - FilterBuildingContext ctx(table_options); - std::unique_ptr filter_bits_builder( - table_options.filter_policy->GetBuilderWithContext(ctx)); - - char key_buffer[sizeof(int)]; - for (std::size_t i = 0; i < num_entries_for_cache_full; ++i) { - filter_bits_builder->AddKey(Key(static_cast(i), key_buffer)); - } - - std::unique_ptr buf; - Slice filter = filter_bits_builder->Finish(&buf); - - // To verify Ribbon Filter fallbacks to Bloom Filter properly - // based on cache charging result - // See BloomFilterPolicy::GetBloomBitsReader re: metadata - // -1 = Marker for newer Bloom implementations - // -2 = Marker for Standard128 Ribbon - if (will_fall_back) { - EXPECT_EQ(filter.data()[filter.size() - 5], static_cast(-1)); - } else { - EXPECT_EQ(filter.data()[filter.size() - 5], static_cast(-2)); - } - - if (charge_filter_construction_mem == - CacheEntryRoleOptions::Decision::kEnabled) { - const size_t dummy_entry_num = static_cast(std::ceil( - filter.size() * 1.0 / - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize())); - EXPECT_GE( - cache->GetPinnedUsage(), - dummy_entry_num * - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize()); - EXPECT_LT( - cache->GetPinnedUsage(), - (dummy_entry_num + 1) * - CacheReservationManagerImpl< - CacheEntryRole::kFilterConstruction>::GetDummyEntrySize()); - } else { - EXPECT_EQ(cache->GetPinnedUsage(), 0); - } - } -} - -namespace { -inline uint32_t SelectByCacheLineSize(uint32_t for64, uint32_t for128, - uint32_t for256) { - (void)for64; - (void)for128; - (void)for256; -#if CACHE_LINE_SIZE == 64 - return for64; -#elif CACHE_LINE_SIZE == 128 - return for128; -#elif CACHE_LINE_SIZE == 256 - return for256; -#else -#error "CACHE_LINE_SIZE unknown or unrecognized" -#endif -} -} // namespace - -// Ensure the implementation doesn't accidentally change in an -// incompatible way. This test doesn't check the reading side -// (FirstFPs/PackedMatches) for LegacyBloom because it requires the -// ability to read filters generated using other cache line sizes. -// See RawSchema. -TEST_P(FullBloomTest, Schema) { -#define EXPECT_EQ_Bloom(a, b) \ - { \ - if (GetParam() != kStandard128Ribbon) { \ - EXPECT_EQ(a, b); \ - } \ - } -#define EXPECT_EQ_Ribbon(a, b) \ - { \ - if (GetParam() == kStandard128Ribbon) { \ - EXPECT_EQ(a, b); \ - } \ - } -#define EXPECT_EQ_FastBloom(a, b) \ - { \ - if (GetParam() == kFastLocalBloom) { \ - EXPECT_EQ(a, b); \ - } \ - } -#define EXPECT_EQ_LegacyBloom(a, b) \ - { \ - if (GetParam() == kLegacyBloom) { \ - EXPECT_EQ(a, b); \ - } \ - } -#define EXPECT_EQ_NotLegacy(a, b) \ - { \ - if (GetParam() != kLegacyBloom) { \ - EXPECT_EQ(a, b); \ - } \ - } - - char buffer[sizeof(int)]; - - // First do a small number of keys, where Ribbon config will fall back on - // fast Bloom filter and generate the same data - ResetPolicy(5); // num_probes = 3 - for (int key = 0; key < 87; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ(GetNumProbesFromFilterData(), 3); - - EXPECT_EQ_NotLegacy(BloomHash(FilterData()), 4130687756U); - - EXPECT_EQ_NotLegacy("31,38,40,43,61,83,86,112,125,131", FirstFPs(10)); - - // Now use enough keys so that changing bits / key by 1 is guaranteed to - // change number of allocated cache lines. So keys > max cache line bits. - - // Note that the first attempted Ribbon seed is determined by the hash - // of the first key added (for pseudorandomness in practice, determinism in - // testing) - - ResetPolicy(2); // num_probes = 1 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 1); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(1567096579, 1964771444, 2659542661U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 3817481309U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1705851228U); - - EXPECT_EQ_FastBloom("11,13,17,25,29,30,35,37,45,53", FirstFPs(10)); - EXPECT_EQ_Ribbon("3,8,10,17,19,20,23,28,31,32", FirstFPs(10)); - - ResetPolicy(3); // num_probes = 2 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 2); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(2707206547U, 2571983456U, 218344685)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 2807269961U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1095342358U); - - EXPECT_EQ_FastBloom("4,15,17,24,27,28,29,53,63,70", FirstFPs(10)); - EXPECT_EQ_Ribbon("3,17,20,28,32,33,36,43,49,54", FirstFPs(10)); - - ResetPolicy(5); // num_probes = 3 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 3); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(515748486, 94611728, 2436112214U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 204628445U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 3971337699U); - - EXPECT_EQ_FastBloom("15,24,29,39,53,87,89,100,103,104", FirstFPs(10)); - EXPECT_EQ_Ribbon("3,33,36,43,67,70,76,78,84,102", FirstFPs(10)); - - ResetPolicy(8); // num_probes = 5 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 5); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(1302145999, 2811644657U, 756553699)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 355564975U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 3651449053U); - - EXPECT_EQ_FastBloom("16,60,66,126,220,238,244,256,265,287", FirstFPs(10)); - EXPECT_EQ_Ribbon("33,187,203,296,300,322,411,419,547,582", FirstFPs(10)); - - ResetPolicy(9); // num_probes = 6 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 6); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(2092755149, 661139132, 1182970461)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 2137566013U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1005676675U); - - EXPECT_EQ_FastBloom("156,367,791,872,945,1015,1139,1159,1265", FirstFPs(9)); - EXPECT_EQ_Ribbon("33,187,203,296,411,419,604,612,615,619", FirstFPs(10)); - - ResetPolicy(11); // num_probes = 7 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 7); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(3755609649U, 1812694762, 1449142939)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 2561502687U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 3129900846U); - - EXPECT_EQ_FastBloom("34,74,130,236,643,882,962,1015,1035,1110", FirstFPs(10)); - EXPECT_EQ_Ribbon("411,419,623,665,727,794,955,1052,1323,1330", FirstFPs(10)); - - // This used to be 9 probes, but 8 is a better choice for speed, - // especially with SIMD groups of 8 probes, with essentially no - // change in FP rate. - // FP rate @ 9 probes, old Bloom: 0.4321% - // FP rate @ 9 probes, new Bloom: 0.1846% - // FP rate @ 8 probes, new Bloom: 0.1843% - ResetPolicy(14); // num_probes = 8 (new), 9 (old) - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_LegacyBloom(GetNumProbesFromFilterData(), 9); - EXPECT_EQ_FastBloom(GetNumProbesFromFilterData(), 8); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(178861123, 379087593, 2574136516U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 3709876890U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1855638875U); - - EXPECT_EQ_FastBloom("130,240,522,565,989,2002,2526,3147,3543", FirstFPs(9)); - EXPECT_EQ_Ribbon("665,727,1323,1755,3866,4232,4442,4492,4736", FirstFPs(9)); - - // This used to be 11 probes, but 9 is a better choice for speed - // AND accuracy. - // FP rate @ 11 probes, old Bloom: 0.3571% - // FP rate @ 11 probes, new Bloom: 0.0884% - // FP rate @ 9 probes, new Bloom: 0.0843% - ResetPolicy(16); // num_probes = 9 (new), 11 (old) - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_LegacyBloom(GetNumProbesFromFilterData(), 11); - EXPECT_EQ_FastBloom(GetNumProbesFromFilterData(), 9); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(1129406313, 3049154394U, 1727750964)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 1087138490U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 459379967U); - - EXPECT_EQ_FastBloom("3299,3611,3916,6620,7822,8079,8482,8942", FirstFPs(8)); - EXPECT_EQ_Ribbon("727,1323,1755,4442,4736,5386,6974,7154,8222", FirstFPs(9)); - - ResetPolicy(10); // num_probes = 6, but different memory ratio vs. 9 - for (int key = 0; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 6); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 61); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(1478976371, 2910591341U, 1182970461)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 2498541272U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1273231667U); - - EXPECT_EQ_FastBloom("16,126,133,422,466,472,813,1002,1035", FirstFPs(9)); - EXPECT_EQ_Ribbon("296,411,419,612,619,623,630,665,686,727", FirstFPs(10)); - - ResetPolicy(10); - for (int key = /*CHANGED*/ 1; key < 2087; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 6); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), /*CHANGED*/ 184); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(4205696321U, 1132081253U, 2385981855U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 2058382345U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 3007790572U); - - EXPECT_EQ_FastBloom("16,126,133,422,466,472,813,1002,1035", FirstFPs(9)); - EXPECT_EQ_Ribbon("33,152,383,497,589,633,737,781,911,990", FirstFPs(10)); - - ResetPolicy(10); - for (int key = 1; key < /*CHANGED*/ 2088; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 6); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 184); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - SelectByCacheLineSize(2885052954U, 769447944, 4175124908U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 23699164U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1942323379U); - - EXPECT_EQ_FastBloom("16,126,133,422,466,472,813,1002,1035", FirstFPs(9)); - EXPECT_EQ_Ribbon("33,95,360,589,737,911,990,1048,1081,1414", FirstFPs(10)); - - // With new fractional bits_per_key, check that we are rounding to - // whole bits per key for old Bloom filters but fractional for - // new Bloom filter. - ResetPolicy(9.5); - for (int key = 1; key < 2088; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_Bloom(GetNumProbesFromFilterData(), 6); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 184); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - /*SAME*/ SelectByCacheLineSize(2885052954U, 769447944, 4175124908U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 3166884174U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 1148258663U); - - EXPECT_EQ_FastBloom("126,156,367,444,458,791,813,976,1015", FirstFPs(9)); - EXPECT_EQ_Ribbon("33,54,95,360,589,693,737,911,990,1048", FirstFPs(10)); - - ResetPolicy(10.499); - for (int key = 1; key < 2088; key++) { - Add(Key(key, buffer)); - } - Build(); - EXPECT_EQ_LegacyBloom(GetNumProbesFromFilterData(), 6); - EXPECT_EQ_FastBloom(GetNumProbesFromFilterData(), 7); - EXPECT_EQ_Ribbon(GetRibbonSeedFromFilterData(), 184); - - EXPECT_EQ_LegacyBloom( - BloomHash(FilterData()), - /*SAME*/ SelectByCacheLineSize(2885052954U, 769447944, 4175124908U)); - EXPECT_EQ_FastBloom(BloomHash(FilterData()), 4098502778U); - EXPECT_EQ_Ribbon(BloomHash(FilterData()), 792138188U); - - EXPECT_EQ_FastBloom("16,236,240,472,1015,1045,1111,1409,1465", FirstFPs(9)); - EXPECT_EQ_Ribbon("33,95,360,589,737,990,1048,1081,1414,1643", FirstFPs(10)); - - ResetPolicy(); -} - -// A helper class for testing custom or corrupt filter bits as read by -// built-in FilterBitsReaders. -struct RawFilterTester { - // Buffer, from which we always return a tail Slice, so the - // last five bytes are always the metadata bytes. - std::array data_{}; - // Points five bytes from the end - char* metadata_ptr_; - - RawFilterTester() : metadata_ptr_(&*(data_.end() - 5)) {} - - Slice ResetNoFill(uint32_t len_without_metadata, uint32_t num_lines, - uint32_t num_probes) { - metadata_ptr_[0] = static_cast(num_probes); - EncodeFixed32(metadata_ptr_ + 1, num_lines); - uint32_t len = len_without_metadata + /*metadata*/ 5; - assert(len <= data_.size()); - return Slice(metadata_ptr_ - len_without_metadata, len); - } - - Slice Reset(uint32_t len_without_metadata, uint32_t num_lines, - uint32_t num_probes, bool fill_ones) { - data_.fill(fill_ones ? 0xff : 0); - return ResetNoFill(len_without_metadata, num_lines, num_probes); - } - - Slice ResetWeirdFill(uint32_t len_without_metadata, uint32_t num_lines, - uint32_t num_probes) { - for (uint32_t i = 0; i < data_.size(); ++i) { - data_[i] = static_cast(0x7b7b >> (i % 7)); - } - return ResetNoFill(len_without_metadata, num_lines, num_probes); - } -}; - -TEST_P(FullBloomTest, RawSchema) { - RawFilterTester cft; - // Legacy Bloom configurations - // Two probes, about 3/4 bits set: ~50% "FP" rate - // One 256-byte cache line. - OpenRaw(cft.ResetWeirdFill(256, 1, 2)); - EXPECT_EQ(uint64_t{11384799501900898790U}, PackedMatches()); - - // Two 128-byte cache lines. - OpenRaw(cft.ResetWeirdFill(256, 2, 2)); - EXPECT_EQ(uint64_t{10157853359773492589U}, PackedMatches()); - - // Four 64-byte cache lines. - OpenRaw(cft.ResetWeirdFill(256, 4, 2)); - EXPECT_EQ(uint64_t{7123594913907464682U}, PackedMatches()); - - // Fast local Bloom configurations (marker 255 -> -1) - // Two probes, about 3/4 bits set: ~50% "FP" rate - // Four 64-byte cache lines. - OpenRaw(cft.ResetWeirdFill(256, 2U << 8, 255)); - EXPECT_EQ(uint64_t{9957045189927952471U}, PackedMatches()); - - // Ribbon configurations (marker 254 -> -2) - - // Even though the builder never builds configurations this - // small (preferring Bloom), we can test that the configuration - // can be read, for possible future-proofing. - - // 256 slots, one result column = 32 bytes (2 blocks, seed 0) - // ~50% FP rate: - // 0b0101010111110101010000110000011011011111100100001110010011101010 - OpenRaw(cft.ResetWeirdFill(32, 2U << 8, 254)); - EXPECT_EQ(uint64_t{6193930559317665002U}, PackedMatches()); - - // 256 slots, three-to-four result columns = 112 bytes - // ~ 1 in 10 FP rate: - // 0b0000000000100000000000000000000001000001000000010000101000000000 - OpenRaw(cft.ResetWeirdFill(112, 2U << 8, 254)); - EXPECT_EQ(uint64_t{9007200345328128U}, PackedMatches()); -} - -TEST_P(FullBloomTest, CorruptFilters) { - RawFilterTester cft; - - for (bool fill : {false, true}) { - // Legacy Bloom configurations - // Good filter bits - returns same as fill - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 6, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Good filter bits - returns same as fill - OpenRaw(cft.Reset(CACHE_LINE_SIZE * 3, 3, 6, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Good filter bits - returns same as fill - // 256 is unusual but legal cache line size - OpenRaw(cft.Reset(256 * 3, 3, 6, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Good filter bits - returns same as fill - // 30 should be max num_probes - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 30, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Good filter bits - returns same as fill - // 1 should be min num_probes - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 1, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Type 1 trivial filter bits - returns true as if FP by zero probes - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 0, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Type 2 trivial filter bits - returns false as if built from zero keys - OpenRaw(cft.Reset(0, 0, 6, fill)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - // Type 2 trivial filter bits - returns false as if built from zero keys - OpenRaw(cft.Reset(0, 37, 6, fill)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - // Type 2 trivial filter bits - returns false as 0 size trumps 0 probes - OpenRaw(cft.Reset(0, 0, 0, fill)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - // Bad filter bits - returns true for safety - // No solution to 0 * x == CACHE_LINE_SIZE - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 0, 6, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Bad filter bits - returns true for safety - // Can't have 3 * x == 4 for integer x - OpenRaw(cft.Reset(4, 3, 6, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Bad filter bits - returns true for safety - // 97 bytes is not a power of two, so not a legal cache line size - OpenRaw(cft.Reset(97 * 3, 3, 6, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Bad filter bits - returns true for safety - // 65 bytes is not a power of two, so not a legal cache line size - OpenRaw(cft.Reset(65 * 3, 3, 6, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Bad filter bits - returns false as if built from zero keys - // < 5 bytes overall means missing even metadata - OpenRaw(cft.Reset(static_cast(-1), 3, 6, fill)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - OpenRaw(cft.Reset(static_cast(-5), 3, 6, fill)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - // Dubious filter bits - returns same as fill (for now) - // 31 is not a useful num_probes, nor generated by RocksDB unless directly - // using filter bits API without BloomFilterPolicy. - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 31, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Dubious filter bits - returns same as fill (for now) - // Similar, with 127, largest positive char - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 127, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Dubious filter bits - returns true (for now) - // num_probes set to 128 / -128, lowest negative char - // NB: Bug in implementation interprets this as negative and has same - // effect as zero probes, but effectively reserves negative char values - // for future use. - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 128, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Dubious filter bits - returns true (for now) - // Similar, with 253 / -3 - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 1, 253, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // ######################################################### - // Fast local Bloom configurations (marker 255 -> -1) - // Good config with six probes - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 6U << 8, 255, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Becomes bad/reserved config (always true) if any other byte set - OpenRaw(cft.Reset(CACHE_LINE_SIZE, (6U << 8) | 1U, 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - OpenRaw(cft.Reset(CACHE_LINE_SIZE, (6U << 8) | (1U << 16), 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - OpenRaw(cft.Reset(CACHE_LINE_SIZE, (6U << 8) | (1U << 24), 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Good config, max 30 probes - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 30U << 8, 255, fill)); - ASSERT_EQ(fill, Matches("hello")); - ASSERT_EQ(fill, Matches("world")); - - // Bad/reserved config (always true) if more than 30 - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 31U << 8, 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 33U << 8, 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 66U << 8, 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - OpenRaw(cft.Reset(CACHE_LINE_SIZE, 130U << 8, 255, fill)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - } - - // ######################################################### - // Ribbon configurations (marker 254 -> -2) - // ("fill" doesn't work to detect good configurations, we just - // have to rely on TN probability) - - // Good: 2 blocks * 16 bytes / segment * 4 columns = 128 bytes - // seed = 123 - OpenRaw(cft.Reset(128, (2U << 8) + 123U, 254, false)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - // Good: 2 blocks * 16 bytes / segment * 8 columns = 256 bytes - OpenRaw(cft.Reset(256, (2U << 8) + 123U, 254, false)); - ASSERT_FALSE(Matches("hello")); - ASSERT_FALSE(Matches("world")); - - // Surprisingly OK: 5000 blocks (640,000 slots) in only 1024 bits - // -> average close to 0 columns - OpenRaw(cft.Reset(128, (5000U << 8) + 123U, 254, false)); - // *Almost* all FPs - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - // Need many queries to find a "true negative" - for (int i = 0; Matches(std::to_string(i)); ++i) { - ASSERT_LT(i, 1000); - } - - // Bad: 1 block not allowed (for implementation detail reasons) - OpenRaw(cft.Reset(128, (1U << 8) + 123U, 254, false)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); - - // Bad: 0 blocks not allowed - OpenRaw(cft.Reset(128, (0U << 8) + 123U, 254, false)); - ASSERT_TRUE(Matches("hello")); - ASSERT_TRUE(Matches("world")); -} - -INSTANTIATE_TEST_CASE_P(Full, FullBloomTest, - testing::Values(kLegacyBloom, kFastLocalBloom, - kStandard128Ribbon)); - -static double GetEffectiveBitsPerKey(FilterBitsBuilder* builder) { - union { - uint64_t key_value = 0; - char key_bytes[8]; - }; - - const unsigned kNumKeys = 1000; - - Slice key_slice{key_bytes, 8}; - for (key_value = 0; key_value < kNumKeys; ++key_value) { - builder->AddKey(key_slice); - } - - std::unique_ptr buf; - auto filter = builder->Finish(&buf); - return filter.size() * /*bits per byte*/ 8 / (1.0 * kNumKeys); -} - -static void SetTestingLevel(int levelish, FilterBuildingContext* ctx) { - if (levelish == -1) { - // Flush is treated as level -1 for this option but actually level 0 - ctx->level_at_creation = 0; - ctx->reason = TableFileCreationReason::kFlush; - } else { - ctx->level_at_creation = levelish; - ctx->reason = TableFileCreationReason::kCompaction; - } -} - -TEST(RibbonTest, RibbonTestLevelThreshold) { - BlockBasedTableOptions opts; - FilterBuildingContext ctx(opts); - // A few settings - for (CompactionStyle cs : {kCompactionStyleLevel, kCompactionStyleUniversal, - kCompactionStyleFIFO, kCompactionStyleNone}) { - ctx.compaction_style = cs; - for (int bloom_before_level : {-1, 0, 1, 10}) { - std::vector > policies; - policies.emplace_back(NewRibbonFilterPolicy(10, bloom_before_level)); - - if (bloom_before_level == 0) { - // Also test new API default - policies.emplace_back(NewRibbonFilterPolicy(10)); - } - - for (std::unique_ptr& policy : policies) { - // Claim to be generating filter for this level - SetTestingLevel(bloom_before_level, &ctx); - - std::unique_ptr builder{ - policy->GetBuilderWithContext(ctx)}; - - // Must be Ribbon (more space efficient than 10 bits per key) - ASSERT_LT(GetEffectiveBitsPerKey(builder.get()), 8); - - if (bloom_before_level >= 0) { - // Claim to be generating filter for previous level - SetTestingLevel(bloom_before_level - 1, &ctx); - - builder.reset(policy->GetBuilderWithContext(ctx)); - - if (cs == kCompactionStyleLevel || cs == kCompactionStyleUniversal) { - // Level is considered. - // Must be Bloom (~ 10 bits per key) - ASSERT_GT(GetEffectiveBitsPerKey(builder.get()), 9); - } else { - // Level is ignored under non-traditional compaction styles. - // Must be Ribbon (more space efficient than 10 bits per key) - ASSERT_LT(GetEffectiveBitsPerKey(builder.get()), 8); - } - } - - // Like SST file writer - ctx.level_at_creation = -1; - ctx.reason = TableFileCreationReason::kMisc; - - builder.reset(policy->GetBuilderWithContext(ctx)); - - // Must be Ribbon (more space efficient than 10 bits per key) - ASSERT_LT(GetEffectiveBitsPerKey(builder.get()), 8); - } - } - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - ParseCommandLineFlags(&argc, &argv, true); - - return RUN_ALL_TESTS(); -} - -#endif // GFLAGS diff --git a/util/coding_test.cc b/util/coding_test.cc deleted file mode 100644 index 79dd7b82e..000000000 --- a/util/coding_test.cc +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "util/coding.h" - -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class Coding {}; -TEST(Coding, Fixed16) { - std::string s; - for (uint16_t v = 0; v < 0xFFFF; v++) { - PutFixed16(&s, v); - } - - const char* p = s.data(); - for (uint16_t v = 0; v < 0xFFFF; v++) { - uint16_t actual = DecodeFixed16(p); - ASSERT_EQ(v, actual); - p += sizeof(uint16_t); - } -} - -TEST(Coding, Fixed32) { - std::string s; - for (uint32_t v = 0; v < 100000; v++) { - PutFixed32(&s, v); - } - - const char* p = s.data(); - for (uint32_t v = 0; v < 100000; v++) { - uint32_t actual = DecodeFixed32(p); - ASSERT_EQ(v, actual); - p += sizeof(uint32_t); - } -} - -TEST(Coding, Fixed64) { - std::string s; - for (int power = 0; power <= 63; power++) { - uint64_t v = static_cast(1) << power; - PutFixed64(&s, v - 1); - PutFixed64(&s, v + 0); - PutFixed64(&s, v + 1); - } - - const char* p = s.data(); - for (int power = 0; power <= 63; power++) { - uint64_t v = static_cast(1) << power; - uint64_t actual = 0; - actual = DecodeFixed64(p); - ASSERT_EQ(v - 1, actual); - p += sizeof(uint64_t); - - actual = DecodeFixed64(p); - ASSERT_EQ(v + 0, actual); - p += sizeof(uint64_t); - - actual = DecodeFixed64(p); - ASSERT_EQ(v + 1, actual); - p += sizeof(uint64_t); - } -} - -// Test that encoding routines generate little-endian encodings -TEST(Coding, EncodingOutput) { - std::string dst; - PutFixed32(&dst, 0x04030201); - ASSERT_EQ(4U, dst.size()); - ASSERT_EQ(0x01, static_cast(dst[0])); - ASSERT_EQ(0x02, static_cast(dst[1])); - ASSERT_EQ(0x03, static_cast(dst[2])); - ASSERT_EQ(0x04, static_cast(dst[3])); - - dst.clear(); - PutFixed64(&dst, 0x0807060504030201ull); - ASSERT_EQ(8U, dst.size()); - ASSERT_EQ(0x01, static_cast(dst[0])); - ASSERT_EQ(0x02, static_cast(dst[1])); - ASSERT_EQ(0x03, static_cast(dst[2])); - ASSERT_EQ(0x04, static_cast(dst[3])); - ASSERT_EQ(0x05, static_cast(dst[4])); - ASSERT_EQ(0x06, static_cast(dst[5])); - ASSERT_EQ(0x07, static_cast(dst[6])); - ASSERT_EQ(0x08, static_cast(dst[7])); -} - -TEST(Coding, Varint32) { - std::string s; - for (uint32_t i = 0; i < (32 * 32); i++) { - uint32_t v = (i / 32) << (i % 32); - PutVarint32(&s, v); - } - - const char* p = s.data(); - const char* limit = p + s.size(); - for (uint32_t i = 0; i < (32 * 32); i++) { - uint32_t expected = (i / 32) << (i % 32); - uint32_t actual = 0; - const char* start = p; - p = GetVarint32Ptr(p, limit, &actual); - ASSERT_TRUE(p != nullptr); - ASSERT_EQ(expected, actual); - ASSERT_EQ(VarintLength(actual), p - start); - } - ASSERT_EQ(p, s.data() + s.size()); -} - -TEST(Coding, Varint64) { - // Construct the list of values to check - std::vector values; - // Some special values - values.push_back(0); - values.push_back(100); - values.push_back(~static_cast(0)); - values.push_back(~static_cast(0) - 1); - for (uint32_t k = 0; k < 64; k++) { - // Test values near powers of two - const uint64_t power = 1ull << k; - values.push_back(power); - values.push_back(power - 1); - values.push_back(power + 1); - }; - - std::string s; - for (unsigned int i = 0; i < values.size(); i++) { - PutVarint64(&s, values[i]); - } - - const char* p = s.data(); - const char* limit = p + s.size(); - for (unsigned int i = 0; i < values.size(); i++) { - ASSERT_TRUE(p < limit); - uint64_t actual = 0; - const char* start = p; - p = GetVarint64Ptr(p, limit, &actual); - ASSERT_TRUE(p != nullptr); - ASSERT_EQ(values[i], actual); - ASSERT_EQ(VarintLength(actual), p - start); - } - ASSERT_EQ(p, limit); -} - -TEST(Coding, Varint32Overflow) { - uint32_t result; - std::string input("\x81\x82\x83\x84\x85\x11"); - ASSERT_TRUE(GetVarint32Ptr(input.data(), input.data() + input.size(), - &result) == nullptr); -} - -TEST(Coding, Varint32Truncation) { - uint32_t large_value = (1u << 31) + 100; - std::string s; - PutVarint32(&s, large_value); - uint32_t result; - for (unsigned int len = 0; len + 1 < s.size(); len++) { - ASSERT_TRUE(GetVarint32Ptr(s.data(), s.data() + len, &result) == nullptr); - } - ASSERT_TRUE(GetVarint32Ptr(s.data(), s.data() + s.size(), &result) != - nullptr); - ASSERT_EQ(large_value, result); -} - -TEST(Coding, Varint64Overflow) { - uint64_t result; - std::string input("\x81\x82\x83\x84\x85\x81\x82\x83\x84\x85\x11"); - ASSERT_TRUE(GetVarint64Ptr(input.data(), input.data() + input.size(), - &result) == nullptr); -} - -TEST(Coding, Varint64Truncation) { - uint64_t large_value = (1ull << 63) + 100ull; - std::string s; - PutVarint64(&s, large_value); - uint64_t result; - for (unsigned int len = 0; len + 1 < s.size(); len++) { - ASSERT_TRUE(GetVarint64Ptr(s.data(), s.data() + len, &result) == nullptr); - } - ASSERT_TRUE(GetVarint64Ptr(s.data(), s.data() + s.size(), &result) != - nullptr); - ASSERT_EQ(large_value, result); -} - -TEST(Coding, Strings) { - std::string s; - PutLengthPrefixedSlice(&s, Slice("")); - PutLengthPrefixedSlice(&s, Slice("foo")); - PutLengthPrefixedSlice(&s, Slice("bar")); - PutLengthPrefixedSlice(&s, Slice(std::string(200, 'x'))); - - Slice input(s); - Slice v; - ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v)); - ASSERT_EQ("", v.ToString()); - ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v)); - ASSERT_EQ("foo", v.ToString()); - ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v)); - ASSERT_EQ("bar", v.ToString()); - ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v)); - ASSERT_EQ(std::string(200, 'x'), v.ToString()); - ASSERT_EQ("", input.ToString()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/crc32c_test.cc b/util/crc32c_test.cc deleted file mode 100644 index 715d63e2d..000000000 --- a/util/crc32c_test.cc +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. -#include "util/crc32c.h" - -#include "test_util/testharness.h" -#include "util/coding.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { -namespace crc32c { - -class CRC {}; - -// Tests for 3-way crc32c algorithm. We need these tests because it uses -// different lookup tables than the original Fast_CRC32 -const unsigned int BUFFER_SIZE = 512 * 1024 * sizeof(uint64_t); -char buffer[BUFFER_SIZE]; - -struct ExpectedResult { - size_t offset; - size_t length; - uint32_t crc32c; -}; - -ExpectedResult expectedResults[] = { - // Zero-byte input - {0, 0, ~0U}, - // Small aligned inputs to test special cases in SIMD implementations - {8, 1, 1543413366}, - {8, 2, 523493126}, - {8, 3, 1560427360}, - {8, 4, 3422504776}, - {8, 5, 447841138}, - {8, 6, 3910050499}, - {8, 7, 3346241981}, - // Small unaligned inputs - {9, 1, 3855826643}, - {10, 2, 560880875}, - {11, 3, 1479707779}, - {12, 4, 2237687071}, - {13, 5, 4063855784}, - {14, 6, 2553454047}, - {15, 7, 1349220140}, - // Larger inputs to test leftover chunks at the end of aligned blocks - {8, 8, 627613930}, - {8, 9, 2105929409}, - {8, 10, 2447068514}, - {8, 11, 863807079}, - {8, 12, 292050879}, - {8, 13, 1411837737}, - {8, 14, 2614515001}, - {8, 15, 3579076296}, - {8, 16, 2897079161}, - {8, 17, 675168386}, - // // Much larger inputs - {0, BUFFER_SIZE, 2096790750}, - {1, BUFFER_SIZE / 2, 3854797577}, - -}; - -TEST(CRC, StandardResults) { - // Original Fast_CRC32 tests. - // From rfc3720 section B.4. - char buf[32]; - - memset(buf, 0, sizeof(buf)); - ASSERT_EQ(0x8a9136aaU, Value(buf, sizeof(buf))); - - memset(buf, 0xff, sizeof(buf)); - ASSERT_EQ(0x62a8ab43U, Value(buf, sizeof(buf))); - - for (int i = 0; i < 32; i++) { - buf[i] = static_cast(i); - } - ASSERT_EQ(0x46dd794eU, Value(buf, sizeof(buf))); - - for (int i = 0; i < 32; i++) { - buf[i] = static_cast(31 - i); - } - ASSERT_EQ(0x113fdb5cU, Value(buf, sizeof(buf))); - - unsigned char data[48] = { - 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, - 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; - ASSERT_EQ(0xd9963a56, Value(reinterpret_cast(data), sizeof(data))); - - // 3-Way Crc32c tests ported from folly. - // Test 1: single computation - for (auto expected : expectedResults) { - uint32_t result = Value(buffer + expected.offset, expected.length); - EXPECT_EQ(~expected.crc32c, result); - } - - // Test 2: stitching two computations - for (auto expected : expectedResults) { - size_t partialLength = expected.length / 2; - uint32_t partialChecksum = Value(buffer + expected.offset, partialLength); - uint32_t result = - Extend(partialChecksum, buffer + expected.offset + partialLength, - expected.length - partialLength); - EXPECT_EQ(~expected.crc32c, result); - } -} - -TEST(CRC, Values) { ASSERT_NE(Value("a", 1), Value("foo", 3)); } - -TEST(CRC, Extend) { - ASSERT_EQ(Value("hello world", 11), Extend(Value("hello ", 6), "world", 5)); -} - -TEST(CRC, Mask) { - uint32_t crc = Value("foo", 3); - ASSERT_NE(crc, Mask(crc)); - ASSERT_NE(crc, Mask(Mask(crc))); - ASSERT_EQ(crc, Unmask(Mask(crc))); - ASSERT_EQ(crc, Unmask(Unmask(Mask(Mask(crc))))); -} - -TEST(CRC, Crc32cCombineBasicTest) { - uint32_t crc1 = Value("hello ", 6); - uint32_t crc2 = Value("world", 5); - uint32_t crc3 = Value("hello world", 11); - uint32_t crc1_2_combine = Crc32cCombine(crc1, crc2, 5); - ASSERT_EQ(crc3, crc1_2_combine); -} - -TEST(CRC, Crc32cCombineOrderMattersTest) { - uint32_t crc1 = Value("hello ", 6); - uint32_t crc2 = Value("world", 5); - uint32_t crc3 = Value("hello world", 11); - uint32_t crc2_1_combine = Crc32cCombine(crc2, crc1, 6); - ASSERT_NE(crc3, crc2_1_combine); -} - -TEST(CRC, Crc32cCombineFullCoverTest) { - int scale = 4 * 1024; - Random rnd(test::RandomSeed()); - int size_1 = 1024 * 1024; - std::string s1 = rnd.RandomBinaryString(size_1); - uint32_t crc1 = Value(s1.data(), size_1); - for (int i = 0; i < scale; i++) { - int size_2 = i; - std::string s2 = rnd.RandomBinaryString(size_2); - uint32_t crc2 = Value(s2.data(), s2.size()); - uint32_t crc1_2 = Extend(crc1, s2.data(), s2.size()); - uint32_t crc1_2_combine = Crc32cCombine(crc1, crc2, size_2); - ASSERT_EQ(crc1_2, crc1_2_combine); - } -} - -TEST(CRC, Crc32cCombineBigSizeTest) { - Random rnd(test::RandomSeed()); - int size_1 = 1024 * 1024; - std::string s1 = rnd.RandomBinaryString(size_1); - uint32_t crc1 = Value(s1.data(), size_1); - int size_2 = 16 * 1024 * 1024 - 1; - std::string s2 = rnd.RandomBinaryString(size_2); - uint32_t crc2 = Value(s2.data(), s2.size()); - uint32_t crc1_2 = Extend(crc1, s2.data(), s2.size()); - uint32_t crc1_2_combine = Crc32cCombine(crc1, crc2, size_2); - ASSERT_EQ(crc1_2, crc1_2_combine); -} - -} // namespace crc32c -} // namespace ROCKSDB_NAMESPACE - -// copied from folly -const uint64_t FNV_64_HASH_START = 14695981039346656037ULL; -inline uint64_t fnv64_buf(const void* buf, size_t n, - uint64_t hash = FNV_64_HASH_START) { - // forcing signed char, since other platforms can use unsigned - const signed char* char_buf = reinterpret_cast(buf); - - for (size_t i = 0; i < n; ++i) { - hash += (hash << 1) + (hash << 4) + (hash << 5) + (hash << 7) + - (hash << 8) + (hash << 40); - hash ^= char_buf[i]; - } - return hash; -} - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - // Populate a buffer with a deterministic pattern - // on which to compute checksums - - const uint8_t* src = (uint8_t*)ROCKSDB_NAMESPACE::crc32c::buffer; - uint64_t* dst = (uint64_t*)ROCKSDB_NAMESPACE::crc32c::buffer; - const uint64_t* end = - (const uint64_t*)(ROCKSDB_NAMESPACE::crc32c::buffer + - ROCKSDB_NAMESPACE::crc32c::BUFFER_SIZE); - *dst++ = 0; - while (dst < end) { - ROCKSDB_NAMESPACE::EncodeFixed64( - reinterpret_cast(dst), - fnv64_buf((const char*)src, sizeof(uint64_t))); - dst++; - src += sizeof(uint64_t); - } - - return RUN_ALL_TESTS(); -} diff --git a/util/defer_test.cc b/util/defer_test.cc deleted file mode 100644 index 0e98f68b6..000000000 --- a/util/defer_test.cc +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "util/defer.h" - -#include "port/port.h" -#include "port/stack_trace.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class DeferTest {}; - -TEST(DeferTest, BlockScope) { - int v = 1; - { - Defer defer([&v]() { v *= 2; }); - } - ASSERT_EQ(2, v); -} - -TEST(DeferTest, FunctionScope) { - int v = 1; - auto f = [&v]() { - Defer defer([&v]() { v *= 2; }); - v = 2; - }; - f(); - ASSERT_EQ(4, v); -} - -TEST(SaveAndRestoreTest, BlockScope) { - int v = 1; - { - SaveAndRestore sr(&v); - ASSERT_EQ(v, 1); - v = 2; - ASSERT_EQ(v, 2); - } - ASSERT_EQ(v, 1); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/dynamic_bloom_test.cc b/util/dynamic_bloom_test.cc deleted file mode 100644 index 925c5479a..000000000 --- a/util/dynamic_bloom_test.cc +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#ifndef GFLAGS -#include -int main() { - fprintf(stderr, "Please install gflags to run this test... Skipping...\n"); - return 0; -} -#else - -#include -#include -#include -#include -#include -#include -#include - -#include "dynamic_bloom.h" -#include "memory/arena.h" -#include "port/port.h" -#include "rocksdb/system_clock.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/gflags_compat.h" -#include "util/stop_watch.h" - -using GFLAGS_NAMESPACE::ParseCommandLineFlags; - -DEFINE_int32(bits_per_key, 10, ""); -DEFINE_int32(num_probes, 6, ""); -DEFINE_bool(enable_perf, false, ""); - -namespace ROCKSDB_NAMESPACE { - -struct KeyMaker { - uint64_t a; - uint64_t b; - - // Sequential, within a hash function block - inline Slice Seq(uint64_t i) { - a = i; - return Slice(reinterpret_cast(&a), sizeof(a)); - } - // Not quite sequential, varies across hash function blocks - inline Slice Nonseq(uint64_t i) { - a = i; - b = i * 123; - return Slice(reinterpret_cast(this), sizeof(*this)); - } - inline Slice Key(uint64_t i, bool nonseq) { - return nonseq ? Nonseq(i) : Seq(i); - } -}; - -class DynamicBloomTest : public testing::Test {}; - -TEST_F(DynamicBloomTest, EmptyFilter) { - Arena arena; - DynamicBloom bloom1(&arena, 100, 2); - ASSERT_TRUE(!bloom1.MayContain("hello")); - ASSERT_TRUE(!bloom1.MayContain("world")); - - DynamicBloom bloom2(&arena, CACHE_LINE_SIZE * 8 * 2 - 1, 2); - ASSERT_TRUE(!bloom2.MayContain("hello")); - ASSERT_TRUE(!bloom2.MayContain("world")); -} - -TEST_F(DynamicBloomTest, Small) { - Arena arena; - DynamicBloom bloom1(&arena, 100, 2); - bloom1.Add("hello"); - bloom1.Add("world"); - ASSERT_TRUE(bloom1.MayContain("hello")); - ASSERT_TRUE(bloom1.MayContain("world")); - ASSERT_TRUE(!bloom1.MayContain("x")); - ASSERT_TRUE(!bloom1.MayContain("foo")); - - DynamicBloom bloom2(&arena, CACHE_LINE_SIZE * 8 * 2 - 1, 2); - bloom2.Add("hello"); - bloom2.Add("world"); - ASSERT_TRUE(bloom2.MayContain("hello")); - ASSERT_TRUE(bloom2.MayContain("world")); - ASSERT_TRUE(!bloom2.MayContain("x")); - ASSERT_TRUE(!bloom2.MayContain("foo")); -} - -TEST_F(DynamicBloomTest, SmallConcurrentAdd) { - Arena arena; - DynamicBloom bloom1(&arena, 100, 2); - bloom1.AddConcurrently("hello"); - bloom1.AddConcurrently("world"); - ASSERT_TRUE(bloom1.MayContain("hello")); - ASSERT_TRUE(bloom1.MayContain("world")); - ASSERT_TRUE(!bloom1.MayContain("x")); - ASSERT_TRUE(!bloom1.MayContain("foo")); - - DynamicBloom bloom2(&arena, CACHE_LINE_SIZE * 8 * 2 - 1, 2); - bloom2.AddConcurrently("hello"); - bloom2.AddConcurrently("world"); - ASSERT_TRUE(bloom2.MayContain("hello")); - ASSERT_TRUE(bloom2.MayContain("world")); - ASSERT_TRUE(!bloom2.MayContain("x")); - ASSERT_TRUE(!bloom2.MayContain("foo")); -} - -static uint32_t NextNum(uint32_t num) { - if (num < 10) { - num += 1; - } else if (num < 100) { - num += 10; - } else if (num < 1000) { - num += 100; - } else { - num = num * 26 / 10; - } - return num; -} - -TEST_F(DynamicBloomTest, VaryingLengths) { - KeyMaker km; - - // Count number of filters that significantly exceed the false positive rate - int mediocre_filters = 0; - int good_filters = 0; - uint32_t num_probes = static_cast(FLAGS_num_probes); - - fprintf(stderr, "bits_per_key: %d num_probes: %d\n", FLAGS_bits_per_key, - num_probes); - - // NB: FP rate impact of 32-bit hash is noticeable starting around 10M keys. - // But that effect is hidden if using sequential keys (unique hashes). - for (bool nonseq : {false, true}) { - const uint32_t max_num = FLAGS_enable_perf ? 40000000 : 400000; - for (uint32_t num = 1; num <= max_num; num = NextNum(num)) { - uint32_t bloom_bits = 0; - Arena arena; - bloom_bits = num * FLAGS_bits_per_key; - DynamicBloom bloom(&arena, bloom_bits, num_probes); - for (uint64_t i = 0; i < num; i++) { - bloom.Add(km.Key(i, nonseq)); - ASSERT_TRUE(bloom.MayContain(km.Key(i, nonseq))); - } - - // All added keys must match - for (uint64_t i = 0; i < num; i++) { - ASSERT_TRUE(bloom.MayContain(km.Key(i, nonseq))); - } - - // Check false positive rate - int result = 0; - for (uint64_t i = 0; i < 30000; i++) { - if (bloom.MayContain(km.Key(i + 1000000000, nonseq))) { - result++; - } - } - double rate = result / 30000.0; - - fprintf(stderr, - "False positives (%s keys): " - "%5.2f%% @ num = %6u, bloom_bits = %6u\n", - nonseq ? "nonseq" : "seq", rate * 100.0, num, bloom_bits); - - if (rate > 0.0125) - mediocre_filters++; // Allowed, but not too often - else - good_filters++; - } - } - - fprintf(stderr, "Filters: %d good, %d mediocre\n", good_filters, - mediocre_filters); - ASSERT_LE(mediocre_filters, good_filters / 25); -} - -TEST_F(DynamicBloomTest, perf) { - KeyMaker km; - StopWatchNano timer(SystemClock::Default().get()); - uint32_t num_probes = static_cast(FLAGS_num_probes); - - if (!FLAGS_enable_perf) { - return; - } - - for (uint32_t m = 1; m <= 8; ++m) { - Arena arena; - const uint32_t num_keys = m * 8 * 1024 * 1024; - fprintf(stderr, "testing %" PRIu32 "M keys\n", m * 8); - - DynamicBloom std_bloom(&arena, num_keys * 10, num_probes); - - timer.Start(); - for (uint64_t i = 1; i <= num_keys; ++i) { - std_bloom.Add(km.Seq(i)); - } - - uint64_t elapsed = timer.ElapsedNanos(); - fprintf(stderr, "dynamic bloom, avg add latency %3g\n", - static_cast(elapsed) / num_keys); - - uint32_t count = 0; - timer.Start(); - for (uint64_t i = 1; i <= num_keys; ++i) { - if (std_bloom.MayContain(km.Seq(i))) { - ++count; - } - } - ASSERT_EQ(count, num_keys); - elapsed = timer.ElapsedNanos(); - assert(count > 0); - fprintf(stderr, "dynamic bloom, avg query latency %3g\n", - static_cast(elapsed) / count); - } -} - -TEST_F(DynamicBloomTest, concurrent_with_perf) { - uint32_t num_probes = static_cast(FLAGS_num_probes); - - uint32_t m_limit = FLAGS_enable_perf ? 8 : 1; - - uint32_t num_threads = 4; - std::vector threads; - - // NB: Uses sequential keys for speed, but that hides the FP rate - // impact of 32-bit hash, which is noticeable starting around 10M keys - // when they vary across hashing blocks. - for (uint32_t m = 1; m <= m_limit; ++m) { - Arena arena; - const uint32_t num_keys = m * 8 * 1024 * 1024; - fprintf(stderr, "testing %" PRIu32 "M keys\n", m * 8); - - DynamicBloom std_bloom(&arena, num_keys * 10, num_probes); - - std::atomic elapsed(0); - - std::function adder([&](size_t t) { - KeyMaker km; - StopWatchNano timer(SystemClock::Default().get()); - timer.Start(); - for (uint64_t i = 1 + t; i <= num_keys; i += num_threads) { - std_bloom.AddConcurrently(km.Seq(i)); - } - elapsed += timer.ElapsedNanos(); - }); - for (size_t t = 0; t < num_threads; ++t) { - threads.emplace_back(adder, t); - } - while (threads.size() > 0) { - threads.back().join(); - threads.pop_back(); - } - - fprintf(stderr, - "dynamic bloom, avg parallel add latency %3g" - " nanos/key\n", - static_cast(elapsed) / num_threads / num_keys); - - elapsed = 0; - std::function hitter([&](size_t t) { - KeyMaker km; - StopWatchNano timer(SystemClock::Default().get()); - timer.Start(); - for (uint64_t i = 1 + t; i <= num_keys; i += num_threads) { - bool f = std_bloom.MayContain(km.Seq(i)); - ASSERT_TRUE(f); - } - elapsed += timer.ElapsedNanos(); - }); - for (size_t t = 0; t < num_threads; ++t) { - threads.emplace_back(hitter, t); - } - while (threads.size() > 0) { - threads.back().join(); - threads.pop_back(); - } - - fprintf(stderr, - "dynamic bloom, avg parallel hit latency %3g" - " nanos/key\n", - static_cast(elapsed) / num_threads / num_keys); - - elapsed = 0; - std::atomic false_positives(0); - std::function misser([&](size_t t) { - KeyMaker km; - StopWatchNano timer(SystemClock::Default().get()); - timer.Start(); - for (uint64_t i = num_keys + 1 + t; i <= 2 * num_keys; i += num_threads) { - bool f = std_bloom.MayContain(km.Seq(i)); - if (f) { - ++false_positives; - } - } - elapsed += timer.ElapsedNanos(); - }); - for (size_t t = 0; t < num_threads; ++t) { - threads.emplace_back(misser, t); - } - while (threads.size() > 0) { - threads.back().join(); - threads.pop_back(); - } - - fprintf(stderr, - "dynamic bloom, avg parallel miss latency %3g" - " nanos/key, %f%% false positive rate\n", - static_cast(elapsed) / num_threads / num_keys, - false_positives.load() * 100.0 / num_keys); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char **argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - ParseCommandLineFlags(&argc, &argv, true); - - return RUN_ALL_TESTS(); -} - -#endif // GFLAGS diff --git a/util/file_reader_writer_test.cc b/util/file_reader_writer_test.cc deleted file mode 100644 index 68776612b..000000000 --- a/util/file_reader_writer_test.cc +++ /dev/null @@ -1,1058 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include -#include - -#include "db/db_test_util.h" -#include "env/mock_env.h" -#include "file/line_file_reader.h" -#include "file/random_access_file_reader.h" -#include "file/read_write_util.h" -#include "file/readahead_raf.h" -#include "file/sequence_file_reader.h" -#include "file/writable_file_writer.h" -#include "rocksdb/file_system.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/crc32c.h" -#include "util/random.h" -#include "utilities/fault_injection_fs.h" - -namespace ROCKSDB_NAMESPACE { - -class WritableFileWriterTest : public testing::Test {}; - -constexpr uint32_t kMb = static_cast(1) << 20; - -TEST_F(WritableFileWriterTest, RangeSync) { - class FakeWF : public FSWritableFile { - public: - explicit FakeWF() : size_(0), last_synced_(0) {} - ~FakeWF() override {} - - using FSWritableFile::Append; - IOStatus Append(const Slice& data, const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - size_ += data.size(); - return IOStatus::OK(); - } - IOStatus Truncate(uint64_t /*size*/, const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Close(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - EXPECT_GE(size_, last_synced_ + kMb); - EXPECT_LT(size_, last_synced_ + 2 * kMb); - // Make sure random writes generated enough writes. - EXPECT_GT(size_, 10 * kMb); - return IOStatus::OK(); - } - IOStatus Flush(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Sync(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Fsync(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - void SetIOPriority(Env::IOPriority /*pri*/) override {} - uint64_t GetFileSize(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return size_; - } - void GetPreallocationStatus(size_t* /*block_size*/, - size_t* /*last_allocated_block*/) override {} - size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const override { - return 0; - } - IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) override { - return IOStatus::OK(); - } - - protected: - IOStatus Allocate(uint64_t /*offset*/, uint64_t /*len*/, - const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus RangeSync(uint64_t offset, uint64_t nbytes, - const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(offset % 4096, 0u); - EXPECT_EQ(nbytes % 4096, 0u); - - EXPECT_EQ(offset, last_synced_); - last_synced_ = offset + nbytes; - EXPECT_GE(size_, last_synced_ + kMb); - if (size_ > 2 * kMb) { - EXPECT_LT(size_, last_synced_ + 2 * kMb); - } - return IOStatus::OK(); - } - - uint64_t size_; - uint64_t last_synced_; - }; - - EnvOptions env_options; - env_options.bytes_per_sync = kMb; - std::unique_ptr wf(new FakeWF); - std::unique_ptr writer( - new WritableFileWriter(std::move(wf), "" /* don't care */, env_options)); - Random r(301); - Status s; - std::unique_ptr large_buf(new char[10 * kMb]); - for (int i = 0; i < 1000; i++) { - int skew_limit = (i < 700) ? 10 : 15; - uint32_t num = r.Skewed(skew_limit) * 100 + r.Uniform(100); - s = writer->Append(Slice(large_buf.get(), num)); - ASSERT_OK(s); - - // Flush in a chance of 1/10. - if (r.Uniform(10) == 0) { - s = writer->Flush(); - ASSERT_OK(s); - } - } - s = writer->Close(); - ASSERT_OK(s); -} - -TEST_F(WritableFileWriterTest, IncrementalBuffer) { - class FakeWF : public FSWritableFile { - public: - explicit FakeWF(std::string* _file_data, bool _use_direct_io, - bool _no_flush) - : file_data_(_file_data), - use_direct_io_(_use_direct_io), - no_flush_(_no_flush) {} - ~FakeWF() override {} - - using FSWritableFile::Append; - IOStatus Append(const Slice& data, const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - file_data_->append(data.data(), data.size()); - size_ += data.size(); - return IOStatus::OK(); - } - using FSWritableFile::PositionedAppend; - IOStatus PositionedAppend(const Slice& data, uint64_t pos, - const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - EXPECT_TRUE(pos % 512 == 0); - EXPECT_TRUE(data.size() % 512 == 0); - file_data_->resize(pos); - file_data_->append(data.data(), data.size()); - size_ += data.size(); - return IOStatus::OK(); - } - - IOStatus Truncate(uint64_t size, const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - file_data_->resize(size); - return IOStatus::OK(); - } - IOStatus Close(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Flush(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Sync(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Fsync(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - void SetIOPriority(Env::IOPriority /*pri*/) override {} - uint64_t GetFileSize(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return size_; - } - void GetPreallocationStatus(size_t* /*block_size*/, - size_t* /*last_allocated_block*/) override {} - size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const override { - return 0; - } - IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) override { - return IOStatus::OK(); - } - bool use_direct_io() const override { return use_direct_io_; } - - std::string* file_data_; - bool use_direct_io_; - bool no_flush_; - size_t size_ = 0; - }; - - Random r(301); - const int kNumAttempts = 50; - for (int attempt = 0; attempt < kNumAttempts; attempt++) { - bool no_flush = (attempt % 3 == 0); - EnvOptions env_options; - env_options.writable_file_max_buffer_size = - (attempt < kNumAttempts / 2) ? 512 * 1024 : 700 * 1024; - std::string actual; - std::unique_ptr wf(new FakeWF(&actual, - attempt % 2 == 1, - no_flush)); - std::unique_ptr writer(new WritableFileWriter( - std::move(wf), "" /* don't care */, env_options)); - - std::string target; - for (int i = 0; i < 20; i++) { - uint32_t num = r.Skewed(16) * 100 + r.Uniform(100); - std::string random_string = r.RandomString(num); - ASSERT_OK(writer->Append(Slice(random_string.c_str(), num))); - target.append(random_string.c_str(), num); - - // In some attempts, flush in a chance of 1/10. - if (!no_flush && r.Uniform(10) == 0) { - ASSERT_OK(writer->Flush()); - } - } - ASSERT_OK(writer->Flush()); - ASSERT_OK(writer->Close()); - ASSERT_EQ(target.size(), actual.size()); - ASSERT_EQ(target, actual); - } -} - -TEST_F(WritableFileWriterTest, BufferWithZeroCapacityDirectIO) { - EnvOptions env_opts; - env_opts.use_direct_writes = true; - env_opts.writable_file_max_buffer_size = 0; - { - std::unique_ptr writer; - const Status s = - WritableFileWriter::Create(FileSystem::Default(), /*fname=*/"dont_care", - FileOptions(env_opts), &writer, - /*dbg=*/nullptr); - ASSERT_TRUE(s.IsInvalidArgument()); - } -} - -class DBWritableFileWriterTest : public DBTestBase { - public: - DBWritableFileWriterTest() - : DBTestBase("db_secondary_cache_test", /*env_do_fsync=*/true) { - fault_fs_.reset(new FaultInjectionTestFS(env_->GetFileSystem())); - fault_env_.reset(new CompositeEnvWrapper(env_, fault_fs_)); - } - - std::shared_ptr fault_fs_; - std::unique_ptr fault_env_; -}; - -TEST_F(DBWritableFileWriterTest, AppendWithChecksum) { - FileOptions file_options = FileOptions(); - Options options = GetDefaultOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - std::string fname = dbname_ + "/test_file"; - std::unique_ptr writable_file_ptr; - ASSERT_OK(fault_fs_->NewWritableFile(fname, file_options, &writable_file_ptr, - /*dbg*/ nullptr)); - std::unique_ptr file; - file.reset(new TestFSWritableFile( - fname, file_options, std::move(writable_file_ptr), fault_fs_.get())); - std::unique_ptr file_writer; - ImmutableOptions ioptions(options); - file_writer.reset(new WritableFileWriter( - std::move(file), fname, file_options, SystemClock::Default().get(), - nullptr, ioptions.stats, ioptions.listeners, - ioptions.file_checksum_gen_factory.get(), true, true)); - - Random rnd(301); - std::string data = rnd.RandomString(1000); - uint32_t data_crc32c = crc32c::Value(data.c_str(), data.size()); - fault_fs_->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - - ASSERT_OK(file_writer->Append(Slice(data.c_str()), data_crc32c)); - ASSERT_OK(file_writer->Flush()); - Random size_r(47); - for (int i = 0; i < 2000; i++) { - data = rnd.RandomString((static_cast(size_r.Next()) % 10000)); - data_crc32c = crc32c::Value(data.c_str(), data.size()); - ASSERT_OK(file_writer->Append(Slice(data.c_str()), data_crc32c)); - - data = rnd.RandomString((static_cast(size_r.Next()) % 97)); - ASSERT_OK(file_writer->Append(Slice(data.c_str()))); - ASSERT_OK(file_writer->Flush()); - } - ASSERT_OK(file_writer->Close()); - Destroy(options); -} - -TEST_F(DBWritableFileWriterTest, AppendVerifyNoChecksum) { - FileOptions file_options = FileOptions(); - Options options = GetDefaultOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - std::string fname = dbname_ + "/test_file"; - std::unique_ptr writable_file_ptr; - ASSERT_OK(fault_fs_->NewWritableFile(fname, file_options, &writable_file_ptr, - /*dbg*/ nullptr)); - std::unique_ptr file; - file.reset(new TestFSWritableFile( - fname, file_options, std::move(writable_file_ptr), fault_fs_.get())); - std::unique_ptr file_writer; - ImmutableOptions ioptions(options); - // Enable checksum handoff for this file, but do not enable buffer checksum. - // So Append with checksum logic will not be triggered - file_writer.reset(new WritableFileWriter( - std::move(file), fname, file_options, SystemClock::Default().get(), - nullptr, ioptions.stats, ioptions.listeners, - ioptions.file_checksum_gen_factory.get(), true, false)); - - Random rnd(301); - std::string data = rnd.RandomString(1000); - uint32_t data_crc32c = crc32c::Value(data.c_str(), data.size()); - fault_fs_->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - - ASSERT_OK(file_writer->Append(Slice(data.c_str()), data_crc32c)); - ASSERT_OK(file_writer->Flush()); - Random size_r(47); - for (int i = 0; i < 1000; i++) { - data = rnd.RandomString((static_cast(size_r.Next()) % 10000)); - data_crc32c = crc32c::Value(data.c_str(), data.size()); - ASSERT_OK(file_writer->Append(Slice(data.c_str()), data_crc32c)); - - data = rnd.RandomString((static_cast(size_r.Next()) % 97)); - ASSERT_OK(file_writer->Append(Slice(data.c_str()))); - ASSERT_OK(file_writer->Flush()); - } - ASSERT_OK(file_writer->Close()); - Destroy(options); -} - -TEST_F(DBWritableFileWriterTest, AppendWithChecksumRateLimiter) { - FileOptions file_options = FileOptions(); - file_options.rate_limiter = nullptr; - Options options = GetDefaultOptions(); - options.create_if_missing = true; - DestroyAndReopen(options); - std::string fname = dbname_ + "/test_file"; - std::unique_ptr writable_file_ptr; - ASSERT_OK(fault_fs_->NewWritableFile(fname, file_options, &writable_file_ptr, - /*dbg*/ nullptr)); - std::unique_ptr file; - file.reset(new TestFSWritableFile( - fname, file_options, std::move(writable_file_ptr), fault_fs_.get())); - std::unique_ptr file_writer; - ImmutableOptions ioptions(options); - // Enable checksum handoff for this file, but do not enable buffer checksum. - // So Append with checksum logic will not be triggered - file_writer.reset(new WritableFileWriter( - std::move(file), fname, file_options, SystemClock::Default().get(), - nullptr, ioptions.stats, ioptions.listeners, - ioptions.file_checksum_gen_factory.get(), true, true)); - fault_fs_->SetChecksumHandoffFuncType(ChecksumType::kCRC32c); - - Random rnd(301); - std::string data; - uint32_t data_crc32c; - uint64_t start = fault_env_->NowMicros(); - Random size_r(47); - uint64_t bytes_written = 0; - for (int i = 0; i < 100; i++) { - data = rnd.RandomString((static_cast(size_r.Next()) % 10000)); - data_crc32c = crc32c::Value(data.c_str(), data.size()); - ASSERT_OK(file_writer->Append(Slice(data.c_str()), data_crc32c)); - bytes_written += static_cast(data.size()); - - data = rnd.RandomString((static_cast(size_r.Next()) % 97)); - ASSERT_OK(file_writer->Append(Slice(data.c_str()))); - ASSERT_OK(file_writer->Flush()); - bytes_written += static_cast(data.size()); - } - uint64_t elapsed = fault_env_->NowMicros() - start; - double raw_rate = bytes_written * 1000000.0 / elapsed; - ASSERT_OK(file_writer->Close()); - - // Set the rate-limiter - FileOptions file_options1 = FileOptions(); - file_options1.rate_limiter = - NewGenericRateLimiter(static_cast(0.5 * raw_rate)); - fname = dbname_ + "/test_file_1"; - std::unique_ptr writable_file_ptr1; - ASSERT_OK(fault_fs_->NewWritableFile(fname, file_options1, - &writable_file_ptr1, - /*dbg*/ nullptr)); - file.reset(new TestFSWritableFile( - fname, file_options1, std::move(writable_file_ptr1), fault_fs_.get())); - // Enable checksum handoff for this file, but do not enable buffer checksum. - // So Append with checksum logic will not be triggered - file_writer.reset(new WritableFileWriter( - std::move(file), fname, file_options1, SystemClock::Default().get(), - nullptr, ioptions.stats, ioptions.listeners, - ioptions.file_checksum_gen_factory.get(), true, true)); - - for (int i = 0; i < 1000; i++) { - data = rnd.RandomString((static_cast(size_r.Next()) % 10000)); - data_crc32c = crc32c::Value(data.c_str(), data.size()); - ASSERT_OK(file_writer->Append(Slice(data.c_str()), data_crc32c)); - - data = rnd.RandomString((static_cast(size_r.Next()) % 97)); - ASSERT_OK(file_writer->Append(Slice(data.c_str()))); - ASSERT_OK(file_writer->Flush()); - } - ASSERT_OK(file_writer->Close()); - if (file_options1.rate_limiter != nullptr) { - delete file_options1.rate_limiter; - } - - Destroy(options); -} - -TEST_F(WritableFileWriterTest, AppendStatusReturn) { - class FakeWF : public FSWritableFile { - public: - explicit FakeWF() : use_direct_io_(false), io_error_(false) {} - - bool use_direct_io() const override { return use_direct_io_; } - - using FSWritableFile::Append; - IOStatus Append(const Slice& /*data*/, const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - if (io_error_) { - return IOStatus::IOError("Fake IO error"); - } - return IOStatus::OK(); - } - using FSWritableFile::PositionedAppend; - IOStatus PositionedAppend(const Slice& /*data*/, uint64_t, - const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - if (io_error_) { - return IOStatus::IOError("Fake IO error"); - } - return IOStatus::OK(); - } - IOStatus Close(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Flush(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Sync(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - void Setuse_direct_io(bool val) { use_direct_io_ = val; } - void SetIOError(bool val) { io_error_ = val; } - - protected: - bool use_direct_io_; - bool io_error_; - }; - std::unique_ptr wf(new FakeWF()); - wf->Setuse_direct_io(true); - std::unique_ptr writer( - new WritableFileWriter(std::move(wf), "" /* don't care */, EnvOptions())); - - ASSERT_OK(writer->Append(std::string(2 * kMb, 'a'))); - - // Next call to WritableFile::Append() should fail - FakeWF* fwf = static_cast(writer->writable_file()); - fwf->SetIOError(true); - ASSERT_NOK(writer->Append(std::string(2 * kMb, 'b'))); -} - -class ReadaheadRandomAccessFileTest - : public testing::Test, - public testing::WithParamInterface { - public: - static std::vector GetReadaheadSizeList() { - return {1lu << 12, 1lu << 16}; - } - void SetUp() override { - readahead_size_ = GetParam(); - scratch_.reset(new char[2 * readahead_size_]); - ResetSourceStr(); - } - ReadaheadRandomAccessFileTest() : control_contents_() {} - std::string Read(uint64_t offset, size_t n) { - Slice result; - Status s = test_read_holder_->Read(offset, n, IOOptions(), &result, - scratch_.get(), nullptr); - EXPECT_TRUE(s.ok() || s.IsInvalidArgument()); - return std::string(result.data(), result.size()); - } - void ResetSourceStr(const std::string& str = "") { - std::unique_ptr sink( - new test::StringSink(&control_contents_)); - std::unique_ptr write_holder(new WritableFileWriter( - std::move(sink), "" /* don't care */, FileOptions())); - Status s = write_holder->Append(Slice(str)); - EXPECT_OK(s); - s = write_holder->Flush(); - EXPECT_OK(s); - std::unique_ptr read_holder( - new test::StringSource(control_contents_)); - test_read_holder_ = - NewReadaheadRandomAccessFile(std::move(read_holder), readahead_size_); - } - size_t GetReadaheadSize() const { return readahead_size_; } - - private: - size_t readahead_size_; - Slice control_contents_; - std::unique_ptr test_read_holder_; - std::unique_ptr scratch_; -}; - -TEST_P(ReadaheadRandomAccessFileTest, EmptySourceStr) { - ASSERT_EQ("", Read(0, 1)); - ASSERT_EQ("", Read(0, 0)); - ASSERT_EQ("", Read(13, 13)); -} - -TEST_P(ReadaheadRandomAccessFileTest, SourceStrLenLessThanReadaheadSize) { - std::string str = "abcdefghijklmnopqrs"; - ResetSourceStr(str); - ASSERT_EQ(str.substr(3, 4), Read(3, 4)); - ASSERT_EQ(str.substr(0, 3), Read(0, 3)); - ASSERT_EQ(str, Read(0, str.size())); - ASSERT_EQ(str.substr(7, std::min(static_cast(str.size()) - 7, 30)), - Read(7, 30)); - ASSERT_EQ("", Read(100, 100)); -} - -TEST_P(ReadaheadRandomAccessFileTest, SourceStrLenGreaterThanReadaheadSize) { - Random rng(42); - for (int k = 0; k < 100; ++k) { - size_t strLen = k * GetReadaheadSize() + - rng.Uniform(static_cast(GetReadaheadSize())); - std::string str = rng.HumanReadableString(static_cast(strLen)); - ResetSourceStr(str); - for (int test = 1; test <= 100; ++test) { - size_t offset = rng.Uniform(static_cast(strLen)); - size_t n = rng.Uniform(static_cast(GetReadaheadSize())); - ASSERT_EQ(str.substr(offset, std::min(n, strLen - offset)), - Read(offset, n)); - } - } -} - -TEST_P(ReadaheadRandomAccessFileTest, ReadExceedsReadaheadSize) { - Random rng(7); - size_t strLen = 4 * GetReadaheadSize() + - rng.Uniform(static_cast(GetReadaheadSize())); - std::string str = rng.HumanReadableString(static_cast(strLen)); - ResetSourceStr(str); - for (int test = 1; test <= 100; ++test) { - size_t offset = rng.Uniform(static_cast(strLen)); - size_t n = - GetReadaheadSize() + rng.Uniform(static_cast(GetReadaheadSize())); - ASSERT_EQ(str.substr(offset, std::min(n, strLen - offset)), - Read(offset, n)); - } -} - -INSTANTIATE_TEST_CASE_P( - EmptySourceStr, ReadaheadRandomAccessFileTest, - ::testing::ValuesIn(ReadaheadRandomAccessFileTest::GetReadaheadSizeList())); -INSTANTIATE_TEST_CASE_P( - SourceStrLenLessThanReadaheadSize, ReadaheadRandomAccessFileTest, - ::testing::ValuesIn(ReadaheadRandomAccessFileTest::GetReadaheadSizeList())); -INSTANTIATE_TEST_CASE_P( - SourceStrLenGreaterThanReadaheadSize, ReadaheadRandomAccessFileTest, - ::testing::ValuesIn(ReadaheadRandomAccessFileTest::GetReadaheadSizeList())); -INSTANTIATE_TEST_CASE_P( - ReadExceedsReadaheadSize, ReadaheadRandomAccessFileTest, - ::testing::ValuesIn(ReadaheadRandomAccessFileTest::GetReadaheadSizeList())); - -class ReadaheadSequentialFileTest : public testing::Test, - public testing::WithParamInterface { - public: - static std::vector GetReadaheadSizeList() { - return {1lu << 8, 1lu << 12, 1lu << 16, 1lu << 18}; - } - void SetUp() override { - readahead_size_ = GetParam(); - scratch_.reset(new char[2 * readahead_size_]); - ResetSourceStr(); - } - ReadaheadSequentialFileTest() {} - std::string Read(size_t n) { - Slice result; - Status s = test_read_holder_->Read( - n, &result, scratch_.get(), Env::IO_TOTAL /* rate_limiter_priority*/); - EXPECT_TRUE(s.ok() || s.IsInvalidArgument()); - return std::string(result.data(), result.size()); - } - void Skip(size_t n) { test_read_holder_->Skip(n); } - void ResetSourceStr(const std::string& str = "") { - auto read_holder = std::unique_ptr( - new test::SeqStringSource(str, &seq_read_count_)); - test_read_holder_.reset(new SequentialFileReader(std::move(read_holder), - "test", readahead_size_)); - } - size_t GetReadaheadSize() const { return readahead_size_; } - - private: - size_t readahead_size_; - std::unique_ptr test_read_holder_; - std::unique_ptr scratch_; - std::atomic seq_read_count_; -}; - -TEST_P(ReadaheadSequentialFileTest, EmptySourceStr) { - ASSERT_EQ("", Read(0)); - ASSERT_EQ("", Read(1)); - ASSERT_EQ("", Read(13)); -} - -TEST_P(ReadaheadSequentialFileTest, SourceStrLenLessThanReadaheadSize) { - std::string str = "abcdefghijklmnopqrs"; - ResetSourceStr(str); - ASSERT_EQ(str.substr(0, 3), Read(3)); - ASSERT_EQ(str.substr(3, 1), Read(1)); - ASSERT_EQ(str.substr(4), Read(str.size())); - ASSERT_EQ("", Read(100)); -} - -TEST_P(ReadaheadSequentialFileTest, SourceStrLenGreaterThanReadaheadSize) { - Random rng(42); - for (int s = 0; s < 1; ++s) { - for (int k = 0; k < 100; ++k) { - size_t strLen = k * GetReadaheadSize() + - rng.Uniform(static_cast(GetReadaheadSize())); - std::string str = rng.HumanReadableString(static_cast(strLen)); - ResetSourceStr(str); - size_t offset = 0; - for (int test = 1; test <= 100; ++test) { - size_t n = rng.Uniform(static_cast(GetReadaheadSize())); - if (s && test % 2) { - Skip(n); - } else { - ASSERT_EQ(str.substr(offset, std::min(n, strLen - offset)), Read(n)); - } - offset = std::min(offset + n, strLen); - } - } - } -} - -TEST_P(ReadaheadSequentialFileTest, ReadExceedsReadaheadSize) { - Random rng(42); - for (int s = 0; s < 1; ++s) { - for (int k = 0; k < 100; ++k) { - size_t strLen = k * GetReadaheadSize() + - rng.Uniform(static_cast(GetReadaheadSize())); - std::string str = rng.HumanReadableString(static_cast(strLen)); - ResetSourceStr(str); - size_t offset = 0; - for (int test = 1; test <= 100; ++test) { - size_t n = GetReadaheadSize() + - rng.Uniform(static_cast(GetReadaheadSize())); - if (s && test % 2) { - Skip(n); - } else { - ASSERT_EQ(str.substr(offset, std::min(n, strLen - offset)), Read(n)); - } - offset = std::min(offset + n, strLen); - } - } - } -} - -INSTANTIATE_TEST_CASE_P( - EmptySourceStr, ReadaheadSequentialFileTest, - ::testing::ValuesIn(ReadaheadSequentialFileTest::GetReadaheadSizeList())); -INSTANTIATE_TEST_CASE_P( - SourceStrLenLessThanReadaheadSize, ReadaheadSequentialFileTest, - ::testing::ValuesIn(ReadaheadSequentialFileTest::GetReadaheadSizeList())); -INSTANTIATE_TEST_CASE_P( - SourceStrLenGreaterThanReadaheadSize, ReadaheadSequentialFileTest, - ::testing::ValuesIn(ReadaheadSequentialFileTest::GetReadaheadSizeList())); -INSTANTIATE_TEST_CASE_P( - ReadExceedsReadaheadSize, ReadaheadSequentialFileTest, - ::testing::ValuesIn(ReadaheadSequentialFileTest::GetReadaheadSizeList())); - -namespace { -std::string GenerateLine(int n) { - std::string rv; - // Multiples of 17 characters per line, for likely bad buffer alignment - for (int i = 0; i < n; ++i) { - rv.push_back(static_cast('0' + (i % 10))); - rv.append("xxxxxxxxxxxxxxxx"); - } - return rv; -} -} // namespace - -TEST(LineFileReaderTest, LineFileReaderTest) { - const int nlines = 1000; - - std::unique_ptr mem_env(MockEnv::Create(Env::Default())); - std::shared_ptr fs = mem_env->GetFileSystem(); - // Create an input file - { - std::unique_ptr file; - ASSERT_OK( - fs->NewWritableFile("testfile", FileOptions(), &file, /*dbg*/ nullptr)); - - for (int i = 0; i < nlines; ++i) { - std::string line = GenerateLine(i); - line.push_back('\n'); - ASSERT_OK(file->Append(line, IOOptions(), /*dbg*/ nullptr)); - } - } - - // Verify with no I/O errors - { - std::unique_ptr reader; - ASSERT_OK(LineFileReader::Create(fs, "testfile", FileOptions(), &reader, - nullptr /* dbg */, - nullptr /* rate_limiter */)); - std::string line; - int count = 0; - while (reader->ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)) { - ASSERT_EQ(line, GenerateLine(count)); - ++count; - ASSERT_EQ(static_cast(reader->GetLineNumber()), count); - } - ASSERT_OK(reader->GetStatus()); - ASSERT_EQ(count, nlines); - ASSERT_EQ(static_cast(reader->GetLineNumber()), count); - // And still - ASSERT_FALSE( - reader->ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)); - ASSERT_OK(reader->GetStatus()); - ASSERT_EQ(static_cast(reader->GetLineNumber()), count); - } - - // Verify with injected I/O error - { - std::unique_ptr reader; - ASSERT_OK(LineFileReader::Create(fs, "testfile", FileOptions(), &reader, - nullptr /* dbg */, - nullptr /* rate_limiter */)); - std::string line; - int count = 0; - // Read part way through the file - while (count < nlines / 4) { - ASSERT_TRUE( - reader->ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)); - ASSERT_EQ(line, GenerateLine(count)); - ++count; - ASSERT_EQ(static_cast(reader->GetLineNumber()), count); - } - ASSERT_OK(reader->GetStatus()); - - // Inject error - int callback_count = 0; - SyncPoint::GetInstance()->SetCallBack( - "MemFile::Read:IOStatus", [&](void* arg) { - IOStatus* status = static_cast(arg); - *status = IOStatus::Corruption("test"); - ++callback_count; - }); - SyncPoint::GetInstance()->EnableProcessing(); - - while (reader->ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)) { - ASSERT_EQ(line, GenerateLine(count)); - ++count; - ASSERT_EQ(static_cast(reader->GetLineNumber()), count); - } - ASSERT_TRUE(reader->GetStatus().IsCorruption()); - ASSERT_LT(count, nlines / 2); - ASSERT_EQ(callback_count, 1); - - // Still get error & no retry - ASSERT_FALSE( - reader->ReadLine(&line, Env::IO_TOTAL /* rate_limiter_priority */)); - ASSERT_TRUE(reader->GetStatus().IsCorruption()); - ASSERT_EQ(callback_count, 1); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } -} - -class IOErrorEventListener : public EventListener { - public: - IOErrorEventListener() { notify_error_.store(0); } - - void OnIOError(const IOErrorInfo& io_error_info) override { - notify_error_++; - EXPECT_FALSE(io_error_info.file_path.empty()); - EXPECT_FALSE(io_error_info.io_status.ok()); - } - - size_t NotifyErrorCount() { return notify_error_; } - - bool ShouldBeNotifiedOnFileIO() override { return true; } - - private: - std::atomic notify_error_; -}; - -TEST_F(DBWritableFileWriterTest, IOErrorNotification) { - class FakeWF : public FSWritableFile { - public: - explicit FakeWF() : io_error_(false) { - file_append_errors_.store(0); - file_flush_errors_.store(0); - } - - using FSWritableFile::Append; - IOStatus Append(const Slice& /*data*/, const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - if (io_error_) { - file_append_errors_++; - return IOStatus::IOError("Fake IO error"); - } - return IOStatus::OK(); - } - - using FSWritableFile::PositionedAppend; - IOStatus PositionedAppend(const Slice& /*data*/, uint64_t, - const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - if (io_error_) { - return IOStatus::IOError("Fake IO error"); - } - return IOStatus::OK(); - } - IOStatus Close(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - IOStatus Flush(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - if (io_error_) { - file_flush_errors_++; - return IOStatus::IOError("Fake IO error"); - } - return IOStatus::OK(); - } - IOStatus Sync(const IOOptions& /*options*/, - IODebugContext* /*dbg*/) override { - return IOStatus::OK(); - } - - void SetIOError(bool val) { io_error_ = val; } - - void CheckCounters(int file_append_errors, int file_flush_errors) { - ASSERT_EQ(file_append_errors, file_append_errors_); - ASSERT_EQ(file_flush_errors_, file_flush_errors); - } - - protected: - bool io_error_; - std::atomic file_append_errors_; - std::atomic file_flush_errors_; - }; - - FileOptions file_options = FileOptions(); - Options options = GetDefaultOptions(); - options.create_if_missing = true; - IOErrorEventListener* listener = new IOErrorEventListener(); - options.listeners.emplace_back(listener); - - DestroyAndReopen(options); - ImmutableOptions ioptions(options); - - std::string fname = dbname_ + "/test_file"; - std::unique_ptr writable_file_ptr(new FakeWF); - - std::unique_ptr file_writer; - writable_file_ptr->SetIOError(true); - - file_writer.reset(new WritableFileWriter( - std::move(writable_file_ptr), fname, file_options, - SystemClock::Default().get(), nullptr, ioptions.stats, ioptions.listeners, - ioptions.file_checksum_gen_factory.get(), true, true)); - - FakeWF* fwf = static_cast(file_writer->writable_file()); - - fwf->SetIOError(true); - ASSERT_NOK(file_writer->Append(std::string(2 * kMb, 'a'))); - fwf->CheckCounters(1, 0); - ASSERT_EQ(listener->NotifyErrorCount(), 1); - - file_writer->reset_seen_error(); - fwf->SetIOError(true); - ASSERT_NOK(file_writer->Flush()); - fwf->CheckCounters(1, 1); - ASSERT_EQ(listener->NotifyErrorCount(), 2); - - /* No error generation */ - file_writer->reset_seen_error(); - fwf->SetIOError(false); - ASSERT_OK(file_writer->Append(std::string(2 * kMb, 'b'))); - ASSERT_EQ(listener->NotifyErrorCount(), 2); - fwf->CheckCounters(1, 1); -} - -class WritableFileWriterIOPriorityTest : public testing::Test { - protected: - // This test is to check whether the rate limiter priority can be passed - // correctly from WritableFileWriter functions to FSWritableFile functions. - - void SetUp() override { - // When op_rate_limiter_priority parameter in WritableFileWriter functions - // is the default (Env::IO_TOTAL). - std::unique_ptr wf{new FakeWF(Env::IO_HIGH)}; - FileOptions file_options; - writer_.reset(new WritableFileWriter(std::move(wf), "" /* don't care */, - file_options)); - } - - class FakeWF : public FSWritableFile { - public: - explicit FakeWF(Env::IOPriority io_priority) { SetIOPriority(io_priority); } - ~FakeWF() override {} - - IOStatus Append(const Slice& /*data*/, const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus Append(const Slice& data, const IOOptions& options, - const DataVerificationInfo& /* verification_info */, - IODebugContext* dbg) override { - return Append(data, options, dbg); - } - IOStatus PositionedAppend(const Slice& /*data*/, uint64_t /*offset*/, - const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus PositionedAppend( - const Slice& /* data */, uint64_t /* offset */, - const IOOptions& options, - const DataVerificationInfo& /* verification_info */, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus Truncate(uint64_t /*size*/, const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus Close(const IOOptions& options, IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus Flush(const IOOptions& options, IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus Sync(const IOOptions& options, IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus Fsync(const IOOptions& options, IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - uint64_t GetFileSize(const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return 0; - } - void GetPreallocationStatus(size_t* /*block_size*/, - size_t* /*last_allocated_block*/) override {} - size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const override { - return 0; - } - IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) override { - return IOStatus::OK(); - } - - IOStatus Allocate(uint64_t /*offset*/, uint64_t /*len*/, - const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - IOStatus RangeSync(uint64_t /*offset*/, uint64_t /*nbytes*/, - const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - return IOStatus::OK(); - } - - void PrepareWrite(size_t /*offset*/, size_t /*len*/, - const IOOptions& options, - IODebugContext* /*dbg*/) override { - EXPECT_EQ(options.rate_limiter_priority, io_priority_); - } - - bool IsSyncThreadSafe() const override { return true; } - }; - - std::unique_ptr writer_; -}; - -TEST_F(WritableFileWriterIOPriorityTest, Append) { - ASSERT_OK(writer_->Append(Slice("abc"))); -} - -TEST_F(WritableFileWriterIOPriorityTest, Pad) { ASSERT_OK(writer_->Pad(500)); } - -TEST_F(WritableFileWriterIOPriorityTest, Flush) { ASSERT_OK(writer_->Flush()); } - -TEST_F(WritableFileWriterIOPriorityTest, Close) { ASSERT_OK(writer_->Close()); } - -TEST_F(WritableFileWriterIOPriorityTest, Sync) { - ASSERT_OK(writer_->Sync(false)); - ASSERT_OK(writer_->Sync(true)); -} - -TEST_F(WritableFileWriterIOPriorityTest, SyncWithoutFlush) { - ASSERT_OK(writer_->SyncWithoutFlush(false)); - ASSERT_OK(writer_->SyncWithoutFlush(true)); -} - -TEST_F(WritableFileWriterIOPriorityTest, BasicOp) { - EnvOptions env_options; - env_options.bytes_per_sync = kMb; - std::unique_ptr wf(new FakeWF(Env::IO_HIGH)); - std::unique_ptr writer( - new WritableFileWriter(std::move(wf), "" /* don't care */, env_options)); - Random r(301); - Status s; - std::unique_ptr large_buf(new char[10 * kMb]); - for (int i = 0; i < 1000; i++) { - int skew_limit = (i < 700) ? 10 : 15; - uint32_t num = r.Skewed(skew_limit) * 100 + r.Uniform(100); - s = writer->Append(Slice(large_buf.get(), num)); - ASSERT_OK(s); - - // Flush in a chance of 1/10. - if (r.Uniform(10) == 0) { - s = writer->Flush(); - ASSERT_OK(s); - } - } - s = writer->Close(); - ASSERT_OK(s); -} -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/filelock_test.cc b/util/filelock_test.cc deleted file mode 100644 index 69947a732..000000000 --- a/util/filelock_test.cc +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -#include - -#include "rocksdb/env.h" -#include "rocksdb/status.h" -#ifdef __FreeBSD__ -#include -#include -#endif -#include - -#include "test_util/testharness.h" -#include "util/coding.h" -#include "util/string_util.h" - -namespace ROCKSDB_NAMESPACE { - -class LockTest : public testing::Test { - public: - static LockTest* current_; - std::string file_; - ROCKSDB_NAMESPACE::Env* env_; - - LockTest() - : file_(test::PerThreadDBPath("db_testlock_file")), - env_(ROCKSDB_NAMESPACE::Env::Default()) { - current_ = this; - } - - ~LockTest() override {} - - Status LockFile(FileLock** db_lock) { return env_->LockFile(file_, db_lock); } - - Status UnlockFile(FileLock* db_lock) { return env_->UnlockFile(db_lock); } - - bool AssertFileIsLocked() { - return CheckFileLock(/* lock_expected = */ true); - } - - bool AssertFileIsNotLocked() { - return CheckFileLock(/* lock_expected = */ false); - } - - bool CheckFileLock(bool lock_expected) { - // We need to fork to check the fcntl lock as we need - // to open and close the file from a different process - // to avoid either releasing the lock on close, or not - // contending for it when requesting a lock. - -#ifdef OS_WIN - - // WaitForSingleObject and GetExitCodeProcess can do what waitpid does. - // TODO - implement on Windows - return true; - -#else - - pid_t pid = fork(); - if (0 == pid) { - // child process - int exit_val = EXIT_FAILURE; - int fd = open(file_.c_str(), O_RDWR | O_CREAT, 0644); - if (fd < 0) { - // could not open file, could not check if it was locked - fprintf(stderr, "Open on on file %s failed.\n", file_.c_str()); - exit(exit_val); - } - - struct flock f; - memset(&f, 0, sizeof(f)); - f.l_type = (F_WRLCK); - f.l_whence = SEEK_SET; - f.l_start = 0; - f.l_len = 0; // Lock/unlock entire file - int value = fcntl(fd, F_SETLK, &f); - if (value == -1) { - if (lock_expected) { - exit_val = EXIT_SUCCESS; - } - } else { - if (!lock_expected) { - exit_val = EXIT_SUCCESS; - } - } - close(fd); // lock is released for child process - exit(exit_val); - } else if (pid > 0) { - // parent process - int status; - while (-1 == waitpid(pid, &status, 0)) - ; - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - // child process exited with non success status - return false; - } else { - return true; - } - } else { - fprintf(stderr, "Fork failed\n"); - return false; - } - return false; - -#endif - } -}; -LockTest* LockTest::current_; - -TEST_F(LockTest, LockBySameThread) { - FileLock* lock1; - FileLock* lock2; - - // acquire a lock on a file - ASSERT_OK(LockFile(&lock1)); - - // check the file is locked - ASSERT_TRUE(AssertFileIsLocked()); - - // re-acquire the lock on the same file. This should fail. - Status s = LockFile(&lock2); - ASSERT_TRUE(s.IsIOError()); -#ifndef OS_WIN - // Validate that error message contains current thread ID. - ASSERT_TRUE(s.ToString().find(std::to_string( - Env::Default()->GetThreadID())) != std::string::npos); -#endif - - // check the file is locked - ASSERT_TRUE(AssertFileIsLocked()); - - // release the lock - ASSERT_OK(UnlockFile(lock1)); - - // check the file is not locked - ASSERT_TRUE(AssertFileIsNotLocked()); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/hash_test.cc b/util/hash_test.cc deleted file mode 100644 index 72112b044..000000000 --- a/util/hash_test.cc +++ /dev/null @@ -1,853 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2012 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "util/hash.h" - -#include -#include -#include - -#include "test_util/testharness.h" -#include "util/coding.h" -#include "util/coding_lean.h" -#include "util/hash128.h" -#include "util/math.h" -#include "util/math128.h" - -using ROCKSDB_NAMESPACE::BijectiveHash2x64; -using ROCKSDB_NAMESPACE::BijectiveUnhash2x64; -using ROCKSDB_NAMESPACE::DecodeFixed64; -using ROCKSDB_NAMESPACE::EncodeFixed32; -using ROCKSDB_NAMESPACE::EndianSwapValue; -using ROCKSDB_NAMESPACE::GetSliceHash64; -using ROCKSDB_NAMESPACE::Hash; -using ROCKSDB_NAMESPACE::Hash128; -using ROCKSDB_NAMESPACE::Hash2x64; -using ROCKSDB_NAMESPACE::Hash64; -using ROCKSDB_NAMESPACE::Lower32of64; -using ROCKSDB_NAMESPACE::Lower64of128; -using ROCKSDB_NAMESPACE::ReverseBits; -using ROCKSDB_NAMESPACE::Slice; -using ROCKSDB_NAMESPACE::Unsigned128; -using ROCKSDB_NAMESPACE::Upper32of64; -using ROCKSDB_NAMESPACE::Upper64of128; - -// The hash algorithm is part of the file format, for example for the Bloom -// filters. Test that the hash values are stable for a set of random strings of -// varying lengths. -TEST(HashTest, Values) { - constexpr uint32_t kSeed = 0xbc9f1d34; // Same as BloomHash. - - EXPECT_EQ(Hash("", 0, kSeed), 3164544308u); - EXPECT_EQ(Hash("\x08", 1, kSeed), 422599524u); - EXPECT_EQ(Hash("\x17", 1, kSeed), 3168152998u); - EXPECT_EQ(Hash("\x9a", 1, kSeed), 3195034349u); - EXPECT_EQ(Hash("\x1c", 1, kSeed), 2651681383u); - EXPECT_EQ(Hash("\x4d\x76", 2, kSeed), 2447836956u); - EXPECT_EQ(Hash("\x52\xd5", 2, kSeed), 3854228105u); - EXPECT_EQ(Hash("\x91\xf7", 2, kSeed), 31066776u); - EXPECT_EQ(Hash("\xd6\x27", 2, kSeed), 1806091603u); - EXPECT_EQ(Hash("\x30\x46\x0b", 3, kSeed), 3808221797u); - EXPECT_EQ(Hash("\x56\xdc\xd6", 3, kSeed), 2157698265u); - EXPECT_EQ(Hash("\xd4\x52\x33", 3, kSeed), 1721992661u); - EXPECT_EQ(Hash("\x6a\xb5\xf4", 3, kSeed), 2469105222u); - EXPECT_EQ(Hash("\x67\x53\x81\x1c", 4, kSeed), 118283265u); - EXPECT_EQ(Hash("\x69\xb8\xc0\x88", 4, kSeed), 3416318611u); - EXPECT_EQ(Hash("\x1e\x84\xaf\x2d", 4, kSeed), 3315003572u); - EXPECT_EQ(Hash("\x46\xdc\x54\xbe", 4, kSeed), 447346355u); - EXPECT_EQ(Hash("\xd0\x7a\x6e\xea\x56", 5, kSeed), 4255445370u); - EXPECT_EQ(Hash("\x86\x83\xd5\xa4\xd8", 5, kSeed), 2390603402u); - EXPECT_EQ(Hash("\xb7\x46\xbb\x77\xce", 5, kSeed), 2048907743u); - EXPECT_EQ(Hash("\x6c\xa8\xbc\xe5\x99", 5, kSeed), 2177978500u); - EXPECT_EQ(Hash("\x5c\x5e\xe1\xa0\x73\x81", 6, kSeed), 1036846008u); - EXPECT_EQ(Hash("\x08\x5d\x73\x1c\xe5\x2e", 6, kSeed), 229980482u); - EXPECT_EQ(Hash("\x42\xfb\xf2\x52\xb4\x10", 6, kSeed), 3655585422u); - EXPECT_EQ(Hash("\x73\xe1\xff\x56\x9c\xce", 6, kSeed), 3502708029u); - EXPECT_EQ(Hash("\x5c\xbe\x97\x75\x54\x9a\x52", 7, kSeed), 815120748u); - EXPECT_EQ(Hash("\x16\x82\x39\x49\x88\x2b\x36", 7, kSeed), 3056033698u); - EXPECT_EQ(Hash("\x59\x77\xf0\xa7\x24\xf4\x78", 7, kSeed), 587205227u); - EXPECT_EQ(Hash("\xd3\xa5\x7c\x0e\xc0\x02\x07", 7, kSeed), 2030937252u); - EXPECT_EQ(Hash("\x31\x1b\x98\x75\x96\x22\xd3\x9a", 8, kSeed), 469635402u); - EXPECT_EQ(Hash("\x38\xd6\xf7\x28\x20\xb4\x8a\xe9", 8, kSeed), 3530274698u); - EXPECT_EQ(Hash("\xbb\x18\x5d\xf4\x12\x03\xf7\x99", 8, kSeed), 1974545809u); - EXPECT_EQ(Hash("\x80\xd4\x3b\x3b\xae\x22\xa2\x78", 8, kSeed), 3563570120u); - EXPECT_EQ(Hash("\x1a\xb5\xd0\xfe\xab\xc3\x61\xb2\x99", 9, kSeed), - 2706087434u); - EXPECT_EQ(Hash("\x8e\x4a\xc3\x18\x20\x2f\x06\xe6\x3c", 9, kSeed), - 1534654151u); - EXPECT_EQ(Hash("\xb6\xc0\xdd\x05\x3f\xc4\x86\x4c\xef", 9, kSeed), - 2355554696u); - EXPECT_EQ(Hash("\x9a\x5f\x78\x0d\xaf\x50\xe1\x1f\x55", 9, kSeed), - 1400800912u); - EXPECT_EQ(Hash("\x22\x6f\x39\x1f\xf8\xdd\x4f\x52\x17\x94", 10, kSeed), - 3420325137u); - EXPECT_EQ(Hash("\x32\x89\x2a\x75\x48\x3a\x4a\x02\x69\xdd", 10, kSeed), - 3427803584u); - EXPECT_EQ(Hash("\x06\x92\x5c\xf4\x88\x0e\x7e\x68\x38\x3e", 10, kSeed), - 1152407945u); - EXPECT_EQ(Hash("\xbd\x2c\x63\x38\xbf\xe9\x78\xb7\xbf\x15", 10, kSeed), - 3382479516u); -} - -// The hash algorithm is part of the file format, for example for the Bloom -// filters. -TEST(HashTest, Hash64Misc) { - constexpr uint32_t kSeed = 0; // Same as GetSliceHash64 - - for (char fill : {'\0', 'a', '1', '\xff'}) { - const size_t max_size = 1000; - const std::string str(max_size, fill); - - for (size_t size = 0; size <= max_size; ++size) { - uint64_t here = Hash64(str.data(), size, kSeed); - - // Must be same as unseeded Hash64 and GetSliceHash64 - EXPECT_EQ(here, Hash64(str.data(), size)); - EXPECT_EQ(here, GetSliceHash64(Slice(str.data(), size))); - - // Upper and Lower must reconstruct hash - EXPECT_EQ(here, (uint64_t{Upper32of64(here)} << 32) | Lower32of64(here)); - EXPECT_EQ(here, (uint64_t{Upper32of64(here)} << 32) + Lower32of64(here)); - EXPECT_EQ(here, (uint64_t{Upper32of64(here)} << 32) ^ Lower32of64(here)); - - // Seed changes hash value (with high probability) - for (uint64_t var_seed = 1; var_seed != 0; var_seed <<= 1) { - EXPECT_NE(here, Hash64(str.data(), size, var_seed)); - } - - // Size changes hash value (with high probability) - size_t max_smaller_by = std::min(size_t{30}, size); - for (size_t smaller_by = 1; smaller_by <= max_smaller_by; ++smaller_by) { - EXPECT_NE(here, Hash64(str.data(), size - smaller_by, kSeed)); - } - } - } -} - -// Test that hash values are "non-trivial" for "trivial" inputs -TEST(HashTest, Hash64Trivial) { - // Thorough test too slow for regression testing - constexpr bool thorough = false; - - // For various seeds, make sure hash of empty string is not zero. - constexpr uint64_t max_seed = thorough ? 0x1000000 : 0x10000; - for (uint64_t seed = 0; seed < max_seed; ++seed) { - uint64_t here = Hash64("", 0, seed); - EXPECT_NE(Lower32of64(here), 0u); - EXPECT_NE(Upper32of64(here), 0u); - } - - // For standard seed, make sure hash of small strings are not zero - constexpr uint32_t kSeed = 0; // Same as GetSliceHash64 - char input[4]; - constexpr int max_len = thorough ? 3 : 2; - for (int len = 1; len <= max_len; ++len) { - for (uint32_t i = 0; (i >> (len * 8)) == 0; ++i) { - EncodeFixed32(input, i); - uint64_t here = Hash64(input, len, kSeed); - EXPECT_NE(Lower32of64(here), 0u); - EXPECT_NE(Upper32of64(here), 0u); - } - } -} - -// Test that the hash values are stable for a set of random strings of -// varying small lengths. -TEST(HashTest, Hash64SmallValueSchema) { - constexpr uint32_t kSeed = 0; // Same as GetSliceHash64 - - EXPECT_EQ(Hash64("", 0, kSeed), uint64_t{5999572062939766020u}); - EXPECT_EQ(Hash64("\x08", 1, kSeed), uint64_t{583283813901344696u}); - EXPECT_EQ(Hash64("\x17", 1, kSeed), uint64_t{16175549975585474943u}); - EXPECT_EQ(Hash64("\x9a", 1, kSeed), uint64_t{16322991629225003903u}); - EXPECT_EQ(Hash64("\x1c", 1, kSeed), uint64_t{13269285487706833447u}); - EXPECT_EQ(Hash64("\x4d\x76", 2, kSeed), uint64_t{6859542833406258115u}); - EXPECT_EQ(Hash64("\x52\xd5", 2, kSeed), uint64_t{4919611532550636959u}); - EXPECT_EQ(Hash64("\x91\xf7", 2, kSeed), uint64_t{14199427467559720719u}); - EXPECT_EQ(Hash64("\xd6\x27", 2, kSeed), uint64_t{12292689282614532691u}); - EXPECT_EQ(Hash64("\x30\x46\x0b", 3, kSeed), uint64_t{11404699285340020889u}); - EXPECT_EQ(Hash64("\x56\xdc\xd6", 3, kSeed), uint64_t{12404347133785524237u}); - EXPECT_EQ(Hash64("\xd4\x52\x33", 3, kSeed), uint64_t{15853805298481534034u}); - EXPECT_EQ(Hash64("\x6a\xb5\xf4", 3, kSeed), uint64_t{16863488758399383382u}); - EXPECT_EQ(Hash64("\x67\x53\x81\x1c", 4, kSeed), - uint64_t{9010661983527562386u}); - EXPECT_EQ(Hash64("\x69\xb8\xc0\x88", 4, kSeed), - uint64_t{6611781377647041447u}); - EXPECT_EQ(Hash64("\x1e\x84\xaf\x2d", 4, kSeed), - uint64_t{15290969111616346501u}); - EXPECT_EQ(Hash64("\x46\xdc\x54\xbe", 4, kSeed), - uint64_t{7063754590279313623u}); - EXPECT_EQ(Hash64("\xd0\x7a\x6e\xea\x56", 5, kSeed), - uint64_t{6384167718754869899u}); - EXPECT_EQ(Hash64("\x86\x83\xd5\xa4\xd8", 5, kSeed), - uint64_t{16874407254108011067u}); - EXPECT_EQ(Hash64("\xb7\x46\xbb\x77\xce", 5, kSeed), - uint64_t{16809880630149135206u}); - EXPECT_EQ(Hash64("\x6c\xa8\xbc\xe5\x99", 5, kSeed), - uint64_t{1249038833153141148u}); - EXPECT_EQ(Hash64("\x5c\x5e\xe1\xa0\x73\x81", 6, kSeed), - uint64_t{17358142495308219330u}); - EXPECT_EQ(Hash64("\x08\x5d\x73\x1c\xe5\x2e", 6, kSeed), - uint64_t{4237646583134806322u}); - EXPECT_EQ(Hash64("\x42\xfb\xf2\x52\xb4\x10", 6, kSeed), - uint64_t{4373664924115234051u}); - EXPECT_EQ(Hash64("\x73\xe1\xff\x56\x9c\xce", 6, kSeed), - uint64_t{12012981210634596029u}); - EXPECT_EQ(Hash64("\x5c\xbe\x97\x75\x54\x9a\x52", 7, kSeed), - uint64_t{5716522398211028826u}); - EXPECT_EQ(Hash64("\x16\x82\x39\x49\x88\x2b\x36", 7, kSeed), - uint64_t{15604531309862565013u}); - EXPECT_EQ(Hash64("\x59\x77\xf0\xa7\x24\xf4\x78", 7, kSeed), - uint64_t{8601330687345614172u}); - EXPECT_EQ(Hash64("\xd3\xa5\x7c\x0e\xc0\x02\x07", 7, kSeed), - uint64_t{8088079329364056942u}); - EXPECT_EQ(Hash64("\x31\x1b\x98\x75\x96\x22\xd3\x9a", 8, kSeed), - uint64_t{9844314944338447628u}); - EXPECT_EQ(Hash64("\x38\xd6\xf7\x28\x20\xb4\x8a\xe9", 8, kSeed), - uint64_t{10973293517982163143u}); - EXPECT_EQ(Hash64("\xbb\x18\x5d\xf4\x12\x03\xf7\x99", 8, kSeed), - uint64_t{9986007080564743219u}); - EXPECT_EQ(Hash64("\x80\xd4\x3b\x3b\xae\x22\xa2\x78", 8, kSeed), - uint64_t{1729303145008254458u}); - EXPECT_EQ(Hash64("\x1a\xb5\xd0\xfe\xab\xc3\x61\xb2\x99", 9, kSeed), - uint64_t{13253403748084181481u}); - EXPECT_EQ(Hash64("\x8e\x4a\xc3\x18\x20\x2f\x06\xe6\x3c", 9, kSeed), - uint64_t{7768754303876232188u}); - EXPECT_EQ(Hash64("\xb6\xc0\xdd\x05\x3f\xc4\x86\x4c\xef", 9, kSeed), - uint64_t{12439346786701492u}); - EXPECT_EQ(Hash64("\x9a\x5f\x78\x0d\xaf\x50\xe1\x1f\x55", 9, kSeed), - uint64_t{10841838338450144690u}); - EXPECT_EQ(Hash64("\x22\x6f\x39\x1f\xf8\xdd\x4f\x52\x17\x94", 10, kSeed), - uint64_t{12883919702069153152u}); - EXPECT_EQ(Hash64("\x32\x89\x2a\x75\x48\x3a\x4a\x02\x69\xdd", 10, kSeed), - uint64_t{12692903507676842188u}); - EXPECT_EQ(Hash64("\x06\x92\x5c\xf4\x88\x0e\x7e\x68\x38\x3e", 10, kSeed), - uint64_t{6540985900674032620u}); - EXPECT_EQ(Hash64("\xbd\x2c\x63\x38\xbf\xe9\x78\xb7\xbf\x15", 10, kSeed), - uint64_t{10551812464348219044u}); -} - -std::string Hash64TestDescriptor(const char *repeat, size_t limit) { - const char *mod61_encode = - "abcdefghijklmnopqrstuvwxyz123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - std::string input; - while (input.size() < limit) { - input.append(repeat); - } - std::string rv; - for (size_t i = 0; i < limit; ++i) { - uint64_t h = GetSliceHash64(Slice(input.data(), i)); - rv.append(1, mod61_encode[static_cast(h % 61)]); - } - return rv; -} - -// XXPH3 changes its algorithm for various sizes up through 250 bytes, so -// we need to check the stability of larger sizes also. -TEST(HashTest, Hash64LargeValueSchema) { - // Each of these derives a "descriptor" from the hash values for all - // lengths up to 430. - // Note that "c" is common for the zero-length string. - EXPECT_EQ( - Hash64TestDescriptor("foo", 430), - "cRhyWsY67B6klRA1udmOuiYuX7IthyGBKqbeosz2hzVglWCmQx8nEdnpkvPfYX56Up2OWOTV" - "lTzfAoYwvtqKzjD8E9xttR2unelbXbIV67NUe6bOO23BxaSFRcA3njGu5cUWfgwOqNoTsszp" - "uPvKRP6qaUR5VdoBkJUCFIefd7edlNK5mv6JYWaGdwxehg65hTkTmjZoPKxTZo4PLyzbL9U4" - "xt12ITSfeP2MfBHuLI2z2pDlBb44UQKVMx27LEoAHsdLp3WfWfgH3sdRBRCHm33UxCM4QmE2" - "xJ7gqSvNwTeH7v9GlC8zWbGroyD3UVNeShMLx29O7tH1biemLULwAHyIw8zdtLMDpEJ8m2ic" - "l6Lb4fDuuFNAs1GCVUthjK8CV8SWI8Rsz5THSwn5CGhpqUwSZcFknjwWIl5rNCvDxXJqYr"); - // Note that "1EeRk" is common for "Rocks" - EXPECT_EQ( - Hash64TestDescriptor("Rocks", 430), - "c1EeRkrzgOYWLA8PuhJrwTePJewoB44WdXYDfhbk3ZxTqqg25WlPExDl7IKIQLJvnA6gJxxn" - "9TCSLkFGfJeXehaSS1GBqWSzfhEH4VXiXIUCuxJXxtKXcSC6FrNIQGTZbYDiUOLD6Y5inzrF" - "9etwQhXUBanw55xAUdNMFQAm2GjJ6UDWp2mISLiMMkLjANWMKLaZMqaFLX37qB4MRO1ooVRv" - "zSvaNRSCLxlggQCasQq8icWjzf3HjBlZtU6pd4rkaUxSzHqmo9oM5MghbU5Rtxg8wEfO7lVN" - "5wdMONYecslQTwjZUpO1K3LDf3K3XK6sUXM6ShQQ3RHmMn2acB4YtTZ3QQcHYJSOHn2DuWpa" - "Q8RqzX5lab92YmOLaCdOHq1BPsM7SIBzMdLgePNsJ1vvMALxAaoDUHPxoFLO2wx18IXnyX"); - EXPECT_EQ( - Hash64TestDescriptor("RocksDB", 430), - "c1EeRkukbkb28wLTahwD2sfUhZzaBEnF8SVrxnPVB6A7b8CaAl3UKsDZISF92GSq2wDCukOq" - "Jgrsp7A3KZhDiLW8dFXp8UPqPxMCRlMdZeVeJ2dJxrmA6cyt99zkQFj7ELbut6jAeVqARFnw" - "fnWVXOsaLrq7bDCbMcns2DKvTaaqTCLMYxI7nhtLpFN1jR755FRQFcOzrrDbh7QhypjdvlYw" - "cdAMSZgp9JMHxbM23wPSuH6BOFgxejz35PScZfhDPvTOxIy1jc3MZsWrMC3P324zNolO7JdW" - "CX2I5UDKjjaEJfxbgVgJIXxtQGlmj2xkO5sPpjULQV4X2HlY7FQleJ4QRaJIB4buhCA4vUTF" - "eMFlxCIYUpTCsal2qsmnGOWa8WCcefrohMjDj1fjzSvSaQwlpyR1GZHF2uPOoQagiCpHpm"); -} - -TEST(HashTest, Hash128Misc) { - constexpr uint32_t kSeed = 0; // Same as GetSliceHash128 - - for (char fill : {'\0', 'a', '1', '\xff', 'e'}) { - const size_t max_size = 1000; - std::string str(max_size, fill); - - if (fill == 'e') { - // Use different characters to check endianness handling - for (size_t i = 0; i < str.size(); ++i) { - str[i] += static_cast(i); - } - } - - for (size_t size = 0; size <= max_size; ++size) { - Unsigned128 here = Hash128(str.data(), size, kSeed); - - // Must be same as unseeded Hash128 and GetSliceHash128 - EXPECT_EQ(here, Hash128(str.data(), size)); - EXPECT_EQ(here, GetSliceHash128(Slice(str.data(), size))); - { - uint64_t hi, lo; - Hash2x64(str.data(), size, &hi, &lo); - EXPECT_EQ(Lower64of128(here), lo); - EXPECT_EQ(Upper64of128(here), hi); - } - if (size == 16) { - const uint64_t in_hi = DecodeFixed64(str.data() + 8); - const uint64_t in_lo = DecodeFixed64(str.data()); - uint64_t hi, lo; - BijectiveHash2x64(in_hi, in_lo, &hi, &lo); - EXPECT_EQ(Lower64of128(here), lo); - EXPECT_EQ(Upper64of128(here), hi); - uint64_t un_hi, un_lo; - BijectiveUnhash2x64(hi, lo, &un_hi, &un_lo); - EXPECT_EQ(in_lo, un_lo); - EXPECT_EQ(in_hi, un_hi); - } - - // Upper and Lower must reconstruct hash - EXPECT_EQ(here, - (Unsigned128{Upper64of128(here)} << 64) | Lower64of128(here)); - EXPECT_EQ(here, - (Unsigned128{Upper64of128(here)} << 64) ^ Lower64of128(here)); - - // Seed changes hash value (with high probability) - for (uint64_t var_seed = 1; var_seed != 0; var_seed <<= 1) { - Unsigned128 seeded = Hash128(str.data(), size, var_seed); - EXPECT_NE(here, seeded); - // Must match seeded Hash2x64 - { - uint64_t hi, lo; - Hash2x64(str.data(), size, var_seed, &hi, &lo); - EXPECT_EQ(Lower64of128(seeded), lo); - EXPECT_EQ(Upper64of128(seeded), hi); - } - if (size == 16) { - const uint64_t in_hi = DecodeFixed64(str.data() + 8); - const uint64_t in_lo = DecodeFixed64(str.data()); - uint64_t hi, lo; - BijectiveHash2x64(in_hi, in_lo, var_seed, &hi, &lo); - EXPECT_EQ(Lower64of128(seeded), lo); - EXPECT_EQ(Upper64of128(seeded), hi); - uint64_t un_hi, un_lo; - BijectiveUnhash2x64(hi, lo, var_seed, &un_hi, &un_lo); - EXPECT_EQ(in_lo, un_lo); - EXPECT_EQ(in_hi, un_hi); - } - } - - // Size changes hash value (with high probability) - size_t max_smaller_by = std::min(size_t{30}, size); - for (size_t smaller_by = 1; smaller_by <= max_smaller_by; ++smaller_by) { - EXPECT_NE(here, Hash128(str.data(), size - smaller_by, kSeed)); - } - } - } -} - -// Test that hash values are "non-trivial" for "trivial" inputs -TEST(HashTest, Hash128Trivial) { - // Thorough test too slow for regression testing - constexpr bool thorough = false; - - // For various seeds, make sure hash of empty string is not zero. - constexpr uint64_t max_seed = thorough ? 0x1000000 : 0x10000; - for (uint64_t seed = 0; seed < max_seed; ++seed) { - Unsigned128 here = Hash128("", 0, seed); - EXPECT_NE(Lower64of128(here), 0u); - EXPECT_NE(Upper64of128(here), 0u); - } - - // For standard seed, make sure hash of small strings are not zero - constexpr uint32_t kSeed = 0; // Same as GetSliceHash128 - char input[4]; - constexpr int max_len = thorough ? 3 : 2; - for (int len = 1; len <= max_len; ++len) { - for (uint32_t i = 0; (i >> (len * 8)) == 0; ++i) { - EncodeFixed32(input, i); - Unsigned128 here = Hash128(input, len, kSeed); - EXPECT_NE(Lower64of128(here), 0u); - EXPECT_NE(Upper64of128(here), 0u); - } - } -} - -std::string Hash128TestDescriptor(const char *repeat, size_t limit) { - const char *mod61_encode = - "abcdefghijklmnopqrstuvwxyz123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - std::string input; - while (input.size() < limit) { - input.append(repeat); - } - std::string rv; - for (size_t i = 0; i < limit; ++i) { - auto h = GetSliceHash128(Slice(input.data(), i)); - uint64_t h2 = Upper64of128(h) + Lower64of128(h); - rv.append(1, mod61_encode[static_cast(h2 % 61)]); - } - return rv; -} - -// XXH3 changes its algorithm for various sizes up through 250 bytes, so -// we need to check the stability of larger sizes also. -TEST(HashTest, Hash128ValueSchema) { - // Each of these derives a "descriptor" from the hash values for all - // lengths up to 430. - // Note that "b" is common for the zero-length string. - EXPECT_EQ( - Hash128TestDescriptor("foo", 430), - "bUMA3As8n9I4vNGhThXlEevxZlyMcbb6TYAlIKJ2f5ponsv99q962rYclQ7u3gfnRdCDQ5JI" - "2LrGUaCycbXrvLFe4SjgRb9RQwCfrnmNQ7VSEwSKMnkGCK3bDbXSrnIh5qLXdtvIZklbJpGH" - "Dqr93BlqF9ubTnOSYkSdx89XvQqflMIW8bjfQp9BPjQejWOeEQspnN1D3sfgVdFhpaQdHYA5" - "pI2XcPlCMFPxvrFuRr7joaDvjNe9IUZaunLPMewuXmC3EL95h52Ju3D7y9RNKhgYxMTrA84B" - "yJrMvyjdm3vlBxet4EN7v2GEyjbGuaZW9UL6lrX6PghJDg7ACfLGdxNbH3qXM4zaiG2RKnL5" - "S3WXKR78RBB5fRFQ8KDIEQjHFvSNsc3GrAEi6W8P2lv8JMTzjBODO2uN4wadVQFT9wpGfV"); - // Note that "35D2v" is common for "Rocks" - EXPECT_EQ( - Hash128TestDescriptor("Rocks", 430), - "b35D2vzvklFVDqJmyLRXyApwGGO3EAT3swhe8XJAN3mY2UVPglzdmydxcba6JI2tSvwO6zSu" - "ANpjSM7tc9G5iMhsa7R8GfyCXRO1TnLg7HvdWNdgGGBirxZR68BgT7TQsYJt6zyEyISeXI1n" - "MXA48Xo7dWfJeYN6Z4KWlqZY7TgFXGbks9AX4ehZNSGtIhdO5i58qlgVX1bEejeOVaCcjC79" - "67DrMfOKds7rUQzjBa77sMPcoPW1vu6ljGJPZH3XkRyDMZ1twxXKkNxN3tE8nR7JHwyqBAxE" - "fTcjbOWrLZ1irWxRSombD8sGDEmclgF11IxqEhe3Rt7gyofO3nExGckKkS9KfRqsCHbiUyva" - "JGkJwUHRXaZnh58b4i1Ei9aQKZjXlvIVDixoZrjcNaH5XJIJlRZce9Z9t82wYapTpckYSg"); - EXPECT_EQ( - Hash128TestDescriptor("RocksDB", 430), - "b35D2vFUst3XDZCRlSrhmYYakmqImV97LbBsV6EZlOEQpUPH1d1sD3xMKAPlA5UErHehg5O7" - "n966fZqhAf3hRc24kGCLfNAWjyUa7vSNOx3IcPoTyVRFZeFlcCtfl7t1QJumHOCpS33EBmBF" - "hvK13QjBbDWYWeHQhJhgV9Mqbx17TIcvUkEnYZxb8IzWNmjVsJG44Z7v52DjGj1ZzS62S2Vv" - "qWcDO7apvH5VHg68E9Wl6nXP21vlmUqEH9GeWRehfWVvY7mUpsAg5drHHQyDSdiMceiUuUxJ" - "XJqHFcDdzbbPk7xDvbLgWCKvH8k3MpQNWOmbSSRDdAP6nGlDjoTToYkcqVREHJzztSWAAq5h" - "GHSUNJ6OxsMHhf8EhXfHtKyUzRmPtjYyeckQcGmrQfFFLidc6cjMDKCdBG6c6HVBrS7H2R"); -} - -TEST(FastRange32Test, Values) { - using ROCKSDB_NAMESPACE::FastRange32; - // Zero range - EXPECT_EQ(FastRange32(0, 0), 0U); - EXPECT_EQ(FastRange32(123, 0), 0U); - EXPECT_EQ(FastRange32(0xffffffff, 0), 0U); - - // One range - EXPECT_EQ(FastRange32(0, 1), 0U); - EXPECT_EQ(FastRange32(123, 1), 0U); - EXPECT_EQ(FastRange32(0xffffffff, 1), 0U); - - // Two range - EXPECT_EQ(FastRange32(0, 2), 0U); - EXPECT_EQ(FastRange32(123, 2), 0U); - EXPECT_EQ(FastRange32(0x7fffffff, 2), 0U); - EXPECT_EQ(FastRange32(0x80000000, 2), 1U); - EXPECT_EQ(FastRange32(0xffffffff, 2), 1U); - - // Seven range - EXPECT_EQ(FastRange32(0, 7), 0U); - EXPECT_EQ(FastRange32(123, 7), 0U); - EXPECT_EQ(FastRange32(613566756, 7), 0U); - EXPECT_EQ(FastRange32(613566757, 7), 1U); - EXPECT_EQ(FastRange32(1227133513, 7), 1U); - EXPECT_EQ(FastRange32(1227133514, 7), 2U); - // etc. - EXPECT_EQ(FastRange32(0xffffffff, 7), 6U); - - // Big - EXPECT_EQ(FastRange32(1, 0x80000000), 0U); - EXPECT_EQ(FastRange32(2, 0x80000000), 1U); - EXPECT_EQ(FastRange32(4, 0x7fffffff), 1U); - EXPECT_EQ(FastRange32(4, 0x80000000), 2U); - EXPECT_EQ(FastRange32(0xffffffff, 0x7fffffff), 0x7ffffffeU); - EXPECT_EQ(FastRange32(0xffffffff, 0x80000000), 0x7fffffffU); -} - -TEST(FastRange64Test, Values) { - using ROCKSDB_NAMESPACE::FastRange64; - // Zero range - EXPECT_EQ(FastRange64(0, 0), 0U); - EXPECT_EQ(FastRange64(123, 0), 0U); - EXPECT_EQ(FastRange64(0xffffFFFF, 0), 0U); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 0), 0U); - - // One range - EXPECT_EQ(FastRange64(0, 1), 0U); - EXPECT_EQ(FastRange64(123, 1), 0U); - EXPECT_EQ(FastRange64(0xffffFFFF, 1), 0U); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 1), 0U); - - // Two range - EXPECT_EQ(FastRange64(0, 2), 0U); - EXPECT_EQ(FastRange64(123, 2), 0U); - EXPECT_EQ(FastRange64(0xffffFFFF, 2), 0U); - EXPECT_EQ(FastRange64(0x7fffFFFFffffFFFF, 2), 0U); - EXPECT_EQ(FastRange64(0x8000000000000000, 2), 1U); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 2), 1U); - - // Seven range - EXPECT_EQ(FastRange64(0, 7), 0U); - EXPECT_EQ(FastRange64(123, 7), 0U); - EXPECT_EQ(FastRange64(0xffffFFFF, 7), 0U); - EXPECT_EQ(FastRange64(2635249153387078802, 7), 0U); - EXPECT_EQ(FastRange64(2635249153387078803, 7), 1U); - EXPECT_EQ(FastRange64(5270498306774157604, 7), 1U); - EXPECT_EQ(FastRange64(5270498306774157605, 7), 2U); - EXPECT_EQ(FastRange64(0x7fffFFFFffffFFFF, 7), 3U); - EXPECT_EQ(FastRange64(0x8000000000000000, 7), 3U); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 7), 6U); - - // Big but 32-bit range - EXPECT_EQ(FastRange64(0x100000000, 0x80000000), 0U); - EXPECT_EQ(FastRange64(0x200000000, 0x80000000), 1U); - EXPECT_EQ(FastRange64(0x400000000, 0x7fffFFFF), 1U); - EXPECT_EQ(FastRange64(0x400000000, 0x80000000), 2U); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 0x7fffFFFF), 0x7fffFFFEU); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 0x80000000), 0x7fffFFFFU); - - // Big, > 32-bit range -#if SIZE_MAX == UINT64_MAX - EXPECT_EQ(FastRange64(0x7fffFFFFffffFFFF, 0x4200000002), 0x2100000000U); - EXPECT_EQ(FastRange64(0x8000000000000000, 0x4200000002), 0x2100000001U); - - EXPECT_EQ(FastRange64(0x0000000000000000, 420000000002), 0U); - EXPECT_EQ(FastRange64(0x7fffFFFFffffFFFF, 420000000002), 210000000000U); - EXPECT_EQ(FastRange64(0x8000000000000000, 420000000002), 210000000001U); - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 420000000002), 420000000001U); - - EXPECT_EQ(FastRange64(0xffffFFFFffffFFFF, 0xffffFFFFffffFFFF), - 0xffffFFFFffffFFFEU); -#endif -} - -TEST(FastRangeGenericTest, Values) { - using ROCKSDB_NAMESPACE::FastRangeGeneric; - // Generic (including big and small) - // Note that FastRangeGeneric is also tested indirectly above via - // FastRange32 and FastRange64. - EXPECT_EQ( - FastRangeGeneric(uint64_t{0x8000000000000000}, uint64_t{420000000002}), - uint64_t{210000000001}); - EXPECT_EQ(FastRangeGeneric(uint64_t{0x8000000000000000}, uint16_t{12468}), - uint16_t{6234}); - EXPECT_EQ(FastRangeGeneric(uint32_t{0x80000000}, uint16_t{12468}), - uint16_t{6234}); - // Not recommended for typical use because for example this could fail on - // some platforms and pass on others: - // EXPECT_EQ(FastRangeGeneric(static_cast(0x80000000), - // uint16_t{12468}), - // uint16_t{6234}); -} - -// for inspection of disassembly -uint32_t FastRange32(uint32_t hash, uint32_t range) { - return ROCKSDB_NAMESPACE::FastRange32(hash, range); -} - -// for inspection of disassembly -size_t FastRange64(uint64_t hash, size_t range) { - return ROCKSDB_NAMESPACE::FastRange64(hash, range); -} - -// Tests for math.h / math128.h (not worth a separate test binary) -using ROCKSDB_NAMESPACE::BitParity; -using ROCKSDB_NAMESPACE::BitsSetToOne; -using ROCKSDB_NAMESPACE::ConstexprFloorLog2; -using ROCKSDB_NAMESPACE::CountTrailingZeroBits; -using ROCKSDB_NAMESPACE::DecodeFixed128; -using ROCKSDB_NAMESPACE::DecodeFixedGeneric; -using ROCKSDB_NAMESPACE::DownwardInvolution; -using ROCKSDB_NAMESPACE::EncodeFixed128; -using ROCKSDB_NAMESPACE::EncodeFixedGeneric; -using ROCKSDB_NAMESPACE::FloorLog2; -using ROCKSDB_NAMESPACE::Lower64of128; -using ROCKSDB_NAMESPACE::Multiply64to128; -using ROCKSDB_NAMESPACE::Unsigned128; -using ROCKSDB_NAMESPACE::Upper64of128; - -int blah(int x) { return DownwardInvolution(x); } - -template -static void test_BitOps() { - // This complex code is to generalize to 128-bit values. Otherwise - // we could just use = static_cast(0x5555555555555555ULL); - T everyOtherBit = 0; - for (unsigned i = 0; i < sizeof(T); ++i) { - everyOtherBit = (everyOtherBit << 8) | T{0x55}; - } - - // This one built using bit operations, as our 128-bit layer - // might not implement arithmetic such as subtraction. - T vm1 = 0; // "v minus one" - - for (int i = 0; i < int{8 * sizeof(T)}; ++i) { - T v = T{1} << i; - // If we could directly use arithmetic: - // T vm1 = static_cast(v - 1); - - // FloorLog2 - if (v > 0) { - EXPECT_EQ(FloorLog2(v), i); - EXPECT_EQ(ConstexprFloorLog2(v), i); - } - if (vm1 > 0) { - EXPECT_EQ(FloorLog2(vm1), i - 1); - EXPECT_EQ(ConstexprFloorLog2(vm1), i - 1); - EXPECT_EQ(FloorLog2(everyOtherBit & vm1), (i - 1) & ~1); - EXPECT_EQ(ConstexprFloorLog2(everyOtherBit & vm1), (i - 1) & ~1); - } - - // CountTrailingZeroBits - if (v != 0) { - EXPECT_EQ(CountTrailingZeroBits(v), i); - } - if (vm1 != 0) { - EXPECT_EQ(CountTrailingZeroBits(vm1), 0); - } - if (i < int{8 * sizeof(T)} - 1) { - EXPECT_EQ(CountTrailingZeroBits(~vm1 & everyOtherBit), (i + 1) & ~1); - } - - // BitsSetToOne - EXPECT_EQ(BitsSetToOne(v), 1); - EXPECT_EQ(BitsSetToOne(vm1), i); - EXPECT_EQ(BitsSetToOne(vm1 & everyOtherBit), (i + 1) / 2); - - // BitParity - EXPECT_EQ(BitParity(v), 1); - EXPECT_EQ(BitParity(vm1), i & 1); - EXPECT_EQ(BitParity(vm1 & everyOtherBit), ((i + 1) / 2) & 1); - - // EndianSwapValue - T ev = T{1} << (((sizeof(T) - 1 - (i / 8)) * 8) + i % 8); - EXPECT_EQ(EndianSwapValue(v), ev); - - // ReverseBits - EXPECT_EQ(ReverseBits(v), static_cast(T{1} << (8 * sizeof(T) - 1 - i))); -#ifdef HAVE_UINT128_EXTENSION // Uses multiplication - if (std::is_unsigned::value) { // Technical UB on signed type - T rv = T{1} << (8 * sizeof(T) - 1 - i); - EXPECT_EQ(ReverseBits(vm1), static_cast(rv * ~T{1})); - } -#endif - - // DownwardInvolution - { - T misc = static_cast(/*random*/ 0xc682cd153d0e3279U + - i * /*random*/ 0x9b3972f3bea0baa3U); - if constexpr (sizeof(T) > 8) { - misc = (misc << 64) | (/*random*/ 0x52af031a38ced62dU + - i * /*random*/ 0x936f803d9752ddc3U); - } - T misc_masked = misc & vm1; - EXPECT_LE(misc_masked, vm1); - T di_misc_masked = DownwardInvolution(misc_masked); - EXPECT_LE(di_misc_masked, vm1); - if (misc_masked > 0) { - // Highest-order 1 in same position - EXPECT_EQ(FloorLog2(misc_masked), FloorLog2(di_misc_masked)); - } - // Validate involution property on short value - EXPECT_EQ(DownwardInvolution(di_misc_masked), misc_masked); - - // Validate involution property on large value - T di_misc = DownwardInvolution(misc); - EXPECT_EQ(DownwardInvolution(di_misc), misc); - // Highest-order 1 in same position - if (misc > 0) { - EXPECT_EQ(FloorLog2(misc), FloorLog2(di_misc)); - } - - // Validate distributes over xor. - // static_casts to avoid numerical promotion effects. - EXPECT_EQ(DownwardInvolution(static_cast(misc_masked ^ vm1)), - static_cast(di_misc_masked ^ DownwardInvolution(vm1))); - T misc2 = static_cast(misc >> 1); - EXPECT_EQ(DownwardInvolution(static_cast(misc ^ misc2)), - static_cast(di_misc ^ DownwardInvolution(misc2))); - - // Choose some small number of bits to pull off to test combined - // uniqueness guarantee - int in_bits = i % 7; - unsigned in_mask = (unsigned{1} << in_bits) - 1U; - // IMPLICIT: int out_bits = 8 - in_bits; - std::vector seen(256, false); - for (int j = 0; j < 255; ++j) { - T t_in = misc ^ static_cast(j); - unsigned in = static_cast(t_in); - unsigned out = static_cast(DownwardInvolution(t_in)); - unsigned val = ((out << in_bits) | (in & in_mask)) & 255U; - EXPECT_FALSE(seen[val]); - seen[val] = true; - } - - if (i + 8 < int{8 * sizeof(T)}) { - // Also test manipulating bits in the middle of input is - // bijective in bottom of output - seen = std::vector(256, false); - for (int j = 0; j < 255; ++j) { - T in = misc ^ (static_cast(j) << i); - unsigned val = static_cast(DownwardInvolution(in)) & 255U; - EXPECT_FALSE(seen[val]); - seen[val] = true; - } - } - } - - vm1 = (vm1 << 1) | 1; - } - - EXPECT_EQ(ConstexprFloorLog2(T{1}), 0); - EXPECT_EQ(ConstexprFloorLog2(T{2}), 1); - EXPECT_EQ(ConstexprFloorLog2(T{3}), 1); - EXPECT_EQ(ConstexprFloorLog2(T{42}), 5); -} - -TEST(MathTest, BitOps) { - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); - test_BitOps(); -} - -TEST(MathTest, BitOps128) { test_BitOps(); } - -TEST(MathTest, Math128) { - const Unsigned128 sixteenHexOnes = 0x1111111111111111U; - const Unsigned128 thirtyHexOnes = (sixteenHexOnes << 56) | sixteenHexOnes; - const Unsigned128 sixteenHexTwos = 0x2222222222222222U; - const Unsigned128 thirtyHexTwos = (sixteenHexTwos << 56) | sixteenHexTwos; - - // v will slide from all hex ones to all hex twos - Unsigned128 v = thirtyHexOnes; - for (int i = 0; i <= 30; ++i) { - // Test bitwise operations - EXPECT_EQ(BitsSetToOne(v), 30); - EXPECT_EQ(BitsSetToOne(~v), 128 - 30); - EXPECT_EQ(BitsSetToOne(v & thirtyHexOnes), 30 - i); - EXPECT_EQ(BitsSetToOne(v | thirtyHexOnes), 30 + i); - EXPECT_EQ(BitsSetToOne(v ^ thirtyHexOnes), 2 * i); - EXPECT_EQ(BitsSetToOne(v & thirtyHexTwos), i); - EXPECT_EQ(BitsSetToOne(v | thirtyHexTwos), 60 - i); - EXPECT_EQ(BitsSetToOne(v ^ thirtyHexTwos), 60 - 2 * i); - - // Test comparisons - EXPECT_EQ(v == thirtyHexOnes, i == 0); - EXPECT_EQ(v == thirtyHexTwos, i == 30); - EXPECT_EQ(v > thirtyHexOnes, i > 0); - EXPECT_EQ(v > thirtyHexTwos, false); - EXPECT_EQ(v >= thirtyHexOnes, true); - EXPECT_EQ(v >= thirtyHexTwos, i == 30); - EXPECT_EQ(v < thirtyHexOnes, false); - EXPECT_EQ(v < thirtyHexTwos, i < 30); - EXPECT_EQ(v <= thirtyHexOnes, i == 0); - EXPECT_EQ(v <= thirtyHexTwos, true); - - // Update v, clearing upper-most byte - v = ((v << 12) >> 8) | 0x2; - } - - for (int i = 0; i < 128; ++i) { - // Test shifts - Unsigned128 sl = thirtyHexOnes << i; - Unsigned128 sr = thirtyHexOnes >> i; - EXPECT_EQ(BitsSetToOne(sl), std::min(30, 32 - i / 4)); - EXPECT_EQ(BitsSetToOne(sr), std::max(0, 30 - (i + 3) / 4)); - EXPECT_EQ(BitsSetToOne(sl & sr), i % 2 ? 0 : std::max(0, 30 - i / 2)); - } - - // Test 64x64->128 multiply - Unsigned128 product = - Multiply64to128(0x1111111111111111U, 0x2222222222222222U); - EXPECT_EQ(Lower64of128(product), 2295594818061633090U); - EXPECT_EQ(Upper64of128(product), 163971058432973792U); -} - -TEST(MathTest, Coding128) { - const char *in = "_1234567890123456"; - // Note: in + 1 is likely unaligned - Unsigned128 decoded = DecodeFixed128(in + 1); - EXPECT_EQ(Lower64of128(decoded), 0x3837363534333231U); - EXPECT_EQ(Upper64of128(decoded), 0x3635343332313039U); - char out[18]; - out[0] = '_'; - EncodeFixed128(out + 1, decoded); - out[17] = '\0'; - EXPECT_EQ(std::string(in), std::string(out)); -} - -TEST(MathTest, CodingGeneric) { - const char *in = "_1234567890123456"; - // Decode - // Note: in + 1 is likely unaligned - Unsigned128 decoded128 = DecodeFixedGeneric(in + 1); - EXPECT_EQ(Lower64of128(decoded128), 0x3837363534333231U); - EXPECT_EQ(Upper64of128(decoded128), 0x3635343332313039U); - - uint64_t decoded64 = DecodeFixedGeneric(in + 1); - EXPECT_EQ(decoded64, 0x3837363534333231U); - - uint32_t decoded32 = DecodeFixedGeneric(in + 1); - EXPECT_EQ(decoded32, 0x34333231U); - - uint16_t decoded16 = DecodeFixedGeneric(in + 1); - EXPECT_EQ(decoded16, 0x3231U); - - // Encode - char out[18]; - out[0] = '_'; - memset(out + 1, '\0', 17); - EncodeFixedGeneric(out + 1, decoded128); - EXPECT_EQ(std::string(in), std::string(out)); - - memset(out + 1, '\0', 9); - EncodeFixedGeneric(out + 1, decoded64); - EXPECT_EQ(std::string("_12345678"), std::string(out)); - - memset(out + 1, '\0', 5); - EncodeFixedGeneric(out + 1, decoded32); - EXPECT_EQ(std::string("_1234"), std::string(out)); - - memset(out + 1, '\0', 3); - EncodeFixedGeneric(out + 1, decoded16); - EXPECT_EQ(std::string("_12"), std::string(out)); -} - -int main(int argc, char **argv) { - fprintf(stderr, "NPHash64 id: %x\n", - static_cast(ROCKSDB_NAMESPACE::GetSliceNPHash64("RocksDB"))); - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/util/heap_test.cc b/util/heap_test.cc deleted file mode 100644 index bbb93324f..000000000 --- a/util/heap_test.cc +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "util/heap.h" - -#include - -#include -#include -#include -#include - -#include "port/stack_trace.h" - -#ifndef GFLAGS -const int64_t FLAGS_iters = 100000; -#else -#include "util/gflags_compat.h" -DEFINE_int64(iters, 100000, "number of pseudo-random operations in each test"); -#endif // GFLAGS - -/* - * Compares the custom heap implementation in util/heap.h against - * std::priority_queue on a pseudo-random sequence of operations. - */ - -namespace ROCKSDB_NAMESPACE { - -using HeapTestValue = uint64_t; -using Params = std::tuple; - -class HeapTest : public ::testing::TestWithParam {}; - -TEST_P(HeapTest, Test) { - // This test performs the same pseudorandom sequence of operations on a - // BinaryHeap and an std::priority_queue, comparing output. The three - // possible operations are insert, replace top and pop. - // - // Insert is chosen slightly more often than the others so that the size of - // the heap slowly grows. Once the size heats the MAX_HEAP_SIZE limit, we - // disallow inserting until the heap becomes empty, testing the "draining" - // scenario. - - const auto MAX_HEAP_SIZE = std::get<0>(GetParam()); - const auto MAX_VALUE = std::get<1>(GetParam()); - const auto RNG_SEED = std::get<2>(GetParam()); - - BinaryHeap heap; - std::priority_queue ref; - - std::mt19937 rng(static_cast(RNG_SEED)); - std::uniform_int_distribution value_dist(0, MAX_VALUE); - int ndrains = 0; - bool draining = false; // hit max size, draining until we empty the heap - size_t size = 0; - for (int64_t i = 0; i < FLAGS_iters; ++i) { - if (size == 0) { - draining = false; - } - - if (!draining && (size == 0 || std::bernoulli_distribution(0.4)(rng))) { - // insert - HeapTestValue val = value_dist(rng); - heap.push(val); - ref.push(val); - ++size; - if (size == MAX_HEAP_SIZE) { - draining = true; - ++ndrains; - } - } else if (std::bernoulli_distribution(0.5)(rng)) { - // replace top - HeapTestValue val = value_dist(rng); - heap.replace_top(val); - ref.pop(); - ref.push(val); - } else { - // pop - assert(size > 0); - heap.pop(); - ref.pop(); - --size; - } - - // After every operation, check that the public methods give the same - // results - assert((size == 0) == ref.empty()); - ASSERT_EQ(size == 0, heap.empty()); - if (size > 0) { - ASSERT_EQ(ref.top(), heap.top()); - } - } - - // Probabilities should be set up to occasionally hit the max heap size and - // drain it - assert(ndrains > 0); - - heap.clear(); - ASSERT_TRUE(heap.empty()); -} - -// Basic test, MAX_VALUE = 3*MAX_HEAP_SIZE (occasional duplicates) -INSTANTIATE_TEST_CASE_P(Basic, HeapTest, - ::testing::Values(Params(1000, 3000, - 0x1b575cf05b708945))); -// Mid-size heap with small values (many duplicates) -INSTANTIATE_TEST_CASE_P(SmallValues, HeapTest, - ::testing::Values(Params(100, 10, 0x5ae213f7bd5dccd0))); -// Small heap, large value range (no duplicates) -INSTANTIATE_TEST_CASE_P(SmallHeap, HeapTest, - ::testing::Values(Params(10, ULLONG_MAX, - 0x3e1fa8f4d01707cf))); -// Two-element heap -INSTANTIATE_TEST_CASE_P(TwoElementHeap, HeapTest, - ::testing::Values(Params(2, 5, 0x4b5e13ea988c6abc))); -// One-element heap -INSTANTIATE_TEST_CASE_P(OneElementHeap, HeapTest, - ::testing::Values(Params(1, 3, 0x176a1019ab0b612e))); - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); -#ifdef GFLAGS - GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); -#endif // GFLAGS - return RUN_ALL_TESTS(); -} diff --git a/util/random_test.cc b/util/random_test.cc deleted file mode 100644 index 1aa62c5da..000000000 --- a/util/random_test.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2012 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "util/random.h" - -#include -#include - -#include "test_util/testharness.h" - -using ROCKSDB_NAMESPACE::Random; - -TEST(RandomTest, Uniform) { - const int average = 20; - for (uint32_t seed : {0, 1, 2, 37, 4096}) { - Random r(seed); - for (int range : {1, 2, 8, 12, 100}) { - std::vector counts(range, 0); - - for (int i = 0; i < range * average; ++i) { - ++counts.at(r.Uniform(range)); - } - int max_variance = static_cast(std::sqrt(range) * 2 + 4); - for (int i = 0; i < range; ++i) { - EXPECT_GE(counts[i], std::max(1, average - max_variance)); - EXPECT_LE(counts[i], average + max_variance + 1); - } - } - } -} - -TEST(RandomTest, OneIn) { - Random r(42); - for (int range : {1, 2, 8, 12, 100, 1234}) { - const int average = 100; - int count = 0; - for (int i = 0; i < average * range; ++i) { - if (r.OneIn(range)) { - ++count; - } - } - if (range == 1) { - EXPECT_EQ(count, average); - } else { - int max_variance = static_cast(std::sqrt(average) * 1.5); - EXPECT_GE(count, average - max_variance); - EXPECT_LE(count, average + max_variance); - } - } -} - -TEST(RandomTest, OneInOpt) { - Random r(42); - for (int range : {-12, 0, 1, 2, 8, 12, 100, 1234}) { - const int average = 100; - int count = 0; - for (int i = 0; i < average * range; ++i) { - if (r.OneInOpt(range)) { - ++count; - } - } - if (range < 1) { - EXPECT_EQ(count, 0); - } else if (range == 1) { - EXPECT_EQ(count, average); - } else { - int max_variance = static_cast(std::sqrt(average) * 1.5); - EXPECT_GE(count, average - max_variance); - EXPECT_LE(count, average + max_variance); - } - } -} - -TEST(RandomTest, PercentTrue) { - Random r(42); - for (int pct : {-12, 0, 1, 2, 10, 50, 90, 98, 99, 100, 1234}) { - const int samples = 10000; - - int count = 0; - for (int i = 0; i < samples; ++i) { - if (r.PercentTrue(pct)) { - ++count; - } - } - if (pct <= 0) { - EXPECT_EQ(count, 0); - } else if (pct >= 100) { - EXPECT_EQ(count, samples); - } else { - int est = (count * 100 + (samples / 2)) / samples; - EXPECT_EQ(est, pct); - } - } -} - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/util/rate_limiter_test.cc b/util/rate_limiter_test.cc deleted file mode 100644 index cda134867..000000000 --- a/util/rate_limiter_test.cc +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "util/rate_limiter.h" - -#include -#include -#include -#include - -#include "db/db_test_util.h" -#include "port/port.h" -#include "rocksdb/system_clock.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "util/random.h" - -namespace ROCKSDB_NAMESPACE { - -// TODO(yhchiang): the rate will not be accurate when we run test in parallel. -class RateLimiterTest : public testing::Test { - protected: - ~RateLimiterTest() override { - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearAllCallBacks(); - } -}; - -TEST_F(RateLimiterTest, OverflowRate) { - GenericRateLimiter limiter(std::numeric_limits::max(), 1000, 10, - RateLimiter::Mode::kWritesOnly, - SystemClock::Default(), false /* auto_tuned */); - ASSERT_GT(limiter.GetSingleBurstBytes(), 1000000000ll); -} - -TEST_F(RateLimiterTest, StartStop) { - std::unique_ptr limiter(NewGenericRateLimiter(100, 100, 10)); -} - -TEST_F(RateLimiterTest, GetTotalBytesThrough) { - std::unique_ptr limiter(NewGenericRateLimiter( - 200 /* rate_bytes_per_sec */, 1000 * 1000 /* refill_period_us */, - 10 /* fairness */)); - for (int i = Env::IO_LOW; i <= Env::IO_TOTAL; ++i) { - ASSERT_EQ(limiter->GetTotalBytesThrough(static_cast(i)), - 0); - } - - std::int64_t request_byte = 200; - std::int64_t request_byte_sum = 0; - for (int i = Env::IO_LOW; i < Env::IO_TOTAL; ++i) { - limiter->Request(request_byte, static_cast(i), - nullptr /* stats */, RateLimiter::OpType::kWrite); - request_byte_sum += request_byte; - } - - for (int i = Env::IO_LOW; i < Env::IO_TOTAL; ++i) { - EXPECT_EQ(limiter->GetTotalBytesThrough(static_cast(i)), - request_byte) - << "Failed to track total_bytes_through_ correctly when IOPriority = " - << static_cast(i); - } - EXPECT_EQ(limiter->GetTotalBytesThrough(Env::IO_TOTAL), request_byte_sum) - << "Failed to track total_bytes_through_ correctly when IOPriority = " - "Env::IO_TOTAL"; -} - -TEST_F(RateLimiterTest, GetTotalRequests) { - std::unique_ptr limiter(NewGenericRateLimiter( - 200 /* rate_bytes_per_sec */, 1000 * 1000 /* refill_period_us */, - 10 /* fairness */)); - for (int i = Env::IO_LOW; i <= Env::IO_TOTAL; ++i) { - ASSERT_EQ(limiter->GetTotalRequests(static_cast(i)), 0); - } - - std::int64_t total_requests_sum = 0; - for (int i = Env::IO_LOW; i < Env::IO_TOTAL; ++i) { - limiter->Request(200, static_cast(i), nullptr /* stats */, - RateLimiter::OpType::kWrite); - total_requests_sum += 1; - } - - for (int i = Env::IO_LOW; i < Env::IO_TOTAL; ++i) { - EXPECT_EQ(limiter->GetTotalRequests(static_cast(i)), 1) - << "Failed to track total_requests_ correctly when IOPriority = " - << static_cast(i); - } - EXPECT_EQ(limiter->GetTotalRequests(Env::IO_TOTAL), total_requests_sum) - << "Failed to track total_requests_ correctly when IOPriority = " - "Env::IO_TOTAL"; -} - -TEST_F(RateLimiterTest, GetTotalPendingRequests) { - std::unique_ptr limiter(NewGenericRateLimiter( - 200 /* rate_bytes_per_sec */, 1000 * 1000 /* refill_period_us */, - 10 /* fairness */)); - int64_t total_pending_requests = 0; - for (int i = Env::IO_LOW; i <= Env::IO_TOTAL; ++i) { - ASSERT_OK(limiter->GetTotalPendingRequests( - &total_pending_requests, static_cast(i))); - ASSERT_EQ(total_pending_requests, 0); - } - // This is a variable for making sure the following callback is called - // and the assertions in it are indeed excuted - bool nonzero_pending_requests_verified = false; - SyncPoint::GetInstance()->SetCallBack( - "GenericRateLimiter::Request:PostEnqueueRequest", [&](void* arg) { - port::Mutex* request_mutex = (port::Mutex*)arg; - // We temporarily unlock the mutex so that the following - // GetTotalPendingRequests() can acquire it - request_mutex->Unlock(); - for (int i = Env::IO_LOW; i <= Env::IO_TOTAL; ++i) { - EXPECT_OK(limiter->GetTotalPendingRequests( - &total_pending_requests, static_cast(i))) - << "Failed to return total pending requests for priority level = " - << static_cast(i); - if (i == Env::IO_USER || i == Env::IO_TOTAL) { - EXPECT_EQ(total_pending_requests, 1) - << "Failed to correctly return total pending requests for " - "priority level = " - << static_cast(i); - } else { - EXPECT_EQ(total_pending_requests, 0) - << "Failed to correctly return total pending requests for " - "priority level = " - << static_cast(i); - } - } - // We lock the mutex again so that the request thread can resume running - // with the mutex locked - request_mutex->Lock(); - nonzero_pending_requests_verified = true; - }); - - SyncPoint::GetInstance()->EnableProcessing(); - limiter->Request(200, Env::IO_USER, nullptr /* stats */, - RateLimiter::OpType::kWrite); - ASSERT_EQ(nonzero_pending_requests_verified, true); - for (int i = Env::IO_LOW; i <= Env::IO_TOTAL; ++i) { - EXPECT_OK(limiter->GetTotalPendingRequests(&total_pending_requests, - static_cast(i))) - << "Failed to return total pending requests for priority level = " - << static_cast(i); - EXPECT_EQ(total_pending_requests, 0) - << "Failed to correctly return total pending requests for priority " - "level = " - << static_cast(i); - } - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearCallBack( - "GenericRateLimiter::Request:PostEnqueueRequest"); -} - -TEST_F(RateLimiterTest, Modes) { - for (auto mode : {RateLimiter::Mode::kWritesOnly, - RateLimiter::Mode::kReadsOnly, RateLimiter::Mode::kAllIo}) { - GenericRateLimiter limiter(2000 /* rate_bytes_per_sec */, - 1000 * 1000 /* refill_period_us */, - 10 /* fairness */, mode, SystemClock::Default(), - false /* auto_tuned */); - limiter.Request(1000 /* bytes */, Env::IO_HIGH, nullptr /* stats */, - RateLimiter::OpType::kRead); - if (mode == RateLimiter::Mode::kWritesOnly) { - ASSERT_EQ(0, limiter.GetTotalBytesThrough(Env::IO_HIGH)); - } else { - ASSERT_EQ(1000, limiter.GetTotalBytesThrough(Env::IO_HIGH)); - } - - limiter.Request(1000 /* bytes */, Env::IO_HIGH, nullptr /* stats */, - RateLimiter::OpType::kWrite); - if (mode == RateLimiter::Mode::kAllIo) { - ASSERT_EQ(2000, limiter.GetTotalBytesThrough(Env::IO_HIGH)); - } else { - ASSERT_EQ(1000, limiter.GetTotalBytesThrough(Env::IO_HIGH)); - } - } -} - -TEST_F(RateLimiterTest, GeneratePriorityIterationOrder) { - std::unique_ptr limiter(NewGenericRateLimiter( - 200 /* rate_bytes_per_sec */, 1000 * 1000 /* refill_period_us */, - 10 /* fairness */)); - - bool possible_random_one_in_fairness_results_for_high_mid_pri[4][2] = { - {false, false}, {false, true}, {true, false}, {true, true}}; - std::vector possible_priority_iteration_orders[4] = { - {Env::IO_USER, Env::IO_HIGH, Env::IO_MID, Env::IO_LOW}, - {Env::IO_USER, Env::IO_HIGH, Env::IO_LOW, Env::IO_MID}, - {Env::IO_USER, Env::IO_MID, Env::IO_LOW, Env::IO_HIGH}, - {Env::IO_USER, Env::IO_LOW, Env::IO_MID, Env::IO_HIGH}}; - - for (int i = 0; i < 4; ++i) { - // These are variables for making sure the following callbacks are called - // and the assertion in the last callback is indeed excuted - bool high_pri_iterated_after_mid_low_pri_set = false; - bool mid_pri_itereated_after_low_pri_set = false; - bool pri_iteration_order_verified = false; - SyncPoint::GetInstance()->SetCallBack( - "GenericRateLimiter::GeneratePriorityIterationOrderLocked::" - "PostRandomOneInFairnessForHighPri", - [&](void* arg) { - bool* high_pri_iterated_after_mid_low_pri = (bool*)arg; - *high_pri_iterated_after_mid_low_pri = - possible_random_one_in_fairness_results_for_high_mid_pri[i][0]; - high_pri_iterated_after_mid_low_pri_set = true; - }); - - SyncPoint::GetInstance()->SetCallBack( - "GenericRateLimiter::GeneratePriorityIterationOrderLocked::" - "PostRandomOneInFairnessForMidPri", - [&](void* arg) { - bool* mid_pri_itereated_after_low_pri = (bool*)arg; - *mid_pri_itereated_after_low_pri = - possible_random_one_in_fairness_results_for_high_mid_pri[i][1]; - mid_pri_itereated_after_low_pri_set = true; - }); - - SyncPoint::GetInstance()->SetCallBack( - "GenericRateLimiter::GeneratePriorityIterationOrderLocked::" - "PreReturnPriIterationOrder", - [&](void* arg) { - std::vector* pri_iteration_order = - (std::vector*)arg; - EXPECT_EQ(*pri_iteration_order, possible_priority_iteration_orders[i]) - << "Failed to generate priority iteration order correctly when " - "high_pri_iterated_after_mid_low_pri = " - << possible_random_one_in_fairness_results_for_high_mid_pri[i][0] - << ", mid_pri_itereated_after_low_pri = " - << possible_random_one_in_fairness_results_for_high_mid_pri[i][1] - << std::endl; - pri_iteration_order_verified = true; - }); - - SyncPoint::GetInstance()->EnableProcessing(); - limiter->Request(200 /* request max bytes to drain so that refill and order - generation will be triggered every time - GenericRateLimiter::Request() is called */ - , - Env::IO_USER, nullptr /* stats */, - RateLimiter::OpType::kWrite); - ASSERT_EQ(high_pri_iterated_after_mid_low_pri_set, true); - ASSERT_EQ(mid_pri_itereated_after_low_pri_set, true); - ASSERT_EQ(pri_iteration_order_verified, true); - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->ClearCallBack( - "GenericRateLimiter::GeneratePriorityIterationOrderLocked::" - "PreReturnPriIterationOrder"); - SyncPoint::GetInstance()->ClearCallBack( - "GenericRateLimiter::GeneratePriorityIterationOrderLocked::" - "PostRandomOneInFairnessForMidPri"); - SyncPoint::GetInstance()->ClearCallBack( - "GenericRateLimiter::GeneratePriorityIterationOrderLocked::" - "PostRandomOneInFairnessForHighPri"); - } -} - -TEST_F(RateLimiterTest, Rate) { - auto* env = Env::Default(); - struct Arg { - Arg(int32_t _target_rate, int _burst) - : limiter(NewGenericRateLimiter(_target_rate /* rate_bytes_per_sec */, - 100 * 1000 /* refill_period_us */, - 10 /* fairness */)), - request_size(_target_rate / - 10 /* refill period here is 1/10 second */), - burst(_burst) {} - std::unique_ptr limiter; - int32_t request_size; - int burst; - }; - - auto writer = [](void* p) { - const auto& thread_clock = SystemClock::Default(); - auto* arg = static_cast(p); - // Test for 2 seconds - auto until = thread_clock->NowMicros() + 2 * 1000000; - Random r((uint32_t)(thread_clock->NowNanos() % - std::numeric_limits::max())); - while (thread_clock->NowMicros() < until) { - for (int i = 0; i < static_cast(r.Skewed(arg->burst * 2) + 1); ++i) { - arg->limiter->Request(r.Uniform(arg->request_size - 1) + 1, - Env::IO_USER, nullptr /* stats */, - RateLimiter::OpType::kWrite); - } - - for (int i = 0; i < static_cast(r.Skewed(arg->burst) + 1); ++i) { - arg->limiter->Request(r.Uniform(arg->request_size - 1) + 1, - Env::IO_HIGH, nullptr /* stats */, - RateLimiter::OpType::kWrite); - } - - for (int i = 0; i < static_cast(r.Skewed(arg->burst / 2 + 1) + 1); - ++i) { - arg->limiter->Request(r.Uniform(arg->request_size - 1) + 1, Env::IO_MID, - nullptr /* stats */, RateLimiter::OpType::kWrite); - } - - arg->limiter->Request(r.Uniform(arg->request_size - 1) + 1, Env::IO_LOW, - nullptr /* stats */, RateLimiter::OpType::kWrite); - } - }; - - int samples = 0; - int samples_at_minimum = 0; - - for (int i = 1; i <= 16; i *= 2) { - int32_t target = i * 1024 * 10; - Arg arg(target, i / 4 + 1); - int64_t old_total_bytes_through = 0; - for (int iter = 1; iter <= 2; ++iter) { - // second iteration changes the target dynamically - if (iter == 2) { - target *= 2; - arg.limiter->SetBytesPerSecond(target); - } - auto start = env->NowMicros(); - for (int t = 0; t < i; ++t) { - env->StartThread(writer, &arg); - } - env->WaitForJoin(); - - auto elapsed = env->NowMicros() - start; - double rate = - (arg.limiter->GetTotalBytesThrough() - old_total_bytes_through) * - 1000000.0 / elapsed; - old_total_bytes_through = arg.limiter->GetTotalBytesThrough(); - fprintf(stderr, - "request size [1 - %" PRIi32 "], limit %" PRIi32 - " KB/sec, actual rate: %lf KB/sec, elapsed %.2lf seconds\n", - arg.request_size - 1, target / 1024, rate / 1024, - elapsed / 1000000.0); - - ++samples; - if (rate / target >= 0.80) { - ++samples_at_minimum; - } - ASSERT_LE(rate / target, 1.25); - } - } - - // This can fail due to slow execution speed, like when using valgrind or in - // heavily loaded CI environments - bool skip_minimum_rate_check = -#if (defined(CIRCLECI) && defined(OS_MACOSX)) || defined(ROCKSDB_VALGRIND_RUN) - true; -#else - getenv("SANDCASTLE"); -#endif - if (skip_minimum_rate_check) { - fprintf(stderr, "Skipped minimum rate check (%d / %d passed)\n", - samples_at_minimum, samples); - } else { - ASSERT_EQ(samples_at_minimum, samples); - } -} - -TEST_F(RateLimiterTest, LimitChangeTest) { - // starvation test when limit changes to a smaller value - int64_t refill_period = 1000 * 1000; - auto* env = Env::Default(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - struct Arg { - Arg(int32_t _request_size, Env::IOPriority _pri, - std::shared_ptr _limiter) - : request_size(_request_size), pri(_pri), limiter(_limiter) {} - int32_t request_size; - Env::IOPriority pri; - std::shared_ptr limiter; - }; - - auto writer = [](void* p) { - auto* arg = static_cast(p); - arg->limiter->Request(arg->request_size, arg->pri, nullptr /* stats */, - RateLimiter::OpType::kWrite); - }; - - for (uint32_t i = 1; i <= 16; i <<= 1) { - int32_t target = i * 1024 * 10; - // refill per second - for (int iter = 0; iter < 2; iter++) { - std::shared_ptr limiter = - std::make_shared( - target, refill_period, 10, RateLimiter::Mode::kWritesOnly, - SystemClock::Default(), false /* auto_tuned */); - // After "GenericRateLimiter::Request:1" the mutex is held until the bytes - // are refilled. This test could be improved to change the limit when lock - // is released in `TimedWait()`. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"GenericRateLimiter::Request", - "RateLimiterTest::LimitChangeTest:changeLimitStart"}, - {"RateLimiterTest::LimitChangeTest:changeLimitEnd", - "GenericRateLimiter::Request:1"}}); - Arg arg(target, Env::IO_HIGH, limiter); - // The idea behind is to start a request first, then before it refills, - // update limit to a different value (2X/0.5X). No starvation should - // be guaranteed under any situation - // TODO(lightmark): more test cases are welcome. - env->StartThread(writer, &arg); - int32_t new_limit = (target << 1) >> (iter << 1); - TEST_SYNC_POINT("RateLimiterTest::LimitChangeTest:changeLimitStart"); - arg.limiter->SetBytesPerSecond(new_limit); - TEST_SYNC_POINT("RateLimiterTest::LimitChangeTest:changeLimitEnd"); - env->WaitForJoin(); - fprintf(stderr, - "[COMPLETE] request size %" PRIi32 " KB, new limit %" PRIi32 - "KB/sec, refill period %" PRIi64 " ms\n", - target / 1024, new_limit / 1024, refill_period / 1000); - } - } - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); -} - -TEST_F(RateLimiterTest, AutoTuneIncreaseWhenFull) { - const std::chrono::seconds kTimePerRefill(1); - const int kRefillsPerTune = 100; // needs to match util/rate_limiter.cc - - SpecialEnv special_env(Env::Default(), /*time_elapse_only_sleep*/ true); - - auto stats = CreateDBStatistics(); - std::unique_ptr rate_limiter(new GenericRateLimiter( - 1000 /* rate_bytes_per_sec */, - std::chrono::microseconds(kTimePerRefill).count(), 10 /* fairness */, - RateLimiter::Mode::kWritesOnly, special_env.GetSystemClock(), - true /* auto_tuned */)); - - // Rate limiter uses `CondVar::TimedWait()`, which does not have access to the - // `Env` to advance its time according to the fake wait duration. The - // workaround is to install a callback that advance the `Env`'s mock time. - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "GenericRateLimiter::Request:PostTimedWait", [&](void* arg) { - int64_t time_waited_us = *static_cast(arg); - special_env.SleepForMicroseconds(static_cast(time_waited_us)); - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); - - // verify rate limit increases after a sequence of periods where rate limiter - // is always drained - int64_t orig_bytes_per_sec = rate_limiter->GetSingleBurstBytes(); - rate_limiter->Request(orig_bytes_per_sec, Env::IO_HIGH, stats.get(), - RateLimiter::OpType::kWrite); - while (std::chrono::microseconds(special_env.NowMicros()) <= - kRefillsPerTune * kTimePerRefill) { - rate_limiter->Request(orig_bytes_per_sec, Env::IO_HIGH, stats.get(), - RateLimiter::OpType::kWrite); - } - int64_t new_bytes_per_sec = rate_limiter->GetSingleBurstBytes(); - ASSERT_GT(new_bytes_per_sec, orig_bytes_per_sec); - - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearCallBack( - "GenericRateLimiter::Request:PostTimedWait"); - - // decreases after a sequence of periods where rate limiter is not drained - orig_bytes_per_sec = new_bytes_per_sec; - special_env.SleepForMicroseconds(static_cast( - kRefillsPerTune * std::chrono::microseconds(kTimePerRefill).count())); - // make a request so tuner can be triggered - rate_limiter->Request(1 /* bytes */, Env::IO_HIGH, stats.get(), - RateLimiter::OpType::kWrite); - new_bytes_per_sec = rate_limiter->GetSingleBurstBytes(); - ASSERT_LT(new_bytes_per_sec, orig_bytes_per_sec); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/repeatable_thread_test.cc b/util/repeatable_thread_test.cc deleted file mode 100644 index 0b3e95464..000000000 --- a/util/repeatable_thread_test.cc +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "util/repeatable_thread.h" - -#include -#include - -#include "db/db_test_util.h" -#include "test_util/mock_time_env.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" - -class RepeatableThreadTest : public testing::Test { - public: - RepeatableThreadTest() - : mock_clock_(std::make_shared( - ROCKSDB_NAMESPACE::SystemClock::Default())) {} - - protected: - std::shared_ptr mock_clock_; -}; - -TEST_F(RepeatableThreadTest, TimedTest) { - constexpr uint64_t kSecond = 1000000; // 1s = 1000000us - constexpr int kIteration = 3; - const auto& clock = ROCKSDB_NAMESPACE::SystemClock::Default(); - ROCKSDB_NAMESPACE::port::Mutex mutex; - ROCKSDB_NAMESPACE::port::CondVar test_cv(&mutex); - int count = 0; - uint64_t prev_time = clock->NowMicros(); - ROCKSDB_NAMESPACE::RepeatableThread thread( - [&] { - ROCKSDB_NAMESPACE::MutexLock l(&mutex); - count++; - uint64_t now = clock->NowMicros(); - assert(count == 1 || prev_time + 1 * kSecond <= now); - prev_time = now; - if (count >= kIteration) { - test_cv.SignalAll(); - } - }, - "rt_test", clock.get(), 1 * kSecond); - // Wait for execution finish. - { - ROCKSDB_NAMESPACE::MutexLock l(&mutex); - while (count < kIteration) { - test_cv.Wait(); - } - } - - // Test cancel - thread.cancel(); -} - -TEST_F(RepeatableThreadTest, MockEnvTest) { - constexpr uint64_t kSecond = 1000000; // 1s = 1000000us - constexpr int kIteration = 3; - mock_clock_->SetCurrentTime(0); // in seconds - std::atomic count{0}; - -#if defined(OS_MACOSX) && !defined(NDEBUG) - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks(); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( - "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { - // Obtain the current (real) time in seconds and add 1000 extra seconds - // to ensure that RepeatableThread::wait invokes TimedWait with a time - // greater than (real) current time. This is to prevent the TimedWait - // function from returning immediately without sleeping and releasing - // the mutex on certain platforms, e.g. OS X. If TimedWait returns - // immediately, the mutex will not be released, and - // RepeatableThread::TEST_WaitForRun never has a chance to execute the - // callback which, in this case, updates the result returned by - // mock_clock->NowMicros. Consequently, RepeatableThread::wait cannot - // break out of the loop, causing test to hang. The extra 1000 seconds - // is a best-effort approach because there seems no reliable and - // deterministic way to provide the aforementioned guarantee. By the - // time RepeatableThread::wait is called, it is no guarantee that the - // delay + mock_clock->NowMicros will be greater than the current real - // time. However, 1000 seconds should be sufficient in most cases. - uint64_t time_us = *reinterpret_cast(arg); - if (time_us < mock_clock_->RealNowMicros()) { - *reinterpret_cast(arg) = - mock_clock_->RealNowMicros() + 1000; - } - }); - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); -#endif // OS_MACOSX && !NDEBUG - - ROCKSDB_NAMESPACE::RepeatableThread thread( - [&] { count++; }, "rt_test", mock_clock_.get(), 1 * kSecond, 1 * kSecond); - for (int i = 1; i <= kIteration; i++) { - // Bump current time - thread.TEST_WaitForRun([&] { mock_clock_->SetCurrentTime(i); }); - } - // Test function should be exectued exactly kIteraion times. - ASSERT_EQ(kIteration, count.load()); - - // Test cancel - thread.cancel(); -} - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/util/ribbon_test.cc b/util/ribbon_test.cc deleted file mode 100644 index 6519df3d5..000000000 --- a/util/ribbon_test.cc +++ /dev/null @@ -1,1308 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/system_clock.h" -#include "test_util/testharness.h" -#include "util/bloom_impl.h" -#include "util/coding.h" -#include "util/hash.h" -#include "util/ribbon_config.h" -#include "util/ribbon_impl.h" -#include "util/stop_watch.h" -#include "util/string_util.h" - -#ifndef GFLAGS -uint32_t FLAGS_thoroughness = 5; -uint32_t FLAGS_max_add = 0; -uint32_t FLAGS_min_check = 4000; -uint32_t FLAGS_max_check = 100000; -bool FLAGS_verbose = false; - -bool FLAGS_find_occ = false; -bool FLAGS_find_slot_occ = false; -double FLAGS_find_next_factor = 1.618; -uint32_t FLAGS_find_iters = 10000; -uint32_t FLAGS_find_min_slots = 128; -uint32_t FLAGS_find_max_slots = 1000000; - -bool FLAGS_optimize_homog = false; -uint32_t FLAGS_optimize_homog_slots = 30000000; -uint32_t FLAGS_optimize_homog_check = 200000; -double FLAGS_optimize_homog_granularity = 0.002; -#else -#include "util/gflags_compat.h" -using GFLAGS_NAMESPACE::ParseCommandLineFlags; -// Using 500 is a good test when you have time to be thorough. -// Default is for general RocksDB regression test runs. -DEFINE_uint32(thoroughness, 5, "iterations per configuration"); -DEFINE_uint32(max_add, 0, - "Add up to this number of entries to a single filter in " - "CompactnessAndBacktrackAndFpRate; 0 == reasonable default"); -DEFINE_uint32(min_check, 4000, - "Minimum number of novel entries for testing FP rate"); -DEFINE_uint32(max_check, 10000, - "Maximum number of novel entries for testing FP rate"); -DEFINE_bool(verbose, false, "Print extra details"); - -// Options for FindOccupancy, which is more of a tool than a test. -DEFINE_bool(find_occ, false, "whether to run the FindOccupancy tool"); -DEFINE_bool(find_slot_occ, false, - "whether to show individual slot occupancies with " - "FindOccupancy tool"); -DEFINE_double(find_next_factor, 1.618, - "factor to next num_slots for FindOccupancy"); -DEFINE_uint32(find_iters, 10000, "number of samples for FindOccupancy"); -DEFINE_uint32(find_min_slots, 128, "number of slots for FindOccupancy"); -DEFINE_uint32(find_max_slots, 1000000, "number of slots for FindOccupancy"); - -// Options for OptimizeHomogAtScale, which is more of a tool than a test. -DEFINE_bool(optimize_homog, false, - "whether to run the OptimizeHomogAtScale tool"); -DEFINE_uint32(optimize_homog_slots, 30000000, - "number of slots for OptimizeHomogAtScale"); -DEFINE_uint32(optimize_homog_check, 200000, - "number of queries for checking FP rate in OptimizeHomogAtScale"); -DEFINE_double( - optimize_homog_granularity, 0.002, - "overhead change between FP rate checking in OptimizeHomogAtScale"); - -#endif // GFLAGS - -template -class RibbonTypeParamTest : public ::testing::Test {}; - -class RibbonTest : public ::testing::Test {}; - -namespace { - -// Different ways of generating keys for testing - -// Generate semi-sequential keys -struct StandardKeyGen { - StandardKeyGen(const std::string& prefix, uint64_t id) - : id_(id), str_(prefix) { - ROCKSDB_NAMESPACE::PutFixed64(&str_, /*placeholder*/ 0); - } - - // Prefix (only one required) - StandardKeyGen& operator++() { - ++id_; - return *this; - } - - StandardKeyGen& operator+=(uint64_t i) { - id_ += i; - return *this; - } - - const std::string& operator*() { - // Use multiplication to mix things up a little in the key - ROCKSDB_NAMESPACE::EncodeFixed64(&str_[str_.size() - 8], - id_ * uint64_t{0x1500000001}); - return str_; - } - - bool operator==(const StandardKeyGen& other) { - // Same prefix is assumed - return id_ == other.id_; - } - bool operator!=(const StandardKeyGen& other) { - // Same prefix is assumed - return id_ != other.id_; - } - - uint64_t id_; - std::string str_; -}; - -// Generate small sequential keys, that can misbehave with sequential seeds -// as in https://github.com/Cyan4973/xxHash/issues/469. -// These keys are only heuristically unique, but that's OK with 64 bits, -// for testing purposes. -struct SmallKeyGen { - SmallKeyGen(const std::string& prefix, uint64_t id) : id_(id) { - // Hash the prefix for a heuristically unique offset - id_ += ROCKSDB_NAMESPACE::GetSliceHash64(prefix); - ROCKSDB_NAMESPACE::PutFixed64(&str_, id_); - } - - // Prefix (only one required) - SmallKeyGen& operator++() { - ++id_; - return *this; - } - - SmallKeyGen& operator+=(uint64_t i) { - id_ += i; - return *this; - } - - const std::string& operator*() { - ROCKSDB_NAMESPACE::EncodeFixed64(&str_[str_.size() - 8], id_); - return str_; - } - - bool operator==(const SmallKeyGen& other) { return id_ == other.id_; } - bool operator!=(const SmallKeyGen& other) { return id_ != other.id_; } - - uint64_t id_; - std::string str_; -}; - -template -struct Hash32KeyGenWrapper : public KeyGen { - Hash32KeyGenWrapper(const std::string& prefix, uint64_t id) - : KeyGen(prefix, id) {} - uint32_t operator*() { - auto& key = *static_cast(*this); - // unseeded - return ROCKSDB_NAMESPACE::GetSliceHash(key); - } -}; - -template -struct Hash64KeyGenWrapper : public KeyGen { - Hash64KeyGenWrapper(const std::string& prefix, uint64_t id) - : KeyGen(prefix, id) {} - uint64_t operator*() { - auto& key = *static_cast(*this); - // unseeded - return ROCKSDB_NAMESPACE::GetSliceHash64(key); - } -}; - -using ROCKSDB_NAMESPACE::ribbon::ConstructionFailureChance; - -const std::vector kFailureOnly50Pct = { - ROCKSDB_NAMESPACE::ribbon::kOneIn2}; - -const std::vector kFailureOnlyRare = { - ROCKSDB_NAMESPACE::ribbon::kOneIn1000}; - -const std::vector kFailureAll = { - ROCKSDB_NAMESPACE::ribbon::kOneIn2, ROCKSDB_NAMESPACE::ribbon::kOneIn20, - ROCKSDB_NAMESPACE::ribbon::kOneIn1000}; - -} // namespace - -using ROCKSDB_NAMESPACE::ribbon::ExpectedCollisionFpRate; -using ROCKSDB_NAMESPACE::ribbon::StandardHasher; -using ROCKSDB_NAMESPACE::ribbon::StandardRehasherAdapter; - -struct DefaultTypesAndSettings { - using CoeffRow = ROCKSDB_NAMESPACE::Unsigned128; - using ResultRow = uint8_t; - using Index = uint32_t; - using Hash = uint64_t; - using Seed = uint32_t; - using Key = ROCKSDB_NAMESPACE::Slice; - static constexpr bool kIsFilter = true; - static constexpr bool kHomogeneous = false; - static constexpr bool kFirstCoeffAlwaysOne = true; - static constexpr bool kUseSmash = false; - static constexpr bool kAllowZeroStarts = false; - static Hash HashFn(const Key& key, uint64_t raw_seed) { - // This version 0.7.2 preview of XXH3 (a.k.a. XXPH3) function does - // not pass SmallKeyGen tests below without some seed premixing from - // StandardHasher. See https://github.com/Cyan4973/xxHash/issues/469 - return ROCKSDB_NAMESPACE::Hash64(key.data(), key.size(), raw_seed); - } - // For testing - using KeyGen = StandardKeyGen; - static const std::vector& FailureChanceToTest() { - return kFailureAll; - } -}; - -using TypesAndSettings_Coeff128 = DefaultTypesAndSettings; -struct TypesAndSettings_Coeff128Smash : public DefaultTypesAndSettings { - static constexpr bool kUseSmash = true; -}; -struct TypesAndSettings_Coeff64 : public DefaultTypesAndSettings { - using CoeffRow = uint64_t; -}; -struct TypesAndSettings_Coeff64Smash : public TypesAndSettings_Coeff64 { - static constexpr bool kUseSmash = true; -}; -struct TypesAndSettings_Coeff64Smash0 : public TypesAndSettings_Coeff64Smash { - static constexpr bool kFirstCoeffAlwaysOne = false; -}; - -// Homogeneous Ribbon configurations -struct TypesAndSettings_Coeff128_Homog : public DefaultTypesAndSettings { - static constexpr bool kHomogeneous = true; - // Since our best construction success setting still has 1/1000 failure - // rate, the best FP rate we test is 1/256 - using ResultRow = uint8_t; - // Homogeneous only makes sense with sufficient slots for equivalent of - // almost sure construction success - static const std::vector& FailureChanceToTest() { - return kFailureOnlyRare; - } -}; -struct TypesAndSettings_Coeff128Smash_Homog - : public TypesAndSettings_Coeff128_Homog { - // Smash (extra time to save space) + Homog (extra space to save time) - // doesn't make much sense in practice, but we minimally test it - static constexpr bool kUseSmash = true; -}; -struct TypesAndSettings_Coeff64_Homog : public TypesAndSettings_Coeff128_Homog { - using CoeffRow = uint64_t; -}; -struct TypesAndSettings_Coeff64Smash_Homog - : public TypesAndSettings_Coeff64_Homog { - // Smash (extra time to save space) + Homog (extra space to save time) - // doesn't make much sense in practice, but we minimally test it - static constexpr bool kUseSmash = true; -}; - -// Less exhaustive mix of coverage, but still covering the most stressful case -// (only 50% construction success) -struct AbridgedTypesAndSettings : public DefaultTypesAndSettings { - static const std::vector& FailureChanceToTest() { - return kFailureOnly50Pct; - } -}; -struct TypesAndSettings_Result16 : public AbridgedTypesAndSettings { - using ResultRow = uint16_t; -}; -struct TypesAndSettings_Result32 : public AbridgedTypesAndSettings { - using ResultRow = uint32_t; -}; -struct TypesAndSettings_IndexSizeT : public AbridgedTypesAndSettings { - using Index = size_t; -}; -struct TypesAndSettings_Hash32 : public AbridgedTypesAndSettings { - using Hash = uint32_t; - static Hash HashFn(const Key& key, Hash raw_seed) { - // This MurmurHash1 function does not pass tests below without the - // seed premixing from StandardHasher. In fact, it needs more than - // just a multiplication mixer on the ordinal seed. - return ROCKSDB_NAMESPACE::Hash(key.data(), key.size(), raw_seed); - } -}; -struct TypesAndSettings_Hash32_Result16 : public AbridgedTypesAndSettings { - using ResultRow = uint16_t; -}; -struct TypesAndSettings_KeyString : public AbridgedTypesAndSettings { - using Key = std::string; -}; -struct TypesAndSettings_Seed8 : public AbridgedTypesAndSettings { - // This is not a generally recommended configuration. With the configured - // hash function, it would fail with SmallKeyGen due to insufficient - // independence among the seeds. - using Seed = uint8_t; -}; -struct TypesAndSettings_NoAlwaysOne : public AbridgedTypesAndSettings { - static constexpr bool kFirstCoeffAlwaysOne = false; -}; -struct TypesAndSettings_AllowZeroStarts : public AbridgedTypesAndSettings { - static constexpr bool kAllowZeroStarts = true; -}; -struct TypesAndSettings_Seed64 : public AbridgedTypesAndSettings { - using Seed = uint64_t; -}; -struct TypesAndSettings_Rehasher - : public StandardRehasherAdapter { - using KeyGen = Hash64KeyGenWrapper; -}; -struct TypesAndSettings_Rehasher_Result16 : public TypesAndSettings_Rehasher { - using ResultRow = uint16_t; -}; -struct TypesAndSettings_Rehasher_Result32 : public TypesAndSettings_Rehasher { - using ResultRow = uint32_t; -}; -struct TypesAndSettings_Rehasher_Seed64 - : public StandardRehasherAdapter { - using KeyGen = Hash64KeyGenWrapper; - // Note: 64-bit seed with Rehasher gives slightly better average reseeds -}; -struct TypesAndSettings_Rehasher32 - : public StandardRehasherAdapter { - using KeyGen = Hash32KeyGenWrapper; -}; -struct TypesAndSettings_Rehasher32_Coeff64 - : public TypesAndSettings_Rehasher32 { - using CoeffRow = uint64_t; -}; -struct TypesAndSettings_SmallKeyGen : public AbridgedTypesAndSettings { - // SmallKeyGen stresses the independence of different hash seeds - using KeyGen = SmallKeyGen; -}; -struct TypesAndSettings_Hash32_SmallKeyGen : public TypesAndSettings_Hash32 { - // SmallKeyGen stresses the independence of different hash seeds - using KeyGen = SmallKeyGen; -}; -struct TypesAndSettings_Coeff32 : public DefaultTypesAndSettings { - using CoeffRow = uint32_t; -}; -struct TypesAndSettings_Coeff32Smash : public TypesAndSettings_Coeff32 { - static constexpr bool kUseSmash = true; -}; -struct TypesAndSettings_Coeff16 : public DefaultTypesAndSettings { - using CoeffRow = uint16_t; -}; -struct TypesAndSettings_Coeff16Smash : public TypesAndSettings_Coeff16 { - static constexpr bool kUseSmash = true; -}; - -using TestTypesAndSettings = ::testing::Types< - TypesAndSettings_Coeff128, TypesAndSettings_Coeff128Smash, - TypesAndSettings_Coeff64, TypesAndSettings_Coeff64Smash, - TypesAndSettings_Coeff64Smash0, TypesAndSettings_Coeff128_Homog, - TypesAndSettings_Coeff128Smash_Homog, TypesAndSettings_Coeff64_Homog, - TypesAndSettings_Coeff64Smash_Homog, TypesAndSettings_Result16, - TypesAndSettings_Result32, TypesAndSettings_IndexSizeT, - TypesAndSettings_Hash32, TypesAndSettings_Hash32_Result16, - TypesAndSettings_KeyString, TypesAndSettings_Seed8, - TypesAndSettings_NoAlwaysOne, TypesAndSettings_AllowZeroStarts, - TypesAndSettings_Seed64, TypesAndSettings_Rehasher, - TypesAndSettings_Rehasher_Result16, TypesAndSettings_Rehasher_Result32, - TypesAndSettings_Rehasher_Seed64, TypesAndSettings_Rehasher32, - TypesAndSettings_Rehasher32_Coeff64, TypesAndSettings_SmallKeyGen, - TypesAndSettings_Hash32_SmallKeyGen, TypesAndSettings_Coeff32, - TypesAndSettings_Coeff32Smash, TypesAndSettings_Coeff16, - TypesAndSettings_Coeff16Smash>; -TYPED_TEST_CASE(RibbonTypeParamTest, TestTypesAndSettings); - -namespace { - -// For testing Poisson-distributed (or similar) statistics, get value for -// `stddevs_allowed` standard deviations above expected mean -// `expected_count`. -// (Poisson approximates Binomial only if probability of a trial being -// in the count is low.) -uint64_t PoissonUpperBound(double expected_count, double stddevs_allowed) { - return static_cast( - expected_count + stddevs_allowed * std::sqrt(expected_count) + 1.0); -} - -uint64_t PoissonLowerBound(double expected_count, double stddevs_allowed) { - return static_cast(std::max( - 0.0, expected_count - stddevs_allowed * std::sqrt(expected_count))); -} - -uint64_t FrequentPoissonUpperBound(double expected_count) { - // Allow up to 5.0 standard deviations for frequently checked statistics - return PoissonUpperBound(expected_count, 5.0); -} - -uint64_t FrequentPoissonLowerBound(double expected_count) { - return PoissonLowerBound(expected_count, 5.0); -} - -uint64_t InfrequentPoissonUpperBound(double expected_count) { - // Allow up to 3 standard deviations for infrequently checked statistics - return PoissonUpperBound(expected_count, 3.0); -} - -uint64_t InfrequentPoissonLowerBound(double expected_count) { - return PoissonLowerBound(expected_count, 3.0); -} - -} // namespace - -TYPED_TEST(RibbonTypeParamTest, CompactnessAndBacktrackAndFpRate) { - IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam); - IMPORT_RIBBON_IMPL_TYPES(TypeParam); - using KeyGen = typename TypeParam::KeyGen; - using ConfigHelper = - ROCKSDB_NAMESPACE::ribbon::BandingConfigHelper; - - if (sizeof(CoeffRow) < 8) { - ROCKSDB_GTEST_BYPASS("Not fully supported"); - return; - } - - const auto log2_thoroughness = - static_cast(ROCKSDB_NAMESPACE::FloorLog2(FLAGS_thoroughness)); - - // We are going to choose num_to_add using an exponential distribution, - // so that we have good representation of small-to-medium filters. - // Here we just pick some reasonable, practical upper bound based on - // kCoeffBits or option. - const double log_max_add = std::log( - FLAGS_max_add > 0 ? FLAGS_max_add - : static_cast(kCoeffBits * kCoeffBits) * - std::max(FLAGS_thoroughness, uint32_t{32})); - - // This needs to be enough below the minimum number of slots to get a - // reasonable number of samples with the minimum number of slots. - const double log_min_add = std::log(0.66 * SimpleSoln::RoundUpNumSlots(1)); - - ASSERT_GT(log_max_add, log_min_add); - - const double diff_log_add = log_max_add - log_min_add; - - for (ConstructionFailureChance cs : TypeParam::FailureChanceToTest()) { - double expected_reseeds; - switch (cs) { - default: - assert(false); - FALLTHROUGH_INTENDED; - case ROCKSDB_NAMESPACE::ribbon::kOneIn2: - fprintf(stderr, "== Failure: 50 percent\n"); - expected_reseeds = 1.0; - break; - case ROCKSDB_NAMESPACE::ribbon::kOneIn20: - fprintf(stderr, "== Failure: 95 percent\n"); - expected_reseeds = 0.053; - break; - case ROCKSDB_NAMESPACE::ribbon::kOneIn1000: - fprintf(stderr, "== Failure: 1/1000\n"); - expected_reseeds = 0.001; - break; - } - - uint64_t total_reseeds = 0; - uint64_t total_singles = 0; - uint64_t total_single_failures = 0; - uint64_t total_batch = 0; - uint64_t total_batch_successes = 0; - uint64_t total_fp_count = 0; - uint64_t total_added = 0; - uint64_t total_expand_trials = 0; - uint64_t total_expand_failures = 0; - double total_expand_overhead = 0.0; - - uint64_t soln_query_nanos = 0; - uint64_t soln_query_count = 0; - uint64_t bloom_query_nanos = 0; - uint64_t isoln_query_nanos = 0; - uint64_t isoln_query_count = 0; - - // Take different samples if you change thoroughness - ROCKSDB_NAMESPACE::Random32 rnd(FLAGS_thoroughness); - - for (uint32_t i = 0; i < FLAGS_thoroughness; ++i) { - // We are going to choose num_to_add using an exponential distribution - // as noted above, but instead of randomly choosing them, we generate - // samples linearly using the golden ratio, which ensures a nice spread - // even for a small number of samples, and starting with the minimum - // number of slots to ensure it is tested. - double log_add = - std::fmod(0.6180339887498948482 * diff_log_add * i, diff_log_add) + - log_min_add; - uint32_t num_to_add = static_cast(std::exp(log_add)); - - // Most of the time, test the Interleaved solution storage, but when - // we do we have to make num_slots a multiple of kCoeffBits. So - // sometimes we want to test without that limitation. - bool test_interleaved = (i % 7) != 6; - - // Compute num_slots, and re-adjust num_to_add to get as close as possible - // to next num_slots, to stress that num_slots in terms of construction - // success. Ensure at least one iteration: - Index num_slots = Index{0} - 1; - --num_to_add; - for (;;) { - Index next_num_slots = SimpleSoln::RoundUpNumSlots( - ConfigHelper::GetNumSlots(num_to_add + 1, cs)); - if (test_interleaved) { - next_num_slots = InterleavedSoln::RoundUpNumSlots(next_num_slots); - // assert idempotent - EXPECT_EQ(next_num_slots, - InterleavedSoln::RoundUpNumSlots(next_num_slots)); - } - // assert idempotent with InterleavedSoln::RoundUpNumSlots - EXPECT_EQ(next_num_slots, SimpleSoln::RoundUpNumSlots(next_num_slots)); - - if (next_num_slots > num_slots) { - break; - } - num_slots = next_num_slots; - ++num_to_add; - } - assert(num_slots < Index{0} - 1); - - total_added += num_to_add; - - std::string prefix; - ROCKSDB_NAMESPACE::PutFixed32(&prefix, rnd.Next()); - - // Batch that must be added - std::string added_str = prefix + "added"; - KeyGen keys_begin(added_str, 0); - KeyGen keys_end(added_str, num_to_add); - - // A couple more that will probably be added - KeyGen one_more(prefix + "more", 1); - KeyGen two_more(prefix + "more", 2); - - // Batch that may or may not be added - uint32_t batch_size = - static_cast(2.0 * std::sqrt(num_slots - num_to_add)); - if (batch_size < 10U) { - batch_size = 0; - } - std::string batch_str = prefix + "batch"; - KeyGen batch_begin(batch_str, 0); - KeyGen batch_end(batch_str, batch_size); - - // Batch never (successfully) added, but used for querying FP rate - std::string not_str = prefix + "not"; - KeyGen other_keys_begin(not_str, 0); - KeyGen other_keys_end(not_str, FLAGS_max_check); - - double overhead_ratio = 1.0 * num_slots / num_to_add; - if (FLAGS_verbose) { - fprintf(stderr, "Adding(%s) %u / %u Overhead: %g Batch size: %u\n", - test_interleaved ? "i" : "s", (unsigned)num_to_add, - (unsigned)num_slots, overhead_ratio, (unsigned)batch_size); - } - - // Vary bytes for InterleavedSoln to use number of solution columns - // from 0 to max allowed by ResultRow type (and used by SimpleSoln). - // Specifically include 0 and max, and otherwise skew toward max. - uint32_t max_ibytes = - static_cast(sizeof(ResultRow) * num_slots); - size_t ibytes; - if (i == 0) { - ibytes = 0; - } else if (i == 1) { - ibytes = max_ibytes; - } else { - // Skewed - ibytes = - std::max(rnd.Uniformish(max_ibytes), rnd.Uniformish(max_ibytes)); - } - std::unique_ptr idata(new char[ibytes]); - InterleavedSoln isoln(idata.get(), ibytes); - - SimpleSoln soln; - Hasher hasher; - bool first_single; - bool second_single; - bool batch_success; - { - Banding banding; - // Traditional solve for a fixed set. - ASSERT_TRUE( - banding.ResetAndFindSeedToSolve(num_slots, keys_begin, keys_end)); - - Index occupied_count = banding.GetOccupiedCount(); - Index more_added = 0; - - if (TypeParam::kHomogeneous || overhead_ratio < 1.01 || - batch_size == 0) { - // Homogeneous not compatible with backtracking because add - // doesn't fail. Small overhead ratio too packed to expect more - first_single = false; - second_single = false; - batch_success = false; - } else { - // Now to test backtracking, starting with guaranteed fail. By using - // the keys that will be used to test FP rate, we are then doing an - // extra check that after backtracking there are no remnants (e.g. in - // result side of banding) of these entries. - KeyGen other_keys_too_big_end = other_keys_begin; - other_keys_too_big_end += num_to_add; - banding.EnsureBacktrackSize(std::max(num_to_add, batch_size)); - EXPECT_FALSE(banding.AddRangeOrRollBack(other_keys_begin, - other_keys_too_big_end)); - EXPECT_EQ(occupied_count, banding.GetOccupiedCount()); - - // Check that we still have a good chance of adding a couple more - // individually - first_single = banding.Add(*one_more); - second_single = banding.Add(*two_more); - more_added += (first_single ? 1 : 0) + (second_single ? 1 : 0); - total_singles += 2U; - total_single_failures += 2U - more_added; - - // Or as a batch - batch_success = banding.AddRangeOrRollBack(batch_begin, batch_end); - ++total_batch; - if (batch_success) { - more_added += batch_size; - ++total_batch_successes; - } - EXPECT_LE(banding.GetOccupiedCount(), occupied_count + more_added); - } - - // Also verify that redundant adds are OK (no effect) - ASSERT_TRUE( - banding.AddRange(keys_begin, KeyGen(added_str, num_to_add / 8))); - EXPECT_LE(banding.GetOccupiedCount(), occupied_count + more_added); - - // Now back-substitution - soln.BackSubstFrom(banding); - if (test_interleaved) { - isoln.BackSubstFrom(banding); - } - - Seed reseeds = banding.GetOrdinalSeed(); - total_reseeds += reseeds; - - EXPECT_LE(reseeds, 8 + log2_thoroughness); - if (reseeds > log2_thoroughness + 1) { - fprintf( - stderr, "%s high reseeds at %u, %u/%u: %u\n", - reseeds > log2_thoroughness + 8 ? "ERROR Extremely" : "Somewhat", - static_cast(i), static_cast(num_to_add), - static_cast(num_slots), static_cast(reseeds)); - } - - if (reseeds > 0) { - // "Expand" test: given a failed construction, how likely is it to - // pass with same seed and more slots. At each step, we increase - // enough to ensure there is at least one shift within each coeff - // block. - ++total_expand_trials; - Index expand_count = 0; - Index ex_slots = num_slots; - banding.SetOrdinalSeed(0); - for (;; ++expand_count) { - ASSERT_LE(expand_count, log2_thoroughness); - ex_slots += ex_slots / kCoeffBits; - if (test_interleaved) { - ex_slots = InterleavedSoln::RoundUpNumSlots(ex_slots); - } - banding.Reset(ex_slots); - bool success = banding.AddRange(keys_begin, keys_end); - if (success) { - break; - } - } - total_expand_failures += expand_count; - total_expand_overhead += 1.0 * (ex_slots - num_slots) / num_slots; - } - - hasher.SetOrdinalSeed(reseeds); - } - // soln and hasher now independent of Banding object - - // Verify keys added - KeyGen cur = keys_begin; - while (cur != keys_end) { - ASSERT_TRUE(soln.FilterQuery(*cur, hasher)); - ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*cur, hasher)); - ++cur; - } - // We (maybe) snuck these in! - if (first_single) { - ASSERT_TRUE(soln.FilterQuery(*one_more, hasher)); - ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*one_more, hasher)); - } - if (second_single) { - ASSERT_TRUE(soln.FilterQuery(*two_more, hasher)); - ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*two_more, hasher)); - } - if (batch_success) { - cur = batch_begin; - while (cur != batch_end) { - ASSERT_TRUE(soln.FilterQuery(*cur, hasher)); - ASSERT_TRUE(!test_interleaved || isoln.FilterQuery(*cur, hasher)); - ++cur; - } - } - - // Check FP rate (depends only on number of result bits == solution - // columns) - Index fp_count = 0; - cur = other_keys_begin; - { - ROCKSDB_NAMESPACE::StopWatchNano timer( - ROCKSDB_NAMESPACE::SystemClock::Default().get(), true); - while (cur != other_keys_end) { - bool fp = soln.FilterQuery(*cur, hasher); - fp_count += fp ? 1 : 0; - ++cur; - } - soln_query_nanos += timer.ElapsedNanos(); - soln_query_count += FLAGS_max_check; - } - { - double expected_fp_count = soln.ExpectedFpRate() * FLAGS_max_check; - // For expected FP rate, also include false positives due to collisions - // in Hash value. (Negligible for 64-bit, can matter for 32-bit.) - double correction = - FLAGS_max_check * ExpectedCollisionFpRate(hasher, num_to_add); - - // NOTE: rare violations expected with kHomogeneous - EXPECT_LE(fp_count, - FrequentPoissonUpperBound(expected_fp_count + correction)); - EXPECT_GE(fp_count, - FrequentPoissonLowerBound(expected_fp_count + correction)); - } - total_fp_count += fp_count; - - // And also check FP rate for isoln - if (test_interleaved) { - Index ifp_count = 0; - cur = other_keys_begin; - ROCKSDB_NAMESPACE::StopWatchNano timer( - ROCKSDB_NAMESPACE::SystemClock::Default().get(), true); - while (cur != other_keys_end) { - ifp_count += isoln.FilterQuery(*cur, hasher) ? 1 : 0; - ++cur; - } - isoln_query_nanos += timer.ElapsedNanos(); - isoln_query_count += FLAGS_max_check; - { - double expected_fp_count = isoln.ExpectedFpRate() * FLAGS_max_check; - // For expected FP rate, also include false positives due to - // collisions in Hash value. (Negligible for 64-bit, can matter for - // 32-bit.) - double correction = - FLAGS_max_check * ExpectedCollisionFpRate(hasher, num_to_add); - - // NOTE: rare violations expected with kHomogeneous - EXPECT_LE(ifp_count, - FrequentPoissonUpperBound(expected_fp_count + correction)); - - // FIXME: why sometimes can we slightly "beat the odds"? - // (0.95 factor should not be needed) - EXPECT_GE(ifp_count, FrequentPoissonLowerBound( - 0.95 * expected_fp_count + correction)); - } - // Since the bits used in isoln are a subset of the bits used in soln, - // it cannot have fewer FPs - EXPECT_GE(ifp_count, fp_count); - } - - // And compare to Bloom time, for fun - if (ibytes >= /* minimum Bloom impl bytes*/ 64) { - Index bfp_count = 0; - cur = other_keys_begin; - ROCKSDB_NAMESPACE::StopWatchNano timer( - ROCKSDB_NAMESPACE::SystemClock::Default().get(), true); - while (cur != other_keys_end) { - uint64_t h = hasher.GetHash(*cur); - uint32_t h1 = ROCKSDB_NAMESPACE::Lower32of64(h); - uint32_t h2 = sizeof(Hash) >= 8 ? ROCKSDB_NAMESPACE::Upper32of64(h) - : h1 * 0x9e3779b9; - bfp_count += - ROCKSDB_NAMESPACE::FastLocalBloomImpl::HashMayMatch( - h1, h2, static_cast(ibytes), 6, idata.get()) - ? 1 - : 0; - ++cur; - } - bloom_query_nanos += timer.ElapsedNanos(); - // ensure bfp_count is used - ASSERT_LT(bfp_count, FLAGS_max_check); - } - } - - // "outside" == key not in original set so either negative or false positive - fprintf(stderr, - "Simple outside query, hot, incl hashing, ns/key: %g\n", - 1.0 * soln_query_nanos / soln_query_count); - fprintf(stderr, - "Interleaved outside query, hot, incl hashing, ns/key: %g\n", - 1.0 * isoln_query_nanos / isoln_query_count); - fprintf(stderr, - "Bloom outside query, hot, incl hashing, ns/key: %g\n", - 1.0 * bloom_query_nanos / soln_query_count); - - if (TypeParam::kHomogeneous) { - EXPECT_EQ(total_reseeds, 0U); - } else { - double average_reseeds = 1.0 * total_reseeds / FLAGS_thoroughness; - fprintf(stderr, "Average re-seeds: %g\n", average_reseeds); - // Values above were chosen to target around 50% chance of encoding - // success rate (average of 1.0 re-seeds) or slightly better. But 1.15 is - // also close enough. - EXPECT_LE(total_reseeds, - InfrequentPoissonUpperBound(1.15 * expected_reseeds * - FLAGS_thoroughness)); - // Would use 0.85 here instead of 0.75, but - // TypesAndSettings_Hash32_SmallKeyGen can "beat the odds" because of - // sequential keys with a small, cheap hash function. We accept that - // there are surely inputs that are somewhat bad for this setup, but - // these somewhat good inputs are probably more likely. - EXPECT_GE(total_reseeds, - InfrequentPoissonLowerBound(0.75 * expected_reseeds * - FLAGS_thoroughness)); - } - - if (total_expand_trials > 0) { - double average_expand_failures = - 1.0 * total_expand_failures / total_expand_trials; - fprintf(stderr, "Average expand failures, and overhead: %g, %g\n", - average_expand_failures, - total_expand_overhead / total_expand_trials); - // Seems to be a generous allowance - EXPECT_LE(total_expand_failures, - InfrequentPoissonUpperBound(1.0 * total_expand_trials)); - } else { - fprintf(stderr, "Average expand failures: N/A\n"); - } - - if (total_singles > 0) { - double single_failure_rate = 1.0 * total_single_failures / total_singles; - fprintf(stderr, "Add'l single, failure rate: %g\n", single_failure_rate); - // A rough bound (one sided) based on nothing in particular - double expected_single_failures = 1.0 * total_singles / - (sizeof(CoeffRow) == 16 ? 128 - : TypeParam::kUseSmash ? 64 - : 32); - EXPECT_LE(total_single_failures, - InfrequentPoissonUpperBound(expected_single_failures)); - } - - if (total_batch > 0) { - // Counting successes here for Poisson to approximate the Binomial - // distribution. - // A rough bound (one sided) based on nothing in particular. - double expected_batch_successes = 1.0 * total_batch / 2; - uint64_t lower_bound = - InfrequentPoissonLowerBound(expected_batch_successes); - fprintf(stderr, "Add'l batch, success rate: %g (>= %g)\n", - 1.0 * total_batch_successes / total_batch, - 1.0 * lower_bound / total_batch); - EXPECT_GE(total_batch_successes, lower_bound); - } - - { - uint64_t total_checked = uint64_t{FLAGS_max_check} * FLAGS_thoroughness; - double expected_total_fp_count = - total_checked * std::pow(0.5, 8U * sizeof(ResultRow)); - // For expected FP rate, also include false positives due to collisions - // in Hash value. (Negligible for 64-bit, can matter for 32-bit.) - double average_added = 1.0 * total_added / FLAGS_thoroughness; - expected_total_fp_count += - total_checked * ExpectedCollisionFpRate(Hasher(), average_added); - - uint64_t upper_bound = - InfrequentPoissonUpperBound(expected_total_fp_count); - uint64_t lower_bound = - InfrequentPoissonLowerBound(expected_total_fp_count); - fprintf(stderr, "Average FP rate: %g (~= %g, <= %g, >= %g)\n", - 1.0 * total_fp_count / total_checked, - expected_total_fp_count / total_checked, - 1.0 * upper_bound / total_checked, - 1.0 * lower_bound / total_checked); - EXPECT_LE(total_fp_count, upper_bound); - EXPECT_GE(total_fp_count, lower_bound); - } - } -} - -TYPED_TEST(RibbonTypeParamTest, Extremes) { - IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam); - IMPORT_RIBBON_IMPL_TYPES(TypeParam); - using KeyGen = typename TypeParam::KeyGen; - - size_t bytes = 128 * 1024; - std::unique_ptr buf(new char[bytes]); - InterleavedSoln isoln(buf.get(), bytes); - SimpleSoln soln; - Hasher hasher; - Banding banding; - - // ######################################## - // Add zero keys to minimal number of slots - KeyGen begin_and_end("foo", 123); - ASSERT_TRUE(banding.ResetAndFindSeedToSolve( - /*slots*/ kCoeffBits, begin_and_end, begin_and_end, /*first seed*/ 0, - /* seed mask*/ 0)); - - soln.BackSubstFrom(banding); - isoln.BackSubstFrom(banding); - - // Because there's plenty of memory, we expect the interleaved solution to - // use maximum supported columns (same as simple solution) - ASSERT_EQ(isoln.GetUpperNumColumns(), 8U * sizeof(ResultRow)); - ASSERT_EQ(isoln.GetUpperStartBlock(), 0U); - - // Somewhat oddly, we expect same FP rate as if we had essentially filled - // up the slots. - KeyGen other_keys_begin("not", 0); - KeyGen other_keys_end("not", FLAGS_max_check); - - Index fp_count = 0; - KeyGen cur = other_keys_begin; - while (cur != other_keys_end) { - bool isoln_query_result = isoln.FilterQuery(*cur, hasher); - bool soln_query_result = soln.FilterQuery(*cur, hasher); - // Solutions are equivalent - ASSERT_EQ(isoln_query_result, soln_query_result); - if (!TypeParam::kHomogeneous) { - // And in fact we only expect an FP when ResultRow is 0 - // (except Homogeneous) - ASSERT_EQ(soln_query_result, hasher.GetResultRowFromHash( - hasher.GetHash(*cur)) == ResultRow{0}); - } - fp_count += soln_query_result ? 1 : 0; - ++cur; - } - { - ASSERT_EQ(isoln.ExpectedFpRate(), soln.ExpectedFpRate()); - double expected_fp_count = isoln.ExpectedFpRate() * FLAGS_max_check; - EXPECT_LE(fp_count, InfrequentPoissonUpperBound(expected_fp_count)); - if (TypeParam::kHomogeneous) { - // Pseudorandom garbage in Homogeneous filter can "beat the odds" if - // nothing added - } else { - EXPECT_GE(fp_count, InfrequentPoissonLowerBound(expected_fp_count)); - } - } - - // ###################################################### - // Use zero bytes for interleaved solution (key(s) added) - - // Add one key - KeyGen key_begin("added", 0); - KeyGen key_end("added", 1); - ASSERT_TRUE(banding.ResetAndFindSeedToSolve( - /*slots*/ kCoeffBits, key_begin, key_end, /*first seed*/ 0, - /* seed mask*/ 0)); - - InterleavedSoln isoln2(nullptr, /*bytes*/ 0); - - isoln2.BackSubstFrom(banding); - - ASSERT_EQ(isoln2.GetUpperNumColumns(), 0U); - ASSERT_EQ(isoln2.GetUpperStartBlock(), 0U); - - // All queries return true - ASSERT_TRUE(isoln2.FilterQuery(*other_keys_begin, hasher)); - ASSERT_EQ(isoln2.ExpectedFpRate(), 1.0); -} - -TEST(RibbonTest, AllowZeroStarts) { - IMPORT_RIBBON_TYPES_AND_SETTINGS(TypesAndSettings_AllowZeroStarts); - IMPORT_RIBBON_IMPL_TYPES(TypesAndSettings_AllowZeroStarts); - using KeyGen = StandardKeyGen; - - InterleavedSoln isoln(nullptr, /*bytes*/ 0); - SimpleSoln soln; - Hasher hasher; - Banding banding; - - KeyGen begin("foo", 0); - KeyGen end("foo", 1); - // Can't add 1 entry - ASSERT_FALSE(banding.ResetAndFindSeedToSolve(/*slots*/ 0, begin, end)); - - KeyGen begin_and_end("foo", 123); - // Can add 0 entries - ASSERT_TRUE(banding.ResetAndFindSeedToSolve(/*slots*/ 0, begin_and_end, - begin_and_end)); - - Seed reseeds = banding.GetOrdinalSeed(); - ASSERT_EQ(reseeds, 0U); - hasher.SetOrdinalSeed(reseeds); - - // Can construct 0-slot solutions - isoln.BackSubstFrom(banding); - soln.BackSubstFrom(banding); - - // Should always return false - ASSERT_FALSE(isoln.FilterQuery(*begin, hasher)); - ASSERT_FALSE(soln.FilterQuery(*begin, hasher)); - - // And report that in FP rate - ASSERT_EQ(isoln.ExpectedFpRate(), 0.0); - ASSERT_EQ(soln.ExpectedFpRate(), 0.0); -} - -TEST(RibbonTest, RawAndOrdinalSeeds) { - StandardHasher hasher64; - StandardHasher hasher64_32; - StandardHasher hasher32; - StandardHasher hasher8; - - for (uint32_t limit : {0xffU, 0xffffU}) { - std::vector seen(limit + 1); - for (uint32_t i = 0; i < limit; ++i) { - hasher64.SetOrdinalSeed(i); - auto raw64 = hasher64.GetRawSeed(); - hasher32.SetOrdinalSeed(i); - auto raw32 = hasher32.GetRawSeed(); - hasher8.SetOrdinalSeed(static_cast(i)); - auto raw8 = hasher8.GetRawSeed(); - { - hasher64_32.SetOrdinalSeed(i); - auto raw64_32 = hasher64_32.GetRawSeed(); - ASSERT_EQ(raw64_32, raw32); // Same size seed - } - if (i == 0) { - // Documented that ordinal seed 0 == raw seed 0 - ASSERT_EQ(raw64, 0U); - ASSERT_EQ(raw32, 0U); - ASSERT_EQ(raw8, 0U); - } else { - // Extremely likely that upper bits are set - ASSERT_GT(raw64, raw32); - ASSERT_GT(raw32, raw8); - } - // Hashers agree on lower bits - ASSERT_EQ(static_cast(raw64), raw32); - ASSERT_EQ(static_cast(raw32), raw8); - - // The translation is one-to-one for this size prefix - uint32_t v = static_cast(raw32 & limit); - ASSERT_EQ(raw64 & limit, v); - ASSERT_FALSE(seen[v]); - seen[v] = true; - } - } -} - -namespace { - -struct PhsfInputGen { - PhsfInputGen(const std::string& prefix, uint64_t id) : id_(id) { - val_.first = prefix; - ROCKSDB_NAMESPACE::PutFixed64(&val_.first, /*placeholder*/ 0); - } - - // Prefix (only one required) - PhsfInputGen& operator++() { - ++id_; - return *this; - } - - const std::pair& operator*() { - // Use multiplication to mix things up a little in the key - ROCKSDB_NAMESPACE::EncodeFixed64(&val_.first[val_.first.size() - 8], - id_ * uint64_t{0x1500000001}); - // Occasionally repeat values etc. - val_.second = static_cast(id_ * 7 / 8); - return val_; - } - - const std::pair* operator->() { return &**this; } - - bool operator==(const PhsfInputGen& other) { - // Same prefix is assumed - return id_ == other.id_; - } - bool operator!=(const PhsfInputGen& other) { - // Same prefix is assumed - return id_ != other.id_; - } - - uint64_t id_; - std::pair val_; -}; - -struct PhsfTypesAndSettings : public DefaultTypesAndSettings { - static constexpr bool kIsFilter = false; -}; -} // namespace - -TEST(RibbonTest, PhsfBasic) { - IMPORT_RIBBON_TYPES_AND_SETTINGS(PhsfTypesAndSettings); - IMPORT_RIBBON_IMPL_TYPES(PhsfTypesAndSettings); - - Index num_slots = 12800; - Index num_to_add = static_cast(num_slots / 1.02); - - PhsfInputGen begin("in", 0); - PhsfInputGen end("in", num_to_add); - - std::unique_ptr idata(new char[/*bytes*/ num_slots]); - InterleavedSoln isoln(idata.get(), /*bytes*/ num_slots); - SimpleSoln soln; - Hasher hasher; - - { - Banding banding; - ASSERT_TRUE(banding.ResetAndFindSeedToSolve(num_slots, begin, end)); - - soln.BackSubstFrom(banding); - isoln.BackSubstFrom(banding); - - hasher.SetOrdinalSeed(banding.GetOrdinalSeed()); - } - - for (PhsfInputGen cur = begin; cur != end; ++cur) { - ASSERT_EQ(cur->second, soln.PhsfQuery(cur->first, hasher)); - ASSERT_EQ(cur->second, isoln.PhsfQuery(cur->first, hasher)); - } -} - -// Not a real test, but a tool used to build APIs in ribbon_config.h -TYPED_TEST(RibbonTypeParamTest, FindOccupancy) { - IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam); - IMPORT_RIBBON_IMPL_TYPES(TypeParam); - using KeyGen = typename TypeParam::KeyGen; - - if (!FLAGS_find_occ) { - ROCKSDB_GTEST_BYPASS("Tool disabled during unit test runs"); - return; - } - - KeyGen cur(std::to_string(testing::UnitTest::GetInstance()->random_seed()), - 0); - - Banding banding; - Index num_slots = InterleavedSoln::RoundUpNumSlots(FLAGS_find_min_slots); - Index max_slots = InterleavedSoln::RoundUpNumSlots(FLAGS_find_max_slots); - while (num_slots <= max_slots) { - std::map rem_histogram; - std::map slot_histogram; - if (FLAGS_find_slot_occ) { - for (Index i = 0; i < kCoeffBits; ++i) { - slot_histogram[i] = 0; - slot_histogram[num_slots - 1 - i] = 0; - slot_histogram[num_slots / 2 - kCoeffBits / 2 + i] = 0; - } - } - uint64_t total_added = 0; - for (uint32_t i = 0; i < FLAGS_find_iters; ++i) { - banding.Reset(num_slots); - uint32_t j = 0; - KeyGen end = cur; - end += num_slots + num_slots / 10; - for (; cur != end; ++cur) { - if (banding.Add(*cur)) { - ++j; - } else { - break; - } - } - total_added += j; - for (auto& slot : slot_histogram) { - slot.second += banding.IsOccupied(slot.first); - } - - int32_t bucket = - static_cast(num_slots) - static_cast(j); - rem_histogram[bucket]++; - if (FLAGS_verbose) { - fprintf(stderr, "num_slots: %u i: %u / %u avg_overhead: %g\r", - static_cast(num_slots), static_cast(i), - static_cast(FLAGS_find_iters), - 1.0 * (i + 1) * num_slots / total_added); - } - } - if (FLAGS_verbose) { - fprintf(stderr, "\n"); - } - - uint32_t cumulative = 0; - - double p50_rem = 0; - double p95_rem = 0; - double p99_9_rem = 0; - - for (auto& h : rem_histogram) { - double before = 1.0 * cumulative / FLAGS_find_iters; - double not_after = 1.0 * (cumulative + h.second) / FLAGS_find_iters; - if (FLAGS_verbose) { - fprintf(stderr, "overhead: %g before: %g not_after: %g\n", - 1.0 * num_slots / (num_slots - h.first), before, not_after); - } - cumulative += h.second; - if (before < 0.5 && 0.5 <= not_after) { - // fake it with linear interpolation - double portion = (0.5 - before) / (not_after - before); - p50_rem = h.first + portion; - } else if (before < 0.95 && 0.95 <= not_after) { - // fake it with linear interpolation - double portion = (0.95 - before) / (not_after - before); - p95_rem = h.first + portion; - } else if (before < 0.999 && 0.999 <= not_after) { - // fake it with linear interpolation - double portion = (0.999 - before) / (not_after - before); - p99_9_rem = h.first + portion; - } - } - for (auto& slot : slot_histogram) { - fprintf(stderr, "slot[%u] occupied: %g\n", (unsigned)slot.first, - 1.0 * slot.second / FLAGS_find_iters); - } - - double mean_rem = - (1.0 * FLAGS_find_iters * num_slots - total_added) / FLAGS_find_iters; - fprintf( - stderr, - "num_slots: %u iters: %u mean_ovr: %g p50_ovr: %g p95_ovr: %g " - "p99.9_ovr: %g mean_rem: %g p50_rem: %g p95_rem: %g p99.9_rem: %g\n", - static_cast(num_slots), - static_cast(FLAGS_find_iters), - 1.0 * num_slots / (num_slots - mean_rem), - 1.0 * num_slots / (num_slots - p50_rem), - 1.0 * num_slots / (num_slots - p95_rem), - 1.0 * num_slots / (num_slots - p99_9_rem), mean_rem, p50_rem, p95_rem, - p99_9_rem); - - num_slots = std::max( - num_slots + 1, static_cast(num_slots * FLAGS_find_next_factor)); - num_slots = InterleavedSoln::RoundUpNumSlots(num_slots); - } -} - -// Not a real test, but a tool to understand Homogeneous Ribbon -// behavior (TODO: configuration APIs & tests) -TYPED_TEST(RibbonTypeParamTest, OptimizeHomogAtScale) { - IMPORT_RIBBON_TYPES_AND_SETTINGS(TypeParam); - IMPORT_RIBBON_IMPL_TYPES(TypeParam); - using KeyGen = typename TypeParam::KeyGen; - - if (!FLAGS_optimize_homog) { - ROCKSDB_GTEST_BYPASS("Tool disabled during unit test runs"); - return; - } - - if (!TypeParam::kHomogeneous) { - ROCKSDB_GTEST_BYPASS("Only for Homogeneous Ribbon"); - return; - } - - KeyGen cur(std::to_string(testing::UnitTest::GetInstance()->random_seed()), - 0); - - Banding banding; - Index num_slots = SimpleSoln::RoundUpNumSlots(FLAGS_optimize_homog_slots); - banding.Reset(num_slots); - - // This and "band_ovr" is the "allocated overhead", or slots over added. - // It does not take into account FP rates. - double target_overhead = 1.20; - uint32_t num_added = 0; - - do { - do { - (void)banding.Add(*cur); - ++cur; - ++num_added; - } while (1.0 * num_slots / num_added > target_overhead); - - SimpleSoln soln; - soln.BackSubstFrom(banding); - - std::array fp_counts_by_cols; - fp_counts_by_cols.fill(0U); - for (uint32_t i = 0; i < FLAGS_optimize_homog_check; ++i) { - ResultRow r = soln.PhsfQuery(*cur, banding); - ++cur; - for (size_t j = 0; j < fp_counts_by_cols.size(); ++j) { - if ((r & 1) == 1) { - break; - } - fp_counts_by_cols[j]++; - r /= 2; - } - } - fprintf(stderr, "band_ovr: %g ", 1.0 * num_slots / num_added); - for (unsigned j = 0; j < fp_counts_by_cols.size(); ++j) { - double inv_fp_rate = - 1.0 * FLAGS_optimize_homog_check / fp_counts_by_cols[j]; - double equiv_cols = std::log(inv_fp_rate) * 1.4426950409; - // Overhead vs. information-theoretic minimum based on observed - // FP rate (subject to sampling error, especially for low FP rates) - double actual_overhead = - 1.0 * (j + 1) * num_slots / (equiv_cols * num_added); - fprintf(stderr, "ovr_%u: %g ", j + 1, actual_overhead); - } - fprintf(stderr, "\n"); - target_overhead -= FLAGS_optimize_homog_granularity; - } while (target_overhead > 1.0); -} - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); -#ifdef GFLAGS - ParseCommandLineFlags(&argc, &argv, true); -#endif // GFLAGS - return RUN_ALL_TESTS(); -} diff --git a/util/slice_test.cc b/util/slice_test.cc deleted file mode 100644 index 010ded3d8..000000000 --- a/util/slice_test.cc +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "rocksdb/slice.h" - -#include - -#include "port/port.h" -#include "port/stack_trace.h" -#include "rocksdb/data_structure.h" -#include "rocksdb/types.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" - -namespace ROCKSDB_NAMESPACE { - -TEST(SliceTest, StringView) { - std::string s = "foo"; - std::string_view sv = s; - ASSERT_EQ(Slice(s), Slice(sv)); - ASSERT_EQ(Slice(s), Slice(std::move(sv))); -} - -// Use this to keep track of the cleanups that were actually performed -void Multiplier(void* arg1, void* arg2) { - int* res = reinterpret_cast(arg1); - int* num = reinterpret_cast(arg2); - *res *= *num; -} - -class PinnableSliceTest : public testing::Test { - public: - void AssertSameData(const std::string& expected, const PinnableSlice& slice) { - std::string got; - got.assign(slice.data(), slice.size()); - ASSERT_EQ(expected, got); - } -}; - -// Test that the external buffer is moved instead of being copied. -TEST_F(PinnableSliceTest, MoveExternalBuffer) { - Slice s("123"); - std::string buf; - PinnableSlice v1(&buf); - v1.PinSelf(s); - - PinnableSlice v2(std::move(v1)); - ASSERT_EQ(buf.data(), v2.data()); - ASSERT_EQ(&buf, v2.GetSelf()); - - PinnableSlice v3; - v3 = std::move(v2); - ASSERT_EQ(buf.data(), v3.data()); - ASSERT_EQ(&buf, v3.GetSelf()); -} - -TEST_F(PinnableSliceTest, Move) { - int n2 = 2; - int res = 1; - const std::string const_str1 = "123"; - const std::string const_str2 = "ABC"; - Slice slice1(const_str1); - Slice slice2(const_str2); - - { - // Test move constructor on a pinned slice. - res = 1; - PinnableSlice v1; - v1.PinSlice(slice1, Multiplier, &res, &n2); - PinnableSlice v2(std::move(v1)); - - // Since v1's Cleanable has been moved to v2, - // no cleanup should happen in Reset. - v1.Reset(); - ASSERT_EQ(1, res); - - AssertSameData(const_str1, v2); - } - // v2 is cleaned up. - ASSERT_EQ(2, res); - - { - // Test move constructor on an unpinned slice. - PinnableSlice v1; - v1.PinSelf(slice1); - PinnableSlice v2(std::move(v1)); - - AssertSameData(const_str1, v2); - } - - { - // Test move assignment from a pinned slice to - // another pinned slice. - res = 1; - PinnableSlice v1; - v1.PinSlice(slice1, Multiplier, &res, &n2); - PinnableSlice v2; - v2.PinSlice(slice2, Multiplier, &res, &n2); - v2 = std::move(v1); - - // v2's Cleanable will be Reset before moving - // anything from v1. - ASSERT_EQ(2, res); - // Since v1's Cleanable has been moved to v2, - // no cleanup should happen in Reset. - v1.Reset(); - ASSERT_EQ(2, res); - - AssertSameData(const_str1, v2); - } - // The Cleanable moved from v1 to v2 will be Reset. - ASSERT_EQ(4, res); - - { - // Test move assignment from a pinned slice to - // an unpinned slice. - res = 1; - PinnableSlice v1; - v1.PinSlice(slice1, Multiplier, &res, &n2); - PinnableSlice v2; - v2.PinSelf(slice2); - v2 = std::move(v1); - - // Since v1's Cleanable has been moved to v2, - // no cleanup should happen in Reset. - v1.Reset(); - ASSERT_EQ(1, res); - - AssertSameData(const_str1, v2); - } - // The Cleanable moved from v1 to v2 will be Reset. - ASSERT_EQ(2, res); - - { - // Test move assignment from an upinned slice to - // another unpinned slice. - PinnableSlice v1; - v1.PinSelf(slice1); - PinnableSlice v2; - v2.PinSelf(slice2); - v2 = std::move(v1); - - AssertSameData(const_str1, v2); - } - - { - // Test move assignment from an upinned slice to - // a pinned slice. - res = 1; - PinnableSlice v1; - v1.PinSelf(slice1); - PinnableSlice v2; - v2.PinSlice(slice2, Multiplier, &res, &n2); - v2 = std::move(v1); - - // v2's Cleanable will be Reset before moving - // anything from v1. - ASSERT_EQ(2, res); - - AssertSameData(const_str1, v2); - } - // No Cleanable is moved from v1 to v2, so no more cleanup. - ASSERT_EQ(2, res); -} - -// ***************************************************************** // -// Unit test for SmallEnumSet -class SmallEnumSetTest : public testing::Test { - public: - SmallEnumSetTest() {} - ~SmallEnumSetTest() {} -}; - -TEST_F(SmallEnumSetTest, SmallEnumSetTest1) { - FileTypeSet fs; // based on a legacy enum type - ASSERT_TRUE(fs.empty()); - ASSERT_TRUE(fs.Add(FileType::kIdentityFile)); - ASSERT_FALSE(fs.empty()); - ASSERT_FALSE(fs.Add(FileType::kIdentityFile)); - ASSERT_TRUE(fs.Add(FileType::kInfoLogFile)); - ASSERT_TRUE(fs.Contains(FileType::kIdentityFile)); - ASSERT_FALSE(fs.Contains(FileType::kDBLockFile)); - ASSERT_FALSE(fs.empty()); - ASSERT_FALSE(fs.Remove(FileType::kDBLockFile)); - ASSERT_TRUE(fs.Remove(FileType::kIdentityFile)); - ASSERT_FALSE(fs.empty()); - ASSERT_TRUE(fs.Remove(FileType::kInfoLogFile)); - ASSERT_TRUE(fs.empty()); -} - -namespace { -enum class MyEnumClass { A, B, C }; -} // namespace - -using MyEnumClassSet = SmallEnumSet; - -TEST_F(SmallEnumSetTest, SmallEnumSetTest2) { - MyEnumClassSet s; // based on an enum class type - ASSERT_TRUE(s.Add(MyEnumClass::A)); - ASSERT_TRUE(s.Contains(MyEnumClass::A)); - ASSERT_FALSE(s.Contains(MyEnumClass::B)); - ASSERT_TRUE(s.With(MyEnumClass::B).Contains(MyEnumClass::B)); - ASSERT_TRUE(s.With(MyEnumClass::A).Contains(MyEnumClass::A)); - ASSERT_FALSE(s.Contains(MyEnumClass::B)); - ASSERT_FALSE(s.Without(MyEnumClass::A).Contains(MyEnumClass::A)); - ASSERT_FALSE( - s.With(MyEnumClass::B).Without(MyEnumClass::B).Contains(MyEnumClass::B)); - ASSERT_TRUE( - s.Without(MyEnumClass::B).With(MyEnumClass::B).Contains(MyEnumClass::B)); - ASSERT_TRUE(s.Contains(MyEnumClass::A)); - - const MyEnumClassSet cs = s; - ASSERT_TRUE(cs.Contains(MyEnumClass::A)); - ASSERT_EQ(cs, MyEnumClassSet{MyEnumClass::A}); - ASSERT_EQ(cs.Without(MyEnumClass::A), MyEnumClassSet{}); - ASSERT_EQ(cs, MyEnumClassSet::All().Without(MyEnumClass::B, MyEnumClass::C)); - ASSERT_EQ(cs.With(MyEnumClass::B, MyEnumClass::C), MyEnumClassSet::All()); - ASSERT_EQ( - MyEnumClassSet::All(), - MyEnumClassSet{}.With(MyEnumClass::A, MyEnumClass::B, MyEnumClass::C)); - ASSERT_NE(cs, MyEnumClassSet{MyEnumClass::B}); - ASSERT_NE(cs, MyEnumClassSet::All()); - - int count = 0; - for (MyEnumClass e : cs) { - ASSERT_EQ(e, MyEnumClass::A); - ++count; - } - ASSERT_EQ(count, 1); - - count = 0; - for (MyEnumClass e : MyEnumClassSet::All().Without(MyEnumClass::B)) { - ASSERT_NE(e, MyEnumClass::B); - ++count; - } - ASSERT_EQ(count, 2); - - for (MyEnumClass e : MyEnumClassSet{}) { - (void)e; - assert(false); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/slice_transform_test.cc b/util/slice_transform_test.cc deleted file mode 100644 index 64ac8bb1f..000000000 --- a/util/slice_transform_test.cc +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). -// -// Copyright (c) 2011 The LevelDB Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. See the AUTHORS file for names of contributors. - -#include "rocksdb/slice_transform.h" - -#include "rocksdb/db.h" -#include "rocksdb/env.h" -#include "rocksdb/filter_policy.h" -#include "rocksdb/statistics.h" -#include "rocksdb/table.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class SliceTransformTest : public testing::Test {}; - -TEST_F(SliceTransformTest, CapPrefixTransform) { - std::string s; - s = "abcdefge"; - - std::unique_ptr transform; - - transform.reset(NewCappedPrefixTransform(6)); - ASSERT_EQ(transform->Transform(s).ToString(), "abcdef"); - ASSERT_TRUE(transform->SameResultWhenAppended("123456")); - ASSERT_TRUE(transform->SameResultWhenAppended("1234567")); - ASSERT_TRUE(!transform->SameResultWhenAppended("12345")); - - transform.reset(NewCappedPrefixTransform(8)); - ASSERT_EQ(transform->Transform(s).ToString(), "abcdefge"); - - transform.reset(NewCappedPrefixTransform(10)); - ASSERT_EQ(transform->Transform(s).ToString(), "abcdefge"); - - transform.reset(NewCappedPrefixTransform(0)); - ASSERT_EQ(transform->Transform(s).ToString(), ""); - - transform.reset(NewCappedPrefixTransform(0)); - ASSERT_EQ(transform->Transform("").ToString(), ""); -} - -class SliceTransformDBTest : public testing::Test { - private: - std::string dbname_; - Env* env_; - DB* db_; - - public: - SliceTransformDBTest() : env_(Env::Default()), db_(nullptr) { - dbname_ = test::PerThreadDBPath("slice_transform_db_test"); - EXPECT_OK(DestroyDB(dbname_, last_options_)); - } - - ~SliceTransformDBTest() override { - delete db_; - EXPECT_OK(DestroyDB(dbname_, last_options_)); - } - - DB* db() { return db_; } - - // Return the current option configuration. - Options* GetOptions() { return &last_options_; } - - void DestroyAndReopen() { - // Destroy using last options - Destroy(); - ASSERT_OK(TryReopen()); - } - - void Destroy() { - delete db_; - db_ = nullptr; - ASSERT_OK(DestroyDB(dbname_, last_options_)); - } - - Status TryReopen() { - delete db_; - db_ = nullptr; - last_options_.create_if_missing = true; - - return DB::Open(last_options_, dbname_, &db_); - } - - Options last_options_; -}; - -namespace { -uint64_t TestGetTickerCount(const Options& options, Tickers ticker_type) { - return options.statistics->getTickerCount(ticker_type); -} -} // namespace - -TEST_F(SliceTransformDBTest, CapPrefix) { - last_options_.prefix_extractor.reset(NewCappedPrefixTransform(8)); - last_options_.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics(); - BlockBasedTableOptions bbto; - bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); - bbto.whole_key_filtering = false; - last_options_.table_factory.reset(NewBlockBasedTableFactory(bbto)); - ASSERT_OK(TryReopen()); - - ReadOptions ro; - FlushOptions fo; - WriteOptions wo; - - ASSERT_OK(db()->Put(wo, "barbarbar", "foo")); - ASSERT_OK(db()->Put(wo, "barbarbar2", "foo2")); - ASSERT_OK(db()->Put(wo, "foo", "bar")); - ASSERT_OK(db()->Put(wo, "foo3", "bar3")); - ASSERT_OK(db()->Flush(fo)); - - std::unique_ptr iter(db()->NewIterator(ro)); - - iter->Seek("foo"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->value().ToString(), "bar"); - ASSERT_EQ(TestGetTickerCount(last_options_, BLOOM_FILTER_PREFIX_USEFUL), 0U); - - iter->Seek("foo2"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ(TestGetTickerCount(last_options_, BLOOM_FILTER_PREFIX_USEFUL), 1U); - - iter->Seek("barbarbar"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(iter->Valid()); - ASSERT_EQ(iter->value().ToString(), "foo"); - ASSERT_EQ(TestGetTickerCount(last_options_, BLOOM_FILTER_PREFIX_USEFUL), 1U); - - iter->Seek("barfoofoo"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ(TestGetTickerCount(last_options_, BLOOM_FILTER_PREFIX_USEFUL), 2U); - - iter->Seek("foobarbar"); - ASSERT_OK(iter->status()); - ASSERT_TRUE(!iter->Valid()); - ASSERT_EQ(TestGetTickerCount(last_options_, BLOOM_FILTER_PREFIX_USEFUL), 3U); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/thread_list_test.cc b/util/thread_list_test.cc deleted file mode 100644 index af4e62355..000000000 --- a/util/thread_list_test.cc +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include -#include - -#include "monitoring/thread_status_updater.h" -#include "rocksdb/db.h" -#include "test_util/testharness.h" - -#ifdef ROCKSDB_USING_THREAD_STATUS - -namespace ROCKSDB_NAMESPACE { - -class SimulatedBackgroundTask { - public: - SimulatedBackgroundTask( - const void* db_key, const std::string& db_name, const void* cf_key, - const std::string& cf_name, - const ThreadStatus::OperationType operation_type = - ThreadStatus::OP_UNKNOWN, - const ThreadStatus::StateType state_type = ThreadStatus::STATE_UNKNOWN) - : db_key_(db_key), - db_name_(db_name), - cf_key_(cf_key), - cf_name_(cf_name), - operation_type_(operation_type), - state_type_(state_type), - should_run_(true), - running_count_(0) { - Env::Default()->GetThreadStatusUpdater()->NewColumnFamilyInfo( - db_key_, db_name_, cf_key_, cf_name_); - } - - ~SimulatedBackgroundTask() { - Env::Default()->GetThreadStatusUpdater()->EraseDatabaseInfo(db_key_); - } - - void Run() { - std::unique_lock l(mutex_); - running_count_++; - bg_cv_.notify_all(); - Env::Default()->GetThreadStatusUpdater()->SetColumnFamilyInfoKey(cf_key_); - Env::Default()->GetThreadStatusUpdater()->SetThreadOperation( - operation_type_); - Env::Default()->GetThreadStatusUpdater()->SetThreadState(state_type_); - while (should_run_) { - bg_cv_.wait(l); - } - Env::Default()->GetThreadStatusUpdater()->ClearThreadState(); - Env::Default()->GetThreadStatusUpdater()->ClearThreadOperation(); - Env::Default()->GetThreadStatusUpdater()->SetColumnFamilyInfoKey(nullptr); - running_count_--; - bg_cv_.notify_all(); - } - - void FinishAllTasks() { - std::unique_lock l(mutex_); - should_run_ = false; - bg_cv_.notify_all(); - } - - void WaitUntilScheduled(int job_count) { - std::unique_lock l(mutex_); - while (running_count_ < job_count) { - bg_cv_.wait(l); - } - } - - void WaitUntilDone() { - std::unique_lock l(mutex_); - while (running_count_ > 0) { - bg_cv_.wait(l); - } - } - - static void DoSimulatedTask(void* arg) { - reinterpret_cast(arg)->Run(); - } - - private: - const void* db_key_; - const std::string db_name_; - const void* cf_key_; - const std::string cf_name_; - const ThreadStatus::OperationType operation_type_; - const ThreadStatus::StateType state_type_; - std::mutex mutex_; - std::condition_variable bg_cv_; - bool should_run_; - std::atomic running_count_; -}; - -class ThreadListTest : public testing::Test { - public: - ThreadListTest() {} -}; - -TEST_F(ThreadListTest, GlobalTables) { - // verify the global tables for operations and states are properly indexed. - for (int type = 0; type != ThreadStatus::NUM_OP_TYPES; ++type) { - ASSERT_EQ(global_operation_table[type].type, type); - ASSERT_EQ( - global_operation_table[type].name, - ThreadStatus::GetOperationName(ThreadStatus::OperationType(type))); - } - - for (int type = 0; type != ThreadStatus::NUM_STATE_TYPES; ++type) { - ASSERT_EQ(global_state_table[type].type, type); - ASSERT_EQ(global_state_table[type].name, - ThreadStatus::GetStateName(ThreadStatus::StateType(type))); - } - - for (int stage = 0; stage != ThreadStatus::NUM_OP_STAGES; ++stage) { - ASSERT_EQ(global_op_stage_table[stage].stage, stage); - ASSERT_EQ(global_op_stage_table[stage].name, - ThreadStatus::GetOperationStageName( - ThreadStatus::OperationStage(stage))); - } -} - -TEST_F(ThreadListTest, SimpleColumnFamilyInfoTest) { - Env* env = Env::Default(); - const int kHighPriorityThreads = 3; - const int kLowPriorityThreads = 5; - const int kSimulatedHighPriThreads = kHighPriorityThreads - 1; - const int kSimulatedLowPriThreads = kLowPriorityThreads / 3; - const int kDelayMicros = 1000000; - env->SetBackgroundThreads(kHighPriorityThreads, Env::HIGH); - env->SetBackgroundThreads(kLowPriorityThreads, Env::LOW); - // Wait 1 second so that threads start - Env::Default()->SleepForMicroseconds(kDelayMicros); - SimulatedBackgroundTask running_task(reinterpret_cast(1234), "running", - reinterpret_cast(5678), - "pikachu"); - - for (int test = 0; test < kSimulatedHighPriThreads; ++test) { - env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask, &running_task, - Env::Priority::HIGH); - } - - for (int test = 0; test < kSimulatedLowPriThreads; ++test) { - env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask, &running_task, - Env::Priority::LOW); - } - running_task.WaitUntilScheduled(kSimulatedHighPriThreads + - kSimulatedLowPriThreads); - // We can only reserve limited number of waiting threads - ASSERT_EQ(kHighPriorityThreads - kSimulatedHighPriThreads, - env->ReserveThreads(kHighPriorityThreads, Env::Priority::HIGH)); - ASSERT_EQ(kLowPriorityThreads - kSimulatedLowPriThreads, - env->ReserveThreads(kLowPriorityThreads, Env::Priority::LOW)); - - // Reservation shall not affect the existing thread list - std::vector thread_list; - - // Verify the number of running threads in each pool. - ASSERT_OK(env->GetThreadList(&thread_list)); - int running_count[ThreadStatus::NUM_THREAD_TYPES] = {0}; - for (auto thread_status : thread_list) { - if (thread_status.cf_name == "pikachu" && - thread_status.db_name == "running") { - running_count[thread_status.thread_type]++; - } - } - // Cannot reserve more threads - ASSERT_EQ(0, env->ReserveThreads(kHighPriorityThreads, Env::Priority::HIGH)); - ASSERT_EQ(0, env->ReserveThreads(kLowPriorityThreads, Env::Priority::LOW)); - - ASSERT_EQ(running_count[ThreadStatus::HIGH_PRIORITY], - kSimulatedHighPriThreads); - ASSERT_EQ(running_count[ThreadStatus::LOW_PRIORITY], kSimulatedLowPriThreads); - ASSERT_EQ(running_count[ThreadStatus::USER], 0); - - running_task.FinishAllTasks(); - running_task.WaitUntilDone(); - - ASSERT_EQ(kHighPriorityThreads - kSimulatedHighPriThreads, - env->ReleaseThreads(kHighPriorityThreads, Env::Priority::HIGH)); - ASSERT_EQ(kLowPriorityThreads - kSimulatedLowPriThreads, - env->ReleaseThreads(kLowPriorityThreads, Env::Priority::LOW)); - // Verify none of the threads are running - ASSERT_OK(env->GetThreadList(&thread_list)); - - for (int i = 0; i < ThreadStatus::NUM_THREAD_TYPES; ++i) { - running_count[i] = 0; - } - for (auto thread_status : thread_list) { - if (thread_status.cf_name == "pikachu" && - thread_status.db_name == "running") { - running_count[thread_status.thread_type]++; - } - } - - ASSERT_EQ(running_count[ThreadStatus::HIGH_PRIORITY], 0); - ASSERT_EQ(running_count[ThreadStatus::LOW_PRIORITY], 0); - ASSERT_EQ(running_count[ThreadStatus::USER], 0); -} - -namespace { -void UpdateStatusCounts(const std::vector& thread_list, - int operation_counts[], int state_counts[]) { - for (auto thread_status : thread_list) { - operation_counts[thread_status.operation_type]++; - state_counts[thread_status.state_type]++; - } -} - -void VerifyAndResetCounts(const int correct_counts[], int collected_counts[], - int size) { - for (int i = 0; i < size; ++i) { - ASSERT_EQ(collected_counts[i], correct_counts[i]); - collected_counts[i] = 0; - } -} - -void UpdateCount(int operation_counts[], int from_event, int to_event, - int amount) { - operation_counts[from_event] -= amount; - operation_counts[to_event] += amount; -} -} // namespace - -TEST_F(ThreadListTest, SimpleEventTest) { - Env* env = Env::Default(); - - // simulated tasks - const int kFlushWriteTasks = 3; - SimulatedBackgroundTask flush_write_task( - reinterpret_cast(1234), "running", reinterpret_cast(5678), - "pikachu", ThreadStatus::OP_FLUSH); - - const int kCompactionWriteTasks = 4; - SimulatedBackgroundTask compaction_write_task( - reinterpret_cast(1234), "running", reinterpret_cast(5678), - "pikachu", ThreadStatus::OP_COMPACTION); - - const int kCompactionReadTasks = 5; - SimulatedBackgroundTask compaction_read_task( - reinterpret_cast(1234), "running", reinterpret_cast(5678), - "pikachu", ThreadStatus::OP_COMPACTION); - - const int kCompactionWaitTasks = 6; - SimulatedBackgroundTask compaction_wait_task( - reinterpret_cast(1234), "running", reinterpret_cast(5678), - "pikachu", ThreadStatus::OP_COMPACTION); - - // setup right answers - int correct_operation_counts[ThreadStatus::NUM_OP_TYPES] = {0}; - correct_operation_counts[ThreadStatus::OP_FLUSH] = kFlushWriteTasks; - correct_operation_counts[ThreadStatus::OP_COMPACTION] = - kCompactionWriteTasks + kCompactionReadTasks + kCompactionWaitTasks; - - env->SetBackgroundThreads(correct_operation_counts[ThreadStatus::OP_FLUSH], - Env::HIGH); - env->SetBackgroundThreads( - correct_operation_counts[ThreadStatus::OP_COMPACTION], Env::LOW); - - // schedule the simulated tasks - for (int t = 0; t < kFlushWriteTasks; ++t) { - env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask, &flush_write_task, - Env::Priority::HIGH); - } - flush_write_task.WaitUntilScheduled(kFlushWriteTasks); - - for (int t = 0; t < kCompactionWriteTasks; ++t) { - env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask, - &compaction_write_task, Env::Priority::LOW); - } - compaction_write_task.WaitUntilScheduled(kCompactionWriteTasks); - - for (int t = 0; t < kCompactionReadTasks; ++t) { - env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask, - &compaction_read_task, Env::Priority::LOW); - } - compaction_read_task.WaitUntilScheduled(kCompactionReadTasks); - - for (int t = 0; t < kCompactionWaitTasks; ++t) { - env->Schedule(&SimulatedBackgroundTask::DoSimulatedTask, - &compaction_wait_task, Env::Priority::LOW); - } - compaction_wait_task.WaitUntilScheduled(kCompactionWaitTasks); - - // verify the thread-status - int operation_counts[ThreadStatus::NUM_OP_TYPES] = {0}; - int state_counts[ThreadStatus::NUM_STATE_TYPES] = {0}; - - std::vector thread_list; - ASSERT_OK(env->GetThreadList(&thread_list)); - UpdateStatusCounts(thread_list, operation_counts, state_counts); - VerifyAndResetCounts(correct_operation_counts, operation_counts, - ThreadStatus::NUM_OP_TYPES); - - // terminate compaction-wait tasks and see if the thread-status - // reflects this update - compaction_wait_task.FinishAllTasks(); - compaction_wait_task.WaitUntilDone(); - UpdateCount(correct_operation_counts, ThreadStatus::OP_COMPACTION, - ThreadStatus::OP_UNKNOWN, kCompactionWaitTasks); - - ASSERT_OK(env->GetThreadList(&thread_list)); - UpdateStatusCounts(thread_list, operation_counts, state_counts); - VerifyAndResetCounts(correct_operation_counts, operation_counts, - ThreadStatus::NUM_OP_TYPES); - - // terminate flush-write tasks and see if the thread-status - // reflects this update - flush_write_task.FinishAllTasks(); - flush_write_task.WaitUntilDone(); - UpdateCount(correct_operation_counts, ThreadStatus::OP_FLUSH, - ThreadStatus::OP_UNKNOWN, kFlushWriteTasks); - - ASSERT_OK(env->GetThreadList(&thread_list)); - UpdateStatusCounts(thread_list, operation_counts, state_counts); - VerifyAndResetCounts(correct_operation_counts, operation_counts, - ThreadStatus::NUM_OP_TYPES); - - // terminate compaction-write tasks and see if the thread-status - // reflects this update - compaction_write_task.FinishAllTasks(); - compaction_write_task.WaitUntilDone(); - UpdateCount(correct_operation_counts, ThreadStatus::OP_COMPACTION, - ThreadStatus::OP_UNKNOWN, kCompactionWriteTasks); - - ASSERT_OK(env->GetThreadList(&thread_list)); - UpdateStatusCounts(thread_list, operation_counts, state_counts); - VerifyAndResetCounts(correct_operation_counts, operation_counts, - ThreadStatus::NUM_OP_TYPES); - - // terminate compaction-write tasks and see if the thread-status - // reflects this update - compaction_read_task.FinishAllTasks(); - compaction_read_task.WaitUntilDone(); - UpdateCount(correct_operation_counts, ThreadStatus::OP_COMPACTION, - ThreadStatus::OP_UNKNOWN, kCompactionReadTasks); - - ASSERT_OK(env->GetThreadList(&thread_list)); - UpdateStatusCounts(thread_list, operation_counts, state_counts); - VerifyAndResetCounts(correct_operation_counts, operation_counts, - ThreadStatus::NUM_OP_TYPES); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - -#else - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return 0; -} - -#endif // ROCKSDB_USING_THREAD_STATUS diff --git a/util/thread_local_test.cc b/util/thread_local_test.cc deleted file mode 100644 index 3d12fe83a..000000000 --- a/util/thread_local_test.cc +++ /dev/null @@ -1,578 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "util/thread_local.h" - -#include -#include -#include - -#include "port/port.h" -#include "rocksdb/env.h" -#include "test_util/sync_point.h" -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "util/autovector.h" - -namespace ROCKSDB_NAMESPACE { - -class ThreadLocalTest : public testing::Test { - public: - ThreadLocalTest() : env_(Env::Default()) {} - - Env* env_; -}; - -namespace { - -struct Params { - Params(port::Mutex* m, port::CondVar* c, int* u, int n, - UnrefHandler handler = nullptr) - : mu(m), - cv(c), - unref(u), - total(n), - started(0), - completed(0), - doWrite(false), - tls1(handler), - tls2(nullptr) {} - - port::Mutex* mu; - port::CondVar* cv; - int* unref; - int total; - int started; - int completed; - bool doWrite; - ThreadLocalPtr tls1; - ThreadLocalPtr* tls2; -}; - -class IDChecker : public ThreadLocalPtr { - public: - static uint32_t PeekId() { return TEST_PeekId(); } -}; - -} // anonymous namespace - -// Suppress false positive clang analyzer warnings. -#ifndef __clang_analyzer__ -TEST_F(ThreadLocalTest, UniqueIdTest) { - port::Mutex mu; - port::CondVar cv(&mu); - - uint32_t base_id = IDChecker::PeekId(); - // New ThreadLocal instance bumps id by 1 - { - // Id used 0 - Params p1(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 1u); - // Id used 1 - Params p2(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 2u); - // Id used 2 - Params p3(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 3u); - // Id used 3 - Params p4(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 4u); - } - // id 3, 2, 1, 0 are in the free queue in order - ASSERT_EQ(IDChecker::PeekId(), base_id + 0u); - - // pick up 0 - Params p1(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 1u); - // pick up 1 - Params* p2 = new Params(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 2u); - // pick up 2 - Params p3(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 3u); - // return up 1 - delete p2; - ASSERT_EQ(IDChecker::PeekId(), base_id + 1u); - // Now we have 3, 1 in queue - // pick up 1 - Params p4(&mu, &cv, nullptr, 1u); - ASSERT_EQ(IDChecker::PeekId(), base_id + 3u); - // pick up 3 - Params p5(&mu, &cv, nullptr, 1u); - // next new id - ASSERT_EQ(IDChecker::PeekId(), base_id + 4u); - // After exit, id sequence in queue: - // 3, 1, 2, 0 -} -#endif // __clang_analyzer__ - -TEST_F(ThreadLocalTest, SequentialReadWriteTest) { - // global id list carries over 3, 1, 2, 0 - uint32_t base_id = IDChecker::PeekId(); - - port::Mutex mu; - port::CondVar cv(&mu); - Params p(&mu, &cv, nullptr, 1); - ThreadLocalPtr tls2; - p.tls2 = &tls2; - - ASSERT_GT(IDChecker::PeekId(), base_id); - base_id = IDChecker::PeekId(); - - auto func = [](Params* ptr) { - Params& params = *ptr; - ASSERT_TRUE(params.tls1.Get() == nullptr); - params.tls1.Reset(reinterpret_cast(1)); - ASSERT_TRUE(params.tls1.Get() == reinterpret_cast(1)); - params.tls1.Reset(reinterpret_cast(2)); - ASSERT_TRUE(params.tls1.Get() == reinterpret_cast(2)); - - ASSERT_TRUE(params.tls2->Get() == nullptr); - params.tls2->Reset(reinterpret_cast(1)); - ASSERT_TRUE(params.tls2->Get() == reinterpret_cast(1)); - params.tls2->Reset(reinterpret_cast(2)); - ASSERT_TRUE(params.tls2->Get() == reinterpret_cast(2)); - - params.mu->Lock(); - ++(params.completed); - params.cv->SignalAll(); - params.mu->Unlock(); - }; - - for (int iter = 0; iter < 1024; ++iter) { - ASSERT_EQ(IDChecker::PeekId(), base_id); - // Another new thread, read/write should not see value from previous thread - env_->StartThreadTyped(func, &p); - - mu.Lock(); - while (p.completed != iter + 1) { - cv.Wait(); - } - mu.Unlock(); - ASSERT_EQ(IDChecker::PeekId(), base_id); - } -} - -TEST_F(ThreadLocalTest, ConcurrentReadWriteTest) { - // global id list carries over 3, 1, 2, 0 - uint32_t base_id = IDChecker::PeekId(); - - ThreadLocalPtr tls2; - port::Mutex mu1; - port::CondVar cv1(&mu1); - Params p1(&mu1, &cv1, nullptr, 16); - p1.tls2 = &tls2; - - port::Mutex mu2; - port::CondVar cv2(&mu2); - Params p2(&mu2, &cv2, nullptr, 16); - p2.doWrite = true; - p2.tls2 = &tls2; - - auto func = [](void* ptr) { - auto& p = *static_cast(ptr); - - p.mu->Lock(); - // Size_T switches size along with the ptr size - // we want to cast to. - size_t own = ++(p.started); - p.cv->SignalAll(); - while (p.started != p.total) { - p.cv->Wait(); - } - p.mu->Unlock(); - - // Let write threads write a different value from the read threads - if (p.doWrite) { - own += 8192; - } - - ASSERT_TRUE(p.tls1.Get() == nullptr); - ASSERT_TRUE(p.tls2->Get() == nullptr); - - auto* env = Env::Default(); - auto start = env->NowMicros(); - - p.tls1.Reset(reinterpret_cast(own)); - p.tls2->Reset(reinterpret_cast(own + 1)); - // Loop for 1 second - while (env->NowMicros() - start < 1000 * 1000) { - for (int iter = 0; iter < 100000; ++iter) { - ASSERT_TRUE(p.tls1.Get() == reinterpret_cast(own)); - ASSERT_TRUE(p.tls2->Get() == reinterpret_cast(own + 1)); - if (p.doWrite) { - p.tls1.Reset(reinterpret_cast(own)); - p.tls2->Reset(reinterpret_cast(own + 1)); - } - } - } - - p.mu->Lock(); - ++(p.completed); - p.cv->SignalAll(); - p.mu->Unlock(); - }; - - // Initiate 2 instnaces: one keeps writing and one keeps reading. - // The read instance should not see data from the write instance. - // Each thread local copy of the value are also different from each - // other. - for (int th = 0; th < p1.total; ++th) { - env_->StartThreadTyped(func, &p1); - } - for (int th = 0; th < p2.total; ++th) { - env_->StartThreadTyped(func, &p2); - } - - mu1.Lock(); - while (p1.completed != p1.total) { - cv1.Wait(); - } - mu1.Unlock(); - - mu2.Lock(); - while (p2.completed != p2.total) { - cv2.Wait(); - } - mu2.Unlock(); - - ASSERT_EQ(IDChecker::PeekId(), base_id + 3u); -} - -TEST_F(ThreadLocalTest, Unref) { - auto unref = [](void* ptr) { - auto& p = *static_cast(ptr); - p.mu->Lock(); - ++(*p.unref); - p.mu->Unlock(); - }; - - // Case 0: no unref triggered if ThreadLocalPtr is never accessed - auto func0 = [](Params* ptr) { - auto& p = *ptr; - p.mu->Lock(); - ++(p.started); - p.cv->SignalAll(); - while (p.started != p.total) { - p.cv->Wait(); - } - p.mu->Unlock(); - }; - - for (int th = 1; th <= 128; th += th) { - port::Mutex mu; - port::CondVar cv(&mu); - int unref_count = 0; - Params p(&mu, &cv, &unref_count, th, unref); - - for (int i = 0; i < p.total; ++i) { - env_->StartThreadTyped(func0, &p); - } - env_->WaitForJoin(); - ASSERT_EQ(unref_count, 0); - } - - // Case 1: unref triggered by thread exit - auto func1 = [](Params* ptr) { - auto& p = *ptr; - - p.mu->Lock(); - ++(p.started); - p.cv->SignalAll(); - while (p.started != p.total) { - p.cv->Wait(); - } - p.mu->Unlock(); - - ASSERT_TRUE(p.tls1.Get() == nullptr); - ASSERT_TRUE(p.tls2->Get() == nullptr); - - p.tls1.Reset(ptr); - p.tls2->Reset(ptr); - - p.tls1.Reset(ptr); - p.tls2->Reset(ptr); - }; - - for (int th = 1; th <= 128; th += th) { - port::Mutex mu; - port::CondVar cv(&mu); - int unref_count = 0; - ThreadLocalPtr tls2(unref); - Params p(&mu, &cv, &unref_count, th, unref); - p.tls2 = &tls2; - - for (int i = 0; i < p.total; ++i) { - env_->StartThreadTyped(func1, &p); - } - - env_->WaitForJoin(); - - // N threads x 2 ThreadLocal instance cleanup on thread exit - ASSERT_EQ(unref_count, 2 * p.total); - } - - // Case 2: unref triggered by ThreadLocal instance destruction - auto func2 = [](Params* ptr) { - auto& p = *ptr; - - p.mu->Lock(); - ++(p.started); - p.cv->SignalAll(); - while (p.started != p.total) { - p.cv->Wait(); - } - p.mu->Unlock(); - - ASSERT_TRUE(p.tls1.Get() == nullptr); - ASSERT_TRUE(p.tls2->Get() == nullptr); - - p.tls1.Reset(ptr); - p.tls2->Reset(ptr); - - p.tls1.Reset(ptr); - p.tls2->Reset(ptr); - - p.mu->Lock(); - ++(p.completed); - p.cv->SignalAll(); - - // Waiting for instruction to exit thread - while (p.completed != 0) { - p.cv->Wait(); - } - p.mu->Unlock(); - }; - - for (int th = 1; th <= 128; th += th) { - port::Mutex mu; - port::CondVar cv(&mu); - int unref_count = 0; - Params p(&mu, &cv, &unref_count, th, unref); - p.tls2 = new ThreadLocalPtr(unref); - - for (int i = 0; i < p.total; ++i) { - env_->StartThreadTyped(func2, &p); - } - - // Wait for all threads to finish using Params - mu.Lock(); - while (p.completed != p.total) { - cv.Wait(); - } - mu.Unlock(); - - // Now destroy one ThreadLocal instance - delete p.tls2; - p.tls2 = nullptr; - // instance destroy for N threads - ASSERT_EQ(unref_count, p.total); - - // Signal to exit - mu.Lock(); - p.completed = 0; - cv.SignalAll(); - mu.Unlock(); - env_->WaitForJoin(); - // additional N threads exit unref for the left instance - ASSERT_EQ(unref_count, 2 * p.total); - } -} - -TEST_F(ThreadLocalTest, Swap) { - ThreadLocalPtr tls; - tls.Reset(reinterpret_cast(1)); - ASSERT_EQ(reinterpret_cast(tls.Swap(nullptr)), 1); - ASSERT_TRUE(tls.Swap(reinterpret_cast(2)) == nullptr); - ASSERT_EQ(reinterpret_cast(tls.Get()), 2); - ASSERT_EQ(reinterpret_cast(tls.Swap(reinterpret_cast(3))), 2); -} - -TEST_F(ThreadLocalTest, Scrape) { - auto unref = [](void* ptr) { - auto& p = *static_cast(ptr); - p.mu->Lock(); - ++(*p.unref); - p.mu->Unlock(); - }; - - auto func = [](void* ptr) { - auto& p = *static_cast(ptr); - - ASSERT_TRUE(p.tls1.Get() == nullptr); - ASSERT_TRUE(p.tls2->Get() == nullptr); - - p.tls1.Reset(ptr); - p.tls2->Reset(ptr); - - p.tls1.Reset(ptr); - p.tls2->Reset(ptr); - - p.mu->Lock(); - ++(p.completed); - p.cv->SignalAll(); - - // Waiting for instruction to exit thread - while (p.completed != 0) { - p.cv->Wait(); - } - p.mu->Unlock(); - }; - - for (int th = 1; th <= 128; th += th) { - port::Mutex mu; - port::CondVar cv(&mu); - int unref_count = 0; - Params p(&mu, &cv, &unref_count, th, unref); - p.tls2 = new ThreadLocalPtr(unref); - - for (int i = 0; i < p.total; ++i) { - env_->StartThreadTyped(func, &p); - } - - // Wait for all threads to finish using Params - mu.Lock(); - while (p.completed != p.total) { - cv.Wait(); - } - mu.Unlock(); - - ASSERT_EQ(unref_count, 0); - - // Scrape all thread local data. No unref at thread - // exit or ThreadLocalPtr destruction - autovector ptrs; - p.tls1.Scrape(&ptrs, nullptr); - p.tls2->Scrape(&ptrs, nullptr); - delete p.tls2; - // Signal to exit - mu.Lock(); - p.completed = 0; - cv.SignalAll(); - mu.Unlock(); - env_->WaitForJoin(); - - ASSERT_EQ(unref_count, 0); - } -} - -TEST_F(ThreadLocalTest, Fold) { - auto unref = [](void* ptr) { - delete static_cast*>(ptr); - }; - static const int kNumThreads = 16; - static const int kItersPerThread = 10; - port::Mutex mu; - port::CondVar cv(&mu); - Params params(&mu, &cv, nullptr, kNumThreads, unref); - auto func = [](void* ptr) { - auto& p = *static_cast(ptr); - ASSERT_TRUE(p.tls1.Get() == nullptr); - p.tls1.Reset(new std::atomic(0)); - - for (int i = 0; i < kItersPerThread; ++i) { - static_cast*>(p.tls1.Get())->fetch_add(1); - } - - p.mu->Lock(); - ++(p.completed); - p.cv->SignalAll(); - - // Waiting for instruction to exit thread - while (p.completed != 0) { - p.cv->Wait(); - } - p.mu->Unlock(); - }; - - for (int th = 0; th < params.total; ++th) { - env_->StartThread(func, ¶ms); - } - - // Wait for all threads to finish using Params - mu.Lock(); - while (params.completed != params.total) { - cv.Wait(); - } - mu.Unlock(); - - // Verify Fold() behavior - int64_t sum = 0; - params.tls1.Fold( - [](void* ptr, void* res) { - auto sum_ptr = static_cast(res); - *sum_ptr += static_cast*>(ptr)->load(); - }, - &sum); - ASSERT_EQ(sum, kNumThreads * kItersPerThread); - - // Signal to exit - mu.Lock(); - params.completed = 0; - cv.SignalAll(); - mu.Unlock(); - env_->WaitForJoin(); -} - -TEST_F(ThreadLocalTest, CompareAndSwap) { - ThreadLocalPtr tls; - ASSERT_TRUE(tls.Swap(reinterpret_cast(1)) == nullptr); - void* expected = reinterpret_cast(1); - // Swap in 2 - ASSERT_TRUE(tls.CompareAndSwap(reinterpret_cast(2), expected)); - expected = reinterpret_cast(100); - // Fail Swap, still 2 - ASSERT_TRUE(!tls.CompareAndSwap(reinterpret_cast(2), expected)); - ASSERT_EQ(expected, reinterpret_cast(2)); - // Swap in 3 - expected = reinterpret_cast(2); - ASSERT_TRUE(tls.CompareAndSwap(reinterpret_cast(3), expected)); - ASSERT_EQ(tls.Get(), reinterpret_cast(3)); -} - -namespace { - -void* AccessThreadLocal(void* /*arg*/) { - TEST_SYNC_POINT("AccessThreadLocal:Start"); - ThreadLocalPtr tlp; - tlp.Reset(new std::string("hello RocksDB")); - TEST_SYNC_POINT("AccessThreadLocal:End"); - return nullptr; -} - -} // namespace - -// The following test is disabled as it requires manual steps to run it -// correctly. -// -// Currently we have no way to acess SyncPoint w/o ASAN error when the -// child thread dies after the main thread dies. So if you manually enable -// this test and only see an ASAN error on SyncPoint, it means you pass the -// test. -TEST_F(ThreadLocalTest, DISABLED_MainThreadDiesFirst) { - ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( - {{"AccessThreadLocal:Start", "MainThreadDiesFirst:End"}, - {"PosixEnv::~PosixEnv():End", "AccessThreadLocal:End"}}); - - // Triggers the initialization of singletons. - Env::Default(); - - try { - ROCKSDB_NAMESPACE::port::Thread th(&AccessThreadLocal, nullptr); - th.detach(); - TEST_SYNC_POINT("MainThreadDiesFirst:End"); - } catch (const std::system_error& ex) { - std::cerr << "Start thread: " << ex.code() << std::endl; - FAIL(); - } -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/util/timer_queue_test.cc b/util/timer_queue_test.cc deleted file mode 100644 index b3c3768ec..000000000 --- a/util/timer_queue_test.cc +++ /dev/null @@ -1,73 +0,0 @@ -// Portions Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -// borrowed from -// http://www.crazygaze.com/blog/2016/03/24/portable-c-timer-queue/ -// Timer Queue -// -// License -// -// The source code in this article is licensed under the CC0 license, so feel -// free -// to copy, modify, share, do whatever you want with it. -// No attribution is required, but Ill be happy if you do. -// CC0 license - -// The person who associated a work with this deed has dedicated the work to the -// public domain by waiving all of his or her rights to the work worldwide -// under copyright law, including all related and neighboring rights, to the -// extent allowed by law. You can copy, modify, distribute and perform the -// work, even for -// commercial purposes, all without asking permission. See Other Information -// below. -// - -#include "util/timer_queue.h" - -#include - -namespace Timing { - -using Clock = std::chrono::high_resolution_clock; -double now() { - static auto start = Clock::now(); - return std::chrono::duration(Clock::now() - start) - .count(); -} - -} // namespace Timing - -int main() { - TimerQueue q; - - double tnow = Timing::now(); - - q.add(10000, [tnow](bool aborted) mutable { - printf("T 1: %d, Elapsed %4.2fms\n", aborted, Timing::now() - tnow); - return std::make_pair(false, 0); - }); - q.add(10001, [tnow](bool aborted) mutable { - printf("T 2: %d, Elapsed %4.2fms\n", aborted, Timing::now() - tnow); - return std::make_pair(false, 0); - }); - - q.add(1000, [tnow](bool aborted) mutable { - printf("T 3: %d, Elapsed %4.2fms\n", aborted, Timing::now() - tnow); - return std::make_pair(!aborted, 1000); - }); - - auto id = q.add(2000, [tnow](bool aborted) mutable { - printf("T 4: %d, Elapsed %4.2fms\n", aborted, Timing::now() - tnow); - return std::make_pair(!aborted, 2000); - }); - - (void)id; - // auto ret = q.cancel(id); - // assert(ret == 1); - // q.cancelAll(); - - return 0; -} -////////////////////////////////////////// diff --git a/util/timer_test.cc b/util/timer_test.cc deleted file mode 100644 index 0ebfa9f3d..000000000 --- a/util/timer_test.cc +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "util/timer.h" - -#include "db/db_test_util.h" -#include "rocksdb/file_system.h" -#include "test_util/mock_time_env.h" - -namespace ROCKSDB_NAMESPACE { - -class TimerTest : public testing::Test { - public: - TimerTest() - : mock_clock_(std::make_shared(SystemClock::Default())) { - } - - protected: - std::shared_ptr mock_clock_; - - void SetUp() override { mock_clock_->InstallTimedWaitFixCallback(); } - - const int kUsPerSec = 1000000; -}; - -TEST_F(TimerTest, SingleScheduleOnce) { - const int kInitDelayUs = 1 * kUsPerSec; - Timer timer(mock_clock_.get()); - - int count = 0; - timer.Add([&] { count++; }, "fn_sch_test", kInitDelayUs, 0); - - ASSERT_TRUE(timer.Start()); - - ASSERT_EQ(0, count); - // Wait for execution to finish - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); - ASSERT_EQ(1, count); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, MultipleScheduleOnce) { - const int kInitDelay1Us = 1 * kUsPerSec; - const int kInitDelay2Us = 3 * kUsPerSec; - Timer timer(mock_clock_.get()); - - int count1 = 0; - timer.Add([&] { count1++; }, "fn_sch_test1", kInitDelay1Us, 0); - - int count2 = 0; - timer.Add([&] { count2++; }, "fn_sch_test2", kInitDelay2Us, 0); - - ASSERT_TRUE(timer.Start()); - ASSERT_EQ(0, count1); - ASSERT_EQ(0, count2); - - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelay1Us); }); - - ASSERT_EQ(1, count1); - ASSERT_EQ(0, count2); - - timer.TEST_WaitForRun([&] { - mock_clock_->SleepForMicroseconds(kInitDelay2Us - kInitDelay1Us); - }); - - ASSERT_EQ(1, count1); - ASSERT_EQ(1, count2); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, SingleScheduleRepeatedly) { - const int kIterations = 5; - const int kInitDelayUs = 1 * kUsPerSec; - const int kRepeatUs = 1 * kUsPerSec; - - Timer timer(mock_clock_.get()); - int count = 0; - timer.Add([&] { count++; }, "fn_sch_test", kInitDelayUs, kRepeatUs); - - ASSERT_TRUE(timer.Start()); - ASSERT_EQ(0, count); - - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); - - ASSERT_EQ(1, count); - - // Wait for execution to finish - for (int i = 1; i < kIterations; i++) { - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kRepeatUs); }); - } - ASSERT_EQ(kIterations, count); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, MultipleScheduleRepeatedly) { - const int kIterations = 5; - const int kInitDelay1Us = 0 * kUsPerSec; - const int kInitDelay2Us = 1 * kUsPerSec; - const int kInitDelay3Us = 0 * kUsPerSec; - const int kRepeatUs = 2 * kUsPerSec; - const int kLargeRepeatUs = 100 * kUsPerSec; - - Timer timer(mock_clock_.get()); - - int count1 = 0; - timer.Add([&] { count1++; }, "fn_sch_test1", kInitDelay1Us, kRepeatUs); - - int count2 = 0; - timer.Add([&] { count2++; }, "fn_sch_test2", kInitDelay2Us, kRepeatUs); - - // Add a function with relatively large repeat interval - int count3 = 0; - timer.Add([&] { count3++; }, "fn_sch_test3", kInitDelay3Us, kLargeRepeatUs); - - ASSERT_TRUE(timer.Start()); - - ASSERT_EQ(0, count2); - // Wait for execution to finish - for (int i = 1; i < kIterations * (kRepeatUs / kUsPerSec); i++) { - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(1 * kUsPerSec); }); - ASSERT_EQ((i + 2) / (kRepeatUs / kUsPerSec), count1); - ASSERT_EQ((i + 1) / (kRepeatUs / kUsPerSec), count2); - - // large interval function should only run once (the first one). - ASSERT_EQ(1, count3); - } - - timer.Cancel("fn_sch_test1"); - - // Wait for execution to finish - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(1 * kUsPerSec); }); - ASSERT_EQ(kIterations, count1); - ASSERT_EQ(kIterations, count2); - ASSERT_EQ(1, count3); - - timer.Cancel("fn_sch_test2"); - - ASSERT_EQ(kIterations, count1); - ASSERT_EQ(kIterations, count2); - - // execute the long interval one - timer.TEST_WaitForRun([&] { - mock_clock_->SleepForMicroseconds( - kLargeRepeatUs - static_cast(mock_clock_->NowMicros())); - }); - ASSERT_EQ(2, count3); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, AddAfterStartTest) { - const int kIterations = 5; - const int kInitDelayUs = 1 * kUsPerSec; - const int kRepeatUs = 1 * kUsPerSec; - - // wait timer to run and then add a new job - SyncPoint::GetInstance()->LoadDependency( - {{"Timer::Run::Waiting", "TimerTest:AddAfterStartTest:1"}}); - SyncPoint::GetInstance()->EnableProcessing(); - - Timer timer(mock_clock_.get()); - - ASSERT_TRUE(timer.Start()); - - TEST_SYNC_POINT("TimerTest:AddAfterStartTest:1"); - int count = 0; - timer.Add([&] { count++; }, "fn_sch_test", kInitDelayUs, kRepeatUs); - ASSERT_EQ(0, count); - // Wait for execution to finish - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); - ASSERT_EQ(1, count); - - for (int i = 1; i < kIterations; i++) { - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kRepeatUs); }); - } - ASSERT_EQ(kIterations, count); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, CancelRunningTask) { - static constexpr char kTestFuncName[] = "test_func"; - const int kRepeatUs = 1 * kUsPerSec; - Timer timer(mock_clock_.get()); - ASSERT_TRUE(timer.Start()); - int* value = new int; - *value = 0; - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"TimerTest::CancelRunningTask:test_func:0", - "TimerTest::CancelRunningTask:BeforeCancel"}, - {"Timer::WaitForTaskCompleteIfNecessary:TaskExecuting", - "TimerTest::CancelRunningTask:test_func:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - timer.Add( - [&]() { - *value = 1; - TEST_SYNC_POINT("TimerTest::CancelRunningTask:test_func:0"); - TEST_SYNC_POINT("TimerTest::CancelRunningTask:test_func:1"); - }, - kTestFuncName, 0, kRepeatUs); - port::Thread control_thr([&]() { - TEST_SYNC_POINT("TimerTest::CancelRunningTask:BeforeCancel"); - timer.Cancel(kTestFuncName); - // Verify that *value has been set to 1. - ASSERT_EQ(1, *value); - delete value; - value = nullptr; - }); - mock_clock_->SleepForMicroseconds(kRepeatUs); - control_thr.join(); - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, ShutdownRunningTask) { - const int kRepeatUs = 1 * kUsPerSec; - constexpr char kTestFunc1Name[] = "test_func1"; - constexpr char kTestFunc2Name[] = "test_func2"; - Timer timer(mock_clock_.get()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"TimerTest::ShutdownRunningTest:test_func:0", - "TimerTest::ShutdownRunningTest:BeforeShutdown"}, - {"Timer::WaitForTaskCompleteIfNecessary:TaskExecuting", - "TimerTest::ShutdownRunningTest:test_func:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_TRUE(timer.Start()); - - int* value = new int; - *value = 0; - timer.Add( - [&]() { - TEST_SYNC_POINT("TimerTest::ShutdownRunningTest:test_func:0"); - *value = 1; - TEST_SYNC_POINT("TimerTest::ShutdownRunningTest:test_func:1"); - }, - kTestFunc1Name, 0, kRepeatUs); - - timer.Add([&]() { ++(*value); }, kTestFunc2Name, 0, kRepeatUs); - - port::Thread control_thr([&]() { - TEST_SYNC_POINT("TimerTest::ShutdownRunningTest:BeforeShutdown"); - timer.Shutdown(); - }); - mock_clock_->SleepForMicroseconds(kRepeatUs); - control_thr.join(); - delete value; -} - -TEST_F(TimerTest, AddSameFuncName) { - const int kInitDelayUs = 1 * kUsPerSec; - const int kRepeat1Us = 5 * kUsPerSec; - const int kRepeat2Us = 4 * kUsPerSec; - - Timer timer(mock_clock_.get()); - ASSERT_TRUE(timer.Start()); - - int func_counter1 = 0; - ASSERT_TRUE(timer.Add([&] { func_counter1++; }, "duplicated_func", - kInitDelayUs, kRepeat1Us)); - - int func2_counter = 0; - ASSERT_TRUE( - timer.Add([&] { func2_counter++; }, "func2", kInitDelayUs, kRepeat2Us)); - - // New function with the same name should fail to add - int func_counter2 = 0; - ASSERT_FALSE(timer.Add([&] { func_counter2++; }, "duplicated_func", - kInitDelayUs, kRepeat1Us)); - - ASSERT_EQ(0, func_counter1); - ASSERT_EQ(0, func2_counter); - - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); - - ASSERT_EQ(1, func_counter1); - ASSERT_EQ(1, func2_counter); - - timer.TEST_WaitForRun([&] { mock_clock_->SleepForMicroseconds(kRepeat1Us); }); - - ASSERT_EQ(2, func_counter1); - ASSERT_EQ(2, func2_counter); - ASSERT_EQ(0, func_counter2); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, RepeatIntervalWithFuncRunningTime) { - const int kInitDelayUs = 1 * kUsPerSec; - const int kRepeatUs = 5 * kUsPerSec; - const int kFuncRunningTimeUs = 1 * kUsPerSec; - - Timer timer(mock_clock_.get()); - ASSERT_TRUE(timer.Start()); - - int func_counter = 0; - timer.Add( - [&] { - mock_clock_->SleepForMicroseconds(kFuncRunningTimeUs); - func_counter++; - }, - "func", kInitDelayUs, kRepeatUs); - - ASSERT_EQ(0, func_counter); - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); - ASSERT_EQ(1, func_counter); - ASSERT_EQ(kInitDelayUs + kFuncRunningTimeUs, mock_clock_->NowMicros()); - - // After repeat interval time, the function is not executed, as running - // the function takes some time (`kFuncRunningTimeSec`). The repeat interval - // is the time between ending time of the last call and starting time of the - // next call. - uint64_t next_abs_interval_time_us = kInitDelayUs + kRepeatUs; - timer.TEST_WaitForRun([&] { - mock_clock_->SetCurrentTime(next_abs_interval_time_us / kUsPerSec); - }); - ASSERT_EQ(1, func_counter); - - // After the function running time, it's executed again - timer.TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kFuncRunningTimeUs); }); - ASSERT_EQ(2, func_counter); - - ASSERT_TRUE(timer.Shutdown()); -} - -TEST_F(TimerTest, DestroyRunningTimer) { - const int kInitDelayUs = 1 * kUsPerSec; - const int kRepeatUs = 1 * kUsPerSec; - - auto timer_ptr = new Timer(mock_clock_.get()); - - int count = 0; - timer_ptr->Add([&] { count++; }, "fn_sch_test", kInitDelayUs, kRepeatUs); - ASSERT_TRUE(timer_ptr->Start()); - - timer_ptr->TEST_WaitForRun( - [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); - - // delete a running timer should not cause any exception - delete timer_ptr; -} - -TEST_F(TimerTest, DestroyTimerWithRunningFunc) { - const int kRepeatUs = 1 * kUsPerSec; - auto timer_ptr = new Timer(mock_clock_.get()); - - SyncPoint::GetInstance()->DisableProcessing(); - SyncPoint::GetInstance()->LoadDependency({ - {"TimerTest::DestroyTimerWithRunningFunc:test_func:0", - "TimerTest::DestroyTimerWithRunningFunc:BeforeDelete"}, - {"Timer::WaitForTaskCompleteIfNecessary:TaskExecuting", - "TimerTest::DestroyTimerWithRunningFunc:test_func:1"}, - }); - SyncPoint::GetInstance()->EnableProcessing(); - - ASSERT_TRUE(timer_ptr->Start()); - - int count = 0; - timer_ptr->Add( - [&]() { - TEST_SYNC_POINT("TimerTest::DestroyTimerWithRunningFunc:test_func:0"); - count++; - TEST_SYNC_POINT("TimerTest::DestroyTimerWithRunningFunc:test_func:1"); - }, - "fn_running_test", 0, kRepeatUs); - - port::Thread control_thr([&] { - TEST_SYNC_POINT("TimerTest::DestroyTimerWithRunningFunc:BeforeDelete"); - delete timer_ptr; - }); - mock_clock_->SleepForMicroseconds(kRepeatUs); - control_thr.join(); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - - return RUN_ALL_TESTS(); -} diff --git a/util/work_queue_test.cc b/util/work_queue_test.cc deleted file mode 100644 index c23a51279..000000000 --- a/util/work_queue_test.cc +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -/* - * Copyright (c) 2016-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under both the BSD-style license (found in the - * LICENSE file in the root directory of this source tree) and the GPLv2 (found - * in the COPYING file in the root directory of this source tree). - */ -#include "util/work_queue.h" - -#include - -#include -#include -#include -#include -#include - -#include "port/stack_trace.h" - -namespace ROCKSDB_NAMESPACE { - -// Unit test for work_queue.h. -// -// This file is an excerpt from Facebook's zstd repo at -// https://github.com/facebook/zstd/. The relevant file is -// contrib/pzstd/utils/test/WorkQueueTest.cpp. - -struct Popper { - WorkQueue* queue; - int* results; - std::mutex* mutex; - - void operator()() { - int result; - while (queue->pop(result)) { - std::lock_guard lock(*mutex); - results[result] = result; - } - } -}; - -TEST(WorkQueue, SingleThreaded) { - WorkQueue queue; - int result; - - queue.push(5); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(5, result); - - queue.push(1); - queue.push(2); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(1, result); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(2, result); - - queue.push(1); - queue.push(2); - queue.finish(); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(1, result); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(2, result); - EXPECT_FALSE(queue.pop(result)); - - queue.waitUntilFinished(); -} - -TEST(WorkQueue, SPSC) { - WorkQueue queue; - const int max = 100; - - for (int i = 0; i < 10; ++i) { - queue.push(i); - } - - std::thread thread([&queue, max] { - int result; - for (int i = 0;; ++i) { - if (!queue.pop(result)) { - EXPECT_EQ(i, max); - break; - } - EXPECT_EQ(i, result); - } - }); - - std::this_thread::yield(); - for (int i = 10; i < max; ++i) { - queue.push(i); - } - queue.finish(); - - thread.join(); -} - -TEST(WorkQueue, SPMC) { - WorkQueue queue; - std::vector results(50, -1); - std::mutex mutex; - std::vector threads; - for (int i = 0; i < 5; ++i) { - threads.emplace_back(Popper{&queue, results.data(), &mutex}); - } - - for (int i = 0; i < 50; ++i) { - queue.push(i); - } - queue.finish(); - - for (auto& thread : threads) { - thread.join(); - } - - for (int i = 0; i < 50; ++i) { - EXPECT_EQ(i, results[i]); - } -} - -TEST(WorkQueue, MPMC) { - WorkQueue queue; - std::vector results(100, -1); - std::mutex mutex; - std::vector popperThreads; - for (int i = 0; i < 4; ++i) { - popperThreads.emplace_back(Popper{&queue, results.data(), &mutex}); - } - - std::vector pusherThreads; - for (int i = 0; i < 2; ++i) { - auto min = i * 50; - auto max = (i + 1) * 50; - pusherThreads.emplace_back([&queue, min, max] { - for (int j = min; j < max; ++j) { - queue.push(j); - } - }); - } - - for (auto& thread : pusherThreads) { - thread.join(); - } - queue.finish(); - - for (auto& thread : popperThreads) { - thread.join(); - } - - for (int i = 0; i < 100; ++i) { - EXPECT_EQ(i, results[i]); - } -} - -TEST(WorkQueue, BoundedSizeWorks) { - WorkQueue queue(1); - int result; - queue.push(5); - queue.pop(result); - queue.push(5); - queue.pop(result); - queue.push(5); - queue.finish(); - queue.pop(result); - EXPECT_EQ(5, result); -} - -TEST(WorkQueue, BoundedSizePushAfterFinish) { - WorkQueue queue(1); - int result; - queue.push(5); - std::thread pusher([&queue] { queue.push(6); }); - // Dirtily try and make sure that pusher has run. - std::this_thread::sleep_for(std::chrono::seconds(1)); - queue.finish(); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(5, result); - EXPECT_FALSE(queue.pop(result)); - - pusher.join(); -} - -TEST(WorkQueue, SetMaxSize) { - WorkQueue queue(2); - int result; - queue.push(5); - queue.push(6); - queue.setMaxSize(1); - std::thread pusher([&queue] { queue.push(7); }); - // Dirtily try and make sure that pusher has run. - std::this_thread::sleep_for(std::chrono::seconds(1)); - queue.finish(); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(5, result); - EXPECT_TRUE(queue.pop(result)); - EXPECT_EQ(6, result); - EXPECT_FALSE(queue.pop(result)); - - pusher.join(); -} - -TEST(WorkQueue, BoundedSizeMPMC) { - WorkQueue queue(10); - std::vector results(200, -1); - std::mutex mutex; - std::cerr << "Creating popperThreads" << std::endl; - std::vector popperThreads; - for (int i = 0; i < 4; ++i) { - popperThreads.emplace_back(Popper{&queue, results.data(), &mutex}); - } - - std::cerr << "Creating pusherThreads" << std::endl; - std::vector pusherThreads; - for (int i = 0; i < 2; ++i) { - auto min = i * 100; - auto max = (i + 1) * 100; - pusherThreads.emplace_back([&queue, min, max] { - for (int j = min; j < max; ++j) { - queue.push(j); - } - }); - } - - std::cerr << "Joining pusherThreads" << std::endl; - for (auto& thread : pusherThreads) { - thread.join(); - } - std::cerr << "Finishing queue" << std::endl; - queue.finish(); - - std::cerr << "Joining popperThreads" << std::endl; - for (auto& thread : popperThreads) { - thread.join(); - } - - std::cerr << "Inspecting results" << std::endl; - for (int i = 0; i < 200; ++i) { - EXPECT_EQ(i, results[i]); - } -} - -TEST(WorkQueue, FailedPush) { - WorkQueue queue; - EXPECT_TRUE(queue.push(1)); - queue.finish(); - EXPECT_FALSE(queue.push(1)); -} - -TEST(WorkQueue, FailedPop) { - WorkQueue queue; - int x = 5; - EXPECT_TRUE(queue.push(x)); - queue.finish(); - x = 0; - EXPECT_TRUE(queue.pop(x)); - EXPECT_EQ(5, x); - EXPECT_FALSE(queue.pop(x)); - EXPECT_EQ(5, x); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/utilities/env_mirror_test.cc b/utilities/env_mirror_test.cc deleted file mode 100644 index ad4cc9366..000000000 --- a/utilities/env_mirror_test.cc +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. -// Copyright (c) 2015, Red Hat, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "rocksdb/utilities/env_mirror.h" - -#include "env/mock_env.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class EnvMirrorTest : public testing::Test { - public: - Env* default_; - MockEnv *a_, *b_; - EnvMirror* env_; - const EnvOptions soptions_; - - EnvMirrorTest() - : default_(Env::Default()), - a_(new MockEnv(default_)), - b_(new MockEnv(default_)), - env_(new EnvMirror(a_, b_)) {} - ~EnvMirrorTest() { - delete env_; - delete a_; - delete b_; - } -}; - -TEST_F(EnvMirrorTest, Basics) { - uint64_t file_size; - std::unique_ptr writable_file; - std::vector children; - - ASSERT_OK(env_->CreateDir("/dir")); - - // Check that the directory is empty. - ASSERT_EQ(Status::NotFound(), env_->FileExists("/dir/non_existent")); - ASSERT_TRUE(!env_->GetFileSize("/dir/non_existent", &file_size).ok()); - ASSERT_OK(env_->GetChildren("/dir", &children)); - ASSERT_EQ(0U, children.size()); - - // Create a file. - ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); - writable_file.reset(); - - // Check that the file exists. - ASSERT_OK(env_->FileExists("/dir/f")); - ASSERT_OK(a_->FileExists("/dir/f")); - ASSERT_OK(b_->FileExists("/dir/f")); - ASSERT_OK(env_->GetFileSize("/dir/f", &file_size)); - ASSERT_EQ(0U, file_size); - ASSERT_OK(env_->GetChildren("/dir", &children)); - ASSERT_EQ(1U, children.size()); - ASSERT_EQ("f", children[0]); - ASSERT_OK(a_->GetChildren("/dir", &children)); - ASSERT_EQ(1U, children.size()); - ASSERT_EQ("f", children[0]); - ASSERT_OK(b_->GetChildren("/dir", &children)); - ASSERT_EQ(1U, children.size()); - ASSERT_EQ("f", children[0]); - - // Write to the file. - ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); - ASSERT_OK(writable_file->Append("abc")); - writable_file.reset(); - - // Check for expected size. - ASSERT_OK(env_->GetFileSize("/dir/f", &file_size)); - ASSERT_EQ(3U, file_size); - ASSERT_OK(a_->GetFileSize("/dir/f", &file_size)); - ASSERT_EQ(3U, file_size); - ASSERT_OK(b_->GetFileSize("/dir/f", &file_size)); - ASSERT_EQ(3U, file_size); - - // Check that renaming works. - ASSERT_TRUE(!env_->RenameFile("/dir/non_existent", "/dir/g").ok()); - ASSERT_OK(env_->RenameFile("/dir/f", "/dir/g")); - ASSERT_EQ(Status::NotFound(), env_->FileExists("/dir/f")); - ASSERT_OK(env_->FileExists("/dir/g")); - ASSERT_OK(env_->GetFileSize("/dir/g", &file_size)); - ASSERT_EQ(3U, file_size); - ASSERT_OK(a_->FileExists("/dir/g")); - ASSERT_OK(a_->GetFileSize("/dir/g", &file_size)); - ASSERT_EQ(3U, file_size); - ASSERT_OK(b_->FileExists("/dir/g")); - ASSERT_OK(b_->GetFileSize("/dir/g", &file_size)); - ASSERT_EQ(3U, file_size); - - // Check that opening non-existent file fails. - std::unique_ptr seq_file; - std::unique_ptr rand_file; - ASSERT_TRUE( - !env_->NewSequentialFile("/dir/non_existent", &seq_file, soptions_).ok()); - ASSERT_TRUE(!seq_file); - ASSERT_TRUE( - !env_->NewRandomAccessFile("/dir/non_existent", &rand_file, soptions_) - .ok()); - ASSERT_TRUE(!rand_file); - - // Check that deleting works. - ASSERT_TRUE(!env_->DeleteFile("/dir/non_existent").ok()); - ASSERT_OK(env_->DeleteFile("/dir/g")); - ASSERT_EQ(Status::NotFound(), env_->FileExists("/dir/g")); - ASSERT_OK(env_->GetChildren("/dir", &children)); - ASSERT_EQ(0U, children.size()); - ASSERT_OK(env_->DeleteDir("/dir")); -} - -TEST_F(EnvMirrorTest, ReadWrite) { - std::unique_ptr writable_file; - std::unique_ptr seq_file; - std::unique_ptr rand_file; - Slice result; - char scratch[100]; - - ASSERT_OK(env_->CreateDir("/dir")); - - ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); - ASSERT_OK(writable_file->Append("hello ")); - ASSERT_OK(writable_file->Append("world")); - writable_file.reset(); - - // Read sequentially. - ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_)); - ASSERT_OK(seq_file->Read(5, &result, scratch)); // Read "hello". - ASSERT_EQ(0, result.compare("hello")); - ASSERT_OK(seq_file->Skip(1)); - ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Read "world". - ASSERT_EQ(0, result.compare("world")); - ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Try reading past EOF. - ASSERT_EQ(0U, result.size()); - ASSERT_OK(seq_file->Skip(100)); // Try to skip past end of file. - ASSERT_OK(seq_file->Read(1000, &result, scratch)); - ASSERT_EQ(0U, result.size()); - - // Random reads. - ASSERT_OK(env_->NewRandomAccessFile("/dir/f", &rand_file, soptions_)); - ASSERT_OK(rand_file->Read(6, 5, &result, scratch)); // Read "world". - ASSERT_EQ(0, result.compare("world")); - ASSERT_OK(rand_file->Read(0, 5, &result, scratch)); // Read "hello". - ASSERT_EQ(0, result.compare("hello")); - ASSERT_OK(rand_file->Read(10, 100, &result, scratch)); // Read "d". - ASSERT_EQ(0, result.compare("d")); - - // Too high offset. - ASSERT_TRUE(!rand_file->Read(1000, 5, &result, scratch).ok()); -} - -TEST_F(EnvMirrorTest, Locks) { - FileLock* lock; - - // These are no-ops, but we test they return success. - ASSERT_OK(env_->LockFile("some file", &lock)); - ASSERT_OK(env_->UnlockFile(lock)); -} - -TEST_F(EnvMirrorTest, Misc) { - std::string test_dir; - ASSERT_OK(env_->GetTestDirectory(&test_dir)); - ASSERT_TRUE(!test_dir.empty()); - - std::unique_ptr writable_file; - ASSERT_OK(env_->NewWritableFile("/a/b", &writable_file, soptions_)); - - // These are no-ops, but we test they return success. - ASSERT_OK(writable_file->Sync()); - ASSERT_OK(writable_file->Flush()); - ASSERT_OK(writable_file->Close()); - writable_file.reset(); -} - -TEST_F(EnvMirrorTest, LargeWrite) { - const size_t kWriteSize = 300 * 1024; - char* scratch = new char[kWriteSize * 2]; - - std::string write_data; - for (size_t i = 0; i < kWriteSize; ++i) { - write_data.append(1, static_cast(i)); - } - - std::unique_ptr writable_file; - ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); - ASSERT_OK(writable_file->Append("foo")); - ASSERT_OK(writable_file->Append(write_data)); - writable_file.reset(); - - std::unique_ptr seq_file; - Slice result; - ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_)); - ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo". - ASSERT_EQ(0, result.compare("foo")); - - size_t read = 0; - std::string read_data; - while (read < kWriteSize) { - ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch)); - read_data.append(result.data(), result.size()); - read += result.size(); - } - ASSERT_TRUE(write_data == read_data); - delete[] scratch; -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/utilities/env_timed_test.cc b/utilities/env_timed_test.cc deleted file mode 100644 index 3099fb74c..000000000 --- a/utilities/env_timed_test.cc +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2017-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "rocksdb/env.h" -#include "rocksdb/perf_context.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class TimedEnvTest : public testing::Test {}; - -TEST_F(TimedEnvTest, BasicTest) { - SetPerfLevel(PerfLevel::kEnableTime); - ASSERT_EQ(0, get_perf_context()->env_new_writable_file_nanos); - - std::unique_ptr mem_env(NewMemEnv(Env::Default())); - std::unique_ptr timed_env(NewTimedEnv(mem_env.get())); - std::unique_ptr writable_file; - ASSERT_OK(timed_env->NewWritableFile("f", &writable_file, EnvOptions())); - - ASSERT_GT(get_perf_context()->env_new_writable_file_nanos, 0); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/utilities/object_registry_test.cc b/utilities/object_registry_test.cc deleted file mode 100644 index 4042bc9b9..000000000 --- a/utilities/object_registry_test.cc +++ /dev/null @@ -1,862 +0,0 @@ -// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - - -#include "rocksdb/utilities/object_registry.h" - -#include "rocksdb/convenience.h" -#include "rocksdb/customizable.h" -#include "test_util/testharness.h" - -namespace ROCKSDB_NAMESPACE { - -class ObjRegistryTest : public testing::Test { - public: - static int num_a, num_b; -}; - -int ObjRegistryTest::num_a = 0; -int ObjRegistryTest::num_b = 0; -static FactoryFunc test_reg_a = ObjectLibrary::Default()->AddFactory( - ObjectLibrary::PatternEntry("a", false).AddSeparator("://"), - [](const std::string& /*uri*/, std::unique_ptr* /*env_guard*/, - std::string* /* errmsg */) { - ++ObjRegistryTest::num_a; - return Env::Default(); - }); - -class WrappedEnv : public EnvWrapper { - private: - std::string id_; - - public: - WrappedEnv(Env* t, const std::string& id) : EnvWrapper(t), id_(id) {} - const char* Name() const override { return id_.c_str(); } - std::string GetId() const override { return id_; } -}; -static FactoryFunc test_reg_b = ObjectLibrary::Default()->AddFactory( - ObjectLibrary::PatternEntry("b", false).AddSeparator("://"), - [](const std::string& uri, std::unique_ptr* env_guard, - std::string* /* errmsg */) { - ++ObjRegistryTest::num_b; - // Env::Default() is a singleton so we can't grant ownership directly to - // the caller - we must wrap it first. - env_guard->reset(new WrappedEnv(Env::Default(), uri)); - return env_guard->get(); - }); - -TEST_F(ObjRegistryTest, Basics) { - std::string msg; - std::unique_ptr guard; - Env* a_env = nullptr; - - auto registry = ObjectRegistry::NewInstance(); - ASSERT_NOK(registry->NewStaticObject("c://test", &a_env)); - ASSERT_NOK(registry->NewUniqueObject("c://test", &guard)); - ASSERT_EQ(a_env, nullptr); - ASSERT_EQ(guard, nullptr); - ASSERT_EQ(0, num_a); - ASSERT_EQ(0, num_b); - - ASSERT_OK(registry->NewStaticObject("a://test", &a_env)); - ASSERT_NE(a_env, nullptr); - ASSERT_EQ(1, num_a); - ASSERT_EQ(0, num_b); - - ASSERT_OK(registry->NewUniqueObject("b://test", &guard)); - ASSERT_NE(guard, nullptr); - ASSERT_EQ(1, num_a); - ASSERT_EQ(1, num_b); - - Env* b_env = nullptr; - ASSERT_NOK(registry->NewStaticObject("b://test", &b_env)); - ASSERT_EQ(b_env, nullptr); - ASSERT_EQ(1, num_a); - ASSERT_EQ(2, num_b); // Created but rejected as not static - - b_env = a_env; - ASSERT_NOK(registry->NewStaticObject("b://test", &b_env)); - ASSERT_EQ(b_env, a_env); - ASSERT_EQ(1, num_a); - ASSERT_EQ(3, num_b); - - b_env = guard.get(); - ASSERT_NOK(registry->NewUniqueObject("a://test", &guard)); - ASSERT_EQ(guard.get(), b_env); // Unchanged - ASSERT_EQ(2, num_a); // Created one but rejected it as not unique - ASSERT_EQ(3, num_b); -} - -TEST_F(ObjRegistryTest, LocalRegistry) { - Env* env = nullptr; - auto registry = ObjectRegistry::NewInstance(); - std::shared_ptr library = - std::make_shared("local"); - registry->AddLibrary(library); - library->AddFactory( - "test-local", - [](const std::string& /*uri*/, std::unique_ptr* /*guard */, - std::string* /* errmsg */) { return Env::Default(); }); - - ObjectLibrary::Default()->AddFactory( - "test-global", - [](const std::string& /*uri*/, std::unique_ptr* /*guard */, - std::string* /* errmsg */) { return Env::Default(); }); - - ASSERT_NOK( - ObjectRegistry::NewInstance()->NewStaticObject("test-local", &env)); - ASSERT_EQ(env, nullptr); - ASSERT_OK( - ObjectRegistry::NewInstance()->NewStaticObject("test-global", &env)); - ASSERT_NE(env, nullptr); - ASSERT_OK(registry->NewStaticObject("test-local", &env)); - ASSERT_NE(env, nullptr); - ASSERT_OK(registry->NewStaticObject("test-global", &env)); - ASSERT_NE(env, nullptr); -} - -static int RegisterTestUnguarded(ObjectLibrary& library, - const std::string& /*arg*/) { - library.AddFactory( - "unguarded", - [](const std::string& /*uri*/, std::unique_ptr* /*guard */, - std::string* /* errmsg */) { return Env::Default(); }); - library.AddFactory( - "guarded", [](const std::string& uri, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new WrappedEnv(Env::Default(), uri)); - return guard->get(); - }); - return 2; -} - -TEST_F(ObjRegistryTest, CheckShared) { - std::shared_ptr shared; - std::shared_ptr registry = ObjectRegistry::NewInstance(); - registry->AddLibrary("shared", RegisterTestUnguarded, ""); - - ASSERT_OK(registry->NewSharedObject("guarded", &shared)); - ASSERT_NE(shared, nullptr); - shared.reset(); - ASSERT_NOK(registry->NewSharedObject("unguarded", &shared)); - ASSERT_EQ(shared, nullptr); -} - -TEST_F(ObjRegistryTest, CheckStatic) { - Env* env = nullptr; - std::shared_ptr registry = ObjectRegistry::NewInstance(); - registry->AddLibrary("static", RegisterTestUnguarded, ""); - - ASSERT_NOK(registry->NewStaticObject("guarded", &env)); - ASSERT_EQ(env, nullptr); - env = nullptr; - ASSERT_OK(registry->NewStaticObject("unguarded", &env)); - ASSERT_NE(env, nullptr); -} - -TEST_F(ObjRegistryTest, CheckUnique) { - std::unique_ptr unique; - std::shared_ptr registry = ObjectRegistry::NewInstance(); - registry->AddLibrary("unique", RegisterTestUnguarded, ""); - - ASSERT_OK(registry->NewUniqueObject("guarded", &unique)); - ASSERT_NE(unique, nullptr); - unique.reset(); - ASSERT_NOK(registry->NewUniqueObject("unguarded", &unique)); - ASSERT_EQ(unique, nullptr); -} - -TEST_F(ObjRegistryTest, FailingFactory) { - std::shared_ptr registry = ObjectRegistry::NewInstance(); - std::shared_ptr library = - std::make_shared("failing"); - registry->AddLibrary(library); - library->AddFactory( - "failing", [](const std::string& /*uri*/, - std::unique_ptr* /*guard */, std::string* errmsg) { - *errmsg = "Bad Factory"; - return nullptr; - }); - std::unique_ptr unique; - std::shared_ptr shared; - Env* pointer = nullptr; - Status s; - s = registry->NewUniqueObject("failing", &unique); - ASSERT_TRUE(s.IsInvalidArgument()); - s = registry->NewSharedObject("failing", &shared); - ASSERT_TRUE(s.IsInvalidArgument()); - s = registry->NewStaticObject("failing", &pointer); - ASSERT_TRUE(s.IsInvalidArgument()); - - s = registry->NewUniqueObject("missing", &unique); - ASSERT_TRUE(s.IsNotSupported()); - s = registry->NewSharedObject("missing", &shared); - ASSERT_TRUE(s.IsNotSupported()); - s = registry->NewStaticObject("missing", &pointer); - ASSERT_TRUE(s.IsNotSupported()); -} - -TEST_F(ObjRegistryTest, TestRegistryParents) { - auto grand = ObjectRegistry::Default(); - auto parent = ObjectRegistry::NewInstance(); // parent with a grandparent - auto uncle = ObjectRegistry::NewInstance(grand); - auto child = ObjectRegistry::NewInstance(parent); - auto cousin = ObjectRegistry::NewInstance(uncle); - - auto library = parent->AddLibrary("parent"); - library->AddFactory( - "parent", [](const std::string& uri, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new WrappedEnv(Env::Default(), uri)); - return guard->get(); - }); - library = cousin->AddLibrary("cousin"); - library->AddFactory( - "cousin", [](const std::string& uri, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new WrappedEnv(Env::Default(), uri)); - return guard->get(); - }); - - Env* env = nullptr; - std::unique_ptr guard; - std::string msg; - - // a:://* is registered in Default, so they should all work - ASSERT_OK(parent->NewStaticObject("a://test", &env)); - ASSERT_OK(child->NewStaticObject("a://test", &env)); - ASSERT_OK(uncle->NewStaticObject("a://test", &env)); - ASSERT_OK(cousin->NewStaticObject("a://test", &env)); - - // The parent env is only registered for parent, not uncle, - // So parent and child should return success and uncle and cousin should fail - ASSERT_OK(parent->NewUniqueObject("parent", &guard)); - ASSERT_OK(child->NewUniqueObject("parent", &guard)); - ASSERT_NOK(uncle->NewUniqueObject("parent", &guard)); - ASSERT_NOK(cousin->NewUniqueObject("parent", &guard)); - - // The cousin is only registered in the cousin, so all of the others should - // fail - ASSERT_OK(cousin->NewUniqueObject("cousin", &guard)); - ASSERT_NOK(parent->NewUniqueObject("cousin", &guard)); - ASSERT_NOK(child->NewUniqueObject("cousin", &guard)); - ASSERT_NOK(uncle->NewUniqueObject("cousin", &guard)); -} - -class MyCustomizable : public Customizable { - public: - static const char* Type() { return "MyCustomizable"; } - MyCustomizable(const char* prefix, const std::string& id) : id_(id) { - name_ = id_.substr(0, strlen(prefix) - 1); - } - const char* Name() const override { return name_.c_str(); } - std::string GetId() const override { return id_; } - - private: - std::string id_; - std::string name_; -}; - -TEST_F(ObjRegistryTest, TestFactoryCount) { - std::string msg; - auto grand = ObjectRegistry::Default(); - auto local = ObjectRegistry::NewInstance(); - std::unordered_set grand_types, local_types; - std::vector grand_names, local_names; - - // Check how many types we have on startup. - // Grand should equal local - grand->GetFactoryTypes(&grand_types); - local->GetFactoryTypes(&local_types); - ASSERT_EQ(grand_types, local_types); - size_t grand_count = grand->GetFactoryCount(Env::Type()); - size_t local_count = local->GetFactoryCount(Env::Type()); - - ASSERT_EQ(grand_count, local_count); - grand->GetFactoryNames(Env::Type(), &grand_names); - local->GetFactoryNames(Env::Type(), &local_names); - ASSERT_EQ(grand_names.size(), grand_count); - ASSERT_EQ(local_names.size(), local_count); - ASSERT_EQ(grand_names, local_names); - - // Add an Env to the local registry. - // This will add one factory. - auto library = local->AddLibrary("local"); - library->AddFactory( - "A", [](const std::string& /*uri*/, std::unique_ptr* /*guard */, - std::string* /* errmsg */) { return nullptr; }); - ASSERT_EQ(local_count + 1, local->GetFactoryCount(Env::Type())); - ASSERT_EQ(grand_count, grand->GetFactoryCount(Env::Type())); - local->GetFactoryTypes(&local_types); - local->GetFactoryNames(Env::Type(), &local_names); - ASSERT_EQ(grand_names.size() + 1, local_names.size()); - ASSERT_EQ(local_names.size(), local->GetFactoryCount(Env::Type())); - - if (grand_count == 0) { - // There were no Env when we started. Should have one more type - // than previously - ASSERT_NE(grand_types, local_types); - ASSERT_EQ(grand_types.size() + 1, local_types.size()); - } else { - // There was an Env type when we started. The types should match - ASSERT_EQ(grand_types, local_types); - } - - // Add a MyCustomizable to the registry. This should be a new type - library->AddFactory( - "MY", [](const std::string& /*uri*/, - std::unique_ptr* /*guard */, - std::string* /* errmsg */) { return nullptr; }); - ASSERT_EQ(local_count + 1, local->GetFactoryCount(Env::Type())); - ASSERT_EQ(grand_count, grand->GetFactoryCount(Env::Type())); - ASSERT_EQ(0U, grand->GetFactoryCount(MyCustomizable::Type())); - ASSERT_EQ(1U, local->GetFactoryCount(MyCustomizable::Type())); - - local->GetFactoryNames(MyCustomizable::Type(), &local_names); - ASSERT_EQ(1U, local_names.size()); - ASSERT_EQ(local_names[0], "MY"); - - local->GetFactoryTypes(&local_types); - ASSERT_EQ(grand_count == 0 ? 2 : grand_types.size() + 1, local_types.size()); - - // Add the same name again. We should now have 2 factories. - library->AddFactory( - "MY", [](const std::string& /*uri*/, - std::unique_ptr* /*guard */, - std::string* /* errmsg */) { return nullptr; }); - local->GetFactoryNames(MyCustomizable::Type(), &local_names); - ASSERT_EQ(2U, local_names.size()); -} - -TEST_F(ObjRegistryTest, TestManagedObjects) { - auto registry = ObjectRegistry::NewInstance(); - auto m_a1 = std::make_shared("", "A"); - auto m_a2 = std::make_shared("", "A"); - - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_OK(registry->SetManagedObject(m_a1)); - ASSERT_EQ(registry->GetManagedObject("A"), m_a1); - - ASSERT_NOK(registry->SetManagedObject(m_a2)); - ASSERT_OK(registry->SetManagedObject(m_a1)); - m_a1.reset(); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_OK(registry->SetManagedObject(m_a2)); - ASSERT_EQ(registry->GetManagedObject("A"), m_a2); -} - -TEST_F(ObjRegistryTest, TestTwoManagedObjects) { - auto registry = ObjectRegistry::NewInstance(); - auto m_a = std::make_shared("", "A"); - auto m_b = std::make_shared("", "B"); - std::vector> objects; - - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("B"), nullptr); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 0U); - ASSERT_OK(registry->SetManagedObject(m_a)); - ASSERT_EQ(registry->GetManagedObject("B"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), m_a); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 1U); - ASSERT_EQ(objects.front(), m_a); - - ASSERT_OK(registry->SetManagedObject(m_b)); - ASSERT_EQ(registry->GetManagedObject("A"), m_a); - ASSERT_EQ(registry->GetManagedObject("B"), m_b); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 2U); - ASSERT_OK(registry->ListManagedObjects("A", &objects)); - ASSERT_EQ(objects.size(), 1U); - ASSERT_EQ(objects.front(), m_a); - ASSERT_OK(registry->ListManagedObjects("B", &objects)); - ASSERT_EQ(objects.size(), 1U); - ASSERT_EQ(objects.front(), m_b); - ASSERT_OK(registry->ListManagedObjects("C", &objects)); - ASSERT_EQ(objects.size(), 0U); - - m_a.reset(); - objects.clear(); - - ASSERT_EQ(registry->GetManagedObject("B"), m_b); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 1U); - ASSERT_EQ(objects.front(), m_b); - - m_b.reset(); - objects.clear(); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("B"), nullptr); -} - -TEST_F(ObjRegistryTest, TestAlternateNames) { - auto registry = ObjectRegistry::NewInstance(); - auto m_a = std::make_shared("", "A"); - auto m_b = std::make_shared("", "B"); - std::vector> objects; - // Test no objects exist - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("B"), nullptr); - ASSERT_EQ(registry->GetManagedObject("TheOne"), nullptr); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 0U); - - // Mark "TheOne" to be A - ASSERT_OK(registry->SetManagedObject("TheOne", m_a)); - ASSERT_EQ(registry->GetManagedObject("B"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("TheOne"), m_a); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 1U); - ASSERT_EQ(objects.front(), m_a); - - // Try to mark "TheOne" again. - ASSERT_NOK(registry->SetManagedObject("TheOne", m_b)); - ASSERT_OK(registry->SetManagedObject("TheOne", m_a)); - - // Add "A" as a managed object. Registered 2x - ASSERT_OK(registry->SetManagedObject(m_a)); - ASSERT_EQ(registry->GetManagedObject("B"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), m_a); - ASSERT_EQ(registry->GetManagedObject("TheOne"), m_a); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 2U); - - // Delete "A". - m_a.reset(); - objects.clear(); - - ASSERT_EQ(registry->GetManagedObject("TheOne"), nullptr); - ASSERT_OK(registry->SetManagedObject("TheOne", m_b)); - ASSERT_EQ(registry->GetManagedObject("TheOne"), m_b); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 1U); - ASSERT_EQ(objects.front(), m_b); - - m_b.reset(); - objects.clear(); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("TheOne"), nullptr); - ASSERT_OK(registry->ListManagedObjects(&objects)); - ASSERT_EQ(objects.size(), 0U); -} - -TEST_F(ObjRegistryTest, TestTwoManagedClasses) { - class MyCustomizable2 : public MyCustomizable { - public: - static const char* Type() { return "MyCustomizable2"; } - MyCustomizable2(const char* prefix, const std::string& id) - : MyCustomizable(prefix, id) {} - }; - - auto registry = ObjectRegistry::NewInstance(); - auto m_a1 = std::make_shared("", "A"); - auto m_a2 = std::make_shared("", "A"); - std::vector> obj1s; - std::vector> obj2s; - - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - - ASSERT_OK(registry->SetManagedObject(m_a1)); - ASSERT_EQ(registry->GetManagedObject("A"), m_a1); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - - ASSERT_OK(registry->SetManagedObject(m_a2)); - ASSERT_EQ(registry->GetManagedObject("A"), m_a2); - ASSERT_OK(registry->ListManagedObjects(&obj1s)); - ASSERT_OK(registry->ListManagedObjects(&obj2s)); - ASSERT_EQ(obj1s.size(), 1U); - ASSERT_EQ(obj2s.size(), 1U); - ASSERT_EQ(obj1s.front(), m_a1); - ASSERT_EQ(obj2s.front(), m_a2); - m_a1.reset(); - obj1s.clear(); - obj2s.clear(); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), m_a2); - - m_a2.reset(); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); -} - -TEST_F(ObjRegistryTest, TestManagedObjectsWithParent) { - auto base = ObjectRegistry::NewInstance(); - auto registry = ObjectRegistry::NewInstance(base); - - auto m_a = std::make_shared("", "A"); - auto m_b = std::make_shared("", "A"); - - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_OK(base->SetManagedObject(m_a)); - ASSERT_EQ(registry->GetManagedObject("A"), m_a); - - ASSERT_NOK(registry->SetManagedObject(m_b)); - ASSERT_OK(registry->SetManagedObject(m_a)); - - m_a.reset(); - ASSERT_EQ(registry->GetManagedObject("A"), nullptr); - ASSERT_OK(registry->SetManagedObject(m_b)); - ASSERT_EQ(registry->GetManagedObject("A"), m_b); -} - -TEST_F(ObjRegistryTest, TestGetOrCreateManagedObject) { - auto registry = ObjectRegistry::NewInstance(); - registry->AddLibrary("test")->AddFactory( - ObjectLibrary::PatternEntry::AsIndividualId("MC"), - [](const std::string& uri, std::unique_ptr* guard, - std::string* /* errmsg */) { - guard->reset(new MyCustomizable("MC", uri)); - return guard->get(); - }); - std::shared_ptr m_a, m_b, obj; - std::vector> objs; - - std::unordered_map opt_map; - - ASSERT_EQ(registry->GetManagedObject("MC@A#1"), nullptr); - ASSERT_EQ(registry->GetManagedObject("MC@B#1"), nullptr); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@A#1", &m_a)); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@B#1", &m_b)); - ASSERT_EQ(registry->GetManagedObject("MC@A#1"), m_a); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@A#1", &obj)); - ASSERT_EQ(obj, m_a); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@B#1", &obj)); - ASSERT_EQ(obj, m_b); - ASSERT_OK(registry->ListManagedObjects(&objs)); - ASSERT_EQ(objs.size(), 2U); - - objs.clear(); - m_a.reset(); - obj.reset(); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@A#1", &m_a)); - ASSERT_EQ(1, m_a.use_count()); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@B#1", &obj)); - ASSERT_EQ(2, obj.use_count()); -} - -TEST_F(ObjRegistryTest, RegisterPlugin) { - std::shared_ptr registry = ObjectRegistry::NewInstance(); - std::unique_ptr guard; - Env* env = nullptr; - - ASSERT_NOK(registry->NewObject("unguarded", &env, &guard)); - ASSERT_EQ(registry->RegisterPlugin("Missing", nullptr), -1); - ASSERT_EQ(registry->RegisterPlugin("", RegisterTestUnguarded), -1); - ASSERT_GT(registry->RegisterPlugin("Valid", RegisterTestUnguarded), 0); - ASSERT_OK(registry->NewObject("unguarded", &env, &guard)); - ASSERT_NE(env, nullptr); -} -class PatternEntryTest : public testing::Test {}; - -TEST_F(PatternEntryTest, TestSimpleEntry) { - ObjectLibrary::PatternEntry entry("ABC", true); - - ASSERT_TRUE(entry.Matches("ABC")); - ASSERT_FALSE(entry.Matches("AABC")); - ASSERT_FALSE(entry.Matches("ABCA")); - ASSERT_FALSE(entry.Matches("AABCA")); - ASSERT_FALSE(entry.Matches("AB")); - ASSERT_FALSE(entry.Matches("BC")); - ASSERT_FALSE(entry.Matches("ABD")); - ASSERT_FALSE(entry.Matches("BCA")); -} - -TEST_F(PatternEntryTest, TestPatternEntry) { - // Matches A:+ - ObjectLibrary::PatternEntry entry("A", false); - entry.AddSeparator(":"); - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AB")); - ASSERT_FALSE(entry.Matches("B")); - ASSERT_FALSE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA:B")); - ASSERT_FALSE(entry.Matches("AA:BB")); - ASSERT_TRUE(entry.Matches("A:B")); - ASSERT_TRUE(entry.Matches("A:BB")); - - entry.SetOptional(true); // Now matches "A" or "A:+" - ASSERT_TRUE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AB")); - ASSERT_FALSE(entry.Matches("B")); - ASSERT_FALSE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA:B")); - ASSERT_FALSE(entry.Matches("AA:BB")); - ASSERT_TRUE(entry.Matches("A:B")); - ASSERT_TRUE(entry.Matches("A:BB")); -} - -TEST_F(PatternEntryTest, MatchZeroOrMore) { - // Matches A:* - ObjectLibrary::PatternEntry entry("A", false); - entry.AddSeparator(":", false); - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AB")); - ASSERT_FALSE(entry.Matches("B")); - ASSERT_TRUE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("B:")); - ASSERT_FALSE(entry.Matches("B:A")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA:B")); - ASSERT_FALSE(entry.Matches("AA:BB")); - ASSERT_TRUE(entry.Matches("A:B")); - ASSERT_TRUE(entry.Matches("A:BB")); - - entry.SetOptional(true); // Now matches "A" or "A:*" - ASSERT_TRUE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AB")); - ASSERT_FALSE(entry.Matches("B")); - ASSERT_TRUE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("B:")); - ASSERT_FALSE(entry.Matches("B:A")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA:B")); - ASSERT_FALSE(entry.Matches("AA:BB")); - ASSERT_TRUE(entry.Matches("A:B")); - ASSERT_TRUE(entry.Matches("A:BB")); -} - -TEST_F(PatternEntryTest, TestSuffixEntry) { - ObjectLibrary::PatternEntry entry("AA", true); - entry.AddSuffix("BB"); - - ASSERT_TRUE(entry.Matches("AA")); - ASSERT_TRUE(entry.Matches("AABB")); - - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AB")); - ASSERT_FALSE(entry.Matches("B")); - ASSERT_FALSE(entry.Matches("BB")); - ASSERT_FALSE(entry.Matches("ABA")); - ASSERT_FALSE(entry.Matches("BBAA")); - ASSERT_FALSE(entry.Matches("AABBA")); - ASSERT_FALSE(entry.Matches("AABBB")); -} - -TEST_F(PatternEntryTest, TestNumericEntry) { - ObjectLibrary::PatternEntry entry("A", false); - entry.AddNumber(":"); - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_TRUE(entry.Matches("A:1")); - ASSERT_TRUE(entry.Matches("A:11")); - ASSERT_FALSE(entry.Matches("AA:1")); - ASSERT_FALSE(entry.Matches("AA:11")); - ASSERT_FALSE(entry.Matches("A:B")); - ASSERT_FALSE(entry.Matches("A:1B")); - ASSERT_FALSE(entry.Matches("A:B1")); - - entry.AddSeparator(":", false); - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_TRUE(entry.Matches("A:1:")); - ASSERT_TRUE(entry.Matches("A:11:")); - ASSERT_FALSE(entry.Matches("A:1")); - ASSERT_FALSE(entry.Matches("A:B1:")); - ASSERT_FALSE(entry.Matches("A:1B:")); - ASSERT_FALSE(entry.Matches("A::")); -} - -TEST_F(PatternEntryTest, TestDoubleEntry) { - ObjectLibrary::PatternEntry entry("A", false); - entry.AddNumber(":", false); - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("A:")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA:1")); - ASSERT_FALSE(entry.Matches("AA:11")); - ASSERT_FALSE(entry.Matches("A:B")); - ASSERT_FALSE(entry.Matches("A:1B")); - ASSERT_FALSE(entry.Matches("A:B1")); - ASSERT_TRUE(entry.Matches("A:1")); - ASSERT_TRUE(entry.Matches("A:11")); - ASSERT_TRUE(entry.Matches("A:1.1")); - ASSERT_TRUE(entry.Matches("A:11.11")); - ASSERT_TRUE(entry.Matches("A:1.")); - ASSERT_TRUE(entry.Matches("A:.1")); - ASSERT_TRUE(entry.Matches("A:0.1")); - ASSERT_TRUE(entry.Matches("A:1.0")); - ASSERT_TRUE(entry.Matches("A:1.0")); - - ASSERT_FALSE(entry.Matches("A:1.0.")); - ASSERT_FALSE(entry.Matches("A:1.0.2")); - ASSERT_FALSE(entry.Matches("A:.1.0")); - ASSERT_FALSE(entry.Matches("A:..10")); - ASSERT_FALSE(entry.Matches("A:10..")); - ASSERT_FALSE(entry.Matches("A:.")); - - entry.AddSeparator(":", false); - ASSERT_FALSE(entry.Matches("A:1")); - ASSERT_FALSE(entry.Matches("A:1.0")); - - ASSERT_TRUE(entry.Matches("A:11:")); - ASSERT_TRUE(entry.Matches("A:1.1:")); - ASSERT_TRUE(entry.Matches("A:11.11:")); - ASSERT_TRUE(entry.Matches("A:1.:")); - ASSERT_TRUE(entry.Matches("A:.1:")); - ASSERT_TRUE(entry.Matches("A:0.1:")); - ASSERT_TRUE(entry.Matches("A:1.0:")); - ASSERT_TRUE(entry.Matches("A:1.0:")); - - ASSERT_FALSE(entry.Matches("A:1.0.:")); - ASSERT_FALSE(entry.Matches("A:1.0.2:")); - ASSERT_FALSE(entry.Matches("A:.1.0:")); - ASSERT_FALSE(entry.Matches("A:..10:")); - ASSERT_FALSE(entry.Matches("A:10..:")); - ASSERT_FALSE(entry.Matches("A:.:")); - ASSERT_FALSE(entry.Matches("A::")); -} - -TEST_F(PatternEntryTest, TestIndividualIdEntry) { - auto entry = ObjectLibrary::PatternEntry::AsIndividualId("AA"); - ASSERT_TRUE(entry.Matches("AA")); - ASSERT_TRUE(entry.Matches("AA@123#456")); - ASSERT_TRUE(entry.Matches("AA@deadbeef#id")); - - ASSERT_FALSE(entry.Matches("A")); - ASSERT_FALSE(entry.Matches("AAA")); - ASSERT_FALSE(entry.Matches("AA@123")); - ASSERT_FALSE(entry.Matches("AA@123#")); - ASSERT_FALSE(entry.Matches("AA@#123")); -} - -TEST_F(PatternEntryTest, TestTwoNameEntry) { - ObjectLibrary::PatternEntry entry("A"); - entry.AnotherName("B"); - ASSERT_TRUE(entry.Matches("A")); - ASSERT_TRUE(entry.Matches("B")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("BB")); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("BA")); - ASSERT_FALSE(entry.Matches("AB")); -} - -TEST_F(PatternEntryTest, TestTwoPatternEntry) { - ObjectLibrary::PatternEntry entry("AA", false); - entry.AddSeparator(":"); - entry.AddSeparator(":"); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA::")); - ASSERT_FALSE(entry.Matches("AA::12")); - ASSERT_TRUE(entry.Matches("AA:1:2")); - ASSERT_TRUE(entry.Matches("AA:1:2:")); - - ObjectLibrary::PatternEntry entry2("AA", false); - entry2.AddSeparator("::"); - entry2.AddSeparator("##"); - ASSERT_FALSE(entry2.Matches("AA")); - ASSERT_FALSE(entry2.Matches("AA:")); - ASSERT_FALSE(entry2.Matches("AA::")); - ASSERT_FALSE(entry2.Matches("AA::#")); - ASSERT_FALSE(entry2.Matches("AA::##")); - ASSERT_FALSE(entry2.Matches("AA##1::2")); - ASSERT_FALSE(entry2.Matches("AA::123##")); - ASSERT_TRUE(entry2.Matches("AA::1##2")); - ASSERT_TRUE(entry2.Matches("AA::12##34:")); - ASSERT_TRUE(entry2.Matches("AA::12::34##56")); - ASSERT_TRUE(entry2.Matches("AA::12##34::56")); -} - -TEST_F(PatternEntryTest, TestTwoNumbersEntry) { - ObjectLibrary::PatternEntry entry("AA", false); - entry.AddNumber(":"); - entry.AddNumber(":"); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AA:")); - ASSERT_FALSE(entry.Matches("AA::")); - ASSERT_FALSE(entry.Matches("AA::12")); - ASSERT_FALSE(entry.Matches("AA:1:2:")); - ASSERT_TRUE(entry.Matches("AA:1:2")); - ASSERT_TRUE(entry.Matches("AA:12:23456")); - - ObjectLibrary::PatternEntry entry2("AA", false); - entry2.AddNumber(":"); - entry2.AddNumber("#"); - ASSERT_FALSE(entry2.Matches("AA")); - ASSERT_FALSE(entry2.Matches("AA:")); - ASSERT_FALSE(entry2.Matches("AA:#")); - ASSERT_FALSE(entry2.Matches("AA#:")); - ASSERT_FALSE(entry2.Matches("AA:123#")); - ASSERT_FALSE(entry2.Matches("AA:123#B")); - ASSERT_FALSE(entry2.Matches("AA:B#123")); - ASSERT_TRUE(entry2.Matches("AA:1#2")); - ASSERT_FALSE(entry2.Matches("AA:123#23:")); - ASSERT_FALSE(entry2.Matches("AA::12#234")); -} - -TEST_F(PatternEntryTest, TestPatternAndSuffix) { - ObjectLibrary::PatternEntry entry("AA", false); - entry.AddSeparator("::"); - entry.AddSuffix("##"); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("AA::")); - ASSERT_FALSE(entry.Matches("AA::##")); - ASSERT_FALSE(entry.Matches("AB::1##")); - ASSERT_FALSE(entry.Matches("AB::1##2")); - ASSERT_FALSE(entry.Matches("AA##1::")); - ASSERT_TRUE(entry.Matches("AA::1##")); - ASSERT_FALSE(entry.Matches("AA::1###")); - - ObjectLibrary::PatternEntry entry2("AA", false); - entry2.AddSuffix("::"); - entry2.AddSeparator("##"); - ASSERT_FALSE(entry2.Matches("AA")); - ASSERT_FALSE(entry2.Matches("AA::")); - ASSERT_FALSE(entry2.Matches("AA::##")); - ASSERT_FALSE(entry2.Matches("AB::1##")); - ASSERT_FALSE(entry2.Matches("AB::1##2")); - ASSERT_TRUE(entry2.Matches("AA::##12")); -} - -TEST_F(PatternEntryTest, TestTwoNamesAndPattern) { - ObjectLibrary::PatternEntry entry("AA", true); - entry.AddSeparator("::"); - entry.AnotherName("BBB"); - ASSERT_TRUE(entry.Matches("AA")); - ASSERT_TRUE(entry.Matches("AA::1")); - ASSERT_TRUE(entry.Matches("BBB")); - ASSERT_TRUE(entry.Matches("BBB::2")); - - ASSERT_FALSE(entry.Matches("AA::")); - ASSERT_FALSE(entry.Matches("AAA::")); - ASSERT_FALSE(entry.Matches("BBB::")); - - entry.SetOptional(false); - ASSERT_FALSE(entry.Matches("AA")); - ASSERT_FALSE(entry.Matches("BBB")); - - ASSERT_FALSE(entry.Matches("AA::")); - ASSERT_FALSE(entry.Matches("AAA::")); - ASSERT_FALSE(entry.Matches("BBB::")); - - ASSERT_TRUE(entry.Matches("AA::1")); - ASSERT_TRUE(entry.Matches("BBB::2")); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} - diff --git a/utilities/util_merge_operators_test.cc b/utilities/util_merge_operators_test.cc deleted file mode 100644 index fed6f1a75..000000000 --- a/utilities/util_merge_operators_test.cc +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. -// This source code is licensed under both the GPLv2 (found in the -// COPYING file in the root directory) and Apache 2.0 License -// (found in the LICENSE.Apache file in the root directory). - -#include "test_util/testharness.h" -#include "test_util/testutil.h" -#include "utilities/merge_operators.h" - -namespace ROCKSDB_NAMESPACE { - -class UtilMergeOperatorTest : public testing::Test { - public: - UtilMergeOperatorTest() {} - - std::string FullMergeV2(std::string existing_value, - std::vector operands, - std::string key = "") { - std::string result; - Slice result_operand(nullptr, 0); - - Slice existing_value_slice(existing_value); - std::vector operands_slice(operands.begin(), operands.end()); - - const MergeOperator::MergeOperationInput merge_in( - key, &existing_value_slice, operands_slice, nullptr); - MergeOperator::MergeOperationOutput merge_out(result, result_operand); - merge_operator_->FullMergeV2(merge_in, &merge_out); - - if (result_operand.data()) { - result.assign(result_operand.data(), result_operand.size()); - } - return result; - } - - std::string FullMergeV2(std::vector operands, - std::string key = "") { - std::string result; - Slice result_operand(nullptr, 0); - - std::vector operands_slice(operands.begin(), operands.end()); - - const MergeOperator::MergeOperationInput merge_in(key, nullptr, - operands_slice, nullptr); - MergeOperator::MergeOperationOutput merge_out(result, result_operand); - merge_operator_->FullMergeV2(merge_in, &merge_out); - - if (result_operand.data()) { - result.assign(result_operand.data(), result_operand.size()); - } - return result; - } - - std::string PartialMerge(std::string left, std::string right, - std::string key = "") { - std::string result; - - merge_operator_->PartialMerge(key, left, right, &result, nullptr); - return result; - } - - std::string PartialMergeMulti(std::deque operands, - std::string key = "") { - std::string result; - std::deque operands_slice(operands.begin(), operands.end()); - - merge_operator_->PartialMergeMulti(key, operands_slice, &result, nullptr); - return result; - } - - protected: - std::shared_ptr merge_operator_; -}; - -TEST_F(UtilMergeOperatorTest, MaxMergeOperator) { - merge_operator_ = MergeOperators::CreateMaxOperator(); - - EXPECT_EQ("B", FullMergeV2("B", {"A"})); - EXPECT_EQ("B", FullMergeV2("A", {"B"})); - EXPECT_EQ("", FullMergeV2({"", "", ""})); - EXPECT_EQ("A", FullMergeV2({"A"})); - EXPECT_EQ("ABC", FullMergeV2({"ABC"})); - EXPECT_EQ("Z", FullMergeV2({"ABC", "Z", "C", "AXX"})); - EXPECT_EQ("ZZZ", FullMergeV2({"ABC", "CC", "Z", "ZZZ"})); - EXPECT_EQ("a", FullMergeV2("a", {"ABC", "CC", "Z", "ZZZ"})); - - EXPECT_EQ("z", PartialMergeMulti({"a", "z", "efqfqwgwew", "aaz", "hhhhh"})); - - EXPECT_EQ("b", PartialMerge("a", "b")); - EXPECT_EQ("z", PartialMerge("z", "azzz")); - EXPECT_EQ("a", PartialMerge("a", "")); -} - -} // namespace ROCKSDB_NAMESPACE - -int main(int argc, char** argv) { - ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -}

    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%0DsoR6LTH&%ahRHH%1bD==&qT=G>rlwRgcpI7w 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 A;s5{u diff --git a/docs/static/images/rocksdb-secondary-cache/Mixgraph_hit_rate.png b/docs/static/images/rocksdb-secondary-cache/Mixgraph_hit_rate.png deleted file mode 100644 index 10fa7372825ee43dad0c60299d569dd29ee9f104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 802366 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

  • eL8N{~94M@EE- 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
    PQ8*8U#|=hQwhZ`IOlrN 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�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

    Y%5Ws7? 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⁡PiFGpYE< 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�ne@BBdOT--vfJIxPK4 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∑h&`PBly!}9;4(X7d{}4P|AFI=_wuVsHVvT?E zh4>2|R=~QHxUXE7uh3fIrRaf?Lu~}UPB1xm@yXL~oZ7pRBCneP{+U&_RcDTp{Emxb zX)a^YI0Xgb@|*Ju;z=HvSer1`0db8c3vX%*01*shp~$uQqx|La#*5EeLcwJ4*mv z>f^oHm`WuT`uJSHUQx$8s|nkmmvgigfsT8h0(&YFe-@xU+8#woa+-)_F@EX19!pk& zZQETtQ_8-7oso1PeTctsa6Tw6)9&J^LZaG7VmsY66%MPv|0*nqK<5fAT*% zfbU?X%&!4eCy1ePs@xJSXZ>0Z{IGgn&joaHDgWpSP%L3g9KGXu-=v7Zw({fD@#wb! z`suyAJ;q8%`_hHySqkz-OvvW)TqIEyF+v)1DJ?N#j?7Y%Z;nsk=AK=AUBG-$L3=nsaCwI(>Ou7j ze{B>U_rZ>(D;RR;#Qi$8pY)NBwBHbWmACR#ZW$R5zoO=vvbMW#tA88oGQgWc@tx>> zJG&VK2JHryD%vFYJQ(<~vwYS|$y#QmPk%7a3PJRLZPesHWHTF04AT(cy=JPl+9g@l zBt*6_oLn3r+Zva3gGCt%r}?A=o=mYO-byLbmG_%3*-tlS!}Abcu6!-ko9I18iDa5$ zez7fG*yt9k{R^S;KR%+QN|AE1;oJc$F>`XwsOWY@68BYkO!Cs{Y_ZLC*qMnp%2dc) z*PjAn=VtT9lV=)jQcpjMvMI#bO^tno)CMq(t6kYm|DrFztMduRWQnS)u`Q}El{N0a zN)u!3lDrI{yl55ideUrad6+pR^Y|q?*{aLhv(s9Pu~z*}-XxQQ8>)J{$!h%N%-+S$ z2iv4MgWVctB(#^KTy4^V??1C`oL$FfWv}JVrH`EN7~0?eI=(Pw)5pTb<>|{HH7F`v zClg({-;l_wE9td48CaPW>kg357KkU9bj-y~E`R$me#tKS`B#zvO$hOlb_g@}k`F_T zHIJR;bM0}B`zl%=$_XdMKvWOF*Sy@l{#Ng+$O1-*s<)4JReg}n2PxN#pbuj|y@kjKG(XAW>7NcTtIF4ILAZa`=l_pOchNzdvLyA4 zZOQ@n!8|~dcQEzs> zE3))UarfpYmGZA2rE>zOzH2v#Fv8ru*va#?od90?ZC-8tZ#Qiy(Ag?n^|SmFp3Kzu zJs=8m6CHW^;HEd_k@JMf^Tw9>Z{uyKM}h|*WvoXdWd6;Q6w zXg56B6|!X7eyp3>&>|@mrVZs4+4ps6057Fw>FW=d-sd51o$0lntNWSj+mqw6>rkdU zp2xw<{!0#^i37g-rOK3S>r#3%ugcUYdrw=^fokcNk|uHFc6x5qY& zB#z*E4wX2BZmDEV0#GCDJa!t&!(RxlIto@%CD3Bi#<3DEM)kwS5J=Lyt`J8LEcI$U z!6Q;(iz$A!va%~xdZ+3Qt_@^-U%l~k=BucJ@zjWMx zP^i9G>`tK)OEU|)5lAMM9d>60M67(x$~8w8|0~c?>_x9yhHqGEb#Ic;ZUXJQE&&uXXI*U2 z#+hs&jL%^3_U(=ui6v~A!vwAS%2KxVP=(;>GnYIK(=n{Q#h`&AqixD-tEr?bY%(@^ z1!edh{2mJrALi)qy8sR&_8Y&;-pXBH^KZ~fx)KqDs0Rz7&3>Z>29Vb>+S(g>Bo}pn zjp?&QU5B5O7`GSS`sP2mmoln@iE&h>Gx$md1Lxh`5nuQe}MFzYFt&{B^)9&m9v=E?ZNU@!}JX(_sl~ z?+jh8e6tKA){jHTYPx`GSelkP=ZNn+UeNl&fE{f3#s%8C&it1_2z~4Ib@k77_>{(* z_xpvL+Vi?r=TW=oGstef?FY(zf#O9Dt%>C7u9c8x%w$Sn45R2g1wm2io(ha6 z-(XzzH@|1UUp^Y&U0C-ZWu>WV`(eR*DK})l1JckIKp?@;$@&SLim#kRR+m!p0w3{@ z=hC0AG@gYYyA5kYUylzGNc3GclKUwisQ-O zWP#V@a;wJ#YhVcf*2`T#`Z(q7VBUNEZ?GC<(>c{vr*gAN z<;(rlLy-j0NNGTL;z0pSNxyL>^0Tganls-hB~1*Ny5I>0`q&`M$iPkG1#xeswSuAW zSnc|*L%plrz_bE3*14LAuN;rh1Ng26a*ET}EGqrmwv0LQ_O2KU6YF&Nfh#%wO(4YSO__DxPb#__A4Z52J+>gcrMC+jp^cnk&#D(ekOhYml&?mcC z#?^f1#;PG3Uun|2vbFjUCDhAtqZV|sp*iL_xGnbE1U2tV^~xU^Cu)`(0@&D{ACS`x z-P0x`(UKp+kYDlm!e6?v^&{A%egO_q+Z(n=E;5!%#*H~iQ)_0ioOoxII&-imGyS0b zmVe#8`+cYM|1yCZ_tH)NfQI_!t|-^!SVMbb6Dj`e3>FGME4R8OxIz9()Ad@(lp+Q6 z>ouT1%D$m&{>Xnqo}$vyU(3u%x8q<5b$2vJU2NqdFGX*P^q|E3w{&E+S5!g@ZO}C> zX6_P~CWC#oyzVCi&s}wXwwU&+AlnKR({o~?hieNJ<^lnp;F)xJKTjPnI?xa*aGNG} z@d8i>^O4LQ!rXZFX^nh>xLmTfBMRgwD9ZMIHm&!(`;u>5=sc8{2^l85M?Q02zZQl$ zxm^_c_^{2O-_o`98^J1l^0<)~($x|tQtSc$QX|Fs@!%Odg*A1kX&1*GajbcE0*Jq= zo!~TM5hYP}^ejUF_gKPE>K!yg<3HoVY=D503HNCh=)| z5y;zU8%aeDUhYA)9$BY8z;FrO{W&i8mmVcV+VN_OMxKdjC11ff-cICEJ4vI6) ziQW(WKjl{P@K+JqGCOp-h*xe&s^!;OqY&M?hkDq`o3kjA%896jYIAz$Q>{$GEphEq z^REliG&qTDI$F*%*4=(liWh=x^grcGd2LrK*8$;aY5~_CZRTC_YF-aA3^*C^YRF!b zN7CSG>-cad+MV5bG~7iz6h1E3kM=vwfzIo}VF9_I-vNitdl{2_;qL_QUOSpNlLWR# zLeKI{KY1d(s!ep_UO|5on8rReq;MmZ8rMONe|`9#mVBgIn?B;Cz-#o zU%ORsYEMRmg~@{W9o>nId;VK;kY#{QyV*B1rk|}v(MBoijhVEjXlRm0nowDouZPL? z*L6N^`ITml5uV<|yufCzCUQTK&3o~NY16P5G05QLYpdTg*L51|bkYBQ0@8P90a7vt z6}=~df8P)BFQ<;bKLp#0c2(-vM|yuudcG|%VesQ{XoxhMzqlb-Cf-O#6UNdQA`=;p>i1azvsQ~H>KPUDb<%s?2+-J0lynPwZ8_y6V z3?emFS35ie1=ZHwOHen8d0N_5T%y@L zSnsiBViA(>&>%jR0^dJ2gO)gmGq2uh6=jaGV6!Cq3eNN)bdZLdgb1BD+0`@LPXPQT zJ<=SE!f&jSWjYc&(K}AAI`p0skmmGuroEmcNccUk$Tg$IY6+V!L)>DSB($aRkq5N7 zw1~U4LQp@T=GgW1TEaAZrD><`r?Z(}D1N}jAxN;&>v`c22mcas?T@Ikx`qq+pR|e9 zU#qml0=y#93S`b#8oY3yzDj_cYsqc)aSk82Ly2MAt4syjs-hR>3qPWpmn~y^%4xxb9qSU;~+< zfzYO@yMPv@flNYfD1Fo5TlteI{UU2jDNWl|V$DCq^)uPxL>Cp;IE$Z~y5;}K z4iba-BJu0kS?gh>^%n4;SqB_=beG}Msm_jxil2w!&Q_scn>I(w>v%L*mOL7p8E?5+ z=5gBTqQ|SwF@O`b)YW&6F@Ff6au19^u89s?nW?zN#%+ZL_m&`8h2r<7*HHGBs|RDz zhVAi_KPsj}>y>K?xqWl|TVb0aMOuknGs9i*UP}oGW}rII$3KR!3Rij{533 zw7+gSA>+FHu!jAmXLFLlEN?s;<0H;UW0|mQ&If$vHV})v*68Z@5dd=3~?$RWKK1| z!2F)E{gRsJcoBe%W+4Vh4)$xoFTVRu^MB_|Fcg|Bdwi!4Ad40)$TLenT?V0}HuS)v zDJH^IQi1JNPAVIYGRLQ+-6(Z2rA!yjB~XY)NeY7PD%x{i2#s#pVrRFg3HO zoxlfoFa^zl*z$paiSo-#R9PR=5=$Iwj1i~3-6^}wXmHcsM^m$jN-|}FK0_?Ql~cCW zQ`4JGBV5*nZSc@vbG#EUEmwSmcCj|{({K%|rKe=^VuzWf%#Fo4m(}~!vZ>Tc(+xdkKcl|hF?XTzF4lT^y^iXcS;Q>6w>~J|6Z_g|<+=e!=jzjf4#U+WfY(ib$GDfvJzF_7|q=vyFWEQzPgMq{$w6V^(fJo0BQc6IoV>{ zARcegRViywRv)BA9cH^B655ak&Rz#{c>oKAO!*JAh{!;@vXctuDT zNy0#bzcwwqdAf?jRDOeH@Hs`;!N!mKcFW0m@QXh>%IJKQ4}*B{UJY&e$nGwYQ#!1% z(d_YS*EI`cemalHE;W40Dn|wMoyt*O>W!Rc6k}VaNLqUucw8dQNyOY?;uUu@t1~2b;&UF6}Y}<2%uGisl>(g(mD9#@1L&l@?49bLwFoBY!CF3 z@{!OrLRhw8Y)!9-2;}}y$}_=Wo*!+>pA`bFLO|cQS^(I3K7-_R+W19%eMqzG;oOo% z8=#>{P~R=le?e2DHZ${6i>j}W7698Ln%3CZk`RaCa5}sngg$(>V1QkS%{R$8Q)IwY zGmP(;Gv^H(&S$vFjQcMKmJAU_sTZCgbMy1R?W%vTakIU#x+*T2foTOc?=>y#xa>rK z$O(4UGnyT%u^a83<&%PyFEtH9FgohnK3gS9{}e3$YtjF6BDv@u_u4)K(~S~^^fmi| z=1d%o+kv_6=C8`E5H;Oqnu|Je^TV=0IMHD(>OD9Zi0C6rwu}~cKAi6mXXdpB5%0un zt>Exe=j#?!Y`5$(w-aZ7_zyMu*g)@1TNq0nm5R>Trtfwiy8|5>l?8k4pF6I<>W~v} zsOl*huXXU*5Fi_xx&eAc>|D5VrQKHj8lXHUO{r1E7Z(rLNV6oRO#FgD0W1zXHeeDo zE|W>Y*uc|htwdUWOnct(Qpkla=Fy@iq}V@bDt7Q!1dZ?PK34cy+X9G-&Ok--Pg=+z z0)9WTVwG`pYAKJTzQmqUDFR4|bA9jMF!`f7njYFNH>hoRzq9DFrkFIpC4R1wGyI_@X?9&}%7J@JRLMrbl$;H=K zS8^A9^TYP_sY7G>&j3@`!V&L-r+2G&GG_UBNvxb6g@Rf6Rkb#uk8OcPeqIKi4rzdb zO(U{OBEw|K2}zZ!xQo;gdyDFNLTBUC#;pq$b$S#cE^T`Es4})AD)0|{sM40wPv(WY z;nF=wakWFGpt4rB7Tzf?gvfE0cg>xa$zP1zruA%a_4G{5yuWJeg%}Nku5lZ>qYOE%5Vb+645^B3 zWUH$tZ9(fE}PEit+=4s~ruGXw7j}AQ}FQdhRc33gBrI5+1w1nM= z$Re}rX4C@gzkTG6u1*&-VT=k^aJ&+}LqxRhl^x$4S-jIqljb4@WebH&A zbNbwNXSLA=^g>7ZbLR&#tA}fsUBLx-&O0fd{(oO(N>cRmqT~hnEFF=&kiOKZp*HrO zR60zIHxBFo4o~xLKd8;F)UY_kTEV+DY%t>SX)qq#WXh464OX#gN>+!0+0Oo|{$0mn z7iO=gkE~{rLN^N!WR^}d-;8V)uBPh^gNAGTdjmlAt8#rly}ONC!}r=JW={V?HUBeC z7LczODNe`SHV|g_Z6*(c@M9wF^lnSj65uD$nSGhkvL5WaU$JFtzY|AIGzywu-2VNn z6hpn`@>RYx@Lbu99oZsE=?fdNl#sZxzP)N~+Dm~}D4dG>To6cX8?@~`ZVM_@E^J;t zFzQLXakz10M>xl6r`d+PqkV&~S*t@sEvAd6vj)67Oh~&gW-3PYc%)B0tgiH0LGTN!`8+IM zZfR`rQP9X41I-e@1v6hdvEAIP zhGd#t80)hQb5`P0?9;!x$JaI2h_S&BNCnVOpr-aGo*y_E^;XU0gbwo{s3VfdZ1|Md z)xN$ji@lKF>X)^J_xnfb$2LfVs*3iokIzN#F-*J92&TkG@n;b;k18)P1uX{dl$V)o ze5_vM7&VOfG17wB|E|(xBhJ4h5@pW;woKiZ21S++DXnt)5-v+uwxh0W$TNJ;eFfdy zZRmpD2oUkNf04aVGFD}q1dRCFm8S5g7i7 zs-n2tVuk`z$T!x=r(=%OF1U5e=V0g|YF;iok#6Q-@L;=xvvl|zn6Qf4ijC}JRb1h0 zB({Bb1ocApzAD}SExS7%`SW3bDX_(eSSL|)%DJ(1C`&?|Keg#?3`wkr+8C*;+}Vr^ zehr#$QdsI4#%)2iB$;60*X*2Lox}t_UTstvgnBAxf86Wwelp{>WprN(4S>9z7zqI{ zDiyXA*2G3jdcKyK`=PhZ1Ph%siTRZD_!>VI%z4v{tNDK+u- z91Rd{s{FQn@u5ubek!B7v>gFV$tDK9*9dFI>Bv84%P9dmKhPzsea_d2}gK<0)eNfLbV+9={1=Ye%HiZCemBRtB_W;4AZ9S z8q)nQ+B#lih?&*k;4P4WP%4AR?X1ggi-P><^p@W=J>JI|9M@Gnu`64<`ad0^qYCa% zt3+|fYdRTi{5o}7NYjZ}K2$Bi|t?WN1pBPTT%J_Pj#aH8Y6KHBtr zJ=zoBkqT59ed9X+;B|xJW}~uzQ(DU`t9(153E8^Yi18Ave{`gHFL;)Vc*?P|B)7NC z^sD?7FMmOPz(@(zYhEU$KH5`Js%0h%__Y0csu5j9M8c7_ta=bKc{XsT5 zCu3{)ZK2orFup|Ew%K~BOg3nwpmiy7Y?s*#70-bmnX0e~uZ1dGkxa|Yc}Zd`LxVe) z9$t71A9$Tt2#aibw-WayCVunlxeI$H$FI+jBHtg2$1Gn!60T=IpMr?bTC~L8rxXMYCBFKa5x; zE`7JZs+yknJf~1`rCPu;b6kkGnN?6QBNr+^0UcPC?xnpjYUr?B(lmA0nwqE{a3kb8 zCI^WODghlA`5qrUIQXgf-BuQYD)DhH8f}!Rs|#Dmd6Rwo^uUMI>!)O3H@j1}@mwef z`o$unW81fU_!_T+aswJ_VpY+s+SNokK04PZ|Pygb!VO(*%(eqNL=kciYrvHS;I6c(#H4~Q&lt*jcwF>&4gs|5UiH#h_K2re6afh=NcMr z8O@AKDM8N%LcF?_>6|g7uf=(m^e2GXWIVI!S5IcYDWP+od<7J$Zu6sn)4&2wj%Y~B zePU0l$I`3>ltbobnO6SEMsx=UR)pm@DQ%vsChUL;u=(2ZGmW6@5wZVUi=}DVfwH_; zLa14Trq0a)|GJXd1fz~)J;I_~al@-z4Lf80U*FEZr+pWqBmrrN|{ z=dY?t8hvdh)NwS~k$3oA39f;TO7^BU*ntC^38|ihV{^;PBN?biXo*B!|8J=+cF`LT zwZdT=G~oeCCXWdQz+aQEVb4bbaPSrP4exJ7>0+b>$3<5(@oZ^f?c;5yQ>OUoF-=S)(#&ZC`>`pCPT^tV)vuL z{#})C#0$K?nZ%h-#m4TBH;$5%*Q}`pRZVKiLE&hz+0t=K7`57|*;|;%YJM%W45bCD zsHL8msFk7`U)^2Ljcb)-DLYYF^H-I<{t%Ltg&Zl-l9TGNcLwZHk86e5s^`2Obf5dh z-gXjJda~|&D2*|gA3A6oeLkv`w1yJ-f^JxkdYF3wV7PJ<4APcAhrdSMHcHNq{pCF~i`&0UFSDJ7*|t3ObWV2_m#q^rO1E9)(EHqf{8ods+UjcV`#bcyy}$h0 zyD?B?QCWF&kY)b9-BEcdl7Hl0U-U?zFDZ2Rielr}JgtMp^ajl-3bDDGJVv&unM$IQ z&lZX6b{teH@8kYafykCsyx}QSz-g7~>Ojp7T3| zc)+}_f1bdWr`TLJIy+&)*t>r9-h&VPwK5eMuJB zBs~!suajd;tS#(L#bP=5^z$tR;sAH+-|&_cX}87oV>rdygX*KU^H(^nA1>usQhNrJ zHCjaZDQky4HkhK24H|ZLn)e8$>&}#)NZ{Ldi7un?<>E*k=qP#Gg8B%PK4&KT)ZYEo zAuOS=&y$+6a<+lECTy4-F5 zOx!&hF*7lmhU!lZBbhTeASXY*eB#%*`$OA&>b9m`VGM|K^d_tK(2GUDiTu7uN|tEY zE%v35kq;VWCE)!}sm*H=)MeRTO@>*|HdL>y6YI;je?EhijN6Je-!WxEg9AxK{fzkC z^m{KNifg3UTR>YOeOY6vKjev-4Nmy4>P$nUqkaaq)$hn>NeFm1Q_uNjP2<`{{6VC^ z?PTFKx==v*Aryl*+|yFT_PR@;vOfOS`Al^I z7~hOZje&~iq(`eP&O<>;J{Ra2Fp`&deY5)F^1|hKE&ro=>i?S!l!$c6GbpDkV}`3+ zBSG-WF&Wn{p6jxP!Y!m3JBTmOm31ahdaYSs_zF-Jt+_@xi)u_9+V|`nwFnOn zXSS9IQ>%Kv9I=qrERd{Gw$mIDp731Qa`-=_ePuvY>l*GBPy_@-1e7)arMpW}kXGqd z>FykcFhCkbLb{P0YUq+0dg!4WhHe;`A@1_HgMIEf`|N%1zmbr&zVD6aecoEnT%*m^ zFGj?tr|@srofS?hFE^9f@!sjBthVi^>Imhkzm8Fmvk?j{;6CD@r+vYEtxo!0dypYxP0EATU4p;b)nuO@fcE zmt{_2-u7}xx~p!9aFVrGa>(ZP|3du#U$S@c=jhpRhE+K*%{GxN`>vDc@=ap)8fSYY zx5QxbcXEwaa~9=6P=~~%%GLoG&fXYhm^p(C#uN>jPi%3V6M}t(32vFci02s0)7(wt z8K0XjacKFR1#Lt^J-S_*B?9a3L3?|*_VZm^au!kN6^@dQr|1kOKC8pe;}5hSI!sst zuxxRd$s*40k!2KzR$hAtXkQeTdMI!AE>?VEc+ewJo%7WCt-nb{BAy`H>j=(RZU6RT zNvf$7*sE0CPd80XmT4$Y6D>cPYwMb?NJ;qxBjyu$y~U6fZBtmcZ=M2yi0Yp1ZCOTZ*e>ds<$7!r%CkZ z>An}Q=TE4D`}wP(90nQZvFUg<=!}N@_lD`Ut*9>9Wo~NI=ncVo-9_H>Oh+HK6y&pr z?@AO;(6ilYYUkQFADpfE7@vMp%6~9B;J8po4o1Vqu&d8TRqrVr9SJ9AZ@cexyDC3i zp*soj+ua9OT;E9gKVwjf~G<`6ew#$YwfZMq~BkFqD; z9T|J_`R#fBami)<%IoEo6;N{@w~{l9-EzKQbi9tln1{wp!0IZt@H_XCC3dvC{&O3I z0km3gnv~QqHZr^SVX-tz3VYwH`H0CdHdZUdWA)3EuCCsbgJ7A0dKW^gMPa2(2rxDf z&2UJ?*mBp`2gxKn7HnY=wecf5ajiPjNj>N*6XG%LVcpSi<|^bI6P!H_-P=j)jILcD zW0Eucd4-iC3!0dB`g9MNIE5o45G#PrI9OqYX4gE3XUWIeKYt$16gM4iObBm#RL!5i z)N1te4xVND;O!(i#Tr{;rnU{O2UxgyIVH*WYxPZT>g9Ojwyx2Ywa3#K{@t#neLo*{ z#}iGsj+W5YCJQgWOW4_!_HK00{d;o>L1&3e)o+CAMP2MrQtkM&Eh6PcbmawA=)%N# zxNW}ne(s6SL5R;{SFgcnWpln{M3|7d(xAJX)p5Ou#N_^RYY5f2C-p!Y``WPB^DJ!E z!R;y>QyR|d(iwKMKKa7#1LP4sPg zU(oKb@9Ci)-R**fL#^j&)}ik!j7dM)KjL=Jver3d{~Y<#?OcE7gNHdU-Xw9)T(LfW zJ&N^^@X4v|TdrWaze(gKBZ0_ftG*VQRuNWc(b9k3OS{x(IljXA6~l3!`N}1WW%Wbu zsySH+!QS2ii^mwp3WOo-I*c+^qVvERF&*!+@Ls)MB>7H~25-Y*yfsu5wY=-^%H&a% z8#R4yNHZYH{RWih(XO9h=GId_;ehaI{9Ln^pFHtR8mSX{ZFu^CHZSqCnnT*st<+3# zKjzSVx+osOrlVgzz5rFrjXIQDUU51)s0JF|(O$_F>lTE+=s&z9|E{p-W`dzRY9{jv z*SNEu|3SB>CZbBJ>6LQ%_#t{=-pRmb5J8obOI6BsCTTqQ>7GFR;VF~#DHEY{Vj7uZ z)(SKxXRYEH9db>$8+6_k7#}*2zI6&TT2T)|EiOwU*>vJ%fS&C`n+2W&Pe|ygPv{$3 zyvYiR#P&*yd~#KJ1A{DL6aRmS5{06!y^RQYIM6go{;zA zSaview#zpe0ZdqY6cj1DL!#JB99V_JmXT*&a7ji4u9a-Q{!PjZUu1B#?ej9tgpR|h zaOGOM@>(bjoz-o}o&}fk&x7c&&M#M}5B5aH787o_mOM_OSxIYCxc)4pVe?RG_jJBK z5NO7UK8N3|#oiXp&V3RrZ zV3$`MqFN;;ArFh8oBQ)6x6QVTl7MbOay$G$s*TKh%IX?%&{ar@>1O8ZE@h{~rJ|ZW z=B@)uF%JiGP@;-O0I8Lw9Z>=ilj~-yBTg#*NEFXE%g3URHU8xtKlm65gj;!xn&?0; zZyrnUMZQ#3sWnpL&#yLQi_6K?fhUwNU&;?ZVxqy^J*jzdzQfnN$U%d{OK<)-{DlCl zIME{y?X?$8F*gRBb<2lg0^F}RrYY@sjq{nL#cI>Mq3Joy4~B+PzJYHHa#rT#NJ`X` z*^KH~OT19oYe>XN`26~M+to`dm7nYkrFEGZ|CM#>4Z>Oq`z2J6q@hHwT3?{Auaot8rQohOR#Den$ud=DSw6 z`}41Cel^5d53Z$)uZ(G4-?{dIafvs$p9)K{e_g9ej&!jpOF1dA<&g$PUjU>M0%tB< zdb$P_3C}taJ_`*59N~CbMPk#2=0L0S?hUy#7kU25?$(+lW~s|f6X^Rn)^8IeUc!;u z4KG;aykfrXaS>sCrB7bmT+D(+zo_IQWhZz1*JzAo=S!pxywh<@n23u->3`>~J=kZS zZR3(k3iTIg6cBL(>Rwr!zQe;&%Qd zJ`TCGCBc^Kf~Ndwii{q{jn^ST)TjU3s6Fde!z+C|Pq|F$eetkNI*DiPx`KFE=6AP^ z_NaE1#>;rW5)S-KiPN86?m}fApBep8!2CaQ<^O^*^bwbtk!)z|#gnJgkUVWCOkQOW zdrfgpPKCxWsU7=)uxaU1H0dK$H;x?1#tcqr3(7|Zw;3;H z3(i5IcNaiv-d_aOr2i2K1oRE@$?%XH-?-V1fN^GxWXG4gg>8<{eJp7qw>(%~L_w67 z;%)M^c^hx6sh2yNu^Yo8hK67h=!G{__1|L-G$!4Lo|rQ_ZuClrWsnVvp&esG^MQK7ljS?>LtVpt*Tc*)zey0}ax5N%c~N?f_E z%quCj(he9}Z!nWTobS+EuyGo|iTzW`xD^^ay<@jt{AqR7|GrFaHOZhf=VRktJN35D zFSHj{J-}ydguSOg(Tw`5cDfazU9p~$zBDnWI6yFuja{2P`YZ!trR(}~l^M(9j7rqs zcn(?CKN&Th=93DdSLIzHmtf?J%uY6&&bIdEoz~4VJZ0n&LIgs%zmN0 zhnku}g1_bHn^OX8nf=pa7h>g>*{epu4J>Sl$MeBj1f)OvaOsDzJi{`z-Kkb!=By_986rQ1%7wCE1Zb!qVkUE+0|!jEZ@tdj{H z`b^N}4@?5{M`d6c0aLS#zo+&aSpH&J&frV)W?W>P+Eiw8R!l^|YNR(lSZL%9W=fLN zO{TQy;0o925Atjq@CAOP;{wy3uGyNy8mR{o(CJEe?W8vgqP<=Xa+p^@)PB;!;!Nbd z%pI0m9QaAY409z~T+D*GJg_c#OBsbJCq3&g0Xf|zk_NmMfB8m7@+i03&)e;n_u1;( zucGa`@|SVop8^eFuBG}V)3}d(Y-2AnQw<9roReFhOZ1`ljm`^S?HFjji3A1rnUo8& zh@uyaN2_45l#O7KfG@@xZW5%PTCEirFb|;p_bvN*pcX_eEg)!Y*rpHX_0-xjg**JF6wcu*Oyt#ebR6?8We@&2-{;nS!)S^nxhWx zEr~|v?eVZH)V?C&M$x%_aMHtn6>mnC}CuGB#y1(T5hVc^x$y zioG~3hZ8J^^W2wTeR(}MrYB_EC%^~UcTL0<=^@QkXPc#%z9;E!Vsw|fso>g;-oTF= z!jsXpnRzdoBog^7)g>TaV%7qYB>6sO4Le=k=_$^%QhZ;XE5${aUd*-*m=?E0F_D9Od-^{t0l-!iSKQh<%yqK1e($z9@0Xmgfd&rJ-(CJpK?30uEhzcsvRh|UPE?i zsTK%qf^=an@xx^i8(~$4DI~tEq-IXq#a2&Qf9@jm9mLxfixsx)`A56O!rgpLvR3eD zhFoYyi=~ld6nTl_;3}O*yofth0zg7=#->~HR0nlBX&@3hKwG>>F^SqkfBEGSjM|M! zuqU&j7?#+A20uwhL!)LLR;h-vuH1y+YRxCM*la$M^AyrQP2z0qGa zl@FQPnSeQn2+C0*9^xxhZ4?v`1tom=FQ>^)#3le9bK(_uj^#1H`MS3<9ur0aJPU!7lMr>2kY4 zSGsOpe^8~BL?OMFReu69LomfD6pN$)2ayslOVd@9K&H>sYmq)J@1!=UV^eAA<40A$ z=4_KN?d^V$a$yU#65cxvvtw*Ge57r^sBWRJR(@}y z(lrDUU%o6M_m-iWcj5?}h6N(;f0cvRf4@Vn`WQ(PJL0U}KuVIGcb$_#-8&;}x&qR= z3XEc>>%;epw*FRNW4A@+m^);foPjLA;!bBSrVao?+AXgl6lWv_-w;S~@E$g|j4fxu zcY_cE_VEgPL!CjcIolcG7Tf6ldm3sS;39{qxk<|RG-RjE!#*dUiw!I=0;#Bg{ER}| zGsr>w=|pHh$2^;GJ#qrw3ruwQyO90gf6gdEvU?X-e`tFa*hE*iX{x6^vf6p0ur3c~ z)%QkKr^VhS*59z5y=XDq@Uq{PX4(wvv{J~~m%iAb*i^Wlql{k9h9F^?CP7A(@Sw2- zN7dCM+zp3Y9Ndbp>L;Bbj9y`G-8vCXq@CC;oter>`U^t>QHPa#6c)Xm+UxVTM2yPM zmFZ6BR$p2QsIPT1TDYk+8J4d$lvfbijHh5G#z6h8&Z`0Xa$!)KqH}-L?fi4wAB7;Z zlkjR8z$qL7pNV%9Kp%AtJ?y6;cR1^@k3|GL(~D8gre**pIOKO=;2D zN78BeHb~&)HAwlsn)&)ll9m_ZeD2D-;1;)(%HD5(YPv(sWE6DjpBp`0&lYvO@W$Z$ zTjjTirqTSFIiC*{?ss?1i+-XniR*LpT zo$iNZg=H&bw?kH!VcOWdPt?h4_4pYby>yu612vS~0o6!!m=h5r@pj^hAh2{=EPanw>y4hK1|oLAP@%pesp z0k+Hoqln2;JHTSjswRo3z>(w~?Sp&_&Qsb?tI`+diR&|nPacxKG}pgKyP ze43iVPvz4RH=HR6twT!Qem?+86U0WRHcwu4cVyUl&=`){e#==kupm3nrac#fu5KY? z?C8diwH3SHiGWdICG%;-9H1hHv$=e2aTiZkDYIWnG7gNEziWmDlye{+>Q@fZW(|o- z4EAoWudbge&ulbUtK@wG<%iei8`n!QT-UBYvjq2DLuRmUT&h=V^FGsofx(PvzB^bwp9{8^C)+ypd5dlt1QA* ziGC(NjZT7)P@9LZTW!g7Jyg7G{zOj7Ak3*o=}Z|Zo`8fK3WXU9g^8aXy@M7}liC^- zsMI)QOBB>*AKLE8bu4{og41Rp#5Hstxob%^ZCTtsFGr(K`C+5>PhyF$mGAkF`p#%+ zZ|-27l4GjM5+Ije8^rn98cZIy`+G0u^8CY%VHYC{Usj3{RlUh7*#){E9H-=?hO-aP zUL`Ii;FH!`b^ELjhz@bf#ADROYMdNXVek(?&CODy+Gd%wN`U<)4Z)Jo4Npml7>3E% zw7S8DpJS-1vx>S|?;{wE8~7}T2jO4Kx1|ZMNo3icr+6u)rp!J>b4Jo-NJ{WF zb;F)Zyj&VED$^8~Mn82c>~>kQ4o>;JmR+)4|BtNd%6+@#2JvIJJy3xPrHEH*@GWz% zggI-Z?uuBYaYl9K(Xv4i-Ooj)WQrXBP>Rtp&`>mb)E_BcLOUULL@ zkV62l<+jLuQW41#m0u8FK0`?;R4~Ljv^}LA>Xm$6-D-o*v>LfCw@7VaK|7)%hf1N*WP{y{%rmoSuw4w?|c&#a0|;? zuZ<3W7w56=0bYsc@oC(y}c<5Pnog?lmnEFMCY+TK8dUqlgwwMWo;>%8A-j76fn=5ZZg*cid8>*Z0kqWEv&Oj zES7Vp)6i=;@8KHeWM5uANw-d^om;WBf<gqjAO z((=f)e*_ePxtqvCWuunj^DhDhpcju*!Xbvs{}xPL*P`dxwK-f$ZCGg-Oq$k)E^8cL zTBEwu`89=%h=ZgP|FA)dC!$B?P3QUK@k*wQfX&3n@VlbVcHh8|XWJq+*dlkwn80Nd zHA*YPKfT`Ki>{*LqgN$46 zj^(Jy6FYCku|t4VFGwEwCZp5y%~u&l{j5l0j$^-qK2e7^AOIW6QlL4Ob|Q0f0w3p^ zl^JXC@bQYOH#Lz_da$k$rTD&*nGVP7TOSvH+V=^+b8_cZbSV~}! z;dl}B=>`<2_zOgdQvZ3=_+Nu?`fyEOXG`_AM7<*|W=XCB#ls6B*P|PLo*tf`tvxN zHfCQ*^?bjU|MAGbb>RM$b&!<4`uscBJ$BNFzdI`W49 z`unG6#Fwx_SL|tGF8>qR{rv&|bhttck_}v0>;ggmFH;Er4dIL7ToufOIlm(RTj%tr z$N2Z(SolHz#8*wQ{@%Fy$L0OQPrm!t>1SeS5x!dg|3Vk8@5*n65i8zN%A`ojA$rILiLl~wpFPG$U{!1ThxXMut8M%&@|w_kES zX7&sDQg{fGOoU$W3f%Y<~k`fIei4 zytMevXyPc~^}8o|o7>+{iw!ZuDq9f)Nb%9<6tq=^-!udFUn_)cZGSQ}`c`KEtd-r< zAQ>OiZVJUkZAC^99%Runtz~5Iq~pB(zKxlxLE&DbPT#`JavLpgZ&$q?iz?8W4A340 z)?HBxdCu%3lVv3c6#G+&_eyH>HwKZ~h@qWP_Fm-D-s^(MN)u!3jsa))&upl?(#xUt zZz^53tO0~UlVtNr3Rh{GXWxsY&NbfZdY=To-6|#nGKGzKzx91d#AIQ)gw*9cWM{Yc z0#W9hNAe7Qo)gYj!}{m7&3`SY&uSY|-eHXPFt(k;Y;ldgS!O((zLnC1HZlIAhg*{e zS{kHpW1YUYI83DzG(9SWQ@-3!&&bdc_dI7CEmCY^tfDPn)!Ntq#=Q!PWR|-Rh|FdI zW~Xhrq_tg>@DdTFXla_zLy2;)8rdng#p7>=22Sm2+&d@PB;!&k&jmFNhihC>p&!9r zN~BJ_#Gk~H4&0BAvy2)Svbo(mcptK{VXW$>nHZamrdXCHWQb@!dc@#eiIv$Xx;HCi zo&F6R0icqTZ$z^2iGoGkqKHIJHoEBD%TnuAvP+3u?LibdUCf~R{;XYfz&?` zm=Ki#>~!!T+bpJdHZjufi#{bM#a8VSFlz|m4cWp>SF7WO9(;~jEenHq2~ykfKH3up zlWGj{4_Efm{o$m26>b2YP*x+T@}GoX2{;PR<|8SR`YaqFu48fWSs_eR3+*u4PqIjZ z`v_gv63wW{LQ)!t7$e+FaAP~=6pxB$Ofq!7O@@+|=Mn67`4l^_mO5aVFB=m^EljVR zpWD~*x{pP?Rm4{c7uCK>hU(0B-2Cj7aSFfHs>%jh4WQ(DUG>Bk?eh-3?XTH+k#_IXk;;E~>?$q3{r6px)m2-rPgRcs? z2%e+1gcF~hRf6jk>H&tL`fPq~(ZBv6qD`iR;`fsTiV>~~n(Pi4ZeB13|3etVORy9} zn2c-v1{D^oRBe9fekl~%5p5IC;47I7%Q^!Ho!z;TdZ61{Ckz&7>=PAb;%+tcZucpn zrBaM2Uz8MR0=4`}su=x=K{mT$l&g{y7WM&~e~-dT<*j?tc}}u>eO|shy}259_pu@w zkIL6wbexH+0+YFfVq{BnqCryWj>r|Stqnv*Mx|E4P>xlA+q7%&_>N=e5^AwBYnYKC zebk-^l9c&539qhf(k|DFG1JXCtWr#8Q;Um3RFSx?1!U`}k~W2+yH4t$aA>mm-BLrG z9;nU?|11uU09OUyI-g?FD+w|o95}FLQb1{TDT|Jo{}B+jN%|(9BQmb(@~Zst#lLov zKfcSYHPh=0-ffdQPuR6H^dB7#6ZpWwepvcuDk@Ao961b8bgw8fq{)n(@XMkj*7&}@ zqAlFIk2Zg-U$0YgH+!}-E6HPodKC;72y~?E~*j*J-`xVtDLsu6!I6ys8E|Un} z?ThJo@v>h7A0OXPI5VvT?|r_v=nfhAG#Id!E~l~jgaR&keJ8xWeE=3oa6LG32PDel z+9yWrg}GV;j+=KDs$TUyl?eOZ&RE)RsXCL%pW-Z6U-&s|1V}>rAGJ{SOC~ExSz4;I z2MOIADL2TD=R4G0G2rLaRqVj2)rJPna^;t~>e6MdP60^56Vj{NlY2{Cjva7nTZY1} z6D5|_R=t7FMK-5$n9k*ro=;vG#Lz6Rb@4G#y~ z;aJTeaHT?1B+jQhf-*c8A~EZ3mZ+|`j_~%`N5}x(M}xFttWe6RBIjVuvU2@%a0O*j zhH=qwz7~6Tw_<^R-0FMmPXBM#bPr#SA0E1JJ(4BGx7ZOZ!!%F6}aBS(8zA(s9S@MA^64u{&@-Ow%1xj}u4+W_CVd!=BIkNE*5rTa zFEK!P;A|Q^Th^p%#&-usW&3q)xZ71{X`&?W+mO)d`?s~spevuZg|@*Y!p>bSAc)r4 z9;Jyyp{*-Zz_K##7vd;!ZRbAE`ej_bdY#qul+E|Nyn`*&Po~OEv(`_Xa)WP+h@cVj4q8nUo{jRWjR?zmt(rj0ri}FKmCe zD>Of!(j&pROdqdqB}INpgGXJ$#&x!J09#{yEKimw z=eZ-U(b_!~_l*owu_my|FOGHP({8e%G-70L#0+}}L|Ws)p^2ctU~1>Gn>Q1+W_wjD zc#+`q1VocWTF8LAckPw83iR9S_DJWcgm5Hd+iKZY)WSZ;G_w?o^po`{gXhUf0V%Vv z3=Mr29dQqXX^ip*sd^IP<LKtrB%Zkz83jOH#=$hH)I z;`+c6W-fTxgqr@$7B`JLbwApV2wV6zMcgs;ElVeJqQo!c7Q^(>=JXTY%?^JrDxzR% ztHFe&Qzv-&8Mn6M*gAX&v?UWawUZ}jv8gf-k7I3;Nnm*Tw1oun6_41v=2-7|W-??J zWZ$er;!?MZF*O?0noqrAc8|J|{X(?oAO|o5N_sZr{~##)%3Ao8rTc66*CgxZLK26A z{oCAbZJZL5O<;wYwX$)F!0aBgw;D`Tt9oICAI1Pq8hMQ z64F~3pe4h4r=$PO*kLEb;5z9h@An&|>$@dldkbj}7(LXpY;{G2)+KVAnxo>}#mD)6 z<~fYg;|je4f-$gdk~Apg!aZ%hzG}(^5h{Za{`8oJKX$pl}1*zp|i{2eE_ebY5 zK1n%MFerL+lVe226Lkvx}Lf&GH9 zg0E!P=HUaulFtUN=Tf+bff{_ZJ+PYNRf7JJkzRL=L1eV9m!cbO5n%C&)ph%23aag+ zChYd84I)dlb#!=nD_%a{*huh~U3FA}_Ps~25BP-WR1{(ED{$@{ifjdFe=Px>KZC%9 zTt9aeXs_S4S@+7r!|Cp)&&$?ELN}(%$2Br!A@(O_1_v!v3ptiH+cY-oqBcOFwXmeM z*Q{EyKh}lZ=|&jruaZM2aSHhB&M@CLXb4X`ulPY;)BrBez6%aG>3)ORW1iYdfaovUtON98j zYpS|9dV8{`I#v9<5o!=6UvTth@KBRdA>6h#U+qofd;RggCR{MQ&yp2#*qAzJ=i|68 zL|RiNZ6yFxu0C1)Xss{8WacbSdcu1r$*t^;lz%5fwBEe?w`?1ih3!jluYhH{V~T=0*fExMEb?UmxSGGj!y-e5i+WhW==XX0@I{G%+QmS5k$`ZlBxPpgcHXT=iJt&;{Te76Ecp zq0L<3sQo>tmf0c})g}*@AVUbbTJ04q@gIE7^-xXW8EeMJCsa-_yusyAm_A;JW4!}Er^MT zPI!7jqyLg51iU6Ap49<~4dC^|Eg|&`vz=9d<;(CMm4w^7dalxra6ifh0__>0(Clk& zpRQDYF8azj^CVw)89D?YLTeoXkn`G$YCJ?t$7iugg_r{@Ldw=+J?{Z)dkvd)Pg+tO zYVdGHPxW1K36BN4=F@;NtslBxwdD7Nkb)NWo~nN49?r7B#JIb|fish6uJ+O*vhz#O z4XWJ@QU9WOCNx@CAW?Mmm~$FlGMl&vDl(t?1hPdCI03Qof-aqJbAe1{J-6D6Q!ChK zPmX;aT=az)B0LlbaR+vCIDc#l|F0tQZ{L_H1*k@VTT@pKV=1Up%3V1?ty5Uyk|y)` zUWZu23pNV$ro85j;j1CvUnS}a7)Ie>wPS|La_f$a4b*U_-S>j(LpAN zfIY=UP$+!txH?isMa7O-VR)LAkE>l}Wo7o>ow`KRPO(nJ8=hoRK!YgEJ6bfHCO`RT zfYFB$#g`Tn@A2^tx*w)sU*NSXAB5MO<9`W?&Aa`-HRku@cvjdjD}~9}F?zT5?ed4G z0Ezan0PNB4wu!UlGk*rI&Zw>60p^RKJf@aS? z1dGAE8wM;3CdZi!1oJTkJo;NFgCaf?$H@ufwGspY-!?~&<3A-vAJSbncZ~*bSm*2M zW86OxqF5!?y55UVfU5N~X75SMA1B|@b7fpsYuSDQ9Et#+$NzOG(Lq5b-4{&E1=0DB zN+X$hb$z6)#W9ERT4Z!|jR7uCU)^O^>yy(niJ25EG*H*zUbanwGC@);jj~S za2Ek7`MwIC&D=3Xg1r^DOR$J*YR!J5${bnEI1o2n8<12;EiY+JjO)FlSW%Ro4v81l z+7d3v4mW2OWOQIz89+Ez7^&OEhc~~1XpO{_z~n!v`#XWoX-Dd1v`hO_rkOt*R9Yv- z$k{l>}rh%!z)Mg2dLYrBYWQf+G4taYaI|F9|Q+~?PQ z@bbM&R}u=C)(>-_85!fW`WHD3AChNN-hGXKMC`u{#(!6(WEhr68A!ul-leUR^-V`h zbZ5iv_gVThJf2vYq`EU^+}Pw~gbDKxYIQJI=kb`Lk5waJCTq(LTxo0#J~Io3QiIFZ z+~>`C5sfS}f||KBSFn8D{K7mGyu2O_mWBG24Ugph;zGT4$aCvj#}OW~+Ihu3P6#H1 zOyHoa!AK7lHLC5pcrgRWlp3mxyy8eGC*Rpf{E26pz*?KH&-Q6g&wMMorgX6SZJPCX z5fRG|XXM^z?(xeHRWf<@!L?jdAo5baRg%xrZ-EJ5jFzq_ShebyE3N zp7y<$BCY+<#D_a6qu7dRet&?qYM86C-yJYD_%4b|8?U6W@Eh*EBauPXS|c9abMvN~f2~x? zVd&<`#hjh55>~*eibBGbrH2=+$Nzj;NjT2o=Gd%I0}yT{f;~#!W&ct>EyK`k^nPn% zSWn#S+ZBpQOPmc4OP+h~0wfCjsu+q@$po{K`yZixJd)vc}b3rsoH ze!(@L;f#zELeAWon(cjudwAbT8!V4ua;gdaeuCrrS#5(8F2m3e>dv@}1TvyGgmeds zfFv~2(-wvoV`)V8Kd->Ssw5s15#f(9* z>WN%le*VNSXO}P6cJGcJD;~5|kwP8zw;u_)y@F5!@=HWF#=*D{lh+MLicM~5wHM1C zRCpCUWuF8Q5Y)1;9xhNWX7!oYGp)^B%>8*t0nwoCH%Fzve@l`L{yumL?$)Ofx{)1g zC;A%zZKAK2UgP=EG|9;6mZo@{qo%|jiaK4ze{SlUAfwRCfcaNhZ|$*q1^ky4iC*H& zw*373&(4c4$r^xAq%rhyc-+GYg0yGuHHY7T3%?76g_^|_)q+szbv-~}_0spHU_}y0 z19+1?u&U;I`(jb^YWpnph4@uc38(_-TIqz({p6>BTiT<$N^|NBtY!R{Gr3;<%Q>BO zQJg)_f6=XQIbTM)OE4!$2Vy-9-}v*Hp~E*3XD2l$6`}IY zkG{X+Bxy=-xs^>6NPhIKX9>AS$)T@fnM>U#7t9n&HzboE-zzyEFb_*tN-Bp!TScrI z!}birH&J=-)Asv)0593NSGTtMo#n!*_saq|A*@HZ^xIk?d%Q229~}B+*U$Z5>XOdV z|NL3*=MA1=#eYuCUy#Ifh);dt^G9fQNz>uEw6})LEIZDv%-bY(RyF`4d={33Tl~Oi z#4wol+Vkf#G)f!aw@s;6nWz_$dmgO~FI?U~zB5|u7-uzoSVS#c$e|m;C5I>-+kiDF zYx3rkN2Wx~PK_Tl?);5szeW(%$+Q8Q7moV1`Jm0&cNQ(y@n5Tr!t1cS8Xq5DUO9!W z$DZO)gUkuN-bMx#%-rglM;zYR36OWzY=>4Vp{<_0Mx=l7m?X!pZX3w;FTLa>`SsL4YP@=LW`-5IGiOizLo6hF z2o!q%q8rS2|K?R_xjaBVt}VRPChQgMcb8ua4+(o^tYc~U*d6=5)s4B8EVRjEB`1&Y z6VENV>~6&!4Gi!11_6EWBYyAgGQrS@w(KmcJgZ96jBBjzOmkClMz8!Xc)4GE-{W1k zQ@F)|{p+Jk+T#6~nJ~;>+pm*_toeDhv&#TK9WuyP0RG|2EO*S^uA`=SYaS@zUBt3?flA)sw#MIVNZWniAcEG;-iBX< z%n&C1rC(WoDqSu6t2&8p)B~#jqJut?9{4wpzOkD<)?iGhxQf&7NFtou_`^{_%PKT- z_NnQ6h`f5@qLb*5%hXW{zGY?2>cji7xf*s4xZezF%_e>L@N{<8)w7D>MHXII1du&{pSnNyY61izNm98Ovq>rdzRPef zmFL$7O;3~T#cz9Chv?*3qm_lNaQzz6^sE1m{y{)uI8&agko2&$4GoXYu{;^k$z8vy zQkgKV?AEn$UhB#E)g%i0 z-hLY0!J}x~DPlcdY-7!O^InTGAZZp(dUdr8GNH>Iw}`LjM5tsb$`qqHRE zH*9Qd4`bTDW|^7W2s@gVi?y^cOdf7?)8Hs9_N&?eZ^OU<_EgMHNl{AlUcxf~iwF}^ z>fSL85zzp+WF^5tvmAy)CrjDKg@ORW7jssOp07k@c1FIUyh?P5iFRI6N?{AWe0BrBE_Hn!2D=`~~NzKl0o>a70#|1~5_j=t=t z8XT@%=D92!WGJ3*1WZFJ3DUpt(%IFk;&y zMNM;L`z%zPZ3sI$TMvXID^KsS3i~JXXuS=~E;kWqshft~V0$aC=U-kaP=%)_=;r&Y z(HOn^s*y^7%z)8=z3R!7#UwMN)bl~yxJ&z4?+bG$?dHC7*iV_11Bl}~h&)Y?A zt_wqbSi(a(jTJ8n{%^Vqd`)tg;2I5oqhpAS$}Kz53fr(jL{c~@?B03G=-g-V;_XC< zD9M?*IOp}r+9qcB_-^NCW;Xbjm8^YWxUoM-~Xv+$Tp@dDnuejO{c z2JcsBPoH~PA!fe6aj!8fPya#CpK_lUf9F@r2bIyA26CqTK*<;w z6DhqB@zHN7v(>Jm&pHoag!CFiXuvFfA^X<^ZN||l3ZmW?usgEn$L+8#DGGGC_WuJ1-ZKS zFtlQY0tH~K$}!hg8fpck*l!M8h$evyp7=SBmJreR-HX!yM>zc-KlwV5UcK^;+ijT> zZf!yu zfS*kSFZY75mc$GjYuhI%DOa20Vb*IJ+Dp0UZ_|~bK-O)14*y2Bia zXWS{4yBEuSEQFd;sPpK$NT^Pb0EDUtA~y0OHS&jx`%j-Fui#(XBO#jD{p6|%$&yEr zrUvsPQktZnjlGHDyv^#W8kaY0 z&ZCRV3$-&-aiTiX7YhV{VG01Ii)XlZ(V+gPhrjqWK*WLMW}@LR0CWKk?{J;M)_*Be z`Ug5+n+3>E)oVez*+t38&wGSyi`RBCcYwU&A(`ktbz8+xLx237704jCF#dkHV0&VzCVOqzxMftX#w5hr~>^?Z}X z3^nJtcK@af6be^b*fpA8;|S2`ZO6GqfKQMX%Dp(pl3M1>TgofYz6TfW+u?pFG3~Wy zEab54gB)Q@k4!R{84V+A)4AEmAPv6A|bgs-F*@#4jM`;XS|=tjk>RpVkyf6(O?LmA>u zAbR4w!@WK7tafg@od#9&B8g5=gJjUJk2rO*3 zMpt5_W_faXUfxN39_MO&c`Qvjx*XkNfMLa{e86BJQ=OAHRnOHhF`C~xN5?ki+V{X)oW&>*XkAgMljyjhI34xU z(i*{E5HBs{+Ji?4GWiDm!_rgiGN;@ zT^;XV)6wC`pEMwQ$Y*Gcb9MbC{kef|_4*RyB`ln)*MYbB<$rwGk8D`L&U!z|v`#Wy zxBIgF&Q>L6xDct2`Qkih*#5+p*JiqmZK5=l54GQF)m6*55f`@`gQ88+nTcUIh9v3H zN%M!~j0qRVuMO8xtFsz>u8v|o{ODT8WR%rgdBtDS#bZhl!9l=d0w+#`1#cXrmz?+5 z)OeZOB<9(z>um|K3)sjsj1*OGxM}Sk2S|ugPl;_bKiAOE;4bKIHBIyoodCl+0;{aY z%b=-8G~cBbc`RWb(q9A_#`4^QhMQBXbOO!o zkH`4n>{^u$#_+SpPssR9rT0!;2N4WGbLkZK?~I-wn48GE-82FtwS2Io5>=n_yyCE%N>9J}ZX+~bo}K-iz#;l~Xpo!3h7jCIU<-xMz=!DE1PG8s1qw;4?-Qa|}kz*D7IjkDSKBGfR+cRRV-7(4FZU z@=L|e z^V!JVp?=Zx+5YYtLXMpdMo*P7o8GVVtNssrUl|b9*8QzqP*PA*QWQaHF5L(hDG3Yd z7U}L`z)=Bdq@@uAmF^C~VFV4(BbVNKTMUM#+o> zHNxk`Se;iC$SGvCh_zWb$1YW{x1@BoTys!e@wlxxV z=(Zc*R=uDb^>p7mEw8DD=e?^lth$)di8U9<>z26Cf&rr1SW0!o5)w4+$#oyN$1Gg! zb9NOT&1wH0SNHY!Us9L0n^Ft2wg)mKt_E!thUVz(K`klP8zOS1O!toYx<&bBy|6G^ z_qUOLKB{;|*m~iCGCnPZg)8-CM&H$)u~~SCo`ZE-mT@>ybUCT@j!@4INMFg3RuCQU zMX1Q;UUquNa`I9O z9ks96yel!>8`=;#z;(5g%*_F%{_MRbY#bc$7UHdX@-P?|O#@_W%BWYy6LV(kAOF7v zwwf3DT@>psn+eQJ8EUr-Y(l-%>8zTAtBVJE#u2GX3(LX+x%K(dX8OYGotj|rjq&Kc z+foJGx&@~5-N4759>lLa4M}?*)s#B-=$)#I6WCwaKF89?adR&1d3xW+HB>-rjO8}B zVl>${GkCEl#`qW!q}0aqEY#Zfw)IUN$WR59!_;;EMiUJO9VS@M6A-*lJT}9eblE_y zmu{r&RU10gmYg)u=B~i_66jv+&hm;M^&ne3sX(l-`L0yXBy;J!Z&l4oxk8w*zi2MP zMxV#Hv8v+fK%YR;cKUe?0ly&#CeL=sKZa1BXesV2zO?KR|-?Ai_-faZZkD1 zTt50oIrrX8=bNXIIm0_tlw6R0hr|FSXOeM4GJ;4&XSwG^d5o&S*`>-v=4}(j)he_! zCT~}@l;xZUr2&sJ^kAnI5ngEV$~{e-zA1Yaj0Djx9|-RV+?;2ts|`zIb8@+uo|AM3 z)+80^d{?w*?Y(jivp`AShm~X_zPK^K+ zW}=4rx^&@iL@4XuMCU0<_R7!|M@txY*dYwN!4$r(vcC7}noXn(MdPfJT< zyg#?Do*Zi_r%V@VD;rL@M-HmGd^eW9z_8Ulb0x{Rn#ILVjIl!?F+t0E&TMpqug1nF zHu~>^%S3pleVOG6naiFqDva@{t}aIn<Fl5F=Z9c&UejC?*2t3z@X>oWOY}#K%!!DV`)~iKc-iZ z;Z~y;0hOn?*P>HX7B&BlWkG+9{dS-Id=w#@J~W!8Jd;nrX3xB8MhRWLF@#V~f3M%h z{;oB^q9YKX(#I;7$cHPzS8|$SV3dwSrpw(;a*ACl#PVMA8TI$fU2Dn0ke+5sMXRF} z4XcG?^U8MY;BM;EvuB%tI}RoQ4b{jW8=+F{j>D6`vu*Znvxt5YtXiyehv&JF>2`Nf z16Hy}^KL#|r9T|Fgs~cO(|>z?gThEVt)}bt0MEebWvY7n#t8nssE&(a10^XW>wq0C z7jbn_y5q|E_n9NaGJvvbJ;u7`Lx&whDKD};jdLgD_E@;3i6>OPyH=+=L8Wtzy%Fam z)rucwGHB6~&OOL9H zPg9j{oZ7tI^l0w7Z%X;@2b9sjlF55gY!(IKI~35cNG`KW4DxX6y-3ae_sehj+S=Pa z`fGd_FD*29adu|7a^{*2YAB{tTv2|%YDguNkaa8TP zgqn0sP{1cb5V#a&yUuR}S{}1@QjVXYbo6Zk2)K$GQKKBQTq2KMu-7TOY#-37F+M9b zX>tk{V`1dbltt%udTdaMj^w}HK$bfni>nZ z53ly7KMPK8fq4p#rs7g~ba8|rpbyC^F)na4?P~%Jwz6mG1clb$1!)@zpyhCPYVEzN z6Qc5S?gUkrRg|W2V^e@S2GRV4K%hJ~K73@{e~dW1U0^RtseQwDW4wIsX%aP9D6v+3 znTiJYjq4ME(8S?94gzk1Kv7jTDSNDV)^xlUDZ?{K&_r0xvy#8+y&C%DxZYj8WJor27>$zS29j9ILX-;*yx9xdlY`)^3G0I+AcXOznyQ_k>aM zCSP_6b^3eTr!19h3I%FHFlTL56)THWwcBJZgLN6Ch&uD=F{W`W)7Bs9IK-Ew7t&6= zz=9ToG2;u(qwKa%nrL5Do88D>p{)%MU!8f{fF-Zok-g!Uy@h@rI52^NNe;S}eBnQK z1fN|V>Xy9LA{dU#KIw8^&Ay#*IPXp;y!IbbGKohwg&gd{xBL=baSZ_G%Y=m6MYPaR z+_+o6%M8qy|Sb37ugD|U^o1rSDJ%lh3Jhttq z{jO^RF@L)_TeOzFX~zJ#tByf5ODXk6$2LdnNbSp3zdeyl1eg@B@!?$z1+CUi0#C?p z%2gs)axS}77o?&TarQ%!Id4tKn~k}&e`FYuoME4Uq_Q$o+U_)1O_1&tN~fyJuwWQ2 zYpQ0H&cVCr%2^g81VQhw8-lmGm8J?=i5h$vlJEHJOaa?2e~ogRW$^eLdt!F$g=g|_ zo|Ub5leMnRaG355IvNWsmabmq^cmPGRVsD3{?66GeiUYVZ7!XuBsP)oR;XRjRHkUF za1G;KV(yyg(pPC|8}{{K8Y?MgH_9DBJ-LQBLCKW1SJfqVCHhLH&ADt=&lA~pcoeTm zOh#D`gVIYDR?0Ue?76KiBP$h%9dT6Fnf*KKy}wSKVizht=R={|u?St9>PVQZJt^lIOBjm{i*cBVg!(E! zb9b-Fv=?}v)%Kw$WwvVOc2g+kwi6-wD<8DwBM zJg;yWX;I=rQ9Rl?!Iz09DI)l=BW$i&9&A^RLoo%N*#(bD$h$TGN40;^3SbS)8R^Mo8#f;GM!Q2Wf#TOXJ_4LHqGz)hPV$tH|(_pHfAx(a+bQGt1+O3 zZkP5zC_^Z7(MGNgDG@ZfX~!xwx{FuGWXCm_lkN&C`kGQDhM!ywZBu3OA_dqee| z(jWNd!!2fBBgXc`18(rQEc1G%< zh!+Wi1vj;Cal+nO`3{10V1~}dF|J&FwurjSneOCQeE-<#^uP8fy`bl2p{%Vd;G6Jj z^Wv;9B=s#Dzw%l@wI4Qvc`;Y2tEqYhT~Kt7!!|;g1i4qpunv(9AUwtGy6s=ZqBwv@ z37%SiE(&T3CA>}vL^0Yo$dgNEmoE5wqWLu0boN>9aj*BCZtN*2(z{mV`*4%p*k5q{ z6xB{warpA3-7fV*t8)~-+Gxu2A zWtxv{a?o|@*X#W-k5E;~@29F7Y%tN-$Vm)PquQ(GaOr%~UIE%IbqosP*v?FSXV|l4 zWx4t+go%l)&TCM-Ir}0p3vm16u55h#QtO6!^#!}rD-&r`Le>*eXqrlP;}-|Pt4YMD z=(lc*Ky+{OUcB&e!Jea;RgA0>hjf(KWvXaO@xhIP#CR$AtQQ;^< zh90i(aS5!B>1vPGd=RV&FNA4iWiCJR!zarbsVoOfUFzAKNsoK6F6RYSniqlOm*8nG z)0ySPH21kTg%?IK!aOJ(Z4A7iaq004nVO7h(d>KvRXgkb_>?X~_);dk|-ol;g zPR-Vp8!jg6r|VUS2HAAsRk$@37p!y{7kU#AZ)ft_o?j@ZaP{C@uh-x1`H-=U+K?KT zTgsw!F{!J^X?m4i-+3^?f=Jvw6y6Wh zf-cF5YM9}3LdsiWjxnVHMTT8wR*_kXPRiPFk?mUIkxm?&&A2K+ZNC1NsA zdfCL&KmCsG=wswY{H>sW|6`$`dIcgI`j0thefj!bd~&#~?JSo3?GF2Qor{_tvP z>EXH?ai_rG@@gz$o!#!!TCaE6D8QCLF9Qt6Iw{M`;X^5yUt$BcbM4JfqoAWtciAmk zs+yS;g|AlF)=xxF#K_V{y54M^Vzp{};TO9zmsVwb&?W!E5P(sQlT?HnVMJ>mr#yWJTd=sg=zoi4K)FFXT{ z+(}pul@20m%_z_)DumHp3at|aHEwsuE3A*+U{hA@8E`Q(u7BP+4z)Fn<>Zce5l)kA zTUODE;3y3=Gk3kGm|M`gqfyH1I%@<4TZ9dxt811j+h7gyqCN$;*T0N z)OCR?`cU9Il~$Q=11hwheK?%H;h=}e(U!GT?}{>;H&xle_!2as-%^jeeD*XgVU0&M zDI6E(&vQPCb>wYx_(nx%$|)x5uVSyZf3!wof<)oS(6{WVFvC)v7kPnM|C#Knn$)2r z7I_0_>+^)AT^G`+L8X&m{)`I@TRqu#Wf)hCFfUx?x*c$R$^x&;co8fH@3JKHhWee) z)yXOwoxF(HqBsagh3wQ+{GDH?P{^IBtKYZN!lvE6n`$Yp<`gSt7u?o$U>Oxdkj*CwteF2 zxW3cAbn(tLV?pwB1sQv7j0=GrVkUEcm-n^egU#U-8DtLEQlPs$Y1DEw6jZgLXA{ou zN^JFCEBfao&PA+*?%WmYPLw7n)BSFg`k4lp+;KrDoqhZfrg3bQ%w9jncDUhM0#) z44O>lo)}P4zQJvhZm6EUv7&LUqBlLVqhh1|`Y2q0JBG^TR>74r1D>2BN6~jXo}~na zSq|5k)>(;OYH)%Up2fP;L=E(-1qP5iT>0Awew$jYTipi%L?74U^xUkQZp?@uVg#a& zRoYz6wOz|Fh_P2Z%m?)?p|$$BTmNXK@zC{LUtB=QMS=?&%14V2f%@N3$`lpEAzPEN zoB^EY2EX|Hg43W1_Hb4yqv4Gg8yW9t42QS-N}+31t2$0Sv1$?Aff6!JPnKt}kNVQfbVr;p}JFh+adOm*MhiB{FsG<8_ zxceH6I#e6}4b%kJ#STibEGzmeRZX#m8^nTMWttT#Hn|n$7(rm`QGH_4aY!^D4wz+ z$7?T1fa1$7W(7@0;=|0HQ+O(3_o^YRbGP!PZ%CFWGC=ys9SJH*x1nFRegC4|woX@y z+4>b$-J3LD{`wt0Ss$V54ENrq7C3t+SkQ%(ieH^-cj>`g+Z%^U9p4O^S|$rd+W7cD zWex&N_LY$!j-7(i@PYin_bu6uK%C{R8^Z$Ux-+@kJ@BTg)*gmWE~8;;c%}fjN3c+w zLRf6Ign~(GrlymVlB#zQp3`AUg@cMdhRO4qEll#TGb}r8Ak$l|nNgx-scVk+;o=v9 zNt#iVpXkD)xe*Jq=fC{tceoJI8ba@L7`;e_(BU#{Dd#nhQWCgx9Y{vIEy~bJeZqtnRT{6Gp@>}_sR;PW{@f%=I@mAQtkr21Se8$)xBSmoCx16|W65^IRAk`n|b>Rr)G<=VC-&rxcy zEUC|P-dy=$DTHg^IS_=9B9&{L4Wq540z}+hUR*eyRVv!ZK|@!=)~$)+&cK%ceM19SR8AD z<8H7!4LQDXuiRrddpcUFa3X~7yRDj5q7)~>7QMyN#O2)ApjUhba@jO`$(Lz8CLe|L zN6`+KIhwiYfMSVw29Q|pC>3vliO==+i;k50N3)IUf|xaT6FB>!h4S}B?gIG@hSf8& zUtj>xkA`9oLy`C#>;a5HhG{JBY@bT4wB(sD%sX%!2iW<{J@!S&Y9peH()I#b(wkOH z+?O(5H%AP?i;u;@63~(I3nOJo6vhnWoJPU(tQgAF8nNnzYywZlxP=WmaO~Rg`h8OBRfS(lq->IF3Sy zDUiL>jR``h+g}ORLNw=-lzoVM@Si|KOjFCrdNsUXthHnm!_(uUn~hN*Q4&zMj!3F) zo3kk|K*fwcb)8Xt39cABQ%X;x9ou&ey4Kzo{_2F@=xx~W11x3|;$+3EMS9sF+pDR^ z&LZtmS>K6Yns%{4$%>D-b@08As)tp{$~ese@83+g}Kk~ zh{+GlO-u+zVgh?%21Ejl)QP+O*BEJ{f(zK8qW$pFaHD+Umdq^a55DotQll7;+b@Tv zPLS3&gb+m!DW!+-pp2tq*Wu1s5z)HuugkAIXlz~qB0 zWCVJaBn&0<2?H%G^J;t?3noMM+Mmzsrf_QTI`)WZUsLCW+CRZC*byYd`)|wX{}8l=vPy&GaC2q^75>5&EDe90B1-EJ&p8D=jZ{ z9B*J^%Furc6r6ZiXxwHTlcyOR>o3vT=CO>-kh?`@hwnpJ9n40EK;$*}xCq8|oXPnH6AZ9GHmvCT87@qwfwQk!yh< z0cBzS-DT^LyE0@9-YgW+?leTlLd#ncozOgR5NJNb8c^{#*S_3mEv0ltzWscwK%VgcJjhWDRf$CwGWcj=snw zDkvTlyNv;YYDiPd%7H56sIOv0EiXW&xVv3292$59824Nz7Q_K_$X-;^Zcx0n*Zlb~ zu_-j4MXlco_^{eQS3SQWA2rQ$8S)^S%fbB8@k-7-1kxU`AUglc+?pXH12>-w3{QlH zcIr2&cTqMJ3&Ys*eFs5{JEP(9ipFY=zz!@NN%ur~IN>3`@DtO2@F(|2mB~$Y%2!Dh zX%Wu5#=O^&20JqdXv5JBbUzz!qpmFxn@dvO;xrC*ExGxCzFbQ5Z)z5G0p4sIs7^8} zSqqcBpIl^%5<&OQOp8SuGSpZonv9u`F76l`zuwT`OYL{Z&-N+4(rN-vzU7uLST*)zchWrF(|81*Stlaq+;q; z)4!{VUc0Ttv*lDL8n4?*dyVg0G^hD{tmVW;x=|saB5l*HX)^OtJ>9&T5RXa=;=9LYU}RYzh>s>{|sHGhxtRizitys}+}`mb!YwG;0$ zvLtcOOHsd++I2XuqZ-vY?`ZA)++A~yCySjWGi)B&&e9(!`WVg6yg~fV!X$^ ztWfENNv_${z3aMS1*9RWrwT04csraQGWF2T6db`EN&U!V5`Xxf+BADLS z)Ks}IB57LJacAUmpexvA73TlWd)5%)AzZ3uGlzIyx@(zkXu!Z+>)k5%z1_nvAiUWh z1xll?-ne9ViCk4qOS3+Zq`O zn|oE%0z#Jm7G&M7*N?so0x9+sRBh<^>|J#M4z{YtFTmPWG{cqyv+x5KX{oyt>UY#j zWz$Vf%jUY(ISR$^i?ATEfFghAx1@NPWAI}DU(}vJntSP!w+Hxg9Ctl* zUXKhawy@)97f6rTzsb*6D;0#trc?6U>T-9$?kj6W?syIm<4@WjNNg z6|$^XhhOKpX!>py+92#RTQB&8J$>=9r(2Q?;FDDD-8Hut1Qh7cB8X~Rqa6$Xwmet9IJ&{sX ztMui6-fVSS6UCe!bv=V=E9^JhTM=dX>TJ$jolH4Jr~PeZ-k`BWpn77jpKK6HSm7Nv z7hJDwO}ry^1N|AY1ff0S|sbK@P2!NGwKfo(C0Yhnx4v z{R`G7nI19dUg^c{;WFP*QDh7!8@#MF(T67S1|LTlUtU5j_z`Q?&70yZK^$|05s|Z^ zGBO}>ia~TUAf*agUVF=~dwj$vdyB3%bxVQU7-$nrX${aIo7`1m!)byA(P;Y3cC$;h zfJGyKcEPpMjdIv*4_g?A=iU2dgOp zp1!!;R=Yj>u~FuttSb@1^)g?uriIEi;r1S<&6}GS9s?C@I%H49vn1el@!wcukqQj8 z*6fXG>=9d(dcMjh;vnxHNG^?)BXD;R!$Ok1mT{ zpC!Cify&HV+|QSPAM#68ACGR=2M1!&QYj z#EnZt!lyCac68HSFSi#kOUXT+7i6-)W>8G+avE@)RBvWAS|`jZTnxUUdpm<^=RHG8 z>~7neGF%_Ui|HKQD$nv#ljdk@kfuRatp9Enp5-m&XdQ*VlD|eaiYK^)6UOC;XIem>9#$#>cdA)8 zB;^CAbA6%q1!GWnzF_h0WD5gPlV*hrcwyQ{N)n_#6y9Jz9TKKN$Rv#C|3SjJ*##25|-!aP#W?%d47j6rUVRfx@NbcrMC5uaMB0u zQP$viypjMz47TGgC$OdqQMzv)=O;BTmN=FbMIr>t!3~_9CPrl=ff*+BW-&LewVJ>A zN7X4R^%jhylw^IOqM~$b+OWIn`b!05RYmh$!Gq#0u{z0L3C0gZ5m$`Dpr{5{c~__f zoaCrTVrD9FMN4dN4*u;+F!LNj-=79 znd=5F+qan>_qQW%4MNn1F_*Kz>nU9YfztpNFsXTkX|$yU9$Ln3l-k3r(LqU4O2q0h zGGf{EF+Lk60$Iy7?NBC$1p~wBe32|xz6KVHYc0f1e=k~Xr5YGIveoQt0&TGs?-e9> z3j-4G)qE&rSuj=S-6`?(zuHjw@7FRS^&^uyp&9!A){R9L>!%ZD_fqVUI87hyl%wBeRt$G~%tIkz}Eo+f$JmfQnH z_^du8iYSIJ_!-S#OR+i9e@u{|+Fcw%g{i+RJYuuqw^lQb3QfO3edA%kBjHEzB}r8_ zffXI?)dc(8T$6>>%Y-euVo{PWjm3&L2X4&w0PEHqeIEt3px5_%5I)yzj#B$hNs&jW z&8-=a3Xv!~4Sd%;#&?|(NL)y=(cDqb8yg`r<^@iz5-)^38tc2En`r2;LI)&?gT3Bg z1!_PGz@Ett*#Cda$IYwzm5M5k-uKWcBj0tt=KfSz4^nS0y*m_os&( z-DV@bEQI=808RJH$0zM80@nnA-tS)f%@qbkTdLO8Q2fZqNKtBME*7!W;%!6VJGa2p zHgo_L{AU3O0lyZ(K^>uz-86cc%dm(=4GJz60xPP#QU+3ZhpokbI&(46b701pA)d67 z=X!K}9ljns0*6(@l(ZY4n9^JjG8y9#iYbBq%n0|1V7b=R}{V z5~)BZ@E+bOgz{48{c3!c*ZmK7bzYEwrsIw>CQs>Hw2i{c#L=azQen7_x(y~t}k z@F0UOekqoXxnSZF#&l`Oi z8q|tMS%;IZEkf>kJ1ab<+@$t-Si1$uOQ77L42aCcT;1{Vh|gS=>5t}K$Cb<^LurEtA>htBBgSNiK$m5qjZd_ zfT()dP(_Vg-lk8*J1>zs)vIVPi=@RqPa|t%ag<~^6xs0EPW&RnMf&XT%?2D)3aLbH zU%ZQmqpcg=TswG*&CCJ`h%QR;#B+}V6-(Kyy=)zH?+g{I<%VTj=V_oNM8p)0fTyG5 z&d>WjejMj;o-SM{uJ5dQvll4X$iJuyYV$R6HmAmE6{QB7pQ)l=?t?B<(~PzREQHcX zrCyNML`J1T8D8_t!8J5O$tl1lqS$n{I5G@~kIHHBLx9hRq4c9)vY0zjWBO}uU>^z( z#fKt{UDj?y&H9=P3~htx#%mUG79O7SM9dMTU@7RT&2yg{#Pfs1P+#{G84i9thL0^V@R^@W}U$~?F1vX#E{-HI9y8*< zc@V(1Y*KKa)FEQPC~d4aV;n(jdD z^OU=G>+)`M#1)4cB;5l=;1t}5^hogJkC{G%x9_B>*MO@Uae}Jt>0H>zZ&aA$vQGh# z^Jk`jL;re%ra6m+-S+AStyTLR|LZ|y9OXlZ{riVsevceT5dW=Q-sU8RD`}`{F{fIQ z0B2!Fqu#exzq?N7v6Yxkbx+6Oi&+0P$^PJ#LxAG(6s|YT9oTYg zKFAz#Wu!zbX(N^GjeoarQ`pS1X@40S63@~c?1LVJIa;lagYX!YAU`cfVyS0vZF_K5 z%ylyDKq>a;{N~|B-u=PK7kQc6hh#i=BaY1w*)CT+npJ;{`+&9sWd9@}AZbByqb}p~ zJ}?}=5ksFy1XS{P5fkliCLccnz&&4Nr&%MW{i8BL;^{0_!UA70&pcyMmOD(Kr8yC1Eo zDL>aXdMdjB>^mSa+TAPkV8KIs&Qv&V{e1vD-#-qaO#FV88Sd3(wHyz|zhh2i_3%4QkuYr2G8+X{e%I0xDl%H)K zfcxfLKN=Av11NG9|$}b}NFxV@XkSbfcJ~;CSg8-XD zz&#S%J^V>m=YK5;WS9xKO~7HXqWvfBi~l)@%V~w7E|$3y`|Z((dHu#z{OC{BszB4q z(tvBq5nSyL3EDXi#59^+IMVy3`_~`b3jm`rT(+n9drc9a5VIu??7b>7$&exD?_v$x z9I`cB=6oQKDO`vQ*8ushO9NFMrPE5y%i;U1d2(94c;^(d0MU8X+8kxVAHL(n0IXrbgRMURBr;m2 zAzW{Ue3JfDRwY6h&SL1#k6Z1ROlhycGymvvnZ~9u)OQ#w!xG!q}d+-M!HT?*|s<*n$VTd*gE>`7@wj{p-2PV32*|48@OS z2VAkW(d_B5slI%?_$8g@#!g0(q0{?g(Hi2d(t%^oR9W`sI_YO}&Qow=RXx7rt z?Y-_XBHO}{s?JXJo(!n3n0)(hG?t1iqRoI}MNfQ!V;l>b*^a&~E~IqRvlU^r1s3X% z&)@!gTOXkd7BcHRp3{oyQRnHV-pSQB*xMVAz}GULpKm54DP~w5Oe>=(Mxox`G^4_~ z$4S8*9A<#b`R%W#w(W_)FLwd#|L4CRu*Htk@tmrbEztW&5UUz$PpeL$O|`s7y;a(N zd$krimfoOo&SC-c5w!^WA`st;8FHt&eOmf1si6T=e|Ju39)JJpL3ePa7Qx%{PW?gi zLowtb7XC#o9*HzE(a>V1^7*VzW>6)CDpl8X8TSF}CvxsD7CdxZe|NNu6hs}^-SmZc zfj*4@Y~36z5UpZ56hv3{jn+AQhqvph!srK+{ESL$HYD>VVoV;Nn0JsF+JuJ&sEqz=STc7)4^_C`Ne!XFX&q6jcs5%^^L zV*j0oDSE(cr956g+EVTF`_+R;12RDp)phKT2Kjo$4n+IaR6ml%=XEebo?DxEbK=Xg z{K{|lXDkLXXLIx;`@6~im~;PkhaVKI0sL{&asAG3_k*`)# zvyLC8ZhwX&K0wlu8G0|E$oB{Qw5b8yGA0K~hA(on%0l%tvWfPXnxOq{_Dny?=F#;7%*;gmqf~UXabx9u!D0>UKezUZAw_ixaH@h$}$wEpuaD8qY! ztZ77KO8-eL{fFhDPtaAsN16cbkLlW@vHve2ep8bCbvS*TH;xvG_bU3!-&d*c%=#UW zC`bd^dzs>^km)%D~XU9L;Cflf}1SlYEI9b6D!kmFKx`&;-;=gS_kgpoK~Ozkz3$Ndy8cT(KS^GZuM}DE^2mqtnep zN?0jzzr7>?*CJ2upMagG===_Zk0wo|a;uVrSX3miugMSV=omQttM`U!RGgYzzxnLq_pHD@uSdtHg;Xb?u{xUH4_(zEu<^03}H2U=->`GM^V zbq58tdt2?@1?TA|PAwnY=3e;>Gpd=7%Y<*{fyMAjj+l?-^-dkDBTm=G1aM=fz5H>L zUrCKJX|}z_>ERr;4i}5XnT&QCDQ(S>Q?q@a>Ms;b@OJmYvbUdusYB~Z=OaW6h;?qj z)zx9BQf&CiQ&`o2o~vYw<{`MApvDu!2CRg|$Q#RHpNn)Jfa;uWRB=)Xf1GyZn)#_C zlE_#L09$+z1qg5Nv(^4YFU zT?gV>e+Bm+wlf@s!ge6APHnvLsn&YnJC0;JzpxW00YvzMqGu00#V`H*)62K$0G#s4 zJUK86-(SQplYVas2qhW^4;=mOL9fXdJMgn#ee;e&G=Rqcf#k5n{s)r7(m<-!|4$$Z zYIi&HtsyapsJzy4YF%($tE+5pcc~KMQFVdc->s~6Hz2X16dg|~Tm+jf^>KZb6h6$Y zH7ts}QP~doHo~-__W3RTS|Zw6v!mJWtH7Rm^u=7T?bn-X$Z<>>sWS^*_VQiuwPgBrklI0P9?2cmLR=Di&RL7IN zcNdPD9C5z_u*o|2fb(mc1ff2?0ek50jw|G-zOnpnR4n^^{W;bc6y=A@Aga9ti+Q$1 z>=au6MXce{&RtQ>^`dW=>9BV?46%pESp1UV>bKJozm4g8QDpg+yLP2U`kXOGqtVJu zhQZFxAo3>H9T`}iO!iT1J}SCTjEM1Cs0s9&w?*1fqymw$YX~AsaVPYzL_y1@g{5-k zkp6Da_l1EiJY;k;+0i{JQ+!;TX|3%FF17PGp~wbn0d zyhfb~+S>v5jT51uo?wboJjrAx^fh)5k8*f|86qI1Sdh8#HJMs((I|PAngcSLSJ3g^ zqL!y9?wtD~?w>o+W>&yoWAG;&Y@3=>~Q~&U)~t z-LZ|Xw|N%Z@T()b?}2|SdB`CTY&BNlx2_^XR@OHtnibRB>6cXoI(LU!sZL#gsQcuJ z@zJ8$uL4aGc4UrSm+J08Z{&V}sufzfx0O;6`JpHlcd}yl`A**iOlk|k}?z_^BW8vK=^NwA_46=4qR&yR5I5IDpA6xa(!7G-3AV+jO5 z79(R|q>}e5M=+w&6^U^N-yMl%^tST=&qRvfoSmthLXXOsd-q;BHRKG5?(sannn)Jg zuch(>{lg&yY81`BAw*wGW9s%$>hokbd=(g+ua5@>mFUX3l=rEMhtAr4rMBM+wV4LF zG4GKr`uZ)15Gt(>1F||Lv|s}?faTSHkwh{@{b}J$$5k>gEtKCwPa&Q90JW z1o#LDA6j$gN=Q6vidusbPNZ>574mBgDhdV ziIL9AH)Er2;lGU`TF=rQq&S?h-9;(#J_cz)q09BdiS#Qzr&hQu*uA zPXwzpz>9=oLFzXbnqW(wGfpj@dfe&G&EzUuxL9(;$cSC`7Ii=*{hXhq zY5MxgLe73!{~zLWAE^I&AW;DfomdGc(VvJl0$AUyA`k=n*RcE7WIsbMYc8Nd9(Klz zetr9UaH8l(g3JF$@V?uZ8Dd!njr27pH1la|H$ z2V!kj1J<{pU-jvqAU+-d8c(jO?w>RQ#1)|gHOR_-UKtS<6mx+OjT-J}5A_sRKE_bP zA9--cxq)s0Q?q1Q5DPbVCP8tj{H9y z$Yuq6``dEAj=g=NfrbTdkHur~3;-eR!mFr10bu~vwNsm0p9R7pJ=EtL zTb^_&9v93NWk2I7enGTw=Gn+>`qxF#BNBZTS!=vB7s!eHwTLdNjt>>H^u$611~o!` zt$5~H;#BtUY!#0|p5PhyzM*G2T1Dpv#2N$x(z|M!toJ95A%6hMxa)v+kmW@O{@$&dpR9iZH4qDMz-#G`9)$dBfkYCJ zxd0>ni-(NIk;HI#q^n{8_zdQPZvPf^KY;>r6K3KzrXsmvZna zpC9Y=k6frPP)6^1F&~V5n8ja2E(s0*w1g~q#Xl+ikzNF1f$P1O&;9}8SxJ%Mv~Fzb zpC~YgG+>4k@^AkA2Z-0=K&6$?efmWpZ5Nk_U0tuJ$3qX@bD(t{Z9!#euCsLSj+n#V zdLY`#Gy7)z-w#?tDCT^VgqA}=5eJv5h)5lmw_wE32kO1QPi&?IuzFbB1fP$*{|G2Q zpWqU*fJ_rU{PVYdK6Ygl;JcXn6j1+w8IiceO1;`;_wTh|v!b9(mcfayYK4Rz?S9-! z9xT8LPxglaUq<*%& zq{uu9HQ8mHZ&~^Uf_}7Z{Ut}k@eiP9Vq7;!zY*;xI^Oaa{Mfy(5S_c)Oui;kS2eOQ9y)%IYjScKiG49Mo4HSbG z9Akge>-iuf0Zq1-*m@peASML=SIR0fxc)bS8kX%^muaxR^JEd&i|2nGG@w^Bb zo--t7K$@`+p=*n31%bY5H*pz!xTBIYRGw{wj03V;vY{?CwH9804 z^-9o^=5Qu3b@=!Mk1U+|j&%A|@Y@}nUdMCjOw~*`g#5`EMEtfBKy#%v@ON~GK9wnm z`0Kq?4Yy%WI|FIFPQtJEfL%v!pY#&vxTBUnwbE(MF?uWh47Au(HC;7F>&kqYlEG&f`xw{`&;*B#b2OgzA4s%BbUgHa> zHf*%Hi=SOLnpT&ssr5-No#W5eGW=pWyVqq()v+<6-St9z9ry>>mIvg|3qiyx8mTVw*P{UDd#fAY5P2`P z6BhUkV81J7*8^wwIOvf$;TgJZ3P35|e}O-A_4@itgY-?KSvy)a_V!cqa`(;gvnwK& zJr#ASJ%r(#Jritqfi$t8PIXl?jplW@QtI~GqNn06P_Hgi(S*q6i*!U^neRxIz3)UK zq#xoHJ<`OzMN;rc^X$@gmLOh{IAvaB?WzbrwZnxwapIJ~zUKSsUjX~M_to^A+>ZFn zk-vRo*lFsHV!8^|j`z`Q1_|u*nwQA|d**QpP_-Wj@xsgp3Qf0ad@4w|ql*YTqFz?2 z69EabqR0u}W`9uL{7W5qU|jWTPGqBLW|Lfc=$EUZfWv@H?{qWV#f;v4MjQyk^gI9^ zznTK1yy;RkXJuAs?!9X7Qq%xd%APW||ym(lQ#*Sc? zoF+|N)lOsamCL)%tD1GE-rkIl(`+_U&A|!VN5VC&Oj8Fg6o~3aXsyVL9E5?bGiGkx z=fc|txZrFPCLG||HmmfhjU7c&c^@B{u8}7gzRgVx;B{ED8RoR_5HLaj;FVL|8L0Sz z_7Qa5wkS%YtQO&}Q8tZh0@?sPiAX$~FXGNjw0pZzmUaXTK&czz$9N}DJVLWsEq(_b zo|KnyP<}vxcz*d?gE!OG#kMD(GoF3K;lBuJJF&$6ai4s3-GGvAbk!% zfQBwpkGMU_I)s-4sJj-#GA`dYk1$QQ%vAlLlvCockc>;%wqL z4DiOxl@E@l9a7{EW()L5GGT%}K)a1A)VNxuSM?M@e?W%_%KJgb-**n(_P=oFsdy$} zkJ5KZzYEU^QF5MB#`(KX>ov`0;&R@BXJLegn+a+Nkh&s!JSv@82oK;D+y+kY66c&W zE*BSD?`{=o4hvxfSbb-gM{`_r$Y|&ft@DY3*_FQm3q|5znkQzz3aHyUwGj)ek}*9t z5!>18GB44FBqrblpc>8h{PL$Jknhz@86vXH=atnWUGbXhF}Zr+0hX03m`woNTn<`3FbW|(|%swZlJK6z4| z7@9>slXnDsme29gsY_f8(Cq8#j_612T*skVWINajAc(y{L~l5*<2AcujTZ*Zt~T&rhmf zIJ91HYdQHly!!%C>VHBDEyqJy+qW`r$MrbNMmSuM+0-QbywPs>38FkD3L+w#;Rt$? zh+<#@`ZM2-?>@8Oplv}+84jqmko1q)XS0bup#SpO3^ZwJC0|+mQn8lHZ@)<@Ovwp!p)8s`UU> z{r$ij4*_)7c7GABkyk5mT1=mtuGx7H;U}Ass~0{yU<f?+%rHOcT*9^?=V^3?5~dF)lmrUNx}7eS!JOg>!KT z-g17TmHWkh(ja+6B5WgZF+l7@R+*4+n43X;fJGpQ;d4V7f`yLr+cfeD3b_pmfRlb$ zMo2r)4v8EzfL;I1K33w&xR4ky9;yk{9Cu)PkJ{ou`C?}MWutfd+?#(g z!9Uq>Kg9fL;(fuBQVdVzcr68;w!PW)%xb&Xi7hM_MtWuY&t^sn>-T!ja}2Ncn>S{3m%qmT51EEZHRZYmd)xJ#3`?gNK$~6WKf7pBuquh{PbKaS;*S8Tn;#HsN!%>9Hh-rfWh_Voa! z(+2BvopN0kJ#Ov*te^1PZ&vb*y}Ss?v#d_eLB=>4!tD4i$k={Z##|^Jed$lDMj7yK zM!&DLC`s$BC(q*2?TE9F{Z>UCZW)m%Tx{RrC1#{`$C>25rFLGC>H^=o^A7Ar>B+uAXr zmYGqQPI5J6wqthb_~PHfqgBG%!KH5;YUZjXyX_ zHzlErnB|(iKDCyiQxYp5T$IF3B=c+SDE@=bDjxy5`~;z^da?$eg8|}6E$Lcr>;<&N zsS~<+Ak_5Itcjc83{K>NFy$idU{v36HA@q(<(1qoE45M^DaU4!WO@;P0)_v$+S6H_;9@LUKXWjhjO`0_IMUz$gc4kUyFZ<9co=G_%Kf<(DW2q z)IH^)5JlIWQRW5*PshSA0<)2dNJ$LhngYAneERdkr2aGgJc}7-!q;x{Y|OTil>K&V zVDa0x_3l4azJFc=@qEq1hqWY~L#B#;INN>Rz)4Y2Y|z5IU|z`496T|SbF_t&aZ8~f zD_yk|(QM^W|7Ec5tgHJpf)=A#JT%5EU$+UL4W{a*bJe4SwMIk=p7*!Yc#w8s>r2ZA zY%6hnQ&CTidt~sN!2NdNC)({lKYBa;&QbfR;{9!8K^zsRu&}@3WzfL-W-Ps-qLO^E z|Kd6_yWhP*$7=D(^b)R)BAg!`u~IVelBn0jBRHEuNg$hD_~G6udOROj?_Hez`{Q^E|sH} zJn#7~4!Be&WQo9IEp38nhUk-%FBSU>ts$E*SSJwDW%%{@pved8 z*1_AMQ3Z>#al|f0U$%iGG>1QH7qx!`*aGJnB$GxD2yCDiT32R3it6k6z0*i+uH)j= zxa3Q1bs9#Q@^U*=7mPAMU_!rRV>U>o?02Q(5#Y|)Y@X(~9ecKssK}*#H^dCNRtH;> zN9d?sOZG975psK@6|KtZqF2BT&nS@W&a0>4Yc9!bB~MRU@wqni@XO*mlXoe60kxu( z(4yZ>*#ROl&X31w%$-OcSQK3ANa6Dk1Pru2Z9&_-P;w?#>C{VUpRt+?(K&{ub8p{p z+hq>g!n;b^nXiam+d#jL>qwC-cUkOXuRrUW0o&lVDkmh^cjqoL+}3whHE4V3SEUjF}y;zNif2=P9#rSV{lPUNDR)O7W>LwRZ4B%z7o3 z{cf_={WKuP!Rv+!bi3=U<7bmC7r8OSTb8#x`&4I%(?P)fG04UfbjISm$#$yJjgF5t zQzJ&fYxnH+{L*hrb%Tp|xxYyIU2sbdQ{M`==}Qqd+IP(V=l*{#0Cie`Cse&5rcjgN z|JX!vgzxC*pnL`dm)?d$s}7vKzzqY_f2J1$_CQQe3>4h95OqKlDaGmED!#^l>GLqG z&}Dwm;7ve>=qnrU(1X$CJ6zw=-$dgyr<>aCWAxmSvna(RXtvPKC5{wZm z+*sjc)5uy4?%j^b5QAh;W*?~^7Fbw+Oq`yWcah?ep?}ok=715ggnj8XOVrnsA7$`B z=Yuem+07qgftLtq#?uhg<70+>tg28;JL2vec$!9P@?FIX;4$hcj+x3jud6>vRxWe>~ z4boXfDs-O^aN5X-vP3?W6x~>LTtK{|rT_s=ct+;2ab;j=Sjl@*e2tfopL$8lNfb1YmU++K%&efl-pCyvrMZ#NNvWQvQ(MaQprK5>=CbMS z)R{iseATSZv18Aq?%r^@bYTO65c>)Z8W6&|47<3c`18QUD(qTdZmlB6v$kyV^*&?` z^<{uzM~7ZeD{*F0C;XPf%0+@}z5RfCTD`jA6>DHal&sw~`Za!zR6SGYJH{!ZR1|!w zDWCNBHSV@-!8+cwP;nmMJOmU{` z@@zGuy+6f8nQgh;nC|1^Ug>|W6cNEX)i%9l5k61qj#!4xwrp>pCw!iYv0`}Nqg2?K z{&~4)PsyJ@p2GQS0y0Q#JmOff#QeZ}i7-TRHhjr%AmMIf)+9QIW^Oo7cLSGy}WY2K?Yu{D=aMHYAX z`$eq^emS|(gu6p25_E?Sri%A2mVsPeJRq014^34~vvg)=(x7*Hae_W40?Bu!&bj8X zG@F*jwfebE+Smv!j6_*s$XK%L;(Fgr_qe%Unmgu+w9bO~wP>_F;LsHQ227fneQUpr5 zM_A473;gbI%6a<5-$ph1t#yR?>g&HlzkCyZ!?HrC1eT4asu%Dj1Fh%9m#(&5KZgph zk2@_PyxKN>6cQIfwC>AHdLjeWE802vWiNpIE3vD+?^Lz{qZH_h+1)oED?N*YYz9|X z)RHNPp=Xzcw^WjB!5-w}_tsTI^wk~I!E7;3JvpBAiOYGctWgL$^xMu>#JhU}+Xv*d z@8>oe6=>Ft+uB3^L`@5=qI#Nl?$n>D&QVKK2Um5fSz){DZd!1fPBy%f4x^@bxqEE3 zggeBNM8mNI6Jl8ke_Ak*3*J~NTl5zN!^|(0&O!cNp9;58MS>%%Qdm;O*w)XN0f@vZ)`xoH7i zR1T`6OQ0{(!#pnXQpfM6tU(V^UoPyks#Ig%4jO*u%wV*se^N@gKH zuK8IO#VTHXwO$tT*l1zt=E~MqO0dwqfz6zV>>#h^r>(Bb!G&`ltymO8sAIMi19@`Y49Xca1%U=6}F;hgoKjXbQN|Pxoy=EapWl4ci zWon5wY*H#fx^?gz!k$MAr6R3M{=14p@*)?nO6u%K{nfF;akb0{box2@&Nd3ZA?s}^ zFk+#1_}22;q}GsFxnXtY6oDHn@i@rse2-!-kI+Z2Sk=zyXxe{; zO~bca*qWb;$g&JjBIup_9~?jH!9|`US1+-3FP$XHpf@Rf%`7?4-Snm$+wa2ci5t!G zImVY!RJ3L}_EL!hJNkv9$7-C{5n{SjnTIG370twKQ?38Yj)_yX8IR zDxqs#shK^iPEzjg7da!s`R6_rPVm7oR-e-YOcPrVb8EP+fBkB}cb#rnf8<_&frp3N zqBrC@V|05HnaiQ6auh70C@n%Xy{<1sb#m>uy}dj56#OR|6dW$MvSya9GP9~?iWz-W zFXZ5~)$aN9!R7VXX=17xp1N*@p%7|k58>v-L7vG4J(t1REd4 z8c#JLxAO|XKwiyJU|m(xrD;0|akL}mC?|=AGng+&-ozQBt5qKApAv)m=s&pDz`4-7 z>D8zUdB8W#^;iEeE(qRnYNuvBAHyZ@O3E;+va3%b^gd(1Z0eP8dnACSu;!T2cM+&M zC)kjC{1?%fXHk)Tj&kJ)Gum^kNwC{b-Qz6z%O#&rvXQ3_)O2`{Z!w5h7peGhzK=gl=Ot)@+~_nmQQXs$Vt8UE^II6c&` zMUN*7o^vMW6pb#_uvIT-rJB{q0N<6ft!c~bQvL=jkTNn#Wju+_Yd0#SOb-x;?JkC} zBrrS=B<{BiTQ9*K`iGVS(8pdmotU0GVtsE*_p&8jrF$?gLxhmbSOS=TL9Sj+C71`A zS6hmcsR*Y&?UB@N)`-1*mIY97$&Ti(7H?GyT}2=cIby~*)cCFiH^tKFv^pr)jxx0M|jOu)eIPZ5%~n55w-DDsbFdukV|&z zEfIg}bs`u(Qmay95w`u}EHqM_$0)C_UhHW1k0`A)0cpAwf2plWBYoY*(CpOi2R!qC`(QB6qwy^3)RzI^JCO|J$Z7*3VO5keuB+*nP%@v8f}2{ z?&qv{vNGA>Z8Pt39#-`V6P_pg&OeSH?mbkD$Nt56!?Ff{-v2-lDFSJ(Bh3$3AX*Cp z#5Oac2cwx}PDoNN&N`j>O4O6nfB+vU3E-hy-W-yqK!xuuUAaXYTNZ705!mj_*;Tt* zf1M+|!aGbl!wHO>+0LVfM>c23PCj*E9EJ??1uL{DIU9LcjfTNfiH-W?uVhBZ$I0@T zjMdSWV+v;%qlU@=U;eN~K<~$;-Xtl{B(h^5pSqccDB~q%W&(ep&igVEx~fXK$|lJm zA@`x##Wp^?&KxxnPyf>B&YK}MWHSLco(zTZXaz4T#24T-$9CU@&@U}sX}WoTlR#4- zwNn%*++R}#SUA0)moWEA+k!@V%Xn*Ay6L=hya<4`)SSN9f+2iON@GM?*VJD$R*mn) z(by5vbQ2&x@RN18s53xgaH)|GEtteoaK|~}!s6Lrl!I=F5l6dBxinI-p9R~nJ?(%? zR2bD#oI5MwX8&yTtt4E5Wr3J`Ft*-}y>^X{n-~(jNU9ebJ7nAcI;-4CwNliftHT(7 zN||9033Zyl@;QPy?KYd7bO#z|T}8W@VW_C(SRw42X?SE%HW;uC;PclJ&E#oKpc=3tV;K=lMvwdMV=n)EDljV4M8Kt)(g z&`~H7FFm?*?wN;GI_&)`aXx%afy@?DKF6wX{5j%WTU9eA!5p0UyE3;mL^!B|3&af1 z-yt`iOzT1-=C+e6&?*qMQqP{080<%4NvfftkBrA1-U6f7C8gYU?;M#Jxp+^d=6(t# z$S9i17FuoTd>{1mo_DnC+&f|g!P(hB!8<3U1Dc-*S+T=VpTn-aLTSd{s?Wz?P}Xu2 z1X9f=@Qm0zbT=2=A@rCB*n+2SNukp55PZz`uE^$&Q?e&G6mJV1$2+Wh^PT6N}PFoTmx{_Ro4{Zs) znfeYBv|;fu>2%u_(pPbbsL+$AtUW9d|pcg{qs7<{X*F}@wIX}n%9T_LD~ zZIl9frWP~b5dn4PbjjL)uCp5Dfx)1MfDO+DtD1oXcG#qtx$PaiuZ+ZB{*m~-cygmo zSgJ=Tp}ii!ZRJ^~3GF3-_wpj24r>Wz(bh5C(TgVs+=O(iv{&NH_<}a7W-k2SmU#|H zo}(4SPacrG=K#o!sF7nTR<^mlRT!g1}hxxNaC#lV%G|iO>U@-yS+H+gMf1q)XSLjxHqw zO=Fx#(@r22z0s)K@mfZC`3k$+LE*&+XVMO1!z`HX2O|~pabbCq1OT47*P-LVxrn;CnX=9IRLjVNZs|5kq5)eEG#2S4 z(AR~QUE??g_$;)sZB=MR#h7?rOd|{9<=q zN`Bsa(l>+@ME0n(mg^P6YSS-QqAM5hTILowVo%NRLAL}`?;WiuK=dnmx*>J$m$v}w zA0~Esb$;ig>qmEy2*QIq7{+wgA*3mwk(F*SoqgyS54DjVlP|sX$`kx0a)T*8wDYUR zyRwsT35Z;LbsTWTl`6$&;&!->xP?oX-wh&FiWAwtsk9l7!j6llYy7MWGbs?0bNlqB zKaN6xmocp{i-_&@&JsXXWwBFdgYl-u#czv#U;W=+_v28FSUP2}-k*1Xw9RcsfaY$aF7(G(lAPA0Z|by2*nq;I$9E@ldMg5mU0!@g ziNlOIJ#h`qJcgR01k3$oF4Hx$yOgcdtm^=`ziTflKT!x=v0O-q6MVPBe$bC6FCSNU zzn7N&uOY?nc=TijAaCN7DYRa`YQ;7_*O$?c@GcG$zsCD=R}#eUe+2>t0et~lzj$Hz zeQChC)V{CoOP5;f-yYhJ5+N$o;@=p_H*0B}lE-mU`opL;V%v9>QQ z>;513x|^XbXf;T;`@-><)5d=^CH^d`A5V?PDGZaORhE^s>3=;|r;Dx$_uSz;phSt5 z?kO0OzNdSBl_Qb2?yFnk*il+cMQPVk9mWM@AMq*vT4%QXcL7|^c%HfdJK8LJOg-G4 zw1mWsldV|t1I7#dI@xwm33Qh^lK1JZ&3GH;mga7UnQ&^r>cgunmHtw&zq(1F>9_;* z1}m^l{yDL%u3f$rN(kfGEwVdtf5{SRXk9`4>C-)F!D>uPvq4&J=r zrqum%Uph*@fCk0L-7ynD5C_nrSTMuk12YH1G8c5~3V^lE4;IURHRzwo1~$rZnD#e{ z406x6&EqzjzVK3Xm#xHc(GHvk`s)n)4ZrqxSz=GL!(Zuy0=LtAWr$-$av?Dj;wQ!Ifost%Rvn#Qefva}gdsI?8w~gNF zBVJa|;rYdCF)ymjba|~hob1O)Ay=QsiCu?;p4E{*dL*adtoT9)>vH- zZDNHAQP=$G4$<=QniHEO3zKQF-WG-rqmk+bF4~Z$(PE^3y5x zP|IWXbC&>u4-Q-e{iOi>fpOmEDzZPwABCPI|AbA?qz>qL`QP2IVJsKjzFq*8;)J<4 zGjGF?ukmsh)fe#VZtB0H`a%DZ`!)Ew4mYo8uQ*vAFA>}5TvoFq@UTG<&`z7eqkl$a z=oaN$DuMFlQc^Fpo;EVT@ao-x9>37t)2?`YKQ3a&ZtnI2@q#lfM}}SJGC5W<4&t6B8v7SP;p~7GEqAI!{|h`na3$k!LrXuc zRFwwVjK8kFY$=R8itVft9|!0^M2F?4e+&ircLRh^n0wjU7-u!87#;p{xLq%Cb>&3J z|24>(5qEhoV5yZ^tfP;A8frmEWG_SZ7u_D1UR>G$qdzqhOaH%gDk6CT zm=9>rC7A=b+5dzTh!OybUK?d{{hy!wlr(?;^8dlc?qQt-m$*0Dr;kPM|{8G6n*24Uy-Z-edpjh_~5+ocOLy0#`)iue<)A?L(Bir@;}Y? zKPmX16#P#L@R7F}5Lyzed;S?v2{LYWNiQmMT)?6GW&3R)n_?@@0py)#Q|n_F$jW(I zh~2xLcvZ|VI-C<0PS(NAQo|&1tiUVCQijouv&LDjp=x6cW4^Jr7K$$F{&0++b#ZZF zE{~Q&fw63??QXj~zY~xTCHGWx%1wbw^`ML)vNKcIzWqF4k$`RAQUGM-SlFh{plP_k|As7ki@; zpf{+Uw`yt?0=7mn50Z1)5}_WD?OZL+B`8Ol#LoH zE>S8fS-Jb?DH>7eWMf#MmwGurCy{7rq0MHfnbfZZU#2Wa%1YalP+Sd5p(OUu%_^vj9lS(^}N<}InMp=5cFOU9%0l#hJM=B%! z9R<(zE_+?uJmklxWt(K!kV2pruN16_m=s--rrx?OH|%wj=6K~$hXGO8 zstP3J3liA|&w*xb7!!jf{IDGK^CtcQuk;>n)U8|(?p9pvL08Z&G6ht|6QTQRYF2z@ zSNh1R$&au*Z5`zJIfXlJhWSnJQzXU|GM;vt)xJkG>V2W_PB8VHftG|e&*w{EvpB)l(@zO zgj`OuXYHe#@MUX6%~tCtt5@JfPtvK)AZlh^L0l%59HKmtVl4&y_?t|e+&H&k(Wg8gqMqCWFoJWbJGl?|2u>OM6T-}<>cyDUA z7^K!Gk9o}{vVHz#Wv^Mc=xAba+xJFhYMceSZw+V* zQP^k3L^TS7XoFFwjfLKvipv=<5HUhxhy=21J0E?}Gb`P!v4L@)F=SrZH7d0Lb`9r_ zxx8SZ7TuC1HRN?g7YE(@oi)W_ud1smTCL7+C$$7x{`u>i^hg{F-2E?9h`c!YT@s+> z)Sv3=bgAKLgMTV-F4!Buspsmh_44DAKqX|=I_E=;^6br5s?@rDXE*Yr9oe2Zx2Rvn zI_S=9cHWy#gA8gI#pLAg8@*V=H!D*p{j!VyjQ%Ln`Xmw5_--_HpmO#bBU^a5o^2 zqAnTi;Tg_iJ@&a(n5m+8rO;I%F{r$4wx8Ave$UEAo>5yFC5i&OwJcKtwIl%Rq|#Lv z)OE}2=GZn*G{W<`E2Qik=@h5+7y>e5gkj<1is)%{SDn*jiL*)qOA7ZAn8i2c^>Y;E`DLI63Kt3Jg%KG{C+-u{tg`~RlSX_EIRPEX*TWX>0aThEZa>?)5B;RFbWmlWr7pV2V0c zbhUY*oH&*$tW?>=4DRasFL3R*KikYBN9r;Ciy0?qhwrHY2!ng_<>VMdJ2YZY zY8x)Lph8Ud6h%4ynLBg{tm5){V^!;{m@`hnoLeFwMa-!J1zZyEwEG6_Z9uX-8iW-N zwTR}-qH?7o$Op=YySJ^shC1h40JBL_iSB{KaEF0Q)MH3yYZ0oLP9M5di1cCCdq>}) zJOA}-zLt{gaE6$y+fqZGS)96Bx5?GX#(Hl9hwAKr ztSs9O$dOh^3zns^DpTHScx~u`&4+{ZR0ERfW-GKc=4bXfz0fb@w}`3G^>6N_P++eH ze&&pxN6y**k4PIaxl%U$@tt|bshs|fB=PjS;q^2Ea-38T_mg_^C*7{IS2236RwauU8`5(Tlbt% zB=d4Iwyz^UL}If{?8G zuJxgr)2Gvg=F{vTb{}76snydAlj*xDHEPrqeh?h$c??u;*rQ9>h^b*^^H2LJTk_cY z@&bSw-rE}aHiC=Ww}9mLV%`_feGmMRYxg%-%>6uIFBN$u_O;zS(c+1>(iPy&$j?|i24eaijb1nSz>20TQ_xbAFYRz1Hdbh;z;3z)4CYDE_(rhr zl>5mGIA>d(o?i@h!eF2G3KSExgopEA^4{zz=1I24*0V-grPFr2uG1;WA3UwJX;L=2 z`nWQQRW%x{T^4@GxG`fO%cc8kz-?Bbj6ktLX)~deX6fkSL=0>^0XQ`bRVi&k#?5%; zGXd|-xBs-_uqr{j2#eRoAfy$OB5yH_uJ@tUa`<|HX)iP?hxqtU%(Z;p^8&;J38;*G z6^SORWKs(b8G7A~A%5GkvEW)=O8`=A>hHfon8xS9+3(@lKc=M!xckJ4f|C*BTLaV+ zwLYAt0#9igxu3k6n%-` z!bQ4bMZk#Z3daeT1%B`t+9*VBl5fH*AehlRL9>~?ZgUOYU0d&c5fV);WGNVA5T>ou zi)>b{?|@lD*-ECDY#nP@Q`*Z}C6lh&IX_|Ed-%Wd$d9tnG^sAvI&0sZrfK(=;*K$I z^`))Qx~A))JTwm2<)ZEP4f$3RTNPVt9o<+AQ^1MoSF$UQ$;Rw7^^}FL)-BJ2lG-7s z^-4y09$eBEzSq09;#R6V)YR*WbRU2>ux6rVVuvW*qu+e}SJ=KgGl%d*0KE1nTL$kb zb)ag2qTObT9qK?Gl4=DRbS1*Ha1K znJK)4EiZia1l~`V8nxYi=LF|jy`0gypls5_?Um||jwr}LgER5~_76Zbze>FQ%;Dkv z%7Y7NYJN-I%@ZW=)ZA5|)v6S_#BoNb;-2k=V|1jBg>!)QDc46$VeL1X$^zvy5dDlv zv?naB5O%tAn>FfJ%2IPL4_z%cTxA+J>)Mvc0X;j&fsl*=XyZsr_xFSYs0P8AqL17) z&ckUjC2Jp4t}L>j5#(&4EyAmh{G4*-RX@BH_8GE03)Bp9KA&tS;P#^25`AUHeY>2q zh11dP9^s@KuDM2S1O+E%sh|Qwy;FKP!M|Tg|Etc#C&cIDM>6jFUOYFHB9w|CIcPn% zl3VWehe~cQ{%nGPql;>h8Rpw-UlGmYCfFu&y8@>vg4BX@Ux(23GDH=YPeGxnxxIbvvNB>c=}>n1BU zZSC+4LV}%Rs%w3+(*R~WN|B_uiBOM^#Oz4Y#G=jrPY zw)($nu=*s1O`)m&lKu#2vLGRwQqZnOf1t;Ip|A$#@hp|x+sigH)@7g^9toGQva)WH zePA^zp|3Qe{>a|JV4g1ps1a*S?=53~$Wkg;aT;z#Tx|S{QxgAj7u)gyrtH6TZO?lI zT9-WAIv((P`AGc`6@N0nogU$2F>TPWz`M$d$ zSxFSXRP-c~%`QLMsX|pVA4W$;xI4uL73kU?OsxVC^pP)9yZ6I8_DomhC&HQ#YR;){ z#xAhGfAiW}E6uy;NqA=kAer{@x-ETEWZ~hL1D_@tc`LbsWM|iCh=-#NkW@MF*h-ylFPjNIqiKXJN3ks!}3{mP) zqeu{WvSQaF#$K~Z3kRRki=e*Fq0&8*^3O&*KYjEMQi&v)f`HxwrRek)R&Uz}A0Nal z-R7(95x-&JcZ_a2NMw6$J&<2tLpw~UoO`%GE{-W}w4TKqcAbuGZ#2+XGaIjAn@=A3n~WAxdpgyF%#4LP^D+A!@XGQA1vuilp1VbG-CuV55~PqKV+plq@XGF(JD>ZzR4WJ*{QW5_WRC$ z-!;{LeL-^r&}vMDv!T0LvpC@5uS1UjB|A1yke`js7tK{If(>3W_>dBtNb2Pb@;aM?;NY; zWk6(f*F^SgBQ8I#w9=Iz2r`O!D8*U z)z~U#JNshtb18D!-{tOj2mJ?*<#!eIvn_01az@z;W2vlKR&VK3|1tgcU+5xk2gI(T z;HUOtaQ7b)7%G+KX{)e>$uc8OT~An&Zrz9oy9?fg9jt8Sd4nT+K+qL`WmYyKL-1=l*a`r<#DY)&{$I)uR zT-3$n(#k9k0liRJNsW=srsvPSQfiV?<+w{(IL(FAbs9<`R7%Ji`DfHRMWBwnvox(_ z2jT|&IpAU@tubL=?_r=$$Dhw*TZ=gc-E^3{P5?{y;@oBawDlPb*rQ|Q`FbNQe6>Cz zg0>`124=`_roUQ4z+EcJ$?I-Wj;PNSu{$PqoE!B?{)&$6WI*$Cd5v|V_iZL@b+sJ^SR{Ad#ysA%z_(&kOkx>~2f7XYk%>>IfvvA^1C?Jv!L z4QO?gCBrqk2ii?d2JdW-vFvCO>Ppek`=CdrdNj;8F!~%*fumrae2<@VnoOY-L(z&Th zN=>_bizg=+{8tv@FQPKu2;gtnNm>t)6liY)G$)~@cb1TI7atR3VuK1G84SsbQ!2+eugE7w!XA~W z0_{D@O5d(13I;nCiw@`bB8I{Bp|}e0{s?aWy_oaAYF*%o@jL)J{_gZJF_M80{$#Qa z-Dg1Bu}8Jl`Q9@5lQtsmEHS~vp~l#ja&2qC{pdK*ByFqdj=QEAIK@W3Dk~^zg;SI*yra; z#VbV+nel1AaB`Q7S@I1O#jI3>6u>gbJ5)QeY*tJd>yVIGnbDlB3tL{#Br&q1W*Q9~ zNRPgfb-7m!?vmx2jR&*K=b=alqw_N=Qsu+}*LS%v{Rx~#Rt(4+2_h2JohV{yOkwUAc;!A{ld*=$V> z_v2z?43nG(xN*bDO%9P?Goo$32_pPTeoLcRICPwLzP5&JC|keQXG6z*y%UI~ne33*^_V~wy3d*EvS z8SI6HIG1+dkW8*-0TgDoqAv37Bk?{&{|7O4=mhN|7#Qg(j)~pln4205jlRns%--$w zb4)ee>6#^@T7chIR^p*;Gr!sQxnQzDB36$gB55;U_!4F9x*6*0q?Vv2MPKH82rzbL zZ=wHqIM5PR=uqZ-8mtB~#Pm5U#_Pg~k57W0h{uy@#&amo+~JZte4^(0#1%)XMv^2+ zY;ICx8bXT{_Ba^{qw>HBxy#4R$U~JFpfBAoJ|9;XgrPIY znROGv8HubK#mIqa8wZ#RWLgA<5|}OzK+QDn7H;eV>Vs#3(LOB-B_cBKFZEDf2vsjp z)<-9d2XYo#H+zv6e@b9p&(P)x@>xSUxYuO7?6w@vc9heJIiTZ%A5i+}EL|X41Q@3I z7m}a?SID$TICQgYN4kv~)+$ix^TXglhwjC*t!ztEezQc@nRWQwrz&6amjhBZK` z2i)gxSCj$W14mUevmSna8Ozh++CneAotbB^aL&U_&cVkJsE2n`Go&p1c#tdmWSRJ0 zPDaHJGVQ%&^~wQ?XJ?5N*_Bq7BCeu9Kx@2AUz;OznKT2=lEV$xVb2B7VTSN$e>{|x zT0xLg$01%0VW8Mz3+q9ns7}7V7CnbqMW@LOr`mq%&Q8&G-R;@7Q2#i~bk$DtQ!`Is z4d$!7s^2j2Q@+{7R;4`=A-_Uc?);vDnYjZU9dyF#Zd8q{EfK%&Y~nW6eOc_&x|UA2 zY1^{pxw4fCw!x>C~eZ1EiAn&Sc(lql^q59J{v?P0H*2)FSo-7dH#;RqArreqhej)9q#LDx0n=XzTaz;pu3Vy!U zSvKp#MfUHg;+McfK_HuiIztBg zOyUea)FP@IV)5|dCS}p5g4@$>=La@Db2mnl)J|lSm!S94quiC38-M!*Ac?&@FbDpVV_Ye84S)yW`;n_iZx zx@oOrTBWwMRg@4?d+ZdgT~$IzqPAE<#1>@x-kurrbe{Klp6R@w-#_o~Q-9?X$({SY zuJb&v<2ugcJg-fsGKN&Sj|0QUEJQT?Z>>B)eGg;@!3r_S@sY@G zL7_tK4v`7oIiEDfkDO!(e=ZlsAlCn#=bY4MDrOO;f zLClu?cS|8k>;Pj3BI#!RlaoG)-2GG@q`_Hli4hc(G>|jA$^Nu>7e>YSvlj_1qoeN% zGE*A==9GOo>-0A*?z7qYk{SK_na>RD>!f@&wm$;f*B4*i;j24*^@6Wn@PEJyG(jf} zwtE45UC{r=&huZ0`mgBmzi>QX|Mk^c{%<;nQ!DqYgGzgM{`EzR}S{r&46 zcWXKv`!9z%1m-tH3kV{HD*T;Xa=Ux}XsPifzPV4J`0n_H$@I@^m$nQ#mHPw;6Ucrf zg;*FQW2N-m$+k1nK*K@=bqSL3s5U0RvP{$n3VXuiP9-|A5GSlF6Qi>=Mb{ zYft);JN@l54Fy}*O{(a6{^#o+`(b4j6c*aozwD+bBY#oJ?{@3oNhPM71vn|CX;+~? z=UF<9BTEpuJgwb$^Gy|4lDhy$*9(!9)g4gyO2MoJeP)m=ub>c=+rO-=vS>hDX&&>eXOY6t5-xa}$p1^cY=BzPxn@(yA_=EFg~ z?p*Q{V})xv+Q;@HVpwc65f7cgCdZo67G|8rBX3rUv!qk zukEO0gSQhDA@U`4L8)wsj3+T+_J%EY6%oTP=gZUky`(4Y^7GeM-kpc(mbauD%|+bDe6oeOQs<%mcf8E-qb7waV%N zcG>`_x8C*AP*RVqzm}|?mMpFFZNSpUaPq`h_j-sGvr0XenV0`Mh!D_0FCvtIap$`> zwXS)Q(@Qp|vrLX#J;@99(^4>b@Pu=+J_x9 z81$c!HNezqBYHr$um3SS;ND4jykKzZH|KWE)g9oR-LR5`E!b6uGuB|odRtx@hgiTl z`{L(q7kLyPEW3y)#QI_@Ib2%Kyoppp$5>8D$#MPfD!hn`lb817PfhU1+Xt9hwb$!f z+T7d0ad+?E183_w!yp1n)s**kdhm{h0{*ho4oSc)~ zj~9<_i?>w5qPb5J_)WT66F-gJ)htIb+N~k|5atM*-C~fvo7}4Pw6&z>bcD`cm5jIF zj4^&XQpn`9Km`FwMY^iN8A$M6LfPcUXT`3oTYx8T8sk~|y&N$UGj$OPVQKU}9YgaB*vm1f9` z1l#P2v})6K;3t)gx4sr$^(_$O({CQI=(ejM6Osk*-j&Qcpz+@tmi~DWntFa_uKp)& zF25_=TDsm_J35|%{70I`F$#|eBM~|~4k@_fm}1}iOz>I37GQ0GyF?vvx{FV;uF;U_ zl8H14&r>L0+Y)uvrg-%DIBC96KLVB?R6JMfyQ*;E;-W&^Ap=x?1+SM4+&VbVXJ8g5 zsJCH26JInrG&gb}!Jsu~(4RvX5H6|6-B8OdF?tS4y(n~bKYik+!udXWxqJsc&YZioqlu_NhPs&`jxz}6nz!s@6p%4 zTD~sN3G2+YDfYgW99`^kZz*55hUVyFtxC|e>Mm8DOLX$Y=^Fn~`u;tQP@|GLB&aGa z9ctwedg&QPAa!51wMz!kHgW*mkrN7-fuVjQ>CW+MMlttD|&FK4f`^w zJ+l7Ry%1g*wH@MzG^N%G?MsdbOh(~<^7ia}{$qtjTY3tcU9&8&kvH_jfq{=Pc?fn) zY+r&`ybq0#>GxSn;z4<=5GZ)FJ}h^xUM20aVzSD8Vd1PRf=@SI7tv9+%h)`jW%uyR zdXE9K@eL*?CUe23MTv1)5h2Z^x{wA$u7f=!Qf35ph=U*2?L8f)iVv0wE!c-Gc+?%9 zlc6ewBW7Q&cEPoh;(W(*@q*r-BSV2frccG~fb4x%M{<$I%^U)F{9ktu{~3++%K<=; zFoxPj+9|G)*>S(Lwkrdf;(gw+KaVMzg=}z-X6&7*n&+ZI&<4!+K~CWQ;P=YMsD?7J01{-oP! zH@KX)yvb!OJkM-J-82v#X0=qVg=CePOj!@sg|Zd35gzwcrTTkvwDL}|ej};NQ z@#Gpy58-c{g9kZQ_k4skeOhr&vktQI_Tk=EyJVUdyW-}I2}!(E^~`7e9jFz0IBTaa zEEvp)4{T2z&`FdW?zgvx^~^c4O7&t)80Nd)kPec{7No0oO3TeToTNSu4^P$!G2FyS ze{m>JISOcLvzKj7xixR6OFIun5{Uy+h|2X+umtxivR{3M`(BOoxum&hHX9XWUjdy= zjT|goh)EP>JPKOD`HSbNp(!%DeXvgr!nQiOM)%2d$E7T7LW{1x?LvVHBqUf>S{s=w z8}2{!<|fzf$wQi=LD1aFAPv^yV|?Jx0zK+z`$6s|;|(PLyAC}XnSar%V_6M*TQZC> zM;n0`gL+sma5Hvgp%CMl=ye?htx~T;x;+WgOHbF0*|~X@uDJy^X4eCL-b{}H`}j~B zUS^&Omtfazyw6ifsFy|9bk1!u0vHTIlhv}7FRy!b&A)wn{7S7->oyWjd3A==e%T=< z-VmpEasDCYjL?yS%a(z1l_zo(^F-(W5jbR)qO#Jmq%NK6>xZ?U3O^h~hFJxkl;f~! z3hFtJKsV{}c|^aGZ}+N_JMXt?(*)_`r-S6@WZGX;uGO&Ga%SDe4?gN8&gJHF^y~St z%)F0DSc6|)50H!7_3G5gW0{X)Wl>9;0+V+N(LA4+kJ5Fq>a|EL(>AdmpVIn~a!eIi z?$wFKKX%fAQ$uH;n5IgFy*9%Ze801^cprHT4-0~GYWyAW2Ta!cbLWEILT3!7pmKw1 zAY8z2Cpi2WP5)1jzV*(@{$26w3T@q|fBM5g1Mf8KMp|YhMx#vPRrd=uqJ(4YDGwW3 zR0AwV_Er-KdlT!%&ANM}uWe8yCdn6^-k@WgW6stGUI!wT$sR8UI`L5@s?vAVg1?k; zHGSAaZ9cJMf|;R4&9X2Jt9U1=N`5)FW^Qisuu5A!9&soj_~y^05n);`(S(hNENW9^ z?Rh(Ft&0{puDbk+fm|m~MQd!&YfaU2!F&hw*g>OAyAdSUOE%^CW@5_lSSa9a=?a;P`lTMugsfV6$C}k0*qN-phz6 ze>a(}h*LV$26mLrfR8;k=4l%npt*fmo}H^-DN{_=edJcL_JP53=tx(k0z%fm?#Mz{ zeYX8ba?C9IR8q4=ds~au`u8^z5l_s}KX->*AF1f4BZk|1gxb*r9&)Xe2g5F}2>DD7 zY^i>&)&Ckr)JsR5ykPJKkyWM>!Y*P-B^pAI^p%8rlNsi6>8$F%m@Al*kEhYni?0=@vN3 zNENNF883NoXc#_&G_%4L?ZP7MxMA~i5(~q-uZOlAXDMr&gNIfq?wu`rnwtlNQG*zE z03^FFGt>%Y1Il5#(bn!An9X9$#+^^Y!k;JM(H@61+W`*bQ~ld;b#Or{t4%yaYkBjX zEo*g4;q_>oR$0Dgi+1iBs?w~NuEXstj1WJhO(mou1W6S|r9nw(q{j6wTeQdMGR^b! zj!~DIrEojdLh!A^gIRaOql7hOpXPzjm!t+CAfD=T+OP+~T=p;>nBj{b(f0K4`fF%y zi-A_ga;8Z{nC~L0&m&o`dZKazC;(5m%1m?F6M=PC&JvAb*Q=P%v-_NVef96z8wh8) z{QJ=GZ^DO@%r1b2I%k3S{Tq>z$9oOeC()lKna{YdvzB_N_BaMptYdsOH@PCpkM|=^ z^W39QW$sAgc)c7LV=Tm6g#1=08wAd|lk{Ajz~J>^yUwF7HN)T`bJ&i0q9H3%xwaN* zBA;MLjaK9>-TN*E0~@owT<%><8RlB+pJiMEfS>&&Cy{=DI#ljO$SAb;*@%u5TnQ9J z3*QK}m0dORKnct16a8Cq_QdttUoIMqqcAJs$t}z1;)Q#yteBJ7o{s~fc%AP%f?cDb z)H-T86tIMVo);_?#t05N^VrN}5g_mq)}GsfI% zwlD*D*m!jfAU&I?(C0}?h-j_L+Jex^XvQ4FMbz__6@eRuP<{4I_+zyDmrJXZET5;N zC6!KYyL6-<&&nF_37Hs_uGjqAnood)Ds2GVd=Fhx{wi?NrZu-Gh<;F%YtNF4-r|QBtbK~P3$)u2uIE8~Im1sY2 z7lYP8QN^H*&aC;B^sVnh> zBIesxrLd(t;}|IsHuzpwyFJsA0FgMn*;QLD9`XJ{eFxVXr?0Wm?tw~SI7kG>iEL{N zLqsD)OA7BlFt#bNO_?n?j$RWBOLLwNJ6Y$bt6O>tL{bBOCf=Viy7?*b^is6`;KpdC z0cU^X*s_6--;=-GFK=%I0#UbbyO4=?O22b;z~(D8ne+3VP?LhR&|NBQiF4sQfesIc zP)IMV*pYG{OTD_&n;l)VhrIZIWX54q^%+}K^ubzIE(YJKjLhFm;%2cNKwh%*{7ysm z<^#L=?T0~*2#1rL!N(qL)+Cc~y318!Xt zxlqcjr`pt#7_jo?#vNb)P=E^~V5OtKMI#OS=N0hLqtZz`A^oxUTBIb3X&t6k!sV~$ zkbPoD+7d2^j6>%<#+{31KvkPVWe@_WUid}c0tO?IOByP_$wJ_;aam4tPglj`ZjZNH z8eh7(A*=_7TgjF^&dbYRnW8OZ22Zb+dk+;$GV8;qX*q}hT6VQ}Z5}QC%Is%UMfj7z%P*>EZyal)Oj-n z`2A~6y+Odupst>1$kgszpYB5-k8dRqgmtUKJMeOkXU6*ikWT_SuJ?v(#_bVIHV!1W z?ra<8-tl0dduKx2nvPh4xafTFaQx)hQ*AmQC%Jr8P}FOSYJv*EC{a_-7#U1C85UMU)fYnkWD%ERwTW7gVn^1ez2R39PKIY#blo zst&k8FRCb=b87cV>x9g9cS-6E?+AhAk5Ok)bZeNQI0*xpUrh+loJgy5HrIR0yXI!kTP0*DvQ?|U6!s$)x5y@Q^flh~2Yp_LWYR!)LsqVfqvqS9+xUay2AELIpf>%6tH zDE?~SDTP)9Lqaj61EA*ZhbgIEw>mb4A*?6r37Z+-K@L&{HeFMyY50O#E>fWknNK|I zRX9STX8qYFacSQy*!+t#F_GQ;(+lZ&Dc}Ddteh5pxNy=pghesXF?UpYADtfxmG6K* zIy0o|Zxdb{R=M8V+uI6HaBw+~Nwu2I-}8fh!yKY~i$hM$#D8E8H8v7tEeqMcrmq~q z`0Fwb*wXMjakql0QpbX4Q*tNGnXp~+nWtROp1$x6nTP+LsJEpOu^hHgMP}>8q`3u+ zJyQ>=VU=5Ru9lJOI&2Fvt`7Z!x{oc%9IkA&Yo#2{MrCHf9QMs8C)flyH3RbOwU_JO zA|A{QT^jj0PMuufO-UW(E;OE(&Bot$Ns7*-@xGMA7SN1CbFg-RzbQ#``Ww0MbZNGP zCo_DIne?G@XLFb4$L-@Ex8cKo9zoY~fUD@N)79VRzg`PeIt}D2N!+8m81cANRJ`aC z)+Ln!@b>&&VN|sDA$Th*dXyjwHfe8pfKOF9aADf}yrEwIZI@Vvo*SLsQyt;E_^BME z5o9`oSzYoeFRX9V%-@5%@Ngv{A-RGKk~|3{#Je(!wLCh-%u0=0WCg9fNp-Buyy?y7 zI>^~^J)3w@-`FwKDTJV|EsvVm$Bj?os4apwO5BT+n64irsJ9GPX^oR@3z!5zSE~~F znLT)3dvxJ#eDknM5vTQA1N>(Bi3e09OJ~wK!cHOoPX%WGc_g;@iRRs{t1+*Y!sH)p z4@b)59XI>T#D6@oB0mq$@kLCYw+dQOr=(}OOL=qCrXN~{;f zQ?aoXiUPPcX)jWS!ksJ7kGx08ML6u0SBEDVQvUcGJ&y>xbv1`{Y}Ix7Di%dMW(EpZ zl(RhiaWw~8`v=Z-xg5!FWJv-G2YTahGGCfuIM~!82JSv)}&h|ZZJ;{D)E`W>U z6wnZgr%OLg4YHQXbKG*q-e$8{ZKn&LeH(ZA>4}+{c}G^X0Me3k>DdRz((sF-#ja(9 z32q1g|AchdVw>1T13V)S3_M@$%g`^)NJkp~G`(=|Qp(P4hd1VVH8z3SFevuYr;ZNx zTj)l;e8+hMbm`KD1Z3P<>oGuZ&^GmG0&dK8e%z;{BA%1JmYx{1L|j`5yr_`)M$Vb% z{Y=+sRW+OsO0a9RnwQ5NpO}UE-K%+e zVIC-Lelqfg*Se&&I2{sISQ|xrqoq7Y%1X}5=USl3Wcp6)@okEb8l^YzqJjLMS6=>i z7j5`)FEDjyRhzGFPu;$K7vmbtyJX=dIin9%$ZxF8lcygR;H?rm4==Am6pKy?#NgG- z%|)tiyO>Vq;TzEz7Yf%_=QA`_i8v41#M~^Dcw*UC@<)cx<9h6Rs2a5<#&|ZB6yTcp zp}+bX#PyXCLZD}Lh*qGj880_TvLn!a;e?5ZZniSme;J|fwiR{+X|*XIR-Jn?vyk^0 zFPpg>aZ=kG!oP%xCzE&Y5+N@URH46p<+`s<*6 zEkpILWeMZ&)^5yeztdfNmhs{C_*uc_e^K-Nx4L_XVlMf2;OiEs-o=zs&Dse=s@5 z*Q-lU>bjb8?w0L%*yq$+fvJsCGWi(hdA&*hiXDlH1~g_zbwc zy{bd0&y|s&pA9hLU}|FBPSp0`Y#ZT!HrJ`jGhIcbZa>lf?Xs~>-$?BWl0!Z6x*^Y2 z^;i}sN>1WV2vii(KJz_9*{n}zMClrwV6LSmfWwj}b~&rIxE#YZuXh{k*$C^zg2a3d zIu_~mK|iH(EOTo7+MD9|-D6LVTS@Y;1TDG+N9^VuPM6*y$O1W7#_%btGEx_WVJ+%MSv}e?+|w7Wa5c;Xq45*spGVdBN2GP6GGF5xuG>XjwWMi- z+4V>E4d=znj#fGkq|)CCk*;xA?q0|E-5J*tmvBu}V19SzwM1kO32w5HjVyAYp8;!M zLFKz(3*IYsrk51fDv=$8w(;k7dUo4R{^#VRWC3m%u+o~Q@|&^ss_)2q$8UedXlMLn z)51Xj2@wubQ5~w5Er~;oijbt$#`Y(>MchWMWCTNOcgD{X8`03oS+*093|)8RzcKRo zTHTRurXi(!Jt;rG4pVX5XZ_tM1}^UC6GB{{okIfLNj_qd^167fJDzdl$zMW;ZG!6g zV<2j4ZY-q!-aY7~)GlX(%4!$Y^8Qq#Acq01Z-yu%Gwv~qV}vexZA>`rnjbSTcCr_I z;hX8Vn8lmB-g)b;Xd&EgpGln(6^RLRCK$(nUJF&5AW+mH5+R?rFjG6>RGoZcT0|8`yC#9G$%5#|SJcy&l5R`Tb? zkGE>Qje81V<_mIBKHfL4EMubStBtC%{d*TYYlUwbtwgY@jBVdzhit;?_+!l`qH*9YZx7##5b7ROpv=KZndET zNJwGFX!R{cD4xTmAu+r<4snt7-|JhTm&(4w{UIZi7$TT)TGYy++;hm3|3P?ASD6^b z)pwDYn)(tAb7^l`!eI1asAv)q6Ec4}_vaBIR@8Nh;Sf_m(;%KoN&+C7vfwlALM(A8 zpeXilkd^rjrJOs#x#v>18xf2?{C;9AAn_dL^~|c-COT){%adZKUaS}t2D{$Ap;>jj z?y(syLxSv+@S`AUCjSGp+{+G1evw=~ho!=A_Ko$6JyaX$iz?S%Q_tSrCZ|z|jv2QS z(^P5k5&Pae2k+~SqKvAwR5&*gdhB|anQ9xU-$Q%H9?^U=MKuqqaOf8Dbwoob8a6IG zR^4l1l@`E`c_BB)eva}!HLsq%FO^CXk~E^piT5CP0%7S2^nVqWS}XcnM@}kU-d+I; z_6p@i_h9#^3HAd!=gNK)J^W0;MeU4zkwvOWdYf6NlI%i5+k27?vNbm?3#+oHsk+B& z09_dB)8K9z9upEspAf_jl$Nm8r81XBrhOtn2v~HK_iJQd#Eo6~r<8&9dr<}q-Hbgm zwr2U=-fC5LE*@h)@`h12OtQ68eSc?AeKi&%?{7T53B3vIu_4DwvI%%iyM-ryU)+-* zvRgn1r|>dJ{_eAFb&6|I1HCSsCFm0MmSE)e%W_G#W7%w;5g6Y)IILNGJbNs53N%@? zB`1o)n(NVK&L@a&s;fj-FZQ~aZB(6rrBY)L3MPPL>e*XddXB%yW@hD?XUjcTm=tVJ z>{TB)Xm#--x)9~Mu{7DHueN>*0#O%)*Zl*-0}LSC56-zG)Ny%kQ;5nN4=mXORh>_R zfN)S{*7|Qw&-@P|_i+R zc9BO=TyRi*El}iLbQ*c9baf@&*A~=Ow{|+r=rZK-agC#0@Q`$e>btk@$-!C!p2cr% zYG<}KwHfcBZG+pm|1U%JnKmAPH)G1Foc+xJo#eSE%m*Iql-yMnnlIOW3iseR(mYlJ zo2X3(32U0_jCjVzJ$E%R^zpRpN0o1>;vi^Q(qj(>GP=H~(A4+m-R;f}xCV3H(bsR> z!zxla!!kH3aZqUdBGg-Yw35tb7Ure**VT0_jT|UaENa2DwFBwIP|s5d_3EB(__nk$ z5Gq8US0=NL4@;aW2|D`4q3_J7Z{$?{Jy>w9$BuAK5ZPv|YsSv;XDZ@mRyx4c-6Pd$E758^BfS)PKJ%+ycH zTbIqoTASMPJjih#`L@m2x`emZL_PNtVBSk1jYkk}Zw0%6{81<1`ONXk;<2@`hJ0>O?=|=?R+;N&?N7mp}LjNGtPc=-u`scgNA=AgoV` ziO__Ba!#|w1+dqqp_f1;i$vY9)qy$j*U0{e8@rvQx|IY)7;j0> zqD=!8)Yu7Q&$P;HNQqi?5ciJO9u?#e$#N>NjZN>0c&fc2m0D)UZyV zba7I4x4#9;X1>9UmX)i^s`5b=3cEFKC9DLlzs7?oYYScQp@KOe@gAY9@{$=`lB#cK zKX<3j0%?UCFnU!Boy_jJeA86;0W7eslWsI|s9Sxj9x-5)+(*<6 zH=%!@C93HVZJifGA)^FJ2v%2QE znTp38;CZ@67dKl8^od_e%{a2GO9c}Dh&fDLui3nOJY)0SBx6`TK_BstbeZ9#!I)Q< z{h@&wpK?I_($%V2^i1KW$l8TB6k<)-M!p$d5l>7)B1AP`^6J;4{YZmF+)WW1sNp(# zCR&!)J7k?bo1=D3{J|AHsyU_g5GfDws9{W8L@R#^;T+XIo(gO~@IZ#>+mdSXZF*MS z{4){(`^)@m!Dpjq!PH%!(*Im9@rQflut!p(U-WT}WH1bb$ z$c9?+raM~M;pzfsK5WFa@3xbm(C=XIFWc+w?Dp*j9{*mGj*XS@vbA*(Wd7lE8~9eb zGLFE)RJX&g=mGqdPAk2vmXEj!hCuLcPtV-oM+}uX5p{uzlWVYqv^XY{eK~XA*+G*7 zdDbHZ^7#D3Ps|)pb@WTeMgJY*2i5Q0?zl+eU zGZP0F@Q=?P7HEq{vTu)VIebbth^t0?)l0s^F%D7!``9WO){BST^SRye#Vs{}UPFN1 z(7>nW*6&}*kUn|3a)IhB*v5zLmd_4B&ySGwp3kWEEKm_vcDja*(L7cN*w|Qqk-{}w z6~pBtQA?9{6$PB>BV29F^{Zu!u|a_atRjz?&$P5zm2^`o)d4t>_XrlSzE>4xgrD4_ur%*iwTs0Ts{wYGr&Qot6Qpp^>t8 zid!Dfb|)OBFpB=6LV-k;TjdSCFdMGho!U^( zzr4@39^aS*tjhCg7Bb$N!9h{*X!4YD%mKujl^L2b^~p+@!w;seccp)MnB=}wu51S#8$TwGJ zaQ#i=3bS@)o2t}$q)^S~>?)r~FJan8axnO8)nXDzkJg6XM_M*e&|tcE55NiKH=C3B zeEe>db6@y{x`KMiNUci(>)2C7+4C=6#F2`Gfdo9byPL29@>{5al*O$DFZk3I;aO>g zAH{5yw659f6d{A%L=bTI;p>PF=gXbrcblu}Ok%ufT7<7P?D0-CW2VG~KA_qf9|s<* zu6cdwR)5Zun8z{eI%RJnVzH(Gz>5oM!7mm`w?toK82YGQdsE$%OEO55XP2bwjz!5zfU+UpxkhClnEfWrZ_1ar{EtYvwjuPr>lUjN?t~on{Rv!UVxv-UV0g9 znJM054K@p#sDdvm-@QvO4{B<(*y`CN-GqUsMlaQij`*+V8r*H?RJk?Q44@y+z@h=p zEO(HVp?`J0cNSN-HntBl1=TG>YRWx&#dwdsWGdLTmGhg}eo15gL_ zsI=9A7NZ1F;kGm&ozb)cmNU`BX1;b@4Gz}=`5mr|60X*CfD#}uai26I6hKm|LCRl@ zq)y_t$kxq$PVC=@92+ejzJDK1g!}5KOr%1L8AQ0yjb~{wG|T0a$$1+K|WjVXa~o z5!E`C5GO5CTYpN2BF`vmCuc83E(7Rd(>-|P>E6O#kZ*YaTx+8>NSujlC+VwL6TSx zV_5L*x>bS5py)E&nDTQ7IIIG(zptua;w-fDxLbV4@$TBJ*ZDZXn;{-6t87J{tQWF+ ziy?QuZPTv@5-WRv^jp%j3Acv(3!4JONS91xI+4Gp zd!S!&D%jY&yDD@BNNgVy@b-*_;6_JTdSdDd=bCiWdj};Kg{~6;f$YL$9AX?ee}S36 zm$1J_9O!ptVHz7&<^AJL83!9HGaT_-u;mTAfd0eo8E3OEQRBw8|Kmc=DjjajWJoI{wj8aKGA0-LTVMah1o!d+vW%ePv)g5kC-3eK`q6vca@Cjia`5|w zA{VK*pYlXJ?8o~hZ}lu!csbPP5pY8)m7iSrYeKKzQ!a9clCo9>EpZdt8ymyl;W*9i z^YL9`kv+V;3+%J2PLu~JoKx&l?bnAy9acxfn-!{@`*Wyeb~opqLU3=53lw5Z;T42% zalB*axC{D1jr;IaS-stlygKDBvjbK|@Wba|2%S=PLl4?)qW>QyNTR`>FR5U1xV}&-5J8U zp3uHl)PriOaD;=W1m;L)k5ELKqq-wu-MB^TDd5ZN8-1Y(^TN8P@wA-@kVzCtZE^Vq zv1dvdyd@Dn6ye*Gpt^&!Pw}GCiP6E7yJb)b=m+(`nETJ@+B32{2!;Fe{wODz{xNd! z;JmQnL1xTCw?pblo zwR*%P zEputh5kFz`W@0iI0SSUrB0?^8GSUYp-(%YE@C z=x}fJ8Ky(QHW7Hc!Tx}Co%3j0C?+PLd32J0Ez~fv|NU0sqE2uH)qX@+^Bv*Wn3W;! z(Xcm$FrAQ=93xVibWp9VLbyX>JMNWzjaE~9?u2=Iy1$O}IVVs;f+9(9xmt0`&qbsj z{cfZ6lQ{?msBt-T+99fI%o*x^B>csRj(SfOqKu%=V0p-VRBQgDVk(a$wES4+BgDhQ z_&n2!_2+KKcEqV?!{f@i2d2O=wFB`lUhGj#7$60TIs$MSJ$Yx`@!Hf_i{3Md;&g;y z5BufaR7Ha84D5(o-Oy-UAN)Mf#7ga-_h%yPA zelZsR4@IZSS|L*ML~s1<@fT*+n7~`~eFqdG{OQCuZZ{?xtIK^zVvWS1eJbEx;{8qc z+mb_wces!QXjqtUF|D&FwRq$O-9-eUd9&+K+Y`6IyfUahN$l>IX77QU-%?yojqphR z;b1^70+AhN{JtV>lSyC9QvPO*Coe_%|4P>U>?pTCtTd8?s}s#Sy#K=4pUfB8ZY#Wp zs)?h`y^-Gu=!Oc)eEDfDCwYX_x^H~BCi1gLvOQ@;P=7h-*bWu{A$PG2 zy1!oV>p=a};QxpMUtfH6kpBg!_-Zy^&E~7weD#8_UhvfmzIwq|FZk*OU%lY}mKQ8i bcR1Rw96pd1Sc?A!_`9TMd;xXN>CXQH*j*YD diff --git a/docs/static/images/compaction/l1-l2-contend.png b/docs/static/images/compaction/l1-l2-contend.png deleted file mode 100644 index 6dafbbbf29e779119b1e5944a5688f1e3cb0448f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230195 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

  • ;k7KePPD~+n@{W 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%0AbA$yj# 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 diff --git a/docs/static/images/align-compaction-output/file_cut_normal.png b/docs/static/images/align-compaction-output/file_cut_normal.png deleted file mode 100644 index e17133ed2a34070df2b5d31ca809403736beddda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19657 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

    `sL$+BpHsL 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 diff --git a/docs/static/fonts/LatoLatin-Regular.woff b/docs/static/fonts/LatoLatin-Regular.woff deleted file mode 100644 index bf73a6d9f97ef166c3f3e8ddbb900fc8e42d9b02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72456 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�iT<391F-_cRjG_ocX=+oC#)Ux6}hh;a=M>gQz zYCtQM;cq{~MZ%Wv2f9W@LwCM%99xIInL^xSf5ji1GIgCa=~K5&k4L(yGZJjzovNH< zuecgN9PTs&y zCggKzeM8=BCy81W6_}g>K^hA)+|9u_lH{%v1VHM4-2N5e>r=Qgc4r2Su{@S%%3LGl zc+JbcOp)2}(~`6#D)l(&Ogu*?utV6!d^rrOo8Z@YgV^~og2(N^+nt4I?$F?0Tg*aH zJ%+Hjqq(lQ2?FoC%=f+&d&-6!HA4aKTXwhcfA2H*KebEBZQiJTk13Bn(5>(yqR3-N zQ%oJB9@|OUxg2qNFm3ax*A>)adbbT;GPI+f!~Ptn2WD&0`5T`3q%zSIGoYiVPU|4a%w&RrzYxuIsV9JevH(A)U-NmQ8p`LvPUL``{@Ua!9 z5$Xhx0+TTd)t0f!72s_Q0P*dtiKrT9dTW_? z3Y;)R!0J~&M_Yn!PI7k($T7sZ$32G53lP{)t;IQp$P0Yg0NfK2LRxL0-d9W^$Invz zSpXNA&^y%Vzq0xQRlhmLvPs!3ibSUzEHjsn&RHt{KPf}XQdpY6zo~=f;M29iROt<^ zq2#Ip%CP>JYaUKpnVQvI0&a=l+mvn7vIdZARddP5c>0I3-JM^>yXbELFE!CX|6qQm zanoT$%-@g=Q;7;}1*%Ki?zJ80ZW_I(GD~`J9rQ--l*iQ+L3EM%GGwLb%<3Bh@ZU89 zap=#Dq4X<3(O4dJ;QyT|@p2Gp8_X zt_1rWEWt1gc0s=g{^8`O znO7ide0ec!^osFJnr3pW1dpZ*QZ!xde(Pg;!^*>=OXtqmsQX z`cYHg^&8X+aq$`RLDqP&R%r#B1hu(C>!7>eVYx>4ORO&Yt7P@T8C%-E&THID=7sw) z>ENA$KByVE)QtR7lEuybF^^%gC!Da}hexX`J4Rn2jEPz-EvaB^WPHbTYwa&om~OoT zWq43(xZimWL$>r|#-hY-r}W=ga7}R4J0Ay|BK#kuw_@tDL8CVh7O-C)<<@M@9T%qa zh>v-+vKtO?giL{bln$+s$Z4QWbZ%Yn@IU7Af<&`xf?a%)UG#9{fiWJQciXeRFM?+Y zKYTn&?^e|wgMF+HAHF>*w{$Gjw#5A&5#GLHXBh>0h)n&?1t(e(4!pt@9=?i?B6g! zI`$Ww+lFcjDfy0+mb(8p zQuLcsLYb~99Ztrm#6L?qGF7aEvZ!G-3Da^HHXv^Xot}X5?cRFkW4Cv^9)>F?F{3cT zELjt;wjQH4yA`W;rMf}%?C&PXs)7Y+*}25VL2V2-;Y`J{6<>bR1>Eq^qUYUUzVf){ zLl0aEo7wpzjmC*g`6~yt&cy}#gVdsUUUU2*rr{ZHXm-h3A>ZgcA&{qGoN-vl!-;psW5GPrEFAu+{BoQy*;N6wJ46uvO8T2GwMD_w&w#d zI%E}HFY!5%OBoUGv<%00Q&Oxsq?20{$^M=yE_~z()k7k8ek(JSF*XO}{3fl8?9U9Z zr9!tMiz(qhDle(37w4qILluJV(2{dhu7MSn%Bt9=Pnth*Z3s;4Y%1mZSHvsrqW^A1`A-wdl4clY zniYGQG%q?)f7*U;n5GwxGeUVQR@nr%3A<>J#hOvFYM>Memoq_`fYqA6D2&~jyNF9| zrtA(?0Y&6mDUYc4php~K^V+a5oWjfU6H3)V0j0oPQ8=zwgtFU_K>vk42R-&glthE^ zOr-)x1;)gocmwadY&5dKA)}8;=*2w{l!Q0?`h-iHJp98B$s$Br(y|ZM4wp14CDPoi zFt#+xe0OG?|8bq4^=fe{Z*@%-rKOqIw2{}!=dGrvScCZVxpF@| zX4SEoSh{{}U(BTOCPGwG%n=B(+g!r_{1z1Jf< z0anM2XOG!0)>#jI2bG(vpcX-_<(=-$vSG&`9Rhp`Qem%hMEX@06R`$kaN)Mc|sa&?>j~@43?j5sl@{3<* zdebw#qx#)_ljp+T^+iY;rUB=Yu)BlXVB^l_`Fr6?@@U>RmS@J@q{9Rg0*^uJfd5Jf zggnlr)QX3-Zs&M8tQJGgG351n&f#8@(7{&Fuf8dMt%5t!v6~qlgJh}N-(*j4o${2e zba!JInwlicBODJOirP)P=H8BQx^I9+*>?9zXY3kUm(lIF2;KfuJUZB$@-L%2tLit0 zUalx<yB#NJ?F>v-+^o|QJdZL z+jDe6`ah}`I`dn=x_|t*>FA1CyO=XGGl)FA8YGNHwD;O>8J%joCy!tJyzs)n^*YHl z{~Dk>F;x?JnyC?AHt(I*w5;QMqH)u}>$H@0AjCd5CAFzeV5)? z2BDW(nKM%|+@Jen9$#?W@&^$;rc`_dRH+&=-P*$PM%ssZf-l#snM^A@1VcG3G)Edn za*cxi=4W^qBMIhkBn-*HTPG&boJ znBCJ<;6zr$u*f;IKUQq+oSSfSQ=t-yz>71->!G=)`n{gbfF9B5@=8a4Z{0e}+FzwA zSE7qrV@~s_%**DqVf13O8t&4LM;5uL<&%SJb9R4|8pn>e=dt8AZ5VH|!g0z^p0~>C z7Y2=T*dFzVb{}9>4_BK^S42t;M)c7(UA6CIhtMh}f%i`MGshV7{EEiFZA~L}?v7-e z>}eNTiRAbTwgk}8zG`NX_W@dHJ#A_fjB|HMP2!SZsqn1@KJ<8arG=zgeMHhJim-~z zj)Q9jQ}*+I+Uq*a4-G0Et4*Yi%Vah}^2zeX+D6_kzpCcK!#f*OPW!_*55G@_cUVP& z4c4RcySg`&CJ&C5rc;0$9(&ZP;1MsuV=n6pKbmmjrx=tEgsC^o5wWSX86;}Fs4ZOvVWL7UqZ)sfnO;)zl+EyD+b~iWRwC*pl{~ z-jp)q%yOF&XJ2TkfOJ}HmXc6oG(VZmQbug&IW$@B!{{{b=YEwqk2<|$Lq9$qvSoqO zUJGJp@%rdr6Gw{uMAoByhWGjp})M7ux)5GSivxNtd5PE-gbAunZ(=rzB|dC~5QNRRpip1rdOPS1N)w(so=~ zFLQ);w955z-Wjbmv# zD14gwGV#3QY^Wo{RJEzfX9;QK{8ST(;zYN{n~Fo88pA z;%0fr(^wUP$cI(@E4uo9*Q2Ij;$gcnQRt+P(f{|}><0Bioj%5hKmM5yeb%C-M%bj@ zi+qk>N41}qTFOO3b;{vQjhA3mH97xBwY^ktE5$LgI1jV8bzvfz6VV;LCE}{fpPaVe zA8}+8Xs+p)RjiE3QT=AQop;RV$$>b{&kQ&1Z-d^zaz5Frek-lX2h~pIm7KH>6*``3 zlxlidbNf%mHx=~bJ0*wfE)@dv?r)pV^WJVZ&cN73saW^Pv0ToT9Fo4A*NM-rCv0x* z!_qjWHrfUhdAj{J2)GYyi<83v{@tH{c|+yx*6)5&%Ue&HA?%-mEVM+~1ILS4Wd4HG zU5O|3vHT#*OS^)szxcU$vHtAK1)VAJ*6Pfq{9*dp!AzW)LR+BM+2D$<8kV!gsUWq` zMsD8rdfg!(mi>if))A*W9OoV8{Gxhk9uRmMdDvlHy-uvD$JzDxP2&C03(?;+~s*o!8I(s^N@;4huHv<+kTpxDsK08O``gHz&7JJg59rIJV z-3yX0hxc8uWEVN@e~ljXvzGquUK@2cKM&G%eWT@kT_Z>xk+P|7h#IBu)*~&MC(CNu zn(r(m_H+U~l(A-5;d|OqX?<<@@u+X<%i;a1iP;^m^6W=*<8*DKKoY2tEQCzEY zPzNEf7CL%}r=p7YL)J{%K84hG_{)b8!<>HK@7_Y;H6Fq)R9ONvkA@-!$FLyemu~YC z3uU1c2?!we4Rv_jhMs^kbRy4C(LM&Z&v0(i``9wOfX*4ZVjQoR(0pbpny|~bIb96J zna4WP^IZ$J%^B?cP)d>QQ)5EXXyXvw`5~PmYf}L{*QnU2Q7=f<4pmY&62!9w(;5q} zY|3`OsoW{4PwgJn2BR~8D)+(qdI_u@)gaxj_;B5jRJl`AuU=$mRVC5 zN)BKbmgfiqPeRJAhZKQ)M&&D!K>f;+@0Kd>d`KwoS=v=@bXYe3vCP#z-x^DPo-V=K z%%&{mMulESs;bg|ezcs9QXfd$Q!aC%43w!YSMgCxK`$v!wgl#hSBz3f0;yQb*)D)x zGiscLR*u?)^wk>V%H{YM-xh3#usW$OzK!~XEP9!#q|r6-)!aPx>i|>1%4OhGfHLB- zqijk*TEF*_Dp01fT;;HgPD1syy5_r%;zLtlxfodrb7wO1ONGzK@z3(99Nx%qAm^); zOlrK}vu4wi%G>oD{ir0wW8H`D15UdDFD~Jl;~R_A7tGJkS6t_3Zx8!#;g}OeI@?$~ z^Lc*gZiG}ms{=iYeL4^(-=9?KcMH=yP)e{N_0 z{ND1IgiWqm#p0Kh??=?u`Lq*vxGtoj<=!~$eqqidKGk__&a6s{`RkcyOuo~n#IFnZ z(y>x?=q=WHJAJoj4dap1e%V{~5R~l^ne#R0;_;%rs;fq$sO5$Jr_J-M(9yzgP2QEVizjwsNhWNtzz!|DmQq+sK_R>We_~o4CMRVl^+GV`f#nxph@XGX9mFjfjRcf``Wh6~1BG$#_^)e8}x=3n*k*cFR zRmt>nR_f_06~e`;MlxB4baqkYb-__eE2b(d+ck|NQdX)E#qyb>LhonyQG5NQ#Jf}M zLZ@$*%U!IVAX9Zn+Y|-Npbm5`f2<)Kx46~6KM;C}hd%@dCUlKW3$b?=`Y&kQv{}Y# zBBD=4ygpW^>wSAY=}VheP+a7AY5#URmbnsW{rL3kqh(dICiCVmv0D6_QG7tBbu^)e z?obpMhp1kMQ|@G18wDasArA%;hLscIRTi?U?F<{HkVB}MLYEI;S6xH~bVtbvVP!PM z5S9%|hD61#&~beS+Io~NovYVv78@7EMa4>EVI|N(m$RwYUV2AddY6uB_KZFi(3wD0 z;KHil(Q8VG{;}T6rKPZH%9|C1y#^8zRpl`^{(z^q6fFS2Bik&I$5KrhS90njoJbHP z9GVFt85wMuN(4843+|Ss@fM@9xisis!s2KE* zI601#;zpf(O74dW>yuzaNgEE6@(u4di=$E}_wK`JN2{)7)-@y1HDTc&c|FGz+qB~1 z)A%T0o>{dE`(mCeU>+!Du54!>*|%G&o=MGPo;q)py9Y~~*WH&r?b^_*&>&NzWs=78 zDUWC1v$OWeZp`P*mU*MK8M-SpdY^dtHR>yaQ2KF)DNa9-$*%5Byw5IuT2B5kVofhk zb;y-263#Vm-Ptvj?u<`s`Q2vbS{U^W6Mr$vrWF%K({z%@%SGwHK*jbqk4K;~D`5OZOD9RKtK|~l_T)&1|&zB10Y*1IMV+uG#pXnFE zD~*K=VVS@{0)S%*inK@ed~&$68W4BDe~&UmA3_F+83mRSRg^j)C@Lx{2&9BGN<#&d~&U2{ow-% zqJaXVxFxVT>Df6%mEt;d*xxCsxcu#AJ8Y*mTg<2L6Bb2Mu&oK7_kBW&PY=G}CMH-t z+$H0B8DO10XtvTEtJ^tFNVCVNLlqK=R!%ueH7GHeDzQGfh##v|9Fu9rLybC2d+r-@ z-JBUrUPIE0?XKq`nj!@LOblfEk65DXZ}Gi&AYo|s7_=Bu%q?kSHr4b|j&BXoipvvV z-eZU5pGfByUk*zJ@r!2gD=zR$B01C&b7v{!XU1e^Y~ykrqlz71%9omlzGxH76A zcZFrp5YK4J5>`aGiOc?JM+1II==I~?LsIwf4*C^hdJ75kg7I1TB2y1R8>9=_HENPJy_?CazEnJZ23*0qG0cwD8LBcs4> z5ubFL4WC8OOlNDdv`~uonYN`;XC~H3;NxJdyH2a!YVj$&yPb<^BhCkm5ARQBg=qYs z-%7cn^HSEYT%0^Qxv8l;Gd|rp|D`L$JYbg_J<<8S?TqU#X>pZ0%loU&TGerTmv>Mb zb6cdhm2-{5YG>YAT$`+!sL&Ta@aly?dc1GwJZVmoyV-#onKpq)R4~EK%z>0d{eu_;+2K_f-`E+3aE@=K81065YI^B<<;MKO%sAyGKA8tc zm9@%fKI)BCT3DetYJ#L=*s-@52N)5M9gjBQ(3aGk z8DD!=ii}oDHt`Ey@QZ`-%OGNaAFI?07c@V9-Tv4|W)lg8U#f@ybrrv`(+?)}(IRJE z{HW^cN7YGXAO+8YmC&-T^K+`rj=Kt9rSdBnIfVz!`#(nbbMFBGeg@WN6=b2|AqiC%ArO76;q}D;5;jdnd(EKAR3n#jD4e7 z=a{)dpQHi{EeD%uhV!ehO&d;zen7#Cc$%$meWQDKq>=sl<7=azy;+uUqFb01Rngta zX^HP$(uh(}OeY8>)KY-myjh#0*_kKjP;#CH^OH6D&S}?)9n6y;W5t z);bq%xWlL=S$tq|3~tu>%LU&H9czAlWV5mytWB;Kk0>TkHGlguj(l3;Gg9D4xim~$ zbH!kBjL;RxRrKY3I7XnQ^m0MlceR=*0Dl|e#dDu@z-W#~<#Eo9?x#4I4f}z%@3&=Q zt>N~x%qX^*t|Cw_=aQFki?PpZb7dxNlIBFo-23rDPxt zWX{Fu)Px?GBN|qlG@ROu;XT zgkEAz%|Qza?N-Z6d5cdLK;v;QZxJ@W=;r~ZiPSb}<@V1`VZ^f@U)nY zD_uerRcS^)(ZuPZiM$v%JAY#}Y5mMBGZ~k1;JDl#z`_V>d8ikQreoD&BAsNIDx(;? zluy~#>?%&=AG@N%GqEZ&&EEM*M_cP7MTx;6q*N%n3lT1Z&+k%N*+U+_285PRGw2(< zGu-NE)d5MSa@m@Cf^pR+*Cy<+*_vvF#4>0Yl8@aPk&gkFhE-^lXNz}fN^_z^hNwJv zKUfkE+^8#WCy$6w>xrX%9H7nk;t0INebRuI87XK|_8O;b*Oa;3EhIKXtP3V4iXFe$ zl9kb&x%Gq0Js{QyLgrKEIU|tu$A8vX|1!Uw*ajQV)aQ@uQ{PE*+ zrRhc9*GND&DWjn)9GQq#T06MOtdIPR!m_}MJu`Fkt*sUC0+h?33W+UrtP zYk~lsHTk)1iRx)6)7NL-Az?%6ZnlWPN>YxG*IZcB{GuGDNhyxa*1V?2AdV zJ_O6Zd%hX!gpm@{8@ul9FMgDt`-H_m9J9^V#ZwC+_(t`6>Z*bXS+%X;YpKH=KJIu^ zZKB#Z6vsx@jN8-x?e5qy22TqXaEx02kgi?*RD=}=p;BN{M4L_xY@PtP#L}*-i(afR zlJs?Po|S{8&!7l1>rLaP?t$OH8l8Mwy6eQjuxP3W_i^3u&5j2a1d2}>lW+Z=&PCmI zy5IKzuP4du=QH~HVO-bQD7PB04s^jP98kVO)0hF3*y5HbVy2Nd0fd0Q<9Gp606FNq zimo#E6~O-jS3s!0dS{C{KEO6}80;{I!7g(c>@nvyz!%J6@FjB?1k7O&GRFhB#~cRt znZw{K<}i4`oCd(x%wg~ia~MR-VGuLN1=wc}gAQ{TbeY58K+*M4m=AeD8$jP`bPMP^ zT~AnSV)4DA8zc4)ywL0)HDdNp8Zq@}MYR$27hY)UuNpD+H;tJ3P|?j1^${;L^>>Y! z`iDkLeXO2NbkO z$H7VGWOF!*9MYZ=PNa>|C*-8>N5y-J>?^ zWv09-X|CGLcyqL-_a37?J|;0E#-1ft%hf|Slk#NYu^LTGxGa}CePfoBPT&w6E{8)G zL7sUW6^8@JL}20aNB}vqkOoo$;sLOePyHoG9i&qKg?#LgE|S05y?Qg(s69kvzfLHL zQeCm7&gs7F{-;g}+L$x)Fo+D|rI~Z|E{r$H53dAacBDF8{`i4OLaLN%drA&rPf=^g zC%xpp3!iFl>8{BdLo_=|8D~@{$7#ZqVwxu){J0o;OQWwTNbfO^Fmq#;Fv60gGsp7; zd_K*Ayba&raJc`1ywRKx#G3^01b}Do@?%gL6zGpUGhG}eVP2zkU}z}W{o6}HX59he z-z4K!W)g|+zh*(Y|Bfq(bRT234}z`C!%8hZFvvtbz-Txm=Q$3CItyt8LA}OtIMGlz z)b<7~Veou)cv_zq4zLjzWW@Q&%d4UW2bs43r+!6^`GFG~U+K><6m%C2K3KT!%5sK# z#9TZ*ec?JrO!Y9DsJto8`|cS??by-mn9)$E3V(R>ltc3}k^TW^`taAZgg(njfEOtd zSn-Jf(v>@-kdk>AKpe*dzX`XI9OEGa`VEGp$jv6UI4_gfiGI~xQbwTYQviL zh0kv)uiErXZ{Dotif9>#5l`+}JohS)JoFHdT$?+yp*2Wsv01A2|7PRn-|VZDMkX7j zm=ESC=A#tqKmw$|BP?ux7q)+UiW4fs;z}fL!<;=uLrdyE8I(<}k4&$%#g<3NxH4|A zCPEb#-%u61=kteKZ^N@JSoFs9K7N*>#2J|yrI1Nna-M)^)R`lK`UQ($1=P`rMG+&L zhHXmkgX$G=v`{o9!KGlM8B!>#rY&I5#5fOz>icq=qmxOqn>cnAnBd z1=(jjq$h;bcq|$8N5zAoUE~sX4Ycsz3=aAXnuDKV&QaO$88f`__2iLf4ZP7EnacL` zUw4}qBXI#t8QpoA4?I2AOKNaTclGcZ2k8pn1*T+z|AG}nmHXl~atU>l5e~W^fX}0Q zr$M_fg!YEKjtnswW%e3YIDst7l1PpimeVDJVq^olaNc+OpJ2ZD$(&g)gWyLS_ph3h z=n}_jLvmVIOkVN&_VTJdzqqgKX!(Z<4e_-5z3M7#`KN*8>KtuSVO)z+D3!!S>uL_X zvwq9(?yt7SfJ}Ap0uS$W9;;`7_=bOW_%EGzZRqB;Yb1>v6tUC2lpb)eBkn`;5}Z)@ z%s=2WBnK)U`J-r)=TX%7=yCWjwQ1-kNrRr#2+^ri5Y@HQl;q$4zIh#?qa;f}2=h7m z^XU+*z&JSu$O2e#?HarSoSDoy@JskD+`ml`kE7cX{L<5wj%%S4HsCl{3-N~xe@#3L z@gqra$G7AFR}O$U5o(9bPCT5s68V6R~TiO`E9HG}1o%!vx{XJta?F91;;EFon5WX`wWTNY|>|eONz`9{DLAD>%_A zqTd7 z>6^oZO~fzUJ73>m5ME|XP^95u4~^G2Ml=S)7TNYpjILI zP_sa(p+2D!?A-0=NP{WwlLN|>(Hl_x<4^*uKn(hougmmaNf;iVDmP;gj zGMgX~@?Zs(rnRYt;04JO2i<&Am#ikg$A04o#)ifXl3GHb4bvIiiH`_i=GZ|F~;KTvQiUZ3sf61=Y4Z=|6= z!Tx)Y8A1*fP=h00e94tfWo4VLEb)5sZ?Dardku(Bo&@68=FNMJ`S#?--|VZd-uIi0 z8{gSiRkiOO*p}0V4?z0w!8kz8NrN`RntiG{2};V6g{|oI?ocm06oV+tBZHv}88{KV z^cbR=pVg9euo>}DQB1bX>cgzgy;9qy%G6iC($|j}Uh?MTI%Q;J*M}vLg^g?8EUSx) zM@(&bcM|#2namfDVa`*MwuWl~H4{W$#QbMC7lvwBoplUf=OI^DpW-2KI)?d^|#vHK*`K(@PU(O7V> z`oOyzH@wVGylUiWBs8~$YWPt9kQ`CcZ?O{KoCTGt;=g|G&m8Jrn$Lz z(-?D%t?nx!e&MmQjUKDJxmBqJa`PCI?0)w6+9Zxohwou|P5$yij(kqj;0;RE8ltFS zU30>tkN%dN`M5*N?37tz4_pQJZ~f`I9NUx)lcPh4Phu-(=dNAv{=}`1B{(4}_c?mN zHT%Gn%8k9{3RMjDVFklqlc@jYdd3%@A#qjyo*)v&U4+QTbLx|`aCQdOimQe`gg$jqOdW{Gt+t(#uCdqF{E8Otkk6!Y2^ zkk?zpuHdeOaC9A*CJz|J#%zAC`mSYbJbZ7pI6pv8yMpSPd~1=R%y8VN*5W)3MjFTf z90qDFW4p>EQ8TIk+zg`ra|+)Fb6aW;yie*EgEJ)nR;o+}nIQ*cV8_f>->?n}Pehnvxk6L%muwSh?@Y{_We|xazs(r?;BU$qf zwUM)i23Gy{XiLk{->!n+kF*_{myPf-4`Y0G&v?S~4^<*Y!k9$TgP#pGXkC8)(|rm{ zgkmIm3BzGck#o&kJ1Y>`R>TNBFf_2}wFhQOg%Re25KlN$fWjQ-G&9wH>WQW+I|^F_=}SZI#Pl&d2@mFdxyF3VuZf>-h|mNzA|g(1jW zD2gRfh`{9mG;9f2?z}-6HT0@l%V*Z|gP83>8s#PMFUE3JrR0Nq;UG7Iua>^RL~A5O zTZqj4D~X2qh?S5t;6v5SipOtVMu&aSc+9Q^W$o|y%$ z`AHhNP5|DZB2tRtVltv-il~%GqOYztt!sCqd$~;85uI*P849|KvlceFsG56M7-9_y z3G3tYnRDdZn9qhWe0sVC{F(?RU^K}m%&6dh4EuoXG-`_@LKI9-#DAnm#?jfr7^&Q! zqh6&?vs)FkOcq$Kbg$rJk_(bk1arL`P}U$$2&WxN>ltCCz{kAq<|TL4rFWLc!Ipbw(enKU=lSLJyJu$wYu($a#H!x> z;`VGyutdg9GBkJ2Nxk=_w9uxx%QA}>Pqr2u@K*}an7^>ribn5+w8=FouDLdY^IBbVp!3d{<_gJ}cIhw_r|DcSSDmetMcTzMyZTlaK1vKBqm z-L=gw;G36R{C9!#tv4<|SvzS{c}vl{=a=-qwWp?{xZMm1z`oNke4X4z(Re;-{Pc`H zCBo05@W@xq50*%y993x*i>oYgRr8DT8(h(HwJ62VFk_Bu`l;nbxhtMtGWS$2nJ*M+ z!?ljC-7W2VX4oUl5wiT)42!&E)3sHLUflsL4%3HYGooL7yLlwi%RzuyBzua6+?IZZ z9erzTu@&LL?C7hGi)*NiZ9XtJ%fnRC1l;c~E$8P27;9Jat2Vu`lw~(>oIJ3tnxOvP zg{qWrqMj8{J>sQMmrvn!c;M>vGeGWSCb%ns69Ed8M7rD@h@? zn`US?voSB$vAh6N0A?H}huvXU*-4{6PoU%=;wtYEf3E)ZuL}o>FAJTozSz0F$x=Is zY`?iJdR0wF-kO)!^u4^jDmTr&=#PJdJk$?=3vG8ZjFAi2sK7NcF9X6!l{Y}}umHzD zp2h6U1!hYk$VG#IOQYuU2utab`b09=Q86>8VsWJgWp# z_I!Gxsi6Nt-?T^8ls7hSeJOuyn%pQXiwWh$Mde7dZ7b7d1|jQjC!=2+oKE1z00<>;b3xz;^}OlnwKRyd<1Nv0MN zy*aZgV(;I`L?d5Ny6vqEbDrN)mA&NbytuHA-P4mIqM&iHd_cd?@=@&1hhHcZh#3?t z@kY<#_3$^j?a0Dh9*^gKXpEaJ5=pH!3-TvJ zmVK_+$Ge~i@?Obly@h7(SQs>wI9{MJ3Vrj=1iZ*(Q{Ru55w}5G2&Hkh=2zW`#AL@P zsj)54;q(if)Z0ot+BlGK0p&s0g&F<^19=a~?|gp?n(blE!xQ%yY=?0?`6(UE(_y8; zFNtG~)~D0_!C8~j< zxV>d)Zp-C_+C!YjR#r~7sU~E7B6VU}xO^_SwJ=!8fRhXds*mYc(ERRbdC-7PAfEMX zo+^8%PpvzVvGj?>=2|@uh}8zwg%d>AMW(^SMcUM(eK|ovQ-k;fIB?KD6u~y*Xc_>; zfo$XsyogPHbcq+ziev}7RXWL*2M=QG7ZBOBWzW;EMC-4Mr3dK)dpx-P>EeD1=}@oG z&%rz?&R-g|l!d(Th$t)vW{VWf?<(wAvcuaF4SHQ^5i3&GGNDG;FW|{msY3ccJmVp? z>C9t6GlTgZ^ER^_Q2Y48jeMbEqXO{Z_>bs60qL7Kd%^dZqf#6PAoy_zM{}Zu-g)_v z;ffw}4Q|0XIawZw*OcY*@EDCIHc}4C{rj-yVyRUj7> z_Y@lui;WkVoWTD6{7Whg!-%DafG%TN;cP!1aUbTT!Zu0aHlqa)Bo>KYWj85qt!8}C&#E! zj~?$7ethkYbaBUi;`Zv{_;h0M)5$?gbM8||4+E6^d(%=Z?5dx`J zQC%>RW4`&@?A~f?P*8_JK!8bgkEWNw7AJ5#KxA-`Q@35f*NHC&P4C$fXp5Wn*d?0F2qi4z0JMCG2sl^2}zW7hAyRUQz4D{mYy z{54$+^ZsSbIZ_xs zPxY-1du9efY9eKk0Pi%aLZy@fH9Nn+g1IB}bxv0z@T@X;Z3%qotWOJDG1rIt%y_NNRzM{b7zEOS+VT}m zi87^C6BQjy3DhYqCHX6-rze!pa8%5WP!^`Elak_s<94){uV_rvqqU)3Pziseui{;> z0g_}p$QzybUL+Tc%Vw`~h~Y;N9-fvX;Y2}9DEvd~5ok|`T@eT`5w%`qkliB^-6J!I zR)2LOXbc%{kU+UhF5jgT0O?R;0FoRq{4J#UKk%zQFz@W_Ewn(8n|GoIA-~W&4?8WPW9&6OG=%X!QPr%9J>wmqetOo3D-VU0N&>@|Azj3i zA`n$j?u!xlm`VVo9((?m$0lw$GJ?EY4|ep`iHESOtY5^mAiw`-TI6?3?*z0^iy&I@ zuwkkpVYCkcxgm(vyXTIm4n29{8SSwPVd>E4#2;#UL&YV;O!r4P#y?{rP&z?m?(!VQ7{5#dIxn_YlZD)JWePQb2rWq-mO1yz+ot#Jk{yv6d-YOW^`7(X0$VaG(GJBefb|*d3-p>Zo6|#L?fntO_Qw(Z@m-6 zn;V(~l4LjERcXZ1*>j135%L6DC!}m=jsO=-@qeQhUZ5UYw`#w;2m%fmxnFMh2b z@o6?q2~#8fpdPsEpdNxaI!@Kd+B6R!6?j)A)xd8dhFy#vweaRWKEgH8pG0IeqvhyW z9+RvR3uxXSe2A-0T&dCMN@+3>PcTQ+rZ zLH*8InZeqT1!yMb%}Z^2W~Q<_1-)q5886JBm8PZ3`ue@9Uok(mG0VWNO|$C}yO^_- zkm9jxnckHIcE!mudhNkuU3tk%%XU9tD(J{=-ry4CJvy%itr#vsD~5JtT4?6~#m?Pm zDRX5_N8tvvin*&UH;u`oE2I5r;x$XtSelOUG)Gg+d#Kk-aN0)u#fyIAHkG#&Y`BWa z73VT`UI?Pq#GEDH^~yJHtyVU=ja*JUX|Ubvw^n;0_jIp!NwjmjLlk5t={%Qrt(JK2 z#wGA&`4sP3ZAor*H0uY5HzCas&lnCfN|jCMhoV z=+zV%H~Sumbdy#T=Bi6|uO+`zhq8X4aQJId32iV7UT`PgdPj`iwMM)z(n`&)%2aEa zJ=9u0t#ewrHD%`RsdFCBisP$g@|g7c?9}R1t+jl5=k#)`t$lM-*Mk{kwOSpj(Zo0+ zEU9L_AttLiyJT5&O0ly@ED1A)g(hYh<6IVfWNglq{EWGEwp2vatLDStV1)C;`1u<;vKb9YaQvGTdQY0h#EyMk8xHw<4aO>X$_qXY1YD;LaR2d zHLqwvZTyAewU_AgSyLC-vziK0Z1zY?sx_&kr@DG&qjjWp@}kp9>~R^B3mi4M&a6^f zWPYk4u3=5XkeU2w_PLb>Y<@~S2Kk5PrSPUXgl}kM6QXyL+t$I%g$E=dk&47}dl+6X zp$c&}(jEi@6m42t{>^LTG!e>K*fD+hYvKhu2aOms%8T%k3|HX93B101-O6C~nXw6x zLNcg7|J7@ zeT!OS^s1*X_4Ou!BRq~@Iwv@B%sJ`-90S)wc5e}{-;Vb&;XL_gEgSc0J@Nd>h<1Bx zS9Mmh`#-rhzZ`ejdY?GN6&&iP#Ntq&f9p^`fS78KhQu5_Day71l@8nAgwMv@P7o^n zg8M%cX`Hxn3&P&^E`+qQ0@?a4=L#n71%LLL;TX*^vA5_Qfu{ugyNril-fy6NPWJ5k z@}ESz4U^+lH^z+L9<>-Rz9sX;0R;Y*o!z1HM=W-n_9tu%R(#gvSv%)Ru$?csQ#)7R zt=wa}0C%!~ZU}S5lb)2;&IogCVR3jmi}osGqE5joV+!%8$FVSGDg*KZEPJY95o?trnsl#XhL~=e~E(R1gOu^1~sHN_u z(IqBRNi?xDSjCP_a6IM`7UItX|a`C#%)(-9HzEhHZR-nRY}IN&{)IP!bGyT)mw9?#4&T5U`)TtdY3a z$0a7k5vvjN0IheEWe_Kt!%%WZZA2%_#6tJ`M4X!ziHI2Yhs0OpSw)oCZQfBXHU$$O z?nL=X7nw`6)8{xsl=*r$J3H@aCp!3ZNk>+wBgS5CVji)toaAiC2+xws`J#}dU*$c5jyj*sHAjpT|z?L;?jo6lN(y<>*=%cwM$CN zme$3`*DWn8T~ZtGcK3F5&7R%W)yvxD25JWp3~2~P_U~9okYI?E+~1?pNxq=Zsx;Td zQb53a6SzTyk(Y(;Re1IhSilzsqwLeQYOR!cgO?SgX1=5)AOhnCMu_{O2RF7S6qOSf z|1Jqt%{eGBMABy&Mk>CpQNeqIxk03g#qF^TWDqLmvNTM&_HQziftSU6Ea3vVItcSU zjoL?C!)LrX-i-lhD>L32KwR^z`qO-hUHA7a=hHzHyPyv^4N#VM@Z9p4nPuXg(UZvj zlg55@Wtrt?~iD&6;936Vj>9TvQ$z8L{N4SID-&UZL zpWMM-{M#C8C-D%pOO+?$vU`tep#}kat7hkit&tehK3?A<-MU1rX)wv}!_&gHM)5}h8Ssw=;5JJ#15 z)Lf#K{($vW3cq*{>B)tiD(kM^h;)_3#7;_!j7*yp8&l?r>~bbqT`p^qlQzW_rG$p2 z6vf3B*+N5YMX?EKX$jV}G=%>*^mU?|SATo_zlpFHnJp!$5fP~+7ITq3qSa|b_-#(^ zNlQT@j1LPemOS*CXYs@TBDI4o!oEu3@2w0Yekpzb=VamYKR+QfC{C^40z#kCYBnnbdL^8qJ?oZS@x=64>#ct2sz5!KL(!Qg=Q z+t~w0S&c8mBO-m9b+qxB#`ckz?p@C&oQO~s$;HfaIP3^Rgv1%4_{J9+LizeJ@)dnWg0(_F)S%A@Pj@-9sjpa_!d^@N zjeZpEqm0JK2pl=2|Iu)b%77w#13Irk`sYtPbOiEmQQ__%>UPY^sM~g_tLxIXx{O&n z>fAqsM}hFDDs%g>MS1gI9NM#I=*9VYi;lIMtD;VIg_%B*&Ny&o&YCN0a&zH7bFLhi zA^pe{*2SkImYsTQ@uBaZ>gagt`$LQ0I<+i<=Cf-bXQ_u_Y-RTFh$=$hHnQL<11Jsg z3(Mgb>fzkiTK}C6EYKRGkIEuT=;&N)fb!-NWC2tBQE_|<62brBV$yCli(b;DWS`~7-a!41~$R=<;n_bU`iU^8`4CRtMGBrQn8J-vs ztW7SlUgwDfJPB7Am7Hk+#m=MxwZ$H8oiV-5JUq zhHbD6!yl8da0&4xif-P3vY9xKQS8~s#|Q$; zI+J`Bl+!<=`Xq`(7^~r9QxZarKF-C3hg9Y|r6gY$9i)s93l-$ErN@u#N;3xO`H}6t zeW@w~+DAT{d8D-7d`{B&@+&i zcIFx=0$j{XI68S`F+cIhjK zR#9?frOITa{}^e+_ddg%0(rC>$5eKi>6slnX!j3i*ckA6<}4`0zwy{#(MF2rckDp- z41LD@W$3S*vGf!;p+zpTure}PM6_1u(?s-DW8@8s5fzHytR_UnT;h~BhFcB3WaPxD z!x+ycLQMXa*yH`}5*a!45wT}I{%tKGrZ0^Aww6x0@wWH3eC89fad--@dj->9vT^92 z4=-QN^7anrBl0=&4b&&bI*NBPyS7wTZRyU;?A}sUxuq+Um|V3LU2UzZf>-cY)Th8p zV1W2M(DMs5v5xSFw3v{Pn6!v+N2~_A9`BVC1=~*ed>ivVc%Lgpa|{ZlT!YMs9nW0I zq`1U)^LvAXr8O13`QhRDy%jam;Naf*#67=f<~2(m5t_|w0wT=DrQA1_S~GK4?N z%tJEeLY!A1PE=_mm9U_$nCm&`$g?C8PM1>-4D);W6j#h2!ORzPDL%#x4n8Z@$i#H= z(vLs+e8tMYefsed8!eWFNS_4<5&mbH4+%4DcQLyMLyb0~8qfd)D$lAznZ2MdR84RF z@t0xB5?*+T60yO_VLk-sJo#yi$uU*&z}9sk%iXm#AZ127@bB4ZpTjD@9?> zy8g+r!Sdjef`StG5j(lR&KV}6w{|zB&F-9;5}ltKEf7Sd=0~T@?3|s}+$C4mCN(TA zPVIz0=GM)v%}bd%vn#cDX+vU-QjU2ofV`^5@fr`Xs3Tl?4oBi*C`f#-y`X50P>(R9 z7b*iVF4T`OPNdi6Lhd>d7fH2Bc~`TS^(cWL+RJ)Vw_I71h*kTn$QdZj9c-cePn+c#35884;c_z3PQVVh(9DeArBJ!bFw%g zhHPUxhQ8NmNC7yJ6=4H99{60NVM2I1V2jA|qk&G+V%vpkU`atKv0(-#X&|_tvL0O-;$QwQp(ad@zIfy}dNu5uvL{ib_i~n@q~^N#Gx7$yyti7GX|L zicfTvwrAJfXO5m(JhB*X3e#G1%+>;XT!Ou1YF^E%DD&)!jJdV89Cn?$WY|Tgz<3em zU*5`d-W<$8r@Vih`N#3uXYR?(z30qqGLGilaM5qxaFJ89dw170?dncLI5rZ`Qxsbl zjh&w}Tf8&0z)n%?Wm?{6ItfoM{E(|tZUq~c5YH>5;6+D*#-?M6B}(Ei>{zfI3{q-1 zNQ91^|Ka*3fQXy_vAzx*)n6OA7mH6Cy2!hEn*&>Y1YfocqWD zpu@G0pgrRSRdW)M0bMX18Xl^rgXsb0gQJlqQ{+)#MB_HRpO^*jH*(&_94}nkS%(Yc_rS@Gc1m8WO zk5cWM_T7QnftBS+4U3DiGaOxeT5Hx%b5RQo!Mup%;>47?^zdMl(+rN86M2MEJ|93I zbS>?~wEWH@Q++Y>xkSBx%3SBHgHuaZ&uIv+-d8>`1@psaquuRy}f^WS=M->meQ>LvF- zH+SB1n=31-YD;1#b!9X@v7<$$gzeise4VROQ-ourMv3D}qkI3R9!174`AKnlPsO#Z&Nki1*Zs-CZv}#D0e~T2Jp; zI@xNSymZg$*3JKTvO96|y?f4ZD4+a6oJdXvrwR8yBnPgKre+-;noUQGLhRm7KS5FY ztMF3{4iL3`G4oESHY@}riTP~I-bz}CztZQ}dE4>xgrj?0Yf8(# z#l_27?Dm#r#l`owq^t>dPKt}0PF+&c2UV3?3Q}FK1@b@j`hBZImQ>n9+Boqn6=0x)wue#)VWsE8~ zT$(1+zx}os%AEWViNaPoqbRCm%iR3*1t(?&flzgUc=kYMyizBLO|MGc#LIrPY=#E5I9BFU%7W zI(0#C`>{p21#4bd2Cr0w!Sx#*iF~O<6lu+kOU-S_GzSaiG`W(S-np&u7Old+BP0G0H1x3t{W%BTEE9P5iUZo^dBUut; zv==7=gZB&^)nlpOYfoo`Ih)STkeUMK`JUW%{+F2{eYLV4(beippq!~A{|ij z4chyOMLoT%b1~e6%bBY80(KvowNpAD;Ujann*jl9)Oxi3NNu& zQkZZoht^Ybfn8z3Aq-w}C+%oLfkzVWeV_k5cu`D8o44m*)N8qde>K#J)lx25`h6)k zQD57*KTONz|04f$bjx5FH$J?!;roUfy@8%Y_A!^in-%&{=CUC+ooN**!(^b1IUXGW z9)SQ*QmzjJJ>f>C4V*FSnd2@xC(n*;3Ruvn*7&}rbYeOtF~CErP~zAvOH z5w8j?y75oEJ`DA)gZ$&Ft7`V(Eo@ld96Lmq1bow~1P|{&cwcWEDhif?pQleNh>9tm z=5)1}+JZ!}(NiUL{e$gO2A3At>gQ%Ln`7$DXN{rrvK28h@XL(3^A_gjPS1&mOs`Hd zY3SgYhkG0_P@Gn~ZPmS5%o?E>^3yc@H5CH+De#}o_0_9K{T7CO8g>gi=Fu^VSIc@y zZU2MqQ;samG3B?X@7z_f{)x`T5A~I4tHRU8VoOnPUB#jbb9nupzV>x>(UzjAPUlp} zyVw#N}X_kYM`3gv}k?u|-4B`01uL(oKx zE+^8R-V>~5wty8H*%b2l5T3v3gSsFf&O{Uo`j5}PH6`!x$>muTtm6Q4`lj_gz9h3| z$CdfHOS_v4XqZSTn^9P?yeZLAb8n6Fp6tFQ#3M=ROb)p!q+sdb)Wy$lDAC3_!z*|75Sr<(WYU324vK6?m^g=vB+*j-Y#(#*SU%nbezKwy-h=hO4;+ z^Qz;(3AI_~+Fn(+Z$W;^n)9<~J-R$!6Q5&YPKj+1r^$a8%`G=4Rrk0G=U2wYSI%Mh z`cz#)qO*6PbK2>ZB}FUG&B|Fgx7Wq+6`nftvmk?Z;(a`q3;U8?=Iu%@*lu>F_W(Ul zAPW|gmJjANHa3Fi6IyvB$*0m@cCW~Qko=4rz@3ipM?%Spw>7QlB8OuR#wG}!$j|=%yHUXwm7+j63v+t?yO3R zDbC3Vf0)b1w2Oy-APOPvG`~K>Q%~GsMwqg+@e=YIlI``Gk@s$nE1qIYZ^(|9y3`gv z9jvz+Ba$M6WtRM;=F$wCEnO6())8VyjU!ZVFN#@fjb7<$NY`tV%aTF_0$ET*xJnrj zqY94?lQe^@)Rfeu7^Rp(?GDEgqJ#J&jPTK_yCc;_vRy-Ea(KH2*uuRIej*V~p2xhc zP?ABQxgQ9>iLo)yL2uOw6CiIG)~2sPcV zkHmWE=A5UZsNJ{@zPEU+U1p%7nA!W7IpEMf@Bq=y)PwU|z++4!YX^sjcJey$iFf|^ z5ZOL-n)qZ3{%t+yeTql5;P)2@fJFdY0-zB9@=<1xIXWG5F^^0Kt)RJsISXd~8FVs7 zJ3tp`?O-0kFlG?t@LimjC=<07pKXk1X@D`&w`|OH7x~IiKA1Unb!_Z4H=4l z!RR#z!Aj!QpkSIS6@C(=3tkKMmxF!x$+ST%uD?KBpFvz`E=q;-nP}+X%$8_$VuAc@ z=w>iNWYyX&Os+DR_-Q$_U~RB2=o6uoD^z|Oq?LKz%h1iKh4yj*wv`mEr$cK|jhJ#b z`SMT!`7+bHpP38p-~Wd##1~u9wEI@DoY~4g{{p9$e1+NzpI3XHA1~PpBzNcrFlcO{ zp~-6Sf=)hkgXGF}5Z$W+DHJg19bs7TO3+^pR;&yTW9vD-3Gx1F6y6ajVH$zOd=ueC ze{vmoL7f~LYSS>KIyuDqvvOt=LiJ9NluJtmze4y~y&VLZMqV4rnY8_9 ztB+rwGQ-w?(1Gw(GZd1&!#53ZbSC2rP}vpUbNDw(wI$ywsBr(fLOu&lqN zcz$KfqSt|N;3yFPbW!T`O%Fah1L+_Sj?WU9LyJ(A3xI4E#Uf@rxkmd&Fs{;94_D(| zjz`myL0QXYRmB4Xob$%h2j*tRm(6f?KK;Pj6p2pKcc{&5%xz7J znzVjKnq$T5yCVcrK5VH49ENTnPvBU!#dSGp zvydrZZ%9&^%?3x2a7VGlQtSx#etO|VaB|D4%F0!(He+UGTwGI=eZuVSTIBkkvTHQeNF;Z#y_U zz5KwR4>s+aol&{%O5f}wHUjMEk1tJ&6AR_S`&Ccr~rAUtIcQX%>Uvj76%z;ftc@oSpk7Q3~ zx^hUij`C-sg3h&-rt@z+Dxx(XJih}2~?XGim?_j~qFvx;ODA~BZ; z&6rwnv$$vfbgMYH5&}SI=F|ez#}Z&1_B-xVC}tJl+{&|oMlrH+oBEym4~K3%78VO? zG-34pLNQGX`QT|fOu>EX$6DerOo-G{rrS_p40Y-0Qg)AV33HY@T)cZ8&$FtmK^oQRURnDBHe)PnZ$bWeDBntp?9tP^ip>87E{Tg}nW*uKh-0R*h zR z$tBDX;OHfMl@y$3x>hk=;MgkSB|@vuj0n%vyFYV(omUW8?EIwI1WbO?lw(xulxf)Z*H zY62nQ3p(eF^Ub;Uo;&w`=bZolInT3M@7{~I?Ck8lSNp9uSE$BdZUXS2>sBYsFij=t zmvZ3eEZ0{pL6*a4O0Ga9N#*#&veKo6SA2^q+jNqhH#OoRGs8I?bIUe?m6=ECU&1! z%TmzF`AjlwM71cj2I?myVaR*Fcvs*7A;vY3+j{95#GSde{g;+HXV+^1T2St+T)(@%p@u5+aUS7V*2nq>$$N2CyG#cRf;ym`!=XQ0pYDf9 ztv~kY^J~ANGD4WOrSElueF49ke;f>WOhg@yf`U`94b7?hIX+S5%k*KE= z*vNM&ya^y&COF&T^Z9jV5A|@S+f%pU*J3GS!)3eR(3dYm!MkO{TI3#M#-p!aFPO4$ zn$2Zrj%k^Do_6|M^wo9&#>Qc6+4U=-Qt(#=V)K5tZu!%Wl_(xk+a*gCpL2{;BacZDpRJGNX1uQg%cJ2JmTvw@ zDt3mRzQn+54{$f_IpOx521dcV_;(lc0SiUmw90XA1Bdz=o-|Qryq|G=A})5@$+;o# z3Gm|f8)*}3zq`03_j2(7GJV0YXqA=WAyq^hUk_-~nQ{ z0I{(l$L$!5tr^?BuLLd1ae4tH8gZM|cJ$12nxAjUlv>xgge{RFh(Lq(4&rP=O42kn zwp$SmOK&LyNe@uE+QY@}bFV2?I3DzC)LBj1aMRbYo)j(bNHI{)3Y^Aiip%8Gz(IBm zpHD}&(=#u+d=fMqdsZXXVWb~>#MSWSRN|4ssi%phw>Mha{W$`^z(8Tj36y!qd^z`0 zx5_Jbnbvs3+ehXgTE}w7u$#RPlpQ4R+*=8E;V`5eJ#Xxuz4)qhCp)u7@;T(CEf2sJ zcn^AypX+Y$0GF`!-9bs8ocQSh+2~c=T8b+~2>0W)(JQRw;30&mbSUN_D+tTKaBY z=c}txbUM6j#lJ8WN#-?(LjWtKZRqfwmzT;UNCd~uPxw;hfk{YXmp_MNELlF-fun5q)|gWauMWpnF1 z!NWM2E^_X7AW)He&*ae%O{6cU|GA=@Y^{3Qu}5DhYy9B{VQ1GY+ulLLHC>yi1KHR@ z!0u@Q4FYXu?G}h>%M47=v8jFvH6O>ojc4;wdm=oS{4uK$fk~1>!>yGd2fdvL)WRtPkhf#^1F|a{lgCIzTn!XBK_6P2 z9I;J=u_VCqvL(8S{Ehg1T?Bk&M_G;5L-O7SBOJL$Wz0W4daO$sNN9TpSn(NVK-fli znEyG_m=Ji?^VL$d-h^%yWnxu=mhapzlN~f)T3j=VZU)Ua)VO+|^5%Gk0(vWlM6Vcc z?r4$+1c)avLc8jp-;d>mdULiid?a9sAX8x|Oxb!VK;6)O6S9M%8GTjdHod=Zj7= zr9DJRc7m5)y|j|>bIeVnBWVkuKYPrDIxbg7LhA%dbXO&2nOM1lH~@*kLS~2tIaraA z)0kz#++pg9y^aK5cppNf`=sx~bDDtjka?AkI(fAE+n@{aM>Y^*S=W>JJFz@qOVJ?Y zw`rKYL)Imd?LC6->fC8LFOq$MI@{}H*^tv;0;UmoHaj!-{ zl<{bpSfzv5AE}tX%!yv6uB~=G$(AmIJu6#s%Ctm#U6wy*#Ge6(bg5!3os$VFFzMD0 z0OQ!be)4v&i=;6i^8c`P?*n={H2`VJAsNWXoU#}WF&eeyJl96q0pU68jL-mN0Wv?( zYxh;i)F$P&uCUi`py=)#?-2S6GxAf7LsBMI9*xsPUgbQ&|N1aVHoha@@0N~|k6fbZ zXj8Fa^&Nux<^k&_p?5UAx??z8~YEZ@GEdFou}G(B_qSU6<~ z&J5$l1r_&UkI`+*lW%B-?r|k#~uin#_oUbPtz7k;;cB=0R-13oe+Lx@;XeK#~16*jF8LM{N zr98b1CGWw#E&!z#B%rU7IF2(>O>m$QSB#^a(pyh+iE*Sa_Nub=(O0NJ!Z(RYxlFmD zpcjhx*n=@5R_{B8cRs}O%gQO=ECQL_ysYR^6qotNQK0^{Q>41L2Du}xAyU$u~R??Q@1(L`)5$MO_N8SNBroc^c0 z-mjb>I^YXiAi@wN0BWb;j+@m@+RXPsDv z=k;gy9a(?5kssTx~5sDY2!o6Mmsr7X}7R_e;jPn+DfMF~<( z%VXNza)P46$8dvP2gxRJ5B&ZeEOI410Lqf15f>HqN?-{p-`8Kn@#@ukw;W3=*)<-^ zOYoMmSie`X9I^`LtQH7`+o6#d;a3Z@0NO09T3o5dxmFCw>ZSj+IGfbE8 z>u^?Xz_=F0VROSvFY39mF<`pj#+wELJ5+p(4hQQBwLac!ehZHJ7{B@O>8yme<1>kr ztZ?dfUQyprvZNHZ%lKd0RbBMfB$zB>8xS2(1SK?JUrLaSykJbcIl_9LqmW-86u)Wo zRK{Z7MX%}GA}h;epWZXio_H~0+7J0?)&2pUT1-HoU8tH*V}HM!E6}RsmQmT){8H`n zx3$L1u$2zl0Sx6{L1qm#JT=bqq9$1*xhy&|n|Q!4i_YgF;NH5}WtI5X-d|IG9 z$-OFYFwjz>c`&d*QLj9Cu0*p(NvuTZaM)=n#k&Kftn*rI3QuiVcYZR;kIquaXLB^k zhD;Z^d?9XdOKHrY(cSLbn zI1keM#9H_^={bTk?)Ud_J~KC8Iuwo#2?0!jnogOdOqhbNehl8Zb?(&_qUYbv37817 zP4ZwPMxyqx9*HEqd?U1T0$1wio3j*|CQ|?kEPAgLrqW@JO`8!}AaN|0xV>O~=L4qD z;%eHrvADD=9b01tA))kE>DnX)&VrwUw8!q15VduUQO1mJ;z3sx&1Ur@}VN`Wb5YZW1>n{0s6_0(M(#qb^@b1 zC&pUL@H(-H_9ltlhlH5;fO` z^w7t2`g}o(VOCIEt<9<@>Te%$D6(Qa^(3;NWK=~v=sQJ?&`=60yMvkDHb(^9z{tHU zIp1xJZ74Q%sTi}mKg3W;ty?KPnDGT61VOWR-|GQRvw$}#`g)4=vSx~<0i2&bY{-m? zhu`$d+wggHzZqsvmT01;Wxk&Ym~j%HKgt$dn75X-8~)%4aCFOovVjus+eQRP zO>P!ejh5X}jsv(9(lDYRkEC8C4?l3E_cEV2A`kNyYCV?q%?kTjYUSh0oL6sS0VG+* zfI|Jy+Mf^CjKa;2u-g{)$dZK^qQj+_tNYPMKaZ|ljngPL zy*7G(;42@nTT0k)S8spqd~H=w+;LWe$Tgv-KO^o%&W11!Uh);dl1o2hpYq=8OnNI( zl>W3fi6fsd>7A#GDW}uhSZ~J7;^cI@S2fEZED)^dXO}i+3QC~*jJdr%Bz4bh+{l_$ zyog0$ysuh`a>c-hU3n;1t5Su0DN+*Vc!~JAXHdDe!?ewkoD2Kndm8rPM-(@9GK*X94UAM6i8_iqryQL;B)85e z{KW-O)x`K^H77x8ZEw$2SDcgZ#5hwYNPd3nba1HRTsEP#9-Ma1L1vTm_=>_X_P(h2 zh-3VYoNsnL<1K+SD?rs7<$2qg5%HW1hr-8pdxHn;{p11WZ1#t@-vUR8d&9R{vMwk) zauCfL?|)+Vp0IvbfHHsc;H}0|IBBxd`YB&miIjKdwN{Lt@pJ$#=zWTV%B6-ibr2b$#rlor%6y5uIu&5k&`cUbHe|r1yCt za3eX!ndFKAm1O+1p;-6pn&s8Zl@z z&4Y8o`?qBC3L(n+RQ%@j&Y>bR_EzD?H66QrPRIZT#(M7E5yC#ah z6u2&rnOK9`a8Q72H}P%Ly6jFr6V*k-S9?aFxKIIs@SRZfxr9 z4^TN!dVF`I0rd*|N!z5`4WcjFD5=}Ske4!FJaJ_iZa0u5rtacG^2!n=bkQNN$zIgx z^0{ps9K+jm-lfwBDGnq|T{b}ny95w%Mh8`Z(JIx3br}3o#3VMV2;yL1rp!9I4W46d zY<06%JeW*{L3-Zna0ap~b8qpdJsTCaM_yuI0LJBkR+GIj95J zy5Xwf<+jx<59%Htl*|qR@C)uIp>`Q$1t!j0!{a1rT zS0hK8&FTI(AW+0nkKv#?x}YM+*d-<_lSLbX6T$G~cFbPSm%lni<6JPb5$5 zXTiC!9v?#p4^4A}_1u-b5D6i%8R%qVvpRL|4@lmYgDbjGw7vhlvT1K1b1H8yO~teP zuH~wFhk?mB2+r=A+Fb)T!%rCv=tb{Oby4KV56anC>wd3iO~o_wVN^2Z)#nK@bbv$hc?AlEg$DL5i| zmLGVNWna$Gwm$vIImDxi6h1+Me0OWt44S6ds*ai5jP--aOX5dz6G@3q$+}Ax=hEf1 z%sl+9$2~6I37?Meq>E>nNS{E>f;o0m9mi^a(K4)sOT6=%=XpF%k(6=*aH)Sl0bwf&lQP@HD zC_>BSM4PKu0;q)M7Q<#~as{`smejI@mn>WBQ)ZZpKJ2*MNLkn#VM+x>OLvCoLra$V52|LbEZrpn_2$$0ch2l4M88^y6E58Xnor>cU|0iZ*&JmMUq~cxljv@2r ztd5cFRdk*m&NGCVazS?$CyM30sjGSC(FFc9Z5Kq5R%kM|7m8D!c@~lcH8*X_i!lI8 z4!8s?nd%QS+xr4Xc;B*{H$nD`!eu*27pIc@OpH5+L_kuPj(H@Nai|yMLtPfFg>eW9 zLbyq)`eG-K={k)CEzQ5l|8xv!Xlbs{{NIH8sebG|wfisb-%on!)x+ndU1mN1x6b>M zJZ`92Pr|HFl~dHqUtYeXtv0K@fB)xiPa~0eKIswTfaFg-xO(mCy{ms8d3(P|Fp0$S zoZufm$hQu1-*YOw&!l30d9_R|nZ%OA@bA3s-~0F<&l9&5I#qFF_t&O!F?Vtb^Ti2P zYkBRm0*Bxs`sIF2lx5{kqnW4%yoiWG;FXH>Cyq#Xv(9JT8VTEoQfxYeMWXY${`q`Y zy4br$M)SG4y6g5-_f{>B=`8SPIJQlK$)s{^N%lw!?s{C6HO=>>VK zmYX|{0ueXrLPbb3l3EHU$PVqu=3;R-jvVI=(!yG#aAg=3H?UX?<1#lYw((qLQ$n}K z*S#k!YDxz}>zasL&M&@J5#V=Srm*t&2HS6GCxvLUm;6BX`ylA?Ck+`5n6aCd}GR{kbB+Qv4FP$@W|u z>_}S4P%o`wdH$=yWEK1O77ev7m!6_be?wK=HC?q&;~QNPJr^8DJ+|!*EG+T5{N_{9 zocGHlgS&R|+l!7G{o93NEKI_s?=?0#?o8O`%4rpi6^t^qXC@1U_sTA~*gP4z%@Irt zxgtqgxO_V^q{fwsBSK~Q2Sp$vh8Or(6+ZW%D9(QNl-?=Jhih|?)xZY)QF>2#>W##Q zVc@ID$>o@@~Xrsa&(Xy;jc(HOE#t4}syUOqlKF@L5fGQ^P1EN5Rj(-ZAtOCAT+ zXgsM>@_NirQn$lAB*~}kFc0@ge9Ad5WAaLJ8TVkYk<9H9RfmOS=j?}@#459(jOS)n zehK3yF%m>(<7oz06t+(=3truBctyk{OY&l&2eDBe=8KTqR9y_9LPd59cUQI)113Xc zV#2+_SnbT|0PXNyXW$pAGey$x?n!rCwBAfx|Dl)!Pv8cao)N;>Y^v=I^5FDk=uf?E zyYQIRZsF!{(>Hlay17sMH*sA))!#f`zb!sPC1cLB6}nhO9KYVLL*qL%Cs(Ui6BRuw zKYca6C5VP+;rfop^tHAQ5lp$sZ368mV&gjRU*saolx`Eu946HzDF>%Xb}?JeD&Q@& z2HvkN>iO9fk2YAF#hF1cCmK+tNX^xKLSgQ<%~>DtURfEZpHWW^0B6>2)`^19peT2E zVG66NJu8kmt{PE1v+{V$?o3mKr$SX6bMh|REUH1JHe2)dw%#;w6kKCrv>ReY;scE? zT{5nu;P8Q|P4`si?^-~JYVFRIlLcSPH3_(tTk^JW0bj|xB}PuSaj3NIgM`a9xcFw7ZY-KhbHwfSOMsxygdNdXidiWj9tT5@A5Oncs1ilxxM{%#0igx)a`ztf` z0^?XMq{@Od8%cX}zlx}~z3oq(3-?$0q?hz7Nfwr|1|2E4z5T*p;y=qVpr?KG!D-<8 zzwuQp0D03tMoYj<^Q)Rup+B>rndUA7_g{%l+k{qs>P{*$!1gGb&&)IMFW2KQ-F||+ zeG=?qaP~~6oN7;=v#WI497lk4jk{LH-P^w(ef}ij6*3?-r#`i2TP!sMHf8kjtS|Vn zw@(~%*YMZ!lFUeYFWfCyMb+!U*zmyamUz2z+KWfc(k@!*jxra$cFSq%Yi<&Ib;=Ylzx(P23_dzcBv$vf`&A*wni6vgX>rJBGc|(%YW?3LJ zED~arx&7Uo?=1Xb&t=Jm7}-FiyiH>v0E!$-G+P&pNxly$6)_^>AOBBeshS7}3d+BI zRjxHnzLF1D+7X#-)$)qy<;gX<%jaQZJO547H%w*xHc<^k&a>Vw(JP6IeK7M*q+ZME zS&GR<3Ac+A@YObY>K0L`LEO#c4Jlm|Ir6oiqH%Dl zn5oW&{c}ZN=b=u0rQJ#8#N?OwMiS8SAZXI~gO(m$a%pMQoRS{h%3mEywxZ*zi3G32U-}e-kW_4!H&XID&x^&&O ziVxTVZJisRVc&d2&FFU+iE3RhVV(ZsYN}ofyu&N!C4N)MSdcjrlDXw_y48pJJ#&@} z4hcGC-x>v(=@I9>GzS}f{+4L4*2mvzM{MYBiGFL@Q)T0x*v4pPw`7|UL2aKWqspK8 zMs%r5lKMHL_A()OA5?q{UJk$sOOsSq#Ju`TwV>`1zsTum6?FfoH0hY4IR2gGDOKz?_RUYaTHgC$MX}ltC6#*L z=zOX5#WKTus3_EuufZ_j6Z(Sx7-bCWj8I5H6~$aSpiYmmVS8_dQh_%1sS}61LCO zh(#(lGIbe@Q3&(?V5ig}+tgsQVgBYGRBGr_saQta)nOSk;5p%@yKd)ABXVr_i*#KY z&b(0Lt#|8uaY^{K{kXDd3Y$BT#?P}X-*&5*X3OKgQKeZXCsJMYzL;yM@?U1_j1%Fxj` z($nMCV3O&2daVskutO=o=;Bl;%R=4A*d6A``J5ekZT^X?Apm0kwb~%lY9te*vU77@ zJil^^p<@^+K99K5qEa%&0BJ^Nqsq{LP1feCMGWprRoiE}M5cFA4SQH0gj7#B`xzz) z?|!*Ge>PXkThGoAof>ynLW7`EDA65Rzt!@D8;D+|P163Bx~(7AJ~1Dnm%*D;zDm9r z$a=Q)gEr>e)Qzn$YQ-gx)G3xgyYe3n*70H|V}@PA;!Ds^*=l0PrbTV7w<;H{rfHKvOW7Z z!Th*#WLdF#N+y5Bp>-3k2Q_IBm}{_aWjNB^4EP#Y%KwSH*=4Eom67Cg@*HQ#xO8PF zmQ#dGqDdi2M8CLYU%IkCK(EYGBg$r>1kE4NFpOa`_2P_q4;rr7{5eb2jAmQCMIAQ1 zlNTrh_!xQKA3V&-5-i-#2vNI9Xcke4pSsUf?{&Feu(lYk4Oh0n|CFT%EJYuZeJOu* zLr0@^L#I;iY4WPqL3W&_YMxrOn3hrHt0QV?2AhvE)pTi_15|0U$h5A0n_zz017uj) zyhMkvuh*(<7LMVEhcxm9ZM2G^ldXjgSf~qBoqtYPxJfj+P}CRa`v;QBIwmX;%XO0+ zpv%}~G1#@JTRB?ln!uDQ+MJUp+N+9^t5)4E`aIzgmFh9VIi1|&Z89CxXz!%~EX-z0 z4Gd5_jyuox0ihb@cJ^6@uXFbT({dLse7(*qakq)I|IuUT(C2hX8UGn8&OSu1$E1q$ z_$KlLbkFr%+c~or!ft~DNQSh?$_^=`B~;tW*KP@q_sR&ahjnY}W8ROrv|`|d2ZD>lzxh)-bY(!k<~8VwMW0dxcMs>R zi{XafLGxT%NqNy+^q(cBh!pG80M|m_dofJm$?y;Q1=*k{zV)F;!LSnywwVS*>6q##0I0!{Ea>4L6DPXlay2KsSJD zs_q;&M2JRxCxrf^vaZ)O=5ls#QSLF9!5HisLM2Gy81s240T;NB$_H^`|1?YfIA9;M zhWd=iUs3@j1ae~k1Vh-41M9crhQ6?HPR;y=Ko~=XN$fp8X9)M?@Sz^wknD%o9-`E7emN#4 zaa*_R`V7UBs*BEHPF3ul?dmfZTP&!z8l+1YV$ZWZJ-vB0Fg48s8hEF3L-QJL=?^Y@ zj*uTL2*bhAc;-0~MHy?`K8o$MJa*Aq%Lvc^4>D6d1ToZ~D_APm_@3WcxQNN4CUm*y z&q3ABxEzDzj`v}1BiKWVNtV~NL5I9EdJIgOOh30;8K1~y0(WJfIxmO>tGLE_@AT5~pJ#nmh{aj#Q|AXUVY}8il}~RT zNB&usihGWaQJJe5UH&Mp<>O1nuw)?~-6pS|A?Lego(xap;6JO}MxAD%ud8H#rf`4J zboz1?cX}gm&@*Iv6Rx;J)wkf06OCGU-3XPZbAl^$E(G0|w@rYn|3yL~efZU!naqNP zJ5$Yx+9=FI!bSu0IY}wIXwt%^O4V%XS0T>hWlbQrgtVR<0o=14o)`s#lTT2M%RwK| ziGN{<8pwWvDqoiP(DSMc!FDld{ zpSu8q=l>gJ|A;L>UL|MbGYs(iDxjV14@GmTMcDwsV%6%9+2Ki0 z@HzSP|CTi?X!GbdPpm1?KXlWU?o*}gbuyQx=Wjugwkuvd=7oheG5 zM9I#BF_jz68=!8Lg@kSob4G7(9!Xaaqjdv*~1OH9Lp|(cS_F>}2=0 z>nU2jm)*a;Kr&!)F}Cy3O(LW6_}xc!i7vh8R?9qh`_dv?%HQKvT8_pg)#-rBopZ7z z)Ey7-UQ-A~W;ov4B}?#wypsS^nvsR{b7k9AP^Foq;{sf0HN@#5P3bp7>eqFb9cIW7l%*`{CG!e*~vAVivn9cQo!KOnI*j!(x17}cVO9H5Cv2O_yzqjbtCNu2ScLZ-7 zHWh}4Vn-uGu*2JO)Wpl=NF_9F9}vIBeb6i?@zYn^)+eJmeKNX5!3r-yyDZ%?V2N*m zH>`u-J(In3mng4yPmM*>&+lZ{#yTil+88CXuB3lv0*x)6Hd^22Qxj#$m%15GxFY$T z;6PNiZCqcUgVBV4-PxINFGSDu&Fnp#t|$}WIROuk>lqon84^Ky`(dsH0VSPn+B3H7 zKT+P6-RMvM3#qeCwB=SW?pE-5k|d^N@VCe~%O2 ze50@c68SW);@vpQB#3sT*(_$ySrcy^udB+H)69EQ&Y8^K1~ETMgxbZ8@u}t4{s_;| zi5(mNUP@l=7b)Wd9Zm(dN#bxC8|}WOn1r=xg?wq29QVi`vT1JRyh+VH|EAkrbHfxt zN(>HC6z1&1<8vlGj=bm#h)g6Uj$DUa_;7+jrb-Mb7miK;KM?cRTok^Z|Or0qcs~Jcqo)&C@l+E zX`HOxbcZQkj>DIFn5{O-BnmxcOJ}>Scixs>R-RQhxC%3 zZ_-8A%g14`%L?$5J9R}n=6eqcdxClnIs-LhV+!5-aQtzR8LehJ67Gy*6&&=_9V&~b z3_0$e)B2^^{-4i6a>jd`G0l9;tP7ztB#EQh8ZB#Ldv;`S?L*;>xo6+QyMfew2^}@| zl`$+iarh9g9*5G&bO<>+8S}AUFtg?EpktU^!j43?BoTA>4 zUC|QnBXDs2ApnsPXmUR8Z#YjMu6>I@oP@x~gg2<9uH$CZqEsYtKccThS8dh=WQj*& zILC?Q-&6ZPI^hSOFaN@oGxZ^`x}Tk~bnzF- zZJ`*^X28^9qKvSqHmk{4kwdK}iZ&l!@JqRj37JmEP!)@Khjm)0pew6m*mf16XQ$8% zrJp(I#zc_=UR7D2K-+y2(qCB=;(Qt%kHrF6zQL&M-|dW}9~-wfT=X~kXOb2HiRVwX zQEyVe_ELVfhNtQ@AC|gXo&#)ghKmDE@2`wptiFd5&sn1oeOlmWRyKwO^E8iugmqi) zy6G05yf_UhYq5?a%=tLk^7MR#>O9YL9c{3OdU6Gz1b>&vt;~{^@BS!ZonG16)9Mq_ zQ_CYdT;DEF-bi`(sLOJRcetTh-jrU^0fT+OAF9O%I`#p{hGqp*djEUXafUC%P-%3L zz1Jg;qc;7`+oqdjPw_tQq0bfTWKSJkIcN6L@KKU4Gh!xpx5nugZeXW@u^WLytS=N@ znc`g^;#Edoj2EC@Zx)>2lIzgi&$O*@#cT!tH|~K{ffA?p81GeJM-+Ug(ZYSx?(CX^ z?(8DJ#ktFMpO~m<%b2MAfFo>Nn%IzjDtnH)Q_+=Hy~9qD@Own*RuKNuHp-=tL;Bsi z(UGB$QQ>7_2nI>ABuAFev4X_E#8;zZ97))AkXOMqeeW#baYFG8GTitw@;85LEstfT z-(e6>h2P=QN@Z!oyl3+GmNr&ac<$7vtwLl^)J+|`*L-~BR((!yQPXXxh@%2g63I#^ z`3$B>EfL=?FgW{Wvg>UA!l)O+_>uDd=G-MPR?g_056|;TmKl%V<|^n&V`P-tyJ}^#V`%AipPMF{CrgMBK}ATR~=*Bwzf;0iJewo2mb2YtJ7*M z^Jm5jX;MSowpdDM1IHKm7`CA>zu6fA>q+qd9)ZA&X4?u6s9Ku8-j;jW0Dl_u1Xbr1 zNeUH7oA0~8ljBmyd0+I0U8{wGqwUiUp}kY{)T5xPf&S?dyz!TuY6T8z*qW75NonnJ z3~Ha6gz;P<1D!5O`9EBn78Or<~m*7Y0^jL#a=o{srQDpVobW!KCAVu zTtIFAxJs#gj7~Mp^}KTKirxLsd>WkPza2^%GPpF>)x9TWHn)VatB{M{q zWj&gg&cK5Q4--0pMik{WAS)8N3IqpzKK!NrI!E**+;Pn<>n_HF=(~2}vm-pQiMuAh zDo@85xw)tZrE@&xp4VtTrSEc9T4=|8!^VYg(&Rg9FMNT=h~V>%e$s8C?y+xBDf0l) zoS|3r1aY0XjM}1C#%D|a3r_KDcRkbA@UYA02^%;@maaG!kDjy0^$iCy^_Hb*(fTFb z=x){0a5!&lhJ{#}7_qyV#r)9Ci$><9Qu#7u^Ugo&fIbDDB1A}^J-*xLespHKel*rW z+&Yu0Z$|J?S&n$kzUk#3@DCDt+aUXxKWOnZ-akxl2(yEXiC65|Ca9r?moPtI6qijJ z2OHpL66S;-F|q)IGD{ulv0PEO1Y1-&b8unr83#`<@Zi&=?f_R(dRm+x0C}7Wq(>0l zWYag4Eed3A=r*xFt()ikAi*vWx%G%x4O+P%xbwC}=vNQ!6Stnl%X$S>+WEii@XI6| z#}1dtTT(1v!r(Uin0gphw4C#w7r{5Y4nSPpU>e_u?s{#T*sN#{sgv4H+; z3K~R{u%gB+o%362yq?iFPv;ChyE>~Q+m&wb>6=zk!`NoK9T|8U`aR~Aw+R>i{p*B= zC;dZqnRQ$_s~-!c@CZ-;^zYT@Tq@3AKE|;%z2cI1{zW-{;p3~XYzD)>CmpXC*bIIo zn(?vEpCxhSvoU}D$g83(^_)({8T#=NTT>L5gxiQpUJ@)EO6 zamX3i{eNSMkt9FL7*`HT#^tN3&OfA#$S)h^bVNR7m!F0J{^f|A{PYUoUyiWltf1NY zV3P8EJ5uo@ZvOErfPW&32PqXYLh7%+3?Rs%LuaUMDXye7Hj_Zw2=5VuMN#S^ACrUGa${&AcvhNUK++P%(TXrm<>>V;@Wm%|Dg+M zIj}V5x&0qp0(ksmIXYX9i^Jk~h{wIvv|A3w@1}mU`SCE(p~+vfrB`b{So`w^j)QU6 zuGWZZRw&yjN3tx%bZMn4?bg0V!Qa72&q#5 zI^!_jQUsr~_~#nlW-0kVDn9S9J%9_Bv?0dgI*?JBznm$4w_MM_as>@ ziV@ORbxyct9LnIGg$Ud*rDKGYpB@E4qhn%9;fQWLLTbC(i!CO3HKTP!|2Kp3cRt%g zBcmB3P|rDe7s00p$;-c2sOE;j$AV`Xm4U+?{v>>2$M8J<6Z{uK3p_Ugb`ubS{tgam zq3KXHr8x@XxWla)E?j*?AFn ze2DvH9)m-6sgnLAOFhZ!Es*YO7BAwy4^O|0COB&MPqM;yg_6ATfaG6IcoFw}82eiu zga0H;6CAheoaB`cWc|9xi%|BF=x_OREb4y0$KbGCjwDnrkoc<{FXFDxpJOdKBZwLE z(>0tLP+>8XV@+8+()D}y`#k*=8u780s!t}2-@Sq<8u-oH+E44aVII@bGq7vX(#k1Y zy9lNhIy72840JuqUOQ{8=E;4k%HmiYDrKu%Ix@0vQgnf->WH*Fljs-L+j4f97EwkUU@nG2Yd z*5XINbDUK$QS{@5yHPT{Zp7U1+-~-y#xPp1$51NO|D5@*J1eO%k=9EKs;;{5AWG(j z8xJ@9KV@n{g;W=mqFQ*}7`fqZyG@cBV`#npGbVOW5wl(X>@2u|(c2M;+rXy3VaW;B zXeM*;O?pL}_&W_Ue!^LzNoymA}4cV8*_@tf|VW;fI$E49an} zS#LWdC>c>rm)#byF_IARsLGb>JCyOWF56G>eCkH~ouB-u3}S4PYPN4U-0*X0{9S?` z+-}EdlQqw483h!CqC6_cT<{ky9c;-Wd~~N0TFap$Qm5bJATKcy-bs6$fC8vny+&hU zccNoXXd#5ye^U{qw!>9F@;Igj&b^a)03P)@95hPMqK%8GUHvin!)ntbU4`{qM9w#t z*2$klp#f}*f12JbPOHTKH9XNh=N;zSBG0<~1Jr|Njj@1#v;H|F;d^I{@HNLT`@!#s zugU$ie*OXV&;MrqvqmDww5g3pzWB|`p6Vte^s+wj3OoVtYx+N-1^^PccAR4C3WR;X~bzWX* zZwdhr!v4Vw=t6f>6o3wPefUvkhNOg>^FNj`JBHa^%taKQ3I*L*3pfg*KHfQzxqF(1 zBgJ0B(qr9m_M;=CD(jX<1-S{BZMs_Y=CMJLiNa>(rVZ8uTL!AgIha1Eu@G9KJa)$= z=GL94j$Y3ypoz;8%mds$*z@r);%M08BnpU-XO-s~ImVnA=vi2sgun4%u&&oW*@ipX zMP077QR~rZn!Sla5Vt}p^wy1hgx5yaM)pqG_Zj!M{Ut?Z_Bx;xqCIB1Bf}>&*qd0A z)|~7-&I0PuLbCcRY8^@+wes))^>y-nrDNKFH-TZ<`LK0@fN-HP?&gPjYkVg8oBJ6T zw|^*%9qB{M)a)%F<2_V{`BF`9tIn%*N2 zQI_|Ka)!Y zzux}2o@+M#nasZ{zYc=sY`m3B%C>&}zbk(kgjd`5>*;3IpUM2Y^6MbLR6&U#hT28~ zNf_C(@y}>jD9+h?%6Oe{AGH?XL%)xb!x0`w?knzh9r@i=xTlaEB)P^pu8@~AICA0J zK6U5&KIJ30wi1ZB5O51YgcZQDVF|GF?G{2nq3FKTUlf@4*G~|kQ?`l2V=sG$Q%0rI zkB@-Of(i3STz7NwMk20_gikBE;b3xsA#Fc1?t!SA@2Bn|E}DEE;WODwJODDd%bfb+ z_k!)*mLuU%(@&=&YdXieV2WeWW5BV3BJFCYL!tKxNH$PrBouecjLbI<2f&Tzf`(P@ zYV6J54N63e42J6&to0>#Na+#5jF~e5R<6T8Hk?;Gp4y92e~w(|+7~&N3<#Bfki++0 zVNWUH^mSlV;2RtRmJG{>bvX7?;;_bL;|Q@X`)_0^j~!pdAvc8fk4_TrnteRIz7}&7 zM0TtdD84ojh>3gZ37IrmI|8z~cdY2`3KN^`dq)Yu%*dNxD{#0^YE12dNSfhpZauC- zz~&fH<9+o1Y41FMn(EfQ4jcnY-gqIcfjwLlg~hNz*s-yToVnt8ts85L%Xa67l`R1*)n~;AQVKldE`OkaH<~imQE4eL9Rj11)h#(n8~kO1GX5}Ul|rahj0uv zf!PNaDNBS6pQowx(U}&)#UqmiYIJgKNdH|>y2=#YWFcHS@{2%?Zmtulli~A!57a49 zAk(=Adpr9*n9WnEjkd;jRH7ifeJgD!;H1Jf%d5XS(Qi}?Cb#+sqOg&QU_)hCXLF+cV1QLWh{8@Pf*sXto#oSyO7!ax6O~;hgf!2mGP0q<5ZyT32-{*UDErkrh|-0k2i{!zeGK4s2c(aV9{ z*vD|0{Wm#El*7RVXx>1+48*(pd#C<9XjdVmq|z0CYRlY;h=_T+-w!t(8OW} zuKszLd0NOK_)TP95W_+B$87bBo9ng)N^z%;e!%`YvdyAInPvzKla>v!lnoO(4dr3g zFGSaa4X|;ipdZ*gObWW~1GZpDqst>jiwfS^cU4ro`Q~0#_pihNxJ6b;Lz_+5t)_eM z>aRrqptwdKo3KaCZxLm#HJyW`8bWNpN0hLYx!3d#3Tpfw!J^^7R_0dIGx&Q1bWl!X zgRRV?=C_DI30Z$}B=hNG3@V2zGB~M~*}Pd6H2~K?_QRb>)t+CDon$?7aSl9V$?!d4 z%X~j$#z~k2$aR$r3hqb~l|>QSw*DEa+qNS688c7vBtV|4aZvDJny4J=pFjYL$F_C3 zKPBVjwFJn0RT~Qao+kPyc)m5Hua$k})bLbqO^J9$=prTOte}D@Gr5%w{Q;ep)0VgM z4yc6MN^@J&w{0~%E!sVnFW^kUC?L~ucBIZ8gdlwWg5!S7Y(B0c`nKfnPa==0=tRFn*5=;%z2*yWm_P52isw zelGoa6$JvtkriRBrl&KwD1g%K;3N$2;%|!!%(=EQ48Dk!3Xu9N9E7=x>B`9_uZT41 zLKLbfu`{)@0A=V;`2+j+e#Sy13rRBbQHke%K}=}I8eg9y`?Zt>dR)mOyiIzx-XE?$ z!#rZkQJ4gWAeYc2r`#7eF0@a1PHj$2&_d`Ebj8{jY0`DR$gG0uAdbOQUeEGf4)^=d z@3a~Cl4SRhD(W*Lg*;!04B9lRn5?l_6jcoVJw9Oo=RCD6;vDRIa-S3Dei+QQtAu?3 zI$Q5@s8%Y7ab8FbzF6I3lPx83R_ZpliFh6{> z|1#3Oa9IyF#HOFl{lFGtWDeRsVgF?$e1V&vXk*Pz=Z3J!m^yS@0rp=;V_HX%!I>a3 z5lkRlqUN|fc?{<;iBS!>?!*PQTrt%-Ny|sX7Y{KdIVC49VVE~ub62wWEy;H9Uu0vs z9ka$dT7VmmJ^u=T-d$T5?kbAbr^@C4DuVrw{~EBjwloYWO48@d z1_A@Yr^o2O0+8=A&r7u1hQRQ$@qq+jqT{~?>@hD&blb+jsI#vFRl!lmpgC`LdRWQzRv7rso!?|z9x)Aj$oCRM4J3*qY8nVd$ z@VwK|IdXrVV4J`?#l01K@XIY&NIuJdXtn{t|Q-tac7 zn`)dH-SS2r`omk!TXN1qcfY^}rxrlTuFcRouhue@Q>M!X*bFfxH#d6n<(G&(a|a35 zK*-C@4W1-IeLP!f<&-E*e^yv8cpmCc=KTK}jhvK2eY{#rP!^dx>tGW^Q!1`v{Ci7o z?oT`X0?}gw4_;tf{dqhCsL`)>MKSbPsr?sN7Qe5-3C<%+3`G9>Ghp0!BP4{I`u^ zE$a;?ibur=%Y*5^!INhQht`Rc_b%S8tLd2)|4OsbbZn%teAKJ9yu{0*Y{TBdWaC#T zca);m`Ja6i*sq@*}lB;oc>PVSomyqFwY-Tc9=3JrEUF^e{bgQ z_M#-xd;FtMXHyuK6)hMj)ZEoZJ4;V@>Yd{TnK|3355Thf?BEbdgy2Hv?Oe`6&fKG6 z33NUh|I}zuf0s#)go8{`JFbM)$9$%KX}u?NSMQo9{VWOHB&FPnbMMUCCIKJA?ZRo@ zhIDtC{}G_9m!IDbX(HJEjbeHGWs~YQWrO_h6V9$jB{@4}I~hAfK9RF2Fv1q4X2G8t z$RyXsI-L;Cu)R&>;}Z^Kn5Cbl;J~d6v@m+`5_H)v^xS#(A#y6H2|<3AB}GxXO_Y@T z07s(NG>{i)@(cdJ5TLOqy+Tw_7zDR??_9Fr*xC1$@g+CLwWpMmd=(b=Vjx@`zg46{ ztT~eW6jt|srNHUcrfvi$lv|H)$KmavZ{0Gm;>;Q3~-cLv^N3B<#DY$#z{ zu$u9H!Yjzrwocrzy5{V7#&0(B1`Q3({vxO99kqz~7^c0e;@sR;-7!pJKU1UJiHVPi ziDCMg!G(+a4*4q@aZ44eZuuTmj9MNNBrs@`P#RUDIcC)T($^@1dM74{n>1EeoIh4; z=(UCBqkVoU&8;DmA@w!8vOH&r}WlIs94+B5PGArJPpOH`NXJMi&}MW~#Q+%Rkhu0WLCvOhE8C zrg);!my*lrHUq;06RYxEPwC=0&3*{SCf6pb^d)?M{{GfXjlC(KX&ju~D5Tga$EjI( z?m<-TXJWlhz%d<$(qzECdYDa&uU~uB300tOkDm?_HEd>EQ^Eb=YnDQmgb z>eqW3mu8}8^!3I8+X~*ZNA>=90>l*|B9#u~QX8i3P7V9z4t0ZDu2fAjGq&3=G`2bv zm$DwglqdlhuL#MK(Rvq*d_5EK~<+1b-jx%2!=OnZz;H{D+H zU7EgbKCAR^v>M%jy@X@s^BY-r2|sZOe+WmvVFT#&d{f^S^3$Ast@K@xr{Kri*eXK~ z(t9-a@h=QX>=R9MfcG|jP##3e6_Wm-Fup|H9>RF*F|}HU!!FS|+0bJ`$B>)j1bp+C z=#-fi;8v!ZtZQ^FI>C8$YYKsC*%ID8U3k2m@nV40mjLVXH2K2LbINZ7leeWmGs=>f zUw#qlJ|7zSF#e#5T>e|(1ig3oKvlpdNk9j~g!mct({R;};IO5RwRy0sSUpSsWhD<@ zDAZ53z093?TcQpo3(mcT{#%9dh$OODixXVyLvk35!3B7lucS=ZSY2m+_SwPZG}um(cY! z?m>Wu@0+n@qVF=@L!X_%e&J!26HE3%v4$X%TTOeb_deUhXcf14BV?yHJD0NFz)gTQ zW%BYZi7uN5$buBS3v$JS>g7o@s;#oRON#Utm$w33qRh*m8XHWh8W@oYbK0(6oVCma zEQ|L9N%BvGoV2+t%=Hm`q42Q`3U{iSBRrvGt(+r15jmTk*Lykl6cSFXJR5S5P2~3` zfe3WjFRn%A*z0B8M1F@$c(cbCC5CA^V6!D5oA5;VFg!u_n@hEeOc3+(*=BUQQs`YG zpStUC7wNVu`ynmW2SwM~I<{RdgunTWrCc87w#;*a_|wckrta84jgzRT(hFm!tMXJ0O5?f$ zXZdm8s3EQDmn6Z(+K}v6mHnAmSAUsBTKc}B&IcB}L#`>Bn6;A#rHZF(`q2c$=Mr|M zLp+=sOVHeegyM{Z(q1m7p)Q0z-`QhYovt=PP;U)167b?$s9{Wy`=k>^*Q@}q&*}Qg z-mWZ*y2jx5@A+;%G101J4lY-e0nCX&wnU)pN=v`Ty>#Vo$@kk9Nf_&^%w28OZje1$ zHHVcGN;Nt!i62MoKDt4QDW>5abn7wL;M#h-3j1M>5P>dRt;~|P5A%Ckd{2wv!F~D_ z?X0^cLs#wb(+sb#=Y_n#-N+>_(B^%$82$bt`fd^|vNDfZk1$zcT->B#Q3ysx@?_V- z#hOu9rLp(fy*3YGY5_Zd8XFmxtrz8>E@2LShbaa6lVrr=+xCQqDb?m*@w|G4@@otz z-*S4a)DL)O@=a?!+O4o(3Gu-TvJ?7j+GsKR^mg^+>Dxj%{6y8)U{m?om(TJX-^CU9 zkR}@%zhEQ#m|-y%MOyT-+?a9b&N+QMEuzzuzU!nHsm1zH!;VV!*jw<r=>{N+B()jG&o=5 z=GSy0XO7zG&VOQ)8+B()Ek{i**IoO3=Y9>))?M^ir=T2cjXwR2un%X9@!;#zc0DTA#GOv&QC+oE94E;kgLoGTSZRe9QUV>V(Pb zVt04ua=*UAE3Mnu%U`nEBcRF$`y$b5ENKYei|)rGV!bFv+vZ+R0d_!_6?KnfiLB=( zMzW7lw>hA<$vhGyKA?0}nG-h_PicH-3>Qozv7xrGgY-?3;ZiH@XQ@^KeW}(NTa~F> zT?bbzANA}q6RlkyEp$!p`ot4}{~kbF`jn(p=i$JrtL+5gbRhZM3(fHk2Oa6bF9e{Z ziR)mNQ?;j`)*ro?uX#q8(SC&m?w#{}Li_Wxg}Kj!6Dykn8A1ZHwmD|)g0@Z6(IHY4 z_tgw?eMk_F@Z#@elM7FoYk@Wh*VokuYLcw2Da$%nJfZAYUn0G9Z6Wh^=kLjdk=Kjq zOCQOGvjEJcN((<@*_w5(X>%4OO@iphzk&^87~I4{g6RZZSw3fncp5wSYVOpOObY}M z20&aFQytZxqAmlI9%GAng1!%}l%CP-LHnr7IfssjYcQwp5gk)Ea~uPV+Wl3V4z24kEP9N&`d}}O7Ji;H z7HBVAdrlYoL`D%s{)cIw5J3f zz#Mj((M9JwzSE3!ZDG^yRZY-A7D=HOT$JQ$4ynUohf^LOH?9b%()QsId6(tWopjsr zJ!iBwEBDgYqb*(0QLAo00FQ5zMETKOPiD~7K7Cy{IbsRx$SzdO2t>7mG$;5%sf6@{ zZqx|fP*|Ew8=}4{1B>>)aZGwwrF2FE^FeZ0A<3=hBvosjN{@e)&WHBizQui2y+X!H z652u-g|*6*=qt{Us!^NArBZpg?sW&#p{G@iK8Vl)2 zRyW2fpnEN|sN&jpG&hW+dx94MU*^&)-wTGaqp8I^U}tm=dis?&jj9z5?>lkZ@HEvu zQ1Y7mM!&F*DBIFK7I%e4Z66I^_JU;2VbV3Q6U(;1*61tmL0M;f${U)#(a`X*bWgvJ zcU8w%XD;}RuFq`gR}Sbg7`yX10aws6RS!8Re<7JOiBIVV)56{>E&8BRvTo>NA2EiztDS$yzp zc61-{yk4R4j;g+*smY?!b)~pW)}CGdoA+?CZfI3*fej3IrxAG7(d^rnquKPXA8*ta z8Ni7G-vl!`f-IrHX&fqcsugw;Q&i>k7))}j`v}b%y>xMc#O|@ zgv>;I-JZzNGqR&6LQ}5zm&PwySCUBgu+($ZJz-8>e4r2!?MAizwYHwpA&)rQO20Jm z>%%aX{spy&TciPxMcLw2Z?QlY@iRhb4JZic`5|{5V+~!!^}aez4+PTSlDcu&s&>eh zM%GqRj(+tY$78L`WY6f$4lCxWI158veA6|+*$Edex&*i zfbhVFtpbk25Q}dDNvgMyN(DMB9+w$R=5~Rw*`h$kdv1CRG$gMRmj=bU!-W)Qu%Xyc?@M9Am0H{mo?)9-hNa9I) zf}h(Rag|m}hhOZcVAL|-hwucOGr7$SynLk*n+Q=2)a=t^I17+ijlZ4E*1b=4L-6^+ z_(watDamSPuJo$cT0Qc$X}T%4?h~Gt0`+8}VO`|tkHv-$M+6Ct@O7`<#Jz9W*I=)E zT?Y6?X2vPL7AjsOBV3on3g+%=JwrTteTN&QcF0x5>DFkHuM%JET(lrK!&*|(Tv_Pd zA7W?LU^LXvpfjaIcvW4V5jiNym&s60caN~<)w9;e2dsn=JUY6@U7_(dti2&=GGtLr zg`ZyC=6xsh?E}yfw6m+`uFF04pNd8QoUcy=zlOcjOvB6+lRXH47#0_euX;)t$#1Ht zm(e8YNOx~oF z*u@x){$R7`NIY_n$k&Q{b(29S*fhVU(Xe1-qu*kLt3z7JAwAv8(+nh*F1=|L+{4fC z);np)QYBXH9i@-dk4h~~-2|aGQCw2Blh$fd1;g^nMWB-5GUHi0vk5d3`cBZhz{$Gt zlcyg~W0ZFMrq(0q$TzbS`0`b!#+mjP+fN87+F}+O-rXDo05+4?r0h>S3X47g=%|q- zkW7cG<;8UORM%cht{HEUraUsF?~~78v8`#|H^eS!>wJHEdcWkU;;ZU{R<&?|l0Fp0 zQ}_|rawWsVwAzOiq=_P6bc?rrJL#@lMw1gU9b$gZdY@fteKa|13Hcse30SS;tN&y}&ZqC1~me9_~Tmi^C+}+%llQ6uc^2qvH zis*PlC=hqm!u?tT-wf;B*$&qm{8n6C)}Upgo=i4!HuCR;tjyyX(weu#pNBG~HvpIb z*KQg}--?^DW0W8u;3D9AIj~R@*MZ;pOwVoNW9K*xQ)Ndk*QI_#f<13H$y>LoWIef~ nLkPJ-A4`Z5b0v*Z_F&6cANtFPi`6?m)pf(id(Y1E2Jim?!Lk1a diff --git a/docs/static/fonts/LatoLatin-Regular.woff2 b/docs/static/fonts/LatoLatin-Regular.woff2 deleted file mode 100644 index a4d084bfb7a5f329b56cf141d14aa5ed99fcabfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43760 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 S6jzx?;5|%#@jgrPNB`Ly5G8{E diff --git a/docs/static/images/align-compaction-output/compaction_output_file_size_compare.png b/docs/static/images/align-compaction-output/compaction_output_file_size_compare.png deleted file mode 100644 index 2ce86fb289df9819a2dfd781e44021c0742b1237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 334304 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